From patchwork Sat Apr 6 04:29:36 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tim FitzGeorge X-Patchwork-Id: 2179 Return-Path: Received: from mail01.ipfire.org (mail01.i.ipfire.org [172.28.1.200]) (using TLSv1.2 with cipher ECDHE-ECDSA-AES256-GCM-SHA384 (256/256 bits)) (Client CN "mail01.ipfire.org", Issuer "Let's Encrypt Authority X3" (verified OK)) by web07.i.ipfire.org (Postfix) with ESMTPS id C6646861F37 for ; Fri, 5 Apr 2019 18:30:57 +0100 (BST) Received: from mail01.i.ipfire.org (localhost [IPv6:::1]) by mail01.ipfire.org (Postfix) with ESMTP id 44bRg51tBgz5Lcxv; Fri, 5 Apr 2019 18:30:57 +0100 (BST) Received: from smtp.hosts.co.uk (smtp.hosts.co.uk [85.233.160.19]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by mail01.ipfire.org (Postfix) with ESMTPS id 44bRfm4X9Yz5Lcxv for ; Fri, 5 Apr 2019 18:30:40 +0100 (BST) Received: from [31.127.205.161] (helo=aragorn.tfitzgeorge.me.uk) by smtp.hosts.co.uk with esmtpa (Exim) (envelope-from ) id 1hCSfl-00006W-5w; Fri, 05 Apr 2019 18:30:40 +0100 From: Tim FitzGeorge To: development@lists.ipfire.org Subject: [PATCH 08/12] statusmail: Plugins for system Date: Fri, 5 Apr 2019 18:29:36 +0100 Message-Id: <20190405172940.13168-9-ipfr@tfitzgeorge.me.uk> X-Mailer: git-send-email 2.16.4 In-Reply-To: <20190405172940.13168-1-ipfr@tfitzgeorge.me.uk> References: <20190405172940.13168-1-ipfr@tfitzgeorge.me.uk> X-Spamd-Result: default: False [-13.17 / 11.00]; ARC_NA(0.00)[]; RCVD_VIA_SMTP_AUTH(0.00)[]; FROM_EQ_ENVFROM(0.00)[]; FROM_HAS_DN(0.00)[]; TO_DN_SOME(0.00)[]; R_SPF_ALLOW(-0.20)[+ip4:85.233.160.19]; MIME_GOOD(-0.10)[text/plain]; RCVD_TLS_LAST(0.00)[]; REPLY(-4.00)[]; DMARC_NA(0.00)[tfitzgeorge.me.uk]; TO_MATCH_ENVRCPT_SOME(0.00)[]; MX_GOOD(-0.01)[cached: mx1.ukservers.net]; RCPT_COUNT_TWO(0.00)[2]; MID_CONTAINS_FROM(1.00)[]; NEURAL_HAM(-2.99)[-0.997,0]; IP_SCORE(-3.77)[ip: (-9.91), ipnet: 85.233.160.0/19(-4.96), asn: 8622(-3.96), country: GB(-0.04)]; RCVD_IN_DNSWL_LOW(-0.10)[19.160.233.85.list.dnswl.org : 127.0.5.1]; R_DKIM_NA(0.00)[]; MIME_TRACE(0.00)[0:+]; ASN(0.00)[asn:8622, ipnet:85.233.160.0/19, country:GB]; RCVD_COUNT_TWO(0.00)[2]; BAYES_HAM(-3.00)[100.00%]; RECEIVED_SPAMHAUS_PBL(0.00)[161.205.127.31.zen.spamhaus.org : 127.0.0.11] Authentication-Results: mail01.ipfire.org; dkim=none; dmarc=none; spf=pass (mail01.ipfire.org: domain of ipfr@tfitzgeorge.me.uk designates 85.233.160.19 as permitted sender) smtp.mailfrom=ipfr@tfitzgeorge.me.uk X-BeenThere: development@lists.ipfire.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: IPFire development talk List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: development-bounces@lists.ipfire.org Sender: "Development" Signed-off-by: Tim FitzGeorge --- 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 = ; + chomp $current; + + close IN; + + my $core_release; + + open IN, '<', $update_list_file or warn "Can't open core update list file: $!"; + + foreach my $line () + { + 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 () + { + 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 () + { + 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 () + { + 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 = ; + close FILE; + chomp $iface; + } + + # Item title and table heading + + $message->add_title( $Lang::tr{'services'} ); + + if ($message->is_html) + { + push @output, "\n"; + push @output, "\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 = ""; + + if ($status[0] ne $Lang::tr{'running'}) + { + $running = ""; + } + + push @output, "$running\n"; + } + else + { + push @output, [ $key, @status ]; + } + } + + # Output the table and the header for the addons + + if ($message->is_html) + { + push @output, "
$Lang::tr{'services'}$Lang::tr{'status'}PID$Lang::tr{'memory'}
$Lang::tr{'running'}$Lang::tr{'stopped'}
$key$status[1]$status[2]
\n"; + + $message->add( @output ); + + @output = (); + + $message->add_title( "Addon - $Lang::tr{'services'}" ); + push @output, "\n"; + push @output, "\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 = ""; + + if ($status[0] ne $Lang::tr{'running'}) + { + $running = ""; + } + + push @output, "$running\n"; + } + else + { + push @output, [ $key, @status ]; + } + } + } + + push @output, "
$Lang::tr{'services'}$Lang::tr{'status'}PID$Lang::tr{'memory'}
$Lang::tr{'running'}$Lang::tr{'stopped'}
$key$status[1]$status[2]
\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 = ; + chomp $pid; + close FILE; + + if (open(FILE, "/proc/${pid}/status")) + { + while () + { + if (/^Name:\W+(.*)/) + { + $testcmd = $1; + } + } + close FILE; + } + + if (open(FILE, "/proc/${pid}/status")) + { + while () + { + 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 = ; + @memory = split(/ /,$temp); + } + $memory += $memory[0]; + } + + push @status, "${memory} kB"; + } + else + { + @status = ( $Lang::tr{'stopped'}, '', '' ); + } + + return @status; +} + +1;