[08/12] statusmail: Plugins for system

Message ID 20190405172940.13168-9-ipfr@tfitzgeorge.me.uk
State New
Headers show
Series statusmail: Status and Log Summary Emails | expand

Commit Message

Tim FitzGeorge April 6, 2019, 4:29 a.m. UTC
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

Patch

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;