478 lines
13 KiB
Plaintext
478 lines
13 KiB
Plaintext
|
#! /usr/bin/perl -wT
|
||
|
|
||
|
#----------------------------------------------------------------------
|
||
|
# heading : Administration
|
||
|
# description : View log files
|
||
|
# navigation : 4000 4400
|
||
|
#
|
||
|
# $Id: viewlogfiles,v 1.31 2005/08/25 21:08:41 charlieb Exp $
|
||
|
#----------------------------------------------------------------------
|
||
|
# copyright (C) 1999-2007 Mitel Networks Corporation
|
||
|
#
|
||
|
# This program is free software; you can redistribute it and/or modify
|
||
|
# it under the terms of the GNU General Public License as published by
|
||
|
# the Free Software Foundation; either version 2 of the License, or
|
||
|
# (at your option) any later version.
|
||
|
#
|
||
|
# This program is distributed in the hope that it will be useful,
|
||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
# GNU General Public License for more details.
|
||
|
#
|
||
|
# You should have received a copy of the GNU General Public License
|
||
|
# along with this program; if not, write to the Free Software
|
||
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||
|
#
|
||
|
#----------------------------------------------------------------------
|
||
|
use strict;
|
||
|
use esmith::FormMagick;
|
||
|
|
||
|
use esmith::ConfigDB;
|
||
|
use esmith::cgi;
|
||
|
use Time::TAI64;
|
||
|
use File::Basename;
|
||
|
use HTML::Entities;
|
||
|
use CGI;
|
||
|
|
||
|
use constant TRUE => 1;
|
||
|
use constant FALSE => 0;
|
||
|
|
||
|
my $viewlogfiles = esmith::ConfigDB->open->get('viewlogfiles');
|
||
|
|
||
|
my $fm = esmith::FormMagick->new();
|
||
|
|
||
|
my $q = CGI->new();
|
||
|
|
||
|
# Save this operation preference for later.
|
||
|
my $operation = $q->param('operation');
|
||
|
$viewlogfiles->merge_props('DefaultOperation', $operation)
|
||
|
if $operation;
|
||
|
|
||
|
if ($operation and $operation eq 'download')
|
||
|
{
|
||
|
perform_download($fm, $q);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
#$fm->debug(1);
|
||
|
$fm->display();
|
||
|
}
|
||
|
|
||
|
=pod
|
||
|
|
||
|
=head1 NAME
|
||
|
|
||
|
viewlogfiles -- An interface to system log files, allowing for pattern
|
||
|
filtering and highlighting.
|
||
|
|
||
|
=head2 DESCRIPTION
|
||
|
|
||
|
This screen allows the administrator to flexibly view various system log files.
|
||
|
|
||
|
=begin testing
|
||
|
|
||
|
use esmith::FormMagick::Tester;
|
||
|
use esmith::TestUtils;
|
||
|
use esmith::ConfigDB;
|
||
|
use esmith::AccountsDB;
|
||
|
|
||
|
my $panel = $Original_File;
|
||
|
my $ua = esmith::FormMagick::Tester->new();
|
||
|
|
||
|
my $c = esmith::ConfigDB->open();
|
||
|
my $a = esmith::AccountsDB->open();
|
||
|
|
||
|
is (mode($panel), '4755', "Check permissions on script");
|
||
|
ok ($ua->get_panel($panel), "ABOUT TO RUN L10N TESTS");
|
||
|
is ($ua->{status}, 200, "200 OK");
|
||
|
ok ($ua->set_language("en-us"), "Set language to English");
|
||
|
ok ($ua->get_panel($panel), "Get panel");
|
||
|
is ($ua->{status}, 200, "200 OK");
|
||
|
like($ua->{content}, qr/View log files/, "Saw translated form title");
|
||
|
|
||
|
# View the messages log:
|
||
|
|
||
|
$ua->field("filename" => "messages");
|
||
|
$ua->field(highlightPattern => "");
|
||
|
$ua->field(matchPattern => "");
|
||
|
ok ($ua->click("View log file"), "Click View log file");
|
||
|
is ($ua->{status}, 200, "200 OK");
|
||
|
like($ua->{content}, qr/Viewed at/, "Saw validation messages");
|
||
|
|
||
|
# View the messages log and filter all output:
|
||
|
|
||
|
ok ($ua->get_panel($panel), "Get panel");
|
||
|
$ua->field("filename" => "messages");
|
||
|
$ua->field(highlightPattern => "gibberish-gibberish");
|
||
|
$ua->field(matchPattern => "gibberish-gibberish");
|
||
|
ok ($ua->click("View log file"), "Click View log file");
|
||
|
is ($ua->{status}, 200, "200 OK");
|
||
|
like($ua->{content}, qr/No matching lines/, "Saw validation messages");
|
||
|
|
||
|
=end testing
|
||
|
|
||
|
=cut
|
||
|
|
||
|
#------------------------------------------------------------
|
||
|
# subroutine to display initial form
|
||
|
#------------------------------------------------------------
|
||
|
|
||
|
our %logfiles = ();
|
||
|
|
||
|
sub timestamp2local
|
||
|
{
|
||
|
$_ = shift;
|
||
|
if (/^(\@[0-9a-f]{24})(.*)/s)
|
||
|
{
|
||
|
return Time::TAI64::tai64nlocal($1) . $2;
|
||
|
}
|
||
|
elsif (/^([0-9]{10}\.[0-9]{3})(.*)/s)
|
||
|
{
|
||
|
return localtime($1) . $2;
|
||
|
}
|
||
|
return $_;
|
||
|
}
|
||
|
|
||
|
sub findlogFiles
|
||
|
{
|
||
|
my $fm = shift;
|
||
|
my $q = $fm->{cgi};
|
||
|
|
||
|
use File::Find;
|
||
|
sub findlogfiles
|
||
|
{
|
||
|
my $path = $File::Find::name;
|
||
|
|
||
|
if (-f)
|
||
|
{
|
||
|
# Remove leading /var/log/messages
|
||
|
$path =~ s:^/var/log/::;
|
||
|
# don't bother to collect files known to be non-text
|
||
|
# or not log files
|
||
|
foreach (qw(
|
||
|
lastlog
|
||
|
btmp$
|
||
|
wtmp
|
||
|
lock
|
||
|
(?<!qpsmtpd/)state
|
||
|
httpd/ssl_mutex.\d*
|
||
|
httpd/ssl_scache.pag
|
||
|
httpd/ssl_scache.dir
|
||
|
\/config$
|
||
|
))
|
||
|
{
|
||
|
return if $path =~ /$_/;
|
||
|
}
|
||
|
|
||
|
my ($file_base, $file_path, $file_type) = fileparse($path);
|
||
|
|
||
|
if ( $file_base =~ /@.*/ )
|
||
|
{
|
||
|
$logfiles{$path} = $file_path . timestamp2local($file_base);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
$logfiles{$path} = $path;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# Now go and find all the files under /var/log
|
||
|
find({wanted => \&findlogfiles, no_chdir => 1}, '/var/log');
|
||
|
|
||
|
return \%logfiles;
|
||
|
}
|
||
|
|
||
|
#------------------------------------------------------------
|
||
|
# subroutine to perform actions and display result
|
||
|
#------------------------------------------------------------
|
||
|
|
||
|
sub performAndShowResult ($)
|
||
|
{
|
||
|
my $fm = shift;
|
||
|
my $q = $fm->{cgi};
|
||
|
|
||
|
#------------------------------------------------------------
|
||
|
# Verify the arguments and untaint the variables (see Camel
|
||
|
# book, "Detecting and laundering tainted data", pg. 358)
|
||
|
#------------------------------------------------------------
|
||
|
my $filename = $q->param ('filename');
|
||
|
if ($filename =~ /^([\S\s]+)$/)
|
||
|
{
|
||
|
$filename = $1;
|
||
|
}
|
||
|
elsif ($filename =~ /^$/)
|
||
|
{
|
||
|
$filename = "messages";
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
print $fm->localise("FILENAME_ERROR", { filename => "$filename" } );
|
||
|
return;
|
||
|
}
|
||
|
my $matchPattern = $q->param ('matchPattern');
|
||
|
if ($matchPattern =~ /^(\S+)$/)
|
||
|
{
|
||
|
$matchPattern = $1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
$matchPattern = ".";
|
||
|
}
|
||
|
my $highlightPattern = $q->param ('highlightPattern');
|
||
|
if ($highlightPattern =~ /^(\S+)$/)
|
||
|
{
|
||
|
$highlightPattern = $1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
$highlightPattern = '';
|
||
|
}
|
||
|
|
||
|
#------------------------------------------------------------
|
||
|
# Looks good; go ahead and generate the report.
|
||
|
#------------------------------------------------------------
|
||
|
|
||
|
my $fullpath = "/var/log/$filename";
|
||
|
|
||
|
if (-z $fullpath)
|
||
|
{
|
||
|
print $fm->localise("LOG_FILE_EMPTY", { filename => "$filename" } );
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
print "$fullpath: \n";
|
||
|
|
||
|
print $fm->localise("VIEWING_TIME",
|
||
|
{ time => $fm->gen_locale_date_string() } );
|
||
|
|
||
|
unless ( $matchPattern eq '.' )
|
||
|
{
|
||
|
print "<p>\n";
|
||
|
print $fm->localise("MATCH_HEADER", { matchPattern => "$matchPattern" } );
|
||
|
}
|
||
|
|
||
|
if ( $highlightPattern )
|
||
|
{
|
||
|
print "<p>\n";
|
||
|
print $fm->localise("HIGHLIGHT_HEADER", { highlightPattern => "$highlightPattern" } );
|
||
|
}
|
||
|
|
||
|
if ($filename =~ /\.gz$/)
|
||
|
{
|
||
|
my $pid = open(LOGFILE, "-|");
|
||
|
die "Couldn't fork: $!" unless defined $pid;
|
||
|
unless ($pid)
|
||
|
{
|
||
|
# Child
|
||
|
exec("/bin/zcat", $fullpath)
|
||
|
|| die "Can't exec zcat: $!";
|
||
|
# NOTREACHED
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
open(LOGFILE, "$fullpath");
|
||
|
}
|
||
|
|
||
|
my $somethingMatched = 0;
|
||
|
my $fileEmpty = 1;
|
||
|
print "<PRE>";
|
||
|
while(<LOGFILE>)
|
||
|
{
|
||
|
$fileEmpty = 0;
|
||
|
next unless /$matchPattern/;
|
||
|
$somethingMatched = 1;
|
||
|
|
||
|
$_ = timestamp2local($_);
|
||
|
$_ = HTML::Entities::encode_entities($_);
|
||
|
($highlightPattern && /$highlightPattern/)
|
||
|
? print "<b>$_</b>"
|
||
|
: print;
|
||
|
}
|
||
|
print "</PRE>";
|
||
|
|
||
|
if ($fileEmpty)
|
||
|
{
|
||
|
print "<p>\n";
|
||
|
print $fm->localise("LOG_FILE_EMPTY");
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
unless ($somethingMatched)
|
||
|
{
|
||
|
print "<p>\n";
|
||
|
print $fm->localise("NO_MATCHING_LINES");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
close LOGFILE;
|
||
|
print $q->table({-width => '100%'},
|
||
|
$q->Tr($q->th({-class => 'sme-layout'},
|
||
|
$q->a( { -href => "viewlogfiles?page=0&Next=viewLog" .
|
||
|
"&filename=$filename&matchPattern=$matchPattern" .
|
||
|
"&highlightPattern=$highlightPattern" .
|
||
|
"&operation=view",
|
||
|
-class => 'button-like'},
|
||
|
$fm->localise('REFRESH')))));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
sub print_viewlog_buttons
|
||
|
{
|
||
|
my $self = shift;
|
||
|
my $q = $self->{cgi};
|
||
|
my $filename = $q->param('filename');
|
||
|
my $matchPattern = $q->param('matchPattern');
|
||
|
my $highlightPattern = $q->param('highlightPattern');
|
||
|
|
||
|
print $q->table({-width => '100%'},
|
||
|
$q->Tr({-valign => 'center'},
|
||
|
$q->th({-class => 'sme-layout', -valign => 'center'},
|
||
|
$q->a( { -href => "viewlogfiles?page=0&Next=viewLog" .
|
||
|
"&filename=$filename&matchPattern=$matchPattern" .
|
||
|
"&highlightPattern=$highlightPattern" .
|
||
|
"&skip_header=1",
|
||
|
-class => 'button-like'},
|
||
|
$self->localise('DOWNLOAD')),
|
||
|
$q->submit( {-value => $self->localise('VIEW') } ))));
|
||
|
return undef;
|
||
|
}
|
||
|
|
||
|
sub perform_download
|
||
|
{
|
||
|
my $fm = shift;
|
||
|
my $q = shift;
|
||
|
my $filename = $q->param("filename");
|
||
|
my $fullpath = "/var/log/$filename";
|
||
|
|
||
|
# Save this information for later.
|
||
|
my $operation = $q->param('operation');
|
||
|
$viewlogfiles->merge_props('DefaultOperation', $operation);
|
||
|
|
||
|
# If the client is on windows, we must handle this a little differently.
|
||
|
my $win32 = FALSE;
|
||
|
my $mac = FALSE;
|
||
|
my $agent = $ENV{HTTP_USER_AGENT} || "";
|
||
|
if ($agent =~ /win32|windows/i)
|
||
|
{
|
||
|
$win32 = TRUE;
|
||
|
}
|
||
|
elsif ($agent =~ /mac/i)
|
||
|
{
|
||
|
$mac = TRUE;
|
||
|
}
|
||
|
|
||
|
# Check for errors first. Once we start sending the file it's too late to
|
||
|
# report them.
|
||
|
my $error = "";
|
||
|
unless (-f $fullpath)
|
||
|
{
|
||
|
$error = $fm->localise("ERR_NOEXIST_FILE");
|
||
|
}
|
||
|
local *FILE;
|
||
|
open(FILE, "<$fullpath")
|
||
|
or $error = $fm->localise("ERR_NOOPEN_FILE");
|
||
|
# Put other error checking here.
|
||
|
if ($error)
|
||
|
{
|
||
|
# FIXME: Add the header and footer template references here.
|
||
|
print <<"EOF";
|
||
|
Content-Type: text/html
|
||
|
|
||
|
$error
|
||
|
EOF
|
||
|
return undef;
|
||
|
}
|
||
|
# Fix the filename, as it might have a directory prefixed to it.
|
||
|
if ($filename =~ m#/#)
|
||
|
{
|
||
|
$filename = (split /\//, $filename)[-1];
|
||
|
}
|
||
|
|
||
|
# Otherwise, send the file. Start with the headers.
|
||
|
# Note: The Content-disposition must be attachment, or IE will view the
|
||
|
# file inline like it's told. It ignores the Content-type, but it likes
|
||
|
# the Content-disposition (an officially unsupported header) for some
|
||
|
# reason. Yay Microsoft.
|
||
|
print <<"EOF";
|
||
|
Expires: 0
|
||
|
Content-type: application/octet-stream
|
||
|
Content-disposition: attachment; filename=$filename
|
||
|
|
||
|
EOF
|
||
|
# And send the file.
|
||
|
my $nl = "\n";
|
||
|
if ($win32) { $nl = "\r\n" }
|
||
|
elsif ($mac) { $nl = "\r" }
|
||
|
while (my $line = <FILE>)
|
||
|
{
|
||
|
chomp $line;
|
||
|
print timestamp2local($line) . $nl;
|
||
|
}
|
||
|
close(FILE);
|
||
|
|
||
|
return undef;
|
||
|
}
|
||
|
|
||
|
sub show_operation_widget
|
||
|
{
|
||
|
my $self = shift;
|
||
|
my $q = $self->{cgi};
|
||
|
my $description = $self->localise('OP_DESC');
|
||
|
my $label = $self->localise('OP_LABEL');
|
||
|
|
||
|
my $defaultop = $viewlogfiles->prop('DefaultOperation');
|
||
|
my $select = '<select name="operation" size="1">' . "\n";
|
||
|
if ($defaultop eq 'view')
|
||
|
{
|
||
|
$select .= '<option value="view" selected>' .
|
||
|
$self->localise("VIEW") . '</option>' . "\n";
|
||
|
$select .= '<option value="download">' .
|
||
|
$self->localise("DOWNLOAD") . '</option>' . "\n";
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
$select .= '<option value="view">' .
|
||
|
$self->localise("VIEW") . '</option>' . "\n";
|
||
|
$select .= '<option value="download" selected>' .
|
||
|
$self->localise("DOWNLOAD") . '</option>' . "\n";
|
||
|
}
|
||
|
$select .= '</select>' . "\n";
|
||
|
|
||
|
print $q->Tr($q->td({-colspan => 2}, $description));
|
||
|
print $q->Tr($q->td({-class => 'sme-noborders-label'}, $label),
|
||
|
$q->td({-class => 'sme-noborders-content'}, $select));
|
||
|
|
||
|
|
||
|
return undef;
|
||
|
}
|
||
|
|
||
|
__DATA__
|
||
|
<form title="View log files" header="/etc/e-smith/web/common/head.tmpl" footer="/etc/e-smith/web/common/foot.tmpl">
|
||
|
<page name="Initial"
|
||
|
pre-event="print_status_message()">
|
||
|
<description>FIRSTPAGE_DESC</description>
|
||
|
<field type="select" id="filename" options="findlogFiles()"
|
||
|
value="messages">
|
||
|
<label>LOG_FILE_SELECT_DESC</label>
|
||
|
</field>
|
||
|
<field type="text" id="matchPattern">
|
||
|
<description>FILTER_PATTERN_DESC</description>
|
||
|
<label>FILTER_PATTERN_LABEL</label>
|
||
|
</field>
|
||
|
<field type="text" id="highlightPattern">
|
||
|
<description>MATCH_PATTERN_DESC</description>
|
||
|
<label>MATCH_PATTERN_LABEL</label>
|
||
|
</field>
|
||
|
<subroutine src="show_operation_widget()" />
|
||
|
<field type="literal">
|
||
|
<description>END_DESC</description>
|
||
|
</field>
|
||
|
<subroutine src="print_button('NEXT')" />
|
||
|
</page>
|
||
|
<page name="viewLog" pre-event="turn_off_buttons()">
|
||
|
<subroutine src="performAndShowResult()" />
|
||
|
</page>
|
||
|
</form>
|