diff options
author | Andreas Unterkircher <unki@netshadow.at> | 2008-12-03 20:37:13 +0100 |
---|---|---|
committer | Andreas Unterkircher <unki@netshadow.at> | 2008-12-12 18:36:55 +0100 |
commit | 0a6e4fae2c79d5f9da1033e0a51abfc69e10b8b2 (patch) | |
tree | 041b13746bede1eeceec181a8a00405e26d9db36 /cgi | |
parent | 226ad0a3c764c0606048acf7371b02765eee60d2 (diff) | |
download | exilog-0a6e4fae2c79d5f9da1033e0a51abfc69e10b8b2.zip exilog-0a6e4fae2c79d5f9da1033e0a51abfc69e10b8b2.tar.gz exilog-0a6e4fae2c79d5f9da1033e0a51abfc69e10b8b2.tar.bz2 |
sort files into their directories. move agent- and cleanup-script into 'agents', all static www-content (icons, stylesheet, javascript, ...) into 'htdocs'. cgi-stuff into 'cgi' and all reused code into 'lib'.
Signed-off-by: Andreas Unterkircher <unki@netshadow.at>
Diffstat (limited to 'cgi')
-rwxr-xr-x | cgi/exilog_cgi.pl | 124 | ||||
-rw-r--r-- | cgi/exilog_cgi_html.pm | 984 | ||||
-rw-r--r-- | cgi/exilog_cgi_messages.pm | 816 | ||||
-rw-r--r-- | cgi/exilog_cgi_param.pm | 74 | ||||
-rw-r--r-- | cgi/exilog_cgi_queues.pm | 131 | ||||
-rw-r--r-- | cgi/exilog_cgi_servers.pm | 102 |
6 files changed, 2231 insertions, 0 deletions
diff --git a/cgi/exilog_cgi.pl b/cgi/exilog_cgi.pl new file mode 100755 index 0000000..50344e6 --- /dev/null +++ b/cgi/exilog_cgi.pl @@ -0,0 +1,124 @@ +#!/usr/bin/perl +# +# This file is part of the exilog suite. +# +# http://duncanthrax.net/exilog/ +# +# (c) Tom Kistner 2004 +# +# See LICENSE for licensing information. +# + +use strict; +use exilog_config; +use exilog_util; +use exilog_cgi_html; +use exilog_cgi_param; +use exilog_sql; + +# Put user name into global variable +my $user = $ENV{'REMOTE_USER'} || 'anonymous'; + +_print_cgi_headers(); +_print_html_header(); +_print_html_tabs(); +_do_global_actions(); + +print '<div class="display" align="center">'; +if ($param->{tab} eq 'queues') { + use exilog_cgi_queues; + queues(); +} +elsif ($param->{tab} eq 'messages') { + use exilog_cgi_messages; + messages(); +} +elsif ($param->{tab} eq 'servers') { + use exilog_cgi_servers; + servers(); +}; +print '</div>'; + +_print_html_footer(); + + +# -- Private functions --------------------------------------------------------- + +sub _do_global_actions { + + # queue actions + my $valid_actions = [ 'deliver', 'cancel', 'delete' ]; + my $restricted_actions = [ 'cancel', 'delete' ]; + foreach my $p (keys %{ $param }) { + if ($p =~ /^ac_([A-Za-z0-9_.-]+?)_([A-Za-z0-9]{6}\-[A-Za-z0-9]{6}-[A-Za-z0-9]{2})$/) { + my $server = $1; + my $message_id = $2; + my $action = $param->{$p}; + if (ina($valid_actions,$action)) { + next if (ina($restricted_actions,$action) && ina($config->{web}->{restricted_users},$main::user)); + sql_queue_set_action($server,$message_id,$action); + } + } + } + +}; + + +sub _print_cgi_headers { + print $q->header(-expires=>'Thursday, 01-Jan-1970 00:00:01 GMT', + -Expires=>'now', + -Cache-Control=>'no-cache', + -Cache-Control=>'no-store', + -Pragma=>'no-cache'); +}; + + +sub _print_html_header { + print $q->start_html({-title=>"Exilog ".$version, + -style=>{-src=>"exilog_stylesheet.css"}, + -script=>[ + {-language=>'JAVASCRIPT', + -src=>"exilog_jscript.js"}, + "document.write(getCalendarStyles());" + ], + -meta=>{'http-equiv' => 'pragma', 'content' => 'no-cache'}}); + # global "centering" div + print $q->start_form({-name=>"exilogform",-method=>"GET"}); + print '<div align="center">'; + print '<div align="center" class="body">'; +}; + +sub _print_html_tabs { + my $tabs = { 'servers' => "Servers", + 'messages' => "Messages", + #'queues' => "Queues", # Queue manager is still unfinished ... + 'messages' => "Messages" }; + + my $html; + + foreach my $tab (sort keys %{ $tabs }) { + $html .= $q->td({-class=>"tabs_spacer"}," "). + (($param->{"tab"} eq $tab) ? + $q->td({-class=>"tabs_active"},$tabs->{$tab}) + : + $q->td({-class=>"tabs_click", -onClick=>"javascript:load_tab('$tab');"},$tabs->{$tab})); + }; + + print $q->table({-class=>"tabs",-cellpadding=>2,-cellspacing=>0}, + $q->Tr( + $q->td({-align=>"center",-class=>"tabs_static",-style=>"font-size: 16px; font-weight: bold;" }, "Exilog"). + $html. + $q->td({-class=>"tabs_spacer"}," "). + $q->td({-align=>"center",-class=>"tabs_static",-style=>"font-size: 12px; width: 240px; white-space: nowrap;" }, " Server time".(($config->{web}->{timestamps} eq 'gmt') ? " (GMT)":"").": ".stamp_to_date(time())." ") + ) + ); + + print $q->input({-type=>"hidden",-name=>"tab",-id=>"tab",-override=>1,-value=>$param->{"tab"}}); +}; + + +sub _print_html_footer { + print '</div></div>'; + print $q->end_form(); + print $q->end_html(); +}; diff --git a/cgi/exilog_cgi_html.pm b/cgi/exilog_cgi_html.pm new file mode 100644 index 0000000..f42b795 --- /dev/null +++ b/cgi/exilog_cgi_html.pm @@ -0,0 +1,984 @@ +#!/usr/bin/perl -w +# +# This file is part of the exilog suite. +# +# http://duncanthrax.net/exilog/ +# +# (c) Tom Kistner 2004 +# +# See LICENSE for licensing information. +# + +package exilog_cgi_html; +use exilog_config; +use exilog_util; +use CGI; +use strict; +use Data::Dumper; + +BEGIN { + use Exporter; + use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); + + $VERSION = 0.1; + @ISA = qw(Exporter); + @EXPORT = qw( + &render_message + &render_reject + &render_queue + &render_header + &render_server + &render_queue_table + $q + + ); + + %EXPORT_TAGS = (); + @EXPORT_OK = qw(); + + use vars qw( $q ); +} + +$q = new CGI; + +# Renders server statistics +# $stats->{ID}->{order} +# ->{desc} +# ->{icon} +# ->{text} + +sub render_server { + my $server = shift; + my $num_queued = shift; + my $h24_stats = shift; + + $q->div({-class=>"top_spacer"}, + $q->table({-class=>"stats", -cellspacing=>1, -cellpadding=>2, -border=>0}, + $q->Tr( + $q->td({-rowspan=>2,-class=>"table_stats",-style=>"width: 300px;"}, + $q->table({-cellpadding=>0, -cellspacing=>0, -border=>0}, + $q->Tr( + $q->td({-class=>"large_icon"}, + $q->img({-src=>"icons/server_normal.png",-border=>0}) + ), + $q->td({-class=>"large_text"}, + $server + ) + ) + ) + ), + $q->td({-class=>"table_stats"}, + $q->table({-cellpadding=>0, -cellspacing=>0, -border=>0, -width=>"1%"}, + $q->Tr( + $q->td({-rowspan=>2,-class=>"large_icon"}, + $q->img({-src=>"icons/queue_normal.png",-border=>0,-title=>"Queue Status"}) + ), + $q->td({-rowspan=>2,-class=>"large_icon"}, + "<b>Queue Status</b>" + ), + $q->td({-class=>"stats"}, + _item( { 'icon' => "icons/queued.png" }, + { + #'link' => { 'tab' => 'queues' }, + 'text' => ($num_queued->{deferred}+$num_queued->{frozen})." queued (".($num_queued->{deferred_bounce}+$num_queued->{frozen_bounce})." bounces)" } ) + + ), + $q->td({-class=>"stats"}, + ( $num_queued->{deferred} ? + _item( { 'icon' => "icons/deferred.png" }, + { 'text' => $num_queued->{deferred}." deferred (".$num_queued->{deferred_bounce}." bounces)" } ) + : + " " + ) + ) + ), + $q->Tr( + $q->td({-class=>"stats"}, + ( $num_queued->{frozen} ? + _item( { 'icon' => "icons/frozen.png" }, + { 'text' => $num_queued->{frozen}." frozen (".$num_queued->{frozen_bounce}." bounces)" } ) + : + " " + ) + ) + ) + ) + ) + ), + $q->Tr( + $q->td({-class=>"table_stats"}, + $q->table({-cellpadding=>0, -cellspacing=>0, -border=>0, -width=>"1%"}, + $q->Tr( + $q->td({-rowspan=>2,-class=>"large_icon"}, + $q->img({-src=>"icons/stats_h24.png",-border=>0,-title=>"Usage Statistics"}) + ), + $q->td({-rowspan=>2,-class=>"large_icon"}, + "<b>Last 24h stats</b>" + ), + $q->td({-class=>"stats"}, + _item( { 'icon' => "icons/arrival.png", 'title' => "Arrivals" }, + { 'text' => $h24_stats->{arrivals}." arrivals" } ) + ), + $q->td({-class=>"stats"}, + _item( { 'icon' => "icons/size.png", 'title' => "Average message size" }, + { 'text' => "Average message size: ".human_size($h24_stats->{avg_msg_size}) } ) + ) + ), + $q->Tr( + $q->td({-class=>"stats"}, + _item( { 'icon' => "icons/delivery.png" }, + { 'text' => $h24_stats->{deliveries}." deliveries" } ) + ), + $q->td({-class=>"stats"}, + _item( { 'icon' => "icons/error.png" }, + { 'text' => $h24_stats->{errors}." errors" } ) + ) + ) + ) + ) + ) + ) + ); + +}; + + +# Renders messages and post-DATA rejects. + +sub render_message { + my $h = shift; # main message context + + # Subclass list with references to HTML generation code. + my $subclasses = { 'rejects' => \&_reject_html, + 'deferrals' => \&_deferral_html, + 'errors' => \&_error_html, + 'deliveries' => \&_delivery_html, + 'unknown' => \&_unknown_html, + 'queue' => \&_queue_html }; + + my $sort_pref = { 'rejects' => 5, + 'deferrals' => 4, + 'errors' => 3, + 'deliveries' => 6, + 'unknown' => 2, + 'queue' => 1 }; + + my @dde = (); # holds list of subclass hashrefs + # --->{html} (HTML code generated by ) + # \->{timestamp} (timestamp for sorting later) + + # Now loop through the subclass list and call HTML + # generation code for each entry in all subclasses. + # Push the stuff onto dde where we can sort it later. + # Remember the timestamp of each entry so we can sort + # by it later to display the message events in the + # right order. + foreach my $subclass (keys %{ $subclasses }) { + foreach my $obj (@{ $h->{$subclass} }) { + my $tmp = {}; + $tmp->{timestamp} = $obj->{timestamp}; + $tmp->{sort_pref} = $sort_pref->{$subclass}; + # pass in "master sort" timestamp too + $tmp->{html} = &{$subclasses->{$subclass}}($obj,$h->{sort_timestamp}); + push @dde, $tmp; + }; + }; + + $q->div({-class=>"top_spacer"}, + $q->table({-class=>"message", -cellspacing=>1, -cellpadding=>2, -border=>0}, + _titlebar_html($h), + (exists($h->{mailfrom}) ? _message_html($h) : ""), + eval { + my $event_html = ""; + foreach my $event (sort by_event_order @dde) { + $event_html .= $event->{html}; + }; + $event_html; + } + ) + ); +}; +sub by_event_order { + if ($a->{timestamp} == $b->{timestamp}) { + ($a->{sort_pref} <=> $b->{sort_pref}); + } + else { + ($a->{timestamp} <=> $b->{timestamp}); + }; +}; + + +# This function is used to render pre-DATA rejects. +# Since those don't have any other associated events +# it is useless to go through all other tables like +# _render_message does. + +sub render_reject { + my $h = shift; + $q->div({-class=>"top_spacer"}, + $q->table({-class=>"message", -cellspacing=>1, -cellpadding=>2, -border=>0}, + _titlebar_html($h). + _reject_html($h,$h->{timestamp}) + ) + ); +}; + + +# renders a small "page header" + +sub render_header { + my $text = shift || ""; + + $q->div({-class=>"top_spacer"}, + $q->table({-class=>"header", -cellspacing=>1, -cellpadding=>2, -border=>0}, + $q->Tr( + $q->td({-class=>"header"}, + $text + ) + ) + ) + ); +} + + + +sub _titlebar_html { + my $h = shift || {}; + + my $actions = [ 0, 'deliver' ]; + unless (ina($config->{web}->{restricted_users}, $main::user)) { + if ($h->{mailfrom} ne '<>') { + push @{$actions}, 'cancel'; + } + push @{$actions}, 'delete'; + } + + $q->Tr( + $q->td({-class=>"table_titlebar"}, + $q->table({-cellpadding=>0,-cellspacing=>0, -border=>0}, + $q->Tr( + $q->td({-class=>"message_wide"}, + _item( { 'text' => $h->{server} }, + ( (edv($h,'message_id') && ($h->{message_id} =~ /^.{6}\-.{6}-.{2}$/) ) ? + { 'text' => '·' } : undef ), + ( (edv($h,'message_id') && ($h->{message_id} =~ /^.{6}\-.{6}-.{2}$/) ) ? + { 'link' => { 'tab' => 'messages', + 'tr' => '0', + 'qt' => 'message_id', + 'qs' => $h->{message_id} }, + 'text' => $h->{message_id} } : undef ), + ( edv($h,'msgid') ? { 'text' => '·' } : undef ), + ( edv($h,'msgid') ? ({ 'link' => { 'tab' => 'messages', + 'qt' => 'msgid', + 'tr' => '0', + 'qs' => $h->{msgid} }, + 'text' => "Track MSGID" }) : undef ), + ( (edv($h,'queue') && + defined(@{$h->{queue}}[0])) ? { 'text' => '·' } : undef ), + ( (edv($h,'queue') && + defined(@{$h->{queue}}[0])) ? { 'html' => $q->popup_menu({ + -name => 'ac_'.$h->{server}.'_'.$h->{'message_id'}, + -values => $actions, + -default => 0, + -labels => { 0 => ':: Please select action ::', + 'deliver' => 'Force delivery', + 'cancel' => 'Cancel (bounce)', + 'delete' => 'Delete' }, + -override => 1 + }).$q->submit({-name=>'Go'}) + } : undef ) ) + ), + (exists($h->{size}) ? + $q->td({-class=>"message"}, + _item( { 'icon' => "icons/size.png" }, + { 'text' => human_size($h->{size})} ) + ) : ""), + (exists($h->{completed}) ? + $q->td({-class=>"message"}, + _item( { 'icon' => "icons/stopwatch.png"}, + { 'text' => _timespan((defined($h->{completed}) ? $h->{completed} : time()) - $h->{timestamp} ) } ) + ) : "") + ) + ) + ) + ); +}; + + +sub _message_html { + my $h = shift || {}; + + $q->Tr( + $q->td({-class=>"table_arrival"}, + $q->table({-cellpadding=>0,-cellspacing=>0, -border=>0}, + $q->Tr( + $q->td({-rowspan=>2,-valign=>"top",-align=>"center",-class=>"large_icon"}, + ( ($h->{proto} =~ /local/i) ? + # local + $q->img({-src=>"icons/arrival_local.png",-border=>0,-title=>uc($h->{proto})." | ".$h->{user}}) + : + ( ( ($h->{proto} eq "asmtp") || ($h->{proto} =~ /a$/) ) ? + ( defined($h->{tls_cipher}) ? + # Auth w/ TLS + $q->img({-src=>"icons/arrival_tls_auth.png",-border=>0,-title=>uc($h->{proto})." | ".$h->{user}." | ".$h->{tls_cipher}}) + : + # Auth w/o TLS + $q->img({-src=>"icons/arrival_auth.png",-border=>0,-title=>uc($h->{proto})." | ".$h->{user}}) + ) + : + ( defined($h->{tls_cipher}) ? + # TLS + $q->img({-src=>"icons/arrival_tls.png",-border=>0,-title=>uc($h->{proto})." | ".$h->{tls_cipher}}) + : + # nothing special + $q->img({-src=>"icons/arrival_normal.png",-border=>0,-title=>uc($h->{proto})}) + ) + ) + ) + ), + $q->td( + _item( { 'style' => "font-weight: bold;", + ( ($h->{mailfrom} eq '<>') ? + ( (defined($h->{bounce_parent}) ? + ('link' => { 'tab' => 'messages', + 'tr' => '0', + 'qt' => 'message_id', + 'qs' => $h->{bounce_parent} }) + : () ), + 'text' => "Bounce". + (defined($h->{bounce_parent}) ? + " of ".$h->{bounce_parent} + : + "" + ) + ) + : + ('link' => { 'tab' => 'messages', + 'tr' => '0', + 'qt' => 'addr-all', + 'qs' => $h->{mailfrom} }, + 'text' => $h->{mailfrom}) + ) } + ) + ) + ), + $q->Tr( + $q->td( + _item( { 'style' => (($h->{timestamp} == $h->{sort_timestamp}) ? "text-decoration: underline;" : undef) , 'text' => stamp_to_date($h->{timestamp}) }, + (defined($h->{host_addr}) ? ( + { 'icon' => "icons/server.png" }, + { 'link' => { 'tab' => 'messages', + 'tr' => '0', + 'qt' => 'host-all', + 'qs' => $h->{host_addr} }, + 'text' => $h->{host_addr} }, + (edt($h,'host_rdns') ? + ( { 'icon' => "icons/dns.png" }, + { 'link' => { 'tab' => 'messages', + 'tr' => '0', + 'qt' => 'host-all', + 'qs' => $h->{host_rdns} }, + 'text' => $h->{host_rdns} } ) + : + () + ), + (edt($h,'host_helo') ? + ( { 'icon' => "icons/helo.png" }, + { 'link' => { 'tab' => 'messages', + 'tr' => '0', + 'qt' => 'host-all', + 'qs' => $h->{host_helo} }, + 'text' => $h->{host_helo} } ) + : + () + ), + (defined($h->{host_ident}) ? ( + { 'icon' => "icons/ident.png" }, + { 'link' => { 'tab' => 'messages', + 'tr' => '0', + 'qt' => 'ident', + 'qs' => $h->{host_ident} }, + 'text' => $h->{host_ident} } ) + : () ) + ) + : () ) ) + ) + ) + ) + ) + ); +}; + +sub _deferral_html { + my $deferral = shift || {}; + my $sort_timestamp = shift || 0; + + $q->Tr( + $q->td({-class=>"table_deferral"}, + $q->table({-cellpadding=>0,-cellspacing=>0, -border=>0}, + $q->Tr( + $q->td({-rowspan=>3,-valign=>"top",-align=>"center",-class=>"large_icon"}, + ( defined($deferral->{tls_cipher}) ? + # w/ TLS + $q->img({-src=>"icons/deferral_tls.png",-border=>0,-title=>$deferral->{tls_cipher}}) + : + # w/o TLS + $q->img({-src=>"icons/deferral_normal.png",-border=>0}) + ) + ), + $q->td( + _item( { 'style' => "font-weight: bold;", + 'link' => { 'tab' => 'messages', + 'tr' => '0', + 'qt' => 'addr-all', + 'qs' => $deferral->{rcpt} }, + 'text' => $deferral->{rcpt} }, + (edt($deferral,'rcpt_intermediate') ? + ({ 'text' => '-> '.$deferral->{rcpt_intermediate} }) + : + () + ), + ((lc($deferral->{rcpt}) ne lc($deferral->{rcpt_final})) ? + ({ 'link' => { 'tab' => 'messages', + 'tr' => '0', + 'qt' => 'addr-all', + 'qs' => $deferral->{rcpt_final} }, + 'text' => '-> '.$deferral->{rcpt_final} }) + : + () + ) + ) + ) + ), + $q->Tr( + $q->td( + _item( { 'style' => (($deferral->{timestamp} == $sort_timestamp) ? "text-decoration: underline;" : undef) , 'text' => stamp_to_date($deferral->{timestamp}) }, + { 'icon' => "icons/router_transport.png" }, + { 'text' => $deferral->{router}. + ( defined($deferral->{transport}) ? + "->".$deferral->{transport}.(defined($deferral->{shadow_transport}) ? " [".$deferral->{shadow_transport}."]" : "") + : + "") }, + ( defined($deferral->{host_addr}) ? + ( { 'icon' => "icons/server.png" }, + { 'link' => { 'tab' => 'messages', + 'tr' => '0', + 'qt' => 'host-all', + 'qs' => $deferral->{host_addr} }, + 'text' => $deferral->{host_addr} }, + { 'icon' => "icons/dns.png" }, + { 'link' => { 'tab' => 'messages', + 'tr' => '0', + 'qt' => 'host-all', + 'qs' => $deferral->{host_dns} }, + 'text' => $deferral->{host_dns} } ) + : + () + ) ) + ) + ), + $q->Tr( + $q->td( + _item( { 'icon' => "icons/errmsg.png" }, + { 'text' => $deferral->{errmsg} } ) + ) + ) + ) + ) + ); +}; + +sub _reject_html { + my $reject = shift || {}; + my $sort_timestamp = shift || 0; + + $q->Tr( + $q->td({-class=>"table_reject"}, + $q->table({-cellpadding=>0,-cellspacing=>0,-border=>0}, + $q->Tr( + $q->td({-rowspan=>2,-valign=>"top",-align=>"center",-class=>"large_icon"}, + (edv($reject,'message_id') ? + # post-DATA + $q->img({-src=>"icons/reject_postdata.png",-border=>0}) + : + # pre-DATA + $q->img({-src=>"icons/reject_predata.png",-border=>0}) + ) + ), + $q->td( + _item( (edv($reject,'mailfrom') ? + (($reject->{mailfrom} eq '<>') ? + { 'style' => "font-weight: bold;", + 'text' => "Bounce" } + : + { 'link' => { 'tab' => 'messages', + 'tr' => '0', + 'qt' => 'addr-all', + 'qs' => $reject->{mailfrom} }, + 'style' => "font-weight: bold;", + 'text' => $reject->{mailfrom} } + ) + : + () ), + { 'icon' => "icons/server.png" }, + { 'link' => { 'tab' => 'messages', + 'tr' => '0', + 'qt' => 'host-all', + 'qs' => $reject->{host_addr} }, + 'text' => $reject->{host_addr} }, + (edt($reject,'host_rdns') ? + ( { 'icon' => "icons/dns.png" }, + { 'link' => { 'tab' => 'messages', + 'tr' => '0', + 'qt' => 'host-all', + 'qs' => $reject->{host_rdns} }, + 'text' => $reject->{host_rdns} } ) + : + () + ), + (edt($reject,'host_helo') ? + ( { 'icon' => "icons/helo.png" }, + { 'link' => { 'tab' => 'messages', + 'tr' => '0', + 'qt' => 'host-all', + 'qs' => $reject->{host_helo} }, + 'text' => $reject->{host_helo} } ) + : + () + ), + (defined($reject->{host_ident}) ? + ( { 'icon' => "icons/ident.png" }, + { 'link' => { 'tab' => 'messages', + 'tr' => '0', + 'qt' => 'ident', + 'qs' => $reject->{host_ident} }, + 'text' => $reject->{host_ident} } ) : () + ) ) + ) + ), + $q->Tr( + $q->td( + _item( { 'style' => (($reject->{timestamp} == $sort_timestamp) ? "text-decoration: underline;" : undef) , 'text' => stamp_to_date($reject->{timestamp}) }, + { 'icon' => "icons/errmsg.png" }, + { 'text' => $reject->{errmsg} } ) + ) + ) + ) + ) + ); +}; + +sub _error_html { + my $error = shift || {}; + my $sort_timestamp = shift || 0; + + $q->Tr( + $q->td({-class=>"table_error"}, + $q->table({-cellpadding=>0,-cellspacing=>0, -border=>0}, + $q->Tr( + $q->td({-rowspan=>3,-valign=>"top",-align=>"center",-class=>"large_icon"}, + ( defined($error->{tls_cipher}) ? + # w/ TLS + $q->img({-src=>"icons/error_tls.png",-border=>0,-title=>$error->{tls_cipher}}) + : + # w/o TLS + $q->img({-src=>"icons/error_normal.png",-border=>0}) + ) + ), + $q->td( + _item( { 'style' => "font-weight: bold;", + 'link' => { 'tab' => 'messages', + 'tr' => '0', + 'qt' => 'addr-all', + 'qs' => $error->{rcpt} }, + 'text' => $error->{rcpt} }, + (edt($error,'rcpt_intermediate') ? + ({ 'text' => '-> '.$error->{rcpt_intermediate} }) + : + () + ), + ((lc($error->{rcpt}) ne lc($error->{rcpt_final})) ? + ({ 'link' => { 'tab' => 'messages', + 'tr' => '0', + 'qt' => 'addr-all', + 'qs' => $error->{rcpt_final} }, + 'text' => '-> '.$error->{rcpt_final} }) + : + () + ) + ) + ) + ), + $q->Tr( + $q->td( + _item( { 'style' => (($error->{timestamp} == $sort_timestamp) ? "text-decoration: underline;" : undef) , 'text' => stamp_to_date($error->{timestamp}) }, + ( edv($error,'router') ? ( + { 'icon' => "icons/router_transport.png" }, + { 'text' => $error->{router}. + ( defined($error->{transport}) ? + "->".$error->{transport}.(defined($error->{shadow_transport}) ? " [".$error->{shadow_transport}."]" : "") + : + "") }, + ( defined($error->{host_addr}) ? + ( { 'icon' => "icons/server.png" }, + { 'link' => { 'tab' => 'messages', + 'tr' => '0', + 'qt' => 'host-all', + 'qs' => $error->{host_addr} }, + 'text' => $error->{host_addr} }, + { 'icon' => "icons/dns.png" }, + { 'link' => { 'tab' => 'messages', + 'tr' => '0', + 'qt' => 'host-all', + 'qs' => $error->{host_dns} }, + 'text' => $error->{host_dns} } ) + : + () + ) ) : () ) ) + ) + ), + $q->Tr( + $q->td( + _item( { 'icon' => "icons/errmsg.png" }, + { 'text' => $error->{errmsg} } ) + ) + ) + ) + ) + ); +}; + +sub _delivery_html { + my $delivery = shift || {}; + my $sort_timestamp = shift || 0; + + $q->Tr( + $q->td({-class=>"table_delivery"}, + $q->table({-cellpadding=>0,-cellspacing=>0, -border=>0}, + $q->Tr( + $q->td({-rowspan=>2,-valign=>"top",-align=>"center",-class=>"large_icon"}, + ( defined($delivery->{tls_cipher}) ? + # w/ TLS + $q->img({-src=>"icons/delivery_tls.png",-border=>0,-title=>$delivery->{tls_cipher}}) + : + # w/o TLS + $q->img({-src=>"icons/delivery_normal.png",-border=>0}) + ) + ), + $q->td( + _item( { 'style' => "font-weight: bold;", + 'link' => { 'tab' => 'messages', + 'tr' => '0', + 'qt' => 'addr-all', + 'qs' => $delivery->{rcpt} }, + 'text' => $delivery->{rcpt} }, + (edt($delivery,'rcpt_intermediate') ? + ({ 'text' => '-> '.$delivery->{rcpt_intermediate} }) + : + () + ), + ((lc($delivery->{rcpt}) ne lc($delivery->{rcpt_final})) ? + ({ 'link' => { 'tab' => 'messages', + 'tr' => '0', + 'qt' => 'addr-all', + 'qs' => $delivery->{rcpt_final} }, + 'text' => '-> '.$delivery->{rcpt_final} }) + : + () + ) + ) + ) + ), + $q->Tr( + $q->td( + _item( { 'style' => (($delivery->{timestamp} == $sort_timestamp) ? "text-decoration: underline;" : undef) , 'text' => stamp_to_date($delivery->{timestamp}) }, + { 'icon' => "icons/router_transport.png" }, + { 'text' => $delivery->{router}. + ( defined($delivery->{transport}) ? + "->".$delivery->{transport}.(defined($delivery->{shadow_transport}) ? " [".$delivery->{shadow_transport}."]" : "") + : + "") }, + ( defined($delivery->{host_addr}) ? + ( { 'icon' => "icons/server.png" }, + { 'link' => { 'tab' => 'messages', + 'tr' => '0', + 'qt' => 'host-all', + 'qs' => $delivery->{host_addr} }, + 'text' => $delivery->{host_addr} }, + { 'icon' => "icons/dns.png" }, + { 'link' => { 'tab' => 'messages', + 'tr' => '0', + 'qt' => 'host-all', + 'qs' => $delivery->{host_dns} }, + 'text' => $delivery->{host_dns} } ) + : + () + ) ) + ) + ) + ) + ) + ); +}; + +sub _unknown_html { + my $unknown = shift || {}; + my $sort_timestamp = shift || 0; + + $q->Tr( + $q->td({-class=>"table_unknown"}, + $q->table({-cellpadding=>0,-cellspacing=>0, -border=>0}, + $q->Tr( + $q->td({-valign=>"top",-align=>"center",-class=>"large_icon"}, + $q->img({-src=>"icons/unknown.png",-border=>0}) + ), + $q->td( + _item( { 'style' => (($unknown->{timestamp} == $sort_timestamp) ? "text-decoration: underline;" : undef) , 'text' => stamp_to_date($unknown->{timestamp}) }, + { 'text' => $unknown->{line} } ) + ) + ) + ) + ) + ); +}; + +sub _queue_html { + my $queue = shift || {}; + my $sort_timestamp = shift || 0; + + my @recipients_delivered = split / /, $queue->{recipients_delivered}; + my @recipients_pending = split / /, $queue->{recipients_pending}; + + $q->Tr( + $q->td({-class=>"table_queue"}, + $q->table({-cellpadding=>0,-cellspacing=>0, -border=>0}, + $q->Tr( + $q->td({-rowspan=>2,-valign=>"top",-align=>"center",-class=>"large_icon"}, + ( edt($queue,'frozen') ? + # frozen + $q->img({-src=>"icons/queue_frozen.png",-border=>0,-title=>"Frozen at ".stamp_to_date($queue->{frozen})}) + : + # normal + $q->img({-src=>"icons/queue_deferred.png",-border=>0}) + ) + ), + $q->td( + _item( { #'style' => "font-family: Arial, Helvetica, Sans-Serif;", + 'text' => $queue->{subject} } ) + ) + ), + $q->Tr( + $q->td( + _item( { 'icon' => "icons/delivered.png", + ( (scalar @recipients_delivered) ? + ( 'title' => join("\n",@recipients_delivered) ) + : + () + ) }, + { 'text' => scalar @recipients_delivered }, + { 'text' => ' ' }, + { 'icon' => "icons/deferred.png", + 'title' => join("\n",@recipients_pending) }, + { 'text' => scalar @recipients_pending }, + { 'text' => ' ' }, + { 'icon' => "icons/dsn_warning.png", + 'title' => "Number of DSNs sent" }, + { 'text' => $queue->{num_dsn} } ) + ) + ) + ) + ) + ); +}; + +sub render_queue_table { + my $messages = shift; + my $now = time(); + + my $rows = ""; + foreach my $message (@{ $messages }) { + my $row_id = $message->{server}.'_'.$message->{message_id}; + my @rcpts_delivered = split / /,$message->{recipients_delivered}; + my @rcpts_pending = split / /,$message->{recipients_pending}; + $rows .= + $q->Tr( + $q->td({-class=>"queue"}, + # Actions + ), + $q->td({-class=>"queue"}, + $message->{server} + ), + $q->td({-class=>"queue"}, + edt($message,'timestamp') ? + _timespan($now - $message->{timestamp}) + : + '?' + ), + $q->td({-class=>"queue"}, + _shorten_addr($message->{mailfrom},40) + ), + $q->td({-class=>"queue", + -onMouseOver=>"javascript:document.getElementById('$row_id' + '_pending').style.visibility = 'visible';", + -onMouseOut=>"javascript:document.getElementById('$row_id' + '_pending').style.visibility = 'hidden';"}, + _shorten_addr($rcpts_pending[0],40) + ), + $q->td({-class=>"queue"}, + (defined($rcpts_pending[0]) ? + $q->div({-id=>$row_id.'_pending', -class=>"rcpts_pending_popup"},"Test<br>Test2<br>Test3") + : + ''). + _shorten_string($message->{subject},60) + ) + ); + }; + + $q->div({-class=>"top_spacer"}, + $q->table({-class=>"queue_table",-cellpadding=>0,-cellspacing=>1,border=>0}, + $q->Tr( + # Table header + $q->td({-class=>"queue_header",-width=>"1%"}, + " " + ), + $q->td({-class=>"queue_header",-width=>"1%"}, + "Server" + ), + $q->td({-class=>"queue_header",-width=>"1%"}, + "Age" + ), + $q->td({-class=>"queue_header",-width=>"1%"}, + "Sender" + ), + $q->td({-class=>"queue_header",-width=>"1%"}, + "Recipient(s)" + ), + $q->td({-class=>"queue_header"}, + "Subject" + ) + ), + $rows + ) + ); +}; + + +# -- Private functions ------------------------------------- + +sub _item { + my $html = ""; + + # Loop through all parts and build the table TDs + while (scalar @_) { + my $part = shift @_; + next unless $part; + + my $link = ""; + if (exists($part->{'link'})) { + # this item has a link + $link = 'exilog_cgi.pl?'; + foreach my $var (keys %{ $part->{'link'} }) { + $link .= $var.'='._url_encode($part->{'link'}->{$var}).'&'; + } + chop($link); + } + + if (exists($part->{icon})) { + $html .= + $q->td({-class=>"item_icon",-style=>(exists($part->{style}) ? $part->{style} : "")}, + $q->img({ -src=>$part->{icon}, + -title=>(exists($part->{title}) ? $part->{title} : "" ), + -border=>0 }) + ); + next; + } + elsif (exists($part->{html})) { + $html .= $q->td({-class=>"item_text"}, $part->{html}); + } + elsif (exists($part->{text})) { + # HTML-quote angle brackets + $part->{text} =~ s/\>/\>\;/g; + $part->{text} =~ s/\</\<\;/g; + + # break long text at colons or blanks + $part->{text} =~ s/([^<>]{80,}?)([: ])/$1$2\<br\>/g; + + $html .= + $q->td({-class=>"item_text", + ($link ? ( -onClick=>"javascript:document.location.href='$link';", + -style=>(exists($part->{style}) ? $part->{style} : "")."cursor:pointer;cursor:hand;", + -onMouseOver=>"javascript:link_on(this);", + -onMouseOut=>"javascript:link_off(this);" ) + : ( + -style=>(exists($part->{style}) ? $part->{style} : "") + ) ) }, + $part->{text} + ); + }; + }; + + # Wrap everything in the surrounding table. + return + $q->table({-class=>"item",-cellspacing=>0,-cellpadding=>0,-border=>0}, + $q->Tr( + $html + ) + ); +}; + + +sub _shorten_addr { + my $addr = shift; + my $max = shift; + return $addr if (length($addr) <= $max); + + my ($localpart,$domain) = split /\@/, $addr, 2; + + if (length($addr) > (int($max/2))) { + # shorten local part first + $localpart = substr($localpart,0,int($max/4)).'...'; + }; + # return if that suffices + return $localpart.'@'.$domain if (length($localpart.'@'.$domain) <= $max); + + # shorten domain + my @domainparts = split /\./, $domain; + while ((scalar @domainparts) > 1) { + shift @domainparts; + last if (length($localpart.'@'.'...'.join('.',@domainparts)) <= $max); + }; + + return $localpart.'@'.'...'.join('.',@domainparts); +}; + + +sub _shorten_string { + my $string = shift; + my $max = shift; + return $string if (length($string) <= $max); + return substr($string,0,($max-3)).'...'; +}; + + +sub _timespan { + my $amnt = shift; + my @steps = (1,60,60,24,7,999999999); + my @units = ('s','m','h','d','wk'); + + my $str = ""; + while ($amnt > $steps[1]) { + my $rest = $amnt % $steps[1]; + $str = $rest.$units[0]." ".$str; + $amnt = int($amnt/$steps[1]); + shift @units; + shift @steps; + }; + $str = $amnt.$units[0]." ".$str; + return $str; +}; + +sub _url_encode { + my $subj = shift; + $subj =~ s/([^A-Za-z0-9])/sprintf("%%%02x",ord($1))/eg; + return $subj; +}; + +1; diff --git a/cgi/exilog_cgi_messages.pm b/cgi/exilog_cgi_messages.pm new file mode 100644 index 0000000..9b36aed --- /dev/null +++ b/cgi/exilog_cgi_messages.pm @@ -0,0 +1,816 @@ +#!/usr/bin/perl -w +# +# This file is part of the exilog suite. +# +# http://duncanthrax.net/exilog/ +# +# (c) Tom Kistner 2004 +# +# See LICENSE for licensing information. +# + +package exilog_cgi_messages; +use strict; +use exilog_config; +use exilog_cgi_html; +use exilog_cgi_param; +use exilog_sql; +use exilog_util; +use Net::Netmask; +use Time::Local; + +use Data::Dumper; + +BEGIN { + use Exporter; + use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); + + # set the version for version checking + $VERSION = 0.1; + @ISA = qw(Exporter); + @EXPORT = qw( + &messages + ); + + %EXPORT_TAGS = (); + + # your exported package globals go here, + # as well as any optionally exported functions + @EXPORT_OK = qw(); +} + +sub _select_all { + # All tables + my @tables = ( 'deliveries','errors','unknown','deferrals','messages','rejects','queue' ); + # Only server and timestamp as criteria. + # Since these are present on every table + # the queries are all the same ... + my $criteria = { 'timestamp' => (edt($param,'tr') ? _make_tr() : undef), + 'server' => (edt($param,'sr') ? $param->{'sr'} : undef ) }; + + my @results = (); + foreach my $table (@tables) { + next unless (ina($param->{'qw'},$table)); + push @results, @{ sql_select( $table, [ 'server','message_id','timestamp' ], $criteria ) }; + }; + return \@results; +}; + +sub _select_ident { + if (!edt($param,'qs')) { + return []; + } + my $criteria = { 'timestamp' => (edt($param,'tr') ? _make_tr() : undef), + 'server' => (edt($param,'sr') ? $param->{'sr'} : undef ), + 'host_ident' => $param->{'qs'} }; + # Only messages table + return sql_select( 'messages', [ 'server','message_id','timestamp' ], $criteria ); +}; + +sub _select_msgid { + if (!edt($param,'qs')) { + return []; + } + # Only messages table + return sql_select( 'messages', [ 'server','message_id','timestamp' ], { 'msgid' => $param->{'qs'} } ); +}; + +sub _select_message_id { + if (!edt($param,'qs')) { + return []; + } + + my @results = (); + my @tables = ( 'deliveries','errors','unknown','deferrals','messages','rejects','queue' ); + my $criteria = { 'message_id' => $param->{'qs'} }; + foreach my $table (@tables) { + push @results, @{ sql_select( $table, [ 'server','message_id','timestamp' ], $criteria ) }; + }; + + # check bounce parent field too + push @results, @{ sql_select( 'messages', [ 'server','message_id','timestamp' ], { 'bounce_parent' => $param->{'qs'} } ) }; + + return \@results; +}; + +sub _select_addr { + my $p = shift || 'all'; + + if (!edt($param,'qs')) { + return []; + } + + my @queries; + push @queries, { 'table' => 'messages', + 'criteria' => { 'mailfrom' => $param->{'qs'} } }, + { 'table' => 'rejects', + 'criteria' => { 'mailfrom' => $param->{'qs'} } } + if (($p eq 'sender') || ($p eq 'all')); + + push @queries, { 'table' => 'rejects', + 'criteria' => { 'rcpt' => $param->{'qs'} } }, + { 'table' => 'deliveries', + 'criteria' => { 'rcpt' => $param->{'qs'} } }, + { 'table' => 'deliveries', + 'criteria' => { 'rcpt_final' => $param->{'qs'} } }, + { 'table' => 'deferrals', + 'criteria' => { 'rcpt' => $param->{'qs'} } }, + { 'table' => 'deferrals', + 'criteria' => { 'rcpt_final' => $param->{'qs'} } }, + { 'table' => 'errors', + 'criteria' => { 'rcpt' => $param->{'qs'} } }, + { 'table' => 'errors', + 'criteria' => { 'rcpt_final' => $param->{'qs'} } } + if (($p eq 'rcpt') || ($p eq 'all')); + + + my @results = (); + foreach my $query (@queries) { + next unless (ina($param->{'qw'},$query->{table})); + # add standard criteria + $query->{criteria}->{'timestamp'} = (edt($param,'tr') ? _make_tr() : undef); + $query->{criteria}->{'server'} = (edt($param,'sr') ? $param->{'sr'} : undef ); + push @results, @{ sql_select( $query->{table}, [ 'server','message_id','timestamp' ], $query->{criteria} ) }; + }; + + return \@results; +}; + + +sub _select_host { + my $p = shift || 'all'; + + if (!edt($param,'qs')) { + return []; + } + + my @queries; + if ($param->{'qs'} =~ /^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/) { + # IPv4 address + push @queries, { 'table' => 'messages', + 'criteria' => { 'host_addr' => $param->{'qs'} } }, + { 'table' => 'rejects', + 'criteria' => { 'host_addr' => $param->{'qs'} } } + if (($p eq 'incoming') || ($p eq 'all')); + + push @queries, { 'table' => 'deliveries', + 'criteria' => { 'host_addr' => $param->{'qs'} } }, + { 'table' => 'deferrals', + 'criteria' => { 'host_addr' => $param->{'qs'} } }, + { 'table' => 'errors', + 'criteria' => { 'host_addr' => $param->{'qs'} } }, + { 'table' => 'unknown', + 'criteria' => { 'line' => '%'.$param->{'qs'}.'%' } } + if (($p eq 'outgoing') || ($p eq 'all')); + + } + elsif ($param->{'qs'} =~ /^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\/[0-9]{1,2}$/) { + # check if we can make a valid Net::Netmask object out of this + my $block = new2 Net::Netmask($param->{'qs'}); + + if (!defined($block)) { + return "Invalid CIDR specification"; + }; + + # Network specification + push @queries, { 'table' => 'messages' }, + { 'table' => 'rejects' } + if (($p eq 'incoming') || ($p eq 'all')); + + push @queries, { 'table' => 'deliveries' }, + { 'table' => 'deferrals' }, + { 'table' => 'errors' } + if (($p eq 'outgoing') || ($p eq 'all')); + + my @results = (); + foreach my $query (@queries) { + next unless (ina($param->{'qw'},$query->{table})); + # add standard criteria + $query->{criteria}->{'timestamp'} = (edt($param,'tr') ? _make_tr() : undef); + $query->{criteria}->{'server'} = (edt($param,'sr') ? $param->{'sr'} : undef ); + push @results, @{ sql_select( $query->{table}, [ 'server','message_id','timestamp','host_addr' ], $query->{criteria} ) }; + }; + + # now weed out those that don't match the CIDR specification + my @valid = (); + foreach my $result (@results) { + if ($block->match($result->{host_addr})) { + delete $result->{host_addr}; + push @valid, $result; + }; + }; + + return \@valid; + } + else { + # assume hostname + my $prefix_wc = ""; + my $suffix_wc = ""; + $prefix_wc = '%' if ($param->{'qs'} !~ /^\%/); + $suffix_wc = '%' if ($param->{'qs'} !~ /\%$/); + + push @queries, { 'table' => 'messages', + 'criteria' => { 'host_helo' => $param->{'qs'} } }, + { 'table' => 'messages', + 'criteria' => { 'host_rdns' => $param->{'qs'} } }, + { 'table' => 'rejects', + 'criteria' => { 'host_helo' => $param->{'qs'} } }, + { 'table' => 'rejects', + 'criteria' => { 'host_rdns' => $param->{'qs'} } } + if (($p eq 'incoming') || ($p eq 'all')); + + push @queries, { 'table' => 'deliveries', + 'criteria' => { 'host_dns' => $param->{'qs'} } }, + { 'table' => 'deferrals', + 'criteria' => { 'host_dns' => $param->{'qs'} } }, + { 'table' => 'errors', + 'criteria' => { 'host_dns' => $param->{'qs'} } }, + { 'table' => 'unknown', + # the blank makes sure that we do not match domains in addresses + 'criteria' => { 'line' => $prefix_wc.' '.$param->{'qs'}.$suffix_wc } } + if (($p eq 'outgoing') || ($p eq 'all')); + }; + + my @results = (); + foreach my $query (@queries) { + next unless (ina($param->{'qw'},$query->{table})); + # add standard criteria + $query->{criteria}->{'timestamp'} = (edt($param,'tr') ? _make_tr() : undef); + $query->{criteria}->{'server'} = (edt($param,'sr') ? $param->{'sr'} : undef ); + push @results, @{ sql_select( $query->{table}, [ 'server','message_id','timestamp' ], $query->{criteria} ) }; + }; + + return \@results; +}; + + + +sub messages { + + _print_Messages_selector(); + + # Check CGI input for event selection. + # We need at least a query type ('qt'), + # otherwise we only display the selector. + my $selected = []; + if (edt($param,'qt')) { + # Call event selection function for this query type. + _print_progress_bar("Collecting message IDs ..."); + # cut off parameter part (separated with dash) + my ($function,$parameter) = split /\-/, $param->{'qt'}; + no strict "refs"; + $selected = &{ "_select_".$function }($parameter); + if (ref($selected) ne 'ARRAY') { + # error + _update_progress_bar($selected); + return; + }; + } + else { + # no query type ('qt'), just return + return; + }; + + # Now we have a set of selected messages in an array: + # + # [0]-->{server} + # |->{timestamp} + # \->{message_id} + # [1]-->{server} + # |->{timestamp} + # \->{message_id} + # ... + + # Perform dupe check. We may have a lot of duplicate IDs + # in the list. It is faster to weed them out this way ... + _update_progress_bar("Performing dupe check ..."); + my $dupe = {}; + my @duped = (); + foreach my $message (@{ $selected }) { + if (exists($dupe->{$message->{server}}->{$message->{message_id}})) { + # Make sure we use the largest timestamp we can find + if ($dupe->{$message->{server}}->{$message->{message_id}}->{timestamp} < $message->{timestamp}) { + $dupe->{$message->{server}}->{$message->{message_id}}->{timestamp} = $message->{timestamp}; + }; + next; + }; + $dupe->{$message->{server}}->{$message->{message_id}} = $message; + push @duped, $message; + }; + undef $dupe; + undef $selected; + + if ((scalar @duped) == 0) { + _update_progress_bar("No matching events found."); + return; + }; + + if (((scalar @duped) > 500) && ($param->{'sm'} !~ /^Confirm/)) { + _update_progress_bar("Warning: ".(scalar @duped)." messages/events found. Narrow down your selection or submit the query again."); + print ' + <script language="Javascript"> + document.forms[0].sm.value = "Confirm Query"; + </script> + '; + return; + }; + + # Initialize stats counters + my $stats = { + 'num_messages' => { 'desc' => "Messages", + 'order' => 1, + 'num' => 0 }, + 'num_rejects' => { 'desc' => "Rejects", + 'order' => 5, + 'num' => 0 }, + 'num_deliveries' => { 'desc' => "Deliveries", + 'order' => 2, + 'num' => 0 }, + 'num_errors' => { 'desc' => "Errors", + 'order' => 3, + 'num' => 0 }, + 'total_turnover' => { 'desc' => "Total Turnover", + 'order' => 4, + 'size' => 0 } + }; + + # Now we need to build the complete message set. + # This requires a large number of SELECTs. + _update_progress_bar("Sorting ..."); + my $c = 0; + foreach my $message (sort { $b->{timestamp} <=> $a->{timestamp} } @duped) { + + # Update the progress bar every 50 entries + if (($c % 50) == 0) { + _update_progress_bar("Grabbing event data (".$c." of ".scalar @duped." events done) ..."); + }; + $c++; + + # Remove timestamp, we'll re-add it later for marking + # the "sort" timestamp. + my $sort_timestamp = $message->{timestamp}; + delete $message->{timestamp}; + + # Check the message ID. + if ($message->{message_id} !~ /^.{6}\-.{6}\-.{2}$/) { + # This is a pre-DATA reject/warning. + # Render it as a reject. + my $complete = @{ sql_select( 'rejects', ['*'], $message ) }[0]; + $complete->{sort_timestamp} = $sort_timestamp; + print render_reject($complete); + $stats->{num_rejects}->{num}++; + } + else { + # Try to grab complete arrival ('messages' table) + my $complete = @{ sql_select( 'messages', ['*'], $message ) }[0]; + + # If there is an arrival, this set has a "real" + # message ID. Scan other tables for events. + if (defined($complete)) { + $complete->{rejects} = sql_select( 'rejects', ['*'], $message ); + $complete->{deliveries} = sql_select( 'deliveries', ['*'], $message ); + $complete->{errors} = sql_select( 'errors', ['*'], $message ); + $complete->{deferrals} = sql_select( 'deferrals', ['*'], $message ); + $complete->{unknown} = sql_select( 'unknown', ['*'], $message ); + $complete->{queue} = sql_select( 'queue', ['*'], $message ); + $complete->{sort_timestamp} = $sort_timestamp; + print render_message($complete); + $stats->{num_messages}->{num}++; + $stats->{total_turnover}->{size} += $complete->{size}; + $stats->{num_rejects}->{num} += (scalar @{ $complete->{rejects} }); + $stats->{num_deliveries}->{num} += (scalar @{ $complete->{deliveries} }); + $stats->{total_turnover}->{size} += ($complete->{size} * (scalar @{ $complete->{deliveries} })); + $stats->{num_errors}->{num} += (scalar @{ $complete->{errors} }); + } + # If there is no associated arrival, this is either + # a POST-DATA reject (in rejects table) or another + # post-DATA warning (in unknown table). Since both + # can occur, we render this as a message. + else { + $complete->{server} = $message->{server}; + $complete->{message_id} = $message->{message_id}; + $complete->{rejects} = sql_select( 'rejects', ['*'], $message ); + $complete->{unknown} = sql_select( 'unknown', ['*'], $message ); + $complete->{sort_timestamp} = $sort_timestamp; + print render_message($complete); + $stats->{num_rejects}->{num}++; + }; + }; + }; + + _update_progress_bar(_render_stats($stats)); +}; + + +sub _make_tr { + + my $str = $q->param('tr') || 0; + + unless ($str eq 'custom') { + my $unit = chop $str; + my $now = time(); + my $units = { '0' => 0, + 'm' => 60, + 'h' => 3600, + 'd' => 86400 }; + my $then = $now + $units->{$unit}*$str; + unless ($now == $then) { # The "unlimited" case + $param->{'tds'} = stamp_to_date($then,1); + $param->{'tde'} = stamp_to_date($now,1); + } + return $then; + } + else { + $param->{'tds'} =~ s/ +$//; + $param->{'tds'} =~ s/^ +//; + $param->{'tde'} =~ s/ +$//; + $param->{'tde'} =~ s/^ +//; + + my ($sd,$st) = split / +/, $param->{'tds'}; + my ($ed,$et) = split / +/, $param->{'tde'}; + + if (!$st && $sd =~ /\:/) { + $st = $sd; + $sd = ''; + } + if (!$et && $ed =~ /\:/) { + $et = $ed; + $ed = ''; + } + + $ed = $sd unless($ed); + + my $fsd = _parse_date($sd, $st || '00:00:00'); + my $fed = _parse_date($ed, $et || '23:59:59'); + + $param->{'tds'} = stamp_to_date($fsd); + $param->{'tde'} = stamp_to_date($fed); + + return $fsd." ".$fed; + } +} + + +sub _parse_date { + my $d = shift; + my $t = shift; + + my ($dn,$tn) = split / /, stamp_to_date(time,1); + my ($year,$month,$day) = split /\-/, $dn; + my ($hour,$minute,$second) = split /\:/, $tn; + + if ($d =~ /^([0-9]{4})\-([0-9]{2})\-([0-9]{2})$/) { + $year = $1; + $month = $2; + $day = $3; + } + elsif ($d =~ /^([0-9]{2})\-([0-9]{2})$/) { + $month = $1; + $day = $2; + } + + if ($t =~ /^([0-9]{2})\:([0-9]{2})\:([0-9]{2})$/) { + $hour = $1; + $minute = $2; + $second = $3; + } + elsif ($t =~ /^([0-9]{2})\:([0-9]{2})$/) { + $hour = $1; + $minute = $2; + } + + return date_to_stamp($year.'-'.$month.'-'.$day, $hour.':'.$minute.':'.$second); +} + +sub _render_stats { + my $stats = shift || {}; + + my @items = (); + foreach (sort {$stats->{$a}->{order} <=> $stats->{$b}->{order}} keys %{ $stats }) { + if (exists($stats->{$_}->{num}) && $stats->{$_}->{num}) { + push @items, $stats->{$_}->{desc}.": ".$stats->{$_}->{num}; + } + elsif (exists($stats->{$_}->{size}) && $stats->{$_}->{size}) { + push @items, $stats->{$_}->{desc}.": ".human_size($stats->{$_}->{size}); + }; + }; + + return join(" <b>|</b> ",@items); +}; + + +sub _print_progress_bar { + my $str = shift || ""; + print render_header( + $q->div({-name=>"progress",-id=>"progress", -align=>"center"}, + $str + ) + ); + print "\n<!-- Block filler follows - ".("xxxx" x 1024)." -->\n"; +}; + + +sub _update_progress_bar { + my $str = shift || ""; + print ' + <script language="JavaScript"> + document.getElementById("progress").innerHTML = "'.$str.'"; + </script> + '; + print "\n<!-- Block filler follows - ".("xxxx" x 1024)." -->\n"; +}; + + +sub _print_Messages_selector { + + _make_tr(); + + print + $q->div({-class=>"top_spacer"}, + $q->div({-align=>"left",-style=>"padding: 10px; border: 1px solid black; background: #eeeeee;"}, + + $q->table({-cellspacing=>0,-cellpadding=>4,-border=>0}, + $q->Tr( + $q->td({-align=>"left",-style=>"width: 16px;"}, + $q->img({-src=>"icons/event_type.png"}) + ), + $q->td({-align=>"left",-style=>"width: 100px;"}, + "Search Type" + ), + $q->td({-align=>"left"}, + $q->popup_menu({ -name=>"qt", + -id=>"qt", + -style=>"width: 400px;", + -values=>[ 'all', + 'addr-all', + 'addr-sender', + 'addr-rcpt', + 'host-all', + 'host-incoming', + 'host-outgoing', + 'msgid', + 'ident', + 'message_id' ], + -labels=>{ 'all' => "Show everything", + 'addr-all' => "Address (All)", + 'addr-sender' => "Address (Sender)", + 'addr-rcpt' => "Address (Recipient)", + 'host-all' => "Host (all)", + 'host-incoming' => "Host (incoming)", + 'host-outgoing' => "Host (outgoing)", + 'msgid' => "Message-ID (Header)", + 'ident' => "Ident String (incoming messages)", + 'message_id' => "Message-ID (Exim)" + }, + -default=>(exists($param->{'qt'}) ? ($param->{'qt'} || 'all') : 'all'), + -onChange=>"javascript:switch_controls(document.getElementById('qt').options[document.getElementById('qt').selectedIndex].value);", + -override=>1}) + ) + ) + ) + . + $q->span({-id=>"term"},'<!-- Dynamic content target DIV -->'). + $q->div({-id=>"term_hidden",-style=>"visibility: hidden; position: absolute;"}, + $q->table({-cellspacing=>0,-cellpadding=>4,-border=>0}, + $q->Tr( + $q->td({-align=>"left",-style=>"width: 16px;"}, + $q->img({-src=>"icons/find.png"}) + ), + $q->td({-align=>"left",-style=>"width: 100px;"}, + "Search Term" + ), + $q->td({-align=>"left"}, + $q->textfield( { -name=>"qs", + -style=>"width: 400px;", + -value=>(exists($param->{'qs'}) ? ($param->{'qs'} || '') : ''), + -override=>1 } ) + ) + ) + ) + ) + . + $q->span({-id=>"events"},'<!-- Dynamic content target DIV -->'). + $q->div({-id=>"events_hidden",-style=>"visibility: hidden; position: absolute;"}, + $q->table({-cellspacing=>0,-cellpadding=>4,-border=>0}, + $q->Tr( + $q->td({-align=>"left",-valign=>"top",-style=>"width: 16px;"}, + $q->img({-src=>"icons/address.png"}) + ), + $q->td({-align=>"left",-valign=>"top",-style=>"width: 100px;"}, + "Event types" + ), + $q->td({-align=>"left",-style=>"padding:2px 4px 4px 4px;"}, + eval { + my @where = ( 'messages', + 'errors', + 'deliveries', + 'deferrals', + 'rejects', + 'queue' + ); + + my $labels = { 'messages' => 'Arrivals', + 'errors' => 'Errors', + 'deliveries' => 'Deliveries', + 'deferrals' => 'Deferrals', + 'rejects' => 'Rejects', + 'queue' => 'Queued' }; + + my $html = ""; + my $num = 0; + foreach my $w (@where) { + if (($num % 3) == 0) { + $html .= '<tr>'; + }; + $html .= $q->td({-width=>"1%",-style=>"padding-right: 4px;"}, + $q->checkbox( { -name=>"qw", + -label=>"", + -checked=>(ina($param->{'qw'},$w) ? 'checked' : undef), + -onDblClick=>"javascript:qw_off_except(this);", + -override=>1, + -value=>$w } ) + ). + $q->td({-style=>"padding-right: 10px;"}, + $labels->{$w} + ); + if (($num % 3) == 2) { + $html .= '</tr>'; + }; + $num++; + } + $q->table({-border=>0,-cellpadding=>0,-cellspacing=>0,-width=>"1%"}, + $html + ); + } + ) + ) + ) + ) + . + $q->span({-id=>"server"},'<!-- Dynamic content target DIV -->'). + $q->div({-id=>"server_hidden",-style=>"visibility: hidden; position: absolute;"}, + $q->table({-cellspacing=>0,-cellpadding=>4,-border=>0}, + $q->Tr( + $q->td({-align=>"left",-valign=>"top",-style=>"width: 16px;"}, + $q->img({-src=>"icons/server.png"}) + ), + $q->td({-align=>"left",-valign=>"top",-style=>"width: 100px;"}, + "Servers" + ), + $q->td({-align=>"left"}, + eval { + my $html =""; + my $num = 0; + my $groups = {}; + foreach my $server (sort {$a cmp $b} keys %{ $config->{servers} }) { + if (($num % 4) == 0) { + $html .= '<tr>'; + }; + $html .= $q->td({-width=>"1%",-style=>"padding-right: 4px;"}, + $q->checkbox( { -name=>"sr", + -label=>"", + -id=>(edt($config->{servers}->{$server},'group') ? $config->{servers}->{$server}->{group} : "-XXX"), + -checked=>(ina($param->{'sr'},$server) ? 'checked' : undef), + -override=>1, + -onDblClick=>"javascript:sr_off_except(this);", + -onChange=>"javascript:sr_changed();", + -value=>$server } ) + ). + $q->td({-width=>"1%",-style=>"padding-right: 10px;"}, + $server + ); + if (($num % 4) == 3) { + $html .= '<td> </td></tr>'; + }; + $num++; + if (edt($config->{servers}->{$server},'group')) { + $groups->{$config->{servers}->{$server}->{group}} = '{'.$config->{servers}->{$server}->{group}.'}'; + }; + }; + if (($num % 4) != 0) { + $html .= '<td> </td>' x ((4-($num % 4))*2); + $html .= '<td> </td></tr>'; + }; + $groups->{'-all'} = 'All servers'; + $groups->{'-custom'} = 'Custom selection'; + $q->table({-border=>0,-cellpadding=>0,-cellspacing=>0,-width=>"1%"}, + $q->Tr( + $q->td({-colspan=>9,-align=>"left",-style=>"padding-bottom: 4px;"}, + $q->popup_menu({ -name=>"ss", + -id=>"ss", + -style=>"width: 400px;", + -values=>[ sort {$a cmp $b} keys(%{$groups}) ], + -labels=>$groups, + -onChange=>"javascript:ss_changed();", + -default=>(exists($param->{'ss'}) ? ($param->{'ss'} || '-all') : '-all'), + -override=>1}) + ) + ), + $html + ); + } + ) + ) + ) + ) + . + $q->span({-id=>"time"},'<!-- Dynamic content target DIV -->'). + $q->div({-id=>"time_hidden",-style=>"visibility: hidden; position: absolute;"}, + $q->table({-cellspacing=>0,-cellpadding=>4,-border=>0}, + $q->Tr( + $q->td({-align=>"left",-style=>"width: 16px;"}, + $q->img({-src=>"icons/timerange.png"}) + ), + $q->td({-align=>"left",-style=>"width: 100px;"}, + "Time Range" + ), + $q->td({-align=>"left"}, + $q->popup_menu({ -name=>"tr", + -id=>"tr", + -style=>"width: 125px;", + -values=>[ 'custom', + '-1m', + '-5m', + '-10m', + '-30m', + '-1h', + '-6h', + '-12h', + '-1d', + '-2d', + '-3d', + '-7d', + '0' ], + -labels=>{ 'custom' => 'Custom', + '-1m' => 'Last minute', + '-5m' => 'Last 5 minutes', + '-10m' => 'Last 10 minutes', + '-30m' => 'Last 30 minutes', + '-1h' => 'Last hour', + '-6h' => 'Last 6 hours', + '-12h' => 'Last 12 hours', + '-1d' => 'Last 24 hours', + '-2d' => 'Last 2 days', + '-3d' => 'Last 3 days', + '-7d' => 'Last 7 days', + '0' => 'Unlimited' }, + -onChange=>"javascript:document.getElementById('tds').value='';document.getElementById('tde').value='';", + -default=>(exists($param->{'tr'}) ? $param->{'tr'} : '-1h'), + -override=>1}) + ), + $q->td({-align=>"left",-style=>"padding-right:0px;"}, + $q->input({-name=>"tds", + -style=>"width: 105px;", + -value=>(exists($param->{'tds'}) ? $param->{'tds'} : ''), + -override=>1, + -onFocus=>"javascript:document.getElementById('tr').selectedIndex = 0; document.getElementById('tde').value='';", + -id=>"tds" }). + $q->button({ -onClick=>"javascript:document.getElementById('tr').selectedIndex = 0; document.getElementById('tde').value=''; cal1x.select(document.forms[0].tds,'anchor1x','yyyy-MM-dd'); return false;", + -name=>"X", + -style=>"height: 18px; width: 18px;", + -id=>"anchor1x" })." - ". + $q->div({ -id=>'caldiv1x', + -style=>"position:absolute;visibility:hidden;background-color:white;layer-background-color:white;" }) + ), + $q->td({-align=>"left",-style=>"padding-left:0px;"}, + $q->input({-name=>"tde", + -style=>"width: 105px;", + -value=>(exists($param->{'tde'}) ? $param->{'tde'} : ''), + -override=>1, + -onFocus=>"javascript:document.getElementById('tr').selectedIndex = 0", + -id=>"tde" }). + $q->button({ -onClick=>"javascript:document.getElementById('tr').selectedIndex = 0; cal2x.select(document.forms[0].tde,'anchor2x','yyyy-MM-dd'); return false;", + -name=>"X", + -style=>"height: 18px; width: 18px;", + -id=>"anchor2x" }). + $q->div({ -id=>'caldiv2x', + -style=>"position:absolute;visibility:hidden;background-color:white;layer-background-color:white;" }) + ) + ) + ) + ) + . + '<hr>' + . + $q->table({-cellspacing=>0,-cellpadding=>4,-border=>0,-align=>"center"}, + $q->Tr( + $q->td({-align=>"center"}, + $q->submit({-name=>"sm",-value=>"Start Query"}) + ) + ) + ) + + ) + ); + + print "\n". + ' + <script language="JavaScript"> + init_controls(); + switch_controls(document.getElementById("qt").options[document.getElementById("qt").selectedIndex].value); + </script> + ' + ."\n"; +}; + +1; diff --git a/cgi/exilog_cgi_param.pm b/cgi/exilog_cgi_param.pm new file mode 100644 index 0000000..5096ee3 --- /dev/null +++ b/cgi/exilog_cgi_param.pm @@ -0,0 +1,74 @@ +#!/usr/bin/perl -w +# +# This file is part of the exilog suite. +# +# http://duncanthrax.net/exilog/ +# +# (c) Tom Kistner 2004 +# +# See LICENSE for licensing information. +# + +package exilog_cgi_param; +use strict; +use exilog_cgi_html; +use exilog_config; + +use Data::Dumper; + +BEGIN { + use Exporter; + use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); + + $VERSION = 0.1; + @ISA = qw(Exporter); + @EXPORT = qw( + $param + ); + + %EXPORT_TAGS = (); + @EXPORT_OK = qw(); + + use vars qw( $param ); +} + +$param = _init_cgi_params(); + +sub _init_cgi_params { + my $param = {}; + + foreach ($q->param) { + my @test = $q->param($_); + + if ((scalar @test) > 1) { + $param->{$_} = \@test; + } + else { + $param->{$_} = $test[0]; + }; + }; + + # defaults + my $defaults = { + 'tab' => 'messages', + 'qw' => [ 'messages', + 'errors', + 'deliveries', + 'deferrals', + 'rejects', + 'queue' ], + 'ss' => '-all', + 'tr' => '-10m', + #'qt' => 'all', + 'qs' => "", + 'sr' => [ keys %{ $config->{servers} } ] + }; + + foreach (keys %{ $defaults }) { + $param->{$_} = $defaults->{$_} unless exists($param->{$_}); + }; + return $param; +}; + + +1; diff --git a/cgi/exilog_cgi_queues.pm b/cgi/exilog_cgi_queues.pm new file mode 100644 index 0000000..ec018a9 --- /dev/null +++ b/cgi/exilog_cgi_queues.pm @@ -0,0 +1,131 @@ +#!/usr/bin/perl -w +# +# This file is part of the exilog suite. +# +# http://duncanthrax.net/exilog/ +# +# (c) Tom Kistner 2004 +# +# See LICENSE for licensing information. +# + +package exilog_cgi_queues; +use exilog_config; +use exilog_cgi_html; +use exilog_cgi_param; +use exilog_sql; +use exilog_util; +use strict; + +BEGIN { + use Exporter; + use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); + + # set the version for version checking + $VERSION = 0.1; + @ISA = qw(Exporter); + @EXPORT = qw( + &queues + ); + + %EXPORT_TAGS = (); + + # your exported package globals go here, + # as well as any optionally exported functions + @EXPORT_OK = qw(); +} + + +sub queues { + _print_Queue_selector(); + my $messages = sql_select('queue',[ '*' ]); + print render_queue_table($messages); +}; + + +sub _print_Queue_selector { + + print + $q->div({-class=>"top_spacer"}, + $q->div({-align=>"left",-style=>"padding: 10px; border: 1px solid black; background: #eeeeee;"}, + + $q->table({-cellspacing=>0,-cellpadding=>4,-border=>0}, + $q->Tr( + $q->td({-align=>"left",-valign=>"top",-style=>"width: 16px;"}, + $q->img({-src=>"icons/server.png"}) + ), + $q->td({-align=>"left",-valign=>"top",-style=>"width: 100px;"}, + "Servers" + ), + $q->td({-align=>"left"}, + eval { + my $html =""; + my $num = 0; + my $groups = {}; + foreach my $server (sort {$a cmp $b} keys %{ $config->{servers} }) { + if (($num % 4) == 0) { + $html .= '<tr>'; + }; + $html .= $q->td({-width=>"1%",-style=>"padding-right: 4px;"}, + $q->checkbox( { -name=>"sr", + -label=>"", + -id=>(edt($config->{servers}->{$server},'group') ? $config->{servers}->{$server}->{group} : "-XXX"), + -checked=>(ina($param->{'sr'},$server) ? 'checked' : undef), + -override=>1, + -onDblClick=>"javascript:sr_off_except(this);", + -onChange=>"javascript:sr_changed();", + -value=>$server } ) + ). + $q->td({-width=>"1%",-style=>"padding-right: 10px;"}, + $server + ); + if (($num % 4) == 3) { + $html .= '<td> </td></tr>'; + }; + $num++; + if (edt($config->{servers}->{$server},'group')) { + $groups->{$config->{servers}->{$server}->{group}} = '{'.$config->{servers}->{$server}->{group}.'}'; + }; + }; + if (($num % 4) != 0) { + $html .= '<td> </td>' x ((4-($num % 4))*2); + $html .= '<td> </td></tr>'; + }; + $groups->{'-all'} = 'All servers'; + $groups->{'-custom'} = 'Custom selection'; + $q->table({-border=>0,-cellpadding=>0,-cellspacing=>0,-width=>"1%"}, + $q->Tr( + $q->td({-colspan=>9,-align=>"left",-style=>"padding-bottom: 4px;"}, + $q->popup_menu({ -name=>"ss", + -id=>"ss", + -style=>"width: 400px;", + -values=>[ sort {$a cmp $b} keys(%{$groups}) ], + -labels=>$groups, + -onChange=>"javascript:ss_changed();", + -default=>(exists($param->{'ss'}) ? ($param->{'ss'} || '-all') : '-all'), + -override=>1}) + ) + ), + $html + ); + }.($@ ? $@ : "") + ) + ) + ) + . + '<hr>' + . + $q->table({-cellspacing=>0,-cellpadding=>4,-border=>0,-align=>"center"}, + $q->Tr( + $q->td({-align=>"center"}, + $q->submit({-name=>"sm",-value=>"Start Query"}) + ) + ) + ) + + ) + ); +}; + +1; + diff --git a/cgi/exilog_cgi_servers.pm b/cgi/exilog_cgi_servers.pm new file mode 100644 index 0000000..424abf1 --- /dev/null +++ b/cgi/exilog_cgi_servers.pm @@ -0,0 +1,102 @@ +#!/usr/bin/perl -w +# +# This file is part of the exilog suite. +# +# http://duncanthrax.net/exilog/ +# +# (c) Tom Kistner 2004 +# +# See LICENSE for licensing information. +# + +package exilog_cgi_servers; +use exilog_config; +use exilog_cgi_html; +use exilog_cgi_param; +use exilog_sql; +use exilog_util; +use strict; + +BEGIN { + use Exporter; + use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); + + # set the version for version checking + $VERSION = 0.1; + @ISA = qw(Exporter); + @EXPORT = qw( + &servers + ); + + %EXPORT_TAGS = (); + + # your exported package globals go here, + # as well as any optionally exported functions + @EXPORT_OK = qw(); +} + + +sub _get_num_queued { + my $server = shift; + my $h = {}; + + $h->{queued} = sql_count('queue',{ 'server' => $server }); + + $h->{frozen} = sql_count('queue',{ 'server' => $server, + 'frozen' => '1' } ); + + $h->{frozen_bounce} = sql_count('queue',{ 'server' => $server, + 'mailfrom' => '<>', + 'frozen' => '1' } ); + + my $tmp = sql_count('queue',{ 'server' => $server, + 'mailfrom' => '<>' } ); + + $h->{deferred} = $h->{queued} - $h->{frozen}; + $h->{deferred_bounce} = $tmp - $h->{frozen_bounce}; + + return $h; +} + +sub _get_h24_stats { + my $server = shift; + my $now = time(); + my $h = {}; + + $h->{arrivals} = int( sql_count( 'messages', + { 'server' => $server, + 'timestamp' => $now-86400 } ) / 1 ); + + $h->{deliveries} = int( sql_count( 'deliveries', + { 'server' => $server, + 'timestamp' => $now-86400 } ) / 1 ); + + $h->{errors} = int( sql_count( 'errors', + { 'server' => $server, + 'timestamp' => $now-86400 } ) / 1 ); + + my $sizes = sql_select( 'messages', [ 'size' ], { 'server' => $server, + 'timestamp' => $now-86400 } ); + + my $total = 0; + foreach (@{ $sizes }) { $total+=$_->{size}; }; + if ((scalar @{ $sizes }) > 0) { + $h->{avg_msg_size} = int($total/(scalar @{ $sizes })); + } + else { + $h->{avg_msg_size} = 0; + }; + + return $h; +} + +sub servers { + + #print $q->div({-style=>"font-size: 28px; font-weight: bold;"},"Basic statictics for all servers"); + + foreach my $server (sort {$a cmp $b} keys %{ $config->{servers} }) { + print render_server($server,_get_num_queued($server),_get_h24_stats($server)); + }; +} + +1; |