Signed-off-by: Tim FitzGeorge ipfr@tfitzgeorge.me.uk --- src/statusmail/plugins/system_kernel.pm | 322 +++++++++++++++++++ src/statusmail/plugins/system_pakfire.pm | 198 ++++++++++++ src/statusmail/plugins/system_ssh.pm | 186 +++++++++++ src/statusmail/plugins/system_status_ps.pm | 132 ++++++++ src/statusmail/plugins/system_status_services.pm | 390 +++++++++++++++++++++++ 5 files changed, 1228 insertions(+) create mode 100644 src/statusmail/plugins/system_kernel.pm create mode 100644 src/statusmail/plugins/system_pakfire.pm create mode 100644 src/statusmail/plugins/system_ssh.pm create mode 100644 src/statusmail/plugins/system_status_ps.pm create mode 100644 src/statusmail/plugins/system_status_services.pm
diff --git a/src/statusmail/plugins/system_kernel.pm b/src/statusmail/plugins/system_kernel.pm new file mode 100644 index 000000000..69dd57224 --- /dev/null +++ b/src/statusmail/plugins/system_kernel.pm @@ -0,0 +1,322 @@ +#!/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 System_Kernel; + +use Time::Local; + +############################################################################ +# BEGIN Block +# +# Register the log items available in this file +############################################################################ + +sub BEGIN +{ + main::add_mail_item( 'ident' => 'system-kernel-alerts', + 'section' => $Lang::tr{'system'}, + 'subsection' => $Lang::tr{'kernel'}, + 'item' => $Lang::tr{'statusmail errors'}, + 'function' => &errors ); +} + + +############################################################################ +# Functions +############################################################################ + +sub get_log( $ ); +sub errors( $ ); + +#------------------------------------------------------------------------------ +# sub get_log( this ) +# +# Gets kernel messages from the system log. +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub get_log( $ ) +{ + my ($this) = @_; + +# Comment out since there's only one item at the moment +# my $data = $this->cache( 'system-kernel' ); +# return $data if (defined $data); + + my %info; + my $line; + + while ($line = $this->get_message_log_line) + { + next unless ($line); + next unless ($line =~ m/ kernel: /); + next if ($line =~ m/ kernel: DROP_/); + + if ( my ($from, $if) = $line =~ m/^Warning: possible SYN flood from ([^ ]+) on ([^ ]+):.+ Sending cookies/ ) + { + $info{SYNflood}{$from}{$if}++; + } + elsif ($line =~ m/continuing in degraded mode/) + { + $info{RAIDErrors}{$line}++; + } + elsif ($line =~ m/([^(]*)[\d+]: segfault at/) + { + $info{SegFaults}{$1}++; + } + elsif ($line =~ m/([^(]*)[\d+] general protection/) + { + $info{GPFaults}{$1}++; + } + elsif ($line =~ m/([^(]*)[\d+] trap int3 /) + { + $info{TrapInt3s}{$1}++; + } + elsif ($line =~ m/([^(]*)(\d+): unaligned access to/) + { + $info{UnalignedErrors}{$1}++; + } + elsif ($line =~ /([^(]*)(\d+): floating-point assist fault at ip/) + { + $info{FPAssists}{$1}++; + } + elsif ($line =~ m/Out of memory: Killed process \d+ ((.*))/) + { + $info{OOM}{$1}++; + } + elsif ($line =~ m/(\S+) invoked oom-killer/) + { + $info{OOM}{$1}++; + } + elsif ($line =~ m/(EDAC (MC|PCI)\d:.*)/) + { + # Standard boot messages + next if ($line =~ m/Giving out device to /); + $info{EDAC}{$1}++; + } + elsif ( ( my $errormsg ) = ( $line =~ m/((BUG|WARNING|INFO):.{0,40})/ ) ) + { + $info{Errors}{$errormsg}++; + } + } + +# $this->cache( 'system-kernel', %info ); + + return %info; +} + +#------------------------------------------------------------------------------ +# sub errors( this ) +# +# Outputs kernel errors +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub errors( $ ) +{ + my ($self) = @_; + my @message; + my @table; + my $rv = 0; + + use Sort::Naturally; + + my $alerts = get_log( $self ); + + if (keys %{ $$alerts{SYNflood} }) + { + $self->add_title( $Lang::tr{'statusmail kernel SYN flood'} ); + push @table, [ $Lang::tr{'interface'}, $Lang::tr{'ip address'}, $Lang::tr{'count'} ]; + + foreach my $interface (sort {ncmp( $a, $b )} keys %{ $$alerts{SYNflood} }) + { + foreach my $source (sort {ncmp( $a, $b ) } keys %{ $$alerts{SYNflood}{$interface} }) + { + push @table, [ $interface, $source, $$alerts{SYNflood}{$interface}{$source} ]; + } + } + + $self->add_table( @table ); + @table = (); + + $rv = 1; + } + + if (keys %{ $$alerts{RAIDErrors} }) + { + $self->add_title( $Lang::tr{'statusmail kernel raid errors'} ); + push @table, [ $Lang::tr{'statusmail error'}, $Lang::tr{'count'} ]; + + foreach my $error ( sort {$$alerts{RAIDErrors}{$b} <=> $$alerts{RAIDErrors}{$a}} keys %{ $$alerts{RAIDErrors} } ) + { + push @table, [ $error, $$alerts{RAIDErrors}{$error} ]; + } + + $self->add_table( @table ); + @table = (); + + $rv = 1; + } + + if (keys %{ $$alerts{SegFaults} }) + { + $self->add_title( $Lang::tr{'statusmail kernel segmentation fault'} ); + push @table, [ $Lang::tr{'statusmail executable'}, $Lang::tr{'count'} ]; + + foreach my $executable ( sort {$$alerts{SegFaults}{$b} <=> $$alerts{SegFaults}{$a}} keys %{ $$alerts{SegFaults} } ) + { + push @table, [ $executable, $$alerts{SegFaults}{$executable} ]; + } + + $self->add_table( @table ); + @table = (); + + $rv = 1; + } + + if (keys %{ $$alerts{GPFaults} }) + { + $self->add_title( $Lang::tr{'statusmail kernel general protection fault'} ); + push @table, [ $Lang::tr{'statusmail executable'}, $Lang::tr{'count'} ]; + + foreach my $executable ( sort {$$alerts{GPFaults}{$b} <=> $$alerts{GPFaults}{$a}} keys %{ $$alerts{GPFaults} } ) + { + push @table, [ $executable, $$alerts{GPFaults}{$executable} ]; + } + + $self->add_table( @table ); + @table = (); + + $rv = 1; + } + + if (keys %{ $$alerts{TrapInt3s} }) + { + $self->add_title( $Lang::tr{'statusmail kernel trap int3'} ); + push @table, [ $Lang::tr{'statusmail executable'}, $Lang::tr{'count'} ]; + + foreach my $executable ( sort {$$alerts{TrapInt3s}{$b} <=> $$alerts{TrapInt3s}{$a}} keys %{ $$alerts{TrapInt3s} } ) + { + push @table, [ $executable, $$alerts{TrapInt3s}{$executable} ]; + } + + $self->add_table( @table ); + @table = (); + + $rv = 1; + } + + if (keys %{ $$alerts{UnalignedErrors} }) + { + $self->add_title( $Lang::tr{'statusmail kernel unaligned'} ); + push @table, [ $Lang::tr{'statusmail executable'}, $Lang::tr{'count'} ]; + + foreach my $executable ( sort {$$alerts{UnalignedErrors}{$b} <=> $$alerts{UnalignedErrors}{$a}} keys %{ $$alerts{UnalignedErrors} } ) + { + push @table, [ $executable, $$alerts{UnalignedErrors}{$executable} ]; + } + + $self->add_table( @table ); + @table = (); + + $rv = 1; + } + + if (keys %{ $$alerts{FPAssists} }) + { + $self->add_title( $Lang::tr{'statusmail kernel FP Assists'} ); + push @table, [ $Lang::tr{'statusmail executable'}, $Lang::tr{'count'} ]; + + foreach my $executable ( sort {$$alerts{FPAssists}{$b} <=> $$alerts{FPAssists}{$a}} keys %{ $$alerts{FPAssists} } ) + { + push @table, [ $executable, $$alerts{FPAssists}{$executable} ]; + } + + $self->add_table( @table ); + @table = (); + + $rv = 1; + } + + if (keys %{ $$alerts{OOM} }) + { + $self->add_title( $Lang::tr{'statusmail kernel out of memory'} ); + push @table, [ $Lang::tr{'statusmail executable'}, $Lang::tr{'count'} ]; + + foreach my $executable ( sort { $$alerts{OOM}{$b} <=> $$alerts{OOM}{$a} } keys %{ $$alerts{OOM} } ) + { + push @table, [ $executable, $$alerts{OOM}{$executable} ]; + } + + $self->add_table( @table ); + @table = (); + + $rv = 1; + } + + if (keys %{ $$alerts{Errors} }) + { + $self->add_title( $Lang::tr{'statusmail kernel errors'} ); + push @table, [ $Lang::tr{'statusmail error'}, $Lang::tr{'count'} ]; + + foreach my $error ( sort {$$alerts{Errors}{$b} <=> $$alerts{Errors}{$a}} keys %{ $$alerts{Errors} } ) + { + push @table, [ $error, $$alerts{Errors}{$error} ]; + } + + $self->add_table( @table ); + @table = (); + + $rv = 1; + } + + if (keys %{ $$alerts{EDACs} }) + { + $self->add_title( $Lang::tr{'statusmail kernel edac messages'} ); + push @table, [ $Lang::tr{'statusmail message'}, $Lang::tr{'count'} ]; + + foreach my $message ( sort {$$alerts{EDACs}{$b} <=> $$alerts{EDACs}{$a}} keys %{ $$alerts{EDACs} } ) + { + push @table, [ $message, $$alerts{EDACs}{$message} ]; + } + + $self->add_table( @table ); + @table = (); + + $rv = 1; + } + + return $rv; +} + +1; diff --git a/src/statusmail/plugins/system_pakfire.pm b/src/statusmail/plugins/system_pakfire.pm new file mode 100644 index 000000000..856086816 --- /dev/null +++ b/src/statusmail/plugins/system_pakfire.pm @@ -0,0 +1,198 @@ +#!/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 System_Pakfire; + +############################################################################ +# Function prototypes +############################################################################ + +sub core( $ ); +sub addon( $ ); + + +############################################################################ +# BEGIN Block +# +# Register the log items available in this file +############################################################################ + +sub BEGIN +{ + main::add_mail_item( 'ident' => 'system-pakfire-core', + 'section' => $Lang::tr{'system'}, + 'subsection' => 'Pakfire', + 'item' => $Lang::tr{'statusmail core'}, + 'function' => &core ); + + main::add_mail_item( 'ident' => 'system-pakfire-addons', + 'section' => $Lang::tr{'system'}, + 'subsection' => 'Pakfire', + 'item' => $Lang::tr{'statusmail addon'}, + 'function' => &addon ); +} + +############################################################################ +# Code +############################################################################ + +#------------------------------------------------------------------------------ +# sub core( this ) +# +# Shows core updates. +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub core( $ ) +{ + my $message = shift; + + my $installed_file = '/opt/pakfire/db/core/mine'; + my $update_list_file = '/opt/pakfire/db/lists/core-list.db'; + + return 0 unless (-r $installed_file and -r $update_list_file); + + open IN, '<', $installed_file or warn "Can't open current core version file: $!"; + + my $current = <IN>; + chomp $current; + + close IN; + + my $core_release; + + open IN, '<', $update_list_file or warn "Can't open core update list file: $!"; + + foreach my $line (<IN>) + { + next unless ($line =~ m/core_release/); + + eval $line; + } + + close IN; + + return 0 unless ($current ne $core_release); + + $message->add_title( $Lang::tr{'statusmail core update available'} ); + $message->add_text( "Release $current to $core_release\n" ); + + return 1; +} + +#------------------------------------------------------------------------------ +# sub addon +# +# Shows available addon updates +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub addon( $ ) +{ + my $message = shift; + + my $installed_dir = '/opt/pakfire/db/installed'; + my $update_list_file = '/opt/pakfire/db/lists/packages_list.db'; + + my $name = ''; + my $version = ''; + my $release = 0; + my %paks = (); + + return 0 unless (-r $update_list_file and -r $installed_dir ); + + # Read the installed versions + + opendir DIR, $installed_dir or warn "Can't open installed package dir: $!"; + + foreach my $file (readdir DIR) + { + open IN, '<', "$installed_dir/$file" or warn "Can't open package file $file: $!"; + + foreach my $line (<IN>) + { + if ($line =~ m/^Name:\s+(\w+)/) + { + $name = $1; + } + elsif ($line =~ m/^ProgVersion:\s+(.+)/) + { + $version = $1; + } + elsif ($line =~ m/^Release:\s+(.+)/) + { + $release = $1; + } + + if ($name and $version and $release) + { + $paks{$name} = [$version, $release]; + $name = ''; + $version = ''; + $release = ''; + } + } + + close IN; + } + + closedir DIR; + + # Read the available versions + + my $output = ''; + + open IN, '<', $update_list_file or warn "Can't open package list file $update_list_file: $!"; + + foreach my $line (<IN>) + { + my ($name, $version, $release) = split ';', $line; + + if (exists $paks{$name} and $release > $paks{$name}[1]) + { + $output .= "$name: from $paks{$name}[0] to $version\n"; + } + } + + close IN; + + return 0 unless ($output); + + $message->add_title( $Lang::tr{'statusmail addon updates available'} ); + + $message->add_text( $output ); + + return 1; +} + +1; diff --git a/src/statusmail/plugins/system_ssh.pm b/src/statusmail/plugins/system_ssh.pm new file mode 100644 index 000000000..e789c19cd --- /dev/null +++ b/src/statusmail/plugins/system_ssh.pm @@ -0,0 +1,186 @@ +#!/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 System_Ssh; + +use Time::Local; + +############################################################################ +# BEGIN Block +# +# Register the log items available in this file +############################################################################ + +sub BEGIN +{ + main::add_mail_item( 'ident' => 'system-ssh-logins', + 'section' => $Lang::tr{'system'}, + 'subsection' => 'SSH', + 'item' => $Lang::tr{'statusmail logins'}, + 'function' => &logins ); + + main::add_mail_item( 'ident' => 'system-ssh-errors', + 'section' => $Lang::tr{'system'}, + 'subsection' => 'SSH', + 'item' => $Lang::tr{'statusmail errors'}, + 'function' => &errors ); +} + +############################################################################ +# Functions +############################################################################ + +sub get_log( $ ); +sub logins( $$ ); +sub errors( $$ ); + +#------------------------------------------------------------------------------ +# sub get_log( this ) +# +# Gets log entries for ssh and caches the results +# +# Parameters: +# this message object +# +# Returns: +# Reference to hash of ssh data +#------------------------------------------------------------------------------ + +sub get_log( $ ) +{ + my ($this) = @_; + + my $data = $this->cache( 'ssh' ); + return $data if (defined $data); + + my %info; + my $line; + my ($type, $user, $from); + + while ($line = $this->get_message_log_line) + { + next unless ($line); + next unless ($line =~ m/ sshd/); + + if (($type, $user, $from) = $line =~ m/(\w+) password for (?:illegal|invalid user )?(.+) from (.+) port/) + { + $info{$type}{"$user||$from"}++; + } + elsif (($user, $from) = $line =~ m/Accepted publickey for (.*) from (.*) port/) + { + $info{'Accepted'}{"$user||$from"}++; + } + } + + $this->cache( 'ssh', %info ); + + return %info; +} + + +#------------------------------------------------------------------------------ +# sub logins( this ) +# +# Outputs information on ssh logins. +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub logins( $$ ) +{ + my ($self) = @_; + my @table; + + use Sort::Naturally; + + push @table, ['|', '|', '|']; + push @table, [ $Lang::tr{'user'}, $Lang::tr{'from'}, $Lang::tr{'count'} ]; + + my $stats = get_log( $self ); + + foreach my $who (sort keys %{ $$stats{'Accepted'} } ) + { + my $count = $$stats{'Accepted'}{$who}; + my ($user, $from) = split /||/, $who; + + push @table, [ $user, $from, $count ]; + } + + if (@table > 2) + { + $self->add_table( @table ); + + return 1; + } + + return 0; +} + + +#------------------------------------------------------------------------------ +# sub errors( this ) +# +# Outputs information on ssh errors. +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub errors( $$ ) +{ + my ($self) = @_; + my @table; + + use Sort::Naturally; + + push @table, ['|', '|', '|']; + push @table, [ $Lang::tr{'user'}, $Lang::tr{'from'}, $Lang::tr{'count'} ]; + + my $stats = get_log( $self ); + + foreach my $who (sort keys %{ $$stats{'Failed'} } ) + { + my $count = $$stats{'Failed'}{$who}; + my ($user, $from) = split /||/, $who; + + push @table, [ $user, $from, $count ]; + } + + if (@table > 2) + { + $self->add_table( @table ); + + return 1; + } + + return 0; +} + +1; diff --git a/src/statusmail/plugins/system_status_ps.pm b/src/statusmail/plugins/system_status_ps.pm new file mode 100644 index 000000000..18481e8d5 --- /dev/null +++ b/src/statusmail/plugins/system_status_ps.pm @@ -0,0 +1,132 @@ +#!/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 System_Status_Ps; + +############################################################################ +# Function prototypes +############################################################################ + +sub processes( $$ ); + + +############################################################################ +# BEGIN Block +# +# Register the log items available in this file +############################################################################ + +sub BEGIN +{ + my @users; + + open PASSWD, '<', '/etc/passwd' or return; + + foreach my $line (<PASSWD>) + { + my ($user) = $line =~ m/^(\w+):/; + push @users, $user if ($user); + } + + close PASSWD; + + main::add_mail_item( 'ident' => 'system-status-processes', + 'section' => $Lang::tr{'system'}, + 'subsection' => $Lang::tr{'status'}, + 'item' => $Lang::tr{'processes'}, + 'function' => &processes, + 'option' => { 'type' => 'select', + 'name' => $Lang::tr{'user'}, + 'values' => [ $Lang::tr{'statusmail system ps any'}, sort @users ] } ); +} + +############################################################################ +# Code +############################################################################ + +#------------------------------------------------------------------------------ +# sub processes( this, user ) +# +# Adds the current status of the system processes +# +# Parameters: +# this message object +# user user to show processes for or 'any' for all users +#------------------------------------------------------------------------------ + +sub processes( $$ ) +{ + my ($message, $user) = @_; + my $cmd = ''; + my @lines; + + use Sort::Naturally; + + # Convert the option to a switch for the PS command + + if (not $user or $user eq $Lang::tr{'statusmail system ps any'}) + { + $cmd = 'ps -AF'; + } + else + { + $cmd = "ps -FU $user"; + } + + # Get the process information + + foreach my $line (`$cmd`) + { + my @fields = split /\s+/, $line, 11; + shift @fields unless ($fields[0]); + push @lines, [ @fields ]; + } + + # Remove the first line so it's not included in the sort + + my $header = shift @lines; + + # Sort the processes in descending order of CPU time + + my @sorted = sort { ncmp( $$b[9], $$a[9] ) } @lines; + + # Put the header row back on + + unshift @sorted, $header; + + if (@sorted > 2) + { + $message->add_title( $Lang::tr{'processes'} ); + $message->add_table( @sorted ); + } + + return 1; +} + +1; diff --git a/src/statusmail/plugins/system_status_services.pm b/src/statusmail/plugins/system_status_services.pm new file mode 100644 index 000000000..467d68a2b --- /dev/null +++ b/src/statusmail/plugins/system_status_services.pm @@ -0,0 +1,390 @@ +#!/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 System_Status_Services; + +############################################################################ +# Variables +############################################################################ + +my %servicenames = ( + $Lang::tr{'dhcp server'} => 'dhcpd', + $Lang::tr{'web server'} => 'httpd', + $Lang::tr{'cron server'} => 'fcron', + $Lang::tr{'dns proxy server'} => 'unbound', + $Lang::tr{'logging server'} => 'syslogd', + $Lang::tr{'kernel logging server'} => 'klogd', + $Lang::tr{'ntp server'} => 'ntpd', + $Lang::tr{'secure shell server'} => 'sshd', + $Lang::tr{'vpn'} => 'charon', + $Lang::tr{'web proxy'} => 'squid', + 'OpenVPN' => 'openvpn', + $Lang::tr{'intrusion prevention system'} => 'suricata' + ); + +my %fullname = ( + $Lang::tr{'dhcp server'} => "$Lang::tr{'dhcp server'}", + $Lang::tr{'web server'} => $Lang::tr{'web server'}, + $Lang::tr{'cron server'} => $Lang::tr{'cron server'}, + $Lang::tr{'dns proxy server'} => $Lang::tr{'dns proxy server'}, + $Lang::tr{'logging server'} => $Lang::tr{'logging server'}, + $Lang::tr{'kernel logging server'} => $Lang::tr{'kernel logging server'}, + $Lang::tr{'ntp server'} => "$Lang::tr{'ntp server'}", + $Lang::tr{'secure shell server'} => "$Lang::tr{'secure shell server'}", + $Lang::tr{'vpn'} => "$Lang::tr{'vpn'}", + $Lang::tr{'web proxy'} => "$Lang::tr{'web proxy'}", + 'OpenVPN' => "OpenVPN", + $Lang::tr{'intrusion prevention system'} => "$Lang::tr{'intrusion prevention system'}", + ); + +# Hash to overwrite the process name of a process if it differs fromt the launch command. +my %overwrite_exename_hash = ( + "suricata" => "Suricata-Main" +); + +############################################################################ +# Function prototypes +############################################################################ + +sub services( $ ); +sub isrunning( $ ); +sub isrunningaddon( $ ); + +############################################################################ +# Function prototypes +############################################################################ + +my %netsettings=(); +my $read_netsettings = 0; + +############################################################################ +# BEGIN Block +# +# Register the log items available in this file +############################################################################ + +sub BEGIN +{ + main::add_mail_item( 'ident' => 'system-status-services', + 'section' => $Lang::tr{'system'}, + 'subsection' => $Lang::tr{'status'}, + 'item' => $Lang::tr{'services'}, + 'function' => &services ); +} + +############################################################################ +# Code +############################################################################ + +#------------------------------------------------------------------------------ +# sub services +# +# Adds the current status of the system services +#------------------------------------------------------------------------------ + +sub services( $ ) +{ + my $message = shift; + + my @output; + + if (not $read_netsettings) + { + &General::readhash("${General::swroot}/ethernet/settings", %netsettings); + $read_netsettings = 1; + } + + my $iface = ''; + + if (open(FILE, "${General::swroot}/red/iface")) + { + $iface = <FILE>; + close FILE; + chomp $iface; + } + + # Item title and table heading + + $message->add_title( $Lang::tr{'services'} ); + + if ($message->is_html) + { + push @output, "<table>\n"; + push @output, "<tr><th align='left'>$Lang::tr{'services'}</th><th>$Lang::tr{'status'}</th><th>PID</th><th>$Lang::tr{'memory'}</th></tr>\n"; + } + else + { + push @output, [ $Lang::tr{'services'}, $Lang::tr{'status'}, 'PID', $Lang::tr{'memory'} ]; + } + + # Get the service statuses + + foreach my $key (sort keys %servicenames) + { + my $shortname = $servicenames{$key}; + + my @status = isrunning( $shortname ); + + if ($message->is_html) + { + my $running = "<td class='ok'>$Lang::tr{'running'}</td>"; + + if ($status[0] ne $Lang::tr{'running'}) + { + $running = "<td class='error'>$Lang::tr{'stopped'}</td>"; + } + + push @output, "<tr><td>$key</td>$running<td style='text-align: right'>$status[1]</td><td style='text-align: right'>$status[2]</td></tr>\n"; + } + else + { + push @output, [ $key, @status ]; + } + } + + # Output the table and the header for the addons + + if ($message->is_html) + { + push @output, "</table>\n"; + + $message->add( @output ); + + @output = (); + + $message->add_title( "Addon - $Lang::tr{'services'}" ); + push @output, "<table>\n"; + push @output, "<tr><th align='left'>$Lang::tr{'services'}</th><th>$Lang::tr{'status'}</th><th>PID</th><th>$Lang::tr{'memory'}</th></tr>\n"; + } + else + { + $message->add_table( @output ); + @output = (); + + $message->add_title( "Addon - $Lang::tr{'services'}" ); + + push @output, [ $Lang::tr{'services'}, $Lang::tr{'status'}, '', $Lang::tr{'memory'} ]; + } + + # Get the status of the addons + + my @pak = `find /opt/pakfire/db/installed/meta-* 2>/dev/null | cut -d"-" -f2`; + + foreach my $pak (@pak) + { + chomp($pak); + + # Check which of the paks are services + my @services = `find /etc/init.d/$pak 2>/dev/null | cut -d"/" -f4`; + + foreach my $key (@services) + { + # blacklist some packages + + chomp($key); + + next if ( $key eq 'squid' ); + + my @status = isrunningaddon( $key ); + + if ($message->is_html) + { + my $running = "<td class='ok'>$Lang::tr{'running'}</td>"; + + if ($status[0] ne $Lang::tr{'running'}) + { + $running = "<td class='error'>$Lang::tr{'stopped'}</td>"; + } + + push @output, "<tr><td>$key</td>$running<td style='text-align: right'>$status[1]</td><td style='text-align: right'>$status[2]</td></tr>\n"; + } + else + { + push @output, [ $key, @status ]; + } + } + } + + push @output, "</table>\n" if ($message->is_html); + + if ($message->is_html) + { + $message->add( @output ); + } + else + { + $message->add_table( @output ); + } + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub isrunning( cmd ) +# +# Gets the status of a system service +# +# Parameters: +# cmd Service command name +#------------------------------------------------------------------------------ + +sub isrunning( $ ) +{ + my ($cmd) = @_; + my @status; + my $pid = ''; + my $testcmd = ''; + my $exename; + my $memory; + + @status = ( $Lang::tr{'stopped'}, '', '' ); + + $exename = $cmd =~ /(^[a-z]+)/; + + # Check if the exename needs to be overwritten. + # This happens if the expected process name string + # differs from the real one. This may happened if + # a service uses multiple processes or threads. + if (exists($overwrite_exename_hash{$cmd})) + { + # Grab the string which will be reported by + # the process from the corresponding hash. + $exename = $overwrite_exename_hash{$cmd}; + } + else + { + # Directly expect the launched command as + # process name. + $exename = $cmd; + } + + if (open(FILE, "/var/run/${cmd}.pid")) + { + $pid = <FILE>; + chomp $pid; + close FILE; + + if (open(FILE, "/proc/${pid}/status")) + { + while (<FILE>) + { + if (/^Name:\W+(.*)/) + { + $testcmd = $1; + } + } + close FILE; + } + + if (open(FILE, "/proc/${pid}/status")) + { + while (<FILE>) + { + my ($key, $val) = split(":", $_, 2); + if ($key eq 'VmRSS') + { + $memory = $val; + chomp $memory; + last; + } + } + close(FILE); + } + + if ($testcmd =~ /$exename/) + { + @status = ( $Lang::tr{'running'}, $pid, $memory ); + } + } + + return @status; +} + + +#------------------------------------------------------------------------------ +# sub isrunningaddon +# +# Gets the status of an addon service +# +# Parameters: +# cmd Service command name +#------------------------------------------------------------------------------ + +sub isrunningaddon( $ ) +{ + my ($cmd) = @_; + my @status; + my $pid = ''; + my $exename; + my @memory; + + @status = ( $Lang::tr{'stopped'}, '', '' ); + + my $testcmd = `/usr/local/bin/addonctrl $cmd status 2>/dev/null`; + + if ( $testcmd =~ /is\ running/ && $testcmd !~ /is\ not\ running/) + { + @status = ( $Lang::tr{'running'} ); + + $testcmd =~ s/.* //gi; + $testcmd =~ s/[a-z_]//gi; + $testcmd =~ s/[[0-1];[0-9]+//gi; + $testcmd =~ s/[().]//gi; + $testcmd =~ s/ //gi; + $testcmd =~ s///gi; + + my @pid = split( /\s/, $testcmd ); + + push @status, $pid[0]; + + my $memory = 0; + + foreach (@pid) + { + chomp($_); + if (open(FILE, "/proc/$_/statm")) + { + my $temp = <FILE>; + @memory = split(/ /,$temp); + } + $memory += $memory[0]; + } + + push @status, "${memory} kB"; + } + else + { + @status = ( $Lang::tr{'stopped'}, '', '' ); + } + + return @status; +} + +1;