Handle hostnames with upper-case letters
[webmin.git] / syslog / syslog-lib.pl
1 # syslog-lib.pl
2 # Functions for the syslog module
3
4 BEGIN { push(@INC, ".."); };
5 use WebminCore;
6 &init_config();
7 %access = &get_module_acl();
8
9 # get_config([file])
10 # Parses the syslog configuration file into an array ref of hash refs, one
11 # for each log file or destination
12 sub get_config
13 {
14 local ($cfile) = @_;
15 $cfile ||= $config{'syslog_conf'};
16 local $lnum = 0;
17 local ($line, $cont, @rv);
18 local $tag = { 'tag' => '*',
19                'index' => 0,
20                'line' => 0 };
21 push(@rv, $tag);
22 &open_readfile(CONF, $cfile);
23 local @lines = <CONF>;
24 close(CONF);
25 foreach my $line (@lines) {
26         local $slnum = $lnum;
27         $line =~ s/\r|\n//g;
28         if ($line =~ /\\$/) {
29                 # continuation .. get the next lines
30                 $line =~ s/\\$//;
31                 while($cont = <CONF>) {
32                         $lnum++;
33                         $cont =~ s/^[#\s]+//;
34                         $cont =~ s/\r|\n//g;
35                         $line .= $cont;
36                         last if ($line !~ s/\\$//);
37                         }
38                 }
39         if ($line =~ /^\$IncludeConfig\s+(\S+)/) {
40                 # rsyslog include statement .. follow the money
41                 foreach my $icfile (glob($1)) {
42                         my $ic = &get_config($icfile);
43                         if ($ic) {
44                                 foreach my $c (@$ic) {
45                                         $c->{'index'} += scalar(@rv);
46                                         }
47                                 push(@rv, @$ic);
48                                 }
49                         }
50                 }
51         elsif ($line =~ /^\$(\S+)\s*(\S*)/) {
52                 # rsyslog special directive - ignored for now
53                 }
54         elsif ($line =~ /^if\s+/) {
55                 # rsyslog if statement .. ignored too
56                 }
57         elsif ($line =~ /^(#*)\s*([^#\s]+\.\S+)\s+(\S+)$/ ||
58                $line =~ /^(#*)\s*([^#\s]+\.\S+)\s+(\|.*)$/) {
59                 # Regular log destination
60                 local $act = $3;
61                 local $log = { 'active' => !$1,
62                                'sel' => [ split(/;/, $2) ],
63                                'cfile' => $cfile,
64                                'line' => $slnum,
65                                'eline' => $lnum };
66                 if ($act =~ /^\-(\/\S+)$/) {
67                         $log->{'file'} = $1;
68                         $log->{'sync'} = 0;
69                         }
70                 elsif ($act =~ /^\|(.*)$/) {
71                         $log->{'pipe'} = $1;
72                         }
73                 elsif ($act =~ /^(\/\S+)$/) {
74                         $log->{'file'} = $1;
75                         $log->{'sync'} = 1;
76                         }
77                 elsif ($act =~ /^\@\@(\S+)$/) {
78                         $log->{'socket'} = $1;
79                         }
80                 elsif ($act =~ /^\@(\S+)$/) {
81                         $log->{'host'} = $1;
82                         }
83                 elsif ($act eq '*') {
84                         $log->{'all'} = 1;
85                         }
86                 else {
87                         $log->{'users'} = [ split(/,/, $act) ];
88                         }
89                 $log->{'index'} = scalar(@rv);
90                 $log->{'section'} = $tag;
91                 $tag->{'eline'} = $lnum;
92                 if ($log->{'file'} =~ s/^(\/\S+);(\S+)$/$1/ ||
93                     $log->{'pipe'} =~ s/^(\/\S+);(\S+)$/$1/) {
94                         # rsyslog file format
95                         $log->{'format'} = $2;
96                         }
97                 push(@rv, $log);
98                 }
99         elsif ($line =~ /^(#?)!(\S+)$/) {
100                 # Start of tagged section, as seen on BSD
101                 push(@rv, { 'tag' => $2,
102                             'index' => scalar(@rv),
103                             'cfile' => $cfile,
104                             'line' => $lnum,
105                             'eline' => $lnum });
106                 $tag = $rv[@rv-1];
107                 }
108         $lnum++;
109         }
110 return \@rv;
111 }
112
113 # create_log(&log)
114 sub create_log
115 {
116 local $lref = &read_file_lines($config{'syslog_conf'});
117 if ($config{'tags'}) {
118         splice(@$lref, $_[0]->{'section'}->{'eline'}+1, 0, &log_line($_[0]));
119         }
120 else {
121         push(@$lref, &log_line($_[0]));
122         }
123 &flush_file_lines();
124 }
125
126 # update_log(&old, &log)
127 sub update_log
128 {
129 local $lref = &read_file_lines($_[0]->{'cfile'} || $config{'syslog_conf'});
130 if ($config{'tags'} && $_[0]->{'section'} ne $_[1]->{'section'}) {
131         if ($_[0]->{'section'}->{'line'} < $_[1]->{'section'}->{'line'}) {
132                 splice(@$lref, $_[1]->{'section'}->{'eline'}+1, 0,
133                        &log_line($_[1]));
134                 splice(@$lref, $_[0]->{'line'},
135                        $_[0]->{'eline'} - $_[0]->{'line'} + 1);
136                 }
137         else {
138                 splice(@$lref, $_[0]->{'line'},
139                        $_[0]->{'eline'} - $_[0]->{'line'} + 1);
140                 splice(@$lref, $_[1]->{'section'}->{'eline'}+1, 0,
141                        &log_line($_[1]));
142                 }
143         }
144 else {
145         splice(@$lref, $_[0]->{'line'}, $_[0]->{'eline'} - $_[0]->{'line'} + 1,
146                &log_line($_[1]));
147         }
148 &flush_file_lines();
149 }
150
151 # delete_log(&log)
152 sub delete_log
153 {
154 local $lref = &read_file_lines($_[0]->{'cfile'} || $config{'syslog_conf'});
155 splice(@$lref, $_[0]->{'line'}, $_[0]->{'eline'} - $_[0]->{'line'} + 1);
156 &flush_file_lines();
157 }
158
159 sub log_line
160 {
161 local $d;
162 if ($_[0]->{'file'}) {
163         $d = ($_[0]->{'sync'} || !$config{'sync'} ? "" : "-").$_[0]->{'file'};
164         }
165 elsif ($_[0]->{'pipe'}) {
166         $d = '|'.$_[0]->{'pipe'};
167         }
168 elsif ($_[0]->{'host'}) {
169         $d = '@'.$_[0]->{'host'};
170         }
171 elsif ($_[0]->{'users'}) {
172         $d = join(",", @{$_[0]->{'users'}});
173         }
174 elsif ($_[0]->{'socket'}) {
175         $d = '@@'.$_[0]->{'socket'};
176         }
177 else {
178         $d = '*';
179         }
180 if ($_[0]->{'format'}) {
181         # Add rsyslog format
182         $d .= ";".$_[0]->{'format'};
183         }
184 return ($_[0]->{'active'} ? "" : "#").join(";", @{$_[0]->{'sel'}})."\t".$d;
185 }
186
187 # list_priorities()
188 # Returns a list of all priorities
189 sub list_priorities
190 {
191 return ( 'debug', 'info', 'notice', 'warning',
192          'err', 'crit', 'alert', 'emerg' );
193 }
194
195 # can_edit_log(&log)
196 # Returns 1 if some log can be viewed/edited, 0 if not
197 sub can_edit_log
198 {
199 return 1 if (!$access{'logs'});
200 local @files = split(/\s+/, $access{'logs'});
201 local $lf;
202 if (ref($_[0])) {
203         $lf = $_[0]->{'file'} || $_[0]->{'pipe'} || $_[0]->{'host'} ||
204               $_[0]->{'socket'} || $_[0]->{'cmd'} ||
205               ($_[0]->{'all'} ? "*" : "users");
206         }
207 else {
208         $lf = $_[0];
209         }
210 foreach $f (@files) {
211         return 1 if ($f eq $lf || &is_under_directory($f, $lf));
212         }
213 return 0;
214 }
215
216 sub needs_m4
217 {
218 local $oldslash = $/;
219 $/ = undef;
220 &open_readfile(CONF, $config{'syslog_conf'});
221 local $conf1 = <CONF>;
222 close(CONF);
223 &open_execute_command(CONF, "$config{'m4_path'} $config{'syslog_conf'}", 1, 1);
224 local $conf2 = <CONF>;
225 close(CONF);
226 $/ = $oldslash;
227 return $conf1 ne $conf2;
228 }
229
230 # get_syslog_pid(pid)
231 # Returns the syslog PID file
232 sub get_syslog_pid
233 {
234 local $pid;
235 if ($config{'pid_file'}) {
236         if (&open_readfile(PID, $config{'pid_file'}) &&
237             <PID> =~ /^(\d+)$/ && kill(0, $1)) {
238                 $pid = $1;
239                 }
240         }
241 else {
242         ($pid) = &find_byname("syslogd");
243         }
244 return $pid;
245 }
246
247 # restart_syslog()
248 # Stop and re-start the syslog server. Returns an error message on failure.
249 sub restart_syslog
250 {
251 if ($config{'restart_cmd'}) {
252         &system_logged("$config{'restart_cmd'} >/dev/null 2>/dev/null </dev/null");
253         }
254 else {
255         local $pid = &get_syslog_pid();
256         $pid && &kill_logged('TERM', $pid) ||
257                 return &text('restart_ekill', $pid, $!);
258         sleep(2);
259         if ($config{'start_cmd'}) {
260                 &system_logged("$config{'start_cmd'} >/dev/null 2>/dev/null </dev/null");
261                 }
262         else {
263                 &system_logged("cd / ; $config{'syslogd'} >/dev/null 2>/dev/null </dev/null &");
264                 }
265         }
266 return undef;
267 }
268
269 # signal_syslog()
270 # Tell the syslog server to re-open it's log files
271 sub signal_syslog
272 {
273 if ($config{'signal_cmd'}) {
274         &system_logged("$config{'signal_cmd'} >/dev/null 2>/dev/null </dev/null");
275         }
276 else {
277         # Use HUP signal
278         local $pid = &get_syslog_pid();
279         if ($pid) {
280                 &kill_logged('HUP', $pid);
281                 }
282         }
283 }
284
285 # all_log_files(file)
286 # Given a filename, returns all rotated versions, ordered by oldest first
287 sub all_log_files
288 {
289 $_[0] =~ /^(.*)\/([^\/]+)$/;
290 local $dir = $1;
291 local $base = $2;
292 local ($f, @rv);
293 opendir(DIR, &translate_filename($dir));
294 foreach $f (readdir(DIR)) {
295         local $trans = &translate_filename("$dir/$f");
296         if ($f =~ /^\Q$base\E/ && -f $trans && $f !~ /\.offset$/) {
297                 push(@rv, "$dir/$f");
298                 $mtime{"$dir/$f"} = [ stat($trans) ];
299                 }
300         }
301 closedir(DIR);
302 return sort { $mtime{$a}->[9] <=> $mtime{$b}->[9] } @rv;
303 }
304
305 # get_other_module_logs([module])
306 # Returns a list of logs supplied by other modules
307 sub get_other_module_logs
308 {
309 local ($mod) = @_;
310 local @rv;
311 local %done;
312 foreach my $minfo (&get_all_module_infos()) {
313         next if ($mod && $minfo->{'dir'} ne $mod);
314         next if (!$minfo->{'syslog'});
315         next if (!&foreign_installed($minfo->{'dir'}));
316         local $mdir = &module_root_directory($minfo->{'dir'});
317         next if (!-r "$mdir/syslog_logs.pl");
318         &foreign_require($minfo->{'dir'}, "syslog_logs.pl");
319         local $j = 0;
320         foreach my $l (&foreign_call($minfo->{'dir'}, "syslog_getlogs")) {
321                 local $fc = $l->{'file'} || $l->{'cmd'};
322                 next if ($done{$fc}++);
323                 $l->{'minfo'} = $minfo;
324                 $l->{'mod'} = $minfo->{'dir'};
325                 $l->{'mindex'} = $j++;
326                 push(@rv, $l);
327                 }
328         }
329 @rv = sort { $a->{'minfo'}->{'desc'} cmp $b->{'minfo'}->{'desc'} } @rv;
330 local $i = 0;
331 foreach my $l (@rv) {
332         $l->{'index'} = $i++;
333         }
334 return @rv;
335 }
336
337 # catter_command(file)
338 # Given a file that may be compressed, returns the command to output it in
339 # plain text, or undef if impossible
340 sub catter_command
341 {
342 local ($l) = @_;
343 local $q = quotemeta($l);
344 if ($l =~ /\.gz$/i) {
345         return &has_command("gunzip") ? "gunzip -c $q" : undef;
346         }
347 elsif ($l =~ /\.Z$/i) {
348         return &has_command("uncompress") ? "uncompress -c $q" : undef;
349         }
350 elsif ($l =~ /\.bz2$/i) {
351         return &has_command("bunzip2") ? "bunzip2 -c $q" : undef;
352         }
353 else {
354         return "cat $q";
355         }
356 }
357
358 # extra_log_files()
359 # Returns a list of extra log files available to the current Webmin user. No filtering
360 # based on allowed directory is done though!
361 sub extra_log_files
362 {
363 local @rv;
364 foreach my $fd (split(/\t+/, $config{'extras'}), split(/\t+/, $access{'extras'})) {
365         if ($fd =~ /^(\S+)\s+(\S.*)$/) {
366                 push(@rv, { 'file' => $1, 'desc' => $2 });
367                 }
368         else {
369                 push(@rv, { 'file' => $fd });
370                 }
371         }
372 return @rv;
373 }
374
375 1;
376