Handle hostnames with upper-case letters
[webmin.git] / webminlog / search.cgi
1 #!/usr/local/bin/perl
2 # search.cgi
3 # Find webmin actions
4
5 use strict;
6 use warnings;
7 use Time::Local;
8 require './webminlog-lib.pl';
9 our (%text, %config, %gconfig, $webmin_logfile, %in, $in);
10 &foreign_require("acl", "acl-lib.pl");
11 &ReadParse();
12 &error_setup($text{'search_err'});
13
14 # Use sensible defaults
15 $in{'tall'} = 2 if (!defined($in{'tall'}));
16 $in{'uall'} = 1 if (!defined($in{'uall'}));
17 $in{'mall'} = 1 if (!defined($in{'mall'}));
18 $in{'fall'} = 1 if (!defined($in{'fall'}));
19 $in{'dall'} = 1 if (!defined($in{'dall'}));
20 $in{'wall'} = 1 if (!defined($in{'wall'}));
21
22 # Parse entered time ranges
23 my ($from, $to);
24 if ($in{'tall'} == 2) {
25         # Today
26         my @now = localtime(time());
27         $from = timelocal(0, 0, 0, $now[3], $now[4], $now[5]);
28         $to = timelocal(59, 59, 23, $now[3], $now[4], $now[5]);
29         $in{'tall'} = 0;
30         }
31 elsif ($in{'tall'} == 3) {
32         # Yesterday
33         my @now = localtime(time()-24*60*60);
34         $from = timelocal(0, 0, 0, $now[3], $now[4], $now[5]);
35         $to = timelocal(59, 59, 23, $now[3], $now[4], $now[5]);
36         $in{'tall'} = 0;
37         }
38 elsif ($in{'tall'} == 4) {
39         # Over the last week
40         my @week = localtime(time()-7*24*60*60);
41         $from = timelocal(0, 0, 0, $week[3], $week[4], $week[5]);
42         $to = time();
43         $in{'tall'} = 0;
44         }
45 elsif ($in{'tall'} == 0) {
46         # Some time range
47         $from = &parse_time('from');
48         $to = &parse_time('to');
49         $to = $to ? $to + 24*60*60 - 1 : time();
50         }
51 else {
52         # All time
53         $from = $to = 0;
54         }
55
56 if ($in{'csv'}) {
57         print "Content-type: text/csv\n\n";
58         }
59 else {
60         &ui_print_header(undef, $text{'search_title'}, "");
61         }
62
63 # Perform initial search in index
64 my @match;
65 my %index;
66 &build_log_index(\%index);
67 open(LOG, $webmin_logfile);
68 while(my ($id, $idx) = each %index) {
69         my ($pos, $time, $user, $module, $sid) = split(/\s+/, $idx);
70         $time ||= 0;
71         if (($in{'uall'} == 1 ||
72              $in{'uall'} == 0 && $in{'user'} eq $user ||
73              $in{'uall'} == 3 && $in{'ouser'} eq $user ||
74              $in{'uall'} == 2 && $in{'nuser'} ne $user) &&
75             ($in{'mall'} || $in{'module'} eq $module) &&
76             (!$in{'sid'} || $in{'sid'} eq $sid ||
77                             $in{'sid'} eq &acl::hash_session_id($sid)) &&
78             ($in{'tall'} || $from < $time && $to > $time)) {
79                 # Passed index check .. now look at actual log entry
80                 seek(LOG, $pos, 0);
81                 my $line = <LOG>;
82                 my $act = &parse_logline($line);
83
84                 # Check Webmin server
85                 next if (!$in{'wall'} && $in{'webmin'} ne $act->{'webmin'});
86
87                 # Check modified files
88                 if ($gconfig{'logfiles'} && (!$in{'fall'} || !$in{'dall'})) {
89                         # Make sure the specified file was modified
90                         my $found = 0;
91                         foreach my $d (&list_diffs($act)) {
92                                 my $filematch = $in{'fall'} ||
93                                         $d->{'object'} &&
94                                         $d->{'object'} eq $in{'file'};
95                                 my $diffmatch = $in{'dall'} ||
96                                         $d->{'diff'} =~ /\Q$in{'diff'}\E/i;
97                                 if ($filematch && $diffmatch) {
98                                         $found++;
99                                         last;
100                                         }
101                                 }
102                         next if (!$found);
103                         }
104                 next if (!&can_user($act->{'user'}));
105                 next if (!&can_mod($act->{'module'}));
106                 push(@match, $act);
107                 }
108         }
109 close(LOG);
110
111 # Build search description
112 my $fromstr = &make_date($from, 1);
113 my $tostr = &make_date($to, 1);
114 my %minfo;
115 if (!$in{'mall'}) {
116         %minfo = &get_module_info($in{'module'});
117         }
118 my $searchmsg = join(" ",
119         $in{'uall'} == 0 ? &text('search_critu',
120                  "<tt>".&html_escape($in{'user'})."</tt>") :
121         $in{'uall'} == 3 ? &text('search_critu',
122                  "<tt>".&html_escape($in{'ouser'})."</tt>") :
123         $in{'uall'} == 2 ? &text('search_critnu',
124                  "<tt>".&html_escape($in{'nuser'})."</tt>") : "",
125         $in{'mall'} ? '' : &text('search_critm',
126                  "<tt>".&html_escape($minfo{'desc'})."</tt>"),
127         $in{'tall'} ? '' : 
128           $fromstr eq $tostr ? &text('search_critt2', $tostr) :
129             &text('search_critt', $fromstr, $tostr));
130
131 my %minfo_cache;
132 if ($in{'csv'}) {
133         # Show search results as CSV
134         my @cols;
135         foreach my $act (sort { $b->{'time'} <=> $a->{'time'} } @match) {
136                 my $m = $act->{'module'};
137                 my $minfo = $m eq "global" ? 
138                                 { 'desc' => $text{'search_global'} } :
139                                 $minfo_cache{$m};
140                 if (!$minfo) {
141                         my %minfo = &get_module_info($m);
142                         $minfo = $minfo_cache{$m} = \%minfo;
143                         }
144                 my $desc = &get_action_description($act, $in{'long'});
145                 $desc =~ s/<[^>]+>//g;
146                 @cols = ( $desc, 
147                           $minfo->{'desc'},
148                           $act->{'user'},
149                           $act->{'ip'} );
150                 if ($config{'host_search'}) {
151                         push(@cols, $act->{'webmin'});
152                         }
153                 push(@cols, &make_date($act->{'time'}));
154                 print join(",", map { "\"$_\"" } @cols),"\n";
155                 }
156         }
157 elsif (@match) {
158         # Show search results in table
159         if ($in{'sid'}) {
160                 print "<b>",&text('search_sid', "<tt>$match[0]->{'user'}</tt>",
161                                   "<tt>$in{'sid'}</tt>")," ..</b><p>\n";
162                 }
163         elsif ($in{'uall'} == 1 && $in{'mall'} && $in{'tall'}) {
164                 print "<b>$text{'search_critall'} ..</b><p>\n";
165                 }
166         else {
167                 my %minfo = &get_module_info($in{'module'}) if (!$in{'mall'});
168                 print "<b>$text{'search_crit'} $searchmsg ...</b><p>\n";
169                 }
170         print &ui_columns_start(
171                 [ $text{'search_action'},
172                   $text{'search_module'},
173                   $text{'search_user'},
174                   $text{'search_host'},
175                   $config{'host_search'} ? ( $text{'search_webmin'} ) : ( ),
176                   $text{'search_date'},
177                   $text{'search_time'} ], "100");
178         foreach my $act (sort { $b->{'time'} <=> $a->{'time'} } @match) {
179                 my @tm = localtime($act->{'time'});
180                 my $m = $act->{'module'};
181                 my $d;
182                 my $minfo = $m eq "global" ? 
183                                 { 'desc' => $text{'search_global'} } :
184                                 $minfo_cache{$m};
185                 if (!$minfo) {
186                         # first time seeing module ..
187                         my %minfo = &get_module_info($m);
188                         $minfo = $minfo_cache{$m} = \%minfo;
189                         }
190
191                 my @cols;
192                 my $desc = &get_action_description($act, $in{'long'});
193                 my $anno = &get_annotation($act);
194                 push(@cols, "<a href='view.cgi?id=$act->{'id'}".
195                       "&return=".&urlize($in{'return'} || "").
196                       "&returndesc=".&urlize($in{'returndesc'} || "").
197                       "&search=".&urlize($in || "").
198                       "'>$desc</a>");
199                 if ($anno) {
200                         $cols[$#cols] .= "&nbsp;<img src=images/star.gif>";
201                         }
202                 push(@cols, $minfo->{'desc'}, $act->{'user'}, $act->{'ip'});
203                 if ($config{'host_search'}) {
204                         push(@cols, $act->{'webmin'});
205                         }
206                 push(@cols, split(/\s+/, &make_date($act->{'time'})));
207                 print &ui_columns_row(\@cols);
208                 }
209         print &ui_columns_end();
210         print "<a href='search.cgi/webminlog.csv?$in&csv=1'>$text{'search_csv'}</a><p>\n";
211         }
212 else {
213         # Tell the user that nothing matches
214         print "<p><b>$text{'search_none2'} $searchmsg.</b><p>\n";
215         }
216
217 if (!$in{'csv'}) {
218         # Show page footer
219         if ($in{'return'}) {
220                 &ui_print_footer($in{'return'}, $in{'returndesc'});
221                 }
222         else {
223                 &ui_print_footer("", $text{'index_return'});
224                 }
225         }
226
227 sub parse_time
228 {
229 my $d = $in{"$_[0]_d"};
230 my $m = $in{"$_[0]_m"};
231 my $y = $in{"$_[0]_y"};
232 return 0 if (!$d && !$y);
233 my $rv;
234 eval { $rv = timelocal(0, 0, 0, $d, $m, $y-1900) };
235 &error($text{'search_etime'}) if ($@);
236 return $rv;
237 }