From: Jon Murphy <jon.murphy@ipfire.org>
To: Michael Tremer <michael.tremer@ipfire.org>
Cc: "IPFire: Development-List" <development@lists.ipfire.org>
Subject: Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
Date: Sun, 16 Mar 2025 12:00:37 -0500 [thread overview]
Message-ID: <8b594873-86ca-46b9-bb4b-94fd6b0239b1@ipfire.org> (raw)
In-Reply-To: <C28A0D7E-6C16-4F6F-9366-A8498F40631E@ipfire.org>
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
>
>
>
>
next prev parent reply other threads:[~2025-03-16 17:00 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
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 [this message]
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=8b594873-86ca-46b9-bb4b-94fd6b0239b1@ipfire.org \
--to=jon.murphy@ipfire.org \
--cc=development@lists.ipfire.org \
--cc=michael.tremer@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