"Fossies" - the Fresh Open Source Software Archive 
Member "timbersee-0.8.3/timbersee.in" (20 Mar 2005, 30453 Bytes) of package /linux/privat/old/timbersee-0.8.3.tar.gz:
As a special service "Fossies" has tried to format the requested text file into HTML format (style:
standard) with prefixed line numbers.
Alternatively you can here
view or
download the uninterpreted source code file.
1 #! @PERL@ -w
2
3 # Copyright 2001-2005, Buttsoft International. All rights reserved.
4 # Author: B. Thomas Adler <thumper@alumni.caltech.edu>
5
6 use vars qw($config_file $libdir $prefix $sysconfdir $datadir
7 $exec_prefix $bindir $pkgdatadir);
8 BEGIN {
9 $prefix = '@prefix@';
10 $sysconfdir = "@sysconfdir@";
11 $datadir = "@datadir@";
12 $config_file = "${sysconfdir}/timbersee.config";
13 $libdir = "${datadir}/@PACKAGE@";
14 $exec_prefix = "@prefix@";
15 $bindir = "@bindir@";
16 $pkgdatadir = "${datadir}/@PACKAGE@";
17 }
18
19 use strict qw(vars subs);
20 use lib ($libdir);
21 use Getopt::Long;
22 use POSIX;
23 use File::Tail;
24 use Term::ANSIColor;
25 use Log::Agent;
26 use Mail::Internet;
27 use Net::SMTP;
28
29 use vars qw($VERSION %optctl);
30
31
32 $VERSION = '@VERSION@';
33
34 GetOptions(\%optctl,
35 "config=s",
36 "daemon",
37 "debug",
38 "quit",
39 "version", "help");
40
41 # Figure out options
42 foreach (qw(version help)) {
43 eval "&$_(\\%optctl)" if defined $optctl{$_};
44 }
45
46 # Read in config file...
47 $config_file = $optctl{'config'} if defined $optctl{'config'};
48
49 &read_config($config_file);
50
51 exit(0) if defined $optctl{'quit'};
52
53 ##############################################################################
54 use vars qw( $config $tails @tails $restart);
55
56 umask 077;
57 $SIG{PIPE} = 'IGNORE'; # ignore aborts
58 $SIG{CHLD} = 'IGNORE'; # ignore children processes
59
60 select(STDERR);
61 $| = 1;
62 select(STDOUT);
63 $| = 1;
64
65 die "$0: Can't define both --daemon and --debug.\n"
66 if defined $optctl{'daemon'} && defined $optctl{'debug'};
67 &background() if defined $optctl{'daemon'};
68
69 $SIG{HUP} = \&restart;
70
71 my ($nfound, $timeleft, @pending);
72 while (1) {
73 while (!$restart) {
74 ($nfound,$timeleft,@pending)=
75 File::Tail::select(undef,undef,undef,10,@tails);
76 foreach (@pending) {
77 my $line = sprintf("%17s: %s", trim17($_->{'input'}),
78 scalar($_->read));
79 logdie "Whoa - no object" if !exists $tails->{$_};
80 my $sub = $tails->{$_};
81 &$sub($line);
82 }
83 }
84 $restart = 0;
85
86 # Do an eval on rereading the config file, because if there's
87 # an error, we have to trap it (so that it can possibly go
88 # to the syslog). When we called &read_config the first time,
89 # we weren't in daemon mode yet, so sending the error to stderr
90 # was okay.
91 eval { &read_config($config_file); };
92 if ($@) {
93 logdie "$@";
94 }
95 logwarn "reread configuration file.";
96 }
97
98 exit 0;
99
100 ##########################################################################
101 sub trim17 {
102 my $str = shift @_;
103 $str = '...'.substr($str, length($str) - 14) if length($str) > 17;
104 return $str;
105 }
106
107 sub restart {
108 $restart = 1;
109 }
110
111 sub background {
112 my $pid = fork;
113 die "ERROR: Unable to fork: $!\n" if !defined $pid;
114 if ($pid != 0) {
115 warn "Backgrounded with PID#$pid\n";
116 exit 0;
117 }
118
119 my $progname = $0;
120 $progname =~ s#^.*/##;
121
122 require Log::Agent::Driver::Syslog;
123 logconfig(-driver => Log::Agent::Driver::Syslog->make(
124 -facility => "daemon",
125 -logopt => 'ndelay',
126 -socktype => 'unix',
127 -prefix => $progname,
128 -showpid => 1,
129 ),
130 -level => 10,
131 );
132
133 POSIX::setsid() || logdie "Unable to detach from terminal: $!\n";
134 close(STDOUT);
135 open(STDOUT, ">/dev/null");
136 close(STDERR);
137 open(STDERR, ">/dev/null");
138 close(STDIN);
139 open(STDIN, "</dev/null");
140 }
141
142 sub read_config {
143 my ($config_file) = @_;
144
145 logdie "Config file '$config_file' does not exist.\n" if !-e $config_file;
146
147 # First, validate the config file against the DTD
148 use XML::DOM::ValParser;
149 my $parser = new XML::DOM::ValParser(ErrorContext=>2, KeepCDATA => 1);
150 local $XML::Checker::FAIL = \&parseFail;
151 my $doc = $parser->parsefile($config_file);
152
153 # And then read it in and transform it into code
154 use XML::Parser;
155 $parser = new XML::Parser(ErrorContext=>2,
156 Handlers => { Start => \&Start,
157 End => \&End,
158 Char => \&Char } );
159 $parser->parsefile($config_file);
160 undef $parser; # we're done with the parser, now.
161 }
162
163 sub parseFail {
164 use XML::Checker;
165 my $code = shift;
166 die "Error: ".XML::Checker::error_string ($code, @_) if $code < 200;
167 XML::Checker::print_error ($code, @_);
168 }
169
170
171
172 ##########################################################################
173 use vars qw($current_log $echo_warning);
174
175 $echo_warning = 0;
176
177 sub Start {
178 no strict 'refs';
179 my $expat = shift;
180 my $tag = shift;
181 my $sub = "open_$tag";
182 &$sub($expat, $tag, @_);
183 }
184
185 sub End {
186 no strict 'refs';
187 my $expat = shift;
188 my $tag = shift;
189 my $sub = "close_${tag}";
190 &$sub($expat, $tag);
191 }
192
193 sub open_config {
194 my ($expat, $element, %attr) = @_;
195 my (%hash);
196 $config = \%hash;
197
198 # define the hash entry where static variables will be declared.
199 $config->{''} = {};
200 }
201
202 sub close_config {
203 my ($expat, $element) = @_;
204 my (%tails);
205 $tails = \%tails;
206 @tails = ();
207
208 # Create a Tail object for every logfile...
209 foreach my $log (keys %$config) {
210 next if $log eq ''; # skip static variables
211 my $obj = File::Tail->new(name=>$log, interval=>2, maxinterval=>10);
212 push(@tails, $obj);
213 my $static_section = $config->{''}->{$log};
214 eval $static_section. '$tails->{$obj} = '.$config->{$log};
215 die "$@" if ($@);
216 }
217
218 # and get rid of our config object, since we've converted it
219 # into an elaborate sub.
220 undef $config;
221 }
222
223 sub open_mailconfig {
224 my ($expat, $element, %attr) = @_;
225
226 my $smtphosts = delete $attr{'smtphosts'};
227 $expat->xpcroak("empty list of SMTP hosts")
228 if !defined $smtphosts || $smtphosts eq '';
229 $ENV{SMTPHOSTS} = $smtphosts;
230 my $from = delete $attr{'from'};
231 if ($from ne '') {
232 $ENV{MAILADDRESS} = $from;
233 }
234
235 my $hello = delete $attr{'hello'};
236
237 $ENV{SMTPHELLO} = $hello if defined $hello;
238
239 my $authuser = delete $attr{'authuser'};
240 my $authpass = delete $attr{'authpass'};
241
242 return if !defined $authuser && !defined $authpass;
243
244 $expat->xpcroak("'authuser' and 'authpass' must be specified together")
245 if !defined $authuser || !defined $authpass;
246 $ENV{SMTPAUTH} = [ $authuser, $authpass ];
247 }
248
249 sub close_mailconfig { }
250
251 sub open_log {
252 my ($expat, $element, %attr) = @_;
253
254 my $file = delete $attr{'file'};
255
256 $expat->xpcroak("empty log file name")
257 if !defined $file || $file eq '';
258 $expat->xpcroak("duplicate log file block")
259 if exists $config->{$file};
260 $expat->xpcroak("non-existant logfile")
261 if !-e $file;
262 $expat->xpcroak("no permission to read logfile")
263 if !-r $file;
264
265 # Declare an empty static variable section
266 $config->{''}->{$file} = '';
267 # and declare the beginning of the actual sub implementing this logfile
268 $config->{$file} = 'sub { my $line = shift @_; ';
269 $current_log = $file;
270 }
271
272 sub close_log {
273 $config->{$current_log} .= "}";
274 undef $current_log;
275 }
276
277 sub protect_re {
278 my $re = shift @_;
279 $re =~ s/#/\\#/g;
280 return $re;
281 }
282
283 sub open_ignore {
284 my ($expat, $element, %attr) = @_;
285
286 my $re = delete $attr{'re'};
287 $re = &protect_re($re);
288 $config->{$current_log} .= "return if \$line =~ m#$re#o; ";
289 }
290 sub close_ignore { }
291
292 sub open_onmatch {
293 my ($expat, $element, %attr) = @_;
294 my $re = delete $attr{'re'};
295 $re = &protect_re($re);
296 $config->{$current_log} .= "if (\$line =~ m#$re#o) { ";
297 }
298 sub close_onmatch {
299 $config->{$current_log} .= "} ";
300 }
301
302 sub open_waitfor {
303 my ($expat, $element, %attr) = @_;
304 my $count = delete $attr{'count'};
305 $expat->xpcroak("count attribute should be a positive integer")
306 if $count < 1;
307 $expat->xpcroak("count attribute should be an integer")
308 if $count != int($count);
309
310 my $timeout = delete $attr{'timeout'};
311 $timeout = -1 if !defined $timeout;
312 $expat->xpcroak("timeout attribute should be an integer")
313 if $timeout != int($timeout);
314
315 my $initial = delete $attr{'initial'};
316 $initial = 0 if !defined $initial;
317 $expat->xpcroak("initial attribute should be an integer")
318 if $initial != int($initial);
319
320 # To implement this, we need a static variable...
321 my $varname = '$static_' . int(rand(1000000));
322 $config->{''}->{$current_log} .=
323 "my $varname = WaitFor->new($initial, $count, $timeout); ";
324 # and the actual subroutine is very simple...
325 $config->{$current_log} .= "if ($varname->inc()) { "
326 }
327
328 sub close_waitfor {
329 $config->{$current_log} .= "} ";
330 }
331
332 sub open_bell {
333 my ($expat, $element, %attr) = @_;
334 my $count = delete $attr{'count'};
335 $count = "1" if !defined $count;
336 $expat->xpcroak("'count' must be a number greater than 0")
337 if ($count !~ m/\d+/) || ($count <= 0);
338
339 # Check to see if the user asked for --daemon mode, which conflicts
340 # with an <echo/> or <bell/> command.
341 if (defined $optctl{'daemon'} && !$echo_warning) {
342 warn "<echo/> and <bell/> commands will have no effect in --daemon mode.\n";
343 $echo_warning = 1;
344 }
345 $config->{$current_log} .= "&ring_bell($count); "
346 if !defined $optctl{'daemon'};
347 }
348 sub close_bell { }
349
350 sub ring_bell {
351 my ($count) = @_;
352 return if defined $optctl{'daemon'}; # no <echo/> in --daemon mode
353 print "\a" x $count;
354 }
355
356 sub open_echo {
357 my ($expat, $element, %attr) = @_;
358 my $mode = delete $attr{'mode'};
359 $mode = '' if !defined $mode;
360 eval {
361 my $verify = color($mode); # verify that it's a valid mode
362 };
363 if ($@) {
364 # Ah, there was an error in the mode specification...
365 # Clean up the error so that it's presentable to the user.
366 $@ =~ s# at .* line \d+[\n\r]+##;
367 $expat->xpcroak($@);
368 }
369
370 my $msg = delete $attr{'msg'};
371 my $interp = delete $attr{'interpolation'};
372 if (defined $interp) {
373 $expat->xpcroak("Need 'msg' attribute to interpolate")
374 if !defined $msg;
375 if ($interp =~ m/^(yes|true)$/i) {
376 # set to 'q', so that below we do qq{$msg}
377 $interp = 'q';
378 # BUT - only allow regular expression substitutions!
379 # We need to escape backslashes, curly braces, and dollar signs!
380 $msg =~ s#\\#\\\\#g;
381 $msg =~ s#\{#\\\{#g;
382 $msg =~ s#\}#\\\}#g;
383 $msg =~ s#\@#\\\@#g;
384 $msg =~ s#\$(?)#\\\$#g;
385 } else { $interp = ''; }
386 } else {
387 # Don't do variable interpolation
388 $interp = '';
389 }
390 # If the user specified a message, then that's what we should echo.
391 # Otherwise, the default is to echo the line from the logfile.
392 if (defined $msg) {
393 $msg = $interp.'q{'.$msg.'}';
394 } else {
395 $msg = '$line';
396 }
397
398 $config->{$current_log} .= "&echo('$mode', $msg); "
399 if (!defined $optctl{'daemon'});
400 # Check to see if the user asked for --daemon mode, which conflicts
401 # with an <echo/> or <bell/> command.
402 if (defined $optctl{'daemon'} && !$echo_warning) {
403 warn "<echo/> and <bell/> commands will have no effect in --daemon mode.\n";
404 $echo_warning = 1;
405 }
406 }
407
408 sub close_echo { }
409
410 sub echo {
411 my ($mode, $line) = @_;
412
413 return if defined $optctl{'daemon'}; # no <echo/> in --daemon mode
414
415 chomp($line);
416
417 # clean up control chars from output
418 $line =~ s/\000/<NUL>/g;
419 $line =~ s/\033/<ESC>/g;
420 $line =~ s/\177/<DEL>/g;
421 $line =~ s/([\001-\037])/'<^'.chr(ord($1)+64).'>'/eg;
422
423 print color($mode) if $mode ne '';
424 print $line;
425 print color('reset') if $mode ne '';
426 print "\n";
427 }
428
429 sub open_last {
430 my ($expat, $element, %attr) = @_;
431 $config->{$current_log} .= "return; ";
432 }
433 sub close_last { }
434
435 sub open_mailto {
436 my ($expat, $element, %attr) = @_;
437 my $subject = delete $attr{'subject'};
438 my $recipients = delete $attr{'recipients'};
439 $config->{$current_log} .= "&email(q{$subject}, q{$recipients}, \$line); ";
440 }
441 sub close_mailto { }
442
443 sub email {
444 my ($subject, $recipients, $msg) = @_;
445
446 my $mail = Mail::Internet->new( Body => [ $msg ] );
447
448 my $header = $mail->head();
449 $header->replace('Subject', $subject);
450 $header->replace('Date', strftime("%a, %d %b %Y %H:%M:%S %z", localtime));
451
452
453 # We might need to specify auth info for the SMTP connection,
454 # so we need to make the Net::SMTP object here.
455 my @hosts = qw(mailhost localhost);
456 unshift(@hosts, split(/:/, $ENV{SMTPHOSTS})) if defined $ENV{SMTPHOSTS};
457 my @args;
458 push @args, 'Debug' => 1 if defined $optctl{'debug'};
459 push @args, 'Hello' => $ENV{SMTPHELLO} if defined $ENV{SMTPHELLO};
460
461 my $smtp;
462 foreach my $host (@hosts) {
463 $smtp = eval { Net::SMTP->new($host, @args) };
464 last if defined $smtp;
465 }
466 if (!defined $smtp) {
467 logerr "ERROR: cannot connect to smtphost";
468 return;
469 }
470
471 my $auth = $ENV{SMTPAUTH};
472 if (ref($auth) eq 'ARRAY') {
473 $smtp->auth(@$auth); # pass in the username/password
474 }
475
476 my @recp = $mail->smtpsend(To=>$recipients, Host=>$smtp);
477
478 logerr "Unable to send email to $recipients" if scalar(@recp) == 0;
479 }
480
481 sub open_exec {
482 my ($expat, $element, %attr) = @_;
483 my $cmdline = delete $attr{'cmdline'};
484 my $user = delete $attr{'user'};
485 my $group = delete $attr{'group'};
486 my ($uid, $gid) = ('undef', 'undef');
487 if (defined $user) {
488 $uid = getpwnam($user); # convert to UID
489 logdie "Invalid username '$user' in <exec/>\n" if !defined $uid;
490 }
491 if (defined $group) {
492 $gid = getgrnam($group); # convert to GID
493 logdie "Invalid group '$group' in <exec/>\n" if !defined $gid;
494 }
495
496 my $sendline = delete $attr{'sendline'};
497 if (defined $sendline && ($sendline eq 'true')) { $sendline = '$line'; }
498 else { $sendline = 'undef'; }
499
500 # WARNING: Do not do interpolation for $cmdline. If we allow
501 # variable substitution (eg. $1, $2, or worse: $line)
502 # then there's a large risk that a hacker could trick
503 # timbersee into running arbitrary code. -thumper! 24-Nov-2001
504 $config->{$current_log} .= "&runprog(q{$cmdline}, $uid, $gid, $sendline); ";
505 }
506 sub close_exec { }
507
508 sub runprog {
509 my ($cmdline, $uid, $gid, $stdin) = @_;
510 if (fork() == 0) {
511 $< = $uid if defined $uid;
512 $( = $gid if defined $gid;
513 if (!defined $stdin) {
514 exec $cmdline || logdie "Exec($cmdline) failed: $!";
515 } else {
516 # We have to send the matching line to stdin
517 open(OUT, "|$cmdline") || logdie "open($cmdline): $!";
518 print OUT $stdin;
519 close OUT;
520 }
521 exit 0;
522 }
523 }
524
525
526 sub Char {
527 my ($expat, $string) = @_;
528 return if $string =~ m/^\s+$/;
529 $expat->xpcroak("No text is allowed within elements");
530 }
531
532 ##########################################################################
533
534 # The following set of routines are called if the option with the same
535 # name was used on the commandline.
536
537 sub version {
538 my ($optctl) = @_;
539 print STDERR "timbersee-", $VERSION, ": ";
540 print STDERR "Copyright 2001-2005, BSI.\n";
541 print STDERR "http://www.fastcoder.net/~thumper/software/sysadmin/timbersee/\n";
542 print STDERR "\n[Other options will be ignored. Terminating.]\n"
543 if scalar(grep {$_ ne 'version'} keys %$optctl) > 0;
544 exit(0);
545 }
546
547 sub help {
548 my ($command, $description);
549 format STDERR_help =
550 @<<<<<<<<<<<<<<< ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
551 $command, $description
552 ~~ ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
553 $description
554 .
555 select(STDERR);
556 $~ = 'STDERR_help';
557 $command = '--config=FILE';
558 $description = 'Specifies an alternative configuration file to be used.';
559 write;
560 $command = '--quit';
561 $description = 'Quit timbersee after reading config file.';
562 write;
563 $command = '--daemon';
564 $description = 'Run timbersee in background.';
565 write;
566 $command = '--help';
567 $description = 'Display this help and exit.';
568 write;
569 $command = '--version';
570 $description = 'Display version information and exit.';
571 write;
572 select(STDOUT);
573
574 print STDERR "\nReport bugs to <thumper\@alumni.caltech.edu>.\n";
575 exit(0);
576 }
577
578
579 ##########################################################################
580 package WaitFor;
581
582
583 sub new {
584 my ($class, $initial, $count, $timeout) = @_;
585 my $this = {
586 initial => $initial, # Where to start counter
587 maxcount => $count, # Max number of times to run
588 # into this <waitfor> object
589 # before executing its internal
590 # commands.
591 count => $initial, # Number of times we've _actually_
592 # run into this <waitfor> object
593 timeout => $timeout, # How many seconds since the last
594 # time we saw this <waitfor> object
595 # before we reset 'count'?
596 lasttime => 0, # The last time we reset this <waitfor>
597 };
598 bless $this, $class;
599 }
600
601 sub inc {
602 my $this = shift @_;
603
604 my $now = time;
605 my $since = $now - $this->{'lasttime'};
606
607 if (($this->{timeout} != -1) && ($since > $this->{timeout})) {
608 # since $since is always greater than or equal to zero!
609 $this->{count} = $this->{initial};
610 # keep track of how long since last reset
611 $this->{lasttime} = $now;
612 }
613
614 # Increment how many times we've since this <waitfor> object
615 $this->{count} ++;
616
617 if ($this->{count} >= $this->{maxcount}) {
618 $this->{count} = 0;
619
620 # keep track of how long before reset needed. Since
621 # the counter hit zero, some action was probably just taken,
622 # so the timer should start from 'now'.
623 $this->{lasttime} = $now;
624
625 return 1; # indicate to parent that internal
626 # commands should be executed.
627 }
628 return 0; # Otherwise, don't execute internal cmd
629 }
630
631
632 ##########################################################################
633 =pod
634
635 =head1 NAME
636
637 timbersee - script to monitor multiple logfiles
638
639 =head1 SYNOPSIS
640
641 timbersee
642
643 timbersee --daemon
644
645 =head1 DESCRIPTION
646
647 Timbersee is a perl script that monitors multiple logfiles simultaneously,
648 and can echo only those lines which the user deems relevant. On
649 a typical unix system, important messages are sent to various
650 logfiles (such as C</var/log/secure> and C</var/log/messages>)
651 where they are often ignored until after a problem is discovered
652 some other way. Besides being a time-consuming chore to just monitor
653 all the logfiles, there is often a lot of information logged which
654 isn't really indicative of a problem. The file C</var/log/maillog>
655 is the best example, where every email sent and received is recorded
656 and where error messages are extremely few and far between.
657
658 In order to reduce the noise, timbersee uses regular expressions
659 to strip out unimportant messages, so that you will only be
660 notified about messages which are unexpected. Currently, you
661 can choose to either echo messages to the tty, or have them
662 emailed to an address.
663
664 =head1 OPTIONS
665
666 Options include:
667
668 --config=FILE Specify an alternate configuration file to use.
669
670 --version Display version information and exit.
671
672 --help Display the list of options and exit.
673
674 --quit Quit after reading the configuration file.
675 Useful for testing the validity of the config.
676
677 --daemon Fork and go into the background. Echoing to
678 the tty is turned off, in this mode.
679
680 =head1 CONFIGURATION FILE
681
682 The configuration file for timbersee is an XML file which takes
683 the basic form of:
684
685 <?xml version="1.0" standalone="no" ?>
686 <!DOCTYPE config SYSTEM "@datadir@/@PACKAGE@/timbersee.dtd">
687 <config>
688 <mailconfig smtphosts="..." from="..." />
689 <log file="...">
690 ...commands...
691 </log>
692 <log file="...">
693 ...commands...
694 </log>
695 </config>
696
697 The <log> tag takes a single property value, named "file", which
698 is the path to a logfile to be monitored. Within the <log> tag,
699 any of several actions can be taken upon successful pattern matching.
700
701 =head1 REGULAR EXPRESSIONS
702
703 The <ignore> and <onmatch> tags require that a regular expression
704 be declared to match against each line in the monitored logfiles.
705 The notation used is perl's quite powerful regular expression syntax.
706 If you're not familiar with perl's syntax, the basics are the
707 same as with other tools; you should not have much of a problem
708 building a working configuration file if you've ever used any
709 regular expressions. You can learn about perl's particular syntax
710 by reading L<perlre>.
711
712 For avid regular expression junkies, please note that the expressions
713 are compiled with no options other than "o". This means that all
714 matches are case sensitive, and you cannot use the "\G" zero-width
715 assertion.
716
717 For purists interested in eeking out faster performance, use perl's
718 "(?: )" construct for grouping, instead of plain parenthesis.
719 This is explained in L<perlre>.
720
721
722 =head1 CONFIGURATION
723
724 =head2 <mailconfig/>
725
726 If you are using the <mailto> tag to email lines from logfiles,
727 this tag can be used to override the defaults of how email is
728 transmitted. One immediate problem will be the "From:" address
729 used for the email; most SMTP servers are configured to forbid
730 relaying, so you need to make sure that the domain matches
731 what your server allows. You can set the "From:" address by
732 specifying the "from" property.
733
734 The next problem is specifying the SMTP server to use.
735 You can provide a colon-separated list to timbersee by
736 using the "smtphosts" property. You can read more about
737 this in L<Mail::Internet>, which is the perl module
738 used to implement this functionality.
739
740 Your server might also require SMTP authentication (SASL)
741 in order to send emails. If so, you can use the
742 "authuser" and "authpass" properties to specify
743 the values to be used. Note that having these as plaintext
744 in your configuration file can be a security risk; at least
745 make sure that your config is chmod'd to not allow everyone
746 on your machine to read it.
747
748 Some SMTP servers require correct hostname identification
749 by the email client. This can be set using the "hello"
750 property.
751
752 Example:
753
754 <mailconfig from="thumper@buttsoft.com"
755 smtphosts="localhost:mail.buttsoft.com:smtp.microsoft.com"
756 hello="mail.buttsoft.com"
757 authuser="thumper" authpass="woohoo!" />
758
759
760 =head1 ACTIONS
761
762 =head2 <ignore/>
763
764 The simplest pattern matching action is the <ignore> tag. It has
765 a mandatory "re" property used to define the regular expression
766 to match against. On a successful match, the line from the logfile
767 is considered to be "ignored" -- no further action or pattern match
768 will be executed based on this line. Use this for those messages
769 which should not trigger any kind of warning. For example,
770
771 <ignore re="\bsendmail\[\d+\]: \w+: from=" />
772
773 will ignore messages which match those generated by sendmail
774 when normally processing mail. A match of the regular expression
775 causes timbersee to immediately abort further testing of the line.
776
777 =head2 <onmatch> .. </onmatch>
778
779 The <onmatch> tag takes a regular expression and attempts to
780 match each line against it. If a match is successful, then
781 the actions declared within the body of the <onmatch> and
782 </onmatch> tags will be executed.
783
784 A new feature in version 0.3 is that <onmatch> and <ignore> tags
785 can be nested within <onmatch> tags, allowing for some grouping
786 to simplify reading of the configuration file.
787
788 =head2 <waitfor> .. </waitfor>
789
790 The <waitfor> tag is a primitive counting mechanism. It has
791 a required "count" attribute, which specifies how many times
792 the <waitfor> must be encountered before it allows execution
793 of its internal commands. For instance, if you wanted to display
794 only every other line from a logfile, you might use this snippet:
795
796 <log file="/var/log/messages">
797 <waitfor count="2">
798 <echo />
799 </waitfor>
800 </log>
801
802 It also has an optional "timeout" attribute, which causes
803 the counter to reset to zero if more than "timeout" seconds
804 have elapsed since the last evaluation of the <waitfor> tag.
805 Be aware that setting the timeout to an appropriate value
806 can be tricky, because files can go unchecked for a minute
807 at a time (if they are not changing quickly).
808
809 More usefully, <waitfor> can be used in conjunction
810 with <onmatch> for detecting multiple occurences of a pattern:
811 <log file="/var/log/messages">
812 <onmatch re="\bauthentication failure\b">
813 <waitfor count="3">
814 <echo msg="Repeated authentication failures!" />
815 </waitfor>
816 </onmatch>
817 </log>
818
819
820 NOTE: There is an extra property, called "initial" which sets
821 the initial counting value. This can be used when you are expecting
822 a message to be repeated hundreds of times, and you'd only like
823 notification the first time it's seen. In the following example,
824 the timeout resets the counter to the initial value every ten minutes,
825 so that only the first occurence every ten minutes will be seen.
826 <log file="/var/log/messages">
827 <onmatch re="\bauthentication failure\b">
828 <waitfor initial="9999" count="10000" timeout="600">
829 <echo msg="Authentication failure!" />
830 </waitfor>
831 </onmatch>
832 </log>
833 The "initial" property may disappear in the future, when proper
834 context blocks are implemented.
835
836
837
838 =head2 <bell/>
839
840 The <bell/> action causes an ASCII 7 character to be sent to the tty.
841 An optional "count" property can be declared to ring the bell
842 multiple times, as in
843
844 <bell count="5" />
845
846 =head2 <mailto/>
847
848 The <mailto/> action causes the matching line to be emailed
849 to the indicated user(s). Properties include:
850
851 recipients Specifies one or more users to receive the email.
852 subject Defines a subject to be used for the email.
853
854 Example:
855
856 <mailto subject="Unexpected log message" recipients=
857 "thumper@alumni.caltech.edu" />
858
859 =head2 <echo/>
860
861 The <echo> tag uses an optional "mode" attribute to define how to echo
862 the matching line to the tty. Generally, the value of mode
863 should be one of the standard ANSI colors. (The mode is
864 implemented by L<Term::ANSIColor>, so any of the strings
865 acceptable to that module will also work here.)
866
867 Examples:
868
869 <echo mode="reset" />
870
871 <echo mode="bold" />
872
873 By default, what is echoed is the "current" line from the logfile.
874 If you specify the "msg" attribute, then the text you provide
875 is echoed instead, as in
876
877 <echo mode="red" msg="Intruder alert!" />
878
879 Specifying an explanatory message followed by another <echo> to
880 print the logfile line can be a useful construct for explaining
881 why a logfile line merits investigation. (Another example appears
882 in the final sample config, below.)
883
884 The last attribute available to the <echo> command is "interpolation".
885 By setting "interpolation" to "true", you enable limited variable
886 interpolation of the regular expression variables $1, $2, etc.
887 For example:
888
889 <onmatch re="sshd\[\d+\]: Failed password for (\w+) from (\d+\.\d+\.\d+\.\d+)\b">
890 <echo mode="bold red" msg="SSH failed: $1@$2" interpolation="true" />
891 <last />
892 </onmatch>
893
894 This technique can be very valuable for rewriting very long messages
895 into more useful short messages.
896
897 NOTE: As a security precaution, any text to be displayed has
898 control characters converted into plain text. This precludes
899 the possibility of generating log messages intended to hijack
900 terminal sessions by embedding terminal control sequences.
901
902 =head2 <last/>
903
904 The <last> tag is used when you want timbersee to abort
905 any further testing of a logfile line. Normally, timbersee
906 will execute each action in term, until a <last> tag or
907 matching <ignore> tag is encountered.
908
909 =head2 <exec/>
910
911 The <exec/> action is used to launch an outside program. It
912 has a required property of "cmdline", which defines the command
913 to be launched by timbersee. You can also optionally specify "user" and
914 "group" properties, which change the UID and/or GID of the launched
915 command. For example,
916
917 <exec cmdline="/usr/sbin/sendmail thumper@alumni.caltech.edu < \
918 /dev/null" user="nobody" />
919
920 will invoke sendmail as user "nobody" to send an annoying email to the
921 author.
922
923 The "user" and "group" names are converted into a UID and GID when
924 the configuration file is parsed, so if you change one of these
925 values, you'll have to restart timbersee.
926
927 Also, the XML parser requires that the "<" character appear as
928 its standard entity C<<>; this is true for all the
929 standard entities, like C<">, C<<>, and C<>>.
930 If you make a mistake, the parser will give an error about
931 an invalid token at the proper line and character position
932 in your config file.
933
934 You can have timbersee send the full text of the logfile line
935 to STDIN of the exec'd command, by setting the C<sendline> property,
936 as in
937
938 <exec cmdline="/bin/echo GOT LINE: `/bin/cat -`" sendline="true" />
939
940
941 which will echo any matching lines back to the screen.
942 More usefully, you can pipe the matching line through
943 sed or awk and then use the result as an argument
944 to a program.
945
946 Finally, the <exec/> action is really a wrapper
947 around the perl C<exec()> call. As in perl, if you use shell metacharacters,
948 then your command line is passed to C</bin/sh> for interpretation,
949 which is why the redirection from C</dev/null> in the example
950 works. It is not possible to use the logfile line directly
951 in the command line because an intruder could subvert such a
952 feature to gain access to your machine.
953
954 =head1 SAMPLE CONFIG FILE
955
956 Hopefully, an example will help to elucidate the usage:
957
958 <?xml version="1.0" standalone="no" ?>
959 <!DOCTYPE config SYSTEM "@datadir@/@PACKAGE@/timbersee.dtd">
960 <config>
961 <log file="/var/log/messages">
962 <!-- Ignore all the DHCP messages -->
963 <ignore re=" DHCP\w+ " />
964 <onmatch re="\bpromiscuous\b">
965 <!-- Oh, it's just another h4x0r on the system -->
966 <bell/>
967 <!-- Put an explanatory message -->
968 <echo mode="red" msg="Beware the Jabberhacker, my son." />
969 <!-- and then echo the actual log line, so the user can see -->
970 <echo mode="bold"/>
971 <last/>
972 </onmatch>
973 <!-- Hmm.. If we got here, it was an unexpected msg -->
974 <echo/>
975 </log>
976 </config>
977
978 For those new to XML, one point that will need explanation
979 is the odd C</E<gt>> notation used with some tags. This is
980 really a shorthand for a matching close tag; for example,
981 C<E<lt>bell/E<gt>> is really the same as
982 C<E<lt>bellE<gt>E<lt>/bellE<gt>>.
983
984 In the <!DOCTYPE> tag, be sure to specify the full path
985 to the timbersee.dtd file, which would normally be installed
986 in C</usr/local/share/timbersee/timbersee.dtd>.
987
988 =head1 AUTHOR
989
990 B. Thomas Adler, thumper@alumni.caltech.edu
991
992 =head1 COPYRIGHT
993
994 timbersee-@VERSION@ is Copyright (c) 2001-2005, BSI
995 All rights reserved. You may distribute this code under the terms
996 of the GNU General Public License.
997
998 =cut
999