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( $;$ ) +{ +}

$Lang::tr{'statusmail schedule name'}$Lang::tr{'statusmail action'}
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+