From mboxrd@z Thu Jan  1 00:00:00 1970
From: Bernhard Bitsch <bbitsch@ipfire.org>
To: development@lists.ipfire.org
Subject:
 Re: [PATCH] RPZ: update code to include WEBGUI and additional languages
Date: Fri, 14 Feb 2025 15:16:49 +0100
Message-ID: <7918fcbf-9787-4b55-aa63-09a215c9d294@ipfire.org>
In-Reply-To: <A242CED5-A2BC-498C-B860-41793005478A@ipfire.org>
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="===============1659952175765241016=="
List-Id: <development.lists.ipfire.org>

--===============1659952175765241016==
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: quoted-printable

Hello Michael,
I use the hagezi lists (=20
https://github.com/hagezi/dns-blocklists?tab=3Dreadme-ov-file#zap-dns-blockli=
sts---for-a-better-internet=20
) which allow more selections.

Regards,
Bernhard

Am 14.02.2025 um 14:52 schrieb Michael Tremer:
> Hello Bernhard,
>=20
>> On 14 Feb 2025, at 12:58, Bernhard Bitsch <bbitsch(a)ipfire.org> wrote:
>>
>> Hello Michael,
>>
>> thanks for your clear words. But nevertheless let be comment at some topic=
s.
>>
>> Am 14.02.2025 um 13:07 schrieb Michael Tremer:
>>> Hello Jon,
>>> It very much depends on the kind of contribution. A one-line patch obviou=
sly has fewer strings attached to it than a larger patch set like this.
>>> However, we have outlined the process in the wiki already, starting from =
here:
>>>    https://www.ipfire.org/docs/devel/submit-patches
>>> This contains some useful pointers about the how (how do I actually make =
my changes happen, how do I build IPFire, etc), and at the bottom it contains=
 a lot of information about the format the changes should be submitted; split=
 into smaller chunks that are ideally as independent from each other so that =
they can be individually reviewed and merged. Usually a development process t=
akes long time and we have already shipped parts of code that we will need fo=
r certain features that are not ready yet. This is a good practice to let cod=
e mature, especially when it is touching rather critical bits like the firewa=
ll and networking stacks.
>>> There are also some guidelines on how to write a good commit message and =
how to use Git tags:
>>>    https://www.ipfire.org/docs/devel/git/commit-messages
>>>    https://www.ipfire.org/docs/devel/git/tags
>>> Then there is something about how to get in touch with the right person a=
nd legal stuff:
>>>    https://www.ipfire.org/docs/devel/contact
>>>    https://www.ipfire.org/docs/legal/ipca
>>
>> IMO, this facts are known by Jon. He just did a big part besides this gene=
ral process. Including publishing his efforts to the community for review and=
 test.
>>
>>> Finally, we have a bit of an underused roadmap section on the wiki. It wo=
uld be nice if we could use that a little bit more because then it would be e=
asier for everyone to keep track of progress on certain features; people coul=
d see what is being worked on and see if they can help development and testin=
g and so on:
>>>    https://www.ipfire.org/docs/roadmap
>>> There is a template on how to create new pages:
>>>    https://www.ipfire.org/docs/roadmap/template
>>> And this is a good example of what this could look like:
>>>    https://www.ipfire.org/docs/roadmap/openvpn-26
>>> All of these steps are coming *after* there has been some initial discuss=
ion about what actually has a chance to become part of the distribution. For =
that, we do not have any specific guidelines because it is not very trivial t=
o write these things. There are just too many possibilities. In the past, the=
re has also been very little need for this, but that does not mean that there=
 have not been problems before.
>>> The reason why I am raising the bar this high here is simply that we have=
 made mistakes in the past that we don=E2=80=99t want to repeat. We have lear=
ned a couple of lessons in a not very pleasant way and I under no circumstanc=
es would want to do this again. The objective is that we want to provide an e=
xcellent distribution. Although IPFire of course has its shortcomings here an=
d there, it is a very stable distribution and we have a very good track recor=
d that I want to keep. This is what our users deserve.
>>> In the past, people have =E2=80=9Cdropped=E2=80=9D their patches on this =
list (or sometimes elsewhere) and we were left with dealing with the entire i=
ntegration only to find out later what problems there were hidden in the code=
. The original author(s) had no interest in fixing any of that because it wor=
ked just fine for them, and so why spend any time on the problems of somebody=
 else? Usually I am the fallback for this and I simply don=E2=80=99t want to =
be that. I have lots of my own projects inside IPFire that are moving at snai=
l speed because fixing existing code usually takes priority over writing new =
code.
>>> Therefore we need a commitment to sort out these problems in the first pl=
ace. It has to be proven that people actually *care* about the patches that t=
hey post here. I am not sure this needs writing down as this should be the sa=
me policy with almost any open source project. If you contribute a line, ther=
e is probably less maintenance required in the future, but if you contribute =
a large code base, then you will need to look after it for the foreseeable fu=
ture. It is your feature and not mine after all.
>>
>> Being one of the first testers and revisors, I commit me to support the de=
velopment. Point.
>>
>>> Then, what actually has a chance to make it into the distribution? Probab=
ly not a lot. IPFire has a very clear use case. There will not be any space f=
or a desktop environment and running Chrome on it, we also don=E2=80=99t it t=
o make coffee and cook me a dinner. We would currently only accept things tha=
t were actually maintainable by the current team in case a contributor moves =
on (see above), because we simply only have so much man power. We already hav=
e a large zoo of features that are very abandoned and we are potentially look=
ing at getting rid of more things simply because we cannot support them prope=
rly. Time just doesn=E2=80=99t permit. Adding something large is therefore ve=
ry difficult at the moment.
>>> I understand that in this specific case you have been trying to not invol=
ve the development team and I understand your motivation. But you cannot forg=
et about how much time and effort a review process can take. Therefore we wan=
t to plan things well; we want to even split it; and we want to have a conver=
sation in advance so that the roadmap is clear and the actual code review ide=
ally only becomes a formality.
>>> All of this above has been for a general case. Please read through this a=
nd feel free to ask any questions if something isn=E2=80=99t clear.
>>> To move forward with this feature, we should start by planning a roadmap.=
 We need to discuss what this project should cover and what it should not cov=
er. I believe we don=E2=80=99t need to talk much about implementation details=
 because you have figured out a lot of them; we need to find what feature we =
want to provide to our users. Are you up for that?
>>
>> As a first input to the 'official' discussion, some thoughts.
>> - Usage of encrypted data traffic is increasing. Especially in communicati=
ons not aware to the user. These unwanted traffic is more and more difficult =
to block with IP orientated tools. Even the proxy based solution do not reall=
y function efficient.
>> - To circumvent these problems a widely used approach is DNS filtering. Th=
is comes in two flavours: 1. online providers, 2. local filtering DNS servers=
 like PiHole.
>> - Solution 1 isn't wantable. Many providers store the access data, outside=
 the local network.
>> - Solution 2 installs another DNS server inside the local networks. It isn=
't trivial to ensure that all DNS traffic is handled by this server.
>> - Therefore it is desirable that this filtering is done in the only DNS se=
rver in a IPFire governed network, unbound on the IPFire system. The RPZ func=
tionality allows exactly this.
>> - There are, IMO, good filter lists outside. The updating of these lists i=
s part of the RPZ module. There is some effort left to investigate these and =
to develop recommendations for a good mix of appliance, we do this for the ex=
ternal DNS servers already.
>=20
> You are thinking too much about the *how* here, but we should think of the =
*what* first. What features do we want to offer and what do we not want to of=
fer.
>=20
> Regarding the lists: I am running a test with one client which has about 20=
00 users on their network on a daily basis. There have been *huge* amounts of=
 false positives from the OISD list and it has been occasionally painful to w=
hitelist those. The quality of this feature stands and falls with the quality=
 of the lists that we can feed into this.
>=20
> I have also raised before that those lists collect random stuff together an=
d in the end you only have one list that you can either take or leave. Coming=
 from URL filter, we have some categorisation there like =E2=80=9Cadult=E2=80=
=9D, =E2=80=9Cgambling=E2=80=9D, =E2=80=9Csocial networks=E2=80=9D, and so on=
 which we don=E2=80=99t have here. Would that not be nice to have, too?
>=20
>> Regards,
>> Bernhard
>>
>>> Best,
>>> -Michael
>>>> On 13 Feb 2025, at 21:34, jon <jon.murphy(a)ipfire.org> wrote:
>>>>
>>>> Michael,
>>>>
>>>> I=E2=80=99ve 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 ag=
enda and it has not been pitched by yourself.
>>>>
>>>>
>>>> To me the efforts to get new code accepted seem to have changed and it s=
eemed 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? / Wh=
at 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=E2=80=99t know what exactly=
 is needed.   Please be specific.
>>>>
>>>>
>>>> Jon
>>>>
>>>> PS - I am not ignoring your other comments, I am just trying to move for=
ward and keep things simple.
>>>>
>>>>
>>>>
>>>>> On Feb 8, 2025, at 1:27=E2=80=AFPM, Michael Tremer <michael.tremer(a)ip=
fire.org> wrote:
>>>>>
>>>>> Hello Jon,
>>>>>
>>>>> Thanks for your reply. And good that you are copying everyone into this=
 conversation.
>>>>>
>>>>>> On 8 Feb 2025, at 18:41, jon <jon.murphy(a)ipfire.org> wrote:
>>>>>>
>>>>>> Michael,
>>>>>>
>>>>>>> I think I have covered this all at lengths before that this project h=
as 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 m=
y 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=E2=80=99t that it is accurate that nobody wanted to help on this.=
 The list was always open - although not every email has been replied to swif=
tly 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 worki=
ng on something without involving the core team is a risky undertaking. Of co=
urse 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 invo=
lved. 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, 2=
024, 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 som=
etimes silence.  (Yes, I get it, people are busy.)
>>>>>>
>>>>>> You and Adolf, Leo, Erik and Bernhard have been aware since the beginn=
ing.  You mention you were aware of the "proof-of-concept".  If you include t=
hose beginning posts, since Sep 2023.
>>>>>
>>>>> Yes, I am aware of a proof-of-concept that I have been running myself f=
or a long time. I am also aware of the efforts that you have been taking.
>>>>>
>>>>> Yet I don=E2=80=99t 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 als=
o many posts about this on the forum."
>>>>>>
>>>>>> Please don=E2=80=99t insult me again by stating "you know what I mean".
>>>>>>
>>>>>> And it has been discussed but not documented in the Monthly Meeting no=
tes.
>>>>>
>>>>> I am not at all insulting you. I don=E2=80=99t want to take this down t=
o a personal level at all. This is a public mailing list and people who read =
this don=E2=80=99t 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 thing=
s on the calls, the weather, politics, how our pets are. None of that makes i=
t 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 conversat=
ion.
>>>>>>
>>>>>> Regular conversation on the Dev Mailing list is many times met with si=
lence.  I get it, people are busy.
>>>>>>
>>>>>> And regular two-way conversation doesn=E2=80=99t happen on the list.  =
At least not with me.  I=E2=80=99d 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 nothin=
g 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 involv=
ed in all the things and simply will ignore emails simply based on their subj=
ect 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 h=
ave a great group of people in the Community.  It is a shame you don=E2=80=99=
t want to have others help.  It would reduce your workload.
>>>>>
>>>>> You should stop making statements that are not true. Who doesn=E2=80=99=
t want anyone to help?
>>>>>
>>>>> Not having this conversation on a Saturday evening would reduce my work=
load. At least it would free up time for something else. Helping with the thi=
ngs 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 alre=
ady have a hundred things on the go.
>>>>>
>>>>>>> Therefore, what am I supposed to do with this email?
>>>>>>
>>>>>> To me it is beyond obvious=E2=80=A6
>>>>>>
>>>>>> If it isn=E2=80=99t what you want, then guide me with how to do this t=
he correct way.  And be specific. I am trying to help.  I am trying to make t=
hings better. I am trying to do things the right way.
>>>>>
>>>>> To me it isn=E2=80=99t. This is yet another project that has been dumpe=
d 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 a=
bout it. There is no chance this will continue if this disagreement isn=E2=80=
=99t solved first. I didn=E2=80=99t even look at the code.
>>>>>
>>>>>>> I don=E2=80=99t want to merge code that I don=E2=80=99t 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 a=
gree with.
>>>>>
>>>>>>> So many fundamental things that I have been raising have either not b=
een discussed or outright dismissed.
>>>>>>
>>>>>> You mentioned this a in the past, but for some reason you do not discl=
ose what I dismissed.  Why do you continue to make this harder, wouldn=E2=80=
=99t it not be easier to tell me what I have dismissed?
>>>>>>
>>>>>> I have sent multiple emails trying to answer your concerns and comment=
s.  On July 28, Aug 14, Aug 22, Aug 23, Sep 30, etc.
>>>>>>
>>>>>> I=E2=80=99ve 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 hav=
e stated that this should never be an add-on considering it is supposed to re=
place URL Filter. We should never allow people to add their own sources. I ha=
ve also stated that we cannot download any lists over HTTPS again and again a=
nd again. The implementation that we have here seems to exactly do that and t=
herefore I think that my feedback has been dismissed entirely.
>>>>>
>>>>>>> I don=E2=80=99t want to merge code that has no future inside IPFire a=
s 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=E2=
=80=99s talk!
>>>>>
>>>>> You. I don=E2=80=99t care much about the providers of the lists.
>>>>>
>>>>>> See, this is where it gets confusing.  There are hundreds of open sour=
ce packages as part of IPFire.  Pick the last five years of items added to th=
e IPFire build.  You're telling me you have "constructive conversation with t=
he maintainers" of all of the added packages?
>>>>>
>>>>> They publish their software and they don=E2=80=99t care whether I am pu=
lling it or not. They publish it with the commitment to maintain it - sometim=
es for better and sometimes for worse.
>>>>>
>>>>> You care about me pulling your code and I don=E2=80=99t know whether yo=
u would commit to maintain this.
>>>>>
>>>>> These two are very different cases.
>>>>>
>>>>>> Pick the IP Blocklists list (i.e., 3CORESEC, ABUSECH, DSHIELD, SPAMHAU=
S, etc.)  or the Suricata lists (i.e., Emergingthreats.net, Abuse.ch, etc.). =
 So you=E2=80=99ve 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 lis=
ts picked, asked for the value to the users, etc.  And I answered the best I =
could.
>>>>>>
>>>>>> You even asked: =E2=80=9CWhy is this realised as an add-on and not par=
t of the core system?=E2=80=9D 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 bein=
g ignored, why should I put this to the top of my list of priorities? I am no=
t 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 w=
ere 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=E2=80=99t know what you actually=
 did with my feedback. I can only see the end product that does not seem cont=
ain much of it. Repeatedly I have been pointing out that we should think befo=
re we build. I am sure a lot of hours have now gone into some code that simpl=
y does not satisfy me. And I am not not talking about the code itself, what i=
t does is what I don=E2=80=99t think is right for us.
>>>>>
>>>>> The process is very clear for me that we should first of all think whet=
her we want a certain feature now. Then there should be a clear roadmap for e=
veryone to follow; tasks can be split-up as we go and hopefully then have som=
ething that is maintainable, interesting for our users and even would do us p=
roud. This is how this should work.
>>>>>
>>>>> So, what has to change? I don=E2=80=99t think with shouting at each oth=
er, throwing patches around and making me generally unhappy is a good start.
>>>>>
>>>>> -Michael
>>>>>
>>>>>> Jon
>>>>>>
>>>>>>
>>>>>>
>>>>>>> On Feb 6, 2025, at 2:13=E2=80=AFPM, Michael Tremer <michael.tremer(a)=
ipfire.org> wrote:
>>>>>>>
>>>>>>> Hello Jon,
>>>>>>>
>>>>>>> Well, here we are again with another patch regarding this feature.
>>>>>>>
>>>>>>> I cannot quite see from your email what the question is, but if this =
is a request to have this merged into IPFire, I am once again sorry to disapp=
oint you.
>>>>>>>
>>>>>>> I think I have covered this all at lengths before that this project h=
as been started as a separate effort and as far as I am aware none of the oth=
er 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 fo=
rum 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=E2=80=99t want to merge code that I don=E2=80=99t agree with. S=
o many fundamental things that I have been raising have either not been discu=
ssed or outright dismissed.
>>>>>>>
>>>>>>> I don=E2=80=99t want to merge code that has no future inside IPFire a=
s there is no constructive conversation with the maintainers of it.
>>>>>>>
>>>>>>> Having been trying for a long time to make you aware of this, nothing=
 of this should come as a surprise.
>>>>>>>
>>>>>>> Please consider if that can be changed and if there is a path forward=
 with this.
>>>>>>>
>>>>>>> All the best,
>>>>>>> -Michael
>>>>>>>
>>>>>>>> On 6 Feb 2025, at 16:35, Jon Murphy <jon.murphy(a)ipfire.org> wrote:
>>>>>>>>
>>>>>>>> What is it?
>>>>>>>> Response Policy Zone (RPZ) is a mechanism to define local policies i=
n a
>>>>>>>> standardized way and load those policies from external sources.
>>>>>>>> Bottom line: RPZ allows admins to easily block access to websites vi=
a DNS lookup.
>>>>>>>>
>>>>>>>> RPZ can block websites via categories.  Examples include: fake websi=
tes, annoying
>>>>>>>> pop-up ads, newly registered domains, DoH bypass sites, bad "host" s=
ervices,
>>>>>>>> maliscious top level domains (e.g., *.zip, *.mov), piracy, gambling,=
 pornography,
>>>>>>>> and more.  RPZ lists come from various RPZ providers and their avail=
able
>>>>>>>> catagories.
>>>>>>>>
>>>>>>>> This RPZ add-on enables the RPZ functionality by adding a couple lin=
es 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, Frenc=
h, 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 ac=
cess
>>>>>>>> gateway, which is (should be) solely used as DNS source of the inter=
nal network.
>>>>>>>>
>>>>>>>> - As most sites use HTTPS it makes it difficult to filter traffic wi=
th URL
>>>>>>>> Filter without also properly configuring conventional (non-transpare=
nt)
>>>>>>>> mode on the proxy.  RPZ is a nice replacement for the URL Filter.
>>>>>>>>
>>>>>>>> - No need to install and maintain an additional device like PiHole o=
r AdBlock
>>>>>>>> browser extensions on multiple user devices.
>>>>>>>>
>>>>>>>> - This is an additional layer of protection for users. Less worry so=
meone will
>>>>>>>> click on something that gets them into trouble. And, saying this wit=
h emphasis,
>>>>>>>> the ability to do it in one place!
>>>>>>>>
>>>>>>>> - Blocked sites save on unneeded traffic and can lessen the threat o=
f 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), school=
s,
>>>>>>>> ministerial, and at the office.  Device counts are small (2-6) to me=
dium (~80)
>>>>>>>> to mediam-large (200+).
>>>>>>>>
>>>>>>>> - RPZ can block ads, popups, phishing, scammers, spyware, malware, a=
nnoying
>>>>>>>> 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 validat=
e 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 =E2=80=9Cundef=E2=
=80=9D and caused a warning
>>>>>>>> - bug fix: HTML textarea removes the first empty line in a custom li=
st
>>>>>>>> - 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/b=
lock 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` displaye=
d 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-ipf=
ire)
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> rpz-beta-0.1.11-11.ipfire on 2024-10-18
>>>>>>>> rpz.cgi:
>>>>>>>> - new feature: added new language file for Italian (thank you umbert=
o)
>>>>>>>> - new feature: added new language file for Spanish (thank you Robert=
o)
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> 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 siosio=
s)
>>>>>>>>
>>>>>>>> ---
>>>>>>>>
>>>>>>>> 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 we=
bgui!!
>>>>>>>> - 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 cou=
nt, by
>>>>>>>> "enabled" list or all lists
>>>>>>>>
>>>>>>>> backups:
>>>>>>>> - bug fix: include all files in `/var/ipfire/dns/rpz` directory in b=
ackup
>>>>>>>>
>>>>>>>> update.sh:
>>>>>>>> - bug fix: corrected ownership for `/var/ipfire/dns/rpz` directory d=
uring an
>>>>>>>> update
>>>>>>>>
>>>>>>>> Build:
>>>>>>>> - bug fix: `block.rpz.conf` and `block.rpz` from build.  Files to be=
 created
>>>>>>>> by `rpz-make`
>>>>>>>>
>>>>>>>> WebGUI and German language file
>>>>>>>> Contribution-by: Leo-Andres Hofmann <hofmann(a)leo-andres.de>
>>>>>>>>
>>>>>>>> Spanish language file
>>>>>>>> Contribution-by: Roberto Pe=C3=B1a
>>>>>>>>
>>>>>>>> Italian language file
>>>>>>>> Contribution-by: Umberto Parma
>>>>>>>>
>>>>>>>> French language file
>>>>>>>> Contribution-by: gw-ipfire
>>>>>>>>
>>>>>>>> Turkish language file
>>>>>>>> Contribution-by: Peppe Tech
>>>>>>>>
>>>>>>>> Contribution-by: Bernhard Bitsch <bbitsch(a)ipfire.org>
>>>>>>>> Contribution-by: Erik Kapfer <erik.kapfer(a)ipfire.org>
>>>>>>>> Signed-off-by: Jon Murphy <jon.murphy(a)ipfire.org
>>>>>>>> ---
>>>>>>>> config/backup/includes/rpz                 |   4 +
>>>>>>>> config/cfgroot/manualpages                 |   1 +
>>>>>>>> config/menu/EX-rpz.menu                    |   6 +
>>>>>>>> config/rootfiles/common/configroot         |   1 +
>>>>>>>> config/rootfiles/common/web-user-interface |   1 +
>>>>>>>> config/rootfiles/packages/rpz              |  20 +
>>>>>>>> config/rpz/00-rpz.conf                     |  10 +
>>>>>>>> config/rpz/rpz-config                      | 130 +++
>>>>>>>> config/rpz/rpz-functions                   |  85 ++
>>>>>>>> config/rpz/rpz-make                        | 203 +++++
>>>>>>>> config/rpz/rpz-metrics                     | 170 ++++
>>>>>>>> config/rpz/rpz-sleep                       |  58 ++
>>>>>>>> config/rpz/rpz.de.pl                       |  30 +
>>>>>>>> config/rpz/rpz.en.pl                       |  30 +
>>>>>>>> config/rpz/rpz.es.pl                       |  30 +
>>>>>>>> config/rpz/rpz.fr.pl                       |  30 +
>>>>>>>> config/rpz/rpz.it.pl                       |  30 +
>>>>>>>> config/rpz/rpz.tr.pl                       |  30 +
>>>>>>>> html/cgi-bin/rpz.cgi                       | 923 +++++++++++++++++++=
++
>>>>>>>> lfs/rpz                                    |  96 +++
>>>>>>>> make.sh                                    |   3 +-
>>>>>>>> src/paks/rpz/install.sh                    |  36 +
>>>>>>>> src/paks/rpz/uninstall.sh                  |  38 +
>>>>>>>> src/paks/rpz/update.sh                     |  52 ++
>>>>>>>> 24 files changed, 2016 insertions(+), 1 deletion(-)
>>>>>>>> create mode 100644 config/backup/includes/rpz
>>>>>>>> create mode 100644 config/menu/EX-rpz.menu
>>>>>>>> create mode 100644 config/rootfiles/packages/rpz
>>>>>>>> create mode 100644 config/rpz/00-rpz.conf
>>>>>>>> create mode 100644 config/rpz/rpz-config
>>>>>>>> create mode 100644 config/rpz/rpz-functions
>>>>>>>> create mode 100644 config/rpz/rpz-make
>>>>>>>> create mode 100755 config/rpz/rpz-metrics
>>>>>>>> create mode 100755 config/rpz/rpz-sleep
>>>>>>>> create mode 100644 config/rpz/rpz.de.pl
>>>>>>>> create mode 100644 config/rpz/rpz.en.pl
>>>>>>>> create mode 100644 config/rpz/rpz.es.pl
>>>>>>>> create mode 100644 config/rpz/rpz.fr.pl
>>>>>>>> create mode 100644 config/rpz/rpz.it.pl
>>>>>>>> create mode 100644 config/rpz/rpz.tr.pl
>>>>>>>> create mode 100644 html/cgi-bin/rpz.cgi
>>>>>>>> create mode 100644 lfs/rpz
>>>>>>>> create mode 100644 src/paks/rpz/install.sh
>>>>>>>> create mode 100644 src/paks/rpz/uninstall.sh
>>>>>>>> create mode 100644 src/paks/rpz/update.sh
>>>>>>>>
>>>>>>>> diff --git a/config/backup/includes/rpz b/config/backup/includes/rpz
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..36513e494
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/backup/includes/rpz
>>>>>>>> @@ -0,0 +1,4 @@
>>>>>>>> +/var/ipfire/dns/rpz/*
>>>>>>>> +/etc/unbound/zonefiles/allow.rpz
>>>>>>>> +/etc/unbound/zonefiles/block.rpz
>>>>>>>> +/etc/unbound/local.d/*rpz.conf
>>>>>>>> diff --git a/config/cfgroot/manualpages b/config/cfgroot/manualpages
>>>>>>>> index 1f7e01efc..d3a48c633 100644
>>>>>>>> --- a/config/cfgroot/manualpages
>>>>>>>> +++ b/config/cfgroot/manualpages
>>>>>>>> @@ -70,6 +70,7 @@ pakfire.cgi=3Dconfiguration/ipfire/pakfire
>>>>>>>> wlanap.cgi=3Daddons/wireless
>>>>>>>> tor.cgi=3Daddons/tor
>>>>>>>> samba.cgi=3Daddons/samba
>>>>>>>> +rpz.cgi=3Daddons/rpz
>>>>>>>>
>>>>>>>> # Logs menu
>>>>>>>> logs.cgi/summary.dat=3Dconfiguration/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'} =3D {
>>>>>>>> +    'caption' =3D> $Lang::tr{'rpz'},
>>>>>>>> +    'uri' =3D> '/cgi-bin/rpz.cgi',
>>>>>>>> +    'title' =3D> "RPZ",
>>>>>>>> +    'enabled' =3D> 1,
>>>>>>>> +};
>>>>>>>> diff --git a/config/rootfiles/common/configroot b/config/rootfiles/c=
ommon/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/roo=
tfiles/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/packag=
es/rpz
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..1c8663049
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rootfiles/packages/rpz
>>>>>>>> @@ -0,0 +1,20 @@
>>>>>>>> +etc/unbound/local.d/00-rpz.conf
>>>>>>>> +etc/unbound/zonefiles
>>>>>>>> +etc/unbound/zonefiles/allow.rpz
>>>>>>>> +usr/sbin/rpz-config
>>>>>>>> +usr/sbin/rpz-functions
>>>>>>>> +usr/sbin/rpz-make
>>>>>>>> +usr/sbin/rpz-metrics
>>>>>>>> +usr/sbin/rpz-sleep
>>>>>>>> +var/ipfire/addon-lang/rpz.de.pl
>>>>>>>> +var/ipfire/addon-lang/rpz.en.pl
>>>>>>>> +var/ipfire/addon-lang/rpz.es.pl
>>>>>>>> +var/ipfire/addon-lang/rpz.fr.pl
>>>>>>>> +var/ipfire/addon-lang/rpz.it.pl
>>>>>>>> +var/ipfire/addon-lang/rpz.tr.pl
>>>>>>>> +var/ipfire/backup/addons/includes/rpz
>>>>>>>> +var/ipfire/dns/rpz
>>>>>>>> +var/ipfire/dns/rpz/allowlist
>>>>>>>> +var/ipfire/dns/rpz/blocklist
>>>>>>>> +var/ipfire/menu.d/EX-rpz.menu
>>>>>>>> +srv/web/ipfire/cgi-bin/rpz.cgi
>>>>>>>> diff --git a/config/rpz/00-rpz.conf b/config/rpz/00-rpz.conf
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..f005a4f2e
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/00-rpz.conf
>>>>>>>> @@ -0,0 +1,10 @@
>>>>>>>> +server:
>>>>>>>> +    module-config: "respip validator iterator"
>>>>>>>> +
>>>>>>>> +rpz:
>>>>>>>> +    name:                    allow.rpz
>>>>>>>> +    zonefile:                /etc/unbound/zonefiles/allow.rpz
>>>>>>>> +    rpz-action-override:     passthru
>>>>>>>> +    rpz-log:                 yes
>>>>>>>> +    rpz-log-name:            allow
>>>>>>>> +    rpz-signal-nxdomain-ra:  yes
>>>>>>>> diff --git a/config/rpz/rpz-config b/config/rpz/rpz-config
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..c72d50f9b
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/rpz-config
>>>>>>>> @@ -0,0 +1,130 @@
>>>>>>>> +#!/bin/bash
>>>>>>>> +###################################################################=
############
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +#  IPFire.org - A linux based firewall                             =
           #
>>>>>>>> +#  Copyright (C) 2024-2025  IPFire Team  <info(a)ipfire.org>       =
             #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +#  This program is free software: you can redistribute it and/or mo=
dify       #
>>>>>>>> +#  it under the terms of the GNU General Public License as publishe=
d by       #
>>>>>>>> +#  the Free Software Foundation, either version 3 of the License, o=
r          #
>>>>>>>> +#  (at your option) any later version.                             =
           #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +#  This program is distributed in the hope that it will be useful, =
           #
>>>>>>>> +#  but WITHOUT ANY WARRANTY; without even the implied warranty of  =
           #
>>>>>>>> +#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the   =
           #
>>>>>>>> +#  GNU General Public License for more details.                    =
           #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +#  You should have received a copy of the GNU General Public Licens=
e          #
>>>>>>>> +#  along with this program.  If not, see <http://www.gnu.org/licens=
es/>.      #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +###################################################################=
############
>>>>>>>> +
>>>>>>>> +version=3D"2025-01-11 - v44"
>>>>>>>> +
>>>>>>>> +###############     Functions     ###############
>>>>>>>> +
>>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>>> +
>>>>>>>> +###############       Main        ###############
>>>>>>>> +
>>>>>>>> +tagName=3D"unbound"
>>>>>>>> +
>>>>>>>> +rpzAction=3D"${1}"                    #  input RPZ action
>>>>>>>> +rpzName=3D"${2}"                      #  input RPZ name
>>>>>>>> +rpzURL=3D"${3}"                       #  input RPZ URL
>>>>>>>> +rpzOption1=3D"${4}"                   #  input RPZ option #1
>>>>>>>> +rpzOption2=3D"${5}"                   #  input RPZ option #2
>>>>>>>> +
>>>>>>>> +rpzConfig=3D"/etc/unbound/local.d/${rpzName}.rpz.conf"    #  output=
 zone conf file
>>>>>>>> +rpzFile=3D"/etc/unbound/zonefiles/${rpzName}.rpz"         #  output=
 for RPZ file
>>>>>>>> +
>>>>>>>> +rpzLog=3D"yes"                        #  log default is yes
>>>>>>>> +ucReload=3D"yes"                      #  reload default is yes
>>>>>>>> +
>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>> +    case "$1" in
>>>>>>>> +        --no-log )      rpzLog=3D"no"   ;;
>>>>>>>> +        --no-reload )   ucReload=3D"no" ; checkConf=3D"no" ;;
>>>>>>>> +    esac
>>>>>>>> +    shift       # Shift after checking all the cases to get next op=
tion
>>>>>>>> +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 e=
xists. exit"
>>>>>>>> +            exit 104
>>>>>>>> +        fi
>>>>>>>> +
>>>>>>>> +        #  is this a valid URL?
>>>>>>>> +        regex=3D'^https://[-[:alnum:]\+&@#/%?=3D~_|!:,.;]*[-[:alnum=
:]\+&@#/%=3D~_|]'
>>>>>>>> +        if ! [[ "${rpzURL}" =3D~ $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 n=
ot exist. exit"
>>>>>>>> +            exit 106
>>>>>>>> +        fi
>>>>>>>> +
>>>>>>>> +        msg_log "info: rpz: remove config file & rpz file \"${rpzNa=
me}\""
>>>>>>>> +        rm "${rpzConfig}"
>>>>>>>> +        rm "${rpzFile}"
>>>>>>>> +        ;;
>>>>>>>> +
>>>>>>>> +    reload )
>>>>>>>> +        check_unbound_conf "${checkConf}"
>>>>>>>> +        ;;
>>>>>>>> +
>>>>>>>> +    list )
>>>>>>>> +        awk -F':' '/^\s*name:/{ gsub(/[[:blank:]]|\.rpz/, "",$2) ; =
NAME=3D$2 } \
>>>>>>>> +            /^\s*url:/{ gsub(/[[:blank:]]/, "") ; print NAME"=3D"$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> <OP=
TION> <OPTION>\n"
>>>>>>>> +        printf "Version: ${version}\n"
>>>>>>>> +        exit 108
>>>>>>>> +        ;;
>>>>>>>> +
>>>>>>>> +esac
>>>>>>>> +
>>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>>> +
>>>>>>>> +exit
>>>>>>>> diff --git a/config/rpz/rpz-functions b/config/rpz/rpz-functions
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..ace1d2690
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/rpz-functions
>>>>>>>> @@ -0,0 +1,85 @@
>>>>>>>> +#!/bin/bash
>>>>>>>> +###################################################################=
############
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +#  IPFire.org - A linux based firewall                             =
           #
>>>>>>>> +#  Copyright (C) 2024  IPFire Team  <info(a)ipfire.org>            =
             #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +#  This program is free software: you can redistribute it and/or mo=
dify       #
>>>>>>>> +#  it under the terms of the GNU General Public License as publishe=
d by       #
>>>>>>>> +#  the Free Software Foundation, either version 3 of the License, o=
r          #
>>>>>>>> +#  (at your option) any later version.                             =
           #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +#  This program is distributed in the hope that it will be useful, =
           #
>>>>>>>> +#  but WITHOUT ANY WARRANTY; without even the implied warranty of  =
           #
>>>>>>>> +#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the   =
           #
>>>>>>>> +#  GNU General Public License for more details.                    =
           #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +#  You should have received a copy of the GNU General Public Licens=
e          #
>>>>>>>> +#  along with this program.  If not, see <http://www.gnu.org/licens=
es/>.      #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +###################################################################=
############
>>>>>>>> +
>>>>>>>> +version=3D"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=3D"${1}"
>>>>>>>> +
>>>>>>>> +    regex=3D'^[a-zA-Z0-9_]+$'             # no dash or plus, alpha =
numeric only
>>>>>>>> +    regex1=3D'^(allow|block)$'            # allow and block are res=
erved NAMEs
>>>>>>>> +    if [[ ! "${theName}" =3D~ $regex ]] || [[ "${theName}" =3D~ $re=
gex1 ]] ; 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=3D"${1:-yes}"      #   check config default =
is yes
>>>>>>>> +
>>>>>>>> +    #  check the above config files
>>>>>>>> +    if [[ "${thecheckConf}" =3D=3D yes ]] ; then
>>>>>>>> +        msg_log "info: rpz: check for errors with \"unbound-checkco=
nf\""
>>>>>>>> +
>>>>>>>> +        if ! unbound-checkconf ; then
>>>>>>>> +            msg_log "error: rpz: unbound-checkconf found invalid co=
nfiguration."
>>>>>>>> +            msg_log \
>>>>>>>> +              "error: rpz: In Terminal run the command \"unbound-ch=
eckconf\" for more information. exit."
>>>>>>>> +            exit 102
>>>>>>>> +        fi
>>>>>>>> +    fi
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +unbound_control_reload () {
>>>>>>>> +    local theReload=3D"${1:-yes}"     #   reload default is yes
>>>>>>>> +
>>>>>>>> +    if [[ "${theReload}" =3D=3D yes ]] ; then
>>>>>>>> +        #  reload due to the changes
>>>>>>>> +        msg_log  "info: rpz: run \"unbound-control reload\""
>>>>>>>> +
>>>>>>>> +        if ! unbound-control reload ; then
>>>>>>>> +            msg_log "error: rpz: unbound-control reload. exit."
>>>>>>>> +            exit 109
>>>>>>>> +        fi
>>>>>>>> +    fi
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +unbound_restart () {
>>>>>>>> +    #  restart due to the changes
>>>>>>>> +    msg_log  "info: rpz: run \"unbound restart\""
>>>>>>>> +
>>>>>>>> +    /usr/local/bin/unboundctrl restart
>>>>>>>> +}
>>>>>>>> diff --git a/config/rpz/rpz-make b/config/rpz/rpz-make
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..927d55170
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/rpz-make
>>>>>>>> @@ -0,0 +1,203 @@
>>>>>>>> +#!/bin/bash
>>>>>>>> +###################################################################=
############
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +#  IPFire.org - A linux based firewall                             =
           #
>>>>>>>> +#  Copyright (C) 2024-2025  IPFire Team  <info(a)ipfire.org>       =
             #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +#  This program is free software: you can redistribute it and/or mo=
dify       #
>>>>>>>> +#  it under the terms of the GNU General Public License as publishe=
d by       #
>>>>>>>> +#  the Free Software Foundation, either version 3 of the License, o=
r          #
>>>>>>>> +#  (at your option) any later version.                             =
           #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +#  This program is distributed in the hope that it will be useful, =
           #
>>>>>>>> +#  but WITHOUT ANY WARRANTY; without even the implied warranty of  =
           #
>>>>>>>> +#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the   =
           #
>>>>>>>> +#  GNU General Public License for more details.                    =
           #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +#  You should have received a copy of the GNU General Public Licens=
e          #
>>>>>>>> +#  along with this program.  If not, see <http://www.gnu.org/licens=
es/>.      #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +###################################################################=
############
>>>>>>>> +
>>>>>>>> +version=3D"2025-01-11 - v14"
>>>>>>>> +
>>>>>>>> +###############     Functions     ###############
>>>>>>>> +
>>>>>>>> +source /usr/sbin/rpz-functions
>>>>>>>> +
>>>>>>>> +#   create the config file for allow
>>>>>>>> +make_allow_config () {
>>>>>>>> +    local theLog=3D"${1:-yes}"                            #  log de=
fault ON
>>>>>>>> +    local theConfig=3D"/etc/unbound/local.d/00-rpz.conf"  #  output=
 zone conf file
>>>>>>>> +    local theList=3D"/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 fi=
les
>>>>>>>> +    set_permissions "${theConfig}"
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +#   create the config file for block
>>>>>>>> +make_block_config () {
>>>>>>>> +    local theLog=3D"${1:-yes}"                                #  lo=
g default ON
>>>>>>>> +    local theConfig=3D"/etc/unbound/local.d/block.rpz.conf"   #  ou=
tput zone conf file
>>>>>>>> +    local theList=3D"/var/ipfire/dns/rpz/blocklist"           #  in=
put 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 rp=
z 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=3D"${1}"        #   allow or block
>>>>>>>> +    local theAction=3D'.'         # the default is nxdomain or block
>>>>>>>> +    local actionList
>>>>>>>> +
>>>>>>>> +    local theList=3D"/var/ipfire/dns/rpz/${theName}list"         # =
 input custom list of domains
>>>>>>>> +    local theZonefile=3D"/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}" =3D=3D allow ]] && theAction=3D'rpz-passthr=
u.'
>>>>>>>> +
>>>>>>>> +        #  drop any extra "blanks" and add "CNAME <RPZ action>." to=
 each line
>>>>>>>> +        actionList=3D$( awk '{$1=3D$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 rp=
z 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=3D"${1}"        #   allow or block
>>>>>>>> +    local theList=3D"/var/ipfire/dns/rpz/${theName}list"  #  input =
custom list of domains
>>>>>>>> +
>>>>>>>> + #   remove good:
>>>>>>>> + #    - properly formated domain names with or without leading wild=
card
>>>>>>>> + #    - properly formated top level domain (TLD) names with wildcard
>>>>>>>> + #    - blank lines and comment lines
>>>>>>>> + #   remaining lines are considered "bad"
>>>>>>>> +    bad_lines=3D$( 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=3D"unbound"
>>>>>>>> +
>>>>>>>> +rpzName=3D"${1}"                      #  input RPZ name
>>>>>>>> +
>>>>>>>> +rpzLog=3D"yes"                        #  log default is yes
>>>>>>>> +ucReload=3D"yes"                      #  reload default is yes
>>>>>>>> +
>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>> +    case "$1" in
>>>>>>>> +        --no-log )      rpzLog=3D"no"   ;;
>>>>>>>> +        --no-reload )   ucReload=3D"no" ;  checkConf=3D"no" ;;
>>>>>>>> +    esac
>>>>>>>> +    shift       # Shift after checking all the cases to get next op=
tion
>>>>>>>> +done
>>>>>>>> +
>>>>>>>> +case "${rpzName}" in
>>>>>>>> +    #  make a new allow or block rpz file
>>>>>>>> +
>>>>>>>> +    allow )
>>>>>>>> +        validate_list 'allow'           #   is the allowlist valid?
>>>>>>>> +        make_allow_config "${rpzLog}"
>>>>>>>> +        make_rpz_file 'allow'
>>>>>>>> +        ;;
>>>>>>>> +
>>>>>>>> +    allowblock )
>>>>>>>> +        validate_list 'allow'           #   is the list valid?
>>>>>>>> +        make_allow_config "${rpzLog}"
>>>>>>>> +        make_rpz_file 'allow'
>>>>>>>> +        ;&
>>>>>>>> +
>>>>>>>> +    block )
>>>>>>>> +        validate_list 'block'           #   is the blocklist valid?
>>>>>>>> +        make_block_config "${rpzLog}"
>>>>>>>> +        make_rpz_file 'block'
>>>>>>>> +        ;;
>>>>>>>> +
>>>>>>>> +    reload )
>>>>>>>> +        check_unbound_conf "${checkConf}"
>>>>>>>> +        ;;
>>>>>>>> +
>>>>>>>> +    unbound-restart )
>>>>>>>> +        check_unbound_conf "${checkConf}"
>>>>>>>> + unbound_restart
>>>>>>>> + exit
>>>>>>>> +        ;;
>>>>>>>> +
>>>>>>>> +    * )
>>>>>>>> +        msg_log "error: rpz: missing or incorrect parameter"
>>>>>>>> +        printf "Usage:   $(basename "$0") <NAME> <OPTION> <OPTION>\=
n"
>>>>>>>> +        printf "Version: ${version}\n"
>>>>>>>> +        exit 108
>>>>>>>> +        ;;
>>>>>>>> +esac
>>>>>>>> +
>>>>>>>> +unbound_control_reload "${ucReload}"
>>>>>>>> +
>>>>>>>> +exit
>>>>>>>> diff --git a/config/rpz/rpz-metrics b/config/rpz/rpz-metrics
>>>>>>>> new file mode 100755
>>>>>>>> index 000000000..4d43e1629
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/rpz-metrics
>>>>>>>> @@ -0,0 +1,170 @@
>>>>>>>> +#!/bin/bash
>>>>>>>> +###################################################################=
############
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +#  IPFire.org - A linux based firewall                             =
           #
>>>>>>>> +#  Copyright (C) 2024  IPFire Team  <info(a)ipfire.org>            =
             #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +#  This program is free software: you can redistribute it and/or mo=
dify       #
>>>>>>>> +#  it under the terms of the GNU General Public License as publishe=
d by       #
>>>>>>>> +#  the Free Software Foundation, either version 3 of the License, o=
r          #
>>>>>>>> +#  (at your option) any later version.                             =
           #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +#  This program is distributed in the hope that it will be useful, =
           #
>>>>>>>> +#  but WITHOUT ANY WARRANTY; without even the implied warranty of  =
           #
>>>>>>>> +#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the   =
           #
>>>>>>>> +#  GNU General Public License for more details.                    =
           #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +#  You should have received a copy of the GNU General Public Licens=
e          #
>>>>>>>> +#  along with this program.  If not, see <http://www.gnu.org/licens=
es/>.      #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +###################################################################=
############
>>>>>>>> +
>>>>>>>> +version=3D"2025-01-20 - v25"
>>>>>>>> +
>>>>>>>> +###############       Main        ###############
>>>>>>>> +
>>>>>>>> +weeks=3D"2"                   #   default to two message logs
>>>>>>>> +sortBy=3D"name"               #   default "by name"
>>>>>>>> +rpzActive=3D"enabled"         #   default "enabled only"
>>>>>>>> +
>>>>>>>> +while [[ $# -gt 0 ]] ; do
>>>>>>>> +    case "$1" in
>>>>>>>> +        --by-names | --by-name | name ) sortBy=3D"name"   ;;
>>>>>>>> +
>>>>>>>> +        --by-hits | --by-hit | hits | hit )      sortBy=3D"hit"    =
;;
>>>>>>>> +
>>>>>>>> +        --by-lines | --by-line | lines | line )     sortBy=3D"line"=
   ;;
>>>>>>>> +
>>>>>>>> +        --by-effect ) sortBy=3D"effect"   ;;
>>>>>>>> +
>>>>>>>> +        --enabled-only )             rpzActive=3D"enabled" ;;
>>>>>>>> +
>>>>>>>> +        --active-all | --all | all )       rpzActive=3D"all"     ;;
>>>>>>>> +
>>>>>>>> +        [0-9] | [0-9][0-9] )         weeks=3D$1        ;;
>>>>>>>> +    esac
>>>>>>>> +    shift       # Shift after checking all the cases to get next op=
tion
>>>>>>>> +done
>>>>>>>> +
>>>>>>>> +#  get the list of message logs for N weeks
>>>>>>>> +messageLogs=3D$( find /var/log/messages* -type f | sort --version-s=
ort |
>>>>>>>> +    head -"${weeks}" )
>>>>>>>> +
>>>>>>>> +#  get the list of RPZ names & counts from the message log(s)
>>>>>>>> +rpzNameCount=3D$( 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=3D$( echo "${rpzNameCount}" |
>>>>>>>> +    awk '{ print $2, $1 }' |
>>>>>>>> +    sed --regexp-extended 's|^\[(.*)\]|\1|' )
>>>>>>>> +
>>>>>>>> +#  grab only names
>>>>>>>> +rpzNames=3D$( echo "${rpzNameCount}" | awk '{ print $1 }' )
>>>>>>>> +
>>>>>>>> +#  get list of RPZ files
>>>>>>>> +rpzFileList=3D$( find /etc/unbound/zonefiles -type f -iname "*.rpz"=
 )
>>>>>>>> +
>>>>>>>> +#  get basename of those files
>>>>>>>> +rpzBaseNames=3D$( echo "${rpzFileList}" |
>>>>>>>> +    sed 's|/etc/unbound/zonefiles/||g ; s|\.rpz||g ;' )
>>>>>>>> +
>>>>>>>> +#  add to rpzNames
>>>>>>>> +rpzNames=3D"${rpzNames}"$'\n'"${rpzBaseNames}"
>>>>>>>> +
>>>>>>>> +#  drop duplicate names
>>>>>>>> +rpzNames=3D$( echo "${rpzNames}" | sort --unique  )
>>>>>>>> +
>>>>>>>> +#  get line count for each RPZ
>>>>>>>> +lineCount=3D$( echo "${rpzFileList}" | xargs wc -l )
>>>>>>>> +
>>>>>>>> +#  get comment line count and blank line count for each RPZ
>>>>>>>> +commentCount=3D$( echo "${rpzFileList}" | xargs grep --count -e "^$=
" -e "^;" )
>>>>>>>> +
>>>>>>>> +#  get modified date each RPZ
>>>>>>>> +modDateList=3D$( echo "${rpzFileList}" | xargs stat -c '%.10y  %n' )
>>>>>>>> +
>>>>>>>> +ucListAuthZones=3D$( unbound-control list_auth_zones )
>>>>>>>> +
>>>>>>>> +#  get width of RPZ names
>>>>>>>> +pWidth=3D$( echo "${rpzNames}" | awk '{ print $1"   " }' | wc -L )
>>>>>>>> +pFormat=3D"%-${pWidth}s %-8s %-8s %8s %12s %12s\n"
>>>>>>>> +
>>>>>>>> +#  print title line
>>>>>>>> +printf "${pFormat}" "name" "hits" "active" "lines" " hits/line" "la=
st download"
>>>>>>>> +printf -- "--------------"
>>>>>>>> +
>>>>>>>> +theResults=3D""
>>>>>>>> +totalLines=3D0
>>>>>>>> +totalHits=3D0
>>>>>>>> +while read -r theName
>>>>>>>> +do
>>>>>>>> +    printf -- "--"        #   pretend progress bar
>>>>>>>> +
>>>>>>>> +    #  is this RPZ list active?
>>>>>>>> +    theActive=3D"disabled"
>>>>>>>> +    if grep --quiet "^${theName}\.rpz" <<< "${ucListAuthZones}"
>>>>>>>> +    then
>>>>>>>> +        theActive=3D"enabled"
>>>>>>>> +    else
>>>>>>>> +        [[ "${rpzActive}" =3D=3D enabled ]] && continue
>>>>>>>> +    fi
>>>>>>>> +
>>>>>>>> +    #  get hit count
>>>>>>>> +    theHits=3D"0"
>>>>>>>> +    if output=3D$( grep "^${theName}\s" <<< "${rpzNameCount}" ) ; t=
hen
>>>>>>>> +        theHits=3D$( echo "${output}" | awk '{ print $2 }' )
>>>>>>>> +        totalHits=3D$(( totalHits + theHits ))
>>>>>>>> +    fi
>>>>>>>> +
>>>>>>>> +    #  get line count
>>>>>>>> +    theLines=3D"n/a"
>>>>>>>> +    hitsPerLine=3D"0"
>>>>>>>> +    if output=3D$( grep --fixed-strings "/${theName}.rpz" <<< "${li=
neCount}" ) ; then
>>>>>>>> +        theLines=3D$( echo "${output}" | awk '{ print $1 }' )
>>>>>>>> +        totalLines=3D$(( totalLines + theLines ))
>>>>>>>> +
>>>>>>>> +        if [[ "${theLines}" -gt 2 ]] ; then
>>>>>>>> +            hitsPerLine=3D$(( 100 * theHits / theLines ))
>>>>>>>> +        fi
>>>>>>>> +    fi
>>>>>>>> +
>>>>>>>> +    #  get modification date
>>>>>>>> +    theModDate=3D"n/a"
>>>>>>>> +    if output=3D$( grep --fixed-strings "/${theName}.rpz" <<< "${mo=
dDateList}" ) ; then
>>>>>>>> +        theModDate=3D$( echo "${output}" | awk '{ print $1 }' )
>>>>>>>> +    fi
>>>>>>>> +
>>>>>>>> +    #  add to results list
>>>>>>>> +    theResults+=3D"${theName} ${theHits} ${theActive} ${theLines} $=
{hitsPerLine} ${theModDate}"$'\n'
>>>>>>>> +
>>>>>>>> +done <<< "${rpzNames}"
>>>>>>>> +
>>>>>>>> +case "${sortBy}" in
>>>>>>>> +    #  sort by "active" then by "name"
>>>>>>>> +    name) sortArg=3D(-k3,3r -k1,1) ;;
>>>>>>>> +
>>>>>>>> +    #  sort by "active" then by "hits" then by "name"
>>>>>>>> +    hit)  sortArg=3D(-k3,3r -k2,2nr -k1,1) ;;
>>>>>>>> +
>>>>>>>> +    #  sort by "active" then by "lines" then by "name"
>>>>>>>> +    line) sortArg=3D(-k3,3r -k4,4nr -k1,1) ;;
>>>>>>>> +
>>>>>>>> +    #  sort by "active" then by "effect" then by "name"
>>>>>>>> +    effect) sortArg=3D(-k3,3r -k5,5nr -k1,1) ;;
>>>>>>>> +esac
>>>>>>>> +
>>>>>>>> +printf -- "--------------\n"
>>>>>>>> +#  remove blank lines, sort, print as columns
>>>>>>>> +echo "${theResults}" |
>>>>>>>> +    awk '!/^[[:space:]]*$/' |
>>>>>>>> +    sort "${sortArg[@]}"    |
>>>>>>>> +    awk --assign=3Dwidth=3D"${pWidth}" \
>>>>>>>> +        '{ printf "%-*s %-8s %-8s %8s %10s %% %12s\n", width, $1, $=
2, $3, $4, $5, $6 }'
>>>>>>>> +
>>>>>>>> +printf "${pFormat}" "" "=3D=3D=3D=3D=3D=3D=3D" "" "=3D=3D=3D=3D=3D=
=3D=3D=3D" "" ""
>>>>>>>> +printf "${pFormat}" "Totals -->" "${totalHits}" "" "${totalLines}" =
"" ""
>>>>>>>> +
>>>>>>>> +exit
>>>>>>>> diff --git a/config/rpz/rpz-sleep b/config/rpz/rpz-sleep
>>>>>>>> new file mode 100755
>>>>>>>> index 000000000..dd3603599
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/config/rpz/rpz-sleep
>>>>>>>> @@ -0,0 +1,58 @@
>>>>>>>> +#!/bin/bash
>>>>>>>> +###################################################################=
############
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +#  IPFire.org - A linux based firewall                             =
           #
>>>>>>>> +#  Copyright (C) 2024  IPFire Team  <info(a)ipfire.org>            =
             #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +#  This program is free software: you can redistribute it and/or mo=
dify       #
>>>>>>>> +#  it under the terms of the GNU General Public License as publishe=
d by       #
>>>>>>>> +#  the Free Software Foundation, either version 3 of the License, o=
r          #
>>>>>>>> +#  (at your option) any later version.                             =
           #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +#  This program is distributed in the hope that it will be useful, =
           #
>>>>>>>> +#  but WITHOUT ANY WARRANTY; without even the implied warranty of  =
           #
>>>>>>>> +#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the   =
           #
>>>>>>>> +#  GNU General Public License for more details.                    =
           #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +#  You should have received a copy of the GNU General Public Licens=
e          #
>>>>>>>> +#  along with this program.  If not, see <http://www.gnu.org/licens=
es/>.      #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +###################################################################=
############
>>>>>>>> +
>>>>>>>> +version=3D"2024-08-16"        # v05
>>>>>>>> +
>>>>>>>> +###############     Functions     ###############
>>>>>>>> +
>>>>>>>> +#   send message to message log
>>>>>>>> +msg_log () {
>>>>>>>> +    logger --tag "${tagName}" "$*"
>>>>>>>> +    if tty --silent ; then
>>>>>>>> +        echo "${tagName}:" "$*"
>>>>>>>> +    fi
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +###############       Main        ###############
>>>>>>>> +
>>>>>>>> +tagName=3D"unbound"
>>>>>>>> +
>>>>>>>> +sleepTime=3D"${1:-5m}"            #  default to sleep for 5m (5 min=
utes)
>>>>>>>> +
>>>>>>>> +zoneList=3D$( 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 =3D (%tr,
>>>>>>>> +'rpz' =3D> 'Response Policy Zones (RPZ)',
>>>>>>>> +'rpz apply' =3D> '=C3=9Cbernehmen',
>>>>>>>> +'rpz cl allow enable' =3D> 'Benutzerdefinierte Allowlist aktivieren=
:',
>>>>>>>> +'rpz cl allow info' =3D> 'Zugelassene Domains (eine pro Zeile)<br>B=
eispiel: domain.com, *.domain.com',
>>>>>>>> +'rpz cl allow' =3D> 'Benutzerdefinierte Allowlist',
>>>>>>>> +'rpz cl block enable' =3D> 'Benutzerdefinierte Blocklist aktivieren=
:',
>>>>>>>> +'rpz cl block info' =3D> 'Gesperrte Domains (eine pro Zeile)<br>Bei=
spiel: domain.com, *.domain.com',
>>>>>>>> +'rpz cl block' =3D> 'Benutzerdefinierte Blocklist',
>>>>>>>> +'rpz cl' =3D> 'Benutzerdefinierte Listen',
>>>>>>>> +'rpz exitcode 101' =3D> 'Der Name enth=C3=A4lt unzul=C3=A4ssige Zei=
chen',
>>>>>>>> +'rpz exitcode 102' =3D> 'unbound-checkconf hat eine fehlerhafte Kon=
figuration ermittelt. F=C3=BChren Sie das Kommando unbound-checkconf auf der =
Konsole aus, um weitere Informationen zu erhalten.',
>>>>>>>> +'rpz exitcode 103' =3D> 'Die benutzerdefinierte Allow-/Blocklist is=
t leer',
>>>>>>>> +'rpz exitcode 104' =3D> 'Ein Eintrag mit identischem Namen existier=
t bereits',
>>>>>>>> +'rpz exitcode 105' =3D> 'Die URL ist ung=C3=BCltig',
>>>>>>>> +'rpz exitcode 106' =3D> 'Eintrag kann nicht entfernt werden, der Na=
me existiert nicht',
>>>>>>>> +'rpz exitcode 107' =3D> 'Der Name ist ung=C3=BCltig - nur "allow" o=
der "block" m=C3=B6glich',
>>>>>>>> +'rpz exitcode 108' =3D> 'Fehlende oder inkorrekte Parameter',
>>>>>>>> +'rpz exitcode 109' =3D> 'unbound-control reload ist fehlgeschlagen',
>>>>>>>> +'rpz exitcode 110' =3D> 'Die benutzerdefinierte Allow-/Blocklist en=
th=C3=A4lt unzul=C3=A4ssige Eintr=C3=A4ge',
>>>>>>>> +'rpz exitcode 201' =3D> 'Die Anmerkung enth=C3=A4lt unzul=C3=A4ssig=
e Zeichen',
>>>>>>>> +'rpz exitcode 202' =3D> 'Ung=C3=BCltiger Eintrag in der benutzerdef=
inierten Allowlist, Zeile ',
>>>>>>>> +'rpz exitcode 203' =3D> 'Ung=C3=BCltiger Eintrag in der benutzerdef=
inierten Blocklist, Zeile ',
>>>>>>>> +'rpz exitcode 204' =3D> 'Ausgew=C3=A4hlter Eintrag existiert nicht:=
 ',
>>>>>>>> +'rpz zf editor' =3D> 'Zonendatei-Eintrag bearbeiten',
>>>>>>>> +'rpz zf imported' =3D> '(importiert aus rpz-config)',
>>>>>>>> +'rpz zf remark info' =3D> 'Erlaubte Zeichen sind a-z, A-Z, 0-9 und =
Unterstriche',
>>>>>>>> +'rpz zf' =3D> '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 =3D (%tr,
>>>>>>>> +'rpz' =3D> 'Response Policy Zones (RPZ)',
>>>>>>>> +'rpz apply' =3D> 'Apply',
>>>>>>>> +'rpz cl allow enable' =3D> 'Enable custom allowlist:',
>>>>>>>> +'rpz cl allow info' =3D> 'Allowed domains (one per line)<br>Example=
: domain.com, *.domain.com',
>>>>>>>> +'rpz cl allow' =3D> 'Custom allowlist',
>>>>>>>> +'rpz cl block enable' =3D> 'Enable custom blocklist:',
>>>>>>>> +'rpz cl block info' =3D> 'Blocked domains (one per line)<br>Example=
: domain.com, *.domain.com',
>>>>>>>> +'rpz cl block' =3D> 'Custom blocklist',
>>>>>>>> +'rpz cl' =3D> 'Custom lists',
>>>>>>>> +'rpz exitcode 101' =3D> 'the NAME is not valid',
>>>>>>>> +'rpz exitcode 102' =3D> 'unbound-checkconf found invalid configurat=
ion. In the Terminal run the command unbound-checkconf for more information',
>>>>>>>> +'rpz exitcode 103' =3D> 'the allow/block list is empty',
>>>>>>>> +'rpz exitcode 104' =3D> 'duplicate - NAME already exists',
>>>>>>>> +'rpz exitcode 105' =3D> 'the URL is not valid',
>>>>>>>> +'rpz exitcode 106' =3D> 'cannot remove the NAME does not exist',
>>>>>>>> +'rpz exitcode 107' =3D> 'the NAME is not valid - "allow" or "block"=
 only',
>>>>>>>> +'rpz exitcode 108' =3D> 'missing or incorrect parameter',
>>>>>>>> +'rpz exitcode 109' =3D> 'unbound-control reload failed',
>>>>>>>> +'rpz exitcode 110' =3D> 'custom Allowlist/Blocklist contains invali=
d entries',
>>>>>>>> +'rpz exitcode 201' =3D> 'the REMARK is not valid',
>>>>>>>> +'rpz exitcode 202' =3D> 'invalid entry in allowlist, line ',
>>>>>>>> +'rpz exitcode 203' =3D> 'invalid entry in blocklist, line ',
>>>>>>>> +'rpz exitcode 204' =3D> 'Selected entry does not exist: ',
>>>>>>>> +'rpz zf editor' =3D> 'Edit zonefiles entry',
>>>>>>>> +'rpz zf imported' =3D> '(imported from rpz-config)',
>>>>>>>> +'rpz zf remark info' =3D> 'Valid characters are a-z, A-Z, 0-9 and u=
nderscore.',
>>>>>>>> +'rpz zf' =3D> '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 =3D (%tr,
>>>>>>>> +'rpz' =3D> 'Zonas de pol=C3=ADtica de respuesta (RPZ)',
>>>>>>>> +'rpz apply' =3D> 'Aplicar',
>>>>>>>> +'rpz cl allow enable' =3D> 'Habilitar la lista blanca personalizada=
:',
>>>>>>>> +'rpz cl allow info' =3D> 'Dominio permitido (uno por l=C3=ADnea)<br=
>Ejemplo: domain.com, *.domain.com',
>>>>>>>> +'rpz cl allow' =3D> 'Lista blanca personalizada',
>>>>>>>> +'rpz cl block enable' =3D> 'Habilitar la lista negra personalizada:=
',
>>>>>>>> +'rpz cl block info' =3D> 'Dominio bloqueado (uno por l=C3=ADnea)<br=
>Ejemplo: domain.com, *.domain.com',
>>>>>>>> +'rpz cl block' =3D> 'Lista negra personalizada',
>>>>>>>> +'rpz cl' =3D> 'Lista personalizada',
>>>>>>>> +'rpz exitcode 101' =3D> 'El NOMBRE no es v=C3=A1lido',
>>>>>>>> +'rpz exitcode 102' =3D> 'unbound-checkconf ha encontrado una config=
uraci=C3=B3n no v=C3=A1lida. Desde Terminal, ejecute el comando unbound-check=
conf para mayor informaci=C3=B3n',
>>>>>>>> +'rpz exitcode 103' =3D> 'La lista de permitidos/bloqueados est=C3=
=A1 vac=C3=ADa',
>>>>>>>> +'rpz exitcode 104' =3D> 'duplicado - NOMBRE ya existe',
>>>>>>>> +'rpz exitcode 105' =3D> 'la URL no es v=C3=A1lida',
>>>>>>>> +'rpz exitcode 106' =3D> 'no es posible eliminar el NOMBRE que no ex=
iste',
>>>>>>>> +'rpz exitcode 107' =3D> 'el NOMBRE no es v=C3=A1lido - s=C3=B3lo "p=
ermitir" o "bloquear"',
>>>>>>>> +'rpz exitcode 108' =3D> 'par=C3=A1metro faltante o incorrecto',
>>>>>>>> +'rpz exitcode 109' =3D> 'Error al recargar unbound-control',
>>>>>>>> +'rpz exitcode 110' =3D> 'la Lista blanca/Lista negra personalizada =
contiene entradas no v=C3=A1lidas',
>>>>>>>> +'rpz exitcode 201' =3D> 'el COMENTARIO no es v=C3=A1lido',
>>>>>>>> +'rpz exitcode 202' =3D> 'entrada no v=C3=A1lida en la lista blanca,=
 l=C3=ADnea ',
>>>>>>>> +'rpz exitcode 203' =3D> 'entrada no v=C3=A1lida en la lista negra, =
l=C3=ADnea ',
>>>>>>>> +'rpz exitcode 204' =3D> 'La entrada seleccionada no existe: ',
>>>>>>>> +'rpz zf editor' =3D> 'Editar la entrada de archivos de zona',
>>>>>>>> +'rpz zf imported' =3D> '(importado de rpz-config)',
>>>>>>>> +'rpz zf remark info' =3D> 'Los caracteres v=C3=A1lidos son a-z, A-Z=
, 0-9 y gui=C3=B3n bajo',
>>>>>>>> +'rpz zf' =3D> '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 =3D (%tr,
>>>>>>>> +'rpz' =3D> 'Response Policy Zones (RPZ)',
>>>>>>>> +'rpz apply' =3D> 'Appliquer',
>>>>>>>> +'rpz cl allow enable' =3D> 'Activer la liste d\'autorisations perso=
nnalis=C3=A9e:',
>>>>>>>> +'rpz cl allow info' =3D> 'Domaines autoris=C3=A9s (un par ligne)<br=
>Example: domain.com, *.domain.com',
>>>>>>>> +'rpz cl allow' =3D> 'Liste d\'autorisations personnalis=C3=A9e',
>>>>>>>> +'rpz cl block enable' =3D> 'Activer la liste de blocage personnalis=
=C3=A9e:',
>>>>>>>> +'rpz cl block info' =3D> 'Domaines bloqu=C3=A9s (un par ligne)<br>E=
xample: domain.com, *.domain.com',
>>>>>>>> +'rpz cl block' =3D> 'liste de blocage personnalis=C3=A9',
>>>>>>>> +'rpz cl' =3D> 'Listes personnalis=C3=A9es',
>>>>>>>> +'rpz exitcode 101' =3D> 'le NOM n\'est pas valide',
>>>>>>>> +'rpz exitcode 102' =3D> 'unbound-checkconf configuration non valide=
 trouv=C3=A9e. Dans le terminal, ex=C3=A9cutez la commande unbound-checkconf =
pour plus d\'informations',
>>>>>>>> +'rpz exitcode 103' =3D> 'la liste autoriser/bloquer est vide',
>>>>>>>> +'rpz exitcode 104' =3D> 'le NOM existe d=C3=A9j=C3=A0',
>>>>>>>> +'rpz exitcode 105' =3D> 'L\'URL n\'est pas valide',
>>>>>>>> +'rpz exitcode 106' =3D> 'impossible de supprimer le NOM n\'existe p=
as',
>>>>>>>> +'rpz exitcode 107' =3D> 'le NOM n\'est pas valide - =C2=AB autorise=
r =C2=BB ou =C2=AB bloquer =C2=BB seulement',
>>>>>>>> +'rpz exitcode 108' =3D> 'param=C3=A8tre manquant ou incorrect',
>>>>>>>> +'rpz exitcode 109' =3D> 'unbound-control rechargement =C3=A9chou=C3=
=A9',
>>>>>>>> +'rpz exitcode 110' =3D> 'la liste autoriser/bloquer contient des en=
tr=C3=A9es non valides',
>>>>>>>> +'rpz exitcode 201' =3D> 'la REMARQUE n\'est pas valable',
>>>>>>>> +'rpz exitcode 202' =3D> 'entr=C3=A9e non valide dans la liste d\'au=
torisation, ligne ',
>>>>>>>> +'rpz exitcode 203' =3D> 'entr=C3=A9e non valide dans la liste de bl=
ocs, ligne ',
>>>>>>>> +'rpz exitcode 204' =3D> 'L\'entr=C3=A9e s=C3=A9lectionn=C3=A9e n\'e=
xiste pas: ',
>>>>>>>> +'rpz zf editor' =3D> 'Modifier l\'entr=C3=A9e Fichiers Zone',
>>>>>>>> +'rpz zf imported' =3D> '(import=C3=A9 de rpz-config)',
>>>>>>>> +'rpz zf remark info' =3D> 'Les caract=C3=A8res valides sont a-z, A-=
Z, 0-9 et soulignement.',
>>>>>>>> +'rpz zf' =3D> '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 =3D (%tr,
>>>>>>>> +'rpz' =3D> 'Response Policy Zones (RPZ)',
>>>>>>>> +'rpz apply' =3D> 'Applica',
>>>>>>>> +'rpz cl allow enable' =3D> 'Abilita la Whitelist personalizzata:',
>>>>>>>> +'rpz cl allow info' =3D> 'Domini consentiti (uno per riga)<br>Esemp=
io: domain.com, *.domain.com',
>>>>>>>> +'rpz cl allow' =3D> 'Whitelist personalizzata',
>>>>>>>> +'rpz cl block enable' =3D> 'Abilita la Blacklist personalizzata:',
>>>>>>>> +'rpz cl block info' =3D> 'Domini bloccati (uno per riga)<br>Esempio=
: domain.com, *.domain.com',
>>>>>>>> +'rpz cl block' =3D> 'Blacklist personalizzata',
>>>>>>>> +'rpz cl' =3D> 'Liste personalizzate',
>>>>>>>> +'rpz exitcode 101' =3D> 'il NOME non =C3=A8 valido',
>>>>>>>> +'rpz exitcode 102' =3D> 'unbound-checkconf ha trovato una configura=
zione non valida. Dal Terminale esegui il comando unbound-checkconf per maggi=
ori informazioni',
>>>>>>>> +'rpz exitcode 103' =3D> 'l\'elenco consentiti/bloccati =C3=A8 vuoto=
',
>>>>>>>> +'rpz exitcode 104' =3D> 'duplicato - NAME esiste di gi=C3=A0',
>>>>>>>> +'rpz exitcode 105' =3D> 'l\'URL non =C3=A8 valido',
>>>>>>>> +'rpz exitcode 106' =3D> 'non =C3=A8 possibile rimuovere il NOME non=
 esiste',
>>>>>>>> +'rpz exitcode 107' =3D> 'il NOME non =C3=A8 valido - solo "consenti=
" o "blocca"',
>>>>>>>> +'rpz exitcode 108' =3D> 'parametro mancante o non corretto',
>>>>>>>> +'rpz exitcode 109' =3D> 'ricaricamento del controllo non associato =
non riuscito',
>>>>>>>> +'rpz exitcode 110' =3D> 'la Whitelist/Blacklist personalizzata cont=
iene voci non valide',
>>>>>>>> +'rpz exitcode 201' =3D> 'l"OSSERVAZIONE non =C3=A8 valida',
>>>>>>>> +'rpz exitcode 202' =3D> 'voce non valida nella Whitelist, riga ',
>>>>>>>> +'rpz exitcode 203' =3D> 'voce non valida nella Blacklist, riga ',
>>>>>>>> +'rpz exitcode 204' =3D> 'La voce selezionata non esiste: ',
>>>>>>>> +'rpz zf editor' =3D> 'Modifica la voce dei file di zona',
>>>>>>>> +'rpz zf imported' =3D> '(importato da rpz-config)',
>>>>>>>> +'rpz zf remark info' =3D> 'I caratteri validi sono a-z, A-Z, 0-9 e =
trattino basso',
>>>>>>>> +'rpz zf' =3D> '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=EF=BF=BDin eklendi Ayriyeten Response Policy Zone (RPZ)
>>>>>>>> +%tr =3D (%tr,
>>>>>>>> +'rpz' =3D> 'Response Policy Zones (RPZ)',
>>>>>>>> +'rpz apply' =3D> 'Uygulamak',
>>>>>>>> +'rpz cl allow enable' =3D> '=EF=BF=BDzel Etkinlestir allowlist:',
>>>>>>>> +'rpz cl allow info' =3D> 'Izin verilmis domains (satir basina bir)<=
br>=EF=BF=BDrnegin: domain.com, *.domain.com',
>>>>>>>> +'rpz cl allow' =3D> 'Etkinlestir allowlist',
>>>>>>>> +'rpz cl block enable' =3D> '=EF=BF=BDzel Etkinlestir blocklist:',
>>>>>>>> +'rpz cl block info' =3D> 'Engellenmis domains (satir basina bir)<br=
>=EF=BF=BDrnegin: domain.com, *.domain.com',
>>>>>>>> +'rpz cl block' =3D> 'Engellenmis blocklist',
>>>>>>>> +'rpz cl' =3D> 'Engellenmis lists',
>>>>>>>> +'rpz exitcode 101' =3D> 'NAME ge=EF=BF=BDerli degil',
>>>>>>>> +'rpz exitcode 102' =3D> 'unbound-checkconf ge=EF=BF=BDersiz yapilan=
dirma bulundu. Terminalde daha fazla bilgi i=EF=BF=BDin Unbound-checkConf kom=
utunu =EF=BF=BDalistirin',
>>>>>>>> +'rpz exitcode 103' =3D> 'allow/block liste bos',
>>>>>>>> +'rpz exitcode 104' =3D> 'kopyalamak - NAME zaten var',
>>>>>>>> +'rpz exitcode 105' =3D> 'URL ge=EF=BF=BDerli degil',
>>>>>>>> +'rpz exitcode 106' =3D> '=EF=BF=BDikarilamiyor NAME yok',
>>>>>>>> +'rpz exitcode 107' =3D> 'NAME ge=EF=BF=BDerli degil - "allow" veya =
"block" yalniz',
>>>>>>>> +'rpz exitcode 108' =3D> 'Parametre eksik veya yanlis',
>>>>>>>> +'rpz exitcode 109' =3D> 'unbound-control basarisiz',
>>>>>>>> +'rpz exitcode 110' =3D> 'Engellenmis Allowlist/Blocklist ge=EF=BF=
=BDersiz girisler i=EF=BF=BDerir',
>>>>>>>> +'rpz exitcode 201' =3D> 'REMARK ge=EF=BF=BDerli degil',
>>>>>>>> +'rpz exitcode 202' =3D> 'Ge=EF=BF=BDersiz giris allowlist, line ',
>>>>>>>> +'rpz exitcode 203' =3D> 'Ge=EF=BF=BDersiz giris blocklist, line ',
>>>>>>>> +'rpz exitcode 204' =3D> 'Se=EF=BF=BDilen giris yok: ',
>>>>>>>> +'rpz zf editor' =3D> 'yazimlamak zonefiles giris',
>>>>>>>> +'rpz zf imported' =3D> '(ithal edildi rpz-config)',
>>>>>>>> +'rpz zf remark info' =3D> 'Ge=EF=BF=BDerli karakterler a-z, A-Z, 0-=
9ve alt=EF=BF=BDst.',
>>>>>>>> +'rpz zf' =3D> 'Zonefiles',
>>>>>>>> +);
>>>>>>>> diff --git a/html/cgi-bin/rpz.cgi b/html/cgi-bin/rpz.cgi
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..a821c92ac
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/html/cgi-bin/rpz.cgi
>>>>>>>> @@ -0,0 +1,923 @@
>>>>>>>> +#!/usr/bin/perl
>>>>>>>> +###################################################################=
############
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +# IPFire.org - A linux based firewall                              =
           #
>>>>>>>> +# Copyright (C) 2005-2024  IPFire Team  <info(a)ipfire.org>        =
             #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +# This program is free software: you can redistribute it and/or mod=
ify        #
>>>>>>>> +# it under the terms of the GNU General Public License as published=
 by        #
>>>>>>>> +# the Free Software Foundation, either version 3 of the License, or=
           #
>>>>>>>> +# (at your option) any later version.                              =
           #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +# This program is distributed in the hope that it will be useful,  =
           #
>>>>>>>> +# but WITHOUT ANY WARRANTY; without even the implied warranty of   =
           #
>>>>>>>> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    =
           #
>>>>>>>> +# GNU General Public License for more details.                     =
           #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +# You should have received a copy of the GNU General Public License=
           #
>>>>>>>> +# along with this program.  If not, see <http://www.gnu.org/license=
s/>.       #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +###################################################################=
############
>>>>>>>> +
>>>>>>>> +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 =3D <<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=3Dsubmit]:disabled {
>>>>>>>> + opacity: 0.6;
>>>>>>>> + }
>>>>>>>> +</style>
>>>>>>>> +END
>>>>>>>> +;
>>>>>>>> +###--- End of extra HTML ---###
>>>>>>>> +
>>>>>>>> +
>>>>>>>> +### Settings ###
>>>>>>>> +
>>>>>>>> +# Request DNS service reload after configuration change
>>>>>>>> +my $RPZ_RELOAD_FLAG =3D "${General::swroot}/dns/rpz/reload.flag";
>>>>>>>> +
>>>>>>>> +# Configuration file for all available zonefiles
>>>>>>>> +# Format: index, name (unique), enabled (on/off), URL, remark
>>>>>>>> +my $ZONEFILES_CONF =3D "${General::swroot}/dns/rpz/zonefiles.conf";
>>>>>>>> +
>>>>>>>> +# Configuration file for custom lists
>>>>>>>> +# IDs: 0=3Dallowlist, 1=3Dblocklist, 2=3Doptions (allow/block enabl=
ed)
>>>>>>>> +my $CUSTOMLISTS_CONF =3D "${General::swroot}/dns/rpz/customlists.co=
nf";
>>>>>>>> +
>>>>>>>> +# Export custom lists to rpz-config
>>>>>>>> +my $RPZ_ALLOWLIST =3D "${General::swroot}/dns/rpz/allowlist";
>>>>>>>> +my $RPZ_BLOCKLIST =3D "${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', "$CUSTOMLI=
STS_CONF"); }
>>>>>>>> +
>>>>>>>> +
>>>>>>>> +## Global gui data
>>>>>>>> +my $errormessage =3D "";
>>>>>>>> +
>>>>>>>> +## Global configuration data
>>>>>>>> +my %zonefiles =3D ();
>>>>>>>> +my %customlists =3D ();
>>>>>>>> +&_zonefiles_load();
>>>>>>>> +&_customlists_load();
>>>>>>>> +
>>>>>>>> +## Global CGI form data
>>>>>>>> +my %cgiparams =3D ();
>>>>>>>> +&Header::getcgihash(\%cgiparams);
>>>>>>>> +
>>>>>>>> +my $action =3D $cgiparams{'ACTION'} // 'NONE';
>>>>>>>> +my $action_key =3D $cgiparams{'KEY'} // ''; # entry being edited, e=
mpty =3D 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 =3D &_rpz_error_tr(204, $action_key);
>>>>>>>> + $action =3D 'NONE';
>>>>>>>> + }
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +## Perform actions
>>>>>>>> +if($action eq 'ZF_SAVE') { ## Save new or modified zonefiles entry
>>>>>>>> + if(&_action_zf_save()) {
>>>>>>>> + $action =3D 'NONE'; # success, return to main page
>>>>>>>> + &_http_prg_redirect();
>>>>>>>> + } else {
>>>>>>>> + $action =3D 'ZF_EDIT'; # error occured, keep editing
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> +} elsif($action eq 'ZF_TOGGLE') { ## Toggle on/off
>>>>>>>> + if(&_action_zf_toggle()) {
>>>>>>>> + $action =3D 'NONE';
>>>>>>>> + &_http_prg_redirect();
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> +} elsif($action eq 'ZF_REMOVE') { ## Remove entry
>>>>>>>> + if(&_action_zf_remove()) {
>>>>>>>> + $action =3D 'NONE';
>>>>>>>> + &_http_prg_redirect();
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> +} elsif($action eq 'CL_SAVE') { ## Save custom lists
>>>>>>>> + if(&_action_cl_save()) {
>>>>>>>> + $action =3D 'NONE';
>>>>>>>> + &_http_prg_redirect();
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> +} elsif($action eq 'RPZ_RELOAD') { ## Reload dns configuration
>>>>>>>> + if(&_action_rpz_reload()) {
>>>>>>>> + $action =3D 'NONE';
>>>>>>>> + &_http_prg_redirect();
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> +} elsif($action eq 'UNB_RESTART') { ## Restart unbound service
>>>>>>>> + if(&_action_unb_restart()) {
>>>>>>>> + $action =3D '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 con=
figuration
>>>>>>>> +sub _zonefiles_load {
>>>>>>>> + # Clean start
>>>>>>>> + %zonefiles =3D ();
>>>>>>>> +
>>>>>>>> + # Source 1: Get the currently enabled zonefiles from rpz-config (e=
xpected format [name]=3D[URL])
>>>>>>>> + my @enabled_files =3D &General::system_output('/usr/sbin/rpz-confi=
g', 'list');
>>>>>>>> +
>>>>>>>> + foreach my $row (@enabled_files) {
>>>>>>>> + chomp($row);
>>>>>>>> +
>>>>>>>> + # Use regex instead of split() to skip non-matching lines
>>>>>>>> + next unless($row =3D~ /^(\w+)=3D(.+)$/);
>>>>>>>> + my ($name, $url) =3D ($1, $2);
>>>>>>>> +
>>>>>>>> + # Unique names are already guaranteed by rpz-config
>>>>>>>> + if(&_rpz_validate_zonefile($name, $url, '', 0) =3D=3D 0) {
>>>>>>>> + # Populate global data hash, mark all found entries as enabled
>>>>>>>> + my %entry =3D ('enabled' =3D> 'on',
>>>>>>>> + 'url' =3D> $url,
>>>>>>>> + 'remark' =3D> $Lang::tr{'rpz zf imported'});
>>>>>>>> +
>>>>>>>> + $zonefiles{$name} =3D \%entry;
>>>>>>>> + }
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Source 2: Get additional data and disabled entries from configur=
ation file
>>>>>>>> + my %configured_files =3D ();
>>>>>>>> + &General::readhasharray($ZONEFILES_CONF, \%configured_files);
>>>>>>>> +
>>>>>>>> + foreach my $row (values (%configured_files)) {
>>>>>>>> + my ($name, $enabled, $url, $remark) =3D @$row;
>>>>>>>> + $remark //=3D "";
>>>>>>>> +
>>>>>>>> + 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'} =3D $remark;
>>>>>>>> + } else {
>>>>>>>> + # Skip entry if it is marked as enabled but not found by rpz-confi=
g. It was then deleted manually
>>>>>>>> + if($enabled ne 'on') {
>>>>>>>> + # Populate global data hash
>>>>>>>> + my %entry =3D ('enabled' =3D> 'off',
>>>>>>>> + 'url' =3D> $url // "",
>>>>>>>> + 'remark' =3D> $remark);
>>>>>>>> +
>>>>>>>> + $zonefiles{$name} =3D \%entry;
>>>>>>>> + }
>>>>>>>> + }
>>>>>>>> + }
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Save internal zonefiles configuration
>>>>>>>> +sub _zonefiles_save_conf {
>>>>>>>> + my $index =3D 0;
>>>>>>>> + my %export =3D ();
>>>>>>>> +
>>>>>>>> + # Loop trough all zonefiles and create "hasharray" type export
>>>>>>>> + foreach my $name (keys %zonefiles) {
>>>>>>>> + my @entry =3D ($name,
>>>>>>>> + $zonefiles{$name}{'enabled'},
>>>>>>>> + $zonefiles{$name}{'url'},
>>>>>>>> + $zonefiles{$name}{'remark'});
>>>>>>>> +
>>>>>>>> + $export{$index++} =3D \@entry;
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + &General::writehasharray($ZONEFILES_CONF, \%export);
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Load custom lists from rpz-config and the internal configuration
>>>>>>>> +sub _customlists_load {
>>>>>>>> + # Clean start
>>>>>>>> + %customlists =3D ();
>>>>>>>> +
>>>>>>>> + # Load configuration file
>>>>>>>> + my %lists_conf =3D ();
>>>>>>>> + &General::readhasharray($CUSTOMLISTS_CONF, \%lists_conf);
>>>>>>>> +
>>>>>>>> + # Get list options, enabled by default to start import
>>>>>>>> + $customlists{'allow'}{'enabled'} =3D $lists_conf{2}[0] // 'on';
>>>>>>>> + $customlists{'block'}{'enabled'} =3D $lists_conf{2}[1] // 'on';
>>>>>>>> +
>>>>>>>> + # Import enabled list from rpz-config, otherwise retrieve stored o=
r empty list from configuration file
>>>>>>>> + if($customlists{'allow'}{'enabled'} eq 'on') {
>>>>>>>> + &_customlist_import('allow', $RPZ_ALLOWLIST);
>>>>>>>> + } else {
>>>>>>>> + $customlists{'allow'}{'list'} =3D $lists_conf{0} // [];
>>>>>>>> + }
>>>>>>>> + if($customlists{'block'}{'enabled'} eq 'on') {
>>>>>>>> + &_customlist_import('block', $RPZ_BLOCKLIST);
>>>>>>>> + } else {
>>>>>>>> + $customlists{'block'}{'list'} =3D $lists_conf{1} // [];
>>>>>>>> + }
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Save internal custom lists configuration
>>>>>>>> +sub _customlists_save_conf {
>>>>>>>> + my %export =3D ();
>>>>>>>> +
>>>>>>>> + # Match IDs with import function
>>>>>>>> + $export{0} =3D $customlists{'allow'}{'list'};
>>>>>>>> + $export{1} =3D $customlists{'block'}{'list'};
>>>>>>>> + $export{2} =3D [$customlists{'allow'}{'enabled'}, $customlists{'bl=
ock'}{'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) =3D @_;
>>>>>>>> + my @list =3D ();
>>>>>>>> +
>>>>>>>> + # File exists, load and check all lines
>>>>>>>> + if(-f $filename) {
>>>>>>>> + open(my $FH, '<', $filename) or die "Can't read $filename: $!";
>>>>>>>> + while(my $line =3D <$FH>) {
>>>>>>>> + chomp($line);
>>>>>>>> + push(@list, $line);
>>>>>>>> + }
>>>>>>>> + close($FH);
>>>>>>>> +
>>>>>>>> + # Clean up imported data
>>>>>>>> + &_rpz_validate_customlist(\@list, 1);
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + $customlists{$listname}{'list'} =3D \@list;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Export a custom list to plain file or clear file if list is disab=
led
>>>>>>>> +sub _customlist_export {
>>>>>>>> + my ($listname, $filename) =3D @_;
>>>>>>>> + 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) =3D @_;
>>>>>>>> + $title ||=3D $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=3D"tbl" width=3D"100%">
>>>>>>>> + <tr>
>>>>>>>> + <th>$Lang::tr{'name'}</th>
>>>>>>>> + <th>URL</th>
>>>>>>>> + <th>$Lang::tr{'remark'}</th>
>>>>>>>> + <th colspan=3D"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 =3D ($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=3D"center" width=3D"5%">
>>>>>>>> + <form method=3D"post" action=3D"$ENV{'SCRIPT_NAME'}">
>>>>>>>> + <input type=3D"hidden" name=3D"KEY" value=3D"$name">
>>>>>>>> + <input type=3D"hidden" name=3D"ACTION" value=3D"ZF_TOGGLE">
>>>>>>>> + <input type=3D"image" src=3D"/images/$zonefiles{$name}{'enabled'}.=
gif" title=3D"$toggle_tr" alt=3D"$toggle_tr">
>>>>>>>> + </form>
>>>>>>>> + </td>
>>>>>>>> + <td align=3D"center" width=3D"5%">
>>>>>>>> + <form method=3D"post" action=3D"$ENV{'SCRIPT_NAME'}">
>>>>>>>> + <input type=3D"hidden" name=3D"KEY" value=3D"$name">
>>>>>>>> + <input type=3D"hidden" name=3D"ACTION" value=3D"ZF_EDIT">
>>>>>>>> + <input type=3D"image" src=3D"/images/edit.gif" title=3D"$Lang::tr{=
'edit'}" alt=3D"$Lang::tr{'edit'}">
>>>>>>>> + </form>
>>>>>>>> + </td>
>>>>>>>> + <td align=3D"center" width=3D"5%">
>>>>>>>> + <form method=3D"post" action=3D"$ENV{'SCRIPT_NAME'}">
>>>>>>>> + <input type=3D"hidden" name=3D"KEY" value=3D"$name">
>>>>>>>> + <input type=3D"hidden" name=3D"ACTION" value=3D"ZF_REMOVE">
>>>>>>>> + <input type=3D"image" src=3D"/images/delete.gif" title=3D"$Lang::t=
r{'remove'}" alt=3D"$Lang::tr{'remove'}">
>>>>>>>> + </form>
>>>>>>>> + </td>
>>>>>>>> + </tr>
>>>>>>>> +END
>>>>>>>> +;
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Disable reload button if not needed
>>>>>>>> + my $reload_state =3D &_rpz_needs_reload() ? "" : " disabled";
>>>>>>>> +
>>>>>>>> + print <<END
>>>>>>>> +</table>
>>>>>>>> +
>>>>>>>> +<div class=3D"right">
>>>>>>>> + <form method=3D"post" action=3D"$ENV{'SCRIPT_NAME'}">
>>>>>>>> + <input type=3D"hidden" name=3D"KEY" value=3D"">
>>>>>>>> + <button type=3D"submit" name=3D"ACTION" value=3D"ZF_EDIT">$Lang::t=
r{'add'}</button>
>>>>>>>> + <button type=3D"submit" name=3D"ACTION" value=3D"RPZ_RELOAD" class=
=3D"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 value=
s (allows user to edit after error)
>>>>>>>> + $cgiparams{'ZF_NAME'} //=3D $action_key;
>>>>>>>> + $cgiparams{'ZF_URL'} //=3D $zonefiles{$action_key}{'url'};
>>>>>>>> + $cgiparams{'ZF_REMARK'} //=3D $zonefiles{$action_key}{'remark'};
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Fallback to empty form
>>>>>>>> + $cgiparams{'ZF_NAME'} //=3D "";
>>>>>>>> + $cgiparams{'ZF_URL'} //=3D "";
>>>>>>>> + $cgiparams{'ZF_REMARK'} //=3D "";
>>>>>>>> +
>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz zf editor'});
>>>>>>>> +
>>>>>>>> + print <<END
>>>>>>>> +<form method=3D"post" action=3D"$ENV{'SCRIPT_NAME'}">
>>>>>>>> +<input type=3D"hidden" name=3D"KEY" value=3D"$action_key">
>>>>>>>> +<table width=3D"100%">
>>>>>>>> + <tr>
>>>>>>>> + <td width=3D"20%">$Lang::tr{'name'}:&nbsp;<img src=3D"/blob.gif" a=
lt=3D"*"></td>
>>>>>>>> + <td><input type=3D"text" name=3D"ZF_NAME" value=3D"$cgiparams{'ZF_=
NAME'}" size=3D"40" maxlength=3D"32" title=3D"$Lang::tr{'rpz zf remark info'}=
" pattern=3D"[a-zA-Z0-9_]{1,32}" required></td>
>>>>>>>> + </tr>
>>>>>>>> + <tr>
>>>>>>>> + <td width=3D"20%">URL:&nbsp;<img src=3D"/blob.gif" alt=3D"*"></td>
>>>>>>>> + <td><input type=3D"url" name=3D"ZF_URL" value=3D"$cgiparams{'ZF_UR=
L'}" size=3D"40" maxlength=3D"128" required></td>
>>>>>>>> + </tr>
>>>>>>>> + <tr>
>>>>>>>> + <td width=3D"20%">$Lang::tr{'remark'}:</td>
>>>>>>>> + <td><input type=3D"text" name=3D"ZF_REMARK" value=3D"$cgiparams{'Z=
F_REMARK'}" size=3D"40" maxlength=3D"32"></td>
>>>>>>>> + </tr>
>>>>>>>> + <tr>
>>>>>>>> + <td colspan=3D"2"><hr></td>
>>>>>>>> + </tr>
>>>>>>>> + <tr>
>>>>>>>> + <td width=3D"55%"><img src=3D"/blob.gif" alt=3D"*">&nbsp;$Lang::tr=
{'required field'}</td>
>>>>>>>> + <td align=3D"right"><button type=3D"submit" name=3D"ACTION" value=
=3D"ZF_SAVE">$Lang::tr{'save'}</button></td>
>>>>>>>> + </tr>
>>>>>>>> +</table>
>>>>>>>> +</form>
>>>>>>>> +
>>>>>>>> +<div class=3D"right">
>>>>>>>> + <form method=3D"post" action=3D"$ENV{'SCRIPT_NAME'}">
>>>>>>>> + <button type=3D"submit" name=3D"ACTION" value=3D"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'} =3D join("\n", @{$customlists{'allow'}{'l=
ist'}});
>>>>>>>> + $cgiparams{'BLOCK_LIST'} =3D join("\n", @{$customlists{'block'}{'l=
ist'}});
>>>>>>>> +
>>>>>>>> + $cgiparams{'ALLOW_ENABLED'} =3D ($customlists{'allow'}{'enabled'} =
eq 'on') ? 'on' : undef;
>>>>>>>> + $cgiparams{'BLOCK_ENABLED'} =3D ($customlists{'block'}{'enabled'} =
eq 'on') ? 'on' : undef;
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Fallback to empty form
>>>>>>>> + $cgiparams{'ALLOW_LIST'} //=3D "";
>>>>>>>> + $cgiparams{'BLOCK_LIST'} //=3D "";
>>>>>>>> +
>>>>>>>> + # HTML checkboxes, unchecked =3D no or undef value in POST data
>>>>>>>> + my %checked =3D ();
>>>>>>>> + $checked{'ALLOW_ENABLED'} =3D (defined $cgiparams{'ALLOW_ENABLED'}=
) ? " checked" : "";
>>>>>>>> + $checked{'BLOCK_ENABLED'} =3D (defined $cgiparams{'BLOCK_ENABLED'}=
) ? " checked" : "";
>>>>>>>> +
>>>>>>>> + # Disable reload button if not needed
>>>>>>>> + my $reload_state =3D &_rpz_needs_reload() ? "" : " disabled";
>>>>>>>> +
>>>>>>>> + &Header::openbox('100%', 'left', $Lang::tr{'rpz cl'});
>>>>>>>> +
>>>>>>>> + print <<END
>>>>>>>> +<form method=3D"post" action=3D"$ENV{'SCRIPT_NAME'}">
>>>>>>>> +<table width=3D"100%">
>>>>>>>> + <tr>
>>>>>>>> + <td colspan=3D"2"><b>$Lang::tr{'rpz cl allow'}</b><br>$Lang::tr{'r=
pz cl allow info'}</td>
>>>>>>>> + <td colspan=3D"2"><b>$Lang::tr{'rpz cl block'}</b><br>$Lang::tr{'r=
pz cl block info'}</td>
>>>>>>>> + </tr>
>>>>>>>> + <tr>
>>>>>>>> + <td colspan=3D"2"><textarea name=3D"ALLOW_LIST" class=3D"domainlis=
t" cols=3D"45">
>>>>>>>> +$cgiparams{'ALLOW_LIST'}</textarea></td>
>>>>>>>> + <td colspan=3D"2"><textarea name=3D"BLOCK_LIST" class=3D"domainlis=
t" cols=3D"45">
>>>>>>>> +$cgiparams{'BLOCK_LIST'}</textarea></td>
>>>>>>>> + </tr>
>>>>>>>> + <tr>
>>>>>>>> + <td><label for=3D"allow_enabled">$Lang::tr{'rpz cl allow enable'}<=
/label></td>
>>>>>>>> + <td width=3D"15%"><input type=3D"checkbox" name=3D"ALLOW_ENABLED" =
id=3D"allow_enabled"$checked{'ALLOW_ENABLED'}></td>
>>>>>>>> + <td><label for=3D"block_enabled">$Lang::tr{'rpz cl block enable'}<=
/label></td>
>>>>>>>> + <td width=3D"15%"><input type=3D"checkbox" name=3D"BLOCK_ENABLED" =
id=3D"block_enabled"$checked{'BLOCK_ENABLED'}></td>
>>>>>>>> + </tr>
>>>>>>>> + <tr>
>>>>>>>> + <td colspan=3D"4"><hr></td>
>>>>>>>> + </tr>
>>>>>>>> + <tr>
>>>>>>>> + <td align=3D"right" colspan=3D"4">
>>>>>>>> + <button type=3D"submit" name=3D"ACTION" value=3D"CL_SAVE">$Lang::t=
r{'save'}</button>
>>>>>>>> + <button type=3D"submit" name=3D"ACTION" value=3D"RPZ_RELOAD" class=
=3D"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 =3D false; // Keyboard events attached
>>>>>>>> + var keyModify =3D false; // Modifier key pressed
>>>>>>>> + var mouseHover =3D false; // Mouse over commit button
>>>>>>>> + var btnModified =3D false; // Button modified to "Restart"
>>>>>>>> +
>>>>>>>> + // Document-level key events, enable only while cursor is over but=
ton
>>>>>>>> + function attachKeyEvents() {
>>>>>>>> + if(keyEventsOn) {
>>>>>>>> + return;
>>>>>>>> + }
>>>>>>>> + keyEventsOn =3D true;
>>>>>>>> +
>>>>>>>> + jq(document).on("keydown.rpz", function(event) {
>>>>>>>> + if((!keyModify) && event.shiftKey) {
>>>>>>>> + keyModify =3D true;
>>>>>>>> + handleModify();
>>>>>>>> + }
>>>>>>>> + });
>>>>>>>> + jq(document).on("keyup.rpz", function(event) {
>>>>>>>> + if(keyModify && (!event.shiftKey)) {
>>>>>>>> + keyModify =3D false;
>>>>>>>> + handleModify();
>>>>>>>> + }
>>>>>>>> + });
>>>>>>>> + }
>>>>>>>> + function removeKeyEvents() {
>>>>>>>> + keyModify =3D false;
>>>>>>>> + if(keyEventsOn) {
>>>>>>>> + jq(document).off("keydown.rpz keyup.rpz");
>>>>>>>> + keyEventsOn =3D false;
>>>>>>>> + }
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + // Attach mouse hover events to commit buttons
>>>>>>>> + function attachMouseEvents() {
>>>>>>>> + jq("button.commit").on("mouseenter", function(event) {
>>>>>>>> + if(!mouseHover) {
>>>>>>>> + mouseHover =3D true;
>>>>>>>> + attachKeyEvents();
>>>>>>>> + // Handle already pressed key
>>>>>>>> + keyModify =3D !!(event.shiftKey);
>>>>>>>> + handleModify();
>>>>>>>> + }
>>>>>>>> + });
>>>>>>>> +
>>>>>>>> + // Cursor moved away: Disable key listener to minimize events
>>>>>>>> + jq("button.commit").on("mouseleave", function() {
>>>>>>>> + if(mouseHover) {
>>>>>>>> + mouseHover =3D false;
>>>>>>>> + removeKeyEvents();
>>>>>>>> + handleModify();
>>>>>>>> + }
>>>>>>>> + });
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + // Modify commit button
>>>>>>>> + function handleModify() {
>>>>>>>> + let modify =3D mouseHover && keyModify;
>>>>>>>> + if(btnModified !=3D 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 =3D 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 =3D 0;
>>>>>>>> + my $enabled =3D $zonefiles{$action_key}{'enabled'};
>>>>>>>> +
>>>>>>>> + # Perform toggle action
>>>>>>>> + if($enabled eq 'on') {
>>>>>>>> + $enabled =3D 'off';
>>>>>>>> + $result =3D &General::system('/usr/sbin/rpz-config', 'remove', $ac=
tion_key, '--no-reload');
>>>>>>>> + } else {
>>>>>>>> + $enabled =3D 'on';
>>>>>>>> + $result =3D &General::system('/usr/sbin/rpz-config', 'add', $actio=
n_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'} =3D $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 =3D &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 =3D "";
>>>>>>>> +
>>>>>>>> + return 1;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Create or update zonefile entry
>>>>>>>> +# Returns undef if gui needs to stay in editor mode
>>>>>>>> +sub _action_zf_save {
>>>>>>>> + my $result =3D 0;
>>>>>>>> +
>>>>>>>> + my $name =3D $cgiparams{'ZF_NAME'} // "";
>>>>>>>> + my $url =3D $cgiparams{'ZF_URL'} // "";
>>>>>>>> + my $remark =3D $cgiparams{'ZF_REMARK'} // "";
>>>>>>>> + my $enabled =3D 'on'; # Enable new entries by default
>>>>>>>> +
>>>>>>>> + # Note on variables:
>>>>>>>> + # name =3D unique key, will be used to address the entry
>>>>>>>> + # action_key =3D name of the entry being edited, empty for new ent=
ry
>>>>>>>> +
>>>>>>>> + # Only check for unique name if it changed
>>>>>>>> + # (this also checks new entries because the action_key is empty in=
 this case)
>>>>>>>> + $result =3D &_rpz_validate_zonefile($name, $url, $remark, (lc($nam=
e) 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'} =3D $remark;
>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>> +
>>>>>>>> + return 1;
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Entry was changed and needs to be recreated, preserve status
>>>>>>>> + $enabled =3D $zonefiles{$action_key}{'enabled'};
>>>>>>>> +
>>>>>>>> + # Remove from rpz-config
>>>>>>>> + return unless &_action_zf_remove();
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Add new entry to rpz-config
>>>>>>>> + if($enabled eq 'on') {
>>>>>>>> + $result =3D &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 =3D ('enabled' =3D> $enabled,
>>>>>>>> + 'url' =3D> $url,
>>>>>>>> + 'remark' =3D> $remark);
>>>>>>>> +
>>>>>>>> + $zonefiles{$name} =3D \%entry;
>>>>>>>> + &_zonefiles_save_conf();
>>>>>>>> +
>>>>>>>> + return 1;
>>>>>>>> +}
>>>>>>>> +
>>>>>>>> +# Save custom lists
>>>>>>>> +sub _action_cl_save {
>>>>>>>> + return unless((defined $cgiparams{'ALLOW_LIST'}) && (defined $cgip=
arams{'BLOCK_LIST'}));
>>>>>>>> +
>>>>>>>> + my $result =3D 0;
>>>>>>>> +
>>>>>>>> + my @allowlist =3D split(/\R/, $cgiparams{'ALLOW_LIST'});
>>>>>>>> + my @blocklist =3D split(/\R/, $cgiparams{'BLOCK_LIST'});
>>>>>>>> +
>>>>>>>> + # Validate lists
>>>>>>>> + $result =3D &_rpz_validate_customlist(\@allowlist);
>>>>>>>> + if($result !=3D 0) {
>>>>>>>> + $errormessage =3D &_rpz_error_tr(202, $result);
>>>>>>>> + return;
>>>>>>>> + }
>>>>>>>> + $result =3D &_rpz_validate_customlist(\@blocklist);
>>>>>>>> + if($result !=3D 0) {
>>>>>>>> + $errormessage =3D &_rpz_error_tr(203, $result);
>>>>>>>> + return;
>>>>>>>> + }
>>>>>>>> +
>>>>>>>> + # Add to global data hash and save changes
>>>>>>>> + $customlists{'allow'}{'list'} =3D \@allowlist;
>>>>>>>> + $customlists{'block'}{'list'} =3D \@blocklist;
>>>>>>>> + $customlists{'allow'}{'enabled'} =3D (defined $cgiparams{'ALLOW_EN=
ABLED'}) ? 'on' : 'off';
>>>>>>>> + $customlists{'block'}{'enabled'} =3D (defined $cgiparams{'BLOCK_EN=
ABLED'}) ? 'on' : 'off';
>>>>>>>> +
>>>>>>>> + &_customlists_save_conf();
>>>>>>>> + &_customlist_export('allow', $RPZ_ALLOWLIST);
>>>>>>>> + &_customlist_export('block', $RPZ_BLOCKLIST);
>>>>>>>> +
>>>>>>>> + # Make new lists, request service reload on success
>>>>>>>> + $result =3D &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 =3D &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) =3D @_;
>>>>>>>> + $append //=3D '';
>>>>>>>> +
>>>>>>>> + # Translate numeric exit codes
>>>>>>>> + if(looks_like_number($error)) {
>>>>>>>> + if(defined $Lang::tr{"rpz exitcode $error"}) {
>>>>>>>> + $error =3D $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) =3D @_;
>>>>>>>> + $request_reload //=3D 0;
>>>>>>>> +
>>>>>>>> + # exitcode 0 =3D success
>>>>>>>> + if($result !=3D 0) {
>>>>>>>> + $errormessage =3D &_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 =3D check for unique name
>>>>>>>> +sub _rpz_validate_zonefile {
>>>>>>>> + my ($name, $url, $remark, $unique) =3D @_;
>>>>>>>> + $unique //=3D 1;
>>>>>>>> +
>>>>>>>> + unless($name =3D~ /^[a-zA-Z0-9_]{1,32}$/) {
>>>>>>>> + return 101;
>>>>>>>> + }
>>>>>>>> + unless($url =3D~ /^[\w+\.:;\/\\&@#%?=3D\-~|!]{1,128}$/) {
>>>>>>>> + return 105;
>>>>>>>> + }
>>>>>>>> + unless($remark =3D~ /^[\w \-()\.:;*\/\\?!&=3D]{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 failur=
e. Check for non-zero results.
>>>>>>>> +# listref =3D array reference, cleanup =3D remove invalid entries i=
nstead of returning an error
>>>>>>>> +sub _rpz_validate_customlist {
>>>>>>>> + my ($listref, $cleanup) =3D @_;
>>>>>>>> + $cleanup //=3D 0;
>>>>>>>> +
>>>>>>>> + foreach my $index (reverse 0..$#{$listref}) {
>>>>>>>> + my $row =3D @$listref[$index];
>>>>>>>> + next unless($row); # Skip/allow empty lines
>>>>>>>> +
>>>>>>>> + # Reject/remove everything besides wildcard domains and remarks
>>>>>>>> + if((not &General::validwildcarddomainname($row)) && (not $row =3D~=
 /^;[\w \-()\.:;*\/\\?!&=3D]*$/)) {
>>>>>>>> + 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 =3D "https://$ENV{'SERVER_NAME'}:$ENV{'SERVER_PORT'}$=
ENV{'SCRIPT_NAME'}";
>>>>>>>> + print "Status: 303 See Other\n";
>>>>>>>> + print "Location: $location\n";
>>>>>>>> +}
>>>>>>>> diff --git a/lfs/rpz b/lfs/rpz
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..7ddbc38e5
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/lfs/rpz
>>>>>>>> @@ -0,0 +1,96 @@
>>>>>>>> +###################################################################=
############
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +# IPFire.org - A linux based firewall                              =
           #
>>>>>>>> +# Copyright (C) 2024  IPFire Team  <info(a)ipfire.org>             =
             #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +# This program is free software: you can redistribute it and/or mod=
ify        #
>>>>>>>> +# it under the terms of the GNU General Public License as published=
 by        #
>>>>>>>> +# the Free Software Foundation, either version 3 of the License, or=
           #
>>>>>>>> +# (at your option) any later version.                              =
           #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +# This program is distributed in the hope that it will be useful,  =
           #
>>>>>>>> +# but WITHOUT ANY WARRANTY; without even the implied warranty of   =
           #
>>>>>>>> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    =
           #
>>>>>>>> +# GNU General Public License for more details.                     =
           #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +# You should have received a copy of the GNU General Public License=
           #
>>>>>>>> +# along with this program.  If not, see <http://www.gnu.org/license=
s/>.       #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +###################################################################=
############
>>>>>>>> +
>>>>>>>> +###################################################################=
############
>>>>>>>> +#  Definitions
>>>>>>>> +###################################################################=
############
>>>>>>>> +
>>>>>>>> +include Config
>>>>>>>> +
>>>>>>>> +SUMMARY    =3D response policy zone - RPZ reputation system for unb=
ound DNS
>>>>>>>> +
>>>>>>>> +VER        =3D 1.0.0
>>>>>>>> +
>>>>>>>> +THISAPP    =3D rpz-$(VER)
>>>>>>>> +DIR_APP    =3D $(DIR_SRC)/$(THISAPP)
>>>>>>>> +TARGET     =3D $(DIR_INFO)/$(THISAPP)
>>>>>>>> +
>>>>>>>> +PROG       =3D rpz
>>>>>>>> +PAK_VER    =3D 18
>>>>>>>> +
>>>>>>>> +DEPS       =3D
>>>>>>>> +
>>>>>>>> +SERVICES   =3D
>>>>>>>> +
>>>>>>>> +###################################################################=
############
>>>>>>>> +# Top-level Rules
>>>>>>>> +###################################################################=
############
>>>>>>>> +
>>>>>>>> +install : $(TARGET)
>>>>>>>> +
>>>>>>>> +check :
>>>>>>>> +
>>>>>>>> +download :
>>>>>>>> +
>>>>>>>> +b2 :
>>>>>>>> +
>>>>>>>> +dist:
>>>>>>>> + @$(PAK)
>>>>>>>> +
>>>>>>>> +###################################################################=
############
>>>>>>>> +# Installation Details
>>>>>>>> +###################################################################=
############
>>>>>>>> +
>>>>>>>> +$(TARGET) :
>>>>>>>> + @$(PREBUILD)
>>>>>>>> + @rm -rf $(DIR_APP)
>>>>>>>> +
>>>>>>>> + #  RPZ scripts
>>>>>>>> + install --verbose --mode=3D755 \
>>>>>>>> +  $(DIR_CONF)/rpz/{rpz-config,rpz-metrics,rpz-sleep,rpz-make,rpz-fu=
nctions} \
>>>>>>>> +  --target-directory=3D/usr/sbin
>>>>>>>> +
>>>>>>>> + #  RPZ config files
>>>>>>>> + mkdir -pv /etc/unbound/local.d
>>>>>>>> + install --verbose --mode=3D644 --owner=3Dnobody --group=3Dnobody \
>>>>>>>> +  $(DIR_CONF)/rpz/00-rpz.conf \
>>>>>>>> +  --target-directory=3D/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=3D004 $(DIR_CONF)/rpz/rpz.*.pl \
>>>>>>>> +  --target-directory=3D/var/ipfire/addon-lang
>>>>>>>> +
>>>>>>>> + # Install backup definition
>>>>>>>> + cp -vf $(DIR_CONF)/backup/includes/rpz /var/ipfire/backup/addons/i=
ncludes/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 +=3D consumed_space / 1024 / 1024 ))
>>>>>>>> + (( free_space +=3D consumed_space / 1024 / 1024 ))
>>>>>>>> done <<< "$(du --summarize --bytes "${BUILD_DIR}" "${IMAGES_DIR}" "$=
{LOG_DIR}" 2>/dev/null)"
>>>>>>>> fi
>>>>>>>>
>>>>>>>> @@ -2087,6 +2087,7 @@ build_system() {
>>>>>>>> lfsmake2 btrfs-progs
>>>>>>>> lfsmake2 inotify-tools
>>>>>>>> lfsmake2 grub-btrfs
>>>>>>>> + lfsmake2 rpz
>>>>>>>>
>>>>>>>> lfsmake2 linux
>>>>>>>> lfsmake2 rtl8812au
>>>>>>>> diff --git a/src/paks/rpz/install.sh b/src/paks/rpz/install.sh
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..ef99bf742
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/src/paks/rpz/install.sh
>>>>>>>> @@ -0,0 +1,36 @@
>>>>>>>> +#!/bin/bash
>>>>>>>> +###################################################################=
############
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +#  IPFire.org - A linux based firewall                             =
           #
>>>>>>>> +#  Copyright (C) 2024  IPFire Team  <info(a)ipfire.org>            =
             #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +#  This program is free software: you can redistribute it and/or mo=
dify       #
>>>>>>>> +#  it under the terms of the GNU General Public License as publishe=
d by       #
>>>>>>>> +#  the Free Software Foundation, either version 3 of the License, o=
r          #
>>>>>>>> +#  (at your option) any later version.                             =
           #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +#  This program is distributed in the hope that it will be useful, =
           #
>>>>>>>> +#  but WITHOUT ANY WARRANTY; without even the implied warranty of  =
           #
>>>>>>>> +#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the   =
           #
>>>>>>>> +#  GNU General Public License for more details.                    =
           #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +#  You should have received a copy of the GNU General Public Licens=
e          #
>>>>>>>> +#  along with this program.  If not, see <http://www.gnu.org/licens=
es/>.      #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +###################################################################=
############
>>>>>>>> +#
>>>>>>>> +. /opt/pakfire/lib/functions.sh
>>>>>>>> +extract_files
>>>>>>>> +restore_backup ${NAME}
>>>>>>>> +
>>>>>>>> +# fix user created files
>>>>>>>> +chown --verbose --recursive nobody:nobody \
>>>>>>>> + /var/ipfire/dns/rpz    \
>>>>>>>> + /etc/unbound/zonefiles \
>>>>>>>> + /etc/unbound/local.d
>>>>>>>> +
>>>>>>>> +# Update Language cache
>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>> +
>>>>>>>> +#  restart unbound to load config file
>>>>>>>> +/etc/init.d/unbound restart
>>>>>>>> diff --git a/src/paks/rpz/uninstall.sh b/src/paks/rpz/uninstall.sh
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..e11427df3
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/src/paks/rpz/uninstall.sh
>>>>>>>> @@ -0,0 +1,38 @@
>>>>>>>> +#!/bin/bash
>>>>>>>> +###################################################################=
############
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +#  IPFire.org - A linux based firewall                             =
           #
>>>>>>>> +#  Copyright (C) 2024  IPFire Team  <info(a)ipfire.org>            =
             #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +#  This program is free software: you can redistribute it and/or mo=
dify       #
>>>>>>>> +#  it under the terms of the GNU General Public License as publishe=
d by       #
>>>>>>>> +#  the Free Software Foundation, either version 3 of the License, o=
r          #
>>>>>>>> +#  (at your option) any later version.                             =
           #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +#  This program is distributed in the hope that it will be useful, =
           #
>>>>>>>> +#  but WITHOUT ANY WARRANTY; without even the implied warranty of  =
           #
>>>>>>>> +#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the   =
           #
>>>>>>>> +#  GNU General Public License for more details.                    =
           #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +#  You should have received a copy of the GNU General Public Licens=
e          #
>>>>>>>> +#  along with this program.  If not, see <http://www.gnu.org/licens=
es/>.      #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +###################################################################=
############
>>>>>>>> +#
>>>>>>>> +. /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: Connect=
ion refused for 127.0.0.1 port 8953"
>>>>>>>> +/bin/rm --verbose --force /etc/unbound/local.d/*.rpz.conf
>>>>>>>> +
>>>>>>>> +# Update Language cache
>>>>>>>> +/usr/local/bin/update-lang-cache
>>>>>>>> +
>>>>>>>> +#  start unbound to load unbound config file
>>>>>>>> +/etc/init.d/unbound start
>>>>>>>> diff --git a/src/paks/rpz/update.sh b/src/paks/rpz/update.sh
>>>>>>>> new file mode 100644
>>>>>>>> index 000000000..9bc340bc6
>>>>>>>> --- /dev/null
>>>>>>>> +++ b/src/paks/rpz/update.sh
>>>>>>>> @@ -0,0 +1,52 @@
>>>>>>>> +#!/bin/bash
>>>>>>>> +###################################################################=
############
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +#  IPFire.org - A linux based firewall                             =
           #
>>>>>>>> +#  Copyright (C) 2024  IPFire Team  <info(a)ipfire.org>            =
             #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +#  This program is free software: you can redistribute it and/or mo=
dify       #
>>>>>>>> +#  it under the terms of the GNU General Public License as publishe=
d by       #
>>>>>>>> +#  the Free Software Foundation, either version 3 of the License, o=
r          #
>>>>>>>> +#  (at your option) any later version.                             =
           #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +#  This program is distributed in the hope that it will be useful, =
           #
>>>>>>>> +#  but WITHOUT ANY WARRANTY; without even the implied warranty of  =
           #
>>>>>>>> +#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the   =
           #
>>>>>>>> +#  GNU General Public License for more details.                    =
           #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +#  You should have received a copy of the GNU General Public Licens=
e          #
>>>>>>>> +#  along with this program.  If not, see <http://www.gnu.org/licens=
es/>.      #
>>>>>>>> +#                                                                  =
           #
>>>>>>>> +###################################################################=
############
>>>>>>>> +#
>>>>>>>> +. /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
>>>>>>>> --=20
>>>>>>>> 2.39.5
>>>>>>>>
>>>>>>>
>>>>>>
>>>>>> Jon
>>>>>>
>>>>>>
>>>>>> --=20
>>>>>> Jon Murphy
>>>>>> jon.murphy(a)ipfire.org
>>>>
>>>>
>>>> Jon
>>>>
>>>>
>>>> --=20
>>>> Jon Murphy
>>>> jon.murphy(a)ipfire.org
>=20
>=20


--===============1659952175765241016==--