public inbox for development@lists.ipfire.org
 help / color / mirror / Atom feed
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'}:&nbsp;<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:&nbsp;<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="*">&nbsp;$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
>
>
>
>



  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