#! /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 (? \&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 "
\n"; print $fm->localise("MATCH_HEADER", { matchPattern => "$matchPattern" } ); } if ( $highlightPattern ) { print "
\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 "
";
    while()
    {
        $fileEmpty = 0;
        next unless /$matchPattern/;
        $somethingMatched = 1;
        $_ = timestamp2local($_);
        $_ = HTML::Entities::encode_entities($_);
        ($highlightPattern && /$highlightPattern/)
        ? print "$_"
        : print;
    }
    print " ";
    if ($fileEmpty)
    {
        print "\n"; print $fm->localise("LOG_FILE_EMPTY"); } else { unless ($somethingMatched) { print "
\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 =