* [PATCH] RPZ: update code to include WEBGUI and additional languages
@ 2025-02-06 16:35 Jon Murphy
2025-02-06 19:35 ` Bernhard Bitsch
2025-02-06 20:13 ` Michael Tremer
0 siblings, 2 replies; 30+ messages in thread
From: Jon Murphy @ 2025-02-06 16:35 UTC (permalink / raw)
To: development
[-- Attachment #1: Type: text/plain, Size: 89678 bytes --]
What is it?
Response Policy Zone (RPZ) is a mechanism to define local policies in a
standardized way and load those policies from external sources.
Bottom line: RPZ allows admins to easily block access to websites via DNS lookup.
RPZ can block websites via categories. Examples include: fake websites, annoying
pop-up ads, newly registered domains, DoH bypass sites, bad "host" services,
maliscious top level domains (e.g., *.zip, *.mov), piracy, gambling, pornography,
and more. RPZ lists come from various RPZ providers and their available
catagories.
This RPZ add-on enables the RPZ functionality by adding a couple lines in a
configuration file. This add-on simply adds configuration files and adds
scripts (config, metrics and sleep) to make RPZ easier for the admin to use.
The RPZ scripts include additional languages: German, Spanish, French, Turkish,
and Italian.
RPZ itself was release in 2010 and has been part of the IPFire build since ~2015.
Why is it needed? What is its value?
- The RPZ concept places this filtering into IPFire, our internet access
gateway, which is (should be) solely used as DNS source of the internal network.
- As most sites use HTTPS it makes it difficult to filter traffic with URL
Filter without also properly configuring conventional (non-transparent)
mode on the proxy. RPZ is a nice replacement for the URL Filter.
- No need to install and maintain an additional device like PiHole or AdBlock
browser extensions on multiple user devices.
- This is an additional layer of protection for users. Less worry someone will
click on something that gets them into trouble. And, saying this with emphasis,
the ability to do it in one place!
- Blocked sites save on unneeded traffic and can lessen the threat of malware
in advertisements
- Logging allows the admin to see the site blocked and take actions
- RPZ will be used at the home, home-office (work from home), schools,
ministerial, and at the office. Device counts are small (2-6) to medium (~80)
to mediam-large (200+).
- RPZ can block ads, popups, phishing, scammers, spyware, malware, annoying
popups, NSFW links, DOH servers, and the usual internet trash.
------------------------------
Change Log for RPZ add-on
rpz-1.0.0-18 on 2025-02-05
- Build for approval & release as IPFire add-on
---
rpz-beta-0.1.18-18.ipfire on 2025-02-01
rpz.cgi:
- new feature: added a mod key to force a unbound restart
rpz-config and rpz-make:
- new feature: added action for unbound restart `rpz-config unbound-restart`
rpz-metrics:
- simple reformatting
- rename far right column from "last update" to "last download"
---
rpz-beta-0.1.17-17.ipfire on 2024-12-09
rpz-make
- bug fix: corrected validation regex for wildcards like: `*.domain.com`
---
rpz-beta-0.1.16-16.ipfire on 2024-11-18
rpz-make
- new feature: updated validation regex
- bug fix: moved validation to beginning of process. Now we validate before
creating config files.
rpz.cgi:
- new feature: use CSS color variables of the main ipfire theme
- bug fix: empty zonefile remarks were stored as “undef” and caused a warning
- bug fix: HTML textarea removes the first empty line in a custom list
- thank you Leo!
---
rpz-beta-0.1.15-15.ipfire on 2024-11-04
rpz.cgi:
- new feature: added new language file for Turkish (thank you Peppe)
rpz-make
- bug fix: corrected empty allow/block list issue. An empty allow/block list
will now remove contents of allow/block.rpz files and remove unneeded
allow/block.conf file. (thank you iptom)
---
rpz-beta-0.1.14-14.ipfire on 2024-10-29
rpz-config:
- bug fix: correct missing rpz extension. `rpz-config list` displayed URL
incorrectly (thank you Bernhard)
rpz.cgi:
- bug fix: remove extra `"` in language files (thank you Bernhard)
- new feature: slightly dim "apply" button when not enabled
---
rpz-beta-0.1.13-13.ipfire on 2024-10-27
- skipped
---
rpz-beta-0.1.12-12.ipfire on 2024-10-21
rpz.cgi:
- new feature: added new language file for French (thank you gw-ipfire)
---
rpz-beta-0.1.11-11.ipfire on 2024-10-18
rpz.cgi:
- new feature: added new language file for Italian (thank you umberto)
- new feature: added new language file for Spanish (thank you Roberto)
---
rpz-beta-0.1.10-10.ipfire on 2024-10-15
rpz-make:
- bug fix: corrected validation error for a custom list entry (thank you siosios)
- e.g., `*.cloudflare-dns.com`
install.sh:
- bug fix: add chown to correct user created files
update.sh:
- bug fix: add chown to correct user created files (thank you siosios)
---
rpz-beta-0.1.9-9.ipfire on 2024-10-08
rpz.cgi:
- new feature: added new language file for German (thank you Leo)
- bug fix: add missing "rpz exitcode 110"
- bug fix: corrected missing RPZ menu item at menu > IPFire
---
rpz-beta-0.1.8-8.ipfire on 2024-10-04
- skipped
---
rpz-beta-0.1.7-7.ipfire on 2024-10-03
All:
- new feature: includes beta version numbers for pakfire package,
instead of only `rpz-1.0.0-1.ipfire`, for each release.
rpz.cgi:
- new feature: added new WebGUI at `rpz.cgi`
- a BIG thank you to Leo Hofmann for all of his work creating the webgui!!
- bug fix: corrected missing RPZ menu item at menu > IPFire
rpz-make:
- new feature: validate entries in allowlist and blocklist
- new feature: add "no-reload" option for WebGUI
rpz-metrics:
- new feature: info can be sorted by name, by hit count, by line count, by
"enabled" list or all lists
backups:
- bug fix: include all files in `/var/ipfire/dns/rpz` directory in backup
update.sh:
- bug fix: corrected ownership for `/var/ipfire/dns/rpz` directory during an
update
Build:
- bug fix: `block.rpz.conf` and `block.rpz` from build. Files to be created
by `rpz-make`
WebGUI and German language file
Contribution-by: Leo-Andres Hofmann <hofmann(a)leo-andres.de>
Spanish language file
Contribution-by: Roberto Peña
Italian language file
Contribution-by: Umberto Parma
French language file
Contribution-by: gw-ipfire
Turkish language file
Contribution-by: Peppe Tech
Contribution-by: Bernhard Bitsch <bbitsch(a)ipfire.org>
Contribution-by: Erik Kapfer <erik.kapfer(a)ipfire.org>
Signed-off-by: Jon Murphy <jon.murphy(a)ipfire.org
---
config/backup/includes/rpz | 4 +
config/cfgroot/manualpages | 1 +
| 6 +
config/rootfiles/common/configroot | 1 +
config/rootfiles/common/web-user-interface | 1 +
config/rootfiles/packages/rpz | 20 +
config/rpz/00-rpz.conf | 10 +
config/rpz/rpz-config | 130 +++
config/rpz/rpz-functions | 85 ++
config/rpz/rpz-make | 203 +++++
config/rpz/rpz-metrics | 170 ++++
config/rpz/rpz-sleep | 58 ++
config/rpz/rpz.de.pl | 30 +
config/rpz/rpz.en.pl | 30 +
config/rpz/rpz.es.pl | 30 +
config/rpz/rpz.fr.pl | 30 +
config/rpz/rpz.it.pl | 30 +
config/rpz/rpz.tr.pl | 30 +
html/cgi-bin/rpz.cgi | 923 +++++++++++++++++++++
lfs/rpz | 96 +++
make.sh | 3 +-
src/paks/rpz/install.sh | 36 +
src/paks/rpz/uninstall.sh | 38 +
src/paks/rpz/update.sh | 52 ++
24 files changed, 2016 insertions(+), 1 deletion(-)
create mode 100644 config/backup/includes/rpz
create mode 100644 config/menu/EX-rpz.menu
create mode 100644 config/rootfiles/packages/rpz
create mode 100644 config/rpz/00-rpz.conf
create mode 100644 config/rpz/rpz-config
create mode 100644 config/rpz/rpz-functions
create mode 100644 config/rpz/rpz-make
create mode 100755 config/rpz/rpz-metrics
create mode 100755 config/rpz/rpz-sleep
create mode 100644 config/rpz/rpz.de.pl
create mode 100644 config/rpz/rpz.en.pl
create mode 100644 config/rpz/rpz.es.pl
create mode 100644 config/rpz/rpz.fr.pl
create mode 100644 config/rpz/rpz.it.pl
create mode 100644 config/rpz/rpz.tr.pl
create mode 100644 html/cgi-bin/rpz.cgi
create mode 100644 lfs/rpz
create mode 100644 src/paks/rpz/install.sh
create mode 100644 src/paks/rpz/uninstall.sh
create mode 100644 src/paks/rpz/update.sh
diff --git a/config/backup/includes/rpz b/config/backup/includes/rpz
new file mode 100644
index 000000000..36513e494
--- /dev/null
+++ b/config/backup/includes/rpz
@@ -0,0 +1,4 @@
+/var/ipfire/dns/rpz/*
+/etc/unbound/zonefiles/allow.rpz
+/etc/unbound/zonefiles/block.rpz
+/etc/unbound/local.d/*rpz.conf
diff --git a/config/cfgroot/manualpages b/config/cfgroot/manualpages
index 1f7e01efc..d3a48c633 100644
--- a/config/cfgroot/manualpages
+++ b/config/cfgroot/manualpages
@@ -70,6 +70,7 @@ pakfire.cgi=configuration/ipfire/pakfire
wlanap.cgi=addons/wireless
tor.cgi=addons/tor
samba.cgi=addons/samba
+rpz.cgi=addons/rpz
# Logs menu
logs.cgi/summary.dat=configuration/logs/summary
--git a/config/menu/EX-rpz.menu b/config/menu/EX-rpz.menu
new file mode 100644
index 000000000..2f4daf410
--- /dev/null
+++ b/config/menu/EX-rpz.menu
@@ -0,0 +1,6 @@
+$subipfire->{'20.rpz'} = {
+ 'caption' => $Lang::tr{'rpz'},
+ 'uri' => '/cgi-bin/rpz.cgi',
+ 'title' => "RPZ",
+ 'enabled' => 1,
+};
diff --git a/config/rootfiles/common/configroot b/config/rootfiles/common/configroot
index 9839eee45..b30d6aae4 100644
--- a/config/rootfiles/common/configroot
+++ b/config/rootfiles/common/configroot
@@ -120,6 +120,7 @@ var/ipfire/menu.d/70-log.menu
#var/ipfire/menu.d/EX-apcupsd.menu
#var/ipfire/menu.d/EX-guardian.menu
#var/ipfire/menu.d/EX-mympd.menu
+#var/ipfire/menu.d/EX-rpz.menu
#var/ipfire/menu.d/EX-samba.menu
#var/ipfire/menu.d/EX-tor.menu
#var/ipfire/menu.d/EX-transmission.menu
diff --git a/config/rootfiles/common/web-user-interface b/config/rootfiles/common/web-user-interface
index 816241dae..e00464076 100644
--- a/config/rootfiles/common/web-user-interface
+++ b/config/rootfiles/common/web-user-interface
@@ -69,6 +69,7 @@ srv/web/ipfire/cgi-bin/proxy.cgi
srv/web/ipfire/cgi-bin/qos.cgi
srv/web/ipfire/cgi-bin/remote.cgi
srv/web/ipfire/cgi-bin/routing.cgi
+#srv/web/ipfire/cgi-bin/rpz.cgi
#srv/web/ipfire/cgi-bin/samba.cgi
srv/web/ipfire/cgi-bin/services.cgi
srv/web/ipfire/cgi-bin/shutdown.cgi
diff --git a/config/rootfiles/packages/rpz b/config/rootfiles/packages/rpz
new file mode 100644
index 000000000..1c8663049
--- /dev/null
+++ b/config/rootfiles/packages/rpz
@@ -0,0 +1,20 @@
+etc/unbound/local.d/00-rpz.conf
+etc/unbound/zonefiles
+etc/unbound/zonefiles/allow.rpz
+usr/sbin/rpz-config
+usr/sbin/rpz-functions
+usr/sbin/rpz-make
+usr/sbin/rpz-metrics
+usr/sbin/rpz-sleep
+var/ipfire/addon-lang/rpz.de.pl
+var/ipfire/addon-lang/rpz.en.pl
+var/ipfire/addon-lang/rpz.es.pl
+var/ipfire/addon-lang/rpz.fr.pl
+var/ipfire/addon-lang/rpz.it.pl
+var/ipfire/addon-lang/rpz.tr.pl
+var/ipfire/backup/addons/includes/rpz
+var/ipfire/dns/rpz
+var/ipfire/dns/rpz/allowlist
+var/ipfire/dns/rpz/blocklist
+var/ipfire/menu.d/EX-rpz.menu
+srv/web/ipfire/cgi-bin/rpz.cgi
diff --git a/config/rpz/00-rpz.conf b/config/rpz/00-rpz.conf
new file mode 100644
index 000000000..f005a4f2e
--- /dev/null
+++ b/config/rpz/00-rpz.conf
@@ -0,0 +1,10 @@
+server:
+ module-config: "respip validator iterator"
+
+rpz:
+ name: allow.rpz
+ zonefile: /etc/unbound/zonefiles/allow.rpz
+ rpz-action-override: passthru
+ rpz-log: yes
+ rpz-log-name: allow
+ rpz-signal-nxdomain-ra: yes
diff --git a/config/rpz/rpz-config b/config/rpz/rpz-config
new file mode 100644
index 000000000..c72d50f9b
--- /dev/null
+++ b/config/rpz/rpz-config
@@ -0,0 +1,130 @@
+#!/bin/bash
+###############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2024-2025 IPFire Team <info(a)ipfire.org> #
+# #
+# 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 <http://www.gnu.org/licenses/>. #
+# #
+###############################################################################
+
+version="2025-01-11 - v44"
+
+############### Functions ###############
+
+source /usr/sbin/rpz-functions
+
+############### Main ###############
+
+tagName="unbound"
+
+rpzAction="${1}" # input RPZ action
+rpzName="${2}" # input RPZ name
+rpzURL="${3}" # input RPZ URL
+rpzOption1="${4}" # input RPZ option #1
+rpzOption2="${5}" # input RPZ option #2
+
+rpzConfig="/etc/unbound/local.d/${rpzName}.rpz.conf" # output zone conf file
+rpzFile="/etc/unbound/zonefiles/${rpzName}.rpz" # output for RPZ file
+
+rpzLog="yes" # log default is yes
+ucReload="yes" # reload default is yes
+
+while [[ $# -gt 0 ]] ; do
+ case "$1" in
+ --no-log ) rpzLog="no" ;;
+ --no-reload ) ucReload="no" ; checkConf="no" ;;
+ esac
+ shift # Shift after checking all the cases to get next option
+done
+
+case "${rpzAction}" in
+ # add new rpz list
+ add )
+ check_name "${rpzName}" # is this a valid name?
+ # does this config already exist? If yes, then exit
+ if [[ -f "${rpzConfig}" ]] ; then
+ msg_log "error: rpz: duplicate - ${rpzConfig} already exists. exit"
+ exit 104
+ fi
+
+ # is this a valid URL?
+ regex='^https://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
+ if ! [[ "${rpzURL}" =~ $regex ]] ; then
+ msg_log "error: rpz: the URL is not valid: \"${rpzURL}\". exit."
+ exit 105
+ fi
+
+ # create the zone config file
+ {
+ echo "rpz:"
+ echo " name: ${rpzName}.rpz"
+ echo " zonefile: ${rpzFile}"
+ echo " url: ${rpzURL}"
+ echo " rpz-action-override: nxdomain"
+ echo " rpz-log: ${rpzLog}"
+ echo " rpz-log-name: ${rpzName}"
+ echo " rpz-signal-nxdomain-ra: yes"
+ } > "${rpzConfig}"
+
+ # set-up zonefile
+ # create an empty rpz file if it does not exist
+ if [[ ! -f "${rpzFile}" ]] ; then
+ touch "${rpzFile}"
+ # unbound requires these settings for rpz files
+ set_permissions "${rpzFile}" "${rpzConfig}"
+ fi
+ ;;
+
+ # trash config file & rpz file
+ remove )
+ if ! [[ -f "${rpzConfig}" ]] ; then
+ msg_log "error: rpz: cannot remove ${rpzConfig}, does not exist. exit"
+ exit 106
+ fi
+
+ msg_log "info: rpz: remove config file & rpz file \"${rpzName}\""
+ rm "${rpzConfig}"
+ rm "${rpzFile}"
+ ;;
+
+ reload )
+ check_unbound_conf "${checkConf}"
+ ;;
+
+ list )
+ awk -F':' '/^\s*name:/{ gsub(/[[:blank:]]|\.rpz/, "",$2) ; NAME=$2 } \
+ /^\s*url:/{ gsub(/[[:blank:]]/, "") ; print NAME"="$2":"$3} ' \
+ /etc/unbound/local.d/*rpz.conf
+ exit
+ ;;
+
+ unbound-restart )
+ check_unbound_conf "${checkConf}"
+ unbound_restart
+ exit
+ ;;
+
+ * )
+ msg_log "error: rpz: missing or incorrect parameter"
+ printf "Usage: $(basename "$0") <ACTION> <NAME> <URL> <OPTION> <OPTION>\n"
+ printf "Version: ${version}\n"
+ exit 108
+ ;;
+
+esac
+
+unbound_control_reload "${ucReload}"
+
+exit
diff --git a/config/rpz/rpz-functions b/config/rpz/rpz-functions
new file mode 100644
index 000000000..ace1d2690
--- /dev/null
+++ b/config/rpz/rpz-functions
@@ -0,0 +1,85 @@
+#!/bin/bash
+###############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
+# #
+# 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 <http://www.gnu.org/licenses/>. #
+# #
+###############################################################################
+
+version="2024-12-10 - v02"
+
+############### Functions ###############
+
+msg_log () {
+ logger --tag "${tagName}" "$*"
+ if tty --silent ; then
+ echo "${tagName}:" "$*"
+ fi
+}
+
+# check for a valid name
+check_name () {
+ local theName="${1}"
+
+ regex='^[a-zA-Z0-9_]+$' # no dash or plus, alpha numeric only
+ regex1='^(allow|block)$' # allow and block are reserved NAMEs
+ if [[ ! "${theName}" =~ $regex ]] || [[ "${theName}" =~ $regex1 ]] ; then
+ msg_log "error: rpz: the NAME is not valid: \"${theName}\". exit."
+ exit 101
+ fi
+}
+
+set_permissions () {
+ chown nobody:nobody "$@"
+ chmod 644 "$@"
+}
+
+check_unbound_conf () {
+ local thecheckConf="${1:-yes}" # check config default is yes
+
+ # check the above config files
+ if [[ "${thecheckConf}" == yes ]] ; then
+ msg_log "info: rpz: check for errors with \"unbound-checkconf\""
+
+ if ! unbound-checkconf ; then
+ msg_log "error: rpz: unbound-checkconf found invalid configuration."
+ msg_log \
+ "error: rpz: In Terminal run the command \"unbound-checkconf\" for more information. exit."
+ exit 102
+ fi
+ fi
+}
+
+unbound_control_reload () {
+ local theReload="${1:-yes}" # reload default is yes
+
+ if [[ "${theReload}" == yes ]] ; then
+ # reload due to the changes
+ msg_log "info: rpz: run \"unbound-control reload\""
+
+ if ! unbound-control reload ; then
+ msg_log "error: rpz: unbound-control reload. exit."
+ exit 109
+ fi
+ fi
+}
+
+unbound_restart () {
+ # restart due to the changes
+ msg_log "info: rpz: run \"unbound restart\""
+
+ /usr/local/bin/unboundctrl restart
+}
diff --git a/config/rpz/rpz-make b/config/rpz/rpz-make
new file mode 100644
index 000000000..927d55170
--- /dev/null
+++ b/config/rpz/rpz-make
@@ -0,0 +1,203 @@
+#!/bin/bash
+###############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2024-2025 IPFire Team <info(a)ipfire.org> #
+# #
+# 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 <http://www.gnu.org/licenses/>. #
+# #
+###############################################################################
+
+version="2025-01-11 - v14"
+
+############### Functions ###############
+
+source /usr/sbin/rpz-functions
+
+# create the config file for allow
+make_allow_config () {
+ local theLog="${1:-yes}" # log default ON
+ local theConfig="/etc/unbound/local.d/00-rpz.conf" # output zone conf file
+ local theList="/var/ipfire/dns/rpz/allowlist" # input custom list of domains
+
+ msg_log "info: rpz: make config file \"00-rpz.conf\""
+
+ echo "server:
+ module-config: \"respip validator iterator\"" > "${theConfig}"
+
+ # does allow list exist?
+ if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
+
+ echo "rpz:
+ name: allow.rpz
+ zonefile: /etc/unbound/zonefiles/allow.rpz
+ rpz-action-override: passthru
+ rpz-log: ${theLog}
+ rpz-log-name: allow
+ rpz-signal-nxdomain-ra: yes" >> "${theConfig}"
+
+ fi
+
+ # set-up zonefile - unbound requires these settings for rpz files
+ set_permissions "${theConfig}"
+}
+
+# create the config file for block
+make_block_config () {
+ local theLog="${1:-yes}" # log default ON
+ local theConfig="/etc/unbound/local.d/block.rpz.conf" # output zone conf file
+ local theList="/var/ipfire/dns/rpz/blocklist" # input custom list of domains
+
+ msg_log "info: rpz: make config file \"block.rpz.conf\""
+
+ # does block list exist?
+ if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
+
+ echo "rpz:
+ name: block.rpz
+ zonefile: /etc/unbound/zonefiles/block.rpz
+ rpz-action-override: nxdomain
+ rpz-log: ${theLog}
+ rpz-log-name: block
+ rpz-signal-nxdomain-ra: yes" > "${theConfig}"
+
+ # set-up zonefile - unbound requires these settings for rpz files
+ set_permissions "${theConfig}"
+ else
+ # no - trash the config file
+ rm --verbose /etc/unbound/local.d/block.rpz.conf
+ fi
+}
+
+# create an RPZ file for allow or block
+make_rpz_file () {
+ local theName="${1}" # allow or block
+ local theAction='.' # the default is nxdomain or block
+ local actionList
+
+ local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
+ local theZonefile="/etc/unbound/zonefiles/${theName}.rpz" # output file for RPZ
+
+ # does a list exist?
+ if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
+
+ # for allow set to passthru
+ [[ "${theName}" == allow ]] && theAction='rpz-passthru.'
+
+ # drop any extra "blanks" and add "CNAME <RPZ action>." to each line
+ actionList=$( awk '{$1=$1};1' "${theList}" |
+ sed "/^[^;].*[[:alnum:]]/ s|$| CNAME ${theAction}|" )
+
+ msg_log "info: rpz: create zonefile for \"${theName}list\""
+
+echo "; Name: ${theName} list
+; Last modified: $(date "+%Y-%m-%d at %H.%M.%S %Z")
+;
+; domains with actions list
+;
+${actionList}" > "${theZonefile}"
+
+ # set-up zonefile - unbound requires these settings for rpz files
+ set_permissions "${theZonefile}"
+ # set-up allow/block list files
+ set_permissions "${theList}"
+ else
+ msg_log "info: rpz: the ${theList} is empty."
+
+ rm --verbose "${theZonefile}" # trash the RPZ file
+ fi
+}
+
+# check if allow/block list is valid
+validate_list () {
+ local theName="${1}" # allow or block
+ local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
+
+ # remove good:
+ # - properly formated domain names with or without leading wildcard
+ # - properly formated top level domain (TLD) names with wildcard
+ # - blank lines and comment lines
+ # remaining lines are considered "bad"
+ bad_lines=$( sed --regexp-extended \
+ '/^(\*\.)?([a-zA-Z0-9](([a-zA-Z0-9\-]){0,61}[a-zA-Z0-9])?\.)+([a-zA-Z]{2,}|xn--[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])$/d ;
+ /^(\*\.)([a-z]{2,61}|xn--[a-z0-9]{1,60})$/d ;
+ /^$/d ; /^;/d' "${theList}" )
+
+ if [[ ! -z "${bad_lines}" ]] ; then
+ msg_log "error: rpz: invalid line(s) in ${theList}."
+ printf "%s\n" "bad line(s): ${bad_lines}"
+ exit 110
+ fi
+}
+
+
+############### Main ###############
+
+tagName="unbound"
+
+rpzName="${1}" # input RPZ name
+
+rpzLog="yes" # log default is yes
+ucReload="yes" # reload default is yes
+
+while [[ $# -gt 0 ]] ; do
+ case "$1" in
+ --no-log ) rpzLog="no" ;;
+ --no-reload ) ucReload="no" ; checkConf="no" ;;
+ esac
+ shift # Shift after checking all the cases to get next option
+done
+
+case "${rpzName}" in
+ # make a new allow or block rpz file
+
+ allow )
+ validate_list 'allow' # is the allowlist valid?
+ make_allow_config "${rpzLog}"
+ make_rpz_file 'allow'
+ ;;
+
+ allowblock )
+ validate_list 'allow' # is the list valid?
+ make_allow_config "${rpzLog}"
+ make_rpz_file 'allow'
+ ;&
+
+ block )
+ validate_list 'block' # is the blocklist valid?
+ make_block_config "${rpzLog}"
+ make_rpz_file 'block'
+ ;;
+
+ reload )
+ check_unbound_conf "${checkConf}"
+ ;;
+
+ unbound-restart )
+ check_unbound_conf "${checkConf}"
+ unbound_restart
+ exit
+ ;;
+
+ * )
+ msg_log "error: rpz: missing or incorrect parameter"
+ printf "Usage: $(basename "$0") <NAME> <OPTION> <OPTION>\n"
+ printf "Version: ${version}\n"
+ exit 108
+ ;;
+esac
+
+unbound_control_reload "${ucReload}"
+
+exit
diff --git a/config/rpz/rpz-metrics b/config/rpz/rpz-metrics
new file mode 100755
index 000000000..4d43e1629
--- /dev/null
+++ b/config/rpz/rpz-metrics
@@ -0,0 +1,170 @@
+#!/bin/bash
+###############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
+# #
+# 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 <http://www.gnu.org/licenses/>. #
+# #
+###############################################################################
+
+version="2025-01-20 - v25"
+
+############### Main ###############
+
+weeks="2" # default to two message logs
+sortBy="name" # default "by name"
+rpzActive="enabled" # default "enabled only"
+
+while [[ $# -gt 0 ]] ; do
+ case "$1" in
+ --by-names | --by-name | name ) sortBy="name" ;;
+
+ --by-hits | --by-hit | hits | hit ) sortBy="hit" ;;
+
+ --by-lines | --by-line | lines | line ) sortBy="line" ;;
+
+ --by-effect ) sortBy="effect" ;;
+
+ --enabled-only ) rpzActive="enabled" ;;
+
+ --active-all | --all | all ) rpzActive="all" ;;
+
+ [0-9] | [0-9][0-9] ) weeks=$1 ;;
+ esac
+ shift # Shift after checking all the cases to get next option
+done
+
+# get the list of message logs for N weeks
+messageLogs=$( find /var/log/messages* -type f | sort --version-sort |
+ head -"${weeks}" )
+
+# get the list of RPZ names & counts from the message log(s)
+rpzNameCount=$( for logf in ${messageLogs} ; do
+ zgrep --text --fixed-strings 'info: rpz: applied' "${logf}" |
+ awk '$10 ~ /\[\w*]/ { print $10 }' ;
+ done | sort | uniq --count )
+
+# flip results and remove brackets `[` and `]`
+rpzNameCount=$( echo "${rpzNameCount}" |
+ awk '{ print $2, $1 }' |
+ sed --regexp-extended 's|^\[(.*)\]|\1|' )
+
+# grab only names
+rpzNames=$( echo "${rpzNameCount}" | awk '{ print $1 }' )
+
+# get list of RPZ files
+rpzFileList=$( find /etc/unbound/zonefiles -type f -iname "*.rpz" )
+
+# get basename of those files
+rpzBaseNames=$( echo "${rpzFileList}" |
+ sed 's|/etc/unbound/zonefiles/||g ; s|\.rpz||g ;' )
+
+# add to rpzNames
+rpzNames="${rpzNames}"$'\n'"${rpzBaseNames}"
+
+# drop duplicate names
+rpzNames=$( echo "${rpzNames}" | sort --unique )
+
+# get line count for each RPZ
+lineCount=$( echo "${rpzFileList}" | xargs wc -l )
+
+# get comment line count and blank line count for each RPZ
+commentCount=$( echo "${rpzFileList}" | xargs grep --count -e "^$" -e "^;" )
+
+# get modified date each RPZ
+modDateList=$( echo "${rpzFileList}" | xargs stat -c '%.10y %n' )
+
+ucListAuthZones=$( unbound-control list_auth_zones )
+
+# get width of RPZ names
+pWidth=$( echo "${rpzNames}" | awk '{ print $1" " }' | wc -L )
+pFormat="%-${pWidth}s %-8s %-8s %8s %12s %12s\n"
+
+# print title line
+printf "${pFormat}" "name" "hits" "active" "lines" " hits/line" "last download"
+printf -- "--------------"
+
+theResults=""
+totalLines=0
+totalHits=0
+while read -r theName
+do
+ printf -- "--" # pretend progress bar
+
+ # is this RPZ list active?
+ theActive="disabled"
+ if grep --quiet "^${theName}\.rpz" <<< "${ucListAuthZones}"
+ then
+ theActive="enabled"
+ else
+ [[ "${rpzActive}" == enabled ]] && continue
+ fi
+
+ # get hit count
+ theHits="0"
+ if output=$( grep "^${theName}\s" <<< "${rpzNameCount}" ) ; then
+ theHits=$( echo "${output}" | awk '{ print $2 }' )
+ totalHits=$(( totalHits + theHits ))
+ fi
+
+ # get line count
+ theLines="n/a"
+ hitsPerLine="0"
+ if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${lineCount}" ) ; then
+ theLines=$( echo "${output}" | awk '{ print $1 }' )
+ totalLines=$(( totalLines + theLines ))
+
+ if [[ "${theLines}" -gt 2 ]] ; then
+ hitsPerLine=$(( 100 * theHits / theLines ))
+ fi
+ fi
+
+ # get modification date
+ theModDate="n/a"
+ if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${modDateList}" ) ; then
+ theModDate=$( echo "${output}" | awk '{ print $1 }' )
+ fi
+
+ # add to results list
+ theResults+="${theName} ${theHits} ${theActive} ${theLines} ${hitsPerLine} ${theModDate}"$'\n'
+
+done <<< "${rpzNames}"
+
+case "${sortBy}" in
+ # sort by "active" then by "name"
+ name) sortArg=(-k3,3r -k1,1) ;;
+
+ # sort by "active" then by "hits" then by "name"
+ hit) sortArg=(-k3,3r -k2,2nr -k1,1) ;;
+
+ # sort by "active" then by "lines" then by "name"
+ line) sortArg=(-k3,3r -k4,4nr -k1,1) ;;
+
+ # sort by "active" then by "effect" then by "name"
+ effect) sortArg=(-k3,3r -k5,5nr -k1,1) ;;
+esac
+
+printf -- "--------------\n"
+# remove blank lines, sort, print as columns
+echo "${theResults}" |
+ awk '!/^[[:space:]]*$/' |
+ sort "${sortArg[@]}" |
+ awk --assign=width="${pWidth}" \
+ '{ printf "%-*s %-8s %-8s %8s %10s %% %12s\n", width, $1, $2, $3, $4, $5, $6 }'
+
+printf "${pFormat}" "" "=======" "" "========" "" ""
+printf "${pFormat}" "Totals -->" "${totalHits}" "" "${totalLines}" "" ""
+
+exit
diff --git a/config/rpz/rpz-sleep b/config/rpz/rpz-sleep
new file mode 100755
index 000000000..dd3603599
--- /dev/null
+++ b/config/rpz/rpz-sleep
@@ -0,0 +1,58 @@
+#!/bin/bash
+###############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
+# #
+# 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 <http://www.gnu.org/licenses/>. #
+# #
+###############################################################################
+
+version="2024-08-16" # v05
+
+############### Functions ###############
+
+# send message to message log
+msg_log () {
+ logger --tag "${tagName}" "$*"
+ if tty --silent ; then
+ echo "${tagName}:" "$*"
+ fi
+}
+
+############### Main ###############
+
+tagName="unbound"
+
+sleepTime="${1:-5m}" # default to sleep for 5m (5 minutes)
+
+zoneList=$( unbound-control list_auth_zones | awk '{print $1}' )
+
+for zone in ${zoneList} ; do
+ printf "disable ${zone}\t"
+ unbound-control rpz_disable "${zone}"
+done
+
+msg_log "info: rpz: disabled all zones for ${sleepTime}"
+
+sleep "${sleepTime}"
+
+for zone in ${zoneList} ; do
+ printf "enable ${zone}\t"
+ unbound-control rpz_enable "${zone}"
+done
+
+msg_log "info: rpz: enabled all zones"
+
+exit
diff --git a/config/rpz/rpz.de.pl b/config/rpz/rpz.de.pl
new file mode 100644
index 000000000..3770c6bb0
--- /dev/null
+++ b/config/rpz/rpz.de.pl
@@ -0,0 +1,30 @@
+# Added for Response Policy Zone (RPZ) add-on
+%tr = (%tr,
+'rpz' => 'Response Policy Zones (RPZ)',
+'rpz apply' => 'Übernehmen',
+'rpz cl allow enable' => 'Benutzerdefinierte Allowlist aktivieren:',
+'rpz cl allow info' => 'Zugelassene Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
+'rpz cl allow' => 'Benutzerdefinierte Allowlist',
+'rpz cl block enable' => 'Benutzerdefinierte Blocklist aktivieren:',
+'rpz cl block info' => 'Gesperrte Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
+'rpz cl block' => 'Benutzerdefinierte Blocklist',
+'rpz cl' => 'Benutzerdefinierte Listen',
+'rpz exitcode 101' => 'Der Name enthält unzulässige Zeichen',
+'rpz exitcode 102' => 'unbound-checkconf hat eine fehlerhafte Konfiguration ermittelt. Führen Sie das Kommando unbound-checkconf auf der Konsole aus, um weitere Informationen zu erhalten.',
+'rpz exitcode 103' => 'Die benutzerdefinierte Allow-/Blocklist ist leer',
+'rpz exitcode 104' => 'Ein Eintrag mit identischem Namen existiert bereits',
+'rpz exitcode 105' => 'Die URL ist ungültig',
+'rpz exitcode 106' => 'Eintrag kann nicht entfernt werden, der Name existiert nicht',
+'rpz exitcode 107' => 'Der Name ist ungültig - nur "allow" oder "block" möglich',
+'rpz exitcode 108' => 'Fehlende oder inkorrekte Parameter',
+'rpz exitcode 109' => 'unbound-control reload ist fehlgeschlagen',
+'rpz exitcode 110' => 'Die benutzerdefinierte Allow-/Blocklist enthält unzulässige Einträge',
+'rpz exitcode 201' => 'Die Anmerkung enthält unzulässige Zeichen',
+'rpz exitcode 202' => 'Ungültiger Eintrag in der benutzerdefinierten Allowlist, Zeile ',
+'rpz exitcode 203' => 'Ungültiger Eintrag in der benutzerdefinierten Blocklist, Zeile ',
+'rpz exitcode 204' => 'Ausgewählter Eintrag existiert nicht: ',
+'rpz zf editor' => 'Zonendatei-Eintrag bearbeiten',
+'rpz zf imported' => '(importiert aus rpz-config)',
+'rpz zf remark info' => 'Erlaubte Zeichen sind a-z, A-Z, 0-9 und Unterstriche',
+'rpz zf' => 'Zonendateien',
+);
diff --git a/config/rpz/rpz.en.pl b/config/rpz/rpz.en.pl
new file mode 100644
index 000000000..0720a8940
--- /dev/null
+++ b/config/rpz/rpz.en.pl
@@ -0,0 +1,30 @@
+# Added for Response Policy Zone (RPZ) add-on
+%tr = (%tr,
+'rpz' => 'Response Policy Zones (RPZ)',
+'rpz apply' => 'Apply',
+'rpz cl allow enable' => 'Enable custom allowlist:',
+'rpz cl allow info' => 'Allowed domains (one per line)<br>Example: domain.com, *.domain.com',
+'rpz cl allow' => 'Custom allowlist',
+'rpz cl block enable' => 'Enable custom blocklist:',
+'rpz cl block info' => 'Blocked domains (one per line)<br>Example: domain.com, *.domain.com',
+'rpz cl block' => 'Custom blocklist',
+'rpz cl' => 'Custom lists',
+'rpz exitcode 101' => 'the NAME is not valid',
+'rpz exitcode 102' => 'unbound-checkconf found invalid configuration. In the Terminal run the command unbound-checkconf for more information',
+'rpz exitcode 103' => 'the allow/block list is empty',
+'rpz exitcode 104' => 'duplicate - NAME already exists',
+'rpz exitcode 105' => 'the URL is not valid',
+'rpz exitcode 106' => 'cannot remove the NAME does not exist',
+'rpz exitcode 107' => 'the NAME is not valid - "allow" or "block" only',
+'rpz exitcode 108' => 'missing or incorrect parameter',
+'rpz exitcode 109' => 'unbound-control reload failed',
+'rpz exitcode 110' => 'custom Allowlist/Blocklist contains invalid entries',
+'rpz exitcode 201' => 'the REMARK is not valid',
+'rpz exitcode 202' => 'invalid entry in allowlist, line ',
+'rpz exitcode 203' => 'invalid entry in blocklist, line ',
+'rpz exitcode 204' => 'Selected entry does not exist: ',
+'rpz zf editor' => 'Edit zonefiles entry',
+'rpz zf imported' => '(imported from rpz-config)',
+'rpz zf remark info' => 'Valid characters are a-z, A-Z, 0-9 and underscore.',
+'rpz zf' => 'Zonefiles',
+);
diff --git a/config/rpz/rpz.es.pl b/config/rpz/rpz.es.pl
new file mode 100644
index 000000000..98628e4aa
--- /dev/null
+++ b/config/rpz/rpz.es.pl
@@ -0,0 +1,30 @@
+# Added for Response Policy Zone (RPZ) add-on
+%tr = (%tr,
+'rpz' => 'Zonas de política de respuesta (RPZ)',
+'rpz apply' => 'Aplicar',
+'rpz cl allow enable' => 'Habilitar la lista blanca personalizada:',
+'rpz cl allow info' => 'Dominio permitido (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
+'rpz cl allow' => 'Lista blanca personalizada',
+'rpz cl block enable' => 'Habilitar la lista negra personalizada:',
+'rpz cl block info' => 'Dominio bloqueado (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
+'rpz cl block' => 'Lista negra personalizada',
+'rpz cl' => 'Lista personalizada',
+'rpz exitcode 101' => 'El NOMBRE no es válido',
+'rpz exitcode 102' => 'unbound-checkconf ha encontrado una configuración no válida. Desde Terminal, ejecute el comando unbound-checkconf para mayor información',
+'rpz exitcode 103' => 'La lista de permitidos/bloqueados está vacía',
+'rpz exitcode 104' => 'duplicado - NOMBRE ya existe',
+'rpz exitcode 105' => 'la URL no es válida',
+'rpz exitcode 106' => 'no es posible eliminar el NOMBRE que no existe',
+'rpz exitcode 107' => 'el NOMBRE no es válido - sólo "permitir" o "bloquear"',
+'rpz exitcode 108' => 'parámetro faltante o incorrecto',
+'rpz exitcode 109' => 'Error al recargar unbound-control',
+'rpz exitcode 110' => 'la Lista blanca/Lista negra personalizada contiene entradas no válidas',
+'rpz exitcode 201' => 'el COMENTARIO no es válido',
+'rpz exitcode 202' => 'entrada no válida en la lista blanca, línea ',
+'rpz exitcode 203' => 'entrada no válida en la lista negra, línea ',
+'rpz exitcode 204' => 'La entrada seleccionada no existe: ',
+'rpz zf editor' => 'Editar la entrada de archivos de zona',
+'rpz zf imported' => '(importado de rpz-config)',
+'rpz zf remark info' => 'Los caracteres válidos son a-z, A-Z, 0-9 y guión bajo',
+'rpz zf' => 'Archivos de zona',
+);
diff --git a/config/rpz/rpz.fr.pl b/config/rpz/rpz.fr.pl
new file mode 100644
index 000000000..f35f3c2d0
--- /dev/null
+++ b/config/rpz/rpz.fr.pl
@@ -0,0 +1,30 @@
+# Added for Response Policy Zone (RPZ) add-on
+%tr = (%tr,
+'rpz' => 'Response Policy Zones (RPZ)',
+'rpz apply' => 'Appliquer',
+'rpz cl allow enable' => 'Activer la liste d\'autorisations personnalisée:',
+'rpz cl allow info' => 'Domaines autorisés (un par ligne)<br>Example: domain.com, *.domain.com',
+'rpz cl allow' => 'Liste d\'autorisations personnalisée',
+'rpz cl block enable' => 'Activer la liste de blocage personnalisée:',
+'rpz cl block info' => 'Domaines bloqués (un par ligne)<br>Example: domain.com, *.domain.com',
+'rpz cl block' => 'liste de blocage personnalisé',
+'rpz cl' => 'Listes personnalisées',
+'rpz exitcode 101' => 'le NOM n\'est pas valide',
+'rpz exitcode 102' => 'unbound-checkconf configuration non valide trouvée. Dans le terminal, exécutez la commande unbound-checkconf pour plus d\'informations',
+'rpz exitcode 103' => 'la liste autoriser/bloquer est vide',
+'rpz exitcode 104' => 'le NOM existe déjà',
+'rpz exitcode 105' => 'L\'URL n\'est pas valide',
+'rpz exitcode 106' => 'impossible de supprimer le NOM n\'existe pas',
+'rpz exitcode 107' => 'le NOM n\'est pas valide - « autoriser » ou « bloquer » seulement',
+'rpz exitcode 108' => 'paramètre manquant ou incorrect',
+'rpz exitcode 109' => 'unbound-control rechargement échoué',
+'rpz exitcode 110' => 'la liste autoriser/bloquer contient des entrées non valides',
+'rpz exitcode 201' => 'la REMARQUE n\'est pas valable',
+'rpz exitcode 202' => 'entrée non valide dans la liste d\'autorisation, ligne ',
+'rpz exitcode 203' => 'entrée non valide dans la liste de blocs, ligne ',
+'rpz exitcode 204' => 'L\'entrée sélectionnée n\'existe pas: ',
+'rpz zf editor' => 'Modifier l\'entrée Fichiers Zone',
+'rpz zf imported' => '(importé de rpz-config)',
+'rpz zf remark info' => 'Les caractères valides sont a-z, A-Z, 0-9 et soulignement.',
+'rpz zf' => 'Fichiers Zone',
+);
diff --git a/config/rpz/rpz.it.pl b/config/rpz/rpz.it.pl
new file mode 100644
index 000000000..ee81605c9
--- /dev/null
+++ b/config/rpz/rpz.it.pl
@@ -0,0 +1,30 @@
+# Added for Response Policy Zone (RPZ) add-on
+%tr = (%tr,
+'rpz' => 'Response Policy Zones (RPZ)',
+'rpz apply' => 'Applica',
+'rpz cl allow enable' => 'Abilita la Whitelist personalizzata:',
+'rpz cl allow info' => 'Domini consentiti (uno per riga)<br>Esempio: domain.com, *.domain.com',
+'rpz cl allow' => 'Whitelist personalizzata',
+'rpz cl block enable' => 'Abilita la Blacklist personalizzata:',
+'rpz cl block info' => 'Domini bloccati (uno per riga)<br>Esempio: domain.com, *.domain.com',
+'rpz cl block' => 'Blacklist personalizzata',
+'rpz cl' => 'Liste personalizzate',
+'rpz exitcode 101' => 'il NOME non è valido',
+'rpz exitcode 102' => 'unbound-checkconf ha trovato una configurazione non valida. Dal Terminale esegui il comando unbound-checkconf per maggiori informazioni',
+'rpz exitcode 103' => 'l\'elenco consentiti/bloccati è vuoto',
+'rpz exitcode 104' => 'duplicato - NAME esiste di già',
+'rpz exitcode 105' => 'l\'URL non è valido',
+'rpz exitcode 106' => 'non è possibile rimuovere il NOME non esiste',
+'rpz exitcode 107' => 'il NOME non è valido - solo "consenti" o "blocca"',
+'rpz exitcode 108' => 'parametro mancante o non corretto',
+'rpz exitcode 109' => 'ricaricamento del controllo non associato non riuscito',
+'rpz exitcode 110' => 'la Whitelist/Blacklist personalizzata contiene voci non valide',
+'rpz exitcode 201' => 'l"OSSERVAZIONE non è valida',
+'rpz exitcode 202' => 'voce non valida nella Whitelist, riga ',
+'rpz exitcode 203' => 'voce non valida nella Blacklist, riga ',
+'rpz exitcode 204' => 'La voce selezionata non esiste: ',
+'rpz zf editor' => 'Modifica la voce dei file di zona',
+'rpz zf imported' => '(importato da rpz-config)',
+'rpz zf remark info' => 'I caratteri validi sono a-z, A-Z, 0-9 e trattino basso',
+'rpz zf' => 'Zonefiles',
+);
diff --git a/config/rpz/rpz.tr.pl b/config/rpz/rpz.tr.pl
new file mode 100644
index 000000000..00226e192
--- /dev/null
+++ b/config/rpz/rpz.tr.pl
@@ -0,0 +1,30 @@
+# I�in eklendi Ayriyeten Response Policy Zone (RPZ)
+%tr = (%tr,
+'rpz' => 'Response Policy Zones (RPZ)',
+'rpz apply' => 'Uygulamak',
+'rpz cl allow enable' => '�zel Etkinlestir allowlist:',
+'rpz cl allow info' => 'Izin verilmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
+'rpz cl allow' => 'Etkinlestir allowlist',
+'rpz cl block enable' => '�zel Etkinlestir blocklist:',
+'rpz cl block info' => 'Engellenmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
+'rpz cl block' => 'Engellenmis blocklist',
+'rpz cl' => 'Engellenmis lists',
+'rpz exitcode 101' => 'NAME ge�erli degil',
+'rpz exitcode 102' => 'unbound-checkconf ge�ersiz yapilandirma bulundu. Terminalde daha fazla bilgi i�in Unbound-checkConf komutunu �alistirin',
+'rpz exitcode 103' => 'allow/block liste bos',
+'rpz exitcode 104' => 'kopyalamak - NAME zaten var',
+'rpz exitcode 105' => 'URL ge�erli degil',
+'rpz exitcode 106' => '�ikarilamiyor NAME yok',
+'rpz exitcode 107' => 'NAME ge�erli degil - "allow" veya "block" yalniz',
+'rpz exitcode 108' => 'Parametre eksik veya yanlis',
+'rpz exitcode 109' => 'unbound-control basarisiz',
+'rpz exitcode 110' => 'Engellenmis Allowlist/Blocklist ge�ersiz girisler i�erir',
+'rpz exitcode 201' => 'REMARK ge�erli degil',
+'rpz exitcode 202' => 'Ge�ersiz giris allowlist, line ',
+'rpz exitcode 203' => 'Ge�ersiz giris blocklist, line ',
+'rpz exitcode 204' => 'Se�ilen giris yok: ',
+'rpz zf editor' => 'yazimlamak zonefiles giris',
+'rpz zf imported' => '(ithal edildi rpz-config)',
+'rpz zf remark info' => 'Ge�erli karakterler a-z, A-Z, 0-9ve alt�st.',
+'rpz zf' => 'Zonefiles',
+);
diff --git a/html/cgi-bin/rpz.cgi b/html/cgi-bin/rpz.cgi
new file mode 100644
index 000000000..a821c92ac
--- /dev/null
+++ b/html/cgi-bin/rpz.cgi
@@ -0,0 +1,923 @@
+#!/usr/bin/perl
+###############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2005-2024 IPFire Team <info(a)ipfire.org> #
+# #
+# 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 <http://www.gnu.org/licenses/>. #
+# #
+###############################################################################
+
+use strict;
+use Scalar::Util qw(looks_like_number);
+
+# debugging
+#use warnings;
+#use CGI::Carp 'fatalsToBrowser';
+#use Data::Dumper;
+
+require '/var/ipfire/general-functions.pl';
+require "${General::swroot}/lang.pl";
+require "${General::swroot}/header.pl";
+
+###--- Extra HTML ---###
+my $extraHead = <<END
+<style>
+ /* alternating row background */
+ .tbl tr:nth-child(2n+2) {
+ background-color: var(--color-light-grey);
+ }
+ .tbl tr:nth-child(2n+3) {
+ background-color: var(--color-grey);
+ }
+ /* text styles */
+ .tbl th:not(:last-child) {
+ text-align: left;
+ }
+ div.right {
+ text-align: right;
+ margin-top: 0.5em;
+ }
+ /* customlist input */
+ textarea.domainlist {
+ margin: 0.5em 0;
+ resize: vertical;
+ min-height: 10em;
+ overflow: auto;
+ white-space: pre;
+ }
+ button[type=submit]:disabled {
+ opacity: 0.6;
+ }
+</style>
+END
+;
+###--- End of extra HTML ---###
+
+
+### Settings ###
+
+# Request DNS service reload after configuration change
+my $RPZ_RELOAD_FLAG = "${General::swroot}/dns/rpz/reload.flag";
+
+# Configuration file for all available zonefiles
+# Format: index, name (unique), enabled (on/off), URL, remark
+my $ZONEFILES_CONF = "${General::swroot}/dns/rpz/zonefiles.conf";
+
+# Configuration file for custom lists
+# IDs: 0=allowlist, 1=blocklist, 2=options (allow/block enabled)
+my $CUSTOMLISTS_CONF = "${General::swroot}/dns/rpz/customlists.conf";
+
+# Export custom lists to rpz-config
+my $RPZ_ALLOWLIST = "${General::swroot}/dns/rpz/allowlist";
+my $RPZ_BLOCKLIST = "${General::swroot}/dns/rpz/blocklist";
+
+
+### Preparation ###
+
+# Create missing config files
+unless(-f $ZONEFILES_CONF) { &General::system('touch', "$ZONEFILES_CONF"); }
+unless(-f $CUSTOMLISTS_CONF) { &General::system('touch', "$CUSTOMLISTS_CONF"); }
+
+
+## Global gui data
+my $errormessage = "";
+
+## Global configuration data
+my %zonefiles = ();
+my %customlists = ();
+&_zonefiles_load();
+&_customlists_load();
+
+## Global CGI form data
+my %cgiparams = ();
+&Header::getcgihash(\%cgiparams);
+
+my $action = $cgiparams{'ACTION'} // 'NONE';
+my $action_key = $cgiparams{'KEY'} // ''; # entry being edited, empty = none/new
+
+
+###--- Process form actions ---###
+
+# Zonefiles action: Check whether the requested entry exists
+if((substr($action, 0, 3) eq 'ZF_') && ($action_key)) {
+ unless(defined $zonefiles{$action_key}) {
+ $errormessage = &_rpz_error_tr(204, $action_key);
+ $action = 'NONE';
+ }
+}
+
+## Perform actions
+if($action eq 'ZF_SAVE') { ## Save new or modified zonefiles entry
+ if(&_action_zf_save()) {
+ $action = 'NONE'; # success, return to main page
+ &_http_prg_redirect();
+ } else {
+ $action = 'ZF_EDIT'; # error occured, keep editing
+ }
+
+} elsif($action eq 'ZF_TOGGLE') { ## Toggle on/off
+ if(&_action_zf_toggle()) {
+ $action = 'NONE';
+ &_http_prg_redirect();
+ }
+
+} elsif($action eq 'ZF_REMOVE') { ## Remove entry
+ if(&_action_zf_remove()) {
+ $action = 'NONE';
+ &_http_prg_redirect();
+ }
+
+} elsif($action eq 'CL_SAVE') { ## Save custom lists
+ if(&_action_cl_save()) {
+ $action = 'NONE';
+ &_http_prg_redirect();
+ }
+
+} elsif($action eq 'RPZ_RELOAD') { ## Reload dns configuration
+ if(&_action_rpz_reload()) {
+ $action = 'NONE';
+ &_http_prg_redirect();
+ }
+
+} elsif($action eq 'UNB_RESTART') { ## Restart unbound service
+ if(&_action_unb_restart()) {
+ $action = 'NONE';
+ &_http_prg_redirect();
+ }
+
+}
+
+
+###--- Start GUI ---###
+
+## Start http output
+&Header::showhttpheaders();
+
+# Start HTML
+&Header::openpage($Lang::tr{'rpz'}, 1, $extraHead);
+&Header::openbigbox('100%', 'left', '');
+
+# Show error messages
+if($errormessage) {
+ &_print_message($errormessage);
+}
+
+# Handle zonefile add/edit mode
+if($action eq "ZF_EDIT") {
+ &_print_zonefile_editor();
+
+ # Finalize page and exit cleanly
+ &Header::closebigbox();
+ &Header::closepage();
+ exit(0);
+}
+
+# Show gui elements
+&_print_zonefiles();
+&_print_customlists();
+&_print_gui_extras();
+
+&Header::closebigbox();
+&Header::closepage();
+
+###--- End of GUI ---###
+
+
+###--- Internal configuration file functions ---###
+
+# Load all available zonefiles from rpz-config and the internal configuration
+sub _zonefiles_load {
+ # Clean start
+ %zonefiles = ();
+
+ # Source 1: Get the currently enabled zonefiles from rpz-config (expected format [name]=[URL])
+ my @enabled_files = &General::system_output('/usr/sbin/rpz-config', 'list');
+
+ foreach my $row (@enabled_files) {
+ chomp($row);
+
+ # Use regex instead of split() to skip non-matching lines
+ next unless($row =~ /^(\w+)=(.+)$/);
+ my ($name, $url) = ($1, $2);
+
+ # Unique names are already guaranteed by rpz-config
+ if(&_rpz_validate_zonefile($name, $url, '', 0) == 0) {
+ # Populate global data hash, mark all found entries as enabled
+ my %entry = ('enabled' => 'on',
+ 'url' => $url,
+ 'remark' => $Lang::tr{'rpz zf imported'});
+
+ $zonefiles{$name} = \%entry;
+ }
+ }
+
+ # Source 2: Get additional data and disabled entries from configuration file
+ my %configured_files = ();
+ &General::readhasharray($ZONEFILES_CONF, \%configured_files);
+
+ foreach my $row (values (%configured_files)) {
+ my ($name, $enabled, $url, $remark) = @$row;
+ $remark //= "";
+
+ next unless($name);
+
+ # Check whether this row belongs to an entry already imported from rpz-config
+ if(defined $zonefiles{$name}) {
+ # Existing entry, only merge additional data
+ $zonefiles{$name}{'remark'} = $remark;
+ } else {
+ # Skip entry if it is marked as enabled but not found by rpz-config. It was then deleted manually
+ if($enabled ne 'on') {
+ # Populate global data hash
+ my %entry = ('enabled' => 'off',
+ 'url' => $url // "",
+ 'remark' => $remark);
+
+ $zonefiles{$name} = \%entry;
+ }
+ }
+ }
+}
+
+# Save internal zonefiles configuration
+sub _zonefiles_save_conf {
+ my $index = 0;
+ my %export = ();
+
+ # Loop trough all zonefiles and create "hasharray" type export
+ foreach my $name (keys %zonefiles) {
+ my @entry = ($name,
+ $zonefiles{$name}{'enabled'},
+ $zonefiles{$name}{'url'},
+ $zonefiles{$name}{'remark'});
+
+ $export{$index++} = \@entry;
+ }
+
+ &General::writehasharray($ZONEFILES_CONF, \%export);
+}
+
+# Load custom lists from rpz-config and the internal configuration
+sub _customlists_load {
+ # Clean start
+ %customlists = ();
+
+ # Load configuration file
+ my %lists_conf = ();
+ &General::readhasharray($CUSTOMLISTS_CONF, \%lists_conf);
+
+ # Get list options, enabled by default to start import
+ $customlists{'allow'}{'enabled'} = $lists_conf{2}[0] // 'on';
+ $customlists{'block'}{'enabled'} = $lists_conf{2}[1] // 'on';
+
+ # Import enabled list from rpz-config, otherwise retrieve stored or empty list from configuration file
+ if($customlists{'allow'}{'enabled'} eq 'on') {
+ &_customlist_import('allow', $RPZ_ALLOWLIST);
+ } else {
+ $customlists{'allow'}{'list'} = $lists_conf{0} // [];
+ }
+ if($customlists{'block'}{'enabled'} eq 'on') {
+ &_customlist_import('block', $RPZ_BLOCKLIST);
+ } else {
+ $customlists{'block'}{'list'} = $lists_conf{1} // [];
+ }
+}
+
+# Save internal custom lists configuration
+sub _customlists_save_conf {
+ my %export = ();
+
+ # Match IDs with import function
+ $export{0} = $customlists{'allow'}{'list'};
+ $export{1} = $customlists{'block'}{'list'};
+ $export{2} = [$customlists{'allow'}{'enabled'}, $customlists{'block'}{'enabled'}];
+
+ &General::writehasharray($CUSTOMLISTS_CONF, \%export);
+}
+
+# Import a custom list from plain file, returns empty list if file is missing
+sub _customlist_import {
+ my ($listname, $filename) = @_;
+ my @list = ();
+
+ # File exists, load and check all lines
+ if(-f $filename) {
+ open(my $FH, '<', $filename) or die "Can't read $filename: $!";
+ while(my $line = <$FH>) {
+ chomp($line);
+ push(@list, $line);
+ }
+ close($FH);
+
+ # Clean up imported data
+ &_rpz_validate_customlist(\@list, 1);
+ }
+
+ $customlists{$listname}{'list'} = \@list;
+}
+
+# Export a custom list to plain file or clear file if list is disabled
+sub _customlist_export {
+ my ($listname, $filename) = @_;
+ return unless(defined $customlists{$listname});
+
+ # Write enabled domain list to file, otherwise save empty file
+ open(my $FH, '>', $filename) or die "Can't write $filename: $!";
+
+ if($customlists{$listname}{'enabled'} eq 'on') {
+ foreach my $line (@{$customlists{$listname}{'list'}}) {
+ print $FH "$line\n";
+ }
+ } else {
+ print $FH "; Note: This list is currently disabled by $ENV{'SCRIPT_NAME'}\n";
+ }
+
+ close($FH);
+}
+
+
+###--- Internal gui functions ---###
+
+# Show simple message box
+sub _print_message {
+ my ($message, $title) = @_;
+ $title ||= $Lang::tr{'error messages'};
+
+ &Header::openbox('100%', 'left', $title);
+ print "<span>$message</span>";
+ &Header::closebox();
+}
+
+# Show all zone files and related gui elements
+sub _print_zonefiles {
+ &Header::openbox('100%', 'left', $Lang::tr{'rpz zf'});
+
+ print <<END
+<table class="tbl" width="100%">
+ <tr>
+ <th>$Lang::tr{'name'}</th>
+ <th>URL</th>
+ <th>$Lang::tr{'remark'}</th>
+ <th colspan="3">$Lang::tr{'action'}</th>
+ </tr>
+END
+;
+
+ # Sort zonefiles by name and loop trough all entries
+ foreach my $name (sort keys %zonefiles) {
+
+ # Toggle button label translation
+ my $toggle_tr = ($zonefiles{$name}{'enabled'} eq 'on') ? $Lang::tr{'click to disable'} : $Lang::tr{'click to enable'};
+
+ print <<END
+ <tr>
+ <td>$name</td>
+ <td>$zonefiles{$name}{'url'}</td>
+ <td>$zonefiles{$name}{'remark'}</td>
+
+ <td align="center" width="5%">
+ <form method="post" action="$ENV{'SCRIPT_NAME'}">
+ <input type="hidden" name="KEY" value="$name">
+ <input type="hidden" name="ACTION" value="ZF_TOGGLE">
+ <input type="image" src="/images/$zonefiles{$name}{'enabled'}.gif" title="$toggle_tr" alt="$toggle_tr">
+ </form>
+ </td>
+ <td align="center" width="5%">
+ <form method="post" action="$ENV{'SCRIPT_NAME'}">
+ <input type="hidden" name="KEY" value="$name">
+ <input type="hidden" name="ACTION" value="ZF_EDIT">
+ <input type="image" src="/images/edit.gif" title="$Lang::tr{'edit'}" alt="$Lang::tr{'edit'}">
+ </form>
+ </td>
+ <td align="center" width="5%">
+ <form method="post" action="$ENV{'SCRIPT_NAME'}">
+ <input type="hidden" name="KEY" value="$name">
+ <input type="hidden" name="ACTION" value="ZF_REMOVE">
+ <input type="image" src="/images/delete.gif" title="$Lang::tr{'remove'}" alt="$Lang::tr{'remove'}">
+ </form>
+ </td>
+ </tr>
+END
+;
+ }
+
+ # Disable reload button if not needed
+ my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
+
+ print <<END
+</table>
+
+<div class="right">
+ <form method="post" action="$ENV{'SCRIPT_NAME'}">
+ <input type="hidden" name="KEY" value="">
+ <button type="submit" name="ACTION" value="ZF_EDIT">$Lang::tr{'add'}</button>
+ <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
+ </form>
+</div>
+END
+;
+
+ &Header::closebox();
+}
+
+# Show zonefiles entry editor
+sub _print_zonefile_editor {
+
+ # Key specified: Edit existing entry
+ if(($action_key) && (defined $zonefiles{$action_key})) {
+ # Load data to be edited, but don't override already present values (allows user to edit after error)
+ $cgiparams{'ZF_NAME'} //= $action_key;
+ $cgiparams{'ZF_URL'} //= $zonefiles{$action_key}{'url'};
+ $cgiparams{'ZF_REMARK'} //= $zonefiles{$action_key}{'remark'};
+ }
+
+ # Fallback to empty form
+ $cgiparams{'ZF_NAME'} //= "";
+ $cgiparams{'ZF_URL'} //= "";
+ $cgiparams{'ZF_REMARK'} //= "";
+
+ &Header::openbox('100%', 'left', $Lang::tr{'rpz zf editor'});
+
+ print <<END
+<form method="post" action="$ENV{'SCRIPT_NAME'}">
+<input type="hidden" name="KEY" value="$action_key">
+<table width="100%">
+ <tr>
+ <td width="20%">$Lang::tr{'name'}: <img src="/blob.gif" alt="*"></td>
+ <td><input type="text" name="ZF_NAME" value="$cgiparams{'ZF_NAME'}" size="40" maxlength="32" title="$Lang::tr{'rpz zf remark info'}" pattern="[a-zA-Z0-9_]{1,32}" required></td>
+ </tr>
+ <tr>
+ <td width="20%">URL: <img src="/blob.gif" alt="*"></td>
+ <td><input type="url" name="ZF_URL" value="$cgiparams{'ZF_URL'}" size="40" maxlength="128" required></td>
+ </tr>
+ <tr>
+ <td width="20%">$Lang::tr{'remark'}:</td>
+ <td><input type="text" name="ZF_REMARK" value="$cgiparams{'ZF_REMARK'}" size="40" maxlength="32"></td>
+ </tr>
+ <tr>
+ <td colspan="2"><hr></td>
+ </tr>
+ <tr>
+ <td width="55%"><img src="/blob.gif" alt="*"> $Lang::tr{'required field'}</td>
+ <td align="right"><button type="submit" name="ACTION" value="ZF_SAVE">$Lang::tr{'save'}</button></td>
+ </tr>
+</table>
+</form>
+
+<div class="right">
+ <form method="post" action="$ENV{'SCRIPT_NAME'}">
+ <button type="submit" name="ACTION" value="NONE">$Lang::tr{'back'}</button>
+ </form>
+</div>
+END
+;
+
+ &Header::closebox();
+}
+
+# Show custom allow/block files and related gui elements
+sub _print_customlists {
+
+ # Load lists from config, unless they are currently being edited
+ if($action ne 'CL_SAVE') {
+ $cgiparams{'ALLOW_LIST'} = join("\n", @{$customlists{'allow'}{'list'}});
+ $cgiparams{'BLOCK_LIST'} = join("\n", @{$customlists{'block'}{'list'}});
+
+ $cgiparams{'ALLOW_ENABLED'} = ($customlists{'allow'}{'enabled'} eq 'on') ? 'on' : undef;
+ $cgiparams{'BLOCK_ENABLED'} = ($customlists{'block'}{'enabled'} eq 'on') ? 'on' : undef;
+ }
+
+ # Fallback to empty form
+ $cgiparams{'ALLOW_LIST'} //= "";
+ $cgiparams{'BLOCK_LIST'} //= "";
+
+ # HTML checkboxes, unchecked = no or undef value in POST data
+ my %checked = ();
+ $checked{'ALLOW_ENABLED'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? " checked" : "";
+ $checked{'BLOCK_ENABLED'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? " checked" : "";
+
+ # Disable reload button if not needed
+ my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
+
+ &Header::openbox('100%', 'left', $Lang::tr{'rpz cl'});
+
+ print <<END
+<form method="post" action="$ENV{'SCRIPT_NAME'}">
+<table width="100%">
+ <tr>
+ <td colspan="2"><b>$Lang::tr{'rpz cl allow'}</b><br>$Lang::tr{'rpz cl allow info'}</td>
+ <td colspan="2"><b>$Lang::tr{'rpz cl block'}</b><br>$Lang::tr{'rpz cl block info'}</td>
+ </tr>
+ <tr>
+ <td colspan="2"><textarea name="ALLOW_LIST" class="domainlist" cols="45">
+$cgiparams{'ALLOW_LIST'}</textarea></td>
+ <td colspan="2"><textarea name="BLOCK_LIST" class="domainlist" cols="45">
+$cgiparams{'BLOCK_LIST'}</textarea></td>
+ </tr>
+ <tr>
+ <td><label for="allow_enabled">$Lang::tr{'rpz cl allow enable'}</label></td>
+ <td width="15%"><input type="checkbox" name="ALLOW_ENABLED" id="allow_enabled"$checked{'ALLOW_ENABLED'}></td>
+ <td><label for="block_enabled">$Lang::tr{'rpz cl block enable'}</label></td>
+ <td width="15%"><input type="checkbox" name="BLOCK_ENABLED" id="block_enabled"$checked{'BLOCK_ENABLED'}></td>
+ </tr>
+ <tr>
+ <td colspan="4"><hr></td>
+ </tr>
+ <tr>
+ <td align="right" colspan="4">
+ <button type="submit" name="ACTION" value="CL_SAVE">$Lang::tr{'save'}</button>
+ <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
+ </td>
+</table>
+</form>
+END
+;
+
+ &Header::closebox();
+}
+
+# Output javascript and extra gui elements
+sub _print_gui_extras {
+
+ # Apply/Restart button modifier key handler
+ if(&_rpz_needs_reload()) {
+ print <<END
+<script>
+ // Commit modifier key handler
+ (function(jq, document) {
+ var keyEventsOn = false; // Keyboard events attached
+ var keyModify = false; // Modifier key pressed
+ var mouseHover = false; // Mouse over commit button
+ var btnModified = false; // Button modified to "Restart"
+
+ // Document-level key events, enable only while cursor is over button
+ function attachKeyEvents() {
+ if(keyEventsOn) {
+ return;
+ }
+ keyEventsOn = true;
+
+ jq(document).on("keydown.rpz", function(event) {
+ if((!keyModify) && event.shiftKey) {
+ keyModify = true;
+ handleModify();
+ }
+ });
+ jq(document).on("keyup.rpz", function(event) {
+ if(keyModify && (!event.shiftKey)) {
+ keyModify = false;
+ handleModify();
+ }
+ });
+ }
+ function removeKeyEvents() {
+ keyModify = false;
+ if(keyEventsOn) {
+ jq(document).off("keydown.rpz keyup.rpz");
+ keyEventsOn = false;
+ }
+ }
+
+ // Attach mouse hover events to commit buttons
+ function attachMouseEvents() {
+ jq("button.commit").on("mouseenter", function(event) {
+ if(!mouseHover) {
+ mouseHover = true;
+ attachKeyEvents();
+ // Handle already pressed key
+ keyModify = !!(event.shiftKey);
+ handleModify();
+ }
+ });
+
+ // Cursor moved away: Disable key listener to minimize events
+ jq("button.commit").on("mouseleave", function() {
+ if(mouseHover) {
+ mouseHover = false;
+ removeKeyEvents();
+ handleModify();
+ }
+ });
+ }
+
+ // Modify commit button
+ function handleModify() {
+ let modify = mouseHover && keyModify;
+ if(btnModified != modify) {
+ if(modify) {
+ jq("button.commit").text("$Lang::tr{'restart'}").val("UNB_RESTART");
+ } else {
+ jq("button.commit").text("$Lang::tr{'rpz apply'}").val("RPZ_RELOAD");
+ }
+ btnModified = modify;
+ }
+ }
+
+ // jQuery DOM ready
+ jq(function() {
+ attachMouseEvents();
+ });
+ })(jQuery, document);
+</script>
+END
+;
+ } # End of modifier key handler
+
+}
+
+
+###--- Internal action processing functions ---###
+
+# Toggle zonefile on/off
+sub _action_zf_toggle {
+ return unless(defined $zonefiles{$action_key});
+
+ my $result = 0;
+ my $enabled = $zonefiles{$action_key}{'enabled'};
+
+ # Perform toggle action
+ if($enabled eq 'on') {
+ $enabled = 'off';
+ $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
+ } else {
+ $enabled = 'on';
+ $result = &General::system('/usr/sbin/rpz-config', 'add', $action_key, $zonefiles{$action_key}{'url'}, '--no-reload');
+ }
+
+ # Check for errors, request service reload on success
+ return unless &_rpz_check_result($result, 1);
+
+ # Save changes
+ $zonefiles{$action_key}{'enabled'} = $enabled;
+ &_zonefiles_save_conf();
+
+ return 1;
+}
+
+# Remove zonefile
+sub _action_zf_remove {
+ return unless(defined $zonefiles{$action_key});
+
+ # Remove from rpz-config if currently active
+ if($zonefiles{$action_key}{'enabled'} eq 'on') {
+ my $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
+
+ # Check for errors, request service reload on success
+ return unless &_rpz_check_result($result, 1);
+ }
+
+ # Remove from data hash and save changes
+ delete $zonefiles{$action_key};
+ &_zonefiles_save_conf();
+
+ # Clear action_key, as the entry is now removed entirely
+ $action_key = "";
+
+ return 1;
+}
+
+# Create or update zonefile entry
+# Returns undef if gui needs to stay in editor mode
+sub _action_zf_save {
+ my $result = 0;
+
+ my $name = $cgiparams{'ZF_NAME'} // "";
+ my $url = $cgiparams{'ZF_URL'} // "";
+ my $remark = $cgiparams{'ZF_REMARK'} // "";
+ my $enabled = 'on'; # Enable new entries by default
+
+ # Note on variables:
+ # name = unique key, will be used to address the entry
+ # action_key = name of the entry being edited, empty for new entry
+
+ # Only check for unique name if it changed
+ # (this also checks new entries because the action_key is empty in this case)
+ $result = &_rpz_validate_zonefile($name, $url, $remark, (lc($name) ne lc($action_key)));
+ return unless &_rpz_check_result($result, 0);
+
+ # Edit existing entry: Determine what was changed
+ if(($action_key) && (defined $zonefiles{$action_key})) {
+ # Name und URL remain unchanged, only save remark and finish
+ if(($name eq $action_key) && ($url eq $zonefiles{$action_key}{'url'})) {
+ $zonefiles{$action_key}{'remark'} = $remark;
+ &_zonefiles_save_conf();
+
+ return 1;
+ }
+
+ # Entry was changed and needs to be recreated, preserve status
+ $enabled = $zonefiles{$action_key}{'enabled'};
+
+ # Remove from rpz-config
+ return unless &_action_zf_remove();
+ }
+
+ # Add new entry to rpz-config
+ if($enabled eq 'on') {
+ $result = &General::system('/usr/sbin/rpz-config', 'add', $name, $url, '--no-reload');
+
+ # Check for errors, request service reload on success
+ return unless &_rpz_check_result($result, 1);
+ }
+
+ # Add to global data hash and save changes
+ my %entry = ('enabled' => $enabled,
+ 'url' => $url,
+ 'remark' => $remark);
+
+ $zonefiles{$name} = \%entry;
+ &_zonefiles_save_conf();
+
+ return 1;
+}
+
+# Save custom lists
+sub _action_cl_save {
+ return unless((defined $cgiparams{'ALLOW_LIST'}) && (defined $cgiparams{'BLOCK_LIST'}));
+
+ my $result = 0;
+
+ my @allowlist = split(/\R/, $cgiparams{'ALLOW_LIST'});
+ my @blocklist = split(/\R/, $cgiparams{'BLOCK_LIST'});
+
+ # Validate lists
+ $result = &_rpz_validate_customlist(\@allowlist);
+ if($result != 0) {
+ $errormessage = &_rpz_error_tr(202, $result);
+ return;
+ }
+ $result = &_rpz_validate_customlist(\@blocklist);
+ if($result != 0) {
+ $errormessage = &_rpz_error_tr(203, $result);
+ return;
+ }
+
+ # Add to global data hash and save changes
+ $customlists{'allow'}{'list'} = \@allowlist;
+ $customlists{'block'}{'list'} = \@blocklist;
+ $customlists{'allow'}{'enabled'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? 'on' : 'off';
+ $customlists{'block'}{'enabled'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? 'on' : 'off';
+
+ &_customlists_save_conf();
+ &_customlist_export('allow', $RPZ_ALLOWLIST);
+ &_customlist_export('block', $RPZ_BLOCKLIST);
+
+ # Make new lists, request service reload on success
+ $result = &General::system('/usr/sbin/rpz-make', 'allowblock', '--no-reload');
+ return unless &_rpz_check_result($result, 1);
+
+ return 1;
+}
+
+# Trigger rpz-config reload
+sub _action_rpz_reload {
+ return 1 unless &_rpz_needs_reload();
+
+ # Immediately clear flag to prevent multiple reloads
+ if(-f $RPZ_RELOAD_FLAG) {
+ unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
+ }
+
+ # Perform reload, recreate reload flag on error to enable retry
+ my $result = &General::system('/usr/sbin/rpz-config', 'reload');
+ if(not &_rpz_check_result($result, 0)) {
+ &General::system('touch', "$RPZ_RELOAD_FLAG");
+ return;
+ }
+
+ return 1;
+}
+
+# Trigger unbound restart
+sub _action_unb_restart {
+ return 1 unless &_rpz_needs_reload();
+
+ # Immediately clear flag to prevent multiple restarts
+ if(-f $RPZ_RELOAD_FLAG) {
+ unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
+ }
+
+ # Perform restart, unboundctrl always exits zero
+ &General::system('/usr/local/bin/unboundctrl', 'restart');
+
+ return 1;
+}
+
+
+###--- Internal rpz-config functions ---###
+
+# Translate rpz-config exitcodes and messages
+# 100-199: rpz-config, 200-299: webgui
+sub _rpz_error_tr {
+ my ($error, $append) = @_;
+ $append //= '';
+
+ # Translate numeric exit codes
+ if(looks_like_number($error)) {
+ if(defined $Lang::tr{"rpz exitcode $error"}) {
+ $error = $Lang::tr{"rpz exitcode $error"};
+ }
+ }
+
+ return "RPZ $Lang::tr{'error'}: $error" . &Header::escape($append);
+}
+
+# Check result of rpz-config system call, request reload on success
+sub _rpz_check_result {
+ my ($result, $request_reload) = @_;
+ $request_reload //= 0;
+
+ # exitcode 0 = success
+ if($result != 0) {
+ $errormessage = &_rpz_error_tr($result);
+ return;
+ }
+
+ # Set reload flag
+ if($request_reload) {
+ &General::system('touch', "$RPZ_RELOAD_FLAG");
+ }
+
+ return 1;
+}
+
+# Test whether reload flag is set
+sub _rpz_needs_reload {
+ return (-f $RPZ_RELOAD_FLAG);
+}
+
+# Validate a zonefile entry, returns rpz-config exitcode on failure. Use _rpz_check_result to verify.
+# unique = check for unique name
+sub _rpz_validate_zonefile {
+ my ($name, $url, $remark, $unique) = @_;
+ $unique //= 1;
+
+ unless($name =~ /^[a-zA-Z0-9_]{1,32}$/) {
+ return 101;
+ }
+ unless($url =~ /^[\w+\.:;\/\\&@#%?=\-~|!]{1,128}$/) {
+ return 105;
+ }
+ unless($remark =~ /^[\w \-()\.:;*\/\\?!&=]{0,32}$/) {
+ return 201;
+ }
+
+ # Check against already existing names
+ if($unique) {
+ foreach my $existing (keys %zonefiles) {
+ if(lc($name) eq lc($existing)) {
+ return 104;
+ }
+ }
+ }
+
+ return 0;
+}
+
+# Validate a custom list, returns number of rejected line on failure. Check for non-zero results.
+# listref = array reference, cleanup = remove invalid entries instead of returning an error
+sub _rpz_validate_customlist {
+ my ($listref, $cleanup) = @_;
+ $cleanup //= 0;
+
+ foreach my $index (reverse 0..$#{$listref}) {
+ my $row = @$listref[$index];
+ next unless($row); # Skip/allow empty lines
+
+ # Reject/remove everything besides wildcard domains and remarks
+ if((not &General::validwildcarddomainname($row)) && (not $row =~ /^;[\w \-()\.:;*\/\\?!&=]*$/)) {
+ unless($cleanup) {
+ # +1 for user friendly line number and to ensure non-zero exitcode
+ return $index + 1;
+ }
+
+ # Remove current row
+ splice(@$listref, $index, 1);
+ }
+ }
+
+ return 0;
+}
+
+
+###--- Internal misc functions ---###
+
+# Send HTTP 303 redirect headers for post/request/get pattern
+# (Must be sent before calling &Header::showhttpheaders())
+sub _http_prg_redirect {
+ my $location = "https://$ENV{'SERVER_NAME'}:$ENV{'SERVER_PORT'}$ENV{'SCRIPT_NAME'}";
+ print "Status: 303 See Other\n";
+ print "Location: $location\n";
+}
diff --git a/lfs/rpz b/lfs/rpz
new file mode 100644
index 000000000..7ddbc38e5
--- /dev/null
+++ b/lfs/rpz
@@ -0,0 +1,96 @@
+###############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
+# #
+# 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 <http://www.gnu.org/licenses/>. #
+# #
+###############################################################################
+
+###############################################################################
+# Definitions
+###############################################################################
+
+include Config
+
+SUMMARY = response policy zone - RPZ reputation system for unbound DNS
+
+VER = 1.0.0
+
+THISAPP = rpz-$(VER)
+DIR_APP = $(DIR_SRC)/$(THISAPP)
+TARGET = $(DIR_INFO)/$(THISAPP)
+
+PROG = rpz
+PAK_VER = 18
+
+DEPS =
+
+SERVICES =
+
+###############################################################################
+# Top-level Rules
+###############################################################################
+
+install : $(TARGET)
+
+check :
+
+download :
+
+b2 :
+
+dist:
+ @$(PAK)
+
+###############################################################################
+# Installation Details
+###############################################################################
+
+$(TARGET) :
+ @$(PREBUILD)
+ @rm -rf $(DIR_APP)
+
+ # RPZ scripts
+ install --verbose --mode=755 \
+ $(DIR_CONF)/rpz/{rpz-config,rpz-metrics,rpz-sleep,rpz-make,rpz-functions} \
+ --target-directory=/usr/sbin
+
+ # RPZ config files
+ mkdir -pv /etc/unbound/local.d
+ install --verbose --mode=644 --owner=nobody --group=nobody \
+ $(DIR_CONF)/rpz/00-rpz.conf \
+ --target-directory=/etc/unbound/local.d
+ chown --verbose --recursive nobody:nobody /etc/unbound/local.d
+
+ # RPZ custom list files for allow and block
+ mkdir -pv /var/ipfire/dns/rpz
+ touch /var/ipfire/dns/rpz/{allowlist,blocklist}
+ chown --verbose --recursive nobody:nobody /var/ipfire/dns/rpz
+
+ # RPZ zone files
+ # create empty RPZ config file to avoid a unbound config error
+ mkdir -pv /etc/unbound/zonefiles
+ touch /etc/unbound/zonefiles/allow.rpz
+ chown --verbose --recursive nobody:nobody /etc/unbound/zonefiles
+
+ # Install addon-specific language-files
+ install --verbose --mode=004 $(DIR_CONF)/rpz/rpz.*.pl \
+ --target-directory=/var/ipfire/addon-lang
+
+ # Install backup definition
+ cp -vf $(DIR_CONF)/backup/includes/rpz /var/ipfire/backup/addons/includes/rpz
+
+ @rm -rf $(DIR_APP)
+ @$(POSTBUILD)
diff --git a/make.sh b/make.sh
index 827ea9e77..a77535b13 100755
--- a/make.sh
+++ b/make.sh
@@ -390,7 +390,7 @@ prepareenv() {
if [ "${free_space}" -lt "${required_space}" ]; then
# Add any consumed space
while read -r consumed_space path; do
- (( free_space += consumed_space / 1024 / 1024 ))
+ (( free_space += consumed_space / 1024 / 1024 ))
done <<< "$(du --summarize --bytes "${BUILD_DIR}" "${IMAGES_DIR}" "${LOG_DIR}" 2>/dev/null)"
fi
@@ -2087,6 +2087,7 @@ build_system() {
lfsmake2 btrfs-progs
lfsmake2 inotify-tools
lfsmake2 grub-btrfs
+ lfsmake2 rpz
lfsmake2 linux
lfsmake2 rtl8812au
diff --git a/src/paks/rpz/install.sh b/src/paks/rpz/install.sh
new file mode 100644
index 000000000..ef99bf742
--- /dev/null
+++ b/src/paks/rpz/install.sh
@@ -0,0 +1,36 @@
+#!/bin/bash
+###############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
+# #
+# 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 <http://www.gnu.org/licenses/>. #
+# #
+###############################################################################
+#
+. /opt/pakfire/lib/functions.sh
+extract_files
+restore_backup ${NAME}
+
+# fix user created files
+chown --verbose --recursive nobody:nobody \
+ /var/ipfire/dns/rpz \
+ /etc/unbound/zonefiles \
+ /etc/unbound/local.d
+
+# Update Language cache
+/usr/local/bin/update-lang-cache
+
+# restart unbound to load config file
+/etc/init.d/unbound restart
diff --git a/src/paks/rpz/uninstall.sh b/src/paks/rpz/uninstall.sh
new file mode 100644
index 000000000..e11427df3
--- /dev/null
+++ b/src/paks/rpz/uninstall.sh
@@ -0,0 +1,38 @@
+#!/bin/bash
+###############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
+# #
+# 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 <http://www.gnu.org/licenses/>. #
+# #
+###############################################################################
+#
+. /opt/pakfire/lib/functions.sh
+
+# stop unbound to delete RPZ conf file
+/etc/init.d/unbound stop
+
+make_backup ${NAME}
+remove_files
+
+# delete rpz config files. Otherwise unbound will throw error:
+# "[1723428668] unbound-control[17117:0] error: connect: Connection refused for 127.0.0.1 port 8953"
+/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
+
+# Update Language cache
+/usr/local/bin/update-lang-cache
+
+# start unbound to load unbound config file
+/etc/init.d/unbound start
diff --git a/src/paks/rpz/update.sh b/src/paks/rpz/update.sh
new file mode 100644
index 000000000..9bc340bc6
--- /dev/null
+++ b/src/paks/rpz/update.sh
@@ -0,0 +1,52 @@
+#!/bin/bash
+###############################################################################
+# #
+# IPFire.org - A linux based firewall #
+# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
+# #
+# 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 <http://www.gnu.org/licenses/>. #
+# #
+###############################################################################
+#
+. /opt/pakfire/lib/functions.sh
+
+# from update.sh
+extract_backup_includes
+
+# stop unbound to delete RPZ conf file
+/etc/init.d/unbound stop
+
+# from uninstall.sh
+make_backup ${NAME}
+remove_files
+
+# delete rpz config files. Otherwise unbound will throw error:
+# "unbound-control[nn:0] error: connect: Connection refused for 127.0.0.1 port 8953"
+/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
+
+# from install.sh
+extract_files
+restore_backup ${NAME}
+
+# fix user created files
+chown --verbose --recursive nobody:nobody \
+ /var/ipfire/dns/rpz \
+ /etc/unbound/zonefiles \
+ /etc/unbound/local.d
+
+# Update Language cache
+/usr/local/bin/update-lang-cache
+
+# restart unbound to load config files
+/etc/init.d/unbound start
--
2.39.5
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
2025-02-06 16:35 [PATCH] RPZ: update code to include WEBGUI and additional languages Jon Murphy
@ 2025-02-06 19:35 ` Bernhard Bitsch
2025-02-06 20:13 ` Michael Tremer
1 sibling, 0 replies; 30+ messages in thread
From: Bernhard Bitsch @ 2025-02-06 19:35 UTC (permalink / raw)
To: development
[-- Attachment #1: Type: text/plain, Size: 95933 bytes --]
Tested-by: Bernhard Bitsch <bbitsch(a)ipfire.org>
I am one of the first testers of this concept and addon.
Thx Jon for bringing up this topic and doing the work to make it running.
Some comments about this functionality:
- From time to time there are discussions about DNS filters like PiHole
in the community. It is a bit complicated to define the data path of DNS
requests going entirely through the PiHole device.
- One solution would be to integrate the filter into IPFire. Don't know
about the effort to do this.
- The RPZ mechanism is integrated in unbound, our standard DNS 'server'.
Unbound is built with RPZ support, yet.
- The lists for RPZ filtering are auto-updated using SOA records.
- The 'Hagezi lists' ( link in Jon's wiki article ) seem to be well
maintained and updated daily. It is necessary to examine these by a
greater amount of IPFire users. Especially we should determine the
amount of extra memory usage for the lists.
- Jon extends the functionality by local allow and block lists. It isn't
clear enough, yet, how these can effective integrated in the RPZ
mechanism. Is it sufficient to 'reload' the unbound configuration or is
it necessary to 'restart' unbound.
Am 06.02.2025 um 17:35 schrieb Jon Murphy:
> What is it?
> Response Policy Zone (RPZ) is a mechanism to define local policies in a
> standardized way and load those policies from external sources.
> Bottom line: RPZ allows admins to easily block access to websites via DNS lookup.
>
> RPZ can block websites via categories. Examples include: fake websites, annoying
> pop-up ads, newly registered domains, DoH bypass sites, bad "host" services,
> maliscious top level domains (e.g., *.zip, *.mov), piracy, gambling, pornography,
> and more. RPZ lists come from various RPZ providers and their available
> catagories.
>
> This RPZ add-on enables the RPZ functionality by adding a couple lines in a
> configuration file. This add-on simply adds configuration files and adds
> scripts (config, metrics and sleep) to make RPZ easier for the admin to use.
>
> The RPZ scripts include additional languages: German, Spanish, French, Turkish,
> and Italian.
>
> RPZ itself was release in 2010 and has been part of the IPFire build since ~2015.
>
> Why is it needed? What is its value?
>
> - The RPZ concept places this filtering into IPFire, our internet access
> gateway, which is (should be) solely used as DNS source of the internal network.
>
> - As most sites use HTTPS it makes it difficult to filter traffic with URL
> Filter without also properly configuring conventional (non-transparent)
> mode on the proxy. RPZ is a nice replacement for the URL Filter.
>
> - No need to install and maintain an additional device like PiHole or AdBlock
> browser extensions on multiple user devices.
>
> - This is an additional layer of protection for users. Less worry someone will
> click on something that gets them into trouble. And, saying this with emphasis,
> the ability to do it in one place!
>
> - Blocked sites save on unneeded traffic and can lessen the threat of malware
> in advertisements
>
> - Logging allows the admin to see the site blocked and take actions
>
> - RPZ will be used at the home, home-office (work from home), schools,
> ministerial, and at the office. Device counts are small (2-6) to medium (~80)
> to mediam-large (200+).
>
> - RPZ can block ads, popups, phishing, scammers, spyware, malware, annoying
> popups, NSFW links, DOH servers, and the usual internet trash.
>
> ------------------------------
>
> Change Log for RPZ add-on
>
> rpz-1.0.0-18 on 2025-02-05
> - Build for approval & release as IPFire add-on
>
> ---
>
> rpz-beta-0.1.18-18.ipfire on 2025-02-01
> rpz.cgi:
> - new feature: added a mod key to force a unbound restart
>
> rpz-config and rpz-make:
> - new feature: added action for unbound restart `rpz-config unbound-restart`
>
> rpz-metrics:
> - simple reformatting
> - rename far right column from "last update" to "last download"
>
> ---
>
> rpz-beta-0.1.17-17.ipfire on 2024-12-09
> rpz-make
> - bug fix: corrected validation regex for wildcards like: `*.domain.com`
>
> ---
>
> rpz-beta-0.1.16-16.ipfire on 2024-11-18
> rpz-make
> - new feature: updated validation regex
> - bug fix: moved validation to beginning of process. Now we validate before
> creating config files.
>
> rpz.cgi:
> - new feature: use CSS color variables of the main ipfire theme
> - bug fix: empty zonefile remarks were stored as “undef” and caused a warning
> - bug fix: HTML textarea removes the first empty line in a custom list
> - thank you Leo!
>
> ---
>
> rpz-beta-0.1.15-15.ipfire on 2024-11-04
> rpz.cgi:
> - new feature: added new language file for Turkish (thank you Peppe)
>
> rpz-make
> - bug fix: corrected empty allow/block list issue. An empty allow/block list
> will now remove contents of allow/block.rpz files and remove unneeded
> allow/block.conf file. (thank you iptom)
>
> ---
>
> rpz-beta-0.1.14-14.ipfire on 2024-10-29
> rpz-config:
> - bug fix: correct missing rpz extension. `rpz-config list` displayed URL
> incorrectly (thank you Bernhard)
>
> rpz.cgi:
> - bug fix: remove extra `"` in language files (thank you Bernhard)
> - new feature: slightly dim "apply" button when not enabled
>
> ---
>
> rpz-beta-0.1.13-13.ipfire on 2024-10-27
> - skipped
>
> ---
>
> rpz-beta-0.1.12-12.ipfire on 2024-10-21
> rpz.cgi:
> - new feature: added new language file for French (thank you gw-ipfire)
>
> ---
>
> rpz-beta-0.1.11-11.ipfire on 2024-10-18
> rpz.cgi:
> - new feature: added new language file for Italian (thank you umberto)
> - new feature: added new language file for Spanish (thank you Roberto)
>
> ---
>
> rpz-beta-0.1.10-10.ipfire on 2024-10-15
> rpz-make:
> - bug fix: corrected validation error for a custom list entry (thank you siosios)
> - e.g., `*.cloudflare-dns.com`
>
> install.sh:
> - bug fix: add chown to correct user created files
>
> update.sh:
> - bug fix: add chown to correct user created files (thank you siosios)
>
> ---
>
> rpz-beta-0.1.9-9.ipfire on 2024-10-08
> rpz.cgi:
> - new feature: added new language file for German (thank you Leo)
> - bug fix: add missing "rpz exitcode 110"
> - bug fix: corrected missing RPZ menu item at menu > IPFire
>
> ---
>
> rpz-beta-0.1.8-8.ipfire on 2024-10-04
> - skipped
>
> ---
>
> rpz-beta-0.1.7-7.ipfire on 2024-10-03
> All:
> - new feature: includes beta version numbers for pakfire package,
> instead of only `rpz-1.0.0-1.ipfire`, for each release.
>
> rpz.cgi:
> - new feature: added new WebGUI at `rpz.cgi`
> - a BIG thank you to Leo Hofmann for all of his work creating the webgui!!
> - bug fix: corrected missing RPZ menu item at menu > IPFire
>
> rpz-make:
> - new feature: validate entries in allowlist and blocklist
> - new feature: add "no-reload" option for WebGUI
>
> rpz-metrics:
> - new feature: info can be sorted by name, by hit count, by line count, by
> "enabled" list or all lists
>
> backups:
> - bug fix: include all files in `/var/ipfire/dns/rpz` directory in backup
>
> update.sh:
> - bug fix: corrected ownership for `/var/ipfire/dns/rpz` directory during an
> update
>
> Build:
> - bug fix: `block.rpz.conf` and `block.rpz` from build. Files to be created
> by `rpz-make`
>
> WebGUI and German language file
> Contribution-by: Leo-Andres Hofmann <hofmann(a)leo-andres.de>
>
> Spanish language file
> Contribution-by: Roberto Peña
>
> Italian language file
> Contribution-by: Umberto Parma
>
> French language file
> Contribution-by: gw-ipfire
>
> Turkish language file
> Contribution-by: Peppe Tech
>
> Contribution-by: Bernhard Bitsch <bbitsch(a)ipfire.org>
> Contribution-by: Erik Kapfer <erik.kapfer(a)ipfire.org>
> Signed-off-by: Jon Murphy <jon.murphy(a)ipfire.org
> ---
> config/backup/includes/rpz | 4 +
> config/cfgroot/manualpages | 1 +
> config/menu/EX-rpz.menu | 6 +
> config/rootfiles/common/configroot | 1 +
> config/rootfiles/common/web-user-interface | 1 +
> config/rootfiles/packages/rpz | 20 +
> config/rpz/00-rpz.conf | 10 +
> config/rpz/rpz-config | 130 +++
> config/rpz/rpz-functions | 85 ++
> config/rpz/rpz-make | 203 +++++
> config/rpz/rpz-metrics | 170 ++++
> config/rpz/rpz-sleep | 58 ++
> config/rpz/rpz.de.pl | 30 +
> config/rpz/rpz.en.pl | 30 +
> config/rpz/rpz.es.pl | 30 +
> config/rpz/rpz.fr.pl | 30 +
> config/rpz/rpz.it.pl | 30 +
> config/rpz/rpz.tr.pl | 30 +
> html/cgi-bin/rpz.cgi | 923 +++++++++++++++++++++
> lfs/rpz | 96 +++
> make.sh | 3 +-
> src/paks/rpz/install.sh | 36 +
> src/paks/rpz/uninstall.sh | 38 +
> src/paks/rpz/update.sh | 52 ++
> 24 files changed, 2016 insertions(+), 1 deletion(-)
> create mode 100644 config/backup/includes/rpz
> create mode 100644 config/menu/EX-rpz.menu
> create mode 100644 config/rootfiles/packages/rpz
> create mode 100644 config/rpz/00-rpz.conf
> create mode 100644 config/rpz/rpz-config
> create mode 100644 config/rpz/rpz-functions
> create mode 100644 config/rpz/rpz-make
> create mode 100755 config/rpz/rpz-metrics
> create mode 100755 config/rpz/rpz-sleep
> create mode 100644 config/rpz/rpz.de.pl
> create mode 100644 config/rpz/rpz.en.pl
> create mode 100644 config/rpz/rpz.es.pl
> create mode 100644 config/rpz/rpz.fr.pl
> create mode 100644 config/rpz/rpz.it.pl
> create mode 100644 config/rpz/rpz.tr.pl
> create mode 100644 html/cgi-bin/rpz.cgi
> create mode 100644 lfs/rpz
> create mode 100644 src/paks/rpz/install.sh
> create mode 100644 src/paks/rpz/uninstall.sh
> create mode 100644 src/paks/rpz/update.sh
>
> diff --git a/config/backup/includes/rpz b/config/backup/includes/rpz
> new file mode 100644
> index 000000000..36513e494
> --- /dev/null
> +++ b/config/backup/includes/rpz
> @@ -0,0 +1,4 @@
> +/var/ipfire/dns/rpz/*
> +/etc/unbound/zonefiles/allow.rpz
> +/etc/unbound/zonefiles/block.rpz
> +/etc/unbound/local.d/*rpz.conf
> diff --git a/config/cfgroot/manualpages b/config/cfgroot/manualpages
> index 1f7e01efc..d3a48c633 100644
> --- a/config/cfgroot/manualpages
> +++ b/config/cfgroot/manualpages
> @@ -70,6 +70,7 @@ pakfire.cgi=configuration/ipfire/pakfire
> wlanap.cgi=addons/wireless
> tor.cgi=addons/tor
> samba.cgi=addons/samba
> +rpz.cgi=addons/rpz
>
> # Logs menu
> logs.cgi/summary.dat=configuration/logs/summary
> diff --git a/config/menu/EX-rpz.menu b/config/menu/EX-rpz.menu
> new file mode 100644
> index 000000000..2f4daf410
> --- /dev/null
> +++ b/config/menu/EX-rpz.menu
> @@ -0,0 +1,6 @@
> +$subipfire->{'20.rpz'} = {
> + 'caption' => $Lang::tr{'rpz'},
> + 'uri' => '/cgi-bin/rpz.cgi',
> + 'title' => "RPZ",
> + 'enabled' => 1,
> +};
> diff --git a/config/rootfiles/common/configroot b/config/rootfiles/common/configroot
> index 9839eee45..b30d6aae4 100644
> --- a/config/rootfiles/common/configroot
> +++ b/config/rootfiles/common/configroot
> @@ -120,6 +120,7 @@ var/ipfire/menu.d/70-log.menu
> #var/ipfire/menu.d/EX-apcupsd.menu
> #var/ipfire/menu.d/EX-guardian.menu
> #var/ipfire/menu.d/EX-mympd.menu
> +#var/ipfire/menu.d/EX-rpz.menu
> #var/ipfire/menu.d/EX-samba.menu
> #var/ipfire/menu.d/EX-tor.menu
> #var/ipfire/menu.d/EX-transmission.menu
> diff --git a/config/rootfiles/common/web-user-interface b/config/rootfiles/common/web-user-interface
> index 816241dae..e00464076 100644
> --- a/config/rootfiles/common/web-user-interface
> +++ b/config/rootfiles/common/web-user-interface
> @@ -69,6 +69,7 @@ srv/web/ipfire/cgi-bin/proxy.cgi
> srv/web/ipfire/cgi-bin/qos.cgi
> srv/web/ipfire/cgi-bin/remote.cgi
> srv/web/ipfire/cgi-bin/routing.cgi
> +#srv/web/ipfire/cgi-bin/rpz.cgi
> #srv/web/ipfire/cgi-bin/samba.cgi
> srv/web/ipfire/cgi-bin/services.cgi
> srv/web/ipfire/cgi-bin/shutdown.cgi
> diff --git a/config/rootfiles/packages/rpz b/config/rootfiles/packages/rpz
> new file mode 100644
> index 000000000..1c8663049
> --- /dev/null
> +++ b/config/rootfiles/packages/rpz
> @@ -0,0 +1,20 @@
> +etc/unbound/local.d/00-rpz.conf
> +etc/unbound/zonefiles
> +etc/unbound/zonefiles/allow.rpz
> +usr/sbin/rpz-config
> +usr/sbin/rpz-functions
> +usr/sbin/rpz-make
> +usr/sbin/rpz-metrics
> +usr/sbin/rpz-sleep
> +var/ipfire/addon-lang/rpz.de.pl
> +var/ipfire/addon-lang/rpz.en.pl
> +var/ipfire/addon-lang/rpz.es.pl
> +var/ipfire/addon-lang/rpz.fr.pl
> +var/ipfire/addon-lang/rpz.it.pl
> +var/ipfire/addon-lang/rpz.tr.pl
> +var/ipfire/backup/addons/includes/rpz
> +var/ipfire/dns/rpz
> +var/ipfire/dns/rpz/allowlist
> +var/ipfire/dns/rpz/blocklist
> +var/ipfire/menu.d/EX-rpz.menu
> +srv/web/ipfire/cgi-bin/rpz.cgi
> diff --git a/config/rpz/00-rpz.conf b/config/rpz/00-rpz.conf
> new file mode 100644
> index 000000000..f005a4f2e
> --- /dev/null
> +++ b/config/rpz/00-rpz.conf
> @@ -0,0 +1,10 @@
> +server:
> + module-config: "respip validator iterator"
> +
> +rpz:
> + name: allow.rpz
> + zonefile: /etc/unbound/zonefiles/allow.rpz
> + rpz-action-override: passthru
> + rpz-log: yes
> + rpz-log-name: allow
> + rpz-signal-nxdomain-ra: yes
> diff --git a/config/rpz/rpz-config b/config/rpz/rpz-config
> new file mode 100644
> index 000000000..c72d50f9b
> --- /dev/null
> +++ b/config/rpz/rpz-config
> @@ -0,0 +1,130 @@
> +#!/bin/bash
> +###############################################################################
> +# #
> +# IPFire.org - A linux based firewall #
> +# Copyright (C) 2024-2025 IPFire Team <info(a)ipfire.org> #
> +# #
> +# 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 <http://www.gnu.org/licenses/>. #
> +# #
> +###############################################################################
> +
> +version="2025-01-11 - v44"
> +
> +############### Functions ###############
> +
> +source /usr/sbin/rpz-functions
> +
> +############### Main ###############
> +
> +tagName="unbound"
> +
> +rpzAction="${1}" # input RPZ action
> +rpzName="${2}" # input RPZ name
> +rpzURL="${3}" # input RPZ URL
> +rpzOption1="${4}" # input RPZ option #1
> +rpzOption2="${5}" # input RPZ option #2
> +
> +rpzConfig="/etc/unbound/local.d/${rpzName}.rpz.conf" # output zone conf file
> +rpzFile="/etc/unbound/zonefiles/${rpzName}.rpz" # output for RPZ file
> +
> +rpzLog="yes" # log default is yes
> +ucReload="yes" # reload default is yes
> +
> +while [[ $# -gt 0 ]] ; do
> + case "$1" in
> + --no-log ) rpzLog="no" ;;
> + --no-reload ) ucReload="no" ; checkConf="no" ;;
> + esac
> + shift # Shift after checking all the cases to get next option
> +done
> +
> +case "${rpzAction}" in
> + # add new rpz list
> + add )
> + check_name "${rpzName}" # is this a valid name?
> + # does this config already exist? If yes, then exit
> + if [[ -f "${rpzConfig}" ]] ; then
> + msg_log "error: rpz: duplicate - ${rpzConfig} already exists. exit"
> + exit 104
> + fi
> +
> + # is this a valid URL?
> + regex='^https://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
> + if ! [[ "${rpzURL}" =~ $regex ]] ; then
> + msg_log "error: rpz: the URL is not valid: \"${rpzURL}\". exit."
> + exit 105
> + fi
> +
> + # create the zone config file
> + {
> + echo "rpz:"
> + echo " name: ${rpzName}.rpz"
> + echo " zonefile: ${rpzFile}"
> + echo " url: ${rpzURL}"
> + echo " rpz-action-override: nxdomain"
> + echo " rpz-log: ${rpzLog}"
> + echo " rpz-log-name: ${rpzName}"
> + echo " rpz-signal-nxdomain-ra: yes"
> + } > "${rpzConfig}"
> +
> + # set-up zonefile
> + # create an empty rpz file if it does not exist
> + if [[ ! -f "${rpzFile}" ]] ; then
> + touch "${rpzFile}"
> + # unbound requires these settings for rpz files
> + set_permissions "${rpzFile}" "${rpzConfig}"
> + fi
> + ;;
> +
> + # trash config file & rpz file
> + remove )
> + if ! [[ -f "${rpzConfig}" ]] ; then
> + msg_log "error: rpz: cannot remove ${rpzConfig}, does not exist. exit"
> + exit 106
> + fi
> +
> + msg_log "info: rpz: remove config file & rpz file \"${rpzName}\""
> + rm "${rpzConfig}"
> + rm "${rpzFile}"
> + ;;
> +
> + reload )
> + check_unbound_conf "${checkConf}"
> + ;;
> +
> + list )
> + awk -F':' '/^\s*name:/{ gsub(/[[:blank:]]|\.rpz/, "",$2) ; NAME=$2 } \
> + /^\s*url:/{ gsub(/[[:blank:]]/, "") ; print NAME"="$2":"$3} ' \
> + /etc/unbound/local.d/*rpz.conf
> + exit
> + ;;
> +
> + unbound-restart )
> + check_unbound_conf "${checkConf}"
> + unbound_restart
> + exit
> + ;;
> +
> + * )
> + msg_log "error: rpz: missing or incorrect parameter"
> + printf "Usage: $(basename "$0") <ACTION> <NAME> <URL> <OPTION> <OPTION>\n"
> + printf "Version: ${version}\n"
> + exit 108
> + ;;
> +
> +esac
> +
> +unbound_control_reload "${ucReload}"
> +
> +exit
> diff --git a/config/rpz/rpz-functions b/config/rpz/rpz-functions
> new file mode 100644
> index 000000000..ace1d2690
> --- /dev/null
> +++ b/config/rpz/rpz-functions
> @@ -0,0 +1,85 @@
> +#!/bin/bash
> +###############################################################################
> +# #
> +# IPFire.org - A linux based firewall #
> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
> +# #
> +# 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 <http://www.gnu.org/licenses/>. #
> +# #
> +###############################################################################
> +
> +version="2024-12-10 - v02"
> +
> +############### Functions ###############
> +
> +msg_log () {
> + logger --tag "${tagName}" "$*"
> + if tty --silent ; then
> + echo "${tagName}:" "$*"
> + fi
> +}
> +
> +# check for a valid name
> +check_name () {
> + local theName="${1}"
> +
> + regex='^[a-zA-Z0-9_]+$' # no dash or plus, alpha numeric only
> + regex1='^(allow|block)$' # allow and block are reserved NAMEs
> + if [[ ! "${theName}" =~ $regex ]] || [[ "${theName}" =~ $regex1 ]] ; then
> + msg_log "error: rpz: the NAME is not valid: \"${theName}\". exit."
> + exit 101
> + fi
> +}
> +
> +set_permissions () {
> + chown nobody:nobody "$@"
> + chmod 644 "$@"
> +}
> +
> +check_unbound_conf () {
> + local thecheckConf="${1:-yes}" # check config default is yes
> +
> + # check the above config files
> + if [[ "${thecheckConf}" == yes ]] ; then
> + msg_log "info: rpz: check for errors with \"unbound-checkconf\""
> +
> + if ! unbound-checkconf ; then
> + msg_log "error: rpz: unbound-checkconf found invalid configuration."
> + msg_log \
> + "error: rpz: In Terminal run the command \"unbound-checkconf\" for more information. exit."
> + exit 102
> + fi
> + fi
> +}
> +
> +unbound_control_reload () {
> + local theReload="${1:-yes}" # reload default is yes
> +
> + if [[ "${theReload}" == yes ]] ; then
> + # reload due to the changes
> + msg_log "info: rpz: run \"unbound-control reload\""
> +
> + if ! unbound-control reload ; then
> + msg_log "error: rpz: unbound-control reload. exit."
> + exit 109
> + fi
> + fi
> +}
> +
> +unbound_restart () {
> + # restart due to the changes
> + msg_log "info: rpz: run \"unbound restart\""
> +
> + /usr/local/bin/unboundctrl restart
> +}
> diff --git a/config/rpz/rpz-make b/config/rpz/rpz-make
> new file mode 100644
> index 000000000..927d55170
> --- /dev/null
> +++ b/config/rpz/rpz-make
> @@ -0,0 +1,203 @@
> +#!/bin/bash
> +###############################################################################
> +# #
> +# IPFire.org - A linux based firewall #
> +# Copyright (C) 2024-2025 IPFire Team <info(a)ipfire.org> #
> +# #
> +# 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 <http://www.gnu.org/licenses/>. #
> +# #
> +###############################################################################
> +
> +version="2025-01-11 - v14"
> +
> +############### Functions ###############
> +
> +source /usr/sbin/rpz-functions
> +
> +# create the config file for allow
> +make_allow_config () {
> + local theLog="${1:-yes}" # log default ON
> + local theConfig="/etc/unbound/local.d/00-rpz.conf" # output zone conf file
> + local theList="/var/ipfire/dns/rpz/allowlist" # input custom list of domains
> +
> + msg_log "info: rpz: make config file \"00-rpz.conf\""
> +
> + echo "server:
> + module-config: \"respip validator iterator\"" > "${theConfig}"
> +
> + # does allow list exist?
> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
> +
> + echo "rpz:
> + name: allow.rpz
> + zonefile: /etc/unbound/zonefiles/allow.rpz
> + rpz-action-override: passthru
> + rpz-log: ${theLog}
> + rpz-log-name: allow
> + rpz-signal-nxdomain-ra: yes" >> "${theConfig}"
> +
> + fi
> +
> + # set-up zonefile - unbound requires these settings for rpz files
> + set_permissions "${theConfig}"
> +}
> +
> +# create the config file for block
> +make_block_config () {
> + local theLog="${1:-yes}" # log default ON
> + local theConfig="/etc/unbound/local.d/block.rpz.conf" # output zone conf file
> + local theList="/var/ipfire/dns/rpz/blocklist" # input custom list of domains
> +
> + msg_log "info: rpz: make config file \"block.rpz.conf\""
> +
> + # does block list exist?
> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
> +
> + echo "rpz:
> + name: block.rpz
> + zonefile: /etc/unbound/zonefiles/block.rpz
> + rpz-action-override: nxdomain
> + rpz-log: ${theLog}
> + rpz-log-name: block
> + rpz-signal-nxdomain-ra: yes" > "${theConfig}"
> +
> + # set-up zonefile - unbound requires these settings for rpz files
> + set_permissions "${theConfig}"
> + else
> + # no - trash the config file
> + rm --verbose /etc/unbound/local.d/block.rpz.conf
> + fi
> +}
> +
> +# create an RPZ file for allow or block
> +make_rpz_file () {
> + local theName="${1}" # allow or block
> + local theAction='.' # the default is nxdomain or block
> + local actionList
> +
> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
> + local theZonefile="/etc/unbound/zonefiles/${theName}.rpz" # output file for RPZ
> +
> + # does a list exist?
> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
> +
> + # for allow set to passthru
> + [[ "${theName}" == allow ]] && theAction='rpz-passthru.'
> +
> + # drop any extra "blanks" and add "CNAME <RPZ action>." to each line
> + actionList=$( awk '{$1=$1};1' "${theList}" |
> + sed "/^[^;].*[[:alnum:]]/ s|$| CNAME ${theAction}|" )
> +
> + msg_log "info: rpz: create zonefile for \"${theName}list\""
> +
> +echo "; Name: ${theName} list
> +; Last modified: $(date "+%Y-%m-%d at %H.%M.%S %Z")
> +;
> +; domains with actions list
> +;
> +${actionList}" > "${theZonefile}"
> +
> + # set-up zonefile - unbound requires these settings for rpz files
> + set_permissions "${theZonefile}"
> + # set-up allow/block list files
> + set_permissions "${theList}"
> + else
> + msg_log "info: rpz: the ${theList} is empty."
> +
> + rm --verbose "${theZonefile}" # trash the RPZ file
> + fi
> +}
> +
> +# check if allow/block list is valid
> +validate_list () {
> + local theName="${1}" # allow or block
> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
> +
> + # remove good:
> + # - properly formated domain names with or without leading wildcard
> + # - properly formated top level domain (TLD) names with wildcard
> + # - blank lines and comment lines
> + # remaining lines are considered "bad"
> + bad_lines=$( sed --regexp-extended \
> + '/^(\*\.)?([a-zA-Z0-9](([a-zA-Z0-9\-]){0,61}[a-zA-Z0-9])?\.)+([a-zA-Z]{2,}|xn--[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])$/d ;
> + /^(\*\.)([a-z]{2,61}|xn--[a-z0-9]{1,60})$/d ;
> + /^$/d ; /^;/d' "${theList}" )
> +
> + if [[ ! -z "${bad_lines}" ]] ; then
> + msg_log "error: rpz: invalid line(s) in ${theList}."
> + printf "%s\n" "bad line(s): ${bad_lines}"
> + exit 110
> + fi
> +}
> +
> +
> +############### Main ###############
> +
> +tagName="unbound"
> +
> +rpzName="${1}" # input RPZ name
> +
> +rpzLog="yes" # log default is yes
> +ucReload="yes" # reload default is yes
> +
> +while [[ $# -gt 0 ]] ; do
> + case "$1" in
> + --no-log ) rpzLog="no" ;;
> + --no-reload ) ucReload="no" ; checkConf="no" ;;
> + esac
> + shift # Shift after checking all the cases to get next option
> +done
> +
> +case "${rpzName}" in
> + # make a new allow or block rpz file
> +
> + allow )
> + validate_list 'allow' # is the allowlist valid?
> + make_allow_config "${rpzLog}"
> + make_rpz_file 'allow'
> + ;;
> +
> + allowblock )
> + validate_list 'allow' # is the list valid?
> + make_allow_config "${rpzLog}"
> + make_rpz_file 'allow'
> + ;&
> +
> + block )
> + validate_list 'block' # is the blocklist valid?
> + make_block_config "${rpzLog}"
> + make_rpz_file 'block'
> + ;;
> +
> + reload )
> + check_unbound_conf "${checkConf}"
> + ;;
> +
> + unbound-restart )
> + check_unbound_conf "${checkConf}"
> + unbound_restart
> + exit
> + ;;
> +
> + * )
> + msg_log "error: rpz: missing or incorrect parameter"
> + printf "Usage: $(basename "$0") <NAME> <OPTION> <OPTION>\n"
> + printf "Version: ${version}\n"
> + exit 108
> + ;;
> +esac
> +
> +unbound_control_reload "${ucReload}"
> +
> +exit
> diff --git a/config/rpz/rpz-metrics b/config/rpz/rpz-metrics
> new file mode 100755
> index 000000000..4d43e1629
> --- /dev/null
> +++ b/config/rpz/rpz-metrics
> @@ -0,0 +1,170 @@
> +#!/bin/bash
> +###############################################################################
> +# #
> +# IPFire.org - A linux based firewall #
> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
> +# #
> +# 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 <http://www.gnu.org/licenses/>. #
> +# #
> +###############################################################################
> +
> +version="2025-01-20 - v25"
> +
> +############### Main ###############
> +
> +weeks="2" # default to two message logs
> +sortBy="name" # default "by name"
> +rpzActive="enabled" # default "enabled only"
> +
> +while [[ $# -gt 0 ]] ; do
> + case "$1" in
> + --by-names | --by-name | name ) sortBy="name" ;;
> +
> + --by-hits | --by-hit | hits | hit ) sortBy="hit" ;;
> +
> + --by-lines | --by-line | lines | line ) sortBy="line" ;;
> +
> + --by-effect ) sortBy="effect" ;;
> +
> + --enabled-only ) rpzActive="enabled" ;;
> +
> + --active-all | --all | all ) rpzActive="all" ;;
> +
> + [0-9] | [0-9][0-9] ) weeks=$1 ;;
> + esac
> + shift # Shift after checking all the cases to get next option
> +done
> +
> +# get the list of message logs for N weeks
> +messageLogs=$( find /var/log/messages* -type f | sort --version-sort |
> + head -"${weeks}" )
> +
> +# get the list of RPZ names & counts from the message log(s)
> +rpzNameCount=$( for logf in ${messageLogs} ; do
> + zgrep --text --fixed-strings 'info: rpz: applied' "${logf}" |
> + awk '$10 ~ /\[\w*]/ { print $10 }' ;
> + done | sort | uniq --count )
> +
> +# flip results and remove brackets `[` and `]`
> +rpzNameCount=$( echo "${rpzNameCount}" |
> + awk '{ print $2, $1 }' |
> + sed --regexp-extended 's|^\[(.*)\]|\1|' )
> +
> +# grab only names
> +rpzNames=$( echo "${rpzNameCount}" | awk '{ print $1 }' )
> +
> +# get list of RPZ files
> +rpzFileList=$( find /etc/unbound/zonefiles -type f -iname "*.rpz" )
> +
> +# get basename of those files
> +rpzBaseNames=$( echo "${rpzFileList}" |
> + sed 's|/etc/unbound/zonefiles/||g ; s|\.rpz||g ;' )
> +
> +# add to rpzNames
> +rpzNames="${rpzNames}"$'\n'"${rpzBaseNames}"
> +
> +# drop duplicate names
> +rpzNames=$( echo "${rpzNames}" | sort --unique )
> +
> +# get line count for each RPZ
> +lineCount=$( echo "${rpzFileList}" | xargs wc -l )
> +
> +# get comment line count and blank line count for each RPZ
> +commentCount=$( echo "${rpzFileList}" | xargs grep --count -e "^$" -e "^;" )
> +
> +# get modified date each RPZ
> +modDateList=$( echo "${rpzFileList}" | xargs stat -c '%.10y %n' )
> +
> +ucListAuthZones=$( unbound-control list_auth_zones )
> +
> +# get width of RPZ names
> +pWidth=$( echo "${rpzNames}" | awk '{ print $1" " }' | wc -L )
> +pFormat="%-${pWidth}s %-8s %-8s %8s %12s %12s\n"
> +
> +# print title line
> +printf "${pFormat}" "name" "hits" "active" "lines" " hits/line" "last download"
> +printf -- "--------------"
> +
> +theResults=""
> +totalLines=0
> +totalHits=0
> +while read -r theName
> +do
> + printf -- "--" # pretend progress bar
> +
> + # is this RPZ list active?
> + theActive="disabled"
> + if grep --quiet "^${theName}\.rpz" <<< "${ucListAuthZones}"
> + then
> + theActive="enabled"
> + else
> + [[ "${rpzActive}" == enabled ]] && continue
> + fi
> +
> + # get hit count
> + theHits="0"
> + if output=$( grep "^${theName}\s" <<< "${rpzNameCount}" ) ; then
> + theHits=$( echo "${output}" | awk '{ print $2 }' )
> + totalHits=$(( totalHits + theHits ))
> + fi
> +
> + # get line count
> + theLines="n/a"
> + hitsPerLine="0"
> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${lineCount}" ) ; then
> + theLines=$( echo "${output}" | awk '{ print $1 }' )
> + totalLines=$(( totalLines + theLines ))
> +
> + if [[ "${theLines}" -gt 2 ]] ; then
> + hitsPerLine=$(( 100 * theHits / theLines ))
> + fi
> + fi
> +
> + # get modification date
> + theModDate="n/a"
> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${modDateList}" ) ; then
> + theModDate=$( echo "${output}" | awk '{ print $1 }' )
> + fi
> +
> + # add to results list
> + theResults+="${theName} ${theHits} ${theActive} ${theLines} ${hitsPerLine} ${theModDate}"$'\n'
> +
> +done <<< "${rpzNames}"
> +
> +case "${sortBy}" in
> + # sort by "active" then by "name"
> + name) sortArg=(-k3,3r -k1,1) ;;
> +
> + # sort by "active" then by "hits" then by "name"
> + hit) sortArg=(-k3,3r -k2,2nr -k1,1) ;;
> +
> + # sort by "active" then by "lines" then by "name"
> + line) sortArg=(-k3,3r -k4,4nr -k1,1) ;;
> +
> + # sort by "active" then by "effect" then by "name"
> + effect) sortArg=(-k3,3r -k5,5nr -k1,1) ;;
> +esac
> +
> +printf -- "--------------\n"
> +# remove blank lines, sort, print as columns
> +echo "${theResults}" |
> + awk '!/^[[:space:]]*$/' |
> + sort "${sortArg[@]}" |
> + awk --assign=width="${pWidth}" \
> + '{ printf "%-*s %-8s %-8s %8s %10s %% %12s\n", width, $1, $2, $3, $4, $5, $6 }'
> +
> +printf "${pFormat}" "" "=======" "" "========" "" ""
> +printf "${pFormat}" "Totals -->" "${totalHits}" "" "${totalLines}" "" ""
> +
> +exit
> diff --git a/config/rpz/rpz-sleep b/config/rpz/rpz-sleep
> new file mode 100755
> index 000000000..dd3603599
> --- /dev/null
> +++ b/config/rpz/rpz-sleep
> @@ -0,0 +1,58 @@
> +#!/bin/bash
> +###############################################################################
> +# #
> +# IPFire.org - A linux based firewall #
> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
> +# #
> +# 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 <http://www.gnu.org/licenses/>. #
> +# #
> +###############################################################################
> +
> +version="2024-08-16" # v05
> +
> +############### Functions ###############
> +
> +# send message to message log
> +msg_log () {
> + logger --tag "${tagName}" "$*"
> + if tty --silent ; then
> + echo "${tagName}:" "$*"
> + fi
> +}
> +
> +############### Main ###############
> +
> +tagName="unbound"
> +
> +sleepTime="${1:-5m}" # default to sleep for 5m (5 minutes)
> +
> +zoneList=$( unbound-control list_auth_zones | awk '{print $1}' )
> +
> +for zone in ${zoneList} ; do
> + printf "disable ${zone}\t"
> + unbound-control rpz_disable "${zone}"
> +done
> +
> +msg_log "info: rpz: disabled all zones for ${sleepTime}"
> +
> +sleep "${sleepTime}"
> +
> +for zone in ${zoneList} ; do
> + printf "enable ${zone}\t"
> + unbound-control rpz_enable "${zone}"
> +done
> +
> +msg_log "info: rpz: enabled all zones"
> +
> +exit
> diff --git a/config/rpz/rpz.de.pl b/config/rpz/rpz.de.pl
> new file mode 100644
> index 000000000..3770c6bb0
> --- /dev/null
> +++ b/config/rpz/rpz.de.pl
> @@ -0,0 +1,30 @@
> +# Added for Response Policy Zone (RPZ) add-on
> +%tr = (%tr,
> +'rpz' => 'Response Policy Zones (RPZ)',
> +'rpz apply' => 'Übernehmen',
> +'rpz cl allow enable' => 'Benutzerdefinierte Allowlist aktivieren:',
> +'rpz cl allow info' => 'Zugelassene Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
> +'rpz cl allow' => 'Benutzerdefinierte Allowlist',
> +'rpz cl block enable' => 'Benutzerdefinierte Blocklist aktivieren:',
> +'rpz cl block info' => 'Gesperrte Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
> +'rpz cl block' => 'Benutzerdefinierte Blocklist',
> +'rpz cl' => 'Benutzerdefinierte Listen',
> +'rpz exitcode 101' => 'Der Name enthält unzulässige Zeichen',
> +'rpz exitcode 102' => 'unbound-checkconf hat eine fehlerhafte Konfiguration ermittelt. Führen Sie das Kommando unbound-checkconf auf der Konsole aus, um weitere Informationen zu erhalten.',
> +'rpz exitcode 103' => 'Die benutzerdefinierte Allow-/Blocklist ist leer',
> +'rpz exitcode 104' => 'Ein Eintrag mit identischem Namen existiert bereits',
> +'rpz exitcode 105' => 'Die URL ist ungültig',
> +'rpz exitcode 106' => 'Eintrag kann nicht entfernt werden, der Name existiert nicht',
> +'rpz exitcode 107' => 'Der Name ist ungültig - nur "allow" oder "block" möglich',
> +'rpz exitcode 108' => 'Fehlende oder inkorrekte Parameter',
> +'rpz exitcode 109' => 'unbound-control reload ist fehlgeschlagen',
> +'rpz exitcode 110' => 'Die benutzerdefinierte Allow-/Blocklist enthält unzulässige Einträge',
> +'rpz exitcode 201' => 'Die Anmerkung enthält unzulässige Zeichen',
> +'rpz exitcode 202' => 'Ungültiger Eintrag in der benutzerdefinierten Allowlist, Zeile ',
> +'rpz exitcode 203' => 'Ungültiger Eintrag in der benutzerdefinierten Blocklist, Zeile ',
> +'rpz exitcode 204' => 'Ausgewählter Eintrag existiert nicht: ',
> +'rpz zf editor' => 'Zonendatei-Eintrag bearbeiten',
> +'rpz zf imported' => '(importiert aus rpz-config)',
> +'rpz zf remark info' => 'Erlaubte Zeichen sind a-z, A-Z, 0-9 und Unterstriche',
> +'rpz zf' => 'Zonendateien',
> +);
> diff --git a/config/rpz/rpz.en.pl b/config/rpz/rpz.en.pl
> new file mode 100644
> index 000000000..0720a8940
> --- /dev/null
> +++ b/config/rpz/rpz.en.pl
> @@ -0,0 +1,30 @@
> +# Added for Response Policy Zone (RPZ) add-on
> +%tr = (%tr,
> +'rpz' => 'Response Policy Zones (RPZ)',
> +'rpz apply' => 'Apply',
> +'rpz cl allow enable' => 'Enable custom allowlist:',
> +'rpz cl allow info' => 'Allowed domains (one per line)<br>Example: domain.com, *.domain.com',
> +'rpz cl allow' => 'Custom allowlist',
> +'rpz cl block enable' => 'Enable custom blocklist:',
> +'rpz cl block info' => 'Blocked domains (one per line)<br>Example: domain.com, *.domain.com',
> +'rpz cl block' => 'Custom blocklist',
> +'rpz cl' => 'Custom lists',
> +'rpz exitcode 101' => 'the NAME is not valid',
> +'rpz exitcode 102' => 'unbound-checkconf found invalid configuration. In the Terminal run the command unbound-checkconf for more information',
> +'rpz exitcode 103' => 'the allow/block list is empty',
> +'rpz exitcode 104' => 'duplicate - NAME already exists',
> +'rpz exitcode 105' => 'the URL is not valid',
> +'rpz exitcode 106' => 'cannot remove the NAME does not exist',
> +'rpz exitcode 107' => 'the NAME is not valid - "allow" or "block" only',
> +'rpz exitcode 108' => 'missing or incorrect parameter',
> +'rpz exitcode 109' => 'unbound-control reload failed',
> +'rpz exitcode 110' => 'custom Allowlist/Blocklist contains invalid entries',
> +'rpz exitcode 201' => 'the REMARK is not valid',
> +'rpz exitcode 202' => 'invalid entry in allowlist, line ',
> +'rpz exitcode 203' => 'invalid entry in blocklist, line ',
> +'rpz exitcode 204' => 'Selected entry does not exist: ',
> +'rpz zf editor' => 'Edit zonefiles entry',
> +'rpz zf imported' => '(imported from rpz-config)',
> +'rpz zf remark info' => 'Valid characters are a-z, A-Z, 0-9 and underscore.',
> +'rpz zf' => 'Zonefiles',
> +);
> diff --git a/config/rpz/rpz.es.pl b/config/rpz/rpz.es.pl
> new file mode 100644
> index 000000000..98628e4aa
> --- /dev/null
> +++ b/config/rpz/rpz.es.pl
> @@ -0,0 +1,30 @@
> +# Added for Response Policy Zone (RPZ) add-on
> +%tr = (%tr,
> +'rpz' => 'Zonas de política de respuesta (RPZ)',
> +'rpz apply' => 'Aplicar',
> +'rpz cl allow enable' => 'Habilitar la lista blanca personalizada:',
> +'rpz cl allow info' => 'Dominio permitido (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
> +'rpz cl allow' => 'Lista blanca personalizada',
> +'rpz cl block enable' => 'Habilitar la lista negra personalizada:',
> +'rpz cl block info' => 'Dominio bloqueado (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
> +'rpz cl block' => 'Lista negra personalizada',
> +'rpz cl' => 'Lista personalizada',
> +'rpz exitcode 101' => 'El NOMBRE no es válido',
> +'rpz exitcode 102' => 'unbound-checkconf ha encontrado una configuración no válida. Desde Terminal, ejecute el comando unbound-checkconf para mayor información',
> +'rpz exitcode 103' => 'La lista de permitidos/bloqueados está vacía',
> +'rpz exitcode 104' => 'duplicado - NOMBRE ya existe',
> +'rpz exitcode 105' => 'la URL no es válida',
> +'rpz exitcode 106' => 'no es posible eliminar el NOMBRE que no existe',
> +'rpz exitcode 107' => 'el NOMBRE no es válido - sólo "permitir" o "bloquear"',
> +'rpz exitcode 108' => 'parámetro faltante o incorrecto',
> +'rpz exitcode 109' => 'Error al recargar unbound-control',
> +'rpz exitcode 110' => 'la Lista blanca/Lista negra personalizada contiene entradas no válidas',
> +'rpz exitcode 201' => 'el COMENTARIO no es válido',
> +'rpz exitcode 202' => 'entrada no válida en la lista blanca, línea ',
> +'rpz exitcode 203' => 'entrada no válida en la lista negra, línea ',
> +'rpz exitcode 204' => 'La entrada seleccionada no existe: ',
> +'rpz zf editor' => 'Editar la entrada de archivos de zona',
> +'rpz zf imported' => '(importado de rpz-config)',
> +'rpz zf remark info' => 'Los caracteres válidos son a-z, A-Z, 0-9 y guión bajo',
> +'rpz zf' => 'Archivos de zona',
> +);
> diff --git a/config/rpz/rpz.fr.pl b/config/rpz/rpz.fr.pl
> new file mode 100644
> index 000000000..f35f3c2d0
> --- /dev/null
> +++ b/config/rpz/rpz.fr.pl
> @@ -0,0 +1,30 @@
> +# Added for Response Policy Zone (RPZ) add-on
> +%tr = (%tr,
> +'rpz' => 'Response Policy Zones (RPZ)',
> +'rpz apply' => 'Appliquer',
> +'rpz cl allow enable' => 'Activer la liste d\'autorisations personnalisée:',
> +'rpz cl allow info' => 'Domaines autorisés (un par ligne)<br>Example: domain.com, *.domain.com',
> +'rpz cl allow' => 'Liste d\'autorisations personnalisée',
> +'rpz cl block enable' => 'Activer la liste de blocage personnalisée:',
> +'rpz cl block info' => 'Domaines bloqués (un par ligne)<br>Example: domain.com, *.domain.com',
> +'rpz cl block' => 'liste de blocage personnalisé',
> +'rpz cl' => 'Listes personnalisées',
> +'rpz exitcode 101' => 'le NOM n\'est pas valide',
> +'rpz exitcode 102' => 'unbound-checkconf configuration non valide trouvée. Dans le terminal, exécutez la commande unbound-checkconf pour plus d\'informations',
> +'rpz exitcode 103' => 'la liste autoriser/bloquer est vide',
> +'rpz exitcode 104' => 'le NOM existe déjà',
> +'rpz exitcode 105' => 'L\'URL n\'est pas valide',
> +'rpz exitcode 106' => 'impossible de supprimer le NOM n\'existe pas',
> +'rpz exitcode 107' => 'le NOM n\'est pas valide - « autoriser » ou « bloquer » seulement',
> +'rpz exitcode 108' => 'paramètre manquant ou incorrect',
> +'rpz exitcode 109' => 'unbound-control rechargement échoué',
> +'rpz exitcode 110' => 'la liste autoriser/bloquer contient des entrées non valides',
> +'rpz exitcode 201' => 'la REMARQUE n\'est pas valable',
> +'rpz exitcode 202' => 'entrée non valide dans la liste d\'autorisation, ligne ',
> +'rpz exitcode 203' => 'entrée non valide dans la liste de blocs, ligne ',
> +'rpz exitcode 204' => 'L\'entrée sélectionnée n\'existe pas: ',
> +'rpz zf editor' => 'Modifier l\'entrée Fichiers Zone',
> +'rpz zf imported' => '(importé de rpz-config)',
> +'rpz zf remark info' => 'Les caractères valides sont a-z, A-Z, 0-9 et soulignement.',
> +'rpz zf' => 'Fichiers Zone',
> +);
> diff --git a/config/rpz/rpz.it.pl b/config/rpz/rpz.it.pl
> new file mode 100644
> index 000000000..ee81605c9
> --- /dev/null
> +++ b/config/rpz/rpz.it.pl
> @@ -0,0 +1,30 @@
> +# Added for Response Policy Zone (RPZ) add-on
> +%tr = (%tr,
> +'rpz' => 'Response Policy Zones (RPZ)',
> +'rpz apply' => 'Applica',
> +'rpz cl allow enable' => 'Abilita la Whitelist personalizzata:',
> +'rpz cl allow info' => 'Domini consentiti (uno per riga)<br>Esempio: domain.com, *.domain.com',
> +'rpz cl allow' => 'Whitelist personalizzata',
> +'rpz cl block enable' => 'Abilita la Blacklist personalizzata:',
> +'rpz cl block info' => 'Domini bloccati (uno per riga)<br>Esempio: domain.com, *.domain.com',
> +'rpz cl block' => 'Blacklist personalizzata',
> +'rpz cl' => 'Liste personalizzate',
> +'rpz exitcode 101' => 'il NOME non è valido',
> +'rpz exitcode 102' => 'unbound-checkconf ha trovato una configurazione non valida. Dal Terminale esegui il comando unbound-checkconf per maggiori informazioni',
> +'rpz exitcode 103' => 'l\'elenco consentiti/bloccati è vuoto',
> +'rpz exitcode 104' => 'duplicato - NAME esiste di già',
> +'rpz exitcode 105' => 'l\'URL non è valido',
> +'rpz exitcode 106' => 'non è possibile rimuovere il NOME non esiste',
> +'rpz exitcode 107' => 'il NOME non è valido - solo "consenti" o "blocca"',
> +'rpz exitcode 108' => 'parametro mancante o non corretto',
> +'rpz exitcode 109' => 'ricaricamento del controllo non associato non riuscito',
> +'rpz exitcode 110' => 'la Whitelist/Blacklist personalizzata contiene voci non valide',
> +'rpz exitcode 201' => 'l"OSSERVAZIONE non è valida',
> +'rpz exitcode 202' => 'voce non valida nella Whitelist, riga ',
> +'rpz exitcode 203' => 'voce non valida nella Blacklist, riga ',
> +'rpz exitcode 204' => 'La voce selezionata non esiste: ',
> +'rpz zf editor' => 'Modifica la voce dei file di zona',
> +'rpz zf imported' => '(importato da rpz-config)',
> +'rpz zf remark info' => 'I caratteri validi sono a-z, A-Z, 0-9 e trattino basso',
> +'rpz zf' => 'Zonefiles',
> +);
> diff --git a/config/rpz/rpz.tr.pl b/config/rpz/rpz.tr.pl
> new file mode 100644
> index 000000000..00226e192
> --- /dev/null
> +++ b/config/rpz/rpz.tr.pl
> @@ -0,0 +1,30 @@
> +# I�in eklendi Ayriyeten Response Policy Zone (RPZ)
> +%tr = (%tr,
> +'rpz' => 'Response Policy Zones (RPZ)',
> +'rpz apply' => 'Uygulamak',
> +'rpz cl allow enable' => '�zel Etkinlestir allowlist:',
> +'rpz cl allow info' => 'Izin verilmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
> +'rpz cl allow' => 'Etkinlestir allowlist',
> +'rpz cl block enable' => '�zel Etkinlestir blocklist:',
> +'rpz cl block info' => 'Engellenmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
> +'rpz cl block' => 'Engellenmis blocklist',
> +'rpz cl' => 'Engellenmis lists',
> +'rpz exitcode 101' => 'NAME ge�erli degil',
> +'rpz exitcode 102' => 'unbound-checkconf ge�ersiz yapilandirma bulundu. Terminalde daha fazla bilgi i�in Unbound-checkConf komutunu �alistirin',
> +'rpz exitcode 103' => 'allow/block liste bos',
> +'rpz exitcode 104' => 'kopyalamak - NAME zaten var',
> +'rpz exitcode 105' => 'URL ge�erli degil',
> +'rpz exitcode 106' => '�ikarilamiyor NAME yok',
> +'rpz exitcode 107' => 'NAME ge�erli degil - "allow" veya "block" yalniz',
> +'rpz exitcode 108' => 'Parametre eksik veya yanlis',
> +'rpz exitcode 109' => 'unbound-control basarisiz',
> +'rpz exitcode 110' => 'Engellenmis Allowlist/Blocklist ge�ersiz girisler i�erir',
> +'rpz exitcode 201' => 'REMARK ge�erli degil',
> +'rpz exitcode 202' => 'Ge�ersiz giris allowlist, line ',
> +'rpz exitcode 203' => 'Ge�ersiz giris blocklist, line ',
> +'rpz exitcode 204' => 'Se�ilen giris yok: ',
> +'rpz zf editor' => 'yazimlamak zonefiles giris',
> +'rpz zf imported' => '(ithal edildi rpz-config)',
> +'rpz zf remark info' => 'Ge�erli karakterler a-z, A-Z, 0-9ve alt�st.',
> +'rpz zf' => 'Zonefiles',
> +);
> diff --git a/html/cgi-bin/rpz.cgi b/html/cgi-bin/rpz.cgi
> new file mode 100644
> index 000000000..a821c92ac
> --- /dev/null
> +++ b/html/cgi-bin/rpz.cgi
> @@ -0,0 +1,923 @@
> +#!/usr/bin/perl
> +###############################################################################
> +# #
> +# IPFire.org - A linux based firewall #
> +# Copyright (C) 2005-2024 IPFire Team <info(a)ipfire.org> #
> +# #
> +# 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 <http://www.gnu.org/licenses/>. #
> +# #
> +###############################################################################
> +
> +use strict;
> +use Scalar::Util qw(looks_like_number);
> +
> +# debugging
> +#use warnings;
> +#use CGI::Carp 'fatalsToBrowser';
> +#use Data::Dumper;
> +
> +require '/var/ipfire/general-functions.pl';
> +require "${General::swroot}/lang.pl";
> +require "${General::swroot}/header.pl";
> +
> +###--- Extra HTML ---###
> +my $extraHead = <<END
> +<style>
> + /* alternating row background */
> + .tbl tr:nth-child(2n+2) {
> + background-color: var(--color-light-grey);
> + }
> + .tbl tr:nth-child(2n+3) {
> + background-color: var(--color-grey);
> + }
> + /* text styles */
> + .tbl th:not(:last-child) {
> + text-align: left;
> + }
> + div.right {
> + text-align: right;
> + margin-top: 0.5em;
> + }
> + /* customlist input */
> + textarea.domainlist {
> + margin: 0.5em 0;
> + resize: vertical;
> + min-height: 10em;
> + overflow: auto;
> + white-space: pre;
> + }
> + button[type=submit]:disabled {
> + opacity: 0.6;
> + }
> +</style>
> +END
> +;
> +###--- End of extra HTML ---###
> +
> +
> +### Settings ###
> +
> +# Request DNS service reload after configuration change
> +my $RPZ_RELOAD_FLAG = "${General::swroot}/dns/rpz/reload.flag";
> +
> +# Configuration file for all available zonefiles
> +# Format: index, name (unique), enabled (on/off), URL, remark
> +my $ZONEFILES_CONF = "${General::swroot}/dns/rpz/zonefiles.conf";
> +
> +# Configuration file for custom lists
> +# IDs: 0=allowlist, 1=blocklist, 2=options (allow/block enabled)
> +my $CUSTOMLISTS_CONF = "${General::swroot}/dns/rpz/customlists.conf";
> +
> +# Export custom lists to rpz-config
> +my $RPZ_ALLOWLIST = "${General::swroot}/dns/rpz/allowlist";
> +my $RPZ_BLOCKLIST = "${General::swroot}/dns/rpz/blocklist";
> +
> +
> +### Preparation ###
> +
> +# Create missing config files
> +unless(-f $ZONEFILES_CONF) { &General::system('touch', "$ZONEFILES_CONF"); }
> +unless(-f $CUSTOMLISTS_CONF) { &General::system('touch', "$CUSTOMLISTS_CONF"); }
> +
> +
> +## Global gui data
> +my $errormessage = "";
> +
> +## Global configuration data
> +my %zonefiles = ();
> +my %customlists = ();
> +&_zonefiles_load();
> +&_customlists_load();
> +
> +## Global CGI form data
> +my %cgiparams = ();
> +&Header::getcgihash(\%cgiparams);
> +
> +my $action = $cgiparams{'ACTION'} // 'NONE';
> +my $action_key = $cgiparams{'KEY'} // ''; # entry being edited, empty = none/new
> +
> +
> +###--- Process form actions ---###
> +
> +# Zonefiles action: Check whether the requested entry exists
> +if((substr($action, 0, 3) eq 'ZF_') && ($action_key)) {
> + unless(defined $zonefiles{$action_key}) {
> + $errormessage = &_rpz_error_tr(204, $action_key);
> + $action = 'NONE';
> + }
> +}
> +
> +## Perform actions
> +if($action eq 'ZF_SAVE') { ## Save new or modified zonefiles entry
> + if(&_action_zf_save()) {
> + $action = 'NONE'; # success, return to main page
> + &_http_prg_redirect();
> + } else {
> + $action = 'ZF_EDIT'; # error occured, keep editing
> + }
> +
> +} elsif($action eq 'ZF_TOGGLE') { ## Toggle on/off
> + if(&_action_zf_toggle()) {
> + $action = 'NONE';
> + &_http_prg_redirect();
> + }
> +
> +} elsif($action eq 'ZF_REMOVE') { ## Remove entry
> + if(&_action_zf_remove()) {
> + $action = 'NONE';
> + &_http_prg_redirect();
> + }
> +
> +} elsif($action eq 'CL_SAVE') { ## Save custom lists
> + if(&_action_cl_save()) {
> + $action = 'NONE';
> + &_http_prg_redirect();
> + }
> +
> +} elsif($action eq 'RPZ_RELOAD') { ## Reload dns configuration
> + if(&_action_rpz_reload()) {
> + $action = 'NONE';
> + &_http_prg_redirect();
> + }
> +
> +} elsif($action eq 'UNB_RESTART') { ## Restart unbound service
> + if(&_action_unb_restart()) {
> + $action = 'NONE';
> + &_http_prg_redirect();
> + }
> +
> +}
> +
> +
> +###--- Start GUI ---###
> +
> +## Start http output
> +&Header::showhttpheaders();
> +
> +# Start HTML
> +&Header::openpage($Lang::tr{'rpz'}, 1, $extraHead);
> +&Header::openbigbox('100%', 'left', '');
> +
> +# Show error messages
> +if($errormessage) {
> + &_print_message($errormessage);
> +}
> +
> +# Handle zonefile add/edit mode
> +if($action eq "ZF_EDIT") {
> + &_print_zonefile_editor();
> +
> + # Finalize page and exit cleanly
> + &Header::closebigbox();
> + &Header::closepage();
> + exit(0);
> +}
> +
> +# Show gui elements
> +&_print_zonefiles();
> +&_print_customlists();
> +&_print_gui_extras();
> +
> +&Header::closebigbox();
> +&Header::closepage();
> +
> +###--- End of GUI ---###
> +
> +
> +###--- Internal configuration file functions ---###
> +
> +# Load all available zonefiles from rpz-config and the internal configuration
> +sub _zonefiles_load {
> + # Clean start
> + %zonefiles = ();
> +
> + # Source 1: Get the currently enabled zonefiles from rpz-config (expected format [name]=[URL])
> + my @enabled_files = &General::system_output('/usr/sbin/rpz-config', 'list');
> +
> + foreach my $row (@enabled_files) {
> + chomp($row);
> +
> + # Use regex instead of split() to skip non-matching lines
> + next unless($row =~ /^(\w+)=(.+)$/);
> + my ($name, $url) = ($1, $2);
> +
> + # Unique names are already guaranteed by rpz-config
> + if(&_rpz_validate_zonefile($name, $url, '', 0) == 0) {
> + # Populate global data hash, mark all found entries as enabled
> + my %entry = ('enabled' => 'on',
> + 'url' => $url,
> + 'remark' => $Lang::tr{'rpz zf imported'});
> +
> + $zonefiles{$name} = \%entry;
> + }
> + }
> +
> + # Source 2: Get additional data and disabled entries from configuration file
> + my %configured_files = ();
> + &General::readhasharray($ZONEFILES_CONF, \%configured_files);
> +
> + foreach my $row (values (%configured_files)) {
> + my ($name, $enabled, $url, $remark) = @$row;
> + $remark //= "";
> +
> + next unless($name);
> +
> + # Check whether this row belongs to an entry already imported from rpz-config
> + if(defined $zonefiles{$name}) {
> + # Existing entry, only merge additional data
> + $zonefiles{$name}{'remark'} = $remark;
> + } else {
> + # Skip entry if it is marked as enabled but not found by rpz-config. It was then deleted manually
> + if($enabled ne 'on') {
> + # Populate global data hash
> + my %entry = ('enabled' => 'off',
> + 'url' => $url // "",
> + 'remark' => $remark);
> +
> + $zonefiles{$name} = \%entry;
> + }
> + }
> + }
> +}
> +
> +# Save internal zonefiles configuration
> +sub _zonefiles_save_conf {
> + my $index = 0;
> + my %export = ();
> +
> + # Loop trough all zonefiles and create "hasharray" type export
> + foreach my $name (keys %zonefiles) {
> + my @entry = ($name,
> + $zonefiles{$name}{'enabled'},
> + $zonefiles{$name}{'url'},
> + $zonefiles{$name}{'remark'});
> +
> + $export{$index++} = \@entry;
> + }
> +
> + &General::writehasharray($ZONEFILES_CONF, \%export);
> +}
> +
> +# Load custom lists from rpz-config and the internal configuration
> +sub _customlists_load {
> + # Clean start
> + %customlists = ();
> +
> + # Load configuration file
> + my %lists_conf = ();
> + &General::readhasharray($CUSTOMLISTS_CONF, \%lists_conf);
> +
> + # Get list options, enabled by default to start import
> + $customlists{'allow'}{'enabled'} = $lists_conf{2}[0] // 'on';
> + $customlists{'block'}{'enabled'} = $lists_conf{2}[1] // 'on';
> +
> + # Import enabled list from rpz-config, otherwise retrieve stored or empty list from configuration file
> + if($customlists{'allow'}{'enabled'} eq 'on') {
> + &_customlist_import('allow', $RPZ_ALLOWLIST);
> + } else {
> + $customlists{'allow'}{'list'} = $lists_conf{0} // [];
> + }
> + if($customlists{'block'}{'enabled'} eq 'on') {
> + &_customlist_import('block', $RPZ_BLOCKLIST);
> + } else {
> + $customlists{'block'}{'list'} = $lists_conf{1} // [];
> + }
> +}
> +
> +# Save internal custom lists configuration
> +sub _customlists_save_conf {
> + my %export = ();
> +
> + # Match IDs with import function
> + $export{0} = $customlists{'allow'}{'list'};
> + $export{1} = $customlists{'block'}{'list'};
> + $export{2} = [$customlists{'allow'}{'enabled'}, $customlists{'block'}{'enabled'}];
> +
> + &General::writehasharray($CUSTOMLISTS_CONF, \%export);
> +}
> +
> +# Import a custom list from plain file, returns empty list if file is missing
> +sub _customlist_import {
> + my ($listname, $filename) = @_;
> + my @list = ();
> +
> + # File exists, load and check all lines
> + if(-f $filename) {
> + open(my $FH, '<', $filename) or die "Can't read $filename: $!";
> + while(my $line = <$FH>) {
> + chomp($line);
> + push(@list, $line);
> + }
> + close($FH);
> +
> + # Clean up imported data
> + &_rpz_validate_customlist(\@list, 1);
> + }
> +
> + $customlists{$listname}{'list'} = \@list;
> +}
> +
> +# Export a custom list to plain file or clear file if list is disabled
> +sub _customlist_export {
> + my ($listname, $filename) = @_;
> + return unless(defined $customlists{$listname});
> +
> + # Write enabled domain list to file, otherwise save empty file
> + open(my $FH, '>', $filename) or die "Can't write $filename: $!";
> +
> + if($customlists{$listname}{'enabled'} eq 'on') {
> + foreach my $line (@{$customlists{$listname}{'list'}}) {
> + print $FH "$line\n";
> + }
> + } else {
> + print $FH "; Note: This list is currently disabled by $ENV{'SCRIPT_NAME'}\n";
> + }
> +
> + close($FH);
> +}
> +
> +
> +###--- Internal gui functions ---###
> +
> +# Show simple message box
> +sub _print_message {
> + my ($message, $title) = @_;
> + $title ||= $Lang::tr{'error messages'};
> +
> + &Header::openbox('100%', 'left', $title);
> + print "<span>$message</span>";
> + &Header::closebox();
> +}
> +
> +# Show all zone files and related gui elements
> +sub _print_zonefiles {
> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf'});
> +
> + print <<END
> +<table class="tbl" width="100%">
> + <tr>
> + <th>$Lang::tr{'name'}</th>
> + <th>URL</th>
> + <th>$Lang::tr{'remark'}</th>
> + <th colspan="3">$Lang::tr{'action'}</th>
> + </tr>
> +END
> +;
> +
> + # Sort zonefiles by name and loop trough all entries
> + foreach my $name (sort keys %zonefiles) {
> +
> + # Toggle button label translation
> + my $toggle_tr = ($zonefiles{$name}{'enabled'} eq 'on') ? $Lang::tr{'click to disable'} : $Lang::tr{'click to enable'};
> +
> + print <<END
> + <tr>
> + <td>$name</td>
> + <td>$zonefiles{$name}{'url'}</td>
> + <td>$zonefiles{$name}{'remark'}</td>
> +
> + <td align="center" width="5%">
> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
> + <input type="hidden" name="KEY" value="$name">
> + <input type="hidden" name="ACTION" value="ZF_TOGGLE">
> + <input type="image" src="/images/$zonefiles{$name}{'enabled'}.gif" title="$toggle_tr" alt="$toggle_tr">
> + </form>
> + </td>
> + <td align="center" width="5%">
> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
> + <input type="hidden" name="KEY" value="$name">
> + <input type="hidden" name="ACTION" value="ZF_EDIT">
> + <input type="image" src="/images/edit.gif" title="$Lang::tr{'edit'}" alt="$Lang::tr{'edit'}">
> + </form>
> + </td>
> + <td align="center" width="5%">
> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
> + <input type="hidden" name="KEY" value="$name">
> + <input type="hidden" name="ACTION" value="ZF_REMOVE">
> + <input type="image" src="/images/delete.gif" title="$Lang::tr{'remove'}" alt="$Lang::tr{'remove'}">
> + </form>
> + </td>
> + </tr>
> +END
> +;
> + }
> +
> + # Disable reload button if not needed
> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
> +
> + print <<END
> +</table>
> +
> +<div class="right">
> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
> + <input type="hidden" name="KEY" value="">
> + <button type="submit" name="ACTION" value="ZF_EDIT">$Lang::tr{'add'}</button>
> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
> + </form>
> +</div>
> +END
> +;
> +
> + &Header::closebox();
> +}
> +
> +# Show zonefiles entry editor
> +sub _print_zonefile_editor {
> +
> + # Key specified: Edit existing entry
> + if(($action_key) && (defined $zonefiles{$action_key})) {
> + # Load data to be edited, but don't override already present values (allows user to edit after error)
> + $cgiparams{'ZF_NAME'} //= $action_key;
> + $cgiparams{'ZF_URL'} //= $zonefiles{$action_key}{'url'};
> + $cgiparams{'ZF_REMARK'} //= $zonefiles{$action_key}{'remark'};
> + }
> +
> + # Fallback to empty form
> + $cgiparams{'ZF_NAME'} //= "";
> + $cgiparams{'ZF_URL'} //= "";
> + $cgiparams{'ZF_REMARK'} //= "";
> +
> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf editor'});
> +
> + print <<END
> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
> +<input type="hidden" name="KEY" value="$action_key">
> +<table width="100%">
> + <tr>
> + <td width="20%">$Lang::tr{'name'}: <img src="/blob.gif" alt="*"></td>
> + <td><input type="text" name="ZF_NAME" value="$cgiparams{'ZF_NAME'}" size="40" maxlength="32" title="$Lang::tr{'rpz zf remark info'}" pattern="[a-zA-Z0-9_]{1,32}" required></td>
> + </tr>
> + <tr>
> + <td width="20%">URL: <img src="/blob.gif" alt="*"></td>
> + <td><input type="url" name="ZF_URL" value="$cgiparams{'ZF_URL'}" size="40" maxlength="128" required></td>
> + </tr>
> + <tr>
> + <td width="20%">$Lang::tr{'remark'}:</td>
> + <td><input type="text" name="ZF_REMARK" value="$cgiparams{'ZF_REMARK'}" size="40" maxlength="32"></td>
> + </tr>
> + <tr>
> + <td colspan="2"><hr></td>
> + </tr>
> + <tr>
> + <td width="55%"><img src="/blob.gif" alt="*"> $Lang::tr{'required field'}</td>
> + <td align="right"><button type="submit" name="ACTION" value="ZF_SAVE">$Lang::tr{'save'}</button></td>
> + </tr>
> +</table>
> +</form>
> +
> +<div class="right">
> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
> + <button type="submit" name="ACTION" value="NONE">$Lang::tr{'back'}</button>
> + </form>
> +</div>
> +END
> +;
> +
> + &Header::closebox();
> +}
> +
> +# Show custom allow/block files and related gui elements
> +sub _print_customlists {
> +
> + # Load lists from config, unless they are currently being edited
> + if($action ne 'CL_SAVE') {
> + $cgiparams{'ALLOW_LIST'} = join("\n", @{$customlists{'allow'}{'list'}});
> + $cgiparams{'BLOCK_LIST'} = join("\n", @{$customlists{'block'}{'list'}});
> +
> + $cgiparams{'ALLOW_ENABLED'} = ($customlists{'allow'}{'enabled'} eq 'on') ? 'on' : undef;
> + $cgiparams{'BLOCK_ENABLED'} = ($customlists{'block'}{'enabled'} eq 'on') ? 'on' : undef;
> + }
> +
> + # Fallback to empty form
> + $cgiparams{'ALLOW_LIST'} //= "";
> + $cgiparams{'BLOCK_LIST'} //= "";
> +
> + # HTML checkboxes, unchecked = no or undef value in POST data
> + my %checked = ();
> + $checked{'ALLOW_ENABLED'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? " checked" : "";
> + $checked{'BLOCK_ENABLED'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? " checked" : "";
> +
> + # Disable reload button if not needed
> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
> +
> + &Header::openbox('100%', 'left', $Lang::tr{'rpz cl'});
> +
> + print <<END
> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
> +<table width="100%">
> + <tr>
> + <td colspan="2"><b>$Lang::tr{'rpz cl allow'}</b><br>$Lang::tr{'rpz cl allow info'}</td>
> + <td colspan="2"><b>$Lang::tr{'rpz cl block'}</b><br>$Lang::tr{'rpz cl block info'}</td>
> + </tr>
> + <tr>
> + <td colspan="2"><textarea name="ALLOW_LIST" class="domainlist" cols="45">
> +$cgiparams{'ALLOW_LIST'}</textarea></td>
> + <td colspan="2"><textarea name="BLOCK_LIST" class="domainlist" cols="45">
> +$cgiparams{'BLOCK_LIST'}</textarea></td>
> + </tr>
> + <tr>
> + <td><label for="allow_enabled">$Lang::tr{'rpz cl allow enable'}</label></td>
> + <td width="15%"><input type="checkbox" name="ALLOW_ENABLED" id="allow_enabled"$checked{'ALLOW_ENABLED'}></td>
> + <td><label for="block_enabled">$Lang::tr{'rpz cl block enable'}</label></td>
> + <td width="15%"><input type="checkbox" name="BLOCK_ENABLED" id="block_enabled"$checked{'BLOCK_ENABLED'}></td>
> + </tr>
> + <tr>
> + <td colspan="4"><hr></td>
> + </tr>
> + <tr>
> + <td align="right" colspan="4">
> + <button type="submit" name="ACTION" value="CL_SAVE">$Lang::tr{'save'}</button>
> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
> + </td>
> +</table>
> +</form>
> +END
> +;
> +
> + &Header::closebox();
> +}
> +
> +# Output javascript and extra gui elements
> +sub _print_gui_extras {
> +
> + # Apply/Restart button modifier key handler
> + if(&_rpz_needs_reload()) {
> + print <<END
> +<script>
> + // Commit modifier key handler
> + (function(jq, document) {
> + var keyEventsOn = false; // Keyboard events attached
> + var keyModify = false; // Modifier key pressed
> + var mouseHover = false; // Mouse over commit button
> + var btnModified = false; // Button modified to "Restart"
> +
> + // Document-level key events, enable only while cursor is over button
> + function attachKeyEvents() {
> + if(keyEventsOn) {
> + return;
> + }
> + keyEventsOn = true;
> +
> + jq(document).on("keydown.rpz", function(event) {
> + if((!keyModify) && event.shiftKey) {
> + keyModify = true;
> + handleModify();
> + }
> + });
> + jq(document).on("keyup.rpz", function(event) {
> + if(keyModify && (!event.shiftKey)) {
> + keyModify = false;
> + handleModify();
> + }
> + });
> + }
> + function removeKeyEvents() {
> + keyModify = false;
> + if(keyEventsOn) {
> + jq(document).off("keydown.rpz keyup.rpz");
> + keyEventsOn = false;
> + }
> + }
> +
> + // Attach mouse hover events to commit buttons
> + function attachMouseEvents() {
> + jq("button.commit").on("mouseenter", function(event) {
> + if(!mouseHover) {
> + mouseHover = true;
> + attachKeyEvents();
> + // Handle already pressed key
> + keyModify = !!(event.shiftKey);
> + handleModify();
> + }
> + });
> +
> + // Cursor moved away: Disable key listener to minimize events
> + jq("button.commit").on("mouseleave", function() {
> + if(mouseHover) {
> + mouseHover = false;
> + removeKeyEvents();
> + handleModify();
> + }
> + });
> + }
> +
> + // Modify commit button
> + function handleModify() {
> + let modify = mouseHover && keyModify;
> + if(btnModified != modify) {
> + if(modify) {
> + jq("button.commit").text("$Lang::tr{'restart'}").val("UNB_RESTART");
> + } else {
> + jq("button.commit").text("$Lang::tr{'rpz apply'}").val("RPZ_RELOAD");
> + }
> + btnModified = modify;
> + }
> + }
> +
> + // jQuery DOM ready
> + jq(function() {
> + attachMouseEvents();
> + });
> + })(jQuery, document);
> +</script>
> +END
> +;
> + } # End of modifier key handler
> +
> +}
> +
> +
> +###--- Internal action processing functions ---###
> +
> +# Toggle zonefile on/off
> +sub _action_zf_toggle {
> + return unless(defined $zonefiles{$action_key});
> +
> + my $result = 0;
> + my $enabled = $zonefiles{$action_key}{'enabled'};
> +
> + # Perform toggle action
> + if($enabled eq 'on') {
> + $enabled = 'off';
> + $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
> + } else {
> + $enabled = 'on';
> + $result = &General::system('/usr/sbin/rpz-config', 'add', $action_key, $zonefiles{$action_key}{'url'}, '--no-reload');
> + }
> +
> + # Check for errors, request service reload on success
> + return unless &_rpz_check_result($result, 1);
> +
> + # Save changes
> + $zonefiles{$action_key}{'enabled'} = $enabled;
> + &_zonefiles_save_conf();
> +
> + return 1;
> +}
> +
> +# Remove zonefile
> +sub _action_zf_remove {
> + return unless(defined $zonefiles{$action_key});
> +
> + # Remove from rpz-config if currently active
> + if($zonefiles{$action_key}{'enabled'} eq 'on') {
> + my $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
> +
> + # Check for errors, request service reload on success
> + return unless &_rpz_check_result($result, 1);
> + }
> +
> + # Remove from data hash and save changes
> + delete $zonefiles{$action_key};
> + &_zonefiles_save_conf();
> +
> + # Clear action_key, as the entry is now removed entirely
> + $action_key = "";
> +
> + return 1;
> +}
> +
> +# Create or update zonefile entry
> +# Returns undef if gui needs to stay in editor mode
> +sub _action_zf_save {
> + my $result = 0;
> +
> + my $name = $cgiparams{'ZF_NAME'} // "";
> + my $url = $cgiparams{'ZF_URL'} // "";
> + my $remark = $cgiparams{'ZF_REMARK'} // "";
> + my $enabled = 'on'; # Enable new entries by default
> +
> + # Note on variables:
> + # name = unique key, will be used to address the entry
> + # action_key = name of the entry being edited, empty for new entry
> +
> + # Only check for unique name if it changed
> + # (this also checks new entries because the action_key is empty in this case)
> + $result = &_rpz_validate_zonefile($name, $url, $remark, (lc($name) ne lc($action_key)));
> + return unless &_rpz_check_result($result, 0);
> +
> + # Edit existing entry: Determine what was changed
> + if(($action_key) && (defined $zonefiles{$action_key})) {
> + # Name und URL remain unchanged, only save remark and finish
> + if(($name eq $action_key) && ($url eq $zonefiles{$action_key}{'url'})) {
> + $zonefiles{$action_key}{'remark'} = $remark;
> + &_zonefiles_save_conf();
> +
> + return 1;
> + }
> +
> + # Entry was changed and needs to be recreated, preserve status
> + $enabled = $zonefiles{$action_key}{'enabled'};
> +
> + # Remove from rpz-config
> + return unless &_action_zf_remove();
> + }
> +
> + # Add new entry to rpz-config
> + if($enabled eq 'on') {
> + $result = &General::system('/usr/sbin/rpz-config', 'add', $name, $url, '--no-reload');
> +
> + # Check for errors, request service reload on success
> + return unless &_rpz_check_result($result, 1);
> + }
> +
> + # Add to global data hash and save changes
> + my %entry = ('enabled' => $enabled,
> + 'url' => $url,
> + 'remark' => $remark);
> +
> + $zonefiles{$name} = \%entry;
> + &_zonefiles_save_conf();
> +
> + return 1;
> +}
> +
> +# Save custom lists
> +sub _action_cl_save {
> + return unless((defined $cgiparams{'ALLOW_LIST'}) && (defined $cgiparams{'BLOCK_LIST'}));
> +
> + my $result = 0;
> +
> + my @allowlist = split(/\R/, $cgiparams{'ALLOW_LIST'});
> + my @blocklist = split(/\R/, $cgiparams{'BLOCK_LIST'});
> +
> + # Validate lists
> + $result = &_rpz_validate_customlist(\@allowlist);
> + if($result != 0) {
> + $errormessage = &_rpz_error_tr(202, $result);
> + return;
> + }
> + $result = &_rpz_validate_customlist(\@blocklist);
> + if($result != 0) {
> + $errormessage = &_rpz_error_tr(203, $result);
> + return;
> + }
> +
> + # Add to global data hash and save changes
> + $customlists{'allow'}{'list'} = \@allowlist;
> + $customlists{'block'}{'list'} = \@blocklist;
> + $customlists{'allow'}{'enabled'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? 'on' : 'off';
> + $customlists{'block'}{'enabled'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? 'on' : 'off';
> +
> + &_customlists_save_conf();
> + &_customlist_export('allow', $RPZ_ALLOWLIST);
> + &_customlist_export('block', $RPZ_BLOCKLIST);
> +
> + # Make new lists, request service reload on success
> + $result = &General::system('/usr/sbin/rpz-make', 'allowblock', '--no-reload');
> + return unless &_rpz_check_result($result, 1);
> +
> + return 1;
> +}
> +
> +# Trigger rpz-config reload
> +sub _action_rpz_reload {
> + return 1 unless &_rpz_needs_reload();
> +
> + # Immediately clear flag to prevent multiple reloads
> + if(-f $RPZ_RELOAD_FLAG) {
> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
> + }
> +
> + # Perform reload, recreate reload flag on error to enable retry
> + my $result = &General::system('/usr/sbin/rpz-config', 'reload');
> + if(not &_rpz_check_result($result, 0)) {
> + &General::system('touch', "$RPZ_RELOAD_FLAG");
> + return;
> + }
> +
> + return 1;
> +}
> +
> +# Trigger unbound restart
> +sub _action_unb_restart {
> + return 1 unless &_rpz_needs_reload();
> +
> + # Immediately clear flag to prevent multiple restarts
> + if(-f $RPZ_RELOAD_FLAG) {
> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
> + }
> +
> + # Perform restart, unboundctrl always exits zero
> + &General::system('/usr/local/bin/unboundctrl', 'restart');
> +
> + return 1;
> +}
> +
> +
> +###--- Internal rpz-config functions ---###
> +
> +# Translate rpz-config exitcodes and messages
> +# 100-199: rpz-config, 200-299: webgui
> +sub _rpz_error_tr {
> + my ($error, $append) = @_;
> + $append //= '';
> +
> + # Translate numeric exit codes
> + if(looks_like_number($error)) {
> + if(defined $Lang::tr{"rpz exitcode $error"}) {
> + $error = $Lang::tr{"rpz exitcode $error"};
> + }
> + }
> +
> + return "RPZ $Lang::tr{'error'}: $error" . &Header::escape($append);
> +}
> +
> +# Check result of rpz-config system call, request reload on success
> +sub _rpz_check_result {
> + my ($result, $request_reload) = @_;
> + $request_reload //= 0;
> +
> + # exitcode 0 = success
> + if($result != 0) {
> + $errormessage = &_rpz_error_tr($result);
> + return;
> + }
> +
> + # Set reload flag
> + if($request_reload) {
> + &General::system('touch', "$RPZ_RELOAD_FLAG");
> + }
> +
> + return 1;
> +}
> +
> +# Test whether reload flag is set
> +sub _rpz_needs_reload {
> + return (-f $RPZ_RELOAD_FLAG);
> +}
> +
> +# Validate a zonefile entry, returns rpz-config exitcode on failure. Use _rpz_check_result to verify.
> +# unique = check for unique name
> +sub _rpz_validate_zonefile {
> + my ($name, $url, $remark, $unique) = @_;
> + $unique //= 1;
> +
> + unless($name =~ /^[a-zA-Z0-9_]{1,32}$/) {
> + return 101;
> + }
> + unless($url =~ /^[\w+\.:;\/\\&@#%?=\-~|!]{1,128}$/) {
> + return 105;
> + }
> + unless($remark =~ /^[\w \-()\.:;*\/\\?!&=]{0,32}$/) {
> + return 201;
> + }
> +
> + # Check against already existing names
> + if($unique) {
> + foreach my $existing (keys %zonefiles) {
> + if(lc($name) eq lc($existing)) {
> + return 104;
> + }
> + }
> + }
> +
> + return 0;
> +}
> +
> +# Validate a custom list, returns number of rejected line on failure. Check for non-zero results.
> +# listref = array reference, cleanup = remove invalid entries instead of returning an error
> +sub _rpz_validate_customlist {
> + my ($listref, $cleanup) = @_;
> + $cleanup //= 0;
> +
> + foreach my $index (reverse 0..$#{$listref}) {
> + my $row = @$listref[$index];
> + next unless($row); # Skip/allow empty lines
> +
> + # Reject/remove everything besides wildcard domains and remarks
> + if((not &General::validwildcarddomainname($row)) && (not $row =~ /^;[\w \-()\.:;*\/\\?!&=]*$/)) {
> + unless($cleanup) {
> + # +1 for user friendly line number and to ensure non-zero exitcode
> + return $index + 1;
> + }
> +
> + # Remove current row
> + splice(@$listref, $index, 1);
> + }
> + }
> +
> + return 0;
> +}
> +
> +
> +###--- Internal misc functions ---###
> +
> +# Send HTTP 303 redirect headers for post/request/get pattern
> +# (Must be sent before calling &Header::showhttpheaders())
> +sub _http_prg_redirect {
> + my $location = "https://$ENV{'SERVER_NAME'}:$ENV{'SERVER_PORT'}$ENV{'SCRIPT_NAME'}";
> + print "Status: 303 See Other\n";
> + print "Location: $location\n";
> +}
> diff --git a/lfs/rpz b/lfs/rpz
> new file mode 100644
> index 000000000..7ddbc38e5
> --- /dev/null
> +++ b/lfs/rpz
> @@ -0,0 +1,96 @@
> +###############################################################################
> +# #
> +# IPFire.org - A linux based firewall #
> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
> +# #
> +# 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 <http://www.gnu.org/licenses/>. #
> +# #
> +###############################################################################
> +
> +###############################################################################
> +# Definitions
> +###############################################################################
> +
> +include Config
> +
> +SUMMARY = response policy zone - RPZ reputation system for unbound DNS
> +
> +VER = 1.0.0
> +
> +THISAPP = rpz-$(VER)
> +DIR_APP = $(DIR_SRC)/$(THISAPP)
> +TARGET = $(DIR_INFO)/$(THISAPP)
> +
> +PROG = rpz
> +PAK_VER = 18
> +
> +DEPS =
> +
> +SERVICES =
> +
> +###############################################################################
> +# Top-level Rules
> +###############################################################################
> +
> +install : $(TARGET)
> +
> +check :
> +
> +download :
> +
> +b2 :
> +
> +dist:
> + @$(PAK)
> +
> +###############################################################################
> +# Installation Details
> +###############################################################################
> +
> +$(TARGET) :
> + @$(PREBUILD)
> + @rm -rf $(DIR_APP)
> +
> + # RPZ scripts
> + install --verbose --mode=755 \
> + $(DIR_CONF)/rpz/{rpz-config,rpz-metrics,rpz-sleep,rpz-make,rpz-functions} \
> + --target-directory=/usr/sbin
> +
> + # RPZ config files
> + mkdir -pv /etc/unbound/local.d
> + install --verbose --mode=644 --owner=nobody --group=nobody \
> + $(DIR_CONF)/rpz/00-rpz.conf \
> + --target-directory=/etc/unbound/local.d
> + chown --verbose --recursive nobody:nobody /etc/unbound/local.d
> +
> + # RPZ custom list files for allow and block
> + mkdir -pv /var/ipfire/dns/rpz
> + touch /var/ipfire/dns/rpz/{allowlist,blocklist}
> + chown --verbose --recursive nobody:nobody /var/ipfire/dns/rpz
> +
> + # RPZ zone files
> + # create empty RPZ config file to avoid a unbound config error
> + mkdir -pv /etc/unbound/zonefiles
> + touch /etc/unbound/zonefiles/allow.rpz
> + chown --verbose --recursive nobody:nobody /etc/unbound/zonefiles
> +
> + # Install addon-specific language-files
> + install --verbose --mode=004 $(DIR_CONF)/rpz/rpz.*.pl \
> + --target-directory=/var/ipfire/addon-lang
> +
> + # Install backup definition
> + cp -vf $(DIR_CONF)/backup/includes/rpz /var/ipfire/backup/addons/includes/rpz
> +
> + @rm -rf $(DIR_APP)
> + @$(POSTBUILD)
> diff --git a/make.sh b/make.sh
> index 827ea9e77..a77535b13 100755
> --- a/make.sh
> +++ b/make.sh
> @@ -390,7 +390,7 @@ prepareenv() {
> if [ "${free_space}" -lt "${required_space}" ]; then
> # Add any consumed space
> while read -r consumed_space path; do
> - (( free_space += consumed_space / 1024 / 1024 ))
> + (( free_space += consumed_space / 1024 / 1024 ))
> done <<< "$(du --summarize --bytes "${BUILD_DIR}" "${IMAGES_DIR}" "${LOG_DIR}" 2>/dev/null)"
> fi
>
> @@ -2087,6 +2087,7 @@ build_system() {
> lfsmake2 btrfs-progs
> lfsmake2 inotify-tools
> lfsmake2 grub-btrfs
> + lfsmake2 rpz
>
> lfsmake2 linux
> lfsmake2 rtl8812au
> diff --git a/src/paks/rpz/install.sh b/src/paks/rpz/install.sh
> new file mode 100644
> index 000000000..ef99bf742
> --- /dev/null
> +++ b/src/paks/rpz/install.sh
> @@ -0,0 +1,36 @@
> +#!/bin/bash
> +###############################################################################
> +# #
> +# IPFire.org - A linux based firewall #
> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
> +# #
> +# 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 <http://www.gnu.org/licenses/>. #
> +# #
> +###############################################################################
> +#
> +. /opt/pakfire/lib/functions.sh
> +extract_files
> +restore_backup ${NAME}
> +
> +# fix user created files
> +chown --verbose --recursive nobody:nobody \
> + /var/ipfire/dns/rpz \
> + /etc/unbound/zonefiles \
> + /etc/unbound/local.d
> +
> +# Update Language cache
> +/usr/local/bin/update-lang-cache
> +
> +# restart unbound to load config file
> +/etc/init.d/unbound restart
> diff --git a/src/paks/rpz/uninstall.sh b/src/paks/rpz/uninstall.sh
> new file mode 100644
> index 000000000..e11427df3
> --- /dev/null
> +++ b/src/paks/rpz/uninstall.sh
> @@ -0,0 +1,38 @@
> +#!/bin/bash
> +###############################################################################
> +# #
> +# IPFire.org - A linux based firewall #
> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
> +# #
> +# 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 <http://www.gnu.org/licenses/>. #
> +# #
> +###############################################################################
> +#
> +. /opt/pakfire/lib/functions.sh
> +
> +# stop unbound to delete RPZ conf file
> +/etc/init.d/unbound stop
> +
> +make_backup ${NAME}
> +remove_files
> +
> +# delete rpz config files. Otherwise unbound will throw error:
> +# "[1723428668] unbound-control[17117:0] error: connect: Connection refused for 127.0.0.1 port 8953"
> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
> +
> +# Update Language cache
> +/usr/local/bin/update-lang-cache
> +
> +# start unbound to load unbound config file
> +/etc/init.d/unbound start
> diff --git a/src/paks/rpz/update.sh b/src/paks/rpz/update.sh
> new file mode 100644
> index 000000000..9bc340bc6
> --- /dev/null
> +++ b/src/paks/rpz/update.sh
> @@ -0,0 +1,52 @@
> +#!/bin/bash
> +###############################################################################
> +# #
> +# IPFire.org - A linux based firewall #
> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
> +# #
> +# 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 <http://www.gnu.org/licenses/>. #
> +# #
> +###############################################################################
> +#
> +. /opt/pakfire/lib/functions.sh
> +
> +# from update.sh
> +extract_backup_includes
> +
> +# stop unbound to delete RPZ conf file
> +/etc/init.d/unbound stop
> +
> +# from uninstall.sh
> +make_backup ${NAME}
> +remove_files
> +
> +# delete rpz config files. Otherwise unbound will throw error:
> +# "unbound-control[nn:0] error: connect: Connection refused for 127.0.0.1 port 8953"
> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
> +
> +# from install.sh
> +extract_files
> +restore_backup ${NAME}
> +
> +# fix user created files
> +chown --verbose --recursive nobody:nobody \
> + /var/ipfire/dns/rpz \
> + /etc/unbound/zonefiles \
> + /etc/unbound/local.d
> +
> +# Update Language cache
> +/usr/local/bin/update-lang-cache
> +
> +# restart unbound to load config files
> +/etc/init.d/unbound start
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
2025-02-06 16:35 [PATCH] RPZ: update code to include WEBGUI and additional languages Jon Murphy
2025-02-06 19:35 ` Bernhard Bitsch
@ 2025-02-06 20:13 ` Michael Tremer
2025-02-08 18:41 ` jon
1 sibling, 1 reply; 30+ messages in thread
From: Michael Tremer @ 2025-02-06 20:13 UTC (permalink / raw)
To: development
[-- Attachment #1: Type: text/plain, Size: 95242 bytes --]
Hello Jon,
Well, here we are again with another patch regarding this feature.
I cannot quite see from your email what the question is, but if this is a request to have this merged into IPFire, I am once again sorry to disappoint you.
I think I have covered this all at lengths before that this project has been started as a separate effort and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls. Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation. Therefore, what am I supposed to do with this email?
I don’t want to merge code that I don’t agree with. So many fundamental things that I have been raising have either not been discussed or outright dismissed.
I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
Please consider if that can be changed and if there is a path forward with this.
All the best,
-Michael
> On 6 Feb 2025, at 16:35, Jon Murphy <jon.murphy(a)ipfire.org> wrote:
>
> What is it?
> Response Policy Zone (RPZ) is a mechanism to define local policies in a
> standardized way and load those policies from external sources.
> Bottom line: RPZ allows admins to easily block access to websites via DNS lookup.
>
> RPZ can block websites via categories. Examples include: fake websites, annoying
> pop-up ads, newly registered domains, DoH bypass sites, bad "host" services,
> maliscious top level domains (e.g., *.zip, *.mov), piracy, gambling, pornography,
> and more. RPZ lists come from various RPZ providers and their available
> catagories.
>
> This RPZ add-on enables the RPZ functionality by adding a couple lines in a
> configuration file. This add-on simply adds configuration files and adds
> scripts (config, metrics and sleep) to make RPZ easier for the admin to use.
>
> The RPZ scripts include additional languages: German, Spanish, French, Turkish,
> and Italian.
>
> RPZ itself was release in 2010 and has been part of the IPFire build since ~2015.
>
> Why is it needed? What is its value?
>
> - The RPZ concept places this filtering into IPFire, our internet access
> gateway, which is (should be) solely used as DNS source of the internal network.
>
> - As most sites use HTTPS it makes it difficult to filter traffic with URL
> Filter without also properly configuring conventional (non-transparent)
> mode on the proxy. RPZ is a nice replacement for the URL Filter.
>
> - No need to install and maintain an additional device like PiHole or AdBlock
> browser extensions on multiple user devices.
>
> - This is an additional layer of protection for users. Less worry someone will
> click on something that gets them into trouble. And, saying this with emphasis,
> the ability to do it in one place!
>
> - Blocked sites save on unneeded traffic and can lessen the threat of malware
> in advertisements
>
> - Logging allows the admin to see the site blocked and take actions
>
> - RPZ will be used at the home, home-office (work from home), schools,
> ministerial, and at the office. Device counts are small (2-6) to medium (~80)
> to mediam-large (200+).
>
> - RPZ can block ads, popups, phishing, scammers, spyware, malware, annoying
> popups, NSFW links, DOH servers, and the usual internet trash.
>
> ------------------------------
>
> Change Log for RPZ add-on
>
> rpz-1.0.0-18 on 2025-02-05
> - Build for approval & release as IPFire add-on
>
> ---
>
> rpz-beta-0.1.18-18.ipfire on 2025-02-01
> rpz.cgi:
> - new feature: added a mod key to force a unbound restart
>
> rpz-config and rpz-make:
> - new feature: added action for unbound restart `rpz-config unbound-restart`
>
> rpz-metrics:
> - simple reformatting
> - rename far right column from "last update" to "last download"
>
> ---
>
> rpz-beta-0.1.17-17.ipfire on 2024-12-09
> rpz-make
> - bug fix: corrected validation regex for wildcards like: `*.domain.com`
>
> ---
>
> rpz-beta-0.1.16-16.ipfire on 2024-11-18
> rpz-make
> - new feature: updated validation regex
> - bug fix: moved validation to beginning of process. Now we validate before
> creating config files.
>
> rpz.cgi:
> - new feature: use CSS color variables of the main ipfire theme
> - bug fix: empty zonefile remarks were stored as “undef” and caused a warning
> - bug fix: HTML textarea removes the first empty line in a custom list
> - thank you Leo!
>
> ---
>
> rpz-beta-0.1.15-15.ipfire on 2024-11-04
> rpz.cgi:
> - new feature: added new language file for Turkish (thank you Peppe)
>
> rpz-make
> - bug fix: corrected empty allow/block list issue. An empty allow/block list
> will now remove contents of allow/block.rpz files and remove unneeded
> allow/block.conf file. (thank you iptom)
>
> ---
>
> rpz-beta-0.1.14-14.ipfire on 2024-10-29
> rpz-config:
> - bug fix: correct missing rpz extension. `rpz-config list` displayed URL
> incorrectly (thank you Bernhard)
>
> rpz.cgi:
> - bug fix: remove extra `"` in language files (thank you Bernhard)
> - new feature: slightly dim "apply" button when not enabled
>
> ---
>
> rpz-beta-0.1.13-13.ipfire on 2024-10-27
> - skipped
>
> ---
>
> rpz-beta-0.1.12-12.ipfire on 2024-10-21
> rpz.cgi:
> - new feature: added new language file for French (thank you gw-ipfire)
>
> ---
>
> rpz-beta-0.1.11-11.ipfire on 2024-10-18
> rpz.cgi:
> - new feature: added new language file for Italian (thank you umberto)
> - new feature: added new language file for Spanish (thank you Roberto)
>
> ---
>
> rpz-beta-0.1.10-10.ipfire on 2024-10-15
> rpz-make:
> - bug fix: corrected validation error for a custom list entry (thank you siosios)
> - e.g., `*.cloudflare-dns.com`
>
> install.sh:
> - bug fix: add chown to correct user created files
>
> update.sh:
> - bug fix: add chown to correct user created files (thank you siosios)
>
> ---
>
> rpz-beta-0.1.9-9.ipfire on 2024-10-08
> rpz.cgi:
> - new feature: added new language file for German (thank you Leo)
> - bug fix: add missing "rpz exitcode 110"
> - bug fix: corrected missing RPZ menu item at menu > IPFire
>
> ---
>
> rpz-beta-0.1.8-8.ipfire on 2024-10-04
> - skipped
>
> ---
>
> rpz-beta-0.1.7-7.ipfire on 2024-10-03
> All:
> - new feature: includes beta version numbers for pakfire package,
> instead of only `rpz-1.0.0-1.ipfire`, for each release.
>
> rpz.cgi:
> - new feature: added new WebGUI at `rpz.cgi`
> - a BIG thank you to Leo Hofmann for all of his work creating the webgui!!
> - bug fix: corrected missing RPZ menu item at menu > IPFire
>
> rpz-make:
> - new feature: validate entries in allowlist and blocklist
> - new feature: add "no-reload" option for WebGUI
>
> rpz-metrics:
> - new feature: info can be sorted by name, by hit count, by line count, by
> "enabled" list or all lists
>
> backups:
> - bug fix: include all files in `/var/ipfire/dns/rpz` directory in backup
>
> update.sh:
> - bug fix: corrected ownership for `/var/ipfire/dns/rpz` directory during an
> update
>
> Build:
> - bug fix: `block.rpz.conf` and `block.rpz` from build. Files to be created
> by `rpz-make`
>
> WebGUI and German language file
> Contribution-by: Leo-Andres Hofmann <hofmann(a)leo-andres.de>
>
> Spanish language file
> Contribution-by: Roberto Peña
>
> Italian language file
> Contribution-by: Umberto Parma
>
> French language file
> Contribution-by: gw-ipfire
>
> Turkish language file
> Contribution-by: Peppe Tech
>
> Contribution-by: Bernhard Bitsch <bbitsch(a)ipfire.org>
> Contribution-by: Erik Kapfer <erik.kapfer(a)ipfire.org>
> Signed-off-by: Jon Murphy <jon.murphy(a)ipfire.org
> ---
> config/backup/includes/rpz | 4 +
> config/cfgroot/manualpages | 1 +
> config/menu/EX-rpz.menu | 6 +
> config/rootfiles/common/configroot | 1 +
> config/rootfiles/common/web-user-interface | 1 +
> config/rootfiles/packages/rpz | 20 +
> config/rpz/00-rpz.conf | 10 +
> config/rpz/rpz-config | 130 +++
> config/rpz/rpz-functions | 85 ++
> config/rpz/rpz-make | 203 +++++
> config/rpz/rpz-metrics | 170 ++++
> config/rpz/rpz-sleep | 58 ++
> config/rpz/rpz.de.pl | 30 +
> config/rpz/rpz.en.pl | 30 +
> config/rpz/rpz.es.pl | 30 +
> config/rpz/rpz.fr.pl | 30 +
> config/rpz/rpz.it.pl | 30 +
> config/rpz/rpz.tr.pl | 30 +
> html/cgi-bin/rpz.cgi | 923 +++++++++++++++++++++
> lfs/rpz | 96 +++
> make.sh | 3 +-
> src/paks/rpz/install.sh | 36 +
> src/paks/rpz/uninstall.sh | 38 +
> src/paks/rpz/update.sh | 52 ++
> 24 files changed, 2016 insertions(+), 1 deletion(-)
> create mode 100644 config/backup/includes/rpz
> create mode 100644 config/menu/EX-rpz.menu
> create mode 100644 config/rootfiles/packages/rpz
> create mode 100644 config/rpz/00-rpz.conf
> create mode 100644 config/rpz/rpz-config
> create mode 100644 config/rpz/rpz-functions
> create mode 100644 config/rpz/rpz-make
> create mode 100755 config/rpz/rpz-metrics
> create mode 100755 config/rpz/rpz-sleep
> create mode 100644 config/rpz/rpz.de.pl
> create mode 100644 config/rpz/rpz.en.pl
> create mode 100644 config/rpz/rpz.es.pl
> create mode 100644 config/rpz/rpz.fr.pl
> create mode 100644 config/rpz/rpz.it.pl
> create mode 100644 config/rpz/rpz.tr.pl
> create mode 100644 html/cgi-bin/rpz.cgi
> create mode 100644 lfs/rpz
> create mode 100644 src/paks/rpz/install.sh
> create mode 100644 src/paks/rpz/uninstall.sh
> create mode 100644 src/paks/rpz/update.sh
>
> diff --git a/config/backup/includes/rpz b/config/backup/includes/rpz
> new file mode 100644
> index 000000000..36513e494
> --- /dev/null
> +++ b/config/backup/includes/rpz
> @@ -0,0 +1,4 @@
> +/var/ipfire/dns/rpz/*
> +/etc/unbound/zonefiles/allow.rpz
> +/etc/unbound/zonefiles/block.rpz
> +/etc/unbound/local.d/*rpz.conf
> diff --git a/config/cfgroot/manualpages b/config/cfgroot/manualpages
> index 1f7e01efc..d3a48c633 100644
> --- a/config/cfgroot/manualpages
> +++ b/config/cfgroot/manualpages
> @@ -70,6 +70,7 @@ pakfire.cgi=configuration/ipfire/pakfire
> wlanap.cgi=addons/wireless
> tor.cgi=addons/tor
> samba.cgi=addons/samba
> +rpz.cgi=addons/rpz
>
> # Logs menu
> logs.cgi/summary.dat=configuration/logs/summary
> diff --git a/config/menu/EX-rpz.menu b/config/menu/EX-rpz.menu
> new file mode 100644
> index 000000000..2f4daf410
> --- /dev/null
> +++ b/config/menu/EX-rpz.menu
> @@ -0,0 +1,6 @@
> +$subipfire->{'20.rpz'} = {
> + 'caption' => $Lang::tr{'rpz'},
> + 'uri' => '/cgi-bin/rpz.cgi',
> + 'title' => "RPZ",
> + 'enabled' => 1,
> +};
> diff --git a/config/rootfiles/common/configroot b/config/rootfiles/common/configroot
> index 9839eee45..b30d6aae4 100644
> --- a/config/rootfiles/common/configroot
> +++ b/config/rootfiles/common/configroot
> @@ -120,6 +120,7 @@ var/ipfire/menu.d/70-log.menu
> #var/ipfire/menu.d/EX-apcupsd.menu
> #var/ipfire/menu.d/EX-guardian.menu
> #var/ipfire/menu.d/EX-mympd.menu
> +#var/ipfire/menu.d/EX-rpz.menu
> #var/ipfire/menu.d/EX-samba.menu
> #var/ipfire/menu.d/EX-tor.menu
> #var/ipfire/menu.d/EX-transmission.menu
> diff --git a/config/rootfiles/common/web-user-interface b/config/rootfiles/common/web-user-interface
> index 816241dae..e00464076 100644
> --- a/config/rootfiles/common/web-user-interface
> +++ b/config/rootfiles/common/web-user-interface
> @@ -69,6 +69,7 @@ srv/web/ipfire/cgi-bin/proxy.cgi
> srv/web/ipfire/cgi-bin/qos.cgi
> srv/web/ipfire/cgi-bin/remote.cgi
> srv/web/ipfire/cgi-bin/routing.cgi
> +#srv/web/ipfire/cgi-bin/rpz.cgi
> #srv/web/ipfire/cgi-bin/samba.cgi
> srv/web/ipfire/cgi-bin/services.cgi
> srv/web/ipfire/cgi-bin/shutdown.cgi
> diff --git a/config/rootfiles/packages/rpz b/config/rootfiles/packages/rpz
> new file mode 100644
> index 000000000..1c8663049
> --- /dev/null
> +++ b/config/rootfiles/packages/rpz
> @@ -0,0 +1,20 @@
> +etc/unbound/local.d/00-rpz.conf
> +etc/unbound/zonefiles
> +etc/unbound/zonefiles/allow.rpz
> +usr/sbin/rpz-config
> +usr/sbin/rpz-functions
> +usr/sbin/rpz-make
> +usr/sbin/rpz-metrics
> +usr/sbin/rpz-sleep
> +var/ipfire/addon-lang/rpz.de.pl
> +var/ipfire/addon-lang/rpz.en.pl
> +var/ipfire/addon-lang/rpz.es.pl
> +var/ipfire/addon-lang/rpz.fr.pl
> +var/ipfire/addon-lang/rpz.it.pl
> +var/ipfire/addon-lang/rpz.tr.pl
> +var/ipfire/backup/addons/includes/rpz
> +var/ipfire/dns/rpz
> +var/ipfire/dns/rpz/allowlist
> +var/ipfire/dns/rpz/blocklist
> +var/ipfire/menu.d/EX-rpz.menu
> +srv/web/ipfire/cgi-bin/rpz.cgi
> diff --git a/config/rpz/00-rpz.conf b/config/rpz/00-rpz.conf
> new file mode 100644
> index 000000000..f005a4f2e
> --- /dev/null
> +++ b/config/rpz/00-rpz.conf
> @@ -0,0 +1,10 @@
> +server:
> + module-config: "respip validator iterator"
> +
> +rpz:
> + name: allow.rpz
> + zonefile: /etc/unbound/zonefiles/allow.rpz
> + rpz-action-override: passthru
> + rpz-log: yes
> + rpz-log-name: allow
> + rpz-signal-nxdomain-ra: yes
> diff --git a/config/rpz/rpz-config b/config/rpz/rpz-config
> new file mode 100644
> index 000000000..c72d50f9b
> --- /dev/null
> +++ b/config/rpz/rpz-config
> @@ -0,0 +1,130 @@
> +#!/bin/bash
> +###############################################################################
> +# #
> +# IPFire.org - A linux based firewall #
> +# Copyright (C) 2024-2025 IPFire Team <info(a)ipfire.org> #
> +# #
> +# 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 <http://www.gnu.org/licenses/>. #
> +# #
> +###############################################################################
> +
> +version="2025-01-11 - v44"
> +
> +############### Functions ###############
> +
> +source /usr/sbin/rpz-functions
> +
> +############### Main ###############
> +
> +tagName="unbound"
> +
> +rpzAction="${1}" # input RPZ action
> +rpzName="${2}" # input RPZ name
> +rpzURL="${3}" # input RPZ URL
> +rpzOption1="${4}" # input RPZ option #1
> +rpzOption2="${5}" # input RPZ option #2
> +
> +rpzConfig="/etc/unbound/local.d/${rpzName}.rpz.conf" # output zone conf file
> +rpzFile="/etc/unbound/zonefiles/${rpzName}.rpz" # output for RPZ file
> +
> +rpzLog="yes" # log default is yes
> +ucReload="yes" # reload default is yes
> +
> +while [[ $# -gt 0 ]] ; do
> + case "$1" in
> + --no-log ) rpzLog="no" ;;
> + --no-reload ) ucReload="no" ; checkConf="no" ;;
> + esac
> + shift # Shift after checking all the cases to get next option
> +done
> +
> +case "${rpzAction}" in
> + # add new rpz list
> + add )
> + check_name "${rpzName}" # is this a valid name?
> + # does this config already exist? If yes, then exit
> + if [[ -f "${rpzConfig}" ]] ; then
> + msg_log "error: rpz: duplicate - ${rpzConfig} already exists. exit"
> + exit 104
> + fi
> +
> + # is this a valid URL?
> + regex='^https://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
> + if ! [[ "${rpzURL}" =~ $regex ]] ; then
> + msg_log "error: rpz: the URL is not valid: \"${rpzURL}\". exit."
> + exit 105
> + fi
> +
> + # create the zone config file
> + {
> + echo "rpz:"
> + echo " name: ${rpzName}.rpz"
> + echo " zonefile: ${rpzFile}"
> + echo " url: ${rpzURL}"
> + echo " rpz-action-override: nxdomain"
> + echo " rpz-log: ${rpzLog}"
> + echo " rpz-log-name: ${rpzName}"
> + echo " rpz-signal-nxdomain-ra: yes"
> + } > "${rpzConfig}"
> +
> + # set-up zonefile
> + # create an empty rpz file if it does not exist
> + if [[ ! -f "${rpzFile}" ]] ; then
> + touch "${rpzFile}"
> + # unbound requires these settings for rpz files
> + set_permissions "${rpzFile}" "${rpzConfig}"
> + fi
> + ;;
> +
> + # trash config file & rpz file
> + remove )
> + if ! [[ -f "${rpzConfig}" ]] ; then
> + msg_log "error: rpz: cannot remove ${rpzConfig}, does not exist. exit"
> + exit 106
> + fi
> +
> + msg_log "info: rpz: remove config file & rpz file \"${rpzName}\""
> + rm "${rpzConfig}"
> + rm "${rpzFile}"
> + ;;
> +
> + reload )
> + check_unbound_conf "${checkConf}"
> + ;;
> +
> + list )
> + awk -F':' '/^\s*name:/{ gsub(/[[:blank:]]|\.rpz/, "",$2) ; NAME=$2 } \
> + /^\s*url:/{ gsub(/[[:blank:]]/, "") ; print NAME"="$2":"$3} ' \
> + /etc/unbound/local.d/*rpz.conf
> + exit
> + ;;
> +
> + unbound-restart )
> + check_unbound_conf "${checkConf}"
> + unbound_restart
> + exit
> + ;;
> +
> + * )
> + msg_log "error: rpz: missing or incorrect parameter"
> + printf "Usage: $(basename "$0") <ACTION> <NAME> <URL> <OPTION> <OPTION>\n"
> + printf "Version: ${version}\n"
> + exit 108
> + ;;
> +
> +esac
> +
> +unbound_control_reload "${ucReload}"
> +
> +exit
> diff --git a/config/rpz/rpz-functions b/config/rpz/rpz-functions
> new file mode 100644
> index 000000000..ace1d2690
> --- /dev/null
> +++ b/config/rpz/rpz-functions
> @@ -0,0 +1,85 @@
> +#!/bin/bash
> +###############################################################################
> +# #
> +# IPFire.org - A linux based firewall #
> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
> +# #
> +# 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 <http://www.gnu.org/licenses/>. #
> +# #
> +###############################################################################
> +
> +version="2024-12-10 - v02"
> +
> +############### Functions ###############
> +
> +msg_log () {
> + logger --tag "${tagName}" "$*"
> + if tty --silent ; then
> + echo "${tagName}:" "$*"
> + fi
> +}
> +
> +# check for a valid name
> +check_name () {
> + local theName="${1}"
> +
> + regex='^[a-zA-Z0-9_]+$' # no dash or plus, alpha numeric only
> + regex1='^(allow|block)$' # allow and block are reserved NAMEs
> + if [[ ! "${theName}" =~ $regex ]] || [[ "${theName}" =~ $regex1 ]] ; then
> + msg_log "error: rpz: the NAME is not valid: \"${theName}\". exit."
> + exit 101
> + fi
> +}
> +
> +set_permissions () {
> + chown nobody:nobody "$@"
> + chmod 644 "$@"
> +}
> +
> +check_unbound_conf () {
> + local thecheckConf="${1:-yes}" # check config default is yes
> +
> + # check the above config files
> + if [[ "${thecheckConf}" == yes ]] ; then
> + msg_log "info: rpz: check for errors with \"unbound-checkconf\""
> +
> + if ! unbound-checkconf ; then
> + msg_log "error: rpz: unbound-checkconf found invalid configuration."
> + msg_log \
> + "error: rpz: In Terminal run the command \"unbound-checkconf\" for more information. exit."
> + exit 102
> + fi
> + fi
> +}
> +
> +unbound_control_reload () {
> + local theReload="${1:-yes}" # reload default is yes
> +
> + if [[ "${theReload}" == yes ]] ; then
> + # reload due to the changes
> + msg_log "info: rpz: run \"unbound-control reload\""
> +
> + if ! unbound-control reload ; then
> + msg_log "error: rpz: unbound-control reload. exit."
> + exit 109
> + fi
> + fi
> +}
> +
> +unbound_restart () {
> + # restart due to the changes
> + msg_log "info: rpz: run \"unbound restart\""
> +
> + /usr/local/bin/unboundctrl restart
> +}
> diff --git a/config/rpz/rpz-make b/config/rpz/rpz-make
> new file mode 100644
> index 000000000..927d55170
> --- /dev/null
> +++ b/config/rpz/rpz-make
> @@ -0,0 +1,203 @@
> +#!/bin/bash
> +###############################################################################
> +# #
> +# IPFire.org - A linux based firewall #
> +# Copyright (C) 2024-2025 IPFire Team <info(a)ipfire.org> #
> +# #
> +# 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 <http://www.gnu.org/licenses/>. #
> +# #
> +###############################################################################
> +
> +version="2025-01-11 - v14"
> +
> +############### Functions ###############
> +
> +source /usr/sbin/rpz-functions
> +
> +# create the config file for allow
> +make_allow_config () {
> + local theLog="${1:-yes}" # log default ON
> + local theConfig="/etc/unbound/local.d/00-rpz.conf" # output zone conf file
> + local theList="/var/ipfire/dns/rpz/allowlist" # input custom list of domains
> +
> + msg_log "info: rpz: make config file \"00-rpz.conf\""
> +
> + echo "server:
> + module-config: \"respip validator iterator\"" > "${theConfig}"
> +
> + # does allow list exist?
> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
> +
> + echo "rpz:
> + name: allow.rpz
> + zonefile: /etc/unbound/zonefiles/allow.rpz
> + rpz-action-override: passthru
> + rpz-log: ${theLog}
> + rpz-log-name: allow
> + rpz-signal-nxdomain-ra: yes" >> "${theConfig}"
> +
> + fi
> +
> + # set-up zonefile - unbound requires these settings for rpz files
> + set_permissions "${theConfig}"
> +}
> +
> +# create the config file for block
> +make_block_config () {
> + local theLog="${1:-yes}" # log default ON
> + local theConfig="/etc/unbound/local.d/block.rpz.conf" # output zone conf file
> + local theList="/var/ipfire/dns/rpz/blocklist" # input custom list of domains
> +
> + msg_log "info: rpz: make config file \"block.rpz.conf\""
> +
> + # does block list exist?
> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
> +
> + echo "rpz:
> + name: block.rpz
> + zonefile: /etc/unbound/zonefiles/block.rpz
> + rpz-action-override: nxdomain
> + rpz-log: ${theLog}
> + rpz-log-name: block
> + rpz-signal-nxdomain-ra: yes" > "${theConfig}"
> +
> + # set-up zonefile - unbound requires these settings for rpz files
> + set_permissions "${theConfig}"
> + else
> + # no - trash the config file
> + rm --verbose /etc/unbound/local.d/block.rpz.conf
> + fi
> +}
> +
> +# create an RPZ file for allow or block
> +make_rpz_file () {
> + local theName="${1}" # allow or block
> + local theAction='.' # the default is nxdomain or block
> + local actionList
> +
> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
> + local theZonefile="/etc/unbound/zonefiles/${theName}.rpz" # output file for RPZ
> +
> + # does a list exist?
> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
> +
> + # for allow set to passthru
> + [[ "${theName}" == allow ]] && theAction='rpz-passthru.'
> +
> + # drop any extra "blanks" and add "CNAME <RPZ action>." to each line
> + actionList=$( awk '{$1=$1};1' "${theList}" |
> + sed "/^[^;].*[[:alnum:]]/ s|$| CNAME ${theAction}|" )
> +
> + msg_log "info: rpz: create zonefile for \"${theName}list\""
> +
> +echo "; Name: ${theName} list
> +; Last modified: $(date "+%Y-%m-%d at %H.%M.%S %Z")
> +;
> +; domains with actions list
> +;
> +${actionList}" > "${theZonefile}"
> +
> + # set-up zonefile - unbound requires these settings for rpz files
> + set_permissions "${theZonefile}"
> + # set-up allow/block list files
> + set_permissions "${theList}"
> + else
> + msg_log "info: rpz: the ${theList} is empty."
> +
> + rm --verbose "${theZonefile}" # trash the RPZ file
> + fi
> +}
> +
> +# check if allow/block list is valid
> +validate_list () {
> + local theName="${1}" # allow or block
> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
> +
> + # remove good:
> + # - properly formated domain names with or without leading wildcard
> + # - properly formated top level domain (TLD) names with wildcard
> + # - blank lines and comment lines
> + # remaining lines are considered "bad"
> + bad_lines=$( sed --regexp-extended \
> + '/^(\*\.)?([a-zA-Z0-9](([a-zA-Z0-9\-]){0,61}[a-zA-Z0-9])?\.)+([a-zA-Z]{2,}|xn--[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])$/d ;
> + /^(\*\.)([a-z]{2,61}|xn--[a-z0-9]{1,60})$/d ;
> + /^$/d ; /^;/d' "${theList}" )
> +
> + if [[ ! -z "${bad_lines}" ]] ; then
> + msg_log "error: rpz: invalid line(s) in ${theList}."
> + printf "%s\n" "bad line(s): ${bad_lines}"
> + exit 110
> + fi
> +}
> +
> +
> +############### Main ###############
> +
> +tagName="unbound"
> +
> +rpzName="${1}" # input RPZ name
> +
> +rpzLog="yes" # log default is yes
> +ucReload="yes" # reload default is yes
> +
> +while [[ $# -gt 0 ]] ; do
> + case "$1" in
> + --no-log ) rpzLog="no" ;;
> + --no-reload ) ucReload="no" ; checkConf="no" ;;
> + esac
> + shift # Shift after checking all the cases to get next option
> +done
> +
> +case "${rpzName}" in
> + # make a new allow or block rpz file
> +
> + allow )
> + validate_list 'allow' # is the allowlist valid?
> + make_allow_config "${rpzLog}"
> + make_rpz_file 'allow'
> + ;;
> +
> + allowblock )
> + validate_list 'allow' # is the list valid?
> + make_allow_config "${rpzLog}"
> + make_rpz_file 'allow'
> + ;&
> +
> + block )
> + validate_list 'block' # is the blocklist valid?
> + make_block_config "${rpzLog}"
> + make_rpz_file 'block'
> + ;;
> +
> + reload )
> + check_unbound_conf "${checkConf}"
> + ;;
> +
> + unbound-restart )
> + check_unbound_conf "${checkConf}"
> + unbound_restart
> + exit
> + ;;
> +
> + * )
> + msg_log "error: rpz: missing or incorrect parameter"
> + printf "Usage: $(basename "$0") <NAME> <OPTION> <OPTION>\n"
> + printf "Version: ${version}\n"
> + exit 108
> + ;;
> +esac
> +
> +unbound_control_reload "${ucReload}"
> +
> +exit
> diff --git a/config/rpz/rpz-metrics b/config/rpz/rpz-metrics
> new file mode 100755
> index 000000000..4d43e1629
> --- /dev/null
> +++ b/config/rpz/rpz-metrics
> @@ -0,0 +1,170 @@
> +#!/bin/bash
> +###############################################################################
> +# #
> +# IPFire.org - A linux based firewall #
> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
> +# #
> +# 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 <http://www.gnu.org/licenses/>. #
> +# #
> +###############################################################################
> +
> +version="2025-01-20 - v25"
> +
> +############### Main ###############
> +
> +weeks="2" # default to two message logs
> +sortBy="name" # default "by name"
> +rpzActive="enabled" # default "enabled only"
> +
> +while [[ $# -gt 0 ]] ; do
> + case "$1" in
> + --by-names | --by-name | name ) sortBy="name" ;;
> +
> + --by-hits | --by-hit | hits | hit ) sortBy="hit" ;;
> +
> + --by-lines | --by-line | lines | line ) sortBy="line" ;;
> +
> + --by-effect ) sortBy="effect" ;;
> +
> + --enabled-only ) rpzActive="enabled" ;;
> +
> + --active-all | --all | all ) rpzActive="all" ;;
> +
> + [0-9] | [0-9][0-9] ) weeks=$1 ;;
> + esac
> + shift # Shift after checking all the cases to get next option
> +done
> +
> +# get the list of message logs for N weeks
> +messageLogs=$( find /var/log/messages* -type f | sort --version-sort |
> + head -"${weeks}" )
> +
> +# get the list of RPZ names & counts from the message log(s)
> +rpzNameCount=$( for logf in ${messageLogs} ; do
> + zgrep --text --fixed-strings 'info: rpz: applied' "${logf}" |
> + awk '$10 ~ /\[\w*]/ { print $10 }' ;
> + done | sort | uniq --count )
> +
> +# flip results and remove brackets `[` and `]`
> +rpzNameCount=$( echo "${rpzNameCount}" |
> + awk '{ print $2, $1 }' |
> + sed --regexp-extended 's|^\[(.*)\]|\1|' )
> +
> +# grab only names
> +rpzNames=$( echo "${rpzNameCount}" | awk '{ print $1 }' )
> +
> +# get list of RPZ files
> +rpzFileList=$( find /etc/unbound/zonefiles -type f -iname "*.rpz" )
> +
> +# get basename of those files
> +rpzBaseNames=$( echo "${rpzFileList}" |
> + sed 's|/etc/unbound/zonefiles/||g ; s|\.rpz||g ;' )
> +
> +# add to rpzNames
> +rpzNames="${rpzNames}"$'\n'"${rpzBaseNames}"
> +
> +# drop duplicate names
> +rpzNames=$( echo "${rpzNames}" | sort --unique )
> +
> +# get line count for each RPZ
> +lineCount=$( echo "${rpzFileList}" | xargs wc -l )
> +
> +# get comment line count and blank line count for each RPZ
> +commentCount=$( echo "${rpzFileList}" | xargs grep --count -e "^$" -e "^;" )
> +
> +# get modified date each RPZ
> +modDateList=$( echo "${rpzFileList}" | xargs stat -c '%.10y %n' )
> +
> +ucListAuthZones=$( unbound-control list_auth_zones )
> +
> +# get width of RPZ names
> +pWidth=$( echo "${rpzNames}" | awk '{ print $1" " }' | wc -L )
> +pFormat="%-${pWidth}s %-8s %-8s %8s %12s %12s\n"
> +
> +# print title line
> +printf "${pFormat}" "name" "hits" "active" "lines" " hits/line" "last download"
> +printf -- "--------------"
> +
> +theResults=""
> +totalLines=0
> +totalHits=0
> +while read -r theName
> +do
> + printf -- "--" # pretend progress bar
> +
> + # is this RPZ list active?
> + theActive="disabled"
> + if grep --quiet "^${theName}\.rpz" <<< "${ucListAuthZones}"
> + then
> + theActive="enabled"
> + else
> + [[ "${rpzActive}" == enabled ]] && continue
> + fi
> +
> + # get hit count
> + theHits="0"
> + if output=$( grep "^${theName}\s" <<< "${rpzNameCount}" ) ; then
> + theHits=$( echo "${output}" | awk '{ print $2 }' )
> + totalHits=$(( totalHits + theHits ))
> + fi
> +
> + # get line count
> + theLines="n/a"
> + hitsPerLine="0"
> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${lineCount}" ) ; then
> + theLines=$( echo "${output}" | awk '{ print $1 }' )
> + totalLines=$(( totalLines + theLines ))
> +
> + if [[ "${theLines}" -gt 2 ]] ; then
> + hitsPerLine=$(( 100 * theHits / theLines ))
> + fi
> + fi
> +
> + # get modification date
> + theModDate="n/a"
> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${modDateList}" ) ; then
> + theModDate=$( echo "${output}" | awk '{ print $1 }' )
> + fi
> +
> + # add to results list
> + theResults+="${theName} ${theHits} ${theActive} ${theLines} ${hitsPerLine} ${theModDate}"$'\n'
> +
> +done <<< "${rpzNames}"
> +
> +case "${sortBy}" in
> + # sort by "active" then by "name"
> + name) sortArg=(-k3,3r -k1,1) ;;
> +
> + # sort by "active" then by "hits" then by "name"
> + hit) sortArg=(-k3,3r -k2,2nr -k1,1) ;;
> +
> + # sort by "active" then by "lines" then by "name"
> + line) sortArg=(-k3,3r -k4,4nr -k1,1) ;;
> +
> + # sort by "active" then by "effect" then by "name"
> + effect) sortArg=(-k3,3r -k5,5nr -k1,1) ;;
> +esac
> +
> +printf -- "--------------\n"
> +# remove blank lines, sort, print as columns
> +echo "${theResults}" |
> + awk '!/^[[:space:]]*$/' |
> + sort "${sortArg[@]}" |
> + awk --assign=width="${pWidth}" \
> + '{ printf "%-*s %-8s %-8s %8s %10s %% %12s\n", width, $1, $2, $3, $4, $5, $6 }'
> +
> +printf "${pFormat}" "" "=======" "" "========" "" ""
> +printf "${pFormat}" "Totals -->" "${totalHits}" "" "${totalLines}" "" ""
> +
> +exit
> diff --git a/config/rpz/rpz-sleep b/config/rpz/rpz-sleep
> new file mode 100755
> index 000000000..dd3603599
> --- /dev/null
> +++ b/config/rpz/rpz-sleep
> @@ -0,0 +1,58 @@
> +#!/bin/bash
> +###############################################################################
> +# #
> +# IPFire.org - A linux based firewall #
> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
> +# #
> +# 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 <http://www.gnu.org/licenses/>. #
> +# #
> +###############################################################################
> +
> +version="2024-08-16" # v05
> +
> +############### Functions ###############
> +
> +# send message to message log
> +msg_log () {
> + logger --tag "${tagName}" "$*"
> + if tty --silent ; then
> + echo "${tagName}:" "$*"
> + fi
> +}
> +
> +############### Main ###############
> +
> +tagName="unbound"
> +
> +sleepTime="${1:-5m}" # default to sleep for 5m (5 minutes)
> +
> +zoneList=$( unbound-control list_auth_zones | awk '{print $1}' )
> +
> +for zone in ${zoneList} ; do
> + printf "disable ${zone}\t"
> + unbound-control rpz_disable "${zone}"
> +done
> +
> +msg_log "info: rpz: disabled all zones for ${sleepTime}"
> +
> +sleep "${sleepTime}"
> +
> +for zone in ${zoneList} ; do
> + printf "enable ${zone}\t"
> + unbound-control rpz_enable "${zone}"
> +done
> +
> +msg_log "info: rpz: enabled all zones"
> +
> +exit
> diff --git a/config/rpz/rpz.de.pl b/config/rpz/rpz.de.pl
> new file mode 100644
> index 000000000..3770c6bb0
> --- /dev/null
> +++ b/config/rpz/rpz.de.pl
> @@ -0,0 +1,30 @@
> +# Added for Response Policy Zone (RPZ) add-on
> +%tr = (%tr,
> +'rpz' => 'Response Policy Zones (RPZ)',
> +'rpz apply' => 'Übernehmen',
> +'rpz cl allow enable' => 'Benutzerdefinierte Allowlist aktivieren:',
> +'rpz cl allow info' => 'Zugelassene Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
> +'rpz cl allow' => 'Benutzerdefinierte Allowlist',
> +'rpz cl block enable' => 'Benutzerdefinierte Blocklist aktivieren:',
> +'rpz cl block info' => 'Gesperrte Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
> +'rpz cl block' => 'Benutzerdefinierte Blocklist',
> +'rpz cl' => 'Benutzerdefinierte Listen',
> +'rpz exitcode 101' => 'Der Name enthält unzulässige Zeichen',
> +'rpz exitcode 102' => 'unbound-checkconf hat eine fehlerhafte Konfiguration ermittelt. Führen Sie das Kommando unbound-checkconf auf der Konsole aus, um weitere Informationen zu erhalten.',
> +'rpz exitcode 103' => 'Die benutzerdefinierte Allow-/Blocklist ist leer',
> +'rpz exitcode 104' => 'Ein Eintrag mit identischem Namen existiert bereits',
> +'rpz exitcode 105' => 'Die URL ist ungültig',
> +'rpz exitcode 106' => 'Eintrag kann nicht entfernt werden, der Name existiert nicht',
> +'rpz exitcode 107' => 'Der Name ist ungültig - nur "allow" oder "block" möglich',
> +'rpz exitcode 108' => 'Fehlende oder inkorrekte Parameter',
> +'rpz exitcode 109' => 'unbound-control reload ist fehlgeschlagen',
> +'rpz exitcode 110' => 'Die benutzerdefinierte Allow-/Blocklist enthält unzulässige Einträge',
> +'rpz exitcode 201' => 'Die Anmerkung enthält unzulässige Zeichen',
> +'rpz exitcode 202' => 'Ungültiger Eintrag in der benutzerdefinierten Allowlist, Zeile ',
> +'rpz exitcode 203' => 'Ungültiger Eintrag in der benutzerdefinierten Blocklist, Zeile ',
> +'rpz exitcode 204' => 'Ausgewählter Eintrag existiert nicht: ',
> +'rpz zf editor' => 'Zonendatei-Eintrag bearbeiten',
> +'rpz zf imported' => '(importiert aus rpz-config)',
> +'rpz zf remark info' => 'Erlaubte Zeichen sind a-z, A-Z, 0-9 und Unterstriche',
> +'rpz zf' => 'Zonendateien',
> +);
> diff --git a/config/rpz/rpz.en.pl b/config/rpz/rpz.en.pl
> new file mode 100644
> index 000000000..0720a8940
> --- /dev/null
> +++ b/config/rpz/rpz.en.pl
> @@ -0,0 +1,30 @@
> +# Added for Response Policy Zone (RPZ) add-on
> +%tr = (%tr,
> +'rpz' => 'Response Policy Zones (RPZ)',
> +'rpz apply' => 'Apply',
> +'rpz cl allow enable' => 'Enable custom allowlist:',
> +'rpz cl allow info' => 'Allowed domains (one per line)<br>Example: domain.com, *.domain.com',
> +'rpz cl allow' => 'Custom allowlist',
> +'rpz cl block enable' => 'Enable custom blocklist:',
> +'rpz cl block info' => 'Blocked domains (one per line)<br>Example: domain.com, *.domain.com',
> +'rpz cl block' => 'Custom blocklist',
> +'rpz cl' => 'Custom lists',
> +'rpz exitcode 101' => 'the NAME is not valid',
> +'rpz exitcode 102' => 'unbound-checkconf found invalid configuration. In the Terminal run the command unbound-checkconf for more information',
> +'rpz exitcode 103' => 'the allow/block list is empty',
> +'rpz exitcode 104' => 'duplicate - NAME already exists',
> +'rpz exitcode 105' => 'the URL is not valid',
> +'rpz exitcode 106' => 'cannot remove the NAME does not exist',
> +'rpz exitcode 107' => 'the NAME is not valid - "allow" or "block" only',
> +'rpz exitcode 108' => 'missing or incorrect parameter',
> +'rpz exitcode 109' => 'unbound-control reload failed',
> +'rpz exitcode 110' => 'custom Allowlist/Blocklist contains invalid entries',
> +'rpz exitcode 201' => 'the REMARK is not valid',
> +'rpz exitcode 202' => 'invalid entry in allowlist, line ',
> +'rpz exitcode 203' => 'invalid entry in blocklist, line ',
> +'rpz exitcode 204' => 'Selected entry does not exist: ',
> +'rpz zf editor' => 'Edit zonefiles entry',
> +'rpz zf imported' => '(imported from rpz-config)',
> +'rpz zf remark info' => 'Valid characters are a-z, A-Z, 0-9 and underscore.',
> +'rpz zf' => 'Zonefiles',
> +);
> diff --git a/config/rpz/rpz.es.pl b/config/rpz/rpz.es.pl
> new file mode 100644
> index 000000000..98628e4aa
> --- /dev/null
> +++ b/config/rpz/rpz.es.pl
> @@ -0,0 +1,30 @@
> +# Added for Response Policy Zone (RPZ) add-on
> +%tr = (%tr,
> +'rpz' => 'Zonas de política de respuesta (RPZ)',
> +'rpz apply' => 'Aplicar',
> +'rpz cl allow enable' => 'Habilitar la lista blanca personalizada:',
> +'rpz cl allow info' => 'Dominio permitido (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
> +'rpz cl allow' => 'Lista blanca personalizada',
> +'rpz cl block enable' => 'Habilitar la lista negra personalizada:',
> +'rpz cl block info' => 'Dominio bloqueado (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
> +'rpz cl block' => 'Lista negra personalizada',
> +'rpz cl' => 'Lista personalizada',
> +'rpz exitcode 101' => 'El NOMBRE no es válido',
> +'rpz exitcode 102' => 'unbound-checkconf ha encontrado una configuración no válida. Desde Terminal, ejecute el comando unbound-checkconf para mayor información',
> +'rpz exitcode 103' => 'La lista de permitidos/bloqueados está vacía',
> +'rpz exitcode 104' => 'duplicado - NOMBRE ya existe',
> +'rpz exitcode 105' => 'la URL no es válida',
> +'rpz exitcode 106' => 'no es posible eliminar el NOMBRE que no existe',
> +'rpz exitcode 107' => 'el NOMBRE no es válido - sólo "permitir" o "bloquear"',
> +'rpz exitcode 108' => 'parámetro faltante o incorrecto',
> +'rpz exitcode 109' => 'Error al recargar unbound-control',
> +'rpz exitcode 110' => 'la Lista blanca/Lista negra personalizada contiene entradas no válidas',
> +'rpz exitcode 201' => 'el COMENTARIO no es válido',
> +'rpz exitcode 202' => 'entrada no válida en la lista blanca, línea ',
> +'rpz exitcode 203' => 'entrada no válida en la lista negra, línea ',
> +'rpz exitcode 204' => 'La entrada seleccionada no existe: ',
> +'rpz zf editor' => 'Editar la entrada de archivos de zona',
> +'rpz zf imported' => '(importado de rpz-config)',
> +'rpz zf remark info' => 'Los caracteres válidos son a-z, A-Z, 0-9 y guión bajo',
> +'rpz zf' => 'Archivos de zona',
> +);
> diff --git a/config/rpz/rpz.fr.pl b/config/rpz/rpz.fr.pl
> new file mode 100644
> index 000000000..f35f3c2d0
> --- /dev/null
> +++ b/config/rpz/rpz.fr.pl
> @@ -0,0 +1,30 @@
> +# Added for Response Policy Zone (RPZ) add-on
> +%tr = (%tr,
> +'rpz' => 'Response Policy Zones (RPZ)',
> +'rpz apply' => 'Appliquer',
> +'rpz cl allow enable' => 'Activer la liste d\'autorisations personnalisée:',
> +'rpz cl allow info' => 'Domaines autorisés (un par ligne)<br>Example: domain.com, *.domain.com',
> +'rpz cl allow' => 'Liste d\'autorisations personnalisée',
> +'rpz cl block enable' => 'Activer la liste de blocage personnalisée:',
> +'rpz cl block info' => 'Domaines bloqués (un par ligne)<br>Example: domain.com, *.domain.com',
> +'rpz cl block' => 'liste de blocage personnalisé',
> +'rpz cl' => 'Listes personnalisées',
> +'rpz exitcode 101' => 'le NOM n\'est pas valide',
> +'rpz exitcode 102' => 'unbound-checkconf configuration non valide trouvée. Dans le terminal, exécutez la commande unbound-checkconf pour plus d\'informations',
> +'rpz exitcode 103' => 'la liste autoriser/bloquer est vide',
> +'rpz exitcode 104' => 'le NOM existe déjà',
> +'rpz exitcode 105' => 'L\'URL n\'est pas valide',
> +'rpz exitcode 106' => 'impossible de supprimer le NOM n\'existe pas',
> +'rpz exitcode 107' => 'le NOM n\'est pas valide - « autoriser » ou « bloquer » seulement',
> +'rpz exitcode 108' => 'paramètre manquant ou incorrect',
> +'rpz exitcode 109' => 'unbound-control rechargement échoué',
> +'rpz exitcode 110' => 'la liste autoriser/bloquer contient des entrées non valides',
> +'rpz exitcode 201' => 'la REMARQUE n\'est pas valable',
> +'rpz exitcode 202' => 'entrée non valide dans la liste d\'autorisation, ligne ',
> +'rpz exitcode 203' => 'entrée non valide dans la liste de blocs, ligne ',
> +'rpz exitcode 204' => 'L\'entrée sélectionnée n\'existe pas: ',
> +'rpz zf editor' => 'Modifier l\'entrée Fichiers Zone',
> +'rpz zf imported' => '(importé de rpz-config)',
> +'rpz zf remark info' => 'Les caractères valides sont a-z, A-Z, 0-9 et soulignement.',
> +'rpz zf' => 'Fichiers Zone',
> +);
> diff --git a/config/rpz/rpz.it.pl b/config/rpz/rpz.it.pl
> new file mode 100644
> index 000000000..ee81605c9
> --- /dev/null
> +++ b/config/rpz/rpz.it.pl
> @@ -0,0 +1,30 @@
> +# Added for Response Policy Zone (RPZ) add-on
> +%tr = (%tr,
> +'rpz' => 'Response Policy Zones (RPZ)',
> +'rpz apply' => 'Applica',
> +'rpz cl allow enable' => 'Abilita la Whitelist personalizzata:',
> +'rpz cl allow info' => 'Domini consentiti (uno per riga)<br>Esempio: domain.com, *.domain.com',
> +'rpz cl allow' => 'Whitelist personalizzata',
> +'rpz cl block enable' => 'Abilita la Blacklist personalizzata:',
> +'rpz cl block info' => 'Domini bloccati (uno per riga)<br>Esempio: domain.com, *.domain.com',
> +'rpz cl block' => 'Blacklist personalizzata',
> +'rpz cl' => 'Liste personalizzate',
> +'rpz exitcode 101' => 'il NOME non è valido',
> +'rpz exitcode 102' => 'unbound-checkconf ha trovato una configurazione non valida. Dal Terminale esegui il comando unbound-checkconf per maggiori informazioni',
> +'rpz exitcode 103' => 'l\'elenco consentiti/bloccati è vuoto',
> +'rpz exitcode 104' => 'duplicato - NAME esiste di già',
> +'rpz exitcode 105' => 'l\'URL non è valido',
> +'rpz exitcode 106' => 'non è possibile rimuovere il NOME non esiste',
> +'rpz exitcode 107' => 'il NOME non è valido - solo "consenti" o "blocca"',
> +'rpz exitcode 108' => 'parametro mancante o non corretto',
> +'rpz exitcode 109' => 'ricaricamento del controllo non associato non riuscito',
> +'rpz exitcode 110' => 'la Whitelist/Blacklist personalizzata contiene voci non valide',
> +'rpz exitcode 201' => 'l"OSSERVAZIONE non è valida',
> +'rpz exitcode 202' => 'voce non valida nella Whitelist, riga ',
> +'rpz exitcode 203' => 'voce non valida nella Blacklist, riga ',
> +'rpz exitcode 204' => 'La voce selezionata non esiste: ',
> +'rpz zf editor' => 'Modifica la voce dei file di zona',
> +'rpz zf imported' => '(importato da rpz-config)',
> +'rpz zf remark info' => 'I caratteri validi sono a-z, A-Z, 0-9 e trattino basso',
> +'rpz zf' => 'Zonefiles',
> +);
> diff --git a/config/rpz/rpz.tr.pl b/config/rpz/rpz.tr.pl
> new file mode 100644
> index 000000000..00226e192
> --- /dev/null
> +++ b/config/rpz/rpz.tr.pl
> @@ -0,0 +1,30 @@
> +# I�in eklendi Ayriyeten Response Policy Zone (RPZ)
> +%tr = (%tr,
> +'rpz' => 'Response Policy Zones (RPZ)',
> +'rpz apply' => 'Uygulamak',
> +'rpz cl allow enable' => '�zel Etkinlestir allowlist:',
> +'rpz cl allow info' => 'Izin verilmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
> +'rpz cl allow' => 'Etkinlestir allowlist',
> +'rpz cl block enable' => '�zel Etkinlestir blocklist:',
> +'rpz cl block info' => 'Engellenmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
> +'rpz cl block' => 'Engellenmis blocklist',
> +'rpz cl' => 'Engellenmis lists',
> +'rpz exitcode 101' => 'NAME ge�erli degil',
> +'rpz exitcode 102' => 'unbound-checkconf ge�ersiz yapilandirma bulundu. Terminalde daha fazla bilgi i�in Unbound-checkConf komutunu �alistirin',
> +'rpz exitcode 103' => 'allow/block liste bos',
> +'rpz exitcode 104' => 'kopyalamak - NAME zaten var',
> +'rpz exitcode 105' => 'URL ge�erli degil',
> +'rpz exitcode 106' => '�ikarilamiyor NAME yok',
> +'rpz exitcode 107' => 'NAME ge�erli degil - "allow" veya "block" yalniz',
> +'rpz exitcode 108' => 'Parametre eksik veya yanlis',
> +'rpz exitcode 109' => 'unbound-control basarisiz',
> +'rpz exitcode 110' => 'Engellenmis Allowlist/Blocklist ge�ersiz girisler i�erir',
> +'rpz exitcode 201' => 'REMARK ge�erli degil',
> +'rpz exitcode 202' => 'Ge�ersiz giris allowlist, line ',
> +'rpz exitcode 203' => 'Ge�ersiz giris blocklist, line ',
> +'rpz exitcode 204' => 'Se�ilen giris yok: ',
> +'rpz zf editor' => 'yazimlamak zonefiles giris',
> +'rpz zf imported' => '(ithal edildi rpz-config)',
> +'rpz zf remark info' => 'Ge�erli karakterler a-z, A-Z, 0-9ve alt�st.',
> +'rpz zf' => 'Zonefiles',
> +);
> diff --git a/html/cgi-bin/rpz.cgi b/html/cgi-bin/rpz.cgi
> new file mode 100644
> index 000000000..a821c92ac
> --- /dev/null
> +++ b/html/cgi-bin/rpz.cgi
> @@ -0,0 +1,923 @@
> +#!/usr/bin/perl
> +###############################################################################
> +# #
> +# IPFire.org - A linux based firewall #
> +# Copyright (C) 2005-2024 IPFire Team <info(a)ipfire.org> #
> +# #
> +# 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 <http://www.gnu.org/licenses/>. #
> +# #
> +###############################################################################
> +
> +use strict;
> +use Scalar::Util qw(looks_like_number);
> +
> +# debugging
> +#use warnings;
> +#use CGI::Carp 'fatalsToBrowser';
> +#use Data::Dumper;
> +
> +require '/var/ipfire/general-functions.pl';
> +require "${General::swroot}/lang.pl";
> +require "${General::swroot}/header.pl";
> +
> +###--- Extra HTML ---###
> +my $extraHead = <<END
> +<style>
> + /* alternating row background */
> + .tbl tr:nth-child(2n+2) {
> + background-color: var(--color-light-grey);
> + }
> + .tbl tr:nth-child(2n+3) {
> + background-color: var(--color-grey);
> + }
> + /* text styles */
> + .tbl th:not(:last-child) {
> + text-align: left;
> + }
> + div.right {
> + text-align: right;
> + margin-top: 0.5em;
> + }
> + /* customlist input */
> + textarea.domainlist {
> + margin: 0.5em 0;
> + resize: vertical;
> + min-height: 10em;
> + overflow: auto;
> + white-space: pre;
> + }
> + button[type=submit]:disabled {
> + opacity: 0.6;
> + }
> +</style>
> +END
> +;
> +###--- End of extra HTML ---###
> +
> +
> +### Settings ###
> +
> +# Request DNS service reload after configuration change
> +my $RPZ_RELOAD_FLAG = "${General::swroot}/dns/rpz/reload.flag";
> +
> +# Configuration file for all available zonefiles
> +# Format: index, name (unique), enabled (on/off), URL, remark
> +my $ZONEFILES_CONF = "${General::swroot}/dns/rpz/zonefiles.conf";
> +
> +# Configuration file for custom lists
> +# IDs: 0=allowlist, 1=blocklist, 2=options (allow/block enabled)
> +my $CUSTOMLISTS_CONF = "${General::swroot}/dns/rpz/customlists.conf";
> +
> +# Export custom lists to rpz-config
> +my $RPZ_ALLOWLIST = "${General::swroot}/dns/rpz/allowlist";
> +my $RPZ_BLOCKLIST = "${General::swroot}/dns/rpz/blocklist";
> +
> +
> +### Preparation ###
> +
> +# Create missing config files
> +unless(-f $ZONEFILES_CONF) { &General::system('touch', "$ZONEFILES_CONF"); }
> +unless(-f $CUSTOMLISTS_CONF) { &General::system('touch', "$CUSTOMLISTS_CONF"); }
> +
> +
> +## Global gui data
> +my $errormessage = "";
> +
> +## Global configuration data
> +my %zonefiles = ();
> +my %customlists = ();
> +&_zonefiles_load();
> +&_customlists_load();
> +
> +## Global CGI form data
> +my %cgiparams = ();
> +&Header::getcgihash(\%cgiparams);
> +
> +my $action = $cgiparams{'ACTION'} // 'NONE';
> +my $action_key = $cgiparams{'KEY'} // ''; # entry being edited, empty = none/new
> +
> +
> +###--- Process form actions ---###
> +
> +# Zonefiles action: Check whether the requested entry exists
> +if((substr($action, 0, 3) eq 'ZF_') && ($action_key)) {
> + unless(defined $zonefiles{$action_key}) {
> + $errormessage = &_rpz_error_tr(204, $action_key);
> + $action = 'NONE';
> + }
> +}
> +
> +## Perform actions
> +if($action eq 'ZF_SAVE') { ## Save new or modified zonefiles entry
> + if(&_action_zf_save()) {
> + $action = 'NONE'; # success, return to main page
> + &_http_prg_redirect();
> + } else {
> + $action = 'ZF_EDIT'; # error occured, keep editing
> + }
> +
> +} elsif($action eq 'ZF_TOGGLE') { ## Toggle on/off
> + if(&_action_zf_toggle()) {
> + $action = 'NONE';
> + &_http_prg_redirect();
> + }
> +
> +} elsif($action eq 'ZF_REMOVE') { ## Remove entry
> + if(&_action_zf_remove()) {
> + $action = 'NONE';
> + &_http_prg_redirect();
> + }
> +
> +} elsif($action eq 'CL_SAVE') { ## Save custom lists
> + if(&_action_cl_save()) {
> + $action = 'NONE';
> + &_http_prg_redirect();
> + }
> +
> +} elsif($action eq 'RPZ_RELOAD') { ## Reload dns configuration
> + if(&_action_rpz_reload()) {
> + $action = 'NONE';
> + &_http_prg_redirect();
> + }
> +
> +} elsif($action eq 'UNB_RESTART') { ## Restart unbound service
> + if(&_action_unb_restart()) {
> + $action = 'NONE';
> + &_http_prg_redirect();
> + }
> +
> +}
> +
> +
> +###--- Start GUI ---###
> +
> +## Start http output
> +&Header::showhttpheaders();
> +
> +# Start HTML
> +&Header::openpage($Lang::tr{'rpz'}, 1, $extraHead);
> +&Header::openbigbox('100%', 'left', '');
> +
> +# Show error messages
> +if($errormessage) {
> + &_print_message($errormessage);
> +}
> +
> +# Handle zonefile add/edit mode
> +if($action eq "ZF_EDIT") {
> + &_print_zonefile_editor();
> +
> + # Finalize page and exit cleanly
> + &Header::closebigbox();
> + &Header::closepage();
> + exit(0);
> +}
> +
> +# Show gui elements
> +&_print_zonefiles();
> +&_print_customlists();
> +&_print_gui_extras();
> +
> +&Header::closebigbox();
> +&Header::closepage();
> +
> +###--- End of GUI ---###
> +
> +
> +###--- Internal configuration file functions ---###
> +
> +# Load all available zonefiles from rpz-config and the internal configuration
> +sub _zonefiles_load {
> + # Clean start
> + %zonefiles = ();
> +
> + # Source 1: Get the currently enabled zonefiles from rpz-config (expected format [name]=[URL])
> + my @enabled_files = &General::system_output('/usr/sbin/rpz-config', 'list');
> +
> + foreach my $row (@enabled_files) {
> + chomp($row);
> +
> + # Use regex instead of split() to skip non-matching lines
> + next unless($row =~ /^(\w+)=(.+)$/);
> + my ($name, $url) = ($1, $2);
> +
> + # Unique names are already guaranteed by rpz-config
> + if(&_rpz_validate_zonefile($name, $url, '', 0) == 0) {
> + # Populate global data hash, mark all found entries as enabled
> + my %entry = ('enabled' => 'on',
> + 'url' => $url,
> + 'remark' => $Lang::tr{'rpz zf imported'});
> +
> + $zonefiles{$name} = \%entry;
> + }
> + }
> +
> + # Source 2: Get additional data and disabled entries from configuration file
> + my %configured_files = ();
> + &General::readhasharray($ZONEFILES_CONF, \%configured_files);
> +
> + foreach my $row (values (%configured_files)) {
> + my ($name, $enabled, $url, $remark) = @$row;
> + $remark //= "";
> +
> + next unless($name);
> +
> + # Check whether this row belongs to an entry already imported from rpz-config
> + if(defined $zonefiles{$name}) {
> + # Existing entry, only merge additional data
> + $zonefiles{$name}{'remark'} = $remark;
> + } else {
> + # Skip entry if it is marked as enabled but not found by rpz-config. It was then deleted manually
> + if($enabled ne 'on') {
> + # Populate global data hash
> + my %entry = ('enabled' => 'off',
> + 'url' => $url // "",
> + 'remark' => $remark);
> +
> + $zonefiles{$name} = \%entry;
> + }
> + }
> + }
> +}
> +
> +# Save internal zonefiles configuration
> +sub _zonefiles_save_conf {
> + my $index = 0;
> + my %export = ();
> +
> + # Loop trough all zonefiles and create "hasharray" type export
> + foreach my $name (keys %zonefiles) {
> + my @entry = ($name,
> + $zonefiles{$name}{'enabled'},
> + $zonefiles{$name}{'url'},
> + $zonefiles{$name}{'remark'});
> +
> + $export{$index++} = \@entry;
> + }
> +
> + &General::writehasharray($ZONEFILES_CONF, \%export);
> +}
> +
> +# Load custom lists from rpz-config and the internal configuration
> +sub _customlists_load {
> + # Clean start
> + %customlists = ();
> +
> + # Load configuration file
> + my %lists_conf = ();
> + &General::readhasharray($CUSTOMLISTS_CONF, \%lists_conf);
> +
> + # Get list options, enabled by default to start import
> + $customlists{'allow'}{'enabled'} = $lists_conf{2}[0] // 'on';
> + $customlists{'block'}{'enabled'} = $lists_conf{2}[1] // 'on';
> +
> + # Import enabled list from rpz-config, otherwise retrieve stored or empty list from configuration file
> + if($customlists{'allow'}{'enabled'} eq 'on') {
> + &_customlist_import('allow', $RPZ_ALLOWLIST);
> + } else {
> + $customlists{'allow'}{'list'} = $lists_conf{0} // [];
> + }
> + if($customlists{'block'}{'enabled'} eq 'on') {
> + &_customlist_import('block', $RPZ_BLOCKLIST);
> + } else {
> + $customlists{'block'}{'list'} = $lists_conf{1} // [];
> + }
> +}
> +
> +# Save internal custom lists configuration
> +sub _customlists_save_conf {
> + my %export = ();
> +
> + # Match IDs with import function
> + $export{0} = $customlists{'allow'}{'list'};
> + $export{1} = $customlists{'block'}{'list'};
> + $export{2} = [$customlists{'allow'}{'enabled'}, $customlists{'block'}{'enabled'}];
> +
> + &General::writehasharray($CUSTOMLISTS_CONF, \%export);
> +}
> +
> +# Import a custom list from plain file, returns empty list if file is missing
> +sub _customlist_import {
> + my ($listname, $filename) = @_;
> + my @list = ();
> +
> + # File exists, load and check all lines
> + if(-f $filename) {
> + open(my $FH, '<', $filename) or die "Can't read $filename: $!";
> + while(my $line = <$FH>) {
> + chomp($line);
> + push(@list, $line);
> + }
> + close($FH);
> +
> + # Clean up imported data
> + &_rpz_validate_customlist(\@list, 1);
> + }
> +
> + $customlists{$listname}{'list'} = \@list;
> +}
> +
> +# Export a custom list to plain file or clear file if list is disabled
> +sub _customlist_export {
> + my ($listname, $filename) = @_;
> + return unless(defined $customlists{$listname});
> +
> + # Write enabled domain list to file, otherwise save empty file
> + open(my $FH, '>', $filename) or die "Can't write $filename: $!";
> +
> + if($customlists{$listname}{'enabled'} eq 'on') {
> + foreach my $line (@{$customlists{$listname}{'list'}}) {
> + print $FH "$line\n";
> + }
> + } else {
> + print $FH "; Note: This list is currently disabled by $ENV{'SCRIPT_NAME'}\n";
> + }
> +
> + close($FH);
> +}
> +
> +
> +###--- Internal gui functions ---###
> +
> +# Show simple message box
> +sub _print_message {
> + my ($message, $title) = @_;
> + $title ||= $Lang::tr{'error messages'};
> +
> + &Header::openbox('100%', 'left', $title);
> + print "<span>$message</span>";
> + &Header::closebox();
> +}
> +
> +# Show all zone files and related gui elements
> +sub _print_zonefiles {
> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf'});
> +
> + print <<END
> +<table class="tbl" width="100%">
> + <tr>
> + <th>$Lang::tr{'name'}</th>
> + <th>URL</th>
> + <th>$Lang::tr{'remark'}</th>
> + <th colspan="3">$Lang::tr{'action'}</th>
> + </tr>
> +END
> +;
> +
> + # Sort zonefiles by name and loop trough all entries
> + foreach my $name (sort keys %zonefiles) {
> +
> + # Toggle button label translation
> + my $toggle_tr = ($zonefiles{$name}{'enabled'} eq 'on') ? $Lang::tr{'click to disable'} : $Lang::tr{'click to enable'};
> +
> + print <<END
> + <tr>
> + <td>$name</td>
> + <td>$zonefiles{$name}{'url'}</td>
> + <td>$zonefiles{$name}{'remark'}</td>
> +
> + <td align="center" width="5%">
> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
> + <input type="hidden" name="KEY" value="$name">
> + <input type="hidden" name="ACTION" value="ZF_TOGGLE">
> + <input type="image" src="/images/$zonefiles{$name}{'enabled'}.gif" title="$toggle_tr" alt="$toggle_tr">
> + </form>
> + </td>
> + <td align="center" width="5%">
> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
> + <input type="hidden" name="KEY" value="$name">
> + <input type="hidden" name="ACTION" value="ZF_EDIT">
> + <input type="image" src="/images/edit.gif" title="$Lang::tr{'edit'}" alt="$Lang::tr{'edit'}">
> + </form>
> + </td>
> + <td align="center" width="5%">
> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
> + <input type="hidden" name="KEY" value="$name">
> + <input type="hidden" name="ACTION" value="ZF_REMOVE">
> + <input type="image" src="/images/delete.gif" title="$Lang::tr{'remove'}" alt="$Lang::tr{'remove'}">
> + </form>
> + </td>
> + </tr>
> +END
> +;
> + }
> +
> + # Disable reload button if not needed
> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
> +
> + print <<END
> +</table>
> +
> +<div class="right">
> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
> + <input type="hidden" name="KEY" value="">
> + <button type="submit" name="ACTION" value="ZF_EDIT">$Lang::tr{'add'}</button>
> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
> + </form>
> +</div>
> +END
> +;
> +
> + &Header::closebox();
> +}
> +
> +# Show zonefiles entry editor
> +sub _print_zonefile_editor {
> +
> + # Key specified: Edit existing entry
> + if(($action_key) && (defined $zonefiles{$action_key})) {
> + # Load data to be edited, but don't override already present values (allows user to edit after error)
> + $cgiparams{'ZF_NAME'} //= $action_key;
> + $cgiparams{'ZF_URL'} //= $zonefiles{$action_key}{'url'};
> + $cgiparams{'ZF_REMARK'} //= $zonefiles{$action_key}{'remark'};
> + }
> +
> + # Fallback to empty form
> + $cgiparams{'ZF_NAME'} //= "";
> + $cgiparams{'ZF_URL'} //= "";
> + $cgiparams{'ZF_REMARK'} //= "";
> +
> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf editor'});
> +
> + print <<END
> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
> +<input type="hidden" name="KEY" value="$action_key">
> +<table width="100%">
> + <tr>
> + <td width="20%">$Lang::tr{'name'}: <img src="/blob.gif" alt="*"></td>
> + <td><input type="text" name="ZF_NAME" value="$cgiparams{'ZF_NAME'}" size="40" maxlength="32" title="$Lang::tr{'rpz zf remark info'}" pattern="[a-zA-Z0-9_]{1,32}" required></td>
> + </tr>
> + <tr>
> + <td width="20%">URL: <img src="/blob.gif" alt="*"></td>
> + <td><input type="url" name="ZF_URL" value="$cgiparams{'ZF_URL'}" size="40" maxlength="128" required></td>
> + </tr>
> + <tr>
> + <td width="20%">$Lang::tr{'remark'}:</td>
> + <td><input type="text" name="ZF_REMARK" value="$cgiparams{'ZF_REMARK'}" size="40" maxlength="32"></td>
> + </tr>
> + <tr>
> + <td colspan="2"><hr></td>
> + </tr>
> + <tr>
> + <td width="55%"><img src="/blob.gif" alt="*"> $Lang::tr{'required field'}</td>
> + <td align="right"><button type="submit" name="ACTION" value="ZF_SAVE">$Lang::tr{'save'}</button></td>
> + </tr>
> +</table>
> +</form>
> +
> +<div class="right">
> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
> + <button type="submit" name="ACTION" value="NONE">$Lang::tr{'back'}</button>
> + </form>
> +</div>
> +END
> +;
> +
> + &Header::closebox();
> +}
> +
> +# Show custom allow/block files and related gui elements
> +sub _print_customlists {
> +
> + # Load lists from config, unless they are currently being edited
> + if($action ne 'CL_SAVE') {
> + $cgiparams{'ALLOW_LIST'} = join("\n", @{$customlists{'allow'}{'list'}});
> + $cgiparams{'BLOCK_LIST'} = join("\n", @{$customlists{'block'}{'list'}});
> +
> + $cgiparams{'ALLOW_ENABLED'} = ($customlists{'allow'}{'enabled'} eq 'on') ? 'on' : undef;
> + $cgiparams{'BLOCK_ENABLED'} = ($customlists{'block'}{'enabled'} eq 'on') ? 'on' : undef;
> + }
> +
> + # Fallback to empty form
> + $cgiparams{'ALLOW_LIST'} //= "";
> + $cgiparams{'BLOCK_LIST'} //= "";
> +
> + # HTML checkboxes, unchecked = no or undef value in POST data
> + my %checked = ();
> + $checked{'ALLOW_ENABLED'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? " checked" : "";
> + $checked{'BLOCK_ENABLED'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? " checked" : "";
> +
> + # Disable reload button if not needed
> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
> +
> + &Header::openbox('100%', 'left', $Lang::tr{'rpz cl'});
> +
> + print <<END
> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
> +<table width="100%">
> + <tr>
> + <td colspan="2"><b>$Lang::tr{'rpz cl allow'}</b><br>$Lang::tr{'rpz cl allow info'}</td>
> + <td colspan="2"><b>$Lang::tr{'rpz cl block'}</b><br>$Lang::tr{'rpz cl block info'}</td>
> + </tr>
> + <tr>
> + <td colspan="2"><textarea name="ALLOW_LIST" class="domainlist" cols="45">
> +$cgiparams{'ALLOW_LIST'}</textarea></td>
> + <td colspan="2"><textarea name="BLOCK_LIST" class="domainlist" cols="45">
> +$cgiparams{'BLOCK_LIST'}</textarea></td>
> + </tr>
> + <tr>
> + <td><label for="allow_enabled">$Lang::tr{'rpz cl allow enable'}</label></td>
> + <td width="15%"><input type="checkbox" name="ALLOW_ENABLED" id="allow_enabled"$checked{'ALLOW_ENABLED'}></td>
> + <td><label for="block_enabled">$Lang::tr{'rpz cl block enable'}</label></td>
> + <td width="15%"><input type="checkbox" name="BLOCK_ENABLED" id="block_enabled"$checked{'BLOCK_ENABLED'}></td>
> + </tr>
> + <tr>
> + <td colspan="4"><hr></td>
> + </tr>
> + <tr>
> + <td align="right" colspan="4">
> + <button type="submit" name="ACTION" value="CL_SAVE">$Lang::tr{'save'}</button>
> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
> + </td>
> +</table>
> +</form>
> +END
> +;
> +
> + &Header::closebox();
> +}
> +
> +# Output javascript and extra gui elements
> +sub _print_gui_extras {
> +
> + # Apply/Restart button modifier key handler
> + if(&_rpz_needs_reload()) {
> + print <<END
> +<script>
> + // Commit modifier key handler
> + (function(jq, document) {
> + var keyEventsOn = false; // Keyboard events attached
> + var keyModify = false; // Modifier key pressed
> + var mouseHover = false; // Mouse over commit button
> + var btnModified = false; // Button modified to "Restart"
> +
> + // Document-level key events, enable only while cursor is over button
> + function attachKeyEvents() {
> + if(keyEventsOn) {
> + return;
> + }
> + keyEventsOn = true;
> +
> + jq(document).on("keydown.rpz", function(event) {
> + if((!keyModify) && event.shiftKey) {
> + keyModify = true;
> + handleModify();
> + }
> + });
> + jq(document).on("keyup.rpz", function(event) {
> + if(keyModify && (!event.shiftKey)) {
> + keyModify = false;
> + handleModify();
> + }
> + });
> + }
> + function removeKeyEvents() {
> + keyModify = false;
> + if(keyEventsOn) {
> + jq(document).off("keydown.rpz keyup.rpz");
> + keyEventsOn = false;
> + }
> + }
> +
> + // Attach mouse hover events to commit buttons
> + function attachMouseEvents() {
> + jq("button.commit").on("mouseenter", function(event) {
> + if(!mouseHover) {
> + mouseHover = true;
> + attachKeyEvents();
> + // Handle already pressed key
> + keyModify = !!(event.shiftKey);
> + handleModify();
> + }
> + });
> +
> + // Cursor moved away: Disable key listener to minimize events
> + jq("button.commit").on("mouseleave", function() {
> + if(mouseHover) {
> + mouseHover = false;
> + removeKeyEvents();
> + handleModify();
> + }
> + });
> + }
> +
> + // Modify commit button
> + function handleModify() {
> + let modify = mouseHover && keyModify;
> + if(btnModified != modify) {
> + if(modify) {
> + jq("button.commit").text("$Lang::tr{'restart'}").val("UNB_RESTART");
> + } else {
> + jq("button.commit").text("$Lang::tr{'rpz apply'}").val("RPZ_RELOAD");
> + }
> + btnModified = modify;
> + }
> + }
> +
> + // jQuery DOM ready
> + jq(function() {
> + attachMouseEvents();
> + });
> + })(jQuery, document);
> +</script>
> +END
> +;
> + } # End of modifier key handler
> +
> +}
> +
> +
> +###--- Internal action processing functions ---###
> +
> +# Toggle zonefile on/off
> +sub _action_zf_toggle {
> + return unless(defined $zonefiles{$action_key});
> +
> + my $result = 0;
> + my $enabled = $zonefiles{$action_key}{'enabled'};
> +
> + # Perform toggle action
> + if($enabled eq 'on') {
> + $enabled = 'off';
> + $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
> + } else {
> + $enabled = 'on';
> + $result = &General::system('/usr/sbin/rpz-config', 'add', $action_key, $zonefiles{$action_key}{'url'}, '--no-reload');
> + }
> +
> + # Check for errors, request service reload on success
> + return unless &_rpz_check_result($result, 1);
> +
> + # Save changes
> + $zonefiles{$action_key}{'enabled'} = $enabled;
> + &_zonefiles_save_conf();
> +
> + return 1;
> +}
> +
> +# Remove zonefile
> +sub _action_zf_remove {
> + return unless(defined $zonefiles{$action_key});
> +
> + # Remove from rpz-config if currently active
> + if($zonefiles{$action_key}{'enabled'} eq 'on') {
> + my $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
> +
> + # Check for errors, request service reload on success
> + return unless &_rpz_check_result($result, 1);
> + }
> +
> + # Remove from data hash and save changes
> + delete $zonefiles{$action_key};
> + &_zonefiles_save_conf();
> +
> + # Clear action_key, as the entry is now removed entirely
> + $action_key = "";
> +
> + return 1;
> +}
> +
> +# Create or update zonefile entry
> +# Returns undef if gui needs to stay in editor mode
> +sub _action_zf_save {
> + my $result = 0;
> +
> + my $name = $cgiparams{'ZF_NAME'} // "";
> + my $url = $cgiparams{'ZF_URL'} // "";
> + my $remark = $cgiparams{'ZF_REMARK'} // "";
> + my $enabled = 'on'; # Enable new entries by default
> +
> + # Note on variables:
> + # name = unique key, will be used to address the entry
> + # action_key = name of the entry being edited, empty for new entry
> +
> + # Only check for unique name if it changed
> + # (this also checks new entries because the action_key is empty in this case)
> + $result = &_rpz_validate_zonefile($name, $url, $remark, (lc($name) ne lc($action_key)));
> + return unless &_rpz_check_result($result, 0);
> +
> + # Edit existing entry: Determine what was changed
> + if(($action_key) && (defined $zonefiles{$action_key})) {
> + # Name und URL remain unchanged, only save remark and finish
> + if(($name eq $action_key) && ($url eq $zonefiles{$action_key}{'url'})) {
> + $zonefiles{$action_key}{'remark'} = $remark;
> + &_zonefiles_save_conf();
> +
> + return 1;
> + }
> +
> + # Entry was changed and needs to be recreated, preserve status
> + $enabled = $zonefiles{$action_key}{'enabled'};
> +
> + # Remove from rpz-config
> + return unless &_action_zf_remove();
> + }
> +
> + # Add new entry to rpz-config
> + if($enabled eq 'on') {
> + $result = &General::system('/usr/sbin/rpz-config', 'add', $name, $url, '--no-reload');
> +
> + # Check for errors, request service reload on success
> + return unless &_rpz_check_result($result, 1);
> + }
> +
> + # Add to global data hash and save changes
> + my %entry = ('enabled' => $enabled,
> + 'url' => $url,
> + 'remark' => $remark);
> +
> + $zonefiles{$name} = \%entry;
> + &_zonefiles_save_conf();
> +
> + return 1;
> +}
> +
> +# Save custom lists
> +sub _action_cl_save {
> + return unless((defined $cgiparams{'ALLOW_LIST'}) && (defined $cgiparams{'BLOCK_LIST'}));
> +
> + my $result = 0;
> +
> + my @allowlist = split(/\R/, $cgiparams{'ALLOW_LIST'});
> + my @blocklist = split(/\R/, $cgiparams{'BLOCK_LIST'});
> +
> + # Validate lists
> + $result = &_rpz_validate_customlist(\@allowlist);
> + if($result != 0) {
> + $errormessage = &_rpz_error_tr(202, $result);
> + return;
> + }
> + $result = &_rpz_validate_customlist(\@blocklist);
> + if($result != 0) {
> + $errormessage = &_rpz_error_tr(203, $result);
> + return;
> + }
> +
> + # Add to global data hash and save changes
> + $customlists{'allow'}{'list'} = \@allowlist;
> + $customlists{'block'}{'list'} = \@blocklist;
> + $customlists{'allow'}{'enabled'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? 'on' : 'off';
> + $customlists{'block'}{'enabled'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? 'on' : 'off';
> +
> + &_customlists_save_conf();
> + &_customlist_export('allow', $RPZ_ALLOWLIST);
> + &_customlist_export('block', $RPZ_BLOCKLIST);
> +
> + # Make new lists, request service reload on success
> + $result = &General::system('/usr/sbin/rpz-make', 'allowblock', '--no-reload');
> + return unless &_rpz_check_result($result, 1);
> +
> + return 1;
> +}
> +
> +# Trigger rpz-config reload
> +sub _action_rpz_reload {
> + return 1 unless &_rpz_needs_reload();
> +
> + # Immediately clear flag to prevent multiple reloads
> + if(-f $RPZ_RELOAD_FLAG) {
> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
> + }
> +
> + # Perform reload, recreate reload flag on error to enable retry
> + my $result = &General::system('/usr/sbin/rpz-config', 'reload');
> + if(not &_rpz_check_result($result, 0)) {
> + &General::system('touch', "$RPZ_RELOAD_FLAG");
> + return;
> + }
> +
> + return 1;
> +}
> +
> +# Trigger unbound restart
> +sub _action_unb_restart {
> + return 1 unless &_rpz_needs_reload();
> +
> + # Immediately clear flag to prevent multiple restarts
> + if(-f $RPZ_RELOAD_FLAG) {
> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
> + }
> +
> + # Perform restart, unboundctrl always exits zero
> + &General::system('/usr/local/bin/unboundctrl', 'restart');
> +
> + return 1;
> +}
> +
> +
> +###--- Internal rpz-config functions ---###
> +
> +# Translate rpz-config exitcodes and messages
> +# 100-199: rpz-config, 200-299: webgui
> +sub _rpz_error_tr {
> + my ($error, $append) = @_;
> + $append //= '';
> +
> + # Translate numeric exit codes
> + if(looks_like_number($error)) {
> + if(defined $Lang::tr{"rpz exitcode $error"}) {
> + $error = $Lang::tr{"rpz exitcode $error"};
> + }
> + }
> +
> + return "RPZ $Lang::tr{'error'}: $error" . &Header::escape($append);
> +}
> +
> +# Check result of rpz-config system call, request reload on success
> +sub _rpz_check_result {
> + my ($result, $request_reload) = @_;
> + $request_reload //= 0;
> +
> + # exitcode 0 = success
> + if($result != 0) {
> + $errormessage = &_rpz_error_tr($result);
> + return;
> + }
> +
> + # Set reload flag
> + if($request_reload) {
> + &General::system('touch', "$RPZ_RELOAD_FLAG");
> + }
> +
> + return 1;
> +}
> +
> +# Test whether reload flag is set
> +sub _rpz_needs_reload {
> + return (-f $RPZ_RELOAD_FLAG);
> +}
> +
> +# Validate a zonefile entry, returns rpz-config exitcode on failure. Use _rpz_check_result to verify.
> +# unique = check for unique name
> +sub _rpz_validate_zonefile {
> + my ($name, $url, $remark, $unique) = @_;
> + $unique //= 1;
> +
> + unless($name =~ /^[a-zA-Z0-9_]{1,32}$/) {
> + return 101;
> + }
> + unless($url =~ /^[\w+\.:;\/\\&@#%?=\-~|!]{1,128}$/) {
> + return 105;
> + }
> + unless($remark =~ /^[\w \-()\.:;*\/\\?!&=]{0,32}$/) {
> + return 201;
> + }
> +
> + # Check against already existing names
> + if($unique) {
> + foreach my $existing (keys %zonefiles) {
> + if(lc($name) eq lc($existing)) {
> + return 104;
> + }
> + }
> + }
> +
> + return 0;
> +}
> +
> +# Validate a custom list, returns number of rejected line on failure. Check for non-zero results.
> +# listref = array reference, cleanup = remove invalid entries instead of returning an error
> +sub _rpz_validate_customlist {
> + my ($listref, $cleanup) = @_;
> + $cleanup //= 0;
> +
> + foreach my $index (reverse 0..$#{$listref}) {
> + my $row = @$listref[$index];
> + next unless($row); # Skip/allow empty lines
> +
> + # Reject/remove everything besides wildcard domains and remarks
> + if((not &General::validwildcarddomainname($row)) && (not $row =~ /^;[\w \-()\.:;*\/\\?!&=]*$/)) {
> + unless($cleanup) {
> + # +1 for user friendly line number and to ensure non-zero exitcode
> + return $index + 1;
> + }
> +
> + # Remove current row
> + splice(@$listref, $index, 1);
> + }
> + }
> +
> + return 0;
> +}
> +
> +
> +###--- Internal misc functions ---###
> +
> +# Send HTTP 303 redirect headers for post/request/get pattern
> +# (Must be sent before calling &Header::showhttpheaders())
> +sub _http_prg_redirect {
> + my $location = "https://$ENV{'SERVER_NAME'}:$ENV{'SERVER_PORT'}$ENV{'SCRIPT_NAME'}";
> + print "Status: 303 See Other\n";
> + print "Location: $location\n";
> +}
> diff --git a/lfs/rpz b/lfs/rpz
> new file mode 100644
> index 000000000..7ddbc38e5
> --- /dev/null
> +++ b/lfs/rpz
> @@ -0,0 +1,96 @@
> +###############################################################################
> +# #
> +# IPFire.org - A linux based firewall #
> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
> +# #
> +# 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 <http://www.gnu.org/licenses/>. #
> +# #
> +###############################################################################
> +
> +###############################################################################
> +# Definitions
> +###############################################################################
> +
> +include Config
> +
> +SUMMARY = response policy zone - RPZ reputation system for unbound DNS
> +
> +VER = 1.0.0
> +
> +THISAPP = rpz-$(VER)
> +DIR_APP = $(DIR_SRC)/$(THISAPP)
> +TARGET = $(DIR_INFO)/$(THISAPP)
> +
> +PROG = rpz
> +PAK_VER = 18
> +
> +DEPS =
> +
> +SERVICES =
> +
> +###############################################################################
> +# Top-level Rules
> +###############################################################################
> +
> +install : $(TARGET)
> +
> +check :
> +
> +download :
> +
> +b2 :
> +
> +dist:
> + @$(PAK)
> +
> +###############################################################################
> +# Installation Details
> +###############################################################################
> +
> +$(TARGET) :
> + @$(PREBUILD)
> + @rm -rf $(DIR_APP)
> +
> + # RPZ scripts
> + install --verbose --mode=755 \
> + $(DIR_CONF)/rpz/{rpz-config,rpz-metrics,rpz-sleep,rpz-make,rpz-functions} \
> + --target-directory=/usr/sbin
> +
> + # RPZ config files
> + mkdir -pv /etc/unbound/local.d
> + install --verbose --mode=644 --owner=nobody --group=nobody \
> + $(DIR_CONF)/rpz/00-rpz.conf \
> + --target-directory=/etc/unbound/local.d
> + chown --verbose --recursive nobody:nobody /etc/unbound/local.d
> +
> + # RPZ custom list files for allow and block
> + mkdir -pv /var/ipfire/dns/rpz
> + touch /var/ipfire/dns/rpz/{allowlist,blocklist}
> + chown --verbose --recursive nobody:nobody /var/ipfire/dns/rpz
> +
> + # RPZ zone files
> + # create empty RPZ config file to avoid a unbound config error
> + mkdir -pv /etc/unbound/zonefiles
> + touch /etc/unbound/zonefiles/allow.rpz
> + chown --verbose --recursive nobody:nobody /etc/unbound/zonefiles
> +
> + # Install addon-specific language-files
> + install --verbose --mode=004 $(DIR_CONF)/rpz/rpz.*.pl \
> + --target-directory=/var/ipfire/addon-lang
> +
> + # Install backup definition
> + cp -vf $(DIR_CONF)/backup/includes/rpz /var/ipfire/backup/addons/includes/rpz
> +
> + @rm -rf $(DIR_APP)
> + @$(POSTBUILD)
> diff --git a/make.sh b/make.sh
> index 827ea9e77..a77535b13 100755
> --- a/make.sh
> +++ b/make.sh
> @@ -390,7 +390,7 @@ prepareenv() {
> if [ "${free_space}" -lt "${required_space}" ]; then
> # Add any consumed space
> while read -r consumed_space path; do
> - (( free_space += consumed_space / 1024 / 1024 ))
> + (( free_space += consumed_space / 1024 / 1024 ))
> done <<< "$(du --summarize --bytes "${BUILD_DIR}" "${IMAGES_DIR}" "${LOG_DIR}" 2>/dev/null)"
> fi
>
> @@ -2087,6 +2087,7 @@ build_system() {
> lfsmake2 btrfs-progs
> lfsmake2 inotify-tools
> lfsmake2 grub-btrfs
> + lfsmake2 rpz
>
> lfsmake2 linux
> lfsmake2 rtl8812au
> diff --git a/src/paks/rpz/install.sh b/src/paks/rpz/install.sh
> new file mode 100644
> index 000000000..ef99bf742
> --- /dev/null
> +++ b/src/paks/rpz/install.sh
> @@ -0,0 +1,36 @@
> +#!/bin/bash
> +###############################################################################
> +# #
> +# IPFire.org - A linux based firewall #
> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
> +# #
> +# 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 <http://www.gnu.org/licenses/>. #
> +# #
> +###############################################################################
> +#
> +. /opt/pakfire/lib/functions.sh
> +extract_files
> +restore_backup ${NAME}
> +
> +# fix user created files
> +chown --verbose --recursive nobody:nobody \
> + /var/ipfire/dns/rpz \
> + /etc/unbound/zonefiles \
> + /etc/unbound/local.d
> +
> +# Update Language cache
> +/usr/local/bin/update-lang-cache
> +
> +# restart unbound to load config file
> +/etc/init.d/unbound restart
> diff --git a/src/paks/rpz/uninstall.sh b/src/paks/rpz/uninstall.sh
> new file mode 100644
> index 000000000..e11427df3
> --- /dev/null
> +++ b/src/paks/rpz/uninstall.sh
> @@ -0,0 +1,38 @@
> +#!/bin/bash
> +###############################################################################
> +# #
> +# IPFire.org - A linux based firewall #
> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
> +# #
> +# 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 <http://www.gnu.org/licenses/>. #
> +# #
> +###############################################################################
> +#
> +. /opt/pakfire/lib/functions.sh
> +
> +# stop unbound to delete RPZ conf file
> +/etc/init.d/unbound stop
> +
> +make_backup ${NAME}
> +remove_files
> +
> +# delete rpz config files. Otherwise unbound will throw error:
> +# "[1723428668] unbound-control[17117:0] error: connect: Connection refused for 127.0.0.1 port 8953"
> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
> +
> +# Update Language cache
> +/usr/local/bin/update-lang-cache
> +
> +# start unbound to load unbound config file
> +/etc/init.d/unbound start
> diff --git a/src/paks/rpz/update.sh b/src/paks/rpz/update.sh
> new file mode 100644
> index 000000000..9bc340bc6
> --- /dev/null
> +++ b/src/paks/rpz/update.sh
> @@ -0,0 +1,52 @@
> +#!/bin/bash
> +###############################################################################
> +# #
> +# IPFire.org - A linux based firewall #
> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
> +# #
> +# 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 <http://www.gnu.org/licenses/>. #
> +# #
> +###############################################################################
> +#
> +. /opt/pakfire/lib/functions.sh
> +
> +# from update.sh
> +extract_backup_includes
> +
> +# stop unbound to delete RPZ conf file
> +/etc/init.d/unbound stop
> +
> +# from uninstall.sh
> +make_backup ${NAME}
> +remove_files
> +
> +# delete rpz config files. Otherwise unbound will throw error:
> +# "unbound-control[nn:0] error: connect: Connection refused for 127.0.0.1 port 8953"
> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
> +
> +# from install.sh
> +extract_files
> +restore_backup ${NAME}
> +
> +# fix user created files
> +chown --verbose --recursive nobody:nobody \
> + /var/ipfire/dns/rpz \
> + /etc/unbound/zonefiles \
> + /etc/unbound/local.d
> +
> +# Update Language cache
> +/usr/local/bin/update-lang-cache
> +
> +# restart unbound to load config files
> +/etc/init.d/unbound start
> --
> 2.39.5
>
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
2025-02-06 20:13 ` Michael Tremer
@ 2025-02-08 18:41 ` jon
2025-02-08 19:27 ` Michael Tremer
0 siblings, 1 reply; 30+ messages in thread
From: jon @ 2025-02-08 18:41 UTC (permalink / raw)
To: development
[-- Attachment #1: Type: text/plain, Size: 102954 bytes --]
Michael,
> I think I have covered this all at lengths before that this project has been started as a separate effort
Yes, this has been a separate effort (a very public separate effort). Yes, as you pointed this out early on with the "proof-of-concept" and then my request for people to help test RPZ. Nothing was hidden.
This was done because you (and maybe others) did not have the time and I wanted to help and because I needed assistance with RPZ. I tried my best to do this without bothering you.
> and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls.
You were aware many steps along the way. See your email on July 28, 2024, August 15, 2024, September 30, 2024, December 23, 2024, and January 16. My attempts to get the team involved were met with "things are busy" and sometimes silence. (Yes, I get it, people are busy.)
You and Adolf, Leo, Erik and Bernhard have been aware since the beginning. You mention you were aware of the "proof-of-concept". If you include those beginning posts, since Sep 2023.
> This has not been discussed . . . on our calls.
On the July 28th you stated:
"We have talked about RPZ many times on the monthly call since the URL filter feature is falling more and more out of fashion. I think there is also many posts about this on the forum."
Please don’t insult me again by stating "you know what I mean".
And it has been discussed but not documented in the Monthly Meeting notes.
> Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation.
Regular conversation on the Dev Mailing list is many times met with silence. I get it, people are busy.
And regular two-way conversation doesn’t happen on the list. At least not with me. I’d be happy to point out the posts that were met with silence.
Again, I get it, people are busy.
But the "dip here to the list" were my attempts to get a conversation started. As I said, many time met with silence.
The only place I was not met with silence was on the Community. You have a great group of people in the Community. It is a shame you don’t want to have others help. It would reduce your workload.
> Therefore, what am I supposed to do with this email?
To me it is beyond obvious…
If it isn’t what you want, then guide me with how to do this the correct way. And be specific. I am trying to help. I am trying to make things better. I am trying to do things the right way.
> I don’t want to merge code that I don’t agree with.
I asked multiple times if you "agreed with the concept" and again, met with silence. Yes I get it, people are busy.
> So many fundamental things that I have been raising have either not been discussed or outright dismissed.
You mentioned this a in the past, but for some reason you do not disclose what I dismissed. Why do you continue to make this harder, wouldn’t it not be easier to tell me what I have dismissed?
I have sent multiple emails trying to answer your concerns and comments. On July 28, Aug 14, Aug 22, Aug 23, Sep 30, etc.
I’ve gone through all of the questions you asked and I cannot find a "dismissed" item.
> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
The maintainers of Unbound and/or RPZ?
The maintainers of Hagezi list, the threatfox list, the urlhaus list, etc.?
What else? The maintainers or the RPZ scripts? That is me. Let’s talk!
See, this is where it gets confusing. There are hundreds of open source packages as part of IPFire. Pick the last five years of items added to the IPFire build. You're telling me you have "constructive conversation with the maintainers" of all of the added packages?
Pick the IP Blocklists list (i.e., 3CORESEC, ABUSECH, DSHIELD, SPAMHAUS, etc.) or the Suricata lists (i.e., Emergingthreats.net, Abuse.ch, etc.). So you’ve have "constructive conversation with the maintainers"?
> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
Ha! Yes a surprise. In the beginning you seemed interested as IPFire needed a replacement for URL Filter. You asked good questions about the lists picked, asked for the value to the users, etc. And I answered the best I could.
You even asked: “Why is this realised as an add-on and not part of the core system?” from your Jul 28, 2024 email.
And on January 16, 2025 I wrote a message looking for help. And you were kind to respond quickly. So in three weeks time, since the kind response, something has changed. You went from supportive to "this".
So yes, I am surprised.
> Please consider if that can be changed and if there is a path forward with this.
Be more specific, what has to change? What exactly did I dismiss?
Jon
> On Feb 6, 2025, at 2:13 PM, Michael Tremer <michael.tremer(a)ipfire.org> wrote:
>
> Hello Jon,
>
> Well, here we are again with another patch regarding this feature.
>
> I cannot quite see from your email what the question is, but if this is a request to have this merged into IPFire, I am once again sorry to disappoint you.
>
> I think I have covered this all at lengths before that this project has been started as a separate effort and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls. Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation. Therefore, what am I supposed to do with this email?
>
> I don’t want to merge code that I don’t agree with. So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>
> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>
> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>
> Please consider if that can be changed and if there is a path forward with this.
>
> All the best,
> -Michael
>
>> On 6 Feb 2025, at 16:35, Jon Murphy <jon.murphy(a)ipfire.org> wrote:
>>
>> What is it?
>> Response Policy Zone (RPZ) is a mechanism to define local policies in a
>> standardized way and load those policies from external sources.
>> Bottom line: RPZ allows admins to easily block access to websites via DNS lookup.
>>
>> RPZ can block websites via categories. Examples include: fake websites, annoying
>> pop-up ads, newly registered domains, DoH bypass sites, bad "host" services,
>> maliscious top level domains (e.g., *.zip, *.mov), piracy, gambling, pornography,
>> and more. RPZ lists come from various RPZ providers and their available
>> catagories.
>>
>> This RPZ add-on enables the RPZ functionality by adding a couple lines in a
>> configuration file. This add-on simply adds configuration files and adds
>> scripts (config, metrics and sleep) to make RPZ easier for the admin to use.
>>
>> The RPZ scripts include additional languages: German, Spanish, French, Turkish,
>> and Italian.
>>
>> RPZ itself was release in 2010 and has been part of the IPFire build since ~2015.
>>
>> Why is it needed? What is its value?
>>
>> - The RPZ concept places this filtering into IPFire, our internet access
>> gateway, which is (should be) solely used as DNS source of the internal network.
>>
>> - As most sites use HTTPS it makes it difficult to filter traffic with URL
>> Filter without also properly configuring conventional (non-transparent)
>> mode on the proxy. RPZ is a nice replacement for the URL Filter.
>>
>> - No need to install and maintain an additional device like PiHole or AdBlock
>> browser extensions on multiple user devices.
>>
>> - This is an additional layer of protection for users. Less worry someone will
>> click on something that gets them into trouble. And, saying this with emphasis,
>> the ability to do it in one place!
>>
>> - Blocked sites save on unneeded traffic and can lessen the threat of malware
>> in advertisements
>>
>> - Logging allows the admin to see the site blocked and take actions
>>
>> - RPZ will be used at the home, home-office (work from home), schools,
>> ministerial, and at the office. Device counts are small (2-6) to medium (~80)
>> to mediam-large (200+).
>>
>> - RPZ can block ads, popups, phishing, scammers, spyware, malware, annoying
>> popups, NSFW links, DOH servers, and the usual internet trash.
>>
>> ------------------------------
>>
>> Change Log for RPZ add-on
>>
>> rpz-1.0.0-18 on 2025-02-05
>> - Build for approval & release as IPFire add-on
>>
>> ---
>>
>> rpz-beta-0.1.18-18.ipfire on 2025-02-01
>> rpz.cgi:
>> - new feature: added a mod key to force a unbound restart
>>
>> rpz-config and rpz-make:
>> - new feature: added action for unbound restart `rpz-config unbound-restart`
>>
>> rpz-metrics:
>> - simple reformatting
>> - rename far right column from "last update" to "last download"
>>
>> ---
>>
>> rpz-beta-0.1.17-17.ipfire on 2024-12-09
>> rpz-make
>> - bug fix: corrected validation regex for wildcards like: `*.domain.com`
>>
>> ---
>>
>> rpz-beta-0.1.16-16.ipfire on 2024-11-18
>> rpz-make
>> - new feature: updated validation regex
>> - bug fix: moved validation to beginning of process. Now we validate before
>> creating config files.
>>
>> rpz.cgi:
>> - new feature: use CSS color variables of the main ipfire theme
>> - bug fix: empty zonefile remarks were stored as “undef” and caused a warning
>> - bug fix: HTML textarea removes the first empty line in a custom list
>> - thank you Leo!
>>
>> ---
>>
>> rpz-beta-0.1.15-15.ipfire on 2024-11-04
>> rpz.cgi:
>> - new feature: added new language file for Turkish (thank you Peppe)
>>
>> rpz-make
>> - bug fix: corrected empty allow/block list issue. An empty allow/block list
>> will now remove contents of allow/block.rpz files and remove unneeded
>> allow/block.conf file. (thank you iptom)
>>
>> ---
>>
>> rpz-beta-0.1.14-14.ipfire on 2024-10-29
>> rpz-config:
>> - bug fix: correct missing rpz extension. `rpz-config list` displayed URL
>> incorrectly (thank you Bernhard)
>>
>> rpz.cgi:
>> - bug fix: remove extra `"` in language files (thank you Bernhard)
>> - new feature: slightly dim "apply" button when not enabled
>>
>> ---
>>
>> rpz-beta-0.1.13-13.ipfire on 2024-10-27
>> - skipped
>>
>> ---
>>
>> rpz-beta-0.1.12-12.ipfire on 2024-10-21
>> rpz.cgi:
>> - new feature: added new language file for French (thank you gw-ipfire)
>>
>> ---
>>
>> rpz-beta-0.1.11-11.ipfire on 2024-10-18
>> rpz.cgi:
>> - new feature: added new language file for Italian (thank you umberto)
>> - new feature: added new language file for Spanish (thank you Roberto)
>>
>> ---
>>
>> rpz-beta-0.1.10-10.ipfire on 2024-10-15
>> rpz-make:
>> - bug fix: corrected validation error for a custom list entry (thank you siosios)
>> - e.g., `*.cloudflare-dns.com`
>>
>> install.sh:
>> - bug fix: add chown to correct user created files
>>
>> update.sh:
>> - bug fix: add chown to correct user created files (thank you siosios)
>>
>> ---
>>
>> rpz-beta-0.1.9-9.ipfire on 2024-10-08
>> rpz.cgi:
>> - new feature: added new language file for German (thank you Leo)
>> - bug fix: add missing "rpz exitcode 110"
>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>
>> ---
>>
>> rpz-beta-0.1.8-8.ipfire on 2024-10-04
>> - skipped
>>
>> ---
>>
>> rpz-beta-0.1.7-7.ipfire on 2024-10-03
>> All:
>> - new feature: includes beta version numbers for pakfire package,
>> instead of only `rpz-1.0.0-1.ipfire`, for each release.
>>
>> rpz.cgi:
>> - new feature: added new WebGUI at `rpz.cgi`
>> - a BIG thank you to Leo Hofmann for all of his work creating the webgui!!
>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>
>> rpz-make:
>> - new feature: validate entries in allowlist and blocklist
>> - new feature: add "no-reload" option for WebGUI
>>
>> rpz-metrics:
>> - new feature: info can be sorted by name, by hit count, by line count, by
>> "enabled" list or all lists
>>
>> backups:
>> - bug fix: include all files in `/var/ipfire/dns/rpz` directory in backup
>>
>> update.sh:
>> - bug fix: corrected ownership for `/var/ipfire/dns/rpz` directory during an
>> update
>>
>> Build:
>> - bug fix: `block.rpz.conf` and `block.rpz` from build. Files to be created
>> by `rpz-make`
>>
>> WebGUI and German language file
>> Contribution-by: Leo-Andres Hofmann <hofmann(a)leo-andres.de>
>>
>> Spanish language file
>> Contribution-by: Roberto Peña
>>
>> Italian language file
>> Contribution-by: Umberto Parma
>>
>> French language file
>> Contribution-by: gw-ipfire
>>
>> Turkish language file
>> Contribution-by: Peppe Tech
>>
>> Contribution-by: Bernhard Bitsch <bbitsch(a)ipfire.org>
>> Contribution-by: Erik Kapfer <erik.kapfer(a)ipfire.org>
>> Signed-off-by: Jon Murphy <jon.murphy(a)ipfire.org
>> ---
>> config/backup/includes/rpz | 4 +
>> config/cfgroot/manualpages | 1 +
>> config/menu/EX-rpz.menu | 6 +
>> config/rootfiles/common/configroot | 1 +
>> config/rootfiles/common/web-user-interface | 1 +
>> config/rootfiles/packages/rpz | 20 +
>> config/rpz/00-rpz.conf | 10 +
>> config/rpz/rpz-config | 130 +++
>> config/rpz/rpz-functions | 85 ++
>> config/rpz/rpz-make | 203 +++++
>> config/rpz/rpz-metrics | 170 ++++
>> config/rpz/rpz-sleep | 58 ++
>> config/rpz/rpz.de.pl | 30 +
>> config/rpz/rpz.en.pl | 30 +
>> config/rpz/rpz.es.pl | 30 +
>> config/rpz/rpz.fr.pl | 30 +
>> config/rpz/rpz.it.pl | 30 +
>> config/rpz/rpz.tr.pl | 30 +
>> html/cgi-bin/rpz.cgi | 923 +++++++++++++++++++++
>> lfs/rpz | 96 +++
>> make.sh | 3 +-
>> src/paks/rpz/install.sh | 36 +
>> src/paks/rpz/uninstall.sh | 38 +
>> src/paks/rpz/update.sh | 52 ++
>> 24 files changed, 2016 insertions(+), 1 deletion(-)
>> create mode 100644 config/backup/includes/rpz
>> create mode 100644 config/menu/EX-rpz.menu
>> create mode 100644 config/rootfiles/packages/rpz
>> create mode 100644 config/rpz/00-rpz.conf
>> create mode 100644 config/rpz/rpz-config
>> create mode 100644 config/rpz/rpz-functions
>> create mode 100644 config/rpz/rpz-make
>> create mode 100755 config/rpz/rpz-metrics
>> create mode 100755 config/rpz/rpz-sleep
>> create mode 100644 config/rpz/rpz.de.pl
>> create mode 100644 config/rpz/rpz.en.pl
>> create mode 100644 config/rpz/rpz.es.pl
>> create mode 100644 config/rpz/rpz.fr.pl
>> create mode 100644 config/rpz/rpz.it.pl
>> create mode 100644 config/rpz/rpz.tr.pl
>> create mode 100644 html/cgi-bin/rpz.cgi
>> create mode 100644 lfs/rpz
>> create mode 100644 src/paks/rpz/install.sh
>> create mode 100644 src/paks/rpz/uninstall.sh
>> create mode 100644 src/paks/rpz/update.sh
>>
>> diff --git a/config/backup/includes/rpz b/config/backup/includes/rpz
>> new file mode 100644
>> index 000000000..36513e494
>> --- /dev/null
>> +++ b/config/backup/includes/rpz
>> @@ -0,0 +1,4 @@
>> +/var/ipfire/dns/rpz/*
>> +/etc/unbound/zonefiles/allow.rpz
>> +/etc/unbound/zonefiles/block.rpz
>> +/etc/unbound/local.d/*rpz.conf
>> diff --git a/config/cfgroot/manualpages b/config/cfgroot/manualpages
>> index 1f7e01efc..d3a48c633 100644
>> --- a/config/cfgroot/manualpages
>> +++ b/config/cfgroot/manualpages
>> @@ -70,6 +70,7 @@ pakfire.cgi=configuration/ipfire/pakfire
>> wlanap.cgi=addons/wireless
>> tor.cgi=addons/tor
>> samba.cgi=addons/samba
>> +rpz.cgi=addons/rpz
>>
>> # Logs menu
>> logs.cgi/summary.dat=configuration/logs/summary
>> diff --git a/config/menu/EX-rpz.menu b/config/menu/EX-rpz.menu
>> new file mode 100644
>> index 000000000..2f4daf410
>> --- /dev/null
>> +++ b/config/menu/EX-rpz.menu
>> @@ -0,0 +1,6 @@
>> +$subipfire->{'20.rpz'} = {
>> + 'caption' => $Lang::tr{'rpz'},
>> + 'uri' => '/cgi-bin/rpz.cgi',
>> + 'title' => "RPZ",
>> + 'enabled' => 1,
>> +};
>> diff --git a/config/rootfiles/common/configroot b/config/rootfiles/common/configroot
>> index 9839eee45..b30d6aae4 100644
>> --- a/config/rootfiles/common/configroot
>> +++ b/config/rootfiles/common/configroot
>> @@ -120,6 +120,7 @@ var/ipfire/menu.d/70-log.menu
>> #var/ipfire/menu.d/EX-apcupsd.menu
>> #var/ipfire/menu.d/EX-guardian.menu
>> #var/ipfire/menu.d/EX-mympd.menu
>> +#var/ipfire/menu.d/EX-rpz.menu
>> #var/ipfire/menu.d/EX-samba.menu
>> #var/ipfire/menu.d/EX-tor.menu
>> #var/ipfire/menu.d/EX-transmission.menu
>> diff --git a/config/rootfiles/common/web-user-interface b/config/rootfiles/common/web-user-interface
>> index 816241dae..e00464076 100644
>> --- a/config/rootfiles/common/web-user-interface
>> +++ b/config/rootfiles/common/web-user-interface
>> @@ -69,6 +69,7 @@ srv/web/ipfire/cgi-bin/proxy.cgi
>> srv/web/ipfire/cgi-bin/qos.cgi
>> srv/web/ipfire/cgi-bin/remote.cgi
>> srv/web/ipfire/cgi-bin/routing.cgi
>> +#srv/web/ipfire/cgi-bin/rpz.cgi
>> #srv/web/ipfire/cgi-bin/samba.cgi
>> srv/web/ipfire/cgi-bin/services.cgi
>> srv/web/ipfire/cgi-bin/shutdown.cgi
>> diff --git a/config/rootfiles/packages/rpz b/config/rootfiles/packages/rpz
>> new file mode 100644
>> index 000000000..1c8663049
>> --- /dev/null
>> +++ b/config/rootfiles/packages/rpz
>> @@ -0,0 +1,20 @@
>> +etc/unbound/local.d/00-rpz.conf
>> +etc/unbound/zonefiles
>> +etc/unbound/zonefiles/allow.rpz
>> +usr/sbin/rpz-config
>> +usr/sbin/rpz-functions
>> +usr/sbin/rpz-make
>> +usr/sbin/rpz-metrics
>> +usr/sbin/rpz-sleep
>> +var/ipfire/addon-lang/rpz.de.pl
>> +var/ipfire/addon-lang/rpz.en.pl
>> +var/ipfire/addon-lang/rpz.es.pl
>> +var/ipfire/addon-lang/rpz.fr.pl
>> +var/ipfire/addon-lang/rpz.it.pl
>> +var/ipfire/addon-lang/rpz.tr.pl
>> +var/ipfire/backup/addons/includes/rpz
>> +var/ipfire/dns/rpz
>> +var/ipfire/dns/rpz/allowlist
>> +var/ipfire/dns/rpz/blocklist
>> +var/ipfire/menu.d/EX-rpz.menu
>> +srv/web/ipfire/cgi-bin/rpz.cgi
>> diff --git a/config/rpz/00-rpz.conf b/config/rpz/00-rpz.conf
>> new file mode 100644
>> index 000000000..f005a4f2e
>> --- /dev/null
>> +++ b/config/rpz/00-rpz.conf
>> @@ -0,0 +1,10 @@
>> +server:
>> + module-config: "respip validator iterator"
>> +
>> +rpz:
>> + name: allow.rpz
>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>> + rpz-action-override: passthru
>> + rpz-log: yes
>> + rpz-log-name: allow
>> + rpz-signal-nxdomain-ra: yes
>> diff --git a/config/rpz/rpz-config b/config/rpz/rpz-config
>> new file mode 100644
>> index 000000000..c72d50f9b
>> --- /dev/null
>> +++ b/config/rpz/rpz-config
>> @@ -0,0 +1,130 @@
>> +#!/bin/bash
>> +###############################################################################
>> +# #
>> +# IPFire.org - A linux based firewall #
>> +# Copyright (C) 2024-2025 IPFire Team <info(a)ipfire.org> #
>> +# #
>> +# 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 <http://www.gnu.org/licenses/>. #
>> +# #
>> +###############################################################################
>> +
>> +version="2025-01-11 - v44"
>> +
>> +############### Functions ###############
>> +
>> +source /usr/sbin/rpz-functions
>> +
>> +############### Main ###############
>> +
>> +tagName="unbound"
>> +
>> +rpzAction="${1}" # input RPZ action
>> +rpzName="${2}" # input RPZ name
>> +rpzURL="${3}" # input RPZ URL
>> +rpzOption1="${4}" # input RPZ option #1
>> +rpzOption2="${5}" # input RPZ option #2
>> +
>> +rpzConfig="/etc/unbound/local.d/${rpzName}.rpz.conf" # output zone conf file
>> +rpzFile="/etc/unbound/zonefiles/${rpzName}.rpz" # output for RPZ file
>> +
>> +rpzLog="yes" # log default is yes
>> +ucReload="yes" # reload default is yes
>> +
>> +while [[ $# -gt 0 ]] ; do
>> + case "$1" in
>> + --no-log ) rpzLog="no" ;;
>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>> + esac
>> + shift # Shift after checking all the cases to get next option
>> +done
>> +
>> +case "${rpzAction}" in
>> + # add new rpz list
>> + add )
>> + check_name "${rpzName}" # is this a valid name?
>> + # does this config already exist? If yes, then exit
>> + if [[ -f "${rpzConfig}" ]] ; then
>> + msg_log "error: rpz: duplicate - ${rpzConfig} already exists. exit"
>> + exit 104
>> + fi
>> +
>> + # is this a valid URL?
>> + regex='^https://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
>> + if ! [[ "${rpzURL}" =~ $regex ]] ; then
>> + msg_log "error: rpz: the URL is not valid: \"${rpzURL}\". exit."
>> + exit 105
>> + fi
>> +
>> + # create the zone config file
>> + {
>> + echo "rpz:"
>> + echo " name: ${rpzName}.rpz"
>> + echo " zonefile: ${rpzFile}"
>> + echo " url: ${rpzURL}"
>> + echo " rpz-action-override: nxdomain"
>> + echo " rpz-log: ${rpzLog}"
>> + echo " rpz-log-name: ${rpzName}"
>> + echo " rpz-signal-nxdomain-ra: yes"
>> + } > "${rpzConfig}"
>> +
>> + # set-up zonefile
>> + # create an empty rpz file if it does not exist
>> + if [[ ! -f "${rpzFile}" ]] ; then
>> + touch "${rpzFile}"
>> + # unbound requires these settings for rpz files
>> + set_permissions "${rpzFile}" "${rpzConfig}"
>> + fi
>> + ;;
>> +
>> + # trash config file & rpz file
>> + remove )
>> + if ! [[ -f "${rpzConfig}" ]] ; then
>> + msg_log "error: rpz: cannot remove ${rpzConfig}, does not exist. exit"
>> + exit 106
>> + fi
>> +
>> + msg_log "info: rpz: remove config file & rpz file \"${rpzName}\""
>> + rm "${rpzConfig}"
>> + rm "${rpzFile}"
>> + ;;
>> +
>> + reload )
>> + check_unbound_conf "${checkConf}"
>> + ;;
>> +
>> + list )
>> + awk -F':' '/^\s*name:/{ gsub(/[[:blank:]]|\.rpz/, "",$2) ; NAME=$2 } \
>> + /^\s*url:/{ gsub(/[[:blank:]]/, "") ; print NAME"="$2":"$3} ' \
>> + /etc/unbound/local.d/*rpz.conf
>> + exit
>> + ;;
>> +
>> + unbound-restart )
>> + check_unbound_conf "${checkConf}"
>> + unbound_restart
>> + exit
>> + ;;
>> +
>> + * )
>> + msg_log "error: rpz: missing or incorrect parameter"
>> + printf "Usage: $(basename "$0") <ACTION> <NAME> <URL> <OPTION> <OPTION>\n"
>> + printf "Version: ${version}\n"
>> + exit 108
>> + ;;
>> +
>> +esac
>> +
>> +unbound_control_reload "${ucReload}"
>> +
>> +exit
>> diff --git a/config/rpz/rpz-functions b/config/rpz/rpz-functions
>> new file mode 100644
>> index 000000000..ace1d2690
>> --- /dev/null
>> +++ b/config/rpz/rpz-functions
>> @@ -0,0 +1,85 @@
>> +#!/bin/bash
>> +###############################################################################
>> +# #
>> +# IPFire.org - A linux based firewall #
>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>> +# #
>> +# 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 <http://www.gnu.org/licenses/>. #
>> +# #
>> +###############################################################################
>> +
>> +version="2024-12-10 - v02"
>> +
>> +############### Functions ###############
>> +
>> +msg_log () {
>> + logger --tag "${tagName}" "$*"
>> + if tty --silent ; then
>> + echo "${tagName}:" "$*"
>> + fi
>> +}
>> +
>> +# check for a valid name
>> +check_name () {
>> + local theName="${1}"
>> +
>> + regex='^[a-zA-Z0-9_]+$' # no dash or plus, alpha numeric only
>> + regex1='^(allow|block)$' # allow and block are reserved NAMEs
>> + if [[ ! "${theName}" =~ $regex ]] || [[ "${theName}" =~ $regex1 ]] ; then
>> + msg_log "error: rpz: the NAME is not valid: \"${theName}\". exit."
>> + exit 101
>> + fi
>> +}
>> +
>> +set_permissions () {
>> + chown nobody:nobody "$@"
>> + chmod 644 "$@"
>> +}
>> +
>> +check_unbound_conf () {
>> + local thecheckConf="${1:-yes}" # check config default is yes
>> +
>> + # check the above config files
>> + if [[ "${thecheckConf}" == yes ]] ; then
>> + msg_log "info: rpz: check for errors with \"unbound-checkconf\""
>> +
>> + if ! unbound-checkconf ; then
>> + msg_log "error: rpz: unbound-checkconf found invalid configuration."
>> + msg_log \
>> + "error: rpz: In Terminal run the command \"unbound-checkconf\" for more information. exit."
>> + exit 102
>> + fi
>> + fi
>> +}
>> +
>> +unbound_control_reload () {
>> + local theReload="${1:-yes}" # reload default is yes
>> +
>> + if [[ "${theReload}" == yes ]] ; then
>> + # reload due to the changes
>> + msg_log "info: rpz: run \"unbound-control reload\""
>> +
>> + if ! unbound-control reload ; then
>> + msg_log "error: rpz: unbound-control reload. exit."
>> + exit 109
>> + fi
>> + fi
>> +}
>> +
>> +unbound_restart () {
>> + # restart due to the changes
>> + msg_log "info: rpz: run \"unbound restart\""
>> +
>> + /usr/local/bin/unboundctrl restart
>> +}
>> diff --git a/config/rpz/rpz-make b/config/rpz/rpz-make
>> new file mode 100644
>> index 000000000..927d55170
>> --- /dev/null
>> +++ b/config/rpz/rpz-make
>> @@ -0,0 +1,203 @@
>> +#!/bin/bash
>> +###############################################################################
>> +# #
>> +# IPFire.org - A linux based firewall #
>> +# Copyright (C) 2024-2025 IPFire Team <info(a)ipfire.org> #
>> +# #
>> +# 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 <http://www.gnu.org/licenses/>. #
>> +# #
>> +###############################################################################
>> +
>> +version="2025-01-11 - v14"
>> +
>> +############### Functions ###############
>> +
>> +source /usr/sbin/rpz-functions
>> +
>> +# create the config file for allow
>> +make_allow_config () {
>> + local theLog="${1:-yes}" # log default ON
>> + local theConfig="/etc/unbound/local.d/00-rpz.conf" # output zone conf file
>> + local theList="/var/ipfire/dns/rpz/allowlist" # input custom list of domains
>> +
>> + msg_log "info: rpz: make config file \"00-rpz.conf\""
>> +
>> + echo "server:
>> + module-config: \"respip validator iterator\"" > "${theConfig}"
>> +
>> + # does allow list exist?
>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>> +
>> + echo "rpz:
>> + name: allow.rpz
>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>> + rpz-action-override: passthru
>> + rpz-log: ${theLog}
>> + rpz-log-name: allow
>> + rpz-signal-nxdomain-ra: yes" >> "${theConfig}"
>> +
>> + fi
>> +
>> + # set-up zonefile - unbound requires these settings for rpz files
>> + set_permissions "${theConfig}"
>> +}
>> +
>> +# create the config file for block
>> +make_block_config () {
>> + local theLog="${1:-yes}" # log default ON
>> + local theConfig="/etc/unbound/local.d/block.rpz.conf" # output zone conf file
>> + local theList="/var/ipfire/dns/rpz/blocklist" # input custom list of domains
>> +
>> + msg_log "info: rpz: make config file \"block.rpz.conf\""
>> +
>> + # does block list exist?
>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>> +
>> + echo "rpz:
>> + name: block.rpz
>> + zonefile: /etc/unbound/zonefiles/block.rpz
>> + rpz-action-override: nxdomain
>> + rpz-log: ${theLog}
>> + rpz-log-name: block
>> + rpz-signal-nxdomain-ra: yes" > "${theConfig}"
>> +
>> + # set-up zonefile - unbound requires these settings for rpz files
>> + set_permissions "${theConfig}"
>> + else
>> + # no - trash the config file
>> + rm --verbose /etc/unbound/local.d/block.rpz.conf
>> + fi
>> +}
>> +
>> +# create an RPZ file for allow or block
>> +make_rpz_file () {
>> + local theName="${1}" # allow or block
>> + local theAction='.' # the default is nxdomain or block
>> + local actionList
>> +
>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>> + local theZonefile="/etc/unbound/zonefiles/${theName}.rpz" # output file for RPZ
>> +
>> + # does a list exist?
>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>> +
>> + # for allow set to passthru
>> + [[ "${theName}" == allow ]] && theAction='rpz-passthru.'
>> +
>> + # drop any extra "blanks" and add "CNAME <RPZ action>." to each line
>> + actionList=$( awk '{$1=$1};1' "${theList}" |
>> + sed "/^[^;].*[[:alnum:]]/ s|$| CNAME ${theAction}|" )
>> +
>> + msg_log "info: rpz: create zonefile for \"${theName}list\""
>> +
>> +echo "; Name: ${theName} list
>> +; Last modified: $(date "+%Y-%m-%d at %H.%M.%S %Z")
>> +;
>> +; domains with actions list
>> +;
>> +${actionList}" > "${theZonefile}"
>> +
>> + # set-up zonefile - unbound requires these settings for rpz files
>> + set_permissions "${theZonefile}"
>> + # set-up allow/block list files
>> + set_permissions "${theList}"
>> + else
>> + msg_log "info: rpz: the ${theList} is empty."
>> +
>> + rm --verbose "${theZonefile}" # trash the RPZ file
>> + fi
>> +}
>> +
>> +# check if allow/block list is valid
>> +validate_list () {
>> + local theName="${1}" # allow or block
>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>> +
>> + # remove good:
>> + # - properly formated domain names with or without leading wildcard
>> + # - properly formated top level domain (TLD) names with wildcard
>> + # - blank lines and comment lines
>> + # remaining lines are considered "bad"
>> + bad_lines=$( sed --regexp-extended \
>> + '/^(\*\.)?([a-zA-Z0-9](([a-zA-Z0-9\-]){0,61}[a-zA-Z0-9])?\.)+([a-zA-Z]{2,}|xn--[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])$/d ;
>> + /^(\*\.)([a-z]{2,61}|xn--[a-z0-9]{1,60})$/d ;
>> + /^$/d ; /^;/d' "${theList}" )
>> +
>> + if [[ ! -z "${bad_lines}" ]] ; then
>> + msg_log "error: rpz: invalid line(s) in ${theList}."
>> + printf "%s\n" "bad line(s): ${bad_lines}"
>> + exit 110
>> + fi
>> +}
>> +
>> +
>> +############### Main ###############
>> +
>> +tagName="unbound"
>> +
>> +rpzName="${1}" # input RPZ name
>> +
>> +rpzLog="yes" # log default is yes
>> +ucReload="yes" # reload default is yes
>> +
>> +while [[ $# -gt 0 ]] ; do
>> + case "$1" in
>> + --no-log ) rpzLog="no" ;;
>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>> + esac
>> + shift # Shift after checking all the cases to get next option
>> +done
>> +
>> +case "${rpzName}" in
>> + # make a new allow or block rpz file
>> +
>> + allow )
>> + validate_list 'allow' # is the allowlist valid?
>> + make_allow_config "${rpzLog}"
>> + make_rpz_file 'allow'
>> + ;;
>> +
>> + allowblock )
>> + validate_list 'allow' # is the list valid?
>> + make_allow_config "${rpzLog}"
>> + make_rpz_file 'allow'
>> + ;&
>> +
>> + block )
>> + validate_list 'block' # is the blocklist valid?
>> + make_block_config "${rpzLog}"
>> + make_rpz_file 'block'
>> + ;;
>> +
>> + reload )
>> + check_unbound_conf "${checkConf}"
>> + ;;
>> +
>> + unbound-restart )
>> + check_unbound_conf "${checkConf}"
>> + unbound_restart
>> + exit
>> + ;;
>> +
>> + * )
>> + msg_log "error: rpz: missing or incorrect parameter"
>> + printf "Usage: $(basename "$0") <NAME> <OPTION> <OPTION>\n"
>> + printf "Version: ${version}\n"
>> + exit 108
>> + ;;
>> +esac
>> +
>> +unbound_control_reload "${ucReload}"
>> +
>> +exit
>> diff --git a/config/rpz/rpz-metrics b/config/rpz/rpz-metrics
>> new file mode 100755
>> index 000000000..4d43e1629
>> --- /dev/null
>> +++ b/config/rpz/rpz-metrics
>> @@ -0,0 +1,170 @@
>> +#!/bin/bash
>> +###############################################################################
>> +# #
>> +# IPFire.org - A linux based firewall #
>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>> +# #
>> +# 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 <http://www.gnu.org/licenses/>. #
>> +# #
>> +###############################################################################
>> +
>> +version="2025-01-20 - v25"
>> +
>> +############### Main ###############
>> +
>> +weeks="2" # default to two message logs
>> +sortBy="name" # default "by name"
>> +rpzActive="enabled" # default "enabled only"
>> +
>> +while [[ $# -gt 0 ]] ; do
>> + case "$1" in
>> + --by-names | --by-name | name ) sortBy="name" ;;
>> +
>> + --by-hits | --by-hit | hits | hit ) sortBy="hit" ;;
>> +
>> + --by-lines | --by-line | lines | line ) sortBy="line" ;;
>> +
>> + --by-effect ) sortBy="effect" ;;
>> +
>> + --enabled-only ) rpzActive="enabled" ;;
>> +
>> + --active-all | --all | all ) rpzActive="all" ;;
>> +
>> + [0-9] | [0-9][0-9] ) weeks=$1 ;;
>> + esac
>> + shift # Shift after checking all the cases to get next option
>> +done
>> +
>> +# get the list of message logs for N weeks
>> +messageLogs=$( find /var/log/messages* -type f | sort --version-sort |
>> + head -"${weeks}" )
>> +
>> +# get the list of RPZ names & counts from the message log(s)
>> +rpzNameCount=$( for logf in ${messageLogs} ; do
>> + zgrep --text --fixed-strings 'info: rpz: applied' "${logf}" |
>> + awk '$10 ~ /\[\w*]/ { print $10 }' ;
>> + done | sort | uniq --count )
>> +
>> +# flip results and remove brackets `[` and `]`
>> +rpzNameCount=$( echo "${rpzNameCount}" |
>> + awk '{ print $2, $1 }' |
>> + sed --regexp-extended 's|^\[(.*)\]|\1|' )
>> +
>> +# grab only names
>> +rpzNames=$( echo "${rpzNameCount}" | awk '{ print $1 }' )
>> +
>> +# get list of RPZ files
>> +rpzFileList=$( find /etc/unbound/zonefiles -type f -iname "*.rpz" )
>> +
>> +# get basename of those files
>> +rpzBaseNames=$( echo "${rpzFileList}" |
>> + sed 's|/etc/unbound/zonefiles/||g ; s|\.rpz||g ;' )
>> +
>> +# add to rpzNames
>> +rpzNames="${rpzNames}"$'\n'"${rpzBaseNames}"
>> +
>> +# drop duplicate names
>> +rpzNames=$( echo "${rpzNames}" | sort --unique )
>> +
>> +# get line count for each RPZ
>> +lineCount=$( echo "${rpzFileList}" | xargs wc -l )
>> +
>> +# get comment line count and blank line count for each RPZ
>> +commentCount=$( echo "${rpzFileList}" | xargs grep --count -e "^$" -e "^;" )
>> +
>> +# get modified date each RPZ
>> +modDateList=$( echo "${rpzFileList}" | xargs stat -c '%.10y %n' )
>> +
>> +ucListAuthZones=$( unbound-control list_auth_zones )
>> +
>> +# get width of RPZ names
>> +pWidth=$( echo "${rpzNames}" | awk '{ print $1" " }' | wc -L )
>> +pFormat="%-${pWidth}s %-8s %-8s %8s %12s %12s\n"
>> +
>> +# print title line
>> +printf "${pFormat}" "name" "hits" "active" "lines" " hits/line" "last download"
>> +printf -- "--------------"
>> +
>> +theResults=""
>> +totalLines=0
>> +totalHits=0
>> +while read -r theName
>> +do
>> + printf -- "--" # pretend progress bar
>> +
>> + # is this RPZ list active?
>> + theActive="disabled"
>> + if grep --quiet "^${theName}\.rpz" <<< "${ucListAuthZones}"
>> + then
>> + theActive="enabled"
>> + else
>> + [[ "${rpzActive}" == enabled ]] && continue
>> + fi
>> +
>> + # get hit count
>> + theHits="0"
>> + if output=$( grep "^${theName}\s" <<< "${rpzNameCount}" ) ; then
>> + theHits=$( echo "${output}" | awk '{ print $2 }' )
>> + totalHits=$(( totalHits + theHits ))
>> + fi
>> +
>> + # get line count
>> + theLines="n/a"
>> + hitsPerLine="0"
>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${lineCount}" ) ; then
>> + theLines=$( echo "${output}" | awk '{ print $1 }' )
>> + totalLines=$(( totalLines + theLines ))
>> +
>> + if [[ "${theLines}" -gt 2 ]] ; then
>> + hitsPerLine=$(( 100 * theHits / theLines ))
>> + fi
>> + fi
>> +
>> + # get modification date
>> + theModDate="n/a"
>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${modDateList}" ) ; then
>> + theModDate=$( echo "${output}" | awk '{ print $1 }' )
>> + fi
>> +
>> + # add to results list
>> + theResults+="${theName} ${theHits} ${theActive} ${theLines} ${hitsPerLine} ${theModDate}"$'\n'
>> +
>> +done <<< "${rpzNames}"
>> +
>> +case "${sortBy}" in
>> + # sort by "active" then by "name"
>> + name) sortArg=(-k3,3r -k1,1) ;;
>> +
>> + # sort by "active" then by "hits" then by "name"
>> + hit) sortArg=(-k3,3r -k2,2nr -k1,1) ;;
>> +
>> + # sort by "active" then by "lines" then by "name"
>> + line) sortArg=(-k3,3r -k4,4nr -k1,1) ;;
>> +
>> + # sort by "active" then by "effect" then by "name"
>> + effect) sortArg=(-k3,3r -k5,5nr -k1,1) ;;
>> +esac
>> +
>> +printf -- "--------------\n"
>> +# remove blank lines, sort, print as columns
>> +echo "${theResults}" |
>> + awk '!/^[[:space:]]*$/' |
>> + sort "${sortArg[@]}" |
>> + awk --assign=width="${pWidth}" \
>> + '{ printf "%-*s %-8s %-8s %8s %10s %% %12s\n", width, $1, $2, $3, $4, $5, $6 }'
>> +
>> +printf "${pFormat}" "" "=======" "" "========" "" ""
>> +printf "${pFormat}" "Totals -->" "${totalHits}" "" "${totalLines}" "" ""
>> +
>> +exit
>> diff --git a/config/rpz/rpz-sleep b/config/rpz/rpz-sleep
>> new file mode 100755
>> index 000000000..dd3603599
>> --- /dev/null
>> +++ b/config/rpz/rpz-sleep
>> @@ -0,0 +1,58 @@
>> +#!/bin/bash
>> +###############################################################################
>> +# #
>> +# IPFire.org - A linux based firewall #
>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>> +# #
>> +# 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 <http://www.gnu.org/licenses/>. #
>> +# #
>> +###############################################################################
>> +
>> +version="2024-08-16" # v05
>> +
>> +############### Functions ###############
>> +
>> +# send message to message log
>> +msg_log () {
>> + logger --tag "${tagName}" "$*"
>> + if tty --silent ; then
>> + echo "${tagName}:" "$*"
>> + fi
>> +}
>> +
>> +############### Main ###############
>> +
>> +tagName="unbound"
>> +
>> +sleepTime="${1:-5m}" # default to sleep for 5m (5 minutes)
>> +
>> +zoneList=$( unbound-control list_auth_zones | awk '{print $1}' )
>> +
>> +for zone in ${zoneList} ; do
>> + printf "disable ${zone}\t"
>> + unbound-control rpz_disable "${zone}"
>> +done
>> +
>> +msg_log "info: rpz: disabled all zones for ${sleepTime}"
>> +
>> +sleep "${sleepTime}"
>> +
>> +for zone in ${zoneList} ; do
>> + printf "enable ${zone}\t"
>> + unbound-control rpz_enable "${zone}"
>> +done
>> +
>> +msg_log "info: rpz: enabled all zones"
>> +
>> +exit
>> diff --git a/config/rpz/rpz.de.pl b/config/rpz/rpz.de.pl
>> new file mode 100644
>> index 000000000..3770c6bb0
>> --- /dev/null
>> +++ b/config/rpz/rpz.de.pl
>> @@ -0,0 +1,30 @@
>> +# Added for Response Policy Zone (RPZ) add-on
>> +%tr = (%tr,
>> +'rpz' => 'Response Policy Zones (RPZ)',
>> +'rpz apply' => 'Übernehmen',
>> +'rpz cl allow enable' => 'Benutzerdefinierte Allowlist aktivieren:',
>> +'rpz cl allow info' => 'Zugelassene Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>> +'rpz cl allow' => 'Benutzerdefinierte Allowlist',
>> +'rpz cl block enable' => 'Benutzerdefinierte Blocklist aktivieren:',
>> +'rpz cl block info' => 'Gesperrte Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>> +'rpz cl block' => 'Benutzerdefinierte Blocklist',
>> +'rpz cl' => 'Benutzerdefinierte Listen',
>> +'rpz exitcode 101' => 'Der Name enthält unzulässige Zeichen',
>> +'rpz exitcode 102' => 'unbound-checkconf hat eine fehlerhafte Konfiguration ermittelt. Führen Sie das Kommando unbound-checkconf auf der Konsole aus, um weitere Informationen zu erhalten.',
>> +'rpz exitcode 103' => 'Die benutzerdefinierte Allow-/Blocklist ist leer',
>> +'rpz exitcode 104' => 'Ein Eintrag mit identischem Namen existiert bereits',
>> +'rpz exitcode 105' => 'Die URL ist ungültig',
>> +'rpz exitcode 106' => 'Eintrag kann nicht entfernt werden, der Name existiert nicht',
>> +'rpz exitcode 107' => 'Der Name ist ungültig - nur "allow" oder "block" möglich',
>> +'rpz exitcode 108' => 'Fehlende oder inkorrekte Parameter',
>> +'rpz exitcode 109' => 'unbound-control reload ist fehlgeschlagen',
>> +'rpz exitcode 110' => 'Die benutzerdefinierte Allow-/Blocklist enthält unzulässige Einträge',
>> +'rpz exitcode 201' => 'Die Anmerkung enthält unzulässige Zeichen',
>> +'rpz exitcode 202' => 'Ungültiger Eintrag in der benutzerdefinierten Allowlist, Zeile ',
>> +'rpz exitcode 203' => 'Ungültiger Eintrag in der benutzerdefinierten Blocklist, Zeile ',
>> +'rpz exitcode 204' => 'Ausgewählter Eintrag existiert nicht: ',
>> +'rpz zf editor' => 'Zonendatei-Eintrag bearbeiten',
>> +'rpz zf imported' => '(importiert aus rpz-config)',
>> +'rpz zf remark info' => 'Erlaubte Zeichen sind a-z, A-Z, 0-9 und Unterstriche',
>> +'rpz zf' => 'Zonendateien',
>> +);
>> diff --git a/config/rpz/rpz.en.pl b/config/rpz/rpz.en.pl
>> new file mode 100644
>> index 000000000..0720a8940
>> --- /dev/null
>> +++ b/config/rpz/rpz.en.pl
>> @@ -0,0 +1,30 @@
>> +# Added for Response Policy Zone (RPZ) add-on
>> +%tr = (%tr,
>> +'rpz' => 'Response Policy Zones (RPZ)',
>> +'rpz apply' => 'Apply',
>> +'rpz cl allow enable' => 'Enable custom allowlist:',
>> +'rpz cl allow info' => 'Allowed domains (one per line)<br>Example: domain.com, *.domain.com',
>> +'rpz cl allow' => 'Custom allowlist',
>> +'rpz cl block enable' => 'Enable custom blocklist:',
>> +'rpz cl block info' => 'Blocked domains (one per line)<br>Example: domain.com, *.domain.com',
>> +'rpz cl block' => 'Custom blocklist',
>> +'rpz cl' => 'Custom lists',
>> +'rpz exitcode 101' => 'the NAME is not valid',
>> +'rpz exitcode 102' => 'unbound-checkconf found invalid configuration. In the Terminal run the command unbound-checkconf for more information',
>> +'rpz exitcode 103' => 'the allow/block list is empty',
>> +'rpz exitcode 104' => 'duplicate - NAME already exists',
>> +'rpz exitcode 105' => 'the URL is not valid',
>> +'rpz exitcode 106' => 'cannot remove the NAME does not exist',
>> +'rpz exitcode 107' => 'the NAME is not valid - "allow" or "block" only',
>> +'rpz exitcode 108' => 'missing or incorrect parameter',
>> +'rpz exitcode 109' => 'unbound-control reload failed',
>> +'rpz exitcode 110' => 'custom Allowlist/Blocklist contains invalid entries',
>> +'rpz exitcode 201' => 'the REMARK is not valid',
>> +'rpz exitcode 202' => 'invalid entry in allowlist, line ',
>> +'rpz exitcode 203' => 'invalid entry in blocklist, line ',
>> +'rpz exitcode 204' => 'Selected entry does not exist: ',
>> +'rpz zf editor' => 'Edit zonefiles entry',
>> +'rpz zf imported' => '(imported from rpz-config)',
>> +'rpz zf remark info' => 'Valid characters are a-z, A-Z, 0-9 and underscore.',
>> +'rpz zf' => 'Zonefiles',
>> +);
>> diff --git a/config/rpz/rpz.es.pl b/config/rpz/rpz.es.pl
>> new file mode 100644
>> index 000000000..98628e4aa
>> --- /dev/null
>> +++ b/config/rpz/rpz.es.pl
>> @@ -0,0 +1,30 @@
>> +# Added for Response Policy Zone (RPZ) add-on
>> +%tr = (%tr,
>> +'rpz' => 'Zonas de política de respuesta (RPZ)',
>> +'rpz apply' => 'Aplicar',
>> +'rpz cl allow enable' => 'Habilitar la lista blanca personalizada:',
>> +'rpz cl allow info' => 'Dominio permitido (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>> +'rpz cl allow' => 'Lista blanca personalizada',
>> +'rpz cl block enable' => 'Habilitar la lista negra personalizada:',
>> +'rpz cl block info' => 'Dominio bloqueado (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>> +'rpz cl block' => 'Lista negra personalizada',
>> +'rpz cl' => 'Lista personalizada',
>> +'rpz exitcode 101' => 'El NOMBRE no es válido',
>> +'rpz exitcode 102' => 'unbound-checkconf ha encontrado una configuración no válida. Desde Terminal, ejecute el comando unbound-checkconf para mayor información',
>> +'rpz exitcode 103' => 'La lista de permitidos/bloqueados está vacía',
>> +'rpz exitcode 104' => 'duplicado - NOMBRE ya existe',
>> +'rpz exitcode 105' => 'la URL no es válida',
>> +'rpz exitcode 106' => 'no es posible eliminar el NOMBRE que no existe',
>> +'rpz exitcode 107' => 'el NOMBRE no es válido - sólo "permitir" o "bloquear"',
>> +'rpz exitcode 108' => 'parámetro faltante o incorrecto',
>> +'rpz exitcode 109' => 'Error al recargar unbound-control',
>> +'rpz exitcode 110' => 'la Lista blanca/Lista negra personalizada contiene entradas no válidas',
>> +'rpz exitcode 201' => 'el COMENTARIO no es válido',
>> +'rpz exitcode 202' => 'entrada no válida en la lista blanca, línea ',
>> +'rpz exitcode 203' => 'entrada no válida en la lista negra, línea ',
>> +'rpz exitcode 204' => 'La entrada seleccionada no existe: ',
>> +'rpz zf editor' => 'Editar la entrada de archivos de zona',
>> +'rpz zf imported' => '(importado de rpz-config)',
>> +'rpz zf remark info' => 'Los caracteres válidos son a-z, A-Z, 0-9 y guión bajo',
>> +'rpz zf' => 'Archivos de zona',
>> +);
>> diff --git a/config/rpz/rpz.fr.pl b/config/rpz/rpz.fr.pl
>> new file mode 100644
>> index 000000000..f35f3c2d0
>> --- /dev/null
>> +++ b/config/rpz/rpz.fr.pl
>> @@ -0,0 +1,30 @@
>> +# Added for Response Policy Zone (RPZ) add-on
>> +%tr = (%tr,
>> +'rpz' => 'Response Policy Zones (RPZ)',
>> +'rpz apply' => 'Appliquer',
>> +'rpz cl allow enable' => 'Activer la liste d\'autorisations personnalisée:',
>> +'rpz cl allow info' => 'Domaines autorisés (un par ligne)<br>Example: domain.com, *.domain.com',
>> +'rpz cl allow' => 'Liste d\'autorisations personnalisée',
>> +'rpz cl block enable' => 'Activer la liste de blocage personnalisée:',
>> +'rpz cl block info' => 'Domaines bloqués (un par ligne)<br>Example: domain.com, *.domain.com',
>> +'rpz cl block' => 'liste de blocage personnalisé',
>> +'rpz cl' => 'Listes personnalisées',
>> +'rpz exitcode 101' => 'le NOM n\'est pas valide',
>> +'rpz exitcode 102' => 'unbound-checkconf configuration non valide trouvée. Dans le terminal, exécutez la commande unbound-checkconf pour plus d\'informations',
>> +'rpz exitcode 103' => 'la liste autoriser/bloquer est vide',
>> +'rpz exitcode 104' => 'le NOM existe déjà',
>> +'rpz exitcode 105' => 'L\'URL n\'est pas valide',
>> +'rpz exitcode 106' => 'impossible de supprimer le NOM n\'existe pas',
>> +'rpz exitcode 107' => 'le NOM n\'est pas valide - « autoriser » ou « bloquer » seulement',
>> +'rpz exitcode 108' => 'paramètre manquant ou incorrect',
>> +'rpz exitcode 109' => 'unbound-control rechargement échoué',
>> +'rpz exitcode 110' => 'la liste autoriser/bloquer contient des entrées non valides',
>> +'rpz exitcode 201' => 'la REMARQUE n\'est pas valable',
>> +'rpz exitcode 202' => 'entrée non valide dans la liste d\'autorisation, ligne ',
>> +'rpz exitcode 203' => 'entrée non valide dans la liste de blocs, ligne ',
>> +'rpz exitcode 204' => 'L\'entrée sélectionnée n\'existe pas: ',
>> +'rpz zf editor' => 'Modifier l\'entrée Fichiers Zone',
>> +'rpz zf imported' => '(importé de rpz-config)',
>> +'rpz zf remark info' => 'Les caractères valides sont a-z, A-Z, 0-9 et soulignement.',
>> +'rpz zf' => 'Fichiers Zone',
>> +);
>> diff --git a/config/rpz/rpz.it.pl b/config/rpz/rpz.it.pl
>> new file mode 100644
>> index 000000000..ee81605c9
>> --- /dev/null
>> +++ b/config/rpz/rpz.it.pl
>> @@ -0,0 +1,30 @@
>> +# Added for Response Policy Zone (RPZ) add-on
>> +%tr = (%tr,
>> +'rpz' => 'Response Policy Zones (RPZ)',
>> +'rpz apply' => 'Applica',
>> +'rpz cl allow enable' => 'Abilita la Whitelist personalizzata:',
>> +'rpz cl allow info' => 'Domini consentiti (uno per riga)<br>Esempio: domain.com, *.domain.com',
>> +'rpz cl allow' => 'Whitelist personalizzata',
>> +'rpz cl block enable' => 'Abilita la Blacklist personalizzata:',
>> +'rpz cl block info' => 'Domini bloccati (uno per riga)<br>Esempio: domain.com, *.domain.com',
>> +'rpz cl block' => 'Blacklist personalizzata',
>> +'rpz cl' => 'Liste personalizzate',
>> +'rpz exitcode 101' => 'il NOME non è valido',
>> +'rpz exitcode 102' => 'unbound-checkconf ha trovato una configurazione non valida. Dal Terminale esegui il comando unbound-checkconf per maggiori informazioni',
>> +'rpz exitcode 103' => 'l\'elenco consentiti/bloccati è vuoto',
>> +'rpz exitcode 104' => 'duplicato - NAME esiste di già',
>> +'rpz exitcode 105' => 'l\'URL non è valido',
>> +'rpz exitcode 106' => 'non è possibile rimuovere il NOME non esiste',
>> +'rpz exitcode 107' => 'il NOME non è valido - solo "consenti" o "blocca"',
>> +'rpz exitcode 108' => 'parametro mancante o non corretto',
>> +'rpz exitcode 109' => 'ricaricamento del controllo non associato non riuscito',
>> +'rpz exitcode 110' => 'la Whitelist/Blacklist personalizzata contiene voci non valide',
>> +'rpz exitcode 201' => 'l"OSSERVAZIONE non è valida',
>> +'rpz exitcode 202' => 'voce non valida nella Whitelist, riga ',
>> +'rpz exitcode 203' => 'voce non valida nella Blacklist, riga ',
>> +'rpz exitcode 204' => 'La voce selezionata non esiste: ',
>> +'rpz zf editor' => 'Modifica la voce dei file di zona',
>> +'rpz zf imported' => '(importato da rpz-config)',
>> +'rpz zf remark info' => 'I caratteri validi sono a-z, A-Z, 0-9 e trattino basso',
>> +'rpz zf' => 'Zonefiles',
>> +);
>> diff --git a/config/rpz/rpz.tr.pl b/config/rpz/rpz.tr.pl
>> new file mode 100644
>> index 000000000..00226e192
>> --- /dev/null
>> +++ b/config/rpz/rpz.tr.pl
>> @@ -0,0 +1,30 @@
>> +# I�in eklendi Ayriyeten Response Policy Zone (RPZ)
>> +%tr = (%tr,
>> +'rpz' => 'Response Policy Zones (RPZ)',
>> +'rpz apply' => 'Uygulamak',
>> +'rpz cl allow enable' => '�zel Etkinlestir allowlist:',
>> +'rpz cl allow info' => 'Izin verilmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>> +'rpz cl allow' => 'Etkinlestir allowlist',
>> +'rpz cl block enable' => '�zel Etkinlestir blocklist:',
>> +'rpz cl block info' => 'Engellenmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>> +'rpz cl block' => 'Engellenmis blocklist',
>> +'rpz cl' => 'Engellenmis lists',
>> +'rpz exitcode 101' => 'NAME ge�erli degil',
>> +'rpz exitcode 102' => 'unbound-checkconf ge�ersiz yapilandirma bulundu. Terminalde daha fazla bilgi i�in Unbound-checkConf komutunu �alistirin',
>> +'rpz exitcode 103' => 'allow/block liste bos',
>> +'rpz exitcode 104' => 'kopyalamak - NAME zaten var',
>> +'rpz exitcode 105' => 'URL ge�erli degil',
>> +'rpz exitcode 106' => '�ikarilamiyor NAME yok',
>> +'rpz exitcode 107' => 'NAME ge�erli degil - "allow" veya "block" yalniz',
>> +'rpz exitcode 108' => 'Parametre eksik veya yanlis',
>> +'rpz exitcode 109' => 'unbound-control basarisiz',
>> +'rpz exitcode 110' => 'Engellenmis Allowlist/Blocklist ge�ersiz girisler i�erir',
>> +'rpz exitcode 201' => 'REMARK ge�erli degil',
>> +'rpz exitcode 202' => 'Ge�ersiz giris allowlist, line ',
>> +'rpz exitcode 203' => 'Ge�ersiz giris blocklist, line ',
>> +'rpz exitcode 204' => 'Se�ilen giris yok: ',
>> +'rpz zf editor' => 'yazimlamak zonefiles giris',
>> +'rpz zf imported' => '(ithal edildi rpz-config)',
>> +'rpz zf remark info' => 'Ge�erli karakterler a-z, A-Z, 0-9ve alt�st.',
>> +'rpz zf' => 'Zonefiles',
>> +);
>> diff --git a/html/cgi-bin/rpz.cgi b/html/cgi-bin/rpz.cgi
>> new file mode 100644
>> index 000000000..a821c92ac
>> --- /dev/null
>> +++ b/html/cgi-bin/rpz.cgi
>> @@ -0,0 +1,923 @@
>> +#!/usr/bin/perl
>> +###############################################################################
>> +# #
>> +# IPFire.org - A linux based firewall #
>> +# Copyright (C) 2005-2024 IPFire Team <info(a)ipfire.org> #
>> +# #
>> +# 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 <http://www.gnu.org/licenses/>. #
>> +# #
>> +###############################################################################
>> +
>> +use strict;
>> +use Scalar::Util qw(looks_like_number);
>> +
>> +# debugging
>> +#use warnings;
>> +#use CGI::Carp 'fatalsToBrowser';
>> +#use Data::Dumper;
>> +
>> +require '/var/ipfire/general-functions.pl';
>> +require "${General::swroot}/lang.pl";
>> +require "${General::swroot}/header.pl";
>> +
>> +###--- Extra HTML ---###
>> +my $extraHead = <<END
>> +<style>
>> + /* alternating row background */
>> + .tbl tr:nth-child(2n+2) {
>> + background-color: var(--color-light-grey);
>> + }
>> + .tbl tr:nth-child(2n+3) {
>> + background-color: var(--color-grey);
>> + }
>> + /* text styles */
>> + .tbl th:not(:last-child) {
>> + text-align: left;
>> + }
>> + div.right {
>> + text-align: right;
>> + margin-top: 0.5em;
>> + }
>> + /* customlist input */
>> + textarea.domainlist {
>> + margin: 0.5em 0;
>> + resize: vertical;
>> + min-height: 10em;
>> + overflow: auto;
>> + white-space: pre;
>> + }
>> + button[type=submit]:disabled {
>> + opacity: 0.6;
>> + }
>> +</style>
>> +END
>> +;
>> +###--- End of extra HTML ---###
>> +
>> +
>> +### Settings ###
>> +
>> +# Request DNS service reload after configuration change
>> +my $RPZ_RELOAD_FLAG = "${General::swroot}/dns/rpz/reload.flag";
>> +
>> +# Configuration file for all available zonefiles
>> +# Format: index, name (unique), enabled (on/off), URL, remark
>> +my $ZONEFILES_CONF = "${General::swroot}/dns/rpz/zonefiles.conf";
>> +
>> +# Configuration file for custom lists
>> +# IDs: 0=allowlist, 1=blocklist, 2=options (allow/block enabled)
>> +my $CUSTOMLISTS_CONF = "${General::swroot}/dns/rpz/customlists.conf";
>> +
>> +# Export custom lists to rpz-config
>> +my $RPZ_ALLOWLIST = "${General::swroot}/dns/rpz/allowlist";
>> +my $RPZ_BLOCKLIST = "${General::swroot}/dns/rpz/blocklist";
>> +
>> +
>> +### Preparation ###
>> +
>> +# Create missing config files
>> +unless(-f $ZONEFILES_CONF) { &General::system('touch', "$ZONEFILES_CONF"); }
>> +unless(-f $CUSTOMLISTS_CONF) { &General::system('touch', "$CUSTOMLISTS_CONF"); }
>> +
>> +
>> +## Global gui data
>> +my $errormessage = "";
>> +
>> +## Global configuration data
>> +my %zonefiles = ();
>> +my %customlists = ();
>> +&_zonefiles_load();
>> +&_customlists_load();
>> +
>> +## Global CGI form data
>> +my %cgiparams = ();
>> +&Header::getcgihash(\%cgiparams);
>> +
>> +my $action = $cgiparams{'ACTION'} // 'NONE';
>> +my $action_key = $cgiparams{'KEY'} // ''; # entry being edited, empty = none/new
>> +
>> +
>> +###--- Process form actions ---###
>> +
>> +# Zonefiles action: Check whether the requested entry exists
>> +if((substr($action, 0, 3) eq 'ZF_') && ($action_key)) {
>> + unless(defined $zonefiles{$action_key}) {
>> + $errormessage = &_rpz_error_tr(204, $action_key);
>> + $action = 'NONE';
>> + }
>> +}
>> +
>> +## Perform actions
>> +if($action eq 'ZF_SAVE') { ## Save new or modified zonefiles entry
>> + if(&_action_zf_save()) {
>> + $action = 'NONE'; # success, return to main page
>> + &_http_prg_redirect();
>> + } else {
>> + $action = 'ZF_EDIT'; # error occured, keep editing
>> + }
>> +
>> +} elsif($action eq 'ZF_TOGGLE') { ## Toggle on/off
>> + if(&_action_zf_toggle()) {
>> + $action = 'NONE';
>> + &_http_prg_redirect();
>> + }
>> +
>> +} elsif($action eq 'ZF_REMOVE') { ## Remove entry
>> + if(&_action_zf_remove()) {
>> + $action = 'NONE';
>> + &_http_prg_redirect();
>> + }
>> +
>> +} elsif($action eq 'CL_SAVE') { ## Save custom lists
>> + if(&_action_cl_save()) {
>> + $action = 'NONE';
>> + &_http_prg_redirect();
>> + }
>> +
>> +} elsif($action eq 'RPZ_RELOAD') { ## Reload dns configuration
>> + if(&_action_rpz_reload()) {
>> + $action = 'NONE';
>> + &_http_prg_redirect();
>> + }
>> +
>> +} elsif($action eq 'UNB_RESTART') { ## Restart unbound service
>> + if(&_action_unb_restart()) {
>> + $action = 'NONE';
>> + &_http_prg_redirect();
>> + }
>> +
>> +}
>> +
>> +
>> +###--- Start GUI ---###
>> +
>> +## Start http output
>> +&Header::showhttpheaders();
>> +
>> +# Start HTML
>> +&Header::openpage($Lang::tr{'rpz'}, 1, $extraHead);
>> +&Header::openbigbox('100%', 'left', '');
>> +
>> +# Show error messages
>> +if($errormessage) {
>> + &_print_message($errormessage);
>> +}
>> +
>> +# Handle zonefile add/edit mode
>> +if($action eq "ZF_EDIT") {
>> + &_print_zonefile_editor();
>> +
>> + # Finalize page and exit cleanly
>> + &Header::closebigbox();
>> + &Header::closepage();
>> + exit(0);
>> +}
>> +
>> +# Show gui elements
>> +&_print_zonefiles();
>> +&_print_customlists();
>> +&_print_gui_extras();
>> +
>> +&Header::closebigbox();
>> +&Header::closepage();
>> +
>> +###--- End of GUI ---###
>> +
>> +
>> +###--- Internal configuration file functions ---###
>> +
>> +# Load all available zonefiles from rpz-config and the internal configuration
>> +sub _zonefiles_load {
>> + # Clean start
>> + %zonefiles = ();
>> +
>> + # Source 1: Get the currently enabled zonefiles from rpz-config (expected format [name]=[URL])
>> + my @enabled_files = &General::system_output('/usr/sbin/rpz-config', 'list');
>> +
>> + foreach my $row (@enabled_files) {
>> + chomp($row);
>> +
>> + # Use regex instead of split() to skip non-matching lines
>> + next unless($row =~ /^(\w+)=(.+)$/);
>> + my ($name, $url) = ($1, $2);
>> +
>> + # Unique names are already guaranteed by rpz-config
>> + if(&_rpz_validate_zonefile($name, $url, '', 0) == 0) {
>> + # Populate global data hash, mark all found entries as enabled
>> + my %entry = ('enabled' => 'on',
>> + 'url' => $url,
>> + 'remark' => $Lang::tr{'rpz zf imported'});
>> +
>> + $zonefiles{$name} = \%entry;
>> + }
>> + }
>> +
>> + # Source 2: Get additional data and disabled entries from configuration file
>> + my %configured_files = ();
>> + &General::readhasharray($ZONEFILES_CONF, \%configured_files);
>> +
>> + foreach my $row (values (%configured_files)) {
>> + my ($name, $enabled, $url, $remark) = @$row;
>> + $remark //= "";
>> +
>> + next unless($name);
>> +
>> + # Check whether this row belongs to an entry already imported from rpz-config
>> + if(defined $zonefiles{$name}) {
>> + # Existing entry, only merge additional data
>> + $zonefiles{$name}{'remark'} = $remark;
>> + } else {
>> + # Skip entry if it is marked as enabled but not found by rpz-config. It was then deleted manually
>> + if($enabled ne 'on') {
>> + # Populate global data hash
>> + my %entry = ('enabled' => 'off',
>> + 'url' => $url // "",
>> + 'remark' => $remark);
>> +
>> + $zonefiles{$name} = \%entry;
>> + }
>> + }
>> + }
>> +}
>> +
>> +# Save internal zonefiles configuration
>> +sub _zonefiles_save_conf {
>> + my $index = 0;
>> + my %export = ();
>> +
>> + # Loop trough all zonefiles and create "hasharray" type export
>> + foreach my $name (keys %zonefiles) {
>> + my @entry = ($name,
>> + $zonefiles{$name}{'enabled'},
>> + $zonefiles{$name}{'url'},
>> + $zonefiles{$name}{'remark'});
>> +
>> + $export{$index++} = \@entry;
>> + }
>> +
>> + &General::writehasharray($ZONEFILES_CONF, \%export);
>> +}
>> +
>> +# Load custom lists from rpz-config and the internal configuration
>> +sub _customlists_load {
>> + # Clean start
>> + %customlists = ();
>> +
>> + # Load configuration file
>> + my %lists_conf = ();
>> + &General::readhasharray($CUSTOMLISTS_CONF, \%lists_conf);
>> +
>> + # Get list options, enabled by default to start import
>> + $customlists{'allow'}{'enabled'} = $lists_conf{2}[0] // 'on';
>> + $customlists{'block'}{'enabled'} = $lists_conf{2}[1] // 'on';
>> +
>> + # Import enabled list from rpz-config, otherwise retrieve stored or empty list from configuration file
>> + if($customlists{'allow'}{'enabled'} eq 'on') {
>> + &_customlist_import('allow', $RPZ_ALLOWLIST);
>> + } else {
>> + $customlists{'allow'}{'list'} = $lists_conf{0} // [];
>> + }
>> + if($customlists{'block'}{'enabled'} eq 'on') {
>> + &_customlist_import('block', $RPZ_BLOCKLIST);
>> + } else {
>> + $customlists{'block'}{'list'} = $lists_conf{1} // [];
>> + }
>> +}
>> +
>> +# Save internal custom lists configuration
>> +sub _customlists_save_conf {
>> + my %export = ();
>> +
>> + # Match IDs with import function
>> + $export{0} = $customlists{'allow'}{'list'};
>> + $export{1} = $customlists{'block'}{'list'};
>> + $export{2} = [$customlists{'allow'}{'enabled'}, $customlists{'block'}{'enabled'}];
>> +
>> + &General::writehasharray($CUSTOMLISTS_CONF, \%export);
>> +}
>> +
>> +# Import a custom list from plain file, returns empty list if file is missing
>> +sub _customlist_import {
>> + my ($listname, $filename) = @_;
>> + my @list = ();
>> +
>> + # File exists, load and check all lines
>> + if(-f $filename) {
>> + open(my $FH, '<', $filename) or die "Can't read $filename: $!";
>> + while(my $line = <$FH>) {
>> + chomp($line);
>> + push(@list, $line);
>> + }
>> + close($FH);
>> +
>> + # Clean up imported data
>> + &_rpz_validate_customlist(\@list, 1);
>> + }
>> +
>> + $customlists{$listname}{'list'} = \@list;
>> +}
>> +
>> +# Export a custom list to plain file or clear file if list is disabled
>> +sub _customlist_export {
>> + my ($listname, $filename) = @_;
>> + return unless(defined $customlists{$listname});
>> +
>> + # Write enabled domain list to file, otherwise save empty file
>> + open(my $FH, '>', $filename) or die "Can't write $filename: $!";
>> +
>> + if($customlists{$listname}{'enabled'} eq 'on') {
>> + foreach my $line (@{$customlists{$listname}{'list'}}) {
>> + print $FH "$line\n";
>> + }
>> + } else {
>> + print $FH "; Note: This list is currently disabled by $ENV{'SCRIPT_NAME'}\n";
>> + }
>> +
>> + close($FH);
>> +}
>> +
>> +
>> +###--- Internal gui functions ---###
>> +
>> +# Show simple message box
>> +sub _print_message {
>> + my ($message, $title) = @_;
>> + $title ||= $Lang::tr{'error messages'};
>> +
>> + &Header::openbox('100%', 'left', $title);
>> + print "<span>$message</span>";
>> + &Header::closebox();
>> +}
>> +
>> +# Show all zone files and related gui elements
>> +sub _print_zonefiles {
>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf'});
>> +
>> + print <<END
>> +<table class="tbl" width="100%">
>> + <tr>
>> + <th>$Lang::tr{'name'}</th>
>> + <th>URL</th>
>> + <th>$Lang::tr{'remark'}</th>
>> + <th colspan="3">$Lang::tr{'action'}</th>
>> + </tr>
>> +END
>> +;
>> +
>> + # Sort zonefiles by name and loop trough all entries
>> + foreach my $name (sort keys %zonefiles) {
>> +
>> + # Toggle button label translation
>> + my $toggle_tr = ($zonefiles{$name}{'enabled'} eq 'on') ? $Lang::tr{'click to disable'} : $Lang::tr{'click to enable'};
>> +
>> + print <<END
>> + <tr>
>> + <td>$name</td>
>> + <td>$zonefiles{$name}{'url'}</td>
>> + <td>$zonefiles{$name}{'remark'}</td>
>> +
>> + <td align="center" width="5%">
>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>> + <input type="hidden" name="KEY" value="$name">
>> + <input type="hidden" name="ACTION" value="ZF_TOGGLE">
>> + <input type="image" src="/images/$zonefiles{$name}{'enabled'}.gif" title="$toggle_tr" alt="$toggle_tr">
>> + </form>
>> + </td>
>> + <td align="center" width="5%">
>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>> + <input type="hidden" name="KEY" value="$name">
>> + <input type="hidden" name="ACTION" value="ZF_EDIT">
>> + <input type="image" src="/images/edit.gif" title="$Lang::tr{'edit'}" alt="$Lang::tr{'edit'}">
>> + </form>
>> + </td>
>> + <td align="center" width="5%">
>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>> + <input type="hidden" name="KEY" value="$name">
>> + <input type="hidden" name="ACTION" value="ZF_REMOVE">
>> + <input type="image" src="/images/delete.gif" title="$Lang::tr{'remove'}" alt="$Lang::tr{'remove'}">
>> + </form>
>> + </td>
>> + </tr>
>> +END
>> +;
>> + }
>> +
>> + # Disable reload button if not needed
>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>> +
>> + print <<END
>> +</table>
>> +
>> +<div class="right">
>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>> + <input type="hidden" name="KEY" value="">
>> + <button type="submit" name="ACTION" value="ZF_EDIT">$Lang::tr{'add'}</button>
>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>> + </form>
>> +</div>
>> +END
>> +;
>> +
>> + &Header::closebox();
>> +}
>> +
>> +# Show zonefiles entry editor
>> +sub _print_zonefile_editor {
>> +
>> + # Key specified: Edit existing entry
>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>> + # Load data to be edited, but don't override already present values (allows user to edit after error)
>> + $cgiparams{'ZF_NAME'} //= $action_key;
>> + $cgiparams{'ZF_URL'} //= $zonefiles{$action_key}{'url'};
>> + $cgiparams{'ZF_REMARK'} //= $zonefiles{$action_key}{'remark'};
>> + }
>> +
>> + # Fallback to empty form
>> + $cgiparams{'ZF_NAME'} //= "";
>> + $cgiparams{'ZF_URL'} //= "";
>> + $cgiparams{'ZF_REMARK'} //= "";
>> +
>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf editor'});
>> +
>> + print <<END
>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>> +<input type="hidden" name="KEY" value="$action_key">
>> +<table width="100%">
>> + <tr>
>> + <td width="20%">$Lang::tr{'name'}: <img src="/blob.gif" alt="*"></td>
>> + <td><input type="text" name="ZF_NAME" value="$cgiparams{'ZF_NAME'}" size="40" maxlength="32" title="$Lang::tr{'rpz zf remark info'}" pattern="[a-zA-Z0-9_]{1,32}" required></td>
>> + </tr>
>> + <tr>
>> + <td width="20%">URL: <img src="/blob.gif" alt="*"></td>
>> + <td><input type="url" name="ZF_URL" value="$cgiparams{'ZF_URL'}" size="40" maxlength="128" required></td>
>> + </tr>
>> + <tr>
>> + <td width="20%">$Lang::tr{'remark'}:</td>
>> + <td><input type="text" name="ZF_REMARK" value="$cgiparams{'ZF_REMARK'}" size="40" maxlength="32"></td>
>> + </tr>
>> + <tr>
>> + <td colspan="2"><hr></td>
>> + </tr>
>> + <tr>
>> + <td width="55%"><img src="/blob.gif" alt="*"> $Lang::tr{'required field'}</td>
>> + <td align="right"><button type="submit" name="ACTION" value="ZF_SAVE">$Lang::tr{'save'}</button></td>
>> + </tr>
>> +</table>
>> +</form>
>> +
>> +<div class="right">
>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>> + <button type="submit" name="ACTION" value="NONE">$Lang::tr{'back'}</button>
>> + </form>
>> +</div>
>> +END
>> +;
>> +
>> + &Header::closebox();
>> +}
>> +
>> +# Show custom allow/block files and related gui elements
>> +sub _print_customlists {
>> +
>> + # Load lists from config, unless they are currently being edited
>> + if($action ne 'CL_SAVE') {
>> + $cgiparams{'ALLOW_LIST'} = join("\n", @{$customlists{'allow'}{'list'}});
>> + $cgiparams{'BLOCK_LIST'} = join("\n", @{$customlists{'block'}{'list'}});
>> +
>> + $cgiparams{'ALLOW_ENABLED'} = ($customlists{'allow'}{'enabled'} eq 'on') ? 'on' : undef;
>> + $cgiparams{'BLOCK_ENABLED'} = ($customlists{'block'}{'enabled'} eq 'on') ? 'on' : undef;
>> + }
>> +
>> + # Fallback to empty form
>> + $cgiparams{'ALLOW_LIST'} //= "";
>> + $cgiparams{'BLOCK_LIST'} //= "";
>> +
>> + # HTML checkboxes, unchecked = no or undef value in POST data
>> + my %checked = ();
>> + $checked{'ALLOW_ENABLED'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? " checked" : "";
>> + $checked{'BLOCK_ENABLED'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? " checked" : "";
>> +
>> + # Disable reload button if not needed
>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>> +
>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz cl'});
>> +
>> + print <<END
>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>> +<table width="100%">
>> + <tr>
>> + <td colspan="2"><b>$Lang::tr{'rpz cl allow'}</b><br>$Lang::tr{'rpz cl allow info'}</td>
>> + <td colspan="2"><b>$Lang::tr{'rpz cl block'}</b><br>$Lang::tr{'rpz cl block info'}</td>
>> + </tr>
>> + <tr>
>> + <td colspan="2"><textarea name="ALLOW_LIST" class="domainlist" cols="45">
>> +$cgiparams{'ALLOW_LIST'}</textarea></td>
>> + <td colspan="2"><textarea name="BLOCK_LIST" class="domainlist" cols="45">
>> +$cgiparams{'BLOCK_LIST'}</textarea></td>
>> + </tr>
>> + <tr>
>> + <td><label for="allow_enabled">$Lang::tr{'rpz cl allow enable'}</label></td>
>> + <td width="15%"><input type="checkbox" name="ALLOW_ENABLED" id="allow_enabled"$checked{'ALLOW_ENABLED'}></td>
>> + <td><label for="block_enabled">$Lang::tr{'rpz cl block enable'}</label></td>
>> + <td width="15%"><input type="checkbox" name="BLOCK_ENABLED" id="block_enabled"$checked{'BLOCK_ENABLED'}></td>
>> + </tr>
>> + <tr>
>> + <td colspan="4"><hr></td>
>> + </tr>
>> + <tr>
>> + <td align="right" colspan="4">
>> + <button type="submit" name="ACTION" value="CL_SAVE">$Lang::tr{'save'}</button>
>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>> + </td>
>> +</table>
>> +</form>
>> +END
>> +;
>> +
>> + &Header::closebox();
>> +}
>> +
>> +# Output javascript and extra gui elements
>> +sub _print_gui_extras {
>> +
>> + # Apply/Restart button modifier key handler
>> + if(&_rpz_needs_reload()) {
>> + print <<END
>> +<script>
>> + // Commit modifier key handler
>> + (function(jq, document) {
>> + var keyEventsOn = false; // Keyboard events attached
>> + var keyModify = false; // Modifier key pressed
>> + var mouseHover = false; // Mouse over commit button
>> + var btnModified = false; // Button modified to "Restart"
>> +
>> + // Document-level key events, enable only while cursor is over button
>> + function attachKeyEvents() {
>> + if(keyEventsOn) {
>> + return;
>> + }
>> + keyEventsOn = true;
>> +
>> + jq(document).on("keydown.rpz", function(event) {
>> + if((!keyModify) && event.shiftKey) {
>> + keyModify = true;
>> + handleModify();
>> + }
>> + });
>> + jq(document).on("keyup.rpz", function(event) {
>> + if(keyModify && (!event.shiftKey)) {
>> + keyModify = false;
>> + handleModify();
>> + }
>> + });
>> + }
>> + function removeKeyEvents() {
>> + keyModify = false;
>> + if(keyEventsOn) {
>> + jq(document).off("keydown.rpz keyup.rpz");
>> + keyEventsOn = false;
>> + }
>> + }
>> +
>> + // Attach mouse hover events to commit buttons
>> + function attachMouseEvents() {
>> + jq("button.commit").on("mouseenter", function(event) {
>> + if(!mouseHover) {
>> + mouseHover = true;
>> + attachKeyEvents();
>> + // Handle already pressed key
>> + keyModify = !!(event.shiftKey);
>> + handleModify();
>> + }
>> + });
>> +
>> + // Cursor moved away: Disable key listener to minimize events
>> + jq("button.commit").on("mouseleave", function() {
>> + if(mouseHover) {
>> + mouseHover = false;
>> + removeKeyEvents();
>> + handleModify();
>> + }
>> + });
>> + }
>> +
>> + // Modify commit button
>> + function handleModify() {
>> + let modify = mouseHover && keyModify;
>> + if(btnModified != modify) {
>> + if(modify) {
>> + jq("button.commit").text("$Lang::tr{'restart'}").val("UNB_RESTART");
>> + } else {
>> + jq("button.commit").text("$Lang::tr{'rpz apply'}").val("RPZ_RELOAD");
>> + }
>> + btnModified = modify;
>> + }
>> + }
>> +
>> + // jQuery DOM ready
>> + jq(function() {
>> + attachMouseEvents();
>> + });
>> + })(jQuery, document);
>> +</script>
>> +END
>> +;
>> + } # End of modifier key handler
>> +
>> +}
>> +
>> +
>> +###--- Internal action processing functions ---###
>> +
>> +# Toggle zonefile on/off
>> +sub _action_zf_toggle {
>> + return unless(defined $zonefiles{$action_key});
>> +
>> + my $result = 0;
>> + my $enabled = $zonefiles{$action_key}{'enabled'};
>> +
>> + # Perform toggle action
>> + if($enabled eq 'on') {
>> + $enabled = 'off';
>> + $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>> + } else {
>> + $enabled = 'on';
>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $action_key, $zonefiles{$action_key}{'url'}, '--no-reload');
>> + }
>> +
>> + # Check for errors, request service reload on success
>> + return unless &_rpz_check_result($result, 1);
>> +
>> + # Save changes
>> + $zonefiles{$action_key}{'enabled'} = $enabled;
>> + &_zonefiles_save_conf();
>> +
>> + return 1;
>> +}
>> +
>> +# Remove zonefile
>> +sub _action_zf_remove {
>> + return unless(defined $zonefiles{$action_key});
>> +
>> + # Remove from rpz-config if currently active
>> + if($zonefiles{$action_key}{'enabled'} eq 'on') {
>> + my $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>> +
>> + # Check for errors, request service reload on success
>> + return unless &_rpz_check_result($result, 1);
>> + }
>> +
>> + # Remove from data hash and save changes
>> + delete $zonefiles{$action_key};
>> + &_zonefiles_save_conf();
>> +
>> + # Clear action_key, as the entry is now removed entirely
>> + $action_key = "";
>> +
>> + return 1;
>> +}
>> +
>> +# Create or update zonefile entry
>> +# Returns undef if gui needs to stay in editor mode
>> +sub _action_zf_save {
>> + my $result = 0;
>> +
>> + my $name = $cgiparams{'ZF_NAME'} // "";
>> + my $url = $cgiparams{'ZF_URL'} // "";
>> + my $remark = $cgiparams{'ZF_REMARK'} // "";
>> + my $enabled = 'on'; # Enable new entries by default
>> +
>> + # Note on variables:
>> + # name = unique key, will be used to address the entry
>> + # action_key = name of the entry being edited, empty for new entry
>> +
>> + # Only check for unique name if it changed
>> + # (this also checks new entries because the action_key is empty in this case)
>> + $result = &_rpz_validate_zonefile($name, $url, $remark, (lc($name) ne lc($action_key)));
>> + return unless &_rpz_check_result($result, 0);
>> +
>> + # Edit existing entry: Determine what was changed
>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>> + # Name und URL remain unchanged, only save remark and finish
>> + if(($name eq $action_key) && ($url eq $zonefiles{$action_key}{'url'})) {
>> + $zonefiles{$action_key}{'remark'} = $remark;
>> + &_zonefiles_save_conf();
>> +
>> + return 1;
>> + }
>> +
>> + # Entry was changed and needs to be recreated, preserve status
>> + $enabled = $zonefiles{$action_key}{'enabled'};
>> +
>> + # Remove from rpz-config
>> + return unless &_action_zf_remove();
>> + }
>> +
>> + # Add new entry to rpz-config
>> + if($enabled eq 'on') {
>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $name, $url, '--no-reload');
>> +
>> + # Check for errors, request service reload on success
>> + return unless &_rpz_check_result($result, 1);
>> + }
>> +
>> + # Add to global data hash and save changes
>> + my %entry = ('enabled' => $enabled,
>> + 'url' => $url,
>> + 'remark' => $remark);
>> +
>> + $zonefiles{$name} = \%entry;
>> + &_zonefiles_save_conf();
>> +
>> + return 1;
>> +}
>> +
>> +# Save custom lists
>> +sub _action_cl_save {
>> + return unless((defined $cgiparams{'ALLOW_LIST'}) && (defined $cgiparams{'BLOCK_LIST'}));
>> +
>> + my $result = 0;
>> +
>> + my @allowlist = split(/\R/, $cgiparams{'ALLOW_LIST'});
>> + my @blocklist = split(/\R/, $cgiparams{'BLOCK_LIST'});
>> +
>> + # Validate lists
>> + $result = &_rpz_validate_customlist(\@allowlist);
>> + if($result != 0) {
>> + $errormessage = &_rpz_error_tr(202, $result);
>> + return;
>> + }
>> + $result = &_rpz_validate_customlist(\@blocklist);
>> + if($result != 0) {
>> + $errormessage = &_rpz_error_tr(203, $result);
>> + return;
>> + }
>> +
>> + # Add to global data hash and save changes
>> + $customlists{'allow'}{'list'} = \@allowlist;
>> + $customlists{'block'}{'list'} = \@blocklist;
>> + $customlists{'allow'}{'enabled'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? 'on' : 'off';
>> + $customlists{'block'}{'enabled'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? 'on' : 'off';
>> +
>> + &_customlists_save_conf();
>> + &_customlist_export('allow', $RPZ_ALLOWLIST);
>> + &_customlist_export('block', $RPZ_BLOCKLIST);
>> +
>> + # Make new lists, request service reload on success
>> + $result = &General::system('/usr/sbin/rpz-make', 'allowblock', '--no-reload');
>> + return unless &_rpz_check_result($result, 1);
>> +
>> + return 1;
>> +}
>> +
>> +# Trigger rpz-config reload
>> +sub _action_rpz_reload {
>> + return 1 unless &_rpz_needs_reload();
>> +
>> + # Immediately clear flag to prevent multiple reloads
>> + if(-f $RPZ_RELOAD_FLAG) {
>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>> + }
>> +
>> + # Perform reload, recreate reload flag on error to enable retry
>> + my $result = &General::system('/usr/sbin/rpz-config', 'reload');
>> + if(not &_rpz_check_result($result, 0)) {
>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>> + return;
>> + }
>> +
>> + return 1;
>> +}
>> +
>> +# Trigger unbound restart
>> +sub _action_unb_restart {
>> + return 1 unless &_rpz_needs_reload();
>> +
>> + # Immediately clear flag to prevent multiple restarts
>> + if(-f $RPZ_RELOAD_FLAG) {
>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>> + }
>> +
>> + # Perform restart, unboundctrl always exits zero
>> + &General::system('/usr/local/bin/unboundctrl', 'restart');
>> +
>> + return 1;
>> +}
>> +
>> +
>> +###--- Internal rpz-config functions ---###
>> +
>> +# Translate rpz-config exitcodes and messages
>> +# 100-199: rpz-config, 200-299: webgui
>> +sub _rpz_error_tr {
>> + my ($error, $append) = @_;
>> + $append //= '';
>> +
>> + # Translate numeric exit codes
>> + if(looks_like_number($error)) {
>> + if(defined $Lang::tr{"rpz exitcode $error"}) {
>> + $error = $Lang::tr{"rpz exitcode $error"};
>> + }
>> + }
>> +
>> + return "RPZ $Lang::tr{'error'}: $error" . &Header::escape($append);
>> +}
>> +
>> +# Check result of rpz-config system call, request reload on success
>> +sub _rpz_check_result {
>> + my ($result, $request_reload) = @_;
>> + $request_reload //= 0;
>> +
>> + # exitcode 0 = success
>> + if($result != 0) {
>> + $errormessage = &_rpz_error_tr($result);
>> + return;
>> + }
>> +
>> + # Set reload flag
>> + if($request_reload) {
>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>> + }
>> +
>> + return 1;
>> +}
>> +
>> +# Test whether reload flag is set
>> +sub _rpz_needs_reload {
>> + return (-f $RPZ_RELOAD_FLAG);
>> +}
>> +
>> +# Validate a zonefile entry, returns rpz-config exitcode on failure. Use _rpz_check_result to verify.
>> +# unique = check for unique name
>> +sub _rpz_validate_zonefile {
>> + my ($name, $url, $remark, $unique) = @_;
>> + $unique //= 1;
>> +
>> + unless($name =~ /^[a-zA-Z0-9_]{1,32}$/) {
>> + return 101;
>> + }
>> + unless($url =~ /^[\w+\.:;\/\\&@#%?=\-~|!]{1,128}$/) {
>> + return 105;
>> + }
>> + unless($remark =~ /^[\w \-()\.:;*\/\\?!&=]{0,32}$/) {
>> + return 201;
>> + }
>> +
>> + # Check against already existing names
>> + if($unique) {
>> + foreach my $existing (keys %zonefiles) {
>> + if(lc($name) eq lc($existing)) {
>> + return 104;
>> + }
>> + }
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +# Validate a custom list, returns number of rejected line on failure. Check for non-zero results.
>> +# listref = array reference, cleanup = remove invalid entries instead of returning an error
>> +sub _rpz_validate_customlist {
>> + my ($listref, $cleanup) = @_;
>> + $cleanup //= 0;
>> +
>> + foreach my $index (reverse 0..$#{$listref}) {
>> + my $row = @$listref[$index];
>> + next unless($row); # Skip/allow empty lines
>> +
>> + # Reject/remove everything besides wildcard domains and remarks
>> + if((not &General::validwildcarddomainname($row)) && (not $row =~ /^;[\w \-()\.:;*\/\\?!&=]*$/)) {
>> + unless($cleanup) {
>> + # +1 for user friendly line number and to ensure non-zero exitcode
>> + return $index + 1;
>> + }
>> +
>> + # Remove current row
>> + splice(@$listref, $index, 1);
>> + }
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +
>> +###--- Internal misc functions ---###
>> +
>> +# Send HTTP 303 redirect headers for post/request/get pattern
>> +# (Must be sent before calling &Header::showhttpheaders())
>> +sub _http_prg_redirect {
>> + my $location = "https://$ENV{'SERVER_NAME'}:$ENV{'SERVER_PORT'}$ENV{'SCRIPT_NAME'}";
>> + print "Status: 303 See Other\n";
>> + print "Location: $location\n";
>> +}
>> diff --git a/lfs/rpz b/lfs/rpz
>> new file mode 100644
>> index 000000000..7ddbc38e5
>> --- /dev/null
>> +++ b/lfs/rpz
>> @@ -0,0 +1,96 @@
>> +###############################################################################
>> +# #
>> +# IPFire.org - A linux based firewall #
>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>> +# #
>> +# 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 <http://www.gnu.org/licenses/>. #
>> +# #
>> +###############################################################################
>> +
>> +###############################################################################
>> +# Definitions
>> +###############################################################################
>> +
>> +include Config
>> +
>> +SUMMARY = response policy zone - RPZ reputation system for unbound DNS
>> +
>> +VER = 1.0.0
>> +
>> +THISAPP = rpz-$(VER)
>> +DIR_APP = $(DIR_SRC)/$(THISAPP)
>> +TARGET = $(DIR_INFO)/$(THISAPP)
>> +
>> +PROG = rpz
>> +PAK_VER = 18
>> +
>> +DEPS =
>> +
>> +SERVICES =
>> +
>> +###############################################################################
>> +# Top-level Rules
>> +###############################################################################
>> +
>> +install : $(TARGET)
>> +
>> +check :
>> +
>> +download :
>> +
>> +b2 :
>> +
>> +dist:
>> + @$(PAK)
>> +
>> +###############################################################################
>> +# Installation Details
>> +###############################################################################
>> +
>> +$(TARGET) :
>> + @$(PREBUILD)
>> + @rm -rf $(DIR_APP)
>> +
>> + # RPZ scripts
>> + install --verbose --mode=755 \
>> + $(DIR_CONF)/rpz/{rpz-config,rpz-metrics,rpz-sleep,rpz-make,rpz-functions} \
>> + --target-directory=/usr/sbin
>> +
>> + # RPZ config files
>> + mkdir -pv /etc/unbound/local.d
>> + install --verbose --mode=644 --owner=nobody --group=nobody \
>> + $(DIR_CONF)/rpz/00-rpz.conf \
>> + --target-directory=/etc/unbound/local.d
>> + chown --verbose --recursive nobody:nobody /etc/unbound/local.d
>> +
>> + # RPZ custom list files for allow and block
>> + mkdir -pv /var/ipfire/dns/rpz
>> + touch /var/ipfire/dns/rpz/{allowlist,blocklist}
>> + chown --verbose --recursive nobody:nobody /var/ipfire/dns/rpz
>> +
>> + # RPZ zone files
>> + # create empty RPZ config file to avoid a unbound config error
>> + mkdir -pv /etc/unbound/zonefiles
>> + touch /etc/unbound/zonefiles/allow.rpz
>> + chown --verbose --recursive nobody:nobody /etc/unbound/zonefiles
>> +
>> + # Install addon-specific language-files
>> + install --verbose --mode=004 $(DIR_CONF)/rpz/rpz.*.pl \
>> + --target-directory=/var/ipfire/addon-lang
>> +
>> + # Install backup definition
>> + cp -vf $(DIR_CONF)/backup/includes/rpz /var/ipfire/backup/addons/includes/rpz
>> +
>> + @rm -rf $(DIR_APP)
>> + @$(POSTBUILD)
>> diff --git a/make.sh b/make.sh
>> index 827ea9e77..a77535b13 100755
>> --- a/make.sh
>> +++ b/make.sh
>> @@ -390,7 +390,7 @@ prepareenv() {
>> if [ "${free_space}" -lt "${required_space}" ]; then
>> # Add any consumed space
>> while read -r consumed_space path; do
>> - (( free_space += consumed_space / 1024 / 1024 ))
>> + (( free_space += consumed_space / 1024 / 1024 ))
>> done <<< "$(du --summarize --bytes "${BUILD_DIR}" "${IMAGES_DIR}" "${LOG_DIR}" 2>/dev/null)"
>> fi
>>
>> @@ -2087,6 +2087,7 @@ build_system() {
>> lfsmake2 btrfs-progs
>> lfsmake2 inotify-tools
>> lfsmake2 grub-btrfs
>> + lfsmake2 rpz
>>
>> lfsmake2 linux
>> lfsmake2 rtl8812au
>> diff --git a/src/paks/rpz/install.sh b/src/paks/rpz/install.sh
>> new file mode 100644
>> index 000000000..ef99bf742
>> --- /dev/null
>> +++ b/src/paks/rpz/install.sh
>> @@ -0,0 +1,36 @@
>> +#!/bin/bash
>> +###############################################################################
>> +# #
>> +# IPFire.org - A linux based firewall #
>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>> +# #
>> +# 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 <http://www.gnu.org/licenses/>. #
>> +# #
>> +###############################################################################
>> +#
>> +. /opt/pakfire/lib/functions.sh
>> +extract_files
>> +restore_backup ${NAME}
>> +
>> +# fix user created files
>> +chown --verbose --recursive nobody:nobody \
>> + /var/ipfire/dns/rpz \
>> + /etc/unbound/zonefiles \
>> + /etc/unbound/local.d
>> +
>> +# Update Language cache
>> +/usr/local/bin/update-lang-cache
>> +
>> +# restart unbound to load config file
>> +/etc/init.d/unbound restart
>> diff --git a/src/paks/rpz/uninstall.sh b/src/paks/rpz/uninstall.sh
>> new file mode 100644
>> index 000000000..e11427df3
>> --- /dev/null
>> +++ b/src/paks/rpz/uninstall.sh
>> @@ -0,0 +1,38 @@
>> +#!/bin/bash
>> +###############################################################################
>> +# #
>> +# IPFire.org - A linux based firewall #
>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>> +# #
>> +# 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 <http://www.gnu.org/licenses/>. #
>> +# #
>> +###############################################################################
>> +#
>> +. /opt/pakfire/lib/functions.sh
>> +
>> +# stop unbound to delete RPZ conf file
>> +/etc/init.d/unbound stop
>> +
>> +make_backup ${NAME}
>> +remove_files
>> +
>> +# delete rpz config files. Otherwise unbound will throw error:
>> +# "[1723428668] unbound-control[17117:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>> +
>> +# Update Language cache
>> +/usr/local/bin/update-lang-cache
>> +
>> +# start unbound to load unbound config file
>> +/etc/init.d/unbound start
>> diff --git a/src/paks/rpz/update.sh b/src/paks/rpz/update.sh
>> new file mode 100644
>> index 000000000..9bc340bc6
>> --- /dev/null
>> +++ b/src/paks/rpz/update.sh
>> @@ -0,0 +1,52 @@
>> +#!/bin/bash
>> +###############################################################################
>> +# #
>> +# IPFire.org - A linux based firewall #
>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>> +# #
>> +# 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 <http://www.gnu.org/licenses/>. #
>> +# #
>> +###############################################################################
>> +#
>> +. /opt/pakfire/lib/functions.sh
>> +
>> +# from update.sh
>> +extract_backup_includes
>> +
>> +# stop unbound to delete RPZ conf file
>> +/etc/init.d/unbound stop
>> +
>> +# from uninstall.sh
>> +make_backup ${NAME}
>> +remove_files
>> +
>> +# delete rpz config files. Otherwise unbound will throw error:
>> +# "unbound-control[nn:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>> +
>> +# from install.sh
>> +extract_files
>> +restore_backup ${NAME}
>> +
>> +# fix user created files
>> +chown --verbose --recursive nobody:nobody \
>> + /var/ipfire/dns/rpz \
>> + /etc/unbound/zonefiles \
>> + /etc/unbound/local.d
>> +
>> +# Update Language cache
>> +/usr/local/bin/update-lang-cache
>> +
>> +# restart unbound to load config files
>> +/etc/init.d/unbound start
>> --
>> 2.39.5
>>
>
Jon
--
Jon Murphy
jon.murphy(a)ipfire.org
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
2025-02-08 18:41 ` jon
@ 2025-02-08 19:27 ` Michael Tremer
[not found] ` <C28A0D7E-6C16-4F6F-9366-A8498F40631E@ipfire.org>
0 siblings, 1 reply; 30+ messages in thread
From: Michael Tremer @ 2025-02-08 19:27 UTC (permalink / raw)
To: development
[-- Attachment #1: Type: text/plain, Size: 110679 bytes --]
Hello Jon,
Thanks for your reply. And good that you are copying everyone into this conversation.
> On 8 Feb 2025, at 18:41, jon <jon.murphy(a)ipfire.org> wrote:
>
> Michael,
>
>> I think I have covered this all at lengths before that this project has been started as a separate effort
>
> Yes, this has been a separate effort (a very public separate effort). Yes, as you pointed this out early on with the "proof-of-concept" and then my request for people to help test RPZ. Nothing was hidden.
>
> This was done because you (and maybe others) did not have the time and I wanted to help and because I needed assistance with RPZ. I tried my best to do this without bothering you.
I don’t that it is accurate that nobody wanted to help on this. The list was always open - although not every email has been replied to swiftly it is also your responsibility to raise a question again if it was missed. People here have open ears.
It was also stated on this very list on in our documentation that working on something without involving the core team is a risky undertaking. Of course IPFire is free software and so everyone is free to fork if they wish to do so.
>> and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls.
>
> You were aware many steps along the way. See your email on July 28, 2024, August 15, 2024, September 30, 2024, December 23, 2024, and January 16. My attempts to get the team involved were met with "things are busy" and sometimes silence. (Yes, I get it, people are busy.)
>
> You and Adolf, Leo, Erik and Bernhard have been aware since the beginning. You mention you were aware of the "proof-of-concept". If you include those beginning posts, since Sep 2023.
Yes, I am aware of a proof-of-concept that I have been running myself for a long time. I am also aware of the efforts that you have been taking.
Yet I don’t think there has ever been any joint effort, or am I seeing that wrong?
>> This has not been discussed . . . on our calls.
>
> On the July 28th you stated:
> "We have talked about RPZ many times on the monthly call since the URL filter feature is falling more and more out of fashion. I think there is also many posts about this on the forum."
>
> Please don’t insult me again by stating "you know what I mean".
>
> And it has been discussed but not documented in the Monthly Meeting notes.
I am not at all insulting you. I don’t want to take this down to a personal level at all. This is a public mailing list and people who read this don’t need to listen to an argument we are having. They are here for the tech inside IPFire.
When I wrote that it has not been discussed that does not mean that we have not been touching on the topic. We have been talking about lots of things on the calls, the weather, politics, how our pets are. None of that makes it to the logs. What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>> Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation.
>
> Regular conversation on the Dev Mailing list is many times met with silence. I get it, people are busy.
>
> And regular two-way conversation doesn’t happen on the list. At least not with me. I’d be happy to point out the posts that were met with silence.
> Again, I get it, people are busy.
And you think my emails are not being met with silence? This has nothing to do with this specific topic. This has something to do with how occupied people are and how engaged they are on certain topics. Not everyone is involved in all the things and simply will ignore emails simply based on their subject line.
> But the "dip here to the list" were my attempts to get a conversation started. As I said, many time met with silence.
>
> The only place I was not met with silence was on the Community. You have a great group of people in the Community. It is a shame you don’t want to have others help. It would reduce your workload.
You should stop making statements that are not true. Who doesn’t want anyone to help?
Not having this conversation on a Saturday evening would reduce my workload. At least it would free up time for something else. Helping with the things that are already on the go would reduce the workload of the entire team. Starting one thing at a time and finishing it is a lot better to manage than starting a hundred things and not even finish one. I can tell you that I already have a hundred things on the go.
>> Therefore, what am I supposed to do with this email?
>
> To me it is beyond obvious…
>
> If it isn’t what you want, then guide me with how to do this the correct way. And be specific. I am trying to help. I am trying to make things better. I am trying to do things the right way.
To me it isn’t. This is yet another project that has been dumped to the list like so many before and later on everyone has left to have the team deal with the rest.
It is a huge patch set. You explained what the vision is, but that is about it. There is no chance this will continue if this disagreement isn’t solved first. I didn’t even look at the code.
>> I don’t want to merge code that I don’t agree with.
>
> I asked multiple times if you "agreed with the concept" and again, met with silence. Yes I get it, people are busy.
Having support for RPZ? Yes, it was definitely on the roadmap. That I agree with.
>> So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>
> You mentioned this a in the past, but for some reason you do not disclose what I dismissed. Why do you continue to make this harder, wouldn’t it not be easier to tell me what I have dismissed?
>
> I have sent multiple emails trying to answer your concerns and comments. On July 28, Aug 14, Aug 22, Aug 23, Sep 30, etc.
>
> I’ve gone through all of the questions you asked and I cannot find a "dismissed" item.
Maybe I need to be *more clear*. I feel humoured by this.
It is late on a Saturday and I want my dinner soon, but certainly I have stated that this should never be an add-on considering it is supposed to replace URL Filter. We should never allow people to add their own sources. I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>
> The maintainers of Unbound and/or RPZ?
>
> The maintainers of Hagezi list, the threatfox list, the urlhaus list, etc.?
>
> What else? The maintainers or the RPZ scripts? That is me. Let’s talk!
You. I don’t care much about the providers of the lists.
> See, this is where it gets confusing. There are hundreds of open source packages as part of IPFire. Pick the last five years of items added to the IPFire build. You're telling me you have "constructive conversation with the maintainers" of all of the added packages?
They publish their software and they don’t care whether I am pulling it or not. They publish it with the commitment to maintain it - sometimes for better and sometimes for worse.
You care about me pulling your code and I don’t know whether you would commit to maintain this.
These two are very different cases.
> Pick the IP Blocklists list (i.e., 3CORESEC, ABUSECH, DSHIELD, SPAMHAUS, etc.) or the Suricata lists (i.e., Emergingthreats.net, Abuse.ch, etc.). So you’ve have "constructive conversation with the maintainers"?
Yes, occasionally I have phone calls with a few of these providers.
>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>
> Ha! Yes a surprise. In the beginning you seemed interested as IPFire needed a replacement for URL Filter. You asked good questions about the lists picked, asked for the value to the users, etc. And I answered the best I could.
>
> You even asked: “Why is this realised as an add-on and not part of the core system?” from your Jul 28, 2024 email.
Ah, so, why is the patch creating an add-on? Not that I am saying that what I say is law, but it has not been challenged either. If my input is being ignored, why should I put this to the top of my list of priorities? I am not disappointed about this, just trying to be very good with my time.
> And on January 16, 2025 I wrote a message looking for help. And you were kind to respond quickly. So in three weeks time, since the kind response, something has changed. You went from supportive to "this".
>
> So yes, I am surprised.
Well, maybe I should not have replied to that email. It was clear that you were on some path that was not right, but you were not interested before in finding the right path from the beginning.
>> Please consider if that can be changed and if there is a path forward with this.
>
> Be more specific, what has to change? What exactly did I dismiss?
Dismissal is just my assumption. I don’t know what you actually did with my feedback. I can only see the end product that does not seem contain much of it. Repeatedly I have been pointing out that we should think before we build. I am sure a lot of hours have now gone into some code that simply does not satisfy me. And I am not not talking about the code itself, what it does is what I don’t think is right for us.
The process is very clear for me that we should first of all think whether we want a certain feature now. Then there should be a clear roadmap for everyone to follow; tasks can be split-up as we go and hopefully then have something that is maintainable, interesting for our users and even would do us proud. This is how this should work.
So, what has to change? I don’t think with shouting at each other, throwing patches around and making me generally unhappy is a good start.
-Michael
> Jon
>
>
>
>> On Feb 6, 2025, at 2:13 PM, Michael Tremer <michael.tremer(a)ipfire.org> wrote:
>>
>> Hello Jon,
>>
>> Well, here we are again with another patch regarding this feature.
>>
>> I cannot quite see from your email what the question is, but if this is a request to have this merged into IPFire, I am once again sorry to disappoint you.
>>
>> I think I have covered this all at lengths before that this project has been started as a separate effort and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls. Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation. Therefore, what am I supposed to do with this email?
>>
>> I don’t want to merge code that I don’t agree with. So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>
>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>
>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>
>> Please consider if that can be changed and if there is a path forward with this.
>>
>> All the best,
>> -Michael
>>
>>> On 6 Feb 2025, at 16:35, Jon Murphy <jon.murphy(a)ipfire.org> wrote:
>>>
>>> What is it?
>>> Response Policy Zone (RPZ) is a mechanism to define local policies in a
>>> standardized way and load those policies from external sources.
>>> Bottom line: RPZ allows admins to easily block access to websites via DNS lookup.
>>>
>>> RPZ can block websites via categories. Examples include: fake websites, annoying
>>> pop-up ads, newly registered domains, DoH bypass sites, bad "host" services,
>>> maliscious top level domains (e.g., *.zip, *.mov), piracy, gambling, pornography,
>>> and more. RPZ lists come from various RPZ providers and their available
>>> catagories.
>>>
>>> This RPZ add-on enables the RPZ functionality by adding a couple lines in a
>>> configuration file. This add-on simply adds configuration files and adds
>>> scripts (config, metrics and sleep) to make RPZ easier for the admin to use.
>>>
>>> The RPZ scripts include additional languages: German, Spanish, French, Turkish,
>>> and Italian.
>>>
>>> RPZ itself was release in 2010 and has been part of the IPFire build since ~2015.
>>>
>>> Why is it needed? What is its value?
>>>
>>> - The RPZ concept places this filtering into IPFire, our internet access
>>> gateway, which is (should be) solely used as DNS source of the internal network.
>>>
>>> - As most sites use HTTPS it makes it difficult to filter traffic with URL
>>> Filter without also properly configuring conventional (non-transparent)
>>> mode on the proxy. RPZ is a nice replacement for the URL Filter.
>>>
>>> - No need to install and maintain an additional device like PiHole or AdBlock
>>> browser extensions on multiple user devices.
>>>
>>> - This is an additional layer of protection for users. Less worry someone will
>>> click on something that gets them into trouble. And, saying this with emphasis,
>>> the ability to do it in one place!
>>>
>>> - Blocked sites save on unneeded traffic and can lessen the threat of malware
>>> in advertisements
>>>
>>> - Logging allows the admin to see the site blocked and take actions
>>>
>>> - RPZ will be used at the home, home-office (work from home), schools,
>>> ministerial, and at the office. Device counts are small (2-6) to medium (~80)
>>> to mediam-large (200+).
>>>
>>> - RPZ can block ads, popups, phishing, scammers, spyware, malware, annoying
>>> popups, NSFW links, DOH servers, and the usual internet trash.
>>>
>>> ------------------------------
>>>
>>> Change Log for RPZ add-on
>>>
>>> rpz-1.0.0-18 on 2025-02-05
>>> - Build for approval & release as IPFire add-on
>>>
>>> ---
>>>
>>> rpz-beta-0.1.18-18.ipfire on 2025-02-01
>>> rpz.cgi:
>>> - new feature: added a mod key to force a unbound restart
>>>
>>> rpz-config and rpz-make:
>>> - new feature: added action for unbound restart `rpz-config unbound-restart`
>>>
>>> rpz-metrics:
>>> - simple reformatting
>>> - rename far right column from "last update" to "last download"
>>>
>>> ---
>>>
>>> rpz-beta-0.1.17-17.ipfire on 2024-12-09
>>> rpz-make
>>> - bug fix: corrected validation regex for wildcards like: `*.domain.com`
>>>
>>> ---
>>>
>>> rpz-beta-0.1.16-16.ipfire on 2024-11-18
>>> rpz-make
>>> - new feature: updated validation regex
>>> - bug fix: moved validation to beginning of process. Now we validate before
>>> creating config files.
>>>
>>> rpz.cgi:
>>> - new feature: use CSS color variables of the main ipfire theme
>>> - bug fix: empty zonefile remarks were stored as “undef” and caused a warning
>>> - bug fix: HTML textarea removes the first empty line in a custom list
>>> - thank you Leo!
>>>
>>> ---
>>>
>>> rpz-beta-0.1.15-15.ipfire on 2024-11-04
>>> rpz.cgi:
>>> - new feature: added new language file for Turkish (thank you Peppe)
>>>
>>> rpz-make
>>> - bug fix: corrected empty allow/block list issue. An empty allow/block list
>>> will now remove contents of allow/block.rpz files and remove unneeded
>>> allow/block.conf file. (thank you iptom)
>>>
>>> ---
>>>
>>> rpz-beta-0.1.14-14.ipfire on 2024-10-29
>>> rpz-config:
>>> - bug fix: correct missing rpz extension. `rpz-config list` displayed URL
>>> incorrectly (thank you Bernhard)
>>>
>>> rpz.cgi:
>>> - bug fix: remove extra `"` in language files (thank you Bernhard)
>>> - new feature: slightly dim "apply" button when not enabled
>>>
>>> ---
>>>
>>> rpz-beta-0.1.13-13.ipfire on 2024-10-27
>>> - skipped
>>>
>>> ---
>>>
>>> rpz-beta-0.1.12-12.ipfire on 2024-10-21
>>> rpz.cgi:
>>> - new feature: added new language file for French (thank you gw-ipfire)
>>>
>>> ---
>>>
>>> rpz-beta-0.1.11-11.ipfire on 2024-10-18
>>> rpz.cgi:
>>> - new feature: added new language file for Italian (thank you umberto)
>>> - new feature: added new language file for Spanish (thank you Roberto)
>>>
>>> ---
>>>
>>> rpz-beta-0.1.10-10.ipfire on 2024-10-15
>>> rpz-make:
>>> - bug fix: corrected validation error for a custom list entry (thank you siosios)
>>> - e.g., `*.cloudflare-dns.com`
>>>
>>> install.sh:
>>> - bug fix: add chown to correct user created files
>>>
>>> update.sh:
>>> - bug fix: add chown to correct user created files (thank you siosios)
>>>
>>> ---
>>>
>>> rpz-beta-0.1.9-9.ipfire on 2024-10-08
>>> rpz.cgi:
>>> - new feature: added new language file for German (thank you Leo)
>>> - bug fix: add missing "rpz exitcode 110"
>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>
>>> ---
>>>
>>> rpz-beta-0.1.8-8.ipfire on 2024-10-04
>>> - skipped
>>>
>>> ---
>>>
>>> rpz-beta-0.1.7-7.ipfire on 2024-10-03
>>> All:
>>> - new feature: includes beta version numbers for pakfire package,
>>> instead of only `rpz-1.0.0-1.ipfire`, for each release.
>>>
>>> rpz.cgi:
>>> - new feature: added new WebGUI at `rpz.cgi`
>>> - a BIG thank you to Leo Hofmann for all of his work creating the webgui!!
>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>
>>> rpz-make:
>>> - new feature: validate entries in allowlist and blocklist
>>> - new feature: add "no-reload" option for WebGUI
>>>
>>> rpz-metrics:
>>> - new feature: info can be sorted by name, by hit count, by line count, by
>>> "enabled" list or all lists
>>>
>>> backups:
>>> - bug fix: include all files in `/var/ipfire/dns/rpz` directory in backup
>>>
>>> update.sh:
>>> - bug fix: corrected ownership for `/var/ipfire/dns/rpz` directory during an
>>> update
>>>
>>> Build:
>>> - bug fix: `block.rpz.conf` and `block.rpz` from build. Files to be created
>>> by `rpz-make`
>>>
>>> WebGUI and German language file
>>> Contribution-by: Leo-Andres Hofmann <hofmann(a)leo-andres.de>
>>>
>>> Spanish language file
>>> Contribution-by: Roberto Peña
>>>
>>> Italian language file
>>> Contribution-by: Umberto Parma
>>>
>>> French language file
>>> Contribution-by: gw-ipfire
>>>
>>> Turkish language file
>>> Contribution-by: Peppe Tech
>>>
>>> Contribution-by: Bernhard Bitsch <bbitsch(a)ipfire.org>
>>> Contribution-by: Erik Kapfer <erik.kapfer(a)ipfire.org>
>>> Signed-off-by: Jon Murphy <jon.murphy(a)ipfire.org
>>> ---
>>> config/backup/includes/rpz | 4 +
>>> config/cfgroot/manualpages | 1 +
>>> config/menu/EX-rpz.menu | 6 +
>>> config/rootfiles/common/configroot | 1 +
>>> config/rootfiles/common/web-user-interface | 1 +
>>> config/rootfiles/packages/rpz | 20 +
>>> config/rpz/00-rpz.conf | 10 +
>>> config/rpz/rpz-config | 130 +++
>>> config/rpz/rpz-functions | 85 ++
>>> config/rpz/rpz-make | 203 +++++
>>> config/rpz/rpz-metrics | 170 ++++
>>> config/rpz/rpz-sleep | 58 ++
>>> config/rpz/rpz.de.pl | 30 +
>>> config/rpz/rpz.en.pl | 30 +
>>> config/rpz/rpz.es.pl | 30 +
>>> config/rpz/rpz.fr.pl | 30 +
>>> config/rpz/rpz.it.pl | 30 +
>>> config/rpz/rpz.tr.pl | 30 +
>>> html/cgi-bin/rpz.cgi | 923 +++++++++++++++++++++
>>> lfs/rpz | 96 +++
>>> make.sh | 3 +-
>>> src/paks/rpz/install.sh | 36 +
>>> src/paks/rpz/uninstall.sh | 38 +
>>> src/paks/rpz/update.sh | 52 ++
>>> 24 files changed, 2016 insertions(+), 1 deletion(-)
>>> create mode 100644 config/backup/includes/rpz
>>> create mode 100644 config/menu/EX-rpz.menu
>>> create mode 100644 config/rootfiles/packages/rpz
>>> create mode 100644 config/rpz/00-rpz.conf
>>> create mode 100644 config/rpz/rpz-config
>>> create mode 100644 config/rpz/rpz-functions
>>> create mode 100644 config/rpz/rpz-make
>>> create mode 100755 config/rpz/rpz-metrics
>>> create mode 100755 config/rpz/rpz-sleep
>>> create mode 100644 config/rpz/rpz.de.pl
>>> create mode 100644 config/rpz/rpz.en.pl
>>> create mode 100644 config/rpz/rpz.es.pl
>>> create mode 100644 config/rpz/rpz.fr.pl
>>> create mode 100644 config/rpz/rpz.it.pl
>>> create mode 100644 config/rpz/rpz.tr.pl
>>> create mode 100644 html/cgi-bin/rpz.cgi
>>> create mode 100644 lfs/rpz
>>> create mode 100644 src/paks/rpz/install.sh
>>> create mode 100644 src/paks/rpz/uninstall.sh
>>> create mode 100644 src/paks/rpz/update.sh
>>>
>>> diff --git a/config/backup/includes/rpz b/config/backup/includes/rpz
>>> new file mode 100644
>>> index 000000000..36513e494
>>> --- /dev/null
>>> +++ b/config/backup/includes/rpz
>>> @@ -0,0 +1,4 @@
>>> +/var/ipfire/dns/rpz/*
>>> +/etc/unbound/zonefiles/allow.rpz
>>> +/etc/unbound/zonefiles/block.rpz
>>> +/etc/unbound/local.d/*rpz.conf
>>> diff --git a/config/cfgroot/manualpages b/config/cfgroot/manualpages
>>> index 1f7e01efc..d3a48c633 100644
>>> --- a/config/cfgroot/manualpages
>>> +++ b/config/cfgroot/manualpages
>>> @@ -70,6 +70,7 @@ pakfire.cgi=configuration/ipfire/pakfire
>>> wlanap.cgi=addons/wireless
>>> tor.cgi=addons/tor
>>> samba.cgi=addons/samba
>>> +rpz.cgi=addons/rpz
>>>
>>> # Logs menu
>>> logs.cgi/summary.dat=configuration/logs/summary
>>> diff --git a/config/menu/EX-rpz.menu b/config/menu/EX-rpz.menu
>>> new file mode 100644
>>> index 000000000..2f4daf410
>>> --- /dev/null
>>> +++ b/config/menu/EX-rpz.menu
>>> @@ -0,0 +1,6 @@
>>> +$subipfire->{'20.rpz'} = {
>>> + 'caption' => $Lang::tr{'rpz'},
>>> + 'uri' => '/cgi-bin/rpz.cgi',
>>> + 'title' => "RPZ",
>>> + 'enabled' => 1,
>>> +};
>>> diff --git a/config/rootfiles/common/configroot b/config/rootfiles/common/configroot
>>> index 9839eee45..b30d6aae4 100644
>>> --- a/config/rootfiles/common/configroot
>>> +++ b/config/rootfiles/common/configroot
>>> @@ -120,6 +120,7 @@ var/ipfire/menu.d/70-log.menu
>>> #var/ipfire/menu.d/EX-apcupsd.menu
>>> #var/ipfire/menu.d/EX-guardian.menu
>>> #var/ipfire/menu.d/EX-mympd.menu
>>> +#var/ipfire/menu.d/EX-rpz.menu
>>> #var/ipfire/menu.d/EX-samba.menu
>>> #var/ipfire/menu.d/EX-tor.menu
>>> #var/ipfire/menu.d/EX-transmission.menu
>>> diff --git a/config/rootfiles/common/web-user-interface b/config/rootfiles/common/web-user-interface
>>> index 816241dae..e00464076 100644
>>> --- a/config/rootfiles/common/web-user-interface
>>> +++ b/config/rootfiles/common/web-user-interface
>>> @@ -69,6 +69,7 @@ srv/web/ipfire/cgi-bin/proxy.cgi
>>> srv/web/ipfire/cgi-bin/qos.cgi
>>> srv/web/ipfire/cgi-bin/remote.cgi
>>> srv/web/ipfire/cgi-bin/routing.cgi
>>> +#srv/web/ipfire/cgi-bin/rpz.cgi
>>> #srv/web/ipfire/cgi-bin/samba.cgi
>>> srv/web/ipfire/cgi-bin/services.cgi
>>> srv/web/ipfire/cgi-bin/shutdown.cgi
>>> diff --git a/config/rootfiles/packages/rpz b/config/rootfiles/packages/rpz
>>> new file mode 100644
>>> index 000000000..1c8663049
>>> --- /dev/null
>>> +++ b/config/rootfiles/packages/rpz
>>> @@ -0,0 +1,20 @@
>>> +etc/unbound/local.d/00-rpz.conf
>>> +etc/unbound/zonefiles
>>> +etc/unbound/zonefiles/allow.rpz
>>> +usr/sbin/rpz-config
>>> +usr/sbin/rpz-functions
>>> +usr/sbin/rpz-make
>>> +usr/sbin/rpz-metrics
>>> +usr/sbin/rpz-sleep
>>> +var/ipfire/addon-lang/rpz.de.pl
>>> +var/ipfire/addon-lang/rpz.en.pl
>>> +var/ipfire/addon-lang/rpz.es.pl
>>> +var/ipfire/addon-lang/rpz.fr.pl
>>> +var/ipfire/addon-lang/rpz.it.pl
>>> +var/ipfire/addon-lang/rpz.tr.pl
>>> +var/ipfire/backup/addons/includes/rpz
>>> +var/ipfire/dns/rpz
>>> +var/ipfire/dns/rpz/allowlist
>>> +var/ipfire/dns/rpz/blocklist
>>> +var/ipfire/menu.d/EX-rpz.menu
>>> +srv/web/ipfire/cgi-bin/rpz.cgi
>>> diff --git a/config/rpz/00-rpz.conf b/config/rpz/00-rpz.conf
>>> new file mode 100644
>>> index 000000000..f005a4f2e
>>> --- /dev/null
>>> +++ b/config/rpz/00-rpz.conf
>>> @@ -0,0 +1,10 @@
>>> +server:
>>> + module-config: "respip validator iterator"
>>> +
>>> +rpz:
>>> + name: allow.rpz
>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>> + rpz-action-override: passthru
>>> + rpz-log: yes
>>> + rpz-log-name: allow
>>> + rpz-signal-nxdomain-ra: yes
>>> diff --git a/config/rpz/rpz-config b/config/rpz/rpz-config
>>> new file mode 100644
>>> index 000000000..c72d50f9b
>>> --- /dev/null
>>> +++ b/config/rpz/rpz-config
>>> @@ -0,0 +1,130 @@
>>> +#!/bin/bash
>>> +###############################################################################
>>> +# #
>>> +# IPFire.org - A linux based firewall #
>>> +# Copyright (C) 2024-2025 IPFire Team <info(a)ipfire.org> #
>>> +# #
>>> +# 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 <http://www.gnu.org/licenses/>. #
>>> +# #
>>> +###############################################################################
>>> +
>>> +version="2025-01-11 - v44"
>>> +
>>> +############### Functions ###############
>>> +
>>> +source /usr/sbin/rpz-functions
>>> +
>>> +############### Main ###############
>>> +
>>> +tagName="unbound"
>>> +
>>> +rpzAction="${1}" # input RPZ action
>>> +rpzName="${2}" # input RPZ name
>>> +rpzURL="${3}" # input RPZ URL
>>> +rpzOption1="${4}" # input RPZ option #1
>>> +rpzOption2="${5}" # input RPZ option #2
>>> +
>>> +rpzConfig="/etc/unbound/local.d/${rpzName}.rpz.conf" # output zone conf file
>>> +rpzFile="/etc/unbound/zonefiles/${rpzName}.rpz" # output for RPZ file
>>> +
>>> +rpzLog="yes" # log default is yes
>>> +ucReload="yes" # reload default is yes
>>> +
>>> +while [[ $# -gt 0 ]] ; do
>>> + case "$1" in
>>> + --no-log ) rpzLog="no" ;;
>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>> + esac
>>> + shift # Shift after checking all the cases to get next option
>>> +done
>>> +
>>> +case "${rpzAction}" in
>>> + # add new rpz list
>>> + add )
>>> + check_name "${rpzName}" # is this a valid name?
>>> + # does this config already exist? If yes, then exit
>>> + if [[ -f "${rpzConfig}" ]] ; then
>>> + msg_log "error: rpz: duplicate - ${rpzConfig} already exists. exit"
>>> + exit 104
>>> + fi
>>> +
>>> + # is this a valid URL?
>>> + regex='^https://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
>>> + if ! [[ "${rpzURL}" =~ $regex ]] ; then
>>> + msg_log "error: rpz: the URL is not valid: \"${rpzURL}\". exit."
>>> + exit 105
>>> + fi
>>> +
>>> + # create the zone config file
>>> + {
>>> + echo "rpz:"
>>> + echo " name: ${rpzName}.rpz"
>>> + echo " zonefile: ${rpzFile}"
>>> + echo " url: ${rpzURL}"
>>> + echo " rpz-action-override: nxdomain"
>>> + echo " rpz-log: ${rpzLog}"
>>> + echo " rpz-log-name: ${rpzName}"
>>> + echo " rpz-signal-nxdomain-ra: yes"
>>> + } > "${rpzConfig}"
>>> +
>>> + # set-up zonefile
>>> + # create an empty rpz file if it does not exist
>>> + if [[ ! -f "${rpzFile}" ]] ; then
>>> + touch "${rpzFile}"
>>> + # unbound requires these settings for rpz files
>>> + set_permissions "${rpzFile}" "${rpzConfig}"
>>> + fi
>>> + ;;
>>> +
>>> + # trash config file & rpz file
>>> + remove )
>>> + if ! [[ -f "${rpzConfig}" ]] ; then
>>> + msg_log "error: rpz: cannot remove ${rpzConfig}, does not exist. exit"
>>> + exit 106
>>> + fi
>>> +
>>> + msg_log "info: rpz: remove config file & rpz file \"${rpzName}\""
>>> + rm "${rpzConfig}"
>>> + rm "${rpzFile}"
>>> + ;;
>>> +
>>> + reload )
>>> + check_unbound_conf "${checkConf}"
>>> + ;;
>>> +
>>> + list )
>>> + awk -F':' '/^\s*name:/{ gsub(/[[:blank:]]|\.rpz/, "",$2) ; NAME=$2 } \
>>> + /^\s*url:/{ gsub(/[[:blank:]]/, "") ; print NAME"="$2":"$3} ' \
>>> + /etc/unbound/local.d/*rpz.conf
>>> + exit
>>> + ;;
>>> +
>>> + unbound-restart )
>>> + check_unbound_conf "${checkConf}"
>>> + unbound_restart
>>> + exit
>>> + ;;
>>> +
>>> + * )
>>> + msg_log "error: rpz: missing or incorrect parameter"
>>> + printf "Usage: $(basename "$0") <ACTION> <NAME> <URL> <OPTION> <OPTION>\n"
>>> + printf "Version: ${version}\n"
>>> + exit 108
>>> + ;;
>>> +
>>> +esac
>>> +
>>> +unbound_control_reload "${ucReload}"
>>> +
>>> +exit
>>> diff --git a/config/rpz/rpz-functions b/config/rpz/rpz-functions
>>> new file mode 100644
>>> index 000000000..ace1d2690
>>> --- /dev/null
>>> +++ b/config/rpz/rpz-functions
>>> @@ -0,0 +1,85 @@
>>> +#!/bin/bash
>>> +###############################################################################
>>> +# #
>>> +# IPFire.org - A linux based firewall #
>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>> +# #
>>> +# 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 <http://www.gnu.org/licenses/>. #
>>> +# #
>>> +###############################################################################
>>> +
>>> +version="2024-12-10 - v02"
>>> +
>>> +############### Functions ###############
>>> +
>>> +msg_log () {
>>> + logger --tag "${tagName}" "$*"
>>> + if tty --silent ; then
>>> + echo "${tagName}:" "$*"
>>> + fi
>>> +}
>>> +
>>> +# check for a valid name
>>> +check_name () {
>>> + local theName="${1}"
>>> +
>>> + regex='^[a-zA-Z0-9_]+$' # no dash or plus, alpha numeric only
>>> + regex1='^(allow|block)$' # allow and block are reserved NAMEs
>>> + if [[ ! "${theName}" =~ $regex ]] || [[ "${theName}" =~ $regex1 ]] ; then
>>> + msg_log "error: rpz: the NAME is not valid: \"${theName}\". exit."
>>> + exit 101
>>> + fi
>>> +}
>>> +
>>> +set_permissions () {
>>> + chown nobody:nobody "$@"
>>> + chmod 644 "$@"
>>> +}
>>> +
>>> +check_unbound_conf () {
>>> + local thecheckConf="${1:-yes}" # check config default is yes
>>> +
>>> + # check the above config files
>>> + if [[ "${thecheckConf}" == yes ]] ; then
>>> + msg_log "info: rpz: check for errors with \"unbound-checkconf\""
>>> +
>>> + if ! unbound-checkconf ; then
>>> + msg_log "error: rpz: unbound-checkconf found invalid configuration."
>>> + msg_log \
>>> + "error: rpz: In Terminal run the command \"unbound-checkconf\" for more information. exit."
>>> + exit 102
>>> + fi
>>> + fi
>>> +}
>>> +
>>> +unbound_control_reload () {
>>> + local theReload="${1:-yes}" # reload default is yes
>>> +
>>> + if [[ "${theReload}" == yes ]] ; then
>>> + # reload due to the changes
>>> + msg_log "info: rpz: run \"unbound-control reload\""
>>> +
>>> + if ! unbound-control reload ; then
>>> + msg_log "error: rpz: unbound-control reload. exit."
>>> + exit 109
>>> + fi
>>> + fi
>>> +}
>>> +
>>> +unbound_restart () {
>>> + # restart due to the changes
>>> + msg_log "info: rpz: run \"unbound restart\""
>>> +
>>> + /usr/local/bin/unboundctrl restart
>>> +}
>>> diff --git a/config/rpz/rpz-make b/config/rpz/rpz-make
>>> new file mode 100644
>>> index 000000000..927d55170
>>> --- /dev/null
>>> +++ b/config/rpz/rpz-make
>>> @@ -0,0 +1,203 @@
>>> +#!/bin/bash
>>> +###############################################################################
>>> +# #
>>> +# IPFire.org - A linux based firewall #
>>> +# Copyright (C) 2024-2025 IPFire Team <info(a)ipfire.org> #
>>> +# #
>>> +# 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 <http://www.gnu.org/licenses/>. #
>>> +# #
>>> +###############################################################################
>>> +
>>> +version="2025-01-11 - v14"
>>> +
>>> +############### Functions ###############
>>> +
>>> +source /usr/sbin/rpz-functions
>>> +
>>> +# create the config file for allow
>>> +make_allow_config () {
>>> + local theLog="${1:-yes}" # log default ON
>>> + local theConfig="/etc/unbound/local.d/00-rpz.conf" # output zone conf file
>>> + local theList="/var/ipfire/dns/rpz/allowlist" # input custom list of domains
>>> +
>>> + msg_log "info: rpz: make config file \"00-rpz.conf\""
>>> +
>>> + echo "server:
>>> + module-config: \"respip validator iterator\"" > "${theConfig}"
>>> +
>>> + # does allow list exist?
>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>> +
>>> + echo "rpz:
>>> + name: allow.rpz
>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>> + rpz-action-override: passthru
>>> + rpz-log: ${theLog}
>>> + rpz-log-name: allow
>>> + rpz-signal-nxdomain-ra: yes" >> "${theConfig}"
>>> +
>>> + fi
>>> +
>>> + # set-up zonefile - unbound requires these settings for rpz files
>>> + set_permissions "${theConfig}"
>>> +}
>>> +
>>> +# create the config file for block
>>> +make_block_config () {
>>> + local theLog="${1:-yes}" # log default ON
>>> + local theConfig="/etc/unbound/local.d/block.rpz.conf" # output zone conf file
>>> + local theList="/var/ipfire/dns/rpz/blocklist" # input custom list of domains
>>> +
>>> + msg_log "info: rpz: make config file \"block.rpz.conf\""
>>> +
>>> + # does block list exist?
>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>> +
>>> + echo "rpz:
>>> + name: block.rpz
>>> + zonefile: /etc/unbound/zonefiles/block.rpz
>>> + rpz-action-override: nxdomain
>>> + rpz-log: ${theLog}
>>> + rpz-log-name: block
>>> + rpz-signal-nxdomain-ra: yes" > "${theConfig}"
>>> +
>>> + # set-up zonefile - unbound requires these settings for rpz files
>>> + set_permissions "${theConfig}"
>>> + else
>>> + # no - trash the config file
>>> + rm --verbose /etc/unbound/local.d/block.rpz.conf
>>> + fi
>>> +}
>>> +
>>> +# create an RPZ file for allow or block
>>> +make_rpz_file () {
>>> + local theName="${1}" # allow or block
>>> + local theAction='.' # the default is nxdomain or block
>>> + local actionList
>>> +
>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>> + local theZonefile="/etc/unbound/zonefiles/${theName}.rpz" # output file for RPZ
>>> +
>>> + # does a list exist?
>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>> +
>>> + # for allow set to passthru
>>> + [[ "${theName}" == allow ]] && theAction='rpz-passthru.'
>>> +
>>> + # drop any extra "blanks" and add "CNAME <RPZ action>." to each line
>>> + actionList=$( awk '{$1=$1};1' "${theList}" |
>>> + sed "/^[^;].*[[:alnum:]]/ s|$| CNAME ${theAction}|" )
>>> +
>>> + msg_log "info: rpz: create zonefile for \"${theName}list\""
>>> +
>>> +echo "; Name: ${theName} list
>>> +; Last modified: $(date "+%Y-%m-%d at %H.%M.%S %Z")
>>> +;
>>> +; domains with actions list
>>> +;
>>> +${actionList}" > "${theZonefile}"
>>> +
>>> + # set-up zonefile - unbound requires these settings for rpz files
>>> + set_permissions "${theZonefile}"
>>> + # set-up allow/block list files
>>> + set_permissions "${theList}"
>>> + else
>>> + msg_log "info: rpz: the ${theList} is empty."
>>> +
>>> + rm --verbose "${theZonefile}" # trash the RPZ file
>>> + fi
>>> +}
>>> +
>>> +# check if allow/block list is valid
>>> +validate_list () {
>>> + local theName="${1}" # allow or block
>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>> +
>>> + # remove good:
>>> + # - properly formated domain names with or without leading wildcard
>>> + # - properly formated top level domain (TLD) names with wildcard
>>> + # - blank lines and comment lines
>>> + # remaining lines are considered "bad"
>>> + bad_lines=$( sed --regexp-extended \
>>> + '/^(\*\.)?([a-zA-Z0-9](([a-zA-Z0-9\-]){0,61}[a-zA-Z0-9])?\.)+([a-zA-Z]{2,}|xn--[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])$/d ;
>>> + /^(\*\.)([a-z]{2,61}|xn--[a-z0-9]{1,60})$/d ;
>>> + /^$/d ; /^;/d' "${theList}" )
>>> +
>>> + if [[ ! -z "${bad_lines}" ]] ; then
>>> + msg_log "error: rpz: invalid line(s) in ${theList}."
>>> + printf "%s\n" "bad line(s): ${bad_lines}"
>>> + exit 110
>>> + fi
>>> +}
>>> +
>>> +
>>> +############### Main ###############
>>> +
>>> +tagName="unbound"
>>> +
>>> +rpzName="${1}" # input RPZ name
>>> +
>>> +rpzLog="yes" # log default is yes
>>> +ucReload="yes" # reload default is yes
>>> +
>>> +while [[ $# -gt 0 ]] ; do
>>> + case "$1" in
>>> + --no-log ) rpzLog="no" ;;
>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>> + esac
>>> + shift # Shift after checking all the cases to get next option
>>> +done
>>> +
>>> +case "${rpzName}" in
>>> + # make a new allow or block rpz file
>>> +
>>> + allow )
>>> + validate_list 'allow' # is the allowlist valid?
>>> + make_allow_config "${rpzLog}"
>>> + make_rpz_file 'allow'
>>> + ;;
>>> +
>>> + allowblock )
>>> + validate_list 'allow' # is the list valid?
>>> + make_allow_config "${rpzLog}"
>>> + make_rpz_file 'allow'
>>> + ;&
>>> +
>>> + block )
>>> + validate_list 'block' # is the blocklist valid?
>>> + make_block_config "${rpzLog}"
>>> + make_rpz_file 'block'
>>> + ;;
>>> +
>>> + reload )
>>> + check_unbound_conf "${checkConf}"
>>> + ;;
>>> +
>>> + unbound-restart )
>>> + check_unbound_conf "${checkConf}"
>>> + unbound_restart
>>> + exit
>>> + ;;
>>> +
>>> + * )
>>> + msg_log "error: rpz: missing or incorrect parameter"
>>> + printf "Usage: $(basename "$0") <NAME> <OPTION> <OPTION>\n"
>>> + printf "Version: ${version}\n"
>>> + exit 108
>>> + ;;
>>> +esac
>>> +
>>> +unbound_control_reload "${ucReload}"
>>> +
>>> +exit
>>> diff --git a/config/rpz/rpz-metrics b/config/rpz/rpz-metrics
>>> new file mode 100755
>>> index 000000000..4d43e1629
>>> --- /dev/null
>>> +++ b/config/rpz/rpz-metrics
>>> @@ -0,0 +1,170 @@
>>> +#!/bin/bash
>>> +###############################################################################
>>> +# #
>>> +# IPFire.org - A linux based firewall #
>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>> +# #
>>> +# 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 <http://www.gnu.org/licenses/>. #
>>> +# #
>>> +###############################################################################
>>> +
>>> +version="2025-01-20 - v25"
>>> +
>>> +############### Main ###############
>>> +
>>> +weeks="2" # default to two message logs
>>> +sortBy="name" # default "by name"
>>> +rpzActive="enabled" # default "enabled only"
>>> +
>>> +while [[ $# -gt 0 ]] ; do
>>> + case "$1" in
>>> + --by-names | --by-name | name ) sortBy="name" ;;
>>> +
>>> + --by-hits | --by-hit | hits | hit ) sortBy="hit" ;;
>>> +
>>> + --by-lines | --by-line | lines | line ) sortBy="line" ;;
>>> +
>>> + --by-effect ) sortBy="effect" ;;
>>> +
>>> + --enabled-only ) rpzActive="enabled" ;;
>>> +
>>> + --active-all | --all | all ) rpzActive="all" ;;
>>> +
>>> + [0-9] | [0-9][0-9] ) weeks=$1 ;;
>>> + esac
>>> + shift # Shift after checking all the cases to get next option
>>> +done
>>> +
>>> +# get the list of message logs for N weeks
>>> +messageLogs=$( find /var/log/messages* -type f | sort --version-sort |
>>> + head -"${weeks}" )
>>> +
>>> +# get the list of RPZ names & counts from the message log(s)
>>> +rpzNameCount=$( for logf in ${messageLogs} ; do
>>> + zgrep --text --fixed-strings 'info: rpz: applied' "${logf}" |
>>> + awk '$10 ~ /\[\w*]/ { print $10 }' ;
>>> + done | sort | uniq --count )
>>> +
>>> +# flip results and remove brackets `[` and `]`
>>> +rpzNameCount=$( echo "${rpzNameCount}" |
>>> + awk '{ print $2, $1 }' |
>>> + sed --regexp-extended 's|^\[(.*)\]|\1|' )
>>> +
>>> +# grab only names
>>> +rpzNames=$( echo "${rpzNameCount}" | awk '{ print $1 }' )
>>> +
>>> +# get list of RPZ files
>>> +rpzFileList=$( find /etc/unbound/zonefiles -type f -iname "*.rpz" )
>>> +
>>> +# get basename of those files
>>> +rpzBaseNames=$( echo "${rpzFileList}" |
>>> + sed 's|/etc/unbound/zonefiles/||g ; s|\.rpz||g ;' )
>>> +
>>> +# add to rpzNames
>>> +rpzNames="${rpzNames}"$'\n'"${rpzBaseNames}"
>>> +
>>> +# drop duplicate names
>>> +rpzNames=$( echo "${rpzNames}" | sort --unique )
>>> +
>>> +# get line count for each RPZ
>>> +lineCount=$( echo "${rpzFileList}" | xargs wc -l )
>>> +
>>> +# get comment line count and blank line count for each RPZ
>>> +commentCount=$( echo "${rpzFileList}" | xargs grep --count -e "^$" -e "^;" )
>>> +
>>> +# get modified date each RPZ
>>> +modDateList=$( echo "${rpzFileList}" | xargs stat -c '%.10y %n' )
>>> +
>>> +ucListAuthZones=$( unbound-control list_auth_zones )
>>> +
>>> +# get width of RPZ names
>>> +pWidth=$( echo "${rpzNames}" | awk '{ print $1" " }' | wc -L )
>>> +pFormat="%-${pWidth}s %-8s %-8s %8s %12s %12s\n"
>>> +
>>> +# print title line
>>> +printf "${pFormat}" "name" "hits" "active" "lines" " hits/line" "last download"
>>> +printf -- "--------------"
>>> +
>>> +theResults=""
>>> +totalLines=0
>>> +totalHits=0
>>> +while read -r theName
>>> +do
>>> + printf -- "--" # pretend progress bar
>>> +
>>> + # is this RPZ list active?
>>> + theActive="disabled"
>>> + if grep --quiet "^${theName}\.rpz" <<< "${ucListAuthZones}"
>>> + then
>>> + theActive="enabled"
>>> + else
>>> + [[ "${rpzActive}" == enabled ]] && continue
>>> + fi
>>> +
>>> + # get hit count
>>> + theHits="0"
>>> + if output=$( grep "^${theName}\s" <<< "${rpzNameCount}" ) ; then
>>> + theHits=$( echo "${output}" | awk '{ print $2 }' )
>>> + totalHits=$(( totalHits + theHits ))
>>> + fi
>>> +
>>> + # get line count
>>> + theLines="n/a"
>>> + hitsPerLine="0"
>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${lineCount}" ) ; then
>>> + theLines=$( echo "${output}" | awk '{ print $1 }' )
>>> + totalLines=$(( totalLines + theLines ))
>>> +
>>> + if [[ "${theLines}" -gt 2 ]] ; then
>>> + hitsPerLine=$(( 100 * theHits / theLines ))
>>> + fi
>>> + fi
>>> +
>>> + # get modification date
>>> + theModDate="n/a"
>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${modDateList}" ) ; then
>>> + theModDate=$( echo "${output}" | awk '{ print $1 }' )
>>> + fi
>>> +
>>> + # add to results list
>>> + theResults+="${theName} ${theHits} ${theActive} ${theLines} ${hitsPerLine} ${theModDate}"$'\n'
>>> +
>>> +done <<< "${rpzNames}"
>>> +
>>> +case "${sortBy}" in
>>> + # sort by "active" then by "name"
>>> + name) sortArg=(-k3,3r -k1,1) ;;
>>> +
>>> + # sort by "active" then by "hits" then by "name"
>>> + hit) sortArg=(-k3,3r -k2,2nr -k1,1) ;;
>>> +
>>> + # sort by "active" then by "lines" then by "name"
>>> + line) sortArg=(-k3,3r -k4,4nr -k1,1) ;;
>>> +
>>> + # sort by "active" then by "effect" then by "name"
>>> + effect) sortArg=(-k3,3r -k5,5nr -k1,1) ;;
>>> +esac
>>> +
>>> +printf -- "--------------\n"
>>> +# remove blank lines, sort, print as columns
>>> +echo "${theResults}" |
>>> + awk '!/^[[:space:]]*$/' |
>>> + sort "${sortArg[@]}" |
>>> + awk --assign=width="${pWidth}" \
>>> + '{ printf "%-*s %-8s %-8s %8s %10s %% %12s\n", width, $1, $2, $3, $4, $5, $6 }'
>>> +
>>> +printf "${pFormat}" "" "=======" "" "========" "" ""
>>> +printf "${pFormat}" "Totals -->" "${totalHits}" "" "${totalLines}" "" ""
>>> +
>>> +exit
>>> diff --git a/config/rpz/rpz-sleep b/config/rpz/rpz-sleep
>>> new file mode 100755
>>> index 000000000..dd3603599
>>> --- /dev/null
>>> +++ b/config/rpz/rpz-sleep
>>> @@ -0,0 +1,58 @@
>>> +#!/bin/bash
>>> +###############################################################################
>>> +# #
>>> +# IPFire.org - A linux based firewall #
>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>> +# #
>>> +# 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 <http://www.gnu.org/licenses/>. #
>>> +# #
>>> +###############################################################################
>>> +
>>> +version="2024-08-16" # v05
>>> +
>>> +############### Functions ###############
>>> +
>>> +# send message to message log
>>> +msg_log () {
>>> + logger --tag "${tagName}" "$*"
>>> + if tty --silent ; then
>>> + echo "${tagName}:" "$*"
>>> + fi
>>> +}
>>> +
>>> +############### Main ###############
>>> +
>>> +tagName="unbound"
>>> +
>>> +sleepTime="${1:-5m}" # default to sleep for 5m (5 minutes)
>>> +
>>> +zoneList=$( unbound-control list_auth_zones | awk '{print $1}' )
>>> +
>>> +for zone in ${zoneList} ; do
>>> + printf "disable ${zone}\t"
>>> + unbound-control rpz_disable "${zone}"
>>> +done
>>> +
>>> +msg_log "info: rpz: disabled all zones for ${sleepTime}"
>>> +
>>> +sleep "${sleepTime}"
>>> +
>>> +for zone in ${zoneList} ; do
>>> + printf "enable ${zone}\t"
>>> + unbound-control rpz_enable "${zone}"
>>> +done
>>> +
>>> +msg_log "info: rpz: enabled all zones"
>>> +
>>> +exit
>>> diff --git a/config/rpz/rpz.de.pl b/config/rpz/rpz.de.pl
>>> new file mode 100644
>>> index 000000000..3770c6bb0
>>> --- /dev/null
>>> +++ b/config/rpz/rpz.de.pl
>>> @@ -0,0 +1,30 @@
>>> +# Added for Response Policy Zone (RPZ) add-on
>>> +%tr = (%tr,
>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>> +'rpz apply' => 'Übernehmen',
>>> +'rpz cl allow enable' => 'Benutzerdefinierte Allowlist aktivieren:',
>>> +'rpz cl allow info' => 'Zugelassene Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>> +'rpz cl allow' => 'Benutzerdefinierte Allowlist',
>>> +'rpz cl block enable' => 'Benutzerdefinierte Blocklist aktivieren:',
>>> +'rpz cl block info' => 'Gesperrte Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>> +'rpz cl block' => 'Benutzerdefinierte Blocklist',
>>> +'rpz cl' => 'Benutzerdefinierte Listen',
>>> +'rpz exitcode 101' => 'Der Name enthält unzulässige Zeichen',
>>> +'rpz exitcode 102' => 'unbound-checkconf hat eine fehlerhafte Konfiguration ermittelt. Führen Sie das Kommando unbound-checkconf auf der Konsole aus, um weitere Informationen zu erhalten.',
>>> +'rpz exitcode 103' => 'Die benutzerdefinierte Allow-/Blocklist ist leer',
>>> +'rpz exitcode 104' => 'Ein Eintrag mit identischem Namen existiert bereits',
>>> +'rpz exitcode 105' => 'Die URL ist ungültig',
>>> +'rpz exitcode 106' => 'Eintrag kann nicht entfernt werden, der Name existiert nicht',
>>> +'rpz exitcode 107' => 'Der Name ist ungültig - nur "allow" oder "block" möglich',
>>> +'rpz exitcode 108' => 'Fehlende oder inkorrekte Parameter',
>>> +'rpz exitcode 109' => 'unbound-control reload ist fehlgeschlagen',
>>> +'rpz exitcode 110' => 'Die benutzerdefinierte Allow-/Blocklist enthält unzulässige Einträge',
>>> +'rpz exitcode 201' => 'Die Anmerkung enthält unzulässige Zeichen',
>>> +'rpz exitcode 202' => 'Ungültiger Eintrag in der benutzerdefinierten Allowlist, Zeile ',
>>> +'rpz exitcode 203' => 'Ungültiger Eintrag in der benutzerdefinierten Blocklist, Zeile ',
>>> +'rpz exitcode 204' => 'Ausgewählter Eintrag existiert nicht: ',
>>> +'rpz zf editor' => 'Zonendatei-Eintrag bearbeiten',
>>> +'rpz zf imported' => '(importiert aus rpz-config)',
>>> +'rpz zf remark info' => 'Erlaubte Zeichen sind a-z, A-Z, 0-9 und Unterstriche',
>>> +'rpz zf' => 'Zonendateien',
>>> +);
>>> diff --git a/config/rpz/rpz.en.pl b/config/rpz/rpz.en.pl
>>> new file mode 100644
>>> index 000000000..0720a8940
>>> --- /dev/null
>>> +++ b/config/rpz/rpz.en.pl
>>> @@ -0,0 +1,30 @@
>>> +# Added for Response Policy Zone (RPZ) add-on
>>> +%tr = (%tr,
>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>> +'rpz apply' => 'Apply',
>>> +'rpz cl allow enable' => 'Enable custom allowlist:',
>>> +'rpz cl allow info' => 'Allowed domains (one per line)<br>Example: domain.com, *.domain.com',
>>> +'rpz cl allow' => 'Custom allowlist',
>>> +'rpz cl block enable' => 'Enable custom blocklist:',
>>> +'rpz cl block info' => 'Blocked domains (one per line)<br>Example: domain.com, *.domain.com',
>>> +'rpz cl block' => 'Custom blocklist',
>>> +'rpz cl' => 'Custom lists',
>>> +'rpz exitcode 101' => 'the NAME is not valid',
>>> +'rpz exitcode 102' => 'unbound-checkconf found invalid configuration. In the Terminal run the command unbound-checkconf for more information',
>>> +'rpz exitcode 103' => 'the allow/block list is empty',
>>> +'rpz exitcode 104' => 'duplicate - NAME already exists',
>>> +'rpz exitcode 105' => 'the URL is not valid',
>>> +'rpz exitcode 106' => 'cannot remove the NAME does not exist',
>>> +'rpz exitcode 107' => 'the NAME is not valid - "allow" or "block" only',
>>> +'rpz exitcode 108' => 'missing or incorrect parameter',
>>> +'rpz exitcode 109' => 'unbound-control reload failed',
>>> +'rpz exitcode 110' => 'custom Allowlist/Blocklist contains invalid entries',
>>> +'rpz exitcode 201' => 'the REMARK is not valid',
>>> +'rpz exitcode 202' => 'invalid entry in allowlist, line ',
>>> +'rpz exitcode 203' => 'invalid entry in blocklist, line ',
>>> +'rpz exitcode 204' => 'Selected entry does not exist: ',
>>> +'rpz zf editor' => 'Edit zonefiles entry',
>>> +'rpz zf imported' => '(imported from rpz-config)',
>>> +'rpz zf remark info' => 'Valid characters are a-z, A-Z, 0-9 and underscore.',
>>> +'rpz zf' => 'Zonefiles',
>>> +);
>>> diff --git a/config/rpz/rpz.es.pl b/config/rpz/rpz.es.pl
>>> new file mode 100644
>>> index 000000000..98628e4aa
>>> --- /dev/null
>>> +++ b/config/rpz/rpz.es.pl
>>> @@ -0,0 +1,30 @@
>>> +# Added for Response Policy Zone (RPZ) add-on
>>> +%tr = (%tr,
>>> +'rpz' => 'Zonas de política de respuesta (RPZ)',
>>> +'rpz apply' => 'Aplicar',
>>> +'rpz cl allow enable' => 'Habilitar la lista blanca personalizada:',
>>> +'rpz cl allow info' => 'Dominio permitido (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>> +'rpz cl allow' => 'Lista blanca personalizada',
>>> +'rpz cl block enable' => 'Habilitar la lista negra personalizada:',
>>> +'rpz cl block info' => 'Dominio bloqueado (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>> +'rpz cl block' => 'Lista negra personalizada',
>>> +'rpz cl' => 'Lista personalizada',
>>> +'rpz exitcode 101' => 'El NOMBRE no es válido',
>>> +'rpz exitcode 102' => 'unbound-checkconf ha encontrado una configuración no válida. Desde Terminal, ejecute el comando unbound-checkconf para mayor información',
>>> +'rpz exitcode 103' => 'La lista de permitidos/bloqueados está vacía',
>>> +'rpz exitcode 104' => 'duplicado - NOMBRE ya existe',
>>> +'rpz exitcode 105' => 'la URL no es válida',
>>> +'rpz exitcode 106' => 'no es posible eliminar el NOMBRE que no existe',
>>> +'rpz exitcode 107' => 'el NOMBRE no es válido - sólo "permitir" o "bloquear"',
>>> +'rpz exitcode 108' => 'parámetro faltante o incorrecto',
>>> +'rpz exitcode 109' => 'Error al recargar unbound-control',
>>> +'rpz exitcode 110' => 'la Lista blanca/Lista negra personalizada contiene entradas no válidas',
>>> +'rpz exitcode 201' => 'el COMENTARIO no es válido',
>>> +'rpz exitcode 202' => 'entrada no válida en la lista blanca, línea ',
>>> +'rpz exitcode 203' => 'entrada no válida en la lista negra, línea ',
>>> +'rpz exitcode 204' => 'La entrada seleccionada no existe: ',
>>> +'rpz zf editor' => 'Editar la entrada de archivos de zona',
>>> +'rpz zf imported' => '(importado de rpz-config)',
>>> +'rpz zf remark info' => 'Los caracteres válidos son a-z, A-Z, 0-9 y guión bajo',
>>> +'rpz zf' => 'Archivos de zona',
>>> +);
>>> diff --git a/config/rpz/rpz.fr.pl b/config/rpz/rpz.fr.pl
>>> new file mode 100644
>>> index 000000000..f35f3c2d0
>>> --- /dev/null
>>> +++ b/config/rpz/rpz.fr.pl
>>> @@ -0,0 +1,30 @@
>>> +# Added for Response Policy Zone (RPZ) add-on
>>> +%tr = (%tr,
>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>> +'rpz apply' => 'Appliquer',
>>> +'rpz cl allow enable' => 'Activer la liste d\'autorisations personnalisée:',
>>> +'rpz cl allow info' => 'Domaines autorisés (un par ligne)<br>Example: domain.com, *.domain.com',
>>> +'rpz cl allow' => 'Liste d\'autorisations personnalisée',
>>> +'rpz cl block enable' => 'Activer la liste de blocage personnalisée:',
>>> +'rpz cl block info' => 'Domaines bloqués (un par ligne)<br>Example: domain.com, *.domain.com',
>>> +'rpz cl block' => 'liste de blocage personnalisé',
>>> +'rpz cl' => 'Listes personnalisées',
>>> +'rpz exitcode 101' => 'le NOM n\'est pas valide',
>>> +'rpz exitcode 102' => 'unbound-checkconf configuration non valide trouvée. Dans le terminal, exécutez la commande unbound-checkconf pour plus d\'informations',
>>> +'rpz exitcode 103' => 'la liste autoriser/bloquer est vide',
>>> +'rpz exitcode 104' => 'le NOM existe déjà',
>>> +'rpz exitcode 105' => 'L\'URL n\'est pas valide',
>>> +'rpz exitcode 106' => 'impossible de supprimer le NOM n\'existe pas',
>>> +'rpz exitcode 107' => 'le NOM n\'est pas valide - « autoriser » ou « bloquer » seulement',
>>> +'rpz exitcode 108' => 'paramètre manquant ou incorrect',
>>> +'rpz exitcode 109' => 'unbound-control rechargement échoué',
>>> +'rpz exitcode 110' => 'la liste autoriser/bloquer contient des entrées non valides',
>>> +'rpz exitcode 201' => 'la REMARQUE n\'est pas valable',
>>> +'rpz exitcode 202' => 'entrée non valide dans la liste d\'autorisation, ligne ',
>>> +'rpz exitcode 203' => 'entrée non valide dans la liste de blocs, ligne ',
>>> +'rpz exitcode 204' => 'L\'entrée sélectionnée n\'existe pas: ',
>>> +'rpz zf editor' => 'Modifier l\'entrée Fichiers Zone',
>>> +'rpz zf imported' => '(importé de rpz-config)',
>>> +'rpz zf remark info' => 'Les caractères valides sont a-z, A-Z, 0-9 et soulignement.',
>>> +'rpz zf' => 'Fichiers Zone',
>>> +);
>>> diff --git a/config/rpz/rpz.it.pl b/config/rpz/rpz.it.pl
>>> new file mode 100644
>>> index 000000000..ee81605c9
>>> --- /dev/null
>>> +++ b/config/rpz/rpz.it.pl
>>> @@ -0,0 +1,30 @@
>>> +# Added for Response Policy Zone (RPZ) add-on
>>> +%tr = (%tr,
>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>> +'rpz apply' => 'Applica',
>>> +'rpz cl allow enable' => 'Abilita la Whitelist personalizzata:',
>>> +'rpz cl allow info' => 'Domini consentiti (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>> +'rpz cl allow' => 'Whitelist personalizzata',
>>> +'rpz cl block enable' => 'Abilita la Blacklist personalizzata:',
>>> +'rpz cl block info' => 'Domini bloccati (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>> +'rpz cl block' => 'Blacklist personalizzata',
>>> +'rpz cl' => 'Liste personalizzate',
>>> +'rpz exitcode 101' => 'il NOME non è valido',
>>> +'rpz exitcode 102' => 'unbound-checkconf ha trovato una configurazione non valida. Dal Terminale esegui il comando unbound-checkconf per maggiori informazioni',
>>> +'rpz exitcode 103' => 'l\'elenco consentiti/bloccati è vuoto',
>>> +'rpz exitcode 104' => 'duplicato - NAME esiste di già',
>>> +'rpz exitcode 105' => 'l\'URL non è valido',
>>> +'rpz exitcode 106' => 'non è possibile rimuovere il NOME non esiste',
>>> +'rpz exitcode 107' => 'il NOME non è valido - solo "consenti" o "blocca"',
>>> +'rpz exitcode 108' => 'parametro mancante o non corretto',
>>> +'rpz exitcode 109' => 'ricaricamento del controllo non associato non riuscito',
>>> +'rpz exitcode 110' => 'la Whitelist/Blacklist personalizzata contiene voci non valide',
>>> +'rpz exitcode 201' => 'l"OSSERVAZIONE non è valida',
>>> +'rpz exitcode 202' => 'voce non valida nella Whitelist, riga ',
>>> +'rpz exitcode 203' => 'voce non valida nella Blacklist, riga ',
>>> +'rpz exitcode 204' => 'La voce selezionata non esiste: ',
>>> +'rpz zf editor' => 'Modifica la voce dei file di zona',
>>> +'rpz zf imported' => '(importato da rpz-config)',
>>> +'rpz zf remark info' => 'I caratteri validi sono a-z, A-Z, 0-9 e trattino basso',
>>> +'rpz zf' => 'Zonefiles',
>>> +);
>>> diff --git a/config/rpz/rpz.tr.pl b/config/rpz/rpz.tr.pl
>>> new file mode 100644
>>> index 000000000..00226e192
>>> --- /dev/null
>>> +++ b/config/rpz/rpz.tr.pl
>>> @@ -0,0 +1,30 @@
>>> +# I�in eklendi Ayriyeten Response Policy Zone (RPZ)
>>> +%tr = (%tr,
>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>> +'rpz apply' => 'Uygulamak',
>>> +'rpz cl allow enable' => '�zel Etkinlestir allowlist:',
>>> +'rpz cl allow info' => 'Izin verilmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>> +'rpz cl allow' => 'Etkinlestir allowlist',
>>> +'rpz cl block enable' => '�zel Etkinlestir blocklist:',
>>> +'rpz cl block info' => 'Engellenmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>> +'rpz cl block' => 'Engellenmis blocklist',
>>> +'rpz cl' => 'Engellenmis lists',
>>> +'rpz exitcode 101' => 'NAME ge�erli degil',
>>> +'rpz exitcode 102' => 'unbound-checkconf ge�ersiz yapilandirma bulundu. Terminalde daha fazla bilgi i�in Unbound-checkConf komutunu �alistirin',
>>> +'rpz exitcode 103' => 'allow/block liste bos',
>>> +'rpz exitcode 104' => 'kopyalamak - NAME zaten var',
>>> +'rpz exitcode 105' => 'URL ge�erli degil',
>>> +'rpz exitcode 106' => '�ikarilamiyor NAME yok',
>>> +'rpz exitcode 107' => 'NAME ge�erli degil - "allow" veya "block" yalniz',
>>> +'rpz exitcode 108' => 'Parametre eksik veya yanlis',
>>> +'rpz exitcode 109' => 'unbound-control basarisiz',
>>> +'rpz exitcode 110' => 'Engellenmis Allowlist/Blocklist ge�ersiz girisler i�erir',
>>> +'rpz exitcode 201' => 'REMARK ge�erli degil',
>>> +'rpz exitcode 202' => 'Ge�ersiz giris allowlist, line ',
>>> +'rpz exitcode 203' => 'Ge�ersiz giris blocklist, line ',
>>> +'rpz exitcode 204' => 'Se�ilen giris yok: ',
>>> +'rpz zf editor' => 'yazimlamak zonefiles giris',
>>> +'rpz zf imported' => '(ithal edildi rpz-config)',
>>> +'rpz zf remark info' => 'Ge�erli karakterler a-z, A-Z, 0-9ve alt�st.',
>>> +'rpz zf' => 'Zonefiles',
>>> +);
>>> diff --git a/html/cgi-bin/rpz.cgi b/html/cgi-bin/rpz.cgi
>>> new file mode 100644
>>> index 000000000..a821c92ac
>>> --- /dev/null
>>> +++ b/html/cgi-bin/rpz.cgi
>>> @@ -0,0 +1,923 @@
>>> +#!/usr/bin/perl
>>> +###############################################################################
>>> +# #
>>> +# IPFire.org - A linux based firewall #
>>> +# Copyright (C) 2005-2024 IPFire Team <info(a)ipfire.org> #
>>> +# #
>>> +# 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 <http://www.gnu.org/licenses/>. #
>>> +# #
>>> +###############################################################################
>>> +
>>> +use strict;
>>> +use Scalar::Util qw(looks_like_number);
>>> +
>>> +# debugging
>>> +#use warnings;
>>> +#use CGI::Carp 'fatalsToBrowser';
>>> +#use Data::Dumper;
>>> +
>>> +require '/var/ipfire/general-functions.pl';
>>> +require "${General::swroot}/lang.pl";
>>> +require "${General::swroot}/header.pl";
>>> +
>>> +###--- Extra HTML ---###
>>> +my $extraHead = <<END
>>> +<style>
>>> + /* alternating row background */
>>> + .tbl tr:nth-child(2n+2) {
>>> + background-color: var(--color-light-grey);
>>> + }
>>> + .tbl tr:nth-child(2n+3) {
>>> + background-color: var(--color-grey);
>>> + }
>>> + /* text styles */
>>> + .tbl th:not(:last-child) {
>>> + text-align: left;
>>> + }
>>> + div.right {
>>> + text-align: right;
>>> + margin-top: 0.5em;
>>> + }
>>> + /* customlist input */
>>> + textarea.domainlist {
>>> + margin: 0.5em 0;
>>> + resize: vertical;
>>> + min-height: 10em;
>>> + overflow: auto;
>>> + white-space: pre;
>>> + }
>>> + button[type=submit]:disabled {
>>> + opacity: 0.6;
>>> + }
>>> +</style>
>>> +END
>>> +;
>>> +###--- End of extra HTML ---###
>>> +
>>> +
>>> +### Settings ###
>>> +
>>> +# Request DNS service reload after configuration change
>>> +my $RPZ_RELOAD_FLAG = "${General::swroot}/dns/rpz/reload.flag";
>>> +
>>> +# Configuration file for all available zonefiles
>>> +# Format: index, name (unique), enabled (on/off), URL, remark
>>> +my $ZONEFILES_CONF = "${General::swroot}/dns/rpz/zonefiles.conf";
>>> +
>>> +# Configuration file for custom lists
>>> +# IDs: 0=allowlist, 1=blocklist, 2=options (allow/block enabled)
>>> +my $CUSTOMLISTS_CONF = "${General::swroot}/dns/rpz/customlists.conf";
>>> +
>>> +# Export custom lists to rpz-config
>>> +my $RPZ_ALLOWLIST = "${General::swroot}/dns/rpz/allowlist";
>>> +my $RPZ_BLOCKLIST = "${General::swroot}/dns/rpz/blocklist";
>>> +
>>> +
>>> +### Preparation ###
>>> +
>>> +# Create missing config files
>>> +unless(-f $ZONEFILES_CONF) { &General::system('touch', "$ZONEFILES_CONF"); }
>>> +unless(-f $CUSTOMLISTS_CONF) { &General::system('touch', "$CUSTOMLISTS_CONF"); }
>>> +
>>> +
>>> +## Global gui data
>>> +my $errormessage = "";
>>> +
>>> +## Global configuration data
>>> +my %zonefiles = ();
>>> +my %customlists = ();
>>> +&_zonefiles_load();
>>> +&_customlists_load();
>>> +
>>> +## Global CGI form data
>>> +my %cgiparams = ();
>>> +&Header::getcgihash(\%cgiparams);
>>> +
>>> +my $action = $cgiparams{'ACTION'} // 'NONE';
>>> +my $action_key = $cgiparams{'KEY'} // ''; # entry being edited, empty = none/new
>>> +
>>> +
>>> +###--- Process form actions ---###
>>> +
>>> +# Zonefiles action: Check whether the requested entry exists
>>> +if((substr($action, 0, 3) eq 'ZF_') && ($action_key)) {
>>> + unless(defined $zonefiles{$action_key}) {
>>> + $errormessage = &_rpz_error_tr(204, $action_key);
>>> + $action = 'NONE';
>>> + }
>>> +}
>>> +
>>> +## Perform actions
>>> +if($action eq 'ZF_SAVE') { ## Save new or modified zonefiles entry
>>> + if(&_action_zf_save()) {
>>> + $action = 'NONE'; # success, return to main page
>>> + &_http_prg_redirect();
>>> + } else {
>>> + $action = 'ZF_EDIT'; # error occured, keep editing
>>> + }
>>> +
>>> +} elsif($action eq 'ZF_TOGGLE') { ## Toggle on/off
>>> + if(&_action_zf_toggle()) {
>>> + $action = 'NONE';
>>> + &_http_prg_redirect();
>>> + }
>>> +
>>> +} elsif($action eq 'ZF_REMOVE') { ## Remove entry
>>> + if(&_action_zf_remove()) {
>>> + $action = 'NONE';
>>> + &_http_prg_redirect();
>>> + }
>>> +
>>> +} elsif($action eq 'CL_SAVE') { ## Save custom lists
>>> + if(&_action_cl_save()) {
>>> + $action = 'NONE';
>>> + &_http_prg_redirect();
>>> + }
>>> +
>>> +} elsif($action eq 'RPZ_RELOAD') { ## Reload dns configuration
>>> + if(&_action_rpz_reload()) {
>>> + $action = 'NONE';
>>> + &_http_prg_redirect();
>>> + }
>>> +
>>> +} elsif($action eq 'UNB_RESTART') { ## Restart unbound service
>>> + if(&_action_unb_restart()) {
>>> + $action = 'NONE';
>>> + &_http_prg_redirect();
>>> + }
>>> +
>>> +}
>>> +
>>> +
>>> +###--- Start GUI ---###
>>> +
>>> +## Start http output
>>> +&Header::showhttpheaders();
>>> +
>>> +# Start HTML
>>> +&Header::openpage($Lang::tr{'rpz'}, 1, $extraHead);
>>> +&Header::openbigbox('100%', 'left', '');
>>> +
>>> +# Show error messages
>>> +if($errormessage) {
>>> + &_print_message($errormessage);
>>> +}
>>> +
>>> +# Handle zonefile add/edit mode
>>> +if($action eq "ZF_EDIT") {
>>> + &_print_zonefile_editor();
>>> +
>>> + # Finalize page and exit cleanly
>>> + &Header::closebigbox();
>>> + &Header::closepage();
>>> + exit(0);
>>> +}
>>> +
>>> +# Show gui elements
>>> +&_print_zonefiles();
>>> +&_print_customlists();
>>> +&_print_gui_extras();
>>> +
>>> +&Header::closebigbox();
>>> +&Header::closepage();
>>> +
>>> +###--- End of GUI ---###
>>> +
>>> +
>>> +###--- Internal configuration file functions ---###
>>> +
>>> +# Load all available zonefiles from rpz-config and the internal configuration
>>> +sub _zonefiles_load {
>>> + # Clean start
>>> + %zonefiles = ();
>>> +
>>> + # Source 1: Get the currently enabled zonefiles from rpz-config (expected format [name]=[URL])
>>> + my @enabled_files = &General::system_output('/usr/sbin/rpz-config', 'list');
>>> +
>>> + foreach my $row (@enabled_files) {
>>> + chomp($row);
>>> +
>>> + # Use regex instead of split() to skip non-matching lines
>>> + next unless($row =~ /^(\w+)=(.+)$/);
>>> + my ($name, $url) = ($1, $2);
>>> +
>>> + # Unique names are already guaranteed by rpz-config
>>> + if(&_rpz_validate_zonefile($name, $url, '', 0) == 0) {
>>> + # Populate global data hash, mark all found entries as enabled
>>> + my %entry = ('enabled' => 'on',
>>> + 'url' => $url,
>>> + 'remark' => $Lang::tr{'rpz zf imported'});
>>> +
>>> + $zonefiles{$name} = \%entry;
>>> + }
>>> + }
>>> +
>>> + # Source 2: Get additional data and disabled entries from configuration file
>>> + my %configured_files = ();
>>> + &General::readhasharray($ZONEFILES_CONF, \%configured_files);
>>> +
>>> + foreach my $row (values (%configured_files)) {
>>> + my ($name, $enabled, $url, $remark) = @$row;
>>> + $remark //= "";
>>> +
>>> + next unless($name);
>>> +
>>> + # Check whether this row belongs to an entry already imported from rpz-config
>>> + if(defined $zonefiles{$name}) {
>>> + # Existing entry, only merge additional data
>>> + $zonefiles{$name}{'remark'} = $remark;
>>> + } else {
>>> + # Skip entry if it is marked as enabled but not found by rpz-config. It was then deleted manually
>>> + if($enabled ne 'on') {
>>> + # Populate global data hash
>>> + my %entry = ('enabled' => 'off',
>>> + 'url' => $url // "",
>>> + 'remark' => $remark);
>>> +
>>> + $zonefiles{$name} = \%entry;
>>> + }
>>> + }
>>> + }
>>> +}
>>> +
>>> +# Save internal zonefiles configuration
>>> +sub _zonefiles_save_conf {
>>> + my $index = 0;
>>> + my %export = ();
>>> +
>>> + # Loop trough all zonefiles and create "hasharray" type export
>>> + foreach my $name (keys %zonefiles) {
>>> + my @entry = ($name,
>>> + $zonefiles{$name}{'enabled'},
>>> + $zonefiles{$name}{'url'},
>>> + $zonefiles{$name}{'remark'});
>>> +
>>> + $export{$index++} = \@entry;
>>> + }
>>> +
>>> + &General::writehasharray($ZONEFILES_CONF, \%export);
>>> +}
>>> +
>>> +# Load custom lists from rpz-config and the internal configuration
>>> +sub _customlists_load {
>>> + # Clean start
>>> + %customlists = ();
>>> +
>>> + # Load configuration file
>>> + my %lists_conf = ();
>>> + &General::readhasharray($CUSTOMLISTS_CONF, \%lists_conf);
>>> +
>>> + # Get list options, enabled by default to start import
>>> + $customlists{'allow'}{'enabled'} = $lists_conf{2}[0] // 'on';
>>> + $customlists{'block'}{'enabled'} = $lists_conf{2}[1] // 'on';
>>> +
>>> + # Import enabled list from rpz-config, otherwise retrieve stored or empty list from configuration file
>>> + if($customlists{'allow'}{'enabled'} eq 'on') {
>>> + &_customlist_import('allow', $RPZ_ALLOWLIST);
>>> + } else {
>>> + $customlists{'allow'}{'list'} = $lists_conf{0} // [];
>>> + }
>>> + if($customlists{'block'}{'enabled'} eq 'on') {
>>> + &_customlist_import('block', $RPZ_BLOCKLIST);
>>> + } else {
>>> + $customlists{'block'}{'list'} = $lists_conf{1} // [];
>>> + }
>>> +}
>>> +
>>> +# Save internal custom lists configuration
>>> +sub _customlists_save_conf {
>>> + my %export = ();
>>> +
>>> + # Match IDs with import function
>>> + $export{0} = $customlists{'allow'}{'list'};
>>> + $export{1} = $customlists{'block'}{'list'};
>>> + $export{2} = [$customlists{'allow'}{'enabled'}, $customlists{'block'}{'enabled'}];
>>> +
>>> + &General::writehasharray($CUSTOMLISTS_CONF, \%export);
>>> +}
>>> +
>>> +# Import a custom list from plain file, returns empty list if file is missing
>>> +sub _customlist_import {
>>> + my ($listname, $filename) = @_;
>>> + my @list = ();
>>> +
>>> + # File exists, load and check all lines
>>> + if(-f $filename) {
>>> + open(my $FH, '<', $filename) or die "Can't read $filename: $!";
>>> + while(my $line = <$FH>) {
>>> + chomp($line);
>>> + push(@list, $line);
>>> + }
>>> + close($FH);
>>> +
>>> + # Clean up imported data
>>> + &_rpz_validate_customlist(\@list, 1);
>>> + }
>>> +
>>> + $customlists{$listname}{'list'} = \@list;
>>> +}
>>> +
>>> +# Export a custom list to plain file or clear file if list is disabled
>>> +sub _customlist_export {
>>> + my ($listname, $filename) = @_;
>>> + return unless(defined $customlists{$listname});
>>> +
>>> + # Write enabled domain list to file, otherwise save empty file
>>> + open(my $FH, '>', $filename) or die "Can't write $filename: $!";
>>> +
>>> + if($customlists{$listname}{'enabled'} eq 'on') {
>>> + foreach my $line (@{$customlists{$listname}{'list'}}) {
>>> + print $FH "$line\n";
>>> + }
>>> + } else {
>>> + print $FH "; Note: This list is currently disabled by $ENV{'SCRIPT_NAME'}\n";
>>> + }
>>> +
>>> + close($FH);
>>> +}
>>> +
>>> +
>>> +###--- Internal gui functions ---###
>>> +
>>> +# Show simple message box
>>> +sub _print_message {
>>> + my ($message, $title) = @_;
>>> + $title ||= $Lang::tr{'error messages'};
>>> +
>>> + &Header::openbox('100%', 'left', $title);
>>> + print "<span>$message</span>";
>>> + &Header::closebox();
>>> +}
>>> +
>>> +# Show all zone files and related gui elements
>>> +sub _print_zonefiles {
>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf'});
>>> +
>>> + print <<END
>>> +<table class="tbl" width="100%">
>>> + <tr>
>>> + <th>$Lang::tr{'name'}</th>
>>> + <th>URL</th>
>>> + <th>$Lang::tr{'remark'}</th>
>>> + <th colspan="3">$Lang::tr{'action'}</th>
>>> + </tr>
>>> +END
>>> +;
>>> +
>>> + # Sort zonefiles by name and loop trough all entries
>>> + foreach my $name (sort keys %zonefiles) {
>>> +
>>> + # Toggle button label translation
>>> + my $toggle_tr = ($zonefiles{$name}{'enabled'} eq 'on') ? $Lang::tr{'click to disable'} : $Lang::tr{'click to enable'};
>>> +
>>> + print <<END
>>> + <tr>
>>> + <td>$name</td>
>>> + <td>$zonefiles{$name}{'url'}</td>
>>> + <td>$zonefiles{$name}{'remark'}</td>
>>> +
>>> + <td align="center" width="5%">
>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>> + <input type="hidden" name="KEY" value="$name">
>>> + <input type="hidden" name="ACTION" value="ZF_TOGGLE">
>>> + <input type="image" src="/images/$zonefiles{$name}{'enabled'}.gif" title="$toggle_tr" alt="$toggle_tr">
>>> + </form>
>>> + </td>
>>> + <td align="center" width="5%">
>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>> + <input type="hidden" name="KEY" value="$name">
>>> + <input type="hidden" name="ACTION" value="ZF_EDIT">
>>> + <input type="image" src="/images/edit.gif" title="$Lang::tr{'edit'}" alt="$Lang::tr{'edit'}">
>>> + </form>
>>> + </td>
>>> + <td align="center" width="5%">
>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>> + <input type="hidden" name="KEY" value="$name">
>>> + <input type="hidden" name="ACTION" value="ZF_REMOVE">
>>> + <input type="image" src="/images/delete.gif" title="$Lang::tr{'remove'}" alt="$Lang::tr{'remove'}">
>>> + </form>
>>> + </td>
>>> + </tr>
>>> +END
>>> +;
>>> + }
>>> +
>>> + # Disable reload button if not needed
>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>> +
>>> + print <<END
>>> +</table>
>>> +
>>> +<div class="right">
>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>> + <input type="hidden" name="KEY" value="">
>>> + <button type="submit" name="ACTION" value="ZF_EDIT">$Lang::tr{'add'}</button>
>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>> + </form>
>>> +</div>
>>> +END
>>> +;
>>> +
>>> + &Header::closebox();
>>> +}
>>> +
>>> +# Show zonefiles entry editor
>>> +sub _print_zonefile_editor {
>>> +
>>> + # Key specified: Edit existing entry
>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>> + # Load data to be edited, but don't override already present values (allows user to edit after error)
>>> + $cgiparams{'ZF_NAME'} //= $action_key;
>>> + $cgiparams{'ZF_URL'} //= $zonefiles{$action_key}{'url'};
>>> + $cgiparams{'ZF_REMARK'} //= $zonefiles{$action_key}{'remark'};
>>> + }
>>> +
>>> + # Fallback to empty form
>>> + $cgiparams{'ZF_NAME'} //= "";
>>> + $cgiparams{'ZF_URL'} //= "";
>>> + $cgiparams{'ZF_REMARK'} //= "";
>>> +
>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf editor'});
>>> +
>>> + print <<END
>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>> +<input type="hidden" name="KEY" value="$action_key">
>>> +<table width="100%">
>>> + <tr>
>>> + <td width="20%">$Lang::tr{'name'}: <img src="/blob.gif" alt="*"></td>
>>> + <td><input type="text" name="ZF_NAME" value="$cgiparams{'ZF_NAME'}" size="40" maxlength="32" title="$Lang::tr{'rpz zf remark info'}" pattern="[a-zA-Z0-9_]{1,32}" required></td>
>>> + </tr>
>>> + <tr>
>>> + <td width="20%">URL: <img src="/blob.gif" alt="*"></td>
>>> + <td><input type="url" name="ZF_URL" value="$cgiparams{'ZF_URL'}" size="40" maxlength="128" required></td>
>>> + </tr>
>>> + <tr>
>>> + <td width="20%">$Lang::tr{'remark'}:</td>
>>> + <td><input type="text" name="ZF_REMARK" value="$cgiparams{'ZF_REMARK'}" size="40" maxlength="32"></td>
>>> + </tr>
>>> + <tr>
>>> + <td colspan="2"><hr></td>
>>> + </tr>
>>> + <tr>
>>> + <td width="55%"><img src="/blob.gif" alt="*"> $Lang::tr{'required field'}</td>
>>> + <td align="right"><button type="submit" name="ACTION" value="ZF_SAVE">$Lang::tr{'save'}</button></td>
>>> + </tr>
>>> +</table>
>>> +</form>
>>> +
>>> +<div class="right">
>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>> + <button type="submit" name="ACTION" value="NONE">$Lang::tr{'back'}</button>
>>> + </form>
>>> +</div>
>>> +END
>>> +;
>>> +
>>> + &Header::closebox();
>>> +}
>>> +
>>> +# Show custom allow/block files and related gui elements
>>> +sub _print_customlists {
>>> +
>>> + # Load lists from config, unless they are currently being edited
>>> + if($action ne 'CL_SAVE') {
>>> + $cgiparams{'ALLOW_LIST'} = join("\n", @{$customlists{'allow'}{'list'}});
>>> + $cgiparams{'BLOCK_LIST'} = join("\n", @{$customlists{'block'}{'list'}});
>>> +
>>> + $cgiparams{'ALLOW_ENABLED'} = ($customlists{'allow'}{'enabled'} eq 'on') ? 'on' : undef;
>>> + $cgiparams{'BLOCK_ENABLED'} = ($customlists{'block'}{'enabled'} eq 'on') ? 'on' : undef;
>>> + }
>>> +
>>> + # Fallback to empty form
>>> + $cgiparams{'ALLOW_LIST'} //= "";
>>> + $cgiparams{'BLOCK_LIST'} //= "";
>>> +
>>> + # HTML checkboxes, unchecked = no or undef value in POST data
>>> + my %checked = ();
>>> + $checked{'ALLOW_ENABLED'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? " checked" : "";
>>> + $checked{'BLOCK_ENABLED'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? " checked" : "";
>>> +
>>> + # Disable reload button if not needed
>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>> +
>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz cl'});
>>> +
>>> + print <<END
>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>> +<table width="100%">
>>> + <tr>
>>> + <td colspan="2"><b>$Lang::tr{'rpz cl allow'}</b><br>$Lang::tr{'rpz cl allow info'}</td>
>>> + <td colspan="2"><b>$Lang::tr{'rpz cl block'}</b><br>$Lang::tr{'rpz cl block info'}</td>
>>> + </tr>
>>> + <tr>
>>> + <td colspan="2"><textarea name="ALLOW_LIST" class="domainlist" cols="45">
>>> +$cgiparams{'ALLOW_LIST'}</textarea></td>
>>> + <td colspan="2"><textarea name="BLOCK_LIST" class="domainlist" cols="45">
>>> +$cgiparams{'BLOCK_LIST'}</textarea></td>
>>> + </tr>
>>> + <tr>
>>> + <td><label for="allow_enabled">$Lang::tr{'rpz cl allow enable'}</label></td>
>>> + <td width="15%"><input type="checkbox" name="ALLOW_ENABLED" id="allow_enabled"$checked{'ALLOW_ENABLED'}></td>
>>> + <td><label for="block_enabled">$Lang::tr{'rpz cl block enable'}</label></td>
>>> + <td width="15%"><input type="checkbox" name="BLOCK_ENABLED" id="block_enabled"$checked{'BLOCK_ENABLED'}></td>
>>> + </tr>
>>> + <tr>
>>> + <td colspan="4"><hr></td>
>>> + </tr>
>>> + <tr>
>>> + <td align="right" colspan="4">
>>> + <button type="submit" name="ACTION" value="CL_SAVE">$Lang::tr{'save'}</button>
>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>> + </td>
>>> +</table>
>>> +</form>
>>> +END
>>> +;
>>> +
>>> + &Header::closebox();
>>> +}
>>> +
>>> +# Output javascript and extra gui elements
>>> +sub _print_gui_extras {
>>> +
>>> + # Apply/Restart button modifier key handler
>>> + if(&_rpz_needs_reload()) {
>>> + print <<END
>>> +<script>
>>> + // Commit modifier key handler
>>> + (function(jq, document) {
>>> + var keyEventsOn = false; // Keyboard events attached
>>> + var keyModify = false; // Modifier key pressed
>>> + var mouseHover = false; // Mouse over commit button
>>> + var btnModified = false; // Button modified to "Restart"
>>> +
>>> + // Document-level key events, enable only while cursor is over button
>>> + function attachKeyEvents() {
>>> + if(keyEventsOn) {
>>> + return;
>>> + }
>>> + keyEventsOn = true;
>>> +
>>> + jq(document).on("keydown.rpz", function(event) {
>>> + if((!keyModify) && event.shiftKey) {
>>> + keyModify = true;
>>> + handleModify();
>>> + }
>>> + });
>>> + jq(document).on("keyup.rpz", function(event) {
>>> + if(keyModify && (!event.shiftKey)) {
>>> + keyModify = false;
>>> + handleModify();
>>> + }
>>> + });
>>> + }
>>> + function removeKeyEvents() {
>>> + keyModify = false;
>>> + if(keyEventsOn) {
>>> + jq(document).off("keydown.rpz keyup.rpz");
>>> + keyEventsOn = false;
>>> + }
>>> + }
>>> +
>>> + // Attach mouse hover events to commit buttons
>>> + function attachMouseEvents() {
>>> + jq("button.commit").on("mouseenter", function(event) {
>>> + if(!mouseHover) {
>>> + mouseHover = true;
>>> + attachKeyEvents();
>>> + // Handle already pressed key
>>> + keyModify = !!(event.shiftKey);
>>> + handleModify();
>>> + }
>>> + });
>>> +
>>> + // Cursor moved away: Disable key listener to minimize events
>>> + jq("button.commit").on("mouseleave", function() {
>>> + if(mouseHover) {
>>> + mouseHover = false;
>>> + removeKeyEvents();
>>> + handleModify();
>>> + }
>>> + });
>>> + }
>>> +
>>> + // Modify commit button
>>> + function handleModify() {
>>> + let modify = mouseHover && keyModify;
>>> + if(btnModified != modify) {
>>> + if(modify) {
>>> + jq("button.commit").text("$Lang::tr{'restart'}").val("UNB_RESTART");
>>> + } else {
>>> + jq("button.commit").text("$Lang::tr{'rpz apply'}").val("RPZ_RELOAD");
>>> + }
>>> + btnModified = modify;
>>> + }
>>> + }
>>> +
>>> + // jQuery DOM ready
>>> + jq(function() {
>>> + attachMouseEvents();
>>> + });
>>> + })(jQuery, document);
>>> +</script>
>>> +END
>>> +;
>>> + } # End of modifier key handler
>>> +
>>> +}
>>> +
>>> +
>>> +###--- Internal action processing functions ---###
>>> +
>>> +# Toggle zonefile on/off
>>> +sub _action_zf_toggle {
>>> + return unless(defined $zonefiles{$action_key});
>>> +
>>> + my $result = 0;
>>> + my $enabled = $zonefiles{$action_key}{'enabled'};
>>> +
>>> + # Perform toggle action
>>> + if($enabled eq 'on') {
>>> + $enabled = 'off';
>>> + $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>> + } else {
>>> + $enabled = 'on';
>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $action_key, $zonefiles{$action_key}{'url'}, '--no-reload');
>>> + }
>>> +
>>> + # Check for errors, request service reload on success
>>> + return unless &_rpz_check_result($result, 1);
>>> +
>>> + # Save changes
>>> + $zonefiles{$action_key}{'enabled'} = $enabled;
>>> + &_zonefiles_save_conf();
>>> +
>>> + return 1;
>>> +}
>>> +
>>> +# Remove zonefile
>>> +sub _action_zf_remove {
>>> + return unless(defined $zonefiles{$action_key});
>>> +
>>> + # Remove from rpz-config if currently active
>>> + if($zonefiles{$action_key}{'enabled'} eq 'on') {
>>> + my $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>> +
>>> + # Check for errors, request service reload on success
>>> + return unless &_rpz_check_result($result, 1);
>>> + }
>>> +
>>> + # Remove from data hash and save changes
>>> + delete $zonefiles{$action_key};
>>> + &_zonefiles_save_conf();
>>> +
>>> + # Clear action_key, as the entry is now removed entirely
>>> + $action_key = "";
>>> +
>>> + return 1;
>>> +}
>>> +
>>> +# Create or update zonefile entry
>>> +# Returns undef if gui needs to stay in editor mode
>>> +sub _action_zf_save {
>>> + my $result = 0;
>>> +
>>> + my $name = $cgiparams{'ZF_NAME'} // "";
>>> + my $url = $cgiparams{'ZF_URL'} // "";
>>> + my $remark = $cgiparams{'ZF_REMARK'} // "";
>>> + my $enabled = 'on'; # Enable new entries by default
>>> +
>>> + # Note on variables:
>>> + # name = unique key, will be used to address the entry
>>> + # action_key = name of the entry being edited, empty for new entry
>>> +
>>> + # Only check for unique name if it changed
>>> + # (this also checks new entries because the action_key is empty in this case)
>>> + $result = &_rpz_validate_zonefile($name, $url, $remark, (lc($name) ne lc($action_key)));
>>> + return unless &_rpz_check_result($result, 0);
>>> +
>>> + # Edit existing entry: Determine what was changed
>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>> + # Name und URL remain unchanged, only save remark and finish
>>> + if(($name eq $action_key) && ($url eq $zonefiles{$action_key}{'url'})) {
>>> + $zonefiles{$action_key}{'remark'} = $remark;
>>> + &_zonefiles_save_conf();
>>> +
>>> + return 1;
>>> + }
>>> +
>>> + # Entry was changed and needs to be recreated, preserve status
>>> + $enabled = $zonefiles{$action_key}{'enabled'};
>>> +
>>> + # Remove from rpz-config
>>> + return unless &_action_zf_remove();
>>> + }
>>> +
>>> + # Add new entry to rpz-config
>>> + if($enabled eq 'on') {
>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $name, $url, '--no-reload');
>>> +
>>> + # Check for errors, request service reload on success
>>> + return unless &_rpz_check_result($result, 1);
>>> + }
>>> +
>>> + # Add to global data hash and save changes
>>> + my %entry = ('enabled' => $enabled,
>>> + 'url' => $url,
>>> + 'remark' => $remark);
>>> +
>>> + $zonefiles{$name} = \%entry;
>>> + &_zonefiles_save_conf();
>>> +
>>> + return 1;
>>> +}
>>> +
>>> +# Save custom lists
>>> +sub _action_cl_save {
>>> + return unless((defined $cgiparams{'ALLOW_LIST'}) && (defined $cgiparams{'BLOCK_LIST'}));
>>> +
>>> + my $result = 0;
>>> +
>>> + my @allowlist = split(/\R/, $cgiparams{'ALLOW_LIST'});
>>> + my @blocklist = split(/\R/, $cgiparams{'BLOCK_LIST'});
>>> +
>>> + # Validate lists
>>> + $result = &_rpz_validate_customlist(\@allowlist);
>>> + if($result != 0) {
>>> + $errormessage = &_rpz_error_tr(202, $result);
>>> + return;
>>> + }
>>> + $result = &_rpz_validate_customlist(\@blocklist);
>>> + if($result != 0) {
>>> + $errormessage = &_rpz_error_tr(203, $result);
>>> + return;
>>> + }
>>> +
>>> + # Add to global data hash and save changes
>>> + $customlists{'allow'}{'list'} = \@allowlist;
>>> + $customlists{'block'}{'list'} = \@blocklist;
>>> + $customlists{'allow'}{'enabled'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? 'on' : 'off';
>>> + $customlists{'block'}{'enabled'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? 'on' : 'off';
>>> +
>>> + &_customlists_save_conf();
>>> + &_customlist_export('allow', $RPZ_ALLOWLIST);
>>> + &_customlist_export('block', $RPZ_BLOCKLIST);
>>> +
>>> + # Make new lists, request service reload on success
>>> + $result = &General::system('/usr/sbin/rpz-make', 'allowblock', '--no-reload');
>>> + return unless &_rpz_check_result($result, 1);
>>> +
>>> + return 1;
>>> +}
>>> +
>>> +# Trigger rpz-config reload
>>> +sub _action_rpz_reload {
>>> + return 1 unless &_rpz_needs_reload();
>>> +
>>> + # Immediately clear flag to prevent multiple reloads
>>> + if(-f $RPZ_RELOAD_FLAG) {
>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>> + }
>>> +
>>> + # Perform reload, recreate reload flag on error to enable retry
>>> + my $result = &General::system('/usr/sbin/rpz-config', 'reload');
>>> + if(not &_rpz_check_result($result, 0)) {
>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>> + return;
>>> + }
>>> +
>>> + return 1;
>>> +}
>>> +
>>> +# Trigger unbound restart
>>> +sub _action_unb_restart {
>>> + return 1 unless &_rpz_needs_reload();
>>> +
>>> + # Immediately clear flag to prevent multiple restarts
>>> + if(-f $RPZ_RELOAD_FLAG) {
>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>> + }
>>> +
>>> + # Perform restart, unboundctrl always exits zero
>>> + &General::system('/usr/local/bin/unboundctrl', 'restart');
>>> +
>>> + return 1;
>>> +}
>>> +
>>> +
>>> +###--- Internal rpz-config functions ---###
>>> +
>>> +# Translate rpz-config exitcodes and messages
>>> +# 100-199: rpz-config, 200-299: webgui
>>> +sub _rpz_error_tr {
>>> + my ($error, $append) = @_;
>>> + $append //= '';
>>> +
>>> + # Translate numeric exit codes
>>> + if(looks_like_number($error)) {
>>> + if(defined $Lang::tr{"rpz exitcode $error"}) {
>>> + $error = $Lang::tr{"rpz exitcode $error"};
>>> + }
>>> + }
>>> +
>>> + return "RPZ $Lang::tr{'error'}: $error" . &Header::escape($append);
>>> +}
>>> +
>>> +# Check result of rpz-config system call, request reload on success
>>> +sub _rpz_check_result {
>>> + my ($result, $request_reload) = @_;
>>> + $request_reload //= 0;
>>> +
>>> + # exitcode 0 = success
>>> + if($result != 0) {
>>> + $errormessage = &_rpz_error_tr($result);
>>> + return;
>>> + }
>>> +
>>> + # Set reload flag
>>> + if($request_reload) {
>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>> + }
>>> +
>>> + return 1;
>>> +}
>>> +
>>> +# Test whether reload flag is set
>>> +sub _rpz_needs_reload {
>>> + return (-f $RPZ_RELOAD_FLAG);
>>> +}
>>> +
>>> +# Validate a zonefile entry, returns rpz-config exitcode on failure. Use _rpz_check_result to verify.
>>> +# unique = check for unique name
>>> +sub _rpz_validate_zonefile {
>>> + my ($name, $url, $remark, $unique) = @_;
>>> + $unique //= 1;
>>> +
>>> + unless($name =~ /^[a-zA-Z0-9_]{1,32}$/) {
>>> + return 101;
>>> + }
>>> + unless($url =~ /^[\w+\.:;\/\\&@#%?=\-~|!]{1,128}$/) {
>>> + return 105;
>>> + }
>>> + unless($remark =~ /^[\w \-()\.:;*\/\\?!&=]{0,32}$/) {
>>> + return 201;
>>> + }
>>> +
>>> + # Check against already existing names
>>> + if($unique) {
>>> + foreach my $existing (keys %zonefiles) {
>>> + if(lc($name) eq lc($existing)) {
>>> + return 104;
>>> + }
>>> + }
>>> + }
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +# Validate a custom list, returns number of rejected line on failure. Check for non-zero results.
>>> +# listref = array reference, cleanup = remove invalid entries instead of returning an error
>>> +sub _rpz_validate_customlist {
>>> + my ($listref, $cleanup) = @_;
>>> + $cleanup //= 0;
>>> +
>>> + foreach my $index (reverse 0..$#{$listref}) {
>>> + my $row = @$listref[$index];
>>> + next unless($row); # Skip/allow empty lines
>>> +
>>> + # Reject/remove everything besides wildcard domains and remarks
>>> + if((not &General::validwildcarddomainname($row)) && (not $row =~ /^;[\w \-()\.:;*\/\\?!&=]*$/)) {
>>> + unless($cleanup) {
>>> + # +1 for user friendly line number and to ensure non-zero exitcode
>>> + return $index + 1;
>>> + }
>>> +
>>> + # Remove current row
>>> + splice(@$listref, $index, 1);
>>> + }
>>> + }
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +
>>> +###--- Internal misc functions ---###
>>> +
>>> +# Send HTTP 303 redirect headers for post/request/get pattern
>>> +# (Must be sent before calling &Header::showhttpheaders())
>>> +sub _http_prg_redirect {
>>> + my $location = "https://$ENV{'SERVER_NAME'}:$ENV{'SERVER_PORT'}$ENV{'SCRIPT_NAME'}";
>>> + print "Status: 303 See Other\n";
>>> + print "Location: $location\n";
>>> +}
>>> diff --git a/lfs/rpz b/lfs/rpz
>>> new file mode 100644
>>> index 000000000..7ddbc38e5
>>> --- /dev/null
>>> +++ b/lfs/rpz
>>> @@ -0,0 +1,96 @@
>>> +###############################################################################
>>> +# #
>>> +# IPFire.org - A linux based firewall #
>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>> +# #
>>> +# 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 <http://www.gnu.org/licenses/>. #
>>> +# #
>>> +###############################################################################
>>> +
>>> +###############################################################################
>>> +# Definitions
>>> +###############################################################################
>>> +
>>> +include Config
>>> +
>>> +SUMMARY = response policy zone - RPZ reputation system for unbound DNS
>>> +
>>> +VER = 1.0.0
>>> +
>>> +THISAPP = rpz-$(VER)
>>> +DIR_APP = $(DIR_SRC)/$(THISAPP)
>>> +TARGET = $(DIR_INFO)/$(THISAPP)
>>> +
>>> +PROG = rpz
>>> +PAK_VER = 18
>>> +
>>> +DEPS =
>>> +
>>> +SERVICES =
>>> +
>>> +###############################################################################
>>> +# Top-level Rules
>>> +###############################################################################
>>> +
>>> +install : $(TARGET)
>>> +
>>> +check :
>>> +
>>> +download :
>>> +
>>> +b2 :
>>> +
>>> +dist:
>>> + @$(PAK)
>>> +
>>> +###############################################################################
>>> +# Installation Details
>>> +###############################################################################
>>> +
>>> +$(TARGET) :
>>> + @$(PREBUILD)
>>> + @rm -rf $(DIR_APP)
>>> +
>>> + # RPZ scripts
>>> + install --verbose --mode=755 \
>>> + $(DIR_CONF)/rpz/{rpz-config,rpz-metrics,rpz-sleep,rpz-make,rpz-functions} \
>>> + --target-directory=/usr/sbin
>>> +
>>> + # RPZ config files
>>> + mkdir -pv /etc/unbound/local.d
>>> + install --verbose --mode=644 --owner=nobody --group=nobody \
>>> + $(DIR_CONF)/rpz/00-rpz.conf \
>>> + --target-directory=/etc/unbound/local.d
>>> + chown --verbose --recursive nobody:nobody /etc/unbound/local.d
>>> +
>>> + # RPZ custom list files for allow and block
>>> + mkdir -pv /var/ipfire/dns/rpz
>>> + touch /var/ipfire/dns/rpz/{allowlist,blocklist}
>>> + chown --verbose --recursive nobody:nobody /var/ipfire/dns/rpz
>>> +
>>> + # RPZ zone files
>>> + # create empty RPZ config file to avoid a unbound config error
>>> + mkdir -pv /etc/unbound/zonefiles
>>> + touch /etc/unbound/zonefiles/allow.rpz
>>> + chown --verbose --recursive nobody:nobody /etc/unbound/zonefiles
>>> +
>>> + # Install addon-specific language-files
>>> + install --verbose --mode=004 $(DIR_CONF)/rpz/rpz.*.pl \
>>> + --target-directory=/var/ipfire/addon-lang
>>> +
>>> + # Install backup definition
>>> + cp -vf $(DIR_CONF)/backup/includes/rpz /var/ipfire/backup/addons/includes/rpz
>>> +
>>> + @rm -rf $(DIR_APP)
>>> + @$(POSTBUILD)
>>> diff --git a/make.sh b/make.sh
>>> index 827ea9e77..a77535b13 100755
>>> --- a/make.sh
>>> +++ b/make.sh
>>> @@ -390,7 +390,7 @@ prepareenv() {
>>> if [ "${free_space}" -lt "${required_space}" ]; then
>>> # Add any consumed space
>>> while read -r consumed_space path; do
>>> - (( free_space += consumed_space / 1024 / 1024 ))
>>> + (( free_space += consumed_space / 1024 / 1024 ))
>>> done <<< "$(du --summarize --bytes "${BUILD_DIR}" "${IMAGES_DIR}" "${LOG_DIR}" 2>/dev/null)"
>>> fi
>>>
>>> @@ -2087,6 +2087,7 @@ build_system() {
>>> lfsmake2 btrfs-progs
>>> lfsmake2 inotify-tools
>>> lfsmake2 grub-btrfs
>>> + lfsmake2 rpz
>>>
>>> lfsmake2 linux
>>> lfsmake2 rtl8812au
>>> diff --git a/src/paks/rpz/install.sh b/src/paks/rpz/install.sh
>>> new file mode 100644
>>> index 000000000..ef99bf742
>>> --- /dev/null
>>> +++ b/src/paks/rpz/install.sh
>>> @@ -0,0 +1,36 @@
>>> +#!/bin/bash
>>> +###############################################################################
>>> +# #
>>> +# IPFire.org - A linux based firewall #
>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>> +# #
>>> +# 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 <http://www.gnu.org/licenses/>. #
>>> +# #
>>> +###############################################################################
>>> +#
>>> +. /opt/pakfire/lib/functions.sh
>>> +extract_files
>>> +restore_backup ${NAME}
>>> +
>>> +# fix user created files
>>> +chown --verbose --recursive nobody:nobody \
>>> + /var/ipfire/dns/rpz \
>>> + /etc/unbound/zonefiles \
>>> + /etc/unbound/local.d
>>> +
>>> +# Update Language cache
>>> +/usr/local/bin/update-lang-cache
>>> +
>>> +# restart unbound to load config file
>>> +/etc/init.d/unbound restart
>>> diff --git a/src/paks/rpz/uninstall.sh b/src/paks/rpz/uninstall.sh
>>> new file mode 100644
>>> index 000000000..e11427df3
>>> --- /dev/null
>>> +++ b/src/paks/rpz/uninstall.sh
>>> @@ -0,0 +1,38 @@
>>> +#!/bin/bash
>>> +###############################################################################
>>> +# #
>>> +# IPFire.org - A linux based firewall #
>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>> +# #
>>> +# 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 <http://www.gnu.org/licenses/>. #
>>> +# #
>>> +###############################################################################
>>> +#
>>> +. /opt/pakfire/lib/functions.sh
>>> +
>>> +# stop unbound to delete RPZ conf file
>>> +/etc/init.d/unbound stop
>>> +
>>> +make_backup ${NAME}
>>> +remove_files
>>> +
>>> +# delete rpz config files. Otherwise unbound will throw error:
>>> +# "[1723428668] unbound-control[17117:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>> +
>>> +# Update Language cache
>>> +/usr/local/bin/update-lang-cache
>>> +
>>> +# start unbound to load unbound config file
>>> +/etc/init.d/unbound start
>>> diff --git a/src/paks/rpz/update.sh b/src/paks/rpz/update.sh
>>> new file mode 100644
>>> index 000000000..9bc340bc6
>>> --- /dev/null
>>> +++ b/src/paks/rpz/update.sh
>>> @@ -0,0 +1,52 @@
>>> +#!/bin/bash
>>> +###############################################################################
>>> +# #
>>> +# IPFire.org - A linux based firewall #
>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>> +# #
>>> +# 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 <http://www.gnu.org/licenses/>. #
>>> +# #
>>> +###############################################################################
>>> +#
>>> +. /opt/pakfire/lib/functions.sh
>>> +
>>> +# from update.sh
>>> +extract_backup_includes
>>> +
>>> +# stop unbound to delete RPZ conf file
>>> +/etc/init.d/unbound stop
>>> +
>>> +# from uninstall.sh
>>> +make_backup ${NAME}
>>> +remove_files
>>> +
>>> +# delete rpz config files. Otherwise unbound will throw error:
>>> +# "unbound-control[nn:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>> +
>>> +# from install.sh
>>> +extract_files
>>> +restore_backup ${NAME}
>>> +
>>> +# fix user created files
>>> +chown --verbose --recursive nobody:nobody \
>>> + /var/ipfire/dns/rpz \
>>> + /etc/unbound/zonefiles \
>>> + /etc/unbound/local.d
>>> +
>>> +# Update Language cache
>>> +/usr/local/bin/update-lang-cache
>>> +
>>> +# restart unbound to load config files
>>> +/etc/init.d/unbound start
>>> --
>>> 2.39.5
>>>
>>
>
> Jon
>
>
> --
> Jon Murphy
> jon.murphy(a)ipfire.org
>
>
>
>
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
[not found] ` <C28A0D7E-6C16-4F6F-9366-A8498F40631E@ipfire.org>
@ 2025-02-14 12:07 ` Michael Tremer
2025-02-14 12:58 ` Bernhard Bitsch
2025-03-01 10:18 ` Adolf Belka
2025-03-16 17:00 ` Jon Murphy
1 sibling, 2 replies; 30+ messages in thread
From: Michael Tremer @ 2025-02-14 12:07 UTC (permalink / raw)
To: development
[-- Attachment #1: Type: text/plain, Size: 122757 bytes --]
Hello Jon,
It very much depends on the kind of contribution. A one-line patch obviously has fewer strings attached to it than a larger patch set like this.
However, we have outlined the process in the wiki already, starting from here:
https://www.ipfire.org/docs/devel/submit-patches
This contains some useful pointers about the how (how do I actually make my changes happen, how do I build IPFire, etc), and at the bottom it contains a lot of information about the format the changes should be submitted; split into smaller chunks that are ideally as independent from each other so that they can be individually reviewed and merged. Usually a development process takes long time and we have already shipped parts of code that we will need for certain features that are not ready yet. This is a good practice to let code mature, especially when it is touching rather critical bits like the firewall and networking stacks.
There are also some guidelines on how to write a good commit message and how to use Git tags:
https://www.ipfire.org/docs/devel/git/commit-messages
https://www.ipfire.org/docs/devel/git/tags
Then there is something about how to get in touch with the right person and legal stuff:
https://www.ipfire.org/docs/devel/contact
https://www.ipfire.org/docs/legal/ipca
Finally, we have a bit of an underused roadmap section on the wiki. It would be nice if we could use that a little bit more because then it would be easier for everyone to keep track of progress on certain features; people could see what is being worked on and see if they can help development and testing and so on:
https://www.ipfire.org/docs/roadmap
There is a template on how to create new pages:
https://www.ipfire.org/docs/roadmap/template
And this is a good example of what this could look like:
https://www.ipfire.org/docs/roadmap/openvpn-26
All of these steps are coming *after* there has been some initial discussion about what actually has a chance to become part of the distribution. For that, we do not have any specific guidelines because it is not very trivial to write these things. There are just too many possibilities. In the past, there has also been very little need for this, but that does not mean that there have not been problems before.
The reason why I am raising the bar this high here is simply that we have made mistakes in the past that we don’t want to repeat. We have learned a couple of lessons in a not very pleasant way and I under no circumstances would want to do this again. The objective is that we want to provide an excellent distribution. Although IPFire of course has its shortcomings here and there, it is a very stable distribution and we have a very good track record that I want to keep. This is what our users deserve.
In the past, people have “dropped” their patches on this list (or sometimes elsewhere) and we were left with dealing with the entire integration only to find out later what problems there were hidden in the code. The original author(s) had no interest in fixing any of that because it worked just fine for them, and so why spend any time on the problems of somebody else? Usually I am the fallback for this and I simply don’t want to be that. I have lots of my own projects inside IPFire that are moving at snail speed because fixing existing code usually takes priority over writing new code.
Therefore we need a commitment to sort out these problems in the first place. It has to be proven that people actually *care* about the patches that they post here. I am not sure this needs writing down as this should be the same policy with almost any open source project. If you contribute a line, there is probably less maintenance required in the future, but if you contribute a large code base, then you will need to look after it for the foreseeable future. It is your feature and not mine after all.
Then, what actually has a chance to make it into the distribution? Probably not a lot. IPFire has a very clear use case. There will not be any space for a desktop environment and running Chrome on it, we also don’t it to make coffee and cook me a dinner. We would currently only accept things that were actually maintainable by the current team in case a contributor moves on (see above), because we simply only have so much man power. We already have a large zoo of features that are very abandoned and we are potentially looking at getting rid of more things simply because we cannot support them properly. Time just doesn’t permit. Adding something large is therefore very difficult at the moment.
I understand that in this specific case you have been trying to not involve the development team and I understand your motivation. But you cannot forget about how much time and effort a review process can take. Therefore we want to plan things well; we want to even split it; and we want to have a conversation in advance so that the roadmap is clear and the actual code review ideally only becomes a formality.
All of this above has been for a general case. Please read through this and feel free to ask any questions if something isn’t clear.
To move forward with this feature, we should start by planning a roadmap. We need to discuss what this project should cover and what it should not cover. I believe we don’t need to talk much about implementation details because you have figured out a lot of them; we need to find what feature we want to provide to our users. Are you up for that?
Best,
-Michael
> On 13 Feb 2025, at 21:34, jon <jon.murphy(a)ipfire.org> wrote:
>
> Michael,
>
> I’ve read through your comments a few times and I ended up with many more questions.
>
>
>> What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>
>
> To me the efforts to get new code accepted seem to have changed and it seemed easier in the past. In the past I made the Core Team aware via the Dev Mailing List and wrote a simple two or three paragraphs of "What is it? / What is the value? / Here is the code"
>
>
> So in an effort to move forward: How exactly is something presented to the Core Team?
>
> Is there an example of a recent effort that was presented that I can see as a sample? (This type of info can also be added to the Wiki)
>
> I understand you want it this way, but I don’t know what exactly is needed. Please be specific.
>
>
> Jon
>
> PS - I am not ignoring your other comments, I am just trying to move forward and keep things simple.
>
>
>
>> On Feb 8, 2025, at 1:27 PM, Michael Tremer <michael.tremer(a)ipfire.org> wrote:
>>
>> Hello Jon,
>>
>> Thanks for your reply. And good that you are copying everyone into this conversation.
>>
>>> On 8 Feb 2025, at 18:41, jon <jon.murphy(a)ipfire.org> wrote:
>>>
>>> Michael,
>>>
>>>> I think I have covered this all at lengths before that this project has been started as a separate effort
>>>
>>> Yes, this has been a separate effort (a very public separate effort). Yes, as you pointed this out early on with the "proof-of-concept" and then my request for people to help test RPZ. Nothing was hidden.
>>>
>>> This was done because you (and maybe others) did not have the time and I wanted to help and because I needed assistance with RPZ. I tried my best to do this without bothering you.
>>
>> I don’t that it is accurate that nobody wanted to help on this. The list was always open - although not every email has been replied to swiftly it is also your responsibility to raise a question again if it was missed. People here have open ears.
>>
>> It was also stated on this very list on in our documentation that working on something without involving the core team is a risky undertaking. Of course IPFire is free software and so everyone is free to fork if they wish to do so.
>>
>>>> and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls.
>>>
>>> You were aware many steps along the way. See your email on July 28, 2024, August 15, 2024, September 30, 2024, December 23, 2024, and January 16. My attempts to get the team involved were met with "things are busy" and sometimes silence. (Yes, I get it, people are busy.)
>>>
>>> You and Adolf, Leo, Erik and Bernhard have been aware since the beginning. You mention you were aware of the "proof-of-concept". If you include those beginning posts, since Sep 2023.
>>
>> Yes, I am aware of a proof-of-concept that I have been running myself for a long time. I am also aware of the efforts that you have been taking.
>>
>> Yet I don’t think there has ever been any joint effort, or am I seeing that wrong?
>>
>>>> This has not been discussed . . . on our calls.
>>>
>>> On the July 28th you stated:
>>> "We have talked about RPZ many times on the monthly call since the URL filter feature is falling more and more out of fashion. I think there is also many posts about this on the forum."
>>>
>>> Please don’t insult me again by stating "you know what I mean".
>>>
>>> And it has been discussed but not documented in the Monthly Meeting notes.
>>
>> I am not at all insulting you. I don’t want to take this down to a personal level at all. This is a public mailing list and people who read this don’t need to listen to an argument we are having. They are here for the tech inside IPFire.
>>
>> When I wrote that it has not been discussed that does not mean that we have not been touching on the topic. We have been talking about lots of things on the calls, the weather, politics, how our pets are. None of that makes it to the logs. What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>
>>>> Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation.
>>>
>>> Regular conversation on the Dev Mailing list is many times met with silence. I get it, people are busy.
>>>
>>> And regular two-way conversation doesn’t happen on the list. At least not with me. I’d be happy to point out the posts that were met with silence.
>>> Again, I get it, people are busy.
>>
>> And you think my emails are not being met with silence? This has nothing to do with this specific topic. This has something to do with how occupied people are and how engaged they are on certain topics. Not everyone is involved in all the things and simply will ignore emails simply based on their subject line.
>>
>>> But the "dip here to the list" were my attempts to get a conversation started. As I said, many time met with silence.
>>>
>>> The only place I was not met with silence was on the Community. You have a great group of people in the Community. It is a shame you don’t want to have others help. It would reduce your workload.
>>
>> You should stop making statements that are not true. Who doesn’t want anyone to help?
>>
>> Not having this conversation on a Saturday evening would reduce my workload. At least it would free up time for something else. Helping with the things that are already on the go would reduce the workload of the entire team. Starting one thing at a time and finishing it is a lot better to manage than starting a hundred things and not even finish one. I can tell you that I already have a hundred things on the go.
>>
>>>> Therefore, what am I supposed to do with this email?
>>>
>>> To me it is beyond obvious…
>>>
>>> If it isn’t what you want, then guide me with how to do this the correct way. And be specific. I am trying to help. I am trying to make things better. I am trying to do things the right way.
>>
>> To me it isn’t. This is yet another project that has been dumped to the list like so many before and later on everyone has left to have the team deal with the rest.
>>
>> It is a huge patch set. You explained what the vision is, but that is about it. There is no chance this will continue if this disagreement isn’t solved first. I didn’t even look at the code.
>>
>>>> I don’t want to merge code that I don’t agree with.
>>>
>>> I asked multiple times if you "agreed with the concept" and again, met with silence. Yes I get it, people are busy.
>>
>> Having support for RPZ? Yes, it was definitely on the roadmap. That I agree with.
>>
>>>> So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>
>>> You mentioned this a in the past, but for some reason you do not disclose what I dismissed. Why do you continue to make this harder, wouldn’t it not be easier to tell me what I have dismissed?
>>>
>>> I have sent multiple emails trying to answer your concerns and comments. On July 28, Aug 14, Aug 22, Aug 23, Sep 30, etc.
>>>
>>> I’ve gone through all of the questions you asked and I cannot find a "dismissed" item.
>>
>> Maybe I need to be *more clear*. I feel humoured by this.
>>
>> It is late on a Saturday and I want my dinner soon, but certainly I have stated that this should never be an add-on considering it is supposed to replace URL Filter. We should never allow people to add their own sources. I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>
>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>
>>> The maintainers of Unbound and/or RPZ?
>>>
>>> The maintainers of Hagezi list, the threatfox list, the urlhaus list, etc.?
>>>
>>> What else? The maintainers or the RPZ scripts? That is me. Let’s talk!
>>
>> You. I don’t care much about the providers of the lists.
>>
>>> See, this is where it gets confusing. There are hundreds of open source packages as part of IPFire. Pick the last five years of items added to the IPFire build. You're telling me you have "constructive conversation with the maintainers" of all of the added packages?
>>
>> They publish their software and they don’t care whether I am pulling it or not. They publish it with the commitment to maintain it - sometimes for better and sometimes for worse.
>>
>> You care about me pulling your code and I don’t know whether you would commit to maintain this.
>>
>> These two are very different cases.
>>
>>> Pick the IP Blocklists list (i.e., 3CORESEC, ABUSECH, DSHIELD, SPAMHAUS, etc.) or the Suricata lists (i.e., Emergingthreats.net, Abuse.ch, etc.). So you’ve have "constructive conversation with the maintainers"?
>>
>> Yes, occasionally I have phone calls with a few of these providers.
>>
>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>
>>> Ha! Yes a surprise. In the beginning you seemed interested as IPFire needed a replacement for URL Filter. You asked good questions about the lists picked, asked for the value to the users, etc. And I answered the best I could.
>>>
>>> You even asked: “Why is this realised as an add-on and not part of the core system?” from your Jul 28, 2024 email.
>>
>> Ah, so, why is the patch creating an add-on? Not that I am saying that what I say is law, but it has not been challenged either. If my input is being ignored, why should I put this to the top of my list of priorities? I am not disappointed about this, just trying to be very good with my time.
>>
>>> And on January 16, 2025 I wrote a message looking for help. And you were kind to respond quickly. So in three weeks time, since the kind response, something has changed. You went from supportive to "this".
>>>
>>> So yes, I am surprised.
>>
>> Well, maybe I should not have replied to that email. It was clear that you were on some path that was not right, but you were not interested before in finding the right path from the beginning.
>>
>>>> Please consider if that can be changed and if there is a path forward with this.
>>>
>>> Be more specific, what has to change? What exactly did I dismiss?
>>
>> Dismissal is just my assumption. I don’t know what you actually did with my feedback. I can only see the end product that does not seem contain much of it. Repeatedly I have been pointing out that we should think before we build. I am sure a lot of hours have now gone into some code that simply does not satisfy me. And I am not not talking about the code itself, what it does is what I don’t think is right for us.
>>
>> The process is very clear for me that we should first of all think whether we want a certain feature now. Then there should be a clear roadmap for everyone to follow; tasks can be split-up as we go and hopefully then have something that is maintainable, interesting for our users and even would do us proud. This is how this should work.
>>
>> So, what has to change? I don’t think with shouting at each other, throwing patches around and making me generally unhappy is a good start.
>>
>> -Michael
>>
>>> Jon
>>>
>>>
>>>
>>>> On Feb 6, 2025, at 2:13 PM, Michael Tremer <michael.tremer(a)ipfire.org> wrote:
>>>>
>>>> Hello Jon,
>>>>
>>>> Well, here we are again with another patch regarding this feature.
>>>>
>>>> I cannot quite see from your email what the question is, but if this is a request to have this merged into IPFire, I am once again sorry to disappoint you.
>>>>
>>>> I think I have covered this all at lengths before that this project has been started as a separate effort and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls. Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation. Therefore, what am I supposed to do with this email?
>>>>
>>>> I don’t want to merge code that I don’t agree with. So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>
>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>
>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>
>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>
>>>> All the best,
>>>> -Michael
>>>>
>>>>> On 6 Feb 2025, at 16:35, Jon Murphy <jon.murphy(a)ipfire.org> wrote:
>>>>>
>>>>> What is it?
>>>>> Response Policy Zone (RPZ) is a mechanism to define local policies in a
>>>>> standardized way and load those policies from external sources.
>>>>> Bottom line: RPZ allows admins to easily block access to websites via DNS lookup.
>>>>>
>>>>> RPZ can block websites via categories. Examples include: fake websites, annoying
>>>>> pop-up ads, newly registered domains, DoH bypass sites, bad "host" services,
>>>>> maliscious top level domains (e.g., *.zip, *.mov), piracy, gambling, pornography,
>>>>> and more. RPZ lists come from various RPZ providers and their available
>>>>> catagories.
>>>>>
>>>>> This RPZ add-on enables the RPZ functionality by adding a couple lines in a
>>>>> configuration file. This add-on simply adds configuration files and adds
>>>>> scripts (config, metrics and sleep) to make RPZ easier for the admin to use.
>>>>>
>>>>> The RPZ scripts include additional languages: German, Spanish, French, Turkish,
>>>>> and Italian.
>>>>>
>>>>> RPZ itself was release in 2010 and has been part of the IPFire build since ~2015.
>>>>>
>>>>> Why is it needed? What is its value?
>>>>>
>>>>> - The RPZ concept places this filtering into IPFire, our internet access
>>>>> gateway, which is (should be) solely used as DNS source of the internal network.
>>>>>
>>>>> - As most sites use HTTPS it makes it difficult to filter traffic with URL
>>>>> Filter without also properly configuring conventional (non-transparent)
>>>>> mode on the proxy. RPZ is a nice replacement for the URL Filter.
>>>>>
>>>>> - No need to install and maintain an additional device like PiHole or AdBlock
>>>>> browser extensions on multiple user devices.
>>>>>
>>>>> - This is an additional layer of protection for users. Less worry someone will
>>>>> click on something that gets them into trouble. And, saying this with emphasis,
>>>>> the ability to do it in one place!
>>>>>
>>>>> - Blocked sites save on unneeded traffic and can lessen the threat of malware
>>>>> in advertisements
>>>>>
>>>>> - Logging allows the admin to see the site blocked and take actions
>>>>>
>>>>> - RPZ will be used at the home, home-office (work from home), schools,
>>>>> ministerial, and at the office. Device counts are small (2-6) to medium (~80)
>>>>> to mediam-large (200+).
>>>>>
>>>>> - RPZ can block ads, popups, phishing, scammers, spyware, malware, annoying
>>>>> popups, NSFW links, DOH servers, and the usual internet trash.
>>>>>
>>>>> ------------------------------
>>>>>
>>>>> Change Log for RPZ add-on
>>>>>
>>>>> rpz-1.0.0-18 on 2025-02-05
>>>>> - Build for approval & release as IPFire add-on
>>>>>
>>>>> ---
>>>>>
>>>>> rpz-beta-0.1.18-18.ipfire on 2025-02-01
>>>>> rpz.cgi:
>>>>> - new feature: added a mod key to force a unbound restart
>>>>>
>>>>> rpz-config and rpz-make:
>>>>> - new feature: added action for unbound restart `rpz-config unbound-restart`
>>>>>
>>>>> rpz-metrics:
>>>>> - simple reformatting
>>>>> - rename far right column from "last update" to "last download"
>>>>>
>>>>> ---
>>>>>
>>>>> rpz-beta-0.1.17-17.ipfire on 2024-12-09
>>>>> rpz-make
>>>>> - bug fix: corrected validation regex for wildcards like: `*.domain.com`
>>>>>
>>>>> ---
>>>>>
>>>>> rpz-beta-0.1.16-16.ipfire on 2024-11-18
>>>>> rpz-make
>>>>> - new feature: updated validation regex
>>>>> - bug fix: moved validation to beginning of process. Now we validate before
>>>>> creating config files.
>>>>>
>>>>> rpz.cgi:
>>>>> - new feature: use CSS color variables of the main ipfire theme
>>>>> - bug fix: empty zonefile remarks were stored as “undef” and caused a warning
>>>>> - bug fix: HTML textarea removes the first empty line in a custom list
>>>>> - thank you Leo!
>>>>>
>>>>> ---
>>>>>
>>>>> rpz-beta-0.1.15-15.ipfire on 2024-11-04
>>>>> rpz.cgi:
>>>>> - new feature: added new language file for Turkish (thank you Peppe)
>>>>>
>>>>> rpz-make
>>>>> - bug fix: corrected empty allow/block list issue. An empty allow/block list
>>>>> will now remove contents of allow/block.rpz files and remove unneeded
>>>>> allow/block.conf file. (thank you iptom)
>>>>>
>>>>> ---
>>>>>
>>>>> rpz-beta-0.1.14-14.ipfire on 2024-10-29
>>>>> rpz-config:
>>>>> - bug fix: correct missing rpz extension. `rpz-config list` displayed URL
>>>>> incorrectly (thank you Bernhard)
>>>>>
>>>>> rpz.cgi:
>>>>> - bug fix: remove extra `"` in language files (thank you Bernhard)
>>>>> - new feature: slightly dim "apply" button when not enabled
>>>>>
>>>>> ---
>>>>>
>>>>> rpz-beta-0.1.13-13.ipfire on 2024-10-27
>>>>> - skipped
>>>>>
>>>>> ---
>>>>>
>>>>> rpz-beta-0.1.12-12.ipfire on 2024-10-21
>>>>> rpz.cgi:
>>>>> - new feature: added new language file for French (thank you gw-ipfire)
>>>>>
>>>>> ---
>>>>>
>>>>> rpz-beta-0.1.11-11.ipfire on 2024-10-18
>>>>> rpz.cgi:
>>>>> - new feature: added new language file for Italian (thank you umberto)
>>>>> - new feature: added new language file for Spanish (thank you Roberto)
>>>>>
>>>>> ---
>>>>>
>>>>> rpz-beta-0.1.10-10.ipfire on 2024-10-15
>>>>> rpz-make:
>>>>> - bug fix: corrected validation error for a custom list entry (thank you siosios)
>>>>> - e.g., `*.cloudflare-dns.com`
>>>>>
>>>>> install.sh:
>>>>> - bug fix: add chown to correct user created files
>>>>>
>>>>> update.sh:
>>>>> - bug fix: add chown to correct user created files (thank you siosios)
>>>>>
>>>>> ---
>>>>>
>>>>> rpz-beta-0.1.9-9.ipfire on 2024-10-08
>>>>> rpz.cgi:
>>>>> - new feature: added new language file for German (thank you Leo)
>>>>> - bug fix: add missing "rpz exitcode 110"
>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>
>>>>> ---
>>>>>
>>>>> rpz-beta-0.1.8-8.ipfire on 2024-10-04
>>>>> - skipped
>>>>>
>>>>> ---
>>>>>
>>>>> rpz-beta-0.1.7-7.ipfire on 2024-10-03
>>>>> All:
>>>>> - new feature: includes beta version numbers for pakfire package,
>>>>> instead of only `rpz-1.0.0-1.ipfire`, for each release.
>>>>>
>>>>> rpz.cgi:
>>>>> - new feature: added new WebGUI at `rpz.cgi`
>>>>> - a BIG thank you to Leo Hofmann for all of his work creating the webgui!!
>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>
>>>>> rpz-make:
>>>>> - new feature: validate entries in allowlist and blocklist
>>>>> - new feature: add "no-reload" option for WebGUI
>>>>>
>>>>> rpz-metrics:
>>>>> - new feature: info can be sorted by name, by hit count, by line count, by
>>>>> "enabled" list or all lists
>>>>>
>>>>> backups:
>>>>> - bug fix: include all files in `/var/ipfire/dns/rpz` directory in backup
>>>>>
>>>>> update.sh:
>>>>> - bug fix: corrected ownership for `/var/ipfire/dns/rpz` directory during an
>>>>> update
>>>>>
>>>>> Build:
>>>>> - bug fix: `block.rpz.conf` and `block.rpz` from build. Files to be created
>>>>> by `rpz-make`
>>>>>
>>>>> WebGUI and German language file
>>>>> Contribution-by: Leo-Andres Hofmann <hofmann(a)leo-andres.de>
>>>>>
>>>>> Spanish language file
>>>>> Contribution-by: Roberto Peña
>>>>>
>>>>> Italian language file
>>>>> Contribution-by: Umberto Parma
>>>>>
>>>>> French language file
>>>>> Contribution-by: gw-ipfire
>>>>>
>>>>> Turkish language file
>>>>> Contribution-by: Peppe Tech
>>>>>
>>>>> Contribution-by: Bernhard Bitsch <bbitsch(a)ipfire.org>
>>>>> Contribution-by: Erik Kapfer <erik.kapfer(a)ipfire.org>
>>>>> Signed-off-by: Jon Murphy <jon.murphy(a)ipfire.org
>>>>> ---
>>>>> config/backup/includes/rpz | 4 +
>>>>> config/cfgroot/manualpages | 1 +
>>>>> config/menu/EX-rpz.menu | 6 +
>>>>> config/rootfiles/common/configroot | 1 +
>>>>> config/rootfiles/common/web-user-interface | 1 +
>>>>> config/rootfiles/packages/rpz | 20 +
>>>>> config/rpz/00-rpz.conf | 10 +
>>>>> config/rpz/rpz-config | 130 +++
>>>>> config/rpz/rpz-functions | 85 ++
>>>>> config/rpz/rpz-make | 203 +++++
>>>>> config/rpz/rpz-metrics | 170 ++++
>>>>> config/rpz/rpz-sleep | 58 ++
>>>>> config/rpz/rpz.de.pl | 30 +
>>>>> config/rpz/rpz.en.pl | 30 +
>>>>> config/rpz/rpz.es.pl | 30 +
>>>>> config/rpz/rpz.fr.pl | 30 +
>>>>> config/rpz/rpz.it.pl | 30 +
>>>>> config/rpz/rpz.tr.pl | 30 +
>>>>> html/cgi-bin/rpz.cgi | 923 +++++++++++++++++++++
>>>>> lfs/rpz | 96 +++
>>>>> make.sh | 3 +-
>>>>> src/paks/rpz/install.sh | 36 +
>>>>> src/paks/rpz/uninstall.sh | 38 +
>>>>> src/paks/rpz/update.sh | 52 ++
>>>>> 24 files changed, 2016 insertions(+), 1 deletion(-)
>>>>> create mode 100644 config/backup/includes/rpz
>>>>> create mode 100644 config/menu/EX-rpz.menu
>>>>> create mode 100644 config/rootfiles/packages/rpz
>>>>> create mode 100644 config/rpz/00-rpz.conf
>>>>> create mode 100644 config/rpz/rpz-config
>>>>> create mode 100644 config/rpz/rpz-functions
>>>>> create mode 100644 config/rpz/rpz-make
>>>>> create mode 100755 config/rpz/rpz-metrics
>>>>> create mode 100755 config/rpz/rpz-sleep
>>>>> create mode 100644 config/rpz/rpz.de.pl
>>>>> create mode 100644 config/rpz/rpz.en.pl
>>>>> create mode 100644 config/rpz/rpz.es.pl
>>>>> create mode 100644 config/rpz/rpz.fr.pl
>>>>> create mode 100644 config/rpz/rpz.it.pl
>>>>> create mode 100644 config/rpz/rpz.tr.pl
>>>>> create mode 100644 html/cgi-bin/rpz.cgi
>>>>> create mode 100644 lfs/rpz
>>>>> create mode 100644 src/paks/rpz/install.sh
>>>>> create mode 100644 src/paks/rpz/uninstall.sh
>>>>> create mode 100644 src/paks/rpz/update.sh
>>>>>
>>>>> diff --git a/config/backup/includes/rpz b/config/backup/includes/rpz
>>>>> new file mode 100644
>>>>> index 000000000..36513e494
>>>>> --- /dev/null
>>>>> +++ b/config/backup/includes/rpz
>>>>> @@ -0,0 +1,4 @@
>>>>> +/var/ipfire/dns/rpz/*
>>>>> +/etc/unbound/zonefiles/allow.rpz
>>>>> +/etc/unbound/zonefiles/block.rpz
>>>>> +/etc/unbound/local.d/*rpz.conf
>>>>> diff --git a/config/cfgroot/manualpages b/config/cfgroot/manualpages
>>>>> index 1f7e01efc..d3a48c633 100644
>>>>> --- a/config/cfgroot/manualpages
>>>>> +++ b/config/cfgroot/manualpages
>>>>> @@ -70,6 +70,7 @@ pakfire.cgi=configuration/ipfire/pakfire
>>>>> wlanap.cgi=addons/wireless
>>>>> tor.cgi=addons/tor
>>>>> samba.cgi=addons/samba
>>>>> +rpz.cgi=addons/rpz
>>>>>
>>>>> # Logs menu
>>>>> logs.cgi/summary.dat=configuration/logs/summary
>>>>> diff --git a/config/menu/EX-rpz.menu b/config/menu/EX-rpz.menu
>>>>> new file mode 100644
>>>>> index 000000000..2f4daf410
>>>>> --- /dev/null
>>>>> +++ b/config/menu/EX-rpz.menu
>>>>> @@ -0,0 +1,6 @@
>>>>> +$subipfire->{'20.rpz'} = {
>>>>> + 'caption' => $Lang::tr{'rpz'},
>>>>> + 'uri' => '/cgi-bin/rpz.cgi',
>>>>> + 'title' => "RPZ",
>>>>> + 'enabled' => 1,
>>>>> +};
>>>>> diff --git a/config/rootfiles/common/configroot b/config/rootfiles/common/configroot
>>>>> index 9839eee45..b30d6aae4 100644
>>>>> --- a/config/rootfiles/common/configroot
>>>>> +++ b/config/rootfiles/common/configroot
>>>>> @@ -120,6 +120,7 @@ var/ipfire/menu.d/70-log.menu
>>>>> #var/ipfire/menu.d/EX-apcupsd.menu
>>>>> #var/ipfire/menu.d/EX-guardian.menu
>>>>> #var/ipfire/menu.d/EX-mympd.menu
>>>>> +#var/ipfire/menu.d/EX-rpz.menu
>>>>> #var/ipfire/menu.d/EX-samba.menu
>>>>> #var/ipfire/menu.d/EX-tor.menu
>>>>> #var/ipfire/menu.d/EX-transmission.menu
>>>>> diff --git a/config/rootfiles/common/web-user-interface b/config/rootfiles/common/web-user-interface
>>>>> index 816241dae..e00464076 100644
>>>>> --- a/config/rootfiles/common/web-user-interface
>>>>> +++ b/config/rootfiles/common/web-user-interface
>>>>> @@ -69,6 +69,7 @@ srv/web/ipfire/cgi-bin/proxy.cgi
>>>>> srv/web/ipfire/cgi-bin/qos.cgi
>>>>> srv/web/ipfire/cgi-bin/remote.cgi
>>>>> srv/web/ipfire/cgi-bin/routing.cgi
>>>>> +#srv/web/ipfire/cgi-bin/rpz.cgi
>>>>> #srv/web/ipfire/cgi-bin/samba.cgi
>>>>> srv/web/ipfire/cgi-bin/services.cgi
>>>>> srv/web/ipfire/cgi-bin/shutdown.cgi
>>>>> diff --git a/config/rootfiles/packages/rpz b/config/rootfiles/packages/rpz
>>>>> new file mode 100644
>>>>> index 000000000..1c8663049
>>>>> --- /dev/null
>>>>> +++ b/config/rootfiles/packages/rpz
>>>>> @@ -0,0 +1,20 @@
>>>>> +etc/unbound/local.d/00-rpz.conf
>>>>> +etc/unbound/zonefiles
>>>>> +etc/unbound/zonefiles/allow.rpz
>>>>> +usr/sbin/rpz-config
>>>>> +usr/sbin/rpz-functions
>>>>> +usr/sbin/rpz-make
>>>>> +usr/sbin/rpz-metrics
>>>>> +usr/sbin/rpz-sleep
>>>>> +var/ipfire/addon-lang/rpz.de.pl
>>>>> +var/ipfire/addon-lang/rpz.en.pl
>>>>> +var/ipfire/addon-lang/rpz.es.pl
>>>>> +var/ipfire/addon-lang/rpz.fr.pl
>>>>> +var/ipfire/addon-lang/rpz.it.pl
>>>>> +var/ipfire/addon-lang/rpz.tr.pl
>>>>> +var/ipfire/backup/addons/includes/rpz
>>>>> +var/ipfire/dns/rpz
>>>>> +var/ipfire/dns/rpz/allowlist
>>>>> +var/ipfire/dns/rpz/blocklist
>>>>> +var/ipfire/menu.d/EX-rpz.menu
>>>>> +srv/web/ipfire/cgi-bin/rpz.cgi
>>>>> diff --git a/config/rpz/00-rpz.conf b/config/rpz/00-rpz.conf
>>>>> new file mode 100644
>>>>> index 000000000..f005a4f2e
>>>>> --- /dev/null
>>>>> +++ b/config/rpz/00-rpz.conf
>>>>> @@ -0,0 +1,10 @@
>>>>> +server:
>>>>> + module-config: "respip validator iterator"
>>>>> +
>>>>> +rpz:
>>>>> + name: allow.rpz
>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>> + rpz-action-override: passthru
>>>>> + rpz-log: yes
>>>>> + rpz-log-name: allow
>>>>> + rpz-signal-nxdomain-ra: yes
>>>>> diff --git a/config/rpz/rpz-config b/config/rpz/rpz-config
>>>>> new file mode 100644
>>>>> index 000000000..c72d50f9b
>>>>> --- /dev/null
>>>>> +++ b/config/rpz/rpz-config
>>>>> @@ -0,0 +1,130 @@
>>>>> +#!/bin/bash
>>>>> +###############################################################################
>>>>> +# #
>>>>> +# IPFire.org - A linux based firewall #
>>>>> +# Copyright (C) 2024-2025 IPFire Team <info(a)ipfire.org> #
>>>>> +# #
>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>> +# #
>>>>> +###############################################################################
>>>>> +
>>>>> +version="2025-01-11 - v44"
>>>>> +
>>>>> +############### Functions ###############
>>>>> +
>>>>> +source /usr/sbin/rpz-functions
>>>>> +
>>>>> +############### Main ###############
>>>>> +
>>>>> +tagName="unbound"
>>>>> +
>>>>> +rpzAction="${1}" # input RPZ action
>>>>> +rpzName="${2}" # input RPZ name
>>>>> +rpzURL="${3}" # input RPZ URL
>>>>> +rpzOption1="${4}" # input RPZ option #1
>>>>> +rpzOption2="${5}" # input RPZ option #2
>>>>> +
>>>>> +rpzConfig="/etc/unbound/local.d/${rpzName}.rpz.conf" # output zone conf file
>>>>> +rpzFile="/etc/unbound/zonefiles/${rpzName}.rpz" # output for RPZ file
>>>>> +
>>>>> +rpzLog="yes" # log default is yes
>>>>> +ucReload="yes" # reload default is yes
>>>>> +
>>>>> +while [[ $# -gt 0 ]] ; do
>>>>> + case "$1" in
>>>>> + --no-log ) rpzLog="no" ;;
>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>> + esac
>>>>> + shift # Shift after checking all the cases to get next option
>>>>> +done
>>>>> +
>>>>> +case "${rpzAction}" in
>>>>> + # add new rpz list
>>>>> + add )
>>>>> + check_name "${rpzName}" # is this a valid name?
>>>>> + # does this config already exist? If yes, then exit
>>>>> + if [[ -f "${rpzConfig}" ]] ; then
>>>>> + msg_log "error: rpz: duplicate - ${rpzConfig} already exists. exit"
>>>>> + exit 104
>>>>> + fi
>>>>> +
>>>>> + # is this a valid URL?
>>>>> + regex='^https://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
>>>>> + if ! [[ "${rpzURL}" =~ $regex ]] ; then
>>>>> + msg_log "error: rpz: the URL is not valid: \"${rpzURL}\". exit."
>>>>> + exit 105
>>>>> + fi
>>>>> +
>>>>> + # create the zone config file
>>>>> + {
>>>>> + echo "rpz:"
>>>>> + echo " name: ${rpzName}.rpz"
>>>>> + echo " zonefile: ${rpzFile}"
>>>>> + echo " url: ${rpzURL}"
>>>>> + echo " rpz-action-override: nxdomain"
>>>>> + echo " rpz-log: ${rpzLog}"
>>>>> + echo " rpz-log-name: ${rpzName}"
>>>>> + echo " rpz-signal-nxdomain-ra: yes"
>>>>> + } > "${rpzConfig}"
>>>>> +
>>>>> + # set-up zonefile
>>>>> + # create an empty rpz file if it does not exist
>>>>> + if [[ ! -f "${rpzFile}" ]] ; then
>>>>> + touch "${rpzFile}"
>>>>> + # unbound requires these settings for rpz files
>>>>> + set_permissions "${rpzFile}" "${rpzConfig}"
>>>>> + fi
>>>>> + ;;
>>>>> +
>>>>> + # trash config file & rpz file
>>>>> + remove )
>>>>> + if ! [[ -f "${rpzConfig}" ]] ; then
>>>>> + msg_log "error: rpz: cannot remove ${rpzConfig}, does not exist. exit"
>>>>> + exit 106
>>>>> + fi
>>>>> +
>>>>> + msg_log "info: rpz: remove config file & rpz file \"${rpzName}\""
>>>>> + rm "${rpzConfig}"
>>>>> + rm "${rpzFile}"
>>>>> + ;;
>>>>> +
>>>>> + reload )
>>>>> + check_unbound_conf "${checkConf}"
>>>>> + ;;
>>>>> +
>>>>> + list )
>>>>> + awk -F':' '/^\s*name:/{ gsub(/[[:blank:]]|\.rpz/, "",$2) ; NAME=$2 } \
>>>>> + /^\s*url:/{ gsub(/[[:blank:]]/, "") ; print NAME"="$2":"$3} ' \
>>>>> + /etc/unbound/local.d/*rpz.conf
>>>>> + exit
>>>>> + ;;
>>>>> +
>>>>> + unbound-restart )
>>>>> + check_unbound_conf "${checkConf}"
>>>>> + unbound_restart
>>>>> + exit
>>>>> + ;;
>>>>> +
>>>>> + * )
>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>> + printf "Usage: $(basename "$0") <ACTION> <NAME> <URL> <OPTION> <OPTION>\n"
>>>>> + printf "Version: ${version}\n"
>>>>> + exit 108
>>>>> + ;;
>>>>> +
>>>>> +esac
>>>>> +
>>>>> +unbound_control_reload "${ucReload}"
>>>>> +
>>>>> +exit
>>>>> diff --git a/config/rpz/rpz-functions b/config/rpz/rpz-functions
>>>>> new file mode 100644
>>>>> index 000000000..ace1d2690
>>>>> --- /dev/null
>>>>> +++ b/config/rpz/rpz-functions
>>>>> @@ -0,0 +1,85 @@
>>>>> +#!/bin/bash
>>>>> +###############################################################################
>>>>> +# #
>>>>> +# IPFire.org - A linux based firewall #
>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>> +# #
>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>> +# #
>>>>> +###############################################################################
>>>>> +
>>>>> +version="2024-12-10 - v02"
>>>>> +
>>>>> +############### Functions ###############
>>>>> +
>>>>> +msg_log () {
>>>>> + logger --tag "${tagName}" "$*"
>>>>> + if tty --silent ; then
>>>>> + echo "${tagName}:" "$*"
>>>>> + fi
>>>>> +}
>>>>> +
>>>>> +# check for a valid name
>>>>> +check_name () {
>>>>> + local theName="${1}"
>>>>> +
>>>>> + regex='^[a-zA-Z0-9_]+$' # no dash or plus, alpha numeric only
>>>>> + regex1='^(allow|block)$' # allow and block are reserved NAMEs
>>>>> + if [[ ! "${theName}" =~ $regex ]] || [[ "${theName}" =~ $regex1 ]] ; then
>>>>> + msg_log "error: rpz: the NAME is not valid: \"${theName}\". exit."
>>>>> + exit 101
>>>>> + fi
>>>>> +}
>>>>> +
>>>>> +set_permissions () {
>>>>> + chown nobody:nobody "$@"
>>>>> + chmod 644 "$@"
>>>>> +}
>>>>> +
>>>>> +check_unbound_conf () {
>>>>> + local thecheckConf="${1:-yes}" # check config default is yes
>>>>> +
>>>>> + # check the above config files
>>>>> + if [[ "${thecheckConf}" == yes ]] ; then
>>>>> + msg_log "info: rpz: check for errors with \"unbound-checkconf\""
>>>>> +
>>>>> + if ! unbound-checkconf ; then
>>>>> + msg_log "error: rpz: unbound-checkconf found invalid configuration."
>>>>> + msg_log \
>>>>> + "error: rpz: In Terminal run the command \"unbound-checkconf\" for more information. exit."
>>>>> + exit 102
>>>>> + fi
>>>>> + fi
>>>>> +}
>>>>> +
>>>>> +unbound_control_reload () {
>>>>> + local theReload="${1:-yes}" # reload default is yes
>>>>> +
>>>>> + if [[ "${theReload}" == yes ]] ; then
>>>>> + # reload due to the changes
>>>>> + msg_log "info: rpz: run \"unbound-control reload\""
>>>>> +
>>>>> + if ! unbound-control reload ; then
>>>>> + msg_log "error: rpz: unbound-control reload. exit."
>>>>> + exit 109
>>>>> + fi
>>>>> + fi
>>>>> +}
>>>>> +
>>>>> +unbound_restart () {
>>>>> + # restart due to the changes
>>>>> + msg_log "info: rpz: run \"unbound restart\""
>>>>> +
>>>>> + /usr/local/bin/unboundctrl restart
>>>>> +}
>>>>> diff --git a/config/rpz/rpz-make b/config/rpz/rpz-make
>>>>> new file mode 100644
>>>>> index 000000000..927d55170
>>>>> --- /dev/null
>>>>> +++ b/config/rpz/rpz-make
>>>>> @@ -0,0 +1,203 @@
>>>>> +#!/bin/bash
>>>>> +###############################################################################
>>>>> +# #
>>>>> +# IPFire.org - A linux based firewall #
>>>>> +# Copyright (C) 2024-2025 IPFire Team <info(a)ipfire.org> #
>>>>> +# #
>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>> +# #
>>>>> +###############################################################################
>>>>> +
>>>>> +version="2025-01-11 - v14"
>>>>> +
>>>>> +############### Functions ###############
>>>>> +
>>>>> +source /usr/sbin/rpz-functions
>>>>> +
>>>>> +# create the config file for allow
>>>>> +make_allow_config () {
>>>>> + local theLog="${1:-yes}" # log default ON
>>>>> + local theConfig="/etc/unbound/local.d/00-rpz.conf" # output zone conf file
>>>>> + local theList="/var/ipfire/dns/rpz/allowlist" # input custom list of domains
>>>>> +
>>>>> + msg_log "info: rpz: make config file \"00-rpz.conf\""
>>>>> +
>>>>> + echo "server:
>>>>> + module-config: \"respip validator iterator\"" > "${theConfig}"
>>>>> +
>>>>> + # does allow list exist?
>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>> +
>>>>> + echo "rpz:
>>>>> + name: allow.rpz
>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>> + rpz-action-override: passthru
>>>>> + rpz-log: ${theLog}
>>>>> + rpz-log-name: allow
>>>>> + rpz-signal-nxdomain-ra: yes" >> "${theConfig}"
>>>>> +
>>>>> + fi
>>>>> +
>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>> + set_permissions "${theConfig}"
>>>>> +}
>>>>> +
>>>>> +# create the config file for block
>>>>> +make_block_config () {
>>>>> + local theLog="${1:-yes}" # log default ON
>>>>> + local theConfig="/etc/unbound/local.d/block.rpz.conf" # output zone conf file
>>>>> + local theList="/var/ipfire/dns/rpz/blocklist" # input custom list of domains
>>>>> +
>>>>> + msg_log "info: rpz: make config file \"block.rpz.conf\""
>>>>> +
>>>>> + # does block list exist?
>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>> +
>>>>> + echo "rpz:
>>>>> + name: block.rpz
>>>>> + zonefile: /etc/unbound/zonefiles/block.rpz
>>>>> + rpz-action-override: nxdomain
>>>>> + rpz-log: ${theLog}
>>>>> + rpz-log-name: block
>>>>> + rpz-signal-nxdomain-ra: yes" > "${theConfig}"
>>>>> +
>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>> + set_permissions "${theConfig}"
>>>>> + else
>>>>> + # no - trash the config file
>>>>> + rm --verbose /etc/unbound/local.d/block.rpz.conf
>>>>> + fi
>>>>> +}
>>>>> +
>>>>> +# create an RPZ file for allow or block
>>>>> +make_rpz_file () {
>>>>> + local theName="${1}" # allow or block
>>>>> + local theAction='.' # the default is nxdomain or block
>>>>> + local actionList
>>>>> +
>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>> + local theZonefile="/etc/unbound/zonefiles/${theName}.rpz" # output file for RPZ
>>>>> +
>>>>> + # does a list exist?
>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>> +
>>>>> + # for allow set to passthru
>>>>> + [[ "${theName}" == allow ]] && theAction='rpz-passthru.'
>>>>> +
>>>>> + # drop any extra "blanks" and add "CNAME <RPZ action>." to each line
>>>>> + actionList=$( awk '{$1=$1};1' "${theList}" |
>>>>> + sed "/^[^;].*[[:alnum:]]/ s|$| CNAME ${theAction}|" )
>>>>> +
>>>>> + msg_log "info: rpz: create zonefile for \"${theName}list\""
>>>>> +
>>>>> +echo "; Name: ${theName} list
>>>>> +; Last modified: $(date "+%Y-%m-%d at %H.%M.%S %Z")
>>>>> +;
>>>>> +; domains with actions list
>>>>> +;
>>>>> +${actionList}" > "${theZonefile}"
>>>>> +
>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>> + set_permissions "${theZonefile}"
>>>>> + # set-up allow/block list files
>>>>> + set_permissions "${theList}"
>>>>> + else
>>>>> + msg_log "info: rpz: the ${theList} is empty."
>>>>> +
>>>>> + rm --verbose "${theZonefile}" # trash the RPZ file
>>>>> + fi
>>>>> +}
>>>>> +
>>>>> +# check if allow/block list is valid
>>>>> +validate_list () {
>>>>> + local theName="${1}" # allow or block
>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>> +
>>>>> + # remove good:
>>>>> + # - properly formated domain names with or without leading wildcard
>>>>> + # - properly formated top level domain (TLD) names with wildcard
>>>>> + # - blank lines and comment lines
>>>>> + # remaining lines are considered "bad"
>>>>> + bad_lines=$( sed --regexp-extended \
>>>>> + '/^(\*\.)?([a-zA-Z0-9](([a-zA-Z0-9\-]){0,61}[a-zA-Z0-9])?\.)+([a-zA-Z]{2,}|xn--[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])$/d ;
>>>>> + /^(\*\.)([a-z]{2,61}|xn--[a-z0-9]{1,60})$/d ;
>>>>> + /^$/d ; /^;/d' "${theList}" )
>>>>> +
>>>>> + if [[ ! -z "${bad_lines}" ]] ; then
>>>>> + msg_log "error: rpz: invalid line(s) in ${theList}."
>>>>> + printf "%s\n" "bad line(s): ${bad_lines}"
>>>>> + exit 110
>>>>> + fi
>>>>> +}
>>>>> +
>>>>> +
>>>>> +############### Main ###############
>>>>> +
>>>>> +tagName="unbound"
>>>>> +
>>>>> +rpzName="${1}" # input RPZ name
>>>>> +
>>>>> +rpzLog="yes" # log default is yes
>>>>> +ucReload="yes" # reload default is yes
>>>>> +
>>>>> +while [[ $# -gt 0 ]] ; do
>>>>> + case "$1" in
>>>>> + --no-log ) rpzLog="no" ;;
>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>> + esac
>>>>> + shift # Shift after checking all the cases to get next option
>>>>> +done
>>>>> +
>>>>> +case "${rpzName}" in
>>>>> + # make a new allow or block rpz file
>>>>> +
>>>>> + allow )
>>>>> + validate_list 'allow' # is the allowlist valid?
>>>>> + make_allow_config "${rpzLog}"
>>>>> + make_rpz_file 'allow'
>>>>> + ;;
>>>>> +
>>>>> + allowblock )
>>>>> + validate_list 'allow' # is the list valid?
>>>>> + make_allow_config "${rpzLog}"
>>>>> + make_rpz_file 'allow'
>>>>> + ;&
>>>>> +
>>>>> + block )
>>>>> + validate_list 'block' # is the blocklist valid?
>>>>> + make_block_config "${rpzLog}"
>>>>> + make_rpz_file 'block'
>>>>> + ;;
>>>>> +
>>>>> + reload )
>>>>> + check_unbound_conf "${checkConf}"
>>>>> + ;;
>>>>> +
>>>>> + unbound-restart )
>>>>> + check_unbound_conf "${checkConf}"
>>>>> + unbound_restart
>>>>> + exit
>>>>> + ;;
>>>>> +
>>>>> + * )
>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>> + printf "Usage: $(basename "$0") <NAME> <OPTION> <OPTION>\n"
>>>>> + printf "Version: ${version}\n"
>>>>> + exit 108
>>>>> + ;;
>>>>> +esac
>>>>> +
>>>>> +unbound_control_reload "${ucReload}"
>>>>> +
>>>>> +exit
>>>>> diff --git a/config/rpz/rpz-metrics b/config/rpz/rpz-metrics
>>>>> new file mode 100755
>>>>> index 000000000..4d43e1629
>>>>> --- /dev/null
>>>>> +++ b/config/rpz/rpz-metrics
>>>>> @@ -0,0 +1,170 @@
>>>>> +#!/bin/bash
>>>>> +###############################################################################
>>>>> +# #
>>>>> +# IPFire.org - A linux based firewall #
>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>> +# #
>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>> +# #
>>>>> +###############################################################################
>>>>> +
>>>>> +version="2025-01-20 - v25"
>>>>> +
>>>>> +############### Main ###############
>>>>> +
>>>>> +weeks="2" # default to two message logs
>>>>> +sortBy="name" # default "by name"
>>>>> +rpzActive="enabled" # default "enabled only"
>>>>> +
>>>>> +while [[ $# -gt 0 ]] ; do
>>>>> + case "$1" in
>>>>> + --by-names | --by-name | name ) sortBy="name" ;;
>>>>> +
>>>>> + --by-hits | --by-hit | hits | hit ) sortBy="hit" ;;
>>>>> +
>>>>> + --by-lines | --by-line | lines | line ) sortBy="line" ;;
>>>>> +
>>>>> + --by-effect ) sortBy="effect" ;;
>>>>> +
>>>>> + --enabled-only ) rpzActive="enabled" ;;
>>>>> +
>>>>> + --active-all | --all | all ) rpzActive="all" ;;
>>>>> +
>>>>> + [0-9] | [0-9][0-9] ) weeks=$1 ;;
>>>>> + esac
>>>>> + shift # Shift after checking all the cases to get next option
>>>>> +done
>>>>> +
>>>>> +# get the list of message logs for N weeks
>>>>> +messageLogs=$( find /var/log/messages* -type f | sort --version-sort |
>>>>> + head -"${weeks}" )
>>>>> +
>>>>> +# get the list of RPZ names & counts from the message log(s)
>>>>> +rpzNameCount=$( for logf in ${messageLogs} ; do
>>>>> + zgrep --text --fixed-strings 'info: rpz: applied' "${logf}" |
>>>>> + awk '$10 ~ /\[\w*]/ { print $10 }' ;
>>>>> + done | sort | uniq --count )
>>>>> +
>>>>> +# flip results and remove brackets `[` and `]`
>>>>> +rpzNameCount=$( echo "${rpzNameCount}" |
>>>>> + awk '{ print $2, $1 }' |
>>>>> + sed --regexp-extended 's|^\[(.*)\]|\1|' )
>>>>> +
>>>>> +# grab only names
>>>>> +rpzNames=$( echo "${rpzNameCount}" | awk '{ print $1 }' )
>>>>> +
>>>>> +# get list of RPZ files
>>>>> +rpzFileList=$( find /etc/unbound/zonefiles -type f -iname "*.rpz" )
>>>>> +
>>>>> +# get basename of those files
>>>>> +rpzBaseNames=$( echo "${rpzFileList}" |
>>>>> + sed 's|/etc/unbound/zonefiles/||g ; s|\.rpz||g ;' )
>>>>> +
>>>>> +# add to rpzNames
>>>>> +rpzNames="${rpzNames}"$'\n'"${rpzBaseNames}"
>>>>> +
>>>>> +# drop duplicate names
>>>>> +rpzNames=$( echo "${rpzNames}" | sort --unique )
>>>>> +
>>>>> +# get line count for each RPZ
>>>>> +lineCount=$( echo "${rpzFileList}" | xargs wc -l )
>>>>> +
>>>>> +# get comment line count and blank line count for each RPZ
>>>>> +commentCount=$( echo "${rpzFileList}" | xargs grep --count -e "^$" -e "^;" )
>>>>> +
>>>>> +# get modified date each RPZ
>>>>> +modDateList=$( echo "${rpzFileList}" | xargs stat -c '%.10y %n' )
>>>>> +
>>>>> +ucListAuthZones=$( unbound-control list_auth_zones )
>>>>> +
>>>>> +# get width of RPZ names
>>>>> +pWidth=$( echo "${rpzNames}" | awk '{ print $1" " }' | wc -L )
>>>>> +pFormat="%-${pWidth}s %-8s %-8s %8s %12s %12s\n"
>>>>> +
>>>>> +# print title line
>>>>> +printf "${pFormat}" "name" "hits" "active" "lines" " hits/line" "last download"
>>>>> +printf -- "--------------"
>>>>> +
>>>>> +theResults=""
>>>>> +totalLines=0
>>>>> +totalHits=0
>>>>> +while read -r theName
>>>>> +do
>>>>> + printf -- "--" # pretend progress bar
>>>>> +
>>>>> + # is this RPZ list active?
>>>>> + theActive="disabled"
>>>>> + if grep --quiet "^${theName}\.rpz" <<< "${ucListAuthZones}"
>>>>> + then
>>>>> + theActive="enabled"
>>>>> + else
>>>>> + [[ "${rpzActive}" == enabled ]] && continue
>>>>> + fi
>>>>> +
>>>>> + # get hit count
>>>>> + theHits="0"
>>>>> + if output=$( grep "^${theName}\s" <<< "${rpzNameCount}" ) ; then
>>>>> + theHits=$( echo "${output}" | awk '{ print $2 }' )
>>>>> + totalHits=$(( totalHits + theHits ))
>>>>> + fi
>>>>> +
>>>>> + # get line count
>>>>> + theLines="n/a"
>>>>> + hitsPerLine="0"
>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${lineCount}" ) ; then
>>>>> + theLines=$( echo "${output}" | awk '{ print $1 }' )
>>>>> + totalLines=$(( totalLines + theLines ))
>>>>> +
>>>>> + if [[ "${theLines}" -gt 2 ]] ; then
>>>>> + hitsPerLine=$(( 100 * theHits / theLines ))
>>>>> + fi
>>>>> + fi
>>>>> +
>>>>> + # get modification date
>>>>> + theModDate="n/a"
>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${modDateList}" ) ; then
>>>>> + theModDate=$( echo "${output}" | awk '{ print $1 }' )
>>>>> + fi
>>>>> +
>>>>> + # add to results list
>>>>> + theResults+="${theName} ${theHits} ${theActive} ${theLines} ${hitsPerLine} ${theModDate}"$'\n'
>>>>> +
>>>>> +done <<< "${rpzNames}"
>>>>> +
>>>>> +case "${sortBy}" in
>>>>> + # sort by "active" then by "name"
>>>>> + name) sortArg=(-k3,3r -k1,1) ;;
>>>>> +
>>>>> + # sort by "active" then by "hits" then by "name"
>>>>> + hit) sortArg=(-k3,3r -k2,2nr -k1,1) ;;
>>>>> +
>>>>> + # sort by "active" then by "lines" then by "name"
>>>>> + line) sortArg=(-k3,3r -k4,4nr -k1,1) ;;
>>>>> +
>>>>> + # sort by "active" then by "effect" then by "name"
>>>>> + effect) sortArg=(-k3,3r -k5,5nr -k1,1) ;;
>>>>> +esac
>>>>> +
>>>>> +printf -- "--------------\n"
>>>>> +# remove blank lines, sort, print as columns
>>>>> +echo "${theResults}" |
>>>>> + awk '!/^[[:space:]]*$/' |
>>>>> + sort "${sortArg[@]}" |
>>>>> + awk --assign=width="${pWidth}" \
>>>>> + '{ printf "%-*s %-8s %-8s %8s %10s %% %12s\n", width, $1, $2, $3, $4, $5, $6 }'
>>>>> +
>>>>> +printf "${pFormat}" "" "=======" "" "========" "" ""
>>>>> +printf "${pFormat}" "Totals -->" "${totalHits}" "" "${totalLines}" "" ""
>>>>> +
>>>>> +exit
>>>>> diff --git a/config/rpz/rpz-sleep b/config/rpz/rpz-sleep
>>>>> new file mode 100755
>>>>> index 000000000..dd3603599
>>>>> --- /dev/null
>>>>> +++ b/config/rpz/rpz-sleep
>>>>> @@ -0,0 +1,58 @@
>>>>> +#!/bin/bash
>>>>> +###############################################################################
>>>>> +# #
>>>>> +# IPFire.org - A linux based firewall #
>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>> +# #
>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>> +# #
>>>>> +###############################################################################
>>>>> +
>>>>> +version="2024-08-16" # v05
>>>>> +
>>>>> +############### Functions ###############
>>>>> +
>>>>> +# send message to message log
>>>>> +msg_log () {
>>>>> + logger --tag "${tagName}" "$*"
>>>>> + if tty --silent ; then
>>>>> + echo "${tagName}:" "$*"
>>>>> + fi
>>>>> +}
>>>>> +
>>>>> +############### Main ###############
>>>>> +
>>>>> +tagName="unbound"
>>>>> +
>>>>> +sleepTime="${1:-5m}" # default to sleep for 5m (5 minutes)
>>>>> +
>>>>> +zoneList=$( unbound-control list_auth_zones | awk '{print $1}' )
>>>>> +
>>>>> +for zone in ${zoneList} ; do
>>>>> + printf "disable ${zone}\t"
>>>>> + unbound-control rpz_disable "${zone}"
>>>>> +done
>>>>> +
>>>>> +msg_log "info: rpz: disabled all zones for ${sleepTime}"
>>>>> +
>>>>> +sleep "${sleepTime}"
>>>>> +
>>>>> +for zone in ${zoneList} ; do
>>>>> + printf "enable ${zone}\t"
>>>>> + unbound-control rpz_enable "${zone}"
>>>>> +done
>>>>> +
>>>>> +msg_log "info: rpz: enabled all zones"
>>>>> +
>>>>> +exit
>>>>> diff --git a/config/rpz/rpz.de.pl b/config/rpz/rpz.de.pl
>>>>> new file mode 100644
>>>>> index 000000000..3770c6bb0
>>>>> --- /dev/null
>>>>> +++ b/config/rpz/rpz.de.pl
>>>>> @@ -0,0 +1,30 @@
>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>> +%tr = (%tr,
>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>> +'rpz apply' => 'Übernehmen',
>>>>> +'rpz cl allow enable' => 'Benutzerdefinierte Allowlist aktivieren:',
>>>>> +'rpz cl allow info' => 'Zugelassene Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>> +'rpz cl allow' => 'Benutzerdefinierte Allowlist',
>>>>> +'rpz cl block enable' => 'Benutzerdefinierte Blocklist aktivieren:',
>>>>> +'rpz cl block info' => 'Gesperrte Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>> +'rpz cl block' => 'Benutzerdefinierte Blocklist',
>>>>> +'rpz cl' => 'Benutzerdefinierte Listen',
>>>>> +'rpz exitcode 101' => 'Der Name enthält unzulässige Zeichen',
>>>>> +'rpz exitcode 102' => 'unbound-checkconf hat eine fehlerhafte Konfiguration ermittelt. Führen Sie das Kommando unbound-checkconf auf der Konsole aus, um weitere Informationen zu erhalten.',
>>>>> +'rpz exitcode 103' => 'Die benutzerdefinierte Allow-/Blocklist ist leer',
>>>>> +'rpz exitcode 104' => 'Ein Eintrag mit identischem Namen existiert bereits',
>>>>> +'rpz exitcode 105' => 'Die URL ist ungültig',
>>>>> +'rpz exitcode 106' => 'Eintrag kann nicht entfernt werden, der Name existiert nicht',
>>>>> +'rpz exitcode 107' => 'Der Name ist ungültig - nur "allow" oder "block" möglich',
>>>>> +'rpz exitcode 108' => 'Fehlende oder inkorrekte Parameter',
>>>>> +'rpz exitcode 109' => 'unbound-control reload ist fehlgeschlagen',
>>>>> +'rpz exitcode 110' => 'Die benutzerdefinierte Allow-/Blocklist enthält unzulässige Einträge',
>>>>> +'rpz exitcode 201' => 'Die Anmerkung enthält unzulässige Zeichen',
>>>>> +'rpz exitcode 202' => 'Ungültiger Eintrag in der benutzerdefinierten Allowlist, Zeile ',
>>>>> +'rpz exitcode 203' => 'Ungültiger Eintrag in der benutzerdefinierten Blocklist, Zeile ',
>>>>> +'rpz exitcode 204' => 'Ausgewählter Eintrag existiert nicht: ',
>>>>> +'rpz zf editor' => 'Zonendatei-Eintrag bearbeiten',
>>>>> +'rpz zf imported' => '(importiert aus rpz-config)',
>>>>> +'rpz zf remark info' => 'Erlaubte Zeichen sind a-z, A-Z, 0-9 und Unterstriche',
>>>>> +'rpz zf' => 'Zonendateien',
>>>>> +);
>>>>> diff --git a/config/rpz/rpz.en.pl b/config/rpz/rpz.en.pl
>>>>> new file mode 100644
>>>>> index 000000000..0720a8940
>>>>> --- /dev/null
>>>>> +++ b/config/rpz/rpz.en.pl
>>>>> @@ -0,0 +1,30 @@
>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>> +%tr = (%tr,
>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>> +'rpz apply' => 'Apply',
>>>>> +'rpz cl allow enable' => 'Enable custom allowlist:',
>>>>> +'rpz cl allow info' => 'Allowed domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>> +'rpz cl allow' => 'Custom allowlist',
>>>>> +'rpz cl block enable' => 'Enable custom blocklist:',
>>>>> +'rpz cl block info' => 'Blocked domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>> +'rpz cl block' => 'Custom blocklist',
>>>>> +'rpz cl' => 'Custom lists',
>>>>> +'rpz exitcode 101' => 'the NAME is not valid',
>>>>> +'rpz exitcode 102' => 'unbound-checkconf found invalid configuration. In the Terminal run the command unbound-checkconf for more information',
>>>>> +'rpz exitcode 103' => 'the allow/block list is empty',
>>>>> +'rpz exitcode 104' => 'duplicate - NAME already exists',
>>>>> +'rpz exitcode 105' => 'the URL is not valid',
>>>>> +'rpz exitcode 106' => 'cannot remove the NAME does not exist',
>>>>> +'rpz exitcode 107' => 'the NAME is not valid - "allow" or "block" only',
>>>>> +'rpz exitcode 108' => 'missing or incorrect parameter',
>>>>> +'rpz exitcode 109' => 'unbound-control reload failed',
>>>>> +'rpz exitcode 110' => 'custom Allowlist/Blocklist contains invalid entries',
>>>>> +'rpz exitcode 201' => 'the REMARK is not valid',
>>>>> +'rpz exitcode 202' => 'invalid entry in allowlist, line ',
>>>>> +'rpz exitcode 203' => 'invalid entry in blocklist, line ',
>>>>> +'rpz exitcode 204' => 'Selected entry does not exist: ',
>>>>> +'rpz zf editor' => 'Edit zonefiles entry',
>>>>> +'rpz zf imported' => '(imported from rpz-config)',
>>>>> +'rpz zf remark info' => 'Valid characters are a-z, A-Z, 0-9 and underscore.',
>>>>> +'rpz zf' => 'Zonefiles',
>>>>> +);
>>>>> diff --git a/config/rpz/rpz.es.pl b/config/rpz/rpz.es.pl
>>>>> new file mode 100644
>>>>> index 000000000..98628e4aa
>>>>> --- /dev/null
>>>>> +++ b/config/rpz/rpz.es.pl
>>>>> @@ -0,0 +1,30 @@
>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>> +%tr = (%tr,
>>>>> +'rpz' => 'Zonas de política de respuesta (RPZ)',
>>>>> +'rpz apply' => 'Aplicar',
>>>>> +'rpz cl allow enable' => 'Habilitar la lista blanca personalizada:',
>>>>> +'rpz cl allow info' => 'Dominio permitido (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>> +'rpz cl allow' => 'Lista blanca personalizada',
>>>>> +'rpz cl block enable' => 'Habilitar la lista negra personalizada:',
>>>>> +'rpz cl block info' => 'Dominio bloqueado (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>> +'rpz cl block' => 'Lista negra personalizada',
>>>>> +'rpz cl' => 'Lista personalizada',
>>>>> +'rpz exitcode 101' => 'El NOMBRE no es válido',
>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha encontrado una configuración no válida. Desde Terminal, ejecute el comando unbound-checkconf para mayor información',
>>>>> +'rpz exitcode 103' => 'La lista de permitidos/bloqueados está vacía',
>>>>> +'rpz exitcode 104' => 'duplicado - NOMBRE ya existe',
>>>>> +'rpz exitcode 105' => 'la URL no es válida',
>>>>> +'rpz exitcode 106' => 'no es posible eliminar el NOMBRE que no existe',
>>>>> +'rpz exitcode 107' => 'el NOMBRE no es válido - sólo "permitir" o "bloquear"',
>>>>> +'rpz exitcode 108' => 'parámetro faltante o incorrecto',
>>>>> +'rpz exitcode 109' => 'Error al recargar unbound-control',
>>>>> +'rpz exitcode 110' => 'la Lista blanca/Lista negra personalizada contiene entradas no válidas',
>>>>> +'rpz exitcode 201' => 'el COMENTARIO no es válido',
>>>>> +'rpz exitcode 202' => 'entrada no válida en la lista blanca, línea ',
>>>>> +'rpz exitcode 203' => 'entrada no válida en la lista negra, línea ',
>>>>> +'rpz exitcode 204' => 'La entrada seleccionada no existe: ',
>>>>> +'rpz zf editor' => 'Editar la entrada de archivos de zona',
>>>>> +'rpz zf imported' => '(importado de rpz-config)',
>>>>> +'rpz zf remark info' => 'Los caracteres válidos son a-z, A-Z, 0-9 y guión bajo',
>>>>> +'rpz zf' => 'Archivos de zona',
>>>>> +);
>>>>> diff --git a/config/rpz/rpz.fr.pl b/config/rpz/rpz.fr.pl
>>>>> new file mode 100644
>>>>> index 000000000..f35f3c2d0
>>>>> --- /dev/null
>>>>> +++ b/config/rpz/rpz.fr.pl
>>>>> @@ -0,0 +1,30 @@
>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>> +%tr = (%tr,
>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>> +'rpz apply' => 'Appliquer',
>>>>> +'rpz cl allow enable' => 'Activer la liste d\'autorisations personnalisée:',
>>>>> +'rpz cl allow info' => 'Domaines autorisés (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>> +'rpz cl allow' => 'Liste d\'autorisations personnalisée',
>>>>> +'rpz cl block enable' => 'Activer la liste de blocage personnalisée:',
>>>>> +'rpz cl block info' => 'Domaines bloqués (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>> +'rpz cl block' => 'liste de blocage personnalisé',
>>>>> +'rpz cl' => 'Listes personnalisées',
>>>>> +'rpz exitcode 101' => 'le NOM n\'est pas valide',
>>>>> +'rpz exitcode 102' => 'unbound-checkconf configuration non valide trouvée. Dans le terminal, exécutez la commande unbound-checkconf pour plus d\'informations',
>>>>> +'rpz exitcode 103' => 'la liste autoriser/bloquer est vide',
>>>>> +'rpz exitcode 104' => 'le NOM existe déjà',
>>>>> +'rpz exitcode 105' => 'L\'URL n\'est pas valide',
>>>>> +'rpz exitcode 106' => 'impossible de supprimer le NOM n\'existe pas',
>>>>> +'rpz exitcode 107' => 'le NOM n\'est pas valide - « autoriser » ou « bloquer » seulement',
>>>>> +'rpz exitcode 108' => 'paramètre manquant ou incorrect',
>>>>> +'rpz exitcode 109' => 'unbound-control rechargement échoué',
>>>>> +'rpz exitcode 110' => 'la liste autoriser/bloquer contient des entrées non valides',
>>>>> +'rpz exitcode 201' => 'la REMARQUE n\'est pas valable',
>>>>> +'rpz exitcode 202' => 'entrée non valide dans la liste d\'autorisation, ligne ',
>>>>> +'rpz exitcode 203' => 'entrée non valide dans la liste de blocs, ligne ',
>>>>> +'rpz exitcode 204' => 'L\'entrée sélectionnée n\'existe pas: ',
>>>>> +'rpz zf editor' => 'Modifier l\'entrée Fichiers Zone',
>>>>> +'rpz zf imported' => '(importé de rpz-config)',
>>>>> +'rpz zf remark info' => 'Les caractères valides sont a-z, A-Z, 0-9 et soulignement.',
>>>>> +'rpz zf' => 'Fichiers Zone',
>>>>> +);
>>>>> diff --git a/config/rpz/rpz.it.pl b/config/rpz/rpz.it.pl
>>>>> new file mode 100644
>>>>> index 000000000..ee81605c9
>>>>> --- /dev/null
>>>>> +++ b/config/rpz/rpz.it.pl
>>>>> @@ -0,0 +1,30 @@
>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>> +%tr = (%tr,
>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>> +'rpz apply' => 'Applica',
>>>>> +'rpz cl allow enable' => 'Abilita la Whitelist personalizzata:',
>>>>> +'rpz cl allow info' => 'Domini consentiti (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>> +'rpz cl allow' => 'Whitelist personalizzata',
>>>>> +'rpz cl block enable' => 'Abilita la Blacklist personalizzata:',
>>>>> +'rpz cl block info' => 'Domini bloccati (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>> +'rpz cl block' => 'Blacklist personalizzata',
>>>>> +'rpz cl' => 'Liste personalizzate',
>>>>> +'rpz exitcode 101' => 'il NOME non è valido',
>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha trovato una configurazione non valida. Dal Terminale esegui il comando unbound-checkconf per maggiori informazioni',
>>>>> +'rpz exitcode 103' => 'l\'elenco consentiti/bloccati è vuoto',
>>>>> +'rpz exitcode 104' => 'duplicato - NAME esiste di già',
>>>>> +'rpz exitcode 105' => 'l\'URL non è valido',
>>>>> +'rpz exitcode 106' => 'non è possibile rimuovere il NOME non esiste',
>>>>> +'rpz exitcode 107' => 'il NOME non è valido - solo "consenti" o "blocca"',
>>>>> +'rpz exitcode 108' => 'parametro mancante o non corretto',
>>>>> +'rpz exitcode 109' => 'ricaricamento del controllo non associato non riuscito',
>>>>> +'rpz exitcode 110' => 'la Whitelist/Blacklist personalizzata contiene voci non valide',
>>>>> +'rpz exitcode 201' => 'l"OSSERVAZIONE non è valida',
>>>>> +'rpz exitcode 202' => 'voce non valida nella Whitelist, riga ',
>>>>> +'rpz exitcode 203' => 'voce non valida nella Blacklist, riga ',
>>>>> +'rpz exitcode 204' => 'La voce selezionata non esiste: ',
>>>>> +'rpz zf editor' => 'Modifica la voce dei file di zona',
>>>>> +'rpz zf imported' => '(importato da rpz-config)',
>>>>> +'rpz zf remark info' => 'I caratteri validi sono a-z, A-Z, 0-9 e trattino basso',
>>>>> +'rpz zf' => 'Zonefiles',
>>>>> +);
>>>>> diff --git a/config/rpz/rpz.tr.pl b/config/rpz/rpz.tr.pl
>>>>> new file mode 100644
>>>>> index 000000000..00226e192
>>>>> --- /dev/null
>>>>> +++ b/config/rpz/rpz.tr.pl
>>>>> @@ -0,0 +1,30 @@
>>>>> +# I�in eklendi Ayriyeten Response Policy Zone (RPZ)
>>>>> +%tr = (%tr,
>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>> +'rpz apply' => 'Uygulamak',
>>>>> +'rpz cl allow enable' => '�zel Etkinlestir allowlist:',
>>>>> +'rpz cl allow info' => 'Izin verilmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>> +'rpz cl allow' => 'Etkinlestir allowlist',
>>>>> +'rpz cl block enable' => '�zel Etkinlestir blocklist:',
>>>>> +'rpz cl block info' => 'Engellenmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>> +'rpz cl block' => 'Engellenmis blocklist',
>>>>> +'rpz cl' => 'Engellenmis lists',
>>>>> +'rpz exitcode 101' => 'NAME ge�erli degil',
>>>>> +'rpz exitcode 102' => 'unbound-checkconf ge�ersiz yapilandirma bulundu. Terminalde daha fazla bilgi i�in Unbound-checkConf komutunu �alistirin',
>>>>> +'rpz exitcode 103' => 'allow/block liste bos',
>>>>> +'rpz exitcode 104' => 'kopyalamak - NAME zaten var',
>>>>> +'rpz exitcode 105' => 'URL ge�erli degil',
>>>>> +'rpz exitcode 106' => '�ikarilamiyor NAME yok',
>>>>> +'rpz exitcode 107' => 'NAME ge�erli degil - "allow" veya "block" yalniz',
>>>>> +'rpz exitcode 108' => 'Parametre eksik veya yanlis',
>>>>> +'rpz exitcode 109' => 'unbound-control basarisiz',
>>>>> +'rpz exitcode 110' => 'Engellenmis Allowlist/Blocklist ge�ersiz girisler i�erir',
>>>>> +'rpz exitcode 201' => 'REMARK ge�erli degil',
>>>>> +'rpz exitcode 202' => 'Ge�ersiz giris allowlist, line ',
>>>>> +'rpz exitcode 203' => 'Ge�ersiz giris blocklist, line ',
>>>>> +'rpz exitcode 204' => 'Se�ilen giris yok: ',
>>>>> +'rpz zf editor' => 'yazimlamak zonefiles giris',
>>>>> +'rpz zf imported' => '(ithal edildi rpz-config)',
>>>>> +'rpz zf remark info' => 'Ge�erli karakterler a-z, A-Z, 0-9ve alt�st.',
>>>>> +'rpz zf' => 'Zonefiles',
>>>>> +);
>>>>> diff --git a/html/cgi-bin/rpz.cgi b/html/cgi-bin/rpz.cgi
>>>>> new file mode 100644
>>>>> index 000000000..a821c92ac
>>>>> --- /dev/null
>>>>> +++ b/html/cgi-bin/rpz.cgi
>>>>> @@ -0,0 +1,923 @@
>>>>> +#!/usr/bin/perl
>>>>> +###############################################################################
>>>>> +# #
>>>>> +# IPFire.org - A linux based firewall #
>>>>> +# Copyright (C) 2005-2024 IPFire Team <info(a)ipfire.org> #
>>>>> +# #
>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>> +# #
>>>>> +###############################################################################
>>>>> +
>>>>> +use strict;
>>>>> +use Scalar::Util qw(looks_like_number);
>>>>> +
>>>>> +# debugging
>>>>> +#use warnings;
>>>>> +#use CGI::Carp 'fatalsToBrowser';
>>>>> +#use Data::Dumper;
>>>>> +
>>>>> +require '/var/ipfire/general-functions.pl';
>>>>> +require "${General::swroot}/lang.pl";
>>>>> +require "${General::swroot}/header.pl";
>>>>> +
>>>>> +###--- Extra HTML ---###
>>>>> +my $extraHead = <<END
>>>>> +<style>
>>>>> + /* alternating row background */
>>>>> + .tbl tr:nth-child(2n+2) {
>>>>> + background-color: var(--color-light-grey);
>>>>> + }
>>>>> + .tbl tr:nth-child(2n+3) {
>>>>> + background-color: var(--color-grey);
>>>>> + }
>>>>> + /* text styles */
>>>>> + .tbl th:not(:last-child) {
>>>>> + text-align: left;
>>>>> + }
>>>>> + div.right {
>>>>> + text-align: right;
>>>>> + margin-top: 0.5em;
>>>>> + }
>>>>> + /* customlist input */
>>>>> + textarea.domainlist {
>>>>> + margin: 0.5em 0;
>>>>> + resize: vertical;
>>>>> + min-height: 10em;
>>>>> + overflow: auto;
>>>>> + white-space: pre;
>>>>> + }
>>>>> + button[type=submit]:disabled {
>>>>> + opacity: 0.6;
>>>>> + }
>>>>> +</style>
>>>>> +END
>>>>> +;
>>>>> +###--- End of extra HTML ---###
>>>>> +
>>>>> +
>>>>> +### Settings ###
>>>>> +
>>>>> +# Request DNS service reload after configuration change
>>>>> +my $RPZ_RELOAD_FLAG = "${General::swroot}/dns/rpz/reload.flag";
>>>>> +
>>>>> +# Configuration file for all available zonefiles
>>>>> +# Format: index, name (unique), enabled (on/off), URL, remark
>>>>> +my $ZONEFILES_CONF = "${General::swroot}/dns/rpz/zonefiles.conf";
>>>>> +
>>>>> +# Configuration file for custom lists
>>>>> +# IDs: 0=allowlist, 1=blocklist, 2=options (allow/block enabled)
>>>>> +my $CUSTOMLISTS_CONF = "${General::swroot}/dns/rpz/customlists.conf";
>>>>> +
>>>>> +# Export custom lists to rpz-config
>>>>> +my $RPZ_ALLOWLIST = "${General::swroot}/dns/rpz/allowlist";
>>>>> +my $RPZ_BLOCKLIST = "${General::swroot}/dns/rpz/blocklist";
>>>>> +
>>>>> +
>>>>> +### Preparation ###
>>>>> +
>>>>> +# Create missing config files
>>>>> +unless(-f $ZONEFILES_CONF) { &General::system('touch', "$ZONEFILES_CONF"); }
>>>>> +unless(-f $CUSTOMLISTS_CONF) { &General::system('touch', "$CUSTOMLISTS_CONF"); }
>>>>> +
>>>>> +
>>>>> +## Global gui data
>>>>> +my $errormessage = "";
>>>>> +
>>>>> +## Global configuration data
>>>>> +my %zonefiles = ();
>>>>> +my %customlists = ();
>>>>> +&_zonefiles_load();
>>>>> +&_customlists_load();
>>>>> +
>>>>> +## Global CGI form data
>>>>> +my %cgiparams = ();
>>>>> +&Header::getcgihash(\%cgiparams);
>>>>> +
>>>>> +my $action = $cgiparams{'ACTION'} // 'NONE';
>>>>> +my $action_key = $cgiparams{'KEY'} // ''; # entry being edited, empty = none/new
>>>>> +
>>>>> +
>>>>> +###--- Process form actions ---###
>>>>> +
>>>>> +# Zonefiles action: Check whether the requested entry exists
>>>>> +if((substr($action, 0, 3) eq 'ZF_') && ($action_key)) {
>>>>> + unless(defined $zonefiles{$action_key}) {
>>>>> + $errormessage = &_rpz_error_tr(204, $action_key);
>>>>> + $action = 'NONE';
>>>>> + }
>>>>> +}
>>>>> +
>>>>> +## Perform actions
>>>>> +if($action eq 'ZF_SAVE') { ## Save new or modified zonefiles entry
>>>>> + if(&_action_zf_save()) {
>>>>> + $action = 'NONE'; # success, return to main page
>>>>> + &_http_prg_redirect();
>>>>> + } else {
>>>>> + $action = 'ZF_EDIT'; # error occured, keep editing
>>>>> + }
>>>>> +
>>>>> +} elsif($action eq 'ZF_TOGGLE') { ## Toggle on/off
>>>>> + if(&_action_zf_toggle()) {
>>>>> + $action = 'NONE';
>>>>> + &_http_prg_redirect();
>>>>> + }
>>>>> +
>>>>> +} elsif($action eq 'ZF_REMOVE') { ## Remove entry
>>>>> + if(&_action_zf_remove()) {
>>>>> + $action = 'NONE';
>>>>> + &_http_prg_redirect();
>>>>> + }
>>>>> +
>>>>> +} elsif($action eq 'CL_SAVE') { ## Save custom lists
>>>>> + if(&_action_cl_save()) {
>>>>> + $action = 'NONE';
>>>>> + &_http_prg_redirect();
>>>>> + }
>>>>> +
>>>>> +} elsif($action eq 'RPZ_RELOAD') { ## Reload dns configuration
>>>>> + if(&_action_rpz_reload()) {
>>>>> + $action = 'NONE';
>>>>> + &_http_prg_redirect();
>>>>> + }
>>>>> +
>>>>> +} elsif($action eq 'UNB_RESTART') { ## Restart unbound service
>>>>> + if(&_action_unb_restart()) {
>>>>> + $action = 'NONE';
>>>>> + &_http_prg_redirect();
>>>>> + }
>>>>> +
>>>>> +}
>>>>> +
>>>>> +
>>>>> +###--- Start GUI ---###
>>>>> +
>>>>> +## Start http output
>>>>> +&Header::showhttpheaders();
>>>>> +
>>>>> +# Start HTML
>>>>> +&Header::openpage($Lang::tr{'rpz'}, 1, $extraHead);
>>>>> +&Header::openbigbox('100%', 'left', '');
>>>>> +
>>>>> +# Show error messages
>>>>> +if($errormessage) {
>>>>> + &_print_message($errormessage);
>>>>> +}
>>>>> +
>>>>> +# Handle zonefile add/edit mode
>>>>> +if($action eq "ZF_EDIT") {
>>>>> + &_print_zonefile_editor();
>>>>> +
>>>>> + # Finalize page and exit cleanly
>>>>> + &Header::closebigbox();
>>>>> + &Header::closepage();
>>>>> + exit(0);
>>>>> +}
>>>>> +
>>>>> +# Show gui elements
>>>>> +&_print_zonefiles();
>>>>> +&_print_customlists();
>>>>> +&_print_gui_extras();
>>>>> +
>>>>> +&Header::closebigbox();
>>>>> +&Header::closepage();
>>>>> +
>>>>> +###--- End of GUI ---###
>>>>> +
>>>>> +
>>>>> +###--- Internal configuration file functions ---###
>>>>> +
>>>>> +# Load all available zonefiles from rpz-config and the internal configuration
>>>>> +sub _zonefiles_load {
>>>>> + # Clean start
>>>>> + %zonefiles = ();
>>>>> +
>>>>> + # Source 1: Get the currently enabled zonefiles from rpz-config (expected format [name]=[URL])
>>>>> + my @enabled_files = &General::system_output('/usr/sbin/rpz-config', 'list');
>>>>> +
>>>>> + foreach my $row (@enabled_files) {
>>>>> + chomp($row);
>>>>> +
>>>>> + # Use regex instead of split() to skip non-matching lines
>>>>> + next unless($row =~ /^(\w+)=(.+)$/);
>>>>> + my ($name, $url) = ($1, $2);
>>>>> +
>>>>> + # Unique names are already guaranteed by rpz-config
>>>>> + if(&_rpz_validate_zonefile($name, $url, '', 0) == 0) {
>>>>> + # Populate global data hash, mark all found entries as enabled
>>>>> + my %entry = ('enabled' => 'on',
>>>>> + 'url' => $url,
>>>>> + 'remark' => $Lang::tr{'rpz zf imported'});
>>>>> +
>>>>> + $zonefiles{$name} = \%entry;
>>>>> + }
>>>>> + }
>>>>> +
>>>>> + # Source 2: Get additional data and disabled entries from configuration file
>>>>> + my %configured_files = ();
>>>>> + &General::readhasharray($ZONEFILES_CONF, \%configured_files);
>>>>> +
>>>>> + foreach my $row (values (%configured_files)) {
>>>>> + my ($name, $enabled, $url, $remark) = @$row;
>>>>> + $remark //= "";
>>>>> +
>>>>> + next unless($name);
>>>>> +
>>>>> + # Check whether this row belongs to an entry already imported from rpz-config
>>>>> + if(defined $zonefiles{$name}) {
>>>>> + # Existing entry, only merge additional data
>>>>> + $zonefiles{$name}{'remark'} = $remark;
>>>>> + } else {
>>>>> + # Skip entry if it is marked as enabled but not found by rpz-config. It was then deleted manually
>>>>> + if($enabled ne 'on') {
>>>>> + # Populate global data hash
>>>>> + my %entry = ('enabled' => 'off',
>>>>> + 'url' => $url // "",
>>>>> + 'remark' => $remark);
>>>>> +
>>>>> + $zonefiles{$name} = \%entry;
>>>>> + }
>>>>> + }
>>>>> + }
>>>>> +}
>>>>> +
>>>>> +# Save internal zonefiles configuration
>>>>> +sub _zonefiles_save_conf {
>>>>> + my $index = 0;
>>>>> + my %export = ();
>>>>> +
>>>>> + # Loop trough all zonefiles and create "hasharray" type export
>>>>> + foreach my $name (keys %zonefiles) {
>>>>> + my @entry = ($name,
>>>>> + $zonefiles{$name}{'enabled'},
>>>>> + $zonefiles{$name}{'url'},
>>>>> + $zonefiles{$name}{'remark'});
>>>>> +
>>>>> + $export{$index++} = \@entry;
>>>>> + }
>>>>> +
>>>>> + &General::writehasharray($ZONEFILES_CONF, \%export);
>>>>> +}
>>>>> +
>>>>> +# Load custom lists from rpz-config and the internal configuration
>>>>> +sub _customlists_load {
>>>>> + # Clean start
>>>>> + %customlists = ();
>>>>> +
>>>>> + # Load configuration file
>>>>> + my %lists_conf = ();
>>>>> + &General::readhasharray($CUSTOMLISTS_CONF, \%lists_conf);
>>>>> +
>>>>> + # Get list options, enabled by default to start import
>>>>> + $customlists{'allow'}{'enabled'} = $lists_conf{2}[0] // 'on';
>>>>> + $customlists{'block'}{'enabled'} = $lists_conf{2}[1] // 'on';
>>>>> +
>>>>> + # Import enabled list from rpz-config, otherwise retrieve stored or empty list from configuration file
>>>>> + if($customlists{'allow'}{'enabled'} eq 'on') {
>>>>> + &_customlist_import('allow', $RPZ_ALLOWLIST);
>>>>> + } else {
>>>>> + $customlists{'allow'}{'list'} = $lists_conf{0} // [];
>>>>> + }
>>>>> + if($customlists{'block'}{'enabled'} eq 'on') {
>>>>> + &_customlist_import('block', $RPZ_BLOCKLIST);
>>>>> + } else {
>>>>> + $customlists{'block'}{'list'} = $lists_conf{1} // [];
>>>>> + }
>>>>> +}
>>>>> +
>>>>> +# Save internal custom lists configuration
>>>>> +sub _customlists_save_conf {
>>>>> + my %export = ();
>>>>> +
>>>>> + # Match IDs with import function
>>>>> + $export{0} = $customlists{'allow'}{'list'};
>>>>> + $export{1} = $customlists{'block'}{'list'};
>>>>> + $export{2} = [$customlists{'allow'}{'enabled'}, $customlists{'block'}{'enabled'}];
>>>>> +
>>>>> + &General::writehasharray($CUSTOMLISTS_CONF, \%export);
>>>>> +}
>>>>> +
>>>>> +# Import a custom list from plain file, returns empty list if file is missing
>>>>> +sub _customlist_import {
>>>>> + my ($listname, $filename) = @_;
>>>>> + my @list = ();
>>>>> +
>>>>> + # File exists, load and check all lines
>>>>> + if(-f $filename) {
>>>>> + open(my $FH, '<', $filename) or die "Can't read $filename: $!";
>>>>> + while(my $line = <$FH>) {
>>>>> + chomp($line);
>>>>> + push(@list, $line);
>>>>> + }
>>>>> + close($FH);
>>>>> +
>>>>> + # Clean up imported data
>>>>> + &_rpz_validate_customlist(\@list, 1);
>>>>> + }
>>>>> +
>>>>> + $customlists{$listname}{'list'} = \@list;
>>>>> +}
>>>>> +
>>>>> +# Export a custom list to plain file or clear file if list is disabled
>>>>> +sub _customlist_export {
>>>>> + my ($listname, $filename) = @_;
>>>>> + return unless(defined $customlists{$listname});
>>>>> +
>>>>> + # Write enabled domain list to file, otherwise save empty file
>>>>> + open(my $FH, '>', $filename) or die "Can't write $filename: $!";
>>>>> +
>>>>> + if($customlists{$listname}{'enabled'} eq 'on') {
>>>>> + foreach my $line (@{$customlists{$listname}{'list'}}) {
>>>>> + print $FH "$line\n";
>>>>> + }
>>>>> + } else {
>>>>> + print $FH "; Note: This list is currently disabled by $ENV{'SCRIPT_NAME'}\n";
>>>>> + }
>>>>> +
>>>>> + close($FH);
>>>>> +}
>>>>> +
>>>>> +
>>>>> +###--- Internal gui functions ---###
>>>>> +
>>>>> +# Show simple message box
>>>>> +sub _print_message {
>>>>> + my ($message, $title) = @_;
>>>>> + $title ||= $Lang::tr{'error messages'};
>>>>> +
>>>>> + &Header::openbox('100%', 'left', $title);
>>>>> + print "<span>$message</span>";
>>>>> + &Header::closebox();
>>>>> +}
>>>>> +
>>>>> +# Show all zone files and related gui elements
>>>>> +sub _print_zonefiles {
>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf'});
>>>>> +
>>>>> + print <<END
>>>>> +<table class="tbl" width="100%">
>>>>> + <tr>
>>>>> + <th>$Lang::tr{'name'}</th>
>>>>> + <th>URL</th>
>>>>> + <th>$Lang::tr{'remark'}</th>
>>>>> + <th colspan="3">$Lang::tr{'action'}</th>
>>>>> + </tr>
>>>>> +END
>>>>> +;
>>>>> +
>>>>> + # Sort zonefiles by name and loop trough all entries
>>>>> + foreach my $name (sort keys %zonefiles) {
>>>>> +
>>>>> + # Toggle button label translation
>>>>> + my $toggle_tr = ($zonefiles{$name}{'enabled'} eq 'on') ? $Lang::tr{'click to disable'} : $Lang::tr{'click to enable'};
>>>>> +
>>>>> + print <<END
>>>>> + <tr>
>>>>> + <td>$name</td>
>>>>> + <td>$zonefiles{$name}{'url'}</td>
>>>>> + <td>$zonefiles{$name}{'remark'}</td>
>>>>> +
>>>>> + <td align="center" width="5%">
>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>> + <input type="hidden" name="ACTION" value="ZF_TOGGLE">
>>>>> + <input type="image" src="/images/$zonefiles{$name}{'enabled'}.gif" title="$toggle_tr" alt="$toggle_tr">
>>>>> + </form>
>>>>> + </td>
>>>>> + <td align="center" width="5%">
>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>> + <input type="hidden" name="ACTION" value="ZF_EDIT">
>>>>> + <input type="image" src="/images/edit.gif" title="$Lang::tr{'edit'}" alt="$Lang::tr{'edit'}">
>>>>> + </form>
>>>>> + </td>
>>>>> + <td align="center" width="5%">
>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>> + <input type="hidden" name="ACTION" value="ZF_REMOVE">
>>>>> + <input type="image" src="/images/delete.gif" title="$Lang::tr{'remove'}" alt="$Lang::tr{'remove'}">
>>>>> + </form>
>>>>> + </td>
>>>>> + </tr>
>>>>> +END
>>>>> +;
>>>>> + }
>>>>> +
>>>>> + # Disable reload button if not needed
>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>> +
>>>>> + print <<END
>>>>> +</table>
>>>>> +
>>>>> +<div class="right">
>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>> + <input type="hidden" name="KEY" value="">
>>>>> + <button type="submit" name="ACTION" value="ZF_EDIT">$Lang::tr{'add'}</button>
>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>> + </form>
>>>>> +</div>
>>>>> +END
>>>>> +;
>>>>> +
>>>>> + &Header::closebox();
>>>>> +}
>>>>> +
>>>>> +# Show zonefiles entry editor
>>>>> +sub _print_zonefile_editor {
>>>>> +
>>>>> + # Key specified: Edit existing entry
>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>> + # Load data to be edited, but don't override already present values (allows user to edit after error)
>>>>> + $cgiparams{'ZF_NAME'} //= $action_key;
>>>>> + $cgiparams{'ZF_URL'} //= $zonefiles{$action_key}{'url'};
>>>>> + $cgiparams{'ZF_REMARK'} //= $zonefiles{$action_key}{'remark'};
>>>>> + }
>>>>> +
>>>>> + # Fallback to empty form
>>>>> + $cgiparams{'ZF_NAME'} //= "";
>>>>> + $cgiparams{'ZF_URL'} //= "";
>>>>> + $cgiparams{'ZF_REMARK'} //= "";
>>>>> +
>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf editor'});
>>>>> +
>>>>> + print <<END
>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>> +<input type="hidden" name="KEY" value="$action_key">
>>>>> +<table width="100%">
>>>>> + <tr>
>>>>> + <td width="20%">$Lang::tr{'name'}: <img src="/blob.gif" alt="*"></td>
>>>>> + <td><input type="text" name="ZF_NAME" value="$cgiparams{'ZF_NAME'}" size="40" maxlength="32" title="$Lang::tr{'rpz zf remark info'}" pattern="[a-zA-Z0-9_]{1,32}" required></td>
>>>>> + </tr>
>>>>> + <tr>
>>>>> + <td width="20%">URL: <img src="/blob.gif" alt="*"></td>
>>>>> + <td><input type="url" name="ZF_URL" value="$cgiparams{'ZF_URL'}" size="40" maxlength="128" required></td>
>>>>> + </tr>
>>>>> + <tr>
>>>>> + <td width="20%">$Lang::tr{'remark'}:</td>
>>>>> + <td><input type="text" name="ZF_REMARK" value="$cgiparams{'ZF_REMARK'}" size="40" maxlength="32"></td>
>>>>> + </tr>
>>>>> + <tr>
>>>>> + <td colspan="2"><hr></td>
>>>>> + </tr>
>>>>> + <tr>
>>>>> + <td width="55%"><img src="/blob.gif" alt="*"> $Lang::tr{'required field'}</td>
>>>>> + <td align="right"><button type="submit" name="ACTION" value="ZF_SAVE">$Lang::tr{'save'}</button></td>
>>>>> + </tr>
>>>>> +</table>
>>>>> +</form>
>>>>> +
>>>>> +<div class="right">
>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>> + <button type="submit" name="ACTION" value="NONE">$Lang::tr{'back'}</button>
>>>>> + </form>
>>>>> +</div>
>>>>> +END
>>>>> +;
>>>>> +
>>>>> + &Header::closebox();
>>>>> +}
>>>>> +
>>>>> +# Show custom allow/block files and related gui elements
>>>>> +sub _print_customlists {
>>>>> +
>>>>> + # Load lists from config, unless they are currently being edited
>>>>> + if($action ne 'CL_SAVE') {
>>>>> + $cgiparams{'ALLOW_LIST'} = join("\n", @{$customlists{'allow'}{'list'}});
>>>>> + $cgiparams{'BLOCK_LIST'} = join("\n", @{$customlists{'block'}{'list'}});
>>>>> +
>>>>> + $cgiparams{'ALLOW_ENABLED'} = ($customlists{'allow'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>> + $cgiparams{'BLOCK_ENABLED'} = ($customlists{'block'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>> + }
>>>>> +
>>>>> + # Fallback to empty form
>>>>> + $cgiparams{'ALLOW_LIST'} //= "";
>>>>> + $cgiparams{'BLOCK_LIST'} //= "";
>>>>> +
>>>>> + # HTML checkboxes, unchecked = no or undef value in POST data
>>>>> + my %checked = ();
>>>>> + $checked{'ALLOW_ENABLED'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? " checked" : "";
>>>>> + $checked{'BLOCK_ENABLED'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? " checked" : "";
>>>>> +
>>>>> + # Disable reload button if not needed
>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>> +
>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz cl'});
>>>>> +
>>>>> + print <<END
>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>> +<table width="100%">
>>>>> + <tr>
>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl allow'}</b><br>$Lang::tr{'rpz cl allow info'}</td>
>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl block'}</b><br>$Lang::tr{'rpz cl block info'}</td>
>>>>> + </tr>
>>>>> + <tr>
>>>>> + <td colspan="2"><textarea name="ALLOW_LIST" class="domainlist" cols="45">
>>>>> +$cgiparams{'ALLOW_LIST'}</textarea></td>
>>>>> + <td colspan="2"><textarea name="BLOCK_LIST" class="domainlist" cols="45">
>>>>> +$cgiparams{'BLOCK_LIST'}</textarea></td>
>>>>> + </tr>
>>>>> + <tr>
>>>>> + <td><label for="allow_enabled">$Lang::tr{'rpz cl allow enable'}</label></td>
>>>>> + <td width="15%"><input type="checkbox" name="ALLOW_ENABLED" id="allow_enabled"$checked{'ALLOW_ENABLED'}></td>
>>>>> + <td><label for="block_enabled">$Lang::tr{'rpz cl block enable'}</label></td>
>>>>> + <td width="15%"><input type="checkbox" name="BLOCK_ENABLED" id="block_enabled"$checked{'BLOCK_ENABLED'}></td>
>>>>> + </tr>
>>>>> + <tr>
>>>>> + <td colspan="4"><hr></td>
>>>>> + </tr>
>>>>> + <tr>
>>>>> + <td align="right" colspan="4">
>>>>> + <button type="submit" name="ACTION" value="CL_SAVE">$Lang::tr{'save'}</button>
>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>> + </td>
>>>>> +</table>
>>>>> +</form>
>>>>> +END
>>>>> +;
>>>>> +
>>>>> + &Header::closebox();
>>>>> +}
>>>>> +
>>>>> +# Output javascript and extra gui elements
>>>>> +sub _print_gui_extras {
>>>>> +
>>>>> + # Apply/Restart button modifier key handler
>>>>> + if(&_rpz_needs_reload()) {
>>>>> + print <<END
>>>>> +<script>
>>>>> + // Commit modifier key handler
>>>>> + (function(jq, document) {
>>>>> + var keyEventsOn = false; // Keyboard events attached
>>>>> + var keyModify = false; // Modifier key pressed
>>>>> + var mouseHover = false; // Mouse over commit button
>>>>> + var btnModified = false; // Button modified to "Restart"
>>>>> +
>>>>> + // Document-level key events, enable only while cursor is over button
>>>>> + function attachKeyEvents() {
>>>>> + if(keyEventsOn) {
>>>>> + return;
>>>>> + }
>>>>> + keyEventsOn = true;
>>>>> +
>>>>> + jq(document).on("keydown.rpz", function(event) {
>>>>> + if((!keyModify) && event.shiftKey) {
>>>>> + keyModify = true;
>>>>> + handleModify();
>>>>> + }
>>>>> + });
>>>>> + jq(document).on("keyup.rpz", function(event) {
>>>>> + if(keyModify && (!event.shiftKey)) {
>>>>> + keyModify = false;
>>>>> + handleModify();
>>>>> + }
>>>>> + });
>>>>> + }
>>>>> + function removeKeyEvents() {
>>>>> + keyModify = false;
>>>>> + if(keyEventsOn) {
>>>>> + jq(document).off("keydown.rpz keyup.rpz");
>>>>> + keyEventsOn = false;
>>>>> + }
>>>>> + }
>>>>> +
>>>>> + // Attach mouse hover events to commit buttons
>>>>> + function attachMouseEvents() {
>>>>> + jq("button.commit").on("mouseenter", function(event) {
>>>>> + if(!mouseHover) {
>>>>> + mouseHover = true;
>>>>> + attachKeyEvents();
>>>>> + // Handle already pressed key
>>>>> + keyModify = !!(event.shiftKey);
>>>>> + handleModify();
>>>>> + }
>>>>> + });
>>>>> +
>>>>> + // Cursor moved away: Disable key listener to minimize events
>>>>> + jq("button.commit").on("mouseleave", function() {
>>>>> + if(mouseHover) {
>>>>> + mouseHover = false;
>>>>> + removeKeyEvents();
>>>>> + handleModify();
>>>>> + }
>>>>> + });
>>>>> + }
>>>>> +
>>>>> + // Modify commit button
>>>>> + function handleModify() {
>>>>> + let modify = mouseHover && keyModify;
>>>>> + if(btnModified != modify) {
>>>>> + if(modify) {
>>>>> + jq("button.commit").text("$Lang::tr{'restart'}").val("UNB_RESTART");
>>>>> + } else {
>>>>> + jq("button.commit").text("$Lang::tr{'rpz apply'}").val("RPZ_RELOAD");
>>>>> + }
>>>>> + btnModified = modify;
>>>>> + }
>>>>> + }
>>>>> +
>>>>> + // jQuery DOM ready
>>>>> + jq(function() {
>>>>> + attachMouseEvents();
>>>>> + });
>>>>> + })(jQuery, document);
>>>>> +</script>
>>>>> +END
>>>>> +;
>>>>> + } # End of modifier key handler
>>>>> +
>>>>> +}
>>>>> +
>>>>> +
>>>>> +###--- Internal action processing functions ---###
>>>>> +
>>>>> +# Toggle zonefile on/off
>>>>> +sub _action_zf_toggle {
>>>>> + return unless(defined $zonefiles{$action_key});
>>>>> +
>>>>> + my $result = 0;
>>>>> + my $enabled = $zonefiles{$action_key}{'enabled'};
>>>>> +
>>>>> + # Perform toggle action
>>>>> + if($enabled eq 'on') {
>>>>> + $enabled = 'off';
>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>> + } else {
>>>>> + $enabled = 'on';
>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $action_key, $zonefiles{$action_key}{'url'}, '--no-reload');
>>>>> + }
>>>>> +
>>>>> + # Check for errors, request service reload on success
>>>>> + return unless &_rpz_check_result($result, 1);
>>>>> +
>>>>> + # Save changes
>>>>> + $zonefiles{$action_key}{'enabled'} = $enabled;
>>>>> + &_zonefiles_save_conf();
>>>>> +
>>>>> + return 1;
>>>>> +}
>>>>> +
>>>>> +# Remove zonefile
>>>>> +sub _action_zf_remove {
>>>>> + return unless(defined $zonefiles{$action_key});
>>>>> +
>>>>> + # Remove from rpz-config if currently active
>>>>> + if($zonefiles{$action_key}{'enabled'} eq 'on') {
>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>> +
>>>>> + # Check for errors, request service reload on success
>>>>> + return unless &_rpz_check_result($result, 1);
>>>>> + }
>>>>> +
>>>>> + # Remove from data hash and save changes
>>>>> + delete $zonefiles{$action_key};
>>>>> + &_zonefiles_save_conf();
>>>>> +
>>>>> + # Clear action_key, as the entry is now removed entirely
>>>>> + $action_key = "";
>>>>> +
>>>>> + return 1;
>>>>> +}
>>>>> +
>>>>> +# Create or update zonefile entry
>>>>> +# Returns undef if gui needs to stay in editor mode
>>>>> +sub _action_zf_save {
>>>>> + my $result = 0;
>>>>> +
>>>>> + my $name = $cgiparams{'ZF_NAME'} // "";
>>>>> + my $url = $cgiparams{'ZF_URL'} // "";
>>>>> + my $remark = $cgiparams{'ZF_REMARK'} // "";
>>>>> + my $enabled = 'on'; # Enable new entries by default
>>>>> +
>>>>> + # Note on variables:
>>>>> + # name = unique key, will be used to address the entry
>>>>> + # action_key = name of the entry being edited, empty for new entry
>>>>> +
>>>>> + # Only check for unique name if it changed
>>>>> + # (this also checks new entries because the action_key is empty in this case)
>>>>> + $result = &_rpz_validate_zonefile($name, $url, $remark, (lc($name) ne lc($action_key)));
>>>>> + return unless &_rpz_check_result($result, 0);
>>>>> +
>>>>> + # Edit existing entry: Determine what was changed
>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>> + # Name und URL remain unchanged, only save remark and finish
>>>>> + if(($name eq $action_key) && ($url eq $zonefiles{$action_key}{'url'})) {
>>>>> + $zonefiles{$action_key}{'remark'} = $remark;
>>>>> + &_zonefiles_save_conf();
>>>>> +
>>>>> + return 1;
>>>>> + }
>>>>> +
>>>>> + # Entry was changed and needs to be recreated, preserve status
>>>>> + $enabled = $zonefiles{$action_key}{'enabled'};
>>>>> +
>>>>> + # Remove from rpz-config
>>>>> + return unless &_action_zf_remove();
>>>>> + }
>>>>> +
>>>>> + # Add new entry to rpz-config
>>>>> + if($enabled eq 'on') {
>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $name, $url, '--no-reload');
>>>>> +
>>>>> + # Check for errors, request service reload on success
>>>>> + return unless &_rpz_check_result($result, 1);
>>>>> + }
>>>>> +
>>>>> + # Add to global data hash and save changes
>>>>> + my %entry = ('enabled' => $enabled,
>>>>> + 'url' => $url,
>>>>> + 'remark' => $remark);
>>>>> +
>>>>> + $zonefiles{$name} = \%entry;
>>>>> + &_zonefiles_save_conf();
>>>>> +
>>>>> + return 1;
>>>>> +}
>>>>> +
>>>>> +# Save custom lists
>>>>> +sub _action_cl_save {
>>>>> + return unless((defined $cgiparams{'ALLOW_LIST'}) && (defined $cgiparams{'BLOCK_LIST'}));
>>>>> +
>>>>> + my $result = 0;
>>>>> +
>>>>> + my @allowlist = split(/\R/, $cgiparams{'ALLOW_LIST'});
>>>>> + my @blocklist = split(/\R/, $cgiparams{'BLOCK_LIST'});
>>>>> +
>>>>> + # Validate lists
>>>>> + $result = &_rpz_validate_customlist(\@allowlist);
>>>>> + if($result != 0) {
>>>>> + $errormessage = &_rpz_error_tr(202, $result);
>>>>> + return;
>>>>> + }
>>>>> + $result = &_rpz_validate_customlist(\@blocklist);
>>>>> + if($result != 0) {
>>>>> + $errormessage = &_rpz_error_tr(203, $result);
>>>>> + return;
>>>>> + }
>>>>> +
>>>>> + # Add to global data hash and save changes
>>>>> + $customlists{'allow'}{'list'} = \@allowlist;
>>>>> + $customlists{'block'}{'list'} = \@blocklist;
>>>>> + $customlists{'allow'}{'enabled'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? 'on' : 'off';
>>>>> + $customlists{'block'}{'enabled'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? 'on' : 'off';
>>>>> +
>>>>> + &_customlists_save_conf();
>>>>> + &_customlist_export('allow', $RPZ_ALLOWLIST);
>>>>> + &_customlist_export('block', $RPZ_BLOCKLIST);
>>>>> +
>>>>> + # Make new lists, request service reload on success
>>>>> + $result = &General::system('/usr/sbin/rpz-make', 'allowblock', '--no-reload');
>>>>> + return unless &_rpz_check_result($result, 1);
>>>>> +
>>>>> + return 1;
>>>>> +}
>>>>> +
>>>>> +# Trigger rpz-config reload
>>>>> +sub _action_rpz_reload {
>>>>> + return 1 unless &_rpz_needs_reload();
>>>>> +
>>>>> + # Immediately clear flag to prevent multiple reloads
>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>> + }
>>>>> +
>>>>> + # Perform reload, recreate reload flag on error to enable retry
>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'reload');
>>>>> + if(not &_rpz_check_result($result, 0)) {
>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>> + return;
>>>>> + }
>>>>> +
>>>>> + return 1;
>>>>> +}
>>>>> +
>>>>> +# Trigger unbound restart
>>>>> +sub _action_unb_restart {
>>>>> + return 1 unless &_rpz_needs_reload();
>>>>> +
>>>>> + # Immediately clear flag to prevent multiple restarts
>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>> + }
>>>>> +
>>>>> + # Perform restart, unboundctrl always exits zero
>>>>> + &General::system('/usr/local/bin/unboundctrl', 'restart');
>>>>> +
>>>>> + return 1;
>>>>> +}
>>>>> +
>>>>> +
>>>>> +###--- Internal rpz-config functions ---###
>>>>> +
>>>>> +# Translate rpz-config exitcodes and messages
>>>>> +# 100-199: rpz-config, 200-299: webgui
>>>>> +sub _rpz_error_tr {
>>>>> + my ($error, $append) = @_;
>>>>> + $append //= '';
>>>>> +
>>>>> + # Translate numeric exit codes
>>>>> + if(looks_like_number($error)) {
>>>>> + if(defined $Lang::tr{"rpz exitcode $error"}) {
>>>>> + $error = $Lang::tr{"rpz exitcode $error"};
>>>>> + }
>>>>> + }
>>>>> +
>>>>> + return "RPZ $Lang::tr{'error'}: $error" . &Header::escape($append);
>>>>> +}
>>>>> +
>>>>> +# Check result of rpz-config system call, request reload on success
>>>>> +sub _rpz_check_result {
>>>>> + my ($result, $request_reload) = @_;
>>>>> + $request_reload //= 0;
>>>>> +
>>>>> + # exitcode 0 = success
>>>>> + if($result != 0) {
>>>>> + $errormessage = &_rpz_error_tr($result);
>>>>> + return;
>>>>> + }
>>>>> +
>>>>> + # Set reload flag
>>>>> + if($request_reload) {
>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>> + }
>>>>> +
>>>>> + return 1;
>>>>> +}
>>>>> +
>>>>> +# Test whether reload flag is set
>>>>> +sub _rpz_needs_reload {
>>>>> + return (-f $RPZ_RELOAD_FLAG);
>>>>> +}
>>>>> +
>>>>> +# Validate a zonefile entry, returns rpz-config exitcode on failure. Use _rpz_check_result to verify.
>>>>> +# unique = check for unique name
>>>>> +sub _rpz_validate_zonefile {
>>>>> + my ($name, $url, $remark, $unique) = @_;
>>>>> + $unique //= 1;
>>>>> +
>>>>> + unless($name =~ /^[a-zA-Z0-9_]{1,32}$/) {
>>>>> + return 101;
>>>>> + }
>>>>> + unless($url =~ /^[\w+\.:;\/\\&@#%?=\-~|!]{1,128}$/) {
>>>>> + return 105;
>>>>> + }
>>>>> + unless($remark =~ /^[\w \-()\.:;*\/\\?!&=]{0,32}$/) {
>>>>> + return 201;
>>>>> + }
>>>>> +
>>>>> + # Check against already existing names
>>>>> + if($unique) {
>>>>> + foreach my $existing (keys %zonefiles) {
>>>>> + if(lc($name) eq lc($existing)) {
>>>>> + return 104;
>>>>> + }
>>>>> + }
>>>>> + }
>>>>> +
>>>>> + return 0;
>>>>> +}
>>>>> +
>>>>> +# Validate a custom list, returns number of rejected line on failure. Check for non-zero results.
>>>>> +# listref = array reference, cleanup = remove invalid entries instead of returning an error
>>>>> +sub _rpz_validate_customlist {
>>>>> + my ($listref, $cleanup) = @_;
>>>>> + $cleanup //= 0;
>>>>> +
>>>>> + foreach my $index (reverse 0..$#{$listref}) {
>>>>> + my $row = @$listref[$index];
>>>>> + next unless($row); # Skip/allow empty lines
>>>>> +
>>>>> + # Reject/remove everything besides wildcard domains and remarks
>>>>> + if((not &General::validwildcarddomainname($row)) && (not $row =~ /^;[\w \-()\.:;*\/\\?!&=]*$/)) {
>>>>> + unless($cleanup) {
>>>>> + # +1 for user friendly line number and to ensure non-zero exitcode
>>>>> + return $index + 1;
>>>>> + }
>>>>> +
>>>>> + # Remove current row
>>>>> + splice(@$listref, $index, 1);
>>>>> + }
>>>>> + }
>>>>> +
>>>>> + return 0;
>>>>> +}
>>>>> +
>>>>> +
>>>>> +###--- Internal misc functions ---###
>>>>> +
>>>>> +# Send HTTP 303 redirect headers for post/request/get pattern
>>>>> +# (Must be sent before calling &Header::showhttpheaders())
>>>>> +sub _http_prg_redirect {
>>>>> + my $location = "https://$ENV{'SERVER_NAME'}:$ENV{'SERVER_PORT'}$ENV{'SCRIPT_NAME'}";
>>>>> + print "Status: 303 See Other\n";
>>>>> + print "Location: $location\n";
>>>>> +}
>>>>> diff --git a/lfs/rpz b/lfs/rpz
>>>>> new file mode 100644
>>>>> index 000000000..7ddbc38e5
>>>>> --- /dev/null
>>>>> +++ b/lfs/rpz
>>>>> @@ -0,0 +1,96 @@
>>>>> +###############################################################################
>>>>> +# #
>>>>> +# IPFire.org - A linux based firewall #
>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>> +# #
>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>> +# #
>>>>> +###############################################################################
>>>>> +
>>>>> +###############################################################################
>>>>> +# Definitions
>>>>> +###############################################################################
>>>>> +
>>>>> +include Config
>>>>> +
>>>>> +SUMMARY = response policy zone - RPZ reputation system for unbound DNS
>>>>> +
>>>>> +VER = 1.0.0
>>>>> +
>>>>> +THISAPP = rpz-$(VER)
>>>>> +DIR_APP = $(DIR_SRC)/$(THISAPP)
>>>>> +TARGET = $(DIR_INFO)/$(THISAPP)
>>>>> +
>>>>> +PROG = rpz
>>>>> +PAK_VER = 18
>>>>> +
>>>>> +DEPS =
>>>>> +
>>>>> +SERVICES =
>>>>> +
>>>>> +###############################################################################
>>>>> +# Top-level Rules
>>>>> +###############################################################################
>>>>> +
>>>>> +install : $(TARGET)
>>>>> +
>>>>> +check :
>>>>> +
>>>>> +download :
>>>>> +
>>>>> +b2 :
>>>>> +
>>>>> +dist:
>>>>> + @$(PAK)
>>>>> +
>>>>> +###############################################################################
>>>>> +# Installation Details
>>>>> +###############################################################################
>>>>> +
>>>>> +$(TARGET) :
>>>>> + @$(PREBUILD)
>>>>> + @rm -rf $(DIR_APP)
>>>>> +
>>>>> + # RPZ scripts
>>>>> + install --verbose --mode=755 \
>>>>> + $(DIR_CONF)/rpz/{rpz-config,rpz-metrics,rpz-sleep,rpz-make,rpz-functions} \
>>>>> + --target-directory=/usr/sbin
>>>>> +
>>>>> + # RPZ config files
>>>>> + mkdir -pv /etc/unbound/local.d
>>>>> + install --verbose --mode=644 --owner=nobody --group=nobody \
>>>>> + $(DIR_CONF)/rpz/00-rpz.conf \
>>>>> + --target-directory=/etc/unbound/local.d
>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/local.d
>>>>> +
>>>>> + # RPZ custom list files for allow and block
>>>>> + mkdir -pv /var/ipfire/dns/rpz
>>>>> + touch /var/ipfire/dns/rpz/{allowlist,blocklist}
>>>>> + chown --verbose --recursive nobody:nobody /var/ipfire/dns/rpz
>>>>> +
>>>>> + # RPZ zone files
>>>>> + # create empty RPZ config file to avoid a unbound config error
>>>>> + mkdir -pv /etc/unbound/zonefiles
>>>>> + touch /etc/unbound/zonefiles/allow.rpz
>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/zonefiles
>>>>> +
>>>>> + # Install addon-specific language-files
>>>>> + install --verbose --mode=004 $(DIR_CONF)/rpz/rpz.*.pl \
>>>>> + --target-directory=/var/ipfire/addon-lang
>>>>> +
>>>>> + # Install backup definition
>>>>> + cp -vf $(DIR_CONF)/backup/includes/rpz /var/ipfire/backup/addons/includes/rpz
>>>>> +
>>>>> + @rm -rf $(DIR_APP)
>>>>> + @$(POSTBUILD)
>>>>> diff --git a/make.sh b/make.sh
>>>>> index 827ea9e77..a77535b13 100755
>>>>> --- a/make.sh
>>>>> +++ b/make.sh
>>>>> @@ -390,7 +390,7 @@ prepareenv() {
>>>>> if [ "${free_space}" -lt "${required_space}" ]; then
>>>>> # Add any consumed space
>>>>> while read -r consumed_space path; do
>>>>> - (( free_space += consumed_space / 1024 / 1024 ))
>>>>> + (( free_space += consumed_space / 1024 / 1024 ))
>>>>> done <<< "$(du --summarize --bytes "${BUILD_DIR}" "${IMAGES_DIR}" "${LOG_DIR}" 2>/dev/null)"
>>>>> fi
>>>>>
>>>>> @@ -2087,6 +2087,7 @@ build_system() {
>>>>> lfsmake2 btrfs-progs
>>>>> lfsmake2 inotify-tools
>>>>> lfsmake2 grub-btrfs
>>>>> + lfsmake2 rpz
>>>>>
>>>>> lfsmake2 linux
>>>>> lfsmake2 rtl8812au
>>>>> diff --git a/src/paks/rpz/install.sh b/src/paks/rpz/install.sh
>>>>> new file mode 100644
>>>>> index 000000000..ef99bf742
>>>>> --- /dev/null
>>>>> +++ b/src/paks/rpz/install.sh
>>>>> @@ -0,0 +1,36 @@
>>>>> +#!/bin/bash
>>>>> +###############################################################################
>>>>> +# #
>>>>> +# IPFire.org - A linux based firewall #
>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>> +# #
>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>> +# #
>>>>> +###############################################################################
>>>>> +#
>>>>> +. /opt/pakfire/lib/functions.sh
>>>>> +extract_files
>>>>> +restore_backup ${NAME}
>>>>> +
>>>>> +# fix user created files
>>>>> +chown --verbose --recursive nobody:nobody \
>>>>> + /var/ipfire/dns/rpz \
>>>>> + /etc/unbound/zonefiles \
>>>>> + /etc/unbound/local.d
>>>>> +
>>>>> +# Update Language cache
>>>>> +/usr/local/bin/update-lang-cache
>>>>> +
>>>>> +# restart unbound to load config file
>>>>> +/etc/init.d/unbound restart
>>>>> diff --git a/src/paks/rpz/uninstall.sh b/src/paks/rpz/uninstall.sh
>>>>> new file mode 100644
>>>>> index 000000000..e11427df3
>>>>> --- /dev/null
>>>>> +++ b/src/paks/rpz/uninstall.sh
>>>>> @@ -0,0 +1,38 @@
>>>>> +#!/bin/bash
>>>>> +###############################################################################
>>>>> +# #
>>>>> +# IPFire.org - A linux based firewall #
>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>> +# #
>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>> +# #
>>>>> +###############################################################################
>>>>> +#
>>>>> +. /opt/pakfire/lib/functions.sh
>>>>> +
>>>>> +# stop unbound to delete RPZ conf file
>>>>> +/etc/init.d/unbound stop
>>>>> +
>>>>> +make_backup ${NAME}
>>>>> +remove_files
>>>>> +
>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>> +# "[1723428668] unbound-control[17117:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>> +
>>>>> +# Update Language cache
>>>>> +/usr/local/bin/update-lang-cache
>>>>> +
>>>>> +# start unbound to load unbound config file
>>>>> +/etc/init.d/unbound start
>>>>> diff --git a/src/paks/rpz/update.sh b/src/paks/rpz/update.sh
>>>>> new file mode 100644
>>>>> index 000000000..9bc340bc6
>>>>> --- /dev/null
>>>>> +++ b/src/paks/rpz/update.sh
>>>>> @@ -0,0 +1,52 @@
>>>>> +#!/bin/bash
>>>>> +###############################################################################
>>>>> +# #
>>>>> +# IPFire.org - A linux based firewall #
>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>> +# #
>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>> +# #
>>>>> +###############################################################################
>>>>> +#
>>>>> +. /opt/pakfire/lib/functions.sh
>>>>> +
>>>>> +# from update.sh
>>>>> +extract_backup_includes
>>>>> +
>>>>> +# stop unbound to delete RPZ conf file
>>>>> +/etc/init.d/unbound stop
>>>>> +
>>>>> +# from uninstall.sh
>>>>> +make_backup ${NAME}
>>>>> +remove_files
>>>>> +
>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>> +# "unbound-control[nn:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>> +
>>>>> +# from install.sh
>>>>> +extract_files
>>>>> +restore_backup ${NAME}
>>>>> +
>>>>> +# fix user created files
>>>>> +chown --verbose --recursive nobody:nobody \
>>>>> + /var/ipfire/dns/rpz \
>>>>> + /etc/unbound/zonefiles \
>>>>> + /etc/unbound/local.d
>>>>> +
>>>>> +# Update Language cache
>>>>> +/usr/local/bin/update-lang-cache
>>>>> +
>>>>> +# restart unbound to load config files
>>>>> +/etc/init.d/unbound start
>>>>> --
>>>>> 2.39.5
>>>>>
>>>>
>>>
>>> Jon
>>>
>>>
>>> --
>>> Jon Murphy
>>> jon.murphy(a)ipfire.org
>
>
> Jon
>
>
> --
> Jon Murphy
> jon.murphy(a)ipfire.org
>
>
>
>
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
2025-02-14 12:07 ` Michael Tremer
@ 2025-02-14 12:58 ` Bernhard Bitsch
2025-02-14 13:52 ` Michael Tremer
2025-03-01 10:18 ` Adolf Belka
1 sibling, 1 reply; 30+ messages in thread
From: Bernhard Bitsch @ 2025-02-14 12:58 UTC (permalink / raw)
To: development
[-- Attachment #1: Type: text/plain, Size: 126956 bytes --]
Hello Michael,
thanks for your clear words. But nevertheless let be comment at some topics.
Am 14.02.2025 um 13:07 schrieb Michael Tremer:
> Hello Jon,
>
> It very much depends on the kind of contribution. A one-line patch obviously has fewer strings attached to it than a larger patch set like this.
>
> However, we have outlined the process in the wiki already, starting from here:
>
> https://www.ipfire.org/docs/devel/submit-patches
>
> This contains some useful pointers about the how (how do I actually make my changes happen, how do I build IPFire, etc), and at the bottom it contains a lot of information about the format the changes should be submitted; split into smaller chunks that are ideally as independent from each other so that they can be individually reviewed and merged. Usually a development process takes long time and we have already shipped parts of code that we will need for certain features that are not ready yet. This is a good practice to let code mature, especially when it is touching rather critical bits like the firewall and networking stacks.
>
> There are also some guidelines on how to write a good commit message and how to use Git tags:
>
> https://www.ipfire.org/docs/devel/git/commit-messages
> https://www.ipfire.org/docs/devel/git/tags
>
> Then there is something about how to get in touch with the right person and legal stuff:
>
> https://www.ipfire.org/docs/devel/contact
> https://www.ipfire.org/docs/legal/ipca
>
IMO, this facts are known by Jon. He just did a big part besides this
general process. Including publishing his efforts to the community for
review and test.
> Finally, we have a bit of an underused roadmap section on the wiki. It would be nice if we could use that a little bit more because then it would be easier for everyone to keep track of progress on certain features; people could see what is being worked on and see if they can help development and testing and so on:
>
> https://www.ipfire.org/docs/roadmap
>
> There is a template on how to create new pages:
>
> https://www.ipfire.org/docs/roadmap/template
>
> And this is a good example of what this could look like:
>
> https://www.ipfire.org/docs/roadmap/openvpn-26
>
> All of these steps are coming *after* there has been some initial discussion about what actually has a chance to become part of the distribution. For that, we do not have any specific guidelines because it is not very trivial to write these things. There are just too many possibilities. In the past, there has also been very little need for this, but that does not mean that there have not been problems before.
>
> The reason why I am raising the bar this high here is simply that we have made mistakes in the past that we don’t want to repeat. We have learned a couple of lessons in a not very pleasant way and I under no circumstances would want to do this again. The objective is that we want to provide an excellent distribution. Although IPFire of course has its shortcomings here and there, it is a very stable distribution and we have a very good track record that I want to keep. This is what our users deserve.
>
> In the past, people have “dropped” their patches on this list (or sometimes elsewhere) and we were left with dealing with the entire integration only to find out later what problems there were hidden in the code. The original author(s) had no interest in fixing any of that because it worked just fine for them, and so why spend any time on the problems of somebody else? Usually I am the fallback for this and I simply don’t want to be that. I have lots of my own projects inside IPFire that are moving at snail speed because fixing existing code usually takes priority over writing new code.
>
> Therefore we need a commitment to sort out these problems in the first place. It has to be proven that people actually *care* about the patches that they post here. I am not sure this needs writing down as this should be the same policy with almost any open source project. If you contribute a line, there is probably less maintenance required in the future, but if you contribute a large code base, then you will need to look after it for the foreseeable future. It is your feature and not mine after all.
>
Being one of the first testers and revisors, I commit me to support the
development. Point.
> Then, what actually has a chance to make it into the distribution? Probably not a lot. IPFire has a very clear use case. There will not be any space for a desktop environment and running Chrome on it, we also don’t it to make coffee and cook me a dinner. We would currently only accept things that were actually maintainable by the current team in case a contributor moves on (see above), because we simply only have so much man power. We already have a large zoo of features that are very abandoned and we are potentially looking at getting rid of more things simply because we cannot support them properly. Time just doesn’t permit. Adding something large is therefore very difficult at the moment.
>
> I understand that in this specific case you have been trying to not involve the development team and I understand your motivation. But you cannot forget about how much time and effort a review process can take. Therefore we want to plan things well; we want to even split it; and we want to have a conversation in advance so that the roadmap is clear and the actual code review ideally only becomes a formality.
>
> All of this above has been for a general case. Please read through this and feel free to ask any questions if something isn’t clear.
>
> To move forward with this feature, we should start by planning a roadmap. We need to discuss what this project should cover and what it should not cover. I believe we don’t need to talk much about implementation details because you have figured out a lot of them; we need to find what feature we want to provide to our users. Are you up for that?
>
As a first input to the 'official' discussion, some thoughts.
- Usage of encrypted data traffic is increasing. Especially in
communications not aware to the user. These unwanted traffic is more and
more difficult to block with IP orientated tools. Even the proxy based
solution do not really function efficient.
- To circumvent these problems a widely used approach is DNS filtering.
This comes in two flavours: 1. online providers, 2. local filtering DNS
servers like PiHole.
- Solution 1 isn't wantable. Many providers store the access data,
outside the local network.
- Solution 2 installs another DNS server inside the local networks. It
isn't trivial to ensure that all DNS traffic is handled by this server.
- Therefore it is desirable that this filtering is done in the only DNS
server in a IPFire governed network, unbound on the IPFire system. The
RPZ functionality allows exactly this.
- There are, IMO, good filter lists outside. The updating of these lists
is part of the RPZ module. There is some effort left to investigate
these and to develop recommendations for a good mix of appliance, we do
this for the external DNS servers already.
Regards,
Bernhard
> Best,
> -Michael
>
>> On 13 Feb 2025, at 21:34, jon <jon.murphy(a)ipfire.org> wrote:
>>
>> Michael,
>>
>> I’ve read through your comments a few times and I ended up with many more questions.
>>
>>
>>> What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>
>>
>> To me the efforts to get new code accepted seem to have changed and it seemed easier in the past. In the past I made the Core Team aware via the Dev Mailing List and wrote a simple two or three paragraphs of "What is it? / What is the value? / Here is the code"
>>
>>
>> So in an effort to move forward: How exactly is something presented to the Core Team?
>>
>> Is there an example of a recent effort that was presented that I can see as a sample? (This type of info can also be added to the Wiki)
>>
>> I understand you want it this way, but I don’t know what exactly is needed. Please be specific.
>>
>>
>> Jon
>>
>> PS - I am not ignoring your other comments, I am just trying to move forward and keep things simple.
>>
>>
>>
>>> On Feb 8, 2025, at 1:27 PM, Michael Tremer <michael.tremer(a)ipfire.org> wrote:
>>>
>>> Hello Jon,
>>>
>>> Thanks for your reply. And good that you are copying everyone into this conversation.
>>>
>>>> On 8 Feb 2025, at 18:41, jon <jon.murphy(a)ipfire.org> wrote:
>>>>
>>>> Michael,
>>>>
>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort
>>>>
>>>> Yes, this has been a separate effort (a very public separate effort). Yes, as you pointed this out early on with the "proof-of-concept" and then my request for people to help test RPZ. Nothing was hidden.
>>>>
>>>> This was done because you (and maybe others) did not have the time and I wanted to help and because I needed assistance with RPZ. I tried my best to do this without bothering you.
>>>
>>> I don’t that it is accurate that nobody wanted to help on this. The list was always open - although not every email has been replied to swiftly it is also your responsibility to raise a question again if it was missed. People here have open ears.
>>>
>>> It was also stated on this very list on in our documentation that working on something without involving the core team is a risky undertaking. Of course IPFire is free software and so everyone is free to fork if they wish to do so.
>>>
>>>>> and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls.
>>>>
>>>> You were aware many steps along the way. See your email on July 28, 2024, August 15, 2024, September 30, 2024, December 23, 2024, and January 16. My attempts to get the team involved were met with "things are busy" and sometimes silence. (Yes, I get it, people are busy.)
>>>>
>>>> You and Adolf, Leo, Erik and Bernhard have been aware since the beginning. You mention you were aware of the "proof-of-concept". If you include those beginning posts, since Sep 2023.
>>>
>>> Yes, I am aware of a proof-of-concept that I have been running myself for a long time. I am also aware of the efforts that you have been taking.
>>>
>>> Yet I don’t think there has ever been any joint effort, or am I seeing that wrong?
>>>
>>>>> This has not been discussed . . . on our calls.
>>>>
>>>> On the July 28th you stated:
>>>> "We have talked about RPZ many times on the monthly call since the URL filter feature is falling more and more out of fashion. I think there is also many posts about this on the forum."
>>>>
>>>> Please don’t insult me again by stating "you know what I mean".
>>>>
>>>> And it has been discussed but not documented in the Monthly Meeting notes.
>>>
>>> I am not at all insulting you. I don’t want to take this down to a personal level at all. This is a public mailing list and people who read this don’t need to listen to an argument we are having. They are here for the tech inside IPFire.
>>>
>>> When I wrote that it has not been discussed that does not mean that we have not been touching on the topic. We have been talking about lots of things on the calls, the weather, politics, how our pets are. None of that makes it to the logs. What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>
>>>>> Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation.
>>>>
>>>> Regular conversation on the Dev Mailing list is many times met with silence. I get it, people are busy.
>>>>
>>>> And regular two-way conversation doesn’t happen on the list. At least not with me. I’d be happy to point out the posts that were met with silence.
>>>> Again, I get it, people are busy.
>>>
>>> And you think my emails are not being met with silence? This has nothing to do with this specific topic. This has something to do with how occupied people are and how engaged they are on certain topics. Not everyone is involved in all the things and simply will ignore emails simply based on their subject line.
>>>
>>>> But the "dip here to the list" were my attempts to get a conversation started. As I said, many time met with silence.
>>>>
>>>> The only place I was not met with silence was on the Community. You have a great group of people in the Community. It is a shame you don’t want to have others help. It would reduce your workload.
>>>
>>> You should stop making statements that are not true. Who doesn’t want anyone to help?
>>>
>>> Not having this conversation on a Saturday evening would reduce my workload. At least it would free up time for something else. Helping with the things that are already on the go would reduce the workload of the entire team. Starting one thing at a time and finishing it is a lot better to manage than starting a hundred things and not even finish one. I can tell you that I already have a hundred things on the go.
>>>
>>>>> Therefore, what am I supposed to do with this email?
>>>>
>>>> To me it is beyond obvious…
>>>>
>>>> If it isn’t what you want, then guide me with how to do this the correct way. And be specific. I am trying to help. I am trying to make things better. I am trying to do things the right way.
>>>
>>> To me it isn’t. This is yet another project that has been dumped to the list like so many before and later on everyone has left to have the team deal with the rest.
>>>
>>> It is a huge patch set. You explained what the vision is, but that is about it. There is no chance this will continue if this disagreement isn’t solved first. I didn’t even look at the code.
>>>
>>>>> I don’t want to merge code that I don’t agree with.
>>>>
>>>> I asked multiple times if you "agreed with the concept" and again, met with silence. Yes I get it, people are busy.
>>>
>>> Having support for RPZ? Yes, it was definitely on the roadmap. That I agree with.
>>>
>>>>> So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>
>>>> You mentioned this a in the past, but for some reason you do not disclose what I dismissed. Why do you continue to make this harder, wouldn’t it not be easier to tell me what I have dismissed?
>>>>
>>>> I have sent multiple emails trying to answer your concerns and comments. On July 28, Aug 14, Aug 22, Aug 23, Sep 30, etc.
>>>>
>>>> I’ve gone through all of the questions you asked and I cannot find a "dismissed" item.
>>>
>>> Maybe I need to be *more clear*. I feel humoured by this.
>>>
>>> It is late on a Saturday and I want my dinner soon, but certainly I have stated that this should never be an add-on considering it is supposed to replace URL Filter. We should never allow people to add their own sources. I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>>
>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>
>>>> The maintainers of Unbound and/or RPZ?
>>>>
>>>> The maintainers of Hagezi list, the threatfox list, the urlhaus list, etc.?
>>>>
>>>> What else? The maintainers or the RPZ scripts? That is me. Let’s talk!
>>>
>>> You. I don’t care much about the providers of the lists.
>>>
>>>> See, this is where it gets confusing. There are hundreds of open source packages as part of IPFire. Pick the last five years of items added to the IPFire build. You're telling me you have "constructive conversation with the maintainers" of all of the added packages?
>>>
>>> They publish their software and they don’t care whether I am pulling it or not. They publish it with the commitment to maintain it - sometimes for better and sometimes for worse.
>>>
>>> You care about me pulling your code and I don’t know whether you would commit to maintain this.
>>>
>>> These two are very different cases.
>>>
>>>> Pick the IP Blocklists list (i.e., 3CORESEC, ABUSECH, DSHIELD, SPAMHAUS, etc.) or the Suricata lists (i.e., Emergingthreats.net, Abuse.ch, etc.). So you’ve have "constructive conversation with the maintainers"?
>>>
>>> Yes, occasionally I have phone calls with a few of these providers.
>>>
>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>
>>>> Ha! Yes a surprise. In the beginning you seemed interested as IPFire needed a replacement for URL Filter. You asked good questions about the lists picked, asked for the value to the users, etc. And I answered the best I could.
>>>>
>>>> You even asked: “Why is this realised as an add-on and not part of the core system?” from your Jul 28, 2024 email.
>>>
>>> Ah, so, why is the patch creating an add-on? Not that I am saying that what I say is law, but it has not been challenged either. If my input is being ignored, why should I put this to the top of my list of priorities? I am not disappointed about this, just trying to be very good with my time.
>>>
>>>> And on January 16, 2025 I wrote a message looking for help. And you were kind to respond quickly. So in three weeks time, since the kind response, something has changed. You went from supportive to "this".
>>>>
>>>> So yes, I am surprised.
>>>
>>> Well, maybe I should not have replied to that email. It was clear that you were on some path that was not right, but you were not interested before in finding the right path from the beginning.
>>>
>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>
>>>> Be more specific, what has to change? What exactly did I dismiss?
>>>
>>> Dismissal is just my assumption. I don’t know what you actually did with my feedback. I can only see the end product that does not seem contain much of it. Repeatedly I have been pointing out that we should think before we build. I am sure a lot of hours have now gone into some code that simply does not satisfy me. And I am not not talking about the code itself, what it does is what I don’t think is right for us.
>>>
>>> The process is very clear for me that we should first of all think whether we want a certain feature now. Then there should be a clear roadmap for everyone to follow; tasks can be split-up as we go and hopefully then have something that is maintainable, interesting for our users and even would do us proud. This is how this should work.
>>>
>>> So, what has to change? I don’t think with shouting at each other, throwing patches around and making me generally unhappy is a good start.
>>>
>>> -Michael
>>>
>>>> Jon
>>>>
>>>>
>>>>
>>>>> On Feb 6, 2025, at 2:13 PM, Michael Tremer <michael.tremer(a)ipfire.org> wrote:
>>>>>
>>>>> Hello Jon,
>>>>>
>>>>> Well, here we are again with another patch regarding this feature.
>>>>>
>>>>> I cannot quite see from your email what the question is, but if this is a request to have this merged into IPFire, I am once again sorry to disappoint you.
>>>>>
>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls. Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation. Therefore, what am I supposed to do with this email?
>>>>>
>>>>> I don’t want to merge code that I don’t agree with. So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>
>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>
>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>
>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>
>>>>> All the best,
>>>>> -Michael
>>>>>
>>>>>> On 6 Feb 2025, at 16:35, Jon Murphy <jon.murphy(a)ipfire.org> wrote:
>>>>>>
>>>>>> What is it?
>>>>>> Response Policy Zone (RPZ) is a mechanism to define local policies in a
>>>>>> standardized way and load those policies from external sources.
>>>>>> Bottom line: RPZ allows admins to easily block access to websites via DNS lookup.
>>>>>>
>>>>>> RPZ can block websites via categories. Examples include: fake websites, annoying
>>>>>> pop-up ads, newly registered domains, DoH bypass sites, bad "host" services,
>>>>>> maliscious top level domains (e.g., *.zip, *.mov), piracy, gambling, pornography,
>>>>>> and more. RPZ lists come from various RPZ providers and their available
>>>>>> catagories.
>>>>>>
>>>>>> This RPZ add-on enables the RPZ functionality by adding a couple lines in a
>>>>>> configuration file. This add-on simply adds configuration files and adds
>>>>>> scripts (config, metrics and sleep) to make RPZ easier for the admin to use.
>>>>>>
>>>>>> The RPZ scripts include additional languages: German, Spanish, French, Turkish,
>>>>>> and Italian.
>>>>>>
>>>>>> RPZ itself was release in 2010 and has been part of the IPFire build since ~2015.
>>>>>>
>>>>>> Why is it needed? What is its value?
>>>>>>
>>>>>> - The RPZ concept places this filtering into IPFire, our internet access
>>>>>> gateway, which is (should be) solely used as DNS source of the internal network.
>>>>>>
>>>>>> - As most sites use HTTPS it makes it difficult to filter traffic with URL
>>>>>> Filter without also properly configuring conventional (non-transparent)
>>>>>> mode on the proxy. RPZ is a nice replacement for the URL Filter.
>>>>>>
>>>>>> - No need to install and maintain an additional device like PiHole or AdBlock
>>>>>> browser extensions on multiple user devices.
>>>>>>
>>>>>> - This is an additional layer of protection for users. Less worry someone will
>>>>>> click on something that gets them into trouble. And, saying this with emphasis,
>>>>>> the ability to do it in one place!
>>>>>>
>>>>>> - Blocked sites save on unneeded traffic and can lessen the threat of malware
>>>>>> in advertisements
>>>>>>
>>>>>> - Logging allows the admin to see the site blocked and take actions
>>>>>>
>>>>>> - RPZ will be used at the home, home-office (work from home), schools,
>>>>>> ministerial, and at the office. Device counts are small (2-6) to medium (~80)
>>>>>> to mediam-large (200+).
>>>>>>
>>>>>> - RPZ can block ads, popups, phishing, scammers, spyware, malware, annoying
>>>>>> popups, NSFW links, DOH servers, and the usual internet trash.
>>>>>>
>>>>>> ------------------------------
>>>>>>
>>>>>> Change Log for RPZ add-on
>>>>>>
>>>>>> rpz-1.0.0-18 on 2025-02-05
>>>>>> - Build for approval & release as IPFire add-on
>>>>>>
>>>>>> ---
>>>>>>
>>>>>> rpz-beta-0.1.18-18.ipfire on 2025-02-01
>>>>>> rpz.cgi:
>>>>>> - new feature: added a mod key to force a unbound restart
>>>>>>
>>>>>> rpz-config and rpz-make:
>>>>>> - new feature: added action for unbound restart `rpz-config unbound-restart`
>>>>>>
>>>>>> rpz-metrics:
>>>>>> - simple reformatting
>>>>>> - rename far right column from "last update" to "last download"
>>>>>>
>>>>>> ---
>>>>>>
>>>>>> rpz-beta-0.1.17-17.ipfire on 2024-12-09
>>>>>> rpz-make
>>>>>> - bug fix: corrected validation regex for wildcards like: `*.domain.com`
>>>>>>
>>>>>> ---
>>>>>>
>>>>>> rpz-beta-0.1.16-16.ipfire on 2024-11-18
>>>>>> rpz-make
>>>>>> - new feature: updated validation regex
>>>>>> - bug fix: moved validation to beginning of process. Now we validate before
>>>>>> creating config files.
>>>>>>
>>>>>> rpz.cgi:
>>>>>> - new feature: use CSS color variables of the main ipfire theme
>>>>>> - bug fix: empty zonefile remarks were stored as “undef” and caused a warning
>>>>>> - bug fix: HTML textarea removes the first empty line in a custom list
>>>>>> - thank you Leo!
>>>>>>
>>>>>> ---
>>>>>>
>>>>>> rpz-beta-0.1.15-15.ipfire on 2024-11-04
>>>>>> rpz.cgi:
>>>>>> - new feature: added new language file for Turkish (thank you Peppe)
>>>>>>
>>>>>> rpz-make
>>>>>> - bug fix: corrected empty allow/block list issue. An empty allow/block list
>>>>>> will now remove contents of allow/block.rpz files and remove unneeded
>>>>>> allow/block.conf file. (thank you iptom)
>>>>>>
>>>>>> ---
>>>>>>
>>>>>> rpz-beta-0.1.14-14.ipfire on 2024-10-29
>>>>>> rpz-config:
>>>>>> - bug fix: correct missing rpz extension. `rpz-config list` displayed URL
>>>>>> incorrectly (thank you Bernhard)
>>>>>>
>>>>>> rpz.cgi:
>>>>>> - bug fix: remove extra `"` in language files (thank you Bernhard)
>>>>>> - new feature: slightly dim "apply" button when not enabled
>>>>>>
>>>>>> ---
>>>>>>
>>>>>> rpz-beta-0.1.13-13.ipfire on 2024-10-27
>>>>>> - skipped
>>>>>>
>>>>>> ---
>>>>>>
>>>>>> rpz-beta-0.1.12-12.ipfire on 2024-10-21
>>>>>> rpz.cgi:
>>>>>> - new feature: added new language file for French (thank you gw-ipfire)
>>>>>>
>>>>>> ---
>>>>>>
>>>>>> rpz-beta-0.1.11-11.ipfire on 2024-10-18
>>>>>> rpz.cgi:
>>>>>> - new feature: added new language file for Italian (thank you umberto)
>>>>>> - new feature: added new language file for Spanish (thank you Roberto)
>>>>>>
>>>>>> ---
>>>>>>
>>>>>> rpz-beta-0.1.10-10.ipfire on 2024-10-15
>>>>>> rpz-make:
>>>>>> - bug fix: corrected validation error for a custom list entry (thank you siosios)
>>>>>> - e.g., `*.cloudflare-dns.com`
>>>>>>
>>>>>> install.sh:
>>>>>> - bug fix: add chown to correct user created files
>>>>>>
>>>>>> update.sh:
>>>>>> - bug fix: add chown to correct user created files (thank you siosios)
>>>>>>
>>>>>> ---
>>>>>>
>>>>>> rpz-beta-0.1.9-9.ipfire on 2024-10-08
>>>>>> rpz.cgi:
>>>>>> - new feature: added new language file for German (thank you Leo)
>>>>>> - bug fix: add missing "rpz exitcode 110"
>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>
>>>>>> ---
>>>>>>
>>>>>> rpz-beta-0.1.8-8.ipfire on 2024-10-04
>>>>>> - skipped
>>>>>>
>>>>>> ---
>>>>>>
>>>>>> rpz-beta-0.1.7-7.ipfire on 2024-10-03
>>>>>> All:
>>>>>> - new feature: includes beta version numbers for pakfire package,
>>>>>> instead of only `rpz-1.0.0-1.ipfire`, for each release.
>>>>>>
>>>>>> rpz.cgi:
>>>>>> - new feature: added new WebGUI at `rpz.cgi`
>>>>>> - a BIG thank you to Leo Hofmann for all of his work creating the webgui!!
>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>
>>>>>> rpz-make:
>>>>>> - new feature: validate entries in allowlist and blocklist
>>>>>> - new feature: add "no-reload" option for WebGUI
>>>>>>
>>>>>> rpz-metrics:
>>>>>> - new feature: info can be sorted by name, by hit count, by line count, by
>>>>>> "enabled" list or all lists
>>>>>>
>>>>>> backups:
>>>>>> - bug fix: include all files in `/var/ipfire/dns/rpz` directory in backup
>>>>>>
>>>>>> update.sh:
>>>>>> - bug fix: corrected ownership for `/var/ipfire/dns/rpz` directory during an
>>>>>> update
>>>>>>
>>>>>> Build:
>>>>>> - bug fix: `block.rpz.conf` and `block.rpz` from build. Files to be created
>>>>>> by `rpz-make`
>>>>>>
>>>>>> WebGUI and German language file
>>>>>> Contribution-by: Leo-Andres Hofmann <hofmann(a)leo-andres.de>
>>>>>>
>>>>>> Spanish language file
>>>>>> Contribution-by: Roberto Peña
>>>>>>
>>>>>> Italian language file
>>>>>> Contribution-by: Umberto Parma
>>>>>>
>>>>>> French language file
>>>>>> Contribution-by: gw-ipfire
>>>>>>
>>>>>> Turkish language file
>>>>>> Contribution-by: Peppe Tech
>>>>>>
>>>>>> Contribution-by: Bernhard Bitsch <bbitsch(a)ipfire.org>
>>>>>> Contribution-by: Erik Kapfer <erik.kapfer(a)ipfire.org>
>>>>>> Signed-off-by: Jon Murphy <jon.murphy(a)ipfire.org
>>>>>> ---
>>>>>> config/backup/includes/rpz | 4 +
>>>>>> config/cfgroot/manualpages | 1 +
>>>>>> config/menu/EX-rpz.menu | 6 +
>>>>>> config/rootfiles/common/configroot | 1 +
>>>>>> config/rootfiles/common/web-user-interface | 1 +
>>>>>> config/rootfiles/packages/rpz | 20 +
>>>>>> config/rpz/00-rpz.conf | 10 +
>>>>>> config/rpz/rpz-config | 130 +++
>>>>>> config/rpz/rpz-functions | 85 ++
>>>>>> config/rpz/rpz-make | 203 +++++
>>>>>> config/rpz/rpz-metrics | 170 ++++
>>>>>> config/rpz/rpz-sleep | 58 ++
>>>>>> config/rpz/rpz.de.pl | 30 +
>>>>>> config/rpz/rpz.en.pl | 30 +
>>>>>> config/rpz/rpz.es.pl | 30 +
>>>>>> config/rpz/rpz.fr.pl | 30 +
>>>>>> config/rpz/rpz.it.pl | 30 +
>>>>>> config/rpz/rpz.tr.pl | 30 +
>>>>>> html/cgi-bin/rpz.cgi | 923 +++++++++++++++++++++
>>>>>> lfs/rpz | 96 +++
>>>>>> make.sh | 3 +-
>>>>>> src/paks/rpz/install.sh | 36 +
>>>>>> src/paks/rpz/uninstall.sh | 38 +
>>>>>> src/paks/rpz/update.sh | 52 ++
>>>>>> 24 files changed, 2016 insertions(+), 1 deletion(-)
>>>>>> create mode 100644 config/backup/includes/rpz
>>>>>> create mode 100644 config/menu/EX-rpz.menu
>>>>>> create mode 100644 config/rootfiles/packages/rpz
>>>>>> create mode 100644 config/rpz/00-rpz.conf
>>>>>> create mode 100644 config/rpz/rpz-config
>>>>>> create mode 100644 config/rpz/rpz-functions
>>>>>> create mode 100644 config/rpz/rpz-make
>>>>>> create mode 100755 config/rpz/rpz-metrics
>>>>>> create mode 100755 config/rpz/rpz-sleep
>>>>>> create mode 100644 config/rpz/rpz.de.pl
>>>>>> create mode 100644 config/rpz/rpz.en.pl
>>>>>> create mode 100644 config/rpz/rpz.es.pl
>>>>>> create mode 100644 config/rpz/rpz.fr.pl
>>>>>> create mode 100644 config/rpz/rpz.it.pl
>>>>>> create mode 100644 config/rpz/rpz.tr.pl
>>>>>> create mode 100644 html/cgi-bin/rpz.cgi
>>>>>> create mode 100644 lfs/rpz
>>>>>> create mode 100644 src/paks/rpz/install.sh
>>>>>> create mode 100644 src/paks/rpz/uninstall.sh
>>>>>> create mode 100644 src/paks/rpz/update.sh
>>>>>>
>>>>>> diff --git a/config/backup/includes/rpz b/config/backup/includes/rpz
>>>>>> new file mode 100644
>>>>>> index 000000000..36513e494
>>>>>> --- /dev/null
>>>>>> +++ b/config/backup/includes/rpz
>>>>>> @@ -0,0 +1,4 @@
>>>>>> +/var/ipfire/dns/rpz/*
>>>>>> +/etc/unbound/zonefiles/allow.rpz
>>>>>> +/etc/unbound/zonefiles/block.rpz
>>>>>> +/etc/unbound/local.d/*rpz.conf
>>>>>> diff --git a/config/cfgroot/manualpages b/config/cfgroot/manualpages
>>>>>> index 1f7e01efc..d3a48c633 100644
>>>>>> --- a/config/cfgroot/manualpages
>>>>>> +++ b/config/cfgroot/manualpages
>>>>>> @@ -70,6 +70,7 @@ pakfire.cgi=configuration/ipfire/pakfire
>>>>>> wlanap.cgi=addons/wireless
>>>>>> tor.cgi=addons/tor
>>>>>> samba.cgi=addons/samba
>>>>>> +rpz.cgi=addons/rpz
>>>>>>
>>>>>> # Logs menu
>>>>>> logs.cgi/summary.dat=configuration/logs/summary
>>>>>> diff --git a/config/menu/EX-rpz.menu b/config/menu/EX-rpz.menu
>>>>>> new file mode 100644
>>>>>> index 000000000..2f4daf410
>>>>>> --- /dev/null
>>>>>> +++ b/config/menu/EX-rpz.menu
>>>>>> @@ -0,0 +1,6 @@
>>>>>> +$subipfire->{'20.rpz'} = {
>>>>>> + 'caption' => $Lang::tr{'rpz'},
>>>>>> + 'uri' => '/cgi-bin/rpz.cgi',
>>>>>> + 'title' => "RPZ",
>>>>>> + 'enabled' => 1,
>>>>>> +};
>>>>>> diff --git a/config/rootfiles/common/configroot b/config/rootfiles/common/configroot
>>>>>> index 9839eee45..b30d6aae4 100644
>>>>>> --- a/config/rootfiles/common/configroot
>>>>>> +++ b/config/rootfiles/common/configroot
>>>>>> @@ -120,6 +120,7 @@ var/ipfire/menu.d/70-log.menu
>>>>>> #var/ipfire/menu.d/EX-apcupsd.menu
>>>>>> #var/ipfire/menu.d/EX-guardian.menu
>>>>>> #var/ipfire/menu.d/EX-mympd.menu
>>>>>> +#var/ipfire/menu.d/EX-rpz.menu
>>>>>> #var/ipfire/menu.d/EX-samba.menu
>>>>>> #var/ipfire/menu.d/EX-tor.menu
>>>>>> #var/ipfire/menu.d/EX-transmission.menu
>>>>>> diff --git a/config/rootfiles/common/web-user-interface b/config/rootfiles/common/web-user-interface
>>>>>> index 816241dae..e00464076 100644
>>>>>> --- a/config/rootfiles/common/web-user-interface
>>>>>> +++ b/config/rootfiles/common/web-user-interface
>>>>>> @@ -69,6 +69,7 @@ srv/web/ipfire/cgi-bin/proxy.cgi
>>>>>> srv/web/ipfire/cgi-bin/qos.cgi
>>>>>> srv/web/ipfire/cgi-bin/remote.cgi
>>>>>> srv/web/ipfire/cgi-bin/routing.cgi
>>>>>> +#srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>> #srv/web/ipfire/cgi-bin/samba.cgi
>>>>>> srv/web/ipfire/cgi-bin/services.cgi
>>>>>> srv/web/ipfire/cgi-bin/shutdown.cgi
>>>>>> diff --git a/config/rootfiles/packages/rpz b/config/rootfiles/packages/rpz
>>>>>> new file mode 100644
>>>>>> index 000000000..1c8663049
>>>>>> --- /dev/null
>>>>>> +++ b/config/rootfiles/packages/rpz
>>>>>> @@ -0,0 +1,20 @@
>>>>>> +etc/unbound/local.d/00-rpz.conf
>>>>>> +etc/unbound/zonefiles
>>>>>> +etc/unbound/zonefiles/allow.rpz
>>>>>> +usr/sbin/rpz-config
>>>>>> +usr/sbin/rpz-functions
>>>>>> +usr/sbin/rpz-make
>>>>>> +usr/sbin/rpz-metrics
>>>>>> +usr/sbin/rpz-sleep
>>>>>> +var/ipfire/addon-lang/rpz.de.pl
>>>>>> +var/ipfire/addon-lang/rpz.en.pl
>>>>>> +var/ipfire/addon-lang/rpz.es.pl
>>>>>> +var/ipfire/addon-lang/rpz.fr.pl
>>>>>> +var/ipfire/addon-lang/rpz.it.pl
>>>>>> +var/ipfire/addon-lang/rpz.tr.pl
>>>>>> +var/ipfire/backup/addons/includes/rpz
>>>>>> +var/ipfire/dns/rpz
>>>>>> +var/ipfire/dns/rpz/allowlist
>>>>>> +var/ipfire/dns/rpz/blocklist
>>>>>> +var/ipfire/menu.d/EX-rpz.menu
>>>>>> +srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>> diff --git a/config/rpz/00-rpz.conf b/config/rpz/00-rpz.conf
>>>>>> new file mode 100644
>>>>>> index 000000000..f005a4f2e
>>>>>> --- /dev/null
>>>>>> +++ b/config/rpz/00-rpz.conf
>>>>>> @@ -0,0 +1,10 @@
>>>>>> +server:
>>>>>> + module-config: "respip validator iterator"
>>>>>> +
>>>>>> +rpz:
>>>>>> + name: allow.rpz
>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>> + rpz-action-override: passthru
>>>>>> + rpz-log: yes
>>>>>> + rpz-log-name: allow
>>>>>> + rpz-signal-nxdomain-ra: yes
>>>>>> diff --git a/config/rpz/rpz-config b/config/rpz/rpz-config
>>>>>> new file mode 100644
>>>>>> index 000000000..c72d50f9b
>>>>>> --- /dev/null
>>>>>> +++ b/config/rpz/rpz-config
>>>>>> @@ -0,0 +1,130 @@
>>>>>> +#!/bin/bash
>>>>>> +###############################################################################
>>>>>> +# #
>>>>>> +# IPFire.org - A linux based firewall #
>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info(a)ipfire.org> #
>>>>>> +# #
>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>> +# #
>>>>>> +###############################################################################
>>>>>> +
>>>>>> +version="2025-01-11 - v44"
>>>>>> +
>>>>>> +############### Functions ###############
>>>>>> +
>>>>>> +source /usr/sbin/rpz-functions
>>>>>> +
>>>>>> +############### Main ###############
>>>>>> +
>>>>>> +tagName="unbound"
>>>>>> +
>>>>>> +rpzAction="${1}" # input RPZ action
>>>>>> +rpzName="${2}" # input RPZ name
>>>>>> +rpzURL="${3}" # input RPZ URL
>>>>>> +rpzOption1="${4}" # input RPZ option #1
>>>>>> +rpzOption2="${5}" # input RPZ option #2
>>>>>> +
>>>>>> +rpzConfig="/etc/unbound/local.d/${rpzName}.rpz.conf" # output zone conf file
>>>>>> +rpzFile="/etc/unbound/zonefiles/${rpzName}.rpz" # output for RPZ file
>>>>>> +
>>>>>> +rpzLog="yes" # log default is yes
>>>>>> +ucReload="yes" # reload default is yes
>>>>>> +
>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>> + case "$1" in
>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>> + esac
>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>> +done
>>>>>> +
>>>>>> +case "${rpzAction}" in
>>>>>> + # add new rpz list
>>>>>> + add )
>>>>>> + check_name "${rpzName}" # is this a valid name?
>>>>>> + # does this config already exist? If yes, then exit
>>>>>> + if [[ -f "${rpzConfig}" ]] ; then
>>>>>> + msg_log "error: rpz: duplicate - ${rpzConfig} already exists. exit"
>>>>>> + exit 104
>>>>>> + fi
>>>>>> +
>>>>>> + # is this a valid URL?
>>>>>> + regex='^https://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
>>>>>> + if ! [[ "${rpzURL}" =~ $regex ]] ; then
>>>>>> + msg_log "error: rpz: the URL is not valid: \"${rpzURL}\". exit."
>>>>>> + exit 105
>>>>>> + fi
>>>>>> +
>>>>>> + # create the zone config file
>>>>>> + {
>>>>>> + echo "rpz:"
>>>>>> + echo " name: ${rpzName}.rpz"
>>>>>> + echo " zonefile: ${rpzFile}"
>>>>>> + echo " url: ${rpzURL}"
>>>>>> + echo " rpz-action-override: nxdomain"
>>>>>> + echo " rpz-log: ${rpzLog}"
>>>>>> + echo " rpz-log-name: ${rpzName}"
>>>>>> + echo " rpz-signal-nxdomain-ra: yes"
>>>>>> + } > "${rpzConfig}"
>>>>>> +
>>>>>> + # set-up zonefile
>>>>>> + # create an empty rpz file if it does not exist
>>>>>> + if [[ ! -f "${rpzFile}" ]] ; then
>>>>>> + touch "${rpzFile}"
>>>>>> + # unbound requires these settings for rpz files
>>>>>> + set_permissions "${rpzFile}" "${rpzConfig}"
>>>>>> + fi
>>>>>> + ;;
>>>>>> +
>>>>>> + # trash config file & rpz file
>>>>>> + remove )
>>>>>> + if ! [[ -f "${rpzConfig}" ]] ; then
>>>>>> + msg_log "error: rpz: cannot remove ${rpzConfig}, does not exist. exit"
>>>>>> + exit 106
>>>>>> + fi
>>>>>> +
>>>>>> + msg_log "info: rpz: remove config file & rpz file \"${rpzName}\""
>>>>>> + rm "${rpzConfig}"
>>>>>> + rm "${rpzFile}"
>>>>>> + ;;
>>>>>> +
>>>>>> + reload )
>>>>>> + check_unbound_conf "${checkConf}"
>>>>>> + ;;
>>>>>> +
>>>>>> + list )
>>>>>> + awk -F':' '/^\s*name:/{ gsub(/[[:blank:]]|\.rpz/, "",$2) ; NAME=$2 } \
>>>>>> + /^\s*url:/{ gsub(/[[:blank:]]/, "") ; print NAME"="$2":"$3} ' \
>>>>>> + /etc/unbound/local.d/*rpz.conf
>>>>>> + exit
>>>>>> + ;;
>>>>>> +
>>>>>> + unbound-restart )
>>>>>> + check_unbound_conf "${checkConf}"
>>>>>> + unbound_restart
>>>>>> + exit
>>>>>> + ;;
>>>>>> +
>>>>>> + * )
>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>> + printf "Usage: $(basename "$0") <ACTION> <NAME> <URL> <OPTION> <OPTION>\n"
>>>>>> + printf "Version: ${version}\n"
>>>>>> + exit 108
>>>>>> + ;;
>>>>>> +
>>>>>> +esac
>>>>>> +
>>>>>> +unbound_control_reload "${ucReload}"
>>>>>> +
>>>>>> +exit
>>>>>> diff --git a/config/rpz/rpz-functions b/config/rpz/rpz-functions
>>>>>> new file mode 100644
>>>>>> index 000000000..ace1d2690
>>>>>> --- /dev/null
>>>>>> +++ b/config/rpz/rpz-functions
>>>>>> @@ -0,0 +1,85 @@
>>>>>> +#!/bin/bash
>>>>>> +###############################################################################
>>>>>> +# #
>>>>>> +# IPFire.org - A linux based firewall #
>>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>>> +# #
>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>> +# #
>>>>>> +###############################################################################
>>>>>> +
>>>>>> +version="2024-12-10 - v02"
>>>>>> +
>>>>>> +############### Functions ###############
>>>>>> +
>>>>>> +msg_log () {
>>>>>> + logger --tag "${tagName}" "$*"
>>>>>> + if tty --silent ; then
>>>>>> + echo "${tagName}:" "$*"
>>>>>> + fi
>>>>>> +}
>>>>>> +
>>>>>> +# check for a valid name
>>>>>> +check_name () {
>>>>>> + local theName="${1}"
>>>>>> +
>>>>>> + regex='^[a-zA-Z0-9_]+$' # no dash or plus, alpha numeric only
>>>>>> + regex1='^(allow|block)$' # allow and block are reserved NAMEs
>>>>>> + if [[ ! "${theName}" =~ $regex ]] || [[ "${theName}" =~ $regex1 ]] ; then
>>>>>> + msg_log "error: rpz: the NAME is not valid: \"${theName}\". exit."
>>>>>> + exit 101
>>>>>> + fi
>>>>>> +}
>>>>>> +
>>>>>> +set_permissions () {
>>>>>> + chown nobody:nobody "$@"
>>>>>> + chmod 644 "$@"
>>>>>> +}
>>>>>> +
>>>>>> +check_unbound_conf () {
>>>>>> + local thecheckConf="${1:-yes}" # check config default is yes
>>>>>> +
>>>>>> + # check the above config files
>>>>>> + if [[ "${thecheckConf}" == yes ]] ; then
>>>>>> + msg_log "info: rpz: check for errors with \"unbound-checkconf\""
>>>>>> +
>>>>>> + if ! unbound-checkconf ; then
>>>>>> + msg_log "error: rpz: unbound-checkconf found invalid configuration."
>>>>>> + msg_log \
>>>>>> + "error: rpz: In Terminal run the command \"unbound-checkconf\" for more information. exit."
>>>>>> + exit 102
>>>>>> + fi
>>>>>> + fi
>>>>>> +}
>>>>>> +
>>>>>> +unbound_control_reload () {
>>>>>> + local theReload="${1:-yes}" # reload default is yes
>>>>>> +
>>>>>> + if [[ "${theReload}" == yes ]] ; then
>>>>>> + # reload due to the changes
>>>>>> + msg_log "info: rpz: run \"unbound-control reload\""
>>>>>> +
>>>>>> + if ! unbound-control reload ; then
>>>>>> + msg_log "error: rpz: unbound-control reload. exit."
>>>>>> + exit 109
>>>>>> + fi
>>>>>> + fi
>>>>>> +}
>>>>>> +
>>>>>> +unbound_restart () {
>>>>>> + # restart due to the changes
>>>>>> + msg_log "info: rpz: run \"unbound restart\""
>>>>>> +
>>>>>> + /usr/local/bin/unboundctrl restart
>>>>>> +}
>>>>>> diff --git a/config/rpz/rpz-make b/config/rpz/rpz-make
>>>>>> new file mode 100644
>>>>>> index 000000000..927d55170
>>>>>> --- /dev/null
>>>>>> +++ b/config/rpz/rpz-make
>>>>>> @@ -0,0 +1,203 @@
>>>>>> +#!/bin/bash
>>>>>> +###############################################################################
>>>>>> +# #
>>>>>> +# IPFire.org - A linux based firewall #
>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info(a)ipfire.org> #
>>>>>> +# #
>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>> +# #
>>>>>> +###############################################################################
>>>>>> +
>>>>>> +version="2025-01-11 - v14"
>>>>>> +
>>>>>> +############### Functions ###############
>>>>>> +
>>>>>> +source /usr/sbin/rpz-functions
>>>>>> +
>>>>>> +# create the config file for allow
>>>>>> +make_allow_config () {
>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>> + local theConfig="/etc/unbound/local.d/00-rpz.conf" # output zone conf file
>>>>>> + local theList="/var/ipfire/dns/rpz/allowlist" # input custom list of domains
>>>>>> +
>>>>>> + msg_log "info: rpz: make config file \"00-rpz.conf\""
>>>>>> +
>>>>>> + echo "server:
>>>>>> + module-config: \"respip validator iterator\"" > "${theConfig}"
>>>>>> +
>>>>>> + # does allow list exist?
>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>> +
>>>>>> + echo "rpz:
>>>>>> + name: allow.rpz
>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>> + rpz-action-override: passthru
>>>>>> + rpz-log: ${theLog}
>>>>>> + rpz-log-name: allow
>>>>>> + rpz-signal-nxdomain-ra: yes" >> "${theConfig}"
>>>>>> +
>>>>>> + fi
>>>>>> +
>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>> + set_permissions "${theConfig}"
>>>>>> +}
>>>>>> +
>>>>>> +# create the config file for block
>>>>>> +make_block_config () {
>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>> + local theConfig="/etc/unbound/local.d/block.rpz.conf" # output zone conf file
>>>>>> + local theList="/var/ipfire/dns/rpz/blocklist" # input custom list of domains
>>>>>> +
>>>>>> + msg_log "info: rpz: make config file \"block.rpz.conf\""
>>>>>> +
>>>>>> + # does block list exist?
>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>> +
>>>>>> + echo "rpz:
>>>>>> + name: block.rpz
>>>>>> + zonefile: /etc/unbound/zonefiles/block.rpz
>>>>>> + rpz-action-override: nxdomain
>>>>>> + rpz-log: ${theLog}
>>>>>> + rpz-log-name: block
>>>>>> + rpz-signal-nxdomain-ra: yes" > "${theConfig}"
>>>>>> +
>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>> + set_permissions "${theConfig}"
>>>>>> + else
>>>>>> + # no - trash the config file
>>>>>> + rm --verbose /etc/unbound/local.d/block.rpz.conf
>>>>>> + fi
>>>>>> +}
>>>>>> +
>>>>>> +# create an RPZ file for allow or block
>>>>>> +make_rpz_file () {
>>>>>> + local theName="${1}" # allow or block
>>>>>> + local theAction='.' # the default is nxdomain or block
>>>>>> + local actionList
>>>>>> +
>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>> + local theZonefile="/etc/unbound/zonefiles/${theName}.rpz" # output file for RPZ
>>>>>> +
>>>>>> + # does a list exist?
>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>> +
>>>>>> + # for allow set to passthru
>>>>>> + [[ "${theName}" == allow ]] && theAction='rpz-passthru.'
>>>>>> +
>>>>>> + # drop any extra "blanks" and add "CNAME <RPZ action>." to each line
>>>>>> + actionList=$( awk '{$1=$1};1' "${theList}" |
>>>>>> + sed "/^[^;].*[[:alnum:]]/ s|$| CNAME ${theAction}|" )
>>>>>> +
>>>>>> + msg_log "info: rpz: create zonefile for \"${theName}list\""
>>>>>> +
>>>>>> +echo "; Name: ${theName} list
>>>>>> +; Last modified: $(date "+%Y-%m-%d at %H.%M.%S %Z")
>>>>>> +;
>>>>>> +; domains with actions list
>>>>>> +;
>>>>>> +${actionList}" > "${theZonefile}"
>>>>>> +
>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>> + set_permissions "${theZonefile}"
>>>>>> + # set-up allow/block list files
>>>>>> + set_permissions "${theList}"
>>>>>> + else
>>>>>> + msg_log "info: rpz: the ${theList} is empty."
>>>>>> +
>>>>>> + rm --verbose "${theZonefile}" # trash the RPZ file
>>>>>> + fi
>>>>>> +}
>>>>>> +
>>>>>> +# check if allow/block list is valid
>>>>>> +validate_list () {
>>>>>> + local theName="${1}" # allow or block
>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>> +
>>>>>> + # remove good:
>>>>>> + # - properly formated domain names with or without leading wildcard
>>>>>> + # - properly formated top level domain (TLD) names with wildcard
>>>>>> + # - blank lines and comment lines
>>>>>> + # remaining lines are considered "bad"
>>>>>> + bad_lines=$( sed --regexp-extended \
>>>>>> + '/^(\*\.)?([a-zA-Z0-9](([a-zA-Z0-9\-]){0,61}[a-zA-Z0-9])?\.)+([a-zA-Z]{2,}|xn--[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])$/d ;
>>>>>> + /^(\*\.)([a-z]{2,61}|xn--[a-z0-9]{1,60})$/d ;
>>>>>> + /^$/d ; /^;/d' "${theList}" )
>>>>>> +
>>>>>> + if [[ ! -z "${bad_lines}" ]] ; then
>>>>>> + msg_log "error: rpz: invalid line(s) in ${theList}."
>>>>>> + printf "%s\n" "bad line(s): ${bad_lines}"
>>>>>> + exit 110
>>>>>> + fi
>>>>>> +}
>>>>>> +
>>>>>> +
>>>>>> +############### Main ###############
>>>>>> +
>>>>>> +tagName="unbound"
>>>>>> +
>>>>>> +rpzName="${1}" # input RPZ name
>>>>>> +
>>>>>> +rpzLog="yes" # log default is yes
>>>>>> +ucReload="yes" # reload default is yes
>>>>>> +
>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>> + case "$1" in
>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>> + esac
>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>> +done
>>>>>> +
>>>>>> +case "${rpzName}" in
>>>>>> + # make a new allow or block rpz file
>>>>>> +
>>>>>> + allow )
>>>>>> + validate_list 'allow' # is the allowlist valid?
>>>>>> + make_allow_config "${rpzLog}"
>>>>>> + make_rpz_file 'allow'
>>>>>> + ;;
>>>>>> +
>>>>>> + allowblock )
>>>>>> + validate_list 'allow' # is the list valid?
>>>>>> + make_allow_config "${rpzLog}"
>>>>>> + make_rpz_file 'allow'
>>>>>> + ;&
>>>>>> +
>>>>>> + block )
>>>>>> + validate_list 'block' # is the blocklist valid?
>>>>>> + make_block_config "${rpzLog}"
>>>>>> + make_rpz_file 'block'
>>>>>> + ;;
>>>>>> +
>>>>>> + reload )
>>>>>> + check_unbound_conf "${checkConf}"
>>>>>> + ;;
>>>>>> +
>>>>>> + unbound-restart )
>>>>>> + check_unbound_conf "${checkConf}"
>>>>>> + unbound_restart
>>>>>> + exit
>>>>>> + ;;
>>>>>> +
>>>>>> + * )
>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>> + printf "Usage: $(basename "$0") <NAME> <OPTION> <OPTION>\n"
>>>>>> + printf "Version: ${version}\n"
>>>>>> + exit 108
>>>>>> + ;;
>>>>>> +esac
>>>>>> +
>>>>>> +unbound_control_reload "${ucReload}"
>>>>>> +
>>>>>> +exit
>>>>>> diff --git a/config/rpz/rpz-metrics b/config/rpz/rpz-metrics
>>>>>> new file mode 100755
>>>>>> index 000000000..4d43e1629
>>>>>> --- /dev/null
>>>>>> +++ b/config/rpz/rpz-metrics
>>>>>> @@ -0,0 +1,170 @@
>>>>>> +#!/bin/bash
>>>>>> +###############################################################################
>>>>>> +# #
>>>>>> +# IPFire.org - A linux based firewall #
>>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>>> +# #
>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>> +# #
>>>>>> +###############################################################################
>>>>>> +
>>>>>> +version="2025-01-20 - v25"
>>>>>> +
>>>>>> +############### Main ###############
>>>>>> +
>>>>>> +weeks="2" # default to two message logs
>>>>>> +sortBy="name" # default "by name"
>>>>>> +rpzActive="enabled" # default "enabled only"
>>>>>> +
>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>> + case "$1" in
>>>>>> + --by-names | --by-name | name ) sortBy="name" ;;
>>>>>> +
>>>>>> + --by-hits | --by-hit | hits | hit ) sortBy="hit" ;;
>>>>>> +
>>>>>> + --by-lines | --by-line | lines | line ) sortBy="line" ;;
>>>>>> +
>>>>>> + --by-effect ) sortBy="effect" ;;
>>>>>> +
>>>>>> + --enabled-only ) rpzActive="enabled" ;;
>>>>>> +
>>>>>> + --active-all | --all | all ) rpzActive="all" ;;
>>>>>> +
>>>>>> + [0-9] | [0-9][0-9] ) weeks=$1 ;;
>>>>>> + esac
>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>> +done
>>>>>> +
>>>>>> +# get the list of message logs for N weeks
>>>>>> +messageLogs=$( find /var/log/messages* -type f | sort --version-sort |
>>>>>> + head -"${weeks}" )
>>>>>> +
>>>>>> +# get the list of RPZ names & counts from the message log(s)
>>>>>> +rpzNameCount=$( for logf in ${messageLogs} ; do
>>>>>> + zgrep --text --fixed-strings 'info: rpz: applied' "${logf}" |
>>>>>> + awk '$10 ~ /\[\w*]/ { print $10 }' ;
>>>>>> + done | sort | uniq --count )
>>>>>> +
>>>>>> +# flip results and remove brackets `[` and `]`
>>>>>> +rpzNameCount=$( echo "${rpzNameCount}" |
>>>>>> + awk '{ print $2, $1 }' |
>>>>>> + sed --regexp-extended 's|^\[(.*)\]|\1|' )
>>>>>> +
>>>>>> +# grab only names
>>>>>> +rpzNames=$( echo "${rpzNameCount}" | awk '{ print $1 }' )
>>>>>> +
>>>>>> +# get list of RPZ files
>>>>>> +rpzFileList=$( find /etc/unbound/zonefiles -type f -iname "*.rpz" )
>>>>>> +
>>>>>> +# get basename of those files
>>>>>> +rpzBaseNames=$( echo "${rpzFileList}" |
>>>>>> + sed 's|/etc/unbound/zonefiles/||g ; s|\.rpz||g ;' )
>>>>>> +
>>>>>> +# add to rpzNames
>>>>>> +rpzNames="${rpzNames}"$'\n'"${rpzBaseNames}"
>>>>>> +
>>>>>> +# drop duplicate names
>>>>>> +rpzNames=$( echo "${rpzNames}" | sort --unique )
>>>>>> +
>>>>>> +# get line count for each RPZ
>>>>>> +lineCount=$( echo "${rpzFileList}" | xargs wc -l )
>>>>>> +
>>>>>> +# get comment line count and blank line count for each RPZ
>>>>>> +commentCount=$( echo "${rpzFileList}" | xargs grep --count -e "^$" -e "^;" )
>>>>>> +
>>>>>> +# get modified date each RPZ
>>>>>> +modDateList=$( echo "${rpzFileList}" | xargs stat -c '%.10y %n' )
>>>>>> +
>>>>>> +ucListAuthZones=$( unbound-control list_auth_zones )
>>>>>> +
>>>>>> +# get width of RPZ names
>>>>>> +pWidth=$( echo "${rpzNames}" | awk '{ print $1" " }' | wc -L )
>>>>>> +pFormat="%-${pWidth}s %-8s %-8s %8s %12s %12s\n"
>>>>>> +
>>>>>> +# print title line
>>>>>> +printf "${pFormat}" "name" "hits" "active" "lines" " hits/line" "last download"
>>>>>> +printf -- "--------------"
>>>>>> +
>>>>>> +theResults=""
>>>>>> +totalLines=0
>>>>>> +totalHits=0
>>>>>> +while read -r theName
>>>>>> +do
>>>>>> + printf -- "--" # pretend progress bar
>>>>>> +
>>>>>> + # is this RPZ list active?
>>>>>> + theActive="disabled"
>>>>>> + if grep --quiet "^${theName}\.rpz" <<< "${ucListAuthZones}"
>>>>>> + then
>>>>>> + theActive="enabled"
>>>>>> + else
>>>>>> + [[ "${rpzActive}" == enabled ]] && continue
>>>>>> + fi
>>>>>> +
>>>>>> + # get hit count
>>>>>> + theHits="0"
>>>>>> + if output=$( grep "^${theName}\s" <<< "${rpzNameCount}" ) ; then
>>>>>> + theHits=$( echo "${output}" | awk '{ print $2 }' )
>>>>>> + totalHits=$(( totalHits + theHits ))
>>>>>> + fi
>>>>>> +
>>>>>> + # get line count
>>>>>> + theLines="n/a"
>>>>>> + hitsPerLine="0"
>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${lineCount}" ) ; then
>>>>>> + theLines=$( echo "${output}" | awk '{ print $1 }' )
>>>>>> + totalLines=$(( totalLines + theLines ))
>>>>>> +
>>>>>> + if [[ "${theLines}" -gt 2 ]] ; then
>>>>>> + hitsPerLine=$(( 100 * theHits / theLines ))
>>>>>> + fi
>>>>>> + fi
>>>>>> +
>>>>>> + # get modification date
>>>>>> + theModDate="n/a"
>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${modDateList}" ) ; then
>>>>>> + theModDate=$( echo "${output}" | awk '{ print $1 }' )
>>>>>> + fi
>>>>>> +
>>>>>> + # add to results list
>>>>>> + theResults+="${theName} ${theHits} ${theActive} ${theLines} ${hitsPerLine} ${theModDate}"$'\n'
>>>>>> +
>>>>>> +done <<< "${rpzNames}"
>>>>>> +
>>>>>> +case "${sortBy}" in
>>>>>> + # sort by "active" then by "name"
>>>>>> + name) sortArg=(-k3,3r -k1,1) ;;
>>>>>> +
>>>>>> + # sort by "active" then by "hits" then by "name"
>>>>>> + hit) sortArg=(-k3,3r -k2,2nr -k1,1) ;;
>>>>>> +
>>>>>> + # sort by "active" then by "lines" then by "name"
>>>>>> + line) sortArg=(-k3,3r -k4,4nr -k1,1) ;;
>>>>>> +
>>>>>> + # sort by "active" then by "effect" then by "name"
>>>>>> + effect) sortArg=(-k3,3r -k5,5nr -k1,1) ;;
>>>>>> +esac
>>>>>> +
>>>>>> +printf -- "--------------\n"
>>>>>> +# remove blank lines, sort, print as columns
>>>>>> +echo "${theResults}" |
>>>>>> + awk '!/^[[:space:]]*$/' |
>>>>>> + sort "${sortArg[@]}" |
>>>>>> + awk --assign=width="${pWidth}" \
>>>>>> + '{ printf "%-*s %-8s %-8s %8s %10s %% %12s\n", width, $1, $2, $3, $4, $5, $6 }'
>>>>>> +
>>>>>> +printf "${pFormat}" "" "=======" "" "========" "" ""
>>>>>> +printf "${pFormat}" "Totals -->" "${totalHits}" "" "${totalLines}" "" ""
>>>>>> +
>>>>>> +exit
>>>>>> diff --git a/config/rpz/rpz-sleep b/config/rpz/rpz-sleep
>>>>>> new file mode 100755
>>>>>> index 000000000..dd3603599
>>>>>> --- /dev/null
>>>>>> +++ b/config/rpz/rpz-sleep
>>>>>> @@ -0,0 +1,58 @@
>>>>>> +#!/bin/bash
>>>>>> +###############################################################################
>>>>>> +# #
>>>>>> +# IPFire.org - A linux based firewall #
>>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>>> +# #
>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>> +# #
>>>>>> +###############################################################################
>>>>>> +
>>>>>> +version="2024-08-16" # v05
>>>>>> +
>>>>>> +############### Functions ###############
>>>>>> +
>>>>>> +# send message to message log
>>>>>> +msg_log () {
>>>>>> + logger --tag "${tagName}" "$*"
>>>>>> + if tty --silent ; then
>>>>>> + echo "${tagName}:" "$*"
>>>>>> + fi
>>>>>> +}
>>>>>> +
>>>>>> +############### Main ###############
>>>>>> +
>>>>>> +tagName="unbound"
>>>>>> +
>>>>>> +sleepTime="${1:-5m}" # default to sleep for 5m (5 minutes)
>>>>>> +
>>>>>> +zoneList=$( unbound-control list_auth_zones | awk '{print $1}' )
>>>>>> +
>>>>>> +for zone in ${zoneList} ; do
>>>>>> + printf "disable ${zone}\t"
>>>>>> + unbound-control rpz_disable "${zone}"
>>>>>> +done
>>>>>> +
>>>>>> +msg_log "info: rpz: disabled all zones for ${sleepTime}"
>>>>>> +
>>>>>> +sleep "${sleepTime}"
>>>>>> +
>>>>>> +for zone in ${zoneList} ; do
>>>>>> + printf "enable ${zone}\t"
>>>>>> + unbound-control rpz_enable "${zone}"
>>>>>> +done
>>>>>> +
>>>>>> +msg_log "info: rpz: enabled all zones"
>>>>>> +
>>>>>> +exit
>>>>>> diff --git a/config/rpz/rpz.de.pl b/config/rpz/rpz.de.pl
>>>>>> new file mode 100644
>>>>>> index 000000000..3770c6bb0
>>>>>> --- /dev/null
>>>>>> +++ b/config/rpz/rpz.de.pl
>>>>>> @@ -0,0 +1,30 @@
>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>> +%tr = (%tr,
>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>> +'rpz apply' => 'Übernehmen',
>>>>>> +'rpz cl allow enable' => 'Benutzerdefinierte Allowlist aktivieren:',
>>>>>> +'rpz cl allow info' => 'Zugelassene Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>> +'rpz cl allow' => 'Benutzerdefinierte Allowlist',
>>>>>> +'rpz cl block enable' => 'Benutzerdefinierte Blocklist aktivieren:',
>>>>>> +'rpz cl block info' => 'Gesperrte Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>> +'rpz cl block' => 'Benutzerdefinierte Blocklist',
>>>>>> +'rpz cl' => 'Benutzerdefinierte Listen',
>>>>>> +'rpz exitcode 101' => 'Der Name enthält unzulässige Zeichen',
>>>>>> +'rpz exitcode 102' => 'unbound-checkconf hat eine fehlerhafte Konfiguration ermittelt. Führen Sie das Kommando unbound-checkconf auf der Konsole aus, um weitere Informationen zu erhalten.',
>>>>>> +'rpz exitcode 103' => 'Die benutzerdefinierte Allow-/Blocklist ist leer',
>>>>>> +'rpz exitcode 104' => 'Ein Eintrag mit identischem Namen existiert bereits',
>>>>>> +'rpz exitcode 105' => 'Die URL ist ungültig',
>>>>>> +'rpz exitcode 106' => 'Eintrag kann nicht entfernt werden, der Name existiert nicht',
>>>>>> +'rpz exitcode 107' => 'Der Name ist ungültig - nur "allow" oder "block" möglich',
>>>>>> +'rpz exitcode 108' => 'Fehlende oder inkorrekte Parameter',
>>>>>> +'rpz exitcode 109' => 'unbound-control reload ist fehlgeschlagen',
>>>>>> +'rpz exitcode 110' => 'Die benutzerdefinierte Allow-/Blocklist enthält unzulässige Einträge',
>>>>>> +'rpz exitcode 201' => 'Die Anmerkung enthält unzulässige Zeichen',
>>>>>> +'rpz exitcode 202' => 'Ungültiger Eintrag in der benutzerdefinierten Allowlist, Zeile ',
>>>>>> +'rpz exitcode 203' => 'Ungültiger Eintrag in der benutzerdefinierten Blocklist, Zeile ',
>>>>>> +'rpz exitcode 204' => 'Ausgewählter Eintrag existiert nicht: ',
>>>>>> +'rpz zf editor' => 'Zonendatei-Eintrag bearbeiten',
>>>>>> +'rpz zf imported' => '(importiert aus rpz-config)',
>>>>>> +'rpz zf remark info' => 'Erlaubte Zeichen sind a-z, A-Z, 0-9 und Unterstriche',
>>>>>> +'rpz zf' => 'Zonendateien',
>>>>>> +);
>>>>>> diff --git a/config/rpz/rpz.en.pl b/config/rpz/rpz.en.pl
>>>>>> new file mode 100644
>>>>>> index 000000000..0720a8940
>>>>>> --- /dev/null
>>>>>> +++ b/config/rpz/rpz.en.pl
>>>>>> @@ -0,0 +1,30 @@
>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>> +%tr = (%tr,
>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>> +'rpz apply' => 'Apply',
>>>>>> +'rpz cl allow enable' => 'Enable custom allowlist:',
>>>>>> +'rpz cl allow info' => 'Allowed domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>> +'rpz cl allow' => 'Custom allowlist',
>>>>>> +'rpz cl block enable' => 'Enable custom blocklist:',
>>>>>> +'rpz cl block info' => 'Blocked domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>> +'rpz cl block' => 'Custom blocklist',
>>>>>> +'rpz cl' => 'Custom lists',
>>>>>> +'rpz exitcode 101' => 'the NAME is not valid',
>>>>>> +'rpz exitcode 102' => 'unbound-checkconf found invalid configuration. In the Terminal run the command unbound-checkconf for more information',
>>>>>> +'rpz exitcode 103' => 'the allow/block list is empty',
>>>>>> +'rpz exitcode 104' => 'duplicate - NAME already exists',
>>>>>> +'rpz exitcode 105' => 'the URL is not valid',
>>>>>> +'rpz exitcode 106' => 'cannot remove the NAME does not exist',
>>>>>> +'rpz exitcode 107' => 'the NAME is not valid - "allow" or "block" only',
>>>>>> +'rpz exitcode 108' => 'missing or incorrect parameter',
>>>>>> +'rpz exitcode 109' => 'unbound-control reload failed',
>>>>>> +'rpz exitcode 110' => 'custom Allowlist/Blocklist contains invalid entries',
>>>>>> +'rpz exitcode 201' => 'the REMARK is not valid',
>>>>>> +'rpz exitcode 202' => 'invalid entry in allowlist, line ',
>>>>>> +'rpz exitcode 203' => 'invalid entry in blocklist, line ',
>>>>>> +'rpz exitcode 204' => 'Selected entry does not exist: ',
>>>>>> +'rpz zf editor' => 'Edit zonefiles entry',
>>>>>> +'rpz zf imported' => '(imported from rpz-config)',
>>>>>> +'rpz zf remark info' => 'Valid characters are a-z, A-Z, 0-9 and underscore.',
>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>> +);
>>>>>> diff --git a/config/rpz/rpz.es.pl b/config/rpz/rpz.es.pl
>>>>>> new file mode 100644
>>>>>> index 000000000..98628e4aa
>>>>>> --- /dev/null
>>>>>> +++ b/config/rpz/rpz.es.pl
>>>>>> @@ -0,0 +1,30 @@
>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>> +%tr = (%tr,
>>>>>> +'rpz' => 'Zonas de política de respuesta (RPZ)',
>>>>>> +'rpz apply' => 'Aplicar',
>>>>>> +'rpz cl allow enable' => 'Habilitar la lista blanca personalizada:',
>>>>>> +'rpz cl allow info' => 'Dominio permitido (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>> +'rpz cl allow' => 'Lista blanca personalizada',
>>>>>> +'rpz cl block enable' => 'Habilitar la lista negra personalizada:',
>>>>>> +'rpz cl block info' => 'Dominio bloqueado (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>> +'rpz cl block' => 'Lista negra personalizada',
>>>>>> +'rpz cl' => 'Lista personalizada',
>>>>>> +'rpz exitcode 101' => 'El NOMBRE no es válido',
>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha encontrado una configuración no válida. Desde Terminal, ejecute el comando unbound-checkconf para mayor información',
>>>>>> +'rpz exitcode 103' => 'La lista de permitidos/bloqueados está vacía',
>>>>>> +'rpz exitcode 104' => 'duplicado - NOMBRE ya existe',
>>>>>> +'rpz exitcode 105' => 'la URL no es válida',
>>>>>> +'rpz exitcode 106' => 'no es posible eliminar el NOMBRE que no existe',
>>>>>> +'rpz exitcode 107' => 'el NOMBRE no es válido - sólo "permitir" o "bloquear"',
>>>>>> +'rpz exitcode 108' => 'parámetro faltante o incorrecto',
>>>>>> +'rpz exitcode 109' => 'Error al recargar unbound-control',
>>>>>> +'rpz exitcode 110' => 'la Lista blanca/Lista negra personalizada contiene entradas no válidas',
>>>>>> +'rpz exitcode 201' => 'el COMENTARIO no es válido',
>>>>>> +'rpz exitcode 202' => 'entrada no válida en la lista blanca, línea ',
>>>>>> +'rpz exitcode 203' => 'entrada no válida en la lista negra, línea ',
>>>>>> +'rpz exitcode 204' => 'La entrada seleccionada no existe: ',
>>>>>> +'rpz zf editor' => 'Editar la entrada de archivos de zona',
>>>>>> +'rpz zf imported' => '(importado de rpz-config)',
>>>>>> +'rpz zf remark info' => 'Los caracteres válidos son a-z, A-Z, 0-9 y guión bajo',
>>>>>> +'rpz zf' => 'Archivos de zona',
>>>>>> +);
>>>>>> diff --git a/config/rpz/rpz.fr.pl b/config/rpz/rpz.fr.pl
>>>>>> new file mode 100644
>>>>>> index 000000000..f35f3c2d0
>>>>>> --- /dev/null
>>>>>> +++ b/config/rpz/rpz.fr.pl
>>>>>> @@ -0,0 +1,30 @@
>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>> +%tr = (%tr,
>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>> +'rpz apply' => 'Appliquer',
>>>>>> +'rpz cl allow enable' => 'Activer la liste d\'autorisations personnalisée:',
>>>>>> +'rpz cl allow info' => 'Domaines autorisés (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>> +'rpz cl allow' => 'Liste d\'autorisations personnalisée',
>>>>>> +'rpz cl block enable' => 'Activer la liste de blocage personnalisée:',
>>>>>> +'rpz cl block info' => 'Domaines bloqués (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>> +'rpz cl block' => 'liste de blocage personnalisé',
>>>>>> +'rpz cl' => 'Listes personnalisées',
>>>>>> +'rpz exitcode 101' => 'le NOM n\'est pas valide',
>>>>>> +'rpz exitcode 102' => 'unbound-checkconf configuration non valide trouvée. Dans le terminal, exécutez la commande unbound-checkconf pour plus d\'informations',
>>>>>> +'rpz exitcode 103' => 'la liste autoriser/bloquer est vide',
>>>>>> +'rpz exitcode 104' => 'le NOM existe déjà',
>>>>>> +'rpz exitcode 105' => 'L\'URL n\'est pas valide',
>>>>>> +'rpz exitcode 106' => 'impossible de supprimer le NOM n\'existe pas',
>>>>>> +'rpz exitcode 107' => 'le NOM n\'est pas valide - « autoriser » ou « bloquer » seulement',
>>>>>> +'rpz exitcode 108' => 'paramètre manquant ou incorrect',
>>>>>> +'rpz exitcode 109' => 'unbound-control rechargement échoué',
>>>>>> +'rpz exitcode 110' => 'la liste autoriser/bloquer contient des entrées non valides',
>>>>>> +'rpz exitcode 201' => 'la REMARQUE n\'est pas valable',
>>>>>> +'rpz exitcode 202' => 'entrée non valide dans la liste d\'autorisation, ligne ',
>>>>>> +'rpz exitcode 203' => 'entrée non valide dans la liste de blocs, ligne ',
>>>>>> +'rpz exitcode 204' => 'L\'entrée sélectionnée n\'existe pas: ',
>>>>>> +'rpz zf editor' => 'Modifier l\'entrée Fichiers Zone',
>>>>>> +'rpz zf imported' => '(importé de rpz-config)',
>>>>>> +'rpz zf remark info' => 'Les caractères valides sont a-z, A-Z, 0-9 et soulignement.',
>>>>>> +'rpz zf' => 'Fichiers Zone',
>>>>>> +);
>>>>>> diff --git a/config/rpz/rpz.it.pl b/config/rpz/rpz.it.pl
>>>>>> new file mode 100644
>>>>>> index 000000000..ee81605c9
>>>>>> --- /dev/null
>>>>>> +++ b/config/rpz/rpz.it.pl
>>>>>> @@ -0,0 +1,30 @@
>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>> +%tr = (%tr,
>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>> +'rpz apply' => 'Applica',
>>>>>> +'rpz cl allow enable' => 'Abilita la Whitelist personalizzata:',
>>>>>> +'rpz cl allow info' => 'Domini consentiti (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>> +'rpz cl allow' => 'Whitelist personalizzata',
>>>>>> +'rpz cl block enable' => 'Abilita la Blacklist personalizzata:',
>>>>>> +'rpz cl block info' => 'Domini bloccati (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>> +'rpz cl block' => 'Blacklist personalizzata',
>>>>>> +'rpz cl' => 'Liste personalizzate',
>>>>>> +'rpz exitcode 101' => 'il NOME non è valido',
>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha trovato una configurazione non valida. Dal Terminale esegui il comando unbound-checkconf per maggiori informazioni',
>>>>>> +'rpz exitcode 103' => 'l\'elenco consentiti/bloccati è vuoto',
>>>>>> +'rpz exitcode 104' => 'duplicato - NAME esiste di già',
>>>>>> +'rpz exitcode 105' => 'l\'URL non è valido',
>>>>>> +'rpz exitcode 106' => 'non è possibile rimuovere il NOME non esiste',
>>>>>> +'rpz exitcode 107' => 'il NOME non è valido - solo "consenti" o "blocca"',
>>>>>> +'rpz exitcode 108' => 'parametro mancante o non corretto',
>>>>>> +'rpz exitcode 109' => 'ricaricamento del controllo non associato non riuscito',
>>>>>> +'rpz exitcode 110' => 'la Whitelist/Blacklist personalizzata contiene voci non valide',
>>>>>> +'rpz exitcode 201' => 'l"OSSERVAZIONE non è valida',
>>>>>> +'rpz exitcode 202' => 'voce non valida nella Whitelist, riga ',
>>>>>> +'rpz exitcode 203' => 'voce non valida nella Blacklist, riga ',
>>>>>> +'rpz exitcode 204' => 'La voce selezionata non esiste: ',
>>>>>> +'rpz zf editor' => 'Modifica la voce dei file di zona',
>>>>>> +'rpz zf imported' => '(importato da rpz-config)',
>>>>>> +'rpz zf remark info' => 'I caratteri validi sono a-z, A-Z, 0-9 e trattino basso',
>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>> +);
>>>>>> diff --git a/config/rpz/rpz.tr.pl b/config/rpz/rpz.tr.pl
>>>>>> new file mode 100644
>>>>>> index 000000000..00226e192
>>>>>> --- /dev/null
>>>>>> +++ b/config/rpz/rpz.tr.pl
>>>>>> @@ -0,0 +1,30 @@
>>>>>> +# I�in eklendi Ayriyeten Response Policy Zone (RPZ)
>>>>>> +%tr = (%tr,
>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>> +'rpz apply' => 'Uygulamak',
>>>>>> +'rpz cl allow enable' => '�zel Etkinlestir allowlist:',
>>>>>> +'rpz cl allow info' => 'Izin verilmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>> +'rpz cl allow' => 'Etkinlestir allowlist',
>>>>>> +'rpz cl block enable' => '�zel Etkinlestir blocklist:',
>>>>>> +'rpz cl block info' => 'Engellenmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>> +'rpz cl block' => 'Engellenmis blocklist',
>>>>>> +'rpz cl' => 'Engellenmis lists',
>>>>>> +'rpz exitcode 101' => 'NAME ge�erli degil',
>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ge�ersiz yapilandirma bulundu. Terminalde daha fazla bilgi i�in Unbound-checkConf komutunu �alistirin',
>>>>>> +'rpz exitcode 103' => 'allow/block liste bos',
>>>>>> +'rpz exitcode 104' => 'kopyalamak - NAME zaten var',
>>>>>> +'rpz exitcode 105' => 'URL ge�erli degil',
>>>>>> +'rpz exitcode 106' => '�ikarilamiyor NAME yok',
>>>>>> +'rpz exitcode 107' => 'NAME ge�erli degil - "allow" veya "block" yalniz',
>>>>>> +'rpz exitcode 108' => 'Parametre eksik veya yanlis',
>>>>>> +'rpz exitcode 109' => 'unbound-control basarisiz',
>>>>>> +'rpz exitcode 110' => 'Engellenmis Allowlist/Blocklist ge�ersiz girisler i�erir',
>>>>>> +'rpz exitcode 201' => 'REMARK ge�erli degil',
>>>>>> +'rpz exitcode 202' => 'Ge�ersiz giris allowlist, line ',
>>>>>> +'rpz exitcode 203' => 'Ge�ersiz giris blocklist, line ',
>>>>>> +'rpz exitcode 204' => 'Se�ilen giris yok: ',
>>>>>> +'rpz zf editor' => 'yazimlamak zonefiles giris',
>>>>>> +'rpz zf imported' => '(ithal edildi rpz-config)',
>>>>>> +'rpz zf remark info' => 'Ge�erli karakterler a-z, A-Z, 0-9ve alt�st.',
>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>> +);
>>>>>> diff --git a/html/cgi-bin/rpz.cgi b/html/cgi-bin/rpz.cgi
>>>>>> new file mode 100644
>>>>>> index 000000000..a821c92ac
>>>>>> --- /dev/null
>>>>>> +++ b/html/cgi-bin/rpz.cgi
>>>>>> @@ -0,0 +1,923 @@
>>>>>> +#!/usr/bin/perl
>>>>>> +###############################################################################
>>>>>> +# #
>>>>>> +# IPFire.org - A linux based firewall #
>>>>>> +# Copyright (C) 2005-2024 IPFire Team <info(a)ipfire.org> #
>>>>>> +# #
>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>> +# #
>>>>>> +###############################################################################
>>>>>> +
>>>>>> +use strict;
>>>>>> +use Scalar::Util qw(looks_like_number);
>>>>>> +
>>>>>> +# debugging
>>>>>> +#use warnings;
>>>>>> +#use CGI::Carp 'fatalsToBrowser';
>>>>>> +#use Data::Dumper;
>>>>>> +
>>>>>> +require '/var/ipfire/general-functions.pl';
>>>>>> +require "${General::swroot}/lang.pl";
>>>>>> +require "${General::swroot}/header.pl";
>>>>>> +
>>>>>> +###--- Extra HTML ---###
>>>>>> +my $extraHead = <<END
>>>>>> +<style>
>>>>>> + /* alternating row background */
>>>>>> + .tbl tr:nth-child(2n+2) {
>>>>>> + background-color: var(--color-light-grey);
>>>>>> + }
>>>>>> + .tbl tr:nth-child(2n+3) {
>>>>>> + background-color: var(--color-grey);
>>>>>> + }
>>>>>> + /* text styles */
>>>>>> + .tbl th:not(:last-child) {
>>>>>> + text-align: left;
>>>>>> + }
>>>>>> + div.right {
>>>>>> + text-align: right;
>>>>>> + margin-top: 0.5em;
>>>>>> + }
>>>>>> + /* customlist input */
>>>>>> + textarea.domainlist {
>>>>>> + margin: 0.5em 0;
>>>>>> + resize: vertical;
>>>>>> + min-height: 10em;
>>>>>> + overflow: auto;
>>>>>> + white-space: pre;
>>>>>> + }
>>>>>> + button[type=submit]:disabled {
>>>>>> + opacity: 0.6;
>>>>>> + }
>>>>>> +</style>
>>>>>> +END
>>>>>> +;
>>>>>> +###--- End of extra HTML ---###
>>>>>> +
>>>>>> +
>>>>>> +### Settings ###
>>>>>> +
>>>>>> +# Request DNS service reload after configuration change
>>>>>> +my $RPZ_RELOAD_FLAG = "${General::swroot}/dns/rpz/reload.flag";
>>>>>> +
>>>>>> +# Configuration file for all available zonefiles
>>>>>> +# Format: index, name (unique), enabled (on/off), URL, remark
>>>>>> +my $ZONEFILES_CONF = "${General::swroot}/dns/rpz/zonefiles.conf";
>>>>>> +
>>>>>> +# Configuration file for custom lists
>>>>>> +# IDs: 0=allowlist, 1=blocklist, 2=options (allow/block enabled)
>>>>>> +my $CUSTOMLISTS_CONF = "${General::swroot}/dns/rpz/customlists.conf";
>>>>>> +
>>>>>> +# Export custom lists to rpz-config
>>>>>> +my $RPZ_ALLOWLIST = "${General::swroot}/dns/rpz/allowlist";
>>>>>> +my $RPZ_BLOCKLIST = "${General::swroot}/dns/rpz/blocklist";
>>>>>> +
>>>>>> +
>>>>>> +### Preparation ###
>>>>>> +
>>>>>> +# Create missing config files
>>>>>> +unless(-f $ZONEFILES_CONF) { &General::system('touch', "$ZONEFILES_CONF"); }
>>>>>> +unless(-f $CUSTOMLISTS_CONF) { &General::system('touch', "$CUSTOMLISTS_CONF"); }
>>>>>> +
>>>>>> +
>>>>>> +## Global gui data
>>>>>> +my $errormessage = "";
>>>>>> +
>>>>>> +## Global configuration data
>>>>>> +my %zonefiles = ();
>>>>>> +my %customlists = ();
>>>>>> +&_zonefiles_load();
>>>>>> +&_customlists_load();
>>>>>> +
>>>>>> +## Global CGI form data
>>>>>> +my %cgiparams = ();
>>>>>> +&Header::getcgihash(\%cgiparams);
>>>>>> +
>>>>>> +my $action = $cgiparams{'ACTION'} // 'NONE';
>>>>>> +my $action_key = $cgiparams{'KEY'} // ''; # entry being edited, empty = none/new
>>>>>> +
>>>>>> +
>>>>>> +###--- Process form actions ---###
>>>>>> +
>>>>>> +# Zonefiles action: Check whether the requested entry exists
>>>>>> +if((substr($action, 0, 3) eq 'ZF_') && ($action_key)) {
>>>>>> + unless(defined $zonefiles{$action_key}) {
>>>>>> + $errormessage = &_rpz_error_tr(204, $action_key);
>>>>>> + $action = 'NONE';
>>>>>> + }
>>>>>> +}
>>>>>> +
>>>>>> +## Perform actions
>>>>>> +if($action eq 'ZF_SAVE') { ## Save new or modified zonefiles entry
>>>>>> + if(&_action_zf_save()) {
>>>>>> + $action = 'NONE'; # success, return to main page
>>>>>> + &_http_prg_redirect();
>>>>>> + } else {
>>>>>> + $action = 'ZF_EDIT'; # error occured, keep editing
>>>>>> + }
>>>>>> +
>>>>>> +} elsif($action eq 'ZF_TOGGLE') { ## Toggle on/off
>>>>>> + if(&_action_zf_toggle()) {
>>>>>> + $action = 'NONE';
>>>>>> + &_http_prg_redirect();
>>>>>> + }
>>>>>> +
>>>>>> +} elsif($action eq 'ZF_REMOVE') { ## Remove entry
>>>>>> + if(&_action_zf_remove()) {
>>>>>> + $action = 'NONE';
>>>>>> + &_http_prg_redirect();
>>>>>> + }
>>>>>> +
>>>>>> +} elsif($action eq 'CL_SAVE') { ## Save custom lists
>>>>>> + if(&_action_cl_save()) {
>>>>>> + $action = 'NONE';
>>>>>> + &_http_prg_redirect();
>>>>>> + }
>>>>>> +
>>>>>> +} elsif($action eq 'RPZ_RELOAD') { ## Reload dns configuration
>>>>>> + if(&_action_rpz_reload()) {
>>>>>> + $action = 'NONE';
>>>>>> + &_http_prg_redirect();
>>>>>> + }
>>>>>> +
>>>>>> +} elsif($action eq 'UNB_RESTART') { ## Restart unbound service
>>>>>> + if(&_action_unb_restart()) {
>>>>>> + $action = 'NONE';
>>>>>> + &_http_prg_redirect();
>>>>>> + }
>>>>>> +
>>>>>> +}
>>>>>> +
>>>>>> +
>>>>>> +###--- Start GUI ---###
>>>>>> +
>>>>>> +## Start http output
>>>>>> +&Header::showhttpheaders();
>>>>>> +
>>>>>> +# Start HTML
>>>>>> +&Header::openpage($Lang::tr{'rpz'}, 1, $extraHead);
>>>>>> +&Header::openbigbox('100%', 'left', '');
>>>>>> +
>>>>>> +# Show error messages
>>>>>> +if($errormessage) {
>>>>>> + &_print_message($errormessage);
>>>>>> +}
>>>>>> +
>>>>>> +# Handle zonefile add/edit mode
>>>>>> +if($action eq "ZF_EDIT") {
>>>>>> + &_print_zonefile_editor();
>>>>>> +
>>>>>> + # Finalize page and exit cleanly
>>>>>> + &Header::closebigbox();
>>>>>> + &Header::closepage();
>>>>>> + exit(0);
>>>>>> +}
>>>>>> +
>>>>>> +# Show gui elements
>>>>>> +&_print_zonefiles();
>>>>>> +&_print_customlists();
>>>>>> +&_print_gui_extras();
>>>>>> +
>>>>>> +&Header::closebigbox();
>>>>>> +&Header::closepage();
>>>>>> +
>>>>>> +###--- End of GUI ---###
>>>>>> +
>>>>>> +
>>>>>> +###--- Internal configuration file functions ---###
>>>>>> +
>>>>>> +# Load all available zonefiles from rpz-config and the internal configuration
>>>>>> +sub _zonefiles_load {
>>>>>> + # Clean start
>>>>>> + %zonefiles = ();
>>>>>> +
>>>>>> + # Source 1: Get the currently enabled zonefiles from rpz-config (expected format [name]=[URL])
>>>>>> + my @enabled_files = &General::system_output('/usr/sbin/rpz-config', 'list');
>>>>>> +
>>>>>> + foreach my $row (@enabled_files) {
>>>>>> + chomp($row);
>>>>>> +
>>>>>> + # Use regex instead of split() to skip non-matching lines
>>>>>> + next unless($row =~ /^(\w+)=(.+)$/);
>>>>>> + my ($name, $url) = ($1, $2);
>>>>>> +
>>>>>> + # Unique names are already guaranteed by rpz-config
>>>>>> + if(&_rpz_validate_zonefile($name, $url, '', 0) == 0) {
>>>>>> + # Populate global data hash, mark all found entries as enabled
>>>>>> + my %entry = ('enabled' => 'on',
>>>>>> + 'url' => $url,
>>>>>> + 'remark' => $Lang::tr{'rpz zf imported'});
>>>>>> +
>>>>>> + $zonefiles{$name} = \%entry;
>>>>>> + }
>>>>>> + }
>>>>>> +
>>>>>> + # Source 2: Get additional data and disabled entries from configuration file
>>>>>> + my %configured_files = ();
>>>>>> + &General::readhasharray($ZONEFILES_CONF, \%configured_files);
>>>>>> +
>>>>>> + foreach my $row (values (%configured_files)) {
>>>>>> + my ($name, $enabled, $url, $remark) = @$row;
>>>>>> + $remark //= "";
>>>>>> +
>>>>>> + next unless($name);
>>>>>> +
>>>>>> + # Check whether this row belongs to an entry already imported from rpz-config
>>>>>> + if(defined $zonefiles{$name}) {
>>>>>> + # Existing entry, only merge additional data
>>>>>> + $zonefiles{$name}{'remark'} = $remark;
>>>>>> + } else {
>>>>>> + # Skip entry if it is marked as enabled but not found by rpz-config. It was then deleted manually
>>>>>> + if($enabled ne 'on') {
>>>>>> + # Populate global data hash
>>>>>> + my %entry = ('enabled' => 'off',
>>>>>> + 'url' => $url // "",
>>>>>> + 'remark' => $remark);
>>>>>> +
>>>>>> + $zonefiles{$name} = \%entry;
>>>>>> + }
>>>>>> + }
>>>>>> + }
>>>>>> +}
>>>>>> +
>>>>>> +# Save internal zonefiles configuration
>>>>>> +sub _zonefiles_save_conf {
>>>>>> + my $index = 0;
>>>>>> + my %export = ();
>>>>>> +
>>>>>> + # Loop trough all zonefiles and create "hasharray" type export
>>>>>> + foreach my $name (keys %zonefiles) {
>>>>>> + my @entry = ($name,
>>>>>> + $zonefiles{$name}{'enabled'},
>>>>>> + $zonefiles{$name}{'url'},
>>>>>> + $zonefiles{$name}{'remark'});
>>>>>> +
>>>>>> + $export{$index++} = \@entry;
>>>>>> + }
>>>>>> +
>>>>>> + &General::writehasharray($ZONEFILES_CONF, \%export);
>>>>>> +}
>>>>>> +
>>>>>> +# Load custom lists from rpz-config and the internal configuration
>>>>>> +sub _customlists_load {
>>>>>> + # Clean start
>>>>>> + %customlists = ();
>>>>>> +
>>>>>> + # Load configuration file
>>>>>> + my %lists_conf = ();
>>>>>> + &General::readhasharray($CUSTOMLISTS_CONF, \%lists_conf);
>>>>>> +
>>>>>> + # Get list options, enabled by default to start import
>>>>>> + $customlists{'allow'}{'enabled'} = $lists_conf{2}[0] // 'on';
>>>>>> + $customlists{'block'}{'enabled'} = $lists_conf{2}[1] // 'on';
>>>>>> +
>>>>>> + # Import enabled list from rpz-config, otherwise retrieve stored or empty list from configuration file
>>>>>> + if($customlists{'allow'}{'enabled'} eq 'on') {
>>>>>> + &_customlist_import('allow', $RPZ_ALLOWLIST);
>>>>>> + } else {
>>>>>> + $customlists{'allow'}{'list'} = $lists_conf{0} // [];
>>>>>> + }
>>>>>> + if($customlists{'block'}{'enabled'} eq 'on') {
>>>>>> + &_customlist_import('block', $RPZ_BLOCKLIST);
>>>>>> + } else {
>>>>>> + $customlists{'block'}{'list'} = $lists_conf{1} // [];
>>>>>> + }
>>>>>> +}
>>>>>> +
>>>>>> +# Save internal custom lists configuration
>>>>>> +sub _customlists_save_conf {
>>>>>> + my %export = ();
>>>>>> +
>>>>>> + # Match IDs with import function
>>>>>> + $export{0} = $customlists{'allow'}{'list'};
>>>>>> + $export{1} = $customlists{'block'}{'list'};
>>>>>> + $export{2} = [$customlists{'allow'}{'enabled'}, $customlists{'block'}{'enabled'}];
>>>>>> +
>>>>>> + &General::writehasharray($CUSTOMLISTS_CONF, \%export);
>>>>>> +}
>>>>>> +
>>>>>> +# Import a custom list from plain file, returns empty list if file is missing
>>>>>> +sub _customlist_import {
>>>>>> + my ($listname, $filename) = @_;
>>>>>> + my @list = ();
>>>>>> +
>>>>>> + # File exists, load and check all lines
>>>>>> + if(-f $filename) {
>>>>>> + open(my $FH, '<', $filename) or die "Can't read $filename: $!";
>>>>>> + while(my $line = <$FH>) {
>>>>>> + chomp($line);
>>>>>> + push(@list, $line);
>>>>>> + }
>>>>>> + close($FH);
>>>>>> +
>>>>>> + # Clean up imported data
>>>>>> + &_rpz_validate_customlist(\@list, 1);
>>>>>> + }
>>>>>> +
>>>>>> + $customlists{$listname}{'list'} = \@list;
>>>>>> +}
>>>>>> +
>>>>>> +# Export a custom list to plain file or clear file if list is disabled
>>>>>> +sub _customlist_export {
>>>>>> + my ($listname, $filename) = @_;
>>>>>> + return unless(defined $customlists{$listname});
>>>>>> +
>>>>>> + # Write enabled domain list to file, otherwise save empty file
>>>>>> + open(my $FH, '>', $filename) or die "Can't write $filename: $!";
>>>>>> +
>>>>>> + if($customlists{$listname}{'enabled'} eq 'on') {
>>>>>> + foreach my $line (@{$customlists{$listname}{'list'}}) {
>>>>>> + print $FH "$line\n";
>>>>>> + }
>>>>>> + } else {
>>>>>> + print $FH "; Note: This list is currently disabled by $ENV{'SCRIPT_NAME'}\n";
>>>>>> + }
>>>>>> +
>>>>>> + close($FH);
>>>>>> +}
>>>>>> +
>>>>>> +
>>>>>> +###--- Internal gui functions ---###
>>>>>> +
>>>>>> +# Show simple message box
>>>>>> +sub _print_message {
>>>>>> + my ($message, $title) = @_;
>>>>>> + $title ||= $Lang::tr{'error messages'};
>>>>>> +
>>>>>> + &Header::openbox('100%', 'left', $title);
>>>>>> + print "<span>$message</span>";
>>>>>> + &Header::closebox();
>>>>>> +}
>>>>>> +
>>>>>> +# Show all zone files and related gui elements
>>>>>> +sub _print_zonefiles {
>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf'});
>>>>>> +
>>>>>> + print <<END
>>>>>> +<table class="tbl" width="100%">
>>>>>> + <tr>
>>>>>> + <th>$Lang::tr{'name'}</th>
>>>>>> + <th>URL</th>
>>>>>> + <th>$Lang::tr{'remark'}</th>
>>>>>> + <th colspan="3">$Lang::tr{'action'}</th>
>>>>>> + </tr>
>>>>>> +END
>>>>>> +;
>>>>>> +
>>>>>> + # Sort zonefiles by name and loop trough all entries
>>>>>> + foreach my $name (sort keys %zonefiles) {
>>>>>> +
>>>>>> + # Toggle button label translation
>>>>>> + my $toggle_tr = ($zonefiles{$name}{'enabled'} eq 'on') ? $Lang::tr{'click to disable'} : $Lang::tr{'click to enable'};
>>>>>> +
>>>>>> + print <<END
>>>>>> + <tr>
>>>>>> + <td>$name</td>
>>>>>> + <td>$zonefiles{$name}{'url'}</td>
>>>>>> + <td>$zonefiles{$name}{'remark'}</td>
>>>>>> +
>>>>>> + <td align="center" width="5%">
>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>> + <input type="hidden" name="ACTION" value="ZF_TOGGLE">
>>>>>> + <input type="image" src="/images/$zonefiles{$name}{'enabled'}.gif" title="$toggle_tr" alt="$toggle_tr">
>>>>>> + </form>
>>>>>> + </td>
>>>>>> + <td align="center" width="5%">
>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>> + <input type="hidden" name="ACTION" value="ZF_EDIT">
>>>>>> + <input type="image" src="/images/edit.gif" title="$Lang::tr{'edit'}" alt="$Lang::tr{'edit'}">
>>>>>> + </form>
>>>>>> + </td>
>>>>>> + <td align="center" width="5%">
>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>> + <input type="hidden" name="ACTION" value="ZF_REMOVE">
>>>>>> + <input type="image" src="/images/delete.gif" title="$Lang::tr{'remove'}" alt="$Lang::tr{'remove'}">
>>>>>> + </form>
>>>>>> + </td>
>>>>>> + </tr>
>>>>>> +END
>>>>>> +;
>>>>>> + }
>>>>>> +
>>>>>> + # Disable reload button if not needed
>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>> +
>>>>>> + print <<END
>>>>>> +</table>
>>>>>> +
>>>>>> +<div class="right">
>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>> + <input type="hidden" name="KEY" value="">
>>>>>> + <button type="submit" name="ACTION" value="ZF_EDIT">$Lang::tr{'add'}</button>
>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>> + </form>
>>>>>> +</div>
>>>>>> +END
>>>>>> +;
>>>>>> +
>>>>>> + &Header::closebox();
>>>>>> +}
>>>>>> +
>>>>>> +# Show zonefiles entry editor
>>>>>> +sub _print_zonefile_editor {
>>>>>> +
>>>>>> + # Key specified: Edit existing entry
>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>> + # Load data to be edited, but don't override already present values (allows user to edit after error)
>>>>>> + $cgiparams{'ZF_NAME'} //= $action_key;
>>>>>> + $cgiparams{'ZF_URL'} //= $zonefiles{$action_key}{'url'};
>>>>>> + $cgiparams{'ZF_REMARK'} //= $zonefiles{$action_key}{'remark'};
>>>>>> + }
>>>>>> +
>>>>>> + # Fallback to empty form
>>>>>> + $cgiparams{'ZF_NAME'} //= "";
>>>>>> + $cgiparams{'ZF_URL'} //= "";
>>>>>> + $cgiparams{'ZF_REMARK'} //= "";
>>>>>> +
>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf editor'});
>>>>>> +
>>>>>> + print <<END
>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>> +<input type="hidden" name="KEY" value="$action_key">
>>>>>> +<table width="100%">
>>>>>> + <tr>
>>>>>> + <td width="20%">$Lang::tr{'name'}: <img src="/blob.gif" alt="*"></td>
>>>>>> + <td><input type="text" name="ZF_NAME" value="$cgiparams{'ZF_NAME'}" size="40" maxlength="32" title="$Lang::tr{'rpz zf remark info'}" pattern="[a-zA-Z0-9_]{1,32}" required></td>
>>>>>> + </tr>
>>>>>> + <tr>
>>>>>> + <td width="20%">URL: <img src="/blob.gif" alt="*"></td>
>>>>>> + <td><input type="url" name="ZF_URL" value="$cgiparams{'ZF_URL'}" size="40" maxlength="128" required></td>
>>>>>> + </tr>
>>>>>> + <tr>
>>>>>> + <td width="20%">$Lang::tr{'remark'}:</td>
>>>>>> + <td><input type="text" name="ZF_REMARK" value="$cgiparams{'ZF_REMARK'}" size="40" maxlength="32"></td>
>>>>>> + </tr>
>>>>>> + <tr>
>>>>>> + <td colspan="2"><hr></td>
>>>>>> + </tr>
>>>>>> + <tr>
>>>>>> + <td width="55%"><img src="/blob.gif" alt="*"> $Lang::tr{'required field'}</td>
>>>>>> + <td align="right"><button type="submit" name="ACTION" value="ZF_SAVE">$Lang::tr{'save'}</button></td>
>>>>>> + </tr>
>>>>>> +</table>
>>>>>> +</form>
>>>>>> +
>>>>>> +<div class="right">
>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>> + <button type="submit" name="ACTION" value="NONE">$Lang::tr{'back'}</button>
>>>>>> + </form>
>>>>>> +</div>
>>>>>> +END
>>>>>> +;
>>>>>> +
>>>>>> + &Header::closebox();
>>>>>> +}
>>>>>> +
>>>>>> +# Show custom allow/block files and related gui elements
>>>>>> +sub _print_customlists {
>>>>>> +
>>>>>> + # Load lists from config, unless they are currently being edited
>>>>>> + if($action ne 'CL_SAVE') {
>>>>>> + $cgiparams{'ALLOW_LIST'} = join("\n", @{$customlists{'allow'}{'list'}});
>>>>>> + $cgiparams{'BLOCK_LIST'} = join("\n", @{$customlists{'block'}{'list'}});
>>>>>> +
>>>>>> + $cgiparams{'ALLOW_ENABLED'} = ($customlists{'allow'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>> + $cgiparams{'BLOCK_ENABLED'} = ($customlists{'block'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>> + }
>>>>>> +
>>>>>> + # Fallback to empty form
>>>>>> + $cgiparams{'ALLOW_LIST'} //= "";
>>>>>> + $cgiparams{'BLOCK_LIST'} //= "";
>>>>>> +
>>>>>> + # HTML checkboxes, unchecked = no or undef value in POST data
>>>>>> + my %checked = ();
>>>>>> + $checked{'ALLOW_ENABLED'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? " checked" : "";
>>>>>> + $checked{'BLOCK_ENABLED'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? " checked" : "";
>>>>>> +
>>>>>> + # Disable reload button if not needed
>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>> +
>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz cl'});
>>>>>> +
>>>>>> + print <<END
>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>> +<table width="100%">
>>>>>> + <tr>
>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl allow'}</b><br>$Lang::tr{'rpz cl allow info'}</td>
>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl block'}</b><br>$Lang::tr{'rpz cl block info'}</td>
>>>>>> + </tr>
>>>>>> + <tr>
>>>>>> + <td colspan="2"><textarea name="ALLOW_LIST" class="domainlist" cols="45">
>>>>>> +$cgiparams{'ALLOW_LIST'}</textarea></td>
>>>>>> + <td colspan="2"><textarea name="BLOCK_LIST" class="domainlist" cols="45">
>>>>>> +$cgiparams{'BLOCK_LIST'}</textarea></td>
>>>>>> + </tr>
>>>>>> + <tr>
>>>>>> + <td><label for="allow_enabled">$Lang::tr{'rpz cl allow enable'}</label></td>
>>>>>> + <td width="15%"><input type="checkbox" name="ALLOW_ENABLED" id="allow_enabled"$checked{'ALLOW_ENABLED'}></td>
>>>>>> + <td><label for="block_enabled">$Lang::tr{'rpz cl block enable'}</label></td>
>>>>>> + <td width="15%"><input type="checkbox" name="BLOCK_ENABLED" id="block_enabled"$checked{'BLOCK_ENABLED'}></td>
>>>>>> + </tr>
>>>>>> + <tr>
>>>>>> + <td colspan="4"><hr></td>
>>>>>> + </tr>
>>>>>> + <tr>
>>>>>> + <td align="right" colspan="4">
>>>>>> + <button type="submit" name="ACTION" value="CL_SAVE">$Lang::tr{'save'}</button>
>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>> + </td>
>>>>>> +</table>
>>>>>> +</form>
>>>>>> +END
>>>>>> +;
>>>>>> +
>>>>>> + &Header::closebox();
>>>>>> +}
>>>>>> +
>>>>>> +# Output javascript and extra gui elements
>>>>>> +sub _print_gui_extras {
>>>>>> +
>>>>>> + # Apply/Restart button modifier key handler
>>>>>> + if(&_rpz_needs_reload()) {
>>>>>> + print <<END
>>>>>> +<script>
>>>>>> + // Commit modifier key handler
>>>>>> + (function(jq, document) {
>>>>>> + var keyEventsOn = false; // Keyboard events attached
>>>>>> + var keyModify = false; // Modifier key pressed
>>>>>> + var mouseHover = false; // Mouse over commit button
>>>>>> + var btnModified = false; // Button modified to "Restart"
>>>>>> +
>>>>>> + // Document-level key events, enable only while cursor is over button
>>>>>> + function attachKeyEvents() {
>>>>>> + if(keyEventsOn) {
>>>>>> + return;
>>>>>> + }
>>>>>> + keyEventsOn = true;
>>>>>> +
>>>>>> + jq(document).on("keydown.rpz", function(event) {
>>>>>> + if((!keyModify) && event.shiftKey) {
>>>>>> + keyModify = true;
>>>>>> + handleModify();
>>>>>> + }
>>>>>> + });
>>>>>> + jq(document).on("keyup.rpz", function(event) {
>>>>>> + if(keyModify && (!event.shiftKey)) {
>>>>>> + keyModify = false;
>>>>>> + handleModify();
>>>>>> + }
>>>>>> + });
>>>>>> + }
>>>>>> + function removeKeyEvents() {
>>>>>> + keyModify = false;
>>>>>> + if(keyEventsOn) {
>>>>>> + jq(document).off("keydown.rpz keyup.rpz");
>>>>>> + keyEventsOn = false;
>>>>>> + }
>>>>>> + }
>>>>>> +
>>>>>> + // Attach mouse hover events to commit buttons
>>>>>> + function attachMouseEvents() {
>>>>>> + jq("button.commit").on("mouseenter", function(event) {
>>>>>> + if(!mouseHover) {
>>>>>> + mouseHover = true;
>>>>>> + attachKeyEvents();
>>>>>> + // Handle already pressed key
>>>>>> + keyModify = !!(event.shiftKey);
>>>>>> + handleModify();
>>>>>> + }
>>>>>> + });
>>>>>> +
>>>>>> + // Cursor moved away: Disable key listener to minimize events
>>>>>> + jq("button.commit").on("mouseleave", function() {
>>>>>> + if(mouseHover) {
>>>>>> + mouseHover = false;
>>>>>> + removeKeyEvents();
>>>>>> + handleModify();
>>>>>> + }
>>>>>> + });
>>>>>> + }
>>>>>> +
>>>>>> + // Modify commit button
>>>>>> + function handleModify() {
>>>>>> + let modify = mouseHover && keyModify;
>>>>>> + if(btnModified != modify) {
>>>>>> + if(modify) {
>>>>>> + jq("button.commit").text("$Lang::tr{'restart'}").val("UNB_RESTART");
>>>>>> + } else {
>>>>>> + jq("button.commit").text("$Lang::tr{'rpz apply'}").val("RPZ_RELOAD");
>>>>>> + }
>>>>>> + btnModified = modify;
>>>>>> + }
>>>>>> + }
>>>>>> +
>>>>>> + // jQuery DOM ready
>>>>>> + jq(function() {
>>>>>> + attachMouseEvents();
>>>>>> + });
>>>>>> + })(jQuery, document);
>>>>>> +</script>
>>>>>> +END
>>>>>> +;
>>>>>> + } # End of modifier key handler
>>>>>> +
>>>>>> +}
>>>>>> +
>>>>>> +
>>>>>> +###--- Internal action processing functions ---###
>>>>>> +
>>>>>> +# Toggle zonefile on/off
>>>>>> +sub _action_zf_toggle {
>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>> +
>>>>>> + my $result = 0;
>>>>>> + my $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>> +
>>>>>> + # Perform toggle action
>>>>>> + if($enabled eq 'on') {
>>>>>> + $enabled = 'off';
>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>> + } else {
>>>>>> + $enabled = 'on';
>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $action_key, $zonefiles{$action_key}{'url'}, '--no-reload');
>>>>>> + }
>>>>>> +
>>>>>> + # Check for errors, request service reload on success
>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>> +
>>>>>> + # Save changes
>>>>>> + $zonefiles{$action_key}{'enabled'} = $enabled;
>>>>>> + &_zonefiles_save_conf();
>>>>>> +
>>>>>> + return 1;
>>>>>> +}
>>>>>> +
>>>>>> +# Remove zonefile
>>>>>> +sub _action_zf_remove {
>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>> +
>>>>>> + # Remove from rpz-config if currently active
>>>>>> + if($zonefiles{$action_key}{'enabled'} eq 'on') {
>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>> +
>>>>>> + # Check for errors, request service reload on success
>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>> + }
>>>>>> +
>>>>>> + # Remove from data hash and save changes
>>>>>> + delete $zonefiles{$action_key};
>>>>>> + &_zonefiles_save_conf();
>>>>>> +
>>>>>> + # Clear action_key, as the entry is now removed entirely
>>>>>> + $action_key = "";
>>>>>> +
>>>>>> + return 1;
>>>>>> +}
>>>>>> +
>>>>>> +# Create or update zonefile entry
>>>>>> +# Returns undef if gui needs to stay in editor mode
>>>>>> +sub _action_zf_save {
>>>>>> + my $result = 0;
>>>>>> +
>>>>>> + my $name = $cgiparams{'ZF_NAME'} // "";
>>>>>> + my $url = $cgiparams{'ZF_URL'} // "";
>>>>>> + my $remark = $cgiparams{'ZF_REMARK'} // "";
>>>>>> + my $enabled = 'on'; # Enable new entries by default
>>>>>> +
>>>>>> + # Note on variables:
>>>>>> + # name = unique key, will be used to address the entry
>>>>>> + # action_key = name of the entry being edited, empty for new entry
>>>>>> +
>>>>>> + # Only check for unique name if it changed
>>>>>> + # (this also checks new entries because the action_key is empty in this case)
>>>>>> + $result = &_rpz_validate_zonefile($name, $url, $remark, (lc($name) ne lc($action_key)));
>>>>>> + return unless &_rpz_check_result($result, 0);
>>>>>> +
>>>>>> + # Edit existing entry: Determine what was changed
>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>> + # Name und URL remain unchanged, only save remark and finish
>>>>>> + if(($name eq $action_key) && ($url eq $zonefiles{$action_key}{'url'})) {
>>>>>> + $zonefiles{$action_key}{'remark'} = $remark;
>>>>>> + &_zonefiles_save_conf();
>>>>>> +
>>>>>> + return 1;
>>>>>> + }
>>>>>> +
>>>>>> + # Entry was changed and needs to be recreated, preserve status
>>>>>> + $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>> +
>>>>>> + # Remove from rpz-config
>>>>>> + return unless &_action_zf_remove();
>>>>>> + }
>>>>>> +
>>>>>> + # Add new entry to rpz-config
>>>>>> + if($enabled eq 'on') {
>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $name, $url, '--no-reload');
>>>>>> +
>>>>>> + # Check for errors, request service reload on success
>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>> + }
>>>>>> +
>>>>>> + # Add to global data hash and save changes
>>>>>> + my %entry = ('enabled' => $enabled,
>>>>>> + 'url' => $url,
>>>>>> + 'remark' => $remark);
>>>>>> +
>>>>>> + $zonefiles{$name} = \%entry;
>>>>>> + &_zonefiles_save_conf();
>>>>>> +
>>>>>> + return 1;
>>>>>> +}
>>>>>> +
>>>>>> +# Save custom lists
>>>>>> +sub _action_cl_save {
>>>>>> + return unless((defined $cgiparams{'ALLOW_LIST'}) && (defined $cgiparams{'BLOCK_LIST'}));
>>>>>> +
>>>>>> + my $result = 0;
>>>>>> +
>>>>>> + my @allowlist = split(/\R/, $cgiparams{'ALLOW_LIST'});
>>>>>> + my @blocklist = split(/\R/, $cgiparams{'BLOCK_LIST'});
>>>>>> +
>>>>>> + # Validate lists
>>>>>> + $result = &_rpz_validate_customlist(\@allowlist);
>>>>>> + if($result != 0) {
>>>>>> + $errormessage = &_rpz_error_tr(202, $result);
>>>>>> + return;
>>>>>> + }
>>>>>> + $result = &_rpz_validate_customlist(\@blocklist);
>>>>>> + if($result != 0) {
>>>>>> + $errormessage = &_rpz_error_tr(203, $result);
>>>>>> + return;
>>>>>> + }
>>>>>> +
>>>>>> + # Add to global data hash and save changes
>>>>>> + $customlists{'allow'}{'list'} = \@allowlist;
>>>>>> + $customlists{'block'}{'list'} = \@blocklist;
>>>>>> + $customlists{'allow'}{'enabled'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? 'on' : 'off';
>>>>>> + $customlists{'block'}{'enabled'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? 'on' : 'off';
>>>>>> +
>>>>>> + &_customlists_save_conf();
>>>>>> + &_customlist_export('allow', $RPZ_ALLOWLIST);
>>>>>> + &_customlist_export('block', $RPZ_BLOCKLIST);
>>>>>> +
>>>>>> + # Make new lists, request service reload on success
>>>>>> + $result = &General::system('/usr/sbin/rpz-make', 'allowblock', '--no-reload');
>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>> +
>>>>>> + return 1;
>>>>>> +}
>>>>>> +
>>>>>> +# Trigger rpz-config reload
>>>>>> +sub _action_rpz_reload {
>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>> +
>>>>>> + # Immediately clear flag to prevent multiple reloads
>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>> + }
>>>>>> +
>>>>>> + # Perform reload, recreate reload flag on error to enable retry
>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'reload');
>>>>>> + if(not &_rpz_check_result($result, 0)) {
>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>> + return;
>>>>>> + }
>>>>>> +
>>>>>> + return 1;
>>>>>> +}
>>>>>> +
>>>>>> +# Trigger unbound restart
>>>>>> +sub _action_unb_restart {
>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>> +
>>>>>> + # Immediately clear flag to prevent multiple restarts
>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>> + }
>>>>>> +
>>>>>> + # Perform restart, unboundctrl always exits zero
>>>>>> + &General::system('/usr/local/bin/unboundctrl', 'restart');
>>>>>> +
>>>>>> + return 1;
>>>>>> +}
>>>>>> +
>>>>>> +
>>>>>> +###--- Internal rpz-config functions ---###
>>>>>> +
>>>>>> +# Translate rpz-config exitcodes and messages
>>>>>> +# 100-199: rpz-config, 200-299: webgui
>>>>>> +sub _rpz_error_tr {
>>>>>> + my ($error, $append) = @_;
>>>>>> + $append //= '';
>>>>>> +
>>>>>> + # Translate numeric exit codes
>>>>>> + if(looks_like_number($error)) {
>>>>>> + if(defined $Lang::tr{"rpz exitcode $error"}) {
>>>>>> + $error = $Lang::tr{"rpz exitcode $error"};
>>>>>> + }
>>>>>> + }
>>>>>> +
>>>>>> + return "RPZ $Lang::tr{'error'}: $error" . &Header::escape($append);
>>>>>> +}
>>>>>> +
>>>>>> +# Check result of rpz-config system call, request reload on success
>>>>>> +sub _rpz_check_result {
>>>>>> + my ($result, $request_reload) = @_;
>>>>>> + $request_reload //= 0;
>>>>>> +
>>>>>> + # exitcode 0 = success
>>>>>> + if($result != 0) {
>>>>>> + $errormessage = &_rpz_error_tr($result);
>>>>>> + return;
>>>>>> + }
>>>>>> +
>>>>>> + # Set reload flag
>>>>>> + if($request_reload) {
>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>> + }
>>>>>> +
>>>>>> + return 1;
>>>>>> +}
>>>>>> +
>>>>>> +# Test whether reload flag is set
>>>>>> +sub _rpz_needs_reload {
>>>>>> + return (-f $RPZ_RELOAD_FLAG);
>>>>>> +}
>>>>>> +
>>>>>> +# Validate a zonefile entry, returns rpz-config exitcode on failure. Use _rpz_check_result to verify.
>>>>>> +# unique = check for unique name
>>>>>> +sub _rpz_validate_zonefile {
>>>>>> + my ($name, $url, $remark, $unique) = @_;
>>>>>> + $unique //= 1;
>>>>>> +
>>>>>> + unless($name =~ /^[a-zA-Z0-9_]{1,32}$/) {
>>>>>> + return 101;
>>>>>> + }
>>>>>> + unless($url =~ /^[\w+\.:;\/\\&@#%?=\-~|!]{1,128}$/) {
>>>>>> + return 105;
>>>>>> + }
>>>>>> + unless($remark =~ /^[\w \-()\.:;*\/\\?!&=]{0,32}$/) {
>>>>>> + return 201;
>>>>>> + }
>>>>>> +
>>>>>> + # Check against already existing names
>>>>>> + if($unique) {
>>>>>> + foreach my $existing (keys %zonefiles) {
>>>>>> + if(lc($name) eq lc($existing)) {
>>>>>> + return 104;
>>>>>> + }
>>>>>> + }
>>>>>> + }
>>>>>> +
>>>>>> + return 0;
>>>>>> +}
>>>>>> +
>>>>>> +# Validate a custom list, returns number of rejected line on failure. Check for non-zero results.
>>>>>> +# listref = array reference, cleanup = remove invalid entries instead of returning an error
>>>>>> +sub _rpz_validate_customlist {
>>>>>> + my ($listref, $cleanup) = @_;
>>>>>> + $cleanup //= 0;
>>>>>> +
>>>>>> + foreach my $index (reverse 0..$#{$listref}) {
>>>>>> + my $row = @$listref[$index];
>>>>>> + next unless($row); # Skip/allow empty lines
>>>>>> +
>>>>>> + # Reject/remove everything besides wildcard domains and remarks
>>>>>> + if((not &General::validwildcarddomainname($row)) && (not $row =~ /^;[\w \-()\.:;*\/\\?!&=]*$/)) {
>>>>>> + unless($cleanup) {
>>>>>> + # +1 for user friendly line number and to ensure non-zero exitcode
>>>>>> + return $index + 1;
>>>>>> + }
>>>>>> +
>>>>>> + # Remove current row
>>>>>> + splice(@$listref, $index, 1);
>>>>>> + }
>>>>>> + }
>>>>>> +
>>>>>> + return 0;
>>>>>> +}
>>>>>> +
>>>>>> +
>>>>>> +###--- Internal misc functions ---###
>>>>>> +
>>>>>> +# Send HTTP 303 redirect headers for post/request/get pattern
>>>>>> +# (Must be sent before calling &Header::showhttpheaders())
>>>>>> +sub _http_prg_redirect {
>>>>>> + my $location = "https://$ENV{'SERVER_NAME'}:$ENV{'SERVER_PORT'}$ENV{'SCRIPT_NAME'}";
>>>>>> + print "Status: 303 See Other\n";
>>>>>> + print "Location: $location\n";
>>>>>> +}
>>>>>> diff --git a/lfs/rpz b/lfs/rpz
>>>>>> new file mode 100644
>>>>>> index 000000000..7ddbc38e5
>>>>>> --- /dev/null
>>>>>> +++ b/lfs/rpz
>>>>>> @@ -0,0 +1,96 @@
>>>>>> +###############################################################################
>>>>>> +# #
>>>>>> +# IPFire.org - A linux based firewall #
>>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>>> +# #
>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>> +# #
>>>>>> +###############################################################################
>>>>>> +
>>>>>> +###############################################################################
>>>>>> +# Definitions
>>>>>> +###############################################################################
>>>>>> +
>>>>>> +include Config
>>>>>> +
>>>>>> +SUMMARY = response policy zone - RPZ reputation system for unbound DNS
>>>>>> +
>>>>>> +VER = 1.0.0
>>>>>> +
>>>>>> +THISAPP = rpz-$(VER)
>>>>>> +DIR_APP = $(DIR_SRC)/$(THISAPP)
>>>>>> +TARGET = $(DIR_INFO)/$(THISAPP)
>>>>>> +
>>>>>> +PROG = rpz
>>>>>> +PAK_VER = 18
>>>>>> +
>>>>>> +DEPS =
>>>>>> +
>>>>>> +SERVICES =
>>>>>> +
>>>>>> +###############################################################################
>>>>>> +# Top-level Rules
>>>>>> +###############################################################################
>>>>>> +
>>>>>> +install : $(TARGET)
>>>>>> +
>>>>>> +check :
>>>>>> +
>>>>>> +download :
>>>>>> +
>>>>>> +b2 :
>>>>>> +
>>>>>> +dist:
>>>>>> + @$(PAK)
>>>>>> +
>>>>>> +###############################################################################
>>>>>> +# Installation Details
>>>>>> +###############################################################################
>>>>>> +
>>>>>> +$(TARGET) :
>>>>>> + @$(PREBUILD)
>>>>>> + @rm -rf $(DIR_APP)
>>>>>> +
>>>>>> + # RPZ scripts
>>>>>> + install --verbose --mode=755 \
>>>>>> + $(DIR_CONF)/rpz/{rpz-config,rpz-metrics,rpz-sleep,rpz-make,rpz-functions} \
>>>>>> + --target-directory=/usr/sbin
>>>>>> +
>>>>>> + # RPZ config files
>>>>>> + mkdir -pv /etc/unbound/local.d
>>>>>> + install --verbose --mode=644 --owner=nobody --group=nobody \
>>>>>> + $(DIR_CONF)/rpz/00-rpz.conf \
>>>>>> + --target-directory=/etc/unbound/local.d
>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/local.d
>>>>>> +
>>>>>> + # RPZ custom list files for allow and block
>>>>>> + mkdir -pv /var/ipfire/dns/rpz
>>>>>> + touch /var/ipfire/dns/rpz/{allowlist,blocklist}
>>>>>> + chown --verbose --recursive nobody:nobody /var/ipfire/dns/rpz
>>>>>> +
>>>>>> + # RPZ zone files
>>>>>> + # create empty RPZ config file to avoid a unbound config error
>>>>>> + mkdir -pv /etc/unbound/zonefiles
>>>>>> + touch /etc/unbound/zonefiles/allow.rpz
>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/zonefiles
>>>>>> +
>>>>>> + # Install addon-specific language-files
>>>>>> + install --verbose --mode=004 $(DIR_CONF)/rpz/rpz.*.pl \
>>>>>> + --target-directory=/var/ipfire/addon-lang
>>>>>> +
>>>>>> + # Install backup definition
>>>>>> + cp -vf $(DIR_CONF)/backup/includes/rpz /var/ipfire/backup/addons/includes/rpz
>>>>>> +
>>>>>> + @rm -rf $(DIR_APP)
>>>>>> + @$(POSTBUILD)
>>>>>> diff --git a/make.sh b/make.sh
>>>>>> index 827ea9e77..a77535b13 100755
>>>>>> --- a/make.sh
>>>>>> +++ b/make.sh
>>>>>> @@ -390,7 +390,7 @@ prepareenv() {
>>>>>> if [ "${free_space}" -lt "${required_space}" ]; then
>>>>>> # Add any consumed space
>>>>>> while read -r consumed_space path; do
>>>>>> - (( free_space += consumed_space / 1024 / 1024 ))
>>>>>> + (( free_space += consumed_space / 1024 / 1024 ))
>>>>>> done <<< "$(du --summarize --bytes "${BUILD_DIR}" "${IMAGES_DIR}" "${LOG_DIR}" 2>/dev/null)"
>>>>>> fi
>>>>>>
>>>>>> @@ -2087,6 +2087,7 @@ build_system() {
>>>>>> lfsmake2 btrfs-progs
>>>>>> lfsmake2 inotify-tools
>>>>>> lfsmake2 grub-btrfs
>>>>>> + lfsmake2 rpz
>>>>>>
>>>>>> lfsmake2 linux
>>>>>> lfsmake2 rtl8812au
>>>>>> diff --git a/src/paks/rpz/install.sh b/src/paks/rpz/install.sh
>>>>>> new file mode 100644
>>>>>> index 000000000..ef99bf742
>>>>>> --- /dev/null
>>>>>> +++ b/src/paks/rpz/install.sh
>>>>>> @@ -0,0 +1,36 @@
>>>>>> +#!/bin/bash
>>>>>> +###############################################################################
>>>>>> +# #
>>>>>> +# IPFire.org - A linux based firewall #
>>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>>> +# #
>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>> +# #
>>>>>> +###############################################################################
>>>>>> +#
>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>> +extract_files
>>>>>> +restore_backup ${NAME}
>>>>>> +
>>>>>> +# fix user created files
>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>> + /var/ipfire/dns/rpz \
>>>>>> + /etc/unbound/zonefiles \
>>>>>> + /etc/unbound/local.d
>>>>>> +
>>>>>> +# Update Language cache
>>>>>> +/usr/local/bin/update-lang-cache
>>>>>> +
>>>>>> +# restart unbound to load config file
>>>>>> +/etc/init.d/unbound restart
>>>>>> diff --git a/src/paks/rpz/uninstall.sh b/src/paks/rpz/uninstall.sh
>>>>>> new file mode 100644
>>>>>> index 000000000..e11427df3
>>>>>> --- /dev/null
>>>>>> +++ b/src/paks/rpz/uninstall.sh
>>>>>> @@ -0,0 +1,38 @@
>>>>>> +#!/bin/bash
>>>>>> +###############################################################################
>>>>>> +# #
>>>>>> +# IPFire.org - A linux based firewall #
>>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>>> +# #
>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>> +# #
>>>>>> +###############################################################################
>>>>>> +#
>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>> +
>>>>>> +# stop unbound to delete RPZ conf file
>>>>>> +/etc/init.d/unbound stop
>>>>>> +
>>>>>> +make_backup ${NAME}
>>>>>> +remove_files
>>>>>> +
>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>> +# "[1723428668] unbound-control[17117:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>> +
>>>>>> +# Update Language cache
>>>>>> +/usr/local/bin/update-lang-cache
>>>>>> +
>>>>>> +# start unbound to load unbound config file
>>>>>> +/etc/init.d/unbound start
>>>>>> diff --git a/src/paks/rpz/update.sh b/src/paks/rpz/update.sh
>>>>>> new file mode 100644
>>>>>> index 000000000..9bc340bc6
>>>>>> --- /dev/null
>>>>>> +++ b/src/paks/rpz/update.sh
>>>>>> @@ -0,0 +1,52 @@
>>>>>> +#!/bin/bash
>>>>>> +###############################################################################
>>>>>> +# #
>>>>>> +# IPFire.org - A linux based firewall #
>>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>>> +# #
>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>> +# #
>>>>>> +###############################################################################
>>>>>> +#
>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>> +
>>>>>> +# from update.sh
>>>>>> +extract_backup_includes
>>>>>> +
>>>>>> +# stop unbound to delete RPZ conf file
>>>>>> +/etc/init.d/unbound stop
>>>>>> +
>>>>>> +# from uninstall.sh
>>>>>> +make_backup ${NAME}
>>>>>> +remove_files
>>>>>> +
>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>> +# "unbound-control[nn:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>> +
>>>>>> +# from install.sh
>>>>>> +extract_files
>>>>>> +restore_backup ${NAME}
>>>>>> +
>>>>>> +# fix user created files
>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>> + /var/ipfire/dns/rpz \
>>>>>> + /etc/unbound/zonefiles \
>>>>>> + /etc/unbound/local.d
>>>>>> +
>>>>>> +# Update Language cache
>>>>>> +/usr/local/bin/update-lang-cache
>>>>>> +
>>>>>> +# restart unbound to load config files
>>>>>> +/etc/init.d/unbound start
>>>>>> --
>>>>>> 2.39.5
>>>>>>
>>>>>
>>>>
>>>> Jon
>>>>
>>>>
>>>> --
>>>> Jon Murphy
>>>> jon.murphy(a)ipfire.org
>>
>>
>> Jon
>>
>>
>> --
>> Jon Murphy
>> jon.murphy(a)ipfire.org
>>
>>
>>
>>
>
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
2025-02-14 12:58 ` Bernhard Bitsch
@ 2025-02-14 13:52 ` Michael Tremer
2025-02-14 14:16 ` Bernhard Bitsch
0 siblings, 1 reply; 30+ messages in thread
From: Michael Tremer @ 2025-02-14 13:52 UTC (permalink / raw)
To: development
[-- Attachment #1: Type: text/plain, Size: 130637 bytes --]
Hello Bernhard,
> On 14 Feb 2025, at 12:58, Bernhard Bitsch <bbitsch(a)ipfire.org> wrote:
>
> Hello Michael,
>
> thanks for your clear words. But nevertheless let be comment at some topics.
>
> Am 14.02.2025 um 13:07 schrieb Michael Tremer:
>> Hello Jon,
>> It very much depends on the kind of contribution. A one-line patch obviously has fewer strings attached to it than a larger patch set like this.
>> However, we have outlined the process in the wiki already, starting from here:
>> https://www.ipfire.org/docs/devel/submit-patches
>> This contains some useful pointers about the how (how do I actually make my changes happen, how do I build IPFire, etc), and at the bottom it contains a lot of information about the format the changes should be submitted; split into smaller chunks that are ideally as independent from each other so that they can be individually reviewed and merged. Usually a development process takes long time and we have already shipped parts of code that we will need for certain features that are not ready yet. This is a good practice to let code mature, especially when it is touching rather critical bits like the firewall and networking stacks.
>> There are also some guidelines on how to write a good commit message and how to use Git tags:
>> https://www.ipfire.org/docs/devel/git/commit-messages
>> https://www.ipfire.org/docs/devel/git/tags
>> Then there is something about how to get in touch with the right person and legal stuff:
>> https://www.ipfire.org/docs/devel/contact
>> https://www.ipfire.org/docs/legal/ipca
>
> IMO, this facts are known by Jon. He just did a big part besides this general process. Including publishing his efforts to the community for review and test.
>
>> Finally, we have a bit of an underused roadmap section on the wiki. It would be nice if we could use that a little bit more because then it would be easier for everyone to keep track of progress on certain features; people could see what is being worked on and see if they can help development and testing and so on:
>> https://www.ipfire.org/docs/roadmap
>> There is a template on how to create new pages:
>> https://www.ipfire.org/docs/roadmap/template
>> And this is a good example of what this could look like:
>> https://www.ipfire.org/docs/roadmap/openvpn-26
>> All of these steps are coming *after* there has been some initial discussion about what actually has a chance to become part of the distribution. For that, we do not have any specific guidelines because it is not very trivial to write these things. There are just too many possibilities. In the past, there has also been very little need for this, but that does not mean that there have not been problems before.
>> The reason why I am raising the bar this high here is simply that we have made mistakes in the past that we don’t want to repeat. We have learned a couple of lessons in a not very pleasant way and I under no circumstances would want to do this again. The objective is that we want to provide an excellent distribution. Although IPFire of course has its shortcomings here and there, it is a very stable distribution and we have a very good track record that I want to keep. This is what our users deserve.
>> In the past, people have “dropped” their patches on this list (or sometimes elsewhere) and we were left with dealing with the entire integration only to find out later what problems there were hidden in the code. The original author(s) had no interest in fixing any of that because it worked just fine for them, and so why spend any time on the problems of somebody else? Usually I am the fallback for this and I simply don’t want to be that. I have lots of my own projects inside IPFire that are moving at snail speed because fixing existing code usually takes priority over writing new code.
>> Therefore we need a commitment to sort out these problems in the first place. It has to be proven that people actually *care* about the patches that they post here. I am not sure this needs writing down as this should be the same policy with almost any open source project. If you contribute a line, there is probably less maintenance required in the future, but if you contribute a large code base, then you will need to look after it for the foreseeable future. It is your feature and not mine after all.
>
> Being one of the first testers and revisors, I commit me to support the development. Point.
>
>> Then, what actually has a chance to make it into the distribution? Probably not a lot. IPFire has a very clear use case. There will not be any space for a desktop environment and running Chrome on it, we also don’t it to make coffee and cook me a dinner. We would currently only accept things that were actually maintainable by the current team in case a contributor moves on (see above), because we simply only have so much man power. We already have a large zoo of features that are very abandoned and we are potentially looking at getting rid of more things simply because we cannot support them properly. Time just doesn’t permit. Adding something large is therefore very difficult at the moment.
>> I understand that in this specific case you have been trying to not involve the development team and I understand your motivation. But you cannot forget about how much time and effort a review process can take. Therefore we want to plan things well; we want to even split it; and we want to have a conversation in advance so that the roadmap is clear and the actual code review ideally only becomes a formality.
>> All of this above has been for a general case. Please read through this and feel free to ask any questions if something isn’t clear.
>> To move forward with this feature, we should start by planning a roadmap. We need to discuss what this project should cover and what it should not cover. I believe we don’t need to talk much about implementation details because you have figured out a lot of them; we need to find what feature we want to provide to our users. Are you up for that?
>
> As a first input to the 'official' discussion, some thoughts.
> - Usage of encrypted data traffic is increasing. Especially in communications not aware to the user. These unwanted traffic is more and more difficult to block with IP orientated tools. Even the proxy based solution do not really function efficient.
> - To circumvent these problems a widely used approach is DNS filtering. This comes in two flavours: 1. online providers, 2. local filtering DNS servers like PiHole.
> - Solution 1 isn't wantable. Many providers store the access data, outside the local network.
> - Solution 2 installs another DNS server inside the local networks. It isn't trivial to ensure that all DNS traffic is handled by this server.
> - Therefore it is desirable that this filtering is done in the only DNS server in a IPFire governed network, unbound on the IPFire system. The RPZ functionality allows exactly this.
> - There are, IMO, good filter lists outside. The updating of these lists is part of the RPZ module. There is some effort left to investigate these and to develop recommendations for a good mix of appliance, we do this for the external DNS servers already.
You are thinking too much about the *how* here, but we should think of the *what* first. What features do we want to offer and what do we not want to offer.
Regarding the lists: I am running a test with one client which has about 2000 users on their network on a daily basis. There have been *huge* amounts of false positives from the OISD list and it has been occasionally painful to whitelist those. The quality of this feature stands and falls with the quality of the lists that we can feed into this.
I have also raised before that those lists collect random stuff together and in the end you only have one list that you can either take or leave. Coming from URL filter, we have some categorisation there like “adult”, “gambling”, “social networks”, and so on which we don’t have here. Would that not be nice to have, too?
> Regards,
> Bernhard
>
>> Best,
>> -Michael
>>> On 13 Feb 2025, at 21:34, jon <jon.murphy(a)ipfire.org> wrote:
>>>
>>> Michael,
>>>
>>> I’ve read through your comments a few times and I ended up with many more questions.
>>>
>>>
>>>> What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>
>>>
>>> To me the efforts to get new code accepted seem to have changed and it seemed easier in the past. In the past I made the Core Team aware via the Dev Mailing List and wrote a simple two or three paragraphs of "What is it? / What is the value? / Here is the code"
>>>
>>>
>>> So in an effort to move forward: How exactly is something presented to the Core Team?
>>>
>>> Is there an example of a recent effort that was presented that I can see as a sample? (This type of info can also be added to the Wiki)
>>>
>>> I understand you want it this way, but I don’t know what exactly is needed. Please be specific.
>>>
>>>
>>> Jon
>>>
>>> PS - I am not ignoring your other comments, I am just trying to move forward and keep things simple.
>>>
>>>
>>>
>>>> On Feb 8, 2025, at 1:27 PM, Michael Tremer <michael.tremer(a)ipfire.org> wrote:
>>>>
>>>> Hello Jon,
>>>>
>>>> Thanks for your reply. And good that you are copying everyone into this conversation.
>>>>
>>>>> On 8 Feb 2025, at 18:41, jon <jon.murphy(a)ipfire.org> wrote:
>>>>>
>>>>> Michael,
>>>>>
>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort
>>>>>
>>>>> Yes, this has been a separate effort (a very public separate effort). Yes, as you pointed this out early on with the "proof-of-concept" and then my request for people to help test RPZ. Nothing was hidden.
>>>>>
>>>>> This was done because you (and maybe others) did not have the time and I wanted to help and because I needed assistance with RPZ. I tried my best to do this without bothering you.
>>>>
>>>> I don’t that it is accurate that nobody wanted to help on this. The list was always open - although not every email has been replied to swiftly it is also your responsibility to raise a question again if it was missed. People here have open ears.
>>>>
>>>> It was also stated on this very list on in our documentation that working on something without involving the core team is a risky undertaking. Of course IPFire is free software and so everyone is free to fork if they wish to do so.
>>>>
>>>>>> and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls.
>>>>>
>>>>> You were aware many steps along the way. See your email on July 28, 2024, August 15, 2024, September 30, 2024, December 23, 2024, and January 16. My attempts to get the team involved were met with "things are busy" and sometimes silence. (Yes, I get it, people are busy.)
>>>>>
>>>>> You and Adolf, Leo, Erik and Bernhard have been aware since the beginning. You mention you were aware of the "proof-of-concept". If you include those beginning posts, since Sep 2023.
>>>>
>>>> Yes, I am aware of a proof-of-concept that I have been running myself for a long time. I am also aware of the efforts that you have been taking.
>>>>
>>>> Yet I don’t think there has ever been any joint effort, or am I seeing that wrong?
>>>>
>>>>>> This has not been discussed . . . on our calls.
>>>>>
>>>>> On the July 28th you stated:
>>>>> "We have talked about RPZ many times on the monthly call since the URL filter feature is falling more and more out of fashion. I think there is also many posts about this on the forum."
>>>>>
>>>>> Please don’t insult me again by stating "you know what I mean".
>>>>>
>>>>> And it has been discussed but not documented in the Monthly Meeting notes.
>>>>
>>>> I am not at all insulting you. I don’t want to take this down to a personal level at all. This is a public mailing list and people who read this don’t need to listen to an argument we are having. They are here for the tech inside IPFire.
>>>>
>>>> When I wrote that it has not been discussed that does not mean that we have not been touching on the topic. We have been talking about lots of things on the calls, the weather, politics, how our pets are. None of that makes it to the logs. What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>>
>>>>>> Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation.
>>>>>
>>>>> Regular conversation on the Dev Mailing list is many times met with silence. I get it, people are busy.
>>>>>
>>>>> And regular two-way conversation doesn’t happen on the list. At least not with me. I’d be happy to point out the posts that were met with silence.
>>>>> Again, I get it, people are busy.
>>>>
>>>> And you think my emails are not being met with silence? This has nothing to do with this specific topic. This has something to do with how occupied people are and how engaged they are on certain topics. Not everyone is involved in all the things and simply will ignore emails simply based on their subject line.
>>>>
>>>>> But the "dip here to the list" were my attempts to get a conversation started. As I said, many time met with silence.
>>>>>
>>>>> The only place I was not met with silence was on the Community. You have a great group of people in the Community. It is a shame you don’t want to have others help. It would reduce your workload.
>>>>
>>>> You should stop making statements that are not true. Who doesn’t want anyone to help?
>>>>
>>>> Not having this conversation on a Saturday evening would reduce my workload. At least it would free up time for something else. Helping with the things that are already on the go would reduce the workload of the entire team. Starting one thing at a time and finishing it is a lot better to manage than starting a hundred things and not even finish one. I can tell you that I already have a hundred things on the go.
>>>>
>>>>>> Therefore, what am I supposed to do with this email?
>>>>>
>>>>> To me it is beyond obvious…
>>>>>
>>>>> If it isn’t what you want, then guide me with how to do this the correct way. And be specific. I am trying to help. I am trying to make things better. I am trying to do things the right way.
>>>>
>>>> To me it isn’t. This is yet another project that has been dumped to the list like so many before and later on everyone has left to have the team deal with the rest.
>>>>
>>>> It is a huge patch set. You explained what the vision is, but that is about it. There is no chance this will continue if this disagreement isn’t solved first. I didn’t even look at the code.
>>>>
>>>>>> I don’t want to merge code that I don’t agree with.
>>>>>
>>>>> I asked multiple times if you "agreed with the concept" and again, met with silence. Yes I get it, people are busy.
>>>>
>>>> Having support for RPZ? Yes, it was definitely on the roadmap. That I agree with.
>>>>
>>>>>> So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>
>>>>> You mentioned this a in the past, but for some reason you do not disclose what I dismissed. Why do you continue to make this harder, wouldn’t it not be easier to tell me what I have dismissed?
>>>>>
>>>>> I have sent multiple emails trying to answer your concerns and comments. On July 28, Aug 14, Aug 22, Aug 23, Sep 30, etc.
>>>>>
>>>>> I’ve gone through all of the questions you asked and I cannot find a "dismissed" item.
>>>>
>>>> Maybe I need to be *more clear*. I feel humoured by this.
>>>>
>>>> It is late on a Saturday and I want my dinner soon, but certainly I have stated that this should never be an add-on considering it is supposed to replace URL Filter. We should never allow people to add their own sources. I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>>>
>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>
>>>>> The maintainers of Unbound and/or RPZ?
>>>>>
>>>>> The maintainers of Hagezi list, the threatfox list, the urlhaus list, etc.?
>>>>>
>>>>> What else? The maintainers or the RPZ scripts? That is me. Let’s talk!
>>>>
>>>> You. I don’t care much about the providers of the lists.
>>>>
>>>>> See, this is where it gets confusing. There are hundreds of open source packages as part of IPFire. Pick the last five years of items added to the IPFire build. You're telling me you have "constructive conversation with the maintainers" of all of the added packages?
>>>>
>>>> They publish their software and they don’t care whether I am pulling it or not. They publish it with the commitment to maintain it - sometimes for better and sometimes for worse.
>>>>
>>>> You care about me pulling your code and I don’t know whether you would commit to maintain this.
>>>>
>>>> These two are very different cases.
>>>>
>>>>> Pick the IP Blocklists list (i.e., 3CORESEC, ABUSECH, DSHIELD, SPAMHAUS, etc.) or the Suricata lists (i.e., Emergingthreats.net, Abuse.ch, etc.). So you’ve have "constructive conversation with the maintainers"?
>>>>
>>>> Yes, occasionally I have phone calls with a few of these providers.
>>>>
>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>
>>>>> Ha! Yes a surprise. In the beginning you seemed interested as IPFire needed a replacement for URL Filter. You asked good questions about the lists picked, asked for the value to the users, etc. And I answered the best I could.
>>>>>
>>>>> You even asked: “Why is this realised as an add-on and not part of the core system?” from your Jul 28, 2024 email.
>>>>
>>>> Ah, so, why is the patch creating an add-on? Not that I am saying that what I say is law, but it has not been challenged either. If my input is being ignored, why should I put this to the top of my list of priorities? I am not disappointed about this, just trying to be very good with my time.
>>>>
>>>>> And on January 16, 2025 I wrote a message looking for help. And you were kind to respond quickly. So in three weeks time, since the kind response, something has changed. You went from supportive to "this".
>>>>>
>>>>> So yes, I am surprised.
>>>>
>>>> Well, maybe I should not have replied to that email. It was clear that you were on some path that was not right, but you were not interested before in finding the right path from the beginning.
>>>>
>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>
>>>>> Be more specific, what has to change? What exactly did I dismiss?
>>>>
>>>> Dismissal is just my assumption. I don’t know what you actually did with my feedback. I can only see the end product that does not seem contain much of it. Repeatedly I have been pointing out that we should think before we build. I am sure a lot of hours have now gone into some code that simply does not satisfy me. And I am not not talking about the code itself, what it does is what I don’t think is right for us.
>>>>
>>>> The process is very clear for me that we should first of all think whether we want a certain feature now. Then there should be a clear roadmap for everyone to follow; tasks can be split-up as we go and hopefully then have something that is maintainable, interesting for our users and even would do us proud. This is how this should work.
>>>>
>>>> So, what has to change? I don’t think with shouting at each other, throwing patches around and making me generally unhappy is a good start.
>>>>
>>>> -Michael
>>>>
>>>>> Jon
>>>>>
>>>>>
>>>>>
>>>>>> On Feb 6, 2025, at 2:13 PM, Michael Tremer <michael.tremer(a)ipfire.org> wrote:
>>>>>>
>>>>>> Hello Jon,
>>>>>>
>>>>>> Well, here we are again with another patch regarding this feature.
>>>>>>
>>>>>> I cannot quite see from your email what the question is, but if this is a request to have this merged into IPFire, I am once again sorry to disappoint you.
>>>>>>
>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls. Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation. Therefore, what am I supposed to do with this email?
>>>>>>
>>>>>> I don’t want to merge code that I don’t agree with. So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>>
>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>>
>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>>
>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>>
>>>>>> All the best,
>>>>>> -Michael
>>>>>>
>>>>>>> On 6 Feb 2025, at 16:35, Jon Murphy <jon.murphy(a)ipfire.org> wrote:
>>>>>>>
>>>>>>> What is it?
>>>>>>> Response Policy Zone (RPZ) is a mechanism to define local policies in a
>>>>>>> standardized way and load those policies from external sources.
>>>>>>> Bottom line: RPZ allows admins to easily block access to websites via DNS lookup.
>>>>>>>
>>>>>>> RPZ can block websites via categories. Examples include: fake websites, annoying
>>>>>>> pop-up ads, newly registered domains, DoH bypass sites, bad "host" services,
>>>>>>> maliscious top level domains (e.g., *.zip, *.mov), piracy, gambling, pornography,
>>>>>>> and more. RPZ lists come from various RPZ providers and their available
>>>>>>> catagories.
>>>>>>>
>>>>>>> This RPZ add-on enables the RPZ functionality by adding a couple lines in a
>>>>>>> configuration file. This add-on simply adds configuration files and adds
>>>>>>> scripts (config, metrics and sleep) to make RPZ easier for the admin to use.
>>>>>>>
>>>>>>> The RPZ scripts include additional languages: German, Spanish, French, Turkish,
>>>>>>> and Italian.
>>>>>>>
>>>>>>> RPZ itself was release in 2010 and has been part of the IPFire build since ~2015.
>>>>>>>
>>>>>>> Why is it needed? What is its value?
>>>>>>>
>>>>>>> - The RPZ concept places this filtering into IPFire, our internet access
>>>>>>> gateway, which is (should be) solely used as DNS source of the internal network.
>>>>>>>
>>>>>>> - As most sites use HTTPS it makes it difficult to filter traffic with URL
>>>>>>> Filter without also properly configuring conventional (non-transparent)
>>>>>>> mode on the proxy. RPZ is a nice replacement for the URL Filter.
>>>>>>>
>>>>>>> - No need to install and maintain an additional device like PiHole or AdBlock
>>>>>>> browser extensions on multiple user devices.
>>>>>>>
>>>>>>> - This is an additional layer of protection for users. Less worry someone will
>>>>>>> click on something that gets them into trouble. And, saying this with emphasis,
>>>>>>> the ability to do it in one place!
>>>>>>>
>>>>>>> - Blocked sites save on unneeded traffic and can lessen the threat of malware
>>>>>>> in advertisements
>>>>>>>
>>>>>>> - Logging allows the admin to see the site blocked and take actions
>>>>>>>
>>>>>>> - RPZ will be used at the home, home-office (work from home), schools,
>>>>>>> ministerial, and at the office. Device counts are small (2-6) to medium (~80)
>>>>>>> to mediam-large (200+).
>>>>>>>
>>>>>>> - RPZ can block ads, popups, phishing, scammers, spyware, malware, annoying
>>>>>>> popups, NSFW links, DOH servers, and the usual internet trash.
>>>>>>>
>>>>>>> ------------------------------
>>>>>>>
>>>>>>> Change Log for RPZ add-on
>>>>>>>
>>>>>>> rpz-1.0.0-18 on 2025-02-05
>>>>>>> - Build for approval & release as IPFire add-on
>>>>>>>
>>>>>>> ---
>>>>>>>
>>>>>>> rpz-beta-0.1.18-18.ipfire on 2025-02-01
>>>>>>> rpz.cgi:
>>>>>>> - new feature: added a mod key to force a unbound restart
>>>>>>>
>>>>>>> rpz-config and rpz-make:
>>>>>>> - new feature: added action for unbound restart `rpz-config unbound-restart`
>>>>>>>
>>>>>>> rpz-metrics:
>>>>>>> - simple reformatting
>>>>>>> - rename far right column from "last update" to "last download"
>>>>>>>
>>>>>>> ---
>>>>>>>
>>>>>>> rpz-beta-0.1.17-17.ipfire on 2024-12-09
>>>>>>> rpz-make
>>>>>>> - bug fix: corrected validation regex for wildcards like: `*.domain.com`
>>>>>>>
>>>>>>> ---
>>>>>>>
>>>>>>> rpz-beta-0.1.16-16.ipfire on 2024-11-18
>>>>>>> rpz-make
>>>>>>> - new feature: updated validation regex
>>>>>>> - bug fix: moved validation to beginning of process. Now we validate before
>>>>>>> creating config files.
>>>>>>>
>>>>>>> rpz.cgi:
>>>>>>> - new feature: use CSS color variables of the main ipfire theme
>>>>>>> - bug fix: empty zonefile remarks were stored as “undef” and caused a warning
>>>>>>> - bug fix: HTML textarea removes the first empty line in a custom list
>>>>>>> - thank you Leo!
>>>>>>>
>>>>>>> ---
>>>>>>>
>>>>>>> rpz-beta-0.1.15-15.ipfire on 2024-11-04
>>>>>>> rpz.cgi:
>>>>>>> - new feature: added new language file for Turkish (thank you Peppe)
>>>>>>>
>>>>>>> rpz-make
>>>>>>> - bug fix: corrected empty allow/block list issue. An empty allow/block list
>>>>>>> will now remove contents of allow/block.rpz files and remove unneeded
>>>>>>> allow/block.conf file. (thank you iptom)
>>>>>>>
>>>>>>> ---
>>>>>>>
>>>>>>> rpz-beta-0.1.14-14.ipfire on 2024-10-29
>>>>>>> rpz-config:
>>>>>>> - bug fix: correct missing rpz extension. `rpz-config list` displayed URL
>>>>>>> incorrectly (thank you Bernhard)
>>>>>>>
>>>>>>> rpz.cgi:
>>>>>>> - bug fix: remove extra `"` in language files (thank you Bernhard)
>>>>>>> - new feature: slightly dim "apply" button when not enabled
>>>>>>>
>>>>>>> ---
>>>>>>>
>>>>>>> rpz-beta-0.1.13-13.ipfire on 2024-10-27
>>>>>>> - skipped
>>>>>>>
>>>>>>> ---
>>>>>>>
>>>>>>> rpz-beta-0.1.12-12.ipfire on 2024-10-21
>>>>>>> rpz.cgi:
>>>>>>> - new feature: added new language file for French (thank you gw-ipfire)
>>>>>>>
>>>>>>> ---
>>>>>>>
>>>>>>> rpz-beta-0.1.11-11.ipfire on 2024-10-18
>>>>>>> rpz.cgi:
>>>>>>> - new feature: added new language file for Italian (thank you umberto)
>>>>>>> - new feature: added new language file for Spanish (thank you Roberto)
>>>>>>>
>>>>>>> ---
>>>>>>>
>>>>>>> rpz-beta-0.1.10-10.ipfire on 2024-10-15
>>>>>>> rpz-make:
>>>>>>> - bug fix: corrected validation error for a custom list entry (thank you siosios)
>>>>>>> - e.g., `*.cloudflare-dns.com`
>>>>>>>
>>>>>>> install.sh:
>>>>>>> - bug fix: add chown to correct user created files
>>>>>>>
>>>>>>> update.sh:
>>>>>>> - bug fix: add chown to correct user created files (thank you siosios)
>>>>>>>
>>>>>>> ---
>>>>>>>
>>>>>>> rpz-beta-0.1.9-9.ipfire on 2024-10-08
>>>>>>> rpz.cgi:
>>>>>>> - new feature: added new language file for German (thank you Leo)
>>>>>>> - bug fix: add missing "rpz exitcode 110"
>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>
>>>>>>> ---
>>>>>>>
>>>>>>> rpz-beta-0.1.8-8.ipfire on 2024-10-04
>>>>>>> - skipped
>>>>>>>
>>>>>>> ---
>>>>>>>
>>>>>>> rpz-beta-0.1.7-7.ipfire on 2024-10-03
>>>>>>> All:
>>>>>>> - new feature: includes beta version numbers for pakfire package,
>>>>>>> instead of only `rpz-1.0.0-1.ipfire`, for each release.
>>>>>>>
>>>>>>> rpz.cgi:
>>>>>>> - new feature: added new WebGUI at `rpz.cgi`
>>>>>>> - a BIG thank you to Leo Hofmann for all of his work creating the webgui!!
>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>
>>>>>>> rpz-make:
>>>>>>> - new feature: validate entries in allowlist and blocklist
>>>>>>> - new feature: add "no-reload" option for WebGUI
>>>>>>>
>>>>>>> rpz-metrics:
>>>>>>> - new feature: info can be sorted by name, by hit count, by line count, by
>>>>>>> "enabled" list or all lists
>>>>>>>
>>>>>>> backups:
>>>>>>> - bug fix: include all files in `/var/ipfire/dns/rpz` directory in backup
>>>>>>>
>>>>>>> update.sh:
>>>>>>> - bug fix: corrected ownership for `/var/ipfire/dns/rpz` directory during an
>>>>>>> update
>>>>>>>
>>>>>>> Build:
>>>>>>> - bug fix: `block.rpz.conf` and `block.rpz` from build. Files to be created
>>>>>>> by `rpz-make`
>>>>>>>
>>>>>>> WebGUI and German language file
>>>>>>> Contribution-by: Leo-Andres Hofmann <hofmann(a)leo-andres.de>
>>>>>>>
>>>>>>> Spanish language file
>>>>>>> Contribution-by: Roberto Peña
>>>>>>>
>>>>>>> Italian language file
>>>>>>> Contribution-by: Umberto Parma
>>>>>>>
>>>>>>> French language file
>>>>>>> Contribution-by: gw-ipfire
>>>>>>>
>>>>>>> Turkish language file
>>>>>>> Contribution-by: Peppe Tech
>>>>>>>
>>>>>>> Contribution-by: Bernhard Bitsch <bbitsch(a)ipfire.org>
>>>>>>> Contribution-by: Erik Kapfer <erik.kapfer(a)ipfire.org>
>>>>>>> Signed-off-by: Jon Murphy <jon.murphy(a)ipfire.org
>>>>>>> ---
>>>>>>> config/backup/includes/rpz | 4 +
>>>>>>> config/cfgroot/manualpages | 1 +
>>>>>>> config/menu/EX-rpz.menu | 6 +
>>>>>>> config/rootfiles/common/configroot | 1 +
>>>>>>> config/rootfiles/common/web-user-interface | 1 +
>>>>>>> config/rootfiles/packages/rpz | 20 +
>>>>>>> config/rpz/00-rpz.conf | 10 +
>>>>>>> config/rpz/rpz-config | 130 +++
>>>>>>> config/rpz/rpz-functions | 85 ++
>>>>>>> config/rpz/rpz-make | 203 +++++
>>>>>>> config/rpz/rpz-metrics | 170 ++++
>>>>>>> config/rpz/rpz-sleep | 58 ++
>>>>>>> config/rpz/rpz.de.pl | 30 +
>>>>>>> config/rpz/rpz.en.pl | 30 +
>>>>>>> config/rpz/rpz.es.pl | 30 +
>>>>>>> config/rpz/rpz.fr.pl | 30 +
>>>>>>> config/rpz/rpz.it.pl | 30 +
>>>>>>> config/rpz/rpz.tr.pl | 30 +
>>>>>>> html/cgi-bin/rpz.cgi | 923 +++++++++++++++++++++
>>>>>>> lfs/rpz | 96 +++
>>>>>>> make.sh | 3 +-
>>>>>>> src/paks/rpz/install.sh | 36 +
>>>>>>> src/paks/rpz/uninstall.sh | 38 +
>>>>>>> src/paks/rpz/update.sh | 52 ++
>>>>>>> 24 files changed, 2016 insertions(+), 1 deletion(-)
>>>>>>> create mode 100644 config/backup/includes/rpz
>>>>>>> create mode 100644 config/menu/EX-rpz.menu
>>>>>>> create mode 100644 config/rootfiles/packages/rpz
>>>>>>> create mode 100644 config/rpz/00-rpz.conf
>>>>>>> create mode 100644 config/rpz/rpz-config
>>>>>>> create mode 100644 config/rpz/rpz-functions
>>>>>>> create mode 100644 config/rpz/rpz-make
>>>>>>> create mode 100755 config/rpz/rpz-metrics
>>>>>>> create mode 100755 config/rpz/rpz-sleep
>>>>>>> create mode 100644 config/rpz/rpz.de.pl
>>>>>>> create mode 100644 config/rpz/rpz.en.pl
>>>>>>> create mode 100644 config/rpz/rpz.es.pl
>>>>>>> create mode 100644 config/rpz/rpz.fr.pl
>>>>>>> create mode 100644 config/rpz/rpz.it.pl
>>>>>>> create mode 100644 config/rpz/rpz.tr.pl
>>>>>>> create mode 100644 html/cgi-bin/rpz.cgi
>>>>>>> create mode 100644 lfs/rpz
>>>>>>> create mode 100644 src/paks/rpz/install.sh
>>>>>>> create mode 100644 src/paks/rpz/uninstall.sh
>>>>>>> create mode 100644 src/paks/rpz/update.sh
>>>>>>>
>>>>>>> diff --git a/config/backup/includes/rpz b/config/backup/includes/rpz
>>>>>>> new file mode 100644
>>>>>>> index 000000000..36513e494
>>>>>>> --- /dev/null
>>>>>>> +++ b/config/backup/includes/rpz
>>>>>>> @@ -0,0 +1,4 @@
>>>>>>> +/var/ipfire/dns/rpz/*
>>>>>>> +/etc/unbound/zonefiles/allow.rpz
>>>>>>> +/etc/unbound/zonefiles/block.rpz
>>>>>>> +/etc/unbound/local.d/*rpz.conf
>>>>>>> diff --git a/config/cfgroot/manualpages b/config/cfgroot/manualpages
>>>>>>> index 1f7e01efc..d3a48c633 100644
>>>>>>> --- a/config/cfgroot/manualpages
>>>>>>> +++ b/config/cfgroot/manualpages
>>>>>>> @@ -70,6 +70,7 @@ pakfire.cgi=configuration/ipfire/pakfire
>>>>>>> wlanap.cgi=addons/wireless
>>>>>>> tor.cgi=addons/tor
>>>>>>> samba.cgi=addons/samba
>>>>>>> +rpz.cgi=addons/rpz
>>>>>>>
>>>>>>> # Logs menu
>>>>>>> logs.cgi/summary.dat=configuration/logs/summary
>>>>>>> diff --git a/config/menu/EX-rpz.menu b/config/menu/EX-rpz.menu
>>>>>>> new file mode 100644
>>>>>>> index 000000000..2f4daf410
>>>>>>> --- /dev/null
>>>>>>> +++ b/config/menu/EX-rpz.menu
>>>>>>> @@ -0,0 +1,6 @@
>>>>>>> +$subipfire->{'20.rpz'} = {
>>>>>>> + 'caption' => $Lang::tr{'rpz'},
>>>>>>> + 'uri' => '/cgi-bin/rpz.cgi',
>>>>>>> + 'title' => "RPZ",
>>>>>>> + 'enabled' => 1,
>>>>>>> +};
>>>>>>> diff --git a/config/rootfiles/common/configroot b/config/rootfiles/common/configroot
>>>>>>> index 9839eee45..b30d6aae4 100644
>>>>>>> --- a/config/rootfiles/common/configroot
>>>>>>> +++ b/config/rootfiles/common/configroot
>>>>>>> @@ -120,6 +120,7 @@ var/ipfire/menu.d/70-log.menu
>>>>>>> #var/ipfire/menu.d/EX-apcupsd.menu
>>>>>>> #var/ipfire/menu.d/EX-guardian.menu
>>>>>>> #var/ipfire/menu.d/EX-mympd.menu
>>>>>>> +#var/ipfire/menu.d/EX-rpz.menu
>>>>>>> #var/ipfire/menu.d/EX-samba.menu
>>>>>>> #var/ipfire/menu.d/EX-tor.menu
>>>>>>> #var/ipfire/menu.d/EX-transmission.menu
>>>>>>> diff --git a/config/rootfiles/common/web-user-interface b/config/rootfiles/common/web-user-interface
>>>>>>> index 816241dae..e00464076 100644
>>>>>>> --- a/config/rootfiles/common/web-user-interface
>>>>>>> +++ b/config/rootfiles/common/web-user-interface
>>>>>>> @@ -69,6 +69,7 @@ srv/web/ipfire/cgi-bin/proxy.cgi
>>>>>>> srv/web/ipfire/cgi-bin/qos.cgi
>>>>>>> srv/web/ipfire/cgi-bin/remote.cgi
>>>>>>> srv/web/ipfire/cgi-bin/routing.cgi
>>>>>>> +#srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>> #srv/web/ipfire/cgi-bin/samba.cgi
>>>>>>> srv/web/ipfire/cgi-bin/services.cgi
>>>>>>> srv/web/ipfire/cgi-bin/shutdown.cgi
>>>>>>> diff --git a/config/rootfiles/packages/rpz b/config/rootfiles/packages/rpz
>>>>>>> new file mode 100644
>>>>>>> index 000000000..1c8663049
>>>>>>> --- /dev/null
>>>>>>> +++ b/config/rootfiles/packages/rpz
>>>>>>> @@ -0,0 +1,20 @@
>>>>>>> +etc/unbound/local.d/00-rpz.conf
>>>>>>> +etc/unbound/zonefiles
>>>>>>> +etc/unbound/zonefiles/allow.rpz
>>>>>>> +usr/sbin/rpz-config
>>>>>>> +usr/sbin/rpz-functions
>>>>>>> +usr/sbin/rpz-make
>>>>>>> +usr/sbin/rpz-metrics
>>>>>>> +usr/sbin/rpz-sleep
>>>>>>> +var/ipfire/addon-lang/rpz.de.pl
>>>>>>> +var/ipfire/addon-lang/rpz.en.pl
>>>>>>> +var/ipfire/addon-lang/rpz.es.pl
>>>>>>> +var/ipfire/addon-lang/rpz.fr.pl
>>>>>>> +var/ipfire/addon-lang/rpz.it.pl
>>>>>>> +var/ipfire/addon-lang/rpz.tr.pl
>>>>>>> +var/ipfire/backup/addons/includes/rpz
>>>>>>> +var/ipfire/dns/rpz
>>>>>>> +var/ipfire/dns/rpz/allowlist
>>>>>>> +var/ipfire/dns/rpz/blocklist
>>>>>>> +var/ipfire/menu.d/EX-rpz.menu
>>>>>>> +srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>> diff --git a/config/rpz/00-rpz.conf b/config/rpz/00-rpz.conf
>>>>>>> new file mode 100644
>>>>>>> index 000000000..f005a4f2e
>>>>>>> --- /dev/null
>>>>>>> +++ b/config/rpz/00-rpz.conf
>>>>>>> @@ -0,0 +1,10 @@
>>>>>>> +server:
>>>>>>> + module-config: "respip validator iterator"
>>>>>>> +
>>>>>>> +rpz:
>>>>>>> + name: allow.rpz
>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>> + rpz-action-override: passthru
>>>>>>> + rpz-log: yes
>>>>>>> + rpz-log-name: allow
>>>>>>> + rpz-signal-nxdomain-ra: yes
>>>>>>> diff --git a/config/rpz/rpz-config b/config/rpz/rpz-config
>>>>>>> new file mode 100644
>>>>>>> index 000000000..c72d50f9b
>>>>>>> --- /dev/null
>>>>>>> +++ b/config/rpz/rpz-config
>>>>>>> @@ -0,0 +1,130 @@
>>>>>>> +#!/bin/bash
>>>>>>> +###############################################################################
>>>>>>> +# #
>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info(a)ipfire.org> #
>>>>>>> +# #
>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>> +# #
>>>>>>> +###############################################################################
>>>>>>> +
>>>>>>> +version="2025-01-11 - v44"
>>>>>>> +
>>>>>>> +############### Functions ###############
>>>>>>> +
>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>> +
>>>>>>> +############### Main ###############
>>>>>>> +
>>>>>>> +tagName="unbound"
>>>>>>> +
>>>>>>> +rpzAction="${1}" # input RPZ action
>>>>>>> +rpzName="${2}" # input RPZ name
>>>>>>> +rpzURL="${3}" # input RPZ URL
>>>>>>> +rpzOption1="${4}" # input RPZ option #1
>>>>>>> +rpzOption2="${5}" # input RPZ option #2
>>>>>>> +
>>>>>>> +rpzConfig="/etc/unbound/local.d/${rpzName}.rpz.conf" # output zone conf file
>>>>>>> +rpzFile="/etc/unbound/zonefiles/${rpzName}.rpz" # output for RPZ file
>>>>>>> +
>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>> +
>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>> + case "$1" in
>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>> + esac
>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>> +done
>>>>>>> +
>>>>>>> +case "${rpzAction}" in
>>>>>>> + # add new rpz list
>>>>>>> + add )
>>>>>>> + check_name "${rpzName}" # is this a valid name?
>>>>>>> + # does this config already exist? If yes, then exit
>>>>>>> + if [[ -f "${rpzConfig}" ]] ; then
>>>>>>> + msg_log "error: rpz: duplicate - ${rpzConfig} already exists. exit"
>>>>>>> + exit 104
>>>>>>> + fi
>>>>>>> +
>>>>>>> + # is this a valid URL?
>>>>>>> + regex='^https://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
>>>>>>> + if ! [[ "${rpzURL}" =~ $regex ]] ; then
>>>>>>> + msg_log "error: rpz: the URL is not valid: \"${rpzURL}\". exit."
>>>>>>> + exit 105
>>>>>>> + fi
>>>>>>> +
>>>>>>> + # create the zone config file
>>>>>>> + {
>>>>>>> + echo "rpz:"
>>>>>>> + echo " name: ${rpzName}.rpz"
>>>>>>> + echo " zonefile: ${rpzFile}"
>>>>>>> + echo " url: ${rpzURL}"
>>>>>>> + echo " rpz-action-override: nxdomain"
>>>>>>> + echo " rpz-log: ${rpzLog}"
>>>>>>> + echo " rpz-log-name: ${rpzName}"
>>>>>>> + echo " rpz-signal-nxdomain-ra: yes"
>>>>>>> + } > "${rpzConfig}"
>>>>>>> +
>>>>>>> + # set-up zonefile
>>>>>>> + # create an empty rpz file if it does not exist
>>>>>>> + if [[ ! -f "${rpzFile}" ]] ; then
>>>>>>> + touch "${rpzFile}"
>>>>>>> + # unbound requires these settings for rpz files
>>>>>>> + set_permissions "${rpzFile}" "${rpzConfig}"
>>>>>>> + fi
>>>>>>> + ;;
>>>>>>> +
>>>>>>> + # trash config file & rpz file
>>>>>>> + remove )
>>>>>>> + if ! [[ -f "${rpzConfig}" ]] ; then
>>>>>>> + msg_log "error: rpz: cannot remove ${rpzConfig}, does not exist. exit"
>>>>>>> + exit 106
>>>>>>> + fi
>>>>>>> +
>>>>>>> + msg_log "info: rpz: remove config file & rpz file \"${rpzName}\""
>>>>>>> + rm "${rpzConfig}"
>>>>>>> + rm "${rpzFile}"
>>>>>>> + ;;
>>>>>>> +
>>>>>>> + reload )
>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>> + ;;
>>>>>>> +
>>>>>>> + list )
>>>>>>> + awk -F':' '/^\s*name:/{ gsub(/[[:blank:]]|\.rpz/, "",$2) ; NAME=$2 } \
>>>>>>> + /^\s*url:/{ gsub(/[[:blank:]]/, "") ; print NAME"="$2":"$3} ' \
>>>>>>> + /etc/unbound/local.d/*rpz.conf
>>>>>>> + exit
>>>>>>> + ;;
>>>>>>> +
>>>>>>> + unbound-restart )
>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>> + unbound_restart
>>>>>>> + exit
>>>>>>> + ;;
>>>>>>> +
>>>>>>> + * )
>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>> + printf "Usage: $(basename "$0") <ACTION> <NAME> <URL> <OPTION> <OPTION>\n"
>>>>>>> + printf "Version: ${version}\n"
>>>>>>> + exit 108
>>>>>>> + ;;
>>>>>>> +
>>>>>>> +esac
>>>>>>> +
>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>> +
>>>>>>> +exit
>>>>>>> diff --git a/config/rpz/rpz-functions b/config/rpz/rpz-functions
>>>>>>> new file mode 100644
>>>>>>> index 000000000..ace1d2690
>>>>>>> --- /dev/null
>>>>>>> +++ b/config/rpz/rpz-functions
>>>>>>> @@ -0,0 +1,85 @@
>>>>>>> +#!/bin/bash
>>>>>>> +###############################################################################
>>>>>>> +# #
>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>>>> +# #
>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>> +# #
>>>>>>> +###############################################################################
>>>>>>> +
>>>>>>> +version="2024-12-10 - v02"
>>>>>>> +
>>>>>>> +############### Functions ###############
>>>>>>> +
>>>>>>> +msg_log () {
>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>> + if tty --silent ; then
>>>>>>> + echo "${tagName}:" "$*"
>>>>>>> + fi
>>>>>>> +}
>>>>>>> +
>>>>>>> +# check for a valid name
>>>>>>> +check_name () {
>>>>>>> + local theName="${1}"
>>>>>>> +
>>>>>>> + regex='^[a-zA-Z0-9_]+$' # no dash or plus, alpha numeric only
>>>>>>> + regex1='^(allow|block)$' # allow and block are reserved NAMEs
>>>>>>> + if [[ ! "${theName}" =~ $regex ]] || [[ "${theName}" =~ $regex1 ]] ; then
>>>>>>> + msg_log "error: rpz: the NAME is not valid: \"${theName}\". exit."
>>>>>>> + exit 101
>>>>>>> + fi
>>>>>>> +}
>>>>>>> +
>>>>>>> +set_permissions () {
>>>>>>> + chown nobody:nobody "$@"
>>>>>>> + chmod 644 "$@"
>>>>>>> +}
>>>>>>> +
>>>>>>> +check_unbound_conf () {
>>>>>>> + local thecheckConf="${1:-yes}" # check config default is yes
>>>>>>> +
>>>>>>> + # check the above config files
>>>>>>> + if [[ "${thecheckConf}" == yes ]] ; then
>>>>>>> + msg_log "info: rpz: check for errors with \"unbound-checkconf\""
>>>>>>> +
>>>>>>> + if ! unbound-checkconf ; then
>>>>>>> + msg_log "error: rpz: unbound-checkconf found invalid configuration."
>>>>>>> + msg_log \
>>>>>>> + "error: rpz: In Terminal run the command \"unbound-checkconf\" for more information. exit."
>>>>>>> + exit 102
>>>>>>> + fi
>>>>>>> + fi
>>>>>>> +}
>>>>>>> +
>>>>>>> +unbound_control_reload () {
>>>>>>> + local theReload="${1:-yes}" # reload default is yes
>>>>>>> +
>>>>>>> + if [[ "${theReload}" == yes ]] ; then
>>>>>>> + # reload due to the changes
>>>>>>> + msg_log "info: rpz: run \"unbound-control reload\""
>>>>>>> +
>>>>>>> + if ! unbound-control reload ; then
>>>>>>> + msg_log "error: rpz: unbound-control reload. exit."
>>>>>>> + exit 109
>>>>>>> + fi
>>>>>>> + fi
>>>>>>> +}
>>>>>>> +
>>>>>>> +unbound_restart () {
>>>>>>> + # restart due to the changes
>>>>>>> + msg_log "info: rpz: run \"unbound restart\""
>>>>>>> +
>>>>>>> + /usr/local/bin/unboundctrl restart
>>>>>>> +}
>>>>>>> diff --git a/config/rpz/rpz-make b/config/rpz/rpz-make
>>>>>>> new file mode 100644
>>>>>>> index 000000000..927d55170
>>>>>>> --- /dev/null
>>>>>>> +++ b/config/rpz/rpz-make
>>>>>>> @@ -0,0 +1,203 @@
>>>>>>> +#!/bin/bash
>>>>>>> +###############################################################################
>>>>>>> +# #
>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info(a)ipfire.org> #
>>>>>>> +# #
>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>> +# #
>>>>>>> +###############################################################################
>>>>>>> +
>>>>>>> +version="2025-01-11 - v14"
>>>>>>> +
>>>>>>> +############### Functions ###############
>>>>>>> +
>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>> +
>>>>>>> +# create the config file for allow
>>>>>>> +make_allow_config () {
>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>> + local theConfig="/etc/unbound/local.d/00-rpz.conf" # output zone conf file
>>>>>>> + local theList="/var/ipfire/dns/rpz/allowlist" # input custom list of domains
>>>>>>> +
>>>>>>> + msg_log "info: rpz: make config file \"00-rpz.conf\""
>>>>>>> +
>>>>>>> + echo "server:
>>>>>>> + module-config: \"respip validator iterator\"" > "${theConfig}"
>>>>>>> +
>>>>>>> + # does allow list exist?
>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>> +
>>>>>>> + echo "rpz:
>>>>>>> + name: allow.rpz
>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>> + rpz-action-override: passthru
>>>>>>> + rpz-log: ${theLog}
>>>>>>> + rpz-log-name: allow
>>>>>>> + rpz-signal-nxdomain-ra: yes" >> "${theConfig}"
>>>>>>> +
>>>>>>> + fi
>>>>>>> +
>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>> + set_permissions "${theConfig}"
>>>>>>> +}
>>>>>>> +
>>>>>>> +# create the config file for block
>>>>>>> +make_block_config () {
>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>> + local theConfig="/etc/unbound/local.d/block.rpz.conf" # output zone conf file
>>>>>>> + local theList="/var/ipfire/dns/rpz/blocklist" # input custom list of domains
>>>>>>> +
>>>>>>> + msg_log "info: rpz: make config file \"block.rpz.conf\""
>>>>>>> +
>>>>>>> + # does block list exist?
>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>> +
>>>>>>> + echo "rpz:
>>>>>>> + name: block.rpz
>>>>>>> + zonefile: /etc/unbound/zonefiles/block.rpz
>>>>>>> + rpz-action-override: nxdomain
>>>>>>> + rpz-log: ${theLog}
>>>>>>> + rpz-log-name: block
>>>>>>> + rpz-signal-nxdomain-ra: yes" > "${theConfig}"
>>>>>>> +
>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>> + set_permissions "${theConfig}"
>>>>>>> + else
>>>>>>> + # no - trash the config file
>>>>>>> + rm --verbose /etc/unbound/local.d/block.rpz.conf
>>>>>>> + fi
>>>>>>> +}
>>>>>>> +
>>>>>>> +# create an RPZ file for allow or block
>>>>>>> +make_rpz_file () {
>>>>>>> + local theName="${1}" # allow or block
>>>>>>> + local theAction='.' # the default is nxdomain or block
>>>>>>> + local actionList
>>>>>>> +
>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>> + local theZonefile="/etc/unbound/zonefiles/${theName}.rpz" # output file for RPZ
>>>>>>> +
>>>>>>> + # does a list exist?
>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>> +
>>>>>>> + # for allow set to passthru
>>>>>>> + [[ "${theName}" == allow ]] && theAction='rpz-passthru.'
>>>>>>> +
>>>>>>> + # drop any extra "blanks" and add "CNAME <RPZ action>." to each line
>>>>>>> + actionList=$( awk '{$1=$1};1' "${theList}" |
>>>>>>> + sed "/^[^;].*[[:alnum:]]/ s|$| CNAME ${theAction}|" )
>>>>>>> +
>>>>>>> + msg_log "info: rpz: create zonefile for \"${theName}list\""
>>>>>>> +
>>>>>>> +echo "; Name: ${theName} list
>>>>>>> +; Last modified: $(date "+%Y-%m-%d at %H.%M.%S %Z")
>>>>>>> +;
>>>>>>> +; domains with actions list
>>>>>>> +;
>>>>>>> +${actionList}" > "${theZonefile}"
>>>>>>> +
>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>> + set_permissions "${theZonefile}"
>>>>>>> + # set-up allow/block list files
>>>>>>> + set_permissions "${theList}"
>>>>>>> + else
>>>>>>> + msg_log "info: rpz: the ${theList} is empty."
>>>>>>> +
>>>>>>> + rm --verbose "${theZonefile}" # trash the RPZ file
>>>>>>> + fi
>>>>>>> +}
>>>>>>> +
>>>>>>> +# check if allow/block list is valid
>>>>>>> +validate_list () {
>>>>>>> + local theName="${1}" # allow or block
>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>> +
>>>>>>> + # remove good:
>>>>>>> + # - properly formated domain names with or without leading wildcard
>>>>>>> + # - properly formated top level domain (TLD) names with wildcard
>>>>>>> + # - blank lines and comment lines
>>>>>>> + # remaining lines are considered "bad"
>>>>>>> + bad_lines=$( sed --regexp-extended \
>>>>>>> + '/^(\*\.)?([a-zA-Z0-9](([a-zA-Z0-9\-]){0,61}[a-zA-Z0-9])?\.)+([a-zA-Z]{2,}|xn--[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])$/d ;
>>>>>>> + /^(\*\.)([a-z]{2,61}|xn--[a-z0-9]{1,60})$/d ;
>>>>>>> + /^$/d ; /^;/d' "${theList}" )
>>>>>>> +
>>>>>>> + if [[ ! -z "${bad_lines}" ]] ; then
>>>>>>> + msg_log "error: rpz: invalid line(s) in ${theList}."
>>>>>>> + printf "%s\n" "bad line(s): ${bad_lines}"
>>>>>>> + exit 110
>>>>>>> + fi
>>>>>>> +}
>>>>>>> +
>>>>>>> +
>>>>>>> +############### Main ###############
>>>>>>> +
>>>>>>> +tagName="unbound"
>>>>>>> +
>>>>>>> +rpzName="${1}" # input RPZ name
>>>>>>> +
>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>> +
>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>> + case "$1" in
>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>> + esac
>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>> +done
>>>>>>> +
>>>>>>> +case "${rpzName}" in
>>>>>>> + # make a new allow or block rpz file
>>>>>>> +
>>>>>>> + allow )
>>>>>>> + validate_list 'allow' # is the allowlist valid?
>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>> + make_rpz_file 'allow'
>>>>>>> + ;;
>>>>>>> +
>>>>>>> + allowblock )
>>>>>>> + validate_list 'allow' # is the list valid?
>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>> + make_rpz_file 'allow'
>>>>>>> + ;&
>>>>>>> +
>>>>>>> + block )
>>>>>>> + validate_list 'block' # is the blocklist valid?
>>>>>>> + make_block_config "${rpzLog}"
>>>>>>> + make_rpz_file 'block'
>>>>>>> + ;;
>>>>>>> +
>>>>>>> + reload )
>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>> + ;;
>>>>>>> +
>>>>>>> + unbound-restart )
>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>> + unbound_restart
>>>>>>> + exit
>>>>>>> + ;;
>>>>>>> +
>>>>>>> + * )
>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>> + printf "Usage: $(basename "$0") <NAME> <OPTION> <OPTION>\n"
>>>>>>> + printf "Version: ${version}\n"
>>>>>>> + exit 108
>>>>>>> + ;;
>>>>>>> +esac
>>>>>>> +
>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>> +
>>>>>>> +exit
>>>>>>> diff --git a/config/rpz/rpz-metrics b/config/rpz/rpz-metrics
>>>>>>> new file mode 100755
>>>>>>> index 000000000..4d43e1629
>>>>>>> --- /dev/null
>>>>>>> +++ b/config/rpz/rpz-metrics
>>>>>>> @@ -0,0 +1,170 @@
>>>>>>> +#!/bin/bash
>>>>>>> +###############################################################################
>>>>>>> +# #
>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>>>> +# #
>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>> +# #
>>>>>>> +###############################################################################
>>>>>>> +
>>>>>>> +version="2025-01-20 - v25"
>>>>>>> +
>>>>>>> +############### Main ###############
>>>>>>> +
>>>>>>> +weeks="2" # default to two message logs
>>>>>>> +sortBy="name" # default "by name"
>>>>>>> +rpzActive="enabled" # default "enabled only"
>>>>>>> +
>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>> + case "$1" in
>>>>>>> + --by-names | --by-name | name ) sortBy="name" ;;
>>>>>>> +
>>>>>>> + --by-hits | --by-hit | hits | hit ) sortBy="hit" ;;
>>>>>>> +
>>>>>>> + --by-lines | --by-line | lines | line ) sortBy="line" ;;
>>>>>>> +
>>>>>>> + --by-effect ) sortBy="effect" ;;
>>>>>>> +
>>>>>>> + --enabled-only ) rpzActive="enabled" ;;
>>>>>>> +
>>>>>>> + --active-all | --all | all ) rpzActive="all" ;;
>>>>>>> +
>>>>>>> + [0-9] | [0-9][0-9] ) weeks=$1 ;;
>>>>>>> + esac
>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>> +done
>>>>>>> +
>>>>>>> +# get the list of message logs for N weeks
>>>>>>> +messageLogs=$( find /var/log/messages* -type f | sort --version-sort |
>>>>>>> + head -"${weeks}" )
>>>>>>> +
>>>>>>> +# get the list of RPZ names & counts from the message log(s)
>>>>>>> +rpzNameCount=$( for logf in ${messageLogs} ; do
>>>>>>> + zgrep --text --fixed-strings 'info: rpz: applied' "${logf}" |
>>>>>>> + awk '$10 ~ /\[\w*]/ { print $10 }' ;
>>>>>>> + done | sort | uniq --count )
>>>>>>> +
>>>>>>> +# flip results and remove brackets `[` and `]`
>>>>>>> +rpzNameCount=$( echo "${rpzNameCount}" |
>>>>>>> + awk '{ print $2, $1 }' |
>>>>>>> + sed --regexp-extended 's|^\[(.*)\]|\1|' )
>>>>>>> +
>>>>>>> +# grab only names
>>>>>>> +rpzNames=$( echo "${rpzNameCount}" | awk '{ print $1 }' )
>>>>>>> +
>>>>>>> +# get list of RPZ files
>>>>>>> +rpzFileList=$( find /etc/unbound/zonefiles -type f -iname "*.rpz" )
>>>>>>> +
>>>>>>> +# get basename of those files
>>>>>>> +rpzBaseNames=$( echo "${rpzFileList}" |
>>>>>>> + sed 's|/etc/unbound/zonefiles/||g ; s|\.rpz||g ;' )
>>>>>>> +
>>>>>>> +# add to rpzNames
>>>>>>> +rpzNames="${rpzNames}"$'\n'"${rpzBaseNames}"
>>>>>>> +
>>>>>>> +# drop duplicate names
>>>>>>> +rpzNames=$( echo "${rpzNames}" | sort --unique )
>>>>>>> +
>>>>>>> +# get line count for each RPZ
>>>>>>> +lineCount=$( echo "${rpzFileList}" | xargs wc -l )
>>>>>>> +
>>>>>>> +# get comment line count and blank line count for each RPZ
>>>>>>> +commentCount=$( echo "${rpzFileList}" | xargs grep --count -e "^$" -e "^;" )
>>>>>>> +
>>>>>>> +# get modified date each RPZ
>>>>>>> +modDateList=$( echo "${rpzFileList}" | xargs stat -c '%.10y %n' )
>>>>>>> +
>>>>>>> +ucListAuthZones=$( unbound-control list_auth_zones )
>>>>>>> +
>>>>>>> +# get width of RPZ names
>>>>>>> +pWidth=$( echo "${rpzNames}" | awk '{ print $1" " }' | wc -L )
>>>>>>> +pFormat="%-${pWidth}s %-8s %-8s %8s %12s %12s\n"
>>>>>>> +
>>>>>>> +# print title line
>>>>>>> +printf "${pFormat}" "name" "hits" "active" "lines" " hits/line" "last download"
>>>>>>> +printf -- "--------------"
>>>>>>> +
>>>>>>> +theResults=""
>>>>>>> +totalLines=0
>>>>>>> +totalHits=0
>>>>>>> +while read -r theName
>>>>>>> +do
>>>>>>> + printf -- "--" # pretend progress bar
>>>>>>> +
>>>>>>> + # is this RPZ list active?
>>>>>>> + theActive="disabled"
>>>>>>> + if grep --quiet "^${theName}\.rpz" <<< "${ucListAuthZones}"
>>>>>>> + then
>>>>>>> + theActive="enabled"
>>>>>>> + else
>>>>>>> + [[ "${rpzActive}" == enabled ]] && continue
>>>>>>> + fi
>>>>>>> +
>>>>>>> + # get hit count
>>>>>>> + theHits="0"
>>>>>>> + if output=$( grep "^${theName}\s" <<< "${rpzNameCount}" ) ; then
>>>>>>> + theHits=$( echo "${output}" | awk '{ print $2 }' )
>>>>>>> + totalHits=$(( totalHits + theHits ))
>>>>>>> + fi
>>>>>>> +
>>>>>>> + # get line count
>>>>>>> + theLines="n/a"
>>>>>>> + hitsPerLine="0"
>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${lineCount}" ) ; then
>>>>>>> + theLines=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>> + totalLines=$(( totalLines + theLines ))
>>>>>>> +
>>>>>>> + if [[ "${theLines}" -gt 2 ]] ; then
>>>>>>> + hitsPerLine=$(( 100 * theHits / theLines ))
>>>>>>> + fi
>>>>>>> + fi
>>>>>>> +
>>>>>>> + # get modification date
>>>>>>> + theModDate="n/a"
>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${modDateList}" ) ; then
>>>>>>> + theModDate=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>> + fi
>>>>>>> +
>>>>>>> + # add to results list
>>>>>>> + theResults+="${theName} ${theHits} ${theActive} ${theLines} ${hitsPerLine} ${theModDate}"$'\n'
>>>>>>> +
>>>>>>> +done <<< "${rpzNames}"
>>>>>>> +
>>>>>>> +case "${sortBy}" in
>>>>>>> + # sort by "active" then by "name"
>>>>>>> + name) sortArg=(-k3,3r -k1,1) ;;
>>>>>>> +
>>>>>>> + # sort by "active" then by "hits" then by "name"
>>>>>>> + hit) sortArg=(-k3,3r -k2,2nr -k1,1) ;;
>>>>>>> +
>>>>>>> + # sort by "active" then by "lines" then by "name"
>>>>>>> + line) sortArg=(-k3,3r -k4,4nr -k1,1) ;;
>>>>>>> +
>>>>>>> + # sort by "active" then by "effect" then by "name"
>>>>>>> + effect) sortArg=(-k3,3r -k5,5nr -k1,1) ;;
>>>>>>> +esac
>>>>>>> +
>>>>>>> +printf -- "--------------\n"
>>>>>>> +# remove blank lines, sort, print as columns
>>>>>>> +echo "${theResults}" |
>>>>>>> + awk '!/^[[:space:]]*$/' |
>>>>>>> + sort "${sortArg[@]}" |
>>>>>>> + awk --assign=width="${pWidth}" \
>>>>>>> + '{ printf "%-*s %-8s %-8s %8s %10s %% %12s\n", width, $1, $2, $3, $4, $5, $6 }'
>>>>>>> +
>>>>>>> +printf "${pFormat}" "" "=======" "" "========" "" ""
>>>>>>> +printf "${pFormat}" "Totals -->" "${totalHits}" "" "${totalLines}" "" ""
>>>>>>> +
>>>>>>> +exit
>>>>>>> diff --git a/config/rpz/rpz-sleep b/config/rpz/rpz-sleep
>>>>>>> new file mode 100755
>>>>>>> index 000000000..dd3603599
>>>>>>> --- /dev/null
>>>>>>> +++ b/config/rpz/rpz-sleep
>>>>>>> @@ -0,0 +1,58 @@
>>>>>>> +#!/bin/bash
>>>>>>> +###############################################################################
>>>>>>> +# #
>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>>>> +# #
>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>> +# #
>>>>>>> +###############################################################################
>>>>>>> +
>>>>>>> +version="2024-08-16" # v05
>>>>>>> +
>>>>>>> +############### Functions ###############
>>>>>>> +
>>>>>>> +# send message to message log
>>>>>>> +msg_log () {
>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>> + if tty --silent ; then
>>>>>>> + echo "${tagName}:" "$*"
>>>>>>> + fi
>>>>>>> +}
>>>>>>> +
>>>>>>> +############### Main ###############
>>>>>>> +
>>>>>>> +tagName="unbound"
>>>>>>> +
>>>>>>> +sleepTime="${1:-5m}" # default to sleep for 5m (5 minutes)
>>>>>>> +
>>>>>>> +zoneList=$( unbound-control list_auth_zones | awk '{print $1}' )
>>>>>>> +
>>>>>>> +for zone in ${zoneList} ; do
>>>>>>> + printf "disable ${zone}\t"
>>>>>>> + unbound-control rpz_disable "${zone}"
>>>>>>> +done
>>>>>>> +
>>>>>>> +msg_log "info: rpz: disabled all zones for ${sleepTime}"
>>>>>>> +
>>>>>>> +sleep "${sleepTime}"
>>>>>>> +
>>>>>>> +for zone in ${zoneList} ; do
>>>>>>> + printf "enable ${zone}\t"
>>>>>>> + unbound-control rpz_enable "${zone}"
>>>>>>> +done
>>>>>>> +
>>>>>>> +msg_log "info: rpz: enabled all zones"
>>>>>>> +
>>>>>>> +exit
>>>>>>> diff --git a/config/rpz/rpz.de.pl b/config/rpz/rpz.de.pl
>>>>>>> new file mode 100644
>>>>>>> index 000000000..3770c6bb0
>>>>>>> --- /dev/null
>>>>>>> +++ b/config/rpz/rpz.de.pl
>>>>>>> @@ -0,0 +1,30 @@
>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>> +%tr = (%tr,
>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>> +'rpz apply' => 'Übernehmen',
>>>>>>> +'rpz cl allow enable' => 'Benutzerdefinierte Allowlist aktivieren:',
>>>>>>> +'rpz cl allow info' => 'Zugelassene Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>> +'rpz cl allow' => 'Benutzerdefinierte Allowlist',
>>>>>>> +'rpz cl block enable' => 'Benutzerdefinierte Blocklist aktivieren:',
>>>>>>> +'rpz cl block info' => 'Gesperrte Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>> +'rpz cl block' => 'Benutzerdefinierte Blocklist',
>>>>>>> +'rpz cl' => 'Benutzerdefinierte Listen',
>>>>>>> +'rpz exitcode 101' => 'Der Name enthält unzulässige Zeichen',
>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf hat eine fehlerhafte Konfiguration ermittelt. Führen Sie das Kommando unbound-checkconf auf der Konsole aus, um weitere Informationen zu erhalten.',
>>>>>>> +'rpz exitcode 103' => 'Die benutzerdefinierte Allow-/Blocklist ist leer',
>>>>>>> +'rpz exitcode 104' => 'Ein Eintrag mit identischem Namen existiert bereits',
>>>>>>> +'rpz exitcode 105' => 'Die URL ist ungültig',
>>>>>>> +'rpz exitcode 106' => 'Eintrag kann nicht entfernt werden, der Name existiert nicht',
>>>>>>> +'rpz exitcode 107' => 'Der Name ist ungültig - nur "allow" oder "block" möglich',
>>>>>>> +'rpz exitcode 108' => 'Fehlende oder inkorrekte Parameter',
>>>>>>> +'rpz exitcode 109' => 'unbound-control reload ist fehlgeschlagen',
>>>>>>> +'rpz exitcode 110' => 'Die benutzerdefinierte Allow-/Blocklist enthält unzulässige Einträge',
>>>>>>> +'rpz exitcode 201' => 'Die Anmerkung enthält unzulässige Zeichen',
>>>>>>> +'rpz exitcode 202' => 'Ungültiger Eintrag in der benutzerdefinierten Allowlist, Zeile ',
>>>>>>> +'rpz exitcode 203' => 'Ungültiger Eintrag in der benutzerdefinierten Blocklist, Zeile ',
>>>>>>> +'rpz exitcode 204' => 'Ausgewählter Eintrag existiert nicht: ',
>>>>>>> +'rpz zf editor' => 'Zonendatei-Eintrag bearbeiten',
>>>>>>> +'rpz zf imported' => '(importiert aus rpz-config)',
>>>>>>> +'rpz zf remark info' => 'Erlaubte Zeichen sind a-z, A-Z, 0-9 und Unterstriche',
>>>>>>> +'rpz zf' => 'Zonendateien',
>>>>>>> +);
>>>>>>> diff --git a/config/rpz/rpz.en.pl b/config/rpz/rpz.en.pl
>>>>>>> new file mode 100644
>>>>>>> index 000000000..0720a8940
>>>>>>> --- /dev/null
>>>>>>> +++ b/config/rpz/rpz.en.pl
>>>>>>> @@ -0,0 +1,30 @@
>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>> +%tr = (%tr,
>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>> +'rpz apply' => 'Apply',
>>>>>>> +'rpz cl allow enable' => 'Enable custom allowlist:',
>>>>>>> +'rpz cl allow info' => 'Allowed domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>> +'rpz cl allow' => 'Custom allowlist',
>>>>>>> +'rpz cl block enable' => 'Enable custom blocklist:',
>>>>>>> +'rpz cl block info' => 'Blocked domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>> +'rpz cl block' => 'Custom blocklist',
>>>>>>> +'rpz cl' => 'Custom lists',
>>>>>>> +'rpz exitcode 101' => 'the NAME is not valid',
>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf found invalid configuration. In the Terminal run the command unbound-checkconf for more information',
>>>>>>> +'rpz exitcode 103' => 'the allow/block list is empty',
>>>>>>> +'rpz exitcode 104' => 'duplicate - NAME already exists',
>>>>>>> +'rpz exitcode 105' => 'the URL is not valid',
>>>>>>> +'rpz exitcode 106' => 'cannot remove the NAME does not exist',
>>>>>>> +'rpz exitcode 107' => 'the NAME is not valid - "allow" or "block" only',
>>>>>>> +'rpz exitcode 108' => 'missing or incorrect parameter',
>>>>>>> +'rpz exitcode 109' => 'unbound-control reload failed',
>>>>>>> +'rpz exitcode 110' => 'custom Allowlist/Blocklist contains invalid entries',
>>>>>>> +'rpz exitcode 201' => 'the REMARK is not valid',
>>>>>>> +'rpz exitcode 202' => 'invalid entry in allowlist, line ',
>>>>>>> +'rpz exitcode 203' => 'invalid entry in blocklist, line ',
>>>>>>> +'rpz exitcode 204' => 'Selected entry does not exist: ',
>>>>>>> +'rpz zf editor' => 'Edit zonefiles entry',
>>>>>>> +'rpz zf imported' => '(imported from rpz-config)',
>>>>>>> +'rpz zf remark info' => 'Valid characters are a-z, A-Z, 0-9 and underscore.',
>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>> +);
>>>>>>> diff --git a/config/rpz/rpz.es.pl b/config/rpz/rpz.es.pl
>>>>>>> new file mode 100644
>>>>>>> index 000000000..98628e4aa
>>>>>>> --- /dev/null
>>>>>>> +++ b/config/rpz/rpz.es.pl
>>>>>>> @@ -0,0 +1,30 @@
>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>> +%tr = (%tr,
>>>>>>> +'rpz' => 'Zonas de política de respuesta (RPZ)',
>>>>>>> +'rpz apply' => 'Aplicar',
>>>>>>> +'rpz cl allow enable' => 'Habilitar la lista blanca personalizada:',
>>>>>>> +'rpz cl allow info' => 'Dominio permitido (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>> +'rpz cl allow' => 'Lista blanca personalizada',
>>>>>>> +'rpz cl block enable' => 'Habilitar la lista negra personalizada:',
>>>>>>> +'rpz cl block info' => 'Dominio bloqueado (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>> +'rpz cl block' => 'Lista negra personalizada',
>>>>>>> +'rpz cl' => 'Lista personalizada',
>>>>>>> +'rpz exitcode 101' => 'El NOMBRE no es válido',
>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha encontrado una configuración no válida. Desde Terminal, ejecute el comando unbound-checkconf para mayor información',
>>>>>>> +'rpz exitcode 103' => 'La lista de permitidos/bloqueados está vacía',
>>>>>>> +'rpz exitcode 104' => 'duplicado - NOMBRE ya existe',
>>>>>>> +'rpz exitcode 105' => 'la URL no es válida',
>>>>>>> +'rpz exitcode 106' => 'no es posible eliminar el NOMBRE que no existe',
>>>>>>> +'rpz exitcode 107' => 'el NOMBRE no es válido - sólo "permitir" o "bloquear"',
>>>>>>> +'rpz exitcode 108' => 'parámetro faltante o incorrecto',
>>>>>>> +'rpz exitcode 109' => 'Error al recargar unbound-control',
>>>>>>> +'rpz exitcode 110' => 'la Lista blanca/Lista negra personalizada contiene entradas no válidas',
>>>>>>> +'rpz exitcode 201' => 'el COMENTARIO no es válido',
>>>>>>> +'rpz exitcode 202' => 'entrada no válida en la lista blanca, línea ',
>>>>>>> +'rpz exitcode 203' => 'entrada no válida en la lista negra, línea ',
>>>>>>> +'rpz exitcode 204' => 'La entrada seleccionada no existe: ',
>>>>>>> +'rpz zf editor' => 'Editar la entrada de archivos de zona',
>>>>>>> +'rpz zf imported' => '(importado de rpz-config)',
>>>>>>> +'rpz zf remark info' => 'Los caracteres válidos son a-z, A-Z, 0-9 y guión bajo',
>>>>>>> +'rpz zf' => 'Archivos de zona',
>>>>>>> +);
>>>>>>> diff --git a/config/rpz/rpz.fr.pl b/config/rpz/rpz.fr.pl
>>>>>>> new file mode 100644
>>>>>>> index 000000000..f35f3c2d0
>>>>>>> --- /dev/null
>>>>>>> +++ b/config/rpz/rpz.fr.pl
>>>>>>> @@ -0,0 +1,30 @@
>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>> +%tr = (%tr,
>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>> +'rpz apply' => 'Appliquer',
>>>>>>> +'rpz cl allow enable' => 'Activer la liste d\'autorisations personnalisée:',
>>>>>>> +'rpz cl allow info' => 'Domaines autorisés (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>> +'rpz cl allow' => 'Liste d\'autorisations personnalisée',
>>>>>>> +'rpz cl block enable' => 'Activer la liste de blocage personnalisée:',
>>>>>>> +'rpz cl block info' => 'Domaines bloqués (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>> +'rpz cl block' => 'liste de blocage personnalisé',
>>>>>>> +'rpz cl' => 'Listes personnalisées',
>>>>>>> +'rpz exitcode 101' => 'le NOM n\'est pas valide',
>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf configuration non valide trouvée. Dans le terminal, exécutez la commande unbound-checkconf pour plus d\'informations',
>>>>>>> +'rpz exitcode 103' => 'la liste autoriser/bloquer est vide',
>>>>>>> +'rpz exitcode 104' => 'le NOM existe déjà',
>>>>>>> +'rpz exitcode 105' => 'L\'URL n\'est pas valide',
>>>>>>> +'rpz exitcode 106' => 'impossible de supprimer le NOM n\'existe pas',
>>>>>>> +'rpz exitcode 107' => 'le NOM n\'est pas valide - « autoriser » ou « bloquer » seulement',
>>>>>>> +'rpz exitcode 108' => 'paramètre manquant ou incorrect',
>>>>>>> +'rpz exitcode 109' => 'unbound-control rechargement échoué',
>>>>>>> +'rpz exitcode 110' => 'la liste autoriser/bloquer contient des entrées non valides',
>>>>>>> +'rpz exitcode 201' => 'la REMARQUE n\'est pas valable',
>>>>>>> +'rpz exitcode 202' => 'entrée non valide dans la liste d\'autorisation, ligne ',
>>>>>>> +'rpz exitcode 203' => 'entrée non valide dans la liste de blocs, ligne ',
>>>>>>> +'rpz exitcode 204' => 'L\'entrée sélectionnée n\'existe pas: ',
>>>>>>> +'rpz zf editor' => 'Modifier l\'entrée Fichiers Zone',
>>>>>>> +'rpz zf imported' => '(importé de rpz-config)',
>>>>>>> +'rpz zf remark info' => 'Les caractères valides sont a-z, A-Z, 0-9 et soulignement.',
>>>>>>> +'rpz zf' => 'Fichiers Zone',
>>>>>>> +);
>>>>>>> diff --git a/config/rpz/rpz.it.pl b/config/rpz/rpz.it.pl
>>>>>>> new file mode 100644
>>>>>>> index 000000000..ee81605c9
>>>>>>> --- /dev/null
>>>>>>> +++ b/config/rpz/rpz.it.pl
>>>>>>> @@ -0,0 +1,30 @@
>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>> +%tr = (%tr,
>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>> +'rpz apply' => 'Applica',
>>>>>>> +'rpz cl allow enable' => 'Abilita la Whitelist personalizzata:',
>>>>>>> +'rpz cl allow info' => 'Domini consentiti (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>> +'rpz cl allow' => 'Whitelist personalizzata',
>>>>>>> +'rpz cl block enable' => 'Abilita la Blacklist personalizzata:',
>>>>>>> +'rpz cl block info' => 'Domini bloccati (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>> +'rpz cl block' => 'Blacklist personalizzata',
>>>>>>> +'rpz cl' => 'Liste personalizzate',
>>>>>>> +'rpz exitcode 101' => 'il NOME non è valido',
>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha trovato una configurazione non valida. Dal Terminale esegui il comando unbound-checkconf per maggiori informazioni',
>>>>>>> +'rpz exitcode 103' => 'l\'elenco consentiti/bloccati è vuoto',
>>>>>>> +'rpz exitcode 104' => 'duplicato - NAME esiste di già',
>>>>>>> +'rpz exitcode 105' => 'l\'URL non è valido',
>>>>>>> +'rpz exitcode 106' => 'non è possibile rimuovere il NOME non esiste',
>>>>>>> +'rpz exitcode 107' => 'il NOME non è valido - solo "consenti" o "blocca"',
>>>>>>> +'rpz exitcode 108' => 'parametro mancante o non corretto',
>>>>>>> +'rpz exitcode 109' => 'ricaricamento del controllo non associato non riuscito',
>>>>>>> +'rpz exitcode 110' => 'la Whitelist/Blacklist personalizzata contiene voci non valide',
>>>>>>> +'rpz exitcode 201' => 'l"OSSERVAZIONE non è valida',
>>>>>>> +'rpz exitcode 202' => 'voce non valida nella Whitelist, riga ',
>>>>>>> +'rpz exitcode 203' => 'voce non valida nella Blacklist, riga ',
>>>>>>> +'rpz exitcode 204' => 'La voce selezionata non esiste: ',
>>>>>>> +'rpz zf editor' => 'Modifica la voce dei file di zona',
>>>>>>> +'rpz zf imported' => '(importato da rpz-config)',
>>>>>>> +'rpz zf remark info' => 'I caratteri validi sono a-z, A-Z, 0-9 e trattino basso',
>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>> +);
>>>>>>> diff --git a/config/rpz/rpz.tr.pl b/config/rpz/rpz.tr.pl
>>>>>>> new file mode 100644
>>>>>>> index 000000000..00226e192
>>>>>>> --- /dev/null
>>>>>>> +++ b/config/rpz/rpz.tr.pl
>>>>>>> @@ -0,0 +1,30 @@
>>>>>>> +# I�in eklendi Ayriyeten Response Policy Zone (RPZ)
>>>>>>> +%tr = (%tr,
>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>> +'rpz apply' => 'Uygulamak',
>>>>>>> +'rpz cl allow enable' => '�zel Etkinlestir allowlist:',
>>>>>>> +'rpz cl allow info' => 'Izin verilmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>> +'rpz cl allow' => 'Etkinlestir allowlist',
>>>>>>> +'rpz cl block enable' => '�zel Etkinlestir blocklist:',
>>>>>>> +'rpz cl block info' => 'Engellenmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>> +'rpz cl block' => 'Engellenmis blocklist',
>>>>>>> +'rpz cl' => 'Engellenmis lists',
>>>>>>> +'rpz exitcode 101' => 'NAME ge�erli degil',
>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ge�ersiz yapilandirma bulundu. Terminalde daha fazla bilgi i�in Unbound-checkConf komutunu �alistirin',
>>>>>>> +'rpz exitcode 103' => 'allow/block liste bos',
>>>>>>> +'rpz exitcode 104' => 'kopyalamak - NAME zaten var',
>>>>>>> +'rpz exitcode 105' => 'URL ge�erli degil',
>>>>>>> +'rpz exitcode 106' => '�ikarilamiyor NAME yok',
>>>>>>> +'rpz exitcode 107' => 'NAME ge�erli degil - "allow" veya "block" yalniz',
>>>>>>> +'rpz exitcode 108' => 'Parametre eksik veya yanlis',
>>>>>>> +'rpz exitcode 109' => 'unbound-control basarisiz',
>>>>>>> +'rpz exitcode 110' => 'Engellenmis Allowlist/Blocklist ge�ersiz girisler i�erir',
>>>>>>> +'rpz exitcode 201' => 'REMARK ge�erli degil',
>>>>>>> +'rpz exitcode 202' => 'Ge�ersiz giris allowlist, line ',
>>>>>>> +'rpz exitcode 203' => 'Ge�ersiz giris blocklist, line ',
>>>>>>> +'rpz exitcode 204' => 'Se�ilen giris yok: ',
>>>>>>> +'rpz zf editor' => 'yazimlamak zonefiles giris',
>>>>>>> +'rpz zf imported' => '(ithal edildi rpz-config)',
>>>>>>> +'rpz zf remark info' => 'Ge�erli karakterler a-z, A-Z, 0-9ve alt�st.',
>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>> +);
>>>>>>> diff --git a/html/cgi-bin/rpz.cgi b/html/cgi-bin/rpz.cgi
>>>>>>> new file mode 100644
>>>>>>> index 000000000..a821c92ac
>>>>>>> --- /dev/null
>>>>>>> +++ b/html/cgi-bin/rpz.cgi
>>>>>>> @@ -0,0 +1,923 @@
>>>>>>> +#!/usr/bin/perl
>>>>>>> +###############################################################################
>>>>>>> +# #
>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>> +# Copyright (C) 2005-2024 IPFire Team <info(a)ipfire.org> #
>>>>>>> +# #
>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>> +# #
>>>>>>> +###############################################################################
>>>>>>> +
>>>>>>> +use strict;
>>>>>>> +use Scalar::Util qw(looks_like_number);
>>>>>>> +
>>>>>>> +# debugging
>>>>>>> +#use warnings;
>>>>>>> +#use CGI::Carp 'fatalsToBrowser';
>>>>>>> +#use Data::Dumper;
>>>>>>> +
>>>>>>> +require '/var/ipfire/general-functions.pl';
>>>>>>> +require "${General::swroot}/lang.pl";
>>>>>>> +require "${General::swroot}/header.pl";
>>>>>>> +
>>>>>>> +###--- Extra HTML ---###
>>>>>>> +my $extraHead = <<END
>>>>>>> +<style>
>>>>>>> + /* alternating row background */
>>>>>>> + .tbl tr:nth-child(2n+2) {
>>>>>>> + background-color: var(--color-light-grey);
>>>>>>> + }
>>>>>>> + .tbl tr:nth-child(2n+3) {
>>>>>>> + background-color: var(--color-grey);
>>>>>>> + }
>>>>>>> + /* text styles */
>>>>>>> + .tbl th:not(:last-child) {
>>>>>>> + text-align: left;
>>>>>>> + }
>>>>>>> + div.right {
>>>>>>> + text-align: right;
>>>>>>> + margin-top: 0.5em;
>>>>>>> + }
>>>>>>> + /* customlist input */
>>>>>>> + textarea.domainlist {
>>>>>>> + margin: 0.5em 0;
>>>>>>> + resize: vertical;
>>>>>>> + min-height: 10em;
>>>>>>> + overflow: auto;
>>>>>>> + white-space: pre;
>>>>>>> + }
>>>>>>> + button[type=submit]:disabled {
>>>>>>> + opacity: 0.6;
>>>>>>> + }
>>>>>>> +</style>
>>>>>>> +END
>>>>>>> +;
>>>>>>> +###--- End of extra HTML ---###
>>>>>>> +
>>>>>>> +
>>>>>>> +### Settings ###
>>>>>>> +
>>>>>>> +# Request DNS service reload after configuration change
>>>>>>> +my $RPZ_RELOAD_FLAG = "${General::swroot}/dns/rpz/reload.flag";
>>>>>>> +
>>>>>>> +# Configuration file for all available zonefiles
>>>>>>> +# Format: index, name (unique), enabled (on/off), URL, remark
>>>>>>> +my $ZONEFILES_CONF = "${General::swroot}/dns/rpz/zonefiles.conf";
>>>>>>> +
>>>>>>> +# Configuration file for custom lists
>>>>>>> +# IDs: 0=allowlist, 1=blocklist, 2=options (allow/block enabled)
>>>>>>> +my $CUSTOMLISTS_CONF = "${General::swroot}/dns/rpz/customlists.conf";
>>>>>>> +
>>>>>>> +# Export custom lists to rpz-config
>>>>>>> +my $RPZ_ALLOWLIST = "${General::swroot}/dns/rpz/allowlist";
>>>>>>> +my $RPZ_BLOCKLIST = "${General::swroot}/dns/rpz/blocklist";
>>>>>>> +
>>>>>>> +
>>>>>>> +### Preparation ###
>>>>>>> +
>>>>>>> +# Create missing config files
>>>>>>> +unless(-f $ZONEFILES_CONF) { &General::system('touch', "$ZONEFILES_CONF"); }
>>>>>>> +unless(-f $CUSTOMLISTS_CONF) { &General::system('touch', "$CUSTOMLISTS_CONF"); }
>>>>>>> +
>>>>>>> +
>>>>>>> +## Global gui data
>>>>>>> +my $errormessage = "";
>>>>>>> +
>>>>>>> +## Global configuration data
>>>>>>> +my %zonefiles = ();
>>>>>>> +my %customlists = ();
>>>>>>> +&_zonefiles_load();
>>>>>>> +&_customlists_load();
>>>>>>> +
>>>>>>> +## Global CGI form data
>>>>>>> +my %cgiparams = ();
>>>>>>> +&Header::getcgihash(\%cgiparams);
>>>>>>> +
>>>>>>> +my $action = $cgiparams{'ACTION'} // 'NONE';
>>>>>>> +my $action_key = $cgiparams{'KEY'} // ''; # entry being edited, empty = none/new
>>>>>>> +
>>>>>>> +
>>>>>>> +###--- Process form actions ---###
>>>>>>> +
>>>>>>> +# Zonefiles action: Check whether the requested entry exists
>>>>>>> +if((substr($action, 0, 3) eq 'ZF_') && ($action_key)) {
>>>>>>> + unless(defined $zonefiles{$action_key}) {
>>>>>>> + $errormessage = &_rpz_error_tr(204, $action_key);
>>>>>>> + $action = 'NONE';
>>>>>>> + }
>>>>>>> +}
>>>>>>> +
>>>>>>> +## Perform actions
>>>>>>> +if($action eq 'ZF_SAVE') { ## Save new or modified zonefiles entry
>>>>>>> + if(&_action_zf_save()) {
>>>>>>> + $action = 'NONE'; # success, return to main page
>>>>>>> + &_http_prg_redirect();
>>>>>>> + } else {
>>>>>>> + $action = 'ZF_EDIT'; # error occured, keep editing
>>>>>>> + }
>>>>>>> +
>>>>>>> +} elsif($action eq 'ZF_TOGGLE') { ## Toggle on/off
>>>>>>> + if(&_action_zf_toggle()) {
>>>>>>> + $action = 'NONE';
>>>>>>> + &_http_prg_redirect();
>>>>>>> + }
>>>>>>> +
>>>>>>> +} elsif($action eq 'ZF_REMOVE') { ## Remove entry
>>>>>>> + if(&_action_zf_remove()) {
>>>>>>> + $action = 'NONE';
>>>>>>> + &_http_prg_redirect();
>>>>>>> + }
>>>>>>> +
>>>>>>> +} elsif($action eq 'CL_SAVE') { ## Save custom lists
>>>>>>> + if(&_action_cl_save()) {
>>>>>>> + $action = 'NONE';
>>>>>>> + &_http_prg_redirect();
>>>>>>> + }
>>>>>>> +
>>>>>>> +} elsif($action eq 'RPZ_RELOAD') { ## Reload dns configuration
>>>>>>> + if(&_action_rpz_reload()) {
>>>>>>> + $action = 'NONE';
>>>>>>> + &_http_prg_redirect();
>>>>>>> + }
>>>>>>> +
>>>>>>> +} elsif($action eq 'UNB_RESTART') { ## Restart unbound service
>>>>>>> + if(&_action_unb_restart()) {
>>>>>>> + $action = 'NONE';
>>>>>>> + &_http_prg_redirect();
>>>>>>> + }
>>>>>>> +
>>>>>>> +}
>>>>>>> +
>>>>>>> +
>>>>>>> +###--- Start GUI ---###
>>>>>>> +
>>>>>>> +## Start http output
>>>>>>> +&Header::showhttpheaders();
>>>>>>> +
>>>>>>> +# Start HTML
>>>>>>> +&Header::openpage($Lang::tr{'rpz'}, 1, $extraHead);
>>>>>>> +&Header::openbigbox('100%', 'left', '');
>>>>>>> +
>>>>>>> +# Show error messages
>>>>>>> +if($errormessage) {
>>>>>>> + &_print_message($errormessage);
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Handle zonefile add/edit mode
>>>>>>> +if($action eq "ZF_EDIT") {
>>>>>>> + &_print_zonefile_editor();
>>>>>>> +
>>>>>>> + # Finalize page and exit cleanly
>>>>>>> + &Header::closebigbox();
>>>>>>> + &Header::closepage();
>>>>>>> + exit(0);
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Show gui elements
>>>>>>> +&_print_zonefiles();
>>>>>>> +&_print_customlists();
>>>>>>> +&_print_gui_extras();
>>>>>>> +
>>>>>>> +&Header::closebigbox();
>>>>>>> +&Header::closepage();
>>>>>>> +
>>>>>>> +###--- End of GUI ---###
>>>>>>> +
>>>>>>> +
>>>>>>> +###--- Internal configuration file functions ---###
>>>>>>> +
>>>>>>> +# Load all available zonefiles from rpz-config and the internal configuration
>>>>>>> +sub _zonefiles_load {
>>>>>>> + # Clean start
>>>>>>> + %zonefiles = ();
>>>>>>> +
>>>>>>> + # Source 1: Get the currently enabled zonefiles from rpz-config (expected format [name]=[URL])
>>>>>>> + my @enabled_files = &General::system_output('/usr/sbin/rpz-config', 'list');
>>>>>>> +
>>>>>>> + foreach my $row (@enabled_files) {
>>>>>>> + chomp($row);
>>>>>>> +
>>>>>>> + # Use regex instead of split() to skip non-matching lines
>>>>>>> + next unless($row =~ /^(\w+)=(.+)$/);
>>>>>>> + my ($name, $url) = ($1, $2);
>>>>>>> +
>>>>>>> + # Unique names are already guaranteed by rpz-config
>>>>>>> + if(&_rpz_validate_zonefile($name, $url, '', 0) == 0) {
>>>>>>> + # Populate global data hash, mark all found entries as enabled
>>>>>>> + my %entry = ('enabled' => 'on',
>>>>>>> + 'url' => $url,
>>>>>>> + 'remark' => $Lang::tr{'rpz zf imported'});
>>>>>>> +
>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>> + }
>>>>>>> + }
>>>>>>> +
>>>>>>> + # Source 2: Get additional data and disabled entries from configuration file
>>>>>>> + my %configured_files = ();
>>>>>>> + &General::readhasharray($ZONEFILES_CONF, \%configured_files);
>>>>>>> +
>>>>>>> + foreach my $row (values (%configured_files)) {
>>>>>>> + my ($name, $enabled, $url, $remark) = @$row;
>>>>>>> + $remark //= "";
>>>>>>> +
>>>>>>> + next unless($name);
>>>>>>> +
>>>>>>> + # Check whether this row belongs to an entry already imported from rpz-config
>>>>>>> + if(defined $zonefiles{$name}) {
>>>>>>> + # Existing entry, only merge additional data
>>>>>>> + $zonefiles{$name}{'remark'} = $remark;
>>>>>>> + } else {
>>>>>>> + # Skip entry if it is marked as enabled but not found by rpz-config. It was then deleted manually
>>>>>>> + if($enabled ne 'on') {
>>>>>>> + # Populate global data hash
>>>>>>> + my %entry = ('enabled' => 'off',
>>>>>>> + 'url' => $url // "",
>>>>>>> + 'remark' => $remark);
>>>>>>> +
>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>> + }
>>>>>>> + }
>>>>>>> + }
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Save internal zonefiles configuration
>>>>>>> +sub _zonefiles_save_conf {
>>>>>>> + my $index = 0;
>>>>>>> + my %export = ();
>>>>>>> +
>>>>>>> + # Loop trough all zonefiles and create "hasharray" type export
>>>>>>> + foreach my $name (keys %zonefiles) {
>>>>>>> + my @entry = ($name,
>>>>>>> + $zonefiles{$name}{'enabled'},
>>>>>>> + $zonefiles{$name}{'url'},
>>>>>>> + $zonefiles{$name}{'remark'});
>>>>>>> +
>>>>>>> + $export{$index++} = \@entry;
>>>>>>> + }
>>>>>>> +
>>>>>>> + &General::writehasharray($ZONEFILES_CONF, \%export);
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Load custom lists from rpz-config and the internal configuration
>>>>>>> +sub _customlists_load {
>>>>>>> + # Clean start
>>>>>>> + %customlists = ();
>>>>>>> +
>>>>>>> + # Load configuration file
>>>>>>> + my %lists_conf = ();
>>>>>>> + &General::readhasharray($CUSTOMLISTS_CONF, \%lists_conf);
>>>>>>> +
>>>>>>> + # Get list options, enabled by default to start import
>>>>>>> + $customlists{'allow'}{'enabled'} = $lists_conf{2}[0] // 'on';
>>>>>>> + $customlists{'block'}{'enabled'} = $lists_conf{2}[1] // 'on';
>>>>>>> +
>>>>>>> + # Import enabled list from rpz-config, otherwise retrieve stored or empty list from configuration file
>>>>>>> + if($customlists{'allow'}{'enabled'} eq 'on') {
>>>>>>> + &_customlist_import('allow', $RPZ_ALLOWLIST);
>>>>>>> + } else {
>>>>>>> + $customlists{'allow'}{'list'} = $lists_conf{0} // [];
>>>>>>> + }
>>>>>>> + if($customlists{'block'}{'enabled'} eq 'on') {
>>>>>>> + &_customlist_import('block', $RPZ_BLOCKLIST);
>>>>>>> + } else {
>>>>>>> + $customlists{'block'}{'list'} = $lists_conf{1} // [];
>>>>>>> + }
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Save internal custom lists configuration
>>>>>>> +sub _customlists_save_conf {
>>>>>>> + my %export = ();
>>>>>>> +
>>>>>>> + # Match IDs with import function
>>>>>>> + $export{0} = $customlists{'allow'}{'list'};
>>>>>>> + $export{1} = $customlists{'block'}{'list'};
>>>>>>> + $export{2} = [$customlists{'allow'}{'enabled'}, $customlists{'block'}{'enabled'}];
>>>>>>> +
>>>>>>> + &General::writehasharray($CUSTOMLISTS_CONF, \%export);
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Import a custom list from plain file, returns empty list if file is missing
>>>>>>> +sub _customlist_import {
>>>>>>> + my ($listname, $filename) = @_;
>>>>>>> + my @list = ();
>>>>>>> +
>>>>>>> + # File exists, load and check all lines
>>>>>>> + if(-f $filename) {
>>>>>>> + open(my $FH, '<', $filename) or die "Can't read $filename: $!";
>>>>>>> + while(my $line = <$FH>) {
>>>>>>> + chomp($line);
>>>>>>> + push(@list, $line);
>>>>>>> + }
>>>>>>> + close($FH);
>>>>>>> +
>>>>>>> + # Clean up imported data
>>>>>>> + &_rpz_validate_customlist(\@list, 1);
>>>>>>> + }
>>>>>>> +
>>>>>>> + $customlists{$listname}{'list'} = \@list;
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Export a custom list to plain file or clear file if list is disabled
>>>>>>> +sub _customlist_export {
>>>>>>> + my ($listname, $filename) = @_;
>>>>>>> + return unless(defined $customlists{$listname});
>>>>>>> +
>>>>>>> + # Write enabled domain list to file, otherwise save empty file
>>>>>>> + open(my $FH, '>', $filename) or die "Can't write $filename: $!";
>>>>>>> +
>>>>>>> + if($customlists{$listname}{'enabled'} eq 'on') {
>>>>>>> + foreach my $line (@{$customlists{$listname}{'list'}}) {
>>>>>>> + print $FH "$line\n";
>>>>>>> + }
>>>>>>> + } else {
>>>>>>> + print $FH "; Note: This list is currently disabled by $ENV{'SCRIPT_NAME'}\n";
>>>>>>> + }
>>>>>>> +
>>>>>>> + close($FH);
>>>>>>> +}
>>>>>>> +
>>>>>>> +
>>>>>>> +###--- Internal gui functions ---###
>>>>>>> +
>>>>>>> +# Show simple message box
>>>>>>> +sub _print_message {
>>>>>>> + my ($message, $title) = @_;
>>>>>>> + $title ||= $Lang::tr{'error messages'};
>>>>>>> +
>>>>>>> + &Header::openbox('100%', 'left', $title);
>>>>>>> + print "<span>$message</span>";
>>>>>>> + &Header::closebox();
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Show all zone files and related gui elements
>>>>>>> +sub _print_zonefiles {
>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf'});
>>>>>>> +
>>>>>>> + print <<END
>>>>>>> +<table class="tbl" width="100%">
>>>>>>> + <tr>
>>>>>>> + <th>$Lang::tr{'name'}</th>
>>>>>>> + <th>URL</th>
>>>>>>> + <th>$Lang::tr{'remark'}</th>
>>>>>>> + <th colspan="3">$Lang::tr{'action'}</th>
>>>>>>> + </tr>
>>>>>>> +END
>>>>>>> +;
>>>>>>> +
>>>>>>> + # Sort zonefiles by name and loop trough all entries
>>>>>>> + foreach my $name (sort keys %zonefiles) {
>>>>>>> +
>>>>>>> + # Toggle button label translation
>>>>>>> + my $toggle_tr = ($zonefiles{$name}{'enabled'} eq 'on') ? $Lang::tr{'click to disable'} : $Lang::tr{'click to enable'};
>>>>>>> +
>>>>>>> + print <<END
>>>>>>> + <tr>
>>>>>>> + <td>$name</td>
>>>>>>> + <td>$zonefiles{$name}{'url'}</td>
>>>>>>> + <td>$zonefiles{$name}{'remark'}</td>
>>>>>>> +
>>>>>>> + <td align="center" width="5%">
>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>> + <input type="hidden" name="ACTION" value="ZF_TOGGLE">
>>>>>>> + <input type="image" src="/images/$zonefiles{$name}{'enabled'}.gif" title="$toggle_tr" alt="$toggle_tr">
>>>>>>> + </form>
>>>>>>> + </td>
>>>>>>> + <td align="center" width="5%">
>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>> + <input type="hidden" name="ACTION" value="ZF_EDIT">
>>>>>>> + <input type="image" src="/images/edit.gif" title="$Lang::tr{'edit'}" alt="$Lang::tr{'edit'}">
>>>>>>> + </form>
>>>>>>> + </td>
>>>>>>> + <td align="center" width="5%">
>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>> + <input type="hidden" name="ACTION" value="ZF_REMOVE">
>>>>>>> + <input type="image" src="/images/delete.gif" title="$Lang::tr{'remove'}" alt="$Lang::tr{'remove'}">
>>>>>>> + </form>
>>>>>>> + </td>
>>>>>>> + </tr>
>>>>>>> +END
>>>>>>> +;
>>>>>>> + }
>>>>>>> +
>>>>>>> + # Disable reload button if not needed
>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>> +
>>>>>>> + print <<END
>>>>>>> +</table>
>>>>>>> +
>>>>>>> +<div class="right">
>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>> + <input type="hidden" name="KEY" value="">
>>>>>>> + <button type="submit" name="ACTION" value="ZF_EDIT">$Lang::tr{'add'}</button>
>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>> + </form>
>>>>>>> +</div>
>>>>>>> +END
>>>>>>> +;
>>>>>>> +
>>>>>>> + &Header::closebox();
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Show zonefiles entry editor
>>>>>>> +sub _print_zonefile_editor {
>>>>>>> +
>>>>>>> + # Key specified: Edit existing entry
>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>> + # Load data to be edited, but don't override already present values (allows user to edit after error)
>>>>>>> + $cgiparams{'ZF_NAME'} //= $action_key;
>>>>>>> + $cgiparams{'ZF_URL'} //= $zonefiles{$action_key}{'url'};
>>>>>>> + $cgiparams{'ZF_REMARK'} //= $zonefiles{$action_key}{'remark'};
>>>>>>> + }
>>>>>>> +
>>>>>>> + # Fallback to empty form
>>>>>>> + $cgiparams{'ZF_NAME'} //= "";
>>>>>>> + $cgiparams{'ZF_URL'} //= "";
>>>>>>> + $cgiparams{'ZF_REMARK'} //= "";
>>>>>>> +
>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf editor'});
>>>>>>> +
>>>>>>> + print <<END
>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>> +<input type="hidden" name="KEY" value="$action_key">
>>>>>>> +<table width="100%">
>>>>>>> + <tr>
>>>>>>> + <td width="20%">$Lang::tr{'name'}: <img src="/blob.gif" alt="*"></td>
>>>>>>> + <td><input type="text" name="ZF_NAME" value="$cgiparams{'ZF_NAME'}" size="40" maxlength="32" title="$Lang::tr{'rpz zf remark info'}" pattern="[a-zA-Z0-9_]{1,32}" required></td>
>>>>>>> + </tr>
>>>>>>> + <tr>
>>>>>>> + <td width="20%">URL: <img src="/blob.gif" alt="*"></td>
>>>>>>> + <td><input type="url" name="ZF_URL" value="$cgiparams{'ZF_URL'}" size="40" maxlength="128" required></td>
>>>>>>> + </tr>
>>>>>>> + <tr>
>>>>>>> + <td width="20%">$Lang::tr{'remark'}:</td>
>>>>>>> + <td><input type="text" name="ZF_REMARK" value="$cgiparams{'ZF_REMARK'}" size="40" maxlength="32"></td>
>>>>>>> + </tr>
>>>>>>> + <tr>
>>>>>>> + <td colspan="2"><hr></td>
>>>>>>> + </tr>
>>>>>>> + <tr>
>>>>>>> + <td width="55%"><img src="/blob.gif" alt="*"> $Lang::tr{'required field'}</td>
>>>>>>> + <td align="right"><button type="submit" name="ACTION" value="ZF_SAVE">$Lang::tr{'save'}</button></td>
>>>>>>> + </tr>
>>>>>>> +</table>
>>>>>>> +</form>
>>>>>>> +
>>>>>>> +<div class="right">
>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>> + <button type="submit" name="ACTION" value="NONE">$Lang::tr{'back'}</button>
>>>>>>> + </form>
>>>>>>> +</div>
>>>>>>> +END
>>>>>>> +;
>>>>>>> +
>>>>>>> + &Header::closebox();
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Show custom allow/block files and related gui elements
>>>>>>> +sub _print_customlists {
>>>>>>> +
>>>>>>> + # Load lists from config, unless they are currently being edited
>>>>>>> + if($action ne 'CL_SAVE') {
>>>>>>> + $cgiparams{'ALLOW_LIST'} = join("\n", @{$customlists{'allow'}{'list'}});
>>>>>>> + $cgiparams{'BLOCK_LIST'} = join("\n", @{$customlists{'block'}{'list'}});
>>>>>>> +
>>>>>>> + $cgiparams{'ALLOW_ENABLED'} = ($customlists{'allow'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>> + $cgiparams{'BLOCK_ENABLED'} = ($customlists{'block'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>> + }
>>>>>>> +
>>>>>>> + # Fallback to empty form
>>>>>>> + $cgiparams{'ALLOW_LIST'} //= "";
>>>>>>> + $cgiparams{'BLOCK_LIST'} //= "";
>>>>>>> +
>>>>>>> + # HTML checkboxes, unchecked = no or undef value in POST data
>>>>>>> + my %checked = ();
>>>>>>> + $checked{'ALLOW_ENABLED'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? " checked" : "";
>>>>>>> + $checked{'BLOCK_ENABLED'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? " checked" : "";
>>>>>>> +
>>>>>>> + # Disable reload button if not needed
>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>> +
>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz cl'});
>>>>>>> +
>>>>>>> + print <<END
>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>> +<table width="100%">
>>>>>>> + <tr>
>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl allow'}</b><br>$Lang::tr{'rpz cl allow info'}</td>
>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl block'}</b><br>$Lang::tr{'rpz cl block info'}</td>
>>>>>>> + </tr>
>>>>>>> + <tr>
>>>>>>> + <td colspan="2"><textarea name="ALLOW_LIST" class="domainlist" cols="45">
>>>>>>> +$cgiparams{'ALLOW_LIST'}</textarea></td>
>>>>>>> + <td colspan="2"><textarea name="BLOCK_LIST" class="domainlist" cols="45">
>>>>>>> +$cgiparams{'BLOCK_LIST'}</textarea></td>
>>>>>>> + </tr>
>>>>>>> + <tr>
>>>>>>> + <td><label for="allow_enabled">$Lang::tr{'rpz cl allow enable'}</label></td>
>>>>>>> + <td width="15%"><input type="checkbox" name="ALLOW_ENABLED" id="allow_enabled"$checked{'ALLOW_ENABLED'}></td>
>>>>>>> + <td><label for="block_enabled">$Lang::tr{'rpz cl block enable'}</label></td>
>>>>>>> + <td width="15%"><input type="checkbox" name="BLOCK_ENABLED" id="block_enabled"$checked{'BLOCK_ENABLED'}></td>
>>>>>>> + </tr>
>>>>>>> + <tr>
>>>>>>> + <td colspan="4"><hr></td>
>>>>>>> + </tr>
>>>>>>> + <tr>
>>>>>>> + <td align="right" colspan="4">
>>>>>>> + <button type="submit" name="ACTION" value="CL_SAVE">$Lang::tr{'save'}</button>
>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>> + </td>
>>>>>>> +</table>
>>>>>>> +</form>
>>>>>>> +END
>>>>>>> +;
>>>>>>> +
>>>>>>> + &Header::closebox();
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Output javascript and extra gui elements
>>>>>>> +sub _print_gui_extras {
>>>>>>> +
>>>>>>> + # Apply/Restart button modifier key handler
>>>>>>> + if(&_rpz_needs_reload()) {
>>>>>>> + print <<END
>>>>>>> +<script>
>>>>>>> + // Commit modifier key handler
>>>>>>> + (function(jq, document) {
>>>>>>> + var keyEventsOn = false; // Keyboard events attached
>>>>>>> + var keyModify = false; // Modifier key pressed
>>>>>>> + var mouseHover = false; // Mouse over commit button
>>>>>>> + var btnModified = false; // Button modified to "Restart"
>>>>>>> +
>>>>>>> + // Document-level key events, enable only while cursor is over button
>>>>>>> + function attachKeyEvents() {
>>>>>>> + if(keyEventsOn) {
>>>>>>> + return;
>>>>>>> + }
>>>>>>> + keyEventsOn = true;
>>>>>>> +
>>>>>>> + jq(document).on("keydown.rpz", function(event) {
>>>>>>> + if((!keyModify) && event.shiftKey) {
>>>>>>> + keyModify = true;
>>>>>>> + handleModify();
>>>>>>> + }
>>>>>>> + });
>>>>>>> + jq(document).on("keyup.rpz", function(event) {
>>>>>>> + if(keyModify && (!event.shiftKey)) {
>>>>>>> + keyModify = false;
>>>>>>> + handleModify();
>>>>>>> + }
>>>>>>> + });
>>>>>>> + }
>>>>>>> + function removeKeyEvents() {
>>>>>>> + keyModify = false;
>>>>>>> + if(keyEventsOn) {
>>>>>>> + jq(document).off("keydown.rpz keyup.rpz");
>>>>>>> + keyEventsOn = false;
>>>>>>> + }
>>>>>>> + }
>>>>>>> +
>>>>>>> + // Attach mouse hover events to commit buttons
>>>>>>> + function attachMouseEvents() {
>>>>>>> + jq("button.commit").on("mouseenter", function(event) {
>>>>>>> + if(!mouseHover) {
>>>>>>> + mouseHover = true;
>>>>>>> + attachKeyEvents();
>>>>>>> + // Handle already pressed key
>>>>>>> + keyModify = !!(event.shiftKey);
>>>>>>> + handleModify();
>>>>>>> + }
>>>>>>> + });
>>>>>>> +
>>>>>>> + // Cursor moved away: Disable key listener to minimize events
>>>>>>> + jq("button.commit").on("mouseleave", function() {
>>>>>>> + if(mouseHover) {
>>>>>>> + mouseHover = false;
>>>>>>> + removeKeyEvents();
>>>>>>> + handleModify();
>>>>>>> + }
>>>>>>> + });
>>>>>>> + }
>>>>>>> +
>>>>>>> + // Modify commit button
>>>>>>> + function handleModify() {
>>>>>>> + let modify = mouseHover && keyModify;
>>>>>>> + if(btnModified != modify) {
>>>>>>> + if(modify) {
>>>>>>> + jq("button.commit").text("$Lang::tr{'restart'}").val("UNB_RESTART");
>>>>>>> + } else {
>>>>>>> + jq("button.commit").text("$Lang::tr{'rpz apply'}").val("RPZ_RELOAD");
>>>>>>> + }
>>>>>>> + btnModified = modify;
>>>>>>> + }
>>>>>>> + }
>>>>>>> +
>>>>>>> + // jQuery DOM ready
>>>>>>> + jq(function() {
>>>>>>> + attachMouseEvents();
>>>>>>> + });
>>>>>>> + })(jQuery, document);
>>>>>>> +</script>
>>>>>>> +END
>>>>>>> +;
>>>>>>> + } # End of modifier key handler
>>>>>>> +
>>>>>>> +}
>>>>>>> +
>>>>>>> +
>>>>>>> +###--- Internal action processing functions ---###
>>>>>>> +
>>>>>>> +# Toggle zonefile on/off
>>>>>>> +sub _action_zf_toggle {
>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>> +
>>>>>>> + my $result = 0;
>>>>>>> + my $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>> +
>>>>>>> + # Perform toggle action
>>>>>>> + if($enabled eq 'on') {
>>>>>>> + $enabled = 'off';
>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>> + } else {
>>>>>>> + $enabled = 'on';
>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $action_key, $zonefiles{$action_key}{'url'}, '--no-reload');
>>>>>>> + }
>>>>>>> +
>>>>>>> + # Check for errors, request service reload on success
>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>> +
>>>>>>> + # Save changes
>>>>>>> + $zonefiles{$action_key}{'enabled'} = $enabled;
>>>>>>> + &_zonefiles_save_conf();
>>>>>>> +
>>>>>>> + return 1;
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Remove zonefile
>>>>>>> +sub _action_zf_remove {
>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>> +
>>>>>>> + # Remove from rpz-config if currently active
>>>>>>> + if($zonefiles{$action_key}{'enabled'} eq 'on') {
>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>> +
>>>>>>> + # Check for errors, request service reload on success
>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>> + }
>>>>>>> +
>>>>>>> + # Remove from data hash and save changes
>>>>>>> + delete $zonefiles{$action_key};
>>>>>>> + &_zonefiles_save_conf();
>>>>>>> +
>>>>>>> + # Clear action_key, as the entry is now removed entirely
>>>>>>> + $action_key = "";
>>>>>>> +
>>>>>>> + return 1;
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Create or update zonefile entry
>>>>>>> +# Returns undef if gui needs to stay in editor mode
>>>>>>> +sub _action_zf_save {
>>>>>>> + my $result = 0;
>>>>>>> +
>>>>>>> + my $name = $cgiparams{'ZF_NAME'} // "";
>>>>>>> + my $url = $cgiparams{'ZF_URL'} // "";
>>>>>>> + my $remark = $cgiparams{'ZF_REMARK'} // "";
>>>>>>> + my $enabled = 'on'; # Enable new entries by default
>>>>>>> +
>>>>>>> + # Note on variables:
>>>>>>> + # name = unique key, will be used to address the entry
>>>>>>> + # action_key = name of the entry being edited, empty for new entry
>>>>>>> +
>>>>>>> + # Only check for unique name if it changed
>>>>>>> + # (this also checks new entries because the action_key is empty in this case)
>>>>>>> + $result = &_rpz_validate_zonefile($name, $url, $remark, (lc($name) ne lc($action_key)));
>>>>>>> + return unless &_rpz_check_result($result, 0);
>>>>>>> +
>>>>>>> + # Edit existing entry: Determine what was changed
>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>> + # Name und URL remain unchanged, only save remark and finish
>>>>>>> + if(($name eq $action_key) && ($url eq $zonefiles{$action_key}{'url'})) {
>>>>>>> + $zonefiles{$action_key}{'remark'} = $remark;
>>>>>>> + &_zonefiles_save_conf();
>>>>>>> +
>>>>>>> + return 1;
>>>>>>> + }
>>>>>>> +
>>>>>>> + # Entry was changed and needs to be recreated, preserve status
>>>>>>> + $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>> +
>>>>>>> + # Remove from rpz-config
>>>>>>> + return unless &_action_zf_remove();
>>>>>>> + }
>>>>>>> +
>>>>>>> + # Add new entry to rpz-config
>>>>>>> + if($enabled eq 'on') {
>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $name, $url, '--no-reload');
>>>>>>> +
>>>>>>> + # Check for errors, request service reload on success
>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>> + }
>>>>>>> +
>>>>>>> + # Add to global data hash and save changes
>>>>>>> + my %entry = ('enabled' => $enabled,
>>>>>>> + 'url' => $url,
>>>>>>> + 'remark' => $remark);
>>>>>>> +
>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>> + &_zonefiles_save_conf();
>>>>>>> +
>>>>>>> + return 1;
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Save custom lists
>>>>>>> +sub _action_cl_save {
>>>>>>> + return unless((defined $cgiparams{'ALLOW_LIST'}) && (defined $cgiparams{'BLOCK_LIST'}));
>>>>>>> +
>>>>>>> + my $result = 0;
>>>>>>> +
>>>>>>> + my @allowlist = split(/\R/, $cgiparams{'ALLOW_LIST'});
>>>>>>> + my @blocklist = split(/\R/, $cgiparams{'BLOCK_LIST'});
>>>>>>> +
>>>>>>> + # Validate lists
>>>>>>> + $result = &_rpz_validate_customlist(\@allowlist);
>>>>>>> + if($result != 0) {
>>>>>>> + $errormessage = &_rpz_error_tr(202, $result);
>>>>>>> + return;
>>>>>>> + }
>>>>>>> + $result = &_rpz_validate_customlist(\@blocklist);
>>>>>>> + if($result != 0) {
>>>>>>> + $errormessage = &_rpz_error_tr(203, $result);
>>>>>>> + return;
>>>>>>> + }
>>>>>>> +
>>>>>>> + # Add to global data hash and save changes
>>>>>>> + $customlists{'allow'}{'list'} = \@allowlist;
>>>>>>> + $customlists{'block'}{'list'} = \@blocklist;
>>>>>>> + $customlists{'allow'}{'enabled'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? 'on' : 'off';
>>>>>>> + $customlists{'block'}{'enabled'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? 'on' : 'off';
>>>>>>> +
>>>>>>> + &_customlists_save_conf();
>>>>>>> + &_customlist_export('allow', $RPZ_ALLOWLIST);
>>>>>>> + &_customlist_export('block', $RPZ_BLOCKLIST);
>>>>>>> +
>>>>>>> + # Make new lists, request service reload on success
>>>>>>> + $result = &General::system('/usr/sbin/rpz-make', 'allowblock', '--no-reload');
>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>> +
>>>>>>> + return 1;
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Trigger rpz-config reload
>>>>>>> +sub _action_rpz_reload {
>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>> +
>>>>>>> + # Immediately clear flag to prevent multiple reloads
>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>> + }
>>>>>>> +
>>>>>>> + # Perform reload, recreate reload flag on error to enable retry
>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'reload');
>>>>>>> + if(not &_rpz_check_result($result, 0)) {
>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>> + return;
>>>>>>> + }
>>>>>>> +
>>>>>>> + return 1;
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Trigger unbound restart
>>>>>>> +sub _action_unb_restart {
>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>> +
>>>>>>> + # Immediately clear flag to prevent multiple restarts
>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>> + }
>>>>>>> +
>>>>>>> + # Perform restart, unboundctrl always exits zero
>>>>>>> + &General::system('/usr/local/bin/unboundctrl', 'restart');
>>>>>>> +
>>>>>>> + return 1;
>>>>>>> +}
>>>>>>> +
>>>>>>> +
>>>>>>> +###--- Internal rpz-config functions ---###
>>>>>>> +
>>>>>>> +# Translate rpz-config exitcodes and messages
>>>>>>> +# 100-199: rpz-config, 200-299: webgui
>>>>>>> +sub _rpz_error_tr {
>>>>>>> + my ($error, $append) = @_;
>>>>>>> + $append //= '';
>>>>>>> +
>>>>>>> + # Translate numeric exit codes
>>>>>>> + if(looks_like_number($error)) {
>>>>>>> + if(defined $Lang::tr{"rpz exitcode $error"}) {
>>>>>>> + $error = $Lang::tr{"rpz exitcode $error"};
>>>>>>> + }
>>>>>>> + }
>>>>>>> +
>>>>>>> + return "RPZ $Lang::tr{'error'}: $error" . &Header::escape($append);
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Check result of rpz-config system call, request reload on success
>>>>>>> +sub _rpz_check_result {
>>>>>>> + my ($result, $request_reload) = @_;
>>>>>>> + $request_reload //= 0;
>>>>>>> +
>>>>>>> + # exitcode 0 = success
>>>>>>> + if($result != 0) {
>>>>>>> + $errormessage = &_rpz_error_tr($result);
>>>>>>> + return;
>>>>>>> + }
>>>>>>> +
>>>>>>> + # Set reload flag
>>>>>>> + if($request_reload) {
>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>> + }
>>>>>>> +
>>>>>>> + return 1;
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Test whether reload flag is set
>>>>>>> +sub _rpz_needs_reload {
>>>>>>> + return (-f $RPZ_RELOAD_FLAG);
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Validate a zonefile entry, returns rpz-config exitcode on failure. Use _rpz_check_result to verify.
>>>>>>> +# unique = check for unique name
>>>>>>> +sub _rpz_validate_zonefile {
>>>>>>> + my ($name, $url, $remark, $unique) = @_;
>>>>>>> + $unique //= 1;
>>>>>>> +
>>>>>>> + unless($name =~ /^[a-zA-Z0-9_]{1,32}$/) {
>>>>>>> + return 101;
>>>>>>> + }
>>>>>>> + unless($url =~ /^[\w+\.:;\/\\&@#%?=\-~|!]{1,128}$/) {
>>>>>>> + return 105;
>>>>>>> + }
>>>>>>> + unless($remark =~ /^[\w \-()\.:;*\/\\?!&=]{0,32}$/) {
>>>>>>> + return 201;
>>>>>>> + }
>>>>>>> +
>>>>>>> + # Check against already existing names
>>>>>>> + if($unique) {
>>>>>>> + foreach my $existing (keys %zonefiles) {
>>>>>>> + if(lc($name) eq lc($existing)) {
>>>>>>> + return 104;
>>>>>>> + }
>>>>>>> + }
>>>>>>> + }
>>>>>>> +
>>>>>>> + return 0;
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Validate a custom list, returns number of rejected line on failure. Check for non-zero results.
>>>>>>> +# listref = array reference, cleanup = remove invalid entries instead of returning an error
>>>>>>> +sub _rpz_validate_customlist {
>>>>>>> + my ($listref, $cleanup) = @_;
>>>>>>> + $cleanup //= 0;
>>>>>>> +
>>>>>>> + foreach my $index (reverse 0..$#{$listref}) {
>>>>>>> + my $row = @$listref[$index];
>>>>>>> + next unless($row); # Skip/allow empty lines
>>>>>>> +
>>>>>>> + # Reject/remove everything besides wildcard domains and remarks
>>>>>>> + if((not &General::validwildcarddomainname($row)) && (not $row =~ /^;[\w \-()\.:;*\/\\?!&=]*$/)) {
>>>>>>> + unless($cleanup) {
>>>>>>> + # +1 for user friendly line number and to ensure non-zero exitcode
>>>>>>> + return $index + 1;
>>>>>>> + }
>>>>>>> +
>>>>>>> + # Remove current row
>>>>>>> + splice(@$listref, $index, 1);
>>>>>>> + }
>>>>>>> + }
>>>>>>> +
>>>>>>> + return 0;
>>>>>>> +}
>>>>>>> +
>>>>>>> +
>>>>>>> +###--- Internal misc functions ---###
>>>>>>> +
>>>>>>> +# Send HTTP 303 redirect headers for post/request/get pattern
>>>>>>> +# (Must be sent before calling &Header::showhttpheaders())
>>>>>>> +sub _http_prg_redirect {
>>>>>>> + my $location = "https://$ENV{'SERVER_NAME'}:$ENV{'SERVER_PORT'}$ENV{'SCRIPT_NAME'}";
>>>>>>> + print "Status: 303 See Other\n";
>>>>>>> + print "Location: $location\n";
>>>>>>> +}
>>>>>>> diff --git a/lfs/rpz b/lfs/rpz
>>>>>>> new file mode 100644
>>>>>>> index 000000000..7ddbc38e5
>>>>>>> --- /dev/null
>>>>>>> +++ b/lfs/rpz
>>>>>>> @@ -0,0 +1,96 @@
>>>>>>> +###############################################################################
>>>>>>> +# #
>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>>>> +# #
>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>> +# #
>>>>>>> +###############################################################################
>>>>>>> +
>>>>>>> +###############################################################################
>>>>>>> +# Definitions
>>>>>>> +###############################################################################
>>>>>>> +
>>>>>>> +include Config
>>>>>>> +
>>>>>>> +SUMMARY = response policy zone - RPZ reputation system for unbound DNS
>>>>>>> +
>>>>>>> +VER = 1.0.0
>>>>>>> +
>>>>>>> +THISAPP = rpz-$(VER)
>>>>>>> +DIR_APP = $(DIR_SRC)/$(THISAPP)
>>>>>>> +TARGET = $(DIR_INFO)/$(THISAPP)
>>>>>>> +
>>>>>>> +PROG = rpz
>>>>>>> +PAK_VER = 18
>>>>>>> +
>>>>>>> +DEPS =
>>>>>>> +
>>>>>>> +SERVICES =
>>>>>>> +
>>>>>>> +###############################################################################
>>>>>>> +# Top-level Rules
>>>>>>> +###############################################################################
>>>>>>> +
>>>>>>> +install : $(TARGET)
>>>>>>> +
>>>>>>> +check :
>>>>>>> +
>>>>>>> +download :
>>>>>>> +
>>>>>>> +b2 :
>>>>>>> +
>>>>>>> +dist:
>>>>>>> + @$(PAK)
>>>>>>> +
>>>>>>> +###############################################################################
>>>>>>> +# Installation Details
>>>>>>> +###############################################################################
>>>>>>> +
>>>>>>> +$(TARGET) :
>>>>>>> + @$(PREBUILD)
>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>> +
>>>>>>> + # RPZ scripts
>>>>>>> + install --verbose --mode=755 \
>>>>>>> + $(DIR_CONF)/rpz/{rpz-config,rpz-metrics,rpz-sleep,rpz-make,rpz-functions} \
>>>>>>> + --target-directory=/usr/sbin
>>>>>>> +
>>>>>>> + # RPZ config files
>>>>>>> + mkdir -pv /etc/unbound/local.d
>>>>>>> + install --verbose --mode=644 --owner=nobody --group=nobody \
>>>>>>> + $(DIR_CONF)/rpz/00-rpz.conf \
>>>>>>> + --target-directory=/etc/unbound/local.d
>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/local.d
>>>>>>> +
>>>>>>> + # RPZ custom list files for allow and block
>>>>>>> + mkdir -pv /var/ipfire/dns/rpz
>>>>>>> + touch /var/ipfire/dns/rpz/{allowlist,blocklist}
>>>>>>> + chown --verbose --recursive nobody:nobody /var/ipfire/dns/rpz
>>>>>>> +
>>>>>>> + # RPZ zone files
>>>>>>> + # create empty RPZ config file to avoid a unbound config error
>>>>>>> + mkdir -pv /etc/unbound/zonefiles
>>>>>>> + touch /etc/unbound/zonefiles/allow.rpz
>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/zonefiles
>>>>>>> +
>>>>>>> + # Install addon-specific language-files
>>>>>>> + install --verbose --mode=004 $(DIR_CONF)/rpz/rpz.*.pl \
>>>>>>> + --target-directory=/var/ipfire/addon-lang
>>>>>>> +
>>>>>>> + # Install backup definition
>>>>>>> + cp -vf $(DIR_CONF)/backup/includes/rpz /var/ipfire/backup/addons/includes/rpz
>>>>>>> +
>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>> + @$(POSTBUILD)
>>>>>>> diff --git a/make.sh b/make.sh
>>>>>>> index 827ea9e77..a77535b13 100755
>>>>>>> --- a/make.sh
>>>>>>> +++ b/make.sh
>>>>>>> @@ -390,7 +390,7 @@ prepareenv() {
>>>>>>> if [ "${free_space}" -lt "${required_space}" ]; then
>>>>>>> # Add any consumed space
>>>>>>> while read -r consumed_space path; do
>>>>>>> - (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>> + (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>> done <<< "$(du --summarize --bytes "${BUILD_DIR}" "${IMAGES_DIR}" "${LOG_DIR}" 2>/dev/null)"
>>>>>>> fi
>>>>>>>
>>>>>>> @@ -2087,6 +2087,7 @@ build_system() {
>>>>>>> lfsmake2 btrfs-progs
>>>>>>> lfsmake2 inotify-tools
>>>>>>> lfsmake2 grub-btrfs
>>>>>>> + lfsmake2 rpz
>>>>>>>
>>>>>>> lfsmake2 linux
>>>>>>> lfsmake2 rtl8812au
>>>>>>> diff --git a/src/paks/rpz/install.sh b/src/paks/rpz/install.sh
>>>>>>> new file mode 100644
>>>>>>> index 000000000..ef99bf742
>>>>>>> --- /dev/null
>>>>>>> +++ b/src/paks/rpz/install.sh
>>>>>>> @@ -0,0 +1,36 @@
>>>>>>> +#!/bin/bash
>>>>>>> +###############################################################################
>>>>>>> +# #
>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>>>> +# #
>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>> +# #
>>>>>>> +###############################################################################
>>>>>>> +#
>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>> +extract_files
>>>>>>> +restore_backup ${NAME}
>>>>>>> +
>>>>>>> +# fix user created files
>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>> + /etc/unbound/zonefiles \
>>>>>>> + /etc/unbound/local.d
>>>>>>> +
>>>>>>> +# Update Language cache
>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>> +
>>>>>>> +# restart unbound to load config file
>>>>>>> +/etc/init.d/unbound restart
>>>>>>> diff --git a/src/paks/rpz/uninstall.sh b/src/paks/rpz/uninstall.sh
>>>>>>> new file mode 100644
>>>>>>> index 000000000..e11427df3
>>>>>>> --- /dev/null
>>>>>>> +++ b/src/paks/rpz/uninstall.sh
>>>>>>> @@ -0,0 +1,38 @@
>>>>>>> +#!/bin/bash
>>>>>>> +###############################################################################
>>>>>>> +# #
>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>>>> +# #
>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>> +# #
>>>>>>> +###############################################################################
>>>>>>> +#
>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>> +
>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>> +/etc/init.d/unbound stop
>>>>>>> +
>>>>>>> +make_backup ${NAME}
>>>>>>> +remove_files
>>>>>>> +
>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>> +# "[1723428668] unbound-control[17117:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>> +
>>>>>>> +# Update Language cache
>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>> +
>>>>>>> +# start unbound to load unbound config file
>>>>>>> +/etc/init.d/unbound start
>>>>>>> diff --git a/src/paks/rpz/update.sh b/src/paks/rpz/update.sh
>>>>>>> new file mode 100644
>>>>>>> index 000000000..9bc340bc6
>>>>>>> --- /dev/null
>>>>>>> +++ b/src/paks/rpz/update.sh
>>>>>>> @@ -0,0 +1,52 @@
>>>>>>> +#!/bin/bash
>>>>>>> +###############################################################################
>>>>>>> +# #
>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>>>> +# #
>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>> +# #
>>>>>>> +###############################################################################
>>>>>>> +#
>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>> +
>>>>>>> +# from update.sh
>>>>>>> +extract_backup_includes
>>>>>>> +
>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>> +/etc/init.d/unbound stop
>>>>>>> +
>>>>>>> +# from uninstall.sh
>>>>>>> +make_backup ${NAME}
>>>>>>> +remove_files
>>>>>>> +
>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>> +# "unbound-control[nn:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>> +
>>>>>>> +# from install.sh
>>>>>>> +extract_files
>>>>>>> +restore_backup ${NAME}
>>>>>>> +
>>>>>>> +# fix user created files
>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>> + /etc/unbound/zonefiles \
>>>>>>> + /etc/unbound/local.d
>>>>>>> +
>>>>>>> +# Update Language cache
>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>> +
>>>>>>> +# restart unbound to load config files
>>>>>>> +/etc/init.d/unbound start
>>>>>>> --
>>>>>>> 2.39.5
>>>>>>>
>>>>>>
>>>>>
>>>>> Jon
>>>>>
>>>>>
>>>>> --
>>>>> Jon Murphy
>>>>> jon.murphy(a)ipfire.org
>>>
>>>
>>> Jon
>>>
>>>
>>> --
>>> Jon Murphy
>>> jon.murphy(a)ipfire.org
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
2025-02-14 13:52 ` Michael Tremer
@ 2025-02-14 14:16 ` Bernhard Bitsch
0 siblings, 0 replies; 30+ messages in thread
From: Bernhard Bitsch @ 2025-02-14 14:16 UTC (permalink / raw)
To: development
[-- Attachment #1: Type: text/plain, Size: 133440 bytes --]
Hello Michael,
I use the hagezi lists (
https://github.com/hagezi/dns-blocklists?tab=readme-ov-file#zap-dns-blocklists---for-a-better-internet
) which allow more selections.
Regards,
Bernhard
Am 14.02.2025 um 14:52 schrieb Michael Tremer:
> Hello Bernhard,
>
>> On 14 Feb 2025, at 12:58, Bernhard Bitsch <bbitsch(a)ipfire.org> wrote:
>>
>> Hello Michael,
>>
>> thanks for your clear words. But nevertheless let be comment at some topics.
>>
>> Am 14.02.2025 um 13:07 schrieb Michael Tremer:
>>> Hello Jon,
>>> It very much depends on the kind of contribution. A one-line patch obviously has fewer strings attached to it than a larger patch set like this.
>>> However, we have outlined the process in the wiki already, starting from here:
>>> https://www.ipfire.org/docs/devel/submit-patches
>>> This contains some useful pointers about the how (how do I actually make my changes happen, how do I build IPFire, etc), and at the bottom it contains a lot of information about the format the changes should be submitted; split into smaller chunks that are ideally as independent from each other so that they can be individually reviewed and merged. Usually a development process takes long time and we have already shipped parts of code that we will need for certain features that are not ready yet. This is a good practice to let code mature, especially when it is touching rather critical bits like the firewall and networking stacks.
>>> There are also some guidelines on how to write a good commit message and how to use Git tags:
>>> https://www.ipfire.org/docs/devel/git/commit-messages
>>> https://www.ipfire.org/docs/devel/git/tags
>>> Then there is something about how to get in touch with the right person and legal stuff:
>>> https://www.ipfire.org/docs/devel/contact
>>> https://www.ipfire.org/docs/legal/ipca
>>
>> IMO, this facts are known by Jon. He just did a big part besides this general process. Including publishing his efforts to the community for review and test.
>>
>>> Finally, we have a bit of an underused roadmap section on the wiki. It would be nice if we could use that a little bit more because then it would be easier for everyone to keep track of progress on certain features; people could see what is being worked on and see if they can help development and testing and so on:
>>> https://www.ipfire.org/docs/roadmap
>>> There is a template on how to create new pages:
>>> https://www.ipfire.org/docs/roadmap/template
>>> And this is a good example of what this could look like:
>>> https://www.ipfire.org/docs/roadmap/openvpn-26
>>> All of these steps are coming *after* there has been some initial discussion about what actually has a chance to become part of the distribution. For that, we do not have any specific guidelines because it is not very trivial to write these things. There are just too many possibilities. In the past, there has also been very little need for this, but that does not mean that there have not been problems before.
>>> The reason why I am raising the bar this high here is simply that we have made mistakes in the past that we don’t want to repeat. We have learned a couple of lessons in a not very pleasant way and I under no circumstances would want to do this again. The objective is that we want to provide an excellent distribution. Although IPFire of course has its shortcomings here and there, it is a very stable distribution and we have a very good track record that I want to keep. This is what our users deserve.
>>> In the past, people have “dropped” their patches on this list (or sometimes elsewhere) and we were left with dealing with the entire integration only to find out later what problems there were hidden in the code. The original author(s) had no interest in fixing any of that because it worked just fine for them, and so why spend any time on the problems of somebody else? Usually I am the fallback for this and I simply don’t want to be that. I have lots of my own projects inside IPFire that are moving at snail speed because fixing existing code usually takes priority over writing new code.
>>> Therefore we need a commitment to sort out these problems in the first place. It has to be proven that people actually *care* about the patches that they post here. I am not sure this needs writing down as this should be the same policy with almost any open source project. If you contribute a line, there is probably less maintenance required in the future, but if you contribute a large code base, then you will need to look after it for the foreseeable future. It is your feature and not mine after all.
>>
>> Being one of the first testers and revisors, I commit me to support the development. Point.
>>
>>> Then, what actually has a chance to make it into the distribution? Probably not a lot. IPFire has a very clear use case. There will not be any space for a desktop environment and running Chrome on it, we also don’t it to make coffee and cook me a dinner. We would currently only accept things that were actually maintainable by the current team in case a contributor moves on (see above), because we simply only have so much man power. We already have a large zoo of features that are very abandoned and we are potentially looking at getting rid of more things simply because we cannot support them properly. Time just doesn’t permit. Adding something large is therefore very difficult at the moment.
>>> I understand that in this specific case you have been trying to not involve the development team and I understand your motivation. But you cannot forget about how much time and effort a review process can take. Therefore we want to plan things well; we want to even split it; and we want to have a conversation in advance so that the roadmap is clear and the actual code review ideally only becomes a formality.
>>> All of this above has been for a general case. Please read through this and feel free to ask any questions if something isn’t clear.
>>> To move forward with this feature, we should start by planning a roadmap. We need to discuss what this project should cover and what it should not cover. I believe we don’t need to talk much about implementation details because you have figured out a lot of them; we need to find what feature we want to provide to our users. Are you up for that?
>>
>> As a first input to the 'official' discussion, some thoughts.
>> - Usage of encrypted data traffic is increasing. Especially in communications not aware to the user. These unwanted traffic is more and more difficult to block with IP orientated tools. Even the proxy based solution do not really function efficient.
>> - To circumvent these problems a widely used approach is DNS filtering. This comes in two flavours: 1. online providers, 2. local filtering DNS servers like PiHole.
>> - Solution 1 isn't wantable. Many providers store the access data, outside the local network.
>> - Solution 2 installs another DNS server inside the local networks. It isn't trivial to ensure that all DNS traffic is handled by this server.
>> - Therefore it is desirable that this filtering is done in the only DNS server in a IPFire governed network, unbound on the IPFire system. The RPZ functionality allows exactly this.
>> - There are, IMO, good filter lists outside. The updating of these lists is part of the RPZ module. There is some effort left to investigate these and to develop recommendations for a good mix of appliance, we do this for the external DNS servers already.
>
> You are thinking too much about the *how* here, but we should think of the *what* first. What features do we want to offer and what do we not want to offer.
>
> Regarding the lists: I am running a test with one client which has about 2000 users on their network on a daily basis. There have been *huge* amounts of false positives from the OISD list and it has been occasionally painful to whitelist those. The quality of this feature stands and falls with the quality of the lists that we can feed into this.
>
> I have also raised before that those lists collect random stuff together and in the end you only have one list that you can either take or leave. Coming from URL filter, we have some categorisation there like “adult”, “gambling”, “social networks”, and so on which we don’t have here. Would that not be nice to have, too?
>
>> Regards,
>> Bernhard
>>
>>> Best,
>>> -Michael
>>>> On 13 Feb 2025, at 21:34, jon <jon.murphy(a)ipfire.org> wrote:
>>>>
>>>> Michael,
>>>>
>>>> I’ve read through your comments a few times and I ended up with many more questions.
>>>>
>>>>
>>>>> What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>>
>>>>
>>>> To me the efforts to get new code accepted seem to have changed and it seemed easier in the past. In the past I made the Core Team aware via the Dev Mailing List and wrote a simple two or three paragraphs of "What is it? / What is the value? / Here is the code"
>>>>
>>>>
>>>> So in an effort to move forward: How exactly is something presented to the Core Team?
>>>>
>>>> Is there an example of a recent effort that was presented that I can see as a sample? (This type of info can also be added to the Wiki)
>>>>
>>>> I understand you want it this way, but I don’t know what exactly is needed. Please be specific.
>>>>
>>>>
>>>> Jon
>>>>
>>>> PS - I am not ignoring your other comments, I am just trying to move forward and keep things simple.
>>>>
>>>>
>>>>
>>>>> On Feb 8, 2025, at 1:27 PM, Michael Tremer <michael.tremer(a)ipfire.org> wrote:
>>>>>
>>>>> Hello Jon,
>>>>>
>>>>> Thanks for your reply. And good that you are copying everyone into this conversation.
>>>>>
>>>>>> On 8 Feb 2025, at 18:41, jon <jon.murphy(a)ipfire.org> wrote:
>>>>>>
>>>>>> Michael,
>>>>>>
>>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort
>>>>>>
>>>>>> Yes, this has been a separate effort (a very public separate effort). Yes, as you pointed this out early on with the "proof-of-concept" and then my request for people to help test RPZ. Nothing was hidden.
>>>>>>
>>>>>> This was done because you (and maybe others) did not have the time and I wanted to help and because I needed assistance with RPZ. I tried my best to do this without bothering you.
>>>>>
>>>>> I don’t that it is accurate that nobody wanted to help on this. The list was always open - although not every email has been replied to swiftly it is also your responsibility to raise a question again if it was missed. People here have open ears.
>>>>>
>>>>> It was also stated on this very list on in our documentation that working on something without involving the core team is a risky undertaking. Of course IPFire is free software and so everyone is free to fork if they wish to do so.
>>>>>
>>>>>>> and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls.
>>>>>>
>>>>>> You were aware many steps along the way. See your email on July 28, 2024, August 15, 2024, September 30, 2024, December 23, 2024, and January 16. My attempts to get the team involved were met with "things are busy" and sometimes silence. (Yes, I get it, people are busy.)
>>>>>>
>>>>>> You and Adolf, Leo, Erik and Bernhard have been aware since the beginning. You mention you were aware of the "proof-of-concept". If you include those beginning posts, since Sep 2023.
>>>>>
>>>>> Yes, I am aware of a proof-of-concept that I have been running myself for a long time. I am also aware of the efforts that you have been taking.
>>>>>
>>>>> Yet I don’t think there has ever been any joint effort, or am I seeing that wrong?
>>>>>
>>>>>>> This has not been discussed . . . on our calls.
>>>>>>
>>>>>> On the July 28th you stated:
>>>>>> "We have talked about RPZ many times on the monthly call since the URL filter feature is falling more and more out of fashion. I think there is also many posts about this on the forum."
>>>>>>
>>>>>> Please don’t insult me again by stating "you know what I mean".
>>>>>>
>>>>>> And it has been discussed but not documented in the Monthly Meeting notes.
>>>>>
>>>>> I am not at all insulting you. I don’t want to take this down to a personal level at all. This is a public mailing list and people who read this don’t need to listen to an argument we are having. They are here for the tech inside IPFire.
>>>>>
>>>>> When I wrote that it has not been discussed that does not mean that we have not been touching on the topic. We have been talking about lots of things on the calls, the weather, politics, how our pets are. None of that makes it to the logs. What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>>>
>>>>>>> Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation.
>>>>>>
>>>>>> Regular conversation on the Dev Mailing list is many times met with silence. I get it, people are busy.
>>>>>>
>>>>>> And regular two-way conversation doesn’t happen on the list. At least not with me. I’d be happy to point out the posts that were met with silence.
>>>>>> Again, I get it, people are busy.
>>>>>
>>>>> And you think my emails are not being met with silence? This has nothing to do with this specific topic. This has something to do with how occupied people are and how engaged they are on certain topics. Not everyone is involved in all the things and simply will ignore emails simply based on their subject line.
>>>>>
>>>>>> But the "dip here to the list" were my attempts to get a conversation started. As I said, many time met with silence.
>>>>>>
>>>>>> The only place I was not met with silence was on the Community. You have a great group of people in the Community. It is a shame you don’t want to have others help. It would reduce your workload.
>>>>>
>>>>> You should stop making statements that are not true. Who doesn’t want anyone to help?
>>>>>
>>>>> Not having this conversation on a Saturday evening would reduce my workload. At least it would free up time for something else. Helping with the things that are already on the go would reduce the workload of the entire team. Starting one thing at a time and finishing it is a lot better to manage than starting a hundred things and not even finish one. I can tell you that I already have a hundred things on the go.
>>>>>
>>>>>>> Therefore, what am I supposed to do with this email?
>>>>>>
>>>>>> To me it is beyond obvious…
>>>>>>
>>>>>> If it isn’t what you want, then guide me with how to do this the correct way. And be specific. I am trying to help. I am trying to make things better. I am trying to do things the right way.
>>>>>
>>>>> To me it isn’t. This is yet another project that has been dumped to the list like so many before and later on everyone has left to have the team deal with the rest.
>>>>>
>>>>> It is a huge patch set. You explained what the vision is, but that is about it. There is no chance this will continue if this disagreement isn’t solved first. I didn’t even look at the code.
>>>>>
>>>>>>> I don’t want to merge code that I don’t agree with.
>>>>>>
>>>>>> I asked multiple times if you "agreed with the concept" and again, met with silence. Yes I get it, people are busy.
>>>>>
>>>>> Having support for RPZ? Yes, it was definitely on the roadmap. That I agree with.
>>>>>
>>>>>>> So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>>
>>>>>> You mentioned this a in the past, but for some reason you do not disclose what I dismissed. Why do you continue to make this harder, wouldn’t it not be easier to tell me what I have dismissed?
>>>>>>
>>>>>> I have sent multiple emails trying to answer your concerns and comments. On July 28, Aug 14, Aug 22, Aug 23, Sep 30, etc.
>>>>>>
>>>>>> I’ve gone through all of the questions you asked and I cannot find a "dismissed" item.
>>>>>
>>>>> Maybe I need to be *more clear*. I feel humoured by this.
>>>>>
>>>>> It is late on a Saturday and I want my dinner soon, but certainly I have stated that this should never be an add-on considering it is supposed to replace URL Filter. We should never allow people to add their own sources. I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>>>>
>>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>>
>>>>>> The maintainers of Unbound and/or RPZ?
>>>>>>
>>>>>> The maintainers of Hagezi list, the threatfox list, the urlhaus list, etc.?
>>>>>>
>>>>>> What else? The maintainers or the RPZ scripts? That is me. Let’s talk!
>>>>>
>>>>> You. I don’t care much about the providers of the lists.
>>>>>
>>>>>> See, this is where it gets confusing. There are hundreds of open source packages as part of IPFire. Pick the last five years of items added to the IPFire build. You're telling me you have "constructive conversation with the maintainers" of all of the added packages?
>>>>>
>>>>> They publish their software and they don’t care whether I am pulling it or not. They publish it with the commitment to maintain it - sometimes for better and sometimes for worse.
>>>>>
>>>>> You care about me pulling your code and I don’t know whether you would commit to maintain this.
>>>>>
>>>>> These two are very different cases.
>>>>>
>>>>>> Pick the IP Blocklists list (i.e., 3CORESEC, ABUSECH, DSHIELD, SPAMHAUS, etc.) or the Suricata lists (i.e., Emergingthreats.net, Abuse.ch, etc.). So you’ve have "constructive conversation with the maintainers"?
>>>>>
>>>>> Yes, occasionally I have phone calls with a few of these providers.
>>>>>
>>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>>
>>>>>> Ha! Yes a surprise. In the beginning you seemed interested as IPFire needed a replacement for URL Filter. You asked good questions about the lists picked, asked for the value to the users, etc. And I answered the best I could.
>>>>>>
>>>>>> You even asked: “Why is this realised as an add-on and not part of the core system?” from your Jul 28, 2024 email.
>>>>>
>>>>> Ah, so, why is the patch creating an add-on? Not that I am saying that what I say is law, but it has not been challenged either. If my input is being ignored, why should I put this to the top of my list of priorities? I am not disappointed about this, just trying to be very good with my time.
>>>>>
>>>>>> And on January 16, 2025 I wrote a message looking for help. And you were kind to respond quickly. So in three weeks time, since the kind response, something has changed. You went from supportive to "this".
>>>>>>
>>>>>> So yes, I am surprised.
>>>>>
>>>>> Well, maybe I should not have replied to that email. It was clear that you were on some path that was not right, but you were not interested before in finding the right path from the beginning.
>>>>>
>>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>>
>>>>>> Be more specific, what has to change? What exactly did I dismiss?
>>>>>
>>>>> Dismissal is just my assumption. I don’t know what you actually did with my feedback. I can only see the end product that does not seem contain much of it. Repeatedly I have been pointing out that we should think before we build. I am sure a lot of hours have now gone into some code that simply does not satisfy me. And I am not not talking about the code itself, what it does is what I don’t think is right for us.
>>>>>
>>>>> The process is very clear for me that we should first of all think whether we want a certain feature now. Then there should be a clear roadmap for everyone to follow; tasks can be split-up as we go and hopefully then have something that is maintainable, interesting for our users and even would do us proud. This is how this should work.
>>>>>
>>>>> So, what has to change? I don’t think with shouting at each other, throwing patches around and making me generally unhappy is a good start.
>>>>>
>>>>> -Michael
>>>>>
>>>>>> Jon
>>>>>>
>>>>>>
>>>>>>
>>>>>>> On Feb 6, 2025, at 2:13 PM, Michael Tremer <michael.tremer(a)ipfire.org> wrote:
>>>>>>>
>>>>>>> Hello Jon,
>>>>>>>
>>>>>>> Well, here we are again with another patch regarding this feature.
>>>>>>>
>>>>>>> I cannot quite see from your email what the question is, but if this is a request to have this merged into IPFire, I am once again sorry to disappoint you.
>>>>>>>
>>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls. Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation. Therefore, what am I supposed to do with this email?
>>>>>>>
>>>>>>> I don’t want to merge code that I don’t agree with. So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>>>
>>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>>>
>>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>>>
>>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>>>
>>>>>>> All the best,
>>>>>>> -Michael
>>>>>>>
>>>>>>>> On 6 Feb 2025, at 16:35, Jon Murphy <jon.murphy(a)ipfire.org> wrote:
>>>>>>>>
>>>>>>>> What is it?
>>>>>>>> Response Policy Zone (RPZ) is a mechanism to define local policies in a
>>>>>>>> standardized way and load those policies from external sources.
>>>>>>>> Bottom line: RPZ allows admins to easily block access to websites via DNS lookup.
>>>>>>>>
>>>>>>>> RPZ can block websites via categories. Examples include: fake websites, annoying
>>>>>>>> pop-up ads, newly registered domains, DoH bypass sites, bad "host" services,
>>>>>>>> maliscious top level domains (e.g., *.zip, *.mov), piracy, gambling, pornography,
>>>>>>>> and more. RPZ lists come from various RPZ providers and their available
>>>>>>>> catagories.
>>>>>>>>
>>>>>>>> This RPZ add-on enables the RPZ functionality by adding a couple lines in a
>>>>>>>> configuration file. This add-on simply adds configuration files and adds
>>>>>>>> scripts (config, metrics and sleep) to make RPZ easier for the admin to use.
>>>>>>>>
>>>>>>>> The RPZ scripts include additional languages: German, Spanish, French, Turkish,
>>>>>>>> and Italian.
>>>>>>>>
>>>>>>>> RPZ itself was release in 2010 and has been part of the IPFire build since ~2015.
>>>>>>>>
>>>>>>>> Why is it needed? What is its value?
>>>>>>>>
>>>>>>>> - The RPZ concept places this filtering into IPFire, our internet access
>>>>>>>> gateway, which is (should be) solely used as DNS source of the internal network.
>>>>>>>>
>>>>>>>> - As most sites use HTTPS it makes it difficult to filter traffic with URL
>>>>>>>> Filter without also properly configuring conventional (non-transparent)
>>>>>>>> mode on the proxy. RPZ is a nice replacement for the URL Filter.
>>>>>>>>
>>>>>>>> - No need to install and maintain an additional device like PiHole or AdBlock
>>>>>>>> browser extensions on multiple user devices.
>>>>>>>>
>>>>>>>> - This is an additional layer of protection for users. Less worry someone will
>>>>>>>> click on something that gets them into trouble. And, saying this with emphasis,
>>>>>>>> the ability to do it in one place!
>>>>>>>>
>>>>>>>> - Blocked sites save on unneeded traffic and can lessen the threat of malware
>>>>>>>> in advertisements
>>>>>>>>
>>>>>>>> - Logging allows the admin to see the site blocked and take actions
>>>>>>>>
>>>>>>>> - RPZ will be used at the home, home-office (work from home), schools,
>>>>>>>> ministerial, and at the office. Device counts are small (2-6) to medium (~80)
>>>>>>>> to mediam-large (200+).
>>>>>>>>
>>>>>>>> - RPZ can block ads, popups, phishing, scammers, spyware, malware, annoying
>>>>>>>> popups, NSFW links, DOH servers, and the usual internet trash.
>>>>>>>>
>>>>>>>> ------------------------------
>>>>>>>>
>>>>>>>> Change Log for RPZ add-on
>>>>>>>>
>>>>>>>> rpz-1.0.0-18 on 2025-02-05
>>>>>>>> - Build for approval & release as IPFire add-on
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> rpz-beta-0.1.18-18.ipfire on 2025-02-01
>>>>>>>> rpz.cgi:
>>>>>>>> - new feature: added a mod key to force a unbound restart
>>>>>>>>
>>>>>>>> rpz-config and rpz-make:
>>>>>>>> - new feature: added action for unbound restart `rpz-config unbound-restart`
>>>>>>>>
>>>>>>>> rpz-metrics:
>>>>>>>> - simple reformatting
>>>>>>>> - rename far right column from "last update" to "last download"
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> rpz-beta-0.1.17-17.ipfire on 2024-12-09
>>>>>>>> rpz-make
>>>>>>>> - bug fix: corrected validation regex for wildcards like: `*.domain.com`
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> rpz-beta-0.1.16-16.ipfire on 2024-11-18
>>>>>>>> rpz-make
>>>>>>>> - new feature: updated validation regex
>>>>>>>> - bug fix: moved validation to beginning of process. Now we validate before
>>>>>>>> creating config files.
>>>>>>>>
>>>>>>>> rpz.cgi:
>>>>>>>> - new feature: use CSS color variables of the main ipfire theme
>>>>>>>> - bug fix: empty zonefile remarks were stored as “undef” and caused a warning
>>>>>>>> - bug fix: HTML textarea removes the first empty line in a custom list
>>>>>>>> - thank you Leo!
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> rpz-beta-0.1.15-15.ipfire on 2024-11-04
>>>>>>>> rpz.cgi:
>>>>>>>> - new feature: added new language file for Turkish (thank you Peppe)
>>>>>>>>
>>>>>>>> rpz-make
>>>>>>>> - bug fix: corrected empty allow/block list issue. An empty allow/block list
>>>>>>>> will now remove contents of allow/block.rpz files and remove unneeded
>>>>>>>> allow/block.conf file. (thank you iptom)
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> rpz-beta-0.1.14-14.ipfire on 2024-10-29
>>>>>>>> rpz-config:
>>>>>>>> - bug fix: correct missing rpz extension. `rpz-config list` displayed URL
>>>>>>>> incorrectly (thank you Bernhard)
>>>>>>>>
>>>>>>>> rpz.cgi:
>>>>>>>> - bug fix: remove extra `"` in language files (thank you Bernhard)
>>>>>>>> - new feature: slightly dim "apply" button when not enabled
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> rpz-beta-0.1.13-13.ipfire on 2024-10-27
>>>>>>>> - skipped
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> rpz-beta-0.1.12-12.ipfire on 2024-10-21
>>>>>>>> rpz.cgi:
>>>>>>>> - new feature: added new language file for French (thank you gw-ipfire)
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> rpz-beta-0.1.11-11.ipfire on 2024-10-18
>>>>>>>> rpz.cgi:
>>>>>>>> - new feature: added new language file for Italian (thank you umberto)
>>>>>>>> - new feature: added new language file for Spanish (thank you Roberto)
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> rpz-beta-0.1.10-10.ipfire on 2024-10-15
>>>>>>>> rpz-make:
>>>>>>>> - bug fix: corrected validation error for a custom list entry (thank you siosios)
>>>>>>>> - e.g., `*.cloudflare-dns.com`
>>>>>>>>
>>>>>>>> install.sh:
>>>>>>>> - bug fix: add chown to correct user created files
>>>>>>>>
>>>>>>>> update.sh:
>>>>>>>> - bug fix: add chown to correct user created files (thank you siosios)
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> rpz-beta-0.1.9-9.ipfire on 2024-10-08
>>>>>>>> rpz.cgi:
>>>>>>>> - new feature: added new language file for German (thank you Leo)
>>>>>>>> - bug fix: add missing "rpz exitcode 110"
>>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> rpz-beta-0.1.8-8.ipfire on 2024-10-04
>>>>>>>> - skipped
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> rpz-beta-0.1.7-7.ipfire on 2024-10-03
>>>>>>>> All:
>>>>>>>> - new feature: includes beta version numbers for pakfire package,
>>>>>>>> instead of only `rpz-1.0.0-1.ipfire`, for each release.
>>>>>>>>
>>>>>>>> rpz.cgi:
>>>>>>>> - new feature: added new WebGUI at `rpz.cgi`
>>>>>>>> - a BIG thank you to Leo Hofmann for all of his work creating the webgui!!
>>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>>
>>>>>>>> rpz-make:
>>>>>>>> - new feature: validate entries in allowlist and blocklist
>>>>>>>> - new feature: add "no-reload" option for WebGUI
>>>>>>>>
>>>>>>>> rpz-metrics:
>>>>>>>> - new feature: info can be sorted by name, by hit count, by line count, by
>>>>>>>> "enabled" list or all lists
>>>>>>>>
>>>>>>>> backups:
>>>>>>>> - bug fix: include all files in `/var/ipfire/dns/rpz` directory in backup
>>>>>>>>
>>>>>>>> update.sh:
>>>>>>>> - bug fix: corrected ownership for `/var/ipfire/dns/rpz` directory during an
>>>>>>>> update
>>>>>>>>
>>>>>>>> Build:
>>>>>>>> - bug fix: `block.rpz.conf` and `block.rpz` from build. Files to be created
>>>>>>>> by `rpz-make`
>>>>>>>>
>>>>>>>> WebGUI and German language file
>>>>>>>> Contribution-by: Leo-Andres Hofmann <hofmann(a)leo-andres.de>
>>>>>>>>
>>>>>>>> Spanish language file
>>>>>>>> Contribution-by: Roberto Peña
>>>>>>>>
>>>>>>>> Italian language file
>>>>>>>> Contribution-by: Umberto Parma
>>>>>>>>
>>>>>>>> French language file
>>>>>>>> Contribution-by: gw-ipfire
>>>>>>>>
>>>>>>>> Turkish language file
>>>>>>>> Contribution-by: Peppe Tech
>>>>>>>>
>>>>>>>> Contribution-by: Bernhard Bitsch <bbitsch(a)ipfire.org>
>>>>>>>> Contribution-by: Erik Kapfer <erik.kapfer(a)ipfire.org>
>>>>>>>> Signed-off-by: Jon Murphy <jon.murphy(a)ipfire.org
>>>>>>>> ---
>>>>>>>> config/backup/includes/rpz | 4 +
>>>>>>>> config/cfgroot/manualpages | 1 +
>>>>>>>> config/menu/EX-rpz.menu | 6 +
>>>>>>>> config/rootfiles/common/configroot | 1 +
>>>>>>>> config/rootfiles/common/web-user-interface | 1 +
>>>>>>>> config/rootfiles/packages/rpz | 20 +
>>>>>>>> config/rpz/00-rpz.conf | 10 +
>>>>>>>> config/rpz/rpz-config | 130 +++
>>>>>>>> config/rpz/rpz-functions | 85 ++
>>>>>>>> config/rpz/rpz-make | 203 +++++
>>>>>>>> config/rpz/rpz-metrics | 170 ++++
>>>>>>>> config/rpz/rpz-sleep | 58 ++
>>>>>>>> config/rpz/rpz.de.pl | 30 +
>>>>>>>> config/rpz/rpz.en.pl | 30 +
>>>>>>>> config/rpz/rpz.es.pl | 30 +
>>>>>>>> config/rpz/rpz.fr.pl | 30 +
>>>>>>>> config/rpz/rpz.it.pl | 30 +
>>>>>>>> config/rpz/rpz.tr.pl | 30 +
>>>>>>>> html/cgi-bin/rpz.cgi | 923 +++++++++++++++++++++
>>>>>>>> lfs/rpz | 96 +++
>>>>>>>> make.sh | 3 +-
>>>>>>>> src/paks/rpz/install.sh | 36 +
>>>>>>>> src/paks/rpz/uninstall.sh | 38 +
>>>>>>>> src/paks/rpz/update.sh | 52 ++
>>>>>>>> 24 files changed, 2016 insertions(+), 1 deletion(-)
>>>>>>>> create mode 100644 config/backup/includes/rpz
>>>>>>>> create mode 100644 config/menu/EX-rpz.menu
>>>>>>>> create mode 100644 config/rootfiles/packages/rpz
>>>>>>>> create mode 100644 config/rpz/00-rpz.conf
>>>>>>>> create mode 100644 config/rpz/rpz-config
>>>>>>>> create mode 100644 config/rpz/rpz-functions
>>>>>>>> create mode 100644 config/rpz/rpz-make
>>>>>>>> create mode 100755 config/rpz/rpz-metrics
>>>>>>>> create mode 100755 config/rpz/rpz-sleep
>>>>>>>> create mode 100644 config/rpz/rpz.de.pl
>>>>>>>> create mode 100644 config/rpz/rpz.en.pl
>>>>>>>> create mode 100644 config/rpz/rpz.es.pl
>>>>>>>> create mode 100644 config/rpz/rpz.fr.pl
>>>>>>>> create mode 100644 config/rpz/rpz.it.pl
>>>>>>>> create mode 100644 config/rpz/rpz.tr.pl
>>>>>>>> create mode 100644 html/cgi-bin/rpz.cgi
>>>>>>>> create mode 100644 lfs/rpz
>>>>>>>> create mode 100644 src/paks/rpz/install.sh
>>>>>>>> create mode 100644 src/paks/rpz/uninstall.sh
>>>>>>>> create mode 100644 src/paks/rpz/update.sh
>>>>>>>>
>>>>>>>> diff --git a/config/backup/includes/rpz b/config/backup/includes/rpz
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..36513e494
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/backup/includes/rpz
>>>>>>>> @@ -0,0 +1,4 @@
>>>>>>>> +/var/ipfire/dns/rpz/*
>>>>>>>> +/etc/unbound/zonefiles/allow.rpz
>>>>>>>> +/etc/unbound/zonefiles/block.rpz
>>>>>>>> +/etc/unbound/local.d/*rpz.conf
>>>>>>>> diff --git a/config/cfgroot/manualpages b/config/cfgroot/manualpages
>>>>>>>> index 1f7e01efc..d3a48c633 100644
>>>>>>>> --- a/config/cfgroot/manualpages
>>>>>>>> +++ b/config/cfgroot/manualpages
>>>>>>>> @@ -70,6 +70,7 @@ pakfire.cgi=configuration/ipfire/pakfire
>>>>>>>> wlanap.cgi=addons/wireless
>>>>>>>> tor.cgi=addons/tor
>>>>>>>> samba.cgi=addons/samba
>>>>>>>> +rpz.cgi=addons/rpz
>>>>>>>>
>>>>>>>> # Logs menu
>>>>>>>> logs.cgi/summary.dat=configuration/logs/summary
>>>>>>>> diff --git a/config/menu/EX-rpz.menu b/config/menu/EX-rpz.menu
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..2f4daf410
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/menu/EX-rpz.menu
>>>>>>>> @@ -0,0 +1,6 @@
>>>>>>>> +$subipfire->{'20.rpz'} = {
>>>>>>>> + 'caption' => $Lang::tr{'rpz'},
>>>>>>>> + 'uri' => '/cgi-bin/rpz.cgi',
>>>>>>>> + 'title' => "RPZ",
>>>>>>>> + 'enabled' => 1,
>>>>>>>> +};
>>>>>>>> diff --git a/config/rootfiles/common/configroot b/config/rootfiles/common/configroot
>>>>>>>> index 9839eee45..b30d6aae4 100644
>>>>>>>> --- a/config/rootfiles/common/configroot
>>>>>>>> +++ b/config/rootfiles/common/configroot
>>>>>>>> @@ -120,6 +120,7 @@ var/ipfire/menu.d/70-log.menu
>>>>>>>> #var/ipfire/menu.d/EX-apcupsd.menu
>>>>>>>> #var/ipfire/menu.d/EX-guardian.menu
>>>>>>>> #var/ipfire/menu.d/EX-mympd.menu
>>>>>>>> +#var/ipfire/menu.d/EX-rpz.menu
>>>>>>>> #var/ipfire/menu.d/EX-samba.menu
>>>>>>>> #var/ipfire/menu.d/EX-tor.menu
>>>>>>>> #var/ipfire/menu.d/EX-transmission.menu
>>>>>>>> diff --git a/config/rootfiles/common/web-user-interface b/config/rootfiles/common/web-user-interface
>>>>>>>> index 816241dae..e00464076 100644
>>>>>>>> --- a/config/rootfiles/common/web-user-interface
>>>>>>>> +++ b/config/rootfiles/common/web-user-interface
>>>>>>>> @@ -69,6 +69,7 @@ srv/web/ipfire/cgi-bin/proxy.cgi
>>>>>>>> srv/web/ipfire/cgi-bin/qos.cgi
>>>>>>>> srv/web/ipfire/cgi-bin/remote.cgi
>>>>>>>> srv/web/ipfire/cgi-bin/routing.cgi
>>>>>>>> +#srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>>> #srv/web/ipfire/cgi-bin/samba.cgi
>>>>>>>> srv/web/ipfire/cgi-bin/services.cgi
>>>>>>>> srv/web/ipfire/cgi-bin/shutdown.cgi
>>>>>>>> diff --git a/config/rootfiles/packages/rpz b/config/rootfiles/packages/rpz
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..1c8663049
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rootfiles/packages/rpz
>>>>>>>> @@ -0,0 +1,20 @@
>>>>>>>> +etc/unbound/local.d/00-rpz.conf
>>>>>>>> +etc/unbound/zonefiles
>>>>>>>> +etc/unbound/zonefiles/allow.rpz
>>>>>>>> +usr/sbin/rpz-config
>>>>>>>> +usr/sbin/rpz-functions
>>>>>>>> +usr/sbin/rpz-make
>>>>>>>> +usr/sbin/rpz-metrics
>>>>>>>> +usr/sbin/rpz-sleep
>>>>>>>> +var/ipfire/addon-lang/rpz.de.pl
>>>>>>>> +var/ipfire/addon-lang/rpz.en.pl
>>>>>>>> +var/ipfire/addon-lang/rpz.es.pl
>>>>>>>> +var/ipfire/addon-lang/rpz.fr.pl
>>>>>>>> +var/ipfire/addon-lang/rpz.it.pl
>>>>>>>> +var/ipfire/addon-lang/rpz.tr.pl
>>>>>>>> +var/ipfire/backup/addons/includes/rpz
>>>>>>>> +var/ipfire/dns/rpz
>>>>>>>> +var/ipfire/dns/rpz/allowlist
>>>>>>>> +var/ipfire/dns/rpz/blocklist
>>>>>>>> +var/ipfire/menu.d/EX-rpz.menu
>>>>>>>> +srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>>> diff --git a/config/rpz/00-rpz.conf b/config/rpz/00-rpz.conf
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..f005a4f2e
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/00-rpz.conf
>>>>>>>> @@ -0,0 +1,10 @@
>>>>>>>> +server:
>>>>>>>> + module-config: "respip validator iterator"
>>>>>>>> +
>>>>>>>> +rpz:
>>>>>>>> + name: allow.rpz
>>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>>> + rpz-action-override: passthru
>>>>>>>> + rpz-log: yes
>>>>>>>> + rpz-log-name: allow
>>>>>>>> + rpz-signal-nxdomain-ra: yes
>>>>>>>> diff --git a/config/rpz/rpz-config b/config/rpz/rpz-config
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..c72d50f9b
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/rpz-config
>>>>>>>> @@ -0,0 +1,130 @@
>>>>>>>> +#!/bin/bash
>>>>>>>> +###############################################################################
>>>>>>>> +# #
>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info(a)ipfire.org> #
>>>>>>>> +# #
>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>> +# #
>>>>>>>> +###############################################################################
>>>>>>>> +
>>>>>>>> +version="2025-01-11 - v44"
>>>>>>>> +
>>>>>>>> +############### Functions ###############
>>>>>>>> +
>>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>>> +
>>>>>>>> +############### Main ###############
>>>>>>>> +
>>>>>>>> +tagName="unbound"
>>>>>>>> +
>>>>>>>> +rpzAction="${1}" # input RPZ action
>>>>>>>> +rpzName="${2}" # input RPZ name
>>>>>>>> +rpzURL="${3}" # input RPZ URL
>>>>>>>> +rpzOption1="${4}" # input RPZ option #1
>>>>>>>> +rpzOption2="${5}" # input RPZ option #2
>>>>>>>> +
>>>>>>>> +rpzConfig="/etc/unbound/local.d/${rpzName}.rpz.conf" # output zone conf file
>>>>>>>> +rpzFile="/etc/unbound/zonefiles/${rpzName}.rpz" # output for RPZ file
>>>>>>>> +
>>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>>> +
>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>> + case "$1" in
>>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>>> + esac
>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>> +done
>>>>>>>> +
>>>>>>>> +case "${rpzAction}" in
>>>>>>>> + # add new rpz list
>>>>>>>> + add )
>>>>>>>> + check_name "${rpzName}" # is this a valid name?
>>>>>>>> + # does this config already exist? If yes, then exit
>>>>>>>> + if [[ -f "${rpzConfig}" ]] ; then
>>>>>>>> + msg_log "error: rpz: duplicate - ${rpzConfig} already exists. exit"
>>>>>>>> + exit 104
>>>>>>>> + fi
>>>>>>>> +
>>>>>>>> + # is this a valid URL?
>>>>>>>> + regex='^https://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
>>>>>>>> + if ! [[ "${rpzURL}" =~ $regex ]] ; then
>>>>>>>> + msg_log "error: rpz: the URL is not valid: \"${rpzURL}\". exit."
>>>>>>>> + exit 105
>>>>>>>> + fi
>>>>>>>> +
>>>>>>>> + # create the zone config file
>>>>>>>> + {
>>>>>>>> + echo "rpz:"
>>>>>>>> + echo " name: ${rpzName}.rpz"
>>>>>>>> + echo " zonefile: ${rpzFile}"
>>>>>>>> + echo " url: ${rpzURL}"
>>>>>>>> + echo " rpz-action-override: nxdomain"
>>>>>>>> + echo " rpz-log: ${rpzLog}"
>>>>>>>> + echo " rpz-log-name: ${rpzName}"
>>>>>>>> + echo " rpz-signal-nxdomain-ra: yes"
>>>>>>>> + } > "${rpzConfig}"
>>>>>>>> +
>>>>>>>> + # set-up zonefile
>>>>>>>> + # create an empty rpz file if it does not exist
>>>>>>>> + if [[ ! -f "${rpzFile}" ]] ; then
>>>>>>>> + touch "${rpzFile}"
>>>>>>>> + # unbound requires these settings for rpz files
>>>>>>>> + set_permissions "${rpzFile}" "${rpzConfig}"
>>>>>>>> + fi
>>>>>>>> + ;;
>>>>>>>> +
>>>>>>>> + # trash config file & rpz file
>>>>>>>> + remove )
>>>>>>>> + if ! [[ -f "${rpzConfig}" ]] ; then
>>>>>>>> + msg_log "error: rpz: cannot remove ${rpzConfig}, does not exist. exit"
>>>>>>>> + exit 106
>>>>>>>> + fi
>>>>>>>> +
>>>>>>>> + msg_log "info: rpz: remove config file & rpz file \"${rpzName}\""
>>>>>>>> + rm "${rpzConfig}"
>>>>>>>> + rm "${rpzFile}"
>>>>>>>> + ;;
>>>>>>>> +
>>>>>>>> + reload )
>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>> + ;;
>>>>>>>> +
>>>>>>>> + list )
>>>>>>>> + awk -F':' '/^\s*name:/{ gsub(/[[:blank:]]|\.rpz/, "",$2) ; NAME=$2 } \
>>>>>>>> + /^\s*url:/{ gsub(/[[:blank:]]/, "") ; print NAME"="$2":"$3} ' \
>>>>>>>> + /etc/unbound/local.d/*rpz.conf
>>>>>>>> + exit
>>>>>>>> + ;;
>>>>>>>> +
>>>>>>>> + unbound-restart )
>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>> + unbound_restart
>>>>>>>> + exit
>>>>>>>> + ;;
>>>>>>>> +
>>>>>>>> + * )
>>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>>> + printf "Usage: $(basename "$0") <ACTION> <NAME> <URL> <OPTION> <OPTION>\n"
>>>>>>>> + printf "Version: ${version}\n"
>>>>>>>> + exit 108
>>>>>>>> + ;;
>>>>>>>> +
>>>>>>>> +esac
>>>>>>>> +
>>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>>> +
>>>>>>>> +exit
>>>>>>>> diff --git a/config/rpz/rpz-functions b/config/rpz/rpz-functions
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..ace1d2690
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/rpz-functions
>>>>>>>> @@ -0,0 +1,85 @@
>>>>>>>> +#!/bin/bash
>>>>>>>> +###############################################################################
>>>>>>>> +# #
>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>>>>> +# #
>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>> +# #
>>>>>>>> +###############################################################################
>>>>>>>> +
>>>>>>>> +version="2024-12-10 - v02"
>>>>>>>> +
>>>>>>>> +############### Functions ###############
>>>>>>>> +
>>>>>>>> +msg_log () {
>>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>>> + if tty --silent ; then
>>>>>>>> + echo "${tagName}:" "$*"
>>>>>>>> + fi
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# check for a valid name
>>>>>>>> +check_name () {
>>>>>>>> + local theName="${1}"
>>>>>>>> +
>>>>>>>> + regex='^[a-zA-Z0-9_]+$' # no dash or plus, alpha numeric only
>>>>>>>> + regex1='^(allow|block)$' # allow and block are reserved NAMEs
>>>>>>>> + if [[ ! "${theName}" =~ $regex ]] || [[ "${theName}" =~ $regex1 ]] ; then
>>>>>>>> + msg_log "error: rpz: the NAME is not valid: \"${theName}\". exit."
>>>>>>>> + exit 101
>>>>>>>> + fi
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +set_permissions () {
>>>>>>>> + chown nobody:nobody "$@"
>>>>>>>> + chmod 644 "$@"
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +check_unbound_conf () {
>>>>>>>> + local thecheckConf="${1:-yes}" # check config default is yes
>>>>>>>> +
>>>>>>>> + # check the above config files
>>>>>>>> + if [[ "${thecheckConf}" == yes ]] ; then
>>>>>>>> + msg_log "info: rpz: check for errors with \"unbound-checkconf\""
>>>>>>>> +
>>>>>>>> + if ! unbound-checkconf ; then
>>>>>>>> + msg_log "error: rpz: unbound-checkconf found invalid configuration."
>>>>>>>> + msg_log \
>>>>>>>> + "error: rpz: In Terminal run the command \"unbound-checkconf\" for more information. exit."
>>>>>>>> + exit 102
>>>>>>>> + fi
>>>>>>>> + fi
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +unbound_control_reload () {
>>>>>>>> + local theReload="${1:-yes}" # reload default is yes
>>>>>>>> +
>>>>>>>> + if [[ "${theReload}" == yes ]] ; then
>>>>>>>> + # reload due to the changes
>>>>>>>> + msg_log "info: rpz: run \"unbound-control reload\""
>>>>>>>> +
>>>>>>>> + if ! unbound-control reload ; then
>>>>>>>> + msg_log "error: rpz: unbound-control reload. exit."
>>>>>>>> + exit 109
>>>>>>>> + fi
>>>>>>>> + fi
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +unbound_restart () {
>>>>>>>> + # restart due to the changes
>>>>>>>> + msg_log "info: rpz: run \"unbound restart\""
>>>>>>>> +
>>>>>>>> + /usr/local/bin/unboundctrl restart
>>>>>>>> +}
>>>>>>>> diff --git a/config/rpz/rpz-make b/config/rpz/rpz-make
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..927d55170
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/rpz-make
>>>>>>>> @@ -0,0 +1,203 @@
>>>>>>>> +#!/bin/bash
>>>>>>>> +###############################################################################
>>>>>>>> +# #
>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info(a)ipfire.org> #
>>>>>>>> +# #
>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>> +# #
>>>>>>>> +###############################################################################
>>>>>>>> +
>>>>>>>> +version="2025-01-11 - v14"
>>>>>>>> +
>>>>>>>> +############### Functions ###############
>>>>>>>> +
>>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>>> +
>>>>>>>> +# create the config file for allow
>>>>>>>> +make_allow_config () {
>>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>>> + local theConfig="/etc/unbound/local.d/00-rpz.conf" # output zone conf file
>>>>>>>> + local theList="/var/ipfire/dns/rpz/allowlist" # input custom list of domains
>>>>>>>> +
>>>>>>>> + msg_log "info: rpz: make config file \"00-rpz.conf\""
>>>>>>>> +
>>>>>>>> + echo "server:
>>>>>>>> + module-config: \"respip validator iterator\"" > "${theConfig}"
>>>>>>>> +
>>>>>>>> + # does allow list exist?
>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>> +
>>>>>>>> + echo "rpz:
>>>>>>>> + name: allow.rpz
>>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>>> + rpz-action-override: passthru
>>>>>>>> + rpz-log: ${theLog}
>>>>>>>> + rpz-log-name: allow
>>>>>>>> + rpz-signal-nxdomain-ra: yes" >> "${theConfig}"
>>>>>>>> +
>>>>>>>> + fi
>>>>>>>> +
>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>> + set_permissions "${theConfig}"
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# create the config file for block
>>>>>>>> +make_block_config () {
>>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>>> + local theConfig="/etc/unbound/local.d/block.rpz.conf" # output zone conf file
>>>>>>>> + local theList="/var/ipfire/dns/rpz/blocklist" # input custom list of domains
>>>>>>>> +
>>>>>>>> + msg_log "info: rpz: make config file \"block.rpz.conf\""
>>>>>>>> +
>>>>>>>> + # does block list exist?
>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>> +
>>>>>>>> + echo "rpz:
>>>>>>>> + name: block.rpz
>>>>>>>> + zonefile: /etc/unbound/zonefiles/block.rpz
>>>>>>>> + rpz-action-override: nxdomain
>>>>>>>> + rpz-log: ${theLog}
>>>>>>>> + rpz-log-name: block
>>>>>>>> + rpz-signal-nxdomain-ra: yes" > "${theConfig}"
>>>>>>>> +
>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>> + set_permissions "${theConfig}"
>>>>>>>> + else
>>>>>>>> + # no - trash the config file
>>>>>>>> + rm --verbose /etc/unbound/local.d/block.rpz.conf
>>>>>>>> + fi
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# create an RPZ file for allow or block
>>>>>>>> +make_rpz_file () {
>>>>>>>> + local theName="${1}" # allow or block
>>>>>>>> + local theAction='.' # the default is nxdomain or block
>>>>>>>> + local actionList
>>>>>>>> +
>>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>>> + local theZonefile="/etc/unbound/zonefiles/${theName}.rpz" # output file for RPZ
>>>>>>>> +
>>>>>>>> + # does a list exist?
>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>> +
>>>>>>>> + # for allow set to passthru
>>>>>>>> + [[ "${theName}" == allow ]] && theAction='rpz-passthru.'
>>>>>>>> +
>>>>>>>> + # drop any extra "blanks" and add "CNAME <RPZ action>." to each line
>>>>>>>> + actionList=$( awk '{$1=$1};1' "${theList}" |
>>>>>>>> + sed "/^[^;].*[[:alnum:]]/ s|$| CNAME ${theAction}|" )
>>>>>>>> +
>>>>>>>> + msg_log "info: rpz: create zonefile for \"${theName}list\""
>>>>>>>> +
>>>>>>>> +echo "; Name: ${theName} list
>>>>>>>> +; Last modified: $(date "+%Y-%m-%d at %H.%M.%S %Z")
>>>>>>>> +;
>>>>>>>> +; domains with actions list
>>>>>>>> +;
>>>>>>>> +${actionList}" > "${theZonefile}"
>>>>>>>> +
>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>> + set_permissions "${theZonefile}"
>>>>>>>> + # set-up allow/block list files
>>>>>>>> + set_permissions "${theList}"
>>>>>>>> + else
>>>>>>>> + msg_log "info: rpz: the ${theList} is empty."
>>>>>>>> +
>>>>>>>> + rm --verbose "${theZonefile}" # trash the RPZ file
>>>>>>>> + fi
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# check if allow/block list is valid
>>>>>>>> +validate_list () {
>>>>>>>> + local theName="${1}" # allow or block
>>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>>> +
>>>>>>>> + # remove good:
>>>>>>>> + # - properly formated domain names with or without leading wildcard
>>>>>>>> + # - properly formated top level domain (TLD) names with wildcard
>>>>>>>> + # - blank lines and comment lines
>>>>>>>> + # remaining lines are considered "bad"
>>>>>>>> + bad_lines=$( sed --regexp-extended \
>>>>>>>> + '/^(\*\.)?([a-zA-Z0-9](([a-zA-Z0-9\-]){0,61}[a-zA-Z0-9])?\.)+([a-zA-Z]{2,}|xn--[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])$/d ;
>>>>>>>> + /^(\*\.)([a-z]{2,61}|xn--[a-z0-9]{1,60})$/d ;
>>>>>>>> + /^$/d ; /^;/d' "${theList}" )
>>>>>>>> +
>>>>>>>> + if [[ ! -z "${bad_lines}" ]] ; then
>>>>>>>> + msg_log "error: rpz: invalid line(s) in ${theList}."
>>>>>>>> + printf "%s\n" "bad line(s): ${bad_lines}"
>>>>>>>> + exit 110
>>>>>>>> + fi
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +
>>>>>>>> +############### Main ###############
>>>>>>>> +
>>>>>>>> +tagName="unbound"
>>>>>>>> +
>>>>>>>> +rpzName="${1}" # input RPZ name
>>>>>>>> +
>>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>>> +
>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>> + case "$1" in
>>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>>> + esac
>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>> +done
>>>>>>>> +
>>>>>>>> +case "${rpzName}" in
>>>>>>>> + # make a new allow or block rpz file
>>>>>>>> +
>>>>>>>> + allow )
>>>>>>>> + validate_list 'allow' # is the allowlist valid?
>>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>>> + make_rpz_file 'allow'
>>>>>>>> + ;;
>>>>>>>> +
>>>>>>>> + allowblock )
>>>>>>>> + validate_list 'allow' # is the list valid?
>>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>>> + make_rpz_file 'allow'
>>>>>>>> + ;&
>>>>>>>> +
>>>>>>>> + block )
>>>>>>>> + validate_list 'block' # is the blocklist valid?
>>>>>>>> + make_block_config "${rpzLog}"
>>>>>>>> + make_rpz_file 'block'
>>>>>>>> + ;;
>>>>>>>> +
>>>>>>>> + reload )
>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>> + ;;
>>>>>>>> +
>>>>>>>> + unbound-restart )
>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>> + unbound_restart
>>>>>>>> + exit
>>>>>>>> + ;;
>>>>>>>> +
>>>>>>>> + * )
>>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>>> + printf "Usage: $(basename "$0") <NAME> <OPTION> <OPTION>\n"
>>>>>>>> + printf "Version: ${version}\n"
>>>>>>>> + exit 108
>>>>>>>> + ;;
>>>>>>>> +esac
>>>>>>>> +
>>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>>> +
>>>>>>>> +exit
>>>>>>>> diff --git a/config/rpz/rpz-metrics b/config/rpz/rpz-metrics
>>>>>>>> new file mode 100755
>>>>>>>> index 000000000..4d43e1629
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/rpz-metrics
>>>>>>>> @@ -0,0 +1,170 @@
>>>>>>>> +#!/bin/bash
>>>>>>>> +###############################################################################
>>>>>>>> +# #
>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>>>>> +# #
>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>> +# #
>>>>>>>> +###############################################################################
>>>>>>>> +
>>>>>>>> +version="2025-01-20 - v25"
>>>>>>>> +
>>>>>>>> +############### Main ###############
>>>>>>>> +
>>>>>>>> +weeks="2" # default to two message logs
>>>>>>>> +sortBy="name" # default "by name"
>>>>>>>> +rpzActive="enabled" # default "enabled only"
>>>>>>>> +
>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>> + case "$1" in
>>>>>>>> + --by-names | --by-name | name ) sortBy="name" ;;
>>>>>>>> +
>>>>>>>> + --by-hits | --by-hit | hits | hit ) sortBy="hit" ;;
>>>>>>>> +
>>>>>>>> + --by-lines | --by-line | lines | line ) sortBy="line" ;;
>>>>>>>> +
>>>>>>>> + --by-effect ) sortBy="effect" ;;
>>>>>>>> +
>>>>>>>> + --enabled-only ) rpzActive="enabled" ;;
>>>>>>>> +
>>>>>>>> + --active-all | --all | all ) rpzActive="all" ;;
>>>>>>>> +
>>>>>>>> + [0-9] | [0-9][0-9] ) weeks=$1 ;;
>>>>>>>> + esac
>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>> +done
>>>>>>>> +
>>>>>>>> +# get the list of message logs for N weeks
>>>>>>>> +messageLogs=$( find /var/log/messages* -type f | sort --version-sort |
>>>>>>>> + head -"${weeks}" )
>>>>>>>> +
>>>>>>>> +# get the list of RPZ names & counts from the message log(s)
>>>>>>>> +rpzNameCount=$( for logf in ${messageLogs} ; do
>>>>>>>> + zgrep --text --fixed-strings 'info: rpz: applied' "${logf}" |
>>>>>>>> + awk '$10 ~ /\[\w*]/ { print $10 }' ;
>>>>>>>> + done | sort | uniq --count )
>>>>>>>> +
>>>>>>>> +# flip results and remove brackets `[` and `]`
>>>>>>>> +rpzNameCount=$( echo "${rpzNameCount}" |
>>>>>>>> + awk '{ print $2, $1 }' |
>>>>>>>> + sed --regexp-extended 's|^\[(.*)\]|\1|' )
>>>>>>>> +
>>>>>>>> +# grab only names
>>>>>>>> +rpzNames=$( echo "${rpzNameCount}" | awk '{ print $1 }' )
>>>>>>>> +
>>>>>>>> +# get list of RPZ files
>>>>>>>> +rpzFileList=$( find /etc/unbound/zonefiles -type f -iname "*.rpz" )
>>>>>>>> +
>>>>>>>> +# get basename of those files
>>>>>>>> +rpzBaseNames=$( echo "${rpzFileList}" |
>>>>>>>> + sed 's|/etc/unbound/zonefiles/||g ; s|\.rpz||g ;' )
>>>>>>>> +
>>>>>>>> +# add to rpzNames
>>>>>>>> +rpzNames="${rpzNames}"$'\n'"${rpzBaseNames}"
>>>>>>>> +
>>>>>>>> +# drop duplicate names
>>>>>>>> +rpzNames=$( echo "${rpzNames}" | sort --unique )
>>>>>>>> +
>>>>>>>> +# get line count for each RPZ
>>>>>>>> +lineCount=$( echo "${rpzFileList}" | xargs wc -l )
>>>>>>>> +
>>>>>>>> +# get comment line count and blank line count for each RPZ
>>>>>>>> +commentCount=$( echo "${rpzFileList}" | xargs grep --count -e "^$" -e "^;" )
>>>>>>>> +
>>>>>>>> +# get modified date each RPZ
>>>>>>>> +modDateList=$( echo "${rpzFileList}" | xargs stat -c '%.10y %n' )
>>>>>>>> +
>>>>>>>> +ucListAuthZones=$( unbound-control list_auth_zones )
>>>>>>>> +
>>>>>>>> +# get width of RPZ names
>>>>>>>> +pWidth=$( echo "${rpzNames}" | awk '{ print $1" " }' | wc -L )
>>>>>>>> +pFormat="%-${pWidth}s %-8s %-8s %8s %12s %12s\n"
>>>>>>>> +
>>>>>>>> +# print title line
>>>>>>>> +printf "${pFormat}" "name" "hits" "active" "lines" " hits/line" "last download"
>>>>>>>> +printf -- "--------------"
>>>>>>>> +
>>>>>>>> +theResults=""
>>>>>>>> +totalLines=0
>>>>>>>> +totalHits=0
>>>>>>>> +while read -r theName
>>>>>>>> +do
>>>>>>>> + printf -- "--" # pretend progress bar
>>>>>>>> +
>>>>>>>> + # is this RPZ list active?
>>>>>>>> + theActive="disabled"
>>>>>>>> + if grep --quiet "^${theName}\.rpz" <<< "${ucListAuthZones}"
>>>>>>>> + then
>>>>>>>> + theActive="enabled"
>>>>>>>> + else
>>>>>>>> + [[ "${rpzActive}" == enabled ]] && continue
>>>>>>>> + fi
>>>>>>>> +
>>>>>>>> + # get hit count
>>>>>>>> + theHits="0"
>>>>>>>> + if output=$( grep "^${theName}\s" <<< "${rpzNameCount}" ) ; then
>>>>>>>> + theHits=$( echo "${output}" | awk '{ print $2 }' )
>>>>>>>> + totalHits=$(( totalHits + theHits ))
>>>>>>>> + fi
>>>>>>>> +
>>>>>>>> + # get line count
>>>>>>>> + theLines="n/a"
>>>>>>>> + hitsPerLine="0"
>>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${lineCount}" ) ; then
>>>>>>>> + theLines=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>>> + totalLines=$(( totalLines + theLines ))
>>>>>>>> +
>>>>>>>> + if [[ "${theLines}" -gt 2 ]] ; then
>>>>>>>> + hitsPerLine=$(( 100 * theHits / theLines ))
>>>>>>>> + fi
>>>>>>>> + fi
>>>>>>>> +
>>>>>>>> + # get modification date
>>>>>>>> + theModDate="n/a"
>>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${modDateList}" ) ; then
>>>>>>>> + theModDate=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>>> + fi
>>>>>>>> +
>>>>>>>> + # add to results list
>>>>>>>> + theResults+="${theName} ${theHits} ${theActive} ${theLines} ${hitsPerLine} ${theModDate}"$'\n'
>>>>>>>> +
>>>>>>>> +done <<< "${rpzNames}"
>>>>>>>> +
>>>>>>>> +case "${sortBy}" in
>>>>>>>> + # sort by "active" then by "name"
>>>>>>>> + name) sortArg=(-k3,3r -k1,1) ;;
>>>>>>>> +
>>>>>>>> + # sort by "active" then by "hits" then by "name"
>>>>>>>> + hit) sortArg=(-k3,3r -k2,2nr -k1,1) ;;
>>>>>>>> +
>>>>>>>> + # sort by "active" then by "lines" then by "name"
>>>>>>>> + line) sortArg=(-k3,3r -k4,4nr -k1,1) ;;
>>>>>>>> +
>>>>>>>> + # sort by "active" then by "effect" then by "name"
>>>>>>>> + effect) sortArg=(-k3,3r -k5,5nr -k1,1) ;;
>>>>>>>> +esac
>>>>>>>> +
>>>>>>>> +printf -- "--------------\n"
>>>>>>>> +# remove blank lines, sort, print as columns
>>>>>>>> +echo "${theResults}" |
>>>>>>>> + awk '!/^[[:space:]]*$/' |
>>>>>>>> + sort "${sortArg[@]}" |
>>>>>>>> + awk --assign=width="${pWidth}" \
>>>>>>>> + '{ printf "%-*s %-8s %-8s %8s %10s %% %12s\n", width, $1, $2, $3, $4, $5, $6 }'
>>>>>>>> +
>>>>>>>> +printf "${pFormat}" "" "=======" "" "========" "" ""
>>>>>>>> +printf "${pFormat}" "Totals -->" "${totalHits}" "" "${totalLines}" "" ""
>>>>>>>> +
>>>>>>>> +exit
>>>>>>>> diff --git a/config/rpz/rpz-sleep b/config/rpz/rpz-sleep
>>>>>>>> new file mode 100755
>>>>>>>> index 000000000..dd3603599
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/rpz-sleep
>>>>>>>> @@ -0,0 +1,58 @@
>>>>>>>> +#!/bin/bash
>>>>>>>> +###############################################################################
>>>>>>>> +# #
>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>>>>> +# #
>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>> +# #
>>>>>>>> +###############################################################################
>>>>>>>> +
>>>>>>>> +version="2024-08-16" # v05
>>>>>>>> +
>>>>>>>> +############### Functions ###############
>>>>>>>> +
>>>>>>>> +# send message to message log
>>>>>>>> +msg_log () {
>>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>>> + if tty --silent ; then
>>>>>>>> + echo "${tagName}:" "$*"
>>>>>>>> + fi
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +############### Main ###############
>>>>>>>> +
>>>>>>>> +tagName="unbound"
>>>>>>>> +
>>>>>>>> +sleepTime="${1:-5m}" # default to sleep for 5m (5 minutes)
>>>>>>>> +
>>>>>>>> +zoneList=$( unbound-control list_auth_zones | awk '{print $1}' )
>>>>>>>> +
>>>>>>>> +for zone in ${zoneList} ; do
>>>>>>>> + printf "disable ${zone}\t"
>>>>>>>> + unbound-control rpz_disable "${zone}"
>>>>>>>> +done
>>>>>>>> +
>>>>>>>> +msg_log "info: rpz: disabled all zones for ${sleepTime}"
>>>>>>>> +
>>>>>>>> +sleep "${sleepTime}"
>>>>>>>> +
>>>>>>>> +for zone in ${zoneList} ; do
>>>>>>>> + printf "enable ${zone}\t"
>>>>>>>> + unbound-control rpz_enable "${zone}"
>>>>>>>> +done
>>>>>>>> +
>>>>>>>> +msg_log "info: rpz: enabled all zones"
>>>>>>>> +
>>>>>>>> +exit
>>>>>>>> diff --git a/config/rpz/rpz.de.pl b/config/rpz/rpz.de.pl
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..3770c6bb0
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/rpz.de.pl
>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>> +%tr = (%tr,
>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>> +'rpz apply' => 'Übernehmen',
>>>>>>>> +'rpz cl allow enable' => 'Benutzerdefinierte Allowlist aktivieren:',
>>>>>>>> +'rpz cl allow info' => 'Zugelassene Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>>> +'rpz cl allow' => 'Benutzerdefinierte Allowlist',
>>>>>>>> +'rpz cl block enable' => 'Benutzerdefinierte Blocklist aktivieren:',
>>>>>>>> +'rpz cl block info' => 'Gesperrte Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>>> +'rpz cl block' => 'Benutzerdefinierte Blocklist',
>>>>>>>> +'rpz cl' => 'Benutzerdefinierte Listen',
>>>>>>>> +'rpz exitcode 101' => 'Der Name enthält unzulässige Zeichen',
>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf hat eine fehlerhafte Konfiguration ermittelt. Führen Sie das Kommando unbound-checkconf auf der Konsole aus, um weitere Informationen zu erhalten.',
>>>>>>>> +'rpz exitcode 103' => 'Die benutzerdefinierte Allow-/Blocklist ist leer',
>>>>>>>> +'rpz exitcode 104' => 'Ein Eintrag mit identischem Namen existiert bereits',
>>>>>>>> +'rpz exitcode 105' => 'Die URL ist ungültig',
>>>>>>>> +'rpz exitcode 106' => 'Eintrag kann nicht entfernt werden, der Name existiert nicht',
>>>>>>>> +'rpz exitcode 107' => 'Der Name ist ungültig - nur "allow" oder "block" möglich',
>>>>>>>> +'rpz exitcode 108' => 'Fehlende oder inkorrekte Parameter',
>>>>>>>> +'rpz exitcode 109' => 'unbound-control reload ist fehlgeschlagen',
>>>>>>>> +'rpz exitcode 110' => 'Die benutzerdefinierte Allow-/Blocklist enthält unzulässige Einträge',
>>>>>>>> +'rpz exitcode 201' => 'Die Anmerkung enthält unzulässige Zeichen',
>>>>>>>> +'rpz exitcode 202' => 'Ungültiger Eintrag in der benutzerdefinierten Allowlist, Zeile ',
>>>>>>>> +'rpz exitcode 203' => 'Ungültiger Eintrag in der benutzerdefinierten Blocklist, Zeile ',
>>>>>>>> +'rpz exitcode 204' => 'Ausgewählter Eintrag existiert nicht: ',
>>>>>>>> +'rpz zf editor' => 'Zonendatei-Eintrag bearbeiten',
>>>>>>>> +'rpz zf imported' => '(importiert aus rpz-config)',
>>>>>>>> +'rpz zf remark info' => 'Erlaubte Zeichen sind a-z, A-Z, 0-9 und Unterstriche',
>>>>>>>> +'rpz zf' => 'Zonendateien',
>>>>>>>> +);
>>>>>>>> diff --git a/config/rpz/rpz.en.pl b/config/rpz/rpz.en.pl
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..0720a8940
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/rpz.en.pl
>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>> +%tr = (%tr,
>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>> +'rpz apply' => 'Apply',
>>>>>>>> +'rpz cl allow enable' => 'Enable custom allowlist:',
>>>>>>>> +'rpz cl allow info' => 'Allowed domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>>> +'rpz cl allow' => 'Custom allowlist',
>>>>>>>> +'rpz cl block enable' => 'Enable custom blocklist:',
>>>>>>>> +'rpz cl block info' => 'Blocked domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>>> +'rpz cl block' => 'Custom blocklist',
>>>>>>>> +'rpz cl' => 'Custom lists',
>>>>>>>> +'rpz exitcode 101' => 'the NAME is not valid',
>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf found invalid configuration. In the Terminal run the command unbound-checkconf for more information',
>>>>>>>> +'rpz exitcode 103' => 'the allow/block list is empty',
>>>>>>>> +'rpz exitcode 104' => 'duplicate - NAME already exists',
>>>>>>>> +'rpz exitcode 105' => 'the URL is not valid',
>>>>>>>> +'rpz exitcode 106' => 'cannot remove the NAME does not exist',
>>>>>>>> +'rpz exitcode 107' => 'the NAME is not valid - "allow" or "block" only',
>>>>>>>> +'rpz exitcode 108' => 'missing or incorrect parameter',
>>>>>>>> +'rpz exitcode 109' => 'unbound-control reload failed',
>>>>>>>> +'rpz exitcode 110' => 'custom Allowlist/Blocklist contains invalid entries',
>>>>>>>> +'rpz exitcode 201' => 'the REMARK is not valid',
>>>>>>>> +'rpz exitcode 202' => 'invalid entry in allowlist, line ',
>>>>>>>> +'rpz exitcode 203' => 'invalid entry in blocklist, line ',
>>>>>>>> +'rpz exitcode 204' => 'Selected entry does not exist: ',
>>>>>>>> +'rpz zf editor' => 'Edit zonefiles entry',
>>>>>>>> +'rpz zf imported' => '(imported from rpz-config)',
>>>>>>>> +'rpz zf remark info' => 'Valid characters are a-z, A-Z, 0-9 and underscore.',
>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>> +);
>>>>>>>> diff --git a/config/rpz/rpz.es.pl b/config/rpz/rpz.es.pl
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..98628e4aa
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/rpz.es.pl
>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>> +%tr = (%tr,
>>>>>>>> +'rpz' => 'Zonas de política de respuesta (RPZ)',
>>>>>>>> +'rpz apply' => 'Aplicar',
>>>>>>>> +'rpz cl allow enable' => 'Habilitar la lista blanca personalizada:',
>>>>>>>> +'rpz cl allow info' => 'Dominio permitido (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>>> +'rpz cl allow' => 'Lista blanca personalizada',
>>>>>>>> +'rpz cl block enable' => 'Habilitar la lista negra personalizada:',
>>>>>>>> +'rpz cl block info' => 'Dominio bloqueado (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>>> +'rpz cl block' => 'Lista negra personalizada',
>>>>>>>> +'rpz cl' => 'Lista personalizada',
>>>>>>>> +'rpz exitcode 101' => 'El NOMBRE no es válido',
>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha encontrado una configuración no válida. Desde Terminal, ejecute el comando unbound-checkconf para mayor información',
>>>>>>>> +'rpz exitcode 103' => 'La lista de permitidos/bloqueados está vacía',
>>>>>>>> +'rpz exitcode 104' => 'duplicado - NOMBRE ya existe',
>>>>>>>> +'rpz exitcode 105' => 'la URL no es válida',
>>>>>>>> +'rpz exitcode 106' => 'no es posible eliminar el NOMBRE que no existe',
>>>>>>>> +'rpz exitcode 107' => 'el NOMBRE no es válido - sólo "permitir" o "bloquear"',
>>>>>>>> +'rpz exitcode 108' => 'parámetro faltante o incorrecto',
>>>>>>>> +'rpz exitcode 109' => 'Error al recargar unbound-control',
>>>>>>>> +'rpz exitcode 110' => 'la Lista blanca/Lista negra personalizada contiene entradas no válidas',
>>>>>>>> +'rpz exitcode 201' => 'el COMENTARIO no es válido',
>>>>>>>> +'rpz exitcode 202' => 'entrada no válida en la lista blanca, línea ',
>>>>>>>> +'rpz exitcode 203' => 'entrada no válida en la lista negra, línea ',
>>>>>>>> +'rpz exitcode 204' => 'La entrada seleccionada no existe: ',
>>>>>>>> +'rpz zf editor' => 'Editar la entrada de archivos de zona',
>>>>>>>> +'rpz zf imported' => '(importado de rpz-config)',
>>>>>>>> +'rpz zf remark info' => 'Los caracteres válidos son a-z, A-Z, 0-9 y guión bajo',
>>>>>>>> +'rpz zf' => 'Archivos de zona',
>>>>>>>> +);
>>>>>>>> diff --git a/config/rpz/rpz.fr.pl b/config/rpz/rpz.fr.pl
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..f35f3c2d0
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/rpz.fr.pl
>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>> +%tr = (%tr,
>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>> +'rpz apply' => 'Appliquer',
>>>>>>>> +'rpz cl allow enable' => 'Activer la liste d\'autorisations personnalisée:',
>>>>>>>> +'rpz cl allow info' => 'Domaines autorisés (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>>> +'rpz cl allow' => 'Liste d\'autorisations personnalisée',
>>>>>>>> +'rpz cl block enable' => 'Activer la liste de blocage personnalisée:',
>>>>>>>> +'rpz cl block info' => 'Domaines bloqués (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>>> +'rpz cl block' => 'liste de blocage personnalisé',
>>>>>>>> +'rpz cl' => 'Listes personnalisées',
>>>>>>>> +'rpz exitcode 101' => 'le NOM n\'est pas valide',
>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf configuration non valide trouvée. Dans le terminal, exécutez la commande unbound-checkconf pour plus d\'informations',
>>>>>>>> +'rpz exitcode 103' => 'la liste autoriser/bloquer est vide',
>>>>>>>> +'rpz exitcode 104' => 'le NOM existe déjà',
>>>>>>>> +'rpz exitcode 105' => 'L\'URL n\'est pas valide',
>>>>>>>> +'rpz exitcode 106' => 'impossible de supprimer le NOM n\'existe pas',
>>>>>>>> +'rpz exitcode 107' => 'le NOM n\'est pas valide - « autoriser » ou « bloquer » seulement',
>>>>>>>> +'rpz exitcode 108' => 'paramètre manquant ou incorrect',
>>>>>>>> +'rpz exitcode 109' => 'unbound-control rechargement échoué',
>>>>>>>> +'rpz exitcode 110' => 'la liste autoriser/bloquer contient des entrées non valides',
>>>>>>>> +'rpz exitcode 201' => 'la REMARQUE n\'est pas valable',
>>>>>>>> +'rpz exitcode 202' => 'entrée non valide dans la liste d\'autorisation, ligne ',
>>>>>>>> +'rpz exitcode 203' => 'entrée non valide dans la liste de blocs, ligne ',
>>>>>>>> +'rpz exitcode 204' => 'L\'entrée sélectionnée n\'existe pas: ',
>>>>>>>> +'rpz zf editor' => 'Modifier l\'entrée Fichiers Zone',
>>>>>>>> +'rpz zf imported' => '(importé de rpz-config)',
>>>>>>>> +'rpz zf remark info' => 'Les caractères valides sont a-z, A-Z, 0-9 et soulignement.',
>>>>>>>> +'rpz zf' => 'Fichiers Zone',
>>>>>>>> +);
>>>>>>>> diff --git a/config/rpz/rpz.it.pl b/config/rpz/rpz.it.pl
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..ee81605c9
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/rpz.it.pl
>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>> +%tr = (%tr,
>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>> +'rpz apply' => 'Applica',
>>>>>>>> +'rpz cl allow enable' => 'Abilita la Whitelist personalizzata:',
>>>>>>>> +'rpz cl allow info' => 'Domini consentiti (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>>> +'rpz cl allow' => 'Whitelist personalizzata',
>>>>>>>> +'rpz cl block enable' => 'Abilita la Blacklist personalizzata:',
>>>>>>>> +'rpz cl block info' => 'Domini bloccati (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>>> +'rpz cl block' => 'Blacklist personalizzata',
>>>>>>>> +'rpz cl' => 'Liste personalizzate',
>>>>>>>> +'rpz exitcode 101' => 'il NOME non è valido',
>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha trovato una configurazione non valida. Dal Terminale esegui il comando unbound-checkconf per maggiori informazioni',
>>>>>>>> +'rpz exitcode 103' => 'l\'elenco consentiti/bloccati è vuoto',
>>>>>>>> +'rpz exitcode 104' => 'duplicato - NAME esiste di già',
>>>>>>>> +'rpz exitcode 105' => 'l\'URL non è valido',
>>>>>>>> +'rpz exitcode 106' => 'non è possibile rimuovere il NOME non esiste',
>>>>>>>> +'rpz exitcode 107' => 'il NOME non è valido - solo "consenti" o "blocca"',
>>>>>>>> +'rpz exitcode 108' => 'parametro mancante o non corretto',
>>>>>>>> +'rpz exitcode 109' => 'ricaricamento del controllo non associato non riuscito',
>>>>>>>> +'rpz exitcode 110' => 'la Whitelist/Blacklist personalizzata contiene voci non valide',
>>>>>>>> +'rpz exitcode 201' => 'l"OSSERVAZIONE non è valida',
>>>>>>>> +'rpz exitcode 202' => 'voce non valida nella Whitelist, riga ',
>>>>>>>> +'rpz exitcode 203' => 'voce non valida nella Blacklist, riga ',
>>>>>>>> +'rpz exitcode 204' => 'La voce selezionata non esiste: ',
>>>>>>>> +'rpz zf editor' => 'Modifica la voce dei file di zona',
>>>>>>>> +'rpz zf imported' => '(importato da rpz-config)',
>>>>>>>> +'rpz zf remark info' => 'I caratteri validi sono a-z, A-Z, 0-9 e trattino basso',
>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>> +);
>>>>>>>> diff --git a/config/rpz/rpz.tr.pl b/config/rpz/rpz.tr.pl
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..00226e192
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/rpz.tr.pl
>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>> +# I�in eklendi Ayriyeten Response Policy Zone (RPZ)
>>>>>>>> +%tr = (%tr,
>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>> +'rpz apply' => 'Uygulamak',
>>>>>>>> +'rpz cl allow enable' => '�zel Etkinlestir allowlist:',
>>>>>>>> +'rpz cl allow info' => 'Izin verilmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>>> +'rpz cl allow' => 'Etkinlestir allowlist',
>>>>>>>> +'rpz cl block enable' => '�zel Etkinlestir blocklist:',
>>>>>>>> +'rpz cl block info' => 'Engellenmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>>> +'rpz cl block' => 'Engellenmis blocklist',
>>>>>>>> +'rpz cl' => 'Engellenmis lists',
>>>>>>>> +'rpz exitcode 101' => 'NAME ge�erli degil',
>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ge�ersiz yapilandirma bulundu. Terminalde daha fazla bilgi i�in Unbound-checkConf komutunu �alistirin',
>>>>>>>> +'rpz exitcode 103' => 'allow/block liste bos',
>>>>>>>> +'rpz exitcode 104' => 'kopyalamak - NAME zaten var',
>>>>>>>> +'rpz exitcode 105' => 'URL ge�erli degil',
>>>>>>>> +'rpz exitcode 106' => '�ikarilamiyor NAME yok',
>>>>>>>> +'rpz exitcode 107' => 'NAME ge�erli degil - "allow" veya "block" yalniz',
>>>>>>>> +'rpz exitcode 108' => 'Parametre eksik veya yanlis',
>>>>>>>> +'rpz exitcode 109' => 'unbound-control basarisiz',
>>>>>>>> +'rpz exitcode 110' => 'Engellenmis Allowlist/Blocklist ge�ersiz girisler i�erir',
>>>>>>>> +'rpz exitcode 201' => 'REMARK ge�erli degil',
>>>>>>>> +'rpz exitcode 202' => 'Ge�ersiz giris allowlist, line ',
>>>>>>>> +'rpz exitcode 203' => 'Ge�ersiz giris blocklist, line ',
>>>>>>>> +'rpz exitcode 204' => 'Se�ilen giris yok: ',
>>>>>>>> +'rpz zf editor' => 'yazimlamak zonefiles giris',
>>>>>>>> +'rpz zf imported' => '(ithal edildi rpz-config)',
>>>>>>>> +'rpz zf remark info' => 'Ge�erli karakterler a-z, A-Z, 0-9ve alt�st.',
>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>> +);
>>>>>>>> diff --git a/html/cgi-bin/rpz.cgi b/html/cgi-bin/rpz.cgi
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..a821c92ac
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/html/cgi-bin/rpz.cgi
>>>>>>>> @@ -0,0 +1,923 @@
>>>>>>>> +#!/usr/bin/perl
>>>>>>>> +###############################################################################
>>>>>>>> +# #
>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>> +# Copyright (C) 2005-2024 IPFire Team <info(a)ipfire.org> #
>>>>>>>> +# #
>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>> +# #
>>>>>>>> +###############################################################################
>>>>>>>> +
>>>>>>>> +use strict;
>>>>>>>> +use Scalar::Util qw(looks_like_number);
>>>>>>>> +
>>>>>>>> +# debugging
>>>>>>>> +#use warnings;
>>>>>>>> +#use CGI::Carp 'fatalsToBrowser';
>>>>>>>> +#use Data::Dumper;
>>>>>>>> +
>>>>>>>> +require '/var/ipfire/general-functions.pl';
>>>>>>>> +require "${General::swroot}/lang.pl";
>>>>>>>> +require "${General::swroot}/header.pl";
>>>>>>>> +
>>>>>>>> +###--- Extra HTML ---###
>>>>>>>> +my $extraHead = <<END
>>>>>>>> +<style>
>>>>>>>> + /* alternating row background */
>>>>>>>> + .tbl tr:nth-child(2n+2) {
>>>>>>>> + background-color: var(--color-light-grey);
>>>>>>>> + }
>>>>>>>> + .tbl tr:nth-child(2n+3) {
>>>>>>>> + background-color: var(--color-grey);
>>>>>>>> + }
>>>>>>>> + /* text styles */
>>>>>>>> + .tbl th:not(:last-child) {
>>>>>>>> + text-align: left;
>>>>>>>> + }
>>>>>>>> + div.right {
>>>>>>>> + text-align: right;
>>>>>>>> + margin-top: 0.5em;
>>>>>>>> + }
>>>>>>>> + /* customlist input */
>>>>>>>> + textarea.domainlist {
>>>>>>>> + margin: 0.5em 0;
>>>>>>>> + resize: vertical;
>>>>>>>> + min-height: 10em;
>>>>>>>> + overflow: auto;
>>>>>>>> + white-space: pre;
>>>>>>>> + }
>>>>>>>> + button[type=submit]:disabled {
>>>>>>>> + opacity: 0.6;
>>>>>>>> + }
>>>>>>>> +</style>
>>>>>>>> +END
>>>>>>>> +;
>>>>>>>> +###--- End of extra HTML ---###
>>>>>>>> +
>>>>>>>> +
>>>>>>>> +### Settings ###
>>>>>>>> +
>>>>>>>> +# Request DNS service reload after configuration change
>>>>>>>> +my $RPZ_RELOAD_FLAG = "${General::swroot}/dns/rpz/reload.flag";
>>>>>>>> +
>>>>>>>> +# Configuration file for all available zonefiles
>>>>>>>> +# Format: index, name (unique), enabled (on/off), URL, remark
>>>>>>>> +my $ZONEFILES_CONF = "${General::swroot}/dns/rpz/zonefiles.conf";
>>>>>>>> +
>>>>>>>> +# Configuration file for custom lists
>>>>>>>> +# IDs: 0=allowlist, 1=blocklist, 2=options (allow/block enabled)
>>>>>>>> +my $CUSTOMLISTS_CONF = "${General::swroot}/dns/rpz/customlists.conf";
>>>>>>>> +
>>>>>>>> +# Export custom lists to rpz-config
>>>>>>>> +my $RPZ_ALLOWLIST = "${General::swroot}/dns/rpz/allowlist";
>>>>>>>> +my $RPZ_BLOCKLIST = "${General::swroot}/dns/rpz/blocklist";
>>>>>>>> +
>>>>>>>> +
>>>>>>>> +### Preparation ###
>>>>>>>> +
>>>>>>>> +# Create missing config files
>>>>>>>> +unless(-f $ZONEFILES_CONF) { &General::system('touch', "$ZONEFILES_CONF"); }
>>>>>>>> +unless(-f $CUSTOMLISTS_CONF) { &General::system('touch', "$CUSTOMLISTS_CONF"); }
>>>>>>>> +
>>>>>>>> +
>>>>>>>> +## Global gui data
>>>>>>>> +my $errormessage = "";
>>>>>>>> +
>>>>>>>> +## Global configuration data
>>>>>>>> +my %zonefiles = ();
>>>>>>>> +my %customlists = ();
>>>>>>>> +&_zonefiles_load();
>>>>>>>> +&_customlists_load();
>>>>>>>> +
>>>>>>>> +## Global CGI form data
>>>>>>>> +my %cgiparams = ();
>>>>>>>> +&Header::getcgihash(\%cgiparams);
>>>>>>>> +
>>>>>>>> +my $action = $cgiparams{'ACTION'} // 'NONE';
>>>>>>>> +my $action_key = $cgiparams{'KEY'} // ''; # entry being edited, empty = none/new
>>>>>>>> +
>>>>>>>> +
>>>>>>>> +###--- Process form actions ---###
>>>>>>>> +
>>>>>>>> +# Zonefiles action: Check whether the requested entry exists
>>>>>>>> +if((substr($action, 0, 3) eq 'ZF_') && ($action_key)) {
>>>>>>>> + unless(defined $zonefiles{$action_key}) {
>>>>>>>> + $errormessage = &_rpz_error_tr(204, $action_key);
>>>>>>>> + $action = 'NONE';
>>>>>>>> + }
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +## Perform actions
>>>>>>>> +if($action eq 'ZF_SAVE') { ## Save new or modified zonefiles entry
>>>>>>>> + if(&_action_zf_save()) {
>>>>>>>> + $action = 'NONE'; # success, return to main page
>>>>>>>> + &_http_prg_redirect();
>>>>>>>> + } else {
>>>>>>>> + $action = 'ZF_EDIT'; # error occured, keep editing
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> +} elsif($action eq 'ZF_TOGGLE') { ## Toggle on/off
>>>>>>>> + if(&_action_zf_toggle()) {
>>>>>>>> + $action = 'NONE';
>>>>>>>> + &_http_prg_redirect();
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> +} elsif($action eq 'ZF_REMOVE') { ## Remove entry
>>>>>>>> + if(&_action_zf_remove()) {
>>>>>>>> + $action = 'NONE';
>>>>>>>> + &_http_prg_redirect();
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> +} elsif($action eq 'CL_SAVE') { ## Save custom lists
>>>>>>>> + if(&_action_cl_save()) {
>>>>>>>> + $action = 'NONE';
>>>>>>>> + &_http_prg_redirect();
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> +} elsif($action eq 'RPZ_RELOAD') { ## Reload dns configuration
>>>>>>>> + if(&_action_rpz_reload()) {
>>>>>>>> + $action = 'NONE';
>>>>>>>> + &_http_prg_redirect();
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> +} elsif($action eq 'UNB_RESTART') { ## Restart unbound service
>>>>>>>> + if(&_action_unb_restart()) {
>>>>>>>> + $action = 'NONE';
>>>>>>>> + &_http_prg_redirect();
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +
>>>>>>>> +###--- Start GUI ---###
>>>>>>>> +
>>>>>>>> +## Start http output
>>>>>>>> +&Header::showhttpheaders();
>>>>>>>> +
>>>>>>>> +# Start HTML
>>>>>>>> +&Header::openpage($Lang::tr{'rpz'}, 1, $extraHead);
>>>>>>>> +&Header::openbigbox('100%', 'left', '');
>>>>>>>> +
>>>>>>>> +# Show error messages
>>>>>>>> +if($errormessage) {
>>>>>>>> + &_print_message($errormessage);
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Handle zonefile add/edit mode
>>>>>>>> +if($action eq "ZF_EDIT") {
>>>>>>>> + &_print_zonefile_editor();
>>>>>>>> +
>>>>>>>> + # Finalize page and exit cleanly
>>>>>>>> + &Header::closebigbox();
>>>>>>>> + &Header::closepage();
>>>>>>>> + exit(0);
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Show gui elements
>>>>>>>> +&_print_zonefiles();
>>>>>>>> +&_print_customlists();
>>>>>>>> +&_print_gui_extras();
>>>>>>>> +
>>>>>>>> +&Header::closebigbox();
>>>>>>>> +&Header::closepage();
>>>>>>>> +
>>>>>>>> +###--- End of GUI ---###
>>>>>>>> +
>>>>>>>> +
>>>>>>>> +###--- Internal configuration file functions ---###
>>>>>>>> +
>>>>>>>> +# Load all available zonefiles from rpz-config and the internal configuration
>>>>>>>> +sub _zonefiles_load {
>>>>>>>> + # Clean start
>>>>>>>> + %zonefiles = ();
>>>>>>>> +
>>>>>>>> + # Source 1: Get the currently enabled zonefiles from rpz-config (expected format [name]=[URL])
>>>>>>>> + my @enabled_files = &General::system_output('/usr/sbin/rpz-config', 'list');
>>>>>>>> +
>>>>>>>> + foreach my $row (@enabled_files) {
>>>>>>>> + chomp($row);
>>>>>>>> +
>>>>>>>> + # Use regex instead of split() to skip non-matching lines
>>>>>>>> + next unless($row =~ /^(\w+)=(.+)$/);
>>>>>>>> + my ($name, $url) = ($1, $2);
>>>>>>>> +
>>>>>>>> + # Unique names are already guaranteed by rpz-config
>>>>>>>> + if(&_rpz_validate_zonefile($name, $url, '', 0) == 0) {
>>>>>>>> + # Populate global data hash, mark all found entries as enabled
>>>>>>>> + my %entry = ('enabled' => 'on',
>>>>>>>> + 'url' => $url,
>>>>>>>> + 'remark' => $Lang::tr{'rpz zf imported'});
>>>>>>>> +
>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>> + }
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Source 2: Get additional data and disabled entries from configuration file
>>>>>>>> + my %configured_files = ();
>>>>>>>> + &General::readhasharray($ZONEFILES_CONF, \%configured_files);
>>>>>>>> +
>>>>>>>> + foreach my $row (values (%configured_files)) {
>>>>>>>> + my ($name, $enabled, $url, $remark) = @$row;
>>>>>>>> + $remark //= "";
>>>>>>>> +
>>>>>>>> + next unless($name);
>>>>>>>> +
>>>>>>>> + # Check whether this row belongs to an entry already imported from rpz-config
>>>>>>>> + if(defined $zonefiles{$name}) {
>>>>>>>> + # Existing entry, only merge additional data
>>>>>>>> + $zonefiles{$name}{'remark'} = $remark;
>>>>>>>> + } else {
>>>>>>>> + # Skip entry if it is marked as enabled but not found by rpz-config. It was then deleted manually
>>>>>>>> + if($enabled ne 'on') {
>>>>>>>> + # Populate global data hash
>>>>>>>> + my %entry = ('enabled' => 'off',
>>>>>>>> + 'url' => $url // "",
>>>>>>>> + 'remark' => $remark);
>>>>>>>> +
>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>> + }
>>>>>>>> + }
>>>>>>>> + }
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Save internal zonefiles configuration
>>>>>>>> +sub _zonefiles_save_conf {
>>>>>>>> + my $index = 0;
>>>>>>>> + my %export = ();
>>>>>>>> +
>>>>>>>> + # Loop trough all zonefiles and create "hasharray" type export
>>>>>>>> + foreach my $name (keys %zonefiles) {
>>>>>>>> + my @entry = ($name,
>>>>>>>> + $zonefiles{$name}{'enabled'},
>>>>>>>> + $zonefiles{$name}{'url'},
>>>>>>>> + $zonefiles{$name}{'remark'});
>>>>>>>> +
>>>>>>>> + $export{$index++} = \@entry;
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + &General::writehasharray($ZONEFILES_CONF, \%export);
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Load custom lists from rpz-config and the internal configuration
>>>>>>>> +sub _customlists_load {
>>>>>>>> + # Clean start
>>>>>>>> + %customlists = ();
>>>>>>>> +
>>>>>>>> + # Load configuration file
>>>>>>>> + my %lists_conf = ();
>>>>>>>> + &General::readhasharray($CUSTOMLISTS_CONF, \%lists_conf);
>>>>>>>> +
>>>>>>>> + # Get list options, enabled by default to start import
>>>>>>>> + $customlists{'allow'}{'enabled'} = $lists_conf{2}[0] // 'on';
>>>>>>>> + $customlists{'block'}{'enabled'} = $lists_conf{2}[1] // 'on';
>>>>>>>> +
>>>>>>>> + # Import enabled list from rpz-config, otherwise retrieve stored or empty list from configuration file
>>>>>>>> + if($customlists{'allow'}{'enabled'} eq 'on') {
>>>>>>>> + &_customlist_import('allow', $RPZ_ALLOWLIST);
>>>>>>>> + } else {
>>>>>>>> + $customlists{'allow'}{'list'} = $lists_conf{0} // [];
>>>>>>>> + }
>>>>>>>> + if($customlists{'block'}{'enabled'} eq 'on') {
>>>>>>>> + &_customlist_import('block', $RPZ_BLOCKLIST);
>>>>>>>> + } else {
>>>>>>>> + $customlists{'block'}{'list'} = $lists_conf{1} // [];
>>>>>>>> + }
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Save internal custom lists configuration
>>>>>>>> +sub _customlists_save_conf {
>>>>>>>> + my %export = ();
>>>>>>>> +
>>>>>>>> + # Match IDs with import function
>>>>>>>> + $export{0} = $customlists{'allow'}{'list'};
>>>>>>>> + $export{1} = $customlists{'block'}{'list'};
>>>>>>>> + $export{2} = [$customlists{'allow'}{'enabled'}, $customlists{'block'}{'enabled'}];
>>>>>>>> +
>>>>>>>> + &General::writehasharray($CUSTOMLISTS_CONF, \%export);
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Import a custom list from plain file, returns empty list if file is missing
>>>>>>>> +sub _customlist_import {
>>>>>>>> + my ($listname, $filename) = @_;
>>>>>>>> + my @list = ();
>>>>>>>> +
>>>>>>>> + # File exists, load and check all lines
>>>>>>>> + if(-f $filename) {
>>>>>>>> + open(my $FH, '<', $filename) or die "Can't read $filename: $!";
>>>>>>>> + while(my $line = <$FH>) {
>>>>>>>> + chomp($line);
>>>>>>>> + push(@list, $line);
>>>>>>>> + }
>>>>>>>> + close($FH);
>>>>>>>> +
>>>>>>>> + # Clean up imported data
>>>>>>>> + &_rpz_validate_customlist(\@list, 1);
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + $customlists{$listname}{'list'} = \@list;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Export a custom list to plain file or clear file if list is disabled
>>>>>>>> +sub _customlist_export {
>>>>>>>> + my ($listname, $filename) = @_;
>>>>>>>> + return unless(defined $customlists{$listname});
>>>>>>>> +
>>>>>>>> + # Write enabled domain list to file, otherwise save empty file
>>>>>>>> + open(my $FH, '>', $filename) or die "Can't write $filename: $!";
>>>>>>>> +
>>>>>>>> + if($customlists{$listname}{'enabled'} eq 'on') {
>>>>>>>> + foreach my $line (@{$customlists{$listname}{'list'}}) {
>>>>>>>> + print $FH "$line\n";
>>>>>>>> + }
>>>>>>>> + } else {
>>>>>>>> + print $FH "; Note: This list is currently disabled by $ENV{'SCRIPT_NAME'}\n";
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + close($FH);
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +
>>>>>>>> +###--- Internal gui functions ---###
>>>>>>>> +
>>>>>>>> +# Show simple message box
>>>>>>>> +sub _print_message {
>>>>>>>> + my ($message, $title) = @_;
>>>>>>>> + $title ||= $Lang::tr{'error messages'};
>>>>>>>> +
>>>>>>>> + &Header::openbox('100%', 'left', $title);
>>>>>>>> + print "<span>$message</span>";
>>>>>>>> + &Header::closebox();
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Show all zone files and related gui elements
>>>>>>>> +sub _print_zonefiles {
>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf'});
>>>>>>>> +
>>>>>>>> + print <<END
>>>>>>>> +<table class="tbl" width="100%">
>>>>>>>> + <tr>
>>>>>>>> + <th>$Lang::tr{'name'}</th>
>>>>>>>> + <th>URL</th>
>>>>>>>> + <th>$Lang::tr{'remark'}</th>
>>>>>>>> + <th colspan="3">$Lang::tr{'action'}</th>
>>>>>>>> + </tr>
>>>>>>>> +END
>>>>>>>> +;
>>>>>>>> +
>>>>>>>> + # Sort zonefiles by name and loop trough all entries
>>>>>>>> + foreach my $name (sort keys %zonefiles) {
>>>>>>>> +
>>>>>>>> + # Toggle button label translation
>>>>>>>> + my $toggle_tr = ($zonefiles{$name}{'enabled'} eq 'on') ? $Lang::tr{'click to disable'} : $Lang::tr{'click to enable'};
>>>>>>>> +
>>>>>>>> + print <<END
>>>>>>>> + <tr>
>>>>>>>> + <td>$name</td>
>>>>>>>> + <td>$zonefiles{$name}{'url'}</td>
>>>>>>>> + <td>$zonefiles{$name}{'remark'}</td>
>>>>>>>> +
>>>>>>>> + <td align="center" width="5%">
>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_TOGGLE">
>>>>>>>> + <input type="image" src="/images/$zonefiles{$name}{'enabled'}.gif" title="$toggle_tr" alt="$toggle_tr">
>>>>>>>> + </form>
>>>>>>>> + </td>
>>>>>>>> + <td align="center" width="5%">
>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_EDIT">
>>>>>>>> + <input type="image" src="/images/edit.gif" title="$Lang::tr{'edit'}" alt="$Lang::tr{'edit'}">
>>>>>>>> + </form>
>>>>>>>> + </td>
>>>>>>>> + <td align="center" width="5%">
>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_REMOVE">
>>>>>>>> + <input type="image" src="/images/delete.gif" title="$Lang::tr{'remove'}" alt="$Lang::tr{'remove'}">
>>>>>>>> + </form>
>>>>>>>> + </td>
>>>>>>>> + </tr>
>>>>>>>> +END
>>>>>>>> +;
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Disable reload button if not needed
>>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>>> +
>>>>>>>> + print <<END
>>>>>>>> +</table>
>>>>>>>> +
>>>>>>>> +<div class="right">
>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>> + <input type="hidden" name="KEY" value="">
>>>>>>>> + <button type="submit" name="ACTION" value="ZF_EDIT">$Lang::tr{'add'}</button>
>>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>>> + </form>
>>>>>>>> +</div>
>>>>>>>> +END
>>>>>>>> +;
>>>>>>>> +
>>>>>>>> + &Header::closebox();
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Show zonefiles entry editor
>>>>>>>> +sub _print_zonefile_editor {
>>>>>>>> +
>>>>>>>> + # Key specified: Edit existing entry
>>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>>> + # Load data to be edited, but don't override already present values (allows user to edit after error)
>>>>>>>> + $cgiparams{'ZF_NAME'} //= $action_key;
>>>>>>>> + $cgiparams{'ZF_URL'} //= $zonefiles{$action_key}{'url'};
>>>>>>>> + $cgiparams{'ZF_REMARK'} //= $zonefiles{$action_key}{'remark'};
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Fallback to empty form
>>>>>>>> + $cgiparams{'ZF_NAME'} //= "";
>>>>>>>> + $cgiparams{'ZF_URL'} //= "";
>>>>>>>> + $cgiparams{'ZF_REMARK'} //= "";
>>>>>>>> +
>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf editor'});
>>>>>>>> +
>>>>>>>> + print <<END
>>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>> +<input type="hidden" name="KEY" value="$action_key">
>>>>>>>> +<table width="100%">
>>>>>>>> + <tr>
>>>>>>>> + <td width="20%">$Lang::tr{'name'}: <img src="/blob.gif" alt="*"></td>
>>>>>>>> + <td><input type="text" name="ZF_NAME" value="$cgiparams{'ZF_NAME'}" size="40" maxlength="32" title="$Lang::tr{'rpz zf remark info'}" pattern="[a-zA-Z0-9_]{1,32}" required></td>
>>>>>>>> + </tr>
>>>>>>>> + <tr>
>>>>>>>> + <td width="20%">URL: <img src="/blob.gif" alt="*"></td>
>>>>>>>> + <td><input type="url" name="ZF_URL" value="$cgiparams{'ZF_URL'}" size="40" maxlength="128" required></td>
>>>>>>>> + </tr>
>>>>>>>> + <tr>
>>>>>>>> + <td width="20%">$Lang::tr{'remark'}:</td>
>>>>>>>> + <td><input type="text" name="ZF_REMARK" value="$cgiparams{'ZF_REMARK'}" size="40" maxlength="32"></td>
>>>>>>>> + </tr>
>>>>>>>> + <tr>
>>>>>>>> + <td colspan="2"><hr></td>
>>>>>>>> + </tr>
>>>>>>>> + <tr>
>>>>>>>> + <td width="55%"><img src="/blob.gif" alt="*"> $Lang::tr{'required field'}</td>
>>>>>>>> + <td align="right"><button type="submit" name="ACTION" value="ZF_SAVE">$Lang::tr{'save'}</button></td>
>>>>>>>> + </tr>
>>>>>>>> +</table>
>>>>>>>> +</form>
>>>>>>>> +
>>>>>>>> +<div class="right">
>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>> + <button type="submit" name="ACTION" value="NONE">$Lang::tr{'back'}</button>
>>>>>>>> + </form>
>>>>>>>> +</div>
>>>>>>>> +END
>>>>>>>> +;
>>>>>>>> +
>>>>>>>> + &Header::closebox();
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Show custom allow/block files and related gui elements
>>>>>>>> +sub _print_customlists {
>>>>>>>> +
>>>>>>>> + # Load lists from config, unless they are currently being edited
>>>>>>>> + if($action ne 'CL_SAVE') {
>>>>>>>> + $cgiparams{'ALLOW_LIST'} = join("\n", @{$customlists{'allow'}{'list'}});
>>>>>>>> + $cgiparams{'BLOCK_LIST'} = join("\n", @{$customlists{'block'}{'list'}});
>>>>>>>> +
>>>>>>>> + $cgiparams{'ALLOW_ENABLED'} = ($customlists{'allow'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>>> + $cgiparams{'BLOCK_ENABLED'} = ($customlists{'block'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Fallback to empty form
>>>>>>>> + $cgiparams{'ALLOW_LIST'} //= "";
>>>>>>>> + $cgiparams{'BLOCK_LIST'} //= "";
>>>>>>>> +
>>>>>>>> + # HTML checkboxes, unchecked = no or undef value in POST data
>>>>>>>> + my %checked = ();
>>>>>>>> + $checked{'ALLOW_ENABLED'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? " checked" : "";
>>>>>>>> + $checked{'BLOCK_ENABLED'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? " checked" : "";
>>>>>>>> +
>>>>>>>> + # Disable reload button if not needed
>>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>>> +
>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz cl'});
>>>>>>>> +
>>>>>>>> + print <<END
>>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>> +<table width="100%">
>>>>>>>> + <tr>
>>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl allow'}</b><br>$Lang::tr{'rpz cl allow info'}</td>
>>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl block'}</b><br>$Lang::tr{'rpz cl block info'}</td>
>>>>>>>> + </tr>
>>>>>>>> + <tr>
>>>>>>>> + <td colspan="2"><textarea name="ALLOW_LIST" class="domainlist" cols="45">
>>>>>>>> +$cgiparams{'ALLOW_LIST'}</textarea></td>
>>>>>>>> + <td colspan="2"><textarea name="BLOCK_LIST" class="domainlist" cols="45">
>>>>>>>> +$cgiparams{'BLOCK_LIST'}</textarea></td>
>>>>>>>> + </tr>
>>>>>>>> + <tr>
>>>>>>>> + <td><label for="allow_enabled">$Lang::tr{'rpz cl allow enable'}</label></td>
>>>>>>>> + <td width="15%"><input type="checkbox" name="ALLOW_ENABLED" id="allow_enabled"$checked{'ALLOW_ENABLED'}></td>
>>>>>>>> + <td><label for="block_enabled">$Lang::tr{'rpz cl block enable'}</label></td>
>>>>>>>> + <td width="15%"><input type="checkbox" name="BLOCK_ENABLED" id="block_enabled"$checked{'BLOCK_ENABLED'}></td>
>>>>>>>> + </tr>
>>>>>>>> + <tr>
>>>>>>>> + <td colspan="4"><hr></td>
>>>>>>>> + </tr>
>>>>>>>> + <tr>
>>>>>>>> + <td align="right" colspan="4">
>>>>>>>> + <button type="submit" name="ACTION" value="CL_SAVE">$Lang::tr{'save'}</button>
>>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>>> + </td>
>>>>>>>> +</table>
>>>>>>>> +</form>
>>>>>>>> +END
>>>>>>>> +;
>>>>>>>> +
>>>>>>>> + &Header::closebox();
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Output javascript and extra gui elements
>>>>>>>> +sub _print_gui_extras {
>>>>>>>> +
>>>>>>>> + # Apply/Restart button modifier key handler
>>>>>>>> + if(&_rpz_needs_reload()) {
>>>>>>>> + print <<END
>>>>>>>> +<script>
>>>>>>>> + // Commit modifier key handler
>>>>>>>> + (function(jq, document) {
>>>>>>>> + var keyEventsOn = false; // Keyboard events attached
>>>>>>>> + var keyModify = false; // Modifier key pressed
>>>>>>>> + var mouseHover = false; // Mouse over commit button
>>>>>>>> + var btnModified = false; // Button modified to "Restart"
>>>>>>>> +
>>>>>>>> + // Document-level key events, enable only while cursor is over button
>>>>>>>> + function attachKeyEvents() {
>>>>>>>> + if(keyEventsOn) {
>>>>>>>> + return;
>>>>>>>> + }
>>>>>>>> + keyEventsOn = true;
>>>>>>>> +
>>>>>>>> + jq(document).on("keydown.rpz", function(event) {
>>>>>>>> + if((!keyModify) && event.shiftKey) {
>>>>>>>> + keyModify = true;
>>>>>>>> + handleModify();
>>>>>>>> + }
>>>>>>>> + });
>>>>>>>> + jq(document).on("keyup.rpz", function(event) {
>>>>>>>> + if(keyModify && (!event.shiftKey)) {
>>>>>>>> + keyModify = false;
>>>>>>>> + handleModify();
>>>>>>>> + }
>>>>>>>> + });
>>>>>>>> + }
>>>>>>>> + function removeKeyEvents() {
>>>>>>>> + keyModify = false;
>>>>>>>> + if(keyEventsOn) {
>>>>>>>> + jq(document).off("keydown.rpz keyup.rpz");
>>>>>>>> + keyEventsOn = false;
>>>>>>>> + }
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + // Attach mouse hover events to commit buttons
>>>>>>>> + function attachMouseEvents() {
>>>>>>>> + jq("button.commit").on("mouseenter", function(event) {
>>>>>>>> + if(!mouseHover) {
>>>>>>>> + mouseHover = true;
>>>>>>>> + attachKeyEvents();
>>>>>>>> + // Handle already pressed key
>>>>>>>> + keyModify = !!(event.shiftKey);
>>>>>>>> + handleModify();
>>>>>>>> + }
>>>>>>>> + });
>>>>>>>> +
>>>>>>>> + // Cursor moved away: Disable key listener to minimize events
>>>>>>>> + jq("button.commit").on("mouseleave", function() {
>>>>>>>> + if(mouseHover) {
>>>>>>>> + mouseHover = false;
>>>>>>>> + removeKeyEvents();
>>>>>>>> + handleModify();
>>>>>>>> + }
>>>>>>>> + });
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + // Modify commit button
>>>>>>>> + function handleModify() {
>>>>>>>> + let modify = mouseHover && keyModify;
>>>>>>>> + if(btnModified != modify) {
>>>>>>>> + if(modify) {
>>>>>>>> + jq("button.commit").text("$Lang::tr{'restart'}").val("UNB_RESTART");
>>>>>>>> + } else {
>>>>>>>> + jq("button.commit").text("$Lang::tr{'rpz apply'}").val("RPZ_RELOAD");
>>>>>>>> + }
>>>>>>>> + btnModified = modify;
>>>>>>>> + }
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + // jQuery DOM ready
>>>>>>>> + jq(function() {
>>>>>>>> + attachMouseEvents();
>>>>>>>> + });
>>>>>>>> + })(jQuery, document);
>>>>>>>> +</script>
>>>>>>>> +END
>>>>>>>> +;
>>>>>>>> + } # End of modifier key handler
>>>>>>>> +
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +
>>>>>>>> +###--- Internal action processing functions ---###
>>>>>>>> +
>>>>>>>> +# Toggle zonefile on/off
>>>>>>>> +sub _action_zf_toggle {
>>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>>> +
>>>>>>>> + my $result = 0;
>>>>>>>> + my $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>>> +
>>>>>>>> + # Perform toggle action
>>>>>>>> + if($enabled eq 'on') {
>>>>>>>> + $enabled = 'off';
>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>>> + } else {
>>>>>>>> + $enabled = 'on';
>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $action_key, $zonefiles{$action_key}{'url'}, '--no-reload');
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>> +
>>>>>>>> + # Save changes
>>>>>>>> + $zonefiles{$action_key}{'enabled'} = $enabled;
>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>> +
>>>>>>>> + return 1;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Remove zonefile
>>>>>>>> +sub _action_zf_remove {
>>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>>> +
>>>>>>>> + # Remove from rpz-config if currently active
>>>>>>>> + if($zonefiles{$action_key}{'enabled'} eq 'on') {
>>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>>> +
>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Remove from data hash and save changes
>>>>>>>> + delete $zonefiles{$action_key};
>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>> +
>>>>>>>> + # Clear action_key, as the entry is now removed entirely
>>>>>>>> + $action_key = "";
>>>>>>>> +
>>>>>>>> + return 1;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Create or update zonefile entry
>>>>>>>> +# Returns undef if gui needs to stay in editor mode
>>>>>>>> +sub _action_zf_save {
>>>>>>>> + my $result = 0;
>>>>>>>> +
>>>>>>>> + my $name = $cgiparams{'ZF_NAME'} // "";
>>>>>>>> + my $url = $cgiparams{'ZF_URL'} // "";
>>>>>>>> + my $remark = $cgiparams{'ZF_REMARK'} // "";
>>>>>>>> + my $enabled = 'on'; # Enable new entries by default
>>>>>>>> +
>>>>>>>> + # Note on variables:
>>>>>>>> + # name = unique key, will be used to address the entry
>>>>>>>> + # action_key = name of the entry being edited, empty for new entry
>>>>>>>> +
>>>>>>>> + # Only check for unique name if it changed
>>>>>>>> + # (this also checks new entries because the action_key is empty in this case)
>>>>>>>> + $result = &_rpz_validate_zonefile($name, $url, $remark, (lc($name) ne lc($action_key)));
>>>>>>>> + return unless &_rpz_check_result($result, 0);
>>>>>>>> +
>>>>>>>> + # Edit existing entry: Determine what was changed
>>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>>> + # Name und URL remain unchanged, only save remark and finish
>>>>>>>> + if(($name eq $action_key) && ($url eq $zonefiles{$action_key}{'url'})) {
>>>>>>>> + $zonefiles{$action_key}{'remark'} = $remark;
>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>> +
>>>>>>>> + return 1;
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Entry was changed and needs to be recreated, preserve status
>>>>>>>> + $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>>> +
>>>>>>>> + # Remove from rpz-config
>>>>>>>> + return unless &_action_zf_remove();
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Add new entry to rpz-config
>>>>>>>> + if($enabled eq 'on') {
>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $name, $url, '--no-reload');
>>>>>>>> +
>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Add to global data hash and save changes
>>>>>>>> + my %entry = ('enabled' => $enabled,
>>>>>>>> + 'url' => $url,
>>>>>>>> + 'remark' => $remark);
>>>>>>>> +
>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>> +
>>>>>>>> + return 1;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Save custom lists
>>>>>>>> +sub _action_cl_save {
>>>>>>>> + return unless((defined $cgiparams{'ALLOW_LIST'}) && (defined $cgiparams{'BLOCK_LIST'}));
>>>>>>>> +
>>>>>>>> + my $result = 0;
>>>>>>>> +
>>>>>>>> + my @allowlist = split(/\R/, $cgiparams{'ALLOW_LIST'});
>>>>>>>> + my @blocklist = split(/\R/, $cgiparams{'BLOCK_LIST'});
>>>>>>>> +
>>>>>>>> + # Validate lists
>>>>>>>> + $result = &_rpz_validate_customlist(\@allowlist);
>>>>>>>> + if($result != 0) {
>>>>>>>> + $errormessage = &_rpz_error_tr(202, $result);
>>>>>>>> + return;
>>>>>>>> + }
>>>>>>>> + $result = &_rpz_validate_customlist(\@blocklist);
>>>>>>>> + if($result != 0) {
>>>>>>>> + $errormessage = &_rpz_error_tr(203, $result);
>>>>>>>> + return;
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Add to global data hash and save changes
>>>>>>>> + $customlists{'allow'}{'list'} = \@allowlist;
>>>>>>>> + $customlists{'block'}{'list'} = \@blocklist;
>>>>>>>> + $customlists{'allow'}{'enabled'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? 'on' : 'off';
>>>>>>>> + $customlists{'block'}{'enabled'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? 'on' : 'off';
>>>>>>>> +
>>>>>>>> + &_customlists_save_conf();
>>>>>>>> + &_customlist_export('allow', $RPZ_ALLOWLIST);
>>>>>>>> + &_customlist_export('block', $RPZ_BLOCKLIST);
>>>>>>>> +
>>>>>>>> + # Make new lists, request service reload on success
>>>>>>>> + $result = &General::system('/usr/sbin/rpz-make', 'allowblock', '--no-reload');
>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>> +
>>>>>>>> + return 1;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Trigger rpz-config reload
>>>>>>>> +sub _action_rpz_reload {
>>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>>> +
>>>>>>>> + # Immediately clear flag to prevent multiple reloads
>>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Perform reload, recreate reload flag on error to enable retry
>>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'reload');
>>>>>>>> + if(not &_rpz_check_result($result, 0)) {
>>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>>> + return;
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + return 1;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Trigger unbound restart
>>>>>>>> +sub _action_unb_restart {
>>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>>> +
>>>>>>>> + # Immediately clear flag to prevent multiple restarts
>>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Perform restart, unboundctrl always exits zero
>>>>>>>> + &General::system('/usr/local/bin/unboundctrl', 'restart');
>>>>>>>> +
>>>>>>>> + return 1;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +
>>>>>>>> +###--- Internal rpz-config functions ---###
>>>>>>>> +
>>>>>>>> +# Translate rpz-config exitcodes and messages
>>>>>>>> +# 100-199: rpz-config, 200-299: webgui
>>>>>>>> +sub _rpz_error_tr {
>>>>>>>> + my ($error, $append) = @_;
>>>>>>>> + $append //= '';
>>>>>>>> +
>>>>>>>> + # Translate numeric exit codes
>>>>>>>> + if(looks_like_number($error)) {
>>>>>>>> + if(defined $Lang::tr{"rpz exitcode $error"}) {
>>>>>>>> + $error = $Lang::tr{"rpz exitcode $error"};
>>>>>>>> + }
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + return "RPZ $Lang::tr{'error'}: $error" . &Header::escape($append);
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Check result of rpz-config system call, request reload on success
>>>>>>>> +sub _rpz_check_result {
>>>>>>>> + my ($result, $request_reload) = @_;
>>>>>>>> + $request_reload //= 0;
>>>>>>>> +
>>>>>>>> + # exitcode 0 = success
>>>>>>>> + if($result != 0) {
>>>>>>>> + $errormessage = &_rpz_error_tr($result);
>>>>>>>> + return;
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Set reload flag
>>>>>>>> + if($request_reload) {
>>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + return 1;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Test whether reload flag is set
>>>>>>>> +sub _rpz_needs_reload {
>>>>>>>> + return (-f $RPZ_RELOAD_FLAG);
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Validate a zonefile entry, returns rpz-config exitcode on failure. Use _rpz_check_result to verify.
>>>>>>>> +# unique = check for unique name
>>>>>>>> +sub _rpz_validate_zonefile {
>>>>>>>> + my ($name, $url, $remark, $unique) = @_;
>>>>>>>> + $unique //= 1;
>>>>>>>> +
>>>>>>>> + unless($name =~ /^[a-zA-Z0-9_]{1,32}$/) {
>>>>>>>> + return 101;
>>>>>>>> + }
>>>>>>>> + unless($url =~ /^[\w+\.:;\/\\&@#%?=\-~|!]{1,128}$/) {
>>>>>>>> + return 105;
>>>>>>>> + }
>>>>>>>> + unless($remark =~ /^[\w \-()\.:;*\/\\?!&=]{0,32}$/) {
>>>>>>>> + return 201;
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Check against already existing names
>>>>>>>> + if($unique) {
>>>>>>>> + foreach my $existing (keys %zonefiles) {
>>>>>>>> + if(lc($name) eq lc($existing)) {
>>>>>>>> + return 104;
>>>>>>>> + }
>>>>>>>> + }
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + return 0;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Validate a custom list, returns number of rejected line on failure. Check for non-zero results.
>>>>>>>> +# listref = array reference, cleanup = remove invalid entries instead of returning an error
>>>>>>>> +sub _rpz_validate_customlist {
>>>>>>>> + my ($listref, $cleanup) = @_;
>>>>>>>> + $cleanup //= 0;
>>>>>>>> +
>>>>>>>> + foreach my $index (reverse 0..$#{$listref}) {
>>>>>>>> + my $row = @$listref[$index];
>>>>>>>> + next unless($row); # Skip/allow empty lines
>>>>>>>> +
>>>>>>>> + # Reject/remove everything besides wildcard domains and remarks
>>>>>>>> + if((not &General::validwildcarddomainname($row)) && (not $row =~ /^;[\w \-()\.:;*\/\\?!&=]*$/)) {
>>>>>>>> + unless($cleanup) {
>>>>>>>> + # +1 for user friendly line number and to ensure non-zero exitcode
>>>>>>>> + return $index + 1;
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Remove current row
>>>>>>>> + splice(@$listref, $index, 1);
>>>>>>>> + }
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + return 0;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +
>>>>>>>> +###--- Internal misc functions ---###
>>>>>>>> +
>>>>>>>> +# Send HTTP 303 redirect headers for post/request/get pattern
>>>>>>>> +# (Must be sent before calling &Header::showhttpheaders())
>>>>>>>> +sub _http_prg_redirect {
>>>>>>>> + my $location = "https://$ENV{'SERVER_NAME'}:$ENV{'SERVER_PORT'}$ENV{'SCRIPT_NAME'}";
>>>>>>>> + print "Status: 303 See Other\n";
>>>>>>>> + print "Location: $location\n";
>>>>>>>> +}
>>>>>>>> diff --git a/lfs/rpz b/lfs/rpz
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..7ddbc38e5
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/lfs/rpz
>>>>>>>> @@ -0,0 +1,96 @@
>>>>>>>> +###############################################################################
>>>>>>>> +# #
>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>>>>> +# #
>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>> +# #
>>>>>>>> +###############################################################################
>>>>>>>> +
>>>>>>>> +###############################################################################
>>>>>>>> +# Definitions
>>>>>>>> +###############################################################################
>>>>>>>> +
>>>>>>>> +include Config
>>>>>>>> +
>>>>>>>> +SUMMARY = response policy zone - RPZ reputation system for unbound DNS
>>>>>>>> +
>>>>>>>> +VER = 1.0.0
>>>>>>>> +
>>>>>>>> +THISAPP = rpz-$(VER)
>>>>>>>> +DIR_APP = $(DIR_SRC)/$(THISAPP)
>>>>>>>> +TARGET = $(DIR_INFO)/$(THISAPP)
>>>>>>>> +
>>>>>>>> +PROG = rpz
>>>>>>>> +PAK_VER = 18
>>>>>>>> +
>>>>>>>> +DEPS =
>>>>>>>> +
>>>>>>>> +SERVICES =
>>>>>>>> +
>>>>>>>> +###############################################################################
>>>>>>>> +# Top-level Rules
>>>>>>>> +###############################################################################
>>>>>>>> +
>>>>>>>> +install : $(TARGET)
>>>>>>>> +
>>>>>>>> +check :
>>>>>>>> +
>>>>>>>> +download :
>>>>>>>> +
>>>>>>>> +b2 :
>>>>>>>> +
>>>>>>>> +dist:
>>>>>>>> + @$(PAK)
>>>>>>>> +
>>>>>>>> +###############################################################################
>>>>>>>> +# Installation Details
>>>>>>>> +###############################################################################
>>>>>>>> +
>>>>>>>> +$(TARGET) :
>>>>>>>> + @$(PREBUILD)
>>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>>> +
>>>>>>>> + # RPZ scripts
>>>>>>>> + install --verbose --mode=755 \
>>>>>>>> + $(DIR_CONF)/rpz/{rpz-config,rpz-metrics,rpz-sleep,rpz-make,rpz-functions} \
>>>>>>>> + --target-directory=/usr/sbin
>>>>>>>> +
>>>>>>>> + # RPZ config files
>>>>>>>> + mkdir -pv /etc/unbound/local.d
>>>>>>>> + install --verbose --mode=644 --owner=nobody --group=nobody \
>>>>>>>> + $(DIR_CONF)/rpz/00-rpz.conf \
>>>>>>>> + --target-directory=/etc/unbound/local.d
>>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/local.d
>>>>>>>> +
>>>>>>>> + # RPZ custom list files for allow and block
>>>>>>>> + mkdir -pv /var/ipfire/dns/rpz
>>>>>>>> + touch /var/ipfire/dns/rpz/{allowlist,blocklist}
>>>>>>>> + chown --verbose --recursive nobody:nobody /var/ipfire/dns/rpz
>>>>>>>> +
>>>>>>>> + # RPZ zone files
>>>>>>>> + # create empty RPZ config file to avoid a unbound config error
>>>>>>>> + mkdir -pv /etc/unbound/zonefiles
>>>>>>>> + touch /etc/unbound/zonefiles/allow.rpz
>>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/zonefiles
>>>>>>>> +
>>>>>>>> + # Install addon-specific language-files
>>>>>>>> + install --verbose --mode=004 $(DIR_CONF)/rpz/rpz.*.pl \
>>>>>>>> + --target-directory=/var/ipfire/addon-lang
>>>>>>>> +
>>>>>>>> + # Install backup definition
>>>>>>>> + cp -vf $(DIR_CONF)/backup/includes/rpz /var/ipfire/backup/addons/includes/rpz
>>>>>>>> +
>>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>>> + @$(POSTBUILD)
>>>>>>>> diff --git a/make.sh b/make.sh
>>>>>>>> index 827ea9e77..a77535b13 100755
>>>>>>>> --- a/make.sh
>>>>>>>> +++ b/make.sh
>>>>>>>> @@ -390,7 +390,7 @@ prepareenv() {
>>>>>>>> if [ "${free_space}" -lt "${required_space}" ]; then
>>>>>>>> # Add any consumed space
>>>>>>>> while read -r consumed_space path; do
>>>>>>>> - (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>>> + (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>>> done <<< "$(du --summarize --bytes "${BUILD_DIR}" "${IMAGES_DIR}" "${LOG_DIR}" 2>/dev/null)"
>>>>>>>> fi
>>>>>>>>
>>>>>>>> @@ -2087,6 +2087,7 @@ build_system() {
>>>>>>>> lfsmake2 btrfs-progs
>>>>>>>> lfsmake2 inotify-tools
>>>>>>>> lfsmake2 grub-btrfs
>>>>>>>> + lfsmake2 rpz
>>>>>>>>
>>>>>>>> lfsmake2 linux
>>>>>>>> lfsmake2 rtl8812au
>>>>>>>> diff --git a/src/paks/rpz/install.sh b/src/paks/rpz/install.sh
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..ef99bf742
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/src/paks/rpz/install.sh
>>>>>>>> @@ -0,0 +1,36 @@
>>>>>>>> +#!/bin/bash
>>>>>>>> +###############################################################################
>>>>>>>> +# #
>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>>>>> +# #
>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>> +# #
>>>>>>>> +###############################################################################
>>>>>>>> +#
>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>> +extract_files
>>>>>>>> +restore_backup ${NAME}
>>>>>>>> +
>>>>>>>> +# fix user created files
>>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>>> + /etc/unbound/zonefiles \
>>>>>>>> + /etc/unbound/local.d
>>>>>>>> +
>>>>>>>> +# Update Language cache
>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>> +
>>>>>>>> +# restart unbound to load config file
>>>>>>>> +/etc/init.d/unbound restart
>>>>>>>> diff --git a/src/paks/rpz/uninstall.sh b/src/paks/rpz/uninstall.sh
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..e11427df3
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/src/paks/rpz/uninstall.sh
>>>>>>>> @@ -0,0 +1,38 @@
>>>>>>>> +#!/bin/bash
>>>>>>>> +###############################################################################
>>>>>>>> +# #
>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>>>>> +# #
>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>> +# #
>>>>>>>> +###############################################################################
>>>>>>>> +#
>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>> +
>>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>>> +/etc/init.d/unbound stop
>>>>>>>> +
>>>>>>>> +make_backup ${NAME}
>>>>>>>> +remove_files
>>>>>>>> +
>>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>>> +# "[1723428668] unbound-control[17117:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>>> +
>>>>>>>> +# Update Language cache
>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>> +
>>>>>>>> +# start unbound to load unbound config file
>>>>>>>> +/etc/init.d/unbound start
>>>>>>>> diff --git a/src/paks/rpz/update.sh b/src/paks/rpz/update.sh
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..9bc340bc6
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/src/paks/rpz/update.sh
>>>>>>>> @@ -0,0 +1,52 @@
>>>>>>>> +#!/bin/bash
>>>>>>>> +###############################################################################
>>>>>>>> +# #
>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>>>>> +# #
>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>> +# #
>>>>>>>> +###############################################################################
>>>>>>>> +#
>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>> +
>>>>>>>> +# from update.sh
>>>>>>>> +extract_backup_includes
>>>>>>>> +
>>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>>> +/etc/init.d/unbound stop
>>>>>>>> +
>>>>>>>> +# from uninstall.sh
>>>>>>>> +make_backup ${NAME}
>>>>>>>> +remove_files
>>>>>>>> +
>>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>>> +# "unbound-control[nn:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>>> +
>>>>>>>> +# from install.sh
>>>>>>>> +extract_files
>>>>>>>> +restore_backup ${NAME}
>>>>>>>> +
>>>>>>>> +# fix user created files
>>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>>> + /etc/unbound/zonefiles \
>>>>>>>> + /etc/unbound/local.d
>>>>>>>> +
>>>>>>>> +# Update Language cache
>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>> +
>>>>>>>> +# restart unbound to load config files
>>>>>>>> +/etc/init.d/unbound start
>>>>>>>> --
>>>>>>>> 2.39.5
>>>>>>>>
>>>>>>>
>>>>>>
>>>>>> Jon
>>>>>>
>>>>>>
>>>>>> --
>>>>>> Jon Murphy
>>>>>> jon.murphy(a)ipfire.org
>>>>
>>>>
>>>> Jon
>>>>
>>>>
>>>> --
>>>> Jon Murphy
>>>> jon.murphy(a)ipfire.org
>
>
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
2025-02-14 12:07 ` Michael Tremer
2025-02-14 12:58 ` Bernhard Bitsch
@ 2025-03-01 10:18 ` Adolf Belka
[not found] ` <3BF29525-C9F4-4FD2-834D-FBE791E99E8C@ipfire.org>
1 sibling, 1 reply; 30+ messages in thread
From: Adolf Belka @ 2025-03-01 10:18 UTC (permalink / raw)
To: development
[-- Attachment #1: Type: text/plain, Size: 125451 bytes --]
Hi Jon,
Would you like to have this topic added to the agenda for the next Dev
Conf Call scheduled for 10th March?
The time of the conf call is 8:00 to 10:00pm Central European Time.
Best regards,
Adolf.
On 14/02/2025 13:07, Michael Tremer wrote:
> Hello Jon,
>
> It very much depends on the kind of contribution. A one-line patch obviously has fewer strings attached to it than a larger patch set like this.
>
> However, we have outlined the process in the wiki already, starting from here:
>
> https://www.ipfire.org/docs/devel/submit-patches
>
> This contains some useful pointers about the how (how do I actually make my changes happen, how do I build IPFire, etc), and at the bottom it contains a lot of information about the format the changes should be submitted; split into smaller chunks that are ideally as independent from each other so that they can be individually reviewed and merged. Usually a development process takes long time and we have already shipped parts of code that we will need for certain features that are not ready yet. This is a good practice to let code mature, especially when it is touching rather critical bits like the firewall and networking stacks.
>
> There are also some guidelines on how to write a good commit message and how to use Git tags:
>
> https://www.ipfire.org/docs/devel/git/commit-messages
> https://www.ipfire.org/docs/devel/git/tags
>
> Then there is something about how to get in touch with the right person and legal stuff:
>
> https://www.ipfire.org/docs/devel/contact
> https://www.ipfire.org/docs/legal/ipca
>
> Finally, we have a bit of an underused roadmap section on the wiki. It would be nice if we could use that a little bit more because then it would be easier for everyone to keep track of progress on certain features; people could see what is being worked on and see if they can help development and testing and so on:
>
> https://www.ipfire.org/docs/roadmap
>
> There is a template on how to create new pages:
>
> https://www.ipfire.org/docs/roadmap/template
>
> And this is a good example of what this could look like:
>
> https://www.ipfire.org/docs/roadmap/openvpn-26
>
> All of these steps are coming *after* there has been some initial discussion about what actually has a chance to become part of the distribution. For that, we do not have any specific guidelines because it is not very trivial to write these things. There are just too many possibilities. In the past, there has also been very little need for this, but that does not mean that there have not been problems before.
>
> The reason why I am raising the bar this high here is simply that we have made mistakes in the past that we don’t want to repeat. We have learned a couple of lessons in a not very pleasant way and I under no circumstances would want to do this again. The objective is that we want to provide an excellent distribution. Although IPFire of course has its shortcomings here and there, it is a very stable distribution and we have a very good track record that I want to keep. This is what our users deserve.
>
> In the past, people have “dropped” their patches on this list (or sometimes elsewhere) and we were left with dealing with the entire integration only to find out later what problems there were hidden in the code. The original author(s) had no interest in fixing any of that because it worked just fine for them, and so why spend any time on the problems of somebody else? Usually I am the fallback for this and I simply don’t want to be that. I have lots of my own projects inside IPFire that are moving at snail speed because fixing existing code usually takes priority over writing new code.
>
> Therefore we need a commitment to sort out these problems in the first place. It has to be proven that people actually *care* about the patches that they post here. I am not sure this needs writing down as this should be the same policy with almost any open source project. If you contribute a line, there is probably less maintenance required in the future, but if you contribute a large code base, then you will need to look after it for the foreseeable future. It is your feature and not mine after all.
>
> Then, what actually has a chance to make it into the distribution? Probably not a lot. IPFire has a very clear use case. There will not be any space for a desktop environment and running Chrome on it, we also don’t it to make coffee and cook me a dinner. We would currently only accept things that were actually maintainable by the current team in case a contributor moves on (see above), because we simply only have so much man power. We already have a large zoo of features that are very abandoned and we are potentially looking at getting rid of more things simply because we cannot support them properly. Time just doesn’t permit. Adding something large is therefore very difficult at the moment.
>
> I understand that in this specific case you have been trying to not involve the development team and I understand your motivation. But you cannot forget about how much time and effort a review process can take. Therefore we want to plan things well; we want to even split it; and we want to have a conversation in advance so that the roadmap is clear and the actual code review ideally only becomes a formality.
>
> All of this above has been for a general case. Please read through this and feel free to ask any questions if something isn’t clear.
>
> To move forward with this feature, we should start by planning a roadmap. We need to discuss what this project should cover and what it should not cover. I believe we don’t need to talk much about implementation details because you have figured out a lot of them; we need to find what feature we want to provide to our users. Are you up for that?
>
> Best,
> -Michael
>
>> On 13 Feb 2025, at 21:34, jon <jon.murphy(a)ipfire.org> wrote:
>>
>> Michael,
>>
>> I’ve read through your comments a few times and I ended up with many more questions.
>>
>>
>>> What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>
>> To me the efforts to get new code accepted seem to have changed and it seemed easier in the past. In the past I made the Core Team aware via the Dev Mailing List and wrote a simple two or three paragraphs of "What is it? / What is the value? / Here is the code"
>>
>>
>> So in an effort to move forward: How exactly is something presented to the Core Team?
>>
>> Is there an example of a recent effort that was presented that I can see as a sample? (This type of info can also be added to the Wiki)
>>
>> I understand you want it this way, but I don’t know what exactly is needed. Please be specific.
>>
>>
>> Jon
>>
>> PS - I am not ignoring your other comments, I am just trying to move forward and keep things simple.
>>
>>
>>
>>> On Feb 8, 2025, at 1:27 PM, Michael Tremer <michael.tremer(a)ipfire.org> wrote:
>>>
>>> Hello Jon,
>>>
>>> Thanks for your reply. And good that you are copying everyone into this conversation.
>>>
>>>> On 8 Feb 2025, at 18:41, jon <jon.murphy(a)ipfire.org> wrote:
>>>>
>>>> Michael,
>>>>
>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort
>>>> Yes, this has been a separate effort (a very public separate effort). Yes, as you pointed this out early on with the "proof-of-concept" and then my request for people to help test RPZ. Nothing was hidden.
>>>>
>>>> This was done because you (and maybe others) did not have the time and I wanted to help and because I needed assistance with RPZ. I tried my best to do this without bothering you.
>>> I don’t that it is accurate that nobody wanted to help on this. The list was always open - although not every email has been replied to swiftly it is also your responsibility to raise a question again if it was missed. People here have open ears.
>>>
>>> It was also stated on this very list on in our documentation that working on something without involving the core team is a risky undertaking. Of course IPFire is free software and so everyone is free to fork if they wish to do so.
>>>
>>>>> and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls.
>>>> You were aware many steps along the way. See your email on July 28, 2024, August 15, 2024, September 30, 2024, December 23, 2024, and January 16. My attempts to get the team involved were met with "things are busy" and sometimes silence. (Yes, I get it, people are busy.)
>>>>
>>>> You and Adolf, Leo, Erik and Bernhard have been aware since the beginning. You mention you were aware of the "proof-of-concept". If you include those beginning posts, since Sep 2023.
>>> Yes, I am aware of a proof-of-concept that I have been running myself for a long time. I am also aware of the efforts that you have been taking.
>>>
>>> Yet I don’t think there has ever been any joint effort, or am I seeing that wrong?
>>>
>>>>> This has not been discussed . . . on our calls.
>>>> On the July 28th you stated:
>>>> "We have talked about RPZ many times on the monthly call since the URL filter feature is falling more and more out of fashion. I think there is also many posts about this on the forum."
>>>>
>>>> Please don’t insult me again by stating "you know what I mean".
>>>>
>>>> And it has been discussed but not documented in the Monthly Meeting notes.
>>> I am not at all insulting you. I don’t want to take this down to a personal level at all. This is a public mailing list and people who read this don’t need to listen to an argument we are having. They are here for the tech inside IPFire.
>>>
>>> When I wrote that it has not been discussed that does not mean that we have not been touching on the topic. We have been talking about lots of things on the calls, the weather, politics, how our pets are. None of that makes it to the logs. What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>
>>>>> Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation.
>>>> Regular conversation on the Dev Mailing list is many times met with silence. I get it, people are busy.
>>>>
>>>> And regular two-way conversation doesn’t happen on the list. At least not with me. I’d be happy to point out the posts that were met with silence.
>>>> Again, I get it, people are busy.
>>> And you think my emails are not being met with silence? This has nothing to do with this specific topic. This has something to do with how occupied people are and how engaged they are on certain topics. Not everyone is involved in all the things and simply will ignore emails simply based on their subject line.
>>>
>>>> But the "dip here to the list" were my attempts to get a conversation started. As I said, many time met with silence.
>>>>
>>>> The only place I was not met with silence was on the Community. You have a great group of people in the Community. It is a shame you don’t want to have others help. It would reduce your workload.
>>> You should stop making statements that are not true. Who doesn’t want anyone to help?
>>>
>>> Not having this conversation on a Saturday evening would reduce my workload. At least it would free up time for something else. Helping with the things that are already on the go would reduce the workload of the entire team. Starting one thing at a time and finishing it is a lot better to manage than starting a hundred things and not even finish one. I can tell you that I already have a hundred things on the go.
>>>
>>>>> Therefore, what am I supposed to do with this email?
>>>> To me it is beyond obvious…
>>>>
>>>> If it isn’t what you want, then guide me with how to do this the correct way. And be specific. I am trying to help. I am trying to make things better. I am trying to do things the right way.
>>> To me it isn’t. This is yet another project that has been dumped to the list like so many before and later on everyone has left to have the team deal with the rest.
>>>
>>> It is a huge patch set. You explained what the vision is, but that is about it. There is no chance this will continue if this disagreement isn’t solved first. I didn’t even look at the code.
>>>
>>>>> I don’t want to merge code that I don’t agree with.
>>>> I asked multiple times if you "agreed with the concept" and again, met with silence. Yes I get it, people are busy.
>>> Having support for RPZ? Yes, it was definitely on the roadmap. That I agree with.
>>>
>>>>> So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>> You mentioned this a in the past, but for some reason you do not disclose what I dismissed. Why do you continue to make this harder, wouldn’t it not be easier to tell me what I have dismissed?
>>>>
>>>> I have sent multiple emails trying to answer your concerns and comments. On July 28, Aug 14, Aug 22, Aug 23, Sep 30, etc.
>>>>
>>>> I’ve gone through all of the questions you asked and I cannot find a "dismissed" item.
>>> Maybe I need to be *more clear*. I feel humoured by this.
>>>
>>> It is late on a Saturday and I want my dinner soon, but certainly I have stated that this should never be an add-on considering it is supposed to replace URL Filter. We should never allow people to add their own sources. I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>>
>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>> The maintainers of Unbound and/or RPZ?
>>>>
>>>> The maintainers of Hagezi list, the threatfox list, the urlhaus list, etc.?
>>>>
>>>> What else? The maintainers or the RPZ scripts? That is me. Let’s talk!
>>> You. I don’t care much about the providers of the lists.
>>>
>>>> See, this is where it gets confusing. There are hundreds of open source packages as part of IPFire. Pick the last five years of items added to the IPFire build. You're telling me you have "constructive conversation with the maintainers" of all of the added packages?
>>> They publish their software and they don’t care whether I am pulling it or not. They publish it with the commitment to maintain it - sometimes for better and sometimes for worse.
>>>
>>> You care about me pulling your code and I don’t know whether you would commit to maintain this.
>>>
>>> These two are very different cases.
>>>
>>>> Pick the IP Blocklists list (i.e., 3CORESEC, ABUSECH, DSHIELD, SPAMHAUS, etc.) or the Suricata lists (i.e., Emergingthreats.net, Abuse.ch, etc.). So you’ve have "constructive conversation with the maintainers"?
>>> Yes, occasionally I have phone calls with a few of these providers.
>>>
>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>> Ha! Yes a surprise. In the beginning you seemed interested as IPFire needed a replacement for URL Filter. You asked good questions about the lists picked, asked for the value to the users, etc. And I answered the best I could.
>>>>
>>>> You even asked: “Why is this realised as an add-on and not part of the core system?” from your Jul 28, 2024 email.
>>> Ah, so, why is the patch creating an add-on? Not that I am saying that what I say is law, but it has not been challenged either. If my input is being ignored, why should I put this to the top of my list of priorities? I am not disappointed about this, just trying to be very good with my time.
>>>
>>>> And on January 16, 2025 I wrote a message looking for help. And you were kind to respond quickly. So in three weeks time, since the kind response, something has changed. You went from supportive to "this".
>>>>
>>>> So yes, I am surprised.
>>> Well, maybe I should not have replied to that email. It was clear that you were on some path that was not right, but you were not interested before in finding the right path from the beginning.
>>>
>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>> Be more specific, what has to change? What exactly did I dismiss?
>>> Dismissal is just my assumption. I don’t know what you actually did with my feedback. I can only see the end product that does not seem contain much of it. Repeatedly I have been pointing out that we should think before we build. I am sure a lot of hours have now gone into some code that simply does not satisfy me. And I am not not talking about the code itself, what it does is what I don’t think is right for us.
>>>
>>> The process is very clear for me that we should first of all think whether we want a certain feature now. Then there should be a clear roadmap for everyone to follow; tasks can be split-up as we go and hopefully then have something that is maintainable, interesting for our users and even would do us proud. This is how this should work.
>>>
>>> So, what has to change? I don’t think with shouting at each other, throwing patches around and making me generally unhappy is a good start.
>>>
>>> -Michael
>>>
>>>> Jon
>>>>
>>>>
>>>>
>>>>> On Feb 6, 2025, at 2:13 PM, Michael Tremer <michael.tremer(a)ipfire.org> wrote:
>>>>>
>>>>> Hello Jon,
>>>>>
>>>>> Well, here we are again with another patch regarding this feature.
>>>>>
>>>>> I cannot quite see from your email what the question is, but if this is a request to have this merged into IPFire, I am once again sorry to disappoint you.
>>>>>
>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls. Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation. Therefore, what am I supposed to do with this email?
>>>>>
>>>>> I don’t want to merge code that I don’t agree with. So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>
>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>
>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>
>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>
>>>>> All the best,
>>>>> -Michael
>>>>>
>>>>>> On 6 Feb 2025, at 16:35, Jon Murphy <jon.murphy(a)ipfire.org> wrote:
>>>>>>
>>>>>> What is it?
>>>>>> Response Policy Zone (RPZ) is a mechanism to define local policies in a
>>>>>> standardized way and load those policies from external sources.
>>>>>> Bottom line: RPZ allows admins to easily block access to websites via DNS lookup.
>>>>>>
>>>>>> RPZ can block websites via categories. Examples include: fake websites, annoying
>>>>>> pop-up ads, newly registered domains, DoH bypass sites, bad "host" services,
>>>>>> maliscious top level domains (e.g., *.zip, *.mov), piracy, gambling, pornography,
>>>>>> and more. RPZ lists come from various RPZ providers and their available
>>>>>> catagories.
>>>>>>
>>>>>> This RPZ add-on enables the RPZ functionality by adding a couple lines in a
>>>>>> configuration file. This add-on simply adds configuration files and adds
>>>>>> scripts (config, metrics and sleep) to make RPZ easier for the admin to use.
>>>>>>
>>>>>> The RPZ scripts include additional languages: German, Spanish, French, Turkish,
>>>>>> and Italian.
>>>>>>
>>>>>> RPZ itself was release in 2010 and has been part of the IPFire build since ~2015.
>>>>>>
>>>>>> Why is it needed? What is its value?
>>>>>>
>>>>>> - The RPZ concept places this filtering into IPFire, our internet access
>>>>>> gateway, which is (should be) solely used as DNS source of the internal network.
>>>>>>
>>>>>> - As most sites use HTTPS it makes it difficult to filter traffic with URL
>>>>>> Filter without also properly configuring conventional (non-transparent)
>>>>>> mode on the proxy. RPZ is a nice replacement for the URL Filter.
>>>>>>
>>>>>> - No need to install and maintain an additional device like PiHole or AdBlock
>>>>>> browser extensions on multiple user devices.
>>>>>>
>>>>>> - This is an additional layer of protection for users. Less worry someone will
>>>>>> click on something that gets them into trouble. And, saying this with emphasis,
>>>>>> the ability to do it in one place!
>>>>>>
>>>>>> - Blocked sites save on unneeded traffic and can lessen the threat of malware
>>>>>> in advertisements
>>>>>>
>>>>>> - Logging allows the admin to see the site blocked and take actions
>>>>>>
>>>>>> - RPZ will be used at the home, home-office (work from home), schools,
>>>>>> ministerial, and at the office. Device counts are small (2-6) to medium (~80)
>>>>>> to mediam-large (200+).
>>>>>>
>>>>>> - RPZ can block ads, popups, phishing, scammers, spyware, malware, annoying
>>>>>> popups, NSFW links, DOH servers, and the usual internet trash.
>>>>>>
>>>>>> ------------------------------
>>>>>>
>>>>>> Change Log for RPZ add-on
>>>>>>
>>>>>> rpz-1.0.0-18 on 2025-02-05
>>>>>> - Build for approval & release as IPFire add-on
>>>>>>
>>>>>> ---
>>>>>>
>>>>>> rpz-beta-0.1.18-18.ipfire on 2025-02-01
>>>>>> rpz.cgi:
>>>>>> - new feature: added a mod key to force a unbound restart
>>>>>>
>>>>>> rpz-config and rpz-make:
>>>>>> - new feature: added action for unbound restart `rpz-config unbound-restart`
>>>>>>
>>>>>> rpz-metrics:
>>>>>> - simple reformatting
>>>>>> - rename far right column from "last update" to "last download"
>>>>>>
>>>>>> ---
>>>>>>
>>>>>> rpz-beta-0.1.17-17.ipfire on 2024-12-09
>>>>>> rpz-make
>>>>>> - bug fix: corrected validation regex for wildcards like: `*.domain.com`
>>>>>>
>>>>>> ---
>>>>>>
>>>>>> rpz-beta-0.1.16-16.ipfire on 2024-11-18
>>>>>> rpz-make
>>>>>> - new feature: updated validation regex
>>>>>> - bug fix: moved validation to beginning of process. Now we validate before
>>>>>> creating config files.
>>>>>>
>>>>>> rpz.cgi:
>>>>>> - new feature: use CSS color variables of the main ipfire theme
>>>>>> - bug fix: empty zonefile remarks were stored as “undef” and caused a warning
>>>>>> - bug fix: HTML textarea removes the first empty line in a custom list
>>>>>> - thank you Leo!
>>>>>>
>>>>>> ---
>>>>>>
>>>>>> rpz-beta-0.1.15-15.ipfire on 2024-11-04
>>>>>> rpz.cgi:
>>>>>> - new feature: added new language file for Turkish (thank you Peppe)
>>>>>>
>>>>>> rpz-make
>>>>>> - bug fix: corrected empty allow/block list issue. An empty allow/block list
>>>>>> will now remove contents of allow/block.rpz files and remove unneeded
>>>>>> allow/block.conf file. (thank you iptom)
>>>>>>
>>>>>> ---
>>>>>>
>>>>>> rpz-beta-0.1.14-14.ipfire on 2024-10-29
>>>>>> rpz-config:
>>>>>> - bug fix: correct missing rpz extension. `rpz-config list` displayed URL
>>>>>> incorrectly (thank you Bernhard)
>>>>>>
>>>>>> rpz.cgi:
>>>>>> - bug fix: remove extra `"` in language files (thank you Bernhard)
>>>>>> - new feature: slightly dim "apply" button when not enabled
>>>>>>
>>>>>> ---
>>>>>>
>>>>>> rpz-beta-0.1.13-13.ipfire on 2024-10-27
>>>>>> - skipped
>>>>>>
>>>>>> ---
>>>>>>
>>>>>> rpz-beta-0.1.12-12.ipfire on 2024-10-21
>>>>>> rpz.cgi:
>>>>>> - new feature: added new language file for French (thank you gw-ipfire)
>>>>>>
>>>>>> ---
>>>>>>
>>>>>> rpz-beta-0.1.11-11.ipfire on 2024-10-18
>>>>>> rpz.cgi:
>>>>>> - new feature: added new language file for Italian (thank you umberto)
>>>>>> - new feature: added new language file for Spanish (thank you Roberto)
>>>>>>
>>>>>> ---
>>>>>>
>>>>>> rpz-beta-0.1.10-10.ipfire on 2024-10-15
>>>>>> rpz-make:
>>>>>> - bug fix: corrected validation error for a custom list entry (thank you siosios)
>>>>>> - e.g., `*.cloudflare-dns.com`
>>>>>>
>>>>>> install.sh:
>>>>>> - bug fix: add chown to correct user created files
>>>>>>
>>>>>> update.sh:
>>>>>> - bug fix: add chown to correct user created files (thank you siosios)
>>>>>>
>>>>>> ---
>>>>>>
>>>>>> rpz-beta-0.1.9-9.ipfire on 2024-10-08
>>>>>> rpz.cgi:
>>>>>> - new feature: added new language file for German (thank you Leo)
>>>>>> - bug fix: add missing "rpz exitcode 110"
>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>
>>>>>> ---
>>>>>>
>>>>>> rpz-beta-0.1.8-8.ipfire on 2024-10-04
>>>>>> - skipped
>>>>>>
>>>>>> ---
>>>>>>
>>>>>> rpz-beta-0.1.7-7.ipfire on 2024-10-03
>>>>>> All:
>>>>>> - new feature: includes beta version numbers for pakfire package,
>>>>>> instead of only `rpz-1.0.0-1.ipfire`, for each release.
>>>>>>
>>>>>> rpz.cgi:
>>>>>> - new feature: added new WebGUI at `rpz.cgi`
>>>>>> - a BIG thank you to Leo Hofmann for all of his work creating the webgui!!
>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>
>>>>>> rpz-make:
>>>>>> - new feature: validate entries in allowlist and blocklist
>>>>>> - new feature: add "no-reload" option for WebGUI
>>>>>>
>>>>>> rpz-metrics:
>>>>>> - new feature: info can be sorted by name, by hit count, by line count, by
>>>>>> "enabled" list or all lists
>>>>>>
>>>>>> backups:
>>>>>> - bug fix: include all files in `/var/ipfire/dns/rpz` directory in backup
>>>>>>
>>>>>> update.sh:
>>>>>> - bug fix: corrected ownership for `/var/ipfire/dns/rpz` directory during an
>>>>>> update
>>>>>>
>>>>>> Build:
>>>>>> - bug fix: `block.rpz.conf` and `block.rpz` from build. Files to be created
>>>>>> by `rpz-make`
>>>>>>
>>>>>> WebGUI and German language file
>>>>>> Contribution-by: Leo-Andres Hofmann <hofmann(a)leo-andres.de>
>>>>>>
>>>>>> Spanish language file
>>>>>> Contribution-by: Roberto Peña
>>>>>>
>>>>>> Italian language file
>>>>>> Contribution-by: Umberto Parma
>>>>>>
>>>>>> French language file
>>>>>> Contribution-by: gw-ipfire
>>>>>>
>>>>>> Turkish language file
>>>>>> Contribution-by: Peppe Tech
>>>>>>
>>>>>> Contribution-by: Bernhard Bitsch <bbitsch(a)ipfire.org>
>>>>>> Contribution-by: Erik Kapfer <erik.kapfer(a)ipfire.org>
>>>>>> Signed-off-by: Jon Murphy <jon.murphy(a)ipfire.org
>>>>>> ---
>>>>>> config/backup/includes/rpz | 4 +
>>>>>> config/cfgroot/manualpages | 1 +
>>>>>> config/menu/EX-rpz.menu | 6 +
>>>>>> config/rootfiles/common/configroot | 1 +
>>>>>> config/rootfiles/common/web-user-interface | 1 +
>>>>>> config/rootfiles/packages/rpz | 20 +
>>>>>> config/rpz/00-rpz.conf | 10 +
>>>>>> config/rpz/rpz-config | 130 +++
>>>>>> config/rpz/rpz-functions | 85 ++
>>>>>> config/rpz/rpz-make | 203 +++++
>>>>>> config/rpz/rpz-metrics | 170 ++++
>>>>>> config/rpz/rpz-sleep | 58 ++
>>>>>> config/rpz/rpz.de.pl | 30 +
>>>>>> config/rpz/rpz.en.pl | 30 +
>>>>>> config/rpz/rpz.es.pl | 30 +
>>>>>> config/rpz/rpz.fr.pl | 30 +
>>>>>> config/rpz/rpz.it.pl | 30 +
>>>>>> config/rpz/rpz.tr.pl | 30 +
>>>>>> html/cgi-bin/rpz.cgi | 923 +++++++++++++++++++++
>>>>>> lfs/rpz | 96 +++
>>>>>> make.sh | 3 +-
>>>>>> src/paks/rpz/install.sh | 36 +
>>>>>> src/paks/rpz/uninstall.sh | 38 +
>>>>>> src/paks/rpz/update.sh | 52 ++
>>>>>> 24 files changed, 2016 insertions(+), 1 deletion(-)
>>>>>> create mode 100644 config/backup/includes/rpz
>>>>>> create mode 100644 config/menu/EX-rpz.menu
>>>>>> create mode 100644 config/rootfiles/packages/rpz
>>>>>> create mode 100644 config/rpz/00-rpz.conf
>>>>>> create mode 100644 config/rpz/rpz-config
>>>>>> create mode 100644 config/rpz/rpz-functions
>>>>>> create mode 100644 config/rpz/rpz-make
>>>>>> create mode 100755 config/rpz/rpz-metrics
>>>>>> create mode 100755 config/rpz/rpz-sleep
>>>>>> create mode 100644 config/rpz/rpz.de.pl
>>>>>> create mode 100644 config/rpz/rpz.en.pl
>>>>>> create mode 100644 config/rpz/rpz.es.pl
>>>>>> create mode 100644 config/rpz/rpz.fr.pl
>>>>>> create mode 100644 config/rpz/rpz.it.pl
>>>>>> create mode 100644 config/rpz/rpz.tr.pl
>>>>>> create mode 100644 html/cgi-bin/rpz.cgi
>>>>>> create mode 100644 lfs/rpz
>>>>>> create mode 100644 src/paks/rpz/install.sh
>>>>>> create mode 100644 src/paks/rpz/uninstall.sh
>>>>>> create mode 100644 src/paks/rpz/update.sh
>>>>>>
>>>>>> diff --git a/config/backup/includes/rpz b/config/backup/includes/rpz
>>>>>> new file mode 100644
>>>>>> index 000000000..36513e494
>>>>>> --- /dev/null
>>>>>> +++ b/config/backup/includes/rpz
>>>>>> @@ -0,0 +1,4 @@
>>>>>> +/var/ipfire/dns/rpz/*
>>>>>> +/etc/unbound/zonefiles/allow.rpz
>>>>>> +/etc/unbound/zonefiles/block.rpz
>>>>>> +/etc/unbound/local.d/*rpz.conf
>>>>>> diff --git a/config/cfgroot/manualpages b/config/cfgroot/manualpages
>>>>>> index 1f7e01efc..d3a48c633 100644
>>>>>> --- a/config/cfgroot/manualpages
>>>>>> +++ b/config/cfgroot/manualpages
>>>>>> @@ -70,6 +70,7 @@ pakfire.cgi=configuration/ipfire/pakfire
>>>>>> wlanap.cgi=addons/wireless
>>>>>> tor.cgi=addons/tor
>>>>>> samba.cgi=addons/samba
>>>>>> +rpz.cgi=addons/rpz
>>>>>>
>>>>>> # Logs menu
>>>>>> logs.cgi/summary.dat=configuration/logs/summary
>>>>>> diff --git a/config/menu/EX-rpz.menu b/config/menu/EX-rpz.menu
>>>>>> new file mode 100644
>>>>>> index 000000000..2f4daf410
>>>>>> --- /dev/null
>>>>>> +++ b/config/menu/EX-rpz.menu
>>>>>> @@ -0,0 +1,6 @@
>>>>>> +$subipfire->{'20.rpz'} = {
>>>>>> + 'caption' => $Lang::tr{'rpz'},
>>>>>> + 'uri' => '/cgi-bin/rpz.cgi',
>>>>>> + 'title' => "RPZ",
>>>>>> + 'enabled' => 1,
>>>>>> +};
>>>>>> diff --git a/config/rootfiles/common/configroot b/config/rootfiles/common/configroot
>>>>>> index 9839eee45..b30d6aae4 100644
>>>>>> --- a/config/rootfiles/common/configroot
>>>>>> +++ b/config/rootfiles/common/configroot
>>>>>> @@ -120,6 +120,7 @@ var/ipfire/menu.d/70-log.menu
>>>>>> #var/ipfire/menu.d/EX-apcupsd.menu
>>>>>> #var/ipfire/menu.d/EX-guardian.menu
>>>>>> #var/ipfire/menu.d/EX-mympd.menu
>>>>>> +#var/ipfire/menu.d/EX-rpz.menu
>>>>>> #var/ipfire/menu.d/EX-samba.menu
>>>>>> #var/ipfire/menu.d/EX-tor.menu
>>>>>> #var/ipfire/menu.d/EX-transmission.menu
>>>>>> diff --git a/config/rootfiles/common/web-user-interface b/config/rootfiles/common/web-user-interface
>>>>>> index 816241dae..e00464076 100644
>>>>>> --- a/config/rootfiles/common/web-user-interface
>>>>>> +++ b/config/rootfiles/common/web-user-interface
>>>>>> @@ -69,6 +69,7 @@ srv/web/ipfire/cgi-bin/proxy.cgi
>>>>>> srv/web/ipfire/cgi-bin/qos.cgi
>>>>>> srv/web/ipfire/cgi-bin/remote.cgi
>>>>>> srv/web/ipfire/cgi-bin/routing.cgi
>>>>>> +#srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>> #srv/web/ipfire/cgi-bin/samba.cgi
>>>>>> srv/web/ipfire/cgi-bin/services.cgi
>>>>>> srv/web/ipfire/cgi-bin/shutdown.cgi
>>>>>> diff --git a/config/rootfiles/packages/rpz b/config/rootfiles/packages/rpz
>>>>>> new file mode 100644
>>>>>> index 000000000..1c8663049
>>>>>> --- /dev/null
>>>>>> +++ b/config/rootfiles/packages/rpz
>>>>>> @@ -0,0 +1,20 @@
>>>>>> +etc/unbound/local.d/00-rpz.conf
>>>>>> +etc/unbound/zonefiles
>>>>>> +etc/unbound/zonefiles/allow.rpz
>>>>>> +usr/sbin/rpz-config
>>>>>> +usr/sbin/rpz-functions
>>>>>> +usr/sbin/rpz-make
>>>>>> +usr/sbin/rpz-metrics
>>>>>> +usr/sbin/rpz-sleep
>>>>>> +var/ipfire/addon-lang/rpz.de.pl
>>>>>> +var/ipfire/addon-lang/rpz.en.pl
>>>>>> +var/ipfire/addon-lang/rpz.es.pl
>>>>>> +var/ipfire/addon-lang/rpz.fr.pl
>>>>>> +var/ipfire/addon-lang/rpz.it.pl
>>>>>> +var/ipfire/addon-lang/rpz.tr.pl
>>>>>> +var/ipfire/backup/addons/includes/rpz
>>>>>> +var/ipfire/dns/rpz
>>>>>> +var/ipfire/dns/rpz/allowlist
>>>>>> +var/ipfire/dns/rpz/blocklist
>>>>>> +var/ipfire/menu.d/EX-rpz.menu
>>>>>> +srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>> diff --git a/config/rpz/00-rpz.conf b/config/rpz/00-rpz.conf
>>>>>> new file mode 100644
>>>>>> index 000000000..f005a4f2e
>>>>>> --- /dev/null
>>>>>> +++ b/config/rpz/00-rpz.conf
>>>>>> @@ -0,0 +1,10 @@
>>>>>> +server:
>>>>>> + module-config: "respip validator iterator"
>>>>>> +
>>>>>> +rpz:
>>>>>> + name: allow.rpz
>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>> + rpz-action-override: passthru
>>>>>> + rpz-log: yes
>>>>>> + rpz-log-name: allow
>>>>>> + rpz-signal-nxdomain-ra: yes
>>>>>> diff --git a/config/rpz/rpz-config b/config/rpz/rpz-config
>>>>>> new file mode 100644
>>>>>> index 000000000..c72d50f9b
>>>>>> --- /dev/null
>>>>>> +++ b/config/rpz/rpz-config
>>>>>> @@ -0,0 +1,130 @@
>>>>>> +#!/bin/bash
>>>>>> +###############################################################################
>>>>>> +# #
>>>>>> +# IPFire.org - A linux based firewall #
>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info(a)ipfire.org> #
>>>>>> +# #
>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>> +# #
>>>>>> +###############################################################################
>>>>>> +
>>>>>> +version="2025-01-11 - v44"
>>>>>> +
>>>>>> +############### Functions ###############
>>>>>> +
>>>>>> +source /usr/sbin/rpz-functions
>>>>>> +
>>>>>> +############### Main ###############
>>>>>> +
>>>>>> +tagName="unbound"
>>>>>> +
>>>>>> +rpzAction="${1}" # input RPZ action
>>>>>> +rpzName="${2}" # input RPZ name
>>>>>> +rpzURL="${3}" # input RPZ URL
>>>>>> +rpzOption1="${4}" # input RPZ option #1
>>>>>> +rpzOption2="${5}" # input RPZ option #2
>>>>>> +
>>>>>> +rpzConfig="/etc/unbound/local.d/${rpzName}.rpz.conf" # output zone conf file
>>>>>> +rpzFile="/etc/unbound/zonefiles/${rpzName}.rpz" # output for RPZ file
>>>>>> +
>>>>>> +rpzLog="yes" # log default is yes
>>>>>> +ucReload="yes" # reload default is yes
>>>>>> +
>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>> + case "$1" in
>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>> + esac
>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>> +done
>>>>>> +
>>>>>> +case "${rpzAction}" in
>>>>>> + # add new rpz list
>>>>>> + add )
>>>>>> + check_name "${rpzName}" # is this a valid name?
>>>>>> + # does this config already exist? If yes, then exit
>>>>>> + if [[ -f "${rpzConfig}" ]] ; then
>>>>>> + msg_log "error: rpz: duplicate - ${rpzConfig} already exists. exit"
>>>>>> + exit 104
>>>>>> + fi
>>>>>> +
>>>>>> + # is this a valid URL?
>>>>>> + regex='^https://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
>>>>>> + if ! [[ "${rpzURL}" =~ $regex ]] ; then
>>>>>> + msg_log "error: rpz: the URL is not valid: \"${rpzURL}\". exit."
>>>>>> + exit 105
>>>>>> + fi
>>>>>> +
>>>>>> + # create the zone config file
>>>>>> + {
>>>>>> + echo "rpz:"
>>>>>> + echo " name: ${rpzName}.rpz"
>>>>>> + echo " zonefile: ${rpzFile}"
>>>>>> + echo " url: ${rpzURL}"
>>>>>> + echo " rpz-action-override: nxdomain"
>>>>>> + echo " rpz-log: ${rpzLog}"
>>>>>> + echo " rpz-log-name: ${rpzName}"
>>>>>> + echo " rpz-signal-nxdomain-ra: yes"
>>>>>> + } > "${rpzConfig}"
>>>>>> +
>>>>>> + # set-up zonefile
>>>>>> + # create an empty rpz file if it does not exist
>>>>>> + if [[ ! -f "${rpzFile}" ]] ; then
>>>>>> + touch "${rpzFile}"
>>>>>> + # unbound requires these settings for rpz files
>>>>>> + set_permissions "${rpzFile}" "${rpzConfig}"
>>>>>> + fi
>>>>>> + ;;
>>>>>> +
>>>>>> + # trash config file & rpz file
>>>>>> + remove )
>>>>>> + if ! [[ -f "${rpzConfig}" ]] ; then
>>>>>> + msg_log "error: rpz: cannot remove ${rpzConfig}, does not exist. exit"
>>>>>> + exit 106
>>>>>> + fi
>>>>>> +
>>>>>> + msg_log "info: rpz: remove config file & rpz file \"${rpzName}\""
>>>>>> + rm "${rpzConfig}"
>>>>>> + rm "${rpzFile}"
>>>>>> + ;;
>>>>>> +
>>>>>> + reload )
>>>>>> + check_unbound_conf "${checkConf}"
>>>>>> + ;;
>>>>>> +
>>>>>> + list )
>>>>>> + awk -F':' '/^\s*name:/{ gsub(/[[:blank:]]|\.rpz/, "",$2) ; NAME=$2 } \
>>>>>> + /^\s*url:/{ gsub(/[[:blank:]]/, "") ; print NAME"="$2":"$3} ' \
>>>>>> + /etc/unbound/local.d/*rpz.conf
>>>>>> + exit
>>>>>> + ;;
>>>>>> +
>>>>>> + unbound-restart )
>>>>>> + check_unbound_conf "${checkConf}"
>>>>>> + unbound_restart
>>>>>> + exit
>>>>>> + ;;
>>>>>> +
>>>>>> + * )
>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>> + printf "Usage: $(basename "$0") <ACTION> <NAME> <URL> <OPTION> <OPTION>\n"
>>>>>> + printf "Version: ${version}\n"
>>>>>> + exit 108
>>>>>> + ;;
>>>>>> +
>>>>>> +esac
>>>>>> +
>>>>>> +unbound_control_reload "${ucReload}"
>>>>>> +
>>>>>> +exit
>>>>>> diff --git a/config/rpz/rpz-functions b/config/rpz/rpz-functions
>>>>>> new file mode 100644
>>>>>> index 000000000..ace1d2690
>>>>>> --- /dev/null
>>>>>> +++ b/config/rpz/rpz-functions
>>>>>> @@ -0,0 +1,85 @@
>>>>>> +#!/bin/bash
>>>>>> +###############################################################################
>>>>>> +# #
>>>>>> +# IPFire.org - A linux based firewall #
>>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>>> +# #
>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>> +# #
>>>>>> +###############################################################################
>>>>>> +
>>>>>> +version="2024-12-10 - v02"
>>>>>> +
>>>>>> +############### Functions ###############
>>>>>> +
>>>>>> +msg_log () {
>>>>>> + logger --tag "${tagName}" "$*"
>>>>>> + if tty --silent ; then
>>>>>> + echo "${tagName}:" "$*"
>>>>>> + fi
>>>>>> +}
>>>>>> +
>>>>>> +# check for a valid name
>>>>>> +check_name () {
>>>>>> + local theName="${1}"
>>>>>> +
>>>>>> + regex='^[a-zA-Z0-9_]+$' # no dash or plus, alpha numeric only
>>>>>> + regex1='^(allow|block)$' # allow and block are reserved NAMEs
>>>>>> + if [[ ! "${theName}" =~ $regex ]] || [[ "${theName}" =~ $regex1 ]] ; then
>>>>>> + msg_log "error: rpz: the NAME is not valid: \"${theName}\". exit."
>>>>>> + exit 101
>>>>>> + fi
>>>>>> +}
>>>>>> +
>>>>>> +set_permissions () {
>>>>>> + chown nobody:nobody "$@"
>>>>>> + chmod 644 "$@"
>>>>>> +}
>>>>>> +
>>>>>> +check_unbound_conf () {
>>>>>> + local thecheckConf="${1:-yes}" # check config default is yes
>>>>>> +
>>>>>> + # check the above config files
>>>>>> + if [[ "${thecheckConf}" == yes ]] ; then
>>>>>> + msg_log "info: rpz: check for errors with \"unbound-checkconf\""
>>>>>> +
>>>>>> + if ! unbound-checkconf ; then
>>>>>> + msg_log "error: rpz: unbound-checkconf found invalid configuration."
>>>>>> + msg_log \
>>>>>> + "error: rpz: In Terminal run the command \"unbound-checkconf\" for more information. exit."
>>>>>> + exit 102
>>>>>> + fi
>>>>>> + fi
>>>>>> +}
>>>>>> +
>>>>>> +unbound_control_reload () {
>>>>>> + local theReload="${1:-yes}" # reload default is yes
>>>>>> +
>>>>>> + if [[ "${theReload}" == yes ]] ; then
>>>>>> + # reload due to the changes
>>>>>> + msg_log "info: rpz: run \"unbound-control reload\""
>>>>>> +
>>>>>> + if ! unbound-control reload ; then
>>>>>> + msg_log "error: rpz: unbound-control reload. exit."
>>>>>> + exit 109
>>>>>> + fi
>>>>>> + fi
>>>>>> +}
>>>>>> +
>>>>>> +unbound_restart () {
>>>>>> + # restart due to the changes
>>>>>> + msg_log "info: rpz: run \"unbound restart\""
>>>>>> +
>>>>>> + /usr/local/bin/unboundctrl restart
>>>>>> +}
>>>>>> diff --git a/config/rpz/rpz-make b/config/rpz/rpz-make
>>>>>> new file mode 100644
>>>>>> index 000000000..927d55170
>>>>>> --- /dev/null
>>>>>> +++ b/config/rpz/rpz-make
>>>>>> @@ -0,0 +1,203 @@
>>>>>> +#!/bin/bash
>>>>>> +###############################################################################
>>>>>> +# #
>>>>>> +# IPFire.org - A linux based firewall #
>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info(a)ipfire.org> #
>>>>>> +# #
>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>> +# #
>>>>>> +###############################################################################
>>>>>> +
>>>>>> +version="2025-01-11 - v14"
>>>>>> +
>>>>>> +############### Functions ###############
>>>>>> +
>>>>>> +source /usr/sbin/rpz-functions
>>>>>> +
>>>>>> +# create the config file for allow
>>>>>> +make_allow_config () {
>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>> + local theConfig="/etc/unbound/local.d/00-rpz.conf" # output zone conf file
>>>>>> + local theList="/var/ipfire/dns/rpz/allowlist" # input custom list of domains
>>>>>> +
>>>>>> + msg_log "info: rpz: make config file \"00-rpz.conf\""
>>>>>> +
>>>>>> + echo "server:
>>>>>> + module-config: \"respip validator iterator\"" > "${theConfig}"
>>>>>> +
>>>>>> + # does allow list exist?
>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>> +
>>>>>> + echo "rpz:
>>>>>> + name: allow.rpz
>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>> + rpz-action-override: passthru
>>>>>> + rpz-log: ${theLog}
>>>>>> + rpz-log-name: allow
>>>>>> + rpz-signal-nxdomain-ra: yes" >> "${theConfig}"
>>>>>> +
>>>>>> + fi
>>>>>> +
>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>> + set_permissions "${theConfig}"
>>>>>> +}
>>>>>> +
>>>>>> +# create the config file for block
>>>>>> +make_block_config () {
>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>> + local theConfig="/etc/unbound/local.d/block.rpz.conf" # output zone conf file
>>>>>> + local theList="/var/ipfire/dns/rpz/blocklist" # input custom list of domains
>>>>>> +
>>>>>> + msg_log "info: rpz: make config file \"block.rpz.conf\""
>>>>>> +
>>>>>> + # does block list exist?
>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>> +
>>>>>> + echo "rpz:
>>>>>> + name: block.rpz
>>>>>> + zonefile: /etc/unbound/zonefiles/block.rpz
>>>>>> + rpz-action-override: nxdomain
>>>>>> + rpz-log: ${theLog}
>>>>>> + rpz-log-name: block
>>>>>> + rpz-signal-nxdomain-ra: yes" > "${theConfig}"
>>>>>> +
>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>> + set_permissions "${theConfig}"
>>>>>> + else
>>>>>> + # no - trash the config file
>>>>>> + rm --verbose /etc/unbound/local.d/block.rpz.conf
>>>>>> + fi
>>>>>> +}
>>>>>> +
>>>>>> +# create an RPZ file for allow or block
>>>>>> +make_rpz_file () {
>>>>>> + local theName="${1}" # allow or block
>>>>>> + local theAction='.' # the default is nxdomain or block
>>>>>> + local actionList
>>>>>> +
>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>> + local theZonefile="/etc/unbound/zonefiles/${theName}.rpz" # output file for RPZ
>>>>>> +
>>>>>> + # does a list exist?
>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>> +
>>>>>> + # for allow set to passthru
>>>>>> + [[ "${theName}" == allow ]] && theAction='rpz-passthru.'
>>>>>> +
>>>>>> + # drop any extra "blanks" and add "CNAME <RPZ action>." to each line
>>>>>> + actionList=$( awk '{$1=$1};1' "${theList}" |
>>>>>> + sed "/^[^;].*[[:alnum:]]/ s|$| CNAME ${theAction}|" )
>>>>>> +
>>>>>> + msg_log "info: rpz: create zonefile for \"${theName}list\""
>>>>>> +
>>>>>> +echo "; Name: ${theName} list
>>>>>> +; Last modified: $(date "+%Y-%m-%d at %H.%M.%S %Z")
>>>>>> +;
>>>>>> +; domains with actions list
>>>>>> +;
>>>>>> +${actionList}" > "${theZonefile}"
>>>>>> +
>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>> + set_permissions "${theZonefile}"
>>>>>> + # set-up allow/block list files
>>>>>> + set_permissions "${theList}"
>>>>>> + else
>>>>>> + msg_log "info: rpz: the ${theList} is empty."
>>>>>> +
>>>>>> + rm --verbose "${theZonefile}" # trash the RPZ file
>>>>>> + fi
>>>>>> +}
>>>>>> +
>>>>>> +# check if allow/block list is valid
>>>>>> +validate_list () {
>>>>>> + local theName="${1}" # allow or block
>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>> +
>>>>>> + # remove good:
>>>>>> + # - properly formated domain names with or without leading wildcard
>>>>>> + # - properly formated top level domain (TLD) names with wildcard
>>>>>> + # - blank lines and comment lines
>>>>>> + # remaining lines are considered "bad"
>>>>>> + bad_lines=$( sed --regexp-extended \
>>>>>> + '/^(\*\.)?([a-zA-Z0-9](([a-zA-Z0-9\-]){0,61}[a-zA-Z0-9])?\.)+([a-zA-Z]{2,}|xn--[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])$/d ;
>>>>>> + /^(\*\.)([a-z]{2,61}|xn--[a-z0-9]{1,60})$/d ;
>>>>>> + /^$/d ; /^;/d' "${theList}" )
>>>>>> +
>>>>>> + if [[ ! -z "${bad_lines}" ]] ; then
>>>>>> + msg_log "error: rpz: invalid line(s) in ${theList}."
>>>>>> + printf "%s\n" "bad line(s): ${bad_lines}"
>>>>>> + exit 110
>>>>>> + fi
>>>>>> +}
>>>>>> +
>>>>>> +
>>>>>> +############### Main ###############
>>>>>> +
>>>>>> +tagName="unbound"
>>>>>> +
>>>>>> +rpzName="${1}" # input RPZ name
>>>>>> +
>>>>>> +rpzLog="yes" # log default is yes
>>>>>> +ucReload="yes" # reload default is yes
>>>>>> +
>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>> + case "$1" in
>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>> + esac
>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>> +done
>>>>>> +
>>>>>> +case "${rpzName}" in
>>>>>> + # make a new allow or block rpz file
>>>>>> +
>>>>>> + allow )
>>>>>> + validate_list 'allow' # is the allowlist valid?
>>>>>> + make_allow_config "${rpzLog}"
>>>>>> + make_rpz_file 'allow'
>>>>>> + ;;
>>>>>> +
>>>>>> + allowblock )
>>>>>> + validate_list 'allow' # is the list valid?
>>>>>> + make_allow_config "${rpzLog}"
>>>>>> + make_rpz_file 'allow'
>>>>>> + ;&
>>>>>> +
>>>>>> + block )
>>>>>> + validate_list 'block' # is the blocklist valid?
>>>>>> + make_block_config "${rpzLog}"
>>>>>> + make_rpz_file 'block'
>>>>>> + ;;
>>>>>> +
>>>>>> + reload )
>>>>>> + check_unbound_conf "${checkConf}"
>>>>>> + ;;
>>>>>> +
>>>>>> + unbound-restart )
>>>>>> + check_unbound_conf "${checkConf}"
>>>>>> + unbound_restart
>>>>>> + exit
>>>>>> + ;;
>>>>>> +
>>>>>> + * )
>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>> + printf "Usage: $(basename "$0") <NAME> <OPTION> <OPTION>\n"
>>>>>> + printf "Version: ${version}\n"
>>>>>> + exit 108
>>>>>> + ;;
>>>>>> +esac
>>>>>> +
>>>>>> +unbound_control_reload "${ucReload}"
>>>>>> +
>>>>>> +exit
>>>>>> diff --git a/config/rpz/rpz-metrics b/config/rpz/rpz-metrics
>>>>>> new file mode 100755
>>>>>> index 000000000..4d43e1629
>>>>>> --- /dev/null
>>>>>> +++ b/config/rpz/rpz-metrics
>>>>>> @@ -0,0 +1,170 @@
>>>>>> +#!/bin/bash
>>>>>> +###############################################################################
>>>>>> +# #
>>>>>> +# IPFire.org - A linux based firewall #
>>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>>> +# #
>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>> +# #
>>>>>> +###############################################################################
>>>>>> +
>>>>>> +version="2025-01-20 - v25"
>>>>>> +
>>>>>> +############### Main ###############
>>>>>> +
>>>>>> +weeks="2" # default to two message logs
>>>>>> +sortBy="name" # default "by name"
>>>>>> +rpzActive="enabled" # default "enabled only"
>>>>>> +
>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>> + case "$1" in
>>>>>> + --by-names | --by-name | name ) sortBy="name" ;;
>>>>>> +
>>>>>> + --by-hits | --by-hit | hits | hit ) sortBy="hit" ;;
>>>>>> +
>>>>>> + --by-lines | --by-line | lines | line ) sortBy="line" ;;
>>>>>> +
>>>>>> + --by-effect ) sortBy="effect" ;;
>>>>>> +
>>>>>> + --enabled-only ) rpzActive="enabled" ;;
>>>>>> +
>>>>>> + --active-all | --all | all ) rpzActive="all" ;;
>>>>>> +
>>>>>> + [0-9] | [0-9][0-9] ) weeks=$1 ;;
>>>>>> + esac
>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>> +done
>>>>>> +
>>>>>> +# get the list of message logs for N weeks
>>>>>> +messageLogs=$( find /var/log/messages* -type f | sort --version-sort |
>>>>>> + head -"${weeks}" )
>>>>>> +
>>>>>> +# get the list of RPZ names & counts from the message log(s)
>>>>>> +rpzNameCount=$( for logf in ${messageLogs} ; do
>>>>>> + zgrep --text --fixed-strings 'info: rpz: applied' "${logf}" |
>>>>>> + awk '$10 ~ /\[\w*]/ { print $10 }' ;
>>>>>> + done | sort | uniq --count )
>>>>>> +
>>>>>> +# flip results and remove brackets `[` and `]`
>>>>>> +rpzNameCount=$( echo "${rpzNameCount}" |
>>>>>> + awk '{ print $2, $1 }' |
>>>>>> + sed --regexp-extended 's|^\[(.*)\]|\1|' )
>>>>>> +
>>>>>> +# grab only names
>>>>>> +rpzNames=$( echo "${rpzNameCount}" | awk '{ print $1 }' )
>>>>>> +
>>>>>> +# get list of RPZ files
>>>>>> +rpzFileList=$( find /etc/unbound/zonefiles -type f -iname "*.rpz" )
>>>>>> +
>>>>>> +# get basename of those files
>>>>>> +rpzBaseNames=$( echo "${rpzFileList}" |
>>>>>> + sed 's|/etc/unbound/zonefiles/||g ; s|\.rpz||g ;' )
>>>>>> +
>>>>>> +# add to rpzNames
>>>>>> +rpzNames="${rpzNames}"$'\n'"${rpzBaseNames}"
>>>>>> +
>>>>>> +# drop duplicate names
>>>>>> +rpzNames=$( echo "${rpzNames}" | sort --unique )
>>>>>> +
>>>>>> +# get line count for each RPZ
>>>>>> +lineCount=$( echo "${rpzFileList}" | xargs wc -l )
>>>>>> +
>>>>>> +# get comment line count and blank line count for each RPZ
>>>>>> +commentCount=$( echo "${rpzFileList}" | xargs grep --count -e "^$" -e "^;" )
>>>>>> +
>>>>>> +# get modified date each RPZ
>>>>>> +modDateList=$( echo "${rpzFileList}" | xargs stat -c '%.10y %n' )
>>>>>> +
>>>>>> +ucListAuthZones=$( unbound-control list_auth_zones )
>>>>>> +
>>>>>> +# get width of RPZ names
>>>>>> +pWidth=$( echo "${rpzNames}" | awk '{ print $1" " }' | wc -L )
>>>>>> +pFormat="%-${pWidth}s %-8s %-8s %8s %12s %12s\n"
>>>>>> +
>>>>>> +# print title line
>>>>>> +printf "${pFormat}" "name" "hits" "active" "lines" " hits/line" "last download"
>>>>>> +printf -- "--------------"
>>>>>> +
>>>>>> +theResults=""
>>>>>> +totalLines=0
>>>>>> +totalHits=0
>>>>>> +while read -r theName
>>>>>> +do
>>>>>> + printf -- "--" # pretend progress bar
>>>>>> +
>>>>>> + # is this RPZ list active?
>>>>>> + theActive="disabled"
>>>>>> + if grep --quiet "^${theName}\.rpz" <<< "${ucListAuthZones}"
>>>>>> + then
>>>>>> + theActive="enabled"
>>>>>> + else
>>>>>> + [[ "${rpzActive}" == enabled ]] && continue
>>>>>> + fi
>>>>>> +
>>>>>> + # get hit count
>>>>>> + theHits="0"
>>>>>> + if output=$( grep "^${theName}\s" <<< "${rpzNameCount}" ) ; then
>>>>>> + theHits=$( echo "${output}" | awk '{ print $2 }' )
>>>>>> + totalHits=$(( totalHits + theHits ))
>>>>>> + fi
>>>>>> +
>>>>>> + # get line count
>>>>>> + theLines="n/a"
>>>>>> + hitsPerLine="0"
>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${lineCount}" ) ; then
>>>>>> + theLines=$( echo "${output}" | awk '{ print $1 }' )
>>>>>> + totalLines=$(( totalLines + theLines ))
>>>>>> +
>>>>>> + if [[ "${theLines}" -gt 2 ]] ; then
>>>>>> + hitsPerLine=$(( 100 * theHits / theLines ))
>>>>>> + fi
>>>>>> + fi
>>>>>> +
>>>>>> + # get modification date
>>>>>> + theModDate="n/a"
>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${modDateList}" ) ; then
>>>>>> + theModDate=$( echo "${output}" | awk '{ print $1 }' )
>>>>>> + fi
>>>>>> +
>>>>>> + # add to results list
>>>>>> + theResults+="${theName} ${theHits} ${theActive} ${theLines} ${hitsPerLine} ${theModDate}"$'\n'
>>>>>> +
>>>>>> +done <<< "${rpzNames}"
>>>>>> +
>>>>>> +case "${sortBy}" in
>>>>>> + # sort by "active" then by "name"
>>>>>> + name) sortArg=(-k3,3r -k1,1) ;;
>>>>>> +
>>>>>> + # sort by "active" then by "hits" then by "name"
>>>>>> + hit) sortArg=(-k3,3r -k2,2nr -k1,1) ;;
>>>>>> +
>>>>>> + # sort by "active" then by "lines" then by "name"
>>>>>> + line) sortArg=(-k3,3r -k4,4nr -k1,1) ;;
>>>>>> +
>>>>>> + # sort by "active" then by "effect" then by "name"
>>>>>> + effect) sortArg=(-k3,3r -k5,5nr -k1,1) ;;
>>>>>> +esac
>>>>>> +
>>>>>> +printf -- "--------------\n"
>>>>>> +# remove blank lines, sort, print as columns
>>>>>> +echo "${theResults}" |
>>>>>> + awk '!/^[[:space:]]*$/' |
>>>>>> + sort "${sortArg[@]}" |
>>>>>> + awk --assign=width="${pWidth}" \
>>>>>> + '{ printf "%-*s %-8s %-8s %8s %10s %% %12s\n", width, $1, $2, $3, $4, $5, $6 }'
>>>>>> +
>>>>>> +printf "${pFormat}" "" "=======" "" "========" "" ""
>>>>>> +printf "${pFormat}" "Totals -->" "${totalHits}" "" "${totalLines}" "" ""
>>>>>> +
>>>>>> +exit
>>>>>> diff --git a/config/rpz/rpz-sleep b/config/rpz/rpz-sleep
>>>>>> new file mode 100755
>>>>>> index 000000000..dd3603599
>>>>>> --- /dev/null
>>>>>> +++ b/config/rpz/rpz-sleep
>>>>>> @@ -0,0 +1,58 @@
>>>>>> +#!/bin/bash
>>>>>> +###############################################################################
>>>>>> +# #
>>>>>> +# IPFire.org - A linux based firewall #
>>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>>> +# #
>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>> +# #
>>>>>> +###############################################################################
>>>>>> +
>>>>>> +version="2024-08-16" # v05
>>>>>> +
>>>>>> +############### Functions ###############
>>>>>> +
>>>>>> +# send message to message log
>>>>>> +msg_log () {
>>>>>> + logger --tag "${tagName}" "$*"
>>>>>> + if tty --silent ; then
>>>>>> + echo "${tagName}:" "$*"
>>>>>> + fi
>>>>>> +}
>>>>>> +
>>>>>> +############### Main ###############
>>>>>> +
>>>>>> +tagName="unbound"
>>>>>> +
>>>>>> +sleepTime="${1:-5m}" # default to sleep for 5m (5 minutes)
>>>>>> +
>>>>>> +zoneList=$( unbound-control list_auth_zones | awk '{print $1}' )
>>>>>> +
>>>>>> +for zone in ${zoneList} ; do
>>>>>> + printf "disable ${zone}\t"
>>>>>> + unbound-control rpz_disable "${zone}"
>>>>>> +done
>>>>>> +
>>>>>> +msg_log "info: rpz: disabled all zones for ${sleepTime}"
>>>>>> +
>>>>>> +sleep "${sleepTime}"
>>>>>> +
>>>>>> +for zone in ${zoneList} ; do
>>>>>> + printf "enable ${zone}\t"
>>>>>> + unbound-control rpz_enable "${zone}"
>>>>>> +done
>>>>>> +
>>>>>> +msg_log "info: rpz: enabled all zones"
>>>>>> +
>>>>>> +exit
>>>>>> diff --git a/config/rpz/rpz.de.pl b/config/rpz/rpz.de.pl
>>>>>> new file mode 100644
>>>>>> index 000000000..3770c6bb0
>>>>>> --- /dev/null
>>>>>> +++ b/config/rpz/rpz.de.pl
>>>>>> @@ -0,0 +1,30 @@
>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>> +%tr = (%tr,
>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>> +'rpz apply' => 'Übernehmen',
>>>>>> +'rpz cl allow enable' => 'Benutzerdefinierte Allowlist aktivieren:',
>>>>>> +'rpz cl allow info' => 'Zugelassene Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>> +'rpz cl allow' => 'Benutzerdefinierte Allowlist',
>>>>>> +'rpz cl block enable' => 'Benutzerdefinierte Blocklist aktivieren:',
>>>>>> +'rpz cl block info' => 'Gesperrte Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>> +'rpz cl block' => 'Benutzerdefinierte Blocklist',
>>>>>> +'rpz cl' => 'Benutzerdefinierte Listen',
>>>>>> +'rpz exitcode 101' => 'Der Name enthält unzulässige Zeichen',
>>>>>> +'rpz exitcode 102' => 'unbound-checkconf hat eine fehlerhafte Konfiguration ermittelt. Führen Sie das Kommando unbound-checkconf auf der Konsole aus, um weitere Informationen zu erhalten.',
>>>>>> +'rpz exitcode 103' => 'Die benutzerdefinierte Allow-/Blocklist ist leer',
>>>>>> +'rpz exitcode 104' => 'Ein Eintrag mit identischem Namen existiert bereits',
>>>>>> +'rpz exitcode 105' => 'Die URL ist ungültig',
>>>>>> +'rpz exitcode 106' => 'Eintrag kann nicht entfernt werden, der Name existiert nicht',
>>>>>> +'rpz exitcode 107' => 'Der Name ist ungültig - nur "allow" oder "block" möglich',
>>>>>> +'rpz exitcode 108' => 'Fehlende oder inkorrekte Parameter',
>>>>>> +'rpz exitcode 109' => 'unbound-control reload ist fehlgeschlagen',
>>>>>> +'rpz exitcode 110' => 'Die benutzerdefinierte Allow-/Blocklist enthält unzulässige Einträge',
>>>>>> +'rpz exitcode 201' => 'Die Anmerkung enthält unzulässige Zeichen',
>>>>>> +'rpz exitcode 202' => 'Ungültiger Eintrag in der benutzerdefinierten Allowlist, Zeile ',
>>>>>> +'rpz exitcode 203' => 'Ungültiger Eintrag in der benutzerdefinierten Blocklist, Zeile ',
>>>>>> +'rpz exitcode 204' => 'Ausgewählter Eintrag existiert nicht: ',
>>>>>> +'rpz zf editor' => 'Zonendatei-Eintrag bearbeiten',
>>>>>> +'rpz zf imported' => '(importiert aus rpz-config)',
>>>>>> +'rpz zf remark info' => 'Erlaubte Zeichen sind a-z, A-Z, 0-9 und Unterstriche',
>>>>>> +'rpz zf' => 'Zonendateien',
>>>>>> +);
>>>>>> diff --git a/config/rpz/rpz.en.pl b/config/rpz/rpz.en.pl
>>>>>> new file mode 100644
>>>>>> index 000000000..0720a8940
>>>>>> --- /dev/null
>>>>>> +++ b/config/rpz/rpz.en.pl
>>>>>> @@ -0,0 +1,30 @@
>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>> +%tr = (%tr,
>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>> +'rpz apply' => 'Apply',
>>>>>> +'rpz cl allow enable' => 'Enable custom allowlist:',
>>>>>> +'rpz cl allow info' => 'Allowed domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>> +'rpz cl allow' => 'Custom allowlist',
>>>>>> +'rpz cl block enable' => 'Enable custom blocklist:',
>>>>>> +'rpz cl block info' => 'Blocked domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>> +'rpz cl block' => 'Custom blocklist',
>>>>>> +'rpz cl' => 'Custom lists',
>>>>>> +'rpz exitcode 101' => 'the NAME is not valid',
>>>>>> +'rpz exitcode 102' => 'unbound-checkconf found invalid configuration. In the Terminal run the command unbound-checkconf for more information',
>>>>>> +'rpz exitcode 103' => 'the allow/block list is empty',
>>>>>> +'rpz exitcode 104' => 'duplicate - NAME already exists',
>>>>>> +'rpz exitcode 105' => 'the URL is not valid',
>>>>>> +'rpz exitcode 106' => 'cannot remove the NAME does not exist',
>>>>>> +'rpz exitcode 107' => 'the NAME is not valid - "allow" or "block" only',
>>>>>> +'rpz exitcode 108' => 'missing or incorrect parameter',
>>>>>> +'rpz exitcode 109' => 'unbound-control reload failed',
>>>>>> +'rpz exitcode 110' => 'custom Allowlist/Blocklist contains invalid entries',
>>>>>> +'rpz exitcode 201' => 'the REMARK is not valid',
>>>>>> +'rpz exitcode 202' => 'invalid entry in allowlist, line ',
>>>>>> +'rpz exitcode 203' => 'invalid entry in blocklist, line ',
>>>>>> +'rpz exitcode 204' => 'Selected entry does not exist: ',
>>>>>> +'rpz zf editor' => 'Edit zonefiles entry',
>>>>>> +'rpz zf imported' => '(imported from rpz-config)',
>>>>>> +'rpz zf remark info' => 'Valid characters are a-z, A-Z, 0-9 and underscore.',
>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>> +);
>>>>>> diff --git a/config/rpz/rpz.es.pl b/config/rpz/rpz.es.pl
>>>>>> new file mode 100644
>>>>>> index 000000000..98628e4aa
>>>>>> --- /dev/null
>>>>>> +++ b/config/rpz/rpz.es.pl
>>>>>> @@ -0,0 +1,30 @@
>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>> +%tr = (%tr,
>>>>>> +'rpz' => 'Zonas de política de respuesta (RPZ)',
>>>>>> +'rpz apply' => 'Aplicar',
>>>>>> +'rpz cl allow enable' => 'Habilitar la lista blanca personalizada:',
>>>>>> +'rpz cl allow info' => 'Dominio permitido (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>> +'rpz cl allow' => 'Lista blanca personalizada',
>>>>>> +'rpz cl block enable' => 'Habilitar la lista negra personalizada:',
>>>>>> +'rpz cl block info' => 'Dominio bloqueado (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>> +'rpz cl block' => 'Lista negra personalizada',
>>>>>> +'rpz cl' => 'Lista personalizada',
>>>>>> +'rpz exitcode 101' => 'El NOMBRE no es válido',
>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha encontrado una configuración no válida. Desde Terminal, ejecute el comando unbound-checkconf para mayor información',
>>>>>> +'rpz exitcode 103' => 'La lista de permitidos/bloqueados está vacía',
>>>>>> +'rpz exitcode 104' => 'duplicado - NOMBRE ya existe',
>>>>>> +'rpz exitcode 105' => 'la URL no es válida',
>>>>>> +'rpz exitcode 106' => 'no es posible eliminar el NOMBRE que no existe',
>>>>>> +'rpz exitcode 107' => 'el NOMBRE no es válido - sólo "permitir" o "bloquear"',
>>>>>> +'rpz exitcode 108' => 'parámetro faltante o incorrecto',
>>>>>> +'rpz exitcode 109' => 'Error al recargar unbound-control',
>>>>>> +'rpz exitcode 110' => 'la Lista blanca/Lista negra personalizada contiene entradas no válidas',
>>>>>> +'rpz exitcode 201' => 'el COMENTARIO no es válido',
>>>>>> +'rpz exitcode 202' => 'entrada no válida en la lista blanca, línea ',
>>>>>> +'rpz exitcode 203' => 'entrada no válida en la lista negra, línea ',
>>>>>> +'rpz exitcode 204' => 'La entrada seleccionada no existe: ',
>>>>>> +'rpz zf editor' => 'Editar la entrada de archivos de zona',
>>>>>> +'rpz zf imported' => '(importado de rpz-config)',
>>>>>> +'rpz zf remark info' => 'Los caracteres válidos son a-z, A-Z, 0-9 y guión bajo',
>>>>>> +'rpz zf' => 'Archivos de zona',
>>>>>> +);
>>>>>> diff --git a/config/rpz/rpz.fr.pl b/config/rpz/rpz.fr.pl
>>>>>> new file mode 100644
>>>>>> index 000000000..f35f3c2d0
>>>>>> --- /dev/null
>>>>>> +++ b/config/rpz/rpz.fr.pl
>>>>>> @@ -0,0 +1,30 @@
>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>> +%tr = (%tr,
>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>> +'rpz apply' => 'Appliquer',
>>>>>> +'rpz cl allow enable' => 'Activer la liste d\'autorisations personnalisée:',
>>>>>> +'rpz cl allow info' => 'Domaines autorisés (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>> +'rpz cl allow' => 'Liste d\'autorisations personnalisée',
>>>>>> +'rpz cl block enable' => 'Activer la liste de blocage personnalisée:',
>>>>>> +'rpz cl block info' => 'Domaines bloqués (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>> +'rpz cl block' => 'liste de blocage personnalisé',
>>>>>> +'rpz cl' => 'Listes personnalisées',
>>>>>> +'rpz exitcode 101' => 'le NOM n\'est pas valide',
>>>>>> +'rpz exitcode 102' => 'unbound-checkconf configuration non valide trouvée. Dans le terminal, exécutez la commande unbound-checkconf pour plus d\'informations',
>>>>>> +'rpz exitcode 103' => 'la liste autoriser/bloquer est vide',
>>>>>> +'rpz exitcode 104' => 'le NOM existe déjà',
>>>>>> +'rpz exitcode 105' => 'L\'URL n\'est pas valide',
>>>>>> +'rpz exitcode 106' => 'impossible de supprimer le NOM n\'existe pas',
>>>>>> +'rpz exitcode 107' => 'le NOM n\'est pas valide - « autoriser » ou « bloquer » seulement',
>>>>>> +'rpz exitcode 108' => 'paramètre manquant ou incorrect',
>>>>>> +'rpz exitcode 109' => 'unbound-control rechargement échoué',
>>>>>> +'rpz exitcode 110' => 'la liste autoriser/bloquer contient des entrées non valides',
>>>>>> +'rpz exitcode 201' => 'la REMARQUE n\'est pas valable',
>>>>>> +'rpz exitcode 202' => 'entrée non valide dans la liste d\'autorisation, ligne ',
>>>>>> +'rpz exitcode 203' => 'entrée non valide dans la liste de blocs, ligne ',
>>>>>> +'rpz exitcode 204' => 'L\'entrée sélectionnée n\'existe pas: ',
>>>>>> +'rpz zf editor' => 'Modifier l\'entrée Fichiers Zone',
>>>>>> +'rpz zf imported' => '(importé de rpz-config)',
>>>>>> +'rpz zf remark info' => 'Les caractères valides sont a-z, A-Z, 0-9 et soulignement.',
>>>>>> +'rpz zf' => 'Fichiers Zone',
>>>>>> +);
>>>>>> diff --git a/config/rpz/rpz.it.pl b/config/rpz/rpz.it.pl
>>>>>> new file mode 100644
>>>>>> index 000000000..ee81605c9
>>>>>> --- /dev/null
>>>>>> +++ b/config/rpz/rpz.it.pl
>>>>>> @@ -0,0 +1,30 @@
>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>> +%tr = (%tr,
>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>> +'rpz apply' => 'Applica',
>>>>>> +'rpz cl allow enable' => 'Abilita la Whitelist personalizzata:',
>>>>>> +'rpz cl allow info' => 'Domini consentiti (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>> +'rpz cl allow' => 'Whitelist personalizzata',
>>>>>> +'rpz cl block enable' => 'Abilita la Blacklist personalizzata:',
>>>>>> +'rpz cl block info' => 'Domini bloccati (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>> +'rpz cl block' => 'Blacklist personalizzata',
>>>>>> +'rpz cl' => 'Liste personalizzate',
>>>>>> +'rpz exitcode 101' => 'il NOME non è valido',
>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha trovato una configurazione non valida. Dal Terminale esegui il comando unbound-checkconf per maggiori informazioni',
>>>>>> +'rpz exitcode 103' => 'l\'elenco consentiti/bloccati è vuoto',
>>>>>> +'rpz exitcode 104' => 'duplicato - NAME esiste di già',
>>>>>> +'rpz exitcode 105' => 'l\'URL non è valido',
>>>>>> +'rpz exitcode 106' => 'non è possibile rimuovere il NOME non esiste',
>>>>>> +'rpz exitcode 107' => 'il NOME non è valido - solo "consenti" o "blocca"',
>>>>>> +'rpz exitcode 108' => 'parametro mancante o non corretto',
>>>>>> +'rpz exitcode 109' => 'ricaricamento del controllo non associato non riuscito',
>>>>>> +'rpz exitcode 110' => 'la Whitelist/Blacklist personalizzata contiene voci non valide',
>>>>>> +'rpz exitcode 201' => 'l"OSSERVAZIONE non è valida',
>>>>>> +'rpz exitcode 202' => 'voce non valida nella Whitelist, riga ',
>>>>>> +'rpz exitcode 203' => 'voce non valida nella Blacklist, riga ',
>>>>>> +'rpz exitcode 204' => 'La voce selezionata non esiste: ',
>>>>>> +'rpz zf editor' => 'Modifica la voce dei file di zona',
>>>>>> +'rpz zf imported' => '(importato da rpz-config)',
>>>>>> +'rpz zf remark info' => 'I caratteri validi sono a-z, A-Z, 0-9 e trattino basso',
>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>> +);
>>>>>> diff --git a/config/rpz/rpz.tr.pl b/config/rpz/rpz.tr.pl
>>>>>> new file mode 100644
>>>>>> index 000000000..00226e192
>>>>>> --- /dev/null
>>>>>> +++ b/config/rpz/rpz.tr.pl
>>>>>> @@ -0,0 +1,30 @@
>>>>>> +# I�in eklendi Ayriyeten Response Policy Zone (RPZ)
>>>>>> +%tr = (%tr,
>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>> +'rpz apply' => 'Uygulamak',
>>>>>> +'rpz cl allow enable' => '�zel Etkinlestir allowlist:',
>>>>>> +'rpz cl allow info' => 'Izin verilmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>> +'rpz cl allow' => 'Etkinlestir allowlist',
>>>>>> +'rpz cl block enable' => '�zel Etkinlestir blocklist:',
>>>>>> +'rpz cl block info' => 'Engellenmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>> +'rpz cl block' => 'Engellenmis blocklist',
>>>>>> +'rpz cl' => 'Engellenmis lists',
>>>>>> +'rpz exitcode 101' => 'NAME ge�erli degil',
>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ge�ersiz yapilandirma bulundu. Terminalde daha fazla bilgi i�in Unbound-checkConf komutunu �alistirin',
>>>>>> +'rpz exitcode 103' => 'allow/block liste bos',
>>>>>> +'rpz exitcode 104' => 'kopyalamak - NAME zaten var',
>>>>>> +'rpz exitcode 105' => 'URL ge�erli degil',
>>>>>> +'rpz exitcode 106' => '�ikarilamiyor NAME yok',
>>>>>> +'rpz exitcode 107' => 'NAME ge�erli degil - "allow" veya "block" yalniz',
>>>>>> +'rpz exitcode 108' => 'Parametre eksik veya yanlis',
>>>>>> +'rpz exitcode 109' => 'unbound-control basarisiz',
>>>>>> +'rpz exitcode 110' => 'Engellenmis Allowlist/Blocklist ge�ersiz girisler i�erir',
>>>>>> +'rpz exitcode 201' => 'REMARK ge�erli degil',
>>>>>> +'rpz exitcode 202' => 'Ge�ersiz giris allowlist, line ',
>>>>>> +'rpz exitcode 203' => 'Ge�ersiz giris blocklist, line ',
>>>>>> +'rpz exitcode 204' => 'Se�ilen giris yok: ',
>>>>>> +'rpz zf editor' => 'yazimlamak zonefiles giris',
>>>>>> +'rpz zf imported' => '(ithal edildi rpz-config)',
>>>>>> +'rpz zf remark info' => 'Ge�erli karakterler a-z, A-Z, 0-9ve alt�st.',
>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>> +);
>>>>>> diff --git a/html/cgi-bin/rpz.cgi b/html/cgi-bin/rpz.cgi
>>>>>> new file mode 100644
>>>>>> index 000000000..a821c92ac
>>>>>> --- /dev/null
>>>>>> +++ b/html/cgi-bin/rpz.cgi
>>>>>> @@ -0,0 +1,923 @@
>>>>>> +#!/usr/bin/perl
>>>>>> +###############################################################################
>>>>>> +# #
>>>>>> +# IPFire.org - A linux based firewall #
>>>>>> +# Copyright (C) 2005-2024 IPFire Team <info(a)ipfire.org> #
>>>>>> +# #
>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>> +# #
>>>>>> +###############################################################################
>>>>>> +
>>>>>> +use strict;
>>>>>> +use Scalar::Util qw(looks_like_number);
>>>>>> +
>>>>>> +# debugging
>>>>>> +#use warnings;
>>>>>> +#use CGI::Carp 'fatalsToBrowser';
>>>>>> +#use Data::Dumper;
>>>>>> +
>>>>>> +require '/var/ipfire/general-functions.pl';
>>>>>> +require "${General::swroot}/lang.pl";
>>>>>> +require "${General::swroot}/header.pl";
>>>>>> +
>>>>>> +###--- Extra HTML ---###
>>>>>> +my $extraHead = <<END
>>>>>> +<style>
>>>>>> + /* alternating row background */
>>>>>> + .tbl tr:nth-child(2n+2) {
>>>>>> + background-color: var(--color-light-grey);
>>>>>> + }
>>>>>> + .tbl tr:nth-child(2n+3) {
>>>>>> + background-color: var(--color-grey);
>>>>>> + }
>>>>>> + /* text styles */
>>>>>> + .tbl th:not(:last-child) {
>>>>>> + text-align: left;
>>>>>> + }
>>>>>> + div.right {
>>>>>> + text-align: right;
>>>>>> + margin-top: 0.5em;
>>>>>> + }
>>>>>> + /* customlist input */
>>>>>> + textarea.domainlist {
>>>>>> + margin: 0.5em 0;
>>>>>> + resize: vertical;
>>>>>> + min-height: 10em;
>>>>>> + overflow: auto;
>>>>>> + white-space: pre;
>>>>>> + }
>>>>>> + button[type=submit]:disabled {
>>>>>> + opacity: 0.6;
>>>>>> + }
>>>>>> +</style>
>>>>>> +END
>>>>>> +;
>>>>>> +###--- End of extra HTML ---###
>>>>>> +
>>>>>> +
>>>>>> +### Settings ###
>>>>>> +
>>>>>> +# Request DNS service reload after configuration change
>>>>>> +my $RPZ_RELOAD_FLAG = "${General::swroot}/dns/rpz/reload.flag";
>>>>>> +
>>>>>> +# Configuration file for all available zonefiles
>>>>>> +# Format: index, name (unique), enabled (on/off), URL, remark
>>>>>> +my $ZONEFILES_CONF = "${General::swroot}/dns/rpz/zonefiles.conf";
>>>>>> +
>>>>>> +# Configuration file for custom lists
>>>>>> +# IDs: 0=allowlist, 1=blocklist, 2=options (allow/block enabled)
>>>>>> +my $CUSTOMLISTS_CONF = "${General::swroot}/dns/rpz/customlists.conf";
>>>>>> +
>>>>>> +# Export custom lists to rpz-config
>>>>>> +my $RPZ_ALLOWLIST = "${General::swroot}/dns/rpz/allowlist";
>>>>>> +my $RPZ_BLOCKLIST = "${General::swroot}/dns/rpz/blocklist";
>>>>>> +
>>>>>> +
>>>>>> +### Preparation ###
>>>>>> +
>>>>>> +# Create missing config files
>>>>>> +unless(-f $ZONEFILES_CONF) { &General::system('touch', "$ZONEFILES_CONF"); }
>>>>>> +unless(-f $CUSTOMLISTS_CONF) { &General::system('touch', "$CUSTOMLISTS_CONF"); }
>>>>>> +
>>>>>> +
>>>>>> +## Global gui data
>>>>>> +my $errormessage = "";
>>>>>> +
>>>>>> +## Global configuration data
>>>>>> +my %zonefiles = ();
>>>>>> +my %customlists = ();
>>>>>> +&_zonefiles_load();
>>>>>> +&_customlists_load();
>>>>>> +
>>>>>> +## Global CGI form data
>>>>>> +my %cgiparams = ();
>>>>>> +&Header::getcgihash(\%cgiparams);
>>>>>> +
>>>>>> +my $action = $cgiparams{'ACTION'} // 'NONE';
>>>>>> +my $action_key = $cgiparams{'KEY'} // ''; # entry being edited, empty = none/new
>>>>>> +
>>>>>> +
>>>>>> +###--- Process form actions ---###
>>>>>> +
>>>>>> +# Zonefiles action: Check whether the requested entry exists
>>>>>> +if((substr($action, 0, 3) eq 'ZF_') && ($action_key)) {
>>>>>> + unless(defined $zonefiles{$action_key}) {
>>>>>> + $errormessage = &_rpz_error_tr(204, $action_key);
>>>>>> + $action = 'NONE';
>>>>>> + }
>>>>>> +}
>>>>>> +
>>>>>> +## Perform actions
>>>>>> +if($action eq 'ZF_SAVE') { ## Save new or modified zonefiles entry
>>>>>> + if(&_action_zf_save()) {
>>>>>> + $action = 'NONE'; # success, return to main page
>>>>>> + &_http_prg_redirect();
>>>>>> + } else {
>>>>>> + $action = 'ZF_EDIT'; # error occured, keep editing
>>>>>> + }
>>>>>> +
>>>>>> +} elsif($action eq 'ZF_TOGGLE') { ## Toggle on/off
>>>>>> + if(&_action_zf_toggle()) {
>>>>>> + $action = 'NONE';
>>>>>> + &_http_prg_redirect();
>>>>>> + }
>>>>>> +
>>>>>> +} elsif($action eq 'ZF_REMOVE') { ## Remove entry
>>>>>> + if(&_action_zf_remove()) {
>>>>>> + $action = 'NONE';
>>>>>> + &_http_prg_redirect();
>>>>>> + }
>>>>>> +
>>>>>> +} elsif($action eq 'CL_SAVE') { ## Save custom lists
>>>>>> + if(&_action_cl_save()) {
>>>>>> + $action = 'NONE';
>>>>>> + &_http_prg_redirect();
>>>>>> + }
>>>>>> +
>>>>>> +} elsif($action eq 'RPZ_RELOAD') { ## Reload dns configuration
>>>>>> + if(&_action_rpz_reload()) {
>>>>>> + $action = 'NONE';
>>>>>> + &_http_prg_redirect();
>>>>>> + }
>>>>>> +
>>>>>> +} elsif($action eq 'UNB_RESTART') { ## Restart unbound service
>>>>>> + if(&_action_unb_restart()) {
>>>>>> + $action = 'NONE';
>>>>>> + &_http_prg_redirect();
>>>>>> + }
>>>>>> +
>>>>>> +}
>>>>>> +
>>>>>> +
>>>>>> +###--- Start GUI ---###
>>>>>> +
>>>>>> +## Start http output
>>>>>> +&Header::showhttpheaders();
>>>>>> +
>>>>>> +# Start HTML
>>>>>> +&Header::openpage($Lang::tr{'rpz'}, 1, $extraHead);
>>>>>> +&Header::openbigbox('100%', 'left', '');
>>>>>> +
>>>>>> +# Show error messages
>>>>>> +if($errormessage) {
>>>>>> + &_print_message($errormessage);
>>>>>> +}
>>>>>> +
>>>>>> +# Handle zonefile add/edit mode
>>>>>> +if($action eq "ZF_EDIT") {
>>>>>> + &_print_zonefile_editor();
>>>>>> +
>>>>>> + # Finalize page and exit cleanly
>>>>>> + &Header::closebigbox();
>>>>>> + &Header::closepage();
>>>>>> + exit(0);
>>>>>> +}
>>>>>> +
>>>>>> +# Show gui elements
>>>>>> +&_print_zonefiles();
>>>>>> +&_print_customlists();
>>>>>> +&_print_gui_extras();
>>>>>> +
>>>>>> +&Header::closebigbox();
>>>>>> +&Header::closepage();
>>>>>> +
>>>>>> +###--- End of GUI ---###
>>>>>> +
>>>>>> +
>>>>>> +###--- Internal configuration file functions ---###
>>>>>> +
>>>>>> +# Load all available zonefiles from rpz-config and the internal configuration
>>>>>> +sub _zonefiles_load {
>>>>>> + # Clean start
>>>>>> + %zonefiles = ();
>>>>>> +
>>>>>> + # Source 1: Get the currently enabled zonefiles from rpz-config (expected format [name]=[URL])
>>>>>> + my @enabled_files = &General::system_output('/usr/sbin/rpz-config', 'list');
>>>>>> +
>>>>>> + foreach my $row (@enabled_files) {
>>>>>> + chomp($row);
>>>>>> +
>>>>>> + # Use regex instead of split() to skip non-matching lines
>>>>>> + next unless($row =~ /^(\w+)=(.+)$/);
>>>>>> + my ($name, $url) = ($1, $2);
>>>>>> +
>>>>>> + # Unique names are already guaranteed by rpz-config
>>>>>> + if(&_rpz_validate_zonefile($name, $url, '', 0) == 0) {
>>>>>> + # Populate global data hash, mark all found entries as enabled
>>>>>> + my %entry = ('enabled' => 'on',
>>>>>> + 'url' => $url,
>>>>>> + 'remark' => $Lang::tr{'rpz zf imported'});
>>>>>> +
>>>>>> + $zonefiles{$name} = \%entry;
>>>>>> + }
>>>>>> + }
>>>>>> +
>>>>>> + # Source 2: Get additional data and disabled entries from configuration file
>>>>>> + my %configured_files = ();
>>>>>> + &General::readhasharray($ZONEFILES_CONF, \%configured_files);
>>>>>> +
>>>>>> + foreach my $row (values (%configured_files)) {
>>>>>> + my ($name, $enabled, $url, $remark) = @$row;
>>>>>> + $remark //= "";
>>>>>> +
>>>>>> + next unless($name);
>>>>>> +
>>>>>> + # Check whether this row belongs to an entry already imported from rpz-config
>>>>>> + if(defined $zonefiles{$name}) {
>>>>>> + # Existing entry, only merge additional data
>>>>>> + $zonefiles{$name}{'remark'} = $remark;
>>>>>> + } else {
>>>>>> + # Skip entry if it is marked as enabled but not found by rpz-config. It was then deleted manually
>>>>>> + if($enabled ne 'on') {
>>>>>> + # Populate global data hash
>>>>>> + my %entry = ('enabled' => 'off',
>>>>>> + 'url' => $url // "",
>>>>>> + 'remark' => $remark);
>>>>>> +
>>>>>> + $zonefiles{$name} = \%entry;
>>>>>> + }
>>>>>> + }
>>>>>> + }
>>>>>> +}
>>>>>> +
>>>>>> +# Save internal zonefiles configuration
>>>>>> +sub _zonefiles_save_conf {
>>>>>> + my $index = 0;
>>>>>> + my %export = ();
>>>>>> +
>>>>>> + # Loop trough all zonefiles and create "hasharray" type export
>>>>>> + foreach my $name (keys %zonefiles) {
>>>>>> + my @entry = ($name,
>>>>>> + $zonefiles{$name}{'enabled'},
>>>>>> + $zonefiles{$name}{'url'},
>>>>>> + $zonefiles{$name}{'remark'});
>>>>>> +
>>>>>> + $export{$index++} = \@entry;
>>>>>> + }
>>>>>> +
>>>>>> + &General::writehasharray($ZONEFILES_CONF, \%export);
>>>>>> +}
>>>>>> +
>>>>>> +# Load custom lists from rpz-config and the internal configuration
>>>>>> +sub _customlists_load {
>>>>>> + # Clean start
>>>>>> + %customlists = ();
>>>>>> +
>>>>>> + # Load configuration file
>>>>>> + my %lists_conf = ();
>>>>>> + &General::readhasharray($CUSTOMLISTS_CONF, \%lists_conf);
>>>>>> +
>>>>>> + # Get list options, enabled by default to start import
>>>>>> + $customlists{'allow'}{'enabled'} = $lists_conf{2}[0] // 'on';
>>>>>> + $customlists{'block'}{'enabled'} = $lists_conf{2}[1] // 'on';
>>>>>> +
>>>>>> + # Import enabled list from rpz-config, otherwise retrieve stored or empty list from configuration file
>>>>>> + if($customlists{'allow'}{'enabled'} eq 'on') {
>>>>>> + &_customlist_import('allow', $RPZ_ALLOWLIST);
>>>>>> + } else {
>>>>>> + $customlists{'allow'}{'list'} = $lists_conf{0} // [];
>>>>>> + }
>>>>>> + if($customlists{'block'}{'enabled'} eq 'on') {
>>>>>> + &_customlist_import('block', $RPZ_BLOCKLIST);
>>>>>> + } else {
>>>>>> + $customlists{'block'}{'list'} = $lists_conf{1} // [];
>>>>>> + }
>>>>>> +}
>>>>>> +
>>>>>> +# Save internal custom lists configuration
>>>>>> +sub _customlists_save_conf {
>>>>>> + my %export = ();
>>>>>> +
>>>>>> + # Match IDs with import function
>>>>>> + $export{0} = $customlists{'allow'}{'list'};
>>>>>> + $export{1} = $customlists{'block'}{'list'};
>>>>>> + $export{2} = [$customlists{'allow'}{'enabled'}, $customlists{'block'}{'enabled'}];
>>>>>> +
>>>>>> + &General::writehasharray($CUSTOMLISTS_CONF, \%export);
>>>>>> +}
>>>>>> +
>>>>>> +# Import a custom list from plain file, returns empty list if file is missing
>>>>>> +sub _customlist_import {
>>>>>> + my ($listname, $filename) = @_;
>>>>>> + my @list = ();
>>>>>> +
>>>>>> + # File exists, load and check all lines
>>>>>> + if(-f $filename) {
>>>>>> + open(my $FH, '<', $filename) or die "Can't read $filename: $!";
>>>>>> + while(my $line = <$FH>) {
>>>>>> + chomp($line);
>>>>>> + push(@list, $line);
>>>>>> + }
>>>>>> + close($FH);
>>>>>> +
>>>>>> + # Clean up imported data
>>>>>> + &_rpz_validate_customlist(\@list, 1);
>>>>>> + }
>>>>>> +
>>>>>> + $customlists{$listname}{'list'} = \@list;
>>>>>> +}
>>>>>> +
>>>>>> +# Export a custom list to plain file or clear file if list is disabled
>>>>>> +sub _customlist_export {
>>>>>> + my ($listname, $filename) = @_;
>>>>>> + return unless(defined $customlists{$listname});
>>>>>> +
>>>>>> + # Write enabled domain list to file, otherwise save empty file
>>>>>> + open(my $FH, '>', $filename) or die "Can't write $filename: $!";
>>>>>> +
>>>>>> + if($customlists{$listname}{'enabled'} eq 'on') {
>>>>>> + foreach my $line (@{$customlists{$listname}{'list'}}) {
>>>>>> + print $FH "$line\n";
>>>>>> + }
>>>>>> + } else {
>>>>>> + print $FH "; Note: This list is currently disabled by $ENV{'SCRIPT_NAME'}\n";
>>>>>> + }
>>>>>> +
>>>>>> + close($FH);
>>>>>> +}
>>>>>> +
>>>>>> +
>>>>>> +###--- Internal gui functions ---###
>>>>>> +
>>>>>> +# Show simple message box
>>>>>> +sub _print_message {
>>>>>> + my ($message, $title) = @_;
>>>>>> + $title ||= $Lang::tr{'error messages'};
>>>>>> +
>>>>>> + &Header::openbox('100%', 'left', $title);
>>>>>> + print "<span>$message</span>";
>>>>>> + &Header::closebox();
>>>>>> +}
>>>>>> +
>>>>>> +# Show all zone files and related gui elements
>>>>>> +sub _print_zonefiles {
>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf'});
>>>>>> +
>>>>>> + print <<END
>>>>>> +<table class="tbl" width="100%">
>>>>>> + <tr>
>>>>>> + <th>$Lang::tr{'name'}</th>
>>>>>> + <th>URL</th>
>>>>>> + <th>$Lang::tr{'remark'}</th>
>>>>>> + <th colspan="3">$Lang::tr{'action'}</th>
>>>>>> + </tr>
>>>>>> +END
>>>>>> +;
>>>>>> +
>>>>>> + # Sort zonefiles by name and loop trough all entries
>>>>>> + foreach my $name (sort keys %zonefiles) {
>>>>>> +
>>>>>> + # Toggle button label translation
>>>>>> + my $toggle_tr = ($zonefiles{$name}{'enabled'} eq 'on') ? $Lang::tr{'click to disable'} : $Lang::tr{'click to enable'};
>>>>>> +
>>>>>> + print <<END
>>>>>> + <tr>
>>>>>> + <td>$name</td>
>>>>>> + <td>$zonefiles{$name}{'url'}</td>
>>>>>> + <td>$zonefiles{$name}{'remark'}</td>
>>>>>> +
>>>>>> + <td align="center" width="5%">
>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>> + <input type="hidden" name="ACTION" value="ZF_TOGGLE">
>>>>>> + <input type="image" src="/images/$zonefiles{$name}{'enabled'}.gif" title="$toggle_tr" alt="$toggle_tr">
>>>>>> + </form>
>>>>>> + </td>
>>>>>> + <td align="center" width="5%">
>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>> + <input type="hidden" name="ACTION" value="ZF_EDIT">
>>>>>> + <input type="image" src="/images/edit.gif" title="$Lang::tr{'edit'}" alt="$Lang::tr{'edit'}">
>>>>>> + </form>
>>>>>> + </td>
>>>>>> + <td align="center" width="5%">
>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>> + <input type="hidden" name="ACTION" value="ZF_REMOVE">
>>>>>> + <input type="image" src="/images/delete.gif" title="$Lang::tr{'remove'}" alt="$Lang::tr{'remove'}">
>>>>>> + </form>
>>>>>> + </td>
>>>>>> + </tr>
>>>>>> +END
>>>>>> +;
>>>>>> + }
>>>>>> +
>>>>>> + # Disable reload button if not needed
>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>> +
>>>>>> + print <<END
>>>>>> +</table>
>>>>>> +
>>>>>> +<div class="right">
>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>> + <input type="hidden" name="KEY" value="">
>>>>>> + <button type="submit" name="ACTION" value="ZF_EDIT">$Lang::tr{'add'}</button>
>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>> + </form>
>>>>>> +</div>
>>>>>> +END
>>>>>> +;
>>>>>> +
>>>>>> + &Header::closebox();
>>>>>> +}
>>>>>> +
>>>>>> +# Show zonefiles entry editor
>>>>>> +sub _print_zonefile_editor {
>>>>>> +
>>>>>> + # Key specified: Edit existing entry
>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>> + # Load data to be edited, but don't override already present values (allows user to edit after error)
>>>>>> + $cgiparams{'ZF_NAME'} //= $action_key;
>>>>>> + $cgiparams{'ZF_URL'} //= $zonefiles{$action_key}{'url'};
>>>>>> + $cgiparams{'ZF_REMARK'} //= $zonefiles{$action_key}{'remark'};
>>>>>> + }
>>>>>> +
>>>>>> + # Fallback to empty form
>>>>>> + $cgiparams{'ZF_NAME'} //= "";
>>>>>> + $cgiparams{'ZF_URL'} //= "";
>>>>>> + $cgiparams{'ZF_REMARK'} //= "";
>>>>>> +
>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf editor'});
>>>>>> +
>>>>>> + print <<END
>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>> +<input type="hidden" name="KEY" value="$action_key">
>>>>>> +<table width="100%">
>>>>>> + <tr>
>>>>>> + <td width="20%">$Lang::tr{'name'}: <img src="/blob.gif" alt="*"></td>
>>>>>> + <td><input type="text" name="ZF_NAME" value="$cgiparams{'ZF_NAME'}" size="40" maxlength="32" title="$Lang::tr{'rpz zf remark info'}" pattern="[a-zA-Z0-9_]{1,32}" required></td>
>>>>>> + </tr>
>>>>>> + <tr>
>>>>>> + <td width="20%">URL: <img src="/blob.gif" alt="*"></td>
>>>>>> + <td><input type="url" name="ZF_URL" value="$cgiparams{'ZF_URL'}" size="40" maxlength="128" required></td>
>>>>>> + </tr>
>>>>>> + <tr>
>>>>>> + <td width="20%">$Lang::tr{'remark'}:</td>
>>>>>> + <td><input type="text" name="ZF_REMARK" value="$cgiparams{'ZF_REMARK'}" size="40" maxlength="32"></td>
>>>>>> + </tr>
>>>>>> + <tr>
>>>>>> + <td colspan="2"><hr></td>
>>>>>> + </tr>
>>>>>> + <tr>
>>>>>> + <td width="55%"><img src="/blob.gif" alt="*"> $Lang::tr{'required field'}</td>
>>>>>> + <td align="right"><button type="submit" name="ACTION" value="ZF_SAVE">$Lang::tr{'save'}</button></td>
>>>>>> + </tr>
>>>>>> +</table>
>>>>>> +</form>
>>>>>> +
>>>>>> +<div class="right">
>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>> + <button type="submit" name="ACTION" value="NONE">$Lang::tr{'back'}</button>
>>>>>> + </form>
>>>>>> +</div>
>>>>>> +END
>>>>>> +;
>>>>>> +
>>>>>> + &Header::closebox();
>>>>>> +}
>>>>>> +
>>>>>> +# Show custom allow/block files and related gui elements
>>>>>> +sub _print_customlists {
>>>>>> +
>>>>>> + # Load lists from config, unless they are currently being edited
>>>>>> + if($action ne 'CL_SAVE') {
>>>>>> + $cgiparams{'ALLOW_LIST'} = join("\n", @{$customlists{'allow'}{'list'}});
>>>>>> + $cgiparams{'BLOCK_LIST'} = join("\n", @{$customlists{'block'}{'list'}});
>>>>>> +
>>>>>> + $cgiparams{'ALLOW_ENABLED'} = ($customlists{'allow'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>> + $cgiparams{'BLOCK_ENABLED'} = ($customlists{'block'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>> + }
>>>>>> +
>>>>>> + # Fallback to empty form
>>>>>> + $cgiparams{'ALLOW_LIST'} //= "";
>>>>>> + $cgiparams{'BLOCK_LIST'} //= "";
>>>>>> +
>>>>>> + # HTML checkboxes, unchecked = no or undef value in POST data
>>>>>> + my %checked = ();
>>>>>> + $checked{'ALLOW_ENABLED'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? " checked" : "";
>>>>>> + $checked{'BLOCK_ENABLED'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? " checked" : "";
>>>>>> +
>>>>>> + # Disable reload button if not needed
>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>> +
>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz cl'});
>>>>>> +
>>>>>> + print <<END
>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>> +<table width="100%">
>>>>>> + <tr>
>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl allow'}</b><br>$Lang::tr{'rpz cl allow info'}</td>
>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl block'}</b><br>$Lang::tr{'rpz cl block info'}</td>
>>>>>> + </tr>
>>>>>> + <tr>
>>>>>> + <td colspan="2"><textarea name="ALLOW_LIST" class="domainlist" cols="45">
>>>>>> +$cgiparams{'ALLOW_LIST'}</textarea></td>
>>>>>> + <td colspan="2"><textarea name="BLOCK_LIST" class="domainlist" cols="45">
>>>>>> +$cgiparams{'BLOCK_LIST'}</textarea></td>
>>>>>> + </tr>
>>>>>> + <tr>
>>>>>> + <td><label for="allow_enabled">$Lang::tr{'rpz cl allow enable'}</label></td>
>>>>>> + <td width="15%"><input type="checkbox" name="ALLOW_ENABLED" id="allow_enabled"$checked{'ALLOW_ENABLED'}></td>
>>>>>> + <td><label for="block_enabled">$Lang::tr{'rpz cl block enable'}</label></td>
>>>>>> + <td width="15%"><input type="checkbox" name="BLOCK_ENABLED" id="block_enabled"$checked{'BLOCK_ENABLED'}></td>
>>>>>> + </tr>
>>>>>> + <tr>
>>>>>> + <td colspan="4"><hr></td>
>>>>>> + </tr>
>>>>>> + <tr>
>>>>>> + <td align="right" colspan="4">
>>>>>> + <button type="submit" name="ACTION" value="CL_SAVE">$Lang::tr{'save'}</button>
>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>> + </td>
>>>>>> +</table>
>>>>>> +</form>
>>>>>> +END
>>>>>> +;
>>>>>> +
>>>>>> + &Header::closebox();
>>>>>> +}
>>>>>> +
>>>>>> +# Output javascript and extra gui elements
>>>>>> +sub _print_gui_extras {
>>>>>> +
>>>>>> + # Apply/Restart button modifier key handler
>>>>>> + if(&_rpz_needs_reload()) {
>>>>>> + print <<END
>>>>>> +<script>
>>>>>> + // Commit modifier key handler
>>>>>> + (function(jq, document) {
>>>>>> + var keyEventsOn = false; // Keyboard events attached
>>>>>> + var keyModify = false; // Modifier key pressed
>>>>>> + var mouseHover = false; // Mouse over commit button
>>>>>> + var btnModified = false; // Button modified to "Restart"
>>>>>> +
>>>>>> + // Document-level key events, enable only while cursor is over button
>>>>>> + function attachKeyEvents() {
>>>>>> + if(keyEventsOn) {
>>>>>> + return;
>>>>>> + }
>>>>>> + keyEventsOn = true;
>>>>>> +
>>>>>> + jq(document).on("keydown.rpz", function(event) {
>>>>>> + if((!keyModify) && event.shiftKey) {
>>>>>> + keyModify = true;
>>>>>> + handleModify();
>>>>>> + }
>>>>>> + });
>>>>>> + jq(document).on("keyup.rpz", function(event) {
>>>>>> + if(keyModify && (!event.shiftKey)) {
>>>>>> + keyModify = false;
>>>>>> + handleModify();
>>>>>> + }
>>>>>> + });
>>>>>> + }
>>>>>> + function removeKeyEvents() {
>>>>>> + keyModify = false;
>>>>>> + if(keyEventsOn) {
>>>>>> + jq(document).off("keydown.rpz keyup.rpz");
>>>>>> + keyEventsOn = false;
>>>>>> + }
>>>>>> + }
>>>>>> +
>>>>>> + // Attach mouse hover events to commit buttons
>>>>>> + function attachMouseEvents() {
>>>>>> + jq("button.commit").on("mouseenter", function(event) {
>>>>>> + if(!mouseHover) {
>>>>>> + mouseHover = true;
>>>>>> + attachKeyEvents();
>>>>>> + // Handle already pressed key
>>>>>> + keyModify = !!(event.shiftKey);
>>>>>> + handleModify();
>>>>>> + }
>>>>>> + });
>>>>>> +
>>>>>> + // Cursor moved away: Disable key listener to minimize events
>>>>>> + jq("button.commit").on("mouseleave", function() {
>>>>>> + if(mouseHover) {
>>>>>> + mouseHover = false;
>>>>>> + removeKeyEvents();
>>>>>> + handleModify();
>>>>>> + }
>>>>>> + });
>>>>>> + }
>>>>>> +
>>>>>> + // Modify commit button
>>>>>> + function handleModify() {
>>>>>> + let modify = mouseHover && keyModify;
>>>>>> + if(btnModified != modify) {
>>>>>> + if(modify) {
>>>>>> + jq("button.commit").text("$Lang::tr{'restart'}").val("UNB_RESTART");
>>>>>> + } else {
>>>>>> + jq("button.commit").text("$Lang::tr{'rpz apply'}").val("RPZ_RELOAD");
>>>>>> + }
>>>>>> + btnModified = modify;
>>>>>> + }
>>>>>> + }
>>>>>> +
>>>>>> + // jQuery DOM ready
>>>>>> + jq(function() {
>>>>>> + attachMouseEvents();
>>>>>> + });
>>>>>> + })(jQuery, document);
>>>>>> +</script>
>>>>>> +END
>>>>>> +;
>>>>>> + } # End of modifier key handler
>>>>>> +
>>>>>> +}
>>>>>> +
>>>>>> +
>>>>>> +###--- Internal action processing functions ---###
>>>>>> +
>>>>>> +# Toggle zonefile on/off
>>>>>> +sub _action_zf_toggle {
>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>> +
>>>>>> + my $result = 0;
>>>>>> + my $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>> +
>>>>>> + # Perform toggle action
>>>>>> + if($enabled eq 'on') {
>>>>>> + $enabled = 'off';
>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>> + } else {
>>>>>> + $enabled = 'on';
>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $action_key, $zonefiles{$action_key}{'url'}, '--no-reload');
>>>>>> + }
>>>>>> +
>>>>>> + # Check for errors, request service reload on success
>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>> +
>>>>>> + # Save changes
>>>>>> + $zonefiles{$action_key}{'enabled'} = $enabled;
>>>>>> + &_zonefiles_save_conf();
>>>>>> +
>>>>>> + return 1;
>>>>>> +}
>>>>>> +
>>>>>> +# Remove zonefile
>>>>>> +sub _action_zf_remove {
>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>> +
>>>>>> + # Remove from rpz-config if currently active
>>>>>> + if($zonefiles{$action_key}{'enabled'} eq 'on') {
>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>> +
>>>>>> + # Check for errors, request service reload on success
>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>> + }
>>>>>> +
>>>>>> + # Remove from data hash and save changes
>>>>>> + delete $zonefiles{$action_key};
>>>>>> + &_zonefiles_save_conf();
>>>>>> +
>>>>>> + # Clear action_key, as the entry is now removed entirely
>>>>>> + $action_key = "";
>>>>>> +
>>>>>> + return 1;
>>>>>> +}
>>>>>> +
>>>>>> +# Create or update zonefile entry
>>>>>> +# Returns undef if gui needs to stay in editor mode
>>>>>> +sub _action_zf_save {
>>>>>> + my $result = 0;
>>>>>> +
>>>>>> + my $name = $cgiparams{'ZF_NAME'} // "";
>>>>>> + my $url = $cgiparams{'ZF_URL'} // "";
>>>>>> + my $remark = $cgiparams{'ZF_REMARK'} // "";
>>>>>> + my $enabled = 'on'; # Enable new entries by default
>>>>>> +
>>>>>> + # Note on variables:
>>>>>> + # name = unique key, will be used to address the entry
>>>>>> + # action_key = name of the entry being edited, empty for new entry
>>>>>> +
>>>>>> + # Only check for unique name if it changed
>>>>>> + # (this also checks new entries because the action_key is empty in this case)
>>>>>> + $result = &_rpz_validate_zonefile($name, $url, $remark, (lc($name) ne lc($action_key)));
>>>>>> + return unless &_rpz_check_result($result, 0);
>>>>>> +
>>>>>> + # Edit existing entry: Determine what was changed
>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>> + # Name und URL remain unchanged, only save remark and finish
>>>>>> + if(($name eq $action_key) && ($url eq $zonefiles{$action_key}{'url'})) {
>>>>>> + $zonefiles{$action_key}{'remark'} = $remark;
>>>>>> + &_zonefiles_save_conf();
>>>>>> +
>>>>>> + return 1;
>>>>>> + }
>>>>>> +
>>>>>> + # Entry was changed and needs to be recreated, preserve status
>>>>>> + $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>> +
>>>>>> + # Remove from rpz-config
>>>>>> + return unless &_action_zf_remove();
>>>>>> + }
>>>>>> +
>>>>>> + # Add new entry to rpz-config
>>>>>> + if($enabled eq 'on') {
>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $name, $url, '--no-reload');
>>>>>> +
>>>>>> + # Check for errors, request service reload on success
>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>> + }
>>>>>> +
>>>>>> + # Add to global data hash and save changes
>>>>>> + my %entry = ('enabled' => $enabled,
>>>>>> + 'url' => $url,
>>>>>> + 'remark' => $remark);
>>>>>> +
>>>>>> + $zonefiles{$name} = \%entry;
>>>>>> + &_zonefiles_save_conf();
>>>>>> +
>>>>>> + return 1;
>>>>>> +}
>>>>>> +
>>>>>> +# Save custom lists
>>>>>> +sub _action_cl_save {
>>>>>> + return unless((defined $cgiparams{'ALLOW_LIST'}) && (defined $cgiparams{'BLOCK_LIST'}));
>>>>>> +
>>>>>> + my $result = 0;
>>>>>> +
>>>>>> + my @allowlist = split(/\R/, $cgiparams{'ALLOW_LIST'});
>>>>>> + my @blocklist = split(/\R/, $cgiparams{'BLOCK_LIST'});
>>>>>> +
>>>>>> + # Validate lists
>>>>>> + $result = &_rpz_validate_customlist(\@allowlist);
>>>>>> + if($result != 0) {
>>>>>> + $errormessage = &_rpz_error_tr(202, $result);
>>>>>> + return;
>>>>>> + }
>>>>>> + $result = &_rpz_validate_customlist(\@blocklist);
>>>>>> + if($result != 0) {
>>>>>> + $errormessage = &_rpz_error_tr(203, $result);
>>>>>> + return;
>>>>>> + }
>>>>>> +
>>>>>> + # Add to global data hash and save changes
>>>>>> + $customlists{'allow'}{'list'} = \@allowlist;
>>>>>> + $customlists{'block'}{'list'} = \@blocklist;
>>>>>> + $customlists{'allow'}{'enabled'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? 'on' : 'off';
>>>>>> + $customlists{'block'}{'enabled'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? 'on' : 'off';
>>>>>> +
>>>>>> + &_customlists_save_conf();
>>>>>> + &_customlist_export('allow', $RPZ_ALLOWLIST);
>>>>>> + &_customlist_export('block', $RPZ_BLOCKLIST);
>>>>>> +
>>>>>> + # Make new lists, request service reload on success
>>>>>> + $result = &General::system('/usr/sbin/rpz-make', 'allowblock', '--no-reload');
>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>> +
>>>>>> + return 1;
>>>>>> +}
>>>>>> +
>>>>>> +# Trigger rpz-config reload
>>>>>> +sub _action_rpz_reload {
>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>> +
>>>>>> + # Immediately clear flag to prevent multiple reloads
>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>> + }
>>>>>> +
>>>>>> + # Perform reload, recreate reload flag on error to enable retry
>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'reload');
>>>>>> + if(not &_rpz_check_result($result, 0)) {
>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>> + return;
>>>>>> + }
>>>>>> +
>>>>>> + return 1;
>>>>>> +}
>>>>>> +
>>>>>> +# Trigger unbound restart
>>>>>> +sub _action_unb_restart {
>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>> +
>>>>>> + # Immediately clear flag to prevent multiple restarts
>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>> + }
>>>>>> +
>>>>>> + # Perform restart, unboundctrl always exits zero
>>>>>> + &General::system('/usr/local/bin/unboundctrl', 'restart');
>>>>>> +
>>>>>> + return 1;
>>>>>> +}
>>>>>> +
>>>>>> +
>>>>>> +###--- Internal rpz-config functions ---###
>>>>>> +
>>>>>> +# Translate rpz-config exitcodes and messages
>>>>>> +# 100-199: rpz-config, 200-299: webgui
>>>>>> +sub _rpz_error_tr {
>>>>>> + my ($error, $append) = @_;
>>>>>> + $append //= '';
>>>>>> +
>>>>>> + # Translate numeric exit codes
>>>>>> + if(looks_like_number($error)) {
>>>>>> + if(defined $Lang::tr{"rpz exitcode $error"}) {
>>>>>> + $error = $Lang::tr{"rpz exitcode $error"};
>>>>>> + }
>>>>>> + }
>>>>>> +
>>>>>> + return "RPZ $Lang::tr{'error'}: $error" . &Header::escape($append);
>>>>>> +}
>>>>>> +
>>>>>> +# Check result of rpz-config system call, request reload on success
>>>>>> +sub _rpz_check_result {
>>>>>> + my ($result, $request_reload) = @_;
>>>>>> + $request_reload //= 0;
>>>>>> +
>>>>>> + # exitcode 0 = success
>>>>>> + if($result != 0) {
>>>>>> + $errormessage = &_rpz_error_tr($result);
>>>>>> + return;
>>>>>> + }
>>>>>> +
>>>>>> + # Set reload flag
>>>>>> + if($request_reload) {
>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>> + }
>>>>>> +
>>>>>> + return 1;
>>>>>> +}
>>>>>> +
>>>>>> +# Test whether reload flag is set
>>>>>> +sub _rpz_needs_reload {
>>>>>> + return (-f $RPZ_RELOAD_FLAG);
>>>>>> +}
>>>>>> +
>>>>>> +# Validate a zonefile entry, returns rpz-config exitcode on failure. Use _rpz_check_result to verify.
>>>>>> +# unique = check for unique name
>>>>>> +sub _rpz_validate_zonefile {
>>>>>> + my ($name, $url, $remark, $unique) = @_;
>>>>>> + $unique //= 1;
>>>>>> +
>>>>>> + unless($name =~ /^[a-zA-Z0-9_]{1,32}$/) {
>>>>>> + return 101;
>>>>>> + }
>>>>>> + unless($url =~ /^[\w+\.:;\/\\&@#%?=\-~|!]{1,128}$/) {
>>>>>> + return 105;
>>>>>> + }
>>>>>> + unless($remark =~ /^[\w \-()\.:;*\/\\?!&=]{0,32}$/) {
>>>>>> + return 201;
>>>>>> + }
>>>>>> +
>>>>>> + # Check against already existing names
>>>>>> + if($unique) {
>>>>>> + foreach my $existing (keys %zonefiles) {
>>>>>> + if(lc($name) eq lc($existing)) {
>>>>>> + return 104;
>>>>>> + }
>>>>>> + }
>>>>>> + }
>>>>>> +
>>>>>> + return 0;
>>>>>> +}
>>>>>> +
>>>>>> +# Validate a custom list, returns number of rejected line on failure. Check for non-zero results.
>>>>>> +# listref = array reference, cleanup = remove invalid entries instead of returning an error
>>>>>> +sub _rpz_validate_customlist {
>>>>>> + my ($listref, $cleanup) = @_;
>>>>>> + $cleanup //= 0;
>>>>>> +
>>>>>> + foreach my $index (reverse 0..$#{$listref}) {
>>>>>> + my $row = @$listref[$index];
>>>>>> + next unless($row); # Skip/allow empty lines
>>>>>> +
>>>>>> + # Reject/remove everything besides wildcard domains and remarks
>>>>>> + if((not &General::validwildcarddomainname($row)) && (not $row =~ /^;[\w \-()\.:;*\/\\?!&=]*$/)) {
>>>>>> + unless($cleanup) {
>>>>>> + # +1 for user friendly line number and to ensure non-zero exitcode
>>>>>> + return $index + 1;
>>>>>> + }
>>>>>> +
>>>>>> + # Remove current row
>>>>>> + splice(@$listref, $index, 1);
>>>>>> + }
>>>>>> + }
>>>>>> +
>>>>>> + return 0;
>>>>>> +}
>>>>>> +
>>>>>> +
>>>>>> +###--- Internal misc functions ---###
>>>>>> +
>>>>>> +# Send HTTP 303 redirect headers for post/request/get pattern
>>>>>> +# (Must be sent before calling &Header::showhttpheaders())
>>>>>> +sub _http_prg_redirect {
>>>>>> + my $location = "https://$ENV{'SERVER_NAME'}:$ENV{'SERVER_PORT'}$ENV{'SCRIPT_NAME'}";
>>>>>> + print "Status: 303 See Other\n";
>>>>>> + print "Location: $location\n";
>>>>>> +}
>>>>>> diff --git a/lfs/rpz b/lfs/rpz
>>>>>> new file mode 100644
>>>>>> index 000000000..7ddbc38e5
>>>>>> --- /dev/null
>>>>>> +++ b/lfs/rpz
>>>>>> @@ -0,0 +1,96 @@
>>>>>> +###############################################################################
>>>>>> +# #
>>>>>> +# IPFire.org - A linux based firewall #
>>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>>> +# #
>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>> +# #
>>>>>> +###############################################################################
>>>>>> +
>>>>>> +###############################################################################
>>>>>> +# Definitions
>>>>>> +###############################################################################
>>>>>> +
>>>>>> +include Config
>>>>>> +
>>>>>> +SUMMARY = response policy zone - RPZ reputation system for unbound DNS
>>>>>> +
>>>>>> +VER = 1.0.0
>>>>>> +
>>>>>> +THISAPP = rpz-$(VER)
>>>>>> +DIR_APP = $(DIR_SRC)/$(THISAPP)
>>>>>> +TARGET = $(DIR_INFO)/$(THISAPP)
>>>>>> +
>>>>>> +PROG = rpz
>>>>>> +PAK_VER = 18
>>>>>> +
>>>>>> +DEPS =
>>>>>> +
>>>>>> +SERVICES =
>>>>>> +
>>>>>> +###############################################################################
>>>>>> +# Top-level Rules
>>>>>> +###############################################################################
>>>>>> +
>>>>>> +install : $(TARGET)
>>>>>> +
>>>>>> +check :
>>>>>> +
>>>>>> +download :
>>>>>> +
>>>>>> +b2 :
>>>>>> +
>>>>>> +dist:
>>>>>> + @$(PAK)
>>>>>> +
>>>>>> +###############################################################################
>>>>>> +# Installation Details
>>>>>> +###############################################################################
>>>>>> +
>>>>>> +$(TARGET) :
>>>>>> + @$(PREBUILD)
>>>>>> + @rm -rf $(DIR_APP)
>>>>>> +
>>>>>> + # RPZ scripts
>>>>>> + install --verbose --mode=755 \
>>>>>> + $(DIR_CONF)/rpz/{rpz-config,rpz-metrics,rpz-sleep,rpz-make,rpz-functions} \
>>>>>> + --target-directory=/usr/sbin
>>>>>> +
>>>>>> + # RPZ config files
>>>>>> + mkdir -pv /etc/unbound/local.d
>>>>>> + install --verbose --mode=644 --owner=nobody --group=nobody \
>>>>>> + $(DIR_CONF)/rpz/00-rpz.conf \
>>>>>> + --target-directory=/etc/unbound/local.d
>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/local.d
>>>>>> +
>>>>>> + # RPZ custom list files for allow and block
>>>>>> + mkdir -pv /var/ipfire/dns/rpz
>>>>>> + touch /var/ipfire/dns/rpz/{allowlist,blocklist}
>>>>>> + chown --verbose --recursive nobody:nobody /var/ipfire/dns/rpz
>>>>>> +
>>>>>> + # RPZ zone files
>>>>>> + # create empty RPZ config file to avoid a unbound config error
>>>>>> + mkdir -pv /etc/unbound/zonefiles
>>>>>> + touch /etc/unbound/zonefiles/allow.rpz
>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/zonefiles
>>>>>> +
>>>>>> + # Install addon-specific language-files
>>>>>> + install --verbose --mode=004 $(DIR_CONF)/rpz/rpz.*.pl \
>>>>>> + --target-directory=/var/ipfire/addon-lang
>>>>>> +
>>>>>> + # Install backup definition
>>>>>> + cp -vf $(DIR_CONF)/backup/includes/rpz /var/ipfire/backup/addons/includes/rpz
>>>>>> +
>>>>>> + @rm -rf $(DIR_APP)
>>>>>> + @$(POSTBUILD)
>>>>>> diff --git a/make.sh b/make.sh
>>>>>> index 827ea9e77..a77535b13 100755
>>>>>> --- a/make.sh
>>>>>> +++ b/make.sh
>>>>>> @@ -390,7 +390,7 @@ prepareenv() {
>>>>>> if [ "${free_space}" -lt "${required_space}" ]; then
>>>>>> # Add any consumed space
>>>>>> while read -r consumed_space path; do
>>>>>> - (( free_space += consumed_space / 1024 / 1024 ))
>>>>>> + (( free_space += consumed_space / 1024 / 1024 ))
>>>>>> done <<< "$(du --summarize --bytes "${BUILD_DIR}" "${IMAGES_DIR}" "${LOG_DIR}" 2>/dev/null)"
>>>>>> fi
>>>>>>
>>>>>> @@ -2087,6 +2087,7 @@ build_system() {
>>>>>> lfsmake2 btrfs-progs
>>>>>> lfsmake2 inotify-tools
>>>>>> lfsmake2 grub-btrfs
>>>>>> + lfsmake2 rpz
>>>>>>
>>>>>> lfsmake2 linux
>>>>>> lfsmake2 rtl8812au
>>>>>> diff --git a/src/paks/rpz/install.sh b/src/paks/rpz/install.sh
>>>>>> new file mode 100644
>>>>>> index 000000000..ef99bf742
>>>>>> --- /dev/null
>>>>>> +++ b/src/paks/rpz/install.sh
>>>>>> @@ -0,0 +1,36 @@
>>>>>> +#!/bin/bash
>>>>>> +###############################################################################
>>>>>> +# #
>>>>>> +# IPFire.org - A linux based firewall #
>>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>>> +# #
>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>> +# #
>>>>>> +###############################################################################
>>>>>> +#
>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>> +extract_files
>>>>>> +restore_backup ${NAME}
>>>>>> +
>>>>>> +# fix user created files
>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>> + /var/ipfire/dns/rpz \
>>>>>> + /etc/unbound/zonefiles \
>>>>>> + /etc/unbound/local.d
>>>>>> +
>>>>>> +# Update Language cache
>>>>>> +/usr/local/bin/update-lang-cache
>>>>>> +
>>>>>> +# restart unbound to load config file
>>>>>> +/etc/init.d/unbound restart
>>>>>> diff --git a/src/paks/rpz/uninstall.sh b/src/paks/rpz/uninstall.sh
>>>>>> new file mode 100644
>>>>>> index 000000000..e11427df3
>>>>>> --- /dev/null
>>>>>> +++ b/src/paks/rpz/uninstall.sh
>>>>>> @@ -0,0 +1,38 @@
>>>>>> +#!/bin/bash
>>>>>> +###############################################################################
>>>>>> +# #
>>>>>> +# IPFire.org - A linux based firewall #
>>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>>> +# #
>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>> +# #
>>>>>> +###############################################################################
>>>>>> +#
>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>> +
>>>>>> +# stop unbound to delete RPZ conf file
>>>>>> +/etc/init.d/unbound stop
>>>>>> +
>>>>>> +make_backup ${NAME}
>>>>>> +remove_files
>>>>>> +
>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>> +# "[1723428668] unbound-control[17117:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>> +
>>>>>> +# Update Language cache
>>>>>> +/usr/local/bin/update-lang-cache
>>>>>> +
>>>>>> +# start unbound to load unbound config file
>>>>>> +/etc/init.d/unbound start
>>>>>> diff --git a/src/paks/rpz/update.sh b/src/paks/rpz/update.sh
>>>>>> new file mode 100644
>>>>>> index 000000000..9bc340bc6
>>>>>> --- /dev/null
>>>>>> +++ b/src/paks/rpz/update.sh
>>>>>> @@ -0,0 +1,52 @@
>>>>>> +#!/bin/bash
>>>>>> +###############################################################################
>>>>>> +# #
>>>>>> +# IPFire.org - A linux based firewall #
>>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>>> +# #
>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>> +# #
>>>>>> +###############################################################################
>>>>>> +#
>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>> +
>>>>>> +# from update.sh
>>>>>> +extract_backup_includes
>>>>>> +
>>>>>> +# stop unbound to delete RPZ conf file
>>>>>> +/etc/init.d/unbound stop
>>>>>> +
>>>>>> +# from uninstall.sh
>>>>>> +make_backup ${NAME}
>>>>>> +remove_files
>>>>>> +
>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>> +# "unbound-control[nn:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>> +
>>>>>> +# from install.sh
>>>>>> +extract_files
>>>>>> +restore_backup ${NAME}
>>>>>> +
>>>>>> +# fix user created files
>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>> + /var/ipfire/dns/rpz \
>>>>>> + /etc/unbound/zonefiles \
>>>>>> + /etc/unbound/local.d
>>>>>> +
>>>>>> +# Update Language cache
>>>>>> +/usr/local/bin/update-lang-cache
>>>>>> +
>>>>>> +# restart unbound to load config files
>>>>>> +/etc/init.d/unbound start
>>>>>> --
>>>>>> 2.39.5
>>>>>>
>>>> Jon
>>>>
>>>>
>>>> --
>>>> Jon Murphy
>>>> jon.murphy(a)ipfire.org
>>
>> Jon
>>
>>
>> --
>> Jon Murphy
>> jon.murphy(a)ipfire.org
>>
>>
>>
>>
--
Sent from my laptop
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
[not found] ` <3BF29525-C9F4-4FD2-834D-FBE791E99E8C@ipfire.org>
@ 2025-03-02 10:51 ` Adolf Belka
2025-03-10 17:47 ` jon
0 siblings, 1 reply; 30+ messages in thread
From: Adolf Belka @ 2025-03-02 10:51 UTC (permalink / raw)
To: development
[-- Attachment #1: Type: text/plain, Size: 140557 bytes --]
Hi Jon,
On 01/03/2025 23:22, jon wrote:
> No thank you. I pass.
Okay.
You should definitely look at putting together an entry for the Roadmap as outlined by Michael. There is a template page you can use to put together an initial entry which can then be used for further discussions in the dev mailing list.
Best regards,
Adolf.
>
> Jon
>
>> On Mar 1, 2025, at 4:18 AM, Adolf Belka <adolf.belka(a)ipfire.org> wrote:
>>
>> Hi Jon,
>>
>> Would you like to have this topic added to the agenda for the next Dev Conf Call scheduled for 10th March?
>>
>> The time of the conf call is 8:00 to 10:00pm Central European Time.
>>
>> Best regards,
>>
>> Adolf.
>>
>>
>> On 14/02/2025 13:07, Michael Tremer wrote:
>>> Hello Jon,
>>>
>>> It very much depends on the kind of contribution. A one-line patch obviously has fewer strings attached to it than a larger patch set like this.
>>>
>>> However, we have outlined the process in the wiki already, starting from here:
>>>
>>> https://www.ipfire.org/docs/devel/submit-patches
>>>
>>> This contains some useful pointers about the how (how do I actually make my changes happen, how do I build IPFire, etc), and at the bottom it contains a lot of information about the format the changes should be submitted; split into smaller chunks that are ideally as independent from each other so that they can be individually reviewed and merged. Usually a development process takes long time and we have already shipped parts of code that we will need for certain features that are not ready yet. This is a good practice to let code mature, especially when it is touching rather critical bits like the firewall and networking stacks.
>>>
>>> There are also some guidelines on how to write a good commit message and how to use Git tags:
>>>
>>> https://www.ipfire.org/docs/devel/git/commit-messages
>>> https://www.ipfire.org/docs/devel/git/tags
>>>
>>> Then there is something about how to get in touch with the right person and legal stuff:
>>>
>>> https://www.ipfire.org/docs/devel/contact
>>> https://www.ipfire.org/docs/legal/ipca
>>>
>>> Finally, we have a bit of an underused roadmap section on the wiki. It would be nice if we could use that a little bit more because then it would be easier for everyone to keep track of progress on certain features; people could see what is being worked on and see if they can help development and testing and so on:
>>>
>>> https://www.ipfire.org/docs/roadmap
>>>
>>> There is a template on how to create new pages:
>>>
>>> https://www.ipfire.org/docs/roadmap/template
>>>
>>> And this is a good example of what this could look like:
>>>
>>> https://www.ipfire.org/docs/roadmap/openvpn-26
>>>
>>> All of these steps are coming *after* there has been some initial discussion about what actually has a chance to become part of the distribution. For that, we do not have any specific guidelines because it is not very trivial to write these things. There are just too many possibilities. In the past, there has also been very little need for this, but that does not mean that there have not been problems before.
>>>
>>> The reason why I am raising the bar this high here is simply that we have made mistakes in the past that we don’t want to repeat. We have learned a couple of lessons in a not very pleasant way and I under no circumstances would want to do this again. The objective is that we want to provide an excellent distribution. Although IPFire of course has its shortcomings here and there, it is a very stable distribution and we have a very good track record that I want to keep. This is what our users deserve.
>>>
>>> In the past, people have “dropped” their patches on this list (or sometimes elsewhere) and we were left with dealing with the entire integration only to find out later what problems there were hidden in the code. The original author(s) had no interest in fixing any of that because it worked just fine for them, and so why spend any time on the problems of somebody else? Usually I am the fallback for this and I simply don’t want to be that. I have lots of my own projects inside IPFire that are moving at snail speed because fixing existing code usually takes priority over writing new code.
>>>
>>> Therefore we need a commitment to sort out these problems in the first place. It has to be proven that people actually *care* about the patches that they post here. I am not sure this needs writing down as this should be the same policy with almost any open source project. If you contribute a line, there is probably less maintenance required in the future, but if you contribute a large code base, then you will need to look after it for the foreseeable future. It is your feature and not mine after all.
>>>
>>> Then, what actually has a chance to make it into the distribution? Probably not a lot. IPFire has a very clear use case. There will not be any space for a desktop environment and running Chrome on it, we also don’t it to make coffee and cook me a dinner. We would currently only accept things that were actually maintainable by the current team in case a contributor moves on (see above), because we simply only have so much man power. We already have a large zoo of features that are very abandoned and we are potentially looking at getting rid of more things simply because we cannot support them properly. Time just doesn’t permit. Adding something large is therefore very difficult at the moment.
>>>
>>> I understand that in this specific case you have been trying to not involve the development team and I understand your motivation. But you cannot forget about how much time and effort a review process can take. Therefore we want to plan things well; we want to even split it; and we want to have a conversation in advance so that the roadmap is clear and the actual code review ideally only becomes a formality.
>>>
>>> All of this above has been for a general case. Please read through this and feel free to ask any questions if something isn’t clear.
>>>
>>> To move forward with this feature, we should start by planning a roadmap. We need to discuss what this project should cover and what it should not cover. I believe we don’t need to talk much about implementation details because you have figured out a lot of them; we need to find what feature we want to provide to our users. Are you up for that?
>>>
>>> Best,
>>> -Michael
>>>
>>>> On 13 Feb 2025, at 21:34, jon <jon.murphy(a)ipfire.org> wrote:
>>>>
>>>> Michael,
>>>>
>>>> I’ve read through your comments a few times and I ended up with many more questions.
>>>>
>>>>
>>>>> What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>>
>>>> To me the efforts to get new code accepted seem to have changed and it seemed easier in the past. In the past I made the Core Team aware via the Dev Mailing List and wrote a simple two or three paragraphs of "What is it? / What is the value? / Here is the code"
>>>>
>>>>
>>>> So in an effort to move forward: How exactly is something presented to the Core Team?
>>>>
>>>> Is there an example of a recent effort that was presented that I can see as a sample? (This type of info can also be added to the Wiki)
>>>>
>>>> I understand you want it this way, but I don’t know what exactly is needed. Please be specific.
>>>>
>>>>
>>>> Jon
>>>>
>>>> PS - I am not ignoring your other comments, I am just trying to move forward and keep things simple.
>>>>
>>>>
>>>>
>>>>> On Feb 8, 2025, at 1:27 PM, Michael Tremer <michael.tremer(a)ipfire.org> wrote:
>>>>>
>>>>> Hello Jon,
>>>>>
>>>>> Thanks for your reply. And good that you are copying everyone into this conversation.
>>>>>
>>>>>> On 8 Feb 2025, at 18:41, jon <jon.murphy(a)ipfire.org> wrote:
>>>>>>
>>>>>> Michael,
>>>>>>
>>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort
>>>>>> Yes, this has been a separate effort (a very public separate effort). Yes, as you pointed this out early on with the "proof-of-concept" and then my request for people to help test RPZ. Nothing was hidden.
>>>>>>
>>>>>> This was done because you (and maybe others) did not have the time and I wanted to help and because I needed assistance with RPZ. I tried my best to do this without bothering you.
>>>>> I don’t that it is accurate that nobody wanted to help on this. The list was always open - although not every email has been replied to swiftly it is also your responsibility to raise a question again if it was missed. People here have open ears.
>>>>>
>>>>> It was also stated on this very list on in our documentation that working on something without involving the core team is a risky undertaking. Of course IPFire is free software and so everyone is free to fork if they wish to do so.
>>>>>
>>>>>>> and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls.
>>>>>> You were aware many steps along the way. See your email on July 28, 2024, August 15, 2024, September 30, 2024, December 23, 2024, and January 16. My attempts to get the team involved were met with "things are busy" and sometimes silence. (Yes, I get it, people are busy.)
>>>>>>
>>>>>> You and Adolf, Leo, Erik and Bernhard have been aware since the beginning. You mention you were aware of the "proof-of-concept". If you include those beginning posts, since Sep 2023.
>>>>> Yes, I am aware of a proof-of-concept that I have been running myself for a long time. I am also aware of the efforts that you have been taking.
>>>>>
>>>>> Yet I don’t think there has ever been any joint effort, or am I seeing that wrong?
>>>>>
>>>>>>> This has not been discussed . . . on our calls.
>>>>>> On the July 28th you stated:
>>>>>> "We have talked about RPZ many times on the monthly call since the URL filter feature is falling more and more out of fashion. I think there is also many posts about this on the forum."
>>>>>>
>>>>>> Please don’t insult me again by stating "you know what I mean".
>>>>>>
>>>>>> And it has been discussed but not documented in the Monthly Meeting notes.
>>>>> I am not at all insulting you. I don’t want to take this down to a personal level at all. This is a public mailing list and people who read this don’t need to listen to an argument we are having. They are here for the tech inside IPFire.
>>>>>
>>>>> When I wrote that it has not been discussed that does not mean that we have not been touching on the topic. We have been talking about lots of things on the calls, the weather, politics, how our pets are. None of that makes it to the logs. What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>>>
>>>>>>> Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation.
>>>>>> Regular conversation on the Dev Mailing list is many times met with silence. I get it, people are busy.
>>>>>>
>>>>>> And regular two-way conversation doesn’t happen on the list. At least not with me. I’d be happy to point out the posts that were met with silence.
>>>>>> Again, I get it, people are busy.
>>>>> And you think my emails are not being met with silence? This has nothing to do with this specific topic. This has something to do with how occupied people are and how engaged they are on certain topics. Not everyone is involved in all the things and simply will ignore emails simply based on their subject line.
>>>>>
>>>>>> But the "dip here to the list" were my attempts to get a conversation started. As I said, many time met with silence.
>>>>>>
>>>>>> The only place I was not met with silence was on the Community. You have a great group of people in the Community. It is a shame you don’t want to have others help. It would reduce your workload.
>>>>> You should stop making statements that are not true. Who doesn’t want anyone to help?
>>>>>
>>>>> Not having this conversation on a Saturday evening would reduce my workload. At least it would free up time for something else. Helping with the things that are already on the go would reduce the workload of the entire team. Starting one thing at a time and finishing it is a lot better to manage than starting a hundred things and not even finish one. I can tell you that I already have a hundred things on the go.
>>>>>
>>>>>>> Therefore, what am I supposed to do with this email?
>>>>>> To me it is beyond obvious…
>>>>>>
>>>>>> If it isn’t what you want, then guide me with how to do this the correct way. And be specific. I am trying to help. I am trying to make things better. I am trying to do things the right way.
>>>>> To me it isn’t. This is yet another project that has been dumped to the list like so many before and later on everyone has left to have the team deal with the rest.
>>>>>
>>>>> It is a huge patch set. You explained what the vision is, but that is about it. There is no chance this will continue if this disagreement isn’t solved first. I didn’t even look at the code.
>>>>>
>>>>>>> I don’t want to merge code that I don’t agree with.
>>>>>> I asked multiple times if you "agreed with the concept" and again, met with silence. Yes I get it, people are busy.
>>>>> Having support for RPZ? Yes, it was definitely on the roadmap. That I agree with.
>>>>>
>>>>>>> So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>> You mentioned this a in the past, but for some reason you do not disclose what I dismissed. Why do you continue to make this harder, wouldn’t it not be easier to tell me what I have dismissed?
>>>>>>
>>>>>> I have sent multiple emails trying to answer your concerns and comments. On July 28, Aug 14, Aug 22, Aug 23, Sep 30, etc.
>>>>>>
>>>>>> I’ve gone through all of the questions you asked and I cannot find a "dismissed" item.
>>>>> Maybe I need to be *more clear*. I feel humoured by this.
>>>>>
>>>>> It is late on a Saturday and I want my dinner soon, but certainly I have stated that this should never be an add-on considering it is supposed to replace URL Filter. We should never allow people to add their own sources. I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>>>>
>>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>> The maintainers of Unbound and/or RPZ?
>>>>>>
>>>>>> The maintainers of Hagezi list, the threatfox list, the urlhaus list, etc.?
>>>>>>
>>>>>> What else? The maintainers or the RPZ scripts? That is me. Let’s talk!
>>>>> You. I don’t care much about the providers of the lists.
>>>>>
>>>>>> See, this is where it gets confusing. There are hundreds of open source packages as part of IPFire. Pick the last five years of items added to the IPFire build. You're telling me you have "constructive conversation with the maintainers" of all of the added packages?
>>>>> They publish their software and they don’t care whether I am pulling it or not. They publish it with the commitment to maintain it - sometimes for better and sometimes for worse.
>>>>>
>>>>> You care about me pulling your code and I don’t know whether you would commit to maintain this.
>>>>>
>>>>> These two are very different cases.
>>>>>
>>>>>> Pick the IP Blocklists list (i.e., 3CORESEC, ABUSECH, DSHIELD, SPAMHAUS, etc.) or the Suricata lists (i.e., Emergingthreats.net, Abuse.ch, etc.). So you’ve have "constructive conversation with the maintainers"?
>>>>> Yes, occasionally I have phone calls with a few of these providers.
>>>>>
>>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>> Ha! Yes a surprise. In the beginning you seemed interested as IPFire needed a replacement for URL Filter. You asked good questions about the lists picked, asked for the value to the users, etc. And I answered the best I could.
>>>>>>
>>>>>> You even asked: “Why is this realised as an add-on and not part of the core system?” from your Jul 28, 2024 email.
>>>>> Ah, so, why is the patch creating an add-on? Not that I am saying that what I say is law, but it has not been challenged either. If my input is being ignored, why should I put this to the top of my list of priorities? I am not disappointed about this, just trying to be very good with my time.
>>>>>
>>>>>> And on January 16, 2025 I wrote a message looking for help. And you were kind to respond quickly. So in three weeks time, since the kind response, something has changed. You went from supportive to "this".
>>>>>>
>>>>>> So yes, I am surprised.
>>>>> Well, maybe I should not have replied to that email. It was clear that you were on some path that was not right, but you were not interested before in finding the right path from the beginning.
>>>>>
>>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>> Be more specific, what has to change? What exactly did I dismiss?
>>>>> Dismissal is just my assumption. I don’t know what you actually did with my feedback. I can only see the end product that does not seem contain much of it. Repeatedly I have been pointing out that we should think before we build. I am sure a lot of hours have now gone into some code that simply does not satisfy me. And I am not not talking about the code itself, what it does is what I don’t think is right for us.
>>>>>
>>>>> The process is very clear for me that we should first of all think whether we want a certain feature now. Then there should be a clear roadmap for everyone to follow; tasks can be split-up as we go and hopefully then have something that is maintainable, interesting for our users and even would do us proud. This is how this should work.
>>>>>
>>>>> So, what has to change? I don’t think with shouting at each other, throwing patches around and making me generally unhappy is a good start.
>>>>>
>>>>> -Michael
>>>>>
>>>>>> Jon
>>>>>>
>>>>>>
>>>>>>
>>>>>>> On Feb 6, 2025, at 2:13 PM, Michael Tremer <michael.tremer(a)ipfire.org> wrote:
>>>>>>>
>>>>>>> Hello Jon,
>>>>>>>
>>>>>>> Well, here we are again with another patch regarding this feature.
>>>>>>>
>>>>>>> I cannot quite see from your email what the question is, but if this is a request to have this merged into IPFire, I am once again sorry to disappoint you.
>>>>>>>
>>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls. Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation. Therefore, what am I supposed to do with this email?
>>>>>>>
>>>>>>> I don’t want to merge code that I don’t agree with. So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>>>
>>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>>>
>>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>>>
>>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>>>
>>>>>>> All the best,
>>>>>>> -Michael
>>>>>>>
>>>>>>>> On 6 Feb 2025, at 16:35, Jon Murphy <jon.murphy(a)ipfire.org> wrote:
>>>>>>>>
>>>>>>>> What is it?
>>>>>>>> Response Policy Zone (RPZ) is a mechanism to define local policies in a
>>>>>>>> standardized way and load those policies from external sources.
>>>>>>>> Bottom line: RPZ allows admins to easily block access to websites via DNS lookup.
>>>>>>>>
>>>>>>>> RPZ can block websites via categories. Examples include: fake websites, annoying
>>>>>>>> pop-up ads, newly registered domains, DoH bypass sites, bad "host" services,
>>>>>>>> maliscious top level domains (e.g., *.zip, *.mov), piracy, gambling, pornography,
>>>>>>>> and more. RPZ lists come from various RPZ providers and their available
>>>>>>>> catagories.
>>>>>>>>
>>>>>>>> This RPZ add-on enables the RPZ functionality by adding a couple lines in a
>>>>>>>> configuration file. This add-on simply adds configuration files and adds
>>>>>>>> scripts (config, metrics and sleep) to make RPZ easier for the admin to use.
>>>>>>>>
>>>>>>>> The RPZ scripts include additional languages: German, Spanish, French, Turkish,
>>>>>>>> and Italian.
>>>>>>>>
>>>>>>>> RPZ itself was release in 2010 and has been part of the IPFire build since ~2015.
>>>>>>>>
>>>>>>>> Why is it needed? What is its value?
>>>>>>>>
>>>>>>>> - The RPZ concept places this filtering into IPFire, our internet access
>>>>>>>> gateway, which is (should be) solely used as DNS source of the internal network.
>>>>>>>>
>>>>>>>> - As most sites use HTTPS it makes it difficult to filter traffic with URL
>>>>>>>> Filter without also properly configuring conventional (non-transparent)
>>>>>>>> mode on the proxy. RPZ is a nice replacement for the URL Filter.
>>>>>>>>
>>>>>>>> - No need to install and maintain an additional device like PiHole or AdBlock
>>>>>>>> browser extensions on multiple user devices.
>>>>>>>>
>>>>>>>> - This is an additional layer of protection for users. Less worry someone will
>>>>>>>> click on something that gets them into trouble. And, saying this with emphasis,
>>>>>>>> the ability to do it in one place!
>>>>>>>>
>>>>>>>> - Blocked sites save on unneeded traffic and can lessen the threat of malware
>>>>>>>> in advertisements
>>>>>>>>
>>>>>>>> - Logging allows the admin to see the site blocked and take actions
>>>>>>>>
>>>>>>>> - RPZ will be used at the home, home-office (work from home), schools,
>>>>>>>> ministerial, and at the office. Device counts are small (2-6) to medium (~80)
>>>>>>>> to mediam-large (200+).
>>>>>>>>
>>>>>>>> - RPZ can block ads, popups, phishing, scammers, spyware, malware, annoying
>>>>>>>> popups, NSFW links, DOH servers, and the usual internet trash.
>>>>>>>>
>>>>>>>> ------------------------------
>>>>>>>>
>>>>>>>> Change Log for RPZ add-on
>>>>>>>>
>>>>>>>> rpz-1.0.0-18 on 2025-02-05
>>>>>>>> - Build for approval & release as IPFire add-on
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> rpz-beta-0.1.18-18.ipfire on 2025-02-01
>>>>>>>> rpz.cgi:
>>>>>>>> - new feature: added a mod key to force a unbound restart
>>>>>>>>
>>>>>>>> rpz-config and rpz-make:
>>>>>>>> - new feature: added action for unbound restart `rpz-config unbound-restart`
>>>>>>>>
>>>>>>>> rpz-metrics:
>>>>>>>> - simple reformatting
>>>>>>>> - rename far right column from "last update" to "last download"
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> rpz-beta-0.1.17-17.ipfire on 2024-12-09
>>>>>>>> rpz-make
>>>>>>>> - bug fix: corrected validation regex for wildcards like: `*.domain.com`
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> rpz-beta-0.1.16-16.ipfire on 2024-11-18
>>>>>>>> rpz-make
>>>>>>>> - new feature: updated validation regex
>>>>>>>> - bug fix: moved validation to beginning of process. Now we validate before
>>>>>>>> creating config files.
>>>>>>>>
>>>>>>>> rpz.cgi:
>>>>>>>> - new feature: use CSS color variables of the main ipfire theme
>>>>>>>> - bug fix: empty zonefile remarks were stored as “undef” and caused a warning
>>>>>>>> - bug fix: HTML textarea removes the first empty line in a custom list
>>>>>>>> - thank you Leo!
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> rpz-beta-0.1.15-15.ipfire on 2024-11-04
>>>>>>>> rpz.cgi:
>>>>>>>> - new feature: added new language file for Turkish (thank you Peppe)
>>>>>>>>
>>>>>>>> rpz-make
>>>>>>>> - bug fix: corrected empty allow/block list issue. An empty allow/block list
>>>>>>>> will now remove contents of allow/block.rpz files and remove unneeded
>>>>>>>> allow/block.conf file. (thank you iptom)
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> rpz-beta-0.1.14-14.ipfire on 2024-10-29
>>>>>>>> rpz-config:
>>>>>>>> - bug fix: correct missing rpz extension. `rpz-config list` displayed URL
>>>>>>>> incorrectly (thank you Bernhard)
>>>>>>>>
>>>>>>>> rpz.cgi:
>>>>>>>> - bug fix: remove extra `"` in language files (thank you Bernhard)
>>>>>>>> - new feature: slightly dim "apply" button when not enabled
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> rpz-beta-0.1.13-13.ipfire on 2024-10-27
>>>>>>>> - skipped
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> rpz-beta-0.1.12-12.ipfire on 2024-10-21
>>>>>>>> rpz.cgi:
>>>>>>>> - new feature: added new language file for French (thank you gw-ipfire)
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> rpz-beta-0.1.11-11.ipfire on 2024-10-18
>>>>>>>> rpz.cgi:
>>>>>>>> - new feature: added new language file for Italian (thank you umberto)
>>>>>>>> - new feature: added new language file for Spanish (thank you Roberto)
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> rpz-beta-0.1.10-10.ipfire on 2024-10-15
>>>>>>>> rpz-make:
>>>>>>>> - bug fix: corrected validation error for a custom list entry (thank you siosios)
>>>>>>>> - e.g., `*.cloudflare-dns.com`
>>>>>>>>
>>>>>>>> install.sh:
>>>>>>>> - bug fix: add chown to correct user created files
>>>>>>>>
>>>>>>>> update.sh:
>>>>>>>> - bug fix: add chown to correct user created files (thank you siosios)
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> rpz-beta-0.1.9-9.ipfire on 2024-10-08
>>>>>>>> rpz.cgi:
>>>>>>>> - new feature: added new language file for German (thank you Leo)
>>>>>>>> - bug fix: add missing "rpz exitcode 110"
>>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> rpz-beta-0.1.8-8.ipfire on 2024-10-04
>>>>>>>> - skipped
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> rpz-beta-0.1.7-7.ipfire on 2024-10-03
>>>>>>>> All:
>>>>>>>> - new feature: includes beta version numbers for pakfire package,
>>>>>>>> instead of only `rpz-1.0.0-1.ipfire`, for each release.
>>>>>>>>
>>>>>>>> rpz.cgi:
>>>>>>>> - new feature: added new WebGUI at `rpz.cgi`
>>>>>>>> - a BIG thank you to Leo Hofmann for all of his work creating the webgui!!
>>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>>
>>>>>>>> rpz-make:
>>>>>>>> - new feature: validate entries in allowlist and blocklist
>>>>>>>> - new feature: add "no-reload" option for WebGUI
>>>>>>>>
>>>>>>>> rpz-metrics:
>>>>>>>> - new feature: info can be sorted by name, by hit count, by line count, by
>>>>>>>> "enabled" list or all lists
>>>>>>>>
>>>>>>>> backups:
>>>>>>>> - bug fix: include all files in `/var/ipfire/dns/rpz` directory in backup
>>>>>>>>
>>>>>>>> update.sh:
>>>>>>>> - bug fix: corrected ownership for `/var/ipfire/dns/rpz` directory during an
>>>>>>>> update
>>>>>>>>
>>>>>>>> Build:
>>>>>>>> - bug fix: `block.rpz.conf` and `block.rpz` from build. Files to be created
>>>>>>>> by `rpz-make`
>>>>>>>>
>>>>>>>> WebGUI and German language file
>>>>>>>> Contribution-by: Leo-Andres Hofmann <hofmann(a)leo-andres.de>
>>>>>>>>
>>>>>>>> Spanish language file
>>>>>>>> Contribution-by: Roberto Peña
>>>>>>>>
>>>>>>>> Italian language file
>>>>>>>> Contribution-by: Umberto Parma
>>>>>>>>
>>>>>>>> French language file
>>>>>>>> Contribution-by: gw-ipfire
>>>>>>>>
>>>>>>>> Turkish language file
>>>>>>>> Contribution-by: Peppe Tech
>>>>>>>>
>>>>>>>> Contribution-by: Bernhard Bitsch <bbitsch(a)ipfire.org>
>>>>>>>> Contribution-by: Erik Kapfer <erik.kapfer(a)ipfire.org>
>>>>>>>> Signed-off-by: Jon Murphy <jon.murphy(a)ipfire.org
>>>>>>>> ---
>>>>>>>> config/backup/includes/rpz | 4 +
>>>>>>>> config/cfgroot/manualpages | 1 +
>>>>>>>> config/menu/EX-rpz.menu | 6 +
>>>>>>>> config/rootfiles/common/configroot | 1 +
>>>>>>>> config/rootfiles/common/web-user-interface | 1 +
>>>>>>>> config/rootfiles/packages/rpz | 20 +
>>>>>>>> config/rpz/00-rpz.conf | 10 +
>>>>>>>> config/rpz/rpz-config | 130 +++
>>>>>>>> config/rpz/rpz-functions | 85 ++
>>>>>>>> config/rpz/rpz-make | 203 +++++
>>>>>>>> config/rpz/rpz-metrics | 170 ++++
>>>>>>>> config/rpz/rpz-sleep | 58 ++
>>>>>>>> config/rpz/rpz.de.pl | 30 +
>>>>>>>> config/rpz/rpz.en.pl | 30 +
>>>>>>>> config/rpz/rpz.es.pl | 30 +
>>>>>>>> config/rpz/rpz.fr.pl | 30 +
>>>>>>>> config/rpz/rpz.it.pl | 30 +
>>>>>>>> config/rpz/rpz.tr.pl | 30 +
>>>>>>>> html/cgi-bin/rpz.cgi | 923 +++++++++++++++++++++
>>>>>>>> lfs/rpz | 96 +++
>>>>>>>> make.sh | 3 +-
>>>>>>>> src/paks/rpz/install.sh | 36 +
>>>>>>>> src/paks/rpz/uninstall.sh | 38 +
>>>>>>>> src/paks/rpz/update.sh | 52 ++
>>>>>>>> 24 files changed, 2016 insertions(+), 1 deletion(-)
>>>>>>>> create mode 100644 config/backup/includes/rpz
>>>>>>>> create mode 100644 config/menu/EX-rpz.menu
>>>>>>>> create mode 100644 config/rootfiles/packages/rpz
>>>>>>>> create mode 100644 config/rpz/00-rpz.conf
>>>>>>>> create mode 100644 config/rpz/rpz-config
>>>>>>>> create mode 100644 config/rpz/rpz-functions
>>>>>>>> create mode 100644 config/rpz/rpz-make
>>>>>>>> create mode 100755 config/rpz/rpz-metrics
>>>>>>>> create mode 100755 config/rpz/rpz-sleep
>>>>>>>> create mode 100644 config/rpz/rpz.de.pl
>>>>>>>> create mode 100644 config/rpz/rpz.en.pl
>>>>>>>> create mode 100644 config/rpz/rpz.es.pl
>>>>>>>> create mode 100644 config/rpz/rpz.fr.pl
>>>>>>>> create mode 100644 config/rpz/rpz.it.pl
>>>>>>>> create mode 100644 config/rpz/rpz.tr.pl
>>>>>>>> create mode 100644 html/cgi-bin/rpz.cgi
>>>>>>>> create mode 100644 lfs/rpz
>>>>>>>> create mode 100644 src/paks/rpz/install.sh
>>>>>>>> create mode 100644 src/paks/rpz/uninstall.sh
>>>>>>>> create mode 100644 src/paks/rpz/update.sh
>>>>>>>>
>>>>>>>> diff --git a/config/backup/includes/rpz b/config/backup/includes/rpz
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..36513e494
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/backup/includes/rpz
>>>>>>>> @@ -0,0 +1,4 @@
>>>>>>>> +/var/ipfire/dns/rpz/*
>>>>>>>> +/etc/unbound/zonefiles/allow.rpz
>>>>>>>> +/etc/unbound/zonefiles/block.rpz
>>>>>>>> +/etc/unbound/local.d/*rpz.conf
>>>>>>>> diff --git a/config/cfgroot/manualpages b/config/cfgroot/manualpages
>>>>>>>> index 1f7e01efc..d3a48c633 100644
>>>>>>>> --- a/config/cfgroot/manualpages
>>>>>>>> +++ b/config/cfgroot/manualpages
>>>>>>>> @@ -70,6 +70,7 @@ pakfire.cgi=configuration/ipfire/pakfire
>>>>>>>> wlanap.cgi=addons/wireless
>>>>>>>> tor.cgi=addons/tor
>>>>>>>> samba.cgi=addons/samba
>>>>>>>> +rpz.cgi=addons/rpz
>>>>>>>>
>>>>>>>> # Logs menu
>>>>>>>> logs.cgi/summary.dat=configuration/logs/summary
>>>>>>>> diff --git a/config/menu/EX-rpz.menu b/config/menu/EX-rpz.menu
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..2f4daf410
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/menu/EX-rpz.menu
>>>>>>>> @@ -0,0 +1,6 @@
>>>>>>>> +$subipfire->{'20.rpz'} = {
>>>>>>>> + 'caption' => $Lang::tr{'rpz'},
>>>>>>>> + 'uri' => '/cgi-bin/rpz.cgi',
>>>>>>>> + 'title' => "RPZ",
>>>>>>>> + 'enabled' => 1,
>>>>>>>> +};
>>>>>>>> diff --git a/config/rootfiles/common/configroot b/config/rootfiles/common/configroot
>>>>>>>> index 9839eee45..b30d6aae4 100644
>>>>>>>> --- a/config/rootfiles/common/configroot
>>>>>>>> +++ b/config/rootfiles/common/configroot
>>>>>>>> @@ -120,6 +120,7 @@ var/ipfire/menu.d/70-log.menu
>>>>>>>> #var/ipfire/menu.d/EX-apcupsd.menu
>>>>>>>> #var/ipfire/menu.d/EX-guardian.menu
>>>>>>>> #var/ipfire/menu.d/EX-mympd.menu
>>>>>>>> +#var/ipfire/menu.d/EX-rpz.menu
>>>>>>>> #var/ipfire/menu.d/EX-samba.menu
>>>>>>>> #var/ipfire/menu.d/EX-tor.menu
>>>>>>>> #var/ipfire/menu.d/EX-transmission.menu
>>>>>>>> diff --git a/config/rootfiles/common/web-user-interface b/config/rootfiles/common/web-user-interface
>>>>>>>> index 816241dae..e00464076 100644
>>>>>>>> --- a/config/rootfiles/common/web-user-interface
>>>>>>>> +++ b/config/rootfiles/common/web-user-interface
>>>>>>>> @@ -69,6 +69,7 @@ srv/web/ipfire/cgi-bin/proxy.cgi
>>>>>>>> srv/web/ipfire/cgi-bin/qos.cgi
>>>>>>>> srv/web/ipfire/cgi-bin/remote.cgi
>>>>>>>> srv/web/ipfire/cgi-bin/routing.cgi
>>>>>>>> +#srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>>> #srv/web/ipfire/cgi-bin/samba.cgi
>>>>>>>> srv/web/ipfire/cgi-bin/services.cgi
>>>>>>>> srv/web/ipfire/cgi-bin/shutdown.cgi
>>>>>>>> diff --git a/config/rootfiles/packages/rpz b/config/rootfiles/packages/rpz
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..1c8663049
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rootfiles/packages/rpz
>>>>>>>> @@ -0,0 +1,20 @@
>>>>>>>> +etc/unbound/local.d/00-rpz.conf
>>>>>>>> +etc/unbound/zonefiles
>>>>>>>> +etc/unbound/zonefiles/allow.rpz
>>>>>>>> +usr/sbin/rpz-config
>>>>>>>> +usr/sbin/rpz-functions
>>>>>>>> +usr/sbin/rpz-make
>>>>>>>> +usr/sbin/rpz-metrics
>>>>>>>> +usr/sbin/rpz-sleep
>>>>>>>> +var/ipfire/addon-lang/rpz.de.pl
>>>>>>>> +var/ipfire/addon-lang/rpz.en.pl
>>>>>>>> +var/ipfire/addon-lang/rpz.es.pl
>>>>>>>> +var/ipfire/addon-lang/rpz.fr.pl
>>>>>>>> +var/ipfire/addon-lang/rpz.it.pl
>>>>>>>> +var/ipfire/addon-lang/rpz.tr.pl
>>>>>>>> +var/ipfire/backup/addons/includes/rpz
>>>>>>>> +var/ipfire/dns/rpz
>>>>>>>> +var/ipfire/dns/rpz/allowlist
>>>>>>>> +var/ipfire/dns/rpz/blocklist
>>>>>>>> +var/ipfire/menu.d/EX-rpz.menu
>>>>>>>> +srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>>> diff --git a/config/rpz/00-rpz.conf b/config/rpz/00-rpz.conf
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..f005a4f2e
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/00-rpz.conf
>>>>>>>> @@ -0,0 +1,10 @@
>>>>>>>> +server:
>>>>>>>> + module-config: "respip validator iterator"
>>>>>>>> +
>>>>>>>> +rpz:
>>>>>>>> + name: allow.rpz
>>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>>> + rpz-action-override: passthru
>>>>>>>> + rpz-log: yes
>>>>>>>> + rpz-log-name: allow
>>>>>>>> + rpz-signal-nxdomain-ra: yes
>>>>>>>> diff --git a/config/rpz/rpz-config b/config/rpz/rpz-config
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..c72d50f9b
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/rpz-config
>>>>>>>> @@ -0,0 +1,130 @@
>>>>>>>> +#!/bin/bash
>>>>>>>> +###############################################################################
>>>>>>>> +# #
>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info(a)ipfire.org> #
>>>>>>>> +# #
>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>> +# #
>>>>>>>> +###############################################################################
>>>>>>>> +
>>>>>>>> +version="2025-01-11 - v44"
>>>>>>>> +
>>>>>>>> +############### Functions ###############
>>>>>>>> +
>>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>>> +
>>>>>>>> +############### Main ###############
>>>>>>>> +
>>>>>>>> +tagName="unbound"
>>>>>>>> +
>>>>>>>> +rpzAction="${1}" # input RPZ action
>>>>>>>> +rpzName="${2}" # input RPZ name
>>>>>>>> +rpzURL="${3}" # input RPZ URL
>>>>>>>> +rpzOption1="${4}" # input RPZ option #1
>>>>>>>> +rpzOption2="${5}" # input RPZ option #2
>>>>>>>> +
>>>>>>>> +rpzConfig="/etc/unbound/local.d/${rpzName}.rpz.conf" # output zone conf file
>>>>>>>> +rpzFile="/etc/unbound/zonefiles/${rpzName}.rpz" # output for RPZ file
>>>>>>>> +
>>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>>> +
>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>> + case "$1" in
>>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>>> + esac
>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>> +done
>>>>>>>> +
>>>>>>>> +case "${rpzAction}" in
>>>>>>>> + # add new rpz list
>>>>>>>> + add )
>>>>>>>> + check_name "${rpzName}" # is this a valid name?
>>>>>>>> + # does this config already exist? If yes, then exit
>>>>>>>> + if [[ -f "${rpzConfig}" ]] ; then
>>>>>>>> + msg_log "error: rpz: duplicate - ${rpzConfig} already exists. exit"
>>>>>>>> + exit 104
>>>>>>>> + fi
>>>>>>>> +
>>>>>>>> + # is this a valid URL?
>>>>>>>> + regex='^https://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
>>>>>>>> + if ! [[ "${rpzURL}" =~ $regex ]] ; then
>>>>>>>> + msg_log "error: rpz: the URL is not valid: \"${rpzURL}\". exit."
>>>>>>>> + exit 105
>>>>>>>> + fi
>>>>>>>> +
>>>>>>>> + # create the zone config file
>>>>>>>> + {
>>>>>>>> + echo "rpz:"
>>>>>>>> + echo " name: ${rpzName}.rpz"
>>>>>>>> + echo " zonefile: ${rpzFile}"
>>>>>>>> + echo " url: ${rpzURL}"
>>>>>>>> + echo " rpz-action-override: nxdomain"
>>>>>>>> + echo " rpz-log: ${rpzLog}"
>>>>>>>> + echo " rpz-log-name: ${rpzName}"
>>>>>>>> + echo " rpz-signal-nxdomain-ra: yes"
>>>>>>>> + } > "${rpzConfig}"
>>>>>>>> +
>>>>>>>> + # set-up zonefile
>>>>>>>> + # create an empty rpz file if it does not exist
>>>>>>>> + if [[ ! -f "${rpzFile}" ]] ; then
>>>>>>>> + touch "${rpzFile}"
>>>>>>>> + # unbound requires these settings for rpz files
>>>>>>>> + set_permissions "${rpzFile}" "${rpzConfig}"
>>>>>>>> + fi
>>>>>>>> + ;;
>>>>>>>> +
>>>>>>>> + # trash config file & rpz file
>>>>>>>> + remove )
>>>>>>>> + if ! [[ -f "${rpzConfig}" ]] ; then
>>>>>>>> + msg_log "error: rpz: cannot remove ${rpzConfig}, does not exist. exit"
>>>>>>>> + exit 106
>>>>>>>> + fi
>>>>>>>> +
>>>>>>>> + msg_log "info: rpz: remove config file & rpz file \"${rpzName}\""
>>>>>>>> + rm "${rpzConfig}"
>>>>>>>> + rm "${rpzFile}"
>>>>>>>> + ;;
>>>>>>>> +
>>>>>>>> + reload )
>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>> + ;;
>>>>>>>> +
>>>>>>>> + list )
>>>>>>>> + awk -F':' '/^\s*name:/{ gsub(/[[:blank:]]|\.rpz/, "",$2) ; NAME=$2 } \
>>>>>>>> + /^\s*url:/{ gsub(/[[:blank:]]/, "") ; print NAME"="$2":"$3} ' \
>>>>>>>> + /etc/unbound/local.d/*rpz.conf
>>>>>>>> + exit
>>>>>>>> + ;;
>>>>>>>> +
>>>>>>>> + unbound-restart )
>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>> + unbound_restart
>>>>>>>> + exit
>>>>>>>> + ;;
>>>>>>>> +
>>>>>>>> + * )
>>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>>> + printf "Usage: $(basename "$0") <ACTION> <NAME> <URL> <OPTION> <OPTION>\n"
>>>>>>>> + printf "Version: ${version}\n"
>>>>>>>> + exit 108
>>>>>>>> + ;;
>>>>>>>> +
>>>>>>>> +esac
>>>>>>>> +
>>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>>> +
>>>>>>>> +exit
>>>>>>>> diff --git a/config/rpz/rpz-functions b/config/rpz/rpz-functions
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..ace1d2690
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/rpz-functions
>>>>>>>> @@ -0,0 +1,85 @@
>>>>>>>> +#!/bin/bash
>>>>>>>> +###############################################################################
>>>>>>>> +# #
>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>>>>> +# #
>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>> +# #
>>>>>>>> +###############################################################################
>>>>>>>> +
>>>>>>>> +version="2024-12-10 - v02"
>>>>>>>> +
>>>>>>>> +############### Functions ###############
>>>>>>>> +
>>>>>>>> +msg_log () {
>>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>>> + if tty --silent ; then
>>>>>>>> + echo "${tagName}:" "$*"
>>>>>>>> + fi
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# check for a valid name
>>>>>>>> +check_name () {
>>>>>>>> + local theName="${1}"
>>>>>>>> +
>>>>>>>> + regex='^[a-zA-Z0-9_]+$' # no dash or plus, alpha numeric only
>>>>>>>> + regex1='^(allow|block)$' # allow and block are reserved NAMEs
>>>>>>>> + if [[ ! "${theName}" =~ $regex ]] || [[ "${theName}" =~ $regex1 ]] ; then
>>>>>>>> + msg_log "error: rpz: the NAME is not valid: \"${theName}\". exit."
>>>>>>>> + exit 101
>>>>>>>> + fi
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +set_permissions () {
>>>>>>>> + chown nobody:nobody "$@"
>>>>>>>> + chmod 644 "$@"
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +check_unbound_conf () {
>>>>>>>> + local thecheckConf="${1:-yes}" # check config default is yes
>>>>>>>> +
>>>>>>>> + # check the above config files
>>>>>>>> + if [[ "${thecheckConf}" == yes ]] ; then
>>>>>>>> + msg_log "info: rpz: check for errors with \"unbound-checkconf\""
>>>>>>>> +
>>>>>>>> + if ! unbound-checkconf ; then
>>>>>>>> + msg_log "error: rpz: unbound-checkconf found invalid configuration."
>>>>>>>> + msg_log \
>>>>>>>> + "error: rpz: In Terminal run the command \"unbound-checkconf\" for more information. exit."
>>>>>>>> + exit 102
>>>>>>>> + fi
>>>>>>>> + fi
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +unbound_control_reload () {
>>>>>>>> + local theReload="${1:-yes}" # reload default is yes
>>>>>>>> +
>>>>>>>> + if [[ "${theReload}" == yes ]] ; then
>>>>>>>> + # reload due to the changes
>>>>>>>> + msg_log "info: rpz: run \"unbound-control reload\""
>>>>>>>> +
>>>>>>>> + if ! unbound-control reload ; then
>>>>>>>> + msg_log "error: rpz: unbound-control reload. exit."
>>>>>>>> + exit 109
>>>>>>>> + fi
>>>>>>>> + fi
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +unbound_restart () {
>>>>>>>> + # restart due to the changes
>>>>>>>> + msg_log "info: rpz: run \"unbound restart\""
>>>>>>>> +
>>>>>>>> + /usr/local/bin/unboundctrl restart
>>>>>>>> +}
>>>>>>>> diff --git a/config/rpz/rpz-make b/config/rpz/rpz-make
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..927d55170
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/rpz-make
>>>>>>>> @@ -0,0 +1,203 @@
>>>>>>>> +#!/bin/bash
>>>>>>>> +###############################################################################
>>>>>>>> +# #
>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info(a)ipfire.org> #
>>>>>>>> +# #
>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>> +# #
>>>>>>>> +###############################################################################
>>>>>>>> +
>>>>>>>> +version="2025-01-11 - v14"
>>>>>>>> +
>>>>>>>> +############### Functions ###############
>>>>>>>> +
>>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>>> +
>>>>>>>> +# create the config file for allow
>>>>>>>> +make_allow_config () {
>>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>>> + local theConfig="/etc/unbound/local.d/00-rpz.conf" # output zone conf file
>>>>>>>> + local theList="/var/ipfire/dns/rpz/allowlist" # input custom list of domains
>>>>>>>> +
>>>>>>>> + msg_log "info: rpz: make config file \"00-rpz.conf\""
>>>>>>>> +
>>>>>>>> + echo "server:
>>>>>>>> + module-config: \"respip validator iterator\"" > "${theConfig}"
>>>>>>>> +
>>>>>>>> + # does allow list exist?
>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>> +
>>>>>>>> + echo "rpz:
>>>>>>>> + name: allow.rpz
>>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>>> + rpz-action-override: passthru
>>>>>>>> + rpz-log: ${theLog}
>>>>>>>> + rpz-log-name: allow
>>>>>>>> + rpz-signal-nxdomain-ra: yes" >> "${theConfig}"
>>>>>>>> +
>>>>>>>> + fi
>>>>>>>> +
>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>> + set_permissions "${theConfig}"
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# create the config file for block
>>>>>>>> +make_block_config () {
>>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>>> + local theConfig="/etc/unbound/local.d/block.rpz.conf" # output zone conf file
>>>>>>>> + local theList="/var/ipfire/dns/rpz/blocklist" # input custom list of domains
>>>>>>>> +
>>>>>>>> + msg_log "info: rpz: make config file \"block.rpz.conf\""
>>>>>>>> +
>>>>>>>> + # does block list exist?
>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>> +
>>>>>>>> + echo "rpz:
>>>>>>>> + name: block.rpz
>>>>>>>> + zonefile: /etc/unbound/zonefiles/block.rpz
>>>>>>>> + rpz-action-override: nxdomain
>>>>>>>> + rpz-log: ${theLog}
>>>>>>>> + rpz-log-name: block
>>>>>>>> + rpz-signal-nxdomain-ra: yes" > "${theConfig}"
>>>>>>>> +
>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>> + set_permissions "${theConfig}"
>>>>>>>> + else
>>>>>>>> + # no - trash the config file
>>>>>>>> + rm --verbose /etc/unbound/local.d/block.rpz.conf
>>>>>>>> + fi
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# create an RPZ file for allow or block
>>>>>>>> +make_rpz_file () {
>>>>>>>> + local theName="${1}" # allow or block
>>>>>>>> + local theAction='.' # the default is nxdomain or block
>>>>>>>> + local actionList
>>>>>>>> +
>>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>>> + local theZonefile="/etc/unbound/zonefiles/${theName}.rpz" # output file for RPZ
>>>>>>>> +
>>>>>>>> + # does a list exist?
>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>> +
>>>>>>>> + # for allow set to passthru
>>>>>>>> + [[ "${theName}" == allow ]] && theAction='rpz-passthru.'
>>>>>>>> +
>>>>>>>> + # drop any extra "blanks" and add "CNAME <RPZ action>." to each line
>>>>>>>> + actionList=$( awk '{$1=$1};1' "${theList}" |
>>>>>>>> + sed "/^[^;].*[[:alnum:]]/ s|$| CNAME ${theAction}|" )
>>>>>>>> +
>>>>>>>> + msg_log "info: rpz: create zonefile for \"${theName}list\""
>>>>>>>> +
>>>>>>>> +echo "; Name: ${theName} list
>>>>>>>> +; Last modified: $(date "+%Y-%m-%d at %H.%M.%S %Z")
>>>>>>>> +;
>>>>>>>> +; domains with actions list
>>>>>>>> +;
>>>>>>>> +${actionList}" > "${theZonefile}"
>>>>>>>> +
>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>> + set_permissions "${theZonefile}"
>>>>>>>> + # set-up allow/block list files
>>>>>>>> + set_permissions "${theList}"
>>>>>>>> + else
>>>>>>>> + msg_log "info: rpz: the ${theList} is empty."
>>>>>>>> +
>>>>>>>> + rm --verbose "${theZonefile}" # trash the RPZ file
>>>>>>>> + fi
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# check if allow/block list is valid
>>>>>>>> +validate_list () {
>>>>>>>> + local theName="${1}" # allow or block
>>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>>> +
>>>>>>>> + # remove good:
>>>>>>>> + # - properly formated domain names with or without leading wildcard
>>>>>>>> + # - properly formated top level domain (TLD) names with wildcard
>>>>>>>> + # - blank lines and comment lines
>>>>>>>> + # remaining lines are considered "bad"
>>>>>>>> + bad_lines=$( sed --regexp-extended \
>>>>>>>> + '/^(\*\.)?([a-zA-Z0-9](([a-zA-Z0-9\-]){0,61}[a-zA-Z0-9])?\.)+([a-zA-Z]{2,}|xn--[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])$/d ;
>>>>>>>> + /^(\*\.)([a-z]{2,61}|xn--[a-z0-9]{1,60})$/d ;
>>>>>>>> + /^$/d ; /^;/d' "${theList}" )
>>>>>>>> +
>>>>>>>> + if [[ ! -z "${bad_lines}" ]] ; then
>>>>>>>> + msg_log "error: rpz: invalid line(s) in ${theList}."
>>>>>>>> + printf "%s\n" "bad line(s): ${bad_lines}"
>>>>>>>> + exit 110
>>>>>>>> + fi
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +
>>>>>>>> +############### Main ###############
>>>>>>>> +
>>>>>>>> +tagName="unbound"
>>>>>>>> +
>>>>>>>> +rpzName="${1}" # input RPZ name
>>>>>>>> +
>>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>>> +
>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>> + case "$1" in
>>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>>> + esac
>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>> +done
>>>>>>>> +
>>>>>>>> +case "${rpzName}" in
>>>>>>>> + # make a new allow or block rpz file
>>>>>>>> +
>>>>>>>> + allow )
>>>>>>>> + validate_list 'allow' # is the allowlist valid?
>>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>>> + make_rpz_file 'allow'
>>>>>>>> + ;;
>>>>>>>> +
>>>>>>>> + allowblock )
>>>>>>>> + validate_list 'allow' # is the list valid?
>>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>>> + make_rpz_file 'allow'
>>>>>>>> + ;&
>>>>>>>> +
>>>>>>>> + block )
>>>>>>>> + validate_list 'block' # is the blocklist valid?
>>>>>>>> + make_block_config "${rpzLog}"
>>>>>>>> + make_rpz_file 'block'
>>>>>>>> + ;;
>>>>>>>> +
>>>>>>>> + reload )
>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>> + ;;
>>>>>>>> +
>>>>>>>> + unbound-restart )
>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>> + unbound_restart
>>>>>>>> + exit
>>>>>>>> + ;;
>>>>>>>> +
>>>>>>>> + * )
>>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>>> + printf "Usage: $(basename "$0") <NAME> <OPTION> <OPTION>\n"
>>>>>>>> + printf "Version: ${version}\n"
>>>>>>>> + exit 108
>>>>>>>> + ;;
>>>>>>>> +esac
>>>>>>>> +
>>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>>> +
>>>>>>>> +exit
>>>>>>>> diff --git a/config/rpz/rpz-metrics b/config/rpz/rpz-metrics
>>>>>>>> new file mode 100755
>>>>>>>> index 000000000..4d43e1629
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/rpz-metrics
>>>>>>>> @@ -0,0 +1,170 @@
>>>>>>>> +#!/bin/bash
>>>>>>>> +###############################################################################
>>>>>>>> +# #
>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>>>>> +# #
>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>> +# #
>>>>>>>> +###############################################################################
>>>>>>>> +
>>>>>>>> +version="2025-01-20 - v25"
>>>>>>>> +
>>>>>>>> +############### Main ###############
>>>>>>>> +
>>>>>>>> +weeks="2" # default to two message logs
>>>>>>>> +sortBy="name" # default "by name"
>>>>>>>> +rpzActive="enabled" # default "enabled only"
>>>>>>>> +
>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>> + case "$1" in
>>>>>>>> + --by-names | --by-name | name ) sortBy="name" ;;
>>>>>>>> +
>>>>>>>> + --by-hits | --by-hit | hits | hit ) sortBy="hit" ;;
>>>>>>>> +
>>>>>>>> + --by-lines | --by-line | lines | line ) sortBy="line" ;;
>>>>>>>> +
>>>>>>>> + --by-effect ) sortBy="effect" ;;
>>>>>>>> +
>>>>>>>> + --enabled-only ) rpzActive="enabled" ;;
>>>>>>>> +
>>>>>>>> + --active-all | --all | all ) rpzActive="all" ;;
>>>>>>>> +
>>>>>>>> + [0-9] | [0-9][0-9] ) weeks=$1 ;;
>>>>>>>> + esac
>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>> +done
>>>>>>>> +
>>>>>>>> +# get the list of message logs for N weeks
>>>>>>>> +messageLogs=$( find /var/log/messages* -type f | sort --version-sort |
>>>>>>>> + head -"${weeks}" )
>>>>>>>> +
>>>>>>>> +# get the list of RPZ names & counts from the message log(s)
>>>>>>>> +rpzNameCount=$( for logf in ${messageLogs} ; do
>>>>>>>> + zgrep --text --fixed-strings 'info: rpz: applied' "${logf}" |
>>>>>>>> + awk '$10 ~ /\[\w*]/ { print $10 }' ;
>>>>>>>> + done | sort | uniq --count )
>>>>>>>> +
>>>>>>>> +# flip results and remove brackets `[` and `]`
>>>>>>>> +rpzNameCount=$( echo "${rpzNameCount}" |
>>>>>>>> + awk '{ print $2, $1 }' |
>>>>>>>> + sed --regexp-extended 's|^\[(.*)\]|\1|' )
>>>>>>>> +
>>>>>>>> +# grab only names
>>>>>>>> +rpzNames=$( echo "${rpzNameCount}" | awk '{ print $1 }' )
>>>>>>>> +
>>>>>>>> +# get list of RPZ files
>>>>>>>> +rpzFileList=$( find /etc/unbound/zonefiles -type f -iname "*.rpz" )
>>>>>>>> +
>>>>>>>> +# get basename of those files
>>>>>>>> +rpzBaseNames=$( echo "${rpzFileList}" |
>>>>>>>> + sed 's|/etc/unbound/zonefiles/||g ; s|\.rpz||g ;' )
>>>>>>>> +
>>>>>>>> +# add to rpzNames
>>>>>>>> +rpzNames="${rpzNames}"$'\n'"${rpzBaseNames}"
>>>>>>>> +
>>>>>>>> +# drop duplicate names
>>>>>>>> +rpzNames=$( echo "${rpzNames}" | sort --unique )
>>>>>>>> +
>>>>>>>> +# get line count for each RPZ
>>>>>>>> +lineCount=$( echo "${rpzFileList}" | xargs wc -l )
>>>>>>>> +
>>>>>>>> +# get comment line count and blank line count for each RPZ
>>>>>>>> +commentCount=$( echo "${rpzFileList}" | xargs grep --count -e "^$" -e "^;" )
>>>>>>>> +
>>>>>>>> +# get modified date each RPZ
>>>>>>>> +modDateList=$( echo "${rpzFileList}" | xargs stat -c '%.10y %n' )
>>>>>>>> +
>>>>>>>> +ucListAuthZones=$( unbound-control list_auth_zones )
>>>>>>>> +
>>>>>>>> +# get width of RPZ names
>>>>>>>> +pWidth=$( echo "${rpzNames}" | awk '{ print $1" " }' | wc -L )
>>>>>>>> +pFormat="%-${pWidth}s %-8s %-8s %8s %12s %12s\n"
>>>>>>>> +
>>>>>>>> +# print title line
>>>>>>>> +printf "${pFormat}" "name" "hits" "active" "lines" " hits/line" "last download"
>>>>>>>> +printf -- "--------------"
>>>>>>>> +
>>>>>>>> +theResults=""
>>>>>>>> +totalLines=0
>>>>>>>> +totalHits=0
>>>>>>>> +while read -r theName
>>>>>>>> +do
>>>>>>>> + printf -- "--" # pretend progress bar
>>>>>>>> +
>>>>>>>> + # is this RPZ list active?
>>>>>>>> + theActive="disabled"
>>>>>>>> + if grep --quiet "^${theName}\.rpz" <<< "${ucListAuthZones}"
>>>>>>>> + then
>>>>>>>> + theActive="enabled"
>>>>>>>> + else
>>>>>>>> + [[ "${rpzActive}" == enabled ]] && continue
>>>>>>>> + fi
>>>>>>>> +
>>>>>>>> + # get hit count
>>>>>>>> + theHits="0"
>>>>>>>> + if output=$( grep "^${theName}\s" <<< "${rpzNameCount}" ) ; then
>>>>>>>> + theHits=$( echo "${output}" | awk '{ print $2 }' )
>>>>>>>> + totalHits=$(( totalHits + theHits ))
>>>>>>>> + fi
>>>>>>>> +
>>>>>>>> + # get line count
>>>>>>>> + theLines="n/a"
>>>>>>>> + hitsPerLine="0"
>>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${lineCount}" ) ; then
>>>>>>>> + theLines=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>>> + totalLines=$(( totalLines + theLines ))
>>>>>>>> +
>>>>>>>> + if [[ "${theLines}" -gt 2 ]] ; then
>>>>>>>> + hitsPerLine=$(( 100 * theHits / theLines ))
>>>>>>>> + fi
>>>>>>>> + fi
>>>>>>>> +
>>>>>>>> + # get modification date
>>>>>>>> + theModDate="n/a"
>>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${modDateList}" ) ; then
>>>>>>>> + theModDate=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>>> + fi
>>>>>>>> +
>>>>>>>> + # add to results list
>>>>>>>> + theResults+="${theName} ${theHits} ${theActive} ${theLines} ${hitsPerLine} ${theModDate}"$'\n'
>>>>>>>> +
>>>>>>>> +done <<< "${rpzNames}"
>>>>>>>> +
>>>>>>>> +case "${sortBy}" in
>>>>>>>> + # sort by "active" then by "name"
>>>>>>>> + name) sortArg=(-k3,3r -k1,1) ;;
>>>>>>>> +
>>>>>>>> + # sort by "active" then by "hits" then by "name"
>>>>>>>> + hit) sortArg=(-k3,3r -k2,2nr -k1,1) ;;
>>>>>>>> +
>>>>>>>> + # sort by "active" then by "lines" then by "name"
>>>>>>>> + line) sortArg=(-k3,3r -k4,4nr -k1,1) ;;
>>>>>>>> +
>>>>>>>> + # sort by "active" then by "effect" then by "name"
>>>>>>>> + effect) sortArg=(-k3,3r -k5,5nr -k1,1) ;;
>>>>>>>> +esac
>>>>>>>> +
>>>>>>>> +printf -- "--------------\n"
>>>>>>>> +# remove blank lines, sort, print as columns
>>>>>>>> +echo "${theResults}" |
>>>>>>>> + awk '!/^[[:space:]]*$/' |
>>>>>>>> + sort "${sortArg[@]}" |
>>>>>>>> + awk --assign=width="${pWidth}" \
>>>>>>>> + '{ printf "%-*s %-8s %-8s %8s %10s %% %12s\n", width, $1, $2, $3, $4, $5, $6 }'
>>>>>>>> +
>>>>>>>> +printf "${pFormat}" "" "=======" "" "========" "" ""
>>>>>>>> +printf "${pFormat}" "Totals -->" "${totalHits}" "" "${totalLines}" "" ""
>>>>>>>> +
>>>>>>>> +exit
>>>>>>>> diff --git a/config/rpz/rpz-sleep b/config/rpz/rpz-sleep
>>>>>>>> new file mode 100755
>>>>>>>> index 000000000..dd3603599
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/rpz-sleep
>>>>>>>> @@ -0,0 +1,58 @@
>>>>>>>> +#!/bin/bash
>>>>>>>> +###############################################################################
>>>>>>>> +# #
>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>>>>> +# #
>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>> +# #
>>>>>>>> +###############################################################################
>>>>>>>> +
>>>>>>>> +version="2024-08-16" # v05
>>>>>>>> +
>>>>>>>> +############### Functions ###############
>>>>>>>> +
>>>>>>>> +# send message to message log
>>>>>>>> +msg_log () {
>>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>>> + if tty --silent ; then
>>>>>>>> + echo "${tagName}:" "$*"
>>>>>>>> + fi
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +############### Main ###############
>>>>>>>> +
>>>>>>>> +tagName="unbound"
>>>>>>>> +
>>>>>>>> +sleepTime="${1:-5m}" # default to sleep for 5m (5 minutes)
>>>>>>>> +
>>>>>>>> +zoneList=$( unbound-control list_auth_zones | awk '{print $1}' )
>>>>>>>> +
>>>>>>>> +for zone in ${zoneList} ; do
>>>>>>>> + printf "disable ${zone}\t"
>>>>>>>> + unbound-control rpz_disable "${zone}"
>>>>>>>> +done
>>>>>>>> +
>>>>>>>> +msg_log "info: rpz: disabled all zones for ${sleepTime}"
>>>>>>>> +
>>>>>>>> +sleep "${sleepTime}"
>>>>>>>> +
>>>>>>>> +for zone in ${zoneList} ; do
>>>>>>>> + printf "enable ${zone}\t"
>>>>>>>> + unbound-control rpz_enable "${zone}"
>>>>>>>> +done
>>>>>>>> +
>>>>>>>> +msg_log "info: rpz: enabled all zones"
>>>>>>>> +
>>>>>>>> +exit
>>>>>>>> diff --git a/config/rpz/rpz.de.pl b/config/rpz/rpz.de.pl
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..3770c6bb0
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/rpz.de.pl
>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>> +%tr = (%tr,
>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>> +'rpz apply' => 'Übernehmen',
>>>>>>>> +'rpz cl allow enable' => 'Benutzerdefinierte Allowlist aktivieren:',
>>>>>>>> +'rpz cl allow info' => 'Zugelassene Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>>> +'rpz cl allow' => 'Benutzerdefinierte Allowlist',
>>>>>>>> +'rpz cl block enable' => 'Benutzerdefinierte Blocklist aktivieren:',
>>>>>>>> +'rpz cl block info' => 'Gesperrte Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>>> +'rpz cl block' => 'Benutzerdefinierte Blocklist',
>>>>>>>> +'rpz cl' => 'Benutzerdefinierte Listen',
>>>>>>>> +'rpz exitcode 101' => 'Der Name enthält unzulässige Zeichen',
>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf hat eine fehlerhafte Konfiguration ermittelt. Führen Sie das Kommando unbound-checkconf auf der Konsole aus, um weitere Informationen zu erhalten.',
>>>>>>>> +'rpz exitcode 103' => 'Die benutzerdefinierte Allow-/Blocklist ist leer',
>>>>>>>> +'rpz exitcode 104' => 'Ein Eintrag mit identischem Namen existiert bereits',
>>>>>>>> +'rpz exitcode 105' => 'Die URL ist ungültig',
>>>>>>>> +'rpz exitcode 106' => 'Eintrag kann nicht entfernt werden, der Name existiert nicht',
>>>>>>>> +'rpz exitcode 107' => 'Der Name ist ungültig - nur "allow" oder "block" möglich',
>>>>>>>> +'rpz exitcode 108' => 'Fehlende oder inkorrekte Parameter',
>>>>>>>> +'rpz exitcode 109' => 'unbound-control reload ist fehlgeschlagen',
>>>>>>>> +'rpz exitcode 110' => 'Die benutzerdefinierte Allow-/Blocklist enthält unzulässige Einträge',
>>>>>>>> +'rpz exitcode 201' => 'Die Anmerkung enthält unzulässige Zeichen',
>>>>>>>> +'rpz exitcode 202' => 'Ungültiger Eintrag in der benutzerdefinierten Allowlist, Zeile ',
>>>>>>>> +'rpz exitcode 203' => 'Ungültiger Eintrag in der benutzerdefinierten Blocklist, Zeile ',
>>>>>>>> +'rpz exitcode 204' => 'Ausgewählter Eintrag existiert nicht: ',
>>>>>>>> +'rpz zf editor' => 'Zonendatei-Eintrag bearbeiten',
>>>>>>>> +'rpz zf imported' => '(importiert aus rpz-config)',
>>>>>>>> +'rpz zf remark info' => 'Erlaubte Zeichen sind a-z, A-Z, 0-9 und Unterstriche',
>>>>>>>> +'rpz zf' => 'Zonendateien',
>>>>>>>> +);
>>>>>>>> diff --git a/config/rpz/rpz.en.pl b/config/rpz/rpz.en.pl
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..0720a8940
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/rpz.en.pl
>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>> +%tr = (%tr,
>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>> +'rpz apply' => 'Apply',
>>>>>>>> +'rpz cl allow enable' => 'Enable custom allowlist:',
>>>>>>>> +'rpz cl allow info' => 'Allowed domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>>> +'rpz cl allow' => 'Custom allowlist',
>>>>>>>> +'rpz cl block enable' => 'Enable custom blocklist:',
>>>>>>>> +'rpz cl block info' => 'Blocked domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>>> +'rpz cl block' => 'Custom blocklist',
>>>>>>>> +'rpz cl' => 'Custom lists',
>>>>>>>> +'rpz exitcode 101' => 'the NAME is not valid',
>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf found invalid configuration. In the Terminal run the command unbound-checkconf for more information',
>>>>>>>> +'rpz exitcode 103' => 'the allow/block list is empty',
>>>>>>>> +'rpz exitcode 104' => 'duplicate - NAME already exists',
>>>>>>>> +'rpz exitcode 105' => 'the URL is not valid',
>>>>>>>> +'rpz exitcode 106' => 'cannot remove the NAME does not exist',
>>>>>>>> +'rpz exitcode 107' => 'the NAME is not valid - "allow" or "block" only',
>>>>>>>> +'rpz exitcode 108' => 'missing or incorrect parameter',
>>>>>>>> +'rpz exitcode 109' => 'unbound-control reload failed',
>>>>>>>> +'rpz exitcode 110' => 'custom Allowlist/Blocklist contains invalid entries',
>>>>>>>> +'rpz exitcode 201' => 'the REMARK is not valid',
>>>>>>>> +'rpz exitcode 202' => 'invalid entry in allowlist, line ',
>>>>>>>> +'rpz exitcode 203' => 'invalid entry in blocklist, line ',
>>>>>>>> +'rpz exitcode 204' => 'Selected entry does not exist: ',
>>>>>>>> +'rpz zf editor' => 'Edit zonefiles entry',
>>>>>>>> +'rpz zf imported' => '(imported from rpz-config)',
>>>>>>>> +'rpz zf remark info' => 'Valid characters are a-z, A-Z, 0-9 and underscore.',
>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>> +);
>>>>>>>> diff --git a/config/rpz/rpz.es.pl b/config/rpz/rpz.es.pl
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..98628e4aa
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/rpz.es.pl
>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>> +%tr = (%tr,
>>>>>>>> +'rpz' => 'Zonas de política de respuesta (RPZ)',
>>>>>>>> +'rpz apply' => 'Aplicar',
>>>>>>>> +'rpz cl allow enable' => 'Habilitar la lista blanca personalizada:',
>>>>>>>> +'rpz cl allow info' => 'Dominio permitido (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>>> +'rpz cl allow' => 'Lista blanca personalizada',
>>>>>>>> +'rpz cl block enable' => 'Habilitar la lista negra personalizada:',
>>>>>>>> +'rpz cl block info' => 'Dominio bloqueado (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>>> +'rpz cl block' => 'Lista negra personalizada',
>>>>>>>> +'rpz cl' => 'Lista personalizada',
>>>>>>>> +'rpz exitcode 101' => 'El NOMBRE no es válido',
>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha encontrado una configuración no válida. Desde Terminal, ejecute el comando unbound-checkconf para mayor información',
>>>>>>>> +'rpz exitcode 103' => 'La lista de permitidos/bloqueados está vacía',
>>>>>>>> +'rpz exitcode 104' => 'duplicado - NOMBRE ya existe',
>>>>>>>> +'rpz exitcode 105' => 'la URL no es válida',
>>>>>>>> +'rpz exitcode 106' => 'no es posible eliminar el NOMBRE que no existe',
>>>>>>>> +'rpz exitcode 107' => 'el NOMBRE no es válido - sólo "permitir" o "bloquear"',
>>>>>>>> +'rpz exitcode 108' => 'parámetro faltante o incorrecto',
>>>>>>>> +'rpz exitcode 109' => 'Error al recargar unbound-control',
>>>>>>>> +'rpz exitcode 110' => 'la Lista blanca/Lista negra personalizada contiene entradas no válidas',
>>>>>>>> +'rpz exitcode 201' => 'el COMENTARIO no es válido',
>>>>>>>> +'rpz exitcode 202' => 'entrada no válida en la lista blanca, línea ',
>>>>>>>> +'rpz exitcode 203' => 'entrada no válida en la lista negra, línea ',
>>>>>>>> +'rpz exitcode 204' => 'La entrada seleccionada no existe: ',
>>>>>>>> +'rpz zf editor' => 'Editar la entrada de archivos de zona',
>>>>>>>> +'rpz zf imported' => '(importado de rpz-config)',
>>>>>>>> +'rpz zf remark info' => 'Los caracteres válidos son a-z, A-Z, 0-9 y guión bajo',
>>>>>>>> +'rpz zf' => 'Archivos de zona',
>>>>>>>> +);
>>>>>>>> diff --git a/config/rpz/rpz.fr.pl b/config/rpz/rpz.fr.pl
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..f35f3c2d0
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/rpz.fr.pl
>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>> +%tr = (%tr,
>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>> +'rpz apply' => 'Appliquer',
>>>>>>>> +'rpz cl allow enable' => 'Activer la liste d\'autorisations personnalisée:',
>>>>>>>> +'rpz cl allow info' => 'Domaines autorisés (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>>> +'rpz cl allow' => 'Liste d\'autorisations personnalisée',
>>>>>>>> +'rpz cl block enable' => 'Activer la liste de blocage personnalisée:',
>>>>>>>> +'rpz cl block info' => 'Domaines bloqués (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>>> +'rpz cl block' => 'liste de blocage personnalisé',
>>>>>>>> +'rpz cl' => 'Listes personnalisées',
>>>>>>>> +'rpz exitcode 101' => 'le NOM n\'est pas valide',
>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf configuration non valide trouvée. Dans le terminal, exécutez la commande unbound-checkconf pour plus d\'informations',
>>>>>>>> +'rpz exitcode 103' => 'la liste autoriser/bloquer est vide',
>>>>>>>> +'rpz exitcode 104' => 'le NOM existe déjà',
>>>>>>>> +'rpz exitcode 105' => 'L\'URL n\'est pas valide',
>>>>>>>> +'rpz exitcode 106' => 'impossible de supprimer le NOM n\'existe pas',
>>>>>>>> +'rpz exitcode 107' => 'le NOM n\'est pas valide - « autoriser » ou « bloquer » seulement',
>>>>>>>> +'rpz exitcode 108' => 'paramètre manquant ou incorrect',
>>>>>>>> +'rpz exitcode 109' => 'unbound-control rechargement échoué',
>>>>>>>> +'rpz exitcode 110' => 'la liste autoriser/bloquer contient des entrées non valides',
>>>>>>>> +'rpz exitcode 201' => 'la REMARQUE n\'est pas valable',
>>>>>>>> +'rpz exitcode 202' => 'entrée non valide dans la liste d\'autorisation, ligne ',
>>>>>>>> +'rpz exitcode 203' => 'entrée non valide dans la liste de blocs, ligne ',
>>>>>>>> +'rpz exitcode 204' => 'L\'entrée sélectionnée n\'existe pas: ',
>>>>>>>> +'rpz zf editor' => 'Modifier l\'entrée Fichiers Zone',
>>>>>>>> +'rpz zf imported' => '(importé de rpz-config)',
>>>>>>>> +'rpz zf remark info' => 'Les caractères valides sont a-z, A-Z, 0-9 et soulignement.',
>>>>>>>> +'rpz zf' => 'Fichiers Zone',
>>>>>>>> +);
>>>>>>>> diff --git a/config/rpz/rpz.it.pl b/config/rpz/rpz.it.pl
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..ee81605c9
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/rpz.it.pl
>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>> +%tr = (%tr,
>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>> +'rpz apply' => 'Applica',
>>>>>>>> +'rpz cl allow enable' => 'Abilita la Whitelist personalizzata:',
>>>>>>>> +'rpz cl allow info' => 'Domini consentiti (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>>> +'rpz cl allow' => 'Whitelist personalizzata',
>>>>>>>> +'rpz cl block enable' => 'Abilita la Blacklist personalizzata:',
>>>>>>>> +'rpz cl block info' => 'Domini bloccati (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>>> +'rpz cl block' => 'Blacklist personalizzata',
>>>>>>>> +'rpz cl' => 'Liste personalizzate',
>>>>>>>> +'rpz exitcode 101' => 'il NOME non è valido',
>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha trovato una configurazione non valida. Dal Terminale esegui il comando unbound-checkconf per maggiori informazioni',
>>>>>>>> +'rpz exitcode 103' => 'l\'elenco consentiti/bloccati è vuoto',
>>>>>>>> +'rpz exitcode 104' => 'duplicato - NAME esiste di già',
>>>>>>>> +'rpz exitcode 105' => 'l\'URL non è valido',
>>>>>>>> +'rpz exitcode 106' => 'non è possibile rimuovere il NOME non esiste',
>>>>>>>> +'rpz exitcode 107' => 'il NOME non è valido - solo "consenti" o "blocca"',
>>>>>>>> +'rpz exitcode 108' => 'parametro mancante o non corretto',
>>>>>>>> +'rpz exitcode 109' => 'ricaricamento del controllo non associato non riuscito',
>>>>>>>> +'rpz exitcode 110' => 'la Whitelist/Blacklist personalizzata contiene voci non valide',
>>>>>>>> +'rpz exitcode 201' => 'l"OSSERVAZIONE non è valida',
>>>>>>>> +'rpz exitcode 202' => 'voce non valida nella Whitelist, riga ',
>>>>>>>> +'rpz exitcode 203' => 'voce non valida nella Blacklist, riga ',
>>>>>>>> +'rpz exitcode 204' => 'La voce selezionata non esiste: ',
>>>>>>>> +'rpz zf editor' => 'Modifica la voce dei file di zona',
>>>>>>>> +'rpz zf imported' => '(importato da rpz-config)',
>>>>>>>> +'rpz zf remark info' => 'I caratteri validi sono a-z, A-Z, 0-9 e trattino basso',
>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>> +);
>>>>>>>> diff --git a/config/rpz/rpz.tr.pl b/config/rpz/rpz.tr.pl
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..00226e192
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/rpz.tr.pl
>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>> +# I�in eklendi Ayriyeten Response Policy Zone (RPZ)
>>>>>>>> +%tr = (%tr,
>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>> +'rpz apply' => 'Uygulamak',
>>>>>>>> +'rpz cl allow enable' => '�zel Etkinlestir allowlist:',
>>>>>>>> +'rpz cl allow info' => 'Izin verilmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>>> +'rpz cl allow' => 'Etkinlestir allowlist',
>>>>>>>> +'rpz cl block enable' => '�zel Etkinlestir blocklist:',
>>>>>>>> +'rpz cl block info' => 'Engellenmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>>> +'rpz cl block' => 'Engellenmis blocklist',
>>>>>>>> +'rpz cl' => 'Engellenmis lists',
>>>>>>>> +'rpz exitcode 101' => 'NAME ge�erli degil',
>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ge�ersiz yapilandirma bulundu. Terminalde daha fazla bilgi i�in Unbound-checkConf komutunu �alistirin',
>>>>>>>> +'rpz exitcode 103' => 'allow/block liste bos',
>>>>>>>> +'rpz exitcode 104' => 'kopyalamak - NAME zaten var',
>>>>>>>> +'rpz exitcode 105' => 'URL ge�erli degil',
>>>>>>>> +'rpz exitcode 106' => '�ikarilamiyor NAME yok',
>>>>>>>> +'rpz exitcode 107' => 'NAME ge�erli degil - "allow" veya "block" yalniz',
>>>>>>>> +'rpz exitcode 108' => 'Parametre eksik veya yanlis',
>>>>>>>> +'rpz exitcode 109' => 'unbound-control basarisiz',
>>>>>>>> +'rpz exitcode 110' => 'Engellenmis Allowlist/Blocklist ge�ersiz girisler i�erir',
>>>>>>>> +'rpz exitcode 201' => 'REMARK ge�erli degil',
>>>>>>>> +'rpz exitcode 202' => 'Ge�ersiz giris allowlist, line ',
>>>>>>>> +'rpz exitcode 203' => 'Ge�ersiz giris blocklist, line ',
>>>>>>>> +'rpz exitcode 204' => 'Se�ilen giris yok: ',
>>>>>>>> +'rpz zf editor' => 'yazimlamak zonefiles giris',
>>>>>>>> +'rpz zf imported' => '(ithal edildi rpz-config)',
>>>>>>>> +'rpz zf remark info' => 'Ge�erli karakterler a-z, A-Z, 0-9ve alt�st.',
>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>> +);
>>>>>>>> diff --git a/html/cgi-bin/rpz.cgi b/html/cgi-bin/rpz.cgi
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..a821c92ac
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/html/cgi-bin/rpz.cgi
>>>>>>>> @@ -0,0 +1,923 @@
>>>>>>>> +#!/usr/bin/perl
>>>>>>>> +###############################################################################
>>>>>>>> +# #
>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>> +# Copyright (C) 2005-2024 IPFire Team <info(a)ipfire.org> #
>>>>>>>> +# #
>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>> +# #
>>>>>>>> +###############################################################################
>>>>>>>> +
>>>>>>>> +use strict;
>>>>>>>> +use Scalar::Util qw(looks_like_number);
>>>>>>>> +
>>>>>>>> +# debugging
>>>>>>>> +#use warnings;
>>>>>>>> +#use CGI::Carp 'fatalsToBrowser';
>>>>>>>> +#use Data::Dumper;
>>>>>>>> +
>>>>>>>> +require '/var/ipfire/general-functions.pl';
>>>>>>>> +require "${General::swroot}/lang.pl";
>>>>>>>> +require "${General::swroot}/header.pl";
>>>>>>>> +
>>>>>>>> +###--- Extra HTML ---###
>>>>>>>> +my $extraHead = <<END
>>>>>>>> +<style>
>>>>>>>> + /* alternating row background */
>>>>>>>> + .tbl tr:nth-child(2n+2) {
>>>>>>>> + background-color: var(--color-light-grey);
>>>>>>>> + }
>>>>>>>> + .tbl tr:nth-child(2n+3) {
>>>>>>>> + background-color: var(--color-grey);
>>>>>>>> + }
>>>>>>>> + /* text styles */
>>>>>>>> + .tbl th:not(:last-child) {
>>>>>>>> + text-align: left;
>>>>>>>> + }
>>>>>>>> + div.right {
>>>>>>>> + text-align: right;
>>>>>>>> + margin-top: 0.5em;
>>>>>>>> + }
>>>>>>>> + /* customlist input */
>>>>>>>> + textarea.domainlist {
>>>>>>>> + margin: 0.5em 0;
>>>>>>>> + resize: vertical;
>>>>>>>> + min-height: 10em;
>>>>>>>> + overflow: auto;
>>>>>>>> + white-space: pre;
>>>>>>>> + }
>>>>>>>> + button[type=submit]:disabled {
>>>>>>>> + opacity: 0.6;
>>>>>>>> + }
>>>>>>>> +</style>
>>>>>>>> +END
>>>>>>>> +;
>>>>>>>> +###--- End of extra HTML ---###
>>>>>>>> +
>>>>>>>> +
>>>>>>>> +### Settings ###
>>>>>>>> +
>>>>>>>> +# Request DNS service reload after configuration change
>>>>>>>> +my $RPZ_RELOAD_FLAG = "${General::swroot}/dns/rpz/reload.flag";
>>>>>>>> +
>>>>>>>> +# Configuration file for all available zonefiles
>>>>>>>> +# Format: index, name (unique), enabled (on/off), URL, remark
>>>>>>>> +my $ZONEFILES_CONF = "${General::swroot}/dns/rpz/zonefiles.conf";
>>>>>>>> +
>>>>>>>> +# Configuration file for custom lists
>>>>>>>> +# IDs: 0=allowlist, 1=blocklist, 2=options (allow/block enabled)
>>>>>>>> +my $CUSTOMLISTS_CONF = "${General::swroot}/dns/rpz/customlists.conf";
>>>>>>>> +
>>>>>>>> +# Export custom lists to rpz-config
>>>>>>>> +my $RPZ_ALLOWLIST = "${General::swroot}/dns/rpz/allowlist";
>>>>>>>> +my $RPZ_BLOCKLIST = "${General::swroot}/dns/rpz/blocklist";
>>>>>>>> +
>>>>>>>> +
>>>>>>>> +### Preparation ###
>>>>>>>> +
>>>>>>>> +# Create missing config files
>>>>>>>> +unless(-f $ZONEFILES_CONF) { &General::system('touch', "$ZONEFILES_CONF"); }
>>>>>>>> +unless(-f $CUSTOMLISTS_CONF) { &General::system('touch', "$CUSTOMLISTS_CONF"); }
>>>>>>>> +
>>>>>>>> +
>>>>>>>> +## Global gui data
>>>>>>>> +my $errormessage = "";
>>>>>>>> +
>>>>>>>> +## Global configuration data
>>>>>>>> +my %zonefiles = ();
>>>>>>>> +my %customlists = ();
>>>>>>>> +&_zonefiles_load();
>>>>>>>> +&_customlists_load();
>>>>>>>> +
>>>>>>>> +## Global CGI form data
>>>>>>>> +my %cgiparams = ();
>>>>>>>> +&Header::getcgihash(\%cgiparams);
>>>>>>>> +
>>>>>>>> +my $action = $cgiparams{'ACTION'} // 'NONE';
>>>>>>>> +my $action_key = $cgiparams{'KEY'} // ''; # entry being edited, empty = none/new
>>>>>>>> +
>>>>>>>> +
>>>>>>>> +###--- Process form actions ---###
>>>>>>>> +
>>>>>>>> +# Zonefiles action: Check whether the requested entry exists
>>>>>>>> +if((substr($action, 0, 3) eq 'ZF_') && ($action_key)) {
>>>>>>>> + unless(defined $zonefiles{$action_key}) {
>>>>>>>> + $errormessage = &_rpz_error_tr(204, $action_key);
>>>>>>>> + $action = 'NONE';
>>>>>>>> + }
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +## Perform actions
>>>>>>>> +if($action eq 'ZF_SAVE') { ## Save new or modified zonefiles entry
>>>>>>>> + if(&_action_zf_save()) {
>>>>>>>> + $action = 'NONE'; # success, return to main page
>>>>>>>> + &_http_prg_redirect();
>>>>>>>> + } else {
>>>>>>>> + $action = 'ZF_EDIT'; # error occured, keep editing
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> +} elsif($action eq 'ZF_TOGGLE') { ## Toggle on/off
>>>>>>>> + if(&_action_zf_toggle()) {
>>>>>>>> + $action = 'NONE';
>>>>>>>> + &_http_prg_redirect();
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> +} elsif($action eq 'ZF_REMOVE') { ## Remove entry
>>>>>>>> + if(&_action_zf_remove()) {
>>>>>>>> + $action = 'NONE';
>>>>>>>> + &_http_prg_redirect();
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> +} elsif($action eq 'CL_SAVE') { ## Save custom lists
>>>>>>>> + if(&_action_cl_save()) {
>>>>>>>> + $action = 'NONE';
>>>>>>>> + &_http_prg_redirect();
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> +} elsif($action eq 'RPZ_RELOAD') { ## Reload dns configuration
>>>>>>>> + if(&_action_rpz_reload()) {
>>>>>>>> + $action = 'NONE';
>>>>>>>> + &_http_prg_redirect();
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> +} elsif($action eq 'UNB_RESTART') { ## Restart unbound service
>>>>>>>> + if(&_action_unb_restart()) {
>>>>>>>> + $action = 'NONE';
>>>>>>>> + &_http_prg_redirect();
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +
>>>>>>>> +###--- Start GUI ---###
>>>>>>>> +
>>>>>>>> +## Start http output
>>>>>>>> +&Header::showhttpheaders();
>>>>>>>> +
>>>>>>>> +# Start HTML
>>>>>>>> +&Header::openpage($Lang::tr{'rpz'}, 1, $extraHead);
>>>>>>>> +&Header::openbigbox('100%', 'left', '');
>>>>>>>> +
>>>>>>>> +# Show error messages
>>>>>>>> +if($errormessage) {
>>>>>>>> + &_print_message($errormessage);
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Handle zonefile add/edit mode
>>>>>>>> +if($action eq "ZF_EDIT") {
>>>>>>>> + &_print_zonefile_editor();
>>>>>>>> +
>>>>>>>> + # Finalize page and exit cleanly
>>>>>>>> + &Header::closebigbox();
>>>>>>>> + &Header::closepage();
>>>>>>>> + exit(0);
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Show gui elements
>>>>>>>> +&_print_zonefiles();
>>>>>>>> +&_print_customlists();
>>>>>>>> +&_print_gui_extras();
>>>>>>>> +
>>>>>>>> +&Header::closebigbox();
>>>>>>>> +&Header::closepage();
>>>>>>>> +
>>>>>>>> +###--- End of GUI ---###
>>>>>>>> +
>>>>>>>> +
>>>>>>>> +###--- Internal configuration file functions ---###
>>>>>>>> +
>>>>>>>> +# Load all available zonefiles from rpz-config and the internal configuration
>>>>>>>> +sub _zonefiles_load {
>>>>>>>> + # Clean start
>>>>>>>> + %zonefiles = ();
>>>>>>>> +
>>>>>>>> + # Source 1: Get the currently enabled zonefiles from rpz-config (expected format [name]=[URL])
>>>>>>>> + my @enabled_files = &General::system_output('/usr/sbin/rpz-config', 'list');
>>>>>>>> +
>>>>>>>> + foreach my $row (@enabled_files) {
>>>>>>>> + chomp($row);
>>>>>>>> +
>>>>>>>> + # Use regex instead of split() to skip non-matching lines
>>>>>>>> + next unless($row =~ /^(\w+)=(.+)$/);
>>>>>>>> + my ($name, $url) = ($1, $2);
>>>>>>>> +
>>>>>>>> + # Unique names are already guaranteed by rpz-config
>>>>>>>> + if(&_rpz_validate_zonefile($name, $url, '', 0) == 0) {
>>>>>>>> + # Populate global data hash, mark all found entries as enabled
>>>>>>>> + my %entry = ('enabled' => 'on',
>>>>>>>> + 'url' => $url,
>>>>>>>> + 'remark' => $Lang::tr{'rpz zf imported'});
>>>>>>>> +
>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>> + }
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Source 2: Get additional data and disabled entries from configuration file
>>>>>>>> + my %configured_files = ();
>>>>>>>> + &General::readhasharray($ZONEFILES_CONF, \%configured_files);
>>>>>>>> +
>>>>>>>> + foreach my $row (values (%configured_files)) {
>>>>>>>> + my ($name, $enabled, $url, $remark) = @$row;
>>>>>>>> + $remark //= "";
>>>>>>>> +
>>>>>>>> + next unless($name);
>>>>>>>> +
>>>>>>>> + # Check whether this row belongs to an entry already imported from rpz-config
>>>>>>>> + if(defined $zonefiles{$name}) {
>>>>>>>> + # Existing entry, only merge additional data
>>>>>>>> + $zonefiles{$name}{'remark'} = $remark;
>>>>>>>> + } else {
>>>>>>>> + # Skip entry if it is marked as enabled but not found by rpz-config. It was then deleted manually
>>>>>>>> + if($enabled ne 'on') {
>>>>>>>> + # Populate global data hash
>>>>>>>> + my %entry = ('enabled' => 'off',
>>>>>>>> + 'url' => $url // "",
>>>>>>>> + 'remark' => $remark);
>>>>>>>> +
>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>> + }
>>>>>>>> + }
>>>>>>>> + }
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Save internal zonefiles configuration
>>>>>>>> +sub _zonefiles_save_conf {
>>>>>>>> + my $index = 0;
>>>>>>>> + my %export = ();
>>>>>>>> +
>>>>>>>> + # Loop trough all zonefiles and create "hasharray" type export
>>>>>>>> + foreach my $name (keys %zonefiles) {
>>>>>>>> + my @entry = ($name,
>>>>>>>> + $zonefiles{$name}{'enabled'},
>>>>>>>> + $zonefiles{$name}{'url'},
>>>>>>>> + $zonefiles{$name}{'remark'});
>>>>>>>> +
>>>>>>>> + $export{$index++} = \@entry;
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + &General::writehasharray($ZONEFILES_CONF, \%export);
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Load custom lists from rpz-config and the internal configuration
>>>>>>>> +sub _customlists_load {
>>>>>>>> + # Clean start
>>>>>>>> + %customlists = ();
>>>>>>>> +
>>>>>>>> + # Load configuration file
>>>>>>>> + my %lists_conf = ();
>>>>>>>> + &General::readhasharray($CUSTOMLISTS_CONF, \%lists_conf);
>>>>>>>> +
>>>>>>>> + # Get list options, enabled by default to start import
>>>>>>>> + $customlists{'allow'}{'enabled'} = $lists_conf{2}[0] // 'on';
>>>>>>>> + $customlists{'block'}{'enabled'} = $lists_conf{2}[1] // 'on';
>>>>>>>> +
>>>>>>>> + # Import enabled list from rpz-config, otherwise retrieve stored or empty list from configuration file
>>>>>>>> + if($customlists{'allow'}{'enabled'} eq 'on') {
>>>>>>>> + &_customlist_import('allow', $RPZ_ALLOWLIST);
>>>>>>>> + } else {
>>>>>>>> + $customlists{'allow'}{'list'} = $lists_conf{0} // [];
>>>>>>>> + }
>>>>>>>> + if($customlists{'block'}{'enabled'} eq 'on') {
>>>>>>>> + &_customlist_import('block', $RPZ_BLOCKLIST);
>>>>>>>> + } else {
>>>>>>>> + $customlists{'block'}{'list'} = $lists_conf{1} // [];
>>>>>>>> + }
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Save internal custom lists configuration
>>>>>>>> +sub _customlists_save_conf {
>>>>>>>> + my %export = ();
>>>>>>>> +
>>>>>>>> + # Match IDs with import function
>>>>>>>> + $export{0} = $customlists{'allow'}{'list'};
>>>>>>>> + $export{1} = $customlists{'block'}{'list'};
>>>>>>>> + $export{2} = [$customlists{'allow'}{'enabled'}, $customlists{'block'}{'enabled'}];
>>>>>>>> +
>>>>>>>> + &General::writehasharray($CUSTOMLISTS_CONF, \%export);
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Import a custom list from plain file, returns empty list if file is missing
>>>>>>>> +sub _customlist_import {
>>>>>>>> + my ($listname, $filename) = @_;
>>>>>>>> + my @list = ();
>>>>>>>> +
>>>>>>>> + # File exists, load and check all lines
>>>>>>>> + if(-f $filename) {
>>>>>>>> + open(my $FH, '<', $filename) or die "Can't read $filename: $!";
>>>>>>>> + while(my $line = <$FH>) {
>>>>>>>> + chomp($line);
>>>>>>>> + push(@list, $line);
>>>>>>>> + }
>>>>>>>> + close($FH);
>>>>>>>> +
>>>>>>>> + # Clean up imported data
>>>>>>>> + &_rpz_validate_customlist(\@list, 1);
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + $customlists{$listname}{'list'} = \@list;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Export a custom list to plain file or clear file if list is disabled
>>>>>>>> +sub _customlist_export {
>>>>>>>> + my ($listname, $filename) = @_;
>>>>>>>> + return unless(defined $customlists{$listname});
>>>>>>>> +
>>>>>>>> + # Write enabled domain list to file, otherwise save empty file
>>>>>>>> + open(my $FH, '>', $filename) or die "Can't write $filename: $!";
>>>>>>>> +
>>>>>>>> + if($customlists{$listname}{'enabled'} eq 'on') {
>>>>>>>> + foreach my $line (@{$customlists{$listname}{'list'}}) {
>>>>>>>> + print $FH "$line\n";
>>>>>>>> + }
>>>>>>>> + } else {
>>>>>>>> + print $FH "; Note: This list is currently disabled by $ENV{'SCRIPT_NAME'}\n";
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + close($FH);
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +
>>>>>>>> +###--- Internal gui functions ---###
>>>>>>>> +
>>>>>>>> +# Show simple message box
>>>>>>>> +sub _print_message {
>>>>>>>> + my ($message, $title) = @_;
>>>>>>>> + $title ||= $Lang::tr{'error messages'};
>>>>>>>> +
>>>>>>>> + &Header::openbox('100%', 'left', $title);
>>>>>>>> + print "<span>$message</span>";
>>>>>>>> + &Header::closebox();
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Show all zone files and related gui elements
>>>>>>>> +sub _print_zonefiles {
>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf'});
>>>>>>>> +
>>>>>>>> + print <<END
>>>>>>>> +<table class="tbl" width="100%">
>>>>>>>> + <tr>
>>>>>>>> + <th>$Lang::tr{'name'}</th>
>>>>>>>> + <th>URL</th>
>>>>>>>> + <th>$Lang::tr{'remark'}</th>
>>>>>>>> + <th colspan="3">$Lang::tr{'action'}</th>
>>>>>>>> + </tr>
>>>>>>>> +END
>>>>>>>> +;
>>>>>>>> +
>>>>>>>> + # Sort zonefiles by name and loop trough all entries
>>>>>>>> + foreach my $name (sort keys %zonefiles) {
>>>>>>>> +
>>>>>>>> + # Toggle button label translation
>>>>>>>> + my $toggle_tr = ($zonefiles{$name}{'enabled'} eq 'on') ? $Lang::tr{'click to disable'} : $Lang::tr{'click to enable'};
>>>>>>>> +
>>>>>>>> + print <<END
>>>>>>>> + <tr>
>>>>>>>> + <td>$name</td>
>>>>>>>> + <td>$zonefiles{$name}{'url'}</td>
>>>>>>>> + <td>$zonefiles{$name}{'remark'}</td>
>>>>>>>> +
>>>>>>>> + <td align="center" width="5%">
>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_TOGGLE">
>>>>>>>> + <input type="image" src="/images/$zonefiles{$name}{'enabled'}.gif" title="$toggle_tr" alt="$toggle_tr">
>>>>>>>> + </form>
>>>>>>>> + </td>
>>>>>>>> + <td align="center" width="5%">
>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_EDIT">
>>>>>>>> + <input type="image" src="/images/edit.gif" title="$Lang::tr{'edit'}" alt="$Lang::tr{'edit'}">
>>>>>>>> + </form>
>>>>>>>> + </td>
>>>>>>>> + <td align="center" width="5%">
>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_REMOVE">
>>>>>>>> + <input type="image" src="/images/delete.gif" title="$Lang::tr{'remove'}" alt="$Lang::tr{'remove'}">
>>>>>>>> + </form>
>>>>>>>> + </td>
>>>>>>>> + </tr>
>>>>>>>> +END
>>>>>>>> +;
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Disable reload button if not needed
>>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>>> +
>>>>>>>> + print <<END
>>>>>>>> +</table>
>>>>>>>> +
>>>>>>>> +<div class="right">
>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>> + <input type="hidden" name="KEY" value="">
>>>>>>>> + <button type="submit" name="ACTION" value="ZF_EDIT">$Lang::tr{'add'}</button>
>>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>>> + </form>
>>>>>>>> +</div>
>>>>>>>> +END
>>>>>>>> +;
>>>>>>>> +
>>>>>>>> + &Header::closebox();
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Show zonefiles entry editor
>>>>>>>> +sub _print_zonefile_editor {
>>>>>>>> +
>>>>>>>> + # Key specified: Edit existing entry
>>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>>> + # Load data to be edited, but don't override already present values (allows user to edit after error)
>>>>>>>> + $cgiparams{'ZF_NAME'} //= $action_key;
>>>>>>>> + $cgiparams{'ZF_URL'} //= $zonefiles{$action_key}{'url'};
>>>>>>>> + $cgiparams{'ZF_REMARK'} //= $zonefiles{$action_key}{'remark'};
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Fallback to empty form
>>>>>>>> + $cgiparams{'ZF_NAME'} //= "";
>>>>>>>> + $cgiparams{'ZF_URL'} //= "";
>>>>>>>> + $cgiparams{'ZF_REMARK'} //= "";
>>>>>>>> +
>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf editor'});
>>>>>>>> +
>>>>>>>> + print <<END
>>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>> +<input type="hidden" name="KEY" value="$action_key">
>>>>>>>> +<table width="100%">
>>>>>>>> + <tr>
>>>>>>>> + <td width="20%">$Lang::tr{'name'}: <img src="/blob.gif" alt="*"></td>
>>>>>>>> + <td><input type="text" name="ZF_NAME" value="$cgiparams{'ZF_NAME'}" size="40" maxlength="32" title="$Lang::tr{'rpz zf remark info'}" pattern="[a-zA-Z0-9_]{1,32}" required></td>
>>>>>>>> + </tr>
>>>>>>>> + <tr>
>>>>>>>> + <td width="20%">URL: <img src="/blob.gif" alt="*"></td>
>>>>>>>> + <td><input type="url" name="ZF_URL" value="$cgiparams{'ZF_URL'}" size="40" maxlength="128" required></td>
>>>>>>>> + </tr>
>>>>>>>> + <tr>
>>>>>>>> + <td width="20%">$Lang::tr{'remark'}:</td>
>>>>>>>> + <td><input type="text" name="ZF_REMARK" value="$cgiparams{'ZF_REMARK'}" size="40" maxlength="32"></td>
>>>>>>>> + </tr>
>>>>>>>> + <tr>
>>>>>>>> + <td colspan="2"><hr></td>
>>>>>>>> + </tr>
>>>>>>>> + <tr>
>>>>>>>> + <td width="55%"><img src="/blob.gif" alt="*"> $Lang::tr{'required field'}</td>
>>>>>>>> + <td align="right"><button type="submit" name="ACTION" value="ZF_SAVE">$Lang::tr{'save'}</button></td>
>>>>>>>> + </tr>
>>>>>>>> +</table>
>>>>>>>> +</form>
>>>>>>>> +
>>>>>>>> +<div class="right">
>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>> + <button type="submit" name="ACTION" value="NONE">$Lang::tr{'back'}</button>
>>>>>>>> + </form>
>>>>>>>> +</div>
>>>>>>>> +END
>>>>>>>> +;
>>>>>>>> +
>>>>>>>> + &Header::closebox();
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Show custom allow/block files and related gui elements
>>>>>>>> +sub _print_customlists {
>>>>>>>> +
>>>>>>>> + # Load lists from config, unless they are currently being edited
>>>>>>>> + if($action ne 'CL_SAVE') {
>>>>>>>> + $cgiparams{'ALLOW_LIST'} = join("\n", @{$customlists{'allow'}{'list'}});
>>>>>>>> + $cgiparams{'BLOCK_LIST'} = join("\n", @{$customlists{'block'}{'list'}});
>>>>>>>> +
>>>>>>>> + $cgiparams{'ALLOW_ENABLED'} = ($customlists{'allow'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>>> + $cgiparams{'BLOCK_ENABLED'} = ($customlists{'block'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Fallback to empty form
>>>>>>>> + $cgiparams{'ALLOW_LIST'} //= "";
>>>>>>>> + $cgiparams{'BLOCK_LIST'} //= "";
>>>>>>>> +
>>>>>>>> + # HTML checkboxes, unchecked = no or undef value in POST data
>>>>>>>> + my %checked = ();
>>>>>>>> + $checked{'ALLOW_ENABLED'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? " checked" : "";
>>>>>>>> + $checked{'BLOCK_ENABLED'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? " checked" : "";
>>>>>>>> +
>>>>>>>> + # Disable reload button if not needed
>>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>>> +
>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz cl'});
>>>>>>>> +
>>>>>>>> + print <<END
>>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>> +<table width="100%">
>>>>>>>> + <tr>
>>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl allow'}</b><br>$Lang::tr{'rpz cl allow info'}</td>
>>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl block'}</b><br>$Lang::tr{'rpz cl block info'}</td>
>>>>>>>> + </tr>
>>>>>>>> + <tr>
>>>>>>>> + <td colspan="2"><textarea name="ALLOW_LIST" class="domainlist" cols="45">
>>>>>>>> +$cgiparams{'ALLOW_LIST'}</textarea></td>
>>>>>>>> + <td colspan="2"><textarea name="BLOCK_LIST" class="domainlist" cols="45">
>>>>>>>> +$cgiparams{'BLOCK_LIST'}</textarea></td>
>>>>>>>> + </tr>
>>>>>>>> + <tr>
>>>>>>>> + <td><label for="allow_enabled">$Lang::tr{'rpz cl allow enable'}</label></td>
>>>>>>>> + <td width="15%"><input type="checkbox" name="ALLOW_ENABLED" id="allow_enabled"$checked{'ALLOW_ENABLED'}></td>
>>>>>>>> + <td><label for="block_enabled">$Lang::tr{'rpz cl block enable'}</label></td>
>>>>>>>> + <td width="15%"><input type="checkbox" name="BLOCK_ENABLED" id="block_enabled"$checked{'BLOCK_ENABLED'}></td>
>>>>>>>> + </tr>
>>>>>>>> + <tr>
>>>>>>>> + <td colspan="4"><hr></td>
>>>>>>>> + </tr>
>>>>>>>> + <tr>
>>>>>>>> + <td align="right" colspan="4">
>>>>>>>> + <button type="submit" name="ACTION" value="CL_SAVE">$Lang::tr{'save'}</button>
>>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>>> + </td>
>>>>>>>> +</table>
>>>>>>>> +</form>
>>>>>>>> +END
>>>>>>>> +;
>>>>>>>> +
>>>>>>>> + &Header::closebox();
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Output javascript and extra gui elements
>>>>>>>> +sub _print_gui_extras {
>>>>>>>> +
>>>>>>>> + # Apply/Restart button modifier key handler
>>>>>>>> + if(&_rpz_needs_reload()) {
>>>>>>>> + print <<END
>>>>>>>> +<script>
>>>>>>>> + // Commit modifier key handler
>>>>>>>> + (function(jq, document) {
>>>>>>>> + var keyEventsOn = false; // Keyboard events attached
>>>>>>>> + var keyModify = false; // Modifier key pressed
>>>>>>>> + var mouseHover = false; // Mouse over commit button
>>>>>>>> + var btnModified = false; // Button modified to "Restart"
>>>>>>>> +
>>>>>>>> + // Document-level key events, enable only while cursor is over button
>>>>>>>> + function attachKeyEvents() {
>>>>>>>> + if(keyEventsOn) {
>>>>>>>> + return;
>>>>>>>> + }
>>>>>>>> + keyEventsOn = true;
>>>>>>>> +
>>>>>>>> + jq(document).on("keydown.rpz", function(event) {
>>>>>>>> + if((!keyModify) && event.shiftKey) {
>>>>>>>> + keyModify = true;
>>>>>>>> + handleModify();
>>>>>>>> + }
>>>>>>>> + });
>>>>>>>> + jq(document).on("keyup.rpz", function(event) {
>>>>>>>> + if(keyModify && (!event.shiftKey)) {
>>>>>>>> + keyModify = false;
>>>>>>>> + handleModify();
>>>>>>>> + }
>>>>>>>> + });
>>>>>>>> + }
>>>>>>>> + function removeKeyEvents() {
>>>>>>>> + keyModify = false;
>>>>>>>> + if(keyEventsOn) {
>>>>>>>> + jq(document).off("keydown.rpz keyup.rpz");
>>>>>>>> + keyEventsOn = false;
>>>>>>>> + }
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + // Attach mouse hover events to commit buttons
>>>>>>>> + function attachMouseEvents() {
>>>>>>>> + jq("button.commit").on("mouseenter", function(event) {
>>>>>>>> + if(!mouseHover) {
>>>>>>>> + mouseHover = true;
>>>>>>>> + attachKeyEvents();
>>>>>>>> + // Handle already pressed key
>>>>>>>> + keyModify = !!(event.shiftKey);
>>>>>>>> + handleModify();
>>>>>>>> + }
>>>>>>>> + });
>>>>>>>> +
>>>>>>>> + // Cursor moved away: Disable key listener to minimize events
>>>>>>>> + jq("button.commit").on("mouseleave", function() {
>>>>>>>> + if(mouseHover) {
>>>>>>>> + mouseHover = false;
>>>>>>>> + removeKeyEvents();
>>>>>>>> + handleModify();
>>>>>>>> + }
>>>>>>>> + });
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + // Modify commit button
>>>>>>>> + function handleModify() {
>>>>>>>> + let modify = mouseHover && keyModify;
>>>>>>>> + if(btnModified != modify) {
>>>>>>>> + if(modify) {
>>>>>>>> + jq("button.commit").text("$Lang::tr{'restart'}").val("UNB_RESTART");
>>>>>>>> + } else {
>>>>>>>> + jq("button.commit").text("$Lang::tr{'rpz apply'}").val("RPZ_RELOAD");
>>>>>>>> + }
>>>>>>>> + btnModified = modify;
>>>>>>>> + }
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + // jQuery DOM ready
>>>>>>>> + jq(function() {
>>>>>>>> + attachMouseEvents();
>>>>>>>> + });
>>>>>>>> + })(jQuery, document);
>>>>>>>> +</script>
>>>>>>>> +END
>>>>>>>> +;
>>>>>>>> + } # End of modifier key handler
>>>>>>>> +
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +
>>>>>>>> +###--- Internal action processing functions ---###
>>>>>>>> +
>>>>>>>> +# Toggle zonefile on/off
>>>>>>>> +sub _action_zf_toggle {
>>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>>> +
>>>>>>>> + my $result = 0;
>>>>>>>> + my $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>>> +
>>>>>>>> + # Perform toggle action
>>>>>>>> + if($enabled eq 'on') {
>>>>>>>> + $enabled = 'off';
>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>>> + } else {
>>>>>>>> + $enabled = 'on';
>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $action_key, $zonefiles{$action_key}{'url'}, '--no-reload');
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>> +
>>>>>>>> + # Save changes
>>>>>>>> + $zonefiles{$action_key}{'enabled'} = $enabled;
>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>> +
>>>>>>>> + return 1;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Remove zonefile
>>>>>>>> +sub _action_zf_remove {
>>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>>> +
>>>>>>>> + # Remove from rpz-config if currently active
>>>>>>>> + if($zonefiles{$action_key}{'enabled'} eq 'on') {
>>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>>> +
>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Remove from data hash and save changes
>>>>>>>> + delete $zonefiles{$action_key};
>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>> +
>>>>>>>> + # Clear action_key, as the entry is now removed entirely
>>>>>>>> + $action_key = "";
>>>>>>>> +
>>>>>>>> + return 1;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Create or update zonefile entry
>>>>>>>> +# Returns undef if gui needs to stay in editor mode
>>>>>>>> +sub _action_zf_save {
>>>>>>>> + my $result = 0;
>>>>>>>> +
>>>>>>>> + my $name = $cgiparams{'ZF_NAME'} // "";
>>>>>>>> + my $url = $cgiparams{'ZF_URL'} // "";
>>>>>>>> + my $remark = $cgiparams{'ZF_REMARK'} // "";
>>>>>>>> + my $enabled = 'on'; # Enable new entries by default
>>>>>>>> +
>>>>>>>> + # Note on variables:
>>>>>>>> + # name = unique key, will be used to address the entry
>>>>>>>> + # action_key = name of the entry being edited, empty for new entry
>>>>>>>> +
>>>>>>>> + # Only check for unique name if it changed
>>>>>>>> + # (this also checks new entries because the action_key is empty in this case)
>>>>>>>> + $result = &_rpz_validate_zonefile($name, $url, $remark, (lc($name) ne lc($action_key)));
>>>>>>>> + return unless &_rpz_check_result($result, 0);
>>>>>>>> +
>>>>>>>> + # Edit existing entry: Determine what was changed
>>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>>> + # Name und URL remain unchanged, only save remark and finish
>>>>>>>> + if(($name eq $action_key) && ($url eq $zonefiles{$action_key}{'url'})) {
>>>>>>>> + $zonefiles{$action_key}{'remark'} = $remark;
>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>> +
>>>>>>>> + return 1;
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Entry was changed and needs to be recreated, preserve status
>>>>>>>> + $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>>> +
>>>>>>>> + # Remove from rpz-config
>>>>>>>> + return unless &_action_zf_remove();
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Add new entry to rpz-config
>>>>>>>> + if($enabled eq 'on') {
>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $name, $url, '--no-reload');
>>>>>>>> +
>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Add to global data hash and save changes
>>>>>>>> + my %entry = ('enabled' => $enabled,
>>>>>>>> + 'url' => $url,
>>>>>>>> + 'remark' => $remark);
>>>>>>>> +
>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>> +
>>>>>>>> + return 1;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Save custom lists
>>>>>>>> +sub _action_cl_save {
>>>>>>>> + return unless((defined $cgiparams{'ALLOW_LIST'}) && (defined $cgiparams{'BLOCK_LIST'}));
>>>>>>>> +
>>>>>>>> + my $result = 0;
>>>>>>>> +
>>>>>>>> + my @allowlist = split(/\R/, $cgiparams{'ALLOW_LIST'});
>>>>>>>> + my @blocklist = split(/\R/, $cgiparams{'BLOCK_LIST'});
>>>>>>>> +
>>>>>>>> + # Validate lists
>>>>>>>> + $result = &_rpz_validate_customlist(\@allowlist);
>>>>>>>> + if($result != 0) {
>>>>>>>> + $errormessage = &_rpz_error_tr(202, $result);
>>>>>>>> + return;
>>>>>>>> + }
>>>>>>>> + $result = &_rpz_validate_customlist(\@blocklist);
>>>>>>>> + if($result != 0) {
>>>>>>>> + $errormessage = &_rpz_error_tr(203, $result);
>>>>>>>> + return;
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Add to global data hash and save changes
>>>>>>>> + $customlists{'allow'}{'list'} = \@allowlist;
>>>>>>>> + $customlists{'block'}{'list'} = \@blocklist;
>>>>>>>> + $customlists{'allow'}{'enabled'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? 'on' : 'off';
>>>>>>>> + $customlists{'block'}{'enabled'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? 'on' : 'off';
>>>>>>>> +
>>>>>>>> + &_customlists_save_conf();
>>>>>>>> + &_customlist_export('allow', $RPZ_ALLOWLIST);
>>>>>>>> + &_customlist_export('block', $RPZ_BLOCKLIST);
>>>>>>>> +
>>>>>>>> + # Make new lists, request service reload on success
>>>>>>>> + $result = &General::system('/usr/sbin/rpz-make', 'allowblock', '--no-reload');
>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>> +
>>>>>>>> + return 1;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Trigger rpz-config reload
>>>>>>>> +sub _action_rpz_reload {
>>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>>> +
>>>>>>>> + # Immediately clear flag to prevent multiple reloads
>>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Perform reload, recreate reload flag on error to enable retry
>>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'reload');
>>>>>>>> + if(not &_rpz_check_result($result, 0)) {
>>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>>> + return;
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + return 1;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Trigger unbound restart
>>>>>>>> +sub _action_unb_restart {
>>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>>> +
>>>>>>>> + # Immediately clear flag to prevent multiple restarts
>>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Perform restart, unboundctrl always exits zero
>>>>>>>> + &General::system('/usr/local/bin/unboundctrl', 'restart');
>>>>>>>> +
>>>>>>>> + return 1;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +
>>>>>>>> +###--- Internal rpz-config functions ---###
>>>>>>>> +
>>>>>>>> +# Translate rpz-config exitcodes and messages
>>>>>>>> +# 100-199: rpz-config, 200-299: webgui
>>>>>>>> +sub _rpz_error_tr {
>>>>>>>> + my ($error, $append) = @_;
>>>>>>>> + $append //= '';
>>>>>>>> +
>>>>>>>> + # Translate numeric exit codes
>>>>>>>> + if(looks_like_number($error)) {
>>>>>>>> + if(defined $Lang::tr{"rpz exitcode $error"}) {
>>>>>>>> + $error = $Lang::tr{"rpz exitcode $error"};
>>>>>>>> + }
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + return "RPZ $Lang::tr{'error'}: $error" . &Header::escape($append);
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Check result of rpz-config system call, request reload on success
>>>>>>>> +sub _rpz_check_result {
>>>>>>>> + my ($result, $request_reload) = @_;
>>>>>>>> + $request_reload //= 0;
>>>>>>>> +
>>>>>>>> + # exitcode 0 = success
>>>>>>>> + if($result != 0) {
>>>>>>>> + $errormessage = &_rpz_error_tr($result);
>>>>>>>> + return;
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Set reload flag
>>>>>>>> + if($request_reload) {
>>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + return 1;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Test whether reload flag is set
>>>>>>>> +sub _rpz_needs_reload {
>>>>>>>> + return (-f $RPZ_RELOAD_FLAG);
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Validate a zonefile entry, returns rpz-config exitcode on failure. Use _rpz_check_result to verify.
>>>>>>>> +# unique = check for unique name
>>>>>>>> +sub _rpz_validate_zonefile {
>>>>>>>> + my ($name, $url, $remark, $unique) = @_;
>>>>>>>> + $unique //= 1;
>>>>>>>> +
>>>>>>>> + unless($name =~ /^[a-zA-Z0-9_]{1,32}$/) {
>>>>>>>> + return 101;
>>>>>>>> + }
>>>>>>>> + unless($url =~ /^[\w+\.:;\/\\&@#%?=\-~|!]{1,128}$/) {
>>>>>>>> + return 105;
>>>>>>>> + }
>>>>>>>> + unless($remark =~ /^[\w \-()\.:;*\/\\?!&=]{0,32}$/) {
>>>>>>>> + return 201;
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Check against already existing names
>>>>>>>> + if($unique) {
>>>>>>>> + foreach my $existing (keys %zonefiles) {
>>>>>>>> + if(lc($name) eq lc($existing)) {
>>>>>>>> + return 104;
>>>>>>>> + }
>>>>>>>> + }
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + return 0;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Validate a custom list, returns number of rejected line on failure. Check for non-zero results.
>>>>>>>> +# listref = array reference, cleanup = remove invalid entries instead of returning an error
>>>>>>>> +sub _rpz_validate_customlist {
>>>>>>>> + my ($listref, $cleanup) = @_;
>>>>>>>> + $cleanup //= 0;
>>>>>>>> +
>>>>>>>> + foreach my $index (reverse 0..$#{$listref}) {
>>>>>>>> + my $row = @$listref[$index];
>>>>>>>> + next unless($row); # Skip/allow empty lines
>>>>>>>> +
>>>>>>>> + # Reject/remove everything besides wildcard domains and remarks
>>>>>>>> + if((not &General::validwildcarddomainname($row)) && (not $row =~ /^;[\w \-()\.:;*\/\\?!&=]*$/)) {
>>>>>>>> + unless($cleanup) {
>>>>>>>> + # +1 for user friendly line number and to ensure non-zero exitcode
>>>>>>>> + return $index + 1;
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Remove current row
>>>>>>>> + splice(@$listref, $index, 1);
>>>>>>>> + }
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + return 0;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +
>>>>>>>> +###--- Internal misc functions ---###
>>>>>>>> +
>>>>>>>> +# Send HTTP 303 redirect headers for post/request/get pattern
>>>>>>>> +# (Must be sent before calling &Header::showhttpheaders())
>>>>>>>> +sub _http_prg_redirect {
>>>>>>>> + my $location = "https://$ENV{'SERVER_NAME'}:$ENV{'SERVER_PORT'}$ENV{'SCRIPT_NAME'}";
>>>>>>>> + print "Status: 303 See Other\n";
>>>>>>>> + print "Location: $location\n";
>>>>>>>> +}
>>>>>>>> diff --git a/lfs/rpz b/lfs/rpz
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..7ddbc38e5
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/lfs/rpz
>>>>>>>> @@ -0,0 +1,96 @@
>>>>>>>> +###############################################################################
>>>>>>>> +# #
>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>>>>> +# #
>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>> +# #
>>>>>>>> +###############################################################################
>>>>>>>> +
>>>>>>>> +###############################################################################
>>>>>>>> +# Definitions
>>>>>>>> +###############################################################################
>>>>>>>> +
>>>>>>>> +include Config
>>>>>>>> +
>>>>>>>> +SUMMARY = response policy zone - RPZ reputation system for unbound DNS
>>>>>>>> +
>>>>>>>> +VER = 1.0.0
>>>>>>>> +
>>>>>>>> +THISAPP = rpz-$(VER)
>>>>>>>> +DIR_APP = $(DIR_SRC)/$(THISAPP)
>>>>>>>> +TARGET = $(DIR_INFO)/$(THISAPP)
>>>>>>>> +
>>>>>>>> +PROG = rpz
>>>>>>>> +PAK_VER = 18
>>>>>>>> +
>>>>>>>> +DEPS =
>>>>>>>> +
>>>>>>>> +SERVICES =
>>>>>>>> +
>>>>>>>> +###############################################################################
>>>>>>>> +# Top-level Rules
>>>>>>>> +###############################################################################
>>>>>>>> +
>>>>>>>> +install : $(TARGET)
>>>>>>>> +
>>>>>>>> +check :
>>>>>>>> +
>>>>>>>> +download :
>>>>>>>> +
>>>>>>>> +b2 :
>>>>>>>> +
>>>>>>>> +dist:
>>>>>>>> + @$(PAK)
>>>>>>>> +
>>>>>>>> +###############################################################################
>>>>>>>> +# Installation Details
>>>>>>>> +###############################################################################
>>>>>>>> +
>>>>>>>> +$(TARGET) :
>>>>>>>> + @$(PREBUILD)
>>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>>> +
>>>>>>>> + # RPZ scripts
>>>>>>>> + install --verbose --mode=755 \
>>>>>>>> + $(DIR_CONF)/rpz/{rpz-config,rpz-metrics,rpz-sleep,rpz-make,rpz-functions} \
>>>>>>>> + --target-directory=/usr/sbin
>>>>>>>> +
>>>>>>>> + # RPZ config files
>>>>>>>> + mkdir -pv /etc/unbound/local.d
>>>>>>>> + install --verbose --mode=644 --owner=nobody --group=nobody \
>>>>>>>> + $(DIR_CONF)/rpz/00-rpz.conf \
>>>>>>>> + --target-directory=/etc/unbound/local.d
>>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/local.d
>>>>>>>> +
>>>>>>>> + # RPZ custom list files for allow and block
>>>>>>>> + mkdir -pv /var/ipfire/dns/rpz
>>>>>>>> + touch /var/ipfire/dns/rpz/{allowlist,blocklist}
>>>>>>>> + chown --verbose --recursive nobody:nobody /var/ipfire/dns/rpz
>>>>>>>> +
>>>>>>>> + # RPZ zone files
>>>>>>>> + # create empty RPZ config file to avoid a unbound config error
>>>>>>>> + mkdir -pv /etc/unbound/zonefiles
>>>>>>>> + touch /etc/unbound/zonefiles/allow.rpz
>>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/zonefiles
>>>>>>>> +
>>>>>>>> + # Install addon-specific language-files
>>>>>>>> + install --verbose --mode=004 $(DIR_CONF)/rpz/rpz.*.pl \
>>>>>>>> + --target-directory=/var/ipfire/addon-lang
>>>>>>>> +
>>>>>>>> + # Install backup definition
>>>>>>>> + cp -vf $(DIR_CONF)/backup/includes/rpz /var/ipfire/backup/addons/includes/rpz
>>>>>>>> +
>>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>>> + @$(POSTBUILD)
>>>>>>>> diff --git a/make.sh b/make.sh
>>>>>>>> index 827ea9e77..a77535b13 100755
>>>>>>>> --- a/make.sh
>>>>>>>> +++ b/make.sh
>>>>>>>> @@ -390,7 +390,7 @@ prepareenv() {
>>>>>>>> if [ "${free_space}" -lt "${required_space}" ]; then
>>>>>>>> # Add any consumed space
>>>>>>>> while read -r consumed_space path; do
>>>>>>>> - (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>>> + (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>>> done <<< "$(du --summarize --bytes "${BUILD_DIR}" "${IMAGES_DIR}" "${LOG_DIR}" 2>/dev/null)"
>>>>>>>> fi
>>>>>>>>
>>>>>>>> @@ -2087,6 +2087,7 @@ build_system() {
>>>>>>>> lfsmake2 btrfs-progs
>>>>>>>> lfsmake2 inotify-tools
>>>>>>>> lfsmake2 grub-btrfs
>>>>>>>> + lfsmake2 rpz
>>>>>>>>
>>>>>>>> lfsmake2 linux
>>>>>>>> lfsmake2 rtl8812au
>>>>>>>> diff --git a/src/paks/rpz/install.sh b/src/paks/rpz/install.sh
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..ef99bf742
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/src/paks/rpz/install.sh
>>>>>>>> @@ -0,0 +1,36 @@
>>>>>>>> +#!/bin/bash
>>>>>>>> +###############################################################################
>>>>>>>> +# #
>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>>>>> +# #
>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>> +# #
>>>>>>>> +###############################################################################
>>>>>>>> +#
>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>> +extract_files
>>>>>>>> +restore_backup ${NAME}
>>>>>>>> +
>>>>>>>> +# fix user created files
>>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>>> + /etc/unbound/zonefiles \
>>>>>>>> + /etc/unbound/local.d
>>>>>>>> +
>>>>>>>> +# Update Language cache
>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>> +
>>>>>>>> +# restart unbound to load config file
>>>>>>>> +/etc/init.d/unbound restart
>>>>>>>> diff --git a/src/paks/rpz/uninstall.sh b/src/paks/rpz/uninstall.sh
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..e11427df3
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/src/paks/rpz/uninstall.sh
>>>>>>>> @@ -0,0 +1,38 @@
>>>>>>>> +#!/bin/bash
>>>>>>>> +###############################################################################
>>>>>>>> +# #
>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>>>>> +# #
>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>> +# #
>>>>>>>> +###############################################################################
>>>>>>>> +#
>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>> +
>>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>>> +/etc/init.d/unbound stop
>>>>>>>> +
>>>>>>>> +make_backup ${NAME}
>>>>>>>> +remove_files
>>>>>>>> +
>>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>>> +# "[1723428668] unbound-control[17117:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>>> +
>>>>>>>> +# Update Language cache
>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>> +
>>>>>>>> +# start unbound to load unbound config file
>>>>>>>> +/etc/init.d/unbound start
>>>>>>>> diff --git a/src/paks/rpz/update.sh b/src/paks/rpz/update.sh
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..9bc340bc6
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/src/paks/rpz/update.sh
>>>>>>>> @@ -0,0 +1,52 @@
>>>>>>>> +#!/bin/bash
>>>>>>>> +###############################################################################
>>>>>>>> +# #
>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>> +# Copyright (C) 2024 IPFire Team <info(a)ipfire.org> #
>>>>>>>> +# #
>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>> +# #
>>>>>>>> +###############################################################################
>>>>>>>> +#
>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>> +
>>>>>>>> +# from update.sh
>>>>>>>> +extract_backup_includes
>>>>>>>> +
>>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>>> +/etc/init.d/unbound stop
>>>>>>>> +
>>>>>>>> +# from uninstall.sh
>>>>>>>> +make_backup ${NAME}
>>>>>>>> +remove_files
>>>>>>>> +
>>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>>> +# "unbound-control[nn:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>>> +
>>>>>>>> +# from install.sh
>>>>>>>> +extract_files
>>>>>>>> +restore_backup ${NAME}
>>>>>>>> +
>>>>>>>> +# fix user created files
>>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>>> + /etc/unbound/zonefiles \
>>>>>>>> + /etc/unbound/local.d
>>>>>>>> +
>>>>>>>> +# Update Language cache
>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>> +
>>>>>>>> +# restart unbound to load config files
>>>>>>>> +/etc/init.d/unbound start
>>>>>>>> --
>>>>>>>> 2.39.5
>>>>>>>>
>>>>>> Jon
>>>>>>
>>>>>>
>>>>>> --
>>>>>> Jon Murphy
>>>>>> jon.murphy(a)ipfire.org
>>>>
>>>> Jon
>>>>
>>>>
>>>> --
>>>> Jon Murphy
>>>> jon.murphy(a)ipfire.org
>>>>
>>>>
>>>>
>>>>
>>
>> --
>> Sent from my laptop
>>
>
> Jon
>
>
> --
> Jon Murphy
> jon.murphy(a)ipfire.org
>
>
>
>
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
2025-03-02 10:51 ` Adolf Belka
@ 2025-03-10 17:47 ` jon
0 siblings, 0 replies; 30+ messages in thread
From: jon @ 2025-03-10 17:47 UTC (permalink / raw)
To: Adolf Belka; +Cc: IPFire: Development-List
[-- Attachment #1: Type: text/plain, Size: 134380 bytes --]
Draft version:
https://www.ipfire.org/docs/roadmap/rpz
> On Mar 2, 2025, at 4:51 AM, Adolf Belka <adolf.belka@ipfire.org> wrote:
>
> Hi Jon,
>
> On 01/03/2025 23:22, jon wrote:
>> No thank you. I pass.
>
> Okay.
>
> You should definitely look at putting together an entry for the Roadmap as outlined by Michael. There is a template page you can use to put together an initial entry which can then be used for further discussions in the dev mailing list.
>
> Best regards,
>
> Adolf.
>
>> Jon
>>> On Mar 1, 2025, at 4:18 AM, Adolf Belka <adolf.belka@ipfire.org> wrote:
>>>
>>> Hi Jon,
>>>
>>> Would you like to have this topic added to the agenda for the next Dev Conf Call scheduled for 10th March?
>>>
>>> The time of the conf call is 8:00 to 10:00pm Central European Time.
>>>
>>> Best regards,
>>>
>>> Adolf.
>>>
>>>
>>> On 14/02/2025 13:07, Michael Tremer wrote:
>>>> Hello Jon,
>>>>
>>>> It very much depends on the kind of contribution. A one-line patch obviously has fewer strings attached to it than a larger patch set like this.
>>>>
>>>> However, we have outlined the process in the wiki already, starting from here:
>>>>
>>>> https://www.ipfire.org/docs/devel/submit-patches
>>>>
>>>> This contains some useful pointers about the how (how do I actually make my changes happen, how do I build IPFire, etc), and at the bottom it contains a lot of information about the format the changes should be submitted; split into smaller chunks that are ideally as independent from each other so that they can be individually reviewed and merged. Usually a development process takes long time and we have already shipped parts of code that we will need for certain features that are not ready yet. This is a good practice to let code mature, especially when it is touching rather critical bits like the firewall and networking stacks.
>>>>
>>>> There are also some guidelines on how to write a good commit message and how to use Git tags:
>>>>
>>>> https://www.ipfire.org/docs/devel/git/commit-messages
>>>> https://www.ipfire.org/docs/devel/git/tags
>>>>
>>>> Then there is something about how to get in touch with the right person and legal stuff:
>>>>
>>>> https://www.ipfire.org/docs/devel/contact
>>>> https://www.ipfire.org/docs/legal/ipca
>>>>
>>>> Finally, we have a bit of an underused roadmap section on the wiki. It would be nice if we could use that a little bit more because then it would be easier for everyone to keep track of progress on certain features; people could see what is being worked on and see if they can help development and testing and so on:
>>>>
>>>> https://www.ipfire.org/docs/roadmap
>>>>
>>>> There is a template on how to create new pages:
>>>>
>>>> https://www.ipfire.org/docs/roadmap/template
>>>>
>>>> And this is a good example of what this could look like:
>>>>
>>>> https://www.ipfire.org/docs/roadmap/openvpn-26
>>>>
>>>> All of these steps are coming *after* there has been some initial discussion about what actually has a chance to become part of the distribution. For that, we do not have any specific guidelines because it is not very trivial to write these things. There are just too many possibilities. In the past, there has also been very little need for this, but that does not mean that there have not been problems before.
>>>>
>>>> The reason why I am raising the bar this high here is simply that we have made mistakes in the past that we don’t want to repeat. We have learned a couple of lessons in a not very pleasant way and I under no circumstances would want to do this again. The objective is that we want to provide an excellent distribution. Although IPFire of course has its shortcomings here and there, it is a very stable distribution and we have a very good track record that I want to keep. This is what our users deserve.
>>>>
>>>> In the past, people have “dropped” their patches on this list (or sometimes elsewhere) and we were left with dealing with the entire integration only to find out later what problems there were hidden in the code. The original author(s) had no interest in fixing any of that because it worked just fine for them, and so why spend any time on the problems of somebody else? Usually I am the fallback for this and I simply don’t want to be that. I have lots of my own projects inside IPFire that are moving at snail speed because fixing existing code usually takes priority over writing new code.
>>>>
>>>> Therefore we need a commitment to sort out these problems in the first place. It has to be proven that people actually *care* about the patches that they post here. I am not sure this needs writing down as this should be the same policy with almost any open source project. If you contribute a line, there is probably less maintenance required in the future, but if you contribute a large code base, then you will need to look after it for the foreseeable future. It is your feature and not mine after all.
>>>>
>>>> Then, what actually has a chance to make it into the distribution? Probably not a lot. IPFire has a very clear use case. There will not be any space for a desktop environment and running Chrome on it, we also don’t it to make coffee and cook me a dinner. We would currently only accept things that were actually maintainable by the current team in case a contributor moves on (see above), because we simply only have so much man power. We already have a large zoo of features that are very abandoned and we are potentially looking at getting rid of more things simply because we cannot support them properly. Time just doesn’t permit. Adding something large is therefore very difficult at the moment.
>>>>
>>>> I understand that in this specific case you have been trying to not involve the development team and I understand your motivation. But you cannot forget about how much time and effort a review process can take. Therefore we want to plan things well; we want to even split it; and we want to have a conversation in advance so that the roadmap is clear and the actual code review ideally only becomes a formality.
>>>>
>>>> All of this above has been for a general case. Please read through this and feel free to ask any questions if something isn’t clear.
>>>>
>>>> To move forward with this feature, we should start by planning a roadmap. We need to discuss what this project should cover and what it should not cover. I believe we don’t need to talk much about implementation details because you have figured out a lot of them; we need to find what feature we want to provide to our users. Are you up for that?
>>>>
>>>> Best,
>>>> -Michael
>>>>
>>>>> On 13 Feb 2025, at 21:34, jon <jon.murphy@ipfire.org> wrote:
>>>>>
>>>>> Michael,
>>>>>
>>>>> I’ve read through your comments a few times and I ended up with many more questions.
>>>>>
>>>>>
>>>>>> What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>>>
>>>>> To me the efforts to get new code accepted seem to have changed and it seemed easier in the past. In the past I made the Core Team aware via the Dev Mailing List and wrote a simple two or three paragraphs of "What is it? / What is the value? / Here is the code"
>>>>>
>>>>>
>>>>> So in an effort to move forward: How exactly is something presented to the Core Team?
>>>>>
>>>>> Is there an example of a recent effort that was presented that I can see as a sample? (This type of info can also be added to the Wiki)
>>>>>
>>>>> I understand you want it this way, but I don’t know what exactly is needed. Please be specific.
>>>>>
>>>>>
>>>>> Jon
>>>>>
>>>>> PS - I am not ignoring your other comments, I am just trying to move forward and keep things simple.
>>>>>
>>>>>
>>>>>
>>>>>> On Feb 8, 2025, at 1:27 PM, Michael Tremer <michael.tremer@ipfire.org> wrote:
>>>>>>
>>>>>> Hello Jon,
>>>>>>
>>>>>> Thanks for your reply. And good that you are copying everyone into this conversation.
>>>>>>
>>>>>>> On 8 Feb 2025, at 18:41, jon <jon.murphy@ipfire.org> wrote:
>>>>>>>
>>>>>>> Michael,
>>>>>>>
>>>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort
>>>>>>> Yes, this has been a separate effort (a very public separate effort). Yes, as you pointed this out early on with the "proof-of-concept" and then my request for people to help test RPZ. Nothing was hidden.
>>>>>>>
>>>>>>> This was done because you (and maybe others) did not have the time and I wanted to help and because I needed assistance with RPZ. I tried my best to do this without bothering you.
>>>>>> I don’t that it is accurate that nobody wanted to help on this. The list was always open - although not every email has been replied to swiftly it is also your responsibility to raise a question again if it was missed. People here have open ears.
>>>>>>
>>>>>> It was also stated on this very list on in our documentation that working on something without involving the core team is a risky undertaking. Of course IPFire is free software and so everyone is free to fork if they wish to do so.
>>>>>>
>>>>>>>> and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls.
>>>>>>> You were aware many steps along the way. See your email on July 28, 2024, August 15, 2024, September 30, 2024, December 23, 2024, and January 16. My attempts to get the team involved were met with "things are busy" and sometimes silence. (Yes, I get it, people are busy.)
>>>>>>>
>>>>>>> You and Adolf, Leo, Erik and Bernhard have been aware since the beginning. You mention you were aware of the "proof-of-concept". If you include those beginning posts, since Sep 2023.
>>>>>> Yes, I am aware of a proof-of-concept that I have been running myself for a long time. I am also aware of the efforts that you have been taking.
>>>>>>
>>>>>> Yet I don’t think there has ever been any joint effort, or am I seeing that wrong?
>>>>>>
>>>>>>>> This has not been discussed . . . on our calls.
>>>>>>> On the July 28th you stated:
>>>>>>> "We have talked about RPZ many times on the monthly call since the URL filter feature is falling more and more out of fashion. I think there is also many posts about this on the forum."
>>>>>>>
>>>>>>> Please don’t insult me again by stating "you know what I mean".
>>>>>>>
>>>>>>> And it has been discussed but not documented in the Monthly Meeting notes.
>>>>>> I am not at all insulting you. I don’t want to take this down to a personal level at all. This is a public mailing list and people who read this don’t need to listen to an argument we are having. They are here for the tech inside IPFire.
>>>>>>
>>>>>> When I wrote that it has not been discussed that does not mean that we have not been touching on the topic. We have been talking about lots of things on the calls, the weather, politics, how our pets are. None of that makes it to the logs. What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>>>>
>>>>>>>> Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation.
>>>>>>> Regular conversation on the Dev Mailing list is many times met with silence. I get it, people are busy.
>>>>>>>
>>>>>>> And regular two-way conversation doesn’t happen on the list. At least not with me. I’d be happy to point out the posts that were met with silence.
>>>>>>> Again, I get it, people are busy.
>>>>>> And you think my emails are not being met with silence? This has nothing to do with this specific topic. This has something to do with how occupied people are and how engaged they are on certain topics. Not everyone is involved in all the things and simply will ignore emails simply based on their subject line.
>>>>>>
>>>>>>> But the "dip here to the list" were my attempts to get a conversation started. As I said, many time met with silence.
>>>>>>>
>>>>>>> The only place I was not met with silence was on the Community. You have a great group of people in the Community. It is a shame you don’t want to have others help. It would reduce your workload.
>>>>>> You should stop making statements that are not true. Who doesn’t want anyone to help?
>>>>>>
>>>>>> Not having this conversation on a Saturday evening would reduce my workload. At least it would free up time for something else. Helping with the things that are already on the go would reduce the workload of the entire team. Starting one thing at a time and finishing it is a lot better to manage than starting a hundred things and not even finish one. I can tell you that I already have a hundred things on the go.
>>>>>>
>>>>>>>> Therefore, what am I supposed to do with this email?
>>>>>>> To me it is beyond obvious…
>>>>>>>
>>>>>>> If it isn’t what you want, then guide me with how to do this the correct way. And be specific. I am trying to help. I am trying to make things better. I am trying to do things the right way.
>>>>>> To me it isn’t. This is yet another project that has been dumped to the list like so many before and later on everyone has left to have the team deal with the rest.
>>>>>>
>>>>>> It is a huge patch set. You explained what the vision is, but that is about it. There is no chance this will continue if this disagreement isn’t solved first. I didn’t even look at the code.
>>>>>>
>>>>>>>> I don’t want to merge code that I don’t agree with.
>>>>>>> I asked multiple times if you "agreed with the concept" and again, met with silence. Yes I get it, people are busy.
>>>>>> Having support for RPZ? Yes, it was definitely on the roadmap. That I agree with.
>>>>>>
>>>>>>>> So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>>> You mentioned this a in the past, but for some reason you do not disclose what I dismissed. Why do you continue to make this harder, wouldn’t it not be easier to tell me what I have dismissed?
>>>>>>>
>>>>>>> I have sent multiple emails trying to answer your concerns and comments. On July 28, Aug 14, Aug 22, Aug 23, Sep 30, etc.
>>>>>>>
>>>>>>> I’ve gone through all of the questions you asked and I cannot find a "dismissed" item.
>>>>>> Maybe I need to be *more clear*. I feel humoured by this.
>>>>>>
>>>>>> It is late on a Saturday and I want my dinner soon, but certainly I have stated that this should never be an add-on considering it is supposed to replace URL Filter. We should never allow people to add their own sources. I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>>>>>
>>>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>>> The maintainers of Unbound and/or RPZ?
>>>>>>>
>>>>>>> The maintainers of Hagezi list, the threatfox list, the urlhaus list, etc.?
>>>>>>>
>>>>>>> What else? The maintainers or the RPZ scripts? That is me. Let’s talk!
>>>>>> You. I don’t care much about the providers of the lists.
>>>>>>
>>>>>>> See, this is where it gets confusing. There are hundreds of open source packages as part of IPFire. Pick the last five years of items added to the IPFire build. You're telling me you have "constructive conversation with the maintainers" of all of the added packages?
>>>>>> They publish their software and they don’t care whether I am pulling it or not. They publish it with the commitment to maintain it - sometimes for better and sometimes for worse.
>>>>>>
>>>>>> You care about me pulling your code and I don’t know whether you would commit to maintain this.
>>>>>>
>>>>>> These two are very different cases.
>>>>>>
>>>>>>> Pick the IP Blocklists list (i.e., 3CORESEC, ABUSECH, DSHIELD, SPAMHAUS, etc.) or the Suricata lists (i.e., Emergingthreats.net, Abuse.ch, etc.). So you’ve have "constructive conversation with the maintainers"?
>>>>>> Yes, occasionally I have phone calls with a few of these providers.
>>>>>>
>>>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>>> Ha! Yes a surprise. In the beginning you seemed interested as IPFire needed a replacement for URL Filter. You asked good questions about the lists picked, asked for the value to the users, etc. And I answered the best I could.
>>>>>>>
>>>>>>> You even asked: “Why is this realised as an add-on and not part of the core system?” from your Jul 28, 2024 email.
>>>>>> Ah, so, why is the patch creating an add-on? Not that I am saying that what I say is law, but it has not been challenged either. If my input is being ignored, why should I put this to the top of my list of priorities? I am not disappointed about this, just trying to be very good with my time.
>>>>>>
>>>>>>> And on January 16, 2025 I wrote a message looking for help. And you were kind to respond quickly. So in three weeks time, since the kind response, something has changed. You went from supportive to "this".
>>>>>>>
>>>>>>> So yes, I am surprised.
>>>>>> Well, maybe I should not have replied to that email. It was clear that you were on some path that was not right, but you were not interested before in finding the right path from the beginning.
>>>>>>
>>>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>>> Be more specific, what has to change? What exactly did I dismiss?
>>>>>> Dismissal is just my assumption. I don’t know what you actually did with my feedback. I can only see the end product that does not seem contain much of it. Repeatedly I have been pointing out that we should think before we build. I am sure a lot of hours have now gone into some code that simply does not satisfy me. And I am not not talking about the code itself, what it does is what I don’t think is right for us.
>>>>>>
>>>>>> The process is very clear for me that we should first of all think whether we want a certain feature now. Then there should be a clear roadmap for everyone to follow; tasks can be split-up as we go and hopefully then have something that is maintainable, interesting for our users and even would do us proud. This is how this should work.
>>>>>>
>>>>>> So, what has to change? I don’t think with shouting at each other, throwing patches around and making me generally unhappy is a good start.
>>>>>>
>>>>>> -Michael
>>>>>>
>>>>>>> Jon
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>>> On Feb 6, 2025, at 2:13 PM, Michael Tremer <michael.tremer@ipfire.org> wrote:
>>>>>>>>
>>>>>>>> Hello Jon,
>>>>>>>>
>>>>>>>> Well, here we are again with another patch regarding this feature.
>>>>>>>>
>>>>>>>> I cannot quite see from your email what the question is, but if this is a request to have this merged into IPFire, I am once again sorry to disappoint you.
>>>>>>>>
>>>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls. Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation. Therefore, what am I supposed to do with this email?
>>>>>>>>
>>>>>>>> I don’t want to merge code that I don’t agree with. So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>>>>
>>>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>>>>
>>>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>>>>
>>>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>>>>
>>>>>>>> All the best,
>>>>>>>> -Michael
>>>>>>>>
>>>>>>>>> On 6 Feb 2025, at 16:35, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>>>
>>>>>>>>> What is it?
>>>>>>>>> Response Policy Zone (RPZ) is a mechanism to define local policies in a
>>>>>>>>> standardized way and load those policies from external sources.
>>>>>>>>> Bottom line: RPZ allows admins to easily block access to websites via DNS lookup.
>>>>>>>>>
>>>>>>>>> RPZ can block websites via categories. Examples include: fake websites, annoying
>>>>>>>>> pop-up ads, newly registered domains, DoH bypass sites, bad "host" services,
>>>>>>>>> maliscious top level domains (e.g., *.zip, *.mov), piracy, gambling, pornography,
>>>>>>>>> and more. RPZ lists come from various RPZ providers and their available
>>>>>>>>> catagories.
>>>>>>>>>
>>>>>>>>> This RPZ add-on enables the RPZ functionality by adding a couple lines in a
>>>>>>>>> configuration file. This add-on simply adds configuration files and adds
>>>>>>>>> scripts (config, metrics and sleep) to make RPZ easier for the admin to use.
>>>>>>>>>
>>>>>>>>> The RPZ scripts include additional languages: German, Spanish, French, Turkish,
>>>>>>>>> and Italian.
>>>>>>>>>
>>>>>>>>> RPZ itself was release in 2010 and has been part of the IPFire build since ~2015.
>>>>>>>>>
>>>>>>>>> Why is it needed? What is its value?
>>>>>>>>>
>>>>>>>>> - The RPZ concept places this filtering into IPFire, our internet access
>>>>>>>>> gateway, which is (should be) solely used as DNS source of the internal network.
>>>>>>>>>
>>>>>>>>> - As most sites use HTTPS it makes it difficult to filter traffic with URL
>>>>>>>>> Filter without also properly configuring conventional (non-transparent)
>>>>>>>>> mode on the proxy. RPZ is a nice replacement for the URL Filter.
>>>>>>>>>
>>>>>>>>> - No need to install and maintain an additional device like PiHole or AdBlock
>>>>>>>>> browser extensions on multiple user devices.
>>>>>>>>>
>>>>>>>>> - This is an additional layer of protection for users. Less worry someone will
>>>>>>>>> click on something that gets them into trouble. And, saying this with emphasis,
>>>>>>>>> the ability to do it in one place!
>>>>>>>>>
>>>>>>>>> - Blocked sites save on unneeded traffic and can lessen the threat of malware
>>>>>>>>> in advertisements
>>>>>>>>>
>>>>>>>>> - Logging allows the admin to see the site blocked and take actions
>>>>>>>>>
>>>>>>>>> - RPZ will be used at the home, home-office (work from home), schools,
>>>>>>>>> ministerial, and at the office. Device counts are small (2-6) to medium (~80)
>>>>>>>>> to mediam-large (200+).
>>>>>>>>>
>>>>>>>>> - RPZ can block ads, popups, phishing, scammers, spyware, malware, annoying
>>>>>>>>> popups, NSFW links, DOH servers, and the usual internet trash.
>>>>>>>>>
>>>>>>>>> ------------------------------
>>>>>>>>>
>>>>>>>>> Change Log for RPZ add-on
>>>>>>>>>
>>>>>>>>> rpz-1.0.0-18 on 2025-02-05
>>>>>>>>> - Build for approval & release as IPFire add-on
>>>>>>>>>
>>>>>>>>> ---
>>>>>>>>>
>>>>>>>>> rpz-beta-0.1.18-18.ipfire on 2025-02-01
>>>>>>>>> rpz.cgi:
>>>>>>>>> - new feature: added a mod key to force a unbound restart
>>>>>>>>>
>>>>>>>>> rpz-config and rpz-make:
>>>>>>>>> - new feature: added action for unbound restart `rpz-config unbound-restart`
>>>>>>>>>
>>>>>>>>> rpz-metrics:
>>>>>>>>> - simple reformatting
>>>>>>>>> - rename far right column from "last update" to "last download"
>>>>>>>>>
>>>>>>>>> ---
>>>>>>>>>
>>>>>>>>> rpz-beta-0.1.17-17.ipfire on 2024-12-09
>>>>>>>>> rpz-make
>>>>>>>>> - bug fix: corrected validation regex for wildcards like: `*.domain.com`
>>>>>>>>>
>>>>>>>>> ---
>>>>>>>>>
>>>>>>>>> rpz-beta-0.1.16-16.ipfire on 2024-11-18
>>>>>>>>> rpz-make
>>>>>>>>> - new feature: updated validation regex
>>>>>>>>> - bug fix: moved validation to beginning of process. Now we validate before
>>>>>>>>> creating config files.
>>>>>>>>>
>>>>>>>>> rpz.cgi:
>>>>>>>>> - new feature: use CSS color variables of the main ipfire theme
>>>>>>>>> - bug fix: empty zonefile remarks were stored as “undef” and caused a warning
>>>>>>>>> - bug fix: HTML textarea removes the first empty line in a custom list
>>>>>>>>> - thank you Leo!
>>>>>>>>>
>>>>>>>>> ---
>>>>>>>>>
>>>>>>>>> rpz-beta-0.1.15-15.ipfire on 2024-11-04
>>>>>>>>> rpz.cgi:
>>>>>>>>> - new feature: added new language file for Turkish (thank you Peppe)
>>>>>>>>>
>>>>>>>>> rpz-make
>>>>>>>>> - bug fix: corrected empty allow/block list issue. An empty allow/block list
>>>>>>>>> will now remove contents of allow/block.rpz files and remove unneeded
>>>>>>>>> allow/block.conf file. (thank you iptom)
>>>>>>>>>
>>>>>>>>> ---
>>>>>>>>>
>>>>>>>>> rpz-beta-0.1.14-14.ipfire on 2024-10-29
>>>>>>>>> rpz-config:
>>>>>>>>> - bug fix: correct missing rpz extension. `rpz-config list` displayed URL
>>>>>>>>> incorrectly (thank you Bernhard)
>>>>>>>>>
>>>>>>>>> rpz.cgi:
>>>>>>>>> - bug fix: remove extra `"` in language files (thank you Bernhard)
>>>>>>>>> - new feature: slightly dim "apply" button when not enabled
>>>>>>>>>
>>>>>>>>> ---
>>>>>>>>>
>>>>>>>>> rpz-beta-0.1.13-13.ipfire on 2024-10-27
>>>>>>>>> - skipped
>>>>>>>>>
>>>>>>>>> ---
>>>>>>>>>
>>>>>>>>> rpz-beta-0.1.12-12.ipfire on 2024-10-21
>>>>>>>>> rpz.cgi:
>>>>>>>>> - new feature: added new language file for French (thank you gw-ipfire)
>>>>>>>>>
>>>>>>>>> ---
>>>>>>>>>
>>>>>>>>> rpz-beta-0.1.11-11.ipfire on 2024-10-18
>>>>>>>>> rpz.cgi:
>>>>>>>>> - new feature: added new language file for Italian (thank you umberto)
>>>>>>>>> - new feature: added new language file for Spanish (thank you Roberto)
>>>>>>>>>
>>>>>>>>> ---
>>>>>>>>>
>>>>>>>>> rpz-beta-0.1.10-10.ipfire on 2024-10-15
>>>>>>>>> rpz-make:
>>>>>>>>> - bug fix: corrected validation error for a custom list entry (thank you siosios)
>>>>>>>>> - e.g., `*.cloudflare-dns.com`
>>>>>>>>>
>>>>>>>>> install.sh:
>>>>>>>>> - bug fix: add chown to correct user created files
>>>>>>>>>
>>>>>>>>> update.sh:
>>>>>>>>> - bug fix: add chown to correct user created files (thank you siosios)
>>>>>>>>>
>>>>>>>>> ---
>>>>>>>>>
>>>>>>>>> rpz-beta-0.1.9-9.ipfire on 2024-10-08
>>>>>>>>> rpz.cgi:
>>>>>>>>> - new feature: added new language file for German (thank you Leo)
>>>>>>>>> - bug fix: add missing "rpz exitcode 110"
>>>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>>>
>>>>>>>>> ---
>>>>>>>>>
>>>>>>>>> rpz-beta-0.1.8-8.ipfire on 2024-10-04
>>>>>>>>> - skipped
>>>>>>>>>
>>>>>>>>> ---
>>>>>>>>>
>>>>>>>>> rpz-beta-0.1.7-7.ipfire on 2024-10-03
>>>>>>>>> All:
>>>>>>>>> - new feature: includes beta version numbers for pakfire package,
>>>>>>>>> instead of only `rpz-1.0.0-1.ipfire`, for each release.
>>>>>>>>>
>>>>>>>>> rpz.cgi:
>>>>>>>>> - new feature: added new WebGUI at `rpz.cgi`
>>>>>>>>> - a BIG thank you to Leo Hofmann for all of his work creating the webgui!!
>>>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>>>
>>>>>>>>> rpz-make:
>>>>>>>>> - new feature: validate entries in allowlist and blocklist
>>>>>>>>> - new feature: add "no-reload" option for WebGUI
>>>>>>>>>
>>>>>>>>> rpz-metrics:
>>>>>>>>> - new feature: info can be sorted by name, by hit count, by line count, by
>>>>>>>>> "enabled" list or all lists
>>>>>>>>>
>>>>>>>>> backups:
>>>>>>>>> - bug fix: include all files in `/var/ipfire/dns/rpz` directory in backup
>>>>>>>>>
>>>>>>>>> update.sh:
>>>>>>>>> - bug fix: corrected ownership for `/var/ipfire/dns/rpz` directory during an
>>>>>>>>> update
>>>>>>>>>
>>>>>>>>> Build:
>>>>>>>>> - bug fix: `block.rpz.conf` and `block.rpz` from build. Files to be created
>>>>>>>>> by `rpz-make`
>>>>>>>>>
>>>>>>>>> WebGUI and German language file
>>>>>>>>> Contribution-by: Leo-Andres Hofmann <hofmann@leo-andres.de>
>>>>>>>>>
>>>>>>>>> Spanish language file
>>>>>>>>> Contribution-by: Roberto Peña
>>>>>>>>>
>>>>>>>>> Italian language file
>>>>>>>>> Contribution-by: Umberto Parma
>>>>>>>>>
>>>>>>>>> French language file
>>>>>>>>> Contribution-by: gw-ipfire
>>>>>>>>>
>>>>>>>>> Turkish language file
>>>>>>>>> Contribution-by: Peppe Tech
>>>>>>>>>
>>>>>>>>> Contribution-by: Bernhard Bitsch <bbitsch@ipfire.org>
>>>>>>>>> Contribution-by: Erik Kapfer <erik.kapfer@ipfire.org>
>>>>>>>>> Signed-off-by: Jon Murphy <jon.murphy@ipfire.org
>>>>>>>>> ---
>>>>>>>>> config/backup/includes/rpz | 4 +
>>>>>>>>> config/cfgroot/manualpages | 1 +
>>>>>>>>> config/menu/EX-rpz.menu | 6 +
>>>>>>>>> config/rootfiles/common/configroot | 1 +
>>>>>>>>> config/rootfiles/common/web-user-interface | 1 +
>>>>>>>>> config/rootfiles/packages/rpz | 20 +
>>>>>>>>> config/rpz/00-rpz.conf | 10 +
>>>>>>>>> config/rpz/rpz-config | 130 +++
>>>>>>>>> config/rpz/rpz-functions | 85 ++
>>>>>>>>> config/rpz/rpz-make | 203 +++++
>>>>>>>>> config/rpz/rpz-metrics | 170 ++++
>>>>>>>>> config/rpz/rpz-sleep | 58 ++
>>>>>>>>> config/rpz/rpz.de.pl | 30 +
>>>>>>>>> config/rpz/rpz.en.pl | 30 +
>>>>>>>>> config/rpz/rpz.es.pl | 30 +
>>>>>>>>> config/rpz/rpz.fr.pl | 30 +
>>>>>>>>> config/rpz/rpz.it.pl | 30 +
>>>>>>>>> config/rpz/rpz.tr.pl | 30 +
>>>>>>>>> html/cgi-bin/rpz.cgi | 923 +++++++++++++++++++++
>>>>>>>>> lfs/rpz | 96 +++
>>>>>>>>> make.sh | 3 +-
>>>>>>>>> src/paks/rpz/install.sh | 36 +
>>>>>>>>> src/paks/rpz/uninstall.sh | 38 +
>>>>>>>>> src/paks/rpz/update.sh | 52 ++
>>>>>>>>> 24 files changed, 2016 insertions(+), 1 deletion(-)
>>>>>>>>> create mode 100644 config/backup/includes/rpz
>>>>>>>>> create mode 100644 config/menu/EX-rpz.menu
>>>>>>>>> create mode 100644 config/rootfiles/packages/rpz
>>>>>>>>> create mode 100644 config/rpz/00-rpz.conf
>>>>>>>>> create mode 100644 config/rpz/rpz-config
>>>>>>>>> create mode 100644 config/rpz/rpz-functions
>>>>>>>>> create mode 100644 config/rpz/rpz-make
>>>>>>>>> create mode 100755 config/rpz/rpz-metrics
>>>>>>>>> create mode 100755 config/rpz/rpz-sleep
>>>>>>>>> create mode 100644 config/rpz/rpz.de.pl
>>>>>>>>> create mode 100644 config/rpz/rpz.en.pl
>>>>>>>>> create mode 100644 config/rpz/rpz.es.pl
>>>>>>>>> create mode 100644 config/rpz/rpz.fr.pl
>>>>>>>>> create mode 100644 config/rpz/rpz.it.pl
>>>>>>>>> create mode 100644 config/rpz/rpz.tr.pl
>>>>>>>>> create mode 100644 html/cgi-bin/rpz.cgi
>>>>>>>>> create mode 100644 lfs/rpz
>>>>>>>>> create mode 100644 src/paks/rpz/install.sh
>>>>>>>>> create mode 100644 src/paks/rpz/uninstall.sh
>>>>>>>>> create mode 100644 src/paks/rpz/update.sh
>>>>>>>>>
>>>>>>>>> diff --git a/config/backup/includes/rpz b/config/backup/includes/rpz
>>>>>>>>> new file mode 100644
>>>>>>>>> index 000000000..36513e494
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/config/backup/includes/rpz
>>>>>>>>> @@ -0,0 +1,4 @@
>>>>>>>>> +/var/ipfire/dns/rpz/*
>>>>>>>>> +/etc/unbound/zonefiles/allow.rpz
>>>>>>>>> +/etc/unbound/zonefiles/block.rpz
>>>>>>>>> +/etc/unbound/local.d/*rpz.conf
>>>>>>>>> diff --git a/config/cfgroot/manualpages b/config/cfgroot/manualpages
>>>>>>>>> index 1f7e01efc..d3a48c633 100644
>>>>>>>>> --- a/config/cfgroot/manualpages
>>>>>>>>> +++ b/config/cfgroot/manualpages
>>>>>>>>> @@ -70,6 +70,7 @@ pakfire.cgi=configuration/ipfire/pakfire
>>>>>>>>> wlanap.cgi=addons/wireless
>>>>>>>>> tor.cgi=addons/tor
>>>>>>>>> samba.cgi=addons/samba
>>>>>>>>> +rpz.cgi=addons/rpz
>>>>>>>>>
>>>>>>>>> # Logs menu
>>>>>>>>> logs.cgi/summary.dat=configuration/logs/summary
>>>>>>>>> diff --git a/config/menu/EX-rpz.menu b/config/menu/EX-rpz.menu
>>>>>>>>> new file mode 100644
>>>>>>>>> index 000000000..2f4daf410
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/config/menu/EX-rpz.menu
>>>>>>>>> @@ -0,0 +1,6 @@
>>>>>>>>> +$subipfire->{'20.rpz'} = {
>>>>>>>>> + 'caption' => $Lang::tr{'rpz'},
>>>>>>>>> + 'uri' => '/cgi-bin/rpz.cgi',
>>>>>>>>> + 'title' => "RPZ",
>>>>>>>>> + 'enabled' => 1,
>>>>>>>>> +};
>>>>>>>>> diff --git a/config/rootfiles/common/configroot b/config/rootfiles/common/configroot
>>>>>>>>> index 9839eee45..b30d6aae4 100644
>>>>>>>>> --- a/config/rootfiles/common/configroot
>>>>>>>>> +++ b/config/rootfiles/common/configroot
>>>>>>>>> @@ -120,6 +120,7 @@ var/ipfire/menu.d/70-log.menu
>>>>>>>>> #var/ipfire/menu.d/EX-apcupsd.menu
>>>>>>>>> #var/ipfire/menu.d/EX-guardian.menu
>>>>>>>>> #var/ipfire/menu.d/EX-mympd.menu
>>>>>>>>> +#var/ipfire/menu.d/EX-rpz.menu
>>>>>>>>> #var/ipfire/menu.d/EX-samba.menu
>>>>>>>>> #var/ipfire/menu.d/EX-tor.menu
>>>>>>>>> #var/ipfire/menu.d/EX-transmission.menu
>>>>>>>>> diff --git a/config/rootfiles/common/web-user-interface b/config/rootfiles/common/web-user-interface
>>>>>>>>> index 816241dae..e00464076 100644
>>>>>>>>> --- a/config/rootfiles/common/web-user-interface
>>>>>>>>> +++ b/config/rootfiles/common/web-user-interface
>>>>>>>>> @@ -69,6 +69,7 @@ srv/web/ipfire/cgi-bin/proxy.cgi
>>>>>>>>> srv/web/ipfire/cgi-bin/qos.cgi
>>>>>>>>> srv/web/ipfire/cgi-bin/remote.cgi
>>>>>>>>> srv/web/ipfire/cgi-bin/routing.cgi
>>>>>>>>> +#srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>>>> #srv/web/ipfire/cgi-bin/samba.cgi
>>>>>>>>> srv/web/ipfire/cgi-bin/services.cgi
>>>>>>>>> srv/web/ipfire/cgi-bin/shutdown.cgi
>>>>>>>>> diff --git a/config/rootfiles/packages/rpz b/config/rootfiles/packages/rpz
>>>>>>>>> new file mode 100644
>>>>>>>>> index 000000000..1c8663049
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/config/rootfiles/packages/rpz
>>>>>>>>> @@ -0,0 +1,20 @@
>>>>>>>>> +etc/unbound/local.d/00-rpz.conf
>>>>>>>>> +etc/unbound/zonefiles
>>>>>>>>> +etc/unbound/zonefiles/allow.rpz
>>>>>>>>> +usr/sbin/rpz-config
>>>>>>>>> +usr/sbin/rpz-functions
>>>>>>>>> +usr/sbin/rpz-make
>>>>>>>>> +usr/sbin/rpz-metrics
>>>>>>>>> +usr/sbin/rpz-sleep
>>>>>>>>> +var/ipfire/addon-lang/rpz.de.pl
>>>>>>>>> +var/ipfire/addon-lang/rpz.en.pl
>>>>>>>>> +var/ipfire/addon-lang/rpz.es.pl
>>>>>>>>> +var/ipfire/addon-lang/rpz.fr.pl
>>>>>>>>> +var/ipfire/addon-lang/rpz.it.pl
>>>>>>>>> +var/ipfire/addon-lang/rpz.tr.pl
>>>>>>>>> +var/ipfire/backup/addons/includes/rpz
>>>>>>>>> +var/ipfire/dns/rpz
>>>>>>>>> +var/ipfire/dns/rpz/allowlist
>>>>>>>>> +var/ipfire/dns/rpz/blocklist
>>>>>>>>> +var/ipfire/menu.d/EX-rpz.menu
>>>>>>>>> +srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>>>> diff --git a/config/rpz/00-rpz.conf b/config/rpz/00-rpz.conf
>>>>>>>>> new file mode 100644
>>>>>>>>> index 000000000..f005a4f2e
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/config/rpz/00-rpz.conf
>>>>>>>>> @@ -0,0 +1,10 @@
>>>>>>>>> +server:
>>>>>>>>> + module-config: "respip validator iterator"
>>>>>>>>> +
>>>>>>>>> +rpz:
>>>>>>>>> + name: allow.rpz
>>>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>>>> + rpz-action-override: passthru
>>>>>>>>> + rpz-log: yes
>>>>>>>>> + rpz-log-name: allow
>>>>>>>>> + rpz-signal-nxdomain-ra: yes
>>>>>>>>> diff --git a/config/rpz/rpz-config b/config/rpz/rpz-config
>>>>>>>>> new file mode 100644
>>>>>>>>> index 000000000..c72d50f9b
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/config/rpz/rpz-config
>>>>>>>>> @@ -0,0 +1,130 @@
>>>>>>>>> +#!/bin/bash
>>>>>>>>> +###############################################################################
>>>>>>>>> +# #
>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info@ipfire.org> #
>>>>>>>>> +# #
>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>> +# #
>>>>>>>>> +###############################################################################
>>>>>>>>> +
>>>>>>>>> +version="2025-01-11 - v44"
>>>>>>>>> +
>>>>>>>>> +############### Functions ###############
>>>>>>>>> +
>>>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>>>> +
>>>>>>>>> +############### Main ###############
>>>>>>>>> +
>>>>>>>>> +tagName="unbound"
>>>>>>>>> +
>>>>>>>>> +rpzAction="${1}" # input RPZ action
>>>>>>>>> +rpzName="${2}" # input RPZ name
>>>>>>>>> +rpzURL="${3}" # input RPZ URL
>>>>>>>>> +rpzOption1="${4}" # input RPZ option #1
>>>>>>>>> +rpzOption2="${5}" # input RPZ option #2
>>>>>>>>> +
>>>>>>>>> +rpzConfig="/etc/unbound/local.d/${rpzName}.rpz.conf" # output zone conf file
>>>>>>>>> +rpzFile="/etc/unbound/zonefiles/${rpzName}.rpz" # output for RPZ file
>>>>>>>>> +
>>>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>>>> +
>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>> + case "$1" in
>>>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>>>> + esac
>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>> +done
>>>>>>>>> +
>>>>>>>>> +case "${rpzAction}" in
>>>>>>>>> + # add new rpz list
>>>>>>>>> + add )
>>>>>>>>> + check_name "${rpzName}" # is this a valid name?
>>>>>>>>> + # does this config already exist? If yes, then exit
>>>>>>>>> + if [[ -f "${rpzConfig}" ]] ; then
>>>>>>>>> + msg_log "error: rpz: duplicate - ${rpzConfig} already exists. exit"
>>>>>>>>> + exit 104
>>>>>>>>> + fi
>>>>>>>>> +
>>>>>>>>> + # is this a valid URL?
>>>>>>>>> + regex='^https://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
>>>>>>>>> + if ! [[ "${rpzURL}" =~ $regex ]] ; then
>>>>>>>>> + msg_log "error: rpz: the URL is not valid: \"${rpzURL}\". exit."
>>>>>>>>> + exit 105
>>>>>>>>> + fi
>>>>>>>>> +
>>>>>>>>> + # create the zone config file
>>>>>>>>> + {
>>>>>>>>> + echo "rpz:"
>>>>>>>>> + echo " name: ${rpzName}.rpz"
>>>>>>>>> + echo " zonefile: ${rpzFile}"
>>>>>>>>> + echo " url: ${rpzURL}"
>>>>>>>>> + echo " rpz-action-override: nxdomain"
>>>>>>>>> + echo " rpz-log: ${rpzLog}"
>>>>>>>>> + echo " rpz-log-name: ${rpzName}"
>>>>>>>>> + echo " rpz-signal-nxdomain-ra: yes"
>>>>>>>>> + } > "${rpzConfig}"
>>>>>>>>> +
>>>>>>>>> + # set-up zonefile
>>>>>>>>> + # create an empty rpz file if it does not exist
>>>>>>>>> + if [[ ! -f "${rpzFile}" ]] ; then
>>>>>>>>> + touch "${rpzFile}"
>>>>>>>>> + # unbound requires these settings for rpz files
>>>>>>>>> + set_permissions "${rpzFile}" "${rpzConfig}"
>>>>>>>>> + fi
>>>>>>>>> + ;;
>>>>>>>>> +
>>>>>>>>> + # trash config file & rpz file
>>>>>>>>> + remove )
>>>>>>>>> + if ! [[ -f "${rpzConfig}" ]] ; then
>>>>>>>>> + msg_log "error: rpz: cannot remove ${rpzConfig}, does not exist. exit"
>>>>>>>>> + exit 106
>>>>>>>>> + fi
>>>>>>>>> +
>>>>>>>>> + msg_log "info: rpz: remove config file & rpz file \"${rpzName}\""
>>>>>>>>> + rm "${rpzConfig}"
>>>>>>>>> + rm "${rpzFile}"
>>>>>>>>> + ;;
>>>>>>>>> +
>>>>>>>>> + reload )
>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>> + ;;
>>>>>>>>> +
>>>>>>>>> + list )
>>>>>>>>> + awk -F':' '/^\s*name:/{ gsub(/[[:blank:]]|\.rpz/, "",$2) ; NAME=$2 } \
>>>>>>>>> + /^\s*url:/{ gsub(/[[:blank:]]/, "") ; print NAME"="$2":"$3} ' \
>>>>>>>>> + /etc/unbound/local.d/*rpz.conf
>>>>>>>>> + exit
>>>>>>>>> + ;;
>>>>>>>>> +
>>>>>>>>> + unbound-restart )
>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>> + unbound_restart
>>>>>>>>> + exit
>>>>>>>>> + ;;
>>>>>>>>> +
>>>>>>>>> + * )
>>>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>>>> + printf "Usage: $(basename "$0") <ACTION> <NAME> <URL> <OPTION> <OPTION>\n"
>>>>>>>>> + printf "Version: ${version}\n"
>>>>>>>>> + exit 108
>>>>>>>>> + ;;
>>>>>>>>> +
>>>>>>>>> +esac
>>>>>>>>> +
>>>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>>>> +
>>>>>>>>> +exit
>>>>>>>>> diff --git a/config/rpz/rpz-functions b/config/rpz/rpz-functions
>>>>>>>>> new file mode 100644
>>>>>>>>> index 000000000..ace1d2690
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/config/rpz/rpz-functions
>>>>>>>>> @@ -0,0 +1,85 @@
>>>>>>>>> +#!/bin/bash
>>>>>>>>> +###############################################################################
>>>>>>>>> +# #
>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>> +# #
>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>> +# #
>>>>>>>>> +###############################################################################
>>>>>>>>> +
>>>>>>>>> +version="2024-12-10 - v02"
>>>>>>>>> +
>>>>>>>>> +############### Functions ###############
>>>>>>>>> +
>>>>>>>>> +msg_log () {
>>>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>>>> + if tty --silent ; then
>>>>>>>>> + echo "${tagName}:" "$*"
>>>>>>>>> + fi
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# check for a valid name
>>>>>>>>> +check_name () {
>>>>>>>>> + local theName="${1}"
>>>>>>>>> +
>>>>>>>>> + regex='^[a-zA-Z0-9_]+$' # no dash or plus, alpha numeric only
>>>>>>>>> + regex1='^(allow|block)$' # allow and block are reserved NAMEs
>>>>>>>>> + if [[ ! "${theName}" =~ $regex ]] || [[ "${theName}" =~ $regex1 ]] ; then
>>>>>>>>> + msg_log "error: rpz: the NAME is not valid: \"${theName}\". exit."
>>>>>>>>> + exit 101
>>>>>>>>> + fi
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +set_permissions () {
>>>>>>>>> + chown nobody:nobody "$@"
>>>>>>>>> + chmod 644 "$@"
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +check_unbound_conf () {
>>>>>>>>> + local thecheckConf="${1:-yes}" # check config default is yes
>>>>>>>>> +
>>>>>>>>> + # check the above config files
>>>>>>>>> + if [[ "${thecheckConf}" == yes ]] ; then
>>>>>>>>> + msg_log "info: rpz: check for errors with \"unbound-checkconf\""
>>>>>>>>> +
>>>>>>>>> + if ! unbound-checkconf ; then
>>>>>>>>> + msg_log "error: rpz: unbound-checkconf found invalid configuration."
>>>>>>>>> + msg_log \
>>>>>>>>> + "error: rpz: In Terminal run the command \"unbound-checkconf\" for more information. exit."
>>>>>>>>> + exit 102
>>>>>>>>> + fi
>>>>>>>>> + fi
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +unbound_control_reload () {
>>>>>>>>> + local theReload="${1:-yes}" # reload default is yes
>>>>>>>>> +
>>>>>>>>> + if [[ "${theReload}" == yes ]] ; then
>>>>>>>>> + # reload due to the changes
>>>>>>>>> + msg_log "info: rpz: run \"unbound-control reload\""
>>>>>>>>> +
>>>>>>>>> + if ! unbound-control reload ; then
>>>>>>>>> + msg_log "error: rpz: unbound-control reload. exit."
>>>>>>>>> + exit 109
>>>>>>>>> + fi
>>>>>>>>> + fi
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +unbound_restart () {
>>>>>>>>> + # restart due to the changes
>>>>>>>>> + msg_log "info: rpz: run \"unbound restart\""
>>>>>>>>> +
>>>>>>>>> + /usr/local/bin/unboundctrl restart
>>>>>>>>> +}
>>>>>>>>> diff --git a/config/rpz/rpz-make b/config/rpz/rpz-make
>>>>>>>>> new file mode 100644
>>>>>>>>> index 000000000..927d55170
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/config/rpz/rpz-make
>>>>>>>>> @@ -0,0 +1,203 @@
>>>>>>>>> +#!/bin/bash
>>>>>>>>> +###############################################################################
>>>>>>>>> +# #
>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info@ipfire.org> #
>>>>>>>>> +# #
>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>> +# #
>>>>>>>>> +###############################################################################
>>>>>>>>> +
>>>>>>>>> +version="2025-01-11 - v14"
>>>>>>>>> +
>>>>>>>>> +############### Functions ###############
>>>>>>>>> +
>>>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>>>> +
>>>>>>>>> +# create the config file for allow
>>>>>>>>> +make_allow_config () {
>>>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>>>> + local theConfig="/etc/unbound/local.d/00-rpz.conf" # output zone conf file
>>>>>>>>> + local theList="/var/ipfire/dns/rpz/allowlist" # input custom list of domains
>>>>>>>>> +
>>>>>>>>> + msg_log "info: rpz: make config file \"00-rpz.conf\""
>>>>>>>>> +
>>>>>>>>> + echo "server:
>>>>>>>>> + module-config: \"respip validator iterator\"" > "${theConfig}"
>>>>>>>>> +
>>>>>>>>> + # does allow list exist?
>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>> +
>>>>>>>>> + echo "rpz:
>>>>>>>>> + name: allow.rpz
>>>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>>>> + rpz-action-override: passthru
>>>>>>>>> + rpz-log: ${theLog}
>>>>>>>>> + rpz-log-name: allow
>>>>>>>>> + rpz-signal-nxdomain-ra: yes" >> "${theConfig}"
>>>>>>>>> +
>>>>>>>>> + fi
>>>>>>>>> +
>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>> + set_permissions "${theConfig}"
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# create the config file for block
>>>>>>>>> +make_block_config () {
>>>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>>>> + local theConfig="/etc/unbound/local.d/block.rpz.conf" # output zone conf file
>>>>>>>>> + local theList="/var/ipfire/dns/rpz/blocklist" # input custom list of domains
>>>>>>>>> +
>>>>>>>>> + msg_log "info: rpz: make config file \"block.rpz.conf\""
>>>>>>>>> +
>>>>>>>>> + # does block list exist?
>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>> +
>>>>>>>>> + echo "rpz:
>>>>>>>>> + name: block.rpz
>>>>>>>>> + zonefile: /etc/unbound/zonefiles/block.rpz
>>>>>>>>> + rpz-action-override: nxdomain
>>>>>>>>> + rpz-log: ${theLog}
>>>>>>>>> + rpz-log-name: block
>>>>>>>>> + rpz-signal-nxdomain-ra: yes" > "${theConfig}"
>>>>>>>>> +
>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>> + set_permissions "${theConfig}"
>>>>>>>>> + else
>>>>>>>>> + # no - trash the config file
>>>>>>>>> + rm --verbose /etc/unbound/local.d/block.rpz.conf
>>>>>>>>> + fi
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# create an RPZ file for allow or block
>>>>>>>>> +make_rpz_file () {
>>>>>>>>> + local theName="${1}" # allow or block
>>>>>>>>> + local theAction='.' # the default is nxdomain or block
>>>>>>>>> + local actionList
>>>>>>>>> +
>>>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>>>> + local theZonefile="/etc/unbound/zonefiles/${theName}.rpz" # output file for RPZ
>>>>>>>>> +
>>>>>>>>> + # does a list exist?
>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>> +
>>>>>>>>> + # for allow set to passthru
>>>>>>>>> + [[ "${theName}" == allow ]] && theAction='rpz-passthru.'
>>>>>>>>> +
>>>>>>>>> + # drop any extra "blanks" and add "CNAME <RPZ action>." to each line
>>>>>>>>> + actionList=$( awk '{$1=$1};1' "${theList}" |
>>>>>>>>> + sed "/^[^;].*[[:alnum:]]/ s|$| CNAME ${theAction}|" )
>>>>>>>>> +
>>>>>>>>> + msg_log "info: rpz: create zonefile for \"${theName}list\""
>>>>>>>>> +
>>>>>>>>> +echo "; Name: ${theName} list
>>>>>>>>> +; Last modified: $(date "+%Y-%m-%d at %H.%M.%S %Z")
>>>>>>>>> +;
>>>>>>>>> +; domains with actions list
>>>>>>>>> +;
>>>>>>>>> +${actionList}" > "${theZonefile}"
>>>>>>>>> +
>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>> + set_permissions "${theZonefile}"
>>>>>>>>> + # set-up allow/block list files
>>>>>>>>> + set_permissions "${theList}"
>>>>>>>>> + else
>>>>>>>>> + msg_log "info: rpz: the ${theList} is empty."
>>>>>>>>> +
>>>>>>>>> + rm --verbose "${theZonefile}" # trash the RPZ file
>>>>>>>>> + fi
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# check if allow/block list is valid
>>>>>>>>> +validate_list () {
>>>>>>>>> + local theName="${1}" # allow or block
>>>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>>>> +
>>>>>>>>> + # remove good:
>>>>>>>>> + # - properly formated domain names with or without leading wildcard
>>>>>>>>> + # - properly formated top level domain (TLD) names with wildcard
>>>>>>>>> + # - blank lines and comment lines
>>>>>>>>> + # remaining lines are considered "bad"
>>>>>>>>> + bad_lines=$( sed --regexp-extended \
>>>>>>>>> + '/^(\*\.)?([a-zA-Z0-9](([a-zA-Z0-9\-]){0,61}[a-zA-Z0-9])?\.)+([a-zA-Z]{2,}|xn--[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])$/d ;
>>>>>>>>> + /^(\*\.)([a-z]{2,61}|xn--[a-z0-9]{1,60})$/d ;
>>>>>>>>> + /^$/d ; /^;/d' "${theList}" )
>>>>>>>>> +
>>>>>>>>> + if [[ ! -z "${bad_lines}" ]] ; then
>>>>>>>>> + msg_log "error: rpz: invalid line(s) in ${theList}."
>>>>>>>>> + printf "%s\n" "bad line(s): ${bad_lines}"
>>>>>>>>> + exit 110
>>>>>>>>> + fi
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +
>>>>>>>>> +############### Main ###############
>>>>>>>>> +
>>>>>>>>> +tagName="unbound"
>>>>>>>>> +
>>>>>>>>> +rpzName="${1}" # input RPZ name
>>>>>>>>> +
>>>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>>>> +
>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>> + case "$1" in
>>>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>>>> + esac
>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>> +done
>>>>>>>>> +
>>>>>>>>> +case "${rpzName}" in
>>>>>>>>> + # make a new allow or block rpz file
>>>>>>>>> +
>>>>>>>>> + allow )
>>>>>>>>> + validate_list 'allow' # is the allowlist valid?
>>>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>>>> + make_rpz_file 'allow'
>>>>>>>>> + ;;
>>>>>>>>> +
>>>>>>>>> + allowblock )
>>>>>>>>> + validate_list 'allow' # is the list valid?
>>>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>>>> + make_rpz_file 'allow'
>>>>>>>>> + ;&
>>>>>>>>> +
>>>>>>>>> + block )
>>>>>>>>> + validate_list 'block' # is the blocklist valid?
>>>>>>>>> + make_block_config "${rpzLog}"
>>>>>>>>> + make_rpz_file 'block'
>>>>>>>>> + ;;
>>>>>>>>> +
>>>>>>>>> + reload )
>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>> + ;;
>>>>>>>>> +
>>>>>>>>> + unbound-restart )
>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>> + unbound_restart
>>>>>>>>> + exit
>>>>>>>>> + ;;
>>>>>>>>> +
>>>>>>>>> + * )
>>>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>>>> + printf "Usage: $(basename "$0") <NAME> <OPTION> <OPTION>\n"
>>>>>>>>> + printf "Version: ${version}\n"
>>>>>>>>> + exit 108
>>>>>>>>> + ;;
>>>>>>>>> +esac
>>>>>>>>> +
>>>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>>>> +
>>>>>>>>> +exit
>>>>>>>>> diff --git a/config/rpz/rpz-metrics b/config/rpz/rpz-metrics
>>>>>>>>> new file mode 100755
>>>>>>>>> index 000000000..4d43e1629
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/config/rpz/rpz-metrics
>>>>>>>>> @@ -0,0 +1,170 @@
>>>>>>>>> +#!/bin/bash
>>>>>>>>> +###############################################################################
>>>>>>>>> +# #
>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>> +# #
>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>> +# #
>>>>>>>>> +###############################################################################
>>>>>>>>> +
>>>>>>>>> +version="2025-01-20 - v25"
>>>>>>>>> +
>>>>>>>>> +############### Main ###############
>>>>>>>>> +
>>>>>>>>> +weeks="2" # default to two message logs
>>>>>>>>> +sortBy="name" # default "by name"
>>>>>>>>> +rpzActive="enabled" # default "enabled only"
>>>>>>>>> +
>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>> + case "$1" in
>>>>>>>>> + --by-names | --by-name | name ) sortBy="name" ;;
>>>>>>>>> +
>>>>>>>>> + --by-hits | --by-hit | hits | hit ) sortBy="hit" ;;
>>>>>>>>> +
>>>>>>>>> + --by-lines | --by-line | lines | line ) sortBy="line" ;;
>>>>>>>>> +
>>>>>>>>> + --by-effect ) sortBy="effect" ;;
>>>>>>>>> +
>>>>>>>>> + --enabled-only ) rpzActive="enabled" ;;
>>>>>>>>> +
>>>>>>>>> + --active-all | --all | all ) rpzActive="all" ;;
>>>>>>>>> +
>>>>>>>>> + [0-9] | [0-9][0-9] ) weeks=$1 ;;
>>>>>>>>> + esac
>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>> +done
>>>>>>>>> +
>>>>>>>>> +# get the list of message logs for N weeks
>>>>>>>>> +messageLogs=$( find /var/log/messages* -type f | sort --version-sort |
>>>>>>>>> + head -"${weeks}" )
>>>>>>>>> +
>>>>>>>>> +# get the list of RPZ names & counts from the message log(s)
>>>>>>>>> +rpzNameCount=$( for logf in ${messageLogs} ; do
>>>>>>>>> + zgrep --text --fixed-strings 'info: rpz: applied' "${logf}" |
>>>>>>>>> + awk '$10 ~ /\[\w*]/ { print $10 }' ;
>>>>>>>>> + done | sort | uniq --count )
>>>>>>>>> +
>>>>>>>>> +# flip results and remove brackets `[` and `]`
>>>>>>>>> +rpzNameCount=$( echo "${rpzNameCount}" |
>>>>>>>>> + awk '{ print $2, $1 }' |
>>>>>>>>> + sed --regexp-extended 's|^\[(.*)\]|\1|' )
>>>>>>>>> +
>>>>>>>>> +# grab only names
>>>>>>>>> +rpzNames=$( echo "${rpzNameCount}" | awk '{ print $1 }' )
>>>>>>>>> +
>>>>>>>>> +# get list of RPZ files
>>>>>>>>> +rpzFileList=$( find /etc/unbound/zonefiles -type f -iname "*.rpz" )
>>>>>>>>> +
>>>>>>>>> +# get basename of those files
>>>>>>>>> +rpzBaseNames=$( echo "${rpzFileList}" |
>>>>>>>>> + sed 's|/etc/unbound/zonefiles/||g ; s|\.rpz||g ;' )
>>>>>>>>> +
>>>>>>>>> +# add to rpzNames
>>>>>>>>> +rpzNames="${rpzNames}"$'\n'"${rpzBaseNames}"
>>>>>>>>> +
>>>>>>>>> +# drop duplicate names
>>>>>>>>> +rpzNames=$( echo "${rpzNames}" | sort --unique )
>>>>>>>>> +
>>>>>>>>> +# get line count for each RPZ
>>>>>>>>> +lineCount=$( echo "${rpzFileList}" | xargs wc -l )
>>>>>>>>> +
>>>>>>>>> +# get comment line count and blank line count for each RPZ
>>>>>>>>> +commentCount=$( echo "${rpzFileList}" | xargs grep --count -e "^$" -e "^;" )
>>>>>>>>> +
>>>>>>>>> +# get modified date each RPZ
>>>>>>>>> +modDateList=$( echo "${rpzFileList}" | xargs stat -c '%.10y %n' )
>>>>>>>>> +
>>>>>>>>> +ucListAuthZones=$( unbound-control list_auth_zones )
>>>>>>>>> +
>>>>>>>>> +# get width of RPZ names
>>>>>>>>> +pWidth=$( echo "${rpzNames}" | awk '{ print $1" " }' | wc -L )
>>>>>>>>> +pFormat="%-${pWidth}s %-8s %-8s %8s %12s %12s\n"
>>>>>>>>> +
>>>>>>>>> +# print title line
>>>>>>>>> +printf "${pFormat}" "name" "hits" "active" "lines" " hits/line" "last download"
>>>>>>>>> +printf -- "--------------"
>>>>>>>>> +
>>>>>>>>> +theResults=""
>>>>>>>>> +totalLines=0
>>>>>>>>> +totalHits=0
>>>>>>>>> +while read -r theName
>>>>>>>>> +do
>>>>>>>>> + printf -- "--" # pretend progress bar
>>>>>>>>> +
>>>>>>>>> + # is this RPZ list active?
>>>>>>>>> + theActive="disabled"
>>>>>>>>> + if grep --quiet "^${theName}\.rpz" <<< "${ucListAuthZones}"
>>>>>>>>> + then
>>>>>>>>> + theActive="enabled"
>>>>>>>>> + else
>>>>>>>>> + [[ "${rpzActive}" == enabled ]] && continue
>>>>>>>>> + fi
>>>>>>>>> +
>>>>>>>>> + # get hit count
>>>>>>>>> + theHits="0"
>>>>>>>>> + if output=$( grep "^${theName}\s" <<< "${rpzNameCount}" ) ; then
>>>>>>>>> + theHits=$( echo "${output}" | awk '{ print $2 }' )
>>>>>>>>> + totalHits=$(( totalHits + theHits ))
>>>>>>>>> + fi
>>>>>>>>> +
>>>>>>>>> + # get line count
>>>>>>>>> + theLines="n/a"
>>>>>>>>> + hitsPerLine="0"
>>>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${lineCount}" ) ; then
>>>>>>>>> + theLines=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>>>> + totalLines=$(( totalLines + theLines ))
>>>>>>>>> +
>>>>>>>>> + if [[ "${theLines}" -gt 2 ]] ; then
>>>>>>>>> + hitsPerLine=$(( 100 * theHits / theLines ))
>>>>>>>>> + fi
>>>>>>>>> + fi
>>>>>>>>> +
>>>>>>>>> + # get modification date
>>>>>>>>> + theModDate="n/a"
>>>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${modDateList}" ) ; then
>>>>>>>>> + theModDate=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>>>> + fi
>>>>>>>>> +
>>>>>>>>> + # add to results list
>>>>>>>>> + theResults+="${theName} ${theHits} ${theActive} ${theLines} ${hitsPerLine} ${theModDate}"$'\n'
>>>>>>>>> +
>>>>>>>>> +done <<< "${rpzNames}"
>>>>>>>>> +
>>>>>>>>> +case "${sortBy}" in
>>>>>>>>> + # sort by "active" then by "name"
>>>>>>>>> + name) sortArg=(-k3,3r -k1,1) ;;
>>>>>>>>> +
>>>>>>>>> + # sort by "active" then by "hits" then by "name"
>>>>>>>>> + hit) sortArg=(-k3,3r -k2,2nr -k1,1) ;;
>>>>>>>>> +
>>>>>>>>> + # sort by "active" then by "lines" then by "name"
>>>>>>>>> + line) sortArg=(-k3,3r -k4,4nr -k1,1) ;;
>>>>>>>>> +
>>>>>>>>> + # sort by "active" then by "effect" then by "name"
>>>>>>>>> + effect) sortArg=(-k3,3r -k5,5nr -k1,1) ;;
>>>>>>>>> +esac
>>>>>>>>> +
>>>>>>>>> +printf -- "--------------\n"
>>>>>>>>> +# remove blank lines, sort, print as columns
>>>>>>>>> +echo "${theResults}" |
>>>>>>>>> + awk '!/^[[:space:]]*$/' |
>>>>>>>>> + sort "${sortArg[@]}" |
>>>>>>>>> + awk --assign=width="${pWidth}" \
>>>>>>>>> + '{ printf "%-*s %-8s %-8s %8s %10s %% %12s\n", width, $1, $2, $3, $4, $5, $6 }'
>>>>>>>>> +
>>>>>>>>> +printf "${pFormat}" "" "=======" "" "========" "" ""
>>>>>>>>> +printf "${pFormat}" "Totals -->" "${totalHits}" "" "${totalLines}" "" ""
>>>>>>>>> +
>>>>>>>>> +exit
>>>>>>>>> diff --git a/config/rpz/rpz-sleep b/config/rpz/rpz-sleep
>>>>>>>>> new file mode 100755
>>>>>>>>> index 000000000..dd3603599
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/config/rpz/rpz-sleep
>>>>>>>>> @@ -0,0 +1,58 @@
>>>>>>>>> +#!/bin/bash
>>>>>>>>> +###############################################################################
>>>>>>>>> +# #
>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>> +# #
>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>> +# #
>>>>>>>>> +###############################################################################
>>>>>>>>> +
>>>>>>>>> +version="2024-08-16" # v05
>>>>>>>>> +
>>>>>>>>> +############### Functions ###############
>>>>>>>>> +
>>>>>>>>> +# send message to message log
>>>>>>>>> +msg_log () {
>>>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>>>> + if tty --silent ; then
>>>>>>>>> + echo "${tagName}:" "$*"
>>>>>>>>> + fi
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +############### Main ###############
>>>>>>>>> +
>>>>>>>>> +tagName="unbound"
>>>>>>>>> +
>>>>>>>>> +sleepTime="${1:-5m}" # default to sleep for 5m (5 minutes)
>>>>>>>>> +
>>>>>>>>> +zoneList=$( unbound-control list_auth_zones | awk '{print $1}' )
>>>>>>>>> +
>>>>>>>>> +for zone in ${zoneList} ; do
>>>>>>>>> + printf "disable ${zone}\t"
>>>>>>>>> + unbound-control rpz_disable "${zone}"
>>>>>>>>> +done
>>>>>>>>> +
>>>>>>>>> +msg_log "info: rpz: disabled all zones for ${sleepTime}"
>>>>>>>>> +
>>>>>>>>> +sleep "${sleepTime}"
>>>>>>>>> +
>>>>>>>>> +for zone in ${zoneList} ; do
>>>>>>>>> + printf "enable ${zone}\t"
>>>>>>>>> + unbound-control rpz_enable "${zone}"
>>>>>>>>> +done
>>>>>>>>> +
>>>>>>>>> +msg_log "info: rpz: enabled all zones"
>>>>>>>>> +
>>>>>>>>> +exit
>>>>>>>>> diff --git a/config/rpz/rpz.de.pl b/config/rpz/rpz.de.pl
>>>>>>>>> new file mode 100644
>>>>>>>>> index 000000000..3770c6bb0
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/config/rpz/rpz.de.pl
>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>> +%tr = (%tr,
>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>> +'rpz apply' => 'Übernehmen',
>>>>>>>>> +'rpz cl allow enable' => 'Benutzerdefinierte Allowlist aktivieren:',
>>>>>>>>> +'rpz cl allow info' => 'Zugelassene Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>>>> +'rpz cl allow' => 'Benutzerdefinierte Allowlist',
>>>>>>>>> +'rpz cl block enable' => 'Benutzerdefinierte Blocklist aktivieren:',
>>>>>>>>> +'rpz cl block info' => 'Gesperrte Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>>>> +'rpz cl block' => 'Benutzerdefinierte Blocklist',
>>>>>>>>> +'rpz cl' => 'Benutzerdefinierte Listen',
>>>>>>>>> +'rpz exitcode 101' => 'Der Name enthält unzulässige Zeichen',
>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf hat eine fehlerhafte Konfiguration ermittelt. Führen Sie das Kommando unbound-checkconf auf der Konsole aus, um weitere Informationen zu erhalten.',
>>>>>>>>> +'rpz exitcode 103' => 'Die benutzerdefinierte Allow-/Blocklist ist leer',
>>>>>>>>> +'rpz exitcode 104' => 'Ein Eintrag mit identischem Namen existiert bereits',
>>>>>>>>> +'rpz exitcode 105' => 'Die URL ist ungültig',
>>>>>>>>> +'rpz exitcode 106' => 'Eintrag kann nicht entfernt werden, der Name existiert nicht',
>>>>>>>>> +'rpz exitcode 107' => 'Der Name ist ungültig - nur "allow" oder "block" möglich',
>>>>>>>>> +'rpz exitcode 108' => 'Fehlende oder inkorrekte Parameter',
>>>>>>>>> +'rpz exitcode 109' => 'unbound-control reload ist fehlgeschlagen',
>>>>>>>>> +'rpz exitcode 110' => 'Die benutzerdefinierte Allow-/Blocklist enthält unzulässige Einträge',
>>>>>>>>> +'rpz exitcode 201' => 'Die Anmerkung enthält unzulässige Zeichen',
>>>>>>>>> +'rpz exitcode 202' => 'Ungültiger Eintrag in der benutzerdefinierten Allowlist, Zeile ',
>>>>>>>>> +'rpz exitcode 203' => 'Ungültiger Eintrag in der benutzerdefinierten Blocklist, Zeile ',
>>>>>>>>> +'rpz exitcode 204' => 'Ausgewählter Eintrag existiert nicht: ',
>>>>>>>>> +'rpz zf editor' => 'Zonendatei-Eintrag bearbeiten',
>>>>>>>>> +'rpz zf imported' => '(importiert aus rpz-config)',
>>>>>>>>> +'rpz zf remark info' => 'Erlaubte Zeichen sind a-z, A-Z, 0-9 und Unterstriche',
>>>>>>>>> +'rpz zf' => 'Zonendateien',
>>>>>>>>> +);
>>>>>>>>> diff --git a/config/rpz/rpz.en.pl b/config/rpz/rpz.en.pl
>>>>>>>>> new file mode 100644
>>>>>>>>> index 000000000..0720a8940
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/config/rpz/rpz.en.pl
>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>> +%tr = (%tr,
>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>> +'rpz apply' => 'Apply',
>>>>>>>>> +'rpz cl allow enable' => 'Enable custom allowlist:',
>>>>>>>>> +'rpz cl allow info' => 'Allowed domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>>>> +'rpz cl allow' => 'Custom allowlist',
>>>>>>>>> +'rpz cl block enable' => 'Enable custom blocklist:',
>>>>>>>>> +'rpz cl block info' => 'Blocked domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>>>> +'rpz cl block' => 'Custom blocklist',
>>>>>>>>> +'rpz cl' => 'Custom lists',
>>>>>>>>> +'rpz exitcode 101' => 'the NAME is not valid',
>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf found invalid configuration. In the Terminal run the command unbound-checkconf for more information',
>>>>>>>>> +'rpz exitcode 103' => 'the allow/block list is empty',
>>>>>>>>> +'rpz exitcode 104' => 'duplicate - NAME already exists',
>>>>>>>>> +'rpz exitcode 105' => 'the URL is not valid',
>>>>>>>>> +'rpz exitcode 106' => 'cannot remove the NAME does not exist',
>>>>>>>>> +'rpz exitcode 107' => 'the NAME is not valid - "allow" or "block" only',
>>>>>>>>> +'rpz exitcode 108' => 'missing or incorrect parameter',
>>>>>>>>> +'rpz exitcode 109' => 'unbound-control reload failed',
>>>>>>>>> +'rpz exitcode 110' => 'custom Allowlist/Blocklist contains invalid entries',
>>>>>>>>> +'rpz exitcode 201' => 'the REMARK is not valid',
>>>>>>>>> +'rpz exitcode 202' => 'invalid entry in allowlist, line ',
>>>>>>>>> +'rpz exitcode 203' => 'invalid entry in blocklist, line ',
>>>>>>>>> +'rpz exitcode 204' => 'Selected entry does not exist: ',
>>>>>>>>> +'rpz zf editor' => 'Edit zonefiles entry',
>>>>>>>>> +'rpz zf imported' => '(imported from rpz-config)',
>>>>>>>>> +'rpz zf remark info' => 'Valid characters are a-z, A-Z, 0-9 and underscore.',
>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>> +);
>>>>>>>>> diff --git a/config/rpz/rpz.es.pl b/config/rpz/rpz.es.pl
>>>>>>>>> new file mode 100644
>>>>>>>>> index 000000000..98628e4aa
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/config/rpz/rpz.es.pl
>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>> +%tr = (%tr,
>>>>>>>>> +'rpz' => 'Zonas de política de respuesta (RPZ)',
>>>>>>>>> +'rpz apply' => 'Aplicar',
>>>>>>>>> +'rpz cl allow enable' => 'Habilitar la lista blanca personalizada:',
>>>>>>>>> +'rpz cl allow info' => 'Dominio permitido (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>>>> +'rpz cl allow' => 'Lista blanca personalizada',
>>>>>>>>> +'rpz cl block enable' => 'Habilitar la lista negra personalizada:',
>>>>>>>>> +'rpz cl block info' => 'Dominio bloqueado (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>>>> +'rpz cl block' => 'Lista negra personalizada',
>>>>>>>>> +'rpz cl' => 'Lista personalizada',
>>>>>>>>> +'rpz exitcode 101' => 'El NOMBRE no es válido',
>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha encontrado una configuración no válida. Desde Terminal, ejecute el comando unbound-checkconf para mayor información',
>>>>>>>>> +'rpz exitcode 103' => 'La lista de permitidos/bloqueados está vacía',
>>>>>>>>> +'rpz exitcode 104' => 'duplicado - NOMBRE ya existe',
>>>>>>>>> +'rpz exitcode 105' => 'la URL no es válida',
>>>>>>>>> +'rpz exitcode 106' => 'no es posible eliminar el NOMBRE que no existe',
>>>>>>>>> +'rpz exitcode 107' => 'el NOMBRE no es válido - sólo "permitir" o "bloquear"',
>>>>>>>>> +'rpz exitcode 108' => 'parámetro faltante o incorrecto',
>>>>>>>>> +'rpz exitcode 109' => 'Error al recargar unbound-control',
>>>>>>>>> +'rpz exitcode 110' => 'la Lista blanca/Lista negra personalizada contiene entradas no válidas',
>>>>>>>>> +'rpz exitcode 201' => 'el COMENTARIO no es válido',
>>>>>>>>> +'rpz exitcode 202' => 'entrada no válida en la lista blanca, línea ',
>>>>>>>>> +'rpz exitcode 203' => 'entrada no válida en la lista negra, línea ',
>>>>>>>>> +'rpz exitcode 204' => 'La entrada seleccionada no existe: ',
>>>>>>>>> +'rpz zf editor' => 'Editar la entrada de archivos de zona',
>>>>>>>>> +'rpz zf imported' => '(importado de rpz-config)',
>>>>>>>>> +'rpz zf remark info' => 'Los caracteres válidos son a-z, A-Z, 0-9 y guión bajo',
>>>>>>>>> +'rpz zf' => 'Archivos de zona',
>>>>>>>>> +);
>>>>>>>>> diff --git a/config/rpz/rpz.fr.pl b/config/rpz/rpz.fr.pl
>>>>>>>>> new file mode 100644
>>>>>>>>> index 000000000..f35f3c2d0
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/config/rpz/rpz.fr.pl
>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>> +%tr = (%tr,
>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>> +'rpz apply' => 'Appliquer',
>>>>>>>>> +'rpz cl allow enable' => 'Activer la liste d\'autorisations personnalisée:',
>>>>>>>>> +'rpz cl allow info' => 'Domaines autorisés (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>>>> +'rpz cl allow' => 'Liste d\'autorisations personnalisée',
>>>>>>>>> +'rpz cl block enable' => 'Activer la liste de blocage personnalisée:',
>>>>>>>>> +'rpz cl block info' => 'Domaines bloqués (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>>>> +'rpz cl block' => 'liste de blocage personnalisé',
>>>>>>>>> +'rpz cl' => 'Listes personnalisées',
>>>>>>>>> +'rpz exitcode 101' => 'le NOM n\'est pas valide',
>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf configuration non valide trouvée. Dans le terminal, exécutez la commande unbound-checkconf pour plus d\'informations',
>>>>>>>>> +'rpz exitcode 103' => 'la liste autoriser/bloquer est vide',
>>>>>>>>> +'rpz exitcode 104' => 'le NOM existe déjà',
>>>>>>>>> +'rpz exitcode 105' => 'L\'URL n\'est pas valide',
>>>>>>>>> +'rpz exitcode 106' => 'impossible de supprimer le NOM n\'existe pas',
>>>>>>>>> +'rpz exitcode 107' => 'le NOM n\'est pas valide - « autoriser » ou « bloquer » seulement',
>>>>>>>>> +'rpz exitcode 108' => 'paramètre manquant ou incorrect',
>>>>>>>>> +'rpz exitcode 109' => 'unbound-control rechargement échoué',
>>>>>>>>> +'rpz exitcode 110' => 'la liste autoriser/bloquer contient des entrées non valides',
>>>>>>>>> +'rpz exitcode 201' => 'la REMARQUE n\'est pas valable',
>>>>>>>>> +'rpz exitcode 202' => 'entrée non valide dans la liste d\'autorisation, ligne ',
>>>>>>>>> +'rpz exitcode 203' => 'entrée non valide dans la liste de blocs, ligne ',
>>>>>>>>> +'rpz exitcode 204' => 'L\'entrée sélectionnée n\'existe pas: ',
>>>>>>>>> +'rpz zf editor' => 'Modifier l\'entrée Fichiers Zone',
>>>>>>>>> +'rpz zf imported' => '(importé de rpz-config)',
>>>>>>>>> +'rpz zf remark info' => 'Les caractères valides sont a-z, A-Z, 0-9 et soulignement.',
>>>>>>>>> +'rpz zf' => 'Fichiers Zone',
>>>>>>>>> +);
>>>>>>>>> diff --git a/config/rpz/rpz.it.pl b/config/rpz/rpz.it.pl
>>>>>>>>> new file mode 100644
>>>>>>>>> index 000000000..ee81605c9
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/config/rpz/rpz.it.pl
>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>> +%tr = (%tr,
>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>> +'rpz apply' => 'Applica',
>>>>>>>>> +'rpz cl allow enable' => 'Abilita la Whitelist personalizzata:',
>>>>>>>>> +'rpz cl allow info' => 'Domini consentiti (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>>>> +'rpz cl allow' => 'Whitelist personalizzata',
>>>>>>>>> +'rpz cl block enable' => 'Abilita la Blacklist personalizzata:',
>>>>>>>>> +'rpz cl block info' => 'Domini bloccati (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>>>> +'rpz cl block' => 'Blacklist personalizzata',
>>>>>>>>> +'rpz cl' => 'Liste personalizzate',
>>>>>>>>> +'rpz exitcode 101' => 'il NOME non è valido',
>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha trovato una configurazione non valida. Dal Terminale esegui il comando unbound-checkconf per maggiori informazioni',
>>>>>>>>> +'rpz exitcode 103' => 'l\'elenco consentiti/bloccati è vuoto',
>>>>>>>>> +'rpz exitcode 104' => 'duplicato - NAME esiste di già',
>>>>>>>>> +'rpz exitcode 105' => 'l\'URL non è valido',
>>>>>>>>> +'rpz exitcode 106' => 'non è possibile rimuovere il NOME non esiste',
>>>>>>>>> +'rpz exitcode 107' => 'il NOME non è valido - solo "consenti" o "blocca"',
>>>>>>>>> +'rpz exitcode 108' => 'parametro mancante o non corretto',
>>>>>>>>> +'rpz exitcode 109' => 'ricaricamento del controllo non associato non riuscito',
>>>>>>>>> +'rpz exitcode 110' => 'la Whitelist/Blacklist personalizzata contiene voci non valide',
>>>>>>>>> +'rpz exitcode 201' => 'l"OSSERVAZIONE non è valida',
>>>>>>>>> +'rpz exitcode 202' => 'voce non valida nella Whitelist, riga ',
>>>>>>>>> +'rpz exitcode 203' => 'voce non valida nella Blacklist, riga ',
>>>>>>>>> +'rpz exitcode 204' => 'La voce selezionata non esiste: ',
>>>>>>>>> +'rpz zf editor' => 'Modifica la voce dei file di zona',
>>>>>>>>> +'rpz zf imported' => '(importato da rpz-config)',
>>>>>>>>> +'rpz zf remark info' => 'I caratteri validi sono a-z, A-Z, 0-9 e trattino basso',
>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>> +);
>>>>>>>>> diff --git a/config/rpz/rpz.tr.pl b/config/rpz/rpz.tr.pl
>>>>>>>>> new file mode 100644
>>>>>>>>> index 000000000..00226e192
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/config/rpz/rpz.tr.pl
>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>> +# I�in eklendi Ayriyeten Response Policy Zone (RPZ)
>>>>>>>>> +%tr = (%tr,
>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>> +'rpz apply' => 'Uygulamak',
>>>>>>>>> +'rpz cl allow enable' => '�zel Etkinlestir allowlist:',
>>>>>>>>> +'rpz cl allow info' => 'Izin verilmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>>>> +'rpz cl allow' => 'Etkinlestir allowlist',
>>>>>>>>> +'rpz cl block enable' => '�zel Etkinlestir blocklist:',
>>>>>>>>> +'rpz cl block info' => 'Engellenmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>>>> +'rpz cl block' => 'Engellenmis blocklist',
>>>>>>>>> +'rpz cl' => 'Engellenmis lists',
>>>>>>>>> +'rpz exitcode 101' => 'NAME ge�erli degil',
>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ge�ersiz yapilandirma bulundu. Terminalde daha fazla bilgi i�in Unbound-checkConf komutunu �alistirin',
>>>>>>>>> +'rpz exitcode 103' => 'allow/block liste bos',
>>>>>>>>> +'rpz exitcode 104' => 'kopyalamak - NAME zaten var',
>>>>>>>>> +'rpz exitcode 105' => 'URL ge�erli degil',
>>>>>>>>> +'rpz exitcode 106' => '�ikarilamiyor NAME yok',
>>>>>>>>> +'rpz exitcode 107' => 'NAME ge�erli degil - "allow" veya "block" yalniz',
>>>>>>>>> +'rpz exitcode 108' => 'Parametre eksik veya yanlis',
>>>>>>>>> +'rpz exitcode 109' => 'unbound-control basarisiz',
>>>>>>>>> +'rpz exitcode 110' => 'Engellenmis Allowlist/Blocklist ge�ersiz girisler i�erir',
>>>>>>>>> +'rpz exitcode 201' => 'REMARK ge�erli degil',
>>>>>>>>> +'rpz exitcode 202' => 'Ge�ersiz giris allowlist, line ',
>>>>>>>>> +'rpz exitcode 203' => 'Ge�ersiz giris blocklist, line ',
>>>>>>>>> +'rpz exitcode 204' => 'Se�ilen giris yok: ',
>>>>>>>>> +'rpz zf editor' => 'yazimlamak zonefiles giris',
>>>>>>>>> +'rpz zf imported' => '(ithal edildi rpz-config)',
>>>>>>>>> +'rpz zf remark info' => 'Ge�erli karakterler a-z, A-Z, 0-9ve alt�st.',
>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>> +);
>>>>>>>>> diff --git a/html/cgi-bin/rpz.cgi b/html/cgi-bin/rpz.cgi
>>>>>>>>> new file mode 100644
>>>>>>>>> index 000000000..a821c92ac
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/html/cgi-bin/rpz.cgi
>>>>>>>>> @@ -0,0 +1,923 @@
>>>>>>>>> +#!/usr/bin/perl
>>>>>>>>> +###############################################################################
>>>>>>>>> +# #
>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>> +# Copyright (C) 2005-2024 IPFire Team <info@ipfire.org> #
>>>>>>>>> +# #
>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>> +# #
>>>>>>>>> +###############################################################################
>>>>>>>>> +
>>>>>>>>> +use strict;
>>>>>>>>> +use Scalar::Util qw(looks_like_number);
>>>>>>>>> +
>>>>>>>>> +# debugging
>>>>>>>>> +#use warnings;
>>>>>>>>> +#use CGI::Carp 'fatalsToBrowser';
>>>>>>>>> +#use Data::Dumper;
>>>>>>>>> +
>>>>>>>>> +require '/var/ipfire/general-functions.pl';
>>>>>>>>> +require "${General::swroot}/lang.pl";
>>>>>>>>> +require "${General::swroot}/header.pl";
>>>>>>>>> +
>>>>>>>>> +###--- Extra HTML ---###
>>>>>>>>> +my $extraHead = <<END
>>>>>>>>> +<style>
>>>>>>>>> + /* alternating row background */
>>>>>>>>> + .tbl tr:nth-child(2n+2) {
>>>>>>>>> + background-color: var(--color-light-grey);
>>>>>>>>> + }
>>>>>>>>> + .tbl tr:nth-child(2n+3) {
>>>>>>>>> + background-color: var(--color-grey);
>>>>>>>>> + }
>>>>>>>>> + /* text styles */
>>>>>>>>> + .tbl th:not(:last-child) {
>>>>>>>>> + text-align: left;
>>>>>>>>> + }
>>>>>>>>> + div.right {
>>>>>>>>> + text-align: right;
>>>>>>>>> + margin-top: 0.5em;
>>>>>>>>> + }
>>>>>>>>> + /* customlist input */
>>>>>>>>> + textarea.domainlist {
>>>>>>>>> + margin: 0.5em 0;
>>>>>>>>> + resize: vertical;
>>>>>>>>> + min-height: 10em;
>>>>>>>>> + overflow: auto;
>>>>>>>>> + white-space: pre;
>>>>>>>>> + }
>>>>>>>>> + button[type=submit]:disabled {
>>>>>>>>> + opacity: 0.6;
>>>>>>>>> + }
>>>>>>>>> +</style>
>>>>>>>>> +END
>>>>>>>>> +;
>>>>>>>>> +###--- End of extra HTML ---###
>>>>>>>>> +
>>>>>>>>> +
>>>>>>>>> +### Settings ###
>>>>>>>>> +
>>>>>>>>> +# Request DNS service reload after configuration change
>>>>>>>>> +my $RPZ_RELOAD_FLAG = "${General::swroot}/dns/rpz/reload.flag";
>>>>>>>>> +
>>>>>>>>> +# Configuration file for all available zonefiles
>>>>>>>>> +# Format: index, name (unique), enabled (on/off), URL, remark
>>>>>>>>> +my $ZONEFILES_CONF = "${General::swroot}/dns/rpz/zonefiles.conf";
>>>>>>>>> +
>>>>>>>>> +# Configuration file for custom lists
>>>>>>>>> +# IDs: 0=allowlist, 1=blocklist, 2=options (allow/block enabled)
>>>>>>>>> +my $CUSTOMLISTS_CONF = "${General::swroot}/dns/rpz/customlists.conf";
>>>>>>>>> +
>>>>>>>>> +# Export custom lists to rpz-config
>>>>>>>>> +my $RPZ_ALLOWLIST = "${General::swroot}/dns/rpz/allowlist";
>>>>>>>>> +my $RPZ_BLOCKLIST = "${General::swroot}/dns/rpz/blocklist";
>>>>>>>>> +
>>>>>>>>> +
>>>>>>>>> +### Preparation ###
>>>>>>>>> +
>>>>>>>>> +# Create missing config files
>>>>>>>>> +unless(-f $ZONEFILES_CONF) { &General::system('touch', "$ZONEFILES_CONF"); }
>>>>>>>>> +unless(-f $CUSTOMLISTS_CONF) { &General::system('touch', "$CUSTOMLISTS_CONF"); }
>>>>>>>>> +
>>>>>>>>> +
>>>>>>>>> +## Global gui data
>>>>>>>>> +my $errormessage = "";
>>>>>>>>> +
>>>>>>>>> +## Global configuration data
>>>>>>>>> +my %zonefiles = ();
>>>>>>>>> +my %customlists = ();
>>>>>>>>> +&_zonefiles_load();
>>>>>>>>> +&_customlists_load();
>>>>>>>>> +
>>>>>>>>> +## Global CGI form data
>>>>>>>>> +my %cgiparams = ();
>>>>>>>>> +&Header::getcgihash(\%cgiparams);
>>>>>>>>> +
>>>>>>>>> +my $action = $cgiparams{'ACTION'} // 'NONE';
>>>>>>>>> +my $action_key = $cgiparams{'KEY'} // ''; # entry being edited, empty = none/new
>>>>>>>>> +
>>>>>>>>> +
>>>>>>>>> +###--- Process form actions ---###
>>>>>>>>> +
>>>>>>>>> +# Zonefiles action: Check whether the requested entry exists
>>>>>>>>> +if((substr($action, 0, 3) eq 'ZF_') && ($action_key)) {
>>>>>>>>> + unless(defined $zonefiles{$action_key}) {
>>>>>>>>> + $errormessage = &_rpz_error_tr(204, $action_key);
>>>>>>>>> + $action = 'NONE';
>>>>>>>>> + }
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +## Perform actions
>>>>>>>>> +if($action eq 'ZF_SAVE') { ## Save new or modified zonefiles entry
>>>>>>>>> + if(&_action_zf_save()) {
>>>>>>>>> + $action = 'NONE'; # success, return to main page
>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>> + } else {
>>>>>>>>> + $action = 'ZF_EDIT'; # error occured, keep editing
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> +} elsif($action eq 'ZF_TOGGLE') { ## Toggle on/off
>>>>>>>>> + if(&_action_zf_toggle()) {
>>>>>>>>> + $action = 'NONE';
>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> +} elsif($action eq 'ZF_REMOVE') { ## Remove entry
>>>>>>>>> + if(&_action_zf_remove()) {
>>>>>>>>> + $action = 'NONE';
>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> +} elsif($action eq 'CL_SAVE') { ## Save custom lists
>>>>>>>>> + if(&_action_cl_save()) {
>>>>>>>>> + $action = 'NONE';
>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> +} elsif($action eq 'RPZ_RELOAD') { ## Reload dns configuration
>>>>>>>>> + if(&_action_rpz_reload()) {
>>>>>>>>> + $action = 'NONE';
>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> +} elsif($action eq 'UNB_RESTART') { ## Restart unbound service
>>>>>>>>> + if(&_action_unb_restart()) {
>>>>>>>>> + $action = 'NONE';
>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +
>>>>>>>>> +###--- Start GUI ---###
>>>>>>>>> +
>>>>>>>>> +## Start http output
>>>>>>>>> +&Header::showhttpheaders();
>>>>>>>>> +
>>>>>>>>> +# Start HTML
>>>>>>>>> +&Header::openpage($Lang::tr{'rpz'}, 1, $extraHead);
>>>>>>>>> +&Header::openbigbox('100%', 'left', '');
>>>>>>>>> +
>>>>>>>>> +# Show error messages
>>>>>>>>> +if($errormessage) {
>>>>>>>>> + &_print_message($errormessage);
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Handle zonefile add/edit mode
>>>>>>>>> +if($action eq "ZF_EDIT") {
>>>>>>>>> + &_print_zonefile_editor();
>>>>>>>>> +
>>>>>>>>> + # Finalize page and exit cleanly
>>>>>>>>> + &Header::closebigbox();
>>>>>>>>> + &Header::closepage();
>>>>>>>>> + exit(0);
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Show gui elements
>>>>>>>>> +&_print_zonefiles();
>>>>>>>>> +&_print_customlists();
>>>>>>>>> +&_print_gui_extras();
>>>>>>>>> +
>>>>>>>>> +&Header::closebigbox();
>>>>>>>>> +&Header::closepage();
>>>>>>>>> +
>>>>>>>>> +###--- End of GUI ---###
>>>>>>>>> +
>>>>>>>>> +
>>>>>>>>> +###--- Internal configuration file functions ---###
>>>>>>>>> +
>>>>>>>>> +# Load all available zonefiles from rpz-config and the internal configuration
>>>>>>>>> +sub _zonefiles_load {
>>>>>>>>> + # Clean start
>>>>>>>>> + %zonefiles = ();
>>>>>>>>> +
>>>>>>>>> + # Source 1: Get the currently enabled zonefiles from rpz-config (expected format [name]=[URL])
>>>>>>>>> + my @enabled_files = &General::system_output('/usr/sbin/rpz-config', 'list');
>>>>>>>>> +
>>>>>>>>> + foreach my $row (@enabled_files) {
>>>>>>>>> + chomp($row);
>>>>>>>>> +
>>>>>>>>> + # Use regex instead of split() to skip non-matching lines
>>>>>>>>> + next unless($row =~ /^(\w+)=(.+)$/);
>>>>>>>>> + my ($name, $url) = ($1, $2);
>>>>>>>>> +
>>>>>>>>> + # Unique names are already guaranteed by rpz-config
>>>>>>>>> + if(&_rpz_validate_zonefile($name, $url, '', 0) == 0) {
>>>>>>>>> + # Populate global data hash, mark all found entries as enabled
>>>>>>>>> + my %entry = ('enabled' => 'on',
>>>>>>>>> + 'url' => $url,
>>>>>>>>> + 'remark' => $Lang::tr{'rpz zf imported'});
>>>>>>>>> +
>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>> + }
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + # Source 2: Get additional data and disabled entries from configuration file
>>>>>>>>> + my %configured_files = ();
>>>>>>>>> + &General::readhasharray($ZONEFILES_CONF, \%configured_files);
>>>>>>>>> +
>>>>>>>>> + foreach my $row (values (%configured_files)) {
>>>>>>>>> + my ($name, $enabled, $url, $remark) = @$row;
>>>>>>>>> + $remark //= "";
>>>>>>>>> +
>>>>>>>>> + next unless($name);
>>>>>>>>> +
>>>>>>>>> + # Check whether this row belongs to an entry already imported from rpz-config
>>>>>>>>> + if(defined $zonefiles{$name}) {
>>>>>>>>> + # Existing entry, only merge additional data
>>>>>>>>> + $zonefiles{$name}{'remark'} = $remark;
>>>>>>>>> + } else {
>>>>>>>>> + # Skip entry if it is marked as enabled but not found by rpz-config. It was then deleted manually
>>>>>>>>> + if($enabled ne 'on') {
>>>>>>>>> + # Populate global data hash
>>>>>>>>> + my %entry = ('enabled' => 'off',
>>>>>>>>> + 'url' => $url // "",
>>>>>>>>> + 'remark' => $remark);
>>>>>>>>> +
>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>> + }
>>>>>>>>> + }
>>>>>>>>> + }
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Save internal zonefiles configuration
>>>>>>>>> +sub _zonefiles_save_conf {
>>>>>>>>> + my $index = 0;
>>>>>>>>> + my %export = ();
>>>>>>>>> +
>>>>>>>>> + # Loop trough all zonefiles and create "hasharray" type export
>>>>>>>>> + foreach my $name (keys %zonefiles) {
>>>>>>>>> + my @entry = ($name,
>>>>>>>>> + $zonefiles{$name}{'enabled'},
>>>>>>>>> + $zonefiles{$name}{'url'},
>>>>>>>>> + $zonefiles{$name}{'remark'});
>>>>>>>>> +
>>>>>>>>> + $export{$index++} = \@entry;
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + &General::writehasharray($ZONEFILES_CONF, \%export);
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Load custom lists from rpz-config and the internal configuration
>>>>>>>>> +sub _customlists_load {
>>>>>>>>> + # Clean start
>>>>>>>>> + %customlists = ();
>>>>>>>>> +
>>>>>>>>> + # Load configuration file
>>>>>>>>> + my %lists_conf = ();
>>>>>>>>> + &General::readhasharray($CUSTOMLISTS_CONF, \%lists_conf);
>>>>>>>>> +
>>>>>>>>> + # Get list options, enabled by default to start import
>>>>>>>>> + $customlists{'allow'}{'enabled'} = $lists_conf{2}[0] // 'on';
>>>>>>>>> + $customlists{'block'}{'enabled'} = $lists_conf{2}[1] // 'on';
>>>>>>>>> +
>>>>>>>>> + # Import enabled list from rpz-config, otherwise retrieve stored or empty list from configuration file
>>>>>>>>> + if($customlists{'allow'}{'enabled'} eq 'on') {
>>>>>>>>> + &_customlist_import('allow', $RPZ_ALLOWLIST);
>>>>>>>>> + } else {
>>>>>>>>> + $customlists{'allow'}{'list'} = $lists_conf{0} // [];
>>>>>>>>> + }
>>>>>>>>> + if($customlists{'block'}{'enabled'} eq 'on') {
>>>>>>>>> + &_customlist_import('block', $RPZ_BLOCKLIST);
>>>>>>>>> + } else {
>>>>>>>>> + $customlists{'block'}{'list'} = $lists_conf{1} // [];
>>>>>>>>> + }
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Save internal custom lists configuration
>>>>>>>>> +sub _customlists_save_conf {
>>>>>>>>> + my %export = ();
>>>>>>>>> +
>>>>>>>>> + # Match IDs with import function
>>>>>>>>> + $export{0} = $customlists{'allow'}{'list'};
>>>>>>>>> + $export{1} = $customlists{'block'}{'list'};
>>>>>>>>> + $export{2} = [$customlists{'allow'}{'enabled'}, $customlists{'block'}{'enabled'}];
>>>>>>>>> +
>>>>>>>>> + &General::writehasharray($CUSTOMLISTS_CONF, \%export);
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Import a custom list from plain file, returns empty list if file is missing
>>>>>>>>> +sub _customlist_import {
>>>>>>>>> + my ($listname, $filename) = @_;
>>>>>>>>> + my @list = ();
>>>>>>>>> +
>>>>>>>>> + # File exists, load and check all lines
>>>>>>>>> + if(-f $filename) {
>>>>>>>>> + open(my $FH, '<', $filename) or die "Can't read $filename: $!";
>>>>>>>>> + while(my $line = <$FH>) {
>>>>>>>>> + chomp($line);
>>>>>>>>> + push(@list, $line);
>>>>>>>>> + }
>>>>>>>>> + close($FH);
>>>>>>>>> +
>>>>>>>>> + # Clean up imported data
>>>>>>>>> + &_rpz_validate_customlist(\@list, 1);
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + $customlists{$listname}{'list'} = \@list;
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Export a custom list to plain file or clear file if list is disabled
>>>>>>>>> +sub _customlist_export {
>>>>>>>>> + my ($listname, $filename) = @_;
>>>>>>>>> + return unless(defined $customlists{$listname});
>>>>>>>>> +
>>>>>>>>> + # Write enabled domain list to file, otherwise save empty file
>>>>>>>>> + open(my $FH, '>', $filename) or die "Can't write $filename: $!";
>>>>>>>>> +
>>>>>>>>> + if($customlists{$listname}{'enabled'} eq 'on') {
>>>>>>>>> + foreach my $line (@{$customlists{$listname}{'list'}}) {
>>>>>>>>> + print $FH "$line\n";
>>>>>>>>> + }
>>>>>>>>> + } else {
>>>>>>>>> + print $FH "; Note: This list is currently disabled by $ENV{'SCRIPT_NAME'}\n";
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + close($FH);
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +
>>>>>>>>> +###--- Internal gui functions ---###
>>>>>>>>> +
>>>>>>>>> +# Show simple message box
>>>>>>>>> +sub _print_message {
>>>>>>>>> + my ($message, $title) = @_;
>>>>>>>>> + $title ||= $Lang::tr{'error messages'};
>>>>>>>>> +
>>>>>>>>> + &Header::openbox('100%', 'left', $title);
>>>>>>>>> + print "<span>$message</span>";
>>>>>>>>> + &Header::closebox();
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Show all zone files and related gui elements
>>>>>>>>> +sub _print_zonefiles {
>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf'});
>>>>>>>>> +
>>>>>>>>> + print <<END
>>>>>>>>> +<table class="tbl" width="100%">
>>>>>>>>> + <tr>
>>>>>>>>> + <th>$Lang::tr{'name'}</th>
>>>>>>>>> + <th>URL</th>
>>>>>>>>> + <th>$Lang::tr{'remark'}</th>
>>>>>>>>> + <th colspan="3">$Lang::tr{'action'}</th>
>>>>>>>>> + </tr>
>>>>>>>>> +END
>>>>>>>>> +;
>>>>>>>>> +
>>>>>>>>> + # Sort zonefiles by name and loop trough all entries
>>>>>>>>> + foreach my $name (sort keys %zonefiles) {
>>>>>>>>> +
>>>>>>>>> + # Toggle button label translation
>>>>>>>>> + my $toggle_tr = ($zonefiles{$name}{'enabled'} eq 'on') ? $Lang::tr{'click to disable'} : $Lang::tr{'click to enable'};
>>>>>>>>> +
>>>>>>>>> + print <<END
>>>>>>>>> + <tr>
>>>>>>>>> + <td>$name</td>
>>>>>>>>> + <td>$zonefiles{$name}{'url'}</td>
>>>>>>>>> + <td>$zonefiles{$name}{'remark'}</td>
>>>>>>>>> +
>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_TOGGLE">
>>>>>>>>> + <input type="image" src="/images/$zonefiles{$name}{'enabled'}.gif" title="$toggle_tr" alt="$toggle_tr">
>>>>>>>>> + </form>
>>>>>>>>> + </td>
>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_EDIT">
>>>>>>>>> + <input type="image" src="/images/edit.gif" title="$Lang::tr{'edit'}" alt="$Lang::tr{'edit'}">
>>>>>>>>> + </form>
>>>>>>>>> + </td>
>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_REMOVE">
>>>>>>>>> + <input type="image" src="/images/delete.gif" title="$Lang::tr{'remove'}" alt="$Lang::tr{'remove'}">
>>>>>>>>> + </form>
>>>>>>>>> + </td>
>>>>>>>>> + </tr>
>>>>>>>>> +END
>>>>>>>>> +;
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + # Disable reload button if not needed
>>>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>>>> +
>>>>>>>>> + print <<END
>>>>>>>>> +</table>
>>>>>>>>> +
>>>>>>>>> +<div class="right">
>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>> + <input type="hidden" name="KEY" value="">
>>>>>>>>> + <button type="submit" name="ACTION" value="ZF_EDIT">$Lang::tr{'add'}</button>
>>>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>>>> + </form>
>>>>>>>>> +</div>
>>>>>>>>> +END
>>>>>>>>> +;
>>>>>>>>> +
>>>>>>>>> + &Header::closebox();
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Show zonefiles entry editor
>>>>>>>>> +sub _print_zonefile_editor {
>>>>>>>>> +
>>>>>>>>> + # Key specified: Edit existing entry
>>>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>>>> + # Load data to be edited, but don't override already present values (allows user to edit after error)
>>>>>>>>> + $cgiparams{'ZF_NAME'} //= $action_key;
>>>>>>>>> + $cgiparams{'ZF_URL'} //= $zonefiles{$action_key}{'url'};
>>>>>>>>> + $cgiparams{'ZF_REMARK'} //= $zonefiles{$action_key}{'remark'};
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + # Fallback to empty form
>>>>>>>>> + $cgiparams{'ZF_NAME'} //= "";
>>>>>>>>> + $cgiparams{'ZF_URL'} //= "";
>>>>>>>>> + $cgiparams{'ZF_REMARK'} //= "";
>>>>>>>>> +
>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf editor'});
>>>>>>>>> +
>>>>>>>>> + print <<END
>>>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>> +<input type="hidden" name="KEY" value="$action_key">
>>>>>>>>> +<table width="100%">
>>>>>>>>> + <tr>
>>>>>>>>> + <td width="20%">$Lang::tr{'name'}: <img src="/blob.gif" alt="*"></td>
>>>>>>>>> + <td><input type="text" name="ZF_NAME" value="$cgiparams{'ZF_NAME'}" size="40" maxlength="32" title="$Lang::tr{'rpz zf remark info'}" pattern="[a-zA-Z0-9_]{1,32}" required></td>
>>>>>>>>> + </tr>
>>>>>>>>> + <tr>
>>>>>>>>> + <td width="20%">URL: <img src="/blob.gif" alt="*"></td>
>>>>>>>>> + <td><input type="url" name="ZF_URL" value="$cgiparams{'ZF_URL'}" size="40" maxlength="128" required></td>
>>>>>>>>> + </tr>
>>>>>>>>> + <tr>
>>>>>>>>> + <td width="20%">$Lang::tr{'remark'}:</td>
>>>>>>>>> + <td><input type="text" name="ZF_REMARK" value="$cgiparams{'ZF_REMARK'}" size="40" maxlength="32"></td>
>>>>>>>>> + </tr>
>>>>>>>>> + <tr>
>>>>>>>>> + <td colspan="2"><hr></td>
>>>>>>>>> + </tr>
>>>>>>>>> + <tr>
>>>>>>>>> + <td width="55%"><img src="/blob.gif" alt="*"> $Lang::tr{'required field'}</td>
>>>>>>>>> + <td align="right"><button type="submit" name="ACTION" value="ZF_SAVE">$Lang::tr{'save'}</button></td>
>>>>>>>>> + </tr>
>>>>>>>>> +</table>
>>>>>>>>> +</form>
>>>>>>>>> +
>>>>>>>>> +<div class="right">
>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>> + <button type="submit" name="ACTION" value="NONE">$Lang::tr{'back'}</button>
>>>>>>>>> + </form>
>>>>>>>>> +</div>
>>>>>>>>> +END
>>>>>>>>> +;
>>>>>>>>> +
>>>>>>>>> + &Header::closebox();
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Show custom allow/block files and related gui elements
>>>>>>>>> +sub _print_customlists {
>>>>>>>>> +
>>>>>>>>> + # Load lists from config, unless they are currently being edited
>>>>>>>>> + if($action ne 'CL_SAVE') {
>>>>>>>>> + $cgiparams{'ALLOW_LIST'} = join("\n", @{$customlists{'allow'}{'list'}});
>>>>>>>>> + $cgiparams{'BLOCK_LIST'} = join("\n", @{$customlists{'block'}{'list'}});
>>>>>>>>> +
>>>>>>>>> + $cgiparams{'ALLOW_ENABLED'} = ($customlists{'allow'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>>>> + $cgiparams{'BLOCK_ENABLED'} = ($customlists{'block'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + # Fallback to empty form
>>>>>>>>> + $cgiparams{'ALLOW_LIST'} //= "";
>>>>>>>>> + $cgiparams{'BLOCK_LIST'} //= "";
>>>>>>>>> +
>>>>>>>>> + # HTML checkboxes, unchecked = no or undef value in POST data
>>>>>>>>> + my %checked = ();
>>>>>>>>> + $checked{'ALLOW_ENABLED'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? " checked" : "";
>>>>>>>>> + $checked{'BLOCK_ENABLED'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? " checked" : "";
>>>>>>>>> +
>>>>>>>>> + # Disable reload button if not needed
>>>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>>>> +
>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz cl'});
>>>>>>>>> +
>>>>>>>>> + print <<END
>>>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>> +<table width="100%">
>>>>>>>>> + <tr>
>>>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl allow'}</b><br>$Lang::tr{'rpz cl allow info'}</td>
>>>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl block'}</b><br>$Lang::tr{'rpz cl block info'}</td>
>>>>>>>>> + </tr>
>>>>>>>>> + <tr>
>>>>>>>>> + <td colspan="2"><textarea name="ALLOW_LIST" class="domainlist" cols="45">
>>>>>>>>> +$cgiparams{'ALLOW_LIST'}</textarea></td>
>>>>>>>>> + <td colspan="2"><textarea name="BLOCK_LIST" class="domainlist" cols="45">
>>>>>>>>> +$cgiparams{'BLOCK_LIST'}</textarea></td>
>>>>>>>>> + </tr>
>>>>>>>>> + <tr>
>>>>>>>>> + <td><label for="allow_enabled">$Lang::tr{'rpz cl allow enable'}</label></td>
>>>>>>>>> + <td width="15%"><input type="checkbox" name="ALLOW_ENABLED" id="allow_enabled"$checked{'ALLOW_ENABLED'}></td>
>>>>>>>>> + <td><label for="block_enabled">$Lang::tr{'rpz cl block enable'}</label></td>
>>>>>>>>> + <td width="15%"><input type="checkbox" name="BLOCK_ENABLED" id="block_enabled"$checked{'BLOCK_ENABLED'}></td>
>>>>>>>>> + </tr>
>>>>>>>>> + <tr>
>>>>>>>>> + <td colspan="4"><hr></td>
>>>>>>>>> + </tr>
>>>>>>>>> + <tr>
>>>>>>>>> + <td align="right" colspan="4">
>>>>>>>>> + <button type="submit" name="ACTION" value="CL_SAVE">$Lang::tr{'save'}</button>
>>>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>>>> + </td>
>>>>>>>>> +</table>
>>>>>>>>> +</form>
>>>>>>>>> +END
>>>>>>>>> +;
>>>>>>>>> +
>>>>>>>>> + &Header::closebox();
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Output javascript and extra gui elements
>>>>>>>>> +sub _print_gui_extras {
>>>>>>>>> +
>>>>>>>>> + # Apply/Restart button modifier key handler
>>>>>>>>> + if(&_rpz_needs_reload()) {
>>>>>>>>> + print <<END
>>>>>>>>> +<script>
>>>>>>>>> + // Commit modifier key handler
>>>>>>>>> + (function(jq, document) {
>>>>>>>>> + var keyEventsOn = false; // Keyboard events attached
>>>>>>>>> + var keyModify = false; // Modifier key pressed
>>>>>>>>> + var mouseHover = false; // Mouse over commit button
>>>>>>>>> + var btnModified = false; // Button modified to "Restart"
>>>>>>>>> +
>>>>>>>>> + // Document-level key events, enable only while cursor is over button
>>>>>>>>> + function attachKeyEvents() {
>>>>>>>>> + if(keyEventsOn) {
>>>>>>>>> + return;
>>>>>>>>> + }
>>>>>>>>> + keyEventsOn = true;
>>>>>>>>> +
>>>>>>>>> + jq(document).on("keydown.rpz", function(event) {
>>>>>>>>> + if((!keyModify) && event.shiftKey) {
>>>>>>>>> + keyModify = true;
>>>>>>>>> + handleModify();
>>>>>>>>> + }
>>>>>>>>> + });
>>>>>>>>> + jq(document).on("keyup.rpz", function(event) {
>>>>>>>>> + if(keyModify && (!event.shiftKey)) {
>>>>>>>>> + keyModify = false;
>>>>>>>>> + handleModify();
>>>>>>>>> + }
>>>>>>>>> + });
>>>>>>>>> + }
>>>>>>>>> + function removeKeyEvents() {
>>>>>>>>> + keyModify = false;
>>>>>>>>> + if(keyEventsOn) {
>>>>>>>>> + jq(document).off("keydown.rpz keyup.rpz");
>>>>>>>>> + keyEventsOn = false;
>>>>>>>>> + }
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + // Attach mouse hover events to commit buttons
>>>>>>>>> + function attachMouseEvents() {
>>>>>>>>> + jq("button.commit").on("mouseenter", function(event) {
>>>>>>>>> + if(!mouseHover) {
>>>>>>>>> + mouseHover = true;
>>>>>>>>> + attachKeyEvents();
>>>>>>>>> + // Handle already pressed key
>>>>>>>>> + keyModify = !!(event.shiftKey);
>>>>>>>>> + handleModify();
>>>>>>>>> + }
>>>>>>>>> + });
>>>>>>>>> +
>>>>>>>>> + // Cursor moved away: Disable key listener to minimize events
>>>>>>>>> + jq("button.commit").on("mouseleave", function() {
>>>>>>>>> + if(mouseHover) {
>>>>>>>>> + mouseHover = false;
>>>>>>>>> + removeKeyEvents();
>>>>>>>>> + handleModify();
>>>>>>>>> + }
>>>>>>>>> + });
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + // Modify commit button
>>>>>>>>> + function handleModify() {
>>>>>>>>> + let modify = mouseHover && keyModify;
>>>>>>>>> + if(btnModified != modify) {
>>>>>>>>> + if(modify) {
>>>>>>>>> + jq("button.commit").text("$Lang::tr{'restart'}").val("UNB_RESTART");
>>>>>>>>> + } else {
>>>>>>>>> + jq("button.commit").text("$Lang::tr{'rpz apply'}").val("RPZ_RELOAD");
>>>>>>>>> + }
>>>>>>>>> + btnModified = modify;
>>>>>>>>> + }
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + // jQuery DOM ready
>>>>>>>>> + jq(function() {
>>>>>>>>> + attachMouseEvents();
>>>>>>>>> + });
>>>>>>>>> + })(jQuery, document);
>>>>>>>>> +</script>
>>>>>>>>> +END
>>>>>>>>> +;
>>>>>>>>> + } # End of modifier key handler
>>>>>>>>> +
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +
>>>>>>>>> +###--- Internal action processing functions ---###
>>>>>>>>> +
>>>>>>>>> +# Toggle zonefile on/off
>>>>>>>>> +sub _action_zf_toggle {
>>>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>>>> +
>>>>>>>>> + my $result = 0;
>>>>>>>>> + my $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>>>> +
>>>>>>>>> + # Perform toggle action
>>>>>>>>> + if($enabled eq 'on') {
>>>>>>>>> + $enabled = 'off';
>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>>>> + } else {
>>>>>>>>> + $enabled = 'on';
>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $action_key, $zonefiles{$action_key}{'url'}, '--no-reload');
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>> +
>>>>>>>>> + # Save changes
>>>>>>>>> + $zonefiles{$action_key}{'enabled'} = $enabled;
>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>> +
>>>>>>>>> + return 1;
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Remove zonefile
>>>>>>>>> +sub _action_zf_remove {
>>>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>>>> +
>>>>>>>>> + # Remove from rpz-config if currently active
>>>>>>>>> + if($zonefiles{$action_key}{'enabled'} eq 'on') {
>>>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>>>> +
>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + # Remove from data hash and save changes
>>>>>>>>> + delete $zonefiles{$action_key};
>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>> +
>>>>>>>>> + # Clear action_key, as the entry is now removed entirely
>>>>>>>>> + $action_key = "";
>>>>>>>>> +
>>>>>>>>> + return 1;
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Create or update zonefile entry
>>>>>>>>> +# Returns undef if gui needs to stay in editor mode
>>>>>>>>> +sub _action_zf_save {
>>>>>>>>> + my $result = 0;
>>>>>>>>> +
>>>>>>>>> + my $name = $cgiparams{'ZF_NAME'} // "";
>>>>>>>>> + my $url = $cgiparams{'ZF_URL'} // "";
>>>>>>>>> + my $remark = $cgiparams{'ZF_REMARK'} // "";
>>>>>>>>> + my $enabled = 'on'; # Enable new entries by default
>>>>>>>>> +
>>>>>>>>> + # Note on variables:
>>>>>>>>> + # name = unique key, will be used to address the entry
>>>>>>>>> + # action_key = name of the entry being edited, empty for new entry
>>>>>>>>> +
>>>>>>>>> + # Only check for unique name if it changed
>>>>>>>>> + # (this also checks new entries because the action_key is empty in this case)
>>>>>>>>> + $result = &_rpz_validate_zonefile($name, $url, $remark, (lc($name) ne lc($action_key)));
>>>>>>>>> + return unless &_rpz_check_result($result, 0);
>>>>>>>>> +
>>>>>>>>> + # Edit existing entry: Determine what was changed
>>>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>>>> + # Name und URL remain unchanged, only save remark and finish
>>>>>>>>> + if(($name eq $action_key) && ($url eq $zonefiles{$action_key}{'url'})) {
>>>>>>>>> + $zonefiles{$action_key}{'remark'} = $remark;
>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>> +
>>>>>>>>> + return 1;
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + # Entry was changed and needs to be recreated, preserve status
>>>>>>>>> + $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>>>> +
>>>>>>>>> + # Remove from rpz-config
>>>>>>>>> + return unless &_action_zf_remove();
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + # Add new entry to rpz-config
>>>>>>>>> + if($enabled eq 'on') {
>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $name, $url, '--no-reload');
>>>>>>>>> +
>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + # Add to global data hash and save changes
>>>>>>>>> + my %entry = ('enabled' => $enabled,
>>>>>>>>> + 'url' => $url,
>>>>>>>>> + 'remark' => $remark);
>>>>>>>>> +
>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>> +
>>>>>>>>> + return 1;
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Save custom lists
>>>>>>>>> +sub _action_cl_save {
>>>>>>>>> + return unless((defined $cgiparams{'ALLOW_LIST'}) && (defined $cgiparams{'BLOCK_LIST'}));
>>>>>>>>> +
>>>>>>>>> + my $result = 0;
>>>>>>>>> +
>>>>>>>>> + my @allowlist = split(/\R/, $cgiparams{'ALLOW_LIST'});
>>>>>>>>> + my @blocklist = split(/\R/, $cgiparams{'BLOCK_LIST'});
>>>>>>>>> +
>>>>>>>>> + # Validate lists
>>>>>>>>> + $result = &_rpz_validate_customlist(\@allowlist);
>>>>>>>>> + if($result != 0) {
>>>>>>>>> + $errormessage = &_rpz_error_tr(202, $result);
>>>>>>>>> + return;
>>>>>>>>> + }
>>>>>>>>> + $result = &_rpz_validate_customlist(\@blocklist);
>>>>>>>>> + if($result != 0) {
>>>>>>>>> + $errormessage = &_rpz_error_tr(203, $result);
>>>>>>>>> + return;
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + # Add to global data hash and save changes
>>>>>>>>> + $customlists{'allow'}{'list'} = \@allowlist;
>>>>>>>>> + $customlists{'block'}{'list'} = \@blocklist;
>>>>>>>>> + $customlists{'allow'}{'enabled'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? 'on' : 'off';
>>>>>>>>> + $customlists{'block'}{'enabled'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? 'on' : 'off';
>>>>>>>>> +
>>>>>>>>> + &_customlists_save_conf();
>>>>>>>>> + &_customlist_export('allow', $RPZ_ALLOWLIST);
>>>>>>>>> + &_customlist_export('block', $RPZ_BLOCKLIST);
>>>>>>>>> +
>>>>>>>>> + # Make new lists, request service reload on success
>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-make', 'allowblock', '--no-reload');
>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>> +
>>>>>>>>> + return 1;
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Trigger rpz-config reload
>>>>>>>>> +sub _action_rpz_reload {
>>>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>>>> +
>>>>>>>>> + # Immediately clear flag to prevent multiple reloads
>>>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + # Perform reload, recreate reload flag on error to enable retry
>>>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'reload');
>>>>>>>>> + if(not &_rpz_check_result($result, 0)) {
>>>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>>>> + return;
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + return 1;
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Trigger unbound restart
>>>>>>>>> +sub _action_unb_restart {
>>>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>>>> +
>>>>>>>>> + # Immediately clear flag to prevent multiple restarts
>>>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + # Perform restart, unboundctrl always exits zero
>>>>>>>>> + &General::system('/usr/local/bin/unboundctrl', 'restart');
>>>>>>>>> +
>>>>>>>>> + return 1;
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +
>>>>>>>>> +###--- Internal rpz-config functions ---###
>>>>>>>>> +
>>>>>>>>> +# Translate rpz-config exitcodes and messages
>>>>>>>>> +# 100-199: rpz-config, 200-299: webgui
>>>>>>>>> +sub _rpz_error_tr {
>>>>>>>>> + my ($error, $append) = @_;
>>>>>>>>> + $append //= '';
>>>>>>>>> +
>>>>>>>>> + # Translate numeric exit codes
>>>>>>>>> + if(looks_like_number($error)) {
>>>>>>>>> + if(defined $Lang::tr{"rpz exitcode $error"}) {
>>>>>>>>> + $error = $Lang::tr{"rpz exitcode $error"};
>>>>>>>>> + }
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + return "RPZ $Lang::tr{'error'}: $error" . &Header::escape($append);
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Check result of rpz-config system call, request reload on success
>>>>>>>>> +sub _rpz_check_result {
>>>>>>>>> + my ($result, $request_reload) = @_;
>>>>>>>>> + $request_reload //= 0;
>>>>>>>>> +
>>>>>>>>> + # exitcode 0 = success
>>>>>>>>> + if($result != 0) {
>>>>>>>>> + $errormessage = &_rpz_error_tr($result);
>>>>>>>>> + return;
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + # Set reload flag
>>>>>>>>> + if($request_reload) {
>>>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + return 1;
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Test whether reload flag is set
>>>>>>>>> +sub _rpz_needs_reload {
>>>>>>>>> + return (-f $RPZ_RELOAD_FLAG);
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Validate a zonefile entry, returns rpz-config exitcode on failure. Use _rpz_check_result to verify.
>>>>>>>>> +# unique = check for unique name
>>>>>>>>> +sub _rpz_validate_zonefile {
>>>>>>>>> + my ($name, $url, $remark, $unique) = @_;
>>>>>>>>> + $unique //= 1;
>>>>>>>>> +
>>>>>>>>> + unless($name =~ /^[a-zA-Z0-9_]{1,32}$/) {
>>>>>>>>> + return 101;
>>>>>>>>> + }
>>>>>>>>> + unless($url =~ /^[\w+\.:;\/\\&@#%?=\-~|!]{1,128}$/) {
>>>>>>>>> + return 105;
>>>>>>>>> + }
>>>>>>>>> + unless($remark =~ /^[\w \-()\.:;*\/\\?!&=]{0,32}$/) {
>>>>>>>>> + return 201;
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + # Check against already existing names
>>>>>>>>> + if($unique) {
>>>>>>>>> + foreach my $existing (keys %zonefiles) {
>>>>>>>>> + if(lc($name) eq lc($existing)) {
>>>>>>>>> + return 104;
>>>>>>>>> + }
>>>>>>>>> + }
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + return 0;
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Validate a custom list, returns number of rejected line on failure. Check for non-zero results.
>>>>>>>>> +# listref = array reference, cleanup = remove invalid entries instead of returning an error
>>>>>>>>> +sub _rpz_validate_customlist {
>>>>>>>>> + my ($listref, $cleanup) = @_;
>>>>>>>>> + $cleanup //= 0;
>>>>>>>>> +
>>>>>>>>> + foreach my $index (reverse 0..$#{$listref}) {
>>>>>>>>> + my $row = @$listref[$index];
>>>>>>>>> + next unless($row); # Skip/allow empty lines
>>>>>>>>> +
>>>>>>>>> + # Reject/remove everything besides wildcard domains and remarks
>>>>>>>>> + if((not &General::validwildcarddomainname($row)) && (not $row =~ /^;[\w \-()\.:;*\/\\?!&=]*$/)) {
>>>>>>>>> + unless($cleanup) {
>>>>>>>>> + # +1 for user friendly line number and to ensure non-zero exitcode
>>>>>>>>> + return $index + 1;
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + # Remove current row
>>>>>>>>> + splice(@$listref, $index, 1);
>>>>>>>>> + }
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + return 0;
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +
>>>>>>>>> +###--- Internal misc functions ---###
>>>>>>>>> +
>>>>>>>>> +# Send HTTP 303 redirect headers for post/request/get pattern
>>>>>>>>> +# (Must be sent before calling &Header::showhttpheaders())
>>>>>>>>> +sub _http_prg_redirect {
>>>>>>>>> + my $location = "https://$ENV{'SERVER_NAME'}:$ENV{'SERVER_PORT'}$ENV{'SCRIPT_NAME'}";
>>>>>>>>> + print "Status: 303 See Other\n";
>>>>>>>>> + print "Location: $location\n";
>>>>>>>>> +}
>>>>>>>>> diff --git a/lfs/rpz b/lfs/rpz
>>>>>>>>> new file mode 100644
>>>>>>>>> index 000000000..7ddbc38e5
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/lfs/rpz
>>>>>>>>> @@ -0,0 +1,96 @@
>>>>>>>>> +###############################################################################
>>>>>>>>> +# #
>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>> +# #
>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>> +# #
>>>>>>>>> +###############################################################################
>>>>>>>>> +
>>>>>>>>> +###############################################################################
>>>>>>>>> +# Definitions
>>>>>>>>> +###############################################################################
>>>>>>>>> +
>>>>>>>>> +include Config
>>>>>>>>> +
>>>>>>>>> +SUMMARY = response policy zone - RPZ reputation system for unbound DNS
>>>>>>>>> +
>>>>>>>>> +VER = 1.0.0
>>>>>>>>> +
>>>>>>>>> +THISAPP = rpz-$(VER)
>>>>>>>>> +DIR_APP = $(DIR_SRC)/$(THISAPP)
>>>>>>>>> +TARGET = $(DIR_INFO)/$(THISAPP)
>>>>>>>>> +
>>>>>>>>> +PROG = rpz
>>>>>>>>> +PAK_VER = 18
>>>>>>>>> +
>>>>>>>>> +DEPS =
>>>>>>>>> +
>>>>>>>>> +SERVICES =
>>>>>>>>> +
>>>>>>>>> +###############################################################################
>>>>>>>>> +# Top-level Rules
>>>>>>>>> +###############################################################################
>>>>>>>>> +
>>>>>>>>> +install : $(TARGET)
>>>>>>>>> +
>>>>>>>>> +check :
>>>>>>>>> +
>>>>>>>>> +download :
>>>>>>>>> +
>>>>>>>>> +b2 :
>>>>>>>>> +
>>>>>>>>> +dist:
>>>>>>>>> + @$(PAK)
>>>>>>>>> +
>>>>>>>>> +###############################################################################
>>>>>>>>> +# Installation Details
>>>>>>>>> +###############################################################################
>>>>>>>>> +
>>>>>>>>> +$(TARGET) :
>>>>>>>>> + @$(PREBUILD)
>>>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>>>> +
>>>>>>>>> + # RPZ scripts
>>>>>>>>> + install --verbose --mode=755 \
>>>>>>>>> + $(DIR_CONF)/rpz/{rpz-config,rpz-metrics,rpz-sleep,rpz-make,rpz-functions} \
>>>>>>>>> + --target-directory=/usr/sbin
>>>>>>>>> +
>>>>>>>>> + # RPZ config files
>>>>>>>>> + mkdir -pv /etc/unbound/local.d
>>>>>>>>> + install --verbose --mode=644 --owner=nobody --group=nobody \
>>>>>>>>> + $(DIR_CONF)/rpz/00-rpz.conf \
>>>>>>>>> + --target-directory=/etc/unbound/local.d
>>>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/local.d
>>>>>>>>> +
>>>>>>>>> + # RPZ custom list files for allow and block
>>>>>>>>> + mkdir -pv /var/ipfire/dns/rpz
>>>>>>>>> + touch /var/ipfire/dns/rpz/{allowlist,blocklist}
>>>>>>>>> + chown --verbose --recursive nobody:nobody /var/ipfire/dns/rpz
>>>>>>>>> +
>>>>>>>>> + # RPZ zone files
>>>>>>>>> + # create empty RPZ config file to avoid a unbound config error
>>>>>>>>> + mkdir -pv /etc/unbound/zonefiles
>>>>>>>>> + touch /etc/unbound/zonefiles/allow.rpz
>>>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/zonefiles
>>>>>>>>> +
>>>>>>>>> + # Install addon-specific language-files
>>>>>>>>> + install --verbose --mode=004 $(DIR_CONF)/rpz/rpz.*.pl \
>>>>>>>>> + --target-directory=/var/ipfire/addon-lang
>>>>>>>>> +
>>>>>>>>> + # Install backup definition
>>>>>>>>> + cp -vf $(DIR_CONF)/backup/includes/rpz /var/ipfire/backup/addons/includes/rpz
>>>>>>>>> +
>>>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>>>> + @$(POSTBUILD)
>>>>>>>>> diff --git a/make.sh b/make.sh
>>>>>>>>> index 827ea9e77..a77535b13 100755
>>>>>>>>> --- a/make.sh
>>>>>>>>> +++ b/make.sh
>>>>>>>>> @@ -390,7 +390,7 @@ prepareenv() {
>>>>>>>>> if [ "${free_space}" -lt "${required_space}" ]; then
>>>>>>>>> # Add any consumed space
>>>>>>>>> while read -r consumed_space path; do
>>>>>>>>> - (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>>>> + (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>>>> done <<< "$(du --summarize --bytes "${BUILD_DIR}" "${IMAGES_DIR}" "${LOG_DIR}" 2>/dev/null)"
>>>>>>>>> fi
>>>>>>>>>
>>>>>>>>> @@ -2087,6 +2087,7 @@ build_system() {
>>>>>>>>> lfsmake2 btrfs-progs
>>>>>>>>> lfsmake2 inotify-tools
>>>>>>>>> lfsmake2 grub-btrfs
>>>>>>>>> + lfsmake2 rpz
>>>>>>>>>
>>>>>>>>> lfsmake2 linux
>>>>>>>>> lfsmake2 rtl8812au
>>>>>>>>> diff --git a/src/paks/rpz/install.sh b/src/paks/rpz/install.sh
>>>>>>>>> new file mode 100644
>>>>>>>>> index 000000000..ef99bf742
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/src/paks/rpz/install.sh
>>>>>>>>> @@ -0,0 +1,36 @@
>>>>>>>>> +#!/bin/bash
>>>>>>>>> +###############################################################################
>>>>>>>>> +# #
>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>> +# #
>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>> +# #
>>>>>>>>> +###############################################################################
>>>>>>>>> +#
>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>> +extract_files
>>>>>>>>> +restore_backup ${NAME}
>>>>>>>>> +
>>>>>>>>> +# fix user created files
>>>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>>>> + /etc/unbound/zonefiles \
>>>>>>>>> + /etc/unbound/local.d
>>>>>>>>> +
>>>>>>>>> +# Update Language cache
>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>> +
>>>>>>>>> +# restart unbound to load config file
>>>>>>>>> +/etc/init.d/unbound restart
>>>>>>>>> diff --git a/src/paks/rpz/uninstall.sh b/src/paks/rpz/uninstall.sh
>>>>>>>>> new file mode 100644
>>>>>>>>> index 000000000..e11427df3
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/src/paks/rpz/uninstall.sh
>>>>>>>>> @@ -0,0 +1,38 @@
>>>>>>>>> +#!/bin/bash
>>>>>>>>> +###############################################################################
>>>>>>>>> +# #
>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>> +# #
>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>> +# #
>>>>>>>>> +###############################################################################
>>>>>>>>> +#
>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>> +
>>>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>>>> +/etc/init.d/unbound stop
>>>>>>>>> +
>>>>>>>>> +make_backup ${NAME}
>>>>>>>>> +remove_files
>>>>>>>>> +
>>>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>>>> +# "[1723428668] unbound-control[17117:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>>>> +
>>>>>>>>> +# Update Language cache
>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>> +
>>>>>>>>> +# start unbound to load unbound config file
>>>>>>>>> +/etc/init.d/unbound start
>>>>>>>>> diff --git a/src/paks/rpz/update.sh b/src/paks/rpz/update.sh
>>>>>>>>> new file mode 100644
>>>>>>>>> index 000000000..9bc340bc6
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/src/paks/rpz/update.sh
>>>>>>>>> @@ -0,0 +1,52 @@
>>>>>>>>> +#!/bin/bash
>>>>>>>>> +###############################################################################
>>>>>>>>> +# #
>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>> +# #
>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>> +# #
>>>>>>>>> +###############################################################################
>>>>>>>>> +#
>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>> +
>>>>>>>>> +# from update.sh
>>>>>>>>> +extract_backup_includes
>>>>>>>>> +
>>>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>>>> +/etc/init.d/unbound stop
>>>>>>>>> +
>>>>>>>>> +# from uninstall.sh
>>>>>>>>> +make_backup ${NAME}
>>>>>>>>> +remove_files
>>>>>>>>> +
>>>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>>>> +# "unbound-control[nn:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>>>> +
>>>>>>>>> +# from install.sh
>>>>>>>>> +extract_files
>>>>>>>>> +restore_backup ${NAME}
>>>>>>>>> +
>>>>>>>>> +# fix user created files
>>>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>>>> + /etc/unbound/zonefiles \
>>>>>>>>> + /etc/unbound/local.d
>>>>>>>>> +
>>>>>>>>> +# Update Language cache
>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>> +
>>>>>>>>> +# restart unbound to load config files
>>>>>>>>> +/etc/init.d/unbound start
>>>>>>>>> --
>>>>>>>>> 2.39.5
>>>>>>>>>
>>>>>>> Jon
>>>>>>>
>>>>>>>
>>>>>>> --
>>>>>>> Jon Murphy
>>>>>>> jon.murphy@ipfire.org
>>>>>
>>>>> Jon
>>>>>
>>>>>
>>>>> --
>>>>> Jon Murphy
>>>>> jon.murphy@ipfire.org
>>>>>
>>>>>
>>>>>
>>>>>
>>>
>>> --
>>> Sent from my laptop
>>>
>> Jon
>> --
>> Jon Murphy
>> jon.murphy@ipfire.org
>
Jon
--
Jon Murphy
jon.murphy@ipfire.org
[-- Attachment #2: Type: text/html, Size: 164965 bytes --]
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
[not found] ` <C28A0D7E-6C16-4F6F-9366-A8498F40631E@ipfire.org>
2025-02-14 12:07 ` Michael Tremer
@ 2025-03-16 17:00 ` Jon Murphy
2025-03-17 10:35 ` Michael Tremer
1 sibling, 1 reply; 30+ messages in thread
From: Jon Murphy @ 2025-03-16 17:00 UTC (permalink / raw)
To: Michael Tremer; +Cc: IPFire: Development-List
Michael,
I was reading through you response again an I want to understand this post:
> I have also stated that we cannot download any lists over HTTPS again
> and again and again. The implementation that we have here seems to
> exactly do that and therefore I think that my feedback has been
> dismissed entirely.
So if RPZ doesn't use HTTPS, what is it using? I am missing a key point
here.
Jon
On 2/13/25 3:34 PM, jon wrote:
> Michael,
>
> I’ve read through your comments a few times and I ended up with many
> more questions.
>
>
>> What I rather mean is that it has never been added as a topic on the
>> agenda and it has not been pitched by yourself.
>
> To me the efforts to get new code accepted seem to have changed and it
> seemed easier in the past. In the past I made the Core Team aware via
> the Dev Mailing List and wrote a simple two or three paragraphs of
> "What is it? / What is the value? / Here is the code"
>
>
> So in an effort to move forward: How exactly is something presented
> to the Core Team?
>
> Is there an example of a recent effort that was presented that I can
> see as a sample? (This type of info can also be added to the Wiki)
>
> I understand you want it this way, but I don’t know what exactly is
> needed. Please be specific.
>
>
> Jon
>
> PS - I am not ignoring your other comments, I am just trying to move
> forward and keep things simple.
>
>
>
>> On Feb 8, 2025, at 1:27 PM, Michael Tremer
>> <michael.tremer@ipfire.org> wrote:
>>
>> Hello Jon,
>>
>> Thanks for your reply. And good that you are copying everyone into
>> this conversation.
>>
>>> On 8 Feb 2025, at 18:41, jon <jon.murphy@ipfire.org> wrote:
>>>
>>> Michael,
>>>
>>>> I think I have covered this all at lengths before that this project
>>>> has been started as a separate effort
>>>
>>> Yes, this has been a separate effort (a very public separate
>>> effort). Yes, as you pointed this out early on with the
>>> "proof-of-concept" and then my request for people to help test RPZ.
>>> Nothing was hidden.
>>>
>>> This was done because you (and maybe others) did not have the time
>>> and I wanted to help and because I needed assistance with RPZ. I
>>> tried my best to do this without bothering you.
>>
>> I don’t that it is accurate that nobody wanted to help on this. The
>> list was always open - although not every email has been replied to
>> swiftly it is also your responsibility to raise a question again if
>> it was missed. People here have open ears.
>>
>> It was also stated on this very list on in our documentation that
>> working on something without involving the core team is a risky
>> undertaking. Of course IPFire is free software and so everyone is
>> free to fork if they wish to do so.
>>
>>>> and as far as I am aware none of the other team members has been
>>>> involved. This has not been discussed either on this list, on our
>>>> calls.
>>>
>>> You were aware many steps along the way. See your email on July 28,
>>> 2024, August 15, 2024, September 30, 2024, December 23, 2024, and
>>> January 16. My attempts to get the team involved were met with
>>> "things are busy" and sometimes silence. (Yes, I get it, people are
>>> busy.)
>>>
>>> You and Adolf, Leo, Erik and Bernhard have been aware since the
>>> beginning. You mention you were aware of the "proof-of-concept".
>>> If you include those beginning posts, since Sep 2023.
>>
>> Yes, I am aware of a proof-of-concept that I have been running myself
>> for a long time. I am also aware of the efforts that you have been
>> taking.
>>
>> Yet I don’t think there has ever been any joint effort, or am I
>> seeing that wrong?
>>
>>>> This has not been discussed . . . on our calls.
>>>
>>> On the July 28th you stated:
>>> "We have talked about RPZ many times on the monthly call since the
>>> URL filter feature is falling more and more out of fashion. I think
>>> there is also many posts about this on the forum."
>>>
>>> Please don’t insult me again by stating "you know what I mean".
>>>
>>> And it has been discussed but not documented in the Monthly Meeting
>>> notes.
>>
>> I am not at all insulting you. I don’t want to take this down to a
>> personal level at all. This is a public mailing list and people who
>> read this don’t need to listen to an argument we are having. They are
>> here for the tech inside IPFire.
>>
>> When I wrote that it has not been discussed that does not mean that
>> we have not been touching on the topic. We have been talking about
>> lots of things on the calls, the weather, politics, how our pets are.
>> None of that makes it to the logs. What I rather mean is that it has
>> never been added as a topic on the agenda and it has not been pitched
>> by yourself.
>>
>>>> Instead there has been a separate conversation on the forum with
>>>> the occasional dip here to the list. But that was not a regular
>>>> two-way conversation.
>>>
>>> Regular conversation on the Dev Mailing list is many times met with
>>> silence. I get it, people are busy.
>>>
>>> And regular two-way conversation doesn’t happen on the list. At
>>> least not with me. I’d be happy to point out the posts that were
>>> met with silence.
>>> Again, I get it, people are busy.
>>
>> And you think my emails are not being met with silence? This has
>> nothing to do with this specific topic. This has something to do with
>> how occupied people are and how engaged they are on certain topics.
>> Not everyone is involved in all the things and simply will ignore
>> emails simply based on their subject line.
>>
>>> But the "dip here to the list" were my attempts to get a
>>> conversation started. As I said, many time met with silence.
>>>
>>> The only place I was not met with silence was on the Community. You
>>> have a great group of people in the Community. It is a shame you
>>> don’t want to have others help. It would reduce your workload.
>>
>> You should stop making statements that are not true. Who doesn’t want
>> anyone to help?
>>
>> Not having this conversation on a Saturday evening would reduce my
>> workload. At least it would free up time for something else. Helping
>> with the things that are already on the go would reduce the workload
>> of the entire team. Starting one thing at a time and finishing it is
>> a lot better to manage than starting a hundred things and not even
>> finish one. I can tell you that I already have a hundred things on
>> the go.
>>
>>>> Therefore, what am I supposed to do with this email?
>>>
>>> To me it is beyond obvious…
>>>
>>> If it isn’t what you want, then guide me with how to do this the
>>> correct way. And be specific. I am trying to help. I am trying to
>>> make things better. I am trying to do things the right way.
>>
>> To me it isn’t. This is yet another project that has been dumped to
>> the list like so many before and later on everyone has left to have
>> the team deal with the rest.
>>
>> It is a huge patch set. You explained what the vision is, but that is
>> about it. There is no chance this will continue if this disagreement
>> isn’t solved first. I didn’t even look at the code.
>>
>>>> I don’t want to merge code that I don’t agree with.
>>>
>>> I asked multiple times if you "agreed with the concept" and again,
>>> met with silence. Yes I get it, people are busy.
>>
>> Having support for RPZ? Yes, it was definitely on the roadmap. That I
>> agree with.
>>
>>>> So many fundamental things that I have been raising have either not
>>>> been discussed or outright dismissed.
>>>
>>> You mentioned this a in the past, but for some reason you do not
>>> disclose what I dismissed. Why do you continue to make this harder,
>>> wouldn’t it not be easier to tell me what I have dismissed?
>>>
>>> I have sent multiple emails trying to answer your concerns and
>>> comments. On July 28, Aug 14, Aug 22, Aug 23, Sep 30, etc.
>>>
>>> I’ve gone through all of the questions you asked and I cannot find a
>>> "dismissed" item.
>>
>> Maybe I need to be *more clear*. I feel humoured by this.
>>
>> It is late on a Saturday and I want my dinner soon, but certainly I
>> have stated that this should never be an add-on considering it is
>> supposed to replace URL Filter. We should never allow people to add
>> their own sources. I have also stated that we cannot download any
>> lists over HTTPS again and again and again. The implementation that
>> we have here seems to exactly do that and therefore I think that my
>> feedback has been dismissed entirely.
>>
>>>> I don’t want to merge code that has no future inside IPFire as
>>>> there is no constructive conversation with the maintainers of it.
>>>
>>> The maintainers of Unbound and/or RPZ?
>>>
>>> The maintainers of Hagezi list, the threatfox list, the urlhaus
>>> list, etc.?
>>>
>>> What else? The maintainers or the RPZ scripts? That is me. Let’s
>>> talk!
>>
>> You. I don’t care much about the providers of the lists.
>>
>>> See, this is where it gets confusing. There are hundreds of open
>>> source packages as part of IPFire. Pick the last five years of
>>> items added to the IPFire build. You're telling me you have
>>> "constructive conversation with the maintainers" of all of the added
>>> packages?
>>
>> They publish their software and they don’t care whether I am pulling
>> it or not. They publish it with the commitment to maintain it -
>> sometimes for better and sometimes for worse.
>>
>> You care about me pulling your code and I don’t know whether you
>> would commit to maintain this.
>>
>> These two are very different cases.
>>
>>> Pick the IP Blocklists list (i.e., 3CORESEC, ABUSECH, DSHIELD,
>>> SPAMHAUS, etc.) or the Suricata lists (i.e.,Emergingthreats.net
>>> <http://emergingthreats.net/>,Abuse.ch <http://abuse.ch/>, etc.).
>>> So you’ve have "constructive conversation with the maintainers"?
>>
>> Yes, occasionally I have phone calls with a few of these providers.
>>
>>>> Having been trying for a long time to make you aware of this,
>>>> nothing of this should come as a surprise.
>>>
>>> Ha! Yes a surprise. In the beginning you seemed interested as
>>> IPFire needed a replacement for URL Filter. You asked good
>>> questions about the lists picked, asked for the value to the users,
>>> etc. And I answered the best I could.
>>>
>>> You even asked: “Why is this realised as an add-on and not part of
>>> the core system?” from your Jul 28, 2024 email.
>>
>> Ah, so, why is the patch creating an add-on? Not that I am saying
>> that what I say is law, but it has not been challenged either. If my
>> input is being ignored, why should I put this to the top of my list
>> of priorities? I am not disappointed about this, just trying to be
>> very good with my time.
>>
>>> And on January 16, 2025 I wrote a message looking for help. And you
>>> were kind to respond quickly. So in three weeks time, since the
>>> kind response, something has changed. You went from supportive to
>>> "this".
>>>
>>> So yes, I am surprised.
>>
>> Well, maybe I should not have replied to that email. It was clear
>> that you were on some path that was not right, but you were not
>> interested before in finding the right path from the beginning.
>>
>>>> Please consider if that can be changed and if there is a path
>>>> forward with this.
>>>
>>> Be more specific, what has to change? What exactly did I dismiss?
>>
>> Dismissal is just my assumption. I don’t know what you actually did
>> with my feedback. I can only see the end product that does not seem
>> contain much of it. Repeatedly I have been pointing out that we
>> should think before we build. I am sure a lot of hours have now gone
>> into some code that simply does not satisfy me. And I am not not
>> talking about the code itself, what it does is what I don’t think is
>> right for us.
>>
>> The process is very clear for me that we should first of all think
>> whether we want a certain feature now. Then there should be a clear
>> roadmap for everyone to follow; tasks can be split-up as we go and
>> hopefully then have something that is maintainable, interesting for
>> our users and even would do us proud. This is how this should work.
>>
>> So, what has to change? I don’t think with shouting at each other,
>> throwing patches around and making me generally unhappy is a good start.
>>
>> -Michael
>>
>>> Jon
>>>
>>>
>>>
>>>> On Feb 6, 2025, at 2:13 PM, Michael Tremer
>>>> <michael.tremer@ipfire.org> wrote:
>>>>
>>>> Hello Jon,
>>>>
>>>> Well, here we are again with another patch regarding this feature.
>>>>
>>>> I cannot quite see from your email what the question is, but if
>>>> this is a request to have this merged into IPFire, I am once again
>>>> sorry to disappoint you.
>>>>
>>>> I think I have covered this all at lengths before that this project
>>>> has been started as a separate effort and as far as I am aware none
>>>> of the other team members has been involved. This has not been
>>>> discussed either on this list, on our calls. Instead there has been
>>>> a separate conversation on the forum with the occasional dip here
>>>> to the list. But that was not a regular two-way conversation.
>>>> Therefore, what am I supposed to do with this email?
>>>>
>>>> I don’t want to merge code that I don’t agree with. So many
>>>> fundamental things that I have been raising have either not been
>>>> discussed or outright dismissed.
>>>>
>>>> I don’t want to merge code that has no future inside IPFire as
>>>> there is no constructive conversation with the maintainers of it.
>>>>
>>>> Having been trying for a long time to make you aware of this,
>>>> nothing of this should come as a surprise.
>>>>
>>>> Please consider if that can be changed and if there is a path
>>>> forward with this.
>>>>
>>>> All the best,
>>>> -Michael
>>>>
>>>>> On 6 Feb 2025, at 16:35, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>
>>>>> What is it?
>>>>> Response Policy Zone (RPZ) is a mechanism to define local policies
>>>>> in a
>>>>> standardized way and load those policies from external sources.
>>>>> Bottom line: RPZ allows admins to easily block access to websites
>>>>> via DNS lookup.
>>>>>
>>>>> RPZ can block websites via categories. Examples include: fake
>>>>> websites, annoying
>>>>> pop-up ads, newly registered domains, DoH bypass sites, bad "host"
>>>>> services,
>>>>> maliscious top level domains (e.g., *.zip, *.mov), piracy,
>>>>> gambling, pornography,
>>>>> and more. RPZ lists come from various RPZ providers and their
>>>>> available
>>>>> catagories.
>>>>>
>>>>> This RPZ add-on enables the RPZ functionality by adding a couple
>>>>> lines in a
>>>>> configuration file. This add-on simply adds configuration files
>>>>> and adds
>>>>> scripts (config, metrics and sleep) to make RPZ easier for the
>>>>> admin to use.
>>>>>
>>>>> The RPZ scripts include additional languages: German, Spanish,
>>>>> French, Turkish,
>>>>> and Italian.
>>>>>
>>>>> RPZ itself was release in 2010 and has been part of the IPFire
>>>>> build since ~2015.
>>>>>
>>>>> Why is it needed? What is its value?
>>>>>
>>>>> - The RPZ concept places this filtering into IPFire, our internet
>>>>> access
>>>>> gateway, which is (should be) solely used as DNS source of the
>>>>> internal network.
>>>>>
>>>>> - As most sites use HTTPS it makes it difficult to filter traffic
>>>>> with URL
>>>>> Filter without also properly configuring conventional
>>>>> (non-transparent)
>>>>> mode on the proxy. RPZ is a nice replacement for the URL Filter.
>>>>>
>>>>> - No need to install and maintain an additional device like PiHole
>>>>> or AdBlock
>>>>> browser extensions on multiple user devices.
>>>>>
>>>>> - This is an additional layer of protection for users. Less worry
>>>>> someone will
>>>>> click on something that gets them into trouble. And, saying this
>>>>> with emphasis,
>>>>> the ability to do it in one place!
>>>>>
>>>>> - Blocked sites save on unneeded traffic and can lessen the threat
>>>>> of malware
>>>>> in advertisements
>>>>>
>>>>> - Logging allows the admin to see the site blocked and take actions
>>>>>
>>>>> - RPZ will be used at the home, home-office (work from home), schools,
>>>>> ministerial, and at the office. Device counts are small (2-6) to
>>>>> medium (~80)
>>>>> to mediam-large (200+).
>>>>>
>>>>> - RPZ can block ads, popups, phishing, scammers, spyware, malware,
>>>>> annoying
>>>>> popups, NSFW links, DOH servers, and the usual internet trash.
>>>>>
>>>>> ------------------------------
>>>>>
>>>>> Change Log for RPZ add-on
>>>>>
>>>>> rpz-1.0.0-18 on 2025-02-05
>>>>> - Build for approval & release as IPFire add-on
>>>>>
>>>>> ---
>>>>>
>>>>> rpz-beta-0.1.18-18.ipfire on 2025-02-01
>>>>> rpz.cgi:
>>>>> - new feature: added a mod key to force a unbound restart
>>>>>
>>>>> rpz-config and rpz-make:
>>>>> - new feature: added action for unbound restart `rpz-config
>>>>> unbound-restart`
>>>>>
>>>>> rpz-metrics:
>>>>> - simple reformatting
>>>>> - rename far right column from "last update" to "last download"
>>>>>
>>>>> ---
>>>>>
>>>>> rpz-beta-0.1.17-17.ipfire on 2024-12-09
>>>>> rpz-make
>>>>> - bug fix: corrected validation regex for wildcards like:
>>>>> `*.domain.com`
>>>>>
>>>>> ---
>>>>>
>>>>> rpz-beta-0.1.16-16.ipfire on 2024-11-18
>>>>> rpz-make
>>>>> - new feature: updated validation regex
>>>>> - bug fix: moved validation to beginning of process. Now we
>>>>> validate before
>>>>> creating config files.
>>>>>
>>>>> rpz.cgi:
>>>>> - new feature: use CSS color variables of the main ipfire theme
>>>>> - bug fix: empty zonefile remarks were stored as “undef” and
>>>>> caused a warning
>>>>> - bug fix: HTML textarea removes the first empty line in a custom list
>>>>> - thank you Leo!
>>>>>
>>>>> ---
>>>>>
>>>>> rpz-beta-0.1.15-15.ipfire on 2024-11-04
>>>>> rpz.cgi:
>>>>> - new feature: added new language file for Turkish (thank you Peppe)
>>>>>
>>>>> rpz-make
>>>>> - bug fix: corrected empty allow/block list issue. An empty
>>>>> allow/block list
>>>>> will now remove contents of allow/block.rpz files and remove unneeded
>>>>> allow/block.conf file. (thank you iptom)
>>>>>
>>>>> ---
>>>>>
>>>>> rpz-beta-0.1.14-14.ipfire on 2024-10-29
>>>>> rpz-config:
>>>>> - bug fix: correct missing rpz extension. `rpz-config list`
>>>>> displayed URL
>>>>> incorrectly (thank you Bernhard)
>>>>>
>>>>> rpz.cgi:
>>>>> - bug fix: remove extra `"` in language files (thank you Bernhard)
>>>>> - new feature: slightly dim "apply" button when not enabled
>>>>>
>>>>> ---
>>>>>
>>>>> rpz-beta-0.1.13-13.ipfire on 2024-10-27
>>>>> - skipped
>>>>>
>>>>> ---
>>>>>
>>>>> rpz-beta-0.1.12-12.ipfire on 2024-10-21
>>>>> rpz.cgi:
>>>>> - new feature: added new language file for French (thank you
>>>>> gw-ipfire)
>>>>>
>>>>> ---
>>>>>
>>>>> rpz-beta-0.1.11-11.ipfire on 2024-10-18
>>>>> rpz.cgi:
>>>>> - new feature: added new language file for Italian (thank you umberto)
>>>>> - new feature: added new language file for Spanish (thank you Roberto)
>>>>>
>>>>> ---
>>>>>
>>>>> rpz-beta-0.1.10-10.ipfire on 2024-10-15
>>>>> rpz-make:
>>>>> - bug fix: corrected validation error for a custom list entry
>>>>> (thank you siosios)
>>>>> - e.g., `*.cloudflare-dns.com`
>>>>>
>>>>> install.sh:
>>>>> - bug fix: add chown to correct user created files
>>>>>
>>>>> update.sh:
>>>>> - bug fix: add chown to correct user created files (thank you siosios)
>>>>>
>>>>> ---
>>>>>
>>>>> rpz-beta-0.1.9-9.ipfire on 2024-10-08
>>>>> rpz.cgi:
>>>>> - new feature: added new language file for German (thank you Leo)
>>>>> - bug fix: add missing "rpz exitcode 110"
>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>
>>>>> ---
>>>>>
>>>>> rpz-beta-0.1.8-8.ipfire on 2024-10-04
>>>>> - skipped
>>>>>
>>>>> ---
>>>>>
>>>>> rpz-beta-0.1.7-7.ipfire on 2024-10-03
>>>>> All:
>>>>> - new feature: includes beta version numbers for pakfire package,
>>>>> instead of only `rpz-1.0.0-1.ipfire`, for each release.
>>>>>
>>>>> rpz.cgi:
>>>>> - new feature: added new WebGUI at `rpz.cgi`
>>>>> - a BIG thank you to Leo Hofmann for all of his work creating the
>>>>> webgui!!
>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>
>>>>> rpz-make:
>>>>> - new feature: validate entries in allowlist and blocklist
>>>>> - new feature: add "no-reload" option for WebGUI
>>>>>
>>>>> rpz-metrics:
>>>>> - new feature: info can be sorted by name, by hit count, by line
>>>>> count, by
>>>>> "enabled" list or all lists
>>>>>
>>>>> backups:
>>>>> - bug fix: include all files in `/var/ipfire/dns/rpz` directory in
>>>>> backup
>>>>>
>>>>> update.sh:
>>>>> - bug fix: corrected ownership for `/var/ipfire/dns/rpz` directory
>>>>> during an
>>>>> update
>>>>>
>>>>> Build:
>>>>> - bug fix: `block.rpz.conf` and `block.rpz` from build. Files to
>>>>> be created
>>>>> by `rpz-make`
>>>>>
>>>>> WebGUI and German language file
>>>>> Contribution-by: Leo-Andres Hofmann <hofmann@leo-andres.de>
>>>>>
>>>>> Spanish language file
>>>>> Contribution-by: Roberto Peña
>>>>>
>>>>> Italian language file
>>>>> Contribution-by: Umberto Parma
>>>>>
>>>>> French language file
>>>>> Contribution-by: gw-ipfire
>>>>>
>>>>> Turkish language file
>>>>> Contribution-by: Peppe Tech
>>>>>
>>>>> Contribution-by: Bernhard Bitsch <bbitsch@ipfire.org>
>>>>> Contribution-by: Erik Kapfer <erik.kapfer@ipfire.org>
>>>>> Signed-off-by: Jon Murphy <jon.murphy@ipfire.org
>>>>> ---
>>>>> config/backup/includes/rpz | 4 +
>>>>> config/cfgroot/manualpages | 1 +
>>>>> config/menu/EX-rpz.menu | 6 +
>>>>> config/rootfiles/common/configroot | 1 +
>>>>> config/rootfiles/common/web-user-interface | 1 +
>>>>> config/rootfiles/packages/rpz | 20 +
>>>>> config/rpz/00-rpz.conf | 10 +
>>>>> config/rpz/rpz-config | 130 +++
>>>>> config/rpz/rpz-functions | 85 ++
>>>>> config/rpz/rpz-make | 203 +++++
>>>>> config/rpz/rpz-metrics | 170 ++++
>>>>> config/rpz/rpz-sleep | 58 ++
>>>>> config/rpz/rpz.de.pl | 30 +
>>>>> config/rpz/rpz.en.pl | 30 +
>>>>> config/rpz/rpz.es.pl | 30 +
>>>>> config/rpz/rpz.fr.pl | 30 +
>>>>> config/rpz/rpz.it.pl | 30 +
>>>>> config/rpz/rpz.tr.pl | 30 +
>>>>> html/cgi-bin/rpz.cgi | 923 +++++++++++++++++++++
>>>>> lfs/rpz | 96 +++
>>>>> make.sh | 3 +-
>>>>> src/paks/rpz/install.sh | 36 +
>>>>> src/paks/rpz/uninstall.sh | 38 +
>>>>> src/paks/rpz/update.sh | 52 ++
>>>>> 24 files changed, 2016 insertions(+), 1 deletion(-)
>>>>> create mode 100644 config/backup/includes/rpz
>>>>> create mode 100644 config/menu/EX-rpz.menu
>>>>> create mode 100644 config/rootfiles/packages/rpz
>>>>> create mode 100644 config/rpz/00-rpz.conf
>>>>> create mode 100644 config/rpz/rpz-config
>>>>> create mode 100644 config/rpz/rpz-functions
>>>>> create mode 100644 config/rpz/rpz-make
>>>>> create mode 100755 config/rpz/rpz-metrics
>>>>> create mode 100755 config/rpz/rpz-sleep
>>>>> create mode 100644 config/rpz/rpz.de.pl
>>>>> create mode 100644 config/rpz/rpz.en.pl
>>>>> create mode 100644 config/rpz/rpz.es.pl
>>>>> create mode 100644 config/rpz/rpz.fr.pl
>>>>> create mode 100644 config/rpz/rpz.it.pl
>>>>> create mode 100644 config/rpz/rpz.tr.pl
>>>>> create mode 100644 html/cgi-bin/rpz.cgi
>>>>> create mode 100644 lfs/rpz
>>>>> create mode 100644 src/paks/rpz/install.sh
>>>>> create mode 100644 src/paks/rpz/uninstall.sh
>>>>> create mode 100644 src/paks/rpz/update.sh
>>>>>
>>>>> diff --git a/config/backup/includes/rpz b/config/backup/includes/rpz
>>>>> new file mode 100644
>>>>> index 000000000..36513e494
>>>>> --- /dev/null
>>>>> +++ b/config/backup/includes/rpz
>>>>> @@ -0,0 +1,4 @@
>>>>> +/var/ipfire/dns/rpz/*
>>>>> +/etc/unbound/zonefiles/allow.rpz
>>>>> +/etc/unbound/zonefiles/block.rpz
>>>>> +/etc/unbound/local.d/*rpz.conf
>>>>> diff --git a/config/cfgroot/manualpages b/config/cfgroot/manualpages
>>>>> index 1f7e01efc..d3a48c633 100644
>>>>> --- a/config/cfgroot/manualpages
>>>>> +++ b/config/cfgroot/manualpages
>>>>> @@ -70,6 +70,7 @@ pakfire.cgi=configuration/ipfire/pakfire
>>>>> wlanap.cgi=addons/wireless
>>>>> tor.cgi=addons/tor
>>>>> samba.cgi=addons/samba
>>>>> +rpz.cgi=addons/rpz
>>>>>
>>>>> # Logs menu
>>>>> logs.cgi/summary.dat=configuration/logs/summary
>>>>> diff --git a/config/menu/EX-rpz.menu b/config/menu/EX-rpz.menu
>>>>> new file mode 100644
>>>>> index 000000000..2f4daf410
>>>>> --- /dev/null
>>>>> +++ b/config/menu/EX-rpz.menu
>>>>> @@ -0,0 +1,6 @@
>>>>> +$subipfire->{'20.rpz'} = {
>>>>> + 'caption' => $Lang::tr{'rpz'},
>>>>> + 'uri' => '/cgi-bin/rpz.cgi',
>>>>> + 'title' => "RPZ",
>>>>> + 'enabled' => 1,
>>>>> +};
>>>>> diff --git a/config/rootfiles/common/configroot
>>>>> b/config/rootfiles/common/configroot
>>>>> index 9839eee45..b30d6aae4 100644
>>>>> --- a/config/rootfiles/common/configroot
>>>>> +++ b/config/rootfiles/common/configroot
>>>>> @@ -120,6 +120,7 @@ var/ipfire/menu.d/70-log.menu
>>>>> #var/ipfire/menu.d/EX-apcupsd.menu
>>>>> #var/ipfire/menu.d/EX-guardian.menu
>>>>> #var/ipfire/menu.d/EX-mympd.menu
>>>>> +#var/ipfire/menu.d/EX-rpz.menu
>>>>> #var/ipfire/menu.d/EX-samba.menu
>>>>> #var/ipfire/menu.d/EX-tor.menu
>>>>> #var/ipfire/menu.d/EX-transmission.menu
>>>>> diff --git a/config/rootfiles/common/web-user-interface
>>>>> b/config/rootfiles/common/web-user-interface
>>>>> index 816241dae..e00464076 100644
>>>>> --- a/config/rootfiles/common/web-user-interface
>>>>> +++ b/config/rootfiles/common/web-user-interface
>>>>> @@ -69,6 +69,7 @@ srv/web/ipfire/cgi-bin/proxy.cgi
>>>>> srv/web/ipfire/cgi-bin/qos.cgi
>>>>> srv/web/ipfire/cgi-bin/remote.cgi
>>>>> srv/web/ipfire/cgi-bin/routing.cgi
>>>>> +#srv/web/ipfire/cgi-bin/rpz.cgi
>>>>> #srv/web/ipfire/cgi-bin/samba.cgi
>>>>> srv/web/ipfire/cgi-bin/services.cgi
>>>>> srv/web/ipfire/cgi-bin/shutdown.cgi
>>>>> diff --git a/config/rootfiles/packages/rpz
>>>>> b/config/rootfiles/packages/rpz
>>>>> new file mode 100644
>>>>> index 000000000..1c8663049
>>>>> --- /dev/null
>>>>> +++ b/config/rootfiles/packages/rpz
>>>>> @@ -0,0 +1,20 @@
>>>>> +etc/unbound/local.d/00-rpz.conf
>>>>> +etc/unbound/zonefiles
>>>>> +etc/unbound/zonefiles/allow.rpz
>>>>> +usr/sbin/rpz-config
>>>>> +usr/sbin/rpz-functions
>>>>> +usr/sbin/rpz-make
>>>>> +usr/sbin/rpz-metrics
>>>>> +usr/sbin/rpz-sleep
>>>>> +var/ipfire/addon-lang/rpz.de.pl
>>>>> +var/ipfire/addon-lang/rpz.en.pl
>>>>> +var/ipfire/addon-lang/rpz.es.pl
>>>>> +var/ipfire/addon-lang/rpz.fr.pl
>>>>> +var/ipfire/addon-lang/rpz.it.pl
>>>>> +var/ipfire/addon-lang/rpz.tr.pl
>>>>> +var/ipfire/backup/addons/includes/rpz
>>>>> +var/ipfire/dns/rpz
>>>>> +var/ipfire/dns/rpz/allowlist
>>>>> +var/ipfire/dns/rpz/blocklist
>>>>> +var/ipfire/menu.d/EX-rpz.menu
>>>>> +srv/web/ipfire/cgi-bin/rpz.cgi
>>>>> diff --git a/config/rpz/00-rpz.conf b/config/rpz/00-rpz.conf
>>>>> new file mode 100644
>>>>> index 000000000..f005a4f2e
>>>>> --- /dev/null
>>>>> +++ b/config/rpz/00-rpz.conf
>>>>> @@ -0,0 +1,10 @@
>>>>> +server:
>>>>> + module-config: "respip validator iterator"
>>>>> +
>>>>> +rpz:
>>>>> + name: allow.rpz
>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>> + rpz-action-override: passthru
>>>>> + rpz-log: yes
>>>>> + rpz-log-name: allow
>>>>> + rpz-signal-nxdomain-ra: yes
>>>>> diff --git a/config/rpz/rpz-config b/config/rpz/rpz-config
>>>>> new file mode 100644
>>>>> index 000000000..c72d50f9b
>>>>> --- /dev/null
>>>>> +++ b/config/rpz/rpz-config
>>>>> @@ -0,0 +1,130 @@
>>>>> +#!/bin/bash
>>>>> +###############################################################################
>>>>> +#
>>>>> #
>>>>> +# IPFire.org - A linux based firewall
>>>>> #
>>>>> +# Copyright (C) 2024-2025 IPFire Team <info@ipfire.org>
>>>>> #
>>>>> +#
>>>>> #
>>>>> +# 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
>>>>> <http://www.gnu.org/licenses/>. #
>>>>> +#
>>>>> #
>>>>> +###############################################################################
>>>>> +
>>>>> +version="2025-01-11 - v44"
>>>>> +
>>>>> +############### Functions ###############
>>>>> +
>>>>> +source /usr/sbin/rpz-functions
>>>>> +
>>>>> +############### Main ###############
>>>>> +
>>>>> +tagName="unbound"
>>>>> +
>>>>> +rpzAction="${1}" # input RPZ action
>>>>> +rpzName="${2}" # input RPZ name
>>>>> +rpzURL="${3}" # input RPZ URL
>>>>> +rpzOption1="${4}" # input RPZ option #1
>>>>> +rpzOption2="${5}" # input RPZ option #2
>>>>> +
>>>>> +rpzConfig="/etc/unbound/local.d/${rpzName}.rpz.conf" # output
>>>>> zone conf file
>>>>> +rpzFile="/etc/unbound/zonefiles/${rpzName}.rpz" # output
>>>>> for RPZ file
>>>>> +
>>>>> +rpzLog="yes" # log default is yes
>>>>> +ucReload="yes" # reload default is yes
>>>>> +
>>>>> +while [[ $# -gt 0 ]] ; do
>>>>> + case "$1" in
>>>>> + --no-log ) rpzLog="no" ;;
>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>> + esac
>>>>> + shift # Shift after checking all the cases to get next
>>>>> option
>>>>> +done
>>>>> +
>>>>> +case "${rpzAction}" in
>>>>> + # add new rpz list
>>>>> + add )
>>>>> + check_name "${rpzName}" # is this a valid name?
>>>>> + # does this config already exist? If yes, then exit
>>>>> + if [[ -f "${rpzConfig}" ]] ; then
>>>>> + msg_log "error: rpz: duplicate - ${rpzConfig} already
>>>>> exists. exit"
>>>>> + exit 104
>>>>> + fi
>>>>> +
>>>>> + # is this a valid URL?
>>>>> +
>>>>> regex='^https://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
>>>>> + if ! [[ "${rpzURL}" =~ $regex ]] ; then
>>>>> + msg_log "error: rpz: the URL is not valid:
>>>>> \"${rpzURL}\". exit."
>>>>> + exit 105
>>>>> + fi
>>>>> +
>>>>> + # create the zone config file
>>>>> + {
>>>>> + echo "rpz:"
>>>>> + echo " name: ${rpzName}.rpz"
>>>>> + echo " zonefile: ${rpzFile}"
>>>>> + echo " url: ${rpzURL}"
>>>>> + echo " rpz-action-override: nxdomain"
>>>>> + echo " rpz-log: ${rpzLog}"
>>>>> + echo " rpz-log-name: ${rpzName}"
>>>>> + echo " rpz-signal-nxdomain-ra: yes"
>>>>> + } > "${rpzConfig}"
>>>>> +
>>>>> + # set-up zonefile
>>>>> + # create an empty rpz file if it does not exist
>>>>> + if [[ ! -f "${rpzFile}" ]] ; then
>>>>> + touch "${rpzFile}"
>>>>> + # unbound requires these settings for rpz files
>>>>> + set_permissions "${rpzFile}" "${rpzConfig}"
>>>>> + fi
>>>>> + ;;
>>>>> +
>>>>> + # trash config file & rpz file
>>>>> + remove )
>>>>> + if ! [[ -f "${rpzConfig}" ]] ; then
>>>>> + msg_log "error: rpz: cannot remove ${rpzConfig}, does
>>>>> not exist. exit"
>>>>> + exit 106
>>>>> + fi
>>>>> +
>>>>> + msg_log "info: rpz: remove config file & rpz file
>>>>> \"${rpzName}\""
>>>>> + rm "${rpzConfig}"
>>>>> + rm "${rpzFile}"
>>>>> + ;;
>>>>> +
>>>>> + reload )
>>>>> + check_unbound_conf "${checkConf}"
>>>>> + ;;
>>>>> +
>>>>> + list )
>>>>> + awk -F':' '/^\s*name:/{ gsub(/[[:blank:]]|\.rpz/, "",$2)
>>>>> ; NAME=$2 } \
>>>>> + /^\s*url:/{ gsub(/[[:blank:]]/, "") ; print
>>>>> NAME"="$2":"$3} ' \
>>>>> + /etc/unbound/local.d/*rpz.conf
>>>>> + exit
>>>>> + ;;
>>>>> +
>>>>> + unbound-restart )
>>>>> + check_unbound_conf "${checkConf}"
>>>>> + unbound_restart
>>>>> + exit
>>>>> + ;;
>>>>> +
>>>>> + * )
>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>> + printf "Usage: $(basename "$0") <ACTION> <NAME> <URL>
>>>>> <OPTION> <OPTION>\n"
>>>>> + printf "Version: ${version}\n"
>>>>> + exit 108
>>>>> + ;;
>>>>> +
>>>>> +esac
>>>>> +
>>>>> +unbound_control_reload "${ucReload}"
>>>>> +
>>>>> +exit
>>>>> diff --git a/config/rpz/rpz-functions b/config/rpz/rpz-functions
>>>>> new file mode 100644
>>>>> index 000000000..ace1d2690
>>>>> --- /dev/null
>>>>> +++ b/config/rpz/rpz-functions
>>>>> @@ -0,0 +1,85 @@
>>>>> +#!/bin/bash
>>>>> +###############################################################################
>>>>> +#
>>>>> #
>>>>> +# IPFire.org - A linux based firewall
>>>>> #
>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org>
>>>>> #
>>>>> +#
>>>>> #
>>>>> +# 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
>>>>> <http://www.gnu.org/licenses/>. #
>>>>> +#
>>>>> #
>>>>> +###############################################################################
>>>>> +
>>>>> +version="2024-12-10 - v02"
>>>>> +
>>>>> +############### Functions ###############
>>>>> +
>>>>> +msg_log () {
>>>>> + logger --tag "${tagName}" "$*"
>>>>> + if tty --silent ; then
>>>>> + echo "${tagName}:" "$*"
>>>>> + fi
>>>>> +}
>>>>> +
>>>>> +# check for a valid name
>>>>> +check_name () {
>>>>> + local theName="${1}"
>>>>> +
>>>>> + regex='^[a-zA-Z0-9_]+$' # no dash or plus, alpha
>>>>> numeric only
>>>>> + regex1='^(allow|block)$' # allow and block are
>>>>> reserved NAMEs
>>>>> + if [[ ! "${theName}" =~ $regex ]] || [[ "${theName}" =~
>>>>> $regex1 ]] ; then
>>>>> + msg_log "error: rpz: the NAME is not valid:
>>>>> \"${theName}\". exit."
>>>>> + exit 101
>>>>> + fi
>>>>> +}
>>>>> +
>>>>> +set_permissions () {
>>>>> + chown nobody:nobody "$@"
>>>>> + chmod 644 "$@"
>>>>> +}
>>>>> +
>>>>> +check_unbound_conf () {
>>>>> + local thecheckConf="${1:-yes}" # check config default
>>>>> is yes
>>>>> +
>>>>> + # check the above config files
>>>>> + if [[ "${thecheckConf}" == yes ]] ; then
>>>>> + msg_log "info: rpz: check for errors with
>>>>> \"unbound-checkconf\""
>>>>> +
>>>>> + if ! unbound-checkconf ; then
>>>>> + msg_log "error: rpz: unbound-checkconf found invalid
>>>>> configuration."
>>>>> + msg_log \
>>>>> + "error: rpz: In Terminal run the command
>>>>> \"unbound-checkconf\" for more information. exit."
>>>>> + exit 102
>>>>> + fi
>>>>> + fi
>>>>> +}
>>>>> +
>>>>> +unbound_control_reload () {
>>>>> + local theReload="${1:-yes}" # reload default is yes
>>>>> +
>>>>> + if [[ "${theReload}" == yes ]] ; then
>>>>> + # reload due to the changes
>>>>> + msg_log "info: rpz: run \"unbound-control reload\""
>>>>> +
>>>>> + if ! unbound-control reload ; then
>>>>> + msg_log "error: rpz: unbound-control reload. exit."
>>>>> + exit 109
>>>>> + fi
>>>>> + fi
>>>>> +}
>>>>> +
>>>>> +unbound_restart () {
>>>>> + # restart due to the changes
>>>>> + msg_log "info: rpz: run \"unbound restart\""
>>>>> +
>>>>> + /usr/local/bin/unboundctrl restart
>>>>> +}
>>>>> diff --git a/config/rpz/rpz-make b/config/rpz/rpz-make
>>>>> new file mode 100644
>>>>> index 000000000..927d55170
>>>>> --- /dev/null
>>>>> +++ b/config/rpz/rpz-make
>>>>> @@ -0,0 +1,203 @@
>>>>> +#!/bin/bash
>>>>> +###############################################################################
>>>>> +#
>>>>> #
>>>>> +# IPFire.org - A linux based firewall
>>>>> #
>>>>> +# Copyright (C) 2024-2025 IPFire Team <info@ipfire.org>
>>>>> #
>>>>> +#
>>>>> #
>>>>> +# 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
>>>>> <http://www.gnu.org/licenses/>. #
>>>>> +#
>>>>> #
>>>>> +###############################################################################
>>>>> +
>>>>> +version="2025-01-11 - v14"
>>>>> +
>>>>> +############### Functions ###############
>>>>> +
>>>>> +source /usr/sbin/rpz-functions
>>>>> +
>>>>> +# create the config file for allow
>>>>> +make_allow_config () {
>>>>> + local theLog="${1:-yes}" # log
>>>>> default ON
>>>>> + local theConfig="/etc/unbound/local.d/00-rpz.conf" # output
>>>>> zone conf file
>>>>> + local theList="/var/ipfire/dns/rpz/allowlist" # input
>>>>> custom list of domains
>>>>> +
>>>>> + msg_log "info: rpz: make config file \"00-rpz.conf\""
>>>>> +
>>>>> + echo "server:
>>>>> + module-config: \"respip validator iterator\"" > "${theConfig}"
>>>>> +
>>>>> + # does allow list exist?
>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>> +
>>>>> + echo "rpz:
>>>>> + name: allow.rpz
>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>> + rpz-action-override: passthru
>>>>> + rpz-log: ${theLog}
>>>>> + rpz-log-name: allow
>>>>> + rpz-signal-nxdomain-ra: yes" >> "${theConfig}"
>>>>> +
>>>>> + fi
>>>>> +
>>>>> + # set-up zonefile - unbound requires these settings for rpz
>>>>> files
>>>>> + set_permissions "${theConfig}"
>>>>> +}
>>>>> +
>>>>> +# create the config file for block
>>>>> +make_block_config () {
>>>>> + local theLog="${1:-yes}" #
>>>>> log default ON
>>>>> + local theConfig="/etc/unbound/local.d/block.rpz.conf" #
>>>>> output zone conf file
>>>>> + local theList="/var/ipfire/dns/rpz/blocklist" #
>>>>> input custom list of domains
>>>>> +
>>>>> + msg_log "info: rpz: make config file \"block.rpz.conf\""
>>>>> +
>>>>> + # does block list exist?
>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>> +
>>>>> + echo "rpz:
>>>>> + name: block.rpz
>>>>> + zonefile: /etc/unbound/zonefiles/block.rpz
>>>>> + rpz-action-override: nxdomain
>>>>> + rpz-log: ${theLog}
>>>>> + rpz-log-name: block
>>>>> + rpz-signal-nxdomain-ra: yes" > "${theConfig}"
>>>>> +
>>>>> + # set-up zonefile - unbound requires these settings for
>>>>> rpz files
>>>>> + set_permissions "${theConfig}"
>>>>> + else
>>>>> + # no - trash the config file
>>>>> + rm --verbose /etc/unbound/local.d/block.rpz.conf
>>>>> + fi
>>>>> +}
>>>>> +
>>>>> +# create an RPZ file for allow or block
>>>>> +make_rpz_file () {
>>>>> + local theName="${1}" # allow or block
>>>>> + local theAction='.' # the default is nxdomain or block
>>>>> + local actionList
>>>>> +
>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" #
>>>>> input custom list of domains
>>>>> + local theZonefile="/etc/unbound/zonefiles/${theName}.rpz" #
>>>>> output file for RPZ
>>>>> +
>>>>> + # does a list exist?
>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>> +
>>>>> + # for allow set to passthru
>>>>> + [[ "${theName}" == allow ]] && theAction='rpz-passthru.'
>>>>> +
>>>>> + # drop any extra "blanks" and add "CNAME <RPZ action>."
>>>>> to each line
>>>>> + actionList=$( awk '{$1=$1};1' "${theList}" |
>>>>> + sed "/^[^;].*[[:alnum:]]/ s|$| CNAME ${theAction}|" )
>>>>> +
>>>>> + msg_log "info: rpz: create zonefile for \"${theName}list\""
>>>>> +
>>>>> +echo "; Name: ${theName} list
>>>>> +; Last modified: $(date "+%Y-%m-%d at %H.%M.%S %Z")
>>>>> +;
>>>>> +; domains with actions list
>>>>> +;
>>>>> +${actionList}" > "${theZonefile}"
>>>>> +
>>>>> + # set-up zonefile - unbound requires these settings for
>>>>> rpz files
>>>>> + set_permissions "${theZonefile}"
>>>>> + # set-up allow/block list files
>>>>> + set_permissions "${theList}"
>>>>> + else
>>>>> + msg_log "info: rpz: the ${theList} is empty."
>>>>> +
>>>>> + rm --verbose "${theZonefile}" # trash the RPZ file
>>>>> + fi
>>>>> +}
>>>>> +
>>>>> +# check if allow/block list is valid
>>>>> +validate_list () {
>>>>> + local theName="${1}" # allow or block
>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input
>>>>> custom list of domains
>>>>> +
>>>>> + # remove good:
>>>>> + # - properly formated domain names with or without leading
>>>>> wildcard
>>>>> + # - properly formated top level domain (TLD) names with wildcard
>>>>> + # - blank lines and comment lines
>>>>> + # remaining lines are considered "bad"
>>>>> + bad_lines=$( sed --regexp-extended \
>>>>> +
>>>>> '/^(\*\.)?([a-zA-Z0-9](([a-zA-Z0-9\-]){0,61}[a-zA-Z0-9])?\.)+([a-zA-Z]{2,}|xn--[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])$/d
>>>>> ;
>>>>> + /^(\*\.)([a-z]{2,61}|xn--[a-z0-9]{1,60})$/d ;
>>>>> + /^$/d ; /^;/d' "${theList}" )
>>>>> +
>>>>> + if [[ ! -z "${bad_lines}" ]] ; then
>>>>> + msg_log "error: rpz: invalid line(s) in ${theList}."
>>>>> + printf "%s\n" "bad line(s): ${bad_lines}"
>>>>> + exit 110
>>>>> + fi
>>>>> +}
>>>>> +
>>>>> +
>>>>> +############### Main ###############
>>>>> +
>>>>> +tagName="unbound"
>>>>> +
>>>>> +rpzName="${1}" # input RPZ name
>>>>> +
>>>>> +rpzLog="yes" # log default is yes
>>>>> +ucReload="yes" # reload default is yes
>>>>> +
>>>>> +while [[ $# -gt 0 ]] ; do
>>>>> + case "$1" in
>>>>> + --no-log ) rpzLog="no" ;;
>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>> + esac
>>>>> + shift # Shift after checking all the cases to get next
>>>>> option
>>>>> +done
>>>>> +
>>>>> +case "${rpzName}" in
>>>>> + # make a new allow or block rpz file
>>>>> +
>>>>> + allow )
>>>>> + validate_list 'allow' # is the allowlist valid?
>>>>> + make_allow_config "${rpzLog}"
>>>>> + make_rpz_file 'allow'
>>>>> + ;;
>>>>> +
>>>>> + allowblock )
>>>>> + validate_list 'allow' # is the list valid?
>>>>> + make_allow_config "${rpzLog}"
>>>>> + make_rpz_file 'allow'
>>>>> + ;&
>>>>> +
>>>>> + block )
>>>>> + validate_list 'block' # is the blocklist valid?
>>>>> + make_block_config "${rpzLog}"
>>>>> + make_rpz_file 'block'
>>>>> + ;;
>>>>> +
>>>>> + reload )
>>>>> + check_unbound_conf "${checkConf}"
>>>>> + ;;
>>>>> +
>>>>> + unbound-restart )
>>>>> + check_unbound_conf "${checkConf}"
>>>>> + unbound_restart
>>>>> + exit
>>>>> + ;;
>>>>> +
>>>>> + * )
>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>> + printf "Usage: $(basename "$0") <NAME> <OPTION> <OPTION>\n"
>>>>> + printf "Version: ${version}\n"
>>>>> + exit 108
>>>>> + ;;
>>>>> +esac
>>>>> +
>>>>> +unbound_control_reload "${ucReload}"
>>>>> +
>>>>> +exit
>>>>> diff --git a/config/rpz/rpz-metrics b/config/rpz/rpz-metrics
>>>>> new file mode 100755
>>>>> index 000000000..4d43e1629
>>>>> --- /dev/null
>>>>> +++ b/config/rpz/rpz-metrics
>>>>> @@ -0,0 +1,170 @@
>>>>> +#!/bin/bash
>>>>> +###############################################################################
>>>>> +#
>>>>> #
>>>>> +# IPFire.org - A linux based firewall
>>>>> #
>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org>
>>>>> #
>>>>> +#
>>>>> #
>>>>> +# 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
>>>>> <http://www.gnu.org/licenses/>. #
>>>>> +#
>>>>> #
>>>>> +###############################################################################
>>>>> +
>>>>> +version="2025-01-20 - v25"
>>>>> +
>>>>> +############### Main ###############
>>>>> +
>>>>> +weeks="2" # default to two message logs
>>>>> +sortBy="name" # default "by name"
>>>>> +rpzActive="enabled" # default "enabled only"
>>>>> +
>>>>> +while [[ $# -gt 0 ]] ; do
>>>>> + case "$1" in
>>>>> + --by-names | --by-name | name ) sortBy="name" ;;
>>>>> +
>>>>> + --by-hits | --by-hit | hits | hit ) sortBy="hit" ;;
>>>>> +
>>>>> + --by-lines | --by-line | lines | line ) sortBy="line"
>>>>> ;;
>>>>> +
>>>>> + --by-effect ) sortBy="effect" ;;
>>>>> +
>>>>> + --enabled-only ) rpzActive="enabled" ;;
>>>>> +
>>>>> + --active-all | --all | all ) rpzActive="all" ;;
>>>>> +
>>>>> + [0-9] | [0-9][0-9] ) weeks=$1 ;;
>>>>> + esac
>>>>> + shift # Shift after checking all the cases to get next
>>>>> option
>>>>> +done
>>>>> +
>>>>> +# get the list of message logs for N weeks
>>>>> +messageLogs=$( find /var/log/messages* -type f | sort
>>>>> --version-sort |
>>>>> + head -"${weeks}" )
>>>>> +
>>>>> +# get the list of RPZ names & counts from the message log(s)
>>>>> +rpzNameCount=$( for logf in ${messageLogs} ; do
>>>>> + zgrep --text --fixed-strings 'info: rpz: applied' "${logf}" |
>>>>> + awk '$10 ~ /\[\w*]/ { print $10 }' ;
>>>>> + done | sort | uniq --count )
>>>>> +
>>>>> +# flip results and remove brackets `[` and `]`
>>>>> +rpzNameCount=$( echo "${rpzNameCount}" |
>>>>> + awk '{ print $2, $1 }' |
>>>>> + sed --regexp-extended 's|^\[(.*)\]|\1|' )
>>>>> +
>>>>> +# grab only names
>>>>> +rpzNames=$( echo "${rpzNameCount}" | awk '{ print $1 }' )
>>>>> +
>>>>> +# get list of RPZ files
>>>>> +rpzFileList=$( find /etc/unbound/zonefiles -type f -iname "*.rpz" )
>>>>> +
>>>>> +# get basename of those files
>>>>> +rpzBaseNames=$( echo "${rpzFileList}" |
>>>>> + sed 's|/etc/unbound/zonefiles/||g ; s|\.rpz||g ;' )
>>>>> +
>>>>> +# add to rpzNames
>>>>> +rpzNames="${rpzNames}"$'\n'"${rpzBaseNames}"
>>>>> +
>>>>> +# drop duplicate names
>>>>> +rpzNames=$( echo "${rpzNames}" | sort --unique )
>>>>> +
>>>>> +# get line count for each RPZ
>>>>> +lineCount=$( echo "${rpzFileList}" | xargs wc -l )
>>>>> +
>>>>> +# get comment line count and blank line count for each RPZ
>>>>> +commentCount=$( echo "${rpzFileList}" | xargs grep --count -e
>>>>> "^$" -e "^;" )
>>>>> +
>>>>> +# get modified date each RPZ
>>>>> +modDateList=$( echo "${rpzFileList}" | xargs stat -c '%.10y %n' )
>>>>> +
>>>>> +ucListAuthZones=$( unbound-control list_auth_zones )
>>>>> +
>>>>> +# get width of RPZ names
>>>>> +pWidth=$( echo "${rpzNames}" | awk '{ print $1" " }' | wc -L )
>>>>> +pFormat="%-${pWidth}s %-8s %-8s %8s %12s %12s\n"
>>>>> +
>>>>> +# print title line
>>>>> +printf "${pFormat}" "name" "hits" "active" "lines" " hits/line"
>>>>> "last download"
>>>>> +printf -- "--------------"
>>>>> +
>>>>> +theResults=""
>>>>> +totalLines=0
>>>>> +totalHits=0
>>>>> +while read -r theName
>>>>> +do
>>>>> + printf -- "--" # pretend progress bar
>>>>> +
>>>>> + # is this RPZ list active?
>>>>> + theActive="disabled"
>>>>> + if grep --quiet "^${theName}\.rpz" <<< "${ucListAuthZones}"
>>>>> + then
>>>>> + theActive="enabled"
>>>>> + else
>>>>> + [[ "${rpzActive}" == enabled ]] && continue
>>>>> + fi
>>>>> +
>>>>> + # get hit count
>>>>> + theHits="0"
>>>>> + if output=$( grep "^${theName}\s" <<< "${rpzNameCount}" ) ; then
>>>>> + theHits=$( echo "${output}" | awk '{ print $2 }' )
>>>>> + totalHits=$(( totalHits + theHits ))
>>>>> + fi
>>>>> +
>>>>> + # get line count
>>>>> + theLines="n/a"
>>>>> + hitsPerLine="0"
>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<<
>>>>> "${lineCount}" ) ; then
>>>>> + theLines=$( echo "${output}" | awk '{ print $1 }' )
>>>>> + totalLines=$(( totalLines + theLines ))
>>>>> +
>>>>> + if [[ "${theLines}" -gt 2 ]] ; then
>>>>> + hitsPerLine=$(( 100 * theHits / theLines ))
>>>>> + fi
>>>>> + fi
>>>>> +
>>>>> + # get modification date
>>>>> + theModDate="n/a"
>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<<
>>>>> "${modDateList}" ) ; then
>>>>> + theModDate=$( echo "${output}" | awk '{ print $1 }' )
>>>>> + fi
>>>>> +
>>>>> + # add to results list
>>>>> + theResults+="${theName} ${theHits} ${theActive} ${theLines}
>>>>> ${hitsPerLine} ${theModDate}"$'\n'
>>>>> +
>>>>> +done <<< "${rpzNames}"
>>>>> +
>>>>> +case "${sortBy}" in
>>>>> + # sort by "active" then by "name"
>>>>> + name) sortArg=(-k3,3r -k1,1) ;;
>>>>> +
>>>>> + # sort by "active" then by "hits" then by "name"
>>>>> + hit) sortArg=(-k3,3r -k2,2nr -k1,1) ;;
>>>>> +
>>>>> + # sort by "active" then by "lines" then by "name"
>>>>> + line) sortArg=(-k3,3r -k4,4nr -k1,1) ;;
>>>>> +
>>>>> + # sort by "active" then by "effect" then by "name"
>>>>> + effect) sortArg=(-k3,3r -k5,5nr -k1,1) ;;
>>>>> +esac
>>>>> +
>>>>> +printf -- "--------------\n"
>>>>> +# remove blank lines, sort, print as columns
>>>>> +echo "${theResults}" |
>>>>> + awk '!/^[[:space:]]*$/' |
>>>>> + sort "${sortArg[@]}" |
>>>>> + awk --assign=width="${pWidth}" \
>>>>> + '{ printf "%-*s %-8s %-8s %8s %10s %% %12s\n", width, $1,
>>>>> $2, $3, $4, $5, $6 }'
>>>>> +
>>>>> +printf "${pFormat}" "" "=======" "" "========" "" ""
>>>>> +printf "${pFormat}" "Totals -->" "${totalHits}" ""
>>>>> "${totalLines}" "" ""
>>>>> +
>>>>> +exit
>>>>> diff --git a/config/rpz/rpz-sleep b/config/rpz/rpz-sleep
>>>>> new file mode 100755
>>>>> index 000000000..dd3603599
>>>>> --- /dev/null
>>>>> +++ b/config/rpz/rpz-sleep
>>>>> @@ -0,0 +1,58 @@
>>>>> +#!/bin/bash
>>>>> +###############################################################################
>>>>> +#
>>>>> #
>>>>> +# IPFire.org - A linux based firewall
>>>>> #
>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org>
>>>>> #
>>>>> +#
>>>>> #
>>>>> +# 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
>>>>> <http://www.gnu.org/licenses/>. #
>>>>> +#
>>>>> #
>>>>> +###############################################################################
>>>>> +
>>>>> +version="2024-08-16" # v05
>>>>> +
>>>>> +############### Functions ###############
>>>>> +
>>>>> +# send message to message log
>>>>> +msg_log () {
>>>>> + logger --tag "${tagName}" "$*"
>>>>> + if tty --silent ; then
>>>>> + echo "${tagName}:" "$*"
>>>>> + fi
>>>>> +}
>>>>> +
>>>>> +############### Main ###############
>>>>> +
>>>>> +tagName="unbound"
>>>>> +
>>>>> +sleepTime="${1:-5m}" # default to sleep for 5m (5
>>>>> minutes)
>>>>> +
>>>>> +zoneList=$( unbound-control list_auth_zones | awk '{print $1}' )
>>>>> +
>>>>> +for zone in ${zoneList} ; do
>>>>> + printf "disable ${zone}\t"
>>>>> + unbound-control rpz_disable "${zone}"
>>>>> +done
>>>>> +
>>>>> +msg_log "info: rpz: disabled all zones for ${sleepTime}"
>>>>> +
>>>>> +sleep "${sleepTime}"
>>>>> +
>>>>> +for zone in ${zoneList} ; do
>>>>> + printf "enable ${zone}\t"
>>>>> + unbound-control rpz_enable "${zone}"
>>>>> +done
>>>>> +
>>>>> +msg_log "info: rpz: enabled all zones"
>>>>> +
>>>>> +exit
>>>>> diff --git a/config/rpz/rpz.de.pl b/config/rpz/rpz.de.pl
>>>>> new file mode 100644
>>>>> index 000000000..3770c6bb0
>>>>> --- /dev/null
>>>>> +++ b/config/rpz/rpz.de.pl
>>>>> @@ -0,0 +1,30 @@
>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>> +%tr = (%tr,
>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>> +'rpz apply' => 'Übernehmen',
>>>>> +'rpz cl allow enable' => 'Benutzerdefinierte Allowlist aktivieren:',
>>>>> +'rpz cl allow info' => 'Zugelassene Domains (eine pro
>>>>> Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>> +'rpz cl allow' => 'Benutzerdefinierte Allowlist',
>>>>> +'rpz cl block enable' => 'Benutzerdefinierte Blocklist aktivieren:',
>>>>> +'rpz cl block info' => 'Gesperrte Domains (eine pro
>>>>> Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>> +'rpz cl block' => 'Benutzerdefinierte Blocklist',
>>>>> +'rpz cl' => 'Benutzerdefinierte Listen',
>>>>> +'rpz exitcode 101' => 'Der Name enthält unzulässige Zeichen',
>>>>> +'rpz exitcode 102' => 'unbound-checkconf hat eine fehlerhafte
>>>>> Konfiguration ermittelt. Führen Sie das Kommando unbound-checkconf
>>>>> auf der Konsole aus, um weitere Informationen zu erhalten.',
>>>>> +'rpz exitcode 103' => 'Die benutzerdefinierte Allow-/Blocklist
>>>>> ist leer',
>>>>> +'rpz exitcode 104' => 'Ein Eintrag mit identischem Namen
>>>>> existiert bereits',
>>>>> +'rpz exitcode 105' => 'Die URL ist ungültig',
>>>>> +'rpz exitcode 106' => 'Eintrag kann nicht entfernt werden, der
>>>>> Name existiert nicht',
>>>>> +'rpz exitcode 107' => 'Der Name ist ungültig - nur "allow" oder
>>>>> "block" möglich',
>>>>> +'rpz exitcode 108' => 'Fehlende oder inkorrekte Parameter',
>>>>> +'rpz exitcode 109' => 'unbound-control reload ist fehlgeschlagen',
>>>>> +'rpz exitcode 110' => 'Die benutzerdefinierte Allow-/Blocklist
>>>>> enthält unzulässige Einträge',
>>>>> +'rpz exitcode 201' => 'Die Anmerkung enthält unzulässige Zeichen',
>>>>> +'rpz exitcode 202' => 'Ungültiger Eintrag in der
>>>>> benutzerdefinierten Allowlist, Zeile ',
>>>>> +'rpz exitcode 203' => 'Ungültiger Eintrag in der
>>>>> benutzerdefinierten Blocklist, Zeile ',
>>>>> +'rpz exitcode 204' => 'Ausgewählter Eintrag existiert nicht: ',
>>>>> +'rpz zf editor' => 'Zonendatei-Eintrag bearbeiten',
>>>>> +'rpz zf imported' => '(importiert aus rpz-config)',
>>>>> +'rpz zf remark info' => 'Erlaubte Zeichen sind a-z, A-Z, 0-9 und
>>>>> Unterstriche',
>>>>> +'rpz zf' => 'Zonendateien',
>>>>> +);
>>>>> diff --git a/config/rpz/rpz.en.pl b/config/rpz/rpz.en.pl
>>>>> new file mode 100644
>>>>> index 000000000..0720a8940
>>>>> --- /dev/null
>>>>> +++ b/config/rpz/rpz.en.pl
>>>>> @@ -0,0 +1,30 @@
>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>> +%tr = (%tr,
>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>> +'rpz apply' => 'Apply',
>>>>> +'rpz cl allow enable' => 'Enable custom allowlist:',
>>>>> +'rpz cl allow info' => 'Allowed domains (one per
>>>>> line)<br>Example: domain.com, *.domain.com',
>>>>> +'rpz cl allow' => 'Custom allowlist',
>>>>> +'rpz cl block enable' => 'Enable custom blocklist:',
>>>>> +'rpz cl block info' => 'Blocked domains (one per
>>>>> line)<br>Example: domain.com, *.domain.com',
>>>>> +'rpz cl block' => 'Custom blocklist',
>>>>> +'rpz cl' => 'Custom lists',
>>>>> +'rpz exitcode 101' => 'the NAME is not valid',
>>>>> +'rpz exitcode 102' => 'unbound-checkconf found invalid
>>>>> configuration. In the Terminal run the command unbound-checkconf
>>>>> for more information',
>>>>> +'rpz exitcode 103' => 'the allow/block list is empty',
>>>>> +'rpz exitcode 104' => 'duplicate - NAME already exists',
>>>>> +'rpz exitcode 105' => 'the URL is not valid',
>>>>> +'rpz exitcode 106' => 'cannot remove the NAME does not exist',
>>>>> +'rpz exitcode 107' => 'the NAME is not valid - "allow" or "block"
>>>>> only',
>>>>> +'rpz exitcode 108' => 'missing or incorrect parameter',
>>>>> +'rpz exitcode 109' => 'unbound-control reload failed',
>>>>> +'rpz exitcode 110' => 'custom Allowlist/Blocklist contains
>>>>> invalid entries',
>>>>> +'rpz exitcode 201' => 'the REMARK is not valid',
>>>>> +'rpz exitcode 202' => 'invalid entry in allowlist, line ',
>>>>> +'rpz exitcode 203' => 'invalid entry in blocklist, line ',
>>>>> +'rpz exitcode 204' => 'Selected entry does not exist: ',
>>>>> +'rpz zf editor' => 'Edit zonefiles entry',
>>>>> +'rpz zf imported' => '(imported from rpz-config)',
>>>>> +'rpz zf remark info' => 'Valid characters are a-z, A-Z, 0-9 and
>>>>> underscore.',
>>>>> +'rpz zf' => 'Zonefiles',
>>>>> +);
>>>>> diff --git a/config/rpz/rpz.es.pl b/config/rpz/rpz.es.pl
>>>>> new file mode 100644
>>>>> index 000000000..98628e4aa
>>>>> --- /dev/null
>>>>> +++ b/config/rpz/rpz.es.pl
>>>>> @@ -0,0 +1,30 @@
>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>> +%tr = (%tr,
>>>>> +'rpz' => 'Zonas de política de respuesta (RPZ)',
>>>>> +'rpz apply' => 'Aplicar',
>>>>> +'rpz cl allow enable' => 'Habilitar la lista blanca personalizada:',
>>>>> +'rpz cl allow info' => 'Dominio permitido (uno por
>>>>> línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>> +'rpz cl allow' => 'Lista blanca personalizada',
>>>>> +'rpz cl block enable' => 'Habilitar la lista negra personalizada:',
>>>>> +'rpz cl block info' => 'Dominio bloqueado (uno por
>>>>> línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>> +'rpz cl block' => 'Lista negra personalizada',
>>>>> +'rpz cl' => 'Lista personalizada',
>>>>> +'rpz exitcode 101' => 'El NOMBRE no es válido',
>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha encontrado una
>>>>> configuración no válida. Desde Terminal, ejecute el comando
>>>>> unbound-checkconf para mayor información',
>>>>> +'rpz exitcode 103' => 'La lista de permitidos/bloqueados está vacía',
>>>>> +'rpz exitcode 104' => 'duplicado - NOMBRE ya existe',
>>>>> +'rpz exitcode 105' => 'la URL no es válida',
>>>>> +'rpz exitcode 106' => 'no es posible eliminar el NOMBRE que no
>>>>> existe',
>>>>> +'rpz exitcode 107' => 'el NOMBRE no es válido - sólo "permitir" o
>>>>> "bloquear"',
>>>>> +'rpz exitcode 108' => 'parámetro faltante o incorrecto',
>>>>> +'rpz exitcode 109' => 'Error al recargar unbound-control',
>>>>> +'rpz exitcode 110' => 'la Lista blanca/Lista negra personalizada
>>>>> contiene entradas no válidas',
>>>>> +'rpz exitcode 201' => 'el COMENTARIO no es válido',
>>>>> +'rpz exitcode 202' => 'entrada no válida en la lista blanca, línea ',
>>>>> +'rpz exitcode 203' => 'entrada no válida en la lista negra, línea ',
>>>>> +'rpz exitcode 204' => 'La entrada seleccionada no existe: ',
>>>>> +'rpz zf editor' => 'Editar la entrada de archivos de zona',
>>>>> +'rpz zf imported' => '(importado de rpz-config)',
>>>>> +'rpz zf remark info' => 'Los caracteres válidos son a-z, A-Z, 0-9
>>>>> y guión bajo',
>>>>> +'rpz zf' => 'Archivos de zona',
>>>>> +);
>>>>> diff --git a/config/rpz/rpz.fr.pl b/config/rpz/rpz.fr.pl
>>>>> new file mode 100644
>>>>> index 000000000..f35f3c2d0
>>>>> --- /dev/null
>>>>> +++ b/config/rpz/rpz.fr.pl
>>>>> @@ -0,0 +1,30 @@
>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>> +%tr = (%tr,
>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>> +'rpz apply' => 'Appliquer',
>>>>> +'rpz cl allow enable' => 'Activer la liste d\'autorisations
>>>>> personnalisée:',
>>>>> +'rpz cl allow info' => 'Domaines autorisés (un par
>>>>> ligne)<br>Example: domain.com, *.domain.com',
>>>>> +'rpz cl allow' => 'Liste d\'autorisations personnalisée',
>>>>> +'rpz cl block enable' => 'Activer la liste de blocage
>>>>> personnalisée:',
>>>>> +'rpz cl block info' => 'Domaines bloqués (un par
>>>>> ligne)<br>Example: domain.com, *.domain.com',
>>>>> +'rpz cl block' => 'liste de blocage personnalisé',
>>>>> +'rpz cl' => 'Listes personnalisées',
>>>>> +'rpz exitcode 101' => 'le NOM n\'est pas valide',
>>>>> +'rpz exitcode 102' => 'unbound-checkconf configuration non valide
>>>>> trouvée. Dans le terminal, exécutez la commande unbound-checkconf
>>>>> pour plus d\'informations',
>>>>> +'rpz exitcode 103' => 'la liste autoriser/bloquer est vide',
>>>>> +'rpz exitcode 104' => 'le NOM existe déjà',
>>>>> +'rpz exitcode 105' => 'L\'URL n\'est pas valide',
>>>>> +'rpz exitcode 106' => 'impossible de supprimer le NOM n\'existe pas',
>>>>> +'rpz exitcode 107' => 'le NOM n\'est pas valide - « autoriser »
>>>>> ou « bloquer » seulement',
>>>>> +'rpz exitcode 108' => 'paramètre manquant ou incorrect',
>>>>> +'rpz exitcode 109' => 'unbound-control rechargement échoué',
>>>>> +'rpz exitcode 110' => 'la liste autoriser/bloquer contient des
>>>>> entrées non valides',
>>>>> +'rpz exitcode 201' => 'la REMARQUE n\'est pas valable',
>>>>> +'rpz exitcode 202' => 'entrée non valide dans la liste
>>>>> d\'autorisation, ligne ',
>>>>> +'rpz exitcode 203' => 'entrée non valide dans la liste de blocs,
>>>>> ligne ',
>>>>> +'rpz exitcode 204' => 'L\'entrée sélectionnée n\'existe pas: ',
>>>>> +'rpz zf editor' => 'Modifier l\'entrée Fichiers Zone',
>>>>> +'rpz zf imported' => '(importé de rpz-config)',
>>>>> +'rpz zf remark info' => 'Les caractères valides sont a-z, A-Z,
>>>>> 0-9 et soulignement.',
>>>>> +'rpz zf' => 'Fichiers Zone',
>>>>> +);
>>>>> diff --git a/config/rpz/rpz.it.pl b/config/rpz/rpz.it.pl
>>>>> new file mode 100644
>>>>> index 000000000..ee81605c9
>>>>> --- /dev/null
>>>>> +++ b/config/rpz/rpz.it.pl
>>>>> @@ -0,0 +1,30 @@
>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>> +%tr = (%tr,
>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>> +'rpz apply' => 'Applica',
>>>>> +'rpz cl allow enable' => 'Abilita la Whitelist personalizzata:',
>>>>> +'rpz cl allow info' => 'Domini consentiti (uno per
>>>>> riga)<br>Esempio: domain.com, *.domain.com',
>>>>> +'rpz cl allow' => 'Whitelist personalizzata',
>>>>> +'rpz cl block enable' => 'Abilita la Blacklist personalizzata:',
>>>>> +'rpz cl block info' => 'Domini bloccati (uno per
>>>>> riga)<br>Esempio: domain.com, *.domain.com',
>>>>> +'rpz cl block' => 'Blacklist personalizzata',
>>>>> +'rpz cl' => 'Liste personalizzate',
>>>>> +'rpz exitcode 101' => 'il NOME non è valido',
>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha trovato una
>>>>> configurazione non valida. Dal Terminale esegui il comando
>>>>> unbound-checkconf per maggiori informazioni',
>>>>> +'rpz exitcode 103' => 'l\'elenco consentiti/bloccati è vuoto',
>>>>> +'rpz exitcode 104' => 'duplicato - NAME esiste di già',
>>>>> +'rpz exitcode 105' => 'l\'URL non è valido',
>>>>> +'rpz exitcode 106' => 'non è possibile rimuovere il NOME non esiste',
>>>>> +'rpz exitcode 107' => 'il NOME non è valido - solo "consenti" o
>>>>> "blocca"',
>>>>> +'rpz exitcode 108' => 'parametro mancante o non corretto',
>>>>> +'rpz exitcode 109' => 'ricaricamento del controllo non associato
>>>>> non riuscito',
>>>>> +'rpz exitcode 110' => 'la Whitelist/Blacklist personalizzata
>>>>> contiene voci non valide',
>>>>> +'rpz exitcode 201' => 'l"OSSERVAZIONE non è valida',
>>>>> +'rpz exitcode 202' => 'voce non valida nella Whitelist, riga ',
>>>>> +'rpz exitcode 203' => 'voce non valida nella Blacklist, riga ',
>>>>> +'rpz exitcode 204' => 'La voce selezionata non esiste: ',
>>>>> +'rpz zf editor' => 'Modifica la voce dei file di zona',
>>>>> +'rpz zf imported' => '(importato da rpz-config)',
>>>>> +'rpz zf remark info' => 'I caratteri validi sono a-z, A-Z, 0-9 e
>>>>> trattino basso',
>>>>> +'rpz zf' => 'Zonefiles',
>>>>> +);
>>>>> diff --git a/config/rpz/rpz.tr.pl b/config/rpz/rpz.tr.pl
>>>>> new file mode 100644
>>>>> index 000000000..00226e192
>>>>> --- /dev/null
>>>>> +++ b/config/rpz/rpz.tr.pl
>>>>> @@ -0,0 +1,30 @@
>>>>> +# I�in eklendi Ayriyeten Response Policy Zone (RPZ)
>>>>> +%tr = (%tr,
>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>> +'rpz apply' => 'Uygulamak',
>>>>> +'rpz cl allow enable' => '�zel Etkinlestir allowlist:',
>>>>> +'rpz cl allow info' => 'Izin verilmis domains (satir basina
>>>>> bir)<br>�rnegin: domain.com, *.domain.com',
>>>>> +'rpz cl allow' => 'Etkinlestir allowlist',
>>>>> +'rpz cl block enable' => '�zel Etkinlestir blocklist:',
>>>>> +'rpz cl block info' => 'Engellenmis domains (satir basina
>>>>> bir)<br>�rnegin: domain.com, *.domain.com',
>>>>> +'rpz cl block' => 'Engellenmis blocklist',
>>>>> +'rpz cl' => 'Engellenmis lists',
>>>>> +'rpz exitcode 101' => 'NAME ge�erli degil',
>>>>> +'rpz exitcode 102' => 'unbound-checkconf ge�ersiz yapilandirma
>>>>> bulundu. Terminalde daha fazla bilgi i�in Unbound-checkConf
>>>>> komutunu �alistirin',
>>>>> +'rpz exitcode 103' => 'allow/block liste bos',
>>>>> +'rpz exitcode 104' => 'kopyalamak - NAME zaten var',
>>>>> +'rpz exitcode 105' => 'URL ge�erli degil',
>>>>> +'rpz exitcode 106' => '�ikarilamiyor NAME yok',
>>>>> +'rpz exitcode 107' => 'NAME ge�erli degil - "allow" veya "block"
>>>>> yalniz',
>>>>> +'rpz exitcode 108' => 'Parametre eksik veya yanlis',
>>>>> +'rpz exitcode 109' => 'unbound-control basarisiz',
>>>>> +'rpz exitcode 110' => 'Engellenmis Allowlist/Blocklist ge�ersiz
>>>>> girisler i�erir',
>>>>> +'rpz exitcode 201' => 'REMARK ge�erli degil',
>>>>> +'rpz exitcode 202' => 'Ge�ersiz giris allowlist, line ',
>>>>> +'rpz exitcode 203' => 'Ge�ersiz giris blocklist, line ',
>>>>> +'rpz exitcode 204' => 'Se�ilen giris yok: ',
>>>>> +'rpz zf editor' => 'yazimlamak zonefiles giris',
>>>>> +'rpz zf imported' => '(ithal edildi rpz-config)',
>>>>> +'rpz zf remark info' => 'Ge�erli karakterler a-z, A-Z, 0-9ve
>>>>> alt�st.',
>>>>> +'rpz zf' => 'Zonefiles',
>>>>> +);
>>>>> diff --git a/html/cgi-bin/rpz.cgi b/html/cgi-bin/rpz.cgi
>>>>> new file mode 100644
>>>>> index 000000000..a821c92ac
>>>>> --- /dev/null
>>>>> +++ b/html/cgi-bin/rpz.cgi
>>>>> @@ -0,0 +1,923 @@
>>>>> +#!/usr/bin/perl
>>>>> +###############################################################################
>>>>> +#
>>>>> #
>>>>> +# IPFire.org - A linux based firewall
>>>>> #
>>>>> +# Copyright (C) 2005-2024 IPFire Team <info@ipfire.org>
>>>>> #
>>>>> +#
>>>>> #
>>>>> +# 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
>>>>> <http://www.gnu.org/licenses/>. #
>>>>> +#
>>>>> #
>>>>> +###############################################################################
>>>>> +
>>>>> +use strict;
>>>>> +use Scalar::Util qw(looks_like_number);
>>>>> +
>>>>> +# debugging
>>>>> +#use warnings;
>>>>> +#use CGI::Carp 'fatalsToBrowser';
>>>>> +#use Data::Dumper;
>>>>> +
>>>>> +require '/var/ipfire/general-functions.pl';
>>>>> +require "${General::swroot}/lang.pl";
>>>>> +require "${General::swroot}/header.pl";
>>>>> +
>>>>> +###--- Extra HTML ---###
>>>>> +my $extraHead = <<END
>>>>> +<style>
>>>>> + /* alternating row background */
>>>>> + .tbl tr:nth-child(2n+2) {
>>>>> + background-color: var(--color-light-grey);
>>>>> + }
>>>>> + .tbl tr:nth-child(2n+3) {
>>>>> + background-color: var(--color-grey);
>>>>> + }
>>>>> + /* text styles */
>>>>> + .tbl th:not(:last-child) {
>>>>> + text-align: left;
>>>>> + }
>>>>> + div.right {
>>>>> + text-align: right;
>>>>> + margin-top: 0.5em;
>>>>> + }
>>>>> + /* customlist input */
>>>>> + textarea.domainlist {
>>>>> + margin: 0.5em 0;
>>>>> + resize: vertical;
>>>>> + min-height: 10em;
>>>>> + overflow: auto;
>>>>> + white-space: pre;
>>>>> + }
>>>>> + button[type=submit]:disabled {
>>>>> + opacity: 0.6;
>>>>> + }
>>>>> +</style>
>>>>> +END
>>>>> +;
>>>>> +###--- End of extra HTML ---###
>>>>> +
>>>>> +
>>>>> +### Settings ###
>>>>> +
>>>>> +# Request DNS service reload after configuration change
>>>>> +my $RPZ_RELOAD_FLAG = "${General::swroot}/dns/rpz/reload.flag";
>>>>> +
>>>>> +# Configuration file for all available zonefiles
>>>>> +# Format: index, name (unique), enabled (on/off), URL, remark
>>>>> +my $ZONEFILES_CONF = "${General::swroot}/dns/rpz/zonefiles.conf";
>>>>> +
>>>>> +# Configuration file for custom lists
>>>>> +# IDs: 0=allowlist, 1=blocklist, 2=options (allow/block enabled)
>>>>> +my $CUSTOMLISTS_CONF = "${General::swroot}/dns/rpz/customlists.conf";
>>>>> +
>>>>> +# Export custom lists to rpz-config
>>>>> +my $RPZ_ALLOWLIST = "${General::swroot}/dns/rpz/allowlist";
>>>>> +my $RPZ_BLOCKLIST = "${General::swroot}/dns/rpz/blocklist";
>>>>> +
>>>>> +
>>>>> +### Preparation ###
>>>>> +
>>>>> +# Create missing config files
>>>>> +unless(-f $ZONEFILES_CONF) { &General::system('touch',
>>>>> "$ZONEFILES_CONF"); }
>>>>> +unless(-f $CUSTOMLISTS_CONF) { &General::system('touch',
>>>>> "$CUSTOMLISTS_CONF"); }
>>>>> +
>>>>> +
>>>>> +## Global gui data
>>>>> +my $errormessage = "";
>>>>> +
>>>>> +## Global configuration data
>>>>> +my %zonefiles = ();
>>>>> +my %customlists = ();
>>>>> +&_zonefiles_load();
>>>>> +&_customlists_load();
>>>>> +
>>>>> +## Global CGI form data
>>>>> +my %cgiparams = ();
>>>>> +&Header::getcgihash(\%cgiparams);
>>>>> +
>>>>> +my $action = $cgiparams{'ACTION'} // 'NONE';
>>>>> +my $action_key = $cgiparams{'KEY'} // ''; # entry being edited,
>>>>> empty = none/new
>>>>> +
>>>>> +
>>>>> +###--- Process form actions ---###
>>>>> +
>>>>> +# Zonefiles action: Check whether the requested entry exists
>>>>> +if((substr($action, 0, 3) eq 'ZF_') && ($action_key)) {
>>>>> + unless(defined $zonefiles{$action_key}) {
>>>>> + $errormessage = &_rpz_error_tr(204, $action_key);
>>>>> + $action = 'NONE';
>>>>> + }
>>>>> +}
>>>>> +
>>>>> +## Perform actions
>>>>> +if($action eq 'ZF_SAVE') { ## Save new or modified zonefiles entry
>>>>> + if(&_action_zf_save()) {
>>>>> + $action = 'NONE'; # success, return to main page
>>>>> + &_http_prg_redirect();
>>>>> + } else {
>>>>> + $action = 'ZF_EDIT'; # error occured, keep editing
>>>>> + }
>>>>> +
>>>>> +} elsif($action eq 'ZF_TOGGLE') { ## Toggle on/off
>>>>> + if(&_action_zf_toggle()) {
>>>>> + $action = 'NONE';
>>>>> + &_http_prg_redirect();
>>>>> + }
>>>>> +
>>>>> +} elsif($action eq 'ZF_REMOVE') { ## Remove entry
>>>>> + if(&_action_zf_remove()) {
>>>>> + $action = 'NONE';
>>>>> + &_http_prg_redirect();
>>>>> + }
>>>>> +
>>>>> +} elsif($action eq 'CL_SAVE') { ## Save custom lists
>>>>> + if(&_action_cl_save()) {
>>>>> + $action = 'NONE';
>>>>> + &_http_prg_redirect();
>>>>> + }
>>>>> +
>>>>> +} elsif($action eq 'RPZ_RELOAD') { ## Reload dns configuration
>>>>> + if(&_action_rpz_reload()) {
>>>>> + $action = 'NONE';
>>>>> + &_http_prg_redirect();
>>>>> + }
>>>>> +
>>>>> +} elsif($action eq 'UNB_RESTART') { ## Restart unbound service
>>>>> + if(&_action_unb_restart()) {
>>>>> + $action = 'NONE';
>>>>> + &_http_prg_redirect();
>>>>> + }
>>>>> +
>>>>> +}
>>>>> +
>>>>> +
>>>>> +###--- Start GUI ---###
>>>>> +
>>>>> +## Start http output
>>>>> +&Header::showhttpheaders();
>>>>> +
>>>>> +# Start HTML
>>>>> +&Header::openpage($Lang::tr{'rpz'}, 1, $extraHead);
>>>>> +&Header::openbigbox('100%', 'left', '');
>>>>> +
>>>>> +# Show error messages
>>>>> +if($errormessage) {
>>>>> + &_print_message($errormessage);
>>>>> +}
>>>>> +
>>>>> +# Handle zonefile add/edit mode
>>>>> +if($action eq "ZF_EDIT") {
>>>>> + &_print_zonefile_editor();
>>>>> +
>>>>> + # Finalize page and exit cleanly
>>>>> + &Header::closebigbox();
>>>>> + &Header::closepage();
>>>>> + exit(0);
>>>>> +}
>>>>> +
>>>>> +# Show gui elements
>>>>> +&_print_zonefiles();
>>>>> +&_print_customlists();
>>>>> +&_print_gui_extras();
>>>>> +
>>>>> +&Header::closebigbox();
>>>>> +&Header::closepage();
>>>>> +
>>>>> +###--- End of GUI ---###
>>>>> +
>>>>> +
>>>>> +###--- Internal configuration file functions ---###
>>>>> +
>>>>> +# Load all available zonefiles from rpz-config and the internal
>>>>> configuration
>>>>> +sub _zonefiles_load {
>>>>> + # Clean start
>>>>> + %zonefiles = ();
>>>>> +
>>>>> + # Source 1: Get the currently enabled zonefiles from rpz-config
>>>>> (expected format [name]=[URL])
>>>>> + my @enabled_files =
>>>>> &General::system_output('/usr/sbin/rpz-config', 'list');
>>>>> +
>>>>> + foreach my $row (@enabled_files) {
>>>>> + chomp($row);
>>>>> +
>>>>> + # Use regex instead of split() to skip non-matching lines
>>>>> + next unless($row =~ /^(\w+)=(.+)$/);
>>>>> + my ($name, $url) = ($1, $2);
>>>>> +
>>>>> + # Unique names are already guaranteed by rpz-config
>>>>> + if(&_rpz_validate_zonefile($name, $url, '', 0) == 0) {
>>>>> + # Populate global data hash, mark all found entries as enabled
>>>>> + my %entry = ('enabled' => 'on',
>>>>> + 'url' => $url,
>>>>> + 'remark' => $Lang::tr{'rpz zf imported'});
>>>>> +
>>>>> + $zonefiles{$name} = \%entry;
>>>>> + }
>>>>> + }
>>>>> +
>>>>> + # Source 2: Get additional data and disabled entries from
>>>>> configuration file
>>>>> + my %configured_files = ();
>>>>> + &General::readhasharray($ZONEFILES_CONF, \%configured_files);
>>>>> +
>>>>> + foreach my $row (values (%configured_files)) {
>>>>> + my ($name, $enabled, $url, $remark) = @$row;
>>>>> + $remark //= "";
>>>>> +
>>>>> + next unless($name);
>>>>> +
>>>>> + # Check whether this row belongs to an entry already imported
>>>>> from rpz-config
>>>>> + if(defined $zonefiles{$name}) {
>>>>> + # Existing entry, only merge additional data
>>>>> + $zonefiles{$name}{'remark'} = $remark;
>>>>> + } else {
>>>>> + # Skip entry if it is marked as enabled but not found by
>>>>> rpz-config. It was then deleted manually
>>>>> + if($enabled ne 'on') {
>>>>> + # Populate global data hash
>>>>> + my %entry = ('enabled' => 'off',
>>>>> + 'url' => $url // "",
>>>>> + 'remark' => $remark);
>>>>> +
>>>>> + $zonefiles{$name} = \%entry;
>>>>> + }
>>>>> + }
>>>>> + }
>>>>> +}
>>>>> +
>>>>> +# Save internal zonefiles configuration
>>>>> +sub _zonefiles_save_conf {
>>>>> + my $index = 0;
>>>>> + my %export = ();
>>>>> +
>>>>> + # Loop trough all zonefiles and create "hasharray" type export
>>>>> + foreach my $name (keys %zonefiles) {
>>>>> + my @entry = ($name,
>>>>> + $zonefiles{$name}{'enabled'},
>>>>> + $zonefiles{$name}{'url'},
>>>>> + $zonefiles{$name}{'remark'});
>>>>> +
>>>>> + $export{$index++} = \@entry;
>>>>> + }
>>>>> +
>>>>> + &General::writehasharray($ZONEFILES_CONF, \%export);
>>>>> +}
>>>>> +
>>>>> +# Load custom lists from rpz-config and the internal configuration
>>>>> +sub _customlists_load {
>>>>> + # Clean start
>>>>> + %customlists = ();
>>>>> +
>>>>> + # Load configuration file
>>>>> + my %lists_conf = ();
>>>>> + &General::readhasharray($CUSTOMLISTS_CONF, \%lists_conf);
>>>>> +
>>>>> + # Get list options, enabled by default to start import
>>>>> + $customlists{'allow'}{'enabled'} = $lists_conf{2}[0] // 'on';
>>>>> + $customlists{'block'}{'enabled'} = $lists_conf{2}[1] // 'on';
>>>>> +
>>>>> + # Import enabled list from rpz-config, otherwise retrieve stored
>>>>> or empty list from configuration file
>>>>> + if($customlists{'allow'}{'enabled'} eq 'on') {
>>>>> + &_customlist_import('allow', $RPZ_ALLOWLIST);
>>>>> + } else {
>>>>> + $customlists{'allow'}{'list'} = $lists_conf{0} // [];
>>>>> + }
>>>>> + if($customlists{'block'}{'enabled'} eq 'on') {
>>>>> + &_customlist_import('block', $RPZ_BLOCKLIST);
>>>>> + } else {
>>>>> + $customlists{'block'}{'list'} = $lists_conf{1} // [];
>>>>> + }
>>>>> +}
>>>>> +
>>>>> +# Save internal custom lists configuration
>>>>> +sub _customlists_save_conf {
>>>>> + my %export = ();
>>>>> +
>>>>> + # Match IDs with import function
>>>>> + $export{0} = $customlists{'allow'}{'list'};
>>>>> + $export{1} = $customlists{'block'}{'list'};
>>>>> + $export{2} = [$customlists{'allow'}{'enabled'},
>>>>> $customlists{'block'}{'enabled'}];
>>>>> +
>>>>> + &General::writehasharray($CUSTOMLISTS_CONF, \%export);
>>>>> +}
>>>>> +
>>>>> +# Import a custom list from plain file, returns empty list if
>>>>> file is missing
>>>>> +sub _customlist_import {
>>>>> + my ($listname, $filename) = @_;
>>>>> + my @list = ();
>>>>> +
>>>>> + # File exists, load and check all lines
>>>>> + if(-f $filename) {
>>>>> + open(my $FH, '<', $filename) or die "Can't read $filename: $!";
>>>>> + while(my $line = <$FH>) {
>>>>> + chomp($line);
>>>>> + push(@list, $line);
>>>>> + }
>>>>> + close($FH);
>>>>> +
>>>>> + # Clean up imported data
>>>>> + &_rpz_validate_customlist(\@list, 1);
>>>>> + }
>>>>> +
>>>>> + $customlists{$listname}{'list'} = \@list;
>>>>> +}
>>>>> +
>>>>> +# Export a custom list to plain file or clear file if list is
>>>>> disabled
>>>>> +sub _customlist_export {
>>>>> + my ($listname, $filename) = @_;
>>>>> + return unless(defined $customlists{$listname});
>>>>> +
>>>>> + # Write enabled domain list to file, otherwise save empty file
>>>>> + open(my $FH, '>', $filename) or die "Can't write $filename: $!";
>>>>> +
>>>>> + if($customlists{$listname}{'enabled'} eq 'on') {
>>>>> + foreach my $line (@{$customlists{$listname}{'list'}}) {
>>>>> + print $FH "$line\n";
>>>>> + }
>>>>> + } else {
>>>>> + print $FH "; Note: This list is currently disabled by
>>>>> $ENV{'SCRIPT_NAME'}\n";
>>>>> + }
>>>>> +
>>>>> + close($FH);
>>>>> +}
>>>>> +
>>>>> +
>>>>> +###--- Internal gui functions ---###
>>>>> +
>>>>> +# Show simple message box
>>>>> +sub _print_message {
>>>>> + my ($message, $title) = @_;
>>>>> + $title ||= $Lang::tr{'error messages'};
>>>>> +
>>>>> + &Header::openbox('100%', 'left', $title);
>>>>> + print "<span>$message</span>";
>>>>> + &Header::closebox();
>>>>> +}
>>>>> +
>>>>> +# Show all zone files and related gui elements
>>>>> +sub _print_zonefiles {
>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf'});
>>>>> +
>>>>> + print <<END
>>>>> +<table class="tbl" width="100%">
>>>>> + <tr>
>>>>> + <th>$Lang::tr{'name'}</th>
>>>>> + <th>URL</th>
>>>>> + <th>$Lang::tr{'remark'}</th>
>>>>> + <th colspan="3">$Lang::tr{'action'}</th>
>>>>> + </tr>
>>>>> +END
>>>>> +;
>>>>> +
>>>>> + # Sort zonefiles by name and loop trough all entries
>>>>> + foreach my $name (sort keys %zonefiles) {
>>>>> +
>>>>> + # Toggle button label translation
>>>>> + my $toggle_tr = ($zonefiles{$name}{'enabled'} eq 'on') ?
>>>>> $Lang::tr{'click to disable'} : $Lang::tr{'click to enable'};
>>>>> +
>>>>> + print <<END
>>>>> + <tr>
>>>>> + <td>$name</td>
>>>>> + <td>$zonefiles{$name}{'url'}</td>
>>>>> + <td>$zonefiles{$name}{'remark'}</td>
>>>>> +
>>>>> + <td align="center" width="5%">
>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>> + <input type="hidden" name="ACTION" value="ZF_TOGGLE">
>>>>> + <input type="image"
>>>>> src="/images/$zonefiles{$name}{'enabled'}.gif" title="$toggle_tr"
>>>>> alt="$toggle_tr">
>>>>> + </form>
>>>>> + </td>
>>>>> + <td align="center" width="5%">
>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>> + <input type="hidden" name="ACTION" value="ZF_EDIT">
>>>>> + <input type="image" src="/images/edit.gif"
>>>>> title="$Lang::tr{'edit'}" alt="$Lang::tr{'edit'}">
>>>>> + </form>
>>>>> + </td>
>>>>> + <td align="center" width="5%">
>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>> + <input type="hidden" name="ACTION" value="ZF_REMOVE">
>>>>> + <input type="image" src="/images/delete.gif"
>>>>> title="$Lang::tr{'remove'}" alt="$Lang::tr{'remove'}">
>>>>> + </form>
>>>>> + </td>
>>>>> + </tr>
>>>>> +END
>>>>> +;
>>>>> + }
>>>>> +
>>>>> + # Disable reload button if not needed
>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>> +
>>>>> + print <<END
>>>>> +</table>
>>>>> +
>>>>> +<div class="right">
>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>> + <input type="hidden" name="KEY" value="">
>>>>> + <button type="submit" name="ACTION"
>>>>> value="ZF_EDIT">$Lang::tr{'add'}</button>
>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD"
>>>>> class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>> + </form>
>>>>> +</div>
>>>>> +END
>>>>> +;
>>>>> +
>>>>> + &Header::closebox();
>>>>> +}
>>>>> +
>>>>> +# Show zonefiles entry editor
>>>>> +sub _print_zonefile_editor {
>>>>> +
>>>>> + # Key specified: Edit existing entry
>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>> + # Load data to be edited, but don't override already present
>>>>> values (allows user to edit after error)
>>>>> + $cgiparams{'ZF_NAME'} //= $action_key;
>>>>> + $cgiparams{'ZF_URL'} //= $zonefiles{$action_key}{'url'};
>>>>> + $cgiparams{'ZF_REMARK'} //= $zonefiles{$action_key}{'remark'};
>>>>> + }
>>>>> +
>>>>> + # Fallback to empty form
>>>>> + $cgiparams{'ZF_NAME'} //= "";
>>>>> + $cgiparams{'ZF_URL'} //= "";
>>>>> + $cgiparams{'ZF_REMARK'} //= "";
>>>>> +
>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf editor'});
>>>>> +
>>>>> + print <<END
>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>> +<input type="hidden" name="KEY" value="$action_key">
>>>>> +<table width="100%">
>>>>> + <tr>
>>>>> + <td width="20%">$Lang::tr{'name'}: <img src="/blob.gif"
>>>>> alt="*"></td>
>>>>> + <td><input type="text" name="ZF_NAME"
>>>>> value="$cgiparams{'ZF_NAME'}" size="40" maxlength="32"
>>>>> title="$Lang::tr{'rpz zf remark info'}"
>>>>> pattern="[a-zA-Z0-9_]{1,32}" required></td>
>>>>> + </tr>
>>>>> + <tr>
>>>>> + <td width="20%">URL: <img src="/blob.gif" alt="*"></td>
>>>>> + <td><input type="url" name="ZF_URL" value="$cgiparams{'ZF_URL'}"
>>>>> size="40" maxlength="128" required></td>
>>>>> + </tr>
>>>>> + <tr>
>>>>> + <td width="20%">$Lang::tr{'remark'}:</td>
>>>>> + <td><input type="text" name="ZF_REMARK"
>>>>> value="$cgiparams{'ZF_REMARK'}" size="40" maxlength="32"></td>
>>>>> + </tr>
>>>>> + <tr>
>>>>> + <td colspan="2"><hr></td>
>>>>> + </tr>
>>>>> + <tr>
>>>>> + <td width="55%"><img src="/blob.gif"
>>>>> alt="*"> $Lang::tr{'required field'}</td>
>>>>> + <td align="right"><button type="submit" name="ACTION"
>>>>> value="ZF_SAVE">$Lang::tr{'save'}</button></td>
>>>>> + </tr>
>>>>> +</table>
>>>>> +</form>
>>>>> +
>>>>> +<div class="right">
>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>> + <button type="submit" name="ACTION"
>>>>> value="NONE">$Lang::tr{'back'}</button>
>>>>> + </form>
>>>>> +</div>
>>>>> +END
>>>>> +;
>>>>> +
>>>>> + &Header::closebox();
>>>>> +}
>>>>> +
>>>>> +# Show custom allow/block files and related gui elements
>>>>> +sub _print_customlists {
>>>>> +
>>>>> + # Load lists from config, unless they are currently being edited
>>>>> + if($action ne 'CL_SAVE') {
>>>>> + $cgiparams{'ALLOW_LIST'} = join("\n",
>>>>> @{$customlists{'allow'}{'list'}});
>>>>> + $cgiparams{'BLOCK_LIST'} = join("\n",
>>>>> @{$customlists{'block'}{'list'}});
>>>>> +
>>>>> + $cgiparams{'ALLOW_ENABLED'} = ($customlists{'allow'}{'enabled'}
>>>>> eq 'on') ? 'on' : undef;
>>>>> + $cgiparams{'BLOCK_ENABLED'} = ($customlists{'block'}{'enabled'}
>>>>> eq 'on') ? 'on' : undef;
>>>>> + }
>>>>> +
>>>>> + # Fallback to empty form
>>>>> + $cgiparams{'ALLOW_LIST'} //= "";
>>>>> + $cgiparams{'BLOCK_LIST'} //= "";
>>>>> +
>>>>> + # HTML checkboxes, unchecked = no or undef value in POST data
>>>>> + my %checked = ();
>>>>> + $checked{'ALLOW_ENABLED'} = (defined
>>>>> $cgiparams{'ALLOW_ENABLED'}) ? " checked" : "";
>>>>> + $checked{'BLOCK_ENABLED'} = (defined
>>>>> $cgiparams{'BLOCK_ENABLED'}) ? " checked" : "";
>>>>> +
>>>>> + # Disable reload button if not needed
>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>> +
>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz cl'});
>>>>> +
>>>>> + print <<END
>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>> +<table width="100%">
>>>>> + <tr>
>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl
>>>>> allow'}</b><br>$Lang::tr{'rpz cl allow info'}</td>
>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl
>>>>> block'}</b><br>$Lang::tr{'rpz cl block info'}</td>
>>>>> + </tr>
>>>>> + <tr>
>>>>> + <td colspan="2"><textarea name="ALLOW_LIST" class="domainlist"
>>>>> cols="45">
>>>>> +$cgiparams{'ALLOW_LIST'}</textarea></td>
>>>>> + <td colspan="2"><textarea name="BLOCK_LIST" class="domainlist"
>>>>> cols="45">
>>>>> +$cgiparams{'BLOCK_LIST'}</textarea></td>
>>>>> + </tr>
>>>>> + <tr>
>>>>> + <td><label for="allow_enabled">$Lang::tr{'rpz cl allow
>>>>> enable'}</label></td>
>>>>> + <td width="15%"><input type="checkbox" name="ALLOW_ENABLED"
>>>>> id="allow_enabled"$checked{'ALLOW_ENABLED'}></td>
>>>>> + <td><label for="block_enabled">$Lang::tr{'rpz cl block
>>>>> enable'}</label></td>
>>>>> + <td width="15%"><input type="checkbox" name="BLOCK_ENABLED"
>>>>> id="block_enabled"$checked{'BLOCK_ENABLED'}></td>
>>>>> + </tr>
>>>>> + <tr>
>>>>> + <td colspan="4"><hr></td>
>>>>> + </tr>
>>>>> + <tr>
>>>>> + <td align="right" colspan="4">
>>>>> + <button type="submit" name="ACTION"
>>>>> value="CL_SAVE">$Lang::tr{'save'}</button>
>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD"
>>>>> class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>> + </td>
>>>>> +</table>
>>>>> +</form>
>>>>> +END
>>>>> +;
>>>>> +
>>>>> + &Header::closebox();
>>>>> +}
>>>>> +
>>>>> +# Output javascript and extra gui elements
>>>>> +sub _print_gui_extras {
>>>>> +
>>>>> + # Apply/Restart button modifier key handler
>>>>> + if(&_rpz_needs_reload()) {
>>>>> + print <<END
>>>>> +<script>
>>>>> + // Commit modifier key handler
>>>>> + (function(jq, document) {
>>>>> + var keyEventsOn = false; // Keyboard events attached
>>>>> + var keyModify = false; // Modifier key pressed
>>>>> + var mouseHover = false; // Mouse over commit button
>>>>> + var btnModified = false; // Button modified to "Restart"
>>>>> +
>>>>> + // Document-level key events, enable only while cursor is over
>>>>> button
>>>>> + function attachKeyEvents() {
>>>>> + if(keyEventsOn) {
>>>>> + return;
>>>>> + }
>>>>> + keyEventsOn = true;
>>>>> +
>>>>> + jq(document).on("keydown.rpz", function(event) {
>>>>> + if((!keyModify) && event.shiftKey) {
>>>>> + keyModify = true;
>>>>> + handleModify();
>>>>> + }
>>>>> + });
>>>>> + jq(document).on("keyup.rpz", function(event) {
>>>>> + if(keyModify && (!event.shiftKey)) {
>>>>> + keyModify = false;
>>>>> + handleModify();
>>>>> + }
>>>>> + });
>>>>> + }
>>>>> + function removeKeyEvents() {
>>>>> + keyModify = false;
>>>>> + if(keyEventsOn) {
>>>>> + jq(document).off("keydown.rpz keyup.rpz");
>>>>> + keyEventsOn = false;
>>>>> + }
>>>>> + }
>>>>> +
>>>>> + // Attach mouse hover events to commit buttons
>>>>> + function attachMouseEvents() {
>>>>> + jq("button.commit").on("mouseenter", function(event) {
>>>>> + if(!mouseHover) {
>>>>> + mouseHover = true;
>>>>> + attachKeyEvents();
>>>>> + // Handle already pressed key
>>>>> + keyModify = !!(event.shiftKey);
>>>>> + handleModify();
>>>>> + }
>>>>> + });
>>>>> +
>>>>> + // Cursor moved away: Disable key listener to minimize events
>>>>> + jq("button.commit").on("mouseleave", function() {
>>>>> + if(mouseHover) {
>>>>> + mouseHover = false;
>>>>> + removeKeyEvents();
>>>>> + handleModify();
>>>>> + }
>>>>> + });
>>>>> + }
>>>>> +
>>>>> + // Modify commit button
>>>>> + function handleModify() {
>>>>> + let modify = mouseHover && keyModify;
>>>>> + if(btnModified != modify) {
>>>>> + if(modify) {
>>>>> + jq("button.commit").text("$Lang::tr{'restart'}").val("UNB_RESTART");
>>>>> + } else {
>>>>> + jq("button.commit").text("$Lang::tr{'rpz
>>>>> apply'}").val("RPZ_RELOAD");
>>>>> + }
>>>>> + btnModified = modify;
>>>>> + }
>>>>> + }
>>>>> +
>>>>> + // jQuery DOM ready
>>>>> + jq(function() {
>>>>> + attachMouseEvents();
>>>>> + });
>>>>> + })(jQuery, document);
>>>>> +</script>
>>>>> +END
>>>>> +;
>>>>> + } # End of modifier key handler
>>>>> +
>>>>> +}
>>>>> +
>>>>> +
>>>>> +###--- Internal action processing functions ---###
>>>>> +
>>>>> +# Toggle zonefile on/off
>>>>> +sub _action_zf_toggle {
>>>>> + return unless(defined $zonefiles{$action_key});
>>>>> +
>>>>> + my $result = 0;
>>>>> + my $enabled = $zonefiles{$action_key}{'enabled'};
>>>>> +
>>>>> + # Perform toggle action
>>>>> + if($enabled eq 'on') {
>>>>> + $enabled = 'off';
>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'remove',
>>>>> $action_key, '--no-reload');
>>>>> + } else {
>>>>> + $enabled = 'on';
>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add',
>>>>> $action_key, $zonefiles{$action_key}{'url'}, '--no-reload');
>>>>> + }
>>>>> +
>>>>> + # Check for errors, request service reload on success
>>>>> + return unless &_rpz_check_result($result, 1);
>>>>> +
>>>>> + # Save changes
>>>>> + $zonefiles{$action_key}{'enabled'} = $enabled;
>>>>> + &_zonefiles_save_conf();
>>>>> +
>>>>> + return 1;
>>>>> +}
>>>>> +
>>>>> +# Remove zonefile
>>>>> +sub _action_zf_remove {
>>>>> + return unless(defined $zonefiles{$action_key});
>>>>> +
>>>>> + # Remove from rpz-config if currently active
>>>>> + if($zonefiles{$action_key}{'enabled'} eq 'on') {
>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'remove',
>>>>> $action_key, '--no-reload');
>>>>> +
>>>>> + # Check for errors, request service reload on success
>>>>> + return unless &_rpz_check_result($result, 1);
>>>>> + }
>>>>> +
>>>>> + # Remove from data hash and save changes
>>>>> + delete $zonefiles{$action_key};
>>>>> + &_zonefiles_save_conf();
>>>>> +
>>>>> + # Clear action_key, as the entry is now removed entirely
>>>>> + $action_key = "";
>>>>> +
>>>>> + return 1;
>>>>> +}
>>>>> +
>>>>> +# Create or update zonefile entry
>>>>> +# Returns undef if gui needs to stay in editor mode
>>>>> +sub _action_zf_save {
>>>>> + my $result = 0;
>>>>> +
>>>>> + my $name = $cgiparams{'ZF_NAME'} // "";
>>>>> + my $url = $cgiparams{'ZF_URL'} // "";
>>>>> + my $remark = $cgiparams{'ZF_REMARK'} // "";
>>>>> + my $enabled = 'on'; # Enable new entries by default
>>>>> +
>>>>> + # Note on variables:
>>>>> + # name = unique key, will be used to address the entry
>>>>> + # action_key = name of the entry being edited, empty for new entry
>>>>> +
>>>>> + # Only check for unique name if it changed
>>>>> + # (this also checks new entries because the action_key is empty
>>>>> in this case)
>>>>> + $result = &_rpz_validate_zonefile($name, $url, $remark,
>>>>> (lc($name) ne lc($action_key)));
>>>>> + return unless &_rpz_check_result($result, 0);
>>>>> +
>>>>> + # Edit existing entry: Determine what was changed
>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>> + # Name und URL remain unchanged, only save remark and finish
>>>>> + if(($name eq $action_key) && ($url eq
>>>>> $zonefiles{$action_key}{'url'})) {
>>>>> + $zonefiles{$action_key}{'remark'} = $remark;
>>>>> + &_zonefiles_save_conf();
>>>>> +
>>>>> + return 1;
>>>>> + }
>>>>> +
>>>>> + # Entry was changed and needs to be recreated, preserve status
>>>>> + $enabled = $zonefiles{$action_key}{'enabled'};
>>>>> +
>>>>> + # Remove from rpz-config
>>>>> + return unless &_action_zf_remove();
>>>>> + }
>>>>> +
>>>>> + # Add new entry to rpz-config
>>>>> + if($enabled eq 'on') {
>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $name,
>>>>> $url, '--no-reload');
>>>>> +
>>>>> + # Check for errors, request service reload on success
>>>>> + return unless &_rpz_check_result($result, 1);
>>>>> + }
>>>>> +
>>>>> + # Add to global data hash and save changes
>>>>> + my %entry = ('enabled' => $enabled,
>>>>> + 'url' => $url,
>>>>> + 'remark' => $remark);
>>>>> +
>>>>> + $zonefiles{$name} = \%entry;
>>>>> + &_zonefiles_save_conf();
>>>>> +
>>>>> + return 1;
>>>>> +}
>>>>> +
>>>>> +# Save custom lists
>>>>> +sub _action_cl_save {
>>>>> + return unless((defined $cgiparams{'ALLOW_LIST'}) && (defined
>>>>> $cgiparams{'BLOCK_LIST'}));
>>>>> +
>>>>> + my $result = 0;
>>>>> +
>>>>> + my @allowlist = split(/\R/, $cgiparams{'ALLOW_LIST'});
>>>>> + my @blocklist = split(/\R/, $cgiparams{'BLOCK_LIST'});
>>>>> +
>>>>> + # Validate lists
>>>>> + $result = &_rpz_validate_customlist(\@allowlist);
>>>>> + if($result != 0) {
>>>>> + $errormessage = &_rpz_error_tr(202, $result);
>>>>> + return;
>>>>> + }
>>>>> + $result = &_rpz_validate_customlist(\@blocklist);
>>>>> + if($result != 0) {
>>>>> + $errormessage = &_rpz_error_tr(203, $result);
>>>>> + return;
>>>>> + }
>>>>> +
>>>>> + # Add to global data hash and save changes
>>>>> + $customlists{'allow'}{'list'} = \@allowlist;
>>>>> + $customlists{'block'}{'list'} = \@blocklist;
>>>>> + $customlists{'allow'}{'enabled'} = (defined
>>>>> $cgiparams{'ALLOW_ENABLED'}) ? 'on' : 'off';
>>>>> + $customlists{'block'}{'enabled'} = (defined
>>>>> $cgiparams{'BLOCK_ENABLED'}) ? 'on' : 'off';
>>>>> +
>>>>> + &_customlists_save_conf();
>>>>> + &_customlist_export('allow', $RPZ_ALLOWLIST);
>>>>> + &_customlist_export('block', $RPZ_BLOCKLIST);
>>>>> +
>>>>> + # Make new lists, request service reload on success
>>>>> + $result = &General::system('/usr/sbin/rpz-make', 'allowblock',
>>>>> '--no-reload');
>>>>> + return unless &_rpz_check_result($result, 1);
>>>>> +
>>>>> + return 1;
>>>>> +}
>>>>> +
>>>>> +# Trigger rpz-config reload
>>>>> +sub _action_rpz_reload {
>>>>> + return 1 unless &_rpz_needs_reload();
>>>>> +
>>>>> + # Immediately clear flag to prevent multiple reloads
>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>> + }
>>>>> +
>>>>> + # Perform reload, recreate reload flag on error to enable retry
>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'reload');
>>>>> + if(not &_rpz_check_result($result, 0)) {
>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>> + return;
>>>>> + }
>>>>> +
>>>>> + return 1;
>>>>> +}
>>>>> +
>>>>> +# Trigger unbound restart
>>>>> +sub _action_unb_restart {
>>>>> + return 1 unless &_rpz_needs_reload();
>>>>> +
>>>>> + # Immediately clear flag to prevent multiple restarts
>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>> + }
>>>>> +
>>>>> + # Perform restart, unboundctrl always exits zero
>>>>> + &General::system('/usr/local/bin/unboundctrl', 'restart');
>>>>> +
>>>>> + return 1;
>>>>> +}
>>>>> +
>>>>> +
>>>>> +###--- Internal rpz-config functions ---###
>>>>> +
>>>>> +# Translate rpz-config exitcodes and messages
>>>>> +# 100-199: rpz-config, 200-299: webgui
>>>>> +sub _rpz_error_tr {
>>>>> + my ($error, $append) = @_;
>>>>> + $append //= '';
>>>>> +
>>>>> + # Translate numeric exit codes
>>>>> + if(looks_like_number($error)) {
>>>>> + if(defined $Lang::tr{"rpz exitcode $error"}) {
>>>>> + $error = $Lang::tr{"rpz exitcode $error"};
>>>>> + }
>>>>> + }
>>>>> +
>>>>> + return "RPZ $Lang::tr{'error'}: $error" . &Header::escape($append);
>>>>> +}
>>>>> +
>>>>> +# Check result of rpz-config system call, request reload on success
>>>>> +sub _rpz_check_result {
>>>>> + my ($result, $request_reload) = @_;
>>>>> + $request_reload //= 0;
>>>>> +
>>>>> + # exitcode 0 = success
>>>>> + if($result != 0) {
>>>>> + $errormessage = &_rpz_error_tr($result);
>>>>> + return;
>>>>> + }
>>>>> +
>>>>> + # Set reload flag
>>>>> + if($request_reload) {
>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>> + }
>>>>> +
>>>>> + return 1;
>>>>> +}
>>>>> +
>>>>> +# Test whether reload flag is set
>>>>> +sub _rpz_needs_reload {
>>>>> + return (-f $RPZ_RELOAD_FLAG);
>>>>> +}
>>>>> +
>>>>> +# Validate a zonefile entry, returns rpz-config exitcode on
>>>>> failure. Use _rpz_check_result to verify.
>>>>> +# unique = check for unique name
>>>>> +sub _rpz_validate_zonefile {
>>>>> + my ($name, $url, $remark, $unique) = @_;
>>>>> + $unique //= 1;
>>>>> +
>>>>> + unless($name =~ /^[a-zA-Z0-9_]{1,32}$/) {
>>>>> + return 101;
>>>>> + }
>>>>> + unless($url =~ /^[\w+\.:;\/\\&@#%?=\-~|!]{1,128}$/) {
>>>>> + return 105;
>>>>> + }
>>>>> + unless($remark =~ /^[\w \-()\.:;*\/\\?!&=]{0,32}$/) {
>>>>> + return 201;
>>>>> + }
>>>>> +
>>>>> + # Check against already existing names
>>>>> + if($unique) {
>>>>> + foreach my $existing (keys %zonefiles) {
>>>>> + if(lc($name) eq lc($existing)) {
>>>>> + return 104;
>>>>> + }
>>>>> + }
>>>>> + }
>>>>> +
>>>>> + return 0;
>>>>> +}
>>>>> +
>>>>> +# Validate a custom list, returns number of rejected line on
>>>>> failure. Check for non-zero results.
>>>>> +# listref = array reference, cleanup = remove invalid entries
>>>>> instead of returning an error
>>>>> +sub _rpz_validate_customlist {
>>>>> + my ($listref, $cleanup) = @_;
>>>>> + $cleanup //= 0;
>>>>> +
>>>>> + foreach my $index (reverse 0..$#{$listref}) {
>>>>> + my $row = @$listref[$index];
>>>>> + next unless($row); # Skip/allow empty lines
>>>>> +
>>>>> + # Reject/remove everything besides wildcard domains and remarks
>>>>> + if((not &General::validwildcarddomainname($row)) && (not $row =~
>>>>> /^;[\w \-()\.:;*\/\\?!&=]*$/)) {
>>>>> + unless($cleanup) {
>>>>> + # +1 for user friendly line number and to ensure non-zero exitcode
>>>>> + return $index + 1;
>>>>> + }
>>>>> +
>>>>> + # Remove current row
>>>>> + splice(@$listref, $index, 1);
>>>>> + }
>>>>> + }
>>>>> +
>>>>> + return 0;
>>>>> +}
>>>>> +
>>>>> +
>>>>> +###--- Internal misc functions ---###
>>>>> +
>>>>> +# Send HTTP 303 redirect headers for post/request/get pattern
>>>>> +# (Must be sent before calling &Header::showhttpheaders())
>>>>> +sub _http_prg_redirect {
>>>>> + my $location =
>>>>> "https://$ENV{'SERVER_NAME'}:$ENV{'SERVER_PORT'}$ENV{'SCRIPT_NAME'}";
>>>>> + print "Status: 303 See Other\n";
>>>>> + print "Location: $location\n";
>>>>> +}
>>>>> diff --git a/lfs/rpz b/lfs/rpz
>>>>> new file mode 100644
>>>>> index 000000000..7ddbc38e5
>>>>> --- /dev/null
>>>>> +++ b/lfs/rpz
>>>>> @@ -0,0 +1,96 @@
>>>>> +###############################################################################
>>>>> +#
>>>>> #
>>>>> +# IPFire.org - A linux based firewall
>>>>> #
>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org>
>>>>> #
>>>>> +#
>>>>> #
>>>>> +# 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
>>>>> <http://www.gnu.org/licenses/>. #
>>>>> +#
>>>>> #
>>>>> +###############################################################################
>>>>> +
>>>>> +###############################################################################
>>>>> +# Definitions
>>>>> +###############################################################################
>>>>> +
>>>>> +include Config
>>>>> +
>>>>> +SUMMARY = response policy zone - RPZ reputation system for
>>>>> unbound DNS
>>>>> +
>>>>> +VER = 1.0.0
>>>>> +
>>>>> +THISAPP = rpz-$(VER)
>>>>> +DIR_APP = $(DIR_SRC)/$(THISAPP)
>>>>> +TARGET = $(DIR_INFO)/$(THISAPP)
>>>>> +
>>>>> +PROG = rpz
>>>>> +PAK_VER = 18
>>>>> +
>>>>> +DEPS =
>>>>> +
>>>>> +SERVICES =
>>>>> +
>>>>> +###############################################################################
>>>>> +# Top-level Rules
>>>>> +###############################################################################
>>>>> +
>>>>> +install : $(TARGET)
>>>>> +
>>>>> +check :
>>>>> +
>>>>> +download :
>>>>> +
>>>>> +b2 :
>>>>> +
>>>>> +dist:
>>>>> + @$(PAK)
>>>>> +
>>>>> +###############################################################################
>>>>> +# Installation Details
>>>>> +###############################################################################
>>>>> +
>>>>> +$(TARGET) :
>>>>> + @$(PREBUILD)
>>>>> + @rm -rf $(DIR_APP)
>>>>> +
>>>>> + # RPZ scripts
>>>>> + install --verbose --mode=755 \
>>>>> +
>>>>> $(DIR_CONF)/rpz/{rpz-config,rpz-metrics,rpz-sleep,rpz-make,rpz-functions}
>>>>> \
>>>>> + --target-directory=/usr/sbin
>>>>> +
>>>>> + # RPZ config files
>>>>> + mkdir -pv /etc/unbound/local.d
>>>>> + install --verbose --mode=644 --owner=nobody --group=nobody \
>>>>> + $(DIR_CONF)/rpz/00-rpz.conf \
>>>>> + --target-directory=/etc/unbound/local.d
>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/local.d
>>>>> +
>>>>> + # RPZ custom list files for allow and block
>>>>> + mkdir -pv /var/ipfire/dns/rpz
>>>>> + touch /var/ipfire/dns/rpz/{allowlist,blocklist}
>>>>> + chown --verbose --recursive nobody:nobody /var/ipfire/dns/rpz
>>>>> +
>>>>> + # RPZ zone files
>>>>> + # create empty RPZ config file to avoid a unbound config error
>>>>> + mkdir -pv /etc/unbound/zonefiles
>>>>> + touch /etc/unbound/zonefiles/allow.rpz
>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/zonefiles
>>>>> +
>>>>> + # Install addon-specific language-files
>>>>> + install --verbose --mode=004 $(DIR_CONF)/rpz/rpz.*.pl \
>>>>> + --target-directory=/var/ipfire/addon-lang
>>>>> +
>>>>> + # Install backup definition
>>>>> + cp -vf $(DIR_CONF)/backup/includes/rpz
>>>>> /var/ipfire/backup/addons/includes/rpz
>>>>> +
>>>>> + @rm -rf $(DIR_APP)
>>>>> + @$(POSTBUILD)
>>>>> diff --git a/make.sh b/make.sh
>>>>> index 827ea9e77..a77535b13 100755
>>>>> --- a/make.sh
>>>>> +++ b/make.sh
>>>>> @@ -390,7 +390,7 @@ prepareenv() {
>>>>> if [ "${free_space}" -lt "${required_space}" ]; then
>>>>> # Add any consumed space
>>>>> while read -r consumed_space path; do
>>>>> - (( free_space += consumed_space / 1024 / 1024 ))
>>>>> + (( free_space += consumed_space / 1024 / 1024 ))
>>>>> done <<< "$(du --summarize --bytes "${BUILD_DIR}" "${IMAGES_DIR}"
>>>>> "${LOG_DIR}" 2>/dev/null)"
>>>>> fi
>>>>>
>>>>> @@ -2087,6 +2087,7 @@ build_system() {
>>>>> lfsmake2 btrfs-progs
>>>>> lfsmake2 inotify-tools
>>>>> lfsmake2 grub-btrfs
>>>>> + lfsmake2 rpz
>>>>>
>>>>> lfsmake2 linux
>>>>> lfsmake2 rtl8812au
>>>>> diff --git a/src/paks/rpz/install.sh b/src/paks/rpz/install.sh
>>>>> new file mode 100644
>>>>> index 000000000..ef99bf742
>>>>> --- /dev/null
>>>>> +++ b/src/paks/rpz/install.sh
>>>>> @@ -0,0 +1,36 @@
>>>>> +#!/bin/bash
>>>>> +###############################################################################
>>>>> +#
>>>>> #
>>>>> +# IPFire.org - A linux based firewall
>>>>> #
>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org>
>>>>> #
>>>>> +#
>>>>> #
>>>>> +# 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
>>>>> <http://www.gnu.org/licenses/>. #
>>>>> +#
>>>>> #
>>>>> +###############################################################################
>>>>> +#
>>>>> +. /opt/pakfire/lib/functions.sh
>>>>> +extract_files
>>>>> +restore_backup ${NAME}
>>>>> +
>>>>> +# fix user created files
>>>>> +chown --verbose --recursive nobody:nobody \
>>>>> + /var/ipfire/dns/rpz \
>>>>> + /etc/unbound/zonefiles \
>>>>> + /etc/unbound/local.d
>>>>> +
>>>>> +# Update Language cache
>>>>> +/usr/local/bin/update-lang-cache
>>>>> +
>>>>> +# restart unbound to load config file
>>>>> +/etc/init.d/unbound restart
>>>>> diff --git a/src/paks/rpz/uninstall.sh b/src/paks/rpz/uninstall.sh
>>>>> new file mode 100644
>>>>> index 000000000..e11427df3
>>>>> --- /dev/null
>>>>> +++ b/src/paks/rpz/uninstall.sh
>>>>> @@ -0,0 +1,38 @@
>>>>> +#!/bin/bash
>>>>> +###############################################################################
>>>>> +#
>>>>> #
>>>>> +# IPFire.org - A linux based firewall
>>>>> #
>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org>
>>>>> #
>>>>> +#
>>>>> #
>>>>> +# 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
>>>>> <http://www.gnu.org/licenses/>. #
>>>>> +#
>>>>> #
>>>>> +###############################################################################
>>>>> +#
>>>>> +. /opt/pakfire/lib/functions.sh
>>>>> +
>>>>> +# stop unbound to delete RPZ conf file
>>>>> +/etc/init.d/unbound stop
>>>>> +
>>>>> +make_backup ${NAME}
>>>>> +remove_files
>>>>> +
>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>> +# "[1723428668] unbound-control[17117:0] error: connect:
>>>>> Connection refused for 127.0.0.1 port 8953"
>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>> +
>>>>> +# Update Language cache
>>>>> +/usr/local/bin/update-lang-cache
>>>>> +
>>>>> +# start unbound to load unbound config file
>>>>> +/etc/init.d/unbound start
>>>>> diff --git a/src/paks/rpz/update.sh b/src/paks/rpz/update.sh
>>>>> new file mode 100644
>>>>> index 000000000..9bc340bc6
>>>>> --- /dev/null
>>>>> +++ b/src/paks/rpz/update.sh
>>>>> @@ -0,0 +1,52 @@
>>>>> +#!/bin/bash
>>>>> +###############################################################################
>>>>> +#
>>>>> #
>>>>> +# IPFire.org - A linux based firewall
>>>>> #
>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org>
>>>>> #
>>>>> +#
>>>>> #
>>>>> +# 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
>>>>> <http://www.gnu.org/licenses/>. #
>>>>> +#
>>>>> #
>>>>> +###############################################################################
>>>>> +#
>>>>> +. /opt/pakfire/lib/functions.sh
>>>>> +
>>>>> +# from update.sh
>>>>> +extract_backup_includes
>>>>> +
>>>>> +# stop unbound to delete RPZ conf file
>>>>> +/etc/init.d/unbound stop
>>>>> +
>>>>> +# from uninstall.sh
>>>>> +make_backup ${NAME}
>>>>> +remove_files
>>>>> +
>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>> +# "unbound-control[nn:0] error: connect: Connection refused
>>>>> for 127.0.0.1 port 8953"
>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>> +
>>>>> +# from install.sh
>>>>> +extract_files
>>>>> +restore_backup ${NAME}
>>>>> +
>>>>> +# fix user created files
>>>>> +chown --verbose --recursive nobody:nobody \
>>>>> + /var/ipfire/dns/rpz \
>>>>> + /etc/unbound/zonefiles \
>>>>> + /etc/unbound/local.d
>>>>> +
>>>>> +# Update Language cache
>>>>> +/usr/local/bin/update-lang-cache
>>>>> +
>>>>> +# restart unbound to load config files
>>>>> +/etc/init.d/unbound start
>>>>> --
>>>>> 2.39.5
>>>>>
>>>>
>>>
>>> Jon
>>>
>>>
>>> --
>>> Jon Murphy
>>> jon.murphy@ipfire.org
>
> Jon
>
>
> --
> Jon Murphy
> jon.murphy@ipfire.org
>
>
>
>
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
2025-03-16 17:00 ` Jon Murphy
@ 2025-03-17 10:35 ` Michael Tremer
2025-03-19 2:58 ` Jon Murphy
0 siblings, 1 reply; 30+ messages in thread
From: Michael Tremer @ 2025-03-17 10:35 UTC (permalink / raw)
To: Jon Murphy; +Cc: IPFire: Development-List
Good Morning Jon,
> On 16 Mar 2025, at 17:00, Jon Murphy <jon.murphy@ipfire.org> wrote:
>
> Michael,
>
> I was reading through you response again an I want to understand this post:
>
>> I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>
> So if RPZ doesn't use HTTPS, what is it using? I am missing a key point here.
The emphasis is on the repeated downloads of the same list. That is what cannot happen.
Although it might not affect a lot of people in our general user-base, there are some that have a metered connection and will pay for data by volume. Some of the lists I looked at are just under 20 MiB. Therefore we need to keep any traffic down to a minimum. The second reason is that we have a lot of firewalls out there. Not all of them will enable this feature and all of the lists, but even if it is a good chunk, we will generate terabytes of traffic which put load on the infrastructure and will cost money. It simply is not what we want to do, regardless of self-hosting those lists and pulling them from somewhere else.
So there has to be a way to ensure that we won’t download a list again unless it has actually changed.
DNS has a builtin functionality called AXFR. It simply does the job for you. I was just wondering whether that was not being used.
HTTPS is an option because that is simply what we use elsewhere, but extra functionality will have to be built for it.
-Michael
> Jon
>
>
> On 2/13/25 3:34 PM, jon wrote:
>> Michael,
>>
>> I’ve read through your comments a few times and I ended up with many more questions.
>>
>>
>>> What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>
>> To me the efforts to get new code accepted seem to have changed and it seemed easier in the past. In the past I made the Core Team aware via the Dev Mailing List and wrote a simple two or three paragraphs of "What is it? / What is the value? / Here is the code"
>>
>>
>> So in an effort to move forward: How exactly is something presented to the Core Team?
>>
>> Is there an example of a recent effort that was presented that I can see as a sample? (This type of info can also be added to the Wiki)
>>
>> I understand you want it this way, but I don’t know what exactly is needed. Please be specific.
>>
>>
>> Jon
>>
>> PS - I am not ignoring your other comments, I am just trying to move forward and keep things simple.
>>
>>
>>
>>> On Feb 8, 2025, at 1:27 PM, Michael Tremer <michael.tremer@ipfire.org> wrote:
>>>
>>> Hello Jon,
>>>
>>> Thanks for your reply. And good that you are copying everyone into this conversation.
>>>
>>>> On 8 Feb 2025, at 18:41, jon <jon.murphy@ipfire.org> wrote:
>>>>
>>>> Michael,
>>>>
>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort
>>>>
>>>> Yes, this has been a separate effort (a very public separate effort). Yes, as you pointed this out early on with the "proof-of-concept" and then my request for people to help test RPZ. Nothing was hidden.
>>>>
>>>> This was done because you (and maybe others) did not have the time and I wanted to help and because I needed assistance with RPZ. I tried my best to do this without bothering you.
>>>
>>> I don’t that it is accurate that nobody wanted to help on this. The list was always open - although not every email has been replied to swiftly it is also your responsibility to raise a question again if it was missed. People here have open ears.
>>>
>>> It was also stated on this very list on in our documentation that working on something without involving the core team is a risky undertaking. Of course IPFire is free software and so everyone is free to fork if they wish to do so.
>>>
>>>>> and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls.
>>>>
>>>> You were aware many steps along the way. See your email on July 28, 2024, August 15, 2024, September 30, 2024, December 23, 2024, and January 16. My attempts to get the team involved were met with "things are busy" and sometimes silence. (Yes, I get it, people are busy.)
>>>>
>>>> You and Adolf, Leo, Erik and Bernhard have been aware since the beginning. You mention you were aware of the "proof-of-concept". If you include those beginning posts, since Sep 2023.
>>>
>>> Yes, I am aware of a proof-of-concept that I have been running myself for a long time. I am also aware of the efforts that you have been taking.
>>>
>>> Yet I don’t think there has ever been any joint effort, or am I seeing that wrong?
>>>
>>>>> This has not been discussed . . . on our calls.
>>>>
>>>> On the July 28th you stated:
>>>> "We have talked about RPZ many times on the monthly call since the URL filter feature is falling more and more out of fashion. I think there is also many posts about this on the forum."
>>>>
>>>> Please don’t insult me again by stating "you know what I mean".
>>>>
>>>> And it has been discussed but not documented in the Monthly Meeting notes.
>>>
>>> I am not at all insulting you. I don’t want to take this down to a personal level at all. This is a public mailing list and people who read this don’t need to listen to an argument we are having. They are here for the tech inside IPFire.
>>>
>>> When I wrote that it has not been discussed that does not mean that we have not been touching on the topic. We have been talking about lots of things on the calls, the weather, politics, how our pets are. None of that makes it to the logs. What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>
>>>>> Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation.
>>>>
>>>> Regular conversation on the Dev Mailing list is many times met with silence. I get it, people are busy.
>>>>
>>>> And regular two-way conversation doesn’t happen on the list. At least not with me. I’d be happy to point out the posts that were met with silence.
>>>> Again, I get it, people are busy.
>>>
>>> And you think my emails are not being met with silence? This has nothing to do with this specific topic. This has something to do with how occupied people are and how engaged they are on certain topics. Not everyone is involved in all the things and simply will ignore emails simply based on their subject line.
>>>
>>>> But the "dip here to the list" were my attempts to get a conversation started. As I said, many time met with silence.
>>>>
>>>> The only place I was not met with silence was on the Community. You have a great group of people in the Community. It is a shame you don’t want to have others help. It would reduce your workload.
>>>
>>> You should stop making statements that are not true. Who doesn’t want anyone to help?
>>>
>>> Not having this conversation on a Saturday evening would reduce my workload. At least it would free up time for something else. Helping with the things that are already on the go would reduce the workload of the entire team. Starting one thing at a time and finishing it is a lot better to manage than starting a hundred things and not even finish one. I can tell you that I already have a hundred things on the go.
>>>
>>>>> Therefore, what am I supposed to do with this email?
>>>>
>>>> To me it is beyond obvious…
>>>>
>>>> If it isn’t what you want, then guide me with how to do this the correct way. And be specific. I am trying to help. I am trying to make things better. I am trying to do things the right way.
>>>
>>> To me it isn’t. This is yet another project that has been dumped to the list like so many before and later on everyone has left to have the team deal with the rest.
>>>
>>> It is a huge patch set. You explained what the vision is, but that is about it. There is no chance this will continue if this disagreement isn’t solved first. I didn’t even look at the code.
>>>
>>>>> I don’t want to merge code that I don’t agree with.
>>>>
>>>> I asked multiple times if you "agreed with the concept" and again, met with silence. Yes I get it, people are busy.
>>>
>>> Having support for RPZ? Yes, it was definitely on the roadmap. That I agree with.
>>>
>>>>> So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>
>>>> You mentioned this a in the past, but for some reason you do not disclose what I dismissed. Why do you continue to make this harder, wouldn’t it not be easier to tell me what I have dismissed?
>>>>
>>>> I have sent multiple emails trying to answer your concerns and comments. On July 28, Aug 14, Aug 22, Aug 23, Sep 30, etc.
>>>>
>>>> I’ve gone through all of the questions you asked and I cannot find a "dismissed" item.
>>>
>>> Maybe I need to be *more clear*. I feel humoured by this.
>>>
>>> It is late on a Saturday and I want my dinner soon, but certainly I have stated that this should never be an add-on considering it is supposed to replace URL Filter. We should never allow people to add their own sources. I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>>
>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>
>>>> The maintainers of Unbound and/or RPZ?
>>>>
>>>> The maintainers of Hagezi list, the threatfox list, the urlhaus list, etc.?
>>>>
>>>> What else? The maintainers or the RPZ scripts? That is me. Let’s talk!
>>>
>>> You. I don’t care much about the providers of the lists.
>>>
>>>> See, this is where it gets confusing. There are hundreds of open source packages as part of IPFire. Pick the last five years of items added to the IPFire build. You're telling me you have "constructive conversation with the maintainers" of all of the added packages?
>>>
>>> They publish their software and they don’t care whether I am pulling it or not. They publish it with the commitment to maintain it - sometimes for better and sometimes for worse.
>>>
>>> You care about me pulling your code and I don’t know whether you would commit to maintain this.
>>>
>>> These two are very different cases.
>>>
>>>> Pick the IP Blocklists list (i.e., 3CORESEC, ABUSECH, DSHIELD, SPAMHAUS, etc.) or the Suricata lists (i.e.,Emergingthreats.net <http://emergingthreats.net/>,Abuse.ch <http://abuse.ch/>, etc.). So you’ve have "constructive conversation with the maintainers"?
>>>
>>> Yes, occasionally I have phone calls with a few of these providers.
>>>
>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>
>>>> Ha! Yes a surprise. In the beginning you seemed interested as IPFire needed a replacement for URL Filter. You asked good questions about the lists picked, asked for the value to the users, etc. And I answered the best I could.
>>>>
>>>> You even asked: “Why is this realised as an add-on and not part of the core system?” from your Jul 28, 2024 email.
>>>
>>> Ah, so, why is the patch creating an add-on? Not that I am saying that what I say is law, but it has not been challenged either. If my input is being ignored, why should I put this to the top of my list of priorities? I am not disappointed about this, just trying to be very good with my time.
>>>
>>>> And on January 16, 2025 I wrote a message looking for help. And you were kind to respond quickly. So in three weeks time, since the kind response, something has changed. You went from supportive to "this".
>>>>
>>>> So yes, I am surprised.
>>>
>>> Well, maybe I should not have replied to that email. It was clear that you were on some path that was not right, but you were not interested before in finding the right path from the beginning.
>>>
>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>
>>>> Be more specific, what has to change? What exactly did I dismiss?
>>>
>>> Dismissal is just my assumption. I don’t know what you actually did with my feedback. I can only see the end product that does not seem contain much of it. Repeatedly I have been pointing out that we should think before we build. I am sure a lot of hours have now gone into some code that simply does not satisfy me. And I am not not talking about the code itself, what it does is what I don’t think is right for us.
>>>
>>> The process is very clear for me that we should first of all think whether we want a certain feature now. Then there should be a clear roadmap for everyone to follow; tasks can be split-up as we go and hopefully then have something that is maintainable, interesting for our users and even would do us proud. This is how this should work.
>>>
>>> So, what has to change? I don’t think with shouting at each other, throwing patches around and making me generally unhappy is a good start.
>>>
>>> -Michael
>>>
>>>> Jon
>>>>
>>>>
>>>>
>>>>> On Feb 6, 2025, at 2:13 PM, Michael Tremer <michael.tremer@ipfire.org> wrote:
>>>>>
>>>>> Hello Jon,
>>>>>
>>>>> Well, here we are again with another patch regarding this feature.
>>>>>
>>>>> I cannot quite see from your email what the question is, but if this is a request to have this merged into IPFire, I am once again sorry to disappoint you.
>>>>>
>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls. Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation. Therefore, what am I supposed to do with this email?
>>>>>
>>>>> I don’t want to merge code that I don’t agree with. So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>
>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>
>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>
>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>
>>>>> All the best,
>>>>> -Michael
>>>>>
>>>>>> On 6 Feb 2025, at 16:35, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>
>>>>>> What is it?
>>>>>> Response Policy Zone (RPZ) is a mechanism to define local policies in a
>>>>>> standardized way and load those policies from external sources.
>>>>>> Bottom line: RPZ allows admins to easily block access to websites via DNS lookup.
>>>>>>
>>>>>> RPZ can block websites via categories. Examples include: fake websites, annoying
>>>>>> pop-up ads, newly registered domains, DoH bypass sites, bad "host" services,
>>>>>> maliscious top level domains (e.g., *.zip, *.mov), piracy, gambling, pornography,
>>>>>> and more. RPZ lists come from various RPZ providers and their available
>>>>>> catagories.
>>>>>>
>>>>>> This RPZ add-on enables the RPZ functionality by adding a couple lines in a
>>>>>> configuration file. This add-on simply adds configuration files and adds
>>>>>> scripts (config, metrics and sleep) to make RPZ easier for the admin to use.
>>>>>>
>>>>>> The RPZ scripts include additional languages: German, Spanish, French, Turkish,
>>>>>> and Italian.
>>>>>>
>>>>>> RPZ itself was release in 2010 and has been part of the IPFire build since ~2015.
>>>>>>
>>>>>> Why is it needed? What is its value?
>>>>>>
>>>>>> - The RPZ concept places this filtering into IPFire, our internet access
>>>>>> gateway, which is (should be) solely used as DNS source of the internal network.
>>>>>>
>>>>>> - As most sites use HTTPS it makes it difficult to filter traffic with URL
>>>>>> Filter without also properly configuring conventional (non-transparent)
>>>>>> mode on the proxy. RPZ is a nice replacement for the URL Filter.
>>>>>>
>>>>>> - No need to install and maintain an additional device like PiHole or AdBlock
>>>>>> browser extensions on multiple user devices.
>>>>>>
>>>>>> - This is an additional layer of protection for users. Less worry someone will
>>>>>> click on something that gets them into trouble. And, saying this with emphasis,
>>>>>> the ability to do it in one place!
>>>>>>
>>>>>> - Blocked sites save on unneeded traffic and can lessen the threat of malware
>>>>>> in advertisements
>>>>>>
>>>>>> - Logging allows the admin to see the site blocked and take actions
>>>>>>
>>>>>> - RPZ will be used at the home, home-office (work from home), schools,
>>>>>> ministerial, and at the office. Device counts are small (2-6) to medium (~80)
>>>>>> to mediam-large (200+).
>>>>>>
>>>>>> - RPZ can block ads, popups, phishing, scammers, spyware, malware, annoying
>>>>>> popups, NSFW links, DOH servers, and the usual internet trash.
>>>>>>
>>>>>> ------------------------------
>>>>>>
>>>>>> Change Log for RPZ add-on
>>>>>>
>>>>>> rpz-1.0.0-18 on 2025-02-05
>>>>>> - Build for approval & release as IPFire add-on
>>>>>>
>>>>>> ---
>>>>>>
>>>>>> rpz-beta-0.1.18-18.ipfire on 2025-02-01
>>>>>> rpz.cgi:
>>>>>> - new feature: added a mod key to force a unbound restart
>>>>>>
>>>>>> rpz-config and rpz-make:
>>>>>> - new feature: added action for unbound restart `rpz-config unbound-restart`
>>>>>>
>>>>>> rpz-metrics:
>>>>>> - simple reformatting
>>>>>> - rename far right column from "last update" to "last download"
>>>>>>
>>>>>> ---
>>>>>>
>>>>>> rpz-beta-0.1.17-17.ipfire on 2024-12-09
>>>>>> rpz-make
>>>>>> - bug fix: corrected validation regex for wildcards like: `*.domain.com`
>>>>>>
>>>>>> ---
>>>>>>
>>>>>> rpz-beta-0.1.16-16.ipfire on 2024-11-18
>>>>>> rpz-make
>>>>>> - new feature: updated validation regex
>>>>>> - bug fix: moved validation to beginning of process. Now we validate before
>>>>>> creating config files.
>>>>>>
>>>>>> rpz.cgi:
>>>>>> - new feature: use CSS color variables of the main ipfire theme
>>>>>> - bug fix: empty zonefile remarks were stored as “undef” and caused a warning
>>>>>> - bug fix: HTML textarea removes the first empty line in a custom list
>>>>>> - thank you Leo!
>>>>>>
>>>>>> ---
>>>>>>
>>>>>> rpz-beta-0.1.15-15.ipfire on 2024-11-04
>>>>>> rpz.cgi:
>>>>>> - new feature: added new language file for Turkish (thank you Peppe)
>>>>>>
>>>>>> rpz-make
>>>>>> - bug fix: corrected empty allow/block list issue. An empty allow/block list
>>>>>> will now remove contents of allow/block.rpz files and remove unneeded
>>>>>> allow/block.conf file. (thank you iptom)
>>>>>>
>>>>>> ---
>>>>>>
>>>>>> rpz-beta-0.1.14-14.ipfire on 2024-10-29
>>>>>> rpz-config:
>>>>>> - bug fix: correct missing rpz extension. `rpz-config list` displayed URL
>>>>>> incorrectly (thank you Bernhard)
>>>>>>
>>>>>> rpz.cgi:
>>>>>> - bug fix: remove extra `"` in language files (thank you Bernhard)
>>>>>> - new feature: slightly dim "apply" button when not enabled
>>>>>>
>>>>>> ---
>>>>>>
>>>>>> rpz-beta-0.1.13-13.ipfire on 2024-10-27
>>>>>> - skipped
>>>>>>
>>>>>> ---
>>>>>>
>>>>>> rpz-beta-0.1.12-12.ipfire on 2024-10-21
>>>>>> rpz.cgi:
>>>>>> - new feature: added new language file for French (thank you gw-ipfire)
>>>>>>
>>>>>> ---
>>>>>>
>>>>>> rpz-beta-0.1.11-11.ipfire on 2024-10-18
>>>>>> rpz.cgi:
>>>>>> - new feature: added new language file for Italian (thank you umberto)
>>>>>> - new feature: added new language file for Spanish (thank you Roberto)
>>>>>>
>>>>>> ---
>>>>>>
>>>>>> rpz-beta-0.1.10-10.ipfire on 2024-10-15
>>>>>> rpz-make:
>>>>>> - bug fix: corrected validation error for a custom list entry (thank you siosios)
>>>>>> - e.g., `*.cloudflare-dns.com`
>>>>>>
>>>>>> install.sh:
>>>>>> - bug fix: add chown to correct user created files
>>>>>>
>>>>>> update.sh:
>>>>>> - bug fix: add chown to correct user created files (thank you siosios)
>>>>>>
>>>>>> ---
>>>>>>
>>>>>> rpz-beta-0.1.9-9.ipfire on 2024-10-08
>>>>>> rpz.cgi:
>>>>>> - new feature: added new language file for German (thank you Leo)
>>>>>> - bug fix: add missing "rpz exitcode 110"
>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>
>>>>>> ---
>>>>>>
>>>>>> rpz-beta-0.1.8-8.ipfire on 2024-10-04
>>>>>> - skipped
>>>>>>
>>>>>> ---
>>>>>>
>>>>>> rpz-beta-0.1.7-7.ipfire on 2024-10-03
>>>>>> All:
>>>>>> - new feature: includes beta version numbers for pakfire package,
>>>>>> instead of only `rpz-1.0.0-1.ipfire`, for each release.
>>>>>>
>>>>>> rpz.cgi:
>>>>>> - new feature: added new WebGUI at `rpz.cgi`
>>>>>> - a BIG thank you to Leo Hofmann for all of his work creating the webgui!!
>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>
>>>>>> rpz-make:
>>>>>> - new feature: validate entries in allowlist and blocklist
>>>>>> - new feature: add "no-reload" option for WebGUI
>>>>>>
>>>>>> rpz-metrics:
>>>>>> - new feature: info can be sorted by name, by hit count, by line count, by
>>>>>> "enabled" list or all lists
>>>>>>
>>>>>> backups:
>>>>>> - bug fix: include all files in `/var/ipfire/dns/rpz` directory in backup
>>>>>>
>>>>>> update.sh:
>>>>>> - bug fix: corrected ownership for `/var/ipfire/dns/rpz` directory during an
>>>>>> update
>>>>>>
>>>>>> Build:
>>>>>> - bug fix: `block.rpz.conf` and `block.rpz` from build. Files to be created
>>>>>> by `rpz-make`
>>>>>>
>>>>>> WebGUI and German language file
>>>>>> Contribution-by: Leo-Andres Hofmann <hofmann@leo-andres.de>
>>>>>>
>>>>>> Spanish language file
>>>>>> Contribution-by: Roberto Peña
>>>>>>
>>>>>> Italian language file
>>>>>> Contribution-by: Umberto Parma
>>>>>>
>>>>>> French language file
>>>>>> Contribution-by: gw-ipfire
>>>>>>
>>>>>> Turkish language file
>>>>>> Contribution-by: Peppe Tech
>>>>>>
>>>>>> Contribution-by: Bernhard Bitsch <bbitsch@ipfire.org>
>>>>>> Contribution-by: Erik Kapfer <erik.kapfer@ipfire.org>
>>>>>> Signed-off-by: Jon Murphy <jon.murphy@ipfire.org
>>>>>> ---
>>>>>> config/backup/includes/rpz | 4 +
>>>>>> config/cfgroot/manualpages | 1 +
>>>>>> config/menu/EX-rpz.menu | 6 +
>>>>>> config/rootfiles/common/configroot | 1 +
>>>>>> config/rootfiles/common/web-user-interface | 1 +
>>>>>> config/rootfiles/packages/rpz | 20 +
>>>>>> config/rpz/00-rpz.conf | 10 +
>>>>>> config/rpz/rpz-config | 130 +++
>>>>>> config/rpz/rpz-functions | 85 ++
>>>>>> config/rpz/rpz-make | 203 +++++
>>>>>> config/rpz/rpz-metrics | 170 ++++
>>>>>> config/rpz/rpz-sleep | 58 ++
>>>>>> config/rpz/rpz.de.pl | 30 +
>>>>>> config/rpz/rpz.en.pl | 30 +
>>>>>> config/rpz/rpz.es.pl | 30 +
>>>>>> config/rpz/rpz.fr.pl | 30 +
>>>>>> config/rpz/rpz.it.pl | 30 +
>>>>>> config/rpz/rpz.tr.pl | 30 +
>>>>>> html/cgi-bin/rpz.cgi | 923 +++++++++++++++++++++
>>>>>> lfs/rpz | 96 +++
>>>>>> make.sh | 3 +-
>>>>>> src/paks/rpz/install.sh | 36 +
>>>>>> src/paks/rpz/uninstall.sh | 38 +
>>>>>> src/paks/rpz/update.sh | 52 ++
>>>>>> 24 files changed, 2016 insertions(+), 1 deletion(-)
>>>>>> create mode 100644 config/backup/includes/rpz
>>>>>> create mode 100644 config/menu/EX-rpz.menu
>>>>>> create mode 100644 config/rootfiles/packages/rpz
>>>>>> create mode 100644 config/rpz/00-rpz.conf
>>>>>> create mode 100644 config/rpz/rpz-config
>>>>>> create mode 100644 config/rpz/rpz-functions
>>>>>> create mode 100644 config/rpz/rpz-make
>>>>>> create mode 100755 config/rpz/rpz-metrics
>>>>>> create mode 100755 config/rpz/rpz-sleep
>>>>>> create mode 100644 config/rpz/rpz.de.pl
>>>>>> create mode 100644 config/rpz/rpz.en.pl
>>>>>> create mode 100644 config/rpz/rpz.es.pl
>>>>>> create mode 100644 config/rpz/rpz.fr.pl
>>>>>> create mode 100644 config/rpz/rpz.it.pl
>>>>>> create mode 100644 config/rpz/rpz.tr.pl
>>>>>> create mode 100644 html/cgi-bin/rpz.cgi
>>>>>> create mode 100644 lfs/rpz
>>>>>> create mode 100644 src/paks/rpz/install.sh
>>>>>> create mode 100644 src/paks/rpz/uninstall.sh
>>>>>> create mode 100644 src/paks/rpz/update.sh
>>>>>>
>>>>>> diff --git a/config/backup/includes/rpz b/config/backup/includes/rpz
>>>>>> new file mode 100644
>>>>>> index 000000000..36513e494
>>>>>> --- /dev/null
>>>>>> +++ b/config/backup/includes/rpz
>>>>>> @@ -0,0 +1,4 @@
>>>>>> +/var/ipfire/dns/rpz/*
>>>>>> +/etc/unbound/zonefiles/allow.rpz
>>>>>> +/etc/unbound/zonefiles/block.rpz
>>>>>> +/etc/unbound/local.d/*rpz.conf
>>>>>> diff --git a/config/cfgroot/manualpages b/config/cfgroot/manualpages
>>>>>> index 1f7e01efc..d3a48c633 100644
>>>>>> --- a/config/cfgroot/manualpages
>>>>>> +++ b/config/cfgroot/manualpages
>>>>>> @@ -70,6 +70,7 @@ pakfire.cgi=configuration/ipfire/pakfire
>>>>>> wlanap.cgi=addons/wireless
>>>>>> tor.cgi=addons/tor
>>>>>> samba.cgi=addons/samba
>>>>>> +rpz.cgi=addons/rpz
>>>>>>
>>>>>> # Logs menu
>>>>>> logs.cgi/summary.dat=configuration/logs/summary
>>>>>> diff --git a/config/menu/EX-rpz.menu b/config/menu/EX-rpz.menu
>>>>>> new file mode 100644
>>>>>> index 000000000..2f4daf410
>>>>>> --- /dev/null
>>>>>> +++ b/config/menu/EX-rpz.menu
>>>>>> @@ -0,0 +1,6 @@
>>>>>> +$subipfire->{'20.rpz'} = {
>>>>>> + 'caption' => $Lang::tr{'rpz'},
>>>>>> + 'uri' => '/cgi-bin/rpz.cgi',
>>>>>> + 'title' => "RPZ",
>>>>>> + 'enabled' => 1,
>>>>>> +};
>>>>>> diff --git a/config/rootfiles/common/configroot b/config/rootfiles/common/configroot
>>>>>> index 9839eee45..b30d6aae4 100644
>>>>>> --- a/config/rootfiles/common/configroot
>>>>>> +++ b/config/rootfiles/common/configroot
>>>>>> @@ -120,6 +120,7 @@ var/ipfire/menu.d/70-log.menu
>>>>>> #var/ipfire/menu.d/EX-apcupsd.menu
>>>>>> #var/ipfire/menu.d/EX-guardian.menu
>>>>>> #var/ipfire/menu.d/EX-mympd.menu
>>>>>> +#var/ipfire/menu.d/EX-rpz.menu
>>>>>> #var/ipfire/menu.d/EX-samba.menu
>>>>>> #var/ipfire/menu.d/EX-tor.menu
>>>>>> #var/ipfire/menu.d/EX-transmission.menu
>>>>>> diff --git a/config/rootfiles/common/web-user-interface b/config/rootfiles/common/web-user-interface
>>>>>> index 816241dae..e00464076 100644
>>>>>> --- a/config/rootfiles/common/web-user-interface
>>>>>> +++ b/config/rootfiles/common/web-user-interface
>>>>>> @@ -69,6 +69,7 @@ srv/web/ipfire/cgi-bin/proxy.cgi
>>>>>> srv/web/ipfire/cgi-bin/qos.cgi
>>>>>> srv/web/ipfire/cgi-bin/remote.cgi
>>>>>> srv/web/ipfire/cgi-bin/routing.cgi
>>>>>> +#srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>> #srv/web/ipfire/cgi-bin/samba.cgi
>>>>>> srv/web/ipfire/cgi-bin/services.cgi
>>>>>> srv/web/ipfire/cgi-bin/shutdown.cgi
>>>>>> diff --git a/config/rootfiles/packages/rpz b/config/rootfiles/packages/rpz
>>>>>> new file mode 100644
>>>>>> index 000000000..1c8663049
>>>>>> --- /dev/null
>>>>>> +++ b/config/rootfiles/packages/rpz
>>>>>> @@ -0,0 +1,20 @@
>>>>>> +etc/unbound/local.d/00-rpz.conf
>>>>>> +etc/unbound/zonefiles
>>>>>> +etc/unbound/zonefiles/allow.rpz
>>>>>> +usr/sbin/rpz-config
>>>>>> +usr/sbin/rpz-functions
>>>>>> +usr/sbin/rpz-make
>>>>>> +usr/sbin/rpz-metrics
>>>>>> +usr/sbin/rpz-sleep
>>>>>> +var/ipfire/addon-lang/rpz.de.pl
>>>>>> +var/ipfire/addon-lang/rpz.en.pl
>>>>>> +var/ipfire/addon-lang/rpz.es.pl
>>>>>> +var/ipfire/addon-lang/rpz.fr.pl
>>>>>> +var/ipfire/addon-lang/rpz.it.pl
>>>>>> +var/ipfire/addon-lang/rpz.tr.pl
>>>>>> +var/ipfire/backup/addons/includes/rpz
>>>>>> +var/ipfire/dns/rpz
>>>>>> +var/ipfire/dns/rpz/allowlist
>>>>>> +var/ipfire/dns/rpz/blocklist
>>>>>> +var/ipfire/menu.d/EX-rpz.menu
>>>>>> +srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>> diff --git a/config/rpz/00-rpz.conf b/config/rpz/00-rpz.conf
>>>>>> new file mode 100644
>>>>>> index 000000000..f005a4f2e
>>>>>> --- /dev/null
>>>>>> +++ b/config/rpz/00-rpz.conf
>>>>>> @@ -0,0 +1,10 @@
>>>>>> +server:
>>>>>> + module-config: "respip validator iterator"
>>>>>> +
>>>>>> +rpz:
>>>>>> + name: allow.rpz
>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>> + rpz-action-override: passthru
>>>>>> + rpz-log: yes
>>>>>> + rpz-log-name: allow
>>>>>> + rpz-signal-nxdomain-ra: yes
>>>>>> diff --git a/config/rpz/rpz-config b/config/rpz/rpz-config
>>>>>> new file mode 100644
>>>>>> index 000000000..c72d50f9b
>>>>>> --- /dev/null
>>>>>> +++ b/config/rpz/rpz-config
>>>>>> @@ -0,0 +1,130 @@
>>>>>> +#!/bin/bash
>>>>>> +###############################################################################
>>>>>> +# #
>>>>>> +# IPFire.org - A linux based firewall #
>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info@ipfire.org> #
>>>>>> +# #
>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>> +# #
>>>>>> +###############################################################################
>>>>>> +
>>>>>> +version="2025-01-11 - v44"
>>>>>> +
>>>>>> +############### Functions ###############
>>>>>> +
>>>>>> +source /usr/sbin/rpz-functions
>>>>>> +
>>>>>> +############### Main ###############
>>>>>> +
>>>>>> +tagName="unbound"
>>>>>> +
>>>>>> +rpzAction="${1}" # input RPZ action
>>>>>> +rpzName="${2}" # input RPZ name
>>>>>> +rpzURL="${3}" # input RPZ URL
>>>>>> +rpzOption1="${4}" # input RPZ option #1
>>>>>> +rpzOption2="${5}" # input RPZ option #2
>>>>>> +
>>>>>> +rpzConfig="/etc/unbound/local.d/${rpzName}.rpz.conf" # output zone conf file
>>>>>> +rpzFile="/etc/unbound/zonefiles/${rpzName}.rpz" # output for RPZ file
>>>>>> +
>>>>>> +rpzLog="yes" # log default is yes
>>>>>> +ucReload="yes" # reload default is yes
>>>>>> +
>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>> + case "$1" in
>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>> + esac
>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>> +done
>>>>>> +
>>>>>> +case "${rpzAction}" in
>>>>>> + # add new rpz list
>>>>>> + add )
>>>>>> + check_name "${rpzName}" # is this a valid name?
>>>>>> + # does this config already exist? If yes, then exit
>>>>>> + if [[ -f "${rpzConfig}" ]] ; then
>>>>>> + msg_log "error: rpz: duplicate - ${rpzConfig} already exists. exit"
>>>>>> + exit 104
>>>>>> + fi
>>>>>> +
>>>>>> + # is this a valid URL?
>>>>>> + regex='^https://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
>>>>>> + if ! [[ "${rpzURL}" =~ $regex ]] ; then
>>>>>> + msg_log "error: rpz: the URL is not valid: \"${rpzURL}\". exit."
>>>>>> + exit 105
>>>>>> + fi
>>>>>> +
>>>>>> + # create the zone config file
>>>>>> + {
>>>>>> + echo "rpz:"
>>>>>> + echo " name: ${rpzName}.rpz"
>>>>>> + echo " zonefile: ${rpzFile}"
>>>>>> + echo " url: ${rpzURL}"
>>>>>> + echo " rpz-action-override: nxdomain"
>>>>>> + echo " rpz-log: ${rpzLog}"
>>>>>> + echo " rpz-log-name: ${rpzName}"
>>>>>> + echo " rpz-signal-nxdomain-ra: yes"
>>>>>> + } > "${rpzConfig}"
>>>>>> +
>>>>>> + # set-up zonefile
>>>>>> + # create an empty rpz file if it does not exist
>>>>>> + if [[ ! -f "${rpzFile}" ]] ; then
>>>>>> + touch "${rpzFile}"
>>>>>> + # unbound requires these settings for rpz files
>>>>>> + set_permissions "${rpzFile}" "${rpzConfig}"
>>>>>> + fi
>>>>>> + ;;
>>>>>> +
>>>>>> + # trash config file & rpz file
>>>>>> + remove )
>>>>>> + if ! [[ -f "${rpzConfig}" ]] ; then
>>>>>> + msg_log "error: rpz: cannot remove ${rpzConfig}, does not exist. exit"
>>>>>> + exit 106
>>>>>> + fi
>>>>>> +
>>>>>> + msg_log "info: rpz: remove config file & rpz file \"${rpzName}\""
>>>>>> + rm "${rpzConfig}"
>>>>>> + rm "${rpzFile}"
>>>>>> + ;;
>>>>>> +
>>>>>> + reload )
>>>>>> + check_unbound_conf "${checkConf}"
>>>>>> + ;;
>>>>>> +
>>>>>> + list )
>>>>>> + awk -F':' '/^\s*name:/{ gsub(/[[:blank:]]|\.rpz/, "",$2) ; NAME=$2 } \
>>>>>> + /^\s*url:/{ gsub(/[[:blank:]]/, "") ; print NAME"="$2":"$3} ' \
>>>>>> + /etc/unbound/local.d/*rpz.conf
>>>>>> + exit
>>>>>> + ;;
>>>>>> +
>>>>>> + unbound-restart )
>>>>>> + check_unbound_conf "${checkConf}"
>>>>>> + unbound_restart
>>>>>> + exit
>>>>>> + ;;
>>>>>> +
>>>>>> + * )
>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>> + printf "Usage: $(basename "$0") <ACTION> <NAME> <URL> <OPTION> <OPTION>\n"
>>>>>> + printf "Version: ${version}\n"
>>>>>> + exit 108
>>>>>> + ;;
>>>>>> +
>>>>>> +esac
>>>>>> +
>>>>>> +unbound_control_reload "${ucReload}"
>>>>>> +
>>>>>> +exit
>>>>>> diff --git a/config/rpz/rpz-functions b/config/rpz/rpz-functions
>>>>>> new file mode 100644
>>>>>> index 000000000..ace1d2690
>>>>>> --- /dev/null
>>>>>> +++ b/config/rpz/rpz-functions
>>>>>> @@ -0,0 +1,85 @@
>>>>>> +#!/bin/bash
>>>>>> +###############################################################################
>>>>>> +# #
>>>>>> +# IPFire.org - A linux based firewall #
>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>> +# #
>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>> +# #
>>>>>> +###############################################################################
>>>>>> +
>>>>>> +version="2024-12-10 - v02"
>>>>>> +
>>>>>> +############### Functions ###############
>>>>>> +
>>>>>> +msg_log () {
>>>>>> + logger --tag "${tagName}" "$*"
>>>>>> + if tty --silent ; then
>>>>>> + echo "${tagName}:" "$*"
>>>>>> + fi
>>>>>> +}
>>>>>> +
>>>>>> +# check for a valid name
>>>>>> +check_name () {
>>>>>> + local theName="${1}"
>>>>>> +
>>>>>> + regex='^[a-zA-Z0-9_]+$' # no dash or plus, alpha numeric only
>>>>>> + regex1='^(allow|block)$' # allow and block are reserved NAMEs
>>>>>> + if [[ ! "${theName}" =~ $regex ]] || [[ "${theName}" =~ $regex1 ]] ; then
>>>>>> + msg_log "error: rpz: the NAME is not valid: \"${theName}\". exit."
>>>>>> + exit 101
>>>>>> + fi
>>>>>> +}
>>>>>> +
>>>>>> +set_permissions () {
>>>>>> + chown nobody:nobody "$@"
>>>>>> + chmod 644 "$@"
>>>>>> +}
>>>>>> +
>>>>>> +check_unbound_conf () {
>>>>>> + local thecheckConf="${1:-yes}" # check config default is yes
>>>>>> +
>>>>>> + # check the above config files
>>>>>> + if [[ "${thecheckConf}" == yes ]] ; then
>>>>>> + msg_log "info: rpz: check for errors with \"unbound-checkconf\""
>>>>>> +
>>>>>> + if ! unbound-checkconf ; then
>>>>>> + msg_log "error: rpz: unbound-checkconf found invalid configuration."
>>>>>> + msg_log \
>>>>>> + "error: rpz: In Terminal run the command \"unbound-checkconf\" for more information. exit."
>>>>>> + exit 102
>>>>>> + fi
>>>>>> + fi
>>>>>> +}
>>>>>> +
>>>>>> +unbound_control_reload () {
>>>>>> + local theReload="${1:-yes}" # reload default is yes
>>>>>> +
>>>>>> + if [[ "${theReload}" == yes ]] ; then
>>>>>> + # reload due to the changes
>>>>>> + msg_log "info: rpz: run \"unbound-control reload\""
>>>>>> +
>>>>>> + if ! unbound-control reload ; then
>>>>>> + msg_log "error: rpz: unbound-control reload. exit."
>>>>>> + exit 109
>>>>>> + fi
>>>>>> + fi
>>>>>> +}
>>>>>> +
>>>>>> +unbound_restart () {
>>>>>> + # restart due to the changes
>>>>>> + msg_log "info: rpz: run \"unbound restart\""
>>>>>> +
>>>>>> + /usr/local/bin/unboundctrl restart
>>>>>> +}
>>>>>> diff --git a/config/rpz/rpz-make b/config/rpz/rpz-make
>>>>>> new file mode 100644
>>>>>> index 000000000..927d55170
>>>>>> --- /dev/null
>>>>>> +++ b/config/rpz/rpz-make
>>>>>> @@ -0,0 +1,203 @@
>>>>>> +#!/bin/bash
>>>>>> +###############################################################################
>>>>>> +# #
>>>>>> +# IPFire.org - A linux based firewall #
>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info@ipfire.org> #
>>>>>> +# #
>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>> +# #
>>>>>> +###############################################################################
>>>>>> +
>>>>>> +version="2025-01-11 - v14"
>>>>>> +
>>>>>> +############### Functions ###############
>>>>>> +
>>>>>> +source /usr/sbin/rpz-functions
>>>>>> +
>>>>>> +# create the config file for allow
>>>>>> +make_allow_config () {
>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>> + local theConfig="/etc/unbound/local.d/00-rpz.conf" # output zone conf file
>>>>>> + local theList="/var/ipfire/dns/rpz/allowlist" # input custom list of domains
>>>>>> +
>>>>>> + msg_log "info: rpz: make config file \"00-rpz.conf\""
>>>>>> +
>>>>>> + echo "server:
>>>>>> + module-config: \"respip validator iterator\"" > "${theConfig}"
>>>>>> +
>>>>>> + # does allow list exist?
>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>> +
>>>>>> + echo "rpz:
>>>>>> + name: allow.rpz
>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>> + rpz-action-override: passthru
>>>>>> + rpz-log: ${theLog}
>>>>>> + rpz-log-name: allow
>>>>>> + rpz-signal-nxdomain-ra: yes" >> "${theConfig}"
>>>>>> +
>>>>>> + fi
>>>>>> +
>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>> + set_permissions "${theConfig}"
>>>>>> +}
>>>>>> +
>>>>>> +# create the config file for block
>>>>>> +make_block_config () {
>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>> + local theConfig="/etc/unbound/local.d/block.rpz.conf" # output zone conf file
>>>>>> + local theList="/var/ipfire/dns/rpz/blocklist" # input custom list of domains
>>>>>> +
>>>>>> + msg_log "info: rpz: make config file \"block.rpz.conf\""
>>>>>> +
>>>>>> + # does block list exist?
>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>> +
>>>>>> + echo "rpz:
>>>>>> + name: block.rpz
>>>>>> + zonefile: /etc/unbound/zonefiles/block.rpz
>>>>>> + rpz-action-override: nxdomain
>>>>>> + rpz-log: ${theLog}
>>>>>> + rpz-log-name: block
>>>>>> + rpz-signal-nxdomain-ra: yes" > "${theConfig}"
>>>>>> +
>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>> + set_permissions "${theConfig}"
>>>>>> + else
>>>>>> + # no - trash the config file
>>>>>> + rm --verbose /etc/unbound/local.d/block.rpz.conf
>>>>>> + fi
>>>>>> +}
>>>>>> +
>>>>>> +# create an RPZ file for allow or block
>>>>>> +make_rpz_file () {
>>>>>> + local theName="${1}" # allow or block
>>>>>> + local theAction='.' # the default is nxdomain or block
>>>>>> + local actionList
>>>>>> +
>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>> + local theZonefile="/etc/unbound/zonefiles/${theName}.rpz" # output file for RPZ
>>>>>> +
>>>>>> + # does a list exist?
>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>> +
>>>>>> + # for allow set to passthru
>>>>>> + [[ "${theName}" == allow ]] && theAction='rpz-passthru.'
>>>>>> +
>>>>>> + # drop any extra "blanks" and add "CNAME <RPZ action>." to each line
>>>>>> + actionList=$( awk '{$1=$1};1' "${theList}" |
>>>>>> + sed "/^[^;].*[[:alnum:]]/ s|$| CNAME ${theAction}|" )
>>>>>> +
>>>>>> + msg_log "info: rpz: create zonefile for \"${theName}list\""
>>>>>> +
>>>>>> +echo "; Name: ${theName} list
>>>>>> +; Last modified: $(date "+%Y-%m-%d at %H.%M.%S %Z")
>>>>>> +;
>>>>>> +; domains with actions list
>>>>>> +;
>>>>>> +${actionList}" > "${theZonefile}"
>>>>>> +
>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>> + set_permissions "${theZonefile}"
>>>>>> + # set-up allow/block list files
>>>>>> + set_permissions "${theList}"
>>>>>> + else
>>>>>> + msg_log "info: rpz: the ${theList} is empty."
>>>>>> +
>>>>>> + rm --verbose "${theZonefile}" # trash the RPZ file
>>>>>> + fi
>>>>>> +}
>>>>>> +
>>>>>> +# check if allow/block list is valid
>>>>>> +validate_list () {
>>>>>> + local theName="${1}" # allow or block
>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>> +
>>>>>> + # remove good:
>>>>>> + # - properly formated domain names with or without leading wildcard
>>>>>> + # - properly formated top level domain (TLD) names with wildcard
>>>>>> + # - blank lines and comment lines
>>>>>> + # remaining lines are considered "bad"
>>>>>> + bad_lines=$( sed --regexp-extended \
>>>>>> + '/^(\*\.)?([a-zA-Z0-9](([a-zA-Z0-9\-]){0,61}[a-zA-Z0-9])?\.)+([a-zA-Z]{2,}|xn--[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])$/d ;
>>>>>> + /^(\*\.)([a-z]{2,61}|xn--[a-z0-9]{1,60})$/d ;
>>>>>> + /^$/d ; /^;/d' "${theList}" )
>>>>>> +
>>>>>> + if [[ ! -z "${bad_lines}" ]] ; then
>>>>>> + msg_log "error: rpz: invalid line(s) in ${theList}."
>>>>>> + printf "%s\n" "bad line(s): ${bad_lines}"
>>>>>> + exit 110
>>>>>> + fi
>>>>>> +}
>>>>>> +
>>>>>> +
>>>>>> +############### Main ###############
>>>>>> +
>>>>>> +tagName="unbound"
>>>>>> +
>>>>>> +rpzName="${1}" # input RPZ name
>>>>>> +
>>>>>> +rpzLog="yes" # log default is yes
>>>>>> +ucReload="yes" # reload default is yes
>>>>>> +
>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>> + case "$1" in
>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>> + esac
>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>> +done
>>>>>> +
>>>>>> +case "${rpzName}" in
>>>>>> + # make a new allow or block rpz file
>>>>>> +
>>>>>> + allow )
>>>>>> + validate_list 'allow' # is the allowlist valid?
>>>>>> + make_allow_config "${rpzLog}"
>>>>>> + make_rpz_file 'allow'
>>>>>> + ;;
>>>>>> +
>>>>>> + allowblock )
>>>>>> + validate_list 'allow' # is the list valid?
>>>>>> + make_allow_config "${rpzLog}"
>>>>>> + make_rpz_file 'allow'
>>>>>> + ;&
>>>>>> +
>>>>>> + block )
>>>>>> + validate_list 'block' # is the blocklist valid?
>>>>>> + make_block_config "${rpzLog}"
>>>>>> + make_rpz_file 'block'
>>>>>> + ;;
>>>>>> +
>>>>>> + reload )
>>>>>> + check_unbound_conf "${checkConf}"
>>>>>> + ;;
>>>>>> +
>>>>>> + unbound-restart )
>>>>>> + check_unbound_conf "${checkConf}"
>>>>>> + unbound_restart
>>>>>> + exit
>>>>>> + ;;
>>>>>> +
>>>>>> + * )
>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>> + printf "Usage: $(basename "$0") <NAME> <OPTION> <OPTION>\n"
>>>>>> + printf "Version: ${version}\n"
>>>>>> + exit 108
>>>>>> + ;;
>>>>>> +esac
>>>>>> +
>>>>>> +unbound_control_reload "${ucReload}"
>>>>>> +
>>>>>> +exit
>>>>>> diff --git a/config/rpz/rpz-metrics b/config/rpz/rpz-metrics
>>>>>> new file mode 100755
>>>>>> index 000000000..4d43e1629
>>>>>> --- /dev/null
>>>>>> +++ b/config/rpz/rpz-metrics
>>>>>> @@ -0,0 +1,170 @@
>>>>>> +#!/bin/bash
>>>>>> +###############################################################################
>>>>>> +# #
>>>>>> +# IPFire.org - A linux based firewall #
>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>> +# #
>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>> +# #
>>>>>> +###############################################################################
>>>>>> +
>>>>>> +version="2025-01-20 - v25"
>>>>>> +
>>>>>> +############### Main ###############
>>>>>> +
>>>>>> +weeks="2" # default to two message logs
>>>>>> +sortBy="name" # default "by name"
>>>>>> +rpzActive="enabled" # default "enabled only"
>>>>>> +
>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>> + case "$1" in
>>>>>> + --by-names | --by-name | name ) sortBy="name" ;;
>>>>>> +
>>>>>> + --by-hits | --by-hit | hits | hit ) sortBy="hit" ;;
>>>>>> +
>>>>>> + --by-lines | --by-line | lines | line ) sortBy="line" ;;
>>>>>> +
>>>>>> + --by-effect ) sortBy="effect" ;;
>>>>>> +
>>>>>> + --enabled-only ) rpzActive="enabled" ;;
>>>>>> +
>>>>>> + --active-all | --all | all ) rpzActive="all" ;;
>>>>>> +
>>>>>> + [0-9] | [0-9][0-9] ) weeks=$1 ;;
>>>>>> + esac
>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>> +done
>>>>>> +
>>>>>> +# get the list of message logs for N weeks
>>>>>> +messageLogs=$( find /var/log/messages* -type f | sort --version-sort |
>>>>>> + head -"${weeks}" )
>>>>>> +
>>>>>> +# get the list of RPZ names & counts from the message log(s)
>>>>>> +rpzNameCount=$( for logf in ${messageLogs} ; do
>>>>>> + zgrep --text --fixed-strings 'info: rpz: applied' "${logf}" |
>>>>>> + awk '$10 ~ /\[\w*]/ { print $10 }' ;
>>>>>> + done | sort | uniq --count )
>>>>>> +
>>>>>> +# flip results and remove brackets `[` and `]`
>>>>>> +rpzNameCount=$( echo "${rpzNameCount}" |
>>>>>> + awk '{ print $2, $1 }' |
>>>>>> + sed --regexp-extended 's|^\[(.*)\]|\1|' )
>>>>>> +
>>>>>> +# grab only names
>>>>>> +rpzNames=$( echo "${rpzNameCount}" | awk '{ print $1 }' )
>>>>>> +
>>>>>> +# get list of RPZ files
>>>>>> +rpzFileList=$( find /etc/unbound/zonefiles -type f -iname "*.rpz" )
>>>>>> +
>>>>>> +# get basename of those files
>>>>>> +rpzBaseNames=$( echo "${rpzFileList}" |
>>>>>> + sed 's|/etc/unbound/zonefiles/||g ; s|\.rpz||g ;' )
>>>>>> +
>>>>>> +# add to rpzNames
>>>>>> +rpzNames="${rpzNames}"$'\n'"${rpzBaseNames}"
>>>>>> +
>>>>>> +# drop duplicate names
>>>>>> +rpzNames=$( echo "${rpzNames}" | sort --unique )
>>>>>> +
>>>>>> +# get line count for each RPZ
>>>>>> +lineCount=$( echo "${rpzFileList}" | xargs wc -l )
>>>>>> +
>>>>>> +# get comment line count and blank line count for each RPZ
>>>>>> +commentCount=$( echo "${rpzFileList}" | xargs grep --count -e "^$" -e "^;" )
>>>>>> +
>>>>>> +# get modified date each RPZ
>>>>>> +modDateList=$( echo "${rpzFileList}" | xargs stat -c '%.10y %n' )
>>>>>> +
>>>>>> +ucListAuthZones=$( unbound-control list_auth_zones )
>>>>>> +
>>>>>> +# get width of RPZ names
>>>>>> +pWidth=$( echo "${rpzNames}" | awk '{ print $1" " }' | wc -L )
>>>>>> +pFormat="%-${pWidth}s %-8s %-8s %8s %12s %12s\n"
>>>>>> +
>>>>>> +# print title line
>>>>>> +printf "${pFormat}" "name" "hits" "active" "lines" " hits/line" "last download"
>>>>>> +printf -- "--------------"
>>>>>> +
>>>>>> +theResults=""
>>>>>> +totalLines=0
>>>>>> +totalHits=0
>>>>>> +while read -r theName
>>>>>> +do
>>>>>> + printf -- "--" # pretend progress bar
>>>>>> +
>>>>>> + # is this RPZ list active?
>>>>>> + theActive="disabled"
>>>>>> + if grep --quiet "^${theName}\.rpz" <<< "${ucListAuthZones}"
>>>>>> + then
>>>>>> + theActive="enabled"
>>>>>> + else
>>>>>> + [[ "${rpzActive}" == enabled ]] && continue
>>>>>> + fi
>>>>>> +
>>>>>> + # get hit count
>>>>>> + theHits="0"
>>>>>> + if output=$( grep "^${theName}\s" <<< "${rpzNameCount}" ) ; then
>>>>>> + theHits=$( echo "${output}" | awk '{ print $2 }' )
>>>>>> + totalHits=$(( totalHits + theHits ))
>>>>>> + fi
>>>>>> +
>>>>>> + # get line count
>>>>>> + theLines="n/a"
>>>>>> + hitsPerLine="0"
>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${lineCount}" ) ; then
>>>>>> + theLines=$( echo "${output}" | awk '{ print $1 }' )
>>>>>> + totalLines=$(( totalLines + theLines ))
>>>>>> +
>>>>>> + if [[ "${theLines}" -gt 2 ]] ; then
>>>>>> + hitsPerLine=$(( 100 * theHits / theLines ))
>>>>>> + fi
>>>>>> + fi
>>>>>> +
>>>>>> + # get modification date
>>>>>> + theModDate="n/a"
>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${modDateList}" ) ; then
>>>>>> + theModDate=$( echo "${output}" | awk '{ print $1 }' )
>>>>>> + fi
>>>>>> +
>>>>>> + # add to results list
>>>>>> + theResults+="${theName} ${theHits} ${theActive} ${theLines} ${hitsPerLine} ${theModDate}"$'\n'
>>>>>> +
>>>>>> +done <<< "${rpzNames}"
>>>>>> +
>>>>>> +case "${sortBy}" in
>>>>>> + # sort by "active" then by "name"
>>>>>> + name) sortArg=(-k3,3r -k1,1) ;;
>>>>>> +
>>>>>> + # sort by "active" then by "hits" then by "name"
>>>>>> + hit) sortArg=(-k3,3r -k2,2nr -k1,1) ;;
>>>>>> +
>>>>>> + # sort by "active" then by "lines" then by "name"
>>>>>> + line) sortArg=(-k3,3r -k4,4nr -k1,1) ;;
>>>>>> +
>>>>>> + # sort by "active" then by "effect" then by "name"
>>>>>> + effect) sortArg=(-k3,3r -k5,5nr -k1,1) ;;
>>>>>> +esac
>>>>>> +
>>>>>> +printf -- "--------------\n"
>>>>>> +# remove blank lines, sort, print as columns
>>>>>> +echo "${theResults}" |
>>>>>> + awk '!/^[[:space:]]*$/' |
>>>>>> + sort "${sortArg[@]}" |
>>>>>> + awk --assign=width="${pWidth}" \
>>>>>> + '{ printf "%-*s %-8s %-8s %8s %10s %% %12s\n", width, $1, $2, $3, $4, $5, $6 }'
>>>>>> +
>>>>>> +printf "${pFormat}" "" "=======" "" "========" "" ""
>>>>>> +printf "${pFormat}" "Totals -->" "${totalHits}" "" "${totalLines}" "" ""
>>>>>> +
>>>>>> +exit
>>>>>> diff --git a/config/rpz/rpz-sleep b/config/rpz/rpz-sleep
>>>>>> new file mode 100755
>>>>>> index 000000000..dd3603599
>>>>>> --- /dev/null
>>>>>> +++ b/config/rpz/rpz-sleep
>>>>>> @@ -0,0 +1,58 @@
>>>>>> +#!/bin/bash
>>>>>> +###############################################################################
>>>>>> +# #
>>>>>> +# IPFire.org - A linux based firewall #
>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>> +# #
>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>> +# #
>>>>>> +###############################################################################
>>>>>> +
>>>>>> +version="2024-08-16" # v05
>>>>>> +
>>>>>> +############### Functions ###############
>>>>>> +
>>>>>> +# send message to message log
>>>>>> +msg_log () {
>>>>>> + logger --tag "${tagName}" "$*"
>>>>>> + if tty --silent ; then
>>>>>> + echo "${tagName}:" "$*"
>>>>>> + fi
>>>>>> +}
>>>>>> +
>>>>>> +############### Main ###############
>>>>>> +
>>>>>> +tagName="unbound"
>>>>>> +
>>>>>> +sleepTime="${1:-5m}" # default to sleep for 5m (5 minutes)
>>>>>> +
>>>>>> +zoneList=$( unbound-control list_auth_zones | awk '{print $1}' )
>>>>>> +
>>>>>> +for zone in ${zoneList} ; do
>>>>>> + printf "disable ${zone}\t"
>>>>>> + unbound-control rpz_disable "${zone}"
>>>>>> +done
>>>>>> +
>>>>>> +msg_log "info: rpz: disabled all zones for ${sleepTime}"
>>>>>> +
>>>>>> +sleep "${sleepTime}"
>>>>>> +
>>>>>> +for zone in ${zoneList} ; do
>>>>>> + printf "enable ${zone}\t"
>>>>>> + unbound-control rpz_enable "${zone}"
>>>>>> +done
>>>>>> +
>>>>>> +msg_log "info: rpz: enabled all zones"
>>>>>> +
>>>>>> +exit
>>>>>> diff --git a/config/rpz/rpz.de.pl b/config/rpz/rpz.de.pl
>>>>>> new file mode 100644
>>>>>> index 000000000..3770c6bb0
>>>>>> --- /dev/null
>>>>>> +++ b/config/rpz/rpz.de.pl
>>>>>> @@ -0,0 +1,30 @@
>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>> +%tr = (%tr,
>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>> +'rpz apply' => 'Übernehmen',
>>>>>> +'rpz cl allow enable' => 'Benutzerdefinierte Allowlist aktivieren:',
>>>>>> +'rpz cl allow info' => 'Zugelassene Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>> +'rpz cl allow' => 'Benutzerdefinierte Allowlist',
>>>>>> +'rpz cl block enable' => 'Benutzerdefinierte Blocklist aktivieren:',
>>>>>> +'rpz cl block info' => 'Gesperrte Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>> +'rpz cl block' => 'Benutzerdefinierte Blocklist',
>>>>>> +'rpz cl' => 'Benutzerdefinierte Listen',
>>>>>> +'rpz exitcode 101' => 'Der Name enthält unzulässige Zeichen',
>>>>>> +'rpz exitcode 102' => 'unbound-checkconf hat eine fehlerhafte Konfiguration ermittelt. Führen Sie das Kommando unbound-checkconf auf der Konsole aus, um weitere Informationen zu erhalten.',
>>>>>> +'rpz exitcode 103' => 'Die benutzerdefinierte Allow-/Blocklist ist leer',
>>>>>> +'rpz exitcode 104' => 'Ein Eintrag mit identischem Namen existiert bereits',
>>>>>> +'rpz exitcode 105' => 'Die URL ist ungültig',
>>>>>> +'rpz exitcode 106' => 'Eintrag kann nicht entfernt werden, der Name existiert nicht',
>>>>>> +'rpz exitcode 107' => 'Der Name ist ungültig - nur "allow" oder "block" möglich',
>>>>>> +'rpz exitcode 108' => 'Fehlende oder inkorrekte Parameter',
>>>>>> +'rpz exitcode 109' => 'unbound-control reload ist fehlgeschlagen',
>>>>>> +'rpz exitcode 110' => 'Die benutzerdefinierte Allow-/Blocklist enthält unzulässige Einträge',
>>>>>> +'rpz exitcode 201' => 'Die Anmerkung enthält unzulässige Zeichen',
>>>>>> +'rpz exitcode 202' => 'Ungültiger Eintrag in der benutzerdefinierten Allowlist, Zeile ',
>>>>>> +'rpz exitcode 203' => 'Ungültiger Eintrag in der benutzerdefinierten Blocklist, Zeile ',
>>>>>> +'rpz exitcode 204' => 'Ausgewählter Eintrag existiert nicht: ',
>>>>>> +'rpz zf editor' => 'Zonendatei-Eintrag bearbeiten',
>>>>>> +'rpz zf imported' => '(importiert aus rpz-config)',
>>>>>> +'rpz zf remark info' => 'Erlaubte Zeichen sind a-z, A-Z, 0-9 und Unterstriche',
>>>>>> +'rpz zf' => 'Zonendateien',
>>>>>> +);
>>>>>> diff --git a/config/rpz/rpz.en.pl b/config/rpz/rpz.en.pl
>>>>>> new file mode 100644
>>>>>> index 000000000..0720a8940
>>>>>> --- /dev/null
>>>>>> +++ b/config/rpz/rpz.en.pl
>>>>>> @@ -0,0 +1,30 @@
>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>> +%tr = (%tr,
>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>> +'rpz apply' => 'Apply',
>>>>>> +'rpz cl allow enable' => 'Enable custom allowlist:',
>>>>>> +'rpz cl allow info' => 'Allowed domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>> +'rpz cl allow' => 'Custom allowlist',
>>>>>> +'rpz cl block enable' => 'Enable custom blocklist:',
>>>>>> +'rpz cl block info' => 'Blocked domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>> +'rpz cl block' => 'Custom blocklist',
>>>>>> +'rpz cl' => 'Custom lists',
>>>>>> +'rpz exitcode 101' => 'the NAME is not valid',
>>>>>> +'rpz exitcode 102' => 'unbound-checkconf found invalid configuration. In the Terminal run the command unbound-checkconf for more information',
>>>>>> +'rpz exitcode 103' => 'the allow/block list is empty',
>>>>>> +'rpz exitcode 104' => 'duplicate - NAME already exists',
>>>>>> +'rpz exitcode 105' => 'the URL is not valid',
>>>>>> +'rpz exitcode 106' => 'cannot remove the NAME does not exist',
>>>>>> +'rpz exitcode 107' => 'the NAME is not valid - "allow" or "block" only',
>>>>>> +'rpz exitcode 108' => 'missing or incorrect parameter',
>>>>>> +'rpz exitcode 109' => 'unbound-control reload failed',
>>>>>> +'rpz exitcode 110' => 'custom Allowlist/Blocklist contains invalid entries',
>>>>>> +'rpz exitcode 201' => 'the REMARK is not valid',
>>>>>> +'rpz exitcode 202' => 'invalid entry in allowlist, line ',
>>>>>> +'rpz exitcode 203' => 'invalid entry in blocklist, line ',
>>>>>> +'rpz exitcode 204' => 'Selected entry does not exist: ',
>>>>>> +'rpz zf editor' => 'Edit zonefiles entry',
>>>>>> +'rpz zf imported' => '(imported from rpz-config)',
>>>>>> +'rpz zf remark info' => 'Valid characters are a-z, A-Z, 0-9 and underscore.',
>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>> +);
>>>>>> diff --git a/config/rpz/rpz.es.pl b/config/rpz/rpz.es.pl
>>>>>> new file mode 100644
>>>>>> index 000000000..98628e4aa
>>>>>> --- /dev/null
>>>>>> +++ b/config/rpz/rpz.es.pl
>>>>>> @@ -0,0 +1,30 @@
>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>> +%tr = (%tr,
>>>>>> +'rpz' => 'Zonas de política de respuesta (RPZ)',
>>>>>> +'rpz apply' => 'Aplicar',
>>>>>> +'rpz cl allow enable' => 'Habilitar la lista blanca personalizada:',
>>>>>> +'rpz cl allow info' => 'Dominio permitido (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>> +'rpz cl allow' => 'Lista blanca personalizada',
>>>>>> +'rpz cl block enable' => 'Habilitar la lista negra personalizada:',
>>>>>> +'rpz cl block info' => 'Dominio bloqueado (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>> +'rpz cl block' => 'Lista negra personalizada',
>>>>>> +'rpz cl' => 'Lista personalizada',
>>>>>> +'rpz exitcode 101' => 'El NOMBRE no es válido',
>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha encontrado una configuración no válida. Desde Terminal, ejecute el comando unbound-checkconf para mayor información',
>>>>>> +'rpz exitcode 103' => 'La lista de permitidos/bloqueados está vacía',
>>>>>> +'rpz exitcode 104' => 'duplicado - NOMBRE ya existe',
>>>>>> +'rpz exitcode 105' => 'la URL no es válida',
>>>>>> +'rpz exitcode 106' => 'no es posible eliminar el NOMBRE que no existe',
>>>>>> +'rpz exitcode 107' => 'el NOMBRE no es válido - sólo "permitir" o "bloquear"',
>>>>>> +'rpz exitcode 108' => 'parámetro faltante o incorrecto',
>>>>>> +'rpz exitcode 109' => 'Error al recargar unbound-control',
>>>>>> +'rpz exitcode 110' => 'la Lista blanca/Lista negra personalizada contiene entradas no válidas',
>>>>>> +'rpz exitcode 201' => 'el COMENTARIO no es válido',
>>>>>> +'rpz exitcode 202' => 'entrada no válida en la lista blanca, línea ',
>>>>>> +'rpz exitcode 203' => 'entrada no válida en la lista negra, línea ',
>>>>>> +'rpz exitcode 204' => 'La entrada seleccionada no existe: ',
>>>>>> +'rpz zf editor' => 'Editar la entrada de archivos de zona',
>>>>>> +'rpz zf imported' => '(importado de rpz-config)',
>>>>>> +'rpz zf remark info' => 'Los caracteres válidos son a-z, A-Z, 0-9 y guión bajo',
>>>>>> +'rpz zf' => 'Archivos de zona',
>>>>>> +);
>>>>>> diff --git a/config/rpz/rpz.fr.pl b/config/rpz/rpz.fr.pl
>>>>>> new file mode 100644
>>>>>> index 000000000..f35f3c2d0
>>>>>> --- /dev/null
>>>>>> +++ b/config/rpz/rpz.fr.pl
>>>>>> @@ -0,0 +1,30 @@
>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>> +%tr = (%tr,
>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>> +'rpz apply' => 'Appliquer',
>>>>>> +'rpz cl allow enable' => 'Activer la liste d\'autorisations personnalisée:',
>>>>>> +'rpz cl allow info' => 'Domaines autorisés (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>> +'rpz cl allow' => 'Liste d\'autorisations personnalisée',
>>>>>> +'rpz cl block enable' => 'Activer la liste de blocage personnalisée:',
>>>>>> +'rpz cl block info' => 'Domaines bloqués (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>> +'rpz cl block' => 'liste de blocage personnalisé',
>>>>>> +'rpz cl' => 'Listes personnalisées',
>>>>>> +'rpz exitcode 101' => 'le NOM n\'est pas valide',
>>>>>> +'rpz exitcode 102' => 'unbound-checkconf configuration non valide trouvée. Dans le terminal, exécutez la commande unbound-checkconf pour plus d\'informations',
>>>>>> +'rpz exitcode 103' => 'la liste autoriser/bloquer est vide',
>>>>>> +'rpz exitcode 104' => 'le NOM existe déjà',
>>>>>> +'rpz exitcode 105' => 'L\'URL n\'est pas valide',
>>>>>> +'rpz exitcode 106' => 'impossible de supprimer le NOM n\'existe pas',
>>>>>> +'rpz exitcode 107' => 'le NOM n\'est pas valide - « autoriser » ou « bloquer » seulement',
>>>>>> +'rpz exitcode 108' => 'paramètre manquant ou incorrect',
>>>>>> +'rpz exitcode 109' => 'unbound-control rechargement échoué',
>>>>>> +'rpz exitcode 110' => 'la liste autoriser/bloquer contient des entrées non valides',
>>>>>> +'rpz exitcode 201' => 'la REMARQUE n\'est pas valable',
>>>>>> +'rpz exitcode 202' => 'entrée non valide dans la liste d\'autorisation, ligne ',
>>>>>> +'rpz exitcode 203' => 'entrée non valide dans la liste de blocs, ligne ',
>>>>>> +'rpz exitcode 204' => 'L\'entrée sélectionnée n\'existe pas: ',
>>>>>> +'rpz zf editor' => 'Modifier l\'entrée Fichiers Zone',
>>>>>> +'rpz zf imported' => '(importé de rpz-config)',
>>>>>> +'rpz zf remark info' => 'Les caractères valides sont a-z, A-Z, 0-9 et soulignement.',
>>>>>> +'rpz zf' => 'Fichiers Zone',
>>>>>> +);
>>>>>> diff --git a/config/rpz/rpz.it.pl b/config/rpz/rpz.it.pl
>>>>>> new file mode 100644
>>>>>> index 000000000..ee81605c9
>>>>>> --- /dev/null
>>>>>> +++ b/config/rpz/rpz.it.pl
>>>>>> @@ -0,0 +1,30 @@
>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>> +%tr = (%tr,
>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>> +'rpz apply' => 'Applica',
>>>>>> +'rpz cl allow enable' => 'Abilita la Whitelist personalizzata:',
>>>>>> +'rpz cl allow info' => 'Domini consentiti (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>> +'rpz cl allow' => 'Whitelist personalizzata',
>>>>>> +'rpz cl block enable' => 'Abilita la Blacklist personalizzata:',
>>>>>> +'rpz cl block info' => 'Domini bloccati (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>> +'rpz cl block' => 'Blacklist personalizzata',
>>>>>> +'rpz cl' => 'Liste personalizzate',
>>>>>> +'rpz exitcode 101' => 'il NOME non è valido',
>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha trovato una configurazione non valida. Dal Terminale esegui il comando unbound-checkconf per maggiori informazioni',
>>>>>> +'rpz exitcode 103' => 'l\'elenco consentiti/bloccati è vuoto',
>>>>>> +'rpz exitcode 104' => 'duplicato - NAME esiste di già',
>>>>>> +'rpz exitcode 105' => 'l\'URL non è valido',
>>>>>> +'rpz exitcode 106' => 'non è possibile rimuovere il NOME non esiste',
>>>>>> +'rpz exitcode 107' => 'il NOME non è valido - solo "consenti" o "blocca"',
>>>>>> +'rpz exitcode 108' => 'parametro mancante o non corretto',
>>>>>> +'rpz exitcode 109' => 'ricaricamento del controllo non associato non riuscito',
>>>>>> +'rpz exitcode 110' => 'la Whitelist/Blacklist personalizzata contiene voci non valide',
>>>>>> +'rpz exitcode 201' => 'l"OSSERVAZIONE non è valida',
>>>>>> +'rpz exitcode 202' => 'voce non valida nella Whitelist, riga ',
>>>>>> +'rpz exitcode 203' => 'voce non valida nella Blacklist, riga ',
>>>>>> +'rpz exitcode 204' => 'La voce selezionata non esiste: ',
>>>>>> +'rpz zf editor' => 'Modifica la voce dei file di zona',
>>>>>> +'rpz zf imported' => '(importato da rpz-config)',
>>>>>> +'rpz zf remark info' => 'I caratteri validi sono a-z, A-Z, 0-9 e trattino basso',
>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>> +);
>>>>>> diff --git a/config/rpz/rpz.tr.pl b/config/rpz/rpz.tr.pl
>>>>>> new file mode 100644
>>>>>> index 000000000..00226e192
>>>>>> --- /dev/null
>>>>>> +++ b/config/rpz/rpz.tr.pl
>>>>>> @@ -0,0 +1,30 @@
>>>>>> +# I�in eklendi Ayriyeten Response Policy Zone (RPZ)
>>>>>> +%tr = (%tr,
>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>> +'rpz apply' => 'Uygulamak',
>>>>>> +'rpz cl allow enable' => '�zel Etkinlestir allowlist:',
>>>>>> +'rpz cl allow info' => 'Izin verilmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>> +'rpz cl allow' => 'Etkinlestir allowlist',
>>>>>> +'rpz cl block enable' => '�zel Etkinlestir blocklist:',
>>>>>> +'rpz cl block info' => 'Engellenmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>> +'rpz cl block' => 'Engellenmis blocklist',
>>>>>> +'rpz cl' => 'Engellenmis lists',
>>>>>> +'rpz exitcode 101' => 'NAME ge�erli degil',
>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ge�ersiz yapilandirma bulundu. Terminalde daha fazla bilgi i�in Unbound-checkConf komutunu �alistirin',
>>>>>> +'rpz exitcode 103' => 'allow/block liste bos',
>>>>>> +'rpz exitcode 104' => 'kopyalamak - NAME zaten var',
>>>>>> +'rpz exitcode 105' => 'URL ge�erli degil',
>>>>>> +'rpz exitcode 106' => '�ikarilamiyor NAME yok',
>>>>>> +'rpz exitcode 107' => 'NAME ge�erli degil - "allow" veya "block" yalniz',
>>>>>> +'rpz exitcode 108' => 'Parametre eksik veya yanlis',
>>>>>> +'rpz exitcode 109' => 'unbound-control basarisiz',
>>>>>> +'rpz exitcode 110' => 'Engellenmis Allowlist/Blocklist ge�ersiz girisler i�erir',
>>>>>> +'rpz exitcode 201' => 'REMARK ge�erli degil',
>>>>>> +'rpz exitcode 202' => 'Ge�ersiz giris allowlist, line ',
>>>>>> +'rpz exitcode 203' => 'Ge�ersiz giris blocklist, line ',
>>>>>> +'rpz exitcode 204' => 'Se�ilen giris yok: ',
>>>>>> +'rpz zf editor' => 'yazimlamak zonefiles giris',
>>>>>> +'rpz zf imported' => '(ithal edildi rpz-config)',
>>>>>> +'rpz zf remark info' => 'Ge�erli karakterler a-z, A-Z, 0-9ve alt�st.',
>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>> +);
>>>>>> diff --git a/html/cgi-bin/rpz.cgi b/html/cgi-bin/rpz.cgi
>>>>>> new file mode 100644
>>>>>> index 000000000..a821c92ac
>>>>>> --- /dev/null
>>>>>> +++ b/html/cgi-bin/rpz.cgi
>>>>>> @@ -0,0 +1,923 @@
>>>>>> +#!/usr/bin/perl
>>>>>> +###############################################################################
>>>>>> +# #
>>>>>> +# IPFire.org - A linux based firewall #
>>>>>> +# Copyright (C) 2005-2024 IPFire Team <info@ipfire.org> #
>>>>>> +# #
>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>> +# #
>>>>>> +###############################################################################
>>>>>> +
>>>>>> +use strict;
>>>>>> +use Scalar::Util qw(looks_like_number);
>>>>>> +
>>>>>> +# debugging
>>>>>> +#use warnings;
>>>>>> +#use CGI::Carp 'fatalsToBrowser';
>>>>>> +#use Data::Dumper;
>>>>>> +
>>>>>> +require '/var/ipfire/general-functions.pl';
>>>>>> +require "${General::swroot}/lang.pl";
>>>>>> +require "${General::swroot}/header.pl";
>>>>>> +
>>>>>> +###--- Extra HTML ---###
>>>>>> +my $extraHead = <<END
>>>>>> +<style>
>>>>>> + /* alternating row background */
>>>>>> + .tbl tr:nth-child(2n+2) {
>>>>>> + background-color: var(--color-light-grey);
>>>>>> + }
>>>>>> + .tbl tr:nth-child(2n+3) {
>>>>>> + background-color: var(--color-grey);
>>>>>> + }
>>>>>> + /* text styles */
>>>>>> + .tbl th:not(:last-child) {
>>>>>> + text-align: left;
>>>>>> + }
>>>>>> + div.right {
>>>>>> + text-align: right;
>>>>>> + margin-top: 0.5em;
>>>>>> + }
>>>>>> + /* customlist input */
>>>>>> + textarea.domainlist {
>>>>>> + margin: 0.5em 0;
>>>>>> + resize: vertical;
>>>>>> + min-height: 10em;
>>>>>> + overflow: auto;
>>>>>> + white-space: pre;
>>>>>> + }
>>>>>> + button[type=submit]:disabled {
>>>>>> + opacity: 0.6;
>>>>>> + }
>>>>>> +</style>
>>>>>> +END
>>>>>> +;
>>>>>> +###--- End of extra HTML ---###
>>>>>> +
>>>>>> +
>>>>>> +### Settings ###
>>>>>> +
>>>>>> +# Request DNS service reload after configuration change
>>>>>> +my $RPZ_RELOAD_FLAG = "${General::swroot}/dns/rpz/reload.flag";
>>>>>> +
>>>>>> +# Configuration file for all available zonefiles
>>>>>> +# Format: index, name (unique), enabled (on/off), URL, remark
>>>>>> +my $ZONEFILES_CONF = "${General::swroot}/dns/rpz/zonefiles.conf";
>>>>>> +
>>>>>> +# Configuration file for custom lists
>>>>>> +# IDs: 0=allowlist, 1=blocklist, 2=options (allow/block enabled)
>>>>>> +my $CUSTOMLISTS_CONF = "${General::swroot}/dns/rpz/customlists.conf";
>>>>>> +
>>>>>> +# Export custom lists to rpz-config
>>>>>> +my $RPZ_ALLOWLIST = "${General::swroot}/dns/rpz/allowlist";
>>>>>> +my $RPZ_BLOCKLIST = "${General::swroot}/dns/rpz/blocklist";
>>>>>> +
>>>>>> +
>>>>>> +### Preparation ###
>>>>>> +
>>>>>> +# Create missing config files
>>>>>> +unless(-f $ZONEFILES_CONF) { &General::system('touch', "$ZONEFILES_CONF"); }
>>>>>> +unless(-f $CUSTOMLISTS_CONF) { &General::system('touch', "$CUSTOMLISTS_CONF"); }
>>>>>> +
>>>>>> +
>>>>>> +## Global gui data
>>>>>> +my $errormessage = "";
>>>>>> +
>>>>>> +## Global configuration data
>>>>>> +my %zonefiles = ();
>>>>>> +my %customlists = ();
>>>>>> +&_zonefiles_load();
>>>>>> +&_customlists_load();
>>>>>> +
>>>>>> +## Global CGI form data
>>>>>> +my %cgiparams = ();
>>>>>> +&Header::getcgihash(\%cgiparams);
>>>>>> +
>>>>>> +my $action = $cgiparams{'ACTION'} // 'NONE';
>>>>>> +my $action_key = $cgiparams{'KEY'} // ''; # entry being edited, empty = none/new
>>>>>> +
>>>>>> +
>>>>>> +###--- Process form actions ---###
>>>>>> +
>>>>>> +# Zonefiles action: Check whether the requested entry exists
>>>>>> +if((substr($action, 0, 3) eq 'ZF_') && ($action_key)) {
>>>>>> + unless(defined $zonefiles{$action_key}) {
>>>>>> + $errormessage = &_rpz_error_tr(204, $action_key);
>>>>>> + $action = 'NONE';
>>>>>> + }
>>>>>> +}
>>>>>> +
>>>>>> +## Perform actions
>>>>>> +if($action eq 'ZF_SAVE') { ## Save new or modified zonefiles entry
>>>>>> + if(&_action_zf_save()) {
>>>>>> + $action = 'NONE'; # success, return to main page
>>>>>> + &_http_prg_redirect();
>>>>>> + } else {
>>>>>> + $action = 'ZF_EDIT'; # error occured, keep editing
>>>>>> + }
>>>>>> +
>>>>>> +} elsif($action eq 'ZF_TOGGLE') { ## Toggle on/off
>>>>>> + if(&_action_zf_toggle()) {
>>>>>> + $action = 'NONE';
>>>>>> + &_http_prg_redirect();
>>>>>> + }
>>>>>> +
>>>>>> +} elsif($action eq 'ZF_REMOVE') { ## Remove entry
>>>>>> + if(&_action_zf_remove()) {
>>>>>> + $action = 'NONE';
>>>>>> + &_http_prg_redirect();
>>>>>> + }
>>>>>> +
>>>>>> +} elsif($action eq 'CL_SAVE') { ## Save custom lists
>>>>>> + if(&_action_cl_save()) {
>>>>>> + $action = 'NONE';
>>>>>> + &_http_prg_redirect();
>>>>>> + }
>>>>>> +
>>>>>> +} elsif($action eq 'RPZ_RELOAD') { ## Reload dns configuration
>>>>>> + if(&_action_rpz_reload()) {
>>>>>> + $action = 'NONE';
>>>>>> + &_http_prg_redirect();
>>>>>> + }
>>>>>> +
>>>>>> +} elsif($action eq 'UNB_RESTART') { ## Restart unbound service
>>>>>> + if(&_action_unb_restart()) {
>>>>>> + $action = 'NONE';
>>>>>> + &_http_prg_redirect();
>>>>>> + }
>>>>>> +
>>>>>> +}
>>>>>> +
>>>>>> +
>>>>>> +###--- Start GUI ---###
>>>>>> +
>>>>>> +## Start http output
>>>>>> +&Header::showhttpheaders();
>>>>>> +
>>>>>> +# Start HTML
>>>>>> +&Header::openpage($Lang::tr{'rpz'}, 1, $extraHead);
>>>>>> +&Header::openbigbox('100%', 'left', '');
>>>>>> +
>>>>>> +# Show error messages
>>>>>> +if($errormessage) {
>>>>>> + &_print_message($errormessage);
>>>>>> +}
>>>>>> +
>>>>>> +# Handle zonefile add/edit mode
>>>>>> +if($action eq "ZF_EDIT") {
>>>>>> + &_print_zonefile_editor();
>>>>>> +
>>>>>> + # Finalize page and exit cleanly
>>>>>> + &Header::closebigbox();
>>>>>> + &Header::closepage();
>>>>>> + exit(0);
>>>>>> +}
>>>>>> +
>>>>>> +# Show gui elements
>>>>>> +&_print_zonefiles();
>>>>>> +&_print_customlists();
>>>>>> +&_print_gui_extras();
>>>>>> +
>>>>>> +&Header::closebigbox();
>>>>>> +&Header::closepage();
>>>>>> +
>>>>>> +###--- End of GUI ---###
>>>>>> +
>>>>>> +
>>>>>> +###--- Internal configuration file functions ---###
>>>>>> +
>>>>>> +# Load all available zonefiles from rpz-config and the internal configuration
>>>>>> +sub _zonefiles_load {
>>>>>> + # Clean start
>>>>>> + %zonefiles = ();
>>>>>> +
>>>>>> + # Source 1: Get the currently enabled zonefiles from rpz-config (expected format [name]=[URL])
>>>>>> + my @enabled_files = &General::system_output('/usr/sbin/rpz-config', 'list');
>>>>>> +
>>>>>> + foreach my $row (@enabled_files) {
>>>>>> + chomp($row);
>>>>>> +
>>>>>> + # Use regex instead of split() to skip non-matching lines
>>>>>> + next unless($row =~ /^(\w+)=(.+)$/);
>>>>>> + my ($name, $url) = ($1, $2);
>>>>>> +
>>>>>> + # Unique names are already guaranteed by rpz-config
>>>>>> + if(&_rpz_validate_zonefile($name, $url, '', 0) == 0) {
>>>>>> + # Populate global data hash, mark all found entries as enabled
>>>>>> + my %entry = ('enabled' => 'on',
>>>>>> + 'url' => $url,
>>>>>> + 'remark' => $Lang::tr{'rpz zf imported'});
>>>>>> +
>>>>>> + $zonefiles{$name} = \%entry;
>>>>>> + }
>>>>>> + }
>>>>>> +
>>>>>> + # Source 2: Get additional data and disabled entries from configuration file
>>>>>> + my %configured_files = ();
>>>>>> + &General::readhasharray($ZONEFILES_CONF, \%configured_files);
>>>>>> +
>>>>>> + foreach my $row (values (%configured_files)) {
>>>>>> + my ($name, $enabled, $url, $remark) = @$row;
>>>>>> + $remark //= "";
>>>>>> +
>>>>>> + next unless($name);
>>>>>> +
>>>>>> + # Check whether this row belongs to an entry already imported from rpz-config
>>>>>> + if(defined $zonefiles{$name}) {
>>>>>> + # Existing entry, only merge additional data
>>>>>> + $zonefiles{$name}{'remark'} = $remark;
>>>>>> + } else {
>>>>>> + # Skip entry if it is marked as enabled but not found by rpz-config. It was then deleted manually
>>>>>> + if($enabled ne 'on') {
>>>>>> + # Populate global data hash
>>>>>> + my %entry = ('enabled' => 'off',
>>>>>> + 'url' => $url // "",
>>>>>> + 'remark' => $remark);
>>>>>> +
>>>>>> + $zonefiles{$name} = \%entry;
>>>>>> + }
>>>>>> + }
>>>>>> + }
>>>>>> +}
>>>>>> +
>>>>>> +# Save internal zonefiles configuration
>>>>>> +sub _zonefiles_save_conf {
>>>>>> + my $index = 0;
>>>>>> + my %export = ();
>>>>>> +
>>>>>> + # Loop trough all zonefiles and create "hasharray" type export
>>>>>> + foreach my $name (keys %zonefiles) {
>>>>>> + my @entry = ($name,
>>>>>> + $zonefiles{$name}{'enabled'},
>>>>>> + $zonefiles{$name}{'url'},
>>>>>> + $zonefiles{$name}{'remark'});
>>>>>> +
>>>>>> + $export{$index++} = \@entry;
>>>>>> + }
>>>>>> +
>>>>>> + &General::writehasharray($ZONEFILES_CONF, \%export);
>>>>>> +}
>>>>>> +
>>>>>> +# Load custom lists from rpz-config and the internal configuration
>>>>>> +sub _customlists_load {
>>>>>> + # Clean start
>>>>>> + %customlists = ();
>>>>>> +
>>>>>> + # Load configuration file
>>>>>> + my %lists_conf = ();
>>>>>> + &General::readhasharray($CUSTOMLISTS_CONF, \%lists_conf);
>>>>>> +
>>>>>> + # Get list options, enabled by default to start import
>>>>>> + $customlists{'allow'}{'enabled'} = $lists_conf{2}[0] // 'on';
>>>>>> + $customlists{'block'}{'enabled'} = $lists_conf{2}[1] // 'on';
>>>>>> +
>>>>>> + # Import enabled list from rpz-config, otherwise retrieve stored or empty list from configuration file
>>>>>> + if($customlists{'allow'}{'enabled'} eq 'on') {
>>>>>> + &_customlist_import('allow', $RPZ_ALLOWLIST);
>>>>>> + } else {
>>>>>> + $customlists{'allow'}{'list'} = $lists_conf{0} // [];
>>>>>> + }
>>>>>> + if($customlists{'block'}{'enabled'} eq 'on') {
>>>>>> + &_customlist_import('block', $RPZ_BLOCKLIST);
>>>>>> + } else {
>>>>>> + $customlists{'block'}{'list'} = $lists_conf{1} // [];
>>>>>> + }
>>>>>> +}
>>>>>> +
>>>>>> +# Save internal custom lists configuration
>>>>>> +sub _customlists_save_conf {
>>>>>> + my %export = ();
>>>>>> +
>>>>>> + # Match IDs with import function
>>>>>> + $export{0} = $customlists{'allow'}{'list'};
>>>>>> + $export{1} = $customlists{'block'}{'list'};
>>>>>> + $export{2} = [$customlists{'allow'}{'enabled'}, $customlists{'block'}{'enabled'}];
>>>>>> +
>>>>>> + &General::writehasharray($CUSTOMLISTS_CONF, \%export);
>>>>>> +}
>>>>>> +
>>>>>> +# Import a custom list from plain file, returns empty list if file is missing
>>>>>> +sub _customlist_import {
>>>>>> + my ($listname, $filename) = @_;
>>>>>> + my @list = ();
>>>>>> +
>>>>>> + # File exists, load and check all lines
>>>>>> + if(-f $filename) {
>>>>>> + open(my $FH, '<', $filename) or die "Can't read $filename: $!";
>>>>>> + while(my $line = <$FH>) {
>>>>>> + chomp($line);
>>>>>> + push(@list, $line);
>>>>>> + }
>>>>>> + close($FH);
>>>>>> +
>>>>>> + # Clean up imported data
>>>>>> + &_rpz_validate_customlist(\@list, 1);
>>>>>> + }
>>>>>> +
>>>>>> + $customlists{$listname}{'list'} = \@list;
>>>>>> +}
>>>>>> +
>>>>>> +# Export a custom list to plain file or clear file if list is disabled
>>>>>> +sub _customlist_export {
>>>>>> + my ($listname, $filename) = @_;
>>>>>> + return unless(defined $customlists{$listname});
>>>>>> +
>>>>>> + # Write enabled domain list to file, otherwise save empty file
>>>>>> + open(my $FH, '>', $filename) or die "Can't write $filename: $!";
>>>>>> +
>>>>>> + if($customlists{$listname}{'enabled'} eq 'on') {
>>>>>> + foreach my $line (@{$customlists{$listname}{'list'}}) {
>>>>>> + print $FH "$line\n";
>>>>>> + }
>>>>>> + } else {
>>>>>> + print $FH "; Note: This list is currently disabled by $ENV{'SCRIPT_NAME'}\n";
>>>>>> + }
>>>>>> +
>>>>>> + close($FH);
>>>>>> +}
>>>>>> +
>>>>>> +
>>>>>> +###--- Internal gui functions ---###
>>>>>> +
>>>>>> +# Show simple message box
>>>>>> +sub _print_message {
>>>>>> + my ($message, $title) = @_;
>>>>>> + $title ||= $Lang::tr{'error messages'};
>>>>>> +
>>>>>> + &Header::openbox('100%', 'left', $title);
>>>>>> + print "<span>$message</span>";
>>>>>> + &Header::closebox();
>>>>>> +}
>>>>>> +
>>>>>> +# Show all zone files and related gui elements
>>>>>> +sub _print_zonefiles {
>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf'});
>>>>>> +
>>>>>> + print <<END
>>>>>> +<table class="tbl" width="100%">
>>>>>> + <tr>
>>>>>> + <th>$Lang::tr{'name'}</th>
>>>>>> + <th>URL</th>
>>>>>> + <th>$Lang::tr{'remark'}</th>
>>>>>> + <th colspan="3">$Lang::tr{'action'}</th>
>>>>>> + </tr>
>>>>>> +END
>>>>>> +;
>>>>>> +
>>>>>> + # Sort zonefiles by name and loop trough all entries
>>>>>> + foreach my $name (sort keys %zonefiles) {
>>>>>> +
>>>>>> + # Toggle button label translation
>>>>>> + my $toggle_tr = ($zonefiles{$name}{'enabled'} eq 'on') ? $Lang::tr{'click to disable'} : $Lang::tr{'click to enable'};
>>>>>> +
>>>>>> + print <<END
>>>>>> + <tr>
>>>>>> + <td>$name</td>
>>>>>> + <td>$zonefiles{$name}{'url'}</td>
>>>>>> + <td>$zonefiles{$name}{'remark'}</td>
>>>>>> +
>>>>>> + <td align="center" width="5%">
>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>> + <input type="hidden" name="ACTION" value="ZF_TOGGLE">
>>>>>> + <input type="image" src="/images/$zonefiles{$name}{'enabled'}.gif" title="$toggle_tr" alt="$toggle_tr">
>>>>>> + </form>
>>>>>> + </td>
>>>>>> + <td align="center" width="5%">
>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>> + <input type="hidden" name="ACTION" value="ZF_EDIT">
>>>>>> + <input type="image" src="/images/edit.gif" title="$Lang::tr{'edit'}" alt="$Lang::tr{'edit'}">
>>>>>> + </form>
>>>>>> + </td>
>>>>>> + <td align="center" width="5%">
>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>> + <input type="hidden" name="ACTION" value="ZF_REMOVE">
>>>>>> + <input type="image" src="/images/delete.gif" title="$Lang::tr{'remove'}" alt="$Lang::tr{'remove'}">
>>>>>> + </form>
>>>>>> + </td>
>>>>>> + </tr>
>>>>>> +END
>>>>>> +;
>>>>>> + }
>>>>>> +
>>>>>> + # Disable reload button if not needed
>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>> +
>>>>>> + print <<END
>>>>>> +</table>
>>>>>> +
>>>>>> +<div class="right">
>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>> + <input type="hidden" name="KEY" value="">
>>>>>> + <button type="submit" name="ACTION" value="ZF_EDIT">$Lang::tr{'add'}</button>
>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>> + </form>
>>>>>> +</div>
>>>>>> +END
>>>>>> +;
>>>>>> +
>>>>>> + &Header::closebox();
>>>>>> +}
>>>>>> +
>>>>>> +# Show zonefiles entry editor
>>>>>> +sub _print_zonefile_editor {
>>>>>> +
>>>>>> + # Key specified: Edit existing entry
>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>> + # Load data to be edited, but don't override already present values (allows user to edit after error)
>>>>>> + $cgiparams{'ZF_NAME'} //= $action_key;
>>>>>> + $cgiparams{'ZF_URL'} //= $zonefiles{$action_key}{'url'};
>>>>>> + $cgiparams{'ZF_REMARK'} //= $zonefiles{$action_key}{'remark'};
>>>>>> + }
>>>>>> +
>>>>>> + # Fallback to empty form
>>>>>> + $cgiparams{'ZF_NAME'} //= "";
>>>>>> + $cgiparams{'ZF_URL'} //= "";
>>>>>> + $cgiparams{'ZF_REMARK'} //= "";
>>>>>> +
>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf editor'});
>>>>>> +
>>>>>> + print <<END
>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>> +<input type="hidden" name="KEY" value="$action_key">
>>>>>> +<table width="100%">
>>>>>> + <tr>
>>>>>> + <td width="20%">$Lang::tr{'name'}: <img src="/blob.gif" alt="*"></td>
>>>>>> + <td><input type="text" name="ZF_NAME" value="$cgiparams{'ZF_NAME'}" size="40" maxlength="32" title="$Lang::tr{'rpz zf remark info'}" pattern="[a-zA-Z0-9_]{1,32}" required></td>
>>>>>> + </tr>
>>>>>> + <tr>
>>>>>> + <td width="20%">URL: <img src="/blob.gif" alt="*"></td>
>>>>>> + <td><input type="url" name="ZF_URL" value="$cgiparams{'ZF_URL'}" size="40" maxlength="128" required></td>
>>>>>> + </tr>
>>>>>> + <tr>
>>>>>> + <td width="20%">$Lang::tr{'remark'}:</td>
>>>>>> + <td><input type="text" name="ZF_REMARK" value="$cgiparams{'ZF_REMARK'}" size="40" maxlength="32"></td>
>>>>>> + </tr>
>>>>>> + <tr>
>>>>>> + <td colspan="2"><hr></td>
>>>>>> + </tr>
>>>>>> + <tr>
>>>>>> + <td width="55%"><img src="/blob.gif" alt="*"> $Lang::tr{'required field'}</td>
>>>>>> + <td align="right"><button type="submit" name="ACTION" value="ZF_SAVE">$Lang::tr{'save'}</button></td>
>>>>>> + </tr>
>>>>>> +</table>
>>>>>> +</form>
>>>>>> +
>>>>>> +<div class="right">
>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>> + <button type="submit" name="ACTION" value="NONE">$Lang::tr{'back'}</button>
>>>>>> + </form>
>>>>>> +</div>
>>>>>> +END
>>>>>> +;
>>>>>> +
>>>>>> + &Header::closebox();
>>>>>> +}
>>>>>> +
>>>>>> +# Show custom allow/block files and related gui elements
>>>>>> +sub _print_customlists {
>>>>>> +
>>>>>> + # Load lists from config, unless they are currently being edited
>>>>>> + if($action ne 'CL_SAVE') {
>>>>>> + $cgiparams{'ALLOW_LIST'} = join("\n", @{$customlists{'allow'}{'list'}});
>>>>>> + $cgiparams{'BLOCK_LIST'} = join("\n", @{$customlists{'block'}{'list'}});
>>>>>> +
>>>>>> + $cgiparams{'ALLOW_ENABLED'} = ($customlists{'allow'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>> + $cgiparams{'BLOCK_ENABLED'} = ($customlists{'block'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>> + }
>>>>>> +
>>>>>> + # Fallback to empty form
>>>>>> + $cgiparams{'ALLOW_LIST'} //= "";
>>>>>> + $cgiparams{'BLOCK_LIST'} //= "";
>>>>>> +
>>>>>> + # HTML checkboxes, unchecked = no or undef value in POST data
>>>>>> + my %checked = ();
>>>>>> + $checked{'ALLOW_ENABLED'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? " checked" : "";
>>>>>> + $checked{'BLOCK_ENABLED'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? " checked" : "";
>>>>>> +
>>>>>> + # Disable reload button if not needed
>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>> +
>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz cl'});
>>>>>> +
>>>>>> + print <<END
>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>> +<table width="100%">
>>>>>> + <tr>
>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl allow'}</b><br>$Lang::tr{'rpz cl allow info'}</td>
>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl block'}</b><br>$Lang::tr{'rpz cl block info'}</td>
>>>>>> + </tr>
>>>>>> + <tr>
>>>>>> + <td colspan="2"><textarea name="ALLOW_LIST" class="domainlist" cols="45">
>>>>>> +$cgiparams{'ALLOW_LIST'}</textarea></td>
>>>>>> + <td colspan="2"><textarea name="BLOCK_LIST" class="domainlist" cols="45">
>>>>>> +$cgiparams{'BLOCK_LIST'}</textarea></td>
>>>>>> + </tr>
>>>>>> + <tr>
>>>>>> + <td><label for="allow_enabled">$Lang::tr{'rpz cl allow enable'}</label></td>
>>>>>> + <td width="15%"><input type="checkbox" name="ALLOW_ENABLED" id="allow_enabled"$checked{'ALLOW_ENABLED'}></td>
>>>>>> + <td><label for="block_enabled">$Lang::tr{'rpz cl block enable'}</label></td>
>>>>>> + <td width="15%"><input type="checkbox" name="BLOCK_ENABLED" id="block_enabled"$checked{'BLOCK_ENABLED'}></td>
>>>>>> + </tr>
>>>>>> + <tr>
>>>>>> + <td colspan="4"><hr></td>
>>>>>> + </tr>
>>>>>> + <tr>
>>>>>> + <td align="right" colspan="4">
>>>>>> + <button type="submit" name="ACTION" value="CL_SAVE">$Lang::tr{'save'}</button>
>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>> + </td>
>>>>>> +</table>
>>>>>> +</form>
>>>>>> +END
>>>>>> +;
>>>>>> +
>>>>>> + &Header::closebox();
>>>>>> +}
>>>>>> +
>>>>>> +# Output javascript and extra gui elements
>>>>>> +sub _print_gui_extras {
>>>>>> +
>>>>>> + # Apply/Restart button modifier key handler
>>>>>> + if(&_rpz_needs_reload()) {
>>>>>> + print <<END
>>>>>> +<script>
>>>>>> + // Commit modifier key handler
>>>>>> + (function(jq, document) {
>>>>>> + var keyEventsOn = false; // Keyboard events attached
>>>>>> + var keyModify = false; // Modifier key pressed
>>>>>> + var mouseHover = false; // Mouse over commit button
>>>>>> + var btnModified = false; // Button modified to "Restart"
>>>>>> +
>>>>>> + // Document-level key events, enable only while cursor is over button
>>>>>> + function attachKeyEvents() {
>>>>>> + if(keyEventsOn) {
>>>>>> + return;
>>>>>> + }
>>>>>> + keyEventsOn = true;
>>>>>> +
>>>>>> + jq(document).on("keydown.rpz", function(event) {
>>>>>> + if((!keyModify) && event.shiftKey) {
>>>>>> + keyModify = true;
>>>>>> + handleModify();
>>>>>> + }
>>>>>> + });
>>>>>> + jq(document).on("keyup.rpz", function(event) {
>>>>>> + if(keyModify && (!event.shiftKey)) {
>>>>>> + keyModify = false;
>>>>>> + handleModify();
>>>>>> + }
>>>>>> + });
>>>>>> + }
>>>>>> + function removeKeyEvents() {
>>>>>> + keyModify = false;
>>>>>> + if(keyEventsOn) {
>>>>>> + jq(document).off("keydown.rpz keyup.rpz");
>>>>>> + keyEventsOn = false;
>>>>>> + }
>>>>>> + }
>>>>>> +
>>>>>> + // Attach mouse hover events to commit buttons
>>>>>> + function attachMouseEvents() {
>>>>>> + jq("button.commit").on("mouseenter", function(event) {
>>>>>> + if(!mouseHover) {
>>>>>> + mouseHover = true;
>>>>>> + attachKeyEvents();
>>>>>> + // Handle already pressed key
>>>>>> + keyModify = !!(event.shiftKey);
>>>>>> + handleModify();
>>>>>> + }
>>>>>> + });
>>>>>> +
>>>>>> + // Cursor moved away: Disable key listener to minimize events
>>>>>> + jq("button.commit").on("mouseleave", function() {
>>>>>> + if(mouseHover) {
>>>>>> + mouseHover = false;
>>>>>> + removeKeyEvents();
>>>>>> + handleModify();
>>>>>> + }
>>>>>> + });
>>>>>> + }
>>>>>> +
>>>>>> + // Modify commit button
>>>>>> + function handleModify() {
>>>>>> + let modify = mouseHover && keyModify;
>>>>>> + if(btnModified != modify) {
>>>>>> + if(modify) {
>>>>>> + jq("button.commit").text("$Lang::tr{'restart'}").val("UNB_RESTART");
>>>>>> + } else {
>>>>>> + jq("button.commit").text("$Lang::tr{'rpz apply'}").val("RPZ_RELOAD");
>>>>>> + }
>>>>>> + btnModified = modify;
>>>>>> + }
>>>>>> + }
>>>>>> +
>>>>>> + // jQuery DOM ready
>>>>>> + jq(function() {
>>>>>> + attachMouseEvents();
>>>>>> + });
>>>>>> + })(jQuery, document);
>>>>>> +</script>
>>>>>> +END
>>>>>> +;
>>>>>> + } # End of modifier key handler
>>>>>> +
>>>>>> +}
>>>>>> +
>>>>>> +
>>>>>> +###--- Internal action processing functions ---###
>>>>>> +
>>>>>> +# Toggle zonefile on/off
>>>>>> +sub _action_zf_toggle {
>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>> +
>>>>>> + my $result = 0;
>>>>>> + my $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>> +
>>>>>> + # Perform toggle action
>>>>>> + if($enabled eq 'on') {
>>>>>> + $enabled = 'off';
>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>> + } else {
>>>>>> + $enabled = 'on';
>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $action_key, $zonefiles{$action_key}{'url'}, '--no-reload');
>>>>>> + }
>>>>>> +
>>>>>> + # Check for errors, request service reload on success
>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>> +
>>>>>> + # Save changes
>>>>>> + $zonefiles{$action_key}{'enabled'} = $enabled;
>>>>>> + &_zonefiles_save_conf();
>>>>>> +
>>>>>> + return 1;
>>>>>> +}
>>>>>> +
>>>>>> +# Remove zonefile
>>>>>> +sub _action_zf_remove {
>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>> +
>>>>>> + # Remove from rpz-config if currently active
>>>>>> + if($zonefiles{$action_key}{'enabled'} eq 'on') {
>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>> +
>>>>>> + # Check for errors, request service reload on success
>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>> + }
>>>>>> +
>>>>>> + # Remove from data hash and save changes
>>>>>> + delete $zonefiles{$action_key};
>>>>>> + &_zonefiles_save_conf();
>>>>>> +
>>>>>> + # Clear action_key, as the entry is now removed entirely
>>>>>> + $action_key = "";
>>>>>> +
>>>>>> + return 1;
>>>>>> +}
>>>>>> +
>>>>>> +# Create or update zonefile entry
>>>>>> +# Returns undef if gui needs to stay in editor mode
>>>>>> +sub _action_zf_save {
>>>>>> + my $result = 0;
>>>>>> +
>>>>>> + my $name = $cgiparams{'ZF_NAME'} // "";
>>>>>> + my $url = $cgiparams{'ZF_URL'} // "";
>>>>>> + my $remark = $cgiparams{'ZF_REMARK'} // "";
>>>>>> + my $enabled = 'on'; # Enable new entries by default
>>>>>> +
>>>>>> + # Note on variables:
>>>>>> + # name = unique key, will be used to address the entry
>>>>>> + # action_key = name of the entry being edited, empty for new entry
>>>>>> +
>>>>>> + # Only check for unique name if it changed
>>>>>> + # (this also checks new entries because the action_key is empty in this case)
>>>>>> + $result = &_rpz_validate_zonefile($name, $url, $remark, (lc($name) ne lc($action_key)));
>>>>>> + return unless &_rpz_check_result($result, 0);
>>>>>> +
>>>>>> + # Edit existing entry: Determine what was changed
>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>> + # Name und URL remain unchanged, only save remark and finish
>>>>>> + if(($name eq $action_key) && ($url eq $zonefiles{$action_key}{'url'})) {
>>>>>> + $zonefiles{$action_key}{'remark'} = $remark;
>>>>>> + &_zonefiles_save_conf();
>>>>>> +
>>>>>> + return 1;
>>>>>> + }
>>>>>> +
>>>>>> + # Entry was changed and needs to be recreated, preserve status
>>>>>> + $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>> +
>>>>>> + # Remove from rpz-config
>>>>>> + return unless &_action_zf_remove();
>>>>>> + }
>>>>>> +
>>>>>> + # Add new entry to rpz-config
>>>>>> + if($enabled eq 'on') {
>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $name, $url, '--no-reload');
>>>>>> +
>>>>>> + # Check for errors, request service reload on success
>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>> + }
>>>>>> +
>>>>>> + # Add to global data hash and save changes
>>>>>> + my %entry = ('enabled' => $enabled,
>>>>>> + 'url' => $url,
>>>>>> + 'remark' => $remark);
>>>>>> +
>>>>>> + $zonefiles{$name} = \%entry;
>>>>>> + &_zonefiles_save_conf();
>>>>>> +
>>>>>> + return 1;
>>>>>> +}
>>>>>> +
>>>>>> +# Save custom lists
>>>>>> +sub _action_cl_save {
>>>>>> + return unless((defined $cgiparams{'ALLOW_LIST'}) && (defined $cgiparams{'BLOCK_LIST'}));
>>>>>> +
>>>>>> + my $result = 0;
>>>>>> +
>>>>>> + my @allowlist = split(/\R/, $cgiparams{'ALLOW_LIST'});
>>>>>> + my @blocklist = split(/\R/, $cgiparams{'BLOCK_LIST'});
>>>>>> +
>>>>>> + # Validate lists
>>>>>> + $result = &_rpz_validate_customlist(\@allowlist);
>>>>>> + if($result != 0) {
>>>>>> + $errormessage = &_rpz_error_tr(202, $result);
>>>>>> + return;
>>>>>> + }
>>>>>> + $result = &_rpz_validate_customlist(\@blocklist);
>>>>>> + if($result != 0) {
>>>>>> + $errormessage = &_rpz_error_tr(203, $result);
>>>>>> + return;
>>>>>> + }
>>>>>> +
>>>>>> + # Add to global data hash and save changes
>>>>>> + $customlists{'allow'}{'list'} = \@allowlist;
>>>>>> + $customlists{'block'}{'list'} = \@blocklist;
>>>>>> + $customlists{'allow'}{'enabled'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? 'on' : 'off';
>>>>>> + $customlists{'block'}{'enabled'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? 'on' : 'off';
>>>>>> +
>>>>>> + &_customlists_save_conf();
>>>>>> + &_customlist_export('allow', $RPZ_ALLOWLIST);
>>>>>> + &_customlist_export('block', $RPZ_BLOCKLIST);
>>>>>> +
>>>>>> + # Make new lists, request service reload on success
>>>>>> + $result = &General::system('/usr/sbin/rpz-make', 'allowblock', '--no-reload');
>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>> +
>>>>>> + return 1;
>>>>>> +}
>>>>>> +
>>>>>> +# Trigger rpz-config reload
>>>>>> +sub _action_rpz_reload {
>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>> +
>>>>>> + # Immediately clear flag to prevent multiple reloads
>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>> + }
>>>>>> +
>>>>>> + # Perform reload, recreate reload flag on error to enable retry
>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'reload');
>>>>>> + if(not &_rpz_check_result($result, 0)) {
>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>> + return;
>>>>>> + }
>>>>>> +
>>>>>> + return 1;
>>>>>> +}
>>>>>> +
>>>>>> +# Trigger unbound restart
>>>>>> +sub _action_unb_restart {
>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>> +
>>>>>> + # Immediately clear flag to prevent multiple restarts
>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>> + }
>>>>>> +
>>>>>> + # Perform restart, unboundctrl always exits zero
>>>>>> + &General::system('/usr/local/bin/unboundctrl', 'restart');
>>>>>> +
>>>>>> + return 1;
>>>>>> +}
>>>>>> +
>>>>>> +
>>>>>> +###--- Internal rpz-config functions ---###
>>>>>> +
>>>>>> +# Translate rpz-config exitcodes and messages
>>>>>> +# 100-199: rpz-config, 200-299: webgui
>>>>>> +sub _rpz_error_tr {
>>>>>> + my ($error, $append) = @_;
>>>>>> + $append //= '';
>>>>>> +
>>>>>> + # Translate numeric exit codes
>>>>>> + if(looks_like_number($error)) {
>>>>>> + if(defined $Lang::tr{"rpz exitcode $error"}) {
>>>>>> + $error = $Lang::tr{"rpz exitcode $error"};
>>>>>> + }
>>>>>> + }
>>>>>> +
>>>>>> + return "RPZ $Lang::tr{'error'}: $error" . &Header::escape($append);
>>>>>> +}
>>>>>> +
>>>>>> +# Check result of rpz-config system call, request reload on success
>>>>>> +sub _rpz_check_result {
>>>>>> + my ($result, $request_reload) = @_;
>>>>>> + $request_reload //= 0;
>>>>>> +
>>>>>> + # exitcode 0 = success
>>>>>> + if($result != 0) {
>>>>>> + $errormessage = &_rpz_error_tr($result);
>>>>>> + return;
>>>>>> + }
>>>>>> +
>>>>>> + # Set reload flag
>>>>>> + if($request_reload) {
>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>> + }
>>>>>> +
>>>>>> + return 1;
>>>>>> +}
>>>>>> +
>>>>>> +# Test whether reload flag is set
>>>>>> +sub _rpz_needs_reload {
>>>>>> + return (-f $RPZ_RELOAD_FLAG);
>>>>>> +}
>>>>>> +
>>>>>> +# Validate a zonefile entry, returns rpz-config exitcode on failure. Use _rpz_check_result to verify.
>>>>>> +# unique = check for unique name
>>>>>> +sub _rpz_validate_zonefile {
>>>>>> + my ($name, $url, $remark, $unique) = @_;
>>>>>> + $unique //= 1;
>>>>>> +
>>>>>> + unless($name =~ /^[a-zA-Z0-9_]{1,32}$/) {
>>>>>> + return 101;
>>>>>> + }
>>>>>> + unless($url =~ /^[\w+\.:;\/\\&@#%?=\-~|!]{1,128}$/) {
>>>>>> + return 105;
>>>>>> + }
>>>>>> + unless($remark =~ /^[\w \-()\.:;*\/\\?!&=]{0,32}$/) {
>>>>>> + return 201;
>>>>>> + }
>>>>>> +
>>>>>> + # Check against already existing names
>>>>>> + if($unique) {
>>>>>> + foreach my $existing (keys %zonefiles) {
>>>>>> + if(lc($name) eq lc($existing)) {
>>>>>> + return 104;
>>>>>> + }
>>>>>> + }
>>>>>> + }
>>>>>> +
>>>>>> + return 0;
>>>>>> +}
>>>>>> +
>>>>>> +# Validate a custom list, returns number of rejected line on failure. Check for non-zero results.
>>>>>> +# listref = array reference, cleanup = remove invalid entries instead of returning an error
>>>>>> +sub _rpz_validate_customlist {
>>>>>> + my ($listref, $cleanup) = @_;
>>>>>> + $cleanup //= 0;
>>>>>> +
>>>>>> + foreach my $index (reverse 0..$#{$listref}) {
>>>>>> + my $row = @$listref[$index];
>>>>>> + next unless($row); # Skip/allow empty lines
>>>>>> +
>>>>>> + # Reject/remove everything besides wildcard domains and remarks
>>>>>> + if((not &General::validwildcarddomainname($row)) && (not $row =~ /^;[\w \-()\.:;*\/\\?!&=]*$/)) {
>>>>>> + unless($cleanup) {
>>>>>> + # +1 for user friendly line number and to ensure non-zero exitcode
>>>>>> + return $index + 1;
>>>>>> + }
>>>>>> +
>>>>>> + # Remove current row
>>>>>> + splice(@$listref, $index, 1);
>>>>>> + }
>>>>>> + }
>>>>>> +
>>>>>> + return 0;
>>>>>> +}
>>>>>> +
>>>>>> +
>>>>>> +###--- Internal misc functions ---###
>>>>>> +
>>>>>> +# Send HTTP 303 redirect headers for post/request/get pattern
>>>>>> +# (Must be sent before calling &Header::showhttpheaders())
>>>>>> +sub _http_prg_redirect {
>>>>>> + my $location = "https://$ENV{'SERVER_NAME'}:$ENV{'SERVER_PORT'}$ENV{'SCRIPT_NAME'}";
>>>>>> + print "Status: 303 See Other\n";
>>>>>> + print "Location: $location\n";
>>>>>> +}
>>>>>> diff --git a/lfs/rpz b/lfs/rpz
>>>>>> new file mode 100644
>>>>>> index 000000000..7ddbc38e5
>>>>>> --- /dev/null
>>>>>> +++ b/lfs/rpz
>>>>>> @@ -0,0 +1,96 @@
>>>>>> +###############################################################################
>>>>>> +# #
>>>>>> +# IPFire.org - A linux based firewall #
>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>> +# #
>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>> +# #
>>>>>> +###############################################################################
>>>>>> +
>>>>>> +###############################################################################
>>>>>> +# Definitions
>>>>>> +###############################################################################
>>>>>> +
>>>>>> +include Config
>>>>>> +
>>>>>> +SUMMARY = response policy zone - RPZ reputation system for unbound DNS
>>>>>> +
>>>>>> +VER = 1.0.0
>>>>>> +
>>>>>> +THISAPP = rpz-$(VER)
>>>>>> +DIR_APP = $(DIR_SRC)/$(THISAPP)
>>>>>> +TARGET = $(DIR_INFO)/$(THISAPP)
>>>>>> +
>>>>>> +PROG = rpz
>>>>>> +PAK_VER = 18
>>>>>> +
>>>>>> +DEPS =
>>>>>> +
>>>>>> +SERVICES =
>>>>>> +
>>>>>> +###############################################################################
>>>>>> +# Top-level Rules
>>>>>> +###############################################################################
>>>>>> +
>>>>>> +install : $(TARGET)
>>>>>> +
>>>>>> +check :
>>>>>> +
>>>>>> +download :
>>>>>> +
>>>>>> +b2 :
>>>>>> +
>>>>>> +dist:
>>>>>> + @$(PAK)
>>>>>> +
>>>>>> +###############################################################################
>>>>>> +# Installation Details
>>>>>> +###############################################################################
>>>>>> +
>>>>>> +$(TARGET) :
>>>>>> + @$(PREBUILD)
>>>>>> + @rm -rf $(DIR_APP)
>>>>>> +
>>>>>> + # RPZ scripts
>>>>>> + install --verbose --mode=755 \
>>>>>> + $(DIR_CONF)/rpz/{rpz-config,rpz-metrics,rpz-sleep,rpz-make,rpz-functions} \
>>>>>> + --target-directory=/usr/sbin
>>>>>> +
>>>>>> + # RPZ config files
>>>>>> + mkdir -pv /etc/unbound/local.d
>>>>>> + install --verbose --mode=644 --owner=nobody --group=nobody \
>>>>>> + $(DIR_CONF)/rpz/00-rpz.conf \
>>>>>> + --target-directory=/etc/unbound/local.d
>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/local.d
>>>>>> +
>>>>>> + # RPZ custom list files for allow and block
>>>>>> + mkdir -pv /var/ipfire/dns/rpz
>>>>>> + touch /var/ipfire/dns/rpz/{allowlist,blocklist}
>>>>>> + chown --verbose --recursive nobody:nobody /var/ipfire/dns/rpz
>>>>>> +
>>>>>> + # RPZ zone files
>>>>>> + # create empty RPZ config file to avoid a unbound config error
>>>>>> + mkdir -pv /etc/unbound/zonefiles
>>>>>> + touch /etc/unbound/zonefiles/allow.rpz
>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/zonefiles
>>>>>> +
>>>>>> + # Install addon-specific language-files
>>>>>> + install --verbose --mode=004 $(DIR_CONF)/rpz/rpz.*.pl \
>>>>>> + --target-directory=/var/ipfire/addon-lang
>>>>>> +
>>>>>> + # Install backup definition
>>>>>> + cp -vf $(DIR_CONF)/backup/includes/rpz /var/ipfire/backup/addons/includes/rpz
>>>>>> +
>>>>>> + @rm -rf $(DIR_APP)
>>>>>> + @$(POSTBUILD)
>>>>>> diff --git a/make.sh b/make.sh
>>>>>> index 827ea9e77..a77535b13 100755
>>>>>> --- a/make.sh
>>>>>> +++ b/make.sh
>>>>>> @@ -390,7 +390,7 @@ prepareenv() {
>>>>>> if [ "${free_space}" -lt "${required_space}" ]; then
>>>>>> # Add any consumed space
>>>>>> while read -r consumed_space path; do
>>>>>> - (( free_space += consumed_space / 1024 / 1024 ))
>>>>>> + (( free_space += consumed_space / 1024 / 1024 ))
>>>>>> done <<< "$(du --summarize --bytes "${BUILD_DIR}" "${IMAGES_DIR}" "${LOG_DIR}" 2>/dev/null)"
>>>>>> fi
>>>>>>
>>>>>> @@ -2087,6 +2087,7 @@ build_system() {
>>>>>> lfsmake2 btrfs-progs
>>>>>> lfsmake2 inotify-tools
>>>>>> lfsmake2 grub-btrfs
>>>>>> + lfsmake2 rpz
>>>>>>
>>>>>> lfsmake2 linux
>>>>>> lfsmake2 rtl8812au
>>>>>> diff --git a/src/paks/rpz/install.sh b/src/paks/rpz/install.sh
>>>>>> new file mode 100644
>>>>>> index 000000000..ef99bf742
>>>>>> --- /dev/null
>>>>>> +++ b/src/paks/rpz/install.sh
>>>>>> @@ -0,0 +1,36 @@
>>>>>> +#!/bin/bash
>>>>>> +###############################################################################
>>>>>> +# #
>>>>>> +# IPFire.org - A linux based firewall #
>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>> +# #
>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>> +# #
>>>>>> +###############################################################################
>>>>>> +#
>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>> +extract_files
>>>>>> +restore_backup ${NAME}
>>>>>> +
>>>>>> +# fix user created files
>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>> + /var/ipfire/dns/rpz \
>>>>>> + /etc/unbound/zonefiles \
>>>>>> + /etc/unbound/local.d
>>>>>> +
>>>>>> +# Update Language cache
>>>>>> +/usr/local/bin/update-lang-cache
>>>>>> +
>>>>>> +# restart unbound to load config file
>>>>>> +/etc/init.d/unbound restart
>>>>>> diff --git a/src/paks/rpz/uninstall.sh b/src/paks/rpz/uninstall.sh
>>>>>> new file mode 100644
>>>>>> index 000000000..e11427df3
>>>>>> --- /dev/null
>>>>>> +++ b/src/paks/rpz/uninstall.sh
>>>>>> @@ -0,0 +1,38 @@
>>>>>> +#!/bin/bash
>>>>>> +###############################################################################
>>>>>> +# #
>>>>>> +# IPFire.org - A linux based firewall #
>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>> +# #
>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>> +# #
>>>>>> +###############################################################################
>>>>>> +#
>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>> +
>>>>>> +# stop unbound to delete RPZ conf file
>>>>>> +/etc/init.d/unbound stop
>>>>>> +
>>>>>> +make_backup ${NAME}
>>>>>> +remove_files
>>>>>> +
>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>> +# "[1723428668] unbound-control[17117:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>> +
>>>>>> +# Update Language cache
>>>>>> +/usr/local/bin/update-lang-cache
>>>>>> +
>>>>>> +# start unbound to load unbound config file
>>>>>> +/etc/init.d/unbound start
>>>>>> diff --git a/src/paks/rpz/update.sh b/src/paks/rpz/update.sh
>>>>>> new file mode 100644
>>>>>> index 000000000..9bc340bc6
>>>>>> --- /dev/null
>>>>>> +++ b/src/paks/rpz/update.sh
>>>>>> @@ -0,0 +1,52 @@
>>>>>> +#!/bin/bash
>>>>>> +###############################################################################
>>>>>> +# #
>>>>>> +# IPFire.org - A linux based firewall #
>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>> +# #
>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>> +# #
>>>>>> +###############################################################################
>>>>>> +#
>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>> +
>>>>>> +# from update.sh
>>>>>> +extract_backup_includes
>>>>>> +
>>>>>> +# stop unbound to delete RPZ conf file
>>>>>> +/etc/init.d/unbound stop
>>>>>> +
>>>>>> +# from uninstall.sh
>>>>>> +make_backup ${NAME}
>>>>>> +remove_files
>>>>>> +
>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>> +# "unbound-control[nn:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>> +
>>>>>> +# from install.sh
>>>>>> +extract_files
>>>>>> +restore_backup ${NAME}
>>>>>> +
>>>>>> +# fix user created files
>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>> + /var/ipfire/dns/rpz \
>>>>>> + /etc/unbound/zonefiles \
>>>>>> + /etc/unbound/local.d
>>>>>> +
>>>>>> +# Update Language cache
>>>>>> +/usr/local/bin/update-lang-cache
>>>>>> +
>>>>>> +# restart unbound to load config files
>>>>>> +/etc/init.d/unbound start
>>>>>> --
>>>>>> 2.39.5
>>>>>>
>>>>>
>>>>
>>>> Jon
>>>>
>>>>
>>>> --
>>>> Jon Murphy
>>>> jon.murphy@ipfire.org
>>
>> Jon
>>
>>
>> --
>> Jon Murphy
>> jon.murphy@ipfire.org
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
2025-03-17 10:35 ` Michael Tremer
@ 2025-03-19 2:58 ` Jon Murphy
2025-03-19 10:35 ` Michael Tremer
0 siblings, 1 reply; 30+ messages in thread
From: Jon Murphy @ 2025-03-19 2:58 UTC (permalink / raw)
To: development
Michael,
> The emphasis is on the repeated downloads of the same list. That is
> what cannot happen.
The Unbound RPZ code, as installed within IPFire, watches for a change
in the SOA line of each RPZ file. This is an example of the first few
lines for every RPZ file.
$TTL 300
@ SOA localhost. root.localhost. 1742298960 43200 3600 86400 300
NS localhost.
;
; Title: HaGeZi's Pop-Up Ads DNS Blocklist
; Description: Blocks annoying and malicious pop-up ads.
If the SOA serial number changes (e.g. the 1742298960), then Unbound RPZ
code does its thing and downloads. Otherwise there is no download.
> So there has to be a way to ensure that we won’t download a list again
> unless it has actually changed.
This should do what you want but I may be missing your point.
> DNS has a builtin functionality called AXFR. It simply does the job
> for you. I was just wondering whether that was not being used.
I need to read about AXFR/IXFR and learn a little more.
Jon
On 3/17/25 5:35 AM, Michael Tremer wrote:
> Good Morning Jon,
>
>> On 16 Mar 2025, at 17:00, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>
>> Michael,
>>
>> I was reading through you response again an I want to understand this post:
>>
>>> I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>> So if RPZ doesn't use HTTPS, what is it using? I am missing a key point here.
> The emphasis is on the repeated downloads of the same list. That is what cannot happen.
>
> Although it might not affect a lot of people in our general user-base, there are some that have a metered connection and will pay for data by volume. Some of the lists I looked at are just under 20 MiB. Therefore we need to keep any traffic down to a minimum. The second reason is that we have a lot of firewalls out there. Not all of them will enable this feature and all of the lists, but even if it is a good chunk, we will generate terabytes of traffic which put load on the infrastructure and will cost money. It simply is not what we want to do, regardless of self-hosting those lists and pulling them from somewhere else.
>
> So there has to be a way to ensure that we won’t download a list again unless it has actually changed.
>
> DNS has a builtin functionality called AXFR. It simply does the job for you. I was just wondering whether that was not being used.
>
> HTTPS is an option because that is simply what we use elsewhere, but extra functionality will have to be built for it.
>
> -Michael
>
>> Jon
>>
>>
>> On 2/13/25 3:34 PM, jon wrote:
>>> Michael,
>>>
>>> I’ve read through your comments a few times and I ended up with many more questions.
>>>
>>>
>>>> What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>> To me the efforts to get new code accepted seem to have changed and it seemed easier in the past. In the past I made the Core Team aware via the Dev Mailing List and wrote a simple two or three paragraphs of "What is it? / What is the value? / Here is the code"
>>>
>>>
>>> So in an effort to move forward: How exactly is something presented to the Core Team?
>>>
>>> Is there an example of a recent effort that was presented that I can see as a sample? (This type of info can also be added to the Wiki)
>>>
>>> I understand you want it this way, but I don’t know what exactly is needed. Please be specific.
>>>
>>>
>>> Jon
>>>
>>> PS - I am not ignoring your other comments, I am just trying to move forward and keep things simple.
>>>
>>>
>>>
>>>> On Feb 8, 2025, at 1:27 PM, Michael Tremer <michael.tremer@ipfire.org> wrote:
>>>>
>>>> Hello Jon,
>>>>
>>>> Thanks for your reply. And good that you are copying everyone into this conversation.
>>>>
>>>>> On 8 Feb 2025, at 18:41, jon <jon.murphy@ipfire.org> wrote:
>>>>>
>>>>> Michael,
>>>>>
>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort
>>>>> Yes, this has been a separate effort (a very public separate effort). Yes, as you pointed this out early on with the "proof-of-concept" and then my request for people to help test RPZ. Nothing was hidden.
>>>>>
>>>>> This was done because you (and maybe others) did not have the time and I wanted to help and because I needed assistance with RPZ. I tried my best to do this without bothering you.
>>>> I don’t that it is accurate that nobody wanted to help on this. The list was always open - although not every email has been replied to swiftly it is also your responsibility to raise a question again if it was missed. People here have open ears.
>>>>
>>>> It was also stated on this very list on in our documentation that working on something without involving the core team is a risky undertaking. Of course IPFire is free software and so everyone is free to fork if they wish to do so.
>>>>
>>>>>> and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls.
>>>>> You were aware many steps along the way. See your email on July 28, 2024, August 15, 2024, September 30, 2024, December 23, 2024, and January 16. My attempts to get the team involved were met with "things are busy" and sometimes silence. (Yes, I get it, people are busy.)
>>>>>
>>>>> You and Adolf, Leo, Erik and Bernhard have been aware since the beginning. You mention you were aware of the "proof-of-concept". If you include those beginning posts, since Sep 2023.
>>>> Yes, I am aware of a proof-of-concept that I have been running myself for a long time. I am also aware of the efforts that you have been taking.
>>>>
>>>> Yet I don’t think there has ever been any joint effort, or am I seeing that wrong?
>>>>
>>>>>> This has not been discussed . . . on our calls.
>>>>> On the July 28th you stated:
>>>>> "We have talked about RPZ many times on the monthly call since the URL filter feature is falling more and more out of fashion. I think there is also many posts about this on the forum."
>>>>>
>>>>> Please don’t insult me again by stating "you know what I mean".
>>>>>
>>>>> And it has been discussed but not documented in the Monthly Meeting notes.
>>>> I am not at all insulting you. I don’t want to take this down to a personal level at all. This is a public mailing list and people who read this don’t need to listen to an argument we are having. They are here for the tech inside IPFire.
>>>>
>>>> When I wrote that it has not been discussed that does not mean that we have not been touching on the topic. We have been talking about lots of things on the calls, the weather, politics, how our pets are. None of that makes it to the logs. What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>>
>>>>>> Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation.
>>>>> Regular conversation on the Dev Mailing list is many times met with silence. I get it, people are busy.
>>>>>
>>>>> And regular two-way conversation doesn’t happen on the list. At least not with me. I’d be happy to point out the posts that were met with silence.
>>>>> Again, I get it, people are busy.
>>>> And you think my emails are not being met with silence? This has nothing to do with this specific topic. This has something to do with how occupied people are and how engaged they are on certain topics. Not everyone is involved in all the things and simply will ignore emails simply based on their subject line.
>>>>
>>>>> But the "dip here to the list" were my attempts to get a conversation started. As I said, many time met with silence.
>>>>>
>>>>> The only place I was not met with silence was on the Community. You have a great group of people in the Community. It is a shame you don’t want to have others help. It would reduce your workload.
>>>> You should stop making statements that are not true. Who doesn’t want anyone to help?
>>>>
>>>> Not having this conversation on a Saturday evening would reduce my workload. At least it would free up time for something else. Helping with the things that are already on the go would reduce the workload of the entire team. Starting one thing at a time and finishing it is a lot better to manage than starting a hundred things and not even finish one. I can tell you that I already have a hundred things on the go.
>>>>
>>>>>> Therefore, what am I supposed to do with this email?
>>>>> To me it is beyond obvious…
>>>>>
>>>>> If it isn’t what you want, then guide me with how to do this the correct way. And be specific. I am trying to help. I am trying to make things better. I am trying to do things the right way.
>>>> To me it isn’t. This is yet another project that has been dumped to the list like so many before and later on everyone has left to have the team deal with the rest.
>>>>
>>>> It is a huge patch set. You explained what the vision is, but that is about it. There is no chance this will continue if this disagreement isn’t solved first. I didn’t even look at the code.
>>>>
>>>>>> I don’t want to merge code that I don’t agree with.
>>>>> I asked multiple times if you "agreed with the concept" and again, met with silence. Yes I get it, people are busy.
>>>> Having support for RPZ? Yes, it was definitely on the roadmap. That I agree with.
>>>>
>>>>>> So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>> You mentioned this a in the past, but for some reason you do not disclose what I dismissed. Why do you continue to make this harder, wouldn’t it not be easier to tell me what I have dismissed?
>>>>>
>>>>> I have sent multiple emails trying to answer your concerns and comments. On July 28, Aug 14, Aug 22, Aug 23, Sep 30, etc.
>>>>>
>>>>> I’ve gone through all of the questions you asked and I cannot find a "dismissed" item.
>>>> Maybe I need to be *more clear*. I feel humoured by this.
>>>>
>>>> It is late on a Saturday and I want my dinner soon, but certainly I have stated that this should never be an add-on considering it is supposed to replace URL Filter. We should never allow people to add their own sources. I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>>>
>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>> The maintainers of Unbound and/or RPZ?
>>>>>
>>>>> The maintainers of Hagezi list, the threatfox list, the urlhaus list, etc.?
>>>>>
>>>>> What else? The maintainers or the RPZ scripts? That is me. Let’s talk!
>>>> You. I don’t care much about the providers of the lists.
>>>>
>>>>> See, this is where it gets confusing. There are hundreds of open source packages as part of IPFire. Pick the last five years of items added to the IPFire build. You're telling me you have "constructive conversation with the maintainers" of all of the added packages?
>>>> They publish their software and they don’t care whether I am pulling it or not. They publish it with the commitment to maintain it - sometimes for better and sometimes for worse.
>>>>
>>>> You care about me pulling your code and I don’t know whether you would commit to maintain this.
>>>>
>>>> These two are very different cases.
>>>>
>>>>> Pick the IP Blocklists list (i.e., 3CORESEC, ABUSECH, DSHIELD, SPAMHAUS, etc.) or the Suricata lists (i.e.,Emergingthreats.net <http://emergingthreats.net/>,Abuse.ch <http://abuse.ch/>, etc.). So you’ve have "constructive conversation with the maintainers"?
>>>> Yes, occasionally I have phone calls with a few of these providers.
>>>>
>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>> Ha! Yes a surprise. In the beginning you seemed interested as IPFire needed a replacement for URL Filter. You asked good questions about the lists picked, asked for the value to the users, etc. And I answered the best I could.
>>>>>
>>>>> You even asked: “Why is this realised as an add-on and not part of the core system?” from your Jul 28, 2024 email.
>>>> Ah, so, why is the patch creating an add-on? Not that I am saying that what I say is law, but it has not been challenged either. If my input is being ignored, why should I put this to the top of my list of priorities? I am not disappointed about this, just trying to be very good with my time.
>>>>
>>>>> And on January 16, 2025 I wrote a message looking for help. And you were kind to respond quickly. So in three weeks time, since the kind response, something has changed. You went from supportive to "this".
>>>>>
>>>>> So yes, I am surprised.
>>>> Well, maybe I should not have replied to that email. It was clear that you were on some path that was not right, but you were not interested before in finding the right path from the beginning.
>>>>
>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>> Be more specific, what has to change? What exactly did I dismiss?
>>>> Dismissal is just my assumption. I don’t know what you actually did with my feedback. I can only see the end product that does not seem contain much of it. Repeatedly I have been pointing out that we should think before we build. I am sure a lot of hours have now gone into some code that simply does not satisfy me. And I am not not talking about the code itself, what it does is what I don’t think is right for us.
>>>>
>>>> The process is very clear for me that we should first of all think whether we want a certain feature now. Then there should be a clear roadmap for everyone to follow; tasks can be split-up as we go and hopefully then have something that is maintainable, interesting for our users and even would do us proud. This is how this should work.
>>>>
>>>> So, what has to change? I don’t think with shouting at each other, throwing patches around and making me generally unhappy is a good start.
>>>>
>>>> -Michael
>>>>
>>>>> Jon
>>>>>
>>>>>
>>>>>
>>>>>> On Feb 6, 2025, at 2:13 PM, Michael Tremer <michael.tremer@ipfire.org> wrote:
>>>>>>
>>>>>> Hello Jon,
>>>>>>
>>>>>> Well, here we are again with another patch regarding this feature.
>>>>>>
>>>>>> I cannot quite see from your email what the question is, but if this is a request to have this merged into IPFire, I am once again sorry to disappoint you.
>>>>>>
>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls. Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation. Therefore, what am I supposed to do with this email?
>>>>>>
>>>>>> I don’t want to merge code that I don’t agree with. So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>>
>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>>
>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>>
>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>>
>>>>>> All the best,
>>>>>> -Michael
>>>>>>
>>>>>>> On 6 Feb 2025, at 16:35, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>
>>>>>>> What is it?
>>>>>>> Response Policy Zone (RPZ) is a mechanism to define local policies in a
>>>>>>> standardized way and load those policies from external sources.
>>>>>>> Bottom line: RPZ allows admins to easily block access to websites via DNS lookup.
>>>>>>>
>>>>>>> RPZ can block websites via categories. Examples include: fake websites, annoying
>>>>>>> pop-up ads, newly registered domains, DoH bypass sites, bad "host" services,
>>>>>>> maliscious top level domains (e.g., *.zip, *.mov), piracy, gambling, pornography,
>>>>>>> and more. RPZ lists come from various RPZ providers and their available
>>>>>>> catagories.
>>>>>>>
>>>>>>> This RPZ add-on enables the RPZ functionality by adding a couple lines in a
>>>>>>> configuration file. This add-on simply adds configuration files and adds
>>>>>>> scripts (config, metrics and sleep) to make RPZ easier for the admin to use.
>>>>>>>
>>>>>>> The RPZ scripts include additional languages: German, Spanish, French, Turkish,
>>>>>>> and Italian.
>>>>>>>
>>>>>>> RPZ itself was release in 2010 and has been part of the IPFire build since ~2015.
>>>>>>>
>>>>>>> Why is it needed? What is its value?
>>>>>>>
>>>>>>> - The RPZ concept places this filtering into IPFire, our internet access
>>>>>>> gateway, which is (should be) solely used as DNS source of the internal network.
>>>>>>>
>>>>>>> - As most sites use HTTPS it makes it difficult to filter traffic with URL
>>>>>>> Filter without also properly configuring conventional (non-transparent)
>>>>>>> mode on the proxy. RPZ is a nice replacement for the URL Filter.
>>>>>>>
>>>>>>> - No need to install and maintain an additional device like PiHole or AdBlock
>>>>>>> browser extensions on multiple user devices.
>>>>>>>
>>>>>>> - This is an additional layer of protection for users. Less worry someone will
>>>>>>> click on something that gets them into trouble. And, saying this with emphasis,
>>>>>>> the ability to do it in one place!
>>>>>>>
>>>>>>> - Blocked sites save on unneeded traffic and can lessen the threat of malware
>>>>>>> in advertisements
>>>>>>>
>>>>>>> - Logging allows the admin to see the site blocked and take actions
>>>>>>>
>>>>>>> - RPZ will be used at the home, home-office (work from home), schools,
>>>>>>> ministerial, and at the office. Device counts are small (2-6) to medium (~80)
>>>>>>> to mediam-large (200+).
>>>>>>>
>>>>>>> - RPZ can block ads, popups, phishing, scammers, spyware, malware, annoying
>>>>>>> popups, NSFW links, DOH servers, and the usual internet trash.
>>>>>>>
>>>>>>> ------------------------------
>>>>>>>
>>>>>>> Change Log for RPZ add-on
>>>>>>>
>>>>>>> rpz-1.0.0-18 on 2025-02-05
>>>>>>> - Build for approval & release as IPFire add-on
>>>>>>>
>>>>>>> ---
>>>>>>>
>>>>>>> rpz-beta-0.1.18-18.ipfire on 2025-02-01
>>>>>>> rpz.cgi:
>>>>>>> - new feature: added a mod key to force a unbound restart
>>>>>>>
>>>>>>> rpz-config and rpz-make:
>>>>>>> - new feature: added action for unbound restart `rpz-config unbound-restart`
>>>>>>>
>>>>>>> rpz-metrics:
>>>>>>> - simple reformatting
>>>>>>> - rename far right column from "last update" to "last download"
>>>>>>>
>>>>>>> ---
>>>>>>>
>>>>>>> rpz-beta-0.1.17-17.ipfire on 2024-12-09
>>>>>>> rpz-make
>>>>>>> - bug fix: corrected validation regex for wildcards like: `*.domain.com`
>>>>>>>
>>>>>>> ---
>>>>>>>
>>>>>>> rpz-beta-0.1.16-16.ipfire on 2024-11-18
>>>>>>> rpz-make
>>>>>>> - new feature: updated validation regex
>>>>>>> - bug fix: moved validation to beginning of process. Now we validate before
>>>>>>> creating config files.
>>>>>>>
>>>>>>> rpz.cgi:
>>>>>>> - new feature: use CSS color variables of the main ipfire theme
>>>>>>> - bug fix: empty zonefile remarks were stored as “undef” and caused a warning
>>>>>>> - bug fix: HTML textarea removes the first empty line in a custom list
>>>>>>> - thank you Leo!
>>>>>>>
>>>>>>> ---
>>>>>>>
>>>>>>> rpz-beta-0.1.15-15.ipfire on 2024-11-04
>>>>>>> rpz.cgi:
>>>>>>> - new feature: added new language file for Turkish (thank you Peppe)
>>>>>>>
>>>>>>> rpz-make
>>>>>>> - bug fix: corrected empty allow/block list issue. An empty allow/block list
>>>>>>> will now remove contents of allow/block.rpz files and remove unneeded
>>>>>>> allow/block.conf file. (thank you iptom)
>>>>>>>
>>>>>>> ---
>>>>>>>
>>>>>>> rpz-beta-0.1.14-14.ipfire on 2024-10-29
>>>>>>> rpz-config:
>>>>>>> - bug fix: correct missing rpz extension. `rpz-config list` displayed URL
>>>>>>> incorrectly (thank you Bernhard)
>>>>>>>
>>>>>>> rpz.cgi:
>>>>>>> - bug fix: remove extra `"` in language files (thank you Bernhard)
>>>>>>> - new feature: slightly dim "apply" button when not enabled
>>>>>>>
>>>>>>> ---
>>>>>>>
>>>>>>> rpz-beta-0.1.13-13.ipfire on 2024-10-27
>>>>>>> - skipped
>>>>>>>
>>>>>>> ---
>>>>>>>
>>>>>>> rpz-beta-0.1.12-12.ipfire on 2024-10-21
>>>>>>> rpz.cgi:
>>>>>>> - new feature: added new language file for French (thank you gw-ipfire)
>>>>>>>
>>>>>>> ---
>>>>>>>
>>>>>>> rpz-beta-0.1.11-11.ipfire on 2024-10-18
>>>>>>> rpz.cgi:
>>>>>>> - new feature: added new language file for Italian (thank you umberto)
>>>>>>> - new feature: added new language file for Spanish (thank you Roberto)
>>>>>>>
>>>>>>> ---
>>>>>>>
>>>>>>> rpz-beta-0.1.10-10.ipfire on 2024-10-15
>>>>>>> rpz-make:
>>>>>>> - bug fix: corrected validation error for a custom list entry (thank you siosios)
>>>>>>> - e.g., `*.cloudflare-dns.com`
>>>>>>>
>>>>>>> install.sh:
>>>>>>> - bug fix: add chown to correct user created files
>>>>>>>
>>>>>>> update.sh:
>>>>>>> - bug fix: add chown to correct user created files (thank you siosios)
>>>>>>>
>>>>>>> ---
>>>>>>>
>>>>>>> rpz-beta-0.1.9-9.ipfire on 2024-10-08
>>>>>>> rpz.cgi:
>>>>>>> - new feature: added new language file for German (thank you Leo)
>>>>>>> - bug fix: add missing "rpz exitcode 110"
>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>
>>>>>>> ---
>>>>>>>
>>>>>>> rpz-beta-0.1.8-8.ipfire on 2024-10-04
>>>>>>> - skipped
>>>>>>>
>>>>>>> ---
>>>>>>>
>>>>>>> rpz-beta-0.1.7-7.ipfire on 2024-10-03
>>>>>>> All:
>>>>>>> - new feature: includes beta version numbers for pakfire package,
>>>>>>> instead of only `rpz-1.0.0-1.ipfire`, for each release.
>>>>>>>
>>>>>>> rpz.cgi:
>>>>>>> - new feature: added new WebGUI at `rpz.cgi`
>>>>>>> - a BIG thank you to Leo Hofmann for all of his work creating the webgui!!
>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>
>>>>>>> rpz-make:
>>>>>>> - new feature: validate entries in allowlist and blocklist
>>>>>>> - new feature: add "no-reload" option for WebGUI
>>>>>>>
>>>>>>> rpz-metrics:
>>>>>>> - new feature: info can be sorted by name, by hit count, by line count, by
>>>>>>> "enabled" list or all lists
>>>>>>>
>>>>>>> backups:
>>>>>>> - bug fix: include all files in `/var/ipfire/dns/rpz` directory in backup
>>>>>>>
>>>>>>> update.sh:
>>>>>>> - bug fix: corrected ownership for `/var/ipfire/dns/rpz` directory during an
>>>>>>> update
>>>>>>>
>>>>>>> Build:
>>>>>>> - bug fix: `block.rpz.conf` and `block.rpz` from build. Files to be created
>>>>>>> by `rpz-make`
>>>>>>>
>>>>>>> WebGUI and German language file
>>>>>>> Contribution-by: Leo-Andres Hofmann <hofmann@leo-andres.de>
>>>>>>>
>>>>>>> Spanish language file
>>>>>>> Contribution-by: Roberto Peña
>>>>>>>
>>>>>>> Italian language file
>>>>>>> Contribution-by: Umberto Parma
>>>>>>>
>>>>>>> French language file
>>>>>>> Contribution-by: gw-ipfire
>>>>>>>
>>>>>>> Turkish language file
>>>>>>> Contribution-by: Peppe Tech
>>>>>>>
>>>>>>> Contribution-by: Bernhard Bitsch <bbitsch@ipfire.org>
>>>>>>> Contribution-by: Erik Kapfer <erik.kapfer@ipfire.org>
>>>>>>> Signed-off-by: Jon Murphy <jon.murphy@ipfire.org
>>>>>>> ---
>>>>>>> config/backup/includes/rpz | 4 +
>>>>>>> config/cfgroot/manualpages | 1 +
>>>>>>> config/menu/EX-rpz.menu | 6 +
>>>>>>> config/rootfiles/common/configroot | 1 +
>>>>>>> config/rootfiles/common/web-user-interface | 1 +
>>>>>>> config/rootfiles/packages/rpz | 20 +
>>>>>>> config/rpz/00-rpz.conf | 10 +
>>>>>>> config/rpz/rpz-config | 130 +++
>>>>>>> config/rpz/rpz-functions | 85 ++
>>>>>>> config/rpz/rpz-make | 203 +++++
>>>>>>> config/rpz/rpz-metrics | 170 ++++
>>>>>>> config/rpz/rpz-sleep | 58 ++
>>>>>>> config/rpz/rpz.de.pl | 30 +
>>>>>>> config/rpz/rpz.en.pl | 30 +
>>>>>>> config/rpz/rpz.es.pl | 30 +
>>>>>>> config/rpz/rpz.fr.pl | 30 +
>>>>>>> config/rpz/rpz.it.pl | 30 +
>>>>>>> config/rpz/rpz.tr.pl | 30 +
>>>>>>> html/cgi-bin/rpz.cgi | 923 +++++++++++++++++++++
>>>>>>> lfs/rpz | 96 +++
>>>>>>> make.sh | 3 +-
>>>>>>> src/paks/rpz/install.sh | 36 +
>>>>>>> src/paks/rpz/uninstall.sh | 38 +
>>>>>>> src/paks/rpz/update.sh | 52 ++
>>>>>>> 24 files changed, 2016 insertions(+), 1 deletion(-)
>>>>>>> create mode 100644 config/backup/includes/rpz
>>>>>>> create mode 100644 config/menu/EX-rpz.menu
>>>>>>> create mode 100644 config/rootfiles/packages/rpz
>>>>>>> create mode 100644 config/rpz/00-rpz.conf
>>>>>>> create mode 100644 config/rpz/rpz-config
>>>>>>> create mode 100644 config/rpz/rpz-functions
>>>>>>> create mode 100644 config/rpz/rpz-make
>>>>>>> create mode 100755 config/rpz/rpz-metrics
>>>>>>> create mode 100755 config/rpz/rpz-sleep
>>>>>>> create mode 100644 config/rpz/rpz.de.pl
>>>>>>> create mode 100644 config/rpz/rpz.en.pl
>>>>>>> create mode 100644 config/rpz/rpz.es.pl
>>>>>>> create mode 100644 config/rpz/rpz.fr.pl
>>>>>>> create mode 100644 config/rpz/rpz.it.pl
>>>>>>> create mode 100644 config/rpz/rpz.tr.pl
>>>>>>> create mode 100644 html/cgi-bin/rpz.cgi
>>>>>>> create mode 100644 lfs/rpz
>>>>>>> create mode 100644 src/paks/rpz/install.sh
>>>>>>> create mode 100644 src/paks/rpz/uninstall.sh
>>>>>>> create mode 100644 src/paks/rpz/update.sh
>>>>>>>
>>>>>>> diff --git a/config/backup/includes/rpz b/config/backup/includes/rpz
>>>>>>> new file mode 100644
>>>>>>> index 000000000..36513e494
>>>>>>> --- /dev/null
>>>>>>> +++ b/config/backup/includes/rpz
>>>>>>> @@ -0,0 +1,4 @@
>>>>>>> +/var/ipfire/dns/rpz/*
>>>>>>> +/etc/unbound/zonefiles/allow.rpz
>>>>>>> +/etc/unbound/zonefiles/block.rpz
>>>>>>> +/etc/unbound/local.d/*rpz.conf
>>>>>>> diff --git a/config/cfgroot/manualpages b/config/cfgroot/manualpages
>>>>>>> index 1f7e01efc..d3a48c633 100644
>>>>>>> --- a/config/cfgroot/manualpages
>>>>>>> +++ b/config/cfgroot/manualpages
>>>>>>> @@ -70,6 +70,7 @@ pakfire.cgi=configuration/ipfire/pakfire
>>>>>>> wlanap.cgi=addons/wireless
>>>>>>> tor.cgi=addons/tor
>>>>>>> samba.cgi=addons/samba
>>>>>>> +rpz.cgi=addons/rpz
>>>>>>>
>>>>>>> # Logs menu
>>>>>>> logs.cgi/summary.dat=configuration/logs/summary
>>>>>>> diff --git a/config/menu/EX-rpz.menu b/config/menu/EX-rpz.menu
>>>>>>> new file mode 100644
>>>>>>> index 000000000..2f4daf410
>>>>>>> --- /dev/null
>>>>>>> +++ b/config/menu/EX-rpz.menu
>>>>>>> @@ -0,0 +1,6 @@
>>>>>>> +$subipfire->{'20.rpz'} = {
>>>>>>> + 'caption' => $Lang::tr{'rpz'},
>>>>>>> + 'uri' => '/cgi-bin/rpz.cgi',
>>>>>>> + 'title' => "RPZ",
>>>>>>> + 'enabled' => 1,
>>>>>>> +};
>>>>>>> diff --git a/config/rootfiles/common/configroot b/config/rootfiles/common/configroot
>>>>>>> index 9839eee45..b30d6aae4 100644
>>>>>>> --- a/config/rootfiles/common/configroot
>>>>>>> +++ b/config/rootfiles/common/configroot
>>>>>>> @@ -120,6 +120,7 @@ var/ipfire/menu.d/70-log.menu
>>>>>>> #var/ipfire/menu.d/EX-apcupsd.menu
>>>>>>> #var/ipfire/menu.d/EX-guardian.menu
>>>>>>> #var/ipfire/menu.d/EX-mympd.menu
>>>>>>> +#var/ipfire/menu.d/EX-rpz.menu
>>>>>>> #var/ipfire/menu.d/EX-samba.menu
>>>>>>> #var/ipfire/menu.d/EX-tor.menu
>>>>>>> #var/ipfire/menu.d/EX-transmission.menu
>>>>>>> diff --git a/config/rootfiles/common/web-user-interface b/config/rootfiles/common/web-user-interface
>>>>>>> index 816241dae..e00464076 100644
>>>>>>> --- a/config/rootfiles/common/web-user-interface
>>>>>>> +++ b/config/rootfiles/common/web-user-interface
>>>>>>> @@ -69,6 +69,7 @@ srv/web/ipfire/cgi-bin/proxy.cgi
>>>>>>> srv/web/ipfire/cgi-bin/qos.cgi
>>>>>>> srv/web/ipfire/cgi-bin/remote.cgi
>>>>>>> srv/web/ipfire/cgi-bin/routing.cgi
>>>>>>> +#srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>> #srv/web/ipfire/cgi-bin/samba.cgi
>>>>>>> srv/web/ipfire/cgi-bin/services.cgi
>>>>>>> srv/web/ipfire/cgi-bin/shutdown.cgi
>>>>>>> diff --git a/config/rootfiles/packages/rpz b/config/rootfiles/packages/rpz
>>>>>>> new file mode 100644
>>>>>>> index 000000000..1c8663049
>>>>>>> --- /dev/null
>>>>>>> +++ b/config/rootfiles/packages/rpz
>>>>>>> @@ -0,0 +1,20 @@
>>>>>>> +etc/unbound/local.d/00-rpz.conf
>>>>>>> +etc/unbound/zonefiles
>>>>>>> +etc/unbound/zonefiles/allow.rpz
>>>>>>> +usr/sbin/rpz-config
>>>>>>> +usr/sbin/rpz-functions
>>>>>>> +usr/sbin/rpz-make
>>>>>>> +usr/sbin/rpz-metrics
>>>>>>> +usr/sbin/rpz-sleep
>>>>>>> +var/ipfire/addon-lang/rpz.de.pl
>>>>>>> +var/ipfire/addon-lang/rpz.en.pl
>>>>>>> +var/ipfire/addon-lang/rpz.es.pl
>>>>>>> +var/ipfire/addon-lang/rpz.fr.pl
>>>>>>> +var/ipfire/addon-lang/rpz.it.pl
>>>>>>> +var/ipfire/addon-lang/rpz.tr.pl
>>>>>>> +var/ipfire/backup/addons/includes/rpz
>>>>>>> +var/ipfire/dns/rpz
>>>>>>> +var/ipfire/dns/rpz/allowlist
>>>>>>> +var/ipfire/dns/rpz/blocklist
>>>>>>> +var/ipfire/menu.d/EX-rpz.menu
>>>>>>> +srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>> diff --git a/config/rpz/00-rpz.conf b/config/rpz/00-rpz.conf
>>>>>>> new file mode 100644
>>>>>>> index 000000000..f005a4f2e
>>>>>>> --- /dev/null
>>>>>>> +++ b/config/rpz/00-rpz.conf
>>>>>>> @@ -0,0 +1,10 @@
>>>>>>> +server:
>>>>>>> + module-config: "respip validator iterator"
>>>>>>> +
>>>>>>> +rpz:
>>>>>>> + name: allow.rpz
>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>> + rpz-action-override: passthru
>>>>>>> + rpz-log: yes
>>>>>>> + rpz-log-name: allow
>>>>>>> + rpz-signal-nxdomain-ra: yes
>>>>>>> diff --git a/config/rpz/rpz-config b/config/rpz/rpz-config
>>>>>>> new file mode 100644
>>>>>>> index 000000000..c72d50f9b
>>>>>>> --- /dev/null
>>>>>>> +++ b/config/rpz/rpz-config
>>>>>>> @@ -0,0 +1,130 @@
>>>>>>> +#!/bin/bash
>>>>>>> +###############################################################################
>>>>>>> +# #
>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info@ipfire.org> #
>>>>>>> +# #
>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>> +# #
>>>>>>> +###############################################################################
>>>>>>> +
>>>>>>> +version="2025-01-11 - v44"
>>>>>>> +
>>>>>>> +############### Functions ###############
>>>>>>> +
>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>> +
>>>>>>> +############### Main ###############
>>>>>>> +
>>>>>>> +tagName="unbound"
>>>>>>> +
>>>>>>> +rpzAction="${1}" # input RPZ action
>>>>>>> +rpzName="${2}" # input RPZ name
>>>>>>> +rpzURL="${3}" # input RPZ URL
>>>>>>> +rpzOption1="${4}" # input RPZ option #1
>>>>>>> +rpzOption2="${5}" # input RPZ option #2
>>>>>>> +
>>>>>>> +rpzConfig="/etc/unbound/local.d/${rpzName}.rpz.conf" # output zone conf file
>>>>>>> +rpzFile="/etc/unbound/zonefiles/${rpzName}.rpz" # output for RPZ file
>>>>>>> +
>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>> +
>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>> + case "$1" in
>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>> + esac
>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>> +done
>>>>>>> +
>>>>>>> +case "${rpzAction}" in
>>>>>>> + # add new rpz list
>>>>>>> + add )
>>>>>>> + check_name "${rpzName}" # is this a valid name?
>>>>>>> + # does this config already exist? If yes, then exit
>>>>>>> + if [[ -f "${rpzConfig}" ]] ; then
>>>>>>> + msg_log "error: rpz: duplicate - ${rpzConfig} already exists. exit"
>>>>>>> + exit 104
>>>>>>> + fi
>>>>>>> +
>>>>>>> + # is this a valid URL?
>>>>>>> + regex='^https://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
>>>>>>> + if ! [[ "${rpzURL}" =~ $regex ]] ; then
>>>>>>> + msg_log "error: rpz: the URL is not valid: \"${rpzURL}\". exit."
>>>>>>> + exit 105
>>>>>>> + fi
>>>>>>> +
>>>>>>> + # create the zone config file
>>>>>>> + {
>>>>>>> + echo "rpz:"
>>>>>>> + echo " name: ${rpzName}.rpz"
>>>>>>> + echo " zonefile: ${rpzFile}"
>>>>>>> + echo " url: ${rpzURL}"
>>>>>>> + echo " rpz-action-override: nxdomain"
>>>>>>> + echo " rpz-log: ${rpzLog}"
>>>>>>> + echo " rpz-log-name: ${rpzName}"
>>>>>>> + echo " rpz-signal-nxdomain-ra: yes"
>>>>>>> + } > "${rpzConfig}"
>>>>>>> +
>>>>>>> + # set-up zonefile
>>>>>>> + # create an empty rpz file if it does not exist
>>>>>>> + if [[ ! -f "${rpzFile}" ]] ; then
>>>>>>> + touch "${rpzFile}"
>>>>>>> + # unbound requires these settings for rpz files
>>>>>>> + set_permissions "${rpzFile}" "${rpzConfig}"
>>>>>>> + fi
>>>>>>> + ;;
>>>>>>> +
>>>>>>> + # trash config file & rpz file
>>>>>>> + remove )
>>>>>>> + if ! [[ -f "${rpzConfig}" ]] ; then
>>>>>>> + msg_log "error: rpz: cannot remove ${rpzConfig}, does not exist. exit"
>>>>>>> + exit 106
>>>>>>> + fi
>>>>>>> +
>>>>>>> + msg_log "info: rpz: remove config file & rpz file \"${rpzName}\""
>>>>>>> + rm "${rpzConfig}"
>>>>>>> + rm "${rpzFile}"
>>>>>>> + ;;
>>>>>>> +
>>>>>>> + reload )
>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>> + ;;
>>>>>>> +
>>>>>>> + list )
>>>>>>> + awk -F':' '/^\s*name:/{ gsub(/[[:blank:]]|\.rpz/, "",$2) ; NAME=$2 } \
>>>>>>> + /^\s*url:/{ gsub(/[[:blank:]]/, "") ; print NAME"="$2":"$3} ' \
>>>>>>> + /etc/unbound/local.d/*rpz.conf
>>>>>>> + exit
>>>>>>> + ;;
>>>>>>> +
>>>>>>> + unbound-restart )
>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>> + unbound_restart
>>>>>>> + exit
>>>>>>> + ;;
>>>>>>> +
>>>>>>> + * )
>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>> + printf "Usage: $(basename "$0") <ACTION> <NAME> <URL> <OPTION> <OPTION>\n"
>>>>>>> + printf "Version: ${version}\n"
>>>>>>> + exit 108
>>>>>>> + ;;
>>>>>>> +
>>>>>>> +esac
>>>>>>> +
>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>> +
>>>>>>> +exit
>>>>>>> diff --git a/config/rpz/rpz-functions b/config/rpz/rpz-functions
>>>>>>> new file mode 100644
>>>>>>> index 000000000..ace1d2690
>>>>>>> --- /dev/null
>>>>>>> +++ b/config/rpz/rpz-functions
>>>>>>> @@ -0,0 +1,85 @@
>>>>>>> +#!/bin/bash
>>>>>>> +###############################################################################
>>>>>>> +# #
>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>> +# #
>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>> +# #
>>>>>>> +###############################################################################
>>>>>>> +
>>>>>>> +version="2024-12-10 - v02"
>>>>>>> +
>>>>>>> +############### Functions ###############
>>>>>>> +
>>>>>>> +msg_log () {
>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>> + if tty --silent ; then
>>>>>>> + echo "${tagName}:" "$*"
>>>>>>> + fi
>>>>>>> +}
>>>>>>> +
>>>>>>> +# check for a valid name
>>>>>>> +check_name () {
>>>>>>> + local theName="${1}"
>>>>>>> +
>>>>>>> + regex='^[a-zA-Z0-9_]+$' # no dash or plus, alpha numeric only
>>>>>>> + regex1='^(allow|block)$' # allow and block are reserved NAMEs
>>>>>>> + if [[ ! "${theName}" =~ $regex ]] || [[ "${theName}" =~ $regex1 ]] ; then
>>>>>>> + msg_log "error: rpz: the NAME is not valid: \"${theName}\". exit."
>>>>>>> + exit 101
>>>>>>> + fi
>>>>>>> +}
>>>>>>> +
>>>>>>> +set_permissions () {
>>>>>>> + chown nobody:nobody "$@"
>>>>>>> + chmod 644 "$@"
>>>>>>> +}
>>>>>>> +
>>>>>>> +check_unbound_conf () {
>>>>>>> + local thecheckConf="${1:-yes}" # check config default is yes
>>>>>>> +
>>>>>>> + # check the above config files
>>>>>>> + if [[ "${thecheckConf}" == yes ]] ; then
>>>>>>> + msg_log "info: rpz: check for errors with \"unbound-checkconf\""
>>>>>>> +
>>>>>>> + if ! unbound-checkconf ; then
>>>>>>> + msg_log "error: rpz: unbound-checkconf found invalid configuration."
>>>>>>> + msg_log \
>>>>>>> + "error: rpz: In Terminal run the command \"unbound-checkconf\" for more information. exit."
>>>>>>> + exit 102
>>>>>>> + fi
>>>>>>> + fi
>>>>>>> +}
>>>>>>> +
>>>>>>> +unbound_control_reload () {
>>>>>>> + local theReload="${1:-yes}" # reload default is yes
>>>>>>> +
>>>>>>> + if [[ "${theReload}" == yes ]] ; then
>>>>>>> + # reload due to the changes
>>>>>>> + msg_log "info: rpz: run \"unbound-control reload\""
>>>>>>> +
>>>>>>> + if ! unbound-control reload ; then
>>>>>>> + msg_log "error: rpz: unbound-control reload. exit."
>>>>>>> + exit 109
>>>>>>> + fi
>>>>>>> + fi
>>>>>>> +}
>>>>>>> +
>>>>>>> +unbound_restart () {
>>>>>>> + # restart due to the changes
>>>>>>> + msg_log "info: rpz: run \"unbound restart\""
>>>>>>> +
>>>>>>> + /usr/local/bin/unboundctrl restart
>>>>>>> +}
>>>>>>> diff --git a/config/rpz/rpz-make b/config/rpz/rpz-make
>>>>>>> new file mode 100644
>>>>>>> index 000000000..927d55170
>>>>>>> --- /dev/null
>>>>>>> +++ b/config/rpz/rpz-make
>>>>>>> @@ -0,0 +1,203 @@
>>>>>>> +#!/bin/bash
>>>>>>> +###############################################################################
>>>>>>> +# #
>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info@ipfire.org> #
>>>>>>> +# #
>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>> +# #
>>>>>>> +###############################################################################
>>>>>>> +
>>>>>>> +version="2025-01-11 - v14"
>>>>>>> +
>>>>>>> +############### Functions ###############
>>>>>>> +
>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>> +
>>>>>>> +# create the config file for allow
>>>>>>> +make_allow_config () {
>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>> + local theConfig="/etc/unbound/local.d/00-rpz.conf" # output zone conf file
>>>>>>> + local theList="/var/ipfire/dns/rpz/allowlist" # input custom list of domains
>>>>>>> +
>>>>>>> + msg_log "info: rpz: make config file \"00-rpz.conf\""
>>>>>>> +
>>>>>>> + echo "server:
>>>>>>> + module-config: \"respip validator iterator\"" > "${theConfig}"
>>>>>>> +
>>>>>>> + # does allow list exist?
>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>> +
>>>>>>> + echo "rpz:
>>>>>>> + name: allow.rpz
>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>> + rpz-action-override: passthru
>>>>>>> + rpz-log: ${theLog}
>>>>>>> + rpz-log-name: allow
>>>>>>> + rpz-signal-nxdomain-ra: yes" >> "${theConfig}"
>>>>>>> +
>>>>>>> + fi
>>>>>>> +
>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>> + set_permissions "${theConfig}"
>>>>>>> +}
>>>>>>> +
>>>>>>> +# create the config file for block
>>>>>>> +make_block_config () {
>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>> + local theConfig="/etc/unbound/local.d/block.rpz.conf" # output zone conf file
>>>>>>> + local theList="/var/ipfire/dns/rpz/blocklist" # input custom list of domains
>>>>>>> +
>>>>>>> + msg_log "info: rpz: make config file \"block.rpz.conf\""
>>>>>>> +
>>>>>>> + # does block list exist?
>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>> +
>>>>>>> + echo "rpz:
>>>>>>> + name: block.rpz
>>>>>>> + zonefile: /etc/unbound/zonefiles/block.rpz
>>>>>>> + rpz-action-override: nxdomain
>>>>>>> + rpz-log: ${theLog}
>>>>>>> + rpz-log-name: block
>>>>>>> + rpz-signal-nxdomain-ra: yes" > "${theConfig}"
>>>>>>> +
>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>> + set_permissions "${theConfig}"
>>>>>>> + else
>>>>>>> + # no - trash the config file
>>>>>>> + rm --verbose /etc/unbound/local.d/block.rpz.conf
>>>>>>> + fi
>>>>>>> +}
>>>>>>> +
>>>>>>> +# create an RPZ file for allow or block
>>>>>>> +make_rpz_file () {
>>>>>>> + local theName="${1}" # allow or block
>>>>>>> + local theAction='.' # the default is nxdomain or block
>>>>>>> + local actionList
>>>>>>> +
>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>> + local theZonefile="/etc/unbound/zonefiles/${theName}.rpz" # output file for RPZ
>>>>>>> +
>>>>>>> + # does a list exist?
>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>> +
>>>>>>> + # for allow set to passthru
>>>>>>> + [[ "${theName}" == allow ]] && theAction='rpz-passthru.'
>>>>>>> +
>>>>>>> + # drop any extra "blanks" and add "CNAME <RPZ action>." to each line
>>>>>>> + actionList=$( awk '{$1=$1};1' "${theList}" |
>>>>>>> + sed "/^[^;].*[[:alnum:]]/ s|$| CNAME ${theAction}|" )
>>>>>>> +
>>>>>>> + msg_log "info: rpz: create zonefile for \"${theName}list\""
>>>>>>> +
>>>>>>> +echo "; Name: ${theName} list
>>>>>>> +; Last modified: $(date "+%Y-%m-%d at %H.%M.%S %Z")
>>>>>>> +;
>>>>>>> +; domains with actions list
>>>>>>> +;
>>>>>>> +${actionList}" > "${theZonefile}"
>>>>>>> +
>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>> + set_permissions "${theZonefile}"
>>>>>>> + # set-up allow/block list files
>>>>>>> + set_permissions "${theList}"
>>>>>>> + else
>>>>>>> + msg_log "info: rpz: the ${theList} is empty."
>>>>>>> +
>>>>>>> + rm --verbose "${theZonefile}" # trash the RPZ file
>>>>>>> + fi
>>>>>>> +}
>>>>>>> +
>>>>>>> +# check if allow/block list is valid
>>>>>>> +validate_list () {
>>>>>>> + local theName="${1}" # allow or block
>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>> +
>>>>>>> + # remove good:
>>>>>>> + # - properly formated domain names with or without leading wildcard
>>>>>>> + # - properly formated top level domain (TLD) names with wildcard
>>>>>>> + # - blank lines and comment lines
>>>>>>> + # remaining lines are considered "bad"
>>>>>>> + bad_lines=$( sed --regexp-extended \
>>>>>>> + '/^(\*\.)?([a-zA-Z0-9](([a-zA-Z0-9\-]){0,61}[a-zA-Z0-9])?\.)+([a-zA-Z]{2,}|xn--[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])$/d ;
>>>>>>> + /^(\*\.)([a-z]{2,61}|xn--[a-z0-9]{1,60})$/d ;
>>>>>>> + /^$/d ; /^;/d' "${theList}" )
>>>>>>> +
>>>>>>> + if [[ ! -z "${bad_lines}" ]] ; then
>>>>>>> + msg_log "error: rpz: invalid line(s) in ${theList}."
>>>>>>> + printf "%s\n" "bad line(s): ${bad_lines}"
>>>>>>> + exit 110
>>>>>>> + fi
>>>>>>> +}
>>>>>>> +
>>>>>>> +
>>>>>>> +############### Main ###############
>>>>>>> +
>>>>>>> +tagName="unbound"
>>>>>>> +
>>>>>>> +rpzName="${1}" # input RPZ name
>>>>>>> +
>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>> +
>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>> + case "$1" in
>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>> + esac
>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>> +done
>>>>>>> +
>>>>>>> +case "${rpzName}" in
>>>>>>> + # make a new allow or block rpz file
>>>>>>> +
>>>>>>> + allow )
>>>>>>> + validate_list 'allow' # is the allowlist valid?
>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>> + make_rpz_file 'allow'
>>>>>>> + ;;
>>>>>>> +
>>>>>>> + allowblock )
>>>>>>> + validate_list 'allow' # is the list valid?
>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>> + make_rpz_file 'allow'
>>>>>>> + ;&
>>>>>>> +
>>>>>>> + block )
>>>>>>> + validate_list 'block' # is the blocklist valid?
>>>>>>> + make_block_config "${rpzLog}"
>>>>>>> + make_rpz_file 'block'
>>>>>>> + ;;
>>>>>>> +
>>>>>>> + reload )
>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>> + ;;
>>>>>>> +
>>>>>>> + unbound-restart )
>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>> + unbound_restart
>>>>>>> + exit
>>>>>>> + ;;
>>>>>>> +
>>>>>>> + * )
>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>> + printf "Usage: $(basename "$0") <NAME> <OPTION> <OPTION>\n"
>>>>>>> + printf "Version: ${version}\n"
>>>>>>> + exit 108
>>>>>>> + ;;
>>>>>>> +esac
>>>>>>> +
>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>> +
>>>>>>> +exit
>>>>>>> diff --git a/config/rpz/rpz-metrics b/config/rpz/rpz-metrics
>>>>>>> new file mode 100755
>>>>>>> index 000000000..4d43e1629
>>>>>>> --- /dev/null
>>>>>>> +++ b/config/rpz/rpz-metrics
>>>>>>> @@ -0,0 +1,170 @@
>>>>>>> +#!/bin/bash
>>>>>>> +###############################################################################
>>>>>>> +# #
>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>> +# #
>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>> +# #
>>>>>>> +###############################################################################
>>>>>>> +
>>>>>>> +version="2025-01-20 - v25"
>>>>>>> +
>>>>>>> +############### Main ###############
>>>>>>> +
>>>>>>> +weeks="2" # default to two message logs
>>>>>>> +sortBy="name" # default "by name"
>>>>>>> +rpzActive="enabled" # default "enabled only"
>>>>>>> +
>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>> + case "$1" in
>>>>>>> + --by-names | --by-name | name ) sortBy="name" ;;
>>>>>>> +
>>>>>>> + --by-hits | --by-hit | hits | hit ) sortBy="hit" ;;
>>>>>>> +
>>>>>>> + --by-lines | --by-line | lines | line ) sortBy="line" ;;
>>>>>>> +
>>>>>>> + --by-effect ) sortBy="effect" ;;
>>>>>>> +
>>>>>>> + --enabled-only ) rpzActive="enabled" ;;
>>>>>>> +
>>>>>>> + --active-all | --all | all ) rpzActive="all" ;;
>>>>>>> +
>>>>>>> + [0-9] | [0-9][0-9] ) weeks=$1 ;;
>>>>>>> + esac
>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>> +done
>>>>>>> +
>>>>>>> +# get the list of message logs for N weeks
>>>>>>> +messageLogs=$( find /var/log/messages* -type f | sort --version-sort |
>>>>>>> + head -"${weeks}" )
>>>>>>> +
>>>>>>> +# get the list of RPZ names & counts from the message log(s)
>>>>>>> +rpzNameCount=$( for logf in ${messageLogs} ; do
>>>>>>> + zgrep --text --fixed-strings 'info: rpz: applied' "${logf}" |
>>>>>>> + awk '$10 ~ /\[\w*]/ { print $10 }' ;
>>>>>>> + done | sort | uniq --count )
>>>>>>> +
>>>>>>> +# flip results and remove brackets `[` and `]`
>>>>>>> +rpzNameCount=$( echo "${rpzNameCount}" |
>>>>>>> + awk '{ print $2, $1 }' |
>>>>>>> + sed --regexp-extended 's|^\[(.*)\]|\1|' )
>>>>>>> +
>>>>>>> +# grab only names
>>>>>>> +rpzNames=$( echo "${rpzNameCount}" | awk '{ print $1 }' )
>>>>>>> +
>>>>>>> +# get list of RPZ files
>>>>>>> +rpzFileList=$( find /etc/unbound/zonefiles -type f -iname "*.rpz" )
>>>>>>> +
>>>>>>> +# get basename of those files
>>>>>>> +rpzBaseNames=$( echo "${rpzFileList}" |
>>>>>>> + sed 's|/etc/unbound/zonefiles/||g ; s|\.rpz||g ;' )
>>>>>>> +
>>>>>>> +# add to rpzNames
>>>>>>> +rpzNames="${rpzNames}"$'\n'"${rpzBaseNames}"
>>>>>>> +
>>>>>>> +# drop duplicate names
>>>>>>> +rpzNames=$( echo "${rpzNames}" | sort --unique )
>>>>>>> +
>>>>>>> +# get line count for each RPZ
>>>>>>> +lineCount=$( echo "${rpzFileList}" | xargs wc -l )
>>>>>>> +
>>>>>>> +# get comment line count and blank line count for each RPZ
>>>>>>> +commentCount=$( echo "${rpzFileList}" | xargs grep --count -e "^$" -e "^;" )
>>>>>>> +
>>>>>>> +# get modified date each RPZ
>>>>>>> +modDateList=$( echo "${rpzFileList}" | xargs stat -c '%.10y %n' )
>>>>>>> +
>>>>>>> +ucListAuthZones=$( unbound-control list_auth_zones )
>>>>>>> +
>>>>>>> +# get width of RPZ names
>>>>>>> +pWidth=$( echo "${rpzNames}" | awk '{ print $1" " }' | wc -L )
>>>>>>> +pFormat="%-${pWidth}s %-8s %-8s %8s %12s %12s\n"
>>>>>>> +
>>>>>>> +# print title line
>>>>>>> +printf "${pFormat}" "name" "hits" "active" "lines" " hits/line" "last download"
>>>>>>> +printf -- "--------------"
>>>>>>> +
>>>>>>> +theResults=""
>>>>>>> +totalLines=0
>>>>>>> +totalHits=0
>>>>>>> +while read -r theName
>>>>>>> +do
>>>>>>> + printf -- "--" # pretend progress bar
>>>>>>> +
>>>>>>> + # is this RPZ list active?
>>>>>>> + theActive="disabled"
>>>>>>> + if grep --quiet "^${theName}\.rpz" <<< "${ucListAuthZones}"
>>>>>>> + then
>>>>>>> + theActive="enabled"
>>>>>>> + else
>>>>>>> + [[ "${rpzActive}" == enabled ]] && continue
>>>>>>> + fi
>>>>>>> +
>>>>>>> + # get hit count
>>>>>>> + theHits="0"
>>>>>>> + if output=$( grep "^${theName}\s" <<< "${rpzNameCount}" ) ; then
>>>>>>> + theHits=$( echo "${output}" | awk '{ print $2 }' )
>>>>>>> + totalHits=$(( totalHits + theHits ))
>>>>>>> + fi
>>>>>>> +
>>>>>>> + # get line count
>>>>>>> + theLines="n/a"
>>>>>>> + hitsPerLine="0"
>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${lineCount}" ) ; then
>>>>>>> + theLines=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>> + totalLines=$(( totalLines + theLines ))
>>>>>>> +
>>>>>>> + if [[ "${theLines}" -gt 2 ]] ; then
>>>>>>> + hitsPerLine=$(( 100 * theHits / theLines ))
>>>>>>> + fi
>>>>>>> + fi
>>>>>>> +
>>>>>>> + # get modification date
>>>>>>> + theModDate="n/a"
>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${modDateList}" ) ; then
>>>>>>> + theModDate=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>> + fi
>>>>>>> +
>>>>>>> + # add to results list
>>>>>>> + theResults+="${theName} ${theHits} ${theActive} ${theLines} ${hitsPerLine} ${theModDate}"$'\n'
>>>>>>> +
>>>>>>> +done <<< "${rpzNames}"
>>>>>>> +
>>>>>>> +case "${sortBy}" in
>>>>>>> + # sort by "active" then by "name"
>>>>>>> + name) sortArg=(-k3,3r -k1,1) ;;
>>>>>>> +
>>>>>>> + # sort by "active" then by "hits" then by "name"
>>>>>>> + hit) sortArg=(-k3,3r -k2,2nr -k1,1) ;;
>>>>>>> +
>>>>>>> + # sort by "active" then by "lines" then by "name"
>>>>>>> + line) sortArg=(-k3,3r -k4,4nr -k1,1) ;;
>>>>>>> +
>>>>>>> + # sort by "active" then by "effect" then by "name"
>>>>>>> + effect) sortArg=(-k3,3r -k5,5nr -k1,1) ;;
>>>>>>> +esac
>>>>>>> +
>>>>>>> +printf -- "--------------\n"
>>>>>>> +# remove blank lines, sort, print as columns
>>>>>>> +echo "${theResults}" |
>>>>>>> + awk '!/^[[:space:]]*$/' |
>>>>>>> + sort "${sortArg[@]}" |
>>>>>>> + awk --assign=width="${pWidth}" \
>>>>>>> + '{ printf "%-*s %-8s %-8s %8s %10s %% %12s\n", width, $1, $2, $3, $4, $5, $6 }'
>>>>>>> +
>>>>>>> +printf "${pFormat}" "" "=======" "" "========" "" ""
>>>>>>> +printf "${pFormat}" "Totals -->" "${totalHits}" "" "${totalLines}" "" ""
>>>>>>> +
>>>>>>> +exit
>>>>>>> diff --git a/config/rpz/rpz-sleep b/config/rpz/rpz-sleep
>>>>>>> new file mode 100755
>>>>>>> index 000000000..dd3603599
>>>>>>> --- /dev/null
>>>>>>> +++ b/config/rpz/rpz-sleep
>>>>>>> @@ -0,0 +1,58 @@
>>>>>>> +#!/bin/bash
>>>>>>> +###############################################################################
>>>>>>> +# #
>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>> +# #
>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>> +# #
>>>>>>> +###############################################################################
>>>>>>> +
>>>>>>> +version="2024-08-16" # v05
>>>>>>> +
>>>>>>> +############### Functions ###############
>>>>>>> +
>>>>>>> +# send message to message log
>>>>>>> +msg_log () {
>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>> + if tty --silent ; then
>>>>>>> + echo "${tagName}:" "$*"
>>>>>>> + fi
>>>>>>> +}
>>>>>>> +
>>>>>>> +############### Main ###############
>>>>>>> +
>>>>>>> +tagName="unbound"
>>>>>>> +
>>>>>>> +sleepTime="${1:-5m}" # default to sleep for 5m (5 minutes)
>>>>>>> +
>>>>>>> +zoneList=$( unbound-control list_auth_zones | awk '{print $1}' )
>>>>>>> +
>>>>>>> +for zone in ${zoneList} ; do
>>>>>>> + printf "disable ${zone}\t"
>>>>>>> + unbound-control rpz_disable "${zone}"
>>>>>>> +done
>>>>>>> +
>>>>>>> +msg_log "info: rpz: disabled all zones for ${sleepTime}"
>>>>>>> +
>>>>>>> +sleep "${sleepTime}"
>>>>>>> +
>>>>>>> +for zone in ${zoneList} ; do
>>>>>>> + printf "enable ${zone}\t"
>>>>>>> + unbound-control rpz_enable "${zone}"
>>>>>>> +done
>>>>>>> +
>>>>>>> +msg_log "info: rpz: enabled all zones"
>>>>>>> +
>>>>>>> +exit
>>>>>>> diff --git a/config/rpz/rpz.de.pl b/config/rpz/rpz.de.pl
>>>>>>> new file mode 100644
>>>>>>> index 000000000..3770c6bb0
>>>>>>> --- /dev/null
>>>>>>> +++ b/config/rpz/rpz.de.pl
>>>>>>> @@ -0,0 +1,30 @@
>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>> +%tr = (%tr,
>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>> +'rpz apply' => 'Übernehmen',
>>>>>>> +'rpz cl allow enable' => 'Benutzerdefinierte Allowlist aktivieren:',
>>>>>>> +'rpz cl allow info' => 'Zugelassene Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>> +'rpz cl allow' => 'Benutzerdefinierte Allowlist',
>>>>>>> +'rpz cl block enable' => 'Benutzerdefinierte Blocklist aktivieren:',
>>>>>>> +'rpz cl block info' => 'Gesperrte Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>> +'rpz cl block' => 'Benutzerdefinierte Blocklist',
>>>>>>> +'rpz cl' => 'Benutzerdefinierte Listen',
>>>>>>> +'rpz exitcode 101' => 'Der Name enthält unzulässige Zeichen',
>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf hat eine fehlerhafte Konfiguration ermittelt. Führen Sie das Kommando unbound-checkconf auf der Konsole aus, um weitere Informationen zu erhalten.',
>>>>>>> +'rpz exitcode 103' => 'Die benutzerdefinierte Allow-/Blocklist ist leer',
>>>>>>> +'rpz exitcode 104' => 'Ein Eintrag mit identischem Namen existiert bereits',
>>>>>>> +'rpz exitcode 105' => 'Die URL ist ungültig',
>>>>>>> +'rpz exitcode 106' => 'Eintrag kann nicht entfernt werden, der Name existiert nicht',
>>>>>>> +'rpz exitcode 107' => 'Der Name ist ungültig - nur "allow" oder "block" möglich',
>>>>>>> +'rpz exitcode 108' => 'Fehlende oder inkorrekte Parameter',
>>>>>>> +'rpz exitcode 109' => 'unbound-control reload ist fehlgeschlagen',
>>>>>>> +'rpz exitcode 110' => 'Die benutzerdefinierte Allow-/Blocklist enthält unzulässige Einträge',
>>>>>>> +'rpz exitcode 201' => 'Die Anmerkung enthält unzulässige Zeichen',
>>>>>>> +'rpz exitcode 202' => 'Ungültiger Eintrag in der benutzerdefinierten Allowlist, Zeile ',
>>>>>>> +'rpz exitcode 203' => 'Ungültiger Eintrag in der benutzerdefinierten Blocklist, Zeile ',
>>>>>>> +'rpz exitcode 204' => 'Ausgewählter Eintrag existiert nicht: ',
>>>>>>> +'rpz zf editor' => 'Zonendatei-Eintrag bearbeiten',
>>>>>>> +'rpz zf imported' => '(importiert aus rpz-config)',
>>>>>>> +'rpz zf remark info' => 'Erlaubte Zeichen sind a-z, A-Z, 0-9 und Unterstriche',
>>>>>>> +'rpz zf' => 'Zonendateien',
>>>>>>> +);
>>>>>>> diff --git a/config/rpz/rpz.en.pl b/config/rpz/rpz.en.pl
>>>>>>> new file mode 100644
>>>>>>> index 000000000..0720a8940
>>>>>>> --- /dev/null
>>>>>>> +++ b/config/rpz/rpz.en.pl
>>>>>>> @@ -0,0 +1,30 @@
>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>> +%tr = (%tr,
>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>> +'rpz apply' => 'Apply',
>>>>>>> +'rpz cl allow enable' => 'Enable custom allowlist:',
>>>>>>> +'rpz cl allow info' => 'Allowed domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>> +'rpz cl allow' => 'Custom allowlist',
>>>>>>> +'rpz cl block enable' => 'Enable custom blocklist:',
>>>>>>> +'rpz cl block info' => 'Blocked domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>> +'rpz cl block' => 'Custom blocklist',
>>>>>>> +'rpz cl' => 'Custom lists',
>>>>>>> +'rpz exitcode 101' => 'the NAME is not valid',
>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf found invalid configuration. In the Terminal run the command unbound-checkconf for more information',
>>>>>>> +'rpz exitcode 103' => 'the allow/block list is empty',
>>>>>>> +'rpz exitcode 104' => 'duplicate - NAME already exists',
>>>>>>> +'rpz exitcode 105' => 'the URL is not valid',
>>>>>>> +'rpz exitcode 106' => 'cannot remove the NAME does not exist',
>>>>>>> +'rpz exitcode 107' => 'the NAME is not valid - "allow" or "block" only',
>>>>>>> +'rpz exitcode 108' => 'missing or incorrect parameter',
>>>>>>> +'rpz exitcode 109' => 'unbound-control reload failed',
>>>>>>> +'rpz exitcode 110' => 'custom Allowlist/Blocklist contains invalid entries',
>>>>>>> +'rpz exitcode 201' => 'the REMARK is not valid',
>>>>>>> +'rpz exitcode 202' => 'invalid entry in allowlist, line ',
>>>>>>> +'rpz exitcode 203' => 'invalid entry in blocklist, line ',
>>>>>>> +'rpz exitcode 204' => 'Selected entry does not exist: ',
>>>>>>> +'rpz zf editor' => 'Edit zonefiles entry',
>>>>>>> +'rpz zf imported' => '(imported from rpz-config)',
>>>>>>> +'rpz zf remark info' => 'Valid characters are a-z, A-Z, 0-9 and underscore.',
>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>> +);
>>>>>>> diff --git a/config/rpz/rpz.es.pl b/config/rpz/rpz.es.pl
>>>>>>> new file mode 100644
>>>>>>> index 000000000..98628e4aa
>>>>>>> --- /dev/null
>>>>>>> +++ b/config/rpz/rpz.es.pl
>>>>>>> @@ -0,0 +1,30 @@
>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>> +%tr = (%tr,
>>>>>>> +'rpz' => 'Zonas de política de respuesta (RPZ)',
>>>>>>> +'rpz apply' => 'Aplicar',
>>>>>>> +'rpz cl allow enable' => 'Habilitar la lista blanca personalizada:',
>>>>>>> +'rpz cl allow info' => 'Dominio permitido (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>> +'rpz cl allow' => 'Lista blanca personalizada',
>>>>>>> +'rpz cl block enable' => 'Habilitar la lista negra personalizada:',
>>>>>>> +'rpz cl block info' => 'Dominio bloqueado (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>> +'rpz cl block' => 'Lista negra personalizada',
>>>>>>> +'rpz cl' => 'Lista personalizada',
>>>>>>> +'rpz exitcode 101' => 'El NOMBRE no es válido',
>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha encontrado una configuración no válida. Desde Terminal, ejecute el comando unbound-checkconf para mayor información',
>>>>>>> +'rpz exitcode 103' => 'La lista de permitidos/bloqueados está vacía',
>>>>>>> +'rpz exitcode 104' => 'duplicado - NOMBRE ya existe',
>>>>>>> +'rpz exitcode 105' => 'la URL no es válida',
>>>>>>> +'rpz exitcode 106' => 'no es posible eliminar el NOMBRE que no existe',
>>>>>>> +'rpz exitcode 107' => 'el NOMBRE no es válido - sólo "permitir" o "bloquear"',
>>>>>>> +'rpz exitcode 108' => 'parámetro faltante o incorrecto',
>>>>>>> +'rpz exitcode 109' => 'Error al recargar unbound-control',
>>>>>>> +'rpz exitcode 110' => 'la Lista blanca/Lista negra personalizada contiene entradas no válidas',
>>>>>>> +'rpz exitcode 201' => 'el COMENTARIO no es válido',
>>>>>>> +'rpz exitcode 202' => 'entrada no válida en la lista blanca, línea ',
>>>>>>> +'rpz exitcode 203' => 'entrada no válida en la lista negra, línea ',
>>>>>>> +'rpz exitcode 204' => 'La entrada seleccionada no existe: ',
>>>>>>> +'rpz zf editor' => 'Editar la entrada de archivos de zona',
>>>>>>> +'rpz zf imported' => '(importado de rpz-config)',
>>>>>>> +'rpz zf remark info' => 'Los caracteres válidos son a-z, A-Z, 0-9 y guión bajo',
>>>>>>> +'rpz zf' => 'Archivos de zona',
>>>>>>> +);
>>>>>>> diff --git a/config/rpz/rpz.fr.pl b/config/rpz/rpz.fr.pl
>>>>>>> new file mode 100644
>>>>>>> index 000000000..f35f3c2d0
>>>>>>> --- /dev/null
>>>>>>> +++ b/config/rpz/rpz.fr.pl
>>>>>>> @@ -0,0 +1,30 @@
>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>> +%tr = (%tr,
>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>> +'rpz apply' => 'Appliquer',
>>>>>>> +'rpz cl allow enable' => 'Activer la liste d\'autorisations personnalisée:',
>>>>>>> +'rpz cl allow info' => 'Domaines autorisés (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>> +'rpz cl allow' => 'Liste d\'autorisations personnalisée',
>>>>>>> +'rpz cl block enable' => 'Activer la liste de blocage personnalisée:',
>>>>>>> +'rpz cl block info' => 'Domaines bloqués (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>> +'rpz cl block' => 'liste de blocage personnalisé',
>>>>>>> +'rpz cl' => 'Listes personnalisées',
>>>>>>> +'rpz exitcode 101' => 'le NOM n\'est pas valide',
>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf configuration non valide trouvée. Dans le terminal, exécutez la commande unbound-checkconf pour plus d\'informations',
>>>>>>> +'rpz exitcode 103' => 'la liste autoriser/bloquer est vide',
>>>>>>> +'rpz exitcode 104' => 'le NOM existe déjà',
>>>>>>> +'rpz exitcode 105' => 'L\'URL n\'est pas valide',
>>>>>>> +'rpz exitcode 106' => 'impossible de supprimer le NOM n\'existe pas',
>>>>>>> +'rpz exitcode 107' => 'le NOM n\'est pas valide - « autoriser » ou « bloquer » seulement',
>>>>>>> +'rpz exitcode 108' => 'paramètre manquant ou incorrect',
>>>>>>> +'rpz exitcode 109' => 'unbound-control rechargement échoué',
>>>>>>> +'rpz exitcode 110' => 'la liste autoriser/bloquer contient des entrées non valides',
>>>>>>> +'rpz exitcode 201' => 'la REMARQUE n\'est pas valable',
>>>>>>> +'rpz exitcode 202' => 'entrée non valide dans la liste d\'autorisation, ligne ',
>>>>>>> +'rpz exitcode 203' => 'entrée non valide dans la liste de blocs, ligne ',
>>>>>>> +'rpz exitcode 204' => 'L\'entrée sélectionnée n\'existe pas: ',
>>>>>>> +'rpz zf editor' => 'Modifier l\'entrée Fichiers Zone',
>>>>>>> +'rpz zf imported' => '(importé de rpz-config)',
>>>>>>> +'rpz zf remark info' => 'Les caractères valides sont a-z, A-Z, 0-9 et soulignement.',
>>>>>>> +'rpz zf' => 'Fichiers Zone',
>>>>>>> +);
>>>>>>> diff --git a/config/rpz/rpz.it.pl b/config/rpz/rpz.it.pl
>>>>>>> new file mode 100644
>>>>>>> index 000000000..ee81605c9
>>>>>>> --- /dev/null
>>>>>>> +++ b/config/rpz/rpz.it.pl
>>>>>>> @@ -0,0 +1,30 @@
>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>> +%tr = (%tr,
>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>> +'rpz apply' => 'Applica',
>>>>>>> +'rpz cl allow enable' => 'Abilita la Whitelist personalizzata:',
>>>>>>> +'rpz cl allow info' => 'Domini consentiti (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>> +'rpz cl allow' => 'Whitelist personalizzata',
>>>>>>> +'rpz cl block enable' => 'Abilita la Blacklist personalizzata:',
>>>>>>> +'rpz cl block info' => 'Domini bloccati (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>> +'rpz cl block' => 'Blacklist personalizzata',
>>>>>>> +'rpz cl' => 'Liste personalizzate',
>>>>>>> +'rpz exitcode 101' => 'il NOME non è valido',
>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha trovato una configurazione non valida. Dal Terminale esegui il comando unbound-checkconf per maggiori informazioni',
>>>>>>> +'rpz exitcode 103' => 'l\'elenco consentiti/bloccati è vuoto',
>>>>>>> +'rpz exitcode 104' => 'duplicato - NAME esiste di già',
>>>>>>> +'rpz exitcode 105' => 'l\'URL non è valido',
>>>>>>> +'rpz exitcode 106' => 'non è possibile rimuovere il NOME non esiste',
>>>>>>> +'rpz exitcode 107' => 'il NOME non è valido - solo "consenti" o "blocca"',
>>>>>>> +'rpz exitcode 108' => 'parametro mancante o non corretto',
>>>>>>> +'rpz exitcode 109' => 'ricaricamento del controllo non associato non riuscito',
>>>>>>> +'rpz exitcode 110' => 'la Whitelist/Blacklist personalizzata contiene voci non valide',
>>>>>>> +'rpz exitcode 201' => 'l"OSSERVAZIONE non è valida',
>>>>>>> +'rpz exitcode 202' => 'voce non valida nella Whitelist, riga ',
>>>>>>> +'rpz exitcode 203' => 'voce non valida nella Blacklist, riga ',
>>>>>>> +'rpz exitcode 204' => 'La voce selezionata non esiste: ',
>>>>>>> +'rpz zf editor' => 'Modifica la voce dei file di zona',
>>>>>>> +'rpz zf imported' => '(importato da rpz-config)',
>>>>>>> +'rpz zf remark info' => 'I caratteri validi sono a-z, A-Z, 0-9 e trattino basso',
>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>> +);
>>>>>>> diff --git a/config/rpz/rpz.tr.pl b/config/rpz/rpz.tr.pl
>>>>>>> new file mode 100644
>>>>>>> index 000000000..00226e192
>>>>>>> --- /dev/null
>>>>>>> +++ b/config/rpz/rpz.tr.pl
>>>>>>> @@ -0,0 +1,30 @@
>>>>>>> +# I�in eklendi Ayriyeten Response Policy Zone (RPZ)
>>>>>>> +%tr = (%tr,
>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>> +'rpz apply' => 'Uygulamak',
>>>>>>> +'rpz cl allow enable' => '�zel Etkinlestir allowlist:',
>>>>>>> +'rpz cl allow info' => 'Izin verilmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>> +'rpz cl allow' => 'Etkinlestir allowlist',
>>>>>>> +'rpz cl block enable' => '�zel Etkinlestir blocklist:',
>>>>>>> +'rpz cl block info' => 'Engellenmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>> +'rpz cl block' => 'Engellenmis blocklist',
>>>>>>> +'rpz cl' => 'Engellenmis lists',
>>>>>>> +'rpz exitcode 101' => 'NAME ge�erli degil',
>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ge�ersiz yapilandirma bulundu. Terminalde daha fazla bilgi i�in Unbound-checkConf komutunu �alistirin',
>>>>>>> +'rpz exitcode 103' => 'allow/block liste bos',
>>>>>>> +'rpz exitcode 104' => 'kopyalamak - NAME zaten var',
>>>>>>> +'rpz exitcode 105' => 'URL ge�erli degil',
>>>>>>> +'rpz exitcode 106' => '�ikarilamiyor NAME yok',
>>>>>>> +'rpz exitcode 107' => 'NAME ge�erli degil - "allow" veya "block" yalniz',
>>>>>>> +'rpz exitcode 108' => 'Parametre eksik veya yanlis',
>>>>>>> +'rpz exitcode 109' => 'unbound-control basarisiz',
>>>>>>> +'rpz exitcode 110' => 'Engellenmis Allowlist/Blocklist ge�ersiz girisler i�erir',
>>>>>>> +'rpz exitcode 201' => 'REMARK ge�erli degil',
>>>>>>> +'rpz exitcode 202' => 'Ge�ersiz giris allowlist, line ',
>>>>>>> +'rpz exitcode 203' => 'Ge�ersiz giris blocklist, line ',
>>>>>>> +'rpz exitcode 204' => 'Se�ilen giris yok: ',
>>>>>>> +'rpz zf editor' => 'yazimlamak zonefiles giris',
>>>>>>> +'rpz zf imported' => '(ithal edildi rpz-config)',
>>>>>>> +'rpz zf remark info' => 'Ge�erli karakterler a-z, A-Z, 0-9ve alt�st.',
>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>> +);
>>>>>>> diff --git a/html/cgi-bin/rpz.cgi b/html/cgi-bin/rpz.cgi
>>>>>>> new file mode 100644
>>>>>>> index 000000000..a821c92ac
>>>>>>> --- /dev/null
>>>>>>> +++ b/html/cgi-bin/rpz.cgi
>>>>>>> @@ -0,0 +1,923 @@
>>>>>>> +#!/usr/bin/perl
>>>>>>> +###############################################################################
>>>>>>> +# #
>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>> +# Copyright (C) 2005-2024 IPFire Team <info@ipfire.org> #
>>>>>>> +# #
>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>> +# #
>>>>>>> +###############################################################################
>>>>>>> +
>>>>>>> +use strict;
>>>>>>> +use Scalar::Util qw(looks_like_number);
>>>>>>> +
>>>>>>> +# debugging
>>>>>>> +#use warnings;
>>>>>>> +#use CGI::Carp 'fatalsToBrowser';
>>>>>>> +#use Data::Dumper;
>>>>>>> +
>>>>>>> +require '/var/ipfire/general-functions.pl';
>>>>>>> +require "${General::swroot}/lang.pl";
>>>>>>> +require "${General::swroot}/header.pl";
>>>>>>> +
>>>>>>> +###--- Extra HTML ---###
>>>>>>> +my $extraHead = <<END
>>>>>>> +<style>
>>>>>>> + /* alternating row background */
>>>>>>> + .tbl tr:nth-child(2n+2) {
>>>>>>> + background-color: var(--color-light-grey);
>>>>>>> + }
>>>>>>> + .tbl tr:nth-child(2n+3) {
>>>>>>> + background-color: var(--color-grey);
>>>>>>> + }
>>>>>>> + /* text styles */
>>>>>>> + .tbl th:not(:last-child) {
>>>>>>> + text-align: left;
>>>>>>> + }
>>>>>>> + div.right {
>>>>>>> + text-align: right;
>>>>>>> + margin-top: 0.5em;
>>>>>>> + }
>>>>>>> + /* customlist input */
>>>>>>> + textarea.domainlist {
>>>>>>> + margin: 0.5em 0;
>>>>>>> + resize: vertical;
>>>>>>> + min-height: 10em;
>>>>>>> + overflow: auto;
>>>>>>> + white-space: pre;
>>>>>>> + }
>>>>>>> + button[type=submit]:disabled {
>>>>>>> + opacity: 0.6;
>>>>>>> + }
>>>>>>> +</style>
>>>>>>> +END
>>>>>>> +;
>>>>>>> +###--- End of extra HTML ---###
>>>>>>> +
>>>>>>> +
>>>>>>> +### Settings ###
>>>>>>> +
>>>>>>> +# Request DNS service reload after configuration change
>>>>>>> +my $RPZ_RELOAD_FLAG = "${General::swroot}/dns/rpz/reload.flag";
>>>>>>> +
>>>>>>> +# Configuration file for all available zonefiles
>>>>>>> +# Format: index, name (unique), enabled (on/off), URL, remark
>>>>>>> +my $ZONEFILES_CONF = "${General::swroot}/dns/rpz/zonefiles.conf";
>>>>>>> +
>>>>>>> +# Configuration file for custom lists
>>>>>>> +# IDs: 0=allowlist, 1=blocklist, 2=options (allow/block enabled)
>>>>>>> +my $CUSTOMLISTS_CONF = "${General::swroot}/dns/rpz/customlists.conf";
>>>>>>> +
>>>>>>> +# Export custom lists to rpz-config
>>>>>>> +my $RPZ_ALLOWLIST = "${General::swroot}/dns/rpz/allowlist";
>>>>>>> +my $RPZ_BLOCKLIST = "${General::swroot}/dns/rpz/blocklist";
>>>>>>> +
>>>>>>> +
>>>>>>> +### Preparation ###
>>>>>>> +
>>>>>>> +# Create missing config files
>>>>>>> +unless(-f $ZONEFILES_CONF) { &General::system('touch', "$ZONEFILES_CONF"); }
>>>>>>> +unless(-f $CUSTOMLISTS_CONF) { &General::system('touch', "$CUSTOMLISTS_CONF"); }
>>>>>>> +
>>>>>>> +
>>>>>>> +## Global gui data
>>>>>>> +my $errormessage = "";
>>>>>>> +
>>>>>>> +## Global configuration data
>>>>>>> +my %zonefiles = ();
>>>>>>> +my %customlists = ();
>>>>>>> +&_zonefiles_load();
>>>>>>> +&_customlists_load();
>>>>>>> +
>>>>>>> +## Global CGI form data
>>>>>>> +my %cgiparams = ();
>>>>>>> +&Header::getcgihash(\%cgiparams);
>>>>>>> +
>>>>>>> +my $action = $cgiparams{'ACTION'} // 'NONE';
>>>>>>> +my $action_key = $cgiparams{'KEY'} // ''; # entry being edited, empty = none/new
>>>>>>> +
>>>>>>> +
>>>>>>> +###--- Process form actions ---###
>>>>>>> +
>>>>>>> +# Zonefiles action: Check whether the requested entry exists
>>>>>>> +if((substr($action, 0, 3) eq 'ZF_') && ($action_key)) {
>>>>>>> + unless(defined $zonefiles{$action_key}) {
>>>>>>> + $errormessage = &_rpz_error_tr(204, $action_key);
>>>>>>> + $action = 'NONE';
>>>>>>> + }
>>>>>>> +}
>>>>>>> +
>>>>>>> +## Perform actions
>>>>>>> +if($action eq 'ZF_SAVE') { ## Save new or modified zonefiles entry
>>>>>>> + if(&_action_zf_save()) {
>>>>>>> + $action = 'NONE'; # success, return to main page
>>>>>>> + &_http_prg_redirect();
>>>>>>> + } else {
>>>>>>> + $action = 'ZF_EDIT'; # error occured, keep editing
>>>>>>> + }
>>>>>>> +
>>>>>>> +} elsif($action eq 'ZF_TOGGLE') { ## Toggle on/off
>>>>>>> + if(&_action_zf_toggle()) {
>>>>>>> + $action = 'NONE';
>>>>>>> + &_http_prg_redirect();
>>>>>>> + }
>>>>>>> +
>>>>>>> +} elsif($action eq 'ZF_REMOVE') { ## Remove entry
>>>>>>> + if(&_action_zf_remove()) {
>>>>>>> + $action = 'NONE';
>>>>>>> + &_http_prg_redirect();
>>>>>>> + }
>>>>>>> +
>>>>>>> +} elsif($action eq 'CL_SAVE') { ## Save custom lists
>>>>>>> + if(&_action_cl_save()) {
>>>>>>> + $action = 'NONE';
>>>>>>> + &_http_prg_redirect();
>>>>>>> + }
>>>>>>> +
>>>>>>> +} elsif($action eq 'RPZ_RELOAD') { ## Reload dns configuration
>>>>>>> + if(&_action_rpz_reload()) {
>>>>>>> + $action = 'NONE';
>>>>>>> + &_http_prg_redirect();
>>>>>>> + }
>>>>>>> +
>>>>>>> +} elsif($action eq 'UNB_RESTART') { ## Restart unbound service
>>>>>>> + if(&_action_unb_restart()) {
>>>>>>> + $action = 'NONE';
>>>>>>> + &_http_prg_redirect();
>>>>>>> + }
>>>>>>> +
>>>>>>> +}
>>>>>>> +
>>>>>>> +
>>>>>>> +###--- Start GUI ---###
>>>>>>> +
>>>>>>> +## Start http output
>>>>>>> +&Header::showhttpheaders();
>>>>>>> +
>>>>>>> +# Start HTML
>>>>>>> +&Header::openpage($Lang::tr{'rpz'}, 1, $extraHead);
>>>>>>> +&Header::openbigbox('100%', 'left', '');
>>>>>>> +
>>>>>>> +# Show error messages
>>>>>>> +if($errormessage) {
>>>>>>> + &_print_message($errormessage);
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Handle zonefile add/edit mode
>>>>>>> +if($action eq "ZF_EDIT") {
>>>>>>> + &_print_zonefile_editor();
>>>>>>> +
>>>>>>> + # Finalize page and exit cleanly
>>>>>>> + &Header::closebigbox();
>>>>>>> + &Header::closepage();
>>>>>>> + exit(0);
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Show gui elements
>>>>>>> +&_print_zonefiles();
>>>>>>> +&_print_customlists();
>>>>>>> +&_print_gui_extras();
>>>>>>> +
>>>>>>> +&Header::closebigbox();
>>>>>>> +&Header::closepage();
>>>>>>> +
>>>>>>> +###--- End of GUI ---###
>>>>>>> +
>>>>>>> +
>>>>>>> +###--- Internal configuration file functions ---###
>>>>>>> +
>>>>>>> +# Load all available zonefiles from rpz-config and the internal configuration
>>>>>>> +sub _zonefiles_load {
>>>>>>> + # Clean start
>>>>>>> + %zonefiles = ();
>>>>>>> +
>>>>>>> + # Source 1: Get the currently enabled zonefiles from rpz-config (expected format [name]=[URL])
>>>>>>> + my @enabled_files = &General::system_output('/usr/sbin/rpz-config', 'list');
>>>>>>> +
>>>>>>> + foreach my $row (@enabled_files) {
>>>>>>> + chomp($row);
>>>>>>> +
>>>>>>> + # Use regex instead of split() to skip non-matching lines
>>>>>>> + next unless($row =~ /^(\w+)=(.+)$/);
>>>>>>> + my ($name, $url) = ($1, $2);
>>>>>>> +
>>>>>>> + # Unique names are already guaranteed by rpz-config
>>>>>>> + if(&_rpz_validate_zonefile($name, $url, '', 0) == 0) {
>>>>>>> + # Populate global data hash, mark all found entries as enabled
>>>>>>> + my %entry = ('enabled' => 'on',
>>>>>>> + 'url' => $url,
>>>>>>> + 'remark' => $Lang::tr{'rpz zf imported'});
>>>>>>> +
>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>> + }
>>>>>>> + }
>>>>>>> +
>>>>>>> + # Source 2: Get additional data and disabled entries from configuration file
>>>>>>> + my %configured_files = ();
>>>>>>> + &General::readhasharray($ZONEFILES_CONF, \%configured_files);
>>>>>>> +
>>>>>>> + foreach my $row (values (%configured_files)) {
>>>>>>> + my ($name, $enabled, $url, $remark) = @$row;
>>>>>>> + $remark //= "";
>>>>>>> +
>>>>>>> + next unless($name);
>>>>>>> +
>>>>>>> + # Check whether this row belongs to an entry already imported from rpz-config
>>>>>>> + if(defined $zonefiles{$name}) {
>>>>>>> + # Existing entry, only merge additional data
>>>>>>> + $zonefiles{$name}{'remark'} = $remark;
>>>>>>> + } else {
>>>>>>> + # Skip entry if it is marked as enabled but not found by rpz-config. It was then deleted manually
>>>>>>> + if($enabled ne 'on') {
>>>>>>> + # Populate global data hash
>>>>>>> + my %entry = ('enabled' => 'off',
>>>>>>> + 'url' => $url // "",
>>>>>>> + 'remark' => $remark);
>>>>>>> +
>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>> + }
>>>>>>> + }
>>>>>>> + }
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Save internal zonefiles configuration
>>>>>>> +sub _zonefiles_save_conf {
>>>>>>> + my $index = 0;
>>>>>>> + my %export = ();
>>>>>>> +
>>>>>>> + # Loop trough all zonefiles and create "hasharray" type export
>>>>>>> + foreach my $name (keys %zonefiles) {
>>>>>>> + my @entry = ($name,
>>>>>>> + $zonefiles{$name}{'enabled'},
>>>>>>> + $zonefiles{$name}{'url'},
>>>>>>> + $zonefiles{$name}{'remark'});
>>>>>>> +
>>>>>>> + $export{$index++} = \@entry;
>>>>>>> + }
>>>>>>> +
>>>>>>> + &General::writehasharray($ZONEFILES_CONF, \%export);
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Load custom lists from rpz-config and the internal configuration
>>>>>>> +sub _customlists_load {
>>>>>>> + # Clean start
>>>>>>> + %customlists = ();
>>>>>>> +
>>>>>>> + # Load configuration file
>>>>>>> + my %lists_conf = ();
>>>>>>> + &General::readhasharray($CUSTOMLISTS_CONF, \%lists_conf);
>>>>>>> +
>>>>>>> + # Get list options, enabled by default to start import
>>>>>>> + $customlists{'allow'}{'enabled'} = $lists_conf{2}[0] // 'on';
>>>>>>> + $customlists{'block'}{'enabled'} = $lists_conf{2}[1] // 'on';
>>>>>>> +
>>>>>>> + # Import enabled list from rpz-config, otherwise retrieve stored or empty list from configuration file
>>>>>>> + if($customlists{'allow'}{'enabled'} eq 'on') {
>>>>>>> + &_customlist_import('allow', $RPZ_ALLOWLIST);
>>>>>>> + } else {
>>>>>>> + $customlists{'allow'}{'list'} = $lists_conf{0} // [];
>>>>>>> + }
>>>>>>> + if($customlists{'block'}{'enabled'} eq 'on') {
>>>>>>> + &_customlist_import('block', $RPZ_BLOCKLIST);
>>>>>>> + } else {
>>>>>>> + $customlists{'block'}{'list'} = $lists_conf{1} // [];
>>>>>>> + }
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Save internal custom lists configuration
>>>>>>> +sub _customlists_save_conf {
>>>>>>> + my %export = ();
>>>>>>> +
>>>>>>> + # Match IDs with import function
>>>>>>> + $export{0} = $customlists{'allow'}{'list'};
>>>>>>> + $export{1} = $customlists{'block'}{'list'};
>>>>>>> + $export{2} = [$customlists{'allow'}{'enabled'}, $customlists{'block'}{'enabled'}];
>>>>>>> +
>>>>>>> + &General::writehasharray($CUSTOMLISTS_CONF, \%export);
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Import a custom list from plain file, returns empty list if file is missing
>>>>>>> +sub _customlist_import {
>>>>>>> + my ($listname, $filename) = @_;
>>>>>>> + my @list = ();
>>>>>>> +
>>>>>>> + # File exists, load and check all lines
>>>>>>> + if(-f $filename) {
>>>>>>> + open(my $FH, '<', $filename) or die "Can't read $filename: $!";
>>>>>>> + while(my $line = <$FH>) {
>>>>>>> + chomp($line);
>>>>>>> + push(@list, $line);
>>>>>>> + }
>>>>>>> + close($FH);
>>>>>>> +
>>>>>>> + # Clean up imported data
>>>>>>> + &_rpz_validate_customlist(\@list, 1);
>>>>>>> + }
>>>>>>> +
>>>>>>> + $customlists{$listname}{'list'} = \@list;
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Export a custom list to plain file or clear file if list is disabled
>>>>>>> +sub _customlist_export {
>>>>>>> + my ($listname, $filename) = @_;
>>>>>>> + return unless(defined $customlists{$listname});
>>>>>>> +
>>>>>>> + # Write enabled domain list to file, otherwise save empty file
>>>>>>> + open(my $FH, '>', $filename) or die "Can't write $filename: $!";
>>>>>>> +
>>>>>>> + if($customlists{$listname}{'enabled'} eq 'on') {
>>>>>>> + foreach my $line (@{$customlists{$listname}{'list'}}) {
>>>>>>> + print $FH "$line\n";
>>>>>>> + }
>>>>>>> + } else {
>>>>>>> + print $FH "; Note: This list is currently disabled by $ENV{'SCRIPT_NAME'}\n";
>>>>>>> + }
>>>>>>> +
>>>>>>> + close($FH);
>>>>>>> +}
>>>>>>> +
>>>>>>> +
>>>>>>> +###--- Internal gui functions ---###
>>>>>>> +
>>>>>>> +# Show simple message box
>>>>>>> +sub _print_message {
>>>>>>> + my ($message, $title) = @_;
>>>>>>> + $title ||= $Lang::tr{'error messages'};
>>>>>>> +
>>>>>>> + &Header::openbox('100%', 'left', $title);
>>>>>>> + print "<span>$message</span>";
>>>>>>> + &Header::closebox();
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Show all zone files and related gui elements
>>>>>>> +sub _print_zonefiles {
>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf'});
>>>>>>> +
>>>>>>> + print <<END
>>>>>>> +<table class="tbl" width="100%">
>>>>>>> + <tr>
>>>>>>> + <th>$Lang::tr{'name'}</th>
>>>>>>> + <th>URL</th>
>>>>>>> + <th>$Lang::tr{'remark'}</th>
>>>>>>> + <th colspan="3">$Lang::tr{'action'}</th>
>>>>>>> + </tr>
>>>>>>> +END
>>>>>>> +;
>>>>>>> +
>>>>>>> + # Sort zonefiles by name and loop trough all entries
>>>>>>> + foreach my $name (sort keys %zonefiles) {
>>>>>>> +
>>>>>>> + # Toggle button label translation
>>>>>>> + my $toggle_tr = ($zonefiles{$name}{'enabled'} eq 'on') ? $Lang::tr{'click to disable'} : $Lang::tr{'click to enable'};
>>>>>>> +
>>>>>>> + print <<END
>>>>>>> + <tr>
>>>>>>> + <td>$name</td>
>>>>>>> + <td>$zonefiles{$name}{'url'}</td>
>>>>>>> + <td>$zonefiles{$name}{'remark'}</td>
>>>>>>> +
>>>>>>> + <td align="center" width="5%">
>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>> + <input type="hidden" name="ACTION" value="ZF_TOGGLE">
>>>>>>> + <input type="image" src="/images/$zonefiles{$name}{'enabled'}.gif" title="$toggle_tr" alt="$toggle_tr">
>>>>>>> + </form>
>>>>>>> + </td>
>>>>>>> + <td align="center" width="5%">
>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>> + <input type="hidden" name="ACTION" value="ZF_EDIT">
>>>>>>> + <input type="image" src="/images/edit.gif" title="$Lang::tr{'edit'}" alt="$Lang::tr{'edit'}">
>>>>>>> + </form>
>>>>>>> + </td>
>>>>>>> + <td align="center" width="5%">
>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>> + <input type="hidden" name="ACTION" value="ZF_REMOVE">
>>>>>>> + <input type="image" src="/images/delete.gif" title="$Lang::tr{'remove'}" alt="$Lang::tr{'remove'}">
>>>>>>> + </form>
>>>>>>> + </td>
>>>>>>> + </tr>
>>>>>>> +END
>>>>>>> +;
>>>>>>> + }
>>>>>>> +
>>>>>>> + # Disable reload button if not needed
>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>> +
>>>>>>> + print <<END
>>>>>>> +</table>
>>>>>>> +
>>>>>>> +<div class="right">
>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>> + <input type="hidden" name="KEY" value="">
>>>>>>> + <button type="submit" name="ACTION" value="ZF_EDIT">$Lang::tr{'add'}</button>
>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>> + </form>
>>>>>>> +</div>
>>>>>>> +END
>>>>>>> +;
>>>>>>> +
>>>>>>> + &Header::closebox();
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Show zonefiles entry editor
>>>>>>> +sub _print_zonefile_editor {
>>>>>>> +
>>>>>>> + # Key specified: Edit existing entry
>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>> + # Load data to be edited, but don't override already present values (allows user to edit after error)
>>>>>>> + $cgiparams{'ZF_NAME'} //= $action_key;
>>>>>>> + $cgiparams{'ZF_URL'} //= $zonefiles{$action_key}{'url'};
>>>>>>> + $cgiparams{'ZF_REMARK'} //= $zonefiles{$action_key}{'remark'};
>>>>>>> + }
>>>>>>> +
>>>>>>> + # Fallback to empty form
>>>>>>> + $cgiparams{'ZF_NAME'} //= "";
>>>>>>> + $cgiparams{'ZF_URL'} //= "";
>>>>>>> + $cgiparams{'ZF_REMARK'} //= "";
>>>>>>> +
>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf editor'});
>>>>>>> +
>>>>>>> + print <<END
>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>> +<input type="hidden" name="KEY" value="$action_key">
>>>>>>> +<table width="100%">
>>>>>>> + <tr>
>>>>>>> + <td width="20%">$Lang::tr{'name'}: <img src="/blob.gif" alt="*"></td>
>>>>>>> + <td><input type="text" name="ZF_NAME" value="$cgiparams{'ZF_NAME'}" size="40" maxlength="32" title="$Lang::tr{'rpz zf remark info'}" pattern="[a-zA-Z0-9_]{1,32}" required></td>
>>>>>>> + </tr>
>>>>>>> + <tr>
>>>>>>> + <td width="20%">URL: <img src="/blob.gif" alt="*"></td>
>>>>>>> + <td><input type="url" name="ZF_URL" value="$cgiparams{'ZF_URL'}" size="40" maxlength="128" required></td>
>>>>>>> + </tr>
>>>>>>> + <tr>
>>>>>>> + <td width="20%">$Lang::tr{'remark'}:</td>
>>>>>>> + <td><input type="text" name="ZF_REMARK" value="$cgiparams{'ZF_REMARK'}" size="40" maxlength="32"></td>
>>>>>>> + </tr>
>>>>>>> + <tr>
>>>>>>> + <td colspan="2"><hr></td>
>>>>>>> + </tr>
>>>>>>> + <tr>
>>>>>>> + <td width="55%"><img src="/blob.gif" alt="*"> $Lang::tr{'required field'}</td>
>>>>>>> + <td align="right"><button type="submit" name="ACTION" value="ZF_SAVE">$Lang::tr{'save'}</button></td>
>>>>>>> + </tr>
>>>>>>> +</table>
>>>>>>> +</form>
>>>>>>> +
>>>>>>> +<div class="right">
>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>> + <button type="submit" name="ACTION" value="NONE">$Lang::tr{'back'}</button>
>>>>>>> + </form>
>>>>>>> +</div>
>>>>>>> +END
>>>>>>> +;
>>>>>>> +
>>>>>>> + &Header::closebox();
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Show custom allow/block files and related gui elements
>>>>>>> +sub _print_customlists {
>>>>>>> +
>>>>>>> + # Load lists from config, unless they are currently being edited
>>>>>>> + if($action ne 'CL_SAVE') {
>>>>>>> + $cgiparams{'ALLOW_LIST'} = join("\n", @{$customlists{'allow'}{'list'}});
>>>>>>> + $cgiparams{'BLOCK_LIST'} = join("\n", @{$customlists{'block'}{'list'}});
>>>>>>> +
>>>>>>> + $cgiparams{'ALLOW_ENABLED'} = ($customlists{'allow'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>> + $cgiparams{'BLOCK_ENABLED'} = ($customlists{'block'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>> + }
>>>>>>> +
>>>>>>> + # Fallback to empty form
>>>>>>> + $cgiparams{'ALLOW_LIST'} //= "";
>>>>>>> + $cgiparams{'BLOCK_LIST'} //= "";
>>>>>>> +
>>>>>>> + # HTML checkboxes, unchecked = no or undef value in POST data
>>>>>>> + my %checked = ();
>>>>>>> + $checked{'ALLOW_ENABLED'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? " checked" : "";
>>>>>>> + $checked{'BLOCK_ENABLED'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? " checked" : "";
>>>>>>> +
>>>>>>> + # Disable reload button if not needed
>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>> +
>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz cl'});
>>>>>>> +
>>>>>>> + print <<END
>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>> +<table width="100%">
>>>>>>> + <tr>
>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl allow'}</b><br>$Lang::tr{'rpz cl allow info'}</td>
>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl block'}</b><br>$Lang::tr{'rpz cl block info'}</td>
>>>>>>> + </tr>
>>>>>>> + <tr>
>>>>>>> + <td colspan="2"><textarea name="ALLOW_LIST" class="domainlist" cols="45">
>>>>>>> +$cgiparams{'ALLOW_LIST'}</textarea></td>
>>>>>>> + <td colspan="2"><textarea name="BLOCK_LIST" class="domainlist" cols="45">
>>>>>>> +$cgiparams{'BLOCK_LIST'}</textarea></td>
>>>>>>> + </tr>
>>>>>>> + <tr>
>>>>>>> + <td><label for="allow_enabled">$Lang::tr{'rpz cl allow enable'}</label></td>
>>>>>>> + <td width="15%"><input type="checkbox" name="ALLOW_ENABLED" id="allow_enabled"$checked{'ALLOW_ENABLED'}></td>
>>>>>>> + <td><label for="block_enabled">$Lang::tr{'rpz cl block enable'}</label></td>
>>>>>>> + <td width="15%"><input type="checkbox" name="BLOCK_ENABLED" id="block_enabled"$checked{'BLOCK_ENABLED'}></td>
>>>>>>> + </tr>
>>>>>>> + <tr>
>>>>>>> + <td colspan="4"><hr></td>
>>>>>>> + </tr>
>>>>>>> + <tr>
>>>>>>> + <td align="right" colspan="4">
>>>>>>> + <button type="submit" name="ACTION" value="CL_SAVE">$Lang::tr{'save'}</button>
>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>> + </td>
>>>>>>> +</table>
>>>>>>> +</form>
>>>>>>> +END
>>>>>>> +;
>>>>>>> +
>>>>>>> + &Header::closebox();
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Output javascript and extra gui elements
>>>>>>> +sub _print_gui_extras {
>>>>>>> +
>>>>>>> + # Apply/Restart button modifier key handler
>>>>>>> + if(&_rpz_needs_reload()) {
>>>>>>> + print <<END
>>>>>>> +<script>
>>>>>>> + // Commit modifier key handler
>>>>>>> + (function(jq, document) {
>>>>>>> + var keyEventsOn = false; // Keyboard events attached
>>>>>>> + var keyModify = false; // Modifier key pressed
>>>>>>> + var mouseHover = false; // Mouse over commit button
>>>>>>> + var btnModified = false; // Button modified to "Restart"
>>>>>>> +
>>>>>>> + // Document-level key events, enable only while cursor is over button
>>>>>>> + function attachKeyEvents() {
>>>>>>> + if(keyEventsOn) {
>>>>>>> + return;
>>>>>>> + }
>>>>>>> + keyEventsOn = true;
>>>>>>> +
>>>>>>> + jq(document).on("keydown.rpz", function(event) {
>>>>>>> + if((!keyModify) && event.shiftKey) {
>>>>>>> + keyModify = true;
>>>>>>> + handleModify();
>>>>>>> + }
>>>>>>> + });
>>>>>>> + jq(document).on("keyup.rpz", function(event) {
>>>>>>> + if(keyModify && (!event.shiftKey)) {
>>>>>>> + keyModify = false;
>>>>>>> + handleModify();
>>>>>>> + }
>>>>>>> + });
>>>>>>> + }
>>>>>>> + function removeKeyEvents() {
>>>>>>> + keyModify = false;
>>>>>>> + if(keyEventsOn) {
>>>>>>> + jq(document).off("keydown.rpz keyup.rpz");
>>>>>>> + keyEventsOn = false;
>>>>>>> + }
>>>>>>> + }
>>>>>>> +
>>>>>>> + // Attach mouse hover events to commit buttons
>>>>>>> + function attachMouseEvents() {
>>>>>>> + jq("button.commit").on("mouseenter", function(event) {
>>>>>>> + if(!mouseHover) {
>>>>>>> + mouseHover = true;
>>>>>>> + attachKeyEvents();
>>>>>>> + // Handle already pressed key
>>>>>>> + keyModify = !!(event.shiftKey);
>>>>>>> + handleModify();
>>>>>>> + }
>>>>>>> + });
>>>>>>> +
>>>>>>> + // Cursor moved away: Disable key listener to minimize events
>>>>>>> + jq("button.commit").on("mouseleave", function() {
>>>>>>> + if(mouseHover) {
>>>>>>> + mouseHover = false;
>>>>>>> + removeKeyEvents();
>>>>>>> + handleModify();
>>>>>>> + }
>>>>>>> + });
>>>>>>> + }
>>>>>>> +
>>>>>>> + // Modify commit button
>>>>>>> + function handleModify() {
>>>>>>> + let modify = mouseHover && keyModify;
>>>>>>> + if(btnModified != modify) {
>>>>>>> + if(modify) {
>>>>>>> + jq("button.commit").text("$Lang::tr{'restart'}").val("UNB_RESTART");
>>>>>>> + } else {
>>>>>>> + jq("button.commit").text("$Lang::tr{'rpz apply'}").val("RPZ_RELOAD");
>>>>>>> + }
>>>>>>> + btnModified = modify;
>>>>>>> + }
>>>>>>> + }
>>>>>>> +
>>>>>>> + // jQuery DOM ready
>>>>>>> + jq(function() {
>>>>>>> + attachMouseEvents();
>>>>>>> + });
>>>>>>> + })(jQuery, document);
>>>>>>> +</script>
>>>>>>> +END
>>>>>>> +;
>>>>>>> + } # End of modifier key handler
>>>>>>> +
>>>>>>> +}
>>>>>>> +
>>>>>>> +
>>>>>>> +###--- Internal action processing functions ---###
>>>>>>> +
>>>>>>> +# Toggle zonefile on/off
>>>>>>> +sub _action_zf_toggle {
>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>> +
>>>>>>> + my $result = 0;
>>>>>>> + my $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>> +
>>>>>>> + # Perform toggle action
>>>>>>> + if($enabled eq 'on') {
>>>>>>> + $enabled = 'off';
>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>> + } else {
>>>>>>> + $enabled = 'on';
>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $action_key, $zonefiles{$action_key}{'url'}, '--no-reload');
>>>>>>> + }
>>>>>>> +
>>>>>>> + # Check for errors, request service reload on success
>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>> +
>>>>>>> + # Save changes
>>>>>>> + $zonefiles{$action_key}{'enabled'} = $enabled;
>>>>>>> + &_zonefiles_save_conf();
>>>>>>> +
>>>>>>> + return 1;
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Remove zonefile
>>>>>>> +sub _action_zf_remove {
>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>> +
>>>>>>> + # Remove from rpz-config if currently active
>>>>>>> + if($zonefiles{$action_key}{'enabled'} eq 'on') {
>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>> +
>>>>>>> + # Check for errors, request service reload on success
>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>> + }
>>>>>>> +
>>>>>>> + # Remove from data hash and save changes
>>>>>>> + delete $zonefiles{$action_key};
>>>>>>> + &_zonefiles_save_conf();
>>>>>>> +
>>>>>>> + # Clear action_key, as the entry is now removed entirely
>>>>>>> + $action_key = "";
>>>>>>> +
>>>>>>> + return 1;
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Create or update zonefile entry
>>>>>>> +# Returns undef if gui needs to stay in editor mode
>>>>>>> +sub _action_zf_save {
>>>>>>> + my $result = 0;
>>>>>>> +
>>>>>>> + my $name = $cgiparams{'ZF_NAME'} // "";
>>>>>>> + my $url = $cgiparams{'ZF_URL'} // "";
>>>>>>> + my $remark = $cgiparams{'ZF_REMARK'} // "";
>>>>>>> + my $enabled = 'on'; # Enable new entries by default
>>>>>>> +
>>>>>>> + # Note on variables:
>>>>>>> + # name = unique key, will be used to address the entry
>>>>>>> + # action_key = name of the entry being edited, empty for new entry
>>>>>>> +
>>>>>>> + # Only check for unique name if it changed
>>>>>>> + # (this also checks new entries because the action_key is empty in this case)
>>>>>>> + $result = &_rpz_validate_zonefile($name, $url, $remark, (lc($name) ne lc($action_key)));
>>>>>>> + return unless &_rpz_check_result($result, 0);
>>>>>>> +
>>>>>>> + # Edit existing entry: Determine what was changed
>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>> + # Name und URL remain unchanged, only save remark and finish
>>>>>>> + if(($name eq $action_key) && ($url eq $zonefiles{$action_key}{'url'})) {
>>>>>>> + $zonefiles{$action_key}{'remark'} = $remark;
>>>>>>> + &_zonefiles_save_conf();
>>>>>>> +
>>>>>>> + return 1;
>>>>>>> + }
>>>>>>> +
>>>>>>> + # Entry was changed and needs to be recreated, preserve status
>>>>>>> + $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>> +
>>>>>>> + # Remove from rpz-config
>>>>>>> + return unless &_action_zf_remove();
>>>>>>> + }
>>>>>>> +
>>>>>>> + # Add new entry to rpz-config
>>>>>>> + if($enabled eq 'on') {
>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $name, $url, '--no-reload');
>>>>>>> +
>>>>>>> + # Check for errors, request service reload on success
>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>> + }
>>>>>>> +
>>>>>>> + # Add to global data hash and save changes
>>>>>>> + my %entry = ('enabled' => $enabled,
>>>>>>> + 'url' => $url,
>>>>>>> + 'remark' => $remark);
>>>>>>> +
>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>> + &_zonefiles_save_conf();
>>>>>>> +
>>>>>>> + return 1;
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Save custom lists
>>>>>>> +sub _action_cl_save {
>>>>>>> + return unless((defined $cgiparams{'ALLOW_LIST'}) && (defined $cgiparams{'BLOCK_LIST'}));
>>>>>>> +
>>>>>>> + my $result = 0;
>>>>>>> +
>>>>>>> + my @allowlist = split(/\R/, $cgiparams{'ALLOW_LIST'});
>>>>>>> + my @blocklist = split(/\R/, $cgiparams{'BLOCK_LIST'});
>>>>>>> +
>>>>>>> + # Validate lists
>>>>>>> + $result = &_rpz_validate_customlist(\@allowlist);
>>>>>>> + if($result != 0) {
>>>>>>> + $errormessage = &_rpz_error_tr(202, $result);
>>>>>>> + return;
>>>>>>> + }
>>>>>>> + $result = &_rpz_validate_customlist(\@blocklist);
>>>>>>> + if($result != 0) {
>>>>>>> + $errormessage = &_rpz_error_tr(203, $result);
>>>>>>> + return;
>>>>>>> + }
>>>>>>> +
>>>>>>> + # Add to global data hash and save changes
>>>>>>> + $customlists{'allow'}{'list'} = \@allowlist;
>>>>>>> + $customlists{'block'}{'list'} = \@blocklist;
>>>>>>> + $customlists{'allow'}{'enabled'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? 'on' : 'off';
>>>>>>> + $customlists{'block'}{'enabled'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? 'on' : 'off';
>>>>>>> +
>>>>>>> + &_customlists_save_conf();
>>>>>>> + &_customlist_export('allow', $RPZ_ALLOWLIST);
>>>>>>> + &_customlist_export('block', $RPZ_BLOCKLIST);
>>>>>>> +
>>>>>>> + # Make new lists, request service reload on success
>>>>>>> + $result = &General::system('/usr/sbin/rpz-make', 'allowblock', '--no-reload');
>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>> +
>>>>>>> + return 1;
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Trigger rpz-config reload
>>>>>>> +sub _action_rpz_reload {
>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>> +
>>>>>>> + # Immediately clear flag to prevent multiple reloads
>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>> + }
>>>>>>> +
>>>>>>> + # Perform reload, recreate reload flag on error to enable retry
>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'reload');
>>>>>>> + if(not &_rpz_check_result($result, 0)) {
>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>> + return;
>>>>>>> + }
>>>>>>> +
>>>>>>> + return 1;
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Trigger unbound restart
>>>>>>> +sub _action_unb_restart {
>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>> +
>>>>>>> + # Immediately clear flag to prevent multiple restarts
>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>> + }
>>>>>>> +
>>>>>>> + # Perform restart, unboundctrl always exits zero
>>>>>>> + &General::system('/usr/local/bin/unboundctrl', 'restart');
>>>>>>> +
>>>>>>> + return 1;
>>>>>>> +}
>>>>>>> +
>>>>>>> +
>>>>>>> +###--- Internal rpz-config functions ---###
>>>>>>> +
>>>>>>> +# Translate rpz-config exitcodes and messages
>>>>>>> +# 100-199: rpz-config, 200-299: webgui
>>>>>>> +sub _rpz_error_tr {
>>>>>>> + my ($error, $append) = @_;
>>>>>>> + $append //= '';
>>>>>>> +
>>>>>>> + # Translate numeric exit codes
>>>>>>> + if(looks_like_number($error)) {
>>>>>>> + if(defined $Lang::tr{"rpz exitcode $error"}) {
>>>>>>> + $error = $Lang::tr{"rpz exitcode $error"};
>>>>>>> + }
>>>>>>> + }
>>>>>>> +
>>>>>>> + return "RPZ $Lang::tr{'error'}: $error" . &Header::escape($append);
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Check result of rpz-config system call, request reload on success
>>>>>>> +sub _rpz_check_result {
>>>>>>> + my ($result, $request_reload) = @_;
>>>>>>> + $request_reload //= 0;
>>>>>>> +
>>>>>>> + # exitcode 0 = success
>>>>>>> + if($result != 0) {
>>>>>>> + $errormessage = &_rpz_error_tr($result);
>>>>>>> + return;
>>>>>>> + }
>>>>>>> +
>>>>>>> + # Set reload flag
>>>>>>> + if($request_reload) {
>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>> + }
>>>>>>> +
>>>>>>> + return 1;
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Test whether reload flag is set
>>>>>>> +sub _rpz_needs_reload {
>>>>>>> + return (-f $RPZ_RELOAD_FLAG);
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Validate a zonefile entry, returns rpz-config exitcode on failure. Use _rpz_check_result to verify.
>>>>>>> +# unique = check for unique name
>>>>>>> +sub _rpz_validate_zonefile {
>>>>>>> + my ($name, $url, $remark, $unique) = @_;
>>>>>>> + $unique //= 1;
>>>>>>> +
>>>>>>> + unless($name =~ /^[a-zA-Z0-9_]{1,32}$/) {
>>>>>>> + return 101;
>>>>>>> + }
>>>>>>> + unless($url =~ /^[\w+\.:;\/\\&@#%?=\-~|!]{1,128}$/) {
>>>>>>> + return 105;
>>>>>>> + }
>>>>>>> + unless($remark =~ /^[\w \-()\.:;*\/\\?!&=]{0,32}$/) {
>>>>>>> + return 201;
>>>>>>> + }
>>>>>>> +
>>>>>>> + # Check against already existing names
>>>>>>> + if($unique) {
>>>>>>> + foreach my $existing (keys %zonefiles) {
>>>>>>> + if(lc($name) eq lc($existing)) {
>>>>>>> + return 104;
>>>>>>> + }
>>>>>>> + }
>>>>>>> + }
>>>>>>> +
>>>>>>> + return 0;
>>>>>>> +}
>>>>>>> +
>>>>>>> +# Validate a custom list, returns number of rejected line on failure. Check for non-zero results.
>>>>>>> +# listref = array reference, cleanup = remove invalid entries instead of returning an error
>>>>>>> +sub _rpz_validate_customlist {
>>>>>>> + my ($listref, $cleanup) = @_;
>>>>>>> + $cleanup //= 0;
>>>>>>> +
>>>>>>> + foreach my $index (reverse 0..$#{$listref}) {
>>>>>>> + my $row = @$listref[$index];
>>>>>>> + next unless($row); # Skip/allow empty lines
>>>>>>> +
>>>>>>> + # Reject/remove everything besides wildcard domains and remarks
>>>>>>> + if((not &General::validwildcarddomainname($row)) && (not $row =~ /^;[\w \-()\.:;*\/\\?!&=]*$/)) {
>>>>>>> + unless($cleanup) {
>>>>>>> + # +1 for user friendly line number and to ensure non-zero exitcode
>>>>>>> + return $index + 1;
>>>>>>> + }
>>>>>>> +
>>>>>>> + # Remove current row
>>>>>>> + splice(@$listref, $index, 1);
>>>>>>> + }
>>>>>>> + }
>>>>>>> +
>>>>>>> + return 0;
>>>>>>> +}
>>>>>>> +
>>>>>>> +
>>>>>>> +###--- Internal misc functions ---###
>>>>>>> +
>>>>>>> +# Send HTTP 303 redirect headers for post/request/get pattern
>>>>>>> +# (Must be sent before calling &Header::showhttpheaders())
>>>>>>> +sub _http_prg_redirect {
>>>>>>> + my $location = "https://$ENV{'SERVER_NAME'}:$ENV{'SERVER_PORT'}$ENV{'SCRIPT_NAME'}";
>>>>>>> + print "Status: 303 See Other\n";
>>>>>>> + print "Location: $location\n";
>>>>>>> +}
>>>>>>> diff --git a/lfs/rpz b/lfs/rpz
>>>>>>> new file mode 100644
>>>>>>> index 000000000..7ddbc38e5
>>>>>>> --- /dev/null
>>>>>>> +++ b/lfs/rpz
>>>>>>> @@ -0,0 +1,96 @@
>>>>>>> +###############################################################################
>>>>>>> +# #
>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>> +# #
>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>> +# #
>>>>>>> +###############################################################################
>>>>>>> +
>>>>>>> +###############################################################################
>>>>>>> +# Definitions
>>>>>>> +###############################################################################
>>>>>>> +
>>>>>>> +include Config
>>>>>>> +
>>>>>>> +SUMMARY = response policy zone - RPZ reputation system for unbound DNS
>>>>>>> +
>>>>>>> +VER = 1.0.0
>>>>>>> +
>>>>>>> +THISAPP = rpz-$(VER)
>>>>>>> +DIR_APP = $(DIR_SRC)/$(THISAPP)
>>>>>>> +TARGET = $(DIR_INFO)/$(THISAPP)
>>>>>>> +
>>>>>>> +PROG = rpz
>>>>>>> +PAK_VER = 18
>>>>>>> +
>>>>>>> +DEPS =
>>>>>>> +
>>>>>>> +SERVICES =
>>>>>>> +
>>>>>>> +###############################################################################
>>>>>>> +# Top-level Rules
>>>>>>> +###############################################################################
>>>>>>> +
>>>>>>> +install : $(TARGET)
>>>>>>> +
>>>>>>> +check :
>>>>>>> +
>>>>>>> +download :
>>>>>>> +
>>>>>>> +b2 :
>>>>>>> +
>>>>>>> +dist:
>>>>>>> + @$(PAK)
>>>>>>> +
>>>>>>> +###############################################################################
>>>>>>> +# Installation Details
>>>>>>> +###############################################################################
>>>>>>> +
>>>>>>> +$(TARGET) :
>>>>>>> + @$(PREBUILD)
>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>> +
>>>>>>> + # RPZ scripts
>>>>>>> + install --verbose --mode=755 \
>>>>>>> + $(DIR_CONF)/rpz/{rpz-config,rpz-metrics,rpz-sleep,rpz-make,rpz-functions} \
>>>>>>> + --target-directory=/usr/sbin
>>>>>>> +
>>>>>>> + # RPZ config files
>>>>>>> + mkdir -pv /etc/unbound/local.d
>>>>>>> + install --verbose --mode=644 --owner=nobody --group=nobody \
>>>>>>> + $(DIR_CONF)/rpz/00-rpz.conf \
>>>>>>> + --target-directory=/etc/unbound/local.d
>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/local.d
>>>>>>> +
>>>>>>> + # RPZ custom list files for allow and block
>>>>>>> + mkdir -pv /var/ipfire/dns/rpz
>>>>>>> + touch /var/ipfire/dns/rpz/{allowlist,blocklist}
>>>>>>> + chown --verbose --recursive nobody:nobody /var/ipfire/dns/rpz
>>>>>>> +
>>>>>>> + # RPZ zone files
>>>>>>> + # create empty RPZ config file to avoid a unbound config error
>>>>>>> + mkdir -pv /etc/unbound/zonefiles
>>>>>>> + touch /etc/unbound/zonefiles/allow.rpz
>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/zonefiles
>>>>>>> +
>>>>>>> + # Install addon-specific language-files
>>>>>>> + install --verbose --mode=004 $(DIR_CONF)/rpz/rpz.*.pl \
>>>>>>> + --target-directory=/var/ipfire/addon-lang
>>>>>>> +
>>>>>>> + # Install backup definition
>>>>>>> + cp -vf $(DIR_CONF)/backup/includes/rpz /var/ipfire/backup/addons/includes/rpz
>>>>>>> +
>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>> + @$(POSTBUILD)
>>>>>>> diff --git a/make.sh b/make.sh
>>>>>>> index 827ea9e77..a77535b13 100755
>>>>>>> --- a/make.sh
>>>>>>> +++ b/make.sh
>>>>>>> @@ -390,7 +390,7 @@ prepareenv() {
>>>>>>> if [ "${free_space}" -lt "${required_space}" ]; then
>>>>>>> # Add any consumed space
>>>>>>> while read -r consumed_space path; do
>>>>>>> - (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>> + (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>> done <<< "$(du --summarize --bytes "${BUILD_DIR}" "${IMAGES_DIR}" "${LOG_DIR}" 2>/dev/null)"
>>>>>>> fi
>>>>>>>
>>>>>>> @@ -2087,6 +2087,7 @@ build_system() {
>>>>>>> lfsmake2 btrfs-progs
>>>>>>> lfsmake2 inotify-tools
>>>>>>> lfsmake2 grub-btrfs
>>>>>>> + lfsmake2 rpz
>>>>>>>
>>>>>>> lfsmake2 linux
>>>>>>> lfsmake2 rtl8812au
>>>>>>> diff --git a/src/paks/rpz/install.sh b/src/paks/rpz/install.sh
>>>>>>> new file mode 100644
>>>>>>> index 000000000..ef99bf742
>>>>>>> --- /dev/null
>>>>>>> +++ b/src/paks/rpz/install.sh
>>>>>>> @@ -0,0 +1,36 @@
>>>>>>> +#!/bin/bash
>>>>>>> +###############################################################################
>>>>>>> +# #
>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>> +# #
>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>> +# #
>>>>>>> +###############################################################################
>>>>>>> +#
>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>> +extract_files
>>>>>>> +restore_backup ${NAME}
>>>>>>> +
>>>>>>> +# fix user created files
>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>> + /etc/unbound/zonefiles \
>>>>>>> + /etc/unbound/local.d
>>>>>>> +
>>>>>>> +# Update Language cache
>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>> +
>>>>>>> +# restart unbound to load config file
>>>>>>> +/etc/init.d/unbound restart
>>>>>>> diff --git a/src/paks/rpz/uninstall.sh b/src/paks/rpz/uninstall.sh
>>>>>>> new file mode 100644
>>>>>>> index 000000000..e11427df3
>>>>>>> --- /dev/null
>>>>>>> +++ b/src/paks/rpz/uninstall.sh
>>>>>>> @@ -0,0 +1,38 @@
>>>>>>> +#!/bin/bash
>>>>>>> +###############################################################################
>>>>>>> +# #
>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>> +# #
>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>> +# #
>>>>>>> +###############################################################################
>>>>>>> +#
>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>> +
>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>> +/etc/init.d/unbound stop
>>>>>>> +
>>>>>>> +make_backup ${NAME}
>>>>>>> +remove_files
>>>>>>> +
>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>> +# "[1723428668] unbound-control[17117:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>> +
>>>>>>> +# Update Language cache
>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>> +
>>>>>>> +# start unbound to load unbound config file
>>>>>>> +/etc/init.d/unbound start
>>>>>>> diff --git a/src/paks/rpz/update.sh b/src/paks/rpz/update.sh
>>>>>>> new file mode 100644
>>>>>>> index 000000000..9bc340bc6
>>>>>>> --- /dev/null
>>>>>>> +++ b/src/paks/rpz/update.sh
>>>>>>> @@ -0,0 +1,52 @@
>>>>>>> +#!/bin/bash
>>>>>>> +###############################################################################
>>>>>>> +# #
>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>> +# #
>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>> +# #
>>>>>>> +###############################################################################
>>>>>>> +#
>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>> +
>>>>>>> +# from update.sh
>>>>>>> +extract_backup_includes
>>>>>>> +
>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>> +/etc/init.d/unbound stop
>>>>>>> +
>>>>>>> +# from uninstall.sh
>>>>>>> +make_backup ${NAME}
>>>>>>> +remove_files
>>>>>>> +
>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>> +# "unbound-control[nn:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>> +
>>>>>>> +# from install.sh
>>>>>>> +extract_files
>>>>>>> +restore_backup ${NAME}
>>>>>>> +
>>>>>>> +# fix user created files
>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>> + /etc/unbound/zonefiles \
>>>>>>> + /etc/unbound/local.d
>>>>>>> +
>>>>>>> +# Update Language cache
>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>> +
>>>>>>> +# restart unbound to load config files
>>>>>>> +/etc/init.d/unbound start
>>>>>>> --
>>>>>>> 2.39.5
>>>>>>>
>>>>> Jon
>>>>>
>>>>>
>>>>> --
>>>>> Jon Murphy
>>>>> jon.murphy@ipfire.org
>>> Jon
>>>
>>>
>>> --
>>> Jon Murphy
>>> jon.murphy@ipfire.org
>
>
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
2025-03-19 2:58 ` Jon Murphy
@ 2025-03-19 10:35 ` Michael Tremer
2025-03-19 18:22 ` Jon Murphy
[not found] ` <afcb2a99-1281-43e3-bd3d-d915024683f6@ipfire.org>
0 siblings, 2 replies; 30+ messages in thread
From: Michael Tremer @ 2025-03-19 10:35 UTC (permalink / raw)
To: Jon Murphy; +Cc: development
Hello Jon,
Where in the code is this implemented? I cannot find anything like this:
Unbound loads the entire file into memory and then starts parsing it. The only special treatment there is is to check whether the first line is a valid zone entry. It does not even have to be a SOA record.
https://git.ipfire.org/?p=thirdparty/unbound.git;a=blob;f=services/authzone.c;hb=30b9cb5f813003d0a2b1c2e678652396615b1b7d#l1188
I am also concerned that Unbound will not be able to support an upstream proxy for any downloads. The caching situation is also unclear for me, so I believe that we will be looking at writing a custom downloader that implements all these things.
-Michael
> On 19 Mar 2025, at 02:58, Jon Murphy <jon.murphy@ipfire.org> wrote:
>
> Michael,
>
> > The emphasis is on the repeated downloads of the same list. That is
> > what cannot happen.
>
> The Unbound RPZ code, as installed within IPFire, watches for a change
> in the SOA line of each RPZ file. This is an example of the first few
> lines for every RPZ file.
>
> $TTL 300
> @ SOA localhost. root.localhost. 1742298960 43200 3600 86400 300
> NS localhost.
> ;
> ; Title: HaGeZi's Pop-Up Ads DNS Blocklist
> ; Description: Blocks annoying and malicious pop-up ads.
>
> If the SOA serial number changes (e.g. the 1742298960), then Unbound RPZ
> code does its thing and downloads. Otherwise there is no download.
>
> > So there has to be a way to ensure that we won’t download a list again
> > unless it has actually changed.
>
> This should do what you want but I may be missing your point.
>
> > DNS has a builtin functionality called AXFR. It simply does the job
> > for you. I was just wondering whether that was not being used.
>
> I need to read about AXFR/IXFR and learn a little more.
>
> Jon
>
> On 3/17/25 5:35 AM, Michael Tremer wrote:
>> Good Morning Jon,
>>
>>> On 16 Mar 2025, at 17:00, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>
>>> Michael,
>>>
>>> I was reading through you response again an I want to understand this post:
>>>
>>>> I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>> So if RPZ doesn't use HTTPS, what is it using? I am missing a key point here.
>> The emphasis is on the repeated downloads of the same list. That is what cannot happen.
>>
>> Although it might not affect a lot of people in our general user-base, there are some that have a metered connection and will pay for data by volume. Some of the lists I looked at are just under 20 MiB. Therefore we need to keep any traffic down to a minimum. The second reason is that we have a lot of firewalls out there. Not all of them will enable this feature and all of the lists, but even if it is a good chunk, we will generate terabytes of traffic which put load on the infrastructure and will cost money. It simply is not what we want to do, regardless of self-hosting those lists and pulling them from somewhere else.
>>
>> So there has to be a way to ensure that we won’t download a list again unless it has actually changed.
>>
>> DNS has a builtin functionality called AXFR. It simply does the job for you. I was just wondering whether that was not being used.
>>
>> HTTPS is an option because that is simply what we use elsewhere, but extra functionality will have to be built for it.
>>
>> -Michael
>>
>>> Jon
>>>
>>>
>>> On 2/13/25 3:34 PM, jon wrote:
>>>> Michael,
>>>>
>>>> I’ve read through your comments a few times and I ended up with many more questions.
>>>>
>>>>
>>>>> What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>> To me the efforts to get new code accepted seem to have changed and it seemed easier in the past. In the past I made the Core Team aware via the Dev Mailing List and wrote a simple two or three paragraphs of "What is it? / What is the value? / Here is the code"
>>>>
>>>>
>>>> So in an effort to move forward: How exactly is something presented to the Core Team?
>>>>
>>>> Is there an example of a recent effort that was presented that I can see as a sample? (This type of info can also be added to the Wiki)
>>>>
>>>> I understand you want it this way, but I don’t know what exactly is needed. Please be specific.
>>>>
>>>>
>>>> Jon
>>>>
>>>> PS - I am not ignoring your other comments, I am just trying to move forward and keep things simple.
>>>>
>>>>
>>>>
>>>>> On Feb 8, 2025, at 1:27 PM, Michael Tremer <michael.tremer@ipfire.org> wrote:
>>>>>
>>>>> Hello Jon,
>>>>>
>>>>> Thanks for your reply. And good that you are copying everyone into this conversation.
>>>>>
>>>>>> On 8 Feb 2025, at 18:41, jon <jon.murphy@ipfire.org> wrote:
>>>>>>
>>>>>> Michael,
>>>>>>
>>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort
>>>>>> Yes, this has been a separate effort (a very public separate effort). Yes, as you pointed this out early on with the "proof-of-concept" and then my request for people to help test RPZ. Nothing was hidden.
>>>>>>
>>>>>> This was done because you (and maybe others) did not have the time and I wanted to help and because I needed assistance with RPZ. I tried my best to do this without bothering you.
>>>>> I don’t that it is accurate that nobody wanted to help on this. The list was always open - although not every email has been replied to swiftly it is also your responsibility to raise a question again if it was missed. People here have open ears.
>>>>>
>>>>> It was also stated on this very list on in our documentation that working on something without involving the core team is a risky undertaking. Of course IPFire is free software and so everyone is free to fork if they wish to do so.
>>>>>
>>>>>>> and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls.
>>>>>> You were aware many steps along the way. See your email on July 28, 2024, August 15, 2024, September 30, 2024, December 23, 2024, and January 16. My attempts to get the team involved were met with "things are busy" and sometimes silence. (Yes, I get it, people are busy.)
>>>>>>
>>>>>> You and Adolf, Leo, Erik and Bernhard have been aware since the beginning. You mention you were aware of the "proof-of-concept". If you include those beginning posts, since Sep 2023.
>>>>> Yes, I am aware of a proof-of-concept that I have been running myself for a long time. I am also aware of the efforts that you have been taking.
>>>>>
>>>>> Yet I don’t think there has ever been any joint effort, or am I seeing that wrong?
>>>>>
>>>>>>> This has not been discussed . . . on our calls.
>>>>>> On the July 28th you stated:
>>>>>> "We have talked about RPZ many times on the monthly call since the URL filter feature is falling more and more out of fashion. I think there is also many posts about this on the forum."
>>>>>>
>>>>>> Please don’t insult me again by stating "you know what I mean".
>>>>>>
>>>>>> And it has been discussed but not documented in the Monthly Meeting notes.
>>>>> I am not at all insulting you. I don’t want to take this down to a personal level at all. This is a public mailing list and people who read this don’t need to listen to an argument we are having. They are here for the tech inside IPFire.
>>>>>
>>>>> When I wrote that it has not been discussed that does not mean that we have not been touching on the topic. We have been talking about lots of things on the calls, the weather, politics, how our pets are. None of that makes it to the logs. What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>>>
>>>>>>> Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation.
>>>>>> Regular conversation on the Dev Mailing list is many times met with silence. I get it, people are busy.
>>>>>>
>>>>>> And regular two-way conversation doesn’t happen on the list. At least not with me. I’d be happy to point out the posts that were met with silence.
>>>>>> Again, I get it, people are busy.
>>>>> And you think my emails are not being met with silence? This has nothing to do with this specific topic. This has something to do with how occupied people are and how engaged they are on certain topics. Not everyone is involved in all the things and simply will ignore emails simply based on their subject line.
>>>>>
>>>>>> But the "dip here to the list" were my attempts to get a conversation started. As I said, many time met with silence.
>>>>>>
>>>>>> The only place I was not met with silence was on the Community. You have a great group of people in the Community. It is a shame you don’t want to have others help. It would reduce your workload.
>>>>> You should stop making statements that are not true. Who doesn’t want anyone to help?
>>>>>
>>>>> Not having this conversation on a Saturday evening would reduce my workload. At least it would free up time for something else. Helping with the things that are already on the go would reduce the workload of the entire team. Starting one thing at a time and finishing it is a lot better to manage than starting a hundred things and not even finish one. I can tell you that I already have a hundred things on the go.
>>>>>
>>>>>>> Therefore, what am I supposed to do with this email?
>>>>>> To me it is beyond obvious…
>>>>>>
>>>>>> If it isn’t what you want, then guide me with how to do this the correct way. And be specific. I am trying to help. I am trying to make things better. I am trying to do things the right way.
>>>>> To me it isn’t. This is yet another project that has been dumped to the list like so many before and later on everyone has left to have the team deal with the rest.
>>>>>
>>>>> It is a huge patch set. You explained what the vision is, but that is about it. There is no chance this will continue if this disagreement isn’t solved first. I didn’t even look at the code.
>>>>>
>>>>>>> I don’t want to merge code that I don’t agree with.
>>>>>> I asked multiple times if you "agreed with the concept" and again, met with silence. Yes I get it, people are busy.
>>>>> Having support for RPZ? Yes, it was definitely on the roadmap. That I agree with.
>>>>>
>>>>>>> So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>> You mentioned this a in the past, but for some reason you do not disclose what I dismissed. Why do you continue to make this harder, wouldn’t it not be easier to tell me what I have dismissed?
>>>>>>
>>>>>> I have sent multiple emails trying to answer your concerns and comments. On July 28, Aug 14, Aug 22, Aug 23, Sep 30, etc.
>>>>>>
>>>>>> I’ve gone through all of the questions you asked and I cannot find a "dismissed" item.
>>>>> Maybe I need to be *more clear*. I feel humoured by this.
>>>>>
>>>>> It is late on a Saturday and I want my dinner soon, but certainly I have stated that this should never be an add-on considering it is supposed to replace URL Filter. We should never allow people to add their own sources. I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>>>>
>>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>> The maintainers of Unbound and/or RPZ?
>>>>>>
>>>>>> The maintainers of Hagezi list, the threatfox list, the urlhaus list, etc.?
>>>>>>
>>>>>> What else? The maintainers or the RPZ scripts? That is me. Let’s talk!
>>>>> You. I don’t care much about the providers of the lists.
>>>>>
>>>>>> See, this is where it gets confusing. There are hundreds of open source packages as part of IPFire. Pick the last five years of items added to the IPFire build. You're telling me you have "constructive conversation with the maintainers" of all of the added packages?
>>>>> They publish their software and they don’t care whether I am pulling it or not. They publish it with the commitment to maintain it - sometimes for better and sometimes for worse.
>>>>>
>>>>> You care about me pulling your code and I don’t know whether you would commit to maintain this.
>>>>>
>>>>> These two are very different cases.
>>>>>
>>>>>> Pick the IP Blocklists list (i.e., 3CORESEC, ABUSECH, DSHIELD, SPAMHAUS, etc.) or the Suricata lists (i.e.,Emergingthreats.net <http://emergingthreats.net/>,Abuse.ch <http://abuse.ch/>, etc.). So you’ve have "constructive conversation with the maintainers"?
>>>>> Yes, occasionally I have phone calls with a few of these providers.
>>>>>
>>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>> Ha! Yes a surprise. In the beginning you seemed interested as IPFire needed a replacement for URL Filter. You asked good questions about the lists picked, asked for the value to the users, etc. And I answered the best I could.
>>>>>>
>>>>>> You even asked: “Why is this realised as an add-on and not part of the core system?” from your Jul 28, 2024 email.
>>>>> Ah, so, why is the patch creating an add-on? Not that I am saying that what I say is law, but it has not been challenged either. If my input is being ignored, why should I put this to the top of my list of priorities? I am not disappointed about this, just trying to be very good with my time.
>>>>>
>>>>>> And on January 16, 2025 I wrote a message looking for help. And you were kind to respond quickly. So in three weeks time, since the kind response, something has changed. You went from supportive to "this".
>>>>>>
>>>>>> So yes, I am surprised.
>>>>> Well, maybe I should not have replied to that email. It was clear that you were on some path that was not right, but you were not interested before in finding the right path from the beginning.
>>>>>
>>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>> Be more specific, what has to change? What exactly did I dismiss?
>>>>> Dismissal is just my assumption. I don’t know what you actually did with my feedback. I can only see the end product that does not seem contain much of it. Repeatedly I have been pointing out that we should think before we build. I am sure a lot of hours have now gone into some code that simply does not satisfy me. And I am not not talking about the code itself, what it does is what I don’t think is right for us.
>>>>>
>>>>> The process is very clear for me that we should first of all think whether we want a certain feature now. Then there should be a clear roadmap for everyone to follow; tasks can be split-up as we go and hopefully then have something that is maintainable, interesting for our users and even would do us proud. This is how this should work.
>>>>>
>>>>> So, what has to change? I don’t think with shouting at each other, throwing patches around and making me generally unhappy is a good start.
>>>>>
>>>>> -Michael
>>>>>
>>>>>> Jon
>>>>>>
>>>>>>
>>>>>>
>>>>>>> On Feb 6, 2025, at 2:13 PM, Michael Tremer <michael.tremer@ipfire.org> wrote:
>>>>>>>
>>>>>>> Hello Jon,
>>>>>>>
>>>>>>> Well, here we are again with another patch regarding this feature.
>>>>>>>
>>>>>>> I cannot quite see from your email what the question is, but if this is a request to have this merged into IPFire, I am once again sorry to disappoint you.
>>>>>>>
>>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls. Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation. Therefore, what am I supposed to do with this email?
>>>>>>>
>>>>>>> I don’t want to merge code that I don’t agree with. So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>>>
>>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>>>
>>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>>>
>>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>>>
>>>>>>> All the best,
>>>>>>> -Michael
>>>>>>>
>>>>>>>> On 6 Feb 2025, at 16:35, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>>
>>>>>>>> What is it?
>>>>>>>> Response Policy Zone (RPZ) is a mechanism to define local policies in a
>>>>>>>> standardized way and load those policies from external sources.
>>>>>>>> Bottom line: RPZ allows admins to easily block access to websites via DNS lookup.
>>>>>>>>
>>>>>>>> RPZ can block websites via categories. Examples include: fake websites, annoying
>>>>>>>> pop-up ads, newly registered domains, DoH bypass sites, bad "host" services,
>>>>>>>> maliscious top level domains (e.g., *.zip, *.mov), piracy, gambling, pornography,
>>>>>>>> and more. RPZ lists come from various RPZ providers and their available
>>>>>>>> catagories.
>>>>>>>>
>>>>>>>> This RPZ add-on enables the RPZ functionality by adding a couple lines in a
>>>>>>>> configuration file. This add-on simply adds configuration files and adds
>>>>>>>> scripts (config, metrics and sleep) to make RPZ easier for the admin to use.
>>>>>>>>
>>>>>>>> The RPZ scripts include additional languages: German, Spanish, French, Turkish,
>>>>>>>> and Italian.
>>>>>>>>
>>>>>>>> RPZ itself was release in 2010 and has been part of the IPFire build since ~2015.
>>>>>>>>
>>>>>>>> Why is it needed? What is its value?
>>>>>>>>
>>>>>>>> - The RPZ concept places this filtering into IPFire, our internet access
>>>>>>>> gateway, which is (should be) solely used as DNS source of the internal network.
>>>>>>>>
>>>>>>>> - As most sites use HTTPS it makes it difficult to filter traffic with URL
>>>>>>>> Filter without also properly configuring conventional (non-transparent)
>>>>>>>> mode on the proxy. RPZ is a nice replacement for the URL Filter.
>>>>>>>>
>>>>>>>> - No need to install and maintain an additional device like PiHole or AdBlock
>>>>>>>> browser extensions on multiple user devices.
>>>>>>>>
>>>>>>>> - This is an additional layer of protection for users. Less worry someone will
>>>>>>>> click on something that gets them into trouble. And, saying this with emphasis,
>>>>>>>> the ability to do it in one place!
>>>>>>>>
>>>>>>>> - Blocked sites save on unneeded traffic and can lessen the threat of malware
>>>>>>>> in advertisements
>>>>>>>>
>>>>>>>> - Logging allows the admin to see the site blocked and take actions
>>>>>>>>
>>>>>>>> - RPZ will be used at the home, home-office (work from home), schools,
>>>>>>>> ministerial, and at the office. Device counts are small (2-6) to medium (~80)
>>>>>>>> to mediam-large (200+).
>>>>>>>>
>>>>>>>> - RPZ can block ads, popups, phishing, scammers, spyware, malware, annoying
>>>>>>>> popups, NSFW links, DOH servers, and the usual internet trash.
>>>>>>>>
>>>>>>>> ------------------------------
>>>>>>>>
>>>>>>>> Change Log for RPZ add-on
>>>>>>>>
>>>>>>>> rpz-1.0.0-18 on 2025-02-05
>>>>>>>> - Build for approval & release as IPFire add-on
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> rpz-beta-0.1.18-18.ipfire on 2025-02-01
>>>>>>>> rpz.cgi:
>>>>>>>> - new feature: added a mod key to force a unbound restart
>>>>>>>>
>>>>>>>> rpz-config and rpz-make:
>>>>>>>> - new feature: added action for unbound restart `rpz-config unbound-restart`
>>>>>>>>
>>>>>>>> rpz-metrics:
>>>>>>>> - simple reformatting
>>>>>>>> - rename far right column from "last update" to "last download"
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> rpz-beta-0.1.17-17.ipfire on 2024-12-09
>>>>>>>> rpz-make
>>>>>>>> - bug fix: corrected validation regex for wildcards like: `*.domain.com`
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> rpz-beta-0.1.16-16.ipfire on 2024-11-18
>>>>>>>> rpz-make
>>>>>>>> - new feature: updated validation regex
>>>>>>>> - bug fix: moved validation to beginning of process. Now we validate before
>>>>>>>> creating config files.
>>>>>>>>
>>>>>>>> rpz.cgi:
>>>>>>>> - new feature: use CSS color variables of the main ipfire theme
>>>>>>>> - bug fix: empty zonefile remarks were stored as “undef” and caused a warning
>>>>>>>> - bug fix: HTML textarea removes the first empty line in a custom list
>>>>>>>> - thank you Leo!
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> rpz-beta-0.1.15-15.ipfire on 2024-11-04
>>>>>>>> rpz.cgi:
>>>>>>>> - new feature: added new language file for Turkish (thank you Peppe)
>>>>>>>>
>>>>>>>> rpz-make
>>>>>>>> - bug fix: corrected empty allow/block list issue. An empty allow/block list
>>>>>>>> will now remove contents of allow/block.rpz files and remove unneeded
>>>>>>>> allow/block.conf file. (thank you iptom)
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> rpz-beta-0.1.14-14.ipfire on 2024-10-29
>>>>>>>> rpz-config:
>>>>>>>> - bug fix: correct missing rpz extension. `rpz-config list` displayed URL
>>>>>>>> incorrectly (thank you Bernhard)
>>>>>>>>
>>>>>>>> rpz.cgi:
>>>>>>>> - bug fix: remove extra `"` in language files (thank you Bernhard)
>>>>>>>> - new feature: slightly dim "apply" button when not enabled
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> rpz-beta-0.1.13-13.ipfire on 2024-10-27
>>>>>>>> - skipped
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> rpz-beta-0.1.12-12.ipfire on 2024-10-21
>>>>>>>> rpz.cgi:
>>>>>>>> - new feature: added new language file for French (thank you gw-ipfire)
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> rpz-beta-0.1.11-11.ipfire on 2024-10-18
>>>>>>>> rpz.cgi:
>>>>>>>> - new feature: added new language file for Italian (thank you umberto)
>>>>>>>> - new feature: added new language file for Spanish (thank you Roberto)
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> rpz-beta-0.1.10-10.ipfire on 2024-10-15
>>>>>>>> rpz-make:
>>>>>>>> - bug fix: corrected validation error for a custom list entry (thank you siosios)
>>>>>>>> - e.g., `*.cloudflare-dns.com`
>>>>>>>>
>>>>>>>> install.sh:
>>>>>>>> - bug fix: add chown to correct user created files
>>>>>>>>
>>>>>>>> update.sh:
>>>>>>>> - bug fix: add chown to correct user created files (thank you siosios)
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> rpz-beta-0.1.9-9.ipfire on 2024-10-08
>>>>>>>> rpz.cgi:
>>>>>>>> - new feature: added new language file for German (thank you Leo)
>>>>>>>> - bug fix: add missing "rpz exitcode 110"
>>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> rpz-beta-0.1.8-8.ipfire on 2024-10-04
>>>>>>>> - skipped
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> rpz-beta-0.1.7-7.ipfire on 2024-10-03
>>>>>>>> All:
>>>>>>>> - new feature: includes beta version numbers for pakfire package,
>>>>>>>> instead of only `rpz-1.0.0-1.ipfire`, for each release.
>>>>>>>>
>>>>>>>> rpz.cgi:
>>>>>>>> - new feature: added new WebGUI at `rpz.cgi`
>>>>>>>> - a BIG thank you to Leo Hofmann for all of his work creating the webgui!!
>>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>>
>>>>>>>> rpz-make:
>>>>>>>> - new feature: validate entries in allowlist and blocklist
>>>>>>>> - new feature: add "no-reload" option for WebGUI
>>>>>>>>
>>>>>>>> rpz-metrics:
>>>>>>>> - new feature: info can be sorted by name, by hit count, by line count, by
>>>>>>>> "enabled" list or all lists
>>>>>>>>
>>>>>>>> backups:
>>>>>>>> - bug fix: include all files in `/var/ipfire/dns/rpz` directory in backup
>>>>>>>>
>>>>>>>> update.sh:
>>>>>>>> - bug fix: corrected ownership for `/var/ipfire/dns/rpz` directory during an
>>>>>>>> update
>>>>>>>>
>>>>>>>> Build:
>>>>>>>> - bug fix: `block.rpz.conf` and `block.rpz` from build. Files to be created
>>>>>>>> by `rpz-make`
>>>>>>>>
>>>>>>>> WebGUI and German language file
>>>>>>>> Contribution-by: Leo-Andres Hofmann <hofmann@leo-andres.de>
>>>>>>>>
>>>>>>>> Spanish language file
>>>>>>>> Contribution-by: Roberto Peña
>>>>>>>>
>>>>>>>> Italian language file
>>>>>>>> Contribution-by: Umberto Parma
>>>>>>>>
>>>>>>>> French language file
>>>>>>>> Contribution-by: gw-ipfire
>>>>>>>>
>>>>>>>> Turkish language file
>>>>>>>> Contribution-by: Peppe Tech
>>>>>>>>
>>>>>>>> Contribution-by: Bernhard Bitsch <bbitsch@ipfire.org>
>>>>>>>> Contribution-by: Erik Kapfer <erik.kapfer@ipfire.org>
>>>>>>>> Signed-off-by: Jon Murphy <jon.murphy@ipfire.org
>>>>>>>> ---
>>>>>>>> config/backup/includes/rpz | 4 +
>>>>>>>> config/cfgroot/manualpages | 1 +
>>>>>>>> config/menu/EX-rpz.menu | 6 +
>>>>>>>> config/rootfiles/common/configroot | 1 +
>>>>>>>> config/rootfiles/common/web-user-interface | 1 +
>>>>>>>> config/rootfiles/packages/rpz | 20 +
>>>>>>>> config/rpz/00-rpz.conf | 10 +
>>>>>>>> config/rpz/rpz-config | 130 +++
>>>>>>>> config/rpz/rpz-functions | 85 ++
>>>>>>>> config/rpz/rpz-make | 203 +++++
>>>>>>>> config/rpz/rpz-metrics | 170 ++++
>>>>>>>> config/rpz/rpz-sleep | 58 ++
>>>>>>>> config/rpz/rpz.de.pl | 30 +
>>>>>>>> config/rpz/rpz.en.pl | 30 +
>>>>>>>> config/rpz/rpz.es.pl | 30 +
>>>>>>>> config/rpz/rpz.fr.pl | 30 +
>>>>>>>> config/rpz/rpz.it.pl | 30 +
>>>>>>>> config/rpz/rpz.tr.pl | 30 +
>>>>>>>> html/cgi-bin/rpz.cgi | 923 +++++++++++++++++++++
>>>>>>>> lfs/rpz | 96 +++
>>>>>>>> make.sh | 3 +-
>>>>>>>> src/paks/rpz/install.sh | 36 +
>>>>>>>> src/paks/rpz/uninstall.sh | 38 +
>>>>>>>> src/paks/rpz/update.sh | 52 ++
>>>>>>>> 24 files changed, 2016 insertions(+), 1 deletion(-)
>>>>>>>> create mode 100644 config/backup/includes/rpz
>>>>>>>> create mode 100644 config/menu/EX-rpz.menu
>>>>>>>> create mode 100644 config/rootfiles/packages/rpz
>>>>>>>> create mode 100644 config/rpz/00-rpz.conf
>>>>>>>> create mode 100644 config/rpz/rpz-config
>>>>>>>> create mode 100644 config/rpz/rpz-functions
>>>>>>>> create mode 100644 config/rpz/rpz-make
>>>>>>>> create mode 100755 config/rpz/rpz-metrics
>>>>>>>> create mode 100755 config/rpz/rpz-sleep
>>>>>>>> create mode 100644 config/rpz/rpz.de.pl
>>>>>>>> create mode 100644 config/rpz/rpz.en.pl
>>>>>>>> create mode 100644 config/rpz/rpz.es.pl
>>>>>>>> create mode 100644 config/rpz/rpz.fr.pl
>>>>>>>> create mode 100644 config/rpz/rpz.it.pl
>>>>>>>> create mode 100644 config/rpz/rpz.tr.pl
>>>>>>>> create mode 100644 html/cgi-bin/rpz.cgi
>>>>>>>> create mode 100644 lfs/rpz
>>>>>>>> create mode 100644 src/paks/rpz/install.sh
>>>>>>>> create mode 100644 src/paks/rpz/uninstall.sh
>>>>>>>> create mode 100644 src/paks/rpz/update.sh
>>>>>>>>
>>>>>>>> diff --git a/config/backup/includes/rpz b/config/backup/includes/rpz
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..36513e494
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/backup/includes/rpz
>>>>>>>> @@ -0,0 +1,4 @@
>>>>>>>> +/var/ipfire/dns/rpz/*
>>>>>>>> +/etc/unbound/zonefiles/allow.rpz
>>>>>>>> +/etc/unbound/zonefiles/block.rpz
>>>>>>>> +/etc/unbound/local.d/*rpz.conf
>>>>>>>> diff --git a/config/cfgroot/manualpages b/config/cfgroot/manualpages
>>>>>>>> index 1f7e01efc..d3a48c633 100644
>>>>>>>> --- a/config/cfgroot/manualpages
>>>>>>>> +++ b/config/cfgroot/manualpages
>>>>>>>> @@ -70,6 +70,7 @@ pakfire.cgi=configuration/ipfire/pakfire
>>>>>>>> wlanap.cgi=addons/wireless
>>>>>>>> tor.cgi=addons/tor
>>>>>>>> samba.cgi=addons/samba
>>>>>>>> +rpz.cgi=addons/rpz
>>>>>>>>
>>>>>>>> # Logs menu
>>>>>>>> logs.cgi/summary.dat=configuration/logs/summary
>>>>>>>> diff --git a/config/menu/EX-rpz.menu b/config/menu/EX-rpz.menu
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..2f4daf410
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/menu/EX-rpz.menu
>>>>>>>> @@ -0,0 +1,6 @@
>>>>>>>> +$subipfire->{'20.rpz'} = {
>>>>>>>> + 'caption' => $Lang::tr{'rpz'},
>>>>>>>> + 'uri' => '/cgi-bin/rpz.cgi',
>>>>>>>> + 'title' => "RPZ",
>>>>>>>> + 'enabled' => 1,
>>>>>>>> +};
>>>>>>>> diff --git a/config/rootfiles/common/configroot b/config/rootfiles/common/configroot
>>>>>>>> index 9839eee45..b30d6aae4 100644
>>>>>>>> --- a/config/rootfiles/common/configroot
>>>>>>>> +++ b/config/rootfiles/common/configroot
>>>>>>>> @@ -120,6 +120,7 @@ var/ipfire/menu.d/70-log.menu
>>>>>>>> #var/ipfire/menu.d/EX-apcupsd.menu
>>>>>>>> #var/ipfire/menu.d/EX-guardian.menu
>>>>>>>> #var/ipfire/menu.d/EX-mympd.menu
>>>>>>>> +#var/ipfire/menu.d/EX-rpz.menu
>>>>>>>> #var/ipfire/menu.d/EX-samba.menu
>>>>>>>> #var/ipfire/menu.d/EX-tor.menu
>>>>>>>> #var/ipfire/menu.d/EX-transmission.menu
>>>>>>>> diff --git a/config/rootfiles/common/web-user-interface b/config/rootfiles/common/web-user-interface
>>>>>>>> index 816241dae..e00464076 100644
>>>>>>>> --- a/config/rootfiles/common/web-user-interface
>>>>>>>> +++ b/config/rootfiles/common/web-user-interface
>>>>>>>> @@ -69,6 +69,7 @@ srv/web/ipfire/cgi-bin/proxy.cgi
>>>>>>>> srv/web/ipfire/cgi-bin/qos.cgi
>>>>>>>> srv/web/ipfire/cgi-bin/remote.cgi
>>>>>>>> srv/web/ipfire/cgi-bin/routing.cgi
>>>>>>>> +#srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>>> #srv/web/ipfire/cgi-bin/samba.cgi
>>>>>>>> srv/web/ipfire/cgi-bin/services.cgi
>>>>>>>> srv/web/ipfire/cgi-bin/shutdown.cgi
>>>>>>>> diff --git a/config/rootfiles/packages/rpz b/config/rootfiles/packages/rpz
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..1c8663049
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rootfiles/packages/rpz
>>>>>>>> @@ -0,0 +1,20 @@
>>>>>>>> +etc/unbound/local.d/00-rpz.conf
>>>>>>>> +etc/unbound/zonefiles
>>>>>>>> +etc/unbound/zonefiles/allow.rpz
>>>>>>>> +usr/sbin/rpz-config
>>>>>>>> +usr/sbin/rpz-functions
>>>>>>>> +usr/sbin/rpz-make
>>>>>>>> +usr/sbin/rpz-metrics
>>>>>>>> +usr/sbin/rpz-sleep
>>>>>>>> +var/ipfire/addon-lang/rpz.de.pl
>>>>>>>> +var/ipfire/addon-lang/rpz.en.pl
>>>>>>>> +var/ipfire/addon-lang/rpz.es.pl
>>>>>>>> +var/ipfire/addon-lang/rpz.fr.pl
>>>>>>>> +var/ipfire/addon-lang/rpz.it.pl
>>>>>>>> +var/ipfire/addon-lang/rpz.tr.pl
>>>>>>>> +var/ipfire/backup/addons/includes/rpz
>>>>>>>> +var/ipfire/dns/rpz
>>>>>>>> +var/ipfire/dns/rpz/allowlist
>>>>>>>> +var/ipfire/dns/rpz/blocklist
>>>>>>>> +var/ipfire/menu.d/EX-rpz.menu
>>>>>>>> +srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>>> diff --git a/config/rpz/00-rpz.conf b/config/rpz/00-rpz.conf
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..f005a4f2e
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/00-rpz.conf
>>>>>>>> @@ -0,0 +1,10 @@
>>>>>>>> +server:
>>>>>>>> + module-config: "respip validator iterator"
>>>>>>>> +
>>>>>>>> +rpz:
>>>>>>>> + name: allow.rpz
>>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>>> + rpz-action-override: passthru
>>>>>>>> + rpz-log: yes
>>>>>>>> + rpz-log-name: allow
>>>>>>>> + rpz-signal-nxdomain-ra: yes
>>>>>>>> diff --git a/config/rpz/rpz-config b/config/rpz/rpz-config
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..c72d50f9b
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/rpz-config
>>>>>>>> @@ -0,0 +1,130 @@
>>>>>>>> +#!/bin/bash
>>>>>>>> +###############################################################################
>>>>>>>> +# #
>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info@ipfire.org> #
>>>>>>>> +# #
>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>> +# #
>>>>>>>> +###############################################################################
>>>>>>>> +
>>>>>>>> +version="2025-01-11 - v44"
>>>>>>>> +
>>>>>>>> +############### Functions ###############
>>>>>>>> +
>>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>>> +
>>>>>>>> +############### Main ###############
>>>>>>>> +
>>>>>>>> +tagName="unbound"
>>>>>>>> +
>>>>>>>> +rpzAction="${1}" # input RPZ action
>>>>>>>> +rpzName="${2}" # input RPZ name
>>>>>>>> +rpzURL="${3}" # input RPZ URL
>>>>>>>> +rpzOption1="${4}" # input RPZ option #1
>>>>>>>> +rpzOption2="${5}" # input RPZ option #2
>>>>>>>> +
>>>>>>>> +rpzConfig="/etc/unbound/local.d/${rpzName}.rpz.conf" # output zone conf file
>>>>>>>> +rpzFile="/etc/unbound/zonefiles/${rpzName}.rpz" # output for RPZ file
>>>>>>>> +
>>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>>> +
>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>> + case "$1" in
>>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>>> + esac
>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>> +done
>>>>>>>> +
>>>>>>>> +case "${rpzAction}" in
>>>>>>>> + # add new rpz list
>>>>>>>> + add )
>>>>>>>> + check_name "${rpzName}" # is this a valid name?
>>>>>>>> + # does this config already exist? If yes, then exit
>>>>>>>> + if [[ -f "${rpzConfig}" ]] ; then
>>>>>>>> + msg_log "error: rpz: duplicate - ${rpzConfig} already exists. exit"
>>>>>>>> + exit 104
>>>>>>>> + fi
>>>>>>>> +
>>>>>>>> + # is this a valid URL?
>>>>>>>> + regex='^https://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
>>>>>>>> + if ! [[ "${rpzURL}" =~ $regex ]] ; then
>>>>>>>> + msg_log "error: rpz: the URL is not valid: \"${rpzURL}\". exit."
>>>>>>>> + exit 105
>>>>>>>> + fi
>>>>>>>> +
>>>>>>>> + # create the zone config file
>>>>>>>> + {
>>>>>>>> + echo "rpz:"
>>>>>>>> + echo " name: ${rpzName}.rpz"
>>>>>>>> + echo " zonefile: ${rpzFile}"
>>>>>>>> + echo " url: ${rpzURL}"
>>>>>>>> + echo " rpz-action-override: nxdomain"
>>>>>>>> + echo " rpz-log: ${rpzLog}"
>>>>>>>> + echo " rpz-log-name: ${rpzName}"
>>>>>>>> + echo " rpz-signal-nxdomain-ra: yes"
>>>>>>>> + } > "${rpzConfig}"
>>>>>>>> +
>>>>>>>> + # set-up zonefile
>>>>>>>> + # create an empty rpz file if it does not exist
>>>>>>>> + if [[ ! -f "${rpzFile}" ]] ; then
>>>>>>>> + touch "${rpzFile}"
>>>>>>>> + # unbound requires these settings for rpz files
>>>>>>>> + set_permissions "${rpzFile}" "${rpzConfig}"
>>>>>>>> + fi
>>>>>>>> + ;;
>>>>>>>> +
>>>>>>>> + # trash config file & rpz file
>>>>>>>> + remove )
>>>>>>>> + if ! [[ -f "${rpzConfig}" ]] ; then
>>>>>>>> + msg_log "error: rpz: cannot remove ${rpzConfig}, does not exist. exit"
>>>>>>>> + exit 106
>>>>>>>> + fi
>>>>>>>> +
>>>>>>>> + msg_log "info: rpz: remove config file & rpz file \"${rpzName}\""
>>>>>>>> + rm "${rpzConfig}"
>>>>>>>> + rm "${rpzFile}"
>>>>>>>> + ;;
>>>>>>>> +
>>>>>>>> + reload )
>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>> + ;;
>>>>>>>> +
>>>>>>>> + list )
>>>>>>>> + awk -F':' '/^\s*name:/{ gsub(/[[:blank:]]|\.rpz/, "",$2) ; NAME=$2 } \
>>>>>>>> + /^\s*url:/{ gsub(/[[:blank:]]/, "") ; print NAME"="$2":"$3} ' \
>>>>>>>> + /etc/unbound/local.d/*rpz.conf
>>>>>>>> + exit
>>>>>>>> + ;;
>>>>>>>> +
>>>>>>>> + unbound-restart )
>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>> + unbound_restart
>>>>>>>> + exit
>>>>>>>> + ;;
>>>>>>>> +
>>>>>>>> + * )
>>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>>> + printf "Usage: $(basename "$0") <ACTION> <NAME> <URL> <OPTION> <OPTION>\n"
>>>>>>>> + printf "Version: ${version}\n"
>>>>>>>> + exit 108
>>>>>>>> + ;;
>>>>>>>> +
>>>>>>>> +esac
>>>>>>>> +
>>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>>> +
>>>>>>>> +exit
>>>>>>>> diff --git a/config/rpz/rpz-functions b/config/rpz/rpz-functions
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..ace1d2690
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/rpz-functions
>>>>>>>> @@ -0,0 +1,85 @@
>>>>>>>> +#!/bin/bash
>>>>>>>> +###############################################################################
>>>>>>>> +# #
>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>> +# #
>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>> +# #
>>>>>>>> +###############################################################################
>>>>>>>> +
>>>>>>>> +version="2024-12-10 - v02"
>>>>>>>> +
>>>>>>>> +############### Functions ###############
>>>>>>>> +
>>>>>>>> +msg_log () {
>>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>>> + if tty --silent ; then
>>>>>>>> + echo "${tagName}:" "$*"
>>>>>>>> + fi
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# check for a valid name
>>>>>>>> +check_name () {
>>>>>>>> + local theName="${1}"
>>>>>>>> +
>>>>>>>> + regex='^[a-zA-Z0-9_]+$' # no dash or plus, alpha numeric only
>>>>>>>> + regex1='^(allow|block)$' # allow and block are reserved NAMEs
>>>>>>>> + if [[ ! "${theName}" =~ $regex ]] || [[ "${theName}" =~ $regex1 ]] ; then
>>>>>>>> + msg_log "error: rpz: the NAME is not valid: \"${theName}\". exit."
>>>>>>>> + exit 101
>>>>>>>> + fi
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +set_permissions () {
>>>>>>>> + chown nobody:nobody "$@"
>>>>>>>> + chmod 644 "$@"
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +check_unbound_conf () {
>>>>>>>> + local thecheckConf="${1:-yes}" # check config default is yes
>>>>>>>> +
>>>>>>>> + # check the above config files
>>>>>>>> + if [[ "${thecheckConf}" == yes ]] ; then
>>>>>>>> + msg_log "info: rpz: check for errors with \"unbound-checkconf\""
>>>>>>>> +
>>>>>>>> + if ! unbound-checkconf ; then
>>>>>>>> + msg_log "error: rpz: unbound-checkconf found invalid configuration."
>>>>>>>> + msg_log \
>>>>>>>> + "error: rpz: In Terminal run the command \"unbound-checkconf\" for more information. exit."
>>>>>>>> + exit 102
>>>>>>>> + fi
>>>>>>>> + fi
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +unbound_control_reload () {
>>>>>>>> + local theReload="${1:-yes}" # reload default is yes
>>>>>>>> +
>>>>>>>> + if [[ "${theReload}" == yes ]] ; then
>>>>>>>> + # reload due to the changes
>>>>>>>> + msg_log "info: rpz: run \"unbound-control reload\""
>>>>>>>> +
>>>>>>>> + if ! unbound-control reload ; then
>>>>>>>> + msg_log "error: rpz: unbound-control reload. exit."
>>>>>>>> + exit 109
>>>>>>>> + fi
>>>>>>>> + fi
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +unbound_restart () {
>>>>>>>> + # restart due to the changes
>>>>>>>> + msg_log "info: rpz: run \"unbound restart\""
>>>>>>>> +
>>>>>>>> + /usr/local/bin/unboundctrl restart
>>>>>>>> +}
>>>>>>>> diff --git a/config/rpz/rpz-make b/config/rpz/rpz-make
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..927d55170
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/rpz-make
>>>>>>>> @@ -0,0 +1,203 @@
>>>>>>>> +#!/bin/bash
>>>>>>>> +###############################################################################
>>>>>>>> +# #
>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info@ipfire.org> #
>>>>>>>> +# #
>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>> +# #
>>>>>>>> +###############################################################################
>>>>>>>> +
>>>>>>>> +version="2025-01-11 - v14"
>>>>>>>> +
>>>>>>>> +############### Functions ###############
>>>>>>>> +
>>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>>> +
>>>>>>>> +# create the config file for allow
>>>>>>>> +make_allow_config () {
>>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>>> + local theConfig="/etc/unbound/local.d/00-rpz.conf" # output zone conf file
>>>>>>>> + local theList="/var/ipfire/dns/rpz/allowlist" # input custom list of domains
>>>>>>>> +
>>>>>>>> + msg_log "info: rpz: make config file \"00-rpz.conf\""
>>>>>>>> +
>>>>>>>> + echo "server:
>>>>>>>> + module-config: \"respip validator iterator\"" > "${theConfig}"
>>>>>>>> +
>>>>>>>> + # does allow list exist?
>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>> +
>>>>>>>> + echo "rpz:
>>>>>>>> + name: allow.rpz
>>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>>> + rpz-action-override: passthru
>>>>>>>> + rpz-log: ${theLog}
>>>>>>>> + rpz-log-name: allow
>>>>>>>> + rpz-signal-nxdomain-ra: yes" >> "${theConfig}"
>>>>>>>> +
>>>>>>>> + fi
>>>>>>>> +
>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>> + set_permissions "${theConfig}"
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# create the config file for block
>>>>>>>> +make_block_config () {
>>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>>> + local theConfig="/etc/unbound/local.d/block.rpz.conf" # output zone conf file
>>>>>>>> + local theList="/var/ipfire/dns/rpz/blocklist" # input custom list of domains
>>>>>>>> +
>>>>>>>> + msg_log "info: rpz: make config file \"block.rpz.conf\""
>>>>>>>> +
>>>>>>>> + # does block list exist?
>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>> +
>>>>>>>> + echo "rpz:
>>>>>>>> + name: block.rpz
>>>>>>>> + zonefile: /etc/unbound/zonefiles/block.rpz
>>>>>>>> + rpz-action-override: nxdomain
>>>>>>>> + rpz-log: ${theLog}
>>>>>>>> + rpz-log-name: block
>>>>>>>> + rpz-signal-nxdomain-ra: yes" > "${theConfig}"
>>>>>>>> +
>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>> + set_permissions "${theConfig}"
>>>>>>>> + else
>>>>>>>> + # no - trash the config file
>>>>>>>> + rm --verbose /etc/unbound/local.d/block.rpz.conf
>>>>>>>> + fi
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# create an RPZ file for allow or block
>>>>>>>> +make_rpz_file () {
>>>>>>>> + local theName="${1}" # allow or block
>>>>>>>> + local theAction='.' # the default is nxdomain or block
>>>>>>>> + local actionList
>>>>>>>> +
>>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>>> + local theZonefile="/etc/unbound/zonefiles/${theName}.rpz" # output file for RPZ
>>>>>>>> +
>>>>>>>> + # does a list exist?
>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>> +
>>>>>>>> + # for allow set to passthru
>>>>>>>> + [[ "${theName}" == allow ]] && theAction='rpz-passthru.'
>>>>>>>> +
>>>>>>>> + # drop any extra "blanks" and add "CNAME <RPZ action>." to each line
>>>>>>>> + actionList=$( awk '{$1=$1};1' "${theList}" |
>>>>>>>> + sed "/^[^;].*[[:alnum:]]/ s|$| CNAME ${theAction}|" )
>>>>>>>> +
>>>>>>>> + msg_log "info: rpz: create zonefile for \"${theName}list\""
>>>>>>>> +
>>>>>>>> +echo "; Name: ${theName} list
>>>>>>>> +; Last modified: $(date "+%Y-%m-%d at %H.%M.%S %Z")
>>>>>>>> +;
>>>>>>>> +; domains with actions list
>>>>>>>> +;
>>>>>>>> +${actionList}" > "${theZonefile}"
>>>>>>>> +
>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>> + set_permissions "${theZonefile}"
>>>>>>>> + # set-up allow/block list files
>>>>>>>> + set_permissions "${theList}"
>>>>>>>> + else
>>>>>>>> + msg_log "info: rpz: the ${theList} is empty."
>>>>>>>> +
>>>>>>>> + rm --verbose "${theZonefile}" # trash the RPZ file
>>>>>>>> + fi
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# check if allow/block list is valid
>>>>>>>> +validate_list () {
>>>>>>>> + local theName="${1}" # allow or block
>>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>>> +
>>>>>>>> + # remove good:
>>>>>>>> + # - properly formated domain names with or without leading wildcard
>>>>>>>> + # - properly formated top level domain (TLD) names with wildcard
>>>>>>>> + # - blank lines and comment lines
>>>>>>>> + # remaining lines are considered "bad"
>>>>>>>> + bad_lines=$( sed --regexp-extended \
>>>>>>>> + '/^(\*\.)?([a-zA-Z0-9](([a-zA-Z0-9\-]){0,61}[a-zA-Z0-9])?\.)+([a-zA-Z]{2,}|xn--[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])$/d ;
>>>>>>>> + /^(\*\.)([a-z]{2,61}|xn--[a-z0-9]{1,60})$/d ;
>>>>>>>> + /^$/d ; /^;/d' "${theList}" )
>>>>>>>> +
>>>>>>>> + if [[ ! -z "${bad_lines}" ]] ; then
>>>>>>>> + msg_log "error: rpz: invalid line(s) in ${theList}."
>>>>>>>> + printf "%s\n" "bad line(s): ${bad_lines}"
>>>>>>>> + exit 110
>>>>>>>> + fi
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +
>>>>>>>> +############### Main ###############
>>>>>>>> +
>>>>>>>> +tagName="unbound"
>>>>>>>> +
>>>>>>>> +rpzName="${1}" # input RPZ name
>>>>>>>> +
>>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>>> +
>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>> + case "$1" in
>>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>>> + esac
>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>> +done
>>>>>>>> +
>>>>>>>> +case "${rpzName}" in
>>>>>>>> + # make a new allow or block rpz file
>>>>>>>> +
>>>>>>>> + allow )
>>>>>>>> + validate_list 'allow' # is the allowlist valid?
>>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>>> + make_rpz_file 'allow'
>>>>>>>> + ;;
>>>>>>>> +
>>>>>>>> + allowblock )
>>>>>>>> + validate_list 'allow' # is the list valid?
>>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>>> + make_rpz_file 'allow'
>>>>>>>> + ;&
>>>>>>>> +
>>>>>>>> + block )
>>>>>>>> + validate_list 'block' # is the blocklist valid?
>>>>>>>> + make_block_config "${rpzLog}"
>>>>>>>> + make_rpz_file 'block'
>>>>>>>> + ;;
>>>>>>>> +
>>>>>>>> + reload )
>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>> + ;;
>>>>>>>> +
>>>>>>>> + unbound-restart )
>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>> + unbound_restart
>>>>>>>> + exit
>>>>>>>> + ;;
>>>>>>>> +
>>>>>>>> + * )
>>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>>> + printf "Usage: $(basename "$0") <NAME> <OPTION> <OPTION>\n"
>>>>>>>> + printf "Version: ${version}\n"
>>>>>>>> + exit 108
>>>>>>>> + ;;
>>>>>>>> +esac
>>>>>>>> +
>>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>>> +
>>>>>>>> +exit
>>>>>>>> diff --git a/config/rpz/rpz-metrics b/config/rpz/rpz-metrics
>>>>>>>> new file mode 100755
>>>>>>>> index 000000000..4d43e1629
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/rpz-metrics
>>>>>>>> @@ -0,0 +1,170 @@
>>>>>>>> +#!/bin/bash
>>>>>>>> +###############################################################################
>>>>>>>> +# #
>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>> +# #
>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>> +# #
>>>>>>>> +###############################################################################
>>>>>>>> +
>>>>>>>> +version="2025-01-20 - v25"
>>>>>>>> +
>>>>>>>> +############### Main ###############
>>>>>>>> +
>>>>>>>> +weeks="2" # default to two message logs
>>>>>>>> +sortBy="name" # default "by name"
>>>>>>>> +rpzActive="enabled" # default "enabled only"
>>>>>>>> +
>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>> + case "$1" in
>>>>>>>> + --by-names | --by-name | name ) sortBy="name" ;;
>>>>>>>> +
>>>>>>>> + --by-hits | --by-hit | hits | hit ) sortBy="hit" ;;
>>>>>>>> +
>>>>>>>> + --by-lines | --by-line | lines | line ) sortBy="line" ;;
>>>>>>>> +
>>>>>>>> + --by-effect ) sortBy="effect" ;;
>>>>>>>> +
>>>>>>>> + --enabled-only ) rpzActive="enabled" ;;
>>>>>>>> +
>>>>>>>> + --active-all | --all | all ) rpzActive="all" ;;
>>>>>>>> +
>>>>>>>> + [0-9] | [0-9][0-9] ) weeks=$1 ;;
>>>>>>>> + esac
>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>> +done
>>>>>>>> +
>>>>>>>> +# get the list of message logs for N weeks
>>>>>>>> +messageLogs=$( find /var/log/messages* -type f | sort --version-sort |
>>>>>>>> + head -"${weeks}" )
>>>>>>>> +
>>>>>>>> +# get the list of RPZ names & counts from the message log(s)
>>>>>>>> +rpzNameCount=$( for logf in ${messageLogs} ; do
>>>>>>>> + zgrep --text --fixed-strings 'info: rpz: applied' "${logf}" |
>>>>>>>> + awk '$10 ~ /\[\w*]/ { print $10 }' ;
>>>>>>>> + done | sort | uniq --count )
>>>>>>>> +
>>>>>>>> +# flip results and remove brackets `[` and `]`
>>>>>>>> +rpzNameCount=$( echo "${rpzNameCount}" |
>>>>>>>> + awk '{ print $2, $1 }' |
>>>>>>>> + sed --regexp-extended 's|^\[(.*)\]|\1|' )
>>>>>>>> +
>>>>>>>> +# grab only names
>>>>>>>> +rpzNames=$( echo "${rpzNameCount}" | awk '{ print $1 }' )
>>>>>>>> +
>>>>>>>> +# get list of RPZ files
>>>>>>>> +rpzFileList=$( find /etc/unbound/zonefiles -type f -iname "*.rpz" )
>>>>>>>> +
>>>>>>>> +# get basename of those files
>>>>>>>> +rpzBaseNames=$( echo "${rpzFileList}" |
>>>>>>>> + sed 's|/etc/unbound/zonefiles/||g ; s|\.rpz||g ;' )
>>>>>>>> +
>>>>>>>> +# add to rpzNames
>>>>>>>> +rpzNames="${rpzNames}"$'\n'"${rpzBaseNames}"
>>>>>>>> +
>>>>>>>> +# drop duplicate names
>>>>>>>> +rpzNames=$( echo "${rpzNames}" | sort --unique )
>>>>>>>> +
>>>>>>>> +# get line count for each RPZ
>>>>>>>> +lineCount=$( echo "${rpzFileList}" | xargs wc -l )
>>>>>>>> +
>>>>>>>> +# get comment line count and blank line count for each RPZ
>>>>>>>> +commentCount=$( echo "${rpzFileList}" | xargs grep --count -e "^$" -e "^;" )
>>>>>>>> +
>>>>>>>> +# get modified date each RPZ
>>>>>>>> +modDateList=$( echo "${rpzFileList}" | xargs stat -c '%.10y %n' )
>>>>>>>> +
>>>>>>>> +ucListAuthZones=$( unbound-control list_auth_zones )
>>>>>>>> +
>>>>>>>> +# get width of RPZ names
>>>>>>>> +pWidth=$( echo "${rpzNames}" | awk '{ print $1" " }' | wc -L )
>>>>>>>> +pFormat="%-${pWidth}s %-8s %-8s %8s %12s %12s\n"
>>>>>>>> +
>>>>>>>> +# print title line
>>>>>>>> +printf "${pFormat}" "name" "hits" "active" "lines" " hits/line" "last download"
>>>>>>>> +printf -- "--------------"
>>>>>>>> +
>>>>>>>> +theResults=""
>>>>>>>> +totalLines=0
>>>>>>>> +totalHits=0
>>>>>>>> +while read -r theName
>>>>>>>> +do
>>>>>>>> + printf -- "--" # pretend progress bar
>>>>>>>> +
>>>>>>>> + # is this RPZ list active?
>>>>>>>> + theActive="disabled"
>>>>>>>> + if grep --quiet "^${theName}\.rpz" <<< "${ucListAuthZones}"
>>>>>>>> + then
>>>>>>>> + theActive="enabled"
>>>>>>>> + else
>>>>>>>> + [[ "${rpzActive}" == enabled ]] && continue
>>>>>>>> + fi
>>>>>>>> +
>>>>>>>> + # get hit count
>>>>>>>> + theHits="0"
>>>>>>>> + if output=$( grep "^${theName}\s" <<< "${rpzNameCount}" ) ; then
>>>>>>>> + theHits=$( echo "${output}" | awk '{ print $2 }' )
>>>>>>>> + totalHits=$(( totalHits + theHits ))
>>>>>>>> + fi
>>>>>>>> +
>>>>>>>> + # get line count
>>>>>>>> + theLines="n/a"
>>>>>>>> + hitsPerLine="0"
>>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${lineCount}" ) ; then
>>>>>>>> + theLines=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>>> + totalLines=$(( totalLines + theLines ))
>>>>>>>> +
>>>>>>>> + if [[ "${theLines}" -gt 2 ]] ; then
>>>>>>>> + hitsPerLine=$(( 100 * theHits / theLines ))
>>>>>>>> + fi
>>>>>>>> + fi
>>>>>>>> +
>>>>>>>> + # get modification date
>>>>>>>> + theModDate="n/a"
>>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${modDateList}" ) ; then
>>>>>>>> + theModDate=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>>> + fi
>>>>>>>> +
>>>>>>>> + # add to results list
>>>>>>>> + theResults+="${theName} ${theHits} ${theActive} ${theLines} ${hitsPerLine} ${theModDate}"$'\n'
>>>>>>>> +
>>>>>>>> +done <<< "${rpzNames}"
>>>>>>>> +
>>>>>>>> +case "${sortBy}" in
>>>>>>>> + # sort by "active" then by "name"
>>>>>>>> + name) sortArg=(-k3,3r -k1,1) ;;
>>>>>>>> +
>>>>>>>> + # sort by "active" then by "hits" then by "name"
>>>>>>>> + hit) sortArg=(-k3,3r -k2,2nr -k1,1) ;;
>>>>>>>> +
>>>>>>>> + # sort by "active" then by "lines" then by "name"
>>>>>>>> + line) sortArg=(-k3,3r -k4,4nr -k1,1) ;;
>>>>>>>> +
>>>>>>>> + # sort by "active" then by "effect" then by "name"
>>>>>>>> + effect) sortArg=(-k3,3r -k5,5nr -k1,1) ;;
>>>>>>>> +esac
>>>>>>>> +
>>>>>>>> +printf -- "--------------\n"
>>>>>>>> +# remove blank lines, sort, print as columns
>>>>>>>> +echo "${theResults}" |
>>>>>>>> + awk '!/^[[:space:]]*$/' |
>>>>>>>> + sort "${sortArg[@]}" |
>>>>>>>> + awk --assign=width="${pWidth}" \
>>>>>>>> + '{ printf "%-*s %-8s %-8s %8s %10s %% %12s\n", width, $1, $2, $3, $4, $5, $6 }'
>>>>>>>> +
>>>>>>>> +printf "${pFormat}" "" "=======" "" "========" "" ""
>>>>>>>> +printf "${pFormat}" "Totals -->" "${totalHits}" "" "${totalLines}" "" ""
>>>>>>>> +
>>>>>>>> +exit
>>>>>>>> diff --git a/config/rpz/rpz-sleep b/config/rpz/rpz-sleep
>>>>>>>> new file mode 100755
>>>>>>>> index 000000000..dd3603599
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/rpz-sleep
>>>>>>>> @@ -0,0 +1,58 @@
>>>>>>>> +#!/bin/bash
>>>>>>>> +###############################################################################
>>>>>>>> +# #
>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>> +# #
>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>> +# #
>>>>>>>> +###############################################################################
>>>>>>>> +
>>>>>>>> +version="2024-08-16" # v05
>>>>>>>> +
>>>>>>>> +############### Functions ###############
>>>>>>>> +
>>>>>>>> +# send message to message log
>>>>>>>> +msg_log () {
>>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>>> + if tty --silent ; then
>>>>>>>> + echo "${tagName}:" "$*"
>>>>>>>> + fi
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +############### Main ###############
>>>>>>>> +
>>>>>>>> +tagName="unbound"
>>>>>>>> +
>>>>>>>> +sleepTime="${1:-5m}" # default to sleep for 5m (5 minutes)
>>>>>>>> +
>>>>>>>> +zoneList=$( unbound-control list_auth_zones | awk '{print $1}' )
>>>>>>>> +
>>>>>>>> +for zone in ${zoneList} ; do
>>>>>>>> + printf "disable ${zone}\t"
>>>>>>>> + unbound-control rpz_disable "${zone}"
>>>>>>>> +done
>>>>>>>> +
>>>>>>>> +msg_log "info: rpz: disabled all zones for ${sleepTime}"
>>>>>>>> +
>>>>>>>> +sleep "${sleepTime}"
>>>>>>>> +
>>>>>>>> +for zone in ${zoneList} ; do
>>>>>>>> + printf "enable ${zone}\t"
>>>>>>>> + unbound-control rpz_enable "${zone}"
>>>>>>>> +done
>>>>>>>> +
>>>>>>>> +msg_log "info: rpz: enabled all zones"
>>>>>>>> +
>>>>>>>> +exit
>>>>>>>> diff --git a/config/rpz/rpz.de.pl b/config/rpz/rpz.de.pl
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..3770c6bb0
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/rpz.de.pl
>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>> +%tr = (%tr,
>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>> +'rpz apply' => 'Übernehmen',
>>>>>>>> +'rpz cl allow enable' => 'Benutzerdefinierte Allowlist aktivieren:',
>>>>>>>> +'rpz cl allow info' => 'Zugelassene Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>>> +'rpz cl allow' => 'Benutzerdefinierte Allowlist',
>>>>>>>> +'rpz cl block enable' => 'Benutzerdefinierte Blocklist aktivieren:',
>>>>>>>> +'rpz cl block info' => 'Gesperrte Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>>> +'rpz cl block' => 'Benutzerdefinierte Blocklist',
>>>>>>>> +'rpz cl' => 'Benutzerdefinierte Listen',
>>>>>>>> +'rpz exitcode 101' => 'Der Name enthält unzulässige Zeichen',
>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf hat eine fehlerhafte Konfiguration ermittelt. Führen Sie das Kommando unbound-checkconf auf der Konsole aus, um weitere Informationen zu erhalten.',
>>>>>>>> +'rpz exitcode 103' => 'Die benutzerdefinierte Allow-/Blocklist ist leer',
>>>>>>>> +'rpz exitcode 104' => 'Ein Eintrag mit identischem Namen existiert bereits',
>>>>>>>> +'rpz exitcode 105' => 'Die URL ist ungültig',
>>>>>>>> +'rpz exitcode 106' => 'Eintrag kann nicht entfernt werden, der Name existiert nicht',
>>>>>>>> +'rpz exitcode 107' => 'Der Name ist ungültig - nur "allow" oder "block" möglich',
>>>>>>>> +'rpz exitcode 108' => 'Fehlende oder inkorrekte Parameter',
>>>>>>>> +'rpz exitcode 109' => 'unbound-control reload ist fehlgeschlagen',
>>>>>>>> +'rpz exitcode 110' => 'Die benutzerdefinierte Allow-/Blocklist enthält unzulässige Einträge',
>>>>>>>> +'rpz exitcode 201' => 'Die Anmerkung enthält unzulässige Zeichen',
>>>>>>>> +'rpz exitcode 202' => 'Ungültiger Eintrag in der benutzerdefinierten Allowlist, Zeile ',
>>>>>>>> +'rpz exitcode 203' => 'Ungültiger Eintrag in der benutzerdefinierten Blocklist, Zeile ',
>>>>>>>> +'rpz exitcode 204' => 'Ausgewählter Eintrag existiert nicht: ',
>>>>>>>> +'rpz zf editor' => 'Zonendatei-Eintrag bearbeiten',
>>>>>>>> +'rpz zf imported' => '(importiert aus rpz-config)',
>>>>>>>> +'rpz zf remark info' => 'Erlaubte Zeichen sind a-z, A-Z, 0-9 und Unterstriche',
>>>>>>>> +'rpz zf' => 'Zonendateien',
>>>>>>>> +);
>>>>>>>> diff --git a/config/rpz/rpz.en.pl b/config/rpz/rpz.en.pl
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..0720a8940
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/rpz.en.pl
>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>> +%tr = (%tr,
>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>> +'rpz apply' => 'Apply',
>>>>>>>> +'rpz cl allow enable' => 'Enable custom allowlist:',
>>>>>>>> +'rpz cl allow info' => 'Allowed domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>>> +'rpz cl allow' => 'Custom allowlist',
>>>>>>>> +'rpz cl block enable' => 'Enable custom blocklist:',
>>>>>>>> +'rpz cl block info' => 'Blocked domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>>> +'rpz cl block' => 'Custom blocklist',
>>>>>>>> +'rpz cl' => 'Custom lists',
>>>>>>>> +'rpz exitcode 101' => 'the NAME is not valid',
>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf found invalid configuration. In the Terminal run the command unbound-checkconf for more information',
>>>>>>>> +'rpz exitcode 103' => 'the allow/block list is empty',
>>>>>>>> +'rpz exitcode 104' => 'duplicate - NAME already exists',
>>>>>>>> +'rpz exitcode 105' => 'the URL is not valid',
>>>>>>>> +'rpz exitcode 106' => 'cannot remove the NAME does not exist',
>>>>>>>> +'rpz exitcode 107' => 'the NAME is not valid - "allow" or "block" only',
>>>>>>>> +'rpz exitcode 108' => 'missing or incorrect parameter',
>>>>>>>> +'rpz exitcode 109' => 'unbound-control reload failed',
>>>>>>>> +'rpz exitcode 110' => 'custom Allowlist/Blocklist contains invalid entries',
>>>>>>>> +'rpz exitcode 201' => 'the REMARK is not valid',
>>>>>>>> +'rpz exitcode 202' => 'invalid entry in allowlist, line ',
>>>>>>>> +'rpz exitcode 203' => 'invalid entry in blocklist, line ',
>>>>>>>> +'rpz exitcode 204' => 'Selected entry does not exist: ',
>>>>>>>> +'rpz zf editor' => 'Edit zonefiles entry',
>>>>>>>> +'rpz zf imported' => '(imported from rpz-config)',
>>>>>>>> +'rpz zf remark info' => 'Valid characters are a-z, A-Z, 0-9 and underscore.',
>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>> +);
>>>>>>>> diff --git a/config/rpz/rpz.es.pl b/config/rpz/rpz.es.pl
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..98628e4aa
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/rpz.es.pl
>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>> +%tr = (%tr,
>>>>>>>> +'rpz' => 'Zonas de política de respuesta (RPZ)',
>>>>>>>> +'rpz apply' => 'Aplicar',
>>>>>>>> +'rpz cl allow enable' => 'Habilitar la lista blanca personalizada:',
>>>>>>>> +'rpz cl allow info' => 'Dominio permitido (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>>> +'rpz cl allow' => 'Lista blanca personalizada',
>>>>>>>> +'rpz cl block enable' => 'Habilitar la lista negra personalizada:',
>>>>>>>> +'rpz cl block info' => 'Dominio bloqueado (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>>> +'rpz cl block' => 'Lista negra personalizada',
>>>>>>>> +'rpz cl' => 'Lista personalizada',
>>>>>>>> +'rpz exitcode 101' => 'El NOMBRE no es válido',
>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha encontrado una configuración no válida. Desde Terminal, ejecute el comando unbound-checkconf para mayor información',
>>>>>>>> +'rpz exitcode 103' => 'La lista de permitidos/bloqueados está vacía',
>>>>>>>> +'rpz exitcode 104' => 'duplicado - NOMBRE ya existe',
>>>>>>>> +'rpz exitcode 105' => 'la URL no es válida',
>>>>>>>> +'rpz exitcode 106' => 'no es posible eliminar el NOMBRE que no existe',
>>>>>>>> +'rpz exitcode 107' => 'el NOMBRE no es válido - sólo "permitir" o "bloquear"',
>>>>>>>> +'rpz exitcode 108' => 'parámetro faltante o incorrecto',
>>>>>>>> +'rpz exitcode 109' => 'Error al recargar unbound-control',
>>>>>>>> +'rpz exitcode 110' => 'la Lista blanca/Lista negra personalizada contiene entradas no válidas',
>>>>>>>> +'rpz exitcode 201' => 'el COMENTARIO no es válido',
>>>>>>>> +'rpz exitcode 202' => 'entrada no válida en la lista blanca, línea ',
>>>>>>>> +'rpz exitcode 203' => 'entrada no válida en la lista negra, línea ',
>>>>>>>> +'rpz exitcode 204' => 'La entrada seleccionada no existe: ',
>>>>>>>> +'rpz zf editor' => 'Editar la entrada de archivos de zona',
>>>>>>>> +'rpz zf imported' => '(importado de rpz-config)',
>>>>>>>> +'rpz zf remark info' => 'Los caracteres válidos son a-z, A-Z, 0-9 y guión bajo',
>>>>>>>> +'rpz zf' => 'Archivos de zona',
>>>>>>>> +);
>>>>>>>> diff --git a/config/rpz/rpz.fr.pl b/config/rpz/rpz.fr.pl
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..f35f3c2d0
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/rpz.fr.pl
>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>> +%tr = (%tr,
>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>> +'rpz apply' => 'Appliquer',
>>>>>>>> +'rpz cl allow enable' => 'Activer la liste d\'autorisations personnalisée:',
>>>>>>>> +'rpz cl allow info' => 'Domaines autorisés (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>>> +'rpz cl allow' => 'Liste d\'autorisations personnalisée',
>>>>>>>> +'rpz cl block enable' => 'Activer la liste de blocage personnalisée:',
>>>>>>>> +'rpz cl block info' => 'Domaines bloqués (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>>> +'rpz cl block' => 'liste de blocage personnalisé',
>>>>>>>> +'rpz cl' => 'Listes personnalisées',
>>>>>>>> +'rpz exitcode 101' => 'le NOM n\'est pas valide',
>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf configuration non valide trouvée. Dans le terminal, exécutez la commande unbound-checkconf pour plus d\'informations',
>>>>>>>> +'rpz exitcode 103' => 'la liste autoriser/bloquer est vide',
>>>>>>>> +'rpz exitcode 104' => 'le NOM existe déjà',
>>>>>>>> +'rpz exitcode 105' => 'L\'URL n\'est pas valide',
>>>>>>>> +'rpz exitcode 106' => 'impossible de supprimer le NOM n\'existe pas',
>>>>>>>> +'rpz exitcode 107' => 'le NOM n\'est pas valide - « autoriser » ou « bloquer » seulement',
>>>>>>>> +'rpz exitcode 108' => 'paramètre manquant ou incorrect',
>>>>>>>> +'rpz exitcode 109' => 'unbound-control rechargement échoué',
>>>>>>>> +'rpz exitcode 110' => 'la liste autoriser/bloquer contient des entrées non valides',
>>>>>>>> +'rpz exitcode 201' => 'la REMARQUE n\'est pas valable',
>>>>>>>> +'rpz exitcode 202' => 'entrée non valide dans la liste d\'autorisation, ligne ',
>>>>>>>> +'rpz exitcode 203' => 'entrée non valide dans la liste de blocs, ligne ',
>>>>>>>> +'rpz exitcode 204' => 'L\'entrée sélectionnée n\'existe pas: ',
>>>>>>>> +'rpz zf editor' => 'Modifier l\'entrée Fichiers Zone',
>>>>>>>> +'rpz zf imported' => '(importé de rpz-config)',
>>>>>>>> +'rpz zf remark info' => 'Les caractères valides sont a-z, A-Z, 0-9 et soulignement.',
>>>>>>>> +'rpz zf' => 'Fichiers Zone',
>>>>>>>> +);
>>>>>>>> diff --git a/config/rpz/rpz.it.pl b/config/rpz/rpz.it.pl
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..ee81605c9
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/rpz.it.pl
>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>> +%tr = (%tr,
>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>> +'rpz apply' => 'Applica',
>>>>>>>> +'rpz cl allow enable' => 'Abilita la Whitelist personalizzata:',
>>>>>>>> +'rpz cl allow info' => 'Domini consentiti (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>>> +'rpz cl allow' => 'Whitelist personalizzata',
>>>>>>>> +'rpz cl block enable' => 'Abilita la Blacklist personalizzata:',
>>>>>>>> +'rpz cl block info' => 'Domini bloccati (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>>> +'rpz cl block' => 'Blacklist personalizzata',
>>>>>>>> +'rpz cl' => 'Liste personalizzate',
>>>>>>>> +'rpz exitcode 101' => 'il NOME non è valido',
>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha trovato una configurazione non valida. Dal Terminale esegui il comando unbound-checkconf per maggiori informazioni',
>>>>>>>> +'rpz exitcode 103' => 'l\'elenco consentiti/bloccati è vuoto',
>>>>>>>> +'rpz exitcode 104' => 'duplicato - NAME esiste di già',
>>>>>>>> +'rpz exitcode 105' => 'l\'URL non è valido',
>>>>>>>> +'rpz exitcode 106' => 'non è possibile rimuovere il NOME non esiste',
>>>>>>>> +'rpz exitcode 107' => 'il NOME non è valido - solo "consenti" o "blocca"',
>>>>>>>> +'rpz exitcode 108' => 'parametro mancante o non corretto',
>>>>>>>> +'rpz exitcode 109' => 'ricaricamento del controllo non associato non riuscito',
>>>>>>>> +'rpz exitcode 110' => 'la Whitelist/Blacklist personalizzata contiene voci non valide',
>>>>>>>> +'rpz exitcode 201' => 'l"OSSERVAZIONE non è valida',
>>>>>>>> +'rpz exitcode 202' => 'voce non valida nella Whitelist, riga ',
>>>>>>>> +'rpz exitcode 203' => 'voce non valida nella Blacklist, riga ',
>>>>>>>> +'rpz exitcode 204' => 'La voce selezionata non esiste: ',
>>>>>>>> +'rpz zf editor' => 'Modifica la voce dei file di zona',
>>>>>>>> +'rpz zf imported' => '(importato da rpz-config)',
>>>>>>>> +'rpz zf remark info' => 'I caratteri validi sono a-z, A-Z, 0-9 e trattino basso',
>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>> +);
>>>>>>>> diff --git a/config/rpz/rpz.tr.pl b/config/rpz/rpz.tr.pl
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..00226e192
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/rpz.tr.pl
>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>> +# I�in eklendi Ayriyeten Response Policy Zone (RPZ)
>>>>>>>> +%tr = (%tr,
>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>> +'rpz apply' => 'Uygulamak',
>>>>>>>> +'rpz cl allow enable' => '�zel Etkinlestir allowlist:',
>>>>>>>> +'rpz cl allow info' => 'Izin verilmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>>> +'rpz cl allow' => 'Etkinlestir allowlist',
>>>>>>>> +'rpz cl block enable' => '�zel Etkinlestir blocklist:',
>>>>>>>> +'rpz cl block info' => 'Engellenmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>>> +'rpz cl block' => 'Engellenmis blocklist',
>>>>>>>> +'rpz cl' => 'Engellenmis lists',
>>>>>>>> +'rpz exitcode 101' => 'NAME ge�erli degil',
>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ge�ersiz yapilandirma bulundu. Terminalde daha fazla bilgi i�in Unbound-checkConf komutunu �alistirin',
>>>>>>>> +'rpz exitcode 103' => 'allow/block liste bos',
>>>>>>>> +'rpz exitcode 104' => 'kopyalamak - NAME zaten var',
>>>>>>>> +'rpz exitcode 105' => 'URL ge�erli degil',
>>>>>>>> +'rpz exitcode 106' => '�ikarilamiyor NAME yok',
>>>>>>>> +'rpz exitcode 107' => 'NAME ge�erli degil - "allow" veya "block" yalniz',
>>>>>>>> +'rpz exitcode 108' => 'Parametre eksik veya yanlis',
>>>>>>>> +'rpz exitcode 109' => 'unbound-control basarisiz',
>>>>>>>> +'rpz exitcode 110' => 'Engellenmis Allowlist/Blocklist ge�ersiz girisler i�erir',
>>>>>>>> +'rpz exitcode 201' => 'REMARK ge�erli degil',
>>>>>>>> +'rpz exitcode 202' => 'Ge�ersiz giris allowlist, line ',
>>>>>>>> +'rpz exitcode 203' => 'Ge�ersiz giris blocklist, line ',
>>>>>>>> +'rpz exitcode 204' => 'Se�ilen giris yok: ',
>>>>>>>> +'rpz zf editor' => 'yazimlamak zonefiles giris',
>>>>>>>> +'rpz zf imported' => '(ithal edildi rpz-config)',
>>>>>>>> +'rpz zf remark info' => 'Ge�erli karakterler a-z, A-Z, 0-9ve alt�st.',
>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>> +);
>>>>>>>> diff --git a/html/cgi-bin/rpz.cgi b/html/cgi-bin/rpz.cgi
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..a821c92ac
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/html/cgi-bin/rpz.cgi
>>>>>>>> @@ -0,0 +1,923 @@
>>>>>>>> +#!/usr/bin/perl
>>>>>>>> +###############################################################################
>>>>>>>> +# #
>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>> +# Copyright (C) 2005-2024 IPFire Team <info@ipfire.org> #
>>>>>>>> +# #
>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>> +# #
>>>>>>>> +###############################################################################
>>>>>>>> +
>>>>>>>> +use strict;
>>>>>>>> +use Scalar::Util qw(looks_like_number);
>>>>>>>> +
>>>>>>>> +# debugging
>>>>>>>> +#use warnings;
>>>>>>>> +#use CGI::Carp 'fatalsToBrowser';
>>>>>>>> +#use Data::Dumper;
>>>>>>>> +
>>>>>>>> +require '/var/ipfire/general-functions.pl';
>>>>>>>> +require "${General::swroot}/lang.pl";
>>>>>>>> +require "${General::swroot}/header.pl";
>>>>>>>> +
>>>>>>>> +###--- Extra HTML ---###
>>>>>>>> +my $extraHead = <<END
>>>>>>>> +<style>
>>>>>>>> + /* alternating row background */
>>>>>>>> + .tbl tr:nth-child(2n+2) {
>>>>>>>> + background-color: var(--color-light-grey);
>>>>>>>> + }
>>>>>>>> + .tbl tr:nth-child(2n+3) {
>>>>>>>> + background-color: var(--color-grey);
>>>>>>>> + }
>>>>>>>> + /* text styles */
>>>>>>>> + .tbl th:not(:last-child) {
>>>>>>>> + text-align: left;
>>>>>>>> + }
>>>>>>>> + div.right {
>>>>>>>> + text-align: right;
>>>>>>>> + margin-top: 0.5em;
>>>>>>>> + }
>>>>>>>> + /* customlist input */
>>>>>>>> + textarea.domainlist {
>>>>>>>> + margin: 0.5em 0;
>>>>>>>> + resize: vertical;
>>>>>>>> + min-height: 10em;
>>>>>>>> + overflow: auto;
>>>>>>>> + white-space: pre;
>>>>>>>> + }
>>>>>>>> + button[type=submit]:disabled {
>>>>>>>> + opacity: 0.6;
>>>>>>>> + }
>>>>>>>> +</style>
>>>>>>>> +END
>>>>>>>> +;
>>>>>>>> +###--- End of extra HTML ---###
>>>>>>>> +
>>>>>>>> +
>>>>>>>> +### Settings ###
>>>>>>>> +
>>>>>>>> +# Request DNS service reload after configuration change
>>>>>>>> +my $RPZ_RELOAD_FLAG = "${General::swroot}/dns/rpz/reload.flag";
>>>>>>>> +
>>>>>>>> +# Configuration file for all available zonefiles
>>>>>>>> +# Format: index, name (unique), enabled (on/off), URL, remark
>>>>>>>> +my $ZONEFILES_CONF = "${General::swroot}/dns/rpz/zonefiles.conf";
>>>>>>>> +
>>>>>>>> +# Configuration file for custom lists
>>>>>>>> +# IDs: 0=allowlist, 1=blocklist, 2=options (allow/block enabled)
>>>>>>>> +my $CUSTOMLISTS_CONF = "${General::swroot}/dns/rpz/customlists.conf";
>>>>>>>> +
>>>>>>>> +# Export custom lists to rpz-config
>>>>>>>> +my $RPZ_ALLOWLIST = "${General::swroot}/dns/rpz/allowlist";
>>>>>>>> +my $RPZ_BLOCKLIST = "${General::swroot}/dns/rpz/blocklist";
>>>>>>>> +
>>>>>>>> +
>>>>>>>> +### Preparation ###
>>>>>>>> +
>>>>>>>> +# Create missing config files
>>>>>>>> +unless(-f $ZONEFILES_CONF) { &General::system('touch', "$ZONEFILES_CONF"); }
>>>>>>>> +unless(-f $CUSTOMLISTS_CONF) { &General::system('touch', "$CUSTOMLISTS_CONF"); }
>>>>>>>> +
>>>>>>>> +
>>>>>>>> +## Global gui data
>>>>>>>> +my $errormessage = "";
>>>>>>>> +
>>>>>>>> +## Global configuration data
>>>>>>>> +my %zonefiles = ();
>>>>>>>> +my %customlists = ();
>>>>>>>> +&_zonefiles_load();
>>>>>>>> +&_customlists_load();
>>>>>>>> +
>>>>>>>> +## Global CGI form data
>>>>>>>> +my %cgiparams = ();
>>>>>>>> +&Header::getcgihash(\%cgiparams);
>>>>>>>> +
>>>>>>>> +my $action = $cgiparams{'ACTION'} // 'NONE';
>>>>>>>> +my $action_key = $cgiparams{'KEY'} // ''; # entry being edited, empty = none/new
>>>>>>>> +
>>>>>>>> +
>>>>>>>> +###--- Process form actions ---###
>>>>>>>> +
>>>>>>>> +# Zonefiles action: Check whether the requested entry exists
>>>>>>>> +if((substr($action, 0, 3) eq 'ZF_') && ($action_key)) {
>>>>>>>> + unless(defined $zonefiles{$action_key}) {
>>>>>>>> + $errormessage = &_rpz_error_tr(204, $action_key);
>>>>>>>> + $action = 'NONE';
>>>>>>>> + }
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +## Perform actions
>>>>>>>> +if($action eq 'ZF_SAVE') { ## Save new or modified zonefiles entry
>>>>>>>> + if(&_action_zf_save()) {
>>>>>>>> + $action = 'NONE'; # success, return to main page
>>>>>>>> + &_http_prg_redirect();
>>>>>>>> + } else {
>>>>>>>> + $action = 'ZF_EDIT'; # error occured, keep editing
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> +} elsif($action eq 'ZF_TOGGLE') { ## Toggle on/off
>>>>>>>> + if(&_action_zf_toggle()) {
>>>>>>>> + $action = 'NONE';
>>>>>>>> + &_http_prg_redirect();
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> +} elsif($action eq 'ZF_REMOVE') { ## Remove entry
>>>>>>>> + if(&_action_zf_remove()) {
>>>>>>>> + $action = 'NONE';
>>>>>>>> + &_http_prg_redirect();
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> +} elsif($action eq 'CL_SAVE') { ## Save custom lists
>>>>>>>> + if(&_action_cl_save()) {
>>>>>>>> + $action = 'NONE';
>>>>>>>> + &_http_prg_redirect();
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> +} elsif($action eq 'RPZ_RELOAD') { ## Reload dns configuration
>>>>>>>> + if(&_action_rpz_reload()) {
>>>>>>>> + $action = 'NONE';
>>>>>>>> + &_http_prg_redirect();
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> +} elsif($action eq 'UNB_RESTART') { ## Restart unbound service
>>>>>>>> + if(&_action_unb_restart()) {
>>>>>>>> + $action = 'NONE';
>>>>>>>> + &_http_prg_redirect();
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +
>>>>>>>> +###--- Start GUI ---###
>>>>>>>> +
>>>>>>>> +## Start http output
>>>>>>>> +&Header::showhttpheaders();
>>>>>>>> +
>>>>>>>> +# Start HTML
>>>>>>>> +&Header::openpage($Lang::tr{'rpz'}, 1, $extraHead);
>>>>>>>> +&Header::openbigbox('100%', 'left', '');
>>>>>>>> +
>>>>>>>> +# Show error messages
>>>>>>>> +if($errormessage) {
>>>>>>>> + &_print_message($errormessage);
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Handle zonefile add/edit mode
>>>>>>>> +if($action eq "ZF_EDIT") {
>>>>>>>> + &_print_zonefile_editor();
>>>>>>>> +
>>>>>>>> + # Finalize page and exit cleanly
>>>>>>>> + &Header::closebigbox();
>>>>>>>> + &Header::closepage();
>>>>>>>> + exit(0);
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Show gui elements
>>>>>>>> +&_print_zonefiles();
>>>>>>>> +&_print_customlists();
>>>>>>>> +&_print_gui_extras();
>>>>>>>> +
>>>>>>>> +&Header::closebigbox();
>>>>>>>> +&Header::closepage();
>>>>>>>> +
>>>>>>>> +###--- End of GUI ---###
>>>>>>>> +
>>>>>>>> +
>>>>>>>> +###--- Internal configuration file functions ---###
>>>>>>>> +
>>>>>>>> +# Load all available zonefiles from rpz-config and the internal configuration
>>>>>>>> +sub _zonefiles_load {
>>>>>>>> + # Clean start
>>>>>>>> + %zonefiles = ();
>>>>>>>> +
>>>>>>>> + # Source 1: Get the currently enabled zonefiles from rpz-config (expected format [name]=[URL])
>>>>>>>> + my @enabled_files = &General::system_output('/usr/sbin/rpz-config', 'list');
>>>>>>>> +
>>>>>>>> + foreach my $row (@enabled_files) {
>>>>>>>> + chomp($row);
>>>>>>>> +
>>>>>>>> + # Use regex instead of split() to skip non-matching lines
>>>>>>>> + next unless($row =~ /^(\w+)=(.+)$/);
>>>>>>>> + my ($name, $url) = ($1, $2);
>>>>>>>> +
>>>>>>>> + # Unique names are already guaranteed by rpz-config
>>>>>>>> + if(&_rpz_validate_zonefile($name, $url, '', 0) == 0) {
>>>>>>>> + # Populate global data hash, mark all found entries as enabled
>>>>>>>> + my %entry = ('enabled' => 'on',
>>>>>>>> + 'url' => $url,
>>>>>>>> + 'remark' => $Lang::tr{'rpz zf imported'});
>>>>>>>> +
>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>> + }
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Source 2: Get additional data and disabled entries from configuration file
>>>>>>>> + my %configured_files = ();
>>>>>>>> + &General::readhasharray($ZONEFILES_CONF, \%configured_files);
>>>>>>>> +
>>>>>>>> + foreach my $row (values (%configured_files)) {
>>>>>>>> + my ($name, $enabled, $url, $remark) = @$row;
>>>>>>>> + $remark //= "";
>>>>>>>> +
>>>>>>>> + next unless($name);
>>>>>>>> +
>>>>>>>> + # Check whether this row belongs to an entry already imported from rpz-config
>>>>>>>> + if(defined $zonefiles{$name}) {
>>>>>>>> + # Existing entry, only merge additional data
>>>>>>>> + $zonefiles{$name}{'remark'} = $remark;
>>>>>>>> + } else {
>>>>>>>> + # Skip entry if it is marked as enabled but not found by rpz-config. It was then deleted manually
>>>>>>>> + if($enabled ne 'on') {
>>>>>>>> + # Populate global data hash
>>>>>>>> + my %entry = ('enabled' => 'off',
>>>>>>>> + 'url' => $url // "",
>>>>>>>> + 'remark' => $remark);
>>>>>>>> +
>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>> + }
>>>>>>>> + }
>>>>>>>> + }
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Save internal zonefiles configuration
>>>>>>>> +sub _zonefiles_save_conf {
>>>>>>>> + my $index = 0;
>>>>>>>> + my %export = ();
>>>>>>>> +
>>>>>>>> + # Loop trough all zonefiles and create "hasharray" type export
>>>>>>>> + foreach my $name (keys %zonefiles) {
>>>>>>>> + my @entry = ($name,
>>>>>>>> + $zonefiles{$name}{'enabled'},
>>>>>>>> + $zonefiles{$name}{'url'},
>>>>>>>> + $zonefiles{$name}{'remark'});
>>>>>>>> +
>>>>>>>> + $export{$index++} = \@entry;
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + &General::writehasharray($ZONEFILES_CONF, \%export);
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Load custom lists from rpz-config and the internal configuration
>>>>>>>> +sub _customlists_load {
>>>>>>>> + # Clean start
>>>>>>>> + %customlists = ();
>>>>>>>> +
>>>>>>>> + # Load configuration file
>>>>>>>> + my %lists_conf = ();
>>>>>>>> + &General::readhasharray($CUSTOMLISTS_CONF, \%lists_conf);
>>>>>>>> +
>>>>>>>> + # Get list options, enabled by default to start import
>>>>>>>> + $customlists{'allow'}{'enabled'} = $lists_conf{2}[0] // 'on';
>>>>>>>> + $customlists{'block'}{'enabled'} = $lists_conf{2}[1] // 'on';
>>>>>>>> +
>>>>>>>> + # Import enabled list from rpz-config, otherwise retrieve stored or empty list from configuration file
>>>>>>>> + if($customlists{'allow'}{'enabled'} eq 'on') {
>>>>>>>> + &_customlist_import('allow', $RPZ_ALLOWLIST);
>>>>>>>> + } else {
>>>>>>>> + $customlists{'allow'}{'list'} = $lists_conf{0} // [];
>>>>>>>> + }
>>>>>>>> + if($customlists{'block'}{'enabled'} eq 'on') {
>>>>>>>> + &_customlist_import('block', $RPZ_BLOCKLIST);
>>>>>>>> + } else {
>>>>>>>> + $customlists{'block'}{'list'} = $lists_conf{1} // [];
>>>>>>>> + }
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Save internal custom lists configuration
>>>>>>>> +sub _customlists_save_conf {
>>>>>>>> + my %export = ();
>>>>>>>> +
>>>>>>>> + # Match IDs with import function
>>>>>>>> + $export{0} = $customlists{'allow'}{'list'};
>>>>>>>> + $export{1} = $customlists{'block'}{'list'};
>>>>>>>> + $export{2} = [$customlists{'allow'}{'enabled'}, $customlists{'block'}{'enabled'}];
>>>>>>>> +
>>>>>>>> + &General::writehasharray($CUSTOMLISTS_CONF, \%export);
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Import a custom list from plain file, returns empty list if file is missing
>>>>>>>> +sub _customlist_import {
>>>>>>>> + my ($listname, $filename) = @_;
>>>>>>>> + my @list = ();
>>>>>>>> +
>>>>>>>> + # File exists, load and check all lines
>>>>>>>> + if(-f $filename) {
>>>>>>>> + open(my $FH, '<', $filename) or die "Can't read $filename: $!";
>>>>>>>> + while(my $line = <$FH>) {
>>>>>>>> + chomp($line);
>>>>>>>> + push(@list, $line);
>>>>>>>> + }
>>>>>>>> + close($FH);
>>>>>>>> +
>>>>>>>> + # Clean up imported data
>>>>>>>> + &_rpz_validate_customlist(\@list, 1);
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + $customlists{$listname}{'list'} = \@list;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Export a custom list to plain file or clear file if list is disabled
>>>>>>>> +sub _customlist_export {
>>>>>>>> + my ($listname, $filename) = @_;
>>>>>>>> + return unless(defined $customlists{$listname});
>>>>>>>> +
>>>>>>>> + # Write enabled domain list to file, otherwise save empty file
>>>>>>>> + open(my $FH, '>', $filename) or die "Can't write $filename: $!";
>>>>>>>> +
>>>>>>>> + if($customlists{$listname}{'enabled'} eq 'on') {
>>>>>>>> + foreach my $line (@{$customlists{$listname}{'list'}}) {
>>>>>>>> + print $FH "$line\n";
>>>>>>>> + }
>>>>>>>> + } else {
>>>>>>>> + print $FH "; Note: This list is currently disabled by $ENV{'SCRIPT_NAME'}\n";
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + close($FH);
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +
>>>>>>>> +###--- Internal gui functions ---###
>>>>>>>> +
>>>>>>>> +# Show simple message box
>>>>>>>> +sub _print_message {
>>>>>>>> + my ($message, $title) = @_;
>>>>>>>> + $title ||= $Lang::tr{'error messages'};
>>>>>>>> +
>>>>>>>> + &Header::openbox('100%', 'left', $title);
>>>>>>>> + print "<span>$message</span>";
>>>>>>>> + &Header::closebox();
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Show all zone files and related gui elements
>>>>>>>> +sub _print_zonefiles {
>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf'});
>>>>>>>> +
>>>>>>>> + print <<END
>>>>>>>> +<table class="tbl" width="100%">
>>>>>>>> + <tr>
>>>>>>>> + <th>$Lang::tr{'name'}</th>
>>>>>>>> + <th>URL</th>
>>>>>>>> + <th>$Lang::tr{'remark'}</th>
>>>>>>>> + <th colspan="3">$Lang::tr{'action'}</th>
>>>>>>>> + </tr>
>>>>>>>> +END
>>>>>>>> +;
>>>>>>>> +
>>>>>>>> + # Sort zonefiles by name and loop trough all entries
>>>>>>>> + foreach my $name (sort keys %zonefiles) {
>>>>>>>> +
>>>>>>>> + # Toggle button label translation
>>>>>>>> + my $toggle_tr = ($zonefiles{$name}{'enabled'} eq 'on') ? $Lang::tr{'click to disable'} : $Lang::tr{'click to enable'};
>>>>>>>> +
>>>>>>>> + print <<END
>>>>>>>> + <tr>
>>>>>>>> + <td>$name</td>
>>>>>>>> + <td>$zonefiles{$name}{'url'}</td>
>>>>>>>> + <td>$zonefiles{$name}{'remark'}</td>
>>>>>>>> +
>>>>>>>> + <td align="center" width="5%">
>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_TOGGLE">
>>>>>>>> + <input type="image" src="/images/$zonefiles{$name}{'enabled'}.gif" title="$toggle_tr" alt="$toggle_tr">
>>>>>>>> + </form>
>>>>>>>> + </td>
>>>>>>>> + <td align="center" width="5%">
>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_EDIT">
>>>>>>>> + <input type="image" src="/images/edit.gif" title="$Lang::tr{'edit'}" alt="$Lang::tr{'edit'}">
>>>>>>>> + </form>
>>>>>>>> + </td>
>>>>>>>> + <td align="center" width="5%">
>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_REMOVE">
>>>>>>>> + <input type="image" src="/images/delete.gif" title="$Lang::tr{'remove'}" alt="$Lang::tr{'remove'}">
>>>>>>>> + </form>
>>>>>>>> + </td>
>>>>>>>> + </tr>
>>>>>>>> +END
>>>>>>>> +;
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Disable reload button if not needed
>>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>>> +
>>>>>>>> + print <<END
>>>>>>>> +</table>
>>>>>>>> +
>>>>>>>> +<div class="right">
>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>> + <input type="hidden" name="KEY" value="">
>>>>>>>> + <button type="submit" name="ACTION" value="ZF_EDIT">$Lang::tr{'add'}</button>
>>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>>> + </form>
>>>>>>>> +</div>
>>>>>>>> +END
>>>>>>>> +;
>>>>>>>> +
>>>>>>>> + &Header::closebox();
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Show zonefiles entry editor
>>>>>>>> +sub _print_zonefile_editor {
>>>>>>>> +
>>>>>>>> + # Key specified: Edit existing entry
>>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>>> + # Load data to be edited, but don't override already present values (allows user to edit after error)
>>>>>>>> + $cgiparams{'ZF_NAME'} //= $action_key;
>>>>>>>> + $cgiparams{'ZF_URL'} //= $zonefiles{$action_key}{'url'};
>>>>>>>> + $cgiparams{'ZF_REMARK'} //= $zonefiles{$action_key}{'remark'};
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Fallback to empty form
>>>>>>>> + $cgiparams{'ZF_NAME'} //= "";
>>>>>>>> + $cgiparams{'ZF_URL'} //= "";
>>>>>>>> + $cgiparams{'ZF_REMARK'} //= "";
>>>>>>>> +
>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf editor'});
>>>>>>>> +
>>>>>>>> + print <<END
>>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>> +<input type="hidden" name="KEY" value="$action_key">
>>>>>>>> +<table width="100%">
>>>>>>>> + <tr>
>>>>>>>> + <td width="20%">$Lang::tr{'name'}: <img src="/blob.gif" alt="*"></td>
>>>>>>>> + <td><input type="text" name="ZF_NAME" value="$cgiparams{'ZF_NAME'}" size="40" maxlength="32" title="$Lang::tr{'rpz zf remark info'}" pattern="[a-zA-Z0-9_]{1,32}" required></td>
>>>>>>>> + </tr>
>>>>>>>> + <tr>
>>>>>>>> + <td width="20%">URL: <img src="/blob.gif" alt="*"></td>
>>>>>>>> + <td><input type="url" name="ZF_URL" value="$cgiparams{'ZF_URL'}" size="40" maxlength="128" required></td>
>>>>>>>> + </tr>
>>>>>>>> + <tr>
>>>>>>>> + <td width="20%">$Lang::tr{'remark'}:</td>
>>>>>>>> + <td><input type="text" name="ZF_REMARK" value="$cgiparams{'ZF_REMARK'}" size="40" maxlength="32"></td>
>>>>>>>> + </tr>
>>>>>>>> + <tr>
>>>>>>>> + <td colspan="2"><hr></td>
>>>>>>>> + </tr>
>>>>>>>> + <tr>
>>>>>>>> + <td width="55%"><img src="/blob.gif" alt="*"> $Lang::tr{'required field'}</td>
>>>>>>>> + <td align="right"><button type="submit" name="ACTION" value="ZF_SAVE">$Lang::tr{'save'}</button></td>
>>>>>>>> + </tr>
>>>>>>>> +</table>
>>>>>>>> +</form>
>>>>>>>> +
>>>>>>>> +<div class="right">
>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>> + <button type="submit" name="ACTION" value="NONE">$Lang::tr{'back'}</button>
>>>>>>>> + </form>
>>>>>>>> +</div>
>>>>>>>> +END
>>>>>>>> +;
>>>>>>>> +
>>>>>>>> + &Header::closebox();
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Show custom allow/block files and related gui elements
>>>>>>>> +sub _print_customlists {
>>>>>>>> +
>>>>>>>> + # Load lists from config, unless they are currently being edited
>>>>>>>> + if($action ne 'CL_SAVE') {
>>>>>>>> + $cgiparams{'ALLOW_LIST'} = join("\n", @{$customlists{'allow'}{'list'}});
>>>>>>>> + $cgiparams{'BLOCK_LIST'} = join("\n", @{$customlists{'block'}{'list'}});
>>>>>>>> +
>>>>>>>> + $cgiparams{'ALLOW_ENABLED'} = ($customlists{'allow'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>>> + $cgiparams{'BLOCK_ENABLED'} = ($customlists{'block'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Fallback to empty form
>>>>>>>> + $cgiparams{'ALLOW_LIST'} //= "";
>>>>>>>> + $cgiparams{'BLOCK_LIST'} //= "";
>>>>>>>> +
>>>>>>>> + # HTML checkboxes, unchecked = no or undef value in POST data
>>>>>>>> + my %checked = ();
>>>>>>>> + $checked{'ALLOW_ENABLED'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? " checked" : "";
>>>>>>>> + $checked{'BLOCK_ENABLED'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? " checked" : "";
>>>>>>>> +
>>>>>>>> + # Disable reload button if not needed
>>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>>> +
>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz cl'});
>>>>>>>> +
>>>>>>>> + print <<END
>>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>> +<table width="100%">
>>>>>>>> + <tr>
>>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl allow'}</b><br>$Lang::tr{'rpz cl allow info'}</td>
>>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl block'}</b><br>$Lang::tr{'rpz cl block info'}</td>
>>>>>>>> + </tr>
>>>>>>>> + <tr>
>>>>>>>> + <td colspan="2"><textarea name="ALLOW_LIST" class="domainlist" cols="45">
>>>>>>>> +$cgiparams{'ALLOW_LIST'}</textarea></td>
>>>>>>>> + <td colspan="2"><textarea name="BLOCK_LIST" class="domainlist" cols="45">
>>>>>>>> +$cgiparams{'BLOCK_LIST'}</textarea></td>
>>>>>>>> + </tr>
>>>>>>>> + <tr>
>>>>>>>> + <td><label for="allow_enabled">$Lang::tr{'rpz cl allow enable'}</label></td>
>>>>>>>> + <td width="15%"><input type="checkbox" name="ALLOW_ENABLED" id="allow_enabled"$checked{'ALLOW_ENABLED'}></td>
>>>>>>>> + <td><label for="block_enabled">$Lang::tr{'rpz cl block enable'}</label></td>
>>>>>>>> + <td width="15%"><input type="checkbox" name="BLOCK_ENABLED" id="block_enabled"$checked{'BLOCK_ENABLED'}></td>
>>>>>>>> + </tr>
>>>>>>>> + <tr>
>>>>>>>> + <td colspan="4"><hr></td>
>>>>>>>> + </tr>
>>>>>>>> + <tr>
>>>>>>>> + <td align="right" colspan="4">
>>>>>>>> + <button type="submit" name="ACTION" value="CL_SAVE">$Lang::tr{'save'}</button>
>>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>>> + </td>
>>>>>>>> +</table>
>>>>>>>> +</form>
>>>>>>>> +END
>>>>>>>> +;
>>>>>>>> +
>>>>>>>> + &Header::closebox();
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Output javascript and extra gui elements
>>>>>>>> +sub _print_gui_extras {
>>>>>>>> +
>>>>>>>> + # Apply/Restart button modifier key handler
>>>>>>>> + if(&_rpz_needs_reload()) {
>>>>>>>> + print <<END
>>>>>>>> +<script>
>>>>>>>> + // Commit modifier key handler
>>>>>>>> + (function(jq, document) {
>>>>>>>> + var keyEventsOn = false; // Keyboard events attached
>>>>>>>> + var keyModify = false; // Modifier key pressed
>>>>>>>> + var mouseHover = false; // Mouse over commit button
>>>>>>>> + var btnModified = false; // Button modified to "Restart"
>>>>>>>> +
>>>>>>>> + // Document-level key events, enable only while cursor is over button
>>>>>>>> + function attachKeyEvents() {
>>>>>>>> + if(keyEventsOn) {
>>>>>>>> + return;
>>>>>>>> + }
>>>>>>>> + keyEventsOn = true;
>>>>>>>> +
>>>>>>>> + jq(document).on("keydown.rpz", function(event) {
>>>>>>>> + if((!keyModify) && event.shiftKey) {
>>>>>>>> + keyModify = true;
>>>>>>>> + handleModify();
>>>>>>>> + }
>>>>>>>> + });
>>>>>>>> + jq(document).on("keyup.rpz", function(event) {
>>>>>>>> + if(keyModify && (!event.shiftKey)) {
>>>>>>>> + keyModify = false;
>>>>>>>> + handleModify();
>>>>>>>> + }
>>>>>>>> + });
>>>>>>>> + }
>>>>>>>> + function removeKeyEvents() {
>>>>>>>> + keyModify = false;
>>>>>>>> + if(keyEventsOn) {
>>>>>>>> + jq(document).off("keydown.rpz keyup.rpz");
>>>>>>>> + keyEventsOn = false;
>>>>>>>> + }
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + // Attach mouse hover events to commit buttons
>>>>>>>> + function attachMouseEvents() {
>>>>>>>> + jq("button.commit").on("mouseenter", function(event) {
>>>>>>>> + if(!mouseHover) {
>>>>>>>> + mouseHover = true;
>>>>>>>> + attachKeyEvents();
>>>>>>>> + // Handle already pressed key
>>>>>>>> + keyModify = !!(event.shiftKey);
>>>>>>>> + handleModify();
>>>>>>>> + }
>>>>>>>> + });
>>>>>>>> +
>>>>>>>> + // Cursor moved away: Disable key listener to minimize events
>>>>>>>> + jq("button.commit").on("mouseleave", function() {
>>>>>>>> + if(mouseHover) {
>>>>>>>> + mouseHover = false;
>>>>>>>> + removeKeyEvents();
>>>>>>>> + handleModify();
>>>>>>>> + }
>>>>>>>> + });
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + // Modify commit button
>>>>>>>> + function handleModify() {
>>>>>>>> + let modify = mouseHover && keyModify;
>>>>>>>> + if(btnModified != modify) {
>>>>>>>> + if(modify) {
>>>>>>>> + jq("button.commit").text("$Lang::tr{'restart'}").val("UNB_RESTART");
>>>>>>>> + } else {
>>>>>>>> + jq("button.commit").text("$Lang::tr{'rpz apply'}").val("RPZ_RELOAD");
>>>>>>>> + }
>>>>>>>> + btnModified = modify;
>>>>>>>> + }
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + // jQuery DOM ready
>>>>>>>> + jq(function() {
>>>>>>>> + attachMouseEvents();
>>>>>>>> + });
>>>>>>>> + })(jQuery, document);
>>>>>>>> +</script>
>>>>>>>> +END
>>>>>>>> +;
>>>>>>>> + } # End of modifier key handler
>>>>>>>> +
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +
>>>>>>>> +###--- Internal action processing functions ---###
>>>>>>>> +
>>>>>>>> +# Toggle zonefile on/off
>>>>>>>> +sub _action_zf_toggle {
>>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>>> +
>>>>>>>> + my $result = 0;
>>>>>>>> + my $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>>> +
>>>>>>>> + # Perform toggle action
>>>>>>>> + if($enabled eq 'on') {
>>>>>>>> + $enabled = 'off';
>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>>> + } else {
>>>>>>>> + $enabled = 'on';
>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $action_key, $zonefiles{$action_key}{'url'}, '--no-reload');
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>> +
>>>>>>>> + # Save changes
>>>>>>>> + $zonefiles{$action_key}{'enabled'} = $enabled;
>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>> +
>>>>>>>> + return 1;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Remove zonefile
>>>>>>>> +sub _action_zf_remove {
>>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>>> +
>>>>>>>> + # Remove from rpz-config if currently active
>>>>>>>> + if($zonefiles{$action_key}{'enabled'} eq 'on') {
>>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>>> +
>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Remove from data hash and save changes
>>>>>>>> + delete $zonefiles{$action_key};
>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>> +
>>>>>>>> + # Clear action_key, as the entry is now removed entirely
>>>>>>>> + $action_key = "";
>>>>>>>> +
>>>>>>>> + return 1;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Create or update zonefile entry
>>>>>>>> +# Returns undef if gui needs to stay in editor mode
>>>>>>>> +sub _action_zf_save {
>>>>>>>> + my $result = 0;
>>>>>>>> +
>>>>>>>> + my $name = $cgiparams{'ZF_NAME'} // "";
>>>>>>>> + my $url = $cgiparams{'ZF_URL'} // "";
>>>>>>>> + my $remark = $cgiparams{'ZF_REMARK'} // "";
>>>>>>>> + my $enabled = 'on'; # Enable new entries by default
>>>>>>>> +
>>>>>>>> + # Note on variables:
>>>>>>>> + # name = unique key, will be used to address the entry
>>>>>>>> + # action_key = name of the entry being edited, empty for new entry
>>>>>>>> +
>>>>>>>> + # Only check for unique name if it changed
>>>>>>>> + # (this also checks new entries because the action_key is empty in this case)
>>>>>>>> + $result = &_rpz_validate_zonefile($name, $url, $remark, (lc($name) ne lc($action_key)));
>>>>>>>> + return unless &_rpz_check_result($result, 0);
>>>>>>>> +
>>>>>>>> + # Edit existing entry: Determine what was changed
>>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>>> + # Name und URL remain unchanged, only save remark and finish
>>>>>>>> + if(($name eq $action_key) && ($url eq $zonefiles{$action_key}{'url'})) {
>>>>>>>> + $zonefiles{$action_key}{'remark'} = $remark;
>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>> +
>>>>>>>> + return 1;
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Entry was changed and needs to be recreated, preserve status
>>>>>>>> + $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>>> +
>>>>>>>> + # Remove from rpz-config
>>>>>>>> + return unless &_action_zf_remove();
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Add new entry to rpz-config
>>>>>>>> + if($enabled eq 'on') {
>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $name, $url, '--no-reload');
>>>>>>>> +
>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Add to global data hash and save changes
>>>>>>>> + my %entry = ('enabled' => $enabled,
>>>>>>>> + 'url' => $url,
>>>>>>>> + 'remark' => $remark);
>>>>>>>> +
>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>> +
>>>>>>>> + return 1;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Save custom lists
>>>>>>>> +sub _action_cl_save {
>>>>>>>> + return unless((defined $cgiparams{'ALLOW_LIST'}) && (defined $cgiparams{'BLOCK_LIST'}));
>>>>>>>> +
>>>>>>>> + my $result = 0;
>>>>>>>> +
>>>>>>>> + my @allowlist = split(/\R/, $cgiparams{'ALLOW_LIST'});
>>>>>>>> + my @blocklist = split(/\R/, $cgiparams{'BLOCK_LIST'});
>>>>>>>> +
>>>>>>>> + # Validate lists
>>>>>>>> + $result = &_rpz_validate_customlist(\@allowlist);
>>>>>>>> + if($result != 0) {
>>>>>>>> + $errormessage = &_rpz_error_tr(202, $result);
>>>>>>>> + return;
>>>>>>>> + }
>>>>>>>> + $result = &_rpz_validate_customlist(\@blocklist);
>>>>>>>> + if($result != 0) {
>>>>>>>> + $errormessage = &_rpz_error_tr(203, $result);
>>>>>>>> + return;
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Add to global data hash and save changes
>>>>>>>> + $customlists{'allow'}{'list'} = \@allowlist;
>>>>>>>> + $customlists{'block'}{'list'} = \@blocklist;
>>>>>>>> + $customlists{'allow'}{'enabled'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? 'on' : 'off';
>>>>>>>> + $customlists{'block'}{'enabled'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? 'on' : 'off';
>>>>>>>> +
>>>>>>>> + &_customlists_save_conf();
>>>>>>>> + &_customlist_export('allow', $RPZ_ALLOWLIST);
>>>>>>>> + &_customlist_export('block', $RPZ_BLOCKLIST);
>>>>>>>> +
>>>>>>>> + # Make new lists, request service reload on success
>>>>>>>> + $result = &General::system('/usr/sbin/rpz-make', 'allowblock', '--no-reload');
>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>> +
>>>>>>>> + return 1;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Trigger rpz-config reload
>>>>>>>> +sub _action_rpz_reload {
>>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>>> +
>>>>>>>> + # Immediately clear flag to prevent multiple reloads
>>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Perform reload, recreate reload flag on error to enable retry
>>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'reload');
>>>>>>>> + if(not &_rpz_check_result($result, 0)) {
>>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>>> + return;
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + return 1;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Trigger unbound restart
>>>>>>>> +sub _action_unb_restart {
>>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>>> +
>>>>>>>> + # Immediately clear flag to prevent multiple restarts
>>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Perform restart, unboundctrl always exits zero
>>>>>>>> + &General::system('/usr/local/bin/unboundctrl', 'restart');
>>>>>>>> +
>>>>>>>> + return 1;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +
>>>>>>>> +###--- Internal rpz-config functions ---###
>>>>>>>> +
>>>>>>>> +# Translate rpz-config exitcodes and messages
>>>>>>>> +# 100-199: rpz-config, 200-299: webgui
>>>>>>>> +sub _rpz_error_tr {
>>>>>>>> + my ($error, $append) = @_;
>>>>>>>> + $append //= '';
>>>>>>>> +
>>>>>>>> + # Translate numeric exit codes
>>>>>>>> + if(looks_like_number($error)) {
>>>>>>>> + if(defined $Lang::tr{"rpz exitcode $error"}) {
>>>>>>>> + $error = $Lang::tr{"rpz exitcode $error"};
>>>>>>>> + }
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + return "RPZ $Lang::tr{'error'}: $error" . &Header::escape($append);
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Check result of rpz-config system call, request reload on success
>>>>>>>> +sub _rpz_check_result {
>>>>>>>> + my ($result, $request_reload) = @_;
>>>>>>>> + $request_reload //= 0;
>>>>>>>> +
>>>>>>>> + # exitcode 0 = success
>>>>>>>> + if($result != 0) {
>>>>>>>> + $errormessage = &_rpz_error_tr($result);
>>>>>>>> + return;
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Set reload flag
>>>>>>>> + if($request_reload) {
>>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + return 1;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Test whether reload flag is set
>>>>>>>> +sub _rpz_needs_reload {
>>>>>>>> + return (-f $RPZ_RELOAD_FLAG);
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Validate a zonefile entry, returns rpz-config exitcode on failure. Use _rpz_check_result to verify.
>>>>>>>> +# unique = check for unique name
>>>>>>>> +sub _rpz_validate_zonefile {
>>>>>>>> + my ($name, $url, $remark, $unique) = @_;
>>>>>>>> + $unique //= 1;
>>>>>>>> +
>>>>>>>> + unless($name =~ /^[a-zA-Z0-9_]{1,32}$/) {
>>>>>>>> + return 101;
>>>>>>>> + }
>>>>>>>> + unless($url =~ /^[\w+\.:;\/\\&@#%?=\-~|!]{1,128}$/) {
>>>>>>>> + return 105;
>>>>>>>> + }
>>>>>>>> + unless($remark =~ /^[\w \-()\.:;*\/\\?!&=]{0,32}$/) {
>>>>>>>> + return 201;
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Check against already existing names
>>>>>>>> + if($unique) {
>>>>>>>> + foreach my $existing (keys %zonefiles) {
>>>>>>>> + if(lc($name) eq lc($existing)) {
>>>>>>>> + return 104;
>>>>>>>> + }
>>>>>>>> + }
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + return 0;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Validate a custom list, returns number of rejected line on failure. Check for non-zero results.
>>>>>>>> +# listref = array reference, cleanup = remove invalid entries instead of returning an error
>>>>>>>> +sub _rpz_validate_customlist {
>>>>>>>> + my ($listref, $cleanup) = @_;
>>>>>>>> + $cleanup //= 0;
>>>>>>>> +
>>>>>>>> + foreach my $index (reverse 0..$#{$listref}) {
>>>>>>>> + my $row = @$listref[$index];
>>>>>>>> + next unless($row); # Skip/allow empty lines
>>>>>>>> +
>>>>>>>> + # Reject/remove everything besides wildcard domains and remarks
>>>>>>>> + if((not &General::validwildcarddomainname($row)) && (not $row =~ /^;[\w \-()\.:;*\/\\?!&=]*$/)) {
>>>>>>>> + unless($cleanup) {
>>>>>>>> + # +1 for user friendly line number and to ensure non-zero exitcode
>>>>>>>> + return $index + 1;
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Remove current row
>>>>>>>> + splice(@$listref, $index, 1);
>>>>>>>> + }
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + return 0;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +
>>>>>>>> +###--- Internal misc functions ---###
>>>>>>>> +
>>>>>>>> +# Send HTTP 303 redirect headers for post/request/get pattern
>>>>>>>> +# (Must be sent before calling &Header::showhttpheaders())
>>>>>>>> +sub _http_prg_redirect {
>>>>>>>> + my $location = "https://$ENV{'SERVER_NAME'}:$ENV{'SERVER_PORT'}$ENV{'SCRIPT_NAME'}";
>>>>>>>> + print "Status: 303 See Other\n";
>>>>>>>> + print "Location: $location\n";
>>>>>>>> +}
>>>>>>>> diff --git a/lfs/rpz b/lfs/rpz
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..7ddbc38e5
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/lfs/rpz
>>>>>>>> @@ -0,0 +1,96 @@
>>>>>>>> +###############################################################################
>>>>>>>> +# #
>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>> +# #
>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>> +# #
>>>>>>>> +###############################################################################
>>>>>>>> +
>>>>>>>> +###############################################################################
>>>>>>>> +# Definitions
>>>>>>>> +###############################################################################
>>>>>>>> +
>>>>>>>> +include Config
>>>>>>>> +
>>>>>>>> +SUMMARY = response policy zone - RPZ reputation system for unbound DNS
>>>>>>>> +
>>>>>>>> +VER = 1.0.0
>>>>>>>> +
>>>>>>>> +THISAPP = rpz-$(VER)
>>>>>>>> +DIR_APP = $(DIR_SRC)/$(THISAPP)
>>>>>>>> +TARGET = $(DIR_INFO)/$(THISAPP)
>>>>>>>> +
>>>>>>>> +PROG = rpz
>>>>>>>> +PAK_VER = 18
>>>>>>>> +
>>>>>>>> +DEPS =
>>>>>>>> +
>>>>>>>> +SERVICES =
>>>>>>>> +
>>>>>>>> +###############################################################################
>>>>>>>> +# Top-level Rules
>>>>>>>> +###############################################################################
>>>>>>>> +
>>>>>>>> +install : $(TARGET)
>>>>>>>> +
>>>>>>>> +check :
>>>>>>>> +
>>>>>>>> +download :
>>>>>>>> +
>>>>>>>> +b2 :
>>>>>>>> +
>>>>>>>> +dist:
>>>>>>>> + @$(PAK)
>>>>>>>> +
>>>>>>>> +###############################################################################
>>>>>>>> +# Installation Details
>>>>>>>> +###############################################################################
>>>>>>>> +
>>>>>>>> +$(TARGET) :
>>>>>>>> + @$(PREBUILD)
>>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>>> +
>>>>>>>> + # RPZ scripts
>>>>>>>> + install --verbose --mode=755 \
>>>>>>>> + $(DIR_CONF)/rpz/{rpz-config,rpz-metrics,rpz-sleep,rpz-make,rpz-functions} \
>>>>>>>> + --target-directory=/usr/sbin
>>>>>>>> +
>>>>>>>> + # RPZ config files
>>>>>>>> + mkdir -pv /etc/unbound/local.d
>>>>>>>> + install --verbose --mode=644 --owner=nobody --group=nobody \
>>>>>>>> + $(DIR_CONF)/rpz/00-rpz.conf \
>>>>>>>> + --target-directory=/etc/unbound/local.d
>>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/local.d
>>>>>>>> +
>>>>>>>> + # RPZ custom list files for allow and block
>>>>>>>> + mkdir -pv /var/ipfire/dns/rpz
>>>>>>>> + touch /var/ipfire/dns/rpz/{allowlist,blocklist}
>>>>>>>> + chown --verbose --recursive nobody:nobody /var/ipfire/dns/rpz
>>>>>>>> +
>>>>>>>> + # RPZ zone files
>>>>>>>> + # create empty RPZ config file to avoid a unbound config error
>>>>>>>> + mkdir -pv /etc/unbound/zonefiles
>>>>>>>> + touch /etc/unbound/zonefiles/allow.rpz
>>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/zonefiles
>>>>>>>> +
>>>>>>>> + # Install addon-specific language-files
>>>>>>>> + install --verbose --mode=004 $(DIR_CONF)/rpz/rpz.*.pl \
>>>>>>>> + --target-directory=/var/ipfire/addon-lang
>>>>>>>> +
>>>>>>>> + # Install backup definition
>>>>>>>> + cp -vf $(DIR_CONF)/backup/includes/rpz /var/ipfire/backup/addons/includes/rpz
>>>>>>>> +
>>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>>> + @$(POSTBUILD)
>>>>>>>> diff --git a/make.sh b/make.sh
>>>>>>>> index 827ea9e77..a77535b13 100755
>>>>>>>> --- a/make.sh
>>>>>>>> +++ b/make.sh
>>>>>>>> @@ -390,7 +390,7 @@ prepareenv() {
>>>>>>>> if [ "${free_space}" -lt "${required_space}" ]; then
>>>>>>>> # Add any consumed space
>>>>>>>> while read -r consumed_space path; do
>>>>>>>> - (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>>> + (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>>> done <<< "$(du --summarize --bytes "${BUILD_DIR}" "${IMAGES_DIR}" "${LOG_DIR}" 2>/dev/null)"
>>>>>>>> fi
>>>>>>>>
>>>>>>>> @@ -2087,6 +2087,7 @@ build_system() {
>>>>>>>> lfsmake2 btrfs-progs
>>>>>>>> lfsmake2 inotify-tools
>>>>>>>> lfsmake2 grub-btrfs
>>>>>>>> + lfsmake2 rpz
>>>>>>>>
>>>>>>>> lfsmake2 linux
>>>>>>>> lfsmake2 rtl8812au
>>>>>>>> diff --git a/src/paks/rpz/install.sh b/src/paks/rpz/install.sh
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..ef99bf742
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/src/paks/rpz/install.sh
>>>>>>>> @@ -0,0 +1,36 @@
>>>>>>>> +#!/bin/bash
>>>>>>>> +###############################################################################
>>>>>>>> +# #
>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>> +# #
>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>> +# #
>>>>>>>> +###############################################################################
>>>>>>>> +#
>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>> +extract_files
>>>>>>>> +restore_backup ${NAME}
>>>>>>>> +
>>>>>>>> +# fix user created files
>>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>>> + /etc/unbound/zonefiles \
>>>>>>>> + /etc/unbound/local.d
>>>>>>>> +
>>>>>>>> +# Update Language cache
>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>> +
>>>>>>>> +# restart unbound to load config file
>>>>>>>> +/etc/init.d/unbound restart
>>>>>>>> diff --git a/src/paks/rpz/uninstall.sh b/src/paks/rpz/uninstall.sh
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..e11427df3
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/src/paks/rpz/uninstall.sh
>>>>>>>> @@ -0,0 +1,38 @@
>>>>>>>> +#!/bin/bash
>>>>>>>> +###############################################################################
>>>>>>>> +# #
>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>> +# #
>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>> +# #
>>>>>>>> +###############################################################################
>>>>>>>> +#
>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>> +
>>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>>> +/etc/init.d/unbound stop
>>>>>>>> +
>>>>>>>> +make_backup ${NAME}
>>>>>>>> +remove_files
>>>>>>>> +
>>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>>> +# "[1723428668] unbound-control[17117:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>>> +
>>>>>>>> +# Update Language cache
>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>> +
>>>>>>>> +# start unbound to load unbound config file
>>>>>>>> +/etc/init.d/unbound start
>>>>>>>> diff --git a/src/paks/rpz/update.sh b/src/paks/rpz/update.sh
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..9bc340bc6
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/src/paks/rpz/update.sh
>>>>>>>> @@ -0,0 +1,52 @@
>>>>>>>> +#!/bin/bash
>>>>>>>> +###############################################################################
>>>>>>>> +# #
>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>> +# #
>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>> +# #
>>>>>>>> +###############################################################################
>>>>>>>> +#
>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>> +
>>>>>>>> +# from update.sh
>>>>>>>> +extract_backup_includes
>>>>>>>> +
>>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>>> +/etc/init.d/unbound stop
>>>>>>>> +
>>>>>>>> +# from uninstall.sh
>>>>>>>> +make_backup ${NAME}
>>>>>>>> +remove_files
>>>>>>>> +
>>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>>> +# "unbound-control[nn:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>>> +
>>>>>>>> +# from install.sh
>>>>>>>> +extract_files
>>>>>>>> +restore_backup ${NAME}
>>>>>>>> +
>>>>>>>> +# fix user created files
>>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>>> + /etc/unbound/zonefiles \
>>>>>>>> + /etc/unbound/local.d
>>>>>>>> +
>>>>>>>> +# Update Language cache
>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>> +
>>>>>>>> +# restart unbound to load config files
>>>>>>>> +/etc/init.d/unbound start
>>>>>>>> --
>>>>>>>> 2.39.5
>>>>>>>>
>>>>>> Jon
>>>>>>
>>>>>>
>>>>>> --
>>>>>> Jon Murphy
>>>>>> jon.murphy@ipfire.org
>>>> Jon
>>>>
>>>>
>>>> --
>>>> Jon Murphy
>>>> jon.murphy@ipfire.org
>>
>>
>
>
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
2025-03-19 10:35 ` Michael Tremer
@ 2025-03-19 18:22 ` Jon Murphy
[not found] ` <afcb2a99-1281-43e3-bd3d-d915024683f6@ipfire.org>
1 sibling, 0 replies; 30+ messages in thread
From: Jon Murphy @ 2025-03-19 18:22 UTC (permalink / raw)
To: development
Michael,
> Where in the code is this implemented? I cannot find anything like this:
Keep in mind I am not a "C" person. Maybe in this section?:
https://git.ipfire.org/?p=thirdparty/unbound.git;a=blob;f=services/authzone.c;hb=30b9cb5f813003d0a2b1c2e678652396615b1b7d#l5875
—
When I was just learning about RPZ I created a separate RPZ files for
testing. When I changed the SOA line with a new serial number, the RPZ
file download would happen in about 5 minutes.
https://people.ipfire.org/~jon/sblack-adhoc.rpz
That is how I found out the SOA line is watched for a serial number change.
I’ll reconfirm my finding.
>>> The second reason is that we have a lot of firewalls out there. Not
>>> all of them will enable this feature and all of the lists, but even
>>> if it is a good chunk, we will generate terabytes of traffic which
>>> put load on the infrastructure and will cost money. It simply is not
>>> what we want to do, regardless of self-hosting those lists and
>>> pulling them from somewhere else.
So I understand, are you thinking of hosting RPZ AXFR (DNS zone
transfer) on IPFire infrastructure?
Jon
On 3/19/25 5:35 AM, Michael Tremer wrote:
> Hello Jon,
>
> Where in the code is this implemented? I cannot find anything like this:
>
> Unbound loads the entire file into memory and then starts parsing it. The only special treatment there is is to check whether the first line is a valid zone entry. It does not even have to be a SOA record.
>
> https://git.ipfire.org/?p=thirdparty/unbound.git;a=blob;f=services/authzone.c;hb=30b9cb5f813003d0a2b1c2e678652396615b1b7d#l1188
>
> I am also concerned that Unbound will not be able to support an upstream proxy for any downloads. The caching situation is also unclear for me, so I believe that we will be looking at writing a custom downloader that implements all these things.
>
> -Michael
>
>> On 19 Mar 2025, at 02:58, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>
>> Michael,
>>
>>> The emphasis is on the repeated downloads of the same list. That is
>> > what cannot happen.
>>
>> The Unbound RPZ code, as installed within IPFire, watches for a change
>> in the SOA line of each RPZ file. This is an example of the first few
>> lines for every RPZ file.
>>
>> $TTL 300
>> @ SOA localhost. root.localhost. 1742298960 43200 3600 86400 300
>> NS localhost.
>> ;
>> ; Title: HaGeZi's Pop-Up Ads DNS Blocklist
>> ; Description: Blocks annoying and malicious pop-up ads.
>>
>> If the SOA serial number changes (e.g. the 1742298960), then Unbound RPZ
>> code does its thing and downloads. Otherwise there is no download.
>>
>>> So there has to be a way to ensure that we won’t download a list again
>> > unless it has actually changed.
>>
>> This should do what you want but I may be missing your point.
>>
>>> DNS has a builtin functionality called AXFR. It simply does the job
>> > for you. I was just wondering whether that was not being used.
>>
>> I need to read about AXFR/IXFR and learn a little more.
>>
>> Jon
>>
>> On 3/17/25 5:35 AM, Michael Tremer wrote:
>>> Good Morning Jon,
>>>
>>>> On 16 Mar 2025, at 17:00, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>
>>>> Michael,
>>>>
>>>> I was reading through you response again an I want to understand this post:
>>>>
>>>>> I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>>> So if RPZ doesn't use HTTPS, what is it using? I am missing a key point here.
>>> The emphasis is on the repeated downloads of the same list. That is what cannot happen.
>>>
>>> Although it might not affect a lot of people in our general user-base, there are some that have a metered connection and will pay for data by volume. Some of the lists I looked at are just under 20 MiB. Therefore we need to keep any traffic down to a minimum. The second reason is that we have a lot of firewalls out there. Not all of them will enable this feature and all of the lists, but even if it is a good chunk, we will generate terabytes of traffic which put load on the infrastructure and will cost money. It simply is not what we want to do, regardless of self-hosting those lists and pulling them from somewhere else.
>>>
>>> So there has to be a way to ensure that we won’t download a list again unless it has actually changed.
>>>
>>> DNS has a builtin functionality called AXFR. It simply does the job for you. I was just wondering whether that was not being used.
>>>
>>> HTTPS is an option because that is simply what we use elsewhere, but extra functionality will have to be built for it.
>>>
>>> -Michael
>>>
>>>> Jon
>>>>
>>>>
>>>> On 2/13/25 3:34 PM, jon wrote:
>>>>> Michael,
>>>>>
>>>>> I’ve read through your comments a few times and I ended up with many more questions.
>>>>>
>>>>>
>>>>>> What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>>> To me the efforts to get new code accepted seem to have changed and it seemed easier in the past. In the past I made the Core Team aware via the Dev Mailing List and wrote a simple two or three paragraphs of "What is it? / What is the value? / Here is the code"
>>>>>
>>>>>
>>>>> So in an effort to move forward: How exactly is something presented to the Core Team?
>>>>>
>>>>> Is there an example of a recent effort that was presented that I can see as a sample? (This type of info can also be added to the Wiki)
>>>>>
>>>>> I understand you want it this way, but I don’t know what exactly is needed. Please be specific.
>>>>>
>>>>>
>>>>> Jon
>>>>>
>>>>> PS - I am not ignoring your other comments, I am just trying to move forward and keep things simple.
>>>>>
>>>>>
>>>>>
>>>>>> On Feb 8, 2025, at 1:27 PM, Michael Tremer <michael.tremer@ipfire.org> wrote:
>>>>>>
>>>>>> Hello Jon,
>>>>>>
>>>>>> Thanks for your reply. And good that you are copying everyone into this conversation.
>>>>>>
>>>>>>> On 8 Feb 2025, at 18:41, jon <jon.murphy@ipfire.org> wrote:
>>>>>>>
>>>>>>> Michael,
>>>>>>>
>>>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort
>>>>>>> Yes, this has been a separate effort (a very public separate effort). Yes, as you pointed this out early on with the "proof-of-concept" and then my request for people to help test RPZ. Nothing was hidden.
>>>>>>>
>>>>>>> This was done because you (and maybe others) did not have the time and I wanted to help and because I needed assistance with RPZ. I tried my best to do this without bothering you.
>>>>>> I don’t that it is accurate that nobody wanted to help on this. The list was always open - although not every email has been replied to swiftly it is also your responsibility to raise a question again if it was missed. People here have open ears.
>>>>>>
>>>>>> It was also stated on this very list on in our documentation that working on something without involving the core team is a risky undertaking. Of course IPFire is free software and so everyone is free to fork if they wish to do so.
>>>>>>
>>>>>>>> and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls.
>>>>>>> You were aware many steps along the way. See your email on July 28, 2024, August 15, 2024, September 30, 2024, December 23, 2024, and January 16. My attempts to get the team involved were met with "things are busy" and sometimes silence. (Yes, I get it, people are busy.)
>>>>>>>
>>>>>>> You and Adolf, Leo, Erik and Bernhard have been aware since the beginning. You mention you were aware of the "proof-of-concept". If you include those beginning posts, since Sep 2023.
>>>>>> Yes, I am aware of a proof-of-concept that I have been running myself for a long time. I am also aware of the efforts that you have been taking.
>>>>>>
>>>>>> Yet I don’t think there has ever been any joint effort, or am I seeing that wrong?
>>>>>>
>>>>>>>> This has not been discussed . . . on our calls.
>>>>>>> On the July 28th you stated:
>>>>>>> "We have talked about RPZ many times on the monthly call since the URL filter feature is falling more and more out of fashion. I think there is also many posts about this on the forum."
>>>>>>>
>>>>>>> Please don’t insult me again by stating "you know what I mean".
>>>>>>>
>>>>>>> And it has been discussed but not documented in the Monthly Meeting notes.
>>>>>> I am not at all insulting you. I don’t want to take this down to a personal level at all. This is a public mailing list and people who read this don’t need to listen to an argument we are having. They are here for the tech inside IPFire.
>>>>>>
>>>>>> When I wrote that it has not been discussed that does not mean that we have not been touching on the topic. We have been talking about lots of things on the calls, the weather, politics, how our pets are. None of that makes it to the logs. What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>>>>
>>>>>>>> Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation.
>>>>>>> Regular conversation on the Dev Mailing list is many times met with silence. I get it, people are busy.
>>>>>>>
>>>>>>> And regular two-way conversation doesn’t happen on the list. At least not with me. I’d be happy to point out the posts that were met with silence.
>>>>>>> Again, I get it, people are busy.
>>>>>> And you think my emails are not being met with silence? This has nothing to do with this specific topic. This has something to do with how occupied people are and how engaged they are on certain topics. Not everyone is involved in all the things and simply will ignore emails simply based on their subject line.
>>>>>>
>>>>>>> But the "dip here to the list" were my attempts to get a conversation started. As I said, many time met with silence.
>>>>>>>
>>>>>>> The only place I was not met with silence was on the Community. You have a great group of people in the Community. It is a shame you don’t want to have others help. It would reduce your workload.
>>>>>> You should stop making statements that are not true. Who doesn’t want anyone to help?
>>>>>>
>>>>>> Not having this conversation on a Saturday evening would reduce my workload. At least it would free up time for something else. Helping with the things that are already on the go would reduce the workload of the entire team. Starting one thing at a time and finishing it is a lot better to manage than starting a hundred things and not even finish one. I can tell you that I already have a hundred things on the go.
>>>>>>
>>>>>>>> Therefore, what am I supposed to do with this email?
>>>>>>> To me it is beyond obvious…
>>>>>>>
>>>>>>> If it isn’t what you want, then guide me with how to do this the correct way. And be specific. I am trying to help. I am trying to make things better. I am trying to do things the right way.
>>>>>> To me it isn’t. This is yet another project that has been dumped to the list like so many before and later on everyone has left to have the team deal with the rest.
>>>>>>
>>>>>> It is a huge patch set. You explained what the vision is, but that is about it. There is no chance this will continue if this disagreement isn’t solved first. I didn’t even look at the code.
>>>>>>
>>>>>>>> I don’t want to merge code that I don’t agree with.
>>>>>>> I asked multiple times if you "agreed with the concept" and again, met with silence. Yes I get it, people are busy.
>>>>>> Having support for RPZ? Yes, it was definitely on the roadmap. That I agree with.
>>>>>>
>>>>>>>> So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>>> You mentioned this a in the past, but for some reason you do not disclose what I dismissed. Why do you continue to make this harder, wouldn’t it not be easier to tell me what I have dismissed?
>>>>>>>
>>>>>>> I have sent multiple emails trying to answer your concerns and comments. On July 28, Aug 14, Aug 22, Aug 23, Sep 30, etc.
>>>>>>>
>>>>>>> I’ve gone through all of the questions you asked and I cannot find a "dismissed" item.
>>>>>> Maybe I need to be *more clear*. I feel humoured by this.
>>>>>>
>>>>>> It is late on a Saturday and I want my dinner soon, but certainly I have stated that this should never be an add-on considering it is supposed to replace URL Filter. We should never allow people to add their own sources. I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>>>>>
>>>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>>> The maintainers of Unbound and/or RPZ?
>>>>>>>
>>>>>>> The maintainers of Hagezi list, the threatfox list, the urlhaus list, etc.?
>>>>>>>
>>>>>>> What else? The maintainers or the RPZ scripts? That is me. Let’s talk!
>>>>>> You. I don’t care much about the providers of the lists.
>>>>>>
>>>>>>> See, this is where it gets confusing. There are hundreds of open source packages as part of IPFire. Pick the last five years of items added to the IPFire build. You're telling me you have "constructive conversation with the maintainers" of all of the added packages?
>>>>>> They publish their software and they don’t care whether I am pulling it or not. They publish it with the commitment to maintain it - sometimes for better and sometimes for worse.
>>>>>>
>>>>>> You care about me pulling your code and I don’t know whether you would commit to maintain this.
>>>>>>
>>>>>> These two are very different cases.
>>>>>>
>>>>>>> Pick the IP Blocklists list (i.e., 3CORESEC, ABUSECH, DSHIELD, SPAMHAUS, etc.) or the Suricata lists (i.e.,Emergingthreats.net <http://emergingthreats.net/>,Abuse.ch <http://abuse.ch/>, etc.). So you’ve have "constructive conversation with the maintainers"?
>>>>>> Yes, occasionally I have phone calls with a few of these providers.
>>>>>>
>>>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>>> Ha! Yes a surprise. In the beginning you seemed interested as IPFire needed a replacement for URL Filter. You asked good questions about the lists picked, asked for the value to the users, etc. And I answered the best I could.
>>>>>>>
>>>>>>> You even asked: “Why is this realised as an add-on and not part of the core system?” from your Jul 28, 2024 email.
>>>>>> Ah, so, why is the patch creating an add-on? Not that I am saying that what I say is law, but it has not been challenged either. If my input is being ignored, why should I put this to the top of my list of priorities? I am not disappointed about this, just trying to be very good with my time.
>>>>>>
>>>>>>> And on January 16, 2025 I wrote a message looking for help. And you were kind to respond quickly. So in three weeks time, since the kind response, something has changed. You went from supportive to "this".
>>>>>>>
>>>>>>> So yes, I am surprised.
>>>>>> Well, maybe I should not have replied to that email. It was clear that you were on some path that was not right, but you were not interested before in finding the right path from the beginning.
>>>>>>
>>>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>>> Be more specific, what has to change? What exactly did I dismiss?
>>>>>> Dismissal is just my assumption. I don’t know what you actually did with my feedback. I can only see the end product that does not seem contain much of it. Repeatedly I have been pointing out that we should think before we build. I am sure a lot of hours have now gone into some code that simply does not satisfy me. And I am not not talking about the code itself, what it does is what I don’t think is right for us.
>>>>>>
>>>>>> The process is very clear for me that we should first of all think whether we want a certain feature now. Then there should be a clear roadmap for everyone to follow; tasks can be split-up as we go and hopefully then have something that is maintainable, interesting for our users and even would do us proud. This is how this should work.
>>>>>>
>>>>>> So, what has to change? I don’t think with shouting at each other, throwing patches around and making me generally unhappy is a good start.
>>>>>>
>>>>>> -Michael
>>>>>>
>>>>>>> Jon
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>>> On Feb 6, 2025, at 2:13 PM, Michael Tremer <michael.tremer@ipfire.org> wrote:
>>>>>>>>
>>>>>>>> Hello Jon,
>>>>>>>>
>>>>>>>> Well, here we are again with another patch regarding this feature.
>>>>>>>>
>>>>>>>> I cannot quite see from your email what the question is, but if this is a request to have this merged into IPFire, I am once again sorry to disappoint you.
>>>>>>>>
>>>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls. Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation. Therefore, what am I supposed to do with this email?
>>>>>>>>
>>>>>>>> I don’t want to merge code that I don’t agree with. So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>>>>
>>>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>>>>
>>>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>>>>
>>>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>>>>
>>>>>>>> All the best,
>>>>>>>> -Michael
>>>>>>>>
>>>>>>>>> On 6 Feb 2025, at 16:35, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>>>
>>>>>>>>> What is it?
>>>>>>>>> Response Policy Zone (RPZ) is a mechanism to define local policies in a
>>>>>>>>> standardized way and load those policies from external sources.
>>>>>>>>> Bottom line: RPZ allows admins to easily block access to websites via DNS lookup.
>>>>>>>>>
>>>>>>>>> RPZ can block websites via categories. Examples include: fake websites, annoying
>>>>>>>>> pop-up ads, newly registered domains, DoH bypass sites, bad "host" services,
>>>>>>>>> maliscious top level domains (e.g., *.zip, *.mov), piracy, gambling, pornography,
>>>>>>>>> and more. RPZ lists come from various RPZ providers and their available
>>>>>>>>> catagories.
>>>>>>>>>
>>>>>>>>> This RPZ add-on enables the RPZ functionality by adding a couple lines in a
>>>>>>>>> configuration file. This add-on simply adds configuration files and adds
>>>>>>>>> scripts (config, metrics and sleep) to make RPZ easier for the admin to use.
>>>>>>>>>
>>>>>>>>> The RPZ scripts include additional languages: German, Spanish, French, Turkish,
>>>>>>>>> and Italian.
>>>>>>>>>
>>>>>>>>> RPZ itself was release in 2010 and has been part of the IPFire build since ~2015.
>>>>>>>>>
>>>>>>>>> Why is it needed? What is its value?
>>>>>>>>>
>>>>>>>>> - The RPZ concept places this filtering into IPFire, our internet access
>>>>>>>>> gateway, which is (should be) solely used as DNS source of the internal network.
>>>>>>>>>
>>>>>>>>> - As most sites use HTTPS it makes it difficult to filter traffic with URL
>>>>>>>>> Filter without also properly configuring conventional (non-transparent)
>>>>>>>>> mode on the proxy. RPZ is a nice replacement for the URL Filter.
>>>>>>>>>
>>>>>>>>> - No need to install and maintain an additional device like PiHole or AdBlock
>>>>>>>>> browser extensions on multiple user devices.
>>>>>>>>>
>>>>>>>>> - This is an additional layer of protection for users. Less worry someone will
>>>>>>>>> click on something that gets them into trouble. And, saying this with emphasis,
>>>>>>>>> the ability to do it in one place!
>>>>>>>>>
>>>>>>>>> - Blocked sites save on unneeded traffic and can lessen the threat of malware
>>>>>>>>> in advertisements
>>>>>>>>>
>>>>>>>>> - Logging allows the admin to see the site blocked and take actions
>>>>>>>>>
>>>>>>>>> - RPZ will be used at the home, home-office (work from home), schools,
>>>>>>>>> ministerial, and at the office. Device counts are small (2-6) to medium (~80)
>>>>>>>>> to mediam-large (200+).
>>>>>>>>>
>>>>>>>>> - RPZ can block ads, popups, phishing, scammers, spyware, malware, annoying
>>>>>>>>> popups, NSFW links, DOH servers, and the usual internet trash.
>>>>>>>>>
>>>>>>>>> ------------------------------
>>>>>>>>>
>>>>>>>>> Change Log for RPZ add-on
>>>>>>>>>
>>>>>>>>> rpz-1.0.0-18 on 2025-02-05
>>>>>>>>> - Build for approval & release as IPFire add-on
>>>>>>>>>
>>>>>>>>> ---
>>>>>>>>>
>>>>>>>>> rpz-beta-0.1.18-18.ipfire on 2025-02-01
>>>>>>>>> rpz.cgi:
>>>>>>>>> - new feature: added a mod key to force a unbound restart
>>>>>>>>>
>>>>>>>>> rpz-config and rpz-make:
>>>>>>>>> - new feature: added action for unbound restart `rpz-config unbound-restart`
>>>>>>>>>
>>>>>>>>> rpz-metrics:
>>>>>>>>> - simple reformatting
>>>>>>>>> - rename far right column from "last update" to "last download"
>>>>>>>>>
>>>>>>>>> ---
>>>>>>>>>
>>>>>>>>> rpz-beta-0.1.17-17.ipfire on 2024-12-09
>>>>>>>>> rpz-make
>>>>>>>>> - bug fix: corrected validation regex for wildcards like: `*.domain.com`
>>>>>>>>>
>>>>>>>>> ---
>>>>>>>>>
>>>>>>>>> rpz-beta-0.1.16-16.ipfire on 2024-11-18
>>>>>>>>> rpz-make
>>>>>>>>> - new feature: updated validation regex
>>>>>>>>> - bug fix: moved validation to beginning of process. Now we validate before
>>>>>>>>> creating config files.
>>>>>>>>>
>>>>>>>>> rpz.cgi:
>>>>>>>>> - new feature: use CSS color variables of the main ipfire theme
>>>>>>>>> - bug fix: empty zonefile remarks were stored as “undef” and caused a warning
>>>>>>>>> - bug fix: HTML textarea removes the first empty line in a custom list
>>>>>>>>> - thank you Leo!
>>>>>>>>>
>>>>>>>>> ---
>>>>>>>>>
>>>>>>>>> rpz-beta-0.1.15-15.ipfire on 2024-11-04
>>>>>>>>> rpz.cgi:
>>>>>>>>> - new feature: added new language file for Turkish (thank you Peppe)
>>>>>>>>>
>>>>>>>>> rpz-make
>>>>>>>>> - bug fix: corrected empty allow/block list issue. An empty allow/block list
>>>>>>>>> will now remove contents of allow/block.rpz files and remove unneeded
>>>>>>>>> allow/block.conf file. (thank you iptom)
>>>>>>>>>
>>>>>>>>> ---
>>>>>>>>>
>>>>>>>>> rpz-beta-0.1.14-14.ipfire on 2024-10-29
>>>>>>>>> rpz-config:
>>>>>>>>> - bug fix: correct missing rpz extension. `rpz-config list` displayed URL
>>>>>>>>> incorrectly (thank you Bernhard)
>>>>>>>>>
>>>>>>>>> rpz.cgi:
>>>>>>>>> - bug fix: remove extra `"` in language files (thank you Bernhard)
>>>>>>>>> - new feature: slightly dim "apply" button when not enabled
>>>>>>>>>
>>>>>>>>> ---
>>>>>>>>>
>>>>>>>>> rpz-beta-0.1.13-13.ipfire on 2024-10-27
>>>>>>>>> - skipped
>>>>>>>>>
>>>>>>>>> ---
>>>>>>>>>
>>>>>>>>> rpz-beta-0.1.12-12.ipfire on 2024-10-21
>>>>>>>>> rpz.cgi:
>>>>>>>>> - new feature: added new language file for French (thank you gw-ipfire)
>>>>>>>>>
>>>>>>>>> ---
>>>>>>>>>
>>>>>>>>> rpz-beta-0.1.11-11.ipfire on 2024-10-18
>>>>>>>>> rpz.cgi:
>>>>>>>>> - new feature: added new language file for Italian (thank you umberto)
>>>>>>>>> - new feature: added new language file for Spanish (thank you Roberto)
>>>>>>>>>
>>>>>>>>> ---
>>>>>>>>>
>>>>>>>>> rpz-beta-0.1.10-10.ipfire on 2024-10-15
>>>>>>>>> rpz-make:
>>>>>>>>> - bug fix: corrected validation error for a custom list entry (thank you siosios)
>>>>>>>>> - e.g., `*.cloudflare-dns.com`
>>>>>>>>>
>>>>>>>>> install.sh:
>>>>>>>>> - bug fix: add chown to correct user created files
>>>>>>>>>
>>>>>>>>> update.sh:
>>>>>>>>> - bug fix: add chown to correct user created files (thank you siosios)
>>>>>>>>>
>>>>>>>>> ---
>>>>>>>>>
>>>>>>>>> rpz-beta-0.1.9-9.ipfire on 2024-10-08
>>>>>>>>> rpz.cgi:
>>>>>>>>> - new feature: added new language file for German (thank you Leo)
>>>>>>>>> - bug fix: add missing "rpz exitcode 110"
>>>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>>>
>>>>>>>>> ---
>>>>>>>>>
>>>>>>>>> rpz-beta-0.1.8-8.ipfire on 2024-10-04
>>>>>>>>> - skipped
>>>>>>>>>
>>>>>>>>> ---
>>>>>>>>>
>>>>>>>>> rpz-beta-0.1.7-7.ipfire on 2024-10-03
>>>>>>>>> All:
>>>>>>>>> - new feature: includes beta version numbers for pakfire package,
>>>>>>>>> instead of only `rpz-1.0.0-1.ipfire`, for each release.
>>>>>>>>>
>>>>>>>>> rpz.cgi:
>>>>>>>>> - new feature: added new WebGUI at `rpz.cgi`
>>>>>>>>> - a BIG thank you to Leo Hofmann for all of his work creating the webgui!!
>>>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>>>
>>>>>>>>> rpz-make:
>>>>>>>>> - new feature: validate entries in allowlist and blocklist
>>>>>>>>> - new feature: add "no-reload" option for WebGUI
>>>>>>>>>
>>>>>>>>> rpz-metrics:
>>>>>>>>> - new feature: info can be sorted by name, by hit count, by line count, by
>>>>>>>>> "enabled" list or all lists
>>>>>>>>>
>>>>>>>>> backups:
>>>>>>>>> - bug fix: include all files in `/var/ipfire/dns/rpz` directory in backup
>>>>>>>>>
>>>>>>>>> update.sh:
>>>>>>>>> - bug fix: corrected ownership for `/var/ipfire/dns/rpz` directory during an
>>>>>>>>> update
>>>>>>>>>
>>>>>>>>> Build:
>>>>>>>>> - bug fix: `block.rpz.conf` and `block.rpz` from build. Files to be created
>>>>>>>>> by `rpz-make`
>>>>>>>>>
>>>>>>>>> WebGUI and German language file
>>>>>>>>> Contribution-by: Leo-Andres Hofmann <hofmann@leo-andres.de>
>>>>>>>>>
>>>>>>>>> Spanish language file
>>>>>>>>> Contribution-by: Roberto Peña
>>>>>>>>>
>>>>>>>>> Italian language file
>>>>>>>>> Contribution-by: Umberto Parma
>>>>>>>>>
>>>>>>>>> French language file
>>>>>>>>> Contribution-by: gw-ipfire
>>>>>>>>>
>>>>>>>>> Turkish language file
>>>>>>>>> Contribution-by: Peppe Tech
>>>>>>>>>
>>>>>>>>> Contribution-by: Bernhard Bitsch <bbitsch@ipfire.org>
>>>>>>>>> Contribution-by: Erik Kapfer <erik.kapfer@ipfire.org>
>>>>>>>>> Signed-off-by: Jon Murphy <jon.murphy@ipfire.org
>>>>>>>>> ---
>>>>>>>>> config/backup/includes/rpz | 4 +
>>>>>>>>> config/cfgroot/manualpages | 1 +
>>>>>>>>> config/menu/EX-rpz.menu | 6 +
>>>>>>>>> config/rootfiles/common/configroot | 1 +
>>>>>>>>> config/rootfiles/common/web-user-interface | 1 +
>>>>>>>>> config/rootfiles/packages/rpz | 20 +
>>>>>>>>> config/rpz/00-rpz.conf | 10 +
>>>>>>>>> config/rpz/rpz-config | 130 +++
>>>>>>>>> config/rpz/rpz-functions | 85 ++
>>>>>>>>> config/rpz/rpz-make | 203 +++++
>>>>>>>>> config/rpz/rpz-metrics | 170 ++++
>>>>>>>>> config/rpz/rpz-sleep | 58 ++
>>>>>>>>> config/rpz/rpz.de.pl | 30 +
>>>>>>>>> config/rpz/rpz.en.pl | 30 +
>>>>>>>>> config/rpz/rpz.es.pl | 30 +
>>>>>>>>> config/rpz/rpz.fr.pl | 30 +
>>>>>>>>> config/rpz/rpz.it.pl | 30 +
>>>>>>>>> config/rpz/rpz.tr.pl | 30 +
>>>>>>>>> html/cgi-bin/rpz.cgi | 923 +++++++++++++++++++++
>>>>>>>>> lfs/rpz | 96 +++
>>>>>>>>> make.sh | 3 +-
>>>>>>>>> src/paks/rpz/install.sh | 36 +
>>>>>>>>> src/paks/rpz/uninstall.sh | 38 +
>>>>>>>>> src/paks/rpz/update.sh | 52 ++
>>>>>>>>> 24 files changed, 2016 insertions(+), 1 deletion(-)
>>>>>>>>> create mode 100644 config/backup/includes/rpz
>>>>>>>>> create mode 100644 config/menu/EX-rpz.menu
>>>>>>>>> create mode 100644 config/rootfiles/packages/rpz
>>>>>>>>> create mode 100644 config/rpz/00-rpz.conf
>>>>>>>>> create mode 100644 config/rpz/rpz-config
>>>>>>>>> create mode 100644 config/rpz/rpz-functions
>>>>>>>>> create mode 100644 config/rpz/rpz-make
>>>>>>>>> create mode 100755 config/rpz/rpz-metrics
>>>>>>>>> create mode 100755 config/rpz/rpz-sleep
>>>>>>>>> create mode 100644 config/rpz/rpz.de.pl
>>>>>>>>> create mode 100644 config/rpz/rpz.en.pl
>>>>>>>>> create mode 100644 config/rpz/rpz.es.pl
>>>>>>>>> create mode 100644 config/rpz/rpz.fr.pl
>>>>>>>>> create mode 100644 config/rpz/rpz.it.pl
>>>>>>>>> create mode 100644 config/rpz/rpz.tr.pl
>>>>>>>>> create mode 100644 html/cgi-bin/rpz.cgi
>>>>>>>>> create mode 100644 lfs/rpz
>>>>>>>>> create mode 100644 src/paks/rpz/install.sh
>>>>>>>>> create mode 100644 src/paks/rpz/uninstall.sh
>>>>>>>>> create mode 100644 src/paks/rpz/update.sh
>>>>>>>>>
>>>>>>>>> diff --git a/config/backup/includes/rpz b/config/backup/includes/rpz
>>>>>>>>> new file mode 100644
>>>>>>>>> index 000000000..36513e494
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/config/backup/includes/rpz
>>>>>>>>> @@ -0,0 +1,4 @@
>>>>>>>>> +/var/ipfire/dns/rpz/*
>>>>>>>>> +/etc/unbound/zonefiles/allow.rpz
>>>>>>>>> +/etc/unbound/zonefiles/block.rpz
>>>>>>>>> +/etc/unbound/local.d/*rpz.conf
>>>>>>>>> diff --git a/config/cfgroot/manualpages b/config/cfgroot/manualpages
>>>>>>>>> index 1f7e01efc..d3a48c633 100644
>>>>>>>>> --- a/config/cfgroot/manualpages
>>>>>>>>> +++ b/config/cfgroot/manualpages
>>>>>>>>> @@ -70,6 +70,7 @@ pakfire.cgi=configuration/ipfire/pakfire
>>>>>>>>> wlanap.cgi=addons/wireless
>>>>>>>>> tor.cgi=addons/tor
>>>>>>>>> samba.cgi=addons/samba
>>>>>>>>> +rpz.cgi=addons/rpz
>>>>>>>>>
>>>>>>>>> # Logs menu
>>>>>>>>> logs.cgi/summary.dat=configuration/logs/summary
>>>>>>>>> diff --git a/config/menu/EX-rpz.menu b/config/menu/EX-rpz.menu
>>>>>>>>> new file mode 100644
>>>>>>>>> index 000000000..2f4daf410
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/config/menu/EX-rpz.menu
>>>>>>>>> @@ -0,0 +1,6 @@
>>>>>>>>> +$subipfire->{'20.rpz'} = {
>>>>>>>>> + 'caption' => $Lang::tr{'rpz'},
>>>>>>>>> + 'uri' => '/cgi-bin/rpz.cgi',
>>>>>>>>> + 'title' => "RPZ",
>>>>>>>>> + 'enabled' => 1,
>>>>>>>>> +};
>>>>>>>>> diff --git a/config/rootfiles/common/configroot b/config/rootfiles/common/configroot
>>>>>>>>> index 9839eee45..b30d6aae4 100644
>>>>>>>>> --- a/config/rootfiles/common/configroot
>>>>>>>>> +++ b/config/rootfiles/common/configroot
>>>>>>>>> @@ -120,6 +120,7 @@ var/ipfire/menu.d/70-log.menu
>>>>>>>>> #var/ipfire/menu.d/EX-apcupsd.menu
>>>>>>>>> #var/ipfire/menu.d/EX-guardian.menu
>>>>>>>>> #var/ipfire/menu.d/EX-mympd.menu
>>>>>>>>> +#var/ipfire/menu.d/EX-rpz.menu
>>>>>>>>> #var/ipfire/menu.d/EX-samba.menu
>>>>>>>>> #var/ipfire/menu.d/EX-tor.menu
>>>>>>>>> #var/ipfire/menu.d/EX-transmission.menu
>>>>>>>>> diff --git a/config/rootfiles/common/web-user-interface b/config/rootfiles/common/web-user-interface
>>>>>>>>> index 816241dae..e00464076 100644
>>>>>>>>> --- a/config/rootfiles/common/web-user-interface
>>>>>>>>> +++ b/config/rootfiles/common/web-user-interface
>>>>>>>>> @@ -69,6 +69,7 @@ srv/web/ipfire/cgi-bin/proxy.cgi
>>>>>>>>> srv/web/ipfire/cgi-bin/qos.cgi
>>>>>>>>> srv/web/ipfire/cgi-bin/remote.cgi
>>>>>>>>> srv/web/ipfire/cgi-bin/routing.cgi
>>>>>>>>> +#srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>>>> #srv/web/ipfire/cgi-bin/samba.cgi
>>>>>>>>> srv/web/ipfire/cgi-bin/services.cgi
>>>>>>>>> srv/web/ipfire/cgi-bin/shutdown.cgi
>>>>>>>>> diff --git a/config/rootfiles/packages/rpz b/config/rootfiles/packages/rpz
>>>>>>>>> new file mode 100644
>>>>>>>>> index 000000000..1c8663049
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/config/rootfiles/packages/rpz
>>>>>>>>> @@ -0,0 +1,20 @@
>>>>>>>>> +etc/unbound/local.d/00-rpz.conf
>>>>>>>>> +etc/unbound/zonefiles
>>>>>>>>> +etc/unbound/zonefiles/allow.rpz
>>>>>>>>> +usr/sbin/rpz-config
>>>>>>>>> +usr/sbin/rpz-functions
>>>>>>>>> +usr/sbin/rpz-make
>>>>>>>>> +usr/sbin/rpz-metrics
>>>>>>>>> +usr/sbin/rpz-sleep
>>>>>>>>> +var/ipfire/addon-lang/rpz.de.pl
>>>>>>>>> +var/ipfire/addon-lang/rpz.en.pl
>>>>>>>>> +var/ipfire/addon-lang/rpz.es.pl
>>>>>>>>> +var/ipfire/addon-lang/rpz.fr.pl
>>>>>>>>> +var/ipfire/addon-lang/rpz.it.pl
>>>>>>>>> +var/ipfire/addon-lang/rpz.tr.pl
>>>>>>>>> +var/ipfire/backup/addons/includes/rpz
>>>>>>>>> +var/ipfire/dns/rpz
>>>>>>>>> +var/ipfire/dns/rpz/allowlist
>>>>>>>>> +var/ipfire/dns/rpz/blocklist
>>>>>>>>> +var/ipfire/menu.d/EX-rpz.menu
>>>>>>>>> +srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>>>> diff --git a/config/rpz/00-rpz.conf b/config/rpz/00-rpz.conf
>>>>>>>>> new file mode 100644
>>>>>>>>> index 000000000..f005a4f2e
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/config/rpz/00-rpz.conf
>>>>>>>>> @@ -0,0 +1,10 @@
>>>>>>>>> +server:
>>>>>>>>> + module-config: "respip validator iterator"
>>>>>>>>> +
>>>>>>>>> +rpz:
>>>>>>>>> + name: allow.rpz
>>>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>>>> + rpz-action-override: passthru
>>>>>>>>> + rpz-log: yes
>>>>>>>>> + rpz-log-name: allow
>>>>>>>>> + rpz-signal-nxdomain-ra: yes
>>>>>>>>> diff --git a/config/rpz/rpz-config b/config/rpz/rpz-config
>>>>>>>>> new file mode 100644
>>>>>>>>> index 000000000..c72d50f9b
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/config/rpz/rpz-config
>>>>>>>>> @@ -0,0 +1,130 @@
>>>>>>>>> +#!/bin/bash
>>>>>>>>> +###############################################################################
>>>>>>>>> +# #
>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info@ipfire.org> #
>>>>>>>>> +# #
>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>> +# #
>>>>>>>>> +###############################################################################
>>>>>>>>> +
>>>>>>>>> +version="2025-01-11 - v44"
>>>>>>>>> +
>>>>>>>>> +############### Functions ###############
>>>>>>>>> +
>>>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>>>> +
>>>>>>>>> +############### Main ###############
>>>>>>>>> +
>>>>>>>>> +tagName="unbound"
>>>>>>>>> +
>>>>>>>>> +rpzAction="${1}" # input RPZ action
>>>>>>>>> +rpzName="${2}" # input RPZ name
>>>>>>>>> +rpzURL="${3}" # input RPZ URL
>>>>>>>>> +rpzOption1="${4}" # input RPZ option #1
>>>>>>>>> +rpzOption2="${5}" # input RPZ option #2
>>>>>>>>> +
>>>>>>>>> +rpzConfig="/etc/unbound/local.d/${rpzName}.rpz.conf" # output zone conf file
>>>>>>>>> +rpzFile="/etc/unbound/zonefiles/${rpzName}.rpz" # output for RPZ file
>>>>>>>>> +
>>>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>>>> +
>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>> + case "$1" in
>>>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>>>> + esac
>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>> +done
>>>>>>>>> +
>>>>>>>>> +case "${rpzAction}" in
>>>>>>>>> + # add new rpz list
>>>>>>>>> + add )
>>>>>>>>> + check_name "${rpzName}" # is this a valid name?
>>>>>>>>> + # does this config already exist? If yes, then exit
>>>>>>>>> + if [[ -f "${rpzConfig}" ]] ; then
>>>>>>>>> + msg_log "error: rpz: duplicate - ${rpzConfig} already exists. exit"
>>>>>>>>> + exit 104
>>>>>>>>> + fi
>>>>>>>>> +
>>>>>>>>> + # is this a valid URL?
>>>>>>>>> + regex='^https://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
>>>>>>>>> + if ! [[ "${rpzURL}" =~ $regex ]] ; then
>>>>>>>>> + msg_log "error: rpz: the URL is not valid: \"${rpzURL}\". exit."
>>>>>>>>> + exit 105
>>>>>>>>> + fi
>>>>>>>>> +
>>>>>>>>> + # create the zone config file
>>>>>>>>> + {
>>>>>>>>> + echo "rpz:"
>>>>>>>>> + echo " name: ${rpzName}.rpz"
>>>>>>>>> + echo " zonefile: ${rpzFile}"
>>>>>>>>> + echo " url: ${rpzURL}"
>>>>>>>>> + echo " rpz-action-override: nxdomain"
>>>>>>>>> + echo " rpz-log: ${rpzLog}"
>>>>>>>>> + echo " rpz-log-name: ${rpzName}"
>>>>>>>>> + echo " rpz-signal-nxdomain-ra: yes"
>>>>>>>>> + } > "${rpzConfig}"
>>>>>>>>> +
>>>>>>>>> + # set-up zonefile
>>>>>>>>> + # create an empty rpz file if it does not exist
>>>>>>>>> + if [[ ! -f "${rpzFile}" ]] ; then
>>>>>>>>> + touch "${rpzFile}"
>>>>>>>>> + # unbound requires these settings for rpz files
>>>>>>>>> + set_permissions "${rpzFile}" "${rpzConfig}"
>>>>>>>>> + fi
>>>>>>>>> + ;;
>>>>>>>>> +
>>>>>>>>> + # trash config file & rpz file
>>>>>>>>> + remove )
>>>>>>>>> + if ! [[ -f "${rpzConfig}" ]] ; then
>>>>>>>>> + msg_log "error: rpz: cannot remove ${rpzConfig}, does not exist. exit"
>>>>>>>>> + exit 106
>>>>>>>>> + fi
>>>>>>>>> +
>>>>>>>>> + msg_log "info: rpz: remove config file & rpz file \"${rpzName}\""
>>>>>>>>> + rm "${rpzConfig}"
>>>>>>>>> + rm "${rpzFile}"
>>>>>>>>> + ;;
>>>>>>>>> +
>>>>>>>>> + reload )
>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>> + ;;
>>>>>>>>> +
>>>>>>>>> + list )
>>>>>>>>> + awk -F':' '/^\s*name:/{ gsub(/[[:blank:]]|\.rpz/, "",$2) ; NAME=$2 } \
>>>>>>>>> + /^\s*url:/{ gsub(/[[:blank:]]/, "") ; print NAME"="$2":"$3} ' \
>>>>>>>>> + /etc/unbound/local.d/*rpz.conf
>>>>>>>>> + exit
>>>>>>>>> + ;;
>>>>>>>>> +
>>>>>>>>> + unbound-restart )
>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>> + unbound_restart
>>>>>>>>> + exit
>>>>>>>>> + ;;
>>>>>>>>> +
>>>>>>>>> + * )
>>>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>>>> + printf "Usage: $(basename "$0") <ACTION> <NAME> <URL> <OPTION> <OPTION>\n"
>>>>>>>>> + printf "Version: ${version}\n"
>>>>>>>>> + exit 108
>>>>>>>>> + ;;
>>>>>>>>> +
>>>>>>>>> +esac
>>>>>>>>> +
>>>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>>>> +
>>>>>>>>> +exit
>>>>>>>>> diff --git a/config/rpz/rpz-functions b/config/rpz/rpz-functions
>>>>>>>>> new file mode 100644
>>>>>>>>> index 000000000..ace1d2690
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/config/rpz/rpz-functions
>>>>>>>>> @@ -0,0 +1,85 @@
>>>>>>>>> +#!/bin/bash
>>>>>>>>> +###############################################################################
>>>>>>>>> +# #
>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>> +# #
>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>> +# #
>>>>>>>>> +###############################################################################
>>>>>>>>> +
>>>>>>>>> +version="2024-12-10 - v02"
>>>>>>>>> +
>>>>>>>>> +############### Functions ###############
>>>>>>>>> +
>>>>>>>>> +msg_log () {
>>>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>>>> + if tty --silent ; then
>>>>>>>>> + echo "${tagName}:" "$*"
>>>>>>>>> + fi
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# check for a valid name
>>>>>>>>> +check_name () {
>>>>>>>>> + local theName="${1}"
>>>>>>>>> +
>>>>>>>>> + regex='^[a-zA-Z0-9_]+$' # no dash or plus, alpha numeric only
>>>>>>>>> + regex1='^(allow|block)$' # allow and block are reserved NAMEs
>>>>>>>>> + if [[ ! "${theName}" =~ $regex ]] || [[ "${theName}" =~ $regex1 ]] ; then
>>>>>>>>> + msg_log "error: rpz: the NAME is not valid: \"${theName}\". exit."
>>>>>>>>> + exit 101
>>>>>>>>> + fi
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +set_permissions () {
>>>>>>>>> + chown nobody:nobody "$@"
>>>>>>>>> + chmod 644 "$@"
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +check_unbound_conf () {
>>>>>>>>> + local thecheckConf="${1:-yes}" # check config default is yes
>>>>>>>>> +
>>>>>>>>> + # check the above config files
>>>>>>>>> + if [[ "${thecheckConf}" == yes ]] ; then
>>>>>>>>> + msg_log "info: rpz: check for errors with \"unbound-checkconf\""
>>>>>>>>> +
>>>>>>>>> + if ! unbound-checkconf ; then
>>>>>>>>> + msg_log "error: rpz: unbound-checkconf found invalid configuration."
>>>>>>>>> + msg_log \
>>>>>>>>> + "error: rpz: In Terminal run the command \"unbound-checkconf\" for more information. exit."
>>>>>>>>> + exit 102
>>>>>>>>> + fi
>>>>>>>>> + fi
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +unbound_control_reload () {
>>>>>>>>> + local theReload="${1:-yes}" # reload default is yes
>>>>>>>>> +
>>>>>>>>> + if [[ "${theReload}" == yes ]] ; then
>>>>>>>>> + # reload due to the changes
>>>>>>>>> + msg_log "info: rpz: run \"unbound-control reload\""
>>>>>>>>> +
>>>>>>>>> + if ! unbound-control reload ; then
>>>>>>>>> + msg_log "error: rpz: unbound-control reload. exit."
>>>>>>>>> + exit 109
>>>>>>>>> + fi
>>>>>>>>> + fi
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +unbound_restart () {
>>>>>>>>> + # restart due to the changes
>>>>>>>>> + msg_log "info: rpz: run \"unbound restart\""
>>>>>>>>> +
>>>>>>>>> + /usr/local/bin/unboundctrl restart
>>>>>>>>> +}
>>>>>>>>> diff --git a/config/rpz/rpz-make b/config/rpz/rpz-make
>>>>>>>>> new file mode 100644
>>>>>>>>> index 000000000..927d55170
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/config/rpz/rpz-make
>>>>>>>>> @@ -0,0 +1,203 @@
>>>>>>>>> +#!/bin/bash
>>>>>>>>> +###############################################################################
>>>>>>>>> +# #
>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info@ipfire.org> #
>>>>>>>>> +# #
>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>> +# #
>>>>>>>>> +###############################################################################
>>>>>>>>> +
>>>>>>>>> +version="2025-01-11 - v14"
>>>>>>>>> +
>>>>>>>>> +############### Functions ###############
>>>>>>>>> +
>>>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>>>> +
>>>>>>>>> +# create the config file for allow
>>>>>>>>> +make_allow_config () {
>>>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>>>> + local theConfig="/etc/unbound/local.d/00-rpz.conf" # output zone conf file
>>>>>>>>> + local theList="/var/ipfire/dns/rpz/allowlist" # input custom list of domains
>>>>>>>>> +
>>>>>>>>> + msg_log "info: rpz: make config file \"00-rpz.conf\""
>>>>>>>>> +
>>>>>>>>> + echo "server:
>>>>>>>>> + module-config: \"respip validator iterator\"" > "${theConfig}"
>>>>>>>>> +
>>>>>>>>> + # does allow list exist?
>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>> +
>>>>>>>>> + echo "rpz:
>>>>>>>>> + name: allow.rpz
>>>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>>>> + rpz-action-override: passthru
>>>>>>>>> + rpz-log: ${theLog}
>>>>>>>>> + rpz-log-name: allow
>>>>>>>>> + rpz-signal-nxdomain-ra: yes" >> "${theConfig}"
>>>>>>>>> +
>>>>>>>>> + fi
>>>>>>>>> +
>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>> + set_permissions "${theConfig}"
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# create the config file for block
>>>>>>>>> +make_block_config () {
>>>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>>>> + local theConfig="/etc/unbound/local.d/block.rpz.conf" # output zone conf file
>>>>>>>>> + local theList="/var/ipfire/dns/rpz/blocklist" # input custom list of domains
>>>>>>>>> +
>>>>>>>>> + msg_log "info: rpz: make config file \"block.rpz.conf\""
>>>>>>>>> +
>>>>>>>>> + # does block list exist?
>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>> +
>>>>>>>>> + echo "rpz:
>>>>>>>>> + name: block.rpz
>>>>>>>>> + zonefile: /etc/unbound/zonefiles/block.rpz
>>>>>>>>> + rpz-action-override: nxdomain
>>>>>>>>> + rpz-log: ${theLog}
>>>>>>>>> + rpz-log-name: block
>>>>>>>>> + rpz-signal-nxdomain-ra: yes" > "${theConfig}"
>>>>>>>>> +
>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>> + set_permissions "${theConfig}"
>>>>>>>>> + else
>>>>>>>>> + # no - trash the config file
>>>>>>>>> + rm --verbose /etc/unbound/local.d/block.rpz.conf
>>>>>>>>> + fi
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# create an RPZ file for allow or block
>>>>>>>>> +make_rpz_file () {
>>>>>>>>> + local theName="${1}" # allow or block
>>>>>>>>> + local theAction='.' # the default is nxdomain or block
>>>>>>>>> + local actionList
>>>>>>>>> +
>>>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>>>> + local theZonefile="/etc/unbound/zonefiles/${theName}.rpz" # output file for RPZ
>>>>>>>>> +
>>>>>>>>> + # does a list exist?
>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>> +
>>>>>>>>> + # for allow set to passthru
>>>>>>>>> + [[ "${theName}" == allow ]] && theAction='rpz-passthru.'
>>>>>>>>> +
>>>>>>>>> + # drop any extra "blanks" and add "CNAME <RPZ action>." to each line
>>>>>>>>> + actionList=$( awk '{$1=$1};1' "${theList}" |
>>>>>>>>> + sed "/^[^;].*[[:alnum:]]/ s|$| CNAME ${theAction}|" )
>>>>>>>>> +
>>>>>>>>> + msg_log "info: rpz: create zonefile for \"${theName}list\""
>>>>>>>>> +
>>>>>>>>> +echo "; Name: ${theName} list
>>>>>>>>> +; Last modified: $(date "+%Y-%m-%d at %H.%M.%S %Z")
>>>>>>>>> +;
>>>>>>>>> +; domains with actions list
>>>>>>>>> +;
>>>>>>>>> +${actionList}" > "${theZonefile}"
>>>>>>>>> +
>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>> + set_permissions "${theZonefile}"
>>>>>>>>> + # set-up allow/block list files
>>>>>>>>> + set_permissions "${theList}"
>>>>>>>>> + else
>>>>>>>>> + msg_log "info: rpz: the ${theList} is empty."
>>>>>>>>> +
>>>>>>>>> + rm --verbose "${theZonefile}" # trash the RPZ file
>>>>>>>>> + fi
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# check if allow/block list is valid
>>>>>>>>> +validate_list () {
>>>>>>>>> + local theName="${1}" # allow or block
>>>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>>>> +
>>>>>>>>> + # remove good:
>>>>>>>>> + # - properly formated domain names with or without leading wildcard
>>>>>>>>> + # - properly formated top level domain (TLD) names with wildcard
>>>>>>>>> + # - blank lines and comment lines
>>>>>>>>> + # remaining lines are considered "bad"
>>>>>>>>> + bad_lines=$( sed --regexp-extended \
>>>>>>>>> + '/^(\*\.)?([a-zA-Z0-9](([a-zA-Z0-9\-]){0,61}[a-zA-Z0-9])?\.)+([a-zA-Z]{2,}|xn--[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])$/d ;
>>>>>>>>> + /^(\*\.)([a-z]{2,61}|xn--[a-z0-9]{1,60})$/d ;
>>>>>>>>> + /^$/d ; /^;/d' "${theList}" )
>>>>>>>>> +
>>>>>>>>> + if [[ ! -z "${bad_lines}" ]] ; then
>>>>>>>>> + msg_log "error: rpz: invalid line(s) in ${theList}."
>>>>>>>>> + printf "%s\n" "bad line(s): ${bad_lines}"
>>>>>>>>> + exit 110
>>>>>>>>> + fi
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +
>>>>>>>>> +############### Main ###############
>>>>>>>>> +
>>>>>>>>> +tagName="unbound"
>>>>>>>>> +
>>>>>>>>> +rpzName="${1}" # input RPZ name
>>>>>>>>> +
>>>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>>>> +
>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>> + case "$1" in
>>>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>>>> + esac
>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>> +done
>>>>>>>>> +
>>>>>>>>> +case "${rpzName}" in
>>>>>>>>> + # make a new allow or block rpz file
>>>>>>>>> +
>>>>>>>>> + allow )
>>>>>>>>> + validate_list 'allow' # is the allowlist valid?
>>>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>>>> + make_rpz_file 'allow'
>>>>>>>>> + ;;
>>>>>>>>> +
>>>>>>>>> + allowblock )
>>>>>>>>> + validate_list 'allow' # is the list valid?
>>>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>>>> + make_rpz_file 'allow'
>>>>>>>>> + ;&
>>>>>>>>> +
>>>>>>>>> + block )
>>>>>>>>> + validate_list 'block' # is the blocklist valid?
>>>>>>>>> + make_block_config "${rpzLog}"
>>>>>>>>> + make_rpz_file 'block'
>>>>>>>>> + ;;
>>>>>>>>> +
>>>>>>>>> + reload )
>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>> + ;;
>>>>>>>>> +
>>>>>>>>> + unbound-restart )
>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>> + unbound_restart
>>>>>>>>> + exit
>>>>>>>>> + ;;
>>>>>>>>> +
>>>>>>>>> + * )
>>>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>>>> + printf "Usage: $(basename "$0") <NAME> <OPTION> <OPTION>\n"
>>>>>>>>> + printf "Version: ${version}\n"
>>>>>>>>> + exit 108
>>>>>>>>> + ;;
>>>>>>>>> +esac
>>>>>>>>> +
>>>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>>>> +
>>>>>>>>> +exit
>>>>>>>>> diff --git a/config/rpz/rpz-metrics b/config/rpz/rpz-metrics
>>>>>>>>> new file mode 100755
>>>>>>>>> index 000000000..4d43e1629
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/config/rpz/rpz-metrics
>>>>>>>>> @@ -0,0 +1,170 @@
>>>>>>>>> +#!/bin/bash
>>>>>>>>> +###############################################################################
>>>>>>>>> +# #
>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>> +# #
>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>> +# #
>>>>>>>>> +###############################################################################
>>>>>>>>> +
>>>>>>>>> +version="2025-01-20 - v25"
>>>>>>>>> +
>>>>>>>>> +############### Main ###############
>>>>>>>>> +
>>>>>>>>> +weeks="2" # default to two message logs
>>>>>>>>> +sortBy="name" # default "by name"
>>>>>>>>> +rpzActive="enabled" # default "enabled only"
>>>>>>>>> +
>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>> + case "$1" in
>>>>>>>>> + --by-names | --by-name | name ) sortBy="name" ;;
>>>>>>>>> +
>>>>>>>>> + --by-hits | --by-hit | hits | hit ) sortBy="hit" ;;
>>>>>>>>> +
>>>>>>>>> + --by-lines | --by-line | lines | line ) sortBy="line" ;;
>>>>>>>>> +
>>>>>>>>> + --by-effect ) sortBy="effect" ;;
>>>>>>>>> +
>>>>>>>>> + --enabled-only ) rpzActive="enabled" ;;
>>>>>>>>> +
>>>>>>>>> + --active-all | --all | all ) rpzActive="all" ;;
>>>>>>>>> +
>>>>>>>>> + [0-9] | [0-9][0-9] ) weeks=$1 ;;
>>>>>>>>> + esac
>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>> +done
>>>>>>>>> +
>>>>>>>>> +# get the list of message logs for N weeks
>>>>>>>>> +messageLogs=$( find /var/log/messages* -type f | sort --version-sort |
>>>>>>>>> + head -"${weeks}" )
>>>>>>>>> +
>>>>>>>>> +# get the list of RPZ names & counts from the message log(s)
>>>>>>>>> +rpzNameCount=$( for logf in ${messageLogs} ; do
>>>>>>>>> + zgrep --text --fixed-strings 'info: rpz: applied' "${logf}" |
>>>>>>>>> + awk '$10 ~ /\[\w*]/ { print $10 }' ;
>>>>>>>>> + done | sort | uniq --count )
>>>>>>>>> +
>>>>>>>>> +# flip results and remove brackets `[` and `]`
>>>>>>>>> +rpzNameCount=$( echo "${rpzNameCount}" |
>>>>>>>>> + awk '{ print $2, $1 }' |
>>>>>>>>> + sed --regexp-extended 's|^\[(.*)\]|\1|' )
>>>>>>>>> +
>>>>>>>>> +# grab only names
>>>>>>>>> +rpzNames=$( echo "${rpzNameCount}" | awk '{ print $1 }' )
>>>>>>>>> +
>>>>>>>>> +# get list of RPZ files
>>>>>>>>> +rpzFileList=$( find /etc/unbound/zonefiles -type f -iname "*.rpz" )
>>>>>>>>> +
>>>>>>>>> +# get basename of those files
>>>>>>>>> +rpzBaseNames=$( echo "${rpzFileList}" |
>>>>>>>>> + sed 's|/etc/unbound/zonefiles/||g ; s|\.rpz||g ;' )
>>>>>>>>> +
>>>>>>>>> +# add to rpzNames
>>>>>>>>> +rpzNames="${rpzNames}"$'\n'"${rpzBaseNames}"
>>>>>>>>> +
>>>>>>>>> +# drop duplicate names
>>>>>>>>> +rpzNames=$( echo "${rpzNames}" | sort --unique )
>>>>>>>>> +
>>>>>>>>> +# get line count for each RPZ
>>>>>>>>> +lineCount=$( echo "${rpzFileList}" | xargs wc -l )
>>>>>>>>> +
>>>>>>>>> +# get comment line count and blank line count for each RPZ
>>>>>>>>> +commentCount=$( echo "${rpzFileList}" | xargs grep --count -e "^$" -e "^;" )
>>>>>>>>> +
>>>>>>>>> +# get modified date each RPZ
>>>>>>>>> +modDateList=$( echo "${rpzFileList}" | xargs stat -c '%.10y %n' )
>>>>>>>>> +
>>>>>>>>> +ucListAuthZones=$( unbound-control list_auth_zones )
>>>>>>>>> +
>>>>>>>>> +# get width of RPZ names
>>>>>>>>> +pWidth=$( echo "${rpzNames}" | awk '{ print $1" " }' | wc -L )
>>>>>>>>> +pFormat="%-${pWidth}s %-8s %-8s %8s %12s %12s\n"
>>>>>>>>> +
>>>>>>>>> +# print title line
>>>>>>>>> +printf "${pFormat}" "name" "hits" "active" "lines" " hits/line" "last download"
>>>>>>>>> +printf -- "--------------"
>>>>>>>>> +
>>>>>>>>> +theResults=""
>>>>>>>>> +totalLines=0
>>>>>>>>> +totalHits=0
>>>>>>>>> +while read -r theName
>>>>>>>>> +do
>>>>>>>>> + printf -- "--" # pretend progress bar
>>>>>>>>> +
>>>>>>>>> + # is this RPZ list active?
>>>>>>>>> + theActive="disabled"
>>>>>>>>> + if grep --quiet "^${theName}\.rpz" <<< "${ucListAuthZones}"
>>>>>>>>> + then
>>>>>>>>> + theActive="enabled"
>>>>>>>>> + else
>>>>>>>>> + [[ "${rpzActive}" == enabled ]] && continue
>>>>>>>>> + fi
>>>>>>>>> +
>>>>>>>>> + # get hit count
>>>>>>>>> + theHits="0"
>>>>>>>>> + if output=$( grep "^${theName}\s" <<< "${rpzNameCount}" ) ; then
>>>>>>>>> + theHits=$( echo "${output}" | awk '{ print $2 }' )
>>>>>>>>> + totalHits=$(( totalHits + theHits ))
>>>>>>>>> + fi
>>>>>>>>> +
>>>>>>>>> + # get line count
>>>>>>>>> + theLines="n/a"
>>>>>>>>> + hitsPerLine="0"
>>>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${lineCount}" ) ; then
>>>>>>>>> + theLines=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>>>> + totalLines=$(( totalLines + theLines ))
>>>>>>>>> +
>>>>>>>>> + if [[ "${theLines}" -gt 2 ]] ; then
>>>>>>>>> + hitsPerLine=$(( 100 * theHits / theLines ))
>>>>>>>>> + fi
>>>>>>>>> + fi
>>>>>>>>> +
>>>>>>>>> + # get modification date
>>>>>>>>> + theModDate="n/a"
>>>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${modDateList}" ) ; then
>>>>>>>>> + theModDate=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>>>> + fi
>>>>>>>>> +
>>>>>>>>> + # add to results list
>>>>>>>>> + theResults+="${theName} ${theHits} ${theActive} ${theLines} ${hitsPerLine} ${theModDate}"$'\n'
>>>>>>>>> +
>>>>>>>>> +done <<< "${rpzNames}"
>>>>>>>>> +
>>>>>>>>> +case "${sortBy}" in
>>>>>>>>> + # sort by "active" then by "name"
>>>>>>>>> + name) sortArg=(-k3,3r -k1,1) ;;
>>>>>>>>> +
>>>>>>>>> + # sort by "active" then by "hits" then by "name"
>>>>>>>>> + hit) sortArg=(-k3,3r -k2,2nr -k1,1) ;;
>>>>>>>>> +
>>>>>>>>> + # sort by "active" then by "lines" then by "name"
>>>>>>>>> + line) sortArg=(-k3,3r -k4,4nr -k1,1) ;;
>>>>>>>>> +
>>>>>>>>> + # sort by "active" then by "effect" then by "name"
>>>>>>>>> + effect) sortArg=(-k3,3r -k5,5nr -k1,1) ;;
>>>>>>>>> +esac
>>>>>>>>> +
>>>>>>>>> +printf -- "--------------\n"
>>>>>>>>> +# remove blank lines, sort, print as columns
>>>>>>>>> +echo "${theResults}" |
>>>>>>>>> + awk '!/^[[:space:]]*$/' |
>>>>>>>>> + sort "${sortArg[@]}" |
>>>>>>>>> + awk --assign=width="${pWidth}" \
>>>>>>>>> + '{ printf "%-*s %-8s %-8s %8s %10s %% %12s\n", width, $1, $2, $3, $4, $5, $6 }'
>>>>>>>>> +
>>>>>>>>> +printf "${pFormat}" "" "=======" "" "========" "" ""
>>>>>>>>> +printf "${pFormat}" "Totals -->" "${totalHits}" "" "${totalLines}" "" ""
>>>>>>>>> +
>>>>>>>>> +exit
>>>>>>>>> diff --git a/config/rpz/rpz-sleep b/config/rpz/rpz-sleep
>>>>>>>>> new file mode 100755
>>>>>>>>> index 000000000..dd3603599
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/config/rpz/rpz-sleep
>>>>>>>>> @@ -0,0 +1,58 @@
>>>>>>>>> +#!/bin/bash
>>>>>>>>> +###############################################################################
>>>>>>>>> +# #
>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>> +# #
>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>> +# #
>>>>>>>>> +###############################################################################
>>>>>>>>> +
>>>>>>>>> +version="2024-08-16" # v05
>>>>>>>>> +
>>>>>>>>> +############### Functions ###############
>>>>>>>>> +
>>>>>>>>> +# send message to message log
>>>>>>>>> +msg_log () {
>>>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>>>> + if tty --silent ; then
>>>>>>>>> + echo "${tagName}:" "$*"
>>>>>>>>> + fi
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +############### Main ###############
>>>>>>>>> +
>>>>>>>>> +tagName="unbound"
>>>>>>>>> +
>>>>>>>>> +sleepTime="${1:-5m}" # default to sleep for 5m (5 minutes)
>>>>>>>>> +
>>>>>>>>> +zoneList=$( unbound-control list_auth_zones | awk '{print $1}' )
>>>>>>>>> +
>>>>>>>>> +for zone in ${zoneList} ; do
>>>>>>>>> + printf "disable ${zone}\t"
>>>>>>>>> + unbound-control rpz_disable "${zone}"
>>>>>>>>> +done
>>>>>>>>> +
>>>>>>>>> +msg_log "info: rpz: disabled all zones for ${sleepTime}"
>>>>>>>>> +
>>>>>>>>> +sleep "${sleepTime}"
>>>>>>>>> +
>>>>>>>>> +for zone in ${zoneList} ; do
>>>>>>>>> + printf "enable ${zone}\t"
>>>>>>>>> + unbound-control rpz_enable "${zone}"
>>>>>>>>> +done
>>>>>>>>> +
>>>>>>>>> +msg_log "info: rpz: enabled all zones"
>>>>>>>>> +
>>>>>>>>> +exit
>>>>>>>>> diff --git a/config/rpz/rpz.de.pl b/config/rpz/rpz.de.pl
>>>>>>>>> new file mode 100644
>>>>>>>>> index 000000000..3770c6bb0
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/config/rpz/rpz.de.pl
>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>> +%tr = (%tr,
>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>> +'rpz apply' => 'Übernehmen',
>>>>>>>>> +'rpz cl allow enable' => 'Benutzerdefinierte Allowlist aktivieren:',
>>>>>>>>> +'rpz cl allow info' => 'Zugelassene Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>>>> +'rpz cl allow' => 'Benutzerdefinierte Allowlist',
>>>>>>>>> +'rpz cl block enable' => 'Benutzerdefinierte Blocklist aktivieren:',
>>>>>>>>> +'rpz cl block info' => 'Gesperrte Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>>>> +'rpz cl block' => 'Benutzerdefinierte Blocklist',
>>>>>>>>> +'rpz cl' => 'Benutzerdefinierte Listen',
>>>>>>>>> +'rpz exitcode 101' => 'Der Name enthält unzulässige Zeichen',
>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf hat eine fehlerhafte Konfiguration ermittelt. Führen Sie das Kommando unbound-checkconf auf der Konsole aus, um weitere Informationen zu erhalten.',
>>>>>>>>> +'rpz exitcode 103' => 'Die benutzerdefinierte Allow-/Blocklist ist leer',
>>>>>>>>> +'rpz exitcode 104' => 'Ein Eintrag mit identischem Namen existiert bereits',
>>>>>>>>> +'rpz exitcode 105' => 'Die URL ist ungültig',
>>>>>>>>> +'rpz exitcode 106' => 'Eintrag kann nicht entfernt werden, der Name existiert nicht',
>>>>>>>>> +'rpz exitcode 107' => 'Der Name ist ungültig - nur "allow" oder "block" möglich',
>>>>>>>>> +'rpz exitcode 108' => 'Fehlende oder inkorrekte Parameter',
>>>>>>>>> +'rpz exitcode 109' => 'unbound-control reload ist fehlgeschlagen',
>>>>>>>>> +'rpz exitcode 110' => 'Die benutzerdefinierte Allow-/Blocklist enthält unzulässige Einträge',
>>>>>>>>> +'rpz exitcode 201' => 'Die Anmerkung enthält unzulässige Zeichen',
>>>>>>>>> +'rpz exitcode 202' => 'Ungültiger Eintrag in der benutzerdefinierten Allowlist, Zeile ',
>>>>>>>>> +'rpz exitcode 203' => 'Ungültiger Eintrag in der benutzerdefinierten Blocklist, Zeile ',
>>>>>>>>> +'rpz exitcode 204' => 'Ausgewählter Eintrag existiert nicht: ',
>>>>>>>>> +'rpz zf editor' => 'Zonendatei-Eintrag bearbeiten',
>>>>>>>>> +'rpz zf imported' => '(importiert aus rpz-config)',
>>>>>>>>> +'rpz zf remark info' => 'Erlaubte Zeichen sind a-z, A-Z, 0-9 und Unterstriche',
>>>>>>>>> +'rpz zf' => 'Zonendateien',
>>>>>>>>> +);
>>>>>>>>> diff --git a/config/rpz/rpz.en.pl b/config/rpz/rpz.en.pl
>>>>>>>>> new file mode 100644
>>>>>>>>> index 000000000..0720a8940
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/config/rpz/rpz.en.pl
>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>> +%tr = (%tr,
>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>> +'rpz apply' => 'Apply',
>>>>>>>>> +'rpz cl allow enable' => 'Enable custom allowlist:',
>>>>>>>>> +'rpz cl allow info' => 'Allowed domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>>>> +'rpz cl allow' => 'Custom allowlist',
>>>>>>>>> +'rpz cl block enable' => 'Enable custom blocklist:',
>>>>>>>>> +'rpz cl block info' => 'Blocked domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>>>> +'rpz cl block' => 'Custom blocklist',
>>>>>>>>> +'rpz cl' => 'Custom lists',
>>>>>>>>> +'rpz exitcode 101' => 'the NAME is not valid',
>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf found invalid configuration. In the Terminal run the command unbound-checkconf for more information',
>>>>>>>>> +'rpz exitcode 103' => 'the allow/block list is empty',
>>>>>>>>> +'rpz exitcode 104' => 'duplicate - NAME already exists',
>>>>>>>>> +'rpz exitcode 105' => 'the URL is not valid',
>>>>>>>>> +'rpz exitcode 106' => 'cannot remove the NAME does not exist',
>>>>>>>>> +'rpz exitcode 107' => 'the NAME is not valid - "allow" or "block" only',
>>>>>>>>> +'rpz exitcode 108' => 'missing or incorrect parameter',
>>>>>>>>> +'rpz exitcode 109' => 'unbound-control reload failed',
>>>>>>>>> +'rpz exitcode 110' => 'custom Allowlist/Blocklist contains invalid entries',
>>>>>>>>> +'rpz exitcode 201' => 'the REMARK is not valid',
>>>>>>>>> +'rpz exitcode 202' => 'invalid entry in allowlist, line ',
>>>>>>>>> +'rpz exitcode 203' => 'invalid entry in blocklist, line ',
>>>>>>>>> +'rpz exitcode 204' => 'Selected entry does not exist: ',
>>>>>>>>> +'rpz zf editor' => 'Edit zonefiles entry',
>>>>>>>>> +'rpz zf imported' => '(imported from rpz-config)',
>>>>>>>>> +'rpz zf remark info' => 'Valid characters are a-z, A-Z, 0-9 and underscore.',
>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>> +);
>>>>>>>>> diff --git a/config/rpz/rpz.es.pl b/config/rpz/rpz.es.pl
>>>>>>>>> new file mode 100644
>>>>>>>>> index 000000000..98628e4aa
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/config/rpz/rpz.es.pl
>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>> +%tr = (%tr,
>>>>>>>>> +'rpz' => 'Zonas de política de respuesta (RPZ)',
>>>>>>>>> +'rpz apply' => 'Aplicar',
>>>>>>>>> +'rpz cl allow enable' => 'Habilitar la lista blanca personalizada:',
>>>>>>>>> +'rpz cl allow info' => 'Dominio permitido (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>>>> +'rpz cl allow' => 'Lista blanca personalizada',
>>>>>>>>> +'rpz cl block enable' => 'Habilitar la lista negra personalizada:',
>>>>>>>>> +'rpz cl block info' => 'Dominio bloqueado (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>>>> +'rpz cl block' => 'Lista negra personalizada',
>>>>>>>>> +'rpz cl' => 'Lista personalizada',
>>>>>>>>> +'rpz exitcode 101' => 'El NOMBRE no es válido',
>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha encontrado una configuración no válida. Desde Terminal, ejecute el comando unbound-checkconf para mayor información',
>>>>>>>>> +'rpz exitcode 103' => 'La lista de permitidos/bloqueados está vacía',
>>>>>>>>> +'rpz exitcode 104' => 'duplicado - NOMBRE ya existe',
>>>>>>>>> +'rpz exitcode 105' => 'la URL no es válida',
>>>>>>>>> +'rpz exitcode 106' => 'no es posible eliminar el NOMBRE que no existe',
>>>>>>>>> +'rpz exitcode 107' => 'el NOMBRE no es válido - sólo "permitir" o "bloquear"',
>>>>>>>>> +'rpz exitcode 108' => 'parámetro faltante o incorrecto',
>>>>>>>>> +'rpz exitcode 109' => 'Error al recargar unbound-control',
>>>>>>>>> +'rpz exitcode 110' => 'la Lista blanca/Lista negra personalizada contiene entradas no válidas',
>>>>>>>>> +'rpz exitcode 201' => 'el COMENTARIO no es válido',
>>>>>>>>> +'rpz exitcode 202' => 'entrada no válida en la lista blanca, línea ',
>>>>>>>>> +'rpz exitcode 203' => 'entrada no válida en la lista negra, línea ',
>>>>>>>>> +'rpz exitcode 204' => 'La entrada seleccionada no existe: ',
>>>>>>>>> +'rpz zf editor' => 'Editar la entrada de archivos de zona',
>>>>>>>>> +'rpz zf imported' => '(importado de rpz-config)',
>>>>>>>>> +'rpz zf remark info' => 'Los caracteres válidos son a-z, A-Z, 0-9 y guión bajo',
>>>>>>>>> +'rpz zf' => 'Archivos de zona',
>>>>>>>>> +);
>>>>>>>>> diff --git a/config/rpz/rpz.fr.pl b/config/rpz/rpz.fr.pl
>>>>>>>>> new file mode 100644
>>>>>>>>> index 000000000..f35f3c2d0
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/config/rpz/rpz.fr.pl
>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>> +%tr = (%tr,
>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>> +'rpz apply' => 'Appliquer',
>>>>>>>>> +'rpz cl allow enable' => 'Activer la liste d\'autorisations personnalisée:',
>>>>>>>>> +'rpz cl allow info' => 'Domaines autorisés (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>>>> +'rpz cl allow' => 'Liste d\'autorisations personnalisée',
>>>>>>>>> +'rpz cl block enable' => 'Activer la liste de blocage personnalisée:',
>>>>>>>>> +'rpz cl block info' => 'Domaines bloqués (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>>>> +'rpz cl block' => 'liste de blocage personnalisé',
>>>>>>>>> +'rpz cl' => 'Listes personnalisées',
>>>>>>>>> +'rpz exitcode 101' => 'le NOM n\'est pas valide',
>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf configuration non valide trouvée. Dans le terminal, exécutez la commande unbound-checkconf pour plus d\'informations',
>>>>>>>>> +'rpz exitcode 103' => 'la liste autoriser/bloquer est vide',
>>>>>>>>> +'rpz exitcode 104' => 'le NOM existe déjà',
>>>>>>>>> +'rpz exitcode 105' => 'L\'URL n\'est pas valide',
>>>>>>>>> +'rpz exitcode 106' => 'impossible de supprimer le NOM n\'existe pas',
>>>>>>>>> +'rpz exitcode 107' => 'le NOM n\'est pas valide - « autoriser » ou « bloquer » seulement',
>>>>>>>>> +'rpz exitcode 108' => 'paramètre manquant ou incorrect',
>>>>>>>>> +'rpz exitcode 109' => 'unbound-control rechargement échoué',
>>>>>>>>> +'rpz exitcode 110' => 'la liste autoriser/bloquer contient des entrées non valides',
>>>>>>>>> +'rpz exitcode 201' => 'la REMARQUE n\'est pas valable',
>>>>>>>>> +'rpz exitcode 202' => 'entrée non valide dans la liste d\'autorisation, ligne ',
>>>>>>>>> +'rpz exitcode 203' => 'entrée non valide dans la liste de blocs, ligne ',
>>>>>>>>> +'rpz exitcode 204' => 'L\'entrée sélectionnée n\'existe pas: ',
>>>>>>>>> +'rpz zf editor' => 'Modifier l\'entrée Fichiers Zone',
>>>>>>>>> +'rpz zf imported' => '(importé de rpz-config)',
>>>>>>>>> +'rpz zf remark info' => 'Les caractères valides sont a-z, A-Z, 0-9 et soulignement.',
>>>>>>>>> +'rpz zf' => 'Fichiers Zone',
>>>>>>>>> +);
>>>>>>>>> diff --git a/config/rpz/rpz.it.pl b/config/rpz/rpz.it.pl
>>>>>>>>> new file mode 100644
>>>>>>>>> index 000000000..ee81605c9
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/config/rpz/rpz.it.pl
>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>> +%tr = (%tr,
>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>> +'rpz apply' => 'Applica',
>>>>>>>>> +'rpz cl allow enable' => 'Abilita la Whitelist personalizzata:',
>>>>>>>>> +'rpz cl allow info' => 'Domini consentiti (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>>>> +'rpz cl allow' => 'Whitelist personalizzata',
>>>>>>>>> +'rpz cl block enable' => 'Abilita la Blacklist personalizzata:',
>>>>>>>>> +'rpz cl block info' => 'Domini bloccati (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>>>> +'rpz cl block' => 'Blacklist personalizzata',
>>>>>>>>> +'rpz cl' => 'Liste personalizzate',
>>>>>>>>> +'rpz exitcode 101' => 'il NOME non è valido',
>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha trovato una configurazione non valida. Dal Terminale esegui il comando unbound-checkconf per maggiori informazioni',
>>>>>>>>> +'rpz exitcode 103' => 'l\'elenco consentiti/bloccati è vuoto',
>>>>>>>>> +'rpz exitcode 104' => 'duplicato - NAME esiste di già',
>>>>>>>>> +'rpz exitcode 105' => 'l\'URL non è valido',
>>>>>>>>> +'rpz exitcode 106' => 'non è possibile rimuovere il NOME non esiste',
>>>>>>>>> +'rpz exitcode 107' => 'il NOME non è valido - solo "consenti" o "blocca"',
>>>>>>>>> +'rpz exitcode 108' => 'parametro mancante o non corretto',
>>>>>>>>> +'rpz exitcode 109' => 'ricaricamento del controllo non associato non riuscito',
>>>>>>>>> +'rpz exitcode 110' => 'la Whitelist/Blacklist personalizzata contiene voci non valide',
>>>>>>>>> +'rpz exitcode 201' => 'l"OSSERVAZIONE non è valida',
>>>>>>>>> +'rpz exitcode 202' => 'voce non valida nella Whitelist, riga ',
>>>>>>>>> +'rpz exitcode 203' => 'voce non valida nella Blacklist, riga ',
>>>>>>>>> +'rpz exitcode 204' => 'La voce selezionata non esiste: ',
>>>>>>>>> +'rpz zf editor' => 'Modifica la voce dei file di zona',
>>>>>>>>> +'rpz zf imported' => '(importato da rpz-config)',
>>>>>>>>> +'rpz zf remark info' => 'I caratteri validi sono a-z, A-Z, 0-9 e trattino basso',
>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>> +);
>>>>>>>>> diff --git a/config/rpz/rpz.tr.pl b/config/rpz/rpz.tr.pl
>>>>>>>>> new file mode 100644
>>>>>>>>> index 000000000..00226e192
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/config/rpz/rpz.tr.pl
>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>> +# I�in eklendi Ayriyeten Response Policy Zone (RPZ)
>>>>>>>>> +%tr = (%tr,
>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>> +'rpz apply' => 'Uygulamak',
>>>>>>>>> +'rpz cl allow enable' => '�zel Etkinlestir allowlist:',
>>>>>>>>> +'rpz cl allow info' => 'Izin verilmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>>>> +'rpz cl allow' => 'Etkinlestir allowlist',
>>>>>>>>> +'rpz cl block enable' => '�zel Etkinlestir blocklist:',
>>>>>>>>> +'rpz cl block info' => 'Engellenmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>>>> +'rpz cl block' => 'Engellenmis blocklist',
>>>>>>>>> +'rpz cl' => 'Engellenmis lists',
>>>>>>>>> +'rpz exitcode 101' => 'NAME ge�erli degil',
>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ge�ersiz yapilandirma bulundu. Terminalde daha fazla bilgi i�in Unbound-checkConf komutunu �alistirin',
>>>>>>>>> +'rpz exitcode 103' => 'allow/block liste bos',
>>>>>>>>> +'rpz exitcode 104' => 'kopyalamak - NAME zaten var',
>>>>>>>>> +'rpz exitcode 105' => 'URL ge�erli degil',
>>>>>>>>> +'rpz exitcode 106' => '�ikarilamiyor NAME yok',
>>>>>>>>> +'rpz exitcode 107' => 'NAME ge�erli degil - "allow" veya "block" yalniz',
>>>>>>>>> +'rpz exitcode 108' => 'Parametre eksik veya yanlis',
>>>>>>>>> +'rpz exitcode 109' => 'unbound-control basarisiz',
>>>>>>>>> +'rpz exitcode 110' => 'Engellenmis Allowlist/Blocklist ge�ersiz girisler i�erir',
>>>>>>>>> +'rpz exitcode 201' => 'REMARK ge�erli degil',
>>>>>>>>> +'rpz exitcode 202' => 'Ge�ersiz giris allowlist, line ',
>>>>>>>>> +'rpz exitcode 203' => 'Ge�ersiz giris blocklist, line ',
>>>>>>>>> +'rpz exitcode 204' => 'Se�ilen giris yok: ',
>>>>>>>>> +'rpz zf editor' => 'yazimlamak zonefiles giris',
>>>>>>>>> +'rpz zf imported' => '(ithal edildi rpz-config)',
>>>>>>>>> +'rpz zf remark info' => 'Ge�erli karakterler a-z, A-Z, 0-9ve alt�st.',
>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>> +);
>>>>>>>>> diff --git a/html/cgi-bin/rpz.cgi b/html/cgi-bin/rpz.cgi
>>>>>>>>> new file mode 100644
>>>>>>>>> index 000000000..a821c92ac
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/html/cgi-bin/rpz.cgi
>>>>>>>>> @@ -0,0 +1,923 @@
>>>>>>>>> +#!/usr/bin/perl
>>>>>>>>> +###############################################################################
>>>>>>>>> +# #
>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>> +# Copyright (C) 2005-2024 IPFire Team <info@ipfire.org> #
>>>>>>>>> +# #
>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>> +# #
>>>>>>>>> +###############################################################################
>>>>>>>>> +
>>>>>>>>> +use strict;
>>>>>>>>> +use Scalar::Util qw(looks_like_number);
>>>>>>>>> +
>>>>>>>>> +# debugging
>>>>>>>>> +#use warnings;
>>>>>>>>> +#use CGI::Carp 'fatalsToBrowser';
>>>>>>>>> +#use Data::Dumper;
>>>>>>>>> +
>>>>>>>>> +require '/var/ipfire/general-functions.pl';
>>>>>>>>> +require "${General::swroot}/lang.pl";
>>>>>>>>> +require "${General::swroot}/header.pl";
>>>>>>>>> +
>>>>>>>>> +###--- Extra HTML ---###
>>>>>>>>> +my $extraHead = <<END
>>>>>>>>> +<style>
>>>>>>>>> + /* alternating row background */
>>>>>>>>> + .tbl tr:nth-child(2n+2) {
>>>>>>>>> + background-color: var(--color-light-grey);
>>>>>>>>> + }
>>>>>>>>> + .tbl tr:nth-child(2n+3) {
>>>>>>>>> + background-color: var(--color-grey);
>>>>>>>>> + }
>>>>>>>>> + /* text styles */
>>>>>>>>> + .tbl th:not(:last-child) {
>>>>>>>>> + text-align: left;
>>>>>>>>> + }
>>>>>>>>> + div.right {
>>>>>>>>> + text-align: right;
>>>>>>>>> + margin-top: 0.5em;
>>>>>>>>> + }
>>>>>>>>> + /* customlist input */
>>>>>>>>> + textarea.domainlist {
>>>>>>>>> + margin: 0.5em 0;
>>>>>>>>> + resize: vertical;
>>>>>>>>> + min-height: 10em;
>>>>>>>>> + overflow: auto;
>>>>>>>>> + white-space: pre;
>>>>>>>>> + }
>>>>>>>>> + button[type=submit]:disabled {
>>>>>>>>> + opacity: 0.6;
>>>>>>>>> + }
>>>>>>>>> +</style>
>>>>>>>>> +END
>>>>>>>>> +;
>>>>>>>>> +###--- End of extra HTML ---###
>>>>>>>>> +
>>>>>>>>> +
>>>>>>>>> +### Settings ###
>>>>>>>>> +
>>>>>>>>> +# Request DNS service reload after configuration change
>>>>>>>>> +my $RPZ_RELOAD_FLAG = "${General::swroot}/dns/rpz/reload.flag";
>>>>>>>>> +
>>>>>>>>> +# Configuration file for all available zonefiles
>>>>>>>>> +# Format: index, name (unique), enabled (on/off), URL, remark
>>>>>>>>> +my $ZONEFILES_CONF = "${General::swroot}/dns/rpz/zonefiles.conf";
>>>>>>>>> +
>>>>>>>>> +# Configuration file for custom lists
>>>>>>>>> +# IDs: 0=allowlist, 1=blocklist, 2=options (allow/block enabled)
>>>>>>>>> +my $CUSTOMLISTS_CONF = "${General::swroot}/dns/rpz/customlists.conf";
>>>>>>>>> +
>>>>>>>>> +# Export custom lists to rpz-config
>>>>>>>>> +my $RPZ_ALLOWLIST = "${General::swroot}/dns/rpz/allowlist";
>>>>>>>>> +my $RPZ_BLOCKLIST = "${General::swroot}/dns/rpz/blocklist";
>>>>>>>>> +
>>>>>>>>> +
>>>>>>>>> +### Preparation ###
>>>>>>>>> +
>>>>>>>>> +# Create missing config files
>>>>>>>>> +unless(-f $ZONEFILES_CONF) { &General::system('touch', "$ZONEFILES_CONF"); }
>>>>>>>>> +unless(-f $CUSTOMLISTS_CONF) { &General::system('touch', "$CUSTOMLISTS_CONF"); }
>>>>>>>>> +
>>>>>>>>> +
>>>>>>>>> +## Global gui data
>>>>>>>>> +my $errormessage = "";
>>>>>>>>> +
>>>>>>>>> +## Global configuration data
>>>>>>>>> +my %zonefiles = ();
>>>>>>>>> +my %customlists = ();
>>>>>>>>> +&_zonefiles_load();
>>>>>>>>> +&_customlists_load();
>>>>>>>>> +
>>>>>>>>> +## Global CGI form data
>>>>>>>>> +my %cgiparams = ();
>>>>>>>>> +&Header::getcgihash(\%cgiparams);
>>>>>>>>> +
>>>>>>>>> +my $action = $cgiparams{'ACTION'} // 'NONE';
>>>>>>>>> +my $action_key = $cgiparams{'KEY'} // ''; # entry being edited, empty = none/new
>>>>>>>>> +
>>>>>>>>> +
>>>>>>>>> +###--- Process form actions ---###
>>>>>>>>> +
>>>>>>>>> +# Zonefiles action: Check whether the requested entry exists
>>>>>>>>> +if((substr($action, 0, 3) eq 'ZF_') && ($action_key)) {
>>>>>>>>> + unless(defined $zonefiles{$action_key}) {
>>>>>>>>> + $errormessage = &_rpz_error_tr(204, $action_key);
>>>>>>>>> + $action = 'NONE';
>>>>>>>>> + }
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +## Perform actions
>>>>>>>>> +if($action eq 'ZF_SAVE') { ## Save new or modified zonefiles entry
>>>>>>>>> + if(&_action_zf_save()) {
>>>>>>>>> + $action = 'NONE'; # success, return to main page
>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>> + } else {
>>>>>>>>> + $action = 'ZF_EDIT'; # error occured, keep editing
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> +} elsif($action eq 'ZF_TOGGLE') { ## Toggle on/off
>>>>>>>>> + if(&_action_zf_toggle()) {
>>>>>>>>> + $action = 'NONE';
>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> +} elsif($action eq 'ZF_REMOVE') { ## Remove entry
>>>>>>>>> + if(&_action_zf_remove()) {
>>>>>>>>> + $action = 'NONE';
>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> +} elsif($action eq 'CL_SAVE') { ## Save custom lists
>>>>>>>>> + if(&_action_cl_save()) {
>>>>>>>>> + $action = 'NONE';
>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> +} elsif($action eq 'RPZ_RELOAD') { ## Reload dns configuration
>>>>>>>>> + if(&_action_rpz_reload()) {
>>>>>>>>> + $action = 'NONE';
>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> +} elsif($action eq 'UNB_RESTART') { ## Restart unbound service
>>>>>>>>> + if(&_action_unb_restart()) {
>>>>>>>>> + $action = 'NONE';
>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +
>>>>>>>>> +###--- Start GUI ---###
>>>>>>>>> +
>>>>>>>>> +## Start http output
>>>>>>>>> +&Header::showhttpheaders();
>>>>>>>>> +
>>>>>>>>> +# Start HTML
>>>>>>>>> +&Header::openpage($Lang::tr{'rpz'}, 1, $extraHead);
>>>>>>>>> +&Header::openbigbox('100%', 'left', '');
>>>>>>>>> +
>>>>>>>>> +# Show error messages
>>>>>>>>> +if($errormessage) {
>>>>>>>>> + &_print_message($errormessage);
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Handle zonefile add/edit mode
>>>>>>>>> +if($action eq "ZF_EDIT") {
>>>>>>>>> + &_print_zonefile_editor();
>>>>>>>>> +
>>>>>>>>> + # Finalize page and exit cleanly
>>>>>>>>> + &Header::closebigbox();
>>>>>>>>> + &Header::closepage();
>>>>>>>>> + exit(0);
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Show gui elements
>>>>>>>>> +&_print_zonefiles();
>>>>>>>>> +&_print_customlists();
>>>>>>>>> +&_print_gui_extras();
>>>>>>>>> +
>>>>>>>>> +&Header::closebigbox();
>>>>>>>>> +&Header::closepage();
>>>>>>>>> +
>>>>>>>>> +###--- End of GUI ---###
>>>>>>>>> +
>>>>>>>>> +
>>>>>>>>> +###--- Internal configuration file functions ---###
>>>>>>>>> +
>>>>>>>>> +# Load all available zonefiles from rpz-config and the internal configuration
>>>>>>>>> +sub _zonefiles_load {
>>>>>>>>> + # Clean start
>>>>>>>>> + %zonefiles = ();
>>>>>>>>> +
>>>>>>>>> + # Source 1: Get the currently enabled zonefiles from rpz-config (expected format [name]=[URL])
>>>>>>>>> + my @enabled_files = &General::system_output('/usr/sbin/rpz-config', 'list');
>>>>>>>>> +
>>>>>>>>> + foreach my $row (@enabled_files) {
>>>>>>>>> + chomp($row);
>>>>>>>>> +
>>>>>>>>> + # Use regex instead of split() to skip non-matching lines
>>>>>>>>> + next unless($row =~ /^(\w+)=(.+)$/);
>>>>>>>>> + my ($name, $url) = ($1, $2);
>>>>>>>>> +
>>>>>>>>> + # Unique names are already guaranteed by rpz-config
>>>>>>>>> + if(&_rpz_validate_zonefile($name, $url, '', 0) == 0) {
>>>>>>>>> + # Populate global data hash, mark all found entries as enabled
>>>>>>>>> + my %entry = ('enabled' => 'on',
>>>>>>>>> + 'url' => $url,
>>>>>>>>> + 'remark' => $Lang::tr{'rpz zf imported'});
>>>>>>>>> +
>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>> + }
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + # Source 2: Get additional data and disabled entries from configuration file
>>>>>>>>> + my %configured_files = ();
>>>>>>>>> + &General::readhasharray($ZONEFILES_CONF, \%configured_files);
>>>>>>>>> +
>>>>>>>>> + foreach my $row (values (%configured_files)) {
>>>>>>>>> + my ($name, $enabled, $url, $remark) = @$row;
>>>>>>>>> + $remark //= "";
>>>>>>>>> +
>>>>>>>>> + next unless($name);
>>>>>>>>> +
>>>>>>>>> + # Check whether this row belongs to an entry already imported from rpz-config
>>>>>>>>> + if(defined $zonefiles{$name}) {
>>>>>>>>> + # Existing entry, only merge additional data
>>>>>>>>> + $zonefiles{$name}{'remark'} = $remark;
>>>>>>>>> + } else {
>>>>>>>>> + # Skip entry if it is marked as enabled but not found by rpz-config. It was then deleted manually
>>>>>>>>> + if($enabled ne 'on') {
>>>>>>>>> + # Populate global data hash
>>>>>>>>> + my %entry = ('enabled' => 'off',
>>>>>>>>> + 'url' => $url // "",
>>>>>>>>> + 'remark' => $remark);
>>>>>>>>> +
>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>> + }
>>>>>>>>> + }
>>>>>>>>> + }
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Save internal zonefiles configuration
>>>>>>>>> +sub _zonefiles_save_conf {
>>>>>>>>> + my $index = 0;
>>>>>>>>> + my %export = ();
>>>>>>>>> +
>>>>>>>>> + # Loop trough all zonefiles and create "hasharray" type export
>>>>>>>>> + foreach my $name (keys %zonefiles) {
>>>>>>>>> + my @entry = ($name,
>>>>>>>>> + $zonefiles{$name}{'enabled'},
>>>>>>>>> + $zonefiles{$name}{'url'},
>>>>>>>>> + $zonefiles{$name}{'remark'});
>>>>>>>>> +
>>>>>>>>> + $export{$index++} = \@entry;
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + &General::writehasharray($ZONEFILES_CONF, \%export);
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Load custom lists from rpz-config and the internal configuration
>>>>>>>>> +sub _customlists_load {
>>>>>>>>> + # Clean start
>>>>>>>>> + %customlists = ();
>>>>>>>>> +
>>>>>>>>> + # Load configuration file
>>>>>>>>> + my %lists_conf = ();
>>>>>>>>> + &General::readhasharray($CUSTOMLISTS_CONF, \%lists_conf);
>>>>>>>>> +
>>>>>>>>> + # Get list options, enabled by default to start import
>>>>>>>>> + $customlists{'allow'}{'enabled'} = $lists_conf{2}[0] // 'on';
>>>>>>>>> + $customlists{'block'}{'enabled'} = $lists_conf{2}[1] // 'on';
>>>>>>>>> +
>>>>>>>>> + # Import enabled list from rpz-config, otherwise retrieve stored or empty list from configuration file
>>>>>>>>> + if($customlists{'allow'}{'enabled'} eq 'on') {
>>>>>>>>> + &_customlist_import('allow', $RPZ_ALLOWLIST);
>>>>>>>>> + } else {
>>>>>>>>> + $customlists{'allow'}{'list'} = $lists_conf{0} // [];
>>>>>>>>> + }
>>>>>>>>> + if($customlists{'block'}{'enabled'} eq 'on') {
>>>>>>>>> + &_customlist_import('block', $RPZ_BLOCKLIST);
>>>>>>>>> + } else {
>>>>>>>>> + $customlists{'block'}{'list'} = $lists_conf{1} // [];
>>>>>>>>> + }
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Save internal custom lists configuration
>>>>>>>>> +sub _customlists_save_conf {
>>>>>>>>> + my %export = ();
>>>>>>>>> +
>>>>>>>>> + # Match IDs with import function
>>>>>>>>> + $export{0} = $customlists{'allow'}{'list'};
>>>>>>>>> + $export{1} = $customlists{'block'}{'list'};
>>>>>>>>> + $export{2} = [$customlists{'allow'}{'enabled'}, $customlists{'block'}{'enabled'}];
>>>>>>>>> +
>>>>>>>>> + &General::writehasharray($CUSTOMLISTS_CONF, \%export);
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Import a custom list from plain file, returns empty list if file is missing
>>>>>>>>> +sub _customlist_import {
>>>>>>>>> + my ($listname, $filename) = @_;
>>>>>>>>> + my @list = ();
>>>>>>>>> +
>>>>>>>>> + # File exists, load and check all lines
>>>>>>>>> + if(-f $filename) {
>>>>>>>>> + open(my $FH, '<', $filename) or die "Can't read $filename: $!";
>>>>>>>>> + while(my $line = <$FH>) {
>>>>>>>>> + chomp($line);
>>>>>>>>> + push(@list, $line);
>>>>>>>>> + }
>>>>>>>>> + close($FH);
>>>>>>>>> +
>>>>>>>>> + # Clean up imported data
>>>>>>>>> + &_rpz_validate_customlist(\@list, 1);
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + $customlists{$listname}{'list'} = \@list;
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Export a custom list to plain file or clear file if list is disabled
>>>>>>>>> +sub _customlist_export {
>>>>>>>>> + my ($listname, $filename) = @_;
>>>>>>>>> + return unless(defined $customlists{$listname});
>>>>>>>>> +
>>>>>>>>> + # Write enabled domain list to file, otherwise save empty file
>>>>>>>>> + open(my $FH, '>', $filename) or die "Can't write $filename: $!";
>>>>>>>>> +
>>>>>>>>> + if($customlists{$listname}{'enabled'} eq 'on') {
>>>>>>>>> + foreach my $line (@{$customlists{$listname}{'list'}}) {
>>>>>>>>> + print $FH "$line\n";
>>>>>>>>> + }
>>>>>>>>> + } else {
>>>>>>>>> + print $FH "; Note: This list is currently disabled by $ENV{'SCRIPT_NAME'}\n";
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + close($FH);
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +
>>>>>>>>> +###--- Internal gui functions ---###
>>>>>>>>> +
>>>>>>>>> +# Show simple message box
>>>>>>>>> +sub _print_message {
>>>>>>>>> + my ($message, $title) = @_;
>>>>>>>>> + $title ||= $Lang::tr{'error messages'};
>>>>>>>>> +
>>>>>>>>> + &Header::openbox('100%', 'left', $title);
>>>>>>>>> + print "<span>$message</span>";
>>>>>>>>> + &Header::closebox();
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Show all zone files and related gui elements
>>>>>>>>> +sub _print_zonefiles {
>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf'});
>>>>>>>>> +
>>>>>>>>> + print <<END
>>>>>>>>> +<table class="tbl" width="100%">
>>>>>>>>> + <tr>
>>>>>>>>> + <th>$Lang::tr{'name'}</th>
>>>>>>>>> + <th>URL</th>
>>>>>>>>> + <th>$Lang::tr{'remark'}</th>
>>>>>>>>> + <th colspan="3">$Lang::tr{'action'}</th>
>>>>>>>>> + </tr>
>>>>>>>>> +END
>>>>>>>>> +;
>>>>>>>>> +
>>>>>>>>> + # Sort zonefiles by name and loop trough all entries
>>>>>>>>> + foreach my $name (sort keys %zonefiles) {
>>>>>>>>> +
>>>>>>>>> + # Toggle button label translation
>>>>>>>>> + my $toggle_tr = ($zonefiles{$name}{'enabled'} eq 'on') ? $Lang::tr{'click to disable'} : $Lang::tr{'click to enable'};
>>>>>>>>> +
>>>>>>>>> + print <<END
>>>>>>>>> + <tr>
>>>>>>>>> + <td>$name</td>
>>>>>>>>> + <td>$zonefiles{$name}{'url'}</td>
>>>>>>>>> + <td>$zonefiles{$name}{'remark'}</td>
>>>>>>>>> +
>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_TOGGLE">
>>>>>>>>> + <input type="image" src="/images/$zonefiles{$name}{'enabled'}.gif" title="$toggle_tr" alt="$toggle_tr">
>>>>>>>>> + </form>
>>>>>>>>> + </td>
>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_EDIT">
>>>>>>>>> + <input type="image" src="/images/edit.gif" title="$Lang::tr{'edit'}" alt="$Lang::tr{'edit'}">
>>>>>>>>> + </form>
>>>>>>>>> + </td>
>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_REMOVE">
>>>>>>>>> + <input type="image" src="/images/delete.gif" title="$Lang::tr{'remove'}" alt="$Lang::tr{'remove'}">
>>>>>>>>> + </form>
>>>>>>>>> + </td>
>>>>>>>>> + </tr>
>>>>>>>>> +END
>>>>>>>>> +;
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + # Disable reload button if not needed
>>>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>>>> +
>>>>>>>>> + print <<END
>>>>>>>>> +</table>
>>>>>>>>> +
>>>>>>>>> +<div class="right">
>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>> + <input type="hidden" name="KEY" value="">
>>>>>>>>> + <button type="submit" name="ACTION" value="ZF_EDIT">$Lang::tr{'add'}</button>
>>>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>>>> + </form>
>>>>>>>>> +</div>
>>>>>>>>> +END
>>>>>>>>> +;
>>>>>>>>> +
>>>>>>>>> + &Header::closebox();
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Show zonefiles entry editor
>>>>>>>>> +sub _print_zonefile_editor {
>>>>>>>>> +
>>>>>>>>> + # Key specified: Edit existing entry
>>>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>>>> + # Load data to be edited, but don't override already present values (allows user to edit after error)
>>>>>>>>> + $cgiparams{'ZF_NAME'} //= $action_key;
>>>>>>>>> + $cgiparams{'ZF_URL'} //= $zonefiles{$action_key}{'url'};
>>>>>>>>> + $cgiparams{'ZF_REMARK'} //= $zonefiles{$action_key}{'remark'};
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + # Fallback to empty form
>>>>>>>>> + $cgiparams{'ZF_NAME'} //= "";
>>>>>>>>> + $cgiparams{'ZF_URL'} //= "";
>>>>>>>>> + $cgiparams{'ZF_REMARK'} //= "";
>>>>>>>>> +
>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf editor'});
>>>>>>>>> +
>>>>>>>>> + print <<END
>>>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>> +<input type="hidden" name="KEY" value="$action_key">
>>>>>>>>> +<table width="100%">
>>>>>>>>> + <tr>
>>>>>>>>> + <td width="20%">$Lang::tr{'name'}: <img src="/blob.gif" alt="*"></td>
>>>>>>>>> + <td><input type="text" name="ZF_NAME" value="$cgiparams{'ZF_NAME'}" size="40" maxlength="32" title="$Lang::tr{'rpz zf remark info'}" pattern="[a-zA-Z0-9_]{1,32}" required></td>
>>>>>>>>> + </tr>
>>>>>>>>> + <tr>
>>>>>>>>> + <td width="20%">URL: <img src="/blob.gif" alt="*"></td>
>>>>>>>>> + <td><input type="url" name="ZF_URL" value="$cgiparams{'ZF_URL'}" size="40" maxlength="128" required></td>
>>>>>>>>> + </tr>
>>>>>>>>> + <tr>
>>>>>>>>> + <td width="20%">$Lang::tr{'remark'}:</td>
>>>>>>>>> + <td><input type="text" name="ZF_REMARK" value="$cgiparams{'ZF_REMARK'}" size="40" maxlength="32"></td>
>>>>>>>>> + </tr>
>>>>>>>>> + <tr>
>>>>>>>>> + <td colspan="2"><hr></td>
>>>>>>>>> + </tr>
>>>>>>>>> + <tr>
>>>>>>>>> + <td width="55%"><img src="/blob.gif" alt="*"> $Lang::tr{'required field'}</td>
>>>>>>>>> + <td align="right"><button type="submit" name="ACTION" value="ZF_SAVE">$Lang::tr{'save'}</button></td>
>>>>>>>>> + </tr>
>>>>>>>>> +</table>
>>>>>>>>> +</form>
>>>>>>>>> +
>>>>>>>>> +<div class="right">
>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>> + <button type="submit" name="ACTION" value="NONE">$Lang::tr{'back'}</button>
>>>>>>>>> + </form>
>>>>>>>>> +</div>
>>>>>>>>> +END
>>>>>>>>> +;
>>>>>>>>> +
>>>>>>>>> + &Header::closebox();
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Show custom allow/block files and related gui elements
>>>>>>>>> +sub _print_customlists {
>>>>>>>>> +
>>>>>>>>> + # Load lists from config, unless they are currently being edited
>>>>>>>>> + if($action ne 'CL_SAVE') {
>>>>>>>>> + $cgiparams{'ALLOW_LIST'} = join("\n", @{$customlists{'allow'}{'list'}});
>>>>>>>>> + $cgiparams{'BLOCK_LIST'} = join("\n", @{$customlists{'block'}{'list'}});
>>>>>>>>> +
>>>>>>>>> + $cgiparams{'ALLOW_ENABLED'} = ($customlists{'allow'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>>>> + $cgiparams{'BLOCK_ENABLED'} = ($customlists{'block'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + # Fallback to empty form
>>>>>>>>> + $cgiparams{'ALLOW_LIST'} //= "";
>>>>>>>>> + $cgiparams{'BLOCK_LIST'} //= "";
>>>>>>>>> +
>>>>>>>>> + # HTML checkboxes, unchecked = no or undef value in POST data
>>>>>>>>> + my %checked = ();
>>>>>>>>> + $checked{'ALLOW_ENABLED'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? " checked" : "";
>>>>>>>>> + $checked{'BLOCK_ENABLED'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? " checked" : "";
>>>>>>>>> +
>>>>>>>>> + # Disable reload button if not needed
>>>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>>>> +
>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz cl'});
>>>>>>>>> +
>>>>>>>>> + print <<END
>>>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>> +<table width="100%">
>>>>>>>>> + <tr>
>>>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl allow'}</b><br>$Lang::tr{'rpz cl allow info'}</td>
>>>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl block'}</b><br>$Lang::tr{'rpz cl block info'}</td>
>>>>>>>>> + </tr>
>>>>>>>>> + <tr>
>>>>>>>>> + <td colspan="2"><textarea name="ALLOW_LIST" class="domainlist" cols="45">
>>>>>>>>> +$cgiparams{'ALLOW_LIST'}</textarea></td>
>>>>>>>>> + <td colspan="2"><textarea name="BLOCK_LIST" class="domainlist" cols="45">
>>>>>>>>> +$cgiparams{'BLOCK_LIST'}</textarea></td>
>>>>>>>>> + </tr>
>>>>>>>>> + <tr>
>>>>>>>>> + <td><label for="allow_enabled">$Lang::tr{'rpz cl allow enable'}</label></td>
>>>>>>>>> + <td width="15%"><input type="checkbox" name="ALLOW_ENABLED" id="allow_enabled"$checked{'ALLOW_ENABLED'}></td>
>>>>>>>>> + <td><label for="block_enabled">$Lang::tr{'rpz cl block enable'}</label></td>
>>>>>>>>> + <td width="15%"><input type="checkbox" name="BLOCK_ENABLED" id="block_enabled"$checked{'BLOCK_ENABLED'}></td>
>>>>>>>>> + </tr>
>>>>>>>>> + <tr>
>>>>>>>>> + <td colspan="4"><hr></td>
>>>>>>>>> + </tr>
>>>>>>>>> + <tr>
>>>>>>>>> + <td align="right" colspan="4">
>>>>>>>>> + <button type="submit" name="ACTION" value="CL_SAVE">$Lang::tr{'save'}</button>
>>>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>>>> + </td>
>>>>>>>>> +</table>
>>>>>>>>> +</form>
>>>>>>>>> +END
>>>>>>>>> +;
>>>>>>>>> +
>>>>>>>>> + &Header::closebox();
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Output javascript and extra gui elements
>>>>>>>>> +sub _print_gui_extras {
>>>>>>>>> +
>>>>>>>>> + # Apply/Restart button modifier key handler
>>>>>>>>> + if(&_rpz_needs_reload()) {
>>>>>>>>> + print <<END
>>>>>>>>> +<script>
>>>>>>>>> + // Commit modifier key handler
>>>>>>>>> + (function(jq, document) {
>>>>>>>>> + var keyEventsOn = false; // Keyboard events attached
>>>>>>>>> + var keyModify = false; // Modifier key pressed
>>>>>>>>> + var mouseHover = false; // Mouse over commit button
>>>>>>>>> + var btnModified = false; // Button modified to "Restart"
>>>>>>>>> +
>>>>>>>>> + // Document-level key events, enable only while cursor is over button
>>>>>>>>> + function attachKeyEvents() {
>>>>>>>>> + if(keyEventsOn) {
>>>>>>>>> + return;
>>>>>>>>> + }
>>>>>>>>> + keyEventsOn = true;
>>>>>>>>> +
>>>>>>>>> + jq(document).on("keydown.rpz", function(event) {
>>>>>>>>> + if((!keyModify) && event.shiftKey) {
>>>>>>>>> + keyModify = true;
>>>>>>>>> + handleModify();
>>>>>>>>> + }
>>>>>>>>> + });
>>>>>>>>> + jq(document).on("keyup.rpz", function(event) {
>>>>>>>>> + if(keyModify && (!event.shiftKey)) {
>>>>>>>>> + keyModify = false;
>>>>>>>>> + handleModify();
>>>>>>>>> + }
>>>>>>>>> + });
>>>>>>>>> + }
>>>>>>>>> + function removeKeyEvents() {
>>>>>>>>> + keyModify = false;
>>>>>>>>> + if(keyEventsOn) {
>>>>>>>>> + jq(document).off("keydown.rpz keyup.rpz");
>>>>>>>>> + keyEventsOn = false;
>>>>>>>>> + }
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + // Attach mouse hover events to commit buttons
>>>>>>>>> + function attachMouseEvents() {
>>>>>>>>> + jq("button.commit").on("mouseenter", function(event) {
>>>>>>>>> + if(!mouseHover) {
>>>>>>>>> + mouseHover = true;
>>>>>>>>> + attachKeyEvents();
>>>>>>>>> + // Handle already pressed key
>>>>>>>>> + keyModify = !!(event.shiftKey);
>>>>>>>>> + handleModify();
>>>>>>>>> + }
>>>>>>>>> + });
>>>>>>>>> +
>>>>>>>>> + // Cursor moved away: Disable key listener to minimize events
>>>>>>>>> + jq("button.commit").on("mouseleave", function() {
>>>>>>>>> + if(mouseHover) {
>>>>>>>>> + mouseHover = false;
>>>>>>>>> + removeKeyEvents();
>>>>>>>>> + handleModify();
>>>>>>>>> + }
>>>>>>>>> + });
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + // Modify commit button
>>>>>>>>> + function handleModify() {
>>>>>>>>> + let modify = mouseHover && keyModify;
>>>>>>>>> + if(btnModified != modify) {
>>>>>>>>> + if(modify) {
>>>>>>>>> + jq("button.commit").text("$Lang::tr{'restart'}").val("UNB_RESTART");
>>>>>>>>> + } else {
>>>>>>>>> + jq("button.commit").text("$Lang::tr{'rpz apply'}").val("RPZ_RELOAD");
>>>>>>>>> + }
>>>>>>>>> + btnModified = modify;
>>>>>>>>> + }
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + // jQuery DOM ready
>>>>>>>>> + jq(function() {
>>>>>>>>> + attachMouseEvents();
>>>>>>>>> + });
>>>>>>>>> + })(jQuery, document);
>>>>>>>>> +</script>
>>>>>>>>> +END
>>>>>>>>> +;
>>>>>>>>> + } # End of modifier key handler
>>>>>>>>> +
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +
>>>>>>>>> +###--- Internal action processing functions ---###
>>>>>>>>> +
>>>>>>>>> +# Toggle zonefile on/off
>>>>>>>>> +sub _action_zf_toggle {
>>>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>>>> +
>>>>>>>>> + my $result = 0;
>>>>>>>>> + my $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>>>> +
>>>>>>>>> + # Perform toggle action
>>>>>>>>> + if($enabled eq 'on') {
>>>>>>>>> + $enabled = 'off';
>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>>>> + } else {
>>>>>>>>> + $enabled = 'on';
>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $action_key, $zonefiles{$action_key}{'url'}, '--no-reload');
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>> +
>>>>>>>>> + # Save changes
>>>>>>>>> + $zonefiles{$action_key}{'enabled'} = $enabled;
>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>> +
>>>>>>>>> + return 1;
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Remove zonefile
>>>>>>>>> +sub _action_zf_remove {
>>>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>>>> +
>>>>>>>>> + # Remove from rpz-config if currently active
>>>>>>>>> + if($zonefiles{$action_key}{'enabled'} eq 'on') {
>>>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>>>> +
>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + # Remove from data hash and save changes
>>>>>>>>> + delete $zonefiles{$action_key};
>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>> +
>>>>>>>>> + # Clear action_key, as the entry is now removed entirely
>>>>>>>>> + $action_key = "";
>>>>>>>>> +
>>>>>>>>> + return 1;
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Create or update zonefile entry
>>>>>>>>> +# Returns undef if gui needs to stay in editor mode
>>>>>>>>> +sub _action_zf_save {
>>>>>>>>> + my $result = 0;
>>>>>>>>> +
>>>>>>>>> + my $name = $cgiparams{'ZF_NAME'} // "";
>>>>>>>>> + my $url = $cgiparams{'ZF_URL'} // "";
>>>>>>>>> + my $remark = $cgiparams{'ZF_REMARK'} // "";
>>>>>>>>> + my $enabled = 'on'; # Enable new entries by default
>>>>>>>>> +
>>>>>>>>> + # Note on variables:
>>>>>>>>> + # name = unique key, will be used to address the entry
>>>>>>>>> + # action_key = name of the entry being edited, empty for new entry
>>>>>>>>> +
>>>>>>>>> + # Only check for unique name if it changed
>>>>>>>>> + # (this also checks new entries because the action_key is empty in this case)
>>>>>>>>> + $result = &_rpz_validate_zonefile($name, $url, $remark, (lc($name) ne lc($action_key)));
>>>>>>>>> + return unless &_rpz_check_result($result, 0);
>>>>>>>>> +
>>>>>>>>> + # Edit existing entry: Determine what was changed
>>>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>>>> + # Name und URL remain unchanged, only save remark and finish
>>>>>>>>> + if(($name eq $action_key) && ($url eq $zonefiles{$action_key}{'url'})) {
>>>>>>>>> + $zonefiles{$action_key}{'remark'} = $remark;
>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>> +
>>>>>>>>> + return 1;
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + # Entry was changed and needs to be recreated, preserve status
>>>>>>>>> + $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>>>> +
>>>>>>>>> + # Remove from rpz-config
>>>>>>>>> + return unless &_action_zf_remove();
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + # Add new entry to rpz-config
>>>>>>>>> + if($enabled eq 'on') {
>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $name, $url, '--no-reload');
>>>>>>>>> +
>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + # Add to global data hash and save changes
>>>>>>>>> + my %entry = ('enabled' => $enabled,
>>>>>>>>> + 'url' => $url,
>>>>>>>>> + 'remark' => $remark);
>>>>>>>>> +
>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>> +
>>>>>>>>> + return 1;
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Save custom lists
>>>>>>>>> +sub _action_cl_save {
>>>>>>>>> + return unless((defined $cgiparams{'ALLOW_LIST'}) && (defined $cgiparams{'BLOCK_LIST'}));
>>>>>>>>> +
>>>>>>>>> + my $result = 0;
>>>>>>>>> +
>>>>>>>>> + my @allowlist = split(/\R/, $cgiparams{'ALLOW_LIST'});
>>>>>>>>> + my @blocklist = split(/\R/, $cgiparams{'BLOCK_LIST'});
>>>>>>>>> +
>>>>>>>>> + # Validate lists
>>>>>>>>> + $result = &_rpz_validate_customlist(\@allowlist);
>>>>>>>>> + if($result != 0) {
>>>>>>>>> + $errormessage = &_rpz_error_tr(202, $result);
>>>>>>>>> + return;
>>>>>>>>> + }
>>>>>>>>> + $result = &_rpz_validate_customlist(\@blocklist);
>>>>>>>>> + if($result != 0) {
>>>>>>>>> + $errormessage = &_rpz_error_tr(203, $result);
>>>>>>>>> + return;
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + # Add to global data hash and save changes
>>>>>>>>> + $customlists{'allow'}{'list'} = \@allowlist;
>>>>>>>>> + $customlists{'block'}{'list'} = \@blocklist;
>>>>>>>>> + $customlists{'allow'}{'enabled'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? 'on' : 'off';
>>>>>>>>> + $customlists{'block'}{'enabled'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? 'on' : 'off';
>>>>>>>>> +
>>>>>>>>> + &_customlists_save_conf();
>>>>>>>>> + &_customlist_export('allow', $RPZ_ALLOWLIST);
>>>>>>>>> + &_customlist_export('block', $RPZ_BLOCKLIST);
>>>>>>>>> +
>>>>>>>>> + # Make new lists, request service reload on success
>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-make', 'allowblock', '--no-reload');
>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>> +
>>>>>>>>> + return 1;
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Trigger rpz-config reload
>>>>>>>>> +sub _action_rpz_reload {
>>>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>>>> +
>>>>>>>>> + # Immediately clear flag to prevent multiple reloads
>>>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + # Perform reload, recreate reload flag on error to enable retry
>>>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'reload');
>>>>>>>>> + if(not &_rpz_check_result($result, 0)) {
>>>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>>>> + return;
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + return 1;
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Trigger unbound restart
>>>>>>>>> +sub _action_unb_restart {
>>>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>>>> +
>>>>>>>>> + # Immediately clear flag to prevent multiple restarts
>>>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + # Perform restart, unboundctrl always exits zero
>>>>>>>>> + &General::system('/usr/local/bin/unboundctrl', 'restart');
>>>>>>>>> +
>>>>>>>>> + return 1;
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +
>>>>>>>>> +###--- Internal rpz-config functions ---###
>>>>>>>>> +
>>>>>>>>> +# Translate rpz-config exitcodes and messages
>>>>>>>>> +# 100-199: rpz-config, 200-299: webgui
>>>>>>>>> +sub _rpz_error_tr {
>>>>>>>>> + my ($error, $append) = @_;
>>>>>>>>> + $append //= '';
>>>>>>>>> +
>>>>>>>>> + # Translate numeric exit codes
>>>>>>>>> + if(looks_like_number($error)) {
>>>>>>>>> + if(defined $Lang::tr{"rpz exitcode $error"}) {
>>>>>>>>> + $error = $Lang::tr{"rpz exitcode $error"};
>>>>>>>>> + }
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + return "RPZ $Lang::tr{'error'}: $error" . &Header::escape($append);
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Check result of rpz-config system call, request reload on success
>>>>>>>>> +sub _rpz_check_result {
>>>>>>>>> + my ($result, $request_reload) = @_;
>>>>>>>>> + $request_reload //= 0;
>>>>>>>>> +
>>>>>>>>> + # exitcode 0 = success
>>>>>>>>> + if($result != 0) {
>>>>>>>>> + $errormessage = &_rpz_error_tr($result);
>>>>>>>>> + return;
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + # Set reload flag
>>>>>>>>> + if($request_reload) {
>>>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + return 1;
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Test whether reload flag is set
>>>>>>>>> +sub _rpz_needs_reload {
>>>>>>>>> + return (-f $RPZ_RELOAD_FLAG);
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Validate a zonefile entry, returns rpz-config exitcode on failure. Use _rpz_check_result to verify.
>>>>>>>>> +# unique = check for unique name
>>>>>>>>> +sub _rpz_validate_zonefile {
>>>>>>>>> + my ($name, $url, $remark, $unique) = @_;
>>>>>>>>> + $unique //= 1;
>>>>>>>>> +
>>>>>>>>> + unless($name =~ /^[a-zA-Z0-9_]{1,32}$/) {
>>>>>>>>> + return 101;
>>>>>>>>> + }
>>>>>>>>> + unless($url =~ /^[\w+\.:;\/\\&@#%?=\-~|!]{1,128}$/) {
>>>>>>>>> + return 105;
>>>>>>>>> + }
>>>>>>>>> + unless($remark =~ /^[\w \-()\.:;*\/\\?!&=]{0,32}$/) {
>>>>>>>>> + return 201;
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + # Check against already existing names
>>>>>>>>> + if($unique) {
>>>>>>>>> + foreach my $existing (keys %zonefiles) {
>>>>>>>>> + if(lc($name) eq lc($existing)) {
>>>>>>>>> + return 104;
>>>>>>>>> + }
>>>>>>>>> + }
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + return 0;
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +# Validate a custom list, returns number of rejected line on failure. Check for non-zero results.
>>>>>>>>> +# listref = array reference, cleanup = remove invalid entries instead of returning an error
>>>>>>>>> +sub _rpz_validate_customlist {
>>>>>>>>> + my ($listref, $cleanup) = @_;
>>>>>>>>> + $cleanup //= 0;
>>>>>>>>> +
>>>>>>>>> + foreach my $index (reverse 0..$#{$listref}) {
>>>>>>>>> + my $row = @$listref[$index];
>>>>>>>>> + next unless($row); # Skip/allow empty lines
>>>>>>>>> +
>>>>>>>>> + # Reject/remove everything besides wildcard domains and remarks
>>>>>>>>> + if((not &General::validwildcarddomainname($row)) && (not $row =~ /^;[\w \-()\.:;*\/\\?!&=]*$/)) {
>>>>>>>>> + unless($cleanup) {
>>>>>>>>> + # +1 for user friendly line number and to ensure non-zero exitcode
>>>>>>>>> + return $index + 1;
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + # Remove current row
>>>>>>>>> + splice(@$listref, $index, 1);
>>>>>>>>> + }
>>>>>>>>> + }
>>>>>>>>> +
>>>>>>>>> + return 0;
>>>>>>>>> +}
>>>>>>>>> +
>>>>>>>>> +
>>>>>>>>> +###--- Internal misc functions ---###
>>>>>>>>> +
>>>>>>>>> +# Send HTTP 303 redirect headers for post/request/get pattern
>>>>>>>>> +# (Must be sent before calling &Header::showhttpheaders())
>>>>>>>>> +sub _http_prg_redirect {
>>>>>>>>> + my $location = "https://$ENV{'SERVER_NAME'}:$ENV{'SERVER_PORT'}$ENV{'SCRIPT_NAME'}";
>>>>>>>>> + print "Status: 303 See Other\n";
>>>>>>>>> + print "Location: $location\n";
>>>>>>>>> +}
>>>>>>>>> diff --git a/lfs/rpz b/lfs/rpz
>>>>>>>>> new file mode 100644
>>>>>>>>> index 000000000..7ddbc38e5
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/lfs/rpz
>>>>>>>>> @@ -0,0 +1,96 @@
>>>>>>>>> +###############################################################################
>>>>>>>>> +# #
>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>> +# #
>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>> +# #
>>>>>>>>> +###############################################################################
>>>>>>>>> +
>>>>>>>>> +###############################################################################
>>>>>>>>> +# Definitions
>>>>>>>>> +###############################################################################
>>>>>>>>> +
>>>>>>>>> +include Config
>>>>>>>>> +
>>>>>>>>> +SUMMARY = response policy zone - RPZ reputation system for unbound DNS
>>>>>>>>> +
>>>>>>>>> +VER = 1.0.0
>>>>>>>>> +
>>>>>>>>> +THISAPP = rpz-$(VER)
>>>>>>>>> +DIR_APP = $(DIR_SRC)/$(THISAPP)
>>>>>>>>> +TARGET = $(DIR_INFO)/$(THISAPP)
>>>>>>>>> +
>>>>>>>>> +PROG = rpz
>>>>>>>>> +PAK_VER = 18
>>>>>>>>> +
>>>>>>>>> +DEPS =
>>>>>>>>> +
>>>>>>>>> +SERVICES =
>>>>>>>>> +
>>>>>>>>> +###############################################################################
>>>>>>>>> +# Top-level Rules
>>>>>>>>> +###############################################################################
>>>>>>>>> +
>>>>>>>>> +install : $(TARGET)
>>>>>>>>> +
>>>>>>>>> +check :
>>>>>>>>> +
>>>>>>>>> +download :
>>>>>>>>> +
>>>>>>>>> +b2 :
>>>>>>>>> +
>>>>>>>>> +dist:
>>>>>>>>> + @$(PAK)
>>>>>>>>> +
>>>>>>>>> +###############################################################################
>>>>>>>>> +# Installation Details
>>>>>>>>> +###############################################################################
>>>>>>>>> +
>>>>>>>>> +$(TARGET) :
>>>>>>>>> + @$(PREBUILD)
>>>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>>>> +
>>>>>>>>> + # RPZ scripts
>>>>>>>>> + install --verbose --mode=755 \
>>>>>>>>> + $(DIR_CONF)/rpz/{rpz-config,rpz-metrics,rpz-sleep,rpz-make,rpz-functions} \
>>>>>>>>> + --target-directory=/usr/sbin
>>>>>>>>> +
>>>>>>>>> + # RPZ config files
>>>>>>>>> + mkdir -pv /etc/unbound/local.d
>>>>>>>>> + install --verbose --mode=644 --owner=nobody --group=nobody \
>>>>>>>>> + $(DIR_CONF)/rpz/00-rpz.conf \
>>>>>>>>> + --target-directory=/etc/unbound/local.d
>>>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/local.d
>>>>>>>>> +
>>>>>>>>> + # RPZ custom list files for allow and block
>>>>>>>>> + mkdir -pv /var/ipfire/dns/rpz
>>>>>>>>> + touch /var/ipfire/dns/rpz/{allowlist,blocklist}
>>>>>>>>> + chown --verbose --recursive nobody:nobody /var/ipfire/dns/rpz
>>>>>>>>> +
>>>>>>>>> + # RPZ zone files
>>>>>>>>> + # create empty RPZ config file to avoid a unbound config error
>>>>>>>>> + mkdir -pv /etc/unbound/zonefiles
>>>>>>>>> + touch /etc/unbound/zonefiles/allow.rpz
>>>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/zonefiles
>>>>>>>>> +
>>>>>>>>> + # Install addon-specific language-files
>>>>>>>>> + install --verbose --mode=004 $(DIR_CONF)/rpz/rpz.*.pl \
>>>>>>>>> + --target-directory=/var/ipfire/addon-lang
>>>>>>>>> +
>>>>>>>>> + # Install backup definition
>>>>>>>>> + cp -vf $(DIR_CONF)/backup/includes/rpz /var/ipfire/backup/addons/includes/rpz
>>>>>>>>> +
>>>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>>>> + @$(POSTBUILD)
>>>>>>>>> diff --git a/make.sh b/make.sh
>>>>>>>>> index 827ea9e77..a77535b13 100755
>>>>>>>>> --- a/make.sh
>>>>>>>>> +++ b/make.sh
>>>>>>>>> @@ -390,7 +390,7 @@ prepareenv() {
>>>>>>>>> if [ "${free_space}" -lt "${required_space}" ]; then
>>>>>>>>> # Add any consumed space
>>>>>>>>> while read -r consumed_space path; do
>>>>>>>>> - (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>>>> + (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>>>> done <<< "$(du --summarize --bytes "${BUILD_DIR}" "${IMAGES_DIR}" "${LOG_DIR}" 2>/dev/null)"
>>>>>>>>> fi
>>>>>>>>>
>>>>>>>>> @@ -2087,6 +2087,7 @@ build_system() {
>>>>>>>>> lfsmake2 btrfs-progs
>>>>>>>>> lfsmake2 inotify-tools
>>>>>>>>> lfsmake2 grub-btrfs
>>>>>>>>> + lfsmake2 rpz
>>>>>>>>>
>>>>>>>>> lfsmake2 linux
>>>>>>>>> lfsmake2 rtl8812au
>>>>>>>>> diff --git a/src/paks/rpz/install.sh b/src/paks/rpz/install.sh
>>>>>>>>> new file mode 100644
>>>>>>>>> index 000000000..ef99bf742
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/src/paks/rpz/install.sh
>>>>>>>>> @@ -0,0 +1,36 @@
>>>>>>>>> +#!/bin/bash
>>>>>>>>> +###############################################################################
>>>>>>>>> +# #
>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>> +# #
>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>> +# #
>>>>>>>>> +###############################################################################
>>>>>>>>> +#
>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>> +extract_files
>>>>>>>>> +restore_backup ${NAME}
>>>>>>>>> +
>>>>>>>>> +# fix user created files
>>>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>>>> + /etc/unbound/zonefiles \
>>>>>>>>> + /etc/unbound/local.d
>>>>>>>>> +
>>>>>>>>> +# Update Language cache
>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>> +
>>>>>>>>> +# restart unbound to load config file
>>>>>>>>> +/etc/init.d/unbound restart
>>>>>>>>> diff --git a/src/paks/rpz/uninstall.sh b/src/paks/rpz/uninstall.sh
>>>>>>>>> new file mode 100644
>>>>>>>>> index 000000000..e11427df3
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/src/paks/rpz/uninstall.sh
>>>>>>>>> @@ -0,0 +1,38 @@
>>>>>>>>> +#!/bin/bash
>>>>>>>>> +###############################################################################
>>>>>>>>> +# #
>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>> +# #
>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>> +# #
>>>>>>>>> +###############################################################################
>>>>>>>>> +#
>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>> +
>>>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>>>> +/etc/init.d/unbound stop
>>>>>>>>> +
>>>>>>>>> +make_backup ${NAME}
>>>>>>>>> +remove_files
>>>>>>>>> +
>>>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>>>> +# "[1723428668] unbound-control[17117:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>>>> +
>>>>>>>>> +# Update Language cache
>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>> +
>>>>>>>>> +# start unbound to load unbound config file
>>>>>>>>> +/etc/init.d/unbound start
>>>>>>>>> diff --git a/src/paks/rpz/update.sh b/src/paks/rpz/update.sh
>>>>>>>>> new file mode 100644
>>>>>>>>> index 000000000..9bc340bc6
>>>>>>>>> --- /dev/null
>>>>>>>>> +++ b/src/paks/rpz/update.sh
>>>>>>>>> @@ -0,0 +1,52 @@
>>>>>>>>> +#!/bin/bash
>>>>>>>>> +###############################################################################
>>>>>>>>> +# #
>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>> +# #
>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>> +# #
>>>>>>>>> +###############################################################################
>>>>>>>>> +#
>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>> +
>>>>>>>>> +# from update.sh
>>>>>>>>> +extract_backup_includes
>>>>>>>>> +
>>>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>>>> +/etc/init.d/unbound stop
>>>>>>>>> +
>>>>>>>>> +# from uninstall.sh
>>>>>>>>> +make_backup ${NAME}
>>>>>>>>> +remove_files
>>>>>>>>> +
>>>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>>>> +# "unbound-control[nn:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>>>> +
>>>>>>>>> +# from install.sh
>>>>>>>>> +extract_files
>>>>>>>>> +restore_backup ${NAME}
>>>>>>>>> +
>>>>>>>>> +# fix user created files
>>>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>>>> + /etc/unbound/zonefiles \
>>>>>>>>> + /etc/unbound/local.d
>>>>>>>>> +
>>>>>>>>> +# Update Language cache
>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>> +
>>>>>>>>> +# restart unbound to load config files
>>>>>>>>> +/etc/init.d/unbound start
>>>>>>>>> --
>>>>>>>>> 2.39.5
>>>>>>>>>
>>>>>>> Jon
>>>>>>>
>>>>>>>
>>>>>>> --
>>>>>>> Jon Murphy
>>>>>>> jon.murphy@ipfire.org
>>>>> Jon
>>>>>
>>>>>
>>>>> --
>>>>> Jon Murphy
>>>>> jon.murphy@ipfire.org
>>>
>>
>
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
[not found] ` <afcb2a99-1281-43e3-bd3d-d915024683f6@ipfire.org>
@ 2025-03-20 16:26 ` Michael Tremer
2025-03-24 0:00 ` Re[2]: " Jon Murphy
0 siblings, 1 reply; 30+ messages in thread
From: Michael Tremer @ 2025-03-20 16:26 UTC (permalink / raw)
To: Jon Murphy; +Cc: IPFire: Development-List
Hello Jon,
Please don’t forget to Cc the list...
> On 19 Mar 2025, at 18:27, Jon Murphy <jon.murphy@ipfire.org> wrote:
>
> Michael,
>
>> Where in the code is this implemented? I cannot find anything like this:
>
> Keep in mind I am not a "C" person. Maybe in this section?:
>
> https://git.ipfire.org/?p=thirdparty/unbound.git;a=blob;f=services/authzone.c;hb=30b9cb5f813003d0a2b1c2e678652396615b1b7d#l5875
This where the AXFR response is being handled when doing a DNS zone transfer. This code is not being called when performing a HTTP download.
I understand that you don’t speak C, but you got the information from somewhere. Documentation maybe? Since that is out of date very often I like to consult the code.
> —
>
> When I was just learning about RPZ I created a separate RPZ file for testing. When I changed the SOA line with a new serial number, the RPZ file download would happen in about 5 minutes.
>
> https://people.ipfire.org/~jon/sblack-adhoc.rpz
It might well be that the file is not being reloaded if the download matches the content that unbound already has. That would of course save some resources.
However that won’t solve our problem with redundant downloads and having no cache.
> That is how I found out the SOA line is watched for a serial number change.
>
> I’ll reconfirm my findings.
>
>
>>>> The second reason is that we have a lot of firewalls out there. Not all of them will enable this feature and all of the lists, but even if it is a good chunk, we will generate terabytes of traffic which put load on the infrastructure and will cost money. It simply is not what we want to do, regardless of self-hosting those lists and pulling them from somewhere else.
>
> So I understand, are you thinking of hosting RPZ AXFR (DNS zone transfer) on IPFire infrastructure?
No, I don’t think that we can generally do this. The biggest problem is licensing as we cannot take anyones content and host it ourselves. We would re-distribute those lists and that will only work with permission of the publishers. I assume that would be too much work to actually get some useful content out there. We might limit ourselves to only those lists that are under a very permissive license. Nobody wants that.
From a technical point of view, DNS over TCP might not be very nice in terms of forging the transfer and so we would need TLS as well… It should work, but even if we would be able to encourage other people to publish their lists I doubt they would implement DNS over TLS for authoritative DNS. That standard is in very early stages as well.
As far as I can see, those vendors who offer a list as a commercial product are using DNS to distribute it (e.g. Spamhaus). Those people who have made this all a hobby are throwing the lists onto GitHub and let them handle the traffic.
Maybe we need to implement both?
-Michael
> Jon
>
>
>
> On 3/19/25 5:35 AM, Michael Tremer wrote:
>> Hello Jon,
>>
>> Where in the code is this implemented? I cannot find anything like this:
>>
>> Unbound loads the entire file into memory and then starts parsing it. The only special treatment there is is to check whether the first line is a valid zone entry. It does not even have to be a SOA record.
>>
>> https://git.ipfire.org/?p=thirdparty/unbound.git;a=blob;f=services/authzone.c;hb=30b9cb5f813003d0a2b1c2e678652396615b1b7d#l1188
>>
>> I am also concerned that Unbound will not be able to support an upstream proxy for any downloads. The caching situation is also unclear for me, so I believe that we will be looking at writing a custom downloader that implements all these things.
>>
>> -Michael
>>
>>> On 19 Mar 2025, at 02:58, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>
>>> Michael,
>>>
>>>> The emphasis is on the repeated downloads of the same list. That is
>>> > what cannot happen.
>>>
>>> The Unbound RPZ code, as installed within IPFire, watches for a change
>>> in the SOA line of each RPZ file. This is an example of the first few
>>> lines for every RPZ file.
>>>
>>> $TTL 300
>>> @ SOA localhost. root.localhost. 1742298960 43200 3600 86400 300
>>> NS localhost.
>>> ;
>>> ; Title: HaGeZi's Pop-Up Ads DNS Blocklist
>>> ; Description: Blocks annoying and malicious pop-up ads.
>>>
>>> If the SOA serial number changes (e.g. the 1742298960), then Unbound RPZ
>>> code does its thing and downloads. Otherwise there is no download.
>>>
>>>> So there has to be a way to ensure that we won’t download a list again
>>> > unless it has actually changed.
>>>
>>> This should do what you want but I may be missing your point.
>>>
>>>> DNS has a builtin functionality called AXFR. It simply does the job
>>> > for you. I was just wondering whether that was not being used.
>>>
>>> I need to read about AXFR/IXFR and learn a little more.
>>>
>>> Jon
>>>
>>> On 3/17/25 5:35 AM, Michael Tremer wrote:
>>>> Good Morning Jon,
>>>>
>>>>> On 16 Mar 2025, at 17:00, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>
>>>>> Michael,
>>>>>
>>>>> I was reading through you response again an I want to understand this post:
>>>>>
>>>>>> I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>>>> So if RPZ doesn't use HTTPS, what is it using? I am missing a key point here.
>>>> The emphasis is on the repeated downloads of the same list. That is what cannot happen.
>>>>
>>>> Although it might not affect a lot of people in our general user-base, there are some that have a metered connection and will pay for data by volume. Some of the lists I looked at are just under 20 MiB. Therefore we need to keep any traffic down to a minimum. The second reason is that we have a lot of firewalls out there. Not all of them will enable this feature and all of the lists, but even if it is a good chunk, we will generate terabytes of traffic which put load on the infrastructure and will cost money. It simply is not what we want to do, regardless of self-hosting those lists and pulling them from somewhere else.
>>>>
>>>> So there has to be a way to ensure that we won’t download a list again unless it has actually changed.
>>>>
>>>> DNS has a builtin functionality called AXFR. It simply does the job for you. I was just wondering whether that was not being used.
>>>>
>>>> HTTPS is an option because that is simply what we use elsewhere, but extra functionality will have to be built for it.
>>>>
>>>> -Michael
>>>>
>>>>> Jon
>>>>>
>>>>>
>>>>> On 2/13/25 3:34 PM, jon wrote:
>>>>>> Michael,
>>>>>>
>>>>>> I’ve read through your comments a few times and I ended up with many more questions.
>>>>>>
>>>>>>
>>>>>>> What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>>>> To me the efforts to get new code accepted seem to have changed and it seemed easier in the past. In the past I made the Core Team aware via the Dev Mailing List and wrote a simple two or three paragraphs of "What is it? / What is the value? / Here is the code"
>>>>>>
>>>>>>
>>>>>> So in an effort to move forward: How exactly is something presented to the Core Team?
>>>>>>
>>>>>> Is there an example of a recent effort that was presented that I can see as a sample? (This type of info can also be added to the Wiki)
>>>>>>
>>>>>> I understand you want it this way, but I don’t know what exactly is needed. Please be specific.
>>>>>>
>>>>>>
>>>>>> Jon
>>>>>>
>>>>>> PS - I am not ignoring your other comments, I am just trying to move forward and keep things simple.
>>>>>>
>>>>>>
>>>>>>
>>>>>>> On Feb 8, 2025, at 1:27 PM, Michael Tremer <michael.tremer@ipfire.org> wrote:
>>>>>>>
>>>>>>> Hello Jon,
>>>>>>>
>>>>>>> Thanks for your reply. And good that you are copying everyone into this conversation.
>>>>>>>
>>>>>>>> On 8 Feb 2025, at 18:41, jon <jon.murphy@ipfire.org> wrote:
>>>>>>>>
>>>>>>>> Michael,
>>>>>>>>
>>>>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort
>>>>>>>> Yes, this has been a separate effort (a very public separate effort). Yes, as you pointed this out early on with the "proof-of-concept" and then my request for people to help test RPZ. Nothing was hidden.
>>>>>>>>
>>>>>>>> This was done because you (and maybe others) did not have the time and I wanted to help and because I needed assistance with RPZ. I tried my best to do this without bothering you.
>>>>>>> I don’t that it is accurate that nobody wanted to help on this. The list was always open - although not every email has been replied to swiftly it is also your responsibility to raise a question again if it was missed. People here have open ears.
>>>>>>>
>>>>>>> It was also stated on this very list on in our documentation that working on something without involving the core team is a risky undertaking. Of course IPFire is free software and so everyone is free to fork if they wish to do so.
>>>>>>>
>>>>>>>>> and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls.
>>>>>>>> You were aware many steps along the way. See your email on July 28, 2024, August 15, 2024, September 30, 2024, December 23, 2024, and January 16. My attempts to get the team involved were met with "things are busy" and sometimes silence. (Yes, I get it, people are busy.)
>>>>>>>>
>>>>>>>> You and Adolf, Leo, Erik and Bernhard have been aware since the beginning. You mention you were aware of the "proof-of-concept". If you include those beginning posts, since Sep 2023.
>>>>>>> Yes, I am aware of a proof-of-concept that I have been running myself for a long time. I am also aware of the efforts that you have been taking.
>>>>>>>
>>>>>>> Yet I don’t think there has ever been any joint effort, or am I seeing that wrong?
>>>>>>>
>>>>>>>>> This has not been discussed . . . on our calls.
>>>>>>>> On the July 28th you stated:
>>>>>>>> "We have talked about RPZ many times on the monthly call since the URL filter feature is falling more and more out of fashion. I think there is also many posts about this on the forum."
>>>>>>>>
>>>>>>>> Please don’t insult me again by stating "you know what I mean".
>>>>>>>>
>>>>>>>> And it has been discussed but not documented in the Monthly Meeting notes.
>>>>>>> I am not at all insulting you. I don’t want to take this down to a personal level at all. This is a public mailing list and people who read this don’t need to listen to an argument we are having. They are here for the tech inside IPFire.
>>>>>>>
>>>>>>> When I wrote that it has not been discussed that does not mean that we have not been touching on the topic. We have been talking about lots of things on the calls, the weather, politics, how our pets are. None of that makes it to the logs. What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>>>>>
>>>>>>>>> Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation.
>>>>>>>> Regular conversation on the Dev Mailing list is many times met with silence. I get it, people are busy.
>>>>>>>>
>>>>>>>> And regular two-way conversation doesn’t happen on the list. At least not with me. I’d be happy to point out the posts that were met with silence.
>>>>>>>> Again, I get it, people are busy.
>>>>>>> And you think my emails are not being met with silence? This has nothing to do with this specific topic. This has something to do with how occupied people are and how engaged they are on certain topics. Not everyone is involved in all the things and simply will ignore emails simply based on their subject line.
>>>>>>>
>>>>>>>> But the "dip here to the list" were my attempts to get a conversation started. As I said, many time met with silence.
>>>>>>>>
>>>>>>>> The only place I was not met with silence was on the Community. You have a great group of people in the Community. It is a shame you don’t want to have others help. It would reduce your workload.
>>>>>>> You should stop making statements that are not true. Who doesn’t want anyone to help?
>>>>>>>
>>>>>>> Not having this conversation on a Saturday evening would reduce my workload. At least it would free up time for something else. Helping with the things that are already on the go would reduce the workload of the entire team. Starting one thing at a time and finishing it is a lot better to manage than starting a hundred things and not even finish one. I can tell you that I already have a hundred things on the go.
>>>>>>>
>>>>>>>>> Therefore, what am I supposed to do with this email?
>>>>>>>> To me it is beyond obvious…
>>>>>>>>
>>>>>>>> If it isn’t what you want, then guide me with how to do this the correct way. And be specific. I am trying to help. I am trying to make things better. I am trying to do things the right way.
>>>>>>> To me it isn’t. This is yet another project that has been dumped to the list like so many before and later on everyone has left to have the team deal with the rest.
>>>>>>>
>>>>>>> It is a huge patch set. You explained what the vision is, but that is about it. There is no chance this will continue if this disagreement isn’t solved first. I didn’t even look at the code.
>>>>>>>
>>>>>>>>> I don’t want to merge code that I don’t agree with.
>>>>>>>> I asked multiple times if you "agreed with the concept" and again, met with silence. Yes I get it, people are busy.
>>>>>>> Having support for RPZ? Yes, it was definitely on the roadmap. That I agree with.
>>>>>>>
>>>>>>>>> So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>>>> You mentioned this a in the past, but for some reason you do not disclose what I dismissed. Why do you continue to make this harder, wouldn’t it not be easier to tell me what I have dismissed?
>>>>>>>>
>>>>>>>> I have sent multiple emails trying to answer your concerns and comments. On July 28, Aug 14, Aug 22, Aug 23, Sep 30, etc.
>>>>>>>>
>>>>>>>> I’ve gone through all of the questions you asked and I cannot find a "dismissed" item.
>>>>>>> Maybe I need to be *more clear*. I feel humoured by this.
>>>>>>>
>>>>>>> It is late on a Saturday and I want my dinner soon, but certainly I have stated that this should never be an add-on considering it is supposed to replace URL Filter. We should never allow people to add their own sources. I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>>>>>>
>>>>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>>>> The maintainers of Unbound and/or RPZ?
>>>>>>>>
>>>>>>>> The maintainers of Hagezi list, the threatfox list, the urlhaus list, etc.?
>>>>>>>>
>>>>>>>> What else? The maintainers or the RPZ scripts? That is me. Let’s talk!
>>>>>>> You. I don’t care much about the providers of the lists.
>>>>>>>
>>>>>>>> See, this is where it gets confusing. There are hundreds of open source packages as part of IPFire. Pick the last five years of items added to the IPFire build. You're telling me you have "constructive conversation with the maintainers" of all of the added packages?
>>>>>>> They publish their software and they don’t care whether I am pulling it or not. They publish it with the commitment to maintain it - sometimes for better and sometimes for worse.
>>>>>>>
>>>>>>> You care about me pulling your code and I don’t know whether you would commit to maintain this.
>>>>>>>
>>>>>>> These two are very different cases.
>>>>>>>
>>>>>>>> Pick the IP Blocklists list (i.e., 3CORESEC, ABUSECH, DSHIELD, SPAMHAUS, etc.) or the Suricata lists (i.e.,Emergingthreats.net <http://emergingthreats.net/>,Abuse.ch <http://abuse.ch/>, etc.). So you’ve have "constructive conversation with the maintainers"?
>>>>>>> Yes, occasionally I have phone calls with a few of these providers.
>>>>>>>
>>>>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>>>> Ha! Yes a surprise. In the beginning you seemed interested as IPFire needed a replacement for URL Filter. You asked good questions about the lists picked, asked for the value to the users, etc. And I answered the best I could.
>>>>>>>>
>>>>>>>> You even asked: “Why is this realised as an add-on and not part of the core system?” from your Jul 28, 2024 email.
>>>>>>> Ah, so, why is the patch creating an add-on? Not that I am saying that what I say is law, but it has not been challenged either. If my input is being ignored, why should I put this to the top of my list of priorities? I am not disappointed about this, just trying to be very good with my time.
>>>>>>>
>>>>>>>> And on January 16, 2025 I wrote a message looking for help. And you were kind to respond quickly. So in three weeks time, since the kind response, something has changed. You went from supportive to "this".
>>>>>>>>
>>>>>>>> So yes, I am surprised.
>>>>>>> Well, maybe I should not have replied to that email. It was clear that you were on some path that was not right, but you were not interested before in finding the right path from the beginning.
>>>>>>>
>>>>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>>>> Be more specific, what has to change? What exactly did I dismiss?
>>>>>>> Dismissal is just my assumption. I don’t know what you actually did with my feedback. I can only see the end product that does not seem contain much of it. Repeatedly I have been pointing out that we should think before we build. I am sure a lot of hours have now gone into some code that simply does not satisfy me. And I am not not talking about the code itself, what it does is what I don’t think is right for us.
>>>>>>>
>>>>>>> The process is very clear for me that we should first of all think whether we want a certain feature now. Then there should be a clear roadmap for everyone to follow; tasks can be split-up as we go and hopefully then have something that is maintainable, interesting for our users and even would do us proud. This is how this should work.
>>>>>>>
>>>>>>> So, what has to change? I don’t think with shouting at each other, throwing patches around and making me generally unhappy is a good start.
>>>>>>>
>>>>>>> -Michael
>>>>>>>
>>>>>>>> Jon
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>> On Feb 6, 2025, at 2:13 PM, Michael Tremer <michael.tremer@ipfire.org> wrote:
>>>>>>>>>
>>>>>>>>> Hello Jon,
>>>>>>>>>
>>>>>>>>> Well, here we are again with another patch regarding this feature.
>>>>>>>>>
>>>>>>>>> I cannot quite see from your email what the question is, but if this is a request to have this merged into IPFire, I am once again sorry to disappoint you.
>>>>>>>>>
>>>>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls. Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation. Therefore, what am I supposed to do with this email?
>>>>>>>>>
>>>>>>>>> I don’t want to merge code that I don’t agree with. So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>>>>>
>>>>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>>>>>
>>>>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>>>>>
>>>>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>>>>>
>>>>>>>>> All the best,
>>>>>>>>> -Michael
>>>>>>>>>
>>>>>>>>>> On 6 Feb 2025, at 16:35, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>>>>
>>>>>>>>>> What is it?
>>>>>>>>>> Response Policy Zone (RPZ) is a mechanism to define local policies in a
>>>>>>>>>> standardized way and load those policies from external sources.
>>>>>>>>>> Bottom line: RPZ allows admins to easily block access to websites via DNS lookup.
>>>>>>>>>>
>>>>>>>>>> RPZ can block websites via categories. Examples include: fake websites, annoying
>>>>>>>>>> pop-up ads, newly registered domains, DoH bypass sites, bad "host" services,
>>>>>>>>>> maliscious top level domains (e.g., *.zip, *.mov), piracy, gambling, pornography,
>>>>>>>>>> and more. RPZ lists come from various RPZ providers and their available
>>>>>>>>>> catagories.
>>>>>>>>>>
>>>>>>>>>> This RPZ add-on enables the RPZ functionality by adding a couple lines in a
>>>>>>>>>> configuration file. This add-on simply adds configuration files and adds
>>>>>>>>>> scripts (config, metrics and sleep) to make RPZ easier for the admin to use.
>>>>>>>>>>
>>>>>>>>>> The RPZ scripts include additional languages: German, Spanish, French, Turkish,
>>>>>>>>>> and Italian.
>>>>>>>>>>
>>>>>>>>>> RPZ itself was release in 2010 and has been part of the IPFire build since ~2015.
>>>>>>>>>>
>>>>>>>>>> Why is it needed? What is its value?
>>>>>>>>>>
>>>>>>>>>> - The RPZ concept places this filtering into IPFire, our internet access
>>>>>>>>>> gateway, which is (should be) solely used as DNS source of the internal network.
>>>>>>>>>>
>>>>>>>>>> - As most sites use HTTPS it makes it difficult to filter traffic with URL
>>>>>>>>>> Filter without also properly configuring conventional (non-transparent)
>>>>>>>>>> mode on the proxy. RPZ is a nice replacement for the URL Filter.
>>>>>>>>>>
>>>>>>>>>> - No need to install and maintain an additional device like PiHole or AdBlock
>>>>>>>>>> browser extensions on multiple user devices.
>>>>>>>>>>
>>>>>>>>>> - This is an additional layer of protection for users. Less worry someone will
>>>>>>>>>> click on something that gets them into trouble. And, saying this with emphasis,
>>>>>>>>>> the ability to do it in one place!
>>>>>>>>>>
>>>>>>>>>> - Blocked sites save on unneeded traffic and can lessen the threat of malware
>>>>>>>>>> in advertisements
>>>>>>>>>>
>>>>>>>>>> - Logging allows the admin to see the site blocked and take actions
>>>>>>>>>>
>>>>>>>>>> - RPZ will be used at the home, home-office (work from home), schools,
>>>>>>>>>> ministerial, and at the office. Device counts are small (2-6) to medium (~80)
>>>>>>>>>> to mediam-large (200+).
>>>>>>>>>>
>>>>>>>>>> - RPZ can block ads, popups, phishing, scammers, spyware, malware, annoying
>>>>>>>>>> popups, NSFW links, DOH servers, and the usual internet trash.
>>>>>>>>>>
>>>>>>>>>> ------------------------------
>>>>>>>>>>
>>>>>>>>>> Change Log for RPZ add-on
>>>>>>>>>>
>>>>>>>>>> rpz-1.0.0-18 on 2025-02-05
>>>>>>>>>> - Build for approval & release as IPFire add-on
>>>>>>>>>>
>>>>>>>>>> ---
>>>>>>>>>>
>>>>>>>>>> rpz-beta-0.1.18-18.ipfire on 2025-02-01
>>>>>>>>>> rpz.cgi:
>>>>>>>>>> - new feature: added a mod key to force a unbound restart
>>>>>>>>>>
>>>>>>>>>> rpz-config and rpz-make:
>>>>>>>>>> - new feature: added action for unbound restart `rpz-config unbound-restart`
>>>>>>>>>>
>>>>>>>>>> rpz-metrics:
>>>>>>>>>> - simple reformatting
>>>>>>>>>> - rename far right column from "last update" to "last download"
>>>>>>>>>>
>>>>>>>>>> ---
>>>>>>>>>>
>>>>>>>>>> rpz-beta-0.1.17-17.ipfire on 2024-12-09
>>>>>>>>>> rpz-make
>>>>>>>>>> - bug fix: corrected validation regex for wildcards like: `*.domain.com`
>>>>>>>>>>
>>>>>>>>>> ---
>>>>>>>>>>
>>>>>>>>>> rpz-beta-0.1.16-16.ipfire on 2024-11-18
>>>>>>>>>> rpz-make
>>>>>>>>>> - new feature: updated validation regex
>>>>>>>>>> - bug fix: moved validation to beginning of process. Now we validate before
>>>>>>>>>> creating config files.
>>>>>>>>>>
>>>>>>>>>> rpz.cgi:
>>>>>>>>>> - new feature: use CSS color variables of the main ipfire theme
>>>>>>>>>> - bug fix: empty zonefile remarks were stored as “undef” and caused a warning
>>>>>>>>>> - bug fix: HTML textarea removes the first empty line in a custom list
>>>>>>>>>> - thank you Leo!
>>>>>>>>>>
>>>>>>>>>> ---
>>>>>>>>>>
>>>>>>>>>> rpz-beta-0.1.15-15.ipfire on 2024-11-04
>>>>>>>>>> rpz.cgi:
>>>>>>>>>> - new feature: added new language file for Turkish (thank you Peppe)
>>>>>>>>>>
>>>>>>>>>> rpz-make
>>>>>>>>>> - bug fix: corrected empty allow/block list issue. An empty allow/block list
>>>>>>>>>> will now remove contents of allow/block.rpz files and remove unneeded
>>>>>>>>>> allow/block.conf file. (thank you iptom)
>>>>>>>>>>
>>>>>>>>>> ---
>>>>>>>>>>
>>>>>>>>>> rpz-beta-0.1.14-14.ipfire on 2024-10-29
>>>>>>>>>> rpz-config:
>>>>>>>>>> - bug fix: correct missing rpz extension. `rpz-config list` displayed URL
>>>>>>>>>> incorrectly (thank you Bernhard)
>>>>>>>>>>
>>>>>>>>>> rpz.cgi:
>>>>>>>>>> - bug fix: remove extra `"` in language files (thank you Bernhard)
>>>>>>>>>> - new feature: slightly dim "apply" button when not enabled
>>>>>>>>>>
>>>>>>>>>> ---
>>>>>>>>>>
>>>>>>>>>> rpz-beta-0.1.13-13.ipfire on 2024-10-27
>>>>>>>>>> - skipped
>>>>>>>>>>
>>>>>>>>>> ---
>>>>>>>>>>
>>>>>>>>>> rpz-beta-0.1.12-12.ipfire on 2024-10-21
>>>>>>>>>> rpz.cgi:
>>>>>>>>>> - new feature: added new language file for French (thank you gw-ipfire)
>>>>>>>>>>
>>>>>>>>>> ---
>>>>>>>>>>
>>>>>>>>>> rpz-beta-0.1.11-11.ipfire on 2024-10-18
>>>>>>>>>> rpz.cgi:
>>>>>>>>>> - new feature: added new language file for Italian (thank you umberto)
>>>>>>>>>> - new feature: added new language file for Spanish (thank you Roberto)
>>>>>>>>>>
>>>>>>>>>> ---
>>>>>>>>>>
>>>>>>>>>> rpz-beta-0.1.10-10.ipfire on 2024-10-15
>>>>>>>>>> rpz-make:
>>>>>>>>>> - bug fix: corrected validation error for a custom list entry (thank you siosios)
>>>>>>>>>> - e.g., `*.cloudflare-dns.com`
>>>>>>>>>>
>>>>>>>>>> install.sh:
>>>>>>>>>> - bug fix: add chown to correct user created files
>>>>>>>>>>
>>>>>>>>>> update.sh:
>>>>>>>>>> - bug fix: add chown to correct user created files (thank you siosios)
>>>>>>>>>>
>>>>>>>>>> ---
>>>>>>>>>>
>>>>>>>>>> rpz-beta-0.1.9-9.ipfire on 2024-10-08
>>>>>>>>>> rpz.cgi:
>>>>>>>>>> - new feature: added new language file for German (thank you Leo)
>>>>>>>>>> - bug fix: add missing "rpz exitcode 110"
>>>>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>>>>
>>>>>>>>>> ---
>>>>>>>>>>
>>>>>>>>>> rpz-beta-0.1.8-8.ipfire on 2024-10-04
>>>>>>>>>> - skipped
>>>>>>>>>>
>>>>>>>>>> ---
>>>>>>>>>>
>>>>>>>>>> rpz-beta-0.1.7-7.ipfire on 2024-10-03
>>>>>>>>>> All:
>>>>>>>>>> - new feature: includes beta version numbers for pakfire package,
>>>>>>>>>> instead of only `rpz-1.0.0-1.ipfire`, for each release.
>>>>>>>>>>
>>>>>>>>>> rpz.cgi:
>>>>>>>>>> - new feature: added new WebGUI at `rpz.cgi`
>>>>>>>>>> - a BIG thank you to Leo Hofmann for all of his work creating the webgui!!
>>>>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>>>>
>>>>>>>>>> rpz-make:
>>>>>>>>>> - new feature: validate entries in allowlist and blocklist
>>>>>>>>>> - new feature: add "no-reload" option for WebGUI
>>>>>>>>>>
>>>>>>>>>> rpz-metrics:
>>>>>>>>>> - new feature: info can be sorted by name, by hit count, by line count, by
>>>>>>>>>> "enabled" list or all lists
>>>>>>>>>>
>>>>>>>>>> backups:
>>>>>>>>>> - bug fix: include all files in `/var/ipfire/dns/rpz` directory in backup
>>>>>>>>>>
>>>>>>>>>> update.sh:
>>>>>>>>>> - bug fix: corrected ownership for `/var/ipfire/dns/rpz` directory during an
>>>>>>>>>> update
>>>>>>>>>>
>>>>>>>>>> Build:
>>>>>>>>>> - bug fix: `block.rpz.conf` and `block.rpz` from build. Files to be created
>>>>>>>>>> by `rpz-make`
>>>>>>>>>>
>>>>>>>>>> WebGUI and German language file
>>>>>>>>>> Contribution-by: Leo-Andres Hofmann <hofmann@leo-andres.de>
>>>>>>>>>>
>>>>>>>>>> Spanish language file
>>>>>>>>>> Contribution-by: Roberto Peña
>>>>>>>>>>
>>>>>>>>>> Italian language file
>>>>>>>>>> Contribution-by: Umberto Parma
>>>>>>>>>>
>>>>>>>>>> French language file
>>>>>>>>>> Contribution-by: gw-ipfire
>>>>>>>>>>
>>>>>>>>>> Turkish language file
>>>>>>>>>> Contribution-by: Peppe Tech
>>>>>>>>>>
>>>>>>>>>> Contribution-by: Bernhard Bitsch <bbitsch@ipfire.org>
>>>>>>>>>> Contribution-by: Erik Kapfer <erik.kapfer@ipfire.org>
>>>>>>>>>> Signed-off-by: Jon Murphy <jon.murphy@ipfire.org
>>>>>>>>>> ---
>>>>>>>>>> config/backup/includes/rpz | 4 +
>>>>>>>>>> config/cfgroot/manualpages | 1 +
>>>>>>>>>> config/menu/EX-rpz.menu | 6 +
>>>>>>>>>> config/rootfiles/common/configroot | 1 +
>>>>>>>>>> config/rootfiles/common/web-user-interface | 1 +
>>>>>>>>>> config/rootfiles/packages/rpz | 20 +
>>>>>>>>>> config/rpz/00-rpz.conf | 10 +
>>>>>>>>>> config/rpz/rpz-config | 130 +++
>>>>>>>>>> config/rpz/rpz-functions | 85 ++
>>>>>>>>>> config/rpz/rpz-make | 203 +++++
>>>>>>>>>> config/rpz/rpz-metrics | 170 ++++
>>>>>>>>>> config/rpz/rpz-sleep | 58 ++
>>>>>>>>>> config/rpz/rpz.de.pl | 30 +
>>>>>>>>>> config/rpz/rpz.en.pl | 30 +
>>>>>>>>>> config/rpz/rpz.es.pl | 30 +
>>>>>>>>>> config/rpz/rpz.fr.pl | 30 +
>>>>>>>>>> config/rpz/rpz.it.pl | 30 +
>>>>>>>>>> config/rpz/rpz.tr.pl | 30 +
>>>>>>>>>> html/cgi-bin/rpz.cgi | 923 +++++++++++++++++++++
>>>>>>>>>> lfs/rpz | 96 +++
>>>>>>>>>> make.sh | 3 +-
>>>>>>>>>> src/paks/rpz/install.sh | 36 +
>>>>>>>>>> src/paks/rpz/uninstall.sh | 38 +
>>>>>>>>>> src/paks/rpz/update.sh | 52 ++
>>>>>>>>>> 24 files changed, 2016 insertions(+), 1 deletion(-)
>>>>>>>>>> create mode 100644 config/backup/includes/rpz
>>>>>>>>>> create mode 100644 config/menu/EX-rpz.menu
>>>>>>>>>> create mode 100644 config/rootfiles/packages/rpz
>>>>>>>>>> create mode 100644 config/rpz/00-rpz.conf
>>>>>>>>>> create mode 100644 config/rpz/rpz-config
>>>>>>>>>> create mode 100644 config/rpz/rpz-functions
>>>>>>>>>> create mode 100644 config/rpz/rpz-make
>>>>>>>>>> create mode 100755 config/rpz/rpz-metrics
>>>>>>>>>> create mode 100755 config/rpz/rpz-sleep
>>>>>>>>>> create mode 100644 config/rpz/rpz.de.pl
>>>>>>>>>> create mode 100644 config/rpz/rpz.en.pl
>>>>>>>>>> create mode 100644 config/rpz/rpz.es.pl
>>>>>>>>>> create mode 100644 config/rpz/rpz.fr.pl
>>>>>>>>>> create mode 100644 config/rpz/rpz.it.pl
>>>>>>>>>> create mode 100644 config/rpz/rpz.tr.pl
>>>>>>>>>> create mode 100644 html/cgi-bin/rpz.cgi
>>>>>>>>>> create mode 100644 lfs/rpz
>>>>>>>>>> create mode 100644 src/paks/rpz/install.sh
>>>>>>>>>> create mode 100644 src/paks/rpz/uninstall.sh
>>>>>>>>>> create mode 100644 src/paks/rpz/update.sh
>>>>>>>>>>
>>>>>>>>>> diff --git a/config/backup/includes/rpz b/config/backup/includes/rpz
>>>>>>>>>> new file mode 100644
>>>>>>>>>> index 000000000..36513e494
>>>>>>>>>> --- /dev/null
>>>>>>>>>> +++ b/config/backup/includes/rpz
>>>>>>>>>> @@ -0,0 +1,4 @@
>>>>>>>>>> +/var/ipfire/dns/rpz/*
>>>>>>>>>> +/etc/unbound/zonefiles/allow.rpz
>>>>>>>>>> +/etc/unbound/zonefiles/block.rpz
>>>>>>>>>> +/etc/unbound/local.d/*rpz.conf
>>>>>>>>>> diff --git a/config/cfgroot/manualpages b/config/cfgroot/manualpages
>>>>>>>>>> index 1f7e01efc..d3a48c633 100644
>>>>>>>>>> --- a/config/cfgroot/manualpages
>>>>>>>>>> +++ b/config/cfgroot/manualpages
>>>>>>>>>> @@ -70,6 +70,7 @@ pakfire.cgi=configuration/ipfire/pakfire
>>>>>>>>>> wlanap.cgi=addons/wireless
>>>>>>>>>> tor.cgi=addons/tor
>>>>>>>>>> samba.cgi=addons/samba
>>>>>>>>>> +rpz.cgi=addons/rpz
>>>>>>>>>>
>>>>>>>>>> # Logs menu
>>>>>>>>>> logs.cgi/summary.dat=configuration/logs/summary
>>>>>>>>>> diff --git a/config/menu/EX-rpz.menu b/config/menu/EX-rpz.menu
>>>>>>>>>> new file mode 100644
>>>>>>>>>> index 000000000..2f4daf410
>>>>>>>>>> --- /dev/null
>>>>>>>>>> +++ b/config/menu/EX-rpz.menu
>>>>>>>>>> @@ -0,0 +1,6 @@
>>>>>>>>>> +$subipfire->{'20.rpz'} = {
>>>>>>>>>> + 'caption' => $Lang::tr{'rpz'},
>>>>>>>>>> + 'uri' => '/cgi-bin/rpz.cgi',
>>>>>>>>>> + 'title' => "RPZ",
>>>>>>>>>> + 'enabled' => 1,
>>>>>>>>>> +};
>>>>>>>>>> diff --git a/config/rootfiles/common/configroot b/config/rootfiles/common/configroot
>>>>>>>>>> index 9839eee45..b30d6aae4 100644
>>>>>>>>>> --- a/config/rootfiles/common/configroot
>>>>>>>>>> +++ b/config/rootfiles/common/configroot
>>>>>>>>>> @@ -120,6 +120,7 @@ var/ipfire/menu.d/70-log.menu
>>>>>>>>>> #var/ipfire/menu.d/EX-apcupsd.menu
>>>>>>>>>> #var/ipfire/menu.d/EX-guardian.menu
>>>>>>>>>> #var/ipfire/menu.d/EX-mympd.menu
>>>>>>>>>> +#var/ipfire/menu.d/EX-rpz.menu
>>>>>>>>>> #var/ipfire/menu.d/EX-samba.menu
>>>>>>>>>> #var/ipfire/menu.d/EX-tor.menu
>>>>>>>>>> #var/ipfire/menu.d/EX-transmission.menu
>>>>>>>>>> diff --git a/config/rootfiles/common/web-user-interface b/config/rootfiles/common/web-user-interface
>>>>>>>>>> index 816241dae..e00464076 100644
>>>>>>>>>> --- a/config/rootfiles/common/web-user-interface
>>>>>>>>>> +++ b/config/rootfiles/common/web-user-interface
>>>>>>>>>> @@ -69,6 +69,7 @@ srv/web/ipfire/cgi-bin/proxy.cgi
>>>>>>>>>> srv/web/ipfire/cgi-bin/qos.cgi
>>>>>>>>>> srv/web/ipfire/cgi-bin/remote.cgi
>>>>>>>>>> srv/web/ipfire/cgi-bin/routing.cgi
>>>>>>>>>> +#srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>>>>> #srv/web/ipfire/cgi-bin/samba.cgi
>>>>>>>>>> srv/web/ipfire/cgi-bin/services.cgi
>>>>>>>>>> srv/web/ipfire/cgi-bin/shutdown.cgi
>>>>>>>>>> diff --git a/config/rootfiles/packages/rpz b/config/rootfiles/packages/rpz
>>>>>>>>>> new file mode 100644
>>>>>>>>>> index 000000000..1c8663049
>>>>>>>>>> --- /dev/null
>>>>>>>>>> +++ b/config/rootfiles/packages/rpz
>>>>>>>>>> @@ -0,0 +1,20 @@
>>>>>>>>>> +etc/unbound/local.d/00-rpz.conf
>>>>>>>>>> +etc/unbound/zonefiles
>>>>>>>>>> +etc/unbound/zonefiles/allow.rpz
>>>>>>>>>> +usr/sbin/rpz-config
>>>>>>>>>> +usr/sbin/rpz-functions
>>>>>>>>>> +usr/sbin/rpz-make
>>>>>>>>>> +usr/sbin/rpz-metrics
>>>>>>>>>> +usr/sbin/rpz-sleep
>>>>>>>>>> +var/ipfire/addon-lang/rpz.de.pl
>>>>>>>>>> +var/ipfire/addon-lang/rpz.en.pl
>>>>>>>>>> +var/ipfire/addon-lang/rpz.es.pl
>>>>>>>>>> +var/ipfire/addon-lang/rpz.fr.pl
>>>>>>>>>> +var/ipfire/addon-lang/rpz.it.pl
>>>>>>>>>> +var/ipfire/addon-lang/rpz.tr.pl
>>>>>>>>>> +var/ipfire/backup/addons/includes/rpz
>>>>>>>>>> +var/ipfire/dns/rpz
>>>>>>>>>> +var/ipfire/dns/rpz/allowlist
>>>>>>>>>> +var/ipfire/dns/rpz/blocklist
>>>>>>>>>> +var/ipfire/menu.d/EX-rpz.menu
>>>>>>>>>> +srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>>>>> diff --git a/config/rpz/00-rpz.conf b/config/rpz/00-rpz.conf
>>>>>>>>>> new file mode 100644
>>>>>>>>>> index 000000000..f005a4f2e
>>>>>>>>>> --- /dev/null
>>>>>>>>>> +++ b/config/rpz/00-rpz.conf
>>>>>>>>>> @@ -0,0 +1,10 @@
>>>>>>>>>> +server:
>>>>>>>>>> + module-config: "respip validator iterator"
>>>>>>>>>> +
>>>>>>>>>> +rpz:
>>>>>>>>>> + name: allow.rpz
>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>> + rpz-action-override: passthru
>>>>>>>>>> + rpz-log: yes
>>>>>>>>>> + rpz-log-name: allow
>>>>>>>>>> + rpz-signal-nxdomain-ra: yes
>>>>>>>>>> diff --git a/config/rpz/rpz-config b/config/rpz/rpz-config
>>>>>>>>>> new file mode 100644
>>>>>>>>>> index 000000000..c72d50f9b
>>>>>>>>>> --- /dev/null
>>>>>>>>>> +++ b/config/rpz/rpz-config
>>>>>>>>>> @@ -0,0 +1,130 @@
>>>>>>>>>> +#!/bin/bash
>>>>>>>>>> +###############################################################################
>>>>>>>>>> +# #
>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info@ipfire.org> #
>>>>>>>>>> +# #
>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>> +# #
>>>>>>>>>> +###############################################################################
>>>>>>>>>> +
>>>>>>>>>> +version="2025-01-11 - v44"
>>>>>>>>>> +
>>>>>>>>>> +############### Functions ###############
>>>>>>>>>> +
>>>>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>>>>> +
>>>>>>>>>> +############### Main ###############
>>>>>>>>>> +
>>>>>>>>>> +tagName="unbound"
>>>>>>>>>> +
>>>>>>>>>> +rpzAction="${1}" # input RPZ action
>>>>>>>>>> +rpzName="${2}" # input RPZ name
>>>>>>>>>> +rpzURL="${3}" # input RPZ URL
>>>>>>>>>> +rpzOption1="${4}" # input RPZ option #1
>>>>>>>>>> +rpzOption2="${5}" # input RPZ option #2
>>>>>>>>>> +
>>>>>>>>>> +rpzConfig="/etc/unbound/local.d/${rpzName}.rpz.conf" # output zone conf file
>>>>>>>>>> +rpzFile="/etc/unbound/zonefiles/${rpzName}.rpz" # output for RPZ file
>>>>>>>>>> +
>>>>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>>>>> +
>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>> + case "$1" in
>>>>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>>>>> + esac
>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>> +done
>>>>>>>>>> +
>>>>>>>>>> +case "${rpzAction}" in
>>>>>>>>>> + # add new rpz list
>>>>>>>>>> + add )
>>>>>>>>>> + check_name "${rpzName}" # is this a valid name?
>>>>>>>>>> + # does this config already exist? If yes, then exit
>>>>>>>>>> + if [[ -f "${rpzConfig}" ]] ; then
>>>>>>>>>> + msg_log "error: rpz: duplicate - ${rpzConfig} already exists. exit"
>>>>>>>>>> + exit 104
>>>>>>>>>> + fi
>>>>>>>>>> +
>>>>>>>>>> + # is this a valid URL?
>>>>>>>>>> + regex='^https://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
>>>>>>>>>> + if ! [[ "${rpzURL}" =~ $regex ]] ; then
>>>>>>>>>> + msg_log "error: rpz: the URL is not valid: \"${rpzURL}\". exit."
>>>>>>>>>> + exit 105
>>>>>>>>>> + fi
>>>>>>>>>> +
>>>>>>>>>> + # create the zone config file
>>>>>>>>>> + {
>>>>>>>>>> + echo "rpz:"
>>>>>>>>>> + echo " name: ${rpzName}.rpz"
>>>>>>>>>> + echo " zonefile: ${rpzFile}"
>>>>>>>>>> + echo " url: ${rpzURL}"
>>>>>>>>>> + echo " rpz-action-override: nxdomain"
>>>>>>>>>> + echo " rpz-log: ${rpzLog}"
>>>>>>>>>> + echo " rpz-log-name: ${rpzName}"
>>>>>>>>>> + echo " rpz-signal-nxdomain-ra: yes"
>>>>>>>>>> + } > "${rpzConfig}"
>>>>>>>>>> +
>>>>>>>>>> + # set-up zonefile
>>>>>>>>>> + # create an empty rpz file if it does not exist
>>>>>>>>>> + if [[ ! -f "${rpzFile}" ]] ; then
>>>>>>>>>> + touch "${rpzFile}"
>>>>>>>>>> + # unbound requires these settings for rpz files
>>>>>>>>>> + set_permissions "${rpzFile}" "${rpzConfig}"
>>>>>>>>>> + fi
>>>>>>>>>> + ;;
>>>>>>>>>> +
>>>>>>>>>> + # trash config file & rpz file
>>>>>>>>>> + remove )
>>>>>>>>>> + if ! [[ -f "${rpzConfig}" ]] ; then
>>>>>>>>>> + msg_log "error: rpz: cannot remove ${rpzConfig}, does not exist. exit"
>>>>>>>>>> + exit 106
>>>>>>>>>> + fi
>>>>>>>>>> +
>>>>>>>>>> + msg_log "info: rpz: remove config file & rpz file \"${rpzName}\""
>>>>>>>>>> + rm "${rpzConfig}"
>>>>>>>>>> + rm "${rpzFile}"
>>>>>>>>>> + ;;
>>>>>>>>>> +
>>>>>>>>>> + reload )
>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>> + ;;
>>>>>>>>>> +
>>>>>>>>>> + list )
>>>>>>>>>> + awk -F':' '/^\s*name:/{ gsub(/[[:blank:]]|\.rpz/, "",$2) ; NAME=$2 } \
>>>>>>>>>> + /^\s*url:/{ gsub(/[[:blank:]]/, "") ; print NAME"="$2":"$3} ' \
>>>>>>>>>> + /etc/unbound/local.d/*rpz.conf
>>>>>>>>>> + exit
>>>>>>>>>> + ;;
>>>>>>>>>> +
>>>>>>>>>> + unbound-restart )
>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>> + unbound_restart
>>>>>>>>>> + exit
>>>>>>>>>> + ;;
>>>>>>>>>> +
>>>>>>>>>> + * )
>>>>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>>>>> + printf "Usage: $(basename "$0") <ACTION> <NAME> <URL> <OPTION> <OPTION>\n"
>>>>>>>>>> + printf "Version: ${version}\n"
>>>>>>>>>> + exit 108
>>>>>>>>>> + ;;
>>>>>>>>>> +
>>>>>>>>>> +esac
>>>>>>>>>> +
>>>>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>>>>> +
>>>>>>>>>> +exit
>>>>>>>>>> diff --git a/config/rpz/rpz-functions b/config/rpz/rpz-functions
>>>>>>>>>> new file mode 100644
>>>>>>>>>> index 000000000..ace1d2690
>>>>>>>>>> --- /dev/null
>>>>>>>>>> +++ b/config/rpz/rpz-functions
>>>>>>>>>> @@ -0,0 +1,85 @@
>>>>>>>>>> +#!/bin/bash
>>>>>>>>>> +###############################################################################
>>>>>>>>>> +# #
>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>> +# #
>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>> +# #
>>>>>>>>>> +###############################################################################
>>>>>>>>>> +
>>>>>>>>>> +version="2024-12-10 - v02"
>>>>>>>>>> +
>>>>>>>>>> +############### Functions ###############
>>>>>>>>>> +
>>>>>>>>>> +msg_log () {
>>>>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>>>>> + if tty --silent ; then
>>>>>>>>>> + echo "${tagName}:" "$*"
>>>>>>>>>> + fi
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +# check for a valid name
>>>>>>>>>> +check_name () {
>>>>>>>>>> + local theName="${1}"
>>>>>>>>>> +
>>>>>>>>>> + regex='^[a-zA-Z0-9_]+$' # no dash or plus, alpha numeric only
>>>>>>>>>> + regex1='^(allow|block)$' # allow and block are reserved NAMEs
>>>>>>>>>> + if [[ ! "${theName}" =~ $regex ]] || [[ "${theName}" =~ $regex1 ]] ; then
>>>>>>>>>> + msg_log "error: rpz: the NAME is not valid: \"${theName}\". exit."
>>>>>>>>>> + exit 101
>>>>>>>>>> + fi
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +set_permissions () {
>>>>>>>>>> + chown nobody:nobody "$@"
>>>>>>>>>> + chmod 644 "$@"
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +check_unbound_conf () {
>>>>>>>>>> + local thecheckConf="${1:-yes}" # check config default is yes
>>>>>>>>>> +
>>>>>>>>>> + # check the above config files
>>>>>>>>>> + if [[ "${thecheckConf}" == yes ]] ; then
>>>>>>>>>> + msg_log "info: rpz: check for errors with \"unbound-checkconf\""
>>>>>>>>>> +
>>>>>>>>>> + if ! unbound-checkconf ; then
>>>>>>>>>> + msg_log "error: rpz: unbound-checkconf found invalid configuration."
>>>>>>>>>> + msg_log \
>>>>>>>>>> + "error: rpz: In Terminal run the command \"unbound-checkconf\" for more information. exit."
>>>>>>>>>> + exit 102
>>>>>>>>>> + fi
>>>>>>>>>> + fi
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +unbound_control_reload () {
>>>>>>>>>> + local theReload="${1:-yes}" # reload default is yes
>>>>>>>>>> +
>>>>>>>>>> + if [[ "${theReload}" == yes ]] ; then
>>>>>>>>>> + # reload due to the changes
>>>>>>>>>> + msg_log "info: rpz: run \"unbound-control reload\""
>>>>>>>>>> +
>>>>>>>>>> + if ! unbound-control reload ; then
>>>>>>>>>> + msg_log "error: rpz: unbound-control reload. exit."
>>>>>>>>>> + exit 109
>>>>>>>>>> + fi
>>>>>>>>>> + fi
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +unbound_restart () {
>>>>>>>>>> + # restart due to the changes
>>>>>>>>>> + msg_log "info: rpz: run \"unbound restart\""
>>>>>>>>>> +
>>>>>>>>>> + /usr/local/bin/unboundctrl restart
>>>>>>>>>> +}
>>>>>>>>>> diff --git a/config/rpz/rpz-make b/config/rpz/rpz-make
>>>>>>>>>> new file mode 100644
>>>>>>>>>> index 000000000..927d55170
>>>>>>>>>> --- /dev/null
>>>>>>>>>> +++ b/config/rpz/rpz-make
>>>>>>>>>> @@ -0,0 +1,203 @@
>>>>>>>>>> +#!/bin/bash
>>>>>>>>>> +###############################################################################
>>>>>>>>>> +# #
>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info@ipfire.org> #
>>>>>>>>>> +# #
>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>> +# #
>>>>>>>>>> +###############################################################################
>>>>>>>>>> +
>>>>>>>>>> +version="2025-01-11 - v14"
>>>>>>>>>> +
>>>>>>>>>> +############### Functions ###############
>>>>>>>>>> +
>>>>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>>>>> +
>>>>>>>>>> +# create the config file for allow
>>>>>>>>>> +make_allow_config () {
>>>>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>>>>> + local theConfig="/etc/unbound/local.d/00-rpz.conf" # output zone conf file
>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/allowlist" # input custom list of domains
>>>>>>>>>> +
>>>>>>>>>> + msg_log "info: rpz: make config file \"00-rpz.conf\""
>>>>>>>>>> +
>>>>>>>>>> + echo "server:
>>>>>>>>>> + module-config: \"respip validator iterator\"" > "${theConfig}"
>>>>>>>>>> +
>>>>>>>>>> + # does allow list exist?
>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>> +
>>>>>>>>>> + echo "rpz:
>>>>>>>>>> + name: allow.rpz
>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>> + rpz-action-override: passthru
>>>>>>>>>> + rpz-log: ${theLog}
>>>>>>>>>> + rpz-log-name: allow
>>>>>>>>>> + rpz-signal-nxdomain-ra: yes" >> "${theConfig}"
>>>>>>>>>> +
>>>>>>>>>> + fi
>>>>>>>>>> +
>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>> + set_permissions "${theConfig}"
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +# create the config file for block
>>>>>>>>>> +make_block_config () {
>>>>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>>>>> + local theConfig="/etc/unbound/local.d/block.rpz.conf" # output zone conf file
>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/blocklist" # input custom list of domains
>>>>>>>>>> +
>>>>>>>>>> + msg_log "info: rpz: make config file \"block.rpz.conf\""
>>>>>>>>>> +
>>>>>>>>>> + # does block list exist?
>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>> +
>>>>>>>>>> + echo "rpz:
>>>>>>>>>> + name: block.rpz
>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/block.rpz
>>>>>>>>>> + rpz-action-override: nxdomain
>>>>>>>>>> + rpz-log: ${theLog}
>>>>>>>>>> + rpz-log-name: block
>>>>>>>>>> + rpz-signal-nxdomain-ra: yes" > "${theConfig}"
>>>>>>>>>> +
>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>> + set_permissions "${theConfig}"
>>>>>>>>>> + else
>>>>>>>>>> + # no - trash the config file
>>>>>>>>>> + rm --verbose /etc/unbound/local.d/block.rpz.conf
>>>>>>>>>> + fi
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +# create an RPZ file for allow or block
>>>>>>>>>> +make_rpz_file () {
>>>>>>>>>> + local theName="${1}" # allow or block
>>>>>>>>>> + local theAction='.' # the default is nxdomain or block
>>>>>>>>>> + local actionList
>>>>>>>>>> +
>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>>>>> + local theZonefile="/etc/unbound/zonefiles/${theName}.rpz" # output file for RPZ
>>>>>>>>>> +
>>>>>>>>>> + # does a list exist?
>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>> +
>>>>>>>>>> + # for allow set to passthru
>>>>>>>>>> + [[ "${theName}" == allow ]] && theAction='rpz-passthru.'
>>>>>>>>>> +
>>>>>>>>>> + # drop any extra "blanks" and add "CNAME <RPZ action>." to each line
>>>>>>>>>> + actionList=$( awk '{$1=$1};1' "${theList}" |
>>>>>>>>>> + sed "/^[^;].*[[:alnum:]]/ s|$| CNAME ${theAction}|" )
>>>>>>>>>> +
>>>>>>>>>> + msg_log "info: rpz: create zonefile for \"${theName}list\""
>>>>>>>>>> +
>>>>>>>>>> +echo "; Name: ${theName} list
>>>>>>>>>> +; Last modified: $(date "+%Y-%m-%d at %H.%M.%S %Z")
>>>>>>>>>> +;
>>>>>>>>>> +; domains with actions list
>>>>>>>>>> +;
>>>>>>>>>> +${actionList}" > "${theZonefile}"
>>>>>>>>>> +
>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>> + set_permissions "${theZonefile}"
>>>>>>>>>> + # set-up allow/block list files
>>>>>>>>>> + set_permissions "${theList}"
>>>>>>>>>> + else
>>>>>>>>>> + msg_log "info: rpz: the ${theList} is empty."
>>>>>>>>>> +
>>>>>>>>>> + rm --verbose "${theZonefile}" # trash the RPZ file
>>>>>>>>>> + fi
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +# check if allow/block list is valid
>>>>>>>>>> +validate_list () {
>>>>>>>>>> + local theName="${1}" # allow or block
>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>>>>> +
>>>>>>>>>> + # remove good:
>>>>>>>>>> + # - properly formated domain names with or without leading wildcard
>>>>>>>>>> + # - properly formated top level domain (TLD) names with wildcard
>>>>>>>>>> + # - blank lines and comment lines
>>>>>>>>>> + # remaining lines are considered "bad"
>>>>>>>>>> + bad_lines=$( sed --regexp-extended \
>>>>>>>>>> + '/^(\*\.)?([a-zA-Z0-9](([a-zA-Z0-9\-]){0,61}[a-zA-Z0-9])?\.)+([a-zA-Z]{2,}|xn--[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])$/d ;
>>>>>>>>>> + /^(\*\.)([a-z]{2,61}|xn--[a-z0-9]{1,60})$/d ;
>>>>>>>>>> + /^$/d ; /^;/d' "${theList}" )
>>>>>>>>>> +
>>>>>>>>>> + if [[ ! -z "${bad_lines}" ]] ; then
>>>>>>>>>> + msg_log "error: rpz: invalid line(s) in ${theList}."
>>>>>>>>>> + printf "%s\n" "bad line(s): ${bad_lines}"
>>>>>>>>>> + exit 110
>>>>>>>>>> + fi
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +
>>>>>>>>>> +############### Main ###############
>>>>>>>>>> +
>>>>>>>>>> +tagName="unbound"
>>>>>>>>>> +
>>>>>>>>>> +rpzName="${1}" # input RPZ name
>>>>>>>>>> +
>>>>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>>>>> +
>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>> + case "$1" in
>>>>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>>>>> + esac
>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>> +done
>>>>>>>>>> +
>>>>>>>>>> +case "${rpzName}" in
>>>>>>>>>> + # make a new allow or block rpz file
>>>>>>>>>> +
>>>>>>>>>> + allow )
>>>>>>>>>> + validate_list 'allow' # is the allowlist valid?
>>>>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>>>>> + make_rpz_file 'allow'
>>>>>>>>>> + ;;
>>>>>>>>>> +
>>>>>>>>>> + allowblock )
>>>>>>>>>> + validate_list 'allow' # is the list valid?
>>>>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>>>>> + make_rpz_file 'allow'
>>>>>>>>>> + ;&
>>>>>>>>>> +
>>>>>>>>>> + block )
>>>>>>>>>> + validate_list 'block' # is the blocklist valid?
>>>>>>>>>> + make_block_config "${rpzLog}"
>>>>>>>>>> + make_rpz_file 'block'
>>>>>>>>>> + ;;
>>>>>>>>>> +
>>>>>>>>>> + reload )
>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>> + ;;
>>>>>>>>>> +
>>>>>>>>>> + unbound-restart )
>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>> + unbound_restart
>>>>>>>>>> + exit
>>>>>>>>>> + ;;
>>>>>>>>>> +
>>>>>>>>>> + * )
>>>>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>>>>> + printf "Usage: $(basename "$0") <NAME> <OPTION> <OPTION>\n"
>>>>>>>>>> + printf "Version: ${version}\n"
>>>>>>>>>> + exit 108
>>>>>>>>>> + ;;
>>>>>>>>>> +esac
>>>>>>>>>> +
>>>>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>>>>> +
>>>>>>>>>> +exit
>>>>>>>>>> diff --git a/config/rpz/rpz-metrics b/config/rpz/rpz-metrics
>>>>>>>>>> new file mode 100755
>>>>>>>>>> index 000000000..4d43e1629
>>>>>>>>>> --- /dev/null
>>>>>>>>>> +++ b/config/rpz/rpz-metrics
>>>>>>>>>> @@ -0,0 +1,170 @@
>>>>>>>>>> +#!/bin/bash
>>>>>>>>>> +###############################################################################
>>>>>>>>>> +# #
>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>> +# #
>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>> +# #
>>>>>>>>>> +###############################################################################
>>>>>>>>>> +
>>>>>>>>>> +version="2025-01-20 - v25"
>>>>>>>>>> +
>>>>>>>>>> +############### Main ###############
>>>>>>>>>> +
>>>>>>>>>> +weeks="2" # default to two message logs
>>>>>>>>>> +sortBy="name" # default "by name"
>>>>>>>>>> +rpzActive="enabled" # default "enabled only"
>>>>>>>>>> +
>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>> + case "$1" in
>>>>>>>>>> + --by-names | --by-name | name ) sortBy="name" ;;
>>>>>>>>>> +
>>>>>>>>>> + --by-hits | --by-hit | hits | hit ) sortBy="hit" ;;
>>>>>>>>>> +
>>>>>>>>>> + --by-lines | --by-line | lines | line ) sortBy="line" ;;
>>>>>>>>>> +
>>>>>>>>>> + --by-effect ) sortBy="effect" ;;
>>>>>>>>>> +
>>>>>>>>>> + --enabled-only ) rpzActive="enabled" ;;
>>>>>>>>>> +
>>>>>>>>>> + --active-all | --all | all ) rpzActive="all" ;;
>>>>>>>>>> +
>>>>>>>>>> + [0-9] | [0-9][0-9] ) weeks=$1 ;;
>>>>>>>>>> + esac
>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>> +done
>>>>>>>>>> +
>>>>>>>>>> +# get the list of message logs for N weeks
>>>>>>>>>> +messageLogs=$( find /var/log/messages* -type f | sort --version-sort |
>>>>>>>>>> + head -"${weeks}" )
>>>>>>>>>> +
>>>>>>>>>> +# get the list of RPZ names & counts from the message log(s)
>>>>>>>>>> +rpzNameCount=$( for logf in ${messageLogs} ; do
>>>>>>>>>> + zgrep --text --fixed-strings 'info: rpz: applied' "${logf}" |
>>>>>>>>>> + awk '$10 ~ /\[\w*]/ { print $10 }' ;
>>>>>>>>>> + done | sort | uniq --count )
>>>>>>>>>> +
>>>>>>>>>> +# flip results and remove brackets `[` and `]`
>>>>>>>>>> +rpzNameCount=$( echo "${rpzNameCount}" |
>>>>>>>>>> + awk '{ print $2, $1 }' |
>>>>>>>>>> + sed --regexp-extended 's|^\[(.*)\]|\1|' )
>>>>>>>>>> +
>>>>>>>>>> +# grab only names
>>>>>>>>>> +rpzNames=$( echo "${rpzNameCount}" | awk '{ print $1 }' )
>>>>>>>>>> +
>>>>>>>>>> +# get list of RPZ files
>>>>>>>>>> +rpzFileList=$( find /etc/unbound/zonefiles -type f -iname "*.rpz" )
>>>>>>>>>> +
>>>>>>>>>> +# get basename of those files
>>>>>>>>>> +rpzBaseNames=$( echo "${rpzFileList}" |
>>>>>>>>>> + sed 's|/etc/unbound/zonefiles/||g ; s|\.rpz||g ;' )
>>>>>>>>>> +
>>>>>>>>>> +# add to rpzNames
>>>>>>>>>> +rpzNames="${rpzNames}"$'\n'"${rpzBaseNames}"
>>>>>>>>>> +
>>>>>>>>>> +# drop duplicate names
>>>>>>>>>> +rpzNames=$( echo "${rpzNames}" | sort --unique )
>>>>>>>>>> +
>>>>>>>>>> +# get line count for each RPZ
>>>>>>>>>> +lineCount=$( echo "${rpzFileList}" | xargs wc -l )
>>>>>>>>>> +
>>>>>>>>>> +# get comment line count and blank line count for each RPZ
>>>>>>>>>> +commentCount=$( echo "${rpzFileList}" | xargs grep --count -e "^$" -e "^;" )
>>>>>>>>>> +
>>>>>>>>>> +# get modified date each RPZ
>>>>>>>>>> +modDateList=$( echo "${rpzFileList}" | xargs stat -c '%.10y %n' )
>>>>>>>>>> +
>>>>>>>>>> +ucListAuthZones=$( unbound-control list_auth_zones )
>>>>>>>>>> +
>>>>>>>>>> +# get width of RPZ names
>>>>>>>>>> +pWidth=$( echo "${rpzNames}" | awk '{ print $1" " }' | wc -L )
>>>>>>>>>> +pFormat="%-${pWidth}s %-8s %-8s %8s %12s %12s\n"
>>>>>>>>>> +
>>>>>>>>>> +# print title line
>>>>>>>>>> +printf "${pFormat}" "name" "hits" "active" "lines" " hits/line" "last download"
>>>>>>>>>> +printf -- "--------------"
>>>>>>>>>> +
>>>>>>>>>> +theResults=""
>>>>>>>>>> +totalLines=0
>>>>>>>>>> +totalHits=0
>>>>>>>>>> +while read -r theName
>>>>>>>>>> +do
>>>>>>>>>> + printf -- "--" # pretend progress bar
>>>>>>>>>> +
>>>>>>>>>> + # is this RPZ list active?
>>>>>>>>>> + theActive="disabled"
>>>>>>>>>> + if grep --quiet "^${theName}\.rpz" <<< "${ucListAuthZones}"
>>>>>>>>>> + then
>>>>>>>>>> + theActive="enabled"
>>>>>>>>>> + else
>>>>>>>>>> + [[ "${rpzActive}" == enabled ]] && continue
>>>>>>>>>> + fi
>>>>>>>>>> +
>>>>>>>>>> + # get hit count
>>>>>>>>>> + theHits="0"
>>>>>>>>>> + if output=$( grep "^${theName}\s" <<< "${rpzNameCount}" ) ; then
>>>>>>>>>> + theHits=$( echo "${output}" | awk '{ print $2 }' )
>>>>>>>>>> + totalHits=$(( totalHits + theHits ))
>>>>>>>>>> + fi
>>>>>>>>>> +
>>>>>>>>>> + # get line count
>>>>>>>>>> + theLines="n/a"
>>>>>>>>>> + hitsPerLine="0"
>>>>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${lineCount}" ) ; then
>>>>>>>>>> + theLines=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>>>>> + totalLines=$(( totalLines + theLines ))
>>>>>>>>>> +
>>>>>>>>>> + if [[ "${theLines}" -gt 2 ]] ; then
>>>>>>>>>> + hitsPerLine=$(( 100 * theHits / theLines ))
>>>>>>>>>> + fi
>>>>>>>>>> + fi
>>>>>>>>>> +
>>>>>>>>>> + # get modification date
>>>>>>>>>> + theModDate="n/a"
>>>>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${modDateList}" ) ; then
>>>>>>>>>> + theModDate=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>>>>> + fi
>>>>>>>>>> +
>>>>>>>>>> + # add to results list
>>>>>>>>>> + theResults+="${theName} ${theHits} ${theActive} ${theLines} ${hitsPerLine} ${theModDate}"$'\n'
>>>>>>>>>> +
>>>>>>>>>> +done <<< "${rpzNames}"
>>>>>>>>>> +
>>>>>>>>>> +case "${sortBy}" in
>>>>>>>>>> + # sort by "active" then by "name"
>>>>>>>>>> + name) sortArg=(-k3,3r -k1,1) ;;
>>>>>>>>>> +
>>>>>>>>>> + # sort by "active" then by "hits" then by "name"
>>>>>>>>>> + hit) sortArg=(-k3,3r -k2,2nr -k1,1) ;;
>>>>>>>>>> +
>>>>>>>>>> + # sort by "active" then by "lines" then by "name"
>>>>>>>>>> + line) sortArg=(-k3,3r -k4,4nr -k1,1) ;;
>>>>>>>>>> +
>>>>>>>>>> + # sort by "active" then by "effect" then by "name"
>>>>>>>>>> + effect) sortArg=(-k3,3r -k5,5nr -k1,1) ;;
>>>>>>>>>> +esac
>>>>>>>>>> +
>>>>>>>>>> +printf -- "--------------\n"
>>>>>>>>>> +# remove blank lines, sort, print as columns
>>>>>>>>>> +echo "${theResults}" |
>>>>>>>>>> + awk '!/^[[:space:]]*$/' |
>>>>>>>>>> + sort "${sortArg[@]}" |
>>>>>>>>>> + awk --assign=width="${pWidth}" \
>>>>>>>>>> + '{ printf "%-*s %-8s %-8s %8s %10s %% %12s\n", width, $1, $2, $3, $4, $5, $6 }'
>>>>>>>>>> +
>>>>>>>>>> +printf "${pFormat}" "" "=======" "" "========" "" ""
>>>>>>>>>> +printf "${pFormat}" "Totals -->" "${totalHits}" "" "${totalLines}" "" ""
>>>>>>>>>> +
>>>>>>>>>> +exit
>>>>>>>>>> diff --git a/config/rpz/rpz-sleep b/config/rpz/rpz-sleep
>>>>>>>>>> new file mode 100755
>>>>>>>>>> index 000000000..dd3603599
>>>>>>>>>> --- /dev/null
>>>>>>>>>> +++ b/config/rpz/rpz-sleep
>>>>>>>>>> @@ -0,0 +1,58 @@
>>>>>>>>>> +#!/bin/bash
>>>>>>>>>> +###############################################################################
>>>>>>>>>> +# #
>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>> +# #
>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>> +# #
>>>>>>>>>> +###############################################################################
>>>>>>>>>> +
>>>>>>>>>> +version="2024-08-16" # v05
>>>>>>>>>> +
>>>>>>>>>> +############### Functions ###############
>>>>>>>>>> +
>>>>>>>>>> +# send message to message log
>>>>>>>>>> +msg_log () {
>>>>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>>>>> + if tty --silent ; then
>>>>>>>>>> + echo "${tagName}:" "$*"
>>>>>>>>>> + fi
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +############### Main ###############
>>>>>>>>>> +
>>>>>>>>>> +tagName="unbound"
>>>>>>>>>> +
>>>>>>>>>> +sleepTime="${1:-5m}" # default to sleep for 5m (5 minutes)
>>>>>>>>>> +
>>>>>>>>>> +zoneList=$( unbound-control list_auth_zones | awk '{print $1}' )
>>>>>>>>>> +
>>>>>>>>>> +for zone in ${zoneList} ; do
>>>>>>>>>> + printf "disable ${zone}\t"
>>>>>>>>>> + unbound-control rpz_disable "${zone}"
>>>>>>>>>> +done
>>>>>>>>>> +
>>>>>>>>>> +msg_log "info: rpz: disabled all zones for ${sleepTime}"
>>>>>>>>>> +
>>>>>>>>>> +sleep "${sleepTime}"
>>>>>>>>>> +
>>>>>>>>>> +for zone in ${zoneList} ; do
>>>>>>>>>> + printf "enable ${zone}\t"
>>>>>>>>>> + unbound-control rpz_enable "${zone}"
>>>>>>>>>> +done
>>>>>>>>>> +
>>>>>>>>>> +msg_log "info: rpz: enabled all zones"
>>>>>>>>>> +
>>>>>>>>>> +exit
>>>>>>>>>> diff --git a/config/rpz/rpz.de.pl b/config/rpz/rpz.de.pl
>>>>>>>>>> new file mode 100644
>>>>>>>>>> index 000000000..3770c6bb0
>>>>>>>>>> --- /dev/null
>>>>>>>>>> +++ b/config/rpz/rpz.de.pl
>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>> +'rpz apply' => 'Übernehmen',
>>>>>>>>>> +'rpz cl allow enable' => 'Benutzerdefinierte Allowlist aktivieren:',
>>>>>>>>>> +'rpz cl allow info' => 'Zugelassene Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>>>>> +'rpz cl allow' => 'Benutzerdefinierte Allowlist',
>>>>>>>>>> +'rpz cl block enable' => 'Benutzerdefinierte Blocklist aktivieren:',
>>>>>>>>>> +'rpz cl block info' => 'Gesperrte Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>>>>> +'rpz cl block' => 'Benutzerdefinierte Blocklist',
>>>>>>>>>> +'rpz cl' => 'Benutzerdefinierte Listen',
>>>>>>>>>> +'rpz exitcode 101' => 'Der Name enthält unzulässige Zeichen',
>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf hat eine fehlerhafte Konfiguration ermittelt. Führen Sie das Kommando unbound-checkconf auf der Konsole aus, um weitere Informationen zu erhalten.',
>>>>>>>>>> +'rpz exitcode 103' => 'Die benutzerdefinierte Allow-/Blocklist ist leer',
>>>>>>>>>> +'rpz exitcode 104' => 'Ein Eintrag mit identischem Namen existiert bereits',
>>>>>>>>>> +'rpz exitcode 105' => 'Die URL ist ungültig',
>>>>>>>>>> +'rpz exitcode 106' => 'Eintrag kann nicht entfernt werden, der Name existiert nicht',
>>>>>>>>>> +'rpz exitcode 107' => 'Der Name ist ungültig - nur "allow" oder "block" möglich',
>>>>>>>>>> +'rpz exitcode 108' => 'Fehlende oder inkorrekte Parameter',
>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control reload ist fehlgeschlagen',
>>>>>>>>>> +'rpz exitcode 110' => 'Die benutzerdefinierte Allow-/Blocklist enthält unzulässige Einträge',
>>>>>>>>>> +'rpz exitcode 201' => 'Die Anmerkung enthält unzulässige Zeichen',
>>>>>>>>>> +'rpz exitcode 202' => 'Ungültiger Eintrag in der benutzerdefinierten Allowlist, Zeile ',
>>>>>>>>>> +'rpz exitcode 203' => 'Ungültiger Eintrag in der benutzerdefinierten Blocklist, Zeile ',
>>>>>>>>>> +'rpz exitcode 204' => 'Ausgewählter Eintrag existiert nicht: ',
>>>>>>>>>> +'rpz zf editor' => 'Zonendatei-Eintrag bearbeiten',
>>>>>>>>>> +'rpz zf imported' => '(importiert aus rpz-config)',
>>>>>>>>>> +'rpz zf remark info' => 'Erlaubte Zeichen sind a-z, A-Z, 0-9 und Unterstriche',
>>>>>>>>>> +'rpz zf' => 'Zonendateien',
>>>>>>>>>> +);
>>>>>>>>>> diff --git a/config/rpz/rpz.en.pl b/config/rpz/rpz.en.pl
>>>>>>>>>> new file mode 100644
>>>>>>>>>> index 000000000..0720a8940
>>>>>>>>>> --- /dev/null
>>>>>>>>>> +++ b/config/rpz/rpz.en.pl
>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>> +'rpz apply' => 'Apply',
>>>>>>>>>> +'rpz cl allow enable' => 'Enable custom allowlist:',
>>>>>>>>>> +'rpz cl allow info' => 'Allowed domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>>>>> +'rpz cl allow' => 'Custom allowlist',
>>>>>>>>>> +'rpz cl block enable' => 'Enable custom blocklist:',
>>>>>>>>>> +'rpz cl block info' => 'Blocked domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>>>>> +'rpz cl block' => 'Custom blocklist',
>>>>>>>>>> +'rpz cl' => 'Custom lists',
>>>>>>>>>> +'rpz exitcode 101' => 'the NAME is not valid',
>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf found invalid configuration. In the Terminal run the command unbound-checkconf for more information',
>>>>>>>>>> +'rpz exitcode 103' => 'the allow/block list is empty',
>>>>>>>>>> +'rpz exitcode 104' => 'duplicate - NAME already exists',
>>>>>>>>>> +'rpz exitcode 105' => 'the URL is not valid',
>>>>>>>>>> +'rpz exitcode 106' => 'cannot remove the NAME does not exist',
>>>>>>>>>> +'rpz exitcode 107' => 'the NAME is not valid - "allow" or "block" only',
>>>>>>>>>> +'rpz exitcode 108' => 'missing or incorrect parameter',
>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control reload failed',
>>>>>>>>>> +'rpz exitcode 110' => 'custom Allowlist/Blocklist contains invalid entries',
>>>>>>>>>> +'rpz exitcode 201' => 'the REMARK is not valid',
>>>>>>>>>> +'rpz exitcode 202' => 'invalid entry in allowlist, line ',
>>>>>>>>>> +'rpz exitcode 203' => 'invalid entry in blocklist, line ',
>>>>>>>>>> +'rpz exitcode 204' => 'Selected entry does not exist: ',
>>>>>>>>>> +'rpz zf editor' => 'Edit zonefiles entry',
>>>>>>>>>> +'rpz zf imported' => '(imported from rpz-config)',
>>>>>>>>>> +'rpz zf remark info' => 'Valid characters are a-z, A-Z, 0-9 and underscore.',
>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>> +);
>>>>>>>>>> diff --git a/config/rpz/rpz.es.pl b/config/rpz/rpz.es.pl
>>>>>>>>>> new file mode 100644
>>>>>>>>>> index 000000000..98628e4aa
>>>>>>>>>> --- /dev/null
>>>>>>>>>> +++ b/config/rpz/rpz.es.pl
>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>> +'rpz' => 'Zonas de política de respuesta (RPZ)',
>>>>>>>>>> +'rpz apply' => 'Aplicar',
>>>>>>>>>> +'rpz cl allow enable' => 'Habilitar la lista blanca personalizada:',
>>>>>>>>>> +'rpz cl allow info' => 'Dominio permitido (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>>>>> +'rpz cl allow' => 'Lista blanca personalizada',
>>>>>>>>>> +'rpz cl block enable' => 'Habilitar la lista negra personalizada:',
>>>>>>>>>> +'rpz cl block info' => 'Dominio bloqueado (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>>>>> +'rpz cl block' => 'Lista negra personalizada',
>>>>>>>>>> +'rpz cl' => 'Lista personalizada',
>>>>>>>>>> +'rpz exitcode 101' => 'El NOMBRE no es válido',
>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha encontrado una configuración no válida. Desde Terminal, ejecute el comando unbound-checkconf para mayor información',
>>>>>>>>>> +'rpz exitcode 103' => 'La lista de permitidos/bloqueados está vacía',
>>>>>>>>>> +'rpz exitcode 104' => 'duplicado - NOMBRE ya existe',
>>>>>>>>>> +'rpz exitcode 105' => 'la URL no es válida',
>>>>>>>>>> +'rpz exitcode 106' => 'no es posible eliminar el NOMBRE que no existe',
>>>>>>>>>> +'rpz exitcode 107' => 'el NOMBRE no es válido - sólo "permitir" o "bloquear"',
>>>>>>>>>> +'rpz exitcode 108' => 'parámetro faltante o incorrecto',
>>>>>>>>>> +'rpz exitcode 109' => 'Error al recargar unbound-control',
>>>>>>>>>> +'rpz exitcode 110' => 'la Lista blanca/Lista negra personalizada contiene entradas no válidas',
>>>>>>>>>> +'rpz exitcode 201' => 'el COMENTARIO no es válido',
>>>>>>>>>> +'rpz exitcode 202' => 'entrada no válida en la lista blanca, línea ',
>>>>>>>>>> +'rpz exitcode 203' => 'entrada no válida en la lista negra, línea ',
>>>>>>>>>> +'rpz exitcode 204' => 'La entrada seleccionada no existe: ',
>>>>>>>>>> +'rpz zf editor' => 'Editar la entrada de archivos de zona',
>>>>>>>>>> +'rpz zf imported' => '(importado de rpz-config)',
>>>>>>>>>> +'rpz zf remark info' => 'Los caracteres válidos son a-z, A-Z, 0-9 y guión bajo',
>>>>>>>>>> +'rpz zf' => 'Archivos de zona',
>>>>>>>>>> +);
>>>>>>>>>> diff --git a/config/rpz/rpz.fr.pl b/config/rpz/rpz.fr.pl
>>>>>>>>>> new file mode 100644
>>>>>>>>>> index 000000000..f35f3c2d0
>>>>>>>>>> --- /dev/null
>>>>>>>>>> +++ b/config/rpz/rpz.fr.pl
>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>> +'rpz apply' => 'Appliquer',
>>>>>>>>>> +'rpz cl allow enable' => 'Activer la liste d\'autorisations personnalisée:',
>>>>>>>>>> +'rpz cl allow info' => 'Domaines autorisés (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>>>>> +'rpz cl allow' => 'Liste d\'autorisations personnalisée',
>>>>>>>>>> +'rpz cl block enable' => 'Activer la liste de blocage personnalisée:',
>>>>>>>>>> +'rpz cl block info' => 'Domaines bloqués (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>>>>> +'rpz cl block' => 'liste de blocage personnalisé',
>>>>>>>>>> +'rpz cl' => 'Listes personnalisées',
>>>>>>>>>> +'rpz exitcode 101' => 'le NOM n\'est pas valide',
>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf configuration non valide trouvée. Dans le terminal, exécutez la commande unbound-checkconf pour plus d\'informations',
>>>>>>>>>> +'rpz exitcode 103' => 'la liste autoriser/bloquer est vide',
>>>>>>>>>> +'rpz exitcode 104' => 'le NOM existe déjà',
>>>>>>>>>> +'rpz exitcode 105' => 'L\'URL n\'est pas valide',
>>>>>>>>>> +'rpz exitcode 106' => 'impossible de supprimer le NOM n\'existe pas',
>>>>>>>>>> +'rpz exitcode 107' => 'le NOM n\'est pas valide - « autoriser » ou « bloquer » seulement',
>>>>>>>>>> +'rpz exitcode 108' => 'paramètre manquant ou incorrect',
>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control rechargement échoué',
>>>>>>>>>> +'rpz exitcode 110' => 'la liste autoriser/bloquer contient des entrées non valides',
>>>>>>>>>> +'rpz exitcode 201' => 'la REMARQUE n\'est pas valable',
>>>>>>>>>> +'rpz exitcode 202' => 'entrée non valide dans la liste d\'autorisation, ligne ',
>>>>>>>>>> +'rpz exitcode 203' => 'entrée non valide dans la liste de blocs, ligne ',
>>>>>>>>>> +'rpz exitcode 204' => 'L\'entrée sélectionnée n\'existe pas: ',
>>>>>>>>>> +'rpz zf editor' => 'Modifier l\'entrée Fichiers Zone',
>>>>>>>>>> +'rpz zf imported' => '(importé de rpz-config)',
>>>>>>>>>> +'rpz zf remark info' => 'Les caractères valides sont a-z, A-Z, 0-9 et soulignement.',
>>>>>>>>>> +'rpz zf' => 'Fichiers Zone',
>>>>>>>>>> +);
>>>>>>>>>> diff --git a/config/rpz/rpz.it.pl b/config/rpz/rpz.it.pl
>>>>>>>>>> new file mode 100644
>>>>>>>>>> index 000000000..ee81605c9
>>>>>>>>>> --- /dev/null
>>>>>>>>>> +++ b/config/rpz/rpz.it.pl
>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>> +'rpz apply' => 'Applica',
>>>>>>>>>> +'rpz cl allow enable' => 'Abilita la Whitelist personalizzata:',
>>>>>>>>>> +'rpz cl allow info' => 'Domini consentiti (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>>>>> +'rpz cl allow' => 'Whitelist personalizzata',
>>>>>>>>>> +'rpz cl block enable' => 'Abilita la Blacklist personalizzata:',
>>>>>>>>>> +'rpz cl block info' => 'Domini bloccati (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>>>>> +'rpz cl block' => 'Blacklist personalizzata',
>>>>>>>>>> +'rpz cl' => 'Liste personalizzate',
>>>>>>>>>> +'rpz exitcode 101' => 'il NOME non è valido',
>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha trovato una configurazione non valida. Dal Terminale esegui il comando unbound-checkconf per maggiori informazioni',
>>>>>>>>>> +'rpz exitcode 103' => 'l\'elenco consentiti/bloccati è vuoto',
>>>>>>>>>> +'rpz exitcode 104' => 'duplicato - NAME esiste di già',
>>>>>>>>>> +'rpz exitcode 105' => 'l\'URL non è valido',
>>>>>>>>>> +'rpz exitcode 106' => 'non è possibile rimuovere il NOME non esiste',
>>>>>>>>>> +'rpz exitcode 107' => 'il NOME non è valido - solo "consenti" o "blocca"',
>>>>>>>>>> +'rpz exitcode 108' => 'parametro mancante o non corretto',
>>>>>>>>>> +'rpz exitcode 109' => 'ricaricamento del controllo non associato non riuscito',
>>>>>>>>>> +'rpz exitcode 110' => 'la Whitelist/Blacklist personalizzata contiene voci non valide',
>>>>>>>>>> +'rpz exitcode 201' => 'l"OSSERVAZIONE non è valida',
>>>>>>>>>> +'rpz exitcode 202' => 'voce non valida nella Whitelist, riga ',
>>>>>>>>>> +'rpz exitcode 203' => 'voce non valida nella Blacklist, riga ',
>>>>>>>>>> +'rpz exitcode 204' => 'La voce selezionata non esiste: ',
>>>>>>>>>> +'rpz zf editor' => 'Modifica la voce dei file di zona',
>>>>>>>>>> +'rpz zf imported' => '(importato da rpz-config)',
>>>>>>>>>> +'rpz zf remark info' => 'I caratteri validi sono a-z, A-Z, 0-9 e trattino basso',
>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>> +);
>>>>>>>>>> diff --git a/config/rpz/rpz.tr.pl b/config/rpz/rpz.tr.pl
>>>>>>>>>> new file mode 100644
>>>>>>>>>> index 000000000..00226e192
>>>>>>>>>> --- /dev/null
>>>>>>>>>> +++ b/config/rpz/rpz.tr.pl
>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>> +# I�in eklendi Ayriyeten Response Policy Zone (RPZ)
>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>> +'rpz apply' => 'Uygulamak',
>>>>>>>>>> +'rpz cl allow enable' => '�zel Etkinlestir allowlist:',
>>>>>>>>>> +'rpz cl allow info' => 'Izin verilmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>>>>> +'rpz cl allow' => 'Etkinlestir allowlist',
>>>>>>>>>> +'rpz cl block enable' => '�zel Etkinlestir blocklist:',
>>>>>>>>>> +'rpz cl block info' => 'Engellenmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>>>>> +'rpz cl block' => 'Engellenmis blocklist',
>>>>>>>>>> +'rpz cl' => 'Engellenmis lists',
>>>>>>>>>> +'rpz exitcode 101' => 'NAME ge�erli degil',
>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ge�ersiz yapilandirma bulundu. Terminalde daha fazla bilgi i�in Unbound-checkConf komutunu �alistirin',
>>>>>>>>>> +'rpz exitcode 103' => 'allow/block liste bos',
>>>>>>>>>> +'rpz exitcode 104' => 'kopyalamak - NAME zaten var',
>>>>>>>>>> +'rpz exitcode 105' => 'URL ge�erli degil',
>>>>>>>>>> +'rpz exitcode 106' => '�ikarilamiyor NAME yok',
>>>>>>>>>> +'rpz exitcode 107' => 'NAME ge�erli degil - "allow" veya "block" yalniz',
>>>>>>>>>> +'rpz exitcode 108' => 'Parametre eksik veya yanlis',
>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control basarisiz',
>>>>>>>>>> +'rpz exitcode 110' => 'Engellenmis Allowlist/Blocklist ge�ersiz girisler i�erir',
>>>>>>>>>> +'rpz exitcode 201' => 'REMARK ge�erli degil',
>>>>>>>>>> +'rpz exitcode 202' => 'Ge�ersiz giris allowlist, line ',
>>>>>>>>>> +'rpz exitcode 203' => 'Ge�ersiz giris blocklist, line ',
>>>>>>>>>> +'rpz exitcode 204' => 'Se�ilen giris yok: ',
>>>>>>>>>> +'rpz zf editor' => 'yazimlamak zonefiles giris',
>>>>>>>>>> +'rpz zf imported' => '(ithal edildi rpz-config)',
>>>>>>>>>> +'rpz zf remark info' => 'Ge�erli karakterler a-z, A-Z, 0-9ve alt�st.',
>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>> +);
>>>>>>>>>> diff --git a/html/cgi-bin/rpz.cgi b/html/cgi-bin/rpz.cgi
>>>>>>>>>> new file mode 100644
>>>>>>>>>> index 000000000..a821c92ac
>>>>>>>>>> --- /dev/null
>>>>>>>>>> +++ b/html/cgi-bin/rpz.cgi
>>>>>>>>>> @@ -0,0 +1,923 @@
>>>>>>>>>> +#!/usr/bin/perl
>>>>>>>>>> +###############################################################################
>>>>>>>>>> +# #
>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>> +# Copyright (C) 2005-2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>> +# #
>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>> +# #
>>>>>>>>>> +###############################################################################
>>>>>>>>>> +
>>>>>>>>>> +use strict;
>>>>>>>>>> +use Scalar::Util qw(looks_like_number);
>>>>>>>>>> +
>>>>>>>>>> +# debugging
>>>>>>>>>> +#use warnings;
>>>>>>>>>> +#use CGI::Carp 'fatalsToBrowser';
>>>>>>>>>> +#use Data::Dumper;
>>>>>>>>>> +
>>>>>>>>>> +require '/var/ipfire/general-functions.pl';
>>>>>>>>>> +require "${General::swroot}/lang.pl";
>>>>>>>>>> +require "${General::swroot}/header.pl";
>>>>>>>>>> +
>>>>>>>>>> +###--- Extra HTML ---###
>>>>>>>>>> +my $extraHead = <<END
>>>>>>>>>> +<style>
>>>>>>>>>> + /* alternating row background */
>>>>>>>>>> + .tbl tr:nth-child(2n+2) {
>>>>>>>>>> + background-color: var(--color-light-grey);
>>>>>>>>>> + }
>>>>>>>>>> + .tbl tr:nth-child(2n+3) {
>>>>>>>>>> + background-color: var(--color-grey);
>>>>>>>>>> + }
>>>>>>>>>> + /* text styles */
>>>>>>>>>> + .tbl th:not(:last-child) {
>>>>>>>>>> + text-align: left;
>>>>>>>>>> + }
>>>>>>>>>> + div.right {
>>>>>>>>>> + text-align: right;
>>>>>>>>>> + margin-top: 0.5em;
>>>>>>>>>> + }
>>>>>>>>>> + /* customlist input */
>>>>>>>>>> + textarea.domainlist {
>>>>>>>>>> + margin: 0.5em 0;
>>>>>>>>>> + resize: vertical;
>>>>>>>>>> + min-height: 10em;
>>>>>>>>>> + overflow: auto;
>>>>>>>>>> + white-space: pre;
>>>>>>>>>> + }
>>>>>>>>>> + button[type=submit]:disabled {
>>>>>>>>>> + opacity: 0.6;
>>>>>>>>>> + }
>>>>>>>>>> +</style>
>>>>>>>>>> +END
>>>>>>>>>> +;
>>>>>>>>>> +###--- End of extra HTML ---###
>>>>>>>>>> +
>>>>>>>>>> +
>>>>>>>>>> +### Settings ###
>>>>>>>>>> +
>>>>>>>>>> +# Request DNS service reload after configuration change
>>>>>>>>>> +my $RPZ_RELOAD_FLAG = "${General::swroot}/dns/rpz/reload.flag";
>>>>>>>>>> +
>>>>>>>>>> +# Configuration file for all available zonefiles
>>>>>>>>>> +# Format: index, name (unique), enabled (on/off), URL, remark
>>>>>>>>>> +my $ZONEFILES_CONF = "${General::swroot}/dns/rpz/zonefiles.conf";
>>>>>>>>>> +
>>>>>>>>>> +# Configuration file for custom lists
>>>>>>>>>> +# IDs: 0=allowlist, 1=blocklist, 2=options (allow/block enabled)
>>>>>>>>>> +my $CUSTOMLISTS_CONF = "${General::swroot}/dns/rpz/customlists.conf";
>>>>>>>>>> +
>>>>>>>>>> +# Export custom lists to rpz-config
>>>>>>>>>> +my $RPZ_ALLOWLIST = "${General::swroot}/dns/rpz/allowlist";
>>>>>>>>>> +my $RPZ_BLOCKLIST = "${General::swroot}/dns/rpz/blocklist";
>>>>>>>>>> +
>>>>>>>>>> +
>>>>>>>>>> +### Preparation ###
>>>>>>>>>> +
>>>>>>>>>> +# Create missing config files
>>>>>>>>>> +unless(-f $ZONEFILES_CONF) { &General::system('touch', "$ZONEFILES_CONF"); }
>>>>>>>>>> +unless(-f $CUSTOMLISTS_CONF) { &General::system('touch', "$CUSTOMLISTS_CONF"); }
>>>>>>>>>> +
>>>>>>>>>> +
>>>>>>>>>> +## Global gui data
>>>>>>>>>> +my $errormessage = "";
>>>>>>>>>> +
>>>>>>>>>> +## Global configuration data
>>>>>>>>>> +my %zonefiles = ();
>>>>>>>>>> +my %customlists = ();
>>>>>>>>>> +&_zonefiles_load();
>>>>>>>>>> +&_customlists_load();
>>>>>>>>>> +
>>>>>>>>>> +## Global CGI form data
>>>>>>>>>> +my %cgiparams = ();
>>>>>>>>>> +&Header::getcgihash(\%cgiparams);
>>>>>>>>>> +
>>>>>>>>>> +my $action = $cgiparams{'ACTION'} // 'NONE';
>>>>>>>>>> +my $action_key = $cgiparams{'KEY'} // ''; # entry being edited, empty = none/new
>>>>>>>>>> +
>>>>>>>>>> +
>>>>>>>>>> +###--- Process form actions ---###
>>>>>>>>>> +
>>>>>>>>>> +# Zonefiles action: Check whether the requested entry exists
>>>>>>>>>> +if((substr($action, 0, 3) eq 'ZF_') && ($action_key)) {
>>>>>>>>>> + unless(defined $zonefiles{$action_key}) {
>>>>>>>>>> + $errormessage = &_rpz_error_tr(204, $action_key);
>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>> + }
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +## Perform actions
>>>>>>>>>> +if($action eq 'ZF_SAVE') { ## Save new or modified zonefiles entry
>>>>>>>>>> + if(&_action_zf_save()) {
>>>>>>>>>> + $action = 'NONE'; # success, return to main page
>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>> + } else {
>>>>>>>>>> + $action = 'ZF_EDIT'; # error occured, keep editing
>>>>>>>>>> + }
>>>>>>>>>> +
>>>>>>>>>> +} elsif($action eq 'ZF_TOGGLE') { ## Toggle on/off
>>>>>>>>>> + if(&_action_zf_toggle()) {
>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>> + }
>>>>>>>>>> +
>>>>>>>>>> +} elsif($action eq 'ZF_REMOVE') { ## Remove entry
>>>>>>>>>> + if(&_action_zf_remove()) {
>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>> + }
>>>>>>>>>> +
>>>>>>>>>> +} elsif($action eq 'CL_SAVE') { ## Save custom lists
>>>>>>>>>> + if(&_action_cl_save()) {
>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>> + }
>>>>>>>>>> +
>>>>>>>>>> +} elsif($action eq 'RPZ_RELOAD') { ## Reload dns configuration
>>>>>>>>>> + if(&_action_rpz_reload()) {
>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>> + }
>>>>>>>>>> +
>>>>>>>>>> +} elsif($action eq 'UNB_RESTART') { ## Restart unbound service
>>>>>>>>>> + if(&_action_unb_restart()) {
>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>> + }
>>>>>>>>>> +
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +
>>>>>>>>>> +###--- Start GUI ---###
>>>>>>>>>> +
>>>>>>>>>> +## Start http output
>>>>>>>>>> +&Header::showhttpheaders();
>>>>>>>>>> +
>>>>>>>>>> +# Start HTML
>>>>>>>>>> +&Header::openpage($Lang::tr{'rpz'}, 1, $extraHead);
>>>>>>>>>> +&Header::openbigbox('100%', 'left', '');
>>>>>>>>>> +
>>>>>>>>>> +# Show error messages
>>>>>>>>>> +if($errormessage) {
>>>>>>>>>> + &_print_message($errormessage);
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +# Handle zonefile add/edit mode
>>>>>>>>>> +if($action eq "ZF_EDIT") {
>>>>>>>>>> + &_print_zonefile_editor();
>>>>>>>>>> +
>>>>>>>>>> + # Finalize page and exit cleanly
>>>>>>>>>> + &Header::closebigbox();
>>>>>>>>>> + &Header::closepage();
>>>>>>>>>> + exit(0);
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +# Show gui elements
>>>>>>>>>> +&_print_zonefiles();
>>>>>>>>>> +&_print_customlists();
>>>>>>>>>> +&_print_gui_extras();
>>>>>>>>>> +
>>>>>>>>>> +&Header::closebigbox();
>>>>>>>>>> +&Header::closepage();
>>>>>>>>>> +
>>>>>>>>>> +###--- End of GUI ---###
>>>>>>>>>> +
>>>>>>>>>> +
>>>>>>>>>> +###--- Internal configuration file functions ---###
>>>>>>>>>> +
>>>>>>>>>> +# Load all available zonefiles from rpz-config and the internal configuration
>>>>>>>>>> +sub _zonefiles_load {
>>>>>>>>>> + # Clean start
>>>>>>>>>> + %zonefiles = ();
>>>>>>>>>> +
>>>>>>>>>> + # Source 1: Get the currently enabled zonefiles from rpz-config (expected format [name]=[URL])
>>>>>>>>>> + my @enabled_files = &General::system_output('/usr/sbin/rpz-config', 'list');
>>>>>>>>>> +
>>>>>>>>>> + foreach my $row (@enabled_files) {
>>>>>>>>>> + chomp($row);
>>>>>>>>>> +
>>>>>>>>>> + # Use regex instead of split() to skip non-matching lines
>>>>>>>>>> + next unless($row =~ /^(\w+)=(.+)$/);
>>>>>>>>>> + my ($name, $url) = ($1, $2);
>>>>>>>>>> +
>>>>>>>>>> + # Unique names are already guaranteed by rpz-config
>>>>>>>>>> + if(&_rpz_validate_zonefile($name, $url, '', 0) == 0) {
>>>>>>>>>> + # Populate global data hash, mark all found entries as enabled
>>>>>>>>>> + my %entry = ('enabled' => 'on',
>>>>>>>>>> + 'url' => $url,
>>>>>>>>>> + 'remark' => $Lang::tr{'rpz zf imported'});
>>>>>>>>>> +
>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>> + }
>>>>>>>>>> + }
>>>>>>>>>> +
>>>>>>>>>> + # Source 2: Get additional data and disabled entries from configuration file
>>>>>>>>>> + my %configured_files = ();
>>>>>>>>>> + &General::readhasharray($ZONEFILES_CONF, \%configured_files);
>>>>>>>>>> +
>>>>>>>>>> + foreach my $row (values (%configured_files)) {
>>>>>>>>>> + my ($name, $enabled, $url, $remark) = @$row;
>>>>>>>>>> + $remark //= "";
>>>>>>>>>> +
>>>>>>>>>> + next unless($name);
>>>>>>>>>> +
>>>>>>>>>> + # Check whether this row belongs to an entry already imported from rpz-config
>>>>>>>>>> + if(defined $zonefiles{$name}) {
>>>>>>>>>> + # Existing entry, only merge additional data
>>>>>>>>>> + $zonefiles{$name}{'remark'} = $remark;
>>>>>>>>>> + } else {
>>>>>>>>>> + # Skip entry if it is marked as enabled but not found by rpz-config. It was then deleted manually
>>>>>>>>>> + if($enabled ne 'on') {
>>>>>>>>>> + # Populate global data hash
>>>>>>>>>> + my %entry = ('enabled' => 'off',
>>>>>>>>>> + 'url' => $url // "",
>>>>>>>>>> + 'remark' => $remark);
>>>>>>>>>> +
>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>> + }
>>>>>>>>>> + }
>>>>>>>>>> + }
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +# Save internal zonefiles configuration
>>>>>>>>>> +sub _zonefiles_save_conf {
>>>>>>>>>> + my $index = 0;
>>>>>>>>>> + my %export = ();
>>>>>>>>>> +
>>>>>>>>>> + # Loop trough all zonefiles and create "hasharray" type export
>>>>>>>>>> + foreach my $name (keys %zonefiles) {
>>>>>>>>>> + my @entry = ($name,
>>>>>>>>>> + $zonefiles{$name}{'enabled'},
>>>>>>>>>> + $zonefiles{$name}{'url'},
>>>>>>>>>> + $zonefiles{$name}{'remark'});
>>>>>>>>>> +
>>>>>>>>>> + $export{$index++} = \@entry;
>>>>>>>>>> + }
>>>>>>>>>> +
>>>>>>>>>> + &General::writehasharray($ZONEFILES_CONF, \%export);
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +# Load custom lists from rpz-config and the internal configuration
>>>>>>>>>> +sub _customlists_load {
>>>>>>>>>> + # Clean start
>>>>>>>>>> + %customlists = ();
>>>>>>>>>> +
>>>>>>>>>> + # Load configuration file
>>>>>>>>>> + my %lists_conf = ();
>>>>>>>>>> + &General::readhasharray($CUSTOMLISTS_CONF, \%lists_conf);
>>>>>>>>>> +
>>>>>>>>>> + # Get list options, enabled by default to start import
>>>>>>>>>> + $customlists{'allow'}{'enabled'} = $lists_conf{2}[0] // 'on';
>>>>>>>>>> + $customlists{'block'}{'enabled'} = $lists_conf{2}[1] // 'on';
>>>>>>>>>> +
>>>>>>>>>> + # Import enabled list from rpz-config, otherwise retrieve stored or empty list from configuration file
>>>>>>>>>> + if($customlists{'allow'}{'enabled'} eq 'on') {
>>>>>>>>>> + &_customlist_import('allow', $RPZ_ALLOWLIST);
>>>>>>>>>> + } else {
>>>>>>>>>> + $customlists{'allow'}{'list'} = $lists_conf{0} // [];
>>>>>>>>>> + }
>>>>>>>>>> + if($customlists{'block'}{'enabled'} eq 'on') {
>>>>>>>>>> + &_customlist_import('block', $RPZ_BLOCKLIST);
>>>>>>>>>> + } else {
>>>>>>>>>> + $customlists{'block'}{'list'} = $lists_conf{1} // [];
>>>>>>>>>> + }
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +# Save internal custom lists configuration
>>>>>>>>>> +sub _customlists_save_conf {
>>>>>>>>>> + my %export = ();
>>>>>>>>>> +
>>>>>>>>>> + # Match IDs with import function
>>>>>>>>>> + $export{0} = $customlists{'allow'}{'list'};
>>>>>>>>>> + $export{1} = $customlists{'block'}{'list'};
>>>>>>>>>> + $export{2} = [$customlists{'allow'}{'enabled'}, $customlists{'block'}{'enabled'}];
>>>>>>>>>> +
>>>>>>>>>> + &General::writehasharray($CUSTOMLISTS_CONF, \%export);
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +# Import a custom list from plain file, returns empty list if file is missing
>>>>>>>>>> +sub _customlist_import {
>>>>>>>>>> + my ($listname, $filename) = @_;
>>>>>>>>>> + my @list = ();
>>>>>>>>>> +
>>>>>>>>>> + # File exists, load and check all lines
>>>>>>>>>> + if(-f $filename) {
>>>>>>>>>> + open(my $FH, '<', $filename) or die "Can't read $filename: $!";
>>>>>>>>>> + while(my $line = <$FH>) {
>>>>>>>>>> + chomp($line);
>>>>>>>>>> + push(@list, $line);
>>>>>>>>>> + }
>>>>>>>>>> + close($FH);
>>>>>>>>>> +
>>>>>>>>>> + # Clean up imported data
>>>>>>>>>> + &_rpz_validate_customlist(\@list, 1);
>>>>>>>>>> + }
>>>>>>>>>> +
>>>>>>>>>> + $customlists{$listname}{'list'} = \@list;
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +# Export a custom list to plain file or clear file if list is disabled
>>>>>>>>>> +sub _customlist_export {
>>>>>>>>>> + my ($listname, $filename) = @_;
>>>>>>>>>> + return unless(defined $customlists{$listname});
>>>>>>>>>> +
>>>>>>>>>> + # Write enabled domain list to file, otherwise save empty file
>>>>>>>>>> + open(my $FH, '>', $filename) or die "Can't write $filename: $!";
>>>>>>>>>> +
>>>>>>>>>> + if($customlists{$listname}{'enabled'} eq 'on') {
>>>>>>>>>> + foreach my $line (@{$customlists{$listname}{'list'}}) {
>>>>>>>>>> + print $FH "$line\n";
>>>>>>>>>> + }
>>>>>>>>>> + } else {
>>>>>>>>>> + print $FH "; Note: This list is currently disabled by $ENV{'SCRIPT_NAME'}\n";
>>>>>>>>>> + }
>>>>>>>>>> +
>>>>>>>>>> + close($FH);
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +
>>>>>>>>>> +###--- Internal gui functions ---###
>>>>>>>>>> +
>>>>>>>>>> +# Show simple message box
>>>>>>>>>> +sub _print_message {
>>>>>>>>>> + my ($message, $title) = @_;
>>>>>>>>>> + $title ||= $Lang::tr{'error messages'};
>>>>>>>>>> +
>>>>>>>>>> + &Header::openbox('100%', 'left', $title);
>>>>>>>>>> + print "<span>$message</span>";
>>>>>>>>>> + &Header::closebox();
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +# Show all zone files and related gui elements
>>>>>>>>>> +sub _print_zonefiles {
>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf'});
>>>>>>>>>> +
>>>>>>>>>> + print <<END
>>>>>>>>>> +<table class="tbl" width="100%">
>>>>>>>>>> + <tr>
>>>>>>>>>> + <th>$Lang::tr{'name'}</th>
>>>>>>>>>> + <th>URL</th>
>>>>>>>>>> + <th>$Lang::tr{'remark'}</th>
>>>>>>>>>> + <th colspan="3">$Lang::tr{'action'}</th>
>>>>>>>>>> + </tr>
>>>>>>>>>> +END
>>>>>>>>>> +;
>>>>>>>>>> +
>>>>>>>>>> + # Sort zonefiles by name and loop trough all entries
>>>>>>>>>> + foreach my $name (sort keys %zonefiles) {
>>>>>>>>>> +
>>>>>>>>>> + # Toggle button label translation
>>>>>>>>>> + my $toggle_tr = ($zonefiles{$name}{'enabled'} eq 'on') ? $Lang::tr{'click to disable'} : $Lang::tr{'click to enable'};
>>>>>>>>>> +
>>>>>>>>>> + print <<END
>>>>>>>>>> + <tr>
>>>>>>>>>> + <td>$name</td>
>>>>>>>>>> + <td>$zonefiles{$name}{'url'}</td>
>>>>>>>>>> + <td>$zonefiles{$name}{'remark'}</td>
>>>>>>>>>> +
>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_TOGGLE">
>>>>>>>>>> + <input type="image" src="/images/$zonefiles{$name}{'enabled'}.gif" title="$toggle_tr" alt="$toggle_tr">
>>>>>>>>>> + </form>
>>>>>>>>>> + </td>
>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_EDIT">
>>>>>>>>>> + <input type="image" src="/images/edit.gif" title="$Lang::tr{'edit'}" alt="$Lang::tr{'edit'}">
>>>>>>>>>> + </form>
>>>>>>>>>> + </td>
>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_REMOVE">
>>>>>>>>>> + <input type="image" src="/images/delete.gif" title="$Lang::tr{'remove'}" alt="$Lang::tr{'remove'}">
>>>>>>>>>> + </form>
>>>>>>>>>> + </td>
>>>>>>>>>> + </tr>
>>>>>>>>>> +END
>>>>>>>>>> +;
>>>>>>>>>> + }
>>>>>>>>>> +
>>>>>>>>>> + # Disable reload button if not needed
>>>>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>>>>> +
>>>>>>>>>> + print <<END
>>>>>>>>>> +</table>
>>>>>>>>>> +
>>>>>>>>>> +<div class="right">
>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>> + <input type="hidden" name="KEY" value="">
>>>>>>>>>> + <button type="submit" name="ACTION" value="ZF_EDIT">$Lang::tr{'add'}</button>
>>>>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>>>>> + </form>
>>>>>>>>>> +</div>
>>>>>>>>>> +END
>>>>>>>>>> +;
>>>>>>>>>> +
>>>>>>>>>> + &Header::closebox();
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +# Show zonefiles entry editor
>>>>>>>>>> +sub _print_zonefile_editor {
>>>>>>>>>> +
>>>>>>>>>> + # Key specified: Edit existing entry
>>>>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>>>>> + # Load data to be edited, but don't override already present values (allows user to edit after error)
>>>>>>>>>> + $cgiparams{'ZF_NAME'} //= $action_key;
>>>>>>>>>> + $cgiparams{'ZF_URL'} //= $zonefiles{$action_key}{'url'};
>>>>>>>>>> + $cgiparams{'ZF_REMARK'} //= $zonefiles{$action_key}{'remark'};
>>>>>>>>>> + }
>>>>>>>>>> +
>>>>>>>>>> + # Fallback to empty form
>>>>>>>>>> + $cgiparams{'ZF_NAME'} //= "";
>>>>>>>>>> + $cgiparams{'ZF_URL'} //= "";
>>>>>>>>>> + $cgiparams{'ZF_REMARK'} //= "";
>>>>>>>>>> +
>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf editor'});
>>>>>>>>>> +
>>>>>>>>>> + print <<END
>>>>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>> +<input type="hidden" name="KEY" value="$action_key">
>>>>>>>>>> +<table width="100%">
>>>>>>>>>> + <tr>
>>>>>>>>>> + <td width="20%">$Lang::tr{'name'}: <img src="/blob.gif" alt="*"></td>
>>>>>>>>>> + <td><input type="text" name="ZF_NAME" value="$cgiparams{'ZF_NAME'}" size="40" maxlength="32" title="$Lang::tr{'rpz zf remark info'}" pattern="[a-zA-Z0-9_]{1,32}" required></td>
>>>>>>>>>> + </tr>
>>>>>>>>>> + <tr>
>>>>>>>>>> + <td width="20%">URL: <img src="/blob.gif" alt="*"></td>
>>>>>>>>>> + <td><input type="url" name="ZF_URL" value="$cgiparams{'ZF_URL'}" size="40" maxlength="128" required></td>
>>>>>>>>>> + </tr>
>>>>>>>>>> + <tr>
>>>>>>>>>> + <td width="20%">$Lang::tr{'remark'}:</td>
>>>>>>>>>> + <td><input type="text" name="ZF_REMARK" value="$cgiparams{'ZF_REMARK'}" size="40" maxlength="32"></td>
>>>>>>>>>> + </tr>
>>>>>>>>>> + <tr>
>>>>>>>>>> + <td colspan="2"><hr></td>
>>>>>>>>>> + </tr>
>>>>>>>>>> + <tr>
>>>>>>>>>> + <td width="55%"><img src="/blob.gif" alt="*"> $Lang::tr{'required field'}</td>
>>>>>>>>>> + <td align="right"><button type="submit" name="ACTION" value="ZF_SAVE">$Lang::tr{'save'}</button></td>
>>>>>>>>>> + </tr>
>>>>>>>>>> +</table>
>>>>>>>>>> +</form>
>>>>>>>>>> +
>>>>>>>>>> +<div class="right">
>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>> + <button type="submit" name="ACTION" value="NONE">$Lang::tr{'back'}</button>
>>>>>>>>>> + </form>
>>>>>>>>>> +</div>
>>>>>>>>>> +END
>>>>>>>>>> +;
>>>>>>>>>> +
>>>>>>>>>> + &Header::closebox();
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +# Show custom allow/block files and related gui elements
>>>>>>>>>> +sub _print_customlists {
>>>>>>>>>> +
>>>>>>>>>> + # Load lists from config, unless they are currently being edited
>>>>>>>>>> + if($action ne 'CL_SAVE') {
>>>>>>>>>> + $cgiparams{'ALLOW_LIST'} = join("\n", @{$customlists{'allow'}{'list'}});
>>>>>>>>>> + $cgiparams{'BLOCK_LIST'} = join("\n", @{$customlists{'block'}{'list'}});
>>>>>>>>>> +
>>>>>>>>>> + $cgiparams{'ALLOW_ENABLED'} = ($customlists{'allow'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>>>>> + $cgiparams{'BLOCK_ENABLED'} = ($customlists{'block'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>>>>> + }
>>>>>>>>>> +
>>>>>>>>>> + # Fallback to empty form
>>>>>>>>>> + $cgiparams{'ALLOW_LIST'} //= "";
>>>>>>>>>> + $cgiparams{'BLOCK_LIST'} //= "";
>>>>>>>>>> +
>>>>>>>>>> + # HTML checkboxes, unchecked = no or undef value in POST data
>>>>>>>>>> + my %checked = ();
>>>>>>>>>> + $checked{'ALLOW_ENABLED'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? " checked" : "";
>>>>>>>>>> + $checked{'BLOCK_ENABLED'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? " checked" : "";
>>>>>>>>>> +
>>>>>>>>>> + # Disable reload button if not needed
>>>>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>>>>> +
>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz cl'});
>>>>>>>>>> +
>>>>>>>>>> + print <<END
>>>>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>> +<table width="100%">
>>>>>>>>>> + <tr>
>>>>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl allow'}</b><br>$Lang::tr{'rpz cl allow info'}</td>
>>>>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl block'}</b><br>$Lang::tr{'rpz cl block info'}</td>
>>>>>>>>>> + </tr>
>>>>>>>>>> + <tr>
>>>>>>>>>> + <td colspan="2"><textarea name="ALLOW_LIST" class="domainlist" cols="45">
>>>>>>>>>> +$cgiparams{'ALLOW_LIST'}</textarea></td>
>>>>>>>>>> + <td colspan="2"><textarea name="BLOCK_LIST" class="domainlist" cols="45">
>>>>>>>>>> +$cgiparams{'BLOCK_LIST'}</textarea></td>
>>>>>>>>>> + </tr>
>>>>>>>>>> + <tr>
>>>>>>>>>> + <td><label for="allow_enabled">$Lang::tr{'rpz cl allow enable'}</label></td>
>>>>>>>>>> + <td width="15%"><input type="checkbox" name="ALLOW_ENABLED" id="allow_enabled"$checked{'ALLOW_ENABLED'}></td>
>>>>>>>>>> + <td><label for="block_enabled">$Lang::tr{'rpz cl block enable'}</label></td>
>>>>>>>>>> + <td width="15%"><input type="checkbox" name="BLOCK_ENABLED" id="block_enabled"$checked{'BLOCK_ENABLED'}></td>
>>>>>>>>>> + </tr>
>>>>>>>>>> + <tr>
>>>>>>>>>> + <td colspan="4"><hr></td>
>>>>>>>>>> + </tr>
>>>>>>>>>> + <tr>
>>>>>>>>>> + <td align="right" colspan="4">
>>>>>>>>>> + <button type="submit" name="ACTION" value="CL_SAVE">$Lang::tr{'save'}</button>
>>>>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>>>>> + </td>
>>>>>>>>>> +</table>
>>>>>>>>>> +</form>
>>>>>>>>>> +END
>>>>>>>>>> +;
>>>>>>>>>> +
>>>>>>>>>> + &Header::closebox();
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +# Output javascript and extra gui elements
>>>>>>>>>> +sub _print_gui_extras {
>>>>>>>>>> +
>>>>>>>>>> + # Apply/Restart button modifier key handler
>>>>>>>>>> + if(&_rpz_needs_reload()) {
>>>>>>>>>> + print <<END
>>>>>>>>>> +<script>
>>>>>>>>>> + // Commit modifier key handler
>>>>>>>>>> + (function(jq, document) {
>>>>>>>>>> + var keyEventsOn = false; // Keyboard events attached
>>>>>>>>>> + var keyModify = false; // Modifier key pressed
>>>>>>>>>> + var mouseHover = false; // Mouse over commit button
>>>>>>>>>> + var btnModified = false; // Button modified to "Restart"
>>>>>>>>>> +
>>>>>>>>>> + // Document-level key events, enable only while cursor is over button
>>>>>>>>>> + function attachKeyEvents() {
>>>>>>>>>> + if(keyEventsOn) {
>>>>>>>>>> + return;
>>>>>>>>>> + }
>>>>>>>>>> + keyEventsOn = true;
>>>>>>>>>> +
>>>>>>>>>> + jq(document).on("keydown.rpz", function(event) {
>>>>>>>>>> + if((!keyModify) && event.shiftKey) {
>>>>>>>>>> + keyModify = true;
>>>>>>>>>> + handleModify();
>>>>>>>>>> + }
>>>>>>>>>> + });
>>>>>>>>>> + jq(document).on("keyup.rpz", function(event) {
>>>>>>>>>> + if(keyModify && (!event.shiftKey)) {
>>>>>>>>>> + keyModify = false;
>>>>>>>>>> + handleModify();
>>>>>>>>>> + }
>>>>>>>>>> + });
>>>>>>>>>> + }
>>>>>>>>>> + function removeKeyEvents() {
>>>>>>>>>> + keyModify = false;
>>>>>>>>>> + if(keyEventsOn) {
>>>>>>>>>> + jq(document).off("keydown.rpz keyup.rpz");
>>>>>>>>>> + keyEventsOn = false;
>>>>>>>>>> + }
>>>>>>>>>> + }
>>>>>>>>>> +
>>>>>>>>>> + // Attach mouse hover events to commit buttons
>>>>>>>>>> + function attachMouseEvents() {
>>>>>>>>>> + jq("button.commit").on("mouseenter", function(event) {
>>>>>>>>>> + if(!mouseHover) {
>>>>>>>>>> + mouseHover = true;
>>>>>>>>>> + attachKeyEvents();
>>>>>>>>>> + // Handle already pressed key
>>>>>>>>>> + keyModify = !!(event.shiftKey);
>>>>>>>>>> + handleModify();
>>>>>>>>>> + }
>>>>>>>>>> + });
>>>>>>>>>> +
>>>>>>>>>> + // Cursor moved away: Disable key listener to minimize events
>>>>>>>>>> + jq("button.commit").on("mouseleave", function() {
>>>>>>>>>> + if(mouseHover) {
>>>>>>>>>> + mouseHover = false;
>>>>>>>>>> + removeKeyEvents();
>>>>>>>>>> + handleModify();
>>>>>>>>>> + }
>>>>>>>>>> + });
>>>>>>>>>> + }
>>>>>>>>>> +
>>>>>>>>>> + // Modify commit button
>>>>>>>>>> + function handleModify() {
>>>>>>>>>> + let modify = mouseHover && keyModify;
>>>>>>>>>> + if(btnModified != modify) {
>>>>>>>>>> + if(modify) {
>>>>>>>>>> + jq("button.commit").text("$Lang::tr{'restart'}").val("UNB_RESTART");
>>>>>>>>>> + } else {
>>>>>>>>>> + jq("button.commit").text("$Lang::tr{'rpz apply'}").val("RPZ_RELOAD");
>>>>>>>>>> + }
>>>>>>>>>> + btnModified = modify;
>>>>>>>>>> + }
>>>>>>>>>> + }
>>>>>>>>>> +
>>>>>>>>>> + // jQuery DOM ready
>>>>>>>>>> + jq(function() {
>>>>>>>>>> + attachMouseEvents();
>>>>>>>>>> + });
>>>>>>>>>> + })(jQuery, document);
>>>>>>>>>> +</script>
>>>>>>>>>> +END
>>>>>>>>>> +;
>>>>>>>>>> + } # End of modifier key handler
>>>>>>>>>> +
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +
>>>>>>>>>> +###--- Internal action processing functions ---###
>>>>>>>>>> +
>>>>>>>>>> +# Toggle zonefile on/off
>>>>>>>>>> +sub _action_zf_toggle {
>>>>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>>>>> +
>>>>>>>>>> + my $result = 0;
>>>>>>>>>> + my $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>>>>> +
>>>>>>>>>> + # Perform toggle action
>>>>>>>>>> + if($enabled eq 'on') {
>>>>>>>>>> + $enabled = 'off';
>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>>>>> + } else {
>>>>>>>>>> + $enabled = 'on';
>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $action_key, $zonefiles{$action_key}{'url'}, '--no-reload');
>>>>>>>>>> + }
>>>>>>>>>> +
>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>> +
>>>>>>>>>> + # Save changes
>>>>>>>>>> + $zonefiles{$action_key}{'enabled'} = $enabled;
>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>> +
>>>>>>>>>> + return 1;
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +# Remove zonefile
>>>>>>>>>> +sub _action_zf_remove {
>>>>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>>>>> +
>>>>>>>>>> + # Remove from rpz-config if currently active
>>>>>>>>>> + if($zonefiles{$action_key}{'enabled'} eq 'on') {
>>>>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>>>>> +
>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>> + }
>>>>>>>>>> +
>>>>>>>>>> + # Remove from data hash and save changes
>>>>>>>>>> + delete $zonefiles{$action_key};
>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>> +
>>>>>>>>>> + # Clear action_key, as the entry is now removed entirely
>>>>>>>>>> + $action_key = "";
>>>>>>>>>> +
>>>>>>>>>> + return 1;
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +# Create or update zonefile entry
>>>>>>>>>> +# Returns undef if gui needs to stay in editor mode
>>>>>>>>>> +sub _action_zf_save {
>>>>>>>>>> + my $result = 0;
>>>>>>>>>> +
>>>>>>>>>> + my $name = $cgiparams{'ZF_NAME'} // "";
>>>>>>>>>> + my $url = $cgiparams{'ZF_URL'} // "";
>>>>>>>>>> + my $remark = $cgiparams{'ZF_REMARK'} // "";
>>>>>>>>>> + my $enabled = 'on'; # Enable new entries by default
>>>>>>>>>> +
>>>>>>>>>> + # Note on variables:
>>>>>>>>>> + # name = unique key, will be used to address the entry
>>>>>>>>>> + # action_key = name of the entry being edited, empty for new entry
>>>>>>>>>> +
>>>>>>>>>> + # Only check for unique name if it changed
>>>>>>>>>> + # (this also checks new entries because the action_key is empty in this case)
>>>>>>>>>> + $result = &_rpz_validate_zonefile($name, $url, $remark, (lc($name) ne lc($action_key)));
>>>>>>>>>> + return unless &_rpz_check_result($result, 0);
>>>>>>>>>> +
>>>>>>>>>> + # Edit existing entry: Determine what was changed
>>>>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>>>>> + # Name und URL remain unchanged, only save remark and finish
>>>>>>>>>> + if(($name eq $action_key) && ($url eq $zonefiles{$action_key}{'url'})) {
>>>>>>>>>> + $zonefiles{$action_key}{'remark'} = $remark;
>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>> +
>>>>>>>>>> + return 1;
>>>>>>>>>> + }
>>>>>>>>>> +
>>>>>>>>>> + # Entry was changed and needs to be recreated, preserve status
>>>>>>>>>> + $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>>>>> +
>>>>>>>>>> + # Remove from rpz-config
>>>>>>>>>> + return unless &_action_zf_remove();
>>>>>>>>>> + }
>>>>>>>>>> +
>>>>>>>>>> + # Add new entry to rpz-config
>>>>>>>>>> + if($enabled eq 'on') {
>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $name, $url, '--no-reload');
>>>>>>>>>> +
>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>> + }
>>>>>>>>>> +
>>>>>>>>>> + # Add to global data hash and save changes
>>>>>>>>>> + my %entry = ('enabled' => $enabled,
>>>>>>>>>> + 'url' => $url,
>>>>>>>>>> + 'remark' => $remark);
>>>>>>>>>> +
>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>> +
>>>>>>>>>> + return 1;
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +# Save custom lists
>>>>>>>>>> +sub _action_cl_save {
>>>>>>>>>> + return unless((defined $cgiparams{'ALLOW_LIST'}) && (defined $cgiparams{'BLOCK_LIST'}));
>>>>>>>>>> +
>>>>>>>>>> + my $result = 0;
>>>>>>>>>> +
>>>>>>>>>> + my @allowlist = split(/\R/, $cgiparams{'ALLOW_LIST'});
>>>>>>>>>> + my @blocklist = split(/\R/, $cgiparams{'BLOCK_LIST'});
>>>>>>>>>> +
>>>>>>>>>> + # Validate lists
>>>>>>>>>> + $result = &_rpz_validate_customlist(\@allowlist);
>>>>>>>>>> + if($result != 0) {
>>>>>>>>>> + $errormessage = &_rpz_error_tr(202, $result);
>>>>>>>>>> + return;
>>>>>>>>>> + }
>>>>>>>>>> + $result = &_rpz_validate_customlist(\@blocklist);
>>>>>>>>>> + if($result != 0) {
>>>>>>>>>> + $errormessage = &_rpz_error_tr(203, $result);
>>>>>>>>>> + return;
>>>>>>>>>> + }
>>>>>>>>>> +
>>>>>>>>>> + # Add to global data hash and save changes
>>>>>>>>>> + $customlists{'allow'}{'list'} = \@allowlist;
>>>>>>>>>> + $customlists{'block'}{'list'} = \@blocklist;
>>>>>>>>>> + $customlists{'allow'}{'enabled'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? 'on' : 'off';
>>>>>>>>>> + $customlists{'block'}{'enabled'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? 'on' : 'off';
>>>>>>>>>> +
>>>>>>>>>> + &_customlists_save_conf();
>>>>>>>>>> + &_customlist_export('allow', $RPZ_ALLOWLIST);
>>>>>>>>>> + &_customlist_export('block', $RPZ_BLOCKLIST);
>>>>>>>>>> +
>>>>>>>>>> + # Make new lists, request service reload on success
>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-make', 'allowblock', '--no-reload');
>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>> +
>>>>>>>>>> + return 1;
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +# Trigger rpz-config reload
>>>>>>>>>> +sub _action_rpz_reload {
>>>>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>>>>> +
>>>>>>>>>> + # Immediately clear flag to prevent multiple reloads
>>>>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>>>>> + }
>>>>>>>>>> +
>>>>>>>>>> + # Perform reload, recreate reload flag on error to enable retry
>>>>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'reload');
>>>>>>>>>> + if(not &_rpz_check_result($result, 0)) {
>>>>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>>>>> + return;
>>>>>>>>>> + }
>>>>>>>>>> +
>>>>>>>>>> + return 1;
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +# Trigger unbound restart
>>>>>>>>>> +sub _action_unb_restart {
>>>>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>>>>> +
>>>>>>>>>> + # Immediately clear flag to prevent multiple restarts
>>>>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>>>>> + }
>>>>>>>>>> +
>>>>>>>>>> + # Perform restart, unboundctrl always exits zero
>>>>>>>>>> + &General::system('/usr/local/bin/unboundctrl', 'restart');
>>>>>>>>>> +
>>>>>>>>>> + return 1;
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +
>>>>>>>>>> +###--- Internal rpz-config functions ---###
>>>>>>>>>> +
>>>>>>>>>> +# Translate rpz-config exitcodes and messages
>>>>>>>>>> +# 100-199: rpz-config, 200-299: webgui
>>>>>>>>>> +sub _rpz_error_tr {
>>>>>>>>>> + my ($error, $append) = @_;
>>>>>>>>>> + $append //= '';
>>>>>>>>>> +
>>>>>>>>>> + # Translate numeric exit codes
>>>>>>>>>> + if(looks_like_number($error)) {
>>>>>>>>>> + if(defined $Lang::tr{"rpz exitcode $error"}) {
>>>>>>>>>> + $error = $Lang::tr{"rpz exitcode $error"};
>>>>>>>>>> + }
>>>>>>>>>> + }
>>>>>>>>>> +
>>>>>>>>>> + return "RPZ $Lang::tr{'error'}: $error" . &Header::escape($append);
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +# Check result of rpz-config system call, request reload on success
>>>>>>>>>> +sub _rpz_check_result {
>>>>>>>>>> + my ($result, $request_reload) = @_;
>>>>>>>>>> + $request_reload //= 0;
>>>>>>>>>> +
>>>>>>>>>> + # exitcode 0 = success
>>>>>>>>>> + if($result != 0) {
>>>>>>>>>> + $errormessage = &_rpz_error_tr($result);
>>>>>>>>>> + return;
>>>>>>>>>> + }
>>>>>>>>>> +
>>>>>>>>>> + # Set reload flag
>>>>>>>>>> + if($request_reload) {
>>>>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>>>>> + }
>>>>>>>>>> +
>>>>>>>>>> + return 1;
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +# Test whether reload flag is set
>>>>>>>>>> +sub _rpz_needs_reload {
>>>>>>>>>> + return (-f $RPZ_RELOAD_FLAG);
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +# Validate a zonefile entry, returns rpz-config exitcode on failure. Use _rpz_check_result to verify.
>>>>>>>>>> +# unique = check for unique name
>>>>>>>>>> +sub _rpz_validate_zonefile {
>>>>>>>>>> + my ($name, $url, $remark, $unique) = @_;
>>>>>>>>>> + $unique //= 1;
>>>>>>>>>> +
>>>>>>>>>> + unless($name =~ /^[a-zA-Z0-9_]{1,32}$/) {
>>>>>>>>>> + return 101;
>>>>>>>>>> + }
>>>>>>>>>> + unless($url =~ /^[\w+\.:;\/\\&@#%?=\-~|!]{1,128}$/) {
>>>>>>>>>> + return 105;
>>>>>>>>>> + }
>>>>>>>>>> + unless($remark =~ /^[\w \-()\.:;*\/\\?!&=]{0,32}$/) {
>>>>>>>>>> + return 201;
>>>>>>>>>> + }
>>>>>>>>>> +
>>>>>>>>>> + # Check against already existing names
>>>>>>>>>> + if($unique) {
>>>>>>>>>> + foreach my $existing (keys %zonefiles) {
>>>>>>>>>> + if(lc($name) eq lc($existing)) {
>>>>>>>>>> + return 104;
>>>>>>>>>> + }
>>>>>>>>>> + }
>>>>>>>>>> + }
>>>>>>>>>> +
>>>>>>>>>> + return 0;
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +# Validate a custom list, returns number of rejected line on failure. Check for non-zero results.
>>>>>>>>>> +# listref = array reference, cleanup = remove invalid entries instead of returning an error
>>>>>>>>>> +sub _rpz_validate_customlist {
>>>>>>>>>> + my ($listref, $cleanup) = @_;
>>>>>>>>>> + $cleanup //= 0;
>>>>>>>>>> +
>>>>>>>>>> + foreach my $index (reverse 0..$#{$listref}) {
>>>>>>>>>> + my $row = @$listref[$index];
>>>>>>>>>> + next unless($row); # Skip/allow empty lines
>>>>>>>>>> +
>>>>>>>>>> + # Reject/remove everything besides wildcard domains and remarks
>>>>>>>>>> + if((not &General::validwildcarddomainname($row)) && (not $row =~ /^;[\w \-()\.:;*\/\\?!&=]*$/)) {
>>>>>>>>>> + unless($cleanup) {
>>>>>>>>>> + # +1 for user friendly line number and to ensure non-zero exitcode
>>>>>>>>>> + return $index + 1;
>>>>>>>>>> + }
>>>>>>>>>> +
>>>>>>>>>> + # Remove current row
>>>>>>>>>> + splice(@$listref, $index, 1);
>>>>>>>>>> + }
>>>>>>>>>> + }
>>>>>>>>>> +
>>>>>>>>>> + return 0;
>>>>>>>>>> +}
>>>>>>>>>> +
>>>>>>>>>> +
>>>>>>>>>> +###--- Internal misc functions ---###
>>>>>>>>>> +
>>>>>>>>>> +# Send HTTP 303 redirect headers for post/request/get pattern
>>>>>>>>>> +# (Must be sent before calling &Header::showhttpheaders())
>>>>>>>>>> +sub _http_prg_redirect {
>>>>>>>>>> + my $location = "https://$ENV{'SERVER_NAME'}:$ENV{'SERVER_PORT'}$ENV{'SCRIPT_NAME'}";
>>>>>>>>>> + print "Status: 303 See Other\n";
>>>>>>>>>> + print "Location: $location\n";
>>>>>>>>>> +}
>>>>>>>>>> diff --git a/lfs/rpz b/lfs/rpz
>>>>>>>>>> new file mode 100644
>>>>>>>>>> index 000000000..7ddbc38e5
>>>>>>>>>> --- /dev/null
>>>>>>>>>> +++ b/lfs/rpz
>>>>>>>>>> @@ -0,0 +1,96 @@
>>>>>>>>>> +###############################################################################
>>>>>>>>>> +# #
>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>> +# #
>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>> +# #
>>>>>>>>>> +###############################################################################
>>>>>>>>>> +
>>>>>>>>>> +###############################################################################
>>>>>>>>>> +# Definitions
>>>>>>>>>> +###############################################################################
>>>>>>>>>> +
>>>>>>>>>> +include Config
>>>>>>>>>> +
>>>>>>>>>> +SUMMARY = response policy zone - RPZ reputation system for unbound DNS
>>>>>>>>>> +
>>>>>>>>>> +VER = 1.0.0
>>>>>>>>>> +
>>>>>>>>>> +THISAPP = rpz-$(VER)
>>>>>>>>>> +DIR_APP = $(DIR_SRC)/$(THISAPP)
>>>>>>>>>> +TARGET = $(DIR_INFO)/$(THISAPP)
>>>>>>>>>> +
>>>>>>>>>> +PROG = rpz
>>>>>>>>>> +PAK_VER = 18
>>>>>>>>>> +
>>>>>>>>>> +DEPS =
>>>>>>>>>> +
>>>>>>>>>> +SERVICES =
>>>>>>>>>> +
>>>>>>>>>> +###############################################################################
>>>>>>>>>> +# Top-level Rules
>>>>>>>>>> +###############################################################################
>>>>>>>>>> +
>>>>>>>>>> +install : $(TARGET)
>>>>>>>>>> +
>>>>>>>>>> +check :
>>>>>>>>>> +
>>>>>>>>>> +download :
>>>>>>>>>> +
>>>>>>>>>> +b2 :
>>>>>>>>>> +
>>>>>>>>>> +dist:
>>>>>>>>>> + @$(PAK)
>>>>>>>>>> +
>>>>>>>>>> +###############################################################################
>>>>>>>>>> +# Installation Details
>>>>>>>>>> +###############################################################################
>>>>>>>>>> +
>>>>>>>>>> +$(TARGET) :
>>>>>>>>>> + @$(PREBUILD)
>>>>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>>>>> +
>>>>>>>>>> + # RPZ scripts
>>>>>>>>>> + install --verbose --mode=755 \
>>>>>>>>>> + $(DIR_CONF)/rpz/{rpz-config,rpz-metrics,rpz-sleep,rpz-make,rpz-functions} \
>>>>>>>>>> + --target-directory=/usr/sbin
>>>>>>>>>> +
>>>>>>>>>> + # RPZ config files
>>>>>>>>>> + mkdir -pv /etc/unbound/local.d
>>>>>>>>>> + install --verbose --mode=644 --owner=nobody --group=nobody \
>>>>>>>>>> + $(DIR_CONF)/rpz/00-rpz.conf \
>>>>>>>>>> + --target-directory=/etc/unbound/local.d
>>>>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/local.d
>>>>>>>>>> +
>>>>>>>>>> + # RPZ custom list files for allow and block
>>>>>>>>>> + mkdir -pv /var/ipfire/dns/rpz
>>>>>>>>>> + touch /var/ipfire/dns/rpz/{allowlist,blocklist}
>>>>>>>>>> + chown --verbose --recursive nobody:nobody /var/ipfire/dns/rpz
>>>>>>>>>> +
>>>>>>>>>> + # RPZ zone files
>>>>>>>>>> + # create empty RPZ config file to avoid a unbound config error
>>>>>>>>>> + mkdir -pv /etc/unbound/zonefiles
>>>>>>>>>> + touch /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/zonefiles
>>>>>>>>>> +
>>>>>>>>>> + # Install addon-specific language-files
>>>>>>>>>> + install --verbose --mode=004 $(DIR_CONF)/rpz/rpz.*.pl \
>>>>>>>>>> + --target-directory=/var/ipfire/addon-lang
>>>>>>>>>> +
>>>>>>>>>> + # Install backup definition
>>>>>>>>>> + cp -vf $(DIR_CONF)/backup/includes/rpz /var/ipfire/backup/addons/includes/rpz
>>>>>>>>>> +
>>>>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>>>>> + @$(POSTBUILD)
>>>>>>>>>> diff --git a/make.sh b/make.sh
>>>>>>>>>> index 827ea9e77..a77535b13 100755
>>>>>>>>>> --- a/make.sh
>>>>>>>>>> +++ b/make.sh
>>>>>>>>>> @@ -390,7 +390,7 @@ prepareenv() {
>>>>>>>>>> if [ "${free_space}" -lt "${required_space}" ]; then
>>>>>>>>>> # Add any consumed space
>>>>>>>>>> while read -r consumed_space path; do
>>>>>>>>>> - (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>>>>> + (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>>>>> done <<< "$(du --summarize --bytes "${BUILD_DIR}" "${IMAGES_DIR}" "${LOG_DIR}" 2>/dev/null)"
>>>>>>>>>> fi
>>>>>>>>>>
>>>>>>>>>> @@ -2087,6 +2087,7 @@ build_system() {
>>>>>>>>>> lfsmake2 btrfs-progs
>>>>>>>>>> lfsmake2 inotify-tools
>>>>>>>>>> lfsmake2 grub-btrfs
>>>>>>>>>> + lfsmake2 rpz
>>>>>>>>>>
>>>>>>>>>> lfsmake2 linux
>>>>>>>>>> lfsmake2 rtl8812au
>>>>>>>>>> diff --git a/src/paks/rpz/install.sh b/src/paks/rpz/install.sh
>>>>>>>>>> new file mode 100644
>>>>>>>>>> index 000000000..ef99bf742
>>>>>>>>>> --- /dev/null
>>>>>>>>>> +++ b/src/paks/rpz/install.sh
>>>>>>>>>> @@ -0,0 +1,36 @@
>>>>>>>>>> +#!/bin/bash
>>>>>>>>>> +###############################################################################
>>>>>>>>>> +# #
>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>> +# #
>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>> +# #
>>>>>>>>>> +###############################################################################
>>>>>>>>>> +#
>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>> +extract_files
>>>>>>>>>> +restore_backup ${NAME}
>>>>>>>>>> +
>>>>>>>>>> +# fix user created files
>>>>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>>>>> + /etc/unbound/zonefiles \
>>>>>>>>>> + /etc/unbound/local.d
>>>>>>>>>> +
>>>>>>>>>> +# Update Language cache
>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>> +
>>>>>>>>>> +# restart unbound to load config file
>>>>>>>>>> +/etc/init.d/unbound restart
>>>>>>>>>> diff --git a/src/paks/rpz/uninstall.sh b/src/paks/rpz/uninstall.sh
>>>>>>>>>> new file mode 100644
>>>>>>>>>> index 000000000..e11427df3
>>>>>>>>>> --- /dev/null
>>>>>>>>>> +++ b/src/paks/rpz/uninstall.sh
>>>>>>>>>> @@ -0,0 +1,38 @@
>>>>>>>>>> +#!/bin/bash
>>>>>>>>>> +###############################################################################
>>>>>>>>>> +# #
>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>> +# #
>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>> +# #
>>>>>>>>>> +###############################################################################
>>>>>>>>>> +#
>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>> +
>>>>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>>>>> +/etc/init.d/unbound stop
>>>>>>>>>> +
>>>>>>>>>> +make_backup ${NAME}
>>>>>>>>>> +remove_files
>>>>>>>>>> +
>>>>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>>>>> +# "[1723428668] unbound-control[17117:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>>>>> +
>>>>>>>>>> +# Update Language cache
>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>> +
>>>>>>>>>> +# start unbound to load unbound config file
>>>>>>>>>> +/etc/init.d/unbound start
>>>>>>>>>> diff --git a/src/paks/rpz/update.sh b/src/paks/rpz/update.sh
>>>>>>>>>> new file mode 100644
>>>>>>>>>> index 000000000..9bc340bc6
>>>>>>>>>> --- /dev/null
>>>>>>>>>> +++ b/src/paks/rpz/update.sh
>>>>>>>>>> @@ -0,0 +1,52 @@
>>>>>>>>>> +#!/bin/bash
>>>>>>>>>> +###############################################################################
>>>>>>>>>> +# #
>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>> +# #
>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>> +# #
>>>>>>>>>> +###############################################################################
>>>>>>>>>> +#
>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>> +
>>>>>>>>>> +# from update.sh
>>>>>>>>>> +extract_backup_includes
>>>>>>>>>> +
>>>>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>>>>> +/etc/init.d/unbound stop
>>>>>>>>>> +
>>>>>>>>>> +# from uninstall.sh
>>>>>>>>>> +make_backup ${NAME}
>>>>>>>>>> +remove_files
>>>>>>>>>> +
>>>>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>>>>> +# "unbound-control[nn:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>>>>> +
>>>>>>>>>> +# from install.sh
>>>>>>>>>> +extract_files
>>>>>>>>>> +restore_backup ${NAME}
>>>>>>>>>> +
>>>>>>>>>> +# fix user created files
>>>>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>>>>> + /etc/unbound/zonefiles \
>>>>>>>>>> + /etc/unbound/local.d
>>>>>>>>>> +
>>>>>>>>>> +# Update Language cache
>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>> +
>>>>>>>>>> +# restart unbound to load config files
>>>>>>>>>> +/etc/init.d/unbound start
>>>>>>>>>> --
>>>>>>>>>> 2.39.5
>>>>>>>>>>
>>>>>>>> Jon
>>>>>>>>
>>>>>>>>
>>>>>>>> --
>>>>>>>> Jon Murphy
>>>>>>>> jon.murphy@ipfire.org
>>>>>> Jon
>>>>>>
>>>>>>
>>>>>> --
>>>>>> Jon Murphy
>>>>>> jon.murphy@ipfire.org
>>>>
>>>
>>
>
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re[2]: [PATCH] RPZ: update code to include WEBGUI and additional languages
2025-03-20 16:26 ` Michael Tremer
@ 2025-03-24 0:00 ` Jon Murphy
2025-03-24 10:17 ` Michael Tremer
0 siblings, 1 reply; 30+ messages in thread
From: Jon Murphy @ 2025-03-24 0:00 UTC (permalink / raw)
To: Michael Tremer; +Cc: IPFire: Development-List
[-- Attachment #1: Type: text/plain, Size: 143037 bytes --]
Michael,
FYI - I was wrong Unbound RPZ is _not_ watching the serial number, it is
watching the "refresh", the number after the serial number.
I understand that you don’t speak C, but you got the information from somewhere. Documentation maybe? Since that is out of date very often I like to consult the code.
From testing. Downloading rpz files using rpz unbound, and watching
what happens. If the rpz file is setup for "once per day" refresh, then
it only downloads one time.
However that won’t solve our problem . . . and having no cache.
In `/etc/unbound/tuning.conf` there is `rrset-cache-size: 128m`. Are you referring to a different cache.
Maybe we need to implement both?
Yes. There are very few AXFR list (I think only four were found). And
many more HTTPS rpz files.
Jon
------ Original Message ------
From "Michael Tremer" <michael.tremer@ipfire.org>
To "Jon Murphy" <jon.murphy@ipfire.org>
Cc "IPFire: Development-List" <development@lists.ipfire.org>
Date 3/20/2025 11:26:43 AM
Subject Re: [PATCH] RPZ: update code to include WEBGUI and additional
languages
>Hello Jon,
>
>Please don’t forget to Cc the list...
>
>> On 19 Mar 2025, at 18:27, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>
>> Michael,
>>
>>> Where in the code is this implemented? I cannot find anything like this:
>>
>> Keep in mind I am not a "C" person. Maybe in this section?:
>>
>> https://git.ipfire.org/?p=thirdparty/unbound.git;a=blob;f=services/authzone.c;hb=30b9cb5f813003d0a2b1c2e678652396615b1b7d#l5875
>
>This where the AXFR response is being handled when doing a DNS zone transfer. This code is not being called when performing a HTTP download.
>
>I understand that you don’t speak C, but you got the information from somewhere. Documentation maybe? Since that is out of date very often I like to consult the code.
>
>> —
>>
>> When I was just learning about RPZ I created a separate RPZ file for testing. When I changed the SOA line with a new serial number, the RPZ file download would happen in about 5 minutes.
>>
>> https://people.ipfire.org/~jon/sblack-adhoc.rpz
>
>It might well be that the file is not being reloaded if the download matches the content that unbound already has. That would of course save some resources.
>
>However that won’t solve our problem with redundant downloads and having no cache.
>
>> That is how I found out the SOA line is watched for a serial number change.
>>
>> I’ll reconfirm my findings.
>>
>>
>>>>> The second reason is that we have a lot of firewalls out there. Not all of them will enable this feature and all of the lists, but even if it is a good chunk, we will generate terabytes of traffic which put load on the infrastructure and will cost money. It simply is not what we want to do, regardless of self-hosting those lists and pulling them from somewhere else.
>>
>> So I understand, are you thinking of hosting RPZ AXFR (DNS zone transfer) on IPFire infrastructure?
>
>No, I don’t think that we can generally do this. The biggest problem is licensing as we cannot take anyones content and host it ourselves. We would re-distribute those lists and that will only work with permission of the publishers. I assume that would be too much work to actually get some useful content out there. We might limit ourselves to only those lists that are under a very permissive license. Nobody wants that.
>
>From a technical point of view, DNS over TCP might not be very nice in terms of forging the transfer and so we would need TLS as well… It should work, but even if we would be able to encourage other people to publish their lists I doubt they would implement DNS over TLS for authoritative DNS. That standard is in very early stages as well.
>
>As far as I can see, those vendors who offer a list as a commercial product are using DNS to distribute it (e.g. Spamhaus). Those people who have made this all a hobby are throwing the lists onto GitHub and let them handle the traffic.
>
>Maybe we need to implement both?
>
>-Michael
>
>> Jon
>>
>>
>>
>> On 3/19/25 5:35 AM, Michael Tremer wrote:
>>> Hello Jon,
>>>
>>> Where in the code is this implemented? I cannot find anything like this:
>>>
>>> Unbound loads the entire file into memory and then starts parsing it. The only special treatment there is is to check whether the first line is a valid zone entry. It does not even have to be a SOA record.
>>>
>>> https://git.ipfire.org/?p=thirdparty/unbound.git;a=blob;f=services/authzone.c;hb=30b9cb5f813003d0a2b1c2e678652396615b1b7d#l1188
>>>
>>> I am also concerned that Unbound will not be able to support an upstream proxy for any downloads. The caching situation is also unclear for me, so I believe that we will be looking at writing a custom downloader that implements all these things.
>>>
>>> -Michael
>>>
>>>> On 19 Mar 2025, at 02:58, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>
>>>> Michael,
>>>>
>>>>> The emphasis is on the repeated downloads of the same list. That is
>>>> > what cannot happen.
>>>>
>>>> The Unbound RPZ code, as installed within IPFire, watches for a change
>>>> in the SOA line of each RPZ file. This is an example of the first few
>>>> lines for every RPZ file.
>>>>
>>>> $TTL 300
>>>> @ SOA localhost. root.localhost. 1742298960 43200 3600 86400 300
>>>> NS localhost.
>>>> ;
>>>> ; Title: HaGeZi's Pop-Up Ads DNS Blocklist
>>>> ; Description: Blocks annoying and malicious pop-up ads.
>>>>
>>>> If the SOA serial number changes (e.g. the 1742298960), then Unbound RPZ
>>>> code does its thing and downloads. Otherwise there is no download.
>>>>
>>>>> So there has to be a way to ensure that we won’t download a list again
>>>> > unless it has actually changed.
>>>>
>>>> This should do what you want but I may be missing your point.
>>>>
>>>>> DNS has a builtin functionality called AXFR. It simply does the job
>>>> > for you. I was just wondering whether that was not being used.
>>>>
>>>> I need to read about AXFR/IXFR and learn a little more.
>>>>
>>>> Jon
>>>>
>>>> On 3/17/25 5:35 AM, Michael Tremer wrote:
>>>>> Good Morning Jon,
>>>>>
>>>>>> On 16 Mar 2025, at 17:00, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>
>>>>>> Michael,
>>>>>>
>>>>>> I was reading through you response again an I want to understand this post:
>>>>>>
>>>>>>> I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>>>>> So if RPZ doesn't use HTTPS, what is it using? I am missing a key point here.
>>>>> The emphasis is on the repeated downloads of the same list. That is what cannot happen.
>>>>>
>>>>> Although it might not affect a lot of people in our general user-base, there are some that have a metered connection and will pay for data by volume. Some of the lists I looked at are just under 20 MiB. Therefore we need to keep any traffic down to a minimum. The second reason is that we have a lot of firewalls out there. Not all of them will enable this feature and all of the lists, but even if it is a good chunk, we will generate terabytes of traffic which put load on the infrastructure and will cost money. It simply is not what we want to do, regardless of self-hosting those lists and pulling them from somewhere else.
>>>>>
>>>>> So there has to be a way to ensure that we won’t download a list again unless it has actually changed.
>>>>>
>>>>> DNS has a builtin functionality called AXFR. It simply does the job for you. I was just wondering whether that was not being used.
>>>>>
>>>>> HTTPS is an option because that is simply what we use elsewhere, but extra functionality will have to be built for it.
>>>>>
>>>>> -Michael
>>>>>
>>>>>> Jon
>>>>>>
>>>>>>
>>>>>> On 2/13/25 3:34 PM, jon wrote:
>>>>>>> Michael,
>>>>>>>
>>>>>>> I’ve read through your comments a few times and I ended up with many more questions.
>>>>>>>
>>>>>>>
>>>>>>>> What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>>>>> To me the efforts to get new code accepted seem to have changed and it seemed easier in the past. In the past I made the Core Team aware via the Dev Mailing List and wrote a simple two or three paragraphs of "What is it? / What is the value? / Here is the code"
>>>>>>>
>>>>>>>
>>>>>>> So in an effort to move forward: How exactly is something presented to the Core Team?
>>>>>>>
>>>>>>> Is there an example of a recent effort that was presented that I can see as a sample? (This type of info can also be added to the Wiki)
>>>>>>>
>>>>>>> I understand you want it this way, but I don’t know what exactly is needed. Please be specific.
>>>>>>>
>>>>>>>
>>>>>>> Jon
>>>>>>>
>>>>>>> PS - I am not ignoring your other comments, I am just trying to move forward and keep things simple.
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>>> On Feb 8, 2025, at 1:27 PM, Michael Tremer <michael.tremer@ipfire.org> wrote:
>>>>>>>>
>>>>>>>> Hello Jon,
>>>>>>>>
>>>>>>>> Thanks for your reply. And good that you are copying everyone into this conversation.
>>>>>>>>
>>>>>>>>> On 8 Feb 2025, at 18:41, jon <jon.murphy@ipfire.org> wrote:
>>>>>>>>>
>>>>>>>>> Michael,
>>>>>>>>>
>>>>>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort
>>>>>>>>> Yes, this has been a separate effort (a very public separate effort). Yes, as you pointed this out early on with the "proof-of-concept" and then my request for people to help test RPZ. Nothing was hidden.
>>>>>>>>>
>>>>>>>>> This was done because you (and maybe others) did not have the time and I wanted to help and because I needed assistance with RPZ. I tried my best to do this without bothering you.
>>>>>>>> I don’t that it is accurate that nobody wanted to help on this. The list was always open - although not every email has been replied to swiftly it is also your responsibility to raise a question again if it was missed. People here have open ears.
>>>>>>>>
>>>>>>>> It was also stated on this very list on in our documentation that working on something without involving the core team is a risky undertaking. Of course IPFire is free software and so everyone is free to fork if they wish to do so.
>>>>>>>>
>>>>>>>>>> and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls.
>>>>>>>>> You were aware many steps along the way. See your email on July 28, 2024, August 15, 2024, September 30, 2024, December 23, 2024, and January 16. My attempts to get the team involved were met with "things are busy" and sometimes silence. (Yes, I get it, people are busy.)
>>>>>>>>>
>>>>>>>>> You and Adolf, Leo, Erik and Bernhard have been aware since the beginning. You mention you were aware of the "proof-of-concept". If you include those beginning posts, since Sep 2023.
>>>>>>>> Yes, I am aware of a proof-of-concept that I have been running myself for a long time. I am also aware of the efforts that you have been taking.
>>>>>>>>
>>>>>>>> Yet I don’t think there has ever been any joint effort, or am I seeing that wrong?
>>>>>>>>
>>>>>>>>>> This has not been discussed . . . on our calls.
>>>>>>>>> On the July 28th you stated:
>>>>>>>>> "We have talked about RPZ many times on the monthly call since the URL filter feature is falling more and more out of fashion. I think there is also many posts about this on the forum."
>>>>>>>>>
>>>>>>>>> Please don’t insult me again by stating "you know what I mean".
>>>>>>>>>
>>>>>>>>> And it has been discussed but not documented in the Monthly Meeting notes.
>>>>>>>> I am not at all insulting you. I don’t want to take this down to a personal level at all. This is a public mailing list and people who read this don’t need to listen to an argument we are having. They are here for the tech inside IPFire.
>>>>>>>>
>>>>>>>> When I wrote that it has not been discussed that does not mean that we have not been touching on the topic. We have been talking about lots of things on the calls, the weather, politics, how our pets are. None of that makes it to the logs. What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>>>>>>
>>>>>>>>>> Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation.
>>>>>>>>> Regular conversation on the Dev Mailing list is many times met with silence. I get it, people are busy.
>>>>>>>>>
>>>>>>>>> And regular two-way conversation doesn’t happen on the list. At least not with me. I’d be happy to point out the posts that were met with silence.
>>>>>>>>> Again, I get it, people are busy.
>>>>>>>> And you think my emails are not being met with silence? This has nothing to do with this specific topic. This has something to do with how occupied people are and how engaged they are on certain topics. Not everyone is involved in all the things and simply will ignore emails simply based on their subject line.
>>>>>>>>
>>>>>>>>> But the "dip here to the list" were my attempts to get a conversation started. As I said, many time met with silence.
>>>>>>>>>
>>>>>>>>> The only place I was not met with silence was on the Community. You have a great group of people in the Community. It is a shame you don’t want to have others help. It would reduce your workload.
>>>>>>>> You should stop making statements that are not true. Who doesn’t want anyone to help?
>>>>>>>>
>>>>>>>> Not having this conversation on a Saturday evening would reduce my workload. At least it would free up time for something else. Helping with the things that are already on the go would reduce the workload of the entire team. Starting one thing at a time and finishing it is a lot better to manage than starting a hundred things and not even finish one. I can tell you that I already have a hundred things on the go.
>>>>>>>>
>>>>>>>>>> Therefore, what am I supposed to do with this email?
>>>>>>>>> To me it is beyond obvious…
>>>>>>>>>
>>>>>>>>> If it isn’t what you want, then guide me with how to do this the correct way. And be specific. I am trying to help. I am trying to make things better. I am trying to do things the right way.
>>>>>>>> To me it isn’t. This is yet another project that has been dumped to the list like so many before and later on everyone has left to have the team deal with the rest.
>>>>>>>>
>>>>>>>> It is a huge patch set. You explained what the vision is, but that is about it. There is no chance this will continue if this disagreement isn’t solved first. I didn’t even look at the code.
>>>>>>>>
>>>>>>>>>> I don’t want to merge code that I don’t agree with.
>>>>>>>>> I asked multiple times if you "agreed with the concept" and again, met with silence. Yes I get it, people are busy.
>>>>>>>> Having support for RPZ? Yes, it was definitely on the roadmap. That I agree with.
>>>>>>>>
>>>>>>>>>> So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>>>>> You mentioned this a in the past, but for some reason you do not disclose what I dismissed. Why do you continue to make this harder, wouldn’t it not be easier to tell me what I have dismissed?
>>>>>>>>>
>>>>>>>>> I have sent multiple emails trying to answer your concerns and comments. On July 28, Aug 14, Aug 22, Aug 23, Sep 30, etc.
>>>>>>>>>
>>>>>>>>> I’ve gone through all of the questions you asked and I cannot find a "dismissed" item.
>>>>>>>> Maybe I need to be *more clear*. I feel humoured by this.
>>>>>>>>
>>>>>>>> It is late on a Saturday and I want my dinner soon, but certainly I have stated that this should never be an add-on considering it is supposed to replace URL Filter. We should never allow people to add their own sources. I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>>>>>>>
>>>>>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>>>>> The maintainers of Unbound and/or RPZ?
>>>>>>>>>
>>>>>>>>> The maintainers of Hagezi list, the threatfox list, the urlhaus list, etc.?
>>>>>>>>>
>>>>>>>>> What else? The maintainers or the RPZ scripts? That is me. Let’s talk!
>>>>>>>> You. I don’t care much about the providers of the lists.
>>>>>>>>
>>>>>>>>> See, this is where it gets confusing. There are hundreds of open source packages as part of IPFire. Pick the last five years of items added to the IPFire build. You're telling me you have "constructive conversation with the maintainers" of all of the added packages?
>>>>>>>> They publish their software and they don’t care whether I am pulling it or not. They publish it with the commitment to maintain it - sometimes for better and sometimes for worse.
>>>>>>>>
>>>>>>>> You care about me pulling your code and I don’t know whether you would commit to maintain this.
>>>>>>>>
>>>>>>>> These two are very different cases.
>>>>>>>>
>>>>>>>>> Pick the IP Blocklists list (i.e., 3CORESEC, ABUSECH, DSHIELD, SPAMHAUS, etc.) or the Suricata lists (i.e.,Emergingthreats.net <http://emergingthreats.net/>,Abuse.ch <http://abuse.ch/>, etc.). So you’ve have "constructive conversation with the maintainers"?
>>>>>>>> Yes, occasionally I have phone calls with a few of these providers.
>>>>>>>>
>>>>>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>>>>> Ha! Yes a surprise. In the beginning you seemed interested as IPFire needed a replacement for URL Filter. You asked good questions about the lists picked, asked for the value to the users, etc. And I answered the best I could.
>>>>>>>>>
>>>>>>>>> You even asked: “Why is this realised as an add-on and not part of the core system?” from your Jul 28, 2024 email.
>>>>>>>> Ah, so, why is the patch creating an add-on? Not that I am saying that what I say is law, but it has not been challenged either. If my input is being ignored, why should I put this to the top of my list of priorities? I am not disappointed about this, just trying to be very good with my time.
>>>>>>>>
>>>>>>>>> And on January 16, 2025 I wrote a message looking for help. And you were kind to respond quickly. So in three weeks time, since the kind response, something has changed. You went from supportive to "this".
>>>>>>>>>
>>>>>>>>> So yes, I am surprised.
>>>>>>>> Well, maybe I should not have replied to that email. It was clear that you were on some path that was not right, but you were not interested before in finding the right path from the beginning.
>>>>>>>>
>>>>>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>>>>> Be more specific, what has to change? What exactly did I dismiss?
>>>>>>>> Dismissal is just my assumption. I don’t know what you actually did with my feedback. I can only see the end product that does not seem contain much of it. Repeatedly I have been pointing out that we should think before we build. I am sure a lot of hours have now gone into some code that simply does not satisfy me. And I am not not talking about the code itself, what it does is what I don’t think is right for us.
>>>>>>>>
>>>>>>>> The process is very clear for me that we should first of all think whether we want a certain feature now. Then there should be a clear roadmap for everyone to follow; tasks can be split-up as we go and hopefully then have something that is maintainable, interesting for our users and even would do us proud. This is how this should work.
>>>>>>>>
>>>>>>>> So, what has to change? I don’t think with shouting at each other, throwing patches around and making me generally unhappy is a good start.
>>>>>>>>
>>>>>>>> -Michael
>>>>>>>>
>>>>>>>>> Jon
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>> On Feb 6, 2025, at 2:13 PM, Michael Tremer <michael.tremer@ipfire.org> wrote:
>>>>>>>>>>
>>>>>>>>>> Hello Jon,
>>>>>>>>>>
>>>>>>>>>> Well, here we are again with another patch regarding this feature.
>>>>>>>>>>
>>>>>>>>>> I cannot quite see from your email what the question is, but if this is a request to have this merged into IPFire, I am once again sorry to disappoint you.
>>>>>>>>>>
>>>>>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls. Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation. Therefore, what am I supposed to do with this email?
>>>>>>>>>>
>>>>>>>>>> I don’t want to merge code that I don’t agree with. So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>>>>>>
>>>>>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>>>>>>
>>>>>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>>>>>>
>>>>>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>>>>>>
>>>>>>>>>> All the best,
>>>>>>>>>> -Michael
>>>>>>>>>>
>>>>>>>>>>> On 6 Feb 2025, at 16:35, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>>>>>
>>>>>>>>>>> What is it?
>>>>>>>>>>> Response Policy Zone (RPZ) is a mechanism to define local policies in a
>>>>>>>>>>> standardized way and load those policies from external sources.
>>>>>>>>>>> Bottom line: RPZ allows admins to easily block access to websites via DNS lookup.
>>>>>>>>>>>
>>>>>>>>>>> RPZ can block websites via categories. Examples include: fake websites, annoying
>>>>>>>>>>> pop-up ads, newly registered domains, DoH bypass sites, bad "host" services,
>>>>>>>>>>> maliscious top level domains (e.g., *.zip, *.mov), piracy, gambling, pornography,
>>>>>>>>>>> and more. RPZ lists come from various RPZ providers and their available
>>>>>>>>>>> catagories.
>>>>>>>>>>>
>>>>>>>>>>> This RPZ add-on enables the RPZ functionality by adding a couple lines in a
>>>>>>>>>>> configuration file. This add-on simply adds configuration files and adds
>>>>>>>>>>> scripts (config, metrics and sleep) to make RPZ easier for the admin to use.
>>>>>>>>>>>
>>>>>>>>>>> The RPZ scripts include additional languages: German, Spanish, French, Turkish,
>>>>>>>>>>> and Italian.
>>>>>>>>>>>
>>>>>>>>>>> RPZ itself was release in 2010 and has been part of the IPFire build since ~2015.
>>>>>>>>>>>
>>>>>>>>>>> Why is it needed? What is its value?
>>>>>>>>>>>
>>>>>>>>>>> - The RPZ concept places this filtering into IPFire, our internet access
>>>>>>>>>>> gateway, which is (should be) solely used as DNS source of the internal network.
>>>>>>>>>>>
>>>>>>>>>>> - As most sites use HTTPS it makes it difficult to filter traffic with URL
>>>>>>>>>>> Filter without also properly configuring conventional (non-transparent)
>>>>>>>>>>> mode on the proxy. RPZ is a nice replacement for the URL Filter.
>>>>>>>>>>>
>>>>>>>>>>> - No need to install and maintain an additional device like PiHole or AdBlock
>>>>>>>>>>> browser extensions on multiple user devices.
>>>>>>>>>>>
>>>>>>>>>>> - This is an additional layer of protection for users. Less worry someone will
>>>>>>>>>>> click on something that gets them into trouble. And, saying this with emphasis,
>>>>>>>>>>> the ability to do it in one place!
>>>>>>>>>>>
>>>>>>>>>>> - Blocked sites save on unneeded traffic and can lessen the threat of malware
>>>>>>>>>>> in advertisements
>>>>>>>>>>>
>>>>>>>>>>> - Logging allows the admin to see the site blocked and take actions
>>>>>>>>>>>
>>>>>>>>>>> - RPZ will be used at the home, home-office (work from home), schools,
>>>>>>>>>>> ministerial, and at the office. Device counts are small (2-6) to medium (~80)
>>>>>>>>>>> to mediam-large (200+).
>>>>>>>>>>>
>>>>>>>>>>> - RPZ can block ads, popups, phishing, scammers, spyware, malware, annoying
>>>>>>>>>>> popups, NSFW links, DOH servers, and the usual internet trash.
>>>>>>>>>>>
>>>>>>>>>>> ------------------------------
>>>>>>>>>>>
>>>>>>>>>>> Change Log for RPZ add-on
>>>>>>>>>>>
>>>>>>>>>>> rpz-1.0.0-18 on 2025-02-05
>>>>>>>>>>> - Build for approval & release as IPFire add-on
>>>>>>>>>>>
>>>>>>>>>>> ---
>>>>>>>>>>>
>>>>>>>>>>> rpz-beta-0.1.18-18.ipfire on 2025-02-01
>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>> - new feature: added a mod key to force a unbound restart
>>>>>>>>>>>
>>>>>>>>>>> rpz-config and rpz-make:
>>>>>>>>>>> - new feature: added action for unbound restart `rpz-config unbound-restart`
>>>>>>>>>>>
>>>>>>>>>>> rpz-metrics:
>>>>>>>>>>> - simple reformatting
>>>>>>>>>>> - rename far right column from "last update" to "last download"
>>>>>>>>>>>
>>>>>>>>>>> ---
>>>>>>>>>>>
>>>>>>>>>>> rpz-beta-0.1.17-17.ipfire on 2024-12-09
>>>>>>>>>>> rpz-make
>>>>>>>>>>> - bug fix: corrected validation regex for wildcards like: `*.domain.com`
>>>>>>>>>>>
>>>>>>>>>>> ---
>>>>>>>>>>>
>>>>>>>>>>> rpz-beta-0.1.16-16.ipfire on 2024-11-18
>>>>>>>>>>> rpz-make
>>>>>>>>>>> - new feature: updated validation regex
>>>>>>>>>>> - bug fix: moved validation to beginning of process. Now we validate before
>>>>>>>>>>> creating config files.
>>>>>>>>>>>
>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>> - new feature: use CSS color variables of the main ipfire theme
>>>>>>>>>>> - bug fix: empty zonefile remarks were stored as “undef” and caused a warning
>>>>>>>>>>> - bug fix: HTML textarea removes the first empty line in a custom list
>>>>>>>>>>> - thank you Leo!
>>>>>>>>>>>
>>>>>>>>>>> ---
>>>>>>>>>>>
>>>>>>>>>>> rpz-beta-0.1.15-15.ipfire on 2024-11-04
>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>> - new feature: added new language file for Turkish (thank you Peppe)
>>>>>>>>>>>
>>>>>>>>>>> rpz-make
>>>>>>>>>>> - bug fix: corrected empty allow/block list issue. An empty allow/block list
>>>>>>>>>>> will now remove contents of allow/block.rpz files and remove unneeded
>>>>>>>>>>> allow/block.conf file. (thank you iptom)
>>>>>>>>>>>
>>>>>>>>>>> ---
>>>>>>>>>>>
>>>>>>>>>>> rpz-beta-0.1.14-14.ipfire on 2024-10-29
>>>>>>>>>>> rpz-config:
>>>>>>>>>>> - bug fix: correct missing rpz extension. `rpz-config list` displayed URL
>>>>>>>>>>> incorrectly (thank you Bernhard)
>>>>>>>>>>>
>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>> - bug fix: remove extra `"` in language files (thank you Bernhard)
>>>>>>>>>>> - new feature: slightly dim "apply" button when not enabled
>>>>>>>>>>>
>>>>>>>>>>> ---
>>>>>>>>>>>
>>>>>>>>>>> rpz-beta-0.1.13-13.ipfire on 2024-10-27
>>>>>>>>>>> - skipped
>>>>>>>>>>>
>>>>>>>>>>> ---
>>>>>>>>>>>
>>>>>>>>>>> rpz-beta-0.1.12-12.ipfire on 2024-10-21
>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>> - new feature: added new language file for French (thank you gw-ipfire)
>>>>>>>>>>>
>>>>>>>>>>> ---
>>>>>>>>>>>
>>>>>>>>>>> rpz-beta-0.1.11-11.ipfire on 2024-10-18
>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>> - new feature: added new language file for Italian (thank you umberto)
>>>>>>>>>>> - new feature: added new language file for Spanish (thank you Roberto)
>>>>>>>>>>>
>>>>>>>>>>> ---
>>>>>>>>>>>
>>>>>>>>>>> rpz-beta-0.1.10-10.ipfire on 2024-10-15
>>>>>>>>>>> rpz-make:
>>>>>>>>>>> - bug fix: corrected validation error for a custom list entry (thank you siosios)
>>>>>>>>>>> - e.g., `*.cloudflare-dns.com`
>>>>>>>>>>>
>>>>>>>>>>> install.sh:
>>>>>>>>>>> - bug fix: add chown to correct user created files
>>>>>>>>>>>
>>>>>>>>>>> update.sh:
>>>>>>>>>>> - bug fix: add chown to correct user created files (thank you siosios)
>>>>>>>>>>>
>>>>>>>>>>> ---
>>>>>>>>>>>
>>>>>>>>>>> rpz-beta-0.1.9-9.ipfire on 2024-10-08
>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>> - new feature: added new language file for German (thank you Leo)
>>>>>>>>>>> - bug fix: add missing "rpz exitcode 110"
>>>>>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>>>>>
>>>>>>>>>>> ---
>>>>>>>>>>>
>>>>>>>>>>> rpz-beta-0.1.8-8.ipfire on 2024-10-04
>>>>>>>>>>> - skipped
>>>>>>>>>>>
>>>>>>>>>>> ---
>>>>>>>>>>>
>>>>>>>>>>> rpz-beta-0.1.7-7.ipfire on 2024-10-03
>>>>>>>>>>> All:
>>>>>>>>>>> - new feature: includes beta version numbers for pakfire package,
>>>>>>>>>>> instead of only `rpz-1.0.0-1.ipfire`, for each release.
>>>>>>>>>>>
>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>> - new feature: added new WebGUI at `rpz.cgi`
>>>>>>>>>>> - a BIG thank you to Leo Hofmann for all of his work creating the webgui!!
>>>>>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>>>>>
>>>>>>>>>>> rpz-make:
>>>>>>>>>>> - new feature: validate entries in allowlist and blocklist
>>>>>>>>>>> - new feature: add "no-reload" option for WebGUI
>>>>>>>>>>>
>>>>>>>>>>> rpz-metrics:
>>>>>>>>>>> - new feature: info can be sorted by name, by hit count, by line count, by
>>>>>>>>>>> "enabled" list or all lists
>>>>>>>>>>>
>>>>>>>>>>> backups:
>>>>>>>>>>> - bug fix: include all files in `/var/ipfire/dns/rpz` directory in backup
>>>>>>>>>>>
>>>>>>>>>>> update.sh:
>>>>>>>>>>> - bug fix: corrected ownership for `/var/ipfire/dns/rpz` directory during an
>>>>>>>>>>> update
>>>>>>>>>>>
>>>>>>>>>>> Build:
>>>>>>>>>>> - bug fix: `block.rpz.conf` and `block.rpz` from build. Files to be created
>>>>>>>>>>> by `rpz-make`
>>>>>>>>>>>
>>>>>>>>>>> WebGUI and German language file
>>>>>>>>>>> Contribution-by: Leo-Andres Hofmann <hofmann@leo-andres.de>
>>>>>>>>>>>
>>>>>>>>>>> Spanish language file
>>>>>>>>>>> Contribution-by: Roberto Peña
>>>>>>>>>>>
>>>>>>>>>>> Italian language file
>>>>>>>>>>> Contribution-by: Umberto Parma
>>>>>>>>>>>
>>>>>>>>>>> French language file
>>>>>>>>>>> Contribution-by: gw-ipfire
>>>>>>>>>>>
>>>>>>>>>>> Turkish language file
>>>>>>>>>>> Contribution-by: Peppe Tech
>>>>>>>>>>>
>>>>>>>>>>> Contribution-by: Bernhard Bitsch <bbitsch@ipfire.org>
>>>>>>>>>>> Contribution-by: Erik Kapfer <erik.kapfer@ipfire.org>
>>>>>>>>>>> Signed-off-by: Jon Murphy <jon.murphy@ipfire.org
>>>>>>>>>>> ---
>>>>>>>>>>> config/backup/includes/rpz | 4 +
>>>>>>>>>>> config/cfgroot/manualpages | 1 +
>>>>>>>>>>> config/menu/EX-rpz.menu | 6 +
>>>>>>>>>>> config/rootfiles/common/configroot | 1 +
>>>>>>>>>>> config/rootfiles/common/web-user-interface | 1 +
>>>>>>>>>>> config/rootfiles/packages/rpz | 20 +
>>>>>>>>>>> config/rpz/00-rpz.conf | 10 +
>>>>>>>>>>> config/rpz/rpz-config | 130 +++
>>>>>>>>>>> config/rpz/rpz-functions | 85 ++
>>>>>>>>>>> config/rpz/rpz-make | 203 +++++
>>>>>>>>>>> config/rpz/rpz-metrics | 170 ++++
>>>>>>>>>>> config/rpz/rpz-sleep | 58 ++
>>>>>>>>>>> config/rpz/rpz.de.pl | 30 +
>>>>>>>>>>> config/rpz/rpz.en.pl | 30 +
>>>>>>>>>>> config/rpz/rpz.es.pl | 30 +
>>>>>>>>>>> config/rpz/rpz.fr.pl | 30 +
>>>>>>>>>>> config/rpz/rpz.it.pl | 30 +
>>>>>>>>>>> config/rpz/rpz.tr.pl | 30 +
>>>>>>>>>>> html/cgi-bin/rpz.cgi | 923 +++++++++++++++++++++
>>>>>>>>>>> lfs/rpz | 96 +++
>>>>>>>>>>> make.sh | 3 +-
>>>>>>>>>>> src/paks/rpz/install.sh | 36 +
>>>>>>>>>>> src/paks/rpz/uninstall.sh | 38 +
>>>>>>>>>>> src/paks/rpz/update.sh | 52 ++
>>>>>>>>>>> 24 files changed, 2016 insertions(+), 1 deletion(-)
>>>>>>>>>>> create mode 100644 config/backup/includes/rpz
>>>>>>>>>>> create mode 100644 config/menu/EX-rpz.menu
>>>>>>>>>>> create mode 100644 config/rootfiles/packages/rpz
>>>>>>>>>>> create mode 100644 config/rpz/00-rpz.conf
>>>>>>>>>>> create mode 100644 config/rpz/rpz-config
>>>>>>>>>>> create mode 100644 config/rpz/rpz-functions
>>>>>>>>>>> create mode 100644 config/rpz/rpz-make
>>>>>>>>>>> create mode 100755 config/rpz/rpz-metrics
>>>>>>>>>>> create mode 100755 config/rpz/rpz-sleep
>>>>>>>>>>> create mode 100644 config/rpz/rpz.de.pl
>>>>>>>>>>> create mode 100644 config/rpz/rpz.en.pl
>>>>>>>>>>> create mode 100644 config/rpz/rpz.es.pl
>>>>>>>>>>> create mode 100644 config/rpz/rpz.fr.pl
>>>>>>>>>>> create mode 100644 config/rpz/rpz.it.pl
>>>>>>>>>>> create mode 100644 config/rpz/rpz.tr.pl
>>>>>>>>>>> create mode 100644 html/cgi-bin/rpz.cgi
>>>>>>>>>>> create mode 100644 lfs/rpz
>>>>>>>>>>> create mode 100644 src/paks/rpz/install.sh
>>>>>>>>>>> create mode 100644 src/paks/rpz/uninstall.sh
>>>>>>>>>>> create mode 100644 src/paks/rpz/update.sh
>>>>>>>>>>>
>>>>>>>>>>> diff --git a/config/backup/includes/rpz b/config/backup/includes/rpz
>>>>>>>>>>> new file mode 100644
>>>>>>>>>>> index 000000000..36513e494
>>>>>>>>>>> --- /dev/null
>>>>>>>>>>> +++ b/config/backup/includes/rpz
>>>>>>>>>>> @@ -0,0 +1,4 @@
>>>>>>>>>>> +/var/ipfire/dns/rpz/*
>>>>>>>>>>> +/etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>> +/etc/unbound/zonefiles/block.rpz
>>>>>>>>>>> +/etc/unbound/local.d/*rpz.conf
>>>>>>>>>>> diff --git a/config/cfgroot/manualpages b/config/cfgroot/manualpages
>>>>>>>>>>> index 1f7e01efc..d3a48c633 100644
>>>>>>>>>>> --- a/config/cfgroot/manualpages
>>>>>>>>>>> +++ b/config/cfgroot/manualpages
>>>>>>>>>>> @@ -70,6 +70,7 @@ pakfire.cgi=configuration/ipfire/pakfire
>>>>>>>>>>> wlanap.cgi=addons/wireless
>>>>>>>>>>> tor.cgi=addons/tor
>>>>>>>>>>> samba.cgi=addons/samba
>>>>>>>>>>> +rpz.cgi=addons/rpz
>>>>>>>>>>>
>>>>>>>>>>> # Logs menu
>>>>>>>>>>> logs.cgi/summary.dat=configuration/logs/summary
>>>>>>>>>>> diff --git a/config/menu/EX-rpz.menu b/config/menu/EX-rpz.menu
>>>>>>>>>>> new file mode 100644
>>>>>>>>>>> index 000000000..2f4daf410
>>>>>>>>>>> --- /dev/null
>>>>>>>>>>> +++ b/config/menu/EX-rpz.menu
>>>>>>>>>>> @@ -0,0 +1,6 @@
>>>>>>>>>>> +$subipfire->{'20.rpz'} = {
>>>>>>>>>>> + 'caption' => $Lang::tr{'rpz'},
>>>>>>>>>>> + 'uri' => '/cgi-bin/rpz.cgi',
>>>>>>>>>>> + 'title' => "RPZ",
>>>>>>>>>>> + 'enabled' => 1,
>>>>>>>>>>> +};
>>>>>>>>>>> diff --git a/config/rootfiles/common/configroot b/config/rootfiles/common/configroot
>>>>>>>>>>> index 9839eee45..b30d6aae4 100644
>>>>>>>>>>> --- a/config/rootfiles/common/configroot
>>>>>>>>>>> +++ b/config/rootfiles/common/configroot
>>>>>>>>>>> @@ -120,6 +120,7 @@ var/ipfire/menu.d/70-log.menu
>>>>>>>>>>> #var/ipfire/menu.d/EX-apcupsd.menu
>>>>>>>>>>> #var/ipfire/menu.d/EX-guardian.menu
>>>>>>>>>>> #var/ipfire/menu.d/EX-mympd.menu
>>>>>>>>>>> +#var/ipfire/menu.d/EX-rpz.menu
>>>>>>>>>>> #var/ipfire/menu.d/EX-samba.menu
>>>>>>>>>>> #var/ipfire/menu.d/EX-tor.menu
>>>>>>>>>>> #var/ipfire/menu.d/EX-transmission.menu
>>>>>>>>>>> diff --git a/config/rootfiles/common/web-user-interface b/config/rootfiles/common/web-user-interface
>>>>>>>>>>> index 816241dae..e00464076 100644
>>>>>>>>>>> --- a/config/rootfiles/common/web-user-interface
>>>>>>>>>>> +++ b/config/rootfiles/common/web-user-interface
>>>>>>>>>>> @@ -69,6 +69,7 @@ srv/web/ipfire/cgi-bin/proxy.cgi
>>>>>>>>>>> srv/web/ipfire/cgi-bin/qos.cgi
>>>>>>>>>>> srv/web/ipfire/cgi-bin/remote.cgi
>>>>>>>>>>> srv/web/ipfire/cgi-bin/routing.cgi
>>>>>>>>>>> +#srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>>>>>> #srv/web/ipfire/cgi-bin/samba.cgi
>>>>>>>>>>> srv/web/ipfire/cgi-bin/services.cgi
>>>>>>>>>>> srv/web/ipfire/cgi-bin/shutdown.cgi
>>>>>>>>>>> diff --git a/config/rootfiles/packages/rpz b/config/rootfiles/packages/rpz
>>>>>>>>>>> new file mode 100644
>>>>>>>>>>> index 000000000..1c8663049
>>>>>>>>>>> --- /dev/null
>>>>>>>>>>> +++ b/config/rootfiles/packages/rpz
>>>>>>>>>>> @@ -0,0 +1,20 @@
>>>>>>>>>>> +etc/unbound/local.d/00-rpz.conf
>>>>>>>>>>> +etc/unbound/zonefiles
>>>>>>>>>>> +etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>> +usr/sbin/rpz-config
>>>>>>>>>>> +usr/sbin/rpz-functions
>>>>>>>>>>> +usr/sbin/rpz-make
>>>>>>>>>>> +usr/sbin/rpz-metrics
>>>>>>>>>>> +usr/sbin/rpz-sleep
>>>>>>>>>>> +var/ipfire/addon-lang/rpz.de.pl
>>>>>>>>>>> +var/ipfire/addon-lang/rpz.en.pl
>>>>>>>>>>> +var/ipfire/addon-lang/rpz.es.pl
>>>>>>>>>>> +var/ipfire/addon-lang/rpz.fr.pl
>>>>>>>>>>> +var/ipfire/addon-lang/rpz.it.pl
>>>>>>>>>>> +var/ipfire/addon-lang/rpz.tr.pl
>>>>>>>>>>> +var/ipfire/backup/addons/includes/rpz
>>>>>>>>>>> +var/ipfire/dns/rpz
>>>>>>>>>>> +var/ipfire/dns/rpz/allowlist
>>>>>>>>>>> +var/ipfire/dns/rpz/blocklist
>>>>>>>>>>> +var/ipfire/menu.d/EX-rpz.menu
>>>>>>>>>>> +srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>>>>>> diff --git a/config/rpz/00-rpz.conf b/config/rpz/00-rpz.conf
>>>>>>>>>>> new file mode 100644
>>>>>>>>>>> index 000000000..f005a4f2e
>>>>>>>>>>> --- /dev/null
>>>>>>>>>>> +++ b/config/rpz/00-rpz.conf
>>>>>>>>>>> @@ -0,0 +1,10 @@
>>>>>>>>>>> +server:
>>>>>>>>>>> + module-config: "respip validator iterator"
>>>>>>>>>>> +
>>>>>>>>>>> +rpz:
>>>>>>>>>>> + name: allow.rpz
>>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>> + rpz-action-override: passthru
>>>>>>>>>>> + rpz-log: yes
>>>>>>>>>>> + rpz-log-name: allow
>>>>>>>>>>> + rpz-signal-nxdomain-ra: yes
>>>>>>>>>>> diff --git a/config/rpz/rpz-config b/config/rpz/rpz-config
>>>>>>>>>>> new file mode 100644
>>>>>>>>>>> index 000000000..c72d50f9b
>>>>>>>>>>> --- /dev/null
>>>>>>>>>>> +++ b/config/rpz/rpz-config
>>>>>>>>>>> @@ -0,0 +1,130 @@
>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>> +###############################################################################
>>>>>>>>>>> +# #
>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info@ipfire.org> #
>>>>>>>>>>> +# #
>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>> +# #
>>>>>>>>>>> +###############################################################################
>>>>>>>>>>> +
>>>>>>>>>>> +version="2025-01-11 - v44"
>>>>>>>>>>> +
>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>> +
>>>>>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>>>>>> +
>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>> +
>>>>>>>>>>> +tagName="unbound"
>>>>>>>>>>> +
>>>>>>>>>>> +rpzAction="${1}" # input RPZ action
>>>>>>>>>>> +rpzName="${2}" # input RPZ name
>>>>>>>>>>> +rpzURL="${3}" # input RPZ URL
>>>>>>>>>>> +rpzOption1="${4}" # input RPZ option #1
>>>>>>>>>>> +rpzOption2="${5}" # input RPZ option #2
>>>>>>>>>>> +
>>>>>>>>>>> +rpzConfig="/etc/unbound/local.d/${rpzName}.rpz.conf" # output zone conf file
>>>>>>>>>>> +rpzFile="/etc/unbound/zonefiles/${rpzName}.rpz" # output for RPZ file
>>>>>>>>>>> +
>>>>>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>>>>>> +
>>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>>> + case "$1" in
>>>>>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>>>>>> + esac
>>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>>> +done
>>>>>>>>>>> +
>>>>>>>>>>> +case "${rpzAction}" in
>>>>>>>>>>> + # add new rpz list
>>>>>>>>>>> + add )
>>>>>>>>>>> + check_name "${rpzName}" # is this a valid name?
>>>>>>>>>>> + # does this config already exist? If yes, then exit
>>>>>>>>>>> + if [[ -f "${rpzConfig}" ]] ; then
>>>>>>>>>>> + msg_log "error: rpz: duplicate - ${rpzConfig} already exists. exit"
>>>>>>>>>>> + exit 104
>>>>>>>>>>> + fi
>>>>>>>>>>> +
>>>>>>>>>>> + # is this a valid URL?
>>>>>>>>>>> + regex='^https://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
>>>>>>>>>>> + if ! [[ "${rpzURL}" =~ $regex ]] ; then
>>>>>>>>>>> + msg_log "error: rpz: the URL is not valid: \"${rpzURL}\". exit."
>>>>>>>>>>> + exit 105
>>>>>>>>>>> + fi
>>>>>>>>>>> +
>>>>>>>>>>> + # create the zone config file
>>>>>>>>>>> + {
>>>>>>>>>>> + echo "rpz:"
>>>>>>>>>>> + echo " name: ${rpzName}.rpz"
>>>>>>>>>>> + echo " zonefile: ${rpzFile}"
>>>>>>>>>>> + echo " url: ${rpzURL}"
>>>>>>>>>>> + echo " rpz-action-override: nxdomain"
>>>>>>>>>>> + echo " rpz-log: ${rpzLog}"
>>>>>>>>>>> + echo " rpz-log-name: ${rpzName}"
>>>>>>>>>>> + echo " rpz-signal-nxdomain-ra: yes"
>>>>>>>>>>> + } > "${rpzConfig}"
>>>>>>>>>>> +
>>>>>>>>>>> + # set-up zonefile
>>>>>>>>>>> + # create an empty rpz file if it does not exist
>>>>>>>>>>> + if [[ ! -f "${rpzFile}" ]] ; then
>>>>>>>>>>> + touch "${rpzFile}"
>>>>>>>>>>> + # unbound requires these settings for rpz files
>>>>>>>>>>> + set_permissions "${rpzFile}" "${rpzConfig}"
>>>>>>>>>>> + fi
>>>>>>>>>>> + ;;
>>>>>>>>>>> +
>>>>>>>>>>> + # trash config file & rpz file
>>>>>>>>>>> + remove )
>>>>>>>>>>> + if ! [[ -f "${rpzConfig}" ]] ; then
>>>>>>>>>>> + msg_log "error: rpz: cannot remove ${rpzConfig}, does not exist. exit"
>>>>>>>>>>> + exit 106
>>>>>>>>>>> + fi
>>>>>>>>>>> +
>>>>>>>>>>> + msg_log "info: rpz: remove config file & rpz file \"${rpzName}\""
>>>>>>>>>>> + rm "${rpzConfig}"
>>>>>>>>>>> + rm "${rpzFile}"
>>>>>>>>>>> + ;;
>>>>>>>>>>> +
>>>>>>>>>>> + reload )
>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>> + ;;
>>>>>>>>>>> +
>>>>>>>>>>> + list )
>>>>>>>>>>> + awk -F':' '/^\s*name:/{ gsub(/[[:blank:]]|\.rpz/, "",$2) ; NAME=$2 } \
>>>>>>>>>>> + /^\s*url:/{ gsub(/[[:blank:]]/, "") ; print NAME"="$2":"$3} ' \
>>>>>>>>>>> + /etc/unbound/local.d/*rpz.conf
>>>>>>>>>>> + exit
>>>>>>>>>>> + ;;
>>>>>>>>>>> +
>>>>>>>>>>> + unbound-restart )
>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>> + unbound_restart
>>>>>>>>>>> + exit
>>>>>>>>>>> + ;;
>>>>>>>>>>> +
>>>>>>>>>>> + * )
>>>>>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>>>>>> + printf "Usage: $(basename "$0") <ACTION> <NAME> <URL> <OPTION> <OPTION>\n"
>>>>>>>>>>> + printf "Version: ${version}\n"
>>>>>>>>>>> + exit 108
>>>>>>>>>>> + ;;
>>>>>>>>>>> +
>>>>>>>>>>> +esac
>>>>>>>>>>> +
>>>>>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>>>>>> +
>>>>>>>>>>> +exit
>>>>>>>>>>> diff --git a/config/rpz/rpz-functions b/config/rpz/rpz-functions
>>>>>>>>>>> new file mode 100644
>>>>>>>>>>> index 000000000..ace1d2690
>>>>>>>>>>> --- /dev/null
>>>>>>>>>>> +++ b/config/rpz/rpz-functions
>>>>>>>>>>> @@ -0,0 +1,85 @@
>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>> +###############################################################################
>>>>>>>>>>> +# #
>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>> +# #
>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>> +# #
>>>>>>>>>>> +###############################################################################
>>>>>>>>>>> +
>>>>>>>>>>> +version="2024-12-10 - v02"
>>>>>>>>>>> +
>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>> +
>>>>>>>>>>> +msg_log () {
>>>>>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>>>>>> + if tty --silent ; then
>>>>>>>>>>> + echo "${tagName}:" "$*"
>>>>>>>>>>> + fi
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +# check for a valid name
>>>>>>>>>>> +check_name () {
>>>>>>>>>>> + local theName="${1}"
>>>>>>>>>>> +
>>>>>>>>>>> + regex='^[a-zA-Z0-9_]+$' # no dash or plus, alpha numeric only
>>>>>>>>>>> + regex1='^(allow|block)$' # allow and block are reserved NAMEs
>>>>>>>>>>> + if [[ ! "${theName}" =~ $regex ]] || [[ "${theName}" =~ $regex1 ]] ; then
>>>>>>>>>>> + msg_log "error: rpz: the NAME is not valid: \"${theName}\". exit."
>>>>>>>>>>> + exit 101
>>>>>>>>>>> + fi
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +set_permissions () {
>>>>>>>>>>> + chown nobody:nobody "$@"
>>>>>>>>>>> + chmod 644 "$@"
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +check_unbound_conf () {
>>>>>>>>>>> + local thecheckConf="${1:-yes}" # check config default is yes
>>>>>>>>>>> +
>>>>>>>>>>> + # check the above config files
>>>>>>>>>>> + if [[ "${thecheckConf}" == yes ]] ; then
>>>>>>>>>>> + msg_log "info: rpz: check for errors with \"unbound-checkconf\""
>>>>>>>>>>> +
>>>>>>>>>>> + if ! unbound-checkconf ; then
>>>>>>>>>>> + msg_log "error: rpz: unbound-checkconf found invalid configuration."
>>>>>>>>>>> + msg_log \
>>>>>>>>>>> + "error: rpz: In Terminal run the command \"unbound-checkconf\" for more information. exit."
>>>>>>>>>>> + exit 102
>>>>>>>>>>> + fi
>>>>>>>>>>> + fi
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +unbound_control_reload () {
>>>>>>>>>>> + local theReload="${1:-yes}" # reload default is yes
>>>>>>>>>>> +
>>>>>>>>>>> + if [[ "${theReload}" == yes ]] ; then
>>>>>>>>>>> + # reload due to the changes
>>>>>>>>>>> + msg_log "info: rpz: run \"unbound-control reload\""
>>>>>>>>>>> +
>>>>>>>>>>> + if ! unbound-control reload ; then
>>>>>>>>>>> + msg_log "error: rpz: unbound-control reload. exit."
>>>>>>>>>>> + exit 109
>>>>>>>>>>> + fi
>>>>>>>>>>> + fi
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +unbound_restart () {
>>>>>>>>>>> + # restart due to the changes
>>>>>>>>>>> + msg_log "info: rpz: run \"unbound restart\""
>>>>>>>>>>> +
>>>>>>>>>>> + /usr/local/bin/unboundctrl restart
>>>>>>>>>>> +}
>>>>>>>>>>> diff --git a/config/rpz/rpz-make b/config/rpz/rpz-make
>>>>>>>>>>> new file mode 100644
>>>>>>>>>>> index 000000000..927d55170
>>>>>>>>>>> --- /dev/null
>>>>>>>>>>> +++ b/config/rpz/rpz-make
>>>>>>>>>>> @@ -0,0 +1,203 @@
>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>> +###############################################################################
>>>>>>>>>>> +# #
>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info@ipfire.org> #
>>>>>>>>>>> +# #
>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>> +# #
>>>>>>>>>>> +###############################################################################
>>>>>>>>>>> +
>>>>>>>>>>> +version="2025-01-11 - v14"
>>>>>>>>>>> +
>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>> +
>>>>>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>>>>>> +
>>>>>>>>>>> +# create the config file for allow
>>>>>>>>>>> +make_allow_config () {
>>>>>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>>>>>> + local theConfig="/etc/unbound/local.d/00-rpz.conf" # output zone conf file
>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/allowlist" # input custom list of domains
>>>>>>>>>>> +
>>>>>>>>>>> + msg_log "info: rpz: make config file \"00-rpz.conf\""
>>>>>>>>>>> +
>>>>>>>>>>> + echo "server:
>>>>>>>>>>> + module-config: \"respip validator iterator\"" > "${theConfig}"
>>>>>>>>>>> +
>>>>>>>>>>> + # does allow list exist?
>>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>>> +
>>>>>>>>>>> + echo "rpz:
>>>>>>>>>>> + name: allow.rpz
>>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>> + rpz-action-override: passthru
>>>>>>>>>>> + rpz-log: ${theLog}
>>>>>>>>>>> + rpz-log-name: allow
>>>>>>>>>>> + rpz-signal-nxdomain-ra: yes" >> "${theConfig}"
>>>>>>>>>>> +
>>>>>>>>>>> + fi
>>>>>>>>>>> +
>>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>>> + set_permissions "${theConfig}"
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +# create the config file for block
>>>>>>>>>>> +make_block_config () {
>>>>>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>>>>>> + local theConfig="/etc/unbound/local.d/block.rpz.conf" # output zone conf file
>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/blocklist" # input custom list of domains
>>>>>>>>>>> +
>>>>>>>>>>> + msg_log "info: rpz: make config file \"block.rpz.conf\""
>>>>>>>>>>> +
>>>>>>>>>>> + # does block list exist?
>>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>>> +
>>>>>>>>>>> + echo "rpz:
>>>>>>>>>>> + name: block.rpz
>>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/block.rpz
>>>>>>>>>>> + rpz-action-override: nxdomain
>>>>>>>>>>> + rpz-log: ${theLog}
>>>>>>>>>>> + rpz-log-name: block
>>>>>>>>>>> + rpz-signal-nxdomain-ra: yes" > "${theConfig}"
>>>>>>>>>>> +
>>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>>> + set_permissions "${theConfig}"
>>>>>>>>>>> + else
>>>>>>>>>>> + # no - trash the config file
>>>>>>>>>>> + rm --verbose /etc/unbound/local.d/block.rpz.conf
>>>>>>>>>>> + fi
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +# create an RPZ file for allow or block
>>>>>>>>>>> +make_rpz_file () {
>>>>>>>>>>> + local theName="${1}" # allow or block
>>>>>>>>>>> + local theAction='.' # the default is nxdomain or block
>>>>>>>>>>> + local actionList
>>>>>>>>>>> +
>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>>>>>> + local theZonefile="/etc/unbound/zonefiles/${theName}.rpz" # output file for RPZ
>>>>>>>>>>> +
>>>>>>>>>>> + # does a list exist?
>>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>>> +
>>>>>>>>>>> + # for allow set to passthru
>>>>>>>>>>> + [[ "${theName}" == allow ]] && theAction='rpz-passthru.'
>>>>>>>>>>> +
>>>>>>>>>>> + # drop any extra "blanks" and add "CNAME <RPZ action>." to each line
>>>>>>>>>>> + actionList=$( awk '{$1=$1};1' "${theList}" |
>>>>>>>>>>> + sed "/^[^;].*[[:alnum:]]/ s|$| CNAME ${theAction}|" )
>>>>>>>>>>> +
>>>>>>>>>>> + msg_log "info: rpz: create zonefile for \"${theName}list\""
>>>>>>>>>>> +
>>>>>>>>>>> +echo "; Name: ${theName} list
>>>>>>>>>>> +; Last modified: $(date "+%Y-%m-%d at %H.%M.%S %Z")
>>>>>>>>>>> +;
>>>>>>>>>>> +; domains with actions list
>>>>>>>>>>> +;
>>>>>>>>>>> +${actionList}" > "${theZonefile}"
>>>>>>>>>>> +
>>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>>> + set_permissions "${theZonefile}"
>>>>>>>>>>> + # set-up allow/block list files
>>>>>>>>>>> + set_permissions "${theList}"
>>>>>>>>>>> + else
>>>>>>>>>>> + msg_log "info: rpz: the ${theList} is empty."
>>>>>>>>>>> +
>>>>>>>>>>> + rm --verbose "${theZonefile}" # trash the RPZ file
>>>>>>>>>>> + fi
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +# check if allow/block list is valid
>>>>>>>>>>> +validate_list () {
>>>>>>>>>>> + local theName="${1}" # allow or block
>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>>>>>> +
>>>>>>>>>>> + # remove good:
>>>>>>>>>>> + # - properly formated domain names with or without leading wildcard
>>>>>>>>>>> + # - properly formated top level domain (TLD) names with wildcard
>>>>>>>>>>> + # - blank lines and comment lines
>>>>>>>>>>> + # remaining lines are considered "bad"
>>>>>>>>>>> + bad_lines=$( sed --regexp-extended \
>>>>>>>>>>> + '/^(\*\.)?([a-zA-Z0-9](([a-zA-Z0-9\-]){0,61}[a-zA-Z0-9])?\.)+([a-zA-Z]{2,}|xn--[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])$/d ;
>>>>>>>>>>> + /^(\*\.)([a-z]{2,61}|xn--[a-z0-9]{1,60})$/d ;
>>>>>>>>>>> + /^$/d ; /^;/d' "${theList}" )
>>>>>>>>>>> +
>>>>>>>>>>> + if [[ ! -z "${bad_lines}" ]] ; then
>>>>>>>>>>> + msg_log "error: rpz: invalid line(s) in ${theList}."
>>>>>>>>>>> + printf "%s\n" "bad line(s): ${bad_lines}"
>>>>>>>>>>> + exit 110
>>>>>>>>>>> + fi
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +
>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>> +
>>>>>>>>>>> +tagName="unbound"
>>>>>>>>>>> +
>>>>>>>>>>> +rpzName="${1}" # input RPZ name
>>>>>>>>>>> +
>>>>>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>>>>>> +
>>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>>> + case "$1" in
>>>>>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>>>>>> + esac
>>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>>> +done
>>>>>>>>>>> +
>>>>>>>>>>> +case "${rpzName}" in
>>>>>>>>>>> + # make a new allow or block rpz file
>>>>>>>>>>> +
>>>>>>>>>>> + allow )
>>>>>>>>>>> + validate_list 'allow' # is the allowlist valid?
>>>>>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>>>>>> + make_rpz_file 'allow'
>>>>>>>>>>> + ;;
>>>>>>>>>>> +
>>>>>>>>>>> + allowblock )
>>>>>>>>>>> + validate_list 'allow' # is the list valid?
>>>>>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>>>>>> + make_rpz_file 'allow'
>>>>>>>>>>> + ;&
>>>>>>>>>>> +
>>>>>>>>>>> + block )
>>>>>>>>>>> + validate_list 'block' # is the blocklist valid?
>>>>>>>>>>> + make_block_config "${rpzLog}"
>>>>>>>>>>> + make_rpz_file 'block'
>>>>>>>>>>> + ;;
>>>>>>>>>>> +
>>>>>>>>>>> + reload )
>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>> + ;;
>>>>>>>>>>> +
>>>>>>>>>>> + unbound-restart )
>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>> + unbound_restart
>>>>>>>>>>> + exit
>>>>>>>>>>> + ;;
>>>>>>>>>>> +
>>>>>>>>>>> + * )
>>>>>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>>>>>> + printf "Usage: $(basename "$0") <NAME> <OPTION> <OPTION>\n"
>>>>>>>>>>> + printf "Version: ${version}\n"
>>>>>>>>>>> + exit 108
>>>>>>>>>>> + ;;
>>>>>>>>>>> +esac
>>>>>>>>>>> +
>>>>>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>>>>>> +
>>>>>>>>>>> +exit
>>>>>>>>>>> diff --git a/config/rpz/rpz-metrics b/config/rpz/rpz-metrics
>>>>>>>>>>> new file mode 100755
>>>>>>>>>>> index 000000000..4d43e1629
>>>>>>>>>>> --- /dev/null
>>>>>>>>>>> +++ b/config/rpz/rpz-metrics
>>>>>>>>>>> @@ -0,0 +1,170 @@
>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>> +###############################################################################
>>>>>>>>>>> +# #
>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>> +# #
>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>> +# #
>>>>>>>>>>> +###############################################################################
>>>>>>>>>>> +
>>>>>>>>>>> +version="2025-01-20 - v25"
>>>>>>>>>>> +
>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>> +
>>>>>>>>>>> +weeks="2" # default to two message logs
>>>>>>>>>>> +sortBy="name" # default "by name"
>>>>>>>>>>> +rpzActive="enabled" # default "enabled only"
>>>>>>>>>>> +
>>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>>> + case "$1" in
>>>>>>>>>>> + --by-names | --by-name | name ) sortBy="name" ;;
>>>>>>>>>>> +
>>>>>>>>>>> + --by-hits | --by-hit | hits | hit ) sortBy="hit" ;;
>>>>>>>>>>> +
>>>>>>>>>>> + --by-lines | --by-line | lines | line ) sortBy="line" ;;
>>>>>>>>>>> +
>>>>>>>>>>> + --by-effect ) sortBy="effect" ;;
>>>>>>>>>>> +
>>>>>>>>>>> + --enabled-only ) rpzActive="enabled" ;;
>>>>>>>>>>> +
>>>>>>>>>>> + --active-all | --all | all ) rpzActive="all" ;;
>>>>>>>>>>> +
>>>>>>>>>>> + [0-9] | [0-9][0-9] ) weeks=$1 ;;
>>>>>>>>>>> + esac
>>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>>> +done
>>>>>>>>>>> +
>>>>>>>>>>> +# get the list of message logs for N weeks
>>>>>>>>>>> +messageLogs=$( find /var/log/messages* -type f | sort --version-sort |
>>>>>>>>>>> + head -"${weeks}" )
>>>>>>>>>>> +
>>>>>>>>>>> +# get the list of RPZ names & counts from the message log(s)
>>>>>>>>>>> +rpzNameCount=$( for logf in ${messageLogs} ; do
>>>>>>>>>>> + zgrep --text --fixed-strings 'info: rpz: applied' "${logf}" |
>>>>>>>>>>> + awk '$10 ~ /\[\w*]/ { print $10 }' ;
>>>>>>>>>>> + done | sort | uniq --count )
>>>>>>>>>>> +
>>>>>>>>>>> +# flip results and remove brackets `[` and `]`
>>>>>>>>>>> +rpzNameCount=$( echo "${rpzNameCount}" |
>>>>>>>>>>> + awk '{ print $2, $1 }' |
>>>>>>>>>>> + sed --regexp-extended 's|^\[(.*)\]|\1|' )
>>>>>>>>>>> +
>>>>>>>>>>> +# grab only names
>>>>>>>>>>> +rpzNames=$( echo "${rpzNameCount}" | awk '{ print $1 }' )
>>>>>>>>>>> +
>>>>>>>>>>> +# get list of RPZ files
>>>>>>>>>>> +rpzFileList=$( find /etc/unbound/zonefiles -type f -iname "*.rpz" )
>>>>>>>>>>> +
>>>>>>>>>>> +# get basename of those files
>>>>>>>>>>> +rpzBaseNames=$( echo "${rpzFileList}" |
>>>>>>>>>>> + sed 's|/etc/unbound/zonefiles/||g ; s|\.rpz||g ;' )
>>>>>>>>>>> +
>>>>>>>>>>> +# add to rpzNames
>>>>>>>>>>> +rpzNames="${rpzNames}"$'\n'"${rpzBaseNames}"
>>>>>>>>>>> +
>>>>>>>>>>> +# drop duplicate names
>>>>>>>>>>> +rpzNames=$( echo "${rpzNames}" | sort --unique )
>>>>>>>>>>> +
>>>>>>>>>>> +# get line count for each RPZ
>>>>>>>>>>> +lineCount=$( echo "${rpzFileList}" | xargs wc -l )
>>>>>>>>>>> +
>>>>>>>>>>> +# get comment line count and blank line count for each RPZ
>>>>>>>>>>> +commentCount=$( echo "${rpzFileList}" | xargs grep --count -e "^$" -e "^;" )
>>>>>>>>>>> +
>>>>>>>>>>> +# get modified date each RPZ
>>>>>>>>>>> +modDateList=$( echo "${rpzFileList}" | xargs stat -c '%.10y %n' )
>>>>>>>>>>> +
>>>>>>>>>>> +ucListAuthZones=$( unbound-control list_auth_zones )
>>>>>>>>>>> +
>>>>>>>>>>> +# get width of RPZ names
>>>>>>>>>>> +pWidth=$( echo "${rpzNames}" | awk '{ print $1" " }' | wc -L )
>>>>>>>>>>> +pFormat="%-${pWidth}s %-8s %-8s %8s %12s %12s\n"
>>>>>>>>>>> +
>>>>>>>>>>> +# print title line
>>>>>>>>>>> +printf "${pFormat}" "name" "hits" "active" "lines" " hits/line" "last download"
>>>>>>>>>>> +printf -- "--------------"
>>>>>>>>>>> +
>>>>>>>>>>> +theResults=""
>>>>>>>>>>> +totalLines=0
>>>>>>>>>>> +totalHits=0
>>>>>>>>>>> +while read -r theName
>>>>>>>>>>> +do
>>>>>>>>>>> + printf -- "--" # pretend progress bar
>>>>>>>>>>> +
>>>>>>>>>>> + # is this RPZ list active?
>>>>>>>>>>> + theActive="disabled"
>>>>>>>>>>> + if grep --quiet "^${theName}\.rpz" <<< "${ucListAuthZones}"
>>>>>>>>>>> + then
>>>>>>>>>>> + theActive="enabled"
>>>>>>>>>>> + else
>>>>>>>>>>> + [[ "${rpzActive}" == enabled ]] && continue
>>>>>>>>>>> + fi
>>>>>>>>>>> +
>>>>>>>>>>> + # get hit count
>>>>>>>>>>> + theHits="0"
>>>>>>>>>>> + if output=$( grep "^${theName}\s" <<< "${rpzNameCount}" ) ; then
>>>>>>>>>>> + theHits=$( echo "${output}" | awk '{ print $2 }' )
>>>>>>>>>>> + totalHits=$(( totalHits + theHits ))
>>>>>>>>>>> + fi
>>>>>>>>>>> +
>>>>>>>>>>> + # get line count
>>>>>>>>>>> + theLines="n/a"
>>>>>>>>>>> + hitsPerLine="0"
>>>>>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${lineCount}" ) ; then
>>>>>>>>>>> + theLines=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>>>>>> + totalLines=$(( totalLines + theLines ))
>>>>>>>>>>> +
>>>>>>>>>>> + if [[ "${theLines}" -gt 2 ]] ; then
>>>>>>>>>>> + hitsPerLine=$(( 100 * theHits / theLines ))
>>>>>>>>>>> + fi
>>>>>>>>>>> + fi
>>>>>>>>>>> +
>>>>>>>>>>> + # get modification date
>>>>>>>>>>> + theModDate="n/a"
>>>>>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${modDateList}" ) ; then
>>>>>>>>>>> + theModDate=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>>>>>> + fi
>>>>>>>>>>> +
>>>>>>>>>>> + # add to results list
>>>>>>>>>>> + theResults+="${theName} ${theHits} ${theActive} ${theLines} ${hitsPerLine} ${theModDate}"$'\n'
>>>>>>>>>>> +
>>>>>>>>>>> +done <<< "${rpzNames}"
>>>>>>>>>>> +
>>>>>>>>>>> +case "${sortBy}" in
>>>>>>>>>>> + # sort by "active" then by "name"
>>>>>>>>>>> + name) sortArg=(-k3,3r -k1,1) ;;
>>>>>>>>>>> +
>>>>>>>>>>> + # sort by "active" then by "hits" then by "name"
>>>>>>>>>>> + hit) sortArg=(-k3,3r -k2,2nr -k1,1) ;;
>>>>>>>>>>> +
>>>>>>>>>>> + # sort by "active" then by "lines" then by "name"
>>>>>>>>>>> + line) sortArg=(-k3,3r -k4,4nr -k1,1) ;;
>>>>>>>>>>> +
>>>>>>>>>>> + # sort by "active" then by "effect" then by "name"
>>>>>>>>>>> + effect) sortArg=(-k3,3r -k5,5nr -k1,1) ;;
>>>>>>>>>>> +esac
>>>>>>>>>>> +
>>>>>>>>>>> +printf -- "--------------\n"
>>>>>>>>>>> +# remove blank lines, sort, print as columns
>>>>>>>>>>> +echo "${theResults}" |
>>>>>>>>>>> + awk '!/^[[:space:]]*$/' |
>>>>>>>>>>> + sort "${sortArg[@]}" |
>>>>>>>>>>> + awk --assign=width="${pWidth}" \
>>>>>>>>>>> + '{ printf "%-*s %-8s %-8s %8s %10s %% %12s\n", width, $1, $2, $3, $4, $5, $6 }'
>>>>>>>>>>> +
>>>>>>>>>>> +printf "${pFormat}" "" "=======" "" "========" "" ""
>>>>>>>>>>> +printf "${pFormat}" "Totals -->" "${totalHits}" "" "${totalLines}" "" ""
>>>>>>>>>>> +
>>>>>>>>>>> +exit
>>>>>>>>>>> diff --git a/config/rpz/rpz-sleep b/config/rpz/rpz-sleep
>>>>>>>>>>> new file mode 100755
>>>>>>>>>>> index 000000000..dd3603599
>>>>>>>>>>> --- /dev/null
>>>>>>>>>>> +++ b/config/rpz/rpz-sleep
>>>>>>>>>>> @@ -0,0 +1,58 @@
>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>> +###############################################################################
>>>>>>>>>>> +# #
>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>> +# #
>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>> +# #
>>>>>>>>>>> +###############################################################################
>>>>>>>>>>> +
>>>>>>>>>>> +version="2024-08-16" # v05
>>>>>>>>>>> +
>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>> +
>>>>>>>>>>> +# send message to message log
>>>>>>>>>>> +msg_log () {
>>>>>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>>>>>> + if tty --silent ; then
>>>>>>>>>>> + echo "${tagName}:" "$*"
>>>>>>>>>>> + fi
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>> +
>>>>>>>>>>> +tagName="unbound"
>>>>>>>>>>> +
>>>>>>>>>>> +sleepTime="${1:-5m}" # default to sleep for 5m (5 minutes)
>>>>>>>>>>> +
>>>>>>>>>>> +zoneList=$( unbound-control list_auth_zones | awk '{print $1}' )
>>>>>>>>>>> +
>>>>>>>>>>> +for zone in ${zoneList} ; do
>>>>>>>>>>> + printf "disable ${zone}\t"
>>>>>>>>>>> + unbound-control rpz_disable "${zone}"
>>>>>>>>>>> +done
>>>>>>>>>>> +
>>>>>>>>>>> +msg_log "info: rpz: disabled all zones for ${sleepTime}"
>>>>>>>>>>> +
>>>>>>>>>>> +sleep "${sleepTime}"
>>>>>>>>>>> +
>>>>>>>>>>> +for zone in ${zoneList} ; do
>>>>>>>>>>> + printf "enable ${zone}\t"
>>>>>>>>>>> + unbound-control rpz_enable "${zone}"
>>>>>>>>>>> +done
>>>>>>>>>>> +
>>>>>>>>>>> +msg_log "info: rpz: enabled all zones"
>>>>>>>>>>> +
>>>>>>>>>>> +exit
>>>>>>>>>>> diff --git a/config/rpz/rpz.de.pl b/config/rpz/rpz.de.pl
>>>>>>>>>>> new file mode 100644
>>>>>>>>>>> index 000000000..3770c6bb0
>>>>>>>>>>> --- /dev/null
>>>>>>>>>>> +++ b/config/rpz/rpz.de.pl
>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>> +'rpz apply' => 'Übernehmen',
>>>>>>>>>>> +'rpz cl allow enable' => 'Benutzerdefinierte Allowlist aktivieren:',
>>>>>>>>>>> +'rpz cl allow info' => 'Zugelassene Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>>>>>> +'rpz cl allow' => 'Benutzerdefinierte Allowlist',
>>>>>>>>>>> +'rpz cl block enable' => 'Benutzerdefinierte Blocklist aktivieren:',
>>>>>>>>>>> +'rpz cl block info' => 'Gesperrte Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>>>>>> +'rpz cl block' => 'Benutzerdefinierte Blocklist',
>>>>>>>>>>> +'rpz cl' => 'Benutzerdefinierte Listen',
>>>>>>>>>>> +'rpz exitcode 101' => 'Der Name enthält unzulässige Zeichen',
>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf hat eine fehlerhafte Konfiguration ermittelt. Führen Sie das Kommando unbound-checkconf auf der Konsole aus, um weitere Informationen zu erhalten.',
>>>>>>>>>>> +'rpz exitcode 103' => 'Die benutzerdefinierte Allow-/Blocklist ist leer',
>>>>>>>>>>> +'rpz exitcode 104' => 'Ein Eintrag mit identischem Namen existiert bereits',
>>>>>>>>>>> +'rpz exitcode 105' => 'Die URL ist ungültig',
>>>>>>>>>>> +'rpz exitcode 106' => 'Eintrag kann nicht entfernt werden, der Name existiert nicht',
>>>>>>>>>>> +'rpz exitcode 107' => 'Der Name ist ungültig - nur "allow" oder "block" möglich',
>>>>>>>>>>> +'rpz exitcode 108' => 'Fehlende oder inkorrekte Parameter',
>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control reload ist fehlgeschlagen',
>>>>>>>>>>> +'rpz exitcode 110' => 'Die benutzerdefinierte Allow-/Blocklist enthält unzulässige Einträge',
>>>>>>>>>>> +'rpz exitcode 201' => 'Die Anmerkung enthält unzulässige Zeichen',
>>>>>>>>>>> +'rpz exitcode 202' => 'Ungültiger Eintrag in der benutzerdefinierten Allowlist, Zeile ',
>>>>>>>>>>> +'rpz exitcode 203' => 'Ungültiger Eintrag in der benutzerdefinierten Blocklist, Zeile ',
>>>>>>>>>>> +'rpz exitcode 204' => 'Ausgewählter Eintrag existiert nicht: ',
>>>>>>>>>>> +'rpz zf editor' => 'Zonendatei-Eintrag bearbeiten',
>>>>>>>>>>> +'rpz zf imported' => '(importiert aus rpz-config)',
>>>>>>>>>>> +'rpz zf remark info' => 'Erlaubte Zeichen sind a-z, A-Z, 0-9 und Unterstriche',
>>>>>>>>>>> +'rpz zf' => 'Zonendateien',
>>>>>>>>>>> +);
>>>>>>>>>>> diff --git a/config/rpz/rpz.en.pl b/config/rpz/rpz.en.pl
>>>>>>>>>>> new file mode 100644
>>>>>>>>>>> index 000000000..0720a8940
>>>>>>>>>>> --- /dev/null
>>>>>>>>>>> +++ b/config/rpz/rpz.en.pl
>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>> +'rpz apply' => 'Apply',
>>>>>>>>>>> +'rpz cl allow enable' => 'Enable custom allowlist:',
>>>>>>>>>>> +'rpz cl allow info' => 'Allowed domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>> +'rpz cl allow' => 'Custom allowlist',
>>>>>>>>>>> +'rpz cl block enable' => 'Enable custom blocklist:',
>>>>>>>>>>> +'rpz cl block info' => 'Blocked domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>> +'rpz cl block' => 'Custom blocklist',
>>>>>>>>>>> +'rpz cl' => 'Custom lists',
>>>>>>>>>>> +'rpz exitcode 101' => 'the NAME is not valid',
>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf found invalid configuration. In the Terminal run the command unbound-checkconf for more information',
>>>>>>>>>>> +'rpz exitcode 103' => 'the allow/block list is empty',
>>>>>>>>>>> +'rpz exitcode 104' => 'duplicate - NAME already exists',
>>>>>>>>>>> +'rpz exitcode 105' => 'the URL is not valid',
>>>>>>>>>>> +'rpz exitcode 106' => 'cannot remove the NAME does not exist',
>>>>>>>>>>> +'rpz exitcode 107' => 'the NAME is not valid - "allow" or "block" only',
>>>>>>>>>>> +'rpz exitcode 108' => 'missing or incorrect parameter',
>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control reload failed',
>>>>>>>>>>> +'rpz exitcode 110' => 'custom Allowlist/Blocklist contains invalid entries',
>>>>>>>>>>> +'rpz exitcode 201' => 'the REMARK is not valid',
>>>>>>>>>>> +'rpz exitcode 202' => 'invalid entry in allowlist, line ',
>>>>>>>>>>> +'rpz exitcode 203' => 'invalid entry in blocklist, line ',
>>>>>>>>>>> +'rpz exitcode 204' => 'Selected entry does not exist: ',
>>>>>>>>>>> +'rpz zf editor' => 'Edit zonefiles entry',
>>>>>>>>>>> +'rpz zf imported' => '(imported from rpz-config)',
>>>>>>>>>>> +'rpz zf remark info' => 'Valid characters are a-z, A-Z, 0-9 and underscore.',
>>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>>> +);
>>>>>>>>>>> diff --git a/config/rpz/rpz.es.pl b/config/rpz/rpz.es.pl
>>>>>>>>>>> new file mode 100644
>>>>>>>>>>> index 000000000..98628e4aa
>>>>>>>>>>> --- /dev/null
>>>>>>>>>>> +++ b/config/rpz/rpz.es.pl
>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>> +'rpz' => 'Zonas de política de respuesta (RPZ)',
>>>>>>>>>>> +'rpz apply' => 'Aplicar',
>>>>>>>>>>> +'rpz cl allow enable' => 'Habilitar la lista blanca personalizada:',
>>>>>>>>>>> +'rpz cl allow info' => 'Dominio permitido (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>>>>>> +'rpz cl allow' => 'Lista blanca personalizada',
>>>>>>>>>>> +'rpz cl block enable' => 'Habilitar la lista negra personalizada:',
>>>>>>>>>>> +'rpz cl block info' => 'Dominio bloqueado (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>>>>>> +'rpz cl block' => 'Lista negra personalizada',
>>>>>>>>>>> +'rpz cl' => 'Lista personalizada',
>>>>>>>>>>> +'rpz exitcode 101' => 'El NOMBRE no es válido',
>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha encontrado una configuración no válida. Desde Terminal, ejecute el comando unbound-checkconf para mayor información',
>>>>>>>>>>> +'rpz exitcode 103' => 'La lista de permitidos/bloqueados está vacía',
>>>>>>>>>>> +'rpz exitcode 104' => 'duplicado - NOMBRE ya existe',
>>>>>>>>>>> +'rpz exitcode 105' => 'la URL no es válida',
>>>>>>>>>>> +'rpz exitcode 106' => 'no es posible eliminar el NOMBRE que no existe',
>>>>>>>>>>> +'rpz exitcode 107' => 'el NOMBRE no es válido - sólo "permitir" o "bloquear"',
>>>>>>>>>>> +'rpz exitcode 108' => 'parámetro faltante o incorrecto',
>>>>>>>>>>> +'rpz exitcode 109' => 'Error al recargar unbound-control',
>>>>>>>>>>> +'rpz exitcode 110' => 'la Lista blanca/Lista negra personalizada contiene entradas no válidas',
>>>>>>>>>>> +'rpz exitcode 201' => 'el COMENTARIO no es válido',
>>>>>>>>>>> +'rpz exitcode 202' => 'entrada no válida en la lista blanca, línea ',
>>>>>>>>>>> +'rpz exitcode 203' => 'entrada no válida en la lista negra, línea ',
>>>>>>>>>>> +'rpz exitcode 204' => 'La entrada seleccionada no existe: ',
>>>>>>>>>>> +'rpz zf editor' => 'Editar la entrada de archivos de zona',
>>>>>>>>>>> +'rpz zf imported' => '(importado de rpz-config)',
>>>>>>>>>>> +'rpz zf remark info' => 'Los caracteres válidos son a-z, A-Z, 0-9 y guión bajo',
>>>>>>>>>>> +'rpz zf' => 'Archivos de zona',
>>>>>>>>>>> +);
>>>>>>>>>>> diff --git a/config/rpz/rpz.fr.pl b/config/rpz/rpz.fr.pl
>>>>>>>>>>> new file mode 100644
>>>>>>>>>>> index 000000000..f35f3c2d0
>>>>>>>>>>> --- /dev/null
>>>>>>>>>>> +++ b/config/rpz/rpz.fr.pl
>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>> +'rpz apply' => 'Appliquer',
>>>>>>>>>>> +'rpz cl allow enable' => 'Activer la liste d\'autorisations personnalisée:',
>>>>>>>>>>> +'rpz cl allow info' => 'Domaines autorisés (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>> +'rpz cl allow' => 'Liste d\'autorisations personnalisée',
>>>>>>>>>>> +'rpz cl block enable' => 'Activer la liste de blocage personnalisée:',
>>>>>>>>>>> +'rpz cl block info' => 'Domaines bloqués (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>> +'rpz cl block' => 'liste de blocage personnalisé',
>>>>>>>>>>> +'rpz cl' => 'Listes personnalisées',
>>>>>>>>>>> +'rpz exitcode 101' => 'le NOM n\'est pas valide',
>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf configuration non valide trouvée. Dans le terminal, exécutez la commande unbound-checkconf pour plus d\'informations',
>>>>>>>>>>> +'rpz exitcode 103' => 'la liste autoriser/bloquer est vide',
>>>>>>>>>>> +'rpz exitcode 104' => 'le NOM existe déjà',
>>>>>>>>>>> +'rpz exitcode 105' => 'L\'URL n\'est pas valide',
>>>>>>>>>>> +'rpz exitcode 106' => 'impossible de supprimer le NOM n\'existe pas',
>>>>>>>>>>> +'rpz exitcode 107' => 'le NOM n\'est pas valide - « autoriser » ou « bloquer » seulement',
>>>>>>>>>>> +'rpz exitcode 108' => 'paramètre manquant ou incorrect',
>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control rechargement échoué',
>>>>>>>>>>> +'rpz exitcode 110' => 'la liste autoriser/bloquer contient des entrées non valides',
>>>>>>>>>>> +'rpz exitcode 201' => 'la REMARQUE n\'est pas valable',
>>>>>>>>>>> +'rpz exitcode 202' => 'entrée non valide dans la liste d\'autorisation, ligne ',
>>>>>>>>>>> +'rpz exitcode 203' => 'entrée non valide dans la liste de blocs, ligne ',
>>>>>>>>>>> +'rpz exitcode 204' => 'L\'entrée sélectionnée n\'existe pas: ',
>>>>>>>>>>> +'rpz zf editor' => 'Modifier l\'entrée Fichiers Zone',
>>>>>>>>>>> +'rpz zf imported' => '(importé de rpz-config)',
>>>>>>>>>>> +'rpz zf remark info' => 'Les caractères valides sont a-z, A-Z, 0-9 et soulignement.',
>>>>>>>>>>> +'rpz zf' => 'Fichiers Zone',
>>>>>>>>>>> +);
>>>>>>>>>>> diff --git a/config/rpz/rpz.it.pl b/config/rpz/rpz.it.pl
>>>>>>>>>>> new file mode 100644
>>>>>>>>>>> index 000000000..ee81605c9
>>>>>>>>>>> --- /dev/null
>>>>>>>>>>> +++ b/config/rpz/rpz.it.pl
>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>> +'rpz apply' => 'Applica',
>>>>>>>>>>> +'rpz cl allow enable' => 'Abilita la Whitelist personalizzata:',
>>>>>>>>>>> +'rpz cl allow info' => 'Domini consentiti (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>>>>>> +'rpz cl allow' => 'Whitelist personalizzata',
>>>>>>>>>>> +'rpz cl block enable' => 'Abilita la Blacklist personalizzata:',
>>>>>>>>>>> +'rpz cl block info' => 'Domini bloccati (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>>>>>> +'rpz cl block' => 'Blacklist personalizzata',
>>>>>>>>>>> +'rpz cl' => 'Liste personalizzate',
>>>>>>>>>>> +'rpz exitcode 101' => 'il NOME non è valido',
>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha trovato una configurazione non valida. Dal Terminale esegui il comando unbound-checkconf per maggiori informazioni',
>>>>>>>>>>> +'rpz exitcode 103' => 'l\'elenco consentiti/bloccati è vuoto',
>>>>>>>>>>> +'rpz exitcode 104' => 'duplicato - NAME esiste di già',
>>>>>>>>>>> +'rpz exitcode 105' => 'l\'URL non è valido',
>>>>>>>>>>> +'rpz exitcode 106' => 'non è possibile rimuovere il NOME non esiste',
>>>>>>>>>>> +'rpz exitcode 107' => 'il NOME non è valido - solo "consenti" o "blocca"',
>>>>>>>>>>> +'rpz exitcode 108' => 'parametro mancante o non corretto',
>>>>>>>>>>> +'rpz exitcode 109' => 'ricaricamento del controllo non associato non riuscito',
>>>>>>>>>>> +'rpz exitcode 110' => 'la Whitelist/Blacklist personalizzata contiene voci non valide',
>>>>>>>>>>> +'rpz exitcode 201' => 'l"OSSERVAZIONE non è valida',
>>>>>>>>>>> +'rpz exitcode 202' => 'voce non valida nella Whitelist, riga ',
>>>>>>>>>>> +'rpz exitcode 203' => 'voce non valida nella Blacklist, riga ',
>>>>>>>>>>> +'rpz exitcode 204' => 'La voce selezionata non esiste: ',
>>>>>>>>>>> +'rpz zf editor' => 'Modifica la voce dei file di zona',
>>>>>>>>>>> +'rpz zf imported' => '(importato da rpz-config)',
>>>>>>>>>>> +'rpz zf remark info' => 'I caratteri validi sono a-z, A-Z, 0-9 e trattino basso',
>>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>>> +);
>>>>>>>>>>> diff --git a/config/rpz/rpz.tr.pl b/config/rpz/rpz.tr.pl
>>>>>>>>>>> new file mode 100644
>>>>>>>>>>> index 000000000..00226e192
>>>>>>>>>>> --- /dev/null
>>>>>>>>>>> +++ b/config/rpz/rpz.tr.pl
>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>> +# I�in eklendi Ayriyeten Response Policy Zone (RPZ)
>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>> +'rpz apply' => 'Uygulamak',
>>>>>>>>>>> +'rpz cl allow enable' => '�zel Etkinlestir allowlist:',
>>>>>>>>>>> +'rpz cl allow info' => 'Izin verilmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>>>>>> +'rpz cl allow' => 'Etkinlestir allowlist',
>>>>>>>>>>> +'rpz cl block enable' => '�zel Etkinlestir blocklist:',
>>>>>>>>>>> +'rpz cl block info' => 'Engellenmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>>>>>> +'rpz cl block' => 'Engellenmis blocklist',
>>>>>>>>>>> +'rpz cl' => 'Engellenmis lists',
>>>>>>>>>>> +'rpz exitcode 101' => 'NAME ge�erli degil',
>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ge�ersiz yapilandirma bulundu. Terminalde daha fazla bilgi i�in Unbound-checkConf komutunu �alistirin',
>>>>>>>>>>> +'rpz exitcode 103' => 'allow/block liste bos',
>>>>>>>>>>> +'rpz exitcode 104' => 'kopyalamak - NAME zaten var',
>>>>>>>>>>> +'rpz exitcode 105' => 'URL ge�erli degil',
>>>>>>>>>>> +'rpz exitcode 106' => '�ikarilamiyor NAME yok',
>>>>>>>>>>> +'rpz exitcode 107' => 'NAME ge�erli degil - "allow" veya "block" yalniz',
>>>>>>>>>>> +'rpz exitcode 108' => 'Parametre eksik veya yanlis',
>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control basarisiz',
>>>>>>>>>>> +'rpz exitcode 110' => 'Engellenmis Allowlist/Blocklist ge�ersiz girisler i�erir',
>>>>>>>>>>> +'rpz exitcode 201' => 'REMARK ge�erli degil',
>>>>>>>>>>> +'rpz exitcode 202' => 'Ge�ersiz giris allowlist, line ',
>>>>>>>>>>> +'rpz exitcode 203' => 'Ge�ersiz giris blocklist, line ',
>>>>>>>>>>> +'rpz exitcode 204' => 'Se�ilen giris yok: ',
>>>>>>>>>>> +'rpz zf editor' => 'yazimlamak zonefiles giris',
>>>>>>>>>>> +'rpz zf imported' => '(ithal edildi rpz-config)',
>>>>>>>>>>> +'rpz zf remark info' => 'Ge�erli karakterler a-z, A-Z, 0-9ve alt�st.',
>>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>>> +);
>>>>>>>>>>> diff --git a/html/cgi-bin/rpz.cgi b/html/cgi-bin/rpz.cgi
>>>>>>>>>>> new file mode 100644
>>>>>>>>>>> index 000000000..a821c92ac
>>>>>>>>>>> --- /dev/null
>>>>>>>>>>> +++ b/html/cgi-bin/rpz.cgi
>>>>>>>>>>> @@ -0,0 +1,923 @@
>>>>>>>>>>> +#!/usr/bin/perl
>>>>>>>>>>> +###############################################################################
>>>>>>>>>>> +# #
>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>> +# Copyright (C) 2005-2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>> +# #
>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>> +# #
>>>>>>>>>>> +###############################################################################
>>>>>>>>>>> +
>>>>>>>>>>> +use strict;
>>>>>>>>>>> +use Scalar::Util qw(looks_like_number);
>>>>>>>>>>> +
>>>>>>>>>>> +# debugging
>>>>>>>>>>> +#use warnings;
>>>>>>>>>>> +#use CGI::Carp 'fatalsToBrowser';
>>>>>>>>>>> +#use Data::Dumper;
>>>>>>>>>>> +
>>>>>>>>>>> +require '/var/ipfire/general-functions.pl';
>>>>>>>>>>> +require "${General::swroot}/lang.pl";
>>>>>>>>>>> +require "${General::swroot}/header.pl";
>>>>>>>>>>> +
>>>>>>>>>>> +###--- Extra HTML ---###
>>>>>>>>>>> +my $extraHead = <<END
>>>>>>>>>>> +<style>
>>>>>>>>>>> + /* alternating row background */
>>>>>>>>>>> + .tbl tr:nth-child(2n+2) {
>>>>>>>>>>> + background-color: var(--color-light-grey);
>>>>>>>>>>> + }
>>>>>>>>>>> + .tbl tr:nth-child(2n+3) {
>>>>>>>>>>> + background-color: var(--color-grey);
>>>>>>>>>>> + }
>>>>>>>>>>> + /* text styles */
>>>>>>>>>>> + .tbl th:not(:last-child) {
>>>>>>>>>>> + text-align: left;
>>>>>>>>>>> + }
>>>>>>>>>>> + div.right {
>>>>>>>>>>> + text-align: right;
>>>>>>>>>>> + margin-top: 0.5em;
>>>>>>>>>>> + }
>>>>>>>>>>> + /* customlist input */
>>>>>>>>>>> + textarea.domainlist {
>>>>>>>>>>> + margin: 0.5em 0;
>>>>>>>>>>> + resize: vertical;
>>>>>>>>>>> + min-height: 10em;
>>>>>>>>>>> + overflow: auto;
>>>>>>>>>>> + white-space: pre;
>>>>>>>>>>> + }
>>>>>>>>>>> + button[type=submit]:disabled {
>>>>>>>>>>> + opacity: 0.6;
>>>>>>>>>>> + }
>>>>>>>>>>> +</style>
>>>>>>>>>>> +END
>>>>>>>>>>> +;
>>>>>>>>>>> +###--- End of extra HTML ---###
>>>>>>>>>>> +
>>>>>>>>>>> +
>>>>>>>>>>> +### Settings ###
>>>>>>>>>>> +
>>>>>>>>>>> +# Request DNS service reload after configuration change
>>>>>>>>>>> +my $RPZ_RELOAD_FLAG = "${General::swroot}/dns/rpz/reload.flag";
>>>>>>>>>>> +
>>>>>>>>>>> +# Configuration file for all available zonefiles
>>>>>>>>>>> +# Format: index, name (unique), enabled (on/off), URL, remark
>>>>>>>>>>> +my $ZONEFILES_CONF = "${General::swroot}/dns/rpz/zonefiles.conf";
>>>>>>>>>>> +
>>>>>>>>>>> +# Configuration file for custom lists
>>>>>>>>>>> +# IDs: 0=allowlist, 1=blocklist, 2=options (allow/block enabled)
>>>>>>>>>>> +my $CUSTOMLISTS_CONF = "${General::swroot}/dns/rpz/customlists.conf";
>>>>>>>>>>> +
>>>>>>>>>>> +# Export custom lists to rpz-config
>>>>>>>>>>> +my $RPZ_ALLOWLIST = "${General::swroot}/dns/rpz/allowlist";
>>>>>>>>>>> +my $RPZ_BLOCKLIST = "${General::swroot}/dns/rpz/blocklist";
>>>>>>>>>>> +
>>>>>>>>>>> +
>>>>>>>>>>> +### Preparation ###
>>>>>>>>>>> +
>>>>>>>>>>> +# Create missing config files
>>>>>>>>>>> +unless(-f $ZONEFILES_CONF) { &General::system('touch', "$ZONEFILES_CONF"); }
>>>>>>>>>>> +unless(-f $CUSTOMLISTS_CONF) { &General::system('touch', "$CUSTOMLISTS_CONF"); }
>>>>>>>>>>> +
>>>>>>>>>>> +
>>>>>>>>>>> +## Global gui data
>>>>>>>>>>> +my $errormessage = "";
>>>>>>>>>>> +
>>>>>>>>>>> +## Global configuration data
>>>>>>>>>>> +my %zonefiles = ();
>>>>>>>>>>> +my %customlists = ();
>>>>>>>>>>> +&_zonefiles_load();
>>>>>>>>>>> +&_customlists_load();
>>>>>>>>>>> +
>>>>>>>>>>> +## Global CGI form data
>>>>>>>>>>> +my %cgiparams = ();
>>>>>>>>>>> +&Header::getcgihash(\%cgiparams);
>>>>>>>>>>> +
>>>>>>>>>>> +my $action = $cgiparams{'ACTION'} // 'NONE';
>>>>>>>>>>> +my $action_key = $cgiparams{'KEY'} // ''; # entry being edited, empty = none/new
>>>>>>>>>>> +
>>>>>>>>>>> +
>>>>>>>>>>> +###--- Process form actions ---###
>>>>>>>>>>> +
>>>>>>>>>>> +# Zonefiles action: Check whether the requested entry exists
>>>>>>>>>>> +if((substr($action, 0, 3) eq 'ZF_') && ($action_key)) {
>>>>>>>>>>> + unless(defined $zonefiles{$action_key}) {
>>>>>>>>>>> + $errormessage = &_rpz_error_tr(204, $action_key);
>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>> + }
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +## Perform actions
>>>>>>>>>>> +if($action eq 'ZF_SAVE') { ## Save new or modified zonefiles entry
>>>>>>>>>>> + if(&_action_zf_save()) {
>>>>>>>>>>> + $action = 'NONE'; # success, return to main page
>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>> + } else {
>>>>>>>>>>> + $action = 'ZF_EDIT'; # error occured, keep editing
>>>>>>>>>>> + }
>>>>>>>>>>> +
>>>>>>>>>>> +} elsif($action eq 'ZF_TOGGLE') { ## Toggle on/off
>>>>>>>>>>> + if(&_action_zf_toggle()) {
>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>> + }
>>>>>>>>>>> +
>>>>>>>>>>> +} elsif($action eq 'ZF_REMOVE') { ## Remove entry
>>>>>>>>>>> + if(&_action_zf_remove()) {
>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>> + }
>>>>>>>>>>> +
>>>>>>>>>>> +} elsif($action eq 'CL_SAVE') { ## Save custom lists
>>>>>>>>>>> + if(&_action_cl_save()) {
>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>> + }
>>>>>>>>>>> +
>>>>>>>>>>> +} elsif($action eq 'RPZ_RELOAD') { ## Reload dns configuration
>>>>>>>>>>> + if(&_action_rpz_reload()) {
>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>> + }
>>>>>>>>>>> +
>>>>>>>>>>> +} elsif($action eq 'UNB_RESTART') { ## Restart unbound service
>>>>>>>>>>> + if(&_action_unb_restart()) {
>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>> + }
>>>>>>>>>>> +
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +
>>>>>>>>>>> +###--- Start GUI ---###
>>>>>>>>>>> +
>>>>>>>>>>> +## Start http output
>>>>>>>>>>> +&Header::showhttpheaders();
>>>>>>>>>>> +
>>>>>>>>>>> +# Start HTML
>>>>>>>>>>> +&Header::openpage($Lang::tr{'rpz'}, 1, $extraHead);
>>>>>>>>>>> +&Header::openbigbox('100%', 'left', '');
>>>>>>>>>>> +
>>>>>>>>>>> +# Show error messages
>>>>>>>>>>> +if($errormessage) {
>>>>>>>>>>> + &_print_message($errormessage);
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +# Handle zonefile add/edit mode
>>>>>>>>>>> +if($action eq "ZF_EDIT") {
>>>>>>>>>>> + &_print_zonefile_editor();
>>>>>>>>>>> +
>>>>>>>>>>> + # Finalize page and exit cleanly
>>>>>>>>>>> + &Header::closebigbox();
>>>>>>>>>>> + &Header::closepage();
>>>>>>>>>>> + exit(0);
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +# Show gui elements
>>>>>>>>>>> +&_print_zonefiles();
>>>>>>>>>>> +&_print_customlists();
>>>>>>>>>>> +&_print_gui_extras();
>>>>>>>>>>> +
>>>>>>>>>>> +&Header::closebigbox();
>>>>>>>>>>> +&Header::closepage();
>>>>>>>>>>> +
>>>>>>>>>>> +###--- End of GUI ---###
>>>>>>>>>>> +
>>>>>>>>>>> +
>>>>>>>>>>> +###--- Internal configuration file functions ---###
>>>>>>>>>>> +
>>>>>>>>>>> +# Load all available zonefiles from rpz-config and the internal configuration
>>>>>>>>>>> +sub _zonefiles_load {
>>>>>>>>>>> + # Clean start
>>>>>>>>>>> + %zonefiles = ();
>>>>>>>>>>> +
>>>>>>>>>>> + # Source 1: Get the currently enabled zonefiles from rpz-config (expected format [name]=[URL])
>>>>>>>>>>> + my @enabled_files = &General::system_output('/usr/sbin/rpz-config', 'list');
>>>>>>>>>>> +
>>>>>>>>>>> + foreach my $row (@enabled_files) {
>>>>>>>>>>> + chomp($row);
>>>>>>>>>>> +
>>>>>>>>>>> + # Use regex instead of split() to skip non-matching lines
>>>>>>>>>>> + next unless($row =~ /^(\w+)=(.+)$/);
>>>>>>>>>>> + my ($name, $url) = ($1, $2);
>>>>>>>>>>> +
>>>>>>>>>>> + # Unique names are already guaranteed by rpz-config
>>>>>>>>>>> + if(&_rpz_validate_zonefile($name, $url, '', 0) == 0) {
>>>>>>>>>>> + # Populate global data hash, mark all found entries as enabled
>>>>>>>>>>> + my %entry = ('enabled' => 'on',
>>>>>>>>>>> + 'url' => $url,
>>>>>>>>>>> + 'remark' => $Lang::tr{'rpz zf imported'});
>>>>>>>>>>> +
>>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>>> + }
>>>>>>>>>>> + }
>>>>>>>>>>> +
>>>>>>>>>>> + # Source 2: Get additional data and disabled entries from configuration file
>>>>>>>>>>> + my %configured_files = ();
>>>>>>>>>>> + &General::readhasharray($ZONEFILES_CONF, \%configured_files);
>>>>>>>>>>> +
>>>>>>>>>>> + foreach my $row (values (%configured_files)) {
>>>>>>>>>>> + my ($name, $enabled, $url, $remark) = @$row;
>>>>>>>>>>> + $remark //= "";
>>>>>>>>>>> +
>>>>>>>>>>> + next unless($name);
>>>>>>>>>>> +
>>>>>>>>>>> + # Check whether this row belongs to an entry already imported from rpz-config
>>>>>>>>>>> + if(defined $zonefiles{$name}) {
>>>>>>>>>>> + # Existing entry, only merge additional data
>>>>>>>>>>> + $zonefiles{$name}{'remark'} = $remark;
>>>>>>>>>>> + } else {
>>>>>>>>>>> + # Skip entry if it is marked as enabled but not found by rpz-config. It was then deleted manually
>>>>>>>>>>> + if($enabled ne 'on') {
>>>>>>>>>>> + # Populate global data hash
>>>>>>>>>>> + my %entry = ('enabled' => 'off',
>>>>>>>>>>> + 'url' => $url // "",
>>>>>>>>>>> + 'remark' => $remark);
>>>>>>>>>>> +
>>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>>> + }
>>>>>>>>>>> + }
>>>>>>>>>>> + }
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +# Save internal zonefiles configuration
>>>>>>>>>>> +sub _zonefiles_save_conf {
>>>>>>>>>>> + my $index = 0;
>>>>>>>>>>> + my %export = ();
>>>>>>>>>>> +
>>>>>>>>>>> + # Loop trough all zonefiles and create "hasharray" type export
>>>>>>>>>>> + foreach my $name (keys %zonefiles) {
>>>>>>>>>>> + my @entry = ($name,
>>>>>>>>>>> + $zonefiles{$name}{'enabled'},
>>>>>>>>>>> + $zonefiles{$name}{'url'},
>>>>>>>>>>> + $zonefiles{$name}{'remark'});
>>>>>>>>>>> +
>>>>>>>>>>> + $export{$index++} = \@entry;
>>>>>>>>>>> + }
>>>>>>>>>>> +
>>>>>>>>>>> + &General::writehasharray($ZONEFILES_CONF, \%export);
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +# Load custom lists from rpz-config and the internal configuration
>>>>>>>>>>> +sub _customlists_load {
>>>>>>>>>>> + # Clean start
>>>>>>>>>>> + %customlists = ();
>>>>>>>>>>> +
>>>>>>>>>>> + # Load configuration file
>>>>>>>>>>> + my %lists_conf = ();
>>>>>>>>>>> + &General::readhasharray($CUSTOMLISTS_CONF, \%lists_conf);
>>>>>>>>>>> +
>>>>>>>>>>> + # Get list options, enabled by default to start import
>>>>>>>>>>> + $customlists{'allow'}{'enabled'} = $lists_conf{2}[0] // 'on';
>>>>>>>>>>> + $customlists{'block'}{'enabled'} = $lists_conf{2}[1] // 'on';
>>>>>>>>>>> +
>>>>>>>>>>> + # Import enabled list from rpz-config, otherwise retrieve stored or empty list from configuration file
>>>>>>>>>>> + if($customlists{'allow'}{'enabled'} eq 'on') {
>>>>>>>>>>> + &_customlist_import('allow', $RPZ_ALLOWLIST);
>>>>>>>>>>> + } else {
>>>>>>>>>>> + $customlists{'allow'}{'list'} = $lists_conf{0} // [];
>>>>>>>>>>> + }
>>>>>>>>>>> + if($customlists{'block'}{'enabled'} eq 'on') {
>>>>>>>>>>> + &_customlist_import('block', $RPZ_BLOCKLIST);
>>>>>>>>>>> + } else {
>>>>>>>>>>> + $customlists{'block'}{'list'} = $lists_conf{1} // [];
>>>>>>>>>>> + }
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +# Save internal custom lists configuration
>>>>>>>>>>> +sub _customlists_save_conf {
>>>>>>>>>>> + my %export = ();
>>>>>>>>>>> +
>>>>>>>>>>> + # Match IDs with import function
>>>>>>>>>>> + $export{0} = $customlists{'allow'}{'list'};
>>>>>>>>>>> + $export{1} = $customlists{'block'}{'list'};
>>>>>>>>>>> + $export{2} = [$customlists{'allow'}{'enabled'}, $customlists{'block'}{'enabled'}];
>>>>>>>>>>> +
>>>>>>>>>>> + &General::writehasharray($CUSTOMLISTS_CONF, \%export);
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +# Import a custom list from plain file, returns empty list if file is missing
>>>>>>>>>>> +sub _customlist_import {
>>>>>>>>>>> + my ($listname, $filename) = @_;
>>>>>>>>>>> + my @list = ();
>>>>>>>>>>> +
>>>>>>>>>>> + # File exists, load and check all lines
>>>>>>>>>>> + if(-f $filename) {
>>>>>>>>>>> + open(my $FH, '<', $filename) or die "Can't read $filename: $!";
>>>>>>>>>>> + while(my $line = <$FH>) {
>>>>>>>>>>> + chomp($line);
>>>>>>>>>>> + push(@list, $line);
>>>>>>>>>>> + }
>>>>>>>>>>> + close($FH);
>>>>>>>>>>> +
>>>>>>>>>>> + # Clean up imported data
>>>>>>>>>>> + &_rpz_validate_customlist(\@list, 1);
>>>>>>>>>>> + }
>>>>>>>>>>> +
>>>>>>>>>>> + $customlists{$listname}{'list'} = \@list;
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +# Export a custom list to plain file or clear file if list is disabled
>>>>>>>>>>> +sub _customlist_export {
>>>>>>>>>>> + my ($listname, $filename) = @_;
>>>>>>>>>>> + return unless(defined $customlists{$listname});
>>>>>>>>>>> +
>>>>>>>>>>> + # Write enabled domain list to file, otherwise save empty file
>>>>>>>>>>> + open(my $FH, '>', $filename) or die "Can't write $filename: $!";
>>>>>>>>>>> +
>>>>>>>>>>> + if($customlists{$listname}{'enabled'} eq 'on') {
>>>>>>>>>>> + foreach my $line (@{$customlists{$listname}{'list'}}) {
>>>>>>>>>>> + print $FH "$line\n";
>>>>>>>>>>> + }
>>>>>>>>>>> + } else {
>>>>>>>>>>> + print $FH "; Note: This list is currently disabled by $ENV{'SCRIPT_NAME'}\n";
>>>>>>>>>>> + }
>>>>>>>>>>> +
>>>>>>>>>>> + close($FH);
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +
>>>>>>>>>>> +###--- Internal gui functions ---###
>>>>>>>>>>> +
>>>>>>>>>>> +# Show simple message box
>>>>>>>>>>> +sub _print_message {
>>>>>>>>>>> + my ($message, $title) = @_;
>>>>>>>>>>> + $title ||= $Lang::tr{'error messages'};
>>>>>>>>>>> +
>>>>>>>>>>> + &Header::openbox('100%', 'left', $title);
>>>>>>>>>>> + print "<span>$message</span>";
>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +# Show all zone files and related gui elements
>>>>>>>>>>> +sub _print_zonefiles {
>>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf'});
>>>>>>>>>>> +
>>>>>>>>>>> + print <<END
>>>>>>>>>>> +<table class="tbl" width="100%">
>>>>>>>>>>> + <tr>
>>>>>>>>>>> + <th>$Lang::tr{'name'}</th>
>>>>>>>>>>> + <th>URL</th>
>>>>>>>>>>> + <th>$Lang::tr{'remark'}</th>
>>>>>>>>>>> + <th colspan="3">$Lang::tr{'action'}</th>
>>>>>>>>>>> + </tr>
>>>>>>>>>>> +END
>>>>>>>>>>> +;
>>>>>>>>>>> +
>>>>>>>>>>> + # Sort zonefiles by name and loop trough all entries
>>>>>>>>>>> + foreach my $name (sort keys %zonefiles) {
>>>>>>>>>>> +
>>>>>>>>>>> + # Toggle button label translation
>>>>>>>>>>> + my $toggle_tr = ($zonefiles{$name}{'enabled'} eq 'on') ? $Lang::tr{'click to disable'} : $Lang::tr{'click to enable'};
>>>>>>>>>>> +
>>>>>>>>>>> + print <<END
>>>>>>>>>>> + <tr>
>>>>>>>>>>> + <td>$name</td>
>>>>>>>>>>> + <td>$zonefiles{$name}{'url'}</td>
>>>>>>>>>>> + <td>$zonefiles{$name}{'remark'}</td>
>>>>>>>>>>> +
>>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_TOGGLE">
>>>>>>>>>>> + <input type="image" src="/images/$zonefiles{$name}{'enabled'}.gif" title="$toggle_tr" alt="$toggle_tr">
>>>>>>>>>>> + </form>
>>>>>>>>>>> + </td>
>>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_EDIT">
>>>>>>>>>>> + <input type="image" src="/images/edit.gif" title="$Lang::tr{'edit'}" alt="$Lang::tr{'edit'}">
>>>>>>>>>>> + </form>
>>>>>>>>>>> + </td>
>>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_REMOVE">
>>>>>>>>>>> + <input type="image" src="/images/delete.gif" title="$Lang::tr{'remove'}" alt="$Lang::tr{'remove'}">
>>>>>>>>>>> + </form>
>>>>>>>>>>> + </td>
>>>>>>>>>>> + </tr>
>>>>>>>>>>> +END
>>>>>>>>>>> +;
>>>>>>>>>>> + }
>>>>>>>>>>> +
>>>>>>>>>>> + # Disable reload button if not needed
>>>>>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>>>>>> +
>>>>>>>>>>> + print <<END
>>>>>>>>>>> +</table>
>>>>>>>>>>> +
>>>>>>>>>>> +<div class="right">
>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>> + <input type="hidden" name="KEY" value="">
>>>>>>>>>>> + <button type="submit" name="ACTION" value="ZF_EDIT">$Lang::tr{'add'}</button>
>>>>>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>>>>>> + </form>
>>>>>>>>>>> +</div>
>>>>>>>>>>> +END
>>>>>>>>>>> +;
>>>>>>>>>>> +
>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +# Show zonefiles entry editor
>>>>>>>>>>> +sub _print_zonefile_editor {
>>>>>>>>>>> +
>>>>>>>>>>> + # Key specified: Edit existing entry
>>>>>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>>>>>> + # Load data to be edited, but don't override already present values (allows user to edit after error)
>>>>>>>>>>> + $cgiparams{'ZF_NAME'} //= $action_key;
>>>>>>>>>>> + $cgiparams{'ZF_URL'} //= $zonefiles{$action_key}{'url'};
>>>>>>>>>>> + $cgiparams{'ZF_REMARK'} //= $zonefiles{$action_key}{'remark'};
>>>>>>>>>>> + }
>>>>>>>>>>> +
>>>>>>>>>>> + # Fallback to empty form
>>>>>>>>>>> + $cgiparams{'ZF_NAME'} //= "";
>>>>>>>>>>> + $cgiparams{'ZF_URL'} //= "";
>>>>>>>>>>> + $cgiparams{'ZF_REMARK'} //= "";
>>>>>>>>>>> +
>>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf editor'});
>>>>>>>>>>> +
>>>>>>>>>>> + print <<END
>>>>>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>> +<input type="hidden" name="KEY" value="$action_key">
>>>>>>>>>>> +<table width="100%">
>>>>>>>>>>> + <tr>
>>>>>>>>>>> + <td width="20%">$Lang::tr{'name'}: <img src="/blob.gif" alt="*"></td>
>>>>>>>>>>> + <td><input type="text" name="ZF_NAME" value="$cgiparams{'ZF_NAME'}" size="40" maxlength="32" title="$Lang::tr{'rpz zf remark info'}" pattern="[a-zA-Z0-9_]{1,32}" required></td>
>>>>>>>>>>> + </tr>
>>>>>>>>>>> + <tr>
>>>>>>>>>>> + <td width="20%">URL: <img src="/blob.gif" alt="*"></td>
>>>>>>>>>>> + <td><input type="url" name="ZF_URL" value="$cgiparams{'ZF_URL'}" size="40" maxlength="128" required></td>
>>>>>>>>>>> + </tr>
>>>>>>>>>>> + <tr>
>>>>>>>>>>> + <td width="20%">$Lang::tr{'remark'}:</td>
>>>>>>>>>>> + <td><input type="text" name="ZF_REMARK" value="$cgiparams{'ZF_REMARK'}" size="40" maxlength="32"></td>
>>>>>>>>>>> + </tr>
>>>>>>>>>>> + <tr>
>>>>>>>>>>> + <td colspan="2"><hr></td>
>>>>>>>>>>> + </tr>
>>>>>>>>>>> + <tr>
>>>>>>>>>>> + <td width="55%"><img src="/blob.gif" alt="*"> $Lang::tr{'required field'}</td>
>>>>>>>>>>> + <td align="right"><button type="submit" name="ACTION" value="ZF_SAVE">$Lang::tr{'save'}</button></td>
>>>>>>>>>>> + </tr>
>>>>>>>>>>> +</table>
>>>>>>>>>>> +</form>
>>>>>>>>>>> +
>>>>>>>>>>> +<div class="right">
>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>> + <button type="submit" name="ACTION" value="NONE">$Lang::tr{'back'}</button>
>>>>>>>>>>> + </form>
>>>>>>>>>>> +</div>
>>>>>>>>>>> +END
>>>>>>>>>>> +;
>>>>>>>>>>> +
>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +# Show custom allow/block files and related gui elements
>>>>>>>>>>> +sub _print_customlists {
>>>>>>>>>>> +
>>>>>>>>>>> + # Load lists from config, unless they are currently being edited
>>>>>>>>>>> + if($action ne 'CL_SAVE') {
>>>>>>>>>>> + $cgiparams{'ALLOW_LIST'} = join("\n", @{$customlists{'allow'}{'list'}});
>>>>>>>>>>> + $cgiparams{'BLOCK_LIST'} = join("\n", @{$customlists{'block'}{'list'}});
>>>>>>>>>>> +
>>>>>>>>>>> + $cgiparams{'ALLOW_ENABLED'} = ($customlists{'allow'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>>>>>> + $cgiparams{'BLOCK_ENABLED'} = ($customlists{'block'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>>>>>> + }
>>>>>>>>>>> +
>>>>>>>>>>> + # Fallback to empty form
>>>>>>>>>>> + $cgiparams{'ALLOW_LIST'} //= "";
>>>>>>>>>>> + $cgiparams{'BLOCK_LIST'} //= "";
>>>>>>>>>>> +
>>>>>>>>>>> + # HTML checkboxes, unchecked = no or undef value in POST data
>>>>>>>>>>> + my %checked = ();
>>>>>>>>>>> + $checked{'ALLOW_ENABLED'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? " checked" : "";
>>>>>>>>>>> + $checked{'BLOCK_ENABLED'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? " checked" : "";
>>>>>>>>>>> +
>>>>>>>>>>> + # Disable reload button if not needed
>>>>>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>>>>>> +
>>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz cl'});
>>>>>>>>>>> +
>>>>>>>>>>> + print <<END
>>>>>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>> +<table width="100%">
>>>>>>>>>>> + <tr>
>>>>>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl allow'}</b><br>$Lang::tr{'rpz cl allow info'}</td>
>>>>>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl block'}</b><br>$Lang::tr{'rpz cl block info'}</td>
>>>>>>>>>>> + </tr>
>>>>>>>>>>> + <tr>
>>>>>>>>>>> + <td colspan="2"><textarea name="ALLOW_LIST" class="domainlist" cols="45">
>>>>>>>>>>> +$cgiparams{'ALLOW_LIST'}</textarea></td>
>>>>>>>>>>> + <td colspan="2"><textarea name="BLOCK_LIST" class="domainlist" cols="45">
>>>>>>>>>>> +$cgiparams{'BLOCK_LIST'}</textarea></td>
>>>>>>>>>>> + </tr>
>>>>>>>>>>> + <tr>
>>>>>>>>>>> + <td><label for="allow_enabled">$Lang::tr{'rpz cl allow enable'}</label></td>
>>>>>>>>>>> + <td width="15%"><input type="checkbox" name="ALLOW_ENABLED" id="allow_enabled"$checked{'ALLOW_ENABLED'}></td>
>>>>>>>>>>> + <td><label for="block_enabled">$Lang::tr{'rpz cl block enable'}</label></td>
>>>>>>>>>>> + <td width="15%"><input type="checkbox" name="BLOCK_ENABLED" id="block_enabled"$checked{'BLOCK_ENABLED'}></td>
>>>>>>>>>>> + </tr>
>>>>>>>>>>> + <tr>
>>>>>>>>>>> + <td colspan="4"><hr></td>
>>>>>>>>>>> + </tr>
>>>>>>>>>>> + <tr>
>>>>>>>>>>> + <td align="right" colspan="4">
>>>>>>>>>>> + <button type="submit" name="ACTION" value="CL_SAVE">$Lang::tr{'save'}</button>
>>>>>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>>>>>> + </td>
>>>>>>>>>>> +</table>
>>>>>>>>>>> +</form>
>>>>>>>>>>> +END
>>>>>>>>>>> +;
>>>>>>>>>>> +
>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +# Output javascript and extra gui elements
>>>>>>>>>>> +sub _print_gui_extras {
>>>>>>>>>>> +
>>>>>>>>>>> + # Apply/Restart button modifier key handler
>>>>>>>>>>> + if(&_rpz_needs_reload()) {
>>>>>>>>>>> + print <<END
>>>>>>>>>>> +<script>
>>>>>>>>>>> + // Commit modifier key handler
>>>>>>>>>>> + (function(jq, document) {
>>>>>>>>>>> + var keyEventsOn = false; // Keyboard events attached
>>>>>>>>>>> + var keyModify = false; // Modifier key pressed
>>>>>>>>>>> + var mouseHover = false; // Mouse over commit button
>>>>>>>>>>> + var btnModified = false; // Button modified to "Restart"
>>>>>>>>>>> +
>>>>>>>>>>> + // Document-level key events, enable only while cursor is over button
>>>>>>>>>>> + function attachKeyEvents() {
>>>>>>>>>>> + if(keyEventsOn) {
>>>>>>>>>>> + return;
>>>>>>>>>>> + }
>>>>>>>>>>> + keyEventsOn = true;
>>>>>>>>>>> +
>>>>>>>>>>> + jq(document).on("keydown.rpz", function(event) {
>>>>>>>>>>> + if((!keyModify) && event.shiftKey) {
>>>>>>>>>>> + keyModify = true;
>>>>>>>>>>> + handleModify();
>>>>>>>>>>> + }
>>>>>>>>>>> + });
>>>>>>>>>>> + jq(document).on("keyup.rpz", function(event) {
>>>>>>>>>>> + if(keyModify && (!event.shiftKey)) {
>>>>>>>>>>> + keyModify = false;
>>>>>>>>>>> + handleModify();
>>>>>>>>>>> + }
>>>>>>>>>>> + });
>>>>>>>>>>> + }
>>>>>>>>>>> + function removeKeyEvents() {
>>>>>>>>>>> + keyModify = false;
>>>>>>>>>>> + if(keyEventsOn) {
>>>>>>>>>>> + jq(document).off("keydown.rpz keyup.rpz");
>>>>>>>>>>> + keyEventsOn = false;
>>>>>>>>>>> + }
>>>>>>>>>>> + }
>>>>>>>>>>> +
>>>>>>>>>>> + // Attach mouse hover events to commit buttons
>>>>>>>>>>> + function attachMouseEvents() {
>>>>>>>>>>> + jq("button.commit").on("mouseenter", function(event) {
>>>>>>>>>>> + if(!mouseHover) {
>>>>>>>>>>> + mouseHover = true;
>>>>>>>>>>> + attachKeyEvents();
>>>>>>>>>>> + // Handle already pressed key
>>>>>>>>>>> + keyModify = !!(event.shiftKey);
>>>>>>>>>>> + handleModify();
>>>>>>>>>>> + }
>>>>>>>>>>> + });
>>>>>>>>>>> +
>>>>>>>>>>> + // Cursor moved away: Disable key listener to minimize events
>>>>>>>>>>> + jq("button.commit").on("mouseleave", function() {
>>>>>>>>>>> + if(mouseHover) {
>>>>>>>>>>> + mouseHover = false;
>>>>>>>>>>> + removeKeyEvents();
>>>>>>>>>>> + handleModify();
>>>>>>>>>>> + }
>>>>>>>>>>> + });
>>>>>>>>>>> + }
>>>>>>>>>>> +
>>>>>>>>>>> + // Modify commit button
>>>>>>>>>>> + function handleModify() {
>>>>>>>>>>> + let modify = mouseHover && keyModify;
>>>>>>>>>>> + if(btnModified != modify) {
>>>>>>>>>>> + if(modify) {
>>>>>>>>>>> + jq("button.commit").text("$Lang::tr{'restart'}").val("UNB_RESTART");
>>>>>>>>>>> + } else {
>>>>>>>>>>> + jq("button.commit").text("$Lang::tr{'rpz apply'}").val("RPZ_RELOAD");
>>>>>>>>>>> + }
>>>>>>>>>>> + btnModified = modify;
>>>>>>>>>>> + }
>>>>>>>>>>> + }
>>>>>>>>>>> +
>>>>>>>>>>> + // jQuery DOM ready
>>>>>>>>>>> + jq(function() {
>>>>>>>>>>> + attachMouseEvents();
>>>>>>>>>>> + });
>>>>>>>>>>> + })(jQuery, document);
>>>>>>>>>>> +</script>
>>>>>>>>>>> +END
>>>>>>>>>>> +;
>>>>>>>>>>> + } # End of modifier key handler
>>>>>>>>>>> +
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +
>>>>>>>>>>> +###--- Internal action processing functions ---###
>>>>>>>>>>> +
>>>>>>>>>>> +# Toggle zonefile on/off
>>>>>>>>>>> +sub _action_zf_toggle {
>>>>>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>>>>>> +
>>>>>>>>>>> + my $result = 0;
>>>>>>>>>>> + my $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>>>>>> +
>>>>>>>>>>> + # Perform toggle action
>>>>>>>>>>> + if($enabled eq 'on') {
>>>>>>>>>>> + $enabled = 'off';
>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>>>>>> + } else {
>>>>>>>>>>> + $enabled = 'on';
>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $action_key, $zonefiles{$action_key}{'url'}, '--no-reload');
>>>>>>>>>>> + }
>>>>>>>>>>> +
>>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>> +
>>>>>>>>>>> + # Save changes
>>>>>>>>>>> + $zonefiles{$action_key}{'enabled'} = $enabled;
>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>> +
>>>>>>>>>>> + return 1;
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +# Remove zonefile
>>>>>>>>>>> +sub _action_zf_remove {
>>>>>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>>>>>> +
>>>>>>>>>>> + # Remove from rpz-config if currently active
>>>>>>>>>>> + if($zonefiles{$action_key}{'enabled'} eq 'on') {
>>>>>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>>>>>> +
>>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>> + }
>>>>>>>>>>> +
>>>>>>>>>>> + # Remove from data hash and save changes
>>>>>>>>>>> + delete $zonefiles{$action_key};
>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>> +
>>>>>>>>>>> + # Clear action_key, as the entry is now removed entirely
>>>>>>>>>>> + $action_key = "";
>>>>>>>>>>> +
>>>>>>>>>>> + return 1;
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +# Create or update zonefile entry
>>>>>>>>>>> +# Returns undef if gui needs to stay in editor mode
>>>>>>>>>>> +sub _action_zf_save {
>>>>>>>>>>> + my $result = 0;
>>>>>>>>>>> +
>>>>>>>>>>> + my $name = $cgiparams{'ZF_NAME'} // "";
>>>>>>>>>>> + my $url = $cgiparams{'ZF_URL'} // "";
>>>>>>>>>>> + my $remark = $cgiparams{'ZF_REMARK'} // "";
>>>>>>>>>>> + my $enabled = 'on'; # Enable new entries by default
>>>>>>>>>>> +
>>>>>>>>>>> + # Note on variables:
>>>>>>>>>>> + # name = unique key, will be used to address the entry
>>>>>>>>>>> + # action_key = name of the entry being edited, empty for new entry
>>>>>>>>>>> +
>>>>>>>>>>> + # Only check for unique name if it changed
>>>>>>>>>>> + # (this also checks new entries because the action_key is empty in this case)
>>>>>>>>>>> + $result = &_rpz_validate_zonefile($name, $url, $remark, (lc($name) ne lc($action_key)));
>>>>>>>>>>> + return unless &_rpz_check_result($result, 0);
>>>>>>>>>>> +
>>>>>>>>>>> + # Edit existing entry: Determine what was changed
>>>>>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>>>>>> + # Name und URL remain unchanged, only save remark and finish
>>>>>>>>>>> + if(($name eq $action_key) && ($url eq $zonefiles{$action_key}{'url'})) {
>>>>>>>>>>> + $zonefiles{$action_key}{'remark'} = $remark;
>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>> +
>>>>>>>>>>> + return 1;
>>>>>>>>>>> + }
>>>>>>>>>>> +
>>>>>>>>>>> + # Entry was changed and needs to be recreated, preserve status
>>>>>>>>>>> + $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>>>>>> +
>>>>>>>>>>> + # Remove from rpz-config
>>>>>>>>>>> + return unless &_action_zf_remove();
>>>>>>>>>>> + }
>>>>>>>>>>> +
>>>>>>>>>>> + # Add new entry to rpz-config
>>>>>>>>>>> + if($enabled eq 'on') {
>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $name, $url, '--no-reload');
>>>>>>>>>>> +
>>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>> + }
>>>>>>>>>>> +
>>>>>>>>>>> + # Add to global data hash and save changes
>>>>>>>>>>> + my %entry = ('enabled' => $enabled,
>>>>>>>>>>> + 'url' => $url,
>>>>>>>>>>> + 'remark' => $remark);
>>>>>>>>>>> +
>>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>> +
>>>>>>>>>>> + return 1;
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +# Save custom lists
>>>>>>>>>>> +sub _action_cl_save {
>>>>>>>>>>> + return unless((defined $cgiparams{'ALLOW_LIST'}) && (defined $cgiparams{'BLOCK_LIST'}));
>>>>>>>>>>> +
>>>>>>>>>>> + my $result = 0;
>>>>>>>>>>> +
>>>>>>>>>>> + my @allowlist = split(/\R/, $cgiparams{'ALLOW_LIST'});
>>>>>>>>>>> + my @blocklist = split(/\R/, $cgiparams{'BLOCK_LIST'});
>>>>>>>>>>> +
>>>>>>>>>>> + # Validate lists
>>>>>>>>>>> + $result = &_rpz_validate_customlist(\@allowlist);
>>>>>>>>>>> + if($result != 0) {
>>>>>>>>>>> + $errormessage = &_rpz_error_tr(202, $result);
>>>>>>>>>>> + return;
>>>>>>>>>>> + }
>>>>>>>>>>> + $result = &_rpz_validate_customlist(\@blocklist);
>>>>>>>>>>> + if($result != 0) {
>>>>>>>>>>> + $errormessage = &_rpz_error_tr(203, $result);
>>>>>>>>>>> + return;
>>>>>>>>>>> + }
>>>>>>>>>>> +
>>>>>>>>>>> + # Add to global data hash and save changes
>>>>>>>>>>> + $customlists{'allow'}{'list'} = \@allowlist;
>>>>>>>>>>> + $customlists{'block'}{'list'} = \@blocklist;
>>>>>>>>>>> + $customlists{'allow'}{'enabled'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? 'on' : 'off';
>>>>>>>>>>> + $customlists{'block'}{'enabled'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? 'on' : 'off';
>>>>>>>>>>> +
>>>>>>>>>>> + &_customlists_save_conf();
>>>>>>>>>>> + &_customlist_export('allow', $RPZ_ALLOWLIST);
>>>>>>>>>>> + &_customlist_export('block', $RPZ_BLOCKLIST);
>>>>>>>>>>> +
>>>>>>>>>>> + # Make new lists, request service reload on success
>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-make', 'allowblock', '--no-reload');
>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>> +
>>>>>>>>>>> + return 1;
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +# Trigger rpz-config reload
>>>>>>>>>>> +sub _action_rpz_reload {
>>>>>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>>>>>> +
>>>>>>>>>>> + # Immediately clear flag to prevent multiple reloads
>>>>>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>>>>>> + }
>>>>>>>>>>> +
>>>>>>>>>>> + # Perform reload, recreate reload flag on error to enable retry
>>>>>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'reload');
>>>>>>>>>>> + if(not &_rpz_check_result($result, 0)) {
>>>>>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>>>>>> + return;
>>>>>>>>>>> + }
>>>>>>>>>>> +
>>>>>>>>>>> + return 1;
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +# Trigger unbound restart
>>>>>>>>>>> +sub _action_unb_restart {
>>>>>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>>>>>> +
>>>>>>>>>>> + # Immediately clear flag to prevent multiple restarts
>>>>>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>>>>>> + }
>>>>>>>>>>> +
>>>>>>>>>>> + # Perform restart, unboundctrl always exits zero
>>>>>>>>>>> + &General::system('/usr/local/bin/unboundctrl', 'restart');
>>>>>>>>>>> +
>>>>>>>>>>> + return 1;
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +
>>>>>>>>>>> +###--- Internal rpz-config functions ---###
>>>>>>>>>>> +
>>>>>>>>>>> +# Translate rpz-config exitcodes and messages
>>>>>>>>>>> +# 100-199: rpz-config, 200-299: webgui
>>>>>>>>>>> +sub _rpz_error_tr {
>>>>>>>>>>> + my ($error, $append) = @_;
>>>>>>>>>>> + $append //= '';
>>>>>>>>>>> +
>>>>>>>>>>> + # Translate numeric exit codes
>>>>>>>>>>> + if(looks_like_number($error)) {
>>>>>>>>>>> + if(defined $Lang::tr{"rpz exitcode $error"}) {
>>>>>>>>>>> + $error = $Lang::tr{"rpz exitcode $error"};
>>>>>>>>>>> + }
>>>>>>>>>>> + }
>>>>>>>>>>> +
>>>>>>>>>>> + return "RPZ $Lang::tr{'error'}: $error" . &Header::escape($append);
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +# Check result of rpz-config system call, request reload on success
>>>>>>>>>>> +sub _rpz_check_result {
>>>>>>>>>>> + my ($result, $request_reload) = @_;
>>>>>>>>>>> + $request_reload //= 0;
>>>>>>>>>>> +
>>>>>>>>>>> + # exitcode 0 = success
>>>>>>>>>>> + if($result != 0) {
>>>>>>>>>>> + $errormessage = &_rpz_error_tr($result);
>>>>>>>>>>> + return;
>>>>>>>>>>> + }
>>>>>>>>>>> +
>>>>>>>>>>> + # Set reload flag
>>>>>>>>>>> + if($request_reload) {
>>>>>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>>>>>> + }
>>>>>>>>>>> +
>>>>>>>>>>> + return 1;
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +# Test whether reload flag is set
>>>>>>>>>>> +sub _rpz_needs_reload {
>>>>>>>>>>> + return (-f $RPZ_RELOAD_FLAG);
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +# Validate a zonefile entry, returns rpz-config exitcode on failure. Use _rpz_check_result to verify.
>>>>>>>>>>> +# unique = check for unique name
>>>>>>>>>>> +sub _rpz_validate_zonefile {
>>>>>>>>>>> + my ($name, $url, $remark, $unique) = @_;
>>>>>>>>>>> + $unique //= 1;
>>>>>>>>>>> +
>>>>>>>>>>> + unless($name =~ /^[a-zA-Z0-9_]{1,32}$/) {
>>>>>>>>>>> + return 101;
>>>>>>>>>>> + }
>>>>>>>>>>> + unless($url =~ /^[\w+\.:;\/\\&@#%?=\-~|!]{1,128}$/) {
>>>>>>>>>>> + return 105;
>>>>>>>>>>> + }
>>>>>>>>>>> + unless($remark =~ /^[\w \-()\.:;*\/\\?!&=]{0,32}$/) {
>>>>>>>>>>> + return 201;
>>>>>>>>>>> + }
>>>>>>>>>>> +
>>>>>>>>>>> + # Check against already existing names
>>>>>>>>>>> + if($unique) {
>>>>>>>>>>> + foreach my $existing (keys %zonefiles) {
>>>>>>>>>>> + if(lc($name) eq lc($existing)) {
>>>>>>>>>>> + return 104;
>>>>>>>>>>> + }
>>>>>>>>>>> + }
>>>>>>>>>>> + }
>>>>>>>>>>> +
>>>>>>>>>>> + return 0;
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +# Validate a custom list, returns number of rejected line on failure. Check for non-zero results.
>>>>>>>>>>> +# listref = array reference, cleanup = remove invalid entries instead of returning an error
>>>>>>>>>>> +sub _rpz_validate_customlist {
>>>>>>>>>>> + my ($listref, $cleanup) = @_;
>>>>>>>>>>> + $cleanup //= 0;
>>>>>>>>>>> +
>>>>>>>>>>> + foreach my $index (reverse 0..$#{$listref}) {
>>>>>>>>>>> + my $row = @$listref[$index];
>>>>>>>>>>> + next unless($row); # Skip/allow empty lines
>>>>>>>>>>> +
>>>>>>>>>>> + # Reject/remove everything besides wildcard domains and remarks
>>>>>>>>>>> + if((not &General::validwildcarddomainname($row)) && (not $row =~ /^;[\w \-()\.:;*\/\\?!&=]*$/)) {
>>>>>>>>>>> + unless($cleanup) {
>>>>>>>>>>> + # +1 for user friendly line number and to ensure non-zero exitcode
>>>>>>>>>>> + return $index + 1;
>>>>>>>>>>> + }
>>>>>>>>>>> +
>>>>>>>>>>> + # Remove current row
>>>>>>>>>>> + splice(@$listref, $index, 1);
>>>>>>>>>>> + }
>>>>>>>>>>> + }
>>>>>>>>>>> +
>>>>>>>>>>> + return 0;
>>>>>>>>>>> +}
>>>>>>>>>>> +
>>>>>>>>>>> +
>>>>>>>>>>> +###--- Internal misc functions ---###
>>>>>>>>>>> +
>>>>>>>>>>> +# Send HTTP 303 redirect headers for post/request/get pattern
>>>>>>>>>>> +# (Must be sent before calling &Header::showhttpheaders())
>>>>>>>>>>> +sub _http_prg_redirect {
>>>>>>>>>>> + my $location = "https://$ENV{'SERVER_NAME'}:$ENV{'SERVER_PORT'}$ENV{'SCRIPT_NAME'}";
>>>>>>>>>>> + print "Status: 303 See Other\n";
>>>>>>>>>>> + print "Location: $location\n";
>>>>>>>>>>> +}
>>>>>>>>>>> diff --git a/lfs/rpz b/lfs/rpz
>>>>>>>>>>> new file mode 100644
>>>>>>>>>>> index 000000000..7ddbc38e5
>>>>>>>>>>> --- /dev/null
>>>>>>>>>>> +++ b/lfs/rpz
>>>>>>>>>>> @@ -0,0 +1,96 @@
>>>>>>>>>>> +###############################################################################
>>>>>>>>>>> +# #
>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>> +# #
>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>> +# #
>>>>>>>>>>> +###############################################################################
>>>>>>>>>>> +
>>>>>>>>>>> +###############################################################################
>>>>>>>>>>> +# Definitions
>>>>>>>>>>> +###############################################################################
>>>>>>>>>>> +
>>>>>>>>>>> +include Config
>>>>>>>>>>> +
>>>>>>>>>>> +SUMMARY = response policy zone - RPZ reputation system for unbound DNS
>>>>>>>>>>> +
>>>>>>>>>>> +VER = 1.0.0
>>>>>>>>>>> +
>>>>>>>>>>> +THISAPP = rpz-$(VER)
>>>>>>>>>>> +DIR_APP = $(DIR_SRC)/$(THISAPP)
>>>>>>>>>>> +TARGET = $(DIR_INFO)/$(THISAPP)
>>>>>>>>>>> +
>>>>>>>>>>> +PROG = rpz
>>>>>>>>>>> +PAK_VER = 18
>>>>>>>>>>> +
>>>>>>>>>>> +DEPS =
>>>>>>>>>>> +
>>>>>>>>>>> +SERVICES =
>>>>>>>>>>> +
>>>>>>>>>>> +###############################################################################
>>>>>>>>>>> +# Top-level Rules
>>>>>>>>>>> +###############################################################################
>>>>>>>>>>> +
>>>>>>>>>>> +install : $(TARGET)
>>>>>>>>>>> +
>>>>>>>>>>> +check :
>>>>>>>>>>> +
>>>>>>>>>>> +download :
>>>>>>>>>>> +
>>>>>>>>>>> +b2 :
>>>>>>>>>>> +
>>>>>>>>>>> +dist:
>>>>>>>>>>> + @$(PAK)
>>>>>>>>>>> +
>>>>>>>>>>> +###############################################################################
>>>>>>>>>>> +# Installation Details
>>>>>>>>>>> +###############################################################################
>>>>>>>>>>> +
>>>>>>>>>>> +$(TARGET) :
>>>>>>>>>>> + @$(PREBUILD)
>>>>>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>>>>>> +
>>>>>>>>>>> + # RPZ scripts
>>>>>>>>>>> + install --verbose --mode=755 \
>>>>>>>>>>> + $(DIR_CONF)/rpz/{rpz-config,rpz-metrics,rpz-sleep,rpz-make,rpz-functions} \
>>>>>>>>>>> + --target-directory=/usr/sbin
>>>>>>>>>>> +
>>>>>>>>>>> + # RPZ config files
>>>>>>>>>>> + mkdir -pv /etc/unbound/local.d
>>>>>>>>>>> + install --verbose --mode=644 --owner=nobody --group=nobody \
>>>>>>>>>>> + $(DIR_CONF)/rpz/00-rpz.conf \
>>>>>>>>>>> + --target-directory=/etc/unbound/local.d
>>>>>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/local.d
>>>>>>>>>>> +
>>>>>>>>>>> + # RPZ custom list files for allow and block
>>>>>>>>>>> + mkdir -pv /var/ipfire/dns/rpz
>>>>>>>>>>> + touch /var/ipfire/dns/rpz/{allowlist,blocklist}
>>>>>>>>>>> + chown --verbose --recursive nobody:nobody /var/ipfire/dns/rpz
>>>>>>>>>>> +
>>>>>>>>>>> + # RPZ zone files
>>>>>>>>>>> + # create empty RPZ config file to avoid a unbound config error
>>>>>>>>>>> + mkdir -pv /etc/unbound/zonefiles
>>>>>>>>>>> + touch /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/zonefiles
>>>>>>>>>>> +
>>>>>>>>>>> + # Install addon-specific language-files
>>>>>>>>>>> + install --verbose --mode=004 $(DIR_CONF)/rpz/rpz.*.pl \
>>>>>>>>>>> + --target-directory=/var/ipfire/addon-lang
>>>>>>>>>>> +
>>>>>>>>>>> + # Install backup definition
>>>>>>>>>>> + cp -vf $(DIR_CONF)/backup/includes/rpz /var/ipfire/backup/addons/includes/rpz
>>>>>>>>>>> +
>>>>>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>>>>>> + @$(POSTBUILD)
>>>>>>>>>>> diff --git a/make.sh b/make.sh
>>>>>>>>>>> index 827ea9e77..a77535b13 100755
>>>>>>>>>>> --- a/make.sh
>>>>>>>>>>> +++ b/make.sh
>>>>>>>>>>> @@ -390,7 +390,7 @@ prepareenv() {
>>>>>>>>>>> if [ "${free_space}" -lt "${required_space}" ]; then
>>>>>>>>>>> # Add any consumed space
>>>>>>>>>>> while read -r consumed_space path; do
>>>>>>>>>>> - (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>>>>>> + (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>>>>>> done <<< "$(du --summarize --bytes "${BUILD_DIR}" "${IMAGES_DIR}" "${LOG_DIR}" 2>/dev/null)"
>>>>>>>>>>> fi
>>>>>>>>>>>
>>>>>>>>>>> @@ -2087,6 +2087,7 @@ build_system() {
>>>>>>>>>>> lfsmake2 btrfs-progs
>>>>>>>>>>> lfsmake2 inotify-tools
>>>>>>>>>>> lfsmake2 grub-btrfs
>>>>>>>>>>> + lfsmake2 rpz
>>>>>>>>>>>
>>>>>>>>>>> lfsmake2 linux
>>>>>>>>>>> lfsmake2 rtl8812au
>>>>>>>>>>> diff --git a/src/paks/rpz/install.sh b/src/paks/rpz/install.sh
>>>>>>>>>>> new file mode 100644
>>>>>>>>>>> index 000000000..ef99bf742
>>>>>>>>>>> --- /dev/null
>>>>>>>>>>> +++ b/src/paks/rpz/install.sh
>>>>>>>>>>> @@ -0,0 +1,36 @@
>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>> +###############################################################################
>>>>>>>>>>> +# #
>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>> +# #
>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>> +# #
>>>>>>>>>>> +###############################################################################
>>>>>>>>>>> +#
>>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>>> +extract_files
>>>>>>>>>>> +restore_backup ${NAME}
>>>>>>>>>>> +
>>>>>>>>>>> +# fix user created files
>>>>>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>>>>>> + /etc/unbound/zonefiles \
>>>>>>>>>>> + /etc/unbound/local.d
>>>>>>>>>>> +
>>>>>>>>>>> +# Update Language cache
>>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>>> +
>>>>>>>>>>> +# restart unbound to load config file
>>>>>>>>>>> +/etc/init.d/unbound restart
>>>>>>>>>>> diff --git a/src/paks/rpz/uninstall.sh b/src/paks/rpz/uninstall.sh
>>>>>>>>>>> new file mode 100644
>>>>>>>>>>> index 000000000..e11427df3
>>>>>>>>>>> --- /dev/null
>>>>>>>>>>> +++ b/src/paks/rpz/uninstall.sh
>>>>>>>>>>> @@ -0,0 +1,38 @@
>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>> +###############################################################################
>>>>>>>>>>> +# #
>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>> +# #
>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>> +# #
>>>>>>>>>>> +###############################################################################
>>>>>>>>>>> +#
>>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>>> +
>>>>>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>>>>>> +/etc/init.d/unbound stop
>>>>>>>>>>> +
>>>>>>>>>>> +make_backup ${NAME}
>>>>>>>>>>> +remove_files
>>>>>>>>>>> +
>>>>>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>>>>>> +# "[1723428668] unbound-control[17117:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>>>>>> +
>>>>>>>>>>> +# Update Language cache
>>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>>> +
>>>>>>>>>>> +# start unbound to load unbound config file
>>>>>>>>>>> +/etc/init.d/unbound start
>>>>>>>>>>> diff --git a/src/paks/rpz/update.sh b/src/paks/rpz/update.sh
>>>>>>>>>>> new file mode 100644
>>>>>>>>>>> index 000000000..9bc340bc6
>>>>>>>>>>> --- /dev/null
>>>>>>>>>>> +++ b/src/paks/rpz/update.sh
>>>>>>>>>>> @@ -0,0 +1,52 @@
>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>> +###############################################################################
>>>>>>>>>>> +# #
>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>> +# #
>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>> +# #
>>>>>>>>>>> +###############################################################################
>>>>>>>>>>> +#
>>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>>> +
>>>>>>>>>>> +# from update.sh
>>>>>>>>>>> +extract_backup_includes
>>>>>>>>>>> +
>>>>>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>>>>>> +/etc/init.d/unbound stop
>>>>>>>>>>> +
>>>>>>>>>>> +# from uninstall.sh
>>>>>>>>>>> +make_backup ${NAME}
>>>>>>>>>>> +remove_files
>>>>>>>>>>> +
>>>>>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>>>>>> +# "unbound-control[nn:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>>>>>> +
>>>>>>>>>>> +# from install.sh
>>>>>>>>>>> +extract_files
>>>>>>>>>>> +restore_backup ${NAME}
>>>>>>>>>>> +
>>>>>>>>>>> +# fix user created files
>>>>>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>>>>>> + /etc/unbound/zonefiles \
>>>>>>>>>>> + /etc/unbound/local.d
>>>>>>>>>>> +
>>>>>>>>>>> +# Update Language cache
>>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>>> +
>>>>>>>>>>> +# restart unbound to load config files
>>>>>>>>>>> +/etc/init.d/unbound start
>>>>>>>>>>> --
>>>>>>>>>>> 2.39.5
>>>>>>>>>>>
>>>>>>>>> Jon
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> --
>>>>>>>>> Jon Murphy
>>>>>>>>> jon.murphy@ipfire.org
>>>>>>> Jon
>>>>>>>
>>>>>>>
>>>>>>> --
>>>>>>> Jon Murphy
>>>>>>> jon.murphy@ipfire.org
>>>>>
>>>>
>>>
>>
>
>
[-- Attachment #2: Type: text/html, Size: 202320 bytes --]
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
2025-03-24 0:00 ` Re[2]: " Jon Murphy
@ 2025-03-24 10:17 ` Michael Tremer
2025-03-24 13:33 ` Bernhard Bitsch
0 siblings, 1 reply; 30+ messages in thread
From: Michael Tremer @ 2025-03-24 10:17 UTC (permalink / raw)
To: Jon Murphy; +Cc: IPFire: Development-List
Hello Jon,
> On 24 Mar 2025, at 00:00, Jon Murphy <jon.murphy@ipfire.org> wrote:
>
> Michael,
>
> FYI - I was wrong Unbound RPZ is _not_ watching the serial number, it is watching the "refresh", the number after the serial number.
Refresh just tells the client how often to check for an update.
If that is actually being set by the list publisher, then we have another problem here, because they could put some insanely low value there and we would then DDoS their infrastructure. I think we should keep it like we have it in other places that we control how often we want to check or pull for updates.
> I understand that you don’t speak C, but you got the information from somewhere. Documentation maybe? Since that is out of date very often I like to consult the code.
> From testing. Downloading rpz files using rpz unbound, and watching what happens. If the rpz file is setup for "once per day" refresh, then it only downloads one time.
>
>
>
> However that won’t solve our problem . . . and having no cache.
> In `/etc/unbound/tuning.conf` there is `rrset-cache-size: 128m`. Are you referring to a different cache.
Naturally unbound is loading the zone into its memory which we generally call cache.
When I say cache I am thinking about persistent data storage across multiple restarts of Unbound. If I am downloading 100 MiB of RPZ lists (which is presumably still on the lower end) and I reboot my firewall, I do not want to download the same data again. We can only ever download a list *once* unless we are 100% certain that it has changed. Then we can download it once again.
> Maybe we need to implement both?
>
> Yes. There are very few AXFR list (I think only four were found). And many more HTTPS rpz files.
>
>
>
> Jon
>
>
> ------ Original Message ------
> From "Michael Tremer" <michael.tremer@ipfire.org>
> To "Jon Murphy" <jon.murphy@ipfire.org>
> Cc "IPFire: Development-List" <development@lists.ipfire.org>
> Date 3/20/2025 11:26:43 AM
> Subject Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
>
>> Hello Jon,
>> Please don’t forget to Cc the list...
>>
>>>
>>> On 19 Mar 2025, at 18:27, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>> Michael,
>>>
>>>>
>>>> Where in the code is this implemented? I cannot find anything like this:
>>>
>>> Keep in mind I am not a "C" person. Maybe in this section?:
>>> https://git.ipfire.org/?p=thirdparty/unbound.git;a=blob;f=services/authzone.c;hb=30b9cb5f813003d0a2b1c2e678652396615b1b7d#l5875
>>
>> This where the AXFR response is being handled when doing a DNS zone transfer. This code is not being called when performing a HTTP download.
>> I understand that you don’t speak C, but you got the information from somewhere. Documentation maybe? Since that is out of date very often I like to consult the code.
>>
>>>
>>> —
>>> When I was just learning about RPZ I created a separate RPZ file for testing. When I changed the SOA line with a new serial number, the RPZ file download would happen in about 5 minutes.
>>> https://people.ipfire.org/~jon/sblack-adhoc.rpz
>>
>> It might well be that the file is not being reloaded if the download matches the content that unbound already has. That would of course save some resources.
>> However that won’t solve our problem with redundant downloads and having no cache.
>>
>>>
>>> That is how I found out the SOA line is watched for a serial number change.
>>> I’ll reconfirm my findings.
>>>
>>>>
>>>>>> The second reason is that we have a lot of firewalls out there. Not all of them will enable this feature and all of the lists, but even if it is a good chunk, we will generate terabytes of traffic which put load on the infrastructure and will cost money. It simply is not what we want to do, regardless of self-hosting those lists and pulling them from somewhere else.
>>>
>>> So I understand, are you thinking of hosting RPZ AXFR (DNS zone transfer) on IPFire infrastructure?
>>
>> No, I don’t think that we can generally do this. The biggest problem is licensing as we cannot take anyones content and host it ourselves. We would re-distribute those lists and that will only work with permission of the publishers. I assume that would be too much work to actually get some useful content out there. We might limit ourselves to only those lists that are under a very permissive license. Nobody wants that.
>> From a technical point of view, DNS over TCP might not be very nice in terms of forging the transfer and so we would need TLS as well… It should work, but even if we would be able to encourage other people to publish their lists I doubt they would implement DNS over TLS for authoritative DNS. That standard is in very early stages as well.
>> As far as I can see, those vendors who offer a list as a commercial product are using DNS to distribute it (e.g. Spamhaus). Those people who have made this all a hobby are throwing the lists onto GitHub and let them handle the traffic.
>> Maybe we need to implement both?
>> -Michael
>>
>>>
>>> Jon
>>> On 3/19/25 5:35 AM, Michael Tremer wrote:
>>>>
>>>>
>>>> Hello Jon,
>>>> Where in the code is this implemented? I cannot find anything like this:
>>>> Unbound loads the entire file into memory and then starts parsing it. The only special treatment there is is to check whether the first line is a valid zone entry. It does not even have to be a SOA record.
>>>> https://git.ipfire.org/?p=thirdparty/unbound.git;a=blob;f=services/authzone.c;hb=30b9cb5f813003d0a2b1c2e678652396615b1b7d#l1188
>>>> I am also concerned that Unbound will not be able to support an upstream proxy for any downloads. The caching situation is also unclear for me, so I believe that we will be looking at writing a custom downloader that implements all these things.
>>>> -Michael
>>>>
>>>>>
>>>>> On 19 Mar 2025, at 02:58, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>> Michael,
>>>>>
>>>>>>
>>>>>> The emphasis is on the repeated downloads of the same list. That is
>>>>>
>>>>> > what cannot happen.
>>>>> The Unbound RPZ code, as installed within IPFire, watches for a change
>>>>> in the SOA line of each RPZ file. This is an example of the first few
>>>>> lines for every RPZ file.
>>>>> $TTL 300
>>>>> @ SOA localhost. root.localhost. 1742298960 43200 3600 86400 300
>>>>> NS localhost.
>>>>> ;
>>>>> ; Title: HaGeZi's Pop-Up Ads DNS Blocklist
>>>>> ; Description: Blocks annoying and malicious pop-up ads.
>>>>> If the SOA serial number changes (e.g. the 1742298960), then Unbound RPZ
>>>>> code does its thing and downloads. Otherwise there is no download.
>>>>>
>>>>>>
>>>>>> So there has to be a way to ensure that we won’t download a list again
>>>>>
>>>>> > unless it has actually changed.
>>>>> This should do what you want but I may be missing your point.
>>>>>
>>>>>>
>>>>>> DNS has a builtin functionality called AXFR. It simply does the job
>>>>>
>>>>> > for you. I was just wondering whether that was not being used.
>>>>> I need to read about AXFR/IXFR and learn a little more.
>>>>> Jon
>>>>> On 3/17/25 5:35 AM, Michael Tremer wrote:
>>>>>>
>>>>>>
>>>>>> Good Morning Jon,
>>>>>>
>>>>>>>
>>>>>>> On 16 Mar 2025, at 17:00, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>> Michael,
>>>>>>> I was reading through you response again an I want to understand this post:
>>>>>>>
>>>>>>>>
>>>>>>>> I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>>>>>>
>>>>>>> So if RPZ doesn't use HTTPS, what is it using? I am missing a key point here.
>>>>>>
>>>>>> The emphasis is on the repeated downloads of the same list. That is what cannot happen.
>>>>>> Although it might not affect a lot of people in our general user-base, there are some that have a metered connection and will pay for data by volume. Some of the lists I looked at are just under 20 MiB. Therefore we need to keep any traffic down to a minimum. The second reason is that we have a lot of firewalls out there. Not all of them will enable this feature and all of the lists, but even if it is a good chunk, we will generate terabytes of traffic which put load on the infrastructure and will cost money. It simply is not what we want to do, regardless of self-hosting those lists and pulling them from somewhere else.
>>>>>> So there has to be a way to ensure that we won’t download a list again unless it has actually changed.
>>>>>> DNS has a builtin functionality called AXFR. It simply does the job for you. I was just wondering whether that was not being used.
>>>>>> HTTPS is an option because that is simply what we use elsewhere, but extra functionality will have to be built for it.
>>>>>> -Michael
>>>>>>
>>>>>>>
>>>>>>> Jon
>>>>>>> On 2/13/25 3:34 PM, jon wrote:
>>>>>>>>
>>>>>>>>
>>>>>>>> Michael,
>>>>>>>> I’ve read through your comments a few times and I ended up with many more questions.
>>>>>>>>
>>>>>>>>>
>>>>>>>>> What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>>>>>>
>>>>>>>> To me the efforts to get new code accepted seem to have changed and it seemed easier in the past. In the past I made the Core Team aware via the Dev Mailing List and wrote a simple two or three paragraphs of "What is it? / What is the value? / Here is the code"
>>>>>>>> So in an effort to move forward: How exactly is something presented to the Core Team?
>>>>>>>> Is there an example of a recent effort that was presented that I can see as a sample? (This type of info can also be added to the Wiki)
>>>>>>>> I understand you want it this way, but I don’t know what exactly is needed. Please be specific.
>>>>>>>> Jon
>>>>>>>> PS - I am not ignoring your other comments, I am just trying to move forward and keep things simple.
>>>>>>>>
>>>>>>>>>
>>>>>>>>> On Feb 8, 2025, at 1:27 PM, Michael Tremer <michael.tremer@ipfire.org> wrote:
>>>>>>>>> Hello Jon,
>>>>>>>>> Thanks for your reply. And good that you are copying everyone into this conversation.
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> On 8 Feb 2025, at 18:41, jon <jon.murphy@ipfire.org> wrote:
>>>>>>>>>> Michael,
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort
>>>>>>>>>>
>>>>>>>>>> Yes, this has been a separate effort (a very public separate effort). Yes, as you pointed this out early on with the "proof-of-concept" and then my request for people to help test RPZ. Nothing was hidden.
>>>>>>>>>> This was done because you (and maybe others) did not have the time and I wanted to help and because I needed assistance with RPZ. I tried my best to do this without bothering you.
>>>>>>>>>
>>>>>>>>> I don’t that it is accurate that nobody wanted to help on this. The list was always open - although not every email has been replied to swiftly it is also your responsibility to raise a question again if it was missed. People here have open ears.
>>>>>>>>> It was also stated on this very list on in our documentation that working on something without involving the core team is a risky undertaking. Of course IPFire is free software and so everyone is free to fork if they wish to do so.
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>> and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls.
>>>>>>>>>>
>>>>>>>>>> You were aware many steps along the way. See your email on July 28, 2024, August 15, 2024, September 30, 2024, December 23, 2024, and January 16. My attempts to get the team involved were met with "things are busy" and sometimes silence. (Yes, I get it, people are busy.)
>>>>>>>>>> You and Adolf, Leo, Erik and Bernhard have been aware since the beginning. You mention you were aware of the "proof-of-concept". If you include those beginning posts, since Sep 2023.
>>>>>>>>>
>>>>>>>>> Yes, I am aware of a proof-of-concept that I have been running myself for a long time. I am also aware of the efforts that you have been taking.
>>>>>>>>> Yet I don’t think there has ever been any joint effort, or am I seeing that wrong?
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>> This has not been discussed . . . on our calls.
>>>>>>>>>>
>>>>>>>>>> On the July 28th you stated:
>>>>>>>>>> "We have talked about RPZ many times on the monthly call since the URL filter feature is falling more and more out of fashion. I think there is also many posts about this on the forum."
>>>>>>>>>> Please don’t insult me again by stating "you know what I mean".
>>>>>>>>>> And it has been discussed but not documented in the Monthly Meeting notes.
>>>>>>>>>
>>>>>>>>> I am not at all insulting you. I don’t want to take this down to a personal level at all. This is a public mailing list and people who read this don’t need to listen to an argument we are having. They are here for the tech inside IPFire.
>>>>>>>>> When I wrote that it has not been discussed that does not mean that we have not been touching on the topic. We have been talking about lots of things on the calls, the weather, politics, how our pets are. None of that makes it to the logs. What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>> Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation.
>>>>>>>>>>
>>>>>>>>>> Regular conversation on the Dev Mailing list is many times met with silence. I get it, people are busy.
>>>>>>>>>> And regular two-way conversation doesn’t happen on the list. At least not with me. I’d be happy to point out the posts that were met with silence.
>>>>>>>>>> Again, I get it, people are busy.
>>>>>>>>>
>>>>>>>>> And you think my emails are not being met with silence? This has nothing to do with this specific topic. This has something to do with how occupied people are and how engaged they are on certain topics. Not everyone is involved in all the things and simply will ignore emails simply based on their subject line.
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> But the "dip here to the list" were my attempts to get a conversation started. As I said, many time met with silence.
>>>>>>>>>> The only place I was not met with silence was on the Community. You have a great group of people in the Community. It is a shame you don’t want to have others help. It would reduce your workload.
>>>>>>>>>
>>>>>>>>> You should stop making statements that are not true. Who doesn’t want anyone to help?
>>>>>>>>> Not having this conversation on a Saturday evening would reduce my workload. At least it would free up time for something else. Helping with the things that are already on the go would reduce the workload of the entire team. Starting one thing at a time and finishing it is a lot better to manage than starting a hundred things and not even finish one. I can tell you that I already have a hundred things on the go.
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>> Therefore, what am I supposed to do with this email?
>>>>>>>>>>
>>>>>>>>>> To me it is beyond obvious…
>>>>>>>>>> If it isn’t what you want, then guide me with how to do this the correct way. And be specific. I am trying to help. I am trying to make things better. I am trying to do things the right way.
>>>>>>>>>
>>>>>>>>> To me it isn’t. This is yet another project that has been dumped to the list like so many before and later on everyone has left to have the team deal with the rest.
>>>>>>>>> It is a huge patch set. You explained what the vision is, but that is about it. There is no chance this will continue if this disagreement isn’t solved first. I didn’t even look at the code.
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>> I don’t want to merge code that I don’t agree with.
>>>>>>>>>>
>>>>>>>>>> I asked multiple times if you "agreed with the concept" and again, met with silence. Yes I get it, people are busy.
>>>>>>>>>
>>>>>>>>> Having support for RPZ? Yes, it was definitely on the roadmap. That I agree with.
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>> So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>>>>>>
>>>>>>>>>> You mentioned this a in the past, but for some reason you do not disclose what I dismissed. Why do you continue to make this harder, wouldn’t it not be easier to tell me what I have dismissed?
>>>>>>>>>> I have sent multiple emails trying to answer your concerns and comments. On July 28, Aug 14, Aug 22, Aug 23, Sep 30, etc.
>>>>>>>>>> I’ve gone through all of the questions you asked and I cannot find a "dismissed" item.
>>>>>>>>>
>>>>>>>>> Maybe I need to be *more clear*. I feel humoured by this.
>>>>>>>>> It is late on a Saturday and I want my dinner soon, but certainly I have stated that this should never be an add-on considering it is supposed to replace URL Filter. We should never allow people to add their own sources. I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>>>>>>
>>>>>>>>>> The maintainers of Unbound and/or RPZ?
>>>>>>>>>> The maintainers of Hagezi list, the threatfox list, the urlhaus list, etc.?
>>>>>>>>>> What else? The maintainers or the RPZ scripts? That is me. Let’s talk!
>>>>>>>>>
>>>>>>>>> You. I don’t care much about the providers of the lists.
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> See, this is where it gets confusing. There are hundreds of open source packages as part of IPFire. Pick the last five years of items added to the IPFire build. You're telling me you have "constructive conversation with the maintainers" of all of the added packages?
>>>>>>>>>
>>>>>>>>> They publish their software and they don’t care whether I am pulling it or not. They publish it with the commitment to maintain it - sometimes for better and sometimes for worse.
>>>>>>>>> You care about me pulling your code and I don’t know whether you would commit to maintain this.
>>>>>>>>> These two are very different cases.
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> Pick the IP Blocklists list (i.e., 3CORESEC, ABUSECH, DSHIELD, SPAMHAUS, etc.) or the Suricata lists (i.e.,Emergingthreats.net <http://emergingthreats.net/>,Abuse.ch <http://abuse.ch/>, etc.). So you’ve have "constructive conversation with the maintainers"?
>>>>>>>>>
>>>>>>>>> Yes, occasionally I have phone calls with a few of these providers.
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>>>>>>
>>>>>>>>>> Ha! Yes a surprise. In the beginning you seemed interested as IPFire needed a replacement for URL Filter. You asked good questions about the lists picked, asked for the value to the users, etc. And I answered the best I could.
>>>>>>>>>> You even asked: “Why is this realised as an add-on and not part of the core system?” from your Jul 28, 2024 email.
>>>>>>>>>
>>>>>>>>> Ah, so, why is the patch creating an add-on? Not that I am saying that what I say is law, but it has not been challenged either. If my input is being ignored, why should I put this to the top of my list of priorities? I am not disappointed about this, just trying to be very good with my time.
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> And on January 16, 2025 I wrote a message looking for help. And you were kind to respond quickly. So in three weeks time, since the kind response, something has changed. You went from supportive to "this".
>>>>>>>>>> So yes, I am surprised.
>>>>>>>>>
>>>>>>>>> Well, maybe I should not have replied to that email. It was clear that you were on some path that was not right, but you were not interested before in finding the right path from the beginning.
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>>>>>>
>>>>>>>>>> Be more specific, what has to change? What exactly did I dismiss?
>>>>>>>>>
>>>>>>>>> Dismissal is just my assumption. I don’t know what you actually did with my feedback. I can only see the end product that does not seem contain much of it. Repeatedly I have been pointing out that we should think before we build. I am sure a lot of hours have now gone into some code that simply does not satisfy me. And I am not not talking about the code itself, what it does is what I don’t think is right for us.
>>>>>>>>> The process is very clear for me that we should first of all think whether we want a certain feature now. Then there should be a clear roadmap for everyone to follow; tasks can be split-up as we go and hopefully then have something that is maintainable, interesting for our users and even would do us proud. This is how this should work.
>>>>>>>>> So, what has to change? I don’t think with shouting at each other, throwing patches around and making me generally unhappy is a good start.
>>>>>>>>> -Michael
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> Jon
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> On Feb 6, 2025, at 2:13 PM, Michael Tremer <michael.tremer@ipfire.org> wrote:
>>>>>>>>>>> Hello Jon,
>>>>>>>>>>> Well, here we are again with another patch regarding this feature.
>>>>>>>>>>> I cannot quite see from your email what the question is, but if this is a request to have this merged into IPFire, I am once again sorry to disappoint you.
>>>>>>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls. Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation. Therefore, what am I supposed to do with this email?
>>>>>>>>>>> I don’t want to merge code that I don’t agree with. So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>>>>>>> All the best,
>>>>>>>>>>> -Michael
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>> On 6 Feb 2025, at 16:35, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>>>>>> What is it?
>>>>>>>>>>>> Response Policy Zone (RPZ) is a mechanism to define local policies in a
>>>>>>>>>>>> standardized way and load those policies from external sources.
>>>>>>>>>>>> Bottom line: RPZ allows admins to easily block access to websites via DNS lookup.
>>>>>>>>>>>> RPZ can block websites via categories. Examples include: fake websites, annoying
>>>>>>>>>>>> pop-up ads, newly registered domains, DoH bypass sites, bad "host" services,
>>>>>>>>>>>> maliscious top level domains (e.g., *.zip, *.mov), piracy, gambling, pornography,
>>>>>>>>>>>> and more. RPZ lists come from various RPZ providers and their available
>>>>>>>>>>>> catagories.
>>>>>>>>>>>> This RPZ add-on enables the RPZ functionality by adding a couple lines in a
>>>>>>>>>>>> configuration file. This add-on simply adds configuration files and adds
>>>>>>>>>>>> scripts (config, metrics and sleep) to make RPZ easier for the admin to use.
>>>>>>>>>>>> The RPZ scripts include additional languages: German, Spanish, French, Turkish,
>>>>>>>>>>>> and Italian.
>>>>>>>>>>>> RPZ itself was release in 2010 and has been part of the IPFire build since ~2015.
>>>>>>>>>>>> Why is it needed? What is its value?
>>>>>>>>>>>> - The RPZ concept places this filtering into IPFire, our internet access
>>>>>>>>>>>> gateway, which is (should be) solely used as DNS source of the internal network.
>>>>>>>>>>>> - As most sites use HTTPS it makes it difficult to filter traffic with URL
>>>>>>>>>>>> Filter without also properly configuring conventional (non-transparent)
>>>>>>>>>>>> mode on the proxy. RPZ is a nice replacement for the URL Filter.
>>>>>>>>>>>> - No need to install and maintain an additional device like PiHole or AdBlock
>>>>>>>>>>>> browser extensions on multiple user devices.
>>>>>>>>>>>> - This is an additional layer of protection for users. Less worry someone will
>>>>>>>>>>>> click on something that gets them into trouble. And, saying this with emphasis,
>>>>>>>>>>>> the ability to do it in one place!
>>>>>>>>>>>> - Blocked sites save on unneeded traffic and can lessen the threat of malware
>>>>>>>>>>>> in advertisements
>>>>>>>>>>>> - Logging allows the admin to see the site blocked and take actions
>>>>>>>>>>>> - RPZ will be used at the home, home-office (work from home), schools,
>>>>>>>>>>>> ministerial, and at the office. Device counts are small (2-6) to medium (~80)
>>>>>>>>>>>> to mediam-large (200+).
>>>>>>>>>>>> - RPZ can block ads, popups, phishing, scammers, spyware, malware, annoying
>>>>>>>>>>>> popups, NSFW links, DOH servers, and the usual internet trash.
>>>>>>>>>>>> ------------------------------
>>>>>>>>>>>> Change Log for RPZ add-on
>>>>>>>>>>>> rpz-1.0.0-18 on 2025-02-05
>>>>>>>>>>>> - Build for approval & release as IPFire add-on
>>>>>>>>>>>> ---
>>>>>>>>>>>> rpz-beta-0.1.18-18.ipfire on 2025-02-01
>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>> - new feature: added a mod key to force a unbound restart
>>>>>>>>>>>> rpz-config and rpz-make:
>>>>>>>>>>>> - new feature: added action for unbound restart `rpz-config unbound-restart`
>>>>>>>>>>>> rpz-metrics:
>>>>>>>>>>>> - simple reformatting
>>>>>>>>>>>> - rename far right column from "last update" to "last download"
>>>>>>>>>>>> ---
>>>>>>>>>>>> rpz-beta-0.1.17-17.ipfire on 2024-12-09
>>>>>>>>>>>> rpz-make
>>>>>>>>>>>> - bug fix: corrected validation regex for wildcards like: `*.domain.com`
>>>>>>>>>>>> ---
>>>>>>>>>>>> rpz-beta-0.1.16-16.ipfire on 2024-11-18
>>>>>>>>>>>> rpz-make
>>>>>>>>>>>> - new feature: updated validation regex
>>>>>>>>>>>> - bug fix: moved validation to beginning of process. Now we validate before
>>>>>>>>>>>> creating config files.
>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>> - new feature: use CSS color variables of the main ipfire theme
>>>>>>>>>>>> - bug fix: empty zonefile remarks were stored as “undef” and caused a warning
>>>>>>>>>>>> - bug fix: HTML textarea removes the first empty line in a custom list
>>>>>>>>>>>> - thank you Leo!
>>>>>>>>>>>> ---
>>>>>>>>>>>> rpz-beta-0.1.15-15.ipfire on 2024-11-04
>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>> - new feature: added new language file for Turkish (thank you Peppe)
>>>>>>>>>>>> rpz-make
>>>>>>>>>>>> - bug fix: corrected empty allow/block list issue. An empty allow/block list
>>>>>>>>>>>> will now remove contents of allow/block.rpz files and remove unneeded
>>>>>>>>>>>> allow/block.conf file. (thank you iptom)
>>>>>>>>>>>> ---
>>>>>>>>>>>> rpz-beta-0.1.14-14.ipfire on 2024-10-29
>>>>>>>>>>>> rpz-config:
>>>>>>>>>>>> - bug fix: correct missing rpz extension. `rpz-config list` displayed URL
>>>>>>>>>>>> incorrectly (thank you Bernhard)
>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>> - bug fix: remove extra `"` in language files (thank you Bernhard)
>>>>>>>>>>>> - new feature: slightly dim "apply" button when not enabled
>>>>>>>>>>>> ---
>>>>>>>>>>>> rpz-beta-0.1.13-13.ipfire on 2024-10-27
>>>>>>>>>>>> - skipped
>>>>>>>>>>>> ---
>>>>>>>>>>>> rpz-beta-0.1.12-12.ipfire on 2024-10-21
>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>> - new feature: added new language file for French (thank you gw-ipfire)
>>>>>>>>>>>> ---
>>>>>>>>>>>> rpz-beta-0.1.11-11.ipfire on 2024-10-18
>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>> - new feature: added new language file for Italian (thank you umberto)
>>>>>>>>>>>> - new feature: added new language file for Spanish (thank you Roberto)
>>>>>>>>>>>> ---
>>>>>>>>>>>> rpz-beta-0.1.10-10.ipfire on 2024-10-15
>>>>>>>>>>>> rpz-make:
>>>>>>>>>>>> - bug fix: corrected validation error for a custom list entry (thank you siosios)
>>>>>>>>>>>> - e.g., `*.cloudflare-dns.com`
>>>>>>>>>>>> install.sh:
>>>>>>>>>>>> - bug fix: add chown to correct user created files
>>>>>>>>>>>> update.sh:
>>>>>>>>>>>> - bug fix: add chown to correct user created files (thank you siosios)
>>>>>>>>>>>> ---
>>>>>>>>>>>> rpz-beta-0.1.9-9.ipfire on 2024-10-08
>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>> - new feature: added new language file for German (thank you Leo)
>>>>>>>>>>>> - bug fix: add missing "rpz exitcode 110"
>>>>>>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>>>>>> ---
>>>>>>>>>>>> rpz-beta-0.1.8-8.ipfire on 2024-10-04
>>>>>>>>>>>> - skipped
>>>>>>>>>>>> ---
>>>>>>>>>>>> rpz-beta-0.1.7-7.ipfire on 2024-10-03
>>>>>>>>>>>> All:
>>>>>>>>>>>> - new feature: includes beta version numbers for pakfire package,
>>>>>>>>>>>> instead of only `rpz-1.0.0-1.ipfire`, for each release.
>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>> - new feature: added new WebGUI at `rpz.cgi`
>>>>>>>>>>>> - a BIG thank you to Leo Hofmann for all of his work creating the webgui!!
>>>>>>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>>>>>> rpz-make:
>>>>>>>>>>>> - new feature: validate entries in allowlist and blocklist
>>>>>>>>>>>> - new feature: add "no-reload" option for WebGUI
>>>>>>>>>>>> rpz-metrics:
>>>>>>>>>>>> - new feature: info can be sorted by name, by hit count, by line count, by
>>>>>>>>>>>> "enabled" list or all lists
>>>>>>>>>>>> backups:
>>>>>>>>>>>> - bug fix: include all files in `/var/ipfire/dns/rpz` directory in backup
>>>>>>>>>>>> update.sh:
>>>>>>>>>>>> - bug fix: corrected ownership for `/var/ipfire/dns/rpz` directory during an
>>>>>>>>>>>> update
>>>>>>>>>>>> Build:
>>>>>>>>>>>> - bug fix: `block.rpz.conf` and `block.rpz` from build. Files to be created
>>>>>>>>>>>> by `rpz-make`
>>>>>>>>>>>> WebGUI and German language file
>>>>>>>>>>>> Contribution-by: Leo-Andres Hofmann <hofmann@leo-andres.de>
>>>>>>>>>>>> Spanish language file
>>>>>>>>>>>> Contribution-by: Roberto Peña
>>>>>>>>>>>> Italian language file
>>>>>>>>>>>> Contribution-by: Umberto Parma
>>>>>>>>>>>> French language file
>>>>>>>>>>>> Contribution-by: gw-ipfire
>>>>>>>>>>>> Turkish language file
>>>>>>>>>>>> Contribution-by: Peppe Tech
>>>>>>>>>>>> Contribution-by: Bernhard Bitsch <bbitsch@ipfire.org>
>>>>>>>>>>>> Contribution-by: Erik Kapfer <erik.kapfer@ipfire.org>
>>>>>>>>>>>> Signed-off-by: Jon Murphy <jon.murphy@ipfire.org
>>>>>>>>>>>> ---
>>>>>>>>>>>> config/backup/includes/rpz | 4 +
>>>>>>>>>>>> config/cfgroot/manualpages | 1 +
>>>>>>>>>>>> config/menu/EX-rpz.menu | 6 +
>>>>>>>>>>>> config/rootfiles/common/configroot | 1 +
>>>>>>>>>>>> config/rootfiles/common/web-user-interface | 1 +
>>>>>>>>>>>> config/rootfiles/packages/rpz | 20 +
>>>>>>>>>>>> config/rpz/00-rpz.conf | 10 +
>>>>>>>>>>>> config/rpz/rpz-config | 130 +++
>>>>>>>>>>>> config/rpz/rpz-functions | 85 ++
>>>>>>>>>>>> config/rpz/rpz-make | 203 +++++
>>>>>>>>>>>> config/rpz/rpz-metrics | 170 ++++
>>>>>>>>>>>> config/rpz/rpz-sleep | 58 ++
>>>>>>>>>>>> config/rpz/rpz.de.pl | 30 +
>>>>>>>>>>>> config/rpz/rpz.en.pl | 30 +
>>>>>>>>>>>> config/rpz/rpz.es.pl | 30 +
>>>>>>>>>>>> config/rpz/rpz.fr.pl | 30 +
>>>>>>>>>>>> config/rpz/rpz.it.pl | 30 +
>>>>>>>>>>>> config/rpz/rpz.tr.pl | 30 +
>>>>>>>>>>>> html/cgi-bin/rpz.cgi | 923 +++++++++++++++++++++
>>>>>>>>>>>> lfs/rpz | 96 +++
>>>>>>>>>>>> make.sh | 3 +-
>>>>>>>>>>>> src/paks/rpz/install.sh | 36 +
>>>>>>>>>>>> src/paks/rpz/uninstall.sh | 38 +
>>>>>>>>>>>> src/paks/rpz/update.sh | 52 ++
>>>>>>>>>>>> 24 files changed, 2016 insertions(+), 1 deletion(-)
>>>>>>>>>>>> create mode 100644 config/backup/includes/rpz
>>>>>>>>>>>> create mode 100644 config/menu/EX-rpz.menu
>>>>>>>>>>>> create mode 100644 config/rootfiles/packages/rpz
>>>>>>>>>>>> create mode 100644 config/rpz/00-rpz.conf
>>>>>>>>>>>> create mode 100644 config/rpz/rpz-config
>>>>>>>>>>>> create mode 100644 config/rpz/rpz-functions
>>>>>>>>>>>> create mode 100644 config/rpz/rpz-make
>>>>>>>>>>>> create mode 100755 config/rpz/rpz-metrics
>>>>>>>>>>>> create mode 100755 config/rpz/rpz-sleep
>>>>>>>>>>>> create mode 100644 config/rpz/rpz.de.pl
>>>>>>>>>>>> create mode 100644 config/rpz/rpz.en.pl
>>>>>>>>>>>> create mode 100644 config/rpz/rpz.es.pl
>>>>>>>>>>>> create mode 100644 config/rpz/rpz.fr.pl
>>>>>>>>>>>> create mode 100644 config/rpz/rpz.it.pl
>>>>>>>>>>>> create mode 100644 config/rpz/rpz.tr.pl
>>>>>>>>>>>> create mode 100644 html/cgi-bin/rpz.cgi
>>>>>>>>>>>> create mode 100644 lfs/rpz
>>>>>>>>>>>> create mode 100644 src/paks/rpz/install.sh
>>>>>>>>>>>> create mode 100644 src/paks/rpz/uninstall.sh
>>>>>>>>>>>> create mode 100644 src/paks/rpz/update.sh
>>>>>>>>>>>> diff --git a/config/backup/includes/rpz b/config/backup/includes/rpz
>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>> index 000000000..36513e494
>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>> +++ b/config/backup/includes/rpz
>>>>>>>>>>>> @@ -0,0 +1,4 @@
>>>>>>>>>>>> +/var/ipfire/dns/rpz/*
>>>>>>>>>>>> +/etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>> +/etc/unbound/zonefiles/block.rpz
>>>>>>>>>>>> +/etc/unbound/local.d/*rpz.conf
>>>>>>>>>>>> diff --git a/config/cfgroot/manualpages b/config/cfgroot/manualpages
>>>>>>>>>>>> index 1f7e01efc..d3a48c633 100644
>>>>>>>>>>>> --- a/config/cfgroot/manualpages
>>>>>>>>>>>> +++ b/config/cfgroot/manualpages
>>>>>>>>>>>> @@ -70,6 +70,7 @@ pakfire.cgi=configuration/ipfire/pakfire
>>>>>>>>>>>> wlanap.cgi=addons/wireless
>>>>>>>>>>>> tor.cgi=addons/tor
>>>>>>>>>>>> samba.cgi=addons/samba
>>>>>>>>>>>> +rpz.cgi=addons/rpz
>>>>>>>>>>>> # Logs menu
>>>>>>>>>>>> logs.cgi/summary.dat=configuration/logs/summary
>>>>>>>>>>>> diff --git a/config/menu/EX-rpz.menu b/config/menu/EX-rpz.menu
>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>> index 000000000..2f4daf410
>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>> +++ b/config/menu/EX-rpz.menu
>>>>>>>>>>>> @@ -0,0 +1,6 @@
>>>>>>>>>>>> +$subipfire->{'20.rpz'} = {
>>>>>>>>>>>> + 'caption' => $Lang::tr{'rpz'},
>>>>>>>>>>>> + 'uri' => '/cgi-bin/rpz.cgi',
>>>>>>>>>>>> + 'title' => "RPZ",
>>>>>>>>>>>> + 'enabled' => 1,
>>>>>>>>>>>> +};
>>>>>>>>>>>> diff --git a/config/rootfiles/common/configroot b/config/rootfiles/common/configroot
>>>>>>>>>>>> index 9839eee45..b30d6aae4 100644
>>>>>>>>>>>> --- a/config/rootfiles/common/configroot
>>>>>>>>>>>> +++ b/config/rootfiles/common/configroot
>>>>>>>>>>>> @@ -120,6 +120,7 @@ var/ipfire/menu.d/70-log.menu
>>>>>>>>>>>> #var/ipfire/menu.d/EX-apcupsd.menu
>>>>>>>>>>>> #var/ipfire/menu.d/EX-guardian.menu
>>>>>>>>>>>> #var/ipfire/menu.d/EX-mympd.menu
>>>>>>>>>>>> +#var/ipfire/menu.d/EX-rpz.menu
>>>>>>>>>>>> #var/ipfire/menu.d/EX-samba.menu
>>>>>>>>>>>> #var/ipfire/menu.d/EX-tor.menu
>>>>>>>>>>>> #var/ipfire/menu.d/EX-transmission.menu
>>>>>>>>>>>> diff --git a/config/rootfiles/common/web-user-interface b/config/rootfiles/common/web-user-interface
>>>>>>>>>>>> index 816241dae..e00464076 100644
>>>>>>>>>>>> --- a/config/rootfiles/common/web-user-interface
>>>>>>>>>>>> +++ b/config/rootfiles/common/web-user-interface
>>>>>>>>>>>> @@ -69,6 +69,7 @@ srv/web/ipfire/cgi-bin/proxy.cgi
>>>>>>>>>>>> srv/web/ipfire/cgi-bin/qos.cgi
>>>>>>>>>>>> srv/web/ipfire/cgi-bin/remote.cgi
>>>>>>>>>>>> srv/web/ipfire/cgi-bin/routing.cgi
>>>>>>>>>>>> +#srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>>>>>>> #srv/web/ipfire/cgi-bin/samba.cgi
>>>>>>>>>>>> srv/web/ipfire/cgi-bin/services.cgi
>>>>>>>>>>>> srv/web/ipfire/cgi-bin/shutdown.cgi
>>>>>>>>>>>> diff --git a/config/rootfiles/packages/rpz b/config/rootfiles/packages/rpz
>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>> index 000000000..1c8663049
>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>> +++ b/config/rootfiles/packages/rpz
>>>>>>>>>>>> @@ -0,0 +1,20 @@
>>>>>>>>>>>> +etc/unbound/local.d/00-rpz.conf
>>>>>>>>>>>> +etc/unbound/zonefiles
>>>>>>>>>>>> +etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>> +usr/sbin/rpz-config
>>>>>>>>>>>> +usr/sbin/rpz-functions
>>>>>>>>>>>> +usr/sbin/rpz-make
>>>>>>>>>>>> +usr/sbin/rpz-metrics
>>>>>>>>>>>> +usr/sbin/rpz-sleep
>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.de.pl
>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.en.pl
>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.es.pl
>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.fr.pl
>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.it.pl
>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.tr.pl
>>>>>>>>>>>> +var/ipfire/backup/addons/includes/rpz
>>>>>>>>>>>> +var/ipfire/dns/rpz
>>>>>>>>>>>> +var/ipfire/dns/rpz/allowlist
>>>>>>>>>>>> +var/ipfire/dns/rpz/blocklist
>>>>>>>>>>>> +var/ipfire/menu.d/EX-rpz.menu
>>>>>>>>>>>> +srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>>>>>>> diff --git a/config/rpz/00-rpz.conf b/config/rpz/00-rpz.conf
>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>> index 000000000..f005a4f2e
>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>> +++ b/config/rpz/00-rpz.conf
>>>>>>>>>>>> @@ -0,0 +1,10 @@
>>>>>>>>>>>> +server:
>>>>>>>>>>>> + module-config: "respip validator iterator"
>>>>>>>>>>>> +
>>>>>>>>>>>> +rpz:
>>>>>>>>>>>> + name: allow.rpz
>>>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>> + rpz-action-override: passthru
>>>>>>>>>>>> + rpz-log: yes
>>>>>>>>>>>> + rpz-log-name: allow
>>>>>>>>>>>> + rpz-signal-nxdomain-ra: yes
>>>>>>>>>>>> diff --git a/config/rpz/rpz-config b/config/rpz/rpz-config
>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>> index 000000000..c72d50f9b
>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>> +++ b/config/rpz/rpz-config
>>>>>>>>>>>> @@ -0,0 +1,130 @@
>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>> +# #
>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>> +# #
>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>> +# #
>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>> +
>>>>>>>>>>>> +version="2025-01-11 - v44"
>>>>>>>>>>>> +
>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>> +
>>>>>>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>>>>>>> +
>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>> +
>>>>>>>>>>>> +tagName="unbound"
>>>>>>>>>>>> +
>>>>>>>>>>>> +rpzAction="${1}" # input RPZ action
>>>>>>>>>>>> +rpzName="${2}" # input RPZ name
>>>>>>>>>>>> +rpzURL="${3}" # input RPZ URL
>>>>>>>>>>>> +rpzOption1="${4}" # input RPZ option #1
>>>>>>>>>>>> +rpzOption2="${5}" # input RPZ option #2
>>>>>>>>>>>> +
>>>>>>>>>>>> +rpzConfig="/etc/unbound/local.d/${rpzName}.rpz.conf" # output zone conf file
>>>>>>>>>>>> +rpzFile="/etc/unbound/zonefiles/${rpzName}.rpz" # output for RPZ file
>>>>>>>>>>>> +
>>>>>>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>>>>>>> +
>>>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>>>> + case "$1" in
>>>>>>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>>>>>>> + esac
>>>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>>>> +done
>>>>>>>>>>>> +
>>>>>>>>>>>> +case "${rpzAction}" in
>>>>>>>>>>>> + # add new rpz list
>>>>>>>>>>>> + add )
>>>>>>>>>>>> + check_name "${rpzName}" # is this a valid name?
>>>>>>>>>>>> + # does this config already exist? If yes, then exit
>>>>>>>>>>>> + if [[ -f "${rpzConfig}" ]] ; then
>>>>>>>>>>>> + msg_log "error: rpz: duplicate - ${rpzConfig} already exists. exit"
>>>>>>>>>>>> + exit 104
>>>>>>>>>>>> + fi
>>>>>>>>>>>> +
>>>>>>>>>>>> + # is this a valid URL?
>>>>>>>>>>>> + regex='^https://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
>>>>>>>>>>>> + if ! [[ "${rpzURL}" =~ $regex ]] ; then
>>>>>>>>>>>> + msg_log "error: rpz: the URL is not valid: \"${rpzURL}\". exit."
>>>>>>>>>>>> + exit 105
>>>>>>>>>>>> + fi
>>>>>>>>>>>> +
>>>>>>>>>>>> + # create the zone config file
>>>>>>>>>>>> + {
>>>>>>>>>>>> + echo "rpz:"
>>>>>>>>>>>> + echo " name: ${rpzName}.rpz"
>>>>>>>>>>>> + echo " zonefile: ${rpzFile}"
>>>>>>>>>>>> + echo " url: ${rpzURL}"
>>>>>>>>>>>> + echo " rpz-action-override: nxdomain"
>>>>>>>>>>>> + echo " rpz-log: ${rpzLog}"
>>>>>>>>>>>> + echo " rpz-log-name: ${rpzName}"
>>>>>>>>>>>> + echo " rpz-signal-nxdomain-ra: yes"
>>>>>>>>>>>> + } > "${rpzConfig}"
>>>>>>>>>>>> +
>>>>>>>>>>>> + # set-up zonefile
>>>>>>>>>>>> + # create an empty rpz file if it does not exist
>>>>>>>>>>>> + if [[ ! -f "${rpzFile}" ]] ; then
>>>>>>>>>>>> + touch "${rpzFile}"
>>>>>>>>>>>> + # unbound requires these settings for rpz files
>>>>>>>>>>>> + set_permissions "${rpzFile}" "${rpzConfig}"
>>>>>>>>>>>> + fi
>>>>>>>>>>>> + ;;
>>>>>>>>>>>> +
>>>>>>>>>>>> + # trash config file & rpz file
>>>>>>>>>>>> + remove )
>>>>>>>>>>>> + if ! [[ -f "${rpzConfig}" ]] ; then
>>>>>>>>>>>> + msg_log "error: rpz: cannot remove ${rpzConfig}, does not exist. exit"
>>>>>>>>>>>> + exit 106
>>>>>>>>>>>> + fi
>>>>>>>>>>>> +
>>>>>>>>>>>> + msg_log "info: rpz: remove config file & rpz file \"${rpzName}\""
>>>>>>>>>>>> + rm "${rpzConfig}"
>>>>>>>>>>>> + rm "${rpzFile}"
>>>>>>>>>>>> + ;;
>>>>>>>>>>>> +
>>>>>>>>>>>> + reload )
>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>> + ;;
>>>>>>>>>>>> +
>>>>>>>>>>>> + list )
>>>>>>>>>>>> + awk -F':' '/^\s*name:/{ gsub(/[[:blank:]]|\.rpz/, "",$2) ; NAME=$2 } \
>>>>>>>>>>>> + /^\s*url:/{ gsub(/[[:blank:]]/, "") ; print NAME"="$2":"$3} ' \
>>>>>>>>>>>> + /etc/unbound/local.d/*rpz.conf
>>>>>>>>>>>> + exit
>>>>>>>>>>>> + ;;
>>>>>>>>>>>> +
>>>>>>>>>>>> + unbound-restart )
>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>> + unbound_restart
>>>>>>>>>>>> + exit
>>>>>>>>>>>> + ;;
>>>>>>>>>>>> +
>>>>>>>>>>>> + * )
>>>>>>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>>>>>>> + printf "Usage: $(basename "$0") <ACTION> <NAME> <URL> <OPTION> <OPTION>\n"
>>>>>>>>>>>> + printf "Version: ${version}\n"
>>>>>>>>>>>> + exit 108
>>>>>>>>>>>> + ;;
>>>>>>>>>>>> +
>>>>>>>>>>>> +esac
>>>>>>>>>>>> +
>>>>>>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>>>>>>> +
>>>>>>>>>>>> +exit
>>>>>>>>>>>> diff --git a/config/rpz/rpz-functions b/config/rpz/rpz-functions
>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>> index 000000000..ace1d2690
>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>> +++ b/config/rpz/rpz-functions
>>>>>>>>>>>> @@ -0,0 +1,85 @@
>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>> +# #
>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>> +# #
>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>> +# #
>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>> +
>>>>>>>>>>>> +version="2024-12-10 - v02"
>>>>>>>>>>>> +
>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>> +
>>>>>>>>>>>> +msg_log () {
>>>>>>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>>>>>>> + if tty --silent ; then
>>>>>>>>>>>> + echo "${tagName}:" "$*"
>>>>>>>>>>>> + fi
>>>>>>>>>>>> +}
>>>>>>>>>>>> +
>>>>>>>>>>>> +# check for a valid name
>>>>>>>>>>>> +check_name () {
>>>>>>>>>>>> + local theName="${1}"
>>>>>>>>>>>> +
>>>>>>>>>>>> + regex='^[a-zA-Z0-9_]+$' # no dash or plus, alpha numeric only
>>>>>>>>>>>> + regex1='^(allow|block)$' # allow and block are reserved NAMEs
>>>>>>>>>>>> + if [[ ! "${theName}" =~ $regex ]] || [[ "${theName}" =~ $regex1 ]] ; then
>>>>>>>>>>>> + msg_log "error: rpz: the NAME is not valid: \"${theName}\". exit."
>>>>>>>>>>>> + exit 101
>>>>>>>>>>>> + fi
>>>>>>>>>>>> +}
>>>>>>>>>>>> +
>>>>>>>>>>>> +set_permissions () {
>>>>>>>>>>>> + chown nobody:nobody "$@"
>>>>>>>>>>>> + chmod 644 "$@"
>>>>>>>>>>>> +}
>>>>>>>>>>>> +
>>>>>>>>>>>> +check_unbound_conf () {
>>>>>>>>>>>> + local thecheckConf="${1:-yes}" # check config default is yes
>>>>>>>>>>>> +
>>>>>>>>>>>> + # check the above config files
>>>>>>>>>>>> + if [[ "${thecheckConf}" == yes ]] ; then
>>>>>>>>>>>> + msg_log "info: rpz: check for errors with \"unbound-checkconf\""
>>>>>>>>>>>> +
>>>>>>>>>>>> + if ! unbound-checkconf ; then
>>>>>>>>>>>> + msg_log "error: rpz: unbound-checkconf found invalid configuration."
>>>>>>>>>>>> + msg_log \
>>>>>>>>>>>> + "error: rpz: In Terminal run the command \"unbound-checkconf\" for more information. exit."
>>>>>>>>>>>> + exit 102
>>>>>>>>>>>> + fi
>>>>>>>>>>>> + fi
>>>>>>>>>>>> +}
>>>>>>>>>>>> +
>>>>>>>>>>>> +unbound_control_reload () {
>>>>>>>>>>>> + local theReload="${1:-yes}" # reload default is yes
>>>>>>>>>>>> +
>>>>>>>>>>>> + if [[ "${theReload}" == yes ]] ; then
>>>>>>>>>>>> + # reload due to the changes
>>>>>>>>>>>> + msg_log "info: rpz: run \"unbound-control reload\""
>>>>>>>>>>>> +
>>>>>>>>>>>> + if ! unbound-control reload ; then
>>>>>>>>>>>> + msg_log "error: rpz: unbound-control reload. exit."
>>>>>>>>>>>> + exit 109
>>>>>>>>>>>> + fi
>>>>>>>>>>>> + fi
>>>>>>>>>>>> +}
>>>>>>>>>>>> +
>>>>>>>>>>>> +unbound_restart () {
>>>>>>>>>>>> + # restart due to the changes
>>>>>>>>>>>> + msg_log "info: rpz: run \"unbound restart\""
>>>>>>>>>>>> +
>>>>>>>>>>>> + /usr/local/bin/unboundctrl restart
>>>>>>>>>>>> +}
>>>>>>>>>>>> diff --git a/config/rpz/rpz-make b/config/rpz/rpz-make
>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>> index 000000000..927d55170
>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>> +++ b/config/rpz/rpz-make
>>>>>>>>>>>> @@ -0,0 +1,203 @@
>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>> +# #
>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>> +# #
>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>> +# #
>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>> +
>>>>>>>>>>>> +version="2025-01-11 - v14"
>>>>>>>>>>>> +
>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>> +
>>>>>>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>>>>>>> +
>>>>>>>>>>>> +# create the config file for allow
>>>>>>>>>>>> +make_allow_config () {
>>>>>>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>>>>>>> + local theConfig="/etc/unbound/local.d/00-rpz.conf" # output zone conf file
>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/allowlist" # input custom list of domains
>>>>>>>>>>>> +
>>>>>>>>>>>> + msg_log "info: rpz: make config file \"00-rpz.conf\""
>>>>>>>>>>>> +
>>>>>>>>>>>> + echo "server:
>>>>>>>>>>>> + module-config: \"respip validator iterator\"" > "${theConfig}"
>>>>>>>>>>>> +
>>>>>>>>>>>> + # does allow list exist?
>>>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>>>> +
>>>>>>>>>>>> + echo "rpz:
>>>>>>>>>>>> + name: allow.rpz
>>>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>> + rpz-action-override: passthru
>>>>>>>>>>>> + rpz-log: ${theLog}
>>>>>>>>>>>> + rpz-log-name: allow
>>>>>>>>>>>> + rpz-signal-nxdomain-ra: yes" >> "${theConfig}"
>>>>>>>>>>>> +
>>>>>>>>>>>> + fi
>>>>>>>>>>>> +
>>>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>>>> + set_permissions "${theConfig}"
>>>>>>>>>>>> +}
>>>>>>>>>>>> +
>>>>>>>>>>>> +# create the config file for block
>>>>>>>>>>>> +make_block_config () {
>>>>>>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>>>>>>> + local theConfig="/etc/unbound/local.d/block.rpz.conf" # output zone conf file
>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/blocklist" # input custom list of domains
>>>>>>>>>>>> +
>>>>>>>>>>>> + msg_log "info: rpz: make config file \"block.rpz.conf\""
>>>>>>>>>>>> +
>>>>>>>>>>>> + # does block list exist?
>>>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>>>> +
>>>>>>>>>>>> + echo "rpz:
>>>>>>>>>>>> + name: block.rpz
>>>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/block.rpz
>>>>>>>>>>>> + rpz-action-override: nxdomain
>>>>>>>>>>>> + rpz-log: ${theLog}
>>>>>>>>>>>> + rpz-log-name: block
>>>>>>>>>>>> + rpz-signal-nxdomain-ra: yes" > "${theConfig}"
>>>>>>>>>>>> +
>>>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>>>> + set_permissions "${theConfig}"
>>>>>>>>>>>> + else
>>>>>>>>>>>> + # no - trash the config file
>>>>>>>>>>>> + rm --verbose /etc/unbound/local.d/block.rpz.conf
>>>>>>>>>>>> + fi
>>>>>>>>>>>> +}
>>>>>>>>>>>> +
>>>>>>>>>>>> +# create an RPZ file for allow or block
>>>>>>>>>>>> +make_rpz_file () {
>>>>>>>>>>>> + local theName="${1}" # allow or block
>>>>>>>>>>>> + local theAction='.' # the default is nxdomain or block
>>>>>>>>>>>> + local actionList
>>>>>>>>>>>> +
>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>>>>>>> + local theZonefile="/etc/unbound/zonefiles/${theName}.rpz" # output file for RPZ
>>>>>>>>>>>> +
>>>>>>>>>>>> + # does a list exist?
>>>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>>>> +
>>>>>>>>>>>> + # for allow set to passthru
>>>>>>>>>>>> + [[ "${theName}" == allow ]] && theAction='rpz-passthru.'
>>>>>>>>>>>> +
>>>>>>>>>>>> + # drop any extra "blanks" and add "CNAME <RPZ action>." to each line
>>>>>>>>>>>> + actionList=$( awk '{$1=$1};1' "${theList}" |
>>>>>>>>>>>> + sed "/^[^;].*[[:alnum:]]/ s|$| CNAME ${theAction}|" )
>>>>>>>>>>>> +
>>>>>>>>>>>> + msg_log "info: rpz: create zonefile for \"${theName}list\""
>>>>>>>>>>>> +
>>>>>>>>>>>> +echo "; Name: ${theName} list
>>>>>>>>>>>> +; Last modified: $(date "+%Y-%m-%d at %H.%M.%S %Z")
>>>>>>>>>>>> +;
>>>>>>>>>>>> +; domains with actions list
>>>>>>>>>>>> +;
>>>>>>>>>>>> +${actionList}" > "${theZonefile}"
>>>>>>>>>>>> +
>>>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>>>> + set_permissions "${theZonefile}"
>>>>>>>>>>>> + # set-up allow/block list files
>>>>>>>>>>>> + set_permissions "${theList}"
>>>>>>>>>>>> + else
>>>>>>>>>>>> + msg_log "info: rpz: the ${theList} is empty."
>>>>>>>>>>>> +
>>>>>>>>>>>> + rm --verbose "${theZonefile}" # trash the RPZ file
>>>>>>>>>>>> + fi
>>>>>>>>>>>> +}
>>>>>>>>>>>> +
>>>>>>>>>>>> +# check if allow/block list is valid
>>>>>>>>>>>> +validate_list () {
>>>>>>>>>>>> + local theName="${1}" # allow or block
>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>>>>>>> +
>>>>>>>>>>>> + # remove good:
>>>>>>>>>>>> + # - properly formated domain names with or without leading wildcard
>>>>>>>>>>>> + # - properly formated top level domain (TLD) names with wildcard
>>>>>>>>>>>> + # - blank lines and comment lines
>>>>>>>>>>>> + # remaining lines are considered "bad"
>>>>>>>>>>>> + bad_lines=$( sed --regexp-extended \
>>>>>>>>>>>> + '/^(\*\.)?([a-zA-Z0-9](([a-zA-Z0-9\-]){0,61}[a-zA-Z0-9])?\.)+([a-zA-Z]{2,}|xn--[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])$/d ;
>>>>>>>>>>>> + /^(\*\.)([a-z]{2,61}|xn--[a-z0-9]{1,60})$/d ;
>>>>>>>>>>>> + /^$/d ; /^;/d' "${theList}" )
>>>>>>>>>>>> +
>>>>>>>>>>>> + if [[ ! -z "${bad_lines}" ]] ; then
>>>>>>>>>>>> + msg_log "error: rpz: invalid line(s) in ${theList}."
>>>>>>>>>>>> + printf "%s\n" "bad line(s): ${bad_lines}"
>>>>>>>>>>>> + exit 110
>>>>>>>>>>>> + fi
>>>>>>>>>>>> +}
>>>>>>>>>>>> +
>>>>>>>>>>>> +
>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>> +
>>>>>>>>>>>> +tagName="unbound"
>>>>>>>>>>>> +
>>>>>>>>>>>> +rpzName="${1}" # input RPZ name
>>>>>>>>>>>> +
>>>>>>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>>>>>>> +
>>>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>>>> + case "$1" in
>>>>>>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>>>>>>> + esac
>>>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>>>> +done
>>>>>>>>>>>> +
>>>>>>>>>>>> +case "${rpzName}" in
>>>>>>>>>>>> + # make a new allow or block rpz file
>>>>>>>>>>>> +
>>>>>>>>>>>> + allow )
>>>>>>>>>>>> + validate_list 'allow' # is the allowlist valid?
>>>>>>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>>>>>>> + make_rpz_file 'allow'
>>>>>>>>>>>> + ;;
>>>>>>>>>>>> +
>>>>>>>>>>>> + allowblock )
>>>>>>>>>>>> + validate_list 'allow' # is the list valid?
>>>>>>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>>>>>>> + make_rpz_file 'allow'
>>>>>>>>>>>> + ;&
>>>>>>>>>>>> +
>>>>>>>>>>>> + block )
>>>>>>>>>>>> + validate_list 'block' # is the blocklist valid?
>>>>>>>>>>>> + make_block_config "${rpzLog}"
>>>>>>>>>>>> + make_rpz_file 'block'
>>>>>>>>>>>> + ;;
>>>>>>>>>>>> +
>>>>>>>>>>>> + reload )
>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>> + ;;
>>>>>>>>>>>> +
>>>>>>>>>>>> + unbound-restart )
>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>> + unbound_restart
>>>>>>>>>>>> + exit
>>>>>>>>>>>> + ;;
>>>>>>>>>>>> +
>>>>>>>>>>>> + * )
>>>>>>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>>>>>>> + printf "Usage: $(basename "$0") <NAME> <OPTION> <OPTION>\n"
>>>>>>>>>>>> + printf "Version: ${version}\n"
>>>>>>>>>>>> + exit 108
>>>>>>>>>>>> + ;;
>>>>>>>>>>>> +esac
>>>>>>>>>>>> +
>>>>>>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>>>>>>> +
>>>>>>>>>>>> +exit
>>>>>>>>>>>> diff --git a/config/rpz/rpz-metrics b/config/rpz/rpz-metrics
>>>>>>>>>>>> new file mode 100755
>>>>>>>>>>>> index 000000000..4d43e1629
>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>> +++ b/config/rpz/rpz-metrics
>>>>>>>>>>>> @@ -0,0 +1,170 @@
>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>> +# #
>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>> +# #
>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>> +# #
>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>> +
>>>>>>>>>>>> +version="2025-01-20 - v25"
>>>>>>>>>>>> +
>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>> +
>>>>>>>>>>>> +weeks="2" # default to two message logs
>>>>>>>>>>>> +sortBy="name" # default "by name"
>>>>>>>>>>>> +rpzActive="enabled" # default "enabled only"
>>>>>>>>>>>> +
>>>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>>>> + case "$1" in
>>>>>>>>>>>> + --by-names | --by-name | name ) sortBy="name" ;;
>>>>>>>>>>>> +
>>>>>>>>>>>> + --by-hits | --by-hit | hits | hit ) sortBy="hit" ;;
>>>>>>>>>>>> +
>>>>>>>>>>>> + --by-lines | --by-line | lines | line ) sortBy="line" ;;
>>>>>>>>>>>> +
>>>>>>>>>>>> + --by-effect ) sortBy="effect" ;;
>>>>>>>>>>>> +
>>>>>>>>>>>> + --enabled-only ) rpzActive="enabled" ;;
>>>>>>>>>>>> +
>>>>>>>>>>>> + --active-all | --all | all ) rpzActive="all" ;;
>>>>>>>>>>>> +
>>>>>>>>>>>> + [0-9] | [0-9][0-9] ) weeks=$1 ;;
>>>>>>>>>>>> + esac
>>>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>>>> +done
>>>>>>>>>>>> +
>>>>>>>>>>>> +# get the list of message logs for N weeks
>>>>>>>>>>>> +messageLogs=$( find /var/log/messages* -type f | sort --version-sort |
>>>>>>>>>>>> + head -"${weeks}" )
>>>>>>>>>>>> +
>>>>>>>>>>>> +# get the list of RPZ names & counts from the message log(s)
>>>>>>>>>>>> +rpzNameCount=$( for logf in ${messageLogs} ; do
>>>>>>>>>>>> + zgrep --text --fixed-strings 'info: rpz: applied' "${logf}" |
>>>>>>>>>>>> + awk '$10 ~ /\[\w*]/ { print $10 }' ;
>>>>>>>>>>>> + done | sort | uniq --count )
>>>>>>>>>>>> +
>>>>>>>>>>>> +# flip results and remove brackets `[` and `]`
>>>>>>>>>>>> +rpzNameCount=$( echo "${rpzNameCount}" |
>>>>>>>>>>>> + awk '{ print $2, $1 }' |
>>>>>>>>>>>> + sed --regexp-extended 's|^\[(.*)\]|\1|' )
>>>>>>>>>>>> +
>>>>>>>>>>>> +# grab only names
>>>>>>>>>>>> +rpzNames=$( echo "${rpzNameCount}" | awk '{ print $1 }' )
>>>>>>>>>>>> +
>>>>>>>>>>>> +# get list of RPZ files
>>>>>>>>>>>> +rpzFileList=$( find /etc/unbound/zonefiles -type f -iname "*.rpz" )
>>>>>>>>>>>> +
>>>>>>>>>>>> +# get basename of those files
>>>>>>>>>>>> +rpzBaseNames=$( echo "${rpzFileList}" |
>>>>>>>>>>>> + sed 's|/etc/unbound/zonefiles/||g ; s|\.rpz||g ;' )
>>>>>>>>>>>> +
>>>>>>>>>>>> +# add to rpzNames
>>>>>>>>>>>> +rpzNames="${rpzNames}"$'\n'"${rpzBaseNames}"
>>>>>>>>>>>> +
>>>>>>>>>>>> +# drop duplicate names
>>>>>>>>>>>> +rpzNames=$( echo "${rpzNames}" | sort --unique )
>>>>>>>>>>>> +
>>>>>>>>>>>> +# get line count for each RPZ
>>>>>>>>>>>> +lineCount=$( echo "${rpzFileList}" | xargs wc -l )
>>>>>>>>>>>> +
>>>>>>>>>>>> +# get comment line count and blank line count for each RPZ
>>>>>>>>>>>> +commentCount=$( echo "${rpzFileList}" | xargs grep --count -e "^$" -e "^;" )
>>>>>>>>>>>> +
>>>>>>>>>>>> +# get modified date each RPZ
>>>>>>>>>>>> +modDateList=$( echo "${rpzFileList}" | xargs stat -c '%.10y %n' )
>>>>>>>>>>>> +
>>>>>>>>>>>> +ucListAuthZones=$( unbound-control list_auth_zones )
>>>>>>>>>>>> +
>>>>>>>>>>>> +# get width of RPZ names
>>>>>>>>>>>> +pWidth=$( echo "${rpzNames}" | awk '{ print $1" " }' | wc -L )
>>>>>>>>>>>> +pFormat="%-${pWidth}s %-8s %-8s %8s %12s %12s\n"
>>>>>>>>>>>> +
>>>>>>>>>>>> +# print title line
>>>>>>>>>>>> +printf "${pFormat}" "name" "hits" "active" "lines" " hits/line" "last download"
>>>>>>>>>>>> +printf -- "--------------"
>>>>>>>>>>>> +
>>>>>>>>>>>> +theResults=""
>>>>>>>>>>>> +totalLines=0
>>>>>>>>>>>> +totalHits=0
>>>>>>>>>>>> +while read -r theName
>>>>>>>>>>>> +do
>>>>>>>>>>>> + printf -- "--" # pretend progress bar
>>>>>>>>>>>> +
>>>>>>>>>>>> + # is this RPZ list active?
>>>>>>>>>>>> + theActive="disabled"
>>>>>>>>>>>> + if grep --quiet "^${theName}\.rpz" <<< "${ucListAuthZones}"
>>>>>>>>>>>> + then
>>>>>>>>>>>> + theActive="enabled"
>>>>>>>>>>>> + else
>>>>>>>>>>>> + [[ "${rpzActive}" == enabled ]] && continue
>>>>>>>>>>>> + fi
>>>>>>>>>>>> +
>>>>>>>>>>>> + # get hit count
>>>>>>>>>>>> + theHits="0"
>>>>>>>>>>>> + if output=$( grep "^${theName}\s" <<< "${rpzNameCount}" ) ; then
>>>>>>>>>>>> + theHits=$( echo "${output}" | awk '{ print $2 }' )
>>>>>>>>>>>> + totalHits=$(( totalHits + theHits ))
>>>>>>>>>>>> + fi
>>>>>>>>>>>> +
>>>>>>>>>>>> + # get line count
>>>>>>>>>>>> + theLines="n/a"
>>>>>>>>>>>> + hitsPerLine="0"
>>>>>>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${lineCount}" ) ; then
>>>>>>>>>>>> + theLines=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>>>>>>> + totalLines=$(( totalLines + theLines ))
>>>>>>>>>>>> +
>>>>>>>>>>>> + if [[ "${theLines}" -gt 2 ]] ; then
>>>>>>>>>>>> + hitsPerLine=$(( 100 * theHits / theLines ))
>>>>>>>>>>>> + fi
>>>>>>>>>>>> + fi
>>>>>>>>>>>> +
>>>>>>>>>>>> + # get modification date
>>>>>>>>>>>> + theModDate="n/a"
>>>>>>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${modDateList}" ) ; then
>>>>>>>>>>>> + theModDate=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>>>>>>> + fi
>>>>>>>>>>>> +
>>>>>>>>>>>> + # add to results list
>>>>>>>>>>>> + theResults+="${theName} ${theHits} ${theActive} ${theLines} ${hitsPerLine} ${theModDate}"$'\n'
>>>>>>>>>>>> +
>>>>>>>>>>>> +done <<< "${rpzNames}"
>>>>>>>>>>>> +
>>>>>>>>>>>> +case "${sortBy}" in
>>>>>>>>>>>> + # sort by "active" then by "name"
>>>>>>>>>>>> + name) sortArg=(-k3,3r -k1,1) ;;
>>>>>>>>>>>> +
>>>>>>>>>>>> + # sort by "active" then by "hits" then by "name"
>>>>>>>>>>>> + hit) sortArg=(-k3,3r -k2,2nr -k1,1) ;;
>>>>>>>>>>>> +
>>>>>>>>>>>> + # sort by "active" then by "lines" then by "name"
>>>>>>>>>>>> + line) sortArg=(-k3,3r -k4,4nr -k1,1) ;;
>>>>>>>>>>>> +
>>>>>>>>>>>> + # sort by "active" then by "effect" then by "name"
>>>>>>>>>>>> + effect) sortArg=(-k3,3r -k5,5nr -k1,1) ;;
>>>>>>>>>>>> +esac
>>>>>>>>>>>> +
>>>>>>>>>>>> +printf -- "--------------\n"
>>>>>>>>>>>> +# remove blank lines, sort, print as columns
>>>>>>>>>>>> +echo "${theResults}" |
>>>>>>>>>>>> + awk '!/^[[:space:]]*$/' |
>>>>>>>>>>>> + sort "${sortArg[@]}" |
>>>>>>>>>>>> + awk --assign=width="${pWidth}" \
>>>>>>>>>>>> + '{ printf "%-*s %-8s %-8s %8s %10s %% %12s\n", width, $1, $2, $3, $4, $5, $6 }'
>>>>>>>>>>>> +
>>>>>>>>>>>> +printf "${pFormat}" "" "=======" "" "========" "" ""
>>>>>>>>>>>> +printf "${pFormat}" "Totals -->" "${totalHits}" "" "${totalLines}" "" ""
>>>>>>>>>>>> +
>>>>>>>>>>>> +exit
>>>>>>>>>>>> diff --git a/config/rpz/rpz-sleep b/config/rpz/rpz-sleep
>>>>>>>>>>>> new file mode 100755
>>>>>>>>>>>> index 000000000..dd3603599
>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>> +++ b/config/rpz/rpz-sleep
>>>>>>>>>>>> @@ -0,0 +1,58 @@
>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>> +# #
>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>> +# #
>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>> +# #
>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>> +
>>>>>>>>>>>> +version="2024-08-16" # v05
>>>>>>>>>>>> +
>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>> +
>>>>>>>>>>>> +# send message to message log
>>>>>>>>>>>> +msg_log () {
>>>>>>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>>>>>>> + if tty --silent ; then
>>>>>>>>>>>> + echo "${tagName}:" "$*"
>>>>>>>>>>>> + fi
>>>>>>>>>>>> +}
>>>>>>>>>>>> +
>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>> +
>>>>>>>>>>>> +tagName="unbound"
>>>>>>>>>>>> +
>>>>>>>>>>>> +sleepTime="${1:-5m}" # default to sleep for 5m (5 minutes)
>>>>>>>>>>>> +
>>>>>>>>>>>> +zoneList=$( unbound-control list_auth_zones | awk '{print $1}' )
>>>>>>>>>>>> +
>>>>>>>>>>>> +for zone in ${zoneList} ; do
>>>>>>>>>>>> + printf "disable ${zone}\t"
>>>>>>>>>>>> + unbound-control rpz_disable "${zone}"
>>>>>>>>>>>> +done
>>>>>>>>>>>> +
>>>>>>>>>>>> +msg_log "info: rpz: disabled all zones for ${sleepTime}"
>>>>>>>>>>>> +
>>>>>>>>>>>> +sleep "${sleepTime}"
>>>>>>>>>>>> +
>>>>>>>>>>>> +for zone in ${zoneList} ; do
>>>>>>>>>>>> + printf "enable ${zone}\t"
>>>>>>>>>>>> + unbound-control rpz_enable "${zone}"
>>>>>>>>>>>> +done
>>>>>>>>>>>> +
>>>>>>>>>>>> +msg_log "info: rpz: enabled all zones"
>>>>>>>>>>>> +
>>>>>>>>>>>> +exit
>>>>>>>>>>>> diff --git a/config/rpz/rpz.de.pl b/config/rpz/rpz.de.pl
>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>> index 000000000..3770c6bb0
>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>> +++ b/config/rpz/rpz.de.pl
>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>> +'rpz apply' => 'Übernehmen',
>>>>>>>>>>>> +'rpz cl allow enable' => 'Benutzerdefinierte Allowlist aktivieren:',
>>>>>>>>>>>> +'rpz cl allow info' => 'Zugelassene Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>>>>>>> +'rpz cl allow' => 'Benutzerdefinierte Allowlist',
>>>>>>>>>>>> +'rpz cl block enable' => 'Benutzerdefinierte Blocklist aktivieren:',
>>>>>>>>>>>> +'rpz cl block info' => 'Gesperrte Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>>>>>>> +'rpz cl block' => 'Benutzerdefinierte Blocklist',
>>>>>>>>>>>> +'rpz cl' => 'Benutzerdefinierte Listen',
>>>>>>>>>>>> +'rpz exitcode 101' => 'Der Name enthält unzulässige Zeichen',
>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf hat eine fehlerhafte Konfiguration ermittelt. Führen Sie das Kommando unbound-checkconf auf der Konsole aus, um weitere Informationen zu erhalten.',
>>>>>>>>>>>> +'rpz exitcode 103' => 'Die benutzerdefinierte Allow-/Blocklist ist leer',
>>>>>>>>>>>> +'rpz exitcode 104' => 'Ein Eintrag mit identischem Namen existiert bereits',
>>>>>>>>>>>> +'rpz exitcode 105' => 'Die URL ist ungültig',
>>>>>>>>>>>> +'rpz exitcode 106' => 'Eintrag kann nicht entfernt werden, der Name existiert nicht',
>>>>>>>>>>>> +'rpz exitcode 107' => 'Der Name ist ungültig - nur "allow" oder "block" möglich',
>>>>>>>>>>>> +'rpz exitcode 108' => 'Fehlende oder inkorrekte Parameter',
>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control reload ist fehlgeschlagen',
>>>>>>>>>>>> +'rpz exitcode 110' => 'Die benutzerdefinierte Allow-/Blocklist enthält unzulässige Einträge',
>>>>>>>>>>>> +'rpz exitcode 201' => 'Die Anmerkung enthält unzulässige Zeichen',
>>>>>>>>>>>> +'rpz exitcode 202' => 'Ungültiger Eintrag in der benutzerdefinierten Allowlist, Zeile ',
>>>>>>>>>>>> +'rpz exitcode 203' => 'Ungültiger Eintrag in der benutzerdefinierten Blocklist, Zeile ',
>>>>>>>>>>>> +'rpz exitcode 204' => 'Ausgewählter Eintrag existiert nicht: ',
>>>>>>>>>>>> +'rpz zf editor' => 'Zonendatei-Eintrag bearbeiten',
>>>>>>>>>>>> +'rpz zf imported' => '(importiert aus rpz-config)',
>>>>>>>>>>>> +'rpz zf remark info' => 'Erlaubte Zeichen sind a-z, A-Z, 0-9 und Unterstriche',
>>>>>>>>>>>> +'rpz zf' => 'Zonendateien',
>>>>>>>>>>>> +);
>>>>>>>>>>>> diff --git a/config/rpz/rpz.en.pl b/config/rpz/rpz.en.pl
>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>> index 000000000..0720a8940
>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>> +++ b/config/rpz/rpz.en.pl
>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>> +'rpz apply' => 'Apply',
>>>>>>>>>>>> +'rpz cl allow enable' => 'Enable custom allowlist:',
>>>>>>>>>>>> +'rpz cl allow info' => 'Allowed domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>> +'rpz cl allow' => 'Custom allowlist',
>>>>>>>>>>>> +'rpz cl block enable' => 'Enable custom blocklist:',
>>>>>>>>>>>> +'rpz cl block info' => 'Blocked domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>> +'rpz cl block' => 'Custom blocklist',
>>>>>>>>>>>> +'rpz cl' => 'Custom lists',
>>>>>>>>>>>> +'rpz exitcode 101' => 'the NAME is not valid',
>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf found invalid configuration. In the Terminal run the command unbound-checkconf for more information',
>>>>>>>>>>>> +'rpz exitcode 103' => 'the allow/block list is empty',
>>>>>>>>>>>> +'rpz exitcode 104' => 'duplicate - NAME already exists',
>>>>>>>>>>>> +'rpz exitcode 105' => 'the URL is not valid',
>>>>>>>>>>>> +'rpz exitcode 106' => 'cannot remove the NAME does not exist',
>>>>>>>>>>>> +'rpz exitcode 107' => 'the NAME is not valid - "allow" or "block" only',
>>>>>>>>>>>> +'rpz exitcode 108' => 'missing or incorrect parameter',
>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control reload failed',
>>>>>>>>>>>> +'rpz exitcode 110' => 'custom Allowlist/Blocklist contains invalid entries',
>>>>>>>>>>>> +'rpz exitcode 201' => 'the REMARK is not valid',
>>>>>>>>>>>> +'rpz exitcode 202' => 'invalid entry in allowlist, line ',
>>>>>>>>>>>> +'rpz exitcode 203' => 'invalid entry in blocklist, line ',
>>>>>>>>>>>> +'rpz exitcode 204' => 'Selected entry does not exist: ',
>>>>>>>>>>>> +'rpz zf editor' => 'Edit zonefiles entry',
>>>>>>>>>>>> +'rpz zf imported' => '(imported from rpz-config)',
>>>>>>>>>>>> +'rpz zf remark info' => 'Valid characters are a-z, A-Z, 0-9 and underscore.',
>>>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>>>> +);
>>>>>>>>>>>> diff --git a/config/rpz/rpz.es.pl b/config/rpz/rpz.es.pl
>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>> index 000000000..98628e4aa
>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>> +++ b/config/rpz/rpz.es.pl
>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>> +'rpz' => 'Zonas de política de respuesta (RPZ)',
>>>>>>>>>>>> +'rpz apply' => 'Aplicar',
>>>>>>>>>>>> +'rpz cl allow enable' => 'Habilitar la lista blanca personalizada:',
>>>>>>>>>>>> +'rpz cl allow info' => 'Dominio permitido (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>>>>>>> +'rpz cl allow' => 'Lista blanca personalizada',
>>>>>>>>>>>> +'rpz cl block enable' => 'Habilitar la lista negra personalizada:',
>>>>>>>>>>>> +'rpz cl block info' => 'Dominio bloqueado (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>>>>>>> +'rpz cl block' => 'Lista negra personalizada',
>>>>>>>>>>>> +'rpz cl' => 'Lista personalizada',
>>>>>>>>>>>> +'rpz exitcode 101' => 'El NOMBRE no es válido',
>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha encontrado una configuración no válida. Desde Terminal, ejecute el comando unbound-checkconf para mayor información',
>>>>>>>>>>>> +'rpz exitcode 103' => 'La lista de permitidos/bloqueados está vacía',
>>>>>>>>>>>> +'rpz exitcode 104' => 'duplicado - NOMBRE ya existe',
>>>>>>>>>>>> +'rpz exitcode 105' => 'la URL no es válida',
>>>>>>>>>>>> +'rpz exitcode 106' => 'no es posible eliminar el NOMBRE que no existe',
>>>>>>>>>>>> +'rpz exitcode 107' => 'el NOMBRE no es válido - sólo "permitir" o "bloquear"',
>>>>>>>>>>>> +'rpz exitcode 108' => 'parámetro faltante o incorrecto',
>>>>>>>>>>>> +'rpz exitcode 109' => 'Error al recargar unbound-control',
>>>>>>>>>>>> +'rpz exitcode 110' => 'la Lista blanca/Lista negra personalizada contiene entradas no válidas',
>>>>>>>>>>>> +'rpz exitcode 201' => 'el COMENTARIO no es válido',
>>>>>>>>>>>> +'rpz exitcode 202' => 'entrada no válida en la lista blanca, línea ',
>>>>>>>>>>>> +'rpz exitcode 203' => 'entrada no válida en la lista negra, línea ',
>>>>>>>>>>>> +'rpz exitcode 204' => 'La entrada seleccionada no existe: ',
>>>>>>>>>>>> +'rpz zf editor' => 'Editar la entrada de archivos de zona',
>>>>>>>>>>>> +'rpz zf imported' => '(importado de rpz-config)',
>>>>>>>>>>>> +'rpz zf remark info' => 'Los caracteres válidos son a-z, A-Z, 0-9 y guión bajo',
>>>>>>>>>>>> +'rpz zf' => 'Archivos de zona',
>>>>>>>>>>>> +);
>>>>>>>>>>>> diff --git a/config/rpz/rpz.fr.pl b/config/rpz/rpz.fr.pl
>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>> index 000000000..f35f3c2d0
>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>> +++ b/config/rpz/rpz.fr.pl
>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>> +'rpz apply' => 'Appliquer',
>>>>>>>>>>>> +'rpz cl allow enable' => 'Activer la liste d\'autorisations personnalisée:',
>>>>>>>>>>>> +'rpz cl allow info' => 'Domaines autorisés (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>> +'rpz cl allow' => 'Liste d\'autorisations personnalisée',
>>>>>>>>>>>> +'rpz cl block enable' => 'Activer la liste de blocage personnalisée:',
>>>>>>>>>>>> +'rpz cl block info' => 'Domaines bloqués (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>> +'rpz cl block' => 'liste de blocage personnalisé',
>>>>>>>>>>>> +'rpz cl' => 'Listes personnalisées',
>>>>>>>>>>>> +'rpz exitcode 101' => 'le NOM n\'est pas valide',
>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf configuration non valide trouvée. Dans le terminal, exécutez la commande unbound-checkconf pour plus d\'informations',
>>>>>>>>>>>> +'rpz exitcode 103' => 'la liste autoriser/bloquer est vide',
>>>>>>>>>>>> +'rpz exitcode 104' => 'le NOM existe déjà',
>>>>>>>>>>>> +'rpz exitcode 105' => 'L\'URL n\'est pas valide',
>>>>>>>>>>>> +'rpz exitcode 106' => 'impossible de supprimer le NOM n\'existe pas',
>>>>>>>>>>>> +'rpz exitcode 107' => 'le NOM n\'est pas valide - « autoriser » ou « bloquer » seulement',
>>>>>>>>>>>> +'rpz exitcode 108' => 'paramètre manquant ou incorrect',
>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control rechargement échoué',
>>>>>>>>>>>> +'rpz exitcode 110' => 'la liste autoriser/bloquer contient des entrées non valides',
>>>>>>>>>>>> +'rpz exitcode 201' => 'la REMARQUE n\'est pas valable',
>>>>>>>>>>>> +'rpz exitcode 202' => 'entrée non valide dans la liste d\'autorisation, ligne ',
>>>>>>>>>>>> +'rpz exitcode 203' => 'entrée non valide dans la liste de blocs, ligne ',
>>>>>>>>>>>> +'rpz exitcode 204' => 'L\'entrée sélectionnée n\'existe pas: ',
>>>>>>>>>>>> +'rpz zf editor' => 'Modifier l\'entrée Fichiers Zone',
>>>>>>>>>>>> +'rpz zf imported' => '(importé de rpz-config)',
>>>>>>>>>>>> +'rpz zf remark info' => 'Les caractères valides sont a-z, A-Z, 0-9 et soulignement.',
>>>>>>>>>>>> +'rpz zf' => 'Fichiers Zone',
>>>>>>>>>>>> +);
>>>>>>>>>>>> diff --git a/config/rpz/rpz.it.pl b/config/rpz/rpz.it.pl
>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>> index 000000000..ee81605c9
>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>> +++ b/config/rpz/rpz.it.pl
>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>> +'rpz apply' => 'Applica',
>>>>>>>>>>>> +'rpz cl allow enable' => 'Abilita la Whitelist personalizzata:',
>>>>>>>>>>>> +'rpz cl allow info' => 'Domini consentiti (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>>>>>>> +'rpz cl allow' => 'Whitelist personalizzata',
>>>>>>>>>>>> +'rpz cl block enable' => 'Abilita la Blacklist personalizzata:',
>>>>>>>>>>>> +'rpz cl block info' => 'Domini bloccati (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>>>>>>> +'rpz cl block' => 'Blacklist personalizzata',
>>>>>>>>>>>> +'rpz cl' => 'Liste personalizzate',
>>>>>>>>>>>> +'rpz exitcode 101' => 'il NOME non è valido',
>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha trovato una configurazione non valida. Dal Terminale esegui il comando unbound-checkconf per maggiori informazioni',
>>>>>>>>>>>> +'rpz exitcode 103' => 'l\'elenco consentiti/bloccati è vuoto',
>>>>>>>>>>>> +'rpz exitcode 104' => 'duplicato - NAME esiste di già',
>>>>>>>>>>>> +'rpz exitcode 105' => 'l\'URL non è valido',
>>>>>>>>>>>> +'rpz exitcode 106' => 'non è possibile rimuovere il NOME non esiste',
>>>>>>>>>>>> +'rpz exitcode 107' => 'il NOME non è valido - solo "consenti" o "blocca"',
>>>>>>>>>>>> +'rpz exitcode 108' => 'parametro mancante o non corretto',
>>>>>>>>>>>> +'rpz exitcode 109' => 'ricaricamento del controllo non associato non riuscito',
>>>>>>>>>>>> +'rpz exitcode 110' => 'la Whitelist/Blacklist personalizzata contiene voci non valide',
>>>>>>>>>>>> +'rpz exitcode 201' => 'l"OSSERVAZIONE non è valida',
>>>>>>>>>>>> +'rpz exitcode 202' => 'voce non valida nella Whitelist, riga ',
>>>>>>>>>>>> +'rpz exitcode 203' => 'voce non valida nella Blacklist, riga ',
>>>>>>>>>>>> +'rpz exitcode 204' => 'La voce selezionata non esiste: ',
>>>>>>>>>>>> +'rpz zf editor' => 'Modifica la voce dei file di zona',
>>>>>>>>>>>> +'rpz zf imported' => '(importato da rpz-config)',
>>>>>>>>>>>> +'rpz zf remark info' => 'I caratteri validi sono a-z, A-Z, 0-9 e trattino basso',
>>>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>>>> +);
>>>>>>>>>>>> diff --git a/config/rpz/rpz.tr.pl b/config/rpz/rpz.tr.pl
>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>> index 000000000..00226e192
>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>> +++ b/config/rpz/rpz.tr.pl
>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>> +# I�in eklendi Ayriyeten Response Policy Zone (RPZ)
>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>> +'rpz apply' => 'Uygulamak',
>>>>>>>>>>>> +'rpz cl allow enable' => '�zel Etkinlestir allowlist:',
>>>>>>>>>>>> +'rpz cl allow info' => 'Izin verilmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>>>>>>> +'rpz cl allow' => 'Etkinlestir allowlist',
>>>>>>>>>>>> +'rpz cl block enable' => '�zel Etkinlestir blocklist:',
>>>>>>>>>>>> +'rpz cl block info' => 'Engellenmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>>>>>>> +'rpz cl block' => 'Engellenmis blocklist',
>>>>>>>>>>>> +'rpz cl' => 'Engellenmis lists',
>>>>>>>>>>>> +'rpz exitcode 101' => 'NAME ge�erli degil',
>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ge�ersiz yapilandirma bulundu. Terminalde daha fazla bilgi i�in Unbound-checkConf komutunu �alistirin',
>>>>>>>>>>>> +'rpz exitcode 103' => 'allow/block liste bos',
>>>>>>>>>>>> +'rpz exitcode 104' => 'kopyalamak - NAME zaten var',
>>>>>>>>>>>> +'rpz exitcode 105' => 'URL ge�erli degil',
>>>>>>>>>>>> +'rpz exitcode 106' => '�ikarilamiyor NAME yok',
>>>>>>>>>>>> +'rpz exitcode 107' => 'NAME ge�erli degil - "allow" veya "block" yalniz',
>>>>>>>>>>>> +'rpz exitcode 108' => 'Parametre eksik veya yanlis',
>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control basarisiz',
>>>>>>>>>>>> +'rpz exitcode 110' => 'Engellenmis Allowlist/Blocklist ge�ersiz girisler i�erir',
>>>>>>>>>>>> +'rpz exitcode 201' => 'REMARK ge�erli degil',
>>>>>>>>>>>> +'rpz exitcode 202' => 'Ge�ersiz giris allowlist, line ',
>>>>>>>>>>>> +'rpz exitcode 203' => 'Ge�ersiz giris blocklist, line ',
>>>>>>>>>>>> +'rpz exitcode 204' => 'Se�ilen giris yok: ',
>>>>>>>>>>>> +'rpz zf editor' => 'yazimlamak zonefiles giris',
>>>>>>>>>>>> +'rpz zf imported' => '(ithal edildi rpz-config)',
>>>>>>>>>>>> +'rpz zf remark info' => 'Ge�erli karakterler a-z, A-Z, 0-9ve alt�st.',
>>>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>>>> +);
>>>>>>>>>>>> diff --git a/html/cgi-bin/rpz.cgi b/html/cgi-bin/rpz.cgi
>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>> index 000000000..a821c92ac
>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>> +++ b/html/cgi-bin/rpz.cgi
>>>>>>>>>>>> @@ -0,0 +1,923 @@
>>>>>>>>>>>> +#!/usr/bin/perl
>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>> +# #
>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>> +# Copyright (C) 2005-2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>> +# #
>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>> +# #
>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>> +
>>>>>>>>>>>> +use strict;
>>>>>>>>>>>> +use Scalar::Util qw(looks_like_number);
>>>>>>>>>>>> +
>>>>>>>>>>>> +# debugging
>>>>>>>>>>>> +#use warnings;
>>>>>>>>>>>> +#use CGI::Carp 'fatalsToBrowser';
>>>>>>>>>>>> +#use Data::Dumper;
>>>>>>>>>>>> +
>>>>>>>>>>>> +require '/var/ipfire/general-functions.pl';
>>>>>>>>>>>> +require "${General::swroot}/lang.pl";
>>>>>>>>>>>> +require "${General::swroot}/header.pl";
>>>>>>>>>>>> +
>>>>>>>>>>>> +###--- Extra HTML ---###
>>>>>>>>>>>> +my $extraHead = <<END
>>>>>>>>>>>> +<style>
>>>>>>>>>>>> + /* alternating row background */
>>>>>>>>>>>> + .tbl tr:nth-child(2n+2) {
>>>>>>>>>>>> + background-color: var(--color-light-grey);
>>>>>>>>>>>> + }
>>>>>>>>>>>> + .tbl tr:nth-child(2n+3) {
>>>>>>>>>>>> + background-color: var(--color-grey);
>>>>>>>>>>>> + }
>>>>>>>>>>>> + /* text styles */
>>>>>>>>>>>> + .tbl th:not(:last-child) {
>>>>>>>>>>>> + text-align: left;
>>>>>>>>>>>> + }
>>>>>>>>>>>> + div.right {
>>>>>>>>>>>> + text-align: right;
>>>>>>>>>>>> + margin-top: 0.5em;
>>>>>>>>>>>> + }
>>>>>>>>>>>> + /* customlist input */
>>>>>>>>>>>> + textarea.domainlist {
>>>>>>>>>>>> + margin: 0.5em 0;
>>>>>>>>>>>> + resize: vertical;
>>>>>>>>>>>> + min-height: 10em;
>>>>>>>>>>>> + overflow: auto;
>>>>>>>>>>>> + white-space: pre;
>>>>>>>>>>>> + }
>>>>>>>>>>>> + button[type=submit]:disabled {
>>>>>>>>>>>> + opacity: 0.6;
>>>>>>>>>>>> + }
>>>>>>>>>>>> +</style>
>>>>>>>>>>>> +END
>>>>>>>>>>>> +;
>>>>>>>>>>>> +###--- End of extra HTML ---###
>>>>>>>>>>>> +
>>>>>>>>>>>> +
>>>>>>>>>>>> +### Settings ###
>>>>>>>>>>>> +
>>>>>>>>>>>> +# Request DNS service reload after configuration change
>>>>>>>>>>>> +my $RPZ_RELOAD_FLAG = "${General::swroot}/dns/rpz/reload.flag";
>>>>>>>>>>>> +
>>>>>>>>>>>> +# Configuration file for all available zonefiles
>>>>>>>>>>>> +# Format: index, name (unique), enabled (on/off), URL, remark
>>>>>>>>>>>> +my $ZONEFILES_CONF = "${General::swroot}/dns/rpz/zonefiles.conf";
>>>>>>>>>>>> +
>>>>>>>>>>>> +# Configuration file for custom lists
>>>>>>>>>>>> +# IDs: 0=allowlist, 1=blocklist, 2=options (allow/block enabled)
>>>>>>>>>>>> +my $CUSTOMLISTS_CONF = "${General::swroot}/dns/rpz/customlists.conf";
>>>>>>>>>>>> +
>>>>>>>>>>>> +# Export custom lists to rpz-config
>>>>>>>>>>>> +my $RPZ_ALLOWLIST = "${General::swroot}/dns/rpz/allowlist";
>>>>>>>>>>>> +my $RPZ_BLOCKLIST = "${General::swroot}/dns/rpz/blocklist";
>>>>>>>>>>>> +
>>>>>>>>>>>> +
>>>>>>>>>>>> +### Preparation ###
>>>>>>>>>>>> +
>>>>>>>>>>>> +# Create missing config files
>>>>>>>>>>>> +unless(-f $ZONEFILES_CONF) { &General::system('touch', "$ZONEFILES_CONF"); }
>>>>>>>>>>>> +unless(-f $CUSTOMLISTS_CONF) { &General::system('touch', "$CUSTOMLISTS_CONF"); }
>>>>>>>>>>>> +
>>>>>>>>>>>> +
>>>>>>>>>>>> +## Global gui data
>>>>>>>>>>>> +my $errormessage = "";
>>>>>>>>>>>> +
>>>>>>>>>>>> +## Global configuration data
>>>>>>>>>>>> +my %zonefiles = ();
>>>>>>>>>>>> +my %customlists = ();
>>>>>>>>>>>> +&_zonefiles_load();
>>>>>>>>>>>> +&_customlists_load();
>>>>>>>>>>>> +
>>>>>>>>>>>> +## Global CGI form data
>>>>>>>>>>>> +my %cgiparams = ();
>>>>>>>>>>>> +&Header::getcgihash(\%cgiparams);
>>>>>>>>>>>> +
>>>>>>>>>>>> +my $action = $cgiparams{'ACTION'} // 'NONE';
>>>>>>>>>>>> +my $action_key = $cgiparams{'KEY'} // ''; # entry being edited, empty = none/new
>>>>>>>>>>>> +
>>>>>>>>>>>> +
>>>>>>>>>>>> +###--- Process form actions ---###
>>>>>>>>>>>> +
>>>>>>>>>>>> +# Zonefiles action: Check whether the requested entry exists
>>>>>>>>>>>> +if((substr($action, 0, 3) eq 'ZF_') && ($action_key)) {
>>>>>>>>>>>> + unless(defined $zonefiles{$action_key}) {
>>>>>>>>>>>> + $errormessage = &_rpz_error_tr(204, $action_key);
>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>> + }
>>>>>>>>>>>> +}
>>>>>>>>>>>> +
>>>>>>>>>>>> +## Perform actions
>>>>>>>>>>>> +if($action eq 'ZF_SAVE') { ## Save new or modified zonefiles entry
>>>>>>>>>>>> + if(&_action_zf_save()) {
>>>>>>>>>>>> + $action = 'NONE'; # success, return to main page
>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>> + } else {
>>>>>>>>>>>> + $action = 'ZF_EDIT'; # error occured, keep editing
>>>>>>>>>>>> + }
>>>>>>>>>>>> +
>>>>>>>>>>>> +} elsif($action eq 'ZF_TOGGLE') { ## Toggle on/off
>>>>>>>>>>>> + if(&_action_zf_toggle()) {
>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>> + }
>>>>>>>>>>>> +
>>>>>>>>>>>> +} elsif($action eq 'ZF_REMOVE') { ## Remove entry
>>>>>>>>>>>> + if(&_action_zf_remove()) {
>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>> + }
>>>>>>>>>>>> +
>>>>>>>>>>>> +} elsif($action eq 'CL_SAVE') { ## Save custom lists
>>>>>>>>>>>> + if(&_action_cl_save()) {
>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>> + }
>>>>>>>>>>>> +
>>>>>>>>>>>> +} elsif($action eq 'RPZ_RELOAD') { ## Reload dns configuration
>>>>>>>>>>>> + if(&_action_rpz_reload()) {
>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>> + }
>>>>>>>>>>>> +
>>>>>>>>>>>> +} elsif($action eq 'UNB_RESTART') { ## Restart unbound service
>>>>>>>>>>>> + if(&_action_unb_restart()) {
>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>> + }
>>>>>>>>>>>> +
>>>>>>>>>>>> +}
>>>>>>>>>>>> +
>>>>>>>>>>>> +
>>>>>>>>>>>> +###--- Start GUI ---###
>>>>>>>>>>>> +
>>>>>>>>>>>> +## Start http output
>>>>>>>>>>>> +&Header::showhttpheaders();
>>>>>>>>>>>> +
>>>>>>>>>>>> +# Start HTML
>>>>>>>>>>>> +&Header::openpage($Lang::tr{'rpz'}, 1, $extraHead);
>>>>>>>>>>>> +&Header::openbigbox('100%', 'left', '');
>>>>>>>>>>>> +
>>>>>>>>>>>> +# Show error messages
>>>>>>>>>>>> +if($errormessage) {
>>>>>>>>>>>> + &_print_message($errormessage);
>>>>>>>>>>>> +}
>>>>>>>>>>>> +
>>>>>>>>>>>> +# Handle zonefile add/edit mode
>>>>>>>>>>>> +if($action eq "ZF_EDIT") {
>>>>>>>>>>>> + &_print_zonefile_editor();
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Finalize page and exit cleanly
>>>>>>>>>>>> + &Header::closebigbox();
>>>>>>>>>>>> + &Header::closepage();
>>>>>>>>>>>> + exit(0);
>>>>>>>>>>>> +}
>>>>>>>>>>>> +
>>>>>>>>>>>> +# Show gui elements
>>>>>>>>>>>> +&_print_zonefiles();
>>>>>>>>>>>> +&_print_customlists();
>>>>>>>>>>>> +&_print_gui_extras();
>>>>>>>>>>>> +
>>>>>>>>>>>> +&Header::closebigbox();
>>>>>>>>>>>> +&Header::closepage();
>>>>>>>>>>>> +
>>>>>>>>>>>> +###--- End of GUI ---###
>>>>>>>>>>>> +
>>>>>>>>>>>> +
>>>>>>>>>>>> +###--- Internal configuration file functions ---###
>>>>>>>>>>>> +
>>>>>>>>>>>> +# Load all available zonefiles from rpz-config and the internal configuration
>>>>>>>>>>>> +sub _zonefiles_load {
>>>>>>>>>>>> + # Clean start
>>>>>>>>>>>> + %zonefiles = ();
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Source 1: Get the currently enabled zonefiles from rpz-config (expected format [name]=[URL])
>>>>>>>>>>>> + my @enabled_files = &General::system_output('/usr/sbin/rpz-config', 'list');
>>>>>>>>>>>> +
>>>>>>>>>>>> + foreach my $row (@enabled_files) {
>>>>>>>>>>>> + chomp($row);
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Use regex instead of split() to skip non-matching lines
>>>>>>>>>>>> + next unless($row =~ /^(\w+)=(.+)$/);
>>>>>>>>>>>> + my ($name, $url) = ($1, $2);
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Unique names are already guaranteed by rpz-config
>>>>>>>>>>>> + if(&_rpz_validate_zonefile($name, $url, '', 0) == 0) {
>>>>>>>>>>>> + # Populate global data hash, mark all found entries as enabled
>>>>>>>>>>>> + my %entry = ('enabled' => 'on',
>>>>>>>>>>>> + 'url' => $url,
>>>>>>>>>>>> + 'remark' => $Lang::tr{'rpz zf imported'});
>>>>>>>>>>>> +
>>>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>>>> + }
>>>>>>>>>>>> + }
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Source 2: Get additional data and disabled entries from configuration file
>>>>>>>>>>>> + my %configured_files = ();
>>>>>>>>>>>> + &General::readhasharray($ZONEFILES_CONF, \%configured_files);
>>>>>>>>>>>> +
>>>>>>>>>>>> + foreach my $row (values (%configured_files)) {
>>>>>>>>>>>> + my ($name, $enabled, $url, $remark) = @$row;
>>>>>>>>>>>> + $remark //= "";
>>>>>>>>>>>> +
>>>>>>>>>>>> + next unless($name);
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Check whether this row belongs to an entry already imported from rpz-config
>>>>>>>>>>>> + if(defined $zonefiles{$name}) {
>>>>>>>>>>>> + # Existing entry, only merge additional data
>>>>>>>>>>>> + $zonefiles{$name}{'remark'} = $remark;
>>>>>>>>>>>> + } else {
>>>>>>>>>>>> + # Skip entry if it is marked as enabled but not found by rpz-config. It was then deleted manually
>>>>>>>>>>>> + if($enabled ne 'on') {
>>>>>>>>>>>> + # Populate global data hash
>>>>>>>>>>>> + my %entry = ('enabled' => 'off',
>>>>>>>>>>>> + 'url' => $url // "",
>>>>>>>>>>>> + 'remark' => $remark);
>>>>>>>>>>>> +
>>>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>>>> + }
>>>>>>>>>>>> + }
>>>>>>>>>>>> + }
>>>>>>>>>>>> +}
>>>>>>>>>>>> +
>>>>>>>>>>>> +# Save internal zonefiles configuration
>>>>>>>>>>>> +sub _zonefiles_save_conf {
>>>>>>>>>>>> + my $index = 0;
>>>>>>>>>>>> + my %export = ();
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Loop trough all zonefiles and create "hasharray" type export
>>>>>>>>>>>> + foreach my $name (keys %zonefiles) {
>>>>>>>>>>>> + my @entry = ($name,
>>>>>>>>>>>> + $zonefiles{$name}{'enabled'},
>>>>>>>>>>>> + $zonefiles{$name}{'url'},
>>>>>>>>>>>> + $zonefiles{$name}{'remark'});
>>>>>>>>>>>> +
>>>>>>>>>>>> + $export{$index++} = \@entry;
>>>>>>>>>>>> + }
>>>>>>>>>>>> +
>>>>>>>>>>>> + &General::writehasharray($ZONEFILES_CONF, \%export);
>>>>>>>>>>>> +}
>>>>>>>>>>>> +
>>>>>>>>>>>> +# Load custom lists from rpz-config and the internal configuration
>>>>>>>>>>>> +sub _customlists_load {
>>>>>>>>>>>> + # Clean start
>>>>>>>>>>>> + %customlists = ();
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Load configuration file
>>>>>>>>>>>> + my %lists_conf = ();
>>>>>>>>>>>> + &General::readhasharray($CUSTOMLISTS_CONF, \%lists_conf);
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Get list options, enabled by default to start import
>>>>>>>>>>>> + $customlists{'allow'}{'enabled'} = $lists_conf{2}[0] // 'on';
>>>>>>>>>>>> + $customlists{'block'}{'enabled'} = $lists_conf{2}[1] // 'on';
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Import enabled list from rpz-config, otherwise retrieve stored or empty list from configuration file
>>>>>>>>>>>> + if($customlists{'allow'}{'enabled'} eq 'on') {
>>>>>>>>>>>> + &_customlist_import('allow', $RPZ_ALLOWLIST);
>>>>>>>>>>>> + } else {
>>>>>>>>>>>> + $customlists{'allow'}{'list'} = $lists_conf{0} // [];
>>>>>>>>>>>> + }
>>>>>>>>>>>> + if($customlists{'block'}{'enabled'} eq 'on') {
>>>>>>>>>>>> + &_customlist_import('block', $RPZ_BLOCKLIST);
>>>>>>>>>>>> + } else {
>>>>>>>>>>>> + $customlists{'block'}{'list'} = $lists_conf{1} // [];
>>>>>>>>>>>> + }
>>>>>>>>>>>> +}
>>>>>>>>>>>> +
>>>>>>>>>>>> +# Save internal custom lists configuration
>>>>>>>>>>>> +sub _customlists_save_conf {
>>>>>>>>>>>> + my %export = ();
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Match IDs with import function
>>>>>>>>>>>> + $export{0} = $customlists{'allow'}{'list'};
>>>>>>>>>>>> + $export{1} = $customlists{'block'}{'list'};
>>>>>>>>>>>> + $export{2} = [$customlists{'allow'}{'enabled'}, $customlists{'block'}{'enabled'}];
>>>>>>>>>>>> +
>>>>>>>>>>>> + &General::writehasharray($CUSTOMLISTS_CONF, \%export);
>>>>>>>>>>>> +}
>>>>>>>>>>>> +
>>>>>>>>>>>> +# Import a custom list from plain file, returns empty list if file is missing
>>>>>>>>>>>> +sub _customlist_import {
>>>>>>>>>>>> + my ($listname, $filename) = @_;
>>>>>>>>>>>> + my @list = ();
>>>>>>>>>>>> +
>>>>>>>>>>>> + # File exists, load and check all lines
>>>>>>>>>>>> + if(-f $filename) {
>>>>>>>>>>>> + open(my $FH, '<', $filename) or die "Can't read $filename: $!";
>>>>>>>>>>>> + while(my $line = <$FH>) {
>>>>>>>>>>>> + chomp($line);
>>>>>>>>>>>> + push(@list, $line);
>>>>>>>>>>>> + }
>>>>>>>>>>>> + close($FH);
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Clean up imported data
>>>>>>>>>>>> + &_rpz_validate_customlist(\@list, 1);
>>>>>>>>>>>> + }
>>>>>>>>>>>> +
>>>>>>>>>>>> + $customlists{$listname}{'list'} = \@list;
>>>>>>>>>>>> +}
>>>>>>>>>>>> +
>>>>>>>>>>>> +# Export a custom list to plain file or clear file if list is disabled
>>>>>>>>>>>> +sub _customlist_export {
>>>>>>>>>>>> + my ($listname, $filename) = @_;
>>>>>>>>>>>> + return unless(defined $customlists{$listname});
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Write enabled domain list to file, otherwise save empty file
>>>>>>>>>>>> + open(my $FH, '>', $filename) or die "Can't write $filename: $!";
>>>>>>>>>>>> +
>>>>>>>>>>>> + if($customlists{$listname}{'enabled'} eq 'on') {
>>>>>>>>>>>> + foreach my $line (@{$customlists{$listname}{'list'}}) {
>>>>>>>>>>>> + print $FH "$line\n";
>>>>>>>>>>>> + }
>>>>>>>>>>>> + } else {
>>>>>>>>>>>> + print $FH "; Note: This list is currently disabled by $ENV{'SCRIPT_NAME'}\n";
>>>>>>>>>>>> + }
>>>>>>>>>>>> +
>>>>>>>>>>>> + close($FH);
>>>>>>>>>>>> +}
>>>>>>>>>>>> +
>>>>>>>>>>>> +
>>>>>>>>>>>> +###--- Internal gui functions ---###
>>>>>>>>>>>> +
>>>>>>>>>>>> +# Show simple message box
>>>>>>>>>>>> +sub _print_message {
>>>>>>>>>>>> + my ($message, $title) = @_;
>>>>>>>>>>>> + $title ||= $Lang::tr{'error messages'};
>>>>>>>>>>>> +
>>>>>>>>>>>> + &Header::openbox('100%', 'left', $title);
>>>>>>>>>>>> + print "<span>$message</span>";
>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>> +}
>>>>>>>>>>>> +
>>>>>>>>>>>> +# Show all zone files and related gui elements
>>>>>>>>>>>> +sub _print_zonefiles {
>>>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf'});
>>>>>>>>>>>> +
>>>>>>>>>>>> + print <<END
>>>>>>>>>>>> +<table class="tbl" width="100%">
>>>>>>>>>>>> + <tr>
>>>>>>>>>>>> + <th>$Lang::tr{'name'}</th>
>>>>>>>>>>>> + <th>URL</th>
>>>>>>>>>>>> + <th>$Lang::tr{'remark'}</th>
>>>>>>>>>>>> + <th colspan="3">$Lang::tr{'action'}</th>
>>>>>>>>>>>> + </tr>
>>>>>>>>>>>> +END
>>>>>>>>>>>> +;
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Sort zonefiles by name and loop trough all entries
>>>>>>>>>>>> + foreach my $name (sort keys %zonefiles) {
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Toggle button label translation
>>>>>>>>>>>> + my $toggle_tr = ($zonefiles{$name}{'enabled'} eq 'on') ? $Lang::tr{'click to disable'} : $Lang::tr{'click to enable'};
>>>>>>>>>>>> +
>>>>>>>>>>>> + print <<END
>>>>>>>>>>>> + <tr>
>>>>>>>>>>>> + <td>$name</td>
>>>>>>>>>>>> + <td>$zonefiles{$name}{'url'}</td>
>>>>>>>>>>>> + <td>$zonefiles{$name}{'remark'}</td>
>>>>>>>>>>>> +
>>>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_TOGGLE">
>>>>>>>>>>>> + <input type="image" src="/images/$zonefiles{$name}{'enabled'}.gif" title="$toggle_tr" alt="$toggle_tr">
>>>>>>>>>>>> + </form>
>>>>>>>>>>>> + </td>
>>>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_EDIT">
>>>>>>>>>>>> + <input type="image" src="/images/edit.gif" title="$Lang::tr{'edit'}" alt="$Lang::tr{'edit'}">
>>>>>>>>>>>> + </form>
>>>>>>>>>>>> + </td>
>>>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_REMOVE">
>>>>>>>>>>>> + <input type="image" src="/images/delete.gif" title="$Lang::tr{'remove'}" alt="$Lang::tr{'remove'}">
>>>>>>>>>>>> + </form>
>>>>>>>>>>>> + </td>
>>>>>>>>>>>> + </tr>
>>>>>>>>>>>> +END
>>>>>>>>>>>> +;
>>>>>>>>>>>> + }
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Disable reload button if not needed
>>>>>>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>>>>>>> +
>>>>>>>>>>>> + print <<END
>>>>>>>>>>>> +</table>
>>>>>>>>>>>> +
>>>>>>>>>>>> +<div class="right">
>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>> + <input type="hidden" name="KEY" value="">
>>>>>>>>>>>> + <button type="submit" name="ACTION" value="ZF_EDIT">$Lang::tr{'add'}</button>
>>>>>>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>>>>>>> + </form>
>>>>>>>>>>>> +</div>
>>>>>>>>>>>> +END
>>>>>>>>>>>> +;
>>>>>>>>>>>> +
>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>> +}
>>>>>>>>>>>> +
>>>>>>>>>>>> +# Show zonefiles entry editor
>>>>>>>>>>>> +sub _print_zonefile_editor {
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Key specified: Edit existing entry
>>>>>>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>>>>>>> + # Load data to be edited, but don't override already present values (allows user to edit after error)
>>>>>>>>>>>> + $cgiparams{'ZF_NAME'} //= $action_key;
>>>>>>>>>>>> + $cgiparams{'ZF_URL'} //= $zonefiles{$action_key}{'url'};
>>>>>>>>>>>> + $cgiparams{'ZF_REMARK'} //= $zonefiles{$action_key}{'remark'};
>>>>>>>>>>>> + }
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Fallback to empty form
>>>>>>>>>>>> + $cgiparams{'ZF_NAME'} //= "";
>>>>>>>>>>>> + $cgiparams{'ZF_URL'} //= "";
>>>>>>>>>>>> + $cgiparams{'ZF_REMARK'} //= "";
>>>>>>>>>>>> +
>>>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf editor'});
>>>>>>>>>>>> +
>>>>>>>>>>>> + print <<END
>>>>>>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>> +<input type="hidden" name="KEY" value="$action_key">
>>>>>>>>>>>> +<table width="100%">
>>>>>>>>>>>> + <tr>
>>>>>>>>>>>> + <td width="20%">$Lang::tr{'name'}: <img src="/blob.gif" alt="*"></td>
>>>>>>>>>>>> + <td><input type="text" name="ZF_NAME" value="$cgiparams{'ZF_NAME'}" size="40" maxlength="32" title="$Lang::tr{'rpz zf remark info'}" pattern="[a-zA-Z0-9_]{1,32}" required></td>
>>>>>>>>>>>> + </tr>
>>>>>>>>>>>> + <tr>
>>>>>>>>>>>> + <td width="20%">URL: <img src="/blob.gif" alt="*"></td>
>>>>>>>>>>>> + <td><input type="url" name="ZF_URL" value="$cgiparams{'ZF_URL'}" size="40" maxlength="128" required></td>
>>>>>>>>>>>> + </tr>
>>>>>>>>>>>> + <tr>
>>>>>>>>>>>> + <td width="20%">$Lang::tr{'remark'}:</td>
>>>>>>>>>>>> + <td><input type="text" name="ZF_REMARK" value="$cgiparams{'ZF_REMARK'}" size="40" maxlength="32"></td>
>>>>>>>>>>>> + </tr>
>>>>>>>>>>>> + <tr>
>>>>>>>>>>>> + <td colspan="2"><hr></td>
>>>>>>>>>>>> + </tr>
>>>>>>>>>>>> + <tr>
>>>>>>>>>>>> + <td width="55%"><img src="/blob.gif" alt="*"> $Lang::tr{'required field'}</td>
>>>>>>>>>>>> + <td align="right"><button type="submit" name="ACTION" value="ZF_SAVE">$Lang::tr{'save'}</button></td>
>>>>>>>>>>>> + </tr>
>>>>>>>>>>>> +</table>
>>>>>>>>>>>> +</form>
>>>>>>>>>>>> +
>>>>>>>>>>>> +<div class="right">
>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>> + <button type="submit" name="ACTION" value="NONE">$Lang::tr{'back'}</button>
>>>>>>>>>>>> + </form>
>>>>>>>>>>>> +</div>
>>>>>>>>>>>> +END
>>>>>>>>>>>> +;
>>>>>>>>>>>> +
>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>> +}
>>>>>>>>>>>> +
>>>>>>>>>>>> +# Show custom allow/block files and related gui elements
>>>>>>>>>>>> +sub _print_customlists {
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Load lists from config, unless they are currently being edited
>>>>>>>>>>>> + if($action ne 'CL_SAVE') {
>>>>>>>>>>>> + $cgiparams{'ALLOW_LIST'} = join("\n", @{$customlists{'allow'}{'list'}});
>>>>>>>>>>>> + $cgiparams{'BLOCK_LIST'} = join("\n", @{$customlists{'block'}{'list'}});
>>>>>>>>>>>> +
>>>>>>>>>>>> + $cgiparams{'ALLOW_ENABLED'} = ($customlists{'allow'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>>>>>>> + $cgiparams{'BLOCK_ENABLED'} = ($customlists{'block'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>>>>>>> + }
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Fallback to empty form
>>>>>>>>>>>> + $cgiparams{'ALLOW_LIST'} //= "";
>>>>>>>>>>>> + $cgiparams{'BLOCK_LIST'} //= "";
>>>>>>>>>>>> +
>>>>>>>>>>>> + # HTML checkboxes, unchecked = no or undef value in POST data
>>>>>>>>>>>> + my %checked = ();
>>>>>>>>>>>> + $checked{'ALLOW_ENABLED'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? " checked" : "";
>>>>>>>>>>>> + $checked{'BLOCK_ENABLED'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? " checked" : "";
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Disable reload button if not needed
>>>>>>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>>>>>>> +
>>>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz cl'});
>>>>>>>>>>>> +
>>>>>>>>>>>> + print <<END
>>>>>>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>> +<table width="100%">
>>>>>>>>>>>> + <tr>
>>>>>>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl allow'}</b><br>$Lang::tr{'rpz cl allow info'}</td>
>>>>>>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl block'}</b><br>$Lang::tr{'rpz cl block info'}</td>
>>>>>>>>>>>> + </tr>
>>>>>>>>>>>> + <tr>
>>>>>>>>>>>> + <td colspan="2"><textarea name="ALLOW_LIST" class="domainlist" cols="45">
>>>>>>>>>>>> +$cgiparams{'ALLOW_LIST'}</textarea></td>
>>>>>>>>>>>> + <td colspan="2"><textarea name="BLOCK_LIST" class="domainlist" cols="45">
>>>>>>>>>>>> +$cgiparams{'BLOCK_LIST'}</textarea></td>
>>>>>>>>>>>> + </tr>
>>>>>>>>>>>> + <tr>
>>>>>>>>>>>> + <td><label for="allow_enabled">$Lang::tr{'rpz cl allow enable'}</label></td>
>>>>>>>>>>>> + <td width="15%"><input type="checkbox" name="ALLOW_ENABLED" id="allow_enabled"$checked{'ALLOW_ENABLED'}></td>
>>>>>>>>>>>> + <td><label for="block_enabled">$Lang::tr{'rpz cl block enable'}</label></td>
>>>>>>>>>>>> + <td width="15%"><input type="checkbox" name="BLOCK_ENABLED" id="block_enabled"$checked{'BLOCK_ENABLED'}></td>
>>>>>>>>>>>> + </tr>
>>>>>>>>>>>> + <tr>
>>>>>>>>>>>> + <td colspan="4"><hr></td>
>>>>>>>>>>>> + </tr>
>>>>>>>>>>>> + <tr>
>>>>>>>>>>>> + <td align="right" colspan="4">
>>>>>>>>>>>> + <button type="submit" name="ACTION" value="CL_SAVE">$Lang::tr{'save'}</button>
>>>>>>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>>>>>>> + </td>
>>>>>>>>>>>> +</table>
>>>>>>>>>>>> +</form>
>>>>>>>>>>>> +END
>>>>>>>>>>>> +;
>>>>>>>>>>>> +
>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>> +}
>>>>>>>>>>>> +
>>>>>>>>>>>> +# Output javascript and extra gui elements
>>>>>>>>>>>> +sub _print_gui_extras {
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Apply/Restart button modifier key handler
>>>>>>>>>>>> + if(&_rpz_needs_reload()) {
>>>>>>>>>>>> + print <<END
>>>>>>>>>>>> +<script>
>>>>>>>>>>>> + // Commit modifier key handler
>>>>>>>>>>>> + (function(jq, document) {
>>>>>>>>>>>> + var keyEventsOn = false; // Keyboard events attached
>>>>>>>>>>>> + var keyModify = false; // Modifier key pressed
>>>>>>>>>>>> + var mouseHover = false; // Mouse over commit button
>>>>>>>>>>>> + var btnModified = false; // Button modified to "Restart"
>>>>>>>>>>>> +
>>>>>>>>>>>> + // Document-level key events, enable only while cursor is over button
>>>>>>>>>>>> + function attachKeyEvents() {
>>>>>>>>>>>> + if(keyEventsOn) {
>>>>>>>>>>>> + return;
>>>>>>>>>>>> + }
>>>>>>>>>>>> + keyEventsOn = true;
>>>>>>>>>>>> +
>>>>>>>>>>>> + jq(document).on("keydown.rpz", function(event) {
>>>>>>>>>>>> + if((!keyModify) && event.shiftKey) {
>>>>>>>>>>>> + keyModify = true;
>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>> + }
>>>>>>>>>>>> + });
>>>>>>>>>>>> + jq(document).on("keyup.rpz", function(event) {
>>>>>>>>>>>> + if(keyModify && (!event.shiftKey)) {
>>>>>>>>>>>> + keyModify = false;
>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>> + }
>>>>>>>>>>>> + });
>>>>>>>>>>>> + }
>>>>>>>>>>>> + function removeKeyEvents() {
>>>>>>>>>>>> + keyModify = false;
>>>>>>>>>>>> + if(keyEventsOn) {
>>>>>>>>>>>> + jq(document).off("keydown.rpz keyup.rpz");
>>>>>>>>>>>> + keyEventsOn = false;
>>>>>>>>>>>> + }
>>>>>>>>>>>> + }
>>>>>>>>>>>> +
>>>>>>>>>>>> + // Attach mouse hover events to commit buttons
>>>>>>>>>>>> + function attachMouseEvents() {
>>>>>>>>>>>> + jq("button.commit").on("mouseenter", function(event) {
>>>>>>>>>>>> + if(!mouseHover) {
>>>>>>>>>>>> + mouseHover = true;
>>>>>>>>>>>> + attachKeyEvents();
>>>>>>>>>>>> + // Handle already pressed key
>>>>>>>>>>>> + keyModify = !!(event.shiftKey);
>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>> + }
>>>>>>>>>>>> + });
>>>>>>>>>>>> +
>>>>>>>>>>>> + // Cursor moved away: Disable key listener to minimize events
>>>>>>>>>>>> + jq("button.commit").on("mouseleave", function() {
>>>>>>>>>>>> + if(mouseHover) {
>>>>>>>>>>>> + mouseHover = false;
>>>>>>>>>>>> + removeKeyEvents();
>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>> + }
>>>>>>>>>>>> + });
>>>>>>>>>>>> + }
>>>>>>>>>>>> +
>>>>>>>>>>>> + // Modify commit button
>>>>>>>>>>>> + function handleModify() {
>>>>>>>>>>>> + let modify = mouseHover && keyModify;
>>>>>>>>>>>> + if(btnModified != modify) {
>>>>>>>>>>>> + if(modify) {
>>>>>>>>>>>> + jq("button.commit").text("$Lang::tr{'restart'}").val("UNB_RESTART");
>>>>>>>>>>>> + } else {
>>>>>>>>>>>> + jq("button.commit").text("$Lang::tr{'rpz apply'}").val("RPZ_RELOAD");
>>>>>>>>>>>> + }
>>>>>>>>>>>> + btnModified = modify;
>>>>>>>>>>>> + }
>>>>>>>>>>>> + }
>>>>>>>>>>>> +
>>>>>>>>>>>> + // jQuery DOM ready
>>>>>>>>>>>> + jq(function() {
>>>>>>>>>>>> + attachMouseEvents();
>>>>>>>>>>>> + });
>>>>>>>>>>>> + })(jQuery, document);
>>>>>>>>>>>> +</script>
>>>>>>>>>>>> +END
>>>>>>>>>>>> +;
>>>>>>>>>>>> + } # End of modifier key handler
>>>>>>>>>>>> +
>>>>>>>>>>>> +}
>>>>>>>>>>>> +
>>>>>>>>>>>> +
>>>>>>>>>>>> +###--- Internal action processing functions ---###
>>>>>>>>>>>> +
>>>>>>>>>>>> +# Toggle zonefile on/off
>>>>>>>>>>>> +sub _action_zf_toggle {
>>>>>>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>>>>>>> +
>>>>>>>>>>>> + my $result = 0;
>>>>>>>>>>>> + my $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Perform toggle action
>>>>>>>>>>>> + if($enabled eq 'on') {
>>>>>>>>>>>> + $enabled = 'off';
>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>>>>>>> + } else {
>>>>>>>>>>>> + $enabled = 'on';
>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $action_key, $zonefiles{$action_key}{'url'}, '--no-reload');
>>>>>>>>>>>> + }
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Save changes
>>>>>>>>>>>> + $zonefiles{$action_key}{'enabled'} = $enabled;
>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>> +
>>>>>>>>>>>> + return 1;
>>>>>>>>>>>> +}
>>>>>>>>>>>> +
>>>>>>>>>>>> +# Remove zonefile
>>>>>>>>>>>> +sub _action_zf_remove {
>>>>>>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Remove from rpz-config if currently active
>>>>>>>>>>>> + if($zonefiles{$action_key}{'enabled'} eq 'on') {
>>>>>>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>> + }
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Remove from data hash and save changes
>>>>>>>>>>>> + delete $zonefiles{$action_key};
>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Clear action_key, as the entry is now removed entirely
>>>>>>>>>>>> + $action_key = "";
>>>>>>>>>>>> +
>>>>>>>>>>>> + return 1;
>>>>>>>>>>>> +}
>>>>>>>>>>>> +
>>>>>>>>>>>> +# Create or update zonefile entry
>>>>>>>>>>>> +# Returns undef if gui needs to stay in editor mode
>>>>>>>>>>>> +sub _action_zf_save {
>>>>>>>>>>>> + my $result = 0;
>>>>>>>>>>>> +
>>>>>>>>>>>> + my $name = $cgiparams{'ZF_NAME'} // "";
>>>>>>>>>>>> + my $url = $cgiparams{'ZF_URL'} // "";
>>>>>>>>>>>> + my $remark = $cgiparams{'ZF_REMARK'} // "";
>>>>>>>>>>>> + my $enabled = 'on'; # Enable new entries by default
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Note on variables:
>>>>>>>>>>>> + # name = unique key, will be used to address the entry
>>>>>>>>>>>> + # action_key = name of the entry being edited, empty for new entry
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Only check for unique name if it changed
>>>>>>>>>>>> + # (this also checks new entries because the action_key is empty in this case)
>>>>>>>>>>>> + $result = &_rpz_validate_zonefile($name, $url, $remark, (lc($name) ne lc($action_key)));
>>>>>>>>>>>> + return unless &_rpz_check_result($result, 0);
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Edit existing entry: Determine what was changed
>>>>>>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>>>>>>> + # Name und URL remain unchanged, only save remark and finish
>>>>>>>>>>>> + if(($name eq $action_key) && ($url eq $zonefiles{$action_key}{'url'})) {
>>>>>>>>>>>> + $zonefiles{$action_key}{'remark'} = $remark;
>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>> +
>>>>>>>>>>>> + return 1;
>>>>>>>>>>>> + }
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Entry was changed and needs to be recreated, preserve status
>>>>>>>>>>>> + $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Remove from rpz-config
>>>>>>>>>>>> + return unless &_action_zf_remove();
>>>>>>>>>>>> + }
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Add new entry to rpz-config
>>>>>>>>>>>> + if($enabled eq 'on') {
>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $name, $url, '--no-reload');
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>> + }
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Add to global data hash and save changes
>>>>>>>>>>>> + my %entry = ('enabled' => $enabled,
>>>>>>>>>>>> + 'url' => $url,
>>>>>>>>>>>> + 'remark' => $remark);
>>>>>>>>>>>> +
>>>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>> +
>>>>>>>>>>>> + return 1;
>>>>>>>>>>>> +}
>>>>>>>>>>>> +
>>>>>>>>>>>> +# Save custom lists
>>>>>>>>>>>> +sub _action_cl_save {
>>>>>>>>>>>> + return unless((defined $cgiparams{'ALLOW_LIST'}) && (defined $cgiparams{'BLOCK_LIST'}));
>>>>>>>>>>>> +
>>>>>>>>>>>> + my $result = 0;
>>>>>>>>>>>> +
>>>>>>>>>>>> + my @allowlist = split(/\R/, $cgiparams{'ALLOW_LIST'});
>>>>>>>>>>>> + my @blocklist = split(/\R/, $cgiparams{'BLOCK_LIST'});
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Validate lists
>>>>>>>>>>>> + $result = &_rpz_validate_customlist(\@allowlist);
>>>>>>>>>>>> + if($result != 0) {
>>>>>>>>>>>> + $errormessage = &_rpz_error_tr(202, $result);
>>>>>>>>>>>> + return;
>>>>>>>>>>>> + }
>>>>>>>>>>>> + $result = &_rpz_validate_customlist(\@blocklist);
>>>>>>>>>>>> + if($result != 0) {
>>>>>>>>>>>> + $errormessage = &_rpz_error_tr(203, $result);
>>>>>>>>>>>> + return;
>>>>>>>>>>>> + }
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Add to global data hash and save changes
>>>>>>>>>>>> + $customlists{'allow'}{'list'} = \@allowlist;
>>>>>>>>>>>> + $customlists{'block'}{'list'} = \@blocklist;
>>>>>>>>>>>> + $customlists{'allow'}{'enabled'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? 'on' : 'off';
>>>>>>>>>>>> + $customlists{'block'}{'enabled'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? 'on' : 'off';
>>>>>>>>>>>> +
>>>>>>>>>>>> + &_customlists_save_conf();
>>>>>>>>>>>> + &_customlist_export('allow', $RPZ_ALLOWLIST);
>>>>>>>>>>>> + &_customlist_export('block', $RPZ_BLOCKLIST);
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Make new lists, request service reload on success
>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-make', 'allowblock', '--no-reload');
>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>> +
>>>>>>>>>>>> + return 1;
>>>>>>>>>>>> +}
>>>>>>>>>>>> +
>>>>>>>>>>>> +# Trigger rpz-config reload
>>>>>>>>>>>> +sub _action_rpz_reload {
>>>>>>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Immediately clear flag to prevent multiple reloads
>>>>>>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>>>>>>> + }
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Perform reload, recreate reload flag on error to enable retry
>>>>>>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'reload');
>>>>>>>>>>>> + if(not &_rpz_check_result($result, 0)) {
>>>>>>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>>>>>>> + return;
>>>>>>>>>>>> + }
>>>>>>>>>>>> +
>>>>>>>>>>>> + return 1;
>>>>>>>>>>>> +}
>>>>>>>>>>>> +
>>>>>>>>>>>> +# Trigger unbound restart
>>>>>>>>>>>> +sub _action_unb_restart {
>>>>>>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Immediately clear flag to prevent multiple restarts
>>>>>>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>>>>>>> + }
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Perform restart, unboundctrl always exits zero
>>>>>>>>>>>> + &General::system('/usr/local/bin/unboundctrl', 'restart');
>>>>>>>>>>>> +
>>>>>>>>>>>> + return 1;
>>>>>>>>>>>> +}
>>>>>>>>>>>> +
>>>>>>>>>>>> +
>>>>>>>>>>>> +###--- Internal rpz-config functions ---###
>>>>>>>>>>>> +
>>>>>>>>>>>> +# Translate rpz-config exitcodes and messages
>>>>>>>>>>>> +# 100-199: rpz-config, 200-299: webgui
>>>>>>>>>>>> +sub _rpz_error_tr {
>>>>>>>>>>>> + my ($error, $append) = @_;
>>>>>>>>>>>> + $append //= '';
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Translate numeric exit codes
>>>>>>>>>>>> + if(looks_like_number($error)) {
>>>>>>>>>>>> + if(defined $Lang::tr{"rpz exitcode $error"}) {
>>>>>>>>>>>> + $error = $Lang::tr{"rpz exitcode $error"};
>>>>>>>>>>>> + }
>>>>>>>>>>>> + }
>>>>>>>>>>>> +
>>>>>>>>>>>> + return "RPZ $Lang::tr{'error'}: $error" . &Header::escape($append);
>>>>>>>>>>>> +}
>>>>>>>>>>>> +
>>>>>>>>>>>> +# Check result of rpz-config system call, request reload on success
>>>>>>>>>>>> +sub _rpz_check_result {
>>>>>>>>>>>> + my ($result, $request_reload) = @_;
>>>>>>>>>>>> + $request_reload //= 0;
>>>>>>>>>>>> +
>>>>>>>>>>>> + # exitcode 0 = success
>>>>>>>>>>>> + if($result != 0) {
>>>>>>>>>>>> + $errormessage = &_rpz_error_tr($result);
>>>>>>>>>>>> + return;
>>>>>>>>>>>> + }
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Set reload flag
>>>>>>>>>>>> + if($request_reload) {
>>>>>>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>>>>>>> + }
>>>>>>>>>>>> +
>>>>>>>>>>>> + return 1;
>>>>>>>>>>>> +}
>>>>>>>>>>>> +
>>>>>>>>>>>> +# Test whether reload flag is set
>>>>>>>>>>>> +sub _rpz_needs_reload {
>>>>>>>>>>>> + return (-f $RPZ_RELOAD_FLAG);
>>>>>>>>>>>> +}
>>>>>>>>>>>> +
>>>>>>>>>>>> +# Validate a zonefile entry, returns rpz-config exitcode on failure. Use _rpz_check_result to verify.
>>>>>>>>>>>> +# unique = check for unique name
>>>>>>>>>>>> +sub _rpz_validate_zonefile {
>>>>>>>>>>>> + my ($name, $url, $remark, $unique) = @_;
>>>>>>>>>>>> + $unique //= 1;
>>>>>>>>>>>> +
>>>>>>>>>>>> + unless($name =~ /^[a-zA-Z0-9_]{1,32}$/) {
>>>>>>>>>>>> + return 101;
>>>>>>>>>>>> + }
>>>>>>>>>>>> + unless($url =~ /^[\w+\.:;\/\\&@#%?=\-~|!]{1,128}$/) {
>>>>>>>>>>>> + return 105;
>>>>>>>>>>>> + }
>>>>>>>>>>>> + unless($remark =~ /^[\w \-()\.:;*\/\\?!&=]{0,32}$/) {
>>>>>>>>>>>> + return 201;
>>>>>>>>>>>> + }
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Check against already existing names
>>>>>>>>>>>> + if($unique) {
>>>>>>>>>>>> + foreach my $existing (keys %zonefiles) {
>>>>>>>>>>>> + if(lc($name) eq lc($existing)) {
>>>>>>>>>>>> + return 104;
>>>>>>>>>>>> + }
>>>>>>>>>>>> + }
>>>>>>>>>>>> + }
>>>>>>>>>>>> +
>>>>>>>>>>>> + return 0;
>>>>>>>>>>>> +}
>>>>>>>>>>>> +
>>>>>>>>>>>> +# Validate a custom list, returns number of rejected line on failure. Check for non-zero results.
>>>>>>>>>>>> +# listref = array reference, cleanup = remove invalid entries instead of returning an error
>>>>>>>>>>>> +sub _rpz_validate_customlist {
>>>>>>>>>>>> + my ($listref, $cleanup) = @_;
>>>>>>>>>>>> + $cleanup //= 0;
>>>>>>>>>>>> +
>>>>>>>>>>>> + foreach my $index (reverse 0..$#{$listref}) {
>>>>>>>>>>>> + my $row = @$listref[$index];
>>>>>>>>>>>> + next unless($row); # Skip/allow empty lines
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Reject/remove everything besides wildcard domains and remarks
>>>>>>>>>>>> + if((not &General::validwildcarddomainname($row)) && (not $row =~ /^;[\w \-()\.:;*\/\\?!&=]*$/)) {
>>>>>>>>>>>> + unless($cleanup) {
>>>>>>>>>>>> + # +1 for user friendly line number and to ensure non-zero exitcode
>>>>>>>>>>>> + return $index + 1;
>>>>>>>>>>>> + }
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Remove current row
>>>>>>>>>>>> + splice(@$listref, $index, 1);
>>>>>>>>>>>> + }
>>>>>>>>>>>> + }
>>>>>>>>>>>> +
>>>>>>>>>>>> + return 0;
>>>>>>>>>>>> +}
>>>>>>>>>>>> +
>>>>>>>>>>>> +
>>>>>>>>>>>> +###--- Internal misc functions ---###
>>>>>>>>>>>> +
>>>>>>>>>>>> +# Send HTTP 303 redirect headers for post/request/get pattern
>>>>>>>>>>>> +# (Must be sent before calling &Header::showhttpheaders())
>>>>>>>>>>>> +sub _http_prg_redirect {
>>>>>>>>>>>> + my $location = "https://$ENV{'SERVER_NAME'}:$ENV{'SERVER_PORT'}$ENV{'SCRIPT_NAME'}";
>>>>>>>>>>>> + print "Status: 303 See Other\n";
>>>>>>>>>>>> + print "Location: $location\n";
>>>>>>>>>>>> +}
>>>>>>>>>>>> diff --git a/lfs/rpz b/lfs/rpz
>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>> index 000000000..7ddbc38e5
>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>> +++ b/lfs/rpz
>>>>>>>>>>>> @@ -0,0 +1,96 @@
>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>> +# #
>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>> +# #
>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>> +# #
>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>> +
>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>> +# Definitions
>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>> +
>>>>>>>>>>>> +include Config
>>>>>>>>>>>> +
>>>>>>>>>>>> +SUMMARY = response policy zone - RPZ reputation system for unbound DNS
>>>>>>>>>>>> +
>>>>>>>>>>>> +VER = 1.0.0
>>>>>>>>>>>> +
>>>>>>>>>>>> +THISAPP = rpz-$(VER)
>>>>>>>>>>>> +DIR_APP = $(DIR_SRC)/$(THISAPP)
>>>>>>>>>>>> +TARGET = $(DIR_INFO)/$(THISAPP)
>>>>>>>>>>>> +
>>>>>>>>>>>> +PROG = rpz
>>>>>>>>>>>> +PAK_VER = 18
>>>>>>>>>>>> +
>>>>>>>>>>>> +DEPS =
>>>>>>>>>>>> +
>>>>>>>>>>>> +SERVICES =
>>>>>>>>>>>> +
>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>> +# Top-level Rules
>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>> +
>>>>>>>>>>>> +install : $(TARGET)
>>>>>>>>>>>> +
>>>>>>>>>>>> +check :
>>>>>>>>>>>> +
>>>>>>>>>>>> +download :
>>>>>>>>>>>> +
>>>>>>>>>>>> +b2 :
>>>>>>>>>>>> +
>>>>>>>>>>>> +dist:
>>>>>>>>>>>> + @$(PAK)
>>>>>>>>>>>> +
>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>> +# Installation Details
>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>> +
>>>>>>>>>>>> +$(TARGET) :
>>>>>>>>>>>> + @$(PREBUILD)
>>>>>>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>>>>>>> +
>>>>>>>>>>>> + # RPZ scripts
>>>>>>>>>>>> + install --verbose --mode=755 \
>>>>>>>>>>>> + $(DIR_CONF)/rpz/{rpz-config,rpz-metrics,rpz-sleep,rpz-make,rpz-functions} \
>>>>>>>>>>>> + --target-directory=/usr/sbin
>>>>>>>>>>>> +
>>>>>>>>>>>> + # RPZ config files
>>>>>>>>>>>> + mkdir -pv /etc/unbound/local.d
>>>>>>>>>>>> + install --verbose --mode=644 --owner=nobody --group=nobody \
>>>>>>>>>>>> + $(DIR_CONF)/rpz/00-rpz.conf \
>>>>>>>>>>>> + --target-directory=/etc/unbound/local.d
>>>>>>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/local.d
>>>>>>>>>>>> +
>>>>>>>>>>>> + # RPZ custom list files for allow and block
>>>>>>>>>>>> + mkdir -pv /var/ipfire/dns/rpz
>>>>>>>>>>>> + touch /var/ipfire/dns/rpz/{allowlist,blocklist}
>>>>>>>>>>>> + chown --verbose --recursive nobody:nobody /var/ipfire/dns/rpz
>>>>>>>>>>>> +
>>>>>>>>>>>> + # RPZ zone files
>>>>>>>>>>>> + # create empty RPZ config file to avoid a unbound config error
>>>>>>>>>>>> + mkdir -pv /etc/unbound/zonefiles
>>>>>>>>>>>> + touch /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/zonefiles
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Install addon-specific language-files
>>>>>>>>>>>> + install --verbose --mode=004 $(DIR_CONF)/rpz/rpz.*.pl \
>>>>>>>>>>>> + --target-directory=/var/ipfire/addon-lang
>>>>>>>>>>>> +
>>>>>>>>>>>> + # Install backup definition
>>>>>>>>>>>> + cp -vf $(DIR_CONF)/backup/includes/rpz /var/ipfire/backup/addons/includes/rpz
>>>>>>>>>>>> +
>>>>>>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>>>>>>> + @$(POSTBUILD)
>>>>>>>>>>>> diff --git a/make.sh b/make.sh
>>>>>>>>>>>> index 827ea9e77..a77535b13 100755
>>>>>>>>>>>> --- a/make.sh
>>>>>>>>>>>> +++ b/make.sh
>>>>>>>>>>>> @@ -390,7 +390,7 @@ prepareenv() {
>>>>>>>>>>>> if [ "${free_space}" -lt "${required_space}" ]; then
>>>>>>>>>>>> # Add any consumed space
>>>>>>>>>>>> while read -r consumed_space path; do
>>>>>>>>>>>> - (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>>>>>>> + (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>>>>>>> done <<< "$(du --summarize --bytes "${BUILD_DIR}" "${IMAGES_DIR}" "${LOG_DIR}" 2>/dev/null)"
>>>>>>>>>>>> fi
>>>>>>>>>>>> @@ -2087,6 +2087,7 @@ build_system() {
>>>>>>>>>>>> lfsmake2 btrfs-progs
>>>>>>>>>>>> lfsmake2 inotify-tools
>>>>>>>>>>>> lfsmake2 grub-btrfs
>>>>>>>>>>>> + lfsmake2 rpz
>>>>>>>>>>>> lfsmake2 linux
>>>>>>>>>>>> lfsmake2 rtl8812au
>>>>>>>>>>>> diff --git a/src/paks/rpz/install.sh b/src/paks/rpz/install.sh
>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>> index 000000000..ef99bf742
>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>> +++ b/src/paks/rpz/install.sh
>>>>>>>>>>>> @@ -0,0 +1,36 @@
>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>> +# #
>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>> +# #
>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>> +# #
>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>> +#
>>>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>>>> +extract_files
>>>>>>>>>>>> +restore_backup ${NAME}
>>>>>>>>>>>> +
>>>>>>>>>>>> +# fix user created files
>>>>>>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>>>>>>> + /etc/unbound/zonefiles \
>>>>>>>>>>>> + /etc/unbound/local.d
>>>>>>>>>>>> +
>>>>>>>>>>>> +# Update Language cache
>>>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>>>> +
>>>>>>>>>>>> +# restart unbound to load config file
>>>>>>>>>>>> +/etc/init.d/unbound restart
>>>>>>>>>>>> diff --git a/src/paks/rpz/uninstall.sh b/src/paks/rpz/uninstall.sh
>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>> index 000000000..e11427df3
>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>> +++ b/src/paks/rpz/uninstall.sh
>>>>>>>>>>>> @@ -0,0 +1,38 @@
>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>> +# #
>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>> +# #
>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>> +# #
>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>> +#
>>>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>>>> +
>>>>>>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>>>>>>> +/etc/init.d/unbound stop
>>>>>>>>>>>> +
>>>>>>>>>>>> +make_backup ${NAME}
>>>>>>>>>>>> +remove_files
>>>>>>>>>>>> +
>>>>>>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>>>>>>> +# "[1723428668] unbound-control[17117:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>>>>>>> +
>>>>>>>>>>>> +# Update Language cache
>>>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>>>> +
>>>>>>>>>>>> +# start unbound to load unbound config file
>>>>>>>>>>>> +/etc/init.d/unbound start
>>>>>>>>>>>> diff --git a/src/paks/rpz/update.sh b/src/paks/rpz/update.sh
>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>> index 000000000..9bc340bc6
>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>> +++ b/src/paks/rpz/update.sh
>>>>>>>>>>>> @@ -0,0 +1,52 @@
>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>> +# #
>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>> +# #
>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>> +# #
>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>> +#
>>>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>>>> +
>>>>>>>>>>>> +# from update.sh
>>>>>>>>>>>> +extract_backup_includes
>>>>>>>>>>>> +
>>>>>>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>>>>>>> +/etc/init.d/unbound stop
>>>>>>>>>>>> +
>>>>>>>>>>>> +# from uninstall.sh
>>>>>>>>>>>> +make_backup ${NAME}
>>>>>>>>>>>> +remove_files
>>>>>>>>>>>> +
>>>>>>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>>>>>>> +# "unbound-control[nn:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>>>>>>> +
>>>>>>>>>>>> +# from install.sh
>>>>>>>>>>>> +extract_files
>>>>>>>>>>>> +restore_backup ${NAME}
>>>>>>>>>>>> +
>>>>>>>>>>>> +# fix user created files
>>>>>>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>>>>>>> + /etc/unbound/zonefiles \
>>>>>>>>>>>> + /etc/unbound/local.d
>>>>>>>>>>>> +
>>>>>>>>>>>> +# Update Language cache
>>>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>>>> +
>>>>>>>>>>>> +# restart unbound to load config files
>>>>>>>>>>>> +/etc/init.d/unbound start
>>>>>>>>>>>> --
>>>>>>>>>>>> 2.39.5
>>>>>>>>>>>>
>>>>>>>>>> Jon
>>>>>>>>>> --
>>>>>>>>>> Jon Murphy
>>>>>>>>>> jon.murphy@ipfire.org
>>>>>>>>
>>>>>>>> Jon
>>>>>>>> --
>>>>>>>> Jon Murphy
>>>>>>>> jon.murphy@ipfire.org
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
2025-03-24 10:17 ` Michael Tremer
@ 2025-03-24 13:33 ` Bernhard Bitsch
2025-03-24 14:25 ` Michael Tremer
0 siblings, 1 reply; 30+ messages in thread
From: Bernhard Bitsch @ 2025-03-24 13:33 UTC (permalink / raw)
To: development
Am 24.03.2025 um 11:17 schrieb Michael Tremer:
> Hello Jon,
>
>> On 24 Mar 2025, at 00:00, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>
>> Michael,
>>
>> FYI - I was wrong Unbound RPZ is _not_ watching the serial number, it is watching the "refresh", the number after the serial number.
>
> Refresh just tells the client how often to check for an update.
>
> If that is actually being set by the list publisher, then we have another problem here, because they could put some insanely low value there and we would then DDoS their infrastructure. I think we should keep it like we have it in other places that we control how often we want to check or pull for updates.
>
You are right. But an extra update process wastes additional processor
time. The update mechanism of unbound does the check for update (
however it is realized ) nevertheless.
>> I understand that you don’t speak C, but you got the information from somewhere. Documentation maybe? Since that is out of date very often I like to consult the code.
>> From testing. Downloading rpz files using rpz unbound, and watching what happens. If the rpz file is setup for "once per day" refresh, then it only downloads one time.
>>
>>
>>
>> However that won’t solve our problem . . . and having no cache.
>> In `/etc/unbound/tuning.conf` there is `rrset-cache-size: 128m`. Are you referring to a different cache.
>
> Naturally unbound is loading the zone into its memory which we generally call cache.
>
> When I say cache I am thinking about persistent data storage across multiple restarts of Unbound. If I am downloading 100 MiB of RPZ lists (which is presumably still on the lower end) and I reboot my firewall, I do not want to download the same data again. We can only ever download a list *once* unless we are 100% certain that it has changed. Then we can download it once again.
>
The RPZ lists are stored in files in persistent storage. Unbound creates
the internal cache from these.
>> Maybe we need to implement both?
>>
>> Yes. There are very few AXFR list (I think only four were found). And many more HTTPS rpz files.
>>
>>
>>
>> Jon
>>
>>
>> ------ Original Message ------
>> From "Michael Tremer" <michael.tremer@ipfire.org>
>> To "Jon Murphy" <jon.murphy@ipfire.org>
>> Cc "IPFire: Development-List" <development@lists.ipfire.org>
>> Date 3/20/2025 11:26:43 AM
>> Subject Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
>>
>>> Hello Jon,
>>> Please don’t forget to Cc the list...
>>>
>>>>
>>>> On 19 Mar 2025, at 18:27, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>> Michael,
>>>>
>>>>>
>>>>> Where in the code is this implemented? I cannot find anything like this:
>>>>
>>>> Keep in mind I am not a "C" person. Maybe in this section?:
>>>> https://git.ipfire.org/?p=thirdparty/unbound.git;a=blob;f=services/authzone.c;hb=30b9cb5f813003d0a2b1c2e678652396615b1b7d#l5875
>>>
>>> This where the AXFR response is being handled when doing a DNS zone transfer. This code is not being called when performing a HTTP download.
>>> I understand that you don’t speak C, but you got the information from somewhere. Documentation maybe? Since that is out of date very often I like to consult the code.
>>>
>>>>
>>>> —
>>>> When I was just learning about RPZ I created a separate RPZ file for testing. When I changed the SOA line with a new serial number, the RPZ file download would happen in about 5 minutes.
>>>> https://people.ipfire.org/~jon/sblack-adhoc.rpz
>>>
>>> It might well be that the file is not being reloaded if the download matches the content that unbound already has. That would of course save some resources.
>>> However that won’t solve our problem with redundant downloads and having no cache.
>>>
>>>>
>>>> That is how I found out the SOA line is watched for a serial number change.
>>>> I’ll reconfirm my findings.
>>>>
>>>>>
>>>>>>> The second reason is that we have a lot of firewalls out there. Not all of them will enable this feature and all of the lists, but even if it is a good chunk, we will generate terabytes of traffic which put load on the infrastructure and will cost money. It simply is not what we want to do, regardless of self-hosting those lists and pulling them from somewhere else.
>>>>
>>>> So I understand, are you thinking of hosting RPZ AXFR (DNS zone transfer) on IPFire infrastructure?
>>>
>>> No, I don’t think that we can generally do this. The biggest problem is licensing as we cannot take anyones content and host it ourselves. We would re-distribute those lists and that will only work with permission of the publishers. I assume that would be too much work to actually get some useful content out there. We might limit ourselves to only those lists that are under a very permissive license. Nobody wants that.
>>> From a technical point of view, DNS over TCP might not be very nice in terms of forging the transfer and so we would need TLS as well… It should work, but even if we would be able to encourage other people to publish their lists I doubt they would implement DNS over TLS for authoritative DNS. That standard is in very early stages as well.
>>> As far as I can see, those vendors who offer a list as a commercial product are using DNS to distribute it (e.g. Spamhaus). Those people who have made this all a hobby are throwing the lists onto GitHub and let them handle the traffic.
>>> Maybe we need to implement both?
>>> -Michael
>>>
>>>>
>>>> Jon
>>>> On 3/19/25 5:35 AM, Michael Tremer wrote:
>>>>>
>>>>>
>>>>> Hello Jon,
>>>>> Where in the code is this implemented? I cannot find anything like this:
>>>>> Unbound loads the entire file into memory and then starts parsing it. The only special treatment there is is to check whether the first line is a valid zone entry. It does not even have to be a SOA record.
>>>>> https://git.ipfire.org/?p=thirdparty/unbound.git;a=blob;f=services/authzone.c;hb=30b9cb5f813003d0a2b1c2e678652396615b1b7d#l1188
>>>>> I am also concerned that Unbound will not be able to support an upstream proxy for any downloads. The caching situation is also unclear for me, so I believe that we will be looking at writing a custom downloader that implements all these things.
>>>>> -Michael
>>>>>
>>>>>>
>>>>>> On 19 Mar 2025, at 02:58, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>> Michael,
>>>>>>
>>>>>>>
>>>>>>> The emphasis is on the repeated downloads of the same list. That is
>>>>>>
>>>>>> > what cannot happen.
>>>>>> The Unbound RPZ code, as installed within IPFire, watches for a change
>>>>>> in the SOA line of each RPZ file. This is an example of the first few
>>>>>> lines for every RPZ file.
>>>>>> $TTL 300
>>>>>> @ SOA localhost. root.localhost. 1742298960 43200 3600 86400 300
>>>>>> NS localhost.
>>>>>> ;
>>>>>> ; Title: HaGeZi's Pop-Up Ads DNS Blocklist
>>>>>> ; Description: Blocks annoying and malicious pop-up ads.
>>>>>> If the SOA serial number changes (e.g. the 1742298960), then Unbound RPZ
>>>>>> code does its thing and downloads. Otherwise there is no download.
>>>>>>
>>>>>>>
>>>>>>> So there has to be a way to ensure that we won’t download a list again
>>>>>>
>>>>>> > unless it has actually changed.
>>>>>> This should do what you want but I may be missing your point.
>>>>>>
>>>>>>>
>>>>>>> DNS has a builtin functionality called AXFR. It simply does the job
>>>>>>
>>>>>> > for you. I was just wondering whether that was not being used.
>>>>>> I need to read about AXFR/IXFR and learn a little more.
>>>>>> Jon
>>>>>> On 3/17/25 5:35 AM, Michael Tremer wrote:
>>>>>>>
>>>>>>>
>>>>>>> Good Morning Jon,
>>>>>>>
>>>>>>>>
>>>>>>>> On 16 Mar 2025, at 17:00, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>> Michael,
>>>>>>>> I was reading through you response again an I want to understand this post:
>>>>>>>>
>>>>>>>>>
>>>>>>>>> I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>>>>>>>
>>>>>>>> So if RPZ doesn't use HTTPS, what is it using? I am missing a key point here.
>>>>>>>
>>>>>>> The emphasis is on the repeated downloads of the same list. That is what cannot happen.
>>>>>>> Although it might not affect a lot of people in our general user-base, there are some that have a metered connection and will pay for data by volume. Some of the lists I looked at are just under 20 MiB. Therefore we need to keep any traffic down to a minimum. The second reason is that we have a lot of firewalls out there. Not all of them will enable this feature and all of the lists, but even if it is a good chunk, we will generate terabytes of traffic which put load on the infrastructure and will cost money. It simply is not what we want to do, regardless of self-hosting those lists and pulling them from somewhere else.
>>>>>>> So there has to be a way to ensure that we won’t download a list again unless it has actually changed.
>>>>>>> DNS has a builtin functionality called AXFR. It simply does the job for you. I was just wondering whether that was not being used.
>>>>>>> HTTPS is an option because that is simply what we use elsewhere, but extra functionality will have to be built for it.
>>>>>>> -Michael
>>>>>>>
>>>>>>>>
>>>>>>>> Jon
>>>>>>>> On 2/13/25 3:34 PM, jon wrote:
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> Michael,
>>>>>>>>> I’ve read through your comments a few times and I ended up with many more questions.
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>>>>>>>
>>>>>>>>> To me the efforts to get new code accepted seem to have changed and it seemed easier in the past. In the past I made the Core Team aware via the Dev Mailing List and wrote a simple two or three paragraphs of "What is it? / What is the value? / Here is the code"
>>>>>>>>> So in an effort to move forward: How exactly is something presented to the Core Team?
>>>>>>>>> Is there an example of a recent effort that was presented that I can see as a sample? (This type of info can also be added to the Wiki)
>>>>>>>>> I understand you want it this way, but I don’t know what exactly is needed. Please be specific.
>>>>>>>>> Jon
>>>>>>>>> PS - I am not ignoring your other comments, I am just trying to move forward and keep things simple.
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> On Feb 8, 2025, at 1:27 PM, Michael Tremer <michael.tremer@ipfire.org> wrote:
>>>>>>>>>> Hello Jon,
>>>>>>>>>> Thanks for your reply. And good that you are copying everyone into this conversation.
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> On 8 Feb 2025, at 18:41, jon <jon.murphy@ipfire.org> wrote:
>>>>>>>>>>> Michael,
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort
>>>>>>>>>>>
>>>>>>>>>>> Yes, this has been a separate effort (a very public separate effort). Yes, as you pointed this out early on with the "proof-of-concept" and then my request for people to help test RPZ. Nothing was hidden.
>>>>>>>>>>> This was done because you (and maybe others) did not have the time and I wanted to help and because I needed assistance with RPZ. I tried my best to do this without bothering you.
>>>>>>>>>>
>>>>>>>>>> I don’t that it is accurate that nobody wanted to help on this. The list was always open - although not every email has been replied to swiftly it is also your responsibility to raise a question again if it was missed. People here have open ears.
>>>>>>>>>> It was also stated on this very list on in our documentation that working on something without involving the core team is a risky undertaking. Of course IPFire is free software and so everyone is free to fork if they wish to do so.
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>> and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls.
>>>>>>>>>>>
>>>>>>>>>>> You were aware many steps along the way. See your email on July 28, 2024, August 15, 2024, September 30, 2024, December 23, 2024, and January 16. My attempts to get the team involved were met with "things are busy" and sometimes silence. (Yes, I get it, people are busy.)
>>>>>>>>>>> You and Adolf, Leo, Erik and Bernhard have been aware since the beginning. You mention you were aware of the "proof-of-concept". If you include those beginning posts, since Sep 2023.
>>>>>>>>>>
>>>>>>>>>> Yes, I am aware of a proof-of-concept that I have been running myself for a long time. I am also aware of the efforts that you have been taking.
>>>>>>>>>> Yet I don’t think there has ever been any joint effort, or am I seeing that wrong?
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>> This has not been discussed . . . on our calls.
>>>>>>>>>>>
>>>>>>>>>>> On the July 28th you stated:
>>>>>>>>>>> "We have talked about RPZ many times on the monthly call since the URL filter feature is falling more and more out of fashion. I think there is also many posts about this on the forum."
>>>>>>>>>>> Please don’t insult me again by stating "you know what I mean".
>>>>>>>>>>> And it has been discussed but not documented in the Monthly Meeting notes.
>>>>>>>>>>
>>>>>>>>>> I am not at all insulting you. I don’t want to take this down to a personal level at all. This is a public mailing list and people who read this don’t need to listen to an argument we are having. They are here for the tech inside IPFire.
>>>>>>>>>> When I wrote that it has not been discussed that does not mean that we have not been touching on the topic. We have been talking about lots of things on the calls, the weather, politics, how our pets are. None of that makes it to the logs. What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>> Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation.
>>>>>>>>>>>
>>>>>>>>>>> Regular conversation on the Dev Mailing list is many times met with silence. I get it, people are busy.
>>>>>>>>>>> And regular two-way conversation doesn’t happen on the list. At least not with me. I’d be happy to point out the posts that were met with silence.
>>>>>>>>>>> Again, I get it, people are busy.
>>>>>>>>>>
>>>>>>>>>> And you think my emails are not being met with silence? This has nothing to do with this specific topic. This has something to do with how occupied people are and how engaged they are on certain topics. Not everyone is involved in all the things and simply will ignore emails simply based on their subject line.
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> But the "dip here to the list" were my attempts to get a conversation started. As I said, many time met with silence.
>>>>>>>>>>> The only place I was not met with silence was on the Community. You have a great group of people in the Community. It is a shame you don’t want to have others help. It would reduce your workload.
>>>>>>>>>>
>>>>>>>>>> You should stop making statements that are not true. Who doesn’t want anyone to help?
>>>>>>>>>> Not having this conversation on a Saturday evening would reduce my workload. At least it would free up time for something else. Helping with the things that are already on the go would reduce the workload of the entire team. Starting one thing at a time and finishing it is a lot better to manage than starting a hundred things and not even finish one. I can tell you that I already have a hundred things on the go.
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>> Therefore, what am I supposed to do with this email?
>>>>>>>>>>>
>>>>>>>>>>> To me it is beyond obvious…
>>>>>>>>>>> If it isn’t what you want, then guide me with how to do this the correct way. And be specific. I am trying to help. I am trying to make things better. I am trying to do things the right way.
>>>>>>>>>>
>>>>>>>>>> To me it isn’t. This is yet another project that has been dumped to the list like so many before and later on everyone has left to have the team deal with the rest.
>>>>>>>>>> It is a huge patch set. You explained what the vision is, but that is about it. There is no chance this will continue if this disagreement isn’t solved first. I didn’t even look at the code.
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>> I don’t want to merge code that I don’t agree with.
>>>>>>>>>>>
>>>>>>>>>>> I asked multiple times if you "agreed with the concept" and again, met with silence. Yes I get it, people are busy.
>>>>>>>>>>
>>>>>>>>>> Having support for RPZ? Yes, it was definitely on the roadmap. That I agree with.
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>> So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>>>>>>>
>>>>>>>>>>> You mentioned this a in the past, but for some reason you do not disclose what I dismissed. Why do you continue to make this harder, wouldn’t it not be easier to tell me what I have dismissed?
>>>>>>>>>>> I have sent multiple emails trying to answer your concerns and comments. On July 28, Aug 14, Aug 22, Aug 23, Sep 30, etc.
>>>>>>>>>>> I’ve gone through all of the questions you asked and I cannot find a "dismissed" item.
>>>>>>>>>>
>>>>>>>>>> Maybe I need to be *more clear*. I feel humoured by this.
>>>>>>>>>> It is late on a Saturday and I want my dinner soon, but certainly I have stated that this should never be an add-on considering it is supposed to replace URL Filter. We should never allow people to add their own sources. I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>>>>>>>
>>>>>>>>>>> The maintainers of Unbound and/or RPZ?
>>>>>>>>>>> The maintainers of Hagezi list, the threatfox list, the urlhaus list, etc.?
>>>>>>>>>>> What else? The maintainers or the RPZ scripts? That is me. Let’s talk!
>>>>>>>>>>
>>>>>>>>>> You. I don’t care much about the providers of the lists.
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> See, this is where it gets confusing. There are hundreds of open source packages as part of IPFire. Pick the last five years of items added to the IPFire build. You're telling me you have "constructive conversation with the maintainers" of all of the added packages?
>>>>>>>>>>
>>>>>>>>>> They publish their software and they don’t care whether I am pulling it or not. They publish it with the commitment to maintain it - sometimes for better and sometimes for worse.
>>>>>>>>>> You care about me pulling your code and I don’t know whether you would commit to maintain this.
>>>>>>>>>> These two are very different cases.
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> Pick the IP Blocklists list (i.e., 3CORESEC, ABUSECH, DSHIELD, SPAMHAUS, etc.) or the Suricata lists (i.e.,Emergingthreats.net <http://emergingthreats.net/>,Abuse.ch <http://abuse.ch/>, etc.). So you’ve have "constructive conversation with the maintainers"?
>>>>>>>>>>
>>>>>>>>>> Yes, occasionally I have phone calls with a few of these providers.
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>>>>>>>
>>>>>>>>>>> Ha! Yes a surprise. In the beginning you seemed interested as IPFire needed a replacement for URL Filter. You asked good questions about the lists picked, asked for the value to the users, etc. And I answered the best I could.
>>>>>>>>>>> You even asked: “Why is this realised as an add-on and not part of the core system?” from your Jul 28, 2024 email.
>>>>>>>>>>
>>>>>>>>>> Ah, so, why is the patch creating an add-on? Not that I am saying that what I say is law, but it has not been challenged either. If my input is being ignored, why should I put this to the top of my list of priorities? I am not disappointed about this, just trying to be very good with my time.
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> And on January 16, 2025 I wrote a message looking for help. And you were kind to respond quickly. So in three weeks time, since the kind response, something has changed. You went from supportive to "this".
>>>>>>>>>>> So yes, I am surprised.
>>>>>>>>>>
>>>>>>>>>> Well, maybe I should not have replied to that email. It was clear that you were on some path that was not right, but you were not interested before in finding the right path from the beginning.
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>>>>>>>
>>>>>>>>>>> Be more specific, what has to change? What exactly did I dismiss?
>>>>>>>>>>
>>>>>>>>>> Dismissal is just my assumption. I don’t know what you actually did with my feedback. I can only see the end product that does not seem contain much of it. Repeatedly I have been pointing out that we should think before we build. I am sure a lot of hours have now gone into some code that simply does not satisfy me. And I am not not talking about the code itself, what it does is what I don’t think is right for us.
>>>>>>>>>> The process is very clear for me that we should first of all think whether we want a certain feature now. Then there should be a clear roadmap for everyone to follow; tasks can be split-up as we go and hopefully then have something that is maintainable, interesting for our users and even would do us proud. This is how this should work.
>>>>>>>>>> So, what has to change? I don’t think with shouting at each other, throwing patches around and making me generally unhappy is a good start.
>>>>>>>>>> -Michael
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> Jon
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>> On Feb 6, 2025, at 2:13 PM, Michael Tremer <michael.tremer@ipfire.org> wrote:
>>>>>>>>>>>> Hello Jon,
>>>>>>>>>>>> Well, here we are again with another patch regarding this feature.
>>>>>>>>>>>> I cannot quite see from your email what the question is, but if this is a request to have this merged into IPFire, I am once again sorry to disappoint you.
>>>>>>>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls. Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation. Therefore, what am I supposed to do with this email?
>>>>>>>>>>>> I don’t want to merge code that I don’t agree with. So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>>>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>>>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>>>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>>>>>>>> All the best,
>>>>>>>>>>>> -Michael
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>> On 6 Feb 2025, at 16:35, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>>>>>>> What is it?
>>>>>>>>>>>>> Response Policy Zone (RPZ) is a mechanism to define local policies in a
>>>>>>>>>>>>> standardized way and load those policies from external sources.
>>>>>>>>>>>>> Bottom line: RPZ allows admins to easily block access to websites via DNS lookup.
>>>>>>>>>>>>> RPZ can block websites via categories. Examples include: fake websites, annoying
>>>>>>>>>>>>> pop-up ads, newly registered domains, DoH bypass sites, bad "host" services,
>>>>>>>>>>>>> maliscious top level domains (e.g., *.zip, *.mov), piracy, gambling, pornography,
>>>>>>>>>>>>> and more. RPZ lists come from various RPZ providers and their available
>>>>>>>>>>>>> catagories.
>>>>>>>>>>>>> This RPZ add-on enables the RPZ functionality by adding a couple lines in a
>>>>>>>>>>>>> configuration file. This add-on simply adds configuration files and adds
>>>>>>>>>>>>> scripts (config, metrics and sleep) to make RPZ easier for the admin to use.
>>>>>>>>>>>>> The RPZ scripts include additional languages: German, Spanish, French, Turkish,
>>>>>>>>>>>>> and Italian.
>>>>>>>>>>>>> RPZ itself was release in 2010 and has been part of the IPFire build since ~2015.
>>>>>>>>>>>>> Why is it needed? What is its value?
>>>>>>>>>>>>> - The RPZ concept places this filtering into IPFire, our internet access
>>>>>>>>>>>>> gateway, which is (should be) solely used as DNS source of the internal network.
>>>>>>>>>>>>> - As most sites use HTTPS it makes it difficult to filter traffic with URL
>>>>>>>>>>>>> Filter without also properly configuring conventional (non-transparent)
>>>>>>>>>>>>> mode on the proxy. RPZ is a nice replacement for the URL Filter.
>>>>>>>>>>>>> - No need to install and maintain an additional device like PiHole or AdBlock
>>>>>>>>>>>>> browser extensions on multiple user devices.
>>>>>>>>>>>>> - This is an additional layer of protection for users. Less worry someone will
>>>>>>>>>>>>> click on something that gets them into trouble. And, saying this with emphasis,
>>>>>>>>>>>>> the ability to do it in one place!
>>>>>>>>>>>>> - Blocked sites save on unneeded traffic and can lessen the threat of malware
>>>>>>>>>>>>> in advertisements
>>>>>>>>>>>>> - Logging allows the admin to see the site blocked and take actions
>>>>>>>>>>>>> - RPZ will be used at the home, home-office (work from home), schools,
>>>>>>>>>>>>> ministerial, and at the office. Device counts are small (2-6) to medium (~80)
>>>>>>>>>>>>> to mediam-large (200+).
>>>>>>>>>>>>> - RPZ can block ads, popups, phishing, scammers, spyware, malware, annoying
>>>>>>>>>>>>> popups, NSFW links, DOH servers, and the usual internet trash.
>>>>>>>>>>>>> ------------------------------
>>>>>>>>>>>>> Change Log for RPZ add-on
>>>>>>>>>>>>> rpz-1.0.0-18 on 2025-02-05
>>>>>>>>>>>>> - Build for approval & release as IPFire add-on
>>>>>>>>>>>>> ---
>>>>>>>>>>>>> rpz-beta-0.1.18-18.ipfire on 2025-02-01
>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>> - new feature: added a mod key to force a unbound restart
>>>>>>>>>>>>> rpz-config and rpz-make:
>>>>>>>>>>>>> - new feature: added action for unbound restart `rpz-config unbound-restart`
>>>>>>>>>>>>> rpz-metrics:
>>>>>>>>>>>>> - simple reformatting
>>>>>>>>>>>>> - rename far right column from "last update" to "last download"
>>>>>>>>>>>>> ---
>>>>>>>>>>>>> rpz-beta-0.1.17-17.ipfire on 2024-12-09
>>>>>>>>>>>>> rpz-make
>>>>>>>>>>>>> - bug fix: corrected validation regex for wildcards like: `*.domain.com`
>>>>>>>>>>>>> ---
>>>>>>>>>>>>> rpz-beta-0.1.16-16.ipfire on 2024-11-18
>>>>>>>>>>>>> rpz-make
>>>>>>>>>>>>> - new feature: updated validation regex
>>>>>>>>>>>>> - bug fix: moved validation to beginning of process. Now we validate before
>>>>>>>>>>>>> creating config files.
>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>> - new feature: use CSS color variables of the main ipfire theme
>>>>>>>>>>>>> - bug fix: empty zonefile remarks were stored as “undef” and caused a warning
>>>>>>>>>>>>> - bug fix: HTML textarea removes the first empty line in a custom list
>>>>>>>>>>>>> - thank you Leo!
>>>>>>>>>>>>> ---
>>>>>>>>>>>>> rpz-beta-0.1.15-15.ipfire on 2024-11-04
>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>> - new feature: added new language file for Turkish (thank you Peppe)
>>>>>>>>>>>>> rpz-make
>>>>>>>>>>>>> - bug fix: corrected empty allow/block list issue. An empty allow/block list
>>>>>>>>>>>>> will now remove contents of allow/block.rpz files and remove unneeded
>>>>>>>>>>>>> allow/block.conf file. (thank you iptom)
>>>>>>>>>>>>> ---
>>>>>>>>>>>>> rpz-beta-0.1.14-14.ipfire on 2024-10-29
>>>>>>>>>>>>> rpz-config:
>>>>>>>>>>>>> - bug fix: correct missing rpz extension. `rpz-config list` displayed URL
>>>>>>>>>>>>> incorrectly (thank you Bernhard)
>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>> - bug fix: remove extra `"` in language files (thank you Bernhard)
>>>>>>>>>>>>> - new feature: slightly dim "apply" button when not enabled
>>>>>>>>>>>>> ---
>>>>>>>>>>>>> rpz-beta-0.1.13-13.ipfire on 2024-10-27
>>>>>>>>>>>>> - skipped
>>>>>>>>>>>>> ---
>>>>>>>>>>>>> rpz-beta-0.1.12-12.ipfire on 2024-10-21
>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>> - new feature: added new language file for French (thank you gw-ipfire)
>>>>>>>>>>>>> ---
>>>>>>>>>>>>> rpz-beta-0.1.11-11.ipfire on 2024-10-18
>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>> - new feature: added new language file for Italian (thank you umberto)
>>>>>>>>>>>>> - new feature: added new language file for Spanish (thank you Roberto)
>>>>>>>>>>>>> ---
>>>>>>>>>>>>> rpz-beta-0.1.10-10.ipfire on 2024-10-15
>>>>>>>>>>>>> rpz-make:
>>>>>>>>>>>>> - bug fix: corrected validation error for a custom list entry (thank you siosios)
>>>>>>>>>>>>> - e.g., `*.cloudflare-dns.com`
>>>>>>>>>>>>> install.sh:
>>>>>>>>>>>>> - bug fix: add chown to correct user created files
>>>>>>>>>>>>> update.sh:
>>>>>>>>>>>>> - bug fix: add chown to correct user created files (thank you siosios)
>>>>>>>>>>>>> ---
>>>>>>>>>>>>> rpz-beta-0.1.9-9.ipfire on 2024-10-08
>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>> - new feature: added new language file for German (thank you Leo)
>>>>>>>>>>>>> - bug fix: add missing "rpz exitcode 110"
>>>>>>>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>>>>>>> ---
>>>>>>>>>>>>> rpz-beta-0.1.8-8.ipfire on 2024-10-04
>>>>>>>>>>>>> - skipped
>>>>>>>>>>>>> ---
>>>>>>>>>>>>> rpz-beta-0.1.7-7.ipfire on 2024-10-03
>>>>>>>>>>>>> All:
>>>>>>>>>>>>> - new feature: includes beta version numbers for pakfire package,
>>>>>>>>>>>>> instead of only `rpz-1.0.0-1.ipfire`, for each release.
>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>> - new feature: added new WebGUI at `rpz.cgi`
>>>>>>>>>>>>> - a BIG thank you to Leo Hofmann for all of his work creating the webgui!!
>>>>>>>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>>>>>>> rpz-make:
>>>>>>>>>>>>> - new feature: validate entries in allowlist and blocklist
>>>>>>>>>>>>> - new feature: add "no-reload" option for WebGUI
>>>>>>>>>>>>> rpz-metrics:
>>>>>>>>>>>>> - new feature: info can be sorted by name, by hit count, by line count, by
>>>>>>>>>>>>> "enabled" list or all lists
>>>>>>>>>>>>> backups:
>>>>>>>>>>>>> - bug fix: include all files in `/var/ipfire/dns/rpz` directory in backup
>>>>>>>>>>>>> update.sh:
>>>>>>>>>>>>> - bug fix: corrected ownership for `/var/ipfire/dns/rpz` directory during an
>>>>>>>>>>>>> update
>>>>>>>>>>>>> Build:
>>>>>>>>>>>>> - bug fix: `block.rpz.conf` and `block.rpz` from build. Files to be created
>>>>>>>>>>>>> by `rpz-make`
>>>>>>>>>>>>> WebGUI and German language file
>>>>>>>>>>>>> Contribution-by: Leo-Andres Hofmann <hofmann@leo-andres.de>
>>>>>>>>>>>>> Spanish language file
>>>>>>>>>>>>> Contribution-by: Roberto Peña
>>>>>>>>>>>>> Italian language file
>>>>>>>>>>>>> Contribution-by: Umberto Parma
>>>>>>>>>>>>> French language file
>>>>>>>>>>>>> Contribution-by: gw-ipfire
>>>>>>>>>>>>> Turkish language file
>>>>>>>>>>>>> Contribution-by: Peppe Tech
>>>>>>>>>>>>> Contribution-by: Bernhard Bitsch <bbitsch@ipfire.org>
>>>>>>>>>>>>> Contribution-by: Erik Kapfer <erik.kapfer@ipfire.org>
>>>>>>>>>>>>> Signed-off-by: Jon Murphy <jon.murphy@ipfire.org
>>>>>>>>>>>>> ---
>>>>>>>>>>>>> config/backup/includes/rpz | 4 +
>>>>>>>>>>>>> config/cfgroot/manualpages | 1 +
>>>>>>>>>>>>> config/menu/EX-rpz.menu | 6 +
>>>>>>>>>>>>> config/rootfiles/common/configroot | 1 +
>>>>>>>>>>>>> config/rootfiles/common/web-user-interface | 1 +
>>>>>>>>>>>>> config/rootfiles/packages/rpz | 20 +
>>>>>>>>>>>>> config/rpz/00-rpz.conf | 10 +
>>>>>>>>>>>>> config/rpz/rpz-config | 130 +++
>>>>>>>>>>>>> config/rpz/rpz-functions | 85 ++
>>>>>>>>>>>>> config/rpz/rpz-make | 203 +++++
>>>>>>>>>>>>> config/rpz/rpz-metrics | 170 ++++
>>>>>>>>>>>>> config/rpz/rpz-sleep | 58 ++
>>>>>>>>>>>>> config/rpz/rpz.de.pl | 30 +
>>>>>>>>>>>>> config/rpz/rpz.en.pl | 30 +
>>>>>>>>>>>>> config/rpz/rpz.es.pl | 30 +
>>>>>>>>>>>>> config/rpz/rpz.fr.pl | 30 +
>>>>>>>>>>>>> config/rpz/rpz.it.pl | 30 +
>>>>>>>>>>>>> config/rpz/rpz.tr.pl | 30 +
>>>>>>>>>>>>> html/cgi-bin/rpz.cgi | 923 +++++++++++++++++++++
>>>>>>>>>>>>> lfs/rpz | 96 +++
>>>>>>>>>>>>> make.sh | 3 +-
>>>>>>>>>>>>> src/paks/rpz/install.sh | 36 +
>>>>>>>>>>>>> src/paks/rpz/uninstall.sh | 38 +
>>>>>>>>>>>>> src/paks/rpz/update.sh | 52 ++
>>>>>>>>>>>>> 24 files changed, 2016 insertions(+), 1 deletion(-)
>>>>>>>>>>>>> create mode 100644 config/backup/includes/rpz
>>>>>>>>>>>>> create mode 100644 config/menu/EX-rpz.menu
>>>>>>>>>>>>> create mode 100644 config/rootfiles/packages/rpz
>>>>>>>>>>>>> create mode 100644 config/rpz/00-rpz.conf
>>>>>>>>>>>>> create mode 100644 config/rpz/rpz-config
>>>>>>>>>>>>> create mode 100644 config/rpz/rpz-functions
>>>>>>>>>>>>> create mode 100644 config/rpz/rpz-make
>>>>>>>>>>>>> create mode 100755 config/rpz/rpz-metrics
>>>>>>>>>>>>> create mode 100755 config/rpz/rpz-sleep
>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.de.pl
>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.en.pl
>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.es.pl
>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.fr.pl
>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.it.pl
>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.tr.pl
>>>>>>>>>>>>> create mode 100644 html/cgi-bin/rpz.cgi
>>>>>>>>>>>>> create mode 100644 lfs/rpz
>>>>>>>>>>>>> create mode 100644 src/paks/rpz/install.sh
>>>>>>>>>>>>> create mode 100644 src/paks/rpz/uninstall.sh
>>>>>>>>>>>>> create mode 100644 src/paks/rpz/update.sh
>>>>>>>>>>>>> diff --git a/config/backup/includes/rpz b/config/backup/includes/rpz
>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>> index 000000000..36513e494
>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>> +++ b/config/backup/includes/rpz
>>>>>>>>>>>>> @@ -0,0 +1,4 @@
>>>>>>>>>>>>> +/var/ipfire/dns/rpz/*
>>>>>>>>>>>>> +/etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>> +/etc/unbound/zonefiles/block.rpz
>>>>>>>>>>>>> +/etc/unbound/local.d/*rpz.conf
>>>>>>>>>>>>> diff --git a/config/cfgroot/manualpages b/config/cfgroot/manualpages
>>>>>>>>>>>>> index 1f7e01efc..d3a48c633 100644
>>>>>>>>>>>>> --- a/config/cfgroot/manualpages
>>>>>>>>>>>>> +++ b/config/cfgroot/manualpages
>>>>>>>>>>>>> @@ -70,6 +70,7 @@ pakfire.cgi=configuration/ipfire/pakfire
>>>>>>>>>>>>> wlanap.cgi=addons/wireless
>>>>>>>>>>>>> tor.cgi=addons/tor
>>>>>>>>>>>>> samba.cgi=addons/samba
>>>>>>>>>>>>> +rpz.cgi=addons/rpz
>>>>>>>>>>>>> # Logs menu
>>>>>>>>>>>>> logs.cgi/summary.dat=configuration/logs/summary
>>>>>>>>>>>>> diff --git a/config/menu/EX-rpz.menu b/config/menu/EX-rpz.menu
>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>> index 000000000..2f4daf410
>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>> +++ b/config/menu/EX-rpz.menu
>>>>>>>>>>>>> @@ -0,0 +1,6 @@
>>>>>>>>>>>>> +$subipfire->{'20.rpz'} = {
>>>>>>>>>>>>> + 'caption' => $Lang::tr{'rpz'},
>>>>>>>>>>>>> + 'uri' => '/cgi-bin/rpz.cgi',
>>>>>>>>>>>>> + 'title' => "RPZ",
>>>>>>>>>>>>> + 'enabled' => 1,
>>>>>>>>>>>>> +};
>>>>>>>>>>>>> diff --git a/config/rootfiles/common/configroot b/config/rootfiles/common/configroot
>>>>>>>>>>>>> index 9839eee45..b30d6aae4 100644
>>>>>>>>>>>>> --- a/config/rootfiles/common/configroot
>>>>>>>>>>>>> +++ b/config/rootfiles/common/configroot
>>>>>>>>>>>>> @@ -120,6 +120,7 @@ var/ipfire/menu.d/70-log.menu
>>>>>>>>>>>>> #var/ipfire/menu.d/EX-apcupsd.menu
>>>>>>>>>>>>> #var/ipfire/menu.d/EX-guardian.menu
>>>>>>>>>>>>> #var/ipfire/menu.d/EX-mympd.menu
>>>>>>>>>>>>> +#var/ipfire/menu.d/EX-rpz.menu
>>>>>>>>>>>>> #var/ipfire/menu.d/EX-samba.menu
>>>>>>>>>>>>> #var/ipfire/menu.d/EX-tor.menu
>>>>>>>>>>>>> #var/ipfire/menu.d/EX-transmission.menu
>>>>>>>>>>>>> diff --git a/config/rootfiles/common/web-user-interface b/config/rootfiles/common/web-user-interface
>>>>>>>>>>>>> index 816241dae..e00464076 100644
>>>>>>>>>>>>> --- a/config/rootfiles/common/web-user-interface
>>>>>>>>>>>>> +++ b/config/rootfiles/common/web-user-interface
>>>>>>>>>>>>> @@ -69,6 +69,7 @@ srv/web/ipfire/cgi-bin/proxy.cgi
>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/qos.cgi
>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/remote.cgi
>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/routing.cgi
>>>>>>>>>>>>> +#srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>>>>>>>> #srv/web/ipfire/cgi-bin/samba.cgi
>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/services.cgi
>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/shutdown.cgi
>>>>>>>>>>>>> diff --git a/config/rootfiles/packages/rpz b/config/rootfiles/packages/rpz
>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>> index 000000000..1c8663049
>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>> +++ b/config/rootfiles/packages/rpz
>>>>>>>>>>>>> @@ -0,0 +1,20 @@
>>>>>>>>>>>>> +etc/unbound/local.d/00-rpz.conf
>>>>>>>>>>>>> +etc/unbound/zonefiles
>>>>>>>>>>>>> +etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>> +usr/sbin/rpz-config
>>>>>>>>>>>>> +usr/sbin/rpz-functions
>>>>>>>>>>>>> +usr/sbin/rpz-make
>>>>>>>>>>>>> +usr/sbin/rpz-metrics
>>>>>>>>>>>>> +usr/sbin/rpz-sleep
>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.de.pl
>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.en.pl
>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.es.pl
>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.fr.pl
>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.it.pl
>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.tr.pl
>>>>>>>>>>>>> +var/ipfire/backup/addons/includes/rpz
>>>>>>>>>>>>> +var/ipfire/dns/rpz
>>>>>>>>>>>>> +var/ipfire/dns/rpz/allowlist
>>>>>>>>>>>>> +var/ipfire/dns/rpz/blocklist
>>>>>>>>>>>>> +var/ipfire/menu.d/EX-rpz.menu
>>>>>>>>>>>>> +srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>>>>>>>> diff --git a/config/rpz/00-rpz.conf b/config/rpz/00-rpz.conf
>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>> index 000000000..f005a4f2e
>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>> +++ b/config/rpz/00-rpz.conf
>>>>>>>>>>>>> @@ -0,0 +1,10 @@
>>>>>>>>>>>>> +server:
>>>>>>>>>>>>> + module-config: "respip validator iterator"
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +rpz:
>>>>>>>>>>>>> + name: allow.rpz
>>>>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>> + rpz-action-override: passthru
>>>>>>>>>>>>> + rpz-log: yes
>>>>>>>>>>>>> + rpz-log-name: allow
>>>>>>>>>>>>> + rpz-signal-nxdomain-ra: yes
>>>>>>>>>>>>> diff --git a/config/rpz/rpz-config b/config/rpz/rpz-config
>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>> index 000000000..c72d50f9b
>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>> +++ b/config/rpz/rpz-config
>>>>>>>>>>>>> @@ -0,0 +1,130 @@
>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>> +# #
>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>> +# #
>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>> +# #
>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +version="2025-01-11 - v44"
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +tagName="unbound"
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +rpzAction="${1}" # input RPZ action
>>>>>>>>>>>>> +rpzName="${2}" # input RPZ name
>>>>>>>>>>>>> +rpzURL="${3}" # input RPZ URL
>>>>>>>>>>>>> +rpzOption1="${4}" # input RPZ option #1
>>>>>>>>>>>>> +rpzOption2="${5}" # input RPZ option #2
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +rpzConfig="/etc/unbound/local.d/${rpzName}.rpz.conf" # output zone conf file
>>>>>>>>>>>>> +rpzFile="/etc/unbound/zonefiles/${rpzName}.rpz" # output for RPZ file
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>>>>> + case "$1" in
>>>>>>>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>>>>>>>> + esac
>>>>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>>>>> +done
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +case "${rpzAction}" in
>>>>>>>>>>>>> + # add new rpz list
>>>>>>>>>>>>> + add )
>>>>>>>>>>>>> + check_name "${rpzName}" # is this a valid name?
>>>>>>>>>>>>> + # does this config already exist? If yes, then exit
>>>>>>>>>>>>> + if [[ -f "${rpzConfig}" ]] ; then
>>>>>>>>>>>>> + msg_log "error: rpz: duplicate - ${rpzConfig} already exists. exit"
>>>>>>>>>>>>> + exit 104
>>>>>>>>>>>>> + fi
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # is this a valid URL?
>>>>>>>>>>>>> + regex='^https://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
>>>>>>>>>>>>> + if ! [[ "${rpzURL}" =~ $regex ]] ; then
>>>>>>>>>>>>> + msg_log "error: rpz: the URL is not valid: \"${rpzURL}\". exit."
>>>>>>>>>>>>> + exit 105
>>>>>>>>>>>>> + fi
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # create the zone config file
>>>>>>>>>>>>> + {
>>>>>>>>>>>>> + echo "rpz:"
>>>>>>>>>>>>> + echo " name: ${rpzName}.rpz"
>>>>>>>>>>>>> + echo " zonefile: ${rpzFile}"
>>>>>>>>>>>>> + echo " url: ${rpzURL}"
>>>>>>>>>>>>> + echo " rpz-action-override: nxdomain"
>>>>>>>>>>>>> + echo " rpz-log: ${rpzLog}"
>>>>>>>>>>>>> + echo " rpz-log-name: ${rpzName}"
>>>>>>>>>>>>> + echo " rpz-signal-nxdomain-ra: yes"
>>>>>>>>>>>>> + } > "${rpzConfig}"
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # set-up zonefile
>>>>>>>>>>>>> + # create an empty rpz file if it does not exist
>>>>>>>>>>>>> + if [[ ! -f "${rpzFile}" ]] ; then
>>>>>>>>>>>>> + touch "${rpzFile}"
>>>>>>>>>>>>> + # unbound requires these settings for rpz files
>>>>>>>>>>>>> + set_permissions "${rpzFile}" "${rpzConfig}"
>>>>>>>>>>>>> + fi
>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # trash config file & rpz file
>>>>>>>>>>>>> + remove )
>>>>>>>>>>>>> + if ! [[ -f "${rpzConfig}" ]] ; then
>>>>>>>>>>>>> + msg_log "error: rpz: cannot remove ${rpzConfig}, does not exist. exit"
>>>>>>>>>>>>> + exit 106
>>>>>>>>>>>>> + fi
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + msg_log "info: rpz: remove config file & rpz file \"${rpzName}\""
>>>>>>>>>>>>> + rm "${rpzConfig}"
>>>>>>>>>>>>> + rm "${rpzFile}"
>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + reload )
>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + list )
>>>>>>>>>>>>> + awk -F':' '/^\s*name:/{ gsub(/[[:blank:]]|\.rpz/, "",$2) ; NAME=$2 } \
>>>>>>>>>>>>> + /^\s*url:/{ gsub(/[[:blank:]]/, "") ; print NAME"="$2":"$3} ' \
>>>>>>>>>>>>> + /etc/unbound/local.d/*rpz.conf
>>>>>>>>>>>>> + exit
>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + unbound-restart )
>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>> + unbound_restart
>>>>>>>>>>>>> + exit
>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + * )
>>>>>>>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>>>>>>>> + printf "Usage: $(basename "$0") <ACTION> <NAME> <URL> <OPTION> <OPTION>\n"
>>>>>>>>>>>>> + printf "Version: ${version}\n"
>>>>>>>>>>>>> + exit 108
>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +esac
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +exit
>>>>>>>>>>>>> diff --git a/config/rpz/rpz-functions b/config/rpz/rpz-functions
>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>> index 000000000..ace1d2690
>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>> +++ b/config/rpz/rpz-functions
>>>>>>>>>>>>> @@ -0,0 +1,85 @@
>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>> +# #
>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>> +# #
>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>> +# #
>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +version="2024-12-10 - v02"
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +msg_log () {
>>>>>>>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>>>>>>>> + if tty --silent ; then
>>>>>>>>>>>>> + echo "${tagName}:" "$*"
>>>>>>>>>>>>> + fi
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# check for a valid name
>>>>>>>>>>>>> +check_name () {
>>>>>>>>>>>>> + local theName="${1}"
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + regex='^[a-zA-Z0-9_]+$' # no dash or plus, alpha numeric only
>>>>>>>>>>>>> + regex1='^(allow|block)$' # allow and block are reserved NAMEs
>>>>>>>>>>>>> + if [[ ! "${theName}" =~ $regex ]] || [[ "${theName}" =~ $regex1 ]] ; then
>>>>>>>>>>>>> + msg_log "error: rpz: the NAME is not valid: \"${theName}\". exit."
>>>>>>>>>>>>> + exit 101
>>>>>>>>>>>>> + fi
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +set_permissions () {
>>>>>>>>>>>>> + chown nobody:nobody "$@"
>>>>>>>>>>>>> + chmod 644 "$@"
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +check_unbound_conf () {
>>>>>>>>>>>>> + local thecheckConf="${1:-yes}" # check config default is yes
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # check the above config files
>>>>>>>>>>>>> + if [[ "${thecheckConf}" == yes ]] ; then
>>>>>>>>>>>>> + msg_log "info: rpz: check for errors with \"unbound-checkconf\""
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + if ! unbound-checkconf ; then
>>>>>>>>>>>>> + msg_log "error: rpz: unbound-checkconf found invalid configuration."
>>>>>>>>>>>>> + msg_log \
>>>>>>>>>>>>> + "error: rpz: In Terminal run the command \"unbound-checkconf\" for more information. exit."
>>>>>>>>>>>>> + exit 102
>>>>>>>>>>>>> + fi
>>>>>>>>>>>>> + fi
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +unbound_control_reload () {
>>>>>>>>>>>>> + local theReload="${1:-yes}" # reload default is yes
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + if [[ "${theReload}" == yes ]] ; then
>>>>>>>>>>>>> + # reload due to the changes
>>>>>>>>>>>>> + msg_log "info: rpz: run \"unbound-control reload\""
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + if ! unbound-control reload ; then
>>>>>>>>>>>>> + msg_log "error: rpz: unbound-control reload. exit."
>>>>>>>>>>>>> + exit 109
>>>>>>>>>>>>> + fi
>>>>>>>>>>>>> + fi
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +unbound_restart () {
>>>>>>>>>>>>> + # restart due to the changes
>>>>>>>>>>>>> + msg_log "info: rpz: run \"unbound restart\""
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + /usr/local/bin/unboundctrl restart
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> diff --git a/config/rpz/rpz-make b/config/rpz/rpz-make
>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>> index 000000000..927d55170
>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>> +++ b/config/rpz/rpz-make
>>>>>>>>>>>>> @@ -0,0 +1,203 @@
>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>> +# #
>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>> +# #
>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>> +# #
>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +version="2025-01-11 - v14"
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# create the config file for allow
>>>>>>>>>>>>> +make_allow_config () {
>>>>>>>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>>>>>>>> + local theConfig="/etc/unbound/local.d/00-rpz.conf" # output zone conf file
>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/allowlist" # input custom list of domains
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + msg_log "info: rpz: make config file \"00-rpz.conf\""
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + echo "server:
>>>>>>>>>>>>> + module-config: \"respip validator iterator\"" > "${theConfig}"
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # does allow list exist?
>>>>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + echo "rpz:
>>>>>>>>>>>>> + name: allow.rpz
>>>>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>> + rpz-action-override: passthru
>>>>>>>>>>>>> + rpz-log: ${theLog}
>>>>>>>>>>>>> + rpz-log-name: allow
>>>>>>>>>>>>> + rpz-signal-nxdomain-ra: yes" >> "${theConfig}"
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + fi
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>>>>> + set_permissions "${theConfig}"
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# create the config file for block
>>>>>>>>>>>>> +make_block_config () {
>>>>>>>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>>>>>>>> + local theConfig="/etc/unbound/local.d/block.rpz.conf" # output zone conf file
>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/blocklist" # input custom list of domains
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + msg_log "info: rpz: make config file \"block.rpz.conf\""
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # does block list exist?
>>>>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + echo "rpz:
>>>>>>>>>>>>> + name: block.rpz
>>>>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/block.rpz
>>>>>>>>>>>>> + rpz-action-override: nxdomain
>>>>>>>>>>>>> + rpz-log: ${theLog}
>>>>>>>>>>>>> + rpz-log-name: block
>>>>>>>>>>>>> + rpz-signal-nxdomain-ra: yes" > "${theConfig}"
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>>>>> + set_permissions "${theConfig}"
>>>>>>>>>>>>> + else
>>>>>>>>>>>>> + # no - trash the config file
>>>>>>>>>>>>> + rm --verbose /etc/unbound/local.d/block.rpz.conf
>>>>>>>>>>>>> + fi
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# create an RPZ file for allow or block
>>>>>>>>>>>>> +make_rpz_file () {
>>>>>>>>>>>>> + local theName="${1}" # allow or block
>>>>>>>>>>>>> + local theAction='.' # the default is nxdomain or block
>>>>>>>>>>>>> + local actionList
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>>>>>>>> + local theZonefile="/etc/unbound/zonefiles/${theName}.rpz" # output file for RPZ
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # does a list exist?
>>>>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # for allow set to passthru
>>>>>>>>>>>>> + [[ "${theName}" == allow ]] && theAction='rpz-passthru.'
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # drop any extra "blanks" and add "CNAME <RPZ action>." to each line
>>>>>>>>>>>>> + actionList=$( awk '{$1=$1};1' "${theList}" |
>>>>>>>>>>>>> + sed "/^[^;].*[[:alnum:]]/ s|$| CNAME ${theAction}|" )
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + msg_log "info: rpz: create zonefile for \"${theName}list\""
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +echo "; Name: ${theName} list
>>>>>>>>>>>>> +; Last modified: $(date "+%Y-%m-%d at %H.%M.%S %Z")
>>>>>>>>>>>>> +;
>>>>>>>>>>>>> +; domains with actions list
>>>>>>>>>>>>> +;
>>>>>>>>>>>>> +${actionList}" > "${theZonefile}"
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>>>>> + set_permissions "${theZonefile}"
>>>>>>>>>>>>> + # set-up allow/block list files
>>>>>>>>>>>>> + set_permissions "${theList}"
>>>>>>>>>>>>> + else
>>>>>>>>>>>>> + msg_log "info: rpz: the ${theList} is empty."
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + rm --verbose "${theZonefile}" # trash the RPZ file
>>>>>>>>>>>>> + fi
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# check if allow/block list is valid
>>>>>>>>>>>>> +validate_list () {
>>>>>>>>>>>>> + local theName="${1}" # allow or block
>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # remove good:
>>>>>>>>>>>>> + # - properly formated domain names with or without leading wildcard
>>>>>>>>>>>>> + # - properly formated top level domain (TLD) names with wildcard
>>>>>>>>>>>>> + # - blank lines and comment lines
>>>>>>>>>>>>> + # remaining lines are considered "bad"
>>>>>>>>>>>>> + bad_lines=$( sed --regexp-extended \
>>>>>>>>>>>>> + '/^(\*\.)?([a-zA-Z0-9](([a-zA-Z0-9\-]){0,61}[a-zA-Z0-9])?\.)+([a-zA-Z]{2,}|xn--[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])$/d ;
>>>>>>>>>>>>> + /^(\*\.)([a-z]{2,61}|xn--[a-z0-9]{1,60})$/d ;
>>>>>>>>>>>>> + /^$/d ; /^;/d' "${theList}" )
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + if [[ ! -z "${bad_lines}" ]] ; then
>>>>>>>>>>>>> + msg_log "error: rpz: invalid line(s) in ${theList}."
>>>>>>>>>>>>> + printf "%s\n" "bad line(s): ${bad_lines}"
>>>>>>>>>>>>> + exit 110
>>>>>>>>>>>>> + fi
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +tagName="unbound"
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +rpzName="${1}" # input RPZ name
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>>>>> + case "$1" in
>>>>>>>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>>>>>>>> + esac
>>>>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>>>>> +done
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +case "${rpzName}" in
>>>>>>>>>>>>> + # make a new allow or block rpz file
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + allow )
>>>>>>>>>>>>> + validate_list 'allow' # is the allowlist valid?
>>>>>>>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>>>>>>>> + make_rpz_file 'allow'
>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + allowblock )
>>>>>>>>>>>>> + validate_list 'allow' # is the list valid?
>>>>>>>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>>>>>>>> + make_rpz_file 'allow'
>>>>>>>>>>>>> + ;&
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + block )
>>>>>>>>>>>>> + validate_list 'block' # is the blocklist valid?
>>>>>>>>>>>>> + make_block_config "${rpzLog}"
>>>>>>>>>>>>> + make_rpz_file 'block'
>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + reload )
>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + unbound-restart )
>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>> + unbound_restart
>>>>>>>>>>>>> + exit
>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + * )
>>>>>>>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>>>>>>>> + printf "Usage: $(basename "$0") <NAME> <OPTION> <OPTION>\n"
>>>>>>>>>>>>> + printf "Version: ${version}\n"
>>>>>>>>>>>>> + exit 108
>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>> +esac
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +exit
>>>>>>>>>>>>> diff --git a/config/rpz/rpz-metrics b/config/rpz/rpz-metrics
>>>>>>>>>>>>> new file mode 100755
>>>>>>>>>>>>> index 000000000..4d43e1629
>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>> +++ b/config/rpz/rpz-metrics
>>>>>>>>>>>>> @@ -0,0 +1,170 @@
>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>> +# #
>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>> +# #
>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>> +# #
>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +version="2025-01-20 - v25"
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +weeks="2" # default to two message logs
>>>>>>>>>>>>> +sortBy="name" # default "by name"
>>>>>>>>>>>>> +rpzActive="enabled" # default "enabled only"
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>>>>> + case "$1" in
>>>>>>>>>>>>> + --by-names | --by-name | name ) sortBy="name" ;;
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + --by-hits | --by-hit | hits | hit ) sortBy="hit" ;;
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + --by-lines | --by-line | lines | line ) sortBy="line" ;;
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + --by-effect ) sortBy="effect" ;;
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + --enabled-only ) rpzActive="enabled" ;;
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + --active-all | --all | all ) rpzActive="all" ;;
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + [0-9] | [0-9][0-9] ) weeks=$1 ;;
>>>>>>>>>>>>> + esac
>>>>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>>>>> +done
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# get the list of message logs for N weeks
>>>>>>>>>>>>> +messageLogs=$( find /var/log/messages* -type f | sort --version-sort |
>>>>>>>>>>>>> + head -"${weeks}" )
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# get the list of RPZ names & counts from the message log(s)
>>>>>>>>>>>>> +rpzNameCount=$( for logf in ${messageLogs} ; do
>>>>>>>>>>>>> + zgrep --text --fixed-strings 'info: rpz: applied' "${logf}" |
>>>>>>>>>>>>> + awk '$10 ~ /\[\w*]/ { print $10 }' ;
>>>>>>>>>>>>> + done | sort | uniq --count )
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# flip results and remove brackets `[` and `]`
>>>>>>>>>>>>> +rpzNameCount=$( echo "${rpzNameCount}" |
>>>>>>>>>>>>> + awk '{ print $2, $1 }' |
>>>>>>>>>>>>> + sed --regexp-extended 's|^\[(.*)\]|\1|' )
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# grab only names
>>>>>>>>>>>>> +rpzNames=$( echo "${rpzNameCount}" | awk '{ print $1 }' )
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# get list of RPZ files
>>>>>>>>>>>>> +rpzFileList=$( find /etc/unbound/zonefiles -type f -iname "*.rpz" )
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# get basename of those files
>>>>>>>>>>>>> +rpzBaseNames=$( echo "${rpzFileList}" |
>>>>>>>>>>>>> + sed 's|/etc/unbound/zonefiles/||g ; s|\.rpz||g ;' )
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# add to rpzNames
>>>>>>>>>>>>> +rpzNames="${rpzNames}"$'\n'"${rpzBaseNames}"
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# drop duplicate names
>>>>>>>>>>>>> +rpzNames=$( echo "${rpzNames}" | sort --unique )
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# get line count for each RPZ
>>>>>>>>>>>>> +lineCount=$( echo "${rpzFileList}" | xargs wc -l )
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# get comment line count and blank line count for each RPZ
>>>>>>>>>>>>> +commentCount=$( echo "${rpzFileList}" | xargs grep --count -e "^$" -e "^;" )
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# get modified date each RPZ
>>>>>>>>>>>>> +modDateList=$( echo "${rpzFileList}" | xargs stat -c '%.10y %n' )
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +ucListAuthZones=$( unbound-control list_auth_zones )
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# get width of RPZ names
>>>>>>>>>>>>> +pWidth=$( echo "${rpzNames}" | awk '{ print $1" " }' | wc -L )
>>>>>>>>>>>>> +pFormat="%-${pWidth}s %-8s %-8s %8s %12s %12s\n"
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# print title line
>>>>>>>>>>>>> +printf "${pFormat}" "name" "hits" "active" "lines" " hits/line" "last download"
>>>>>>>>>>>>> +printf -- "--------------"
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +theResults=""
>>>>>>>>>>>>> +totalLines=0
>>>>>>>>>>>>> +totalHits=0
>>>>>>>>>>>>> +while read -r theName
>>>>>>>>>>>>> +do
>>>>>>>>>>>>> + printf -- "--" # pretend progress bar
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # is this RPZ list active?
>>>>>>>>>>>>> + theActive="disabled"
>>>>>>>>>>>>> + if grep --quiet "^${theName}\.rpz" <<< "${ucListAuthZones}"
>>>>>>>>>>>>> + then
>>>>>>>>>>>>> + theActive="enabled"
>>>>>>>>>>>>> + else
>>>>>>>>>>>>> + [[ "${rpzActive}" == enabled ]] && continue
>>>>>>>>>>>>> + fi
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # get hit count
>>>>>>>>>>>>> + theHits="0"
>>>>>>>>>>>>> + if output=$( grep "^${theName}\s" <<< "${rpzNameCount}" ) ; then
>>>>>>>>>>>>> + theHits=$( echo "${output}" | awk '{ print $2 }' )
>>>>>>>>>>>>> + totalHits=$(( totalHits + theHits ))
>>>>>>>>>>>>> + fi
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # get line count
>>>>>>>>>>>>> + theLines="n/a"
>>>>>>>>>>>>> + hitsPerLine="0"
>>>>>>>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${lineCount}" ) ; then
>>>>>>>>>>>>> + theLines=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>>>>>>>> + totalLines=$(( totalLines + theLines ))
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + if [[ "${theLines}" -gt 2 ]] ; then
>>>>>>>>>>>>> + hitsPerLine=$(( 100 * theHits / theLines ))
>>>>>>>>>>>>> + fi
>>>>>>>>>>>>> + fi
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # get modification date
>>>>>>>>>>>>> + theModDate="n/a"
>>>>>>>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${modDateList}" ) ; then
>>>>>>>>>>>>> + theModDate=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>>>>>>>> + fi
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # add to results list
>>>>>>>>>>>>> + theResults+="${theName} ${theHits} ${theActive} ${theLines} ${hitsPerLine} ${theModDate}"$'\n'
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +done <<< "${rpzNames}"
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +case "${sortBy}" in
>>>>>>>>>>>>> + # sort by "active" then by "name"
>>>>>>>>>>>>> + name) sortArg=(-k3,3r -k1,1) ;;
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # sort by "active" then by "hits" then by "name"
>>>>>>>>>>>>> + hit) sortArg=(-k3,3r -k2,2nr -k1,1) ;;
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # sort by "active" then by "lines" then by "name"
>>>>>>>>>>>>> + line) sortArg=(-k3,3r -k4,4nr -k1,1) ;;
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # sort by "active" then by "effect" then by "name"
>>>>>>>>>>>>> + effect) sortArg=(-k3,3r -k5,5nr -k1,1) ;;
>>>>>>>>>>>>> +esac
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +printf -- "--------------\n"
>>>>>>>>>>>>> +# remove blank lines, sort, print as columns
>>>>>>>>>>>>> +echo "${theResults}" |
>>>>>>>>>>>>> + awk '!/^[[:space:]]*$/' |
>>>>>>>>>>>>> + sort "${sortArg[@]}" |
>>>>>>>>>>>>> + awk --assign=width="${pWidth}" \
>>>>>>>>>>>>> + '{ printf "%-*s %-8s %-8s %8s %10s %% %12s\n", width, $1, $2, $3, $4, $5, $6 }'
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +printf "${pFormat}" "" "=======" "" "========" "" ""
>>>>>>>>>>>>> +printf "${pFormat}" "Totals -->" "${totalHits}" "" "${totalLines}" "" ""
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +exit
>>>>>>>>>>>>> diff --git a/config/rpz/rpz-sleep b/config/rpz/rpz-sleep
>>>>>>>>>>>>> new file mode 100755
>>>>>>>>>>>>> index 000000000..dd3603599
>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>> +++ b/config/rpz/rpz-sleep
>>>>>>>>>>>>> @@ -0,0 +1,58 @@
>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>> +# #
>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>> +# #
>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>> +# #
>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +version="2024-08-16" # v05
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# send message to message log
>>>>>>>>>>>>> +msg_log () {
>>>>>>>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>>>>>>>> + if tty --silent ; then
>>>>>>>>>>>>> + echo "${tagName}:" "$*"
>>>>>>>>>>>>> + fi
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +tagName="unbound"
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +sleepTime="${1:-5m}" # default to sleep for 5m (5 minutes)
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +zoneList=$( unbound-control list_auth_zones | awk '{print $1}' )
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +for zone in ${zoneList} ; do
>>>>>>>>>>>>> + printf "disable ${zone}\t"
>>>>>>>>>>>>> + unbound-control rpz_disable "${zone}"
>>>>>>>>>>>>> +done
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +msg_log "info: rpz: disabled all zones for ${sleepTime}"
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +sleep "${sleepTime}"
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +for zone in ${zoneList} ; do
>>>>>>>>>>>>> + printf "enable ${zone}\t"
>>>>>>>>>>>>> + unbound-control rpz_enable "${zone}"
>>>>>>>>>>>>> +done
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +msg_log "info: rpz: enabled all zones"
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +exit
>>>>>>>>>>>>> diff --git a/config/rpz/rpz.de.pl b/config/rpz/rpz.de.pl
>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>> index 000000000..3770c6bb0
>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>> +++ b/config/rpz/rpz.de.pl
>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>> +'rpz apply' => 'Übernehmen',
>>>>>>>>>>>>> +'rpz cl allow enable' => 'Benutzerdefinierte Allowlist aktivieren:',
>>>>>>>>>>>>> +'rpz cl allow info' => 'Zugelassene Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>>>>>>>> +'rpz cl allow' => 'Benutzerdefinierte Allowlist',
>>>>>>>>>>>>> +'rpz cl block enable' => 'Benutzerdefinierte Blocklist aktivieren:',
>>>>>>>>>>>>> +'rpz cl block info' => 'Gesperrte Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>>>>>>>> +'rpz cl block' => 'Benutzerdefinierte Blocklist',
>>>>>>>>>>>>> +'rpz cl' => 'Benutzerdefinierte Listen',
>>>>>>>>>>>>> +'rpz exitcode 101' => 'Der Name enthält unzulässige Zeichen',
>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf hat eine fehlerhafte Konfiguration ermittelt. Führen Sie das Kommando unbound-checkconf auf der Konsole aus, um weitere Informationen zu erhalten.',
>>>>>>>>>>>>> +'rpz exitcode 103' => 'Die benutzerdefinierte Allow-/Blocklist ist leer',
>>>>>>>>>>>>> +'rpz exitcode 104' => 'Ein Eintrag mit identischem Namen existiert bereits',
>>>>>>>>>>>>> +'rpz exitcode 105' => 'Die URL ist ungültig',
>>>>>>>>>>>>> +'rpz exitcode 106' => 'Eintrag kann nicht entfernt werden, der Name existiert nicht',
>>>>>>>>>>>>> +'rpz exitcode 107' => 'Der Name ist ungültig - nur "allow" oder "block" möglich',
>>>>>>>>>>>>> +'rpz exitcode 108' => 'Fehlende oder inkorrekte Parameter',
>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control reload ist fehlgeschlagen',
>>>>>>>>>>>>> +'rpz exitcode 110' => 'Die benutzerdefinierte Allow-/Blocklist enthält unzulässige Einträge',
>>>>>>>>>>>>> +'rpz exitcode 201' => 'Die Anmerkung enthält unzulässige Zeichen',
>>>>>>>>>>>>> +'rpz exitcode 202' => 'Ungültiger Eintrag in der benutzerdefinierten Allowlist, Zeile ',
>>>>>>>>>>>>> +'rpz exitcode 203' => 'Ungültiger Eintrag in der benutzerdefinierten Blocklist, Zeile ',
>>>>>>>>>>>>> +'rpz exitcode 204' => 'Ausgewählter Eintrag existiert nicht: ',
>>>>>>>>>>>>> +'rpz zf editor' => 'Zonendatei-Eintrag bearbeiten',
>>>>>>>>>>>>> +'rpz zf imported' => '(importiert aus rpz-config)',
>>>>>>>>>>>>> +'rpz zf remark info' => 'Erlaubte Zeichen sind a-z, A-Z, 0-9 und Unterstriche',
>>>>>>>>>>>>> +'rpz zf' => 'Zonendateien',
>>>>>>>>>>>>> +);
>>>>>>>>>>>>> diff --git a/config/rpz/rpz.en.pl b/config/rpz/rpz.en.pl
>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>> index 000000000..0720a8940
>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>> +++ b/config/rpz/rpz.en.pl
>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>> +'rpz apply' => 'Apply',
>>>>>>>>>>>>> +'rpz cl allow enable' => 'Enable custom allowlist:',
>>>>>>>>>>>>> +'rpz cl allow info' => 'Allowed domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>> +'rpz cl allow' => 'Custom allowlist',
>>>>>>>>>>>>> +'rpz cl block enable' => 'Enable custom blocklist:',
>>>>>>>>>>>>> +'rpz cl block info' => 'Blocked domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>> +'rpz cl block' => 'Custom blocklist',
>>>>>>>>>>>>> +'rpz cl' => 'Custom lists',
>>>>>>>>>>>>> +'rpz exitcode 101' => 'the NAME is not valid',
>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf found invalid configuration. In the Terminal run the command unbound-checkconf for more information',
>>>>>>>>>>>>> +'rpz exitcode 103' => 'the allow/block list is empty',
>>>>>>>>>>>>> +'rpz exitcode 104' => 'duplicate - NAME already exists',
>>>>>>>>>>>>> +'rpz exitcode 105' => 'the URL is not valid',
>>>>>>>>>>>>> +'rpz exitcode 106' => 'cannot remove the NAME does not exist',
>>>>>>>>>>>>> +'rpz exitcode 107' => 'the NAME is not valid - "allow" or "block" only',
>>>>>>>>>>>>> +'rpz exitcode 108' => 'missing or incorrect parameter',
>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control reload failed',
>>>>>>>>>>>>> +'rpz exitcode 110' => 'custom Allowlist/Blocklist contains invalid entries',
>>>>>>>>>>>>> +'rpz exitcode 201' => 'the REMARK is not valid',
>>>>>>>>>>>>> +'rpz exitcode 202' => 'invalid entry in allowlist, line ',
>>>>>>>>>>>>> +'rpz exitcode 203' => 'invalid entry in blocklist, line ',
>>>>>>>>>>>>> +'rpz exitcode 204' => 'Selected entry does not exist: ',
>>>>>>>>>>>>> +'rpz zf editor' => 'Edit zonefiles entry',
>>>>>>>>>>>>> +'rpz zf imported' => '(imported from rpz-config)',
>>>>>>>>>>>>> +'rpz zf remark info' => 'Valid characters are a-z, A-Z, 0-9 and underscore.',
>>>>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>>>>> +);
>>>>>>>>>>>>> diff --git a/config/rpz/rpz.es.pl b/config/rpz/rpz.es.pl
>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>> index 000000000..98628e4aa
>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>> +++ b/config/rpz/rpz.es.pl
>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>> +'rpz' => 'Zonas de política de respuesta (RPZ)',
>>>>>>>>>>>>> +'rpz apply' => 'Aplicar',
>>>>>>>>>>>>> +'rpz cl allow enable' => 'Habilitar la lista blanca personalizada:',
>>>>>>>>>>>>> +'rpz cl allow info' => 'Dominio permitido (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>>>>>>>> +'rpz cl allow' => 'Lista blanca personalizada',
>>>>>>>>>>>>> +'rpz cl block enable' => 'Habilitar la lista negra personalizada:',
>>>>>>>>>>>>> +'rpz cl block info' => 'Dominio bloqueado (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>>>>>>>> +'rpz cl block' => 'Lista negra personalizada',
>>>>>>>>>>>>> +'rpz cl' => 'Lista personalizada',
>>>>>>>>>>>>> +'rpz exitcode 101' => 'El NOMBRE no es válido',
>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha encontrado una configuración no válida. Desde Terminal, ejecute el comando unbound-checkconf para mayor información',
>>>>>>>>>>>>> +'rpz exitcode 103' => 'La lista de permitidos/bloqueados está vacía',
>>>>>>>>>>>>> +'rpz exitcode 104' => 'duplicado - NOMBRE ya existe',
>>>>>>>>>>>>> +'rpz exitcode 105' => 'la URL no es válida',
>>>>>>>>>>>>> +'rpz exitcode 106' => 'no es posible eliminar el NOMBRE que no existe',
>>>>>>>>>>>>> +'rpz exitcode 107' => 'el NOMBRE no es válido - sólo "permitir" o "bloquear"',
>>>>>>>>>>>>> +'rpz exitcode 108' => 'parámetro faltante o incorrecto',
>>>>>>>>>>>>> +'rpz exitcode 109' => 'Error al recargar unbound-control',
>>>>>>>>>>>>> +'rpz exitcode 110' => 'la Lista blanca/Lista negra personalizada contiene entradas no válidas',
>>>>>>>>>>>>> +'rpz exitcode 201' => 'el COMENTARIO no es válido',
>>>>>>>>>>>>> +'rpz exitcode 202' => 'entrada no válida en la lista blanca, línea ',
>>>>>>>>>>>>> +'rpz exitcode 203' => 'entrada no válida en la lista negra, línea ',
>>>>>>>>>>>>> +'rpz exitcode 204' => 'La entrada seleccionada no existe: ',
>>>>>>>>>>>>> +'rpz zf editor' => 'Editar la entrada de archivos de zona',
>>>>>>>>>>>>> +'rpz zf imported' => '(importado de rpz-config)',
>>>>>>>>>>>>> +'rpz zf remark info' => 'Los caracteres válidos son a-z, A-Z, 0-9 y guión bajo',
>>>>>>>>>>>>> +'rpz zf' => 'Archivos de zona',
>>>>>>>>>>>>> +);
>>>>>>>>>>>>> diff --git a/config/rpz/rpz.fr.pl b/config/rpz/rpz.fr.pl
>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>> index 000000000..f35f3c2d0
>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>> +++ b/config/rpz/rpz.fr.pl
>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>> +'rpz apply' => 'Appliquer',
>>>>>>>>>>>>> +'rpz cl allow enable' => 'Activer la liste d\'autorisations personnalisée:',
>>>>>>>>>>>>> +'rpz cl allow info' => 'Domaines autorisés (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>> +'rpz cl allow' => 'Liste d\'autorisations personnalisée',
>>>>>>>>>>>>> +'rpz cl block enable' => 'Activer la liste de blocage personnalisée:',
>>>>>>>>>>>>> +'rpz cl block info' => 'Domaines bloqués (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>> +'rpz cl block' => 'liste de blocage personnalisé',
>>>>>>>>>>>>> +'rpz cl' => 'Listes personnalisées',
>>>>>>>>>>>>> +'rpz exitcode 101' => 'le NOM n\'est pas valide',
>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf configuration non valide trouvée. Dans le terminal, exécutez la commande unbound-checkconf pour plus d\'informations',
>>>>>>>>>>>>> +'rpz exitcode 103' => 'la liste autoriser/bloquer est vide',
>>>>>>>>>>>>> +'rpz exitcode 104' => 'le NOM existe déjà',
>>>>>>>>>>>>> +'rpz exitcode 105' => 'L\'URL n\'est pas valide',
>>>>>>>>>>>>> +'rpz exitcode 106' => 'impossible de supprimer le NOM n\'existe pas',
>>>>>>>>>>>>> +'rpz exitcode 107' => 'le NOM n\'est pas valide - « autoriser » ou « bloquer » seulement',
>>>>>>>>>>>>> +'rpz exitcode 108' => 'paramètre manquant ou incorrect',
>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control rechargement échoué',
>>>>>>>>>>>>> +'rpz exitcode 110' => 'la liste autoriser/bloquer contient des entrées non valides',
>>>>>>>>>>>>> +'rpz exitcode 201' => 'la REMARQUE n\'est pas valable',
>>>>>>>>>>>>> +'rpz exitcode 202' => 'entrée non valide dans la liste d\'autorisation, ligne ',
>>>>>>>>>>>>> +'rpz exitcode 203' => 'entrée non valide dans la liste de blocs, ligne ',
>>>>>>>>>>>>> +'rpz exitcode 204' => 'L\'entrée sélectionnée n\'existe pas: ',
>>>>>>>>>>>>> +'rpz zf editor' => 'Modifier l\'entrée Fichiers Zone',
>>>>>>>>>>>>> +'rpz zf imported' => '(importé de rpz-config)',
>>>>>>>>>>>>> +'rpz zf remark info' => 'Les caractères valides sont a-z, A-Z, 0-9 et soulignement.',
>>>>>>>>>>>>> +'rpz zf' => 'Fichiers Zone',
>>>>>>>>>>>>> +);
>>>>>>>>>>>>> diff --git a/config/rpz/rpz.it.pl b/config/rpz/rpz.it.pl
>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>> index 000000000..ee81605c9
>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>> +++ b/config/rpz/rpz.it.pl
>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>> +'rpz apply' => 'Applica',
>>>>>>>>>>>>> +'rpz cl allow enable' => 'Abilita la Whitelist personalizzata:',
>>>>>>>>>>>>> +'rpz cl allow info' => 'Domini consentiti (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>>>>>>>> +'rpz cl allow' => 'Whitelist personalizzata',
>>>>>>>>>>>>> +'rpz cl block enable' => 'Abilita la Blacklist personalizzata:',
>>>>>>>>>>>>> +'rpz cl block info' => 'Domini bloccati (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>>>>>>>> +'rpz cl block' => 'Blacklist personalizzata',
>>>>>>>>>>>>> +'rpz cl' => 'Liste personalizzate',
>>>>>>>>>>>>> +'rpz exitcode 101' => 'il NOME non è valido',
>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha trovato una configurazione non valida. Dal Terminale esegui il comando unbound-checkconf per maggiori informazioni',
>>>>>>>>>>>>> +'rpz exitcode 103' => 'l\'elenco consentiti/bloccati è vuoto',
>>>>>>>>>>>>> +'rpz exitcode 104' => 'duplicato - NAME esiste di già',
>>>>>>>>>>>>> +'rpz exitcode 105' => 'l\'URL non è valido',
>>>>>>>>>>>>> +'rpz exitcode 106' => 'non è possibile rimuovere il NOME non esiste',
>>>>>>>>>>>>> +'rpz exitcode 107' => 'il NOME non è valido - solo "consenti" o "blocca"',
>>>>>>>>>>>>> +'rpz exitcode 108' => 'parametro mancante o non corretto',
>>>>>>>>>>>>> +'rpz exitcode 109' => 'ricaricamento del controllo non associato non riuscito',
>>>>>>>>>>>>> +'rpz exitcode 110' => 'la Whitelist/Blacklist personalizzata contiene voci non valide',
>>>>>>>>>>>>> +'rpz exitcode 201' => 'l"OSSERVAZIONE non è valida',
>>>>>>>>>>>>> +'rpz exitcode 202' => 'voce non valida nella Whitelist, riga ',
>>>>>>>>>>>>> +'rpz exitcode 203' => 'voce non valida nella Blacklist, riga ',
>>>>>>>>>>>>> +'rpz exitcode 204' => 'La voce selezionata non esiste: ',
>>>>>>>>>>>>> +'rpz zf editor' => 'Modifica la voce dei file di zona',
>>>>>>>>>>>>> +'rpz zf imported' => '(importato da rpz-config)',
>>>>>>>>>>>>> +'rpz zf remark info' => 'I caratteri validi sono a-z, A-Z, 0-9 e trattino basso',
>>>>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>>>>> +);
>>>>>>>>>>>>> diff --git a/config/rpz/rpz.tr.pl b/config/rpz/rpz.tr.pl
>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>> index 000000000..00226e192
>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>> +++ b/config/rpz/rpz.tr.pl
>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>> +# I�in eklendi Ayriyeten Response Policy Zone (RPZ)
>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>> +'rpz apply' => 'Uygulamak',
>>>>>>>>>>>>> +'rpz cl allow enable' => '�zel Etkinlestir allowlist:',
>>>>>>>>>>>>> +'rpz cl allow info' => 'Izin verilmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>>>>>>>> +'rpz cl allow' => 'Etkinlestir allowlist',
>>>>>>>>>>>>> +'rpz cl block enable' => '�zel Etkinlestir blocklist:',
>>>>>>>>>>>>> +'rpz cl block info' => 'Engellenmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>>>>>>>> +'rpz cl block' => 'Engellenmis blocklist',
>>>>>>>>>>>>> +'rpz cl' => 'Engellenmis lists',
>>>>>>>>>>>>> +'rpz exitcode 101' => 'NAME ge�erli degil',
>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ge�ersiz yapilandirma bulundu. Terminalde daha fazla bilgi i�in Unbound-checkConf komutunu �alistirin',
>>>>>>>>>>>>> +'rpz exitcode 103' => 'allow/block liste bos',
>>>>>>>>>>>>> +'rpz exitcode 104' => 'kopyalamak - NAME zaten var',
>>>>>>>>>>>>> +'rpz exitcode 105' => 'URL ge�erli degil',
>>>>>>>>>>>>> +'rpz exitcode 106' => '�ikarilamiyor NAME yok',
>>>>>>>>>>>>> +'rpz exitcode 107' => 'NAME ge�erli degil - "allow" veya "block" yalniz',
>>>>>>>>>>>>> +'rpz exitcode 108' => 'Parametre eksik veya yanlis',
>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control basarisiz',
>>>>>>>>>>>>> +'rpz exitcode 110' => 'Engellenmis Allowlist/Blocklist ge�ersiz girisler i�erir',
>>>>>>>>>>>>> +'rpz exitcode 201' => 'REMARK ge�erli degil',
>>>>>>>>>>>>> +'rpz exitcode 202' => 'Ge�ersiz giris allowlist, line ',
>>>>>>>>>>>>> +'rpz exitcode 203' => 'Ge�ersiz giris blocklist, line ',
>>>>>>>>>>>>> +'rpz exitcode 204' => 'Se�ilen giris yok: ',
>>>>>>>>>>>>> +'rpz zf editor' => 'yazimlamak zonefiles giris',
>>>>>>>>>>>>> +'rpz zf imported' => '(ithal edildi rpz-config)',
>>>>>>>>>>>>> +'rpz zf remark info' => 'Ge�erli karakterler a-z, A-Z, 0-9ve alt�st.',
>>>>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>>>>> +);
>>>>>>>>>>>>> diff --git a/html/cgi-bin/rpz.cgi b/html/cgi-bin/rpz.cgi
>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>> index 000000000..a821c92ac
>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>> +++ b/html/cgi-bin/rpz.cgi
>>>>>>>>>>>>> @@ -0,0 +1,923 @@
>>>>>>>>>>>>> +#!/usr/bin/perl
>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>> +# #
>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>> +# Copyright (C) 2005-2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>> +# #
>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>> +# #
>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +use strict;
>>>>>>>>>>>>> +use Scalar::Util qw(looks_like_number);
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# debugging
>>>>>>>>>>>>> +#use warnings;
>>>>>>>>>>>>> +#use CGI::Carp 'fatalsToBrowser';
>>>>>>>>>>>>> +#use Data::Dumper;
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +require '/var/ipfire/general-functions.pl';
>>>>>>>>>>>>> +require "${General::swroot}/lang.pl";
>>>>>>>>>>>>> +require "${General::swroot}/header.pl";
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +###--- Extra HTML ---###
>>>>>>>>>>>>> +my $extraHead = <<END
>>>>>>>>>>>>> +<style>
>>>>>>>>>>>>> + /* alternating row background */
>>>>>>>>>>>>> + .tbl tr:nth-child(2n+2) {
>>>>>>>>>>>>> + background-color: var(--color-light-grey);
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> + .tbl tr:nth-child(2n+3) {
>>>>>>>>>>>>> + background-color: var(--color-grey);
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> + /* text styles */
>>>>>>>>>>>>> + .tbl th:not(:last-child) {
>>>>>>>>>>>>> + text-align: left;
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> + div.right {
>>>>>>>>>>>>> + text-align: right;
>>>>>>>>>>>>> + margin-top: 0.5em;
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> + /* customlist input */
>>>>>>>>>>>>> + textarea.domainlist {
>>>>>>>>>>>>> + margin: 0.5em 0;
>>>>>>>>>>>>> + resize: vertical;
>>>>>>>>>>>>> + min-height: 10em;
>>>>>>>>>>>>> + overflow: auto;
>>>>>>>>>>>>> + white-space: pre;
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> + button[type=submit]:disabled {
>>>>>>>>>>>>> + opacity: 0.6;
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> +</style>
>>>>>>>>>>>>> +END
>>>>>>>>>>>>> +;
>>>>>>>>>>>>> +###--- End of extra HTML ---###
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +### Settings ###
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# Request DNS service reload after configuration change
>>>>>>>>>>>>> +my $RPZ_RELOAD_FLAG = "${General::swroot}/dns/rpz/reload.flag";
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# Configuration file for all available zonefiles
>>>>>>>>>>>>> +# Format: index, name (unique), enabled (on/off), URL, remark
>>>>>>>>>>>>> +my $ZONEFILES_CONF = "${General::swroot}/dns/rpz/zonefiles.conf";
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# Configuration file for custom lists
>>>>>>>>>>>>> +# IDs: 0=allowlist, 1=blocklist, 2=options (allow/block enabled)
>>>>>>>>>>>>> +my $CUSTOMLISTS_CONF = "${General::swroot}/dns/rpz/customlists.conf";
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# Export custom lists to rpz-config
>>>>>>>>>>>>> +my $RPZ_ALLOWLIST = "${General::swroot}/dns/rpz/allowlist";
>>>>>>>>>>>>> +my $RPZ_BLOCKLIST = "${General::swroot}/dns/rpz/blocklist";
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +### Preparation ###
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# Create missing config files
>>>>>>>>>>>>> +unless(-f $ZONEFILES_CONF) { &General::system('touch', "$ZONEFILES_CONF"); }
>>>>>>>>>>>>> +unless(-f $CUSTOMLISTS_CONF) { &General::system('touch', "$CUSTOMLISTS_CONF"); }
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +## Global gui data
>>>>>>>>>>>>> +my $errormessage = "";
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +## Global configuration data
>>>>>>>>>>>>> +my %zonefiles = ();
>>>>>>>>>>>>> +my %customlists = ();
>>>>>>>>>>>>> +&_zonefiles_load();
>>>>>>>>>>>>> +&_customlists_load();
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +## Global CGI form data
>>>>>>>>>>>>> +my %cgiparams = ();
>>>>>>>>>>>>> +&Header::getcgihash(\%cgiparams);
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +my $action = $cgiparams{'ACTION'} // 'NONE';
>>>>>>>>>>>>> +my $action_key = $cgiparams{'KEY'} // ''; # entry being edited, empty = none/new
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +###--- Process form actions ---###
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# Zonefiles action: Check whether the requested entry exists
>>>>>>>>>>>>> +if((substr($action, 0, 3) eq 'ZF_') && ($action_key)) {
>>>>>>>>>>>>> + unless(defined $zonefiles{$action_key}) {
>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr(204, $action_key);
>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +## Perform actions
>>>>>>>>>>>>> +if($action eq 'ZF_SAVE') { ## Save new or modified zonefiles entry
>>>>>>>>>>>>> + if(&_action_zf_save()) {
>>>>>>>>>>>>> + $action = 'NONE'; # success, return to main page
>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>> + $action = 'ZF_EDIT'; # error occured, keep editing
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +} elsif($action eq 'ZF_TOGGLE') { ## Toggle on/off
>>>>>>>>>>>>> + if(&_action_zf_toggle()) {
>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +} elsif($action eq 'ZF_REMOVE') { ## Remove entry
>>>>>>>>>>>>> + if(&_action_zf_remove()) {
>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +} elsif($action eq 'CL_SAVE') { ## Save custom lists
>>>>>>>>>>>>> + if(&_action_cl_save()) {
>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +} elsif($action eq 'RPZ_RELOAD') { ## Reload dns configuration
>>>>>>>>>>>>> + if(&_action_rpz_reload()) {
>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +} elsif($action eq 'UNB_RESTART') { ## Restart unbound service
>>>>>>>>>>>>> + if(&_action_unb_restart()) {
>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +###--- Start GUI ---###
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +## Start http output
>>>>>>>>>>>>> +&Header::showhttpheaders();
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# Start HTML
>>>>>>>>>>>>> +&Header::openpage($Lang::tr{'rpz'}, 1, $extraHead);
>>>>>>>>>>>>> +&Header::openbigbox('100%', 'left', '');
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# Show error messages
>>>>>>>>>>>>> +if($errormessage) {
>>>>>>>>>>>>> + &_print_message($errormessage);
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# Handle zonefile add/edit mode
>>>>>>>>>>>>> +if($action eq "ZF_EDIT") {
>>>>>>>>>>>>> + &_print_zonefile_editor();
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Finalize page and exit cleanly
>>>>>>>>>>>>> + &Header::closebigbox();
>>>>>>>>>>>>> + &Header::closepage();
>>>>>>>>>>>>> + exit(0);
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# Show gui elements
>>>>>>>>>>>>> +&_print_zonefiles();
>>>>>>>>>>>>> +&_print_customlists();
>>>>>>>>>>>>> +&_print_gui_extras();
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +&Header::closebigbox();
>>>>>>>>>>>>> +&Header::closepage();
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +###--- End of GUI ---###
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +###--- Internal configuration file functions ---###
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# Load all available zonefiles from rpz-config and the internal configuration
>>>>>>>>>>>>> +sub _zonefiles_load {
>>>>>>>>>>>>> + # Clean start
>>>>>>>>>>>>> + %zonefiles = ();
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Source 1: Get the currently enabled zonefiles from rpz-config (expected format [name]=[URL])
>>>>>>>>>>>>> + my @enabled_files = &General::system_output('/usr/sbin/rpz-config', 'list');
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + foreach my $row (@enabled_files) {
>>>>>>>>>>>>> + chomp($row);
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Use regex instead of split() to skip non-matching lines
>>>>>>>>>>>>> + next unless($row =~ /^(\w+)=(.+)$/);
>>>>>>>>>>>>> + my ($name, $url) = ($1, $2);
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Unique names are already guaranteed by rpz-config
>>>>>>>>>>>>> + if(&_rpz_validate_zonefile($name, $url, '', 0) == 0) {
>>>>>>>>>>>>> + # Populate global data hash, mark all found entries as enabled
>>>>>>>>>>>>> + my %entry = ('enabled' => 'on',
>>>>>>>>>>>>> + 'url' => $url,
>>>>>>>>>>>>> + 'remark' => $Lang::tr{'rpz zf imported'});
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Source 2: Get additional data and disabled entries from configuration file
>>>>>>>>>>>>> + my %configured_files = ();
>>>>>>>>>>>>> + &General::readhasharray($ZONEFILES_CONF, \%configured_files);
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + foreach my $row (values (%configured_files)) {
>>>>>>>>>>>>> + my ($name, $enabled, $url, $remark) = @$row;
>>>>>>>>>>>>> + $remark //= "";
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + next unless($name);
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Check whether this row belongs to an entry already imported from rpz-config
>>>>>>>>>>>>> + if(defined $zonefiles{$name}) {
>>>>>>>>>>>>> + # Existing entry, only merge additional data
>>>>>>>>>>>>> + $zonefiles{$name}{'remark'} = $remark;
>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>> + # Skip entry if it is marked as enabled but not found by rpz-config. It was then deleted manually
>>>>>>>>>>>>> + if($enabled ne 'on') {
>>>>>>>>>>>>> + # Populate global data hash
>>>>>>>>>>>>> + my %entry = ('enabled' => 'off',
>>>>>>>>>>>>> + 'url' => $url // "",
>>>>>>>>>>>>> + 'remark' => $remark);
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# Save internal zonefiles configuration
>>>>>>>>>>>>> +sub _zonefiles_save_conf {
>>>>>>>>>>>>> + my $index = 0;
>>>>>>>>>>>>> + my %export = ();
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Loop trough all zonefiles and create "hasharray" type export
>>>>>>>>>>>>> + foreach my $name (keys %zonefiles) {
>>>>>>>>>>>>> + my @entry = ($name,
>>>>>>>>>>>>> + $zonefiles{$name}{'enabled'},
>>>>>>>>>>>>> + $zonefiles{$name}{'url'},
>>>>>>>>>>>>> + $zonefiles{$name}{'remark'});
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + $export{$index++} = \@entry;
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + &General::writehasharray($ZONEFILES_CONF, \%export);
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# Load custom lists from rpz-config and the internal configuration
>>>>>>>>>>>>> +sub _customlists_load {
>>>>>>>>>>>>> + # Clean start
>>>>>>>>>>>>> + %customlists = ();
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Load configuration file
>>>>>>>>>>>>> + my %lists_conf = ();
>>>>>>>>>>>>> + &General::readhasharray($CUSTOMLISTS_CONF, \%lists_conf);
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Get list options, enabled by default to start import
>>>>>>>>>>>>> + $customlists{'allow'}{'enabled'} = $lists_conf{2}[0] // 'on';
>>>>>>>>>>>>> + $customlists{'block'}{'enabled'} = $lists_conf{2}[1] // 'on';
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Import enabled list from rpz-config, otherwise retrieve stored or empty list from configuration file
>>>>>>>>>>>>> + if($customlists{'allow'}{'enabled'} eq 'on') {
>>>>>>>>>>>>> + &_customlist_import('allow', $RPZ_ALLOWLIST);
>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>> + $customlists{'allow'}{'list'} = $lists_conf{0} // [];
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> + if($customlists{'block'}{'enabled'} eq 'on') {
>>>>>>>>>>>>> + &_customlist_import('block', $RPZ_BLOCKLIST);
>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>> + $customlists{'block'}{'list'} = $lists_conf{1} // [];
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# Save internal custom lists configuration
>>>>>>>>>>>>> +sub _customlists_save_conf {
>>>>>>>>>>>>> + my %export = ();
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Match IDs with import function
>>>>>>>>>>>>> + $export{0} = $customlists{'allow'}{'list'};
>>>>>>>>>>>>> + $export{1} = $customlists{'block'}{'list'};
>>>>>>>>>>>>> + $export{2} = [$customlists{'allow'}{'enabled'}, $customlists{'block'}{'enabled'}];
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + &General::writehasharray($CUSTOMLISTS_CONF, \%export);
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# Import a custom list from plain file, returns empty list if file is missing
>>>>>>>>>>>>> +sub _customlist_import {
>>>>>>>>>>>>> + my ($listname, $filename) = @_;
>>>>>>>>>>>>> + my @list = ();
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # File exists, load and check all lines
>>>>>>>>>>>>> + if(-f $filename) {
>>>>>>>>>>>>> + open(my $FH, '<', $filename) or die "Can't read $filename: $!";
>>>>>>>>>>>>> + while(my $line = <$FH>) {
>>>>>>>>>>>>> + chomp($line);
>>>>>>>>>>>>> + push(@list, $line);
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> + close($FH);
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Clean up imported data
>>>>>>>>>>>>> + &_rpz_validate_customlist(\@list, 1);
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + $customlists{$listname}{'list'} = \@list;
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# Export a custom list to plain file or clear file if list is disabled
>>>>>>>>>>>>> +sub _customlist_export {
>>>>>>>>>>>>> + my ($listname, $filename) = @_;
>>>>>>>>>>>>> + return unless(defined $customlists{$listname});
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Write enabled domain list to file, otherwise save empty file
>>>>>>>>>>>>> + open(my $FH, '>', $filename) or die "Can't write $filename: $!";
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + if($customlists{$listname}{'enabled'} eq 'on') {
>>>>>>>>>>>>> + foreach my $line (@{$customlists{$listname}{'list'}}) {
>>>>>>>>>>>>> + print $FH "$line\n";
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>> + print $FH "; Note: This list is currently disabled by $ENV{'SCRIPT_NAME'}\n";
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + close($FH);
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +###--- Internal gui functions ---###
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# Show simple message box
>>>>>>>>>>>>> +sub _print_message {
>>>>>>>>>>>>> + my ($message, $title) = @_;
>>>>>>>>>>>>> + $title ||= $Lang::tr{'error messages'};
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $title);
>>>>>>>>>>>>> + print "<span>$message</span>";
>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# Show all zone files and related gui elements
>>>>>>>>>>>>> +sub _print_zonefiles {
>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf'});
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>> +<table class="tbl" width="100%">
>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>> + <th>$Lang::tr{'name'}</th>
>>>>>>>>>>>>> + <th>URL</th>
>>>>>>>>>>>>> + <th>$Lang::tr{'remark'}</th>
>>>>>>>>>>>>> + <th colspan="3">$Lang::tr{'action'}</th>
>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>> +END
>>>>>>>>>>>>> +;
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Sort zonefiles by name and loop trough all entries
>>>>>>>>>>>>> + foreach my $name (sort keys %zonefiles) {
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Toggle button label translation
>>>>>>>>>>>>> + my $toggle_tr = ($zonefiles{$name}{'enabled'} eq 'on') ? $Lang::tr{'click to disable'} : $Lang::tr{'click to enable'};
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>> + <td>$name</td>
>>>>>>>>>>>>> + <td>$zonefiles{$name}{'url'}</td>
>>>>>>>>>>>>> + <td>$zonefiles{$name}{'remark'}</td>
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_TOGGLE">
>>>>>>>>>>>>> + <input type="image" src="/images/$zonefiles{$name}{'enabled'}.gif" title="$toggle_tr" alt="$toggle_tr">
>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_EDIT">
>>>>>>>>>>>>> + <input type="image" src="/images/edit.gif" title="$Lang::tr{'edit'}" alt="$Lang::tr{'edit'}">
>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_REMOVE">
>>>>>>>>>>>>> + <input type="image" src="/images/delete.gif" title="$Lang::tr{'remove'}" alt="$Lang::tr{'remove'}">
>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>> +END
>>>>>>>>>>>>> +;
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Disable reload button if not needed
>>>>>>>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>> +</table>
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +<div class="right">
>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="">
>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="ZF_EDIT">$Lang::tr{'add'}</button>
>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>> +</div>
>>>>>>>>>>>>> +END
>>>>>>>>>>>>> +;
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# Show zonefiles entry editor
>>>>>>>>>>>>> +sub _print_zonefile_editor {
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Key specified: Edit existing entry
>>>>>>>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>>>>>>>> + # Load data to be edited, but don't override already present values (allows user to edit after error)
>>>>>>>>>>>>> + $cgiparams{'ZF_NAME'} //= $action_key;
>>>>>>>>>>>>> + $cgiparams{'ZF_URL'} //= $zonefiles{$action_key}{'url'};
>>>>>>>>>>>>> + $cgiparams{'ZF_REMARK'} //= $zonefiles{$action_key}{'remark'};
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Fallback to empty form
>>>>>>>>>>>>> + $cgiparams{'ZF_NAME'} //= "";
>>>>>>>>>>>>> + $cgiparams{'ZF_URL'} //= "";
>>>>>>>>>>>>> + $cgiparams{'ZF_REMARK'} //= "";
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf editor'});
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>> +<input type="hidden" name="KEY" value="$action_key">
>>>>>>>>>>>>> +<table width="100%">
>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>> + <td width="20%">$Lang::tr{'name'}: <img src="/blob.gif" alt="*"></td>
>>>>>>>>>>>>> + <td><input type="text" name="ZF_NAME" value="$cgiparams{'ZF_NAME'}" size="40" maxlength="32" title="$Lang::tr{'rpz zf remark info'}" pattern="[a-zA-Z0-9_]{1,32}" required></td>
>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>> + <td width="20%">URL: <img src="/blob.gif" alt="*"></td>
>>>>>>>>>>>>> + <td><input type="url" name="ZF_URL" value="$cgiparams{'ZF_URL'}" size="40" maxlength="128" required></td>
>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>> + <td width="20%">$Lang::tr{'remark'}:</td>
>>>>>>>>>>>>> + <td><input type="text" name="ZF_REMARK" value="$cgiparams{'ZF_REMARK'}" size="40" maxlength="32"></td>
>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>> + <td colspan="2"><hr></td>
>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>> + <td width="55%"><img src="/blob.gif" alt="*"> $Lang::tr{'required field'}</td>
>>>>>>>>>>>>> + <td align="right"><button type="submit" name="ACTION" value="ZF_SAVE">$Lang::tr{'save'}</button></td>
>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>> +</table>
>>>>>>>>>>>>> +</form>
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +<div class="right">
>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="NONE">$Lang::tr{'back'}</button>
>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>> +</div>
>>>>>>>>>>>>> +END
>>>>>>>>>>>>> +;
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# Show custom allow/block files and related gui elements
>>>>>>>>>>>>> +sub _print_customlists {
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Load lists from config, unless they are currently being edited
>>>>>>>>>>>>> + if($action ne 'CL_SAVE') {
>>>>>>>>>>>>> + $cgiparams{'ALLOW_LIST'} = join("\n", @{$customlists{'allow'}{'list'}});
>>>>>>>>>>>>> + $cgiparams{'BLOCK_LIST'} = join("\n", @{$customlists{'block'}{'list'}});
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + $cgiparams{'ALLOW_ENABLED'} = ($customlists{'allow'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>>>>>>>> + $cgiparams{'BLOCK_ENABLED'} = ($customlists{'block'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Fallback to empty form
>>>>>>>>>>>>> + $cgiparams{'ALLOW_LIST'} //= "";
>>>>>>>>>>>>> + $cgiparams{'BLOCK_LIST'} //= "";
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # HTML checkboxes, unchecked = no or undef value in POST data
>>>>>>>>>>>>> + my %checked = ();
>>>>>>>>>>>>> + $checked{'ALLOW_ENABLED'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? " checked" : "";
>>>>>>>>>>>>> + $checked{'BLOCK_ENABLED'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? " checked" : "";
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Disable reload button if not needed
>>>>>>>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz cl'});
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>> +<table width="100%">
>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl allow'}</b><br>$Lang::tr{'rpz cl allow info'}</td>
>>>>>>>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl block'}</b><br>$Lang::tr{'rpz cl block info'}</td>
>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>> + <td colspan="2"><textarea name="ALLOW_LIST" class="domainlist" cols="45">
>>>>>>>>>>>>> +$cgiparams{'ALLOW_LIST'}</textarea></td>
>>>>>>>>>>>>> + <td colspan="2"><textarea name="BLOCK_LIST" class="domainlist" cols="45">
>>>>>>>>>>>>> +$cgiparams{'BLOCK_LIST'}</textarea></td>
>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>> + <td><label for="allow_enabled">$Lang::tr{'rpz cl allow enable'}</label></td>
>>>>>>>>>>>>> + <td width="15%"><input type="checkbox" name="ALLOW_ENABLED" id="allow_enabled"$checked{'ALLOW_ENABLED'}></td>
>>>>>>>>>>>>> + <td><label for="block_enabled">$Lang::tr{'rpz cl block enable'}</label></td>
>>>>>>>>>>>>> + <td width="15%"><input type="checkbox" name="BLOCK_ENABLED" id="block_enabled"$checked{'BLOCK_ENABLED'}></td>
>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>> + <td colspan="4"><hr></td>
>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>> + <td align="right" colspan="4">
>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="CL_SAVE">$Lang::tr{'save'}</button>
>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>> +</table>
>>>>>>>>>>>>> +</form>
>>>>>>>>>>>>> +END
>>>>>>>>>>>>> +;
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# Output javascript and extra gui elements
>>>>>>>>>>>>> +sub _print_gui_extras {
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Apply/Restart button modifier key handler
>>>>>>>>>>>>> + if(&_rpz_needs_reload()) {
>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>> +<script>
>>>>>>>>>>>>> + // Commit modifier key handler
>>>>>>>>>>>>> + (function(jq, document) {
>>>>>>>>>>>>> + var keyEventsOn = false; // Keyboard events attached
>>>>>>>>>>>>> + var keyModify = false; // Modifier key pressed
>>>>>>>>>>>>> + var mouseHover = false; // Mouse over commit button
>>>>>>>>>>>>> + var btnModified = false; // Button modified to "Restart"
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + // Document-level key events, enable only while cursor is over button
>>>>>>>>>>>>> + function attachKeyEvents() {
>>>>>>>>>>>>> + if(keyEventsOn) {
>>>>>>>>>>>>> + return;
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> + keyEventsOn = true;
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + jq(document).on("keydown.rpz", function(event) {
>>>>>>>>>>>>> + if((!keyModify) && event.shiftKey) {
>>>>>>>>>>>>> + keyModify = true;
>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> + });
>>>>>>>>>>>>> + jq(document).on("keyup.rpz", function(event) {
>>>>>>>>>>>>> + if(keyModify && (!event.shiftKey)) {
>>>>>>>>>>>>> + keyModify = false;
>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> + });
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> + function removeKeyEvents() {
>>>>>>>>>>>>> + keyModify = false;
>>>>>>>>>>>>> + if(keyEventsOn) {
>>>>>>>>>>>>> + jq(document).off("keydown.rpz keyup.rpz");
>>>>>>>>>>>>> + keyEventsOn = false;
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + // Attach mouse hover events to commit buttons
>>>>>>>>>>>>> + function attachMouseEvents() {
>>>>>>>>>>>>> + jq("button.commit").on("mouseenter", function(event) {
>>>>>>>>>>>>> + if(!mouseHover) {
>>>>>>>>>>>>> + mouseHover = true;
>>>>>>>>>>>>> + attachKeyEvents();
>>>>>>>>>>>>> + // Handle already pressed key
>>>>>>>>>>>>> + keyModify = !!(event.shiftKey);
>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> + });
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + // Cursor moved away: Disable key listener to minimize events
>>>>>>>>>>>>> + jq("button.commit").on("mouseleave", function() {
>>>>>>>>>>>>> + if(mouseHover) {
>>>>>>>>>>>>> + mouseHover = false;
>>>>>>>>>>>>> + removeKeyEvents();
>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> + });
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + // Modify commit button
>>>>>>>>>>>>> + function handleModify() {
>>>>>>>>>>>>> + let modify = mouseHover && keyModify;
>>>>>>>>>>>>> + if(btnModified != modify) {
>>>>>>>>>>>>> + if(modify) {
>>>>>>>>>>>>> + jq("button.commit").text("$Lang::tr{'restart'}").val("UNB_RESTART");
>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>> + jq("button.commit").text("$Lang::tr{'rpz apply'}").val("RPZ_RELOAD");
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> + btnModified = modify;
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + // jQuery DOM ready
>>>>>>>>>>>>> + jq(function() {
>>>>>>>>>>>>> + attachMouseEvents();
>>>>>>>>>>>>> + });
>>>>>>>>>>>>> + })(jQuery, document);
>>>>>>>>>>>>> +</script>
>>>>>>>>>>>>> +END
>>>>>>>>>>>>> +;
>>>>>>>>>>>>> + } # End of modifier key handler
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +###--- Internal action processing functions ---###
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# Toggle zonefile on/off
>>>>>>>>>>>>> +sub _action_zf_toggle {
>>>>>>>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + my $result = 0;
>>>>>>>>>>>>> + my $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Perform toggle action
>>>>>>>>>>>>> + if($enabled eq 'on') {
>>>>>>>>>>>>> + $enabled = 'off';
>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>> + $enabled = 'on';
>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $action_key, $zonefiles{$action_key}{'url'}, '--no-reload');
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Save changes
>>>>>>>>>>>>> + $zonefiles{$action_key}{'enabled'} = $enabled;
>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# Remove zonefile
>>>>>>>>>>>>> +sub _action_zf_remove {
>>>>>>>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Remove from rpz-config if currently active
>>>>>>>>>>>>> + if($zonefiles{$action_key}{'enabled'} eq 'on') {
>>>>>>>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Remove from data hash and save changes
>>>>>>>>>>>>> + delete $zonefiles{$action_key};
>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Clear action_key, as the entry is now removed entirely
>>>>>>>>>>>>> + $action_key = "";
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# Create or update zonefile entry
>>>>>>>>>>>>> +# Returns undef if gui needs to stay in editor mode
>>>>>>>>>>>>> +sub _action_zf_save {
>>>>>>>>>>>>> + my $result = 0;
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + my $name = $cgiparams{'ZF_NAME'} // "";
>>>>>>>>>>>>> + my $url = $cgiparams{'ZF_URL'} // "";
>>>>>>>>>>>>> + my $remark = $cgiparams{'ZF_REMARK'} // "";
>>>>>>>>>>>>> + my $enabled = 'on'; # Enable new entries by default
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Note on variables:
>>>>>>>>>>>>> + # name = unique key, will be used to address the entry
>>>>>>>>>>>>> + # action_key = name of the entry being edited, empty for new entry
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Only check for unique name if it changed
>>>>>>>>>>>>> + # (this also checks new entries because the action_key is empty in this case)
>>>>>>>>>>>>> + $result = &_rpz_validate_zonefile($name, $url, $remark, (lc($name) ne lc($action_key)));
>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 0);
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Edit existing entry: Determine what was changed
>>>>>>>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>>>>>>>> + # Name und URL remain unchanged, only save remark and finish
>>>>>>>>>>>>> + if(($name eq $action_key) && ($url eq $zonefiles{$action_key}{'url'})) {
>>>>>>>>>>>>> + $zonefiles{$action_key}{'remark'} = $remark;
>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Entry was changed and needs to be recreated, preserve status
>>>>>>>>>>>>> + $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Remove from rpz-config
>>>>>>>>>>>>> + return unless &_action_zf_remove();
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Add new entry to rpz-config
>>>>>>>>>>>>> + if($enabled eq 'on') {
>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $name, $url, '--no-reload');
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Add to global data hash and save changes
>>>>>>>>>>>>> + my %entry = ('enabled' => $enabled,
>>>>>>>>>>>>> + 'url' => $url,
>>>>>>>>>>>>> + 'remark' => $remark);
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# Save custom lists
>>>>>>>>>>>>> +sub _action_cl_save {
>>>>>>>>>>>>> + return unless((defined $cgiparams{'ALLOW_LIST'}) && (defined $cgiparams{'BLOCK_LIST'}));
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + my $result = 0;
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + my @allowlist = split(/\R/, $cgiparams{'ALLOW_LIST'});
>>>>>>>>>>>>> + my @blocklist = split(/\R/, $cgiparams{'BLOCK_LIST'});
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Validate lists
>>>>>>>>>>>>> + $result = &_rpz_validate_customlist(\@allowlist);
>>>>>>>>>>>>> + if($result != 0) {
>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr(202, $result);
>>>>>>>>>>>>> + return;
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> + $result = &_rpz_validate_customlist(\@blocklist);
>>>>>>>>>>>>> + if($result != 0) {
>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr(203, $result);
>>>>>>>>>>>>> + return;
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Add to global data hash and save changes
>>>>>>>>>>>>> + $customlists{'allow'}{'list'} = \@allowlist;
>>>>>>>>>>>>> + $customlists{'block'}{'list'} = \@blocklist;
>>>>>>>>>>>>> + $customlists{'allow'}{'enabled'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? 'on' : 'off';
>>>>>>>>>>>>> + $customlists{'block'}{'enabled'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? 'on' : 'off';
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + &_customlists_save_conf();
>>>>>>>>>>>>> + &_customlist_export('allow', $RPZ_ALLOWLIST);
>>>>>>>>>>>>> + &_customlist_export('block', $RPZ_BLOCKLIST);
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Make new lists, request service reload on success
>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-make', 'allowblock', '--no-reload');
>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# Trigger rpz-config reload
>>>>>>>>>>>>> +sub _action_rpz_reload {
>>>>>>>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Immediately clear flag to prevent multiple reloads
>>>>>>>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Perform reload, recreate reload flag on error to enable retry
>>>>>>>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'reload');
>>>>>>>>>>>>> + if(not &_rpz_check_result($result, 0)) {
>>>>>>>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>>>>>>>> + return;
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# Trigger unbound restart
>>>>>>>>>>>>> +sub _action_unb_restart {
>>>>>>>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Immediately clear flag to prevent multiple restarts
>>>>>>>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Perform restart, unboundctrl always exits zero
>>>>>>>>>>>>> + &General::system('/usr/local/bin/unboundctrl', 'restart');
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +###--- Internal rpz-config functions ---###
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# Translate rpz-config exitcodes and messages
>>>>>>>>>>>>> +# 100-199: rpz-config, 200-299: webgui
>>>>>>>>>>>>> +sub _rpz_error_tr {
>>>>>>>>>>>>> + my ($error, $append) = @_;
>>>>>>>>>>>>> + $append //= '';
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Translate numeric exit codes
>>>>>>>>>>>>> + if(looks_like_number($error)) {
>>>>>>>>>>>>> + if(defined $Lang::tr{"rpz exitcode $error"}) {
>>>>>>>>>>>>> + $error = $Lang::tr{"rpz exitcode $error"};
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + return "RPZ $Lang::tr{'error'}: $error" . &Header::escape($append);
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# Check result of rpz-config system call, request reload on success
>>>>>>>>>>>>> +sub _rpz_check_result {
>>>>>>>>>>>>> + my ($result, $request_reload) = @_;
>>>>>>>>>>>>> + $request_reload //= 0;
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # exitcode 0 = success
>>>>>>>>>>>>> + if($result != 0) {
>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr($result);
>>>>>>>>>>>>> + return;
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Set reload flag
>>>>>>>>>>>>> + if($request_reload) {
>>>>>>>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# Test whether reload flag is set
>>>>>>>>>>>>> +sub _rpz_needs_reload {
>>>>>>>>>>>>> + return (-f $RPZ_RELOAD_FLAG);
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# Validate a zonefile entry, returns rpz-config exitcode on failure. Use _rpz_check_result to verify.
>>>>>>>>>>>>> +# unique = check for unique name
>>>>>>>>>>>>> +sub _rpz_validate_zonefile {
>>>>>>>>>>>>> + my ($name, $url, $remark, $unique) = @_;
>>>>>>>>>>>>> + $unique //= 1;
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + unless($name =~ /^[a-zA-Z0-9_]{1,32}$/) {
>>>>>>>>>>>>> + return 101;
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> + unless($url =~ /^[\w+\.:;\/\\&@#%?=\-~|!]{1,128}$/) {
>>>>>>>>>>>>> + return 105;
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> + unless($remark =~ /^[\w \-()\.:;*\/\\?!&=]{0,32}$/) {
>>>>>>>>>>>>> + return 201;
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Check against already existing names
>>>>>>>>>>>>> + if($unique) {
>>>>>>>>>>>>> + foreach my $existing (keys %zonefiles) {
>>>>>>>>>>>>> + if(lc($name) eq lc($existing)) {
>>>>>>>>>>>>> + return 104;
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + return 0;
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# Validate a custom list, returns number of rejected line on failure. Check for non-zero results.
>>>>>>>>>>>>> +# listref = array reference, cleanup = remove invalid entries instead of returning an error
>>>>>>>>>>>>> +sub _rpz_validate_customlist {
>>>>>>>>>>>>> + my ($listref, $cleanup) = @_;
>>>>>>>>>>>>> + $cleanup //= 0;
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + foreach my $index (reverse 0..$#{$listref}) {
>>>>>>>>>>>>> + my $row = @$listref[$index];
>>>>>>>>>>>>> + next unless($row); # Skip/allow empty lines
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Reject/remove everything besides wildcard domains and remarks
>>>>>>>>>>>>> + if((not &General::validwildcarddomainname($row)) && (not $row =~ /^;[\w \-()\.:;*\/\\?!&=]*$/)) {
>>>>>>>>>>>>> + unless($cleanup) {
>>>>>>>>>>>>> + # +1 for user friendly line number and to ensure non-zero exitcode
>>>>>>>>>>>>> + return $index + 1;
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Remove current row
>>>>>>>>>>>>> + splice(@$listref, $index, 1);
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> + }
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + return 0;
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +###--- Internal misc functions ---###
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# Send HTTP 303 redirect headers for post/request/get pattern
>>>>>>>>>>>>> +# (Must be sent before calling &Header::showhttpheaders())
>>>>>>>>>>>>> +sub _http_prg_redirect {
>>>>>>>>>>>>> + my $location = "https://$ENV{'SERVER_NAME'}:$ENV{'SERVER_PORT'}$ENV{'SCRIPT_NAME'}";
>>>>>>>>>>>>> + print "Status: 303 See Other\n";
>>>>>>>>>>>>> + print "Location: $location\n";
>>>>>>>>>>>>> +}
>>>>>>>>>>>>> diff --git a/lfs/rpz b/lfs/rpz
>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>> index 000000000..7ddbc38e5
>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>> +++ b/lfs/rpz
>>>>>>>>>>>>> @@ -0,0 +1,96 @@
>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>> +# #
>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>> +# #
>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>> +# #
>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>> +# Definitions
>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +include Config
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +SUMMARY = response policy zone - RPZ reputation system for unbound DNS
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +VER = 1.0.0
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +THISAPP = rpz-$(VER)
>>>>>>>>>>>>> +DIR_APP = $(DIR_SRC)/$(THISAPP)
>>>>>>>>>>>>> +TARGET = $(DIR_INFO)/$(THISAPP)
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +PROG = rpz
>>>>>>>>>>>>> +PAK_VER = 18
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +DEPS =
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +SERVICES =
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>> +# Top-level Rules
>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +install : $(TARGET)
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +check :
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +download :
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +b2 :
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +dist:
>>>>>>>>>>>>> + @$(PAK)
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>> +# Installation Details
>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +$(TARGET) :
>>>>>>>>>>>>> + @$(PREBUILD)
>>>>>>>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # RPZ scripts
>>>>>>>>>>>>> + install --verbose --mode=755 \
>>>>>>>>>>>>> + $(DIR_CONF)/rpz/{rpz-config,rpz-metrics,rpz-sleep,rpz-make,rpz-functions} \
>>>>>>>>>>>>> + --target-directory=/usr/sbin
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # RPZ config files
>>>>>>>>>>>>> + mkdir -pv /etc/unbound/local.d
>>>>>>>>>>>>> + install --verbose --mode=644 --owner=nobody --group=nobody \
>>>>>>>>>>>>> + $(DIR_CONF)/rpz/00-rpz.conf \
>>>>>>>>>>>>> + --target-directory=/etc/unbound/local.d
>>>>>>>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/local.d
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # RPZ custom list files for allow and block
>>>>>>>>>>>>> + mkdir -pv /var/ipfire/dns/rpz
>>>>>>>>>>>>> + touch /var/ipfire/dns/rpz/{allowlist,blocklist}
>>>>>>>>>>>>> + chown --verbose --recursive nobody:nobody /var/ipfire/dns/rpz
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # RPZ zone files
>>>>>>>>>>>>> + # create empty RPZ config file to avoid a unbound config error
>>>>>>>>>>>>> + mkdir -pv /etc/unbound/zonefiles
>>>>>>>>>>>>> + touch /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/zonefiles
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Install addon-specific language-files
>>>>>>>>>>>>> + install --verbose --mode=004 $(DIR_CONF)/rpz/rpz.*.pl \
>>>>>>>>>>>>> + --target-directory=/var/ipfire/addon-lang
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + # Install backup definition
>>>>>>>>>>>>> + cp -vf $(DIR_CONF)/backup/includes/rpz /var/ipfire/backup/addons/includes/rpz
>>>>>>>>>>>>> +
>>>>>>>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>>>>>>>> + @$(POSTBUILD)
>>>>>>>>>>>>> diff --git a/make.sh b/make.sh
>>>>>>>>>>>>> index 827ea9e77..a77535b13 100755
>>>>>>>>>>>>> --- a/make.sh
>>>>>>>>>>>>> +++ b/make.sh
>>>>>>>>>>>>> @@ -390,7 +390,7 @@ prepareenv() {
>>>>>>>>>>>>> if [ "${free_space}" -lt "${required_space}" ]; then
>>>>>>>>>>>>> # Add any consumed space
>>>>>>>>>>>>> while read -r consumed_space path; do
>>>>>>>>>>>>> - (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>>>>>>>> + (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>>>>>>>> done <<< "$(du --summarize --bytes "${BUILD_DIR}" "${IMAGES_DIR}" "${LOG_DIR}" 2>/dev/null)"
>>>>>>>>>>>>> fi
>>>>>>>>>>>>> @@ -2087,6 +2087,7 @@ build_system() {
>>>>>>>>>>>>> lfsmake2 btrfs-progs
>>>>>>>>>>>>> lfsmake2 inotify-tools
>>>>>>>>>>>>> lfsmake2 grub-btrfs
>>>>>>>>>>>>> + lfsmake2 rpz
>>>>>>>>>>>>> lfsmake2 linux
>>>>>>>>>>>>> lfsmake2 rtl8812au
>>>>>>>>>>>>> diff --git a/src/paks/rpz/install.sh b/src/paks/rpz/install.sh
>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>> index 000000000..ef99bf742
>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>> +++ b/src/paks/rpz/install.sh
>>>>>>>>>>>>> @@ -0,0 +1,36 @@
>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>> +# #
>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>> +# #
>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>> +# #
>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>> +#
>>>>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>>>>> +extract_files
>>>>>>>>>>>>> +restore_backup ${NAME}
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# fix user created files
>>>>>>>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>>>>>>>> + /etc/unbound/zonefiles \
>>>>>>>>>>>>> + /etc/unbound/local.d
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# Update Language cache
>>>>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# restart unbound to load config file
>>>>>>>>>>>>> +/etc/init.d/unbound restart
>>>>>>>>>>>>> diff --git a/src/paks/rpz/uninstall.sh b/src/paks/rpz/uninstall.sh
>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>> index 000000000..e11427df3
>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>> +++ b/src/paks/rpz/uninstall.sh
>>>>>>>>>>>>> @@ -0,0 +1,38 @@
>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>> +# #
>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>> +# #
>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>> +# #
>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>> +#
>>>>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>>>>>>>> +/etc/init.d/unbound stop
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +make_backup ${NAME}
>>>>>>>>>>>>> +remove_files
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>>>>>>>> +# "[1723428668] unbound-control[17117:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# Update Language cache
>>>>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# start unbound to load unbound config file
>>>>>>>>>>>>> +/etc/init.d/unbound start
>>>>>>>>>>>>> diff --git a/src/paks/rpz/update.sh b/src/paks/rpz/update.sh
>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>> index 000000000..9bc340bc6
>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>> +++ b/src/paks/rpz/update.sh
>>>>>>>>>>>>> @@ -0,0 +1,52 @@
>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>> +# #
>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>> +# #
>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>> +# #
>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>> +#
>>>>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# from update.sh
>>>>>>>>>>>>> +extract_backup_includes
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>>>>>>>> +/etc/init.d/unbound stop
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# from uninstall.sh
>>>>>>>>>>>>> +make_backup ${NAME}
>>>>>>>>>>>>> +remove_files
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>>>>>>>> +# "unbound-control[nn:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# from install.sh
>>>>>>>>>>>>> +extract_files
>>>>>>>>>>>>> +restore_backup ${NAME}
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# fix user created files
>>>>>>>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>>>>>>>> + /etc/unbound/zonefiles \
>>>>>>>>>>>>> + /etc/unbound/local.d
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# Update Language cache
>>>>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>>>>> +
>>>>>>>>>>>>> +# restart unbound to load config files
>>>>>>>>>>>>> +/etc/init.d/unbound start
>>>>>>>>>>>>> --
>>>>>>>>>>>>> 2.39.5
>>>>>>>>>>>>>
>>>>>>>>>>> Jon
>>>>>>>>>>> --
>>>>>>>>>>> Jon Murphy
>>>>>>>>>>> jon.murphy@ipfire.org
>>>>>>>>>
>>>>>>>>> Jon
>>>>>>>>> --
>>>>>>>>> Jon Murphy
>>>>>>>>> jon.murphy@ipfire.org
>
>
>
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
2025-03-24 13:33 ` Bernhard Bitsch
@ 2025-03-24 14:25 ` Michael Tremer
2025-03-24 14:33 ` Re[2]: " Jon Murphy
2025-03-24 14:41 ` Bernhard Bitsch
0 siblings, 2 replies; 30+ messages in thread
From: Michael Tremer @ 2025-03-24 14:25 UTC (permalink / raw)
To: Bernhard Bitsch; +Cc: development
Hello,
> On 24 Mar 2025, at 13:33, Bernhard Bitsch <bbitsch@ipfire.org> wrote:
>
>
>
> Am 24.03.2025 um 11:17 schrieb Michael Tremer:
>> Hello Jon,
>>> On 24 Mar 2025, at 00:00, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>
>>> Michael,
>>>
>>> FYI - I was wrong Unbound RPZ is _not_ watching the serial number, it is watching the "refresh", the number after the serial number.
>> Refresh just tells the client how often to check for an update.
>> If that is actually being set by the list publisher, then we have another problem here, because they could put some insanely low value there and we would then DDoS their infrastructure. I think we should keep it like we have it in other places that we control how often we want to check or pull for updates.
>>
>
> You are right. But an extra update process wastes additional processor time. The update mechanism of unbound does the check for update ( however it is realized ) nevertheless.
Yes, doing more things needs resources. But we are not seriously considering whether an IPFire system has enough resources to perform the download of a text file, or are we?
>>> I understand that you don’t speak C, but you got the information from somewhere. Documentation maybe? Since that is out of date very often I like to consult the code.
>>> From testing. Downloading rpz files using rpz unbound, and watching what happens. If the rpz file is setup for "once per day" refresh, then it only downloads one time.
>>>
>>>
>>>
>>> However that won’t solve our problem . . . and having no cache.
>>> In `/etc/unbound/tuning.conf` there is `rrset-cache-size: 128m`. Are you referring to a different cache.
>> Naturally unbound is loading the zone into its memory which we generally call cache.
>> When I say cache I am thinking about persistent data storage across multiple restarts of Unbound. If I am downloading 100 MiB of RPZ lists (which is presumably still on the lower end) and I reboot my firewall, I do not want to download the same data again. We can only ever download a list *once* unless we are 100% certain that it has changed. Then we can download it once again.
>
> The RPZ lists are stored in files in persistent storage. Unbound creates the internal cache from these.
And where are these stored?
>>> Maybe we need to implement both?
>>>
>>> Yes. There are very few AXFR list (I think only four were found). And many more HTTPS rpz files.
>>>
>>>
>>>
>>> Jon
>>>
>>>
>>> ------ Original Message ------
>>> From "Michael Tremer" <michael.tremer@ipfire.org>
>>> To "Jon Murphy" <jon.murphy@ipfire.org>
>>> Cc "IPFire: Development-List" <development@lists.ipfire.org>
>>> Date 3/20/2025 11:26:43 AM
>>> Subject Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
>>>
>>>> Hello Jon,
>>>> Please don’t forget to Cc the list...
>>>>
>>>>>
>>>>> On 19 Mar 2025, at 18:27, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>> Michael,
>>>>>
>>>>>>
>>>>>> Where in the code is this implemented? I cannot find anything like this:
>>>>>
>>>>> Keep in mind I am not a "C" person. Maybe in this section?:
>>>>> https://git.ipfire.org/?p=thirdparty/unbound.git;a=blob;f=services/authzone.c;hb=30b9cb5f813003d0a2b1c2e678652396615b1b7d#l5875
>>>>
>>>> This where the AXFR response is being handled when doing a DNS zone transfer. This code is not being called when performing a HTTP download.
>>>> I understand that you don’t speak C, but you got the information from somewhere. Documentation maybe? Since that is out of date very often I like to consult the code.
>>>>
>>>>>
>>>>> —
>>>>> When I was just learning about RPZ I created a separate RPZ file for testing. When I changed the SOA line with a new serial number, the RPZ file download would happen in about 5 minutes.
>>>>> https://people.ipfire.org/~jon/sblack-adhoc.rpz
>>>>
>>>> It might well be that the file is not being reloaded if the download matches the content that unbound already has. That would of course save some resources.
>>>> However that won’t solve our problem with redundant downloads and having no cache.
>>>>
>>>>>
>>>>> That is how I found out the SOA line is watched for a serial number change.
>>>>> I’ll reconfirm my findings.
>>>>>
>>>>>>
>>>>>>>> The second reason is that we have a lot of firewalls out there. Not all of them will enable this feature and all of the lists, but even if it is a good chunk, we will generate terabytes of traffic which put load on the infrastructure and will cost money. It simply is not what we want to do, regardless of self-hosting those lists and pulling them from somewhere else.
>>>>>
>>>>> So I understand, are you thinking of hosting RPZ AXFR (DNS zone transfer) on IPFire infrastructure?
>>>>
>>>> No, I don’t think that we can generally do this. The biggest problem is licensing as we cannot take anyones content and host it ourselves. We would re-distribute those lists and that will only work with permission of the publishers. I assume that would be too much work to actually get some useful content out there. We might limit ourselves to only those lists that are under a very permissive license. Nobody wants that.
>>>> From a technical point of view, DNS over TCP might not be very nice in terms of forging the transfer and so we would need TLS as well… It should work, but even if we would be able to encourage other people to publish their lists I doubt they would implement DNS over TLS for authoritative DNS. That standard is in very early stages as well.
>>>> As far as I can see, those vendors who offer a list as a commercial product are using DNS to distribute it (e.g. Spamhaus). Those people who have made this all a hobby are throwing the lists onto GitHub and let them handle the traffic.
>>>> Maybe we need to implement both?
>>>> -Michael
>>>>
>>>>>
>>>>> Jon
>>>>> On 3/19/25 5:35 AM, Michael Tremer wrote:
>>>>>>
>>>>>>
>>>>>> Hello Jon,
>>>>>> Where in the code is this implemented? I cannot find anything like this:
>>>>>> Unbound loads the entire file into memory and then starts parsing it. The only special treatment there is is to check whether the first line is a valid zone entry. It does not even have to be a SOA record.
>>>>>> https://git.ipfire.org/?p=thirdparty/unbound.git;a=blob;f=services/authzone.c;hb=30b9cb5f813003d0a2b1c2e678652396615b1b7d#l1188
>>>>>> I am also concerned that Unbound will not be able to support an upstream proxy for any downloads. The caching situation is also unclear for me, so I believe that we will be looking at writing a custom downloader that implements all these things.
>>>>>> -Michael
>>>>>>
>>>>>>>
>>>>>>> On 19 Mar 2025, at 02:58, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>> Michael,
>>>>>>>
>>>>>>>>
>>>>>>>> The emphasis is on the repeated downloads of the same list. That is
>>>>>>>
>>>>>>> > what cannot happen.
>>>>>>> The Unbound RPZ code, as installed within IPFire, watches for a change
>>>>>>> in the SOA line of each RPZ file. This is an example of the first few
>>>>>>> lines for every RPZ file.
>>>>>>> $TTL 300
>>>>>>> @ SOA localhost. root.localhost. 1742298960 43200 3600 86400 300
>>>>>>> NS localhost.
>>>>>>> ;
>>>>>>> ; Title: HaGeZi's Pop-Up Ads DNS Blocklist
>>>>>>> ; Description: Blocks annoying and malicious pop-up ads.
>>>>>>> If the SOA serial number changes (e.g. the 1742298960), then Unbound RPZ
>>>>>>> code does its thing and downloads. Otherwise there is no download.
>>>>>>>
>>>>>>>>
>>>>>>>> So there has to be a way to ensure that we won’t download a list again
>>>>>>>
>>>>>>> > unless it has actually changed.
>>>>>>> This should do what you want but I may be missing your point.
>>>>>>>
>>>>>>>>
>>>>>>>> DNS has a builtin functionality called AXFR. It simply does the job
>>>>>>>
>>>>>>> > for you. I was just wondering whether that was not being used.
>>>>>>> I need to read about AXFR/IXFR and learn a little more.
>>>>>>> Jon
>>>>>>> On 3/17/25 5:35 AM, Michael Tremer wrote:
>>>>>>>>
>>>>>>>>
>>>>>>>> Good Morning Jon,
>>>>>>>>
>>>>>>>>>
>>>>>>>>> On 16 Mar 2025, at 17:00, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>>> Michael,
>>>>>>>>> I was reading through you response again an I want to understand this post:
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>>>>>>>>
>>>>>>>>> So if RPZ doesn't use HTTPS, what is it using? I am missing a key point here.
>>>>>>>>
>>>>>>>> The emphasis is on the repeated downloads of the same list. That is what cannot happen.
>>>>>>>> Although it might not affect a lot of people in our general user-base, there are some that have a metered connection and will pay for data by volume. Some of the lists I looked at are just under 20 MiB. Therefore we need to keep any traffic down to a minimum. The second reason is that we have a lot of firewalls out there. Not all of them will enable this feature and all of the lists, but even if it is a good chunk, we will generate terabytes of traffic which put load on the infrastructure and will cost money. It simply is not what we want to do, regardless of self-hosting those lists and pulling them from somewhere else.
>>>>>>>> So there has to be a way to ensure that we won’t download a list again unless it has actually changed.
>>>>>>>> DNS has a builtin functionality called AXFR. It simply does the job for you. I was just wondering whether that was not being used.
>>>>>>>> HTTPS is an option because that is simply what we use elsewhere, but extra functionality will have to be built for it.
>>>>>>>> -Michael
>>>>>>>>
>>>>>>>>>
>>>>>>>>> Jon
>>>>>>>>> On 2/13/25 3:34 PM, jon wrote:
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> Michael,
>>>>>>>>>> I’ve read through your comments a few times and I ended up with many more questions.
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>>>>>>>>
>>>>>>>>>> To me the efforts to get new code accepted seem to have changed and it seemed easier in the past. In the past I made the Core Team aware via the Dev Mailing List and wrote a simple two or three paragraphs of "What is it? / What is the value? / Here is the code"
>>>>>>>>>> So in an effort to move forward: How exactly is something presented to the Core Team?
>>>>>>>>>> Is there an example of a recent effort that was presented that I can see as a sample? (This type of info can also be added to the Wiki)
>>>>>>>>>> I understand you want it this way, but I don’t know what exactly is needed. Please be specific.
>>>>>>>>>> Jon
>>>>>>>>>> PS - I am not ignoring your other comments, I am just trying to move forward and keep things simple.
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> On Feb 8, 2025, at 1:27 PM, Michael Tremer <michael.tremer@ipfire.org> wrote:
>>>>>>>>>>> Hello Jon,
>>>>>>>>>>> Thanks for your reply. And good that you are copying everyone into this conversation.
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>> On 8 Feb 2025, at 18:41, jon <jon.murphy@ipfire.org> wrote:
>>>>>>>>>>>> Michael,
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort
>>>>>>>>>>>>
>>>>>>>>>>>> Yes, this has been a separate effort (a very public separate effort). Yes, as you pointed this out early on with the "proof-of-concept" and then my request for people to help test RPZ. Nothing was hidden.
>>>>>>>>>>>> This was done because you (and maybe others) did not have the time and I wanted to help and because I needed assistance with RPZ. I tried my best to do this without bothering you.
>>>>>>>>>>>
>>>>>>>>>>> I don’t that it is accurate that nobody wanted to help on this. The list was always open - although not every email has been replied to swiftly it is also your responsibility to raise a question again if it was missed. People here have open ears.
>>>>>>>>>>> It was also stated on this very list on in our documentation that working on something without involving the core team is a risky undertaking. Of course IPFire is free software and so everyone is free to fork if they wish to do so.
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>>> and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls.
>>>>>>>>>>>>
>>>>>>>>>>>> You were aware many steps along the way. See your email on July 28, 2024, August 15, 2024, September 30, 2024, December 23, 2024, and January 16. My attempts to get the team involved were met with "things are busy" and sometimes silence. (Yes, I get it, people are busy.)
>>>>>>>>>>>> You and Adolf, Leo, Erik and Bernhard have been aware since the beginning. You mention you were aware of the "proof-of-concept". If you include those beginning posts, since Sep 2023.
>>>>>>>>>>>
>>>>>>>>>>> Yes, I am aware of a proof-of-concept that I have been running myself for a long time. I am also aware of the efforts that you have been taking.
>>>>>>>>>>> Yet I don’t think there has ever been any joint effort, or am I seeing that wrong?
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>>> This has not been discussed . . . on our calls.
>>>>>>>>>>>>
>>>>>>>>>>>> On the July 28th you stated:
>>>>>>>>>>>> "We have talked about RPZ many times on the monthly call since the URL filter feature is falling more and more out of fashion. I think there is also many posts about this on the forum."
>>>>>>>>>>>> Please don’t insult me again by stating "you know what I mean".
>>>>>>>>>>>> And it has been discussed but not documented in the Monthly Meeting notes.
>>>>>>>>>>>
>>>>>>>>>>> I am not at all insulting you. I don’t want to take this down to a personal level at all. This is a public mailing list and people who read this don’t need to listen to an argument we are having. They are here for the tech inside IPFire.
>>>>>>>>>>> When I wrote that it has not been discussed that does not mean that we have not been touching on the topic. We have been talking about lots of things on the calls, the weather, politics, how our pets are. None of that makes it to the logs. What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>>> Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation.
>>>>>>>>>>>>
>>>>>>>>>>>> Regular conversation on the Dev Mailing list is many times met with silence. I get it, people are busy.
>>>>>>>>>>>> And regular two-way conversation doesn’t happen on the list. At least not with me. I’d be happy to point out the posts that were met with silence.
>>>>>>>>>>>> Again, I get it, people are busy.
>>>>>>>>>>>
>>>>>>>>>>> And you think my emails are not being met with silence? This has nothing to do with this specific topic. This has something to do with how occupied people are and how engaged they are on certain topics. Not everyone is involved in all the things and simply will ignore emails simply based on their subject line.
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>> But the "dip here to the list" were my attempts to get a conversation started. As I said, many time met with silence.
>>>>>>>>>>>> The only place I was not met with silence was on the Community. You have a great group of people in the Community. It is a shame you don’t want to have others help. It would reduce your workload.
>>>>>>>>>>>
>>>>>>>>>>> You should stop making statements that are not true. Who doesn’t want anyone to help?
>>>>>>>>>>> Not having this conversation on a Saturday evening would reduce my workload. At least it would free up time for something else. Helping with the things that are already on the go would reduce the workload of the entire team. Starting one thing at a time and finishing it is a lot better to manage than starting a hundred things and not even finish one. I can tell you that I already have a hundred things on the go.
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>>> Therefore, what am I supposed to do with this email?
>>>>>>>>>>>>
>>>>>>>>>>>> To me it is beyond obvious…
>>>>>>>>>>>> If it isn’t what you want, then guide me with how to do this the correct way. And be specific. I am trying to help. I am trying to make things better. I am trying to do things the right way.
>>>>>>>>>>>
>>>>>>>>>>> To me it isn’t. This is yet another project that has been dumped to the list like so many before and later on everyone has left to have the team deal with the rest.
>>>>>>>>>>> It is a huge patch set. You explained what the vision is, but that is about it. There is no chance this will continue if this disagreement isn’t solved first. I didn’t even look at the code.
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>>> I don’t want to merge code that I don’t agree with.
>>>>>>>>>>>>
>>>>>>>>>>>> I asked multiple times if you "agreed with the concept" and again, met with silence. Yes I get it, people are busy.
>>>>>>>>>>>
>>>>>>>>>>> Having support for RPZ? Yes, it was definitely on the roadmap. That I agree with.
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>>> So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>>>>>>>>
>>>>>>>>>>>> You mentioned this a in the past, but for some reason you do not disclose what I dismissed. Why do you continue to make this harder, wouldn’t it not be easier to tell me what I have dismissed?
>>>>>>>>>>>> I have sent multiple emails trying to answer your concerns and comments. On July 28, Aug 14, Aug 22, Aug 23, Sep 30, etc.
>>>>>>>>>>>> I’ve gone through all of the questions you asked and I cannot find a "dismissed" item.
>>>>>>>>>>>
>>>>>>>>>>> Maybe I need to be *more clear*. I feel humoured by this.
>>>>>>>>>>> It is late on a Saturday and I want my dinner soon, but certainly I have stated that this should never be an add-on considering it is supposed to replace URL Filter. We should never allow people to add their own sources. I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>>>>>>>>
>>>>>>>>>>>> The maintainers of Unbound and/or RPZ?
>>>>>>>>>>>> The maintainers of Hagezi list, the threatfox list, the urlhaus list, etc.?
>>>>>>>>>>>> What else? The maintainers or the RPZ scripts? That is me. Let’s talk!
>>>>>>>>>>>
>>>>>>>>>>> You. I don’t care much about the providers of the lists.
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>> See, this is where it gets confusing. There are hundreds of open source packages as part of IPFire. Pick the last five years of items added to the IPFire build. You're telling me you have "constructive conversation with the maintainers" of all of the added packages?
>>>>>>>>>>>
>>>>>>>>>>> They publish their software and they don’t care whether I am pulling it or not. They publish it with the commitment to maintain it - sometimes for better and sometimes for worse.
>>>>>>>>>>> You care about me pulling your code and I don’t know whether you would commit to maintain this.
>>>>>>>>>>> These two are very different cases.
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>> Pick the IP Blocklists list (i.e., 3CORESEC, ABUSECH, DSHIELD, SPAMHAUS, etc.) or the Suricata lists (i.e.,Emergingthreats.net <http://emergingthreats.net/>,Abuse.ch <http://abuse.ch/>, etc.). So you’ve have "constructive conversation with the maintainers"?
>>>>>>>>>>>
>>>>>>>>>>> Yes, occasionally I have phone calls with a few of these providers.
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>>>>>>>>
>>>>>>>>>>>> Ha! Yes a surprise. In the beginning you seemed interested as IPFire needed a replacement for URL Filter. You asked good questions about the lists picked, asked for the value to the users, etc. And I answered the best I could.
>>>>>>>>>>>> You even asked: “Why is this realised as an add-on and not part of the core system?” from your Jul 28, 2024 email.
>>>>>>>>>>>
>>>>>>>>>>> Ah, so, why is the patch creating an add-on? Not that I am saying that what I say is law, but it has not been challenged either. If my input is being ignored, why should I put this to the top of my list of priorities? I am not disappointed about this, just trying to be very good with my time.
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>> And on January 16, 2025 I wrote a message looking for help. And you were kind to respond quickly. So in three weeks time, since the kind response, something has changed. You went from supportive to "this".
>>>>>>>>>>>> So yes, I am surprised.
>>>>>>>>>>>
>>>>>>>>>>> Well, maybe I should not have replied to that email. It was clear that you were on some path that was not right, but you were not interested before in finding the right path from the beginning.
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>>>>>>>>
>>>>>>>>>>>> Be more specific, what has to change? What exactly did I dismiss?
>>>>>>>>>>>
>>>>>>>>>>> Dismissal is just my assumption. I don’t know what you actually did with my feedback. I can only see the end product that does not seem contain much of it. Repeatedly I have been pointing out that we should think before we build. I am sure a lot of hours have now gone into some code that simply does not satisfy me. And I am not not talking about the code itself, what it does is what I don’t think is right for us.
>>>>>>>>>>> The process is very clear for me that we should first of all think whether we want a certain feature now. Then there should be a clear roadmap for everyone to follow; tasks can be split-up as we go and hopefully then have something that is maintainable, interesting for our users and even would do us proud. This is how this should work.
>>>>>>>>>>> So, what has to change? I don’t think with shouting at each other, throwing patches around and making me generally unhappy is a good start.
>>>>>>>>>>> -Michael
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>> Jon
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>> On Feb 6, 2025, at 2:13 PM, Michael Tremer <michael.tremer@ipfire.org> wrote:
>>>>>>>>>>>>> Hello Jon,
>>>>>>>>>>>>> Well, here we are again with another patch regarding this feature.
>>>>>>>>>>>>> I cannot quite see from your email what the question is, but if this is a request to have this merged into IPFire, I am once again sorry to disappoint you.
>>>>>>>>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls. Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation. Therefore, what am I supposed to do with this email?
>>>>>>>>>>>>> I don’t want to merge code that I don’t agree with. So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>>>>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>>>>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>>>>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>>>>>>>>> All the best,
>>>>>>>>>>>>> -Michael
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> On 6 Feb 2025, at 16:35, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>>>>>>>> What is it?
>>>>>>>>>>>>>> Response Policy Zone (RPZ) is a mechanism to define local policies in a
>>>>>>>>>>>>>> standardized way and load those policies from external sources.
>>>>>>>>>>>>>> Bottom line: RPZ allows admins to easily block access to websites via DNS lookup.
>>>>>>>>>>>>>> RPZ can block websites via categories. Examples include: fake websites, annoying
>>>>>>>>>>>>>> pop-up ads, newly registered domains, DoH bypass sites, bad "host" services,
>>>>>>>>>>>>>> maliscious top level domains (e.g., *.zip, *.mov), piracy, gambling, pornography,
>>>>>>>>>>>>>> and more. RPZ lists come from various RPZ providers and their available
>>>>>>>>>>>>>> catagories.
>>>>>>>>>>>>>> This RPZ add-on enables the RPZ functionality by adding a couple lines in a
>>>>>>>>>>>>>> configuration file. This add-on simply adds configuration files and adds
>>>>>>>>>>>>>> scripts (config, metrics and sleep) to make RPZ easier for the admin to use.
>>>>>>>>>>>>>> The RPZ scripts include additional languages: German, Spanish, French, Turkish,
>>>>>>>>>>>>>> and Italian.
>>>>>>>>>>>>>> RPZ itself was release in 2010 and has been part of the IPFire build since ~2015.
>>>>>>>>>>>>>> Why is it needed? What is its value?
>>>>>>>>>>>>>> - The RPZ concept places this filtering into IPFire, our internet access
>>>>>>>>>>>>>> gateway, which is (should be) solely used as DNS source of the internal network.
>>>>>>>>>>>>>> - As most sites use HTTPS it makes it difficult to filter traffic with URL
>>>>>>>>>>>>>> Filter without also properly configuring conventional (non-transparent)
>>>>>>>>>>>>>> mode on the proxy. RPZ is a nice replacement for the URL Filter.
>>>>>>>>>>>>>> - No need to install and maintain an additional device like PiHole or AdBlock
>>>>>>>>>>>>>> browser extensions on multiple user devices.
>>>>>>>>>>>>>> - This is an additional layer of protection for users. Less worry someone will
>>>>>>>>>>>>>> click on something that gets them into trouble. And, saying this with emphasis,
>>>>>>>>>>>>>> the ability to do it in one place!
>>>>>>>>>>>>>> - Blocked sites save on unneeded traffic and can lessen the threat of malware
>>>>>>>>>>>>>> in advertisements
>>>>>>>>>>>>>> - Logging allows the admin to see the site blocked and take actions
>>>>>>>>>>>>>> - RPZ will be used at the home, home-office (work from home), schools,
>>>>>>>>>>>>>> ministerial, and at the office. Device counts are small (2-6) to medium (~80)
>>>>>>>>>>>>>> to mediam-large (200+).
>>>>>>>>>>>>>> - RPZ can block ads, popups, phishing, scammers, spyware, malware, annoying
>>>>>>>>>>>>>> popups, NSFW links, DOH servers, and the usual internet trash.
>>>>>>>>>>>>>> ------------------------------
>>>>>>>>>>>>>> Change Log for RPZ add-on
>>>>>>>>>>>>>> rpz-1.0.0-18 on 2025-02-05
>>>>>>>>>>>>>> - Build for approval & release as IPFire add-on
>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>> rpz-beta-0.1.18-18.ipfire on 2025-02-01
>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>> - new feature: added a mod key to force a unbound restart
>>>>>>>>>>>>>> rpz-config and rpz-make:
>>>>>>>>>>>>>> - new feature: added action for unbound restart `rpz-config unbound-restart`
>>>>>>>>>>>>>> rpz-metrics:
>>>>>>>>>>>>>> - simple reformatting
>>>>>>>>>>>>>> - rename far right column from "last update" to "last download"
>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>> rpz-beta-0.1.17-17.ipfire on 2024-12-09
>>>>>>>>>>>>>> rpz-make
>>>>>>>>>>>>>> - bug fix: corrected validation regex for wildcards like: `*.domain.com`
>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>> rpz-beta-0.1.16-16.ipfire on 2024-11-18
>>>>>>>>>>>>>> rpz-make
>>>>>>>>>>>>>> - new feature: updated validation regex
>>>>>>>>>>>>>> - bug fix: moved validation to beginning of process. Now we validate before
>>>>>>>>>>>>>> creating config files.
>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>> - new feature: use CSS color variables of the main ipfire theme
>>>>>>>>>>>>>> - bug fix: empty zonefile remarks were stored as “undef” and caused a warning
>>>>>>>>>>>>>> - bug fix: HTML textarea removes the first empty line in a custom list
>>>>>>>>>>>>>> - thank you Leo!
>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>> rpz-beta-0.1.15-15.ipfire on 2024-11-04
>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>> - new feature: added new language file for Turkish (thank you Peppe)
>>>>>>>>>>>>>> rpz-make
>>>>>>>>>>>>>> - bug fix: corrected empty allow/block list issue. An empty allow/block list
>>>>>>>>>>>>>> will now remove contents of allow/block.rpz files and remove unneeded
>>>>>>>>>>>>>> allow/block.conf file. (thank you iptom)
>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>> rpz-beta-0.1.14-14.ipfire on 2024-10-29
>>>>>>>>>>>>>> rpz-config:
>>>>>>>>>>>>>> - bug fix: correct missing rpz extension. `rpz-config list` displayed URL
>>>>>>>>>>>>>> incorrectly (thank you Bernhard)
>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>> - bug fix: remove extra `"` in language files (thank you Bernhard)
>>>>>>>>>>>>>> - new feature: slightly dim "apply" button when not enabled
>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>> rpz-beta-0.1.13-13.ipfire on 2024-10-27
>>>>>>>>>>>>>> - skipped
>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>> rpz-beta-0.1.12-12.ipfire on 2024-10-21
>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>> - new feature: added new language file for French (thank you gw-ipfire)
>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>> rpz-beta-0.1.11-11.ipfire on 2024-10-18
>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>> - new feature: added new language file for Italian (thank you umberto)
>>>>>>>>>>>>>> - new feature: added new language file for Spanish (thank you Roberto)
>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>> rpz-beta-0.1.10-10.ipfire on 2024-10-15
>>>>>>>>>>>>>> rpz-make:
>>>>>>>>>>>>>> - bug fix: corrected validation error for a custom list entry (thank you siosios)
>>>>>>>>>>>>>> - e.g., `*.cloudflare-dns.com`
>>>>>>>>>>>>>> install.sh:
>>>>>>>>>>>>>> - bug fix: add chown to correct user created files
>>>>>>>>>>>>>> update.sh:
>>>>>>>>>>>>>> - bug fix: add chown to correct user created files (thank you siosios)
>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>> rpz-beta-0.1.9-9.ipfire on 2024-10-08
>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>> - new feature: added new language file for German (thank you Leo)
>>>>>>>>>>>>>> - bug fix: add missing "rpz exitcode 110"
>>>>>>>>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>> rpz-beta-0.1.8-8.ipfire on 2024-10-04
>>>>>>>>>>>>>> - skipped
>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>> rpz-beta-0.1.7-7.ipfire on 2024-10-03
>>>>>>>>>>>>>> All:
>>>>>>>>>>>>>> - new feature: includes beta version numbers for pakfire package,
>>>>>>>>>>>>>> instead of only `rpz-1.0.0-1.ipfire`, for each release.
>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>> - new feature: added new WebGUI at `rpz.cgi`
>>>>>>>>>>>>>> - a BIG thank you to Leo Hofmann for all of his work creating the webgui!!
>>>>>>>>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>>>>>>>> rpz-make:
>>>>>>>>>>>>>> - new feature: validate entries in allowlist and blocklist
>>>>>>>>>>>>>> - new feature: add "no-reload" option for WebGUI
>>>>>>>>>>>>>> rpz-metrics:
>>>>>>>>>>>>>> - new feature: info can be sorted by name, by hit count, by line count, by
>>>>>>>>>>>>>> "enabled" list or all lists
>>>>>>>>>>>>>> backups:
>>>>>>>>>>>>>> - bug fix: include all files in `/var/ipfire/dns/rpz` directory in backup
>>>>>>>>>>>>>> update.sh:
>>>>>>>>>>>>>> - bug fix: corrected ownership for `/var/ipfire/dns/rpz` directory during an
>>>>>>>>>>>>>> update
>>>>>>>>>>>>>> Build:
>>>>>>>>>>>>>> - bug fix: `block.rpz.conf` and `block.rpz` from build. Files to be created
>>>>>>>>>>>>>> by `rpz-make`
>>>>>>>>>>>>>> WebGUI and German language file
>>>>>>>>>>>>>> Contribution-by: Leo-Andres Hofmann <hofmann@leo-andres.de>
>>>>>>>>>>>>>> Spanish language file
>>>>>>>>>>>>>> Contribution-by: Roberto Peña
>>>>>>>>>>>>>> Italian language file
>>>>>>>>>>>>>> Contribution-by: Umberto Parma
>>>>>>>>>>>>>> French language file
>>>>>>>>>>>>>> Contribution-by: gw-ipfire
>>>>>>>>>>>>>> Turkish language file
>>>>>>>>>>>>>> Contribution-by: Peppe Tech
>>>>>>>>>>>>>> Contribution-by: Bernhard Bitsch <bbitsch@ipfire.org>
>>>>>>>>>>>>>> Contribution-by: Erik Kapfer <erik.kapfer@ipfire.org>
>>>>>>>>>>>>>> Signed-off-by: Jon Murphy <jon.murphy@ipfire.org
>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>> config/backup/includes/rpz | 4 +
>>>>>>>>>>>>>> config/cfgroot/manualpages | 1 +
>>>>>>>>>>>>>> config/menu/EX-rpz.menu | 6 +
>>>>>>>>>>>>>> config/rootfiles/common/configroot | 1 +
>>>>>>>>>>>>>> config/rootfiles/common/web-user-interface | 1 +
>>>>>>>>>>>>>> config/rootfiles/packages/rpz | 20 +
>>>>>>>>>>>>>> config/rpz/00-rpz.conf | 10 +
>>>>>>>>>>>>>> config/rpz/rpz-config | 130 +++
>>>>>>>>>>>>>> config/rpz/rpz-functions | 85 ++
>>>>>>>>>>>>>> config/rpz/rpz-make | 203 +++++
>>>>>>>>>>>>>> config/rpz/rpz-metrics | 170 ++++
>>>>>>>>>>>>>> config/rpz/rpz-sleep | 58 ++
>>>>>>>>>>>>>> config/rpz/rpz.de.pl | 30 +
>>>>>>>>>>>>>> config/rpz/rpz.en.pl | 30 +
>>>>>>>>>>>>>> config/rpz/rpz.es.pl | 30 +
>>>>>>>>>>>>>> config/rpz/rpz.fr.pl | 30 +
>>>>>>>>>>>>>> config/rpz/rpz.it.pl | 30 +
>>>>>>>>>>>>>> config/rpz/rpz.tr.pl | 30 +
>>>>>>>>>>>>>> html/cgi-bin/rpz.cgi | 923 +++++++++++++++++++++
>>>>>>>>>>>>>> lfs/rpz | 96 +++
>>>>>>>>>>>>>> make.sh | 3 +-
>>>>>>>>>>>>>> src/paks/rpz/install.sh | 36 +
>>>>>>>>>>>>>> src/paks/rpz/uninstall.sh | 38 +
>>>>>>>>>>>>>> src/paks/rpz/update.sh | 52 ++
>>>>>>>>>>>>>> 24 files changed, 2016 insertions(+), 1 deletion(-)
>>>>>>>>>>>>>> create mode 100644 config/backup/includes/rpz
>>>>>>>>>>>>>> create mode 100644 config/menu/EX-rpz.menu
>>>>>>>>>>>>>> create mode 100644 config/rootfiles/packages/rpz
>>>>>>>>>>>>>> create mode 100644 config/rpz/00-rpz.conf
>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz-config
>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz-functions
>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz-make
>>>>>>>>>>>>>> create mode 100755 config/rpz/rpz-metrics
>>>>>>>>>>>>>> create mode 100755 config/rpz/rpz-sleep
>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.de.pl
>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.en.pl
>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.es.pl
>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.fr.pl
>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.it.pl
>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.tr.pl
>>>>>>>>>>>>>> create mode 100644 html/cgi-bin/rpz.cgi
>>>>>>>>>>>>>> create mode 100644 lfs/rpz
>>>>>>>>>>>>>> create mode 100644 src/paks/rpz/install.sh
>>>>>>>>>>>>>> create mode 100644 src/paks/rpz/uninstall.sh
>>>>>>>>>>>>>> create mode 100644 src/paks/rpz/update.sh
>>>>>>>>>>>>>> diff --git a/config/backup/includes/rpz b/config/backup/includes/rpz
>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>> index 000000000..36513e494
>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>> +++ b/config/backup/includes/rpz
>>>>>>>>>>>>>> @@ -0,0 +1,4 @@
>>>>>>>>>>>>>> +/var/ipfire/dns/rpz/*
>>>>>>>>>>>>>> +/etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>> +/etc/unbound/zonefiles/block.rpz
>>>>>>>>>>>>>> +/etc/unbound/local.d/*rpz.conf
>>>>>>>>>>>>>> diff --git a/config/cfgroot/manualpages b/config/cfgroot/manualpages
>>>>>>>>>>>>>> index 1f7e01efc..d3a48c633 100644
>>>>>>>>>>>>>> --- a/config/cfgroot/manualpages
>>>>>>>>>>>>>> +++ b/config/cfgroot/manualpages
>>>>>>>>>>>>>> @@ -70,6 +70,7 @@ pakfire.cgi=configuration/ipfire/pakfire
>>>>>>>>>>>>>> wlanap.cgi=addons/wireless
>>>>>>>>>>>>>> tor.cgi=addons/tor
>>>>>>>>>>>>>> samba.cgi=addons/samba
>>>>>>>>>>>>>> +rpz.cgi=addons/rpz
>>>>>>>>>>>>>> # Logs menu
>>>>>>>>>>>>>> logs.cgi/summary.dat=configuration/logs/summary
>>>>>>>>>>>>>> diff --git a/config/menu/EX-rpz.menu b/config/menu/EX-rpz.menu
>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>> index 000000000..2f4daf410
>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>> +++ b/config/menu/EX-rpz.menu
>>>>>>>>>>>>>> @@ -0,0 +1,6 @@
>>>>>>>>>>>>>> +$subipfire->{'20.rpz'} = {
>>>>>>>>>>>>>> + 'caption' => $Lang::tr{'rpz'},
>>>>>>>>>>>>>> + 'uri' => '/cgi-bin/rpz.cgi',
>>>>>>>>>>>>>> + 'title' => "RPZ",
>>>>>>>>>>>>>> + 'enabled' => 1,
>>>>>>>>>>>>>> +};
>>>>>>>>>>>>>> diff --git a/config/rootfiles/common/configroot b/config/rootfiles/common/configroot
>>>>>>>>>>>>>> index 9839eee45..b30d6aae4 100644
>>>>>>>>>>>>>> --- a/config/rootfiles/common/configroot
>>>>>>>>>>>>>> +++ b/config/rootfiles/common/configroot
>>>>>>>>>>>>>> @@ -120,6 +120,7 @@ var/ipfire/menu.d/70-log.menu
>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-apcupsd.menu
>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-guardian.menu
>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-mympd.menu
>>>>>>>>>>>>>> +#var/ipfire/menu.d/EX-rpz.menu
>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-samba.menu
>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-tor.menu
>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-transmission.menu
>>>>>>>>>>>>>> diff --git a/config/rootfiles/common/web-user-interface b/config/rootfiles/common/web-user-interface
>>>>>>>>>>>>>> index 816241dae..e00464076 100644
>>>>>>>>>>>>>> --- a/config/rootfiles/common/web-user-interface
>>>>>>>>>>>>>> +++ b/config/rootfiles/common/web-user-interface
>>>>>>>>>>>>>> @@ -69,6 +69,7 @@ srv/web/ipfire/cgi-bin/proxy.cgi
>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/qos.cgi
>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/remote.cgi
>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/routing.cgi
>>>>>>>>>>>>>> +#srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>>>>>>>>> #srv/web/ipfire/cgi-bin/samba.cgi
>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/services.cgi
>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/shutdown.cgi
>>>>>>>>>>>>>> diff --git a/config/rootfiles/packages/rpz b/config/rootfiles/packages/rpz
>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>> index 000000000..1c8663049
>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>> +++ b/config/rootfiles/packages/rpz
>>>>>>>>>>>>>> @@ -0,0 +1,20 @@
>>>>>>>>>>>>>> +etc/unbound/local.d/00-rpz.conf
>>>>>>>>>>>>>> +etc/unbound/zonefiles
>>>>>>>>>>>>>> +etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>> +usr/sbin/rpz-config
>>>>>>>>>>>>>> +usr/sbin/rpz-functions
>>>>>>>>>>>>>> +usr/sbin/rpz-make
>>>>>>>>>>>>>> +usr/sbin/rpz-metrics
>>>>>>>>>>>>>> +usr/sbin/rpz-sleep
>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.de.pl
>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.en.pl
>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.es.pl
>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.fr.pl
>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.it.pl
>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.tr.pl
>>>>>>>>>>>>>> +var/ipfire/backup/addons/includes/rpz
>>>>>>>>>>>>>> +var/ipfire/dns/rpz
>>>>>>>>>>>>>> +var/ipfire/dns/rpz/allowlist
>>>>>>>>>>>>>> +var/ipfire/dns/rpz/blocklist
>>>>>>>>>>>>>> +var/ipfire/menu.d/EX-rpz.menu
>>>>>>>>>>>>>> +srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>>>>>>>>> diff --git a/config/rpz/00-rpz.conf b/config/rpz/00-rpz.conf
>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>> index 000000000..f005a4f2e
>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>> +++ b/config/rpz/00-rpz.conf
>>>>>>>>>>>>>> @@ -0,0 +1,10 @@
>>>>>>>>>>>>>> +server:
>>>>>>>>>>>>>> + module-config: "respip validator iterator"
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +rpz:
>>>>>>>>>>>>>> + name: allow.rpz
>>>>>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>> + rpz-action-override: passthru
>>>>>>>>>>>>>> + rpz-log: yes
>>>>>>>>>>>>>> + rpz-log-name: allow
>>>>>>>>>>>>>> + rpz-signal-nxdomain-ra: yes
>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-config b/config/rpz/rpz-config
>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>> index 000000000..c72d50f9b
>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>> +++ b/config/rpz/rpz-config
>>>>>>>>>>>>>> @@ -0,0 +1,130 @@
>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +version="2025-01-11 - v44"
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +tagName="unbound"
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +rpzAction="${1}" # input RPZ action
>>>>>>>>>>>>>> +rpzName="${2}" # input RPZ name
>>>>>>>>>>>>>> +rpzURL="${3}" # input RPZ URL
>>>>>>>>>>>>>> +rpzOption1="${4}" # input RPZ option #1
>>>>>>>>>>>>>> +rpzOption2="${5}" # input RPZ option #2
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +rpzConfig="/etc/unbound/local.d/${rpzName}.rpz.conf" # output zone conf file
>>>>>>>>>>>>>> +rpzFile="/etc/unbound/zonefiles/${rpzName}.rpz" # output for RPZ file
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>>>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>>>>>> + case "$1" in
>>>>>>>>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>>>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>>>>>>>>> + esac
>>>>>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +case "${rpzAction}" in
>>>>>>>>>>>>>> + # add new rpz list
>>>>>>>>>>>>>> + add )
>>>>>>>>>>>>>> + check_name "${rpzName}" # is this a valid name?
>>>>>>>>>>>>>> + # does this config already exist? If yes, then exit
>>>>>>>>>>>>>> + if [[ -f "${rpzConfig}" ]] ; then
>>>>>>>>>>>>>> + msg_log "error: rpz: duplicate - ${rpzConfig} already exists. exit"
>>>>>>>>>>>>>> + exit 104
>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # is this a valid URL?
>>>>>>>>>>>>>> + regex='^https://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
>>>>>>>>>>>>>> + if ! [[ "${rpzURL}" =~ $regex ]] ; then
>>>>>>>>>>>>>> + msg_log "error: rpz: the URL is not valid: \"${rpzURL}\". exit."
>>>>>>>>>>>>>> + exit 105
>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # create the zone config file
>>>>>>>>>>>>>> + {
>>>>>>>>>>>>>> + echo "rpz:"
>>>>>>>>>>>>>> + echo " name: ${rpzName}.rpz"
>>>>>>>>>>>>>> + echo " zonefile: ${rpzFile}"
>>>>>>>>>>>>>> + echo " url: ${rpzURL}"
>>>>>>>>>>>>>> + echo " rpz-action-override: nxdomain"
>>>>>>>>>>>>>> + echo " rpz-log: ${rpzLog}"
>>>>>>>>>>>>>> + echo " rpz-log-name: ${rpzName}"
>>>>>>>>>>>>>> + echo " rpz-signal-nxdomain-ra: yes"
>>>>>>>>>>>>>> + } > "${rpzConfig}"
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # set-up zonefile
>>>>>>>>>>>>>> + # create an empty rpz file if it does not exist
>>>>>>>>>>>>>> + if [[ ! -f "${rpzFile}" ]] ; then
>>>>>>>>>>>>>> + touch "${rpzFile}"
>>>>>>>>>>>>>> + # unbound requires these settings for rpz files
>>>>>>>>>>>>>> + set_permissions "${rpzFile}" "${rpzConfig}"
>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # trash config file & rpz file
>>>>>>>>>>>>>> + remove )
>>>>>>>>>>>>>> + if ! [[ -f "${rpzConfig}" ]] ; then
>>>>>>>>>>>>>> + msg_log "error: rpz: cannot remove ${rpzConfig}, does not exist. exit"
>>>>>>>>>>>>>> + exit 106
>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + msg_log "info: rpz: remove config file & rpz file \"${rpzName}\""
>>>>>>>>>>>>>> + rm "${rpzConfig}"
>>>>>>>>>>>>>> + rm "${rpzFile}"
>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + reload )
>>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + list )
>>>>>>>>>>>>>> + awk -F':' '/^\s*name:/{ gsub(/[[:blank:]]|\.rpz/, "",$2) ; NAME=$2 } \
>>>>>>>>>>>>>> + /^\s*url:/{ gsub(/[[:blank:]]/, "") ; print NAME"="$2":"$3} ' \
>>>>>>>>>>>>>> + /etc/unbound/local.d/*rpz.conf
>>>>>>>>>>>>>> + exit
>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + unbound-restart )
>>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>>> + unbound_restart
>>>>>>>>>>>>>> + exit
>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + * )
>>>>>>>>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>>>>>>>>> + printf "Usage: $(basename "$0") <ACTION> <NAME> <URL> <OPTION> <OPTION>\n"
>>>>>>>>>>>>>> + printf "Version: ${version}\n"
>>>>>>>>>>>>>> + exit 108
>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +esac
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +exit
>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-functions b/config/rpz/rpz-functions
>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>> index 000000000..ace1d2690
>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>> +++ b/config/rpz/rpz-functions
>>>>>>>>>>>>>> @@ -0,0 +1,85 @@
>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +version="2024-12-10 - v02"
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +msg_log () {
>>>>>>>>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>>>>>>>>> + if tty --silent ; then
>>>>>>>>>>>>>> + echo "${tagName}:" "$*"
>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# check for a valid name
>>>>>>>>>>>>>> +check_name () {
>>>>>>>>>>>>>> + local theName="${1}"
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + regex='^[a-zA-Z0-9_]+$' # no dash or plus, alpha numeric only
>>>>>>>>>>>>>> + regex1='^(allow|block)$' # allow and block are reserved NAMEs
>>>>>>>>>>>>>> + if [[ ! "${theName}" =~ $regex ]] || [[ "${theName}" =~ $regex1 ]] ; then
>>>>>>>>>>>>>> + msg_log "error: rpz: the NAME is not valid: \"${theName}\". exit."
>>>>>>>>>>>>>> + exit 101
>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +set_permissions () {
>>>>>>>>>>>>>> + chown nobody:nobody "$@"
>>>>>>>>>>>>>> + chmod 644 "$@"
>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +check_unbound_conf () {
>>>>>>>>>>>>>> + local thecheckConf="${1:-yes}" # check config default is yes
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # check the above config files
>>>>>>>>>>>>>> + if [[ "${thecheckConf}" == yes ]] ; then
>>>>>>>>>>>>>> + msg_log "info: rpz: check for errors with \"unbound-checkconf\""
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + if ! unbound-checkconf ; then
>>>>>>>>>>>>>> + msg_log "error: rpz: unbound-checkconf found invalid configuration."
>>>>>>>>>>>>>> + msg_log \
>>>>>>>>>>>>>> + "error: rpz: In Terminal run the command \"unbound-checkconf\" for more information. exit."
>>>>>>>>>>>>>> + exit 102
>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +unbound_control_reload () {
>>>>>>>>>>>>>> + local theReload="${1:-yes}" # reload default is yes
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + if [[ "${theReload}" == yes ]] ; then
>>>>>>>>>>>>>> + # reload due to the changes
>>>>>>>>>>>>>> + msg_log "info: rpz: run \"unbound-control reload\""
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + if ! unbound-control reload ; then
>>>>>>>>>>>>>> + msg_log "error: rpz: unbound-control reload. exit."
>>>>>>>>>>>>>> + exit 109
>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +unbound_restart () {
>>>>>>>>>>>>>> + # restart due to the changes
>>>>>>>>>>>>>> + msg_log "info: rpz: run \"unbound restart\""
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + /usr/local/bin/unboundctrl restart
>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-make b/config/rpz/rpz-make
>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>> index 000000000..927d55170
>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>> +++ b/config/rpz/rpz-make
>>>>>>>>>>>>>> @@ -0,0 +1,203 @@
>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +version="2025-01-11 - v14"
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# create the config file for allow
>>>>>>>>>>>>>> +make_allow_config () {
>>>>>>>>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>>>>>>>>> + local theConfig="/etc/unbound/local.d/00-rpz.conf" # output zone conf file
>>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/allowlist" # input custom list of domains
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + msg_log "info: rpz: make config file \"00-rpz.conf\""
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + echo "server:
>>>>>>>>>>>>>> + module-config: \"respip validator iterator\"" > "${theConfig}"
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # does allow list exist?
>>>>>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + echo "rpz:
>>>>>>>>>>>>>> + name: allow.rpz
>>>>>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>> + rpz-action-override: passthru
>>>>>>>>>>>>>> + rpz-log: ${theLog}
>>>>>>>>>>>>>> + rpz-log-name: allow
>>>>>>>>>>>>>> + rpz-signal-nxdomain-ra: yes" >> "${theConfig}"
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>>>>>> + set_permissions "${theConfig}"
>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# create the config file for block
>>>>>>>>>>>>>> +make_block_config () {
>>>>>>>>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>>>>>>>>> + local theConfig="/etc/unbound/local.d/block.rpz.conf" # output zone conf file
>>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/blocklist" # input custom list of domains
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + msg_log "info: rpz: make config file \"block.rpz.conf\""
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # does block list exist?
>>>>>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + echo "rpz:
>>>>>>>>>>>>>> + name: block.rpz
>>>>>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/block.rpz
>>>>>>>>>>>>>> + rpz-action-override: nxdomain
>>>>>>>>>>>>>> + rpz-log: ${theLog}
>>>>>>>>>>>>>> + rpz-log-name: block
>>>>>>>>>>>>>> + rpz-signal-nxdomain-ra: yes" > "${theConfig}"
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>>>>>> + set_permissions "${theConfig}"
>>>>>>>>>>>>>> + else
>>>>>>>>>>>>>> + # no - trash the config file
>>>>>>>>>>>>>> + rm --verbose /etc/unbound/local.d/block.rpz.conf
>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# create an RPZ file for allow or block
>>>>>>>>>>>>>> +make_rpz_file () {
>>>>>>>>>>>>>> + local theName="${1}" # allow or block
>>>>>>>>>>>>>> + local theAction='.' # the default is nxdomain or block
>>>>>>>>>>>>>> + local actionList
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>>>>>>>>> + local theZonefile="/etc/unbound/zonefiles/${theName}.rpz" # output file for RPZ
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # does a list exist?
>>>>>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # for allow set to passthru
>>>>>>>>>>>>>> + [[ "${theName}" == allow ]] && theAction='rpz-passthru.'
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # drop any extra "blanks" and add "CNAME <RPZ action>." to each line
>>>>>>>>>>>>>> + actionList=$( awk '{$1=$1};1' "${theList}" |
>>>>>>>>>>>>>> + sed "/^[^;].*[[:alnum:]]/ s|$| CNAME ${theAction}|" )
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + msg_log "info: rpz: create zonefile for \"${theName}list\""
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +echo "; Name: ${theName} list
>>>>>>>>>>>>>> +; Last modified: $(date "+%Y-%m-%d at %H.%M.%S %Z")
>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>> +; domains with actions list
>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>> +${actionList}" > "${theZonefile}"
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>>>>>> + set_permissions "${theZonefile}"
>>>>>>>>>>>>>> + # set-up allow/block list files
>>>>>>>>>>>>>> + set_permissions "${theList}"
>>>>>>>>>>>>>> + else
>>>>>>>>>>>>>> + msg_log "info: rpz: the ${theList} is empty."
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + rm --verbose "${theZonefile}" # trash the RPZ file
>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# check if allow/block list is valid
>>>>>>>>>>>>>> +validate_list () {
>>>>>>>>>>>>>> + local theName="${1}" # allow or block
>>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # remove good:
>>>>>>>>>>>>>> + # - properly formated domain names with or without leading wildcard
>>>>>>>>>>>>>> + # - properly formated top level domain (TLD) names with wildcard
>>>>>>>>>>>>>> + # - blank lines and comment lines
>>>>>>>>>>>>>> + # remaining lines are considered "bad"
>>>>>>>>>>>>>> + bad_lines=$( sed --regexp-extended \
>>>>>>>>>>>>>> + '/^(\*\.)?([a-zA-Z0-9](([a-zA-Z0-9\-]){0,61}[a-zA-Z0-9])?\.)+([a-zA-Z]{2,}|xn--[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])$/d ;
>>>>>>>>>>>>>> + /^(\*\.)([a-z]{2,61}|xn--[a-z0-9]{1,60})$/d ;
>>>>>>>>>>>>>> + /^$/d ; /^;/d' "${theList}" )
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + if [[ ! -z "${bad_lines}" ]] ; then
>>>>>>>>>>>>>> + msg_log "error: rpz: invalid line(s) in ${theList}."
>>>>>>>>>>>>>> + printf "%s\n" "bad line(s): ${bad_lines}"
>>>>>>>>>>>>>> + exit 110
>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +tagName="unbound"
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +rpzName="${1}" # input RPZ name
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>>>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>>>>>> + case "$1" in
>>>>>>>>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>>>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>>>>>>>>> + esac
>>>>>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +case "${rpzName}" in
>>>>>>>>>>>>>> + # make a new allow or block rpz file
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + allow )
>>>>>>>>>>>>>> + validate_list 'allow' # is the allowlist valid?
>>>>>>>>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>>>>>>>>> + make_rpz_file 'allow'
>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + allowblock )
>>>>>>>>>>>>>> + validate_list 'allow' # is the list valid?
>>>>>>>>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>>>>>>>>> + make_rpz_file 'allow'
>>>>>>>>>>>>>> + ;&
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + block )
>>>>>>>>>>>>>> + validate_list 'block' # is the blocklist valid?
>>>>>>>>>>>>>> + make_block_config "${rpzLog}"
>>>>>>>>>>>>>> + make_rpz_file 'block'
>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + reload )
>>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + unbound-restart )
>>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>>> + unbound_restart
>>>>>>>>>>>>>> + exit
>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + * )
>>>>>>>>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>>>>>>>>> + printf "Usage: $(basename "$0") <NAME> <OPTION> <OPTION>\n"
>>>>>>>>>>>>>> + printf "Version: ${version}\n"
>>>>>>>>>>>>>> + exit 108
>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>> +esac
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +exit
>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-metrics b/config/rpz/rpz-metrics
>>>>>>>>>>>>>> new file mode 100755
>>>>>>>>>>>>>> index 000000000..4d43e1629
>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>> +++ b/config/rpz/rpz-metrics
>>>>>>>>>>>>>> @@ -0,0 +1,170 @@
>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +version="2025-01-20 - v25"
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +weeks="2" # default to two message logs
>>>>>>>>>>>>>> +sortBy="name" # default "by name"
>>>>>>>>>>>>>> +rpzActive="enabled" # default "enabled only"
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>>>>>> + case "$1" in
>>>>>>>>>>>>>> + --by-names | --by-name | name ) sortBy="name" ;;
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + --by-hits | --by-hit | hits | hit ) sortBy="hit" ;;
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + --by-lines | --by-line | lines | line ) sortBy="line" ;;
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + --by-effect ) sortBy="effect" ;;
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + --enabled-only ) rpzActive="enabled" ;;
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + --active-all | --all | all ) rpzActive="all" ;;
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + [0-9] | [0-9][0-9] ) weeks=$1 ;;
>>>>>>>>>>>>>> + esac
>>>>>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# get the list of message logs for N weeks
>>>>>>>>>>>>>> +messageLogs=$( find /var/log/messages* -type f | sort --version-sort |
>>>>>>>>>>>>>> + head -"${weeks}" )
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# get the list of RPZ names & counts from the message log(s)
>>>>>>>>>>>>>> +rpzNameCount=$( for logf in ${messageLogs} ; do
>>>>>>>>>>>>>> + zgrep --text --fixed-strings 'info: rpz: applied' "${logf}" |
>>>>>>>>>>>>>> + awk '$10 ~ /\[\w*]/ { print $10 }' ;
>>>>>>>>>>>>>> + done | sort | uniq --count )
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# flip results and remove brackets `[` and `]`
>>>>>>>>>>>>>> +rpzNameCount=$( echo "${rpzNameCount}" |
>>>>>>>>>>>>>> + awk '{ print $2, $1 }' |
>>>>>>>>>>>>>> + sed --regexp-extended 's|^\[(.*)\]|\1|' )
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# grab only names
>>>>>>>>>>>>>> +rpzNames=$( echo "${rpzNameCount}" | awk '{ print $1 }' )
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# get list of RPZ files
>>>>>>>>>>>>>> +rpzFileList=$( find /etc/unbound/zonefiles -type f -iname "*.rpz" )
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# get basename of those files
>>>>>>>>>>>>>> +rpzBaseNames=$( echo "${rpzFileList}" |
>>>>>>>>>>>>>> + sed 's|/etc/unbound/zonefiles/||g ; s|\.rpz||g ;' )
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# add to rpzNames
>>>>>>>>>>>>>> +rpzNames="${rpzNames}"$'\n'"${rpzBaseNames}"
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# drop duplicate names
>>>>>>>>>>>>>> +rpzNames=$( echo "${rpzNames}" | sort --unique )
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# get line count for each RPZ
>>>>>>>>>>>>>> +lineCount=$( echo "${rpzFileList}" | xargs wc -l )
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# get comment line count and blank line count for each RPZ
>>>>>>>>>>>>>> +commentCount=$( echo "${rpzFileList}" | xargs grep --count -e "^$" -e "^;" )
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# get modified date each RPZ
>>>>>>>>>>>>>> +modDateList=$( echo "${rpzFileList}" | xargs stat -c '%.10y %n' )
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +ucListAuthZones=$( unbound-control list_auth_zones )
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# get width of RPZ names
>>>>>>>>>>>>>> +pWidth=$( echo "${rpzNames}" | awk '{ print $1" " }' | wc -L )
>>>>>>>>>>>>>> +pFormat="%-${pWidth}s %-8s %-8s %8s %12s %12s\n"
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# print title line
>>>>>>>>>>>>>> +printf "${pFormat}" "name" "hits" "active" "lines" " hits/line" "last download"
>>>>>>>>>>>>>> +printf -- "--------------"
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +theResults=""
>>>>>>>>>>>>>> +totalLines=0
>>>>>>>>>>>>>> +totalHits=0
>>>>>>>>>>>>>> +while read -r theName
>>>>>>>>>>>>>> +do
>>>>>>>>>>>>>> + printf -- "--" # pretend progress bar
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # is this RPZ list active?
>>>>>>>>>>>>>> + theActive="disabled"
>>>>>>>>>>>>>> + if grep --quiet "^${theName}\.rpz" <<< "${ucListAuthZones}"
>>>>>>>>>>>>>> + then
>>>>>>>>>>>>>> + theActive="enabled"
>>>>>>>>>>>>>> + else
>>>>>>>>>>>>>> + [[ "${rpzActive}" == enabled ]] && continue
>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # get hit count
>>>>>>>>>>>>>> + theHits="0"
>>>>>>>>>>>>>> + if output=$( grep "^${theName}\s" <<< "${rpzNameCount}" ) ; then
>>>>>>>>>>>>>> + theHits=$( echo "${output}" | awk '{ print $2 }' )
>>>>>>>>>>>>>> + totalHits=$(( totalHits + theHits ))
>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # get line count
>>>>>>>>>>>>>> + theLines="n/a"
>>>>>>>>>>>>>> + hitsPerLine="0"
>>>>>>>>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${lineCount}" ) ; then
>>>>>>>>>>>>>> + theLines=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>>>>>>>>> + totalLines=$(( totalLines + theLines ))
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + if [[ "${theLines}" -gt 2 ]] ; then
>>>>>>>>>>>>>> + hitsPerLine=$(( 100 * theHits / theLines ))
>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # get modification date
>>>>>>>>>>>>>> + theModDate="n/a"
>>>>>>>>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${modDateList}" ) ; then
>>>>>>>>>>>>>> + theModDate=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # add to results list
>>>>>>>>>>>>>> + theResults+="${theName} ${theHits} ${theActive} ${theLines} ${hitsPerLine} ${theModDate}"$'\n'
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +done <<< "${rpzNames}"
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +case "${sortBy}" in
>>>>>>>>>>>>>> + # sort by "active" then by "name"
>>>>>>>>>>>>>> + name) sortArg=(-k3,3r -k1,1) ;;
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # sort by "active" then by "hits" then by "name"
>>>>>>>>>>>>>> + hit) sortArg=(-k3,3r -k2,2nr -k1,1) ;;
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # sort by "active" then by "lines" then by "name"
>>>>>>>>>>>>>> + line) sortArg=(-k3,3r -k4,4nr -k1,1) ;;
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # sort by "active" then by "effect" then by "name"
>>>>>>>>>>>>>> + effect) sortArg=(-k3,3r -k5,5nr -k1,1) ;;
>>>>>>>>>>>>>> +esac
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +printf -- "--------------\n"
>>>>>>>>>>>>>> +# remove blank lines, sort, print as columns
>>>>>>>>>>>>>> +echo "${theResults}" |
>>>>>>>>>>>>>> + awk '!/^[[:space:]]*$/' |
>>>>>>>>>>>>>> + sort "${sortArg[@]}" |
>>>>>>>>>>>>>> + awk --assign=width="${pWidth}" \
>>>>>>>>>>>>>> + '{ printf "%-*s %-8s %-8s %8s %10s %% %12s\n", width, $1, $2, $3, $4, $5, $6 }'
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +printf "${pFormat}" "" "=======" "" "========" "" ""
>>>>>>>>>>>>>> +printf "${pFormat}" "Totals -->" "${totalHits}" "" "${totalLines}" "" ""
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +exit
>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-sleep b/config/rpz/rpz-sleep
>>>>>>>>>>>>>> new file mode 100755
>>>>>>>>>>>>>> index 000000000..dd3603599
>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>> +++ b/config/rpz/rpz-sleep
>>>>>>>>>>>>>> @@ -0,0 +1,58 @@
>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +version="2024-08-16" # v05
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# send message to message log
>>>>>>>>>>>>>> +msg_log () {
>>>>>>>>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>>>>>>>>> + if tty --silent ; then
>>>>>>>>>>>>>> + echo "${tagName}:" "$*"
>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +tagName="unbound"
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +sleepTime="${1:-5m}" # default to sleep for 5m (5 minutes)
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +zoneList=$( unbound-control list_auth_zones | awk '{print $1}' )
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +for zone in ${zoneList} ; do
>>>>>>>>>>>>>> + printf "disable ${zone}\t"
>>>>>>>>>>>>>> + unbound-control rpz_disable "${zone}"
>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +msg_log "info: rpz: disabled all zones for ${sleepTime}"
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +sleep "${sleepTime}"
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +for zone in ${zoneList} ; do
>>>>>>>>>>>>>> + printf "enable ${zone}\t"
>>>>>>>>>>>>>> + unbound-control rpz_enable "${zone}"
>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +msg_log "info: rpz: enabled all zones"
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +exit
>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.de.pl b/config/rpz/rpz.de.pl
>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>> index 000000000..3770c6bb0
>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>> +++ b/config/rpz/rpz.de.pl
>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>> +'rpz apply' => 'Übernehmen',
>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Benutzerdefinierte Allowlist aktivieren:',
>>>>>>>>>>>>>> +'rpz cl allow info' => 'Zugelassene Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>>>>>>>>> +'rpz cl allow' => 'Benutzerdefinierte Allowlist',
>>>>>>>>>>>>>> +'rpz cl block enable' => 'Benutzerdefinierte Blocklist aktivieren:',
>>>>>>>>>>>>>> +'rpz cl block info' => 'Gesperrte Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>>>>>>>>> +'rpz cl block' => 'Benutzerdefinierte Blocklist',
>>>>>>>>>>>>>> +'rpz cl' => 'Benutzerdefinierte Listen',
>>>>>>>>>>>>>> +'rpz exitcode 101' => 'Der Name enthält unzulässige Zeichen',
>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf hat eine fehlerhafte Konfiguration ermittelt. Führen Sie das Kommando unbound-checkconf auf der Konsole aus, um weitere Informationen zu erhalten.',
>>>>>>>>>>>>>> +'rpz exitcode 103' => 'Die benutzerdefinierte Allow-/Blocklist ist leer',
>>>>>>>>>>>>>> +'rpz exitcode 104' => 'Ein Eintrag mit identischem Namen existiert bereits',
>>>>>>>>>>>>>> +'rpz exitcode 105' => 'Die URL ist ungültig',
>>>>>>>>>>>>>> +'rpz exitcode 106' => 'Eintrag kann nicht entfernt werden, der Name existiert nicht',
>>>>>>>>>>>>>> +'rpz exitcode 107' => 'Der Name ist ungültig - nur "allow" oder "block" möglich',
>>>>>>>>>>>>>> +'rpz exitcode 108' => 'Fehlende oder inkorrekte Parameter',
>>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control reload ist fehlgeschlagen',
>>>>>>>>>>>>>> +'rpz exitcode 110' => 'Die benutzerdefinierte Allow-/Blocklist enthält unzulässige Einträge',
>>>>>>>>>>>>>> +'rpz exitcode 201' => 'Die Anmerkung enthält unzulässige Zeichen',
>>>>>>>>>>>>>> +'rpz exitcode 202' => 'Ungültiger Eintrag in der benutzerdefinierten Allowlist, Zeile ',
>>>>>>>>>>>>>> +'rpz exitcode 203' => 'Ungültiger Eintrag in der benutzerdefinierten Blocklist, Zeile ',
>>>>>>>>>>>>>> +'rpz exitcode 204' => 'Ausgewählter Eintrag existiert nicht: ',
>>>>>>>>>>>>>> +'rpz zf editor' => 'Zonendatei-Eintrag bearbeiten',
>>>>>>>>>>>>>> +'rpz zf imported' => '(importiert aus rpz-config)',
>>>>>>>>>>>>>> +'rpz zf remark info' => 'Erlaubte Zeichen sind a-z, A-Z, 0-9 und Unterstriche',
>>>>>>>>>>>>>> +'rpz zf' => 'Zonendateien',
>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.en.pl b/config/rpz/rpz.en.pl
>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>> index 000000000..0720a8940
>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>> +++ b/config/rpz/rpz.en.pl
>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>> +'rpz apply' => 'Apply',
>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Enable custom allowlist:',
>>>>>>>>>>>>>> +'rpz cl allow info' => 'Allowed domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>>> +'rpz cl allow' => 'Custom allowlist',
>>>>>>>>>>>>>> +'rpz cl block enable' => 'Enable custom blocklist:',
>>>>>>>>>>>>>> +'rpz cl block info' => 'Blocked domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>>> +'rpz cl block' => 'Custom blocklist',
>>>>>>>>>>>>>> +'rpz cl' => 'Custom lists',
>>>>>>>>>>>>>> +'rpz exitcode 101' => 'the NAME is not valid',
>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf found invalid configuration. In the Terminal run the command unbound-checkconf for more information',
>>>>>>>>>>>>>> +'rpz exitcode 103' => 'the allow/block list is empty',
>>>>>>>>>>>>>> +'rpz exitcode 104' => 'duplicate - NAME already exists',
>>>>>>>>>>>>>> +'rpz exitcode 105' => 'the URL is not valid',
>>>>>>>>>>>>>> +'rpz exitcode 106' => 'cannot remove the NAME does not exist',
>>>>>>>>>>>>>> +'rpz exitcode 107' => 'the NAME is not valid - "allow" or "block" only',
>>>>>>>>>>>>>> +'rpz exitcode 108' => 'missing or incorrect parameter',
>>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control reload failed',
>>>>>>>>>>>>>> +'rpz exitcode 110' => 'custom Allowlist/Blocklist contains invalid entries',
>>>>>>>>>>>>>> +'rpz exitcode 201' => 'the REMARK is not valid',
>>>>>>>>>>>>>> +'rpz exitcode 202' => 'invalid entry in allowlist, line ',
>>>>>>>>>>>>>> +'rpz exitcode 203' => 'invalid entry in blocklist, line ',
>>>>>>>>>>>>>> +'rpz exitcode 204' => 'Selected entry does not exist: ',
>>>>>>>>>>>>>> +'rpz zf editor' => 'Edit zonefiles entry',
>>>>>>>>>>>>>> +'rpz zf imported' => '(imported from rpz-config)',
>>>>>>>>>>>>>> +'rpz zf remark info' => 'Valid characters are a-z, A-Z, 0-9 and underscore.',
>>>>>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.es.pl b/config/rpz/rpz.es.pl
>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>> index 000000000..98628e4aa
>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>> +++ b/config/rpz/rpz.es.pl
>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>> +'rpz' => 'Zonas de política de respuesta (RPZ)',
>>>>>>>>>>>>>> +'rpz apply' => 'Aplicar',
>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Habilitar la lista blanca personalizada:',
>>>>>>>>>>>>>> +'rpz cl allow info' => 'Dominio permitido (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>>>>>>>>> +'rpz cl allow' => 'Lista blanca personalizada',
>>>>>>>>>>>>>> +'rpz cl block enable' => 'Habilitar la lista negra personalizada:',
>>>>>>>>>>>>>> +'rpz cl block info' => 'Dominio bloqueado (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>>>>>>>>> +'rpz cl block' => 'Lista negra personalizada',
>>>>>>>>>>>>>> +'rpz cl' => 'Lista personalizada',
>>>>>>>>>>>>>> +'rpz exitcode 101' => 'El NOMBRE no es válido',
>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha encontrado una configuración no válida. Desde Terminal, ejecute el comando unbound-checkconf para mayor información',
>>>>>>>>>>>>>> +'rpz exitcode 103' => 'La lista de permitidos/bloqueados está vacía',
>>>>>>>>>>>>>> +'rpz exitcode 104' => 'duplicado - NOMBRE ya existe',
>>>>>>>>>>>>>> +'rpz exitcode 105' => 'la URL no es válida',
>>>>>>>>>>>>>> +'rpz exitcode 106' => 'no es posible eliminar el NOMBRE que no existe',
>>>>>>>>>>>>>> +'rpz exitcode 107' => 'el NOMBRE no es válido - sólo "permitir" o "bloquear"',
>>>>>>>>>>>>>> +'rpz exitcode 108' => 'parámetro faltante o incorrecto',
>>>>>>>>>>>>>> +'rpz exitcode 109' => 'Error al recargar unbound-control',
>>>>>>>>>>>>>> +'rpz exitcode 110' => 'la Lista blanca/Lista negra personalizada contiene entradas no válidas',
>>>>>>>>>>>>>> +'rpz exitcode 201' => 'el COMENTARIO no es válido',
>>>>>>>>>>>>>> +'rpz exitcode 202' => 'entrada no válida en la lista blanca, línea ',
>>>>>>>>>>>>>> +'rpz exitcode 203' => 'entrada no válida en la lista negra, línea ',
>>>>>>>>>>>>>> +'rpz exitcode 204' => 'La entrada seleccionada no existe: ',
>>>>>>>>>>>>>> +'rpz zf editor' => 'Editar la entrada de archivos de zona',
>>>>>>>>>>>>>> +'rpz zf imported' => '(importado de rpz-config)',
>>>>>>>>>>>>>> +'rpz zf remark info' => 'Los caracteres válidos son a-z, A-Z, 0-9 y guión bajo',
>>>>>>>>>>>>>> +'rpz zf' => 'Archivos de zona',
>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.fr.pl b/config/rpz/rpz.fr.pl
>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>> index 000000000..f35f3c2d0
>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>> +++ b/config/rpz/rpz.fr.pl
>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>> +'rpz apply' => 'Appliquer',
>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Activer la liste d\'autorisations personnalisée:',
>>>>>>>>>>>>>> +'rpz cl allow info' => 'Domaines autorisés (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>>> +'rpz cl allow' => 'Liste d\'autorisations personnalisée',
>>>>>>>>>>>>>> +'rpz cl block enable' => 'Activer la liste de blocage personnalisée:',
>>>>>>>>>>>>>> +'rpz cl block info' => 'Domaines bloqués (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>>> +'rpz cl block' => 'liste de blocage personnalisé',
>>>>>>>>>>>>>> +'rpz cl' => 'Listes personnalisées',
>>>>>>>>>>>>>> +'rpz exitcode 101' => 'le NOM n\'est pas valide',
>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf configuration non valide trouvée. Dans le terminal, exécutez la commande unbound-checkconf pour plus d\'informations',
>>>>>>>>>>>>>> +'rpz exitcode 103' => 'la liste autoriser/bloquer est vide',
>>>>>>>>>>>>>> +'rpz exitcode 104' => 'le NOM existe déjà',
>>>>>>>>>>>>>> +'rpz exitcode 105' => 'L\'URL n\'est pas valide',
>>>>>>>>>>>>>> +'rpz exitcode 106' => 'impossible de supprimer le NOM n\'existe pas',
>>>>>>>>>>>>>> +'rpz exitcode 107' => 'le NOM n\'est pas valide - « autoriser » ou « bloquer » seulement',
>>>>>>>>>>>>>> +'rpz exitcode 108' => 'paramètre manquant ou incorrect',
>>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control rechargement échoué',
>>>>>>>>>>>>>> +'rpz exitcode 110' => 'la liste autoriser/bloquer contient des entrées non valides',
>>>>>>>>>>>>>> +'rpz exitcode 201' => 'la REMARQUE n\'est pas valable',
>>>>>>>>>>>>>> +'rpz exitcode 202' => 'entrée non valide dans la liste d\'autorisation, ligne ',
>>>>>>>>>>>>>> +'rpz exitcode 203' => 'entrée non valide dans la liste de blocs, ligne ',
>>>>>>>>>>>>>> +'rpz exitcode 204' => 'L\'entrée sélectionnée n\'existe pas: ',
>>>>>>>>>>>>>> +'rpz zf editor' => 'Modifier l\'entrée Fichiers Zone',
>>>>>>>>>>>>>> +'rpz zf imported' => '(importé de rpz-config)',
>>>>>>>>>>>>>> +'rpz zf remark info' => 'Les caractères valides sont a-z, A-Z, 0-9 et soulignement.',
>>>>>>>>>>>>>> +'rpz zf' => 'Fichiers Zone',
>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.it.pl b/config/rpz/rpz.it.pl
>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>> index 000000000..ee81605c9
>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>> +++ b/config/rpz/rpz.it.pl
>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>> +'rpz apply' => 'Applica',
>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Abilita la Whitelist personalizzata:',
>>>>>>>>>>>>>> +'rpz cl allow info' => 'Domini consentiti (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>>>>>>>>> +'rpz cl allow' => 'Whitelist personalizzata',
>>>>>>>>>>>>>> +'rpz cl block enable' => 'Abilita la Blacklist personalizzata:',
>>>>>>>>>>>>>> +'rpz cl block info' => 'Domini bloccati (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>>>>>>>>> +'rpz cl block' => 'Blacklist personalizzata',
>>>>>>>>>>>>>> +'rpz cl' => 'Liste personalizzate',
>>>>>>>>>>>>>> +'rpz exitcode 101' => 'il NOME non è valido',
>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha trovato una configurazione non valida. Dal Terminale esegui il comando unbound-checkconf per maggiori informazioni',
>>>>>>>>>>>>>> +'rpz exitcode 103' => 'l\'elenco consentiti/bloccati è vuoto',
>>>>>>>>>>>>>> +'rpz exitcode 104' => 'duplicato - NAME esiste di già',
>>>>>>>>>>>>>> +'rpz exitcode 105' => 'l\'URL non è valido',
>>>>>>>>>>>>>> +'rpz exitcode 106' => 'non è possibile rimuovere il NOME non esiste',
>>>>>>>>>>>>>> +'rpz exitcode 107' => 'il NOME non è valido - solo "consenti" o "blocca"',
>>>>>>>>>>>>>> +'rpz exitcode 108' => 'parametro mancante o non corretto',
>>>>>>>>>>>>>> +'rpz exitcode 109' => 'ricaricamento del controllo non associato non riuscito',
>>>>>>>>>>>>>> +'rpz exitcode 110' => 'la Whitelist/Blacklist personalizzata contiene voci non valide',
>>>>>>>>>>>>>> +'rpz exitcode 201' => 'l"OSSERVAZIONE non è valida',
>>>>>>>>>>>>>> +'rpz exitcode 202' => 'voce non valida nella Whitelist, riga ',
>>>>>>>>>>>>>> +'rpz exitcode 203' => 'voce non valida nella Blacklist, riga ',
>>>>>>>>>>>>>> +'rpz exitcode 204' => 'La voce selezionata non esiste: ',
>>>>>>>>>>>>>> +'rpz zf editor' => 'Modifica la voce dei file di zona',
>>>>>>>>>>>>>> +'rpz zf imported' => '(importato da rpz-config)',
>>>>>>>>>>>>>> +'rpz zf remark info' => 'I caratteri validi sono a-z, A-Z, 0-9 e trattino basso',
>>>>>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.tr.pl b/config/rpz/rpz.tr.pl
>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>> index 000000000..00226e192
>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>> +++ b/config/rpz/rpz.tr.pl
>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>> +# I�in eklendi Ayriyeten Response Policy Zone (RPZ)
>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>> +'rpz apply' => 'Uygulamak',
>>>>>>>>>>>>>> +'rpz cl allow enable' => '�zel Etkinlestir allowlist:',
>>>>>>>>>>>>>> +'rpz cl allow info' => 'Izin verilmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>>>>>>>>> +'rpz cl allow' => 'Etkinlestir allowlist',
>>>>>>>>>>>>>> +'rpz cl block enable' => '�zel Etkinlestir blocklist:',
>>>>>>>>>>>>>> +'rpz cl block info' => 'Engellenmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>>>>>>>>> +'rpz cl block' => 'Engellenmis blocklist',
>>>>>>>>>>>>>> +'rpz cl' => 'Engellenmis lists',
>>>>>>>>>>>>>> +'rpz exitcode 101' => 'NAME ge�erli degil',
>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ge�ersiz yapilandirma bulundu. Terminalde daha fazla bilgi i�in Unbound-checkConf komutunu �alistirin',
>>>>>>>>>>>>>> +'rpz exitcode 103' => 'allow/block liste bos',
>>>>>>>>>>>>>> +'rpz exitcode 104' => 'kopyalamak - NAME zaten var',
>>>>>>>>>>>>>> +'rpz exitcode 105' => 'URL ge�erli degil',
>>>>>>>>>>>>>> +'rpz exitcode 106' => '�ikarilamiyor NAME yok',
>>>>>>>>>>>>>> +'rpz exitcode 107' => 'NAME ge�erli degil - "allow" veya "block" yalniz',
>>>>>>>>>>>>>> +'rpz exitcode 108' => 'Parametre eksik veya yanlis',
>>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control basarisiz',
>>>>>>>>>>>>>> +'rpz exitcode 110' => 'Engellenmis Allowlist/Blocklist ge�ersiz girisler i�erir',
>>>>>>>>>>>>>> +'rpz exitcode 201' => 'REMARK ge�erli degil',
>>>>>>>>>>>>>> +'rpz exitcode 202' => 'Ge�ersiz giris allowlist, line ',
>>>>>>>>>>>>>> +'rpz exitcode 203' => 'Ge�ersiz giris blocklist, line ',
>>>>>>>>>>>>>> +'rpz exitcode 204' => 'Se�ilen giris yok: ',
>>>>>>>>>>>>>> +'rpz zf editor' => 'yazimlamak zonefiles giris',
>>>>>>>>>>>>>> +'rpz zf imported' => '(ithal edildi rpz-config)',
>>>>>>>>>>>>>> +'rpz zf remark info' => 'Ge�erli karakterler a-z, A-Z, 0-9ve alt�st.',
>>>>>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>> diff --git a/html/cgi-bin/rpz.cgi b/html/cgi-bin/rpz.cgi
>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>> index 000000000..a821c92ac
>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>> +++ b/html/cgi-bin/rpz.cgi
>>>>>>>>>>>>>> @@ -0,0 +1,923 @@
>>>>>>>>>>>>>> +#!/usr/bin/perl
>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>> +# Copyright (C) 2005-2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +use strict;
>>>>>>>>>>>>>> +use Scalar::Util qw(looks_like_number);
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# debugging
>>>>>>>>>>>>>> +#use warnings;
>>>>>>>>>>>>>> +#use CGI::Carp 'fatalsToBrowser';
>>>>>>>>>>>>>> +#use Data::Dumper;
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +require '/var/ipfire/general-functions.pl';
>>>>>>>>>>>>>> +require "${General::swroot}/lang.pl";
>>>>>>>>>>>>>> +require "${General::swroot}/header.pl";
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +###--- Extra HTML ---###
>>>>>>>>>>>>>> +my $extraHead = <<END
>>>>>>>>>>>>>> +<style>
>>>>>>>>>>>>>> + /* alternating row background */
>>>>>>>>>>>>>> + .tbl tr:nth-child(2n+2) {
>>>>>>>>>>>>>> + background-color: var(--color-light-grey);
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> + .tbl tr:nth-child(2n+3) {
>>>>>>>>>>>>>> + background-color: var(--color-grey);
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> + /* text styles */
>>>>>>>>>>>>>> + .tbl th:not(:last-child) {
>>>>>>>>>>>>>> + text-align: left;
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> + div.right {
>>>>>>>>>>>>>> + text-align: right;
>>>>>>>>>>>>>> + margin-top: 0.5em;
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> + /* customlist input */
>>>>>>>>>>>>>> + textarea.domainlist {
>>>>>>>>>>>>>> + margin: 0.5em 0;
>>>>>>>>>>>>>> + resize: vertical;
>>>>>>>>>>>>>> + min-height: 10em;
>>>>>>>>>>>>>> + overflow: auto;
>>>>>>>>>>>>>> + white-space: pre;
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> + button[type=submit]:disabled {
>>>>>>>>>>>>>> + opacity: 0.6;
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> +</style>
>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>> +###--- End of extra HTML ---###
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +### Settings ###
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# Request DNS service reload after configuration change
>>>>>>>>>>>>>> +my $RPZ_RELOAD_FLAG = "${General::swroot}/dns/rpz/reload.flag";
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# Configuration file for all available zonefiles
>>>>>>>>>>>>>> +# Format: index, name (unique), enabled (on/off), URL, remark
>>>>>>>>>>>>>> +my $ZONEFILES_CONF = "${General::swroot}/dns/rpz/zonefiles.conf";
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# Configuration file for custom lists
>>>>>>>>>>>>>> +# IDs: 0=allowlist, 1=blocklist, 2=options (allow/block enabled)
>>>>>>>>>>>>>> +my $CUSTOMLISTS_CONF = "${General::swroot}/dns/rpz/customlists.conf";
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# Export custom lists to rpz-config
>>>>>>>>>>>>>> +my $RPZ_ALLOWLIST = "${General::swroot}/dns/rpz/allowlist";
>>>>>>>>>>>>>> +my $RPZ_BLOCKLIST = "${General::swroot}/dns/rpz/blocklist";
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +### Preparation ###
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# Create missing config files
>>>>>>>>>>>>>> +unless(-f $ZONEFILES_CONF) { &General::system('touch', "$ZONEFILES_CONF"); }
>>>>>>>>>>>>>> +unless(-f $CUSTOMLISTS_CONF) { &General::system('touch', "$CUSTOMLISTS_CONF"); }
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +## Global gui data
>>>>>>>>>>>>>> +my $errormessage = "";
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +## Global configuration data
>>>>>>>>>>>>>> +my %zonefiles = ();
>>>>>>>>>>>>>> +my %customlists = ();
>>>>>>>>>>>>>> +&_zonefiles_load();
>>>>>>>>>>>>>> +&_customlists_load();
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +## Global CGI form data
>>>>>>>>>>>>>> +my %cgiparams = ();
>>>>>>>>>>>>>> +&Header::getcgihash(\%cgiparams);
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +my $action = $cgiparams{'ACTION'} // 'NONE';
>>>>>>>>>>>>>> +my $action_key = $cgiparams{'KEY'} // ''; # entry being edited, empty = none/new
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +###--- Process form actions ---###
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# Zonefiles action: Check whether the requested entry exists
>>>>>>>>>>>>>> +if((substr($action, 0, 3) eq 'ZF_') && ($action_key)) {
>>>>>>>>>>>>>> + unless(defined $zonefiles{$action_key}) {
>>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr(204, $action_key);
>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +## Perform actions
>>>>>>>>>>>>>> +if($action eq 'ZF_SAVE') { ## Save new or modified zonefiles entry
>>>>>>>>>>>>>> + if(&_action_zf_save()) {
>>>>>>>>>>>>>> + $action = 'NONE'; # success, return to main page
>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>> + $action = 'ZF_EDIT'; # error occured, keep editing
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +} elsif($action eq 'ZF_TOGGLE') { ## Toggle on/off
>>>>>>>>>>>>>> + if(&_action_zf_toggle()) {
>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +} elsif($action eq 'ZF_REMOVE') { ## Remove entry
>>>>>>>>>>>>>> + if(&_action_zf_remove()) {
>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +} elsif($action eq 'CL_SAVE') { ## Save custom lists
>>>>>>>>>>>>>> + if(&_action_cl_save()) {
>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +} elsif($action eq 'RPZ_RELOAD') { ## Reload dns configuration
>>>>>>>>>>>>>> + if(&_action_rpz_reload()) {
>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +} elsif($action eq 'UNB_RESTART') { ## Restart unbound service
>>>>>>>>>>>>>> + if(&_action_unb_restart()) {
>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +###--- Start GUI ---###
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +## Start http output
>>>>>>>>>>>>>> +&Header::showhttpheaders();
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# Start HTML
>>>>>>>>>>>>>> +&Header::openpage($Lang::tr{'rpz'}, 1, $extraHead);
>>>>>>>>>>>>>> +&Header::openbigbox('100%', 'left', '');
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# Show error messages
>>>>>>>>>>>>>> +if($errormessage) {
>>>>>>>>>>>>>> + &_print_message($errormessage);
>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# Handle zonefile add/edit mode
>>>>>>>>>>>>>> +if($action eq "ZF_EDIT") {
>>>>>>>>>>>>>> + &_print_zonefile_editor();
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Finalize page and exit cleanly
>>>>>>>>>>>>>> + &Header::closebigbox();
>>>>>>>>>>>>>> + &Header::closepage();
>>>>>>>>>>>>>> + exit(0);
>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# Show gui elements
>>>>>>>>>>>>>> +&_print_zonefiles();
>>>>>>>>>>>>>> +&_print_customlists();
>>>>>>>>>>>>>> +&_print_gui_extras();
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +&Header::closebigbox();
>>>>>>>>>>>>>> +&Header::closepage();
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +###--- End of GUI ---###
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +###--- Internal configuration file functions ---###
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# Load all available zonefiles from rpz-config and the internal configuration
>>>>>>>>>>>>>> +sub _zonefiles_load {
>>>>>>>>>>>>>> + # Clean start
>>>>>>>>>>>>>> + %zonefiles = ();
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Source 1: Get the currently enabled zonefiles from rpz-config (expected format [name]=[URL])
>>>>>>>>>>>>>> + my @enabled_files = &General::system_output('/usr/sbin/rpz-config', 'list');
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + foreach my $row (@enabled_files) {
>>>>>>>>>>>>>> + chomp($row);
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Use regex instead of split() to skip non-matching lines
>>>>>>>>>>>>>> + next unless($row =~ /^(\w+)=(.+)$/);
>>>>>>>>>>>>>> + my ($name, $url) = ($1, $2);
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Unique names are already guaranteed by rpz-config
>>>>>>>>>>>>>> + if(&_rpz_validate_zonefile($name, $url, '', 0) == 0) {
>>>>>>>>>>>>>> + # Populate global data hash, mark all found entries as enabled
>>>>>>>>>>>>>> + my %entry = ('enabled' => 'on',
>>>>>>>>>>>>>> + 'url' => $url,
>>>>>>>>>>>>>> + 'remark' => $Lang::tr{'rpz zf imported'});
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Source 2: Get additional data and disabled entries from configuration file
>>>>>>>>>>>>>> + my %configured_files = ();
>>>>>>>>>>>>>> + &General::readhasharray($ZONEFILES_CONF, \%configured_files);
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + foreach my $row (values (%configured_files)) {
>>>>>>>>>>>>>> + my ($name, $enabled, $url, $remark) = @$row;
>>>>>>>>>>>>>> + $remark //= "";
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + next unless($name);
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Check whether this row belongs to an entry already imported from rpz-config
>>>>>>>>>>>>>> + if(defined $zonefiles{$name}) {
>>>>>>>>>>>>>> + # Existing entry, only merge additional data
>>>>>>>>>>>>>> + $zonefiles{$name}{'remark'} = $remark;
>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>> + # Skip entry if it is marked as enabled but not found by rpz-config. It was then deleted manually
>>>>>>>>>>>>>> + if($enabled ne 'on') {
>>>>>>>>>>>>>> + # Populate global data hash
>>>>>>>>>>>>>> + my %entry = ('enabled' => 'off',
>>>>>>>>>>>>>> + 'url' => $url // "",
>>>>>>>>>>>>>> + 'remark' => $remark);
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# Save internal zonefiles configuration
>>>>>>>>>>>>>> +sub _zonefiles_save_conf {
>>>>>>>>>>>>>> + my $index = 0;
>>>>>>>>>>>>>> + my %export = ();
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Loop trough all zonefiles and create "hasharray" type export
>>>>>>>>>>>>>> + foreach my $name (keys %zonefiles) {
>>>>>>>>>>>>>> + my @entry = ($name,
>>>>>>>>>>>>>> + $zonefiles{$name}{'enabled'},
>>>>>>>>>>>>>> + $zonefiles{$name}{'url'},
>>>>>>>>>>>>>> + $zonefiles{$name}{'remark'});
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + $export{$index++} = \@entry;
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + &General::writehasharray($ZONEFILES_CONF, \%export);
>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# Load custom lists from rpz-config and the internal configuration
>>>>>>>>>>>>>> +sub _customlists_load {
>>>>>>>>>>>>>> + # Clean start
>>>>>>>>>>>>>> + %customlists = ();
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Load configuration file
>>>>>>>>>>>>>> + my %lists_conf = ();
>>>>>>>>>>>>>> + &General::readhasharray($CUSTOMLISTS_CONF, \%lists_conf);
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Get list options, enabled by default to start import
>>>>>>>>>>>>>> + $customlists{'allow'}{'enabled'} = $lists_conf{2}[0] // 'on';
>>>>>>>>>>>>>> + $customlists{'block'}{'enabled'} = $lists_conf{2}[1] // 'on';
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Import enabled list from rpz-config, otherwise retrieve stored or empty list from configuration file
>>>>>>>>>>>>>> + if($customlists{'allow'}{'enabled'} eq 'on') {
>>>>>>>>>>>>>> + &_customlist_import('allow', $RPZ_ALLOWLIST);
>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>> + $customlists{'allow'}{'list'} = $lists_conf{0} // [];
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> + if($customlists{'block'}{'enabled'} eq 'on') {
>>>>>>>>>>>>>> + &_customlist_import('block', $RPZ_BLOCKLIST);
>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>> + $customlists{'block'}{'list'} = $lists_conf{1} // [];
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# Save internal custom lists configuration
>>>>>>>>>>>>>> +sub _customlists_save_conf {
>>>>>>>>>>>>>> + my %export = ();
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Match IDs with import function
>>>>>>>>>>>>>> + $export{0} = $customlists{'allow'}{'list'};
>>>>>>>>>>>>>> + $export{1} = $customlists{'block'}{'list'};
>>>>>>>>>>>>>> + $export{2} = [$customlists{'allow'}{'enabled'}, $customlists{'block'}{'enabled'}];
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + &General::writehasharray($CUSTOMLISTS_CONF, \%export);
>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# Import a custom list from plain file, returns empty list if file is missing
>>>>>>>>>>>>>> +sub _customlist_import {
>>>>>>>>>>>>>> + my ($listname, $filename) = @_;
>>>>>>>>>>>>>> + my @list = ();
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # File exists, load and check all lines
>>>>>>>>>>>>>> + if(-f $filename) {
>>>>>>>>>>>>>> + open(my $FH, '<', $filename) or die "Can't read $filename: $!";
>>>>>>>>>>>>>> + while(my $line = <$FH>) {
>>>>>>>>>>>>>> + chomp($line);
>>>>>>>>>>>>>> + push(@list, $line);
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> + close($FH);
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Clean up imported data
>>>>>>>>>>>>>> + &_rpz_validate_customlist(\@list, 1);
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + $customlists{$listname}{'list'} = \@list;
>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# Export a custom list to plain file or clear file if list is disabled
>>>>>>>>>>>>>> +sub _customlist_export {
>>>>>>>>>>>>>> + my ($listname, $filename) = @_;
>>>>>>>>>>>>>> + return unless(defined $customlists{$listname});
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Write enabled domain list to file, otherwise save empty file
>>>>>>>>>>>>>> + open(my $FH, '>', $filename) or die "Can't write $filename: $!";
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + if($customlists{$listname}{'enabled'} eq 'on') {
>>>>>>>>>>>>>> + foreach my $line (@{$customlists{$listname}{'list'}}) {
>>>>>>>>>>>>>> + print $FH "$line\n";
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>> + print $FH "; Note: This list is currently disabled by $ENV{'SCRIPT_NAME'}\n";
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + close($FH);
>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +###--- Internal gui functions ---###
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# Show simple message box
>>>>>>>>>>>>>> +sub _print_message {
>>>>>>>>>>>>>> + my ($message, $title) = @_;
>>>>>>>>>>>>>> + $title ||= $Lang::tr{'error messages'};
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $title);
>>>>>>>>>>>>>> + print "<span>$message</span>";
>>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# Show all zone files and related gui elements
>>>>>>>>>>>>>> +sub _print_zonefiles {
>>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf'});
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>> +<table class="tbl" width="100%">
>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>> + <th>$Lang::tr{'name'}</th>
>>>>>>>>>>>>>> + <th>URL</th>
>>>>>>>>>>>>>> + <th>$Lang::tr{'remark'}</th>
>>>>>>>>>>>>>> + <th colspan="3">$Lang::tr{'action'}</th>
>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Sort zonefiles by name and loop trough all entries
>>>>>>>>>>>>>> + foreach my $name (sort keys %zonefiles) {
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Toggle button label translation
>>>>>>>>>>>>>> + my $toggle_tr = ($zonefiles{$name}{'enabled'} eq 'on') ? $Lang::tr{'click to disable'} : $Lang::tr{'click to enable'};
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>> + <td>$name</td>
>>>>>>>>>>>>>> + <td>$zonefiles{$name}{'url'}</td>
>>>>>>>>>>>>>> + <td>$zonefiles{$name}{'remark'}</td>
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_TOGGLE">
>>>>>>>>>>>>>> + <input type="image" src="/images/$zonefiles{$name}{'enabled'}.gif" title="$toggle_tr" alt="$toggle_tr">
>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_EDIT">
>>>>>>>>>>>>>> + <input type="image" src="/images/edit.gif" title="$Lang::tr{'edit'}" alt="$Lang::tr{'edit'}">
>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_REMOVE">
>>>>>>>>>>>>>> + <input type="image" src="/images/delete.gif" title="$Lang::tr{'remove'}" alt="$Lang::tr{'remove'}">
>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Disable reload button if not needed
>>>>>>>>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>> +</table>
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +<div class="right">
>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="">
>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="ZF_EDIT">$Lang::tr{'add'}</button>
>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>> +</div>
>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# Show zonefiles entry editor
>>>>>>>>>>>>>> +sub _print_zonefile_editor {
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Key specified: Edit existing entry
>>>>>>>>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>>>>>>>>> + # Load data to be edited, but don't override already present values (allows user to edit after error)
>>>>>>>>>>>>>> + $cgiparams{'ZF_NAME'} //= $action_key;
>>>>>>>>>>>>>> + $cgiparams{'ZF_URL'} //= $zonefiles{$action_key}{'url'};
>>>>>>>>>>>>>> + $cgiparams{'ZF_REMARK'} //= $zonefiles{$action_key}{'remark'};
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Fallback to empty form
>>>>>>>>>>>>>> + $cgiparams{'ZF_NAME'} //= "";
>>>>>>>>>>>>>> + $cgiparams{'ZF_URL'} //= "";
>>>>>>>>>>>>>> + $cgiparams{'ZF_REMARK'} //= "";
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf editor'});
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>> +<input type="hidden" name="KEY" value="$action_key">
>>>>>>>>>>>>>> +<table width="100%">
>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>> + <td width="20%">$Lang::tr{'name'}: <img src="/blob.gif" alt="*"></td>
>>>>>>>>>>>>>> + <td><input type="text" name="ZF_NAME" value="$cgiparams{'ZF_NAME'}" size="40" maxlength="32" title="$Lang::tr{'rpz zf remark info'}" pattern="[a-zA-Z0-9_]{1,32}" required></td>
>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>> + <td width="20%">URL: <img src="/blob.gif" alt="*"></td>
>>>>>>>>>>>>>> + <td><input type="url" name="ZF_URL" value="$cgiparams{'ZF_URL'}" size="40" maxlength="128" required></td>
>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>> + <td width="20%">$Lang::tr{'remark'}:</td>
>>>>>>>>>>>>>> + <td><input type="text" name="ZF_REMARK" value="$cgiparams{'ZF_REMARK'}" size="40" maxlength="32"></td>
>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>> + <td colspan="2"><hr></td>
>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>> + <td width="55%"><img src="/blob.gif" alt="*"> $Lang::tr{'required field'}</td>
>>>>>>>>>>>>>> + <td align="right"><button type="submit" name="ACTION" value="ZF_SAVE">$Lang::tr{'save'}</button></td>
>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>> +</table>
>>>>>>>>>>>>>> +</form>
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +<div class="right">
>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="NONE">$Lang::tr{'back'}</button>
>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>> +</div>
>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# Show custom allow/block files and related gui elements
>>>>>>>>>>>>>> +sub _print_customlists {
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Load lists from config, unless they are currently being edited
>>>>>>>>>>>>>> + if($action ne 'CL_SAVE') {
>>>>>>>>>>>>>> + $cgiparams{'ALLOW_LIST'} = join("\n", @{$customlists{'allow'}{'list'}});
>>>>>>>>>>>>>> + $cgiparams{'BLOCK_LIST'} = join("\n", @{$customlists{'block'}{'list'}});
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + $cgiparams{'ALLOW_ENABLED'} = ($customlists{'allow'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>>>>>>>>> + $cgiparams{'BLOCK_ENABLED'} = ($customlists{'block'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Fallback to empty form
>>>>>>>>>>>>>> + $cgiparams{'ALLOW_LIST'} //= "";
>>>>>>>>>>>>>> + $cgiparams{'BLOCK_LIST'} //= "";
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # HTML checkboxes, unchecked = no or undef value in POST data
>>>>>>>>>>>>>> + my %checked = ();
>>>>>>>>>>>>>> + $checked{'ALLOW_ENABLED'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? " checked" : "";
>>>>>>>>>>>>>> + $checked{'BLOCK_ENABLED'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? " checked" : "";
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Disable reload button if not needed
>>>>>>>>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz cl'});
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>> +<table width="100%">
>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl allow'}</b><br>$Lang::tr{'rpz cl allow info'}</td>
>>>>>>>>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl block'}</b><br>$Lang::tr{'rpz cl block info'}</td>
>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>> + <td colspan="2"><textarea name="ALLOW_LIST" class="domainlist" cols="45">
>>>>>>>>>>>>>> +$cgiparams{'ALLOW_LIST'}</textarea></td>
>>>>>>>>>>>>>> + <td colspan="2"><textarea name="BLOCK_LIST" class="domainlist" cols="45">
>>>>>>>>>>>>>> +$cgiparams{'BLOCK_LIST'}</textarea></td>
>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>> + <td><label for="allow_enabled">$Lang::tr{'rpz cl allow enable'}</label></td>
>>>>>>>>>>>>>> + <td width="15%"><input type="checkbox" name="ALLOW_ENABLED" id="allow_enabled"$checked{'ALLOW_ENABLED'}></td>
>>>>>>>>>>>>>> + <td><label for="block_enabled">$Lang::tr{'rpz cl block enable'}</label></td>
>>>>>>>>>>>>>> + <td width="15%"><input type="checkbox" name="BLOCK_ENABLED" id="block_enabled"$checked{'BLOCK_ENABLED'}></td>
>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>> + <td colspan="4"><hr></td>
>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>> + <td align="right" colspan="4">
>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="CL_SAVE">$Lang::tr{'save'}</button>
>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>>> +</table>
>>>>>>>>>>>>>> +</form>
>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# Output javascript and extra gui elements
>>>>>>>>>>>>>> +sub _print_gui_extras {
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Apply/Restart button modifier key handler
>>>>>>>>>>>>>> + if(&_rpz_needs_reload()) {
>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>> +<script>
>>>>>>>>>>>>>> + // Commit modifier key handler
>>>>>>>>>>>>>> + (function(jq, document) {
>>>>>>>>>>>>>> + var keyEventsOn = false; // Keyboard events attached
>>>>>>>>>>>>>> + var keyModify = false; // Modifier key pressed
>>>>>>>>>>>>>> + var mouseHover = false; // Mouse over commit button
>>>>>>>>>>>>>> + var btnModified = false; // Button modified to "Restart"
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + // Document-level key events, enable only while cursor is over button
>>>>>>>>>>>>>> + function attachKeyEvents() {
>>>>>>>>>>>>>> + if(keyEventsOn) {
>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> + keyEventsOn = true;
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + jq(document).on("keydown.rpz", function(event) {
>>>>>>>>>>>>>> + if((!keyModify) && event.shiftKey) {
>>>>>>>>>>>>>> + keyModify = true;
>>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>> + jq(document).on("keyup.rpz", function(event) {
>>>>>>>>>>>>>> + if(keyModify && (!event.shiftKey)) {
>>>>>>>>>>>>>> + keyModify = false;
>>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> + function removeKeyEvents() {
>>>>>>>>>>>>>> + keyModify = false;
>>>>>>>>>>>>>> + if(keyEventsOn) {
>>>>>>>>>>>>>> + jq(document).off("keydown.rpz keyup.rpz");
>>>>>>>>>>>>>> + keyEventsOn = false;
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + // Attach mouse hover events to commit buttons
>>>>>>>>>>>>>> + function attachMouseEvents() {
>>>>>>>>>>>>>> + jq("button.commit").on("mouseenter", function(event) {
>>>>>>>>>>>>>> + if(!mouseHover) {
>>>>>>>>>>>>>> + mouseHover = true;
>>>>>>>>>>>>>> + attachKeyEvents();
>>>>>>>>>>>>>> + // Handle already pressed key
>>>>>>>>>>>>>> + keyModify = !!(event.shiftKey);
>>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + // Cursor moved away: Disable key listener to minimize events
>>>>>>>>>>>>>> + jq("button.commit").on("mouseleave", function() {
>>>>>>>>>>>>>> + if(mouseHover) {
>>>>>>>>>>>>>> + mouseHover = false;
>>>>>>>>>>>>>> + removeKeyEvents();
>>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + // Modify commit button
>>>>>>>>>>>>>> + function handleModify() {
>>>>>>>>>>>>>> + let modify = mouseHover && keyModify;
>>>>>>>>>>>>>> + if(btnModified != modify) {
>>>>>>>>>>>>>> + if(modify) {
>>>>>>>>>>>>>> + jq("button.commit").text("$Lang::tr{'restart'}").val("UNB_RESTART");
>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>> + jq("button.commit").text("$Lang::tr{'rpz apply'}").val("RPZ_RELOAD");
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> + btnModified = modify;
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + // jQuery DOM ready
>>>>>>>>>>>>>> + jq(function() {
>>>>>>>>>>>>>> + attachMouseEvents();
>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>> + })(jQuery, document);
>>>>>>>>>>>>>> +</script>
>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>> + } # End of modifier key handler
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +###--- Internal action processing functions ---###
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# Toggle zonefile on/off
>>>>>>>>>>>>>> +sub _action_zf_toggle {
>>>>>>>>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + my $result = 0;
>>>>>>>>>>>>>> + my $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Perform toggle action
>>>>>>>>>>>>>> + if($enabled eq 'on') {
>>>>>>>>>>>>>> + $enabled = 'off';
>>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>> + $enabled = 'on';
>>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $action_key, $zonefiles{$action_key}{'url'}, '--no-reload');
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Save changes
>>>>>>>>>>>>>> + $zonefiles{$action_key}{'enabled'} = $enabled;
>>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# Remove zonefile
>>>>>>>>>>>>>> +sub _action_zf_remove {
>>>>>>>>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Remove from rpz-config if currently active
>>>>>>>>>>>>>> + if($zonefiles{$action_key}{'enabled'} eq 'on') {
>>>>>>>>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Remove from data hash and save changes
>>>>>>>>>>>>>> + delete $zonefiles{$action_key};
>>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Clear action_key, as the entry is now removed entirely
>>>>>>>>>>>>>> + $action_key = "";
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# Create or update zonefile entry
>>>>>>>>>>>>>> +# Returns undef if gui needs to stay in editor mode
>>>>>>>>>>>>>> +sub _action_zf_save {
>>>>>>>>>>>>>> + my $result = 0;
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + my $name = $cgiparams{'ZF_NAME'} // "";
>>>>>>>>>>>>>> + my $url = $cgiparams{'ZF_URL'} // "";
>>>>>>>>>>>>>> + my $remark = $cgiparams{'ZF_REMARK'} // "";
>>>>>>>>>>>>>> + my $enabled = 'on'; # Enable new entries by default
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Note on variables:
>>>>>>>>>>>>>> + # name = unique key, will be used to address the entry
>>>>>>>>>>>>>> + # action_key = name of the entry being edited, empty for new entry
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Only check for unique name if it changed
>>>>>>>>>>>>>> + # (this also checks new entries because the action_key is empty in this case)
>>>>>>>>>>>>>> + $result = &_rpz_validate_zonefile($name, $url, $remark, (lc($name) ne lc($action_key)));
>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 0);
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Edit existing entry: Determine what was changed
>>>>>>>>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>>>>>>>>> + # Name und URL remain unchanged, only save remark and finish
>>>>>>>>>>>>>> + if(($name eq $action_key) && ($url eq $zonefiles{$action_key}{'url'})) {
>>>>>>>>>>>>>> + $zonefiles{$action_key}{'remark'} = $remark;
>>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Entry was changed and needs to be recreated, preserve status
>>>>>>>>>>>>>> + $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Remove from rpz-config
>>>>>>>>>>>>>> + return unless &_action_zf_remove();
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Add new entry to rpz-config
>>>>>>>>>>>>>> + if($enabled eq 'on') {
>>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $name, $url, '--no-reload');
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Add to global data hash and save changes
>>>>>>>>>>>>>> + my %entry = ('enabled' => $enabled,
>>>>>>>>>>>>>> + 'url' => $url,
>>>>>>>>>>>>>> + 'remark' => $remark);
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# Save custom lists
>>>>>>>>>>>>>> +sub _action_cl_save {
>>>>>>>>>>>>>> + return unless((defined $cgiparams{'ALLOW_LIST'}) && (defined $cgiparams{'BLOCK_LIST'}));
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + my $result = 0;
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + my @allowlist = split(/\R/, $cgiparams{'ALLOW_LIST'});
>>>>>>>>>>>>>> + my @blocklist = split(/\R/, $cgiparams{'BLOCK_LIST'});
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Validate lists
>>>>>>>>>>>>>> + $result = &_rpz_validate_customlist(\@allowlist);
>>>>>>>>>>>>>> + if($result != 0) {
>>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr(202, $result);
>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> + $result = &_rpz_validate_customlist(\@blocklist);
>>>>>>>>>>>>>> + if($result != 0) {
>>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr(203, $result);
>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Add to global data hash and save changes
>>>>>>>>>>>>>> + $customlists{'allow'}{'list'} = \@allowlist;
>>>>>>>>>>>>>> + $customlists{'block'}{'list'} = \@blocklist;
>>>>>>>>>>>>>> + $customlists{'allow'}{'enabled'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? 'on' : 'off';
>>>>>>>>>>>>>> + $customlists{'block'}{'enabled'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? 'on' : 'off';
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + &_customlists_save_conf();
>>>>>>>>>>>>>> + &_customlist_export('allow', $RPZ_ALLOWLIST);
>>>>>>>>>>>>>> + &_customlist_export('block', $RPZ_BLOCKLIST);
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Make new lists, request service reload on success
>>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-make', 'allowblock', '--no-reload');
>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# Trigger rpz-config reload
>>>>>>>>>>>>>> +sub _action_rpz_reload {
>>>>>>>>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Immediately clear flag to prevent multiple reloads
>>>>>>>>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>>>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Perform reload, recreate reload flag on error to enable retry
>>>>>>>>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'reload');
>>>>>>>>>>>>>> + if(not &_rpz_check_result($result, 0)) {
>>>>>>>>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# Trigger unbound restart
>>>>>>>>>>>>>> +sub _action_unb_restart {
>>>>>>>>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Immediately clear flag to prevent multiple restarts
>>>>>>>>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>>>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Perform restart, unboundctrl always exits zero
>>>>>>>>>>>>>> + &General::system('/usr/local/bin/unboundctrl', 'restart');
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +###--- Internal rpz-config functions ---###
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# Translate rpz-config exitcodes and messages
>>>>>>>>>>>>>> +# 100-199: rpz-config, 200-299: webgui
>>>>>>>>>>>>>> +sub _rpz_error_tr {
>>>>>>>>>>>>>> + my ($error, $append) = @_;
>>>>>>>>>>>>>> + $append //= '';
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Translate numeric exit codes
>>>>>>>>>>>>>> + if(looks_like_number($error)) {
>>>>>>>>>>>>>> + if(defined $Lang::tr{"rpz exitcode $error"}) {
>>>>>>>>>>>>>> + $error = $Lang::tr{"rpz exitcode $error"};
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + return "RPZ $Lang::tr{'error'}: $error" . &Header::escape($append);
>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# Check result of rpz-config system call, request reload on success
>>>>>>>>>>>>>> +sub _rpz_check_result {
>>>>>>>>>>>>>> + my ($result, $request_reload) = @_;
>>>>>>>>>>>>>> + $request_reload //= 0;
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # exitcode 0 = success
>>>>>>>>>>>>>> + if($result != 0) {
>>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr($result);
>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Set reload flag
>>>>>>>>>>>>>> + if($request_reload) {
>>>>>>>>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# Test whether reload flag is set
>>>>>>>>>>>>>> +sub _rpz_needs_reload {
>>>>>>>>>>>>>> + return (-f $RPZ_RELOAD_FLAG);
>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# Validate a zonefile entry, returns rpz-config exitcode on failure. Use _rpz_check_result to verify.
>>>>>>>>>>>>>> +# unique = check for unique name
>>>>>>>>>>>>>> +sub _rpz_validate_zonefile {
>>>>>>>>>>>>>> + my ($name, $url, $remark, $unique) = @_;
>>>>>>>>>>>>>> + $unique //= 1;
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + unless($name =~ /^[a-zA-Z0-9_]{1,32}$/) {
>>>>>>>>>>>>>> + return 101;
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> + unless($url =~ /^[\w+\.:;\/\\&@#%?=\-~|!]{1,128}$/) {
>>>>>>>>>>>>>> + return 105;
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> + unless($remark =~ /^[\w \-()\.:;*\/\\?!&=]{0,32}$/) {
>>>>>>>>>>>>>> + return 201;
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Check against already existing names
>>>>>>>>>>>>>> + if($unique) {
>>>>>>>>>>>>>> + foreach my $existing (keys %zonefiles) {
>>>>>>>>>>>>>> + if(lc($name) eq lc($existing)) {
>>>>>>>>>>>>>> + return 104;
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + return 0;
>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# Validate a custom list, returns number of rejected line on failure. Check for non-zero results.
>>>>>>>>>>>>>> +# listref = array reference, cleanup = remove invalid entries instead of returning an error
>>>>>>>>>>>>>> +sub _rpz_validate_customlist {
>>>>>>>>>>>>>> + my ($listref, $cleanup) = @_;
>>>>>>>>>>>>>> + $cleanup //= 0;
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + foreach my $index (reverse 0..$#{$listref}) {
>>>>>>>>>>>>>> + my $row = @$listref[$index];
>>>>>>>>>>>>>> + next unless($row); # Skip/allow empty lines
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Reject/remove everything besides wildcard domains and remarks
>>>>>>>>>>>>>> + if((not &General::validwildcarddomainname($row)) && (not $row =~ /^;[\w \-()\.:;*\/\\?!&=]*$/)) {
>>>>>>>>>>>>>> + unless($cleanup) {
>>>>>>>>>>>>>> + # +1 for user friendly line number and to ensure non-zero exitcode
>>>>>>>>>>>>>> + return $index + 1;
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Remove current row
>>>>>>>>>>>>>> + splice(@$listref, $index, 1);
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + return 0;
>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +###--- Internal misc functions ---###
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# Send HTTP 303 redirect headers for post/request/get pattern
>>>>>>>>>>>>>> +# (Must be sent before calling &Header::showhttpheaders())
>>>>>>>>>>>>>> +sub _http_prg_redirect {
>>>>>>>>>>>>>> + my $location = "https://$ENV{'SERVER_NAME'}:$ENV{'SERVER_PORT'}$ENV{'SCRIPT_NAME'}";
>>>>>>>>>>>>>> + print "Status: 303 See Other\n";
>>>>>>>>>>>>>> + print "Location: $location\n";
>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>> diff --git a/lfs/rpz b/lfs/rpz
>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>> index 000000000..7ddbc38e5
>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>> +++ b/lfs/rpz
>>>>>>>>>>>>>> @@ -0,0 +1,96 @@
>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>> +# Definitions
>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +include Config
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +SUMMARY = response policy zone - RPZ reputation system for unbound DNS
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +VER = 1.0.0
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +THISAPP = rpz-$(VER)
>>>>>>>>>>>>>> +DIR_APP = $(DIR_SRC)/$(THISAPP)
>>>>>>>>>>>>>> +TARGET = $(DIR_INFO)/$(THISAPP)
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +PROG = rpz
>>>>>>>>>>>>>> +PAK_VER = 18
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +DEPS =
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +SERVICES =
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>> +# Top-level Rules
>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +install : $(TARGET)
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +check :
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +download :
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +b2 :
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +dist:
>>>>>>>>>>>>>> + @$(PAK)
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>> +# Installation Details
>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +$(TARGET) :
>>>>>>>>>>>>>> + @$(PREBUILD)
>>>>>>>>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # RPZ scripts
>>>>>>>>>>>>>> + install --verbose --mode=755 \
>>>>>>>>>>>>>> + $(DIR_CONF)/rpz/{rpz-config,rpz-metrics,rpz-sleep,rpz-make,rpz-functions} \
>>>>>>>>>>>>>> + --target-directory=/usr/sbin
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # RPZ config files
>>>>>>>>>>>>>> + mkdir -pv /etc/unbound/local.d
>>>>>>>>>>>>>> + install --verbose --mode=644 --owner=nobody --group=nobody \
>>>>>>>>>>>>>> + $(DIR_CONF)/rpz/00-rpz.conf \
>>>>>>>>>>>>>> + --target-directory=/etc/unbound/local.d
>>>>>>>>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/local.d
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # RPZ custom list files for allow and block
>>>>>>>>>>>>>> + mkdir -pv /var/ipfire/dns/rpz
>>>>>>>>>>>>>> + touch /var/ipfire/dns/rpz/{allowlist,blocklist}
>>>>>>>>>>>>>> + chown --verbose --recursive nobody:nobody /var/ipfire/dns/rpz
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # RPZ zone files
>>>>>>>>>>>>>> + # create empty RPZ config file to avoid a unbound config error
>>>>>>>>>>>>>> + mkdir -pv /etc/unbound/zonefiles
>>>>>>>>>>>>>> + touch /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/zonefiles
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Install addon-specific language-files
>>>>>>>>>>>>>> + install --verbose --mode=004 $(DIR_CONF)/rpz/rpz.*.pl \
>>>>>>>>>>>>>> + --target-directory=/var/ipfire/addon-lang
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + # Install backup definition
>>>>>>>>>>>>>> + cp -vf $(DIR_CONF)/backup/includes/rpz /var/ipfire/backup/addons/includes/rpz
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>>>>>>>>> + @$(POSTBUILD)
>>>>>>>>>>>>>> diff --git a/make.sh b/make.sh
>>>>>>>>>>>>>> index 827ea9e77..a77535b13 100755
>>>>>>>>>>>>>> --- a/make.sh
>>>>>>>>>>>>>> +++ b/make.sh
>>>>>>>>>>>>>> @@ -390,7 +390,7 @@ prepareenv() {
>>>>>>>>>>>>>> if [ "${free_space}" -lt "${required_space}" ]; then
>>>>>>>>>>>>>> # Add any consumed space
>>>>>>>>>>>>>> while read -r consumed_space path; do
>>>>>>>>>>>>>> - (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>>>>>>>>> + (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>>>>>>>>> done <<< "$(du --summarize --bytes "${BUILD_DIR}" "${IMAGES_DIR}" "${LOG_DIR}" 2>/dev/null)"
>>>>>>>>>>>>>> fi
>>>>>>>>>>>>>> @@ -2087,6 +2087,7 @@ build_system() {
>>>>>>>>>>>>>> lfsmake2 btrfs-progs
>>>>>>>>>>>>>> lfsmake2 inotify-tools
>>>>>>>>>>>>>> lfsmake2 grub-btrfs
>>>>>>>>>>>>>> + lfsmake2 rpz
>>>>>>>>>>>>>> lfsmake2 linux
>>>>>>>>>>>>>> lfsmake2 rtl8812au
>>>>>>>>>>>>>> diff --git a/src/paks/rpz/install.sh b/src/paks/rpz/install.sh
>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>> index 000000000..ef99bf742
>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>> +++ b/src/paks/rpz/install.sh
>>>>>>>>>>>>>> @@ -0,0 +1,36 @@
>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>> +#
>>>>>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>>>>>> +extract_files
>>>>>>>>>>>>>> +restore_backup ${NAME}
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# fix user created files
>>>>>>>>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>>>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>>>>>>>>> + /etc/unbound/zonefiles \
>>>>>>>>>>>>>> + /etc/unbound/local.d
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# Update Language cache
>>>>>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# restart unbound to load config file
>>>>>>>>>>>>>> +/etc/init.d/unbound restart
>>>>>>>>>>>>>> diff --git a/src/paks/rpz/uninstall.sh b/src/paks/rpz/uninstall.sh
>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>> index 000000000..e11427df3
>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>> +++ b/src/paks/rpz/uninstall.sh
>>>>>>>>>>>>>> @@ -0,0 +1,38 @@
>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>> +#
>>>>>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>>>>>>>>> +/etc/init.d/unbound stop
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +make_backup ${NAME}
>>>>>>>>>>>>>> +remove_files
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>>>>>>>>> +# "[1723428668] unbound-control[17117:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>>>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# Update Language cache
>>>>>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# start unbound to load unbound config file
>>>>>>>>>>>>>> +/etc/init.d/unbound start
>>>>>>>>>>>>>> diff --git a/src/paks/rpz/update.sh b/src/paks/rpz/update.sh
>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>> index 000000000..9bc340bc6
>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>> +++ b/src/paks/rpz/update.sh
>>>>>>>>>>>>>> @@ -0,0 +1,52 @@
>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>> +#
>>>>>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# from update.sh
>>>>>>>>>>>>>> +extract_backup_includes
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>>>>>>>>> +/etc/init.d/unbound stop
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# from uninstall.sh
>>>>>>>>>>>>>> +make_backup ${NAME}
>>>>>>>>>>>>>> +remove_files
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>>>>>>>>> +# "unbound-control[nn:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>>>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# from install.sh
>>>>>>>>>>>>>> +extract_files
>>>>>>>>>>>>>> +restore_backup ${NAME}
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# fix user created files
>>>>>>>>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>>>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>>>>>>>>> + /etc/unbound/zonefiles \
>>>>>>>>>>>>>> + /etc/unbound/local.d
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# Update Language cache
>>>>>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>>>>>> +
>>>>>>>>>>>>>> +# restart unbound to load config files
>>>>>>>>>>>>>> +/etc/init.d/unbound start
>>>>>>>>>>>>>> --
>>>>>>>>>>>>>> 2.39.5
>>>>>>>>>>>>>>
>>>>>>>>>>>> Jon
>>>>>>>>>>>> --
>>>>>>>>>>>> Jon Murphy
>>>>>>>>>>>> jon.murphy@ipfire.org
>>>>>>>>>>
>>>>>>>>>> Jon
>>>>>>>>>> --
>>>>>>>>>> Jon Murphy
>>>>>>>>>> jon.murphy@ipfire.org
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re[2]: [PATCH] RPZ: update code to include WEBGUI and additional languages
2025-03-24 14:25 ` Michael Tremer
@ 2025-03-24 14:33 ` Jon Murphy
2025-03-24 14:36 ` Michael Tremer
2025-03-24 14:41 ` Bernhard Bitsch
1 sibling, 1 reply; 30+ messages in thread
From: Jon Murphy @ 2025-03-24 14:33 UTC (permalink / raw)
To: Michael Tremer, Bernhard Bitsch; +Cc: IPFire: Development-List
[-- Attachment #1: Type: text/plain, Size: 146505 bytes --]
And where are these stored?
In `/etc/unbound/zonefiles`:
[root@ipfire ~] # ls -al /etc/unbound/zonefiles
total 20664
drwxr-xr-x 2 nobody nobody 4096 Mar 24 04:40 .
drwxr-xr-x 4 root root 4096 Mar 19 16:24 ..
-rw-r--r-- 1 nobody nobody 3999087 Mar 23 15:11 adhocSB.rpz
-rw-r--r-- 1 nobody nobody 1411 Mar 23 14:23 allow.rpz
-rw-r--r-- 1 nobody nobody 25355 Mar 24 04:40 AmazonTrkrHZ.rpz
-rw-r--r-- 1 nobody nobody 7241 Mar 24 04:40 AppleTrkrHZ.rpz
-rw-r--r-- 1 nobody nobody 178 Mar 23 14:23 block.rpz
-rw-r--r-- 1 nobody nobody 78496 Mar 24 04:40 DOHblockHZ.rpz
-rw-r--r-- 1 nobody nobody 16983551 Mar 24 04:40 MxProPlusHZ.rpz
-rw-r--r-- 1 nobody nobody 2893 Mar 24 04:40 tldHZ.rpz
-rw-r--r-- 1 nobody nobody 29419 Mar 24 04:40 WinTrkrHZ.rpz
[root@ipfire ~] #
------ Original Message ------
From "Michael Tremer" <michael.tremer@ipfire.org>
To "Bernhard Bitsch" <bbitsch@ipfire.org>
Cc development@lists.ipfire.org
Date 3/24/2025 9:25:40 AM
Subject Re: [PATCH] RPZ: update code to include WEBGUI and additional
languages
>Hello,
>
>> On 24 Mar 2025, at 13:33, Bernhard Bitsch <bbitsch@ipfire.org> wrote:
>>
>>
>>
>> Am 24.03.2025 um 11:17 schrieb Michael Tremer:
>>> Hello Jon,
>>>> On 24 Mar 2025, at 00:00, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>
>>>> Michael,
>>>>
>>>> FYI - I was wrong Unbound RPZ is _not_ watching the serial number, it is watching the "refresh", the number after the serial number.
>>> Refresh just tells the client how often to check for an update.
>>> If that is actually being set by the list publisher, then we have another problem here, because they could put some insanely low value there and we would then DDoS their infrastructure. I think we should keep it like we have it in other places that we control how often we want to check or pull for updates.
>>>
>>
>> You are right. But an extra update process wastes additional processor time. The update mechanism of unbound does the check for update ( however it is realized ) nevertheless.
>
>Yes, doing more things needs resources. But we are not seriously considering whether an IPFire system has enough resources to perform the download of a text file, or are we?
>
>>>> I understand that you don’t speak C, but you got the information from somewhere. Documentation maybe? Since that is out of date very often I like to consult the code.
>>>> From testing. Downloading rpz files using rpz unbound, and watching what happens. If the rpz file is setup for "once per day" refresh, then it only downloads one time.
>>>>
>>>>
>>>>
>>>> However that won’t solve our problem . . . and having no cache.
>>>> In `/etc/unbound/tuning.conf` there is `rrset-cache-size: 128m`. Are you referring to a different cache.
>>> Naturally unbound is loading the zone into its memory which we generally call cache.
>>> When I say cache I am thinking about persistent data storage across multiple restarts of Unbound. If I am downloading 100 MiB of RPZ lists (which is presumably still on the lower end) and I reboot my firewall, I do not want to download the same data again. We can only ever download a list *once* unless we are 100% certain that it has changed. Then we can download it once again.
>>
>> The RPZ lists are stored in files in persistent storage. Unbound creates the internal cache from these.
>
>And where are these stored?
>
>>>> Maybe we need to implement both?
>>>>
>>>> Yes. There are very few AXFR list (I think only four were found). And many more HTTPS rpz files.
>>>>
>>>>
>>>>
>>>> Jon
>>>>
>>>>
>>>> ------ Original Message ------
>>>> From "Michael Tremer" <michael.tremer@ipfire.org>
>>>> To "Jon Murphy" <jon.murphy@ipfire.org>
>>>> Cc "IPFire: Development-List" <development@lists.ipfire.org>
>>>> Date 3/20/2025 11:26:43 AM
>>>> Subject Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
>>>>
>>>>> Hello Jon,
>>>>> Please don’t forget to Cc the list...
>>>>>
>>>>>>
>>>>>> On 19 Mar 2025, at 18:27, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>> Michael,
>>>>>>
>>>>>>>
>>>>>>> Where in the code is this implemented? I cannot find anything like this:
>>>>>>
>>>>>> Keep in mind I am not a "C" person. Maybe in this section?:
>>>>>> https://git.ipfire.org/?p=thirdparty/unbound.git;a=blob;f=services/authzone.c;hb=30b9cb5f813003d0a2b1c2e678652396615b1b7d#l5875
>>>>>
>>>>> This where the AXFR response is being handled when doing a DNS zone transfer. This code is not being called when performing a HTTP download.
>>>>> I understand that you don’t speak C, but you got the information from somewhere. Documentation maybe? Since that is out of date very often I like to consult the code.
>>>>>
>>>>>>
>>>>>> —
>>>>>> When I was just learning about RPZ I created a separate RPZ file for testing. When I changed the SOA line with a new serial number, the RPZ file download would happen in about 5 minutes.
>>>>>> https://people.ipfire.org/~jon/sblack-adhoc.rpz
>>>>>
>>>>> It might well be that the file is not being reloaded if the download matches the content that unbound already has. That would of course save some resources.
>>>>> However that won’t solve our problem with redundant downloads and having no cache.
>>>>>
>>>>>>
>>>>>> That is how I found out the SOA line is watched for a serial number change.
>>>>>> I’ll reconfirm my findings.
>>>>>>
>>>>>>>
>>>>>>>>> The second reason is that we have a lot of firewalls out there. Not all of them will enable this feature and all of the lists, but even if it is a good chunk, we will generate terabytes of traffic which put load on the infrastructure and will cost money. It simply is not what we want to do, regardless of self-hosting those lists and pulling them from somewhere else.
>>>>>>
>>>>>> So I understand, are you thinking of hosting RPZ AXFR (DNS zone transfer) on IPFire infrastructure?
>>>>>
>>>>> No, I don’t think that we can generally do this. The biggest problem is licensing as we cannot take anyones content and host it ourselves. We would re-distribute those lists and that will only work with permission of the publishers. I assume that would be too much work to actually get some useful content out there. We might limit ourselves to only those lists that are under a very permissive license. Nobody wants that.
>>>>> From a technical point of view, DNS over TCP might not be very nice in terms of forging the transfer and so we would need TLS as well… It should work, but even if we would be able to encourage other people to publish their lists I doubt they would implement DNS over TLS for authoritative DNS. That standard is in very early stages as well.
>>>>> As far as I can see, those vendors who offer a list as a commercial product are using DNS to distribute it (e.g. Spamhaus). Those people who have made this all a hobby are throwing the lists onto GitHub and let them handle the traffic.
>>>>> Maybe we need to implement both?
>>>>> -Michael
>>>>>
>>>>>>
>>>>>> Jon
>>>>>> On 3/19/25 5:35 AM, Michael Tremer wrote:
>>>>>>>
>>>>>>>
>>>>>>> Hello Jon,
>>>>>>> Where in the code is this implemented? I cannot find anything like this:
>>>>>>> Unbound loads the entire file into memory and then starts parsing it. The only special treatment there is is to check whether the first line is a valid zone entry. It does not even have to be a SOA record.
>>>>>>> https://git.ipfire.org/?p=thirdparty/unbound.git;a=blob;f=services/authzone.c;hb=30b9cb5f813003d0a2b1c2e678652396615b1b7d#l1188
>>>>>>> I am also concerned that Unbound will not be able to support an upstream proxy for any downloads. The caching situation is also unclear for me, so I believe that we will be looking at writing a custom downloader that implements all these things.
>>>>>>> -Michael
>>>>>>>
>>>>>>>>
>>>>>>>> On 19 Mar 2025, at 02:58, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>> Michael,
>>>>>>>>
>>>>>>>>>
>>>>>>>>> The emphasis is on the repeated downloads of the same list. That is
>>>>>>>>
>>>>>>>> > what cannot happen.
>>>>>>>> The Unbound RPZ code, as installed within IPFire, watches for a change
>>>>>>>> in the SOA line of each RPZ file. This is an example of the first few
>>>>>>>> lines for every RPZ file.
>>>>>>>> $TTL 300
>>>>>>>> @ SOA localhost. root.localhost. 1742298960 43200 3600 86400 300
>>>>>>>> NS localhost.
>>>>>>>> ;
>>>>>>>> ; Title: HaGeZi's Pop-Up Ads DNS Blocklist
>>>>>>>> ; Description: Blocks annoying and malicious pop-up ads.
>>>>>>>> If the SOA serial number changes (e.g. the 1742298960), then Unbound RPZ
>>>>>>>> code does its thing and downloads. Otherwise there is no download.
>>>>>>>>
>>>>>>>>>
>>>>>>>>> So there has to be a way to ensure that we won’t download a list again
>>>>>>>>
>>>>>>>> > unless it has actually changed.
>>>>>>>> This should do what you want but I may be missing your point.
>>>>>>>>
>>>>>>>>>
>>>>>>>>> DNS has a builtin functionality called AXFR. It simply does the job
>>>>>>>>
>>>>>>>> > for you. I was just wondering whether that was not being used.
>>>>>>>> I need to read about AXFR/IXFR and learn a little more.
>>>>>>>> Jon
>>>>>>>> On 3/17/25 5:35 AM, Michael Tremer wrote:
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> Good Morning Jon,
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> On 16 Mar 2025, at 17:00, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>>>> Michael,
>>>>>>>>>> I was reading through you response again an I want to understand this post:
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>>>>>>>>>
>>>>>>>>>> So if RPZ doesn't use HTTPS, what is it using? I am missing a key point here.
>>>>>>>>>
>>>>>>>>> The emphasis is on the repeated downloads of the same list. That is what cannot happen.
>>>>>>>>> Although it might not affect a lot of people in our general user-base, there are some that have a metered connection and will pay for data by volume. Some of the lists I looked at are just under 20 MiB. Therefore we need to keep any traffic down to a minimum. The second reason is that we have a lot of firewalls out there. Not all of them will enable this feature and all of the lists, but even if it is a good chunk, we will generate terabytes of traffic which put load on the infrastructure and will cost money. It simply is not what we want to do, regardless of self-hosting those lists and pulling them from somewhere else.
>>>>>>>>> So there has to be a way to ensure that we won’t download a list again unless it has actually changed.
>>>>>>>>> DNS has a builtin functionality called AXFR. It simply does the job for you. I was just wondering whether that was not being used.
>>>>>>>>> HTTPS is an option because that is simply what we use elsewhere, but extra functionality will have to be built for it.
>>>>>>>>> -Michael
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> Jon
>>>>>>>>>> On 2/13/25 3:34 PM, jon wrote:
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> Michael,
>>>>>>>>>>> I’ve read through your comments a few times and I ended up with many more questions.
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>> What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>>>>>>>>>
>>>>>>>>>>> To me the efforts to get new code accepted seem to have changed and it seemed easier in the past. In the past I made the Core Team aware via the Dev Mailing List and wrote a simple two or three paragraphs of "What is it? / What is the value? / Here is the code"
>>>>>>>>>>> So in an effort to move forward: How exactly is something presented to the Core Team?
>>>>>>>>>>> Is there an example of a recent effort that was presented that I can see as a sample? (This type of info can also be added to the Wiki)
>>>>>>>>>>> I understand you want it this way, but I don’t know what exactly is needed. Please be specific.
>>>>>>>>>>> Jon
>>>>>>>>>>> PS - I am not ignoring your other comments, I am just trying to move forward and keep things simple.
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>> On Feb 8, 2025, at 1:27 PM, Michael Tremer <michael.tremer@ipfire.org> wrote:
>>>>>>>>>>>> Hello Jon,
>>>>>>>>>>>> Thanks for your reply. And good that you are copying everyone into this conversation.
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>> On 8 Feb 2025, at 18:41, jon <jon.murphy@ipfire.org> wrote:
>>>>>>>>>>>>> Michael,
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort
>>>>>>>>>>>>>
>>>>>>>>>>>>> Yes, this has been a separate effort (a very public separate effort). Yes, as you pointed this out early on with the "proof-of-concept" and then my request for people to help test RPZ. Nothing was hidden.
>>>>>>>>>>>>> This was done because you (and maybe others) did not have the time and I wanted to help and because I needed assistance with RPZ. I tried my best to do this without bothering you.
>>>>>>>>>>>>
>>>>>>>>>>>> I don’t that it is accurate that nobody wanted to help on this. The list was always open - although not every email has been replied to swiftly it is also your responsibility to raise a question again if it was missed. People here have open ears.
>>>>>>>>>>>> It was also stated on this very list on in our documentation that working on something without involving the core team is a risky undertaking. Of course IPFire is free software and so everyone is free to fork if they wish to do so.
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>>> and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls.
>>>>>>>>>>>>>
>>>>>>>>>>>>> You were aware many steps along the way. See your email on July 28, 2024, August 15, 2024, September 30, 2024, December 23, 2024, and January 16. My attempts to get the team involved were met with "things are busy" and sometimes silence. (Yes, I get it, people are busy.)
>>>>>>>>>>>>> You and Adolf, Leo, Erik and Bernhard have been aware since the beginning. You mention you were aware of the "proof-of-concept". If you include those beginning posts, since Sep 2023.
>>>>>>>>>>>>
>>>>>>>>>>>> Yes, I am aware of a proof-of-concept that I have been running myself for a long time. I am also aware of the efforts that you have been taking.
>>>>>>>>>>>> Yet I don’t think there has ever been any joint effort, or am I seeing that wrong?
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>>> This has not been discussed . . . on our calls.
>>>>>>>>>>>>>
>>>>>>>>>>>>> On the July 28th you stated:
>>>>>>>>>>>>> "We have talked about RPZ many times on the monthly call since the URL filter feature is falling more and more out of fashion. I think there is also many posts about this on the forum."
>>>>>>>>>>>>> Please don’t insult me again by stating "you know what I mean".
>>>>>>>>>>>>> And it has been discussed but not documented in the Monthly Meeting notes.
>>>>>>>>>>>>
>>>>>>>>>>>> I am not at all insulting you. I don’t want to take this down to a personal level at all. This is a public mailing list and people who read this don’t need to listen to an argument we are having. They are here for the tech inside IPFire.
>>>>>>>>>>>> When I wrote that it has not been discussed that does not mean that we have not been touching on the topic. We have been talking about lots of things on the calls, the weather, politics, how our pets are. None of that makes it to the logs. What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>>> Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation.
>>>>>>>>>>>>>
>>>>>>>>>>>>> Regular conversation on the Dev Mailing list is many times met with silence. I get it, people are busy.
>>>>>>>>>>>>> And regular two-way conversation doesn’t happen on the list. At least not with me. I’d be happy to point out the posts that were met with silence.
>>>>>>>>>>>>> Again, I get it, people are busy.
>>>>>>>>>>>>
>>>>>>>>>>>> And you think my emails are not being met with silence? This has nothing to do with this specific topic. This has something to do with how occupied people are and how engaged they are on certain topics. Not everyone is involved in all the things and simply will ignore emails simply based on their subject line.
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>> But the "dip here to the list" were my attempts to get a conversation started. As I said, many time met with silence.
>>>>>>>>>>>>> The only place I was not met with silence was on the Community. You have a great group of people in the Community. It is a shame you don’t want to have others help. It would reduce your workload.
>>>>>>>>>>>>
>>>>>>>>>>>> You should stop making statements that are not true. Who doesn’t want anyone to help?
>>>>>>>>>>>> Not having this conversation on a Saturday evening would reduce my workload. At least it would free up time for something else. Helping with the things that are already on the go would reduce the workload of the entire team. Starting one thing at a time and finishing it is a lot better to manage than starting a hundred things and not even finish one. I can tell you that I already have a hundred things on the go.
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>>> Therefore, what am I supposed to do with this email?
>>>>>>>>>>>>>
>>>>>>>>>>>>> To me it is beyond obvious…
>>>>>>>>>>>>> If it isn’t what you want, then guide me with how to do this the correct way. And be specific. I am trying to help. I am trying to make things better. I am trying to do things the right way.
>>>>>>>>>>>>
>>>>>>>>>>>> To me it isn’t. This is yet another project that has been dumped to the list like so many before and later on everyone has left to have the team deal with the rest.
>>>>>>>>>>>> It is a huge patch set. You explained what the vision is, but that is about it. There is no chance this will continue if this disagreement isn’t solved first. I didn’t even look at the code.
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>>> I don’t want to merge code that I don’t agree with.
>>>>>>>>>>>>>
>>>>>>>>>>>>> I asked multiple times if you "agreed with the concept" and again, met with silence. Yes I get it, people are busy.
>>>>>>>>>>>>
>>>>>>>>>>>> Having support for RPZ? Yes, it was definitely on the roadmap. That I agree with.
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>>> So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>>>>>>>>>
>>>>>>>>>>>>> You mentioned this a in the past, but for some reason you do not disclose what I dismissed. Why do you continue to make this harder, wouldn’t it not be easier to tell me what I have dismissed?
>>>>>>>>>>>>> I have sent multiple emails trying to answer your concerns and comments. On July 28, Aug 14, Aug 22, Aug 23, Sep 30, etc.
>>>>>>>>>>>>> I’ve gone through all of the questions you asked and I cannot find a "dismissed" item.
>>>>>>>>>>>>
>>>>>>>>>>>> Maybe I need to be *more clear*. I feel humoured by this.
>>>>>>>>>>>> It is late on a Saturday and I want my dinner soon, but certainly I have stated that this should never be an add-on considering it is supposed to replace URL Filter. We should never allow people to add their own sources. I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>>>>>>>>>
>>>>>>>>>>>>> The maintainers of Unbound and/or RPZ?
>>>>>>>>>>>>> The maintainers of Hagezi list, the threatfox list, the urlhaus list, etc.?
>>>>>>>>>>>>> What else? The maintainers or the RPZ scripts? That is me. Let’s talk!
>>>>>>>>>>>>
>>>>>>>>>>>> You. I don’t care much about the providers of the lists.
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>> See, this is where it gets confusing. There are hundreds of open source packages as part of IPFire. Pick the last five years of items added to the IPFire build. You're telling me you have "constructive conversation with the maintainers" of all of the added packages?
>>>>>>>>>>>>
>>>>>>>>>>>> They publish their software and they don’t care whether I am pulling it or not. They publish it with the commitment to maintain it - sometimes for better and sometimes for worse.
>>>>>>>>>>>> You care about me pulling your code and I don’t know whether you would commit to maintain this.
>>>>>>>>>>>> These two are very different cases.
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>> Pick the IP Blocklists list (i.e., 3CORESEC, ABUSECH, DSHIELD, SPAMHAUS, etc.) or the Suricata lists (i.e.,Emergingthreats.net <http://emergingthreats.net/>,Abuse.ch <http://abuse.ch/>, etc.). So you’ve have "constructive conversation with the maintainers"?
>>>>>>>>>>>>
>>>>>>>>>>>> Yes, occasionally I have phone calls with a few of these providers.
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>>>>>>>>>
>>>>>>>>>>>>> Ha! Yes a surprise. In the beginning you seemed interested as IPFire needed a replacement for URL Filter. You asked good questions about the lists picked, asked for the value to the users, etc. And I answered the best I could.
>>>>>>>>>>>>> You even asked: “Why is this realised as an add-on and not part of the core system?” from your Jul 28, 2024 email.
>>>>>>>>>>>>
>>>>>>>>>>>> Ah, so, why is the patch creating an add-on? Not that I am saying that what I say is law, but it has not been challenged either. If my input is being ignored, why should I put this to the top of my list of priorities? I am not disappointed about this, just trying to be very good with my time.
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>> And on January 16, 2025 I wrote a message looking for help. And you were kind to respond quickly. So in three weeks time, since the kind response, something has changed. You went from supportive to "this".
>>>>>>>>>>>>> So yes, I am surprised.
>>>>>>>>>>>>
>>>>>>>>>>>> Well, maybe I should not have replied to that email. It was clear that you were on some path that was not right, but you were not interested before in finding the right path from the beginning.
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>>>>>>>>>
>>>>>>>>>>>>> Be more specific, what has to change? What exactly did I dismiss?
>>>>>>>>>>>>
>>>>>>>>>>>> Dismissal is just my assumption. I don’t know what you actually did with my feedback. I can only see the end product that does not seem contain much of it. Repeatedly I have been pointing out that we should think before we build. I am sure a lot of hours have now gone into some code that simply does not satisfy me. And I am not not talking about the code itself, what it does is what I don’t think is right for us.
>>>>>>>>>>>> The process is very clear for me that we should first of all think whether we want a certain feature now. Then there should be a clear roadmap for everyone to follow; tasks can be split-up as we go and hopefully then have something that is maintainable, interesting for our users and even would do us proud. This is how this should work.
>>>>>>>>>>>> So, what has to change? I don’t think with shouting at each other, throwing patches around and making me generally unhappy is a good start.
>>>>>>>>>>>> -Michael
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>> Jon
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> On Feb 6, 2025, at 2:13 PM, Michael Tremer <michael.tremer@ipfire.org> wrote:
>>>>>>>>>>>>>> Hello Jon,
>>>>>>>>>>>>>> Well, here we are again with another patch regarding this feature.
>>>>>>>>>>>>>> I cannot quite see from your email what the question is, but if this is a request to have this merged into IPFire, I am once again sorry to disappoint you.
>>>>>>>>>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls. Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation. Therefore, what am I supposed to do with this email?
>>>>>>>>>>>>>> I don’t want to merge code that I don’t agree with. So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>>>>>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>>>>>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>>>>>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>>>>>>>>>> All the best,
>>>>>>>>>>>>>> -Michael
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> On 6 Feb 2025, at 16:35, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>>>>>>>>> What is it?
>>>>>>>>>>>>>>> Response Policy Zone (RPZ) is a mechanism to define local policies in a
>>>>>>>>>>>>>>> standardized way and load those policies from external sources.
>>>>>>>>>>>>>>> Bottom line: RPZ allows admins to easily block access to websites via DNS lookup.
>>>>>>>>>>>>>>> RPZ can block websites via categories. Examples include: fake websites, annoying
>>>>>>>>>>>>>>> pop-up ads, newly registered domains, DoH bypass sites, bad "host" services,
>>>>>>>>>>>>>>> maliscious top level domains (e.g., *.zip, *.mov), piracy, gambling, pornography,
>>>>>>>>>>>>>>> and more. RPZ lists come from various RPZ providers and their available
>>>>>>>>>>>>>>> catagories.
>>>>>>>>>>>>>>> This RPZ add-on enables the RPZ functionality by adding a couple lines in a
>>>>>>>>>>>>>>> configuration file. This add-on simply adds configuration files and adds
>>>>>>>>>>>>>>> scripts (config, metrics and sleep) to make RPZ easier for the admin to use.
>>>>>>>>>>>>>>> The RPZ scripts include additional languages: German, Spanish, French, Turkish,
>>>>>>>>>>>>>>> and Italian.
>>>>>>>>>>>>>>> RPZ itself was release in 2010 and has been part of the IPFire build since ~2015.
>>>>>>>>>>>>>>> Why is it needed? What is its value?
>>>>>>>>>>>>>>> - The RPZ concept places this filtering into IPFire, our internet access
>>>>>>>>>>>>>>> gateway, which is (should be) solely used as DNS source of the internal network.
>>>>>>>>>>>>>>> - As most sites use HTTPS it makes it difficult to filter traffic with URL
>>>>>>>>>>>>>>> Filter without also properly configuring conventional (non-transparent)
>>>>>>>>>>>>>>> mode on the proxy. RPZ is a nice replacement for the URL Filter.
>>>>>>>>>>>>>>> - No need to install and maintain an additional device like PiHole or AdBlock
>>>>>>>>>>>>>>> browser extensions on multiple user devices.
>>>>>>>>>>>>>>> - This is an additional layer of protection for users. Less worry someone will
>>>>>>>>>>>>>>> click on something that gets them into trouble. And, saying this with emphasis,
>>>>>>>>>>>>>>> the ability to do it in one place!
>>>>>>>>>>>>>>> - Blocked sites save on unneeded traffic and can lessen the threat of malware
>>>>>>>>>>>>>>> in advertisements
>>>>>>>>>>>>>>> - Logging allows the admin to see the site blocked and take actions
>>>>>>>>>>>>>>> - RPZ will be used at the home, home-office (work from home), schools,
>>>>>>>>>>>>>>> ministerial, and at the office. Device counts are small (2-6) to medium (~80)
>>>>>>>>>>>>>>> to mediam-large (200+).
>>>>>>>>>>>>>>> - RPZ can block ads, popups, phishing, scammers, spyware, malware, annoying
>>>>>>>>>>>>>>> popups, NSFW links, DOH servers, and the usual internet trash.
>>>>>>>>>>>>>>> ------------------------------
>>>>>>>>>>>>>>> Change Log for RPZ add-on
>>>>>>>>>>>>>>> rpz-1.0.0-18 on 2025-02-05
>>>>>>>>>>>>>>> - Build for approval & release as IPFire add-on
>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>> rpz-beta-0.1.18-18.ipfire on 2025-02-01
>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>> - new feature: added a mod key to force a unbound restart
>>>>>>>>>>>>>>> rpz-config and rpz-make:
>>>>>>>>>>>>>>> - new feature: added action for unbound restart `rpz-config unbound-restart`
>>>>>>>>>>>>>>> rpz-metrics:
>>>>>>>>>>>>>>> - simple reformatting
>>>>>>>>>>>>>>> - rename far right column from "last update" to "last download"
>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>> rpz-beta-0.1.17-17.ipfire on 2024-12-09
>>>>>>>>>>>>>>> rpz-make
>>>>>>>>>>>>>>> - bug fix: corrected validation regex for wildcards like: `*.domain.com`
>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>> rpz-beta-0.1.16-16.ipfire on 2024-11-18
>>>>>>>>>>>>>>> rpz-make
>>>>>>>>>>>>>>> - new feature: updated validation regex
>>>>>>>>>>>>>>> - bug fix: moved validation to beginning of process. Now we validate before
>>>>>>>>>>>>>>> creating config files.
>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>> - new feature: use CSS color variables of the main ipfire theme
>>>>>>>>>>>>>>> - bug fix: empty zonefile remarks were stored as “undef” and caused a warning
>>>>>>>>>>>>>>> - bug fix: HTML textarea removes the first empty line in a custom list
>>>>>>>>>>>>>>> - thank you Leo!
>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>> rpz-beta-0.1.15-15.ipfire on 2024-11-04
>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>> - new feature: added new language file for Turkish (thank you Peppe)
>>>>>>>>>>>>>>> rpz-make
>>>>>>>>>>>>>>> - bug fix: corrected empty allow/block list issue. An empty allow/block list
>>>>>>>>>>>>>>> will now remove contents of allow/block.rpz files and remove unneeded
>>>>>>>>>>>>>>> allow/block.conf file. (thank you iptom)
>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>> rpz-beta-0.1.14-14.ipfire on 2024-10-29
>>>>>>>>>>>>>>> rpz-config:
>>>>>>>>>>>>>>> - bug fix: correct missing rpz extension. `rpz-config list` displayed URL
>>>>>>>>>>>>>>> incorrectly (thank you Bernhard)
>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>> - bug fix: remove extra `"` in language files (thank you Bernhard)
>>>>>>>>>>>>>>> - new feature: slightly dim "apply" button when not enabled
>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>> rpz-beta-0.1.13-13.ipfire on 2024-10-27
>>>>>>>>>>>>>>> - skipped
>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>> rpz-beta-0.1.12-12.ipfire on 2024-10-21
>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>> - new feature: added new language file for French (thank you gw-ipfire)
>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>> rpz-beta-0.1.11-11.ipfire on 2024-10-18
>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>> - new feature: added new language file for Italian (thank you umberto)
>>>>>>>>>>>>>>> - new feature: added new language file for Spanish (thank you Roberto)
>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>> rpz-beta-0.1.10-10.ipfire on 2024-10-15
>>>>>>>>>>>>>>> rpz-make:
>>>>>>>>>>>>>>> - bug fix: corrected validation error for a custom list entry (thank you siosios)
>>>>>>>>>>>>>>> - e.g., `*.cloudflare-dns.com`
>>>>>>>>>>>>>>> install.sh:
>>>>>>>>>>>>>>> - bug fix: add chown to correct user created files
>>>>>>>>>>>>>>> update.sh:
>>>>>>>>>>>>>>> - bug fix: add chown to correct user created files (thank you siosios)
>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>> rpz-beta-0.1.9-9.ipfire on 2024-10-08
>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>> - new feature: added new language file for German (thank you Leo)
>>>>>>>>>>>>>>> - bug fix: add missing "rpz exitcode 110"
>>>>>>>>>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>> rpz-beta-0.1.8-8.ipfire on 2024-10-04
>>>>>>>>>>>>>>> - skipped
>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>> rpz-beta-0.1.7-7.ipfire on 2024-10-03
>>>>>>>>>>>>>>> All:
>>>>>>>>>>>>>>> - new feature: includes beta version numbers for pakfire package,
>>>>>>>>>>>>>>> instead of only `rpz-1.0.0-1.ipfire`, for each release.
>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>> - new feature: added new WebGUI at `rpz.cgi`
>>>>>>>>>>>>>>> - a BIG thank you to Leo Hofmann for all of his work creating the webgui!!
>>>>>>>>>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>>>>>>>>> rpz-make:
>>>>>>>>>>>>>>> - new feature: validate entries in allowlist and blocklist
>>>>>>>>>>>>>>> - new feature: add "no-reload" option for WebGUI
>>>>>>>>>>>>>>> rpz-metrics:
>>>>>>>>>>>>>>> - new feature: info can be sorted by name, by hit count, by line count, by
>>>>>>>>>>>>>>> "enabled" list or all lists
>>>>>>>>>>>>>>> backups:
>>>>>>>>>>>>>>> - bug fix: include all files in `/var/ipfire/dns/rpz` directory in backup
>>>>>>>>>>>>>>> update.sh:
>>>>>>>>>>>>>>> - bug fix: corrected ownership for `/var/ipfire/dns/rpz` directory during an
>>>>>>>>>>>>>>> update
>>>>>>>>>>>>>>> Build:
>>>>>>>>>>>>>>> - bug fix: `block.rpz.conf` and `block.rpz` from build. Files to be created
>>>>>>>>>>>>>>> by `rpz-make`
>>>>>>>>>>>>>>> WebGUI and German language file
>>>>>>>>>>>>>>> Contribution-by: Leo-Andres Hofmann <hofmann@leo-andres.de>
>>>>>>>>>>>>>>> Spanish language file
>>>>>>>>>>>>>>> Contribution-by: Roberto Peña
>>>>>>>>>>>>>>> Italian language file
>>>>>>>>>>>>>>> Contribution-by: Umberto Parma
>>>>>>>>>>>>>>> French language file
>>>>>>>>>>>>>>> Contribution-by: gw-ipfire
>>>>>>>>>>>>>>> Turkish language file
>>>>>>>>>>>>>>> Contribution-by: Peppe Tech
>>>>>>>>>>>>>>> Contribution-by: Bernhard Bitsch <bbitsch@ipfire.org>
>>>>>>>>>>>>>>> Contribution-by: Erik Kapfer <erik.kapfer@ipfire.org>
>>>>>>>>>>>>>>> Signed-off-by: Jon Murphy <jon.murphy@ipfire.org
>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>> config/backup/includes/rpz | 4 +
>>>>>>>>>>>>>>> config/cfgroot/manualpages | 1 +
>>>>>>>>>>>>>>> config/menu/EX-rpz.menu | 6 +
>>>>>>>>>>>>>>> config/rootfiles/common/configroot | 1 +
>>>>>>>>>>>>>>> config/rootfiles/common/web-user-interface | 1 +
>>>>>>>>>>>>>>> config/rootfiles/packages/rpz | 20 +
>>>>>>>>>>>>>>> config/rpz/00-rpz.conf | 10 +
>>>>>>>>>>>>>>> config/rpz/rpz-config | 130 +++
>>>>>>>>>>>>>>> config/rpz/rpz-functions | 85 ++
>>>>>>>>>>>>>>> config/rpz/rpz-make | 203 +++++
>>>>>>>>>>>>>>> config/rpz/rpz-metrics | 170 ++++
>>>>>>>>>>>>>>> config/rpz/rpz-sleep | 58 ++
>>>>>>>>>>>>>>> config/rpz/rpz.de.pl | 30 +
>>>>>>>>>>>>>>> config/rpz/rpz.en.pl | 30 +
>>>>>>>>>>>>>>> config/rpz/rpz.es.pl | 30 +
>>>>>>>>>>>>>>> config/rpz/rpz.fr.pl | 30 +
>>>>>>>>>>>>>>> config/rpz/rpz.it.pl | 30 +
>>>>>>>>>>>>>>> config/rpz/rpz.tr.pl | 30 +
>>>>>>>>>>>>>>> html/cgi-bin/rpz.cgi | 923 +++++++++++++++++++++
>>>>>>>>>>>>>>> lfs/rpz | 96 +++
>>>>>>>>>>>>>>> make.sh | 3 +-
>>>>>>>>>>>>>>> src/paks/rpz/install.sh | 36 +
>>>>>>>>>>>>>>> src/paks/rpz/uninstall.sh | 38 +
>>>>>>>>>>>>>>> src/paks/rpz/update.sh | 52 ++
>>>>>>>>>>>>>>> 24 files changed, 2016 insertions(+), 1 deletion(-)
>>>>>>>>>>>>>>> create mode 100644 config/backup/includes/rpz
>>>>>>>>>>>>>>> create mode 100644 config/menu/EX-rpz.menu
>>>>>>>>>>>>>>> create mode 100644 config/rootfiles/packages/rpz
>>>>>>>>>>>>>>> create mode 100644 config/rpz/00-rpz.conf
>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz-config
>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz-functions
>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz-make
>>>>>>>>>>>>>>> create mode 100755 config/rpz/rpz-metrics
>>>>>>>>>>>>>>> create mode 100755 config/rpz/rpz-sleep
>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.de.pl
>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.en.pl
>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.es.pl
>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.fr.pl
>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.it.pl
>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.tr.pl
>>>>>>>>>>>>>>> create mode 100644 html/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>> create mode 100644 lfs/rpz
>>>>>>>>>>>>>>> create mode 100644 src/paks/rpz/install.sh
>>>>>>>>>>>>>>> create mode 100644 src/paks/rpz/uninstall.sh
>>>>>>>>>>>>>>> create mode 100644 src/paks/rpz/update.sh
>>>>>>>>>>>>>>> diff --git a/config/backup/includes/rpz b/config/backup/includes/rpz
>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>> index 000000000..36513e494
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/config/backup/includes/rpz
>>>>>>>>>>>>>>> @@ -0,0 +1,4 @@
>>>>>>>>>>>>>>> +/var/ipfire/dns/rpz/*
>>>>>>>>>>>>>>> +/etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>> +/etc/unbound/zonefiles/block.rpz
>>>>>>>>>>>>>>> +/etc/unbound/local.d/*rpz.conf
>>>>>>>>>>>>>>> diff --git a/config/cfgroot/manualpages b/config/cfgroot/manualpages
>>>>>>>>>>>>>>> index 1f7e01efc..d3a48c633 100644
>>>>>>>>>>>>>>> --- a/config/cfgroot/manualpages
>>>>>>>>>>>>>>> +++ b/config/cfgroot/manualpages
>>>>>>>>>>>>>>> @@ -70,6 +70,7 @@ pakfire.cgi=configuration/ipfire/pakfire
>>>>>>>>>>>>>>> wlanap.cgi=addons/wireless
>>>>>>>>>>>>>>> tor.cgi=addons/tor
>>>>>>>>>>>>>>> samba.cgi=addons/samba
>>>>>>>>>>>>>>> +rpz.cgi=addons/rpz
>>>>>>>>>>>>>>> # Logs menu
>>>>>>>>>>>>>>> logs.cgi/summary.dat=configuration/logs/summary
>>>>>>>>>>>>>>> diff --git a/config/menu/EX-rpz.menu b/config/menu/EX-rpz.menu
>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>> index 000000000..2f4daf410
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/config/menu/EX-rpz.menu
>>>>>>>>>>>>>>> @@ -0,0 +1,6 @@
>>>>>>>>>>>>>>> +$subipfire->{'20.rpz'} = {
>>>>>>>>>>>>>>> + 'caption' => $Lang::tr{'rpz'},
>>>>>>>>>>>>>>> + 'uri' => '/cgi-bin/rpz.cgi',
>>>>>>>>>>>>>>> + 'title' => "RPZ",
>>>>>>>>>>>>>>> + 'enabled' => 1,
>>>>>>>>>>>>>>> +};
>>>>>>>>>>>>>>> diff --git a/config/rootfiles/common/configroot b/config/rootfiles/common/configroot
>>>>>>>>>>>>>>> index 9839eee45..b30d6aae4 100644
>>>>>>>>>>>>>>> --- a/config/rootfiles/common/configroot
>>>>>>>>>>>>>>> +++ b/config/rootfiles/common/configroot
>>>>>>>>>>>>>>> @@ -120,6 +120,7 @@ var/ipfire/menu.d/70-log.menu
>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-apcupsd.menu
>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-guardian.menu
>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-mympd.menu
>>>>>>>>>>>>>>> +#var/ipfire/menu.d/EX-rpz.menu
>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-samba.menu
>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-tor.menu
>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-transmission.menu
>>>>>>>>>>>>>>> diff --git a/config/rootfiles/common/web-user-interface b/config/rootfiles/common/web-user-interface
>>>>>>>>>>>>>>> index 816241dae..e00464076 100644
>>>>>>>>>>>>>>> --- a/config/rootfiles/common/web-user-interface
>>>>>>>>>>>>>>> +++ b/config/rootfiles/common/web-user-interface
>>>>>>>>>>>>>>> @@ -69,6 +69,7 @@ srv/web/ipfire/cgi-bin/proxy.cgi
>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/qos.cgi
>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/remote.cgi
>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/routing.cgi
>>>>>>>>>>>>>>> +#srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>> #srv/web/ipfire/cgi-bin/samba.cgi
>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/services.cgi
>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/shutdown.cgi
>>>>>>>>>>>>>>> diff --git a/config/rootfiles/packages/rpz b/config/rootfiles/packages/rpz
>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>> index 000000000..1c8663049
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/config/rootfiles/packages/rpz
>>>>>>>>>>>>>>> @@ -0,0 +1,20 @@
>>>>>>>>>>>>>>> +etc/unbound/local.d/00-rpz.conf
>>>>>>>>>>>>>>> +etc/unbound/zonefiles
>>>>>>>>>>>>>>> +etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>> +usr/sbin/rpz-config
>>>>>>>>>>>>>>> +usr/sbin/rpz-functions
>>>>>>>>>>>>>>> +usr/sbin/rpz-make
>>>>>>>>>>>>>>> +usr/sbin/rpz-metrics
>>>>>>>>>>>>>>> +usr/sbin/rpz-sleep
>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.de.pl
>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.en.pl
>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.es.pl
>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.fr.pl
>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.it.pl
>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.tr.pl
>>>>>>>>>>>>>>> +var/ipfire/backup/addons/includes/rpz
>>>>>>>>>>>>>>> +var/ipfire/dns/rpz
>>>>>>>>>>>>>>> +var/ipfire/dns/rpz/allowlist
>>>>>>>>>>>>>>> +var/ipfire/dns/rpz/blocklist
>>>>>>>>>>>>>>> +var/ipfire/menu.d/EX-rpz.menu
>>>>>>>>>>>>>>> +srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>> diff --git a/config/rpz/00-rpz.conf b/config/rpz/00-rpz.conf
>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>> index 000000000..f005a4f2e
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/config/rpz/00-rpz.conf
>>>>>>>>>>>>>>> @@ -0,0 +1,10 @@
>>>>>>>>>>>>>>> +server:
>>>>>>>>>>>>>>> + module-config: "respip validator iterator"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +rpz:
>>>>>>>>>>>>>>> + name: allow.rpz
>>>>>>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>> + rpz-action-override: passthru
>>>>>>>>>>>>>>> + rpz-log: yes
>>>>>>>>>>>>>>> + rpz-log-name: allow
>>>>>>>>>>>>>>> + rpz-signal-nxdomain-ra: yes
>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-config b/config/rpz/rpz-config
>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>> index 000000000..c72d50f9b
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-config
>>>>>>>>>>>>>>> @@ -0,0 +1,130 @@
>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +version="2025-01-11 - v44"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +tagName="unbound"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +rpzAction="${1}" # input RPZ action
>>>>>>>>>>>>>>> +rpzName="${2}" # input RPZ name
>>>>>>>>>>>>>>> +rpzURL="${3}" # input RPZ URL
>>>>>>>>>>>>>>> +rpzOption1="${4}" # input RPZ option #1
>>>>>>>>>>>>>>> +rpzOption2="${5}" # input RPZ option #2
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +rpzConfig="/etc/unbound/local.d/${rpzName}.rpz.conf" # output zone conf file
>>>>>>>>>>>>>>> +rpzFile="/etc/unbound/zonefiles/${rpzName}.rpz" # output for RPZ file
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>>>>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>>>>>>> + case "$1" in
>>>>>>>>>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>>>>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>>>>>>>>>> + esac
>>>>>>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +case "${rpzAction}" in
>>>>>>>>>>>>>>> + # add new rpz list
>>>>>>>>>>>>>>> + add )
>>>>>>>>>>>>>>> + check_name "${rpzName}" # is this a valid name?
>>>>>>>>>>>>>>> + # does this config already exist? If yes, then exit
>>>>>>>>>>>>>>> + if [[ -f "${rpzConfig}" ]] ; then
>>>>>>>>>>>>>>> + msg_log "error: rpz: duplicate - ${rpzConfig} already exists. exit"
>>>>>>>>>>>>>>> + exit 104
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # is this a valid URL?
>>>>>>>>>>>>>>> + regex='^https://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
>>>>>>>>>>>>>>> + if ! [[ "${rpzURL}" =~ $regex ]] ; then
>>>>>>>>>>>>>>> + msg_log "error: rpz: the URL is not valid: \"${rpzURL}\". exit."
>>>>>>>>>>>>>>> + exit 105
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # create the zone config file
>>>>>>>>>>>>>>> + {
>>>>>>>>>>>>>>> + echo "rpz:"
>>>>>>>>>>>>>>> + echo " name: ${rpzName}.rpz"
>>>>>>>>>>>>>>> + echo " zonefile: ${rpzFile}"
>>>>>>>>>>>>>>> + echo " url: ${rpzURL}"
>>>>>>>>>>>>>>> + echo " rpz-action-override: nxdomain"
>>>>>>>>>>>>>>> + echo " rpz-log: ${rpzLog}"
>>>>>>>>>>>>>>> + echo " rpz-log-name: ${rpzName}"
>>>>>>>>>>>>>>> + echo " rpz-signal-nxdomain-ra: yes"
>>>>>>>>>>>>>>> + } > "${rpzConfig}"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # set-up zonefile
>>>>>>>>>>>>>>> + # create an empty rpz file if it does not exist
>>>>>>>>>>>>>>> + if [[ ! -f "${rpzFile}" ]] ; then
>>>>>>>>>>>>>>> + touch "${rpzFile}"
>>>>>>>>>>>>>>> + # unbound requires these settings for rpz files
>>>>>>>>>>>>>>> + set_permissions "${rpzFile}" "${rpzConfig}"
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # trash config file & rpz file
>>>>>>>>>>>>>>> + remove )
>>>>>>>>>>>>>>> + if ! [[ -f "${rpzConfig}" ]] ; then
>>>>>>>>>>>>>>> + msg_log "error: rpz: cannot remove ${rpzConfig}, does not exist. exit"
>>>>>>>>>>>>>>> + exit 106
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + msg_log "info: rpz: remove config file & rpz file \"${rpzName}\""
>>>>>>>>>>>>>>> + rm "${rpzConfig}"
>>>>>>>>>>>>>>> + rm "${rpzFile}"
>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + reload )
>>>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + list )
>>>>>>>>>>>>>>> + awk -F':' '/^\s*name:/{ gsub(/[[:blank:]]|\.rpz/, "",$2) ; NAME=$2 } \
>>>>>>>>>>>>>>> + /^\s*url:/{ gsub(/[[:blank:]]/, "") ; print NAME"="$2":"$3} ' \
>>>>>>>>>>>>>>> + /etc/unbound/local.d/*rpz.conf
>>>>>>>>>>>>>>> + exit
>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + unbound-restart )
>>>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>>>> + unbound_restart
>>>>>>>>>>>>>>> + exit
>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + * )
>>>>>>>>>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>>>>>>>>>> + printf "Usage: $(basename "$0") <ACTION> <NAME> <URL> <OPTION> <OPTION>\n"
>>>>>>>>>>>>>>> + printf "Version: ${version}\n"
>>>>>>>>>>>>>>> + exit 108
>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +esac
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +exit
>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-functions b/config/rpz/rpz-functions
>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>> index 000000000..ace1d2690
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-functions
>>>>>>>>>>>>>>> @@ -0,0 +1,85 @@
>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +version="2024-12-10 - v02"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +msg_log () {
>>>>>>>>>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>>>>>>>>>> + if tty --silent ; then
>>>>>>>>>>>>>>> + echo "${tagName}:" "$*"
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# check for a valid name
>>>>>>>>>>>>>>> +check_name () {
>>>>>>>>>>>>>>> + local theName="${1}"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + regex='^[a-zA-Z0-9_]+$' # no dash or plus, alpha numeric only
>>>>>>>>>>>>>>> + regex1='^(allow|block)$' # allow and block are reserved NAMEs
>>>>>>>>>>>>>>> + if [[ ! "${theName}" =~ $regex ]] || [[ "${theName}" =~ $regex1 ]] ; then
>>>>>>>>>>>>>>> + msg_log "error: rpz: the NAME is not valid: \"${theName}\". exit."
>>>>>>>>>>>>>>> + exit 101
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +set_permissions () {
>>>>>>>>>>>>>>> + chown nobody:nobody "$@"
>>>>>>>>>>>>>>> + chmod 644 "$@"
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +check_unbound_conf () {
>>>>>>>>>>>>>>> + local thecheckConf="${1:-yes}" # check config default is yes
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # check the above config files
>>>>>>>>>>>>>>> + if [[ "${thecheckConf}" == yes ]] ; then
>>>>>>>>>>>>>>> + msg_log "info: rpz: check for errors with \"unbound-checkconf\""
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + if ! unbound-checkconf ; then
>>>>>>>>>>>>>>> + msg_log "error: rpz: unbound-checkconf found invalid configuration."
>>>>>>>>>>>>>>> + msg_log \
>>>>>>>>>>>>>>> + "error: rpz: In Terminal run the command \"unbound-checkconf\" for more information. exit."
>>>>>>>>>>>>>>> + exit 102
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +unbound_control_reload () {
>>>>>>>>>>>>>>> + local theReload="${1:-yes}" # reload default is yes
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + if [[ "${theReload}" == yes ]] ; then
>>>>>>>>>>>>>>> + # reload due to the changes
>>>>>>>>>>>>>>> + msg_log "info: rpz: run \"unbound-control reload\""
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + if ! unbound-control reload ; then
>>>>>>>>>>>>>>> + msg_log "error: rpz: unbound-control reload. exit."
>>>>>>>>>>>>>>> + exit 109
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +unbound_restart () {
>>>>>>>>>>>>>>> + # restart due to the changes
>>>>>>>>>>>>>>> + msg_log "info: rpz: run \"unbound restart\""
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + /usr/local/bin/unboundctrl restart
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-make b/config/rpz/rpz-make
>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>> index 000000000..927d55170
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-make
>>>>>>>>>>>>>>> @@ -0,0 +1,203 @@
>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +version="2025-01-11 - v14"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# create the config file for allow
>>>>>>>>>>>>>>> +make_allow_config () {
>>>>>>>>>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>>>>>>>>>> + local theConfig="/etc/unbound/local.d/00-rpz.conf" # output zone conf file
>>>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/allowlist" # input custom list of domains
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + msg_log "info: rpz: make config file \"00-rpz.conf\""
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + echo "server:
>>>>>>>>>>>>>>> + module-config: \"respip validator iterator\"" > "${theConfig}"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # does allow list exist?
>>>>>>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + echo "rpz:
>>>>>>>>>>>>>>> + name: allow.rpz
>>>>>>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>> + rpz-action-override: passthru
>>>>>>>>>>>>>>> + rpz-log: ${theLog}
>>>>>>>>>>>>>>> + rpz-log-name: allow
>>>>>>>>>>>>>>> + rpz-signal-nxdomain-ra: yes" >> "${theConfig}"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>>>>>>> + set_permissions "${theConfig}"
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# create the config file for block
>>>>>>>>>>>>>>> +make_block_config () {
>>>>>>>>>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>>>>>>>>>> + local theConfig="/etc/unbound/local.d/block.rpz.conf" # output zone conf file
>>>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/blocklist" # input custom list of domains
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + msg_log "info: rpz: make config file \"block.rpz.conf\""
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # does block list exist?
>>>>>>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + echo "rpz:
>>>>>>>>>>>>>>> + name: block.rpz
>>>>>>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/block.rpz
>>>>>>>>>>>>>>> + rpz-action-override: nxdomain
>>>>>>>>>>>>>>> + rpz-log: ${theLog}
>>>>>>>>>>>>>>> + rpz-log-name: block
>>>>>>>>>>>>>>> + rpz-signal-nxdomain-ra: yes" > "${theConfig}"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>>>>>>> + set_permissions "${theConfig}"
>>>>>>>>>>>>>>> + else
>>>>>>>>>>>>>>> + # no - trash the config file
>>>>>>>>>>>>>>> + rm --verbose /etc/unbound/local.d/block.rpz.conf
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# create an RPZ file for allow or block
>>>>>>>>>>>>>>> +make_rpz_file () {
>>>>>>>>>>>>>>> + local theName="${1}" # allow or block
>>>>>>>>>>>>>>> + local theAction='.' # the default is nxdomain or block
>>>>>>>>>>>>>>> + local actionList
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>>>>>>>>>> + local theZonefile="/etc/unbound/zonefiles/${theName}.rpz" # output file for RPZ
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # does a list exist?
>>>>>>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # for allow set to passthru
>>>>>>>>>>>>>>> + [[ "${theName}" == allow ]] && theAction='rpz-passthru.'
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # drop any extra "blanks" and add "CNAME <RPZ action>." to each line
>>>>>>>>>>>>>>> + actionList=$( awk '{$1=$1};1' "${theList}" |
>>>>>>>>>>>>>>> + sed "/^[^;].*[[:alnum:]]/ s|$| CNAME ${theAction}|" )
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + msg_log "info: rpz: create zonefile for \"${theName}list\""
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +echo "; Name: ${theName} list
>>>>>>>>>>>>>>> +; Last modified: $(date "+%Y-%m-%d at %H.%M.%S %Z")
>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>> +; domains with actions list
>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>> +${actionList}" > "${theZonefile}"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>>>>>>> + set_permissions "${theZonefile}"
>>>>>>>>>>>>>>> + # set-up allow/block list files
>>>>>>>>>>>>>>> + set_permissions "${theList}"
>>>>>>>>>>>>>>> + else
>>>>>>>>>>>>>>> + msg_log "info: rpz: the ${theList} is empty."
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + rm --verbose "${theZonefile}" # trash the RPZ file
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# check if allow/block list is valid
>>>>>>>>>>>>>>> +validate_list () {
>>>>>>>>>>>>>>> + local theName="${1}" # allow or block
>>>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # remove good:
>>>>>>>>>>>>>>> + # - properly formated domain names with or without leading wildcard
>>>>>>>>>>>>>>> + # - properly formated top level domain (TLD) names with wildcard
>>>>>>>>>>>>>>> + # - blank lines and comment lines
>>>>>>>>>>>>>>> + # remaining lines are considered "bad"
>>>>>>>>>>>>>>> + bad_lines=$( sed --regexp-extended \
>>>>>>>>>>>>>>> + '/^(\*\.)?([a-zA-Z0-9](([a-zA-Z0-9\-]){0,61}[a-zA-Z0-9])?\.)+([a-zA-Z]{2,}|xn--[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])$/d ;
>>>>>>>>>>>>>>> + /^(\*\.)([a-z]{2,61}|xn--[a-z0-9]{1,60})$/d ;
>>>>>>>>>>>>>>> + /^$/d ; /^;/d' "${theList}" )
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + if [[ ! -z "${bad_lines}" ]] ; then
>>>>>>>>>>>>>>> + msg_log "error: rpz: invalid line(s) in ${theList}."
>>>>>>>>>>>>>>> + printf "%s\n" "bad line(s): ${bad_lines}"
>>>>>>>>>>>>>>> + exit 110
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +tagName="unbound"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +rpzName="${1}" # input RPZ name
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>>>>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>>>>>>> + case "$1" in
>>>>>>>>>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>>>>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>>>>>>>>>> + esac
>>>>>>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +case "${rpzName}" in
>>>>>>>>>>>>>>> + # make a new allow or block rpz file
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + allow )
>>>>>>>>>>>>>>> + validate_list 'allow' # is the allowlist valid?
>>>>>>>>>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>>>>>>>>>> + make_rpz_file 'allow'
>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + allowblock )
>>>>>>>>>>>>>>> + validate_list 'allow' # is the list valid?
>>>>>>>>>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>>>>>>>>>> + make_rpz_file 'allow'
>>>>>>>>>>>>>>> + ;&
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + block )
>>>>>>>>>>>>>>> + validate_list 'block' # is the blocklist valid?
>>>>>>>>>>>>>>> + make_block_config "${rpzLog}"
>>>>>>>>>>>>>>> + make_rpz_file 'block'
>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + reload )
>>>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + unbound-restart )
>>>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>>>> + unbound_restart
>>>>>>>>>>>>>>> + exit
>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + * )
>>>>>>>>>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>>>>>>>>>> + printf "Usage: $(basename "$0") <NAME> <OPTION> <OPTION>\n"
>>>>>>>>>>>>>>> + printf "Version: ${version}\n"
>>>>>>>>>>>>>>> + exit 108
>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>> +esac
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +exit
>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-metrics b/config/rpz/rpz-metrics
>>>>>>>>>>>>>>> new file mode 100755
>>>>>>>>>>>>>>> index 000000000..4d43e1629
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-metrics
>>>>>>>>>>>>>>> @@ -0,0 +1,170 @@
>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +version="2025-01-20 - v25"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +weeks="2" # default to two message logs
>>>>>>>>>>>>>>> +sortBy="name" # default "by name"
>>>>>>>>>>>>>>> +rpzActive="enabled" # default "enabled only"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>>>>>>> + case "$1" in
>>>>>>>>>>>>>>> + --by-names | --by-name | name ) sortBy="name" ;;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + --by-hits | --by-hit | hits | hit ) sortBy="hit" ;;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + --by-lines | --by-line | lines | line ) sortBy="line" ;;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + --by-effect ) sortBy="effect" ;;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + --enabled-only ) rpzActive="enabled" ;;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + --active-all | --all | all ) rpzActive="all" ;;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + [0-9] | [0-9][0-9] ) weeks=$1 ;;
>>>>>>>>>>>>>>> + esac
>>>>>>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# get the list of message logs for N weeks
>>>>>>>>>>>>>>> +messageLogs=$( find /var/log/messages* -type f | sort --version-sort |
>>>>>>>>>>>>>>> + head -"${weeks}" )
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# get the list of RPZ names & counts from the message log(s)
>>>>>>>>>>>>>>> +rpzNameCount=$( for logf in ${messageLogs} ; do
>>>>>>>>>>>>>>> + zgrep --text --fixed-strings 'info: rpz: applied' "${logf}" |
>>>>>>>>>>>>>>> + awk '$10 ~ /\[\w*]/ { print $10 }' ;
>>>>>>>>>>>>>>> + done | sort | uniq --count )
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# flip results and remove brackets `[` and `]`
>>>>>>>>>>>>>>> +rpzNameCount=$( echo "${rpzNameCount}" |
>>>>>>>>>>>>>>> + awk '{ print $2, $1 }' |
>>>>>>>>>>>>>>> + sed --regexp-extended 's|^\[(.*)\]|\1|' )
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# grab only names
>>>>>>>>>>>>>>> +rpzNames=$( echo "${rpzNameCount}" | awk '{ print $1 }' )
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# get list of RPZ files
>>>>>>>>>>>>>>> +rpzFileList=$( find /etc/unbound/zonefiles -type f -iname "*.rpz" )
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# get basename of those files
>>>>>>>>>>>>>>> +rpzBaseNames=$( echo "${rpzFileList}" |
>>>>>>>>>>>>>>> + sed 's|/etc/unbound/zonefiles/||g ; s|\.rpz||g ;' )
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# add to rpzNames
>>>>>>>>>>>>>>> +rpzNames="${rpzNames}"$'\n'"${rpzBaseNames}"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# drop duplicate names
>>>>>>>>>>>>>>> +rpzNames=$( echo "${rpzNames}" | sort --unique )
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# get line count for each RPZ
>>>>>>>>>>>>>>> +lineCount=$( echo "${rpzFileList}" | xargs wc -l )
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# get comment line count and blank line count for each RPZ
>>>>>>>>>>>>>>> +commentCount=$( echo "${rpzFileList}" | xargs grep --count -e "^$" -e "^;" )
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# get modified date each RPZ
>>>>>>>>>>>>>>> +modDateList=$( echo "${rpzFileList}" | xargs stat -c '%.10y %n' )
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +ucListAuthZones=$( unbound-control list_auth_zones )
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# get width of RPZ names
>>>>>>>>>>>>>>> +pWidth=$( echo "${rpzNames}" | awk '{ print $1" " }' | wc -L )
>>>>>>>>>>>>>>> +pFormat="%-${pWidth}s %-8s %-8s %8s %12s %12s\n"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# print title line
>>>>>>>>>>>>>>> +printf "${pFormat}" "name" "hits" "active" "lines" " hits/line" "last download"
>>>>>>>>>>>>>>> +printf -- "--------------"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +theResults=""
>>>>>>>>>>>>>>> +totalLines=0
>>>>>>>>>>>>>>> +totalHits=0
>>>>>>>>>>>>>>> +while read -r theName
>>>>>>>>>>>>>>> +do
>>>>>>>>>>>>>>> + printf -- "--" # pretend progress bar
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # is this RPZ list active?
>>>>>>>>>>>>>>> + theActive="disabled"
>>>>>>>>>>>>>>> + if grep --quiet "^${theName}\.rpz" <<< "${ucListAuthZones}"
>>>>>>>>>>>>>>> + then
>>>>>>>>>>>>>>> + theActive="enabled"
>>>>>>>>>>>>>>> + else
>>>>>>>>>>>>>>> + [[ "${rpzActive}" == enabled ]] && continue
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # get hit count
>>>>>>>>>>>>>>> + theHits="0"
>>>>>>>>>>>>>>> + if output=$( grep "^${theName}\s" <<< "${rpzNameCount}" ) ; then
>>>>>>>>>>>>>>> + theHits=$( echo "${output}" | awk '{ print $2 }' )
>>>>>>>>>>>>>>> + totalHits=$(( totalHits + theHits ))
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # get line count
>>>>>>>>>>>>>>> + theLines="n/a"
>>>>>>>>>>>>>>> + hitsPerLine="0"
>>>>>>>>>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${lineCount}" ) ; then
>>>>>>>>>>>>>>> + theLines=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>>>>>>>>>> + totalLines=$(( totalLines + theLines ))
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + if [[ "${theLines}" -gt 2 ]] ; then
>>>>>>>>>>>>>>> + hitsPerLine=$(( 100 * theHits / theLines ))
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # get modification date
>>>>>>>>>>>>>>> + theModDate="n/a"
>>>>>>>>>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${modDateList}" ) ; then
>>>>>>>>>>>>>>> + theModDate=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # add to results list
>>>>>>>>>>>>>>> + theResults+="${theName} ${theHits} ${theActive} ${theLines} ${hitsPerLine} ${theModDate}"$'\n'
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +done <<< "${rpzNames}"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +case "${sortBy}" in
>>>>>>>>>>>>>>> + # sort by "active" then by "name"
>>>>>>>>>>>>>>> + name) sortArg=(-k3,3r -k1,1) ;;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # sort by "active" then by "hits" then by "name"
>>>>>>>>>>>>>>> + hit) sortArg=(-k3,3r -k2,2nr -k1,1) ;;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # sort by "active" then by "lines" then by "name"
>>>>>>>>>>>>>>> + line) sortArg=(-k3,3r -k4,4nr -k1,1) ;;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # sort by "active" then by "effect" then by "name"
>>>>>>>>>>>>>>> + effect) sortArg=(-k3,3r -k5,5nr -k1,1) ;;
>>>>>>>>>>>>>>> +esac
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +printf -- "--------------\n"
>>>>>>>>>>>>>>> +# remove blank lines, sort, print as columns
>>>>>>>>>>>>>>> +echo "${theResults}" |
>>>>>>>>>>>>>>> + awk '!/^[[:space:]]*$/' |
>>>>>>>>>>>>>>> + sort "${sortArg[@]}" |
>>>>>>>>>>>>>>> + awk --assign=width="${pWidth}" \
>>>>>>>>>>>>>>> + '{ printf "%-*s %-8s %-8s %8s %10s %% %12s\n", width, $1, $2, $3, $4, $5, $6 }'
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +printf "${pFormat}" "" "=======" "" "========" "" ""
>>>>>>>>>>>>>>> +printf "${pFormat}" "Totals -->" "${totalHits}" "" "${totalLines}" "" ""
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +exit
>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-sleep b/config/rpz/rpz-sleep
>>>>>>>>>>>>>>> new file mode 100755
>>>>>>>>>>>>>>> index 000000000..dd3603599
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-sleep
>>>>>>>>>>>>>>> @@ -0,0 +1,58 @@
>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +version="2024-08-16" # v05
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# send message to message log
>>>>>>>>>>>>>>> +msg_log () {
>>>>>>>>>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>>>>>>>>>> + if tty --silent ; then
>>>>>>>>>>>>>>> + echo "${tagName}:" "$*"
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +tagName="unbound"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +sleepTime="${1:-5m}" # default to sleep for 5m (5 minutes)
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +zoneList=$( unbound-control list_auth_zones | awk '{print $1}' )
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +for zone in ${zoneList} ; do
>>>>>>>>>>>>>>> + printf "disable ${zone}\t"
>>>>>>>>>>>>>>> + unbound-control rpz_disable "${zone}"
>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +msg_log "info: rpz: disabled all zones for ${sleepTime}"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +sleep "${sleepTime}"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +for zone in ${zoneList} ; do
>>>>>>>>>>>>>>> + printf "enable ${zone}\t"
>>>>>>>>>>>>>>> + unbound-control rpz_enable "${zone}"
>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +msg_log "info: rpz: enabled all zones"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +exit
>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.de.pl b/config/rpz/rpz.de.pl
>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>> index 000000000..3770c6bb0
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.de.pl
>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>> +'rpz apply' => 'Übernehmen',
>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Benutzerdefinierte Allowlist aktivieren:',
>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Zugelassene Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>>>>>>>>>> +'rpz cl allow' => 'Benutzerdefinierte Allowlist',
>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Benutzerdefinierte Blocklist aktivieren:',
>>>>>>>>>>>>>>> +'rpz cl block info' => 'Gesperrte Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>>>>>>>>>> +'rpz cl block' => 'Benutzerdefinierte Blocklist',
>>>>>>>>>>>>>>> +'rpz cl' => 'Benutzerdefinierte Listen',
>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'Der Name enthält unzulässige Zeichen',
>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf hat eine fehlerhafte Konfiguration ermittelt. Führen Sie das Kommando unbound-checkconf auf der Konsole aus, um weitere Informationen zu erhalten.',
>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'Die benutzerdefinierte Allow-/Blocklist ist leer',
>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'Ein Eintrag mit identischem Namen existiert bereits',
>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'Die URL ist ungültig',
>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'Eintrag kann nicht entfernt werden, der Name existiert nicht',
>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'Der Name ist ungültig - nur "allow" oder "block" möglich',
>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'Fehlende oder inkorrekte Parameter',
>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control reload ist fehlgeschlagen',
>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'Die benutzerdefinierte Allow-/Blocklist enthält unzulässige Einträge',
>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'Die Anmerkung enthält unzulässige Zeichen',
>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'Ungültiger Eintrag in der benutzerdefinierten Allowlist, Zeile ',
>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'Ungültiger Eintrag in der benutzerdefinierten Blocklist, Zeile ',
>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'Ausgewählter Eintrag existiert nicht: ',
>>>>>>>>>>>>>>> +'rpz zf editor' => 'Zonendatei-Eintrag bearbeiten',
>>>>>>>>>>>>>>> +'rpz zf imported' => '(importiert aus rpz-config)',
>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Erlaubte Zeichen sind a-z, A-Z, 0-9 und Unterstriche',
>>>>>>>>>>>>>>> +'rpz zf' => 'Zonendateien',
>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.en.pl b/config/rpz/rpz.en.pl
>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>> index 000000000..0720a8940
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.en.pl
>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>> +'rpz apply' => 'Apply',
>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Enable custom allowlist:',
>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Allowed domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>>>> +'rpz cl allow' => 'Custom allowlist',
>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Enable custom blocklist:',
>>>>>>>>>>>>>>> +'rpz cl block info' => 'Blocked domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>>>> +'rpz cl block' => 'Custom blocklist',
>>>>>>>>>>>>>>> +'rpz cl' => 'Custom lists',
>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'the NAME is not valid',
>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf found invalid configuration. In the Terminal run the command unbound-checkconf for more information',
>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'the allow/block list is empty',
>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'duplicate - NAME already exists',
>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'the URL is not valid',
>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'cannot remove the NAME does not exist',
>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'the NAME is not valid - "allow" or "block" only',
>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'missing or incorrect parameter',
>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control reload failed',
>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'custom Allowlist/Blocklist contains invalid entries',
>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'the REMARK is not valid',
>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'invalid entry in allowlist, line ',
>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'invalid entry in blocklist, line ',
>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'Selected entry does not exist: ',
>>>>>>>>>>>>>>> +'rpz zf editor' => 'Edit zonefiles entry',
>>>>>>>>>>>>>>> +'rpz zf imported' => '(imported from rpz-config)',
>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Valid characters are a-z, A-Z, 0-9 and underscore.',
>>>>>>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.es.pl b/config/rpz/rpz.es.pl
>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>> index 000000000..98628e4aa
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.es.pl
>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>> +'rpz' => 'Zonas de política de respuesta (RPZ)',
>>>>>>>>>>>>>>> +'rpz apply' => 'Aplicar',
>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Habilitar la lista blanca personalizada:',
>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Dominio permitido (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>>>>>>>>>> +'rpz cl allow' => 'Lista blanca personalizada',
>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Habilitar la lista negra personalizada:',
>>>>>>>>>>>>>>> +'rpz cl block info' => 'Dominio bloqueado (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>>>>>>>>>> +'rpz cl block' => 'Lista negra personalizada',
>>>>>>>>>>>>>>> +'rpz cl' => 'Lista personalizada',
>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'El NOMBRE no es válido',
>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha encontrado una configuración no válida. Desde Terminal, ejecute el comando unbound-checkconf para mayor información',
>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'La lista de permitidos/bloqueados está vacía',
>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'duplicado - NOMBRE ya existe',
>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'la URL no es válida',
>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'no es posible eliminar el NOMBRE que no existe',
>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'el NOMBRE no es válido - sólo "permitir" o "bloquear"',
>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'parámetro faltante o incorrecto',
>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'Error al recargar unbound-control',
>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'la Lista blanca/Lista negra personalizada contiene entradas no válidas',
>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'el COMENTARIO no es válido',
>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'entrada no válida en la lista blanca, línea ',
>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'entrada no válida en la lista negra, línea ',
>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'La entrada seleccionada no existe: ',
>>>>>>>>>>>>>>> +'rpz zf editor' => 'Editar la entrada de archivos de zona',
>>>>>>>>>>>>>>> +'rpz zf imported' => '(importado de rpz-config)',
>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Los caracteres válidos son a-z, A-Z, 0-9 y guión bajo',
>>>>>>>>>>>>>>> +'rpz zf' => 'Archivos de zona',
>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.fr.pl b/config/rpz/rpz.fr.pl
>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>> index 000000000..f35f3c2d0
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.fr.pl
>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>> +'rpz apply' => 'Appliquer',
>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Activer la liste d\'autorisations personnalisée:',
>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Domaines autorisés (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>>>> +'rpz cl allow' => 'Liste d\'autorisations personnalisée',
>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Activer la liste de blocage personnalisée:',
>>>>>>>>>>>>>>> +'rpz cl block info' => 'Domaines bloqués (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>>>> +'rpz cl block' => 'liste de blocage personnalisé',
>>>>>>>>>>>>>>> +'rpz cl' => 'Listes personnalisées',
>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'le NOM n\'est pas valide',
>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf configuration non valide trouvée. Dans le terminal, exécutez la commande unbound-checkconf pour plus d\'informations',
>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'la liste autoriser/bloquer est vide',
>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'le NOM existe déjà',
>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'L\'URL n\'est pas valide',
>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'impossible de supprimer le NOM n\'existe pas',
>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'le NOM n\'est pas valide - « autoriser » ou « bloquer » seulement',
>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'paramètre manquant ou incorrect',
>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control rechargement échoué',
>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'la liste autoriser/bloquer contient des entrées non valides',
>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'la REMARQUE n\'est pas valable',
>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'entrée non valide dans la liste d\'autorisation, ligne ',
>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'entrée non valide dans la liste de blocs, ligne ',
>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'L\'entrée sélectionnée n\'existe pas: ',
>>>>>>>>>>>>>>> +'rpz zf editor' => 'Modifier l\'entrée Fichiers Zone',
>>>>>>>>>>>>>>> +'rpz zf imported' => '(importé de rpz-config)',
>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Les caractères valides sont a-z, A-Z, 0-9 et soulignement.',
>>>>>>>>>>>>>>> +'rpz zf' => 'Fichiers Zone',
>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.it.pl b/config/rpz/rpz.it.pl
>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>> index 000000000..ee81605c9
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.it.pl
>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>> +'rpz apply' => 'Applica',
>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Abilita la Whitelist personalizzata:',
>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Domini consentiti (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>>>>>>>>>> +'rpz cl allow' => 'Whitelist personalizzata',
>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Abilita la Blacklist personalizzata:',
>>>>>>>>>>>>>>> +'rpz cl block info' => 'Domini bloccati (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>>>>>>>>>> +'rpz cl block' => 'Blacklist personalizzata',
>>>>>>>>>>>>>>> +'rpz cl' => 'Liste personalizzate',
>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'il NOME non è valido',
>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha trovato una configurazione non valida. Dal Terminale esegui il comando unbound-checkconf per maggiori informazioni',
>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'l\'elenco consentiti/bloccati è vuoto',
>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'duplicato - NAME esiste di già',
>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'l\'URL non è valido',
>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'non è possibile rimuovere il NOME non esiste',
>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'il NOME non è valido - solo "consenti" o "blocca"',
>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'parametro mancante o non corretto',
>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'ricaricamento del controllo non associato non riuscito',
>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'la Whitelist/Blacklist personalizzata contiene voci non valide',
>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'l"OSSERVAZIONE non è valida',
>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'voce non valida nella Whitelist, riga ',
>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'voce non valida nella Blacklist, riga ',
>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'La voce selezionata non esiste: ',
>>>>>>>>>>>>>>> +'rpz zf editor' => 'Modifica la voce dei file di zona',
>>>>>>>>>>>>>>> +'rpz zf imported' => '(importato da rpz-config)',
>>>>>>>>>>>>>>> +'rpz zf remark info' => 'I caratteri validi sono a-z, A-Z, 0-9 e trattino basso',
>>>>>>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.tr.pl b/config/rpz/rpz.tr.pl
>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>> index 000000000..00226e192
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.tr.pl
>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>> +# I�in eklendi Ayriyeten Response Policy Zone (RPZ)
>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>> +'rpz apply' => 'Uygulamak',
>>>>>>>>>>>>>>> +'rpz cl allow enable' => '�zel Etkinlestir allowlist:',
>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Izin verilmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>>>>>>>>>> +'rpz cl allow' => 'Etkinlestir allowlist',
>>>>>>>>>>>>>>> +'rpz cl block enable' => '�zel Etkinlestir blocklist:',
>>>>>>>>>>>>>>> +'rpz cl block info' => 'Engellenmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>>>>>>>>>> +'rpz cl block' => 'Engellenmis blocklist',
>>>>>>>>>>>>>>> +'rpz cl' => 'Engellenmis lists',
>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'NAME ge�erli degil',
>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ge�ersiz yapilandirma bulundu. Terminalde daha fazla bilgi i�in Unbound-checkConf komutunu �alistirin',
>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'allow/block liste bos',
>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'kopyalamak - NAME zaten var',
>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'URL ge�erli degil',
>>>>>>>>>>>>>>> +'rpz exitcode 106' => '�ikarilamiyor NAME yok',
>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'NAME ge�erli degil - "allow" veya "block" yalniz',
>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'Parametre eksik veya yanlis',
>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control basarisiz',
>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'Engellenmis Allowlist/Blocklist ge�ersiz girisler i�erir',
>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'REMARK ge�erli degil',
>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'Ge�ersiz giris allowlist, line ',
>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'Ge�ersiz giris blocklist, line ',
>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'Se�ilen giris yok: ',
>>>>>>>>>>>>>>> +'rpz zf editor' => 'yazimlamak zonefiles giris',
>>>>>>>>>>>>>>> +'rpz zf imported' => '(ithal edildi rpz-config)',
>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Ge�erli karakterler a-z, A-Z, 0-9ve alt�st.',
>>>>>>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>> diff --git a/html/cgi-bin/rpz.cgi b/html/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>> index 000000000..a821c92ac
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/html/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>> @@ -0,0 +1,923 @@
>>>>>>>>>>>>>>> +#!/usr/bin/perl
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>> +# Copyright (C) 2005-2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +use strict;
>>>>>>>>>>>>>>> +use Scalar::Util qw(looks_like_number);
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# debugging
>>>>>>>>>>>>>>> +#use warnings;
>>>>>>>>>>>>>>> +#use CGI::Carp 'fatalsToBrowser';
>>>>>>>>>>>>>>> +#use Data::Dumper;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +require '/var/ipfire/general-functions.pl';
>>>>>>>>>>>>>>> +require "${General::swroot}/lang.pl";
>>>>>>>>>>>>>>> +require "${General::swroot}/header.pl";
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +###--- Extra HTML ---###
>>>>>>>>>>>>>>> +my $extraHead = <<END
>>>>>>>>>>>>>>> +<style>
>>>>>>>>>>>>>>> + /* alternating row background */
>>>>>>>>>>>>>>> + .tbl tr:nth-child(2n+2) {
>>>>>>>>>>>>>>> + background-color: var(--color-light-grey);
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + .tbl tr:nth-child(2n+3) {
>>>>>>>>>>>>>>> + background-color: var(--color-grey);
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + /* text styles */
>>>>>>>>>>>>>>> + .tbl th:not(:last-child) {
>>>>>>>>>>>>>>> + text-align: left;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + div.right {
>>>>>>>>>>>>>>> + text-align: right;
>>>>>>>>>>>>>>> + margin-top: 0.5em;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + /* customlist input */
>>>>>>>>>>>>>>> + textarea.domainlist {
>>>>>>>>>>>>>>> + margin: 0.5em 0;
>>>>>>>>>>>>>>> + resize: vertical;
>>>>>>>>>>>>>>> + min-height: 10em;
>>>>>>>>>>>>>>> + overflow: auto;
>>>>>>>>>>>>>>> + white-space: pre;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + button[type=submit]:disabled {
>>>>>>>>>>>>>>> + opacity: 0.6;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +</style>
>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>> +###--- End of extra HTML ---###
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +### Settings ###
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Request DNS service reload after configuration change
>>>>>>>>>>>>>>> +my $RPZ_RELOAD_FLAG = "${General::swroot}/dns/rpz/reload.flag";
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Configuration file for all available zonefiles
>>>>>>>>>>>>>>> +# Format: index, name (unique), enabled (on/off), URL, remark
>>>>>>>>>>>>>>> +my $ZONEFILES_CONF = "${General::swroot}/dns/rpz/zonefiles.conf";
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Configuration file for custom lists
>>>>>>>>>>>>>>> +# IDs: 0=allowlist, 1=blocklist, 2=options (allow/block enabled)
>>>>>>>>>>>>>>> +my $CUSTOMLISTS_CONF = "${General::swroot}/dns/rpz/customlists.conf";
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Export custom lists to rpz-config
>>>>>>>>>>>>>>> +my $RPZ_ALLOWLIST = "${General::swroot}/dns/rpz/allowlist";
>>>>>>>>>>>>>>> +my $RPZ_BLOCKLIST = "${General::swroot}/dns/rpz/blocklist";
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +### Preparation ###
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Create missing config files
>>>>>>>>>>>>>>> +unless(-f $ZONEFILES_CONF) { &General::system('touch', "$ZONEFILES_CONF"); }
>>>>>>>>>>>>>>> +unless(-f $CUSTOMLISTS_CONF) { &General::system('touch', "$CUSTOMLISTS_CONF"); }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +## Global gui data
>>>>>>>>>>>>>>> +my $errormessage = "";
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +## Global configuration data
>>>>>>>>>>>>>>> +my %zonefiles = ();
>>>>>>>>>>>>>>> +my %customlists = ();
>>>>>>>>>>>>>>> +&_zonefiles_load();
>>>>>>>>>>>>>>> +&_customlists_load();
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +## Global CGI form data
>>>>>>>>>>>>>>> +my %cgiparams = ();
>>>>>>>>>>>>>>> +&Header::getcgihash(\%cgiparams);
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +my $action = $cgiparams{'ACTION'} // 'NONE';
>>>>>>>>>>>>>>> +my $action_key = $cgiparams{'KEY'} // ''; # entry being edited, empty = none/new
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +###--- Process form actions ---###
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Zonefiles action: Check whether the requested entry exists
>>>>>>>>>>>>>>> +if((substr($action, 0, 3) eq 'ZF_') && ($action_key)) {
>>>>>>>>>>>>>>> + unless(defined $zonefiles{$action_key}) {
>>>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr(204, $action_key);
>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +## Perform actions
>>>>>>>>>>>>>>> +if($action eq 'ZF_SAVE') { ## Save new or modified zonefiles entry
>>>>>>>>>>>>>>> + if(&_action_zf_save()) {
>>>>>>>>>>>>>>> + $action = 'NONE'; # success, return to main page
>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>> + $action = 'ZF_EDIT'; # error occured, keep editing
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +} elsif($action eq 'ZF_TOGGLE') { ## Toggle on/off
>>>>>>>>>>>>>>> + if(&_action_zf_toggle()) {
>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +} elsif($action eq 'ZF_REMOVE') { ## Remove entry
>>>>>>>>>>>>>>> + if(&_action_zf_remove()) {
>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +} elsif($action eq 'CL_SAVE') { ## Save custom lists
>>>>>>>>>>>>>>> + if(&_action_cl_save()) {
>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +} elsif($action eq 'RPZ_RELOAD') { ## Reload dns configuration
>>>>>>>>>>>>>>> + if(&_action_rpz_reload()) {
>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +} elsif($action eq 'UNB_RESTART') { ## Restart unbound service
>>>>>>>>>>>>>>> + if(&_action_unb_restart()) {
>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +###--- Start GUI ---###
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +## Start http output
>>>>>>>>>>>>>>> +&Header::showhttpheaders();
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Start HTML
>>>>>>>>>>>>>>> +&Header::openpage($Lang::tr{'rpz'}, 1, $extraHead);
>>>>>>>>>>>>>>> +&Header::openbigbox('100%', 'left', '');
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Show error messages
>>>>>>>>>>>>>>> +if($errormessage) {
>>>>>>>>>>>>>>> + &_print_message($errormessage);
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Handle zonefile add/edit mode
>>>>>>>>>>>>>>> +if($action eq "ZF_EDIT") {
>>>>>>>>>>>>>>> + &_print_zonefile_editor();
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Finalize page and exit cleanly
>>>>>>>>>>>>>>> + &Header::closebigbox();
>>>>>>>>>>>>>>> + &Header::closepage();
>>>>>>>>>>>>>>> + exit(0);
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Show gui elements
>>>>>>>>>>>>>>> +&_print_zonefiles();
>>>>>>>>>>>>>>> +&_print_customlists();
>>>>>>>>>>>>>>> +&_print_gui_extras();
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +&Header::closebigbox();
>>>>>>>>>>>>>>> +&Header::closepage();
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +###--- End of GUI ---###
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +###--- Internal configuration file functions ---###
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Load all available zonefiles from rpz-config and the internal configuration
>>>>>>>>>>>>>>> +sub _zonefiles_load {
>>>>>>>>>>>>>>> + # Clean start
>>>>>>>>>>>>>>> + %zonefiles = ();
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Source 1: Get the currently enabled zonefiles from rpz-config (expected format [name]=[URL])
>>>>>>>>>>>>>>> + my @enabled_files = &General::system_output('/usr/sbin/rpz-config', 'list');
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + foreach my $row (@enabled_files) {
>>>>>>>>>>>>>>> + chomp($row);
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Use regex instead of split() to skip non-matching lines
>>>>>>>>>>>>>>> + next unless($row =~ /^(\w+)=(.+)$/);
>>>>>>>>>>>>>>> + my ($name, $url) = ($1, $2);
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Unique names are already guaranteed by rpz-config
>>>>>>>>>>>>>>> + if(&_rpz_validate_zonefile($name, $url, '', 0) == 0) {
>>>>>>>>>>>>>>> + # Populate global data hash, mark all found entries as enabled
>>>>>>>>>>>>>>> + my %entry = ('enabled' => 'on',
>>>>>>>>>>>>>>> + 'url' => $url,
>>>>>>>>>>>>>>> + 'remark' => $Lang::tr{'rpz zf imported'});
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Source 2: Get additional data and disabled entries from configuration file
>>>>>>>>>>>>>>> + my %configured_files = ();
>>>>>>>>>>>>>>> + &General::readhasharray($ZONEFILES_CONF, \%configured_files);
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + foreach my $row (values (%configured_files)) {
>>>>>>>>>>>>>>> + my ($name, $enabled, $url, $remark) = @$row;
>>>>>>>>>>>>>>> + $remark //= "";
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + next unless($name);
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Check whether this row belongs to an entry already imported from rpz-config
>>>>>>>>>>>>>>> + if(defined $zonefiles{$name}) {
>>>>>>>>>>>>>>> + # Existing entry, only merge additional data
>>>>>>>>>>>>>>> + $zonefiles{$name}{'remark'} = $remark;
>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>> + # Skip entry if it is marked as enabled but not found by rpz-config. It was then deleted manually
>>>>>>>>>>>>>>> + if($enabled ne 'on') {
>>>>>>>>>>>>>>> + # Populate global data hash
>>>>>>>>>>>>>>> + my %entry = ('enabled' => 'off',
>>>>>>>>>>>>>>> + 'url' => $url // "",
>>>>>>>>>>>>>>> + 'remark' => $remark);
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Save internal zonefiles configuration
>>>>>>>>>>>>>>> +sub _zonefiles_save_conf {
>>>>>>>>>>>>>>> + my $index = 0;
>>>>>>>>>>>>>>> + my %export = ();
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Loop trough all zonefiles and create "hasharray" type export
>>>>>>>>>>>>>>> + foreach my $name (keys %zonefiles) {
>>>>>>>>>>>>>>> + my @entry = ($name,
>>>>>>>>>>>>>>> + $zonefiles{$name}{'enabled'},
>>>>>>>>>>>>>>> + $zonefiles{$name}{'url'},
>>>>>>>>>>>>>>> + $zonefiles{$name}{'remark'});
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + $export{$index++} = \@entry;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + &General::writehasharray($ZONEFILES_CONF, \%export);
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Load custom lists from rpz-config and the internal configuration
>>>>>>>>>>>>>>> +sub _customlists_load {
>>>>>>>>>>>>>>> + # Clean start
>>>>>>>>>>>>>>> + %customlists = ();
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Load configuration file
>>>>>>>>>>>>>>> + my %lists_conf = ();
>>>>>>>>>>>>>>> + &General::readhasharray($CUSTOMLISTS_CONF, \%lists_conf);
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Get list options, enabled by default to start import
>>>>>>>>>>>>>>> + $customlists{'allow'}{'enabled'} = $lists_conf{2}[0] // 'on';
>>>>>>>>>>>>>>> + $customlists{'block'}{'enabled'} = $lists_conf{2}[1] // 'on';
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Import enabled list from rpz-config, otherwise retrieve stored or empty list from configuration file
>>>>>>>>>>>>>>> + if($customlists{'allow'}{'enabled'} eq 'on') {
>>>>>>>>>>>>>>> + &_customlist_import('allow', $RPZ_ALLOWLIST);
>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>> + $customlists{'allow'}{'list'} = $lists_conf{0} // [];
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + if($customlists{'block'}{'enabled'} eq 'on') {
>>>>>>>>>>>>>>> + &_customlist_import('block', $RPZ_BLOCKLIST);
>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>> + $customlists{'block'}{'list'} = $lists_conf{1} // [];
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Save internal custom lists configuration
>>>>>>>>>>>>>>> +sub _customlists_save_conf {
>>>>>>>>>>>>>>> + my %export = ();
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Match IDs with import function
>>>>>>>>>>>>>>> + $export{0} = $customlists{'allow'}{'list'};
>>>>>>>>>>>>>>> + $export{1} = $customlists{'block'}{'list'};
>>>>>>>>>>>>>>> + $export{2} = [$customlists{'allow'}{'enabled'}, $customlists{'block'}{'enabled'}];
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + &General::writehasharray($CUSTOMLISTS_CONF, \%export);
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Import a custom list from plain file, returns empty list if file is missing
>>>>>>>>>>>>>>> +sub _customlist_import {
>>>>>>>>>>>>>>> + my ($listname, $filename) = @_;
>>>>>>>>>>>>>>> + my @list = ();
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # File exists, load and check all lines
>>>>>>>>>>>>>>> + if(-f $filename) {
>>>>>>>>>>>>>>> + open(my $FH, '<', $filename) or die "Can't read $filename: $!";
>>>>>>>>>>>>>>> + while(my $line = <$FH>) {
>>>>>>>>>>>>>>> + chomp($line);
>>>>>>>>>>>>>>> + push(@list, $line);
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + close($FH);
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Clean up imported data
>>>>>>>>>>>>>>> + &_rpz_validate_customlist(\@list, 1);
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + $customlists{$listname}{'list'} = \@list;
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Export a custom list to plain file or clear file if list is disabled
>>>>>>>>>>>>>>> +sub _customlist_export {
>>>>>>>>>>>>>>> + my ($listname, $filename) = @_;
>>>>>>>>>>>>>>> + return unless(defined $customlists{$listname});
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Write enabled domain list to file, otherwise save empty file
>>>>>>>>>>>>>>> + open(my $FH, '>', $filename) or die "Can't write $filename: $!";
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + if($customlists{$listname}{'enabled'} eq 'on') {
>>>>>>>>>>>>>>> + foreach my $line (@{$customlists{$listname}{'list'}}) {
>>>>>>>>>>>>>>> + print $FH "$line\n";
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>> + print $FH "; Note: This list is currently disabled by $ENV{'SCRIPT_NAME'}\n";
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + close($FH);
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +###--- Internal gui functions ---###
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Show simple message box
>>>>>>>>>>>>>>> +sub _print_message {
>>>>>>>>>>>>>>> + my ($message, $title) = @_;
>>>>>>>>>>>>>>> + $title ||= $Lang::tr{'error messages'};
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $title);
>>>>>>>>>>>>>>> + print "<span>$message</span>";
>>>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Show all zone files and related gui elements
>>>>>>>>>>>>>>> +sub _print_zonefiles {
>>>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf'});
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>> +<table class="tbl" width="100%">
>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>> + <th>$Lang::tr{'name'}</th>
>>>>>>>>>>>>>>> + <th>URL</th>
>>>>>>>>>>>>>>> + <th>$Lang::tr{'remark'}</th>
>>>>>>>>>>>>>>> + <th colspan="3">$Lang::tr{'action'}</th>
>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Sort zonefiles by name and loop trough all entries
>>>>>>>>>>>>>>> + foreach my $name (sort keys %zonefiles) {
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Toggle button label translation
>>>>>>>>>>>>>>> + my $toggle_tr = ($zonefiles{$name}{'enabled'} eq 'on') ? $Lang::tr{'click to disable'} : $Lang::tr{'click to enable'};
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>> + <td>$name</td>
>>>>>>>>>>>>>>> + <td>$zonefiles{$name}{'url'}</td>
>>>>>>>>>>>>>>> + <td>$zonefiles{$name}{'remark'}</td>
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_TOGGLE">
>>>>>>>>>>>>>>> + <input type="image" src="/images/$zonefiles{$name}{'enabled'}.gif" title="$toggle_tr" alt="$toggle_tr">
>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_EDIT">
>>>>>>>>>>>>>>> + <input type="image" src="/images/edit.gif" title="$Lang::tr{'edit'}" alt="$Lang::tr{'edit'}">
>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_REMOVE">
>>>>>>>>>>>>>>> + <input type="image" src="/images/delete.gif" title="$Lang::tr{'remove'}" alt="$Lang::tr{'remove'}">
>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Disable reload button if not needed
>>>>>>>>>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>> +</table>
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +<div class="right">
>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="">
>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="ZF_EDIT">$Lang::tr{'add'}</button>
>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>> +</div>
>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Show zonefiles entry editor
>>>>>>>>>>>>>>> +sub _print_zonefile_editor {
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Key specified: Edit existing entry
>>>>>>>>>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>>>>>>>>>> + # Load data to be edited, but don't override already present values (allows user to edit after error)
>>>>>>>>>>>>>>> + $cgiparams{'ZF_NAME'} //= $action_key;
>>>>>>>>>>>>>>> + $cgiparams{'ZF_URL'} //= $zonefiles{$action_key}{'url'};
>>>>>>>>>>>>>>> + $cgiparams{'ZF_REMARK'} //= $zonefiles{$action_key}{'remark'};
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Fallback to empty form
>>>>>>>>>>>>>>> + $cgiparams{'ZF_NAME'} //= "";
>>>>>>>>>>>>>>> + $cgiparams{'ZF_URL'} //= "";
>>>>>>>>>>>>>>> + $cgiparams{'ZF_REMARK'} //= "";
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf editor'});
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>> +<input type="hidden" name="KEY" value="$action_key">
>>>>>>>>>>>>>>> +<table width="100%">
>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>> + <td width="20%">$Lang::tr{'name'}: <img src="/blob.gif" alt="*"></td>
>>>>>>>>>>>>>>> + <td><input type="text" name="ZF_NAME" value="$cgiparams{'ZF_NAME'}" size="40" maxlength="32" title="$Lang::tr{'rpz zf remark info'}" pattern="[a-zA-Z0-9_]{1,32}" required></td>
>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>> + <td width="20%">URL: <img src="/blob.gif" alt="*"></td>
>>>>>>>>>>>>>>> + <td><input type="url" name="ZF_URL" value="$cgiparams{'ZF_URL'}" size="40" maxlength="128" required></td>
>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>> + <td width="20%">$Lang::tr{'remark'}:</td>
>>>>>>>>>>>>>>> + <td><input type="text" name="ZF_REMARK" value="$cgiparams{'ZF_REMARK'}" size="40" maxlength="32"></td>
>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>> + <td colspan="2"><hr></td>
>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>> + <td width="55%"><img src="/blob.gif" alt="*"> $Lang::tr{'required field'}</td>
>>>>>>>>>>>>>>> + <td align="right"><button type="submit" name="ACTION" value="ZF_SAVE">$Lang::tr{'save'}</button></td>
>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>> +</table>
>>>>>>>>>>>>>>> +</form>
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +<div class="right">
>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="NONE">$Lang::tr{'back'}</button>
>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>> +</div>
>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Show custom allow/block files and related gui elements
>>>>>>>>>>>>>>> +sub _print_customlists {
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Load lists from config, unless they are currently being edited
>>>>>>>>>>>>>>> + if($action ne 'CL_SAVE') {
>>>>>>>>>>>>>>> + $cgiparams{'ALLOW_LIST'} = join("\n", @{$customlists{'allow'}{'list'}});
>>>>>>>>>>>>>>> + $cgiparams{'BLOCK_LIST'} = join("\n", @{$customlists{'block'}{'list'}});
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + $cgiparams{'ALLOW_ENABLED'} = ($customlists{'allow'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>>>>>>>>>> + $cgiparams{'BLOCK_ENABLED'} = ($customlists{'block'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Fallback to empty form
>>>>>>>>>>>>>>> + $cgiparams{'ALLOW_LIST'} //= "";
>>>>>>>>>>>>>>> + $cgiparams{'BLOCK_LIST'} //= "";
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # HTML checkboxes, unchecked = no or undef value in POST data
>>>>>>>>>>>>>>> + my %checked = ();
>>>>>>>>>>>>>>> + $checked{'ALLOW_ENABLED'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? " checked" : "";
>>>>>>>>>>>>>>> + $checked{'BLOCK_ENABLED'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? " checked" : "";
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Disable reload button if not needed
>>>>>>>>>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz cl'});
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>> +<table width="100%">
>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl allow'}</b><br>$Lang::tr{'rpz cl allow info'}</td>
>>>>>>>>>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl block'}</b><br>$Lang::tr{'rpz cl block info'}</td>
>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>> + <td colspan="2"><textarea name="ALLOW_LIST" class="domainlist" cols="45">
>>>>>>>>>>>>>>> +$cgiparams{'ALLOW_LIST'}</textarea></td>
>>>>>>>>>>>>>>> + <td colspan="2"><textarea name="BLOCK_LIST" class="domainlist" cols="45">
>>>>>>>>>>>>>>> +$cgiparams{'BLOCK_LIST'}</textarea></td>
>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>> + <td><label for="allow_enabled">$Lang::tr{'rpz cl allow enable'}</label></td>
>>>>>>>>>>>>>>> + <td width="15%"><input type="checkbox" name="ALLOW_ENABLED" id="allow_enabled"$checked{'ALLOW_ENABLED'}></td>
>>>>>>>>>>>>>>> + <td><label for="block_enabled">$Lang::tr{'rpz cl block enable'}</label></td>
>>>>>>>>>>>>>>> + <td width="15%"><input type="checkbox" name="BLOCK_ENABLED" id="block_enabled"$checked{'BLOCK_ENABLED'}></td>
>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>> + <td colspan="4"><hr></td>
>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>> + <td align="right" colspan="4">
>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="CL_SAVE">$Lang::tr{'save'}</button>
>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>>>> +</table>
>>>>>>>>>>>>>>> +</form>
>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Output javascript and extra gui elements
>>>>>>>>>>>>>>> +sub _print_gui_extras {
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Apply/Restart button modifier key handler
>>>>>>>>>>>>>>> + if(&_rpz_needs_reload()) {
>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>> +<script>
>>>>>>>>>>>>>>> + // Commit modifier key handler
>>>>>>>>>>>>>>> + (function(jq, document) {
>>>>>>>>>>>>>>> + var keyEventsOn = false; // Keyboard events attached
>>>>>>>>>>>>>>> + var keyModify = false; // Modifier key pressed
>>>>>>>>>>>>>>> + var mouseHover = false; // Mouse over commit button
>>>>>>>>>>>>>>> + var btnModified = false; // Button modified to "Restart"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + // Document-level key events, enable only while cursor is over button
>>>>>>>>>>>>>>> + function attachKeyEvents() {
>>>>>>>>>>>>>>> + if(keyEventsOn) {
>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + keyEventsOn = true;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + jq(document).on("keydown.rpz", function(event) {
>>>>>>>>>>>>>>> + if((!keyModify) && event.shiftKey) {
>>>>>>>>>>>>>>> + keyModify = true;
>>>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>> + jq(document).on("keyup.rpz", function(event) {
>>>>>>>>>>>>>>> + if(keyModify && (!event.shiftKey)) {
>>>>>>>>>>>>>>> + keyModify = false;
>>>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + function removeKeyEvents() {
>>>>>>>>>>>>>>> + keyModify = false;
>>>>>>>>>>>>>>> + if(keyEventsOn) {
>>>>>>>>>>>>>>> + jq(document).off("keydown.rpz keyup.rpz");
>>>>>>>>>>>>>>> + keyEventsOn = false;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + // Attach mouse hover events to commit buttons
>>>>>>>>>>>>>>> + function attachMouseEvents() {
>>>>>>>>>>>>>>> + jq("button.commit").on("mouseenter", function(event) {
>>>>>>>>>>>>>>> + if(!mouseHover) {
>>>>>>>>>>>>>>> + mouseHover = true;
>>>>>>>>>>>>>>> + attachKeyEvents();
>>>>>>>>>>>>>>> + // Handle already pressed key
>>>>>>>>>>>>>>> + keyModify = !!(event.shiftKey);
>>>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + // Cursor moved away: Disable key listener to minimize events
>>>>>>>>>>>>>>> + jq("button.commit").on("mouseleave", function() {
>>>>>>>>>>>>>>> + if(mouseHover) {
>>>>>>>>>>>>>>> + mouseHover = false;
>>>>>>>>>>>>>>> + removeKeyEvents();
>>>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + // Modify commit button
>>>>>>>>>>>>>>> + function handleModify() {
>>>>>>>>>>>>>>> + let modify = mouseHover && keyModify;
>>>>>>>>>>>>>>> + if(btnModified != modify) {
>>>>>>>>>>>>>>> + if(modify) {
>>>>>>>>>>>>>>> + jq("button.commit").text("$Lang::tr{'restart'}").val("UNB_RESTART");
>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>> + jq("button.commit").text("$Lang::tr{'rpz apply'}").val("RPZ_RELOAD");
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + btnModified = modify;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + // jQuery DOM ready
>>>>>>>>>>>>>>> + jq(function() {
>>>>>>>>>>>>>>> + attachMouseEvents();
>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>> + })(jQuery, document);
>>>>>>>>>>>>>>> +</script>
>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>> + } # End of modifier key handler
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +###--- Internal action processing functions ---###
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Toggle zonefile on/off
>>>>>>>>>>>>>>> +sub _action_zf_toggle {
>>>>>>>>>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + my $result = 0;
>>>>>>>>>>>>>>> + my $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Perform toggle action
>>>>>>>>>>>>>>> + if($enabled eq 'on') {
>>>>>>>>>>>>>>> + $enabled = 'off';
>>>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>> + $enabled = 'on';
>>>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $action_key, $zonefiles{$action_key}{'url'}, '--no-reload');
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Save changes
>>>>>>>>>>>>>>> + $zonefiles{$action_key}{'enabled'} = $enabled;
>>>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Remove zonefile
>>>>>>>>>>>>>>> +sub _action_zf_remove {
>>>>>>>>>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Remove from rpz-config if currently active
>>>>>>>>>>>>>>> + if($zonefiles{$action_key}{'enabled'} eq 'on') {
>>>>>>>>>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Remove from data hash and save changes
>>>>>>>>>>>>>>> + delete $zonefiles{$action_key};
>>>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Clear action_key, as the entry is now removed entirely
>>>>>>>>>>>>>>> + $action_key = "";
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Create or update zonefile entry
>>>>>>>>>>>>>>> +# Returns undef if gui needs to stay in editor mode
>>>>>>>>>>>>>>> +sub _action_zf_save {
>>>>>>>>>>>>>>> + my $result = 0;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + my $name = $cgiparams{'ZF_NAME'} // "";
>>>>>>>>>>>>>>> + my $url = $cgiparams{'ZF_URL'} // "";
>>>>>>>>>>>>>>> + my $remark = $cgiparams{'ZF_REMARK'} // "";
>>>>>>>>>>>>>>> + my $enabled = 'on'; # Enable new entries by default
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Note on variables:
>>>>>>>>>>>>>>> + # name = unique key, will be used to address the entry
>>>>>>>>>>>>>>> + # action_key = name of the entry being edited, empty for new entry
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Only check for unique name if it changed
>>>>>>>>>>>>>>> + # (this also checks new entries because the action_key is empty in this case)
>>>>>>>>>>>>>>> + $result = &_rpz_validate_zonefile($name, $url, $remark, (lc($name) ne lc($action_key)));
>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 0);
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Edit existing entry: Determine what was changed
>>>>>>>>>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>>>>>>>>>> + # Name und URL remain unchanged, only save remark and finish
>>>>>>>>>>>>>>> + if(($name eq $action_key) && ($url eq $zonefiles{$action_key}{'url'})) {
>>>>>>>>>>>>>>> + $zonefiles{$action_key}{'remark'} = $remark;
>>>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Entry was changed and needs to be recreated, preserve status
>>>>>>>>>>>>>>> + $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Remove from rpz-config
>>>>>>>>>>>>>>> + return unless &_action_zf_remove();
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Add new entry to rpz-config
>>>>>>>>>>>>>>> + if($enabled eq 'on') {
>>>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $name, $url, '--no-reload');
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Add to global data hash and save changes
>>>>>>>>>>>>>>> + my %entry = ('enabled' => $enabled,
>>>>>>>>>>>>>>> + 'url' => $url,
>>>>>>>>>>>>>>> + 'remark' => $remark);
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Save custom lists
>>>>>>>>>>>>>>> +sub _action_cl_save {
>>>>>>>>>>>>>>> + return unless((defined $cgiparams{'ALLOW_LIST'}) && (defined $cgiparams{'BLOCK_LIST'}));
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + my $result = 0;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + my @allowlist = split(/\R/, $cgiparams{'ALLOW_LIST'});
>>>>>>>>>>>>>>> + my @blocklist = split(/\R/, $cgiparams{'BLOCK_LIST'});
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Validate lists
>>>>>>>>>>>>>>> + $result = &_rpz_validate_customlist(\@allowlist);
>>>>>>>>>>>>>>> + if($result != 0) {
>>>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr(202, $result);
>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + $result = &_rpz_validate_customlist(\@blocklist);
>>>>>>>>>>>>>>> + if($result != 0) {
>>>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr(203, $result);
>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Add to global data hash and save changes
>>>>>>>>>>>>>>> + $customlists{'allow'}{'list'} = \@allowlist;
>>>>>>>>>>>>>>> + $customlists{'block'}{'list'} = \@blocklist;
>>>>>>>>>>>>>>> + $customlists{'allow'}{'enabled'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? 'on' : 'off';
>>>>>>>>>>>>>>> + $customlists{'block'}{'enabled'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? 'on' : 'off';
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + &_customlists_save_conf();
>>>>>>>>>>>>>>> + &_customlist_export('allow', $RPZ_ALLOWLIST);
>>>>>>>>>>>>>>> + &_customlist_export('block', $RPZ_BLOCKLIST);
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Make new lists, request service reload on success
>>>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-make', 'allowblock', '--no-reload');
>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Trigger rpz-config reload
>>>>>>>>>>>>>>> +sub _action_rpz_reload {
>>>>>>>>>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Immediately clear flag to prevent multiple reloads
>>>>>>>>>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>>>>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Perform reload, recreate reload flag on error to enable retry
>>>>>>>>>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'reload');
>>>>>>>>>>>>>>> + if(not &_rpz_check_result($result, 0)) {
>>>>>>>>>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Trigger unbound restart
>>>>>>>>>>>>>>> +sub _action_unb_restart {
>>>>>>>>>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Immediately clear flag to prevent multiple restarts
>>>>>>>>>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>>>>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Perform restart, unboundctrl always exits zero
>>>>>>>>>>>>>>> + &General::system('/usr/local/bin/unboundctrl', 'restart');
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +###--- Internal rpz-config functions ---###
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Translate rpz-config exitcodes and messages
>>>>>>>>>>>>>>> +# 100-199: rpz-config, 200-299: webgui
>>>>>>>>>>>>>>> +sub _rpz_error_tr {
>>>>>>>>>>>>>>> + my ($error, $append) = @_;
>>>>>>>>>>>>>>> + $append //= '';
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Translate numeric exit codes
>>>>>>>>>>>>>>> + if(looks_like_number($error)) {
>>>>>>>>>>>>>>> + if(defined $Lang::tr{"rpz exitcode $error"}) {
>>>>>>>>>>>>>>> + $error = $Lang::tr{"rpz exitcode $error"};
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + return "RPZ $Lang::tr{'error'}: $error" . &Header::escape($append);
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Check result of rpz-config system call, request reload on success
>>>>>>>>>>>>>>> +sub _rpz_check_result {
>>>>>>>>>>>>>>> + my ($result, $request_reload) = @_;
>>>>>>>>>>>>>>> + $request_reload //= 0;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # exitcode 0 = success
>>>>>>>>>>>>>>> + if($result != 0) {
>>>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr($result);
>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Set reload flag
>>>>>>>>>>>>>>> + if($request_reload) {
>>>>>>>>>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Test whether reload flag is set
>>>>>>>>>>>>>>> +sub _rpz_needs_reload {
>>>>>>>>>>>>>>> + return (-f $RPZ_RELOAD_FLAG);
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Validate a zonefile entry, returns rpz-config exitcode on failure. Use _rpz_check_result to verify.
>>>>>>>>>>>>>>> +# unique = check for unique name
>>>>>>>>>>>>>>> +sub _rpz_validate_zonefile {
>>>>>>>>>>>>>>> + my ($name, $url, $remark, $unique) = @_;
>>>>>>>>>>>>>>> + $unique //= 1;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + unless($name =~ /^[a-zA-Z0-9_]{1,32}$/) {
>>>>>>>>>>>>>>> + return 101;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + unless($url =~ /^[\w+\.:;\/\\&@#%?=\-~|!]{1,128}$/) {
>>>>>>>>>>>>>>> + return 105;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + unless($remark =~ /^[\w \-()\.:;*\/\\?!&=]{0,32}$/) {
>>>>>>>>>>>>>>> + return 201;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Check against already existing names
>>>>>>>>>>>>>>> + if($unique) {
>>>>>>>>>>>>>>> + foreach my $existing (keys %zonefiles) {
>>>>>>>>>>>>>>> + if(lc($name) eq lc($existing)) {
>>>>>>>>>>>>>>> + return 104;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + return 0;
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Validate a custom list, returns number of rejected line on failure. Check for non-zero results.
>>>>>>>>>>>>>>> +# listref = array reference, cleanup = remove invalid entries instead of returning an error
>>>>>>>>>>>>>>> +sub _rpz_validate_customlist {
>>>>>>>>>>>>>>> + my ($listref, $cleanup) = @_;
>>>>>>>>>>>>>>> + $cleanup //= 0;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + foreach my $index (reverse 0..$#{$listref}) {
>>>>>>>>>>>>>>> + my $row = @$listref[$index];
>>>>>>>>>>>>>>> + next unless($row); # Skip/allow empty lines
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Reject/remove everything besides wildcard domains and remarks
>>>>>>>>>>>>>>> + if((not &General::validwildcarddomainname($row)) && (not $row =~ /^;[\w \-()\.:;*\/\\?!&=]*$/)) {
>>>>>>>>>>>>>>> + unless($cleanup) {
>>>>>>>>>>>>>>> + # +1 for user friendly line number and to ensure non-zero exitcode
>>>>>>>>>>>>>>> + return $index + 1;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Remove current row
>>>>>>>>>>>>>>> + splice(@$listref, $index, 1);
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + return 0;
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +###--- Internal misc functions ---###
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Send HTTP 303 redirect headers for post/request/get pattern
>>>>>>>>>>>>>>> +# (Must be sent before calling &Header::showhttpheaders())
>>>>>>>>>>>>>>> +sub _http_prg_redirect {
>>>>>>>>>>>>>>> + my $location = "https://$ENV{'SERVER_NAME'}:$ENV{'SERVER_PORT'}$ENV{'SCRIPT_NAME'}";
>>>>>>>>>>>>>>> + print "Status: 303 See Other\n";
>>>>>>>>>>>>>>> + print "Location: $location\n";
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> diff --git a/lfs/rpz b/lfs/rpz
>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>> index 000000000..7ddbc38e5
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/lfs/rpz
>>>>>>>>>>>>>>> @@ -0,0 +1,96 @@
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +# Definitions
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +include Config
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +SUMMARY = response policy zone - RPZ reputation system for unbound DNS
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +VER = 1.0.0
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +THISAPP = rpz-$(VER)
>>>>>>>>>>>>>>> +DIR_APP = $(DIR_SRC)/$(THISAPP)
>>>>>>>>>>>>>>> +TARGET = $(DIR_INFO)/$(THISAPP)
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +PROG = rpz
>>>>>>>>>>>>>>> +PAK_VER = 18
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +DEPS =
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +SERVICES =
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +# Top-level Rules
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +install : $(TARGET)
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +check :
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +download :
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +b2 :
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +dist:
>>>>>>>>>>>>>>> + @$(PAK)
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +# Installation Details
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +$(TARGET) :
>>>>>>>>>>>>>>> + @$(PREBUILD)
>>>>>>>>>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # RPZ scripts
>>>>>>>>>>>>>>> + install --verbose --mode=755 \
>>>>>>>>>>>>>>> + $(DIR_CONF)/rpz/{rpz-config,rpz-metrics,rpz-sleep,rpz-make,rpz-functions} \
>>>>>>>>>>>>>>> + --target-directory=/usr/sbin
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # RPZ config files
>>>>>>>>>>>>>>> + mkdir -pv /etc/unbound/local.d
>>>>>>>>>>>>>>> + install --verbose --mode=644 --owner=nobody --group=nobody \
>>>>>>>>>>>>>>> + $(DIR_CONF)/rpz/00-rpz.conf \
>>>>>>>>>>>>>>> + --target-directory=/etc/unbound/local.d
>>>>>>>>>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/local.d
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # RPZ custom list files for allow and block
>>>>>>>>>>>>>>> + mkdir -pv /var/ipfire/dns/rpz
>>>>>>>>>>>>>>> + touch /var/ipfire/dns/rpz/{allowlist,blocklist}
>>>>>>>>>>>>>>> + chown --verbose --recursive nobody:nobody /var/ipfire/dns/rpz
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # RPZ zone files
>>>>>>>>>>>>>>> + # create empty RPZ config file to avoid a unbound config error
>>>>>>>>>>>>>>> + mkdir -pv /etc/unbound/zonefiles
>>>>>>>>>>>>>>> + touch /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/zonefiles
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Install addon-specific language-files
>>>>>>>>>>>>>>> + install --verbose --mode=004 $(DIR_CONF)/rpz/rpz.*.pl \
>>>>>>>>>>>>>>> + --target-directory=/var/ipfire/addon-lang
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Install backup definition
>>>>>>>>>>>>>>> + cp -vf $(DIR_CONF)/backup/includes/rpz /var/ipfire/backup/addons/includes/rpz
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>>>>>>>>>> + @$(POSTBUILD)
>>>>>>>>>>>>>>> diff --git a/make.sh b/make.sh
>>>>>>>>>>>>>>> index 827ea9e77..a77535b13 100755
>>>>>>>>>>>>>>> --- a/make.sh
>>>>>>>>>>>>>>> +++ b/make.sh
>>>>>>>>>>>>>>> @@ -390,7 +390,7 @@ prepareenv() {
>>>>>>>>>>>>>>> if [ "${free_space}" -lt "${required_space}" ]; then
>>>>>>>>>>>>>>> # Add any consumed space
>>>>>>>>>>>>>>> while read -r consumed_space path; do
>>>>>>>>>>>>>>> - (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>>>>>>>>>> + (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>>>>>>>>>> done <<< "$(du --summarize --bytes "${BUILD_DIR}" "${IMAGES_DIR}" "${LOG_DIR}" 2>/dev/null)"
>>>>>>>>>>>>>>> fi
>>>>>>>>>>>>>>> @@ -2087,6 +2087,7 @@ build_system() {
>>>>>>>>>>>>>>> lfsmake2 btrfs-progs
>>>>>>>>>>>>>>> lfsmake2 inotify-tools
>>>>>>>>>>>>>>> lfsmake2 grub-btrfs
>>>>>>>>>>>>>>> + lfsmake2 rpz
>>>>>>>>>>>>>>> lfsmake2 linux
>>>>>>>>>>>>>>> lfsmake2 rtl8812au
>>>>>>>>>>>>>>> diff --git a/src/paks/rpz/install.sh b/src/paks/rpz/install.sh
>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>> index 000000000..ef99bf742
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/src/paks/rpz/install.sh
>>>>>>>>>>>>>>> @@ -0,0 +1,36 @@
>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +#
>>>>>>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>>>>>>> +extract_files
>>>>>>>>>>>>>>> +restore_backup ${NAME}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# fix user created files
>>>>>>>>>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>>>>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>>>>>>>>>> + /etc/unbound/zonefiles \
>>>>>>>>>>>>>>> + /etc/unbound/local.d
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Update Language cache
>>>>>>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# restart unbound to load config file
>>>>>>>>>>>>>>> +/etc/init.d/unbound restart
>>>>>>>>>>>>>>> diff --git a/src/paks/rpz/uninstall.sh b/src/paks/rpz/uninstall.sh
>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>> index 000000000..e11427df3
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/src/paks/rpz/uninstall.sh
>>>>>>>>>>>>>>> @@ -0,0 +1,38 @@
>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +#
>>>>>>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>>>>>>>>>> +/etc/init.d/unbound stop
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +make_backup ${NAME}
>>>>>>>>>>>>>>> +remove_files
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>>>>>>>>>> +# "[1723428668] unbound-control[17117:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>>>>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Update Language cache
>>>>>>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# start unbound to load unbound config file
>>>>>>>>>>>>>>> +/etc/init.d/unbound start
>>>>>>>>>>>>>>> diff --git a/src/paks/rpz/update.sh b/src/paks/rpz/update.sh
>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>> index 000000000..9bc340bc6
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/src/paks/rpz/update.sh
>>>>>>>>>>>>>>> @@ -0,0 +1,52 @@
>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +#
>>>>>>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# from update.sh
>>>>>>>>>>>>>>> +extract_backup_includes
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>>>>>>>>>> +/etc/init.d/unbound stop
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# from uninstall.sh
>>>>>>>>>>>>>>> +make_backup ${NAME}
>>>>>>>>>>>>>>> +remove_files
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>>>>>>>>>> +# "unbound-control[nn:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>>>>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# from install.sh
>>>>>>>>>>>>>>> +extract_files
>>>>>>>>>>>>>>> +restore_backup ${NAME}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# fix user created files
>>>>>>>>>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>>>>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>>>>>>>>>> + /etc/unbound/zonefiles \
>>>>>>>>>>>>>>> + /etc/unbound/local.d
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Update Language cache
>>>>>>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# restart unbound to load config files
>>>>>>>>>>>>>>> +/etc/init.d/unbound start
>>>>>>>>>>>>>>> --
>>>>>>>>>>>>>>> 2.39.5
>>>>>>>>>>>>>>>
>>>>>>>>>>>>> Jon
>>>>>>>>>>>>> --
>>>>>>>>>>>>> Jon Murphy
>>>>>>>>>>>>> jon.murphy@ipfire.org
>>>>>>>>>>>
>>>>>>>>>>> Jon
>>>>>>>>>>> --
>>>>>>>>>>> Jon Murphy
>>>>>>>>>>> jon.murphy@ipfire.org
>
>
>
[-- Attachment #2: Type: text/html, Size: 193990 bytes --]
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
2025-03-24 14:33 ` Re[2]: " Jon Murphy
@ 2025-03-24 14:36 ` Michael Tremer
2025-03-24 14:38 ` Re[2]: " Jon Murphy
2025-03-24 14:49 ` Bernhard Bitsch
0 siblings, 2 replies; 30+ messages in thread
From: Michael Tremer @ 2025-03-24 14:36 UTC (permalink / raw)
To: Jon Murphy; +Cc: Bernhard Bitsch, IPFire: Development-List
Unbound did not put those there...
> On 24 Mar 2025, at 14:33, Jon Murphy <jon.murphy@ipfire.org> wrote:
>
>
>
> And where are these stored?
>
> In `/etc/unbound/zonefiles`:
>
>
> [root@ipfire ~] # ls -al /etc/unbound/zonefiles
> total 20664
> drwxr-xr-x 2 nobody nobody 4096 Mar 24 04:40 .
> drwxr-xr-x 4 root root 4096 Mar 19 16:24 ..
> -rw-r--r-- 1 nobody nobody 3999087 Mar 23 15:11 adhocSB.rpz
> -rw-r--r-- 1 nobody nobody 1411 Mar 23 14:23 allow.rpz
> -rw-r--r-- 1 nobody nobody 25355 Mar 24 04:40 AmazonTrkrHZ.rpz
> -rw-r--r-- 1 nobody nobody 7241 Mar 24 04:40 AppleTrkrHZ.rpz
> -rw-r--r-- 1 nobody nobody 178 Mar 23 14:23 block.rpz
> -rw-r--r-- 1 nobody nobody 78496 Mar 24 04:40 DOHblockHZ.rpz
> -rw-r--r-- 1 nobody nobody 16983551 Mar 24 04:40 MxProPlusHZ.rpz
> -rw-r--r-- 1 nobody nobody 2893 Mar 24 04:40 tldHZ.rpz
> -rw-r--r-- 1 nobody nobody 29419 Mar 24 04:40 WinTrkrHZ.rpz
> [root@ipfire ~] #
>
>
>
> ------ Original Message ------
> From "Michael Tremer" <michael.tremer@ipfire.org>
> To "Bernhard Bitsch" <bbitsch@ipfire.org>
> Cc development@lists.ipfire.org
> Date 3/24/2025 9:25:40 AM
> Subject Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
>
>> Hello,
>>
>>>
>>> On 24 Mar 2025, at 13:33, Bernhard Bitsch <bbitsch@ipfire.org> wrote:
>>> Am 24.03.2025 um 11:17 schrieb Michael Tremer:
>>>>
>>>>
>>>> Hello Jon,
>>>>>
>>>>>
>>>>> On 24 Mar 2025, at 00:00, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>> Michael,
>>>>> FYI - I was wrong Unbound RPZ is _not_ watching the serial number, it is watching the "refresh", the number after the serial number.
>>>>
>>>> Refresh just tells the client how often to check for an update.
>>>> If that is actually being set by the list publisher, then we have another problem here, because they could put some insanely low value there and we would then DDoS their infrastructure. I think we should keep it like we have it in other places that we control how often we want to check or pull for updates.
>>>>
>>> You are right. But an extra update process wastes additional processor time. The update mechanism of unbound does the check for update ( however it is realized ) nevertheless.
>>
>> Yes, doing more things needs resources. But we are not seriously considering whether an IPFire system has enough resources to perform the download of a text file, or are we?
>>
>>>
>>>>> I understand that you don’t speak C, but you got the information from somewhere. Documentation maybe? Since that is out of date very often I like to consult the code.
>>>>> From testing. Downloading rpz files using rpz unbound, and watching what happens. If the rpz file is setup for "once per day" refresh, then it only downloads one time.
>>>>> However that won’t solve our problem . . . and having no cache.
>>>>> In `/etc/unbound/tuning.conf` there is `rrset-cache-size: 128m`. Are you referring to a different cache.
>>>>
>>>> Naturally unbound is loading the zone into its memory which we generally call cache.
>>>> When I say cache I am thinking about persistent data storage across multiple restarts of Unbound. If I am downloading 100 MiB of RPZ lists (which is presumably still on the lower end) and I reboot my firewall, I do not want to download the same data again. We can only ever download a list *once* unless we are 100% certain that it has changed. Then we can download it once again.
>>>
>>> The RPZ lists are stored in files in persistent storage. Unbound creates the internal cache from these.
>>
>> And where are these stored?
>>
>>>
>>>>> Maybe we need to implement both?
>>>>> Yes. There are very few AXFR list (I think only four were found). And many more HTTPS rpz files.
>>>>> Jon
>>>>> ------ Original Message ------
>>>>> From "Michael Tremer" <michael.tremer@ipfire.org>
>>>>> To "Jon Murphy" <jon.murphy@ipfire.org>
>>>>> Cc "IPFire: Development-List" <development@lists.ipfire.org>
>>>>> Date 3/20/2025 11:26:43 AM
>>>>> Subject Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
>>>>>
>>>>>>
>>>>>> Hello Jon,
>>>>>> Please don’t forget to Cc the list...
>>>>>>
>>>>>>>
>>>>>>> On 19 Mar 2025, at 18:27, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>> Michael,
>>>>>>>
>>>>>>>>
>>>>>>>> Where in the code is this implemented? I cannot find anything like this:
>>>>>>>
>>>>>>> Keep in mind I am not a "C" person. Maybe in this section?:
>>>>>>> https://git.ipfire.org/?p=thirdparty/unbound.git;a=blob;f=services/authzone.c;hb=30b9cb5f813003d0a2b1c2e678652396615b1b7d#l5875
>>>>>>
>>>>>> This where the AXFR response is being handled when doing a DNS zone transfer. This code is not being called when performing a HTTP download.
>>>>>> I understand that you don’t speak C, but you got the information from somewhere. Documentation maybe? Since that is out of date very often I like to consult the code.
>>>>>>
>>>>>>>
>>>>>>> —
>>>>>>> When I was just learning about RPZ I created a separate RPZ file for testing. When I changed the SOA line with a new serial number, the RPZ file download would happen in about 5 minutes.
>>>>>>> https://people.ipfire.org/~jon/sblack-adhoc.rpz
>>>>>>
>>>>>> It might well be that the file is not being reloaded if the download matches the content that unbound already has. That would of course save some resources.
>>>>>> However that won’t solve our problem with redundant downloads and having no cache.
>>>>>>
>>>>>>>
>>>>>>> That is how I found out the SOA line is watched for a serial number change.
>>>>>>> I’ll reconfirm my findings.
>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>>
>>>>>>>>>> The second reason is that we have a lot of firewalls out there. Not all of them will enable this feature and all of the lists, but even if it is a good chunk, we will generate terabytes of traffic which put load on the infrastructure and will cost money. It simply is not what we want to do, regardless of self-hosting those lists and pulling them from somewhere else.
>>>>>>>
>>>>>>> So I understand, are you thinking of hosting RPZ AXFR (DNS zone transfer) on IPFire infrastructure?
>>>>>>
>>>>>> No, I don’t think that we can generally do this. The biggest problem is licensing as we cannot take anyones content and host it ourselves. We would re-distribute those lists and that will only work with permission of the publishers. I assume that would be too much work to actually get some useful content out there. We might limit ourselves to only those lists that are under a very permissive license. Nobody wants that.
>>>>>> From a technical point of view, DNS over TCP might not be very nice in terms of forging the transfer and so we would need TLS as well… It should work, but even if we would be able to encourage other people to publish their lists I doubt they would implement DNS over TLS for authoritative DNS. That standard is in very early stages as well.
>>>>>> As far as I can see, those vendors who offer a list as a commercial product are using DNS to distribute it (e.g. Spamhaus). Those people who have made this all a hobby are throwing the lists onto GitHub and let them handle the traffic.
>>>>>> Maybe we need to implement both?
>>>>>> -Michael
>>>>>>
>>>>>>>
>>>>>>> Jon
>>>>>>> On 3/19/25 5:35 AM, Michael Tremer wrote:
>>>>>>>>
>>>>>>>>
>>>>>>>> Hello Jon,
>>>>>>>> Where in the code is this implemented? I cannot find anything like this:
>>>>>>>> Unbound loads the entire file into memory and then starts parsing it. The only special treatment there is is to check whether the first line is a valid zone entry. It does not even have to be a SOA record.
>>>>>>>> https://git.ipfire.org/?p=thirdparty/unbound.git;a=blob;f=services/authzone.c;hb=30b9cb5f813003d0a2b1c2e678652396615b1b7d#l1188
>>>>>>>> I am also concerned that Unbound will not be able to support an upstream proxy for any downloads. The caching situation is also unclear for me, so I believe that we will be looking at writing a custom downloader that implements all these things.
>>>>>>>> -Michael
>>>>>>>>
>>>>>>>>>
>>>>>>>>> On 19 Mar 2025, at 02:58, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>>> Michael,
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> The emphasis is on the repeated downloads of the same list. That is
>>>>>>>>>
>>>>>>>>> > what cannot happen.
>>>>>>>>> The Unbound RPZ code, as installed within IPFire, watches for a change
>>>>>>>>> in the SOA line of each RPZ file. This is an example of the first few
>>>>>>>>> lines for every RPZ file.
>>>>>>>>> $TTL 300
>>>>>>>>> @ SOA localhost. root.localhost. 1742298960 43200 3600 86400 300
>>>>>>>>> NS localhost.
>>>>>>>>> ;
>>>>>>>>> ; Title: HaGeZi's Pop-Up Ads DNS Blocklist
>>>>>>>>> ; Description: Blocks annoying and malicious pop-up ads.
>>>>>>>>> If the SOA serial number changes (e.g. the 1742298960), then Unbound RPZ
>>>>>>>>> code does its thing and downloads. Otherwise there is no download.
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> So there has to be a way to ensure that we won’t download a list again
>>>>>>>>>
>>>>>>>>> > unless it has actually changed.
>>>>>>>>> This should do what you want but I may be missing your point.
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> DNS has a builtin functionality called AXFR. It simply does the job
>>>>>>>>>
>>>>>>>>> > for you. I was just wondering whether that was not being used.
>>>>>>>>> I need to read about AXFR/IXFR and learn a little more.
>>>>>>>>> Jon
>>>>>>>>> On 3/17/25 5:35 AM, Michael Tremer wrote:
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> Good Morning Jon,
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> On 16 Mar 2025, at 17:00, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>>>>> Michael,
>>>>>>>>>>> I was reading through you response again an I want to understand this post:
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>> I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>>>>>>>>>>
>>>>>>>>>>> So if RPZ doesn't use HTTPS, what is it using? I am missing a key point here.
>>>>>>>>>>
>>>>>>>>>> The emphasis is on the repeated downloads of the same list. That is what cannot happen.
>>>>>>>>>> Although it might not affect a lot of people in our general user-base, there are some that have a metered connection and will pay for data by volume. Some of the lists I looked at are just under 20 MiB. Therefore we need to keep any traffic down to a minimum. The second reason is that we have a lot of firewalls out there. Not all of them will enable this feature and all of the lists, but even if it is a good chunk, we will generate terabytes of traffic which put load on the infrastructure and will cost money. It simply is not what we want to do, regardless of self-hosting those lists and pulling them from somewhere else.
>>>>>>>>>> So there has to be a way to ensure that we won’t download a list again unless it has actually changed.
>>>>>>>>>> DNS has a builtin functionality called AXFR. It simply does the job for you. I was just wondering whether that was not being used.
>>>>>>>>>> HTTPS is an option because that is simply what we use elsewhere, but extra functionality will have to be built for it.
>>>>>>>>>> -Michael
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> Jon
>>>>>>>>>>> On 2/13/25 3:34 PM, jon wrote:
>>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>> Michael,
>>>>>>>>>>>> I’ve read through your comments a few times and I ended up with many more questions.
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>> What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>>>>>>>>>>
>>>>>>>>>>>> To me the efforts to get new code accepted seem to have changed and it seemed easier in the past. In the past I made the Core Team aware via the Dev Mailing List and wrote a simple two or three paragraphs of "What is it? / What is the value? / Here is the code"
>>>>>>>>>>>> So in an effort to move forward: How exactly is something presented to the Core Team?
>>>>>>>>>>>> Is there an example of a recent effort that was presented that I can see as a sample? (This type of info can also be added to the Wiki)
>>>>>>>>>>>> I understand you want it this way, but I don’t know what exactly is needed. Please be specific.
>>>>>>>>>>>> Jon
>>>>>>>>>>>> PS - I am not ignoring your other comments, I am just trying to move forward and keep things simple.
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>> On Feb 8, 2025, at 1:27 PM, Michael Tremer <michael.tremer@ipfire.org> wrote:
>>>>>>>>>>>>> Hello Jon,
>>>>>>>>>>>>> Thanks for your reply. And good that you are copying everyone into this conversation.
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> On 8 Feb 2025, at 18:41, jon <jon.murphy@ipfire.org> wrote:
>>>>>>>>>>>>>> Michael,
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Yes, this has been a separate effort (a very public separate effort). Yes, as you pointed this out early on with the "proof-of-concept" and then my request for people to help test RPZ. Nothing was hidden.
>>>>>>>>>>>>>> This was done because you (and maybe others) did not have the time and I wanted to help and because I needed assistance with RPZ. I tried my best to do this without bothering you.
>>>>>>>>>>>>>
>>>>>>>>>>>>> I don’t that it is accurate that nobody wanted to help on this. The list was always open - although not every email has been replied to swiftly it is also your responsibility to raise a question again if it was missed. People here have open ears.
>>>>>>>>>>>>> It was also stated on this very list on in our documentation that working on something without involving the core team is a risky undertaking. Of course IPFire is free software and so everyone is free to fork if they wish to do so.
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> You were aware many steps along the way. See your email on July 28, 2024, August 15, 2024, September 30, 2024, December 23, 2024, and January 16. My attempts to get the team involved were met with "things are busy" and sometimes silence. (Yes, I get it, people are busy.)
>>>>>>>>>>>>>> You and Adolf, Leo, Erik and Bernhard have been aware since the beginning. You mention you were aware of the "proof-of-concept". If you include those beginning posts, since Sep 2023.
>>>>>>>>>>>>>
>>>>>>>>>>>>> Yes, I am aware of a proof-of-concept that I have been running myself for a long time. I am also aware of the efforts that you have been taking.
>>>>>>>>>>>>> Yet I don’t think there has ever been any joint effort, or am I seeing that wrong?
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> This has not been discussed . . . on our calls.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> On the July 28th you stated:
>>>>>>>>>>>>>> "We have talked about RPZ many times on the monthly call since the URL filter feature is falling more and more out of fashion. I think there is also many posts about this on the forum."
>>>>>>>>>>>>>> Please don’t insult me again by stating "you know what I mean".
>>>>>>>>>>>>>> And it has been discussed but not documented in the Monthly Meeting notes.
>>>>>>>>>>>>>
>>>>>>>>>>>>> I am not at all insulting you. I don’t want to take this down to a personal level at all. This is a public mailing list and people who read this don’t need to listen to an argument we are having. They are here for the tech inside IPFire.
>>>>>>>>>>>>> When I wrote that it has not been discussed that does not mean that we have not been touching on the topic. We have been talking about lots of things on the calls, the weather, politics, how our pets are. None of that makes it to the logs. What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Regular conversation on the Dev Mailing list is many times met with silence. I get it, people are busy.
>>>>>>>>>>>>>> And regular two-way conversation doesn’t happen on the list. At least not with me. I’d be happy to point out the posts that were met with silence.
>>>>>>>>>>>>>> Again, I get it, people are busy.
>>>>>>>>>>>>>
>>>>>>>>>>>>> And you think my emails are not being met with silence? This has nothing to do with this specific topic. This has something to do with how occupied people are and how engaged they are on certain topics. Not everyone is involved in all the things and simply will ignore emails simply based on their subject line.
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> But the "dip here to the list" were my attempts to get a conversation started. As I said, many time met with silence.
>>>>>>>>>>>>>> The only place I was not met with silence was on the Community. You have a great group of people in the Community. It is a shame you don’t want to have others help. It would reduce your workload.
>>>>>>>>>>>>>
>>>>>>>>>>>>> You should stop making statements that are not true. Who doesn’t want anyone to help?
>>>>>>>>>>>>> Not having this conversation on a Saturday evening would reduce my workload. At least it would free up time for something else. Helping with the things that are already on the go would reduce the workload of the entire team. Starting one thing at a time and finishing it is a lot better to manage than starting a hundred things and not even finish one. I can tell you that I already have a hundred things on the go.
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> Therefore, what am I supposed to do with this email?
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> To me it is beyond obvious…
>>>>>>>>>>>>>> If it isn’t what you want, then guide me with how to do this the correct way. And be specific. I am trying to help. I am trying to make things better. I am trying to do things the right way.
>>>>>>>>>>>>>
>>>>>>>>>>>>> To me it isn’t. This is yet another project that has been dumped to the list like so many before and later on everyone has left to have the team deal with the rest.
>>>>>>>>>>>>> It is a huge patch set. You explained what the vision is, but that is about it. There is no chance this will continue if this disagreement isn’t solved first. I didn’t even look at the code.
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> I don’t want to merge code that I don’t agree with.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> I asked multiple times if you "agreed with the concept" and again, met with silence. Yes I get it, people are busy.
>>>>>>>>>>>>>
>>>>>>>>>>>>> Having support for RPZ? Yes, it was definitely on the roadmap. That I agree with.
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> You mentioned this a in the past, but for some reason you do not disclose what I dismissed. Why do you continue to make this harder, wouldn’t it not be easier to tell me what I have dismissed?
>>>>>>>>>>>>>> I have sent multiple emails trying to answer your concerns and comments. On July 28, Aug 14, Aug 22, Aug 23, Sep 30, etc.
>>>>>>>>>>>>>> I’ve gone through all of the questions you asked and I cannot find a "dismissed" item.
>>>>>>>>>>>>>
>>>>>>>>>>>>> Maybe I need to be *more clear*. I feel humoured by this.
>>>>>>>>>>>>> It is late on a Saturday and I want my dinner soon, but certainly I have stated that this should never be an add-on considering it is supposed to replace URL Filter. We should never allow people to add their own sources. I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> The maintainers of Unbound and/or RPZ?
>>>>>>>>>>>>>> The maintainers of Hagezi list, the threatfox list, the urlhaus list, etc.?
>>>>>>>>>>>>>> What else? The maintainers or the RPZ scripts? That is me. Let’s talk!
>>>>>>>>>>>>>
>>>>>>>>>>>>> You. I don’t care much about the providers of the lists.
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> See, this is where it gets confusing. There are hundreds of open source packages as part of IPFire. Pick the last five years of items added to the IPFire build. You're telling me you have "constructive conversation with the maintainers" of all of the added packages?
>>>>>>>>>>>>>
>>>>>>>>>>>>> They publish their software and they don’t care whether I am pulling it or not. They publish it with the commitment to maintain it - sometimes for better and sometimes for worse.
>>>>>>>>>>>>> You care about me pulling your code and I don’t know whether you would commit to maintain this.
>>>>>>>>>>>>> These two are very different cases.
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Pick the IP Blocklists list (i.e., 3CORESEC, ABUSECH, DSHIELD, SPAMHAUS, etc.) or the Suricata lists (i.e.,Emergingthreats.net <http://emergingthreats.net/>,Abuse.ch <http://abuse.ch/>, etc.). So you’ve have "constructive conversation with the maintainers"?
>>>>>>>>>>>>>
>>>>>>>>>>>>> Yes, occasionally I have phone calls with a few of these providers.
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Ha! Yes a surprise. In the beginning you seemed interested as IPFire needed a replacement for URL Filter. You asked good questions about the lists picked, asked for the value to the users, etc. And I answered the best I could.
>>>>>>>>>>>>>> You even asked: “Why is this realised as an add-on and not part of the core system?” from your Jul 28, 2024 email.
>>>>>>>>>>>>>
>>>>>>>>>>>>> Ah, so, why is the patch creating an add-on? Not that I am saying that what I say is law, but it has not been challenged either. If my input is being ignored, why should I put this to the top of my list of priorities? I am not disappointed about this, just trying to be very good with my time.
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> And on January 16, 2025 I wrote a message looking for help. And you were kind to respond quickly. So in three weeks time, since the kind response, something has changed. You went from supportive to "this".
>>>>>>>>>>>>>> So yes, I am surprised.
>>>>>>>>>>>>>
>>>>>>>>>>>>> Well, maybe I should not have replied to that email. It was clear that you were on some path that was not right, but you were not interested before in finding the right path from the beginning.
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Be more specific, what has to change? What exactly did I dismiss?
>>>>>>>>>>>>>
>>>>>>>>>>>>> Dismissal is just my assumption. I don’t know what you actually did with my feedback. I can only see the end product that does not seem contain much of it. Repeatedly I have been pointing out that we should think before we build. I am sure a lot of hours have now gone into some code that simply does not satisfy me. And I am not not talking about the code itself, what it does is what I don’t think is right for us.
>>>>>>>>>>>>> The process is very clear for me that we should first of all think whether we want a certain feature now. Then there should be a clear roadmap for everyone to follow; tasks can be split-up as we go and hopefully then have something that is maintainable, interesting for our users and even would do us proud. This is how this should work.
>>>>>>>>>>>>> So, what has to change? I don’t think with shouting at each other, throwing patches around and making me generally unhappy is a good start.
>>>>>>>>>>>>> -Michael
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Jon
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> On Feb 6, 2025, at 2:13 PM, Michael Tremer <michael.tremer@ipfire.org> wrote:
>>>>>>>>>>>>>>> Hello Jon,
>>>>>>>>>>>>>>> Well, here we are again with another patch regarding this feature.
>>>>>>>>>>>>>>> I cannot quite see from your email what the question is, but if this is a request to have this merged into IPFire, I am once again sorry to disappoint you.
>>>>>>>>>>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls. Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation. Therefore, what am I supposed to do with this email?
>>>>>>>>>>>>>>> I don’t want to merge code that I don’t agree with. So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>>>>>>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>>>>>>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>>>>>>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>>>>>>>>>>> All the best,
>>>>>>>>>>>>>>> -Michael
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> On 6 Feb 2025, at 16:35, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>>>>>>>>>> What is it?
>>>>>>>>>>>>>>>> Response Policy Zone (RPZ) is a mechanism to define local policies in a
>>>>>>>>>>>>>>>> standardized way and load those policies from external sources.
>>>>>>>>>>>>>>>> Bottom line: RPZ allows admins to easily block access to websites via DNS lookup.
>>>>>>>>>>>>>>>> RPZ can block websites via categories. Examples include: fake websites, annoying
>>>>>>>>>>>>>>>> pop-up ads, newly registered domains, DoH bypass sites, bad "host" services,
>>>>>>>>>>>>>>>> maliscious top level domains (e.g., *.zip, *.mov), piracy, gambling, pornography,
>>>>>>>>>>>>>>>> and more. RPZ lists come from various RPZ providers and their available
>>>>>>>>>>>>>>>> catagories.
>>>>>>>>>>>>>>>> This RPZ add-on enables the RPZ functionality by adding a couple lines in a
>>>>>>>>>>>>>>>> configuration file. This add-on simply adds configuration files and adds
>>>>>>>>>>>>>>>> scripts (config, metrics and sleep) to make RPZ easier for the admin to use.
>>>>>>>>>>>>>>>> The RPZ scripts include additional languages: German, Spanish, French, Turkish,
>>>>>>>>>>>>>>>> and Italian.
>>>>>>>>>>>>>>>> RPZ itself was release in 2010 and has been part of the IPFire build since ~2015.
>>>>>>>>>>>>>>>> Why is it needed? What is its value?
>>>>>>>>>>>>>>>> - The RPZ concept places this filtering into IPFire, our internet access
>>>>>>>>>>>>>>>> gateway, which is (should be) solely used as DNS source of the internal network.
>>>>>>>>>>>>>>>> - As most sites use HTTPS it makes it difficult to filter traffic with URL
>>>>>>>>>>>>>>>> Filter without also properly configuring conventional (non-transparent)
>>>>>>>>>>>>>>>> mode on the proxy. RPZ is a nice replacement for the URL Filter.
>>>>>>>>>>>>>>>> - No need to install and maintain an additional device like PiHole or AdBlock
>>>>>>>>>>>>>>>> browser extensions on multiple user devices.
>>>>>>>>>>>>>>>> - This is an additional layer of protection for users. Less worry someone will
>>>>>>>>>>>>>>>> click on something that gets them into trouble. And, saying this with emphasis,
>>>>>>>>>>>>>>>> the ability to do it in one place!
>>>>>>>>>>>>>>>> - Blocked sites save on unneeded traffic and can lessen the threat of malware
>>>>>>>>>>>>>>>> in advertisements
>>>>>>>>>>>>>>>> - Logging allows the admin to see the site blocked and take actions
>>>>>>>>>>>>>>>> - RPZ will be used at the home, home-office (work from home), schools,
>>>>>>>>>>>>>>>> ministerial, and at the office. Device counts are small (2-6) to medium (~80)
>>>>>>>>>>>>>>>> to mediam-large (200+).
>>>>>>>>>>>>>>>> - RPZ can block ads, popups, phishing, scammers, spyware, malware, annoying
>>>>>>>>>>>>>>>> popups, NSFW links, DOH servers, and the usual internet trash.
>>>>>>>>>>>>>>>> ------------------------------
>>>>>>>>>>>>>>>> Change Log for RPZ add-on
>>>>>>>>>>>>>>>> rpz-1.0.0-18 on 2025-02-05
>>>>>>>>>>>>>>>> - Build for approval & release as IPFire add-on
>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>> rpz-beta-0.1.18-18.ipfire on 2025-02-01
>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>> - new feature: added a mod key to force a unbound restart
>>>>>>>>>>>>>>>> rpz-config and rpz-make:
>>>>>>>>>>>>>>>> - new feature: added action for unbound restart `rpz-config unbound-restart`
>>>>>>>>>>>>>>>> rpz-metrics:
>>>>>>>>>>>>>>>> - simple reformatting
>>>>>>>>>>>>>>>> - rename far right column from "last update" to "last download"
>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>> rpz-beta-0.1.17-17.ipfire on 2024-12-09
>>>>>>>>>>>>>>>> rpz-make
>>>>>>>>>>>>>>>> - bug fix: corrected validation regex for wildcards like: `*.domain.com`
>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>> rpz-beta-0.1.16-16.ipfire on 2024-11-18
>>>>>>>>>>>>>>>> rpz-make
>>>>>>>>>>>>>>>> - new feature: updated validation regex
>>>>>>>>>>>>>>>> - bug fix: moved validation to beginning of process. Now we validate before
>>>>>>>>>>>>>>>> creating config files.
>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>> - new feature: use CSS color variables of the main ipfire theme
>>>>>>>>>>>>>>>> - bug fix: empty zonefile remarks were stored as “undef” and caused a warning
>>>>>>>>>>>>>>>> - bug fix: HTML textarea removes the first empty line in a custom list
>>>>>>>>>>>>>>>> - thank you Leo!
>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>> rpz-beta-0.1.15-15.ipfire on 2024-11-04
>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>> - new feature: added new language file for Turkish (thank you Peppe)
>>>>>>>>>>>>>>>> rpz-make
>>>>>>>>>>>>>>>> - bug fix: corrected empty allow/block list issue. An empty allow/block list
>>>>>>>>>>>>>>>> will now remove contents of allow/block.rpz files and remove unneeded
>>>>>>>>>>>>>>>> allow/block.conf file. (thank you iptom)
>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>> rpz-beta-0.1.14-14.ipfire on 2024-10-29
>>>>>>>>>>>>>>>> rpz-config:
>>>>>>>>>>>>>>>> - bug fix: correct missing rpz extension. `rpz-config list` displayed URL
>>>>>>>>>>>>>>>> incorrectly (thank you Bernhard)
>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>> - bug fix: remove extra `"` in language files (thank you Bernhard)
>>>>>>>>>>>>>>>> - new feature: slightly dim "apply" button when not enabled
>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>> rpz-beta-0.1.13-13.ipfire on 2024-10-27
>>>>>>>>>>>>>>>> - skipped
>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>> rpz-beta-0.1.12-12.ipfire on 2024-10-21
>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>> - new feature: added new language file for French (thank you gw-ipfire)
>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>> rpz-beta-0.1.11-11.ipfire on 2024-10-18
>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>> - new feature: added new language file for Italian (thank you umberto)
>>>>>>>>>>>>>>>> - new feature: added new language file for Spanish (thank you Roberto)
>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>> rpz-beta-0.1.10-10.ipfire on 2024-10-15
>>>>>>>>>>>>>>>> rpz-make:
>>>>>>>>>>>>>>>> - bug fix: corrected validation error for a custom list entry (thank you siosios)
>>>>>>>>>>>>>>>> - e.g., `*.cloudflare-dns.com`
>>>>>>>>>>>>>>>> install.sh:
>>>>>>>>>>>>>>>> - bug fix: add chown to correct user created files
>>>>>>>>>>>>>>>> update.sh:
>>>>>>>>>>>>>>>> - bug fix: add chown to correct user created files (thank you siosios)
>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>> rpz-beta-0.1.9-9.ipfire on 2024-10-08
>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>> - new feature: added new language file for German (thank you Leo)
>>>>>>>>>>>>>>>> - bug fix: add missing "rpz exitcode 110"
>>>>>>>>>>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>> rpz-beta-0.1.8-8.ipfire on 2024-10-04
>>>>>>>>>>>>>>>> - skipped
>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>> rpz-beta-0.1.7-7.ipfire on 2024-10-03
>>>>>>>>>>>>>>>> All:
>>>>>>>>>>>>>>>> - new feature: includes beta version numbers for pakfire package,
>>>>>>>>>>>>>>>> instead of only `rpz-1.0.0-1.ipfire`, for each release.
>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>> - new feature: added new WebGUI at `rpz.cgi`
>>>>>>>>>>>>>>>> - a BIG thank you to Leo Hofmann for all of his work creating the webgui!!
>>>>>>>>>>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>>>>>>>>>> rpz-make:
>>>>>>>>>>>>>>>> - new feature: validate entries in allowlist and blocklist
>>>>>>>>>>>>>>>> - new feature: add "no-reload" option for WebGUI
>>>>>>>>>>>>>>>> rpz-metrics:
>>>>>>>>>>>>>>>> - new feature: info can be sorted by name, by hit count, by line count, by
>>>>>>>>>>>>>>>> "enabled" list or all lists
>>>>>>>>>>>>>>>> backups:
>>>>>>>>>>>>>>>> - bug fix: include all files in `/var/ipfire/dns/rpz` directory in backup
>>>>>>>>>>>>>>>> update.sh:
>>>>>>>>>>>>>>>> - bug fix: corrected ownership for `/var/ipfire/dns/rpz` directory during an
>>>>>>>>>>>>>>>> update
>>>>>>>>>>>>>>>> Build:
>>>>>>>>>>>>>>>> - bug fix: `block.rpz.conf` and `block.rpz` from build. Files to be created
>>>>>>>>>>>>>>>> by `rpz-make`
>>>>>>>>>>>>>>>> WebGUI and German language file
>>>>>>>>>>>>>>>> Contribution-by: Leo-Andres Hofmann <hofmann@leo-andres.de>
>>>>>>>>>>>>>>>> Spanish language file
>>>>>>>>>>>>>>>> Contribution-by: Roberto Peña
>>>>>>>>>>>>>>>> Italian language file
>>>>>>>>>>>>>>>> Contribution-by: Umberto Parma
>>>>>>>>>>>>>>>> French language file
>>>>>>>>>>>>>>>> Contribution-by: gw-ipfire
>>>>>>>>>>>>>>>> Turkish language file
>>>>>>>>>>>>>>>> Contribution-by: Peppe Tech
>>>>>>>>>>>>>>>> Contribution-by: Bernhard Bitsch <bbitsch@ipfire.org>
>>>>>>>>>>>>>>>> Contribution-by: Erik Kapfer <erik.kapfer@ipfire.org>
>>>>>>>>>>>>>>>> Signed-off-by: Jon Murphy <jon.murphy@ipfire.org
>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>> config/backup/includes/rpz | 4 +
>>>>>>>>>>>>>>>> config/cfgroot/manualpages | 1 +
>>>>>>>>>>>>>>>> config/menu/EX-rpz.menu | 6 +
>>>>>>>>>>>>>>>> config/rootfiles/common/configroot | 1 +
>>>>>>>>>>>>>>>> config/rootfiles/common/web-user-interface | 1 +
>>>>>>>>>>>>>>>> config/rootfiles/packages/rpz | 20 +
>>>>>>>>>>>>>>>> config/rpz/00-rpz.conf | 10 +
>>>>>>>>>>>>>>>> config/rpz/rpz-config | 130 +++
>>>>>>>>>>>>>>>> config/rpz/rpz-functions | 85 ++
>>>>>>>>>>>>>>>> config/rpz/rpz-make | 203 +++++
>>>>>>>>>>>>>>>> config/rpz/rpz-metrics | 170 ++++
>>>>>>>>>>>>>>>> config/rpz/rpz-sleep | 58 ++
>>>>>>>>>>>>>>>> config/rpz/rpz.de.pl | 30 +
>>>>>>>>>>>>>>>> config/rpz/rpz.en.pl | 30 +
>>>>>>>>>>>>>>>> config/rpz/rpz.es.pl | 30 +
>>>>>>>>>>>>>>>> config/rpz/rpz.fr.pl | 30 +
>>>>>>>>>>>>>>>> config/rpz/rpz.it.pl | 30 +
>>>>>>>>>>>>>>>> config/rpz/rpz.tr.pl | 30 +
>>>>>>>>>>>>>>>> html/cgi-bin/rpz.cgi | 923 +++++++++++++++++++++
>>>>>>>>>>>>>>>> lfs/rpz | 96 +++
>>>>>>>>>>>>>>>> make.sh | 3 +-
>>>>>>>>>>>>>>>> src/paks/rpz/install.sh | 36 +
>>>>>>>>>>>>>>>> src/paks/rpz/uninstall.sh | 38 +
>>>>>>>>>>>>>>>> src/paks/rpz/update.sh | 52 ++
>>>>>>>>>>>>>>>> 24 files changed, 2016 insertions(+), 1 deletion(-)
>>>>>>>>>>>>>>>> create mode 100644 config/backup/includes/rpz
>>>>>>>>>>>>>>>> create mode 100644 config/menu/EX-rpz.menu
>>>>>>>>>>>>>>>> create mode 100644 config/rootfiles/packages/rpz
>>>>>>>>>>>>>>>> create mode 100644 config/rpz/00-rpz.conf
>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz-config
>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz-functions
>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz-make
>>>>>>>>>>>>>>>> create mode 100755 config/rpz/rpz-metrics
>>>>>>>>>>>>>>>> create mode 100755 config/rpz/rpz-sleep
>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.de.pl
>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.en.pl
>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.es.pl
>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.fr.pl
>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.it.pl
>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.tr.pl
>>>>>>>>>>>>>>>> create mode 100644 html/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>>> create mode 100644 lfs/rpz
>>>>>>>>>>>>>>>> create mode 100644 src/paks/rpz/install.sh
>>>>>>>>>>>>>>>> create mode 100644 src/paks/rpz/uninstall.sh
>>>>>>>>>>>>>>>> create mode 100644 src/paks/rpz/update.sh
>>>>>>>>>>>>>>>> diff --git a/config/backup/includes/rpz b/config/backup/includes/rpz
>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>> index 000000000..36513e494
>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>> +++ b/config/backup/includes/rpz
>>>>>>>>>>>>>>>> @@ -0,0 +1,4 @@
>>>>>>>>>>>>>>>> +/var/ipfire/dns/rpz/*
>>>>>>>>>>>>>>>> +/etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>>> +/etc/unbound/zonefiles/block.rpz
>>>>>>>>>>>>>>>> +/etc/unbound/local.d/*rpz.conf
>>>>>>>>>>>>>>>> diff --git a/config/cfgroot/manualpages b/config/cfgroot/manualpages
>>>>>>>>>>>>>>>> index 1f7e01efc..d3a48c633 100644
>>>>>>>>>>>>>>>> --- a/config/cfgroot/manualpages
>>>>>>>>>>>>>>>> +++ b/config/cfgroot/manualpages
>>>>>>>>>>>>>>>> @@ -70,6 +70,7 @@ pakfire.cgi=configuration/ipfire/pakfire
>>>>>>>>>>>>>>>> wlanap.cgi=addons/wireless
>>>>>>>>>>>>>>>> tor.cgi=addons/tor
>>>>>>>>>>>>>>>> samba.cgi=addons/samba
>>>>>>>>>>>>>>>> +rpz.cgi=addons/rpz
>>>>>>>>>>>>>>>> # Logs menu
>>>>>>>>>>>>>>>> logs.cgi/summary.dat=configuration/logs/summary
>>>>>>>>>>>>>>>> diff --git a/config/menu/EX-rpz.menu b/config/menu/EX-rpz.menu
>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>> index 000000000..2f4daf410
>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>> +++ b/config/menu/EX-rpz.menu
>>>>>>>>>>>>>>>> @@ -0,0 +1,6 @@
>>>>>>>>>>>>>>>> +$subipfire->{'20.rpz'} = {
>>>>>>>>>>>>>>>> + 'caption' => $Lang::tr{'rpz'},
>>>>>>>>>>>>>>>> + 'uri' => '/cgi-bin/rpz.cgi',
>>>>>>>>>>>>>>>> + 'title' => "RPZ",
>>>>>>>>>>>>>>>> + 'enabled' => 1,
>>>>>>>>>>>>>>>> +};
>>>>>>>>>>>>>>>> diff --git a/config/rootfiles/common/configroot b/config/rootfiles/common/configroot
>>>>>>>>>>>>>>>> index 9839eee45..b30d6aae4 100644
>>>>>>>>>>>>>>>> --- a/config/rootfiles/common/configroot
>>>>>>>>>>>>>>>> +++ b/config/rootfiles/common/configroot
>>>>>>>>>>>>>>>> @@ -120,6 +120,7 @@ var/ipfire/menu.d/70-log.menu
>>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-apcupsd.menu
>>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-guardian.menu
>>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-mympd.menu
>>>>>>>>>>>>>>>> +#var/ipfire/menu.d/EX-rpz.menu
>>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-samba.menu
>>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-tor.menu
>>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-transmission.menu
>>>>>>>>>>>>>>>> diff --git a/config/rootfiles/common/web-user-interface b/config/rootfiles/common/web-user-interface
>>>>>>>>>>>>>>>> index 816241dae..e00464076 100644
>>>>>>>>>>>>>>>> --- a/config/rootfiles/common/web-user-interface
>>>>>>>>>>>>>>>> +++ b/config/rootfiles/common/web-user-interface
>>>>>>>>>>>>>>>> @@ -69,6 +69,7 @@ srv/web/ipfire/cgi-bin/proxy.cgi
>>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/qos.cgi
>>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/remote.cgi
>>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/routing.cgi
>>>>>>>>>>>>>>>> +#srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>>> #srv/web/ipfire/cgi-bin/samba.cgi
>>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/services.cgi
>>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/shutdown.cgi
>>>>>>>>>>>>>>>> diff --git a/config/rootfiles/packages/rpz b/config/rootfiles/packages/rpz
>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>> index 000000000..1c8663049
>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>> +++ b/config/rootfiles/packages/rpz
>>>>>>>>>>>>>>>> @@ -0,0 +1,20 @@
>>>>>>>>>>>>>>>> +etc/unbound/local.d/00-rpz.conf
>>>>>>>>>>>>>>>> +etc/unbound/zonefiles
>>>>>>>>>>>>>>>> +etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>>> +usr/sbin/rpz-config
>>>>>>>>>>>>>>>> +usr/sbin/rpz-functions
>>>>>>>>>>>>>>>> +usr/sbin/rpz-make
>>>>>>>>>>>>>>>> +usr/sbin/rpz-metrics
>>>>>>>>>>>>>>>> +usr/sbin/rpz-sleep
>>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.de.pl
>>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.en.pl
>>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.es.pl
>>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.fr.pl
>>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.it.pl
>>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.tr.pl
>>>>>>>>>>>>>>>> +var/ipfire/backup/addons/includes/rpz
>>>>>>>>>>>>>>>> +var/ipfire/dns/rpz
>>>>>>>>>>>>>>>> +var/ipfire/dns/rpz/allowlist
>>>>>>>>>>>>>>>> +var/ipfire/dns/rpz/blocklist
>>>>>>>>>>>>>>>> +var/ipfire/menu.d/EX-rpz.menu
>>>>>>>>>>>>>>>> +srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>>> diff --git a/config/rpz/00-rpz.conf b/config/rpz/00-rpz.conf
>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>> index 000000000..f005a4f2e
>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>> +++ b/config/rpz/00-rpz.conf
>>>>>>>>>>>>>>>> @@ -0,0 +1,10 @@
>>>>>>>>>>>>>>>> +server:
>>>>>>>>>>>>>>>> + module-config: "respip validator iterator"
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +rpz:
>>>>>>>>>>>>>>>> + name: allow.rpz
>>>>>>>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>>> + rpz-action-override: passthru
>>>>>>>>>>>>>>>> + rpz-log: yes
>>>>>>>>>>>>>>>> + rpz-log-name: allow
>>>>>>>>>>>>>>>> + rpz-signal-nxdomain-ra: yes
>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-config b/config/rpz/rpz-config
>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>> index 000000000..c72d50f9b
>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-config
>>>>>>>>>>>>>>>> @@ -0,0 +1,130 @@
>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +version="2025-01-11 - v44"
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +tagName="unbound"
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +rpzAction="${1}" # input RPZ action
>>>>>>>>>>>>>>>> +rpzName="${2}" # input RPZ name
>>>>>>>>>>>>>>>> +rpzURL="${3}" # input RPZ URL
>>>>>>>>>>>>>>>> +rpzOption1="${4}" # input RPZ option #1
>>>>>>>>>>>>>>>> +rpzOption2="${5}" # input RPZ option #2
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +rpzConfig="/etc/unbound/local.d/${rpzName}.rpz.conf" # output zone conf file
>>>>>>>>>>>>>>>> +rpzFile="/etc/unbound/zonefiles/${rpzName}.rpz" # output for RPZ file
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>>>>>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>>>>>>>> + case "$1" in
>>>>>>>>>>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>>>>>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>>>>>>>>>>> + esac
>>>>>>>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +case "${rpzAction}" in
>>>>>>>>>>>>>>>> + # add new rpz list
>>>>>>>>>>>>>>>> + add )
>>>>>>>>>>>>>>>> + check_name "${rpzName}" # is this a valid name?
>>>>>>>>>>>>>>>> + # does this config already exist? If yes, then exit
>>>>>>>>>>>>>>>> + if [[ -f "${rpzConfig}" ]] ; then
>>>>>>>>>>>>>>>> + msg_log "error: rpz: duplicate - ${rpzConfig} already exists. exit"
>>>>>>>>>>>>>>>> + exit 104
>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # is this a valid URL?
>>>>>>>>>>>>>>>> + regex='^https://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
>>>>>>>>>>>>>>>> + if ! [[ "${rpzURL}" =~ $regex ]] ; then
>>>>>>>>>>>>>>>> + msg_log "error: rpz: the URL is not valid: \"${rpzURL}\". exit."
>>>>>>>>>>>>>>>> + exit 105
>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # create the zone config file
>>>>>>>>>>>>>>>> + {
>>>>>>>>>>>>>>>> + echo "rpz:"
>>>>>>>>>>>>>>>> + echo " name: ${rpzName}.rpz"
>>>>>>>>>>>>>>>> + echo " zonefile: ${rpzFile}"
>>>>>>>>>>>>>>>> + echo " url: ${rpzURL}"
>>>>>>>>>>>>>>>> + echo " rpz-action-override: nxdomain"
>>>>>>>>>>>>>>>> + echo " rpz-log: ${rpzLog}"
>>>>>>>>>>>>>>>> + echo " rpz-log-name: ${rpzName}"
>>>>>>>>>>>>>>>> + echo " rpz-signal-nxdomain-ra: yes"
>>>>>>>>>>>>>>>> + } > "${rpzConfig}"
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # set-up zonefile
>>>>>>>>>>>>>>>> + # create an empty rpz file if it does not exist
>>>>>>>>>>>>>>>> + if [[ ! -f "${rpzFile}" ]] ; then
>>>>>>>>>>>>>>>> + touch "${rpzFile}"
>>>>>>>>>>>>>>>> + # unbound requires these settings for rpz files
>>>>>>>>>>>>>>>> + set_permissions "${rpzFile}" "${rpzConfig}"
>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # trash config file & rpz file
>>>>>>>>>>>>>>>> + remove )
>>>>>>>>>>>>>>>> + if ! [[ -f "${rpzConfig}" ]] ; then
>>>>>>>>>>>>>>>> + msg_log "error: rpz: cannot remove ${rpzConfig}, does not exist. exit"
>>>>>>>>>>>>>>>> + exit 106
>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + msg_log "info: rpz: remove config file & rpz file \"${rpzName}\""
>>>>>>>>>>>>>>>> + rm "${rpzConfig}"
>>>>>>>>>>>>>>>> + rm "${rpzFile}"
>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + reload )
>>>>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + list )
>>>>>>>>>>>>>>>> + awk -F':' '/^\s*name:/{ gsub(/[[:blank:]]|\.rpz/, "",$2) ; NAME=$2 } \
>>>>>>>>>>>>>>>> + /^\s*url:/{ gsub(/[[:blank:]]/, "") ; print NAME"="$2":"$3} ' \
>>>>>>>>>>>>>>>> + /etc/unbound/local.d/*rpz.conf
>>>>>>>>>>>>>>>> + exit
>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + unbound-restart )
>>>>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>>>>> + unbound_restart
>>>>>>>>>>>>>>>> + exit
>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + * )
>>>>>>>>>>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>>>>>>>>>>> + printf "Usage: $(basename "$0") <ACTION> <NAME> <URL> <OPTION> <OPTION>\n"
>>>>>>>>>>>>>>>> + printf "Version: ${version}\n"
>>>>>>>>>>>>>>>> + exit 108
>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +esac
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +exit
>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-functions b/config/rpz/rpz-functions
>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>> index 000000000..ace1d2690
>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-functions
>>>>>>>>>>>>>>>> @@ -0,0 +1,85 @@
>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +version="2024-12-10 - v02"
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +msg_log () {
>>>>>>>>>>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>>>>>>>>>>> + if tty --silent ; then
>>>>>>>>>>>>>>>> + echo "${tagName}:" "$*"
>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# check for a valid name
>>>>>>>>>>>>>>>> +check_name () {
>>>>>>>>>>>>>>>> + local theName="${1}"
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + regex='^[a-zA-Z0-9_]+$' # no dash or plus, alpha numeric only
>>>>>>>>>>>>>>>> + regex1='^(allow|block)$' # allow and block are reserved NAMEs
>>>>>>>>>>>>>>>> + if [[ ! "${theName}" =~ $regex ]] || [[ "${theName}" =~ $regex1 ]] ; then
>>>>>>>>>>>>>>>> + msg_log "error: rpz: the NAME is not valid: \"${theName}\". exit."
>>>>>>>>>>>>>>>> + exit 101
>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +set_permissions () {
>>>>>>>>>>>>>>>> + chown nobody:nobody "$@"
>>>>>>>>>>>>>>>> + chmod 644 "$@"
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +check_unbound_conf () {
>>>>>>>>>>>>>>>> + local thecheckConf="${1:-yes}" # check config default is yes
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # check the above config files
>>>>>>>>>>>>>>>> + if [[ "${thecheckConf}" == yes ]] ; then
>>>>>>>>>>>>>>>> + msg_log "info: rpz: check for errors with \"unbound-checkconf\""
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + if ! unbound-checkconf ; then
>>>>>>>>>>>>>>>> + msg_log "error: rpz: unbound-checkconf found invalid configuration."
>>>>>>>>>>>>>>>> + msg_log \
>>>>>>>>>>>>>>>> + "error: rpz: In Terminal run the command \"unbound-checkconf\" for more information. exit."
>>>>>>>>>>>>>>>> + exit 102
>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +unbound_control_reload () {
>>>>>>>>>>>>>>>> + local theReload="${1:-yes}" # reload default is yes
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + if [[ "${theReload}" == yes ]] ; then
>>>>>>>>>>>>>>>> + # reload due to the changes
>>>>>>>>>>>>>>>> + msg_log "info: rpz: run \"unbound-control reload\""
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + if ! unbound-control reload ; then
>>>>>>>>>>>>>>>> + msg_log "error: rpz: unbound-control reload. exit."
>>>>>>>>>>>>>>>> + exit 109
>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +unbound_restart () {
>>>>>>>>>>>>>>>> + # restart due to the changes
>>>>>>>>>>>>>>>> + msg_log "info: rpz: run \"unbound restart\""
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + /usr/local/bin/unboundctrl restart
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-make b/config/rpz/rpz-make
>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>> index 000000000..927d55170
>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-make
>>>>>>>>>>>>>>>> @@ -0,0 +1,203 @@
>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +version="2025-01-11 - v14"
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# create the config file for allow
>>>>>>>>>>>>>>>> +make_allow_config () {
>>>>>>>>>>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>>>>>>>>>>> + local theConfig="/etc/unbound/local.d/00-rpz.conf" # output zone conf file
>>>>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/allowlist" # input custom list of domains
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + msg_log "info: rpz: make config file \"00-rpz.conf\""
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + echo "server:
>>>>>>>>>>>>>>>> + module-config: \"respip validator iterator\"" > "${theConfig}"
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # does allow list exist?
>>>>>>>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + echo "rpz:
>>>>>>>>>>>>>>>> + name: allow.rpz
>>>>>>>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>>> + rpz-action-override: passthru
>>>>>>>>>>>>>>>> + rpz-log: ${theLog}
>>>>>>>>>>>>>>>> + rpz-log-name: allow
>>>>>>>>>>>>>>>> + rpz-signal-nxdomain-ra: yes" >> "${theConfig}"
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>>>>>>>> + set_permissions "${theConfig}"
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# create the config file for block
>>>>>>>>>>>>>>>> +make_block_config () {
>>>>>>>>>>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>>>>>>>>>>> + local theConfig="/etc/unbound/local.d/block.rpz.conf" # output zone conf file
>>>>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/blocklist" # input custom list of domains
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + msg_log "info: rpz: make config file \"block.rpz.conf\""
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # does block list exist?
>>>>>>>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + echo "rpz:
>>>>>>>>>>>>>>>> + name: block.rpz
>>>>>>>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/block.rpz
>>>>>>>>>>>>>>>> + rpz-action-override: nxdomain
>>>>>>>>>>>>>>>> + rpz-log: ${theLog}
>>>>>>>>>>>>>>>> + rpz-log-name: block
>>>>>>>>>>>>>>>> + rpz-signal-nxdomain-ra: yes" > "${theConfig}"
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>>>>>>>> + set_permissions "${theConfig}"
>>>>>>>>>>>>>>>> + else
>>>>>>>>>>>>>>>> + # no - trash the config file
>>>>>>>>>>>>>>>> + rm --verbose /etc/unbound/local.d/block.rpz.conf
>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# create an RPZ file for allow or block
>>>>>>>>>>>>>>>> +make_rpz_file () {
>>>>>>>>>>>>>>>> + local theName="${1}" # allow or block
>>>>>>>>>>>>>>>> + local theAction='.' # the default is nxdomain or block
>>>>>>>>>>>>>>>> + local actionList
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>>>>>>>>>>> + local theZonefile="/etc/unbound/zonefiles/${theName}.rpz" # output file for RPZ
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # does a list exist?
>>>>>>>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # for allow set to passthru
>>>>>>>>>>>>>>>> + [[ "${theName}" == allow ]] && theAction='rpz-passthru.'
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # drop any extra "blanks" and add "CNAME <RPZ action>." to each line
>>>>>>>>>>>>>>>> + actionList=$( awk '{$1=$1};1' "${theList}" |
>>>>>>>>>>>>>>>> + sed "/^[^;].*[[:alnum:]]/ s|$| CNAME ${theAction}|" )
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + msg_log "info: rpz: create zonefile for \"${theName}list\""
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +echo "; Name: ${theName} list
>>>>>>>>>>>>>>>> +; Last modified: $(date "+%Y-%m-%d at %H.%M.%S %Z")
>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>> +; domains with actions list
>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>> +${actionList}" > "${theZonefile}"
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>>>>>>>> + set_permissions "${theZonefile}"
>>>>>>>>>>>>>>>> + # set-up allow/block list files
>>>>>>>>>>>>>>>> + set_permissions "${theList}"
>>>>>>>>>>>>>>>> + else
>>>>>>>>>>>>>>>> + msg_log "info: rpz: the ${theList} is empty."
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + rm --verbose "${theZonefile}" # trash the RPZ file
>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# check if allow/block list is valid
>>>>>>>>>>>>>>>> +validate_list () {
>>>>>>>>>>>>>>>> + local theName="${1}" # allow or block
>>>>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # remove good:
>>>>>>>>>>>>>>>> + # - properly formated domain names with or without leading wildcard
>>>>>>>>>>>>>>>> + # - properly formated top level domain (TLD) names with wildcard
>>>>>>>>>>>>>>>> + # - blank lines and comment lines
>>>>>>>>>>>>>>>> + # remaining lines are considered "bad"
>>>>>>>>>>>>>>>> + bad_lines=$( sed --regexp-extended \
>>>>>>>>>>>>>>>> + '/^(\*\.)?([a-zA-Z0-9](([a-zA-Z0-9\-]){0,61}[a-zA-Z0-9])?\.)+([a-zA-Z]{2,}|xn--[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])$/d ;
>>>>>>>>>>>>>>>> + /^(\*\.)([a-z]{2,61}|xn--[a-z0-9]{1,60})$/d ;
>>>>>>>>>>>>>>>> + /^$/d ; /^;/d' "${theList}" )
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + if [[ ! -z "${bad_lines}" ]] ; then
>>>>>>>>>>>>>>>> + msg_log "error: rpz: invalid line(s) in ${theList}."
>>>>>>>>>>>>>>>> + printf "%s\n" "bad line(s): ${bad_lines}"
>>>>>>>>>>>>>>>> + exit 110
>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +tagName="unbound"
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +rpzName="${1}" # input RPZ name
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>>>>>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>>>>>>>> + case "$1" in
>>>>>>>>>>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>>>>>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>>>>>>>>>>> + esac
>>>>>>>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +case "${rpzName}" in
>>>>>>>>>>>>>>>> + # make a new allow or block rpz file
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + allow )
>>>>>>>>>>>>>>>> + validate_list 'allow' # is the allowlist valid?
>>>>>>>>>>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>>>>>>>>>>> + make_rpz_file 'allow'
>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + allowblock )
>>>>>>>>>>>>>>>> + validate_list 'allow' # is the list valid?
>>>>>>>>>>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>>>>>>>>>>> + make_rpz_file 'allow'
>>>>>>>>>>>>>>>> + ;&
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + block )
>>>>>>>>>>>>>>>> + validate_list 'block' # is the blocklist valid?
>>>>>>>>>>>>>>>> + make_block_config "${rpzLog}"
>>>>>>>>>>>>>>>> + make_rpz_file 'block'
>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + reload )
>>>>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + unbound-restart )
>>>>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>>>>> + unbound_restart
>>>>>>>>>>>>>>>> + exit
>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + * )
>>>>>>>>>>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>>>>>>>>>>> + printf "Usage: $(basename "$0") <NAME> <OPTION> <OPTION>\n"
>>>>>>>>>>>>>>>> + printf "Version: ${version}\n"
>>>>>>>>>>>>>>>> + exit 108
>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>> +esac
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +exit
>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-metrics b/config/rpz/rpz-metrics
>>>>>>>>>>>>>>>> new file mode 100755
>>>>>>>>>>>>>>>> index 000000000..4d43e1629
>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-metrics
>>>>>>>>>>>>>>>> @@ -0,0 +1,170 @@
>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +version="2025-01-20 - v25"
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +weeks="2" # default to two message logs
>>>>>>>>>>>>>>>> +sortBy="name" # default "by name"
>>>>>>>>>>>>>>>> +rpzActive="enabled" # default "enabled only"
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>>>>>>>> + case "$1" in
>>>>>>>>>>>>>>>> + --by-names | --by-name | name ) sortBy="name" ;;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + --by-hits | --by-hit | hits | hit ) sortBy="hit" ;;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + --by-lines | --by-line | lines | line ) sortBy="line" ;;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + --by-effect ) sortBy="effect" ;;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + --enabled-only ) rpzActive="enabled" ;;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + --active-all | --all | all ) rpzActive="all" ;;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + [0-9] | [0-9][0-9] ) weeks=$1 ;;
>>>>>>>>>>>>>>>> + esac
>>>>>>>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# get the list of message logs for N weeks
>>>>>>>>>>>>>>>> +messageLogs=$( find /var/log/messages* -type f | sort --version-sort |
>>>>>>>>>>>>>>>> + head -"${weeks}" )
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# get the list of RPZ names & counts from the message log(s)
>>>>>>>>>>>>>>>> +rpzNameCount=$( for logf in ${messageLogs} ; do
>>>>>>>>>>>>>>>> + zgrep --text --fixed-strings 'info: rpz: applied' "${logf}" |
>>>>>>>>>>>>>>>> + awk '$10 ~ /\[\w*]/ { print $10 }' ;
>>>>>>>>>>>>>>>> + done | sort | uniq --count )
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# flip results and remove brackets `[` and `]`
>>>>>>>>>>>>>>>> +rpzNameCount=$( echo "${rpzNameCount}" |
>>>>>>>>>>>>>>>> + awk '{ print $2, $1 }' |
>>>>>>>>>>>>>>>> + sed --regexp-extended 's|^\[(.*)\]|\1|' )
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# grab only names
>>>>>>>>>>>>>>>> +rpzNames=$( echo "${rpzNameCount}" | awk '{ print $1 }' )
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# get list of RPZ files
>>>>>>>>>>>>>>>> +rpzFileList=$( find /etc/unbound/zonefiles -type f -iname "*.rpz" )
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# get basename of those files
>>>>>>>>>>>>>>>> +rpzBaseNames=$( echo "${rpzFileList}" |
>>>>>>>>>>>>>>>> + sed 's|/etc/unbound/zonefiles/||g ; s|\.rpz||g ;' )
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# add to rpzNames
>>>>>>>>>>>>>>>> +rpzNames="${rpzNames}"$'\n'"${rpzBaseNames}"
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# drop duplicate names
>>>>>>>>>>>>>>>> +rpzNames=$( echo "${rpzNames}" | sort --unique )
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# get line count for each RPZ
>>>>>>>>>>>>>>>> +lineCount=$( echo "${rpzFileList}" | xargs wc -l )
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# get comment line count and blank line count for each RPZ
>>>>>>>>>>>>>>>> +commentCount=$( echo "${rpzFileList}" | xargs grep --count -e "^$" -e "^;" )
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# get modified date each RPZ
>>>>>>>>>>>>>>>> +modDateList=$( echo "${rpzFileList}" | xargs stat -c '%.10y %n' )
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +ucListAuthZones=$( unbound-control list_auth_zones )
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# get width of RPZ names
>>>>>>>>>>>>>>>> +pWidth=$( echo "${rpzNames}" | awk '{ print $1" " }' | wc -L )
>>>>>>>>>>>>>>>> +pFormat="%-${pWidth}s %-8s %-8s %8s %12s %12s\n"
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# print title line
>>>>>>>>>>>>>>>> +printf "${pFormat}" "name" "hits" "active" "lines" " hits/line" "last download"
>>>>>>>>>>>>>>>> +printf -- "--------------"
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +theResults=""
>>>>>>>>>>>>>>>> +totalLines=0
>>>>>>>>>>>>>>>> +totalHits=0
>>>>>>>>>>>>>>>> +while read -r theName
>>>>>>>>>>>>>>>> +do
>>>>>>>>>>>>>>>> + printf -- "--" # pretend progress bar
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # is this RPZ list active?
>>>>>>>>>>>>>>>> + theActive="disabled"
>>>>>>>>>>>>>>>> + if grep --quiet "^${theName}\.rpz" <<< "${ucListAuthZones}"
>>>>>>>>>>>>>>>> + then
>>>>>>>>>>>>>>>> + theActive="enabled"
>>>>>>>>>>>>>>>> + else
>>>>>>>>>>>>>>>> + [[ "${rpzActive}" == enabled ]] && continue
>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # get hit count
>>>>>>>>>>>>>>>> + theHits="0"
>>>>>>>>>>>>>>>> + if output=$( grep "^${theName}\s" <<< "${rpzNameCount}" ) ; then
>>>>>>>>>>>>>>>> + theHits=$( echo "${output}" | awk '{ print $2 }' )
>>>>>>>>>>>>>>>> + totalHits=$(( totalHits + theHits ))
>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # get line count
>>>>>>>>>>>>>>>> + theLines="n/a"
>>>>>>>>>>>>>>>> + hitsPerLine="0"
>>>>>>>>>>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${lineCount}" ) ; then
>>>>>>>>>>>>>>>> + theLines=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>>>>>>>>>>> + totalLines=$(( totalLines + theLines ))
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + if [[ "${theLines}" -gt 2 ]] ; then
>>>>>>>>>>>>>>>> + hitsPerLine=$(( 100 * theHits / theLines ))
>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # get modification date
>>>>>>>>>>>>>>>> + theModDate="n/a"
>>>>>>>>>>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${modDateList}" ) ; then
>>>>>>>>>>>>>>>> + theModDate=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # add to results list
>>>>>>>>>>>>>>>> + theResults+="${theName} ${theHits} ${theActive} ${theLines} ${hitsPerLine} ${theModDate}"$'\n'
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +done <<< "${rpzNames}"
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +case "${sortBy}" in
>>>>>>>>>>>>>>>> + # sort by "active" then by "name"
>>>>>>>>>>>>>>>> + name) sortArg=(-k3,3r -k1,1) ;;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # sort by "active" then by "hits" then by "name"
>>>>>>>>>>>>>>>> + hit) sortArg=(-k3,3r -k2,2nr -k1,1) ;;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # sort by "active" then by "lines" then by "name"
>>>>>>>>>>>>>>>> + line) sortArg=(-k3,3r -k4,4nr -k1,1) ;;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # sort by "active" then by "effect" then by "name"
>>>>>>>>>>>>>>>> + effect) sortArg=(-k3,3r -k5,5nr -k1,1) ;;
>>>>>>>>>>>>>>>> +esac
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +printf -- "--------------\n"
>>>>>>>>>>>>>>>> +# remove blank lines, sort, print as columns
>>>>>>>>>>>>>>>> +echo "${theResults}" |
>>>>>>>>>>>>>>>> + awk '!/^[[:space:]]*$/' |
>>>>>>>>>>>>>>>> + sort "${sortArg[@]}" |
>>>>>>>>>>>>>>>> + awk --assign=width="${pWidth}" \
>>>>>>>>>>>>>>>> + '{ printf "%-*s %-8s %-8s %8s %10s %% %12s\n", width, $1, $2, $3, $4, $5, $6 }'
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +printf "${pFormat}" "" "=======" "" "========" "" ""
>>>>>>>>>>>>>>>> +printf "${pFormat}" "Totals -->" "${totalHits}" "" "${totalLines}" "" ""
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +exit
>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-sleep b/config/rpz/rpz-sleep
>>>>>>>>>>>>>>>> new file mode 100755
>>>>>>>>>>>>>>>> index 000000000..dd3603599
>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-sleep
>>>>>>>>>>>>>>>> @@ -0,0 +1,58 @@
>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +version="2024-08-16" # v05
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# send message to message log
>>>>>>>>>>>>>>>> +msg_log () {
>>>>>>>>>>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>>>>>>>>>>> + if tty --silent ; then
>>>>>>>>>>>>>>>> + echo "${tagName}:" "$*"
>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +tagName="unbound"
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +sleepTime="${1:-5m}" # default to sleep for 5m (5 minutes)
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +zoneList=$( unbound-control list_auth_zones | awk '{print $1}' )
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +for zone in ${zoneList} ; do
>>>>>>>>>>>>>>>> + printf "disable ${zone}\t"
>>>>>>>>>>>>>>>> + unbound-control rpz_disable "${zone}"
>>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +msg_log "info: rpz: disabled all zones for ${sleepTime}"
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +sleep "${sleepTime}"
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +for zone in ${zoneList} ; do
>>>>>>>>>>>>>>>> + printf "enable ${zone}\t"
>>>>>>>>>>>>>>>> + unbound-control rpz_enable "${zone}"
>>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +msg_log "info: rpz: enabled all zones"
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +exit
>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.de.pl b/config/rpz/rpz.de.pl
>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>> index 000000000..3770c6bb0
>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.de.pl
>>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>>> +'rpz apply' => 'Übernehmen',
>>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Benutzerdefinierte Allowlist aktivieren:',
>>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Zugelassene Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>>>>>>>>>>> +'rpz cl allow' => 'Benutzerdefinierte Allowlist',
>>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Benutzerdefinierte Blocklist aktivieren:',
>>>>>>>>>>>>>>>> +'rpz cl block info' => 'Gesperrte Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>>>>>>>>>>> +'rpz cl block' => 'Benutzerdefinierte Blocklist',
>>>>>>>>>>>>>>>> +'rpz cl' => 'Benutzerdefinierte Listen',
>>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'Der Name enthält unzulässige Zeichen',
>>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf hat eine fehlerhafte Konfiguration ermittelt. Führen Sie das Kommando unbound-checkconf auf der Konsole aus, um weitere Informationen zu erhalten.',
>>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'Die benutzerdefinierte Allow-/Blocklist ist leer',
>>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'Ein Eintrag mit identischem Namen existiert bereits',
>>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'Die URL ist ungültig',
>>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'Eintrag kann nicht entfernt werden, der Name existiert nicht',
>>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'Der Name ist ungültig - nur "allow" oder "block" möglich',
>>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'Fehlende oder inkorrekte Parameter',
>>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control reload ist fehlgeschlagen',
>>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'Die benutzerdefinierte Allow-/Blocklist enthält unzulässige Einträge',
>>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'Die Anmerkung enthält unzulässige Zeichen',
>>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'Ungültiger Eintrag in der benutzerdefinierten Allowlist, Zeile ',
>>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'Ungültiger Eintrag in der benutzerdefinierten Blocklist, Zeile ',
>>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'Ausgewählter Eintrag existiert nicht: ',
>>>>>>>>>>>>>>>> +'rpz zf editor' => 'Zonendatei-Eintrag bearbeiten',
>>>>>>>>>>>>>>>> +'rpz zf imported' => '(importiert aus rpz-config)',
>>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Erlaubte Zeichen sind a-z, A-Z, 0-9 und Unterstriche',
>>>>>>>>>>>>>>>> +'rpz zf' => 'Zonendateien',
>>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.en.pl b/config/rpz/rpz.en.pl
>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>> index 000000000..0720a8940
>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.en.pl
>>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>>> +'rpz apply' => 'Apply',
>>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Enable custom allowlist:',
>>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Allowed domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>>>>> +'rpz cl allow' => 'Custom allowlist',
>>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Enable custom blocklist:',
>>>>>>>>>>>>>>>> +'rpz cl block info' => 'Blocked domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>>>>> +'rpz cl block' => 'Custom blocklist',
>>>>>>>>>>>>>>>> +'rpz cl' => 'Custom lists',
>>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'the NAME is not valid',
>>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf found invalid configuration. In the Terminal run the command unbound-checkconf for more information',
>>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'the allow/block list is empty',
>>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'duplicate - NAME already exists',
>>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'the URL is not valid',
>>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'cannot remove the NAME does not exist',
>>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'the NAME is not valid - "allow" or "block" only',
>>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'missing or incorrect parameter',
>>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control reload failed',
>>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'custom Allowlist/Blocklist contains invalid entries',
>>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'the REMARK is not valid',
>>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'invalid entry in allowlist, line ',
>>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'invalid entry in blocklist, line ',
>>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'Selected entry does not exist: ',
>>>>>>>>>>>>>>>> +'rpz zf editor' => 'Edit zonefiles entry',
>>>>>>>>>>>>>>>> +'rpz zf imported' => '(imported from rpz-config)',
>>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Valid characters are a-z, A-Z, 0-9 and underscore.',
>>>>>>>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.es.pl b/config/rpz/rpz.es.pl
>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>> index 000000000..98628e4aa
>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.es.pl
>>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>>> +'rpz' => 'Zonas de política de respuesta (RPZ)',
>>>>>>>>>>>>>>>> +'rpz apply' => 'Aplicar',
>>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Habilitar la lista blanca personalizada:',
>>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Dominio permitido (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>>>>>>>>>>> +'rpz cl allow' => 'Lista blanca personalizada',
>>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Habilitar la lista negra personalizada:',
>>>>>>>>>>>>>>>> +'rpz cl block info' => 'Dominio bloqueado (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>>>>>>>>>>> +'rpz cl block' => 'Lista negra personalizada',
>>>>>>>>>>>>>>>> +'rpz cl' => 'Lista personalizada',
>>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'El NOMBRE no es válido',
>>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha encontrado una configuración no válida. Desde Terminal, ejecute el comando unbound-checkconf para mayor información',
>>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'La lista de permitidos/bloqueados está vacía',
>>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'duplicado - NOMBRE ya existe',
>>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'la URL no es válida',
>>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'no es posible eliminar el NOMBRE que no existe',
>>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'el NOMBRE no es válido - sólo "permitir" o "bloquear"',
>>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'parámetro faltante o incorrecto',
>>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'Error al recargar unbound-control',
>>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'la Lista blanca/Lista negra personalizada contiene entradas no válidas',
>>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'el COMENTARIO no es válido',
>>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'entrada no válida en la lista blanca, línea ',
>>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'entrada no válida en la lista negra, línea ',
>>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'La entrada seleccionada no existe: ',
>>>>>>>>>>>>>>>> +'rpz zf editor' => 'Editar la entrada de archivos de zona',
>>>>>>>>>>>>>>>> +'rpz zf imported' => '(importado de rpz-config)',
>>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Los caracteres válidos son a-z, A-Z, 0-9 y guión bajo',
>>>>>>>>>>>>>>>> +'rpz zf' => 'Archivos de zona',
>>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.fr.pl b/config/rpz/rpz.fr.pl
>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>> index 000000000..f35f3c2d0
>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.fr.pl
>>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>>> +'rpz apply' => 'Appliquer',
>>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Activer la liste d\'autorisations personnalisée:',
>>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Domaines autorisés (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>>>>> +'rpz cl allow' => 'Liste d\'autorisations personnalisée',
>>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Activer la liste de blocage personnalisée:',
>>>>>>>>>>>>>>>> +'rpz cl block info' => 'Domaines bloqués (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>>>>> +'rpz cl block' => 'liste de blocage personnalisé',
>>>>>>>>>>>>>>>> +'rpz cl' => 'Listes personnalisées',
>>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'le NOM n\'est pas valide',
>>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf configuration non valide trouvée. Dans le terminal, exécutez la commande unbound-checkconf pour plus d\'informations',
>>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'la liste autoriser/bloquer est vide',
>>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'le NOM existe déjà',
>>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'L\'URL n\'est pas valide',
>>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'impossible de supprimer le NOM n\'existe pas',
>>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'le NOM n\'est pas valide - « autoriser » ou « bloquer » seulement',
>>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'paramètre manquant ou incorrect',
>>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control rechargement échoué',
>>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'la liste autoriser/bloquer contient des entrées non valides',
>>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'la REMARQUE n\'est pas valable',
>>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'entrée non valide dans la liste d\'autorisation, ligne ',
>>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'entrée non valide dans la liste de blocs, ligne ',
>>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'L\'entrée sélectionnée n\'existe pas: ',
>>>>>>>>>>>>>>>> +'rpz zf editor' => 'Modifier l\'entrée Fichiers Zone',
>>>>>>>>>>>>>>>> +'rpz zf imported' => '(importé de rpz-config)',
>>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Les caractères valides sont a-z, A-Z, 0-9 et soulignement.',
>>>>>>>>>>>>>>>> +'rpz zf' => 'Fichiers Zone',
>>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.it.pl b/config/rpz/rpz.it.pl
>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>> index 000000000..ee81605c9
>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.it.pl
>>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>>> +'rpz apply' => 'Applica',
>>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Abilita la Whitelist personalizzata:',
>>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Domini consentiti (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>>>>>>>>>>> +'rpz cl allow' => 'Whitelist personalizzata',
>>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Abilita la Blacklist personalizzata:',
>>>>>>>>>>>>>>>> +'rpz cl block info' => 'Domini bloccati (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>>>>>>>>>>> +'rpz cl block' => 'Blacklist personalizzata',
>>>>>>>>>>>>>>>> +'rpz cl' => 'Liste personalizzate',
>>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'il NOME non è valido',
>>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha trovato una configurazione non valida. Dal Terminale esegui il comando unbound-checkconf per maggiori informazioni',
>>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'l\'elenco consentiti/bloccati è vuoto',
>>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'duplicato - NAME esiste di già',
>>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'l\'URL non è valido',
>>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'non è possibile rimuovere il NOME non esiste',
>>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'il NOME non è valido - solo "consenti" o "blocca"',
>>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'parametro mancante o non corretto',
>>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'ricaricamento del controllo non associato non riuscito',
>>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'la Whitelist/Blacklist personalizzata contiene voci non valide',
>>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'l"OSSERVAZIONE non è valida',
>>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'voce non valida nella Whitelist, riga ',
>>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'voce non valida nella Blacklist, riga ',
>>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'La voce selezionata non esiste: ',
>>>>>>>>>>>>>>>> +'rpz zf editor' => 'Modifica la voce dei file di zona',
>>>>>>>>>>>>>>>> +'rpz zf imported' => '(importato da rpz-config)',
>>>>>>>>>>>>>>>> +'rpz zf remark info' => 'I caratteri validi sono a-z, A-Z, 0-9 e trattino basso',
>>>>>>>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.tr.pl b/config/rpz/rpz.tr.pl
>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>> index 000000000..00226e192
>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.tr.pl
>>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>>> +# I�in eklendi Ayriyeten Response Policy Zone (RPZ)
>>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>>> +'rpz apply' => 'Uygulamak',
>>>>>>>>>>>>>>>> +'rpz cl allow enable' => '�zel Etkinlestir allowlist:',
>>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Izin verilmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>>>>>>>>>>> +'rpz cl allow' => 'Etkinlestir allowlist',
>>>>>>>>>>>>>>>> +'rpz cl block enable' => '�zel Etkinlestir blocklist:',
>>>>>>>>>>>>>>>> +'rpz cl block info' => 'Engellenmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>>>>>>>>>>> +'rpz cl block' => 'Engellenmis blocklist',
>>>>>>>>>>>>>>>> +'rpz cl' => 'Engellenmis lists',
>>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'NAME ge�erli degil',
>>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ge�ersiz yapilandirma bulundu. Terminalde daha fazla bilgi i�in Unbound-checkConf komutunu �alistirin',
>>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'allow/block liste bos',
>>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'kopyalamak - NAME zaten var',
>>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'URL ge�erli degil',
>>>>>>>>>>>>>>>> +'rpz exitcode 106' => '�ikarilamiyor NAME yok',
>>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'NAME ge�erli degil - "allow" veya "block" yalniz',
>>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'Parametre eksik veya yanlis',
>>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control basarisiz',
>>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'Engellenmis Allowlist/Blocklist ge�ersiz girisler i�erir',
>>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'REMARK ge�erli degil',
>>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'Ge�ersiz giris allowlist, line ',
>>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'Ge�ersiz giris blocklist, line ',
>>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'Se�ilen giris yok: ',
>>>>>>>>>>>>>>>> +'rpz zf editor' => 'yazimlamak zonefiles giris',
>>>>>>>>>>>>>>>> +'rpz zf imported' => '(ithal edildi rpz-config)',
>>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Ge�erli karakterler a-z, A-Z, 0-9ve alt�st.',
>>>>>>>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>>> diff --git a/html/cgi-bin/rpz.cgi b/html/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>> index 000000000..a821c92ac
>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>> +++ b/html/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>>> @@ -0,0 +1,923 @@
>>>>>>>>>>>>>>>> +#!/usr/bin/perl
>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>> +# Copyright (C) 2005-2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +use strict;
>>>>>>>>>>>>>>>> +use Scalar::Util qw(looks_like_number);
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# debugging
>>>>>>>>>>>>>>>> +#use warnings;
>>>>>>>>>>>>>>>> +#use CGI::Carp 'fatalsToBrowser';
>>>>>>>>>>>>>>>> +#use Data::Dumper;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +require '/var/ipfire/general-functions.pl';
>>>>>>>>>>>>>>>> +require "${General::swroot}/lang.pl";
>>>>>>>>>>>>>>>> +require "${General::swroot}/header.pl";
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +###--- Extra HTML ---###
>>>>>>>>>>>>>>>> +my $extraHead = <<END
>>>>>>>>>>>>>>>> +<style>
>>>>>>>>>>>>>>>> + /* alternating row background */
>>>>>>>>>>>>>>>> + .tbl tr:nth-child(2n+2) {
>>>>>>>>>>>>>>>> + background-color: var(--color-light-grey);
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> + .tbl tr:nth-child(2n+3) {
>>>>>>>>>>>>>>>> + background-color: var(--color-grey);
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> + /* text styles */
>>>>>>>>>>>>>>>> + .tbl th:not(:last-child) {
>>>>>>>>>>>>>>>> + text-align: left;
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> + div.right {
>>>>>>>>>>>>>>>> + text-align: right;
>>>>>>>>>>>>>>>> + margin-top: 0.5em;
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> + /* customlist input */
>>>>>>>>>>>>>>>> + textarea.domainlist {
>>>>>>>>>>>>>>>> + margin: 0.5em 0;
>>>>>>>>>>>>>>>> + resize: vertical;
>>>>>>>>>>>>>>>> + min-height: 10em;
>>>>>>>>>>>>>>>> + overflow: auto;
>>>>>>>>>>>>>>>> + white-space: pre;
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> + button[type=submit]:disabled {
>>>>>>>>>>>>>>>> + opacity: 0.6;
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> +</style>
>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>> +###--- End of extra HTML ---###
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +### Settings ###
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# Request DNS service reload after configuration change
>>>>>>>>>>>>>>>> +my $RPZ_RELOAD_FLAG = "${General::swroot}/dns/rpz/reload.flag";
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# Configuration file for all available zonefiles
>>>>>>>>>>>>>>>> +# Format: index, name (unique), enabled (on/off), URL, remark
>>>>>>>>>>>>>>>> +my $ZONEFILES_CONF = "${General::swroot}/dns/rpz/zonefiles.conf";
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# Configuration file for custom lists
>>>>>>>>>>>>>>>> +# IDs: 0=allowlist, 1=blocklist, 2=options (allow/block enabled)
>>>>>>>>>>>>>>>> +my $CUSTOMLISTS_CONF = "${General::swroot}/dns/rpz/customlists.conf";
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# Export custom lists to rpz-config
>>>>>>>>>>>>>>>> +my $RPZ_ALLOWLIST = "${General::swroot}/dns/rpz/allowlist";
>>>>>>>>>>>>>>>> +my $RPZ_BLOCKLIST = "${General::swroot}/dns/rpz/blocklist";
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +### Preparation ###
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# Create missing config files
>>>>>>>>>>>>>>>> +unless(-f $ZONEFILES_CONF) { &General::system('touch', "$ZONEFILES_CONF"); }
>>>>>>>>>>>>>>>> +unless(-f $CUSTOMLISTS_CONF) { &General::system('touch', "$CUSTOMLISTS_CONF"); }
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +## Global gui data
>>>>>>>>>>>>>>>> +my $errormessage = "";
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +## Global configuration data
>>>>>>>>>>>>>>>> +my %zonefiles = ();
>>>>>>>>>>>>>>>> +my %customlists = ();
>>>>>>>>>>>>>>>> +&_zonefiles_load();
>>>>>>>>>>>>>>>> +&_customlists_load();
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +## Global CGI form data
>>>>>>>>>>>>>>>> +my %cgiparams = ();
>>>>>>>>>>>>>>>> +&Header::getcgihash(\%cgiparams);
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +my $action = $cgiparams{'ACTION'} // 'NONE';
>>>>>>>>>>>>>>>> +my $action_key = $cgiparams{'KEY'} // ''; # entry being edited, empty = none/new
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +###--- Process form actions ---###
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# Zonefiles action: Check whether the requested entry exists
>>>>>>>>>>>>>>>> +if((substr($action, 0, 3) eq 'ZF_') && ($action_key)) {
>>>>>>>>>>>>>>>> + unless(defined $zonefiles{$action_key}) {
>>>>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr(204, $action_key);
>>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +## Perform actions
>>>>>>>>>>>>>>>> +if($action eq 'ZF_SAVE') { ## Save new or modified zonefiles entry
>>>>>>>>>>>>>>>> + if(&_action_zf_save()) {
>>>>>>>>>>>>>>>> + $action = 'NONE'; # success, return to main page
>>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>> + $action = 'ZF_EDIT'; # error occured, keep editing
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +} elsif($action eq 'ZF_TOGGLE') { ## Toggle on/off
>>>>>>>>>>>>>>>> + if(&_action_zf_toggle()) {
>>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +} elsif($action eq 'ZF_REMOVE') { ## Remove entry
>>>>>>>>>>>>>>>> + if(&_action_zf_remove()) {
>>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +} elsif($action eq 'CL_SAVE') { ## Save custom lists
>>>>>>>>>>>>>>>> + if(&_action_cl_save()) {
>>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +} elsif($action eq 'RPZ_RELOAD') { ## Reload dns configuration
>>>>>>>>>>>>>>>> + if(&_action_rpz_reload()) {
>>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +} elsif($action eq 'UNB_RESTART') { ## Restart unbound service
>>>>>>>>>>>>>>>> + if(&_action_unb_restart()) {
>>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +###--- Start GUI ---###
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +## Start http output
>>>>>>>>>>>>>>>> +&Header::showhttpheaders();
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# Start HTML
>>>>>>>>>>>>>>>> +&Header::openpage($Lang::tr{'rpz'}, 1, $extraHead);
>>>>>>>>>>>>>>>> +&Header::openbigbox('100%', 'left', '');
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# Show error messages
>>>>>>>>>>>>>>>> +if($errormessage) {
>>>>>>>>>>>>>>>> + &_print_message($errormessage);
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# Handle zonefile add/edit mode
>>>>>>>>>>>>>>>> +if($action eq "ZF_EDIT") {
>>>>>>>>>>>>>>>> + &_print_zonefile_editor();
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Finalize page and exit cleanly
>>>>>>>>>>>>>>>> + &Header::closebigbox();
>>>>>>>>>>>>>>>> + &Header::closepage();
>>>>>>>>>>>>>>>> + exit(0);
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# Show gui elements
>>>>>>>>>>>>>>>> +&_print_zonefiles();
>>>>>>>>>>>>>>>> +&_print_customlists();
>>>>>>>>>>>>>>>> +&_print_gui_extras();
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +&Header::closebigbox();
>>>>>>>>>>>>>>>> +&Header::closepage();
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +###--- End of GUI ---###
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +###--- Internal configuration file functions ---###
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# Load all available zonefiles from rpz-config and the internal configuration
>>>>>>>>>>>>>>>> +sub _zonefiles_load {
>>>>>>>>>>>>>>>> + # Clean start
>>>>>>>>>>>>>>>> + %zonefiles = ();
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Source 1: Get the currently enabled zonefiles from rpz-config (expected format [name]=[URL])
>>>>>>>>>>>>>>>> + my @enabled_files = &General::system_output('/usr/sbin/rpz-config', 'list');
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + foreach my $row (@enabled_files) {
>>>>>>>>>>>>>>>> + chomp($row);
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Use regex instead of split() to skip non-matching lines
>>>>>>>>>>>>>>>> + next unless($row =~ /^(\w+)=(.+)$/);
>>>>>>>>>>>>>>>> + my ($name, $url) = ($1, $2);
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Unique names are already guaranteed by rpz-config
>>>>>>>>>>>>>>>> + if(&_rpz_validate_zonefile($name, $url, '', 0) == 0) {
>>>>>>>>>>>>>>>> + # Populate global data hash, mark all found entries as enabled
>>>>>>>>>>>>>>>> + my %entry = ('enabled' => 'on',
>>>>>>>>>>>>>>>> + 'url' => $url,
>>>>>>>>>>>>>>>> + 'remark' => $Lang::tr{'rpz zf imported'});
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Source 2: Get additional data and disabled entries from configuration file
>>>>>>>>>>>>>>>> + my %configured_files = ();
>>>>>>>>>>>>>>>> + &General::readhasharray($ZONEFILES_CONF, \%configured_files);
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + foreach my $row (values (%configured_files)) {
>>>>>>>>>>>>>>>> + my ($name, $enabled, $url, $remark) = @$row;
>>>>>>>>>>>>>>>> + $remark //= "";
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + next unless($name);
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Check whether this row belongs to an entry already imported from rpz-config
>>>>>>>>>>>>>>>> + if(defined $zonefiles{$name}) {
>>>>>>>>>>>>>>>> + # Existing entry, only merge additional data
>>>>>>>>>>>>>>>> + $zonefiles{$name}{'remark'} = $remark;
>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>> + # Skip entry if it is marked as enabled but not found by rpz-config. It was then deleted manually
>>>>>>>>>>>>>>>> + if($enabled ne 'on') {
>>>>>>>>>>>>>>>> + # Populate global data hash
>>>>>>>>>>>>>>>> + my %entry = ('enabled' => 'off',
>>>>>>>>>>>>>>>> + 'url' => $url // "",
>>>>>>>>>>>>>>>> + 'remark' => $remark);
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# Save internal zonefiles configuration
>>>>>>>>>>>>>>>> +sub _zonefiles_save_conf {
>>>>>>>>>>>>>>>> + my $index = 0;
>>>>>>>>>>>>>>>> + my %export = ();
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Loop trough all zonefiles and create "hasharray" type export
>>>>>>>>>>>>>>>> + foreach my $name (keys %zonefiles) {
>>>>>>>>>>>>>>>> + my @entry = ($name,
>>>>>>>>>>>>>>>> + $zonefiles{$name}{'enabled'},
>>>>>>>>>>>>>>>> + $zonefiles{$name}{'url'},
>>>>>>>>>>>>>>>> + $zonefiles{$name}{'remark'});
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + $export{$index++} = \@entry;
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + &General::writehasharray($ZONEFILES_CONF, \%export);
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# Load custom lists from rpz-config and the internal configuration
>>>>>>>>>>>>>>>> +sub _customlists_load {
>>>>>>>>>>>>>>>> + # Clean start
>>>>>>>>>>>>>>>> + %customlists = ();
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Load configuration file
>>>>>>>>>>>>>>>> + my %lists_conf = ();
>>>>>>>>>>>>>>>> + &General::readhasharray($CUSTOMLISTS_CONF, \%lists_conf);
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Get list options, enabled by default to start import
>>>>>>>>>>>>>>>> + $customlists{'allow'}{'enabled'} = $lists_conf{2}[0] // 'on';
>>>>>>>>>>>>>>>> + $customlists{'block'}{'enabled'} = $lists_conf{2}[1] // 'on';
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Import enabled list from rpz-config, otherwise retrieve stored or empty list from configuration file
>>>>>>>>>>>>>>>> + if($customlists{'allow'}{'enabled'} eq 'on') {
>>>>>>>>>>>>>>>> + &_customlist_import('allow', $RPZ_ALLOWLIST);
>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>> + $customlists{'allow'}{'list'} = $lists_conf{0} // [];
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> + if($customlists{'block'}{'enabled'} eq 'on') {
>>>>>>>>>>>>>>>> + &_customlist_import('block', $RPZ_BLOCKLIST);
>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>> + $customlists{'block'}{'list'} = $lists_conf{1} // [];
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# Save internal custom lists configuration
>>>>>>>>>>>>>>>> +sub _customlists_save_conf {
>>>>>>>>>>>>>>>> + my %export = ();
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Match IDs with import function
>>>>>>>>>>>>>>>> + $export{0} = $customlists{'allow'}{'list'};
>>>>>>>>>>>>>>>> + $export{1} = $customlists{'block'}{'list'};
>>>>>>>>>>>>>>>> + $export{2} = [$customlists{'allow'}{'enabled'}, $customlists{'block'}{'enabled'}];
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + &General::writehasharray($CUSTOMLISTS_CONF, \%export);
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# Import a custom list from plain file, returns empty list if file is missing
>>>>>>>>>>>>>>>> +sub _customlist_import {
>>>>>>>>>>>>>>>> + my ($listname, $filename) = @_;
>>>>>>>>>>>>>>>> + my @list = ();
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # File exists, load and check all lines
>>>>>>>>>>>>>>>> + if(-f $filename) {
>>>>>>>>>>>>>>>> + open(my $FH, '<', $filename) or die "Can't read $filename: $!";
>>>>>>>>>>>>>>>> + while(my $line = <$FH>) {
>>>>>>>>>>>>>>>> + chomp($line);
>>>>>>>>>>>>>>>> + push(@list, $line);
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> + close($FH);
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Clean up imported data
>>>>>>>>>>>>>>>> + &_rpz_validate_customlist(\@list, 1);
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + $customlists{$listname}{'list'} = \@list;
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# Export a custom list to plain file or clear file if list is disabled
>>>>>>>>>>>>>>>> +sub _customlist_export {
>>>>>>>>>>>>>>>> + my ($listname, $filename) = @_;
>>>>>>>>>>>>>>>> + return unless(defined $customlists{$listname});
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Write enabled domain list to file, otherwise save empty file
>>>>>>>>>>>>>>>> + open(my $FH, '>', $filename) or die "Can't write $filename: $!";
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + if($customlists{$listname}{'enabled'} eq 'on') {
>>>>>>>>>>>>>>>> + foreach my $line (@{$customlists{$listname}{'list'}}) {
>>>>>>>>>>>>>>>> + print $FH "$line\n";
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>> + print $FH "; Note: This list is currently disabled by $ENV{'SCRIPT_NAME'}\n";
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + close($FH);
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +###--- Internal gui functions ---###
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# Show simple message box
>>>>>>>>>>>>>>>> +sub _print_message {
>>>>>>>>>>>>>>>> + my ($message, $title) = @_;
>>>>>>>>>>>>>>>> + $title ||= $Lang::tr{'error messages'};
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $title);
>>>>>>>>>>>>>>>> + print "<span>$message</span>";
>>>>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# Show all zone files and related gui elements
>>>>>>>>>>>>>>>> +sub _print_zonefiles {
>>>>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf'});
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>>> +<table class="tbl" width="100%">
>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>> + <th>$Lang::tr{'name'}</th>
>>>>>>>>>>>>>>>> + <th>URL</th>
>>>>>>>>>>>>>>>> + <th>$Lang::tr{'remark'}</th>
>>>>>>>>>>>>>>>> + <th colspan="3">$Lang::tr{'action'}</th>
>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Sort zonefiles by name and loop trough all entries
>>>>>>>>>>>>>>>> + foreach my $name (sort keys %zonefiles) {
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Toggle button label translation
>>>>>>>>>>>>>>>> + my $toggle_tr = ($zonefiles{$name}{'enabled'} eq 'on') ? $Lang::tr{'click to disable'} : $Lang::tr{'click to enable'};
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>> + <td>$name</td>
>>>>>>>>>>>>>>>> + <td>$zonefiles{$name}{'url'}</td>
>>>>>>>>>>>>>>>> + <td>$zonefiles{$name}{'remark'}</td>
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_TOGGLE">
>>>>>>>>>>>>>>>> + <input type="image" src="/images/$zonefiles{$name}{'enabled'}.gif" title="$toggle_tr" alt="$toggle_tr">
>>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_EDIT">
>>>>>>>>>>>>>>>> + <input type="image" src="/images/edit.gif" title="$Lang::tr{'edit'}" alt="$Lang::tr{'edit'}">
>>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_REMOVE">
>>>>>>>>>>>>>>>> + <input type="image" src="/images/delete.gif" title="$Lang::tr{'remove'}" alt="$Lang::tr{'remove'}">
>>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Disable reload button if not needed
>>>>>>>>>>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>>> +</table>
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +<div class="right">
>>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="">
>>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="ZF_EDIT">$Lang::tr{'add'}</button>
>>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>>> +</div>
>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# Show zonefiles entry editor
>>>>>>>>>>>>>>>> +sub _print_zonefile_editor {
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Key specified: Edit existing entry
>>>>>>>>>>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>>>>>>>>>>> + # Load data to be edited, but don't override already present values (allows user to edit after error)
>>>>>>>>>>>>>>>> + $cgiparams{'ZF_NAME'} //= $action_key;
>>>>>>>>>>>>>>>> + $cgiparams{'ZF_URL'} //= $zonefiles{$action_key}{'url'};
>>>>>>>>>>>>>>>> + $cgiparams{'ZF_REMARK'} //= $zonefiles{$action_key}{'remark'};
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Fallback to empty form
>>>>>>>>>>>>>>>> + $cgiparams{'ZF_NAME'} //= "";
>>>>>>>>>>>>>>>> + $cgiparams{'ZF_URL'} //= "";
>>>>>>>>>>>>>>>> + $cgiparams{'ZF_REMARK'} //= "";
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf editor'});
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>> +<input type="hidden" name="KEY" value="$action_key">
>>>>>>>>>>>>>>>> +<table width="100%">
>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>> + <td width="20%">$Lang::tr{'name'}: <img src="/blob.gif" alt="*"></td>
>>>>>>>>>>>>>>>> + <td><input type="text" name="ZF_NAME" value="$cgiparams{'ZF_NAME'}" size="40" maxlength="32" title="$Lang::tr{'rpz zf remark info'}" pattern="[a-zA-Z0-9_]{1,32}" required></td>
>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>> + <td width="20%">URL: <img src="/blob.gif" alt="*"></td>
>>>>>>>>>>>>>>>> + <td><input type="url" name="ZF_URL" value="$cgiparams{'ZF_URL'}" size="40" maxlength="128" required></td>
>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>> + <td width="20%">$Lang::tr{'remark'}:</td>
>>>>>>>>>>>>>>>> + <td><input type="text" name="ZF_REMARK" value="$cgiparams{'ZF_REMARK'}" size="40" maxlength="32"></td>
>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>> + <td colspan="2"><hr></td>
>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>> + <td width="55%"><img src="/blob.gif" alt="*"> $Lang::tr{'required field'}</td>
>>>>>>>>>>>>>>>> + <td align="right"><button type="submit" name="ACTION" value="ZF_SAVE">$Lang::tr{'save'}</button></td>
>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>> +</table>
>>>>>>>>>>>>>>>> +</form>
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +<div class="right">
>>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="NONE">$Lang::tr{'back'}</button>
>>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>>> +</div>
>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# Show custom allow/block files and related gui elements
>>>>>>>>>>>>>>>> +sub _print_customlists {
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Load lists from config, unless they are currently being edited
>>>>>>>>>>>>>>>> + if($action ne 'CL_SAVE') {
>>>>>>>>>>>>>>>> + $cgiparams{'ALLOW_LIST'} = join("\n", @{$customlists{'allow'}{'list'}});
>>>>>>>>>>>>>>>> + $cgiparams{'BLOCK_LIST'} = join("\n", @{$customlists{'block'}{'list'}});
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + $cgiparams{'ALLOW_ENABLED'} = ($customlists{'allow'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>>>>>>>>>>> + $cgiparams{'BLOCK_ENABLED'} = ($customlists{'block'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Fallback to empty form
>>>>>>>>>>>>>>>> + $cgiparams{'ALLOW_LIST'} //= "";
>>>>>>>>>>>>>>>> + $cgiparams{'BLOCK_LIST'} //= "";
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # HTML checkboxes, unchecked = no or undef value in POST data
>>>>>>>>>>>>>>>> + my %checked = ();
>>>>>>>>>>>>>>>> + $checked{'ALLOW_ENABLED'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? " checked" : "";
>>>>>>>>>>>>>>>> + $checked{'BLOCK_ENABLED'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? " checked" : "";
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Disable reload button if not needed
>>>>>>>>>>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz cl'});
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>> +<table width="100%">
>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl allow'}</b><br>$Lang::tr{'rpz cl allow info'}</td>
>>>>>>>>>>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl block'}</b><br>$Lang::tr{'rpz cl block info'}</td>
>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>> + <td colspan="2"><textarea name="ALLOW_LIST" class="domainlist" cols="45">
>>>>>>>>>>>>>>>> +$cgiparams{'ALLOW_LIST'}</textarea></td>
>>>>>>>>>>>>>>>> + <td colspan="2"><textarea name="BLOCK_LIST" class="domainlist" cols="45">
>>>>>>>>>>>>>>>> +$cgiparams{'BLOCK_LIST'}</textarea></td>
>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>> + <td><label for="allow_enabled">$Lang::tr{'rpz cl allow enable'}</label></td>
>>>>>>>>>>>>>>>> + <td width="15%"><input type="checkbox" name="ALLOW_ENABLED" id="allow_enabled"$checked{'ALLOW_ENABLED'}></td>
>>>>>>>>>>>>>>>> + <td><label for="block_enabled">$Lang::tr{'rpz cl block enable'}</label></td>
>>>>>>>>>>>>>>>> + <td width="15%"><input type="checkbox" name="BLOCK_ENABLED" id="block_enabled"$checked{'BLOCK_ENABLED'}></td>
>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>> + <td colspan="4"><hr></td>
>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>> + <td align="right" colspan="4">
>>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="CL_SAVE">$Lang::tr{'save'}</button>
>>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>>>>> +</table>
>>>>>>>>>>>>>>>> +</form>
>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# Output javascript and extra gui elements
>>>>>>>>>>>>>>>> +sub _print_gui_extras {
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Apply/Restart button modifier key handler
>>>>>>>>>>>>>>>> + if(&_rpz_needs_reload()) {
>>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>>> +<script>
>>>>>>>>>>>>>>>> + // Commit modifier key handler
>>>>>>>>>>>>>>>> + (function(jq, document) {
>>>>>>>>>>>>>>>> + var keyEventsOn = false; // Keyboard events attached
>>>>>>>>>>>>>>>> + var keyModify = false; // Modifier key pressed
>>>>>>>>>>>>>>>> + var mouseHover = false; // Mouse over commit button
>>>>>>>>>>>>>>>> + var btnModified = false; // Button modified to "Restart"
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + // Document-level key events, enable only while cursor is over button
>>>>>>>>>>>>>>>> + function attachKeyEvents() {
>>>>>>>>>>>>>>>> + if(keyEventsOn) {
>>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> + keyEventsOn = true;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + jq(document).on("keydown.rpz", function(event) {
>>>>>>>>>>>>>>>> + if((!keyModify) && event.shiftKey) {
>>>>>>>>>>>>>>>> + keyModify = true;
>>>>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>>> + jq(document).on("keyup.rpz", function(event) {
>>>>>>>>>>>>>>>> + if(keyModify && (!event.shiftKey)) {
>>>>>>>>>>>>>>>> + keyModify = false;
>>>>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> + function removeKeyEvents() {
>>>>>>>>>>>>>>>> + keyModify = false;
>>>>>>>>>>>>>>>> + if(keyEventsOn) {
>>>>>>>>>>>>>>>> + jq(document).off("keydown.rpz keyup.rpz");
>>>>>>>>>>>>>>>> + keyEventsOn = false;
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + // Attach mouse hover events to commit buttons
>>>>>>>>>>>>>>>> + function attachMouseEvents() {
>>>>>>>>>>>>>>>> + jq("button.commit").on("mouseenter", function(event) {
>>>>>>>>>>>>>>>> + if(!mouseHover) {
>>>>>>>>>>>>>>>> + mouseHover = true;
>>>>>>>>>>>>>>>> + attachKeyEvents();
>>>>>>>>>>>>>>>> + // Handle already pressed key
>>>>>>>>>>>>>>>> + keyModify = !!(event.shiftKey);
>>>>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + // Cursor moved away: Disable key listener to minimize events
>>>>>>>>>>>>>>>> + jq("button.commit").on("mouseleave", function() {
>>>>>>>>>>>>>>>> + if(mouseHover) {
>>>>>>>>>>>>>>>> + mouseHover = false;
>>>>>>>>>>>>>>>> + removeKeyEvents();
>>>>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + // Modify commit button
>>>>>>>>>>>>>>>> + function handleModify() {
>>>>>>>>>>>>>>>> + let modify = mouseHover && keyModify;
>>>>>>>>>>>>>>>> + if(btnModified != modify) {
>>>>>>>>>>>>>>>> + if(modify) {
>>>>>>>>>>>>>>>> + jq("button.commit").text("$Lang::tr{'restart'}").val("UNB_RESTART");
>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>> + jq("button.commit").text("$Lang::tr{'rpz apply'}").val("RPZ_RELOAD");
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> + btnModified = modify;
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + // jQuery DOM ready
>>>>>>>>>>>>>>>> + jq(function() {
>>>>>>>>>>>>>>>> + attachMouseEvents();
>>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>>> + })(jQuery, document);
>>>>>>>>>>>>>>>> +</script>
>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>> + } # End of modifier key handler
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +###--- Internal action processing functions ---###
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# Toggle zonefile on/off
>>>>>>>>>>>>>>>> +sub _action_zf_toggle {
>>>>>>>>>>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + my $result = 0;
>>>>>>>>>>>>>>>> + my $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Perform toggle action
>>>>>>>>>>>>>>>> + if($enabled eq 'on') {
>>>>>>>>>>>>>>>> + $enabled = 'off';
>>>>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>> + $enabled = 'on';
>>>>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $action_key, $zonefiles{$action_key}{'url'}, '--no-reload');
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Save changes
>>>>>>>>>>>>>>>> + $zonefiles{$action_key}{'enabled'} = $enabled;
>>>>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# Remove zonefile
>>>>>>>>>>>>>>>> +sub _action_zf_remove {
>>>>>>>>>>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Remove from rpz-config if currently active
>>>>>>>>>>>>>>>> + if($zonefiles{$action_key}{'enabled'} eq 'on') {
>>>>>>>>>>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Remove from data hash and save changes
>>>>>>>>>>>>>>>> + delete $zonefiles{$action_key};
>>>>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Clear action_key, as the entry is now removed entirely
>>>>>>>>>>>>>>>> + $action_key = "";
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# Create or update zonefile entry
>>>>>>>>>>>>>>>> +# Returns undef if gui needs to stay in editor mode
>>>>>>>>>>>>>>>> +sub _action_zf_save {
>>>>>>>>>>>>>>>> + my $result = 0;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + my $name = $cgiparams{'ZF_NAME'} // "";
>>>>>>>>>>>>>>>> + my $url = $cgiparams{'ZF_URL'} // "";
>>>>>>>>>>>>>>>> + my $remark = $cgiparams{'ZF_REMARK'} // "";
>>>>>>>>>>>>>>>> + my $enabled = 'on'; # Enable new entries by default
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Note on variables:
>>>>>>>>>>>>>>>> + # name = unique key, will be used to address the entry
>>>>>>>>>>>>>>>> + # action_key = name of the entry being edited, empty for new entry
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Only check for unique name if it changed
>>>>>>>>>>>>>>>> + # (this also checks new entries because the action_key is empty in this case)
>>>>>>>>>>>>>>>> + $result = &_rpz_validate_zonefile($name, $url, $remark, (lc($name) ne lc($action_key)));
>>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 0);
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Edit existing entry: Determine what was changed
>>>>>>>>>>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>>>>>>>>>>> + # Name und URL remain unchanged, only save remark and finish
>>>>>>>>>>>>>>>> + if(($name eq $action_key) && ($url eq $zonefiles{$action_key}{'url'})) {
>>>>>>>>>>>>>>>> + $zonefiles{$action_key}{'remark'} = $remark;
>>>>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Entry was changed and needs to be recreated, preserve status
>>>>>>>>>>>>>>>> + $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Remove from rpz-config
>>>>>>>>>>>>>>>> + return unless &_action_zf_remove();
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Add new entry to rpz-config
>>>>>>>>>>>>>>>> + if($enabled eq 'on') {
>>>>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $name, $url, '--no-reload');
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Add to global data hash and save changes
>>>>>>>>>>>>>>>> + my %entry = ('enabled' => $enabled,
>>>>>>>>>>>>>>>> + 'url' => $url,
>>>>>>>>>>>>>>>> + 'remark' => $remark);
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# Save custom lists
>>>>>>>>>>>>>>>> +sub _action_cl_save {
>>>>>>>>>>>>>>>> + return unless((defined $cgiparams{'ALLOW_LIST'}) && (defined $cgiparams{'BLOCK_LIST'}));
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + my $result = 0;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + my @allowlist = split(/\R/, $cgiparams{'ALLOW_LIST'});
>>>>>>>>>>>>>>>> + my @blocklist = split(/\R/, $cgiparams{'BLOCK_LIST'});
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Validate lists
>>>>>>>>>>>>>>>> + $result = &_rpz_validate_customlist(\@allowlist);
>>>>>>>>>>>>>>>> + if($result != 0) {
>>>>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr(202, $result);
>>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> + $result = &_rpz_validate_customlist(\@blocklist);
>>>>>>>>>>>>>>>> + if($result != 0) {
>>>>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr(203, $result);
>>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Add to global data hash and save changes
>>>>>>>>>>>>>>>> + $customlists{'allow'}{'list'} = \@allowlist;
>>>>>>>>>>>>>>>> + $customlists{'block'}{'list'} = \@blocklist;
>>>>>>>>>>>>>>>> + $customlists{'allow'}{'enabled'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? 'on' : 'off';
>>>>>>>>>>>>>>>> + $customlists{'block'}{'enabled'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? 'on' : 'off';
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + &_customlists_save_conf();
>>>>>>>>>>>>>>>> + &_customlist_export('allow', $RPZ_ALLOWLIST);
>>>>>>>>>>>>>>>> + &_customlist_export('block', $RPZ_BLOCKLIST);
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Make new lists, request service reload on success
>>>>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-make', 'allowblock', '--no-reload');
>>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# Trigger rpz-config reload
>>>>>>>>>>>>>>>> +sub _action_rpz_reload {
>>>>>>>>>>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Immediately clear flag to prevent multiple reloads
>>>>>>>>>>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>>>>>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Perform reload, recreate reload flag on error to enable retry
>>>>>>>>>>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'reload');
>>>>>>>>>>>>>>>> + if(not &_rpz_check_result($result, 0)) {
>>>>>>>>>>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# Trigger unbound restart
>>>>>>>>>>>>>>>> +sub _action_unb_restart {
>>>>>>>>>>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Immediately clear flag to prevent multiple restarts
>>>>>>>>>>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>>>>>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Perform restart, unboundctrl always exits zero
>>>>>>>>>>>>>>>> + &General::system('/usr/local/bin/unboundctrl', 'restart');
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +###--- Internal rpz-config functions ---###
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# Translate rpz-config exitcodes and messages
>>>>>>>>>>>>>>>> +# 100-199: rpz-config, 200-299: webgui
>>>>>>>>>>>>>>>> +sub _rpz_error_tr {
>>>>>>>>>>>>>>>> + my ($error, $append) = @_;
>>>>>>>>>>>>>>>> + $append //= '';
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Translate numeric exit codes
>>>>>>>>>>>>>>>> + if(looks_like_number($error)) {
>>>>>>>>>>>>>>>> + if(defined $Lang::tr{"rpz exitcode $error"}) {
>>>>>>>>>>>>>>>> + $error = $Lang::tr{"rpz exitcode $error"};
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + return "RPZ $Lang::tr{'error'}: $error" . &Header::escape($append);
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# Check result of rpz-config system call, request reload on success
>>>>>>>>>>>>>>>> +sub _rpz_check_result {
>>>>>>>>>>>>>>>> + my ($result, $request_reload) = @_;
>>>>>>>>>>>>>>>> + $request_reload //= 0;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # exitcode 0 = success
>>>>>>>>>>>>>>>> + if($result != 0) {
>>>>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr($result);
>>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Set reload flag
>>>>>>>>>>>>>>>> + if($request_reload) {
>>>>>>>>>>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# Test whether reload flag is set
>>>>>>>>>>>>>>>> +sub _rpz_needs_reload {
>>>>>>>>>>>>>>>> + return (-f $RPZ_RELOAD_FLAG);
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# Validate a zonefile entry, returns rpz-config exitcode on failure. Use _rpz_check_result to verify.
>>>>>>>>>>>>>>>> +# unique = check for unique name
>>>>>>>>>>>>>>>> +sub _rpz_validate_zonefile {
>>>>>>>>>>>>>>>> + my ($name, $url, $remark, $unique) = @_;
>>>>>>>>>>>>>>>> + $unique //= 1;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + unless($name =~ /^[a-zA-Z0-9_]{1,32}$/) {
>>>>>>>>>>>>>>>> + return 101;
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> + unless($url =~ /^[\w+\.:;\/\\&@#%?=\-~|!]{1,128}$/) {
>>>>>>>>>>>>>>>> + return 105;
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> + unless($remark =~ /^[\w \-()\.:;*\/\\?!&=]{0,32}$/) {
>>>>>>>>>>>>>>>> + return 201;
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Check against already existing names
>>>>>>>>>>>>>>>> + if($unique) {
>>>>>>>>>>>>>>>> + foreach my $existing (keys %zonefiles) {
>>>>>>>>>>>>>>>> + if(lc($name) eq lc($existing)) {
>>>>>>>>>>>>>>>> + return 104;
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + return 0;
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# Validate a custom list, returns number of rejected line on failure. Check for non-zero results.
>>>>>>>>>>>>>>>> +# listref = array reference, cleanup = remove invalid entries instead of returning an error
>>>>>>>>>>>>>>>> +sub _rpz_validate_customlist {
>>>>>>>>>>>>>>>> + my ($listref, $cleanup) = @_;
>>>>>>>>>>>>>>>> + $cleanup //= 0;
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + foreach my $index (reverse 0..$#{$listref}) {
>>>>>>>>>>>>>>>> + my $row = @$listref[$index];
>>>>>>>>>>>>>>>> + next unless($row); # Skip/allow empty lines
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Reject/remove everything besides wildcard domains and remarks
>>>>>>>>>>>>>>>> + if((not &General::validwildcarddomainname($row)) && (not $row =~ /^;[\w \-()\.:;*\/\\?!&=]*$/)) {
>>>>>>>>>>>>>>>> + unless($cleanup) {
>>>>>>>>>>>>>>>> + # +1 for user friendly line number and to ensure non-zero exitcode
>>>>>>>>>>>>>>>> + return $index + 1;
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Remove current row
>>>>>>>>>>>>>>>> + splice(@$listref, $index, 1);
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + return 0;
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +###--- Internal misc functions ---###
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# Send HTTP 303 redirect headers for post/request/get pattern
>>>>>>>>>>>>>>>> +# (Must be sent before calling &Header::showhttpheaders())
>>>>>>>>>>>>>>>> +sub _http_prg_redirect {
>>>>>>>>>>>>>>>> + my $location = "https://$ENV{'SERVER_NAME'}:$ENV{'SERVER_PORT'}$ENV{'SCRIPT_NAME'}";
>>>>>>>>>>>>>>>> + print "Status: 303 See Other\n";
>>>>>>>>>>>>>>>> + print "Location: $location\n";
>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>> diff --git a/lfs/rpz b/lfs/rpz
>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>> index 000000000..7ddbc38e5
>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>> +++ b/lfs/rpz
>>>>>>>>>>>>>>>> @@ -0,0 +1,96 @@
>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>> +# Definitions
>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +include Config
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +SUMMARY = response policy zone - RPZ reputation system for unbound DNS
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +VER = 1.0.0
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +THISAPP = rpz-$(VER)
>>>>>>>>>>>>>>>> +DIR_APP = $(DIR_SRC)/$(THISAPP)
>>>>>>>>>>>>>>>> +TARGET = $(DIR_INFO)/$(THISAPP)
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +PROG = rpz
>>>>>>>>>>>>>>>> +PAK_VER = 18
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +DEPS =
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +SERVICES =
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>> +# Top-level Rules
>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +install : $(TARGET)
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +check :
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +download :
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +b2 :
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +dist:
>>>>>>>>>>>>>>>> + @$(PAK)
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>> +# Installation Details
>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +$(TARGET) :
>>>>>>>>>>>>>>>> + @$(PREBUILD)
>>>>>>>>>>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # RPZ scripts
>>>>>>>>>>>>>>>> + install --verbose --mode=755 \
>>>>>>>>>>>>>>>> + $(DIR_CONF)/rpz/{rpz-config,rpz-metrics,rpz-sleep,rpz-make,rpz-functions} \
>>>>>>>>>>>>>>>> + --target-directory=/usr/sbin
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # RPZ config files
>>>>>>>>>>>>>>>> + mkdir -pv /etc/unbound/local.d
>>>>>>>>>>>>>>>> + install --verbose --mode=644 --owner=nobody --group=nobody \
>>>>>>>>>>>>>>>> + $(DIR_CONF)/rpz/00-rpz.conf \
>>>>>>>>>>>>>>>> + --target-directory=/etc/unbound/local.d
>>>>>>>>>>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/local.d
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # RPZ custom list files for allow and block
>>>>>>>>>>>>>>>> + mkdir -pv /var/ipfire/dns/rpz
>>>>>>>>>>>>>>>> + touch /var/ipfire/dns/rpz/{allowlist,blocklist}
>>>>>>>>>>>>>>>> + chown --verbose --recursive nobody:nobody /var/ipfire/dns/rpz
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # RPZ zone files
>>>>>>>>>>>>>>>> + # create empty RPZ config file to avoid a unbound config error
>>>>>>>>>>>>>>>> + mkdir -pv /etc/unbound/zonefiles
>>>>>>>>>>>>>>>> + touch /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/zonefiles
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Install addon-specific language-files
>>>>>>>>>>>>>>>> + install --verbose --mode=004 $(DIR_CONF)/rpz/rpz.*.pl \
>>>>>>>>>>>>>>>> + --target-directory=/var/ipfire/addon-lang
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + # Install backup definition
>>>>>>>>>>>>>>>> + cp -vf $(DIR_CONF)/backup/includes/rpz /var/ipfire/backup/addons/includes/rpz
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>>>>>>>>>>> + @$(POSTBUILD)
>>>>>>>>>>>>>>>> diff --git a/make.sh b/make.sh
>>>>>>>>>>>>>>>> index 827ea9e77..a77535b13 100755
>>>>>>>>>>>>>>>> --- a/make.sh
>>>>>>>>>>>>>>>> +++ b/make.sh
>>>>>>>>>>>>>>>> @@ -390,7 +390,7 @@ prepareenv() {
>>>>>>>>>>>>>>>> if [ "${free_space}" -lt "${required_space}" ]; then
>>>>>>>>>>>>>>>> # Add any consumed space
>>>>>>>>>>>>>>>> while read -r consumed_space path; do
>>>>>>>>>>>>>>>> - (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>>>>>>>>>>> + (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>>>>>>>>>>> done <<< "$(du --summarize --bytes "${BUILD_DIR}" "${IMAGES_DIR}" "${LOG_DIR}" 2>/dev/null)"
>>>>>>>>>>>>>>>> fi
>>>>>>>>>>>>>>>> @@ -2087,6 +2087,7 @@ build_system() {
>>>>>>>>>>>>>>>> lfsmake2 btrfs-progs
>>>>>>>>>>>>>>>> lfsmake2 inotify-tools
>>>>>>>>>>>>>>>> lfsmake2 grub-btrfs
>>>>>>>>>>>>>>>> + lfsmake2 rpz
>>>>>>>>>>>>>>>> lfsmake2 linux
>>>>>>>>>>>>>>>> lfsmake2 rtl8812au
>>>>>>>>>>>>>>>> diff --git a/src/paks/rpz/install.sh b/src/paks/rpz/install.sh
>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>> index 000000000..ef99bf742
>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>> +++ b/src/paks/rpz/install.sh
>>>>>>>>>>>>>>>> @@ -0,0 +1,36 @@
>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>> +#
>>>>>>>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>>>>>>>> +extract_files
>>>>>>>>>>>>>>>> +restore_backup ${NAME}
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# fix user created files
>>>>>>>>>>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>>>>>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>>>>>>>>>>> + /etc/unbound/zonefiles \
>>>>>>>>>>>>>>>> + /etc/unbound/local.d
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# Update Language cache
>>>>>>>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# restart unbound to load config file
>>>>>>>>>>>>>>>> +/etc/init.d/unbound restart
>>>>>>>>>>>>>>>> diff --git a/src/paks/rpz/uninstall.sh b/src/paks/rpz/uninstall.sh
>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>> index 000000000..e11427df3
>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>> +++ b/src/paks/rpz/uninstall.sh
>>>>>>>>>>>>>>>> @@ -0,0 +1,38 @@
>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>> +#
>>>>>>>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>>>>>>>>>>> +/etc/init.d/unbound stop
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +make_backup ${NAME}
>>>>>>>>>>>>>>>> +remove_files
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>>>>>>>>>>> +# "[1723428668] unbound-control[17117:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>>>>>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# Update Language cache
>>>>>>>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# start unbound to load unbound config file
>>>>>>>>>>>>>>>> +/etc/init.d/unbound start
>>>>>>>>>>>>>>>> diff --git a/src/paks/rpz/update.sh b/src/paks/rpz/update.sh
>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>> index 000000000..9bc340bc6
>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>> +++ b/src/paks/rpz/update.sh
>>>>>>>>>>>>>>>> @@ -0,0 +1,52 @@
>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>> +#
>>>>>>>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# from update.sh
>>>>>>>>>>>>>>>> +extract_backup_includes
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>>>>>>>>>>> +/etc/init.d/unbound stop
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# from uninstall.sh
>>>>>>>>>>>>>>>> +make_backup ${NAME}
>>>>>>>>>>>>>>>> +remove_files
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>>>>>>>>>>> +# "unbound-control[nn:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>>>>>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# from install.sh
>>>>>>>>>>>>>>>> +extract_files
>>>>>>>>>>>>>>>> +restore_backup ${NAME}
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# fix user created files
>>>>>>>>>>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>>>>>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>>>>>>>>>>> + /etc/unbound/zonefiles \
>>>>>>>>>>>>>>>> + /etc/unbound/local.d
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# Update Language cache
>>>>>>>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>> +# restart unbound to load config files
>>>>>>>>>>>>>>>> +/etc/init.d/unbound start
>>>>>>>>>>>>>>>> --
>>>>>>>>>>>>>>>> 2.39.5
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Jon
>>>>>>>>>>>>>> --
>>>>>>>>>>>>>> Jon Murphy
>>>>>>>>>>>>>> jon.murphy@ipfire.org
>>>>>>>>>>>>
>>>>>>>>>>>> Jon
>>>>>>>>>>>> --
>>>>>>>>>>>> Jon Murphy
>>>>>>>>>>>> jon.murphy@ipfire.org
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re[2]: [PATCH] RPZ: update code to include WEBGUI and additional languages
2025-03-24 14:36 ` Michael Tremer
@ 2025-03-24 14:38 ` Jon Murphy
2025-03-24 14:40 ` Michael Tremer
2025-03-24 14:49 ` Bernhard Bitsch
1 sibling, 1 reply; 30+ messages in thread
From: Jon Murphy @ 2025-03-24 14:38 UTC (permalink / raw)
To: Michael Tremer; +Cc: Bernhard Bitsch, IPFire: Development-List
Actually it did.
Why do you think Unbound did not?
------ Original Message ------
From "Michael Tremer" <michael.tremer@ipfire.org>
To "Jon Murphy" <jon.murphy@ipfire.org>
Cc "Bernhard Bitsch" <bbitsch@ipfire.org>; "IPFire: Development-List"
<development@lists.ipfire.org>
Date 3/24/2025 9:36:53 AM
Subject Re: [PATCH] RPZ: update code to include WEBGUI and additional
languages
>Unbound did not put those there...
>
>> On 24 Mar 2025, at 14:33, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>
>>
>>
>> And where are these stored?
>>
>> In `/etc/unbound/zonefiles`:
>>
>>
>> [root@ipfire ~] # ls -al /etc/unbound/zonefiles
>> total 20664
>> drwxr-xr-x 2 nobody nobody 4096 Mar 24 04:40 .
>> drwxr-xr-x 4 root root 4096 Mar 19 16:24 ..
>> -rw-r--r-- 1 nobody nobody 3999087 Mar 23 15:11 adhocSB.rpz
>> -rw-r--r-- 1 nobody nobody 1411 Mar 23 14:23 allow.rpz
>> -rw-r--r-- 1 nobody nobody 25355 Mar 24 04:40 AmazonTrkrHZ.rpz
>> -rw-r--r-- 1 nobody nobody 7241 Mar 24 04:40 AppleTrkrHZ.rpz
>> -rw-r--r-- 1 nobody nobody 178 Mar 23 14:23 block.rpz
>> -rw-r--r-- 1 nobody nobody 78496 Mar 24 04:40 DOHblockHZ.rpz
>> -rw-r--r-- 1 nobody nobody 16983551 Mar 24 04:40 MxProPlusHZ.rpz
>> -rw-r--r-- 1 nobody nobody 2893 Mar 24 04:40 tldHZ.rpz
>> -rw-r--r-- 1 nobody nobody 29419 Mar 24 04:40 WinTrkrHZ.rpz
>> [root@ipfire ~] #
>>
>>
>>
>> ------ Original Message ------
>> From "Michael Tremer" <michael.tremer@ipfire.org>
>> To "Bernhard Bitsch" <bbitsch@ipfire.org>
>> Cc development@lists.ipfire.org
>> Date 3/24/2025 9:25:40 AM
>> Subject Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
>>
>>> Hello,
>>>
>>>>
>>>> On 24 Mar 2025, at 13:33, Bernhard Bitsch <bbitsch@ipfire.org> wrote:
>>>> Am 24.03.2025 um 11:17 schrieb Michael Tremer:
>>>>>
>>>>>
>>>>> Hello Jon,
>>>>>>
>>>>>>
>>>>>> On 24 Mar 2025, at 00:00, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>> Michael,
>>>>>> FYI - I was wrong Unbound RPZ is _not_ watching the serial number, it is watching the "refresh", the number after the serial number.
>>>>>
>>>>> Refresh just tells the client how often to check for an update.
>>>>> If that is actually being set by the list publisher, then we have another problem here, because they could put some insanely low value there and we would then DDoS their infrastructure. I think we should keep it like we have it in other places that we control how often we want to check or pull for updates.
>>>>>
>>>> You are right. But an extra update process wastes additional processor time. The update mechanism of unbound does the check for update ( however it is realized ) nevertheless.
>>>
>>> Yes, doing more things needs resources. But we are not seriously considering whether an IPFire system has enough resources to perform the download of a text file, or are we?
>>>
>>>>
>>>>>> I understand that you don’t speak C, but you got the information from somewhere. Documentation maybe? Since that is out of date very often I like to consult the code.
>>>>>> From testing. Downloading rpz files using rpz unbound, and watching what happens. If the rpz file is setup for "once per day" refresh, then it only downloads one time.
>>>>>> However that won’t solve our problem . . . and having no cache.
>>>>>> In `/etc/unbound/tuning.conf` there is `rrset-cache-size: 128m`. Are you referring to a different cache.
>>>>>
>>>>> Naturally unbound is loading the zone into its memory which we generally call cache.
>>>>> When I say cache I am thinking about persistent data storage across multiple restarts of Unbound. If I am downloading 100 MiB of RPZ lists (which is presumably still on the lower end) and I reboot my firewall, I do not want to download the same data again. We can only ever download a list *once* unless we are 100% certain that it has changed. Then we can download it once again.
>>>>
>>>> The RPZ lists are stored in files in persistent storage. Unbound creates the internal cache from these.
>>>
>>> And where are these stored?
>>>
>>>>
>>>>>> Maybe we need to implement both?
>>>>>> Yes. There are very few AXFR list (I think only four were found). And many more HTTPS rpz files.
>>>>>> Jon
>>>>>> ------ Original Message ------
>>>>>> From "Michael Tremer" <michael.tremer@ipfire.org>
>>>>>> To "Jon Murphy" <jon.murphy@ipfire.org>
>>>>>> Cc "IPFire: Development-List" <development@lists.ipfire.org>
>>>>>> Date 3/20/2025 11:26:43 AM
>>>>>> Subject Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
>>>>>>
>>>>>>>
>>>>>>> Hello Jon,
>>>>>>> Please don’t forget to Cc the list...
>>>>>>>
>>>>>>>>
>>>>>>>> On 19 Mar 2025, at 18:27, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>> Michael,
>>>>>>>>
>>>>>>>>>
>>>>>>>>> Where in the code is this implemented? I cannot find anything like this:
>>>>>>>>
>>>>>>>> Keep in mind I am not a "C" person. Maybe in this section?:
>>>>>>>> https://git.ipfire.org/?p=thirdparty/unbound.git;a=blob;f=services/authzone.c;hb=30b9cb5f813003d0a2b1c2e678652396615b1b7d#l5875
>>>>>>>
>>>>>>> This where the AXFR response is being handled when doing a DNS zone transfer. This code is not being called when performing a HTTP download.
>>>>>>> I understand that you don’t speak C, but you got the information from somewhere. Documentation maybe? Since that is out of date very often I like to consult the code.
>>>>>>>
>>>>>>>>
>>>>>>>> —
>>>>>>>> When I was just learning about RPZ I created a separate RPZ file for testing. When I changed the SOA line with a new serial number, the RPZ file download would happen in about 5 minutes.
>>>>>>>> https://people.ipfire.org/~jon/sblack-adhoc.rpz
>>>>>>>
>>>>>>> It might well be that the file is not being reloaded if the download matches the content that unbound already has. That would of course save some resources.
>>>>>>> However that won’t solve our problem with redundant downloads and having no cache.
>>>>>>>
>>>>>>>>
>>>>>>>> That is how I found out the SOA line is watched for a serial number change.
>>>>>>>> I’ll reconfirm my findings.
>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>> The second reason is that we have a lot of firewalls out there. Not all of them will enable this feature and all of the lists, but even if it is a good chunk, we will generate terabytes of traffic which put load on the infrastructure and will cost money. It simply is not what we want to do, regardless of self-hosting those lists and pulling them from somewhere else.
>>>>>>>>
>>>>>>>> So I understand, are you thinking of hosting RPZ AXFR (DNS zone transfer) on IPFire infrastructure?
>>>>>>>
>>>>>>> No, I don’t think that we can generally do this. The biggest problem is licensing as we cannot take anyones content and host it ourselves. We would re-distribute those lists and that will only work with permission of the publishers. I assume that would be too much work to actually get some useful content out there. We might limit ourselves to only those lists that are under a very permissive license. Nobody wants that.
>>>>>>> From a technical point of view, DNS over TCP might not be very nice in terms of forging the transfer and so we would need TLS as well… It should work, but even if we would be able to encourage other people to publish their lists I doubt they would implement DNS over TLS for authoritative DNS. That standard is in very early stages as well.
>>>>>>> As far as I can see, those vendors who offer a list as a commercial product are using DNS to distribute it (e.g. Spamhaus). Those people who have made this all a hobby are throwing the lists onto GitHub and let them handle the traffic.
>>>>>>> Maybe we need to implement both?
>>>>>>> -Michael
>>>>>>>
>>>>>>>>
>>>>>>>> Jon
>>>>>>>> On 3/19/25 5:35 AM, Michael Tremer wrote:
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> Hello Jon,
>>>>>>>>> Where in the code is this implemented? I cannot find anything like this:
>>>>>>>>> Unbound loads the entire file into memory and then starts parsing it. The only special treatment there is is to check whether the first line is a valid zone entry. It does not even have to be a SOA record.
>>>>>>>>> https://git.ipfire.org/?p=thirdparty/unbound.git;a=blob;f=services/authzone.c;hb=30b9cb5f813003d0a2b1c2e678652396615b1b7d#l1188
>>>>>>>>> I am also concerned that Unbound will not be able to support an upstream proxy for any downloads. The caching situation is also unclear for me, so I believe that we will be looking at writing a custom downloader that implements all these things.
>>>>>>>>> -Michael
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> On 19 Mar 2025, at 02:58, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>>>> Michael,
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> The emphasis is on the repeated downloads of the same list. That is
>>>>>>>>>>
>>>>>>>>>> > what cannot happen.
>>>>>>>>>> The Unbound RPZ code, as installed within IPFire, watches for a change
>>>>>>>>>> in the SOA line of each RPZ file. This is an example of the first few
>>>>>>>>>> lines for every RPZ file.
>>>>>>>>>> $TTL 300
>>>>>>>>>> @ SOA localhost. root.localhost. 1742298960 43200 3600 86400 300
>>>>>>>>>> NS localhost.
>>>>>>>>>> ;
>>>>>>>>>> ; Title: HaGeZi's Pop-Up Ads DNS Blocklist
>>>>>>>>>> ; Description: Blocks annoying and malicious pop-up ads.
>>>>>>>>>> If the SOA serial number changes (e.g. the 1742298960), then Unbound RPZ
>>>>>>>>>> code does its thing and downloads. Otherwise there is no download.
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> So there has to be a way to ensure that we won’t download a list again
>>>>>>>>>>
>>>>>>>>>> > unless it has actually changed.
>>>>>>>>>> This should do what you want but I may be missing your point.
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> DNS has a builtin functionality called AXFR. It simply does the job
>>>>>>>>>>
>>>>>>>>>> > for you. I was just wondering whether that was not being used.
>>>>>>>>>> I need to read about AXFR/IXFR and learn a little more.
>>>>>>>>>> Jon
>>>>>>>>>> On 3/17/25 5:35 AM, Michael Tremer wrote:
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> Good Morning Jon,
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>> On 16 Mar 2025, at 17:00, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>>>>>> Michael,
>>>>>>>>>>>> I was reading through you response again an I want to understand this post:
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>> I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>>>>>>>>>>>
>>>>>>>>>>>> So if RPZ doesn't use HTTPS, what is it using? I am missing a key point here.
>>>>>>>>>>>
>>>>>>>>>>> The emphasis is on the repeated downloads of the same list. That is what cannot happen.
>>>>>>>>>>> Although it might not affect a lot of people in our general user-base, there are some that have a metered connection and will pay for data by volume. Some of the lists I looked at are just under 20 MiB. Therefore we need to keep any traffic down to a minimum. The second reason is that we have a lot of firewalls out there. Not all of them will enable this feature and all of the lists, but even if it is a good chunk, we will generate terabytes of traffic which put load on the infrastructure and will cost money. It simply is not what we want to do, regardless of self-hosting those lists and pulling them from somewhere else.
>>>>>>>>>>> So there has to be a way to ensure that we won’t download a list again unless it has actually changed.
>>>>>>>>>>> DNS has a builtin functionality called AXFR. It simply does the job for you. I was just wondering whether that was not being used.
>>>>>>>>>>> HTTPS is an option because that is simply what we use elsewhere, but extra functionality will have to be built for it.
>>>>>>>>>>> -Michael
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>> Jon
>>>>>>>>>>>> On 2/13/25 3:34 PM, jon wrote:
>>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>> Michael,
>>>>>>>>>>>>> I’ve read through your comments a few times and I ended up with many more questions.
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>>>>>>>>>>>
>>>>>>>>>>>>> To me the efforts to get new code accepted seem to have changed and it seemed easier in the past. In the past I made the Core Team aware via the Dev Mailing List and wrote a simple two or three paragraphs of "What is it? / What is the value? / Here is the code"
>>>>>>>>>>>>> So in an effort to move forward: How exactly is something presented to the Core Team?
>>>>>>>>>>>>> Is there an example of a recent effort that was presented that I can see as a sample? (This type of info can also be added to the Wiki)
>>>>>>>>>>>>> I understand you want it this way, but I don’t know what exactly is needed. Please be specific.
>>>>>>>>>>>>> Jon
>>>>>>>>>>>>> PS - I am not ignoring your other comments, I am just trying to move forward and keep things simple.
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> On Feb 8, 2025, at 1:27 PM, Michael Tremer <michael.tremer@ipfire.org> wrote:
>>>>>>>>>>>>>> Hello Jon,
>>>>>>>>>>>>>> Thanks for your reply. And good that you are copying everyone into this conversation.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> On 8 Feb 2025, at 18:41, jon <jon.murphy@ipfire.org> wrote:
>>>>>>>>>>>>>>> Michael,
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> Yes, this has been a separate effort (a very public separate effort). Yes, as you pointed this out early on with the "proof-of-concept" and then my request for people to help test RPZ. Nothing was hidden.
>>>>>>>>>>>>>>> This was done because you (and maybe others) did not have the time and I wanted to help and because I needed assistance with RPZ. I tried my best to do this without bothering you.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> I don’t that it is accurate that nobody wanted to help on this. The list was always open - although not every email has been replied to swiftly it is also your responsibility to raise a question again if it was missed. People here have open ears.
>>>>>>>>>>>>>> It was also stated on this very list on in our documentation that working on something without involving the core team is a risky undertaking. Of course IPFire is free software and so everyone is free to fork if they wish to do so.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> You were aware many steps along the way. See your email on July 28, 2024, August 15, 2024, September 30, 2024, December 23, 2024, and January 16. My attempts to get the team involved were met with "things are busy" and sometimes silence. (Yes, I get it, people are busy.)
>>>>>>>>>>>>>>> You and Adolf, Leo, Erik and Bernhard have been aware since the beginning. You mention you were aware of the "proof-of-concept". If you include those beginning posts, since Sep 2023.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Yes, I am aware of a proof-of-concept that I have been running myself for a long time. I am also aware of the efforts that you have been taking.
>>>>>>>>>>>>>> Yet I don’t think there has ever been any joint effort, or am I seeing that wrong?
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> This has not been discussed . . . on our calls.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> On the July 28th you stated:
>>>>>>>>>>>>>>> "We have talked about RPZ many times on the monthly call since the URL filter feature is falling more and more out of fashion. I think there is also many posts about this on the forum."
>>>>>>>>>>>>>>> Please don’t insult me again by stating "you know what I mean".
>>>>>>>>>>>>>>> And it has been discussed but not documented in the Monthly Meeting notes.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> I am not at all insulting you. I don’t want to take this down to a personal level at all. This is a public mailing list and people who read this don’t need to listen to an argument we are having. They are here for the tech inside IPFire.
>>>>>>>>>>>>>> When I wrote that it has not been discussed that does not mean that we have not been touching on the topic. We have been talking about lots of things on the calls, the weather, politics, how our pets are. None of that makes it to the logs. What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> Regular conversation on the Dev Mailing list is many times met with silence. I get it, people are busy.
>>>>>>>>>>>>>>> And regular two-way conversation doesn’t happen on the list. At least not with me. I’d be happy to point out the posts that were met with silence.
>>>>>>>>>>>>>>> Again, I get it, people are busy.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> And you think my emails are not being met with silence? This has nothing to do with this specific topic. This has something to do with how occupied people are and how engaged they are on certain topics. Not everyone is involved in all the things and simply will ignore emails simply based on their subject line.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> But the "dip here to the list" were my attempts to get a conversation started. As I said, many time met with silence.
>>>>>>>>>>>>>>> The only place I was not met with silence was on the Community. You have a great group of people in the Community. It is a shame you don’t want to have others help. It would reduce your workload.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> You should stop making statements that are not true. Who doesn’t want anyone to help?
>>>>>>>>>>>>>> Not having this conversation on a Saturday evening would reduce my workload. At least it would free up time for something else. Helping with the things that are already on the go would reduce the workload of the entire team. Starting one thing at a time and finishing it is a lot better to manage than starting a hundred things and not even finish one. I can tell you that I already have a hundred things on the go.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> Therefore, what am I supposed to do with this email?
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> To me it is beyond obvious…
>>>>>>>>>>>>>>> If it isn’t what you want, then guide me with how to do this the correct way. And be specific. I am trying to help. I am trying to make things better. I am trying to do things the right way.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> To me it isn’t. This is yet another project that has been dumped to the list like so many before and later on everyone has left to have the team deal with the rest.
>>>>>>>>>>>>>> It is a huge patch set. You explained what the vision is, but that is about it. There is no chance this will continue if this disagreement isn’t solved first. I didn’t even look at the code.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> I don’t want to merge code that I don’t agree with.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> I asked multiple times if you "agreed with the concept" and again, met with silence. Yes I get it, people are busy.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Having support for RPZ? Yes, it was definitely on the roadmap. That I agree with.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> You mentioned this a in the past, but for some reason you do not disclose what I dismissed. Why do you continue to make this harder, wouldn’t it not be easier to tell me what I have dismissed?
>>>>>>>>>>>>>>> I have sent multiple emails trying to answer your concerns and comments. On July 28, Aug 14, Aug 22, Aug 23, Sep 30, etc.
>>>>>>>>>>>>>>> I’ve gone through all of the questions you asked and I cannot find a "dismissed" item.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Maybe I need to be *more clear*. I feel humoured by this.
>>>>>>>>>>>>>> It is late on a Saturday and I want my dinner soon, but certainly I have stated that this should never be an add-on considering it is supposed to replace URL Filter. We should never allow people to add their own sources. I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> The maintainers of Unbound and/or RPZ?
>>>>>>>>>>>>>>> The maintainers of Hagezi list, the threatfox list, the urlhaus list, etc.?
>>>>>>>>>>>>>>> What else? The maintainers or the RPZ scripts? That is me. Let’s talk!
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> You. I don’t care much about the providers of the lists.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> See, this is where it gets confusing. There are hundreds of open source packages as part of IPFire. Pick the last five years of items added to the IPFire build. You're telling me you have "constructive conversation with the maintainers" of all of the added packages?
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> They publish their software and they don’t care whether I am pulling it or not. They publish it with the commitment to maintain it - sometimes for better and sometimes for worse.
>>>>>>>>>>>>>> You care about me pulling your code and I don’t know whether you would commit to maintain this.
>>>>>>>>>>>>>> These two are very different cases.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> Pick the IP Blocklists list (i.e., 3CORESEC, ABUSECH, DSHIELD, SPAMHAUS, etc.) or the Suricata lists (i.e.,Emergingthreats.net <http://emergingthreats.net/>,Abuse.ch <http://abuse.ch/>, etc.). So you’ve have "constructive conversation with the maintainers"?
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Yes, occasionally I have phone calls with a few of these providers.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> Ha! Yes a surprise. In the beginning you seemed interested as IPFire needed a replacement for URL Filter. You asked good questions about the lists picked, asked for the value to the users, etc. And I answered the best I could.
>>>>>>>>>>>>>>> You even asked: “Why is this realised as an add-on and not part of the core system?” from your Jul 28, 2024 email.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Ah, so, why is the patch creating an add-on? Not that I am saying that what I say is law, but it has not been challenged either. If my input is being ignored, why should I put this to the top of my list of priorities? I am not disappointed about this, just trying to be very good with my time.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> And on January 16, 2025 I wrote a message looking for help. And you were kind to respond quickly. So in three weeks time, since the kind response, something has changed. You went from supportive to "this".
>>>>>>>>>>>>>>> So yes, I am surprised.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Well, maybe I should not have replied to that email. It was clear that you were on some path that was not right, but you were not interested before in finding the right path from the beginning.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> Be more specific, what has to change? What exactly did I dismiss?
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Dismissal is just my assumption. I don’t know what you actually did with my feedback. I can only see the end product that does not seem contain much of it. Repeatedly I have been pointing out that we should think before we build. I am sure a lot of hours have now gone into some code that simply does not satisfy me. And I am not not talking about the code itself, what it does is what I don’t think is right for us.
>>>>>>>>>>>>>> The process is very clear for me that we should first of all think whether we want a certain feature now. Then there should be a clear roadmap for everyone to follow; tasks can be split-up as we go and hopefully then have something that is maintainable, interesting for our users and even would do us proud. This is how this should work.
>>>>>>>>>>>>>> So, what has to change? I don’t think with shouting at each other, throwing patches around and making me generally unhappy is a good start.
>>>>>>>>>>>>>> -Michael
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> Jon
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> On Feb 6, 2025, at 2:13 PM, Michael Tremer <michael.tremer@ipfire.org> wrote:
>>>>>>>>>>>>>>>> Hello Jon,
>>>>>>>>>>>>>>>> Well, here we are again with another patch regarding this feature.
>>>>>>>>>>>>>>>> I cannot quite see from your email what the question is, but if this is a request to have this merged into IPFire, I am once again sorry to disappoint you.
>>>>>>>>>>>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls. Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation. Therefore, what am I supposed to do with this email?
>>>>>>>>>>>>>>>> I don’t want to merge code that I don’t agree with. So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>>>>>>>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>>>>>>>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>>>>>>>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>>>>>>>>>>>> All the best,
>>>>>>>>>>>>>>>> -Michael
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> On 6 Feb 2025, at 16:35, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>>>>>>>>>>> What is it?
>>>>>>>>>>>>>>>>> Response Policy Zone (RPZ) is a mechanism to define local policies in a
>>>>>>>>>>>>>>>>> standardized way and load those policies from external sources.
>>>>>>>>>>>>>>>>> Bottom line: RPZ allows admins to easily block access to websites via DNS lookup.
>>>>>>>>>>>>>>>>> RPZ can block websites via categories. Examples include: fake websites, annoying
>>>>>>>>>>>>>>>>> pop-up ads, newly registered domains, DoH bypass sites, bad "host" services,
>>>>>>>>>>>>>>>>> maliscious top level domains (e.g., *.zip, *.mov), piracy, gambling, pornography,
>>>>>>>>>>>>>>>>> and more. RPZ lists come from various RPZ providers and their available
>>>>>>>>>>>>>>>>> catagories.
>>>>>>>>>>>>>>>>> This RPZ add-on enables the RPZ functionality by adding a couple lines in a
>>>>>>>>>>>>>>>>> configuration file. This add-on simply adds configuration files and adds
>>>>>>>>>>>>>>>>> scripts (config, metrics and sleep) to make RPZ easier for the admin to use.
>>>>>>>>>>>>>>>>> The RPZ scripts include additional languages: German, Spanish, French, Turkish,
>>>>>>>>>>>>>>>>> and Italian.
>>>>>>>>>>>>>>>>> RPZ itself was release in 2010 and has been part of the IPFire build since ~2015.
>>>>>>>>>>>>>>>>> Why is it needed? What is its value?
>>>>>>>>>>>>>>>>> - The RPZ concept places this filtering into IPFire, our internet access
>>>>>>>>>>>>>>>>> gateway, which is (should be) solely used as DNS source of the internal network.
>>>>>>>>>>>>>>>>> - As most sites use HTTPS it makes it difficult to filter traffic with URL
>>>>>>>>>>>>>>>>> Filter without also properly configuring conventional (non-transparent)
>>>>>>>>>>>>>>>>> mode on the proxy. RPZ is a nice replacement for the URL Filter.
>>>>>>>>>>>>>>>>> - No need to install and maintain an additional device like PiHole or AdBlock
>>>>>>>>>>>>>>>>> browser extensions on multiple user devices.
>>>>>>>>>>>>>>>>> - This is an additional layer of protection for users. Less worry someone will
>>>>>>>>>>>>>>>>> click on something that gets them into trouble. And, saying this with emphasis,
>>>>>>>>>>>>>>>>> the ability to do it in one place!
>>>>>>>>>>>>>>>>> - Blocked sites save on unneeded traffic and can lessen the threat of malware
>>>>>>>>>>>>>>>>> in advertisements
>>>>>>>>>>>>>>>>> - Logging allows the admin to see the site blocked and take actions
>>>>>>>>>>>>>>>>> - RPZ will be used at the home, home-office (work from home), schools,
>>>>>>>>>>>>>>>>> ministerial, and at the office. Device counts are small (2-6) to medium (~80)
>>>>>>>>>>>>>>>>> to mediam-large (200+).
>>>>>>>>>>>>>>>>> - RPZ can block ads, popups, phishing, scammers, spyware, malware, annoying
>>>>>>>>>>>>>>>>> popups, NSFW links, DOH servers, and the usual internet trash.
>>>>>>>>>>>>>>>>> ------------------------------
>>>>>>>>>>>>>>>>> Change Log for RPZ add-on
>>>>>>>>>>>>>>>>> rpz-1.0.0-18 on 2025-02-05
>>>>>>>>>>>>>>>>> - Build for approval & release as IPFire add-on
>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>> rpz-beta-0.1.18-18.ipfire on 2025-02-01
>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>> - new feature: added a mod key to force a unbound restart
>>>>>>>>>>>>>>>>> rpz-config and rpz-make:
>>>>>>>>>>>>>>>>> - new feature: added action for unbound restart `rpz-config unbound-restart`
>>>>>>>>>>>>>>>>> rpz-metrics:
>>>>>>>>>>>>>>>>> - simple reformatting
>>>>>>>>>>>>>>>>> - rename far right column from "last update" to "last download"
>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>> rpz-beta-0.1.17-17.ipfire on 2024-12-09
>>>>>>>>>>>>>>>>> rpz-make
>>>>>>>>>>>>>>>>> - bug fix: corrected validation regex for wildcards like: `*.domain.com`
>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>> rpz-beta-0.1.16-16.ipfire on 2024-11-18
>>>>>>>>>>>>>>>>> rpz-make
>>>>>>>>>>>>>>>>> - new feature: updated validation regex
>>>>>>>>>>>>>>>>> - bug fix: moved validation to beginning of process. Now we validate before
>>>>>>>>>>>>>>>>> creating config files.
>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>> - new feature: use CSS color variables of the main ipfire theme
>>>>>>>>>>>>>>>>> - bug fix: empty zonefile remarks were stored as “undef” and caused a warning
>>>>>>>>>>>>>>>>> - bug fix: HTML textarea removes the first empty line in a custom list
>>>>>>>>>>>>>>>>> - thank you Leo!
>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>> rpz-beta-0.1.15-15.ipfire on 2024-11-04
>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>> - new feature: added new language file for Turkish (thank you Peppe)
>>>>>>>>>>>>>>>>> rpz-make
>>>>>>>>>>>>>>>>> - bug fix: corrected empty allow/block list issue. An empty allow/block list
>>>>>>>>>>>>>>>>> will now remove contents of allow/block.rpz files and remove unneeded
>>>>>>>>>>>>>>>>> allow/block.conf file. (thank you iptom)
>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>> rpz-beta-0.1.14-14.ipfire on 2024-10-29
>>>>>>>>>>>>>>>>> rpz-config:
>>>>>>>>>>>>>>>>> - bug fix: correct missing rpz extension. `rpz-config list` displayed URL
>>>>>>>>>>>>>>>>> incorrectly (thank you Bernhard)
>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>> - bug fix: remove extra `"` in language files (thank you Bernhard)
>>>>>>>>>>>>>>>>> - new feature: slightly dim "apply" button when not enabled
>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>> rpz-beta-0.1.13-13.ipfire on 2024-10-27
>>>>>>>>>>>>>>>>> - skipped
>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>> rpz-beta-0.1.12-12.ipfire on 2024-10-21
>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>> - new feature: added new language file for French (thank you gw-ipfire)
>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>> rpz-beta-0.1.11-11.ipfire on 2024-10-18
>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>> - new feature: added new language file for Italian (thank you umberto)
>>>>>>>>>>>>>>>>> - new feature: added new language file for Spanish (thank you Roberto)
>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>> rpz-beta-0.1.10-10.ipfire on 2024-10-15
>>>>>>>>>>>>>>>>> rpz-make:
>>>>>>>>>>>>>>>>> - bug fix: corrected validation error for a custom list entry (thank you siosios)
>>>>>>>>>>>>>>>>> - e.g., `*.cloudflare-dns.com`
>>>>>>>>>>>>>>>>> install.sh:
>>>>>>>>>>>>>>>>> - bug fix: add chown to correct user created files
>>>>>>>>>>>>>>>>> update.sh:
>>>>>>>>>>>>>>>>> - bug fix: add chown to correct user created files (thank you siosios)
>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>> rpz-beta-0.1.9-9.ipfire on 2024-10-08
>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>> - new feature: added new language file for German (thank you Leo)
>>>>>>>>>>>>>>>>> - bug fix: add missing "rpz exitcode 110"
>>>>>>>>>>>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>> rpz-beta-0.1.8-8.ipfire on 2024-10-04
>>>>>>>>>>>>>>>>> - skipped
>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>> rpz-beta-0.1.7-7.ipfire on 2024-10-03
>>>>>>>>>>>>>>>>> All:
>>>>>>>>>>>>>>>>> - new feature: includes beta version numbers for pakfire package,
>>>>>>>>>>>>>>>>> instead of only `rpz-1.0.0-1.ipfire`, for each release.
>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>> - new feature: added new WebGUI at `rpz.cgi`
>>>>>>>>>>>>>>>>> - a BIG thank you to Leo Hofmann for all of his work creating the webgui!!
>>>>>>>>>>>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>>>>>>>>>>> rpz-make:
>>>>>>>>>>>>>>>>> - new feature: validate entries in allowlist and blocklist
>>>>>>>>>>>>>>>>> - new feature: add "no-reload" option for WebGUI
>>>>>>>>>>>>>>>>> rpz-metrics:
>>>>>>>>>>>>>>>>> - new feature: info can be sorted by name, by hit count, by line count, by
>>>>>>>>>>>>>>>>> "enabled" list or all lists
>>>>>>>>>>>>>>>>> backups:
>>>>>>>>>>>>>>>>> - bug fix: include all files in `/var/ipfire/dns/rpz` directory in backup
>>>>>>>>>>>>>>>>> update.sh:
>>>>>>>>>>>>>>>>> - bug fix: corrected ownership for `/var/ipfire/dns/rpz` directory during an
>>>>>>>>>>>>>>>>> update
>>>>>>>>>>>>>>>>> Build:
>>>>>>>>>>>>>>>>> - bug fix: `block.rpz.conf` and `block.rpz` from build. Files to be created
>>>>>>>>>>>>>>>>> by `rpz-make`
>>>>>>>>>>>>>>>>> WebGUI and German language file
>>>>>>>>>>>>>>>>> Contribution-by: Leo-Andres Hofmann <hofmann@leo-andres.de>
>>>>>>>>>>>>>>>>> Spanish language file
>>>>>>>>>>>>>>>>> Contribution-by: Roberto Peña
>>>>>>>>>>>>>>>>> Italian language file
>>>>>>>>>>>>>>>>> Contribution-by: Umberto Parma
>>>>>>>>>>>>>>>>> French language file
>>>>>>>>>>>>>>>>> Contribution-by: gw-ipfire
>>>>>>>>>>>>>>>>> Turkish language file
>>>>>>>>>>>>>>>>> Contribution-by: Peppe Tech
>>>>>>>>>>>>>>>>> Contribution-by: Bernhard Bitsch <bbitsch@ipfire.org>
>>>>>>>>>>>>>>>>> Contribution-by: Erik Kapfer <erik.kapfer@ipfire.org>
>>>>>>>>>>>>>>>>> Signed-off-by: Jon Murphy <jon.murphy@ipfire.org
>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>> config/backup/includes/rpz | 4 +
>>>>>>>>>>>>>>>>> config/cfgroot/manualpages | 1 +
>>>>>>>>>>>>>>>>> config/menu/EX-rpz.menu | 6 +
>>>>>>>>>>>>>>>>> config/rootfiles/common/configroot | 1 +
>>>>>>>>>>>>>>>>> config/rootfiles/common/web-user-interface | 1 +
>>>>>>>>>>>>>>>>> config/rootfiles/packages/rpz | 20 +
>>>>>>>>>>>>>>>>> config/rpz/00-rpz.conf | 10 +
>>>>>>>>>>>>>>>>> config/rpz/rpz-config | 130 +++
>>>>>>>>>>>>>>>>> config/rpz/rpz-functions | 85 ++
>>>>>>>>>>>>>>>>> config/rpz/rpz-make | 203 +++++
>>>>>>>>>>>>>>>>> config/rpz/rpz-metrics | 170 ++++
>>>>>>>>>>>>>>>>> config/rpz/rpz-sleep | 58 ++
>>>>>>>>>>>>>>>>> config/rpz/rpz.de.pl | 30 +
>>>>>>>>>>>>>>>>> config/rpz/rpz.en.pl | 30 +
>>>>>>>>>>>>>>>>> config/rpz/rpz.es.pl | 30 +
>>>>>>>>>>>>>>>>> config/rpz/rpz.fr.pl | 30 +
>>>>>>>>>>>>>>>>> config/rpz/rpz.it.pl | 30 +
>>>>>>>>>>>>>>>>> config/rpz/rpz.tr.pl | 30 +
>>>>>>>>>>>>>>>>> html/cgi-bin/rpz.cgi | 923 +++++++++++++++++++++
>>>>>>>>>>>>>>>>> lfs/rpz | 96 +++
>>>>>>>>>>>>>>>>> make.sh | 3 +-
>>>>>>>>>>>>>>>>> src/paks/rpz/install.sh | 36 +
>>>>>>>>>>>>>>>>> src/paks/rpz/uninstall.sh | 38 +
>>>>>>>>>>>>>>>>> src/paks/rpz/update.sh | 52 ++
>>>>>>>>>>>>>>>>> 24 files changed, 2016 insertions(+), 1 deletion(-)
>>>>>>>>>>>>>>>>> create mode 100644 config/backup/includes/rpz
>>>>>>>>>>>>>>>>> create mode 100644 config/menu/EX-rpz.menu
>>>>>>>>>>>>>>>>> create mode 100644 config/rootfiles/packages/rpz
>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/00-rpz.conf
>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz-config
>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz-functions
>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz-make
>>>>>>>>>>>>>>>>> create mode 100755 config/rpz/rpz-metrics
>>>>>>>>>>>>>>>>> create mode 100755 config/rpz/rpz-sleep
>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.de.pl
>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.en.pl
>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.es.pl
>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.fr.pl
>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.it.pl
>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.tr.pl
>>>>>>>>>>>>>>>>> create mode 100644 html/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>>>> create mode 100644 lfs/rpz
>>>>>>>>>>>>>>>>> create mode 100644 src/paks/rpz/install.sh
>>>>>>>>>>>>>>>>> create mode 100644 src/paks/rpz/uninstall.sh
>>>>>>>>>>>>>>>>> create mode 100644 src/paks/rpz/update.sh
>>>>>>>>>>>>>>>>> diff --git a/config/backup/includes/rpz b/config/backup/includes/rpz
>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>> index 000000000..36513e494
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/config/backup/includes/rpz
>>>>>>>>>>>>>>>>> @@ -0,0 +1,4 @@
>>>>>>>>>>>>>>>>> +/var/ipfire/dns/rpz/*
>>>>>>>>>>>>>>>>> +/etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>>>> +/etc/unbound/zonefiles/block.rpz
>>>>>>>>>>>>>>>>> +/etc/unbound/local.d/*rpz.conf
>>>>>>>>>>>>>>>>> diff --git a/config/cfgroot/manualpages b/config/cfgroot/manualpages
>>>>>>>>>>>>>>>>> index 1f7e01efc..d3a48c633 100644
>>>>>>>>>>>>>>>>> --- a/config/cfgroot/manualpages
>>>>>>>>>>>>>>>>> +++ b/config/cfgroot/manualpages
>>>>>>>>>>>>>>>>> @@ -70,6 +70,7 @@ pakfire.cgi=configuration/ipfire/pakfire
>>>>>>>>>>>>>>>>> wlanap.cgi=addons/wireless
>>>>>>>>>>>>>>>>> tor.cgi=addons/tor
>>>>>>>>>>>>>>>>> samba.cgi=addons/samba
>>>>>>>>>>>>>>>>> +rpz.cgi=addons/rpz
>>>>>>>>>>>>>>>>> # Logs menu
>>>>>>>>>>>>>>>>> logs.cgi/summary.dat=configuration/logs/summary
>>>>>>>>>>>>>>>>> diff --git a/config/menu/EX-rpz.menu b/config/menu/EX-rpz.menu
>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>> index 000000000..2f4daf410
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/config/menu/EX-rpz.menu
>>>>>>>>>>>>>>>>> @@ -0,0 +1,6 @@
>>>>>>>>>>>>>>>>> +$subipfire->{'20.rpz'} = {
>>>>>>>>>>>>>>>>> + 'caption' => $Lang::tr{'rpz'},
>>>>>>>>>>>>>>>>> + 'uri' => '/cgi-bin/rpz.cgi',
>>>>>>>>>>>>>>>>> + 'title' => "RPZ",
>>>>>>>>>>>>>>>>> + 'enabled' => 1,
>>>>>>>>>>>>>>>>> +};
>>>>>>>>>>>>>>>>> diff --git a/config/rootfiles/common/configroot b/config/rootfiles/common/configroot
>>>>>>>>>>>>>>>>> index 9839eee45..b30d6aae4 100644
>>>>>>>>>>>>>>>>> --- a/config/rootfiles/common/configroot
>>>>>>>>>>>>>>>>> +++ b/config/rootfiles/common/configroot
>>>>>>>>>>>>>>>>> @@ -120,6 +120,7 @@ var/ipfire/menu.d/70-log.menu
>>>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-apcupsd.menu
>>>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-guardian.menu
>>>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-mympd.menu
>>>>>>>>>>>>>>>>> +#var/ipfire/menu.d/EX-rpz.menu
>>>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-samba.menu
>>>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-tor.menu
>>>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-transmission.menu
>>>>>>>>>>>>>>>>> diff --git a/config/rootfiles/common/web-user-interface b/config/rootfiles/common/web-user-interface
>>>>>>>>>>>>>>>>> index 816241dae..e00464076 100644
>>>>>>>>>>>>>>>>> --- a/config/rootfiles/common/web-user-interface
>>>>>>>>>>>>>>>>> +++ b/config/rootfiles/common/web-user-interface
>>>>>>>>>>>>>>>>> @@ -69,6 +69,7 @@ srv/web/ipfire/cgi-bin/proxy.cgi
>>>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/qos.cgi
>>>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/remote.cgi
>>>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/routing.cgi
>>>>>>>>>>>>>>>>> +#srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>>>> #srv/web/ipfire/cgi-bin/samba.cgi
>>>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/services.cgi
>>>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/shutdown.cgi
>>>>>>>>>>>>>>>>> diff --git a/config/rootfiles/packages/rpz b/config/rootfiles/packages/rpz
>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>> index 000000000..1c8663049
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/config/rootfiles/packages/rpz
>>>>>>>>>>>>>>>>> @@ -0,0 +1,20 @@
>>>>>>>>>>>>>>>>> +etc/unbound/local.d/00-rpz.conf
>>>>>>>>>>>>>>>>> +etc/unbound/zonefiles
>>>>>>>>>>>>>>>>> +etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>>>> +usr/sbin/rpz-config
>>>>>>>>>>>>>>>>> +usr/sbin/rpz-functions
>>>>>>>>>>>>>>>>> +usr/sbin/rpz-make
>>>>>>>>>>>>>>>>> +usr/sbin/rpz-metrics
>>>>>>>>>>>>>>>>> +usr/sbin/rpz-sleep
>>>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.de.pl
>>>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.en.pl
>>>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.es.pl
>>>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.fr.pl
>>>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.it.pl
>>>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.tr.pl
>>>>>>>>>>>>>>>>> +var/ipfire/backup/addons/includes/rpz
>>>>>>>>>>>>>>>>> +var/ipfire/dns/rpz
>>>>>>>>>>>>>>>>> +var/ipfire/dns/rpz/allowlist
>>>>>>>>>>>>>>>>> +var/ipfire/dns/rpz/blocklist
>>>>>>>>>>>>>>>>> +var/ipfire/menu.d/EX-rpz.menu
>>>>>>>>>>>>>>>>> +srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>>>> diff --git a/config/rpz/00-rpz.conf b/config/rpz/00-rpz.conf
>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>> index 000000000..f005a4f2e
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/config/rpz/00-rpz.conf
>>>>>>>>>>>>>>>>> @@ -0,0 +1,10 @@
>>>>>>>>>>>>>>>>> +server:
>>>>>>>>>>>>>>>>> + module-config: "respip validator iterator"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +rpz:
>>>>>>>>>>>>>>>>> + name: allow.rpz
>>>>>>>>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>>>> + rpz-action-override: passthru
>>>>>>>>>>>>>>>>> + rpz-log: yes
>>>>>>>>>>>>>>>>> + rpz-log-name: allow
>>>>>>>>>>>>>>>>> + rpz-signal-nxdomain-ra: yes
>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-config b/config/rpz/rpz-config
>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>> index 000000000..c72d50f9b
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-config
>>>>>>>>>>>>>>>>> @@ -0,0 +1,130 @@
>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +version="2025-01-11 - v44"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +tagName="unbound"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +rpzAction="${1}" # input RPZ action
>>>>>>>>>>>>>>>>> +rpzName="${2}" # input RPZ name
>>>>>>>>>>>>>>>>> +rpzURL="${3}" # input RPZ URL
>>>>>>>>>>>>>>>>> +rpzOption1="${4}" # input RPZ option #1
>>>>>>>>>>>>>>>>> +rpzOption2="${5}" # input RPZ option #2
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +rpzConfig="/etc/unbound/local.d/${rpzName}.rpz.conf" # output zone conf file
>>>>>>>>>>>>>>>>> +rpzFile="/etc/unbound/zonefiles/${rpzName}.rpz" # output for RPZ file
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>>>>>>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>>>>>>>>> + case "$1" in
>>>>>>>>>>>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>>>>>>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>>>>>>>>>>>> + esac
>>>>>>>>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +case "${rpzAction}" in
>>>>>>>>>>>>>>>>> + # add new rpz list
>>>>>>>>>>>>>>>>> + add )
>>>>>>>>>>>>>>>>> + check_name "${rpzName}" # is this a valid name?
>>>>>>>>>>>>>>>>> + # does this config already exist? If yes, then exit
>>>>>>>>>>>>>>>>> + if [[ -f "${rpzConfig}" ]] ; then
>>>>>>>>>>>>>>>>> + msg_log "error: rpz: duplicate - ${rpzConfig} already exists. exit"
>>>>>>>>>>>>>>>>> + exit 104
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # is this a valid URL?
>>>>>>>>>>>>>>>>> + regex='^https://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
>>>>>>>>>>>>>>>>> + if ! [[ "${rpzURL}" =~ $regex ]] ; then
>>>>>>>>>>>>>>>>> + msg_log "error: rpz: the URL is not valid: \"${rpzURL}\". exit."
>>>>>>>>>>>>>>>>> + exit 105
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # create the zone config file
>>>>>>>>>>>>>>>>> + {
>>>>>>>>>>>>>>>>> + echo "rpz:"
>>>>>>>>>>>>>>>>> + echo " name: ${rpzName}.rpz"
>>>>>>>>>>>>>>>>> + echo " zonefile: ${rpzFile}"
>>>>>>>>>>>>>>>>> + echo " url: ${rpzURL}"
>>>>>>>>>>>>>>>>> + echo " rpz-action-override: nxdomain"
>>>>>>>>>>>>>>>>> + echo " rpz-log: ${rpzLog}"
>>>>>>>>>>>>>>>>> + echo " rpz-log-name: ${rpzName}"
>>>>>>>>>>>>>>>>> + echo " rpz-signal-nxdomain-ra: yes"
>>>>>>>>>>>>>>>>> + } > "${rpzConfig}"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # set-up zonefile
>>>>>>>>>>>>>>>>> + # create an empty rpz file if it does not exist
>>>>>>>>>>>>>>>>> + if [[ ! -f "${rpzFile}" ]] ; then
>>>>>>>>>>>>>>>>> + touch "${rpzFile}"
>>>>>>>>>>>>>>>>> + # unbound requires these settings for rpz files
>>>>>>>>>>>>>>>>> + set_permissions "${rpzFile}" "${rpzConfig}"
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # trash config file & rpz file
>>>>>>>>>>>>>>>>> + remove )
>>>>>>>>>>>>>>>>> + if ! [[ -f "${rpzConfig}" ]] ; then
>>>>>>>>>>>>>>>>> + msg_log "error: rpz: cannot remove ${rpzConfig}, does not exist. exit"
>>>>>>>>>>>>>>>>> + exit 106
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + msg_log "info: rpz: remove config file & rpz file \"${rpzName}\""
>>>>>>>>>>>>>>>>> + rm "${rpzConfig}"
>>>>>>>>>>>>>>>>> + rm "${rpzFile}"
>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + reload )
>>>>>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + list )
>>>>>>>>>>>>>>>>> + awk -F':' '/^\s*name:/{ gsub(/[[:blank:]]|\.rpz/, "",$2) ; NAME=$2 } \
>>>>>>>>>>>>>>>>> + /^\s*url:/{ gsub(/[[:blank:]]/, "") ; print NAME"="$2":"$3} ' \
>>>>>>>>>>>>>>>>> + /etc/unbound/local.d/*rpz.conf
>>>>>>>>>>>>>>>>> + exit
>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + unbound-restart )
>>>>>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>>>>>> + unbound_restart
>>>>>>>>>>>>>>>>> + exit
>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + * )
>>>>>>>>>>>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>>>>>>>>>>>> + printf "Usage: $(basename "$0") <ACTION> <NAME> <URL> <OPTION> <OPTION>\n"
>>>>>>>>>>>>>>>>> + printf "Version: ${version}\n"
>>>>>>>>>>>>>>>>> + exit 108
>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +esac
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +exit
>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-functions b/config/rpz/rpz-functions
>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>> index 000000000..ace1d2690
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-functions
>>>>>>>>>>>>>>>>> @@ -0,0 +1,85 @@
>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +version="2024-12-10 - v02"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +msg_log () {
>>>>>>>>>>>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>>>>>>>>>>>> + if tty --silent ; then
>>>>>>>>>>>>>>>>> + echo "${tagName}:" "$*"
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# check for a valid name
>>>>>>>>>>>>>>>>> +check_name () {
>>>>>>>>>>>>>>>>> + local theName="${1}"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + regex='^[a-zA-Z0-9_]+$' # no dash or plus, alpha numeric only
>>>>>>>>>>>>>>>>> + regex1='^(allow|block)$' # allow and block are reserved NAMEs
>>>>>>>>>>>>>>>>> + if [[ ! "${theName}" =~ $regex ]] || [[ "${theName}" =~ $regex1 ]] ; then
>>>>>>>>>>>>>>>>> + msg_log "error: rpz: the NAME is not valid: \"${theName}\". exit."
>>>>>>>>>>>>>>>>> + exit 101
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +set_permissions () {
>>>>>>>>>>>>>>>>> + chown nobody:nobody "$@"
>>>>>>>>>>>>>>>>> + chmod 644 "$@"
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +check_unbound_conf () {
>>>>>>>>>>>>>>>>> + local thecheckConf="${1:-yes}" # check config default is yes
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # check the above config files
>>>>>>>>>>>>>>>>> + if [[ "${thecheckConf}" == yes ]] ; then
>>>>>>>>>>>>>>>>> + msg_log "info: rpz: check for errors with \"unbound-checkconf\""
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + if ! unbound-checkconf ; then
>>>>>>>>>>>>>>>>> + msg_log "error: rpz: unbound-checkconf found invalid configuration."
>>>>>>>>>>>>>>>>> + msg_log \
>>>>>>>>>>>>>>>>> + "error: rpz: In Terminal run the command \"unbound-checkconf\" for more information. exit."
>>>>>>>>>>>>>>>>> + exit 102
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +unbound_control_reload () {
>>>>>>>>>>>>>>>>> + local theReload="${1:-yes}" # reload default is yes
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + if [[ "${theReload}" == yes ]] ; then
>>>>>>>>>>>>>>>>> + # reload due to the changes
>>>>>>>>>>>>>>>>> + msg_log "info: rpz: run \"unbound-control reload\""
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + if ! unbound-control reload ; then
>>>>>>>>>>>>>>>>> + msg_log "error: rpz: unbound-control reload. exit."
>>>>>>>>>>>>>>>>> + exit 109
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +unbound_restart () {
>>>>>>>>>>>>>>>>> + # restart due to the changes
>>>>>>>>>>>>>>>>> + msg_log "info: rpz: run \"unbound restart\""
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + /usr/local/bin/unboundctrl restart
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-make b/config/rpz/rpz-make
>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>> index 000000000..927d55170
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-make
>>>>>>>>>>>>>>>>> @@ -0,0 +1,203 @@
>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +version="2025-01-11 - v14"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# create the config file for allow
>>>>>>>>>>>>>>>>> +make_allow_config () {
>>>>>>>>>>>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>>>>>>>>>>>> + local theConfig="/etc/unbound/local.d/00-rpz.conf" # output zone conf file
>>>>>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/allowlist" # input custom list of domains
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + msg_log "info: rpz: make config file \"00-rpz.conf\""
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + echo "server:
>>>>>>>>>>>>>>>>> + module-config: \"respip validator iterator\"" > "${theConfig}"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # does allow list exist?
>>>>>>>>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + echo "rpz:
>>>>>>>>>>>>>>>>> + name: allow.rpz
>>>>>>>>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>>>> + rpz-action-override: passthru
>>>>>>>>>>>>>>>>> + rpz-log: ${theLog}
>>>>>>>>>>>>>>>>> + rpz-log-name: allow
>>>>>>>>>>>>>>>>> + rpz-signal-nxdomain-ra: yes" >> "${theConfig}"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>>>>>>>>> + set_permissions "${theConfig}"
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# create the config file for block
>>>>>>>>>>>>>>>>> +make_block_config () {
>>>>>>>>>>>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>>>>>>>>>>>> + local theConfig="/etc/unbound/local.d/block.rpz.conf" # output zone conf file
>>>>>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/blocklist" # input custom list of domains
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + msg_log "info: rpz: make config file \"block.rpz.conf\""
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # does block list exist?
>>>>>>>>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + echo "rpz:
>>>>>>>>>>>>>>>>> + name: block.rpz
>>>>>>>>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/block.rpz
>>>>>>>>>>>>>>>>> + rpz-action-override: nxdomain
>>>>>>>>>>>>>>>>> + rpz-log: ${theLog}
>>>>>>>>>>>>>>>>> + rpz-log-name: block
>>>>>>>>>>>>>>>>> + rpz-signal-nxdomain-ra: yes" > "${theConfig}"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>>>>>>>>> + set_permissions "${theConfig}"
>>>>>>>>>>>>>>>>> + else
>>>>>>>>>>>>>>>>> + # no - trash the config file
>>>>>>>>>>>>>>>>> + rm --verbose /etc/unbound/local.d/block.rpz.conf
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# create an RPZ file for allow or block
>>>>>>>>>>>>>>>>> +make_rpz_file () {
>>>>>>>>>>>>>>>>> + local theName="${1}" # allow or block
>>>>>>>>>>>>>>>>> + local theAction='.' # the default is nxdomain or block
>>>>>>>>>>>>>>>>> + local actionList
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>>>>>>>>>>>> + local theZonefile="/etc/unbound/zonefiles/${theName}.rpz" # output file for RPZ
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # does a list exist?
>>>>>>>>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # for allow set to passthru
>>>>>>>>>>>>>>>>> + [[ "${theName}" == allow ]] && theAction='rpz-passthru.'
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # drop any extra "blanks" and add "CNAME <RPZ action>." to each line
>>>>>>>>>>>>>>>>> + actionList=$( awk '{$1=$1};1' "${theList}" |
>>>>>>>>>>>>>>>>> + sed "/^[^;].*[[:alnum:]]/ s|$| CNAME ${theAction}|" )
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + msg_log "info: rpz: create zonefile for \"${theName}list\""
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +echo "; Name: ${theName} list
>>>>>>>>>>>>>>>>> +; Last modified: $(date "+%Y-%m-%d at %H.%M.%S %Z")
>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>> +; domains with actions list
>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>> +${actionList}" > "${theZonefile}"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>>>>>>>>> + set_permissions "${theZonefile}"
>>>>>>>>>>>>>>>>> + # set-up allow/block list files
>>>>>>>>>>>>>>>>> + set_permissions "${theList}"
>>>>>>>>>>>>>>>>> + else
>>>>>>>>>>>>>>>>> + msg_log "info: rpz: the ${theList} is empty."
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + rm --verbose "${theZonefile}" # trash the RPZ file
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# check if allow/block list is valid
>>>>>>>>>>>>>>>>> +validate_list () {
>>>>>>>>>>>>>>>>> + local theName="${1}" # allow or block
>>>>>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # remove good:
>>>>>>>>>>>>>>>>> + # - properly formated domain names with or without leading wildcard
>>>>>>>>>>>>>>>>> + # - properly formated top level domain (TLD) names with wildcard
>>>>>>>>>>>>>>>>> + # - blank lines and comment lines
>>>>>>>>>>>>>>>>> + # remaining lines are considered "bad"
>>>>>>>>>>>>>>>>> + bad_lines=$( sed --regexp-extended \
>>>>>>>>>>>>>>>>> + '/^(\*\.)?([a-zA-Z0-9](([a-zA-Z0-9\-]){0,61}[a-zA-Z0-9])?\.)+([a-zA-Z]{2,}|xn--[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])$/d ;
>>>>>>>>>>>>>>>>> + /^(\*\.)([a-z]{2,61}|xn--[a-z0-9]{1,60})$/d ;
>>>>>>>>>>>>>>>>> + /^$/d ; /^;/d' "${theList}" )
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + if [[ ! -z "${bad_lines}" ]] ; then
>>>>>>>>>>>>>>>>> + msg_log "error: rpz: invalid line(s) in ${theList}."
>>>>>>>>>>>>>>>>> + printf "%s\n" "bad line(s): ${bad_lines}"
>>>>>>>>>>>>>>>>> + exit 110
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +tagName="unbound"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +rpzName="${1}" # input RPZ name
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>>>>>>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>>>>>>>>> + case "$1" in
>>>>>>>>>>>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>>>>>>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>>>>>>>>>>>> + esac
>>>>>>>>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +case "${rpzName}" in
>>>>>>>>>>>>>>>>> + # make a new allow or block rpz file
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + allow )
>>>>>>>>>>>>>>>>> + validate_list 'allow' # is the allowlist valid?
>>>>>>>>>>>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>>>>>>>>>>>> + make_rpz_file 'allow'
>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + allowblock )
>>>>>>>>>>>>>>>>> + validate_list 'allow' # is the list valid?
>>>>>>>>>>>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>>>>>>>>>>>> + make_rpz_file 'allow'
>>>>>>>>>>>>>>>>> + ;&
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + block )
>>>>>>>>>>>>>>>>> + validate_list 'block' # is the blocklist valid?
>>>>>>>>>>>>>>>>> + make_block_config "${rpzLog}"
>>>>>>>>>>>>>>>>> + make_rpz_file 'block'
>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + reload )
>>>>>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + unbound-restart )
>>>>>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>>>>>> + unbound_restart
>>>>>>>>>>>>>>>>> + exit
>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + * )
>>>>>>>>>>>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>>>>>>>>>>>> + printf "Usage: $(basename "$0") <NAME> <OPTION> <OPTION>\n"
>>>>>>>>>>>>>>>>> + printf "Version: ${version}\n"
>>>>>>>>>>>>>>>>> + exit 108
>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>> +esac
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +exit
>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-metrics b/config/rpz/rpz-metrics
>>>>>>>>>>>>>>>>> new file mode 100755
>>>>>>>>>>>>>>>>> index 000000000..4d43e1629
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-metrics
>>>>>>>>>>>>>>>>> @@ -0,0 +1,170 @@
>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +version="2025-01-20 - v25"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +weeks="2" # default to two message logs
>>>>>>>>>>>>>>>>> +sortBy="name" # default "by name"
>>>>>>>>>>>>>>>>> +rpzActive="enabled" # default "enabled only"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>>>>>>>>> + case "$1" in
>>>>>>>>>>>>>>>>> + --by-names | --by-name | name ) sortBy="name" ;;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + --by-hits | --by-hit | hits | hit ) sortBy="hit" ;;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + --by-lines | --by-line | lines | line ) sortBy="line" ;;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + --by-effect ) sortBy="effect" ;;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + --enabled-only ) rpzActive="enabled" ;;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + --active-all | --all | all ) rpzActive="all" ;;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + [0-9] | [0-9][0-9] ) weeks=$1 ;;
>>>>>>>>>>>>>>>>> + esac
>>>>>>>>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# get the list of message logs for N weeks
>>>>>>>>>>>>>>>>> +messageLogs=$( find /var/log/messages* -type f | sort --version-sort |
>>>>>>>>>>>>>>>>> + head -"${weeks}" )
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# get the list of RPZ names & counts from the message log(s)
>>>>>>>>>>>>>>>>> +rpzNameCount=$( for logf in ${messageLogs} ; do
>>>>>>>>>>>>>>>>> + zgrep --text --fixed-strings 'info: rpz: applied' "${logf}" |
>>>>>>>>>>>>>>>>> + awk '$10 ~ /\[\w*]/ { print $10 }' ;
>>>>>>>>>>>>>>>>> + done | sort | uniq --count )
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# flip results and remove brackets `[` and `]`
>>>>>>>>>>>>>>>>> +rpzNameCount=$( echo "${rpzNameCount}" |
>>>>>>>>>>>>>>>>> + awk '{ print $2, $1 }' |
>>>>>>>>>>>>>>>>> + sed --regexp-extended 's|^\[(.*)\]|\1|' )
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# grab only names
>>>>>>>>>>>>>>>>> +rpzNames=$( echo "${rpzNameCount}" | awk '{ print $1 }' )
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# get list of RPZ files
>>>>>>>>>>>>>>>>> +rpzFileList=$( find /etc/unbound/zonefiles -type f -iname "*.rpz" )
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# get basename of those files
>>>>>>>>>>>>>>>>> +rpzBaseNames=$( echo "${rpzFileList}" |
>>>>>>>>>>>>>>>>> + sed 's|/etc/unbound/zonefiles/||g ; s|\.rpz||g ;' )
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# add to rpzNames
>>>>>>>>>>>>>>>>> +rpzNames="${rpzNames}"$'\n'"${rpzBaseNames}"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# drop duplicate names
>>>>>>>>>>>>>>>>> +rpzNames=$( echo "${rpzNames}" | sort --unique )
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# get line count for each RPZ
>>>>>>>>>>>>>>>>> +lineCount=$( echo "${rpzFileList}" | xargs wc -l )
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# get comment line count and blank line count for each RPZ
>>>>>>>>>>>>>>>>> +commentCount=$( echo "${rpzFileList}" | xargs grep --count -e "^$" -e "^;" )
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# get modified date each RPZ
>>>>>>>>>>>>>>>>> +modDateList=$( echo "${rpzFileList}" | xargs stat -c '%.10y %n' )
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +ucListAuthZones=$( unbound-control list_auth_zones )
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# get width of RPZ names
>>>>>>>>>>>>>>>>> +pWidth=$( echo "${rpzNames}" | awk '{ print $1" " }' | wc -L )
>>>>>>>>>>>>>>>>> +pFormat="%-${pWidth}s %-8s %-8s %8s %12s %12s\n"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# print title line
>>>>>>>>>>>>>>>>> +printf "${pFormat}" "name" "hits" "active" "lines" " hits/line" "last download"
>>>>>>>>>>>>>>>>> +printf -- "--------------"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +theResults=""
>>>>>>>>>>>>>>>>> +totalLines=0
>>>>>>>>>>>>>>>>> +totalHits=0
>>>>>>>>>>>>>>>>> +while read -r theName
>>>>>>>>>>>>>>>>> +do
>>>>>>>>>>>>>>>>> + printf -- "--" # pretend progress bar
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # is this RPZ list active?
>>>>>>>>>>>>>>>>> + theActive="disabled"
>>>>>>>>>>>>>>>>> + if grep --quiet "^${theName}\.rpz" <<< "${ucListAuthZones}"
>>>>>>>>>>>>>>>>> + then
>>>>>>>>>>>>>>>>> + theActive="enabled"
>>>>>>>>>>>>>>>>> + else
>>>>>>>>>>>>>>>>> + [[ "${rpzActive}" == enabled ]] && continue
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # get hit count
>>>>>>>>>>>>>>>>> + theHits="0"
>>>>>>>>>>>>>>>>> + if output=$( grep "^${theName}\s" <<< "${rpzNameCount}" ) ; then
>>>>>>>>>>>>>>>>> + theHits=$( echo "${output}" | awk '{ print $2 }' )
>>>>>>>>>>>>>>>>> + totalHits=$(( totalHits + theHits ))
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # get line count
>>>>>>>>>>>>>>>>> + theLines="n/a"
>>>>>>>>>>>>>>>>> + hitsPerLine="0"
>>>>>>>>>>>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${lineCount}" ) ; then
>>>>>>>>>>>>>>>>> + theLines=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>>>>>>>>>>>> + totalLines=$(( totalLines + theLines ))
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + if [[ "${theLines}" -gt 2 ]] ; then
>>>>>>>>>>>>>>>>> + hitsPerLine=$(( 100 * theHits / theLines ))
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # get modification date
>>>>>>>>>>>>>>>>> + theModDate="n/a"
>>>>>>>>>>>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${modDateList}" ) ; then
>>>>>>>>>>>>>>>>> + theModDate=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # add to results list
>>>>>>>>>>>>>>>>> + theResults+="${theName} ${theHits} ${theActive} ${theLines} ${hitsPerLine} ${theModDate}"$'\n'
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +done <<< "${rpzNames}"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +case "${sortBy}" in
>>>>>>>>>>>>>>>>> + # sort by "active" then by "name"
>>>>>>>>>>>>>>>>> + name) sortArg=(-k3,3r -k1,1) ;;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # sort by "active" then by "hits" then by "name"
>>>>>>>>>>>>>>>>> + hit) sortArg=(-k3,3r -k2,2nr -k1,1) ;;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # sort by "active" then by "lines" then by "name"
>>>>>>>>>>>>>>>>> + line) sortArg=(-k3,3r -k4,4nr -k1,1) ;;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # sort by "active" then by "effect" then by "name"
>>>>>>>>>>>>>>>>> + effect) sortArg=(-k3,3r -k5,5nr -k1,1) ;;
>>>>>>>>>>>>>>>>> +esac
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +printf -- "--------------\n"
>>>>>>>>>>>>>>>>> +# remove blank lines, sort, print as columns
>>>>>>>>>>>>>>>>> +echo "${theResults}" |
>>>>>>>>>>>>>>>>> + awk '!/^[[:space:]]*$/' |
>>>>>>>>>>>>>>>>> + sort "${sortArg[@]}" |
>>>>>>>>>>>>>>>>> + awk --assign=width="${pWidth}" \
>>>>>>>>>>>>>>>>> + '{ printf "%-*s %-8s %-8s %8s %10s %% %12s\n", width, $1, $2, $3, $4, $5, $6 }'
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +printf "${pFormat}" "" "=======" "" "========" "" ""
>>>>>>>>>>>>>>>>> +printf "${pFormat}" "Totals -->" "${totalHits}" "" "${totalLines}" "" ""
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +exit
>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-sleep b/config/rpz/rpz-sleep
>>>>>>>>>>>>>>>>> new file mode 100755
>>>>>>>>>>>>>>>>> index 000000000..dd3603599
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-sleep
>>>>>>>>>>>>>>>>> @@ -0,0 +1,58 @@
>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +version="2024-08-16" # v05
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# send message to message log
>>>>>>>>>>>>>>>>> +msg_log () {
>>>>>>>>>>>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>>>>>>>>>>>> + if tty --silent ; then
>>>>>>>>>>>>>>>>> + echo "${tagName}:" "$*"
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +tagName="unbound"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +sleepTime="${1:-5m}" # default to sleep for 5m (5 minutes)
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +zoneList=$( unbound-control list_auth_zones | awk '{print $1}' )
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +for zone in ${zoneList} ; do
>>>>>>>>>>>>>>>>> + printf "disable ${zone}\t"
>>>>>>>>>>>>>>>>> + unbound-control rpz_disable "${zone}"
>>>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +msg_log "info: rpz: disabled all zones for ${sleepTime}"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +sleep "${sleepTime}"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +for zone in ${zoneList} ; do
>>>>>>>>>>>>>>>>> + printf "enable ${zone}\t"
>>>>>>>>>>>>>>>>> + unbound-control rpz_enable "${zone}"
>>>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +msg_log "info: rpz: enabled all zones"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +exit
>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.de.pl b/config/rpz/rpz.de.pl
>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>> index 000000000..3770c6bb0
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.de.pl
>>>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>>>> +'rpz apply' => 'Übernehmen',
>>>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Benutzerdefinierte Allowlist aktivieren:',
>>>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Zugelassene Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>> +'rpz cl allow' => 'Benutzerdefinierte Allowlist',
>>>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Benutzerdefinierte Blocklist aktivieren:',
>>>>>>>>>>>>>>>>> +'rpz cl block info' => 'Gesperrte Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>> +'rpz cl block' => 'Benutzerdefinierte Blocklist',
>>>>>>>>>>>>>>>>> +'rpz cl' => 'Benutzerdefinierte Listen',
>>>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'Der Name enthält unzulässige Zeichen',
>>>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf hat eine fehlerhafte Konfiguration ermittelt. Führen Sie das Kommando unbound-checkconf auf der Konsole aus, um weitere Informationen zu erhalten.',
>>>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'Die benutzerdefinierte Allow-/Blocklist ist leer',
>>>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'Ein Eintrag mit identischem Namen existiert bereits',
>>>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'Die URL ist ungültig',
>>>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'Eintrag kann nicht entfernt werden, der Name existiert nicht',
>>>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'Der Name ist ungültig - nur "allow" oder "block" möglich',
>>>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'Fehlende oder inkorrekte Parameter',
>>>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control reload ist fehlgeschlagen',
>>>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'Die benutzerdefinierte Allow-/Blocklist enthält unzulässige Einträge',
>>>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'Die Anmerkung enthält unzulässige Zeichen',
>>>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'Ungültiger Eintrag in der benutzerdefinierten Allowlist, Zeile ',
>>>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'Ungültiger Eintrag in der benutzerdefinierten Blocklist, Zeile ',
>>>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'Ausgewählter Eintrag existiert nicht: ',
>>>>>>>>>>>>>>>>> +'rpz zf editor' => 'Zonendatei-Eintrag bearbeiten',
>>>>>>>>>>>>>>>>> +'rpz zf imported' => '(importiert aus rpz-config)',
>>>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Erlaubte Zeichen sind a-z, A-Z, 0-9 und Unterstriche',
>>>>>>>>>>>>>>>>> +'rpz zf' => 'Zonendateien',
>>>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.en.pl b/config/rpz/rpz.en.pl
>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>> index 000000000..0720a8940
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.en.pl
>>>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>>>> +'rpz apply' => 'Apply',
>>>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Enable custom allowlist:',
>>>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Allowed domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>> +'rpz cl allow' => 'Custom allowlist',
>>>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Enable custom blocklist:',
>>>>>>>>>>>>>>>>> +'rpz cl block info' => 'Blocked domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>> +'rpz cl block' => 'Custom blocklist',
>>>>>>>>>>>>>>>>> +'rpz cl' => 'Custom lists',
>>>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'the NAME is not valid',
>>>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf found invalid configuration. In the Terminal run the command unbound-checkconf for more information',
>>>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'the allow/block list is empty',
>>>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'duplicate - NAME already exists',
>>>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'the URL is not valid',
>>>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'cannot remove the NAME does not exist',
>>>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'the NAME is not valid - "allow" or "block" only',
>>>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'missing or incorrect parameter',
>>>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control reload failed',
>>>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'custom Allowlist/Blocklist contains invalid entries',
>>>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'the REMARK is not valid',
>>>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'invalid entry in allowlist, line ',
>>>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'invalid entry in blocklist, line ',
>>>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'Selected entry does not exist: ',
>>>>>>>>>>>>>>>>> +'rpz zf editor' => 'Edit zonefiles entry',
>>>>>>>>>>>>>>>>> +'rpz zf imported' => '(imported from rpz-config)',
>>>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Valid characters are a-z, A-Z, 0-9 and underscore.',
>>>>>>>>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.es.pl b/config/rpz/rpz.es.pl
>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>> index 000000000..98628e4aa
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.es.pl
>>>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>>>> +'rpz' => 'Zonas de política de respuesta (RPZ)',
>>>>>>>>>>>>>>>>> +'rpz apply' => 'Aplicar',
>>>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Habilitar la lista blanca personalizada:',
>>>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Dominio permitido (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>> +'rpz cl allow' => 'Lista blanca personalizada',
>>>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Habilitar la lista negra personalizada:',
>>>>>>>>>>>>>>>>> +'rpz cl block info' => 'Dominio bloqueado (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>> +'rpz cl block' => 'Lista negra personalizada',
>>>>>>>>>>>>>>>>> +'rpz cl' => 'Lista personalizada',
>>>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'El NOMBRE no es válido',
>>>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha encontrado una configuración no válida. Desde Terminal, ejecute el comando unbound-checkconf para mayor información',
>>>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'La lista de permitidos/bloqueados está vacía',
>>>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'duplicado - NOMBRE ya existe',
>>>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'la URL no es válida',
>>>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'no es posible eliminar el NOMBRE que no existe',
>>>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'el NOMBRE no es válido - sólo "permitir" o "bloquear"',
>>>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'parámetro faltante o incorrecto',
>>>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'Error al recargar unbound-control',
>>>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'la Lista blanca/Lista negra personalizada contiene entradas no válidas',
>>>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'el COMENTARIO no es válido',
>>>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'entrada no válida en la lista blanca, línea ',
>>>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'entrada no válida en la lista negra, línea ',
>>>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'La entrada seleccionada no existe: ',
>>>>>>>>>>>>>>>>> +'rpz zf editor' => 'Editar la entrada de archivos de zona',
>>>>>>>>>>>>>>>>> +'rpz zf imported' => '(importado de rpz-config)',
>>>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Los caracteres válidos son a-z, A-Z, 0-9 y guión bajo',
>>>>>>>>>>>>>>>>> +'rpz zf' => 'Archivos de zona',
>>>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.fr.pl b/config/rpz/rpz.fr.pl
>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>> index 000000000..f35f3c2d0
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.fr.pl
>>>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>>>> +'rpz apply' => 'Appliquer',
>>>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Activer la liste d\'autorisations personnalisée:',
>>>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Domaines autorisés (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>> +'rpz cl allow' => 'Liste d\'autorisations personnalisée',
>>>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Activer la liste de blocage personnalisée:',
>>>>>>>>>>>>>>>>> +'rpz cl block info' => 'Domaines bloqués (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>> +'rpz cl block' => 'liste de blocage personnalisé',
>>>>>>>>>>>>>>>>> +'rpz cl' => 'Listes personnalisées',
>>>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'le NOM n\'est pas valide',
>>>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf configuration non valide trouvée. Dans le terminal, exécutez la commande unbound-checkconf pour plus d\'informations',
>>>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'la liste autoriser/bloquer est vide',
>>>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'le NOM existe déjà',
>>>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'L\'URL n\'est pas valide',
>>>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'impossible de supprimer le NOM n\'existe pas',
>>>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'le NOM n\'est pas valide - « autoriser » ou « bloquer » seulement',
>>>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'paramètre manquant ou incorrect',
>>>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control rechargement échoué',
>>>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'la liste autoriser/bloquer contient des entrées non valides',
>>>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'la REMARQUE n\'est pas valable',
>>>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'entrée non valide dans la liste d\'autorisation, ligne ',
>>>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'entrée non valide dans la liste de blocs, ligne ',
>>>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'L\'entrée sélectionnée n\'existe pas: ',
>>>>>>>>>>>>>>>>> +'rpz zf editor' => 'Modifier l\'entrée Fichiers Zone',
>>>>>>>>>>>>>>>>> +'rpz zf imported' => '(importé de rpz-config)',
>>>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Les caractères valides sont a-z, A-Z, 0-9 et soulignement.',
>>>>>>>>>>>>>>>>> +'rpz zf' => 'Fichiers Zone',
>>>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.it.pl b/config/rpz/rpz.it.pl
>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>> index 000000000..ee81605c9
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.it.pl
>>>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>>>> +'rpz apply' => 'Applica',
>>>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Abilita la Whitelist personalizzata:',
>>>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Domini consentiti (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>> +'rpz cl allow' => 'Whitelist personalizzata',
>>>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Abilita la Blacklist personalizzata:',
>>>>>>>>>>>>>>>>> +'rpz cl block info' => 'Domini bloccati (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>> +'rpz cl block' => 'Blacklist personalizzata',
>>>>>>>>>>>>>>>>> +'rpz cl' => 'Liste personalizzate',
>>>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'il NOME non è valido',
>>>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha trovato una configurazione non valida. Dal Terminale esegui il comando unbound-checkconf per maggiori informazioni',
>>>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'l\'elenco consentiti/bloccati è vuoto',
>>>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'duplicato - NAME esiste di già',
>>>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'l\'URL non è valido',
>>>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'non è possibile rimuovere il NOME non esiste',
>>>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'il NOME non è valido - solo "consenti" o "blocca"',
>>>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'parametro mancante o non corretto',
>>>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'ricaricamento del controllo non associato non riuscito',
>>>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'la Whitelist/Blacklist personalizzata contiene voci non valide',
>>>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'l"OSSERVAZIONE non è valida',
>>>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'voce non valida nella Whitelist, riga ',
>>>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'voce non valida nella Blacklist, riga ',
>>>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'La voce selezionata non esiste: ',
>>>>>>>>>>>>>>>>> +'rpz zf editor' => 'Modifica la voce dei file di zona',
>>>>>>>>>>>>>>>>> +'rpz zf imported' => '(importato da rpz-config)',
>>>>>>>>>>>>>>>>> +'rpz zf remark info' => 'I caratteri validi sono a-z, A-Z, 0-9 e trattino basso',
>>>>>>>>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.tr.pl b/config/rpz/rpz.tr.pl
>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>> index 000000000..00226e192
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.tr.pl
>>>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>>>> +# I�in eklendi Ayriyeten Response Policy Zone (RPZ)
>>>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>>>> +'rpz apply' => 'Uygulamak',
>>>>>>>>>>>>>>>>> +'rpz cl allow enable' => '�zel Etkinlestir allowlist:',
>>>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Izin verilmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>> +'rpz cl allow' => 'Etkinlestir allowlist',
>>>>>>>>>>>>>>>>> +'rpz cl block enable' => '�zel Etkinlestir blocklist:',
>>>>>>>>>>>>>>>>> +'rpz cl block info' => 'Engellenmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>> +'rpz cl block' => 'Engellenmis blocklist',
>>>>>>>>>>>>>>>>> +'rpz cl' => 'Engellenmis lists',
>>>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'NAME ge�erli degil',
>>>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ge�ersiz yapilandirma bulundu. Terminalde daha fazla bilgi i�in Unbound-checkConf komutunu �alistirin',
>>>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'allow/block liste bos',
>>>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'kopyalamak - NAME zaten var',
>>>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'URL ge�erli degil',
>>>>>>>>>>>>>>>>> +'rpz exitcode 106' => '�ikarilamiyor NAME yok',
>>>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'NAME ge�erli degil - "allow" veya "block" yalniz',
>>>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'Parametre eksik veya yanlis',
>>>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control basarisiz',
>>>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'Engellenmis Allowlist/Blocklist ge�ersiz girisler i�erir',
>>>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'REMARK ge�erli degil',
>>>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'Ge�ersiz giris allowlist, line ',
>>>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'Ge�ersiz giris blocklist, line ',
>>>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'Se�ilen giris yok: ',
>>>>>>>>>>>>>>>>> +'rpz zf editor' => 'yazimlamak zonefiles giris',
>>>>>>>>>>>>>>>>> +'rpz zf imported' => '(ithal edildi rpz-config)',
>>>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Ge�erli karakterler a-z, A-Z, 0-9ve alt�st.',
>>>>>>>>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>>>> diff --git a/html/cgi-bin/rpz.cgi b/html/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>> index 000000000..a821c92ac
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/html/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>>>> @@ -0,0 +1,923 @@
>>>>>>>>>>>>>>>>> +#!/usr/bin/perl
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>> +# Copyright (C) 2005-2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +use strict;
>>>>>>>>>>>>>>>>> +use Scalar::Util qw(looks_like_number);
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# debugging
>>>>>>>>>>>>>>>>> +#use warnings;
>>>>>>>>>>>>>>>>> +#use CGI::Carp 'fatalsToBrowser';
>>>>>>>>>>>>>>>>> +#use Data::Dumper;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +require '/var/ipfire/general-functions.pl';
>>>>>>>>>>>>>>>>> +require "${General::swroot}/lang.pl";
>>>>>>>>>>>>>>>>> +require "${General::swroot}/header.pl";
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +###--- Extra HTML ---###
>>>>>>>>>>>>>>>>> +my $extraHead = <<END
>>>>>>>>>>>>>>>>> +<style>
>>>>>>>>>>>>>>>>> + /* alternating row background */
>>>>>>>>>>>>>>>>> + .tbl tr:nth-child(2n+2) {
>>>>>>>>>>>>>>>>> + background-color: var(--color-light-grey);
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + .tbl tr:nth-child(2n+3) {
>>>>>>>>>>>>>>>>> + background-color: var(--color-grey);
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + /* text styles */
>>>>>>>>>>>>>>>>> + .tbl th:not(:last-child) {
>>>>>>>>>>>>>>>>> + text-align: left;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + div.right {
>>>>>>>>>>>>>>>>> + text-align: right;
>>>>>>>>>>>>>>>>> + margin-top: 0.5em;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + /* customlist input */
>>>>>>>>>>>>>>>>> + textarea.domainlist {
>>>>>>>>>>>>>>>>> + margin: 0.5em 0;
>>>>>>>>>>>>>>>>> + resize: vertical;
>>>>>>>>>>>>>>>>> + min-height: 10em;
>>>>>>>>>>>>>>>>> + overflow: auto;
>>>>>>>>>>>>>>>>> + white-space: pre;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + button[type=submit]:disabled {
>>>>>>>>>>>>>>>>> + opacity: 0.6;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +</style>
>>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>> +###--- End of extra HTML ---###
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +### Settings ###
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Request DNS service reload after configuration change
>>>>>>>>>>>>>>>>> +my $RPZ_RELOAD_FLAG = "${General::swroot}/dns/rpz/reload.flag";
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Configuration file for all available zonefiles
>>>>>>>>>>>>>>>>> +# Format: index, name (unique), enabled (on/off), URL, remark
>>>>>>>>>>>>>>>>> +my $ZONEFILES_CONF = "${General::swroot}/dns/rpz/zonefiles.conf";
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Configuration file for custom lists
>>>>>>>>>>>>>>>>> +# IDs: 0=allowlist, 1=blocklist, 2=options (allow/block enabled)
>>>>>>>>>>>>>>>>> +my $CUSTOMLISTS_CONF = "${General::swroot}/dns/rpz/customlists.conf";
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Export custom lists to rpz-config
>>>>>>>>>>>>>>>>> +my $RPZ_ALLOWLIST = "${General::swroot}/dns/rpz/allowlist";
>>>>>>>>>>>>>>>>> +my $RPZ_BLOCKLIST = "${General::swroot}/dns/rpz/blocklist";
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +### Preparation ###
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Create missing config files
>>>>>>>>>>>>>>>>> +unless(-f $ZONEFILES_CONF) { &General::system('touch', "$ZONEFILES_CONF"); }
>>>>>>>>>>>>>>>>> +unless(-f $CUSTOMLISTS_CONF) { &General::system('touch', "$CUSTOMLISTS_CONF"); }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +## Global gui data
>>>>>>>>>>>>>>>>> +my $errormessage = "";
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +## Global configuration data
>>>>>>>>>>>>>>>>> +my %zonefiles = ();
>>>>>>>>>>>>>>>>> +my %customlists = ();
>>>>>>>>>>>>>>>>> +&_zonefiles_load();
>>>>>>>>>>>>>>>>> +&_customlists_load();
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +## Global CGI form data
>>>>>>>>>>>>>>>>> +my %cgiparams = ();
>>>>>>>>>>>>>>>>> +&Header::getcgihash(\%cgiparams);
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +my $action = $cgiparams{'ACTION'} // 'NONE';
>>>>>>>>>>>>>>>>> +my $action_key = $cgiparams{'KEY'} // ''; # entry being edited, empty = none/new
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +###--- Process form actions ---###
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Zonefiles action: Check whether the requested entry exists
>>>>>>>>>>>>>>>>> +if((substr($action, 0, 3) eq 'ZF_') && ($action_key)) {
>>>>>>>>>>>>>>>>> + unless(defined $zonefiles{$action_key}) {
>>>>>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr(204, $action_key);
>>>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +## Perform actions
>>>>>>>>>>>>>>>>> +if($action eq 'ZF_SAVE') { ## Save new or modified zonefiles entry
>>>>>>>>>>>>>>>>> + if(&_action_zf_save()) {
>>>>>>>>>>>>>>>>> + $action = 'NONE'; # success, return to main page
>>>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>>> + $action = 'ZF_EDIT'; # error occured, keep editing
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +} elsif($action eq 'ZF_TOGGLE') { ## Toggle on/off
>>>>>>>>>>>>>>>>> + if(&_action_zf_toggle()) {
>>>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +} elsif($action eq 'ZF_REMOVE') { ## Remove entry
>>>>>>>>>>>>>>>>> + if(&_action_zf_remove()) {
>>>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +} elsif($action eq 'CL_SAVE') { ## Save custom lists
>>>>>>>>>>>>>>>>> + if(&_action_cl_save()) {
>>>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +} elsif($action eq 'RPZ_RELOAD') { ## Reload dns configuration
>>>>>>>>>>>>>>>>> + if(&_action_rpz_reload()) {
>>>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +} elsif($action eq 'UNB_RESTART') { ## Restart unbound service
>>>>>>>>>>>>>>>>> + if(&_action_unb_restart()) {
>>>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +###--- Start GUI ---###
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +## Start http output
>>>>>>>>>>>>>>>>> +&Header::showhttpheaders();
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Start HTML
>>>>>>>>>>>>>>>>> +&Header::openpage($Lang::tr{'rpz'}, 1, $extraHead);
>>>>>>>>>>>>>>>>> +&Header::openbigbox('100%', 'left', '');
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Show error messages
>>>>>>>>>>>>>>>>> +if($errormessage) {
>>>>>>>>>>>>>>>>> + &_print_message($errormessage);
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Handle zonefile add/edit mode
>>>>>>>>>>>>>>>>> +if($action eq "ZF_EDIT") {
>>>>>>>>>>>>>>>>> + &_print_zonefile_editor();
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Finalize page and exit cleanly
>>>>>>>>>>>>>>>>> + &Header::closebigbox();
>>>>>>>>>>>>>>>>> + &Header::closepage();
>>>>>>>>>>>>>>>>> + exit(0);
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Show gui elements
>>>>>>>>>>>>>>>>> +&_print_zonefiles();
>>>>>>>>>>>>>>>>> +&_print_customlists();
>>>>>>>>>>>>>>>>> +&_print_gui_extras();
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +&Header::closebigbox();
>>>>>>>>>>>>>>>>> +&Header::closepage();
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +###--- End of GUI ---###
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +###--- Internal configuration file functions ---###
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Load all available zonefiles from rpz-config and the internal configuration
>>>>>>>>>>>>>>>>> +sub _zonefiles_load {
>>>>>>>>>>>>>>>>> + # Clean start
>>>>>>>>>>>>>>>>> + %zonefiles = ();
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Source 1: Get the currently enabled zonefiles from rpz-config (expected format [name]=[URL])
>>>>>>>>>>>>>>>>> + my @enabled_files = &General::system_output('/usr/sbin/rpz-config', 'list');
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + foreach my $row (@enabled_files) {
>>>>>>>>>>>>>>>>> + chomp($row);
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Use regex instead of split() to skip non-matching lines
>>>>>>>>>>>>>>>>> + next unless($row =~ /^(\w+)=(.+)$/);
>>>>>>>>>>>>>>>>> + my ($name, $url) = ($1, $2);
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Unique names are already guaranteed by rpz-config
>>>>>>>>>>>>>>>>> + if(&_rpz_validate_zonefile($name, $url, '', 0) == 0) {
>>>>>>>>>>>>>>>>> + # Populate global data hash, mark all found entries as enabled
>>>>>>>>>>>>>>>>> + my %entry = ('enabled' => 'on',
>>>>>>>>>>>>>>>>> + 'url' => $url,
>>>>>>>>>>>>>>>>> + 'remark' => $Lang::tr{'rpz zf imported'});
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Source 2: Get additional data and disabled entries from configuration file
>>>>>>>>>>>>>>>>> + my %configured_files = ();
>>>>>>>>>>>>>>>>> + &General::readhasharray($ZONEFILES_CONF, \%configured_files);
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + foreach my $row (values (%configured_files)) {
>>>>>>>>>>>>>>>>> + my ($name, $enabled, $url, $remark) = @$row;
>>>>>>>>>>>>>>>>> + $remark //= "";
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + next unless($name);
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Check whether this row belongs to an entry already imported from rpz-config
>>>>>>>>>>>>>>>>> + if(defined $zonefiles{$name}) {
>>>>>>>>>>>>>>>>> + # Existing entry, only merge additional data
>>>>>>>>>>>>>>>>> + $zonefiles{$name}{'remark'} = $remark;
>>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>>> + # Skip entry if it is marked as enabled but not found by rpz-config. It was then deleted manually
>>>>>>>>>>>>>>>>> + if($enabled ne 'on') {
>>>>>>>>>>>>>>>>> + # Populate global data hash
>>>>>>>>>>>>>>>>> + my %entry = ('enabled' => 'off',
>>>>>>>>>>>>>>>>> + 'url' => $url // "",
>>>>>>>>>>>>>>>>> + 'remark' => $remark);
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Save internal zonefiles configuration
>>>>>>>>>>>>>>>>> +sub _zonefiles_save_conf {
>>>>>>>>>>>>>>>>> + my $index = 0;
>>>>>>>>>>>>>>>>> + my %export = ();
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Loop trough all zonefiles and create "hasharray" type export
>>>>>>>>>>>>>>>>> + foreach my $name (keys %zonefiles) {
>>>>>>>>>>>>>>>>> + my @entry = ($name,
>>>>>>>>>>>>>>>>> + $zonefiles{$name}{'enabled'},
>>>>>>>>>>>>>>>>> + $zonefiles{$name}{'url'},
>>>>>>>>>>>>>>>>> + $zonefiles{$name}{'remark'});
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + $export{$index++} = \@entry;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + &General::writehasharray($ZONEFILES_CONF, \%export);
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Load custom lists from rpz-config and the internal configuration
>>>>>>>>>>>>>>>>> +sub _customlists_load {
>>>>>>>>>>>>>>>>> + # Clean start
>>>>>>>>>>>>>>>>> + %customlists = ();
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Load configuration file
>>>>>>>>>>>>>>>>> + my %lists_conf = ();
>>>>>>>>>>>>>>>>> + &General::readhasharray($CUSTOMLISTS_CONF, \%lists_conf);
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Get list options, enabled by default to start import
>>>>>>>>>>>>>>>>> + $customlists{'allow'}{'enabled'} = $lists_conf{2}[0] // 'on';
>>>>>>>>>>>>>>>>> + $customlists{'block'}{'enabled'} = $lists_conf{2}[1] // 'on';
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Import enabled list from rpz-config, otherwise retrieve stored or empty list from configuration file
>>>>>>>>>>>>>>>>> + if($customlists{'allow'}{'enabled'} eq 'on') {
>>>>>>>>>>>>>>>>> + &_customlist_import('allow', $RPZ_ALLOWLIST);
>>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>>> + $customlists{'allow'}{'list'} = $lists_conf{0} // [];
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + if($customlists{'block'}{'enabled'} eq 'on') {
>>>>>>>>>>>>>>>>> + &_customlist_import('block', $RPZ_BLOCKLIST);
>>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>>> + $customlists{'block'}{'list'} = $lists_conf{1} // [];
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Save internal custom lists configuration
>>>>>>>>>>>>>>>>> +sub _customlists_save_conf {
>>>>>>>>>>>>>>>>> + my %export = ();
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Match IDs with import function
>>>>>>>>>>>>>>>>> + $export{0} = $customlists{'allow'}{'list'};
>>>>>>>>>>>>>>>>> + $export{1} = $customlists{'block'}{'list'};
>>>>>>>>>>>>>>>>> + $export{2} = [$customlists{'allow'}{'enabled'}, $customlists{'block'}{'enabled'}];
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + &General::writehasharray($CUSTOMLISTS_CONF, \%export);
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Import a custom list from plain file, returns empty list if file is missing
>>>>>>>>>>>>>>>>> +sub _customlist_import {
>>>>>>>>>>>>>>>>> + my ($listname, $filename) = @_;
>>>>>>>>>>>>>>>>> + my @list = ();
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # File exists, load and check all lines
>>>>>>>>>>>>>>>>> + if(-f $filename) {
>>>>>>>>>>>>>>>>> + open(my $FH, '<', $filename) or die "Can't read $filename: $!";
>>>>>>>>>>>>>>>>> + while(my $line = <$FH>) {
>>>>>>>>>>>>>>>>> + chomp($line);
>>>>>>>>>>>>>>>>> + push(@list, $line);
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + close($FH);
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Clean up imported data
>>>>>>>>>>>>>>>>> + &_rpz_validate_customlist(\@list, 1);
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + $customlists{$listname}{'list'} = \@list;
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Export a custom list to plain file or clear file if list is disabled
>>>>>>>>>>>>>>>>> +sub _customlist_export {
>>>>>>>>>>>>>>>>> + my ($listname, $filename) = @_;
>>>>>>>>>>>>>>>>> + return unless(defined $customlists{$listname});
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Write enabled domain list to file, otherwise save empty file
>>>>>>>>>>>>>>>>> + open(my $FH, '>', $filename) or die "Can't write $filename: $!";
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + if($customlists{$listname}{'enabled'} eq 'on') {
>>>>>>>>>>>>>>>>> + foreach my $line (@{$customlists{$listname}{'list'}}) {
>>>>>>>>>>>>>>>>> + print $FH "$line\n";
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>>> + print $FH "; Note: This list is currently disabled by $ENV{'SCRIPT_NAME'}\n";
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + close($FH);
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +###--- Internal gui functions ---###
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Show simple message box
>>>>>>>>>>>>>>>>> +sub _print_message {
>>>>>>>>>>>>>>>>> + my ($message, $title) = @_;
>>>>>>>>>>>>>>>>> + $title ||= $Lang::tr{'error messages'};
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $title);
>>>>>>>>>>>>>>>>> + print "<span>$message</span>";
>>>>>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Show all zone files and related gui elements
>>>>>>>>>>>>>>>>> +sub _print_zonefiles {
>>>>>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf'});
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>>>> +<table class="tbl" width="100%">
>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>> + <th>$Lang::tr{'name'}</th>
>>>>>>>>>>>>>>>>> + <th>URL</th>
>>>>>>>>>>>>>>>>> + <th>$Lang::tr{'remark'}</th>
>>>>>>>>>>>>>>>>> + <th colspan="3">$Lang::tr{'action'}</th>
>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Sort zonefiles by name and loop trough all entries
>>>>>>>>>>>>>>>>> + foreach my $name (sort keys %zonefiles) {
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Toggle button label translation
>>>>>>>>>>>>>>>>> + my $toggle_tr = ($zonefiles{$name}{'enabled'} eq 'on') ? $Lang::tr{'click to disable'} : $Lang::tr{'click to enable'};
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>> + <td>$name</td>
>>>>>>>>>>>>>>>>> + <td>$zonefiles{$name}{'url'}</td>
>>>>>>>>>>>>>>>>> + <td>$zonefiles{$name}{'remark'}</td>
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_TOGGLE">
>>>>>>>>>>>>>>>>> + <input type="image" src="/images/$zonefiles{$name}{'enabled'}.gif" title="$toggle_tr" alt="$toggle_tr">
>>>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_EDIT">
>>>>>>>>>>>>>>>>> + <input type="image" src="/images/edit.gif" title="$Lang::tr{'edit'}" alt="$Lang::tr{'edit'}">
>>>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_REMOVE">
>>>>>>>>>>>>>>>>> + <input type="image" src="/images/delete.gif" title="$Lang::tr{'remove'}" alt="$Lang::tr{'remove'}">
>>>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Disable reload button if not needed
>>>>>>>>>>>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>>>> +</table>
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +<div class="right">
>>>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="">
>>>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="ZF_EDIT">$Lang::tr{'add'}</button>
>>>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>>>> +</div>
>>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Show zonefiles entry editor
>>>>>>>>>>>>>>>>> +sub _print_zonefile_editor {
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Key specified: Edit existing entry
>>>>>>>>>>>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>>>>>>>>>>>> + # Load data to be edited, but don't override already present values (allows user to edit after error)
>>>>>>>>>>>>>>>>> + $cgiparams{'ZF_NAME'} //= $action_key;
>>>>>>>>>>>>>>>>> + $cgiparams{'ZF_URL'} //= $zonefiles{$action_key}{'url'};
>>>>>>>>>>>>>>>>> + $cgiparams{'ZF_REMARK'} //= $zonefiles{$action_key}{'remark'};
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Fallback to empty form
>>>>>>>>>>>>>>>>> + $cgiparams{'ZF_NAME'} //= "";
>>>>>>>>>>>>>>>>> + $cgiparams{'ZF_URL'} //= "";
>>>>>>>>>>>>>>>>> + $cgiparams{'ZF_REMARK'} //= "";
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf editor'});
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>>> +<input type="hidden" name="KEY" value="$action_key">
>>>>>>>>>>>>>>>>> +<table width="100%">
>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>> + <td width="20%">$Lang::tr{'name'}: <img src="/blob.gif" alt="*"></td>
>>>>>>>>>>>>>>>>> + <td><input type="text" name="ZF_NAME" value="$cgiparams{'ZF_NAME'}" size="40" maxlength="32" title="$Lang::tr{'rpz zf remark info'}" pattern="[a-zA-Z0-9_]{1,32}" required></td>
>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>> + <td width="20%">URL: <img src="/blob.gif" alt="*"></td>
>>>>>>>>>>>>>>>>> + <td><input type="url" name="ZF_URL" value="$cgiparams{'ZF_URL'}" size="40" maxlength="128" required></td>
>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>> + <td width="20%">$Lang::tr{'remark'}:</td>
>>>>>>>>>>>>>>>>> + <td><input type="text" name="ZF_REMARK" value="$cgiparams{'ZF_REMARK'}" size="40" maxlength="32"></td>
>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>> + <td colspan="2"><hr></td>
>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>> + <td width="55%"><img src="/blob.gif" alt="*"> $Lang::tr{'required field'}</td>
>>>>>>>>>>>>>>>>> + <td align="right"><button type="submit" name="ACTION" value="ZF_SAVE">$Lang::tr{'save'}</button></td>
>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>> +</table>
>>>>>>>>>>>>>>>>> +</form>
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +<div class="right">
>>>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="NONE">$Lang::tr{'back'}</button>
>>>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>>>> +</div>
>>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Show custom allow/block files and related gui elements
>>>>>>>>>>>>>>>>> +sub _print_customlists {
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Load lists from config, unless they are currently being edited
>>>>>>>>>>>>>>>>> + if($action ne 'CL_SAVE') {
>>>>>>>>>>>>>>>>> + $cgiparams{'ALLOW_LIST'} = join("\n", @{$customlists{'allow'}{'list'}});
>>>>>>>>>>>>>>>>> + $cgiparams{'BLOCK_LIST'} = join("\n", @{$customlists{'block'}{'list'}});
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + $cgiparams{'ALLOW_ENABLED'} = ($customlists{'allow'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>>>>>>>>>>>> + $cgiparams{'BLOCK_ENABLED'} = ($customlists{'block'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Fallback to empty form
>>>>>>>>>>>>>>>>> + $cgiparams{'ALLOW_LIST'} //= "";
>>>>>>>>>>>>>>>>> + $cgiparams{'BLOCK_LIST'} //= "";
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # HTML checkboxes, unchecked = no or undef value in POST data
>>>>>>>>>>>>>>>>> + my %checked = ();
>>>>>>>>>>>>>>>>> + $checked{'ALLOW_ENABLED'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? " checked" : "";
>>>>>>>>>>>>>>>>> + $checked{'BLOCK_ENABLED'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? " checked" : "";
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Disable reload button if not needed
>>>>>>>>>>>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz cl'});
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>>> +<table width="100%">
>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl allow'}</b><br>$Lang::tr{'rpz cl allow info'}</td>
>>>>>>>>>>>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl block'}</b><br>$Lang::tr{'rpz cl block info'}</td>
>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>> + <td colspan="2"><textarea name="ALLOW_LIST" class="domainlist" cols="45">
>>>>>>>>>>>>>>>>> +$cgiparams{'ALLOW_LIST'}</textarea></td>
>>>>>>>>>>>>>>>>> + <td colspan="2"><textarea name="BLOCK_LIST" class="domainlist" cols="45">
>>>>>>>>>>>>>>>>> +$cgiparams{'BLOCK_LIST'}</textarea></td>
>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>> + <td><label for="allow_enabled">$Lang::tr{'rpz cl allow enable'}</label></td>
>>>>>>>>>>>>>>>>> + <td width="15%"><input type="checkbox" name="ALLOW_ENABLED" id="allow_enabled"$checked{'ALLOW_ENABLED'}></td>
>>>>>>>>>>>>>>>>> + <td><label for="block_enabled">$Lang::tr{'rpz cl block enable'}</label></td>
>>>>>>>>>>>>>>>>> + <td width="15%"><input type="checkbox" name="BLOCK_ENABLED" id="block_enabled"$checked{'BLOCK_ENABLED'}></td>
>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>> + <td colspan="4"><hr></td>
>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>> + <td align="right" colspan="4">
>>>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="CL_SAVE">$Lang::tr{'save'}</button>
>>>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>>>>>> +</table>
>>>>>>>>>>>>>>>>> +</form>
>>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Output javascript and extra gui elements
>>>>>>>>>>>>>>>>> +sub _print_gui_extras {
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Apply/Restart button modifier key handler
>>>>>>>>>>>>>>>>> + if(&_rpz_needs_reload()) {
>>>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>>>> +<script>
>>>>>>>>>>>>>>>>> + // Commit modifier key handler
>>>>>>>>>>>>>>>>> + (function(jq, document) {
>>>>>>>>>>>>>>>>> + var keyEventsOn = false; // Keyboard events attached
>>>>>>>>>>>>>>>>> + var keyModify = false; // Modifier key pressed
>>>>>>>>>>>>>>>>> + var mouseHover = false; // Mouse over commit button
>>>>>>>>>>>>>>>>> + var btnModified = false; // Button modified to "Restart"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + // Document-level key events, enable only while cursor is over button
>>>>>>>>>>>>>>>>> + function attachKeyEvents() {
>>>>>>>>>>>>>>>>> + if(keyEventsOn) {
>>>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + keyEventsOn = true;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + jq(document).on("keydown.rpz", function(event) {
>>>>>>>>>>>>>>>>> + if((!keyModify) && event.shiftKey) {
>>>>>>>>>>>>>>>>> + keyModify = true;
>>>>>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>>>> + jq(document).on("keyup.rpz", function(event) {
>>>>>>>>>>>>>>>>> + if(keyModify && (!event.shiftKey)) {
>>>>>>>>>>>>>>>>> + keyModify = false;
>>>>>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + function removeKeyEvents() {
>>>>>>>>>>>>>>>>> + keyModify = false;
>>>>>>>>>>>>>>>>> + if(keyEventsOn) {
>>>>>>>>>>>>>>>>> + jq(document).off("keydown.rpz keyup.rpz");
>>>>>>>>>>>>>>>>> + keyEventsOn = false;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + // Attach mouse hover events to commit buttons
>>>>>>>>>>>>>>>>> + function attachMouseEvents() {
>>>>>>>>>>>>>>>>> + jq("button.commit").on("mouseenter", function(event) {
>>>>>>>>>>>>>>>>> + if(!mouseHover) {
>>>>>>>>>>>>>>>>> + mouseHover = true;
>>>>>>>>>>>>>>>>> + attachKeyEvents();
>>>>>>>>>>>>>>>>> + // Handle already pressed key
>>>>>>>>>>>>>>>>> + keyModify = !!(event.shiftKey);
>>>>>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + // Cursor moved away: Disable key listener to minimize events
>>>>>>>>>>>>>>>>> + jq("button.commit").on("mouseleave", function() {
>>>>>>>>>>>>>>>>> + if(mouseHover) {
>>>>>>>>>>>>>>>>> + mouseHover = false;
>>>>>>>>>>>>>>>>> + removeKeyEvents();
>>>>>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + // Modify commit button
>>>>>>>>>>>>>>>>> + function handleModify() {
>>>>>>>>>>>>>>>>> + let modify = mouseHover && keyModify;
>>>>>>>>>>>>>>>>> + if(btnModified != modify) {
>>>>>>>>>>>>>>>>> + if(modify) {
>>>>>>>>>>>>>>>>> + jq("button.commit").text("$Lang::tr{'restart'}").val("UNB_RESTART");
>>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>>> + jq("button.commit").text("$Lang::tr{'rpz apply'}").val("RPZ_RELOAD");
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + btnModified = modify;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + // jQuery DOM ready
>>>>>>>>>>>>>>>>> + jq(function() {
>>>>>>>>>>>>>>>>> + attachMouseEvents();
>>>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>>>> + })(jQuery, document);
>>>>>>>>>>>>>>>>> +</script>
>>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>> + } # End of modifier key handler
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +###--- Internal action processing functions ---###
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Toggle zonefile on/off
>>>>>>>>>>>>>>>>> +sub _action_zf_toggle {
>>>>>>>>>>>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + my $result = 0;
>>>>>>>>>>>>>>>>> + my $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Perform toggle action
>>>>>>>>>>>>>>>>> + if($enabled eq 'on') {
>>>>>>>>>>>>>>>>> + $enabled = 'off';
>>>>>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>>> + $enabled = 'on';
>>>>>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $action_key, $zonefiles{$action_key}{'url'}, '--no-reload');
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Save changes
>>>>>>>>>>>>>>>>> + $zonefiles{$action_key}{'enabled'} = $enabled;
>>>>>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Remove zonefile
>>>>>>>>>>>>>>>>> +sub _action_zf_remove {
>>>>>>>>>>>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Remove from rpz-config if currently active
>>>>>>>>>>>>>>>>> + if($zonefiles{$action_key}{'enabled'} eq 'on') {
>>>>>>>>>>>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Remove from data hash and save changes
>>>>>>>>>>>>>>>>> + delete $zonefiles{$action_key};
>>>>>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Clear action_key, as the entry is now removed entirely
>>>>>>>>>>>>>>>>> + $action_key = "";
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Create or update zonefile entry
>>>>>>>>>>>>>>>>> +# Returns undef if gui needs to stay in editor mode
>>>>>>>>>>>>>>>>> +sub _action_zf_save {
>>>>>>>>>>>>>>>>> + my $result = 0;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + my $name = $cgiparams{'ZF_NAME'} // "";
>>>>>>>>>>>>>>>>> + my $url = $cgiparams{'ZF_URL'} // "";
>>>>>>>>>>>>>>>>> + my $remark = $cgiparams{'ZF_REMARK'} // "";
>>>>>>>>>>>>>>>>> + my $enabled = 'on'; # Enable new entries by default
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Note on variables:
>>>>>>>>>>>>>>>>> + # name = unique key, will be used to address the entry
>>>>>>>>>>>>>>>>> + # action_key = name of the entry being edited, empty for new entry
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Only check for unique name if it changed
>>>>>>>>>>>>>>>>> + # (this also checks new entries because the action_key is empty in this case)
>>>>>>>>>>>>>>>>> + $result = &_rpz_validate_zonefile($name, $url, $remark, (lc($name) ne lc($action_key)));
>>>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 0);
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Edit existing entry: Determine what was changed
>>>>>>>>>>>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>>>>>>>>>>>> + # Name und URL remain unchanged, only save remark and finish
>>>>>>>>>>>>>>>>> + if(($name eq $action_key) && ($url eq $zonefiles{$action_key}{'url'})) {
>>>>>>>>>>>>>>>>> + $zonefiles{$action_key}{'remark'} = $remark;
>>>>>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Entry was changed and needs to be recreated, preserve status
>>>>>>>>>>>>>>>>> + $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Remove from rpz-config
>>>>>>>>>>>>>>>>> + return unless &_action_zf_remove();
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Add new entry to rpz-config
>>>>>>>>>>>>>>>>> + if($enabled eq 'on') {
>>>>>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $name, $url, '--no-reload');
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Add to global data hash and save changes
>>>>>>>>>>>>>>>>> + my %entry = ('enabled' => $enabled,
>>>>>>>>>>>>>>>>> + 'url' => $url,
>>>>>>>>>>>>>>>>> + 'remark' => $remark);
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Save custom lists
>>>>>>>>>>>>>>>>> +sub _action_cl_save {
>>>>>>>>>>>>>>>>> + return unless((defined $cgiparams{'ALLOW_LIST'}) && (defined $cgiparams{'BLOCK_LIST'}));
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + my $result = 0;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + my @allowlist = split(/\R/, $cgiparams{'ALLOW_LIST'});
>>>>>>>>>>>>>>>>> + my @blocklist = split(/\R/, $cgiparams{'BLOCK_LIST'});
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Validate lists
>>>>>>>>>>>>>>>>> + $result = &_rpz_validate_customlist(\@allowlist);
>>>>>>>>>>>>>>>>> + if($result != 0) {
>>>>>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr(202, $result);
>>>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + $result = &_rpz_validate_customlist(\@blocklist);
>>>>>>>>>>>>>>>>> + if($result != 0) {
>>>>>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr(203, $result);
>>>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Add to global data hash and save changes
>>>>>>>>>>>>>>>>> + $customlists{'allow'}{'list'} = \@allowlist;
>>>>>>>>>>>>>>>>> + $customlists{'block'}{'list'} = \@blocklist;
>>>>>>>>>>>>>>>>> + $customlists{'allow'}{'enabled'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? 'on' : 'off';
>>>>>>>>>>>>>>>>> + $customlists{'block'}{'enabled'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? 'on' : 'off';
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + &_customlists_save_conf();
>>>>>>>>>>>>>>>>> + &_customlist_export('allow', $RPZ_ALLOWLIST);
>>>>>>>>>>>>>>>>> + &_customlist_export('block', $RPZ_BLOCKLIST);
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Make new lists, request service reload on success
>>>>>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-make', 'allowblock', '--no-reload');
>>>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Trigger rpz-config reload
>>>>>>>>>>>>>>>>> +sub _action_rpz_reload {
>>>>>>>>>>>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Immediately clear flag to prevent multiple reloads
>>>>>>>>>>>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>>>>>>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Perform reload, recreate reload flag on error to enable retry
>>>>>>>>>>>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'reload');
>>>>>>>>>>>>>>>>> + if(not &_rpz_check_result($result, 0)) {
>>>>>>>>>>>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Trigger unbound restart
>>>>>>>>>>>>>>>>> +sub _action_unb_restart {
>>>>>>>>>>>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Immediately clear flag to prevent multiple restarts
>>>>>>>>>>>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>>>>>>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Perform restart, unboundctrl always exits zero
>>>>>>>>>>>>>>>>> + &General::system('/usr/local/bin/unboundctrl', 'restart');
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +###--- Internal rpz-config functions ---###
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Translate rpz-config exitcodes and messages
>>>>>>>>>>>>>>>>> +# 100-199: rpz-config, 200-299: webgui
>>>>>>>>>>>>>>>>> +sub _rpz_error_tr {
>>>>>>>>>>>>>>>>> + my ($error, $append) = @_;
>>>>>>>>>>>>>>>>> + $append //= '';
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Translate numeric exit codes
>>>>>>>>>>>>>>>>> + if(looks_like_number($error)) {
>>>>>>>>>>>>>>>>> + if(defined $Lang::tr{"rpz exitcode $error"}) {
>>>>>>>>>>>>>>>>> + $error = $Lang::tr{"rpz exitcode $error"};
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + return "RPZ $Lang::tr{'error'}: $error" . &Header::escape($append);
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Check result of rpz-config system call, request reload on success
>>>>>>>>>>>>>>>>> +sub _rpz_check_result {
>>>>>>>>>>>>>>>>> + my ($result, $request_reload) = @_;
>>>>>>>>>>>>>>>>> + $request_reload //= 0;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # exitcode 0 = success
>>>>>>>>>>>>>>>>> + if($result != 0) {
>>>>>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr($result);
>>>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Set reload flag
>>>>>>>>>>>>>>>>> + if($request_reload) {
>>>>>>>>>>>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Test whether reload flag is set
>>>>>>>>>>>>>>>>> +sub _rpz_needs_reload {
>>>>>>>>>>>>>>>>> + return (-f $RPZ_RELOAD_FLAG);
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Validate a zonefile entry, returns rpz-config exitcode on failure. Use _rpz_check_result to verify.
>>>>>>>>>>>>>>>>> +# unique = check for unique name
>>>>>>>>>>>>>>>>> +sub _rpz_validate_zonefile {
>>>>>>>>>>>>>>>>> + my ($name, $url, $remark, $unique) = @_;
>>>>>>>>>>>>>>>>> + $unique //= 1;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + unless($name =~ /^[a-zA-Z0-9_]{1,32}$/) {
>>>>>>>>>>>>>>>>> + return 101;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + unless($url =~ /^[\w+\.:;\/\\&@#%?=\-~|!]{1,128}$/) {
>>>>>>>>>>>>>>>>> + return 105;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + unless($remark =~ /^[\w \-()\.:;*\/\\?!&=]{0,32}$/) {
>>>>>>>>>>>>>>>>> + return 201;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Check against already existing names
>>>>>>>>>>>>>>>>> + if($unique) {
>>>>>>>>>>>>>>>>> + foreach my $existing (keys %zonefiles) {
>>>>>>>>>>>>>>>>> + if(lc($name) eq lc($existing)) {
>>>>>>>>>>>>>>>>> + return 104;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + return 0;
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Validate a custom list, returns number of rejected line on failure. Check for non-zero results.
>>>>>>>>>>>>>>>>> +# listref = array reference, cleanup = remove invalid entries instead of returning an error
>>>>>>>>>>>>>>>>> +sub _rpz_validate_customlist {
>>>>>>>>>>>>>>>>> + my ($listref, $cleanup) = @_;
>>>>>>>>>>>>>>>>> + $cleanup //= 0;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + foreach my $index (reverse 0..$#{$listref}) {
>>>>>>>>>>>>>>>>> + my $row = @$listref[$index];
>>>>>>>>>>>>>>>>> + next unless($row); # Skip/allow empty lines
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Reject/remove everything besides wildcard domains and remarks
>>>>>>>>>>>>>>>>> + if((not &General::validwildcarddomainname($row)) && (not $row =~ /^;[\w \-()\.:;*\/\\?!&=]*$/)) {
>>>>>>>>>>>>>>>>> + unless($cleanup) {
>>>>>>>>>>>>>>>>> + # +1 for user friendly line number and to ensure non-zero exitcode
>>>>>>>>>>>>>>>>> + return $index + 1;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Remove current row
>>>>>>>>>>>>>>>>> + splice(@$listref, $index, 1);
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + return 0;
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +###--- Internal misc functions ---###
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Send HTTP 303 redirect headers for post/request/get pattern
>>>>>>>>>>>>>>>>> +# (Must be sent before calling &Header::showhttpheaders())
>>>>>>>>>>>>>>>>> +sub _http_prg_redirect {
>>>>>>>>>>>>>>>>> + my $location = "https://$ENV{'SERVER_NAME'}:$ENV{'SERVER_PORT'}$ENV{'SCRIPT_NAME'}";
>>>>>>>>>>>>>>>>> + print "Status: 303 See Other\n";
>>>>>>>>>>>>>>>>> + print "Location: $location\n";
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> diff --git a/lfs/rpz b/lfs/rpz
>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>> index 000000000..7ddbc38e5
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/lfs/rpz
>>>>>>>>>>>>>>>>> @@ -0,0 +1,96 @@
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +# Definitions
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +include Config
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +SUMMARY = response policy zone - RPZ reputation system for unbound DNS
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +VER = 1.0.0
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +THISAPP = rpz-$(VER)
>>>>>>>>>>>>>>>>> +DIR_APP = $(DIR_SRC)/$(THISAPP)
>>>>>>>>>>>>>>>>> +TARGET = $(DIR_INFO)/$(THISAPP)
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +PROG = rpz
>>>>>>>>>>>>>>>>> +PAK_VER = 18
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +DEPS =
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +SERVICES =
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +# Top-level Rules
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +install : $(TARGET)
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +check :
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +download :
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +b2 :
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +dist:
>>>>>>>>>>>>>>>>> + @$(PAK)
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +# Installation Details
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +$(TARGET) :
>>>>>>>>>>>>>>>>> + @$(PREBUILD)
>>>>>>>>>>>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # RPZ scripts
>>>>>>>>>>>>>>>>> + install --verbose --mode=755 \
>>>>>>>>>>>>>>>>> + $(DIR_CONF)/rpz/{rpz-config,rpz-metrics,rpz-sleep,rpz-make,rpz-functions} \
>>>>>>>>>>>>>>>>> + --target-directory=/usr/sbin
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # RPZ config files
>>>>>>>>>>>>>>>>> + mkdir -pv /etc/unbound/local.d
>>>>>>>>>>>>>>>>> + install --verbose --mode=644 --owner=nobody --group=nobody \
>>>>>>>>>>>>>>>>> + $(DIR_CONF)/rpz/00-rpz.conf \
>>>>>>>>>>>>>>>>> + --target-directory=/etc/unbound/local.d
>>>>>>>>>>>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/local.d
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # RPZ custom list files for allow and block
>>>>>>>>>>>>>>>>> + mkdir -pv /var/ipfire/dns/rpz
>>>>>>>>>>>>>>>>> + touch /var/ipfire/dns/rpz/{allowlist,blocklist}
>>>>>>>>>>>>>>>>> + chown --verbose --recursive nobody:nobody /var/ipfire/dns/rpz
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # RPZ zone files
>>>>>>>>>>>>>>>>> + # create empty RPZ config file to avoid a unbound config error
>>>>>>>>>>>>>>>>> + mkdir -pv /etc/unbound/zonefiles
>>>>>>>>>>>>>>>>> + touch /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/zonefiles
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Install addon-specific language-files
>>>>>>>>>>>>>>>>> + install --verbose --mode=004 $(DIR_CONF)/rpz/rpz.*.pl \
>>>>>>>>>>>>>>>>> + --target-directory=/var/ipfire/addon-lang
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Install backup definition
>>>>>>>>>>>>>>>>> + cp -vf $(DIR_CONF)/backup/includes/rpz /var/ipfire/backup/addons/includes/rpz
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>>>>>>>>>>>> + @$(POSTBUILD)
>>>>>>>>>>>>>>>>> diff --git a/make.sh b/make.sh
>>>>>>>>>>>>>>>>> index 827ea9e77..a77535b13 100755
>>>>>>>>>>>>>>>>> --- a/make.sh
>>>>>>>>>>>>>>>>> +++ b/make.sh
>>>>>>>>>>>>>>>>> @@ -390,7 +390,7 @@ prepareenv() {
>>>>>>>>>>>>>>>>> if [ "${free_space}" -lt "${required_space}" ]; then
>>>>>>>>>>>>>>>>> # Add any consumed space
>>>>>>>>>>>>>>>>> while read -r consumed_space path; do
>>>>>>>>>>>>>>>>> - (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>>>>>>>>>>>> + (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>>>>>>>>>>>> done <<< "$(du --summarize --bytes "${BUILD_DIR}" "${IMAGES_DIR}" "${LOG_DIR}" 2>/dev/null)"
>>>>>>>>>>>>>>>>> fi
>>>>>>>>>>>>>>>>> @@ -2087,6 +2087,7 @@ build_system() {
>>>>>>>>>>>>>>>>> lfsmake2 btrfs-progs
>>>>>>>>>>>>>>>>> lfsmake2 inotify-tools
>>>>>>>>>>>>>>>>> lfsmake2 grub-btrfs
>>>>>>>>>>>>>>>>> + lfsmake2 rpz
>>>>>>>>>>>>>>>>> lfsmake2 linux
>>>>>>>>>>>>>>>>> lfsmake2 rtl8812au
>>>>>>>>>>>>>>>>> diff --git a/src/paks/rpz/install.sh b/src/paks/rpz/install.sh
>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>> index 000000000..ef99bf742
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/src/paks/rpz/install.sh
>>>>>>>>>>>>>>>>> @@ -0,0 +1,36 @@
>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +#
>>>>>>>>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>>>>>>>>> +extract_files
>>>>>>>>>>>>>>>>> +restore_backup ${NAME}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# fix user created files
>>>>>>>>>>>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>>>>>>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>>>>>>>>>>>> + /etc/unbound/zonefiles \
>>>>>>>>>>>>>>>>> + /etc/unbound/local.d
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Update Language cache
>>>>>>>>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# restart unbound to load config file
>>>>>>>>>>>>>>>>> +/etc/init.d/unbound restart
>>>>>>>>>>>>>>>>> diff --git a/src/paks/rpz/uninstall.sh b/src/paks/rpz/uninstall.sh
>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>> index 000000000..e11427df3
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/src/paks/rpz/uninstall.sh
>>>>>>>>>>>>>>>>> @@ -0,0 +1,38 @@
>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +#
>>>>>>>>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>>>>>>>>>>>> +/etc/init.d/unbound stop
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +make_backup ${NAME}
>>>>>>>>>>>>>>>>> +remove_files
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>>>>>>>>>>>> +# "[1723428668] unbound-control[17117:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>>>>>>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Update Language cache
>>>>>>>>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# start unbound to load unbound config file
>>>>>>>>>>>>>>>>> +/etc/init.d/unbound start
>>>>>>>>>>>>>>>>> diff --git a/src/paks/rpz/update.sh b/src/paks/rpz/update.sh
>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>> index 000000000..9bc340bc6
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/src/paks/rpz/update.sh
>>>>>>>>>>>>>>>>> @@ -0,0 +1,52 @@
>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +#
>>>>>>>>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# from update.sh
>>>>>>>>>>>>>>>>> +extract_backup_includes
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>>>>>>>>>>>> +/etc/init.d/unbound stop
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# from uninstall.sh
>>>>>>>>>>>>>>>>> +make_backup ${NAME}
>>>>>>>>>>>>>>>>> +remove_files
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>>>>>>>>>>>> +# "unbound-control[nn:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>>>>>>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# from install.sh
>>>>>>>>>>>>>>>>> +extract_files
>>>>>>>>>>>>>>>>> +restore_backup ${NAME}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# fix user created files
>>>>>>>>>>>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>>>>>>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>>>>>>>>>>>> + /etc/unbound/zonefiles \
>>>>>>>>>>>>>>>>> + /etc/unbound/local.d
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Update Language cache
>>>>>>>>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# restart unbound to load config files
>>>>>>>>>>>>>>>>> +/etc/init.d/unbound start
>>>>>>>>>>>>>>>>> --
>>>>>>>>>>>>>>>>> 2.39.5
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> Jon
>>>>>>>>>>>>>>> --
>>>>>>>>>>>>>>> Jon Murphy
>>>>>>>>>>>>>>> jon.murphy@ipfire.org
>>>>>>>>>>>>>
>>>>>>>>>>>>> Jon
>>>>>>>>>>>>> --
>>>>>>>>>>>>> Jon Murphy
>>>>>>>>>>>>> jon.murphy@ipfire.org
>
>
>
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
2025-03-24 14:38 ` Re[2]: " Jon Murphy
@ 2025-03-24 14:40 ` Michael Tremer
2025-03-24 14:42 ` Re[2]: " Jon Murphy
0 siblings, 1 reply; 30+ messages in thread
From: Michael Tremer @ 2025-03-24 14:40 UTC (permalink / raw)
To: Jon Murphy; +Cc: Bernhard Bitsch, IPFire: Development-List
Because it is not doing it on my system...
> On 24 Mar 2025, at 14:38, Jon Murphy <jon.murphy@ipfire.org> wrote:
>
> Actually it did.
>
> Why do you think Unbound did not?
>
>
> ------ Original Message ------
> From "Michael Tremer" <michael.tremer@ipfire.org>
> To "Jon Murphy" <jon.murphy@ipfire.org>
> Cc "Bernhard Bitsch" <bbitsch@ipfire.org>; "IPFire: Development-List" <development@lists.ipfire.org>
> Date 3/24/2025 9:36:53 AM
> Subject Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
>
>> Unbound did not put those there...
>>
>>> On 24 Mar 2025, at 14:33, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>
>>>
>>>
>>> And where are these stored?
>>>
>>> In `/etc/unbound/zonefiles`:
>>>
>>>
>>> [root@ipfire ~] # ls -al /etc/unbound/zonefiles
>>> total 20664
>>> drwxr-xr-x 2 nobody nobody 4096 Mar 24 04:40 .
>>> drwxr-xr-x 4 root root 4096 Mar 19 16:24 ..
>>> -rw-r--r-- 1 nobody nobody 3999087 Mar 23 15:11 adhocSB.rpz
>>> -rw-r--r-- 1 nobody nobody 1411 Mar 23 14:23 allow.rpz
>>> -rw-r--r-- 1 nobody nobody 25355 Mar 24 04:40 AmazonTrkrHZ.rpz
>>> -rw-r--r-- 1 nobody nobody 7241 Mar 24 04:40 AppleTrkrHZ.rpz
>>> -rw-r--r-- 1 nobody nobody 178 Mar 23 14:23 block.rpz
>>> -rw-r--r-- 1 nobody nobody 78496 Mar 24 04:40 DOHblockHZ.rpz
>>> -rw-r--r-- 1 nobody nobody 16983551 Mar 24 04:40 MxProPlusHZ.rpz
>>> -rw-r--r-- 1 nobody nobody 2893 Mar 24 04:40 tldHZ.rpz
>>> -rw-r--r-- 1 nobody nobody 29419 Mar 24 04:40 WinTrkrHZ.rpz
>>> [root@ipfire ~] #
>>>
>>>
>>>
>>> ------ Original Message ------
>>> From "Michael Tremer" <michael.tremer@ipfire.org>
>>> To "Bernhard Bitsch" <bbitsch@ipfire.org>
>>> Cc development@lists.ipfire.org
>>> Date 3/24/2025 9:25:40 AM
>>> Subject Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
>>>
>>>> Hello,
>>>>
>>>>>
>>>>> On 24 Mar 2025, at 13:33, Bernhard Bitsch <bbitsch@ipfire.org> wrote:
>>>>> Am 24.03.2025 um 11:17 schrieb Michael Tremer:
>>>>>>
>>>>>>
>>>>>> Hello Jon,
>>>>>>>
>>>>>>>
>>>>>>> On 24 Mar 2025, at 00:00, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>> Michael,
>>>>>>> FYI - I was wrong Unbound RPZ is _not_ watching the serial number, it is watching the "refresh", the number after the serial number.
>>>>>>
>>>>>> Refresh just tells the client how often to check for an update.
>>>>>> If that is actually being set by the list publisher, then we have another problem here, because they could put some insanely low value there and we would then DDoS their infrastructure. I think we should keep it like we have it in other places that we control how often we want to check or pull for updates.
>>>>>>
>>>>> You are right. But an extra update process wastes additional processor time. The update mechanism of unbound does the check for update ( however it is realized ) nevertheless.
>>>>
>>>> Yes, doing more things needs resources. But we are not seriously considering whether an IPFire system has enough resources to perform the download of a text file, or are we?
>>>>
>>>>>
>>>>>>> I understand that you don’t speak C, but you got the information from somewhere. Documentation maybe? Since that is out of date very often I like to consult the code.
>>>>>>> From testing. Downloading rpz files using rpz unbound, and watching what happens. If the rpz file is setup for "once per day" refresh, then it only downloads one time.
>>>>>>> However that won’t solve our problem . . . and having no cache.
>>>>>>> In `/etc/unbound/tuning.conf` there is `rrset-cache-size: 128m`. Are you referring to a different cache.
>>>>>>
>>>>>> Naturally unbound is loading the zone into its memory which we generally call cache.
>>>>>> When I say cache I am thinking about persistent data storage across multiple restarts of Unbound. If I am downloading 100 MiB of RPZ lists (which is presumably still on the lower end) and I reboot my firewall, I do not want to download the same data again. We can only ever download a list *once* unless we are 100% certain that it has changed. Then we can download it once again.
>>>>>
>>>>> The RPZ lists are stored in files in persistent storage. Unbound creates the internal cache from these.
>>>>
>>>> And where are these stored?
>>>>
>>>>>
>>>>>>> Maybe we need to implement both?
>>>>>>> Yes. There are very few AXFR list (I think only four were found). And many more HTTPS rpz files.
>>>>>>> Jon
>>>>>>> ------ Original Message ------
>>>>>>> From "Michael Tremer" <michael.tremer@ipfire.org>
>>>>>>> To "Jon Murphy" <jon.murphy@ipfire.org>
>>>>>>> Cc "IPFire: Development-List" <development@lists.ipfire.org>
>>>>>>> Date 3/20/2025 11:26:43 AM
>>>>>>> Subject Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
>>>>>>>
>>>>>>>>
>>>>>>>> Hello Jon,
>>>>>>>> Please don’t forget to Cc the list...
>>>>>>>>
>>>>>>>>>
>>>>>>>>> On 19 Mar 2025, at 18:27, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>>> Michael,
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> Where in the code is this implemented? I cannot find anything like this:
>>>>>>>>>
>>>>>>>>> Keep in mind I am not a "C" person. Maybe in this section?:
>>>>>>>>> https://git.ipfire.org/?p=thirdparty/unbound.git;a=blob;f=services/authzone.c;hb=30b9cb5f813003d0a2b1c2e678652396615b1b7d#l5875
>>>>>>>>
>>>>>>>> This where the AXFR response is being handled when doing a DNS zone transfer. This code is not being called when performing a HTTP download.
>>>>>>>> I understand that you don’t speak C, but you got the information from somewhere. Documentation maybe? Since that is out of date very often I like to consult the code.
>>>>>>>>
>>>>>>>>>
>>>>>>>>> —
>>>>>>>>> When I was just learning about RPZ I created a separate RPZ file for testing. When I changed the SOA line with a new serial number, the RPZ file download would happen in about 5 minutes.
>>>>>>>>> https://people.ipfire.org/~jon/sblack-adhoc.rpz
>>>>>>>>
>>>>>>>> It might well be that the file is not being reloaded if the download matches the content that unbound already has. That would of course save some resources.
>>>>>>>> However that won’t solve our problem with redundant downloads and having no cache.
>>>>>>>>
>>>>>>>>>
>>>>>>>>> That is how I found out the SOA line is watched for a serial number change.
>>>>>>>>> I’ll reconfirm my findings.
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>> The second reason is that we have a lot of firewalls out there. Not all of them will enable this feature and all of the lists, but even if it is a good chunk, we will generate terabytes of traffic which put load on the infrastructure and will cost money. It simply is not what we want to do, regardless of self-hosting those lists and pulling them from somewhere else.
>>>>>>>>>
>>>>>>>>> So I understand, are you thinking of hosting RPZ AXFR (DNS zone transfer) on IPFire infrastructure?
>>>>>>>>
>>>>>>>> No, I don’t think that we can generally do this. The biggest problem is licensing as we cannot take anyones content and host it ourselves. We would re-distribute those lists and that will only work with permission of the publishers. I assume that would be too much work to actually get some useful content out there. We might limit ourselves to only those lists that are under a very permissive license. Nobody wants that.
>>>>>>>> From a technical point of view, DNS over TCP might not be very nice in terms of forging the transfer and so we would need TLS as well… It should work, but even if we would be able to encourage other people to publish their lists I doubt they would implement DNS over TLS for authoritative DNS. That standard is in very early stages as well.
>>>>>>>> As far as I can see, those vendors who offer a list as a commercial product are using DNS to distribute it (e.g. Spamhaus). Those people who have made this all a hobby are throwing the lists onto GitHub and let them handle the traffic.
>>>>>>>> Maybe we need to implement both?
>>>>>>>> -Michael
>>>>>>>>
>>>>>>>>>
>>>>>>>>> Jon
>>>>>>>>> On 3/19/25 5:35 AM, Michael Tremer wrote:
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> Hello Jon,
>>>>>>>>>> Where in the code is this implemented? I cannot find anything like this:
>>>>>>>>>> Unbound loads the entire file into memory and then starts parsing it. The only special treatment there is is to check whether the first line is a valid zone entry. It does not even have to be a SOA record.
>>>>>>>>>> https://git.ipfire.org/?p=thirdparty/unbound.git;a=blob;f=services/authzone.c;hb=30b9cb5f813003d0a2b1c2e678652396615b1b7d#l1188
>>>>>>>>>> I am also concerned that Unbound will not be able to support an upstream proxy for any downloads. The caching situation is also unclear for me, so I believe that we will be looking at writing a custom downloader that implements all these things.
>>>>>>>>>> -Michael
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> On 19 Mar 2025, at 02:58, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>>>>> Michael,
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>> The emphasis is on the repeated downloads of the same list. That is
>>>>>>>>>>>
>>>>>>>>>>> > what cannot happen.
>>>>>>>>>>> The Unbound RPZ code, as installed within IPFire, watches for a change
>>>>>>>>>>> in the SOA line of each RPZ file. This is an example of the first few
>>>>>>>>>>> lines for every RPZ file.
>>>>>>>>>>> $TTL 300
>>>>>>>>>>> @ SOA localhost. root.localhost. 1742298960 43200 3600 86400 300
>>>>>>>>>>> NS localhost.
>>>>>>>>>>> ;
>>>>>>>>>>> ; Title: HaGeZi's Pop-Up Ads DNS Blocklist
>>>>>>>>>>> ; Description: Blocks annoying and malicious pop-up ads.
>>>>>>>>>>> If the SOA serial number changes (e.g. the 1742298960), then Unbound RPZ
>>>>>>>>>>> code does its thing and downloads. Otherwise there is no download.
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>> So there has to be a way to ensure that we won’t download a list again
>>>>>>>>>>>
>>>>>>>>>>> > unless it has actually changed.
>>>>>>>>>>> This should do what you want but I may be missing your point.
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>> DNS has a builtin functionality called AXFR. It simply does the job
>>>>>>>>>>>
>>>>>>>>>>> > for you. I was just wondering whether that was not being used.
>>>>>>>>>>> I need to read about AXFR/IXFR and learn a little more.
>>>>>>>>>>> Jon
>>>>>>>>>>> On 3/17/25 5:35 AM, Michael Tremer wrote:
>>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>> Good Morning Jon,
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>> On 16 Mar 2025, at 17:00, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>>>>>>> Michael,
>>>>>>>>>>>>> I was reading through you response again an I want to understand this post:
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>>>>>>>>>>>>
>>>>>>>>>>>>> So if RPZ doesn't use HTTPS, what is it using? I am missing a key point here.
>>>>>>>>>>>>
>>>>>>>>>>>> The emphasis is on the repeated downloads of the same list. That is what cannot happen.
>>>>>>>>>>>> Although it might not affect a lot of people in our general user-base, there are some that have a metered connection and will pay for data by volume. Some of the lists I looked at are just under 20 MiB. Therefore we need to keep any traffic down to a minimum. The second reason is that we have a lot of firewalls out there. Not all of them will enable this feature and all of the lists, but even if it is a good chunk, we will generate terabytes of traffic which put load on the infrastructure and will cost money. It simply is not what we want to do, regardless of self-hosting those lists and pulling them from somewhere else.
>>>>>>>>>>>> So there has to be a way to ensure that we won’t download a list again unless it has actually changed.
>>>>>>>>>>>> DNS has a builtin functionality called AXFR. It simply does the job for you. I was just wondering whether that was not being used.
>>>>>>>>>>>> HTTPS is an option because that is simply what we use elsewhere, but extra functionality will have to be built for it.
>>>>>>>>>>>> -Michael
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>> Jon
>>>>>>>>>>>>> On 2/13/25 3:34 PM, jon wrote:
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Michael,
>>>>>>>>>>>>>> I’ve read through your comments a few times and I ended up with many more questions.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> To me the efforts to get new code accepted seem to have changed and it seemed easier in the past. In the past I made the Core Team aware via the Dev Mailing List and wrote a simple two or three paragraphs of "What is it? / What is the value? / Here is the code"
>>>>>>>>>>>>>> So in an effort to move forward: How exactly is something presented to the Core Team?
>>>>>>>>>>>>>> Is there an example of a recent effort that was presented that I can see as a sample? (This type of info can also be added to the Wiki)
>>>>>>>>>>>>>> I understand you want it this way, but I don’t know what exactly is needed. Please be specific.
>>>>>>>>>>>>>> Jon
>>>>>>>>>>>>>> PS - I am not ignoring your other comments, I am just trying to move forward and keep things simple.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> On Feb 8, 2025, at 1:27 PM, Michael Tremer <michael.tremer@ipfire.org> wrote:
>>>>>>>>>>>>>>> Hello Jon,
>>>>>>>>>>>>>>> Thanks for your reply. And good that you are copying everyone into this conversation.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> On 8 Feb 2025, at 18:41, jon <jon.murphy@ipfire.org> wrote:
>>>>>>>>>>>>>>>> Michael,
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> Yes, this has been a separate effort (a very public separate effort). Yes, as you pointed this out early on with the "proof-of-concept" and then my request for people to help test RPZ. Nothing was hidden.
>>>>>>>>>>>>>>>> This was done because you (and maybe others) did not have the time and I wanted to help and because I needed assistance with RPZ. I tried my best to do this without bothering you.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> I don’t that it is accurate that nobody wanted to help on this. The list was always open - although not every email has been replied to swiftly it is also your responsibility to raise a question again if it was missed. People here have open ears.
>>>>>>>>>>>>>>> It was also stated on this very list on in our documentation that working on something without involving the core team is a risky undertaking. Of course IPFire is free software and so everyone is free to fork if they wish to do so.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> You were aware many steps along the way. See your email on July 28, 2024, August 15, 2024, September 30, 2024, December 23, 2024, and January 16. My attempts to get the team involved were met with "things are busy" and sometimes silence. (Yes, I get it, people are busy.)
>>>>>>>>>>>>>>>> You and Adolf, Leo, Erik and Bernhard have been aware since the beginning. You mention you were aware of the "proof-of-concept". If you include those beginning posts, since Sep 2023.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> Yes, I am aware of a proof-of-concept that I have been running myself for a long time. I am also aware of the efforts that you have been taking.
>>>>>>>>>>>>>>> Yet I don’t think there has ever been any joint effort, or am I seeing that wrong?
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> This has not been discussed . . . on our calls.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> On the July 28th you stated:
>>>>>>>>>>>>>>>> "We have talked about RPZ many times on the monthly call since the URL filter feature is falling more and more out of fashion. I think there is also many posts about this on the forum."
>>>>>>>>>>>>>>>> Please don’t insult me again by stating "you know what I mean".
>>>>>>>>>>>>>>>> And it has been discussed but not documented in the Monthly Meeting notes.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> I am not at all insulting you. I don’t want to take this down to a personal level at all. This is a public mailing list and people who read this don’t need to listen to an argument we are having. They are here for the tech inside IPFire.
>>>>>>>>>>>>>>> When I wrote that it has not been discussed that does not mean that we have not been touching on the topic. We have been talking about lots of things on the calls, the weather, politics, how our pets are. None of that makes it to the logs. What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> Regular conversation on the Dev Mailing list is many times met with silence. I get it, people are busy.
>>>>>>>>>>>>>>>> And regular two-way conversation doesn’t happen on the list. At least not with me. I’d be happy to point out the posts that were met with silence.
>>>>>>>>>>>>>>>> Again, I get it, people are busy.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> And you think my emails are not being met with silence? This has nothing to do with this specific topic. This has something to do with how occupied people are and how engaged they are on certain topics. Not everyone is involved in all the things and simply will ignore emails simply based on their subject line.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> But the "dip here to the list" were my attempts to get a conversation started. As I said, many time met with silence.
>>>>>>>>>>>>>>>> The only place I was not met with silence was on the Community. You have a great group of people in the Community. It is a shame you don’t want to have others help. It would reduce your workload.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> You should stop making statements that are not true. Who doesn’t want anyone to help?
>>>>>>>>>>>>>>> Not having this conversation on a Saturday evening would reduce my workload. At least it would free up time for something else. Helping with the things that are already on the go would reduce the workload of the entire team. Starting one thing at a time and finishing it is a lot better to manage than starting a hundred things and not even finish one. I can tell you that I already have a hundred things on the go.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> Therefore, what am I supposed to do with this email?
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> To me it is beyond obvious…
>>>>>>>>>>>>>>>> If it isn’t what you want, then guide me with how to do this the correct way. And be specific. I am trying to help. I am trying to make things better. I am trying to do things the right way.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> To me it isn’t. This is yet another project that has been dumped to the list like so many before and later on everyone has left to have the team deal with the rest.
>>>>>>>>>>>>>>> It is a huge patch set. You explained what the vision is, but that is about it. There is no chance this will continue if this disagreement isn’t solved first. I didn’t even look at the code.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> I don’t want to merge code that I don’t agree with.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> I asked multiple times if you "agreed with the concept" and again, met with silence. Yes I get it, people are busy.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> Having support for RPZ? Yes, it was definitely on the roadmap. That I agree with.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> You mentioned this a in the past, but for some reason you do not disclose what I dismissed. Why do you continue to make this harder, wouldn’t it not be easier to tell me what I have dismissed?
>>>>>>>>>>>>>>>> I have sent multiple emails trying to answer your concerns and comments. On July 28, Aug 14, Aug 22, Aug 23, Sep 30, etc.
>>>>>>>>>>>>>>>> I’ve gone through all of the questions you asked and I cannot find a "dismissed" item.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> Maybe I need to be *more clear*. I feel humoured by this.
>>>>>>>>>>>>>>> It is late on a Saturday and I want my dinner soon, but certainly I have stated that this should never be an add-on considering it is supposed to replace URL Filter. We should never allow people to add their own sources. I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> The maintainers of Unbound and/or RPZ?
>>>>>>>>>>>>>>>> The maintainers of Hagezi list, the threatfox list, the urlhaus list, etc.?
>>>>>>>>>>>>>>>> What else? The maintainers or the RPZ scripts? That is me. Let’s talk!
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> You. I don’t care much about the providers of the lists.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> See, this is where it gets confusing. There are hundreds of open source packages as part of IPFire. Pick the last five years of items added to the IPFire build. You're telling me you have "constructive conversation with the maintainers" of all of the added packages?
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> They publish their software and they don’t care whether I am pulling it or not. They publish it with the commitment to maintain it - sometimes for better and sometimes for worse.
>>>>>>>>>>>>>>> You care about me pulling your code and I don’t know whether you would commit to maintain this.
>>>>>>>>>>>>>>> These two are very different cases.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> Pick the IP Blocklists list (i.e., 3CORESEC, ABUSECH, DSHIELD, SPAMHAUS, etc.) or the Suricata lists (i.e.,Emergingthreats.net <http://emergingthreats.net/>,Abuse.ch <http://abuse.ch/>, etc.). So you’ve have "constructive conversation with the maintainers"?
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> Yes, occasionally I have phone calls with a few of these providers.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> Ha! Yes a surprise. In the beginning you seemed interested as IPFire needed a replacement for URL Filter. You asked good questions about the lists picked, asked for the value to the users, etc. And I answered the best I could.
>>>>>>>>>>>>>>>> You even asked: “Why is this realised as an add-on and not part of the core system?” from your Jul 28, 2024 email.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> Ah, so, why is the patch creating an add-on? Not that I am saying that what I say is law, but it has not been challenged either. If my input is being ignored, why should I put this to the top of my list of priorities? I am not disappointed about this, just trying to be very good with my time.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> And on January 16, 2025 I wrote a message looking for help. And you were kind to respond quickly. So in three weeks time, since the kind response, something has changed. You went from supportive to "this".
>>>>>>>>>>>>>>>> So yes, I am surprised.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> Well, maybe I should not have replied to that email. It was clear that you were on some path that was not right, but you were not interested before in finding the right path from the beginning.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> Be more specific, what has to change? What exactly did I dismiss?
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> Dismissal is just my assumption. I don’t know what you actually did with my feedback. I can only see the end product that does not seem contain much of it. Repeatedly I have been pointing out that we should think before we build. I am sure a lot of hours have now gone into some code that simply does not satisfy me. And I am not not talking about the code itself, what it does is what I don’t think is right for us.
>>>>>>>>>>>>>>> The process is very clear for me that we should first of all think whether we want a certain feature now. Then there should be a clear roadmap for everyone to follow; tasks can be split-up as we go and hopefully then have something that is maintainable, interesting for our users and even would do us proud. This is how this should work.
>>>>>>>>>>>>>>> So, what has to change? I don’t think with shouting at each other, throwing patches around and making me generally unhappy is a good start.
>>>>>>>>>>>>>>> -Michael
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> Jon
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> On Feb 6, 2025, at 2:13 PM, Michael Tremer <michael.tremer@ipfire.org> wrote:
>>>>>>>>>>>>>>>>> Hello Jon,
>>>>>>>>>>>>>>>>> Well, here we are again with another patch regarding this feature.
>>>>>>>>>>>>>>>>> I cannot quite see from your email what the question is, but if this is a request to have this merged into IPFire, I am once again sorry to disappoint you.
>>>>>>>>>>>>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls. Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation. Therefore, what am I supposed to do with this email?
>>>>>>>>>>>>>>>>> I don’t want to merge code that I don’t agree with. So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>>>>>>>>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>>>>>>>>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>>>>>>>>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>>>>>>>>>>>>> All the best,
>>>>>>>>>>>>>>>>> -Michael
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> On 6 Feb 2025, at 16:35, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>>>>>>>>>>>> What is it?
>>>>>>>>>>>>>>>>>> Response Policy Zone (RPZ) is a mechanism to define local policies in a
>>>>>>>>>>>>>>>>>> standardized way and load those policies from external sources.
>>>>>>>>>>>>>>>>>> Bottom line: RPZ allows admins to easily block access to websites via DNS lookup.
>>>>>>>>>>>>>>>>>> RPZ can block websites via categories. Examples include: fake websites, annoying
>>>>>>>>>>>>>>>>>> pop-up ads, newly registered domains, DoH bypass sites, bad "host" services,
>>>>>>>>>>>>>>>>>> maliscious top level domains (e.g., *.zip, *.mov), piracy, gambling, pornography,
>>>>>>>>>>>>>>>>>> and more. RPZ lists come from various RPZ providers and their available
>>>>>>>>>>>>>>>>>> catagories.
>>>>>>>>>>>>>>>>>> This RPZ add-on enables the RPZ functionality by adding a couple lines in a
>>>>>>>>>>>>>>>>>> configuration file. This add-on simply adds configuration files and adds
>>>>>>>>>>>>>>>>>> scripts (config, metrics and sleep) to make RPZ easier for the admin to use.
>>>>>>>>>>>>>>>>>> The RPZ scripts include additional languages: German, Spanish, French, Turkish,
>>>>>>>>>>>>>>>>>> and Italian.
>>>>>>>>>>>>>>>>>> RPZ itself was release in 2010 and has been part of the IPFire build since ~2015.
>>>>>>>>>>>>>>>>>> Why is it needed? What is its value?
>>>>>>>>>>>>>>>>>> - The RPZ concept places this filtering into IPFire, our internet access
>>>>>>>>>>>>>>>>>> gateway, which is (should be) solely used as DNS source of the internal network.
>>>>>>>>>>>>>>>>>> - As most sites use HTTPS it makes it difficult to filter traffic with URL
>>>>>>>>>>>>>>>>>> Filter without also properly configuring conventional (non-transparent)
>>>>>>>>>>>>>>>>>> mode on the proxy. RPZ is a nice replacement for the URL Filter.
>>>>>>>>>>>>>>>>>> - No need to install and maintain an additional device like PiHole or AdBlock
>>>>>>>>>>>>>>>>>> browser extensions on multiple user devices.
>>>>>>>>>>>>>>>>>> - This is an additional layer of protection for users. Less worry someone will
>>>>>>>>>>>>>>>>>> click on something that gets them into trouble. And, saying this with emphasis,
>>>>>>>>>>>>>>>>>> the ability to do it in one place!
>>>>>>>>>>>>>>>>>> - Blocked sites save on unneeded traffic and can lessen the threat of malware
>>>>>>>>>>>>>>>>>> in advertisements
>>>>>>>>>>>>>>>>>> - Logging allows the admin to see the site blocked and take actions
>>>>>>>>>>>>>>>>>> - RPZ will be used at the home, home-office (work from home), schools,
>>>>>>>>>>>>>>>>>> ministerial, and at the office. Device counts are small (2-6) to medium (~80)
>>>>>>>>>>>>>>>>>> to mediam-large (200+).
>>>>>>>>>>>>>>>>>> - RPZ can block ads, popups, phishing, scammers, spyware, malware, annoying
>>>>>>>>>>>>>>>>>> popups, NSFW links, DOH servers, and the usual internet trash.
>>>>>>>>>>>>>>>>>> ------------------------------
>>>>>>>>>>>>>>>>>> Change Log for RPZ add-on
>>>>>>>>>>>>>>>>>> rpz-1.0.0-18 on 2025-02-05
>>>>>>>>>>>>>>>>>> - Build for approval & release as IPFire add-on
>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>> rpz-beta-0.1.18-18.ipfire on 2025-02-01
>>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>>> - new feature: added a mod key to force a unbound restart
>>>>>>>>>>>>>>>>>> rpz-config and rpz-make:
>>>>>>>>>>>>>>>>>> - new feature: added action for unbound restart `rpz-config unbound-restart`
>>>>>>>>>>>>>>>>>> rpz-metrics:
>>>>>>>>>>>>>>>>>> - simple reformatting
>>>>>>>>>>>>>>>>>> - rename far right column from "last update" to "last download"
>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>> rpz-beta-0.1.17-17.ipfire on 2024-12-09
>>>>>>>>>>>>>>>>>> rpz-make
>>>>>>>>>>>>>>>>>> - bug fix: corrected validation regex for wildcards like: `*.domain.com`
>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>> rpz-beta-0.1.16-16.ipfire on 2024-11-18
>>>>>>>>>>>>>>>>>> rpz-make
>>>>>>>>>>>>>>>>>> - new feature: updated validation regex
>>>>>>>>>>>>>>>>>> - bug fix: moved validation to beginning of process. Now we validate before
>>>>>>>>>>>>>>>>>> creating config files.
>>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>>> - new feature: use CSS color variables of the main ipfire theme
>>>>>>>>>>>>>>>>>> - bug fix: empty zonefile remarks were stored as “undef” and caused a warning
>>>>>>>>>>>>>>>>>> - bug fix: HTML textarea removes the first empty line in a custom list
>>>>>>>>>>>>>>>>>> - thank you Leo!
>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>> rpz-beta-0.1.15-15.ipfire on 2024-11-04
>>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>>> - new feature: added new language file for Turkish (thank you Peppe)
>>>>>>>>>>>>>>>>>> rpz-make
>>>>>>>>>>>>>>>>>> - bug fix: corrected empty allow/block list issue. An empty allow/block list
>>>>>>>>>>>>>>>>>> will now remove contents of allow/block.rpz files and remove unneeded
>>>>>>>>>>>>>>>>>> allow/block.conf file. (thank you iptom)
>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>> rpz-beta-0.1.14-14.ipfire on 2024-10-29
>>>>>>>>>>>>>>>>>> rpz-config:
>>>>>>>>>>>>>>>>>> - bug fix: correct missing rpz extension. `rpz-config list` displayed URL
>>>>>>>>>>>>>>>>>> incorrectly (thank you Bernhard)
>>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>>> - bug fix: remove extra `"` in language files (thank you Bernhard)
>>>>>>>>>>>>>>>>>> - new feature: slightly dim "apply" button when not enabled
>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>> rpz-beta-0.1.13-13.ipfire on 2024-10-27
>>>>>>>>>>>>>>>>>> - skipped
>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>> rpz-beta-0.1.12-12.ipfire on 2024-10-21
>>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>>> - new feature: added new language file for French (thank you gw-ipfire)
>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>> rpz-beta-0.1.11-11.ipfire on 2024-10-18
>>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>>> - new feature: added new language file for Italian (thank you umberto)
>>>>>>>>>>>>>>>>>> - new feature: added new language file for Spanish (thank you Roberto)
>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>> rpz-beta-0.1.10-10.ipfire on 2024-10-15
>>>>>>>>>>>>>>>>>> rpz-make:
>>>>>>>>>>>>>>>>>> - bug fix: corrected validation error for a custom list entry (thank you siosios)
>>>>>>>>>>>>>>>>>> - e.g., `*.cloudflare-dns.com`
>>>>>>>>>>>>>>>>>> install.sh:
>>>>>>>>>>>>>>>>>> - bug fix: add chown to correct user created files
>>>>>>>>>>>>>>>>>> update.sh:
>>>>>>>>>>>>>>>>>> - bug fix: add chown to correct user created files (thank you siosios)
>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>> rpz-beta-0.1.9-9.ipfire on 2024-10-08
>>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>>> - new feature: added new language file for German (thank you Leo)
>>>>>>>>>>>>>>>>>> - bug fix: add missing "rpz exitcode 110"
>>>>>>>>>>>>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>> rpz-beta-0.1.8-8.ipfire on 2024-10-04
>>>>>>>>>>>>>>>>>> - skipped
>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>> rpz-beta-0.1.7-7.ipfire on 2024-10-03
>>>>>>>>>>>>>>>>>> All:
>>>>>>>>>>>>>>>>>> - new feature: includes beta version numbers for pakfire package,
>>>>>>>>>>>>>>>>>> instead of only `rpz-1.0.0-1.ipfire`, for each release.
>>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>>> - new feature: added new WebGUI at `rpz.cgi`
>>>>>>>>>>>>>>>>>> - a BIG thank you to Leo Hofmann for all of his work creating the webgui!!
>>>>>>>>>>>>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>>>>>>>>>>>> rpz-make:
>>>>>>>>>>>>>>>>>> - new feature: validate entries in allowlist and blocklist
>>>>>>>>>>>>>>>>>> - new feature: add "no-reload" option for WebGUI
>>>>>>>>>>>>>>>>>> rpz-metrics:
>>>>>>>>>>>>>>>>>> - new feature: info can be sorted by name, by hit count, by line count, by
>>>>>>>>>>>>>>>>>> "enabled" list or all lists
>>>>>>>>>>>>>>>>>> backups:
>>>>>>>>>>>>>>>>>> - bug fix: include all files in `/var/ipfire/dns/rpz` directory in backup
>>>>>>>>>>>>>>>>>> update.sh:
>>>>>>>>>>>>>>>>>> - bug fix: corrected ownership for `/var/ipfire/dns/rpz` directory during an
>>>>>>>>>>>>>>>>>> update
>>>>>>>>>>>>>>>>>> Build:
>>>>>>>>>>>>>>>>>> - bug fix: `block.rpz.conf` and `block.rpz` from build. Files to be created
>>>>>>>>>>>>>>>>>> by `rpz-make`
>>>>>>>>>>>>>>>>>> WebGUI and German language file
>>>>>>>>>>>>>>>>>> Contribution-by: Leo-Andres Hofmann <hofmann@leo-andres.de>
>>>>>>>>>>>>>>>>>> Spanish language file
>>>>>>>>>>>>>>>>>> Contribution-by: Roberto Peña
>>>>>>>>>>>>>>>>>> Italian language file
>>>>>>>>>>>>>>>>>> Contribution-by: Umberto Parma
>>>>>>>>>>>>>>>>>> French language file
>>>>>>>>>>>>>>>>>> Contribution-by: gw-ipfire
>>>>>>>>>>>>>>>>>> Turkish language file
>>>>>>>>>>>>>>>>>> Contribution-by: Peppe Tech
>>>>>>>>>>>>>>>>>> Contribution-by: Bernhard Bitsch <bbitsch@ipfire.org>
>>>>>>>>>>>>>>>>>> Contribution-by: Erik Kapfer <erik.kapfer@ipfire.org>
>>>>>>>>>>>>>>>>>> Signed-off-by: Jon Murphy <jon.murphy@ipfire.org
>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>> config/backup/includes/rpz | 4 +
>>>>>>>>>>>>>>>>>> config/cfgroot/manualpages | 1 +
>>>>>>>>>>>>>>>>>> config/menu/EX-rpz.menu | 6 +
>>>>>>>>>>>>>>>>>> config/rootfiles/common/configroot | 1 +
>>>>>>>>>>>>>>>>>> config/rootfiles/common/web-user-interface | 1 +
>>>>>>>>>>>>>>>>>> config/rootfiles/packages/rpz | 20 +
>>>>>>>>>>>>>>>>>> config/rpz/00-rpz.conf | 10 +
>>>>>>>>>>>>>>>>>> config/rpz/rpz-config | 130 +++
>>>>>>>>>>>>>>>>>> config/rpz/rpz-functions | 85 ++
>>>>>>>>>>>>>>>>>> config/rpz/rpz-make | 203 +++++
>>>>>>>>>>>>>>>>>> config/rpz/rpz-metrics | 170 ++++
>>>>>>>>>>>>>>>>>> config/rpz/rpz-sleep | 58 ++
>>>>>>>>>>>>>>>>>> config/rpz/rpz.de.pl | 30 +
>>>>>>>>>>>>>>>>>> config/rpz/rpz.en.pl | 30 +
>>>>>>>>>>>>>>>>>> config/rpz/rpz.es.pl | 30 +
>>>>>>>>>>>>>>>>>> config/rpz/rpz.fr.pl | 30 +
>>>>>>>>>>>>>>>>>> config/rpz/rpz.it.pl | 30 +
>>>>>>>>>>>>>>>>>> config/rpz/rpz.tr.pl | 30 +
>>>>>>>>>>>>>>>>>> html/cgi-bin/rpz.cgi | 923 +++++++++++++++++++++
>>>>>>>>>>>>>>>>>> lfs/rpz | 96 +++
>>>>>>>>>>>>>>>>>> make.sh | 3 +-
>>>>>>>>>>>>>>>>>> src/paks/rpz/install.sh | 36 +
>>>>>>>>>>>>>>>>>> src/paks/rpz/uninstall.sh | 38 +
>>>>>>>>>>>>>>>>>> src/paks/rpz/update.sh | 52 ++
>>>>>>>>>>>>>>>>>> 24 files changed, 2016 insertions(+), 1 deletion(-)
>>>>>>>>>>>>>>>>>> create mode 100644 config/backup/includes/rpz
>>>>>>>>>>>>>>>>>> create mode 100644 config/menu/EX-rpz.menu
>>>>>>>>>>>>>>>>>> create mode 100644 config/rootfiles/packages/rpz
>>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/00-rpz.conf
>>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz-config
>>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz-functions
>>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz-make
>>>>>>>>>>>>>>>>>> create mode 100755 config/rpz/rpz-metrics
>>>>>>>>>>>>>>>>>> create mode 100755 config/rpz/rpz-sleep
>>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.de.pl
>>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.en.pl
>>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.es.pl
>>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.fr.pl
>>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.it.pl
>>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.tr.pl
>>>>>>>>>>>>>>>>>> create mode 100644 html/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>>>>> create mode 100644 lfs/rpz
>>>>>>>>>>>>>>>>>> create mode 100644 src/paks/rpz/install.sh
>>>>>>>>>>>>>>>>>> create mode 100644 src/paks/rpz/uninstall.sh
>>>>>>>>>>>>>>>>>> create mode 100644 src/paks/rpz/update.sh
>>>>>>>>>>>>>>>>>> diff --git a/config/backup/includes/rpz b/config/backup/includes/rpz
>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>> index 000000000..36513e494
>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>> +++ b/config/backup/includes/rpz
>>>>>>>>>>>>>>>>>> @@ -0,0 +1,4 @@
>>>>>>>>>>>>>>>>>> +/var/ipfire/dns/rpz/*
>>>>>>>>>>>>>>>>>> +/etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>>>>> +/etc/unbound/zonefiles/block.rpz
>>>>>>>>>>>>>>>>>> +/etc/unbound/local.d/*rpz.conf
>>>>>>>>>>>>>>>>>> diff --git a/config/cfgroot/manualpages b/config/cfgroot/manualpages
>>>>>>>>>>>>>>>>>> index 1f7e01efc..d3a48c633 100644
>>>>>>>>>>>>>>>>>> --- a/config/cfgroot/manualpages
>>>>>>>>>>>>>>>>>> +++ b/config/cfgroot/manualpages
>>>>>>>>>>>>>>>>>> @@ -70,6 +70,7 @@ pakfire.cgi=configuration/ipfire/pakfire
>>>>>>>>>>>>>>>>>> wlanap.cgi=addons/wireless
>>>>>>>>>>>>>>>>>> tor.cgi=addons/tor
>>>>>>>>>>>>>>>>>> samba.cgi=addons/samba
>>>>>>>>>>>>>>>>>> +rpz.cgi=addons/rpz
>>>>>>>>>>>>>>>>>> # Logs menu
>>>>>>>>>>>>>>>>>> logs.cgi/summary.dat=configuration/logs/summary
>>>>>>>>>>>>>>>>>> diff --git a/config/menu/EX-rpz.menu b/config/menu/EX-rpz.menu
>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>> index 000000000..2f4daf410
>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>> +++ b/config/menu/EX-rpz.menu
>>>>>>>>>>>>>>>>>> @@ -0,0 +1,6 @@
>>>>>>>>>>>>>>>>>> +$subipfire->{'20.rpz'} = {
>>>>>>>>>>>>>>>>>> + 'caption' => $Lang::tr{'rpz'},
>>>>>>>>>>>>>>>>>> + 'uri' => '/cgi-bin/rpz.cgi',
>>>>>>>>>>>>>>>>>> + 'title' => "RPZ",
>>>>>>>>>>>>>>>>>> + 'enabled' => 1,
>>>>>>>>>>>>>>>>>> +};
>>>>>>>>>>>>>>>>>> diff --git a/config/rootfiles/common/configroot b/config/rootfiles/common/configroot
>>>>>>>>>>>>>>>>>> index 9839eee45..b30d6aae4 100644
>>>>>>>>>>>>>>>>>> --- a/config/rootfiles/common/configroot
>>>>>>>>>>>>>>>>>> +++ b/config/rootfiles/common/configroot
>>>>>>>>>>>>>>>>>> @@ -120,6 +120,7 @@ var/ipfire/menu.d/70-log.menu
>>>>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-apcupsd.menu
>>>>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-guardian.menu
>>>>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-mympd.menu
>>>>>>>>>>>>>>>>>> +#var/ipfire/menu.d/EX-rpz.menu
>>>>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-samba.menu
>>>>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-tor.menu
>>>>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-transmission.menu
>>>>>>>>>>>>>>>>>> diff --git a/config/rootfiles/common/web-user-interface b/config/rootfiles/common/web-user-interface
>>>>>>>>>>>>>>>>>> index 816241dae..e00464076 100644
>>>>>>>>>>>>>>>>>> --- a/config/rootfiles/common/web-user-interface
>>>>>>>>>>>>>>>>>> +++ b/config/rootfiles/common/web-user-interface
>>>>>>>>>>>>>>>>>> @@ -69,6 +69,7 @@ srv/web/ipfire/cgi-bin/proxy.cgi
>>>>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/qos.cgi
>>>>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/remote.cgi
>>>>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/routing.cgi
>>>>>>>>>>>>>>>>>> +#srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>>>>> #srv/web/ipfire/cgi-bin/samba.cgi
>>>>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/services.cgi
>>>>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/shutdown.cgi
>>>>>>>>>>>>>>>>>> diff --git a/config/rootfiles/packages/rpz b/config/rootfiles/packages/rpz
>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>> index 000000000..1c8663049
>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>> +++ b/config/rootfiles/packages/rpz
>>>>>>>>>>>>>>>>>> @@ -0,0 +1,20 @@
>>>>>>>>>>>>>>>>>> +etc/unbound/local.d/00-rpz.conf
>>>>>>>>>>>>>>>>>> +etc/unbound/zonefiles
>>>>>>>>>>>>>>>>>> +etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>>>>> +usr/sbin/rpz-config
>>>>>>>>>>>>>>>>>> +usr/sbin/rpz-functions
>>>>>>>>>>>>>>>>>> +usr/sbin/rpz-make
>>>>>>>>>>>>>>>>>> +usr/sbin/rpz-metrics
>>>>>>>>>>>>>>>>>> +usr/sbin/rpz-sleep
>>>>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.de.pl
>>>>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.en.pl
>>>>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.es.pl
>>>>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.fr.pl
>>>>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.it.pl
>>>>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.tr.pl
>>>>>>>>>>>>>>>>>> +var/ipfire/backup/addons/includes/rpz
>>>>>>>>>>>>>>>>>> +var/ipfire/dns/rpz
>>>>>>>>>>>>>>>>>> +var/ipfire/dns/rpz/allowlist
>>>>>>>>>>>>>>>>>> +var/ipfire/dns/rpz/blocklist
>>>>>>>>>>>>>>>>>> +var/ipfire/menu.d/EX-rpz.menu
>>>>>>>>>>>>>>>>>> +srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>>>>> diff --git a/config/rpz/00-rpz.conf b/config/rpz/00-rpz.conf
>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>> index 000000000..f005a4f2e
>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>> +++ b/config/rpz/00-rpz.conf
>>>>>>>>>>>>>>>>>> @@ -0,0 +1,10 @@
>>>>>>>>>>>>>>>>>> +server:
>>>>>>>>>>>>>>>>>> + module-config: "respip validator iterator"
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +rpz:
>>>>>>>>>>>>>>>>>> + name: allow.rpz
>>>>>>>>>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>>>>> + rpz-action-override: passthru
>>>>>>>>>>>>>>>>>> + rpz-log: yes
>>>>>>>>>>>>>>>>>> + rpz-log-name: allow
>>>>>>>>>>>>>>>>>> + rpz-signal-nxdomain-ra: yes
>>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-config b/config/rpz/rpz-config
>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>> index 000000000..c72d50f9b
>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-config
>>>>>>>>>>>>>>>>>> @@ -0,0 +1,130 @@
>>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +version="2025-01-11 - v44"
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +tagName="unbound"
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +rpzAction="${1}" # input RPZ action
>>>>>>>>>>>>>>>>>> +rpzName="${2}" # input RPZ name
>>>>>>>>>>>>>>>>>> +rpzURL="${3}" # input RPZ URL
>>>>>>>>>>>>>>>>>> +rpzOption1="${4}" # input RPZ option #1
>>>>>>>>>>>>>>>>>> +rpzOption2="${5}" # input RPZ option #2
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +rpzConfig="/etc/unbound/local.d/${rpzName}.rpz.conf" # output zone conf file
>>>>>>>>>>>>>>>>>> +rpzFile="/etc/unbound/zonefiles/${rpzName}.rpz" # output for RPZ file
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>>>>>>>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>>>>>>>>>> + case "$1" in
>>>>>>>>>>>>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>>>>>>>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>>>>>>>>>>>>> + esac
>>>>>>>>>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +case "${rpzAction}" in
>>>>>>>>>>>>>>>>>> + # add new rpz list
>>>>>>>>>>>>>>>>>> + add )
>>>>>>>>>>>>>>>>>> + check_name "${rpzName}" # is this a valid name?
>>>>>>>>>>>>>>>>>> + # does this config already exist? If yes, then exit
>>>>>>>>>>>>>>>>>> + if [[ -f "${rpzConfig}" ]] ; then
>>>>>>>>>>>>>>>>>> + msg_log "error: rpz: duplicate - ${rpzConfig} already exists. exit"
>>>>>>>>>>>>>>>>>> + exit 104
>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # is this a valid URL?
>>>>>>>>>>>>>>>>>> + regex='^https://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
>>>>>>>>>>>>>>>>>> + if ! [[ "${rpzURL}" =~ $regex ]] ; then
>>>>>>>>>>>>>>>>>> + msg_log "error: rpz: the URL is not valid: \"${rpzURL}\". exit."
>>>>>>>>>>>>>>>>>> + exit 105
>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # create the zone config file
>>>>>>>>>>>>>>>>>> + {
>>>>>>>>>>>>>>>>>> + echo "rpz:"
>>>>>>>>>>>>>>>>>> + echo " name: ${rpzName}.rpz"
>>>>>>>>>>>>>>>>>> + echo " zonefile: ${rpzFile}"
>>>>>>>>>>>>>>>>>> + echo " url: ${rpzURL}"
>>>>>>>>>>>>>>>>>> + echo " rpz-action-override: nxdomain"
>>>>>>>>>>>>>>>>>> + echo " rpz-log: ${rpzLog}"
>>>>>>>>>>>>>>>>>> + echo " rpz-log-name: ${rpzName}"
>>>>>>>>>>>>>>>>>> + echo " rpz-signal-nxdomain-ra: yes"
>>>>>>>>>>>>>>>>>> + } > "${rpzConfig}"
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # set-up zonefile
>>>>>>>>>>>>>>>>>> + # create an empty rpz file if it does not exist
>>>>>>>>>>>>>>>>>> + if [[ ! -f "${rpzFile}" ]] ; then
>>>>>>>>>>>>>>>>>> + touch "${rpzFile}"
>>>>>>>>>>>>>>>>>> + # unbound requires these settings for rpz files
>>>>>>>>>>>>>>>>>> + set_permissions "${rpzFile}" "${rpzConfig}"
>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # trash config file & rpz file
>>>>>>>>>>>>>>>>>> + remove )
>>>>>>>>>>>>>>>>>> + if ! [[ -f "${rpzConfig}" ]] ; then
>>>>>>>>>>>>>>>>>> + msg_log "error: rpz: cannot remove ${rpzConfig}, does not exist. exit"
>>>>>>>>>>>>>>>>>> + exit 106
>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + msg_log "info: rpz: remove config file & rpz file \"${rpzName}\""
>>>>>>>>>>>>>>>>>> + rm "${rpzConfig}"
>>>>>>>>>>>>>>>>>> + rm "${rpzFile}"
>>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + reload )
>>>>>>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + list )
>>>>>>>>>>>>>>>>>> + awk -F':' '/^\s*name:/{ gsub(/[[:blank:]]|\.rpz/, "",$2) ; NAME=$2 } \
>>>>>>>>>>>>>>>>>> + /^\s*url:/{ gsub(/[[:blank:]]/, "") ; print NAME"="$2":"$3} ' \
>>>>>>>>>>>>>>>>>> + /etc/unbound/local.d/*rpz.conf
>>>>>>>>>>>>>>>>>> + exit
>>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + unbound-restart )
>>>>>>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>>>>>>> + unbound_restart
>>>>>>>>>>>>>>>>>> + exit
>>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + * )
>>>>>>>>>>>>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>>>>>>>>>>>>> + printf "Usage: $(basename "$0") <ACTION> <NAME> <URL> <OPTION> <OPTION>\n"
>>>>>>>>>>>>>>>>>> + printf "Version: ${version}\n"
>>>>>>>>>>>>>>>>>> + exit 108
>>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +esac
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +exit
>>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-functions b/config/rpz/rpz-functions
>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>> index 000000000..ace1d2690
>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-functions
>>>>>>>>>>>>>>>>>> @@ -0,0 +1,85 @@
>>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +version="2024-12-10 - v02"
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +msg_log () {
>>>>>>>>>>>>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>>>>>>>>>>>>> + if tty --silent ; then
>>>>>>>>>>>>>>>>>> + echo "${tagName}:" "$*"
>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# check for a valid name
>>>>>>>>>>>>>>>>>> +check_name () {
>>>>>>>>>>>>>>>>>> + local theName="${1}"
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + regex='^[a-zA-Z0-9_]+$' # no dash or plus, alpha numeric only
>>>>>>>>>>>>>>>>>> + regex1='^(allow|block)$' # allow and block are reserved NAMEs
>>>>>>>>>>>>>>>>>> + if [[ ! "${theName}" =~ $regex ]] || [[ "${theName}" =~ $regex1 ]] ; then
>>>>>>>>>>>>>>>>>> + msg_log "error: rpz: the NAME is not valid: \"${theName}\". exit."
>>>>>>>>>>>>>>>>>> + exit 101
>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +set_permissions () {
>>>>>>>>>>>>>>>>>> + chown nobody:nobody "$@"
>>>>>>>>>>>>>>>>>> + chmod 644 "$@"
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +check_unbound_conf () {
>>>>>>>>>>>>>>>>>> + local thecheckConf="${1:-yes}" # check config default is yes
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # check the above config files
>>>>>>>>>>>>>>>>>> + if [[ "${thecheckConf}" == yes ]] ; then
>>>>>>>>>>>>>>>>>> + msg_log "info: rpz: check for errors with \"unbound-checkconf\""
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + if ! unbound-checkconf ; then
>>>>>>>>>>>>>>>>>> + msg_log "error: rpz: unbound-checkconf found invalid configuration."
>>>>>>>>>>>>>>>>>> + msg_log \
>>>>>>>>>>>>>>>>>> + "error: rpz: In Terminal run the command \"unbound-checkconf\" for more information. exit."
>>>>>>>>>>>>>>>>>> + exit 102
>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +unbound_control_reload () {
>>>>>>>>>>>>>>>>>> + local theReload="${1:-yes}" # reload default is yes
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + if [[ "${theReload}" == yes ]] ; then
>>>>>>>>>>>>>>>>>> + # reload due to the changes
>>>>>>>>>>>>>>>>>> + msg_log "info: rpz: run \"unbound-control reload\""
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + if ! unbound-control reload ; then
>>>>>>>>>>>>>>>>>> + msg_log "error: rpz: unbound-control reload. exit."
>>>>>>>>>>>>>>>>>> + exit 109
>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +unbound_restart () {
>>>>>>>>>>>>>>>>>> + # restart due to the changes
>>>>>>>>>>>>>>>>>> + msg_log "info: rpz: run \"unbound restart\""
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + /usr/local/bin/unboundctrl restart
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-make b/config/rpz/rpz-make
>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>> index 000000000..927d55170
>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-make
>>>>>>>>>>>>>>>>>> @@ -0,0 +1,203 @@
>>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +version="2025-01-11 - v14"
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# create the config file for allow
>>>>>>>>>>>>>>>>>> +make_allow_config () {
>>>>>>>>>>>>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>>>>>>>>>>>>> + local theConfig="/etc/unbound/local.d/00-rpz.conf" # output zone conf file
>>>>>>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/allowlist" # input custom list of domains
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + msg_log "info: rpz: make config file \"00-rpz.conf\""
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + echo "server:
>>>>>>>>>>>>>>>>>> + module-config: \"respip validator iterator\"" > "${theConfig}"
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # does allow list exist?
>>>>>>>>>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + echo "rpz:
>>>>>>>>>>>>>>>>>> + name: allow.rpz
>>>>>>>>>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>>>>> + rpz-action-override: passthru
>>>>>>>>>>>>>>>>>> + rpz-log: ${theLog}
>>>>>>>>>>>>>>>>>> + rpz-log-name: allow
>>>>>>>>>>>>>>>>>> + rpz-signal-nxdomain-ra: yes" >> "${theConfig}"
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>>>>>>>>>> + set_permissions "${theConfig}"
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# create the config file for block
>>>>>>>>>>>>>>>>>> +make_block_config () {
>>>>>>>>>>>>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>>>>>>>>>>>>> + local theConfig="/etc/unbound/local.d/block.rpz.conf" # output zone conf file
>>>>>>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/blocklist" # input custom list of domains
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + msg_log "info: rpz: make config file \"block.rpz.conf\""
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # does block list exist?
>>>>>>>>>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + echo "rpz:
>>>>>>>>>>>>>>>>>> + name: block.rpz
>>>>>>>>>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/block.rpz
>>>>>>>>>>>>>>>>>> + rpz-action-override: nxdomain
>>>>>>>>>>>>>>>>>> + rpz-log: ${theLog}
>>>>>>>>>>>>>>>>>> + rpz-log-name: block
>>>>>>>>>>>>>>>>>> + rpz-signal-nxdomain-ra: yes" > "${theConfig}"
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>>>>>>>>>> + set_permissions "${theConfig}"
>>>>>>>>>>>>>>>>>> + else
>>>>>>>>>>>>>>>>>> + # no - trash the config file
>>>>>>>>>>>>>>>>>> + rm --verbose /etc/unbound/local.d/block.rpz.conf
>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# create an RPZ file for allow or block
>>>>>>>>>>>>>>>>>> +make_rpz_file () {
>>>>>>>>>>>>>>>>>> + local theName="${1}" # allow or block
>>>>>>>>>>>>>>>>>> + local theAction='.' # the default is nxdomain or block
>>>>>>>>>>>>>>>>>> + local actionList
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>>>>>>>>>>>>> + local theZonefile="/etc/unbound/zonefiles/${theName}.rpz" # output file for RPZ
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # does a list exist?
>>>>>>>>>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # for allow set to passthru
>>>>>>>>>>>>>>>>>> + [[ "${theName}" == allow ]] && theAction='rpz-passthru.'
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # drop any extra "blanks" and add "CNAME <RPZ action>." to each line
>>>>>>>>>>>>>>>>>> + actionList=$( awk '{$1=$1};1' "${theList}" |
>>>>>>>>>>>>>>>>>> + sed "/^[^;].*[[:alnum:]]/ s|$| CNAME ${theAction}|" )
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + msg_log "info: rpz: create zonefile for \"${theName}list\""
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +echo "; Name: ${theName} list
>>>>>>>>>>>>>>>>>> +; Last modified: $(date "+%Y-%m-%d at %H.%M.%S %Z")
>>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>>> +; domains with actions list
>>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>>> +${actionList}" > "${theZonefile}"
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>>>>>>>>>> + set_permissions "${theZonefile}"
>>>>>>>>>>>>>>>>>> + # set-up allow/block list files
>>>>>>>>>>>>>>>>>> + set_permissions "${theList}"
>>>>>>>>>>>>>>>>>> + else
>>>>>>>>>>>>>>>>>> + msg_log "info: rpz: the ${theList} is empty."
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + rm --verbose "${theZonefile}" # trash the RPZ file
>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# check if allow/block list is valid
>>>>>>>>>>>>>>>>>> +validate_list () {
>>>>>>>>>>>>>>>>>> + local theName="${1}" # allow or block
>>>>>>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # remove good:
>>>>>>>>>>>>>>>>>> + # - properly formated domain names with or without leading wildcard
>>>>>>>>>>>>>>>>>> + # - properly formated top level domain (TLD) names with wildcard
>>>>>>>>>>>>>>>>>> + # - blank lines and comment lines
>>>>>>>>>>>>>>>>>> + # remaining lines are considered "bad"
>>>>>>>>>>>>>>>>>> + bad_lines=$( sed --regexp-extended \
>>>>>>>>>>>>>>>>>> + '/^(\*\.)?([a-zA-Z0-9](([a-zA-Z0-9\-]){0,61}[a-zA-Z0-9])?\.)+([a-zA-Z]{2,}|xn--[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])$/d ;
>>>>>>>>>>>>>>>>>> + /^(\*\.)([a-z]{2,61}|xn--[a-z0-9]{1,60})$/d ;
>>>>>>>>>>>>>>>>>> + /^$/d ; /^;/d' "${theList}" )
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + if [[ ! -z "${bad_lines}" ]] ; then
>>>>>>>>>>>>>>>>>> + msg_log "error: rpz: invalid line(s) in ${theList}."
>>>>>>>>>>>>>>>>>> + printf "%s\n" "bad line(s): ${bad_lines}"
>>>>>>>>>>>>>>>>>> + exit 110
>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +tagName="unbound"
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +rpzName="${1}" # input RPZ name
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>>>>>>>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>>>>>>>>>> + case "$1" in
>>>>>>>>>>>>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>>>>>>>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>>>>>>>>>>>>> + esac
>>>>>>>>>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +case "${rpzName}" in
>>>>>>>>>>>>>>>>>> + # make a new allow or block rpz file
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + allow )
>>>>>>>>>>>>>>>>>> + validate_list 'allow' # is the allowlist valid?
>>>>>>>>>>>>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>>>>>>>>>>>>> + make_rpz_file 'allow'
>>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + allowblock )
>>>>>>>>>>>>>>>>>> + validate_list 'allow' # is the list valid?
>>>>>>>>>>>>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>>>>>>>>>>>>> + make_rpz_file 'allow'
>>>>>>>>>>>>>>>>>> + ;&
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + block )
>>>>>>>>>>>>>>>>>> + validate_list 'block' # is the blocklist valid?
>>>>>>>>>>>>>>>>>> + make_block_config "${rpzLog}"
>>>>>>>>>>>>>>>>>> + make_rpz_file 'block'
>>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + reload )
>>>>>>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + unbound-restart )
>>>>>>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>>>>>>> + unbound_restart
>>>>>>>>>>>>>>>>>> + exit
>>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + * )
>>>>>>>>>>>>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>>>>>>>>>>>>> + printf "Usage: $(basename "$0") <NAME> <OPTION> <OPTION>\n"
>>>>>>>>>>>>>>>>>> + printf "Version: ${version}\n"
>>>>>>>>>>>>>>>>>> + exit 108
>>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>>> +esac
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +exit
>>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-metrics b/config/rpz/rpz-metrics
>>>>>>>>>>>>>>>>>> new file mode 100755
>>>>>>>>>>>>>>>>>> index 000000000..4d43e1629
>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-metrics
>>>>>>>>>>>>>>>>>> @@ -0,0 +1,170 @@
>>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +version="2025-01-20 - v25"
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +weeks="2" # default to two message logs
>>>>>>>>>>>>>>>>>> +sortBy="name" # default "by name"
>>>>>>>>>>>>>>>>>> +rpzActive="enabled" # default "enabled only"
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>>>>>>>>>> + case "$1" in
>>>>>>>>>>>>>>>>>> + --by-names | --by-name | name ) sortBy="name" ;;
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + --by-hits | --by-hit | hits | hit ) sortBy="hit" ;;
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + --by-lines | --by-line | lines | line ) sortBy="line" ;;
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + --by-effect ) sortBy="effect" ;;
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + --enabled-only ) rpzActive="enabled" ;;
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + --active-all | --all | all ) rpzActive="all" ;;
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + [0-9] | [0-9][0-9] ) weeks=$1 ;;
>>>>>>>>>>>>>>>>>> + esac
>>>>>>>>>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# get the list of message logs for N weeks
>>>>>>>>>>>>>>>>>> +messageLogs=$( find /var/log/messages* -type f | sort --version-sort |
>>>>>>>>>>>>>>>>>> + head -"${weeks}" )
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# get the list of RPZ names & counts from the message log(s)
>>>>>>>>>>>>>>>>>> +rpzNameCount=$( for logf in ${messageLogs} ; do
>>>>>>>>>>>>>>>>>> + zgrep --text --fixed-strings 'info: rpz: applied' "${logf}" |
>>>>>>>>>>>>>>>>>> + awk '$10 ~ /\[\w*]/ { print $10 }' ;
>>>>>>>>>>>>>>>>>> + done | sort | uniq --count )
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# flip results and remove brackets `[` and `]`
>>>>>>>>>>>>>>>>>> +rpzNameCount=$( echo "${rpzNameCount}" |
>>>>>>>>>>>>>>>>>> + awk '{ print $2, $1 }' |
>>>>>>>>>>>>>>>>>> + sed --regexp-extended 's|^\[(.*)\]|\1|' )
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# grab only names
>>>>>>>>>>>>>>>>>> +rpzNames=$( echo "${rpzNameCount}" | awk '{ print $1 }' )
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# get list of RPZ files
>>>>>>>>>>>>>>>>>> +rpzFileList=$( find /etc/unbound/zonefiles -type f -iname "*.rpz" )
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# get basename of those files
>>>>>>>>>>>>>>>>>> +rpzBaseNames=$( echo "${rpzFileList}" |
>>>>>>>>>>>>>>>>>> + sed 's|/etc/unbound/zonefiles/||g ; s|\.rpz||g ;' )
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# add to rpzNames
>>>>>>>>>>>>>>>>>> +rpzNames="${rpzNames}"$'\n'"${rpzBaseNames}"
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# drop duplicate names
>>>>>>>>>>>>>>>>>> +rpzNames=$( echo "${rpzNames}" | sort --unique )
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# get line count for each RPZ
>>>>>>>>>>>>>>>>>> +lineCount=$( echo "${rpzFileList}" | xargs wc -l )
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# get comment line count and blank line count for each RPZ
>>>>>>>>>>>>>>>>>> +commentCount=$( echo "${rpzFileList}" | xargs grep --count -e "^$" -e "^;" )
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# get modified date each RPZ
>>>>>>>>>>>>>>>>>> +modDateList=$( echo "${rpzFileList}" | xargs stat -c '%.10y %n' )
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +ucListAuthZones=$( unbound-control list_auth_zones )
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# get width of RPZ names
>>>>>>>>>>>>>>>>>> +pWidth=$( echo "${rpzNames}" | awk '{ print $1" " }' | wc -L )
>>>>>>>>>>>>>>>>>> +pFormat="%-${pWidth}s %-8s %-8s %8s %12s %12s\n"
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# print title line
>>>>>>>>>>>>>>>>>> +printf "${pFormat}" "name" "hits" "active" "lines" " hits/line" "last download"
>>>>>>>>>>>>>>>>>> +printf -- "--------------"
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +theResults=""
>>>>>>>>>>>>>>>>>> +totalLines=0
>>>>>>>>>>>>>>>>>> +totalHits=0
>>>>>>>>>>>>>>>>>> +while read -r theName
>>>>>>>>>>>>>>>>>> +do
>>>>>>>>>>>>>>>>>> + printf -- "--" # pretend progress bar
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # is this RPZ list active?
>>>>>>>>>>>>>>>>>> + theActive="disabled"
>>>>>>>>>>>>>>>>>> + if grep --quiet "^${theName}\.rpz" <<< "${ucListAuthZones}"
>>>>>>>>>>>>>>>>>> + then
>>>>>>>>>>>>>>>>>> + theActive="enabled"
>>>>>>>>>>>>>>>>>> + else
>>>>>>>>>>>>>>>>>> + [[ "${rpzActive}" == enabled ]] && continue
>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # get hit count
>>>>>>>>>>>>>>>>>> + theHits="0"
>>>>>>>>>>>>>>>>>> + if output=$( grep "^${theName}\s" <<< "${rpzNameCount}" ) ; then
>>>>>>>>>>>>>>>>>> + theHits=$( echo "${output}" | awk '{ print $2 }' )
>>>>>>>>>>>>>>>>>> + totalHits=$(( totalHits + theHits ))
>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # get line count
>>>>>>>>>>>>>>>>>> + theLines="n/a"
>>>>>>>>>>>>>>>>>> + hitsPerLine="0"
>>>>>>>>>>>>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${lineCount}" ) ; then
>>>>>>>>>>>>>>>>>> + theLines=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>>>>>>>>>>>>> + totalLines=$(( totalLines + theLines ))
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + if [[ "${theLines}" -gt 2 ]] ; then
>>>>>>>>>>>>>>>>>> + hitsPerLine=$(( 100 * theHits / theLines ))
>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # get modification date
>>>>>>>>>>>>>>>>>> + theModDate="n/a"
>>>>>>>>>>>>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${modDateList}" ) ; then
>>>>>>>>>>>>>>>>>> + theModDate=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # add to results list
>>>>>>>>>>>>>>>>>> + theResults+="${theName} ${theHits} ${theActive} ${theLines} ${hitsPerLine} ${theModDate}"$'\n'
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +done <<< "${rpzNames}"
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +case "${sortBy}" in
>>>>>>>>>>>>>>>>>> + # sort by "active" then by "name"
>>>>>>>>>>>>>>>>>> + name) sortArg=(-k3,3r -k1,1) ;;
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # sort by "active" then by "hits" then by "name"
>>>>>>>>>>>>>>>>>> + hit) sortArg=(-k3,3r -k2,2nr -k1,1) ;;
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # sort by "active" then by "lines" then by "name"
>>>>>>>>>>>>>>>>>> + line) sortArg=(-k3,3r -k4,4nr -k1,1) ;;
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # sort by "active" then by "effect" then by "name"
>>>>>>>>>>>>>>>>>> + effect) sortArg=(-k3,3r -k5,5nr -k1,1) ;;
>>>>>>>>>>>>>>>>>> +esac
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +printf -- "--------------\n"
>>>>>>>>>>>>>>>>>> +# remove blank lines, sort, print as columns
>>>>>>>>>>>>>>>>>> +echo "${theResults}" |
>>>>>>>>>>>>>>>>>> + awk '!/^[[:space:]]*$/' |
>>>>>>>>>>>>>>>>>> + sort "${sortArg[@]}" |
>>>>>>>>>>>>>>>>>> + awk --assign=width="${pWidth}" \
>>>>>>>>>>>>>>>>>> + '{ printf "%-*s %-8s %-8s %8s %10s %% %12s\n", width, $1, $2, $3, $4, $5, $6 }'
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +printf "${pFormat}" "" "=======" "" "========" "" ""
>>>>>>>>>>>>>>>>>> +printf "${pFormat}" "Totals -->" "${totalHits}" "" "${totalLines}" "" ""
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +exit
>>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-sleep b/config/rpz/rpz-sleep
>>>>>>>>>>>>>>>>>> new file mode 100755
>>>>>>>>>>>>>>>>>> index 000000000..dd3603599
>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-sleep
>>>>>>>>>>>>>>>>>> @@ -0,0 +1,58 @@
>>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +version="2024-08-16" # v05
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# send message to message log
>>>>>>>>>>>>>>>>>> +msg_log () {
>>>>>>>>>>>>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>>>>>>>>>>>>> + if tty --silent ; then
>>>>>>>>>>>>>>>>>> + echo "${tagName}:" "$*"
>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +tagName="unbound"
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +sleepTime="${1:-5m}" # default to sleep for 5m (5 minutes)
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +zoneList=$( unbound-control list_auth_zones | awk '{print $1}' )
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +for zone in ${zoneList} ; do
>>>>>>>>>>>>>>>>>> + printf "disable ${zone}\t"
>>>>>>>>>>>>>>>>>> + unbound-control rpz_disable "${zone}"
>>>>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +msg_log "info: rpz: disabled all zones for ${sleepTime}"
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +sleep "${sleepTime}"
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +for zone in ${zoneList} ; do
>>>>>>>>>>>>>>>>>> + printf "enable ${zone}\t"
>>>>>>>>>>>>>>>>>> + unbound-control rpz_enable "${zone}"
>>>>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +msg_log "info: rpz: enabled all zones"
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +exit
>>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.de.pl b/config/rpz/rpz.de.pl
>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>> index 000000000..3770c6bb0
>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.de.pl
>>>>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>>>>> +'rpz apply' => 'Übernehmen',
>>>>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Benutzerdefinierte Allowlist aktivieren:',
>>>>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Zugelassene Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>>> +'rpz cl allow' => 'Benutzerdefinierte Allowlist',
>>>>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Benutzerdefinierte Blocklist aktivieren:',
>>>>>>>>>>>>>>>>>> +'rpz cl block info' => 'Gesperrte Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>>> +'rpz cl block' => 'Benutzerdefinierte Blocklist',
>>>>>>>>>>>>>>>>>> +'rpz cl' => 'Benutzerdefinierte Listen',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'Der Name enthält unzulässige Zeichen',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf hat eine fehlerhafte Konfiguration ermittelt. Führen Sie das Kommando unbound-checkconf auf der Konsole aus, um weitere Informationen zu erhalten.',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'Die benutzerdefinierte Allow-/Blocklist ist leer',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'Ein Eintrag mit identischem Namen existiert bereits',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'Die URL ist ungültig',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'Eintrag kann nicht entfernt werden, der Name existiert nicht',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'Der Name ist ungültig - nur "allow" oder "block" möglich',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'Fehlende oder inkorrekte Parameter',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control reload ist fehlgeschlagen',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'Die benutzerdefinierte Allow-/Blocklist enthält unzulässige Einträge',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'Die Anmerkung enthält unzulässige Zeichen',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'Ungültiger Eintrag in der benutzerdefinierten Allowlist, Zeile ',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'Ungültiger Eintrag in der benutzerdefinierten Blocklist, Zeile ',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'Ausgewählter Eintrag existiert nicht: ',
>>>>>>>>>>>>>>>>>> +'rpz zf editor' => 'Zonendatei-Eintrag bearbeiten',
>>>>>>>>>>>>>>>>>> +'rpz zf imported' => '(importiert aus rpz-config)',
>>>>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Erlaubte Zeichen sind a-z, A-Z, 0-9 und Unterstriche',
>>>>>>>>>>>>>>>>>> +'rpz zf' => 'Zonendateien',
>>>>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.en.pl b/config/rpz/rpz.en.pl
>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>> index 000000000..0720a8940
>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.en.pl
>>>>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>>>>> +'rpz apply' => 'Apply',
>>>>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Enable custom allowlist:',
>>>>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Allowed domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>>> +'rpz cl allow' => 'Custom allowlist',
>>>>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Enable custom blocklist:',
>>>>>>>>>>>>>>>>>> +'rpz cl block info' => 'Blocked domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>>> +'rpz cl block' => 'Custom blocklist',
>>>>>>>>>>>>>>>>>> +'rpz cl' => 'Custom lists',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'the NAME is not valid',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf found invalid configuration. In the Terminal run the command unbound-checkconf for more information',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'the allow/block list is empty',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'duplicate - NAME already exists',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'the URL is not valid',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'cannot remove the NAME does not exist',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'the NAME is not valid - "allow" or "block" only',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'missing or incorrect parameter',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control reload failed',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'custom Allowlist/Blocklist contains invalid entries',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'the REMARK is not valid',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'invalid entry in allowlist, line ',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'invalid entry in blocklist, line ',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'Selected entry does not exist: ',
>>>>>>>>>>>>>>>>>> +'rpz zf editor' => 'Edit zonefiles entry',
>>>>>>>>>>>>>>>>>> +'rpz zf imported' => '(imported from rpz-config)',
>>>>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Valid characters are a-z, A-Z, 0-9 and underscore.',
>>>>>>>>>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.es.pl b/config/rpz/rpz.es.pl
>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>> index 000000000..98628e4aa
>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.es.pl
>>>>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>>>>> +'rpz' => 'Zonas de política de respuesta (RPZ)',
>>>>>>>>>>>>>>>>>> +'rpz apply' => 'Aplicar',
>>>>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Habilitar la lista blanca personalizada:',
>>>>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Dominio permitido (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>>> +'rpz cl allow' => 'Lista blanca personalizada',
>>>>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Habilitar la lista negra personalizada:',
>>>>>>>>>>>>>>>>>> +'rpz cl block info' => 'Dominio bloqueado (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>>> +'rpz cl block' => 'Lista negra personalizada',
>>>>>>>>>>>>>>>>>> +'rpz cl' => 'Lista personalizada',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'El NOMBRE no es válido',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha encontrado una configuración no válida. Desde Terminal, ejecute el comando unbound-checkconf para mayor información',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'La lista de permitidos/bloqueados está vacía',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'duplicado - NOMBRE ya existe',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'la URL no es válida',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'no es posible eliminar el NOMBRE que no existe',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'el NOMBRE no es válido - sólo "permitir" o "bloquear"',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'parámetro faltante o incorrecto',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'Error al recargar unbound-control',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'la Lista blanca/Lista negra personalizada contiene entradas no válidas',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'el COMENTARIO no es válido',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'entrada no válida en la lista blanca, línea ',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'entrada no válida en la lista negra, línea ',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'La entrada seleccionada no existe: ',
>>>>>>>>>>>>>>>>>> +'rpz zf editor' => 'Editar la entrada de archivos de zona',
>>>>>>>>>>>>>>>>>> +'rpz zf imported' => '(importado de rpz-config)',
>>>>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Los caracteres válidos son a-z, A-Z, 0-9 y guión bajo',
>>>>>>>>>>>>>>>>>> +'rpz zf' => 'Archivos de zona',
>>>>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.fr.pl b/config/rpz/rpz.fr.pl
>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>> index 000000000..f35f3c2d0
>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.fr.pl
>>>>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>>>>> +'rpz apply' => 'Appliquer',
>>>>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Activer la liste d\'autorisations personnalisée:',
>>>>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Domaines autorisés (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>>> +'rpz cl allow' => 'Liste d\'autorisations personnalisée',
>>>>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Activer la liste de blocage personnalisée:',
>>>>>>>>>>>>>>>>>> +'rpz cl block info' => 'Domaines bloqués (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>>> +'rpz cl block' => 'liste de blocage personnalisé',
>>>>>>>>>>>>>>>>>> +'rpz cl' => 'Listes personnalisées',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'le NOM n\'est pas valide',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf configuration non valide trouvée. Dans le terminal, exécutez la commande unbound-checkconf pour plus d\'informations',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'la liste autoriser/bloquer est vide',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'le NOM existe déjà',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'L\'URL n\'est pas valide',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'impossible de supprimer le NOM n\'existe pas',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'le NOM n\'est pas valide - « autoriser » ou « bloquer » seulement',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'paramètre manquant ou incorrect',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control rechargement échoué',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'la liste autoriser/bloquer contient des entrées non valides',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'la REMARQUE n\'est pas valable',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'entrée non valide dans la liste d\'autorisation, ligne ',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'entrée non valide dans la liste de blocs, ligne ',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'L\'entrée sélectionnée n\'existe pas: ',
>>>>>>>>>>>>>>>>>> +'rpz zf editor' => 'Modifier l\'entrée Fichiers Zone',
>>>>>>>>>>>>>>>>>> +'rpz zf imported' => '(importé de rpz-config)',
>>>>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Les caractères valides sont a-z, A-Z, 0-9 et soulignement.',
>>>>>>>>>>>>>>>>>> +'rpz zf' => 'Fichiers Zone',
>>>>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.it.pl b/config/rpz/rpz.it.pl
>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>> index 000000000..ee81605c9
>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.it.pl
>>>>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>>>>> +'rpz apply' => 'Applica',
>>>>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Abilita la Whitelist personalizzata:',
>>>>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Domini consentiti (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>>> +'rpz cl allow' => 'Whitelist personalizzata',
>>>>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Abilita la Blacklist personalizzata:',
>>>>>>>>>>>>>>>>>> +'rpz cl block info' => 'Domini bloccati (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>>> +'rpz cl block' => 'Blacklist personalizzata',
>>>>>>>>>>>>>>>>>> +'rpz cl' => 'Liste personalizzate',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'il NOME non è valido',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha trovato una configurazione non valida. Dal Terminale esegui il comando unbound-checkconf per maggiori informazioni',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'l\'elenco consentiti/bloccati è vuoto',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'duplicato - NAME esiste di già',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'l\'URL non è valido',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'non è possibile rimuovere il NOME non esiste',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'il NOME non è valido - solo "consenti" o "blocca"',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'parametro mancante o non corretto',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'ricaricamento del controllo non associato non riuscito',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'la Whitelist/Blacklist personalizzata contiene voci non valide',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'l"OSSERVAZIONE non è valida',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'voce non valida nella Whitelist, riga ',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'voce non valida nella Blacklist, riga ',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'La voce selezionata non esiste: ',
>>>>>>>>>>>>>>>>>> +'rpz zf editor' => 'Modifica la voce dei file di zona',
>>>>>>>>>>>>>>>>>> +'rpz zf imported' => '(importato da rpz-config)',
>>>>>>>>>>>>>>>>>> +'rpz zf remark info' => 'I caratteri validi sono a-z, A-Z, 0-9 e trattino basso',
>>>>>>>>>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.tr.pl b/config/rpz/rpz.tr.pl
>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>> index 000000000..00226e192
>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.tr.pl
>>>>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>>>>> +# I�in eklendi Ayriyeten Response Policy Zone (RPZ)
>>>>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>>>>> +'rpz apply' => 'Uygulamak',
>>>>>>>>>>>>>>>>>> +'rpz cl allow enable' => '�zel Etkinlestir allowlist:',
>>>>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Izin verilmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>>> +'rpz cl allow' => 'Etkinlestir allowlist',
>>>>>>>>>>>>>>>>>> +'rpz cl block enable' => '�zel Etkinlestir blocklist:',
>>>>>>>>>>>>>>>>>> +'rpz cl block info' => 'Engellenmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>>> +'rpz cl block' => 'Engellenmis blocklist',
>>>>>>>>>>>>>>>>>> +'rpz cl' => 'Engellenmis lists',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'NAME ge�erli degil',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ge�ersiz yapilandirma bulundu. Terminalde daha fazla bilgi i�in Unbound-checkConf komutunu �alistirin',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'allow/block liste bos',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'kopyalamak - NAME zaten var',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'URL ge�erli degil',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 106' => '�ikarilamiyor NAME yok',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'NAME ge�erli degil - "allow" veya "block" yalniz',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'Parametre eksik veya yanlis',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control basarisiz',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'Engellenmis Allowlist/Blocklist ge�ersiz girisler i�erir',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'REMARK ge�erli degil',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'Ge�ersiz giris allowlist, line ',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'Ge�ersiz giris blocklist, line ',
>>>>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'Se�ilen giris yok: ',
>>>>>>>>>>>>>>>>>> +'rpz zf editor' => 'yazimlamak zonefiles giris',
>>>>>>>>>>>>>>>>>> +'rpz zf imported' => '(ithal edildi rpz-config)',
>>>>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Ge�erli karakterler a-z, A-Z, 0-9ve alt�st.',
>>>>>>>>>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>>>>> diff --git a/html/cgi-bin/rpz.cgi b/html/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>> index 000000000..a821c92ac
>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>> +++ b/html/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>>>>> @@ -0,0 +1,923 @@
>>>>>>>>>>>>>>>>>> +#!/usr/bin/perl
>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>>> +# Copyright (C) 2005-2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +use strict;
>>>>>>>>>>>>>>>>>> +use Scalar::Util qw(looks_like_number);
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# debugging
>>>>>>>>>>>>>>>>>> +#use warnings;
>>>>>>>>>>>>>>>>>> +#use CGI::Carp 'fatalsToBrowser';
>>>>>>>>>>>>>>>>>> +#use Data::Dumper;
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +require '/var/ipfire/general-functions.pl';
>>>>>>>>>>>>>>>>>> +require "${General::swroot}/lang.pl";
>>>>>>>>>>>>>>>>>> +require "${General::swroot}/header.pl";
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +###--- Extra HTML ---###
>>>>>>>>>>>>>>>>>> +my $extraHead = <<END
>>>>>>>>>>>>>>>>>> +<style>
>>>>>>>>>>>>>>>>>> + /* alternating row background */
>>>>>>>>>>>>>>>>>> + .tbl tr:nth-child(2n+2) {
>>>>>>>>>>>>>>>>>> + background-color: var(--color-light-grey);
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> + .tbl tr:nth-child(2n+3) {
>>>>>>>>>>>>>>>>>> + background-color: var(--color-grey);
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> + /* text styles */
>>>>>>>>>>>>>>>>>> + .tbl th:not(:last-child) {
>>>>>>>>>>>>>>>>>> + text-align: left;
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> + div.right {
>>>>>>>>>>>>>>>>>> + text-align: right;
>>>>>>>>>>>>>>>>>> + margin-top: 0.5em;
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> + /* customlist input */
>>>>>>>>>>>>>>>>>> + textarea.domainlist {
>>>>>>>>>>>>>>>>>> + margin: 0.5em 0;
>>>>>>>>>>>>>>>>>> + resize: vertical;
>>>>>>>>>>>>>>>>>> + min-height: 10em;
>>>>>>>>>>>>>>>>>> + overflow: auto;
>>>>>>>>>>>>>>>>>> + white-space: pre;
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> + button[type=submit]:disabled {
>>>>>>>>>>>>>>>>>> + opacity: 0.6;
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> +</style>
>>>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>>> +###--- End of extra HTML ---###
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +### Settings ###
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# Request DNS service reload after configuration change
>>>>>>>>>>>>>>>>>> +my $RPZ_RELOAD_FLAG = "${General::swroot}/dns/rpz/reload.flag";
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# Configuration file for all available zonefiles
>>>>>>>>>>>>>>>>>> +# Format: index, name (unique), enabled (on/off), URL, remark
>>>>>>>>>>>>>>>>>> +my $ZONEFILES_CONF = "${General::swroot}/dns/rpz/zonefiles.conf";
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# Configuration file for custom lists
>>>>>>>>>>>>>>>>>> +# IDs: 0=allowlist, 1=blocklist, 2=options (allow/block enabled)
>>>>>>>>>>>>>>>>>> +my $CUSTOMLISTS_CONF = "${General::swroot}/dns/rpz/customlists.conf";
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# Export custom lists to rpz-config
>>>>>>>>>>>>>>>>>> +my $RPZ_ALLOWLIST = "${General::swroot}/dns/rpz/allowlist";
>>>>>>>>>>>>>>>>>> +my $RPZ_BLOCKLIST = "${General::swroot}/dns/rpz/blocklist";
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +### Preparation ###
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# Create missing config files
>>>>>>>>>>>>>>>>>> +unless(-f $ZONEFILES_CONF) { &General::system('touch', "$ZONEFILES_CONF"); }
>>>>>>>>>>>>>>>>>> +unless(-f $CUSTOMLISTS_CONF) { &General::system('touch', "$CUSTOMLISTS_CONF"); }
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +## Global gui data
>>>>>>>>>>>>>>>>>> +my $errormessage = "";
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +## Global configuration data
>>>>>>>>>>>>>>>>>> +my %zonefiles = ();
>>>>>>>>>>>>>>>>>> +my %customlists = ();
>>>>>>>>>>>>>>>>>> +&_zonefiles_load();
>>>>>>>>>>>>>>>>>> +&_customlists_load();
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +## Global CGI form data
>>>>>>>>>>>>>>>>>> +my %cgiparams = ();
>>>>>>>>>>>>>>>>>> +&Header::getcgihash(\%cgiparams);
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +my $action = $cgiparams{'ACTION'} // 'NONE';
>>>>>>>>>>>>>>>>>> +my $action_key = $cgiparams{'KEY'} // ''; # entry being edited, empty = none/new
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +###--- Process form actions ---###
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# Zonefiles action: Check whether the requested entry exists
>>>>>>>>>>>>>>>>>> +if((substr($action, 0, 3) eq 'ZF_') && ($action_key)) {
>>>>>>>>>>>>>>>>>> + unless(defined $zonefiles{$action_key}) {
>>>>>>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr(204, $action_key);
>>>>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +## Perform actions
>>>>>>>>>>>>>>>>>> +if($action eq 'ZF_SAVE') { ## Save new or modified zonefiles entry
>>>>>>>>>>>>>>>>>> + if(&_action_zf_save()) {
>>>>>>>>>>>>>>>>>> + $action = 'NONE'; # success, return to main page
>>>>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>>>> + $action = 'ZF_EDIT'; # error occured, keep editing
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +} elsif($action eq 'ZF_TOGGLE') { ## Toggle on/off
>>>>>>>>>>>>>>>>>> + if(&_action_zf_toggle()) {
>>>>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +} elsif($action eq 'ZF_REMOVE') { ## Remove entry
>>>>>>>>>>>>>>>>>> + if(&_action_zf_remove()) {
>>>>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +} elsif($action eq 'CL_SAVE') { ## Save custom lists
>>>>>>>>>>>>>>>>>> + if(&_action_cl_save()) {
>>>>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +} elsif($action eq 'RPZ_RELOAD') { ## Reload dns configuration
>>>>>>>>>>>>>>>>>> + if(&_action_rpz_reload()) {
>>>>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +} elsif($action eq 'UNB_RESTART') { ## Restart unbound service
>>>>>>>>>>>>>>>>>> + if(&_action_unb_restart()) {
>>>>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +###--- Start GUI ---###
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +## Start http output
>>>>>>>>>>>>>>>>>> +&Header::showhttpheaders();
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# Start HTML
>>>>>>>>>>>>>>>>>> +&Header::openpage($Lang::tr{'rpz'}, 1, $extraHead);
>>>>>>>>>>>>>>>>>> +&Header::openbigbox('100%', 'left', '');
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# Show error messages
>>>>>>>>>>>>>>>>>> +if($errormessage) {
>>>>>>>>>>>>>>>>>> + &_print_message($errormessage);
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# Handle zonefile add/edit mode
>>>>>>>>>>>>>>>>>> +if($action eq "ZF_EDIT") {
>>>>>>>>>>>>>>>>>> + &_print_zonefile_editor();
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Finalize page and exit cleanly
>>>>>>>>>>>>>>>>>> + &Header::closebigbox();
>>>>>>>>>>>>>>>>>> + &Header::closepage();
>>>>>>>>>>>>>>>>>> + exit(0);
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# Show gui elements
>>>>>>>>>>>>>>>>>> +&_print_zonefiles();
>>>>>>>>>>>>>>>>>> +&_print_customlists();
>>>>>>>>>>>>>>>>>> +&_print_gui_extras();
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +&Header::closebigbox();
>>>>>>>>>>>>>>>>>> +&Header::closepage();
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +###--- End of GUI ---###
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +###--- Internal configuration file functions ---###
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# Load all available zonefiles from rpz-config and the internal configuration
>>>>>>>>>>>>>>>>>> +sub _zonefiles_load {
>>>>>>>>>>>>>>>>>> + # Clean start
>>>>>>>>>>>>>>>>>> + %zonefiles = ();
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Source 1: Get the currently enabled zonefiles from rpz-config (expected format [name]=[URL])
>>>>>>>>>>>>>>>>>> + my @enabled_files = &General::system_output('/usr/sbin/rpz-config', 'list');
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + foreach my $row (@enabled_files) {
>>>>>>>>>>>>>>>>>> + chomp($row);
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Use regex instead of split() to skip non-matching lines
>>>>>>>>>>>>>>>>>> + next unless($row =~ /^(\w+)=(.+)$/);
>>>>>>>>>>>>>>>>>> + my ($name, $url) = ($1, $2);
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Unique names are already guaranteed by rpz-config
>>>>>>>>>>>>>>>>>> + if(&_rpz_validate_zonefile($name, $url, '', 0) == 0) {
>>>>>>>>>>>>>>>>>> + # Populate global data hash, mark all found entries as enabled
>>>>>>>>>>>>>>>>>> + my %entry = ('enabled' => 'on',
>>>>>>>>>>>>>>>>>> + 'url' => $url,
>>>>>>>>>>>>>>>>>> + 'remark' => $Lang::tr{'rpz zf imported'});
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Source 2: Get additional data and disabled entries from configuration file
>>>>>>>>>>>>>>>>>> + my %configured_files = ();
>>>>>>>>>>>>>>>>>> + &General::readhasharray($ZONEFILES_CONF, \%configured_files);
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + foreach my $row (values (%configured_files)) {
>>>>>>>>>>>>>>>>>> + my ($name, $enabled, $url, $remark) = @$row;
>>>>>>>>>>>>>>>>>> + $remark //= "";
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + next unless($name);
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Check whether this row belongs to an entry already imported from rpz-config
>>>>>>>>>>>>>>>>>> + if(defined $zonefiles{$name}) {
>>>>>>>>>>>>>>>>>> + # Existing entry, only merge additional data
>>>>>>>>>>>>>>>>>> + $zonefiles{$name}{'remark'} = $remark;
>>>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>>>> + # Skip entry if it is marked as enabled but not found by rpz-config. It was then deleted manually
>>>>>>>>>>>>>>>>>> + if($enabled ne 'on') {
>>>>>>>>>>>>>>>>>> + # Populate global data hash
>>>>>>>>>>>>>>>>>> + my %entry = ('enabled' => 'off',
>>>>>>>>>>>>>>>>>> + 'url' => $url // "",
>>>>>>>>>>>>>>>>>> + 'remark' => $remark);
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# Save internal zonefiles configuration
>>>>>>>>>>>>>>>>>> +sub _zonefiles_save_conf {
>>>>>>>>>>>>>>>>>> + my $index = 0;
>>>>>>>>>>>>>>>>>> + my %export = ();
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Loop trough all zonefiles and create "hasharray" type export
>>>>>>>>>>>>>>>>>> + foreach my $name (keys %zonefiles) {
>>>>>>>>>>>>>>>>>> + my @entry = ($name,
>>>>>>>>>>>>>>>>>> + $zonefiles{$name}{'enabled'},
>>>>>>>>>>>>>>>>>> + $zonefiles{$name}{'url'},
>>>>>>>>>>>>>>>>>> + $zonefiles{$name}{'remark'});
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + $export{$index++} = \@entry;
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + &General::writehasharray($ZONEFILES_CONF, \%export);
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# Load custom lists from rpz-config and the internal configuration
>>>>>>>>>>>>>>>>>> +sub _customlists_load {
>>>>>>>>>>>>>>>>>> + # Clean start
>>>>>>>>>>>>>>>>>> + %customlists = ();
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Load configuration file
>>>>>>>>>>>>>>>>>> + my %lists_conf = ();
>>>>>>>>>>>>>>>>>> + &General::readhasharray($CUSTOMLISTS_CONF, \%lists_conf);
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Get list options, enabled by default to start import
>>>>>>>>>>>>>>>>>> + $customlists{'allow'}{'enabled'} = $lists_conf{2}[0] // 'on';
>>>>>>>>>>>>>>>>>> + $customlists{'block'}{'enabled'} = $lists_conf{2}[1] // 'on';
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Import enabled list from rpz-config, otherwise retrieve stored or empty list from configuration file
>>>>>>>>>>>>>>>>>> + if($customlists{'allow'}{'enabled'} eq 'on') {
>>>>>>>>>>>>>>>>>> + &_customlist_import('allow', $RPZ_ALLOWLIST);
>>>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>>>> + $customlists{'allow'}{'list'} = $lists_conf{0} // [];
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> + if($customlists{'block'}{'enabled'} eq 'on') {
>>>>>>>>>>>>>>>>>> + &_customlist_import('block', $RPZ_BLOCKLIST);
>>>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>>>> + $customlists{'block'}{'list'} = $lists_conf{1} // [];
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# Save internal custom lists configuration
>>>>>>>>>>>>>>>>>> +sub _customlists_save_conf {
>>>>>>>>>>>>>>>>>> + my %export = ();
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Match IDs with import function
>>>>>>>>>>>>>>>>>> + $export{0} = $customlists{'allow'}{'list'};
>>>>>>>>>>>>>>>>>> + $export{1} = $customlists{'block'}{'list'};
>>>>>>>>>>>>>>>>>> + $export{2} = [$customlists{'allow'}{'enabled'}, $customlists{'block'}{'enabled'}];
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + &General::writehasharray($CUSTOMLISTS_CONF, \%export);
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# Import a custom list from plain file, returns empty list if file is missing
>>>>>>>>>>>>>>>>>> +sub _customlist_import {
>>>>>>>>>>>>>>>>>> + my ($listname, $filename) = @_;
>>>>>>>>>>>>>>>>>> + my @list = ();
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # File exists, load and check all lines
>>>>>>>>>>>>>>>>>> + if(-f $filename) {
>>>>>>>>>>>>>>>>>> + open(my $FH, '<', $filename) or die "Can't read $filename: $!";
>>>>>>>>>>>>>>>>>> + while(my $line = <$FH>) {
>>>>>>>>>>>>>>>>>> + chomp($line);
>>>>>>>>>>>>>>>>>> + push(@list, $line);
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> + close($FH);
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Clean up imported data
>>>>>>>>>>>>>>>>>> + &_rpz_validate_customlist(\@list, 1);
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + $customlists{$listname}{'list'} = \@list;
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# Export a custom list to plain file or clear file if list is disabled
>>>>>>>>>>>>>>>>>> +sub _customlist_export {
>>>>>>>>>>>>>>>>>> + my ($listname, $filename) = @_;
>>>>>>>>>>>>>>>>>> + return unless(defined $customlists{$listname});
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Write enabled domain list to file, otherwise save empty file
>>>>>>>>>>>>>>>>>> + open(my $FH, '>', $filename) or die "Can't write $filename: $!";
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + if($customlists{$listname}{'enabled'} eq 'on') {
>>>>>>>>>>>>>>>>>> + foreach my $line (@{$customlists{$listname}{'list'}}) {
>>>>>>>>>>>>>>>>>> + print $FH "$line\n";
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>>>> + print $FH "; Note: This list is currently disabled by $ENV{'SCRIPT_NAME'}\n";
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + close($FH);
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +###--- Internal gui functions ---###
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# Show simple message box
>>>>>>>>>>>>>>>>>> +sub _print_message {
>>>>>>>>>>>>>>>>>> + my ($message, $title) = @_;
>>>>>>>>>>>>>>>>>> + $title ||= $Lang::tr{'error messages'};
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $title);
>>>>>>>>>>>>>>>>>> + print "<span>$message</span>";
>>>>>>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# Show all zone files and related gui elements
>>>>>>>>>>>>>>>>>> +sub _print_zonefiles {
>>>>>>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf'});
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>>>>> +<table class="tbl" width="100%">
>>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>>> + <th>$Lang::tr{'name'}</th>
>>>>>>>>>>>>>>>>>> + <th>URL</th>
>>>>>>>>>>>>>>>>>> + <th>$Lang::tr{'remark'}</th>
>>>>>>>>>>>>>>>>>> + <th colspan="3">$Lang::tr{'action'}</th>
>>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Sort zonefiles by name and loop trough all entries
>>>>>>>>>>>>>>>>>> + foreach my $name (sort keys %zonefiles) {
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Toggle button label translation
>>>>>>>>>>>>>>>>>> + my $toggle_tr = ($zonefiles{$name}{'enabled'} eq 'on') ? $Lang::tr{'click to disable'} : $Lang::tr{'click to enable'};
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>>> + <td>$name</td>
>>>>>>>>>>>>>>>>>> + <td>$zonefiles{$name}{'url'}</td>
>>>>>>>>>>>>>>>>>> + <td>$zonefiles{$name}{'remark'}</td>
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_TOGGLE">
>>>>>>>>>>>>>>>>>> + <input type="image" src="/images/$zonefiles{$name}{'enabled'}.gif" title="$toggle_tr" alt="$toggle_tr">
>>>>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_EDIT">
>>>>>>>>>>>>>>>>>> + <input type="image" src="/images/edit.gif" title="$Lang::tr{'edit'}" alt="$Lang::tr{'edit'}">
>>>>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_REMOVE">
>>>>>>>>>>>>>>>>>> + <input type="image" src="/images/delete.gif" title="$Lang::tr{'remove'}" alt="$Lang::tr{'remove'}">
>>>>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Disable reload button if not needed
>>>>>>>>>>>>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>>>>> +</table>
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +<div class="right">
>>>>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="">
>>>>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="ZF_EDIT">$Lang::tr{'add'}</button>
>>>>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>>>>> +</div>
>>>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# Show zonefiles entry editor
>>>>>>>>>>>>>>>>>> +sub _print_zonefile_editor {
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Key specified: Edit existing entry
>>>>>>>>>>>>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>>>>>>>>>>>>> + # Load data to be edited, but don't override already present values (allows user to edit after error)
>>>>>>>>>>>>>>>>>> + $cgiparams{'ZF_NAME'} //= $action_key;
>>>>>>>>>>>>>>>>>> + $cgiparams{'ZF_URL'} //= $zonefiles{$action_key}{'url'};
>>>>>>>>>>>>>>>>>> + $cgiparams{'ZF_REMARK'} //= $zonefiles{$action_key}{'remark'};
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Fallback to empty form
>>>>>>>>>>>>>>>>>> + $cgiparams{'ZF_NAME'} //= "";
>>>>>>>>>>>>>>>>>> + $cgiparams{'ZF_URL'} //= "";
>>>>>>>>>>>>>>>>>> + $cgiparams{'ZF_REMARK'} //= "";
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf editor'});
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>>>> +<input type="hidden" name="KEY" value="$action_key">
>>>>>>>>>>>>>>>>>> +<table width="100%">
>>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>>> + <td width="20%">$Lang::tr{'name'}: <img src="/blob.gif" alt="*"></td>
>>>>>>>>>>>>>>>>>> + <td><input type="text" name="ZF_NAME" value="$cgiparams{'ZF_NAME'}" size="40" maxlength="32" title="$Lang::tr{'rpz zf remark info'}" pattern="[a-zA-Z0-9_]{1,32}" required></td>
>>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>>> + <td width="20%">URL: <img src="/blob.gif" alt="*"></td>
>>>>>>>>>>>>>>>>>> + <td><input type="url" name="ZF_URL" value="$cgiparams{'ZF_URL'}" size="40" maxlength="128" required></td>
>>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>>> + <td width="20%">$Lang::tr{'remark'}:</td>
>>>>>>>>>>>>>>>>>> + <td><input type="text" name="ZF_REMARK" value="$cgiparams{'ZF_REMARK'}" size="40" maxlength="32"></td>
>>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>>> + <td colspan="2"><hr></td>
>>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>>> + <td width="55%"><img src="/blob.gif" alt="*"> $Lang::tr{'required field'}</td>
>>>>>>>>>>>>>>>>>> + <td align="right"><button type="submit" name="ACTION" value="ZF_SAVE">$Lang::tr{'save'}</button></td>
>>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>>> +</table>
>>>>>>>>>>>>>>>>>> +</form>
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +<div class="right">
>>>>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="NONE">$Lang::tr{'back'}</button>
>>>>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>>>>> +</div>
>>>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# Show custom allow/block files and related gui elements
>>>>>>>>>>>>>>>>>> +sub _print_customlists {
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Load lists from config, unless they are currently being edited
>>>>>>>>>>>>>>>>>> + if($action ne 'CL_SAVE') {
>>>>>>>>>>>>>>>>>> + $cgiparams{'ALLOW_LIST'} = join("\n", @{$customlists{'allow'}{'list'}});
>>>>>>>>>>>>>>>>>> + $cgiparams{'BLOCK_LIST'} = join("\n", @{$customlists{'block'}{'list'}});
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + $cgiparams{'ALLOW_ENABLED'} = ($customlists{'allow'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>>>>>>>>>>>>> + $cgiparams{'BLOCK_ENABLED'} = ($customlists{'block'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Fallback to empty form
>>>>>>>>>>>>>>>>>> + $cgiparams{'ALLOW_LIST'} //= "";
>>>>>>>>>>>>>>>>>> + $cgiparams{'BLOCK_LIST'} //= "";
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # HTML checkboxes, unchecked = no or undef value in POST data
>>>>>>>>>>>>>>>>>> + my %checked = ();
>>>>>>>>>>>>>>>>>> + $checked{'ALLOW_ENABLED'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? " checked" : "";
>>>>>>>>>>>>>>>>>> + $checked{'BLOCK_ENABLED'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? " checked" : "";
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Disable reload button if not needed
>>>>>>>>>>>>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz cl'});
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>>>> +<table width="100%">
>>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl allow'}</b><br>$Lang::tr{'rpz cl allow info'}</td>
>>>>>>>>>>>>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl block'}</b><br>$Lang::tr{'rpz cl block info'}</td>
>>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>>> + <td colspan="2"><textarea name="ALLOW_LIST" class="domainlist" cols="45">
>>>>>>>>>>>>>>>>>> +$cgiparams{'ALLOW_LIST'}</textarea></td>
>>>>>>>>>>>>>>>>>> + <td colspan="2"><textarea name="BLOCK_LIST" class="domainlist" cols="45">
>>>>>>>>>>>>>>>>>> +$cgiparams{'BLOCK_LIST'}</textarea></td>
>>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>>> + <td><label for="allow_enabled">$Lang::tr{'rpz cl allow enable'}</label></td>
>>>>>>>>>>>>>>>>>> + <td width="15%"><input type="checkbox" name="ALLOW_ENABLED" id="allow_enabled"$checked{'ALLOW_ENABLED'}></td>
>>>>>>>>>>>>>>>>>> + <td><label for="block_enabled">$Lang::tr{'rpz cl block enable'}</label></td>
>>>>>>>>>>>>>>>>>> + <td width="15%"><input type="checkbox" name="BLOCK_ENABLED" id="block_enabled"$checked{'BLOCK_ENABLED'}></td>
>>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>>> + <td colspan="4"><hr></td>
>>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>>> + <td align="right" colspan="4">
>>>>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="CL_SAVE">$Lang::tr{'save'}</button>
>>>>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>>>>>>> +</table>
>>>>>>>>>>>>>>>>>> +</form>
>>>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# Output javascript and extra gui elements
>>>>>>>>>>>>>>>>>> +sub _print_gui_extras {
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Apply/Restart button modifier key handler
>>>>>>>>>>>>>>>>>> + if(&_rpz_needs_reload()) {
>>>>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>>>>> +<script>
>>>>>>>>>>>>>>>>>> + // Commit modifier key handler
>>>>>>>>>>>>>>>>>> + (function(jq, document) {
>>>>>>>>>>>>>>>>>> + var keyEventsOn = false; // Keyboard events attached
>>>>>>>>>>>>>>>>>> + var keyModify = false; // Modifier key pressed
>>>>>>>>>>>>>>>>>> + var mouseHover = false; // Mouse over commit button
>>>>>>>>>>>>>>>>>> + var btnModified = false; // Button modified to "Restart"
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + // Document-level key events, enable only while cursor is over button
>>>>>>>>>>>>>>>>>> + function attachKeyEvents() {
>>>>>>>>>>>>>>>>>> + if(keyEventsOn) {
>>>>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> + keyEventsOn = true;
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + jq(document).on("keydown.rpz", function(event) {
>>>>>>>>>>>>>>>>>> + if((!keyModify) && event.shiftKey) {
>>>>>>>>>>>>>>>>>> + keyModify = true;
>>>>>>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>>>>> + jq(document).on("keyup.rpz", function(event) {
>>>>>>>>>>>>>>>>>> + if(keyModify && (!event.shiftKey)) {
>>>>>>>>>>>>>>>>>> + keyModify = false;
>>>>>>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> + function removeKeyEvents() {
>>>>>>>>>>>>>>>>>> + keyModify = false;
>>>>>>>>>>>>>>>>>> + if(keyEventsOn) {
>>>>>>>>>>>>>>>>>> + jq(document).off("keydown.rpz keyup.rpz");
>>>>>>>>>>>>>>>>>> + keyEventsOn = false;
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + // Attach mouse hover events to commit buttons
>>>>>>>>>>>>>>>>>> + function attachMouseEvents() {
>>>>>>>>>>>>>>>>>> + jq("button.commit").on("mouseenter", function(event) {
>>>>>>>>>>>>>>>>>> + if(!mouseHover) {
>>>>>>>>>>>>>>>>>> + mouseHover = true;
>>>>>>>>>>>>>>>>>> + attachKeyEvents();
>>>>>>>>>>>>>>>>>> + // Handle already pressed key
>>>>>>>>>>>>>>>>>> + keyModify = !!(event.shiftKey);
>>>>>>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + // Cursor moved away: Disable key listener to minimize events
>>>>>>>>>>>>>>>>>> + jq("button.commit").on("mouseleave", function() {
>>>>>>>>>>>>>>>>>> + if(mouseHover) {
>>>>>>>>>>>>>>>>>> + mouseHover = false;
>>>>>>>>>>>>>>>>>> + removeKeyEvents();
>>>>>>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + // Modify commit button
>>>>>>>>>>>>>>>>>> + function handleModify() {
>>>>>>>>>>>>>>>>>> + let modify = mouseHover && keyModify;
>>>>>>>>>>>>>>>>>> + if(btnModified != modify) {
>>>>>>>>>>>>>>>>>> + if(modify) {
>>>>>>>>>>>>>>>>>> + jq("button.commit").text("$Lang::tr{'restart'}").val("UNB_RESTART");
>>>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>>>> + jq("button.commit").text("$Lang::tr{'rpz apply'}").val("RPZ_RELOAD");
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> + btnModified = modify;
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + // jQuery DOM ready
>>>>>>>>>>>>>>>>>> + jq(function() {
>>>>>>>>>>>>>>>>>> + attachMouseEvents();
>>>>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>>>>> + })(jQuery, document);
>>>>>>>>>>>>>>>>>> +</script>
>>>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>>> + } # End of modifier key handler
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +###--- Internal action processing functions ---###
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# Toggle zonefile on/off
>>>>>>>>>>>>>>>>>> +sub _action_zf_toggle {
>>>>>>>>>>>>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + my $result = 0;
>>>>>>>>>>>>>>>>>> + my $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Perform toggle action
>>>>>>>>>>>>>>>>>> + if($enabled eq 'on') {
>>>>>>>>>>>>>>>>>> + $enabled = 'off';
>>>>>>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>>>> + $enabled = 'on';
>>>>>>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $action_key, $zonefiles{$action_key}{'url'}, '--no-reload');
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Save changes
>>>>>>>>>>>>>>>>>> + $zonefiles{$action_key}{'enabled'} = $enabled;
>>>>>>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# Remove zonefile
>>>>>>>>>>>>>>>>>> +sub _action_zf_remove {
>>>>>>>>>>>>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Remove from rpz-config if currently active
>>>>>>>>>>>>>>>>>> + if($zonefiles{$action_key}{'enabled'} eq 'on') {
>>>>>>>>>>>>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Remove from data hash and save changes
>>>>>>>>>>>>>>>>>> + delete $zonefiles{$action_key};
>>>>>>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Clear action_key, as the entry is now removed entirely
>>>>>>>>>>>>>>>>>> + $action_key = "";
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# Create or update zonefile entry
>>>>>>>>>>>>>>>>>> +# Returns undef if gui needs to stay in editor mode
>>>>>>>>>>>>>>>>>> +sub _action_zf_save {
>>>>>>>>>>>>>>>>>> + my $result = 0;
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + my $name = $cgiparams{'ZF_NAME'} // "";
>>>>>>>>>>>>>>>>>> + my $url = $cgiparams{'ZF_URL'} // "";
>>>>>>>>>>>>>>>>>> + my $remark = $cgiparams{'ZF_REMARK'} // "";
>>>>>>>>>>>>>>>>>> + my $enabled = 'on'; # Enable new entries by default
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Note on variables:
>>>>>>>>>>>>>>>>>> + # name = unique key, will be used to address the entry
>>>>>>>>>>>>>>>>>> + # action_key = name of the entry being edited, empty for new entry
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Only check for unique name if it changed
>>>>>>>>>>>>>>>>>> + # (this also checks new entries because the action_key is empty in this case)
>>>>>>>>>>>>>>>>>> + $result = &_rpz_validate_zonefile($name, $url, $remark, (lc($name) ne lc($action_key)));
>>>>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 0);
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Edit existing entry: Determine what was changed
>>>>>>>>>>>>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>>>>>>>>>>>>> + # Name und URL remain unchanged, only save remark and finish
>>>>>>>>>>>>>>>>>> + if(($name eq $action_key) && ($url eq $zonefiles{$action_key}{'url'})) {
>>>>>>>>>>>>>>>>>> + $zonefiles{$action_key}{'remark'} = $remark;
>>>>>>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Entry was changed and needs to be recreated, preserve status
>>>>>>>>>>>>>>>>>> + $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Remove from rpz-config
>>>>>>>>>>>>>>>>>> + return unless &_action_zf_remove();
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Add new entry to rpz-config
>>>>>>>>>>>>>>>>>> + if($enabled eq 'on') {
>>>>>>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $name, $url, '--no-reload');
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Add to global data hash and save changes
>>>>>>>>>>>>>>>>>> + my %entry = ('enabled' => $enabled,
>>>>>>>>>>>>>>>>>> + 'url' => $url,
>>>>>>>>>>>>>>>>>> + 'remark' => $remark);
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# Save custom lists
>>>>>>>>>>>>>>>>>> +sub _action_cl_save {
>>>>>>>>>>>>>>>>>> + return unless((defined $cgiparams{'ALLOW_LIST'}) && (defined $cgiparams{'BLOCK_LIST'}));
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + my $result = 0;
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + my @allowlist = split(/\R/, $cgiparams{'ALLOW_LIST'});
>>>>>>>>>>>>>>>>>> + my @blocklist = split(/\R/, $cgiparams{'BLOCK_LIST'});
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Validate lists
>>>>>>>>>>>>>>>>>> + $result = &_rpz_validate_customlist(\@allowlist);
>>>>>>>>>>>>>>>>>> + if($result != 0) {
>>>>>>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr(202, $result);
>>>>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> + $result = &_rpz_validate_customlist(\@blocklist);
>>>>>>>>>>>>>>>>>> + if($result != 0) {
>>>>>>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr(203, $result);
>>>>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Add to global data hash and save changes
>>>>>>>>>>>>>>>>>> + $customlists{'allow'}{'list'} = \@allowlist;
>>>>>>>>>>>>>>>>>> + $customlists{'block'}{'list'} = \@blocklist;
>>>>>>>>>>>>>>>>>> + $customlists{'allow'}{'enabled'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? 'on' : 'off';
>>>>>>>>>>>>>>>>>> + $customlists{'block'}{'enabled'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? 'on' : 'off';
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + &_customlists_save_conf();
>>>>>>>>>>>>>>>>>> + &_customlist_export('allow', $RPZ_ALLOWLIST);
>>>>>>>>>>>>>>>>>> + &_customlist_export('block', $RPZ_BLOCKLIST);
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Make new lists, request service reload on success
>>>>>>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-make', 'allowblock', '--no-reload');
>>>>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# Trigger rpz-config reload
>>>>>>>>>>>>>>>>>> +sub _action_rpz_reload {
>>>>>>>>>>>>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Immediately clear flag to prevent multiple reloads
>>>>>>>>>>>>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>>>>>>>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Perform reload, recreate reload flag on error to enable retry
>>>>>>>>>>>>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'reload');
>>>>>>>>>>>>>>>>>> + if(not &_rpz_check_result($result, 0)) {
>>>>>>>>>>>>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# Trigger unbound restart
>>>>>>>>>>>>>>>>>> +sub _action_unb_restart {
>>>>>>>>>>>>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Immediately clear flag to prevent multiple restarts
>>>>>>>>>>>>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>>>>>>>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Perform restart, unboundctrl always exits zero
>>>>>>>>>>>>>>>>>> + &General::system('/usr/local/bin/unboundctrl', 'restart');
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +###--- Internal rpz-config functions ---###
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# Translate rpz-config exitcodes and messages
>>>>>>>>>>>>>>>>>> +# 100-199: rpz-config, 200-299: webgui
>>>>>>>>>>>>>>>>>> +sub _rpz_error_tr {
>>>>>>>>>>>>>>>>>> + my ($error, $append) = @_;
>>>>>>>>>>>>>>>>>> + $append //= '';
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Translate numeric exit codes
>>>>>>>>>>>>>>>>>> + if(looks_like_number($error)) {
>>>>>>>>>>>>>>>>>> + if(defined $Lang::tr{"rpz exitcode $error"}) {
>>>>>>>>>>>>>>>>>> + $error = $Lang::tr{"rpz exitcode $error"};
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + return "RPZ $Lang::tr{'error'}: $error" . &Header::escape($append);
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# Check result of rpz-config system call, request reload on success
>>>>>>>>>>>>>>>>>> +sub _rpz_check_result {
>>>>>>>>>>>>>>>>>> + my ($result, $request_reload) = @_;
>>>>>>>>>>>>>>>>>> + $request_reload //= 0;
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # exitcode 0 = success
>>>>>>>>>>>>>>>>>> + if($result != 0) {
>>>>>>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr($result);
>>>>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Set reload flag
>>>>>>>>>>>>>>>>>> + if($request_reload) {
>>>>>>>>>>>>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# Test whether reload flag is set
>>>>>>>>>>>>>>>>>> +sub _rpz_needs_reload {
>>>>>>>>>>>>>>>>>> + return (-f $RPZ_RELOAD_FLAG);
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# Validate a zonefile entry, returns rpz-config exitcode on failure. Use _rpz_check_result to verify.
>>>>>>>>>>>>>>>>>> +# unique = check for unique name
>>>>>>>>>>>>>>>>>> +sub _rpz_validate_zonefile {
>>>>>>>>>>>>>>>>>> + my ($name, $url, $remark, $unique) = @_;
>>>>>>>>>>>>>>>>>> + $unique //= 1;
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + unless($name =~ /^[a-zA-Z0-9_]{1,32}$/) {
>>>>>>>>>>>>>>>>>> + return 101;
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> + unless($url =~ /^[\w+\.:;\/\\&@#%?=\-~|!]{1,128}$/) {
>>>>>>>>>>>>>>>>>> + return 105;
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> + unless($remark =~ /^[\w \-()\.:;*\/\\?!&=]{0,32}$/) {
>>>>>>>>>>>>>>>>>> + return 201;
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Check against already existing names
>>>>>>>>>>>>>>>>>> + if($unique) {
>>>>>>>>>>>>>>>>>> + foreach my $existing (keys %zonefiles) {
>>>>>>>>>>>>>>>>>> + if(lc($name) eq lc($existing)) {
>>>>>>>>>>>>>>>>>> + return 104;
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + return 0;
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# Validate a custom list, returns number of rejected line on failure. Check for non-zero results.
>>>>>>>>>>>>>>>>>> +# listref = array reference, cleanup = remove invalid entries instead of returning an error
>>>>>>>>>>>>>>>>>> +sub _rpz_validate_customlist {
>>>>>>>>>>>>>>>>>> + my ($listref, $cleanup) = @_;
>>>>>>>>>>>>>>>>>> + $cleanup //= 0;
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + foreach my $index (reverse 0..$#{$listref}) {
>>>>>>>>>>>>>>>>>> + my $row = @$listref[$index];
>>>>>>>>>>>>>>>>>> + next unless($row); # Skip/allow empty lines
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Reject/remove everything besides wildcard domains and remarks
>>>>>>>>>>>>>>>>>> + if((not &General::validwildcarddomainname($row)) && (not $row =~ /^;[\w \-()\.:;*\/\\?!&=]*$/)) {
>>>>>>>>>>>>>>>>>> + unless($cleanup) {
>>>>>>>>>>>>>>>>>> + # +1 for user friendly line number and to ensure non-zero exitcode
>>>>>>>>>>>>>>>>>> + return $index + 1;
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Remove current row
>>>>>>>>>>>>>>>>>> + splice(@$listref, $index, 1);
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + return 0;
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +###--- Internal misc functions ---###
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# Send HTTP 303 redirect headers for post/request/get pattern
>>>>>>>>>>>>>>>>>> +# (Must be sent before calling &Header::showhttpheaders())
>>>>>>>>>>>>>>>>>> +sub _http_prg_redirect {
>>>>>>>>>>>>>>>>>> + my $location = "https://$ENV{'SERVER_NAME'}:$ENV{'SERVER_PORT'}$ENV{'SCRIPT_NAME'}";
>>>>>>>>>>>>>>>>>> + print "Status: 303 See Other\n";
>>>>>>>>>>>>>>>>>> + print "Location: $location\n";
>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>> diff --git a/lfs/rpz b/lfs/rpz
>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>> index 000000000..7ddbc38e5
>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>> +++ b/lfs/rpz
>>>>>>>>>>>>>>>>>> @@ -0,0 +1,96 @@
>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>> +# Definitions
>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +include Config
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +SUMMARY = response policy zone - RPZ reputation system for unbound DNS
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +VER = 1.0.0
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +THISAPP = rpz-$(VER)
>>>>>>>>>>>>>>>>>> +DIR_APP = $(DIR_SRC)/$(THISAPP)
>>>>>>>>>>>>>>>>>> +TARGET = $(DIR_INFO)/$(THISAPP)
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +PROG = rpz
>>>>>>>>>>>>>>>>>> +PAK_VER = 18
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +DEPS =
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +SERVICES =
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>> +# Top-level Rules
>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +install : $(TARGET)
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +check :
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +download :
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +b2 :
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +dist:
>>>>>>>>>>>>>>>>>> + @$(PAK)
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>> +# Installation Details
>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +$(TARGET) :
>>>>>>>>>>>>>>>>>> + @$(PREBUILD)
>>>>>>>>>>>>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # RPZ scripts
>>>>>>>>>>>>>>>>>> + install --verbose --mode=755 \
>>>>>>>>>>>>>>>>>> + $(DIR_CONF)/rpz/{rpz-config,rpz-metrics,rpz-sleep,rpz-make,rpz-functions} \
>>>>>>>>>>>>>>>>>> + --target-directory=/usr/sbin
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # RPZ config files
>>>>>>>>>>>>>>>>>> + mkdir -pv /etc/unbound/local.d
>>>>>>>>>>>>>>>>>> + install --verbose --mode=644 --owner=nobody --group=nobody \
>>>>>>>>>>>>>>>>>> + $(DIR_CONF)/rpz/00-rpz.conf \
>>>>>>>>>>>>>>>>>> + --target-directory=/etc/unbound/local.d
>>>>>>>>>>>>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/local.d
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # RPZ custom list files for allow and block
>>>>>>>>>>>>>>>>>> + mkdir -pv /var/ipfire/dns/rpz
>>>>>>>>>>>>>>>>>> + touch /var/ipfire/dns/rpz/{allowlist,blocklist}
>>>>>>>>>>>>>>>>>> + chown --verbose --recursive nobody:nobody /var/ipfire/dns/rpz
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # RPZ zone files
>>>>>>>>>>>>>>>>>> + # create empty RPZ config file to avoid a unbound config error
>>>>>>>>>>>>>>>>>> + mkdir -pv /etc/unbound/zonefiles
>>>>>>>>>>>>>>>>>> + touch /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/zonefiles
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Install addon-specific language-files
>>>>>>>>>>>>>>>>>> + install --verbose --mode=004 $(DIR_CONF)/rpz/rpz.*.pl \
>>>>>>>>>>>>>>>>>> + --target-directory=/var/ipfire/addon-lang
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + # Install backup definition
>>>>>>>>>>>>>>>>>> + cp -vf $(DIR_CONF)/backup/includes/rpz /var/ipfire/backup/addons/includes/rpz
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>>>>>>>>>>>>> + @$(POSTBUILD)
>>>>>>>>>>>>>>>>>> diff --git a/make.sh b/make.sh
>>>>>>>>>>>>>>>>>> index 827ea9e77..a77535b13 100755
>>>>>>>>>>>>>>>>>> --- a/make.sh
>>>>>>>>>>>>>>>>>> +++ b/make.sh
>>>>>>>>>>>>>>>>>> @@ -390,7 +390,7 @@ prepareenv() {
>>>>>>>>>>>>>>>>>> if [ "${free_space}" -lt "${required_space}" ]; then
>>>>>>>>>>>>>>>>>> # Add any consumed space
>>>>>>>>>>>>>>>>>> while read -r consumed_space path; do
>>>>>>>>>>>>>>>>>> - (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>>>>>>>>>>>>> + (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>>>>>>>>>>>>> done <<< "$(du --summarize --bytes "${BUILD_DIR}" "${IMAGES_DIR}" "${LOG_DIR}" 2>/dev/null)"
>>>>>>>>>>>>>>>>>> fi
>>>>>>>>>>>>>>>>>> @@ -2087,6 +2087,7 @@ build_system() {
>>>>>>>>>>>>>>>>>> lfsmake2 btrfs-progs
>>>>>>>>>>>>>>>>>> lfsmake2 inotify-tools
>>>>>>>>>>>>>>>>>> lfsmake2 grub-btrfs
>>>>>>>>>>>>>>>>>> + lfsmake2 rpz
>>>>>>>>>>>>>>>>>> lfsmake2 linux
>>>>>>>>>>>>>>>>>> lfsmake2 rtl8812au
>>>>>>>>>>>>>>>>>> diff --git a/src/paks/rpz/install.sh b/src/paks/rpz/install.sh
>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>> index 000000000..ef99bf742
>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>> +++ b/src/paks/rpz/install.sh
>>>>>>>>>>>>>>>>>> @@ -0,0 +1,36 @@
>>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>> +#
>>>>>>>>>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>>>>>>>>>> +extract_files
>>>>>>>>>>>>>>>>>> +restore_backup ${NAME}
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# fix user created files
>>>>>>>>>>>>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>>>>>>>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>>>>>>>>>>>>> + /etc/unbound/zonefiles \
>>>>>>>>>>>>>>>>>> + /etc/unbound/local.d
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# Update Language cache
>>>>>>>>>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# restart unbound to load config file
>>>>>>>>>>>>>>>>>> +/etc/init.d/unbound restart
>>>>>>>>>>>>>>>>>> diff --git a/src/paks/rpz/uninstall.sh b/src/paks/rpz/uninstall.sh
>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>> index 000000000..e11427df3
>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>> +++ b/src/paks/rpz/uninstall.sh
>>>>>>>>>>>>>>>>>> @@ -0,0 +1,38 @@
>>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>> +#
>>>>>>>>>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>>>>>>>>>>>>> +/etc/init.d/unbound stop
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +make_backup ${NAME}
>>>>>>>>>>>>>>>>>> +remove_files
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>>>>>>>>>>>>> +# "[1723428668] unbound-control[17117:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>>>>>>>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# Update Language cache
>>>>>>>>>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# start unbound to load unbound config file
>>>>>>>>>>>>>>>>>> +/etc/init.d/unbound start
>>>>>>>>>>>>>>>>>> diff --git a/src/paks/rpz/update.sh b/src/paks/rpz/update.sh
>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>> index 000000000..9bc340bc6
>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>> +++ b/src/paks/rpz/update.sh
>>>>>>>>>>>>>>>>>> @@ -0,0 +1,52 @@
>>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>> +#
>>>>>>>>>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# from update.sh
>>>>>>>>>>>>>>>>>> +extract_backup_includes
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>>>>>>>>>>>>> +/etc/init.d/unbound stop
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# from uninstall.sh
>>>>>>>>>>>>>>>>>> +make_backup ${NAME}
>>>>>>>>>>>>>>>>>> +remove_files
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>>>>>>>>>>>>> +# "unbound-control[nn:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>>>>>>>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# from install.sh
>>>>>>>>>>>>>>>>>> +extract_files
>>>>>>>>>>>>>>>>>> +restore_backup ${NAME}
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# fix user created files
>>>>>>>>>>>>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>>>>>>>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>>>>>>>>>>>>> + /etc/unbound/zonefiles \
>>>>>>>>>>>>>>>>>> + /etc/unbound/local.d
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# Update Language cache
>>>>>>>>>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>> +# restart unbound to load config files
>>>>>>>>>>>>>>>>>> +/etc/init.d/unbound start
>>>>>>>>>>>>>>>>>> --
>>>>>>>>>>>>>>>>>> 2.39.5
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> Jon
>>>>>>>>>>>>>>>> --
>>>>>>>>>>>>>>>> Jon Murphy
>>>>>>>>>>>>>>>> jon.murphy@ipfire.org
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Jon
>>>>>>>>>>>>>> --
>>>>>>>>>>>>>> Jon Murphy
>>>>>>>>>>>>>> jon.murphy@ipfire.org
>>
>>
>>
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
2025-03-24 14:25 ` Michael Tremer
2025-03-24 14:33 ` Re[2]: " Jon Murphy
@ 2025-03-24 14:41 ` Bernhard Bitsch
1 sibling, 0 replies; 30+ messages in thread
From: Bernhard Bitsch @ 2025-03-24 14:41 UTC (permalink / raw)
To: Michael Tremer; +Cc: development
Am 24.03.2025 um 15:25 schrieb Michael Tremer:
> Hello,
>
>> On 24 Mar 2025, at 13:33, Bernhard Bitsch <bbitsch@ipfire.org> wrote:
>>
>>
>>
>> Am 24.03.2025 um 11:17 schrieb Michael Tremer:
>>> Hello Jon,
>>>> On 24 Mar 2025, at 00:00, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>
>>>> Michael,
>>>>
>>>> FYI - I was wrong Unbound RPZ is _not_ watching the serial number, it is watching the "refresh", the number after the serial number.
>>> Refresh just tells the client how often to check for an update.
>>> If that is actually being set by the list publisher, then we have another problem here, because they could put some insanely low value there and we would then DDoS their infrastructure. I think we should keep it like we have it in other places that we control how often we want to check or pull for updates.
>>>
>>
>> You are right. But an extra update process wastes additional processor time. The update mechanism of unbound does the check for update ( however it is realized ) nevertheless.
>
> Yes, doing more things needs resources. But we are not seriously considering whether an IPFire system has enough resources to perform the download of a text file, or are we?
>
>>>> I understand that you don’t speak C, but you got the information from somewhere. Documentation maybe? Since that is out of date very often I like to consult the code.
>>>> From testing. Downloading rpz files using rpz unbound, and watching what happens. If the rpz file is setup for "once per day" refresh, then it only downloads one time.
>>>>
>>>>
>>>>
>>>> However that won’t solve our problem . . . and having no cache.
>>>> In `/etc/unbound/tuning.conf` there is `rrset-cache-size: 128m`. Are you referring to a different cache.
>>> Naturally unbound is loading the zone into its memory which we generally call cache.
>>> When I say cache I am thinking about persistent data storage across multiple restarts of Unbound. If I am downloading 100 MiB of RPZ lists (which is presumably still on the lower end) and I reboot my firewall, I do not want to download the same data again. We can only ever download a list *once* unless we are 100% certain that it has changed. Then we can download it once again.
>>
>> The RPZ lists are stored in files in persistent storage. Unbound creates the internal cache from these.
>
> And where are these stored?
>
This is defined in the RPZ definition, parameter 'zonefile'.
>>>> Maybe we need to implement both?
>>>>
>>>> Yes. There are very few AXFR list (I think only four were found). And many more HTTPS rpz files.
>>>>
>>>>
>>>>
>>>> Jon
>>>>
>>>>
>>>> ------ Original Message ------
>>>> From "Michael Tremer" <michael.tremer@ipfire.org>
>>>> To "Jon Murphy" <jon.murphy@ipfire.org>
>>>> Cc "IPFire: Development-List" <development@lists.ipfire.org>
>>>> Date 3/20/2025 11:26:43 AM
>>>> Subject Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
>>>>
>>>>> Hello Jon,
>>>>> Please don’t forget to Cc the list...
>>>>>
>>>>>>
>>>>>> On 19 Mar 2025, at 18:27, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>> Michael,
>>>>>>
>>>>>>>
>>>>>>> Where in the code is this implemented? I cannot find anything like this:
>>>>>>
>>>>>> Keep in mind I am not a "C" person. Maybe in this section?:
>>>>>> https://git.ipfire.org/?p=thirdparty/unbound.git;a=blob;f=services/authzone.c;hb=30b9cb5f813003d0a2b1c2e678652396615b1b7d#l5875
>>>>>
>>>>> This where the AXFR response is being handled when doing a DNS zone transfer. This code is not being called when performing a HTTP download.
>>>>> I understand that you don’t speak C, but you got the information from somewhere. Documentation maybe? Since that is out of date very often I like to consult the code.
>>>>>
>>>>>>
>>>>>> —
>>>>>> When I was just learning about RPZ I created a separate RPZ file for testing. When I changed the SOA line with a new serial number, the RPZ file download would happen in about 5 minutes.
>>>>>> https://people.ipfire.org/~jon/sblack-adhoc.rpz
>>>>>
>>>>> It might well be that the file is not being reloaded if the download matches the content that unbound already has. That would of course save some resources.
>>>>> However that won’t solve our problem with redundant downloads and having no cache.
>>>>>
>>>>>>
>>>>>> That is how I found out the SOA line is watched for a serial number change.
>>>>>> I’ll reconfirm my findings.
>>>>>>
>>>>>>>
>>>>>>>>> The second reason is that we have a lot of firewalls out there. Not all of them will enable this feature and all of the lists, but even if it is a good chunk, we will generate terabytes of traffic which put load on the infrastructure and will cost money. It simply is not what we want to do, regardless of self-hosting those lists and pulling them from somewhere else.
>>>>>>
>>>>>> So I understand, are you thinking of hosting RPZ AXFR (DNS zone transfer) on IPFire infrastructure?
>>>>>
>>>>> No, I don’t think that we can generally do this. The biggest problem is licensing as we cannot take anyones content and host it ourselves. We would re-distribute those lists and that will only work with permission of the publishers. I assume that would be too much work to actually get some useful content out there. We might limit ourselves to only those lists that are under a very permissive license. Nobody wants that.
>>>>> From a technical point of view, DNS over TCP might not be very nice in terms of forging the transfer and so we would need TLS as well… It should work, but even if we would be able to encourage other people to publish their lists I doubt they would implement DNS over TLS for authoritative DNS. That standard is in very early stages as well.
>>>>> As far as I can see, those vendors who offer a list as a commercial product are using DNS to distribute it (e.g. Spamhaus). Those people who have made this all a hobby are throwing the lists onto GitHub and let them handle the traffic.
>>>>> Maybe we need to implement both?
>>>>> -Michael
>>>>>
>>>>>>
>>>>>> Jon
>>>>>> On 3/19/25 5:35 AM, Michael Tremer wrote:
>>>>>>>
>>>>>>>
>>>>>>> Hello Jon,
>>>>>>> Where in the code is this implemented? I cannot find anything like this:
>>>>>>> Unbound loads the entire file into memory and then starts parsing it. The only special treatment there is is to check whether the first line is a valid zone entry. It does not even have to be a SOA record.
>>>>>>> https://git.ipfire.org/?p=thirdparty/unbound.git;a=blob;f=services/authzone.c;hb=30b9cb5f813003d0a2b1c2e678652396615b1b7d#l1188
>>>>>>> I am also concerned that Unbound will not be able to support an upstream proxy for any downloads. The caching situation is also unclear for me, so I believe that we will be looking at writing a custom downloader that implements all these things.
>>>>>>> -Michael
>>>>>>>
>>>>>>>>
>>>>>>>> On 19 Mar 2025, at 02:58, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>> Michael,
>>>>>>>>
>>>>>>>>>
>>>>>>>>> The emphasis is on the repeated downloads of the same list. That is
>>>>>>>>
>>>>>>>> > what cannot happen.
>>>>>>>> The Unbound RPZ code, as installed within IPFire, watches for a change
>>>>>>>> in the SOA line of each RPZ file. This is an example of the first few
>>>>>>>> lines for every RPZ file.
>>>>>>>> $TTL 300
>>>>>>>> @ SOA localhost. root.localhost. 1742298960 43200 3600 86400 300
>>>>>>>> NS localhost.
>>>>>>>> ;
>>>>>>>> ; Title: HaGeZi's Pop-Up Ads DNS Blocklist
>>>>>>>> ; Description: Blocks annoying and malicious pop-up ads.
>>>>>>>> If the SOA serial number changes (e.g. the 1742298960), then Unbound RPZ
>>>>>>>> code does its thing and downloads. Otherwise there is no download.
>>>>>>>>
>>>>>>>>>
>>>>>>>>> So there has to be a way to ensure that we won’t download a list again
>>>>>>>>
>>>>>>>> > unless it has actually changed.
>>>>>>>> This should do what you want but I may be missing your point.
>>>>>>>>
>>>>>>>>>
>>>>>>>>> DNS has a builtin functionality called AXFR. It simply does the job
>>>>>>>>
>>>>>>>> > for you. I was just wondering whether that was not being used.
>>>>>>>> I need to read about AXFR/IXFR and learn a little more.
>>>>>>>> Jon
>>>>>>>> On 3/17/25 5:35 AM, Michael Tremer wrote:
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> Good Morning Jon,
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> On 16 Mar 2025, at 17:00, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>>>> Michael,
>>>>>>>>>> I was reading through you response again an I want to understand this post:
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>>>>>>>>>
>>>>>>>>>> So if RPZ doesn't use HTTPS, what is it using? I am missing a key point here.
>>>>>>>>>
>>>>>>>>> The emphasis is on the repeated downloads of the same list. That is what cannot happen.
>>>>>>>>> Although it might not affect a lot of people in our general user-base, there are some that have a metered connection and will pay for data by volume. Some of the lists I looked at are just under 20 MiB. Therefore we need to keep any traffic down to a minimum. The second reason is that we have a lot of firewalls out there. Not all of them will enable this feature and all of the lists, but even if it is a good chunk, we will generate terabytes of traffic which put load on the infrastructure and will cost money. It simply is not what we want to do, regardless of self-hosting those lists and pulling them from somewhere else.
>>>>>>>>> So there has to be a way to ensure that we won’t download a list again unless it has actually changed.
>>>>>>>>> DNS has a builtin functionality called AXFR. It simply does the job for you. I was just wondering whether that was not being used.
>>>>>>>>> HTTPS is an option because that is simply what we use elsewhere, but extra functionality will have to be built for it.
>>>>>>>>> -Michael
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> Jon
>>>>>>>>>> On 2/13/25 3:34 PM, jon wrote:
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> Michael,
>>>>>>>>>>> I’ve read through your comments a few times and I ended up with many more questions.
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>> What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>>>>>>>>>
>>>>>>>>>>> To me the efforts to get new code accepted seem to have changed and it seemed easier in the past. In the past I made the Core Team aware via the Dev Mailing List and wrote a simple two or three paragraphs of "What is it? / What is the value? / Here is the code"
>>>>>>>>>>> So in an effort to move forward: How exactly is something presented to the Core Team?
>>>>>>>>>>> Is there an example of a recent effort that was presented that I can see as a sample? (This type of info can also be added to the Wiki)
>>>>>>>>>>> I understand you want it this way, but I don’t know what exactly is needed. Please be specific.
>>>>>>>>>>> Jon
>>>>>>>>>>> PS - I am not ignoring your other comments, I am just trying to move forward and keep things simple.
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>> On Feb 8, 2025, at 1:27 PM, Michael Tremer <michael.tremer@ipfire.org> wrote:
>>>>>>>>>>>> Hello Jon,
>>>>>>>>>>>> Thanks for your reply. And good that you are copying everyone into this conversation.
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>> On 8 Feb 2025, at 18:41, jon <jon.murphy@ipfire.org> wrote:
>>>>>>>>>>>>> Michael,
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort
>>>>>>>>>>>>>
>>>>>>>>>>>>> Yes, this has been a separate effort (a very public separate effort). Yes, as you pointed this out early on with the "proof-of-concept" and then my request for people to help test RPZ. Nothing was hidden.
>>>>>>>>>>>>> This was done because you (and maybe others) did not have the time and I wanted to help and because I needed assistance with RPZ. I tried my best to do this without bothering you.
>>>>>>>>>>>>
>>>>>>>>>>>> I don’t that it is accurate that nobody wanted to help on this. The list was always open - although not every email has been replied to swiftly it is also your responsibility to raise a question again if it was missed. People here have open ears.
>>>>>>>>>>>> It was also stated on this very list on in our documentation that working on something without involving the core team is a risky undertaking. Of course IPFire is free software and so everyone is free to fork if they wish to do so.
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>>> and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls.
>>>>>>>>>>>>>
>>>>>>>>>>>>> You were aware many steps along the way. See your email on July 28, 2024, August 15, 2024, September 30, 2024, December 23, 2024, and January 16. My attempts to get the team involved were met with "things are busy" and sometimes silence. (Yes, I get it, people are busy.)
>>>>>>>>>>>>> You and Adolf, Leo, Erik and Bernhard have been aware since the beginning. You mention you were aware of the "proof-of-concept". If you include those beginning posts, since Sep 2023.
>>>>>>>>>>>>
>>>>>>>>>>>> Yes, I am aware of a proof-of-concept that I have been running myself for a long time. I am also aware of the efforts that you have been taking.
>>>>>>>>>>>> Yet I don’t think there has ever been any joint effort, or am I seeing that wrong?
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>>> This has not been discussed . . . on our calls.
>>>>>>>>>>>>>
>>>>>>>>>>>>> On the July 28th you stated:
>>>>>>>>>>>>> "We have talked about RPZ many times on the monthly call since the URL filter feature is falling more and more out of fashion. I think there is also many posts about this on the forum."
>>>>>>>>>>>>> Please don’t insult me again by stating "you know what I mean".
>>>>>>>>>>>>> And it has been discussed but not documented in the Monthly Meeting notes.
>>>>>>>>>>>>
>>>>>>>>>>>> I am not at all insulting you. I don’t want to take this down to a personal level at all. This is a public mailing list and people who read this don’t need to listen to an argument we are having. They are here for the tech inside IPFire.
>>>>>>>>>>>> When I wrote that it has not been discussed that does not mean that we have not been touching on the topic. We have been talking about lots of things on the calls, the weather, politics, how our pets are. None of that makes it to the logs. What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>>> Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation.
>>>>>>>>>>>>>
>>>>>>>>>>>>> Regular conversation on the Dev Mailing list is many times met with silence. I get it, people are busy.
>>>>>>>>>>>>> And regular two-way conversation doesn’t happen on the list. At least not with me. I’d be happy to point out the posts that were met with silence.
>>>>>>>>>>>>> Again, I get it, people are busy.
>>>>>>>>>>>>
>>>>>>>>>>>> And you think my emails are not being met with silence? This has nothing to do with this specific topic. This has something to do with how occupied people are and how engaged they are on certain topics. Not everyone is involved in all the things and simply will ignore emails simply based on their subject line.
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>> But the "dip here to the list" were my attempts to get a conversation started. As I said, many time met with silence.
>>>>>>>>>>>>> The only place I was not met with silence was on the Community. You have a great group of people in the Community. It is a shame you don’t want to have others help. It would reduce your workload.
>>>>>>>>>>>>
>>>>>>>>>>>> You should stop making statements that are not true. Who doesn’t want anyone to help?
>>>>>>>>>>>> Not having this conversation on a Saturday evening would reduce my workload. At least it would free up time for something else. Helping with the things that are already on the go would reduce the workload of the entire team. Starting one thing at a time and finishing it is a lot better to manage than starting a hundred things and not even finish one. I can tell you that I already have a hundred things on the go.
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>>> Therefore, what am I supposed to do with this email?
>>>>>>>>>>>>>
>>>>>>>>>>>>> To me it is beyond obvious…
>>>>>>>>>>>>> If it isn’t what you want, then guide me with how to do this the correct way. And be specific. I am trying to help. I am trying to make things better. I am trying to do things the right way.
>>>>>>>>>>>>
>>>>>>>>>>>> To me it isn’t. This is yet another project that has been dumped to the list like so many before and later on everyone has left to have the team deal with the rest.
>>>>>>>>>>>> It is a huge patch set. You explained what the vision is, but that is about it. There is no chance this will continue if this disagreement isn’t solved first. I didn’t even look at the code.
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>>> I don’t want to merge code that I don’t agree with.
>>>>>>>>>>>>>
>>>>>>>>>>>>> I asked multiple times if you "agreed with the concept" and again, met with silence. Yes I get it, people are busy.
>>>>>>>>>>>>
>>>>>>>>>>>> Having support for RPZ? Yes, it was definitely on the roadmap. That I agree with.
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>>> So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>>>>>>>>>
>>>>>>>>>>>>> You mentioned this a in the past, but for some reason you do not disclose what I dismissed. Why do you continue to make this harder, wouldn’t it not be easier to tell me what I have dismissed?
>>>>>>>>>>>>> I have sent multiple emails trying to answer your concerns and comments. On July 28, Aug 14, Aug 22, Aug 23, Sep 30, etc.
>>>>>>>>>>>>> I’ve gone through all of the questions you asked and I cannot find a "dismissed" item.
>>>>>>>>>>>>
>>>>>>>>>>>> Maybe I need to be *more clear*. I feel humoured by this.
>>>>>>>>>>>> It is late on a Saturday and I want my dinner soon, but certainly I have stated that this should never be an add-on considering it is supposed to replace URL Filter. We should never allow people to add their own sources. I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>>>>>>>>>
>>>>>>>>>>>>> The maintainers of Unbound and/or RPZ?
>>>>>>>>>>>>> The maintainers of Hagezi list, the threatfox list, the urlhaus list, etc.?
>>>>>>>>>>>>> What else? The maintainers or the RPZ scripts? That is me. Let’s talk!
>>>>>>>>>>>>
>>>>>>>>>>>> You. I don’t care much about the providers of the lists.
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>> See, this is where it gets confusing. There are hundreds of open source packages as part of IPFire. Pick the last five years of items added to the IPFire build. You're telling me you have "constructive conversation with the maintainers" of all of the added packages?
>>>>>>>>>>>>
>>>>>>>>>>>> They publish their software and they don’t care whether I am pulling it or not. They publish it with the commitment to maintain it - sometimes for better and sometimes for worse.
>>>>>>>>>>>> You care about me pulling your code and I don’t know whether you would commit to maintain this.
>>>>>>>>>>>> These two are very different cases.
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>> Pick the IP Blocklists list (i.e., 3CORESEC, ABUSECH, DSHIELD, SPAMHAUS, etc.) or the Suricata lists (i.e.,Emergingthreats.net <http://emergingthreats.net/>,Abuse.ch <http://abuse.ch/>, etc.). So you’ve have "constructive conversation with the maintainers"?
>>>>>>>>>>>>
>>>>>>>>>>>> Yes, occasionally I have phone calls with a few of these providers.
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>>>>>>>>>
>>>>>>>>>>>>> Ha! Yes a surprise. In the beginning you seemed interested as IPFire needed a replacement for URL Filter. You asked good questions about the lists picked, asked for the value to the users, etc. And I answered the best I could.
>>>>>>>>>>>>> You even asked: “Why is this realised as an add-on and not part of the core system?” from your Jul 28, 2024 email.
>>>>>>>>>>>>
>>>>>>>>>>>> Ah, so, why is the patch creating an add-on? Not that I am saying that what I say is law, but it has not been challenged either. If my input is being ignored, why should I put this to the top of my list of priorities? I am not disappointed about this, just trying to be very good with my time.
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>> And on January 16, 2025 I wrote a message looking for help. And you were kind to respond quickly. So in three weeks time, since the kind response, something has changed. You went from supportive to "this".
>>>>>>>>>>>>> So yes, I am surprised.
>>>>>>>>>>>>
>>>>>>>>>>>> Well, maybe I should not have replied to that email. It was clear that you were on some path that was not right, but you were not interested before in finding the right path from the beginning.
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>>>>>>>>>
>>>>>>>>>>>>> Be more specific, what has to change? What exactly did I dismiss?
>>>>>>>>>>>>
>>>>>>>>>>>> Dismissal is just my assumption. I don’t know what you actually did with my feedback. I can only see the end product that does not seem contain much of it. Repeatedly I have been pointing out that we should think before we build. I am sure a lot of hours have now gone into some code that simply does not satisfy me. And I am not not talking about the code itself, what it does is what I don’t think is right for us.
>>>>>>>>>>>> The process is very clear for me that we should first of all think whether we want a certain feature now. Then there should be a clear roadmap for everyone to follow; tasks can be split-up as we go and hopefully then have something that is maintainable, interesting for our users and even would do us proud. This is how this should work.
>>>>>>>>>>>> So, what has to change? I don’t think with shouting at each other, throwing patches around and making me generally unhappy is a good start.
>>>>>>>>>>>> -Michael
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>> Jon
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> On Feb 6, 2025, at 2:13 PM, Michael Tremer <michael.tremer@ipfire.org> wrote:
>>>>>>>>>>>>>> Hello Jon,
>>>>>>>>>>>>>> Well, here we are again with another patch regarding this feature.
>>>>>>>>>>>>>> I cannot quite see from your email what the question is, but if this is a request to have this merged into IPFire, I am once again sorry to disappoint you.
>>>>>>>>>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls. Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation. Therefore, what am I supposed to do with this email?
>>>>>>>>>>>>>> I don’t want to merge code that I don’t agree with. So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>>>>>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>>>>>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>>>>>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>>>>>>>>>> All the best,
>>>>>>>>>>>>>> -Michael
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> On 6 Feb 2025, at 16:35, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>>>>>>>>> What is it?
>>>>>>>>>>>>>>> Response Policy Zone (RPZ) is a mechanism to define local policies in a
>>>>>>>>>>>>>>> standardized way and load those policies from external sources.
>>>>>>>>>>>>>>> Bottom line: RPZ allows admins to easily block access to websites via DNS lookup.
>>>>>>>>>>>>>>> RPZ can block websites via categories. Examples include: fake websites, annoying
>>>>>>>>>>>>>>> pop-up ads, newly registered domains, DoH bypass sites, bad "host" services,
>>>>>>>>>>>>>>> maliscious top level domains (e.g., *.zip, *.mov), piracy, gambling, pornography,
>>>>>>>>>>>>>>> and more. RPZ lists come from various RPZ providers and their available
>>>>>>>>>>>>>>> catagories.
>>>>>>>>>>>>>>> This RPZ add-on enables the RPZ functionality by adding a couple lines in a
>>>>>>>>>>>>>>> configuration file. This add-on simply adds configuration files and adds
>>>>>>>>>>>>>>> scripts (config, metrics and sleep) to make RPZ easier for the admin to use.
>>>>>>>>>>>>>>> The RPZ scripts include additional languages: German, Spanish, French, Turkish,
>>>>>>>>>>>>>>> and Italian.
>>>>>>>>>>>>>>> RPZ itself was release in 2010 and has been part of the IPFire build since ~2015.
>>>>>>>>>>>>>>> Why is it needed? What is its value?
>>>>>>>>>>>>>>> - The RPZ concept places this filtering into IPFire, our internet access
>>>>>>>>>>>>>>> gateway, which is (should be) solely used as DNS source of the internal network.
>>>>>>>>>>>>>>> - As most sites use HTTPS it makes it difficult to filter traffic with URL
>>>>>>>>>>>>>>> Filter without also properly configuring conventional (non-transparent)
>>>>>>>>>>>>>>> mode on the proxy. RPZ is a nice replacement for the URL Filter.
>>>>>>>>>>>>>>> - No need to install and maintain an additional device like PiHole or AdBlock
>>>>>>>>>>>>>>> browser extensions on multiple user devices.
>>>>>>>>>>>>>>> - This is an additional layer of protection for users. Less worry someone will
>>>>>>>>>>>>>>> click on something that gets them into trouble. And, saying this with emphasis,
>>>>>>>>>>>>>>> the ability to do it in one place!
>>>>>>>>>>>>>>> - Blocked sites save on unneeded traffic and can lessen the threat of malware
>>>>>>>>>>>>>>> in advertisements
>>>>>>>>>>>>>>> - Logging allows the admin to see the site blocked and take actions
>>>>>>>>>>>>>>> - RPZ will be used at the home, home-office (work from home), schools,
>>>>>>>>>>>>>>> ministerial, and at the office. Device counts are small (2-6) to medium (~80)
>>>>>>>>>>>>>>> to mediam-large (200+).
>>>>>>>>>>>>>>> - RPZ can block ads, popups, phishing, scammers, spyware, malware, annoying
>>>>>>>>>>>>>>> popups, NSFW links, DOH servers, and the usual internet trash.
>>>>>>>>>>>>>>> ------------------------------
>>>>>>>>>>>>>>> Change Log for RPZ add-on
>>>>>>>>>>>>>>> rpz-1.0.0-18 on 2025-02-05
>>>>>>>>>>>>>>> - Build for approval & release as IPFire add-on
>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>> rpz-beta-0.1.18-18.ipfire on 2025-02-01
>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>> - new feature: added a mod key to force a unbound restart
>>>>>>>>>>>>>>> rpz-config and rpz-make:
>>>>>>>>>>>>>>> - new feature: added action for unbound restart `rpz-config unbound-restart`
>>>>>>>>>>>>>>> rpz-metrics:
>>>>>>>>>>>>>>> - simple reformatting
>>>>>>>>>>>>>>> - rename far right column from "last update" to "last download"
>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>> rpz-beta-0.1.17-17.ipfire on 2024-12-09
>>>>>>>>>>>>>>> rpz-make
>>>>>>>>>>>>>>> - bug fix: corrected validation regex for wildcards like: `*.domain.com`
>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>> rpz-beta-0.1.16-16.ipfire on 2024-11-18
>>>>>>>>>>>>>>> rpz-make
>>>>>>>>>>>>>>> - new feature: updated validation regex
>>>>>>>>>>>>>>> - bug fix: moved validation to beginning of process. Now we validate before
>>>>>>>>>>>>>>> creating config files.
>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>> - new feature: use CSS color variables of the main ipfire theme
>>>>>>>>>>>>>>> - bug fix: empty zonefile remarks were stored as “undef” and caused a warning
>>>>>>>>>>>>>>> - bug fix: HTML textarea removes the first empty line in a custom list
>>>>>>>>>>>>>>> - thank you Leo!
>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>> rpz-beta-0.1.15-15.ipfire on 2024-11-04
>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>> - new feature: added new language file for Turkish (thank you Peppe)
>>>>>>>>>>>>>>> rpz-make
>>>>>>>>>>>>>>> - bug fix: corrected empty allow/block list issue. An empty allow/block list
>>>>>>>>>>>>>>> will now remove contents of allow/block.rpz files and remove unneeded
>>>>>>>>>>>>>>> allow/block.conf file. (thank you iptom)
>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>> rpz-beta-0.1.14-14.ipfire on 2024-10-29
>>>>>>>>>>>>>>> rpz-config:
>>>>>>>>>>>>>>> - bug fix: correct missing rpz extension. `rpz-config list` displayed URL
>>>>>>>>>>>>>>> incorrectly (thank you Bernhard)
>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>> - bug fix: remove extra `"` in language files (thank you Bernhard)
>>>>>>>>>>>>>>> - new feature: slightly dim "apply" button when not enabled
>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>> rpz-beta-0.1.13-13.ipfire on 2024-10-27
>>>>>>>>>>>>>>> - skipped
>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>> rpz-beta-0.1.12-12.ipfire on 2024-10-21
>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>> - new feature: added new language file for French (thank you gw-ipfire)
>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>> rpz-beta-0.1.11-11.ipfire on 2024-10-18
>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>> - new feature: added new language file for Italian (thank you umberto)
>>>>>>>>>>>>>>> - new feature: added new language file for Spanish (thank you Roberto)
>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>> rpz-beta-0.1.10-10.ipfire on 2024-10-15
>>>>>>>>>>>>>>> rpz-make:
>>>>>>>>>>>>>>> - bug fix: corrected validation error for a custom list entry (thank you siosios)
>>>>>>>>>>>>>>> - e.g., `*.cloudflare-dns.com`
>>>>>>>>>>>>>>> install.sh:
>>>>>>>>>>>>>>> - bug fix: add chown to correct user created files
>>>>>>>>>>>>>>> update.sh:
>>>>>>>>>>>>>>> - bug fix: add chown to correct user created files (thank you siosios)
>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>> rpz-beta-0.1.9-9.ipfire on 2024-10-08
>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>> - new feature: added new language file for German (thank you Leo)
>>>>>>>>>>>>>>> - bug fix: add missing "rpz exitcode 110"
>>>>>>>>>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>> rpz-beta-0.1.8-8.ipfire on 2024-10-04
>>>>>>>>>>>>>>> - skipped
>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>> rpz-beta-0.1.7-7.ipfire on 2024-10-03
>>>>>>>>>>>>>>> All:
>>>>>>>>>>>>>>> - new feature: includes beta version numbers for pakfire package,
>>>>>>>>>>>>>>> instead of only `rpz-1.0.0-1.ipfire`, for each release.
>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>> - new feature: added new WebGUI at `rpz.cgi`
>>>>>>>>>>>>>>> - a BIG thank you to Leo Hofmann for all of his work creating the webgui!!
>>>>>>>>>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>>>>>>>>> rpz-make:
>>>>>>>>>>>>>>> - new feature: validate entries in allowlist and blocklist
>>>>>>>>>>>>>>> - new feature: add "no-reload" option for WebGUI
>>>>>>>>>>>>>>> rpz-metrics:
>>>>>>>>>>>>>>> - new feature: info can be sorted by name, by hit count, by line count, by
>>>>>>>>>>>>>>> "enabled" list or all lists
>>>>>>>>>>>>>>> backups:
>>>>>>>>>>>>>>> - bug fix: include all files in `/var/ipfire/dns/rpz` directory in backup
>>>>>>>>>>>>>>> update.sh:
>>>>>>>>>>>>>>> - bug fix: corrected ownership for `/var/ipfire/dns/rpz` directory during an
>>>>>>>>>>>>>>> update
>>>>>>>>>>>>>>> Build:
>>>>>>>>>>>>>>> - bug fix: `block.rpz.conf` and `block.rpz` from build. Files to be created
>>>>>>>>>>>>>>> by `rpz-make`
>>>>>>>>>>>>>>> WebGUI and German language file
>>>>>>>>>>>>>>> Contribution-by: Leo-Andres Hofmann <hofmann@leo-andres.de>
>>>>>>>>>>>>>>> Spanish language file
>>>>>>>>>>>>>>> Contribution-by: Roberto Peña
>>>>>>>>>>>>>>> Italian language file
>>>>>>>>>>>>>>> Contribution-by: Umberto Parma
>>>>>>>>>>>>>>> French language file
>>>>>>>>>>>>>>> Contribution-by: gw-ipfire
>>>>>>>>>>>>>>> Turkish language file
>>>>>>>>>>>>>>> Contribution-by: Peppe Tech
>>>>>>>>>>>>>>> Contribution-by: Bernhard Bitsch <bbitsch@ipfire.org>
>>>>>>>>>>>>>>> Contribution-by: Erik Kapfer <erik.kapfer@ipfire.org>
>>>>>>>>>>>>>>> Signed-off-by: Jon Murphy <jon.murphy@ipfire.org
>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>> config/backup/includes/rpz | 4 +
>>>>>>>>>>>>>>> config/cfgroot/manualpages | 1 +
>>>>>>>>>>>>>>> config/menu/EX-rpz.menu | 6 +
>>>>>>>>>>>>>>> config/rootfiles/common/configroot | 1 +
>>>>>>>>>>>>>>> config/rootfiles/common/web-user-interface | 1 +
>>>>>>>>>>>>>>> config/rootfiles/packages/rpz | 20 +
>>>>>>>>>>>>>>> config/rpz/00-rpz.conf | 10 +
>>>>>>>>>>>>>>> config/rpz/rpz-config | 130 +++
>>>>>>>>>>>>>>> config/rpz/rpz-functions | 85 ++
>>>>>>>>>>>>>>> config/rpz/rpz-make | 203 +++++
>>>>>>>>>>>>>>> config/rpz/rpz-metrics | 170 ++++
>>>>>>>>>>>>>>> config/rpz/rpz-sleep | 58 ++
>>>>>>>>>>>>>>> config/rpz/rpz.de.pl | 30 +
>>>>>>>>>>>>>>> config/rpz/rpz.en.pl | 30 +
>>>>>>>>>>>>>>> config/rpz/rpz.es.pl | 30 +
>>>>>>>>>>>>>>> config/rpz/rpz.fr.pl | 30 +
>>>>>>>>>>>>>>> config/rpz/rpz.it.pl | 30 +
>>>>>>>>>>>>>>> config/rpz/rpz.tr.pl | 30 +
>>>>>>>>>>>>>>> html/cgi-bin/rpz.cgi | 923 +++++++++++++++++++++
>>>>>>>>>>>>>>> lfs/rpz | 96 +++
>>>>>>>>>>>>>>> make.sh | 3 +-
>>>>>>>>>>>>>>> src/paks/rpz/install.sh | 36 +
>>>>>>>>>>>>>>> src/paks/rpz/uninstall.sh | 38 +
>>>>>>>>>>>>>>> src/paks/rpz/update.sh | 52 ++
>>>>>>>>>>>>>>> 24 files changed, 2016 insertions(+), 1 deletion(-)
>>>>>>>>>>>>>>> create mode 100644 config/backup/includes/rpz
>>>>>>>>>>>>>>> create mode 100644 config/menu/EX-rpz.menu
>>>>>>>>>>>>>>> create mode 100644 config/rootfiles/packages/rpz
>>>>>>>>>>>>>>> create mode 100644 config/rpz/00-rpz.conf
>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz-config
>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz-functions
>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz-make
>>>>>>>>>>>>>>> create mode 100755 config/rpz/rpz-metrics
>>>>>>>>>>>>>>> create mode 100755 config/rpz/rpz-sleep
>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.de.pl
>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.en.pl
>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.es.pl
>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.fr.pl
>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.it.pl
>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.tr.pl
>>>>>>>>>>>>>>> create mode 100644 html/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>> create mode 100644 lfs/rpz
>>>>>>>>>>>>>>> create mode 100644 src/paks/rpz/install.sh
>>>>>>>>>>>>>>> create mode 100644 src/paks/rpz/uninstall.sh
>>>>>>>>>>>>>>> create mode 100644 src/paks/rpz/update.sh
>>>>>>>>>>>>>>> diff --git a/config/backup/includes/rpz b/config/backup/includes/rpz
>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>> index 000000000..36513e494
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/config/backup/includes/rpz
>>>>>>>>>>>>>>> @@ -0,0 +1,4 @@
>>>>>>>>>>>>>>> +/var/ipfire/dns/rpz/*
>>>>>>>>>>>>>>> +/etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>> +/etc/unbound/zonefiles/block.rpz
>>>>>>>>>>>>>>> +/etc/unbound/local.d/*rpz.conf
>>>>>>>>>>>>>>> diff --git a/config/cfgroot/manualpages b/config/cfgroot/manualpages
>>>>>>>>>>>>>>> index 1f7e01efc..d3a48c633 100644
>>>>>>>>>>>>>>> --- a/config/cfgroot/manualpages
>>>>>>>>>>>>>>> +++ b/config/cfgroot/manualpages
>>>>>>>>>>>>>>> @@ -70,6 +70,7 @@ pakfire.cgi=configuration/ipfire/pakfire
>>>>>>>>>>>>>>> wlanap.cgi=addons/wireless
>>>>>>>>>>>>>>> tor.cgi=addons/tor
>>>>>>>>>>>>>>> samba.cgi=addons/samba
>>>>>>>>>>>>>>> +rpz.cgi=addons/rpz
>>>>>>>>>>>>>>> # Logs menu
>>>>>>>>>>>>>>> logs.cgi/summary.dat=configuration/logs/summary
>>>>>>>>>>>>>>> diff --git a/config/menu/EX-rpz.menu b/config/menu/EX-rpz.menu
>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>> index 000000000..2f4daf410
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/config/menu/EX-rpz.menu
>>>>>>>>>>>>>>> @@ -0,0 +1,6 @@
>>>>>>>>>>>>>>> +$subipfire->{'20.rpz'} = {
>>>>>>>>>>>>>>> + 'caption' => $Lang::tr{'rpz'},
>>>>>>>>>>>>>>> + 'uri' => '/cgi-bin/rpz.cgi',
>>>>>>>>>>>>>>> + 'title' => "RPZ",
>>>>>>>>>>>>>>> + 'enabled' => 1,
>>>>>>>>>>>>>>> +};
>>>>>>>>>>>>>>> diff --git a/config/rootfiles/common/configroot b/config/rootfiles/common/configroot
>>>>>>>>>>>>>>> index 9839eee45..b30d6aae4 100644
>>>>>>>>>>>>>>> --- a/config/rootfiles/common/configroot
>>>>>>>>>>>>>>> +++ b/config/rootfiles/common/configroot
>>>>>>>>>>>>>>> @@ -120,6 +120,7 @@ var/ipfire/menu.d/70-log.menu
>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-apcupsd.menu
>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-guardian.menu
>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-mympd.menu
>>>>>>>>>>>>>>> +#var/ipfire/menu.d/EX-rpz.menu
>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-samba.menu
>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-tor.menu
>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-transmission.menu
>>>>>>>>>>>>>>> diff --git a/config/rootfiles/common/web-user-interface b/config/rootfiles/common/web-user-interface
>>>>>>>>>>>>>>> index 816241dae..e00464076 100644
>>>>>>>>>>>>>>> --- a/config/rootfiles/common/web-user-interface
>>>>>>>>>>>>>>> +++ b/config/rootfiles/common/web-user-interface
>>>>>>>>>>>>>>> @@ -69,6 +69,7 @@ srv/web/ipfire/cgi-bin/proxy.cgi
>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/qos.cgi
>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/remote.cgi
>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/routing.cgi
>>>>>>>>>>>>>>> +#srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>> #srv/web/ipfire/cgi-bin/samba.cgi
>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/services.cgi
>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/shutdown.cgi
>>>>>>>>>>>>>>> diff --git a/config/rootfiles/packages/rpz b/config/rootfiles/packages/rpz
>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>> index 000000000..1c8663049
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/config/rootfiles/packages/rpz
>>>>>>>>>>>>>>> @@ -0,0 +1,20 @@
>>>>>>>>>>>>>>> +etc/unbound/local.d/00-rpz.conf
>>>>>>>>>>>>>>> +etc/unbound/zonefiles
>>>>>>>>>>>>>>> +etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>> +usr/sbin/rpz-config
>>>>>>>>>>>>>>> +usr/sbin/rpz-functions
>>>>>>>>>>>>>>> +usr/sbin/rpz-make
>>>>>>>>>>>>>>> +usr/sbin/rpz-metrics
>>>>>>>>>>>>>>> +usr/sbin/rpz-sleep
>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.de.pl
>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.en.pl
>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.es.pl
>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.fr.pl
>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.it.pl
>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.tr.pl
>>>>>>>>>>>>>>> +var/ipfire/backup/addons/includes/rpz
>>>>>>>>>>>>>>> +var/ipfire/dns/rpz
>>>>>>>>>>>>>>> +var/ipfire/dns/rpz/allowlist
>>>>>>>>>>>>>>> +var/ipfire/dns/rpz/blocklist
>>>>>>>>>>>>>>> +var/ipfire/menu.d/EX-rpz.menu
>>>>>>>>>>>>>>> +srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>> diff --git a/config/rpz/00-rpz.conf b/config/rpz/00-rpz.conf
>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>> index 000000000..f005a4f2e
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/config/rpz/00-rpz.conf
>>>>>>>>>>>>>>> @@ -0,0 +1,10 @@
>>>>>>>>>>>>>>> +server:
>>>>>>>>>>>>>>> + module-config: "respip validator iterator"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +rpz:
>>>>>>>>>>>>>>> + name: allow.rpz
>>>>>>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>> + rpz-action-override: passthru
>>>>>>>>>>>>>>> + rpz-log: yes
>>>>>>>>>>>>>>> + rpz-log-name: allow
>>>>>>>>>>>>>>> + rpz-signal-nxdomain-ra: yes
>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-config b/config/rpz/rpz-config
>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>> index 000000000..c72d50f9b
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-config
>>>>>>>>>>>>>>> @@ -0,0 +1,130 @@
>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +version="2025-01-11 - v44"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +tagName="unbound"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +rpzAction="${1}" # input RPZ action
>>>>>>>>>>>>>>> +rpzName="${2}" # input RPZ name
>>>>>>>>>>>>>>> +rpzURL="${3}" # input RPZ URL
>>>>>>>>>>>>>>> +rpzOption1="${4}" # input RPZ option #1
>>>>>>>>>>>>>>> +rpzOption2="${5}" # input RPZ option #2
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +rpzConfig="/etc/unbound/local.d/${rpzName}.rpz.conf" # output zone conf file
>>>>>>>>>>>>>>> +rpzFile="/etc/unbound/zonefiles/${rpzName}.rpz" # output for RPZ file
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>>>>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>>>>>>> + case "$1" in
>>>>>>>>>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>>>>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>>>>>>>>>> + esac
>>>>>>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +case "${rpzAction}" in
>>>>>>>>>>>>>>> + # add new rpz list
>>>>>>>>>>>>>>> + add )
>>>>>>>>>>>>>>> + check_name "${rpzName}" # is this a valid name?
>>>>>>>>>>>>>>> + # does this config already exist? If yes, then exit
>>>>>>>>>>>>>>> + if [[ -f "${rpzConfig}" ]] ; then
>>>>>>>>>>>>>>> + msg_log "error: rpz: duplicate - ${rpzConfig} already exists. exit"
>>>>>>>>>>>>>>> + exit 104
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # is this a valid URL?
>>>>>>>>>>>>>>> + regex='^https://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
>>>>>>>>>>>>>>> + if ! [[ "${rpzURL}" =~ $regex ]] ; then
>>>>>>>>>>>>>>> + msg_log "error: rpz: the URL is not valid: \"${rpzURL}\". exit."
>>>>>>>>>>>>>>> + exit 105
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # create the zone config file
>>>>>>>>>>>>>>> + {
>>>>>>>>>>>>>>> + echo "rpz:"
>>>>>>>>>>>>>>> + echo " name: ${rpzName}.rpz"
>>>>>>>>>>>>>>> + echo " zonefile: ${rpzFile}"
>>>>>>>>>>>>>>> + echo " url: ${rpzURL}"
>>>>>>>>>>>>>>> + echo " rpz-action-override: nxdomain"
>>>>>>>>>>>>>>> + echo " rpz-log: ${rpzLog}"
>>>>>>>>>>>>>>> + echo " rpz-log-name: ${rpzName}"
>>>>>>>>>>>>>>> + echo " rpz-signal-nxdomain-ra: yes"
>>>>>>>>>>>>>>> + } > "${rpzConfig}"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # set-up zonefile
>>>>>>>>>>>>>>> + # create an empty rpz file if it does not exist
>>>>>>>>>>>>>>> + if [[ ! -f "${rpzFile}" ]] ; then
>>>>>>>>>>>>>>> + touch "${rpzFile}"
>>>>>>>>>>>>>>> + # unbound requires these settings for rpz files
>>>>>>>>>>>>>>> + set_permissions "${rpzFile}" "${rpzConfig}"
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # trash config file & rpz file
>>>>>>>>>>>>>>> + remove )
>>>>>>>>>>>>>>> + if ! [[ -f "${rpzConfig}" ]] ; then
>>>>>>>>>>>>>>> + msg_log "error: rpz: cannot remove ${rpzConfig}, does not exist. exit"
>>>>>>>>>>>>>>> + exit 106
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + msg_log "info: rpz: remove config file & rpz file \"${rpzName}\""
>>>>>>>>>>>>>>> + rm "${rpzConfig}"
>>>>>>>>>>>>>>> + rm "${rpzFile}"
>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + reload )
>>>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + list )
>>>>>>>>>>>>>>> + awk -F':' '/^\s*name:/{ gsub(/[[:blank:]]|\.rpz/, "",$2) ; NAME=$2 } \
>>>>>>>>>>>>>>> + /^\s*url:/{ gsub(/[[:blank:]]/, "") ; print NAME"="$2":"$3} ' \
>>>>>>>>>>>>>>> + /etc/unbound/local.d/*rpz.conf
>>>>>>>>>>>>>>> + exit
>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + unbound-restart )
>>>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>>>> + unbound_restart
>>>>>>>>>>>>>>> + exit
>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + * )
>>>>>>>>>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>>>>>>>>>> + printf "Usage: $(basename "$0") <ACTION> <NAME> <URL> <OPTION> <OPTION>\n"
>>>>>>>>>>>>>>> + printf "Version: ${version}\n"
>>>>>>>>>>>>>>> + exit 108
>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +esac
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +exit
>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-functions b/config/rpz/rpz-functions
>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>> index 000000000..ace1d2690
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-functions
>>>>>>>>>>>>>>> @@ -0,0 +1,85 @@
>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +version="2024-12-10 - v02"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +msg_log () {
>>>>>>>>>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>>>>>>>>>> + if tty --silent ; then
>>>>>>>>>>>>>>> + echo "${tagName}:" "$*"
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# check for a valid name
>>>>>>>>>>>>>>> +check_name () {
>>>>>>>>>>>>>>> + local theName="${1}"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + regex='^[a-zA-Z0-9_]+$' # no dash or plus, alpha numeric only
>>>>>>>>>>>>>>> + regex1='^(allow|block)$' # allow and block are reserved NAMEs
>>>>>>>>>>>>>>> + if [[ ! "${theName}" =~ $regex ]] || [[ "${theName}" =~ $regex1 ]] ; then
>>>>>>>>>>>>>>> + msg_log "error: rpz: the NAME is not valid: \"${theName}\". exit."
>>>>>>>>>>>>>>> + exit 101
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +set_permissions () {
>>>>>>>>>>>>>>> + chown nobody:nobody "$@"
>>>>>>>>>>>>>>> + chmod 644 "$@"
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +check_unbound_conf () {
>>>>>>>>>>>>>>> + local thecheckConf="${1:-yes}" # check config default is yes
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # check the above config files
>>>>>>>>>>>>>>> + if [[ "${thecheckConf}" == yes ]] ; then
>>>>>>>>>>>>>>> + msg_log "info: rpz: check for errors with \"unbound-checkconf\""
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + if ! unbound-checkconf ; then
>>>>>>>>>>>>>>> + msg_log "error: rpz: unbound-checkconf found invalid configuration."
>>>>>>>>>>>>>>> + msg_log \
>>>>>>>>>>>>>>> + "error: rpz: In Terminal run the command \"unbound-checkconf\" for more information. exit."
>>>>>>>>>>>>>>> + exit 102
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +unbound_control_reload () {
>>>>>>>>>>>>>>> + local theReload="${1:-yes}" # reload default is yes
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + if [[ "${theReload}" == yes ]] ; then
>>>>>>>>>>>>>>> + # reload due to the changes
>>>>>>>>>>>>>>> + msg_log "info: rpz: run \"unbound-control reload\""
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + if ! unbound-control reload ; then
>>>>>>>>>>>>>>> + msg_log "error: rpz: unbound-control reload. exit."
>>>>>>>>>>>>>>> + exit 109
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +unbound_restart () {
>>>>>>>>>>>>>>> + # restart due to the changes
>>>>>>>>>>>>>>> + msg_log "info: rpz: run \"unbound restart\""
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + /usr/local/bin/unboundctrl restart
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-make b/config/rpz/rpz-make
>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>> index 000000000..927d55170
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-make
>>>>>>>>>>>>>>> @@ -0,0 +1,203 @@
>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +version="2025-01-11 - v14"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# create the config file for allow
>>>>>>>>>>>>>>> +make_allow_config () {
>>>>>>>>>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>>>>>>>>>> + local theConfig="/etc/unbound/local.d/00-rpz.conf" # output zone conf file
>>>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/allowlist" # input custom list of domains
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + msg_log "info: rpz: make config file \"00-rpz.conf\""
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + echo "server:
>>>>>>>>>>>>>>> + module-config: \"respip validator iterator\"" > "${theConfig}"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # does allow list exist?
>>>>>>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + echo "rpz:
>>>>>>>>>>>>>>> + name: allow.rpz
>>>>>>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>> + rpz-action-override: passthru
>>>>>>>>>>>>>>> + rpz-log: ${theLog}
>>>>>>>>>>>>>>> + rpz-log-name: allow
>>>>>>>>>>>>>>> + rpz-signal-nxdomain-ra: yes" >> "${theConfig}"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>>>>>>> + set_permissions "${theConfig}"
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# create the config file for block
>>>>>>>>>>>>>>> +make_block_config () {
>>>>>>>>>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>>>>>>>>>> + local theConfig="/etc/unbound/local.d/block.rpz.conf" # output zone conf file
>>>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/blocklist" # input custom list of domains
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + msg_log "info: rpz: make config file \"block.rpz.conf\""
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # does block list exist?
>>>>>>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + echo "rpz:
>>>>>>>>>>>>>>> + name: block.rpz
>>>>>>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/block.rpz
>>>>>>>>>>>>>>> + rpz-action-override: nxdomain
>>>>>>>>>>>>>>> + rpz-log: ${theLog}
>>>>>>>>>>>>>>> + rpz-log-name: block
>>>>>>>>>>>>>>> + rpz-signal-nxdomain-ra: yes" > "${theConfig}"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>>>>>>> + set_permissions "${theConfig}"
>>>>>>>>>>>>>>> + else
>>>>>>>>>>>>>>> + # no - trash the config file
>>>>>>>>>>>>>>> + rm --verbose /etc/unbound/local.d/block.rpz.conf
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# create an RPZ file for allow or block
>>>>>>>>>>>>>>> +make_rpz_file () {
>>>>>>>>>>>>>>> + local theName="${1}" # allow or block
>>>>>>>>>>>>>>> + local theAction='.' # the default is nxdomain or block
>>>>>>>>>>>>>>> + local actionList
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>>>>>>>>>> + local theZonefile="/etc/unbound/zonefiles/${theName}.rpz" # output file for RPZ
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # does a list exist?
>>>>>>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # for allow set to passthru
>>>>>>>>>>>>>>> + [[ "${theName}" == allow ]] && theAction='rpz-passthru.'
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # drop any extra "blanks" and add "CNAME <RPZ action>." to each line
>>>>>>>>>>>>>>> + actionList=$( awk '{$1=$1};1' "${theList}" |
>>>>>>>>>>>>>>> + sed "/^[^;].*[[:alnum:]]/ s|$| CNAME ${theAction}|" )
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + msg_log "info: rpz: create zonefile for \"${theName}list\""
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +echo "; Name: ${theName} list
>>>>>>>>>>>>>>> +; Last modified: $(date "+%Y-%m-%d at %H.%M.%S %Z")
>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>> +; domains with actions list
>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>> +${actionList}" > "${theZonefile}"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>>>>>>> + set_permissions "${theZonefile}"
>>>>>>>>>>>>>>> + # set-up allow/block list files
>>>>>>>>>>>>>>> + set_permissions "${theList}"
>>>>>>>>>>>>>>> + else
>>>>>>>>>>>>>>> + msg_log "info: rpz: the ${theList} is empty."
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + rm --verbose "${theZonefile}" # trash the RPZ file
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# check if allow/block list is valid
>>>>>>>>>>>>>>> +validate_list () {
>>>>>>>>>>>>>>> + local theName="${1}" # allow or block
>>>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # remove good:
>>>>>>>>>>>>>>> + # - properly formated domain names with or without leading wildcard
>>>>>>>>>>>>>>> + # - properly formated top level domain (TLD) names with wildcard
>>>>>>>>>>>>>>> + # - blank lines and comment lines
>>>>>>>>>>>>>>> + # remaining lines are considered "bad"
>>>>>>>>>>>>>>> + bad_lines=$( sed --regexp-extended \
>>>>>>>>>>>>>>> + '/^(\*\.)?([a-zA-Z0-9](([a-zA-Z0-9\-]){0,61}[a-zA-Z0-9])?\.)+([a-zA-Z]{2,}|xn--[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])$/d ;
>>>>>>>>>>>>>>> + /^(\*\.)([a-z]{2,61}|xn--[a-z0-9]{1,60})$/d ;
>>>>>>>>>>>>>>> + /^$/d ; /^;/d' "${theList}" )
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + if [[ ! -z "${bad_lines}" ]] ; then
>>>>>>>>>>>>>>> + msg_log "error: rpz: invalid line(s) in ${theList}."
>>>>>>>>>>>>>>> + printf "%s\n" "bad line(s): ${bad_lines}"
>>>>>>>>>>>>>>> + exit 110
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +tagName="unbound"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +rpzName="${1}" # input RPZ name
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>>>>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>>>>>>> + case "$1" in
>>>>>>>>>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>>>>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>>>>>>>>>> + esac
>>>>>>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +case "${rpzName}" in
>>>>>>>>>>>>>>> + # make a new allow or block rpz file
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + allow )
>>>>>>>>>>>>>>> + validate_list 'allow' # is the allowlist valid?
>>>>>>>>>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>>>>>>>>>> + make_rpz_file 'allow'
>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + allowblock )
>>>>>>>>>>>>>>> + validate_list 'allow' # is the list valid?
>>>>>>>>>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>>>>>>>>>> + make_rpz_file 'allow'
>>>>>>>>>>>>>>> + ;&
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + block )
>>>>>>>>>>>>>>> + validate_list 'block' # is the blocklist valid?
>>>>>>>>>>>>>>> + make_block_config "${rpzLog}"
>>>>>>>>>>>>>>> + make_rpz_file 'block'
>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + reload )
>>>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + unbound-restart )
>>>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>>>> + unbound_restart
>>>>>>>>>>>>>>> + exit
>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + * )
>>>>>>>>>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>>>>>>>>>> + printf "Usage: $(basename "$0") <NAME> <OPTION> <OPTION>\n"
>>>>>>>>>>>>>>> + printf "Version: ${version}\n"
>>>>>>>>>>>>>>> + exit 108
>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>> +esac
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +exit
>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-metrics b/config/rpz/rpz-metrics
>>>>>>>>>>>>>>> new file mode 100755
>>>>>>>>>>>>>>> index 000000000..4d43e1629
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-metrics
>>>>>>>>>>>>>>> @@ -0,0 +1,170 @@
>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +version="2025-01-20 - v25"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +weeks="2" # default to two message logs
>>>>>>>>>>>>>>> +sortBy="name" # default "by name"
>>>>>>>>>>>>>>> +rpzActive="enabled" # default "enabled only"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>>>>>>> + case "$1" in
>>>>>>>>>>>>>>> + --by-names | --by-name | name ) sortBy="name" ;;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + --by-hits | --by-hit | hits | hit ) sortBy="hit" ;;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + --by-lines | --by-line | lines | line ) sortBy="line" ;;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + --by-effect ) sortBy="effect" ;;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + --enabled-only ) rpzActive="enabled" ;;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + --active-all | --all | all ) rpzActive="all" ;;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + [0-9] | [0-9][0-9] ) weeks=$1 ;;
>>>>>>>>>>>>>>> + esac
>>>>>>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# get the list of message logs for N weeks
>>>>>>>>>>>>>>> +messageLogs=$( find /var/log/messages* -type f | sort --version-sort |
>>>>>>>>>>>>>>> + head -"${weeks}" )
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# get the list of RPZ names & counts from the message log(s)
>>>>>>>>>>>>>>> +rpzNameCount=$( for logf in ${messageLogs} ; do
>>>>>>>>>>>>>>> + zgrep --text --fixed-strings 'info: rpz: applied' "${logf}" |
>>>>>>>>>>>>>>> + awk '$10 ~ /\[\w*]/ { print $10 }' ;
>>>>>>>>>>>>>>> + done | sort | uniq --count )
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# flip results and remove brackets `[` and `]`
>>>>>>>>>>>>>>> +rpzNameCount=$( echo "${rpzNameCount}" |
>>>>>>>>>>>>>>> + awk '{ print $2, $1 }' |
>>>>>>>>>>>>>>> + sed --regexp-extended 's|^\[(.*)\]|\1|' )
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# grab only names
>>>>>>>>>>>>>>> +rpzNames=$( echo "${rpzNameCount}" | awk '{ print $1 }' )
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# get list of RPZ files
>>>>>>>>>>>>>>> +rpzFileList=$( find /etc/unbound/zonefiles -type f -iname "*.rpz" )
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# get basename of those files
>>>>>>>>>>>>>>> +rpzBaseNames=$( echo "${rpzFileList}" |
>>>>>>>>>>>>>>> + sed 's|/etc/unbound/zonefiles/||g ; s|\.rpz||g ;' )
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# add to rpzNames
>>>>>>>>>>>>>>> +rpzNames="${rpzNames}"$'\n'"${rpzBaseNames}"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# drop duplicate names
>>>>>>>>>>>>>>> +rpzNames=$( echo "${rpzNames}" | sort --unique )
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# get line count for each RPZ
>>>>>>>>>>>>>>> +lineCount=$( echo "${rpzFileList}" | xargs wc -l )
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# get comment line count and blank line count for each RPZ
>>>>>>>>>>>>>>> +commentCount=$( echo "${rpzFileList}" | xargs grep --count -e "^$" -e "^;" )
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# get modified date each RPZ
>>>>>>>>>>>>>>> +modDateList=$( echo "${rpzFileList}" | xargs stat -c '%.10y %n' )
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +ucListAuthZones=$( unbound-control list_auth_zones )
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# get width of RPZ names
>>>>>>>>>>>>>>> +pWidth=$( echo "${rpzNames}" | awk '{ print $1" " }' | wc -L )
>>>>>>>>>>>>>>> +pFormat="%-${pWidth}s %-8s %-8s %8s %12s %12s\n"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# print title line
>>>>>>>>>>>>>>> +printf "${pFormat}" "name" "hits" "active" "lines" " hits/line" "last download"
>>>>>>>>>>>>>>> +printf -- "--------------"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +theResults=""
>>>>>>>>>>>>>>> +totalLines=0
>>>>>>>>>>>>>>> +totalHits=0
>>>>>>>>>>>>>>> +while read -r theName
>>>>>>>>>>>>>>> +do
>>>>>>>>>>>>>>> + printf -- "--" # pretend progress bar
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # is this RPZ list active?
>>>>>>>>>>>>>>> + theActive="disabled"
>>>>>>>>>>>>>>> + if grep --quiet "^${theName}\.rpz" <<< "${ucListAuthZones}"
>>>>>>>>>>>>>>> + then
>>>>>>>>>>>>>>> + theActive="enabled"
>>>>>>>>>>>>>>> + else
>>>>>>>>>>>>>>> + [[ "${rpzActive}" == enabled ]] && continue
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # get hit count
>>>>>>>>>>>>>>> + theHits="0"
>>>>>>>>>>>>>>> + if output=$( grep "^${theName}\s" <<< "${rpzNameCount}" ) ; then
>>>>>>>>>>>>>>> + theHits=$( echo "${output}" | awk '{ print $2 }' )
>>>>>>>>>>>>>>> + totalHits=$(( totalHits + theHits ))
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # get line count
>>>>>>>>>>>>>>> + theLines="n/a"
>>>>>>>>>>>>>>> + hitsPerLine="0"
>>>>>>>>>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${lineCount}" ) ; then
>>>>>>>>>>>>>>> + theLines=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>>>>>>>>>> + totalLines=$(( totalLines + theLines ))
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + if [[ "${theLines}" -gt 2 ]] ; then
>>>>>>>>>>>>>>> + hitsPerLine=$(( 100 * theHits / theLines ))
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # get modification date
>>>>>>>>>>>>>>> + theModDate="n/a"
>>>>>>>>>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${modDateList}" ) ; then
>>>>>>>>>>>>>>> + theModDate=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # add to results list
>>>>>>>>>>>>>>> + theResults+="${theName} ${theHits} ${theActive} ${theLines} ${hitsPerLine} ${theModDate}"$'\n'
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +done <<< "${rpzNames}"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +case "${sortBy}" in
>>>>>>>>>>>>>>> + # sort by "active" then by "name"
>>>>>>>>>>>>>>> + name) sortArg=(-k3,3r -k1,1) ;;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # sort by "active" then by "hits" then by "name"
>>>>>>>>>>>>>>> + hit) sortArg=(-k3,3r -k2,2nr -k1,1) ;;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # sort by "active" then by "lines" then by "name"
>>>>>>>>>>>>>>> + line) sortArg=(-k3,3r -k4,4nr -k1,1) ;;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # sort by "active" then by "effect" then by "name"
>>>>>>>>>>>>>>> + effect) sortArg=(-k3,3r -k5,5nr -k1,1) ;;
>>>>>>>>>>>>>>> +esac
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +printf -- "--------------\n"
>>>>>>>>>>>>>>> +# remove blank lines, sort, print as columns
>>>>>>>>>>>>>>> +echo "${theResults}" |
>>>>>>>>>>>>>>> + awk '!/^[[:space:]]*$/' |
>>>>>>>>>>>>>>> + sort "${sortArg[@]}" |
>>>>>>>>>>>>>>> + awk --assign=width="${pWidth}" \
>>>>>>>>>>>>>>> + '{ printf "%-*s %-8s %-8s %8s %10s %% %12s\n", width, $1, $2, $3, $4, $5, $6 }'
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +printf "${pFormat}" "" "=======" "" "========" "" ""
>>>>>>>>>>>>>>> +printf "${pFormat}" "Totals -->" "${totalHits}" "" "${totalLines}" "" ""
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +exit
>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-sleep b/config/rpz/rpz-sleep
>>>>>>>>>>>>>>> new file mode 100755
>>>>>>>>>>>>>>> index 000000000..dd3603599
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-sleep
>>>>>>>>>>>>>>> @@ -0,0 +1,58 @@
>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +version="2024-08-16" # v05
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# send message to message log
>>>>>>>>>>>>>>> +msg_log () {
>>>>>>>>>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>>>>>>>>>> + if tty --silent ; then
>>>>>>>>>>>>>>> + echo "${tagName}:" "$*"
>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +tagName="unbound"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +sleepTime="${1:-5m}" # default to sleep for 5m (5 minutes)
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +zoneList=$( unbound-control list_auth_zones | awk '{print $1}' )
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +for zone in ${zoneList} ; do
>>>>>>>>>>>>>>> + printf "disable ${zone}\t"
>>>>>>>>>>>>>>> + unbound-control rpz_disable "${zone}"
>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +msg_log "info: rpz: disabled all zones for ${sleepTime}"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +sleep "${sleepTime}"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +for zone in ${zoneList} ; do
>>>>>>>>>>>>>>> + printf "enable ${zone}\t"
>>>>>>>>>>>>>>> + unbound-control rpz_enable "${zone}"
>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +msg_log "info: rpz: enabled all zones"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +exit
>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.de.pl b/config/rpz/rpz.de.pl
>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>> index 000000000..3770c6bb0
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.de.pl
>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>> +'rpz apply' => 'Übernehmen',
>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Benutzerdefinierte Allowlist aktivieren:',
>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Zugelassene Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>>>>>>>>>> +'rpz cl allow' => 'Benutzerdefinierte Allowlist',
>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Benutzerdefinierte Blocklist aktivieren:',
>>>>>>>>>>>>>>> +'rpz cl block info' => 'Gesperrte Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>>>>>>>>>> +'rpz cl block' => 'Benutzerdefinierte Blocklist',
>>>>>>>>>>>>>>> +'rpz cl' => 'Benutzerdefinierte Listen',
>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'Der Name enthält unzulässige Zeichen',
>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf hat eine fehlerhafte Konfiguration ermittelt. Führen Sie das Kommando unbound-checkconf auf der Konsole aus, um weitere Informationen zu erhalten.',
>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'Die benutzerdefinierte Allow-/Blocklist ist leer',
>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'Ein Eintrag mit identischem Namen existiert bereits',
>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'Die URL ist ungültig',
>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'Eintrag kann nicht entfernt werden, der Name existiert nicht',
>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'Der Name ist ungültig - nur "allow" oder "block" möglich',
>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'Fehlende oder inkorrekte Parameter',
>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control reload ist fehlgeschlagen',
>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'Die benutzerdefinierte Allow-/Blocklist enthält unzulässige Einträge',
>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'Die Anmerkung enthält unzulässige Zeichen',
>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'Ungültiger Eintrag in der benutzerdefinierten Allowlist, Zeile ',
>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'Ungültiger Eintrag in der benutzerdefinierten Blocklist, Zeile ',
>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'Ausgewählter Eintrag existiert nicht: ',
>>>>>>>>>>>>>>> +'rpz zf editor' => 'Zonendatei-Eintrag bearbeiten',
>>>>>>>>>>>>>>> +'rpz zf imported' => '(importiert aus rpz-config)',
>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Erlaubte Zeichen sind a-z, A-Z, 0-9 und Unterstriche',
>>>>>>>>>>>>>>> +'rpz zf' => 'Zonendateien',
>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.en.pl b/config/rpz/rpz.en.pl
>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>> index 000000000..0720a8940
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.en.pl
>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>> +'rpz apply' => 'Apply',
>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Enable custom allowlist:',
>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Allowed domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>>>> +'rpz cl allow' => 'Custom allowlist',
>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Enable custom blocklist:',
>>>>>>>>>>>>>>> +'rpz cl block info' => 'Blocked domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>>>> +'rpz cl block' => 'Custom blocklist',
>>>>>>>>>>>>>>> +'rpz cl' => 'Custom lists',
>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'the NAME is not valid',
>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf found invalid configuration. In the Terminal run the command unbound-checkconf for more information',
>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'the allow/block list is empty',
>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'duplicate - NAME already exists',
>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'the URL is not valid',
>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'cannot remove the NAME does not exist',
>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'the NAME is not valid - "allow" or "block" only',
>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'missing or incorrect parameter',
>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control reload failed',
>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'custom Allowlist/Blocklist contains invalid entries',
>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'the REMARK is not valid',
>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'invalid entry in allowlist, line ',
>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'invalid entry in blocklist, line ',
>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'Selected entry does not exist: ',
>>>>>>>>>>>>>>> +'rpz zf editor' => 'Edit zonefiles entry',
>>>>>>>>>>>>>>> +'rpz zf imported' => '(imported from rpz-config)',
>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Valid characters are a-z, A-Z, 0-9 and underscore.',
>>>>>>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.es.pl b/config/rpz/rpz.es.pl
>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>> index 000000000..98628e4aa
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.es.pl
>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>> +'rpz' => 'Zonas de política de respuesta (RPZ)',
>>>>>>>>>>>>>>> +'rpz apply' => 'Aplicar',
>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Habilitar la lista blanca personalizada:',
>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Dominio permitido (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>>>>>>>>>> +'rpz cl allow' => 'Lista blanca personalizada',
>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Habilitar la lista negra personalizada:',
>>>>>>>>>>>>>>> +'rpz cl block info' => 'Dominio bloqueado (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>>>>>>>>>> +'rpz cl block' => 'Lista negra personalizada',
>>>>>>>>>>>>>>> +'rpz cl' => 'Lista personalizada',
>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'El NOMBRE no es válido',
>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha encontrado una configuración no válida. Desde Terminal, ejecute el comando unbound-checkconf para mayor información',
>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'La lista de permitidos/bloqueados está vacía',
>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'duplicado - NOMBRE ya existe',
>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'la URL no es válida',
>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'no es posible eliminar el NOMBRE que no existe',
>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'el NOMBRE no es válido - sólo "permitir" o "bloquear"',
>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'parámetro faltante o incorrecto',
>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'Error al recargar unbound-control',
>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'la Lista blanca/Lista negra personalizada contiene entradas no válidas',
>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'el COMENTARIO no es válido',
>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'entrada no válida en la lista blanca, línea ',
>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'entrada no válida en la lista negra, línea ',
>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'La entrada seleccionada no existe: ',
>>>>>>>>>>>>>>> +'rpz zf editor' => 'Editar la entrada de archivos de zona',
>>>>>>>>>>>>>>> +'rpz zf imported' => '(importado de rpz-config)',
>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Los caracteres válidos son a-z, A-Z, 0-9 y guión bajo',
>>>>>>>>>>>>>>> +'rpz zf' => 'Archivos de zona',
>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.fr.pl b/config/rpz/rpz.fr.pl
>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>> index 000000000..f35f3c2d0
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.fr.pl
>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>> +'rpz apply' => 'Appliquer',
>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Activer la liste d\'autorisations personnalisée:',
>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Domaines autorisés (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>>>> +'rpz cl allow' => 'Liste d\'autorisations personnalisée',
>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Activer la liste de blocage personnalisée:',
>>>>>>>>>>>>>>> +'rpz cl block info' => 'Domaines bloqués (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>>>> +'rpz cl block' => 'liste de blocage personnalisé',
>>>>>>>>>>>>>>> +'rpz cl' => 'Listes personnalisées',
>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'le NOM n\'est pas valide',
>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf configuration non valide trouvée. Dans le terminal, exécutez la commande unbound-checkconf pour plus d\'informations',
>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'la liste autoriser/bloquer est vide',
>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'le NOM existe déjà',
>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'L\'URL n\'est pas valide',
>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'impossible de supprimer le NOM n\'existe pas',
>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'le NOM n\'est pas valide - « autoriser » ou « bloquer » seulement',
>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'paramètre manquant ou incorrect',
>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control rechargement échoué',
>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'la liste autoriser/bloquer contient des entrées non valides',
>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'la REMARQUE n\'est pas valable',
>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'entrée non valide dans la liste d\'autorisation, ligne ',
>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'entrée non valide dans la liste de blocs, ligne ',
>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'L\'entrée sélectionnée n\'existe pas: ',
>>>>>>>>>>>>>>> +'rpz zf editor' => 'Modifier l\'entrée Fichiers Zone',
>>>>>>>>>>>>>>> +'rpz zf imported' => '(importé de rpz-config)',
>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Les caractères valides sont a-z, A-Z, 0-9 et soulignement.',
>>>>>>>>>>>>>>> +'rpz zf' => 'Fichiers Zone',
>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.it.pl b/config/rpz/rpz.it.pl
>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>> index 000000000..ee81605c9
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.it.pl
>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>> +'rpz apply' => 'Applica',
>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Abilita la Whitelist personalizzata:',
>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Domini consentiti (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>>>>>>>>>> +'rpz cl allow' => 'Whitelist personalizzata',
>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Abilita la Blacklist personalizzata:',
>>>>>>>>>>>>>>> +'rpz cl block info' => 'Domini bloccati (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>>>>>>>>>> +'rpz cl block' => 'Blacklist personalizzata',
>>>>>>>>>>>>>>> +'rpz cl' => 'Liste personalizzate',
>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'il NOME non è valido',
>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha trovato una configurazione non valida. Dal Terminale esegui il comando unbound-checkconf per maggiori informazioni',
>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'l\'elenco consentiti/bloccati è vuoto',
>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'duplicato - NAME esiste di già',
>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'l\'URL non è valido',
>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'non è possibile rimuovere il NOME non esiste',
>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'il NOME non è valido - solo "consenti" o "blocca"',
>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'parametro mancante o non corretto',
>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'ricaricamento del controllo non associato non riuscito',
>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'la Whitelist/Blacklist personalizzata contiene voci non valide',
>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'l"OSSERVAZIONE non è valida',
>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'voce non valida nella Whitelist, riga ',
>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'voce non valida nella Blacklist, riga ',
>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'La voce selezionata non esiste: ',
>>>>>>>>>>>>>>> +'rpz zf editor' => 'Modifica la voce dei file di zona',
>>>>>>>>>>>>>>> +'rpz zf imported' => '(importato da rpz-config)',
>>>>>>>>>>>>>>> +'rpz zf remark info' => 'I caratteri validi sono a-z, A-Z, 0-9 e trattino basso',
>>>>>>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.tr.pl b/config/rpz/rpz.tr.pl
>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>> index 000000000..00226e192
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.tr.pl
>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>> +# I�in eklendi Ayriyeten Response Policy Zone (RPZ)
>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>> +'rpz apply' => 'Uygulamak',
>>>>>>>>>>>>>>> +'rpz cl allow enable' => '�zel Etkinlestir allowlist:',
>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Izin verilmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>>>>>>>>>> +'rpz cl allow' => 'Etkinlestir allowlist',
>>>>>>>>>>>>>>> +'rpz cl block enable' => '�zel Etkinlestir blocklist:',
>>>>>>>>>>>>>>> +'rpz cl block info' => 'Engellenmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>>>>>>>>>> +'rpz cl block' => 'Engellenmis blocklist',
>>>>>>>>>>>>>>> +'rpz cl' => 'Engellenmis lists',
>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'NAME ge�erli degil',
>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ge�ersiz yapilandirma bulundu. Terminalde daha fazla bilgi i�in Unbound-checkConf komutunu �alistirin',
>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'allow/block liste bos',
>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'kopyalamak - NAME zaten var',
>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'URL ge�erli degil',
>>>>>>>>>>>>>>> +'rpz exitcode 106' => '�ikarilamiyor NAME yok',
>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'NAME ge�erli degil - "allow" veya "block" yalniz',
>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'Parametre eksik veya yanlis',
>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control basarisiz',
>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'Engellenmis Allowlist/Blocklist ge�ersiz girisler i�erir',
>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'REMARK ge�erli degil',
>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'Ge�ersiz giris allowlist, line ',
>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'Ge�ersiz giris blocklist, line ',
>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'Se�ilen giris yok: ',
>>>>>>>>>>>>>>> +'rpz zf editor' => 'yazimlamak zonefiles giris',
>>>>>>>>>>>>>>> +'rpz zf imported' => '(ithal edildi rpz-config)',
>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Ge�erli karakterler a-z, A-Z, 0-9ve alt�st.',
>>>>>>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>> diff --git a/html/cgi-bin/rpz.cgi b/html/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>> index 000000000..a821c92ac
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/html/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>> @@ -0,0 +1,923 @@
>>>>>>>>>>>>>>> +#!/usr/bin/perl
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>> +# Copyright (C) 2005-2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +use strict;
>>>>>>>>>>>>>>> +use Scalar::Util qw(looks_like_number);
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# debugging
>>>>>>>>>>>>>>> +#use warnings;
>>>>>>>>>>>>>>> +#use CGI::Carp 'fatalsToBrowser';
>>>>>>>>>>>>>>> +#use Data::Dumper;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +require '/var/ipfire/general-functions.pl';
>>>>>>>>>>>>>>> +require "${General::swroot}/lang.pl";
>>>>>>>>>>>>>>> +require "${General::swroot}/header.pl";
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +###--- Extra HTML ---###
>>>>>>>>>>>>>>> +my $extraHead = <<END
>>>>>>>>>>>>>>> +<style>
>>>>>>>>>>>>>>> + /* alternating row background */
>>>>>>>>>>>>>>> + .tbl tr:nth-child(2n+2) {
>>>>>>>>>>>>>>> + background-color: var(--color-light-grey);
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + .tbl tr:nth-child(2n+3) {
>>>>>>>>>>>>>>> + background-color: var(--color-grey);
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + /* text styles */
>>>>>>>>>>>>>>> + .tbl th:not(:last-child) {
>>>>>>>>>>>>>>> + text-align: left;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + div.right {
>>>>>>>>>>>>>>> + text-align: right;
>>>>>>>>>>>>>>> + margin-top: 0.5em;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + /* customlist input */
>>>>>>>>>>>>>>> + textarea.domainlist {
>>>>>>>>>>>>>>> + margin: 0.5em 0;
>>>>>>>>>>>>>>> + resize: vertical;
>>>>>>>>>>>>>>> + min-height: 10em;
>>>>>>>>>>>>>>> + overflow: auto;
>>>>>>>>>>>>>>> + white-space: pre;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + button[type=submit]:disabled {
>>>>>>>>>>>>>>> + opacity: 0.6;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +</style>
>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>> +###--- End of extra HTML ---###
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +### Settings ###
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Request DNS service reload after configuration change
>>>>>>>>>>>>>>> +my $RPZ_RELOAD_FLAG = "${General::swroot}/dns/rpz/reload.flag";
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Configuration file for all available zonefiles
>>>>>>>>>>>>>>> +# Format: index, name (unique), enabled (on/off), URL, remark
>>>>>>>>>>>>>>> +my $ZONEFILES_CONF = "${General::swroot}/dns/rpz/zonefiles.conf";
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Configuration file for custom lists
>>>>>>>>>>>>>>> +# IDs: 0=allowlist, 1=blocklist, 2=options (allow/block enabled)
>>>>>>>>>>>>>>> +my $CUSTOMLISTS_CONF = "${General::swroot}/dns/rpz/customlists.conf";
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Export custom lists to rpz-config
>>>>>>>>>>>>>>> +my $RPZ_ALLOWLIST = "${General::swroot}/dns/rpz/allowlist";
>>>>>>>>>>>>>>> +my $RPZ_BLOCKLIST = "${General::swroot}/dns/rpz/blocklist";
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +### Preparation ###
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Create missing config files
>>>>>>>>>>>>>>> +unless(-f $ZONEFILES_CONF) { &General::system('touch', "$ZONEFILES_CONF"); }
>>>>>>>>>>>>>>> +unless(-f $CUSTOMLISTS_CONF) { &General::system('touch', "$CUSTOMLISTS_CONF"); }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +## Global gui data
>>>>>>>>>>>>>>> +my $errormessage = "";
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +## Global configuration data
>>>>>>>>>>>>>>> +my %zonefiles = ();
>>>>>>>>>>>>>>> +my %customlists = ();
>>>>>>>>>>>>>>> +&_zonefiles_load();
>>>>>>>>>>>>>>> +&_customlists_load();
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +## Global CGI form data
>>>>>>>>>>>>>>> +my %cgiparams = ();
>>>>>>>>>>>>>>> +&Header::getcgihash(\%cgiparams);
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +my $action = $cgiparams{'ACTION'} // 'NONE';
>>>>>>>>>>>>>>> +my $action_key = $cgiparams{'KEY'} // ''; # entry being edited, empty = none/new
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +###--- Process form actions ---###
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Zonefiles action: Check whether the requested entry exists
>>>>>>>>>>>>>>> +if((substr($action, 0, 3) eq 'ZF_') && ($action_key)) {
>>>>>>>>>>>>>>> + unless(defined $zonefiles{$action_key}) {
>>>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr(204, $action_key);
>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +## Perform actions
>>>>>>>>>>>>>>> +if($action eq 'ZF_SAVE') { ## Save new or modified zonefiles entry
>>>>>>>>>>>>>>> + if(&_action_zf_save()) {
>>>>>>>>>>>>>>> + $action = 'NONE'; # success, return to main page
>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>> + $action = 'ZF_EDIT'; # error occured, keep editing
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +} elsif($action eq 'ZF_TOGGLE') { ## Toggle on/off
>>>>>>>>>>>>>>> + if(&_action_zf_toggle()) {
>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +} elsif($action eq 'ZF_REMOVE') { ## Remove entry
>>>>>>>>>>>>>>> + if(&_action_zf_remove()) {
>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +} elsif($action eq 'CL_SAVE') { ## Save custom lists
>>>>>>>>>>>>>>> + if(&_action_cl_save()) {
>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +} elsif($action eq 'RPZ_RELOAD') { ## Reload dns configuration
>>>>>>>>>>>>>>> + if(&_action_rpz_reload()) {
>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +} elsif($action eq 'UNB_RESTART') { ## Restart unbound service
>>>>>>>>>>>>>>> + if(&_action_unb_restart()) {
>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +###--- Start GUI ---###
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +## Start http output
>>>>>>>>>>>>>>> +&Header::showhttpheaders();
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Start HTML
>>>>>>>>>>>>>>> +&Header::openpage($Lang::tr{'rpz'}, 1, $extraHead);
>>>>>>>>>>>>>>> +&Header::openbigbox('100%', 'left', '');
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Show error messages
>>>>>>>>>>>>>>> +if($errormessage) {
>>>>>>>>>>>>>>> + &_print_message($errormessage);
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Handle zonefile add/edit mode
>>>>>>>>>>>>>>> +if($action eq "ZF_EDIT") {
>>>>>>>>>>>>>>> + &_print_zonefile_editor();
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Finalize page and exit cleanly
>>>>>>>>>>>>>>> + &Header::closebigbox();
>>>>>>>>>>>>>>> + &Header::closepage();
>>>>>>>>>>>>>>> + exit(0);
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Show gui elements
>>>>>>>>>>>>>>> +&_print_zonefiles();
>>>>>>>>>>>>>>> +&_print_customlists();
>>>>>>>>>>>>>>> +&_print_gui_extras();
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +&Header::closebigbox();
>>>>>>>>>>>>>>> +&Header::closepage();
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +###--- End of GUI ---###
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +###--- Internal configuration file functions ---###
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Load all available zonefiles from rpz-config and the internal configuration
>>>>>>>>>>>>>>> +sub _zonefiles_load {
>>>>>>>>>>>>>>> + # Clean start
>>>>>>>>>>>>>>> + %zonefiles = ();
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Source 1: Get the currently enabled zonefiles from rpz-config (expected format [name]=[URL])
>>>>>>>>>>>>>>> + my @enabled_files = &General::system_output('/usr/sbin/rpz-config', 'list');
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + foreach my $row (@enabled_files) {
>>>>>>>>>>>>>>> + chomp($row);
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Use regex instead of split() to skip non-matching lines
>>>>>>>>>>>>>>> + next unless($row =~ /^(\w+)=(.+)$/);
>>>>>>>>>>>>>>> + my ($name, $url) = ($1, $2);
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Unique names are already guaranteed by rpz-config
>>>>>>>>>>>>>>> + if(&_rpz_validate_zonefile($name, $url, '', 0) == 0) {
>>>>>>>>>>>>>>> + # Populate global data hash, mark all found entries as enabled
>>>>>>>>>>>>>>> + my %entry = ('enabled' => 'on',
>>>>>>>>>>>>>>> + 'url' => $url,
>>>>>>>>>>>>>>> + 'remark' => $Lang::tr{'rpz zf imported'});
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Source 2: Get additional data and disabled entries from configuration file
>>>>>>>>>>>>>>> + my %configured_files = ();
>>>>>>>>>>>>>>> + &General::readhasharray($ZONEFILES_CONF, \%configured_files);
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + foreach my $row (values (%configured_files)) {
>>>>>>>>>>>>>>> + my ($name, $enabled, $url, $remark) = @$row;
>>>>>>>>>>>>>>> + $remark //= "";
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + next unless($name);
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Check whether this row belongs to an entry already imported from rpz-config
>>>>>>>>>>>>>>> + if(defined $zonefiles{$name}) {
>>>>>>>>>>>>>>> + # Existing entry, only merge additional data
>>>>>>>>>>>>>>> + $zonefiles{$name}{'remark'} = $remark;
>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>> + # Skip entry if it is marked as enabled but not found by rpz-config. It was then deleted manually
>>>>>>>>>>>>>>> + if($enabled ne 'on') {
>>>>>>>>>>>>>>> + # Populate global data hash
>>>>>>>>>>>>>>> + my %entry = ('enabled' => 'off',
>>>>>>>>>>>>>>> + 'url' => $url // "",
>>>>>>>>>>>>>>> + 'remark' => $remark);
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Save internal zonefiles configuration
>>>>>>>>>>>>>>> +sub _zonefiles_save_conf {
>>>>>>>>>>>>>>> + my $index = 0;
>>>>>>>>>>>>>>> + my %export = ();
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Loop trough all zonefiles and create "hasharray" type export
>>>>>>>>>>>>>>> + foreach my $name (keys %zonefiles) {
>>>>>>>>>>>>>>> + my @entry = ($name,
>>>>>>>>>>>>>>> + $zonefiles{$name}{'enabled'},
>>>>>>>>>>>>>>> + $zonefiles{$name}{'url'},
>>>>>>>>>>>>>>> + $zonefiles{$name}{'remark'});
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + $export{$index++} = \@entry;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + &General::writehasharray($ZONEFILES_CONF, \%export);
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Load custom lists from rpz-config and the internal configuration
>>>>>>>>>>>>>>> +sub _customlists_load {
>>>>>>>>>>>>>>> + # Clean start
>>>>>>>>>>>>>>> + %customlists = ();
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Load configuration file
>>>>>>>>>>>>>>> + my %lists_conf = ();
>>>>>>>>>>>>>>> + &General::readhasharray($CUSTOMLISTS_CONF, \%lists_conf);
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Get list options, enabled by default to start import
>>>>>>>>>>>>>>> + $customlists{'allow'}{'enabled'} = $lists_conf{2}[0] // 'on';
>>>>>>>>>>>>>>> + $customlists{'block'}{'enabled'} = $lists_conf{2}[1] // 'on';
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Import enabled list from rpz-config, otherwise retrieve stored or empty list from configuration file
>>>>>>>>>>>>>>> + if($customlists{'allow'}{'enabled'} eq 'on') {
>>>>>>>>>>>>>>> + &_customlist_import('allow', $RPZ_ALLOWLIST);
>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>> + $customlists{'allow'}{'list'} = $lists_conf{0} // [];
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + if($customlists{'block'}{'enabled'} eq 'on') {
>>>>>>>>>>>>>>> + &_customlist_import('block', $RPZ_BLOCKLIST);
>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>> + $customlists{'block'}{'list'} = $lists_conf{1} // [];
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Save internal custom lists configuration
>>>>>>>>>>>>>>> +sub _customlists_save_conf {
>>>>>>>>>>>>>>> + my %export = ();
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Match IDs with import function
>>>>>>>>>>>>>>> + $export{0} = $customlists{'allow'}{'list'};
>>>>>>>>>>>>>>> + $export{1} = $customlists{'block'}{'list'};
>>>>>>>>>>>>>>> + $export{2} = [$customlists{'allow'}{'enabled'}, $customlists{'block'}{'enabled'}];
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + &General::writehasharray($CUSTOMLISTS_CONF, \%export);
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Import a custom list from plain file, returns empty list if file is missing
>>>>>>>>>>>>>>> +sub _customlist_import {
>>>>>>>>>>>>>>> + my ($listname, $filename) = @_;
>>>>>>>>>>>>>>> + my @list = ();
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # File exists, load and check all lines
>>>>>>>>>>>>>>> + if(-f $filename) {
>>>>>>>>>>>>>>> + open(my $FH, '<', $filename) or die "Can't read $filename: $!";
>>>>>>>>>>>>>>> + while(my $line = <$FH>) {
>>>>>>>>>>>>>>> + chomp($line);
>>>>>>>>>>>>>>> + push(@list, $line);
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + close($FH);
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Clean up imported data
>>>>>>>>>>>>>>> + &_rpz_validate_customlist(\@list, 1);
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + $customlists{$listname}{'list'} = \@list;
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Export a custom list to plain file or clear file if list is disabled
>>>>>>>>>>>>>>> +sub _customlist_export {
>>>>>>>>>>>>>>> + my ($listname, $filename) = @_;
>>>>>>>>>>>>>>> + return unless(defined $customlists{$listname});
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Write enabled domain list to file, otherwise save empty file
>>>>>>>>>>>>>>> + open(my $FH, '>', $filename) or die "Can't write $filename: $!";
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + if($customlists{$listname}{'enabled'} eq 'on') {
>>>>>>>>>>>>>>> + foreach my $line (@{$customlists{$listname}{'list'}}) {
>>>>>>>>>>>>>>> + print $FH "$line\n";
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>> + print $FH "; Note: This list is currently disabled by $ENV{'SCRIPT_NAME'}\n";
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + close($FH);
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +###--- Internal gui functions ---###
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Show simple message box
>>>>>>>>>>>>>>> +sub _print_message {
>>>>>>>>>>>>>>> + my ($message, $title) = @_;
>>>>>>>>>>>>>>> + $title ||= $Lang::tr{'error messages'};
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $title);
>>>>>>>>>>>>>>> + print "<span>$message</span>";
>>>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Show all zone files and related gui elements
>>>>>>>>>>>>>>> +sub _print_zonefiles {
>>>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf'});
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>> +<table class="tbl" width="100%">
>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>> + <th>$Lang::tr{'name'}</th>
>>>>>>>>>>>>>>> + <th>URL</th>
>>>>>>>>>>>>>>> + <th>$Lang::tr{'remark'}</th>
>>>>>>>>>>>>>>> + <th colspan="3">$Lang::tr{'action'}</th>
>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Sort zonefiles by name and loop trough all entries
>>>>>>>>>>>>>>> + foreach my $name (sort keys %zonefiles) {
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Toggle button label translation
>>>>>>>>>>>>>>> + my $toggle_tr = ($zonefiles{$name}{'enabled'} eq 'on') ? $Lang::tr{'click to disable'} : $Lang::tr{'click to enable'};
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>> + <td>$name</td>
>>>>>>>>>>>>>>> + <td>$zonefiles{$name}{'url'}</td>
>>>>>>>>>>>>>>> + <td>$zonefiles{$name}{'remark'}</td>
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_TOGGLE">
>>>>>>>>>>>>>>> + <input type="image" src="/images/$zonefiles{$name}{'enabled'}.gif" title="$toggle_tr" alt="$toggle_tr">
>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_EDIT">
>>>>>>>>>>>>>>> + <input type="image" src="/images/edit.gif" title="$Lang::tr{'edit'}" alt="$Lang::tr{'edit'}">
>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_REMOVE">
>>>>>>>>>>>>>>> + <input type="image" src="/images/delete.gif" title="$Lang::tr{'remove'}" alt="$Lang::tr{'remove'}">
>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Disable reload button if not needed
>>>>>>>>>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>> +</table>
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +<div class="right">
>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="">
>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="ZF_EDIT">$Lang::tr{'add'}</button>
>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>> +</div>
>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Show zonefiles entry editor
>>>>>>>>>>>>>>> +sub _print_zonefile_editor {
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Key specified: Edit existing entry
>>>>>>>>>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>>>>>>>>>> + # Load data to be edited, but don't override already present values (allows user to edit after error)
>>>>>>>>>>>>>>> + $cgiparams{'ZF_NAME'} //= $action_key;
>>>>>>>>>>>>>>> + $cgiparams{'ZF_URL'} //= $zonefiles{$action_key}{'url'};
>>>>>>>>>>>>>>> + $cgiparams{'ZF_REMARK'} //= $zonefiles{$action_key}{'remark'};
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Fallback to empty form
>>>>>>>>>>>>>>> + $cgiparams{'ZF_NAME'} //= "";
>>>>>>>>>>>>>>> + $cgiparams{'ZF_URL'} //= "";
>>>>>>>>>>>>>>> + $cgiparams{'ZF_REMARK'} //= "";
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf editor'});
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>> +<input type="hidden" name="KEY" value="$action_key">
>>>>>>>>>>>>>>> +<table width="100%">
>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>> + <td width="20%">$Lang::tr{'name'}: <img src="/blob.gif" alt="*"></td>
>>>>>>>>>>>>>>> + <td><input type="text" name="ZF_NAME" value="$cgiparams{'ZF_NAME'}" size="40" maxlength="32" title="$Lang::tr{'rpz zf remark info'}" pattern="[a-zA-Z0-9_]{1,32}" required></td>
>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>> + <td width="20%">URL: <img src="/blob.gif" alt="*"></td>
>>>>>>>>>>>>>>> + <td><input type="url" name="ZF_URL" value="$cgiparams{'ZF_URL'}" size="40" maxlength="128" required></td>
>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>> + <td width="20%">$Lang::tr{'remark'}:</td>
>>>>>>>>>>>>>>> + <td><input type="text" name="ZF_REMARK" value="$cgiparams{'ZF_REMARK'}" size="40" maxlength="32"></td>
>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>> + <td colspan="2"><hr></td>
>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>> + <td width="55%"><img src="/blob.gif" alt="*"> $Lang::tr{'required field'}</td>
>>>>>>>>>>>>>>> + <td align="right"><button type="submit" name="ACTION" value="ZF_SAVE">$Lang::tr{'save'}</button></td>
>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>> +</table>
>>>>>>>>>>>>>>> +</form>
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +<div class="right">
>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="NONE">$Lang::tr{'back'}</button>
>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>> +</div>
>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Show custom allow/block files and related gui elements
>>>>>>>>>>>>>>> +sub _print_customlists {
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Load lists from config, unless they are currently being edited
>>>>>>>>>>>>>>> + if($action ne 'CL_SAVE') {
>>>>>>>>>>>>>>> + $cgiparams{'ALLOW_LIST'} = join("\n", @{$customlists{'allow'}{'list'}});
>>>>>>>>>>>>>>> + $cgiparams{'BLOCK_LIST'} = join("\n", @{$customlists{'block'}{'list'}});
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + $cgiparams{'ALLOW_ENABLED'} = ($customlists{'allow'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>>>>>>>>>> + $cgiparams{'BLOCK_ENABLED'} = ($customlists{'block'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Fallback to empty form
>>>>>>>>>>>>>>> + $cgiparams{'ALLOW_LIST'} //= "";
>>>>>>>>>>>>>>> + $cgiparams{'BLOCK_LIST'} //= "";
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # HTML checkboxes, unchecked = no or undef value in POST data
>>>>>>>>>>>>>>> + my %checked = ();
>>>>>>>>>>>>>>> + $checked{'ALLOW_ENABLED'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? " checked" : "";
>>>>>>>>>>>>>>> + $checked{'BLOCK_ENABLED'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? " checked" : "";
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Disable reload button if not needed
>>>>>>>>>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz cl'});
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>> +<table width="100%">
>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl allow'}</b><br>$Lang::tr{'rpz cl allow info'}</td>
>>>>>>>>>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl block'}</b><br>$Lang::tr{'rpz cl block info'}</td>
>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>> + <td colspan="2"><textarea name="ALLOW_LIST" class="domainlist" cols="45">
>>>>>>>>>>>>>>> +$cgiparams{'ALLOW_LIST'}</textarea></td>
>>>>>>>>>>>>>>> + <td colspan="2"><textarea name="BLOCK_LIST" class="domainlist" cols="45">
>>>>>>>>>>>>>>> +$cgiparams{'BLOCK_LIST'}</textarea></td>
>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>> + <td><label for="allow_enabled">$Lang::tr{'rpz cl allow enable'}</label></td>
>>>>>>>>>>>>>>> + <td width="15%"><input type="checkbox" name="ALLOW_ENABLED" id="allow_enabled"$checked{'ALLOW_ENABLED'}></td>
>>>>>>>>>>>>>>> + <td><label for="block_enabled">$Lang::tr{'rpz cl block enable'}</label></td>
>>>>>>>>>>>>>>> + <td width="15%"><input type="checkbox" name="BLOCK_ENABLED" id="block_enabled"$checked{'BLOCK_ENABLED'}></td>
>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>> + <td colspan="4"><hr></td>
>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>> + <td align="right" colspan="4">
>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="CL_SAVE">$Lang::tr{'save'}</button>
>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>>>> +</table>
>>>>>>>>>>>>>>> +</form>
>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Output javascript and extra gui elements
>>>>>>>>>>>>>>> +sub _print_gui_extras {
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Apply/Restart button modifier key handler
>>>>>>>>>>>>>>> + if(&_rpz_needs_reload()) {
>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>> +<script>
>>>>>>>>>>>>>>> + // Commit modifier key handler
>>>>>>>>>>>>>>> + (function(jq, document) {
>>>>>>>>>>>>>>> + var keyEventsOn = false; // Keyboard events attached
>>>>>>>>>>>>>>> + var keyModify = false; // Modifier key pressed
>>>>>>>>>>>>>>> + var mouseHover = false; // Mouse over commit button
>>>>>>>>>>>>>>> + var btnModified = false; // Button modified to "Restart"
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + // Document-level key events, enable only while cursor is over button
>>>>>>>>>>>>>>> + function attachKeyEvents() {
>>>>>>>>>>>>>>> + if(keyEventsOn) {
>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + keyEventsOn = true;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + jq(document).on("keydown.rpz", function(event) {
>>>>>>>>>>>>>>> + if((!keyModify) && event.shiftKey) {
>>>>>>>>>>>>>>> + keyModify = true;
>>>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>> + jq(document).on("keyup.rpz", function(event) {
>>>>>>>>>>>>>>> + if(keyModify && (!event.shiftKey)) {
>>>>>>>>>>>>>>> + keyModify = false;
>>>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + function removeKeyEvents() {
>>>>>>>>>>>>>>> + keyModify = false;
>>>>>>>>>>>>>>> + if(keyEventsOn) {
>>>>>>>>>>>>>>> + jq(document).off("keydown.rpz keyup.rpz");
>>>>>>>>>>>>>>> + keyEventsOn = false;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + // Attach mouse hover events to commit buttons
>>>>>>>>>>>>>>> + function attachMouseEvents() {
>>>>>>>>>>>>>>> + jq("button.commit").on("mouseenter", function(event) {
>>>>>>>>>>>>>>> + if(!mouseHover) {
>>>>>>>>>>>>>>> + mouseHover = true;
>>>>>>>>>>>>>>> + attachKeyEvents();
>>>>>>>>>>>>>>> + // Handle already pressed key
>>>>>>>>>>>>>>> + keyModify = !!(event.shiftKey);
>>>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + // Cursor moved away: Disable key listener to minimize events
>>>>>>>>>>>>>>> + jq("button.commit").on("mouseleave", function() {
>>>>>>>>>>>>>>> + if(mouseHover) {
>>>>>>>>>>>>>>> + mouseHover = false;
>>>>>>>>>>>>>>> + removeKeyEvents();
>>>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + // Modify commit button
>>>>>>>>>>>>>>> + function handleModify() {
>>>>>>>>>>>>>>> + let modify = mouseHover && keyModify;
>>>>>>>>>>>>>>> + if(btnModified != modify) {
>>>>>>>>>>>>>>> + if(modify) {
>>>>>>>>>>>>>>> + jq("button.commit").text("$Lang::tr{'restart'}").val("UNB_RESTART");
>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>> + jq("button.commit").text("$Lang::tr{'rpz apply'}").val("RPZ_RELOAD");
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + btnModified = modify;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + // jQuery DOM ready
>>>>>>>>>>>>>>> + jq(function() {
>>>>>>>>>>>>>>> + attachMouseEvents();
>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>> + })(jQuery, document);
>>>>>>>>>>>>>>> +</script>
>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>> + } # End of modifier key handler
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +###--- Internal action processing functions ---###
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Toggle zonefile on/off
>>>>>>>>>>>>>>> +sub _action_zf_toggle {
>>>>>>>>>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + my $result = 0;
>>>>>>>>>>>>>>> + my $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Perform toggle action
>>>>>>>>>>>>>>> + if($enabled eq 'on') {
>>>>>>>>>>>>>>> + $enabled = 'off';
>>>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>> + $enabled = 'on';
>>>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $action_key, $zonefiles{$action_key}{'url'}, '--no-reload');
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Save changes
>>>>>>>>>>>>>>> + $zonefiles{$action_key}{'enabled'} = $enabled;
>>>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Remove zonefile
>>>>>>>>>>>>>>> +sub _action_zf_remove {
>>>>>>>>>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Remove from rpz-config if currently active
>>>>>>>>>>>>>>> + if($zonefiles{$action_key}{'enabled'} eq 'on') {
>>>>>>>>>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Remove from data hash and save changes
>>>>>>>>>>>>>>> + delete $zonefiles{$action_key};
>>>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Clear action_key, as the entry is now removed entirely
>>>>>>>>>>>>>>> + $action_key = "";
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Create or update zonefile entry
>>>>>>>>>>>>>>> +# Returns undef if gui needs to stay in editor mode
>>>>>>>>>>>>>>> +sub _action_zf_save {
>>>>>>>>>>>>>>> + my $result = 0;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + my $name = $cgiparams{'ZF_NAME'} // "";
>>>>>>>>>>>>>>> + my $url = $cgiparams{'ZF_URL'} // "";
>>>>>>>>>>>>>>> + my $remark = $cgiparams{'ZF_REMARK'} // "";
>>>>>>>>>>>>>>> + my $enabled = 'on'; # Enable new entries by default
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Note on variables:
>>>>>>>>>>>>>>> + # name = unique key, will be used to address the entry
>>>>>>>>>>>>>>> + # action_key = name of the entry being edited, empty for new entry
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Only check for unique name if it changed
>>>>>>>>>>>>>>> + # (this also checks new entries because the action_key is empty in this case)
>>>>>>>>>>>>>>> + $result = &_rpz_validate_zonefile($name, $url, $remark, (lc($name) ne lc($action_key)));
>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 0);
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Edit existing entry: Determine what was changed
>>>>>>>>>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>>>>>>>>>> + # Name und URL remain unchanged, only save remark and finish
>>>>>>>>>>>>>>> + if(($name eq $action_key) && ($url eq $zonefiles{$action_key}{'url'})) {
>>>>>>>>>>>>>>> + $zonefiles{$action_key}{'remark'} = $remark;
>>>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Entry was changed and needs to be recreated, preserve status
>>>>>>>>>>>>>>> + $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Remove from rpz-config
>>>>>>>>>>>>>>> + return unless &_action_zf_remove();
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Add new entry to rpz-config
>>>>>>>>>>>>>>> + if($enabled eq 'on') {
>>>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $name, $url, '--no-reload');
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Add to global data hash and save changes
>>>>>>>>>>>>>>> + my %entry = ('enabled' => $enabled,
>>>>>>>>>>>>>>> + 'url' => $url,
>>>>>>>>>>>>>>> + 'remark' => $remark);
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Save custom lists
>>>>>>>>>>>>>>> +sub _action_cl_save {
>>>>>>>>>>>>>>> + return unless((defined $cgiparams{'ALLOW_LIST'}) && (defined $cgiparams{'BLOCK_LIST'}));
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + my $result = 0;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + my @allowlist = split(/\R/, $cgiparams{'ALLOW_LIST'});
>>>>>>>>>>>>>>> + my @blocklist = split(/\R/, $cgiparams{'BLOCK_LIST'});
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Validate lists
>>>>>>>>>>>>>>> + $result = &_rpz_validate_customlist(\@allowlist);
>>>>>>>>>>>>>>> + if($result != 0) {
>>>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr(202, $result);
>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + $result = &_rpz_validate_customlist(\@blocklist);
>>>>>>>>>>>>>>> + if($result != 0) {
>>>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr(203, $result);
>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Add to global data hash and save changes
>>>>>>>>>>>>>>> + $customlists{'allow'}{'list'} = \@allowlist;
>>>>>>>>>>>>>>> + $customlists{'block'}{'list'} = \@blocklist;
>>>>>>>>>>>>>>> + $customlists{'allow'}{'enabled'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? 'on' : 'off';
>>>>>>>>>>>>>>> + $customlists{'block'}{'enabled'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? 'on' : 'off';
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + &_customlists_save_conf();
>>>>>>>>>>>>>>> + &_customlist_export('allow', $RPZ_ALLOWLIST);
>>>>>>>>>>>>>>> + &_customlist_export('block', $RPZ_BLOCKLIST);
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Make new lists, request service reload on success
>>>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-make', 'allowblock', '--no-reload');
>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Trigger rpz-config reload
>>>>>>>>>>>>>>> +sub _action_rpz_reload {
>>>>>>>>>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Immediately clear flag to prevent multiple reloads
>>>>>>>>>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>>>>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Perform reload, recreate reload flag on error to enable retry
>>>>>>>>>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'reload');
>>>>>>>>>>>>>>> + if(not &_rpz_check_result($result, 0)) {
>>>>>>>>>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Trigger unbound restart
>>>>>>>>>>>>>>> +sub _action_unb_restart {
>>>>>>>>>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Immediately clear flag to prevent multiple restarts
>>>>>>>>>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>>>>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Perform restart, unboundctrl always exits zero
>>>>>>>>>>>>>>> + &General::system('/usr/local/bin/unboundctrl', 'restart');
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +###--- Internal rpz-config functions ---###
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Translate rpz-config exitcodes and messages
>>>>>>>>>>>>>>> +# 100-199: rpz-config, 200-299: webgui
>>>>>>>>>>>>>>> +sub _rpz_error_tr {
>>>>>>>>>>>>>>> + my ($error, $append) = @_;
>>>>>>>>>>>>>>> + $append //= '';
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Translate numeric exit codes
>>>>>>>>>>>>>>> + if(looks_like_number($error)) {
>>>>>>>>>>>>>>> + if(defined $Lang::tr{"rpz exitcode $error"}) {
>>>>>>>>>>>>>>> + $error = $Lang::tr{"rpz exitcode $error"};
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + return "RPZ $Lang::tr{'error'}: $error" . &Header::escape($append);
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Check result of rpz-config system call, request reload on success
>>>>>>>>>>>>>>> +sub _rpz_check_result {
>>>>>>>>>>>>>>> + my ($result, $request_reload) = @_;
>>>>>>>>>>>>>>> + $request_reload //= 0;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # exitcode 0 = success
>>>>>>>>>>>>>>> + if($result != 0) {
>>>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr($result);
>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Set reload flag
>>>>>>>>>>>>>>> + if($request_reload) {
>>>>>>>>>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Test whether reload flag is set
>>>>>>>>>>>>>>> +sub _rpz_needs_reload {
>>>>>>>>>>>>>>> + return (-f $RPZ_RELOAD_FLAG);
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Validate a zonefile entry, returns rpz-config exitcode on failure. Use _rpz_check_result to verify.
>>>>>>>>>>>>>>> +# unique = check for unique name
>>>>>>>>>>>>>>> +sub _rpz_validate_zonefile {
>>>>>>>>>>>>>>> + my ($name, $url, $remark, $unique) = @_;
>>>>>>>>>>>>>>> + $unique //= 1;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + unless($name =~ /^[a-zA-Z0-9_]{1,32}$/) {
>>>>>>>>>>>>>>> + return 101;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + unless($url =~ /^[\w+\.:;\/\\&@#%?=\-~|!]{1,128}$/) {
>>>>>>>>>>>>>>> + return 105;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + unless($remark =~ /^[\w \-()\.:;*\/\\?!&=]{0,32}$/) {
>>>>>>>>>>>>>>> + return 201;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Check against already existing names
>>>>>>>>>>>>>>> + if($unique) {
>>>>>>>>>>>>>>> + foreach my $existing (keys %zonefiles) {
>>>>>>>>>>>>>>> + if(lc($name) eq lc($existing)) {
>>>>>>>>>>>>>>> + return 104;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + return 0;
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Validate a custom list, returns number of rejected line on failure. Check for non-zero results.
>>>>>>>>>>>>>>> +# listref = array reference, cleanup = remove invalid entries instead of returning an error
>>>>>>>>>>>>>>> +sub _rpz_validate_customlist {
>>>>>>>>>>>>>>> + my ($listref, $cleanup) = @_;
>>>>>>>>>>>>>>> + $cleanup //= 0;
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + foreach my $index (reverse 0..$#{$listref}) {
>>>>>>>>>>>>>>> + my $row = @$listref[$index];
>>>>>>>>>>>>>>> + next unless($row); # Skip/allow empty lines
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Reject/remove everything besides wildcard domains and remarks
>>>>>>>>>>>>>>> + if((not &General::validwildcarddomainname($row)) && (not $row =~ /^;[\w \-()\.:;*\/\\?!&=]*$/)) {
>>>>>>>>>>>>>>> + unless($cleanup) {
>>>>>>>>>>>>>>> + # +1 for user friendly line number and to ensure non-zero exitcode
>>>>>>>>>>>>>>> + return $index + 1;
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Remove current row
>>>>>>>>>>>>>>> + splice(@$listref, $index, 1);
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + return 0;
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +###--- Internal misc functions ---###
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Send HTTP 303 redirect headers for post/request/get pattern
>>>>>>>>>>>>>>> +# (Must be sent before calling &Header::showhttpheaders())
>>>>>>>>>>>>>>> +sub _http_prg_redirect {
>>>>>>>>>>>>>>> + my $location = "https://$ENV{'SERVER_NAME'}:$ENV{'SERVER_PORT'}$ENV{'SCRIPT_NAME'}";
>>>>>>>>>>>>>>> + print "Status: 303 See Other\n";
>>>>>>>>>>>>>>> + print "Location: $location\n";
>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>> diff --git a/lfs/rpz b/lfs/rpz
>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>> index 000000000..7ddbc38e5
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/lfs/rpz
>>>>>>>>>>>>>>> @@ -0,0 +1,96 @@
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +# Definitions
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +include Config
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +SUMMARY = response policy zone - RPZ reputation system for unbound DNS
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +VER = 1.0.0
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +THISAPP = rpz-$(VER)
>>>>>>>>>>>>>>> +DIR_APP = $(DIR_SRC)/$(THISAPP)
>>>>>>>>>>>>>>> +TARGET = $(DIR_INFO)/$(THISAPP)
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +PROG = rpz
>>>>>>>>>>>>>>> +PAK_VER = 18
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +DEPS =
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +SERVICES =
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +# Top-level Rules
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +install : $(TARGET)
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +check :
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +download :
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +b2 :
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +dist:
>>>>>>>>>>>>>>> + @$(PAK)
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +# Installation Details
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +$(TARGET) :
>>>>>>>>>>>>>>> + @$(PREBUILD)
>>>>>>>>>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # RPZ scripts
>>>>>>>>>>>>>>> + install --verbose --mode=755 \
>>>>>>>>>>>>>>> + $(DIR_CONF)/rpz/{rpz-config,rpz-metrics,rpz-sleep,rpz-make,rpz-functions} \
>>>>>>>>>>>>>>> + --target-directory=/usr/sbin
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # RPZ config files
>>>>>>>>>>>>>>> + mkdir -pv /etc/unbound/local.d
>>>>>>>>>>>>>>> + install --verbose --mode=644 --owner=nobody --group=nobody \
>>>>>>>>>>>>>>> + $(DIR_CONF)/rpz/00-rpz.conf \
>>>>>>>>>>>>>>> + --target-directory=/etc/unbound/local.d
>>>>>>>>>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/local.d
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # RPZ custom list files for allow and block
>>>>>>>>>>>>>>> + mkdir -pv /var/ipfire/dns/rpz
>>>>>>>>>>>>>>> + touch /var/ipfire/dns/rpz/{allowlist,blocklist}
>>>>>>>>>>>>>>> + chown --verbose --recursive nobody:nobody /var/ipfire/dns/rpz
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # RPZ zone files
>>>>>>>>>>>>>>> + # create empty RPZ config file to avoid a unbound config error
>>>>>>>>>>>>>>> + mkdir -pv /etc/unbound/zonefiles
>>>>>>>>>>>>>>> + touch /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/zonefiles
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Install addon-specific language-files
>>>>>>>>>>>>>>> + install --verbose --mode=004 $(DIR_CONF)/rpz/rpz.*.pl \
>>>>>>>>>>>>>>> + --target-directory=/var/ipfire/addon-lang
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + # Install backup definition
>>>>>>>>>>>>>>> + cp -vf $(DIR_CONF)/backup/includes/rpz /var/ipfire/backup/addons/includes/rpz
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>>>>>>>>>> + @$(POSTBUILD)
>>>>>>>>>>>>>>> diff --git a/make.sh b/make.sh
>>>>>>>>>>>>>>> index 827ea9e77..a77535b13 100755
>>>>>>>>>>>>>>> --- a/make.sh
>>>>>>>>>>>>>>> +++ b/make.sh
>>>>>>>>>>>>>>> @@ -390,7 +390,7 @@ prepareenv() {
>>>>>>>>>>>>>>> if [ "${free_space}" -lt "${required_space}" ]; then
>>>>>>>>>>>>>>> # Add any consumed space
>>>>>>>>>>>>>>> while read -r consumed_space path; do
>>>>>>>>>>>>>>> - (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>>>>>>>>>> + (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>>>>>>>>>> done <<< "$(du --summarize --bytes "${BUILD_DIR}" "${IMAGES_DIR}" "${LOG_DIR}" 2>/dev/null)"
>>>>>>>>>>>>>>> fi
>>>>>>>>>>>>>>> @@ -2087,6 +2087,7 @@ build_system() {
>>>>>>>>>>>>>>> lfsmake2 btrfs-progs
>>>>>>>>>>>>>>> lfsmake2 inotify-tools
>>>>>>>>>>>>>>> lfsmake2 grub-btrfs
>>>>>>>>>>>>>>> + lfsmake2 rpz
>>>>>>>>>>>>>>> lfsmake2 linux
>>>>>>>>>>>>>>> lfsmake2 rtl8812au
>>>>>>>>>>>>>>> diff --git a/src/paks/rpz/install.sh b/src/paks/rpz/install.sh
>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>> index 000000000..ef99bf742
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/src/paks/rpz/install.sh
>>>>>>>>>>>>>>> @@ -0,0 +1,36 @@
>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +#
>>>>>>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>>>>>>> +extract_files
>>>>>>>>>>>>>>> +restore_backup ${NAME}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# fix user created files
>>>>>>>>>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>>>>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>>>>>>>>>> + /etc/unbound/zonefiles \
>>>>>>>>>>>>>>> + /etc/unbound/local.d
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Update Language cache
>>>>>>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# restart unbound to load config file
>>>>>>>>>>>>>>> +/etc/init.d/unbound restart
>>>>>>>>>>>>>>> diff --git a/src/paks/rpz/uninstall.sh b/src/paks/rpz/uninstall.sh
>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>> index 000000000..e11427df3
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/src/paks/rpz/uninstall.sh
>>>>>>>>>>>>>>> @@ -0,0 +1,38 @@
>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +#
>>>>>>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>>>>>>>>>> +/etc/init.d/unbound stop
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +make_backup ${NAME}
>>>>>>>>>>>>>>> +remove_files
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>>>>>>>>>> +# "[1723428668] unbound-control[17117:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>>>>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Update Language cache
>>>>>>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# start unbound to load unbound config file
>>>>>>>>>>>>>>> +/etc/init.d/unbound start
>>>>>>>>>>>>>>> diff --git a/src/paks/rpz/update.sh b/src/paks/rpz/update.sh
>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>> index 000000000..9bc340bc6
>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>> +++ b/src/paks/rpz/update.sh
>>>>>>>>>>>>>>> @@ -0,0 +1,52 @@
>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>> +#
>>>>>>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# from update.sh
>>>>>>>>>>>>>>> +extract_backup_includes
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>>>>>>>>>> +/etc/init.d/unbound stop
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# from uninstall.sh
>>>>>>>>>>>>>>> +make_backup ${NAME}
>>>>>>>>>>>>>>> +remove_files
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>>>>>>>>>> +# "unbound-control[nn:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>>>>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# from install.sh
>>>>>>>>>>>>>>> +extract_files
>>>>>>>>>>>>>>> +restore_backup ${NAME}
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# fix user created files
>>>>>>>>>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>>>>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>>>>>>>>>> + /etc/unbound/zonefiles \
>>>>>>>>>>>>>>> + /etc/unbound/local.d
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# Update Language cache
>>>>>>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>> +# restart unbound to load config files
>>>>>>>>>>>>>>> +/etc/init.d/unbound start
>>>>>>>>>>>>>>> --
>>>>>>>>>>>>>>> 2.39.5
>>>>>>>>>>>>>>>
>>>>>>>>>>>>> Jon
>>>>>>>>>>>>> --
>>>>>>>>>>>>> Jon Murphy
>>>>>>>>>>>>> jon.murphy@ipfire.org
>>>>>>>>>>>
>>>>>>>>>>> Jon
>>>>>>>>>>> --
>>>>>>>>>>> Jon Murphy
>>>>>>>>>>> jon.murphy@ipfire.org
>
>
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re[2]: [PATCH] RPZ: update code to include WEBGUI and additional languages
2025-03-24 14:40 ` Michael Tremer
@ 2025-03-24 14:42 ` Jon Murphy
2025-03-24 14:43 ` Michael Tremer
0 siblings, 1 reply; 30+ messages in thread
From: Jon Murphy @ 2025-03-24 14:42 UTC (permalink / raw)
To: Michael Tremer; +Cc: Bernhard Bitsch, IPFire: Development-List
Is there a:
server:
module-config: "respip validator iterator"
In your RPZ set-up?
------ Original Message ------
From "Michael Tremer" <michael.tremer@ipfire.org>
To "Jon Murphy" <jon.murphy@ipfire.org>
Cc "Bernhard Bitsch" <bbitsch@ipfire.org>; "IPFire: Development-List"
<development@lists.ipfire.org>
Date 3/24/2025 9:40:15 AM
Subject Re: [PATCH] RPZ: update code to include WEBGUI and additional
languages
>Because it is not doing it on my system...
>
>> On 24 Mar 2025, at 14:38, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>
>> Actually it did.
>>
>> Why do you think Unbound did not?
>>
>>
>> ------ Original Message ------
>> From "Michael Tremer" <michael.tremer@ipfire.org>
>> To "Jon Murphy" <jon.murphy@ipfire.org>
>> Cc "Bernhard Bitsch" <bbitsch@ipfire.org>; "IPFire: Development-List" <development@lists.ipfire.org>
>> Date 3/24/2025 9:36:53 AM
>> Subject Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
>>
>>> Unbound did not put those there...
>>>
>>>> On 24 Mar 2025, at 14:33, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>
>>>>
>>>>
>>>> And where are these stored?
>>>>
>>>> In `/etc/unbound/zonefiles`:
>>>>
>>>>
>>>> [root@ipfire ~] # ls -al /etc/unbound/zonefiles
>>>> total 20664
>>>> drwxr-xr-x 2 nobody nobody 4096 Mar 24 04:40 .
>>>> drwxr-xr-x 4 root root 4096 Mar 19 16:24 ..
>>>> -rw-r--r-- 1 nobody nobody 3999087 Mar 23 15:11 adhocSB.rpz
>>>> -rw-r--r-- 1 nobody nobody 1411 Mar 23 14:23 allow.rpz
>>>> -rw-r--r-- 1 nobody nobody 25355 Mar 24 04:40 AmazonTrkrHZ.rpz
>>>> -rw-r--r-- 1 nobody nobody 7241 Mar 24 04:40 AppleTrkrHZ.rpz
>>>> -rw-r--r-- 1 nobody nobody 178 Mar 23 14:23 block.rpz
>>>> -rw-r--r-- 1 nobody nobody 78496 Mar 24 04:40 DOHblockHZ.rpz
>>>> -rw-r--r-- 1 nobody nobody 16983551 Mar 24 04:40 MxProPlusHZ.rpz
>>>> -rw-r--r-- 1 nobody nobody 2893 Mar 24 04:40 tldHZ.rpz
>>>> -rw-r--r-- 1 nobody nobody 29419 Mar 24 04:40 WinTrkrHZ.rpz
>>>> [root@ipfire ~] #
>>>>
>>>>
>>>>
>>>> ------ Original Message ------
>>>> From "Michael Tremer" <michael.tremer@ipfire.org>
>>>> To "Bernhard Bitsch" <bbitsch@ipfire.org>
>>>> Cc development@lists.ipfire.org
>>>> Date 3/24/2025 9:25:40 AM
>>>> Subject Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
>>>>
>>>>> Hello,
>>>>>
>>>>>>
>>>>>> On 24 Mar 2025, at 13:33, Bernhard Bitsch <bbitsch@ipfire.org> wrote:
>>>>>> Am 24.03.2025 um 11:17 schrieb Michael Tremer:
>>>>>>>
>>>>>>>
>>>>>>> Hello Jon,
>>>>>>>>
>>>>>>>>
>>>>>>>> On 24 Mar 2025, at 00:00, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>> Michael,
>>>>>>>> FYI - I was wrong Unbound RPZ is _not_ watching the serial number, it is watching the "refresh", the number after the serial number.
>>>>>>>
>>>>>>> Refresh just tells the client how often to check for an update.
>>>>>>> If that is actually being set by the list publisher, then we have another problem here, because they could put some insanely low value there and we would then DDoS their infrastructure. I think we should keep it like we have it in other places that we control how often we want to check or pull for updates.
>>>>>>>
>>>>>> You are right. But an extra update process wastes additional processor time. The update mechanism of unbound does the check for update ( however it is realized ) nevertheless.
>>>>>
>>>>> Yes, doing more things needs resources. But we are not seriously considering whether an IPFire system has enough resources to perform the download of a text file, or are we?
>>>>>
>>>>>>
>>>>>>>> I understand that you don’t speak C, but you got the information from somewhere. Documentation maybe? Since that is out of date very often I like to consult the code.
>>>>>>>> From testing. Downloading rpz files using rpz unbound, and watching what happens. If the rpz file is setup for "once per day" refresh, then it only downloads one time.
>>>>>>>> However that won’t solve our problem . . . and having no cache.
>>>>>>>> In `/etc/unbound/tuning.conf` there is `rrset-cache-size: 128m`. Are you referring to a different cache.
>>>>>>>
>>>>>>> Naturally unbound is loading the zone into its memory which we generally call cache.
>>>>>>> When I say cache I am thinking about persistent data storage across multiple restarts of Unbound. If I am downloading 100 MiB of RPZ lists (which is presumably still on the lower end) and I reboot my firewall, I do not want to download the same data again. We can only ever download a list *once* unless we are 100% certain that it has changed. Then we can download it once again.
>>>>>>
>>>>>> The RPZ lists are stored in files in persistent storage. Unbound creates the internal cache from these.
>>>>>
>>>>> And where are these stored?
>>>>>
>>>>>>
>>>>>>>> Maybe we need to implement both?
>>>>>>>> Yes. There are very few AXFR list (I think only four were found). And many more HTTPS rpz files.
>>>>>>>> Jon
>>>>>>>> ------ Original Message ------
>>>>>>>> From "Michael Tremer" <michael.tremer@ipfire.org>
>>>>>>>> To "Jon Murphy" <jon.murphy@ipfire.org>
>>>>>>>> Cc "IPFire: Development-List" <development@lists.ipfire.org>
>>>>>>>> Date 3/20/2025 11:26:43 AM
>>>>>>>> Subject Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
>>>>>>>>
>>>>>>>>>
>>>>>>>>> Hello Jon,
>>>>>>>>> Please don’t forget to Cc the list...
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> On 19 Mar 2025, at 18:27, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>>>> Michael,
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> Where in the code is this implemented? I cannot find anything like this:
>>>>>>>>>>
>>>>>>>>>> Keep in mind I am not a "C" person. Maybe in this section?:
>>>>>>>>>> https://git.ipfire.org/?p=thirdparty/unbound.git;a=blob;f=services/authzone.c;hb=30b9cb5f813003d0a2b1c2e678652396615b1b7d#l5875
>>>>>>>>>
>>>>>>>>> This where the AXFR response is being handled when doing a DNS zone transfer. This code is not being called when performing a HTTP download.
>>>>>>>>> I understand that you don’t speak C, but you got the information from somewhere. Documentation maybe? Since that is out of date very often I like to consult the code.
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> —
>>>>>>>>>> When I was just learning about RPZ I created a separate RPZ file for testing. When I changed the SOA line with a new serial number, the RPZ file download would happen in about 5 minutes.
>>>>>>>>>> https://people.ipfire.org/~jon/sblack-adhoc.rpz
>>>>>>>>>
>>>>>>>>> It might well be that the file is not being reloaded if the download matches the content that unbound already has. That would of course save some resources.
>>>>>>>>> However that won’t solve our problem with redundant downloads and having no cache.
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> That is how I found out the SOA line is watched for a serial number change.
>>>>>>>>>> I’ll reconfirm my findings.
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>>> The second reason is that we have a lot of firewalls out there. Not all of them will enable this feature and all of the lists, but even if it is a good chunk, we will generate terabytes of traffic which put load on the infrastructure and will cost money. It simply is not what we want to do, regardless of self-hosting those lists and pulling them from somewhere else.
>>>>>>>>>>
>>>>>>>>>> So I understand, are you thinking of hosting RPZ AXFR (DNS zone transfer) on IPFire infrastructure?
>>>>>>>>>
>>>>>>>>> No, I don’t think that we can generally do this. The biggest problem is licensing as we cannot take anyones content and host it ourselves. We would re-distribute those lists and that will only work with permission of the publishers. I assume that would be too much work to actually get some useful content out there. We might limit ourselves to only those lists that are under a very permissive license. Nobody wants that.
>>>>>>>>> From a technical point of view, DNS over TCP might not be very nice in terms of forging the transfer and so we would need TLS as well… It should work, but even if we would be able to encourage other people to publish their lists I doubt they would implement DNS over TLS for authoritative DNS. That standard is in very early stages as well.
>>>>>>>>> As far as I can see, those vendors who offer a list as a commercial product are using DNS to distribute it (e.g. Spamhaus). Those people who have made this all a hobby are throwing the lists onto GitHub and let them handle the traffic.
>>>>>>>>> Maybe we need to implement both?
>>>>>>>>> -Michael
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> Jon
>>>>>>>>>> On 3/19/25 5:35 AM, Michael Tremer wrote:
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> Hello Jon,
>>>>>>>>>>> Where in the code is this implemented? I cannot find anything like this:
>>>>>>>>>>> Unbound loads the entire file into memory and then starts parsing it. The only special treatment there is is to check whether the first line is a valid zone entry. It does not even have to be a SOA record.
>>>>>>>>>>> https://git.ipfire.org/?p=thirdparty/unbound.git;a=blob;f=services/authzone.c;hb=30b9cb5f813003d0a2b1c2e678652396615b1b7d#l1188
>>>>>>>>>>> I am also concerned that Unbound will not be able to support an upstream proxy for any downloads. The caching situation is also unclear for me, so I believe that we will be looking at writing a custom downloader that implements all these things.
>>>>>>>>>>> -Michael
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>> On 19 Mar 2025, at 02:58, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>>>>>> Michael,
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>> The emphasis is on the repeated downloads of the same list. That is
>>>>>>>>>>>>
>>>>>>>>>>>> > what cannot happen.
>>>>>>>>>>>> The Unbound RPZ code, as installed within IPFire, watches for a change
>>>>>>>>>>>> in the SOA line of each RPZ file. This is an example of the first few
>>>>>>>>>>>> lines for every RPZ file.
>>>>>>>>>>>> $TTL 300
>>>>>>>>>>>> @ SOA localhost. root.localhost. 1742298960 43200 3600 86400 300
>>>>>>>>>>>> NS localhost.
>>>>>>>>>>>> ;
>>>>>>>>>>>> ; Title: HaGeZi's Pop-Up Ads DNS Blocklist
>>>>>>>>>>>> ; Description: Blocks annoying and malicious pop-up ads.
>>>>>>>>>>>> If the SOA serial number changes (e.g. the 1742298960), then Unbound RPZ
>>>>>>>>>>>> code does its thing and downloads. Otherwise there is no download.
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>> So there has to be a way to ensure that we won’t download a list again
>>>>>>>>>>>>
>>>>>>>>>>>> > unless it has actually changed.
>>>>>>>>>>>> This should do what you want but I may be missing your point.
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>> DNS has a builtin functionality called AXFR. It simply does the job
>>>>>>>>>>>>
>>>>>>>>>>>> > for you. I was just wondering whether that was not being used.
>>>>>>>>>>>> I need to read about AXFR/IXFR and learn a little more.
>>>>>>>>>>>> Jon
>>>>>>>>>>>> On 3/17/25 5:35 AM, Michael Tremer wrote:
>>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>> Good Morning Jon,
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> On 16 Mar 2025, at 17:00, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>>>>>>>> Michael,
>>>>>>>>>>>>>> I was reading through you response again an I want to understand this post:
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> So if RPZ doesn't use HTTPS, what is it using? I am missing a key point here.
>>>>>>>>>>>>>
>>>>>>>>>>>>> The emphasis is on the repeated downloads of the same list. That is what cannot happen.
>>>>>>>>>>>>> Although it might not affect a lot of people in our general user-base, there are some that have a metered connection and will pay for data by volume. Some of the lists I looked at are just under 20 MiB. Therefore we need to keep any traffic down to a minimum. The second reason is that we have a lot of firewalls out there. Not all of them will enable this feature and all of the lists, but even if it is a good chunk, we will generate terabytes of traffic which put load on the infrastructure and will cost money. It simply is not what we want to do, regardless of self-hosting those lists and pulling them from somewhere else.
>>>>>>>>>>>>> So there has to be a way to ensure that we won’t download a list again unless it has actually changed.
>>>>>>>>>>>>> DNS has a builtin functionality called AXFR. It simply does the job for you. I was just wondering whether that was not being used.
>>>>>>>>>>>>> HTTPS is an option because that is simply what we use elsewhere, but extra functionality will have to be built for it.
>>>>>>>>>>>>> -Michael
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Jon
>>>>>>>>>>>>>> On 2/13/25 3:34 PM, jon wrote:
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> Michael,
>>>>>>>>>>>>>>> I’ve read through your comments a few times and I ended up with many more questions.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> To me the efforts to get new code accepted seem to have changed and it seemed easier in the past. In the past I made the Core Team aware via the Dev Mailing List and wrote a simple two or three paragraphs of "What is it? / What is the value? / Here is the code"
>>>>>>>>>>>>>>> So in an effort to move forward: How exactly is something presented to the Core Team?
>>>>>>>>>>>>>>> Is there an example of a recent effort that was presented that I can see as a sample? (This type of info can also be added to the Wiki)
>>>>>>>>>>>>>>> I understand you want it this way, but I don’t know what exactly is needed. Please be specific.
>>>>>>>>>>>>>>> Jon
>>>>>>>>>>>>>>> PS - I am not ignoring your other comments, I am just trying to move forward and keep things simple.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> On Feb 8, 2025, at 1:27 PM, Michael Tremer <michael.tremer@ipfire.org> wrote:
>>>>>>>>>>>>>>>> Hello Jon,
>>>>>>>>>>>>>>>> Thanks for your reply. And good that you are copying everyone into this conversation.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> On 8 Feb 2025, at 18:41, jon <jon.murphy@ipfire.org> wrote:
>>>>>>>>>>>>>>>>> Michael,
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> Yes, this has been a separate effort (a very public separate effort). Yes, as you pointed this out early on with the "proof-of-concept" and then my request for people to help test RPZ. Nothing was hidden.
>>>>>>>>>>>>>>>>> This was done because you (and maybe others) did not have the time and I wanted to help and because I needed assistance with RPZ. I tried my best to do this without bothering you.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> I don’t that it is accurate that nobody wanted to help on this. The list was always open - although not every email has been replied to swiftly it is also your responsibility to raise a question again if it was missed. People here have open ears.
>>>>>>>>>>>>>>>> It was also stated on this very list on in our documentation that working on something without involving the core team is a risky undertaking. Of course IPFire is free software and so everyone is free to fork if they wish to do so.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> You were aware many steps along the way. See your email on July 28, 2024, August 15, 2024, September 30, 2024, December 23, 2024, and January 16. My attempts to get the team involved were met with "things are busy" and sometimes silence. (Yes, I get it, people are busy.)
>>>>>>>>>>>>>>>>> You and Adolf, Leo, Erik and Bernhard have been aware since the beginning. You mention you were aware of the "proof-of-concept". If you include those beginning posts, since Sep 2023.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> Yes, I am aware of a proof-of-concept that I have been running myself for a long time. I am also aware of the efforts that you have been taking.
>>>>>>>>>>>>>>>> Yet I don’t think there has ever been any joint effort, or am I seeing that wrong?
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> This has not been discussed . . . on our calls.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> On the July 28th you stated:
>>>>>>>>>>>>>>>>> "We have talked about RPZ many times on the monthly call since the URL filter feature is falling more and more out of fashion. I think there is also many posts about this on the forum."
>>>>>>>>>>>>>>>>> Please don’t insult me again by stating "you know what I mean".
>>>>>>>>>>>>>>>>> And it has been discussed but not documented in the Monthly Meeting notes.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> I am not at all insulting you. I don’t want to take this down to a personal level at all. This is a public mailing list and people who read this don’t need to listen to an argument we are having. They are here for the tech inside IPFire.
>>>>>>>>>>>>>>>> When I wrote that it has not been discussed that does not mean that we have not been touching on the topic. We have been talking about lots of things on the calls, the weather, politics, how our pets are. None of that makes it to the logs. What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> Regular conversation on the Dev Mailing list is many times met with silence. I get it, people are busy.
>>>>>>>>>>>>>>>>> And regular two-way conversation doesn’t happen on the list. At least not with me. I’d be happy to point out the posts that were met with silence.
>>>>>>>>>>>>>>>>> Again, I get it, people are busy.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> And you think my emails are not being met with silence? This has nothing to do with this specific topic. This has something to do with how occupied people are and how engaged they are on certain topics. Not everyone is involved in all the things and simply will ignore emails simply based on their subject line.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> But the "dip here to the list" were my attempts to get a conversation started. As I said, many time met with silence.
>>>>>>>>>>>>>>>>> The only place I was not met with silence was on the Community. You have a great group of people in the Community. It is a shame you don’t want to have others help. It would reduce your workload.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> You should stop making statements that are not true. Who doesn’t want anyone to help?
>>>>>>>>>>>>>>>> Not having this conversation on a Saturday evening would reduce my workload. At least it would free up time for something else. Helping with the things that are already on the go would reduce the workload of the entire team. Starting one thing at a time and finishing it is a lot better to manage than starting a hundred things and not even finish one. I can tell you that I already have a hundred things on the go.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> Therefore, what am I supposed to do with this email?
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> To me it is beyond obvious…
>>>>>>>>>>>>>>>>> If it isn’t what you want, then guide me with how to do this the correct way. And be specific. I am trying to help. I am trying to make things better. I am trying to do things the right way.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> To me it isn’t. This is yet another project that has been dumped to the list like so many before and later on everyone has left to have the team deal with the rest.
>>>>>>>>>>>>>>>> It is a huge patch set. You explained what the vision is, but that is about it. There is no chance this will continue if this disagreement isn’t solved first. I didn’t even look at the code.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> I don’t want to merge code that I don’t agree with.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> I asked multiple times if you "agreed with the concept" and again, met with silence. Yes I get it, people are busy.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> Having support for RPZ? Yes, it was definitely on the roadmap. That I agree with.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> You mentioned this a in the past, but for some reason you do not disclose what I dismissed. Why do you continue to make this harder, wouldn’t it not be easier to tell me what I have dismissed?
>>>>>>>>>>>>>>>>> I have sent multiple emails trying to answer your concerns and comments. On July 28, Aug 14, Aug 22, Aug 23, Sep 30, etc.
>>>>>>>>>>>>>>>>> I’ve gone through all of the questions you asked and I cannot find a "dismissed" item.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> Maybe I need to be *more clear*. I feel humoured by this.
>>>>>>>>>>>>>>>> It is late on a Saturday and I want my dinner soon, but certainly I have stated that this should never be an add-on considering it is supposed to replace URL Filter. We should never allow people to add their own sources. I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> The maintainers of Unbound and/or RPZ?
>>>>>>>>>>>>>>>>> The maintainers of Hagezi list, the threatfox list, the urlhaus list, etc.?
>>>>>>>>>>>>>>>>> What else? The maintainers or the RPZ scripts? That is me. Let’s talk!
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> You. I don’t care much about the providers of the lists.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> See, this is where it gets confusing. There are hundreds of open source packages as part of IPFire. Pick the last five years of items added to the IPFire build. You're telling me you have "constructive conversation with the maintainers" of all of the added packages?
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> They publish their software and they don’t care whether I am pulling it or not. They publish it with the commitment to maintain it - sometimes for better and sometimes for worse.
>>>>>>>>>>>>>>>> You care about me pulling your code and I don’t know whether you would commit to maintain this.
>>>>>>>>>>>>>>>> These two are very different cases.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> Pick the IP Blocklists list (i.e., 3CORESEC, ABUSECH, DSHIELD, SPAMHAUS, etc.) or the Suricata lists (i.e.,Emergingthreats.net <http://emergingthreats.net/>,Abuse.ch <http://abuse.ch/>, etc.). So you’ve have "constructive conversation with the maintainers"?
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> Yes, occasionally I have phone calls with a few of these providers.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> Ha! Yes a surprise. In the beginning you seemed interested as IPFire needed a replacement for URL Filter. You asked good questions about the lists picked, asked for the value to the users, etc. And I answered the best I could.
>>>>>>>>>>>>>>>>> You even asked: “Why is this realised as an add-on and not part of the core system?” from your Jul 28, 2024 email.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> Ah, so, why is the patch creating an add-on? Not that I am saying that what I say is law, but it has not been challenged either. If my input is being ignored, why should I put this to the top of my list of priorities? I am not disappointed about this, just trying to be very good with my time.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> And on January 16, 2025 I wrote a message looking for help. And you were kind to respond quickly. So in three weeks time, since the kind response, something has changed. You went from supportive to "this".
>>>>>>>>>>>>>>>>> So yes, I am surprised.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> Well, maybe I should not have replied to that email. It was clear that you were on some path that was not right, but you were not interested before in finding the right path from the beginning.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> Be more specific, what has to change? What exactly did I dismiss?
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> Dismissal is just my assumption. I don’t know what you actually did with my feedback. I can only see the end product that does not seem contain much of it. Repeatedly I have been pointing out that we should think before we build. I am sure a lot of hours have now gone into some code that simply does not satisfy me. And I am not not talking about the code itself, what it does is what I don’t think is right for us.
>>>>>>>>>>>>>>>> The process is very clear for me that we should first of all think whether we want a certain feature now. Then there should be a clear roadmap for everyone to follow; tasks can be split-up as we go and hopefully then have something that is maintainable, interesting for our users and even would do us proud. This is how this should work.
>>>>>>>>>>>>>>>> So, what has to change? I don’t think with shouting at each other, throwing patches around and making me generally unhappy is a good start.
>>>>>>>>>>>>>>>> -Michael
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> Jon
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> On Feb 6, 2025, at 2:13 PM, Michael Tremer <michael.tremer@ipfire.org> wrote:
>>>>>>>>>>>>>>>>>> Hello Jon,
>>>>>>>>>>>>>>>>>> Well, here we are again with another patch regarding this feature.
>>>>>>>>>>>>>>>>>> I cannot quite see from your email what the question is, but if this is a request to have this merged into IPFire, I am once again sorry to disappoint you.
>>>>>>>>>>>>>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls. Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation. Therefore, what am I supposed to do with this email?
>>>>>>>>>>>>>>>>>> I don’t want to merge code that I don’t agree with. So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>>>>>>>>>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>>>>>>>>>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>>>>>>>>>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>>>>>>>>>>>>>> All the best,
>>>>>>>>>>>>>>>>>> -Michael
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>> On 6 Feb 2025, at 16:35, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>>>>>>>>>>>>> What is it?
>>>>>>>>>>>>>>>>>>> Response Policy Zone (RPZ) is a mechanism to define local policies in a
>>>>>>>>>>>>>>>>>>> standardized way and load those policies from external sources.
>>>>>>>>>>>>>>>>>>> Bottom line: RPZ allows admins to easily block access to websites via DNS lookup.
>>>>>>>>>>>>>>>>>>> RPZ can block websites via categories. Examples include: fake websites, annoying
>>>>>>>>>>>>>>>>>>> pop-up ads, newly registered domains, DoH bypass sites, bad "host" services,
>>>>>>>>>>>>>>>>>>> maliscious top level domains (e.g., *.zip, *.mov), piracy, gambling, pornography,
>>>>>>>>>>>>>>>>>>> and more. RPZ lists come from various RPZ providers and their available
>>>>>>>>>>>>>>>>>>> catagories.
>>>>>>>>>>>>>>>>>>> This RPZ add-on enables the RPZ functionality by adding a couple lines in a
>>>>>>>>>>>>>>>>>>> configuration file. This add-on simply adds configuration files and adds
>>>>>>>>>>>>>>>>>>> scripts (config, metrics and sleep) to make RPZ easier for the admin to use.
>>>>>>>>>>>>>>>>>>> The RPZ scripts include additional languages: German, Spanish, French, Turkish,
>>>>>>>>>>>>>>>>>>> and Italian.
>>>>>>>>>>>>>>>>>>> RPZ itself was release in 2010 and has been part of the IPFire build since ~2015.
>>>>>>>>>>>>>>>>>>> Why is it needed? What is its value?
>>>>>>>>>>>>>>>>>>> - The RPZ concept places this filtering into IPFire, our internet access
>>>>>>>>>>>>>>>>>>> gateway, which is (should be) solely used as DNS source of the internal network.
>>>>>>>>>>>>>>>>>>> - As most sites use HTTPS it makes it difficult to filter traffic with URL
>>>>>>>>>>>>>>>>>>> Filter without also properly configuring conventional (non-transparent)
>>>>>>>>>>>>>>>>>>> mode on the proxy. RPZ is a nice replacement for the URL Filter.
>>>>>>>>>>>>>>>>>>> - No need to install and maintain an additional device like PiHole or AdBlock
>>>>>>>>>>>>>>>>>>> browser extensions on multiple user devices.
>>>>>>>>>>>>>>>>>>> - This is an additional layer of protection for users. Less worry someone will
>>>>>>>>>>>>>>>>>>> click on something that gets them into trouble. And, saying this with emphasis,
>>>>>>>>>>>>>>>>>>> the ability to do it in one place!
>>>>>>>>>>>>>>>>>>> - Blocked sites save on unneeded traffic and can lessen the threat of malware
>>>>>>>>>>>>>>>>>>> in advertisements
>>>>>>>>>>>>>>>>>>> - Logging allows the admin to see the site blocked and take actions
>>>>>>>>>>>>>>>>>>> - RPZ will be used at the home, home-office (work from home), schools,
>>>>>>>>>>>>>>>>>>> ministerial, and at the office. Device counts are small (2-6) to medium (~80)
>>>>>>>>>>>>>>>>>>> to mediam-large (200+).
>>>>>>>>>>>>>>>>>>> - RPZ can block ads, popups, phishing, scammers, spyware, malware, annoying
>>>>>>>>>>>>>>>>>>> popups, NSFW links, DOH servers, and the usual internet trash.
>>>>>>>>>>>>>>>>>>> ------------------------------
>>>>>>>>>>>>>>>>>>> Change Log for RPZ add-on
>>>>>>>>>>>>>>>>>>> rpz-1.0.0-18 on 2025-02-05
>>>>>>>>>>>>>>>>>>> - Build for approval & release as IPFire add-on
>>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>>> rpz-beta-0.1.18-18.ipfire on 2025-02-01
>>>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>>>> - new feature: added a mod key to force a unbound restart
>>>>>>>>>>>>>>>>>>> rpz-config and rpz-make:
>>>>>>>>>>>>>>>>>>> - new feature: added action for unbound restart `rpz-config unbound-restart`
>>>>>>>>>>>>>>>>>>> rpz-metrics:
>>>>>>>>>>>>>>>>>>> - simple reformatting
>>>>>>>>>>>>>>>>>>> - rename far right column from "last update" to "last download"
>>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>>> rpz-beta-0.1.17-17.ipfire on 2024-12-09
>>>>>>>>>>>>>>>>>>> rpz-make
>>>>>>>>>>>>>>>>>>> - bug fix: corrected validation regex for wildcards like: `*.domain.com`
>>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>>> rpz-beta-0.1.16-16.ipfire on 2024-11-18
>>>>>>>>>>>>>>>>>>> rpz-make
>>>>>>>>>>>>>>>>>>> - new feature: updated validation regex
>>>>>>>>>>>>>>>>>>> - bug fix: moved validation to beginning of process. Now we validate before
>>>>>>>>>>>>>>>>>>> creating config files.
>>>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>>>> - new feature: use CSS color variables of the main ipfire theme
>>>>>>>>>>>>>>>>>>> - bug fix: empty zonefile remarks were stored as “undef” and caused a warning
>>>>>>>>>>>>>>>>>>> - bug fix: HTML textarea removes the first empty line in a custom list
>>>>>>>>>>>>>>>>>>> - thank you Leo!
>>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>>> rpz-beta-0.1.15-15.ipfire on 2024-11-04
>>>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>>>> - new feature: added new language file for Turkish (thank you Peppe)
>>>>>>>>>>>>>>>>>>> rpz-make
>>>>>>>>>>>>>>>>>>> - bug fix: corrected empty allow/block list issue. An empty allow/block list
>>>>>>>>>>>>>>>>>>> will now remove contents of allow/block.rpz files and remove unneeded
>>>>>>>>>>>>>>>>>>> allow/block.conf file. (thank you iptom)
>>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>>> rpz-beta-0.1.14-14.ipfire on 2024-10-29
>>>>>>>>>>>>>>>>>>> rpz-config:
>>>>>>>>>>>>>>>>>>> - bug fix: correct missing rpz extension. `rpz-config list` displayed URL
>>>>>>>>>>>>>>>>>>> incorrectly (thank you Bernhard)
>>>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>>>> - bug fix: remove extra `"` in language files (thank you Bernhard)
>>>>>>>>>>>>>>>>>>> - new feature: slightly dim "apply" button when not enabled
>>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>>> rpz-beta-0.1.13-13.ipfire on 2024-10-27
>>>>>>>>>>>>>>>>>>> - skipped
>>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>>> rpz-beta-0.1.12-12.ipfire on 2024-10-21
>>>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>>>> - new feature: added new language file for French (thank you gw-ipfire)
>>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>>> rpz-beta-0.1.11-11.ipfire on 2024-10-18
>>>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>>>> - new feature: added new language file for Italian (thank you umberto)
>>>>>>>>>>>>>>>>>>> - new feature: added new language file for Spanish (thank you Roberto)
>>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>>> rpz-beta-0.1.10-10.ipfire on 2024-10-15
>>>>>>>>>>>>>>>>>>> rpz-make:
>>>>>>>>>>>>>>>>>>> - bug fix: corrected validation error for a custom list entry (thank you siosios)
>>>>>>>>>>>>>>>>>>> - e.g., `*.cloudflare-dns.com`
>>>>>>>>>>>>>>>>>>> install.sh:
>>>>>>>>>>>>>>>>>>> - bug fix: add chown to correct user created files
>>>>>>>>>>>>>>>>>>> update.sh:
>>>>>>>>>>>>>>>>>>> - bug fix: add chown to correct user created files (thank you siosios)
>>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>>> rpz-beta-0.1.9-9.ipfire on 2024-10-08
>>>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>>>> - new feature: added new language file for German (thank you Leo)
>>>>>>>>>>>>>>>>>>> - bug fix: add missing "rpz exitcode 110"
>>>>>>>>>>>>>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>>> rpz-beta-0.1.8-8.ipfire on 2024-10-04
>>>>>>>>>>>>>>>>>>> - skipped
>>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>>> rpz-beta-0.1.7-7.ipfire on 2024-10-03
>>>>>>>>>>>>>>>>>>> All:
>>>>>>>>>>>>>>>>>>> - new feature: includes beta version numbers for pakfire package,
>>>>>>>>>>>>>>>>>>> instead of only `rpz-1.0.0-1.ipfire`, for each release.
>>>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>>>> - new feature: added new WebGUI at `rpz.cgi`
>>>>>>>>>>>>>>>>>>> - a BIG thank you to Leo Hofmann for all of his work creating the webgui!!
>>>>>>>>>>>>>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>>>>>>>>>>>>> rpz-make:
>>>>>>>>>>>>>>>>>>> - new feature: validate entries in allowlist and blocklist
>>>>>>>>>>>>>>>>>>> - new feature: add "no-reload" option for WebGUI
>>>>>>>>>>>>>>>>>>> rpz-metrics:
>>>>>>>>>>>>>>>>>>> - new feature: info can be sorted by name, by hit count, by line count, by
>>>>>>>>>>>>>>>>>>> "enabled" list or all lists
>>>>>>>>>>>>>>>>>>> backups:
>>>>>>>>>>>>>>>>>>> - bug fix: include all files in `/var/ipfire/dns/rpz` directory in backup
>>>>>>>>>>>>>>>>>>> update.sh:
>>>>>>>>>>>>>>>>>>> - bug fix: corrected ownership for `/var/ipfire/dns/rpz` directory during an
>>>>>>>>>>>>>>>>>>> update
>>>>>>>>>>>>>>>>>>> Build:
>>>>>>>>>>>>>>>>>>> - bug fix: `block.rpz.conf` and `block.rpz` from build. Files to be created
>>>>>>>>>>>>>>>>>>> by `rpz-make`
>>>>>>>>>>>>>>>>>>> WebGUI and German language file
>>>>>>>>>>>>>>>>>>> Contribution-by: Leo-Andres Hofmann <hofmann@leo-andres.de>
>>>>>>>>>>>>>>>>>>> Spanish language file
>>>>>>>>>>>>>>>>>>> Contribution-by: Roberto Peña
>>>>>>>>>>>>>>>>>>> Italian language file
>>>>>>>>>>>>>>>>>>> Contribution-by: Umberto Parma
>>>>>>>>>>>>>>>>>>> French language file
>>>>>>>>>>>>>>>>>>> Contribution-by: gw-ipfire
>>>>>>>>>>>>>>>>>>> Turkish language file
>>>>>>>>>>>>>>>>>>> Contribution-by: Peppe Tech
>>>>>>>>>>>>>>>>>>> Contribution-by: Bernhard Bitsch <bbitsch@ipfire.org>
>>>>>>>>>>>>>>>>>>> Contribution-by: Erik Kapfer <erik.kapfer@ipfire.org>
>>>>>>>>>>>>>>>>>>> Signed-off-by: Jon Murphy <jon.murphy@ipfire.org
>>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>>> config/backup/includes/rpz | 4 +
>>>>>>>>>>>>>>>>>>> config/cfgroot/manualpages | 1 +
>>>>>>>>>>>>>>>>>>> config/menu/EX-rpz.menu | 6 +
>>>>>>>>>>>>>>>>>>> config/rootfiles/common/configroot | 1 +
>>>>>>>>>>>>>>>>>>> config/rootfiles/common/web-user-interface | 1 +
>>>>>>>>>>>>>>>>>>> config/rootfiles/packages/rpz | 20 +
>>>>>>>>>>>>>>>>>>> config/rpz/00-rpz.conf | 10 +
>>>>>>>>>>>>>>>>>>> config/rpz/rpz-config | 130 +++
>>>>>>>>>>>>>>>>>>> config/rpz/rpz-functions | 85 ++
>>>>>>>>>>>>>>>>>>> config/rpz/rpz-make | 203 +++++
>>>>>>>>>>>>>>>>>>> config/rpz/rpz-metrics | 170 ++++
>>>>>>>>>>>>>>>>>>> config/rpz/rpz-sleep | 58 ++
>>>>>>>>>>>>>>>>>>> config/rpz/rpz.de.pl | 30 +
>>>>>>>>>>>>>>>>>>> config/rpz/rpz.en.pl | 30 +
>>>>>>>>>>>>>>>>>>> config/rpz/rpz.es.pl | 30 +
>>>>>>>>>>>>>>>>>>> config/rpz/rpz.fr.pl | 30 +
>>>>>>>>>>>>>>>>>>> config/rpz/rpz.it.pl | 30 +
>>>>>>>>>>>>>>>>>>> config/rpz/rpz.tr.pl | 30 +
>>>>>>>>>>>>>>>>>>> html/cgi-bin/rpz.cgi | 923 +++++++++++++++++++++
>>>>>>>>>>>>>>>>>>> lfs/rpz | 96 +++
>>>>>>>>>>>>>>>>>>> make.sh | 3 +-
>>>>>>>>>>>>>>>>>>> src/paks/rpz/install.sh | 36 +
>>>>>>>>>>>>>>>>>>> src/paks/rpz/uninstall.sh | 38 +
>>>>>>>>>>>>>>>>>>> src/paks/rpz/update.sh | 52 ++
>>>>>>>>>>>>>>>>>>> 24 files changed, 2016 insertions(+), 1 deletion(-)
>>>>>>>>>>>>>>>>>>> create mode 100644 config/backup/includes/rpz
>>>>>>>>>>>>>>>>>>> create mode 100644 config/menu/EX-rpz.menu
>>>>>>>>>>>>>>>>>>> create mode 100644 config/rootfiles/packages/rpz
>>>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/00-rpz.conf
>>>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz-config
>>>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz-functions
>>>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz-make
>>>>>>>>>>>>>>>>>>> create mode 100755 config/rpz/rpz-metrics
>>>>>>>>>>>>>>>>>>> create mode 100755 config/rpz/rpz-sleep
>>>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.de.pl
>>>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.en.pl
>>>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.es.pl
>>>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.fr.pl
>>>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.it.pl
>>>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.tr.pl
>>>>>>>>>>>>>>>>>>> create mode 100644 html/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>>>>>> create mode 100644 lfs/rpz
>>>>>>>>>>>>>>>>>>> create mode 100644 src/paks/rpz/install.sh
>>>>>>>>>>>>>>>>>>> create mode 100644 src/paks/rpz/uninstall.sh
>>>>>>>>>>>>>>>>>>> create mode 100644 src/paks/rpz/update.sh
>>>>>>>>>>>>>>>>>>> diff --git a/config/backup/includes/rpz b/config/backup/includes/rpz
>>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>>> index 000000000..36513e494
>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>> +++ b/config/backup/includes/rpz
>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,4 @@
>>>>>>>>>>>>>>>>>>> +/var/ipfire/dns/rpz/*
>>>>>>>>>>>>>>>>>>> +/etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>>>>>> +/etc/unbound/zonefiles/block.rpz
>>>>>>>>>>>>>>>>>>> +/etc/unbound/local.d/*rpz.conf
>>>>>>>>>>>>>>>>>>> diff --git a/config/cfgroot/manualpages b/config/cfgroot/manualpages
>>>>>>>>>>>>>>>>>>> index 1f7e01efc..d3a48c633 100644
>>>>>>>>>>>>>>>>>>> --- a/config/cfgroot/manualpages
>>>>>>>>>>>>>>>>>>> +++ b/config/cfgroot/manualpages
>>>>>>>>>>>>>>>>>>> @@ -70,6 +70,7 @@ pakfire.cgi=configuration/ipfire/pakfire
>>>>>>>>>>>>>>>>>>> wlanap.cgi=addons/wireless
>>>>>>>>>>>>>>>>>>> tor.cgi=addons/tor
>>>>>>>>>>>>>>>>>>> samba.cgi=addons/samba
>>>>>>>>>>>>>>>>>>> +rpz.cgi=addons/rpz
>>>>>>>>>>>>>>>>>>> # Logs menu
>>>>>>>>>>>>>>>>>>> logs.cgi/summary.dat=configuration/logs/summary
>>>>>>>>>>>>>>>>>>> diff --git a/config/menu/EX-rpz.menu b/config/menu/EX-rpz.menu
>>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>>> index 000000000..2f4daf410
>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>> +++ b/config/menu/EX-rpz.menu
>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,6 @@
>>>>>>>>>>>>>>>>>>> +$subipfire->{'20.rpz'} = {
>>>>>>>>>>>>>>>>>>> + 'caption' => $Lang::tr{'rpz'},
>>>>>>>>>>>>>>>>>>> + 'uri' => '/cgi-bin/rpz.cgi',
>>>>>>>>>>>>>>>>>>> + 'title' => "RPZ",
>>>>>>>>>>>>>>>>>>> + 'enabled' => 1,
>>>>>>>>>>>>>>>>>>> +};
>>>>>>>>>>>>>>>>>>> diff --git a/config/rootfiles/common/configroot b/config/rootfiles/common/configroot
>>>>>>>>>>>>>>>>>>> index 9839eee45..b30d6aae4 100644
>>>>>>>>>>>>>>>>>>> --- a/config/rootfiles/common/configroot
>>>>>>>>>>>>>>>>>>> +++ b/config/rootfiles/common/configroot
>>>>>>>>>>>>>>>>>>> @@ -120,6 +120,7 @@ var/ipfire/menu.d/70-log.menu
>>>>>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-apcupsd.menu
>>>>>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-guardian.menu
>>>>>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-mympd.menu
>>>>>>>>>>>>>>>>>>> +#var/ipfire/menu.d/EX-rpz.menu
>>>>>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-samba.menu
>>>>>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-tor.menu
>>>>>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-transmission.menu
>>>>>>>>>>>>>>>>>>> diff --git a/config/rootfiles/common/web-user-interface b/config/rootfiles/common/web-user-interface
>>>>>>>>>>>>>>>>>>> index 816241dae..e00464076 100644
>>>>>>>>>>>>>>>>>>> --- a/config/rootfiles/common/web-user-interface
>>>>>>>>>>>>>>>>>>> +++ b/config/rootfiles/common/web-user-interface
>>>>>>>>>>>>>>>>>>> @@ -69,6 +69,7 @@ srv/web/ipfire/cgi-bin/proxy.cgi
>>>>>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/qos.cgi
>>>>>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/remote.cgi
>>>>>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/routing.cgi
>>>>>>>>>>>>>>>>>>> +#srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>>>>>> #srv/web/ipfire/cgi-bin/samba.cgi
>>>>>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/services.cgi
>>>>>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/shutdown.cgi
>>>>>>>>>>>>>>>>>>> diff --git a/config/rootfiles/packages/rpz b/config/rootfiles/packages/rpz
>>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>>> index 000000000..1c8663049
>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>> +++ b/config/rootfiles/packages/rpz
>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,20 @@
>>>>>>>>>>>>>>>>>>> +etc/unbound/local.d/00-rpz.conf
>>>>>>>>>>>>>>>>>>> +etc/unbound/zonefiles
>>>>>>>>>>>>>>>>>>> +etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>>>>>> +usr/sbin/rpz-config
>>>>>>>>>>>>>>>>>>> +usr/sbin/rpz-functions
>>>>>>>>>>>>>>>>>>> +usr/sbin/rpz-make
>>>>>>>>>>>>>>>>>>> +usr/sbin/rpz-metrics
>>>>>>>>>>>>>>>>>>> +usr/sbin/rpz-sleep
>>>>>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.de.pl
>>>>>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.en.pl
>>>>>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.es.pl
>>>>>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.fr.pl
>>>>>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.it.pl
>>>>>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.tr.pl
>>>>>>>>>>>>>>>>>>> +var/ipfire/backup/addons/includes/rpz
>>>>>>>>>>>>>>>>>>> +var/ipfire/dns/rpz
>>>>>>>>>>>>>>>>>>> +var/ipfire/dns/rpz/allowlist
>>>>>>>>>>>>>>>>>>> +var/ipfire/dns/rpz/blocklist
>>>>>>>>>>>>>>>>>>> +var/ipfire/menu.d/EX-rpz.menu
>>>>>>>>>>>>>>>>>>> +srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>>>>>> diff --git a/config/rpz/00-rpz.conf b/config/rpz/00-rpz.conf
>>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>>> index 000000000..f005a4f2e
>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>> +++ b/config/rpz/00-rpz.conf
>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,10 @@
>>>>>>>>>>>>>>>>>>> +server:
>>>>>>>>>>>>>>>>>>> + module-config: "respip validator iterator"
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +rpz:
>>>>>>>>>>>>>>>>>>> + name: allow.rpz
>>>>>>>>>>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>>>>>> + rpz-action-override: passthru
>>>>>>>>>>>>>>>>>>> + rpz-log: yes
>>>>>>>>>>>>>>>>>>> + rpz-log-name: allow
>>>>>>>>>>>>>>>>>>> + rpz-signal-nxdomain-ra: yes
>>>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-config b/config/rpz/rpz-config
>>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>>> index 000000000..c72d50f9b
>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-config
>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,130 @@
>>>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +version="2025-01-11 - v44"
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +tagName="unbound"
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +rpzAction="${1}" # input RPZ action
>>>>>>>>>>>>>>>>>>> +rpzName="${2}" # input RPZ name
>>>>>>>>>>>>>>>>>>> +rpzURL="${3}" # input RPZ URL
>>>>>>>>>>>>>>>>>>> +rpzOption1="${4}" # input RPZ option #1
>>>>>>>>>>>>>>>>>>> +rpzOption2="${5}" # input RPZ option #2
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +rpzConfig="/etc/unbound/local.d/${rpzName}.rpz.conf" # output zone conf file
>>>>>>>>>>>>>>>>>>> +rpzFile="/etc/unbound/zonefiles/${rpzName}.rpz" # output for RPZ file
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>>>>>>>>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>>>>>>>>>>> + case "$1" in
>>>>>>>>>>>>>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>>>>>>>>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>>>>>>>>>>>>>> + esac
>>>>>>>>>>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +case "${rpzAction}" in
>>>>>>>>>>>>>>>>>>> + # add new rpz list
>>>>>>>>>>>>>>>>>>> + add )
>>>>>>>>>>>>>>>>>>> + check_name "${rpzName}" # is this a valid name?
>>>>>>>>>>>>>>>>>>> + # does this config already exist? If yes, then exit
>>>>>>>>>>>>>>>>>>> + if [[ -f "${rpzConfig}" ]] ; then
>>>>>>>>>>>>>>>>>>> + msg_log "error: rpz: duplicate - ${rpzConfig} already exists. exit"
>>>>>>>>>>>>>>>>>>> + exit 104
>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # is this a valid URL?
>>>>>>>>>>>>>>>>>>> + regex='^https://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
>>>>>>>>>>>>>>>>>>> + if ! [[ "${rpzURL}" =~ $regex ]] ; then
>>>>>>>>>>>>>>>>>>> + msg_log "error: rpz: the URL is not valid: \"${rpzURL}\". exit."
>>>>>>>>>>>>>>>>>>> + exit 105
>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # create the zone config file
>>>>>>>>>>>>>>>>>>> + {
>>>>>>>>>>>>>>>>>>> + echo "rpz:"
>>>>>>>>>>>>>>>>>>> + echo " name: ${rpzName}.rpz"
>>>>>>>>>>>>>>>>>>> + echo " zonefile: ${rpzFile}"
>>>>>>>>>>>>>>>>>>> + echo " url: ${rpzURL}"
>>>>>>>>>>>>>>>>>>> + echo " rpz-action-override: nxdomain"
>>>>>>>>>>>>>>>>>>> + echo " rpz-log: ${rpzLog}"
>>>>>>>>>>>>>>>>>>> + echo " rpz-log-name: ${rpzName}"
>>>>>>>>>>>>>>>>>>> + echo " rpz-signal-nxdomain-ra: yes"
>>>>>>>>>>>>>>>>>>> + } > "${rpzConfig}"
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # set-up zonefile
>>>>>>>>>>>>>>>>>>> + # create an empty rpz file if it does not exist
>>>>>>>>>>>>>>>>>>> + if [[ ! -f "${rpzFile}" ]] ; then
>>>>>>>>>>>>>>>>>>> + touch "${rpzFile}"
>>>>>>>>>>>>>>>>>>> + # unbound requires these settings for rpz files
>>>>>>>>>>>>>>>>>>> + set_permissions "${rpzFile}" "${rpzConfig}"
>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # trash config file & rpz file
>>>>>>>>>>>>>>>>>>> + remove )
>>>>>>>>>>>>>>>>>>> + if ! [[ -f "${rpzConfig}" ]] ; then
>>>>>>>>>>>>>>>>>>> + msg_log "error: rpz: cannot remove ${rpzConfig}, does not exist. exit"
>>>>>>>>>>>>>>>>>>> + exit 106
>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + msg_log "info: rpz: remove config file & rpz file \"${rpzName}\""
>>>>>>>>>>>>>>>>>>> + rm "${rpzConfig}"
>>>>>>>>>>>>>>>>>>> + rm "${rpzFile}"
>>>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + reload )
>>>>>>>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + list )
>>>>>>>>>>>>>>>>>>> + awk -F':' '/^\s*name:/{ gsub(/[[:blank:]]|\.rpz/, "",$2) ; NAME=$2 } \
>>>>>>>>>>>>>>>>>>> + /^\s*url:/{ gsub(/[[:blank:]]/, "") ; print NAME"="$2":"$3} ' \
>>>>>>>>>>>>>>>>>>> + /etc/unbound/local.d/*rpz.conf
>>>>>>>>>>>>>>>>>>> + exit
>>>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + unbound-restart )
>>>>>>>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>>>>>>>> + unbound_restart
>>>>>>>>>>>>>>>>>>> + exit
>>>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + * )
>>>>>>>>>>>>>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>>>>>>>>>>>>>> + printf "Usage: $(basename "$0") <ACTION> <NAME> <URL> <OPTION> <OPTION>\n"
>>>>>>>>>>>>>>>>>>> + printf "Version: ${version}\n"
>>>>>>>>>>>>>>>>>>> + exit 108
>>>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +esac
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +exit
>>>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-functions b/config/rpz/rpz-functions
>>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>>> index 000000000..ace1d2690
>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-functions
>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,85 @@
>>>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +version="2024-12-10 - v02"
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +msg_log () {
>>>>>>>>>>>>>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>>>>>>>>>>>>>> + if tty --silent ; then
>>>>>>>>>>>>>>>>>>> + echo "${tagName}:" "$*"
>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# check for a valid name
>>>>>>>>>>>>>>>>>>> +check_name () {
>>>>>>>>>>>>>>>>>>> + local theName="${1}"
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + regex='^[a-zA-Z0-9_]+$' # no dash or plus, alpha numeric only
>>>>>>>>>>>>>>>>>>> + regex1='^(allow|block)$' # allow and block are reserved NAMEs
>>>>>>>>>>>>>>>>>>> + if [[ ! "${theName}" =~ $regex ]] || [[ "${theName}" =~ $regex1 ]] ; then
>>>>>>>>>>>>>>>>>>> + msg_log "error: rpz: the NAME is not valid: \"${theName}\". exit."
>>>>>>>>>>>>>>>>>>> + exit 101
>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +set_permissions () {
>>>>>>>>>>>>>>>>>>> + chown nobody:nobody "$@"
>>>>>>>>>>>>>>>>>>> + chmod 644 "$@"
>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +check_unbound_conf () {
>>>>>>>>>>>>>>>>>>> + local thecheckConf="${1:-yes}" # check config default is yes
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # check the above config files
>>>>>>>>>>>>>>>>>>> + if [[ "${thecheckConf}" == yes ]] ; then
>>>>>>>>>>>>>>>>>>> + msg_log "info: rpz: check for errors with \"unbound-checkconf\""
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + if ! unbound-checkconf ; then
>>>>>>>>>>>>>>>>>>> + msg_log "error: rpz: unbound-checkconf found invalid configuration."
>>>>>>>>>>>>>>>>>>> + msg_log \
>>>>>>>>>>>>>>>>>>> + "error: rpz: In Terminal run the command \"unbound-checkconf\" for more information. exit."
>>>>>>>>>>>>>>>>>>> + exit 102
>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +unbound_control_reload () {
>>>>>>>>>>>>>>>>>>> + local theReload="${1:-yes}" # reload default is yes
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + if [[ "${theReload}" == yes ]] ; then
>>>>>>>>>>>>>>>>>>> + # reload due to the changes
>>>>>>>>>>>>>>>>>>> + msg_log "info: rpz: run \"unbound-control reload\""
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + if ! unbound-control reload ; then
>>>>>>>>>>>>>>>>>>> + msg_log "error: rpz: unbound-control reload. exit."
>>>>>>>>>>>>>>>>>>> + exit 109
>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +unbound_restart () {
>>>>>>>>>>>>>>>>>>> + # restart due to the changes
>>>>>>>>>>>>>>>>>>> + msg_log "info: rpz: run \"unbound restart\""
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + /usr/local/bin/unboundctrl restart
>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-make b/config/rpz/rpz-make
>>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>>> index 000000000..927d55170
>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-make
>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,203 @@
>>>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +version="2025-01-11 - v14"
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# create the config file for allow
>>>>>>>>>>>>>>>>>>> +make_allow_config () {
>>>>>>>>>>>>>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>>>>>>>>>>>>>> + local theConfig="/etc/unbound/local.d/00-rpz.conf" # output zone conf file
>>>>>>>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/allowlist" # input custom list of domains
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + msg_log "info: rpz: make config file \"00-rpz.conf\""
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + echo "server:
>>>>>>>>>>>>>>>>>>> + module-config: \"respip validator iterator\"" > "${theConfig}"
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # does allow list exist?
>>>>>>>>>>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + echo "rpz:
>>>>>>>>>>>>>>>>>>> + name: allow.rpz
>>>>>>>>>>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>>>>>> + rpz-action-override: passthru
>>>>>>>>>>>>>>>>>>> + rpz-log: ${theLog}
>>>>>>>>>>>>>>>>>>> + rpz-log-name: allow
>>>>>>>>>>>>>>>>>>> + rpz-signal-nxdomain-ra: yes" >> "${theConfig}"
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>>>>>>>>>>> + set_permissions "${theConfig}"
>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# create the config file for block
>>>>>>>>>>>>>>>>>>> +make_block_config () {
>>>>>>>>>>>>>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>>>>>>>>>>>>>> + local theConfig="/etc/unbound/local.d/block.rpz.conf" # output zone conf file
>>>>>>>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/blocklist" # input custom list of domains
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + msg_log "info: rpz: make config file \"block.rpz.conf\""
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # does block list exist?
>>>>>>>>>>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + echo "rpz:
>>>>>>>>>>>>>>>>>>> + name: block.rpz
>>>>>>>>>>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/block.rpz
>>>>>>>>>>>>>>>>>>> + rpz-action-override: nxdomain
>>>>>>>>>>>>>>>>>>> + rpz-log: ${theLog}
>>>>>>>>>>>>>>>>>>> + rpz-log-name: block
>>>>>>>>>>>>>>>>>>> + rpz-signal-nxdomain-ra: yes" > "${theConfig}"
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>>>>>>>>>>> + set_permissions "${theConfig}"
>>>>>>>>>>>>>>>>>>> + else
>>>>>>>>>>>>>>>>>>> + # no - trash the config file
>>>>>>>>>>>>>>>>>>> + rm --verbose /etc/unbound/local.d/block.rpz.conf
>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# create an RPZ file for allow or block
>>>>>>>>>>>>>>>>>>> +make_rpz_file () {
>>>>>>>>>>>>>>>>>>> + local theName="${1}" # allow or block
>>>>>>>>>>>>>>>>>>> + local theAction='.' # the default is nxdomain or block
>>>>>>>>>>>>>>>>>>> + local actionList
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>>>>>>>>>>>>>> + local theZonefile="/etc/unbound/zonefiles/${theName}.rpz" # output file for RPZ
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # does a list exist?
>>>>>>>>>>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # for allow set to passthru
>>>>>>>>>>>>>>>>>>> + [[ "${theName}" == allow ]] && theAction='rpz-passthru.'
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # drop any extra "blanks" and add "CNAME <RPZ action>." to each line
>>>>>>>>>>>>>>>>>>> + actionList=$( awk '{$1=$1};1' "${theList}" |
>>>>>>>>>>>>>>>>>>> + sed "/^[^;].*[[:alnum:]]/ s|$| CNAME ${theAction}|" )
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + msg_log "info: rpz: create zonefile for \"${theName}list\""
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +echo "; Name: ${theName} list
>>>>>>>>>>>>>>>>>>> +; Last modified: $(date "+%Y-%m-%d at %H.%M.%S %Z")
>>>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>>>> +; domains with actions list
>>>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>>>> +${actionList}" > "${theZonefile}"
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>>>>>>>>>>> + set_permissions "${theZonefile}"
>>>>>>>>>>>>>>>>>>> + # set-up allow/block list files
>>>>>>>>>>>>>>>>>>> + set_permissions "${theList}"
>>>>>>>>>>>>>>>>>>> + else
>>>>>>>>>>>>>>>>>>> + msg_log "info: rpz: the ${theList} is empty."
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + rm --verbose "${theZonefile}" # trash the RPZ file
>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# check if allow/block list is valid
>>>>>>>>>>>>>>>>>>> +validate_list () {
>>>>>>>>>>>>>>>>>>> + local theName="${1}" # allow or block
>>>>>>>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # remove good:
>>>>>>>>>>>>>>>>>>> + # - properly formated domain names with or without leading wildcard
>>>>>>>>>>>>>>>>>>> + # - properly formated top level domain (TLD) names with wildcard
>>>>>>>>>>>>>>>>>>> + # - blank lines and comment lines
>>>>>>>>>>>>>>>>>>> + # remaining lines are considered "bad"
>>>>>>>>>>>>>>>>>>> + bad_lines=$( sed --regexp-extended \
>>>>>>>>>>>>>>>>>>> + '/^(\*\.)?([a-zA-Z0-9](([a-zA-Z0-9\-]){0,61}[a-zA-Z0-9])?\.)+([a-zA-Z]{2,}|xn--[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])$/d ;
>>>>>>>>>>>>>>>>>>> + /^(\*\.)([a-z]{2,61}|xn--[a-z0-9]{1,60})$/d ;
>>>>>>>>>>>>>>>>>>> + /^$/d ; /^;/d' "${theList}" )
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + if [[ ! -z "${bad_lines}" ]] ; then
>>>>>>>>>>>>>>>>>>> + msg_log "error: rpz: invalid line(s) in ${theList}."
>>>>>>>>>>>>>>>>>>> + printf "%s\n" "bad line(s): ${bad_lines}"
>>>>>>>>>>>>>>>>>>> + exit 110
>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +tagName="unbound"
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +rpzName="${1}" # input RPZ name
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>>>>>>>>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>>>>>>>>>>> + case "$1" in
>>>>>>>>>>>>>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>>>>>>>>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>>>>>>>>>>>>>> + esac
>>>>>>>>>>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +case "${rpzName}" in
>>>>>>>>>>>>>>>>>>> + # make a new allow or block rpz file
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + allow )
>>>>>>>>>>>>>>>>>>> + validate_list 'allow' # is the allowlist valid?
>>>>>>>>>>>>>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>>>>>>>>>>>>>> + make_rpz_file 'allow'
>>>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + allowblock )
>>>>>>>>>>>>>>>>>>> + validate_list 'allow' # is the list valid?
>>>>>>>>>>>>>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>>>>>>>>>>>>>> + make_rpz_file 'allow'
>>>>>>>>>>>>>>>>>>> + ;&
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + block )
>>>>>>>>>>>>>>>>>>> + validate_list 'block' # is the blocklist valid?
>>>>>>>>>>>>>>>>>>> + make_block_config "${rpzLog}"
>>>>>>>>>>>>>>>>>>> + make_rpz_file 'block'
>>>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + reload )
>>>>>>>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + unbound-restart )
>>>>>>>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>>>>>>>> + unbound_restart
>>>>>>>>>>>>>>>>>>> + exit
>>>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + * )
>>>>>>>>>>>>>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>>>>>>>>>>>>>> + printf "Usage: $(basename "$0") <NAME> <OPTION> <OPTION>\n"
>>>>>>>>>>>>>>>>>>> + printf "Version: ${version}\n"
>>>>>>>>>>>>>>>>>>> + exit 108
>>>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>>>> +esac
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +exit
>>>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-metrics b/config/rpz/rpz-metrics
>>>>>>>>>>>>>>>>>>> new file mode 100755
>>>>>>>>>>>>>>>>>>> index 000000000..4d43e1629
>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-metrics
>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,170 @@
>>>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +version="2025-01-20 - v25"
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +weeks="2" # default to two message logs
>>>>>>>>>>>>>>>>>>> +sortBy="name" # default "by name"
>>>>>>>>>>>>>>>>>>> +rpzActive="enabled" # default "enabled only"
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>>>>>>>>>>> + case "$1" in
>>>>>>>>>>>>>>>>>>> + --by-names | --by-name | name ) sortBy="name" ;;
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + --by-hits | --by-hit | hits | hit ) sortBy="hit" ;;
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + --by-lines | --by-line | lines | line ) sortBy="line" ;;
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + --by-effect ) sortBy="effect" ;;
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + --enabled-only ) rpzActive="enabled" ;;
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + --active-all | --all | all ) rpzActive="all" ;;
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + [0-9] | [0-9][0-9] ) weeks=$1 ;;
>>>>>>>>>>>>>>>>>>> + esac
>>>>>>>>>>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# get the list of message logs for N weeks
>>>>>>>>>>>>>>>>>>> +messageLogs=$( find /var/log/messages* -type f | sort --version-sort |
>>>>>>>>>>>>>>>>>>> + head -"${weeks}" )
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# get the list of RPZ names & counts from the message log(s)
>>>>>>>>>>>>>>>>>>> +rpzNameCount=$( for logf in ${messageLogs} ; do
>>>>>>>>>>>>>>>>>>> + zgrep --text --fixed-strings 'info: rpz: applied' "${logf}" |
>>>>>>>>>>>>>>>>>>> + awk '$10 ~ /\[\w*]/ { print $10 }' ;
>>>>>>>>>>>>>>>>>>> + done | sort | uniq --count )
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# flip results and remove brackets `[` and `]`
>>>>>>>>>>>>>>>>>>> +rpzNameCount=$( echo "${rpzNameCount}" |
>>>>>>>>>>>>>>>>>>> + awk '{ print $2, $1 }' |
>>>>>>>>>>>>>>>>>>> + sed --regexp-extended 's|^\[(.*)\]|\1|' )
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# grab only names
>>>>>>>>>>>>>>>>>>> +rpzNames=$( echo "${rpzNameCount}" | awk '{ print $1 }' )
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# get list of RPZ files
>>>>>>>>>>>>>>>>>>> +rpzFileList=$( find /etc/unbound/zonefiles -type f -iname "*.rpz" )
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# get basename of those files
>>>>>>>>>>>>>>>>>>> +rpzBaseNames=$( echo "${rpzFileList}" |
>>>>>>>>>>>>>>>>>>> + sed 's|/etc/unbound/zonefiles/||g ; s|\.rpz||g ;' )
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# add to rpzNames
>>>>>>>>>>>>>>>>>>> +rpzNames="${rpzNames}"$'\n'"${rpzBaseNames}"
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# drop duplicate names
>>>>>>>>>>>>>>>>>>> +rpzNames=$( echo "${rpzNames}" | sort --unique )
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# get line count for each RPZ
>>>>>>>>>>>>>>>>>>> +lineCount=$( echo "${rpzFileList}" | xargs wc -l )
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# get comment line count and blank line count for each RPZ
>>>>>>>>>>>>>>>>>>> +commentCount=$( echo "${rpzFileList}" | xargs grep --count -e "^$" -e "^;" )
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# get modified date each RPZ
>>>>>>>>>>>>>>>>>>> +modDateList=$( echo "${rpzFileList}" | xargs stat -c '%.10y %n' )
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +ucListAuthZones=$( unbound-control list_auth_zones )
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# get width of RPZ names
>>>>>>>>>>>>>>>>>>> +pWidth=$( echo "${rpzNames}" | awk '{ print $1" " }' | wc -L )
>>>>>>>>>>>>>>>>>>> +pFormat="%-${pWidth}s %-8s %-8s %8s %12s %12s\n"
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# print title line
>>>>>>>>>>>>>>>>>>> +printf "${pFormat}" "name" "hits" "active" "lines" " hits/line" "last download"
>>>>>>>>>>>>>>>>>>> +printf -- "--------------"
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +theResults=""
>>>>>>>>>>>>>>>>>>> +totalLines=0
>>>>>>>>>>>>>>>>>>> +totalHits=0
>>>>>>>>>>>>>>>>>>> +while read -r theName
>>>>>>>>>>>>>>>>>>> +do
>>>>>>>>>>>>>>>>>>> + printf -- "--" # pretend progress bar
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # is this RPZ list active?
>>>>>>>>>>>>>>>>>>> + theActive="disabled"
>>>>>>>>>>>>>>>>>>> + if grep --quiet "^${theName}\.rpz" <<< "${ucListAuthZones}"
>>>>>>>>>>>>>>>>>>> + then
>>>>>>>>>>>>>>>>>>> + theActive="enabled"
>>>>>>>>>>>>>>>>>>> + else
>>>>>>>>>>>>>>>>>>> + [[ "${rpzActive}" == enabled ]] && continue
>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # get hit count
>>>>>>>>>>>>>>>>>>> + theHits="0"
>>>>>>>>>>>>>>>>>>> + if output=$( grep "^${theName}\s" <<< "${rpzNameCount}" ) ; then
>>>>>>>>>>>>>>>>>>> + theHits=$( echo "${output}" | awk '{ print $2 }' )
>>>>>>>>>>>>>>>>>>> + totalHits=$(( totalHits + theHits ))
>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # get line count
>>>>>>>>>>>>>>>>>>> + theLines="n/a"
>>>>>>>>>>>>>>>>>>> + hitsPerLine="0"
>>>>>>>>>>>>>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${lineCount}" ) ; then
>>>>>>>>>>>>>>>>>>> + theLines=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>>>>>>>>>>>>>> + totalLines=$(( totalLines + theLines ))
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + if [[ "${theLines}" -gt 2 ]] ; then
>>>>>>>>>>>>>>>>>>> + hitsPerLine=$(( 100 * theHits / theLines ))
>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # get modification date
>>>>>>>>>>>>>>>>>>> + theModDate="n/a"
>>>>>>>>>>>>>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${modDateList}" ) ; then
>>>>>>>>>>>>>>>>>>> + theModDate=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # add to results list
>>>>>>>>>>>>>>>>>>> + theResults+="${theName} ${theHits} ${theActive} ${theLines} ${hitsPerLine} ${theModDate}"$'\n'
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +done <<< "${rpzNames}"
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +case "${sortBy}" in
>>>>>>>>>>>>>>>>>>> + # sort by "active" then by "name"
>>>>>>>>>>>>>>>>>>> + name) sortArg=(-k3,3r -k1,1) ;;
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # sort by "active" then by "hits" then by "name"
>>>>>>>>>>>>>>>>>>> + hit) sortArg=(-k3,3r -k2,2nr -k1,1) ;;
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # sort by "active" then by "lines" then by "name"
>>>>>>>>>>>>>>>>>>> + line) sortArg=(-k3,3r -k4,4nr -k1,1) ;;
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # sort by "active" then by "effect" then by "name"
>>>>>>>>>>>>>>>>>>> + effect) sortArg=(-k3,3r -k5,5nr -k1,1) ;;
>>>>>>>>>>>>>>>>>>> +esac
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +printf -- "--------------\n"
>>>>>>>>>>>>>>>>>>> +# remove blank lines, sort, print as columns
>>>>>>>>>>>>>>>>>>> +echo "${theResults}" |
>>>>>>>>>>>>>>>>>>> + awk '!/^[[:space:]]*$/' |
>>>>>>>>>>>>>>>>>>> + sort "${sortArg[@]}" |
>>>>>>>>>>>>>>>>>>> + awk --assign=width="${pWidth}" \
>>>>>>>>>>>>>>>>>>> + '{ printf "%-*s %-8s %-8s %8s %10s %% %12s\n", width, $1, $2, $3, $4, $5, $6 }'
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +printf "${pFormat}" "" "=======" "" "========" "" ""
>>>>>>>>>>>>>>>>>>> +printf "${pFormat}" "Totals -->" "${totalHits}" "" "${totalLines}" "" ""
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +exit
>>>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-sleep b/config/rpz/rpz-sleep
>>>>>>>>>>>>>>>>>>> new file mode 100755
>>>>>>>>>>>>>>>>>>> index 000000000..dd3603599
>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-sleep
>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,58 @@
>>>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +version="2024-08-16" # v05
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# send message to message log
>>>>>>>>>>>>>>>>>>> +msg_log () {
>>>>>>>>>>>>>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>>>>>>>>>>>>>> + if tty --silent ; then
>>>>>>>>>>>>>>>>>>> + echo "${tagName}:" "$*"
>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +tagName="unbound"
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +sleepTime="${1:-5m}" # default to sleep for 5m (5 minutes)
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +zoneList=$( unbound-control list_auth_zones | awk '{print $1}' )
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +for zone in ${zoneList} ; do
>>>>>>>>>>>>>>>>>>> + printf "disable ${zone}\t"
>>>>>>>>>>>>>>>>>>> + unbound-control rpz_disable "${zone}"
>>>>>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +msg_log "info: rpz: disabled all zones for ${sleepTime}"
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +sleep "${sleepTime}"
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +for zone in ${zoneList} ; do
>>>>>>>>>>>>>>>>>>> + printf "enable ${zone}\t"
>>>>>>>>>>>>>>>>>>> + unbound-control rpz_enable "${zone}"
>>>>>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +msg_log "info: rpz: enabled all zones"
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +exit
>>>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.de.pl b/config/rpz/rpz.de.pl
>>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>>> index 000000000..3770c6bb0
>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.de.pl
>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>>>>>> +'rpz apply' => 'Übernehmen',
>>>>>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Benutzerdefinierte Allowlist aktivieren:',
>>>>>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Zugelassene Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>>>> +'rpz cl allow' => 'Benutzerdefinierte Allowlist',
>>>>>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Benutzerdefinierte Blocklist aktivieren:',
>>>>>>>>>>>>>>>>>>> +'rpz cl block info' => 'Gesperrte Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>>>> +'rpz cl block' => 'Benutzerdefinierte Blocklist',
>>>>>>>>>>>>>>>>>>> +'rpz cl' => 'Benutzerdefinierte Listen',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'Der Name enthält unzulässige Zeichen',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf hat eine fehlerhafte Konfiguration ermittelt. Führen Sie das Kommando unbound-checkconf auf der Konsole aus, um weitere Informationen zu erhalten.',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'Die benutzerdefinierte Allow-/Blocklist ist leer',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'Ein Eintrag mit identischem Namen existiert bereits',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'Die URL ist ungültig',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'Eintrag kann nicht entfernt werden, der Name existiert nicht',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'Der Name ist ungültig - nur "allow" oder "block" möglich',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'Fehlende oder inkorrekte Parameter',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control reload ist fehlgeschlagen',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'Die benutzerdefinierte Allow-/Blocklist enthält unzulässige Einträge',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'Die Anmerkung enthält unzulässige Zeichen',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'Ungültiger Eintrag in der benutzerdefinierten Allowlist, Zeile ',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'Ungültiger Eintrag in der benutzerdefinierten Blocklist, Zeile ',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'Ausgewählter Eintrag existiert nicht: ',
>>>>>>>>>>>>>>>>>>> +'rpz zf editor' => 'Zonendatei-Eintrag bearbeiten',
>>>>>>>>>>>>>>>>>>> +'rpz zf imported' => '(importiert aus rpz-config)',
>>>>>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Erlaubte Zeichen sind a-z, A-Z, 0-9 und Unterstriche',
>>>>>>>>>>>>>>>>>>> +'rpz zf' => 'Zonendateien',
>>>>>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.en.pl b/config/rpz/rpz.en.pl
>>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>>> index 000000000..0720a8940
>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.en.pl
>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>>>>>> +'rpz apply' => 'Apply',
>>>>>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Enable custom allowlist:',
>>>>>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Allowed domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>>>> +'rpz cl allow' => 'Custom allowlist',
>>>>>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Enable custom blocklist:',
>>>>>>>>>>>>>>>>>>> +'rpz cl block info' => 'Blocked domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>>>> +'rpz cl block' => 'Custom blocklist',
>>>>>>>>>>>>>>>>>>> +'rpz cl' => 'Custom lists',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'the NAME is not valid',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf found invalid configuration. In the Terminal run the command unbound-checkconf for more information',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'the allow/block list is empty',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'duplicate - NAME already exists',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'the URL is not valid',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'cannot remove the NAME does not exist',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'the NAME is not valid - "allow" or "block" only',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'missing or incorrect parameter',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control reload failed',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'custom Allowlist/Blocklist contains invalid entries',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'the REMARK is not valid',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'invalid entry in allowlist, line ',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'invalid entry in blocklist, line ',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'Selected entry does not exist: ',
>>>>>>>>>>>>>>>>>>> +'rpz zf editor' => 'Edit zonefiles entry',
>>>>>>>>>>>>>>>>>>> +'rpz zf imported' => '(imported from rpz-config)',
>>>>>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Valid characters are a-z, A-Z, 0-9 and underscore.',
>>>>>>>>>>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.es.pl b/config/rpz/rpz.es.pl
>>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>>> index 000000000..98628e4aa
>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.es.pl
>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>>>>>> +'rpz' => 'Zonas de política de respuesta (RPZ)',
>>>>>>>>>>>>>>>>>>> +'rpz apply' => 'Aplicar',
>>>>>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Habilitar la lista blanca personalizada:',
>>>>>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Dominio permitido (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>>>> +'rpz cl allow' => 'Lista blanca personalizada',
>>>>>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Habilitar la lista negra personalizada:',
>>>>>>>>>>>>>>>>>>> +'rpz cl block info' => 'Dominio bloqueado (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>>>> +'rpz cl block' => 'Lista negra personalizada',
>>>>>>>>>>>>>>>>>>> +'rpz cl' => 'Lista personalizada',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'El NOMBRE no es válido',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha encontrado una configuración no válida. Desde Terminal, ejecute el comando unbound-checkconf para mayor información',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'La lista de permitidos/bloqueados está vacía',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'duplicado - NOMBRE ya existe',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'la URL no es válida',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'no es posible eliminar el NOMBRE que no existe',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'el NOMBRE no es válido - sólo "permitir" o "bloquear"',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'parámetro faltante o incorrecto',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'Error al recargar unbound-control',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'la Lista blanca/Lista negra personalizada contiene entradas no válidas',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'el COMENTARIO no es válido',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'entrada no válida en la lista blanca, línea ',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'entrada no válida en la lista negra, línea ',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'La entrada seleccionada no existe: ',
>>>>>>>>>>>>>>>>>>> +'rpz zf editor' => 'Editar la entrada de archivos de zona',
>>>>>>>>>>>>>>>>>>> +'rpz zf imported' => '(importado de rpz-config)',
>>>>>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Los caracteres válidos son a-z, A-Z, 0-9 y guión bajo',
>>>>>>>>>>>>>>>>>>> +'rpz zf' => 'Archivos de zona',
>>>>>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.fr.pl b/config/rpz/rpz.fr.pl
>>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>>> index 000000000..f35f3c2d0
>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.fr.pl
>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>>>>>> +'rpz apply' => 'Appliquer',
>>>>>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Activer la liste d\'autorisations personnalisée:',
>>>>>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Domaines autorisés (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>>>> +'rpz cl allow' => 'Liste d\'autorisations personnalisée',
>>>>>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Activer la liste de blocage personnalisée:',
>>>>>>>>>>>>>>>>>>> +'rpz cl block info' => 'Domaines bloqués (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>>>> +'rpz cl block' => 'liste de blocage personnalisé',
>>>>>>>>>>>>>>>>>>> +'rpz cl' => 'Listes personnalisées',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'le NOM n\'est pas valide',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf configuration non valide trouvée. Dans le terminal, exécutez la commande unbound-checkconf pour plus d\'informations',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'la liste autoriser/bloquer est vide',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'le NOM existe déjà',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'L\'URL n\'est pas valide',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'impossible de supprimer le NOM n\'existe pas',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'le NOM n\'est pas valide - « autoriser » ou « bloquer » seulement',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'paramètre manquant ou incorrect',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control rechargement échoué',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'la liste autoriser/bloquer contient des entrées non valides',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'la REMARQUE n\'est pas valable',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'entrée non valide dans la liste d\'autorisation, ligne ',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'entrée non valide dans la liste de blocs, ligne ',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'L\'entrée sélectionnée n\'existe pas: ',
>>>>>>>>>>>>>>>>>>> +'rpz zf editor' => 'Modifier l\'entrée Fichiers Zone',
>>>>>>>>>>>>>>>>>>> +'rpz zf imported' => '(importé de rpz-config)',
>>>>>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Les caractères valides sont a-z, A-Z, 0-9 et soulignement.',
>>>>>>>>>>>>>>>>>>> +'rpz zf' => 'Fichiers Zone',
>>>>>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.it.pl b/config/rpz/rpz.it.pl
>>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>>> index 000000000..ee81605c9
>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.it.pl
>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>>>>>> +'rpz apply' => 'Applica',
>>>>>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Abilita la Whitelist personalizzata:',
>>>>>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Domini consentiti (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>>>> +'rpz cl allow' => 'Whitelist personalizzata',
>>>>>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Abilita la Blacklist personalizzata:',
>>>>>>>>>>>>>>>>>>> +'rpz cl block info' => 'Domini bloccati (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>>>> +'rpz cl block' => 'Blacklist personalizzata',
>>>>>>>>>>>>>>>>>>> +'rpz cl' => 'Liste personalizzate',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'il NOME non è valido',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha trovato una configurazione non valida. Dal Terminale esegui il comando unbound-checkconf per maggiori informazioni',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'l\'elenco consentiti/bloccati è vuoto',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'duplicato - NAME esiste di già',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'l\'URL non è valido',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'non è possibile rimuovere il NOME non esiste',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'il NOME non è valido - solo "consenti" o "blocca"',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'parametro mancante o non corretto',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'ricaricamento del controllo non associato non riuscito',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'la Whitelist/Blacklist personalizzata contiene voci non valide',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'l"OSSERVAZIONE non è valida',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'voce non valida nella Whitelist, riga ',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'voce non valida nella Blacklist, riga ',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'La voce selezionata non esiste: ',
>>>>>>>>>>>>>>>>>>> +'rpz zf editor' => 'Modifica la voce dei file di zona',
>>>>>>>>>>>>>>>>>>> +'rpz zf imported' => '(importato da rpz-config)',
>>>>>>>>>>>>>>>>>>> +'rpz zf remark info' => 'I caratteri validi sono a-z, A-Z, 0-9 e trattino basso',
>>>>>>>>>>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.tr.pl b/config/rpz/rpz.tr.pl
>>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>>> index 000000000..00226e192
>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.tr.pl
>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>>>>>> +# I�in eklendi Ayriyeten Response Policy Zone (RPZ)
>>>>>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>>>>>> +'rpz apply' => 'Uygulamak',
>>>>>>>>>>>>>>>>>>> +'rpz cl allow enable' => '�zel Etkinlestir allowlist:',
>>>>>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Izin verilmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>>>> +'rpz cl allow' => 'Etkinlestir allowlist',
>>>>>>>>>>>>>>>>>>> +'rpz cl block enable' => '�zel Etkinlestir blocklist:',
>>>>>>>>>>>>>>>>>>> +'rpz cl block info' => 'Engellenmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>>>> +'rpz cl block' => 'Engellenmis blocklist',
>>>>>>>>>>>>>>>>>>> +'rpz cl' => 'Engellenmis lists',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'NAME ge�erli degil',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ge�ersiz yapilandirma bulundu. Terminalde daha fazla bilgi i�in Unbound-checkConf komutunu �alistirin',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'allow/block liste bos',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'kopyalamak - NAME zaten var',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'URL ge�erli degil',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 106' => '�ikarilamiyor NAME yok',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'NAME ge�erli degil - "allow" veya "block" yalniz',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'Parametre eksik veya yanlis',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control basarisiz',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'Engellenmis Allowlist/Blocklist ge�ersiz girisler i�erir',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'REMARK ge�erli degil',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'Ge�ersiz giris allowlist, line ',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'Ge�ersiz giris blocklist, line ',
>>>>>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'Se�ilen giris yok: ',
>>>>>>>>>>>>>>>>>>> +'rpz zf editor' => 'yazimlamak zonefiles giris',
>>>>>>>>>>>>>>>>>>> +'rpz zf imported' => '(ithal edildi rpz-config)',
>>>>>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Ge�erli karakterler a-z, A-Z, 0-9ve alt�st.',
>>>>>>>>>>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>>>>>> diff --git a/html/cgi-bin/rpz.cgi b/html/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>>> index 000000000..a821c92ac
>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>> +++ b/html/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,923 @@
>>>>>>>>>>>>>>>>>>> +#!/usr/bin/perl
>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>>>> +# Copyright (C) 2005-2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +use strict;
>>>>>>>>>>>>>>>>>>> +use Scalar::Util qw(looks_like_number);
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# debugging
>>>>>>>>>>>>>>>>>>> +#use warnings;
>>>>>>>>>>>>>>>>>>> +#use CGI::Carp 'fatalsToBrowser';
>>>>>>>>>>>>>>>>>>> +#use Data::Dumper;
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +require '/var/ipfire/general-functions.pl';
>>>>>>>>>>>>>>>>>>> +require "${General::swroot}/lang.pl";
>>>>>>>>>>>>>>>>>>> +require "${General::swroot}/header.pl";
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +###--- Extra HTML ---###
>>>>>>>>>>>>>>>>>>> +my $extraHead = <<END
>>>>>>>>>>>>>>>>>>> +<style>
>>>>>>>>>>>>>>>>>>> + /* alternating row background */
>>>>>>>>>>>>>>>>>>> + .tbl tr:nth-child(2n+2) {
>>>>>>>>>>>>>>>>>>> + background-color: var(--color-light-grey);
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> + .tbl tr:nth-child(2n+3) {
>>>>>>>>>>>>>>>>>>> + background-color: var(--color-grey);
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> + /* text styles */
>>>>>>>>>>>>>>>>>>> + .tbl th:not(:last-child) {
>>>>>>>>>>>>>>>>>>> + text-align: left;
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> + div.right {
>>>>>>>>>>>>>>>>>>> + text-align: right;
>>>>>>>>>>>>>>>>>>> + margin-top: 0.5em;
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> + /* customlist input */
>>>>>>>>>>>>>>>>>>> + textarea.domainlist {
>>>>>>>>>>>>>>>>>>> + margin: 0.5em 0;
>>>>>>>>>>>>>>>>>>> + resize: vertical;
>>>>>>>>>>>>>>>>>>> + min-height: 10em;
>>>>>>>>>>>>>>>>>>> + overflow: auto;
>>>>>>>>>>>>>>>>>>> + white-space: pre;
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> + button[type=submit]:disabled {
>>>>>>>>>>>>>>>>>>> + opacity: 0.6;
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> +</style>
>>>>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>>>> +###--- End of extra HTML ---###
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +### Settings ###
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# Request DNS service reload after configuration change
>>>>>>>>>>>>>>>>>>> +my $RPZ_RELOAD_FLAG = "${General::swroot}/dns/rpz/reload.flag";
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# Configuration file for all available zonefiles
>>>>>>>>>>>>>>>>>>> +# Format: index, name (unique), enabled (on/off), URL, remark
>>>>>>>>>>>>>>>>>>> +my $ZONEFILES_CONF = "${General::swroot}/dns/rpz/zonefiles.conf";
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# Configuration file for custom lists
>>>>>>>>>>>>>>>>>>> +# IDs: 0=allowlist, 1=blocklist, 2=options (allow/block enabled)
>>>>>>>>>>>>>>>>>>> +my $CUSTOMLISTS_CONF = "${General::swroot}/dns/rpz/customlists.conf";
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# Export custom lists to rpz-config
>>>>>>>>>>>>>>>>>>> +my $RPZ_ALLOWLIST = "${General::swroot}/dns/rpz/allowlist";
>>>>>>>>>>>>>>>>>>> +my $RPZ_BLOCKLIST = "${General::swroot}/dns/rpz/blocklist";
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +### Preparation ###
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# Create missing config files
>>>>>>>>>>>>>>>>>>> +unless(-f $ZONEFILES_CONF) { &General::system('touch', "$ZONEFILES_CONF"); }
>>>>>>>>>>>>>>>>>>> +unless(-f $CUSTOMLISTS_CONF) { &General::system('touch', "$CUSTOMLISTS_CONF"); }
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +## Global gui data
>>>>>>>>>>>>>>>>>>> +my $errormessage = "";
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +## Global configuration data
>>>>>>>>>>>>>>>>>>> +my %zonefiles = ();
>>>>>>>>>>>>>>>>>>> +my %customlists = ();
>>>>>>>>>>>>>>>>>>> +&_zonefiles_load();
>>>>>>>>>>>>>>>>>>> +&_customlists_load();
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +## Global CGI form data
>>>>>>>>>>>>>>>>>>> +my %cgiparams = ();
>>>>>>>>>>>>>>>>>>> +&Header::getcgihash(\%cgiparams);
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +my $action = $cgiparams{'ACTION'} // 'NONE';
>>>>>>>>>>>>>>>>>>> +my $action_key = $cgiparams{'KEY'} // ''; # entry being edited, empty = none/new
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +###--- Process form actions ---###
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# Zonefiles action: Check whether the requested entry exists
>>>>>>>>>>>>>>>>>>> +if((substr($action, 0, 3) eq 'ZF_') && ($action_key)) {
>>>>>>>>>>>>>>>>>>> + unless(defined $zonefiles{$action_key}) {
>>>>>>>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr(204, $action_key);
>>>>>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +## Perform actions
>>>>>>>>>>>>>>>>>>> +if($action eq 'ZF_SAVE') { ## Save new or modified zonefiles entry
>>>>>>>>>>>>>>>>>>> + if(&_action_zf_save()) {
>>>>>>>>>>>>>>>>>>> + $action = 'NONE'; # success, return to main page
>>>>>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>>>>> + $action = 'ZF_EDIT'; # error occured, keep editing
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +} elsif($action eq 'ZF_TOGGLE') { ## Toggle on/off
>>>>>>>>>>>>>>>>>>> + if(&_action_zf_toggle()) {
>>>>>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +} elsif($action eq 'ZF_REMOVE') { ## Remove entry
>>>>>>>>>>>>>>>>>>> + if(&_action_zf_remove()) {
>>>>>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +} elsif($action eq 'CL_SAVE') { ## Save custom lists
>>>>>>>>>>>>>>>>>>> + if(&_action_cl_save()) {
>>>>>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +} elsif($action eq 'RPZ_RELOAD') { ## Reload dns configuration
>>>>>>>>>>>>>>>>>>> + if(&_action_rpz_reload()) {
>>>>>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +} elsif($action eq 'UNB_RESTART') { ## Restart unbound service
>>>>>>>>>>>>>>>>>>> + if(&_action_unb_restart()) {
>>>>>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +###--- Start GUI ---###
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +## Start http output
>>>>>>>>>>>>>>>>>>> +&Header::showhttpheaders();
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# Start HTML
>>>>>>>>>>>>>>>>>>> +&Header::openpage($Lang::tr{'rpz'}, 1, $extraHead);
>>>>>>>>>>>>>>>>>>> +&Header::openbigbox('100%', 'left', '');
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# Show error messages
>>>>>>>>>>>>>>>>>>> +if($errormessage) {
>>>>>>>>>>>>>>>>>>> + &_print_message($errormessage);
>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# Handle zonefile add/edit mode
>>>>>>>>>>>>>>>>>>> +if($action eq "ZF_EDIT") {
>>>>>>>>>>>>>>>>>>> + &_print_zonefile_editor();
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Finalize page and exit cleanly
>>>>>>>>>>>>>>>>>>> + &Header::closebigbox();
>>>>>>>>>>>>>>>>>>> + &Header::closepage();
>>>>>>>>>>>>>>>>>>> + exit(0);
>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# Show gui elements
>>>>>>>>>>>>>>>>>>> +&_print_zonefiles();
>>>>>>>>>>>>>>>>>>> +&_print_customlists();
>>>>>>>>>>>>>>>>>>> +&_print_gui_extras();
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +&Header::closebigbox();
>>>>>>>>>>>>>>>>>>> +&Header::closepage();
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +###--- End of GUI ---###
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +###--- Internal configuration file functions ---###
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# Load all available zonefiles from rpz-config and the internal configuration
>>>>>>>>>>>>>>>>>>> +sub _zonefiles_load {
>>>>>>>>>>>>>>>>>>> + # Clean start
>>>>>>>>>>>>>>>>>>> + %zonefiles = ();
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Source 1: Get the currently enabled zonefiles from rpz-config (expected format [name]=[URL])
>>>>>>>>>>>>>>>>>>> + my @enabled_files = &General::system_output('/usr/sbin/rpz-config', 'list');
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + foreach my $row (@enabled_files) {
>>>>>>>>>>>>>>>>>>> + chomp($row);
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Use regex instead of split() to skip non-matching lines
>>>>>>>>>>>>>>>>>>> + next unless($row =~ /^(\w+)=(.+)$/);
>>>>>>>>>>>>>>>>>>> + my ($name, $url) = ($1, $2);
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Unique names are already guaranteed by rpz-config
>>>>>>>>>>>>>>>>>>> + if(&_rpz_validate_zonefile($name, $url, '', 0) == 0) {
>>>>>>>>>>>>>>>>>>> + # Populate global data hash, mark all found entries as enabled
>>>>>>>>>>>>>>>>>>> + my %entry = ('enabled' => 'on',
>>>>>>>>>>>>>>>>>>> + 'url' => $url,
>>>>>>>>>>>>>>>>>>> + 'remark' => $Lang::tr{'rpz zf imported'});
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Source 2: Get additional data and disabled entries from configuration file
>>>>>>>>>>>>>>>>>>> + my %configured_files = ();
>>>>>>>>>>>>>>>>>>> + &General::readhasharray($ZONEFILES_CONF, \%configured_files);
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + foreach my $row (values (%configured_files)) {
>>>>>>>>>>>>>>>>>>> + my ($name, $enabled, $url, $remark) = @$row;
>>>>>>>>>>>>>>>>>>> + $remark //= "";
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + next unless($name);
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Check whether this row belongs to an entry already imported from rpz-config
>>>>>>>>>>>>>>>>>>> + if(defined $zonefiles{$name}) {
>>>>>>>>>>>>>>>>>>> + # Existing entry, only merge additional data
>>>>>>>>>>>>>>>>>>> + $zonefiles{$name}{'remark'} = $remark;
>>>>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>>>>> + # Skip entry if it is marked as enabled but not found by rpz-config. It was then deleted manually
>>>>>>>>>>>>>>>>>>> + if($enabled ne 'on') {
>>>>>>>>>>>>>>>>>>> + # Populate global data hash
>>>>>>>>>>>>>>>>>>> + my %entry = ('enabled' => 'off',
>>>>>>>>>>>>>>>>>>> + 'url' => $url // "",
>>>>>>>>>>>>>>>>>>> + 'remark' => $remark);
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# Save internal zonefiles configuration
>>>>>>>>>>>>>>>>>>> +sub _zonefiles_save_conf {
>>>>>>>>>>>>>>>>>>> + my $index = 0;
>>>>>>>>>>>>>>>>>>> + my %export = ();
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Loop trough all zonefiles and create "hasharray" type export
>>>>>>>>>>>>>>>>>>> + foreach my $name (keys %zonefiles) {
>>>>>>>>>>>>>>>>>>> + my @entry = ($name,
>>>>>>>>>>>>>>>>>>> + $zonefiles{$name}{'enabled'},
>>>>>>>>>>>>>>>>>>> + $zonefiles{$name}{'url'},
>>>>>>>>>>>>>>>>>>> + $zonefiles{$name}{'remark'});
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + $export{$index++} = \@entry;
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + &General::writehasharray($ZONEFILES_CONF, \%export);
>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# Load custom lists from rpz-config and the internal configuration
>>>>>>>>>>>>>>>>>>> +sub _customlists_load {
>>>>>>>>>>>>>>>>>>> + # Clean start
>>>>>>>>>>>>>>>>>>> + %customlists = ();
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Load configuration file
>>>>>>>>>>>>>>>>>>> + my %lists_conf = ();
>>>>>>>>>>>>>>>>>>> + &General::readhasharray($CUSTOMLISTS_CONF, \%lists_conf);
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Get list options, enabled by default to start import
>>>>>>>>>>>>>>>>>>> + $customlists{'allow'}{'enabled'} = $lists_conf{2}[0] // 'on';
>>>>>>>>>>>>>>>>>>> + $customlists{'block'}{'enabled'} = $lists_conf{2}[1] // 'on';
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Import enabled list from rpz-config, otherwise retrieve stored or empty list from configuration file
>>>>>>>>>>>>>>>>>>> + if($customlists{'allow'}{'enabled'} eq 'on') {
>>>>>>>>>>>>>>>>>>> + &_customlist_import('allow', $RPZ_ALLOWLIST);
>>>>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>>>>> + $customlists{'allow'}{'list'} = $lists_conf{0} // [];
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> + if($customlists{'block'}{'enabled'} eq 'on') {
>>>>>>>>>>>>>>>>>>> + &_customlist_import('block', $RPZ_BLOCKLIST);
>>>>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>>>>> + $customlists{'block'}{'list'} = $lists_conf{1} // [];
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# Save internal custom lists configuration
>>>>>>>>>>>>>>>>>>> +sub _customlists_save_conf {
>>>>>>>>>>>>>>>>>>> + my %export = ();
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Match IDs with import function
>>>>>>>>>>>>>>>>>>> + $export{0} = $customlists{'allow'}{'list'};
>>>>>>>>>>>>>>>>>>> + $export{1} = $customlists{'block'}{'list'};
>>>>>>>>>>>>>>>>>>> + $export{2} = [$customlists{'allow'}{'enabled'}, $customlists{'block'}{'enabled'}];
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + &General::writehasharray($CUSTOMLISTS_CONF, \%export);
>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# Import a custom list from plain file, returns empty list if file is missing
>>>>>>>>>>>>>>>>>>> +sub _customlist_import {
>>>>>>>>>>>>>>>>>>> + my ($listname, $filename) = @_;
>>>>>>>>>>>>>>>>>>> + my @list = ();
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # File exists, load and check all lines
>>>>>>>>>>>>>>>>>>> + if(-f $filename) {
>>>>>>>>>>>>>>>>>>> + open(my $FH, '<', $filename) or die "Can't read $filename: $!";
>>>>>>>>>>>>>>>>>>> + while(my $line = <$FH>) {
>>>>>>>>>>>>>>>>>>> + chomp($line);
>>>>>>>>>>>>>>>>>>> + push(@list, $line);
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> + close($FH);
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Clean up imported data
>>>>>>>>>>>>>>>>>>> + &_rpz_validate_customlist(\@list, 1);
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + $customlists{$listname}{'list'} = \@list;
>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# Export a custom list to plain file or clear file if list is disabled
>>>>>>>>>>>>>>>>>>> +sub _customlist_export {
>>>>>>>>>>>>>>>>>>> + my ($listname, $filename) = @_;
>>>>>>>>>>>>>>>>>>> + return unless(defined $customlists{$listname});
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Write enabled domain list to file, otherwise save empty file
>>>>>>>>>>>>>>>>>>> + open(my $FH, '>', $filename) or die "Can't write $filename: $!";
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + if($customlists{$listname}{'enabled'} eq 'on') {
>>>>>>>>>>>>>>>>>>> + foreach my $line (@{$customlists{$listname}{'list'}}) {
>>>>>>>>>>>>>>>>>>> + print $FH "$line\n";
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>>>>> + print $FH "; Note: This list is currently disabled by $ENV{'SCRIPT_NAME'}\n";
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + close($FH);
>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +###--- Internal gui functions ---###
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# Show simple message box
>>>>>>>>>>>>>>>>>>> +sub _print_message {
>>>>>>>>>>>>>>>>>>> + my ($message, $title) = @_;
>>>>>>>>>>>>>>>>>>> + $title ||= $Lang::tr{'error messages'};
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $title);
>>>>>>>>>>>>>>>>>>> + print "<span>$message</span>";
>>>>>>>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# Show all zone files and related gui elements
>>>>>>>>>>>>>>>>>>> +sub _print_zonefiles {
>>>>>>>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf'});
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>>>>>> +<table class="tbl" width="100%">
>>>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>>>> + <th>$Lang::tr{'name'}</th>
>>>>>>>>>>>>>>>>>>> + <th>URL</th>
>>>>>>>>>>>>>>>>>>> + <th>$Lang::tr{'remark'}</th>
>>>>>>>>>>>>>>>>>>> + <th colspan="3">$Lang::tr{'action'}</th>
>>>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Sort zonefiles by name and loop trough all entries
>>>>>>>>>>>>>>>>>>> + foreach my $name (sort keys %zonefiles) {
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Toggle button label translation
>>>>>>>>>>>>>>>>>>> + my $toggle_tr = ($zonefiles{$name}{'enabled'} eq 'on') ? $Lang::tr{'click to disable'} : $Lang::tr{'click to enable'};
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>>>> + <td>$name</td>
>>>>>>>>>>>>>>>>>>> + <td>$zonefiles{$name}{'url'}</td>
>>>>>>>>>>>>>>>>>>> + <td>$zonefiles{$name}{'remark'}</td>
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_TOGGLE">
>>>>>>>>>>>>>>>>>>> + <input type="image" src="/images/$zonefiles{$name}{'enabled'}.gif" title="$toggle_tr" alt="$toggle_tr">
>>>>>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_EDIT">
>>>>>>>>>>>>>>>>>>> + <input type="image" src="/images/edit.gif" title="$Lang::tr{'edit'}" alt="$Lang::tr{'edit'}">
>>>>>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_REMOVE">
>>>>>>>>>>>>>>>>>>> + <input type="image" src="/images/delete.gif" title="$Lang::tr{'remove'}" alt="$Lang::tr{'remove'}">
>>>>>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Disable reload button if not needed
>>>>>>>>>>>>>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>>>>>> +</table>
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +<div class="right">
>>>>>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="">
>>>>>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="ZF_EDIT">$Lang::tr{'add'}</button>
>>>>>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>>>>>> +</div>
>>>>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# Show zonefiles entry editor
>>>>>>>>>>>>>>>>>>> +sub _print_zonefile_editor {
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Key specified: Edit existing entry
>>>>>>>>>>>>>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>>>>>>>>>>>>>> + # Load data to be edited, but don't override already present values (allows user to edit after error)
>>>>>>>>>>>>>>>>>>> + $cgiparams{'ZF_NAME'} //= $action_key;
>>>>>>>>>>>>>>>>>>> + $cgiparams{'ZF_URL'} //= $zonefiles{$action_key}{'url'};
>>>>>>>>>>>>>>>>>>> + $cgiparams{'ZF_REMARK'} //= $zonefiles{$action_key}{'remark'};
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Fallback to empty form
>>>>>>>>>>>>>>>>>>> + $cgiparams{'ZF_NAME'} //= "";
>>>>>>>>>>>>>>>>>>> + $cgiparams{'ZF_URL'} //= "";
>>>>>>>>>>>>>>>>>>> + $cgiparams{'ZF_REMARK'} //= "";
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf editor'});
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>>>>> +<input type="hidden" name="KEY" value="$action_key">
>>>>>>>>>>>>>>>>>>> +<table width="100%">
>>>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>>>> + <td width="20%">$Lang::tr{'name'}: <img src="/blob.gif" alt="*"></td>
>>>>>>>>>>>>>>>>>>> + <td><input type="text" name="ZF_NAME" value="$cgiparams{'ZF_NAME'}" size="40" maxlength="32" title="$Lang::tr{'rpz zf remark info'}" pattern="[a-zA-Z0-9_]{1,32}" required></td>
>>>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>>>> + <td width="20%">URL: <img src="/blob.gif" alt="*"></td>
>>>>>>>>>>>>>>>>>>> + <td><input type="url" name="ZF_URL" value="$cgiparams{'ZF_URL'}" size="40" maxlength="128" required></td>
>>>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>>>> + <td width="20%">$Lang::tr{'remark'}:</td>
>>>>>>>>>>>>>>>>>>> + <td><input type="text" name="ZF_REMARK" value="$cgiparams{'ZF_REMARK'}" size="40" maxlength="32"></td>
>>>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>>>> + <td colspan="2"><hr></td>
>>>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>>>> + <td width="55%"><img src="/blob.gif" alt="*"> $Lang::tr{'required field'}</td>
>>>>>>>>>>>>>>>>>>> + <td align="right"><button type="submit" name="ACTION" value="ZF_SAVE">$Lang::tr{'save'}</button></td>
>>>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>>>> +</table>
>>>>>>>>>>>>>>>>>>> +</form>
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +<div class="right">
>>>>>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="NONE">$Lang::tr{'back'}</button>
>>>>>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>>>>>> +</div>
>>>>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# Show custom allow/block files and related gui elements
>>>>>>>>>>>>>>>>>>> +sub _print_customlists {
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Load lists from config, unless they are currently being edited
>>>>>>>>>>>>>>>>>>> + if($action ne 'CL_SAVE') {
>>>>>>>>>>>>>>>>>>> + $cgiparams{'ALLOW_LIST'} = join("\n", @{$customlists{'allow'}{'list'}});
>>>>>>>>>>>>>>>>>>> + $cgiparams{'BLOCK_LIST'} = join("\n", @{$customlists{'block'}{'list'}});
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + $cgiparams{'ALLOW_ENABLED'} = ($customlists{'allow'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>>>>>>>>>>>>>> + $cgiparams{'BLOCK_ENABLED'} = ($customlists{'block'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Fallback to empty form
>>>>>>>>>>>>>>>>>>> + $cgiparams{'ALLOW_LIST'} //= "";
>>>>>>>>>>>>>>>>>>> + $cgiparams{'BLOCK_LIST'} //= "";
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # HTML checkboxes, unchecked = no or undef value in POST data
>>>>>>>>>>>>>>>>>>> + my %checked = ();
>>>>>>>>>>>>>>>>>>> + $checked{'ALLOW_ENABLED'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? " checked" : "";
>>>>>>>>>>>>>>>>>>> + $checked{'BLOCK_ENABLED'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? " checked" : "";
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Disable reload button if not needed
>>>>>>>>>>>>>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz cl'});
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>>>>> +<table width="100%">
>>>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl allow'}</b><br>$Lang::tr{'rpz cl allow info'}</td>
>>>>>>>>>>>>>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl block'}</b><br>$Lang::tr{'rpz cl block info'}</td>
>>>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>>>> + <td colspan="2"><textarea name="ALLOW_LIST" class="domainlist" cols="45">
>>>>>>>>>>>>>>>>>>> +$cgiparams{'ALLOW_LIST'}</textarea></td>
>>>>>>>>>>>>>>>>>>> + <td colspan="2"><textarea name="BLOCK_LIST" class="domainlist" cols="45">
>>>>>>>>>>>>>>>>>>> +$cgiparams{'BLOCK_LIST'}</textarea></td>
>>>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>>>> + <td><label for="allow_enabled">$Lang::tr{'rpz cl allow enable'}</label></td>
>>>>>>>>>>>>>>>>>>> + <td width="15%"><input type="checkbox" name="ALLOW_ENABLED" id="allow_enabled"$checked{'ALLOW_ENABLED'}></td>
>>>>>>>>>>>>>>>>>>> + <td><label for="block_enabled">$Lang::tr{'rpz cl block enable'}</label></td>
>>>>>>>>>>>>>>>>>>> + <td width="15%"><input type="checkbox" name="BLOCK_ENABLED" id="block_enabled"$checked{'BLOCK_ENABLED'}></td>
>>>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>>>> + <td colspan="4"><hr></td>
>>>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>>>> + <td align="right" colspan="4">
>>>>>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="CL_SAVE">$Lang::tr{'save'}</button>
>>>>>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>>>>>>>> +</table>
>>>>>>>>>>>>>>>>>>> +</form>
>>>>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# Output javascript and extra gui elements
>>>>>>>>>>>>>>>>>>> +sub _print_gui_extras {
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Apply/Restart button modifier key handler
>>>>>>>>>>>>>>>>>>> + if(&_rpz_needs_reload()) {
>>>>>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>>>>>> +<script>
>>>>>>>>>>>>>>>>>>> + // Commit modifier key handler
>>>>>>>>>>>>>>>>>>> + (function(jq, document) {
>>>>>>>>>>>>>>>>>>> + var keyEventsOn = false; // Keyboard events attached
>>>>>>>>>>>>>>>>>>> + var keyModify = false; // Modifier key pressed
>>>>>>>>>>>>>>>>>>> + var mouseHover = false; // Mouse over commit button
>>>>>>>>>>>>>>>>>>> + var btnModified = false; // Button modified to "Restart"
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + // Document-level key events, enable only while cursor is over button
>>>>>>>>>>>>>>>>>>> + function attachKeyEvents() {
>>>>>>>>>>>>>>>>>>> + if(keyEventsOn) {
>>>>>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> + keyEventsOn = true;
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + jq(document).on("keydown.rpz", function(event) {
>>>>>>>>>>>>>>>>>>> + if((!keyModify) && event.shiftKey) {
>>>>>>>>>>>>>>>>>>> + keyModify = true;
>>>>>>>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>>>>>> + jq(document).on("keyup.rpz", function(event) {
>>>>>>>>>>>>>>>>>>> + if(keyModify && (!event.shiftKey)) {
>>>>>>>>>>>>>>>>>>> + keyModify = false;
>>>>>>>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> + function removeKeyEvents() {
>>>>>>>>>>>>>>>>>>> + keyModify = false;
>>>>>>>>>>>>>>>>>>> + if(keyEventsOn) {
>>>>>>>>>>>>>>>>>>> + jq(document).off("keydown.rpz keyup.rpz");
>>>>>>>>>>>>>>>>>>> + keyEventsOn = false;
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + // Attach mouse hover events to commit buttons
>>>>>>>>>>>>>>>>>>> + function attachMouseEvents() {
>>>>>>>>>>>>>>>>>>> + jq("button.commit").on("mouseenter", function(event) {
>>>>>>>>>>>>>>>>>>> + if(!mouseHover) {
>>>>>>>>>>>>>>>>>>> + mouseHover = true;
>>>>>>>>>>>>>>>>>>> + attachKeyEvents();
>>>>>>>>>>>>>>>>>>> + // Handle already pressed key
>>>>>>>>>>>>>>>>>>> + keyModify = !!(event.shiftKey);
>>>>>>>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + // Cursor moved away: Disable key listener to minimize events
>>>>>>>>>>>>>>>>>>> + jq("button.commit").on("mouseleave", function() {
>>>>>>>>>>>>>>>>>>> + if(mouseHover) {
>>>>>>>>>>>>>>>>>>> + mouseHover = false;
>>>>>>>>>>>>>>>>>>> + removeKeyEvents();
>>>>>>>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + // Modify commit button
>>>>>>>>>>>>>>>>>>> + function handleModify() {
>>>>>>>>>>>>>>>>>>> + let modify = mouseHover && keyModify;
>>>>>>>>>>>>>>>>>>> + if(btnModified != modify) {
>>>>>>>>>>>>>>>>>>> + if(modify) {
>>>>>>>>>>>>>>>>>>> + jq("button.commit").text("$Lang::tr{'restart'}").val("UNB_RESTART");
>>>>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>>>>> + jq("button.commit").text("$Lang::tr{'rpz apply'}").val("RPZ_RELOAD");
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> + btnModified = modify;
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + // jQuery DOM ready
>>>>>>>>>>>>>>>>>>> + jq(function() {
>>>>>>>>>>>>>>>>>>> + attachMouseEvents();
>>>>>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>>>>>> + })(jQuery, document);
>>>>>>>>>>>>>>>>>>> +</script>
>>>>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>>>> + } # End of modifier key handler
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +###--- Internal action processing functions ---###
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# Toggle zonefile on/off
>>>>>>>>>>>>>>>>>>> +sub _action_zf_toggle {
>>>>>>>>>>>>>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + my $result = 0;
>>>>>>>>>>>>>>>>>>> + my $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Perform toggle action
>>>>>>>>>>>>>>>>>>> + if($enabled eq 'on') {
>>>>>>>>>>>>>>>>>>> + $enabled = 'off';
>>>>>>>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>>>>> + $enabled = 'on';
>>>>>>>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $action_key, $zonefiles{$action_key}{'url'}, '--no-reload');
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Save changes
>>>>>>>>>>>>>>>>>>> + $zonefiles{$action_key}{'enabled'} = $enabled;
>>>>>>>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# Remove zonefile
>>>>>>>>>>>>>>>>>>> +sub _action_zf_remove {
>>>>>>>>>>>>>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Remove from rpz-config if currently active
>>>>>>>>>>>>>>>>>>> + if($zonefiles{$action_key}{'enabled'} eq 'on') {
>>>>>>>>>>>>>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Remove from data hash and save changes
>>>>>>>>>>>>>>>>>>> + delete $zonefiles{$action_key};
>>>>>>>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Clear action_key, as the entry is now removed entirely
>>>>>>>>>>>>>>>>>>> + $action_key = "";
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# Create or update zonefile entry
>>>>>>>>>>>>>>>>>>> +# Returns undef if gui needs to stay in editor mode
>>>>>>>>>>>>>>>>>>> +sub _action_zf_save {
>>>>>>>>>>>>>>>>>>> + my $result = 0;
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + my $name = $cgiparams{'ZF_NAME'} // "";
>>>>>>>>>>>>>>>>>>> + my $url = $cgiparams{'ZF_URL'} // "";
>>>>>>>>>>>>>>>>>>> + my $remark = $cgiparams{'ZF_REMARK'} // "";
>>>>>>>>>>>>>>>>>>> + my $enabled = 'on'; # Enable new entries by default
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Note on variables:
>>>>>>>>>>>>>>>>>>> + # name = unique key, will be used to address the entry
>>>>>>>>>>>>>>>>>>> + # action_key = name of the entry being edited, empty for new entry
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Only check for unique name if it changed
>>>>>>>>>>>>>>>>>>> + # (this also checks new entries because the action_key is empty in this case)
>>>>>>>>>>>>>>>>>>> + $result = &_rpz_validate_zonefile($name, $url, $remark, (lc($name) ne lc($action_key)));
>>>>>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 0);
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Edit existing entry: Determine what was changed
>>>>>>>>>>>>>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>>>>>>>>>>>>>> + # Name und URL remain unchanged, only save remark and finish
>>>>>>>>>>>>>>>>>>> + if(($name eq $action_key) && ($url eq $zonefiles{$action_key}{'url'})) {
>>>>>>>>>>>>>>>>>>> + $zonefiles{$action_key}{'remark'} = $remark;
>>>>>>>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Entry was changed and needs to be recreated, preserve status
>>>>>>>>>>>>>>>>>>> + $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Remove from rpz-config
>>>>>>>>>>>>>>>>>>> + return unless &_action_zf_remove();
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Add new entry to rpz-config
>>>>>>>>>>>>>>>>>>> + if($enabled eq 'on') {
>>>>>>>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $name, $url, '--no-reload');
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Add to global data hash and save changes
>>>>>>>>>>>>>>>>>>> + my %entry = ('enabled' => $enabled,
>>>>>>>>>>>>>>>>>>> + 'url' => $url,
>>>>>>>>>>>>>>>>>>> + 'remark' => $remark);
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# Save custom lists
>>>>>>>>>>>>>>>>>>> +sub _action_cl_save {
>>>>>>>>>>>>>>>>>>> + return unless((defined $cgiparams{'ALLOW_LIST'}) && (defined $cgiparams{'BLOCK_LIST'}));
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + my $result = 0;
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + my @allowlist = split(/\R/, $cgiparams{'ALLOW_LIST'});
>>>>>>>>>>>>>>>>>>> + my @blocklist = split(/\R/, $cgiparams{'BLOCK_LIST'});
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Validate lists
>>>>>>>>>>>>>>>>>>> + $result = &_rpz_validate_customlist(\@allowlist);
>>>>>>>>>>>>>>>>>>> + if($result != 0) {
>>>>>>>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr(202, $result);
>>>>>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> + $result = &_rpz_validate_customlist(\@blocklist);
>>>>>>>>>>>>>>>>>>> + if($result != 0) {
>>>>>>>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr(203, $result);
>>>>>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Add to global data hash and save changes
>>>>>>>>>>>>>>>>>>> + $customlists{'allow'}{'list'} = \@allowlist;
>>>>>>>>>>>>>>>>>>> + $customlists{'block'}{'list'} = \@blocklist;
>>>>>>>>>>>>>>>>>>> + $customlists{'allow'}{'enabled'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? 'on' : 'off';
>>>>>>>>>>>>>>>>>>> + $customlists{'block'}{'enabled'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? 'on' : 'off';
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + &_customlists_save_conf();
>>>>>>>>>>>>>>>>>>> + &_customlist_export('allow', $RPZ_ALLOWLIST);
>>>>>>>>>>>>>>>>>>> + &_customlist_export('block', $RPZ_BLOCKLIST);
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Make new lists, request service reload on success
>>>>>>>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-make', 'allowblock', '--no-reload');
>>>>>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# Trigger rpz-config reload
>>>>>>>>>>>>>>>>>>> +sub _action_rpz_reload {
>>>>>>>>>>>>>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Immediately clear flag to prevent multiple reloads
>>>>>>>>>>>>>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>>>>>>>>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Perform reload, recreate reload flag on error to enable retry
>>>>>>>>>>>>>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'reload');
>>>>>>>>>>>>>>>>>>> + if(not &_rpz_check_result($result, 0)) {
>>>>>>>>>>>>>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# Trigger unbound restart
>>>>>>>>>>>>>>>>>>> +sub _action_unb_restart {
>>>>>>>>>>>>>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Immediately clear flag to prevent multiple restarts
>>>>>>>>>>>>>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>>>>>>>>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Perform restart, unboundctrl always exits zero
>>>>>>>>>>>>>>>>>>> + &General::system('/usr/local/bin/unboundctrl', 'restart');
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +###--- Internal rpz-config functions ---###
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# Translate rpz-config exitcodes and messages
>>>>>>>>>>>>>>>>>>> +# 100-199: rpz-config, 200-299: webgui
>>>>>>>>>>>>>>>>>>> +sub _rpz_error_tr {
>>>>>>>>>>>>>>>>>>> + my ($error, $append) = @_;
>>>>>>>>>>>>>>>>>>> + $append //= '';
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Translate numeric exit codes
>>>>>>>>>>>>>>>>>>> + if(looks_like_number($error)) {
>>>>>>>>>>>>>>>>>>> + if(defined $Lang::tr{"rpz exitcode $error"}) {
>>>>>>>>>>>>>>>>>>> + $error = $Lang::tr{"rpz exitcode $error"};
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + return "RPZ $Lang::tr{'error'}: $error" . &Header::escape($append);
>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# Check result of rpz-config system call, request reload on success
>>>>>>>>>>>>>>>>>>> +sub _rpz_check_result {
>>>>>>>>>>>>>>>>>>> + my ($result, $request_reload) = @_;
>>>>>>>>>>>>>>>>>>> + $request_reload //= 0;
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # exitcode 0 = success
>>>>>>>>>>>>>>>>>>> + if($result != 0) {
>>>>>>>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr($result);
>>>>>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Set reload flag
>>>>>>>>>>>>>>>>>>> + if($request_reload) {
>>>>>>>>>>>>>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# Test whether reload flag is set
>>>>>>>>>>>>>>>>>>> +sub _rpz_needs_reload {
>>>>>>>>>>>>>>>>>>> + return (-f $RPZ_RELOAD_FLAG);
>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# Validate a zonefile entry, returns rpz-config exitcode on failure. Use _rpz_check_result to verify.
>>>>>>>>>>>>>>>>>>> +# unique = check for unique name
>>>>>>>>>>>>>>>>>>> +sub _rpz_validate_zonefile {
>>>>>>>>>>>>>>>>>>> + my ($name, $url, $remark, $unique) = @_;
>>>>>>>>>>>>>>>>>>> + $unique //= 1;
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + unless($name =~ /^[a-zA-Z0-9_]{1,32}$/) {
>>>>>>>>>>>>>>>>>>> + return 101;
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> + unless($url =~ /^[\w+\.:;\/\\&@#%?=\-~|!]{1,128}$/) {
>>>>>>>>>>>>>>>>>>> + return 105;
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> + unless($remark =~ /^[\w \-()\.:;*\/\\?!&=]{0,32}$/) {
>>>>>>>>>>>>>>>>>>> + return 201;
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Check against already existing names
>>>>>>>>>>>>>>>>>>> + if($unique) {
>>>>>>>>>>>>>>>>>>> + foreach my $existing (keys %zonefiles) {
>>>>>>>>>>>>>>>>>>> + if(lc($name) eq lc($existing)) {
>>>>>>>>>>>>>>>>>>> + return 104;
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + return 0;
>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# Validate a custom list, returns number of rejected line on failure. Check for non-zero results.
>>>>>>>>>>>>>>>>>>> +# listref = array reference, cleanup = remove invalid entries instead of returning an error
>>>>>>>>>>>>>>>>>>> +sub _rpz_validate_customlist {
>>>>>>>>>>>>>>>>>>> + my ($listref, $cleanup) = @_;
>>>>>>>>>>>>>>>>>>> + $cleanup //= 0;
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + foreach my $index (reverse 0..$#{$listref}) {
>>>>>>>>>>>>>>>>>>> + my $row = @$listref[$index];
>>>>>>>>>>>>>>>>>>> + next unless($row); # Skip/allow empty lines
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Reject/remove everything besides wildcard domains and remarks
>>>>>>>>>>>>>>>>>>> + if((not &General::validwildcarddomainname($row)) && (not $row =~ /^;[\w \-()\.:;*\/\\?!&=]*$/)) {
>>>>>>>>>>>>>>>>>>> + unless($cleanup) {
>>>>>>>>>>>>>>>>>>> + # +1 for user friendly line number and to ensure non-zero exitcode
>>>>>>>>>>>>>>>>>>> + return $index + 1;
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Remove current row
>>>>>>>>>>>>>>>>>>> + splice(@$listref, $index, 1);
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + return 0;
>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +###--- Internal misc functions ---###
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# Send HTTP 303 redirect headers for post/request/get pattern
>>>>>>>>>>>>>>>>>>> +# (Must be sent before calling &Header::showhttpheaders())
>>>>>>>>>>>>>>>>>>> +sub _http_prg_redirect {
>>>>>>>>>>>>>>>>>>> + my $location = "https://$ENV{'SERVER_NAME'}:$ENV{'SERVER_PORT'}$ENV{'SCRIPT_NAME'}";
>>>>>>>>>>>>>>>>>>> + print "Status: 303 See Other\n";
>>>>>>>>>>>>>>>>>>> + print "Location: $location\n";
>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>> diff --git a/lfs/rpz b/lfs/rpz
>>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>>> index 000000000..7ddbc38e5
>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>> +++ b/lfs/rpz
>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,96 @@
>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>> +# Definitions
>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +include Config
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +SUMMARY = response policy zone - RPZ reputation system for unbound DNS
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +VER = 1.0.0
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +THISAPP = rpz-$(VER)
>>>>>>>>>>>>>>>>>>> +DIR_APP = $(DIR_SRC)/$(THISAPP)
>>>>>>>>>>>>>>>>>>> +TARGET = $(DIR_INFO)/$(THISAPP)
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +PROG = rpz
>>>>>>>>>>>>>>>>>>> +PAK_VER = 18
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +DEPS =
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +SERVICES =
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>> +# Top-level Rules
>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +install : $(TARGET)
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +check :
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +download :
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +b2 :
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +dist:
>>>>>>>>>>>>>>>>>>> + @$(PAK)
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>> +# Installation Details
>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +$(TARGET) :
>>>>>>>>>>>>>>>>>>> + @$(PREBUILD)
>>>>>>>>>>>>>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # RPZ scripts
>>>>>>>>>>>>>>>>>>> + install --verbose --mode=755 \
>>>>>>>>>>>>>>>>>>> + $(DIR_CONF)/rpz/{rpz-config,rpz-metrics,rpz-sleep,rpz-make,rpz-functions} \
>>>>>>>>>>>>>>>>>>> + --target-directory=/usr/sbin
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # RPZ config files
>>>>>>>>>>>>>>>>>>> + mkdir -pv /etc/unbound/local.d
>>>>>>>>>>>>>>>>>>> + install --verbose --mode=644 --owner=nobody --group=nobody \
>>>>>>>>>>>>>>>>>>> + $(DIR_CONF)/rpz/00-rpz.conf \
>>>>>>>>>>>>>>>>>>> + --target-directory=/etc/unbound/local.d
>>>>>>>>>>>>>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/local.d
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # RPZ custom list files for allow and block
>>>>>>>>>>>>>>>>>>> + mkdir -pv /var/ipfire/dns/rpz
>>>>>>>>>>>>>>>>>>> + touch /var/ipfire/dns/rpz/{allowlist,blocklist}
>>>>>>>>>>>>>>>>>>> + chown --verbose --recursive nobody:nobody /var/ipfire/dns/rpz
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # RPZ zone files
>>>>>>>>>>>>>>>>>>> + # create empty RPZ config file to avoid a unbound config error
>>>>>>>>>>>>>>>>>>> + mkdir -pv /etc/unbound/zonefiles
>>>>>>>>>>>>>>>>>>> + touch /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/zonefiles
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Install addon-specific language-files
>>>>>>>>>>>>>>>>>>> + install --verbose --mode=004 $(DIR_CONF)/rpz/rpz.*.pl \
>>>>>>>>>>>>>>>>>>> + --target-directory=/var/ipfire/addon-lang
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + # Install backup definition
>>>>>>>>>>>>>>>>>>> + cp -vf $(DIR_CONF)/backup/includes/rpz /var/ipfire/backup/addons/includes/rpz
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>>>>>>>>>>>>>> + @$(POSTBUILD)
>>>>>>>>>>>>>>>>>>> diff --git a/make.sh b/make.sh
>>>>>>>>>>>>>>>>>>> index 827ea9e77..a77535b13 100755
>>>>>>>>>>>>>>>>>>> --- a/make.sh
>>>>>>>>>>>>>>>>>>> +++ b/make.sh
>>>>>>>>>>>>>>>>>>> @@ -390,7 +390,7 @@ prepareenv() {
>>>>>>>>>>>>>>>>>>> if [ "${free_space}" -lt "${required_space}" ]; then
>>>>>>>>>>>>>>>>>>> # Add any consumed space
>>>>>>>>>>>>>>>>>>> while read -r consumed_space path; do
>>>>>>>>>>>>>>>>>>> - (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>>>>>>>>>>>>>> + (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>>>>>>>>>>>>>> done <<< "$(du --summarize --bytes "${BUILD_DIR}" "${IMAGES_DIR}" "${LOG_DIR}" 2>/dev/null)"
>>>>>>>>>>>>>>>>>>> fi
>>>>>>>>>>>>>>>>>>> @@ -2087,6 +2087,7 @@ build_system() {
>>>>>>>>>>>>>>>>>>> lfsmake2 btrfs-progs
>>>>>>>>>>>>>>>>>>> lfsmake2 inotify-tools
>>>>>>>>>>>>>>>>>>> lfsmake2 grub-btrfs
>>>>>>>>>>>>>>>>>>> + lfsmake2 rpz
>>>>>>>>>>>>>>>>>>> lfsmake2 linux
>>>>>>>>>>>>>>>>>>> lfsmake2 rtl8812au
>>>>>>>>>>>>>>>>>>> diff --git a/src/paks/rpz/install.sh b/src/paks/rpz/install.sh
>>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>>> index 000000000..ef99bf742
>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>> +++ b/src/paks/rpz/install.sh
>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,36 @@
>>>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>> +#
>>>>>>>>>>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>>>>>>>>>>> +extract_files
>>>>>>>>>>>>>>>>>>> +restore_backup ${NAME}
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# fix user created files
>>>>>>>>>>>>>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>>>>>>>>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>>>>>>>>>>>>>> + /etc/unbound/zonefiles \
>>>>>>>>>>>>>>>>>>> + /etc/unbound/local.d
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# Update Language cache
>>>>>>>>>>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# restart unbound to load config file
>>>>>>>>>>>>>>>>>>> +/etc/init.d/unbound restart
>>>>>>>>>>>>>>>>>>> diff --git a/src/paks/rpz/uninstall.sh b/src/paks/rpz/uninstall.sh
>>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>>> index 000000000..e11427df3
>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>> +++ b/src/paks/rpz/uninstall.sh
>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,38 @@
>>>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>> +#
>>>>>>>>>>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>>>>>>>>>>>>>> +/etc/init.d/unbound stop
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +make_backup ${NAME}
>>>>>>>>>>>>>>>>>>> +remove_files
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>>>>>>>>>>>>>> +# "[1723428668] unbound-control[17117:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>>>>>>>>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# Update Language cache
>>>>>>>>>>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# start unbound to load unbound config file
>>>>>>>>>>>>>>>>>>> +/etc/init.d/unbound start
>>>>>>>>>>>>>>>>>>> diff --git a/src/paks/rpz/update.sh b/src/paks/rpz/update.sh
>>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>>> index 000000000..9bc340bc6
>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>> +++ b/src/paks/rpz/update.sh
>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,52 @@
>>>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>> +#
>>>>>>>>>>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# from update.sh
>>>>>>>>>>>>>>>>>>> +extract_backup_includes
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>>>>>>>>>>>>>> +/etc/init.d/unbound stop
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# from uninstall.sh
>>>>>>>>>>>>>>>>>>> +make_backup ${NAME}
>>>>>>>>>>>>>>>>>>> +remove_files
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>>>>>>>>>>>>>> +# "unbound-control[nn:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>>>>>>>>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# from install.sh
>>>>>>>>>>>>>>>>>>> +extract_files
>>>>>>>>>>>>>>>>>>> +restore_backup ${NAME}
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# fix user created files
>>>>>>>>>>>>>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>>>>>>>>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>>>>>>>>>>>>>> + /etc/unbound/zonefiles \
>>>>>>>>>>>>>>>>>>> + /etc/unbound/local.d
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# Update Language cache
>>>>>>>>>>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>> +# restart unbound to load config files
>>>>>>>>>>>>>>>>>>> +/etc/init.d/unbound start
>>>>>>>>>>>>>>>>>>> --
>>>>>>>>>>>>>>>>>>> 2.39.5
>>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> Jon
>>>>>>>>>>>>>>>>> --
>>>>>>>>>>>>>>>>> Jon Murphy
>>>>>>>>>>>>>>>>> jon.murphy@ipfire.org
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> Jon
>>>>>>>>>>>>>>> --
>>>>>>>>>>>>>>> Jon Murphy
>>>>>>>>>>>>>>> jon.murphy@ipfire.org
>>>
>>>
>>>
>
>
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
2025-03-24 14:42 ` Re[2]: " Jon Murphy
@ 2025-03-24 14:43 ` Michael Tremer
0 siblings, 0 replies; 30+ messages in thread
From: Michael Tremer @ 2025-03-24 14:43 UTC (permalink / raw)
To: Jon Murphy; +Cc: Bernhard Bitsch, IPFire: Development-List
Yes, I don’t need any debugging of this...
> On 24 Mar 2025, at 14:42, Jon Murphy <jon.murphy@ipfire.org> wrote:
>
> Is there a:
>
> server:
> module-config: "respip validator iterator"
>
> In your RPZ set-up?
>
>
> ------ Original Message ------
> From "Michael Tremer" <michael.tremer@ipfire.org>
> To "Jon Murphy" <jon.murphy@ipfire.org>
> Cc "Bernhard Bitsch" <bbitsch@ipfire.org>; "IPFire: Development-List" <development@lists.ipfire.org>
> Date 3/24/2025 9:40:15 AM
> Subject Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
>
>> Because it is not doing it on my system...
>>
>>> On 24 Mar 2025, at 14:38, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>
>>> Actually it did.
>>>
>>> Why do you think Unbound did not?
>>>
>>>
>>> ------ Original Message ------
>>> From "Michael Tremer" <michael.tremer@ipfire.org>
>>> To "Jon Murphy" <jon.murphy@ipfire.org>
>>> Cc "Bernhard Bitsch" <bbitsch@ipfire.org>; "IPFire: Development-List" <development@lists.ipfire.org>
>>> Date 3/24/2025 9:36:53 AM
>>> Subject Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
>>>
>>>> Unbound did not put those there...
>>>>
>>>>> On 24 Mar 2025, at 14:33, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>
>>>>>
>>>>>
>>>>> And where are these stored?
>>>>>
>>>>> In `/etc/unbound/zonefiles`:
>>>>>
>>>>>
>>>>> [root@ipfire ~] # ls -al /etc/unbound/zonefiles
>>>>> total 20664
>>>>> drwxr-xr-x 2 nobody nobody 4096 Mar 24 04:40 .
>>>>> drwxr-xr-x 4 root root 4096 Mar 19 16:24 ..
>>>>> -rw-r--r-- 1 nobody nobody 3999087 Mar 23 15:11 adhocSB.rpz
>>>>> -rw-r--r-- 1 nobody nobody 1411 Mar 23 14:23 allow.rpz
>>>>> -rw-r--r-- 1 nobody nobody 25355 Mar 24 04:40 AmazonTrkrHZ.rpz
>>>>> -rw-r--r-- 1 nobody nobody 7241 Mar 24 04:40 AppleTrkrHZ.rpz
>>>>> -rw-r--r-- 1 nobody nobody 178 Mar 23 14:23 block.rpz
>>>>> -rw-r--r-- 1 nobody nobody 78496 Mar 24 04:40 DOHblockHZ.rpz
>>>>> -rw-r--r-- 1 nobody nobody 16983551 Mar 24 04:40 MxProPlusHZ.rpz
>>>>> -rw-r--r-- 1 nobody nobody 2893 Mar 24 04:40 tldHZ.rpz
>>>>> -rw-r--r-- 1 nobody nobody 29419 Mar 24 04:40 WinTrkrHZ.rpz
>>>>> [root@ipfire ~] #
>>>>>
>>>>>
>>>>>
>>>>> ------ Original Message ------
>>>>> From "Michael Tremer" <michael.tremer@ipfire.org>
>>>>> To "Bernhard Bitsch" <bbitsch@ipfire.org>
>>>>> Cc development@lists.ipfire.org
>>>>> Date 3/24/2025 9:25:40 AM
>>>>> Subject Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
>>>>>
>>>>>> Hello,
>>>>>>
>>>>>>>
>>>>>>> On 24 Mar 2025, at 13:33, Bernhard Bitsch <bbitsch@ipfire.org> wrote:
>>>>>>> Am 24.03.2025 um 11:17 schrieb Michael Tremer:
>>>>>>>>
>>>>>>>>
>>>>>>>> Hello Jon,
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> On 24 Mar 2025, at 00:00, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>>> Michael,
>>>>>>>>> FYI - I was wrong Unbound RPZ is _not_ watching the serial number, it is watching the "refresh", the number after the serial number.
>>>>>>>>
>>>>>>>> Refresh just tells the client how often to check for an update.
>>>>>>>> If that is actually being set by the list publisher, then we have another problem here, because they could put some insanely low value there and we would then DDoS their infrastructure. I think we should keep it like we have it in other places that we control how often we want to check or pull for updates.
>>>>>>>>
>>>>>>> You are right. But an extra update process wastes additional processor time. The update mechanism of unbound does the check for update ( however it is realized ) nevertheless.
>>>>>>
>>>>>> Yes, doing more things needs resources. But we are not seriously considering whether an IPFire system has enough resources to perform the download of a text file, or are we?
>>>>>>
>>>>>>>
>>>>>>>>> I understand that you don’t speak C, but you got the information from somewhere. Documentation maybe? Since that is out of date very often I like to consult the code.
>>>>>>>>> From testing. Downloading rpz files using rpz unbound, and watching what happens. If the rpz file is setup for "once per day" refresh, then it only downloads one time.
>>>>>>>>> However that won’t solve our problem . . . and having no cache.
>>>>>>>>> In `/etc/unbound/tuning.conf` there is `rrset-cache-size: 128m`. Are you referring to a different cache.
>>>>>>>>
>>>>>>>> Naturally unbound is loading the zone into its memory which we generally call cache.
>>>>>>>> When I say cache I am thinking about persistent data storage across multiple restarts of Unbound. If I am downloading 100 MiB of RPZ lists (which is presumably still on the lower end) and I reboot my firewall, I do not want to download the same data again. We can only ever download a list *once* unless we are 100% certain that it has changed. Then we can download it once again.
>>>>>>>
>>>>>>> The RPZ lists are stored in files in persistent storage. Unbound creates the internal cache from these.
>>>>>>
>>>>>> And where are these stored?
>>>>>>
>>>>>>>
>>>>>>>>> Maybe we need to implement both?
>>>>>>>>> Yes. There are very few AXFR list (I think only four were found). And many more HTTPS rpz files.
>>>>>>>>> Jon
>>>>>>>>> ------ Original Message ------
>>>>>>>>> From "Michael Tremer" <michael.tremer@ipfire.org>
>>>>>>>>> To "Jon Murphy" <jon.murphy@ipfire.org>
>>>>>>>>> Cc "IPFire: Development-List" <development@lists.ipfire.org>
>>>>>>>>> Date 3/20/2025 11:26:43 AM
>>>>>>>>> Subject Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> Hello Jon,
>>>>>>>>>> Please don’t forget to Cc the list...
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> On 19 Mar 2025, at 18:27, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>>>>> Michael,
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>> Where in the code is this implemented? I cannot find anything like this:
>>>>>>>>>>>
>>>>>>>>>>> Keep in mind I am not a "C" person. Maybe in this section?:
>>>>>>>>>>> https://git.ipfire.org/?p=thirdparty/unbound.git;a=blob;f=services/authzone.c;hb=30b9cb5f813003d0a2b1c2e678652396615b1b7d#l5875
>>>>>>>>>>
>>>>>>>>>> This where the AXFR response is being handled when doing a DNS zone transfer. This code is not being called when performing a HTTP download.
>>>>>>>>>> I understand that you don’t speak C, but you got the information from somewhere. Documentation maybe? Since that is out of date very often I like to consult the code.
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> —
>>>>>>>>>>> When I was just learning about RPZ I created a separate RPZ file for testing. When I changed the SOA line with a new serial number, the RPZ file download would happen in about 5 minutes.
>>>>>>>>>>> https://people.ipfire.org/~jon/sblack-adhoc.rpz
>>>>>>>>>>
>>>>>>>>>> It might well be that the file is not being reloaded if the download matches the content that unbound already has. That would of course save some resources.
>>>>>>>>>> However that won’t solve our problem with redundant downloads and having no cache.
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> That is how I found out the SOA line is watched for a serial number change.
>>>>>>>>>>> I’ll reconfirm my findings.
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>>> The second reason is that we have a lot of firewalls out there. Not all of them will enable this feature and all of the lists, but even if it is a good chunk, we will generate terabytes of traffic which put load on the infrastructure and will cost money. It simply is not what we want to do, regardless of self-hosting those lists and pulling them from somewhere else.
>>>>>>>>>>>
>>>>>>>>>>> So I understand, are you thinking of hosting RPZ AXFR (DNS zone transfer) on IPFire infrastructure?
>>>>>>>>>>
>>>>>>>>>> No, I don’t think that we can generally do this. The biggest problem is licensing as we cannot take anyones content and host it ourselves. We would re-distribute those lists and that will only work with permission of the publishers. I assume that would be too much work to actually get some useful content out there. We might limit ourselves to only those lists that are under a very permissive license. Nobody wants that.
>>>>>>>>>> From a technical point of view, DNS over TCP might not be very nice in terms of forging the transfer and so we would need TLS as well… It should work, but even if we would be able to encourage other people to publish their lists I doubt they would implement DNS over TLS for authoritative DNS. That standard is in very early stages as well.
>>>>>>>>>> As far as I can see, those vendors who offer a list as a commercial product are using DNS to distribute it (e.g. Spamhaus). Those people who have made this all a hobby are throwing the lists onto GitHub and let them handle the traffic.
>>>>>>>>>> Maybe we need to implement both?
>>>>>>>>>> -Michael
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> Jon
>>>>>>>>>>> On 3/19/25 5:35 AM, Michael Tremer wrote:
>>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>> Hello Jon,
>>>>>>>>>>>> Where in the code is this implemented? I cannot find anything like this:
>>>>>>>>>>>> Unbound loads the entire file into memory and then starts parsing it. The only special treatment there is is to check whether the first line is a valid zone entry. It does not even have to be a SOA record.
>>>>>>>>>>>> https://git.ipfire.org/?p=thirdparty/unbound.git;a=blob;f=services/authzone.c;hb=30b9cb5f813003d0a2b1c2e678652396615b1b7d#l1188
>>>>>>>>>>>> I am also concerned that Unbound will not be able to support an upstream proxy for any downloads. The caching situation is also unclear for me, so I believe that we will be looking at writing a custom downloader that implements all these things.
>>>>>>>>>>>> -Michael
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>> On 19 Mar 2025, at 02:58, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>>>>>>> Michael,
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> The emphasis is on the repeated downloads of the same list. That is
>>>>>>>>>>>>>
>>>>>>>>>>>>> > what cannot happen.
>>>>>>>>>>>>> The Unbound RPZ code, as installed within IPFire, watches for a change
>>>>>>>>>>>>> in the SOA line of each RPZ file. This is an example of the first few
>>>>>>>>>>>>> lines for every RPZ file.
>>>>>>>>>>>>> $TTL 300
>>>>>>>>>>>>> @ SOA localhost. root.localhost. 1742298960 43200 3600 86400 300
>>>>>>>>>>>>> NS localhost.
>>>>>>>>>>>>> ;
>>>>>>>>>>>>> ; Title: HaGeZi's Pop-Up Ads DNS Blocklist
>>>>>>>>>>>>> ; Description: Blocks annoying and malicious pop-up ads.
>>>>>>>>>>>>> If the SOA serial number changes (e.g. the 1742298960), then Unbound RPZ
>>>>>>>>>>>>> code does its thing and downloads. Otherwise there is no download.
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> So there has to be a way to ensure that we won’t download a list again
>>>>>>>>>>>>>
>>>>>>>>>>>>> > unless it has actually changed.
>>>>>>>>>>>>> This should do what you want but I may be missing your point.
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> DNS has a builtin functionality called AXFR. It simply does the job
>>>>>>>>>>>>>
>>>>>>>>>>>>> > for you. I was just wondering whether that was not being used.
>>>>>>>>>>>>> I need to read about AXFR/IXFR and learn a little more.
>>>>>>>>>>>>> Jon
>>>>>>>>>>>>> On 3/17/25 5:35 AM, Michael Tremer wrote:
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Good Morning Jon,
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> On 16 Mar 2025, at 17:00, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>>>>>>>>> Michael,
>>>>>>>>>>>>>>> I was reading through you response again an I want to understand this post:
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> So if RPZ doesn't use HTTPS, what is it using? I am missing a key point here.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> The emphasis is on the repeated downloads of the same list. That is what cannot happen.
>>>>>>>>>>>>>> Although it might not affect a lot of people in our general user-base, there are some that have a metered connection and will pay for data by volume. Some of the lists I looked at are just under 20 MiB. Therefore we need to keep any traffic down to a minimum. The second reason is that we have a lot of firewalls out there. Not all of them will enable this feature and all of the lists, but even if it is a good chunk, we will generate terabytes of traffic which put load on the infrastructure and will cost money. It simply is not what we want to do, regardless of self-hosting those lists and pulling them from somewhere else.
>>>>>>>>>>>>>> So there has to be a way to ensure that we won’t download a list again unless it has actually changed.
>>>>>>>>>>>>>> DNS has a builtin functionality called AXFR. It simply does the job for you. I was just wondering whether that was not being used.
>>>>>>>>>>>>>> HTTPS is an option because that is simply what we use elsewhere, but extra functionality will have to be built for it.
>>>>>>>>>>>>>> -Michael
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> Jon
>>>>>>>>>>>>>>> On 2/13/25 3:34 PM, jon wrote:
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> Michael,
>>>>>>>>>>>>>>>> I’ve read through your comments a few times and I ended up with many more questions.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> To me the efforts to get new code accepted seem to have changed and it seemed easier in the past. In the past I made the Core Team aware via the Dev Mailing List and wrote a simple two or three paragraphs of "What is it? / What is the value? / Here is the code"
>>>>>>>>>>>>>>>> So in an effort to move forward: How exactly is something presented to the Core Team?
>>>>>>>>>>>>>>>> Is there an example of a recent effort that was presented that I can see as a sample? (This type of info can also be added to the Wiki)
>>>>>>>>>>>>>>>> I understand you want it this way, but I don’t know what exactly is needed. Please be specific.
>>>>>>>>>>>>>>>> Jon
>>>>>>>>>>>>>>>> PS - I am not ignoring your other comments, I am just trying to move forward and keep things simple.
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> On Feb 8, 2025, at 1:27 PM, Michael Tremer <michael.tremer@ipfire.org> wrote:
>>>>>>>>>>>>>>>>> Hello Jon,
>>>>>>>>>>>>>>>>> Thanks for your reply. And good that you are copying everyone into this conversation.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> On 8 Feb 2025, at 18:41, jon <jon.murphy@ipfire.org> wrote:
>>>>>>>>>>>>>>>>>> Michael,
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> Yes, this has been a separate effort (a very public separate effort). Yes, as you pointed this out early on with the "proof-of-concept" and then my request for people to help test RPZ. Nothing was hidden.
>>>>>>>>>>>>>>>>>> This was done because you (and maybe others) did not have the time and I wanted to help and because I needed assistance with RPZ. I tried my best to do this without bothering you.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> I don’t that it is accurate that nobody wanted to help on this. The list was always open - although not every email has been replied to swiftly it is also your responsibility to raise a question again if it was missed. People here have open ears.
>>>>>>>>>>>>>>>>> It was also stated on this very list on in our documentation that working on something without involving the core team is a risky undertaking. Of course IPFire is free software and so everyone is free to fork if they wish to do so.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>> and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls.
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> You were aware many steps along the way. See your email on July 28, 2024, August 15, 2024, September 30, 2024, December 23, 2024, and January 16. My attempts to get the team involved were met with "things are busy" and sometimes silence. (Yes, I get it, people are busy.)
>>>>>>>>>>>>>>>>>> You and Adolf, Leo, Erik and Bernhard have been aware since the beginning. You mention you were aware of the "proof-of-concept". If you include those beginning posts, since Sep 2023.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> Yes, I am aware of a proof-of-concept that I have been running myself for a long time. I am also aware of the efforts that you have been taking.
>>>>>>>>>>>>>>>>> Yet I don’t think there has ever been any joint effort, or am I seeing that wrong?
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>> This has not been discussed . . . on our calls.
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> On the July 28th you stated:
>>>>>>>>>>>>>>>>>> "We have talked about RPZ many times on the monthly call since the URL filter feature is falling more and more out of fashion. I think there is also many posts about this on the forum."
>>>>>>>>>>>>>>>>>> Please don’t insult me again by stating "you know what I mean".
>>>>>>>>>>>>>>>>>> And it has been discussed but not documented in the Monthly Meeting notes.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> I am not at all insulting you. I don’t want to take this down to a personal level at all. This is a public mailing list and people who read this don’t need to listen to an argument we are having. They are here for the tech inside IPFire.
>>>>>>>>>>>>>>>>> When I wrote that it has not been discussed that does not mean that we have not been touching on the topic. We have been talking about lots of things on the calls, the weather, politics, how our pets are. None of that makes it to the logs. What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>> Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation.
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> Regular conversation on the Dev Mailing list is many times met with silence. I get it, people are busy.
>>>>>>>>>>>>>>>>>> And regular two-way conversation doesn’t happen on the list. At least not with me. I’d be happy to point out the posts that were met with silence.
>>>>>>>>>>>>>>>>>> Again, I get it, people are busy.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> And you think my emails are not being met with silence? This has nothing to do with this specific topic. This has something to do with how occupied people are and how engaged they are on certain topics. Not everyone is involved in all the things and simply will ignore emails simply based on their subject line.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> But the "dip here to the list" were my attempts to get a conversation started. As I said, many time met with silence.
>>>>>>>>>>>>>>>>>> The only place I was not met with silence was on the Community. You have a great group of people in the Community. It is a shame you don’t want to have others help. It would reduce your workload.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> You should stop making statements that are not true. Who doesn’t want anyone to help?
>>>>>>>>>>>>>>>>> Not having this conversation on a Saturday evening would reduce my workload. At least it would free up time for something else. Helping with the things that are already on the go would reduce the workload of the entire team. Starting one thing at a time and finishing it is a lot better to manage than starting a hundred things and not even finish one. I can tell you that I already have a hundred things on the go.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>> Therefore, what am I supposed to do with this email?
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> To me it is beyond obvious…
>>>>>>>>>>>>>>>>>> If it isn’t what you want, then guide me with how to do this the correct way. And be specific. I am trying to help. I am trying to make things better. I am trying to do things the right way.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> To me it isn’t. This is yet another project that has been dumped to the list like so many before and later on everyone has left to have the team deal with the rest.
>>>>>>>>>>>>>>>>> It is a huge patch set. You explained what the vision is, but that is about it. There is no chance this will continue if this disagreement isn’t solved first. I didn’t even look at the code.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>> I don’t want to merge code that I don’t agree with.
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> I asked multiple times if you "agreed with the concept" and again, met with silence. Yes I get it, people are busy.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> Having support for RPZ? Yes, it was definitely on the roadmap. That I agree with.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>> So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> You mentioned this a in the past, but for some reason you do not disclose what I dismissed. Why do you continue to make this harder, wouldn’t it not be easier to tell me what I have dismissed?
>>>>>>>>>>>>>>>>>> I have sent multiple emails trying to answer your concerns and comments. On July 28, Aug 14, Aug 22, Aug 23, Sep 30, etc.
>>>>>>>>>>>>>>>>>> I’ve gone through all of the questions you asked and I cannot find a "dismissed" item.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> Maybe I need to be *more clear*. I feel humoured by this.
>>>>>>>>>>>>>>>>> It is late on a Saturday and I want my dinner soon, but certainly I have stated that this should never be an add-on considering it is supposed to replace URL Filter. We should never allow people to add their own sources. I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> The maintainers of Unbound and/or RPZ?
>>>>>>>>>>>>>>>>>> The maintainers of Hagezi list, the threatfox list, the urlhaus list, etc.?
>>>>>>>>>>>>>>>>>> What else? The maintainers or the RPZ scripts? That is me. Let’s talk!
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> You. I don’t care much about the providers of the lists.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> See, this is where it gets confusing. There are hundreds of open source packages as part of IPFire. Pick the last five years of items added to the IPFire build. You're telling me you have "constructive conversation with the maintainers" of all of the added packages?
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> They publish their software and they don’t care whether I am pulling it or not. They publish it with the commitment to maintain it - sometimes for better and sometimes for worse.
>>>>>>>>>>>>>>>>> You care about me pulling your code and I don’t know whether you would commit to maintain this.
>>>>>>>>>>>>>>>>> These two are very different cases.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> Pick the IP Blocklists list (i.e., 3CORESEC, ABUSECH, DSHIELD, SPAMHAUS, etc.) or the Suricata lists (i.e.,Emergingthreats.net <http://emergingthreats.net/>,Abuse.ch <http://abuse.ch/>, etc.). So you’ve have "constructive conversation with the maintainers"?
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> Yes, occasionally I have phone calls with a few of these providers.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> Ha! Yes a surprise. In the beginning you seemed interested as IPFire needed a replacement for URL Filter. You asked good questions about the lists picked, asked for the value to the users, etc. And I answered the best I could.
>>>>>>>>>>>>>>>>>> You even asked: “Why is this realised as an add-on and not part of the core system?” from your Jul 28, 2024 email.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> Ah, so, why is the patch creating an add-on? Not that I am saying that what I say is law, but it has not been challenged either. If my input is being ignored, why should I put this to the top of my list of priorities? I am not disappointed about this, just trying to be very good with my time.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> And on January 16, 2025 I wrote a message looking for help. And you were kind to respond quickly. So in three weeks time, since the kind response, something has changed. You went from supportive to "this".
>>>>>>>>>>>>>>>>>> So yes, I am surprised.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> Well, maybe I should not have replied to that email. It was clear that you were on some path that was not right, but you were not interested before in finding the right path from the beginning.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> Be more specific, what has to change? What exactly did I dismiss?
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> Dismissal is just my assumption. I don’t know what you actually did with my feedback. I can only see the end product that does not seem contain much of it. Repeatedly I have been pointing out that we should think before we build. I am sure a lot of hours have now gone into some code that simply does not satisfy me. And I am not not talking about the code itself, what it does is what I don’t think is right for us.
>>>>>>>>>>>>>>>>> The process is very clear for me that we should first of all think whether we want a certain feature now. Then there should be a clear roadmap for everyone to follow; tasks can be split-up as we go and hopefully then have something that is maintainable, interesting for our users and even would do us proud. This is how this should work.
>>>>>>>>>>>>>>>>> So, what has to change? I don’t think with shouting at each other, throwing patches around and making me generally unhappy is a good start.
>>>>>>>>>>>>>>>>> -Michael
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> Jon
>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>> On Feb 6, 2025, at 2:13 PM, Michael Tremer <michael.tremer@ipfire.org> wrote:
>>>>>>>>>>>>>>>>>>> Hello Jon,
>>>>>>>>>>>>>>>>>>> Well, here we are again with another patch regarding this feature.
>>>>>>>>>>>>>>>>>>> I cannot quite see from your email what the question is, but if this is a request to have this merged into IPFire, I am once again sorry to disappoint you.
>>>>>>>>>>>>>>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls. Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation. Therefore, what am I supposed to do with this email?
>>>>>>>>>>>>>>>>>>> I don’t want to merge code that I don’t agree with. So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>>>>>>>>>>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>>>>>>>>>>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>>>>>>>>>>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>>>>>>>>>>>>>>> All the best,
>>>>>>>>>>>>>>>>>>> -Michael
>>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>>>> On 6 Feb 2025, at 16:35, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>>>>>>>>>>>>>> What is it?
>>>>>>>>>>>>>>>>>>>> Response Policy Zone (RPZ) is a mechanism to define local policies in a
>>>>>>>>>>>>>>>>>>>> standardized way and load those policies from external sources.
>>>>>>>>>>>>>>>>>>>> Bottom line: RPZ allows admins to easily block access to websites via DNS lookup.
>>>>>>>>>>>>>>>>>>>> RPZ can block websites via categories. Examples include: fake websites, annoying
>>>>>>>>>>>>>>>>>>>> pop-up ads, newly registered domains, DoH bypass sites, bad "host" services,
>>>>>>>>>>>>>>>>>>>> maliscious top level domains (e.g., *.zip, *.mov), piracy, gambling, pornography,
>>>>>>>>>>>>>>>>>>>> and more. RPZ lists come from various RPZ providers and their available
>>>>>>>>>>>>>>>>>>>> catagories.
>>>>>>>>>>>>>>>>>>>> This RPZ add-on enables the RPZ functionality by adding a couple lines in a
>>>>>>>>>>>>>>>>>>>> configuration file. This add-on simply adds configuration files and adds
>>>>>>>>>>>>>>>>>>>> scripts (config, metrics and sleep) to make RPZ easier for the admin to use.
>>>>>>>>>>>>>>>>>>>> The RPZ scripts include additional languages: German, Spanish, French, Turkish,
>>>>>>>>>>>>>>>>>>>> and Italian.
>>>>>>>>>>>>>>>>>>>> RPZ itself was release in 2010 and has been part of the IPFire build since ~2015.
>>>>>>>>>>>>>>>>>>>> Why is it needed? What is its value?
>>>>>>>>>>>>>>>>>>>> - The RPZ concept places this filtering into IPFire, our internet access
>>>>>>>>>>>>>>>>>>>> gateway, which is (should be) solely used as DNS source of the internal network.
>>>>>>>>>>>>>>>>>>>> - As most sites use HTTPS it makes it difficult to filter traffic with URL
>>>>>>>>>>>>>>>>>>>> Filter without also properly configuring conventional (non-transparent)
>>>>>>>>>>>>>>>>>>>> mode on the proxy. RPZ is a nice replacement for the URL Filter.
>>>>>>>>>>>>>>>>>>>> - No need to install and maintain an additional device like PiHole or AdBlock
>>>>>>>>>>>>>>>>>>>> browser extensions on multiple user devices.
>>>>>>>>>>>>>>>>>>>> - This is an additional layer of protection for users. Less worry someone will
>>>>>>>>>>>>>>>>>>>> click on something that gets them into trouble. And, saying this with emphasis,
>>>>>>>>>>>>>>>>>>>> the ability to do it in one place!
>>>>>>>>>>>>>>>>>>>> - Blocked sites save on unneeded traffic and can lessen the threat of malware
>>>>>>>>>>>>>>>>>>>> in advertisements
>>>>>>>>>>>>>>>>>>>> - Logging allows the admin to see the site blocked and take actions
>>>>>>>>>>>>>>>>>>>> - RPZ will be used at the home, home-office (work from home), schools,
>>>>>>>>>>>>>>>>>>>> ministerial, and at the office. Device counts are small (2-6) to medium (~80)
>>>>>>>>>>>>>>>>>>>> to mediam-large (200+).
>>>>>>>>>>>>>>>>>>>> - RPZ can block ads, popups, phishing, scammers, spyware, malware, annoying
>>>>>>>>>>>>>>>>>>>> popups, NSFW links, DOH servers, and the usual internet trash.
>>>>>>>>>>>>>>>>>>>> ------------------------------
>>>>>>>>>>>>>>>>>>>> Change Log for RPZ add-on
>>>>>>>>>>>>>>>>>>>> rpz-1.0.0-18 on 2025-02-05
>>>>>>>>>>>>>>>>>>>> - Build for approval & release as IPFire add-on
>>>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>>>> rpz-beta-0.1.18-18.ipfire on 2025-02-01
>>>>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>>>>> - new feature: added a mod key to force a unbound restart
>>>>>>>>>>>>>>>>>>>> rpz-config and rpz-make:
>>>>>>>>>>>>>>>>>>>> - new feature: added action for unbound restart `rpz-config unbound-restart`
>>>>>>>>>>>>>>>>>>>> rpz-metrics:
>>>>>>>>>>>>>>>>>>>> - simple reformatting
>>>>>>>>>>>>>>>>>>>> - rename far right column from "last update" to "last download"
>>>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>>>> rpz-beta-0.1.17-17.ipfire on 2024-12-09
>>>>>>>>>>>>>>>>>>>> rpz-make
>>>>>>>>>>>>>>>>>>>> - bug fix: corrected validation regex for wildcards like: `*.domain.com`
>>>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>>>> rpz-beta-0.1.16-16.ipfire on 2024-11-18
>>>>>>>>>>>>>>>>>>>> rpz-make
>>>>>>>>>>>>>>>>>>>> - new feature: updated validation regex
>>>>>>>>>>>>>>>>>>>> - bug fix: moved validation to beginning of process. Now we validate before
>>>>>>>>>>>>>>>>>>>> creating config files.
>>>>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>>>>> - new feature: use CSS color variables of the main ipfire theme
>>>>>>>>>>>>>>>>>>>> - bug fix: empty zonefile remarks were stored as “undef” and caused a warning
>>>>>>>>>>>>>>>>>>>> - bug fix: HTML textarea removes the first empty line in a custom list
>>>>>>>>>>>>>>>>>>>> - thank you Leo!
>>>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>>>> rpz-beta-0.1.15-15.ipfire on 2024-11-04
>>>>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>>>>> - new feature: added new language file for Turkish (thank you Peppe)
>>>>>>>>>>>>>>>>>>>> rpz-make
>>>>>>>>>>>>>>>>>>>> - bug fix: corrected empty allow/block list issue. An empty allow/block list
>>>>>>>>>>>>>>>>>>>> will now remove contents of allow/block.rpz files and remove unneeded
>>>>>>>>>>>>>>>>>>>> allow/block.conf file. (thank you iptom)
>>>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>>>> rpz-beta-0.1.14-14.ipfire on 2024-10-29
>>>>>>>>>>>>>>>>>>>> rpz-config:
>>>>>>>>>>>>>>>>>>>> - bug fix: correct missing rpz extension. `rpz-config list` displayed URL
>>>>>>>>>>>>>>>>>>>> incorrectly (thank you Bernhard)
>>>>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>>>>> - bug fix: remove extra `"` in language files (thank you Bernhard)
>>>>>>>>>>>>>>>>>>>> - new feature: slightly dim "apply" button when not enabled
>>>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>>>> rpz-beta-0.1.13-13.ipfire on 2024-10-27
>>>>>>>>>>>>>>>>>>>> - skipped
>>>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>>>> rpz-beta-0.1.12-12.ipfire on 2024-10-21
>>>>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>>>>> - new feature: added new language file for French (thank you gw-ipfire)
>>>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>>>> rpz-beta-0.1.11-11.ipfire on 2024-10-18
>>>>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>>>>> - new feature: added new language file for Italian (thank you umberto)
>>>>>>>>>>>>>>>>>>>> - new feature: added new language file for Spanish (thank you Roberto)
>>>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>>>> rpz-beta-0.1.10-10.ipfire on 2024-10-15
>>>>>>>>>>>>>>>>>>>> rpz-make:
>>>>>>>>>>>>>>>>>>>> - bug fix: corrected validation error for a custom list entry (thank you siosios)
>>>>>>>>>>>>>>>>>>>> - e.g., `*.cloudflare-dns.com`
>>>>>>>>>>>>>>>>>>>> install.sh:
>>>>>>>>>>>>>>>>>>>> - bug fix: add chown to correct user created files
>>>>>>>>>>>>>>>>>>>> update.sh:
>>>>>>>>>>>>>>>>>>>> - bug fix: add chown to correct user created files (thank you siosios)
>>>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>>>> rpz-beta-0.1.9-9.ipfire on 2024-10-08
>>>>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>>>>> - new feature: added new language file for German (thank you Leo)
>>>>>>>>>>>>>>>>>>>> - bug fix: add missing "rpz exitcode 110"
>>>>>>>>>>>>>>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>>>> rpz-beta-0.1.8-8.ipfire on 2024-10-04
>>>>>>>>>>>>>>>>>>>> - skipped
>>>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>>>> rpz-beta-0.1.7-7.ipfire on 2024-10-03
>>>>>>>>>>>>>>>>>>>> All:
>>>>>>>>>>>>>>>>>>>> - new feature: includes beta version numbers for pakfire package,
>>>>>>>>>>>>>>>>>>>> instead of only `rpz-1.0.0-1.ipfire`, for each release.
>>>>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>>>>> - new feature: added new WebGUI at `rpz.cgi`
>>>>>>>>>>>>>>>>>>>> - a BIG thank you to Leo Hofmann for all of his work creating the webgui!!
>>>>>>>>>>>>>>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>>>>>>>>>>>>>> rpz-make:
>>>>>>>>>>>>>>>>>>>> - new feature: validate entries in allowlist and blocklist
>>>>>>>>>>>>>>>>>>>> - new feature: add "no-reload" option for WebGUI
>>>>>>>>>>>>>>>>>>>> rpz-metrics:
>>>>>>>>>>>>>>>>>>>> - new feature: info can be sorted by name, by hit count, by line count, by
>>>>>>>>>>>>>>>>>>>> "enabled" list or all lists
>>>>>>>>>>>>>>>>>>>> backups:
>>>>>>>>>>>>>>>>>>>> - bug fix: include all files in `/var/ipfire/dns/rpz` directory in backup
>>>>>>>>>>>>>>>>>>>> update.sh:
>>>>>>>>>>>>>>>>>>>> - bug fix: corrected ownership for `/var/ipfire/dns/rpz` directory during an
>>>>>>>>>>>>>>>>>>>> update
>>>>>>>>>>>>>>>>>>>> Build:
>>>>>>>>>>>>>>>>>>>> - bug fix: `block.rpz.conf` and `block.rpz` from build. Files to be created
>>>>>>>>>>>>>>>>>>>> by `rpz-make`
>>>>>>>>>>>>>>>>>>>> WebGUI and German language file
>>>>>>>>>>>>>>>>>>>> Contribution-by: Leo-Andres Hofmann <hofmann@leo-andres.de>
>>>>>>>>>>>>>>>>>>>> Spanish language file
>>>>>>>>>>>>>>>>>>>> Contribution-by: Roberto Peña
>>>>>>>>>>>>>>>>>>>> Italian language file
>>>>>>>>>>>>>>>>>>>> Contribution-by: Umberto Parma
>>>>>>>>>>>>>>>>>>>> French language file
>>>>>>>>>>>>>>>>>>>> Contribution-by: gw-ipfire
>>>>>>>>>>>>>>>>>>>> Turkish language file
>>>>>>>>>>>>>>>>>>>> Contribution-by: Peppe Tech
>>>>>>>>>>>>>>>>>>>> Contribution-by: Bernhard Bitsch <bbitsch@ipfire.org>
>>>>>>>>>>>>>>>>>>>> Contribution-by: Erik Kapfer <erik.kapfer@ipfire.org>
>>>>>>>>>>>>>>>>>>>> Signed-off-by: Jon Murphy <jon.murphy@ipfire.org
>>>>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>>>>> config/backup/includes/rpz | 4 +
>>>>>>>>>>>>>>>>>>>> config/cfgroot/manualpages | 1 +
>>>>>>>>>>>>>>>>>>>> config/menu/EX-rpz.menu | 6 +
>>>>>>>>>>>>>>>>>>>> config/rootfiles/common/configroot | 1 +
>>>>>>>>>>>>>>>>>>>> config/rootfiles/common/web-user-interface | 1 +
>>>>>>>>>>>>>>>>>>>> config/rootfiles/packages/rpz | 20 +
>>>>>>>>>>>>>>>>>>>> config/rpz/00-rpz.conf | 10 +
>>>>>>>>>>>>>>>>>>>> config/rpz/rpz-config | 130 +++
>>>>>>>>>>>>>>>>>>>> config/rpz/rpz-functions | 85 ++
>>>>>>>>>>>>>>>>>>>> config/rpz/rpz-make | 203 +++++
>>>>>>>>>>>>>>>>>>>> config/rpz/rpz-metrics | 170 ++++
>>>>>>>>>>>>>>>>>>>> config/rpz/rpz-sleep | 58 ++
>>>>>>>>>>>>>>>>>>>> config/rpz/rpz.de.pl | 30 +
>>>>>>>>>>>>>>>>>>>> config/rpz/rpz.en.pl | 30 +
>>>>>>>>>>>>>>>>>>>> config/rpz/rpz.es.pl | 30 +
>>>>>>>>>>>>>>>>>>>> config/rpz/rpz.fr.pl | 30 +
>>>>>>>>>>>>>>>>>>>> config/rpz/rpz.it.pl | 30 +
>>>>>>>>>>>>>>>>>>>> config/rpz/rpz.tr.pl | 30 +
>>>>>>>>>>>>>>>>>>>> html/cgi-bin/rpz.cgi | 923 +++++++++++++++++++++
>>>>>>>>>>>>>>>>>>>> lfs/rpz | 96 +++
>>>>>>>>>>>>>>>>>>>> make.sh | 3 +-
>>>>>>>>>>>>>>>>>>>> src/paks/rpz/install.sh | 36 +
>>>>>>>>>>>>>>>>>>>> src/paks/rpz/uninstall.sh | 38 +
>>>>>>>>>>>>>>>>>>>> src/paks/rpz/update.sh | 52 ++
>>>>>>>>>>>>>>>>>>>> 24 files changed, 2016 insertions(+), 1 deletion(-)
>>>>>>>>>>>>>>>>>>>> create mode 100644 config/backup/includes/rpz
>>>>>>>>>>>>>>>>>>>> create mode 100644 config/menu/EX-rpz.menu
>>>>>>>>>>>>>>>>>>>> create mode 100644 config/rootfiles/packages/rpz
>>>>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/00-rpz.conf
>>>>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz-config
>>>>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz-functions
>>>>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz-make
>>>>>>>>>>>>>>>>>>>> create mode 100755 config/rpz/rpz-metrics
>>>>>>>>>>>>>>>>>>>> create mode 100755 config/rpz/rpz-sleep
>>>>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.de.pl
>>>>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.en.pl
>>>>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.es.pl
>>>>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.fr.pl
>>>>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.it.pl
>>>>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.tr.pl
>>>>>>>>>>>>>>>>>>>> create mode 100644 html/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>>>>>>> create mode 100644 lfs/rpz
>>>>>>>>>>>>>>>>>>>> create mode 100644 src/paks/rpz/install.sh
>>>>>>>>>>>>>>>>>>>> create mode 100644 src/paks/rpz/uninstall.sh
>>>>>>>>>>>>>>>>>>>> create mode 100644 src/paks/rpz/update.sh
>>>>>>>>>>>>>>>>>>>> diff --git a/config/backup/includes/rpz b/config/backup/includes/rpz
>>>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>>>> index 000000000..36513e494
>>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>>> +++ b/config/backup/includes/rpz
>>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,4 @@
>>>>>>>>>>>>>>>>>>>> +/var/ipfire/dns/rpz/*
>>>>>>>>>>>>>>>>>>>> +/etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>>>>>>> +/etc/unbound/zonefiles/block.rpz
>>>>>>>>>>>>>>>>>>>> +/etc/unbound/local.d/*rpz.conf
>>>>>>>>>>>>>>>>>>>> diff --git a/config/cfgroot/manualpages b/config/cfgroot/manualpages
>>>>>>>>>>>>>>>>>>>> index 1f7e01efc..d3a48c633 100644
>>>>>>>>>>>>>>>>>>>> --- a/config/cfgroot/manualpages
>>>>>>>>>>>>>>>>>>>> +++ b/config/cfgroot/manualpages
>>>>>>>>>>>>>>>>>>>> @@ -70,6 +70,7 @@ pakfire.cgi=configuration/ipfire/pakfire
>>>>>>>>>>>>>>>>>>>> wlanap.cgi=addons/wireless
>>>>>>>>>>>>>>>>>>>> tor.cgi=addons/tor
>>>>>>>>>>>>>>>>>>>> samba.cgi=addons/samba
>>>>>>>>>>>>>>>>>>>> +rpz.cgi=addons/rpz
>>>>>>>>>>>>>>>>>>>> # Logs menu
>>>>>>>>>>>>>>>>>>>> logs.cgi/summary.dat=configuration/logs/summary
>>>>>>>>>>>>>>>>>>>> diff --git a/config/menu/EX-rpz.menu b/config/menu/EX-rpz.menu
>>>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>>>> index 000000000..2f4daf410
>>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>>> +++ b/config/menu/EX-rpz.menu
>>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,6 @@
>>>>>>>>>>>>>>>>>>>> +$subipfire->{'20.rpz'} = {
>>>>>>>>>>>>>>>>>>>> + 'caption' => $Lang::tr{'rpz'},
>>>>>>>>>>>>>>>>>>>> + 'uri' => '/cgi-bin/rpz.cgi',
>>>>>>>>>>>>>>>>>>>> + 'title' => "RPZ",
>>>>>>>>>>>>>>>>>>>> + 'enabled' => 1,
>>>>>>>>>>>>>>>>>>>> +};
>>>>>>>>>>>>>>>>>>>> diff --git a/config/rootfiles/common/configroot b/config/rootfiles/common/configroot
>>>>>>>>>>>>>>>>>>>> index 9839eee45..b30d6aae4 100644
>>>>>>>>>>>>>>>>>>>> --- a/config/rootfiles/common/configroot
>>>>>>>>>>>>>>>>>>>> +++ b/config/rootfiles/common/configroot
>>>>>>>>>>>>>>>>>>>> @@ -120,6 +120,7 @@ var/ipfire/menu.d/70-log.menu
>>>>>>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-apcupsd.menu
>>>>>>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-guardian.menu
>>>>>>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-mympd.menu
>>>>>>>>>>>>>>>>>>>> +#var/ipfire/menu.d/EX-rpz.menu
>>>>>>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-samba.menu
>>>>>>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-tor.menu
>>>>>>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-transmission.menu
>>>>>>>>>>>>>>>>>>>> diff --git a/config/rootfiles/common/web-user-interface b/config/rootfiles/common/web-user-interface
>>>>>>>>>>>>>>>>>>>> index 816241dae..e00464076 100644
>>>>>>>>>>>>>>>>>>>> --- a/config/rootfiles/common/web-user-interface
>>>>>>>>>>>>>>>>>>>> +++ b/config/rootfiles/common/web-user-interface
>>>>>>>>>>>>>>>>>>>> @@ -69,6 +69,7 @@ srv/web/ipfire/cgi-bin/proxy.cgi
>>>>>>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/qos.cgi
>>>>>>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/remote.cgi
>>>>>>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/routing.cgi
>>>>>>>>>>>>>>>>>>>> +#srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>>>>>>> #srv/web/ipfire/cgi-bin/samba.cgi
>>>>>>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/services.cgi
>>>>>>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/shutdown.cgi
>>>>>>>>>>>>>>>>>>>> diff --git a/config/rootfiles/packages/rpz b/config/rootfiles/packages/rpz
>>>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>>>> index 000000000..1c8663049
>>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>>> +++ b/config/rootfiles/packages/rpz
>>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,20 @@
>>>>>>>>>>>>>>>>>>>> +etc/unbound/local.d/00-rpz.conf
>>>>>>>>>>>>>>>>>>>> +etc/unbound/zonefiles
>>>>>>>>>>>>>>>>>>>> +etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>>>>>>> +usr/sbin/rpz-config
>>>>>>>>>>>>>>>>>>>> +usr/sbin/rpz-functions
>>>>>>>>>>>>>>>>>>>> +usr/sbin/rpz-make
>>>>>>>>>>>>>>>>>>>> +usr/sbin/rpz-metrics
>>>>>>>>>>>>>>>>>>>> +usr/sbin/rpz-sleep
>>>>>>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.de.pl
>>>>>>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.en.pl
>>>>>>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.es.pl
>>>>>>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.fr.pl
>>>>>>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.it.pl
>>>>>>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.tr.pl
>>>>>>>>>>>>>>>>>>>> +var/ipfire/backup/addons/includes/rpz
>>>>>>>>>>>>>>>>>>>> +var/ipfire/dns/rpz
>>>>>>>>>>>>>>>>>>>> +var/ipfire/dns/rpz/allowlist
>>>>>>>>>>>>>>>>>>>> +var/ipfire/dns/rpz/blocklist
>>>>>>>>>>>>>>>>>>>> +var/ipfire/menu.d/EX-rpz.menu
>>>>>>>>>>>>>>>>>>>> +srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>>>>>>> diff --git a/config/rpz/00-rpz.conf b/config/rpz/00-rpz.conf
>>>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>>>> index 000000000..f005a4f2e
>>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>>> +++ b/config/rpz/00-rpz.conf
>>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,10 @@
>>>>>>>>>>>>>>>>>>>> +server:
>>>>>>>>>>>>>>>>>>>> + module-config: "respip validator iterator"
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +rpz:
>>>>>>>>>>>>>>>>>>>> + name: allow.rpz
>>>>>>>>>>>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>>>>>>> + rpz-action-override: passthru
>>>>>>>>>>>>>>>>>>>> + rpz-log: yes
>>>>>>>>>>>>>>>>>>>> + rpz-log-name: allow
>>>>>>>>>>>>>>>>>>>> + rpz-signal-nxdomain-ra: yes
>>>>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-config b/config/rpz/rpz-config
>>>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>>>> index 000000000..c72d50f9b
>>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-config
>>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,130 @@
>>>>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +version="2025-01-11 - v44"
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +tagName="unbound"
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +rpzAction="${1}" # input RPZ action
>>>>>>>>>>>>>>>>>>>> +rpzName="${2}" # input RPZ name
>>>>>>>>>>>>>>>>>>>> +rpzURL="${3}" # input RPZ URL
>>>>>>>>>>>>>>>>>>>> +rpzOption1="${4}" # input RPZ option #1
>>>>>>>>>>>>>>>>>>>> +rpzOption2="${5}" # input RPZ option #2
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +rpzConfig="/etc/unbound/local.d/${rpzName}.rpz.conf" # output zone conf file
>>>>>>>>>>>>>>>>>>>> +rpzFile="/etc/unbound/zonefiles/${rpzName}.rpz" # output for RPZ file
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>>>>>>>>>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>>>>>>>>>>>> + case "$1" in
>>>>>>>>>>>>>>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>>>>>>>>>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>>>>>>>>>>>>>>> + esac
>>>>>>>>>>>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +case "${rpzAction}" in
>>>>>>>>>>>>>>>>>>>> + # add new rpz list
>>>>>>>>>>>>>>>>>>>> + add )
>>>>>>>>>>>>>>>>>>>> + check_name "${rpzName}" # is this a valid name?
>>>>>>>>>>>>>>>>>>>> + # does this config already exist? If yes, then exit
>>>>>>>>>>>>>>>>>>>> + if [[ -f "${rpzConfig}" ]] ; then
>>>>>>>>>>>>>>>>>>>> + msg_log "error: rpz: duplicate - ${rpzConfig} already exists. exit"
>>>>>>>>>>>>>>>>>>>> + exit 104
>>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # is this a valid URL?
>>>>>>>>>>>>>>>>>>>> + regex='^https://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
>>>>>>>>>>>>>>>>>>>> + if ! [[ "${rpzURL}" =~ $regex ]] ; then
>>>>>>>>>>>>>>>>>>>> + msg_log "error: rpz: the URL is not valid: \"${rpzURL}\". exit."
>>>>>>>>>>>>>>>>>>>> + exit 105
>>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # create the zone config file
>>>>>>>>>>>>>>>>>>>> + {
>>>>>>>>>>>>>>>>>>>> + echo "rpz:"
>>>>>>>>>>>>>>>>>>>> + echo " name: ${rpzName}.rpz"
>>>>>>>>>>>>>>>>>>>> + echo " zonefile: ${rpzFile}"
>>>>>>>>>>>>>>>>>>>> + echo " url: ${rpzURL}"
>>>>>>>>>>>>>>>>>>>> + echo " rpz-action-override: nxdomain"
>>>>>>>>>>>>>>>>>>>> + echo " rpz-log: ${rpzLog}"
>>>>>>>>>>>>>>>>>>>> + echo " rpz-log-name: ${rpzName}"
>>>>>>>>>>>>>>>>>>>> + echo " rpz-signal-nxdomain-ra: yes"
>>>>>>>>>>>>>>>>>>>> + } > "${rpzConfig}"
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # set-up zonefile
>>>>>>>>>>>>>>>>>>>> + # create an empty rpz file if it does not exist
>>>>>>>>>>>>>>>>>>>> + if [[ ! -f "${rpzFile}" ]] ; then
>>>>>>>>>>>>>>>>>>>> + touch "${rpzFile}"
>>>>>>>>>>>>>>>>>>>> + # unbound requires these settings for rpz files
>>>>>>>>>>>>>>>>>>>> + set_permissions "${rpzFile}" "${rpzConfig}"
>>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # trash config file & rpz file
>>>>>>>>>>>>>>>>>>>> + remove )
>>>>>>>>>>>>>>>>>>>> + if ! [[ -f "${rpzConfig}" ]] ; then
>>>>>>>>>>>>>>>>>>>> + msg_log "error: rpz: cannot remove ${rpzConfig}, does not exist. exit"
>>>>>>>>>>>>>>>>>>>> + exit 106
>>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + msg_log "info: rpz: remove config file & rpz file \"${rpzName}\""
>>>>>>>>>>>>>>>>>>>> + rm "${rpzConfig}"
>>>>>>>>>>>>>>>>>>>> + rm "${rpzFile}"
>>>>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + reload )
>>>>>>>>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + list )
>>>>>>>>>>>>>>>>>>>> + awk -F':' '/^\s*name:/{ gsub(/[[:blank:]]|\.rpz/, "",$2) ; NAME=$2 } \
>>>>>>>>>>>>>>>>>>>> + /^\s*url:/{ gsub(/[[:blank:]]/, "") ; print NAME"="$2":"$3} ' \
>>>>>>>>>>>>>>>>>>>> + /etc/unbound/local.d/*rpz.conf
>>>>>>>>>>>>>>>>>>>> + exit
>>>>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + unbound-restart )
>>>>>>>>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>>>>>>>>> + unbound_restart
>>>>>>>>>>>>>>>>>>>> + exit
>>>>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + * )
>>>>>>>>>>>>>>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>>>>>>>>>>>>>>> + printf "Usage: $(basename "$0") <ACTION> <NAME> <URL> <OPTION> <OPTION>\n"
>>>>>>>>>>>>>>>>>>>> + printf "Version: ${version}\n"
>>>>>>>>>>>>>>>>>>>> + exit 108
>>>>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +esac
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +exit
>>>>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-functions b/config/rpz/rpz-functions
>>>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>>>> index 000000000..ace1d2690
>>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-functions
>>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,85 @@
>>>>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +version="2024-12-10 - v02"
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +msg_log () {
>>>>>>>>>>>>>>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>>>>>>>>>>>>>>> + if tty --silent ; then
>>>>>>>>>>>>>>>>>>>> + echo "${tagName}:" "$*"
>>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# check for a valid name
>>>>>>>>>>>>>>>>>>>> +check_name () {
>>>>>>>>>>>>>>>>>>>> + local theName="${1}"
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + regex='^[a-zA-Z0-9_]+$' # no dash or plus, alpha numeric only
>>>>>>>>>>>>>>>>>>>> + regex1='^(allow|block)$' # allow and block are reserved NAMEs
>>>>>>>>>>>>>>>>>>>> + if [[ ! "${theName}" =~ $regex ]] || [[ "${theName}" =~ $regex1 ]] ; then
>>>>>>>>>>>>>>>>>>>> + msg_log "error: rpz: the NAME is not valid: \"${theName}\". exit."
>>>>>>>>>>>>>>>>>>>> + exit 101
>>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +set_permissions () {
>>>>>>>>>>>>>>>>>>>> + chown nobody:nobody "$@"
>>>>>>>>>>>>>>>>>>>> + chmod 644 "$@"
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +check_unbound_conf () {
>>>>>>>>>>>>>>>>>>>> + local thecheckConf="${1:-yes}" # check config default is yes
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # check the above config files
>>>>>>>>>>>>>>>>>>>> + if [[ "${thecheckConf}" == yes ]] ; then
>>>>>>>>>>>>>>>>>>>> + msg_log "info: rpz: check for errors with \"unbound-checkconf\""
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + if ! unbound-checkconf ; then
>>>>>>>>>>>>>>>>>>>> + msg_log "error: rpz: unbound-checkconf found invalid configuration."
>>>>>>>>>>>>>>>>>>>> + msg_log \
>>>>>>>>>>>>>>>>>>>> + "error: rpz: In Terminal run the command \"unbound-checkconf\" for more information. exit."
>>>>>>>>>>>>>>>>>>>> + exit 102
>>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +unbound_control_reload () {
>>>>>>>>>>>>>>>>>>>> + local theReload="${1:-yes}" # reload default is yes
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + if [[ "${theReload}" == yes ]] ; then
>>>>>>>>>>>>>>>>>>>> + # reload due to the changes
>>>>>>>>>>>>>>>>>>>> + msg_log "info: rpz: run \"unbound-control reload\""
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + if ! unbound-control reload ; then
>>>>>>>>>>>>>>>>>>>> + msg_log "error: rpz: unbound-control reload. exit."
>>>>>>>>>>>>>>>>>>>> + exit 109
>>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +unbound_restart () {
>>>>>>>>>>>>>>>>>>>> + # restart due to the changes
>>>>>>>>>>>>>>>>>>>> + msg_log "info: rpz: run \"unbound restart\""
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + /usr/local/bin/unboundctrl restart
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-make b/config/rpz/rpz-make
>>>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>>>> index 000000000..927d55170
>>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-make
>>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,203 @@
>>>>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +version="2025-01-11 - v14"
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# create the config file for allow
>>>>>>>>>>>>>>>>>>>> +make_allow_config () {
>>>>>>>>>>>>>>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>>>>>>>>>>>>>>> + local theConfig="/etc/unbound/local.d/00-rpz.conf" # output zone conf file
>>>>>>>>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/allowlist" # input custom list of domains
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + msg_log "info: rpz: make config file \"00-rpz.conf\""
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + echo "server:
>>>>>>>>>>>>>>>>>>>> + module-config: \"respip validator iterator\"" > "${theConfig}"
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # does allow list exist?
>>>>>>>>>>>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + echo "rpz:
>>>>>>>>>>>>>>>>>>>> + name: allow.rpz
>>>>>>>>>>>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>>>>>>> + rpz-action-override: passthru
>>>>>>>>>>>>>>>>>>>> + rpz-log: ${theLog}
>>>>>>>>>>>>>>>>>>>> + rpz-log-name: allow
>>>>>>>>>>>>>>>>>>>> + rpz-signal-nxdomain-ra: yes" >> "${theConfig}"
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>>>>>>>>>>>> + set_permissions "${theConfig}"
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# create the config file for block
>>>>>>>>>>>>>>>>>>>> +make_block_config () {
>>>>>>>>>>>>>>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>>>>>>>>>>>>>>> + local theConfig="/etc/unbound/local.d/block.rpz.conf" # output zone conf file
>>>>>>>>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/blocklist" # input custom list of domains
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + msg_log "info: rpz: make config file \"block.rpz.conf\""
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # does block list exist?
>>>>>>>>>>>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + echo "rpz:
>>>>>>>>>>>>>>>>>>>> + name: block.rpz
>>>>>>>>>>>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/block.rpz
>>>>>>>>>>>>>>>>>>>> + rpz-action-override: nxdomain
>>>>>>>>>>>>>>>>>>>> + rpz-log: ${theLog}
>>>>>>>>>>>>>>>>>>>> + rpz-log-name: block
>>>>>>>>>>>>>>>>>>>> + rpz-signal-nxdomain-ra: yes" > "${theConfig}"
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>>>>>>>>>>>> + set_permissions "${theConfig}"
>>>>>>>>>>>>>>>>>>>> + else
>>>>>>>>>>>>>>>>>>>> + # no - trash the config file
>>>>>>>>>>>>>>>>>>>> + rm --verbose /etc/unbound/local.d/block.rpz.conf
>>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# create an RPZ file for allow or block
>>>>>>>>>>>>>>>>>>>> +make_rpz_file () {
>>>>>>>>>>>>>>>>>>>> + local theName="${1}" # allow or block
>>>>>>>>>>>>>>>>>>>> + local theAction='.' # the default is nxdomain or block
>>>>>>>>>>>>>>>>>>>> + local actionList
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>>>>>>>>>>>>>>> + local theZonefile="/etc/unbound/zonefiles/${theName}.rpz" # output file for RPZ
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # does a list exist?
>>>>>>>>>>>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # for allow set to passthru
>>>>>>>>>>>>>>>>>>>> + [[ "${theName}" == allow ]] && theAction='rpz-passthru.'
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # drop any extra "blanks" and add "CNAME <RPZ action>." to each line
>>>>>>>>>>>>>>>>>>>> + actionList=$( awk '{$1=$1};1' "${theList}" |
>>>>>>>>>>>>>>>>>>>> + sed "/^[^;].*[[:alnum:]]/ s|$| CNAME ${theAction}|" )
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + msg_log "info: rpz: create zonefile for \"${theName}list\""
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +echo "; Name: ${theName} list
>>>>>>>>>>>>>>>>>>>> +; Last modified: $(date "+%Y-%m-%d at %H.%M.%S %Z")
>>>>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>>>>> +; domains with actions list
>>>>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>>>>> +${actionList}" > "${theZonefile}"
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>>>>>>>>>>>> + set_permissions "${theZonefile}"
>>>>>>>>>>>>>>>>>>>> + # set-up allow/block list files
>>>>>>>>>>>>>>>>>>>> + set_permissions "${theList}"
>>>>>>>>>>>>>>>>>>>> + else
>>>>>>>>>>>>>>>>>>>> + msg_log "info: rpz: the ${theList} is empty."
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + rm --verbose "${theZonefile}" # trash the RPZ file
>>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# check if allow/block list is valid
>>>>>>>>>>>>>>>>>>>> +validate_list () {
>>>>>>>>>>>>>>>>>>>> + local theName="${1}" # allow or block
>>>>>>>>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # remove good:
>>>>>>>>>>>>>>>>>>>> + # - properly formated domain names with or without leading wildcard
>>>>>>>>>>>>>>>>>>>> + # - properly formated top level domain (TLD) names with wildcard
>>>>>>>>>>>>>>>>>>>> + # - blank lines and comment lines
>>>>>>>>>>>>>>>>>>>> + # remaining lines are considered "bad"
>>>>>>>>>>>>>>>>>>>> + bad_lines=$( sed --regexp-extended \
>>>>>>>>>>>>>>>>>>>> + '/^(\*\.)?([a-zA-Z0-9](([a-zA-Z0-9\-]){0,61}[a-zA-Z0-9])?\.)+([a-zA-Z]{2,}|xn--[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])$/d ;
>>>>>>>>>>>>>>>>>>>> + /^(\*\.)([a-z]{2,61}|xn--[a-z0-9]{1,60})$/d ;
>>>>>>>>>>>>>>>>>>>> + /^$/d ; /^;/d' "${theList}" )
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + if [[ ! -z "${bad_lines}" ]] ; then
>>>>>>>>>>>>>>>>>>>> + msg_log "error: rpz: invalid line(s) in ${theList}."
>>>>>>>>>>>>>>>>>>>> + printf "%s\n" "bad line(s): ${bad_lines}"
>>>>>>>>>>>>>>>>>>>> + exit 110
>>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +tagName="unbound"
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +rpzName="${1}" # input RPZ name
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>>>>>>>>>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>>>>>>>>>>>> + case "$1" in
>>>>>>>>>>>>>>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>>>>>>>>>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>>>>>>>>>>>>>>> + esac
>>>>>>>>>>>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +case "${rpzName}" in
>>>>>>>>>>>>>>>>>>>> + # make a new allow or block rpz file
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + allow )
>>>>>>>>>>>>>>>>>>>> + validate_list 'allow' # is the allowlist valid?
>>>>>>>>>>>>>>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>>>>>>>>>>>>>>> + make_rpz_file 'allow'
>>>>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + allowblock )
>>>>>>>>>>>>>>>>>>>> + validate_list 'allow' # is the list valid?
>>>>>>>>>>>>>>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>>>>>>>>>>>>>>> + make_rpz_file 'allow'
>>>>>>>>>>>>>>>>>>>> + ;&
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + block )
>>>>>>>>>>>>>>>>>>>> + validate_list 'block' # is the blocklist valid?
>>>>>>>>>>>>>>>>>>>> + make_block_config "${rpzLog}"
>>>>>>>>>>>>>>>>>>>> + make_rpz_file 'block'
>>>>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + reload )
>>>>>>>>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + unbound-restart )
>>>>>>>>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>>>>>>>>> + unbound_restart
>>>>>>>>>>>>>>>>>>>> + exit
>>>>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + * )
>>>>>>>>>>>>>>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>>>>>>>>>>>>>>> + printf "Usage: $(basename "$0") <NAME> <OPTION> <OPTION>\n"
>>>>>>>>>>>>>>>>>>>> + printf "Version: ${version}\n"
>>>>>>>>>>>>>>>>>>>> + exit 108
>>>>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>>>>> +esac
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +exit
>>>>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-metrics b/config/rpz/rpz-metrics
>>>>>>>>>>>>>>>>>>>> new file mode 100755
>>>>>>>>>>>>>>>>>>>> index 000000000..4d43e1629
>>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-metrics
>>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,170 @@
>>>>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +version="2025-01-20 - v25"
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +weeks="2" # default to two message logs
>>>>>>>>>>>>>>>>>>>> +sortBy="name" # default "by name"
>>>>>>>>>>>>>>>>>>>> +rpzActive="enabled" # default "enabled only"
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>>>>>>>>>>>> + case "$1" in
>>>>>>>>>>>>>>>>>>>> + --by-names | --by-name | name ) sortBy="name" ;;
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + --by-hits | --by-hit | hits | hit ) sortBy="hit" ;;
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + --by-lines | --by-line | lines | line ) sortBy="line" ;;
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + --by-effect ) sortBy="effect" ;;
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + --enabled-only ) rpzActive="enabled" ;;
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + --active-all | --all | all ) rpzActive="all" ;;
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + [0-9] | [0-9][0-9] ) weeks=$1 ;;
>>>>>>>>>>>>>>>>>>>> + esac
>>>>>>>>>>>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# get the list of message logs for N weeks
>>>>>>>>>>>>>>>>>>>> +messageLogs=$( find /var/log/messages* -type f | sort --version-sort |
>>>>>>>>>>>>>>>>>>>> + head -"${weeks}" )
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# get the list of RPZ names & counts from the message log(s)
>>>>>>>>>>>>>>>>>>>> +rpzNameCount=$( for logf in ${messageLogs} ; do
>>>>>>>>>>>>>>>>>>>> + zgrep --text --fixed-strings 'info: rpz: applied' "${logf}" |
>>>>>>>>>>>>>>>>>>>> + awk '$10 ~ /\[\w*]/ { print $10 }' ;
>>>>>>>>>>>>>>>>>>>> + done | sort | uniq --count )
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# flip results and remove brackets `[` and `]`
>>>>>>>>>>>>>>>>>>>> +rpzNameCount=$( echo "${rpzNameCount}" |
>>>>>>>>>>>>>>>>>>>> + awk '{ print $2, $1 }' |
>>>>>>>>>>>>>>>>>>>> + sed --regexp-extended 's|^\[(.*)\]|\1|' )
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# grab only names
>>>>>>>>>>>>>>>>>>>> +rpzNames=$( echo "${rpzNameCount}" | awk '{ print $1 }' )
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# get list of RPZ files
>>>>>>>>>>>>>>>>>>>> +rpzFileList=$( find /etc/unbound/zonefiles -type f -iname "*.rpz" )
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# get basename of those files
>>>>>>>>>>>>>>>>>>>> +rpzBaseNames=$( echo "${rpzFileList}" |
>>>>>>>>>>>>>>>>>>>> + sed 's|/etc/unbound/zonefiles/||g ; s|\.rpz||g ;' )
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# add to rpzNames
>>>>>>>>>>>>>>>>>>>> +rpzNames="${rpzNames}"$'\n'"${rpzBaseNames}"
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# drop duplicate names
>>>>>>>>>>>>>>>>>>>> +rpzNames=$( echo "${rpzNames}" | sort --unique )
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# get line count for each RPZ
>>>>>>>>>>>>>>>>>>>> +lineCount=$( echo "${rpzFileList}" | xargs wc -l )
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# get comment line count and blank line count for each RPZ
>>>>>>>>>>>>>>>>>>>> +commentCount=$( echo "${rpzFileList}" | xargs grep --count -e "^$" -e "^;" )
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# get modified date each RPZ
>>>>>>>>>>>>>>>>>>>> +modDateList=$( echo "${rpzFileList}" | xargs stat -c '%.10y %n' )
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +ucListAuthZones=$( unbound-control list_auth_zones )
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# get width of RPZ names
>>>>>>>>>>>>>>>>>>>> +pWidth=$( echo "${rpzNames}" | awk '{ print $1" " }' | wc -L )
>>>>>>>>>>>>>>>>>>>> +pFormat="%-${pWidth}s %-8s %-8s %8s %12s %12s\n"
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# print title line
>>>>>>>>>>>>>>>>>>>> +printf "${pFormat}" "name" "hits" "active" "lines" " hits/line" "last download"
>>>>>>>>>>>>>>>>>>>> +printf -- "--------------"
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +theResults=""
>>>>>>>>>>>>>>>>>>>> +totalLines=0
>>>>>>>>>>>>>>>>>>>> +totalHits=0
>>>>>>>>>>>>>>>>>>>> +while read -r theName
>>>>>>>>>>>>>>>>>>>> +do
>>>>>>>>>>>>>>>>>>>> + printf -- "--" # pretend progress bar
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # is this RPZ list active?
>>>>>>>>>>>>>>>>>>>> + theActive="disabled"
>>>>>>>>>>>>>>>>>>>> + if grep --quiet "^${theName}\.rpz" <<< "${ucListAuthZones}"
>>>>>>>>>>>>>>>>>>>> + then
>>>>>>>>>>>>>>>>>>>> + theActive="enabled"
>>>>>>>>>>>>>>>>>>>> + else
>>>>>>>>>>>>>>>>>>>> + [[ "${rpzActive}" == enabled ]] && continue
>>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # get hit count
>>>>>>>>>>>>>>>>>>>> + theHits="0"
>>>>>>>>>>>>>>>>>>>> + if output=$( grep "^${theName}\s" <<< "${rpzNameCount}" ) ; then
>>>>>>>>>>>>>>>>>>>> + theHits=$( echo "${output}" | awk '{ print $2 }' )
>>>>>>>>>>>>>>>>>>>> + totalHits=$(( totalHits + theHits ))
>>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # get line count
>>>>>>>>>>>>>>>>>>>> + theLines="n/a"
>>>>>>>>>>>>>>>>>>>> + hitsPerLine="0"
>>>>>>>>>>>>>>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${lineCount}" ) ; then
>>>>>>>>>>>>>>>>>>>> + theLines=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>>>>>>>>>>>>>>> + totalLines=$(( totalLines + theLines ))
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + if [[ "${theLines}" -gt 2 ]] ; then
>>>>>>>>>>>>>>>>>>>> + hitsPerLine=$(( 100 * theHits / theLines ))
>>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # get modification date
>>>>>>>>>>>>>>>>>>>> + theModDate="n/a"
>>>>>>>>>>>>>>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${modDateList}" ) ; then
>>>>>>>>>>>>>>>>>>>> + theModDate=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # add to results list
>>>>>>>>>>>>>>>>>>>> + theResults+="${theName} ${theHits} ${theActive} ${theLines} ${hitsPerLine} ${theModDate}"$'\n'
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +done <<< "${rpzNames}"
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +case "${sortBy}" in
>>>>>>>>>>>>>>>>>>>> + # sort by "active" then by "name"
>>>>>>>>>>>>>>>>>>>> + name) sortArg=(-k3,3r -k1,1) ;;
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # sort by "active" then by "hits" then by "name"
>>>>>>>>>>>>>>>>>>>> + hit) sortArg=(-k3,3r -k2,2nr -k1,1) ;;
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # sort by "active" then by "lines" then by "name"
>>>>>>>>>>>>>>>>>>>> + line) sortArg=(-k3,3r -k4,4nr -k1,1) ;;
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # sort by "active" then by "effect" then by "name"
>>>>>>>>>>>>>>>>>>>> + effect) sortArg=(-k3,3r -k5,5nr -k1,1) ;;
>>>>>>>>>>>>>>>>>>>> +esac
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +printf -- "--------------\n"
>>>>>>>>>>>>>>>>>>>> +# remove blank lines, sort, print as columns
>>>>>>>>>>>>>>>>>>>> +echo "${theResults}" |
>>>>>>>>>>>>>>>>>>>> + awk '!/^[[:space:]]*$/' |
>>>>>>>>>>>>>>>>>>>> + sort "${sortArg[@]}" |
>>>>>>>>>>>>>>>>>>>> + awk --assign=width="${pWidth}" \
>>>>>>>>>>>>>>>>>>>> + '{ printf "%-*s %-8s %-8s %8s %10s %% %12s\n", width, $1, $2, $3, $4, $5, $6 }'
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +printf "${pFormat}" "" "=======" "" "========" "" ""
>>>>>>>>>>>>>>>>>>>> +printf "${pFormat}" "Totals -->" "${totalHits}" "" "${totalLines}" "" ""
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +exit
>>>>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-sleep b/config/rpz/rpz-sleep
>>>>>>>>>>>>>>>>>>>> new file mode 100755
>>>>>>>>>>>>>>>>>>>> index 000000000..dd3603599
>>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-sleep
>>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,58 @@
>>>>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +version="2024-08-16" # v05
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# send message to message log
>>>>>>>>>>>>>>>>>>>> +msg_log () {
>>>>>>>>>>>>>>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>>>>>>>>>>>>>>> + if tty --silent ; then
>>>>>>>>>>>>>>>>>>>> + echo "${tagName}:" "$*"
>>>>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +tagName="unbound"
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +sleepTime="${1:-5m}" # default to sleep for 5m (5 minutes)
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +zoneList=$( unbound-control list_auth_zones | awk '{print $1}' )
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +for zone in ${zoneList} ; do
>>>>>>>>>>>>>>>>>>>> + printf "disable ${zone}\t"
>>>>>>>>>>>>>>>>>>>> + unbound-control rpz_disable "${zone}"
>>>>>>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +msg_log "info: rpz: disabled all zones for ${sleepTime}"
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +sleep "${sleepTime}"
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +for zone in ${zoneList} ; do
>>>>>>>>>>>>>>>>>>>> + printf "enable ${zone}\t"
>>>>>>>>>>>>>>>>>>>> + unbound-control rpz_enable "${zone}"
>>>>>>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +msg_log "info: rpz: enabled all zones"
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +exit
>>>>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.de.pl b/config/rpz/rpz.de.pl
>>>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>>>> index 000000000..3770c6bb0
>>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.de.pl
>>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>>>>>>> +'rpz apply' => 'Übernehmen',
>>>>>>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Benutzerdefinierte Allowlist aktivieren:',
>>>>>>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Zugelassene Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>>>>> +'rpz cl allow' => 'Benutzerdefinierte Allowlist',
>>>>>>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Benutzerdefinierte Blocklist aktivieren:',
>>>>>>>>>>>>>>>>>>>> +'rpz cl block info' => 'Gesperrte Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>>>>> +'rpz cl block' => 'Benutzerdefinierte Blocklist',
>>>>>>>>>>>>>>>>>>>> +'rpz cl' => 'Benutzerdefinierte Listen',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'Der Name enthält unzulässige Zeichen',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf hat eine fehlerhafte Konfiguration ermittelt. Führen Sie das Kommando unbound-checkconf auf der Konsole aus, um weitere Informationen zu erhalten.',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'Die benutzerdefinierte Allow-/Blocklist ist leer',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'Ein Eintrag mit identischem Namen existiert bereits',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'Die URL ist ungültig',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'Eintrag kann nicht entfernt werden, der Name existiert nicht',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'Der Name ist ungültig - nur "allow" oder "block" möglich',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'Fehlende oder inkorrekte Parameter',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control reload ist fehlgeschlagen',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'Die benutzerdefinierte Allow-/Blocklist enthält unzulässige Einträge',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'Die Anmerkung enthält unzulässige Zeichen',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'Ungültiger Eintrag in der benutzerdefinierten Allowlist, Zeile ',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'Ungültiger Eintrag in der benutzerdefinierten Blocklist, Zeile ',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'Ausgewählter Eintrag existiert nicht: ',
>>>>>>>>>>>>>>>>>>>> +'rpz zf editor' => 'Zonendatei-Eintrag bearbeiten',
>>>>>>>>>>>>>>>>>>>> +'rpz zf imported' => '(importiert aus rpz-config)',
>>>>>>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Erlaubte Zeichen sind a-z, A-Z, 0-9 und Unterstriche',
>>>>>>>>>>>>>>>>>>>> +'rpz zf' => 'Zonendateien',
>>>>>>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.en.pl b/config/rpz/rpz.en.pl
>>>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>>>> index 000000000..0720a8940
>>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.en.pl
>>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>>>>>>> +'rpz apply' => 'Apply',
>>>>>>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Enable custom allowlist:',
>>>>>>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Allowed domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>>>>> +'rpz cl allow' => 'Custom allowlist',
>>>>>>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Enable custom blocklist:',
>>>>>>>>>>>>>>>>>>>> +'rpz cl block info' => 'Blocked domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>>>>> +'rpz cl block' => 'Custom blocklist',
>>>>>>>>>>>>>>>>>>>> +'rpz cl' => 'Custom lists',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'the NAME is not valid',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf found invalid configuration. In the Terminal run the command unbound-checkconf for more information',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'the allow/block list is empty',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'duplicate - NAME already exists',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'the URL is not valid',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'cannot remove the NAME does not exist',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'the NAME is not valid - "allow" or "block" only',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'missing or incorrect parameter',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control reload failed',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'custom Allowlist/Blocklist contains invalid entries',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'the REMARK is not valid',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'invalid entry in allowlist, line ',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'invalid entry in blocklist, line ',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'Selected entry does not exist: ',
>>>>>>>>>>>>>>>>>>>> +'rpz zf editor' => 'Edit zonefiles entry',
>>>>>>>>>>>>>>>>>>>> +'rpz zf imported' => '(imported from rpz-config)',
>>>>>>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Valid characters are a-z, A-Z, 0-9 and underscore.',
>>>>>>>>>>>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.es.pl b/config/rpz/rpz.es.pl
>>>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>>>> index 000000000..98628e4aa
>>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.es.pl
>>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>>>>>>> +'rpz' => 'Zonas de política de respuesta (RPZ)',
>>>>>>>>>>>>>>>>>>>> +'rpz apply' => 'Aplicar',
>>>>>>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Habilitar la lista blanca personalizada:',
>>>>>>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Dominio permitido (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>>>>> +'rpz cl allow' => 'Lista blanca personalizada',
>>>>>>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Habilitar la lista negra personalizada:',
>>>>>>>>>>>>>>>>>>>> +'rpz cl block info' => 'Dominio bloqueado (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>>>>> +'rpz cl block' => 'Lista negra personalizada',
>>>>>>>>>>>>>>>>>>>> +'rpz cl' => 'Lista personalizada',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'El NOMBRE no es válido',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha encontrado una configuración no válida. Desde Terminal, ejecute el comando unbound-checkconf para mayor información',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'La lista de permitidos/bloqueados está vacía',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'duplicado - NOMBRE ya existe',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'la URL no es válida',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'no es posible eliminar el NOMBRE que no existe',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'el NOMBRE no es válido - sólo "permitir" o "bloquear"',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'parámetro faltante o incorrecto',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'Error al recargar unbound-control',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'la Lista blanca/Lista negra personalizada contiene entradas no válidas',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'el COMENTARIO no es válido',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'entrada no válida en la lista blanca, línea ',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'entrada no válida en la lista negra, línea ',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'La entrada seleccionada no existe: ',
>>>>>>>>>>>>>>>>>>>> +'rpz zf editor' => 'Editar la entrada de archivos de zona',
>>>>>>>>>>>>>>>>>>>> +'rpz zf imported' => '(importado de rpz-config)',
>>>>>>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Los caracteres válidos son a-z, A-Z, 0-9 y guión bajo',
>>>>>>>>>>>>>>>>>>>> +'rpz zf' => 'Archivos de zona',
>>>>>>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.fr.pl b/config/rpz/rpz.fr.pl
>>>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>>>> index 000000000..f35f3c2d0
>>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.fr.pl
>>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>>>>>>> +'rpz apply' => 'Appliquer',
>>>>>>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Activer la liste d\'autorisations personnalisée:',
>>>>>>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Domaines autorisés (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>>>>> +'rpz cl allow' => 'Liste d\'autorisations personnalisée',
>>>>>>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Activer la liste de blocage personnalisée:',
>>>>>>>>>>>>>>>>>>>> +'rpz cl block info' => 'Domaines bloqués (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>>>>> +'rpz cl block' => 'liste de blocage personnalisé',
>>>>>>>>>>>>>>>>>>>> +'rpz cl' => 'Listes personnalisées',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'le NOM n\'est pas valide',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf configuration non valide trouvée. Dans le terminal, exécutez la commande unbound-checkconf pour plus d\'informations',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'la liste autoriser/bloquer est vide',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'le NOM existe déjà',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'L\'URL n\'est pas valide',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'impossible de supprimer le NOM n\'existe pas',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'le NOM n\'est pas valide - « autoriser » ou « bloquer » seulement',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'paramètre manquant ou incorrect',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control rechargement échoué',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'la liste autoriser/bloquer contient des entrées non valides',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'la REMARQUE n\'est pas valable',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'entrée non valide dans la liste d\'autorisation, ligne ',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'entrée non valide dans la liste de blocs, ligne ',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'L\'entrée sélectionnée n\'existe pas: ',
>>>>>>>>>>>>>>>>>>>> +'rpz zf editor' => 'Modifier l\'entrée Fichiers Zone',
>>>>>>>>>>>>>>>>>>>> +'rpz zf imported' => '(importé de rpz-config)',
>>>>>>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Les caractères valides sont a-z, A-Z, 0-9 et soulignement.',
>>>>>>>>>>>>>>>>>>>> +'rpz zf' => 'Fichiers Zone',
>>>>>>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.it.pl b/config/rpz/rpz.it.pl
>>>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>>>> index 000000000..ee81605c9
>>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.it.pl
>>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>>>>>>> +'rpz apply' => 'Applica',
>>>>>>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Abilita la Whitelist personalizzata:',
>>>>>>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Domini consentiti (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>>>>> +'rpz cl allow' => 'Whitelist personalizzata',
>>>>>>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Abilita la Blacklist personalizzata:',
>>>>>>>>>>>>>>>>>>>> +'rpz cl block info' => 'Domini bloccati (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>>>>> +'rpz cl block' => 'Blacklist personalizzata',
>>>>>>>>>>>>>>>>>>>> +'rpz cl' => 'Liste personalizzate',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'il NOME non è valido',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha trovato una configurazione non valida. Dal Terminale esegui il comando unbound-checkconf per maggiori informazioni',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'l\'elenco consentiti/bloccati è vuoto',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'duplicato - NAME esiste di già',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'l\'URL non è valido',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'non è possibile rimuovere il NOME non esiste',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'il NOME non è valido - solo "consenti" o "blocca"',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'parametro mancante o non corretto',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'ricaricamento del controllo non associato non riuscito',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'la Whitelist/Blacklist personalizzata contiene voci non valide',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'l"OSSERVAZIONE non è valida',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'voce non valida nella Whitelist, riga ',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'voce non valida nella Blacklist, riga ',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'La voce selezionata non esiste: ',
>>>>>>>>>>>>>>>>>>>> +'rpz zf editor' => 'Modifica la voce dei file di zona',
>>>>>>>>>>>>>>>>>>>> +'rpz zf imported' => '(importato da rpz-config)',
>>>>>>>>>>>>>>>>>>>> +'rpz zf remark info' => 'I caratteri validi sono a-z, A-Z, 0-9 e trattino basso',
>>>>>>>>>>>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.tr.pl b/config/rpz/rpz.tr.pl
>>>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>>>> index 000000000..00226e192
>>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.tr.pl
>>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>>>>>>> +# I�in eklendi Ayriyeten Response Policy Zone (RPZ)
>>>>>>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>>>>>>> +'rpz apply' => 'Uygulamak',
>>>>>>>>>>>>>>>>>>>> +'rpz cl allow enable' => '�zel Etkinlestir allowlist:',
>>>>>>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Izin verilmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>>>>> +'rpz cl allow' => 'Etkinlestir allowlist',
>>>>>>>>>>>>>>>>>>>> +'rpz cl block enable' => '�zel Etkinlestir blocklist:',
>>>>>>>>>>>>>>>>>>>> +'rpz cl block info' => 'Engellenmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>>>>> +'rpz cl block' => 'Engellenmis blocklist',
>>>>>>>>>>>>>>>>>>>> +'rpz cl' => 'Engellenmis lists',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'NAME ge�erli degil',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ge�ersiz yapilandirma bulundu. Terminalde daha fazla bilgi i�in Unbound-checkConf komutunu �alistirin',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'allow/block liste bos',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'kopyalamak - NAME zaten var',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'URL ge�erli degil',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 106' => '�ikarilamiyor NAME yok',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'NAME ge�erli degil - "allow" veya "block" yalniz',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'Parametre eksik veya yanlis',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control basarisiz',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'Engellenmis Allowlist/Blocklist ge�ersiz girisler i�erir',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'REMARK ge�erli degil',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'Ge�ersiz giris allowlist, line ',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'Ge�ersiz giris blocklist, line ',
>>>>>>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'Se�ilen giris yok: ',
>>>>>>>>>>>>>>>>>>>> +'rpz zf editor' => 'yazimlamak zonefiles giris',
>>>>>>>>>>>>>>>>>>>> +'rpz zf imported' => '(ithal edildi rpz-config)',
>>>>>>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Ge�erli karakterler a-z, A-Z, 0-9ve alt�st.',
>>>>>>>>>>>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>>>>>>> diff --git a/html/cgi-bin/rpz.cgi b/html/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>>>> index 000000000..a821c92ac
>>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>>> +++ b/html/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,923 @@
>>>>>>>>>>>>>>>>>>>> +#!/usr/bin/perl
>>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>>>>> +# Copyright (C) 2005-2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +use strict;
>>>>>>>>>>>>>>>>>>>> +use Scalar::Util qw(looks_like_number);
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# debugging
>>>>>>>>>>>>>>>>>>>> +#use warnings;
>>>>>>>>>>>>>>>>>>>> +#use CGI::Carp 'fatalsToBrowser';
>>>>>>>>>>>>>>>>>>>> +#use Data::Dumper;
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +require '/var/ipfire/general-functions.pl';
>>>>>>>>>>>>>>>>>>>> +require "${General::swroot}/lang.pl";
>>>>>>>>>>>>>>>>>>>> +require "${General::swroot}/header.pl";
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +###--- Extra HTML ---###
>>>>>>>>>>>>>>>>>>>> +my $extraHead = <<END
>>>>>>>>>>>>>>>>>>>> +<style>
>>>>>>>>>>>>>>>>>>>> + /* alternating row background */
>>>>>>>>>>>>>>>>>>>> + .tbl tr:nth-child(2n+2) {
>>>>>>>>>>>>>>>>>>>> + background-color: var(--color-light-grey);
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> + .tbl tr:nth-child(2n+3) {
>>>>>>>>>>>>>>>>>>>> + background-color: var(--color-grey);
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> + /* text styles */
>>>>>>>>>>>>>>>>>>>> + .tbl th:not(:last-child) {
>>>>>>>>>>>>>>>>>>>> + text-align: left;
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> + div.right {
>>>>>>>>>>>>>>>>>>>> + text-align: right;
>>>>>>>>>>>>>>>>>>>> + margin-top: 0.5em;
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> + /* customlist input */
>>>>>>>>>>>>>>>>>>>> + textarea.domainlist {
>>>>>>>>>>>>>>>>>>>> + margin: 0.5em 0;
>>>>>>>>>>>>>>>>>>>> + resize: vertical;
>>>>>>>>>>>>>>>>>>>> + min-height: 10em;
>>>>>>>>>>>>>>>>>>>> + overflow: auto;
>>>>>>>>>>>>>>>>>>>> + white-space: pre;
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> + button[type=submit]:disabled {
>>>>>>>>>>>>>>>>>>>> + opacity: 0.6;
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> +</style>
>>>>>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>>>>> +###--- End of extra HTML ---###
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +### Settings ###
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# Request DNS service reload after configuration change
>>>>>>>>>>>>>>>>>>>> +my $RPZ_RELOAD_FLAG = "${General::swroot}/dns/rpz/reload.flag";
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# Configuration file for all available zonefiles
>>>>>>>>>>>>>>>>>>>> +# Format: index, name (unique), enabled (on/off), URL, remark
>>>>>>>>>>>>>>>>>>>> +my $ZONEFILES_CONF = "${General::swroot}/dns/rpz/zonefiles.conf";
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# Configuration file for custom lists
>>>>>>>>>>>>>>>>>>>> +# IDs: 0=allowlist, 1=blocklist, 2=options (allow/block enabled)
>>>>>>>>>>>>>>>>>>>> +my $CUSTOMLISTS_CONF = "${General::swroot}/dns/rpz/customlists.conf";
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# Export custom lists to rpz-config
>>>>>>>>>>>>>>>>>>>> +my $RPZ_ALLOWLIST = "${General::swroot}/dns/rpz/allowlist";
>>>>>>>>>>>>>>>>>>>> +my $RPZ_BLOCKLIST = "${General::swroot}/dns/rpz/blocklist";
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +### Preparation ###
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# Create missing config files
>>>>>>>>>>>>>>>>>>>> +unless(-f $ZONEFILES_CONF) { &General::system('touch', "$ZONEFILES_CONF"); }
>>>>>>>>>>>>>>>>>>>> +unless(-f $CUSTOMLISTS_CONF) { &General::system('touch', "$CUSTOMLISTS_CONF"); }
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +## Global gui data
>>>>>>>>>>>>>>>>>>>> +my $errormessage = "";
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +## Global configuration data
>>>>>>>>>>>>>>>>>>>> +my %zonefiles = ();
>>>>>>>>>>>>>>>>>>>> +my %customlists = ();
>>>>>>>>>>>>>>>>>>>> +&_zonefiles_load();
>>>>>>>>>>>>>>>>>>>> +&_customlists_load();
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +## Global CGI form data
>>>>>>>>>>>>>>>>>>>> +my %cgiparams = ();
>>>>>>>>>>>>>>>>>>>> +&Header::getcgihash(\%cgiparams);
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +my $action = $cgiparams{'ACTION'} // 'NONE';
>>>>>>>>>>>>>>>>>>>> +my $action_key = $cgiparams{'KEY'} // ''; # entry being edited, empty = none/new
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +###--- Process form actions ---###
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# Zonefiles action: Check whether the requested entry exists
>>>>>>>>>>>>>>>>>>>> +if((substr($action, 0, 3) eq 'ZF_') && ($action_key)) {
>>>>>>>>>>>>>>>>>>>> + unless(defined $zonefiles{$action_key}) {
>>>>>>>>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr(204, $action_key);
>>>>>>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +## Perform actions
>>>>>>>>>>>>>>>>>>>> +if($action eq 'ZF_SAVE') { ## Save new or modified zonefiles entry
>>>>>>>>>>>>>>>>>>>> + if(&_action_zf_save()) {
>>>>>>>>>>>>>>>>>>>> + $action = 'NONE'; # success, return to main page
>>>>>>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>>>>>> + $action = 'ZF_EDIT'; # error occured, keep editing
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +} elsif($action eq 'ZF_TOGGLE') { ## Toggle on/off
>>>>>>>>>>>>>>>>>>>> + if(&_action_zf_toggle()) {
>>>>>>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +} elsif($action eq 'ZF_REMOVE') { ## Remove entry
>>>>>>>>>>>>>>>>>>>> + if(&_action_zf_remove()) {
>>>>>>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +} elsif($action eq 'CL_SAVE') { ## Save custom lists
>>>>>>>>>>>>>>>>>>>> + if(&_action_cl_save()) {
>>>>>>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +} elsif($action eq 'RPZ_RELOAD') { ## Reload dns configuration
>>>>>>>>>>>>>>>>>>>> + if(&_action_rpz_reload()) {
>>>>>>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +} elsif($action eq 'UNB_RESTART') { ## Restart unbound service
>>>>>>>>>>>>>>>>>>>> + if(&_action_unb_restart()) {
>>>>>>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +###--- Start GUI ---###
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +## Start http output
>>>>>>>>>>>>>>>>>>>> +&Header::showhttpheaders();
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# Start HTML
>>>>>>>>>>>>>>>>>>>> +&Header::openpage($Lang::tr{'rpz'}, 1, $extraHead);
>>>>>>>>>>>>>>>>>>>> +&Header::openbigbox('100%', 'left', '');
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# Show error messages
>>>>>>>>>>>>>>>>>>>> +if($errormessage) {
>>>>>>>>>>>>>>>>>>>> + &_print_message($errormessage);
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# Handle zonefile add/edit mode
>>>>>>>>>>>>>>>>>>>> +if($action eq "ZF_EDIT") {
>>>>>>>>>>>>>>>>>>>> + &_print_zonefile_editor();
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Finalize page and exit cleanly
>>>>>>>>>>>>>>>>>>>> + &Header::closebigbox();
>>>>>>>>>>>>>>>>>>>> + &Header::closepage();
>>>>>>>>>>>>>>>>>>>> + exit(0);
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# Show gui elements
>>>>>>>>>>>>>>>>>>>> +&_print_zonefiles();
>>>>>>>>>>>>>>>>>>>> +&_print_customlists();
>>>>>>>>>>>>>>>>>>>> +&_print_gui_extras();
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +&Header::closebigbox();
>>>>>>>>>>>>>>>>>>>> +&Header::closepage();
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +###--- End of GUI ---###
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +###--- Internal configuration file functions ---###
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# Load all available zonefiles from rpz-config and the internal configuration
>>>>>>>>>>>>>>>>>>>> +sub _zonefiles_load {
>>>>>>>>>>>>>>>>>>>> + # Clean start
>>>>>>>>>>>>>>>>>>>> + %zonefiles = ();
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Source 1: Get the currently enabled zonefiles from rpz-config (expected format [name]=[URL])
>>>>>>>>>>>>>>>>>>>> + my @enabled_files = &General::system_output('/usr/sbin/rpz-config', 'list');
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + foreach my $row (@enabled_files) {
>>>>>>>>>>>>>>>>>>>> + chomp($row);
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Use regex instead of split() to skip non-matching lines
>>>>>>>>>>>>>>>>>>>> + next unless($row =~ /^(\w+)=(.+)$/);
>>>>>>>>>>>>>>>>>>>> + my ($name, $url) = ($1, $2);
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Unique names are already guaranteed by rpz-config
>>>>>>>>>>>>>>>>>>>> + if(&_rpz_validate_zonefile($name, $url, '', 0) == 0) {
>>>>>>>>>>>>>>>>>>>> + # Populate global data hash, mark all found entries as enabled
>>>>>>>>>>>>>>>>>>>> + my %entry = ('enabled' => 'on',
>>>>>>>>>>>>>>>>>>>> + 'url' => $url,
>>>>>>>>>>>>>>>>>>>> + 'remark' => $Lang::tr{'rpz zf imported'});
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Source 2: Get additional data and disabled entries from configuration file
>>>>>>>>>>>>>>>>>>>> + my %configured_files = ();
>>>>>>>>>>>>>>>>>>>> + &General::readhasharray($ZONEFILES_CONF, \%configured_files);
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + foreach my $row (values (%configured_files)) {
>>>>>>>>>>>>>>>>>>>> + my ($name, $enabled, $url, $remark) = @$row;
>>>>>>>>>>>>>>>>>>>> + $remark //= "";
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + next unless($name);
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Check whether this row belongs to an entry already imported from rpz-config
>>>>>>>>>>>>>>>>>>>> + if(defined $zonefiles{$name}) {
>>>>>>>>>>>>>>>>>>>> + # Existing entry, only merge additional data
>>>>>>>>>>>>>>>>>>>> + $zonefiles{$name}{'remark'} = $remark;
>>>>>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>>>>>> + # Skip entry if it is marked as enabled but not found by rpz-config. It was then deleted manually
>>>>>>>>>>>>>>>>>>>> + if($enabled ne 'on') {
>>>>>>>>>>>>>>>>>>>> + # Populate global data hash
>>>>>>>>>>>>>>>>>>>> + my %entry = ('enabled' => 'off',
>>>>>>>>>>>>>>>>>>>> + 'url' => $url // "",
>>>>>>>>>>>>>>>>>>>> + 'remark' => $remark);
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# Save internal zonefiles configuration
>>>>>>>>>>>>>>>>>>>> +sub _zonefiles_save_conf {
>>>>>>>>>>>>>>>>>>>> + my $index = 0;
>>>>>>>>>>>>>>>>>>>> + my %export = ();
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Loop trough all zonefiles and create "hasharray" type export
>>>>>>>>>>>>>>>>>>>> + foreach my $name (keys %zonefiles) {
>>>>>>>>>>>>>>>>>>>> + my @entry = ($name,
>>>>>>>>>>>>>>>>>>>> + $zonefiles{$name}{'enabled'},
>>>>>>>>>>>>>>>>>>>> + $zonefiles{$name}{'url'},
>>>>>>>>>>>>>>>>>>>> + $zonefiles{$name}{'remark'});
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + $export{$index++} = \@entry;
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + &General::writehasharray($ZONEFILES_CONF, \%export);
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# Load custom lists from rpz-config and the internal configuration
>>>>>>>>>>>>>>>>>>>> +sub _customlists_load {
>>>>>>>>>>>>>>>>>>>> + # Clean start
>>>>>>>>>>>>>>>>>>>> + %customlists = ();
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Load configuration file
>>>>>>>>>>>>>>>>>>>> + my %lists_conf = ();
>>>>>>>>>>>>>>>>>>>> + &General::readhasharray($CUSTOMLISTS_CONF, \%lists_conf);
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Get list options, enabled by default to start import
>>>>>>>>>>>>>>>>>>>> + $customlists{'allow'}{'enabled'} = $lists_conf{2}[0] // 'on';
>>>>>>>>>>>>>>>>>>>> + $customlists{'block'}{'enabled'} = $lists_conf{2}[1] // 'on';
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Import enabled list from rpz-config, otherwise retrieve stored or empty list from configuration file
>>>>>>>>>>>>>>>>>>>> + if($customlists{'allow'}{'enabled'} eq 'on') {
>>>>>>>>>>>>>>>>>>>> + &_customlist_import('allow', $RPZ_ALLOWLIST);
>>>>>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>>>>>> + $customlists{'allow'}{'list'} = $lists_conf{0} // [];
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> + if($customlists{'block'}{'enabled'} eq 'on') {
>>>>>>>>>>>>>>>>>>>> + &_customlist_import('block', $RPZ_BLOCKLIST);
>>>>>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>>>>>> + $customlists{'block'}{'list'} = $lists_conf{1} // [];
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# Save internal custom lists configuration
>>>>>>>>>>>>>>>>>>>> +sub _customlists_save_conf {
>>>>>>>>>>>>>>>>>>>> + my %export = ();
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Match IDs with import function
>>>>>>>>>>>>>>>>>>>> + $export{0} = $customlists{'allow'}{'list'};
>>>>>>>>>>>>>>>>>>>> + $export{1} = $customlists{'block'}{'list'};
>>>>>>>>>>>>>>>>>>>> + $export{2} = [$customlists{'allow'}{'enabled'}, $customlists{'block'}{'enabled'}];
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + &General::writehasharray($CUSTOMLISTS_CONF, \%export);
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# Import a custom list from plain file, returns empty list if file is missing
>>>>>>>>>>>>>>>>>>>> +sub _customlist_import {
>>>>>>>>>>>>>>>>>>>> + my ($listname, $filename) = @_;
>>>>>>>>>>>>>>>>>>>> + my @list = ();
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # File exists, load and check all lines
>>>>>>>>>>>>>>>>>>>> + if(-f $filename) {
>>>>>>>>>>>>>>>>>>>> + open(my $FH, '<', $filename) or die "Can't read $filename: $!";
>>>>>>>>>>>>>>>>>>>> + while(my $line = <$FH>) {
>>>>>>>>>>>>>>>>>>>> + chomp($line);
>>>>>>>>>>>>>>>>>>>> + push(@list, $line);
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> + close($FH);
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Clean up imported data
>>>>>>>>>>>>>>>>>>>> + &_rpz_validate_customlist(\@list, 1);
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + $customlists{$listname}{'list'} = \@list;
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# Export a custom list to plain file or clear file if list is disabled
>>>>>>>>>>>>>>>>>>>> +sub _customlist_export {
>>>>>>>>>>>>>>>>>>>> + my ($listname, $filename) = @_;
>>>>>>>>>>>>>>>>>>>> + return unless(defined $customlists{$listname});
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Write enabled domain list to file, otherwise save empty file
>>>>>>>>>>>>>>>>>>>> + open(my $FH, '>', $filename) or die "Can't write $filename: $!";
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + if($customlists{$listname}{'enabled'} eq 'on') {
>>>>>>>>>>>>>>>>>>>> + foreach my $line (@{$customlists{$listname}{'list'}}) {
>>>>>>>>>>>>>>>>>>>> + print $FH "$line\n";
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>>>>>> + print $FH "; Note: This list is currently disabled by $ENV{'SCRIPT_NAME'}\n";
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + close($FH);
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +###--- Internal gui functions ---###
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# Show simple message box
>>>>>>>>>>>>>>>>>>>> +sub _print_message {
>>>>>>>>>>>>>>>>>>>> + my ($message, $title) = @_;
>>>>>>>>>>>>>>>>>>>> + $title ||= $Lang::tr{'error messages'};
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $title);
>>>>>>>>>>>>>>>>>>>> + print "<span>$message</span>";
>>>>>>>>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# Show all zone files and related gui elements
>>>>>>>>>>>>>>>>>>>> +sub _print_zonefiles {
>>>>>>>>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf'});
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>>>>>>> +<table class="tbl" width="100%">
>>>>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>>>>> + <th>$Lang::tr{'name'}</th>
>>>>>>>>>>>>>>>>>>>> + <th>URL</th>
>>>>>>>>>>>>>>>>>>>> + <th>$Lang::tr{'remark'}</th>
>>>>>>>>>>>>>>>>>>>> + <th colspan="3">$Lang::tr{'action'}</th>
>>>>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Sort zonefiles by name and loop trough all entries
>>>>>>>>>>>>>>>>>>>> + foreach my $name (sort keys %zonefiles) {
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Toggle button label translation
>>>>>>>>>>>>>>>>>>>> + my $toggle_tr = ($zonefiles{$name}{'enabled'} eq 'on') ? $Lang::tr{'click to disable'} : $Lang::tr{'click to enable'};
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>>>>> + <td>$name</td>
>>>>>>>>>>>>>>>>>>>> + <td>$zonefiles{$name}{'url'}</td>
>>>>>>>>>>>>>>>>>>>> + <td>$zonefiles{$name}{'remark'}</td>
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>>>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_TOGGLE">
>>>>>>>>>>>>>>>>>>>> + <input type="image" src="/images/$zonefiles{$name}{'enabled'}.gif" title="$toggle_tr" alt="$toggle_tr">
>>>>>>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>>>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_EDIT">
>>>>>>>>>>>>>>>>>>>> + <input type="image" src="/images/edit.gif" title="$Lang::tr{'edit'}" alt="$Lang::tr{'edit'}">
>>>>>>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>>>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_REMOVE">
>>>>>>>>>>>>>>>>>>>> + <input type="image" src="/images/delete.gif" title="$Lang::tr{'remove'}" alt="$Lang::tr{'remove'}">
>>>>>>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Disable reload button if not needed
>>>>>>>>>>>>>>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>>>>>>> +</table>
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +<div class="right">
>>>>>>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="">
>>>>>>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="ZF_EDIT">$Lang::tr{'add'}</button>
>>>>>>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>>>>>>> +</div>
>>>>>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# Show zonefiles entry editor
>>>>>>>>>>>>>>>>>>>> +sub _print_zonefile_editor {
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Key specified: Edit existing entry
>>>>>>>>>>>>>>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>>>>>>>>>>>>>>> + # Load data to be edited, but don't override already present values (allows user to edit after error)
>>>>>>>>>>>>>>>>>>>> + $cgiparams{'ZF_NAME'} //= $action_key;
>>>>>>>>>>>>>>>>>>>> + $cgiparams{'ZF_URL'} //= $zonefiles{$action_key}{'url'};
>>>>>>>>>>>>>>>>>>>> + $cgiparams{'ZF_REMARK'} //= $zonefiles{$action_key}{'remark'};
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Fallback to empty form
>>>>>>>>>>>>>>>>>>>> + $cgiparams{'ZF_NAME'} //= "";
>>>>>>>>>>>>>>>>>>>> + $cgiparams{'ZF_URL'} //= "";
>>>>>>>>>>>>>>>>>>>> + $cgiparams{'ZF_REMARK'} //= "";
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf editor'});
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>>>>>> +<input type="hidden" name="KEY" value="$action_key">
>>>>>>>>>>>>>>>>>>>> +<table width="100%">
>>>>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>>>>> + <td width="20%">$Lang::tr{'name'}: <img src="/blob.gif" alt="*"></td>
>>>>>>>>>>>>>>>>>>>> + <td><input type="text" name="ZF_NAME" value="$cgiparams{'ZF_NAME'}" size="40" maxlength="32" title="$Lang::tr{'rpz zf remark info'}" pattern="[a-zA-Z0-9_]{1,32}" required></td>
>>>>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>>>>> + <td width="20%">URL: <img src="/blob.gif" alt="*"></td>
>>>>>>>>>>>>>>>>>>>> + <td><input type="url" name="ZF_URL" value="$cgiparams{'ZF_URL'}" size="40" maxlength="128" required></td>
>>>>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>>>>> + <td width="20%">$Lang::tr{'remark'}:</td>
>>>>>>>>>>>>>>>>>>>> + <td><input type="text" name="ZF_REMARK" value="$cgiparams{'ZF_REMARK'}" size="40" maxlength="32"></td>
>>>>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>>>>> + <td colspan="2"><hr></td>
>>>>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>>>>> + <td width="55%"><img src="/blob.gif" alt="*"> $Lang::tr{'required field'}</td>
>>>>>>>>>>>>>>>>>>>> + <td align="right"><button type="submit" name="ACTION" value="ZF_SAVE">$Lang::tr{'save'}</button></td>
>>>>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>>>>> +</table>
>>>>>>>>>>>>>>>>>>>> +</form>
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +<div class="right">
>>>>>>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="NONE">$Lang::tr{'back'}</button>
>>>>>>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>>>>>>> +</div>
>>>>>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# Show custom allow/block files and related gui elements
>>>>>>>>>>>>>>>>>>>> +sub _print_customlists {
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Load lists from config, unless they are currently being edited
>>>>>>>>>>>>>>>>>>>> + if($action ne 'CL_SAVE') {
>>>>>>>>>>>>>>>>>>>> + $cgiparams{'ALLOW_LIST'} = join("\n", @{$customlists{'allow'}{'list'}});
>>>>>>>>>>>>>>>>>>>> + $cgiparams{'BLOCK_LIST'} = join("\n", @{$customlists{'block'}{'list'}});
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + $cgiparams{'ALLOW_ENABLED'} = ($customlists{'allow'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>>>>>>>>>>>>>>> + $cgiparams{'BLOCK_ENABLED'} = ($customlists{'block'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Fallback to empty form
>>>>>>>>>>>>>>>>>>>> + $cgiparams{'ALLOW_LIST'} //= "";
>>>>>>>>>>>>>>>>>>>> + $cgiparams{'BLOCK_LIST'} //= "";
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # HTML checkboxes, unchecked = no or undef value in POST data
>>>>>>>>>>>>>>>>>>>> + my %checked = ();
>>>>>>>>>>>>>>>>>>>> + $checked{'ALLOW_ENABLED'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? " checked" : "";
>>>>>>>>>>>>>>>>>>>> + $checked{'BLOCK_ENABLED'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? " checked" : "";
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Disable reload button if not needed
>>>>>>>>>>>>>>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz cl'});
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>>>>>> +<table width="100%">
>>>>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl allow'}</b><br>$Lang::tr{'rpz cl allow info'}</td>
>>>>>>>>>>>>>>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl block'}</b><br>$Lang::tr{'rpz cl block info'}</td>
>>>>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>>>>> + <td colspan="2"><textarea name="ALLOW_LIST" class="domainlist" cols="45">
>>>>>>>>>>>>>>>>>>>> +$cgiparams{'ALLOW_LIST'}</textarea></td>
>>>>>>>>>>>>>>>>>>>> + <td colspan="2"><textarea name="BLOCK_LIST" class="domainlist" cols="45">
>>>>>>>>>>>>>>>>>>>> +$cgiparams{'BLOCK_LIST'}</textarea></td>
>>>>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>>>>> + <td><label for="allow_enabled">$Lang::tr{'rpz cl allow enable'}</label></td>
>>>>>>>>>>>>>>>>>>>> + <td width="15%"><input type="checkbox" name="ALLOW_ENABLED" id="allow_enabled"$checked{'ALLOW_ENABLED'}></td>
>>>>>>>>>>>>>>>>>>>> + <td><label for="block_enabled">$Lang::tr{'rpz cl block enable'}</label></td>
>>>>>>>>>>>>>>>>>>>> + <td width="15%"><input type="checkbox" name="BLOCK_ENABLED" id="block_enabled"$checked{'BLOCK_ENABLED'}></td>
>>>>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>>>>> + <td colspan="4"><hr></td>
>>>>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>>>>> + <td align="right" colspan="4">
>>>>>>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="CL_SAVE">$Lang::tr{'save'}</button>
>>>>>>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>>>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>>>>>>>>> +</table>
>>>>>>>>>>>>>>>>>>>> +</form>
>>>>>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# Output javascript and extra gui elements
>>>>>>>>>>>>>>>>>>>> +sub _print_gui_extras {
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Apply/Restart button modifier key handler
>>>>>>>>>>>>>>>>>>>> + if(&_rpz_needs_reload()) {
>>>>>>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>>>>>>> +<script>
>>>>>>>>>>>>>>>>>>>> + // Commit modifier key handler
>>>>>>>>>>>>>>>>>>>> + (function(jq, document) {
>>>>>>>>>>>>>>>>>>>> + var keyEventsOn = false; // Keyboard events attached
>>>>>>>>>>>>>>>>>>>> + var keyModify = false; // Modifier key pressed
>>>>>>>>>>>>>>>>>>>> + var mouseHover = false; // Mouse over commit button
>>>>>>>>>>>>>>>>>>>> + var btnModified = false; // Button modified to "Restart"
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + // Document-level key events, enable only while cursor is over button
>>>>>>>>>>>>>>>>>>>> + function attachKeyEvents() {
>>>>>>>>>>>>>>>>>>>> + if(keyEventsOn) {
>>>>>>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> + keyEventsOn = true;
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + jq(document).on("keydown.rpz", function(event) {
>>>>>>>>>>>>>>>>>>>> + if((!keyModify) && event.shiftKey) {
>>>>>>>>>>>>>>>>>>>> + keyModify = true;
>>>>>>>>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>>>>>>> + jq(document).on("keyup.rpz", function(event) {
>>>>>>>>>>>>>>>>>>>> + if(keyModify && (!event.shiftKey)) {
>>>>>>>>>>>>>>>>>>>> + keyModify = false;
>>>>>>>>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> + function removeKeyEvents() {
>>>>>>>>>>>>>>>>>>>> + keyModify = false;
>>>>>>>>>>>>>>>>>>>> + if(keyEventsOn) {
>>>>>>>>>>>>>>>>>>>> + jq(document).off("keydown.rpz keyup.rpz");
>>>>>>>>>>>>>>>>>>>> + keyEventsOn = false;
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + // Attach mouse hover events to commit buttons
>>>>>>>>>>>>>>>>>>>> + function attachMouseEvents() {
>>>>>>>>>>>>>>>>>>>> + jq("button.commit").on("mouseenter", function(event) {
>>>>>>>>>>>>>>>>>>>> + if(!mouseHover) {
>>>>>>>>>>>>>>>>>>>> + mouseHover = true;
>>>>>>>>>>>>>>>>>>>> + attachKeyEvents();
>>>>>>>>>>>>>>>>>>>> + // Handle already pressed key
>>>>>>>>>>>>>>>>>>>> + keyModify = !!(event.shiftKey);
>>>>>>>>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + // Cursor moved away: Disable key listener to minimize events
>>>>>>>>>>>>>>>>>>>> + jq("button.commit").on("mouseleave", function() {
>>>>>>>>>>>>>>>>>>>> + if(mouseHover) {
>>>>>>>>>>>>>>>>>>>> + mouseHover = false;
>>>>>>>>>>>>>>>>>>>> + removeKeyEvents();
>>>>>>>>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + // Modify commit button
>>>>>>>>>>>>>>>>>>>> + function handleModify() {
>>>>>>>>>>>>>>>>>>>> + let modify = mouseHover && keyModify;
>>>>>>>>>>>>>>>>>>>> + if(btnModified != modify) {
>>>>>>>>>>>>>>>>>>>> + if(modify) {
>>>>>>>>>>>>>>>>>>>> + jq("button.commit").text("$Lang::tr{'restart'}").val("UNB_RESTART");
>>>>>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>>>>>> + jq("button.commit").text("$Lang::tr{'rpz apply'}").val("RPZ_RELOAD");
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> + btnModified = modify;
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + // jQuery DOM ready
>>>>>>>>>>>>>>>>>>>> + jq(function() {
>>>>>>>>>>>>>>>>>>>> + attachMouseEvents();
>>>>>>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>>>>>>> + })(jQuery, document);
>>>>>>>>>>>>>>>>>>>> +</script>
>>>>>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>>>>> + } # End of modifier key handler
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +###--- Internal action processing functions ---###
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# Toggle zonefile on/off
>>>>>>>>>>>>>>>>>>>> +sub _action_zf_toggle {
>>>>>>>>>>>>>>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + my $result = 0;
>>>>>>>>>>>>>>>>>>>> + my $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Perform toggle action
>>>>>>>>>>>>>>>>>>>> + if($enabled eq 'on') {
>>>>>>>>>>>>>>>>>>>> + $enabled = 'off';
>>>>>>>>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>>>>>> + $enabled = 'on';
>>>>>>>>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $action_key, $zonefiles{$action_key}{'url'}, '--no-reload');
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Save changes
>>>>>>>>>>>>>>>>>>>> + $zonefiles{$action_key}{'enabled'} = $enabled;
>>>>>>>>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# Remove zonefile
>>>>>>>>>>>>>>>>>>>> +sub _action_zf_remove {
>>>>>>>>>>>>>>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Remove from rpz-config if currently active
>>>>>>>>>>>>>>>>>>>> + if($zonefiles{$action_key}{'enabled'} eq 'on') {
>>>>>>>>>>>>>>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Remove from data hash and save changes
>>>>>>>>>>>>>>>>>>>> + delete $zonefiles{$action_key};
>>>>>>>>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Clear action_key, as the entry is now removed entirely
>>>>>>>>>>>>>>>>>>>> + $action_key = "";
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# Create or update zonefile entry
>>>>>>>>>>>>>>>>>>>> +# Returns undef if gui needs to stay in editor mode
>>>>>>>>>>>>>>>>>>>> +sub _action_zf_save {
>>>>>>>>>>>>>>>>>>>> + my $result = 0;
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + my $name = $cgiparams{'ZF_NAME'} // "";
>>>>>>>>>>>>>>>>>>>> + my $url = $cgiparams{'ZF_URL'} // "";
>>>>>>>>>>>>>>>>>>>> + my $remark = $cgiparams{'ZF_REMARK'} // "";
>>>>>>>>>>>>>>>>>>>> + my $enabled = 'on'; # Enable new entries by default
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Note on variables:
>>>>>>>>>>>>>>>>>>>> + # name = unique key, will be used to address the entry
>>>>>>>>>>>>>>>>>>>> + # action_key = name of the entry being edited, empty for new entry
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Only check for unique name if it changed
>>>>>>>>>>>>>>>>>>>> + # (this also checks new entries because the action_key is empty in this case)
>>>>>>>>>>>>>>>>>>>> + $result = &_rpz_validate_zonefile($name, $url, $remark, (lc($name) ne lc($action_key)));
>>>>>>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 0);
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Edit existing entry: Determine what was changed
>>>>>>>>>>>>>>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>>>>>>>>>>>>>>> + # Name und URL remain unchanged, only save remark and finish
>>>>>>>>>>>>>>>>>>>> + if(($name eq $action_key) && ($url eq $zonefiles{$action_key}{'url'})) {
>>>>>>>>>>>>>>>>>>>> + $zonefiles{$action_key}{'remark'} = $remark;
>>>>>>>>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Entry was changed and needs to be recreated, preserve status
>>>>>>>>>>>>>>>>>>>> + $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Remove from rpz-config
>>>>>>>>>>>>>>>>>>>> + return unless &_action_zf_remove();
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Add new entry to rpz-config
>>>>>>>>>>>>>>>>>>>> + if($enabled eq 'on') {
>>>>>>>>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $name, $url, '--no-reload');
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Add to global data hash and save changes
>>>>>>>>>>>>>>>>>>>> + my %entry = ('enabled' => $enabled,
>>>>>>>>>>>>>>>>>>>> + 'url' => $url,
>>>>>>>>>>>>>>>>>>>> + 'remark' => $remark);
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# Save custom lists
>>>>>>>>>>>>>>>>>>>> +sub _action_cl_save {
>>>>>>>>>>>>>>>>>>>> + return unless((defined $cgiparams{'ALLOW_LIST'}) && (defined $cgiparams{'BLOCK_LIST'}));
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + my $result = 0;
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + my @allowlist = split(/\R/, $cgiparams{'ALLOW_LIST'});
>>>>>>>>>>>>>>>>>>>> + my @blocklist = split(/\R/, $cgiparams{'BLOCK_LIST'});
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Validate lists
>>>>>>>>>>>>>>>>>>>> + $result = &_rpz_validate_customlist(\@allowlist);
>>>>>>>>>>>>>>>>>>>> + if($result != 0) {
>>>>>>>>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr(202, $result);
>>>>>>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> + $result = &_rpz_validate_customlist(\@blocklist);
>>>>>>>>>>>>>>>>>>>> + if($result != 0) {
>>>>>>>>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr(203, $result);
>>>>>>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Add to global data hash and save changes
>>>>>>>>>>>>>>>>>>>> + $customlists{'allow'}{'list'} = \@allowlist;
>>>>>>>>>>>>>>>>>>>> + $customlists{'block'}{'list'} = \@blocklist;
>>>>>>>>>>>>>>>>>>>> + $customlists{'allow'}{'enabled'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? 'on' : 'off';
>>>>>>>>>>>>>>>>>>>> + $customlists{'block'}{'enabled'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? 'on' : 'off';
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + &_customlists_save_conf();
>>>>>>>>>>>>>>>>>>>> + &_customlist_export('allow', $RPZ_ALLOWLIST);
>>>>>>>>>>>>>>>>>>>> + &_customlist_export('block', $RPZ_BLOCKLIST);
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Make new lists, request service reload on success
>>>>>>>>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-make', 'allowblock', '--no-reload');
>>>>>>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# Trigger rpz-config reload
>>>>>>>>>>>>>>>>>>>> +sub _action_rpz_reload {
>>>>>>>>>>>>>>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Immediately clear flag to prevent multiple reloads
>>>>>>>>>>>>>>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>>>>>>>>>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Perform reload, recreate reload flag on error to enable retry
>>>>>>>>>>>>>>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'reload');
>>>>>>>>>>>>>>>>>>>> + if(not &_rpz_check_result($result, 0)) {
>>>>>>>>>>>>>>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# Trigger unbound restart
>>>>>>>>>>>>>>>>>>>> +sub _action_unb_restart {
>>>>>>>>>>>>>>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Immediately clear flag to prevent multiple restarts
>>>>>>>>>>>>>>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>>>>>>>>>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Perform restart, unboundctrl always exits zero
>>>>>>>>>>>>>>>>>>>> + &General::system('/usr/local/bin/unboundctrl', 'restart');
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +###--- Internal rpz-config functions ---###
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# Translate rpz-config exitcodes and messages
>>>>>>>>>>>>>>>>>>>> +# 100-199: rpz-config, 200-299: webgui
>>>>>>>>>>>>>>>>>>>> +sub _rpz_error_tr {
>>>>>>>>>>>>>>>>>>>> + my ($error, $append) = @_;
>>>>>>>>>>>>>>>>>>>> + $append //= '';
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Translate numeric exit codes
>>>>>>>>>>>>>>>>>>>> + if(looks_like_number($error)) {
>>>>>>>>>>>>>>>>>>>> + if(defined $Lang::tr{"rpz exitcode $error"}) {
>>>>>>>>>>>>>>>>>>>> + $error = $Lang::tr{"rpz exitcode $error"};
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + return "RPZ $Lang::tr{'error'}: $error" . &Header::escape($append);
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# Check result of rpz-config system call, request reload on success
>>>>>>>>>>>>>>>>>>>> +sub _rpz_check_result {
>>>>>>>>>>>>>>>>>>>> + my ($result, $request_reload) = @_;
>>>>>>>>>>>>>>>>>>>> + $request_reload //= 0;
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # exitcode 0 = success
>>>>>>>>>>>>>>>>>>>> + if($result != 0) {
>>>>>>>>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr($result);
>>>>>>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Set reload flag
>>>>>>>>>>>>>>>>>>>> + if($request_reload) {
>>>>>>>>>>>>>>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# Test whether reload flag is set
>>>>>>>>>>>>>>>>>>>> +sub _rpz_needs_reload {
>>>>>>>>>>>>>>>>>>>> + return (-f $RPZ_RELOAD_FLAG);
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# Validate a zonefile entry, returns rpz-config exitcode on failure. Use _rpz_check_result to verify.
>>>>>>>>>>>>>>>>>>>> +# unique = check for unique name
>>>>>>>>>>>>>>>>>>>> +sub _rpz_validate_zonefile {
>>>>>>>>>>>>>>>>>>>> + my ($name, $url, $remark, $unique) = @_;
>>>>>>>>>>>>>>>>>>>> + $unique //= 1;
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + unless($name =~ /^[a-zA-Z0-9_]{1,32}$/) {
>>>>>>>>>>>>>>>>>>>> + return 101;
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> + unless($url =~ /^[\w+\.:;\/\\&@#%?=\-~|!]{1,128}$/) {
>>>>>>>>>>>>>>>>>>>> + return 105;
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> + unless($remark =~ /^[\w \-()\.:;*\/\\?!&=]{0,32}$/) {
>>>>>>>>>>>>>>>>>>>> + return 201;
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Check against already existing names
>>>>>>>>>>>>>>>>>>>> + if($unique) {
>>>>>>>>>>>>>>>>>>>> + foreach my $existing (keys %zonefiles) {
>>>>>>>>>>>>>>>>>>>> + if(lc($name) eq lc($existing)) {
>>>>>>>>>>>>>>>>>>>> + return 104;
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + return 0;
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# Validate a custom list, returns number of rejected line on failure. Check for non-zero results.
>>>>>>>>>>>>>>>>>>>> +# listref = array reference, cleanup = remove invalid entries instead of returning an error
>>>>>>>>>>>>>>>>>>>> +sub _rpz_validate_customlist {
>>>>>>>>>>>>>>>>>>>> + my ($listref, $cleanup) = @_;
>>>>>>>>>>>>>>>>>>>> + $cleanup //= 0;
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + foreach my $index (reverse 0..$#{$listref}) {
>>>>>>>>>>>>>>>>>>>> + my $row = @$listref[$index];
>>>>>>>>>>>>>>>>>>>> + next unless($row); # Skip/allow empty lines
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Reject/remove everything besides wildcard domains and remarks
>>>>>>>>>>>>>>>>>>>> + if((not &General::validwildcarddomainname($row)) && (not $row =~ /^;[\w \-()\.:;*\/\\?!&=]*$/)) {
>>>>>>>>>>>>>>>>>>>> + unless($cleanup) {
>>>>>>>>>>>>>>>>>>>> + # +1 for user friendly line number and to ensure non-zero exitcode
>>>>>>>>>>>>>>>>>>>> + return $index + 1;
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Remove current row
>>>>>>>>>>>>>>>>>>>> + splice(@$listref, $index, 1);
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + return 0;
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +###--- Internal misc functions ---###
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# Send HTTP 303 redirect headers for post/request/get pattern
>>>>>>>>>>>>>>>>>>>> +# (Must be sent before calling &Header::showhttpheaders())
>>>>>>>>>>>>>>>>>>>> +sub _http_prg_redirect {
>>>>>>>>>>>>>>>>>>>> + my $location = "https://$ENV{'SERVER_NAME'}:$ENV{'SERVER_PORT'}$ENV{'SCRIPT_NAME'}";
>>>>>>>>>>>>>>>>>>>> + print "Status: 303 See Other\n";
>>>>>>>>>>>>>>>>>>>> + print "Location: $location\n";
>>>>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>>>>> diff --git a/lfs/rpz b/lfs/rpz
>>>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>>>> index 000000000..7ddbc38e5
>>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>>> +++ b/lfs/rpz
>>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,96 @@
>>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>>> +# Definitions
>>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +include Config
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +SUMMARY = response policy zone - RPZ reputation system for unbound DNS
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +VER = 1.0.0
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +THISAPP = rpz-$(VER)
>>>>>>>>>>>>>>>>>>>> +DIR_APP = $(DIR_SRC)/$(THISAPP)
>>>>>>>>>>>>>>>>>>>> +TARGET = $(DIR_INFO)/$(THISAPP)
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +PROG = rpz
>>>>>>>>>>>>>>>>>>>> +PAK_VER = 18
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +DEPS =
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +SERVICES =
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>>> +# Top-level Rules
>>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +install : $(TARGET)
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +check :
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +download :
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +b2 :
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +dist:
>>>>>>>>>>>>>>>>>>>> + @$(PAK)
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>>> +# Installation Details
>>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +$(TARGET) :
>>>>>>>>>>>>>>>>>>>> + @$(PREBUILD)
>>>>>>>>>>>>>>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # RPZ scripts
>>>>>>>>>>>>>>>>>>>> + install --verbose --mode=755 \
>>>>>>>>>>>>>>>>>>>> + $(DIR_CONF)/rpz/{rpz-config,rpz-metrics,rpz-sleep,rpz-make,rpz-functions} \
>>>>>>>>>>>>>>>>>>>> + --target-directory=/usr/sbin
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # RPZ config files
>>>>>>>>>>>>>>>>>>>> + mkdir -pv /etc/unbound/local.d
>>>>>>>>>>>>>>>>>>>> + install --verbose --mode=644 --owner=nobody --group=nobody \
>>>>>>>>>>>>>>>>>>>> + $(DIR_CONF)/rpz/00-rpz.conf \
>>>>>>>>>>>>>>>>>>>> + --target-directory=/etc/unbound/local.d
>>>>>>>>>>>>>>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/local.d
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # RPZ custom list files for allow and block
>>>>>>>>>>>>>>>>>>>> + mkdir -pv /var/ipfire/dns/rpz
>>>>>>>>>>>>>>>>>>>> + touch /var/ipfire/dns/rpz/{allowlist,blocklist}
>>>>>>>>>>>>>>>>>>>> + chown --verbose --recursive nobody:nobody /var/ipfire/dns/rpz
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # RPZ zone files
>>>>>>>>>>>>>>>>>>>> + # create empty RPZ config file to avoid a unbound config error
>>>>>>>>>>>>>>>>>>>> + mkdir -pv /etc/unbound/zonefiles
>>>>>>>>>>>>>>>>>>>> + touch /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/zonefiles
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Install addon-specific language-files
>>>>>>>>>>>>>>>>>>>> + install --verbose --mode=004 $(DIR_CONF)/rpz/rpz.*.pl \
>>>>>>>>>>>>>>>>>>>> + --target-directory=/var/ipfire/addon-lang
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + # Install backup definition
>>>>>>>>>>>>>>>>>>>> + cp -vf $(DIR_CONF)/backup/includes/rpz /var/ipfire/backup/addons/includes/rpz
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>>>>>>>>>>>>>>> + @$(POSTBUILD)
>>>>>>>>>>>>>>>>>>>> diff --git a/make.sh b/make.sh
>>>>>>>>>>>>>>>>>>>> index 827ea9e77..a77535b13 100755
>>>>>>>>>>>>>>>>>>>> --- a/make.sh
>>>>>>>>>>>>>>>>>>>> +++ b/make.sh
>>>>>>>>>>>>>>>>>>>> @@ -390,7 +390,7 @@ prepareenv() {
>>>>>>>>>>>>>>>>>>>> if [ "${free_space}" -lt "${required_space}" ]; then
>>>>>>>>>>>>>>>>>>>> # Add any consumed space
>>>>>>>>>>>>>>>>>>>> while read -r consumed_space path; do
>>>>>>>>>>>>>>>>>>>> - (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>>>>>>>>>>>>>>> + (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>>>>>>>>>>>>>>> done <<< "$(du --summarize --bytes "${BUILD_DIR}" "${IMAGES_DIR}" "${LOG_DIR}" 2>/dev/null)"
>>>>>>>>>>>>>>>>>>>> fi
>>>>>>>>>>>>>>>>>>>> @@ -2087,6 +2087,7 @@ build_system() {
>>>>>>>>>>>>>>>>>>>> lfsmake2 btrfs-progs
>>>>>>>>>>>>>>>>>>>> lfsmake2 inotify-tools
>>>>>>>>>>>>>>>>>>>> lfsmake2 grub-btrfs
>>>>>>>>>>>>>>>>>>>> + lfsmake2 rpz
>>>>>>>>>>>>>>>>>>>> lfsmake2 linux
>>>>>>>>>>>>>>>>>>>> lfsmake2 rtl8812au
>>>>>>>>>>>>>>>>>>>> diff --git a/src/paks/rpz/install.sh b/src/paks/rpz/install.sh
>>>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>>>> index 000000000..ef99bf742
>>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>>> +++ b/src/paks/rpz/install.sh
>>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,36 @@
>>>>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>>> +#
>>>>>>>>>>>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>>>>>>>>>>>> +extract_files
>>>>>>>>>>>>>>>>>>>> +restore_backup ${NAME}
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# fix user created files
>>>>>>>>>>>>>>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>>>>>>>>>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>>>>>>>>>>>>>>> + /etc/unbound/zonefiles \
>>>>>>>>>>>>>>>>>>>> + /etc/unbound/local.d
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# Update Language cache
>>>>>>>>>>>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# restart unbound to load config file
>>>>>>>>>>>>>>>>>>>> +/etc/init.d/unbound restart
>>>>>>>>>>>>>>>>>>>> diff --git a/src/paks/rpz/uninstall.sh b/src/paks/rpz/uninstall.sh
>>>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>>>> index 000000000..e11427df3
>>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>>> +++ b/src/paks/rpz/uninstall.sh
>>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,38 @@
>>>>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>>> +#
>>>>>>>>>>>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>>>>>>>>>>>>>>> +/etc/init.d/unbound stop
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +make_backup ${NAME}
>>>>>>>>>>>>>>>>>>>> +remove_files
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>>>>>>>>>>>>>>> +# "[1723428668] unbound-control[17117:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>>>>>>>>>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# Update Language cache
>>>>>>>>>>>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# start unbound to load unbound config file
>>>>>>>>>>>>>>>>>>>> +/etc/init.d/unbound start
>>>>>>>>>>>>>>>>>>>> diff --git a/src/paks/rpz/update.sh b/src/paks/rpz/update.sh
>>>>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>>>>> index 000000000..9bc340bc6
>>>>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>>>>> +++ b/src/paks/rpz/update.sh
>>>>>>>>>>>>>>>>>>>> @@ -0,0 +1,52 @@
>>>>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>>>>> +#
>>>>>>>>>>>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# from update.sh
>>>>>>>>>>>>>>>>>>>> +extract_backup_includes
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>>>>>>>>>>>>>>> +/etc/init.d/unbound stop
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# from uninstall.sh
>>>>>>>>>>>>>>>>>>>> +make_backup ${NAME}
>>>>>>>>>>>>>>>>>>>> +remove_files
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>>>>>>>>>>>>>>> +# "unbound-control[nn:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>>>>>>>>>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# from install.sh
>>>>>>>>>>>>>>>>>>>> +extract_files
>>>>>>>>>>>>>>>>>>>> +restore_backup ${NAME}
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# fix user created files
>>>>>>>>>>>>>>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>>>>>>>>>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>>>>>>>>>>>>>>> + /etc/unbound/zonefiles \
>>>>>>>>>>>>>>>>>>>> + /etc/unbound/local.d
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# Update Language cache
>>>>>>>>>>>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>>>>> +# restart unbound to load config files
>>>>>>>>>>>>>>>>>>>> +/etc/init.d/unbound start
>>>>>>>>>>>>>>>>>>>> --
>>>>>>>>>>>>>>>>>>>> 2.39.5
>>>>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>> Jon
>>>>>>>>>>>>>>>>>> --
>>>>>>>>>>>>>>>>>> Jon Murphy
>>>>>>>>>>>>>>>>>> jon.murphy@ipfire.org
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> Jon
>>>>>>>>>>>>>>>> --
>>>>>>>>>>>>>>>> Jon Murphy
>>>>>>>>>>>>>>>> jon.murphy@ipfire.org
>>>>
>>>>
>>>>
>>
>>
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
2025-03-24 14:36 ` Michael Tremer
2025-03-24 14:38 ` Re[2]: " Jon Murphy
@ 2025-03-24 14:49 ` Bernhard Bitsch
1 sibling, 0 replies; 30+ messages in thread
From: Bernhard Bitsch @ 2025-03-24 14:49 UTC (permalink / raw)
To: Michael Tremer, Jon Murphy; +Cc: IPFire: Development-List
Am 24.03.2025 um 15:36 schrieb Michael Tremer:
> Unbound did not put those there...
>
Sorry, but it does this really. You can check this by defining
rpz:
name: doh2.rpz
zonefile: /etc/unbound/zonefiles/doh2.rpz
url:
https://gitlab.com/hagezi/mirror/-/raw/main/dns-blocklists/rpz/doh.txt
rpz-action-override: nxdomain
rpz-log: yes
rpz-log-name: doh2
rpz-signal-nxdomain-ra: yes
This is little file, not much load traffic. ;)
>> On 24 Mar 2025, at 14:33, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>
>>
>>
>> And where are these stored?
>>
>> In `/etc/unbound/zonefiles`:
>>
>>
>> [root@ipfire ~] # ls -al /etc/unbound/zonefiles
>> total 20664
>> drwxr-xr-x 2 nobody nobody 4096 Mar 24 04:40 .
>> drwxr-xr-x 4 root root 4096 Mar 19 16:24 ..
>> -rw-r--r-- 1 nobody nobody 3999087 Mar 23 15:11 adhocSB.rpz
>> -rw-r--r-- 1 nobody nobody 1411 Mar 23 14:23 allow.rpz
>> -rw-r--r-- 1 nobody nobody 25355 Mar 24 04:40 AmazonTrkrHZ.rpz
>> -rw-r--r-- 1 nobody nobody 7241 Mar 24 04:40 AppleTrkrHZ.rpz
>> -rw-r--r-- 1 nobody nobody 178 Mar 23 14:23 block.rpz
>> -rw-r--r-- 1 nobody nobody 78496 Mar 24 04:40 DOHblockHZ.rpz
>> -rw-r--r-- 1 nobody nobody 16983551 Mar 24 04:40 MxProPlusHZ.rpz
>> -rw-r--r-- 1 nobody nobody 2893 Mar 24 04:40 tldHZ.rpz
>> -rw-r--r-- 1 nobody nobody 29419 Mar 24 04:40 WinTrkrHZ.rpz
>> [root@ipfire ~] #
>>
>>
>>
>> ------ Original Message ------
>> From "Michael Tremer" <michael.tremer@ipfire.org>
>> To "Bernhard Bitsch" <bbitsch@ipfire.org>
>> Cc development@lists.ipfire.org
>> Date 3/24/2025 9:25:40 AM
>> Subject Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
>>
>>> Hello,
>>>
>>>>
>>>> On 24 Mar 2025, at 13:33, Bernhard Bitsch <bbitsch@ipfire.org> wrote:
>>>> Am 24.03.2025 um 11:17 schrieb Michael Tremer:
>>>>>
>>>>>
>>>>> Hello Jon,
>>>>>>
>>>>>>
>>>>>> On 24 Mar 2025, at 00:00, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>> Michael,
>>>>>> FYI - I was wrong Unbound RPZ is _not_ watching the serial number, it is watching the "refresh", the number after the serial number.
>>>>>
>>>>> Refresh just tells the client how often to check for an update.
>>>>> If that is actually being set by the list publisher, then we have another problem here, because they could put some insanely low value there and we would then DDoS their infrastructure. I think we should keep it like we have it in other places that we control how often we want to check or pull for updates.
>>>>>
>>>> You are right. But an extra update process wastes additional processor time. The update mechanism of unbound does the check for update ( however it is realized ) nevertheless.
>>>
>>> Yes, doing more things needs resources. But we are not seriously considering whether an IPFire system has enough resources to perform the download of a text file, or are we?
>>>
>>>>
>>>>>> I understand that you don’t speak C, but you got the information from somewhere. Documentation maybe? Since that is out of date very often I like to consult the code.
>>>>>> From testing. Downloading rpz files using rpz unbound, and watching what happens. If the rpz file is setup for "once per day" refresh, then it only downloads one time.
>>>>>> However that won’t solve our problem . . . and having no cache.
>>>>>> In `/etc/unbound/tuning.conf` there is `rrset-cache-size: 128m`. Are you referring to a different cache.
>>>>>
>>>>> Naturally unbound is loading the zone into its memory which we generally call cache.
>>>>> When I say cache I am thinking about persistent data storage across multiple restarts of Unbound. If I am downloading 100 MiB of RPZ lists (which is presumably still on the lower end) and I reboot my firewall, I do not want to download the same data again. We can only ever download a list *once* unless we are 100% certain that it has changed. Then we can download it once again.
>>>>
>>>> The RPZ lists are stored in files in persistent storage. Unbound creates the internal cache from these.
>>>
>>> And where are these stored?
>>>
>>>>
>>>>>> Maybe we need to implement both?
>>>>>> Yes. There are very few AXFR list (I think only four were found). And many more HTTPS rpz files.
>>>>>> Jon
>>>>>> ------ Original Message ------
>>>>>> From "Michael Tremer" <michael.tremer@ipfire.org>
>>>>>> To "Jon Murphy" <jon.murphy@ipfire.org>
>>>>>> Cc "IPFire: Development-List" <development@lists.ipfire.org>
>>>>>> Date 3/20/2025 11:26:43 AM
>>>>>> Subject Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
>>>>>>
>>>>>>>
>>>>>>> Hello Jon,
>>>>>>> Please don’t forget to Cc the list...
>>>>>>>
>>>>>>>>
>>>>>>>> On 19 Mar 2025, at 18:27, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>> Michael,
>>>>>>>>
>>>>>>>>>
>>>>>>>>> Where in the code is this implemented? I cannot find anything like this:
>>>>>>>>
>>>>>>>> Keep in mind I am not a "C" person. Maybe in this section?:
>>>>>>>> https://git.ipfire.org/?p=thirdparty/unbound.git;a=blob;f=services/authzone.c;hb=30b9cb5f813003d0a2b1c2e678652396615b1b7d#l5875
>>>>>>>
>>>>>>> This where the AXFR response is being handled when doing a DNS zone transfer. This code is not being called when performing a HTTP download.
>>>>>>> I understand that you don’t speak C, but you got the information from somewhere. Documentation maybe? Since that is out of date very often I like to consult the code.
>>>>>>>
>>>>>>>>
>>>>>>>> —
>>>>>>>> When I was just learning about RPZ I created a separate RPZ file for testing. When I changed the SOA line with a new serial number, the RPZ file download would happen in about 5 minutes.
>>>>>>>> https://people.ipfire.org/~jon/sblack-adhoc.rpz
>>>>>>>
>>>>>>> It might well be that the file is not being reloaded if the download matches the content that unbound already has. That would of course save some resources.
>>>>>>> However that won’t solve our problem with redundant downloads and having no cache.
>>>>>>>
>>>>>>>>
>>>>>>>> That is how I found out the SOA line is watched for a serial number change.
>>>>>>>> I’ll reconfirm my findings.
>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>> The second reason is that we have a lot of firewalls out there. Not all of them will enable this feature and all of the lists, but even if it is a good chunk, we will generate terabytes of traffic which put load on the infrastructure and will cost money. It simply is not what we want to do, regardless of self-hosting those lists and pulling them from somewhere else.
>>>>>>>>
>>>>>>>> So I understand, are you thinking of hosting RPZ AXFR (DNS zone transfer) on IPFire infrastructure?
>>>>>>>
>>>>>>> No, I don’t think that we can generally do this. The biggest problem is licensing as we cannot take anyones content and host it ourselves. We would re-distribute those lists and that will only work with permission of the publishers. I assume that would be too much work to actually get some useful content out there. We might limit ourselves to only those lists that are under a very permissive license. Nobody wants that.
>>>>>>> From a technical point of view, DNS over TCP might not be very nice in terms of forging the transfer and so we would need TLS as well… It should work, but even if we would be able to encourage other people to publish their lists I doubt they would implement DNS over TLS for authoritative DNS. That standard is in very early stages as well.
>>>>>>> As far as I can see, those vendors who offer a list as a commercial product are using DNS to distribute it (e.g. Spamhaus). Those people who have made this all a hobby are throwing the lists onto GitHub and let them handle the traffic.
>>>>>>> Maybe we need to implement both?
>>>>>>> -Michael
>>>>>>>
>>>>>>>>
>>>>>>>> Jon
>>>>>>>> On 3/19/25 5:35 AM, Michael Tremer wrote:
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> Hello Jon,
>>>>>>>>> Where in the code is this implemented? I cannot find anything like this:
>>>>>>>>> Unbound loads the entire file into memory and then starts parsing it. The only special treatment there is is to check whether the first line is a valid zone entry. It does not even have to be a SOA record.
>>>>>>>>> https://git.ipfire.org/?p=thirdparty/unbound.git;a=blob;f=services/authzone.c;hb=30b9cb5f813003d0a2b1c2e678652396615b1b7d#l1188
>>>>>>>>> I am also concerned that Unbound will not be able to support an upstream proxy for any downloads. The caching situation is also unclear for me, so I believe that we will be looking at writing a custom downloader that implements all these things.
>>>>>>>>> -Michael
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> On 19 Mar 2025, at 02:58, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>>>> Michael,
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> The emphasis is on the repeated downloads of the same list. That is
>>>>>>>>>>
>>>>>>>>>> > what cannot happen.
>>>>>>>>>> The Unbound RPZ code, as installed within IPFire, watches for a change
>>>>>>>>>> in the SOA line of each RPZ file. This is an example of the first few
>>>>>>>>>> lines for every RPZ file.
>>>>>>>>>> $TTL 300
>>>>>>>>>> @ SOA localhost. root.localhost. 1742298960 43200 3600 86400 300
>>>>>>>>>> NS localhost.
>>>>>>>>>> ;
>>>>>>>>>> ; Title: HaGeZi's Pop-Up Ads DNS Blocklist
>>>>>>>>>> ; Description: Blocks annoying and malicious pop-up ads.
>>>>>>>>>> If the SOA serial number changes (e.g. the 1742298960), then Unbound RPZ
>>>>>>>>>> code does its thing and downloads. Otherwise there is no download.
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> So there has to be a way to ensure that we won’t download a list again
>>>>>>>>>>
>>>>>>>>>> > unless it has actually changed.
>>>>>>>>>> This should do what you want but I may be missing your point.
>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> DNS has a builtin functionality called AXFR. It simply does the job
>>>>>>>>>>
>>>>>>>>>> > for you. I was just wondering whether that was not being used.
>>>>>>>>>> I need to read about AXFR/IXFR and learn a little more.
>>>>>>>>>> Jon
>>>>>>>>>> On 3/17/25 5:35 AM, Michael Tremer wrote:
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> Good Morning Jon,
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>> On 16 Mar 2025, at 17:00, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>>>>>> Michael,
>>>>>>>>>>>> I was reading through you response again an I want to understand this post:
>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>> I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>>>>>>>>>>>
>>>>>>>>>>>> So if RPZ doesn't use HTTPS, what is it using? I am missing a key point here.
>>>>>>>>>>>
>>>>>>>>>>> The emphasis is on the repeated downloads of the same list. That is what cannot happen.
>>>>>>>>>>> Although it might not affect a lot of people in our general user-base, there are some that have a metered connection and will pay for data by volume. Some of the lists I looked at are just under 20 MiB. Therefore we need to keep any traffic down to a minimum. The second reason is that we have a lot of firewalls out there. Not all of them will enable this feature and all of the lists, but even if it is a good chunk, we will generate terabytes of traffic which put load on the infrastructure and will cost money. It simply is not what we want to do, regardless of self-hosting those lists and pulling them from somewhere else.
>>>>>>>>>>> So there has to be a way to ensure that we won’t download a list again unless it has actually changed.
>>>>>>>>>>> DNS has a builtin functionality called AXFR. It simply does the job for you. I was just wondering whether that was not being used.
>>>>>>>>>>> HTTPS is an option because that is simply what we use elsewhere, but extra functionality will have to be built for it.
>>>>>>>>>>> -Michael
>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>> Jon
>>>>>>>>>>>> On 2/13/25 3:34 PM, jon wrote:
>>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>> Michael,
>>>>>>>>>>>>> I’ve read through your comments a few times and I ended up with many more questions.
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>>>>>>>>>>>
>>>>>>>>>>>>> To me the efforts to get new code accepted seem to have changed and it seemed easier in the past. In the past I made the Core Team aware via the Dev Mailing List and wrote a simple two or three paragraphs of "What is it? / What is the value? / Here is the code"
>>>>>>>>>>>>> So in an effort to move forward: How exactly is something presented to the Core Team?
>>>>>>>>>>>>> Is there an example of a recent effort that was presented that I can see as a sample? (This type of info can also be added to the Wiki)
>>>>>>>>>>>>> I understand you want it this way, but I don’t know what exactly is needed. Please be specific.
>>>>>>>>>>>>> Jon
>>>>>>>>>>>>> PS - I am not ignoring your other comments, I am just trying to move forward and keep things simple.
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> On Feb 8, 2025, at 1:27 PM, Michael Tremer <michael.tremer@ipfire.org> wrote:
>>>>>>>>>>>>>> Hello Jon,
>>>>>>>>>>>>>> Thanks for your reply. And good that you are copying everyone into this conversation.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> On 8 Feb 2025, at 18:41, jon <jon.murphy@ipfire.org> wrote:
>>>>>>>>>>>>>>> Michael,
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> Yes, this has been a separate effort (a very public separate effort). Yes, as you pointed this out early on with the "proof-of-concept" and then my request for people to help test RPZ. Nothing was hidden.
>>>>>>>>>>>>>>> This was done because you (and maybe others) did not have the time and I wanted to help and because I needed assistance with RPZ. I tried my best to do this without bothering you.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> I don’t that it is accurate that nobody wanted to help on this. The list was always open - although not every email has been replied to swiftly it is also your responsibility to raise a question again if it was missed. People here have open ears.
>>>>>>>>>>>>>> It was also stated on this very list on in our documentation that working on something without involving the core team is a risky undertaking. Of course IPFire is free software and so everyone is free to fork if they wish to do so.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> You were aware many steps along the way. See your email on July 28, 2024, August 15, 2024, September 30, 2024, December 23, 2024, and January 16. My attempts to get the team involved were met with "things are busy" and sometimes silence. (Yes, I get it, people are busy.)
>>>>>>>>>>>>>>> You and Adolf, Leo, Erik and Bernhard have been aware since the beginning. You mention you were aware of the "proof-of-concept". If you include those beginning posts, since Sep 2023.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Yes, I am aware of a proof-of-concept that I have been running myself for a long time. I am also aware of the efforts that you have been taking.
>>>>>>>>>>>>>> Yet I don’t think there has ever been any joint effort, or am I seeing that wrong?
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> This has not been discussed . . . on our calls.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> On the July 28th you stated:
>>>>>>>>>>>>>>> "We have talked about RPZ many times on the monthly call since the URL filter feature is falling more and more out of fashion. I think there is also many posts about this on the forum."
>>>>>>>>>>>>>>> Please don’t insult me again by stating "you know what I mean".
>>>>>>>>>>>>>>> And it has been discussed but not documented in the Monthly Meeting notes.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> I am not at all insulting you. I don’t want to take this down to a personal level at all. This is a public mailing list and people who read this don’t need to listen to an argument we are having. They are here for the tech inside IPFire.
>>>>>>>>>>>>>> When I wrote that it has not been discussed that does not mean that we have not been touching on the topic. We have been talking about lots of things on the calls, the weather, politics, how our pets are. None of that makes it to the logs. What I rather mean is that it has never been added as a topic on the agenda and it has not been pitched by yourself.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> Regular conversation on the Dev Mailing list is many times met with silence. I get it, people are busy.
>>>>>>>>>>>>>>> And regular two-way conversation doesn’t happen on the list. At least not with me. I’d be happy to point out the posts that were met with silence.
>>>>>>>>>>>>>>> Again, I get it, people are busy.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> And you think my emails are not being met with silence? This has nothing to do with this specific topic. This has something to do with how occupied people are and how engaged they are on certain topics. Not everyone is involved in all the things and simply will ignore emails simply based on their subject line.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> But the "dip here to the list" were my attempts to get a conversation started. As I said, many time met with silence.
>>>>>>>>>>>>>>> The only place I was not met with silence was on the Community. You have a great group of people in the Community. It is a shame you don’t want to have others help. It would reduce your workload.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> You should stop making statements that are not true. Who doesn’t want anyone to help?
>>>>>>>>>>>>>> Not having this conversation on a Saturday evening would reduce my workload. At least it would free up time for something else. Helping with the things that are already on the go would reduce the workload of the entire team. Starting one thing at a time and finishing it is a lot better to manage than starting a hundred things and not even finish one. I can tell you that I already have a hundred things on the go.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> Therefore, what am I supposed to do with this email?
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> To me it is beyond obvious…
>>>>>>>>>>>>>>> If it isn’t what you want, then guide me with how to do this the correct way. And be specific. I am trying to help. I am trying to make things better. I am trying to do things the right way.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> To me it isn’t. This is yet another project that has been dumped to the list like so many before and later on everyone has left to have the team deal with the rest.
>>>>>>>>>>>>>> It is a huge patch set. You explained what the vision is, but that is about it. There is no chance this will continue if this disagreement isn’t solved first. I didn’t even look at the code.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> I don’t want to merge code that I don’t agree with.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> I asked multiple times if you "agreed with the concept" and again, met with silence. Yes I get it, people are busy.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Having support for RPZ? Yes, it was definitely on the roadmap. That I agree with.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> You mentioned this a in the past, but for some reason you do not disclose what I dismissed. Why do you continue to make this harder, wouldn’t it not be easier to tell me what I have dismissed?
>>>>>>>>>>>>>>> I have sent multiple emails trying to answer your concerns and comments. On July 28, Aug 14, Aug 22, Aug 23, Sep 30, etc.
>>>>>>>>>>>>>>> I’ve gone through all of the questions you asked and I cannot find a "dismissed" item.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Maybe I need to be *more clear*. I feel humoured by this.
>>>>>>>>>>>>>> It is late on a Saturday and I want my dinner soon, but certainly I have stated that this should never be an add-on considering it is supposed to replace URL Filter. We should never allow people to add their own sources. I have also stated that we cannot download any lists over HTTPS again and again and again. The implementation that we have here seems to exactly do that and therefore I think that my feedback has been dismissed entirely.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> The maintainers of Unbound and/or RPZ?
>>>>>>>>>>>>>>> The maintainers of Hagezi list, the threatfox list, the urlhaus list, etc.?
>>>>>>>>>>>>>>> What else? The maintainers or the RPZ scripts? That is me. Let’s talk!
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> You. I don’t care much about the providers of the lists.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> See, this is where it gets confusing. There are hundreds of open source packages as part of IPFire. Pick the last five years of items added to the IPFire build. You're telling me you have "constructive conversation with the maintainers" of all of the added packages?
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> They publish their software and they don’t care whether I am pulling it or not. They publish it with the commitment to maintain it - sometimes for better and sometimes for worse.
>>>>>>>>>>>>>> You care about me pulling your code and I don’t know whether you would commit to maintain this.
>>>>>>>>>>>>>> These two are very different cases.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> Pick the IP Blocklists list (i.e., 3CORESEC, ABUSECH, DSHIELD, SPAMHAUS, etc.) or the Suricata lists (i.e.,Emergingthreats.net <http://emergingthreats.net/>,Abuse.ch <http://abuse.ch/>, etc.). So you’ve have "constructive conversation with the maintainers"?
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Yes, occasionally I have phone calls with a few of these providers.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> Ha! Yes a surprise. In the beginning you seemed interested as IPFire needed a replacement for URL Filter. You asked good questions about the lists picked, asked for the value to the users, etc. And I answered the best I could.
>>>>>>>>>>>>>>> You even asked: “Why is this realised as an add-on and not part of the core system?” from your Jul 28, 2024 email.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Ah, so, why is the patch creating an add-on? Not that I am saying that what I say is law, but it has not been challenged either. If my input is being ignored, why should I put this to the top of my list of priorities? I am not disappointed about this, just trying to be very good with my time.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> And on January 16, 2025 I wrote a message looking for help. And you were kind to respond quickly. So in three weeks time, since the kind response, something has changed. You went from supportive to "this".
>>>>>>>>>>>>>>> So yes, I am surprised.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Well, maybe I should not have replied to that email. It was clear that you were on some path that was not right, but you were not interested before in finding the right path from the beginning.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> Be more specific, what has to change? What exactly did I dismiss?
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Dismissal is just my assumption. I don’t know what you actually did with my feedback. I can only see the end product that does not seem contain much of it. Repeatedly I have been pointing out that we should think before we build. I am sure a lot of hours have now gone into some code that simply does not satisfy me. And I am not not talking about the code itself, what it does is what I don’t think is right for us.
>>>>>>>>>>>>>> The process is very clear for me that we should first of all think whether we want a certain feature now. Then there should be a clear roadmap for everyone to follow; tasks can be split-up as we go and hopefully then have something that is maintainable, interesting for our users and even would do us proud. This is how this should work.
>>>>>>>>>>>>>> So, what has to change? I don’t think with shouting at each other, throwing patches around and making me generally unhappy is a good start.
>>>>>>>>>>>>>> -Michael
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> Jon
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> On Feb 6, 2025, at 2:13 PM, Michael Tremer <michael.tremer@ipfire.org> wrote:
>>>>>>>>>>>>>>>> Hello Jon,
>>>>>>>>>>>>>>>> Well, here we are again with another patch regarding this feature.
>>>>>>>>>>>>>>>> I cannot quite see from your email what the question is, but if this is a request to have this merged into IPFire, I am once again sorry to disappoint you.
>>>>>>>>>>>>>>>> I think I have covered this all at lengths before that this project has been started as a separate effort and as far as I am aware none of the other team members has been involved. This has not been discussed either on this list, on our calls. Instead there has been a separate conversation on the forum with the occasional dip here to the list. But that was not a regular two-way conversation. Therefore, what am I supposed to do with this email?
>>>>>>>>>>>>>>>> I don’t want to merge code that I don’t agree with. So many fundamental things that I have been raising have either not been discussed or outright dismissed.
>>>>>>>>>>>>>>>> I don’t want to merge code that has no future inside IPFire as there is no constructive conversation with the maintainers of it.
>>>>>>>>>>>>>>>> Having been trying for a long time to make you aware of this, nothing of this should come as a surprise.
>>>>>>>>>>>>>>>> Please consider if that can be changed and if there is a path forward with this.
>>>>>>>>>>>>>>>> All the best,
>>>>>>>>>>>>>>>> -Michael
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> On 6 Feb 2025, at 16:35, Jon Murphy <jon.murphy@ipfire.org> wrote:
>>>>>>>>>>>>>>>>> What is it?
>>>>>>>>>>>>>>>>> Response Policy Zone (RPZ) is a mechanism to define local policies in a
>>>>>>>>>>>>>>>>> standardized way and load those policies from external sources.
>>>>>>>>>>>>>>>>> Bottom line: RPZ allows admins to easily block access to websites via DNS lookup.
>>>>>>>>>>>>>>>>> RPZ can block websites via categories. Examples include: fake websites, annoying
>>>>>>>>>>>>>>>>> pop-up ads, newly registered domains, DoH bypass sites, bad "host" services,
>>>>>>>>>>>>>>>>> maliscious top level domains (e.g., *.zip, *.mov), piracy, gambling, pornography,
>>>>>>>>>>>>>>>>> and more. RPZ lists come from various RPZ providers and their available
>>>>>>>>>>>>>>>>> catagories.
>>>>>>>>>>>>>>>>> This RPZ add-on enables the RPZ functionality by adding a couple lines in a
>>>>>>>>>>>>>>>>> configuration file. This add-on simply adds configuration files and adds
>>>>>>>>>>>>>>>>> scripts (config, metrics and sleep) to make RPZ easier for the admin to use.
>>>>>>>>>>>>>>>>> The RPZ scripts include additional languages: German, Spanish, French, Turkish,
>>>>>>>>>>>>>>>>> and Italian.
>>>>>>>>>>>>>>>>> RPZ itself was release in 2010 and has been part of the IPFire build since ~2015.
>>>>>>>>>>>>>>>>> Why is it needed? What is its value?
>>>>>>>>>>>>>>>>> - The RPZ concept places this filtering into IPFire, our internet access
>>>>>>>>>>>>>>>>> gateway, which is (should be) solely used as DNS source of the internal network.
>>>>>>>>>>>>>>>>> - As most sites use HTTPS it makes it difficult to filter traffic with URL
>>>>>>>>>>>>>>>>> Filter without also properly configuring conventional (non-transparent)
>>>>>>>>>>>>>>>>> mode on the proxy. RPZ is a nice replacement for the URL Filter.
>>>>>>>>>>>>>>>>> - No need to install and maintain an additional device like PiHole or AdBlock
>>>>>>>>>>>>>>>>> browser extensions on multiple user devices.
>>>>>>>>>>>>>>>>> - This is an additional layer of protection for users. Less worry someone will
>>>>>>>>>>>>>>>>> click on something that gets them into trouble. And, saying this with emphasis,
>>>>>>>>>>>>>>>>> the ability to do it in one place!
>>>>>>>>>>>>>>>>> - Blocked sites save on unneeded traffic and can lessen the threat of malware
>>>>>>>>>>>>>>>>> in advertisements
>>>>>>>>>>>>>>>>> - Logging allows the admin to see the site blocked and take actions
>>>>>>>>>>>>>>>>> - RPZ will be used at the home, home-office (work from home), schools,
>>>>>>>>>>>>>>>>> ministerial, and at the office. Device counts are small (2-6) to medium (~80)
>>>>>>>>>>>>>>>>> to mediam-large (200+).
>>>>>>>>>>>>>>>>> - RPZ can block ads, popups, phishing, scammers, spyware, malware, annoying
>>>>>>>>>>>>>>>>> popups, NSFW links, DOH servers, and the usual internet trash.
>>>>>>>>>>>>>>>>> ------------------------------
>>>>>>>>>>>>>>>>> Change Log for RPZ add-on
>>>>>>>>>>>>>>>>> rpz-1.0.0-18 on 2025-02-05
>>>>>>>>>>>>>>>>> - Build for approval & release as IPFire add-on
>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>> rpz-beta-0.1.18-18.ipfire on 2025-02-01
>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>> - new feature: added a mod key to force a unbound restart
>>>>>>>>>>>>>>>>> rpz-config and rpz-make:
>>>>>>>>>>>>>>>>> - new feature: added action for unbound restart `rpz-config unbound-restart`
>>>>>>>>>>>>>>>>> rpz-metrics:
>>>>>>>>>>>>>>>>> - simple reformatting
>>>>>>>>>>>>>>>>> - rename far right column from "last update" to "last download"
>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>> rpz-beta-0.1.17-17.ipfire on 2024-12-09
>>>>>>>>>>>>>>>>> rpz-make
>>>>>>>>>>>>>>>>> - bug fix: corrected validation regex for wildcards like: `*.domain.com`
>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>> rpz-beta-0.1.16-16.ipfire on 2024-11-18
>>>>>>>>>>>>>>>>> rpz-make
>>>>>>>>>>>>>>>>> - new feature: updated validation regex
>>>>>>>>>>>>>>>>> - bug fix: moved validation to beginning of process. Now we validate before
>>>>>>>>>>>>>>>>> creating config files.
>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>> - new feature: use CSS color variables of the main ipfire theme
>>>>>>>>>>>>>>>>> - bug fix: empty zonefile remarks were stored as “undef” and caused a warning
>>>>>>>>>>>>>>>>> - bug fix: HTML textarea removes the first empty line in a custom list
>>>>>>>>>>>>>>>>> - thank you Leo!
>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>> rpz-beta-0.1.15-15.ipfire on 2024-11-04
>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>> - new feature: added new language file for Turkish (thank you Peppe)
>>>>>>>>>>>>>>>>> rpz-make
>>>>>>>>>>>>>>>>> - bug fix: corrected empty allow/block list issue. An empty allow/block list
>>>>>>>>>>>>>>>>> will now remove contents of allow/block.rpz files and remove unneeded
>>>>>>>>>>>>>>>>> allow/block.conf file. (thank you iptom)
>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>> rpz-beta-0.1.14-14.ipfire on 2024-10-29
>>>>>>>>>>>>>>>>> rpz-config:
>>>>>>>>>>>>>>>>> - bug fix: correct missing rpz extension. `rpz-config list` displayed URL
>>>>>>>>>>>>>>>>> incorrectly (thank you Bernhard)
>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>> - bug fix: remove extra `"` in language files (thank you Bernhard)
>>>>>>>>>>>>>>>>> - new feature: slightly dim "apply" button when not enabled
>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>> rpz-beta-0.1.13-13.ipfire on 2024-10-27
>>>>>>>>>>>>>>>>> - skipped
>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>> rpz-beta-0.1.12-12.ipfire on 2024-10-21
>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>> - new feature: added new language file for French (thank you gw-ipfire)
>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>> rpz-beta-0.1.11-11.ipfire on 2024-10-18
>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>> - new feature: added new language file for Italian (thank you umberto)
>>>>>>>>>>>>>>>>> - new feature: added new language file for Spanish (thank you Roberto)
>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>> rpz-beta-0.1.10-10.ipfire on 2024-10-15
>>>>>>>>>>>>>>>>> rpz-make:
>>>>>>>>>>>>>>>>> - bug fix: corrected validation error for a custom list entry (thank you siosios)
>>>>>>>>>>>>>>>>> - e.g., `*.cloudflare-dns.com`
>>>>>>>>>>>>>>>>> install.sh:
>>>>>>>>>>>>>>>>> - bug fix: add chown to correct user created files
>>>>>>>>>>>>>>>>> update.sh:
>>>>>>>>>>>>>>>>> - bug fix: add chown to correct user created files (thank you siosios)
>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>> rpz-beta-0.1.9-9.ipfire on 2024-10-08
>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>> - new feature: added new language file for German (thank you Leo)
>>>>>>>>>>>>>>>>> - bug fix: add missing "rpz exitcode 110"
>>>>>>>>>>>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>> rpz-beta-0.1.8-8.ipfire on 2024-10-04
>>>>>>>>>>>>>>>>> - skipped
>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>> rpz-beta-0.1.7-7.ipfire on 2024-10-03
>>>>>>>>>>>>>>>>> All:
>>>>>>>>>>>>>>>>> - new feature: includes beta version numbers for pakfire package,
>>>>>>>>>>>>>>>>> instead of only `rpz-1.0.0-1.ipfire`, for each release.
>>>>>>>>>>>>>>>>> rpz.cgi:
>>>>>>>>>>>>>>>>> - new feature: added new WebGUI at `rpz.cgi`
>>>>>>>>>>>>>>>>> - a BIG thank you to Leo Hofmann for all of his work creating the webgui!!
>>>>>>>>>>>>>>>>> - bug fix: corrected missing RPZ menu item at menu > IPFire
>>>>>>>>>>>>>>>>> rpz-make:
>>>>>>>>>>>>>>>>> - new feature: validate entries in allowlist and blocklist
>>>>>>>>>>>>>>>>> - new feature: add "no-reload" option for WebGUI
>>>>>>>>>>>>>>>>> rpz-metrics:
>>>>>>>>>>>>>>>>> - new feature: info can be sorted by name, by hit count, by line count, by
>>>>>>>>>>>>>>>>> "enabled" list or all lists
>>>>>>>>>>>>>>>>> backups:
>>>>>>>>>>>>>>>>> - bug fix: include all files in `/var/ipfire/dns/rpz` directory in backup
>>>>>>>>>>>>>>>>> update.sh:
>>>>>>>>>>>>>>>>> - bug fix: corrected ownership for `/var/ipfire/dns/rpz` directory during an
>>>>>>>>>>>>>>>>> update
>>>>>>>>>>>>>>>>> Build:
>>>>>>>>>>>>>>>>> - bug fix: `block.rpz.conf` and `block.rpz` from build. Files to be created
>>>>>>>>>>>>>>>>> by `rpz-make`
>>>>>>>>>>>>>>>>> WebGUI and German language file
>>>>>>>>>>>>>>>>> Contribution-by: Leo-Andres Hofmann <hofmann@leo-andres.de>
>>>>>>>>>>>>>>>>> Spanish language file
>>>>>>>>>>>>>>>>> Contribution-by: Roberto Peña
>>>>>>>>>>>>>>>>> Italian language file
>>>>>>>>>>>>>>>>> Contribution-by: Umberto Parma
>>>>>>>>>>>>>>>>> French language file
>>>>>>>>>>>>>>>>> Contribution-by: gw-ipfire
>>>>>>>>>>>>>>>>> Turkish language file
>>>>>>>>>>>>>>>>> Contribution-by: Peppe Tech
>>>>>>>>>>>>>>>>> Contribution-by: Bernhard Bitsch <bbitsch@ipfire.org>
>>>>>>>>>>>>>>>>> Contribution-by: Erik Kapfer <erik.kapfer@ipfire.org>
>>>>>>>>>>>>>>>>> Signed-off-by: Jon Murphy <jon.murphy@ipfire.org
>>>>>>>>>>>>>>>>> ---
>>>>>>>>>>>>>>>>> config/backup/includes/rpz | 4 +
>>>>>>>>>>>>>>>>> config/cfgroot/manualpages | 1 +
>>>>>>>>>>>>>>>>> config/menu/EX-rpz.menu | 6 +
>>>>>>>>>>>>>>>>> config/rootfiles/common/configroot | 1 +
>>>>>>>>>>>>>>>>> config/rootfiles/common/web-user-interface | 1 +
>>>>>>>>>>>>>>>>> config/rootfiles/packages/rpz | 20 +
>>>>>>>>>>>>>>>>> config/rpz/00-rpz.conf | 10 +
>>>>>>>>>>>>>>>>> config/rpz/rpz-config | 130 +++
>>>>>>>>>>>>>>>>> config/rpz/rpz-functions | 85 ++
>>>>>>>>>>>>>>>>> config/rpz/rpz-make | 203 +++++
>>>>>>>>>>>>>>>>> config/rpz/rpz-metrics | 170 ++++
>>>>>>>>>>>>>>>>> config/rpz/rpz-sleep | 58 ++
>>>>>>>>>>>>>>>>> config/rpz/rpz.de.pl | 30 +
>>>>>>>>>>>>>>>>> config/rpz/rpz.en.pl | 30 +
>>>>>>>>>>>>>>>>> config/rpz/rpz.es.pl | 30 +
>>>>>>>>>>>>>>>>> config/rpz/rpz.fr.pl | 30 +
>>>>>>>>>>>>>>>>> config/rpz/rpz.it.pl | 30 +
>>>>>>>>>>>>>>>>> config/rpz/rpz.tr.pl | 30 +
>>>>>>>>>>>>>>>>> html/cgi-bin/rpz.cgi | 923 +++++++++++++++++++++
>>>>>>>>>>>>>>>>> lfs/rpz | 96 +++
>>>>>>>>>>>>>>>>> make.sh | 3 +-
>>>>>>>>>>>>>>>>> src/paks/rpz/install.sh | 36 +
>>>>>>>>>>>>>>>>> src/paks/rpz/uninstall.sh | 38 +
>>>>>>>>>>>>>>>>> src/paks/rpz/update.sh | 52 ++
>>>>>>>>>>>>>>>>> 24 files changed, 2016 insertions(+), 1 deletion(-)
>>>>>>>>>>>>>>>>> create mode 100644 config/backup/includes/rpz
>>>>>>>>>>>>>>>>> create mode 100644 config/menu/EX-rpz.menu
>>>>>>>>>>>>>>>>> create mode 100644 config/rootfiles/packages/rpz
>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/00-rpz.conf
>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz-config
>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz-functions
>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz-make
>>>>>>>>>>>>>>>>> create mode 100755 config/rpz/rpz-metrics
>>>>>>>>>>>>>>>>> create mode 100755 config/rpz/rpz-sleep
>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.de.pl
>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.en.pl
>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.es.pl
>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.fr.pl
>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.it.pl
>>>>>>>>>>>>>>>>> create mode 100644 config/rpz/rpz.tr.pl
>>>>>>>>>>>>>>>>> create mode 100644 html/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>>>> create mode 100644 lfs/rpz
>>>>>>>>>>>>>>>>> create mode 100644 src/paks/rpz/install.sh
>>>>>>>>>>>>>>>>> create mode 100644 src/paks/rpz/uninstall.sh
>>>>>>>>>>>>>>>>> create mode 100644 src/paks/rpz/update.sh
>>>>>>>>>>>>>>>>> diff --git a/config/backup/includes/rpz b/config/backup/includes/rpz
>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>> index 000000000..36513e494
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/config/backup/includes/rpz
>>>>>>>>>>>>>>>>> @@ -0,0 +1,4 @@
>>>>>>>>>>>>>>>>> +/var/ipfire/dns/rpz/*
>>>>>>>>>>>>>>>>> +/etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>>>> +/etc/unbound/zonefiles/block.rpz
>>>>>>>>>>>>>>>>> +/etc/unbound/local.d/*rpz.conf
>>>>>>>>>>>>>>>>> diff --git a/config/cfgroot/manualpages b/config/cfgroot/manualpages
>>>>>>>>>>>>>>>>> index 1f7e01efc..d3a48c633 100644
>>>>>>>>>>>>>>>>> --- a/config/cfgroot/manualpages
>>>>>>>>>>>>>>>>> +++ b/config/cfgroot/manualpages
>>>>>>>>>>>>>>>>> @@ -70,6 +70,7 @@ pakfire.cgi=configuration/ipfire/pakfire
>>>>>>>>>>>>>>>>> wlanap.cgi=addons/wireless
>>>>>>>>>>>>>>>>> tor.cgi=addons/tor
>>>>>>>>>>>>>>>>> samba.cgi=addons/samba
>>>>>>>>>>>>>>>>> +rpz.cgi=addons/rpz
>>>>>>>>>>>>>>>>> # Logs menu
>>>>>>>>>>>>>>>>> logs.cgi/summary.dat=configuration/logs/summary
>>>>>>>>>>>>>>>>> diff --git a/config/menu/EX-rpz.menu b/config/menu/EX-rpz.menu
>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>> index 000000000..2f4daf410
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/config/menu/EX-rpz.menu
>>>>>>>>>>>>>>>>> @@ -0,0 +1,6 @@
>>>>>>>>>>>>>>>>> +$subipfire->{'20.rpz'} = {
>>>>>>>>>>>>>>>>> + 'caption' => $Lang::tr{'rpz'},
>>>>>>>>>>>>>>>>> + 'uri' => '/cgi-bin/rpz.cgi',
>>>>>>>>>>>>>>>>> + 'title' => "RPZ",
>>>>>>>>>>>>>>>>> + 'enabled' => 1,
>>>>>>>>>>>>>>>>> +};
>>>>>>>>>>>>>>>>> diff --git a/config/rootfiles/common/configroot b/config/rootfiles/common/configroot
>>>>>>>>>>>>>>>>> index 9839eee45..b30d6aae4 100644
>>>>>>>>>>>>>>>>> --- a/config/rootfiles/common/configroot
>>>>>>>>>>>>>>>>> +++ b/config/rootfiles/common/configroot
>>>>>>>>>>>>>>>>> @@ -120,6 +120,7 @@ var/ipfire/menu.d/70-log.menu
>>>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-apcupsd.menu
>>>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-guardian.menu
>>>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-mympd.menu
>>>>>>>>>>>>>>>>> +#var/ipfire/menu.d/EX-rpz.menu
>>>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-samba.menu
>>>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-tor.menu
>>>>>>>>>>>>>>>>> #var/ipfire/menu.d/EX-transmission.menu
>>>>>>>>>>>>>>>>> diff --git a/config/rootfiles/common/web-user-interface b/config/rootfiles/common/web-user-interface
>>>>>>>>>>>>>>>>> index 816241dae..e00464076 100644
>>>>>>>>>>>>>>>>> --- a/config/rootfiles/common/web-user-interface
>>>>>>>>>>>>>>>>> +++ b/config/rootfiles/common/web-user-interface
>>>>>>>>>>>>>>>>> @@ -69,6 +69,7 @@ srv/web/ipfire/cgi-bin/proxy.cgi
>>>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/qos.cgi
>>>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/remote.cgi
>>>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/routing.cgi
>>>>>>>>>>>>>>>>> +#srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>>>> #srv/web/ipfire/cgi-bin/samba.cgi
>>>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/services.cgi
>>>>>>>>>>>>>>>>> srv/web/ipfire/cgi-bin/shutdown.cgi
>>>>>>>>>>>>>>>>> diff --git a/config/rootfiles/packages/rpz b/config/rootfiles/packages/rpz
>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>> index 000000000..1c8663049
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/config/rootfiles/packages/rpz
>>>>>>>>>>>>>>>>> @@ -0,0 +1,20 @@
>>>>>>>>>>>>>>>>> +etc/unbound/local.d/00-rpz.conf
>>>>>>>>>>>>>>>>> +etc/unbound/zonefiles
>>>>>>>>>>>>>>>>> +etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>>>> +usr/sbin/rpz-config
>>>>>>>>>>>>>>>>> +usr/sbin/rpz-functions
>>>>>>>>>>>>>>>>> +usr/sbin/rpz-make
>>>>>>>>>>>>>>>>> +usr/sbin/rpz-metrics
>>>>>>>>>>>>>>>>> +usr/sbin/rpz-sleep
>>>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.de.pl
>>>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.en.pl
>>>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.es.pl
>>>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.fr.pl
>>>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.it.pl
>>>>>>>>>>>>>>>>> +var/ipfire/addon-lang/rpz.tr.pl
>>>>>>>>>>>>>>>>> +var/ipfire/backup/addons/includes/rpz
>>>>>>>>>>>>>>>>> +var/ipfire/dns/rpz
>>>>>>>>>>>>>>>>> +var/ipfire/dns/rpz/allowlist
>>>>>>>>>>>>>>>>> +var/ipfire/dns/rpz/blocklist
>>>>>>>>>>>>>>>>> +var/ipfire/menu.d/EX-rpz.menu
>>>>>>>>>>>>>>>>> +srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>>>> diff --git a/config/rpz/00-rpz.conf b/config/rpz/00-rpz.conf
>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>> index 000000000..f005a4f2e
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/config/rpz/00-rpz.conf
>>>>>>>>>>>>>>>>> @@ -0,0 +1,10 @@
>>>>>>>>>>>>>>>>> +server:
>>>>>>>>>>>>>>>>> + module-config: "respip validator iterator"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +rpz:
>>>>>>>>>>>>>>>>> + name: allow.rpz
>>>>>>>>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>>>> + rpz-action-override: passthru
>>>>>>>>>>>>>>>>> + rpz-log: yes
>>>>>>>>>>>>>>>>> + rpz-log-name: allow
>>>>>>>>>>>>>>>>> + rpz-signal-nxdomain-ra: yes
>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-config b/config/rpz/rpz-config
>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>> index 000000000..c72d50f9b
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-config
>>>>>>>>>>>>>>>>> @@ -0,0 +1,130 @@
>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +version="2025-01-11 - v44"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +tagName="unbound"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +rpzAction="${1}" # input RPZ action
>>>>>>>>>>>>>>>>> +rpzName="${2}" # input RPZ name
>>>>>>>>>>>>>>>>> +rpzURL="${3}" # input RPZ URL
>>>>>>>>>>>>>>>>> +rpzOption1="${4}" # input RPZ option #1
>>>>>>>>>>>>>>>>> +rpzOption2="${5}" # input RPZ option #2
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +rpzConfig="/etc/unbound/local.d/${rpzName}.rpz.conf" # output zone conf file
>>>>>>>>>>>>>>>>> +rpzFile="/etc/unbound/zonefiles/${rpzName}.rpz" # output for RPZ file
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>>>>>>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>>>>>>>>> + case "$1" in
>>>>>>>>>>>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>>>>>>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>>>>>>>>>>>> + esac
>>>>>>>>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +case "${rpzAction}" in
>>>>>>>>>>>>>>>>> + # add new rpz list
>>>>>>>>>>>>>>>>> + add )
>>>>>>>>>>>>>>>>> + check_name "${rpzName}" # is this a valid name?
>>>>>>>>>>>>>>>>> + # does this config already exist? If yes, then exit
>>>>>>>>>>>>>>>>> + if [[ -f "${rpzConfig}" ]] ; then
>>>>>>>>>>>>>>>>> + msg_log "error: rpz: duplicate - ${rpzConfig} already exists. exit"
>>>>>>>>>>>>>>>>> + exit 104
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # is this a valid URL?
>>>>>>>>>>>>>>>>> + regex='^https://[-[:alnum:]\+&@#/%?=~_|!:,.;]*[-[:alnum:]\+&@#/%=~_|]'
>>>>>>>>>>>>>>>>> + if ! [[ "${rpzURL}" =~ $regex ]] ; then
>>>>>>>>>>>>>>>>> + msg_log "error: rpz: the URL is not valid: \"${rpzURL}\". exit."
>>>>>>>>>>>>>>>>> + exit 105
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # create the zone config file
>>>>>>>>>>>>>>>>> + {
>>>>>>>>>>>>>>>>> + echo "rpz:"
>>>>>>>>>>>>>>>>> + echo " name: ${rpzName}.rpz"
>>>>>>>>>>>>>>>>> + echo " zonefile: ${rpzFile}"
>>>>>>>>>>>>>>>>> + echo " url: ${rpzURL}"
>>>>>>>>>>>>>>>>> + echo " rpz-action-override: nxdomain"
>>>>>>>>>>>>>>>>> + echo " rpz-log: ${rpzLog}"
>>>>>>>>>>>>>>>>> + echo " rpz-log-name: ${rpzName}"
>>>>>>>>>>>>>>>>> + echo " rpz-signal-nxdomain-ra: yes"
>>>>>>>>>>>>>>>>> + } > "${rpzConfig}"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # set-up zonefile
>>>>>>>>>>>>>>>>> + # create an empty rpz file if it does not exist
>>>>>>>>>>>>>>>>> + if [[ ! -f "${rpzFile}" ]] ; then
>>>>>>>>>>>>>>>>> + touch "${rpzFile}"
>>>>>>>>>>>>>>>>> + # unbound requires these settings for rpz files
>>>>>>>>>>>>>>>>> + set_permissions "${rpzFile}" "${rpzConfig}"
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # trash config file & rpz file
>>>>>>>>>>>>>>>>> + remove )
>>>>>>>>>>>>>>>>> + if ! [[ -f "${rpzConfig}" ]] ; then
>>>>>>>>>>>>>>>>> + msg_log "error: rpz: cannot remove ${rpzConfig}, does not exist. exit"
>>>>>>>>>>>>>>>>> + exit 106
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + msg_log "info: rpz: remove config file & rpz file \"${rpzName}\""
>>>>>>>>>>>>>>>>> + rm "${rpzConfig}"
>>>>>>>>>>>>>>>>> + rm "${rpzFile}"
>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + reload )
>>>>>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + list )
>>>>>>>>>>>>>>>>> + awk -F':' '/^\s*name:/{ gsub(/[[:blank:]]|\.rpz/, "",$2) ; NAME=$2 } \
>>>>>>>>>>>>>>>>> + /^\s*url:/{ gsub(/[[:blank:]]/, "") ; print NAME"="$2":"$3} ' \
>>>>>>>>>>>>>>>>> + /etc/unbound/local.d/*rpz.conf
>>>>>>>>>>>>>>>>> + exit
>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + unbound-restart )
>>>>>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>>>>>> + unbound_restart
>>>>>>>>>>>>>>>>> + exit
>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + * )
>>>>>>>>>>>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>>>>>>>>>>>> + printf "Usage: $(basename "$0") <ACTION> <NAME> <URL> <OPTION> <OPTION>\n"
>>>>>>>>>>>>>>>>> + printf "Version: ${version}\n"
>>>>>>>>>>>>>>>>> + exit 108
>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +esac
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +exit
>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-functions b/config/rpz/rpz-functions
>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>> index 000000000..ace1d2690
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-functions
>>>>>>>>>>>>>>>>> @@ -0,0 +1,85 @@
>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +version="2024-12-10 - v02"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +msg_log () {
>>>>>>>>>>>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>>>>>>>>>>>> + if tty --silent ; then
>>>>>>>>>>>>>>>>> + echo "${tagName}:" "$*"
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# check for a valid name
>>>>>>>>>>>>>>>>> +check_name () {
>>>>>>>>>>>>>>>>> + local theName="${1}"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + regex='^[a-zA-Z0-9_]+$' # no dash or plus, alpha numeric only
>>>>>>>>>>>>>>>>> + regex1='^(allow|block)$' # allow and block are reserved NAMEs
>>>>>>>>>>>>>>>>> + if [[ ! "${theName}" =~ $regex ]] || [[ "${theName}" =~ $regex1 ]] ; then
>>>>>>>>>>>>>>>>> + msg_log "error: rpz: the NAME is not valid: \"${theName}\". exit."
>>>>>>>>>>>>>>>>> + exit 101
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +set_permissions () {
>>>>>>>>>>>>>>>>> + chown nobody:nobody "$@"
>>>>>>>>>>>>>>>>> + chmod 644 "$@"
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +check_unbound_conf () {
>>>>>>>>>>>>>>>>> + local thecheckConf="${1:-yes}" # check config default is yes
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # check the above config files
>>>>>>>>>>>>>>>>> + if [[ "${thecheckConf}" == yes ]] ; then
>>>>>>>>>>>>>>>>> + msg_log "info: rpz: check for errors with \"unbound-checkconf\""
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + if ! unbound-checkconf ; then
>>>>>>>>>>>>>>>>> + msg_log "error: rpz: unbound-checkconf found invalid configuration."
>>>>>>>>>>>>>>>>> + msg_log \
>>>>>>>>>>>>>>>>> + "error: rpz: In Terminal run the command \"unbound-checkconf\" for more information. exit."
>>>>>>>>>>>>>>>>> + exit 102
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +unbound_control_reload () {
>>>>>>>>>>>>>>>>> + local theReload="${1:-yes}" # reload default is yes
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + if [[ "${theReload}" == yes ]] ; then
>>>>>>>>>>>>>>>>> + # reload due to the changes
>>>>>>>>>>>>>>>>> + msg_log "info: rpz: run \"unbound-control reload\""
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + if ! unbound-control reload ; then
>>>>>>>>>>>>>>>>> + msg_log "error: rpz: unbound-control reload. exit."
>>>>>>>>>>>>>>>>> + exit 109
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +unbound_restart () {
>>>>>>>>>>>>>>>>> + # restart due to the changes
>>>>>>>>>>>>>>>>> + msg_log "info: rpz: run \"unbound restart\""
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + /usr/local/bin/unboundctrl restart
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-make b/config/rpz/rpz-make
>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>> index 000000000..927d55170
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-make
>>>>>>>>>>>>>>>>> @@ -0,0 +1,203 @@
>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>> +# Copyright (C) 2024-2025 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +version="2025-01-11 - v14"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# create the config file for allow
>>>>>>>>>>>>>>>>> +make_allow_config () {
>>>>>>>>>>>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>>>>>>>>>>>> + local theConfig="/etc/unbound/local.d/00-rpz.conf" # output zone conf file
>>>>>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/allowlist" # input custom list of domains
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + msg_log "info: rpz: make config file \"00-rpz.conf\""
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + echo "server:
>>>>>>>>>>>>>>>>> + module-config: \"respip validator iterator\"" > "${theConfig}"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # does allow list exist?
>>>>>>>>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + echo "rpz:
>>>>>>>>>>>>>>>>> + name: allow.rpz
>>>>>>>>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>>>> + rpz-action-override: passthru
>>>>>>>>>>>>>>>>> + rpz-log: ${theLog}
>>>>>>>>>>>>>>>>> + rpz-log-name: allow
>>>>>>>>>>>>>>>>> + rpz-signal-nxdomain-ra: yes" >> "${theConfig}"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>>>>>>>>> + set_permissions "${theConfig}"
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# create the config file for block
>>>>>>>>>>>>>>>>> +make_block_config () {
>>>>>>>>>>>>>>>>> + local theLog="${1:-yes}" # log default ON
>>>>>>>>>>>>>>>>> + local theConfig="/etc/unbound/local.d/block.rpz.conf" # output zone conf file
>>>>>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/blocklist" # input custom list of domains
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + msg_log "info: rpz: make config file \"block.rpz.conf\""
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # does block list exist?
>>>>>>>>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + echo "rpz:
>>>>>>>>>>>>>>>>> + name: block.rpz
>>>>>>>>>>>>>>>>> + zonefile: /etc/unbound/zonefiles/block.rpz
>>>>>>>>>>>>>>>>> + rpz-action-override: nxdomain
>>>>>>>>>>>>>>>>> + rpz-log: ${theLog}
>>>>>>>>>>>>>>>>> + rpz-log-name: block
>>>>>>>>>>>>>>>>> + rpz-signal-nxdomain-ra: yes" > "${theConfig}"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>>>>>>>>> + set_permissions "${theConfig}"
>>>>>>>>>>>>>>>>> + else
>>>>>>>>>>>>>>>>> + # no - trash the config file
>>>>>>>>>>>>>>>>> + rm --verbose /etc/unbound/local.d/block.rpz.conf
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# create an RPZ file for allow or block
>>>>>>>>>>>>>>>>> +make_rpz_file () {
>>>>>>>>>>>>>>>>> + local theName="${1}" # allow or block
>>>>>>>>>>>>>>>>> + local theAction='.' # the default is nxdomain or block
>>>>>>>>>>>>>>>>> + local actionList
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>>>>>>>>>>>> + local theZonefile="/etc/unbound/zonefiles/${theName}.rpz" # output file for RPZ
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # does a list exist?
>>>>>>>>>>>>>>>>> + if [[ -s "${theList}" ]] && grep -q . "${theList}" ; then
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # for allow set to passthru
>>>>>>>>>>>>>>>>> + [[ "${theName}" == allow ]] && theAction='rpz-passthru.'
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # drop any extra "blanks" and add "CNAME <RPZ action>." to each line
>>>>>>>>>>>>>>>>> + actionList=$( awk '{$1=$1};1' "${theList}" |
>>>>>>>>>>>>>>>>> + sed "/^[^;].*[[:alnum:]]/ s|$| CNAME ${theAction}|" )
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + msg_log "info: rpz: create zonefile for \"${theName}list\""
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +echo "; Name: ${theName} list
>>>>>>>>>>>>>>>>> +; Last modified: $(date "+%Y-%m-%d at %H.%M.%S %Z")
>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>> +; domains with actions list
>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>> +${actionList}" > "${theZonefile}"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # set-up zonefile - unbound requires these settings for rpz files
>>>>>>>>>>>>>>>>> + set_permissions "${theZonefile}"
>>>>>>>>>>>>>>>>> + # set-up allow/block list files
>>>>>>>>>>>>>>>>> + set_permissions "${theList}"
>>>>>>>>>>>>>>>>> + else
>>>>>>>>>>>>>>>>> + msg_log "info: rpz: the ${theList} is empty."
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + rm --verbose "${theZonefile}" # trash the RPZ file
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# check if allow/block list is valid
>>>>>>>>>>>>>>>>> +validate_list () {
>>>>>>>>>>>>>>>>> + local theName="${1}" # allow or block
>>>>>>>>>>>>>>>>> + local theList="/var/ipfire/dns/rpz/${theName}list" # input custom list of domains
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # remove good:
>>>>>>>>>>>>>>>>> + # - properly formated domain names with or without leading wildcard
>>>>>>>>>>>>>>>>> + # - properly formated top level domain (TLD) names with wildcard
>>>>>>>>>>>>>>>>> + # - blank lines and comment lines
>>>>>>>>>>>>>>>>> + # remaining lines are considered "bad"
>>>>>>>>>>>>>>>>> + bad_lines=$( sed --regexp-extended \
>>>>>>>>>>>>>>>>> + '/^(\*\.)?([a-zA-Z0-9](([a-zA-Z0-9\-]){0,61}[a-zA-Z0-9])?\.)+([a-zA-Z]{2,}|xn--[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])$/d ;
>>>>>>>>>>>>>>>>> + /^(\*\.)([a-z]{2,61}|xn--[a-z0-9]{1,60})$/d ;
>>>>>>>>>>>>>>>>> + /^$/d ; /^;/d' "${theList}" )
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + if [[ ! -z "${bad_lines}" ]] ; then
>>>>>>>>>>>>>>>>> + msg_log "error: rpz: invalid line(s) in ${theList}."
>>>>>>>>>>>>>>>>> + printf "%s\n" "bad line(s): ${bad_lines}"
>>>>>>>>>>>>>>>>> + exit 110
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +tagName="unbound"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +rpzName="${1}" # input RPZ name
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +rpzLog="yes" # log default is yes
>>>>>>>>>>>>>>>>> +ucReload="yes" # reload default is yes
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>>>>>>>>> + case "$1" in
>>>>>>>>>>>>>>>>> + --no-log ) rpzLog="no" ;;
>>>>>>>>>>>>>>>>> + --no-reload ) ucReload="no" ; checkConf="no" ;;
>>>>>>>>>>>>>>>>> + esac
>>>>>>>>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +case "${rpzName}" in
>>>>>>>>>>>>>>>>> + # make a new allow or block rpz file
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + allow )
>>>>>>>>>>>>>>>>> + validate_list 'allow' # is the allowlist valid?
>>>>>>>>>>>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>>>>>>>>>>>> + make_rpz_file 'allow'
>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + allowblock )
>>>>>>>>>>>>>>>>> + validate_list 'allow' # is the list valid?
>>>>>>>>>>>>>>>>> + make_allow_config "${rpzLog}"
>>>>>>>>>>>>>>>>> + make_rpz_file 'allow'
>>>>>>>>>>>>>>>>> + ;&
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + block )
>>>>>>>>>>>>>>>>> + validate_list 'block' # is the blocklist valid?
>>>>>>>>>>>>>>>>> + make_block_config "${rpzLog}"
>>>>>>>>>>>>>>>>> + make_rpz_file 'block'
>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + reload )
>>>>>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + unbound-restart )
>>>>>>>>>>>>>>>>> + check_unbound_conf "${checkConf}"
>>>>>>>>>>>>>>>>> + unbound_restart
>>>>>>>>>>>>>>>>> + exit
>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + * )
>>>>>>>>>>>>>>>>> + msg_log "error: rpz: missing or incorrect parameter"
>>>>>>>>>>>>>>>>> + printf "Usage: $(basename "$0") <NAME> <OPTION> <OPTION>\n"
>>>>>>>>>>>>>>>>> + printf "Version: ${version}\n"
>>>>>>>>>>>>>>>>> + exit 108
>>>>>>>>>>>>>>>>> + ;;
>>>>>>>>>>>>>>>>> +esac
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +exit
>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-metrics b/config/rpz/rpz-metrics
>>>>>>>>>>>>>>>>> new file mode 100755
>>>>>>>>>>>>>>>>> index 000000000..4d43e1629
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-metrics
>>>>>>>>>>>>>>>>> @@ -0,0 +1,170 @@
>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +version="2025-01-20 - v25"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +weeks="2" # default to two message logs
>>>>>>>>>>>>>>>>> +sortBy="name" # default "by name"
>>>>>>>>>>>>>>>>> +rpzActive="enabled" # default "enabled only"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>>>>>>>>>>> + case "$1" in
>>>>>>>>>>>>>>>>> + --by-names | --by-name | name ) sortBy="name" ;;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + --by-hits | --by-hit | hits | hit ) sortBy="hit" ;;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + --by-lines | --by-line | lines | line ) sortBy="line" ;;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + --by-effect ) sortBy="effect" ;;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + --enabled-only ) rpzActive="enabled" ;;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + --active-all | --all | all ) rpzActive="all" ;;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + [0-9] | [0-9][0-9] ) weeks=$1 ;;
>>>>>>>>>>>>>>>>> + esac
>>>>>>>>>>>>>>>>> + shift # Shift after checking all the cases to get next option
>>>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# get the list of message logs for N weeks
>>>>>>>>>>>>>>>>> +messageLogs=$( find /var/log/messages* -type f | sort --version-sort |
>>>>>>>>>>>>>>>>> + head -"${weeks}" )
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# get the list of RPZ names & counts from the message log(s)
>>>>>>>>>>>>>>>>> +rpzNameCount=$( for logf in ${messageLogs} ; do
>>>>>>>>>>>>>>>>> + zgrep --text --fixed-strings 'info: rpz: applied' "${logf}" |
>>>>>>>>>>>>>>>>> + awk '$10 ~ /\[\w*]/ { print $10 }' ;
>>>>>>>>>>>>>>>>> + done | sort | uniq --count )
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# flip results and remove brackets `[` and `]`
>>>>>>>>>>>>>>>>> +rpzNameCount=$( echo "${rpzNameCount}" |
>>>>>>>>>>>>>>>>> + awk '{ print $2, $1 }' |
>>>>>>>>>>>>>>>>> + sed --regexp-extended 's|^\[(.*)\]|\1|' )
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# grab only names
>>>>>>>>>>>>>>>>> +rpzNames=$( echo "${rpzNameCount}" | awk '{ print $1 }' )
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# get list of RPZ files
>>>>>>>>>>>>>>>>> +rpzFileList=$( find /etc/unbound/zonefiles -type f -iname "*.rpz" )
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# get basename of those files
>>>>>>>>>>>>>>>>> +rpzBaseNames=$( echo "${rpzFileList}" |
>>>>>>>>>>>>>>>>> + sed 's|/etc/unbound/zonefiles/||g ; s|\.rpz||g ;' )
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# add to rpzNames
>>>>>>>>>>>>>>>>> +rpzNames="${rpzNames}"$'\n'"${rpzBaseNames}"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# drop duplicate names
>>>>>>>>>>>>>>>>> +rpzNames=$( echo "${rpzNames}" | sort --unique )
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# get line count for each RPZ
>>>>>>>>>>>>>>>>> +lineCount=$( echo "${rpzFileList}" | xargs wc -l )
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# get comment line count and blank line count for each RPZ
>>>>>>>>>>>>>>>>> +commentCount=$( echo "${rpzFileList}" | xargs grep --count -e "^$" -e "^;" )
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# get modified date each RPZ
>>>>>>>>>>>>>>>>> +modDateList=$( echo "${rpzFileList}" | xargs stat -c '%.10y %n' )
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +ucListAuthZones=$( unbound-control list_auth_zones )
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# get width of RPZ names
>>>>>>>>>>>>>>>>> +pWidth=$( echo "${rpzNames}" | awk '{ print $1" " }' | wc -L )
>>>>>>>>>>>>>>>>> +pFormat="%-${pWidth}s %-8s %-8s %8s %12s %12s\n"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# print title line
>>>>>>>>>>>>>>>>> +printf "${pFormat}" "name" "hits" "active" "lines" " hits/line" "last download"
>>>>>>>>>>>>>>>>> +printf -- "--------------"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +theResults=""
>>>>>>>>>>>>>>>>> +totalLines=0
>>>>>>>>>>>>>>>>> +totalHits=0
>>>>>>>>>>>>>>>>> +while read -r theName
>>>>>>>>>>>>>>>>> +do
>>>>>>>>>>>>>>>>> + printf -- "--" # pretend progress bar
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # is this RPZ list active?
>>>>>>>>>>>>>>>>> + theActive="disabled"
>>>>>>>>>>>>>>>>> + if grep --quiet "^${theName}\.rpz" <<< "${ucListAuthZones}"
>>>>>>>>>>>>>>>>> + then
>>>>>>>>>>>>>>>>> + theActive="enabled"
>>>>>>>>>>>>>>>>> + else
>>>>>>>>>>>>>>>>> + [[ "${rpzActive}" == enabled ]] && continue
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # get hit count
>>>>>>>>>>>>>>>>> + theHits="0"
>>>>>>>>>>>>>>>>> + if output=$( grep "^${theName}\s" <<< "${rpzNameCount}" ) ; then
>>>>>>>>>>>>>>>>> + theHits=$( echo "${output}" | awk '{ print $2 }' )
>>>>>>>>>>>>>>>>> + totalHits=$(( totalHits + theHits ))
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # get line count
>>>>>>>>>>>>>>>>> + theLines="n/a"
>>>>>>>>>>>>>>>>> + hitsPerLine="0"
>>>>>>>>>>>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${lineCount}" ) ; then
>>>>>>>>>>>>>>>>> + theLines=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>>>>>>>>>>>> + totalLines=$(( totalLines + theLines ))
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + if [[ "${theLines}" -gt 2 ]] ; then
>>>>>>>>>>>>>>>>> + hitsPerLine=$(( 100 * theHits / theLines ))
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # get modification date
>>>>>>>>>>>>>>>>> + theModDate="n/a"
>>>>>>>>>>>>>>>>> + if output=$( grep --fixed-strings "/${theName}.rpz" <<< "${modDateList}" ) ; then
>>>>>>>>>>>>>>>>> + theModDate=$( echo "${output}" | awk '{ print $1 }' )
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # add to results list
>>>>>>>>>>>>>>>>> + theResults+="${theName} ${theHits} ${theActive} ${theLines} ${hitsPerLine} ${theModDate}"$'\n'
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +done <<< "${rpzNames}"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +case "${sortBy}" in
>>>>>>>>>>>>>>>>> + # sort by "active" then by "name"
>>>>>>>>>>>>>>>>> + name) sortArg=(-k3,3r -k1,1) ;;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # sort by "active" then by "hits" then by "name"
>>>>>>>>>>>>>>>>> + hit) sortArg=(-k3,3r -k2,2nr -k1,1) ;;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # sort by "active" then by "lines" then by "name"
>>>>>>>>>>>>>>>>> + line) sortArg=(-k3,3r -k4,4nr -k1,1) ;;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # sort by "active" then by "effect" then by "name"
>>>>>>>>>>>>>>>>> + effect) sortArg=(-k3,3r -k5,5nr -k1,1) ;;
>>>>>>>>>>>>>>>>> +esac
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +printf -- "--------------\n"
>>>>>>>>>>>>>>>>> +# remove blank lines, sort, print as columns
>>>>>>>>>>>>>>>>> +echo "${theResults}" |
>>>>>>>>>>>>>>>>> + awk '!/^[[:space:]]*$/' |
>>>>>>>>>>>>>>>>> + sort "${sortArg[@]}" |
>>>>>>>>>>>>>>>>> + awk --assign=width="${pWidth}" \
>>>>>>>>>>>>>>>>> + '{ printf "%-*s %-8s %-8s %8s %10s %% %12s\n", width, $1, $2, $3, $4, $5, $6 }'
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +printf "${pFormat}" "" "=======" "" "========" "" ""
>>>>>>>>>>>>>>>>> +printf "${pFormat}" "Totals -->" "${totalHits}" "" "${totalLines}" "" ""
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +exit
>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz-sleep b/config/rpz/rpz-sleep
>>>>>>>>>>>>>>>>> new file mode 100755
>>>>>>>>>>>>>>>>> index 000000000..dd3603599
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz-sleep
>>>>>>>>>>>>>>>>> @@ -0,0 +1,58 @@
>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +version="2024-08-16" # v05
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +############### Functions ###############
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# send message to message log
>>>>>>>>>>>>>>>>> +msg_log () {
>>>>>>>>>>>>>>>>> + logger --tag "${tagName}" "$*"
>>>>>>>>>>>>>>>>> + if tty --silent ; then
>>>>>>>>>>>>>>>>> + echo "${tagName}:" "$*"
>>>>>>>>>>>>>>>>> + fi
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +############### Main ###############
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +tagName="unbound"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +sleepTime="${1:-5m}" # default to sleep for 5m (5 minutes)
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +zoneList=$( unbound-control list_auth_zones | awk '{print $1}' )
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +for zone in ${zoneList} ; do
>>>>>>>>>>>>>>>>> + printf "disable ${zone}\t"
>>>>>>>>>>>>>>>>> + unbound-control rpz_disable "${zone}"
>>>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +msg_log "info: rpz: disabled all zones for ${sleepTime}"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +sleep "${sleepTime}"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +for zone in ${zoneList} ; do
>>>>>>>>>>>>>>>>> + printf "enable ${zone}\t"
>>>>>>>>>>>>>>>>> + unbound-control rpz_enable "${zone}"
>>>>>>>>>>>>>>>>> +done
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +msg_log "info: rpz: enabled all zones"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +exit
>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.de.pl b/config/rpz/rpz.de.pl
>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>> index 000000000..3770c6bb0
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.de.pl
>>>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>>>> +'rpz apply' => 'Übernehmen',
>>>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Benutzerdefinierte Allowlist aktivieren:',
>>>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Zugelassene Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>> +'rpz cl allow' => 'Benutzerdefinierte Allowlist',
>>>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Benutzerdefinierte Blocklist aktivieren:',
>>>>>>>>>>>>>>>>> +'rpz cl block info' => 'Gesperrte Domains (eine pro Zeile)<br>Beispiel: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>> +'rpz cl block' => 'Benutzerdefinierte Blocklist',
>>>>>>>>>>>>>>>>> +'rpz cl' => 'Benutzerdefinierte Listen',
>>>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'Der Name enthält unzulässige Zeichen',
>>>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf hat eine fehlerhafte Konfiguration ermittelt. Führen Sie das Kommando unbound-checkconf auf der Konsole aus, um weitere Informationen zu erhalten.',
>>>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'Die benutzerdefinierte Allow-/Blocklist ist leer',
>>>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'Ein Eintrag mit identischem Namen existiert bereits',
>>>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'Die URL ist ungültig',
>>>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'Eintrag kann nicht entfernt werden, der Name existiert nicht',
>>>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'Der Name ist ungültig - nur "allow" oder "block" möglich',
>>>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'Fehlende oder inkorrekte Parameter',
>>>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control reload ist fehlgeschlagen',
>>>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'Die benutzerdefinierte Allow-/Blocklist enthält unzulässige Einträge',
>>>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'Die Anmerkung enthält unzulässige Zeichen',
>>>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'Ungültiger Eintrag in der benutzerdefinierten Allowlist, Zeile ',
>>>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'Ungültiger Eintrag in der benutzerdefinierten Blocklist, Zeile ',
>>>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'Ausgewählter Eintrag existiert nicht: ',
>>>>>>>>>>>>>>>>> +'rpz zf editor' => 'Zonendatei-Eintrag bearbeiten',
>>>>>>>>>>>>>>>>> +'rpz zf imported' => '(importiert aus rpz-config)',
>>>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Erlaubte Zeichen sind a-z, A-Z, 0-9 und Unterstriche',
>>>>>>>>>>>>>>>>> +'rpz zf' => 'Zonendateien',
>>>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.en.pl b/config/rpz/rpz.en.pl
>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>> index 000000000..0720a8940
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.en.pl
>>>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>>>> +'rpz apply' => 'Apply',
>>>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Enable custom allowlist:',
>>>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Allowed domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>> +'rpz cl allow' => 'Custom allowlist',
>>>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Enable custom blocklist:',
>>>>>>>>>>>>>>>>> +'rpz cl block info' => 'Blocked domains (one per line)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>> +'rpz cl block' => 'Custom blocklist',
>>>>>>>>>>>>>>>>> +'rpz cl' => 'Custom lists',
>>>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'the NAME is not valid',
>>>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf found invalid configuration. In the Terminal run the command unbound-checkconf for more information',
>>>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'the allow/block list is empty',
>>>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'duplicate - NAME already exists',
>>>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'the URL is not valid',
>>>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'cannot remove the NAME does not exist',
>>>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'the NAME is not valid - "allow" or "block" only',
>>>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'missing or incorrect parameter',
>>>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control reload failed',
>>>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'custom Allowlist/Blocklist contains invalid entries',
>>>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'the REMARK is not valid',
>>>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'invalid entry in allowlist, line ',
>>>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'invalid entry in blocklist, line ',
>>>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'Selected entry does not exist: ',
>>>>>>>>>>>>>>>>> +'rpz zf editor' => 'Edit zonefiles entry',
>>>>>>>>>>>>>>>>> +'rpz zf imported' => '(imported from rpz-config)',
>>>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Valid characters are a-z, A-Z, 0-9 and underscore.',
>>>>>>>>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.es.pl b/config/rpz/rpz.es.pl
>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>> index 000000000..98628e4aa
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.es.pl
>>>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>>>> +'rpz' => 'Zonas de política de respuesta (RPZ)',
>>>>>>>>>>>>>>>>> +'rpz apply' => 'Aplicar',
>>>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Habilitar la lista blanca personalizada:',
>>>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Dominio permitido (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>> +'rpz cl allow' => 'Lista blanca personalizada',
>>>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Habilitar la lista negra personalizada:',
>>>>>>>>>>>>>>>>> +'rpz cl block info' => 'Dominio bloqueado (uno por línea)<br>Ejemplo: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>> +'rpz cl block' => 'Lista negra personalizada',
>>>>>>>>>>>>>>>>> +'rpz cl' => 'Lista personalizada',
>>>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'El NOMBRE no es válido',
>>>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha encontrado una configuración no válida. Desde Terminal, ejecute el comando unbound-checkconf para mayor información',
>>>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'La lista de permitidos/bloqueados está vacía',
>>>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'duplicado - NOMBRE ya existe',
>>>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'la URL no es válida',
>>>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'no es posible eliminar el NOMBRE que no existe',
>>>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'el NOMBRE no es válido - sólo "permitir" o "bloquear"',
>>>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'parámetro faltante o incorrecto',
>>>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'Error al recargar unbound-control',
>>>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'la Lista blanca/Lista negra personalizada contiene entradas no válidas',
>>>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'el COMENTARIO no es válido',
>>>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'entrada no válida en la lista blanca, línea ',
>>>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'entrada no válida en la lista negra, línea ',
>>>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'La entrada seleccionada no existe: ',
>>>>>>>>>>>>>>>>> +'rpz zf editor' => 'Editar la entrada de archivos de zona',
>>>>>>>>>>>>>>>>> +'rpz zf imported' => '(importado de rpz-config)',
>>>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Los caracteres válidos son a-z, A-Z, 0-9 y guión bajo',
>>>>>>>>>>>>>>>>> +'rpz zf' => 'Archivos de zona',
>>>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.fr.pl b/config/rpz/rpz.fr.pl
>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>> index 000000000..f35f3c2d0
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.fr.pl
>>>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>>>> +'rpz apply' => 'Appliquer',
>>>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Activer la liste d\'autorisations personnalisée:',
>>>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Domaines autorisés (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>> +'rpz cl allow' => 'Liste d\'autorisations personnalisée',
>>>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Activer la liste de blocage personnalisée:',
>>>>>>>>>>>>>>>>> +'rpz cl block info' => 'Domaines bloqués (un par ligne)<br>Example: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>> +'rpz cl block' => 'liste de blocage personnalisé',
>>>>>>>>>>>>>>>>> +'rpz cl' => 'Listes personnalisées',
>>>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'le NOM n\'est pas valide',
>>>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf configuration non valide trouvée. Dans le terminal, exécutez la commande unbound-checkconf pour plus d\'informations',
>>>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'la liste autoriser/bloquer est vide',
>>>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'le NOM existe déjà',
>>>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'L\'URL n\'est pas valide',
>>>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'impossible de supprimer le NOM n\'existe pas',
>>>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'le NOM n\'est pas valide - « autoriser » ou « bloquer » seulement',
>>>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'paramètre manquant ou incorrect',
>>>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control rechargement échoué',
>>>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'la liste autoriser/bloquer contient des entrées non valides',
>>>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'la REMARQUE n\'est pas valable',
>>>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'entrée non valide dans la liste d\'autorisation, ligne ',
>>>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'entrée non valide dans la liste de blocs, ligne ',
>>>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'L\'entrée sélectionnée n\'existe pas: ',
>>>>>>>>>>>>>>>>> +'rpz zf editor' => 'Modifier l\'entrée Fichiers Zone',
>>>>>>>>>>>>>>>>> +'rpz zf imported' => '(importé de rpz-config)',
>>>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Les caractères valides sont a-z, A-Z, 0-9 et soulignement.',
>>>>>>>>>>>>>>>>> +'rpz zf' => 'Fichiers Zone',
>>>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.it.pl b/config/rpz/rpz.it.pl
>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>> index 000000000..ee81605c9
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.it.pl
>>>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>>>> +# Added for Response Policy Zone (RPZ) add-on
>>>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>>>> +'rpz apply' => 'Applica',
>>>>>>>>>>>>>>>>> +'rpz cl allow enable' => 'Abilita la Whitelist personalizzata:',
>>>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Domini consentiti (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>> +'rpz cl allow' => 'Whitelist personalizzata',
>>>>>>>>>>>>>>>>> +'rpz cl block enable' => 'Abilita la Blacklist personalizzata:',
>>>>>>>>>>>>>>>>> +'rpz cl block info' => 'Domini bloccati (uno per riga)<br>Esempio: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>> +'rpz cl block' => 'Blacklist personalizzata',
>>>>>>>>>>>>>>>>> +'rpz cl' => 'Liste personalizzate',
>>>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'il NOME non è valido',
>>>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ha trovato una configurazione non valida. Dal Terminale esegui il comando unbound-checkconf per maggiori informazioni',
>>>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'l\'elenco consentiti/bloccati è vuoto',
>>>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'duplicato - NAME esiste di già',
>>>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'l\'URL non è valido',
>>>>>>>>>>>>>>>>> +'rpz exitcode 106' => 'non è possibile rimuovere il NOME non esiste',
>>>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'il NOME non è valido - solo "consenti" o "blocca"',
>>>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'parametro mancante o non corretto',
>>>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'ricaricamento del controllo non associato non riuscito',
>>>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'la Whitelist/Blacklist personalizzata contiene voci non valide',
>>>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'l"OSSERVAZIONE non è valida',
>>>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'voce non valida nella Whitelist, riga ',
>>>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'voce non valida nella Blacklist, riga ',
>>>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'La voce selezionata non esiste: ',
>>>>>>>>>>>>>>>>> +'rpz zf editor' => 'Modifica la voce dei file di zona',
>>>>>>>>>>>>>>>>> +'rpz zf imported' => '(importato da rpz-config)',
>>>>>>>>>>>>>>>>> +'rpz zf remark info' => 'I caratteri validi sono a-z, A-Z, 0-9 e trattino basso',
>>>>>>>>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>>>> diff --git a/config/rpz/rpz.tr.pl b/config/rpz/rpz.tr.pl
>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>> index 000000000..00226e192
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/config/rpz/rpz.tr.pl
>>>>>>>>>>>>>>>>> @@ -0,0 +1,30 @@
>>>>>>>>>>>>>>>>> +# I�in eklendi Ayriyeten Response Policy Zone (RPZ)
>>>>>>>>>>>>>>>>> +%tr = (%tr,
>>>>>>>>>>>>>>>>> +'rpz' => 'Response Policy Zones (RPZ)',
>>>>>>>>>>>>>>>>> +'rpz apply' => 'Uygulamak',
>>>>>>>>>>>>>>>>> +'rpz cl allow enable' => '�zel Etkinlestir allowlist:',
>>>>>>>>>>>>>>>>> +'rpz cl allow info' => 'Izin verilmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>> +'rpz cl allow' => 'Etkinlestir allowlist',
>>>>>>>>>>>>>>>>> +'rpz cl block enable' => '�zel Etkinlestir blocklist:',
>>>>>>>>>>>>>>>>> +'rpz cl block info' => 'Engellenmis domains (satir basina bir)<br>�rnegin: domain.com, *.domain.com',
>>>>>>>>>>>>>>>>> +'rpz cl block' => 'Engellenmis blocklist',
>>>>>>>>>>>>>>>>> +'rpz cl' => 'Engellenmis lists',
>>>>>>>>>>>>>>>>> +'rpz exitcode 101' => 'NAME ge�erli degil',
>>>>>>>>>>>>>>>>> +'rpz exitcode 102' => 'unbound-checkconf ge�ersiz yapilandirma bulundu. Terminalde daha fazla bilgi i�in Unbound-checkConf komutunu �alistirin',
>>>>>>>>>>>>>>>>> +'rpz exitcode 103' => 'allow/block liste bos',
>>>>>>>>>>>>>>>>> +'rpz exitcode 104' => 'kopyalamak - NAME zaten var',
>>>>>>>>>>>>>>>>> +'rpz exitcode 105' => 'URL ge�erli degil',
>>>>>>>>>>>>>>>>> +'rpz exitcode 106' => '�ikarilamiyor NAME yok',
>>>>>>>>>>>>>>>>> +'rpz exitcode 107' => 'NAME ge�erli degil - "allow" veya "block" yalniz',
>>>>>>>>>>>>>>>>> +'rpz exitcode 108' => 'Parametre eksik veya yanlis',
>>>>>>>>>>>>>>>>> +'rpz exitcode 109' => 'unbound-control basarisiz',
>>>>>>>>>>>>>>>>> +'rpz exitcode 110' => 'Engellenmis Allowlist/Blocklist ge�ersiz girisler i�erir',
>>>>>>>>>>>>>>>>> +'rpz exitcode 201' => 'REMARK ge�erli degil',
>>>>>>>>>>>>>>>>> +'rpz exitcode 202' => 'Ge�ersiz giris allowlist, line ',
>>>>>>>>>>>>>>>>> +'rpz exitcode 203' => 'Ge�ersiz giris blocklist, line ',
>>>>>>>>>>>>>>>>> +'rpz exitcode 204' => 'Se�ilen giris yok: ',
>>>>>>>>>>>>>>>>> +'rpz zf editor' => 'yazimlamak zonefiles giris',
>>>>>>>>>>>>>>>>> +'rpz zf imported' => '(ithal edildi rpz-config)',
>>>>>>>>>>>>>>>>> +'rpz zf remark info' => 'Ge�erli karakterler a-z, A-Z, 0-9ve alt�st.',
>>>>>>>>>>>>>>>>> +'rpz zf' => 'Zonefiles',
>>>>>>>>>>>>>>>>> +);
>>>>>>>>>>>>>>>>> diff --git a/html/cgi-bin/rpz.cgi b/html/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>> index 000000000..a821c92ac
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/html/cgi-bin/rpz.cgi
>>>>>>>>>>>>>>>>> @@ -0,0 +1,923 @@
>>>>>>>>>>>>>>>>> +#!/usr/bin/perl
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>> +# Copyright (C) 2005-2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +use strict;
>>>>>>>>>>>>>>>>> +use Scalar::Util qw(looks_like_number);
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# debugging
>>>>>>>>>>>>>>>>> +#use warnings;
>>>>>>>>>>>>>>>>> +#use CGI::Carp 'fatalsToBrowser';
>>>>>>>>>>>>>>>>> +#use Data::Dumper;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +require '/var/ipfire/general-functions.pl';
>>>>>>>>>>>>>>>>> +require "${General::swroot}/lang.pl";
>>>>>>>>>>>>>>>>> +require "${General::swroot}/header.pl";
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +###--- Extra HTML ---###
>>>>>>>>>>>>>>>>> +my $extraHead = <<END
>>>>>>>>>>>>>>>>> +<style>
>>>>>>>>>>>>>>>>> + /* alternating row background */
>>>>>>>>>>>>>>>>> + .tbl tr:nth-child(2n+2) {
>>>>>>>>>>>>>>>>> + background-color: var(--color-light-grey);
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + .tbl tr:nth-child(2n+3) {
>>>>>>>>>>>>>>>>> + background-color: var(--color-grey);
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + /* text styles */
>>>>>>>>>>>>>>>>> + .tbl th:not(:last-child) {
>>>>>>>>>>>>>>>>> + text-align: left;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + div.right {
>>>>>>>>>>>>>>>>> + text-align: right;
>>>>>>>>>>>>>>>>> + margin-top: 0.5em;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + /* customlist input */
>>>>>>>>>>>>>>>>> + textarea.domainlist {
>>>>>>>>>>>>>>>>> + margin: 0.5em 0;
>>>>>>>>>>>>>>>>> + resize: vertical;
>>>>>>>>>>>>>>>>> + min-height: 10em;
>>>>>>>>>>>>>>>>> + overflow: auto;
>>>>>>>>>>>>>>>>> + white-space: pre;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + button[type=submit]:disabled {
>>>>>>>>>>>>>>>>> + opacity: 0.6;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +</style>
>>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>> +###--- End of extra HTML ---###
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +### Settings ###
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Request DNS service reload after configuration change
>>>>>>>>>>>>>>>>> +my $RPZ_RELOAD_FLAG = "${General::swroot}/dns/rpz/reload.flag";
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Configuration file for all available zonefiles
>>>>>>>>>>>>>>>>> +# Format: index, name (unique), enabled (on/off), URL, remark
>>>>>>>>>>>>>>>>> +my $ZONEFILES_CONF = "${General::swroot}/dns/rpz/zonefiles.conf";
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Configuration file for custom lists
>>>>>>>>>>>>>>>>> +# IDs: 0=allowlist, 1=blocklist, 2=options (allow/block enabled)
>>>>>>>>>>>>>>>>> +my $CUSTOMLISTS_CONF = "${General::swroot}/dns/rpz/customlists.conf";
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Export custom lists to rpz-config
>>>>>>>>>>>>>>>>> +my $RPZ_ALLOWLIST = "${General::swroot}/dns/rpz/allowlist";
>>>>>>>>>>>>>>>>> +my $RPZ_BLOCKLIST = "${General::swroot}/dns/rpz/blocklist";
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +### Preparation ###
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Create missing config files
>>>>>>>>>>>>>>>>> +unless(-f $ZONEFILES_CONF) { &General::system('touch', "$ZONEFILES_CONF"); }
>>>>>>>>>>>>>>>>> +unless(-f $CUSTOMLISTS_CONF) { &General::system('touch', "$CUSTOMLISTS_CONF"); }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +## Global gui data
>>>>>>>>>>>>>>>>> +my $errormessage = "";
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +## Global configuration data
>>>>>>>>>>>>>>>>> +my %zonefiles = ();
>>>>>>>>>>>>>>>>> +my %customlists = ();
>>>>>>>>>>>>>>>>> +&_zonefiles_load();
>>>>>>>>>>>>>>>>> +&_customlists_load();
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +## Global CGI form data
>>>>>>>>>>>>>>>>> +my %cgiparams = ();
>>>>>>>>>>>>>>>>> +&Header::getcgihash(\%cgiparams);
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +my $action = $cgiparams{'ACTION'} // 'NONE';
>>>>>>>>>>>>>>>>> +my $action_key = $cgiparams{'KEY'} // ''; # entry being edited, empty = none/new
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +###--- Process form actions ---###
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Zonefiles action: Check whether the requested entry exists
>>>>>>>>>>>>>>>>> +if((substr($action, 0, 3) eq 'ZF_') && ($action_key)) {
>>>>>>>>>>>>>>>>> + unless(defined $zonefiles{$action_key}) {
>>>>>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr(204, $action_key);
>>>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +## Perform actions
>>>>>>>>>>>>>>>>> +if($action eq 'ZF_SAVE') { ## Save new or modified zonefiles entry
>>>>>>>>>>>>>>>>> + if(&_action_zf_save()) {
>>>>>>>>>>>>>>>>> + $action = 'NONE'; # success, return to main page
>>>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>>> + $action = 'ZF_EDIT'; # error occured, keep editing
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +} elsif($action eq 'ZF_TOGGLE') { ## Toggle on/off
>>>>>>>>>>>>>>>>> + if(&_action_zf_toggle()) {
>>>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +} elsif($action eq 'ZF_REMOVE') { ## Remove entry
>>>>>>>>>>>>>>>>> + if(&_action_zf_remove()) {
>>>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +} elsif($action eq 'CL_SAVE') { ## Save custom lists
>>>>>>>>>>>>>>>>> + if(&_action_cl_save()) {
>>>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +} elsif($action eq 'RPZ_RELOAD') { ## Reload dns configuration
>>>>>>>>>>>>>>>>> + if(&_action_rpz_reload()) {
>>>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +} elsif($action eq 'UNB_RESTART') { ## Restart unbound service
>>>>>>>>>>>>>>>>> + if(&_action_unb_restart()) {
>>>>>>>>>>>>>>>>> + $action = 'NONE';
>>>>>>>>>>>>>>>>> + &_http_prg_redirect();
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +###--- Start GUI ---###
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +## Start http output
>>>>>>>>>>>>>>>>> +&Header::showhttpheaders();
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Start HTML
>>>>>>>>>>>>>>>>> +&Header::openpage($Lang::tr{'rpz'}, 1, $extraHead);
>>>>>>>>>>>>>>>>> +&Header::openbigbox('100%', 'left', '');
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Show error messages
>>>>>>>>>>>>>>>>> +if($errormessage) {
>>>>>>>>>>>>>>>>> + &_print_message($errormessage);
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Handle zonefile add/edit mode
>>>>>>>>>>>>>>>>> +if($action eq "ZF_EDIT") {
>>>>>>>>>>>>>>>>> + &_print_zonefile_editor();
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Finalize page and exit cleanly
>>>>>>>>>>>>>>>>> + &Header::closebigbox();
>>>>>>>>>>>>>>>>> + &Header::closepage();
>>>>>>>>>>>>>>>>> + exit(0);
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Show gui elements
>>>>>>>>>>>>>>>>> +&_print_zonefiles();
>>>>>>>>>>>>>>>>> +&_print_customlists();
>>>>>>>>>>>>>>>>> +&_print_gui_extras();
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +&Header::closebigbox();
>>>>>>>>>>>>>>>>> +&Header::closepage();
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +###--- End of GUI ---###
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +###--- Internal configuration file functions ---###
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Load all available zonefiles from rpz-config and the internal configuration
>>>>>>>>>>>>>>>>> +sub _zonefiles_load {
>>>>>>>>>>>>>>>>> + # Clean start
>>>>>>>>>>>>>>>>> + %zonefiles = ();
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Source 1: Get the currently enabled zonefiles from rpz-config (expected format [name]=[URL])
>>>>>>>>>>>>>>>>> + my @enabled_files = &General::system_output('/usr/sbin/rpz-config', 'list');
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + foreach my $row (@enabled_files) {
>>>>>>>>>>>>>>>>> + chomp($row);
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Use regex instead of split() to skip non-matching lines
>>>>>>>>>>>>>>>>> + next unless($row =~ /^(\w+)=(.+)$/);
>>>>>>>>>>>>>>>>> + my ($name, $url) = ($1, $2);
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Unique names are already guaranteed by rpz-config
>>>>>>>>>>>>>>>>> + if(&_rpz_validate_zonefile($name, $url, '', 0) == 0) {
>>>>>>>>>>>>>>>>> + # Populate global data hash, mark all found entries as enabled
>>>>>>>>>>>>>>>>> + my %entry = ('enabled' => 'on',
>>>>>>>>>>>>>>>>> + 'url' => $url,
>>>>>>>>>>>>>>>>> + 'remark' => $Lang::tr{'rpz zf imported'});
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Source 2: Get additional data and disabled entries from configuration file
>>>>>>>>>>>>>>>>> + my %configured_files = ();
>>>>>>>>>>>>>>>>> + &General::readhasharray($ZONEFILES_CONF, \%configured_files);
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + foreach my $row (values (%configured_files)) {
>>>>>>>>>>>>>>>>> + my ($name, $enabled, $url, $remark) = @$row;
>>>>>>>>>>>>>>>>> + $remark //= "";
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + next unless($name);
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Check whether this row belongs to an entry already imported from rpz-config
>>>>>>>>>>>>>>>>> + if(defined $zonefiles{$name}) {
>>>>>>>>>>>>>>>>> + # Existing entry, only merge additional data
>>>>>>>>>>>>>>>>> + $zonefiles{$name}{'remark'} = $remark;
>>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>>> + # Skip entry if it is marked as enabled but not found by rpz-config. It was then deleted manually
>>>>>>>>>>>>>>>>> + if($enabled ne 'on') {
>>>>>>>>>>>>>>>>> + # Populate global data hash
>>>>>>>>>>>>>>>>> + my %entry = ('enabled' => 'off',
>>>>>>>>>>>>>>>>> + 'url' => $url // "",
>>>>>>>>>>>>>>>>> + 'remark' => $remark);
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Save internal zonefiles configuration
>>>>>>>>>>>>>>>>> +sub _zonefiles_save_conf {
>>>>>>>>>>>>>>>>> + my $index = 0;
>>>>>>>>>>>>>>>>> + my %export = ();
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Loop trough all zonefiles and create "hasharray" type export
>>>>>>>>>>>>>>>>> + foreach my $name (keys %zonefiles) {
>>>>>>>>>>>>>>>>> + my @entry = ($name,
>>>>>>>>>>>>>>>>> + $zonefiles{$name}{'enabled'},
>>>>>>>>>>>>>>>>> + $zonefiles{$name}{'url'},
>>>>>>>>>>>>>>>>> + $zonefiles{$name}{'remark'});
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + $export{$index++} = \@entry;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + &General::writehasharray($ZONEFILES_CONF, \%export);
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Load custom lists from rpz-config and the internal configuration
>>>>>>>>>>>>>>>>> +sub _customlists_load {
>>>>>>>>>>>>>>>>> + # Clean start
>>>>>>>>>>>>>>>>> + %customlists = ();
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Load configuration file
>>>>>>>>>>>>>>>>> + my %lists_conf = ();
>>>>>>>>>>>>>>>>> + &General::readhasharray($CUSTOMLISTS_CONF, \%lists_conf);
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Get list options, enabled by default to start import
>>>>>>>>>>>>>>>>> + $customlists{'allow'}{'enabled'} = $lists_conf{2}[0] // 'on';
>>>>>>>>>>>>>>>>> + $customlists{'block'}{'enabled'} = $lists_conf{2}[1] // 'on';
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Import enabled list from rpz-config, otherwise retrieve stored or empty list from configuration file
>>>>>>>>>>>>>>>>> + if($customlists{'allow'}{'enabled'} eq 'on') {
>>>>>>>>>>>>>>>>> + &_customlist_import('allow', $RPZ_ALLOWLIST);
>>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>>> + $customlists{'allow'}{'list'} = $lists_conf{0} // [];
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + if($customlists{'block'}{'enabled'} eq 'on') {
>>>>>>>>>>>>>>>>> + &_customlist_import('block', $RPZ_BLOCKLIST);
>>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>>> + $customlists{'block'}{'list'} = $lists_conf{1} // [];
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Save internal custom lists configuration
>>>>>>>>>>>>>>>>> +sub _customlists_save_conf {
>>>>>>>>>>>>>>>>> + my %export = ();
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Match IDs with import function
>>>>>>>>>>>>>>>>> + $export{0} = $customlists{'allow'}{'list'};
>>>>>>>>>>>>>>>>> + $export{1} = $customlists{'block'}{'list'};
>>>>>>>>>>>>>>>>> + $export{2} = [$customlists{'allow'}{'enabled'}, $customlists{'block'}{'enabled'}];
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + &General::writehasharray($CUSTOMLISTS_CONF, \%export);
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Import a custom list from plain file, returns empty list if file is missing
>>>>>>>>>>>>>>>>> +sub _customlist_import {
>>>>>>>>>>>>>>>>> + my ($listname, $filename) = @_;
>>>>>>>>>>>>>>>>> + my @list = ();
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # File exists, load and check all lines
>>>>>>>>>>>>>>>>> + if(-f $filename) {
>>>>>>>>>>>>>>>>> + open(my $FH, '<', $filename) or die "Can't read $filename: $!";
>>>>>>>>>>>>>>>>> + while(my $line = <$FH>) {
>>>>>>>>>>>>>>>>> + chomp($line);
>>>>>>>>>>>>>>>>> + push(@list, $line);
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + close($FH);
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Clean up imported data
>>>>>>>>>>>>>>>>> + &_rpz_validate_customlist(\@list, 1);
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + $customlists{$listname}{'list'} = \@list;
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Export a custom list to plain file or clear file if list is disabled
>>>>>>>>>>>>>>>>> +sub _customlist_export {
>>>>>>>>>>>>>>>>> + my ($listname, $filename) = @_;
>>>>>>>>>>>>>>>>> + return unless(defined $customlists{$listname});
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Write enabled domain list to file, otherwise save empty file
>>>>>>>>>>>>>>>>> + open(my $FH, '>', $filename) or die "Can't write $filename: $!";
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + if($customlists{$listname}{'enabled'} eq 'on') {
>>>>>>>>>>>>>>>>> + foreach my $line (@{$customlists{$listname}{'list'}}) {
>>>>>>>>>>>>>>>>> + print $FH "$line\n";
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>>> + print $FH "; Note: This list is currently disabled by $ENV{'SCRIPT_NAME'}\n";
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + close($FH);
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +###--- Internal gui functions ---###
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Show simple message box
>>>>>>>>>>>>>>>>> +sub _print_message {
>>>>>>>>>>>>>>>>> + my ($message, $title) = @_;
>>>>>>>>>>>>>>>>> + $title ||= $Lang::tr{'error messages'};
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $title);
>>>>>>>>>>>>>>>>> + print "<span>$message</span>";
>>>>>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Show all zone files and related gui elements
>>>>>>>>>>>>>>>>> +sub _print_zonefiles {
>>>>>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf'});
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>>>> +<table class="tbl" width="100%">
>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>> + <th>$Lang::tr{'name'}</th>
>>>>>>>>>>>>>>>>> + <th>URL</th>
>>>>>>>>>>>>>>>>> + <th>$Lang::tr{'remark'}</th>
>>>>>>>>>>>>>>>>> + <th colspan="3">$Lang::tr{'action'}</th>
>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Sort zonefiles by name and loop trough all entries
>>>>>>>>>>>>>>>>> + foreach my $name (sort keys %zonefiles) {
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Toggle button label translation
>>>>>>>>>>>>>>>>> + my $toggle_tr = ($zonefiles{$name}{'enabled'} eq 'on') ? $Lang::tr{'click to disable'} : $Lang::tr{'click to enable'};
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>> + <td>$name</td>
>>>>>>>>>>>>>>>>> + <td>$zonefiles{$name}{'url'}</td>
>>>>>>>>>>>>>>>>> + <td>$zonefiles{$name}{'remark'}</td>
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_TOGGLE">
>>>>>>>>>>>>>>>>> + <input type="image" src="/images/$zonefiles{$name}{'enabled'}.gif" title="$toggle_tr" alt="$toggle_tr">
>>>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_EDIT">
>>>>>>>>>>>>>>>>> + <input type="image" src="/images/edit.gif" title="$Lang::tr{'edit'}" alt="$Lang::tr{'edit'}">
>>>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>>>>>> + <td align="center" width="5%">
>>>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="$name">
>>>>>>>>>>>>>>>>> + <input type="hidden" name="ACTION" value="ZF_REMOVE">
>>>>>>>>>>>>>>>>> + <input type="image" src="/images/delete.gif" title="$Lang::tr{'remove'}" alt="$Lang::tr{'remove'}">
>>>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Disable reload button if not needed
>>>>>>>>>>>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>>>> +</table>
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +<div class="right">
>>>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>>> + <input type="hidden" name="KEY" value="">
>>>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="ZF_EDIT">$Lang::tr{'add'}</button>
>>>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>>>> +</div>
>>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Show zonefiles entry editor
>>>>>>>>>>>>>>>>> +sub _print_zonefile_editor {
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Key specified: Edit existing entry
>>>>>>>>>>>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>>>>>>>>>>>> + # Load data to be edited, but don't override already present values (allows user to edit after error)
>>>>>>>>>>>>>>>>> + $cgiparams{'ZF_NAME'} //= $action_key;
>>>>>>>>>>>>>>>>> + $cgiparams{'ZF_URL'} //= $zonefiles{$action_key}{'url'};
>>>>>>>>>>>>>>>>> + $cgiparams{'ZF_REMARK'} //= $zonefiles{$action_key}{'remark'};
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Fallback to empty form
>>>>>>>>>>>>>>>>> + $cgiparams{'ZF_NAME'} //= "";
>>>>>>>>>>>>>>>>> + $cgiparams{'ZF_URL'} //= "";
>>>>>>>>>>>>>>>>> + $cgiparams{'ZF_REMARK'} //= "";
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf editor'});
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>>> +<input type="hidden" name="KEY" value="$action_key">
>>>>>>>>>>>>>>>>> +<table width="100%">
>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>> + <td width="20%">$Lang::tr{'name'}: <img src="/blob.gif" alt="*"></td>
>>>>>>>>>>>>>>>>> + <td><input type="text" name="ZF_NAME" value="$cgiparams{'ZF_NAME'}" size="40" maxlength="32" title="$Lang::tr{'rpz zf remark info'}" pattern="[a-zA-Z0-9_]{1,32}" required></td>
>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>> + <td width="20%">URL: <img src="/blob.gif" alt="*"></td>
>>>>>>>>>>>>>>>>> + <td><input type="url" name="ZF_URL" value="$cgiparams{'ZF_URL'}" size="40" maxlength="128" required></td>
>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>> + <td width="20%">$Lang::tr{'remark'}:</td>
>>>>>>>>>>>>>>>>> + <td><input type="text" name="ZF_REMARK" value="$cgiparams{'ZF_REMARK'}" size="40" maxlength="32"></td>
>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>> + <td colspan="2"><hr></td>
>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>> + <td width="55%"><img src="/blob.gif" alt="*"> $Lang::tr{'required field'}</td>
>>>>>>>>>>>>>>>>> + <td align="right"><button type="submit" name="ACTION" value="ZF_SAVE">$Lang::tr{'save'}</button></td>
>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>> +</table>
>>>>>>>>>>>>>>>>> +</form>
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +<div class="right">
>>>>>>>>>>>>>>>>> + <form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="NONE">$Lang::tr{'back'}</button>
>>>>>>>>>>>>>>>>> + </form>
>>>>>>>>>>>>>>>>> +</div>
>>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Show custom allow/block files and related gui elements
>>>>>>>>>>>>>>>>> +sub _print_customlists {
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Load lists from config, unless they are currently being edited
>>>>>>>>>>>>>>>>> + if($action ne 'CL_SAVE') {
>>>>>>>>>>>>>>>>> + $cgiparams{'ALLOW_LIST'} = join("\n", @{$customlists{'allow'}{'list'}});
>>>>>>>>>>>>>>>>> + $cgiparams{'BLOCK_LIST'} = join("\n", @{$customlists{'block'}{'list'}});
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + $cgiparams{'ALLOW_ENABLED'} = ($customlists{'allow'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>>>>>>>>>>>> + $cgiparams{'BLOCK_ENABLED'} = ($customlists{'block'}{'enabled'} eq 'on') ? 'on' : undef;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Fallback to empty form
>>>>>>>>>>>>>>>>> + $cgiparams{'ALLOW_LIST'} //= "";
>>>>>>>>>>>>>>>>> + $cgiparams{'BLOCK_LIST'} //= "";
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # HTML checkboxes, unchecked = no or undef value in POST data
>>>>>>>>>>>>>>>>> + my %checked = ();
>>>>>>>>>>>>>>>>> + $checked{'ALLOW_ENABLED'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? " checked" : "";
>>>>>>>>>>>>>>>>> + $checked{'BLOCK_ENABLED'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? " checked" : "";
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Disable reload button if not needed
>>>>>>>>>>>>>>>>> + my $reload_state = &_rpz_needs_reload() ? "" : " disabled";
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz cl'});
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>>>> +<form method="post" action="$ENV{'SCRIPT_NAME'}">
>>>>>>>>>>>>>>>>> +<table width="100%">
>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl allow'}</b><br>$Lang::tr{'rpz cl allow info'}</td>
>>>>>>>>>>>>>>>>> + <td colspan="2"><b>$Lang::tr{'rpz cl block'}</b><br>$Lang::tr{'rpz cl block info'}</td>
>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>> + <td colspan="2"><textarea name="ALLOW_LIST" class="domainlist" cols="45">
>>>>>>>>>>>>>>>>> +$cgiparams{'ALLOW_LIST'}</textarea></td>
>>>>>>>>>>>>>>>>> + <td colspan="2"><textarea name="BLOCK_LIST" class="domainlist" cols="45">
>>>>>>>>>>>>>>>>> +$cgiparams{'BLOCK_LIST'}</textarea></td>
>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>> + <td><label for="allow_enabled">$Lang::tr{'rpz cl allow enable'}</label></td>
>>>>>>>>>>>>>>>>> + <td width="15%"><input type="checkbox" name="ALLOW_ENABLED" id="allow_enabled"$checked{'ALLOW_ENABLED'}></td>
>>>>>>>>>>>>>>>>> + <td><label for="block_enabled">$Lang::tr{'rpz cl block enable'}</label></td>
>>>>>>>>>>>>>>>>> + <td width="15%"><input type="checkbox" name="BLOCK_ENABLED" id="block_enabled"$checked{'BLOCK_ENABLED'}></td>
>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>> + <td colspan="4"><hr></td>
>>>>>>>>>>>>>>>>> + </tr>
>>>>>>>>>>>>>>>>> + <tr>
>>>>>>>>>>>>>>>>> + <td align="right" colspan="4">
>>>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="CL_SAVE">$Lang::tr{'save'}</button>
>>>>>>>>>>>>>>>>> + <button type="submit" name="ACTION" value="RPZ_RELOAD" class="commit"$reload_state>$Lang::tr{'rpz apply'}</button>
>>>>>>>>>>>>>>>>> + </td>
>>>>>>>>>>>>>>>>> +</table>
>>>>>>>>>>>>>>>>> +</form>
>>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + &Header::closebox();
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Output javascript and extra gui elements
>>>>>>>>>>>>>>>>> +sub _print_gui_extras {
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Apply/Restart button modifier key handler
>>>>>>>>>>>>>>>>> + if(&_rpz_needs_reload()) {
>>>>>>>>>>>>>>>>> + print <<END
>>>>>>>>>>>>>>>>> +<script>
>>>>>>>>>>>>>>>>> + // Commit modifier key handler
>>>>>>>>>>>>>>>>> + (function(jq, document) {
>>>>>>>>>>>>>>>>> + var keyEventsOn = false; // Keyboard events attached
>>>>>>>>>>>>>>>>> + var keyModify = false; // Modifier key pressed
>>>>>>>>>>>>>>>>> + var mouseHover = false; // Mouse over commit button
>>>>>>>>>>>>>>>>> + var btnModified = false; // Button modified to "Restart"
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + // Document-level key events, enable only while cursor is over button
>>>>>>>>>>>>>>>>> + function attachKeyEvents() {
>>>>>>>>>>>>>>>>> + if(keyEventsOn) {
>>>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + keyEventsOn = true;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + jq(document).on("keydown.rpz", function(event) {
>>>>>>>>>>>>>>>>> + if((!keyModify) && event.shiftKey) {
>>>>>>>>>>>>>>>>> + keyModify = true;
>>>>>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>>>> + jq(document).on("keyup.rpz", function(event) {
>>>>>>>>>>>>>>>>> + if(keyModify && (!event.shiftKey)) {
>>>>>>>>>>>>>>>>> + keyModify = false;
>>>>>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + function removeKeyEvents() {
>>>>>>>>>>>>>>>>> + keyModify = false;
>>>>>>>>>>>>>>>>> + if(keyEventsOn) {
>>>>>>>>>>>>>>>>> + jq(document).off("keydown.rpz keyup.rpz");
>>>>>>>>>>>>>>>>> + keyEventsOn = false;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + // Attach mouse hover events to commit buttons
>>>>>>>>>>>>>>>>> + function attachMouseEvents() {
>>>>>>>>>>>>>>>>> + jq("button.commit").on("mouseenter", function(event) {
>>>>>>>>>>>>>>>>> + if(!mouseHover) {
>>>>>>>>>>>>>>>>> + mouseHover = true;
>>>>>>>>>>>>>>>>> + attachKeyEvents();
>>>>>>>>>>>>>>>>> + // Handle already pressed key
>>>>>>>>>>>>>>>>> + keyModify = !!(event.shiftKey);
>>>>>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + // Cursor moved away: Disable key listener to minimize events
>>>>>>>>>>>>>>>>> + jq("button.commit").on("mouseleave", function() {
>>>>>>>>>>>>>>>>> + if(mouseHover) {
>>>>>>>>>>>>>>>>> + mouseHover = false;
>>>>>>>>>>>>>>>>> + removeKeyEvents();
>>>>>>>>>>>>>>>>> + handleModify();
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + // Modify commit button
>>>>>>>>>>>>>>>>> + function handleModify() {
>>>>>>>>>>>>>>>>> + let modify = mouseHover && keyModify;
>>>>>>>>>>>>>>>>> + if(btnModified != modify) {
>>>>>>>>>>>>>>>>> + if(modify) {
>>>>>>>>>>>>>>>>> + jq("button.commit").text("$Lang::tr{'restart'}").val("UNB_RESTART");
>>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>>> + jq("button.commit").text("$Lang::tr{'rpz apply'}").val("RPZ_RELOAD");
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + btnModified = modify;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + // jQuery DOM ready
>>>>>>>>>>>>>>>>> + jq(function() {
>>>>>>>>>>>>>>>>> + attachMouseEvents();
>>>>>>>>>>>>>>>>> + });
>>>>>>>>>>>>>>>>> + })(jQuery, document);
>>>>>>>>>>>>>>>>> +</script>
>>>>>>>>>>>>>>>>> +END
>>>>>>>>>>>>>>>>> +;
>>>>>>>>>>>>>>>>> + } # End of modifier key handler
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +###--- Internal action processing functions ---###
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Toggle zonefile on/off
>>>>>>>>>>>>>>>>> +sub _action_zf_toggle {
>>>>>>>>>>>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + my $result = 0;
>>>>>>>>>>>>>>>>> + my $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Perform toggle action
>>>>>>>>>>>>>>>>> + if($enabled eq 'on') {
>>>>>>>>>>>>>>>>> + $enabled = 'off';
>>>>>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>>>>>>>>>>>> + } else {
>>>>>>>>>>>>>>>>> + $enabled = 'on';
>>>>>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $action_key, $zonefiles{$action_key}{'url'}, '--no-reload');
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Save changes
>>>>>>>>>>>>>>>>> + $zonefiles{$action_key}{'enabled'} = $enabled;
>>>>>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Remove zonefile
>>>>>>>>>>>>>>>>> +sub _action_zf_remove {
>>>>>>>>>>>>>>>>> + return unless(defined $zonefiles{$action_key});
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Remove from rpz-config if currently active
>>>>>>>>>>>>>>>>> + if($zonefiles{$action_key}{'enabled'} eq 'on') {
>>>>>>>>>>>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'remove', $action_key, '--no-reload');
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Remove from data hash and save changes
>>>>>>>>>>>>>>>>> + delete $zonefiles{$action_key};
>>>>>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Clear action_key, as the entry is now removed entirely
>>>>>>>>>>>>>>>>> + $action_key = "";
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Create or update zonefile entry
>>>>>>>>>>>>>>>>> +# Returns undef if gui needs to stay in editor mode
>>>>>>>>>>>>>>>>> +sub _action_zf_save {
>>>>>>>>>>>>>>>>> + my $result = 0;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + my $name = $cgiparams{'ZF_NAME'} // "";
>>>>>>>>>>>>>>>>> + my $url = $cgiparams{'ZF_URL'} // "";
>>>>>>>>>>>>>>>>> + my $remark = $cgiparams{'ZF_REMARK'} // "";
>>>>>>>>>>>>>>>>> + my $enabled = 'on'; # Enable new entries by default
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Note on variables:
>>>>>>>>>>>>>>>>> + # name = unique key, will be used to address the entry
>>>>>>>>>>>>>>>>> + # action_key = name of the entry being edited, empty for new entry
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Only check for unique name if it changed
>>>>>>>>>>>>>>>>> + # (this also checks new entries because the action_key is empty in this case)
>>>>>>>>>>>>>>>>> + $result = &_rpz_validate_zonefile($name, $url, $remark, (lc($name) ne lc($action_key)));
>>>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 0);
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Edit existing entry: Determine what was changed
>>>>>>>>>>>>>>>>> + if(($action_key) && (defined $zonefiles{$action_key})) {
>>>>>>>>>>>>>>>>> + # Name und URL remain unchanged, only save remark and finish
>>>>>>>>>>>>>>>>> + if(($name eq $action_key) && ($url eq $zonefiles{$action_key}{'url'})) {
>>>>>>>>>>>>>>>>> + $zonefiles{$action_key}{'remark'} = $remark;
>>>>>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Entry was changed and needs to be recreated, preserve status
>>>>>>>>>>>>>>>>> + $enabled = $zonefiles{$action_key}{'enabled'};
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Remove from rpz-config
>>>>>>>>>>>>>>>>> + return unless &_action_zf_remove();
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Add new entry to rpz-config
>>>>>>>>>>>>>>>>> + if($enabled eq 'on') {
>>>>>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-config', 'add', $name, $url, '--no-reload');
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Check for errors, request service reload on success
>>>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Add to global data hash and save changes
>>>>>>>>>>>>>>>>> + my %entry = ('enabled' => $enabled,
>>>>>>>>>>>>>>>>> + 'url' => $url,
>>>>>>>>>>>>>>>>> + 'remark' => $remark);
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + $zonefiles{$name} = \%entry;
>>>>>>>>>>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Save custom lists
>>>>>>>>>>>>>>>>> +sub _action_cl_save {
>>>>>>>>>>>>>>>>> + return unless((defined $cgiparams{'ALLOW_LIST'}) && (defined $cgiparams{'BLOCK_LIST'}));
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + my $result = 0;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + my @allowlist = split(/\R/, $cgiparams{'ALLOW_LIST'});
>>>>>>>>>>>>>>>>> + my @blocklist = split(/\R/, $cgiparams{'BLOCK_LIST'});
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Validate lists
>>>>>>>>>>>>>>>>> + $result = &_rpz_validate_customlist(\@allowlist);
>>>>>>>>>>>>>>>>> + if($result != 0) {
>>>>>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr(202, $result);
>>>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + $result = &_rpz_validate_customlist(\@blocklist);
>>>>>>>>>>>>>>>>> + if($result != 0) {
>>>>>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr(203, $result);
>>>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Add to global data hash and save changes
>>>>>>>>>>>>>>>>> + $customlists{'allow'}{'list'} = \@allowlist;
>>>>>>>>>>>>>>>>> + $customlists{'block'}{'list'} = \@blocklist;
>>>>>>>>>>>>>>>>> + $customlists{'allow'}{'enabled'} = (defined $cgiparams{'ALLOW_ENABLED'}) ? 'on' : 'off';
>>>>>>>>>>>>>>>>> + $customlists{'block'}{'enabled'} = (defined $cgiparams{'BLOCK_ENABLED'}) ? 'on' : 'off';
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + &_customlists_save_conf();
>>>>>>>>>>>>>>>>> + &_customlist_export('allow', $RPZ_ALLOWLIST);
>>>>>>>>>>>>>>>>> + &_customlist_export('block', $RPZ_BLOCKLIST);
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Make new lists, request service reload on success
>>>>>>>>>>>>>>>>> + $result = &General::system('/usr/sbin/rpz-make', 'allowblock', '--no-reload');
>>>>>>>>>>>>>>>>> + return unless &_rpz_check_result($result, 1);
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Trigger rpz-config reload
>>>>>>>>>>>>>>>>> +sub _action_rpz_reload {
>>>>>>>>>>>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Immediately clear flag to prevent multiple reloads
>>>>>>>>>>>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>>>>>>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Perform reload, recreate reload flag on error to enable retry
>>>>>>>>>>>>>>>>> + my $result = &General::system('/usr/sbin/rpz-config', 'reload');
>>>>>>>>>>>>>>>>> + if(not &_rpz_check_result($result, 0)) {
>>>>>>>>>>>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Trigger unbound restart
>>>>>>>>>>>>>>>>> +sub _action_unb_restart {
>>>>>>>>>>>>>>>>> + return 1 unless &_rpz_needs_reload();
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Immediately clear flag to prevent multiple restarts
>>>>>>>>>>>>>>>>> + if(-f $RPZ_RELOAD_FLAG) {
>>>>>>>>>>>>>>>>> + unlink($RPZ_RELOAD_FLAG) or die "Can't remove $RPZ_RELOAD_FLAG: $!";
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Perform restart, unboundctrl always exits zero
>>>>>>>>>>>>>>>>> + &General::system('/usr/local/bin/unboundctrl', 'restart');
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +###--- Internal rpz-config functions ---###
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Translate rpz-config exitcodes and messages
>>>>>>>>>>>>>>>>> +# 100-199: rpz-config, 200-299: webgui
>>>>>>>>>>>>>>>>> +sub _rpz_error_tr {
>>>>>>>>>>>>>>>>> + my ($error, $append) = @_;
>>>>>>>>>>>>>>>>> + $append //= '';
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Translate numeric exit codes
>>>>>>>>>>>>>>>>> + if(looks_like_number($error)) {
>>>>>>>>>>>>>>>>> + if(defined $Lang::tr{"rpz exitcode $error"}) {
>>>>>>>>>>>>>>>>> + $error = $Lang::tr{"rpz exitcode $error"};
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + return "RPZ $Lang::tr{'error'}: $error" . &Header::escape($append);
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Check result of rpz-config system call, request reload on success
>>>>>>>>>>>>>>>>> +sub _rpz_check_result {
>>>>>>>>>>>>>>>>> + my ($result, $request_reload) = @_;
>>>>>>>>>>>>>>>>> + $request_reload //= 0;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # exitcode 0 = success
>>>>>>>>>>>>>>>>> + if($result != 0) {
>>>>>>>>>>>>>>>>> + $errormessage = &_rpz_error_tr($result);
>>>>>>>>>>>>>>>>> + return;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Set reload flag
>>>>>>>>>>>>>>>>> + if($request_reload) {
>>>>>>>>>>>>>>>>> + &General::system('touch', "$RPZ_RELOAD_FLAG");
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + return 1;
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Test whether reload flag is set
>>>>>>>>>>>>>>>>> +sub _rpz_needs_reload {
>>>>>>>>>>>>>>>>> + return (-f $RPZ_RELOAD_FLAG);
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Validate a zonefile entry, returns rpz-config exitcode on failure. Use _rpz_check_result to verify.
>>>>>>>>>>>>>>>>> +# unique = check for unique name
>>>>>>>>>>>>>>>>> +sub _rpz_validate_zonefile {
>>>>>>>>>>>>>>>>> + my ($name, $url, $remark, $unique) = @_;
>>>>>>>>>>>>>>>>> + $unique //= 1;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + unless($name =~ /^[a-zA-Z0-9_]{1,32}$/) {
>>>>>>>>>>>>>>>>> + return 101;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + unless($url =~ /^[\w+\.:;\/\\&@#%?=\-~|!]{1,128}$/) {
>>>>>>>>>>>>>>>>> + return 105;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + unless($remark =~ /^[\w \-()\.:;*\/\\?!&=]{0,32}$/) {
>>>>>>>>>>>>>>>>> + return 201;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Check against already existing names
>>>>>>>>>>>>>>>>> + if($unique) {
>>>>>>>>>>>>>>>>> + foreach my $existing (keys %zonefiles) {
>>>>>>>>>>>>>>>>> + if(lc($name) eq lc($existing)) {
>>>>>>>>>>>>>>>>> + return 104;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + return 0;
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Validate a custom list, returns number of rejected line on failure. Check for non-zero results.
>>>>>>>>>>>>>>>>> +# listref = array reference, cleanup = remove invalid entries instead of returning an error
>>>>>>>>>>>>>>>>> +sub _rpz_validate_customlist {
>>>>>>>>>>>>>>>>> + my ($listref, $cleanup) = @_;
>>>>>>>>>>>>>>>>> + $cleanup //= 0;
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + foreach my $index (reverse 0..$#{$listref}) {
>>>>>>>>>>>>>>>>> + my $row = @$listref[$index];
>>>>>>>>>>>>>>>>> + next unless($row); # Skip/allow empty lines
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Reject/remove everything besides wildcard domains and remarks
>>>>>>>>>>>>>>>>> + if((not &General::validwildcarddomainname($row)) && (not $row =~ /^;[\w \-()\.:;*\/\\?!&=]*$/)) {
>>>>>>>>>>>>>>>>> + unless($cleanup) {
>>>>>>>>>>>>>>>>> + # +1 for user friendly line number and to ensure non-zero exitcode
>>>>>>>>>>>>>>>>> + return $index + 1;
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Remove current row
>>>>>>>>>>>>>>>>> + splice(@$listref, $index, 1);
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> + }
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + return 0;
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +###--- Internal misc functions ---###
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Send HTTP 303 redirect headers for post/request/get pattern
>>>>>>>>>>>>>>>>> +# (Must be sent before calling &Header::showhttpheaders())
>>>>>>>>>>>>>>>>> +sub _http_prg_redirect {
>>>>>>>>>>>>>>>>> + my $location = "https://$ENV{'SERVER_NAME'}:$ENV{'SERVER_PORT'}$ENV{'SCRIPT_NAME'}";
>>>>>>>>>>>>>>>>> + print "Status: 303 See Other\n";
>>>>>>>>>>>>>>>>> + print "Location: $location\n";
>>>>>>>>>>>>>>>>> +}
>>>>>>>>>>>>>>>>> diff --git a/lfs/rpz b/lfs/rpz
>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>> index 000000000..7ddbc38e5
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/lfs/rpz
>>>>>>>>>>>>>>>>> @@ -0,0 +1,96 @@
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +# Definitions
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +include Config
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +SUMMARY = response policy zone - RPZ reputation system for unbound DNS
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +VER = 1.0.0
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +THISAPP = rpz-$(VER)
>>>>>>>>>>>>>>>>> +DIR_APP = $(DIR_SRC)/$(THISAPP)
>>>>>>>>>>>>>>>>> +TARGET = $(DIR_INFO)/$(THISAPP)
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +PROG = rpz
>>>>>>>>>>>>>>>>> +PAK_VER = 18
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +DEPS =
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +SERVICES =
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +# Top-level Rules
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +install : $(TARGET)
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +check :
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +download :
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +b2 :
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +dist:
>>>>>>>>>>>>>>>>> + @$(PAK)
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +# Installation Details
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +$(TARGET) :
>>>>>>>>>>>>>>>>> + @$(PREBUILD)
>>>>>>>>>>>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # RPZ scripts
>>>>>>>>>>>>>>>>> + install --verbose --mode=755 \
>>>>>>>>>>>>>>>>> + $(DIR_CONF)/rpz/{rpz-config,rpz-metrics,rpz-sleep,rpz-make,rpz-functions} \
>>>>>>>>>>>>>>>>> + --target-directory=/usr/sbin
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # RPZ config files
>>>>>>>>>>>>>>>>> + mkdir -pv /etc/unbound/local.d
>>>>>>>>>>>>>>>>> + install --verbose --mode=644 --owner=nobody --group=nobody \
>>>>>>>>>>>>>>>>> + $(DIR_CONF)/rpz/00-rpz.conf \
>>>>>>>>>>>>>>>>> + --target-directory=/etc/unbound/local.d
>>>>>>>>>>>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/local.d
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # RPZ custom list files for allow and block
>>>>>>>>>>>>>>>>> + mkdir -pv /var/ipfire/dns/rpz
>>>>>>>>>>>>>>>>> + touch /var/ipfire/dns/rpz/{allowlist,blocklist}
>>>>>>>>>>>>>>>>> + chown --verbose --recursive nobody:nobody /var/ipfire/dns/rpz
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # RPZ zone files
>>>>>>>>>>>>>>>>> + # create empty RPZ config file to avoid a unbound config error
>>>>>>>>>>>>>>>>> + mkdir -pv /etc/unbound/zonefiles
>>>>>>>>>>>>>>>>> + touch /etc/unbound/zonefiles/allow.rpz
>>>>>>>>>>>>>>>>> + chown --verbose --recursive nobody:nobody /etc/unbound/zonefiles
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Install addon-specific language-files
>>>>>>>>>>>>>>>>> + install --verbose --mode=004 $(DIR_CONF)/rpz/rpz.*.pl \
>>>>>>>>>>>>>>>>> + --target-directory=/var/ipfire/addon-lang
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + # Install backup definition
>>>>>>>>>>>>>>>>> + cp -vf $(DIR_CONF)/backup/includes/rpz /var/ipfire/backup/addons/includes/rpz
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>>>>>>>>>>>> + @$(POSTBUILD)
>>>>>>>>>>>>>>>>> diff --git a/make.sh b/make.sh
>>>>>>>>>>>>>>>>> index 827ea9e77..a77535b13 100755
>>>>>>>>>>>>>>>>> --- a/make.sh
>>>>>>>>>>>>>>>>> +++ b/make.sh
>>>>>>>>>>>>>>>>> @@ -390,7 +390,7 @@ prepareenv() {
>>>>>>>>>>>>>>>>> if [ "${free_space}" -lt "${required_space}" ]; then
>>>>>>>>>>>>>>>>> # Add any consumed space
>>>>>>>>>>>>>>>>> while read -r consumed_space path; do
>>>>>>>>>>>>>>>>> - (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>>>>>>>>>>>> + (( free_space += consumed_space / 1024 / 1024 ))
>>>>>>>>>>>>>>>>> done <<< "$(du --summarize --bytes "${BUILD_DIR}" "${IMAGES_DIR}" "${LOG_DIR}" 2>/dev/null)"
>>>>>>>>>>>>>>>>> fi
>>>>>>>>>>>>>>>>> @@ -2087,6 +2087,7 @@ build_system() {
>>>>>>>>>>>>>>>>> lfsmake2 btrfs-progs
>>>>>>>>>>>>>>>>> lfsmake2 inotify-tools
>>>>>>>>>>>>>>>>> lfsmake2 grub-btrfs
>>>>>>>>>>>>>>>>> + lfsmake2 rpz
>>>>>>>>>>>>>>>>> lfsmake2 linux
>>>>>>>>>>>>>>>>> lfsmake2 rtl8812au
>>>>>>>>>>>>>>>>> diff --git a/src/paks/rpz/install.sh b/src/paks/rpz/install.sh
>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>> index 000000000..ef99bf742
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/src/paks/rpz/install.sh
>>>>>>>>>>>>>>>>> @@ -0,0 +1,36 @@
>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +#
>>>>>>>>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>>>>>>>>> +extract_files
>>>>>>>>>>>>>>>>> +restore_backup ${NAME}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# fix user created files
>>>>>>>>>>>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>>>>>>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>>>>>>>>>>>> + /etc/unbound/zonefiles \
>>>>>>>>>>>>>>>>> + /etc/unbound/local.d
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Update Language cache
>>>>>>>>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# restart unbound to load config file
>>>>>>>>>>>>>>>>> +/etc/init.d/unbound restart
>>>>>>>>>>>>>>>>> diff --git a/src/paks/rpz/uninstall.sh b/src/paks/rpz/uninstall.sh
>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>> index 000000000..e11427df3
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/src/paks/rpz/uninstall.sh
>>>>>>>>>>>>>>>>> @@ -0,0 +1,38 @@
>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +#
>>>>>>>>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>>>>>>>>>>>> +/etc/init.d/unbound stop
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +make_backup ${NAME}
>>>>>>>>>>>>>>>>> +remove_files
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>>>>>>>>>>>> +# "[1723428668] unbound-control[17117:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>>>>>>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Update Language cache
>>>>>>>>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# start unbound to load unbound config file
>>>>>>>>>>>>>>>>> +/etc/init.d/unbound start
>>>>>>>>>>>>>>>>> diff --git a/src/paks/rpz/update.sh b/src/paks/rpz/update.sh
>>>>>>>>>>>>>>>>> new file mode 100644
>>>>>>>>>>>>>>>>> index 000000000..9bc340bc6
>>>>>>>>>>>>>>>>> --- /dev/null
>>>>>>>>>>>>>>>>> +++ b/src/paks/rpz/update.sh
>>>>>>>>>>>>>>>>> @@ -0,0 +1,52 @@
>>>>>>>>>>>>>>>>> +#!/bin/bash
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# IPFire.org - A linux based firewall #
>>>>>>>>>>>>>>>>> +# Copyright (C) 2024 IPFire Team <info@ipfire.org> #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +# 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 <http://www.gnu.org/licenses/>. #
>>>>>>>>>>>>>>>>> +# #
>>>>>>>>>>>>>>>>> +###############################################################################
>>>>>>>>>>>>>>>>> +#
>>>>>>>>>>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# from update.sh
>>>>>>>>>>>>>>>>> +extract_backup_includes
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# stop unbound to delete RPZ conf file
>>>>>>>>>>>>>>>>> +/etc/init.d/unbound stop
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# from uninstall.sh
>>>>>>>>>>>>>>>>> +make_backup ${NAME}
>>>>>>>>>>>>>>>>> +remove_files
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# delete rpz config files. Otherwise unbound will throw error:
>>>>>>>>>>>>>>>>> +# "unbound-control[nn:0] error: connect: Connection refused for 127.0.0.1 port 8953"
>>>>>>>>>>>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# from install.sh
>>>>>>>>>>>>>>>>> +extract_files
>>>>>>>>>>>>>>>>> +restore_backup ${NAME}
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# fix user created files
>>>>>>>>>>>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>>>>>>>>>>>> + /var/ipfire/dns/rpz \
>>>>>>>>>>>>>>>>> + /etc/unbound/zonefiles \
>>>>>>>>>>>>>>>>> + /etc/unbound/local.d
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# Update Language cache
>>>>>>>>>>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>>>>>>>>>>> +
>>>>>>>>>>>>>>>>> +# restart unbound to load config files
>>>>>>>>>>>>>>>>> +/etc/init.d/unbound start
>>>>>>>>>>>>>>>>> --
>>>>>>>>>>>>>>>>> 2.39.5
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> Jon
>>>>>>>>>>>>>>> --
>>>>>>>>>>>>>>> Jon Murphy
>>>>>>>>>>>>>>> jon.murphy@ipfire.org
>>>>>>>>>>>>>
>>>>>>>>>>>>> Jon
>>>>>>>>>>>>> --
>>>>>>>>>>>>> Jon Murphy
>>>>>>>>>>>>> jon.murphy@ipfire.org
>
>
^ permalink raw reply [flat|nested] 30+ messages in thread
end of thread, other threads:[~2025-03-24 14:49 UTC | newest]
Thread overview: 30+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-02-06 16:35 [PATCH] RPZ: update code to include WEBGUI and additional languages Jon Murphy
2025-02-06 19:35 ` Bernhard Bitsch
2025-02-06 20:13 ` Michael Tremer
2025-02-08 18:41 ` jon
2025-02-08 19:27 ` Michael Tremer
[not found] ` <C28A0D7E-6C16-4F6F-9366-A8498F40631E@ipfire.org>
2025-02-14 12:07 ` Michael Tremer
2025-02-14 12:58 ` Bernhard Bitsch
2025-02-14 13:52 ` Michael Tremer
2025-02-14 14:16 ` Bernhard Bitsch
2025-03-01 10:18 ` Adolf Belka
[not found] ` <3BF29525-C9F4-4FD2-834D-FBE791E99E8C@ipfire.org>
2025-03-02 10:51 ` Adolf Belka
2025-03-10 17:47 ` jon
2025-03-16 17:00 ` Jon Murphy
2025-03-17 10:35 ` Michael Tremer
2025-03-19 2:58 ` Jon Murphy
2025-03-19 10:35 ` Michael Tremer
2025-03-19 18:22 ` Jon Murphy
[not found] ` <afcb2a99-1281-43e3-bd3d-d915024683f6@ipfire.org>
2025-03-20 16:26 ` Michael Tremer
2025-03-24 0:00 ` Re[2]: " Jon Murphy
2025-03-24 10:17 ` Michael Tremer
2025-03-24 13:33 ` Bernhard Bitsch
2025-03-24 14:25 ` Michael Tremer
2025-03-24 14:33 ` Re[2]: " Jon Murphy
2025-03-24 14:36 ` Michael Tremer
2025-03-24 14:38 ` Re[2]: " Jon Murphy
2025-03-24 14:40 ` Michael Tremer
2025-03-24 14:42 ` Re[2]: " Jon Murphy
2025-03-24 14:43 ` Michael Tremer
2025-03-24 14:49 ` Bernhard Bitsch
2025-03-24 14:41 ` Bernhard Bitsch
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox