From: Michael Tremer <michael.tremer@ipfire.org>
To: development@lists.ipfire.org
Subject: Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
Date: Thu, 06 Feb 2025 20:13:01 +0000 [thread overview]
Message-ID: <A0568469-68D8-4C26-9154-E9961F4AAA60@ipfire.org> (raw)
In-Reply-To: <20250206163522.2363178-1-jon.murphy@ipfire.org>
[-- 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
>
next prev parent reply other threads:[~2025-02-06 20:13 UTC|newest]
Thread overview: 30+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-02-06 16:35 Jon Murphy
2025-02-06 19:35 ` Bernhard Bitsch
2025-02-06 20:13 ` Michael Tremer [this message]
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
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=A0568469-68D8-4C26-9154-E9961F4AAA60@ipfire.org \
--to=michael.tremer@ipfire.org \
--cc=development@lists.ipfire.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox