337 lines
8.1 KiB
Perl
337 lines
8.1 KiB
Perl
|
package SrvMngr::Controller::Viewlogfiles;
|
||
|
|
||
|
#----------------------------------------------------------------------
|
||
|
# heading : Investigation
|
||
|
# description : View log files
|
||
|
# navigation : 7000 100
|
||
|
#
|
||
|
#
|
||
|
# routes : end
|
||
|
#----------------------------------------------------------------------
|
||
|
use strict;
|
||
|
use warnings;
|
||
|
use Mojo::Base 'Mojolicious::Controller';
|
||
|
|
||
|
#use esmith::FormMagick qw(gen_locale_date_string);
|
||
|
|
||
|
use Locale::gettext;
|
||
|
use SrvMngr::I18N;
|
||
|
use SrvMngr qw(theme_list init_session);
|
||
|
|
||
|
use esmith::ConfigDB;
|
||
|
use Time::TAI64;
|
||
|
use File::Basename;
|
||
|
use HTML::Entities;
|
||
|
use esmith::FormMagick qw(gen_locale_date_string);
|
||
|
use File::Temp qw(tempfile);
|
||
|
|
||
|
use constant TRUE => 1;
|
||
|
use constant FALSE => 0;
|
||
|
|
||
|
our $cdb = esmith::ConfigDB->open() || die "Couldn't open config db";
|
||
|
|
||
|
our @logfiles = (); # with array
|
||
|
|
||
|
|
||
|
sub main {
|
||
|
my $c = shift;
|
||
|
$c->app->log->info($c->log_req);
|
||
|
|
||
|
my %log_datas = ();
|
||
|
my $title = $c->l('log_FORM_TITLE');
|
||
|
my $notif = '';
|
||
|
|
||
|
$log_datas{default_op} =
|
||
|
($cdb->get('viewlogfiles')->prop('DefaultOperation')) || 'view';
|
||
|
|
||
|
$c->stash( title => $title, notif => $notif, log_datas => \%log_datas);
|
||
|
$c->render(template => 'viewlogfiles');
|
||
|
};
|
||
|
|
||
|
|
||
|
sub do_action {
|
||
|
my $c = shift;
|
||
|
$c->app->log->info($c->log_req);
|
||
|
|
||
|
my $title = $c->l('log_FORM_TITLE');
|
||
|
my $notif = '';
|
||
|
my $result = "";
|
||
|
my %log_datas = ();
|
||
|
|
||
|
$log_datas{filename} = $c->param('Filename');
|
||
|
$log_datas{matchpattern} = $c->param('Matchpattern');
|
||
|
$log_datas{highlightpattern} = $c->param('Highlightpattern');
|
||
|
$log_datas{operation} = $c->param('Operation');
|
||
|
if ($log_datas{operation} eq 'download') {
|
||
|
$log_datas{'trt'} = "DOWN"
|
||
|
} else {
|
||
|
$log_datas{'trt'} = "SHOW"
|
||
|
}
|
||
|
|
||
|
|
||
|
if ($log_datas{filename} =~ /^([\S\s]+)$/) {
|
||
|
$log_datas{filename} = $1;
|
||
|
} elsif ($log_datas{filename} =~ /^$/) {
|
||
|
$log_datas{filename} = "messages";
|
||
|
} else {
|
||
|
$result .= $c->l("log_FILENAME_ERROR", $log_datas{filename} )
|
||
|
. " ";
|
||
|
}
|
||
|
|
||
|
if ($log_datas{matchpattern} =~ /^(\S+)$/) {
|
||
|
$log_datas{matchpattern} = $1;
|
||
|
} else {
|
||
|
$log_datas{matchpattern} = ".";
|
||
|
}
|
||
|
|
||
|
if ($log_datas{highlightpattern} =~ /^(\S+)$/) {
|
||
|
$log_datas{highlightpattern} = $1;
|
||
|
} else {
|
||
|
$log_datas{highlightpattern} = '';
|
||
|
}
|
||
|
|
||
|
my $fullpath = "/var/log/$log_datas{filename}";
|
||
|
if (-z $fullpath) {
|
||
|
$result .= $c->l("log_LOG_FILE_EMPTY", "$log_datas{filename}" );
|
||
|
}
|
||
|
|
||
|
if ($log_datas{trt} eq "SHOW") {
|
||
|
if ( ! $result ) {
|
||
|
$result = $c->render_to_string(inline => showlogFile( $c, %log_datas ));
|
||
|
}
|
||
|
|
||
|
if ( $result ) {
|
||
|
$c->stash(title => $title, modul => $result, log_datas => \%log_datas);
|
||
|
return $c->render(template => 'viewlogfiles2');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
if ( $log_datas{trt} eq 'DOWN' ) {
|
||
|
|
||
|
my $modul = 'Log file download';
|
||
|
|
||
|
$notif = download_logFile( $c, %log_datas );
|
||
|
return undef unless defined $notif;
|
||
|
}
|
||
|
|
||
|
$c->stash( title => $title, notif => $notif, log_datas => \%log_datas);
|
||
|
$c->render(template => 'viewlogfiles');
|
||
|
|
||
|
};
|
||
|
|
||
|
|
||
|
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 $c = shift;
|
||
|
|
||
|
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);
|
||
|
push @logfiles, [ $file_path . timestamp2local($file_base), $path ];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
#$logfiles{$path} = $path;
|
||
|
push @logfiles, [ $path, $path ];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@logfiles = ();
|
||
|
# Now go and find all the files under /var/log
|
||
|
find({wanted => \&findlogfiles, no_chdir => 1}, '/var/log');
|
||
|
|
||
|
my @logf = sort { $a->[0] cmp $b->[0] } @logfiles;
|
||
|
|
||
|
return \@logf;
|
||
|
}
|
||
|
|
||
|
|
||
|
sub showlogFile {
|
||
|
|
||
|
my ($c, %log_datas) = @_;
|
||
|
|
||
|
my $fullpath = "/var/log/$log_datas{filename}";
|
||
|
my $out = '';
|
||
|
|
||
|
$out .= sprintf("$fullpath: \n");
|
||
|
|
||
|
$out .= sprintf($c->l("log_VIEWING_TIME", $c->gen_locale_date_string() ));
|
||
|
|
||
|
unless ( $log_datas{matchpattern} eq '.' )
|
||
|
{
|
||
|
#$out .= sprintf("<p>\n");
|
||
|
$out .= sprintf($c->l("log_MATCH_HEADER", $log_datas{matchpattern} ));
|
||
|
}
|
||
|
|
||
|
if ( $log_datas{highlightpattern} )
|
||
|
{
|
||
|
#$out .= sprintf("<p>\n");
|
||
|
$out .= sprintf($c->l("log_HIGHLIGHT_HEADER", "$log_datas{highlightpattern}" ));
|
||
|
}
|
||
|
|
||
|
if ($log_datas{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;
|
||
|
$out .= sprintf("<PRE>");
|
||
|
while(<LOGFILE>)
|
||
|
{
|
||
|
$fileEmpty = 0;
|
||
|
next unless /$log_datas{matchpattern}/;
|
||
|
$somethingMatched = 1;
|
||
|
|
||
|
$_ = timestamp2local($_);
|
||
|
$_ = HTML::Entities::encode_entities($_);
|
||
|
($log_datas{highlightpattern} && /$log_datas{highlightpattern}/)
|
||
|
? $out .= sprintf( "<b>$_</b>" )
|
||
|
: $out .= sprintf("$_");
|
||
|
}
|
||
|
$out .= sprintf("</PRE>");
|
||
|
|
||
|
if ($fileEmpty) {
|
||
|
$out .= sprintf("<p>\n");
|
||
|
$out .= sprintf($c->l("log_LOG_FILE_EMPTY"));
|
||
|
} else {
|
||
|
unless ($somethingMatched)
|
||
|
{
|
||
|
$out .= sprintf("<p>\n");
|
||
|
$out .= sprintf($c->l("log_NO_MATCHING_LINES"));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
close LOGFILE;
|
||
|
|
||
|
return $out;
|
||
|
}
|
||
|
|
||
|
|
||
|
sub download_logFile {
|
||
|
|
||
|
my ($c, %log_datas) = @_;
|
||
|
|
||
|
my $fullpath = "/var/log/$log_datas{filename}";
|
||
|
|
||
|
# Save this information for later.
|
||
|
|
||
|
$cdb->get('viewlogfiles')->merge_props('DefaultOperation', $log_datas{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 = $c->l("log_ERR_NOEXIST_FILE") . $fullpath;
|
||
|
}
|
||
|
|
||
|
local *FILE;
|
||
|
open(FILE, "<$fullpath")
|
||
|
or $error = $c->l("log_ERR_NOOPEN_FILE");
|
||
|
# Put other error checking here.
|
||
|
return $error if $error;
|
||
|
|
||
|
# Fix the filename, as it might have a directory prefixed to it.
|
||
|
my $filename = $log_datas{filename};
|
||
|
if ( $filename =~ m#/# ) {
|
||
|
$filename = (split /\//, $filename)[-1];
|
||
|
}
|
||
|
|
||
|
# And send the file.
|
||
|
my $nl = "\n";
|
||
|
if ($win32) { $nl = "\r\n" }
|
||
|
elsif ($mac) { $nl = "\r" }
|
||
|
|
||
|
# 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.
|
||
|
|
||
|
my $file2 = new File::Temp( UNLINK => 0 );
|
||
|
|
||
|
while (my $line = <FILE>) {
|
||
|
chomp $line;
|
||
|
my $linew = timestamp2local($line) . $nl;
|
||
|
print $file2 $linew;
|
||
|
}
|
||
|
close(FILE);
|
||
|
|
||
|
$c->render_file(
|
||
|
'filepath' => "$file2",
|
||
|
'filename' => "$filename",
|
||
|
'format' => 'x-download',
|
||
|
'content_disposition' => 'attachment',
|
||
|
'cleanup' => 1,
|
||
|
);
|
||
|
|
||
|
return undef;
|
||
|
}
|
||
|
|
||
|
|
||
|
1;
|
||
|
|