"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#\$(?![1-9](\D|$))#\\\$#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 &lt; \
  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<&lt;>; this is true for all the
  929 standard entities, like C<&quot;>, C<&lt;>, and C<&gt;>.
  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