ONO::ToolBox::Security

package ONO::ToolBox::Security;
################################################################################
# COPYRIGHT / LICENSE #
################################################################################
#
# This file is part of the ONO Software Project.
#
# Copyright (C) 2000-2025 Jos KIRPS [ www.kirps.com | jos_AT_kirps_DOT_com ]
# and The Joopita Project [ www.joopita.org | contact_AT_joopita_DOT_com ]
#
# This file, as well as other parts of the ONO Software Project or related
# elements, are FREE SOFTWARE available under the ARTISTIC LICENSE 2.0.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# For the full license, see /ono/osr/license/LICENSE.txt, or write to
# jos_AT_kirps_DOT_com or contact_AT_joopita_DOT_com.
#
################################################################################
# END OF COPYRIGHT / LICENSE, HERE COMES THE CODE ... #
################################################################################


use strict;

use ONO::IO;
use ONO::Lib::Basic;

use ONO::Lib::Web::BotDetect;
use ONO::Lib::Web::Client;

###############################################################################
# wiki engine
###############################################################################

sub bad_bot_protection_test {

#: This will test if the User Agent points to an evil bot, or if the client
#: IP address is in an evil range. If this returns 1, then it may be a
#: good idea to limit or block client access.

my $test;

# for local testing proposes only:

if (ONO::IO->devstation()) {

# $test = 1;

}

# lock out evils bots, as well as archive.org:

if (ONO::Lib::Web::BotDetect->evil_bot() || ONO::Lib::Web::BotDetect->crawler_bot() || ONO::Lib::Web::BotDetect->archive_bot() || $test) {

$test = 1;

}

# be careful with entire IP ranges, some are used by bing for example (www.bing.com/toolbox/bingbot.json) :

my $IP = ONO::Lib::Web::Client->ip();

if ($IP =~ /^(3|13|18|20|34|35|40|44|51|52|54)\./) {

if ($IP =~ /^(3|34|54)\./) {

$test = 1;

} else {

if ($IP =~ /^13\.(.*?)\.(.*?)\.(.*?)$/) {

# bing: 13.66, 13.67, 13.69, 13.71

if ($1 > 23 && $1 < 127 && $1 != 66 && $1 != 67 && $1 != 68 && $1 != 69 && $1 != 70 && $1 != 71) {
$test = 1;
}

if ($1 > 165) {
$test = 1;
}

}

if ($IP =~ /^18\.(.*?)\.(.*?)\.(.*?)$/) {

if ($1 > 31) {
$test = 1;
}

}

if ($IP =~ /^20\.(.*?)\.(.*?)\.(.*?)$/) {

# bing: 20.15, 20.36, 20.43, 20.74, 20.79

if ($1 != 15 && $1 != 36 && $1 != 43 && $1 != 74 && $1 != 79) {
$test = 1;
}

}

if ($IP =~ /^35\.(.*?)\.(.*?)\.(.*?)$/) {


if ($1 > 71 && $1 < 128) {
$test = 1;
}

if ($1 > 151) {
$test = 1;
}

}

if ($IP =~ /^40\.(.*?)\.(.*?)\.(.*?)$/) {

# bing: 40.77, 40.79

if ($1 > 63 && $1 < 128 && $1 != 77 && $1 != 79) {
$test = 1;
}

if ($1 > 159 && $1 < 211) {
$test = 1;
}

}

if ($IP =~ /^44\.(.*?)\.(.*?)\.(.*?)$/) {

if ($1 > 191) {
$test = 1;
}

}

if ($IP =~ /^51\.(.*?)\.(.*?)\.(.*?)$/) {

# bing: 51.105

if ($1 > 2 && $1 < 111 && $1 != 105) {
$test = 1;
}

if ($1 > 115 && $1 < 135) {
$test = 1;
}

if ($1 > 149 && $1 < 238) {
$test = 1;
}

}

if ($IP =~ /^52\.(.*?)\.(.*?)\.(.*?)$/) {

# bing: 52.167, 52.231

if ($1 != 167 && $1 != 231) {
$test = 1;
}

}

}

}

return $test;

}

sub bad_bot_protection_msg {

#: This returns a simple 404 error message, as an HTML code snipplet.

return qq~<div class="p20"><h3>404 - Page Not Found</h3></div>~;

}

sub bad_bot_protection {

#: This allows to protect HTML content from evil clients in a very simple
#: way - it either lets the content pass unmodified, or it replaces the
#: content by a 404 (Page Not Found) message.

my $WEB = $_[1];


if (&bad_bot_protection_test()) {

$WEB = &bad_bot_protection_msg();

}

return $WEB;

}

sub apache_log_ip_analytics {

#: The apache logfile analyzer, as used by the ONO CronSync service and
#: the ONO Admin application's Security screen.
#:
#: This will generate lists (arrays) or IP addresses and IP ranges.

my (
$self,
$RULES_ref,
$vars_ref,
$CUSTOM_ref,
) = @_;

my %RULES = %$RULES_ref;
my %vars = %$vars_ref;

my %CUSTOM;
if ($CUSTOM_ref) {
%CUSTOM = %$CUSTOM_ref;
}

my (@IPS_08,@IPS_16,@IPS_24,@IPS_32,@LIST,%TOPS);

my $min_hits = 2;
if ($vars{'ip_analytics_mode'} eq "mail") {
$min_hits = $vars{'ip_analytics_min_hits'};
}

# abort loops if the script takes more than 20 secs to load

my $max_seconds = 10;
my $start_time = time;

my @lines = ONO::IO->list_raw_fast("var/log/security/report_ips.txt");


foreach my $line (@lines) {

last if (time - $start_time) > $max_seconds/2;

if ($line =~ m/^apache_ip_32_/) {

$line =~ s~(\n|\r|\t)~~g;
my @lp = split(/=/,$line);

if ($lp[0] !~ /:/ && $lp[1] > 1) {

$lp[0] =~ s~^apache_ip_32_~~;
$lp[0] =~ /^(.*?)\.(.*?)\.(.*?)\.(.*?)$/;

my $IP_32 = "$1.$2.$3.$4";
my $IP_24 = "$1.$2.$3";
my $IP_16 = "$1.$2";
my $IP_08 = "$1";

if (!$TOPS{$IP_24}) {
$TOPS{$IP_24} = $IP_32;
}
if (!$TOPS{$IP_16}) {
$TOPS{$IP_16} = $IP_32;
}
if (!$TOPS{$IP_08}) {
$TOPS{$IP_08} = $IP_32;
}

}
}

}

foreach my $line (@lines) {

last if (time - $start_time) > $max_seconds;

if ($line =~ s/^apache_ip_(08|16|24|32)_//) {

my $range = $1;
$line =~ s~(\n|\r|\t)~~g;

my @lp = split(/=/,$line);

if ($lp[0] !~ /:/ && $lp[1] > $min_hits-1) {

my $ip32 = $lp[0];

if ($range < 32) {
if ($TOPS{$lp[0]}) {
$ip32 = $TOPS{$lp[0]};
} else {
$ip32 .= ".1";
}
}

my $COL;
my $BUT = qq~<td>
<a href="/ono/admin/?/info_security/firewall:trust:$lp[0]/">
<img class="block16" src="/ono/osr/images/icons/ono/32x32/checked_inv.png" alt="">
</a>
</td>
<td>
<a href="/ono/admin/?/info_security/firewall:warn:$lp[0]/">
<img class="block16" src="/ono/osr/images/icons/ono/32x32/warn_yellow.png" alt="">
</a>
</td>
<td>
<a href="/ono/admin/?/info_security/firewall:block:$lp[0]/">
<img class="block16" src="/ono/osr/images/icons/ono/32x32/remove.png" alt="">
</a>
</td>
~;

if ($RULES{$lp[0]}) {
if ($RULES{$lp[0]} eq "trust") {
$COL = " bg_green";
}
if ($RULES{$lp[0]} eq "warn") {
$COL = " bg_yellow";
}
if ($RULES{$lp[0]} eq "block") {
$COL = " bg_red";
}
if ($CUSTOM{$lp[0]} eq "block") {
$COL = " bg_blue";
}
$BUT = qq~<td></td>
<td></td>
<td>
<a href="/ono/admin/?/info_security/firewall:flush:$lp[0]/">
<img class="block16" src="/ono/osr/images/icons/ono/32x32/delete_inv.png" alt="">
</a>
</td>
~;
}

if ($lp[0] =~ /^(.*?)\.(.*?)\.(.*?)\.(.*?)$/) {
if ($RULES{"$1.$2.$3"} eq "trust" || $RULES{"$1.$2"} eq "trust" || $RULES{"$1"} eq "trust") {
$COL = " bg_green";
}
if ($RULES{"$1.$2.$3"} eq "warn" || $RULES{"$1.$2"} eq "warn" || $RULES{"$1"} eq "warn") {
$COL = " bg_yellow";
}
if ($RULES{"$1.$2.$3"} eq "block" || $RULES{"$1.$2"} eq "block" || $RULES{"$1"} eq "block") {
$COL = " bg_red";
}
if ($CUSTOM{"$1.$2.$3"} eq "block" || $CUSTOM{"$1.$2"} eq "block" || $CUSTOM{"$1"} eq "block") {
$COL = " bg_blue";
}
} else {

if ($lp[0] =~ /^(.*?)\.(.*?)\.(.*?)$/) {
if ($RULES{"$1.$2"} eq "trust" || $RULES{"$1"} eq "trust") {
$COL = " bg_green";
}
if ($RULES{"$1.$2"} eq "warn" || $RULES{"$1"} eq "warn") {
$COL = " bg_yellow";
}
if ($RULES{"$1.$2"} eq "block" || $RULES{"$1"} eq "block") {
$COL = " bg_red";
}
if ($CUSTOM{"$1.$2"} eq "block" || $CUSTOM{"$1"} eq "block") {
$COL = " bg_blue";
}
}

}

if (!$vars{'filter'} || $vars{'filter'} eq $RULES{$lp[0]} || ($vars{'filter'} eq "undef" && !$RULES{$lp[0]})) {

my $sort = 999999999-(ONO::Lib::Basic->add_leading_zeroes($lp[1],9));

my $RES;

if ($vars{'ip_analytics_mode'} eq "mail") {

if (!$COL) {

$RES = qq~<!-- sort:$sort:$lp[0] -->$lp[0] ($lp[1])\nhttps://www.abuseipdb.com/check/$ip32\n\n~;

}

} else {

$RES = qq~<!-- sort:$sort:$lp[0] -->
<tr class="row bt bb$COL">
<td>
<a href="/ono/admin/?/info_security/$lp[0]-$ip32/" class="col6">$lp[0]</a>
<span class="col9 small">[<a href="https://www.abuseipdb.com/check/$ip32" target="_blank" class="col9">?</a>]</span>
</td>
<td class="col9 tar">$lp[1]</td>
$BUT
</tr>
~;

}

if ($range eq "08") {
@IPS_08 = (@IPS_08,$RES);
}
if ($range eq "16") {
@IPS_16 = (@IPS_16,$RES);
}
if ($range eq "24") {
@IPS_24 = (@IPS_24,$RES);
}
if ($range eq "32") {
@IPS_32 = (@IPS_32,$RES);
}

}

}

}

}

foreach my $opt ("08","16","24","32") {
my @IPS = @IPS_08;
if ($opt eq "16") {
@IPS = @IPS_16;
}
if ($opt eq "24") {
@IPS = @IPS_24;
}
if ($opt eq "32") {
@IPS = @IPS_32;
}
foreach my $IP (sort @IPS) {
$IP =~ s~\<\!-- (.*?) --\>~~;
$LIST[$opt] .= $IP;
}
}

if ((time - $start_time) > $max_seconds) {
$LIST[8] .= qq~<div class="large bold lightred mb10 lh150">Warning - list too long, could not process all data!</div>~;
}

return (\@LIST);

}

###############################################################################
# end of script
###############################################################################

1;

__END__