Note that the graphs plugin requires a change to the existing graphs.pl to allow for arbitary time periods.
Signed-off-by: Tim FitzGeorge ipfr@tfitzgeorge.me.uk --- src/statusmail/plugins/graphs.pm | 697 +++++++++++++++++++++++++ src/statusmail/plugins/hardware_media_space.pm | 154 ++++++ src/statusmail/plugins/network_firewall.pm | 357 +++++++++++++ 3 files changed, 1208 insertions(+) create mode 100644 src/statusmail/plugins/graphs.pm create mode 100644 src/statusmail/plugins/hardware_media_space.pm create mode 100644 src/statusmail/plugins/network_firewall.pm
diff --git a/src/statusmail/plugins/graphs.pm b/src/statusmail/plugins/graphs.pm new file mode 100644 index 000000000..5b72c6e1a --- /dev/null +++ b/src/statusmail/plugins/graphs.pm @@ -0,0 +1,697 @@ +#!/usr/bin/perl + +############################################################################ +# # +# Send log and status emails for IPFire # +# # +# This is free software; you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation; either version 3 of the License, or # +# (at your option) any later version. # +# # +# This 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 IPFire; if not, write to the Free Software # +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # +# # +# Copyright (C) 2018 - 2019 The IPFire Team # +# # +############################################################################ + +use strict; +use warnings; + +require "${General::swroot}/lang.pl"; +require "${General::swroot}/graphs.pl"; + +package Graphs; + +############################################################################ +# Function prototypes +############################################################################ + +sub add_graph( $$$$@ ); + +############################################################################ +# BEGIN Block +# +# Register the graphs available in this file. +# +# Note that some graphs are only available under certain circumstances, so +# it's necessary to check the circumstances apply. +############################################################################ + +sub BEGIN +{ + my %netsettings; + my %mainsettings; + + &General::readhash("${General::swroot}/ethernet/settings", %netsettings); + &General::readhash("${General::swroot}/main/settings", %mainsettings); + + my $config_type = $netsettings{'CONFIG_TYPE'}; + + my %common_options = ( 'section' => $Lang::tr{'graph'}, + 'format' => 'html' ); + + #---------------------------------------------------------------------------- + # Network + + if ($netsettings{'RED_TYPE'} ne 'PPPOE') + { + if ($netsettings{'RED_DEV'} ne $netsettings{'GREEN_DEV'}) + { + if ($netsettings{'RED_DEV'} eq 'red0') + { + main::add_mail_item( %common_options, + 'ident' => 'graph-network-red0', + 'subsection' => $Lang::tr{'interfaces'}, + 'item' => 'red0', + 'function' => &red0 ); + } + else + { + main::add_mail_item( %common_options, + 'ident' => 'graph-network-ppp0', + 'subsection' => $Lang::tr{'interfaces'}, + 'item' => 'ppp0', + 'function' => &ppp0 ); + } + } + } + else + { + main::add_mail_item( %common_options, + 'ident' => 'graph-network-ppp0', + 'subsection' => $Lang::tr{'interfaces'}, + 'item' => 'ppp0', + 'function' => &ppp0 ); + } + + main::add_mail_item( %common_options, + 'ident' => 'graph-network-green0', + 'subsection' => $Lang::tr{'interfaces'}, + 'item' => 'green0', + 'function' => &green0 ); + + if ($config_type == 3 or $config_type == 4) + { + # BLUE + main::add_mail_item( %common_options, + 'ident' => 'graph-network-blue0', + 'subsection' => $Lang::tr{'interfaces'}, + 'item' => 'blue0', + 'function' => &blue0 ); + } + + if ($config_type == 2 or $config_type == 4) + { + # ORANGE + main::add_mail_item( %common_options, + 'ident' => 'graph-network-orange0', + 'subsection' => $Lang::tr{'interfaces'}, + 'item' => 'orange0', + 'function' => &orange0 ); + } + + + if (-e "/var/log/rrd/collectd/localhost/interface/if_octets-ipsec0.rrd") + { + main::add_mail_item( %common_options, + 'ident' => 'graph-network-ipsec0', + 'subsection' => $Lang::tr{'network'}, + 'item' => 'ipsec0', + 'function' => &ipsec0 ); + } + + if (-e "/var/log/rrd/collectd/localhost/interface/if_octets-tun0.rrd") + { + main::add_mail_item( %common_options, + 'ident' => 'graph-network-tun0', + 'subsection' => $Lang::tr{'network'}, + 'item' => 'tun0', + 'function' => &tun0 ); + } + + main::add_mail_item( %common_options, + 'ident' => 'graph-network-fwhits', + 'subsection' => $Lang::tr{'network'}, + 'item' => $Lang::tr{'firewallhits'}, + 'function' => &fw_hits ); + + #---------------------------------------------------------------------------- + # System + + main::add_mail_item( %common_options, + 'ident' => 'graph-system-cpu-usage', + 'subsection' => $Lang::tr{'system'}, + 'item' => "CPU $Lang::tr{'graph'}", + 'function' => &cpu_usage ); + + main::add_mail_item( %common_options, + 'ident' => 'graph-system-cpu-load', + 'subsection' => $Lang::tr{'system'}, + 'item' => "Load $Lang::tr{'graph'}", + 'function' => &cpu_load ); + + if ( -e "$mainsettings{'RRDLOG'}/collectd/localhost/cpufreq/cpufreq-0.rrd") + { + main::add_mail_item( %common_options, + 'ident' => 'graph-system-cpu-frequency', + 'subsection' => $Lang::tr{'system'}, + 'item' => "CPU $Lang::tr{'frequency'}", + 'function' => &cpu_freq ); + } + + main::add_mail_item( %common_options, + 'ident' => 'graph-system-entropy', + 'subsection' => $Lang::tr{'system'}, + 'item' => $Lang::tr{'entropy'}, + 'function' => &entropy ); + + #---------------------------------------------------------------------------- + # Hardware + + main::add_mail_item( %common_options, + 'ident' => 'graph-hardware-cpu-load', + 'subsection' => $Lang::tr{'hardware graphs'}, + 'item' => "Load $Lang::tr{'graph'}", + 'function' => &cpu_load ); + + if ( `ls $mainsettings{'RRDLOG'}/collectd/localhost/thermal-thermal_zone* 2>/dev/null` ) + { + main::add_mail_item( %common_options, + 'ident' => 'graph-hardware-acpi-zone-temp', + 'subsection' => $Lang::tr{'hardware graphs'}, + 'item' => "ACPI Thermal-Zone Temp", + 'function' => &therm ); + } + + if ( `ls $mainsettings{'RRDLOG'}/collectd/localhost/sensors-*/temperature-* 2>/dev/null` ) + { + main::add_mail_item( %common_options, + 'ident' => 'graph-hardware-temp', + 'subsection' => $Lang::tr{'hardware graphs'}, + 'item' => "hwtemp", + 'function' => &hwtemp ); + } + + if ( `ls $mainsettings{'RRDLOG'}/collectd/localhost/sensors-*/fanspeed-* 2>/dev/null` ) + { + main::add_mail_item( %common_options, + 'ident' => 'graph-hardware-fan', + 'subsection' => $Lang::tr{'hardware graphs'}, + 'item' => "hwfan", + 'function' => &hwfan ); + } + + if ( `ls $mainsettings{'RRDLOG'}/collectd/localhost/sensors-*/voltage-* 2>/dev/null` ) + { + main::add_mail_item( %common_options, + 'ident' => 'graph-hardware-volt', + 'subsection' => $Lang::tr{'hardware graphs'}, + 'item' => "hwvolt", + 'function' => &hwvolt ); + } + + #---------------------------------------------------------------------------- + # Memory + + main::add_mail_item( %common_options, + 'ident' => 'graph-memory-memory', + 'subsection' => $Lang::tr{'memory'}, + 'item' => $Lang::tr{'memory'}, + 'function' => &memory ); + + main::add_mail_item( %common_options, + 'ident' => 'graph-memory-swap', + 'subsection' => $Lang::tr{'memory'}, + 'item' => $Lang::tr{'swap'}, + 'function' => &swap ); + + #---------------------------------------------------------------------------- + # Disks + + foreach my $path (glob '/var/log/rrd/collectd/localhost/disk*') + { + my ($name) = $path =~ m/disk-(\w+)/; + + main::add_mail_item( %common_options, + 'ident' => "graph-disk-access-$name", + 'subsection' => $Lang::tr{'statusmail disk access'}, + 'item' => $name, + 'function' => sub { my ($this) = @_; diskaccess( $this, $name ); } ); + + main::add_mail_item( %common_options, + 'ident' => "graph-disk-temp-$name", + 'subsection' => $Lang::tr{'statusmail disk temperature'}, + 'item' => $name, + 'function' => sub { my ($this) = @_; disktemp( $this, $name ); } ); + } + +# Other graphs that aren't available. +# updatepinggraph( host, period ) : netother.cgi +# updateprocessescpugraph( period ) +# updateprocessesmemorygraph( period ) +# updateqosgraph( device, period ) red0 | ppp0 | imq0 : qos.cgi +# updatevpngraph( interface, period ) : netovpnrw.cgi +# updatevpnn2ngraph( interface, period ) : netovpnsrv.cgi +# updatewirelessgraph( interface, period ) +} + +############################################################################ +# Code +############################################################################ + +#------------------------------------------------------------------------------ +# sub add_graph( object, function, name, alternate[, params...] ) +# +# Adds a graph to the mail message. This runs a sub-process to capture the +# output from running the standard WUI's graphing function, which is sent to +# stdout. +# +# Parameters: +# this message object +# function function producing graph +# name name of graph file +# alternate alternate text for image +# params parameters to be passed to graph function +#------------------------------------------------------------------------------ + +sub add_graph( $$$$@ ) +{ + my ($this, $function, $name, $alternate, @params) = @_; + + my $from_child; + + my $pid = open( $from_child, "-|" ); + + if ($pid) + { # parent + binmode $from_child; + + $this->add_image( fh => $from_child, + alt => $alternate, + type => 'image/png', + name => $name ); + + waitpid( $pid, 0 ); + close $from_child; + } + else + { # child + binmode( STDOUT ); + + my $period = $this->get_period(); + + &$function( @params, $period ); + + exit; + } +} + + +#------------------------------------------------------------------------------ +# sub ppp0( this ) +# +# Adds a graph of the ppp0 interface throughput +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub ppp0( $ ) +{ + my ($this) = @_; + + add_graph( $this, &Graphs::updateifgraph, 'ppp0_if.png', 'ppp0 interface throughput', 'ppp0' ); + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub red0( this ) +# +# Adds a graph of the red0 interface throughput +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub red0( $ ) +{ + my ($this) = @_; + + add_graph( $this, &Graphs::updateifgraph, 'red0_if.png', 'red0 interface throughput', 'red0' ); + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub green0( this ) +# +# Adds a graph of the green0 interface throughput +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub green0( $ ) +{ + my ($this) = @_; + + add_graph( $this, &Graphs::updateifgraph, 'green0_if.png', 'green0 interface throughput', 'green0' ); + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub blue0( this ) +# +# Adds a graph of the blue0 interface throughput +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub blue0( $ ) +{ + my ($this) = @_; + + add_graph( $this, &Graphs::updateifgraph, 'blue0_if.png', 'blue0 interface throughput', 'blue0' ); + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub orange0( this ) +# +# Adds a graph of the orange0 interface throughput +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub orange0( $ ) +{ + my ($this) = @_; + + add_graph( $this, &Graphs::updateifgraph, 'orange0_if.png', 'orange0 interface throughput', 'orange0' ); + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub ipsec0( this ) +# +# Adds a graph of the ipsec0 interface +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub ipsec0( $ ) +{ + my ($this) = @_; + + add_graph( $this, &Graphs::updateifgraph, 'ipsec0_if.png', 'ipsec0 interface throughput', 'ipsec0' ); + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub tun0( this ) +# +# Adds a graph of the tun0 interface +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub tun0( $ ) +{ + my ($this) = @_; + + add_graph( $this, &Graphs::updateifgraph, 'tun0_if.png', 'tun0 interface throughput', 'tun0' ); + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub cpu_usage( this ) +# +# Adds a graph of the CPU usage +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub cpu_usage( $ ) +{ + my ($this) = @_; + + add_graph( $this, &Graphs::updatecpugraph, 'cpu_usage.png', "CPU $Lang::tr{'graph'}" ); + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub cpu_freq( this ) +# +# Adds a graph of the CPU frequency +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub cpu_freq( $ ) +{ + my ($this) = @_; + + add_graph( $this, &Graphs::updatecpufreqgraph, 'cpu_freq.png', "CPU $Lang::tr{'frequency'}" ); + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub cpu_load( this ) +# +# Adds a graph of the CPU load +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub cpu_load( $$ ) +{ + my ($this) = @_; + + add_graph( $this, &Graphs::updateloadgraph,, 'cpu_load.png', "Load $Lang::tr{'graph'}" ); + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub fw_hits( this ) +# +# Adds a graph of the Firewall hits +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub fw_hits( $ ) +{ + my ($this) = @_; + + add_graph( $this, &Graphs::updatefwhitsgraph, 'fw_hits.png', $Lang::tr{'firewallhits'} ); + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub therm( this ) +# +# Adds a graph of the ACPI Thermal zone temperatures +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub therm( $ ) +{ + my ($this) = @_; + + add_graph( $this, &Graphs::updatethermaltempgraph, 'therm.png', "ACPI Thermal-Zone Temp" ); + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub hwtemp( this ) +# +# Adds a graph of the Hardware Temperatures +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub hwtemp( $ ) +{ + my ($this) = @_; + + add_graph( $this, &Graphs::updatehwtempgraph, 'hw_temp.png', 'hwtemp' ); + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub hwfan( this ) +# +# Adds a graph of the Fan Speeds +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub hwfan( $ ) +{ + my ($this) = @_; + + add_graph( $this, &Graphs::updatehwfangraph, 'hw_fan.png', 'hwfan' ); + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub hwvolt( this ) +# +# Adds a graph of the Hardware voltages +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub hwvolt( $ ) +{ + my ($this) = @_; + + add_graph( $this, &Graphs::updatehwvoltgraph, 'hw_volt.png', 'hw volt' ); + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub entropy( this ) +# +# Adds a graph of the Entropy +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub entropy( $ ) +{ + my ($this) = @_; + + add_graph( $this, &Graphs::updateentropygraph, 'entropy.png', $Lang::tr{'entropy'} ); + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub memory( this ) +# +# Adds a graph of the memory usage +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub memory( $ ) +{ + my ($this) = @_; + + add_graph( $this, &Graphs::updatememorygraph, 'memory.png', $Lang::tr{'memory'} ); + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub swap( this ) +# +# Adds a graph of the swapfile usage +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub swap( $ ) +{ + my ($this) = @_; + + add_graph( $this, &Graphs::updateswapgraph, 'swap.png', $Lang::tr{'swap'} ); + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub diskaccess( this, name ) +# +# Adds a graph of the disk access rate +# +# Parameters: +# this message object +# name disk name +#------------------------------------------------------------------------------ + +sub diskaccess( $$ ) +{ + my ($this, $name) = @_; + + add_graph( $this, &Graphs::updatediskgraph, "disk_access_$name.png", $name, $name ); + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub updatehddgraph( this, name ) +# +# Adds a graph of the disk temperature +# +# Parameters: +# this message object +# name disk name +#------------------------------------------------------------------------------ + +sub disktemp( $$ ) +{ + my ($this, $name) = @_; + + add_graph( $this, &Graphs::updatehddgraph, "disk_temp_$name.png", $name, $name ); + + return 1; +} diff --git a/src/statusmail/plugins/hardware_media_space.pm b/src/statusmail/plugins/hardware_media_space.pm new file mode 100644 index 000000000..ce3db2def --- /dev/null +++ b/src/statusmail/plugins/hardware_media_space.pm @@ -0,0 +1,154 @@ +#!/usr/bin/perl + +############################################################################ +# # +# Send log and status emails for IPFire # +# # +# This is free software; you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation; either version 3 of the License, or # +# (at your option) any later version. # +# # +# This 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 IPFire; if not, write to the Free Software # +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # +# # +# Copyright (C) 2018 - 2019 The IPFire Team # +# # +############################################################################ + +use strict; +use warnings; + +require "${General::swroot}/lang.pl"; + +package Hardware_Media_Space; + +############################################################################ +# Function prototypes +############################################################################ + +sub space( $$ ); +sub inodes( $$ ); + +############################################################################ +# BEGIN Block +# +# Register the log items available in this file +############################################################################ + +sub BEGIN +{ + main::add_mail_item( 'ident' => 'hardware-media-space', + 'section' => $Lang::tr{'statusmail hardware'}, + 'subsection' => $Lang::tr{'media'}, + 'item' => $Lang::tr{'disk usage'}, + 'function' => &space, + 'option' => { 'type' => 'integer', + 'name' => $Lang::tr{'statusmail max free percent'}, + 'min' => 0, + 'max' => 100 } ); + + main::add_mail_item( 'ident' => 'hardware-media-inodes', + 'section' => $Lang::tr{'statusmail hardware'}, + 'subsection' => $Lang::tr{'media'}, + 'item' => 'inodes', + 'function' => &inodes, + 'option' => { 'type' => 'integer', + 'name' => $Lang::tr{'statusmail max free percent'}, + 'min' => 0, + 'max' => 100 } ); +} + +############################################################################ +# Code +############################################################################ + +#------------------------------------------------------------------------------ +# sub space( this, min_percent ) +# +# Adds the disk usage in terms of space used. +# +# Parameters: +# this message object +# min_percent Only display information if this amount of space or less is +# free +#------------------------------------------------------------------------------ + +sub space( $$ ) +{ + my $message = shift; + my $min_percent = 100 - shift; + my @lines; + + # Get the process information + + foreach my $line (`df -BM`) + { + my @fields = split /\s+/, $line, 6; + if ($fields[4] =~ m/\d+%/) + { + my ($percent) = $fields[4] =~ m/(\d+)%/; + next if ($percent <= $min_percent); + } + push @lines, [ @fields ]; + } + + if (@lines > 1) + { + $message->add_table( @lines ); + + return 1; + } + + return 0; +} + + +#------------------------------------------------------------------------------ +# sub inodes( this, min_percent ) +# +# Adds the disk usage in terms of inodes used. +# +# Parameters: +# this message object +# min_percent Only display information if this number of inodes or less is +# free +#------------------------------------------------------------------------------ + +sub inodes( $$ ) +{ + my $message = shift; + my $min_percent = 100 - shift; + my @lines; + + # Get the process information + + foreach my $line (`df -i`) + { + my @fields = split /\s+/, $line, 6; + next if ($fields[1] == 0); + if ($fields[4] =~ m/\d+%/) + { + my ($percent) = $fields[4] =~ m/(\d+)%/; + next if ($percent <= $min_percent); + } + push @lines, [ @fields ]; + } + + if (@lines > 1) + { + $message->add_table( @lines ); + + return 1; + } + + return 0; +} + +1; diff --git a/src/statusmail/plugins/network_firewall.pm b/src/statusmail/plugins/network_firewall.pm new file mode 100644 index 000000000..1abe4e482 --- /dev/null +++ b/src/statusmail/plugins/network_firewall.pm @@ -0,0 +1,357 @@ +#!/usr/bin/perl + +############################################################################ +# # +# Send log and status emails for IPFire # +# # +# This is free software; you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation; either version 3 of the License, or # +# (at your option) any later version. # +# # +# This 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 IPFire; if not, write to the Free Software # +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # +# # +# Copyright (C) 2018 - 2019 The IPFire Team # +# # +############################################################################ + +require "${General::swroot}/lang.pl"; + +use strict; +use warnings; + +package Network_Firewall; + +use Time::Local; + +require "${General::swroot}/geoip-functions.pl"; + +############################################################################ +# BEGIN Block +# +# Register the log items available in this file +############################################################################ + +sub BEGIN +{ + main::add_mail_item( 'ident' => 'network-firewall-ipaddresses', + 'section' => $Lang::tr{'network'}, + 'subsection' => $Lang::tr{'firewall'}, + 'item' => $Lang::tr{'ip address'}, + 'function' => &addresses, + 'option' => { 'type' => 'integer', + 'name' => $Lang::tr{'statusmail firewall min count'}, + 'min' => 1, + 'max' => 1000 } ); + + main::add_mail_item( 'ident' => 'network-firewall-ports', + 'section' => $Lang::tr{'network'}, + 'subsection' => $Lang::tr{'firewall'}, + 'item' => $Lang::tr{port}, + 'function' => &ports, + 'option' => { 'type' => 'integer', + 'name' => $Lang::tr{'statusmail firewall min count'}, + 'min' => 1, + 'max' => 1000 } ); + + main::add_mail_item( 'ident' => 'network-firewall-countries', + 'section' => $Lang::tr{'network'}, + 'subsection' => $Lang::tr{'firewall'}, + 'item' => $Lang::tr{country}, + 'function' => &countries, + 'option' => { 'type' => 'integer', + 'name' => $Lang::tr{'statusmail firewall min count'}, + 'min' => 1, + 'max' => 1000 } ); + + main::add_mail_item( 'ident' => 'network-firewall-reason', + 'section' => $Lang::tr{'network'}, + 'subsection' => $Lang::tr{'firewall'}, + 'item' => $Lang::tr{'statusmail firewall reason'}, + 'function' => &reasons, + 'option' => { 'type' => 'integer', + 'name' => $Lang::tr{'statusmail firewall min count'}, + 'min' => 1, + 'max' => 1000 } ); +} + + +############################################################################ +# Functions +############################################################################ + +sub get_log( $ ); +sub addresses( $$ ); + +#------------------------------------------------------------------------------ +# sub get_log( this ) +# +# Gets information on blocked packets from the system log and caches it. +# +# Parameters: +# this message object +# +# Returns: +# reference to hash of information +#------------------------------------------------------------------------------ + +sub get_log( $ ) +{ + my ($this, $name) = @_; + + my $data = $this->cache( 'network-firewall' ); + + return $data if (defined $data); + + my %info; + my $line; + + while ($line = $this->get_message_log_line) + { + next unless ($line); + next unless ($line =~ m/kernel: DROP/); + + my ($time, $rule, $interface, $src_addrs, $dst_port) = + $line =~ m/(\w+\s+\d+\s+\d+:\d+:\d+).*DROP_(\w+?)\s*IN=(\w+).*SRC=(\d+.\d+.\d+.\d+).*(?:DPT=(\d*))/; +# mmm dd hh:mm:dd ipfire kernel: DROP_SPAMHAUS_EDROPIN=ppp0 OUT= MAC= SRC=999.999.999.999 DST=888.888.888.888 LEN=40 TOS=0x00 PREC=0x00 TTL=248 ID=35549 PROTO=TCP SPT=47851 DPT=28672 WINDOW=1024 RES=0x00 SYN URGP=0 MARK=0xd2 + + next unless ($src_addrs); + + my $country = GeoIP::lookup( $src_addrs ) || $src_addrs; + + $info{'by_address'}{$src_addrs}{'count'}++; + $info{'by_address'}{$src_addrs}{'first'} = $time unless ($info{'by_address'}{$src_addrs}{'first'}); + $info{'by_address'}{$src_addrs}{'last'} = $time; + + if ($dst_port) + { + $info{'by_port'}{$dst_port}{'count'}++ ; + $info{'by_port'}{$dst_port}{'first'} = $time unless ($info{'by_port'}{$dst_port}{'first'}); + $info{'by_port'}{$dst_port}{'last'} = $time; + } + + if ($country) + { + $info{'by_country'}{$country}{'count'}++; + $info{'by_country'}{$country}{'first'} = $time unless ($info{'by_country'}{$country}{'first'}); + $info{'by_country'}{$country}{'last'} = $time; + } + + $info{'by_rule'}{$rule}{'count'}++; + $info{'by_rule'}{$rule}{'first'} = $time unless ($info{'by_rule'}{$rule}{'first'}); + $info{'by_rule'}{$rule}{'last'} = $time; + + $info{'total'}++; + }; + + $this->cache( 'network-firewall', %info ); + + return %info; +} + + +#------------------------------------------------------------------------------ +# sub addresses( this, min_count ) +# +# Output information on blocked addresses. +# +# Parameters: +# this message object +# min_count only output blocked addresses occurring at least this number of +# times +#------------------------------------------------------------------------------ + +sub addresses( $$ ) +{ + my ($self, $min_count) = @_; + my @table; + + use Sort::Naturally; + + push @table, ['|', '|', '|', '|', '|', '|']; + push @table, [ $Lang::tr{'ip address'}, $Lang::tr{'country'}, $Lang::tr{'count'}, $Lang::tr{'percentage'}, $Lang::tr{'first'}, $Lang::tr{'last'} ]; + + my $stats = get_log( $self ); + + foreach my $address (sort { $$stats{'by_address'}{$b}{'count'} <=> $$stats{'by_address'}{$a}{'count'} || + ncmp( $b, $a ) } keys %{ $$stats{'by_address'} } ) + { + my $count = $$stats{'by_address'}{$address}{'count'}; + my $country = GeoIP::lookup( $address ); + my $first = $$stats{'by_address'}{$address}{'first'}; + my $last = $$stats{'by_address'}{$address}{'last'}; + my $percent = int( 100 * $count / $$stats{'total'} + 0.5); + + last if ($count < $min_count); + + my $name = $self->lookup_ip_address( $address ); + + $address = "$address\n$name" if ($name); + + if ($country) + { + $country = GeoIP::get_full_country_name( $country) || $address; + } + else + { + $country = $Lang::tr{'unknown'}; + } + + push @table, [ $address, $country, $count, $percent, $first, $last ]; + + last if (@table > $self->get_max_lines_per_item + 2) + } + + if (@table > 2) + { + $self->add_table( @table ); + + return 1; + } + + return 0; +} + + +#------------------------------------------------------------------------------ +# sub ports( this, min_count ) +# +# Output information on blocked ports. +# +# Parameters: +# this message object +# min_count only output blocked ports occurring at least this number of +# times +#------------------------------------------------------------------------------ + +sub ports( $$ ) +{ + my ($self, $min_count) = @_; + my @table; + + push @table, ['|', '|', '|', '|', '|']; + push @table, [ $Lang::tr{'port'}, $Lang::tr{'count'}, $Lang::tr{'percentage'}, $Lang::tr{'first'}, $Lang::tr{'last'} ]; + + my $stats = get_log( $self ); + + foreach my $port (sort { $$stats{'by_port'}{$b}{'count'} <=> $$stats{'by_port'}{$a}{'count'} || + ncmp( $b, $a ) } keys %{ $$stats{'by_port'} } ) + { + my $count = $$stats{'by_port'}{$port}{'count'}; + my $first = $$stats{'by_port'}{$port}{'first'}; + my $last = $$stats{'by_port'}{$port}{'last'}; + my $percent = int( 100 * $count / $$stats{'total'} + 0.5); + + last if ($count < $min_count); + + push @table, [ $port, $count, $percent, $first, $last ]; + } + + if (@table > 2) + { + $self->add_table( @table ); + + return 1; + } + + return 0; +} + + +#------------------------------------------------------------------------------ +# sub countries( this, min_count ) +# +# Output information on blocked countries. +# +# Parameters: +# this message object +# min_count only output blocked countries occurring at least this number of +# times +#------------------------------------------------------------------------------ + +sub countries( $$ ) +{ + my ($self, $min_count) = @_; + my @table; + + push @table, ['<', '|', '|', '|', '|']; + push @table, [ $Lang::tr{'country'}, $Lang::tr{'count'}, $Lang::tr{'percentage'}, $Lang::tr{'first'}, $Lang::tr{'last'} ]; + + my $stats = get_log( $self ); + + foreach my $country (sort { $$stats{'by_country'}{$b}{'count'} <=> $$stats{'by_country'}{$a}{'count'} } keys %{ $$stats{'by_country'} } ) + { + my $count = $$stats{'by_country'}{$country}{'count'}; + my $first = $$stats{'by_country'}{$country}{'first'}; + my $last = $$stats{'by_country'}{$country}{'last'}; + my $percent = int( 100 * $count / $$stats{'total'} + 0.5); + + last if ($count < $min_count); + + my $full_country = GeoIP::get_full_country_name( $country) || $country; + + push @table, [ $full_country, $count, $percent, $first, $last ]; + } + + if (@table > 2) + { + $self->add_table( @table ); + + return 1; + } + + return 0; +} + + +#------------------------------------------------------------------------------ +# sub reasons( this, min_count ) +# +# Output information on blocked reasons (the IPtable blocking the packet). +# +# Parameters: +# this message object +# min_count only output blocked reasons occurring at least this number of +# times +#------------------------------------------------------------------------------ + +sub reasons( $$ ) +{ + my ($self, $min_count) = @_; + my @table; + + push @table, ['<', '|', '|', '|', '|']; + push @table, [ $Lang::tr{'statusmail firewall reason'}, $Lang::tr{'count'}, $Lang::tr{'percentage'}, $Lang::tr{'first'}, $Lang::tr{'last'} ]; + + my $stats = get_log( $self ); + + foreach my $reason (sort { $$stats{'by_rule'}{$b}{'count'} <=> $$stats{'by_rule'}{$a}{'count'} } keys %{ $$stats{'by_rule'} } ) + { + my $count = $$stats{'by_rule'}{$reason}{'count'}; + my $first = $$stats{'by_rule'}{$reason}{'first'}; + my $last = $$stats{'by_rule'}{$reason}{'last'}; + my $percent = int( 100 * $count / $$stats{'total'} + 0.5); + + last if ($count < $min_count); + + push @table, [ $reason, $count, $percent, $first, $last ]; + } + + if (@table > 2) + { + $self->add_table( @table ); + + return 1; + } + + return 0; +} + +1;