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__