#!/usr/local/bin/perl # search.cgi # Find webmin actions use strict; use warnings; use Time::Local; require './webminlog-lib.pl'; our (%text, %config, %gconfig, $webmin_logfile, %in, $in); &foreign_require("acl", "acl-lib.pl"); &ReadParse(); &error_setup($text{'search_err'}); # Use sensible defaults $in{'tall'} = 2 if (!defined($in{'tall'})); $in{'uall'} = 1 if (!defined($in{'uall'})); $in{'mall'} = 1 if (!defined($in{'mall'})); $in{'fall'} = 1 if (!defined($in{'fall'})); $in{'dall'} = 1 if (!defined($in{'dall'})); $in{'wall'} = 1 if (!defined($in{'wall'})); # Parse entered time ranges my ($from, $to); if ($in{'tall'} == 2) { # Today my @now = localtime(time()); $from = timelocal(0, 0, 0, $now[3], $now[4], $now[5]); $to = timelocal(59, 59, 23, $now[3], $now[4], $now[5]); $in{'tall'} = 0; } elsif ($in{'tall'} == 3) { # Yesterday my @now = localtime(time()-24*60*60); $from = timelocal(0, 0, 0, $now[3], $now[4], $now[5]); $to = timelocal(59, 59, 23, $now[3], $now[4], $now[5]); $in{'tall'} = 0; } elsif ($in{'tall'} == 4) { # Over the last week my @week = localtime(time()-7*24*60*60); $from = timelocal(0, 0, 0, $week[3], $week[4], $week[5]); $to = time(); $in{'tall'} = 0; } elsif ($in{'tall'} == 0) { # Some time range $from = &parse_time('from'); $to = &parse_time('to'); $to = $to ? $to + 24*60*60 - 1 : time(); } else { # All time $from = $to = 0; } if ($in{'csv'}) { print "Content-type: text/csv\n\n"; } else { &ui_print_header(undef, $text{'search_title'}, ""); } # Perform initial search in index my @match; my %index; &build_log_index(\%index); open(LOG, $webmin_logfile); while(my ($id, $idx) = each %index) { my ($pos, $time, $user, $module, $sid) = split(/\s+/, $idx); $time ||= 0; if (($in{'uall'} == 1 || $in{'uall'} == 0 && $in{'user'} eq $user || $in{'uall'} == 3 && $in{'ouser'} eq $user || $in{'uall'} == 2 && $in{'nuser'} ne $user) && ($in{'mall'} || $in{'module'} eq $module) && (!$in{'sid'} || $in{'sid'} eq $sid || $in{'sid'} eq &acl::hash_session_id($sid)) && ($in{'tall'} || $from < $time && $to > $time)) { # Passed index check .. now look at actual log entry seek(LOG, $pos, 0); my $line = ; my $act = &parse_logline($line); # Check Webmin server next if (!$in{'wall'} && $in{'webmin'} ne $act->{'webmin'}); # Check modified files if ($gconfig{'logfiles'} && (!$in{'fall'} || !$in{'dall'})) { # Make sure the specified file was modified my $found = 0; foreach my $d (&list_diffs($act)) { my $filematch = $in{'fall'} || $d->{'object'} && $d->{'object'} eq $in{'file'}; my $diffmatch = $in{'dall'} || $d->{'diff'} =~ /\Q$in{'diff'}\E/i; if ($filematch && $diffmatch) { $found++; last; } } next if (!$found); } next if (!&can_user($act->{'user'})); next if (!&can_mod($act->{'module'})); push(@match, $act); } } close(LOG); # Build search description my $fromstr = &make_date($from, 1); my $tostr = &make_date($to, 1); my %minfo; if (!$in{'mall'}) { %minfo = &get_module_info($in{'module'}); } my $searchmsg = join(" ", $in{'uall'} == 0 ? &text('search_critu', "".&html_escape($in{'user'})."") : $in{'uall'} == 3 ? &text('search_critu', "".&html_escape($in{'ouser'})."") : $in{'uall'} == 2 ? &text('search_critnu', "".&html_escape($in{'nuser'})."") : "", $in{'mall'} ? '' : &text('search_critm', "".&html_escape($minfo{'desc'}).""), $in{'tall'} ? '' : $fromstr eq $tostr ? &text('search_critt2', $tostr) : &text('search_critt', $fromstr, $tostr)); my %minfo_cache; if ($in{'csv'}) { # Show search results as CSV my @cols; foreach my $act (sort { $b->{'time'} <=> $a->{'time'} } @match) { my $m = $act->{'module'}; my $minfo = $m eq "global" ? { 'desc' => $text{'search_global'} } : $minfo_cache{$m}; if (!$minfo) { my %minfo = &get_module_info($m); $minfo = $minfo_cache{$m} = \%minfo; } my $desc = &get_action_description($act, $in{'long'}); $desc =~ s/<[^>]+>//g; @cols = ( $desc, $minfo->{'desc'}, $act->{'user'}, $act->{'ip'} ); if ($config{'host_search'}) { push(@cols, $act->{'webmin'}); } push(@cols, &make_date($act->{'time'})); print join(",", map { "\"$_\"" } @cols),"\n"; } } elsif (@match) { # Show search results in table if ($in{'sid'}) { print "",&text('search_sid', "$match[0]->{'user'}", "$in{'sid'}")," ..

\n"; } elsif ($in{'uall'} == 1 && $in{'mall'} && $in{'tall'}) { print "$text{'search_critall'} ..

\n"; } else { my %minfo = &get_module_info($in{'module'}) if (!$in{'mall'}); print "$text{'search_crit'} $searchmsg ...

\n"; } print &ui_columns_start( [ $text{'search_action'}, $text{'search_module'}, $text{'search_user'}, $text{'search_host'}, $config{'host_search'} ? ( $text{'search_webmin'} ) : ( ), $text{'search_date'}, $text{'search_time'} ], "100"); foreach my $act (sort { $b->{'time'} <=> $a->{'time'} } @match) { my @tm = localtime($act->{'time'}); my $m = $act->{'module'}; my $d; my $minfo = $m eq "global" ? { 'desc' => $text{'search_global'} } : $minfo_cache{$m}; if (!$minfo) { # first time seeing module .. my %minfo = &get_module_info($m); $minfo = $minfo_cache{$m} = \%minfo; } my @cols; my $desc = &get_action_description($act, $in{'long'}); my $anno = &get_annotation($act); push(@cols, "$desc"); if ($anno) { $cols[$#cols] .= " "; } push(@cols, $minfo->{'desc'}, $act->{'user'}, $act->{'ip'}); if ($config{'host_search'}) { push(@cols, $act->{'webmin'}); } push(@cols, split(/\s+/, &make_date($act->{'time'}))); print &ui_columns_row(\@cols); } print &ui_columns_end(); print "$text{'search_csv'}

\n"; } else { # Tell the user that nothing matches print "

$text{'search_none2'} $searchmsg.

\n"; } if (!$in{'csv'}) { # Show page footer if ($in{'return'}) { &ui_print_footer($in{'return'}, $in{'returndesc'}); } else { &ui_print_footer("", $text{'index_return'}); } } sub parse_time { my $d = $in{"$_[0]_d"}; my $m = $in{"$_[0]_m"}; my $y = $in{"$_[0]_y"}; return 0 if (!$d && !$y); my $rv; eval { $rv = timelocal(0, 0, 0, $d, $m, $y-1900) }; &error($text{'search_etime'}) if ($@); return $rv; }