From patchwork Sat Apr 6 04:29:29 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tim FitzGeorge X-Patchwork-Id: 2172 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 4907A861F37 for ; Fri, 5 Apr 2019 18:30:09 +0100 (BST) Received: from mail01.i.ipfire.org (localhost [IPv6:::1]) by mail01.ipfire.org (Postfix) with ESMTP id 44bRf84jSqz5D0PN; Fri, 5 Apr 2019 18:30:08 +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 44bRdy321tz5D0PB for ; Fri, 5 Apr 2019 18:29:58 +0100 (BST) Received: from [31.127.205.161] (helo=aragorn.tfitzgeorge.me.uk) by smtp.hosts.co.uk with esmtpa (Exim) (envelope-from ) id 1hCSf5-0008Ml-64; Fri, 05 Apr 2019 18:29:58 +0100 From: Tim FitzGeorge To: development@lists.ipfire.org Subject: [PATCH 01/12] statusmail: Main script Date: Fri, 5 Apr 2019 18:29:29 +0100 Message-Id: <20190405172940.13168-2-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 [-9.14 / 11.00]; ARC_NA(0.00)[]; RCVD_VIA_SMTP_AUTH(0.00)[]; RECEIVED_SPAMHAUS_PBL(0.00)[161.205.127.31.zen.spamhaus.org : 127.0.0.11]; 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)[]; DMARC_NA(0.00)[tfitzgeorge.me.uk]; TO_MATCH_ENVRCPT_SOME(0.00)[]; MX_GOOD(-0.01)[mx1.ukservers.net]; RCPT_COUNT_TWO(0.00)[2]; MID_CONTAINS_FROM(1.00)[]; NEURAL_HAM(-2.96)[-0.987,0]; IP_SCORE(-3.77)[ip: (-9.91), ipnet: 85.233.160.0/19(-4.96), asn: 8622(-3.96), country: GB(-0.04)]; FROM_EQ_ENVFROM(0.00)[]; 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(-2.99)[99.98%]; RCVD_IN_DNSWL_LOW(-0.10)[19.160.233.85.list.dnswl.org : 127.0.5.1] 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" Called by fcron hourly to look for scheduled messages. StatusMail.pm declares an object which is used to create the mail messages. Signed-off-by: Tim FitzGeorge --- src/statusmail/StatusMail.pm | 530 +++++++++++++++++++++++++++++++++++++++++++ src/statusmail/statusmail.pl | 422 ++++++++++++++++++++++++++++++++++ 2 files changed, 952 insertions(+) create mode 100644 src/statusmail/StatusMail.pm create mode 100755 src/statusmail/statusmail.pl diff --git a/src/statusmail/StatusMail.pm b/src/statusmail/StatusMail.pm new file mode 100644 index 000000000..fb37c3663 --- /dev/null +++ b/src/statusmail/StatusMail.pm @@ -0,0 +1,530 @@ +#!/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; + +use lib "/usr/lib/statusmail"; + +package StatusMail; + +use base qw/EncryptedMail/; + +############################################################################ +# Constants +############################################################################ + +use constant { SEC => 0, + MIN => 1, + HOUR => 2, + MDAY => 3, + MON => 4, + YEAR => 5, + WDAY => 6, + YDAY => 7, + ISDST => 8, + MONSTR => 9 }; + +use constant MONTHS => qw( Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec ); + +use constant LOGNAME => '/var/log/messages'; + +############################################################################ +# Configuration variables +############################################################################ + +my @monthnames = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', + 'Sep', 'Oct', 'Nov', 'Dec'); +my %months; + +############################################################################ +# Variables +############################################################################ + +my %address_lookup_cache; + +############################################################################ +# Function prototypes +############################################################################ + +sub calculate_period( $$ ); +sub get_period_start(); +sub get_period_end(); +sub get_number_weeks(); +sub cache( $;$ ); +sub lookup_ip_address( $$ ); +sub set_host_name( $$$ ); +sub split_string( $$$ ); + +############################################################################ +# Initialisation code +############################################################################ + + +foreach (my $monindex = 0 ; $monindex < MONTHS ; $monindex++) +{ + $months{(MONTHS)[$monindex]} = $monindex; +} + +#------------------------------------------------------------------------------ +# sub new +# +# Class constructor +#------------------------------------------------------------------------------ + +sub new +{ + my $invocant = shift; + + my $class = ref($invocant) || $invocant; + + my $self = $class->SUPER::new( @_ ); + + $self->{last_time} = 0; + $self->{last_mon} = 0; + $self->{last_day} = 0; + $self->{last_hour} = 0; + + bless( $self, $class ); + + return $self; +} + +#------------------------------------------------------------------------------ +# sub calculate_period( value, unit ) +# +# Calculates the limits of the period covered by the message +# +# Parameters: +# value Number of units +# unit Unit of time +#------------------------------------------------------------------------------ + +sub calculate_period( $$ ) +{ + my ( $self, $value, $unit ) = @_; + + use Time::Local; + + my $start_time = 0; + my @start_time = (); + my $end_time = 0; + my @end_time = (); + my $weeks_covered = 0; + + @end_time = localtime(); + + $end_time[SEC] = 0; + $end_time[MIN] = 0; + + $end_time = timelocal( @end_time ); + + if ($unit eq 'months') + { + # Go back the specified number of months + + @start_time = @end_time; + + $start_time[MON] -= $value; + if ($start_time[MON] < 0 ) + { + $start_time[MON] += 12; + $start_time[YEAR]--; + } + + $start_time = timelocal( @start_time ); + } + else + { + my $hours = $value; + + # Go back the specified number of hours, days or weeks + + $hours *= 24 if ($unit eq 'days'); + $hours *= 24 * 7 if ($unit eq 'weeks'); + + $start_time = timelocal( @end_time ) - ($hours * 3600); + @start_time = localtime( $start_time ); + } + + # Adjust end to end of previous hour rather than start of current hour + + $end_time--; + @end_time = localtime( $end_time ); + + # Add the alphabetic month to the end of the time lists + + push @start_time, $monthnames[ $start_time[MON] ]; + push @end_time, $monthnames[ $end_time[MON] ]; + + # Calculate how many archive files have to be read + + my $week_start = $start_time - ($start_time[WDAY] * 86400) - ($start_time[HOUR] * 3600) + 3600; + $weeks_covered = int( (time() - $week_start) / (86400 * 7) ); + + $self->{'start_time_array'} = \@start_time; + $self->{'start_time'} = $start_time; + $self->{'end_time_array'} = \@end_time; + $self->{'end_time'} = $end_time; + $self->{'weeks_covered'} = $weeks_covered; + $self->{'period'} = "$value$unit"; + $self->{'period'} =~ s/s$//; + $self->{'total_days'} = ($end_time - $start_time) / 86400; +} + + +#------------------------------------------------------------------------------ +# sub get_period() +# +# Returns the period covered by a report. +#------------------------------------------------------------------------------ + +sub get_period() +{ + my $self = shift; + + return $self->{'period'}; +} + + +#------------------------------------------------------------------------------ +# sub get_period_start() +# +# Returns the start of the period covered by a report. +#------------------------------------------------------------------------------ + +sub get_period_start() +{ + my $self = shift; + + return wantarray ? @{$self->{'start_time_array'}} : $self->{'start_time'}; +} + + +#------------------------------------------------------------------------------ +# sub get_period_end() +# +# Returns the end of the period covered by a report. +#------------------------------------------------------------------------------ + +sub get_period_end() +{ + my $self = shift; + + return wantarray ? @{$self->{'end_time_array'}} : $self->{'end_time'}; +} + + +#------------------------------------------------------------------------------ +# sub get_number_weeks() +# +# Returns the number of complete weeks covered by a report. +#------------------------------------------------------------------------------ + +sub get_number_weeks() +{ + my $self = shift; + + return $self->{'weeks_covered'}; +} + + +#------------------------------------------------------------------------------ +# sub cache( name [, item] ) +# +# Either caches an item or returns the cached item. +# +# Parameters: +# Name name of item +# Item item to be cached (optional) +# +# Returns: +# Cached item if no item specified, undef otherwise +#------------------------------------------------------------------------------ + +my %cache; + +sub cache( $;$ ) +{ + my ($self, $name, $item) = @_; + + if ($item) + { + $cache{$name} = $item; + } + else + { + return $cache{$name}; + } + + return undef; +} + + +#------------------------------------------------------------------------------ +# sub clear_cache() +# +# Clears any cached values. +#------------------------------------------------------------------------------ + +sub clear_cache() +{ + %cache = (); +} + + +#------------------------------------------------------------------------------ +# sub get_message_log_line() +# +# Gets the next line from the message log. +# Will cache log entries if the period covered is short. +#------------------------------------------------------------------------------ + +sub get_message_log_line +{ + my $self = shift; + my $line; + + if (exists $self->{logindex}) + { + # Reading from the cache + + if ($self->{logindex} < @{ $self->{logcache} }) + { + return $self->{logcache}[$self->{logindex}++]; + } + else + { + # End of cache - reset to start again on next call + + $self->{logindex} = 0; + return undef; + } + } + + $self->{logfile} = $self->{'weeks_covered'} if (not exists $self->{logfile} or $self->{logfile} < 0); + + LINE: + while (1) + { + if (not exists $self->{fh} or (exists $self->{fh} and eof $self->{fh})) + { + # Reading from a file and need to open a file + + FILE: + while ($self->{logfile} >= 0) + { + my $name = $self->{logfile} < 1 ? LOGNAME : LOGNAME . '.' . $self->{logfile}; + $self->{logfile}--; + + if (-r $name) + { + # Not compressed + + open $self->{fh}, '<', $name or die "Can't open $name: $!"; + $self->{year} = (localtime( (stat(_))[9] ))[YEAR]; + last FILE; + } + elsif (-r "$name.gz") + { + # Compressed + + open $self->{fh}, "gzip -dc $name.gz |" or next; + $self->{year} = (localtime( (stat(_))[9] ))[YEAR]; + last FILE; + } + + # Not found - go back for next file + } + + if ($self->{logfile} < -1) + { + # No further files - reset to start again on next call + + delete $self->{fh}; + return undef; + } + } + + if (exists $self->{fh}) + { + # Reading from a file + + $line = readline $self->{fh}; + + if (eof $self->{fh}) + { + if ($self->{logfile} < 0) + { + # No further files - reset to start again on next call + + delete $self->{fh}; + return undef; + } + # Go back for next file + + close $self->{fh}; + next LINE; + } + + my ($mon, $day, $hour) = unpack 'Lsxs', $line; + + if ($mon != $self->{last_mon} or $day != $self->{last_day} or $hour != $self->{last_hour}) + { + # Hour, day or month changed. Convert to unix time so we can work out + # whether the message time falls between the limits we're interested in. + # This is complicated by the lack of a year in the logged information, + # so assume the current year, and adjust if necessary. + + my @time; + + $time[YEAR] = $self->{year}; + + ($time[MON], $time[MDAY], $time[HOUR], $time[MIN], $time[SEC]) = split /[\s:]+/, $line; + $time[MON] = $months{$time[MON]}; + + $self->{time} = timelocal( @time ); + + if ($self->{time} > time()) + { + # We can't have times in the future, so this must be the previous year. + + $self->{year}--; + $time[YEAR]--; + $self->{time} = timelocal( @time ); + $self->{last_time} = $self->{time}; + } + elsif ($self->{time} < $self->{last_time}) + { + # Time should be increasing, so we must have gone over a year boundary. + + $self->{year}++; + $time[YEAR]++; + $self->{time} = timelocal( @time ); + $self->{last_time} = $self->{time}; + } + + ($self->{last_mon}, $self->{last_day}, $self->{last_hour}) = ($mon, $day, $hour); + } + + # Check to see if we're within the specified limits. + # Note that the minutes and seconds may be incorrect, but since we only deal + # in hour boundaries this doesn't matter. + + next LINE if ($self->{time} < $self->{start_time}); + + if ($self->{time} > $self->{end_time}) + { + # After end time - reset to start again on next call + + close $self->{fh}; + delete $self->{fh}; + $self->{logfile} = $self->{'weeks_covered'}; + + return undef; + } + + # Cache the entry if the time covered is less than two days + + push @{$self->{logcache}}, $line if ($self->{'total_days'} <= 2); + + return $line; + } + } + + return $line; +} + + +#------------------------------------------------------------------------------ +# sub lookup_ip_address( string ) +# +# Converts an IP Address to a URL +#------------------------------------------------------------------------------ + +sub lookup_ip_address( $$ ) +{ + my ($self, $address) = @_; + + use Socket; + + return $address_lookup_cache{$address} if (exists $address_lookup_cache{$address}); + + my $name = gethostbyaddr( inet_aton( $address ), AF_INET ) || ""; + + $address_lookup_cache{$address} = $name; + + return $name; +} + + +#------------------------------------------------------------------------------ +# sub set_host_name( address, name ) +# +# Records the mapping from an IP address to a name +#------------------------------------------------------------------------------ + +sub set_host_name( $$$ ) +{ + my ($self, $address, $name) = @_; + + return unless ($address and $name); + return if ($address eq $name); + + if (exists $address_lookup_cache{$address}) + { + $address_lookup_cache{$address} = "" if ($address_lookup_cache{$address} ne $name); + } + else + { + $address_lookup_cache{$address} = $name; + } +} + + +#------------------------------------------------------------------------------ +# sub spilt_string( string, size ) +# +# Splits a string into multiple lf separated lines +#------------------------------------------------------------------------------ + +sub split_string( $$$ ) +{ + my ($self, $string, $size) = @_; + + my $out = ''; + + while (length $string > $size) + { + $string =~ s/(.{$size,}?)\s+//; + last unless ($1); + $out .= $1 . "\n"; + } + + $out .= $string; + + return $out; +} + +1; diff --git a/src/statusmail/statusmail.pl b/src/statusmail/statusmail.pl new file mode 100755 index 000000000..4ced65880 --- /dev/null +++ b/src/statusmail/statusmail.pl @@ -0,0 +1,422 @@ +#!/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 # +# # +############################################################################ +# Main script for statusmail. # +# # +# Usually called by fcron when it will check to see if any schedules are # +# due in which case the schedule will be executed. If the schedule # +# produces any output it is sent as an email to the recipients given in # +# the schedule. Emails are always signed using GPG and will be encrypted # +# if an encryption keys is available for the user. # +# # +# Can also be run with the name of a schedule as an argument in which case # +# the schedule is executed immediately regrardless of whether it is due or # +# not. # +# # +# If run from a terminal additional debugging will be turned on and log # +# messages will be output to the terminal. # +############################################################################ + +use strict; +use warnings; + +use Sys::Syslog qw(:standard :macros); + +use lib "/usr/lib/statusmail"; + +require "/var/ipfire/general-functions.pl"; +require "${General::swroot}/lang.pl"; + +use StatusMail; + +############################################################################ +# Configuration variables +# +# These variables give the locations of various files used by this script +############################################################################ + +my $lib_dir = "/usr/lib/statusmail"; +my $plugin_dir = "$lib_dir/plugins"; +my $stylesheet = "$lib_dir/stylesheet.css"; +my $mainsettings = "${General::swroot}/main/settings"; +my $mailsettings = "${General::swroot}/dma/mail.conf"; +my $contactsettings = "${General::swroot}/statusmail/contact_settings"; +my $schedulesettings = "${General::swroot}/statusmail/schedule_settings"; +my $debug = 0; + +############################################################################ +# Function prototypes +############################################################################ + +# Used by plugins + +sub add_mail_item( % ); + +# Local functions + +sub send_email( $ ); +sub execute_schedule( $$ ); +sub abort( $ ); +sub log_message( $$ ); +sub debug( $$ ); + +############################################################################ +# Variables +############################################################################ + +my %mainsettings = (); +my %sections = (); +my $contacts = {}; +my $schedules = {}; +my %mailsettings = (); + + +############################################################################ +# Main function +############################################################################ + +openlog( "statusmail", "nofatal", LOG_USER); +log_message LOG_INFO, "Starting log and status email processing"; + +# Check for existence of settings files + +exit unless (-r $contactsettings); +exit unless (-e $mailsettings); +exit unless (-r $schedulesettings); + +# Read settings + +General::readhash($mailsettings, \%mailsettings); +General::readhash($mainsettings, \%mainsettings); + +unless ($mailsettings{'USEMAIL'} eq 'on') +{ + log_message LOG_WARNING, "Email disabled"; + exit; +}; + +eval qx|/bin/cat $contactsettings| if (-r $contactsettings); +eval qx|/bin/cat $schedulesettings| if (-r $schedulesettings); + +# Scan for plugins + +opendir DIR, $plugin_dir or abort "Can't open Plug-in directory $plugin_dir: $!"; + +foreach my $file (readdir DIR) +{ + next unless ($file =~ m/\.pm$/); + + debug 1, "Initialising plugin $file"; + + require "$plugin_dir/$file"; +} + +# Check command line parameters + +if (@ARGV) +{ + # Command line parameters provided - try to execute the named schedule. + + my ($schedule) = $ARGV[0]; + + if (exists $$schedules{$schedule}) + { + execute_schedule( $schedule, $$schedules{$schedule} ); + } + else + { + print "Schedule '$schedule' not found\n"; + } + + closelog; + exit; +} + +# Look for a due schedule + +my (undef, undef, $hour, $mday, undef, undef, $wday, undef, undef) = localtime; + +$hour = 1 << $hour; +$wday = 1 << $wday; +$mday = 1 << $mday; + +foreach my $schedule (keys %$schedules) +{ + next unless ($$schedules{$schedule}{'enable'} eq 'on'); # Must be enabled + + next unless ($$schedules{$schedule}{'mday'} & $mday or # Must be due today + $$schedules{$schedule}{'wday'} & $wday); + + next unless ($$schedules{$schedule}{'hours'} & $hour); # Must be due this hour + + debug 1, "Schedule $schedule due"; + + execute_schedule( $schedule, $$schedules{$schedule} ); +} + +closelog; + +exit; + +#------------------------------------------------------------------------------ +# sub execute_schedule( name, schedule ) +# +# Executes the specified schedule as long as at least one of the contacts is +# enabled. +# +# Parameters: +# name name of Schedule +# schedule reference of Schedule hash to be executed +#------------------------------------------------------------------------------ + +sub execute_schedule( $$ ) +{ + my ($name, $schedule) = @_; + my @contacts; + my $status = 0; + + # Check that at least one of the contacts is enabled + + foreach my $contact (split '\|', $$schedule{'email'}) + { + push @contacts, $contact if (exists $$contacts{$contact} and $$contacts{$contact}{'enable'} eq 'on'); + } + + if (not @contacts) + { + debug 1, "No enabled contacts"; + return; + } + + log_message LOG_INFO, "Executing status mail schedule $name"; + + # Look for a theme stylesheet + + my $theme_stylesheet = "$lib_dir/$mainsettings{'THEME'}.css"; + $stylesheet = $theme_stylesheet if (-r $theme_stylesheet); + + # Create message + + my $message = new StatusMail( 'format' => $$schedule{'format'}, + 'subject' => $$schedule{'subject'}, + 'to' => [ @contacts ], + 'sender' => $mailsettings{'SENDER'}, + 'max_lines_per_item' => $$schedule{'lines'}, + 'stylesheet' => $stylesheet ); + + if (not $message) + { + log_message LOG_WARNING, "Failed to create message object: $!"; + return; + } + + $message->calculate_period( $$schedule{'period-value'}, $$schedule{'period-unit'} ); + + $message->add_text( "$Lang::tr{'statusmail period from'} " . localtime( $message->get_period_start ) . + " $Lang::tr{'statusmail period to'} " . localtime( $message->get_period_end ) . "\n" ); + + # Loop through the various log items + + foreach my $section ( sort keys %sections ) + { + debug 3, "Section $section"; + $message->add_section( $section ); + + foreach my $subsection ( sort keys %{ $sections{$section} } ) + { + debug 3, "Subsection $subsection"; + $message->add_subsection( $subsection ); + + foreach my $item ( sort keys %{ $sections{$section}{$subsection} } ) + { + debug 3, "Item $item"; + + # Is the item enabled? + + my $key = $sections{$section}{$subsection}{$item}{'ident'}; + + next unless (exists $$schedule{"enable_$key"} and $$schedule{"enable_$key"} eq 'on'); + next unless ($sections{$section}{$subsection}{$item}{'format'} eq 'both' or + $sections{$section}{$subsection}{$item}{'format'} eq $$schedule{'format'}); + + # Yes. Call the function to get it's content - with option if necessary + + debug 2, "Process item $section :: $subsection :: $item"; + + $message->add_title( $item ); + + my $function = $sections{$section}{$subsection}{$item}{'function'}; + + if (exists $$schedule{"value_$key"}) + { + $status += &$function( $message, $$schedule{"value_$key"} ); + } + else + { + $status += &$function( $message ); + } + } + + $message->clear_cache; + } + } + + # End the Message + + if ($status > 0) + { + debug 1, "Send mail message"; + $message->send; + } +} + + +#------------------------------------------------------------------------------ +# sub add_mail_item( params ) +# +# Adds a possible status item to the section and subsection specified. This +# function is called from the BEGIN block of the plugin. +# +# Any errors cause the item to be ignored without raising an error. +# +# Parameters: +# params hash containing details of the item to be added: +# section name of the section containing this item +# subsection name of the subsection containing this item +# item name of the item +# function function called to add item to message +# format available formats for the item 'html', 'text' or 'both' +# option hash specifying option parameter (optional) +# +# option can specify either a selection or an integer. For a selection it +# contains: +# type must be 'option' +# values array of strings representing the possible options +# +# For an integer option contains: +# type must be 'integer' +# min minimum valid value of parameter +# max maximum valid value of parameter +#------------------------------------------------------------------------------ + +sub add_mail_item( % ) +{ + my %params = @_; + + # Check for all required parameters + + return unless (exists $params{'section'} and + exists $params{'subsection'} and + exists $params{'item'} and + exists $params{'function'} ); + + # Check the option + + if ($params{'option'}) + { + return unless (ref $params{'option'} eq 'HASH'); + + if ($params{'option'}{'type'} eq 'select') + { + return unless (ref $params{'option'}{'values'} eq 'ARRAY' and @{ $params{'option'}{'values'} } > 1); + } + elsif ($params{'option'}{'type'} eq 'integer') + { + return unless (exists $params{'option'}{'min'} and + exists $params{'option'}{'max'} and + $params{'option'}{'min'} < $params{'option'}{'max'}); + } + else + { + return; + } + } + + $params{'format'} = 'both' unless (exists $params{'format'}); + + # Record that the option exists + + $sections{$params{'section'}}{$params{'subsection'}}{$params{'item'}} = { 'function' => $params{'function'}, + 'format' => $params{'format'}, + 'ident' => $params{'ident'} }; +} + + +#------------------------------------------------------------------------------ +# sub abort( message ) +# +# Aborts the update run, printing out an error message. +# +# Parameters: +# message Message to be printed +#------------------------------------------------------------------------------ + +sub abort( $ ) +{ +my ($message) = @_; + + log_message( LOG_ERR, $message ); + croak $message; +} + + +#------------------------------------------------------------------------------ +# sub log_message( level, message ) +# +# Logs a message to the system log. If the script is run from the terminal +# then the message is also printed locally. +# +# Parameters: +# level Severity of message +# message Message to be logged +#------------------------------------------------------------------------------ + +sub log_message( $$ ) +{ + my ($level, $message) = @_; + + print "($level) $message\n" if (-t STDIN); + syslog( $level, $message ); +} + + +#------------------------------------------------------------------------------ +# sub debug( level, message ) +# +# Optionally logs a debug message +# +# Parameters: +# level Debug level +# message Message to be logged +#------------------------------------------------------------------------------ + +sub debug( $$ ) +{ + my ($level, $message) = @_; + + if (($level <= $debug) or + ($level == 1 and -t STDIN)) + { + log_message LOG_DEBUG, $message; + } +} From patchwork Sat Apr 6 04:29:30 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tim FitzGeorge X-Patchwork-Id: 2173 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 54B94861F37 for ; Fri, 5 Apr 2019 18:30:17 +0100 (BST) Received: from mail01.i.ipfire.org (localhost [IPv6:::1]) by mail01.ipfire.org (Postfix) with ESMTP id 44bRfJ5MBCz5Lcxg; Fri, 5 Apr 2019 18:30:16 +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 44bRf40gXjz5D0PJ for ; Fri, 5 Apr 2019 18:30:04 +0100 (BST) Received: from [31.127.205.161] (helo=aragorn.tfitzgeorge.me.uk) by smtp.hosts.co.uk with esmtpa (Exim) (envelope-from ) id 1hCSfB-0008Ml-54; Fri, 05 Apr 2019 18:30:03 +0100 From: Tim FitzGeorge To: development@lists.ipfire.org Subject: [PATCH 02/12] statusmail: Perl module for encrypted Encrypted Mail Date: Fri, 5 Apr 2019 18:29:30 +0100 Message-Id: <20190405172940.13168-3-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 [-10.26 / 11.00]; ARC_NA(0.00)[]; RCVD_VIA_SMTP_AUTH(0.00)[]; RECEIVED_SPAMHAUS_PBL(0.00)[161.205.127.31.zen.spamhaus.org : 127.0.0.11]; 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(-0.09)[64.31%]; FROM_EQ_ENVFROM(0.00)[] 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" Contains functions for writing structured emails and then both signing and encrypting them using GPG. There is a module in CPAN that can sign and encrypt emails, but this conatins additionaly functionality that is not required (decryption etc) and also requires a number of other modules that are not currently used in IPFire. Signed-off-by: Tim FitzGeorge --- src/statusmail/EncryptedMail.pm | 1060 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 1060 insertions(+) create mode 100644 src/statusmail/EncryptedMail.pm diff --git a/src/statusmail/EncryptedMail.pm b/src/statusmail/EncryptedMail.pm new file mode 100644 index 000000000..22732cb9e --- /dev/null +++ b/src/statusmail/EncryptedMail.pm @@ -0,0 +1,1060 @@ +#!/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; + +use MIME::Lite; +use IPC::Open2; +use IO::Select; +use Socket; + +require "/var/ipfire/general-functions.pl"; +require "${General::swroot}/lang.pl"; + +package EncryptedMail; + +############################################################################ +# Configuration variables +############################################################################ + +my $gpg = "/usr/bin/gpg --homedir ${General::swroot}/statusmail/keys"; +my $contactsettings = "${General::swroot}/statusmail/contact_settings"; + + +############################################################################ +# Function prototypes +############################################################################ + +sub new( @ ); +sub send( $@ ); +sub add( $@ ); +sub add_text( $@ ); +sub add_table( $@ ); +sub _add_table_text( $@ ); +sub _add_table_html( $@ ); +sub add_section( $$ ); +sub add_subsection( $$ ); +sub add_title( $$ ); +sub is_html( $ ); +sub is_text( $ ); +sub get_max_lines_per_item( $ ); + +#------------------------------------------------------------------------------ +# sub new( params ) +# +# Creates a new mail message. +# +# Parameters: +# A hash which can contain the following fields: +# format format of the message: 'html' or 'text' +# to reference to a list of contact names. +# subject subject for email message +# sender email address of sender +#------------------------------------------------------------------------------ + +sub new( @ ) +{ + my $invocant = shift; + + my $class = ref($invocant) || $invocant; + + my $self = { 'message' => '', + 'format' => 'html', + 'in_section' => 0, + 'in_subsection' => 0, + 'in_item' => 0, + 'section' => '', + 'subsection' => '', + 'subject' => 'Encrypted email', + 'empty' => 1, + 'skip_blank_sections' => 0, + 'skip_blank_subsections' => 0, + 'image_file' => 'img0000', + 'max_lines_per_item' => 100, + @_ }; + + bless( $self, $class ); + + if ($self->{'format'} eq 'html') + { + # For an HTML message, put the head on the beginning. + + $self->{'message'} = "\n\n\n\n"; + + if ($self->{'stylesheet'}) + { + $self->{'message'} .= "\n"; + } + + $self->{'message'} .= "\n\n\n
\n"; + } + else + { + # For a text message, just put the subject + + $self->{'message'} .= "$self->{subject}\n\n" + } + + $self->{'to'} =~ s/\|/ /g if ($self->{'to'}); + + # Create an email message object + + $self->{'object'} = MIME::Lite->new( Type => 'multipart/related', + Encoding => '7bit' ); + + # Create the main part of the message + + if ($self->{format} eq 'html') + { + $self->{'text'} = $self->{'object'}->attach( Type => 'text/html', + Encoding => 'quoted-printable' ); + $self->{'text'}->attr('content-type.charset' => 'UTF-8'); + } + else + { + $self->{'text'} = $self->{'object'}->attach( Type => 'TEXT', + Encoding => 'quoted-printable' ); + } + + return $self; +} + + +#------------------------------------------------------------------------------ +# sub is_html() +# +# Returns true if the message format is HTML. +# Used by plugins. +#------------------------------------------------------------------------------ + +sub is_html( $ ) +{ + return shift->{'format'} eq 'html'; +} + + +#------------------------------------------------------------------------------ +# sub is_text() + +# Return true if the message format is text. +# Used by plugins. +#------------------------------------------------------------------------------ + +sub is_text( $ ) +{ + return not shift->{'format'} eq 'html'; +} + + +#------------------------------------------------------------------------------ +# sub get_max_lines_per_item() + +# Returns the maximum number of lines to include in an item. +# Used by plugins. +#------------------------------------------------------------------------------ + +sub get_max_lines_per_item( $ ) +{ + return shift->{'max_lines_per_item'}; +} + + +#------------------------------------------------------------------------------ +# sub send( subject ) +# +# Sends the message. A subject may be specified which overrides the one +# specified when creating the object. +#------------------------------------------------------------------------------ + +sub send( $@ ) +{ + use IPC::Open2; + + my $self = shift; + my ($subject) = @_; + my $contacts; + + # Don't do anything if there's no data + + return if ($self->{'empty'}); + + $subject ||= $self->{'subject'}; + + # Get the list of recipients, dividing it into signed and encrypted. + + return unless (-r $contactsettings); + eval qx|/bin/cat $contactsettings|; + + my @signed_recipients; + my @encrypted_recipients; + + foreach my $recipient ( @{ $self->{'to'} } ) + { + if ($$contacts{$recipient}{'fingerprint'}) + { + # Signed and encrypted + + push @encrypted_recipients, $$contacts{$recipient}{'email'}; + } + else + { + # Signed only + + push @signed_recipients, $$contacts{$recipient}{'email'}; + } + } + + return unless (@encrypted_recipients or @signed_recipients); + + # Build the data that's going to be signed + + if ($self->{format} eq 'html') + { + $self->{message} .= "
\n" if ($self->{in_item}); + $self->{message} .= "\n" if ($self->{in_subsection}); + $self->{message} .= "\n" if ($self->{in_section}); + + $self->{message} .= "\n\n\n"; + } + $self->{in_section} = 0; + + $self->{text}->data( $self->{message} ); + + #---------------------------------------------------------------------------- + # Sign message + + # Prepare to sign the data + + my ($from_gpg, $to_gpg); + + my $body = $self->{'object'}->as_string; + + # Sign the data + # Create a process running GPG + + my $cmd = "$gpg --batch --detach-sign --digest-algo sha256 --armour --local-user \"<$self->{'sender'}>\" --passphrase-fd 0"; + + my $childpid = open2( $from_gpg, $to_gpg, $cmd ); + + print $to_gpg "ipfirestatusemail\n"; + + # Pipe the data to be signed to GPG + # The signature is fairly short so we don't need to worry about buffering. + + foreach my $line (split /[\n]/, $body) + { + chomp $line; + $line =~ s/\s*$/\r\n/; + + print $to_gpg $line; + } + + print $to_gpg "\r\n"; + + close $to_gpg; + + # Get the signature + + my $signature = ''; + + $signature .= $_ while <$from_gpg>; + + waitpid( $childpid, 0 ); + + # Create the message that will contain the data and its signature + + my $signed_message = new MIME::Lite(Type => 'multipart/mixed' ); + + $signed_message->attr( 'content-type' => 'multipart/signed' ); + $signed_message->attr( 'content-type.protocol' => 'application/pgp-signature' ); + $signed_message->attr( 'content-type.micalg' => 'pgp-sha256' ); + + # Attach the signed data to the message + + $signed_message->attach( $self->{'object'} ); + + delete $self->{object}; + + # Attach the signature to the message + + $signed_message->attach( Type => 'application/pgp-signature', + Encoding => '7bit', + Data => $signature ); + + # Send the message to signed only recipients + + if (@signed_recipients) + { + $signed_message->add( From => $self->{'sender'} ); + $signed_message->add( To => [ @signed_recipients ] ); + $signed_message->add( Subject => $self->{'subject'} ); + + $signed_message->send(); + + # Delete tags that are not needed when the message is encrypted. + + $signed_message->delete( 'From' ); + $signed_message->delete( 'To' ); + $signed_message->delete( 'Subject' ); + } + + #---------------------------------------------------------------------------- + # Encrypt message + + # Encrypt and send the message to signed and encrypted recipients + + if (@encrypted_recipients) + { + # Find the keys of the recipients and build the GPG command + + $cmd = "$gpg --batch --encrypt --armour --always-trust --charset UTF8"; + + foreach my $recipient ( @{ $self->{'to'} } ) + { + if ($$contacts{$recipient}{'fingerprint'}) + { + my $fingerprint = $$contacts{$recipient}{'fingerprint'}; + $fingerprint =~ s/\s+//g; + + $cmd .= " --recipient $fingerprint"; + } + } + + # Build a mask to check if there's any output from GPG later + + my $reader = IO::Select->new; + + my $encrypted = ''; + + # Start GPG and pipe the signed message to it + # The encrypted message is longer than the IO buffer so we need to arrange + # to read and write asynchronously. + + $childpid = open2( $from_gpg, $to_gpg, $cmd ) or die "Can't fork GPG child: $!"; + + $reader->add( $from_gpg ); + + my $signed_data = $signed_message->as_string; + my $read; + + foreach my $line (split /[\n]/, $signed_data) + { + chomp $line; + $line =~ s/\s*$/\r\n/; + print $to_gpg $line; + + while ($reader->can_read( 0 )) + { + $encrypted .= <$from_gpg>; + } + } + + close $to_gpg; + + $encrypted .= $_ while (<$from_gpg>); + + close $from_gpg; + + # Create the message that will contain the data and its signature + + my $encrypted_message = new MIME::Lite( From => $self->{'sender'}, + To => [ @encrypted_recipients ], + Subject => $self->{'subject'}, + Type => 'multipart/mixed' ); + + $encrypted_message->attr( 'content-type' => 'multipart/encrypted' ); + $encrypted_message->attr( 'content-type.protocol' => 'application/pgp-encrypted' ); + + # Attach the control information to the message + + $encrypted_message->attach( Type => 'application/pgp-encrypted', + Encoding => '7bit', + Data => 'Version 1' ); + + # Attach the encrypted data + + $encrypted_message->attach( Type => 'application/octet-stream', + Encoding => '7bit', + Data => $encrypted ); + + $encrypted_message->send(); + } +} + + +#------------------------------------------------------------------------------ +# add_section( name ) +# +# Starts a new section in the message. Nothing is actually added to the +# message until the contents of an item are added. This allows empty sections +# to be omitted. +#------------------------------------------------------------------------------ + +sub add_section( $$ ) +{ + my ($self, $name) = @_; + + if ($self->{format} eq 'html') + { + # Terminate old items/subsections/sections + + $self->{message} .= "\n" if ($self->{in_item}); + $self->{message} .= "\n" if ($self->{in_subsection}); + $self->{message} .= "\n" if ($self->{in_section}); + + # Start new section + + $self->{section} = "

$name

\n"; + } + else + { + # Start new section + + $self->{section} = "\n$name\n"; + $self->{section} .= '-' x length($name); + $self->{section} .= "\n"; + } + + $self->{subsection} = ''; + $self->{item} = ''; + + $self->{in_section} = 0; + $self->{in_subsection} = 0; + $self->{in_item} = 0 +} + + +#------------------------------------------------------------------------------ +# sub add_subsection( name ) +# +# Starts a new subsection in the message. Nothing is actually added to the +# message until the contents of an item are added. This allows empty +# subsections to be omitted. +#------------------------------------------------------------------------------ + +sub add_subsection( $$ ) +{ + my ($self, $name) = @_; + + if ($self->{format} eq 'html') + { + # Terminate old items/subsections + + $self->{message} .= "
\n" if ($self->{in_item}); + $self->{message} .= "\n" if ($self->{in_subsection}); + + # Start new subsection + + $self->{subsection} = "

$name

\n"; + } + else + { + # Start new subsection + + $self->{subsection} = "\n $name\n"; + } + + $self->{item} = ''; + + $self->{in_subsection} = 0; + $self->{in_item} = 0 +} + + +#------------------------------------------------------------------------------ +# sub title( name ) +# +# Adds a new item title to the message. Nothing is actually added to the +# message until the contents of an item are added. This allows empty items to +# be omitted. +#------------------------------------------------------------------------------ + +sub add_title( $$ ) +{ + my ($self, $string) = @_; + + if ($self->{format} eq 'html') + { + # Terminate old item + + $self->{message} .= "
\n" if ($self->{in_item}); + + # Start new item + + $self->{item} = "

$string

\n"; + } + else + { + # Start new item + + $self->{item} = "\n $string\n\n"; + } + + $self->{in_item} = 0; +} + + +#------------------------------------------------------------------------------ +# sub add( lines ) +# +# Adds an item (or part of an item) to the message. If there are section, +# subsection or item titles outstanding, they are added, and then the contents +# of the parameter array is added. No formatting is carried out. This +# function should generally only be used by a plugin to add pre-formatted +# HTML to the message. +#------------------------------------------------------------------------------ + +sub add( $@ ) +{ + my ($self, @lines) = @_; + + # Add section/subsection/item titles, if they haven't already been added. + + if ($self->{section}) + { + $self->{message} .= $self->{section}; + $self->{section} = ''; + $self->{in_section} = 1; + $self->{in_subsection} = 0; + $self->{in_item} = 0; + } + + if ($self->{subsection}) + { + $self->{message} .= $self->{subsection}; + $self->{subsection} = ''; + $self->{in_subsection} = 1; + $self->{in_item} = 0; + } + + if ($self->{item}) + { + $self->{message} .= $self->{item}; + $self->{item} = ''; + $self->{in_item} = 1; + } + + # Add the lines + + foreach my $line (@lines) + { + $self->{message} .= $line; + } + + $self->{empty} = 0; +} + + +#------------------------------------------------------------------------------ +# sub add_text( lines ) +# +# Adds the textual lines to the message. If there are section, subsection or +# item titles outstanding, they are added, and then the contents of the +# parameter array is added. The lines are formatted as plain text with HTML +# breaks inserted if necessary. +#------------------------------------------------------------------------------ + +sub add_text( $@ ) +{ + my ($self, @lines) = @_; + + my $message = ''; + + foreach my $string (@lines) + { + if ($self->{format} eq 'html') + { + $string =~ s/[\n\r]+/
\n/g; + $message .= $string; + } + else + { + foreach my $string ( split /[\n\r]+/, $string ) + { + $message .= ' ' . $string . "\n"; + } + } + } + + $self->add( $message ); + + $self->{empty} = 0; +} + + +#------------------------------------------------------------------------------ +# sub add_image( params ) +# +# Adds an image to the message. +#------------------------------------------------------------------------------ + +sub add_image( $@ ) +{ + my ($self, %params) = @_; + + # Add section/subsection/item titles, if they haven't already been added. + + if ($self->{section}) + { + $self->{message} .= $self->{section}; + $self->{section} = ''; + $self->{in_section} = 1; + $self->{in_subsection} = 0; + $self->{in_item} = 0; + } + + if ($self->{subsection}) + { + $self->{message} .= $self->{subsection}; + $self->{subsection} = ''; + $self->{in_subsection} = 1; + $self->{in_item} = 0; + } + + if ($self->{item}) + { + $self->{message} .= $self->{item}; + $self->{item} = ''; + $self->{in_item} = 1; + } + + # Work out the name of the embedded file + + $self->{'image_file'}++; + + my $image_name = $self->{'image_file'}; + + if ($params{'type'} eq 'image/jpeg') + { + $image_name .= '.jpg'; + } + elsif ($params{'type'} eq 'image/gif') + { + $image_name .= '.gif'; + } + elsif ($params{'type'} eq 'image/png') + { + $image_name .= '.png'; + } + else + { + return; + } + + # Get the image + + my $data; + + if (exists $params{fh}) + { + my $buffer; + binmode $params{fh}; + + while (read $params{fh}, $buffer, 1024) + { + $data .= $buffer; + } + } + elsif (exists $params{data}) + { + $data = $params{data}; + } + + # Embed the file + + my $name = $params{'name'} || $image_name; + + $self->{object}->attach( Type => $params{'type'}, + Data => $data, + Id => $image_name ); + + $self->{message} .= "{message} .= " alt='$params{alt}'" if (exists $params{alt}); + $self->{message} .= ">\n"; + + $self->{empty} = 0; +} + +#------------------------------------------------------------------------------ +# sub add_table( contents ) +# +# Adds a table to the message. If there are section, subsection or item titles +# outstanding, they are added, and then the contents of the parameter array are +# added. +# +# The parameters passed should be an array of references to arrays. Each entry +# in the parameter array is a reference to an array representing a row of the +# table. The first row is taken to be the header. +# +# The entire table is scanned to work out the size and alignment of each column +# and then the table is formatted and added to the message. Columns are right +# aligned if they only contain numeric data (which may be suffixed to indicate +# units), otherwise columns are left aligned. The header row is centered. +# +# The alignment algorithm may be overridden by inserting a row consisting only +# of entries containing the following characters: '<', '|', '#' and '>', for +# left centre, numeric and right alignment. This line will override the +# alignment for subsequent columns. This also works for the header row, by +# inserting the alignment information before the header row; in this case the +# entire body of the table must also be manually aligned. +#------------------------------------------------------------------------------ + +sub add_table( $@ ) +{ + my ($self, @lines) = @_; + + if ($self->{format} eq 'html') + { + _add_table_html( $self, @lines ); + } + else + { + _add_table_text( $self, @lines ); + } + + $self->{empty} = 0; +} + + +#------------------------------------------------------------------------------ +# _add_table_html( contents ) +# +# Internal subroutine called by add_table to format a table in HTML. +#------------------------------------------------------------------------------ + +sub _add_table_html( $@ ) +{ + my ($self, @lines) = @_; + my @align; + my $header_row = 1; + my $text = ''; + my $number_lines = 0; + my $centre_header = 1; + + # Scan through the table entries to work out the alignment of each column. + # Note that the header row is always centre aligned, unless the alignment of + # entire table is set manually. + + foreach my $line ( @lines ) + { + my @fields = @{ $line }; + + unless ($align[0]) + { + for (my $column = 0 ; $column < @fields ; $column++) + { + # Initialise to right aligned. + + $align[$column] = '>'; + } + } + + # Check for manually defined alignment. + + last if ((join '', @fields) !~ m/[^<>|#]/); + + for (my $column = 0 ; $column < @fields ; $column++) + { + # Handle multiple lines in a single table cell. + + foreach my $item (split /[\n\r]+/, $fields[$column]) + { + if (not $header_row and $align[$column] ne '<') + { + if ($align[$column] eq '>' and ($fields[$column] =~ m/^\d*\.\d*(?:\s*[-\w\/%]+)?$/)) + { + # Decimal number - number align. + # Note that a number may be followed by a suffix (e.g. unit). + + $align[$column] = '#'; + } + elsif ($align[$column] ne '<' and $fields[$column] !~ m/^(?:\d*\.)?\d+(?:\s*[-\w\/%]+)?$/) + { + # Alphanumeric - left align. + + $align[$column] = '<'; + } + } + } + } + + $header_row = 0; + } + + # Now scan the table again, outputting the information. + + $header_row = 1; + + $text = "\n"; + + foreach my $line ( @lines ) + { + my @fields = @{ $line }; + + $text .= ""; + + if ((join '', @fields) !~ m/[^<>|#]/) + { + # Explicit alignment. + + for (my $column = 0 ; $column < @fields ; $column++) + { + $align[$column] = $fields[$column] if ($fields[$column]); + } + + # Override the centre justification of the header. + + $centre_header = 0; + + next; + } + + for (my $column = 0 ; $column < @fields ; $column++) + { + my $item = $fields[$column]; + my $tag = 'td'; + my $align = ' style="text-align: right"'; + + $item =~ s/[\n\r]+/
/g; + $align = ' style="text-align: center"' if ($align[$column] eq '|'); + $align = '' if ($align[$column] eq '<'); + + if ($header_row) + { + $tag = 'th'; + $align = '' if ($centre_header); + } + + $text .= "<$tag$align>$item"; + } + + $text .= "\n"; + $header_row = 0; + last if (++$number_lines > $self->{'max_lines_per_item'}); + } + + $text .= "
\n"; + + $self->add( $text ); + $self->{empty} = 0; +} + +#------------------------------------------------------------------------------ +# sub _add_table_text( contents ) +# +# Internal subroutine called by add_table to format a table in text. +#------------------------------------------------------------------------------ + +sub _add_table_text( $@ ) +{ + my ($self, @lines) = @_; + my @width; + my @align; + my $header_row = 1; + my $ignore_align = 0; + my $text = ''; + my $number_lines = 0; + my $centre_header = 1; + + # Scan through the table entries to work out the width and alignment of each + # column. + # Note that the header row is always centre aligned, unless the alignment of + # entire table is set manually. + + foreach my $line ( @lines ) + { + my @fields = @{ $line }; + + unless ($align[0]) + { + for (my $column = 0 ; $column < @fields ; $column++) + { + # Initialise to zero width and right aligned. + + $width[$column] = 0; + $align[$column] = '>'; + } + } + + # Check for manually defined alignment. + + if ((join '', @fields) !~ m/[^<>|#]/) + { + $ignore_align = 1; + next; + } + + for (my $column = 0 ; $column < @fields ; $column++) + { + next unless ($fields[$column]); + + # Handle multiple lines in a single table cell. + + foreach my $item (split /[\n\r]+/, $fields[$column]) + { + next unless ($item); + + if (length $item > $width[$column]) + { + $width[$column] = length $item; + } + + if (not $header_row and $align[$column] ne '<' and not $ignore_align) + { + if ($align[$column] eq '>' and ($item =~ m/^\d*\.\d*(?:\s*[-\w\/%]+)?$/)) + { + # Decimal number - number align. + # Note that a number may be followed by a suffix (e.g. unit). + + $align[$column] = '#'; + } + elsif ($align[$column] ne '<' and $item !~ m/^(?:\d*\.)?\d+(?:\s*[-\w\/%]+)?$/) + { + # Alphanumeric - left align. + + $align[$column] = '<'; + } + } + } + } + + last if (++$number_lines > $self->{'max_lines_per_item'}); + $header_row = 0; + } + + # Now scan the table again, outputting the information. + + $header_row = 1; + $number_lines = 0; + + foreach my $line ( @lines ) + { + my @fields = @{ $line }; + my @next_fields = (); + my $more_lines = 0; + + if ((join '', @fields) !~ m/[^<>|#]/) + { + # Explicit alignment. + + for (my $column = 0 ; $column < @fields ; $column++) + { + $align[$column] = $fields[$column] if ($fields[$column]); + } + + # Override the centre justification of the header. + + $centre_header = 0; + + next; + } + + $text .= ' '; + + for (my $column = 0 ; $column < @fields ; $column++) + { + my $item = ''; + $next_fields[$column] = ''; + + # Try to split the cell into multiple lines. + + ($item, $next_fields[$column]) = split /[\n\r]+/, $fields[$column], 2; + + if ($next_fields[$column]) + { + $more_lines = 1 ; + } + else + { + $next_fields[$column] = ' '; + } + + $item = '' unless ($item); + + my $width = length $item; + + if ($centre_header or $align[$column] eq '|') + { + # Centre justified pre-spacing. + $text .= ' ' x (($width[$column] - $width) / 2 ); + } + elsif ($align[$column] eq '>' or $align[$column] eq '#') + { + # Right justified pre-spacing. + $text .= ' ' x ($width[$column] - $width); + } + + $text .= $item; + + if ($column != $#fields) + { + if ($centre_header or $align[$column] eq '|') + { + # Centre justified post spacing. + $text .= ' ' x (($width[$column] - $width + 1) / 2 ); + } + elsif ($align[$column] eq '<') + { + # Left justified post spacing. + $text .= ' ' x ($width[$column] - $width); + } + + # Inter column spacing + $text .= " "; + } + } + + $text =~ s/\s+$//; + $text .= "\n"; + $header_row = 0; + $centre_header = 0; + + if ($more_lines) + { + # One or more of the cells in this row has multiple lines outstanding. + # Go back to output the next line. + $line = [ @next_fields ]; + redo; + } + + last if (++$number_lines > $self->{'max_lines_per_item'}); + } + + $self->add( $text ); + $self->{empty} = 0; +} + + +1; From patchwork Sat Apr 6 04:29:31 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tim FitzGeorge X-Patchwork-Id: 2174 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 A634A861F37 for ; Fri, 5 Apr 2019 18:30:24 +0100 (BST) Received: from mail01.i.ipfire.org (localhost [IPv6:::1]) by mail01.ipfire.org (Postfix) with ESMTP id 44bRfS0bm5z5Lcxp; Fri, 5 Apr 2019 18:30:24 +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 44bRfC2B9dz5K3Kw for ; Fri, 5 Apr 2019 18:30:11 +0100 (BST) Received: from [31.127.205.161] (helo=aragorn.tfitzgeorge.me.uk) by smtp.hosts.co.uk with esmtpa (Exim) (envelope-from ) id 1hCSfH-0008Ml-4c; Fri, 05 Apr 2019 18:30:11 +0100 From: Tim FitzGeorge To: development@lists.ipfire.org Subject: [PATCH 03/12] statusmail: WUI Date: Fri, 5 Apr 2019 18:29:31 +0100 Message-Id: <20190405172940.13168-4-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 [-10.17 / 11.00]; ARC_NA(0.00)[]; RCVD_VIA_SMTP_AUTH(0.00)[]; RECEIVED_SPAMHAUS_PBL(0.00)[161.205.127.31.zen.spamhaus.org : 127.0.0.11]; 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(-0.00)[35.32%]; FROM_EQ_ENVFROM(0.00)[] 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" CGI script for statusmail WUI and icon for 'Execute now'. Signed-off-by: Tim FitzGeorge --- html/cgi-bin/statusmail.cgi | 1687 +++++++++++++++++++++++++++++++++++++++++++ html/html/images/play.png | Bin 0 -> 182 bytes 2 files changed, 1687 insertions(+) create mode 100755 html/cgi-bin/statusmail.cgi create mode 100644 html/html/images/play.png diff --git a/html/html/images/play.png b/html/html/images/play.png new file mode 100644 index 0000000000000000000000000000000000000000..d5ecf1236d92c50c912353239f6e5826b6328cb5 GIT binary patch literal 182 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc3?z4jzqJQa5&=FTuD%_g|NsAg;J|_Q_V%D4 zkh*Q#wt>{>{9Xp6I7)*2g8%<#0IK=_U)=Sl1W?S?)5S4F;&Sf=Pc8-p9%cv8S%3Z? z?+RM7&8cXP^Ykr&OKK;G9qPU2d`3`o(uxm74$s;7)m1`Uws2hf_|rG6bxq|?tBpT7 Y??. # +# # +# Copyright (C) 2019 # +# # +############################################################################### + +# Enable the following only for debugging +use strict; +use warnings; +use CGI qw/:standard/; +use CGI::Carp 'fatalsToBrowser'; + +use IPC::Open3; +use Data::Dumper; + +require '/var/ipfire/general-functions.pl'; +require "${General::swroot}/lang.pl"; +require "${General::swroot}/header.pl"; + +############################################################################### +# Function prototypes +############################################################################### + +# Used by plugins + +sub add_mail_item( % ); +sub get_period_start(); +sub get_period_end(); +sub get_weeks_covered(); +sub cache( $;$ ); + +# Local functions + +sub show_encryption_keys(); +sub show_contacts(); +sub show_schedules(); +sub show_signing_key(); +sub check_key( $ ); +sub get_keys(); +sub check_schedule( % ); +sub toggle_on_off( $ ); +sub export_signing_key(); + +############################################################################### +# Configuration variables +############################################################################### + +my $contactsettings = "${General::swroot}/statusmail/contact_settings"; +my $schedulesettings = "${General::swroot}/statusmail/schedule_settings"; +my $generate_signature = "/usr/lib/statusmail/generate_signature.sh"; +my $mailsettings = "${General::swroot}/dma/mail.conf"; +my $mainsettings = "${General::swroot}/main/settings"; +my $plugin_dir = '/usr/lib/statusmail/plugins'; +my $gpg = "/usr/bin/gpg --homedir ${General::swroot}/statusmail/keys"; +my $execute = '/usr/local/bin/statusmail.pl'; +my $tmpdir = '/var/tmp'; +my $statusmailctrl = '/usr/local/bin/statusmailctrl'; + +############################################################################### +# Initialize variables and hashes +############################################################################### + +my %mainsettings = (); +my %cgiparams = (); +my $errormessage = ''; +my $current_contact = ''; +my $current_key = ''; +my $current_schedule = ''; +my $save_contacts = 0; +my $save_schedules = 0; +my %items; +my %colour; +my $contacts; +my $schedules; +my %keys; +my %mailsettings; +my $sign_key; +my $signing_fingerprint = ''; +my $signing_keyid = ''; +my $encryption_key = ''; +my $show_signing_key = 0; +my $show_encryption_keys = 0; +my $show_contacts = 0; +my @debug; + +############################################################################### +# Main code +############################################################################### + +# Read CGI parameters +my $a = new CGI; + +Header::getcgihash( \%cgiparams ); + +# Read settings + +General::readhash( $mainsettings, \%mainsettings ); +General::readhash( $mailsettings, \%mailsettings ) if (-e $mailsettings); +General::readhash( "/srv/web/ipfire/html/themes/" . $mainsettings{'THEME'} . "/include/colors.txt", \%colour ); + +if (-r $contactsettings) +{ + eval qx|/bin/cat $contactsettings|; +} +else +{ + # No settings file - set up defaults + + $contacts = {}; +} + +if (-r $schedulesettings) +{ + eval qx|/bin/cat $schedulesettings|; +} +else +{ + # No settings file - set up defaults + + $schedules = {}; +} + +# Get key information + +get_keys(); + +# Get the email signing key + +$sign_key = `$gpg --armour --export IPFire`; + +$sign_key =~ s/^\s+//; +$sign_key =~ s/\s+$//; + +$errormessage .= "

$Lang::tr{'statusmail no signing key'}

" if ($sign_key =~ m/nothing exported/ or not $sign_key); + +# Scan for plugins + +opendir DIR, $plugin_dir or die "Can't open Plug-in directory $plugin_dir: $!"; + +foreach my $file (readdir DIR) +{ + next unless ($file =~ m/\.pm$/); + + require "$plugin_dir/$file"; +} + +############################################################################### +# ACTIONS +############################################################################### + +# ACTIONS for Installed PGP Keys + +$show_signing_key = $cgiparams{'show signing key'} || 0; +$show_encryption_keys = $cgiparams{'show encryption keys'} || 0; +$show_contacts = $cgiparams{'show contacts'} || 0; + +$show_signing_key = 1 if (exists $cgiparams{'SIGN_ACTION'}); +$show_encryption_keys = 1 if (exists $cgiparams{'KEY_ACTION'}); +$show_contacts = 1 if (exists $cgiparams{'CONTACT_ACTION'}); +$show_signing_key = 1 if ($sign_key =~ m/nothing exported/ or not $sign_key); + +if ($cgiparams{'KEY_ACTION'} eq $Lang::tr{'statusmail import'}) +{ + my $upload = $a->param("UPLOAD"); + $encryption_key = ''; + + binmode $upload; + + foreach my $line ( <$upload> ) + { + $encryption_key .= $line; + } + + check_key( $encryption_key ); + get_keys(); + + $show_contacts = 1; +} +elsif ($cgiparams{'KEY_ACTION'} eq $Lang::tr{'add'}) +{ + check_key( $cgiparams{'key'} ); + + get_keys(); +} +elsif ($cgiparams{'KEY_ACTION'} eq 'remove key') +{ + my $key = $cgiparams{'KEY'}; + $key =~ s/\s+//g; + + my @output = `$gpg --batch --yes --delete-key $key 2>&1`; + + if ($?) + { + $errormessage .= join '
', "

$Lang::tr{'statusmail key remove failed'} $?", @output; + $errormessage .= "

\n"; + } + + get_keys(); +} +elsif ($cgiparams{'KEY_ACTION'} eq $Lang::tr{'statusmail show'}) +{ + $show_encryption_keys = 1; +} +elsif ($cgiparams{'KEY_ACTION'} eq $Lang::tr{'statusmail hide'}) +{ + $show_encryption_keys = 0; +} + +# ACTIONS for Signing Certificate + +elsif ($cgiparams{'SIGN_ACTION'} eq $Lang::tr{'statusmail generate'}) +{ + system( "$generate_signature &>$tmpdir/statusmail_log &" ); +} +elsif ($cgiparams{'SIGN_ACTION'} eq $Lang::tr{'export'}) +{ + export_signing_key(); +} +elsif ($cgiparams{'SIGN_ACTION'} eq $Lang::tr{'statusmail show'}) +{ + $show_signing_key = 1; +} +elsif ($cgiparams{'SIGN_ACTION'} eq $Lang::tr{'statusmail hide'}) +{ + $show_signing_key = 0; +} + +# ACTIONS for Contacts + +elsif ($cgiparams{'CONTACT_ACTION'} eq $Lang::tr{'add'} or $cgiparams{'CONTACT_ACTION'} eq $Lang::tr{'update'}) +{ + if (not $cgiparams{'name'}) + { + $errormessage .= "

$Lang::tr{'statusmail no contact name'}

"; + } + + if (not General::validemail( $cgiparams{'address'} )) + { + $errormessage .= "

$Lang::tr{'statusmail email invalid'}

"; + } + + if (not $errormessage) + { + my $enable = $$contacts{$cgiparams{'name'}}{'enable'}; + + $$contacts{$cgiparams{'name'}} = { 'email' => $cgiparams{'address'}, + 'keyid' => '', + 'fingerprint' => '', + 'enable' => $enable }; + $save_contacts = 1; + } +} +elsif ($cgiparams{'CONTACT_ACTION'} eq 'edit contact') +{ + $current_contact = $cgiparams{'KEY'}; +} +elsif ($cgiparams{'CONTACT_ACTION'} eq 'remove contact') +{ + my $key = $cgiparams{'KEY'}; + + delete $$contacts{$key}; + $save_contacts = 1; +} +elsif ($cgiparams{'CONTACT_ACTION'} eq 'toggle contact') +{ + my $key = $cgiparams{'KEY'}; + + toggle_on_off( $$contacts{$key}{'enable'} ); + $save_contacts = 1; +} +elsif ($cgiparams{'CONTACT_ACTION'} eq $Lang::tr{'statusmail show'}) +{ + $show_contacts = 1; +} +elsif ($cgiparams{'CONTACT_ACTION'} eq $Lang::tr{'statusmail hide'}) +{ + $show_contacts = 0; +} + +# ACTIONS for Schedules + +elsif ($cgiparams{'SCHEDULE_ACTION'} eq $Lang::tr{'add'} or $cgiparams{'SCHEDULE_ACTION'} eq $Lang::tr{'update'}) +{ + check_schedule( %cgiparams ); +} +elsif ($cgiparams{'SCHEDULE_ACTION'} eq 'edit schedule') +{ + $current_schedule = $cgiparams{'KEY'}; +} +elsif ($cgiparams{'SCHEDULE_ACTION'} eq 'execute schedule') +{ + system( "$execute '$cgiparams{'KEY'}' &" ); +} +elsif ($cgiparams{'SCHEDULE_ACTION'} eq 'remove schedule') +{ + my $key = $cgiparams{'KEY'}; + + delete $$schedules{$key}; + $save_schedules = 1; +} +elsif ($cgiparams{'SCHEDULE_ACTION'} eq 'toggle schedule') +{ + my $key = $cgiparams{'KEY'}; + + toggle_on_off( $$schedules{$key}{'enable'} ); + + # Check to see if the service needs to be enabled or disabled + + if ($$schedules{$key}{'enable'} eq 'on' and not -e '/etc/fcron.hourly/statusmail') + { + system( "$statusmailctrl enable >/dev/null" ); + } + elsif (-e '/etc/fcron.hourly/statusmail') + { + my $do_disable = 1; + + foreach my $name (keys %$schedules ) + { + if ($$schedules{$name}{'enable'} eq 'on') + { + $do_disable = 0; + last; + } + } + + if ($do_disable) + { + system( "$statusmailctrl disable >/dev/null" ); + } + } + + $save_schedules = 1; +} + +############################################################################### +# Start of HTTP/HTML output +############################################################################### + +# Show Headers +Header::showhttpheaders(); + +############################################################################### +# Page contents +############################################################################### + +Header::openpage($Lang::tr{'statusmail status emails'}, 1, ''); + +# Display error if email is not enabled + +if ($mailsettings{'USEMAIL'} ne 'on') +{ + $errormessage = "

$Lang::tr{'statusmail email not enabled'}

"; +} + +if ($errormessage) +{ + Header::openbox( '100%', 'left', $Lang::tr{'error messages'} ); + print "$errormessage\n"; + Header::closebox(); +} + +# Check for key generation in progress + +my $return = `pidof generate_signature.sh -x`; + +chomp($return); + +if ($return) +{ + Header::openbox( 'Working', 1, "$Lang::tr{'statusmail working'}" ); + + print < + + + $Lang::tr{  + + + + +
+ +
+ + +
+END
+;
+  my @output = `tail -20 $tmpdir/statusmail_log`;
+  foreach (@output)
+  {
+    print "$_";
+  }
+  print <
+    
+END
+;
+
+  unlink "$tmpdir/statusmail_log";
+
+  Header::closebox();
+  Header::closebigbox();
+  Header::closepage();
+  exit;
+}
+
+# Show main bulk of page
+
+if ($mailsettings{'USEMAIL'} eq 'on')
+{
+  show_signing_key();
+
+  unless ($sign_key =~ m/nothing exported/ or not $sign_key)
+  {
+    show_encryption_keys();
+    show_contacts();
+    show_schedules();
+  }
+}
+
+foreach my $line (@debug)
+{
+  print "$line
\n"; +} + +# End of page + +Header::closebigbox(); +Header::closepage(); + +# Save settings if necessary + +if ($save_contacts) +{ + open OUT, '>', $contactsettings or die "Can't open contact settings file $contactsettings: $!"; + print OUT Data::Dumper->Dump( [$contacts], ['contacts'] ); + close OUT; +} + +if ($save_schedules) +{ + open OUT, '>', $schedulesettings or die "Can't open schedule settings file $schedulesettings: $!"; + print OUT Data::Dumper->Dump( [$schedules], ['schedules'] ); + close OUT; +} + +############################################################################### +# Subroutines +############################################################################### + +#------------------------------------------------------------------------------ +# sub show_signing_key() +# +# Outputs the 'Signing Key' section of the page. +# +# A new signing key can be generated and exported. +#------------------------------------------------------------------------------ + +sub show_signing_key() +{ + Header::openbox('100%', 'left', $Lang::tr{'statusmail signing key'}); + + # Javascript to copy key to clipboard + + print < + function copy_clipboard() { + /* Get the text field */ + var copyText = document.getElementById( "key_out" ); + + /* Select the text field */ + copyText.select(); + + /* Copy the text inside the text field */ + document.execCommand( "copy" ); + + /* Alert the copied text */ + alert( "$Lang::tr{'statusmail copied to clipboard'}" ); + } + +END +; + + # Hide/Show button + + my $button = $show_signing_key ? $Lang::tr{"statusmail hide"} : $Lang::tr{"statusmail show"}; + + print < + + + + +
+ + + + +
+ +END +; + + # Key information and export/generate buttons + + if ($show_signing_key) + { + my $disabled = ''; + $disabled = ' disabled' if ($sign_key =~ m/nothing exported/ or not $sign_key); + + print < + + + + + + + + + + + + + +
$Lang::tr{'statusmail signing key'} + +
$Lang::tr{'statusmail fingerprint'}$signing_fingerprint
$Lang::tr{'statusmail keyid'}$signing_keyid
+

+
+ + + + +
+ + + +
+
+END +; + } + + Header::closebox(); +} + +#------------------------------------------------------------------------------ +# sub show_encryption_keys() +# +# Outputs the 'Installed PGP Keys' section of the page. +# +# User keys can be imported or deleted. +#------------------------------------------------------------------------------ + +sub show_encryption_keys() +{ + Header::openbox('100%', 'left', $Lang::tr{'statusmail keys'}); + + # Hide/show button + + my $button = $show_encryption_keys ? $Lang::tr{"statusmail hide"} : $Lang::tr{"statusmail show"}; + + print < + + + + +
+ + + + +
+ +END +; + + if ($show_encryption_keys) + { + # Selected key details and Import/Add buttons + + print < + function enable_file_import() { + /* Get the text field */ + var importButton = document.getElementById( "file-import" ); + + /* Select the text field */ + importButton.removeAttribute( 'disabled' ); + } + +
+ + + + + +
$Lang::tr{'statusmail key'} + +
+

+ + + + +
+ + + + +
+
+
+ + + + + + + + + +END +; + + # List installed keys + + my $row = 0; + foreach my $fingerprint (sort keys %keys) + { + my $show_fingerprint = $fingerprint; + $show_fingerprint =~ s/((?:\w{4}\s){4}(?:\w{4}))\s(.+)/$1
$2/; + + if ($row % 2) + { + print ""; + } + else + { + print ""; + } + + my $name = Header::escape( $keys{$fingerprint}{'userid'} ); + my $email = Header::escape( $keys{$fingerprint}{'email'} ); + + print <$name + + + + + + +END +; + $row++; + } + + print < +END +; + } + Header::closebox(); +} + + +#------------------------------------------------------------------------------ +# sub show_contacts() +# +# Outputs the 'Contacts' part of the page. +# +# New contacts can be added and existing ones enabled/disabled or deleted. +#------------------------------------------------------------------------------ + +sub show_contacts() +{ + my $button = $Lang::tr{'add'}; + my $current_address = ''; + my $name = ''; + my $keyid = ''; + my $enable = 0; + + Header::openbox('100%', 'left', $Lang::tr{'statusmail contacts'}); + + # Hide/Show button + + my $button = $show_contacts ? $Lang::tr{"statusmail hide"} : $Lang::tr{"statusmail show"}; + + print < +
$Lang::tr{'statusmail contact name'}$Lang::tr{'statusmail email'}$Lang::tr{'statusmail keyid'}$Lang::tr{'statusmail fingerprint'}$Lang::tr{'statusmail key expires'}$Lang::tr{'statusmail action'}
$email$keys{$fingerprint}{'keyid'}$show_fingerprint$keys{$fingerprint}{'expires'} +
+ + + +
+
+ + + +
+ + + + +
+ +END +; + + if ($show_contacts) + { + # Selected contact details and Import/Add buttons + + if ($current_contact) + { + $button = $Lang::tr{'update'}; + + if (exists $$contacts{$current_contact}) + { + $name = Header::escape( $current_contact ); + $current_address = Header::escape( $$contacts{$current_contact}{'email'} ); + $keyid = $$contacts{$current_contact}{'keyid'}; + } + } + + print < + + + + + + + + + + + +
$Lang::tr{'statusmail contact name'} + + $Lang::tr{'statusmail email'} + +
$Lang::tr{'statusmail keyid'} +
+

+ + + + +
+ +
+ + + + + + + +END +; + + # List contacts + + my $row = 0; + foreach my $contact (sort keys %$contacts) + { + my $col = ''; + my $gif; + my $gdesc; + + if ($current_contact eq $contact) + { + print ""; + } + elsif ($row % 2) + { + print ""; + } + else + { + print ""; + } + + if ($$contacts{$contact}{'enable'} eq 'on') + { + $gif = 'on.gif'; + $gdesc = $Lang::tr{'click to disable'}; + } + else + { + $gif = 'off.gif'; + $gdesc = $Lang::tr{'click to enable'}; + } + + $name = Header::escape( $contact ); + my $address = Header::escape( $$contacts{$contact}{'email'} ); + + print <$name + + + + + + + + + +END +; + $row++; + } + + print < +END +; + } + + Header::closebox(); +} + + +#------------------------------------------------------------------------------ +# sub show_schedules() +# +# Outputs the 'Schedules' part of the page. +#------------------------------------------------------------------------------ + +sub show_schedules() +{ + my $button = $Lang::tr{'add'}; + my $enable = 0; + my %schedule = ( 'subject' => '', + 'email' => '', + 'format' => 'HTML', + 'mday' => 0, + 'wday' => 0, + 'hours' => 0, + 'enable' => 0 ); + + Header::openbox('100%', 'left', $Lang::tr{'statusmail schedules'}); + + if ($current_schedule) + { + $button = $Lang::tr{'update'}; + + foreach my $field ( keys %{ $$schedules{$current_schedule} } ) + { + $schedule{$field} = $$schedules{$current_schedule}{$field}; + } + } + + my $name = Header::escape( $current_schedule ); + my $subject = Header::escape( $schedule{'subject'} ); + + # Selected schedule - email information + + print < +
$Lang::tr{'statusmail contact name'}$Lang::tr{'statusmail email'}$Lang::tr{'statusmail key'}$Lang::tr{'statusmail action'}
$address$$contacts{$contact}{'keyid'} +
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + +
$Lang::tr{'statusmail schedule name'} + +
$Lang::tr{'statusmail email subject'} + +
$Lang::tr{'statusmail email to'} + $Lang::tr{'statusmail email format'} + +
$Lang::tr{'statusmail period covered'}: + + + $Lang::tr{'statusmail lines per item'} + +
+
+ + + + + +END +; + + # Selected schedule - frequency information + + foreach my $day (1..31) + { + print "\n"; + } + + print "\n"; + + foreach my $day (1..31) + { + my $checked = ($schedule{'mday'} & (1 << $day)) ? ' checked' : ''; + print "\n"; + } + + print < + + + + + +END +; + + foreach my $day ('sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday') + { + my $wday = "statusmail $day"; + print "\n"; + } + + print "\n"; + + foreach my $day (0..6) + { + my $checked = ($schedule{'wday'} & (1 << $day)) ? ' checked' : ''; + print "\n"; + } + + print < + + + + +END +; + + foreach my $hour (0..23) + { + print "\n"; + } + + print "\n"; + + foreach my $hour (0..23) + { + my $checked = ($schedule{'hours'} & (1 << $hour)) ? ' checked' : ''; + print "\n"; + } + + # Javascript to show/hide HTML only items + + print < +
$Lang::tr{'statusmail day of month'}:
$day
 
$Lang::tr{'statusmail day of week'}:
$Lang::tr{$wday}
 
$Lang::tr{'statusmail hour of day'}:
$hour
+

+ + + + +END +; + + # List items + + foreach my $section (sort keys %items) + { + print "\n"; + + foreach my $subsection (sort keys %{ $items{$section} } ) + { + print "\n"; + + foreach my $item (sort keys %{ $items{$section}{$subsection} } ) + { + my $name = $items{$section}{$subsection}{$item}{'ident'}; + my $class = $items{$section}{$subsection}{$item}{'format'}; + my $hidden = ''; + my $checked = ''; + + if (($class eq 'html' and $schedule{'format'} eq 'text') or + ($class eq 'text' and $schedule{'format'} eq 'html')) + { + $hidden = ' hidden'; + } + + $checked = ' checked' if ($schedule{"enable_${name}"} eq 'on'); + + print "\n"; + print "\n"; + + if (exists $items{$section}{$subsection}{$item}{'option'}) + { + my $key = "value_$name"; + + print "\n"; + + if ($items{$section}{$subsection}{$item}{'option'}{'type'} eq 'integer') + { + my $min = $items{$section}{$subsection}{$item}{'option'}{'min'}; + my $max = $items{$section}{$subsection}{$item}{'option'}{'max'}; + my $value = $min; + + $value = $schedule{"value_$name"} if (exists $schedule{"value_$name"}); + + print "\n"; + } + else + { + print "\n"; + } + } + else + { + print "\n"; + print "\n"; + } + print "\n"; + } + } + } + + # Add/Update button + + print < +
+

$section

$subsection

$item$items{$section}{$subsection}{$item}{'option'}{'name'}  
+ + + +
+ +
+ + + + + +END +; + + # List schedules + + my $row = 0; + foreach my $schedule (sort keys %$schedules) + { + my $col = ''; + my $gif; + my $gdesc; + $name = Header::escape( $schedule ); + + if ($current_contact eq $schedule) + { + print ""; + } + elsif ($row % 2) + { + print ""; + } + else + { + print ""; + } + + if ($$schedules{$schedule}{'enable'} eq 'on') + { + $gif = 'on.gif'; + $gdesc = $Lang::tr{'click to disable'}; + } + else + { + $gif = 'off.gif'; + $gdesc = $Lang::tr{'click to enable'}; + } + + print <$name + + + + + + + + + +END +; + $row++; + } + + print < +END +; + Header::closebox(); +} + + +#------------------------------------------------------------------------------ +# sub check_key( key ) +# +# Checks an imported PGP key to see if it looks correct and then tries +# to import it into the the keyring. +#------------------------------------------------------------------------------ + +sub check_key( $ ) +{ + my ($key) = @_; + + # Remove leading and trailing whitespace + + $key =~ s/^\s+//g; + $key =~ s/\s+$//g; + + # Check it looks like a key + + if ($key !~ m/^-----BEGIN PGP PUBLIC KEY BLOCK-----[A-Za-z\d \/=|\+\n\r-]+-----END PGP PUBLIC KEY BLOCK-----$/) + { + $errormessage .= "

$Lang::tr{'statusmail invalid key'}

"; + + return; + } + + # Looks OK - try to import it + + my ($in, $out, $err); + + my $childpid = open3( $in, $out, $err, "$gpg --import" ); + + print $in $key; + + close $in; + + waitpid( $childpid, 0); + + if ($?) + { + $errormessage .= join '
', "

$Lang::tr{'statusmail import key failed'}", <$out>, "

\n"; + } +} + + +#------------------------------------------------------------------------------ +# sub get_keys() +# +# Reads the PGP keyring and extracts information on suitable email encryption +# keys. If a key is found that corresponds to a user not in the contacts list +# the user is added to the contacts. If a key is found for a user in the list +# that does not have a referenced key, the key is added to the contact. +#------------------------------------------------------------------------------ + +sub get_keys() +{ + %keys = (); + + my @keys = `$gpg --fingerprint --fingerprint --with-colons`; + + my $keyid = ''; + my $userid = ''; + my $email = ''; + my $expires = 0; + my $fingerprint = ''; + my $use = ''; + + # Iterate through the list of keys + + foreach my $line (@keys) + { + my @fields = split /:/, $line; + + if ($fields[0] eq 'pub') + { + $keyid = ''; + $userid = ''; + $email = ''; + $expires = 0; + $fingerprint = ''; + $use = ''; + } + + # 0 1 2 3 4 5 6 7 8 9 10 + # type, validity, length, algorithm, keyid, creation date, expiry date, ??, trust, userid, sig class, + # 11 12 13 14 15 16 17 18 19 + # capabilities, issuer, flag, serial, hash algorithm, curve, compliance, updated, origin + + if (($fields[0] eq 'pub' or $fields[0] eq 'sub')) + { + # Key that can be used for encryption + + $userid = $fields[9] if ($fields[9]); + $expires = $fields[6]; + $keyid = $fields[4]; + $use = $fields[11]; + } + elsif ($fields[0] eq 'uid') + { + # User id + + $userid = $fields[9]; + } + elsif ($fields[0] eq 'fpr') + { + # Fingerprint + + $fingerprint = $fields[9]; + $fingerprint =~ s/\w{4}\K(?=.)/ /sg; # Adds a space after every fourth character + } + + if ($keyid and $userid and $expires and $fingerprint and $use =~ m/e/) + { + # We've got all the information for one key + + if ($userid =~ m/\@/) + { + ($userid, $email) = $userid =~ m/^(.*?)\s+\<(.*)\>/; + } + + $keys{$fingerprint} = { 'keyid' => $keyid, + 'userid' => $userid, + 'email' => $email, + 'expires' => $expires, + 'fingerprint' => $fingerprint }; + + if (exists $$contacts{$userid} and ($$contacts{$userid}{'email'} eq $email or not $$contacts{$userid}{'email'})) + { + # Update existing contact + + $$contacts{$userid}{'email'} = $email; + $$contacts{$userid}{'keyid'} = $keyid; + $$contacts{$userid}{'fingerprint'} = $fingerprint; + + $save_contacts = 1; + } + elsif (not exists $$contacts{$userid}) + { + # New contact + + $$contacts{$userid}{'email'} = $email; + $$contacts{$userid}{'keyid'} = $keyid; + $$contacts{$userid}{'fingerprint'} = $fingerprint; + $$contacts{$userid}{'enable'} = 0; + + $save_contacts = 1; + } + + $fingerprint = ''; + $keyid = ''; + } + elsif ($keyid and $userid =~ m/IPFire/ and $fingerprint and $use =~ m/s/) + { + # The signing key + + $signing_fingerprint = $fingerprint; + $signing_keyid = $keyid; + + $fingerprint = ''; + $keyid = ''; + } + } + + # Check for contacts which no longer have a key defined + + foreach my $contact (keys %$contacts) + { + if ($$contacts{$contact}{'fingerprint'} and not exists $keys{$$contacts{$contact}{'fingerprint'}}) + { + $$contacts{$contact}{'fingerprint'} = ''; + $$contacts{$contact}{'keyid'} = ''; + + $save_contacts = 1; + } + } +} + + +#------------------------------------------------------------------------------ +# sub check_schedule( params ) +# +# Checks a schedule is valid +#------------------------------------------------------------------------------ + +sub check_schedule( % ) +{ + my %params = @_; + + my $mdays = 0; + my $wdays = 0; + my $hours = 0; + my $enable = $$schedules{$params{'name'}}{'enable'}; + + # Check required fields are set + + $errormessage .= "

$Lang::tr{'statusmail no schedule name'}

" if (not $params{'name'}); + $errormessage .= "

$Lang::tr{'statusmail no email subject'}

" if (not $params{'subject'}); + $errormessage .= "

$Lang::tr{'statusmail no email addresses'}

" if (not $params{'emails'}); + $errormessage .= "

$Lang::tr{'statusmail no period covered'}

" if (not $params{'period-value'}); + $errormessage .= "

$Lang::tr{'statusmail no lines per item'}

" if (not $params{'lines'}); + + # Convert time/date buttons to bitmap + + foreach my $mday (1..31) + { + $mdays |= 1 << $mday if (exists $params{"mday_$mday"}); + } + + foreach my $wday (0..6) + { + $wdays |= 1 << $wday if (exists $params{"wday_$wday"}); + } + + foreach my $hour (0..24) + { + $hours |= 1 << $hour if (exists $params{"hour_$hour"}); + } + + # Check schedule is OK + + $errormessage .= "

$Lang::tr{'statusmail no schedule date'}" if (not ($mdays+$wdays)); + $errormessage .= "

$Lang::tr{'statusmail no schedule time'}" if (not $hours); + $errormessage .= "

$Lang::tr{'statusmail excessive period'}" if (($params{'period-unit'} eq 'hours' and $params{'period-value'} > (365 * 24)) or + ($params{'period-unit'} eq 'days' and $params{'period-value'} > 365) or + ($params{'period-unit'} eq 'weeks' and $params{'period-value'} > 52) or + ($params{'period-unit'} eq 'months' and $params{'period-value'} > 12)); + + $$schedules{$params{'name'}} = { 'subject' => $params{'subject'}, + 'email' => $params{'emails'}, + 'format' => $params{'format'}, + 'period-value' => $params{'period-value'}, + 'period-unit' => $params{'period-unit'}, + 'lines' => $params{'lines'}, + 'mday' => $mdays, + 'wday' => $wdays, + 'hours' => $hours, + 'enable' => $enable }; + + # Check individual items + + foreach my $section (sort keys %items) + { + foreach my $subsection (sort keys %{ $items{$section} } ) + { + foreach my $item (sort keys %{ $items{$section}{$subsection} } ) + { + my $name = $items{$section}{$subsection}{$item}{'ident'}; + my $format = $items{$section}{$subsection}{$item}{'format'}; + + if (($format eq 'html' and $params{'format'} eq 'text') or + ($format eq 'text' and $params{'format'} eq 'html')) + { + $$schedules{$params{'name'}}{"enable_${name}"} = 'off'; + } + else + { + my $state = 'off'; + + $state = 'on' if (exists($params{"enable_${name}"})); + $$schedules{$params{'name'}}{"enable_${name}"} = $state; + } + + if ($items{$section}{$subsection}{$item}{'option'}) + { + $$schedules{$params{'name'}}{"value_${name}"} = $params{"value_${name}"}; + } + } + } + } + + $save_schedules = 1 unless ($errormessage); +} + + +#------------------------------------------------------------------------------ +# sub add_mail_item( params ) +# +# Adds a possible status item to the section and subsection specified. This +# is called by plugins during the BEGIN phase. +# +# In the event of an error in specifying the parameters the item will be +# ignored with no error being raised. +# +# Parameters: +# params hash containing details of the item to be added: +# section name of the section containing this item +# subsection name of the subsection containing this item +# item name of the item +# function function called to add item to message +# format available formats for the item 'html', 'text' or 'both' +# option hash specifying option parameter (optional) +# +# option can specify either a selection or an integer. For a selection it +# contains: +# type must be 'option' +# values array of strings representing the possible options +# +# For an integer option contains: +# type must be 'integer' +# min minimum valid value of parameter +# max maximum valid value of parameter +# +# The function is sanity checked but otherwise ignored by this script. +#------------------------------------------------------------------------------ + +sub add_mail_item( %) +{ + my %params = @_; + + # Check we've got all the expected parameters + + return unless (exists $params{'section'} and + exists $params{'subsection'} and + exists $params{'item'} and + exists $params{'function'}); + + if ($params{'format'}) + { + $params{'format'} = lc $params{'format'}; + + return unless ($params{'format'} eq 'html' or + $params{'format'} eq 'text' or + $params{'format'} eq 'both'); + } + else + { + $params{'format'} = 'both'; + } + + $items{$params{'section'}}{$params{'subsection'}}{$params{'item'}} = { 'format' => $params{'format'}, + 'ident' => $params{'ident'} }; + + # Check the option parameter, if it exists + + if ($params{'option'}) + { + return unless (ref $params{'option'} eq 'HASH'); + return unless ($params{'option'}{'name'}); + + if ($params{'option'}{'type'} eq 'select') + { + return unless (ref $params{'option'}{'values'} eq 'ARRAY' and @{ $params{'option'}{'values'} } > 1); + + $items{$params{'section'}}{$params{'subsection'}}{$params{'item'}}{'option'}{'type'} = $params{'option'}{'type'}; + $items{$params{'section'}}{$params{'subsection'}}{$params{'item'}}{'option'}{'values'} = $params{'option'}{'values'}; + $items{$params{'section'}}{$params{'subsection'}}{$params{'item'}}{'option'}{'name'} = $params{'option'}{'name'}; + } + elsif ($params{'option'}{'type'} eq 'integer') + { + return unless (exists $params{'option'}{'min'} and exists $params{'option'}{'max'} and $params{'option'}{'min'} < $params{'option'}{'max'}); + + $items{$params{'section'}}{$params{'subsection'}}{$params{'item'}}{'option'}{'type'} = $params{'option'}{'type'}; + $items{$params{'section'}}{$params{'subsection'}}{$params{'item'}}{'option'}{'min'} = $params{'option'}{'min'}; + $items{$params{'section'}}{$params{'subsection'}}{$params{'item'}}{'option'}{'max'} = $params{'option'}{'max'}; + $items{$params{'section'}}{$params{'subsection'}}{$params{'item'}}{'option'}{'name'} = $params{'option'}{'name'}; + } + } +} + + +#------------------------------------------------------------------------------ +# sub toggle_on_off( string ) +# +# Toggles between 'on' and 'off'. +#------------------------------------------------------------------------------ + +sub toggle_on_off( $ ) +{ + $_[0] = $_[0] eq 'on' ? 'off' : 'on'; +} + + +#------------------------------------------------------------------------------ +# sub export_signing_key() +# +# Exports the signing key to a file on the WUI client computer +#------------------------------------------------------------------------------ + +sub export_signing_key() +{ + # Print headers + print "Content-Disposition: attachment; filename=$mainsettings{HOSTNAME}.asc\n"; + print "Content-Type: application/octet-stream\n"; + print "Content-Length: " . length( $sign_key ) . "\n"; + print "\n"; + + # Deliver content + print $sign_key; + + exit( 0 ); +} + + +#------------------------------------------------------------------------------ +# These functions are referenced by plugins but will not actually be called. +# +# The script to send mail messages makes use of theses functions. +#------------------------------------------------------------------------------ + + +sub get_period_start() +{ +} + +sub get_period_end() +{ +} + +sub get_weeks_covered() +{ +} + +sub cache( $;$ ) +{ +} From patchwork Sat Apr 6 04:29:32 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tim FitzGeorge X-Patchwork-Id: 2175 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 31DFB861F37 for ; Fri, 5 Apr 2019 18:30:32 +0100 (BST) Received: from mail01.i.ipfire.org (localhost [IPv6:::1]) by mail01.ipfire.org (Postfix) with ESMTP id 44bRfb4F8yz5Lcxv; Fri, 5 Apr 2019 18:30:31 +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 44bRfH15Mxz5Lcxm for ; Fri, 5 Apr 2019 18:30:15 +0100 (BST) Received: from [31.127.205.161] (helo=aragorn.tfitzgeorge.me.uk) by smtp.hosts.co.uk with esmtpa (Exim) (envelope-from ) id 1hCSfM-0008Ml-67; Fri, 05 Apr 2019 18:30:14 +0100 From: Tim FitzGeorge To: development@lists.ipfire.org Subject: [PATCH 04/12] statusmail: Supporting files Date: Fri, 5 Apr 2019 18:29:32 +0100 Message-Id: <20190405172940.13168-5-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)[]; RCVD_COUNT_TWO(0.00)[2]; 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.996,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]; HAS_DATA_URI(0.00)[]; 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" generate_signature.sh Generate a PGP key pair for signing emails stylesheet.css Stylesheet for HTML format emails test_plugin.pl Aid for testing plugins statusmail.sh Simple shell script for running statusmail.pl statusmailctrl.c Add/Remove statusmail from fcron.hourly Signed-off-by: Tim FitzGeorge --- src/misc-progs/statusmailctrl.c | 36 +++ src/statusmail/generate_signature.sh | 51 ++++ src/statusmail/statusmail.sh | 3 + src/statusmail/stylesheet.css | 30 ++ src/statusmail/test_plugin.pl | 541 +++++++++++++++++++++++++++++++++++ 5 files changed, 661 insertions(+) create mode 100644 src/misc-progs/statusmailctrl.c create mode 100755 src/statusmail/generate_signature.sh create mode 100755 src/statusmail/statusmail.sh create mode 100755 src/statusmail/stylesheet.css create mode 100755 src/statusmail/test_plugin.pl diff --git a/src/misc-progs/statusmailctrl.c b/src/misc-progs/statusmailctrl.c new file mode 100644 index 000000000..828aaf223 --- /dev/null +++ b/src/misc-progs/statusmailctrl.c @@ -0,0 +1,36 @@ +/* This file is part of the IPFire Firewall. + * + * This program is distributed under the terms of the GNU General Public + * Licence. See the file COPYING for details. + * + */ + +#include +#include +#include +#include +#include +#include +#include "setuid.h" + +int main(int argc, char *argv[]) { + + if (!(initsetuid())) + exit(1); + + if (argc < 2) { + fprintf(stderr, "\nNo argument given.\n\nstatusmailctrl (enable|disable)\n\n"); + exit(1); + } + + if (strcmp(argv[1], "enable") == 0) { + safe_system("ln -fs /usr/lib/statusmail/statusmail.sh /etc/fcron.hourly/statusmail >/dev/null 2>&1"); + } else if (strcmp(argv[1], "disable") == 0) { + safe_system("rm -f /etc/fcron.hourly/statusmail >/dev/null 2>&1"); + } else { + fprintf(stderr, "\nBad argument given.\n\nstatusmailctrl (enable|disable)\n\n"); + exit(1); + } + + return 0; +} diff --git a/src/statusmail/generate_signature.sh b/src/statusmail/generate_signature.sh new file mode 100755 index 000000000..267d6a302 --- /dev/null +++ b/src/statusmail/generate_signature.sh @@ -0,0 +1,51 @@ +#!/bin/sh + +############################################################################ +# # +# 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 # +# # +############################################################################ +# Generates a PGP key that is used to sign email messages # +############################################################################ + +source /var/ipfire/dma/mail.conf + +# Find the old key if there is one so we can delete it later + +OLDKEY=`gpg --homedir /var/ipfire/statusmail/keys --with-colons --fingerprint --list-keys IPFire | sed -ne '/^fpr/{s/fpr//;s/://g;p}'` 2>/dev/null + +echo Generate new keys + +/usr/bin/gpg --homedir /var/ipfire/statusmail/keys --batch --gen-key < $format, stylesheet => $stylesheet, subject => 'Test email' ); + +get_period ( $message ); + +$message->{'max_lines_per_item'} = integer( 'Maximum lines per item', 1, 1000 ); + +# Loop through the various items + +foreach my $section ( sort keys %items ) +{ + $message->add_section( $section ); + + foreach my $subsection ( sort keys %{ $items{$section} } ) + { + $message->add_subsection( $subsection ); + + foreach my $item ( sort keys %{ $items{$section}{$subsection} } ) + { + next unless ($items{$section}{$subsection}{$item}{'format'} eq 'both' or + $items{$section}{$subsection}{$item}{'format'} eq $format); + + if (yesno( "Add item $section : $subsection : $item ? " )) + { + $message->add_title( $item ); + + my $function = $items{$section}{$subsection}{$item}{'function'}; + + if (exists $items{$section}{$subsection}{$item}{'option'}) + { + if ($items{$section}{$subsection}{$item}{'option'}{'type'} eq 'select') + { + my $option = choices( $items{$section}{$subsection}{$item}{'option'}{'name'}, + @{$items{$section}{$subsection}{$item}{'option'}{'values'} } ); + + &$function( $message, $option ); + } + else + { + my $value = integer( $items{$section}{$subsection}{$item}{'option'}{'name'}, + $items{$section}{$subsection}{$item}{'option'}{'min'}, + $items{$section}{$subsection}{$item}{'option'}{'max'} ); + + &$function( $message, $value ); + } + } + else + { + &$function( $message ); + } + } + } + } +} + +$message->print( $testdir ); + +exit; + + +#------------------------------------------------------------------------------ +# sub choices( text, options ) +# +# Asks the user for an option from the provided list. +# +# Parameters: +# text the question to ask the user +# options list of options +# +# Returns: +# the selected option +#------------------------------------------------------------------------------ + +sub choices( $@ ) +{ + my ($text, @options) = @_; + + my $selection = ''; + my %options; + my @display; + + foreach my $option (@options) + { + my ($name, $value) = split /:/, $option; + + $value ||= $name; + + $options{$name} = $value; + push @display, $name; + } + + while (not $selection) + { + print "Select $text from the following options: " . join( ', ', @display ) . ": "; + + my $line = ; + + chomp $line; + + ($selection) = grep /^$line/i, @display; + } + + return $options{$selection}; +} + + +#------------------------------------------------------------------------------ +# sub yesno( text ) +# +# Asks the user for a yes or no option. +# +# Parameters: +# text the question to ask the user +# +# Returns: +# true for yes, false for no +#------------------------------------------------------------------------------ + +sub yesno( $) +{ + my ($text) = @_; + + my $selection = ''; + + while (not $selection) + { + print "$text"; + + my $line = ; + + chomp $line; + + ($selection) = grep /$line/i, ( 'yes', 'no' ); + } + + return $selection eq 'yes'; +} + + +#------------------------------------------------------------------------------ +# sub integer( text, min, max ) +# +# Asks the user for an integer within the specified limits. +# +# Parameters: +# text the question to ask the user +# min minimum value of input +# max maximum value of input +# +# Returns: +# the selected value +#------------------------------------------------------------------------------ + +sub integer( $$$ ) +{ + my ($text, $min, $max) = @_; + + my $value; + + while (not defined $value) + { + print "Select $text ($min..$max):"; + + my $line = ; + + chomp $line; + + next if ($line =~ m/\D+/); + next unless ($line =~ m/\d/); + next if ($line < $min); + next if ($line > $max); + + $value = $line; + } + + return $value; +} + + +#------------------------------------------------------------------------------ +# sub add_mail_item( params ) +# +# Adds a possible status item to the section and subsection specified. +# +# Parameters: +# params hash containing details of the item to be added: +# section name of the section containing this item +# subsection name of the subsection containing this item +# item name of the item +# function function called to add item to message +# format available formats for the item 'html', 'text' or 'both' +# option hash specifying option parameter (optional) +# +# option can specify either a selection or an integer. For a selection it +# contains: +# type must be 'option' +# values array of strings representing the possible options +# +# For an integer option contains: +# type must be 'integer' +# min minimum valid value of parameter +# max maximum valid value of parameter +#------------------------------------------------------------------------------ + +sub add_mail_item( @ ) +{ + my %params = @_; + + if (not exists $params{'section'}) + { + print "Plugin $plugin has no section specified\n"; + return; + } + + if (not exists $params{'subsection'}) + { + print "Plugin $plugin has no subsection specified\n"; + return; + } + + if (not exists $params{'item'}) + { + print "Plugin $plugin has no item specified\n"; + return; + } + + if (not exists $params{'function'}) + { + print "Plugin $plugin has no function specified\n"; + return; + } + + if ($params{'option'}) + { + unless (ref $params{'option'} eq 'HASH') + { + print "Plugin $plugin option incorrectly specified - should be hash\n"; + } + + unless ($params{'option'}{'type'}) + { + print "Plugin $plugin has no option type specified\n"; + return; + } + + unless ($params{'option'}{'name'}) + { + print "Plugin $plugin has no option name specified\n"; + return; + } + + if ($params{'option'}{'type'} eq 'select') + { + unless (ref $params{'option'}{'values'} eq 'ARRAY' and @{ $params{'option'}{'values'} } > 1) + { + print "Plugin $plugin select option values incorrectly specified\n"; + return; + } + } + elsif ($params{'option'}{'type'} eq 'integer') + { + unless (exists $params{'option'}{'min'} and exists $params{'option'}{'max'} and $params{'option'}{'min'} < $params{'option'}{'max'}) + { + print "Plugin $plugin integer option limits not correctly specified\n"; + print "No minimum value specified\n" unless (exists $params{'option'}{'min'}); + print "No maximum value specified\n" unless (exists $params{'option'}{'max'}); + print "Maximum not greater than minimum\n" unless (exists $params{'option'}{'min'} and + exists $params{'option'}{'min'} and + $params{'option'}{'min'} < $params{'option'}{'max'}); + } + } + else + { + print "Plugin $plugin has invalid option $params{'option'}{'type'}\n"; + return; + } + } + + if ($params{'format'} and $params{'format'} ne 'html' and $params{'format'} ne 'text' and $params{'format'} ne 'both') + { + print "Plugin $plugin has invalid format\n"; + } + + $params{'format'} = 'both' unless (exists $params{'format'}); + + $items{$params{'section'}}{$params{'subsection'}}{$params{'item'}} = { 'function' => $params{'function'}, + 'format' => $params{'format'} }; + + if ($params{'option'}) + { + if ($params{'option'}{'type'} eq 'select') + { + $items{$params{'section'}}{$params{'subsection'}}{$params{'item'}}{'option'}{'type'} = $params{'option'}{'type'}; + $items{$params{'section'}}{$params{'subsection'}}{$params{'item'}}{'option'}{'values'} = $params{'option'}{'values'}; + $items{$params{'section'}}{$params{'subsection'}}{$params{'item'}}{'option'}{'name'} = $params{'option'}{'name'}; + } + elsif ($params{'option'}{'type'} eq 'integer') + { + $items{$params{'section'}}{$params{'subsection'}}{$params{'item'}}{'option'}{'type'} = $params{'option'}{'type'}; + $items{$params{'section'}}{$params{'subsection'}}{$params{'item'}}{'option'}{'min'} = $params{'option'}{'min'}; + $items{$params{'section'}}{$params{'subsection'}}{$params{'item'}}{'option'}{'max'} = $params{'option'}{'max'}; + $items{$params{'section'}}{$params{'subsection'}}{$params{'item'}}{'option'}{'name'} = $params{'option'}{'name'}; + } + } +} + + +#------------------------------------------------------------------------------ +# sub get_period +# +# Gets the period covered by a report +#------------------------------------------------------------------------------ + +sub get_period +{ + my $self = shift; + + my @monthnames = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ); + + my $unit = choices( 'Period covered by report', 'hours', 'days', 'weeks', 'months' ); + my $value = integer( "$unit covered by report", 1, 365 ); + + $self->calculate_period( $value, $unit ); +} + +#------------------------------------------------------------------------------ +#------------------------------------------------------------------------------ +# Package TestStatusMail +# +# This package is used to override some of the functionality of the StatusMail +# and EncryptedMail packages. +#------------------------------------------------------------------------------ +#------------------------------------------------------------------------------ + + +package TestStatusMail; + +use base qw/StatusMail/; + +#------------------------------------------------------------------------------ +# sub print( directory ) +# +# Prints the plugin(s) output to the specified directory. +#------------------------------------------------------------------------------ + +sub print( $$ ) +{ + my $self = shift; + my $dir = shift; + my $file = "$dir/test.txt"; + + if ($self->{'empty'}) + { + print "No output produced\n"; + return; + } + + if ($self->{format} eq 'html') + { + $self->{message} .= "\n" if ($self->{in_item}); + $self->{message} .= "\n" if ($self->{in_subsection}); + $self->{message} .= "\n" if ($self->{in_section}); + + $self->{message} .= "\n\n\n"; + $file = "$dir/test.html"; + } + + open OUT, '>', $file or die "Can't open test output file $file: $!"; + + print OUT $self->{message}; + + close OUT; + + print "Output is in $file\n"; +} + + +#------------------------------------------------------------------------------ +# sub add_image( params ) +# +# Outputs an image as a file. +#------------------------------------------------------------------------------ + +sub add_image +{ + my ($self, %params) = @_; + + if ($self->{section}) + { + $self->{message} .= $self->{section}; + $self->{section} = ''; + $self->{in_section} = 1; + $self->{in_subsection} = 0; + $self->{in_item} = 0; + } + + if ($self->{subsection}) + { + $self->{message} .= $self->{subsection}; + $self->{subsection} = ''; + $self->{in_subsection} = 1; + $self->{in_item} = 0; + } + + if ($self->{item}) + { + $self->{message} .= $self->{item}; + $self->{item} = ''; + $self->{in_item} = 1; + } + + $self->{'image_file'}++; + + my $image_name = $self->{'image_file'}; + + $image_name .= '.jpg' if ($params{'type'} eq 'image/jpeg'); + $image_name .= '.gif' if ($params{'type'} eq 'image/gif'); + $image_name .= '.png' if ($params{'type'} eq 'image/png'); + + open OUT, '>', "test/$image_name" or die "Can't open image file $image_name: $!"; + binmode( OUT ); + + if (exists $params{fh}) + { + my $buffer; + binmode $params{fh}; + + while (read $params{fh}, $buffer, 1024) + { + print OUT $buffer; + } + } + elsif (exists $params{data}) + { + print OUT $params{data}; + } + + close OUT; + + $self->{message} .= "{message} .= " alt='$params{alt}'" if (exists $params{alt}); + $self->{message} .= ">\n"; + + $self->{empty} = 0; +} + +1; From patchwork Sat Apr 6 04:29:33 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Tim FitzGeorge X-Patchwork-Id: 2176 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 82416861F37 for ; Fri, 5 Apr 2019 18:30:37 +0100 (BST) Received: from mail01.i.ipfire.org (localhost [IPv6:::1]) by mail01.ipfire.org (Postfix) with ESMTP id 44bRfh72NMz5Lcxg; Fri, 5 Apr 2019 18:30:36 +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 44bRfX2gSqz5Lcxl for ; Fri, 5 Apr 2019 18:30:28 +0100 (BST) Received: from [31.127.205.161] (helo=aragorn.tfitzgeorge.me.uk) by smtp.hosts.co.uk with esmtpa (Exim) (envelope-from ) id 1hCSfb-00006W-3U; Fri, 05 Apr 2019 18:30:28 +0100 From: Tim FitzGeorge To: development@lists.ipfire.org Subject: [PATCH 05/12] statusmail: Language files Date: Fri, 5 Apr 2019 18:29:33 +0100 Message-Id: <20190405172940.13168-6-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> MIME-Version: 1.0 X-Spamd-Result: default: False [-13.16 / 11.00]; ARC_NA(0.00)[]; RCVD_VIA_SMTP_AUTH(0.00)[]; RECEIVED_SPAMHAUS_PBL(0.00)[161.205.127.31.zen.spamhaus.org : 127.0.0.11]; 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.996,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(-2.99)[99.96%]; FROM_EQ_ENVFROM(0.00)[] 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" Language file updates. Note that the french language strings have been checked by a native speaker. Signed-off-by: Tim FitzGeorge --- langs/en/cgi-bin/en.pl | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++ langs/fr/cgi-bin/fr.pl | 92 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 186 insertions(+) diff --git a/langs/en/cgi-bin/en.pl b/langs/en/cgi-bin/en.pl index af0d514af..dba0be3eb 100644 --- a/langs/en/cgi-bin/en.pl +++ b/langs/en/cgi-bin/en.pl @@ -1353,6 +1353,7 @@ 'hosts config changed' => 'Hosts config changed', 'hour' => 'Hour', 'hour-graph' => 'Hour', +'hours-graph' => 'Hours', 'hours' => 'Hours', 'hours2' => 'Hours', 'ibod for dual isdn only' => 'iBOD can only be used with Dual ISDN.', @@ -2266,6 +2267,99 @@ 'status' => 'Status', 'status information' => 'Status information', 'status ovpn' => 'OpenVPN', +'statusmail action' => 'Action', +'statusmail addon' => 'Addons', +'statusmail addon updates available' => 'Addon updates available', +'statusmail contact name' => 'Name', +'statusmail contacts' => 'Contacts', +'statusmail copied to clipboard' => 'Copied key to clipboard', +'statusmail copy to clipboard' => 'Copy to clipboard', +'statusmail core' => 'Core', +'statusmail core update available' => 'Core update available', +'statusmail day of month' => 'Days of month', +'statusmail day of week' => 'Days of Week', +'statusmail detail' => 'Level of detail', +'statusmail disk access' => 'Disk access', +'statusmail disk temperature' => 'Disk temperature', +'statusmail email' => 'Email address', +'statusmail email format' => 'Email format', +'statusmail email invalid' => 'Invalid email address', +'statusmail email not enabled' => 'Not available: emails not enabled', +'statusmail email subject' => 'Email subject', +'statusmail email to' => 'Send email to', +'statusmail error' => 'Error', +'statusmail errors' => 'Errors', +'statusmail event' => 'Event', +'statusmail events' => 'Events', +'statusmail executable' => 'Executable', +'statusmail execute' => 'Execute schedule now', +'statusmail fingerprint' => 'Fingerprint', +'statusmail firewall min count' => 'Minimum count', +'statusmail firewall reason' => 'Reason', +'statusmail friday' => 'Fri', +'statusmail full' => 'Full', +'statusmail generate' => 'Generate', +'statusmail hardware' => 'Hardware', +'statusmail hide' => 'Hide', +'statusmail hour of day' => 'Hours', +'statusmail ips alert' => 'Alert', +'statusmail ips alerts' => 'Alerts', +'statusmail ips min priority' => 'Minimum priority', +'statusmail import' => 'Import', +'statusmail import key failed' => 'Failed to import PGP key', +'statusmail invalid key' => 'Invalid PGP Key format', +'statusmail kernel edac messages' => 'EDAC Messages', +'statusmail kernel errors' => 'Kernel errors', +'statusmail kernel FP Assists' => 'Floating-point assists', +'statusmail kernel general protection fault' => 'General Protection Faults', +'statusmail kernel out of memory' => 'Out of Memory Killer', +'statusmail kernel raid errors' => 'RAID errors', +'statusmail kernel segmentation fault' => 'Segmentation faults', +'statusmail kernel SYN flood' => 'SYN Flood', +'statusmail kernel trap int3' => 'Trap INT3', +'statusmail kernel unaligned' => 'Unaligned errors', +'statusmail key' => 'Encryption Key', +'statusmail key expires' => 'Expires', +'statusmail keyid' => 'Encryption Key ID', +'statusmail key remove failed' => 'Failed to remove signing key', +'statusmail keys' => 'Installed Encryption Key', +'statusmail lines per item' => 'Lines per item', +'statusmail logins' => 'Logins', +'statusmail max free percent' => 'Maximum percentage free', +'statusmail message' => 'Message', +'statusmail monday' => 'Mon', +'statusmail no contact name' => 'No Contact name specified', +'statusmail no email addresses' => 'No email addresses specified', +'statusmail no email subject' => 'No email subject specified', +'statusmail no lines per item' => 'No lines per item specified', +'statusmail no period covered' => 'No period covered specified', +'statusmail no schedule date' => 'No scheduled days to send message', +'statusmail no schedule name' => 'No schedule name specified', +'statusmail no schedule time' => 'No scheduled time to send message', +'statusmail no signing key' => 'Please generate a signing key', +'statusmail paste from clipboard' => 'Paste from clipboard', +'statusmail period covered' => 'Period covered', +'statusmail period from' => 'From', +'statusmail period' => 'Period', +'statusmail period to' => 'to', +'statusmail saturday' => 'Sat', +'statusmail schedule name' => 'Name', +'statusmail schedules' => 'Schedules', +'statusmail show' => 'Show', +'statusmail signatures' => 'Signatures: ', +'statusmail signing key' => 'Signing Key', +'statusmail statistics' => 'Statistics', +'statusmail status emails' => 'Status Emails', +'statusmail summary' => 'Summary', +'statusmail sunday' => 'Sun', +'statusmail system ps any' => 'any', +'statusmail system ps process status' => 'Process Status', +'statusmail thursday' => 'Thu', +'statusmail tuesday' => 'Tue', +'statusmail update' => 'Updates', +'statusmail urlfilter min count' => 'Minimum count', +'statusmail wednesday' => 'Wed', +'statusmail working' => 'Generating signing key', 'std classes' => 'Standardclasses', 'stop' => 'Stop', 'stop ovpn server' => 'Stop OpenVPN Server', diff --git a/langs/fr/cgi-bin/fr.pl b/langs/fr/cgi-bin/fr.pl index b4ecf32fa..a1fae24c3 100644 --- a/langs/fr/cgi-bin/fr.pl +++ b/langs/fr/cgi-bin/fr.pl @@ -1344,6 +1344,7 @@ 'hosts config changed' => 'configuration de l\'hôte changée', 'hour' => 'Heure', 'hour-graph' => 'Heure', +'hour-graph' => 'Heures', 'hours' => 'heure(s)', 'hours2' => 'Heures', 'ibod for dual isdn only' => 'iBOD peut seulement être utilisé avec un double ISDN.', @@ -2230,6 +2231,97 @@ 'status' => 'Statut', 'status information' => 'Etat matériel', 'status ovpn' => 'Statut / configuration OpenVPN :', +'statusmail action' => 'Action :', +'statusmail addon' => 'Addons', +'statusmail addon updates available' => 'Ajout de mises à jour disponibles', +'statusmail blocklist update' => 'Mises à jour', +'statusmail contact name' => 'Nom du contact :', +'statusmail contacts' => 'Liste de contacts', +'statusmail copied to clipboard' => 'Clé copiée dans le presse-papier', +'statusmail copy to clipboard' => 'Copier dans le presse-papier', +'statusmail core' => 'Noyau', +'statusmail core update available' => 'Mise à jour du noyau disponible', +'statusmail day of month' => 'Jours du mois ', +'statusmail day of week' => 'Jours de la semaine ', +'statusmail detail' => 'Niveau de détail', +'statusmail disk access' => 'Accès disque', +'statusmail disk temperature' => 'Température du disque', +'statusmail email' => 'Adresse électronique :', +'statusmail email format' => 'Format de l\'email :', +'statusmail email invalid' => 'Adresse email invalide', +'statusmail email not enabled' => 'Non disponible : Les emails ne sont pas activés', +'statusmail email subject' => 'Sujet de l\'email :', +'statusmail email to' => 'Envoyer un email à :', +'statusmail error' => 'Erreur', +'statusmail errors' => 'Erreurs', +'statusmail event' => 'Evènement', +'statusmail events' => 'Evènements', +'statusmail executable' => 'Exécutable', +'statusmail execute' => 'Exécuter le profil maintenant', +'statusmail fingerprint' => 'Empreinte digitale :', +'statusmail firewall min count' => 'Quantité minimum', +'statusmail firewall reason' => 'Raison', +'statusmail friday' => 'Ven', +'statusmail full' => 'Plein', +'statusmail generate' => 'Générer', +'statusmail hardware' => 'Matériel', +'statusmail hour of day' => 'Heures ', +'statusmail ips alert' => 'Alerte', +'statusmail ips alerts' => 'Alertes', +'statusmail ips min priority' => 'Priorité minimale', +'statusmail ips update' => 'Mises à jour', +'statusmail import' => 'Importer', +'statusmail import key failed' => "Échec de l'importation de la clé PGP", +'statusmail invalid key' => 'Format de clé PGP non valide', +'statusmail kernel edac messages' => 'Messages EDAC', +'statusmail kernel errors' => 'Erreurs du noyau', +'statusmail kernel FP Assists' => 'Aides en virgule flottante', +'statusmail kernel general protection fault' => 'Erreur de protection générale', +'statusmail kernel out of memory' => 'Tueur de mémoire insuffisante', +'statusmail kernel raid errors' => 'Erreurs RAID', +'statusmail kernel segmentation fault' => 'Erreurs de segmentation', +'statusmail kernel SYN flood' => 'SYN Flood', +'statusmail kernel trap int3' => 'Piège INT3', +'statusmail kernel unaligned' => 'Erreurs non alignées', +'statusmail key' => 'Clé de cryptage :', +'statusmail key expires' => 'Expire :', +'statusmail keyid' => 'ID de clé de cryptage :', +'statusmail key remove failed' => 'Échec de la suppression de la clé de signature', +'statusmail keys' => 'Clé de cryptage installée', +'statusmail lines per item' => 'Nombre de lignes maximum par élément :', +'statusmail logins' => 'Connexions', +'statusmail max free percent' => 'Pourcentage libre maximum', +'statusmail message' => 'Message', +'statusmail monday' => 'Lun', +'statusmail no contact name' => 'Aucun nom de contact spécifié', +'statusmail no email addresses' => 'Aucune adresse email spécifiée', +'statusmail no email subject' => "Aucun sujet d'email spécifié", +'statusmail no lines per item' => 'Aucun nombre de ligne par élément spécifié', +'statusmail no period covered' => 'Aucune période de référence spécifiée', +'statusmail no schedule date' => 'Aucun jour programmé pour envoyer un message', +'statusmail no schedule name' => 'Aucun nom de configuration spécifié', +'statusmail no schedule time' => 'Aucune heure programmée pour envoyer un message', +'statusmail no signing key' => "Veuillez générer une clé de signature", +'statusmail paste from clipboard' => 'Coller du presse-papier', +'statusmail period covered' => 'Période couverte par l\'email ', +'statusmail period' => 'Période', +'statusmail saturday' => 'Sam', +'statusmail schedule name' => 'Nom de la planification :', +'statusmail schedules' => 'Paramètres de planification', +'statusmail signatures' => 'Signatures : ', +'statusmail signing key' => 'Clé de signature :', +'statusmail statistics' => 'Statistiques', +'statusmail status emails' => 'Emails d\'état', +'statusmail summary' => 'Résumé', +'statusmail sunday' => 'Dim', +'statusmail system ps any' => 'tout', +'statusmail system ps process status' => 'Statut du processus', +'statusmail thursday' => 'Jeu', +'statusmail tuesday' => 'Mar', +'statusmail update' => 'Mises à jour', +'statusmail urlfilter min count' => 'Quantité minimum', +'statusmail wednesday' => 'Mer', +'statusmail working' => 'Génération de clé de signature', 'std classes' => 'Classes standards', 'stop' => 'Arrêter', 'stop ovpn server' => 'Arrêter serveur OpenVPN', From patchwork Sat Apr 6 04:29:34 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tim FitzGeorge X-Patchwork-Id: 2177 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 D9136861F37 for ; Fri, 5 Apr 2019 18:30:44 +0100 (BST) Received: from mail01.i.ipfire.org (localhost [IPv6:::1]) by mail01.ipfire.org (Postfix) with ESMTP id 44bRfr2TmXz5Lcxj; Fri, 5 Apr 2019 18:30:44 +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 44bRfc2hgMz5Lcy3 for ; Fri, 5 Apr 2019 18:30:32 +0100 (BST) Received: from [31.127.205.161] (helo=aragorn.tfitzgeorge.me.uk) by smtp.hosts.co.uk with esmtpa (Exim) (envelope-from ) id 1hCSfe-00006W-5y; Fri, 05 Apr 2019 18:30:32 +0100 From: Tim FitzGeorge To: development@lists.ipfire.org Subject: [PATCH 06/12] statusmail: Infrastructure files Date: Fri, 5 Apr 2019 18:29:34 +0100 Message-Id: <20190405172940.13168-7-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.996,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" Adds statusmail to build and menu Signed-off-by: Tim FitzGeorge --- config/backup/include | 3 ++ config/menu/10-system.menu | 5 +++ config/rootfiles/common/misc-progs | 1 + config/rootfiles/common/statusmail | 22 +++++++++ config/rootfiles/common/web-user-interface | 2 + lfs/statusmail | 71 ++++++++++++++++++++++++++++++ make.sh | 2 + src/misc-progs/Makefile | 2 +- 8 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 config/rootfiles/common/statusmail create mode 100644 lfs/statusmail diff --git a/config/backup/include b/config/backup/include index 1190eda81..bd8b77198 100644 --- a/config/backup/include +++ b/config/backup/include @@ -49,6 +49,9 @@ /var/ipfire/suricata/*.conf /var/ipfire/suricata/*.yaml /var/ipfire/*/settings +/var/ipfire/statusmail/contact_settings +/var/ipfire/statusmail/schedule_settings +/var/ipfire/statusmail/keys/*.gpg /var/ipfire/time/ /var/ipfire/urlfilter /var/ipfire/vpn diff --git a/config/menu/10-system.menu b/config/menu/10-system.menu index 3b84e31c0..007f7e95d 100644 --- a/config/menu/10-system.menu +++ b/config/menu/10-system.menu @@ -21,6 +21,11 @@ 'title' => "$Lang::tr{'email settings'}", 'enabled' => 1, }; + $subsystem->{'25.statusmail'} = {'caption' => $Lang::tr{'statusmail status emails'}, + 'uri' => '/cgi-bin/statusmail.cgi', + 'title' => $Lang::tr{'statusmail status emails'}, + 'enabled' => 1, + }; $subsystem->{'30.ssh'} = { 'caption' => $Lang::tr{'ssh access'}, 'uri' => '/cgi-bin/remote.cgi', diff --git a/config/rootfiles/common/misc-progs b/config/rootfiles/common/misc-progs index c48a474b2..828ed2383 100644 --- a/config/rootfiles/common/misc-progs +++ b/config/rootfiles/common/misc-progs @@ -26,6 +26,7 @@ usr/local/bin/redctrl #usr/local/bin/sambactrl usr/local/bin/setaliases usr/local/bin/smartctrl +usr/local/bin/statusmailctrl usr/local/bin/squidctrl usr/local/bin/suricatactrl usr/local/bin/sshctrl diff --git a/config/rootfiles/common/statusmail b/config/rootfiles/common/statusmail new file mode 100644 index 000000000..4a754990e --- /dev/null +++ b/config/rootfiles/common/statusmail @@ -0,0 +1,22 @@ +#usr/lib/statusmail +usr/lib/statusmail/EncryptedMail.pm +usr/lib/statusmail/StatusMail.pm +usr/lib/statusmail/generate_signature.sh +#usr/lib/statusmail/plugins +usr/lib/statusmail/plugins/graphs.pm +usr/lib/statusmail/plugins/hardware_media_space.pm +usr/lib/statusmail/plugins/network_firewall.pm +#usr/lib/statusmail/plugins/services_ups_apc.pm +#usr/lib/statusmail/plugins/services_clamav.pm +usr/lib/statusmail/plugins/services_intrusion_prevention_system.pm +usr/lib/statusmail/plugins/services_urlfilter.pm +usr/lib/statusmail/plugins/system_kernel.pm +usr/lib/statusmail/plugins/system_pakfire.pm +usr/lib/statusmail/plugins/system_ssh.pm +usr/lib/statusmail/plugins/system_status_ps.pm +usr/lib/statusmail/plugins/system_status_services.pm +usr/lib/statusmail/stylesheet.css +usr/local/bin/statusmail.pl +usr/lib/statusmail/statusmail.sh +var/ipfire/statusmail +var/ipfire/statusmail/keys diff --git a/config/rootfiles/common/web-user-interface b/config/rootfiles/common/web-user-interface index d538b8a5b..e78e09abe 100644 --- a/config/rootfiles/common/web-user-interface +++ b/config/rootfiles/common/web-user-interface @@ -79,6 +79,7 @@ srv/web/ipfire/cgi-bin/routing.cgi srv/web/ipfire/cgi-bin/services.cgi srv/web/ipfire/cgi-bin/shutdown.cgi srv/web/ipfire/cgi-bin/speed.cgi +srv/web/ipfire/cgi-bin/statusmail.cgi srv/web/ipfire/cgi-bin/system.cgi srv/web/ipfire/cgi-bin/time.cgi #srv/web/ipfire/cgi-bin/tor.cgi @@ -237,6 +238,7 @@ srv/web/ipfire/html/images/on.gif srv/web/ipfire/html/images/openvpn.gif srv/web/ipfire/html/images/openvpn.png srv/web/ipfire/html/images/package-x-generic.png +srv/web/ipfire/html/images/play.png srv/web/ipfire/html/images/printer-error.png srv/web/ipfire/html/images/printer.png srv/web/ipfire/html/images/process-stop.png diff --git a/lfs/statusmail b/lfs/statusmail new file mode 100644 index 000000000..8ddf2df36 --- /dev/null +++ b/lfs/statusmail @@ -0,0 +1,71 @@ +############################################################################### +# IPFire.org - An Open Source Firewall Solution # +# Copyright (C) 2007-2019 IPFire Team # +############################################################################### + +############################################################################### +# Definitions +############################################################################### + +include Config + +VER = 1.0 + +THISAPP = statusmail +DIR_APP = $(DIR_SRC)/$(THISAPP) +TARGET = $(DIR_INFO)/$(THISAPP) +PROG = statusmail +PAK_VER = 1 + +############################################################################### +# Top-level Rules +############################################################################### + +install : $(TARGET) + +check : + +download : + +md5 : + + +############################################################################### +# Installation Details +############################################################################### + +$(TARGET) : $(patsubst %,$(DIR_DL)/%,$(objects)) + @$(PREBUILD) + @rm -rf $(DIR_APP) && mkdir $(DIR_APP) && cp -R $(DIR_SRC)/src/statusmail/ $(DIR_APP) + cd $(DIR_APP) + mkdir -p /usr/lib/statusmail + mkdir -p /usr/lib/statusmail/plugins + mkdir -p /var/ipfire/statusmail + mkdir -p /var/ipfire/statusmail/keys + + chown nobody.nobody /var/ipfire/statusmail/keys + chmod 0700 /var/ipfire/statusmail/keys + chown nobody.nobody /var/ipfire/statusmail + + install -v -m 0755 $(DIR_APP)/statusmail/statusmail.pl /usr/local/bin + install -v -m 0755 $(DIR_APP)/statusmail/statusmail.sh /usr/lib/statusmail + install -v -m 0644 $(DIR_APP)/statusmail/EncryptedMail.pm /usr/lib/statusmail + install -v -m 0644 $(DIR_APP)/statusmail/StatusMail.pm /usr/lib/statusmail + install -v -m 0755 $(DIR_APP)/statusmail/stylesheet.css /usr/lib/statusmail + install -v -m 0755 $(DIR_APP)/statusmail/generate_signature.sh /usr/lib/statusmail + + install -v -m 0644 $(DIR_APP)/statusmail/plugins/graphs.pm /usr/lib/statusmail/plugins + install -v -m 0644 $(DIR_APP)/statusmail/plugins/hardware_media_space.pm /usr/lib/statusmail/plugins/ + install -v -m 0644 $(DIR_APP)/statusmail/plugins/network_firewall.pm /usr/lib/statusmail/plugins/ + install -v -m 0644 $(DIR_APP)/statusmail/plugins/system_kernel.pm /usr/lib/statusmail/plugins/ + install -v -m 0644 $(DIR_APP)/statusmail/plugins/system_pakfire.pm /usr/lib/statusmail/plugins/ + install -v -m 0644 $(DIR_APP)/statusmail/plugins/system_ssh.pm /usr/lib/statusmail/plugins/ + install -v -m 0644 $(DIR_APP)/statusmail/plugins/system_status_ps.pm /usr/lib/statusmail/plugins/ + install -v -m 0644 $(DIR_APP)/statusmail/plugins/system_status_services.pm /usr/lib/statusmail/plugins/ + install -v -m 0644 $(DIR_APP)/statusmail/plugins/services_urlfilter.pm /usr/lib/statusmail/plugins/ + install -v -m 0644 $(DIR_APP)/statusmail/plugins/services_intrusion_prevention_system.pm /usr/lib/statusmail/plugins/ +# install -v -m 0644 $(DIR_APP)/statusmail/plugins/services_clamav.pm /usr/lib/statusmail/plugins/ +# install -v -m 0644 $(DIR_APP)/statusmail/plugins/services_ups_apc.pm /usr/lib/statusmail/plugins/ + + @rm -rf $(DIR_APP) + @$(POSTBUILD) diff --git a/make.sh b/make.sh index de973c69c..6be8d1d7f 100755 --- a/make.sh +++ b/make.sh @@ -1594,6 +1595,7 @@ buildipfire() { lfsmake2 zabbix_agentd lfsmake2 flashrom lfsmake2 firmware-update + lfsmake2 statusmail } buildinstaller() { diff --git a/src/misc-progs/Makefile b/src/misc-progs/Makefile index bea54e773..42f239cec 100644 --- a/src/misc-progs/Makefile +++ b/src/misc-progs/Makefile @@ -32,7 +32,7 @@ SUID_PROGS = squidctrl sshctrl ipfirereboot \ smartctrl clamavctrl addonctrl pakfire mpfirectrl wlanapctrl \ setaliases urlfilterctrl updxlratorctrl fireinfoctrl rebuildroutes \ getconntracktable wirelessclient torctrl ddnsctrl unboundctrl \ - captivectrl + captivectrl statusmailctrl SUID_UPDX = updxsetperms OBJS = $(patsubst %,%.o,$(PROGS) $(SUID_PROGS)) From patchwork Sat Apr 6 04:29:35 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tim FitzGeorge X-Patchwork-Id: 2178 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 7F21E861F37 for ; Fri, 5 Apr 2019 18:30:50 +0100 (BST) Received: from mail01.i.ipfire.org (localhost [IPv6:::1]) by mail01.ipfire.org (Postfix) with ESMTP id 44bRfx6jMjz5Lcxl; Fri, 5 Apr 2019 18:30:49 +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 44bRfh16hqz5D0P7 for ; Fri, 5 Apr 2019 18:30:36 +0100 (BST) Received: from [31.127.205.161] (helo=aragorn.tfitzgeorge.me.uk) by smtp.hosts.co.uk with esmtpa (Exim) (envelope-from ) id 1hCSfi-00006W-4B; Fri, 05 Apr 2019 18:30:35 +0100 From: Tim FitzGeorge To: development@lists.ipfire.org Subject: [PATCH 07/12] statusmail: Plugins for services Date: Fri, 5 Apr 2019 18:29:35 +0100 Message-Id: <20190405172940.13168-8-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)[]; RECEIVED_SPAMHAUS_PBL(0.00)[161.205.127.31.zen.spamhaus.org : 127.0.0.11]; 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.996,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%]; FROM_EQ_ENVFROM(0.00)[] 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" Intrusion Prevention System plugin works with Suricata, but not Snort Signed-off-by: Tim FitzGeorge --- .../services_intrusion_prevention_system.pm | 239 ++++++++++++++++++ src/statusmail/plugins/services_urlfilter.pm | 275 +++++++++++++++++++++ 2 files changed, 514 insertions(+) create mode 100644 src/statusmail/plugins/services_intrusion_prevention_system.pm create mode 100644 src/statusmail/plugins/services_urlfilter.pm diff --git a/src/statusmail/plugins/services_intrusion_prevention_system.pm b/src/statusmail/plugins/services_intrusion_prevention_system.pm new file mode 100644 index 000000000..4ca174d4e --- /dev/null +++ b/src/statusmail/plugins/services_intrusion_prevention_system.pm @@ -0,0 +1,239 @@ +#!/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 Services_Intrusion_Prevention_System; + +use Time::Local; + +############################################################################ +# Function prototypes +############################################################################ + +sub get_log( $ ); + +############################################################################ +# Constants +############################################################################ + +use constant { SEC => 0, + MIN => 1, + HOUR => 2, + MDAY => 3, + MON => 4, + YEAR => 5, + WDAY => 6, + YDAY => 7, + ISDST => 8, + MONSTR => 9 }; + +############################################################################ +# BEGIN Block +# +# Register the log items available in this file +############################################################################ + +sub BEGIN +{ + main::add_mail_item( 'ident' => 'services-ips-alerts', + 'section' => $Lang::tr{'services'}, + 'subsection' => $Lang::tr{'intrusion prevention system'}, + 'item' => $Lang::tr{'statusmail ips alerts'}, + 'function' => \&alerts, + 'option' => { 'type' => 'integer', + 'name' => $Lang::tr{'statusmail ips min priority'}, + 'min' => 1, + 'max' => 4 } ); +} + +############################################################################ +# Code +############################################################################ + +#------------------------------------------------------------------------------ +# sub get_log +# +# +#------------------------------------------------------------------------------ + +sub get_log( $ ) +{ + my ($this) = @_; + +# There's only one data item, so don't use the cache +# my $data = $this->cache( 'ips-alerts' ); +# return $data if (defined $data); + + my $name = '/var/log/suricata/fast.log'; + + my %info; + my $last_mon = 0; + my $last_day = 0; + my $last_hour = 0; + my $last_time = 0; + my $time = 0; + my $now = time(); + my $year = 0; + my $start_time = $this->get_period_start; + my $end_time = $this->get_period_end; + my @stats; + + for (my $filenum = $this->get_number_weeks ; $filenum >= 0 ; $filenum--) + { + my $filename = $filenum < 1 ? $name : "$name.$filenum"; + + if (-r "$filename.gz") + { + @stats = stat( _ ); + next if ($stats[9] < $start_time); + + open IN, "gzip -dc $filename.gz |" or next; + } + elsif (-r $filename) + { + @stats = stat( _ ); + open IN, '<', $filename or next; + } + else + { + next; + } + + foreach my $line () + { + chomp $line; + + # Alerts have the format: + # + # mm/dd/yyyy-hh:mm:ss.uuuuuu [Action] [**] [gid:sid:prio] message [**] [Classification: type] [Priority: prio] {protocol} src-ip:src-port -> dest-ip:dest-port + + $line =~ s/^\s+//; + $line =~ s/\s+$//; + + next unless ($line); + + my ($mon, $day, $year, $hour, $min, $sec, $gid, $sid, $message, $prio, $src, $dest) = + $line =~ m|(\d+)/(\d+)/(\d+)-(\d+):(\d+):(\d+)\.\d+\s+\[\w+\]\s+\[\*\*\]\s+\[(\d+):(\d+):\d+\]\s*(.*)\s+\[\*\*\].*\[Priority:\s(\d+)\].*?\s+(\d+\.\d+\.\d+\.\d+(?::\d+)?) -> (\d+\.\d+\.\d+\.\d+(?::\d+)?)|; + + $sid = "$gid-$sid"; + + if ($mon != $last_mon or $day != $last_day or $hour != $last_hour) + { + # Hour, day or month changed. Convert to unix time so we can work out + # whether the message time falls between the limits we're interested in. + + my @time; + + $time[YEAR] = $year; + + ($time[MON], $time[MDAY], $time[HOUR], $time[MIN], $time[SEC]) = ($mon - 1, $day, $hour, $min, $sec); + + $time = timelocal( @time ); + + ($last_mon, $last_day, $last_hour) = ($mon, $day, $hour); + } + + # Check to see if we're within the specified limits. + # Note that the minutes and seconds may be incorrect, but since we only deal + # in hour boundaries this doesn't matter. + + next if ($time < $start_time); + last if ($time > $end_time); + + my $timestr = "$mon/$day $hour:$min:$sec"; + + $info{total}++; + + if (exists $info{by_sid}{$sid}) + { + $info{by_sid}{$sid}{count}++; + $info{by_sid}{$sid}{last} = $timestr; + } + else + { + $info{by_sid}{$sid}{count} = 1; + $info{by_sid}{$sid}{priority} = $prio; + $info{by_sid}{$sid}{message} = $message; + $info{by_sid}{$sid}{first} = $timestr; + $info{by_sid}{$sid}{last} = $timestr; + } + } + + close IN; + } + +# $this->cache( 'ids-alerts', \%info ); + + return \%info; + +} + + +#------------------------------------------------------------------------------ + +sub alerts( $$ ) +{ + my ($self, $min_priority) = @_; + my @table; + + use Sort::Naturally; + + push @table, ['|', '|', '<', '|', '|', '|', '|']; + push @table, [ 'SID', $Lang::tr{'priority'}, $Lang::tr{'name'}, $Lang::tr{'count'}, $Lang::tr{'percentage'}, $Lang::tr{'first'}, $Lang::tr{'last'} ]; + + my $stats = get_log( $self ); + + foreach my $sid (sort { $$stats{by_sid}{$a}{priority} <=> $$stats{by_sid}{$b}{priority} || + $$stats{by_sid}{$b}{count} <=> $$stats{by_sid}{$a}{count}} keys %{ $$stats{by_sid} } ) + { + my $message = $$stats{by_sid}{$sid}{message}; + my $priority = $$stats{by_sid}{$sid}{priority}; + my $count = $$stats{by_sid}{$sid}{count}; + my $first = $$stats{by_sid}{$sid}{first}; + my $last = $$stats{by_sid}{$sid}{last}; + my $percent = int( 100 * $count / $$stats{total} + 0.5); + + last if ($priority > $min_priority); + + $message = $self->split_string( $message, 40 ); + + push @table, [ $sid, $priority, $message, $count, $percent, $first, $last ]; + } + + if (@table > 2) + { + $self->add_table( @table ); + + return 1; + } + + return 0; +} + + +1; diff --git a/src/statusmail/plugins/services_urlfilter.pm b/src/statusmail/plugins/services_urlfilter.pm new file mode 100644 index 000000000..620dc1e20 --- /dev/null +++ b/src/statusmail/plugins/services_urlfilter.pm @@ -0,0 +1,275 @@ +#!/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 Services_Urlfilter; + +use Time::Local; + +############################################################################ +# BEGIN Block +# +# Register the log items available in this file +############################################################################ + +sub BEGIN +{ + main::add_mail_item( 'ident' => 'services-urlfilter-client', + 'section' => $Lang::tr{'services'}, + 'subsection' => $Lang::tr{'urlfilter url filter'}, + 'item' => $Lang::tr{'urlfilter client'}, + 'function' => \&clients, + 'option' => { 'type' => 'integer', + 'name' => $Lang::tr{'statusmail urlfilter min count'}, + 'min' => 1, + 'max' => 1000 } ); + + main::add_mail_item( 'ident' => 'services-urlfilter-destination', + 'section' => $Lang::tr{'services'}, + 'subsection' => $Lang::tr{'urlfilter url filter'}, + 'item' => $Lang::tr{'destination'}, + 'function' => \&destinations, + 'option' => { 'type' => 'integer', + 'name' => $Lang::tr{'statusmail urlfilter min count'}, + 'min' => 1, + 'max' => 1000 } ); +} + +############################################################################ +# Constants +############################################################################ + +use constant { SEC => 0, + MIN => 1, + HOUR => 2, + MDAY => 3, + MON => 4, + YEAR => 5, + WDAY => 6, + YDAY => 7, + ISDST => 8, + MONSTR => 9 }; + + +############################################################################ +# Functions +############################################################################ + +sub get_log( $ ); + +#------------------------------------------------------------------------------ +# sub get_log( this ) +# +# Gets messages from the log files that relate to the URL filter. The data is +# cached sot hat a second call does not process the logs again. +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub get_log( $ ) +{ + my ($this) = @_; + + my $data = $this->cache( 'urlfilter' ); + return $data if (defined $data); + + my %info; + my $weeks = $this->get_number_weeks; + my @start_time = $this->get_period_start;; + my @end_time = $this->get_period_end; + + # Iterate over the log files + + foreach my $name (glob '/var/log/squidGuard/*\.log') + { + next if ($name =~ m/squidGuard.log/); + + # Iterate over old versions of the file + + for (my $filenum = $weeks ; $filenum >= 0 ; $filenum--) + { + my $filename = $filenum < 1 ? $name : "$name.$filenum"; + + if (-r "$filename.gz") + { + open IN, "gzip -dc $filename.gz |" or next; + } + elsif (-r $filename) + { + open IN, '<', $filename or next; + } + else + { + next; + } + + # Scan the file + + foreach my $line () + { + my ($year, $mon, $day, $hour) = split /[\s:-]+/, $line; + + # Check to see if we're within the specified limits. + # Note that the minutes and seconds may be incorrect, but since we only deal + # in hour boundaries this doesn't matter. + + next if (($year < ($start_time[YEAR]+1900)) or + ($year == ($start_time[YEAR]+1900) and $mon < ($start_time[MON]+1)) or + ($year == ($start_time[YEAR]+1900) and $mon == ($start_time[MON]+1) and $day < $start_time[MDAY]) or + ($year == ($start_time[YEAR]+1900) and $mon == ($start_time[MON]+1) and $day == $start_time[MDAY] and $hour < $start_time[HOUR])); + + last if (($year > ($end_time[YEAR]+1900)) or + ($year == ($end_time[YEAR]+1900) and $mon > ($end_time[MON]+1)) or + ($year == ($end_time[YEAR]+1900) and $mon == ($end_time[MON]+1) and $day > $end_time[MDAY]) or + ($year == ($end_time[YEAR]+1900) and $mon == ($end_time[MON]+1) and $day == $end_time[MDAY] and $hour > $end_time[HOUR])); + + # Is it an entry we're interested in? + + next unless ($line =~ m/Request/); + + # Process the entry + + if (my ($date, $time, $pid, $type, $destination, $client) = split / /, $line) + { + $destination =~ s#^http://|^https://##; + $destination =~ s/\/.*$//; + $destination =~ s/:\d+$//; + my $site = substr( $destination, 0, 69 ); + $site .= "..." if (length( $destination ) > 69); + + my @category = split /\//, $type; + + my ($address, $name) = split "/", $client; + + $this->set_host_name( $address, $name ) unless ($address eq $name); + + $info{'client'}{$address}++; + $info{'destination'}{"$site||$category[1]"}++; + $info{'count'}++; + } + } + + close IN; + } + } + + $this->cache( 'urlfilter', \%info ); + + return \%info; +} + + +#------------------------------------------------------------------------------ +# sub clients( this, min_count ) +# +# Output information on the systems trying to access forbidden destinations. +# +# Parameters: +# this message object +# min_count don't output information on clients accessing less than this +# number of destinations +#------------------------------------------------------------------------------ + +sub clients( $$ ) +{ + my ($self, $min_count) = @_; + my @table; + + use Sort::Naturally; + + push @table, [ $Lang::tr{'urlfilter client'}, $Lang::tr{'count'} ]; + + my $stats = get_log( $self ); + + foreach my $client (sort { $$stats{'client'}{$b} <=> $$stats{'client'}{$a} } keys %{ $$stats{'client'} } ) + { + my $count = $$stats{'client'}{$client}; + last if ($count < $min_count); + + my $host = $self->lookup_ip_address( $client ); + + $client .= "\n$host" if ($host); + + push @table, [ $client, $count ]; + } + + if (@table > 1) + { + $self->add_table( @table ); + + return 1; + } + + return 0; +} + + +#------------------------------------------------------------------------------ +# sub destinations( this, min_count ) +# +# Output information on the forbidden destinations being accessed. +# +# Parameters: +# this message object +# min_count don't output information on destinations accessed less than this +# number of times. +#------------------------------------------------------------------------------ + +sub destinations( $$ ) +{ + my ($self, $min_count) = @_; + my @table; + + use Sort::Naturally; + + push @table, [ $Lang::tr{'destination'}, $Lang::tr{'urlfilter category'}, $Lang::tr{'count'} ]; + + my $stats = get_log( $self ); + + foreach my $key (sort { $$stats{'destination'}{$b} <=> $$stats{'destination'}{$a} } keys %{ $$stats{'destination'} } ) + { + my $count = $$stats{'destination'}{$key}; + last if ($count < $min_count); + + my ($destination, $category) = split /\|\|/, $key; + + push @table, [ $destination, $category, $count ]; + } + + if (@table > 1) + { + $self->add_table( @table ); + + return 1; + } + + return 0; +} + +1; 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, "

$Lang::tr{'statusmail schedule name'}$Lang::tr{'statusmail action'}
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
\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; From patchwork Sat Apr 6 04:29:37 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tim FitzGeorge X-Patchwork-Id: 2180 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 48079861F37 for ; Fri, 5 Apr 2019 18:31:05 +0100 (BST) Received: from mail01.i.ipfire.org (localhost [IPv6:::1]) by mail01.ipfire.org (Postfix) with ESMTP id 44bRgD5Fd5z5Lcxs; Fri, 5 Apr 2019 18:31:04 +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 44bRft12Ymz5LcyC for ; Fri, 5 Apr 2019 18:30:46 +0100 (BST) Received: from [31.127.205.161] (helo=aragorn.tfitzgeorge.me.uk) by smtp.hosts.co.uk with esmtpa (Exim) (envelope-from ) id 1hCSfq-00006W-6R; Fri, 05 Apr 2019 18:30:45 +0100 From: Tim FitzGeorge To: development@lists.ipfire.org Subject: [PATCH 09/12] statusmail: Other plugins Date: Fri, 5 Apr 2019 18:29:37 +0100 Message-Id: <20190405172940.13168-10-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" Note that the graphs plugin requires a change to the existing graphs.pl to allow for arbitary time periods. Signed-off-by: Tim FitzGeorge --- src/statusmail/plugins/graphs.pm | 697 +++++++++++++++++++++++++ src/statusmail/plugins/hardware_media_space.pm | 154 ++++++ src/statusmail/plugins/network_firewall.pm | 357 +++++++++++++ 3 files changed, 1208 insertions(+) create mode 100644 src/statusmail/plugins/graphs.pm create mode 100644 src/statusmail/plugins/hardware_media_space.pm create mode 100644 src/statusmail/plugins/network_firewall.pm diff --git a/src/statusmail/plugins/graphs.pm b/src/statusmail/plugins/graphs.pm new file mode 100644 index 000000000..5b72c6e1a --- /dev/null +++ b/src/statusmail/plugins/graphs.pm @@ -0,0 +1,697 @@ +#!/usr/bin/perl + +############################################################################ +# # +# Send log and status emails for IPFire # +# # +# This is free software; you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation; either version 3 of the License, or # +# (at your option) any later version. # +# # +# This is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with IPFire; if not, write to the Free Software # +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # +# # +# Copyright (C) 2018 - 2019 The IPFire Team # +# # +############################################################################ + +use strict; +use warnings; + +require "${General::swroot}/lang.pl"; +require "${General::swroot}/graphs.pl"; + +package Graphs; + +############################################################################ +# Function prototypes +############################################################################ + +sub add_graph( $$$$@ ); + +############################################################################ +# BEGIN Block +# +# Register the graphs available in this file. +# +# Note that some graphs are only available under certain circumstances, so +# it's necessary to check the circumstances apply. +############################################################################ + +sub BEGIN +{ + my %netsettings; + my %mainsettings; + + &General::readhash("${General::swroot}/ethernet/settings", \%netsettings); + &General::readhash("${General::swroot}/main/settings", \%mainsettings); + + my $config_type = $netsettings{'CONFIG_TYPE'}; + + my %common_options = ( 'section' => $Lang::tr{'graph'}, + 'format' => 'html' ); + + #---------------------------------------------------------------------------- + # Network + + if ($netsettings{'RED_TYPE'} ne 'PPPOE') + { + if ($netsettings{'RED_DEV'} ne $netsettings{'GREEN_DEV'}) + { + if ($netsettings{'RED_DEV'} eq 'red0') + { + main::add_mail_item( %common_options, + 'ident' => 'graph-network-red0', + 'subsection' => $Lang::tr{'interfaces'}, + 'item' => 'red0', + 'function' => \&red0 ); + } + else + { + main::add_mail_item( %common_options, + 'ident' => 'graph-network-ppp0', + 'subsection' => $Lang::tr{'interfaces'}, + 'item' => 'ppp0', + 'function' => \&ppp0 ); + } + } + } + else + { + main::add_mail_item( %common_options, + 'ident' => 'graph-network-ppp0', + 'subsection' => $Lang::tr{'interfaces'}, + 'item' => 'ppp0', + 'function' => \&ppp0 ); + } + + main::add_mail_item( %common_options, + 'ident' => 'graph-network-green0', + 'subsection' => $Lang::tr{'interfaces'}, + 'item' => 'green0', + 'function' => \&green0 ); + + if ($config_type == 3 or $config_type == 4) + { + # BLUE + main::add_mail_item( %common_options, + 'ident' => 'graph-network-blue0', + 'subsection' => $Lang::tr{'interfaces'}, + 'item' => 'blue0', + 'function' => \&blue0 ); + } + + if ($config_type == 2 or $config_type == 4) + { + # ORANGE + main::add_mail_item( %common_options, + 'ident' => 'graph-network-orange0', + 'subsection' => $Lang::tr{'interfaces'}, + 'item' => 'orange0', + 'function' => \&orange0 ); + } + + + if (-e "/var/log/rrd/collectd/localhost/interface/if_octets-ipsec0.rrd") + { + main::add_mail_item( %common_options, + 'ident' => 'graph-network-ipsec0', + 'subsection' => $Lang::tr{'network'}, + 'item' => 'ipsec0', + 'function' => \&ipsec0 ); + } + + if (-e "/var/log/rrd/collectd/localhost/interface/if_octets-tun0.rrd") + { + main::add_mail_item( %common_options, + 'ident' => 'graph-network-tun0', + 'subsection' => $Lang::tr{'network'}, + 'item' => 'tun0', + 'function' => \&tun0 ); + } + + main::add_mail_item( %common_options, + 'ident' => 'graph-network-fwhits', + 'subsection' => $Lang::tr{'network'}, + 'item' => $Lang::tr{'firewallhits'}, + 'function' => \&fw_hits ); + + #---------------------------------------------------------------------------- + # System + + main::add_mail_item( %common_options, + 'ident' => 'graph-system-cpu-usage', + 'subsection' => $Lang::tr{'system'}, + 'item' => "CPU $Lang::tr{'graph'}", + 'function' => \&cpu_usage ); + + main::add_mail_item( %common_options, + 'ident' => 'graph-system-cpu-load', + 'subsection' => $Lang::tr{'system'}, + 'item' => "Load $Lang::tr{'graph'}", + 'function' => \&cpu_load ); + + if ( -e "$mainsettings{'RRDLOG'}/collectd/localhost/cpufreq/cpufreq-0.rrd") + { + main::add_mail_item( %common_options, + 'ident' => 'graph-system-cpu-frequency', + 'subsection' => $Lang::tr{'system'}, + 'item' => "CPU $Lang::tr{'frequency'}", + 'function' => \&cpu_freq ); + } + + main::add_mail_item( %common_options, + 'ident' => 'graph-system-entropy', + 'subsection' => $Lang::tr{'system'}, + 'item' => $Lang::tr{'entropy'}, + 'function' => \&entropy ); + + #---------------------------------------------------------------------------- + # Hardware + + main::add_mail_item( %common_options, + 'ident' => 'graph-hardware-cpu-load', + 'subsection' => $Lang::tr{'hardware graphs'}, + 'item' => "Load $Lang::tr{'graph'}", + 'function' => \&cpu_load ); + + if ( `ls $mainsettings{'RRDLOG'}/collectd/localhost/thermal-thermal_zone* 2>/dev/null` ) + { + main::add_mail_item( %common_options, + 'ident' => 'graph-hardware-acpi-zone-temp', + 'subsection' => $Lang::tr{'hardware graphs'}, + 'item' => "ACPI Thermal-Zone Temp", + 'function' => \&therm ); + } + + if ( `ls $mainsettings{'RRDLOG'}/collectd/localhost/sensors-*/temperature-* 2>/dev/null` ) + { + main::add_mail_item( %common_options, + 'ident' => 'graph-hardware-temp', + 'subsection' => $Lang::tr{'hardware graphs'}, + 'item' => "hwtemp", + 'function' => \&hwtemp ); + } + + if ( `ls $mainsettings{'RRDLOG'}/collectd/localhost/sensors-*/fanspeed-* 2>/dev/null` ) + { + main::add_mail_item( %common_options, + 'ident' => 'graph-hardware-fan', + 'subsection' => $Lang::tr{'hardware graphs'}, + 'item' => "hwfan", + 'function' => \&hwfan ); + } + + if ( `ls $mainsettings{'RRDLOG'}/collectd/localhost/sensors-*/voltage-* 2>/dev/null` ) + { + main::add_mail_item( %common_options, + 'ident' => 'graph-hardware-volt', + 'subsection' => $Lang::tr{'hardware graphs'}, + 'item' => "hwvolt", + 'function' => \&hwvolt ); + } + + #---------------------------------------------------------------------------- + # Memory + + main::add_mail_item( %common_options, + 'ident' => 'graph-memory-memory', + 'subsection' => $Lang::tr{'memory'}, + 'item' => $Lang::tr{'memory'}, + 'function' => \&memory ); + + main::add_mail_item( %common_options, + 'ident' => 'graph-memory-swap', + 'subsection' => $Lang::tr{'memory'}, + 'item' => $Lang::tr{'swap'}, + 'function' => \&swap ); + + #---------------------------------------------------------------------------- + # Disks + + foreach my $path (glob '/var/log/rrd/collectd/localhost/disk*') + { + my ($name) = $path =~ m/disk\-(\w+)/; + + main::add_mail_item( %common_options, + 'ident' => "graph-disk-access-$name", + 'subsection' => $Lang::tr{'statusmail disk access'}, + 'item' => $name, + 'function' => sub { my ($this) = @_; diskaccess( $this, $name ); } ); + + main::add_mail_item( %common_options, + 'ident' => "graph-disk-temp-$name", + 'subsection' => $Lang::tr{'statusmail disk temperature'}, + 'item' => $name, + 'function' => sub { my ($this) = @_; disktemp( $this, $name ); } ); + } + +# Other graphs that aren't available. +# updatepinggraph( host, period ) : netother.cgi +# updateprocessescpugraph( period ) +# updateprocessesmemorygraph( period ) +# updateqosgraph( device, period ) red0 | ppp0 | imq0 : qos.cgi +# updatevpngraph( interface, period ) : netovpnrw.cgi +# updatevpnn2ngraph( interface, period ) : netovpnsrv.cgi +# updatewirelessgraph( interface, period ) +} + +############################################################################ +# Code +############################################################################ + +#------------------------------------------------------------------------------ +# sub add_graph( object, function, name, alternate[, params...] ) +# +# Adds a graph to the mail message. This runs a sub-process to capture the +# output from running the standard WUI's graphing function, which is sent to +# stdout. +# +# Parameters: +# this message object +# function function producing graph +# name name of graph file +# alternate alternate text for image +# params parameters to be passed to graph function +#------------------------------------------------------------------------------ + +sub add_graph( $$$$@ ) +{ + my ($this, $function, $name, $alternate, @params) = @_; + + my $from_child; + + my $pid = open( $from_child, "-|" ); + + if ($pid) + { # parent + binmode $from_child; + + $this->add_image( fh => $from_child, + alt => $alternate, + type => 'image/png', + name => $name ); + + waitpid( $pid, 0 ); + close $from_child; + } + else + { # child + binmode( STDOUT ); + + my $period = $this->get_period(); + + &$function( @params, $period ); + + exit; + } +} + + +#------------------------------------------------------------------------------ +# sub ppp0( this ) +# +# Adds a graph of the ppp0 interface throughput +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub ppp0( $ ) +{ + my ($this) = @_; + + add_graph( $this, \&Graphs::updateifgraph, 'ppp0_if.png', 'ppp0 interface throughput', 'ppp0' ); + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub red0( this ) +# +# Adds a graph of the red0 interface throughput +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub red0( $ ) +{ + my ($this) = @_; + + add_graph( $this, \&Graphs::updateifgraph, 'red0_if.png', 'red0 interface throughput', 'red0' ); + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub green0( this ) +# +# Adds a graph of the green0 interface throughput +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub green0( $ ) +{ + my ($this) = @_; + + add_graph( $this, \&Graphs::updateifgraph, 'green0_if.png', 'green0 interface throughput', 'green0' ); + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub blue0( this ) +# +# Adds a graph of the blue0 interface throughput +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub blue0( $ ) +{ + my ($this) = @_; + + add_graph( $this, \&Graphs::updateifgraph, 'blue0_if.png', 'blue0 interface throughput', 'blue0' ); + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub orange0( this ) +# +# Adds a graph of the orange0 interface throughput +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub orange0( $ ) +{ + my ($this) = @_; + + add_graph( $this, \&Graphs::updateifgraph, 'orange0_if.png', 'orange0 interface throughput', 'orange0' ); + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub ipsec0( this ) +# +# Adds a graph of the ipsec0 interface +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub ipsec0( $ ) +{ + my ($this) = @_; + + add_graph( $this, \&Graphs::updateifgraph, 'ipsec0_if.png', 'ipsec0 interface throughput', 'ipsec0' ); + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub tun0( this ) +# +# Adds a graph of the tun0 interface +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub tun0( $ ) +{ + my ($this) = @_; + + add_graph( $this, \&Graphs::updateifgraph, 'tun0_if.png', 'tun0 interface throughput', 'tun0' ); + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub cpu_usage( this ) +# +# Adds a graph of the CPU usage +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub cpu_usage( $ ) +{ + my ($this) = @_; + + add_graph( $this, \&Graphs::updatecpugraph, 'cpu_usage.png', "CPU $Lang::tr{'graph'}" ); + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub cpu_freq( this ) +# +# Adds a graph of the CPU frequency +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub cpu_freq( $ ) +{ + my ($this) = @_; + + add_graph( $this, \&Graphs::updatecpufreqgraph, 'cpu_freq.png', "CPU $Lang::tr{'frequency'}" ); + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub cpu_load( this ) +# +# Adds a graph of the CPU load +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub cpu_load( $$ ) +{ + my ($this) = @_; + + add_graph( $this, \&Graphs::updateloadgraph,, 'cpu_load.png', "Load $Lang::tr{'graph'}" ); + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub fw_hits( this ) +# +# Adds a graph of the Firewall hits +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub fw_hits( $ ) +{ + my ($this) = @_; + + add_graph( $this, \&Graphs::updatefwhitsgraph, 'fw_hits.png', $Lang::tr{'firewallhits'} ); + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub therm( this ) +# +# Adds a graph of the ACPI Thermal zone temperatures +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub therm( $ ) +{ + my ($this) = @_; + + add_graph( $this, \&Graphs::updatethermaltempgraph, 'therm.png', "ACPI Thermal-Zone Temp" ); + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub hwtemp( this ) +# +# Adds a graph of the Hardware Temperatures +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub hwtemp( $ ) +{ + my ($this) = @_; + + add_graph( $this, \&Graphs::updatehwtempgraph, 'hw_temp.png', 'hwtemp' ); + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub hwfan( this ) +# +# Adds a graph of the Fan Speeds +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub hwfan( $ ) +{ + my ($this) = @_; + + add_graph( $this, \&Graphs::updatehwfangraph, 'hw_fan.png', 'hwfan' ); + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub hwvolt( this ) +# +# Adds a graph of the Hardware voltages +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub hwvolt( $ ) +{ + my ($this) = @_; + + add_graph( $this, \&Graphs::updatehwvoltgraph, 'hw_volt.png', 'hw volt' ); + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub entropy( this ) +# +# Adds a graph of the Entropy +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub entropy( $ ) +{ + my ($this) = @_; + + add_graph( $this, \&Graphs::updateentropygraph, 'entropy.png', $Lang::tr{'entropy'} ); + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub memory( this ) +# +# Adds a graph of the memory usage +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub memory( $ ) +{ + my ($this) = @_; + + add_graph( $this, \&Graphs::updatememorygraph, 'memory.png', $Lang::tr{'memory'} ); + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub swap( this ) +# +# Adds a graph of the swapfile usage +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + +sub swap( $ ) +{ + my ($this) = @_; + + add_graph( $this, \&Graphs::updateswapgraph, 'swap.png', $Lang::tr{'swap'} ); + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub diskaccess( this, name ) +# +# Adds a graph of the disk access rate +# +# Parameters: +# this message object +# name disk name +#------------------------------------------------------------------------------ + +sub diskaccess( $$ ) +{ + my ($this, $name) = @_; + + add_graph( $this, \&Graphs::updatediskgraph, "disk_access_$name.png", $name, $name ); + + return 1; +} + + +#------------------------------------------------------------------------------ +# sub updatehddgraph( this, name ) +# +# Adds a graph of the disk temperature +# +# Parameters: +# this message object +# name disk name +#------------------------------------------------------------------------------ + +sub disktemp( $$ ) +{ + my ($this, $name) = @_; + + add_graph( $this, \&Graphs::updatehddgraph, "disk_temp_$name.png", $name, $name ); + + return 1; +} diff --git a/src/statusmail/plugins/hardware_media_space.pm b/src/statusmail/plugins/hardware_media_space.pm new file mode 100644 index 000000000..ce3db2def --- /dev/null +++ b/src/statusmail/plugins/hardware_media_space.pm @@ -0,0 +1,154 @@ +#!/usr/bin/perl + +############################################################################ +# # +# Send log and status emails for IPFire # +# # +# This is free software; you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation; either version 3 of the License, or # +# (at your option) any later version. # +# # +# This is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with IPFire; if not, write to the Free Software # +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # +# # +# Copyright (C) 2018 - 2019 The IPFire Team # +# # +############################################################################ + +use strict; +use warnings; + +require "${General::swroot}/lang.pl"; + +package Hardware_Media_Space; + +############################################################################ +# Function prototypes +############################################################################ + +sub space( $$ ); +sub inodes( $$ ); + +############################################################################ +# BEGIN Block +# +# Register the log items available in this file +############################################################################ + +sub BEGIN +{ + main::add_mail_item( 'ident' => 'hardware-media-space', + 'section' => $Lang::tr{'statusmail hardware'}, + 'subsection' => $Lang::tr{'media'}, + 'item' => $Lang::tr{'disk usage'}, + 'function' => \&space, + 'option' => { 'type' => 'integer', + 'name' => $Lang::tr{'statusmail max free percent'}, + 'min' => 0, + 'max' => 100 } ); + + main::add_mail_item( 'ident' => 'hardware-media-inodes', + 'section' => $Lang::tr{'statusmail hardware'}, + 'subsection' => $Lang::tr{'media'}, + 'item' => 'inodes', + 'function' => \&inodes, + 'option' => { 'type' => 'integer', + 'name' => $Lang::tr{'statusmail max free percent'}, + 'min' => 0, + 'max' => 100 } ); +} + +############################################################################ +# Code +############################################################################ + +#------------------------------------------------------------------------------ +# sub space( this, min_percent ) +# +# Adds the disk usage in terms of space used. +# +# Parameters: +# this message object +# min_percent Only display information if this amount of space or less is +# free +#------------------------------------------------------------------------------ + +sub space( $$ ) +{ + my $message = shift; + my $min_percent = 100 - shift; + my @lines; + + # Get the process information + + foreach my $line (`df -BM`) + { + my @fields = split /\s+/, $line, 6; + if ($fields[4] =~ m/\d+\%/) + { + my ($percent) = $fields[4] =~ m/(\d+)\%/; + next if ($percent <= $min_percent); + } + push @lines, [ @fields ]; + } + + if (@lines > 1) + { + $message->add_table( @lines ); + + return 1; + } + + return 0; +} + + +#------------------------------------------------------------------------------ +# sub inodes( this, min_percent ) +# +# Adds the disk usage in terms of inodes used. +# +# Parameters: +# this message object +# min_percent Only display information if this number of inodes or less is +# free +#------------------------------------------------------------------------------ + +sub inodes( $$ ) +{ + my $message = shift; + my $min_percent = 100 - shift; + my @lines; + + # Get the process information + + foreach my $line (`df -i`) + { + my @fields = split /\s+/, $line, 6; + next if ($fields[1] == 0); + if ($fields[4] =~ m/\d+\%/) + { + my ($percent) = $fields[4] =~ m/(\d+)\%/; + next if ($percent <= $min_percent); + } + push @lines, [ @fields ]; + } + + if (@lines > 1) + { + $message->add_table( @lines ); + + return 1; + } + + return 0; +} + +1; diff --git a/src/statusmail/plugins/network_firewall.pm b/src/statusmail/plugins/network_firewall.pm new file mode 100644 index 000000000..1abe4e482 --- /dev/null +++ b/src/statusmail/plugins/network_firewall.pm @@ -0,0 +1,357 @@ +#!/usr/bin/perl + +############################################################################ +# # +# Send log and status emails for IPFire # +# # +# This is free software; you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation; either version 3 of the License, or # +# (at your option) any later version. # +# # +# This is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with IPFire; if not, write to the Free Software # +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # +# # +# Copyright (C) 2018 - 2019 The IPFire Team # +# # +############################################################################ + +require "${General::swroot}/lang.pl"; + +use strict; +use warnings; + +package Network_Firewall; + +use Time::Local; + +require "${General::swroot}/geoip-functions.pl"; + +############################################################################ +# BEGIN Block +# +# Register the log items available in this file +############################################################################ + +sub BEGIN +{ + main::add_mail_item( 'ident' => 'network-firewall-ipaddresses', + 'section' => $Lang::tr{'network'}, + 'subsection' => $Lang::tr{'firewall'}, + 'item' => $Lang::tr{'ip address'}, + 'function' => \&addresses, + 'option' => { 'type' => 'integer', + 'name' => $Lang::tr{'statusmail firewall min count'}, + 'min' => 1, + 'max' => 1000 } ); + + main::add_mail_item( 'ident' => 'network-firewall-ports', + 'section' => $Lang::tr{'network'}, + 'subsection' => $Lang::tr{'firewall'}, + 'item' => $Lang::tr{port}, + 'function' => \&ports, + 'option' => { 'type' => 'integer', + 'name' => $Lang::tr{'statusmail firewall min count'}, + 'min' => 1, + 'max' => 1000 } ); + + main::add_mail_item( 'ident' => 'network-firewall-countries', + 'section' => $Lang::tr{'network'}, + 'subsection' => $Lang::tr{'firewall'}, + 'item' => $Lang::tr{country}, + 'function' => \&countries, + 'option' => { 'type' => 'integer', + 'name' => $Lang::tr{'statusmail firewall min count'}, + 'min' => 1, + 'max' => 1000 } ); + + main::add_mail_item( 'ident' => 'network-firewall-reason', + 'section' => $Lang::tr{'network'}, + 'subsection' => $Lang::tr{'firewall'}, + 'item' => $Lang::tr{'statusmail firewall reason'}, + 'function' => \&reasons, + 'option' => { 'type' => 'integer', + 'name' => $Lang::tr{'statusmail firewall min count'}, + 'min' => 1, + 'max' => 1000 } ); +} + + +############################################################################ +# Functions +############################################################################ + +sub get_log( $ ); +sub addresses( $$ ); + +#------------------------------------------------------------------------------ +# sub get_log( this ) +# +# Gets information on blocked packets from the system log and caches it. +# +# Parameters: +# this message object +# +# Returns: +# reference to hash of information +#------------------------------------------------------------------------------ + +sub get_log( $ ) +{ + my ($this, $name) = @_; + + my $data = $this->cache( 'network-firewall' ); + + return $data if (defined $data); + + my %info; + my $line; + + while ($line = $this->get_message_log_line) + { + next unless ($line); + next unless ($line =~ m/kernel: DROP/); + + my ($time, $rule, $interface, $src_addrs, $dst_port) = + $line =~ m/(\w+\s+\d+\s+\d+:\d+:\d+).*DROP_(\w+?)\s*IN=(\w+).*SRC=(\d+\.\d+\.\d+\.\d+).*(?:DPT=(\d*))/; +# mmm dd hh:mm:dd ipfire kernel: DROP_SPAMHAUS_EDROPIN=ppp0 OUT= MAC= SRC=999.999.999.999 DST=888.888.888.888 LEN=40 TOS=0x00 PREC=0x00 TTL=248 ID=35549 PROTO=TCP SPT=47851 DPT=28672 WINDOW=1024 RES=0x00 SYN URGP=0 MARK=0xd2 + + next unless ($src_addrs); + + my $country = GeoIP::lookup( $src_addrs ) || $src_addrs; + + $info{'by_address'}{$src_addrs}{'count'}++; + $info{'by_address'}{$src_addrs}{'first'} = $time unless ($info{'by_address'}{$src_addrs}{'first'}); + $info{'by_address'}{$src_addrs}{'last'} = $time; + + if ($dst_port) + { + $info{'by_port'}{$dst_port}{'count'}++ ; + $info{'by_port'}{$dst_port}{'first'} = $time unless ($info{'by_port'}{$dst_port}{'first'}); + $info{'by_port'}{$dst_port}{'last'} = $time; + } + + if ($country) + { + $info{'by_country'}{$country}{'count'}++; + $info{'by_country'}{$country}{'first'} = $time unless ($info{'by_country'}{$country}{'first'}); + $info{'by_country'}{$country}{'last'} = $time; + } + + $info{'by_rule'}{$rule}{'count'}++; + $info{'by_rule'}{$rule}{'first'} = $time unless ($info{'by_rule'}{$rule}{'first'}); + $info{'by_rule'}{$rule}{'last'} = $time; + + $info{'total'}++; + }; + + $this->cache( 'network-firewall', \%info ); + + return \%info; +} + + +#------------------------------------------------------------------------------ +# sub addresses( this, min_count ) +# +# Output information on blocked addresses. +# +# Parameters: +# this message object +# min_count only output blocked addresses occurring at least this number of +# times +#------------------------------------------------------------------------------ + +sub addresses( $$ ) +{ + my ($self, $min_count) = @_; + my @table; + + use Sort::Naturally; + + push @table, ['|', '|', '|', '|', '|', '|']; + push @table, [ $Lang::tr{'ip address'}, $Lang::tr{'country'}, $Lang::tr{'count'}, $Lang::tr{'percentage'}, $Lang::tr{'first'}, $Lang::tr{'last'} ]; + + my $stats = get_log( $self ); + + foreach my $address (sort { $$stats{'by_address'}{$b}{'count'} <=> $$stats{'by_address'}{$a}{'count'} || + ncmp( $b, $a ) } keys %{ $$stats{'by_address'} } ) + { + my $count = $$stats{'by_address'}{$address}{'count'}; + my $country = GeoIP::lookup( $address ); + my $first = $$stats{'by_address'}{$address}{'first'}; + my $last = $$stats{'by_address'}{$address}{'last'}; + my $percent = int( 100 * $count / $$stats{'total'} + 0.5); + + last if ($count < $min_count); + + my $name = $self->lookup_ip_address( $address ); + + $address = "$address\n$name" if ($name); + + if ($country) + { + $country = GeoIP::get_full_country_name( $country) || $address; + } + else + { + $country = $Lang::tr{'unknown'}; + } + + push @table, [ $address, $country, $count, $percent, $first, $last ]; + + last if (@table > $self->get_max_lines_per_item + 2) + } + + if (@table > 2) + { + $self->add_table( @table ); + + return 1; + } + + return 0; +} + + +#------------------------------------------------------------------------------ +# sub ports( this, min_count ) +# +# Output information on blocked ports. +# +# Parameters: +# this message object +# min_count only output blocked ports occurring at least this number of +# times +#------------------------------------------------------------------------------ + +sub ports( $$ ) +{ + my ($self, $min_count) = @_; + my @table; + + push @table, ['|', '|', '|', '|', '|']; + push @table, [ $Lang::tr{'port'}, $Lang::tr{'count'}, $Lang::tr{'percentage'}, $Lang::tr{'first'}, $Lang::tr{'last'} ]; + + my $stats = get_log( $self ); + + foreach my $port (sort { $$stats{'by_port'}{$b}{'count'} <=> $$stats{'by_port'}{$a}{'count'} || + ncmp( $b, $a ) } keys %{ $$stats{'by_port'} } ) + { + my $count = $$stats{'by_port'}{$port}{'count'}; + my $first = $$stats{'by_port'}{$port}{'first'}; + my $last = $$stats{'by_port'}{$port}{'last'}; + my $percent = int( 100 * $count / $$stats{'total'} + 0.5); + + last if ($count < $min_count); + + push @table, [ $port, $count, $percent, $first, $last ]; + } + + if (@table > 2) + { + $self->add_table( @table ); + + return 1; + } + + return 0; +} + + +#------------------------------------------------------------------------------ +# sub countries( this, min_count ) +# +# Output information on blocked countries. +# +# Parameters: +# this message object +# min_count only output blocked countries occurring at least this number of +# times +#------------------------------------------------------------------------------ + +sub countries( $$ ) +{ + my ($self, $min_count) = @_; + my @table; + + push @table, ['<', '|', '|', '|', '|']; + push @table, [ $Lang::tr{'country'}, $Lang::tr{'count'}, $Lang::tr{'percentage'}, $Lang::tr{'first'}, $Lang::tr{'last'} ]; + + my $stats = get_log( $self ); + + foreach my $country (sort { $$stats{'by_country'}{$b}{'count'} <=> $$stats{'by_country'}{$a}{'count'} } keys %{ $$stats{'by_country'} } ) + { + my $count = $$stats{'by_country'}{$country}{'count'}; + my $first = $$stats{'by_country'}{$country}{'first'}; + my $last = $$stats{'by_country'}{$country}{'last'}; + my $percent = int( 100 * $count / $$stats{'total'} + 0.5); + + last if ($count < $min_count); + + my $full_country = GeoIP::get_full_country_name( $country) || $country; + + push @table, [ $full_country, $count, $percent, $first, $last ]; + } + + if (@table > 2) + { + $self->add_table( @table ); + + return 1; + } + + return 0; +} + + +#------------------------------------------------------------------------------ +# sub reasons( this, min_count ) +# +# Output information on blocked reasons (the IPtable blocking the packet). +# +# Parameters: +# this message object +# min_count only output blocked reasons occurring at least this number of +# times +#------------------------------------------------------------------------------ + +sub reasons( $$ ) +{ + my ($self, $min_count) = @_; + my @table; + + push @table, ['<', '|', '|', '|', '|']; + push @table, [ $Lang::tr{'statusmail firewall reason'}, $Lang::tr{'count'}, $Lang::tr{'percentage'}, $Lang::tr{'first'}, $Lang::tr{'last'} ]; + + my $stats = get_log( $self ); + + foreach my $reason (sort { $$stats{'by_rule'}{$b}{'count'} <=> $$stats{'by_rule'}{$a}{'count'} } keys %{ $$stats{'by_rule'} } ) + { + my $count = $$stats{'by_rule'}{$reason}{'count'}; + my $first = $$stats{'by_rule'}{$reason}{'first'}; + my $last = $$stats{'by_rule'}{$reason}{'last'}; + my $percent = int( 100 * $count / $$stats{'total'} + 0.5); + + last if ($count < $min_count); + + push @table, [ $reason, $count, $percent, $first, $last ]; + } + + if (@table > 2) + { + $self->add_table( @table ); + + return 1; + } + + return 0; +} + +1; From patchwork Sat Apr 6 04:29:38 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Tim FitzGeorge X-Patchwork-Id: 2181 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 A2ADE861F37 for ; Fri, 5 Apr 2019 18:31:12 +0100 (BST) Received: from mail01.i.ipfire.org (localhost [IPv6:::1]) by mail01.ipfire.org (Postfix) with ESMTP id 44bRgN0PSpz5Lcxx; Fri, 5 Apr 2019 18:31:12 +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 44bRg83F9Sz5D0P7 for ; Fri, 5 Apr 2019 18:31:00 +0100 (BST) Received: from [31.127.205.161] (helo=aragorn.tfitzgeorge.me.uk) by smtp.hosts.co.uk with esmtpa (Exim) (envelope-from ) id 1hCSg6-0000Ix-3i; Fri, 05 Apr 2019 18:31:00 +0100 From: Tim FitzGeorge To: development@lists.ipfire.org Subject: [PATCH 10/12] statusmail: Graph infrastructure changes Date: Fri, 5 Apr 2019 18:29:38 +0100 Message-Id: <20190405172940.13168-11-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> MIME-Version: 1.0 X-Spamd-Result: default: False [-13.17 / 11.00]; ARC_NA(0.00)[]; RCVD_VIA_SMTP_AUTH(0.00)[]; RECEIVED_SPAMHAUS_PBL(0.00)[161.205.127.31.zen.spamhaus.org : 127.0.0.11]; 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.996,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%]; FROM_EQ_ENVFROM(0.00)[] 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" Modifies graphs.pl to expect the second parameter to include the number as well as the unit, that is 1hour instead of hour. makegraphbox is modified to pass the number. The change to entropy.cgi as because it passes the period as the first parameter instead of the second. Signed-off-by: Tim FitzGeorge --- config/cfgroot/graphs.pl | 274 ++++++++++++++++++++++++++++++++++------------- html/cgi-bin/entropy.cgi | 2 +- langs/de/cgi-bin/de.pl | 1 + langs/es/cgi-bin/es.pl | 1 + langs/it/cgi-bin/it.pl | 1 + langs/nl/cgi-bin/nl.pl | 1 + langs/pl/cgi-bin/pl.pl | 1 + langs/tr/cgi-bin/tr.pl | 1 + 8 files changed, 207 insertions(+), 75 deletions(-) diff --git a/config/cfgroot/graphs.pl b/config/cfgroot/graphs.pl index 1bed49fa3..25e62b4ac 100644 --- a/config/cfgroot/graphs.pl +++ b/config/cfgroot/graphs.pl @@ -3,7 +3,6 @@ ############################################################################### # # # IPFire.org - A linux based firewall # -# Copyright (C) 2005-2010 IPFire Team # +# Copyright (C) 2005-2019 IPFire Team # # # # This program is free software: you can redistribute it and/or modify # # it under the terms of the GNU General Public License as published by # @@ -102,17 +103,17 @@ foreach (@sensorsdir){ sub makegraphbox { print "
"; - print "".$Lang::tr{'hour'}.""; + print "".$Lang::tr{'hour'}.""; print " - "; - print "".$Lang::tr{'day'}.""; + print "".$Lang::tr{'day'}.""; print " - "; - print "".$Lang::tr{'week'}.""; + print "".$Lang::tr{'week'}.""; print " - "; - print "".$Lang::tr{'month'}.""; + print "".$Lang::tr{'month'}.""; print " - "; - print "".$Lang::tr{'year'}.""; + print "".$Lang::tr{'year'}.""; print "
"; - print ""; + print ""; } # Generate the CPU Graph for the current period of time for values given by @@ -120,16 +121,22 @@ sub makegraphbox { sub updatecpugraph { my $cpucount = `ls -dA $mainsettings{'RRDLOG'}/collectd/localhost/cpu-*/ 2>/dev/null | wc -l`; - my $period = $_[0]; + my ($number, $period) = $_[0] =~ m/(\d+)(\D+)s?/; + my $numstr = ' '; + if ($number > 1) + { + $period .= 's'; + $numstr = " $number "; + } my @command = ( @GRAPH_ARGS, "-", "--start", - "-1".$period, + "-".$number.$period, "-l 0", "-u 100", "-r", - "-t ".$Lang::tr{'cpu usage per'}." ".$Lang::tr{$period."-graph"}, + "-t ".$Lang::tr{'cpu usage per'}.$numstr.$Lang::tr{$period."-graph"}, "-v ".$Lang::tr{'percentage'}, "--color=SHADEA".$color{"color19"}, "--color=SHADEB".$color{"color19"}, @@ -140,7 +147,7 @@ sub updatecpugraph { "COMMENT:".sprintf("%15s",$Lang::tr{'minimal'}), "COMMENT:".sprintf("%15s",$Lang::tr{'current'})."\\j" ); - + my $nice = "CDEF:nice="; my $interrupt = "CDEF:interrupt="; my $steal = "CDEF:steal="; @@ -150,7 +157,7 @@ sub updatecpugraph { my $iowait = "CDEF:iowait="; my $irq = "CDEF:irq="; my $addstring = ""; - + for(my $i = 0; $i < $cpucount; $i++) { push(@command,"DEF:iowait".$i."=".$mainsettings{'RRDLOG'}."/collectd/localhost/cpu-".$i."/cpu-wait.rrd:value:AVERAGE" ,"DEF:nice".$i."=".$mainsettings{'RRDLOG'}."/collectd/localhost/cpu-".$i."/cpu-nice.rrd:value:AVERAGE" @@ -170,7 +177,7 @@ sub updatecpugraph { $iowait .= "iowait".$i.","; $irq .= "irq".$i.","; } - + for(my $i = 2; $i < $cpucount; $i++) { $addstring .= "+,"; } @@ -248,15 +255,21 @@ sub updatecpugraph { # Generate the Load Graph for the current period of time for values given by collecd sub updateloadgraph { - my $period = $_[0]; + my ($number, $period) = $_[0] =~ m/(\d+)(\D+)s?/; + my $numstr = ' '; + if ($number > 1) + { + $period .= 's'; + $numstr = " $number "; + } RRDs::graph( @GRAPH_ARGS, "-", "--start", - "-1".$period, + "-".$number.$period, "-l 0", "-r", - "-t Load Average ".$Lang::tr{'graph per'}." ".$Lang::tr{$period."-graph"}, + "-t Load Average ".$Lang::tr{'graph per'}.$numstr.$Lang::tr{$period."-graph"}, "-v ".$Lang::tr{'processes'}, "--color=SHADEA".$color{"color19"}, "--color=SHADEB".$color{"color19"}, @@ -280,16 +293,22 @@ sub updateloadgraph { # Generate the Memory Graph for the current period of time for values given by collecd sub updatememorygraph { - my $period = $_[0]; + my ($number, $period) = $_[0] =~ m/(\d+)(\D+)s?/; + my $numstr = ' '; + if ($number > 1) + { + $period .= 's'; + $numstr = " $number "; + } RRDs::graph( @GRAPH_ARGS, "-", "--start", - "-1".$period, + "-".$number.$period, "-l 0", "-u 100", "-r", - "-t ".$Lang::tr{'memory usage per'}." ".$Lang::tr{$period."-graph"}, + "-t ".$Lang::tr{'memory usage per'}.$numstr.$Lang::tr{$period."-graph"}, "-v ".$Lang::tr{'percentage'}, "--color=SHADEA".$color{"color19"}, "--color=SHADEB".$color{"color19"}, @@ -336,16 +355,22 @@ sub updatememorygraph { # Generate the Swap Graph for the current period of time for values given by collecd sub updateswapgraph { - my $period = $_[0]; + my ($number, $period) = $_[0] =~ m/(\d+)(\D+)s?/; + my $numstr = ' '; + if ($number > 1) + { + $period .= 's'; + $numstr = " $number "; + } RRDs::graph( @GRAPH_ARGS, "-", "--start", - "-1".$period, + "-".$number.$period, "-l 0", "-u 100", "-r", - "-t ".$Lang::tr{'swap usage per'}." ".$Lang::tr{$period."-graph"}, + "-t ".$Lang::tr{'swap usage per'}.$numstr.$Lang::tr{$period."-graph"}, "-v ".$Lang::tr{'percentage'}, "--color=SHADEA".$color{"color19"}, "--color=SHADEB".$color{"color19"}, @@ -386,17 +411,23 @@ sub updateswapgraph { sub updateprocessescpugraph { my @processesgraph = getprocesses(); - my $period = $_[0]; + my ($number, $period) = $_[0] =~ m/(\d+)(\D+)s?/; + my $numstr = ' '; + if ($number > 1) + { + $period .= 's'; + $numstr = " $number "; + } my $count="0"; my @command = ( @GRAPH_ARGS, "-", "--start", - "-1".$period, + "-".$number.$period, "-l 0", "-r", - "-t ".$Lang::tr{'processes'}." ".$Lang::tr{'graph per'}." ".$Lang::tr{$period."-graph"}, + "-t ".$Lang::tr{'processes'}." ".$Lang::tr{'graph per'}.$numstr.$Lang::tr{$period."-graph"}, "--color=SHADEA".$color{"color19"}, "--color=SHADEB".$color{"color19"}, "--color=BACK".$color{"color21"} @@ -433,17 +464,23 @@ sub updateprocessescpugraph { sub updateprocessesmemorygraph { my @processesgraph = getprocesses(); - my $period = $_[0]; + my ($number, $period) = $_[0] =~ m/(\d+)(\D+)s?/; + my $numstr = ' '; + if ($number > 1) + { + $period .= 's'; + $numstr = " $number "; + } my $count="0"; my @command = ( @GRAPH_ARGS, "-", "--start", - "-1".$period, + "-".$number.$period, "-l 0", "-r", - "-t ".$Lang::tr{'processes'}." ".$Lang::tr{'memory'}." ".$Lang::tr{'graph per'}." ".$Lang::tr{$period."-graph"}, + "-t ".$Lang::tr{'processes'}." ".$Lang::tr{'memory'}." ".$Lang::tr{'graph per'}.$numstr.$Lang::tr{$period."-graph"}, "-v ".$Lang::tr{'bytes'}, "--color=SHADEA".$color{"color19"}, "--color=SHADEB".$color{"color19"}, @@ -479,14 +516,20 @@ sub updateprocessesmemorygraph { sub updatediskgraph { my $disk = $_[0]; - my $period = $_[1]; + my ($number, $period) = $_[1] =~ m/(\d+)(\D+)s?/; + my $numstr = ' '; + if ($number > 1) + { + $period .= 's'; + $numstr = " $number "; + } RRDs::graph( @GRAPH_ARGS, "-", "--start", - "-1".$period, + "-".$number.$period, "-r", - "-t ".$disk." ".$Lang::tr{'disk access per'}." ".$Lang::tr{$period."-graph"}, + "-t ".$disk." ".$Lang::tr{'disk access per'}.$numstr.$Lang::tr{$period."-graph"}, "-v ".$Lang::tr{'bytes per second'}, "--color=SHADEA".$color{"color19"}, "--color=SHADEB".$color{"color19"}, @@ -523,14 +566,20 @@ sub updatediskgraph { sub updateifgraph { my $interface = $_[0]; - my $period = $_[1]; + my ($number, $period) = $_[1] =~ m/(\d+)(\D+)s?/; + my $numstr = ' '; + if ($number > 1) + { + $period .= 's'; + $numstr = " $number "; + } RRDs::graph( @GRAPH_ARGS, "-", "--start", - "-1".$period, + "-".$number.$period, "-r", - "-t ".$Lang::tr{'traffic on'}." ".$interface." ".$Lang::tr{'graph per'}." ".$Lang::tr{$period."-graph"}, + "-t ".$Lang::tr{'traffic on'}." ".$interface." ".$Lang::tr{'graph per'}.$numstr.$Lang::tr{$period."-graph"}, "-v ".$Lang::tr{'bytes per second'}, "--color=SHADEA".$color{"color19"}, "--color=SHADEB".$color{"color19"}, @@ -560,14 +609,20 @@ sub updateifgraph { sub updatevpngraph { my $interface = $_[0]; - my $period = $_[1]; + my ($number, $period) = $_[1] =~ m/(\d+)(\D+)s?/; + my $numstr = ' '; + if ($number > 1) + { + $period .= 's'; + $numstr = " $number "; + } RRDs::graph( @GRAPH_ARGS, "-", "--start", - "-1".$period, + "-".$number.$period, "-r", - "-t ".$Lang::tr{'traffic on'}." ".$interface." ".$Lang::tr{'graph per'}." ".$Lang::tr{$period."-graph"}, + "-t ".$Lang::tr{'traffic on'}." ".$interface." ".$Lang::tr{'graph per'}.$numstr.$Lang::tr{$period."-graph"}, "-v ".$Lang::tr{'bytes per second'}, "--color=SHADEA".$color{"color19"}, "--color=SHADEB".$color{"color19"}, @@ -597,14 +652,20 @@ sub updatevpngraph { sub updatevpnn2ngraph { my $interface = $_[0]; - my $period = $_[1]; + my ($number, $period) = $_[1] =~ m/(\d+)(\D+)s?/; + my $numstr = ' '; + if ($number > 1) + { + $period .= 's'; + $numstr = " $number "; + } RRDs::graph( @GRAPH_ARGS, "-", "--start", - "-1".$period, + "-".$number.$period, "-r", - "-t ".$Lang::tr{'traffic on'}." ".$interface." ".$Lang::tr{'graph per'}." ".$Lang::tr{$period."-graph"}, + "-t ".$Lang::tr{'traffic on'}." ".$interface." ".$Lang::tr{'graph per'}.$numstr.$Lang::tr{$period."-graph"}, "-v ".$Lang::tr{'bytes per second'}, "--color=SHADEA".$color{"color19"}, "--color=SHADEB".$color{"color19"}, @@ -661,14 +722,20 @@ sub updatevpnn2ngraph { # Generate the Firewall Graph for the current period of time for values given by collecd sub updatefwhitsgraph { - my $period = $_[0]; + my ($number, $period) = $_[0] =~ m/(\d+)(\D+)s?/; + my $numstr = ' '; + if ($number > 1) + { + $period .= 's'; + $numstr = " $number "; + } RRDs::graph( @GRAPH_ARGS, "-", "--start", - "-1".$period, + "-".$number.$period, "-r", - "-t ".$Lang::tr{'firewall hits per'}." ".$Lang::tr{$period."-graph"}, + "-t ".$Lang::tr{'firewall hits per'}.$numstr.$Lang::tr{$period."-graph"}, "-v ".$Lang::tr{'bytes per second'}, "--color=SHADEA".$color{"color19"}, "--color=SHADEB".$color{"color19"}, @@ -716,16 +783,22 @@ sub updatefwhitsgraph { # Generate the Line Quality Graph for the current period of time for values given by collecd sub updatepinggraph { - my $period = $_[1]; + my ($number, $period) = $_[1] =~ m/(\d+)(\D+)s?/; + my $numstr = ' '; + if ($number > 1) + { + $period .= 's'; + $numstr = " $number "; + } my $host = $_[0]; RRDs::graph( @GRAPH_ARGS, "-", "--start", - "-1".$period, + "-".$number.$period, "-l 0", "-r", - "-t ".$Lang::tr{'linkq'}." ".$host." ".$Lang::tr{'graph per'}." ".$Lang::tr{$period."-graph"}, + "-t ".$Lang::tr{'linkq'}." ".$host." ".$Lang::tr{'graph per'}.$numstr.$Lang::tr{$period."-graph"}, "-v ms", "--color=SHADEA".$color{"color19"}, "--color=SHADEB".$color{"color19"}, @@ -756,14 +829,20 @@ sub updatepinggraph { } sub updatewirelessgraph { - my $period = $_[1]; + my ($number, $period) = $_[1] =~ m/(\d+)(\D+)s?/; + my $numstr = ' '; + if ($number > 1) + { + $period .= 's'; + $numstr = " $number "; + } my $interface = $_[0]; RRDs::graph( @GRAPH_ARGS, "-", "--start", - "-1".$period, - "-t Wireless ".$interface." ".$Lang::tr{'graph per'}." ".$Lang::tr{$period."-graph"}, + "-".$number.$period, + "-t Wireless ".$interface." ".$Lang::tr{'graph per'}.$numstr.$Lang::tr{$period."-graph"}, "-v dBm", "--color=SHADEA".$color{"color19"}, "--color=SHADEB".$color{"color19"}, @@ -794,14 +873,20 @@ sub updatewirelessgraph { sub updatehddgraph { my $disk = $_[0]; - my $period = $_[1]; + my ($number, $period) = $_[1] =~ m/(\d+)(\D+)s?/; + my $numstr = ' '; + if ($number > 1) + { + $period .= 's'; + $numstr = " $number "; + } RRDs::graph( @GRAPH_ARGS, "-", "--start", - "-1".$period, + "-".$number.$period, "-r", - "-t ".$disk." ".$Lang::tr{'harddisk temperature'}." ".$Lang::tr{'graph per'}." ".$Lang::tr{$period."-graph"}, + "-t ".$disk." ".$Lang::tr{'harddisk temperature'}." ".$Lang::tr{'graph per'}.$numstr.$Lang::tr{$period."-graph"}, "-v Celsius", "--color=SHADEA".$color{"color19"}, "--color=SHADEB".$color{"color19"}, @@ -827,15 +912,21 @@ sub updatehddgraph { # Generate the Temp Graph for the current period of time for values given by collecd and lm_sensors sub updatehwtempgraph { - my $period = $_[0]; + my ($number, $period) = $_[0] =~ m/(\d+)(\D+)s?/; + my $numstr = ' '; + if ($number > 1) + { + $period .= 's'; + $numstr = " $number "; + } my @command = ( @GRAPH_ARGS, "-", "--start", - "-1".$period, + "-".$number.$period, "-r", - "-t ".$Lang::tr{'mbmon temp'}." ".$Lang::tr{'graph per'}." ".$Lang::tr{$period."-graph"}, + "-t ".$Lang::tr{'mbmon temp'}." ".$Lang::tr{'graph per'}.$numstr.$Lang::tr{$period."-graph"}, "--color=SHADEA".$color{"color19"}, "--color=SHADEB".$color{"color19"}, "--color=BACK".$color{"color21"}, @@ -874,15 +965,21 @@ sub updatehwtempgraph { # Generate the Fan Graph for the current period of time for values given by collecd and lm_sensors sub updatehwfangraph { - my $period = $_[0]; + my ($number, $period) = $_[0] =~ m/(\d+)(\D+)s?/; + my $numstr = ' '; + if ($number > 1) + { + $period .= 's'; + $numstr = " $number "; + } my @command = ( @GRAPH_ARGS, "-", "--start", - "-1".$period, + "-".$number.$period, "-r", - "-t ".$Lang::tr{'mbmon fan'}." ".$Lang::tr{'graph per'}." ".$Lang::tr{$period."-graph"}, + "-t ".$Lang::tr{'mbmon fan'}." ".$Lang::tr{'graph per'}.$numstr.$Lang::tr{$period."-graph"}, "--color=SHADEA".$color{"color19"}, "--color=SHADEB".$color{"color19"}, "--color=BACK".$color{"color21"}, @@ -921,15 +1018,21 @@ sub updatehwfangraph { # Generate the Voltage Graph for the current period of time for values given by collecd and lm_sensors sub updatehwvoltgraph { - my $period = $_[0]; + my ($number, $period) = $_[0] =~ m/(\d+)(\D+)s?/; + my $numstr = ' '; + if ($number > 1) + { + $period .= 's'; + $numstr = " $number "; + } my @command = ( @GRAPH_ARGS, "-", "--start", - "-1".$period, + "-".$number.$period, "-r", - "-t ".$Lang::tr{'mbmon volt'}." ".$Lang::tr{'graph per'}." ".$Lang::tr{$period."-graph"}, + "-t ".$Lang::tr{'mbmon volt'}." ".$Lang::tr{'graph per'}.$numstr.$Lang::tr{$period."-graph"}, "--color=SHADEA".$color{"color19"}, "--color=SHADEB".$color{"color19"}, "--color=BACK".$color{"color21"}, @@ -969,8 +1072,13 @@ sub updatehwvoltgraph { # Generate the QoS Graph for the current period of time sub updateqosgraph { - - my $period = $_[1]; + my ($number, $period) = $_[1] =~ m/(\d+)(\D+)s?/; + my $numstr = ' '; + if ($number > 1) + { + $period .= 's'; + $numstr = " $number "; + } my %qossettings = (); &General::readhash("${General::swroot}/qos/settings", \%qossettings); @@ -994,9 +1102,9 @@ sub updateqosgraph { @GRAPH_ARGS, "-", "--start", - "-1".$period, + "-".$number.$period, "-r", - "-t ".$Lang::tr{'Utilization on'}." (".$qossettings{'DEV'}.") ".$Lang::tr{'graph per'}." ".$Lang::tr{$period."-graph"}, + "-t ".$Lang::tr{'Utilization on'}." (".$qossettings{'DEV'}.") ".$Lang::tr{'graph per'}.$numstr.$Lang::tr{$period."-graph"}, "-v ".$Lang::tr{'bytes per second'}, "--color=SHADEA".$color{"color19"}, "--color=SHADEB".$color{"color19"}, @@ -1042,14 +1150,20 @@ sub updateqosgraph { sub updatecpufreqgraph { my $cpucount = `ls -dA $mainsettings{'RRDLOG'}/collectd/localhost/cpu-*/ 2>/dev/null | wc -l`; - my $period = $_[0]; + my ($number, $period) = $_[0] =~ m/(\d+)(\D+)s?/; + my $numstr = ' '; + if ($number > 1) + { + $period .= 's'; + $numstr = " $number "; + } my @command = ( @GRAPH_ARGS, "-", "--start", - "-1".$period, + "-".$number.$period, "-r", - "-t ".$Lang::tr{'cpu frequency per'}." ".$Lang::tr{$period."-graph"}, + "-t ".$Lang::tr{'cpu frequency per'}.$numstr.$Lang::tr{$period."-graph"}, "-v MHz", "--color=SHADEA".$color{"color19"}, "--color=SHADEB".$color{"color19"}, @@ -1081,14 +1195,20 @@ sub updatecpufreqgraph { sub updatethermaltempgraph { my $thermalcount = `ls -dA $mainsettings{'RRDLOG'}/collectd/localhost/thermal-thermal_zone* 2>/dev/null | wc -l`; - my $period = $_[0]; + my ($number, $period) = $_[0] =~ m/(\d+)(\D+)s?/; + my $numstr = ' '; + if ($number > 1) + { + $period .= 's'; + $numstr = " $number "; + } my @command = ( @GRAPH_ARGS, "-", "--start", - "-1".$period, + "-".$number.$period, "-r", - "-t "."ACPI Thermal-Zone Temperature"." - ".$Lang::tr{$period."-graph"}, + "-t "."ACPI Thermal-Zone Temperature"." -$numstr".$Lang::tr{$period."-graph"}, "-v Grad Celsius", "--color=SHADEA".$color{"color19"}, "--color=SHADEB".$color{"color19"}, @@ -1134,12 +1254,18 @@ sub getprocesses { } sub updateentropygraph { - my $period = $_[0]; - my @command = ( + my ($number, $period) = $_[0] =~ m/(\d+)(\D+)s?/; + my $numstr = ' '; + if ($number > 1) + { + $period .= 's'; + $numstr = " $number "; + } + my @command = ( @GRAPH_ARGS, "-", "--start", - "-1".$period, + "-".$number.$period, "-r", "--lower-limit","0", "-t $Lang::tr{'entropy'}", diff --git a/html/cgi-bin/entropy.cgi b/html/cgi-bin/entropy.cgi index d7a9ca5d8..f8045db5a 100644 --- a/html/cgi-bin/entropy.cgi +++ b/html/cgi-bin/entropy.cgi @@ -45,7 +45,7 @@ if ( $querry[0] ne~ "") { &Header::openbigbox('100%', 'left'); &Header::openbox('100%', 'center', $Lang::tr{'entropy'}); - &Graphs::makegraphbox("entropy.cgi", "day"); + &Graphs::makegraphbox("entropy.cgi", "entropy", "day"); &Header::closebox(); # Check for hardware support. diff --git a/langs/de/cgi-bin/de.pl b/langs/de/cgi-bin/de.pl index 88b34d23b..855d8d33f 100644 --- a/langs/de/cgi-bin/de.pl +++ b/langs/de/cgi-bin/de.pl @@ -1320,6 +1320,7 @@ 'hosts config changed' => 'Hosts-Konfiguration geändert', 'hour' => 'Stunde', 'hour-graph' => 'Stunde', +'hours-graph' => 'Stunden', 'hours' => 'Stunden', 'hours2' => 'Stunden', 'ibod for dual isdn only' => 'iBOD kann nur bei ISDN-Kanalbündelung genutzt werden.', diff --git a/langs/es/cgi-bin/es.pl b/langs/es/cgi-bin/es.pl index 23bc20727..cb0471f12 100644 --- a/langs/es/cgi-bin/es.pl +++ b/langs/es/cgi-bin/es.pl @@ -915,6 +915,7 @@ 'hosts config changed' => 'Configuración de hosts ha cambiado', 'hour' => 'Hora', 'hour-graph' => 'Hora', +'hours-graph' => 'Horas', 'hours' => 'horas', 'hours2' => 'Horas', 'ibod for dual isdn only' => 'iBPD sólo puede usarse con ISDN dual', diff --git a/langs/it/cgi-bin/it.pl b/langs/it/cgi-bin/it.pl index 6c9137c28..586ae882c 100644 --- a/langs/it/cgi-bin/it.pl +++ b/langs/it/cgi-bin/it.pl @@ -1217,6 +1217,7 @@ 'hosts config changed' => 'Hosts config changed', 'hour' => 'Ora', 'hour-graph' => 'Ora', +'hours-graph' => 'Ore', 'hours' => 'Ore', 'hours2' => 'Ore', 'ibod for dual isdn only' => 'iBOD can only be used with Dual ISDN.', diff --git a/langs/nl/cgi-bin/nl.pl b/langs/nl/cgi-bin/nl.pl index 5fa89b1ac..75c2b63f4 100644 --- a/langs/nl/cgi-bin/nl.pl +++ b/langs/nl/cgi-bin/nl.pl @@ -1198,6 +1198,7 @@ 'hosts config changed' => 'Hosts configuratie gewijzigd', 'hour' => 'Uur', 'hour-graph' => 'Uur', +'hours-graph' => 'Uren', 'hours' => 'uren', 'hours2' => 'Uren', 'ibod for dual isdn only' => 'iBOD kan alleen gebruikt worden met Dual ISDN.', diff --git a/langs/pl/cgi-bin/pl.pl b/langs/pl/cgi-bin/pl.pl index 521381af2..8742f0c29 100644 --- a/langs/pl/cgi-bin/pl.pl +++ b/langs/pl/cgi-bin/pl.pl @@ -914,6 +914,7 @@ 'hosts config changed' => 'Zmieniona konfiguracja hostów', 'hour' => 'Godzina', 'hour-graph' => 'Godzina', +'hours-graph' => 'Godziny', 'hours' => 'Godziny', 'hours2' => 'Godziny', 'ibod for dual isdn only' => 'iBOD can only be used with Dual ISDN.', diff --git a/langs/tr/cgi-bin/tr.pl b/langs/tr/cgi-bin/tr.pl index 1917b8241..0e7bb3219 100644 --- a/langs/tr/cgi-bin/tr.pl +++ b/langs/tr/cgi-bin/tr.pl @@ -1341,6 +1341,7 @@ 'hosts config changed' => 'Ana bilgisayar yapılandırması değiştirildi', 'hour' => 'Saat', 'hour-graph' => 'saat', +'hours-graph' => 'saat', 'hours' => 'saat', 'hours2' => 'saat', 'ibod for dual isdn only' => 'iBOD yalnızca çift ISDN ile kullanılabilir.', From patchwork Sat Apr 6 04:29:39 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tim FitzGeorge X-Patchwork-Id: 2182 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 DA759861F37 for ; Fri, 5 Apr 2019 18:31:17 +0100 (BST) Received: from mail01.i.ipfire.org (localhost [IPv6:::1]) by mail01.ipfire.org (Postfix) with ESMTP id 44bRgT2ZTQz5Lcxf; Fri, 5 Apr 2019 18:31:17 +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 44bRgC5K0Mz5Lcy7 for ; Fri, 5 Apr 2019 18:31:03 +0100 (BST) Received: from [31.127.205.161] (helo=aragorn.tfitzgeorge.me.uk) by smtp.hosts.co.uk with esmtpa (Exim) (envelope-from ) id 1hCSgA-0000Ix-4r; Fri, 05 Apr 2019 18:31:03 +0100 From: Tim FitzGeorge To: development@lists.ipfire.org Subject: [PATCH 11/12] statusmail: Plugin for apcupsd Date: Fri, 5 Apr 2019 18:29:39 +0100 Message-Id: <20190405172940.13168-12-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.996,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 --- config/rootfiles/packages/apcupsd | 1 + lfs/apcupsd | 2 +- lfs/statusmail | 2 +- src/statusmail/plugins/services_ups_apc.pm | 115 +++++++++++++++++++++++++++++ 4 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 src/statusmail/plugins/services_ups_apc.pm diff --git a/config/rootfiles/packages/apcupsd b/config/rootfiles/packages/apcupsd index b58ed2b47..bbd510884 100644 --- a/config/rootfiles/packages/apcupsd +++ b/config/rootfiles/packages/apcupsd @@ -33,3 +33,4 @@ srv/web/ipfire/cgi-bin/upsstats.cgi #usr/share/man/man8/apcupsd.8 etc/rc.d/init.d/apcupsd var/ipfire/menu.d/EX-apcupsd.menu +usr/lib/statusmail/plugins/services_ups_apc.pm diff --git a/lfs/apcupsd b/lfs/apcupsd index e9766bb57..c03ef6657 100644 --- a/lfs/apcupsd +++ b/lfs/apcupsd @@ -32,7 +32,7 @@ DL_FROM = $(URL_IPFIRE) DIR_APP = $(DIR_SRC)/$(THISAPP) TARGET = $(DIR_INFO)/$(THISAPP) PROG = apcupsd -PAK_VER = 6 +PAK_VER = 7 DEPS = "" diff --git a/lfs/statusmail b/lfs/statusmail index 8ddf2df36..c57c3adab 100644 --- a/lfs/statusmail +++ b/lfs/statusmail @@ -65,7 +65,7 @@ $(TARGET) : $(patsubst %,$(DIR_DL)/%,$(objects)) install -v -m 0644 $(DIR_APP)/statusmail/plugins/services_urlfilter.pm /usr/lib/statusmail/plugins/ install -v -m 0644 $(DIR_APP)/statusmail/plugins/services_intrusion_prevention_system.pm /usr/lib/statusmail/plugins/ # install -v -m 0644 $(DIR_APP)/statusmail/plugins/services_clamav.pm /usr/lib/statusmail/plugins/ -# install -v -m 0644 $(DIR_APP)/statusmail/plugins/services_ups_apc.pm /usr/lib/statusmail/plugins/ + install -v -m 0644 $(DIR_APP)/statusmail/plugins/services_ups_apc.pm /usr/lib/statusmail/plugins/ @rm -rf $(DIR_APP) @$(POSTBUILD) diff --git a/src/statusmail/plugins/services_ups_apc.pm b/src/statusmail/plugins/services_ups_apc.pm new file mode 100644 index 000000000..62483ec35 --- /dev/null +++ b/src/statusmail/plugins/services_ups_apc.pm @@ -0,0 +1,115 @@ +#!/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 Services_Apcups; + +############################################################################ +# BEGIN Block +# +# Register the log items available in this file +############################################################################ + +sub BEGIN +{ + main::add_mail_item( 'ident' => 'services-apcupsd-events', + 'section' => $Lang::tr{'services'}, + 'subsection' => 'APC UPS', + 'item' => $Lang::tr{'statusmail events'}, + 'function' => \&events ); +} + +############################################################################ +# Code +############################################################################ + +#--------------------------------------------------------------------------- +# sub events( this ) +# +# Output apcupsd events +# +# Parameters: +# this message object +#--------------------------------------------------------------------------- + +sub events( $ ) +{ + my ($this) = @_; + my $line; + my @table; + + push @table, [$Lang::tr{'time'}, $Lang::tr{'statusmail event'}]; + + while ($line = $this->get_message_log_line) + { + next unless ($line); + next unless ($line =~ m/^(\w+\s+\d+ \d+:\d+:\d+) \S+ apcupsd\[\d+\]: (.*)/); + + push @table, [$1, $2]; + } + + if (@table > 1) + { + $this->add_table( @table ); + + return 1; + } + + return 0; +} + +# Known apcupsd messages +# At the moment none of them are ignored +# +# apcupsd exiting, signal %u +# apcupsd shutdown succeeded +# apcupsd error shutdown completed +# Ignoring --kill-on-powerfail since it is unsafe on Simple Signaling UPSes +# Could not open events file %s: %s +# apcupsd " APCUPSD_RELEASE " (" ADATE ") " APCUPSD_HOST " startup succeeded +# Power failure. +# Running on UPS batteries. +# Battery power exhausted. +# Reached run time limit on batteries. +# Battery charge below low limit. +# Reached remaining time percentage limit on batteries. +# Initiating system shutdown! +# Power is back. UPS running on mains. +# Users requested to logoff. +# Battery failure. Emergency. +# UPS battery must be replaced. +# Remote shutdown requested. +# Communications with UPS lost. +# Communications with UPS restored. +# UPS Self Test switch to battery. +# UPS Self Test completed. +# Mains returned. No longer on UPS batteries. +# Battery disconnected. +# Battery reattached. + +1; From patchwork Sat Apr 6 04:29:40 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tim FitzGeorge X-Patchwork-Id: 2183 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 C8F34861F37 for ; Fri, 5 Apr 2019 18:31:25 +0100 (BST) Received: from mail01.i.ipfire.org (localhost [IPv6:::1]) by mail01.ipfire.org (Postfix) with ESMTP id 44bRgd24g1z5Lcxx; Fri, 5 Apr 2019 18:31:25 +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 44bRgG6rrQz5Lcy8 for ; Fri, 5 Apr 2019 18:31:06 +0100 (BST) Received: from [31.127.205.161] (helo=aragorn.tfitzgeorge.me.uk) by smtp.hosts.co.uk with esmtpa (Exim) (envelope-from ) id 1hCSgD-0000Ix-5V; Fri, 05 Apr 2019 18:31:06 +0100 From: Tim FitzGeorge To: development@lists.ipfire.org Subject: [PATCH 12/12] statusmail: Plugin for clamav Date: Fri, 5 Apr 2019 18:29:40 +0100 Message-Id: <20190405172940.13168-13-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)[]; RECEIVED_SPAMHAUS_PBL(0.00)[161.205.127.31.zen.spamhaus.org : 127.0.0.11]; 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.996,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%]; FROM_EQ_ENVFROM(0.00)[] 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 --- config/rootfiles/packages/clamav | 1 + lfs/clamav | 2 +- lfs/statusmail | 2 +- src/statusmail/plugins/services_clamav.pm | 170 ++++++++++++++++++++++++++++++ 4 files changed, 173 insertions(+), 2 deletions(-) create mode 100644 src/statusmail/plugins/services_clamav.pm diff --git a/config/rootfiles/packages/clamav b/config/rootfiles/packages/clamav index 9d6d68647..d4fc283a7 100644 --- a/config/rootfiles/packages/clamav +++ b/config/rootfiles/packages/clamav @@ -49,3 +49,4 @@ var/ipfire/clamav/freshclam.conf.sample var/lib/clamav etc/rc.d/init.d/clamav usr/local/bin/clamavctrl +usr/lib/statusmail/plugins/services_clamav.pm diff --git a/lfs/clamav b/lfs/clamav index 640691408..e4270799d 100644 --- a/lfs/clamav +++ b/lfs/clamav @@ -32,7 +32,7 @@ DL_FROM = $(URL_IPFIRE) DIR_APP = $(DIR_SRC)/$(THISAPP) TARGET = $(DIR_INFO)/$(THISAPP) PROG = clamav -PAK_VER = 44 +PAK_VER = 45 DEPS = "" diff --git a/lfs/statusmail b/lfs/statusmail index c57c3adab..c448a92af 100644 --- a/lfs/statusmail +++ b/lfs/statusmail @@ -64,7 +64,7 @@ $(TARGET) : $(patsubst %,$(DIR_DL)/%,$(objects)) install -v -m 0644 $(DIR_APP)/statusmail/plugins/system_status_services.pm /usr/lib/statusmail/plugins/ install -v -m 0644 $(DIR_APP)/statusmail/plugins/services_urlfilter.pm /usr/lib/statusmail/plugins/ install -v -m 0644 $(DIR_APP)/statusmail/plugins/services_intrusion_prevention_system.pm /usr/lib/statusmail/plugins/ -# install -v -m 0644 $(DIR_APP)/statusmail/plugins/services_clamav.pm /usr/lib/statusmail/plugins/ + install -v -m 0644 $(DIR_APP)/statusmail/plugins/services_clamav.pm /usr/lib/statusmail/plugins/ install -v -m 0644 $(DIR_APP)/statusmail/plugins/services_ups_apc.pm /usr/lib/statusmail/plugins/ @rm -rf $(DIR_APP) diff --git a/src/statusmail/plugins/services_clamav.pm b/src/statusmail/plugins/services_clamav.pm new file mode 100644 index 000000000..ff4af766c --- /dev/null +++ b/src/statusmail/plugins/services_clamav.pm @@ -0,0 +1,170 @@ +#!/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 Services_Calmav; + +use Time::Local; + +############################################################################ +# BEGIN Block +# +# Register the log items available in this file +############################################################################ + +sub BEGIN +{ + main::add_mail_item( 'ident' => 'services-clamav-alerts', + 'section' => $Lang::tr{'services'}, + 'subsection' => 'Clam AV', + 'item' => $Lang::tr{'statusmail ids alerts'},, + 'function' => \&alerts ); + + main::add_mail_item( 'ident' => 'services-clamav-updates', + 'section' => $Lang::tr{'services'}, + 'subsection' => 'Clam AV', + 'item' => $Lang::tr{'updates'}, + 'function' => \&updates ); +} + +############################################################################ +# Functions +############################################################################ + +sub get_log( $ ); + +#------------------------------------------------------------------------------ +# sub get_log( this ) +# +# Gets relevant information from the system log and caches it. +# +# Parameters: +# this message object +# +# Returns: +# reference to hash of wanted information +#------------------------------------------------------------------------------ + +sub get_log( $ ) +{ + my ($this) = @_; + + my $data = $this->cache( 'services-clamav' ); + return $data if (defined $data); + + my %info; + my $line; + + while ($line = $this->get_message_log_line) + { + next unless ($line); + next unless ($line =~ m/clamd\[.*\]:|freshclam\[.*\]:/); + + my ($time, $message) = $line =~ m/\w+\s+(\d+\s+\d+:\d+:\d+).*(?:clamd\[.*\]:|freshclam\[.*\]:) (.*)/; + + if ($message =~ m/^.+?: (.*?) FOUND/i) + { + $info{viruses}{$1}++; + } + elsif ($message =~ m/^Database correctly reloaded \((\d+) (?:signatures|viruses)\)/i) + { + $info{rules} = $1; + $info{updates}++; + } + } + + $this->cache( 'services-clamav', \%info ); + + return \%info; +} + +#------------------------------------------------------------------------------ +# sub alerts( this, min_count ) +# +# Outputs information on detected viruses etc. +# +# Parameters: +# this message object +# min_count only output information if it occurs at least this many times. +#------------------------------------------------------------------------------ + +sub alerts( $$ ) +{ + my ($self, $min_count) = @_; + my @table; + + use Sort::Naturally; + + push @table, [ $Lang::tr{'statusmail ids alert'}, $Lang::tr{'count'} ]; + + my $info = get_log( $self ); + + foreach my $virus ( sort { $$info{viruses}{$b} <=> $$info{viruses}{$a} || $a cmp $b} keys %{ $$info{viruses} } ) + { + push @table, [ $virus, $$info{viruses}{$virus} ]; + } + + if (@table > 1) + { + $self->add_table( @table ); + + return 1; + } + + return 0; +} + +#------------------------------------------------------------------------------ +# sub updates( this ) +# +# Output information on ClamAV rule updates. +# +# Parameters: +# this message object +#------------------------------------------------------------------------------ + + +sub updates( $ ) +{ + my ($self) = @_; + my @table; + + my $info = get_log( $self ); + + if (exists $$info{rules}) + { + $self->add_text( "$Lang::tr{'installed updates'} $$info{updates}\n" ); + $self->add_text( "$Lang::tr{'statusmail signatures'} $$info{rules}\n" ); + + return 1; + } + + return 0; +} + +1;