#! /usr/bin/perl -T
# LMon 1.2, anders@bsdconsulting.no, 2005-05-19
# External requirements: Mail::Sendmail, File::Tail.
package LMon;
use Fcntl;
use Getopt::Std;
use POSIX;
use strict;
use Mail::Sendmail;
use File::Tail;
use FindBin qw($Bin);
$ENV{PATH} = "/sbin:/bin:/usr/sbin:/usr/bin";
$ENV{ENV} = "";
# --configuration ---
$LMon::mailservers = ['smtp1.tld.com', 'smtp2.tld.com'];
$LMon::recipients = "user\@tld.com";
$LMon::from = "user\@tld.com";
# How many lines to read initially from log:
$LMon::lines = 0;
# How many lines to read initially from log if rotated:
$LMon::resetlines = -1;
# For lines/resetlines, 0 = only read new lines, -1 = read everything.
# How long to wait before checking log buffer:
$LMon::waitsecs = 5;
# How long to wait before checking log buffer after a mail is sent:
$LMon::waitsecsnext = 3600;
# Max buffer lines, set to 0 for unlimited (otherwise drop lines from the top):
$LMon::maxbuffer = 50;
# --- end configuration ---
# Copyright (c) 2005, Anders Nordby <anders@bsdconsulting.no>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of BSD Consulting nor the names of its contributors may
# be used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@LMon::reglist = ();
@LMon::logbuffer = ();
$LMon::logfile = ();
$LMon::logbuffertruncated = 0;
$LMon::fnok = '([-\w\.\_]+)';
use vars qw { $opt_f $opt_p $opt_r $opt_n $opt_s $opt_i $opt_t $opt_F $opt_b $opt_m $opt_d };
# Untaint $Bin if OK
if ($Bin =~ /$LMon::fnok/) {
$Bin = $1;
}
sub usage {
print "Usage: lmon.pl -r <rule file> -f <log file> [-t <recipients>] [-p <pid file>]\n";
print "[-n <log name (for alerts)>] [-s <system name (for alerts)]\n";
print "[-i (include mode: alert on rule hits instead of misses) [-F <from mailaddress>]\n";
print "[-m <mail server(s)>] [-d (detach)]\n";
}
if ($#ARGV < 0) { usage; exit(0); }
getopts('r:f:t:p:n:s:F:b:im:d');
if ($opt_t) {
$LMon::recipients = $opt_t;
}
if ($opt_b) {
$LMon::maxbuffer = $opt_b;
}
my $line = "";
if ($opt_s) {
$LMon::sysname = $opt_s;
} else {
$LMon::sysname = (POSIX::uname)[1];
}
if (!$LMon::from) {
$LMon::from = "hostmaster\@$LMon::sysname";
} elsif($opt_F) {
$LMon::from = $opt_F;
}
if ($opt_i) {
$LMon::alertstr = "LMon: interesting data in";
} else {
$LMon::alertstr = "LMon: unrecognized data in";
}
if ($opt_m) {
@LMon::mailserverstxt = split(/ /, $opt_m);
$LMon::mailservers = \@LMon::mailserverstxt;
for (my $i = 0; $i <= $#LMon::mailserverstxt; $i++) {
if ($LMon::mailserverstxt[$i] =~ /^([\s\w\.-_]+)$/) {
# untaint mailserver
$LMon::mailserverstxt[$i] = $1;
} else {
print "Bad data $opt_m in option -m.\n";
exit(1);
}
}
$Mail::Sendmail::mailcfg{'smtp'} = \@LMon::mailserverstxt;
} else {
$Mail::Sendmail::mailcfg{'smtp'} = $LMon::mailservers;
}
sub errusage {
my $msg = $_[0];
print "$msg\n\n";
usage;
exit(1);
}
sub sender {
my $text = "$_[0]";
my $subject = "$_[1]";
for my $recipient (split /([ \s,;])/, $LMon::recipients) {
my %mail = ( To => "$recipient",
From => "$LMon::from",
Message => "$text",
Subject => "$subject"
);
sendmail(%mail);
}
}
sub isinteresting {
my $str = $_[0];
chomp($str);
my $substr;
if ($opt_i) {
for (my $i = 0; $i <= $#LMon::reglist; $i++) {
if (substr($LMon::reglist[$i], 0, 1) eq '!') {
$substr = substr($LMon::reglist[$i], 1);
if ($str !~ /$substr/) { return(1); }
} else {
if ($str =~ /$LMon::reglist[$i]/) { return(1); }
}
}
return(0);
} else {
for (my $i = 0; $i <= $#LMon::reglist; $i++) {
if (substr($LMon::reglist[$i], 0, 1) eq '!') {
$substr = substr($LMon::reglist[$i], 1);
if ($str !~ /$substr/) { return(0); }
} else {
if ($str =~ /$LMon::reglist[$i]/) { return(0); }
}
}
return(1);
}
}
sub sendbuffer {
if($LMon::logbuffertruncated != 0) {
unshift(@LMon::logbuffer, "\n");
unshift(@LMon::logbuffer, "from the top of the file.\n");
unshift(@LMon::logbuffer, "ERROR: Log buffer got full at $LMon::maxbuffer lines. Data has been dropped\n");
$LMon::logbuffertruncated = 0;
}
if ($opt_n) {
sender(join("", @LMon::logbuffer), "$LMon::alertstr $opt_n on $LMon::sysname");
} else {
sender(join("", @LMon::logbuffer), "$LMon::alertstr $opt_f on $LMon::sysname");
}
}
sub checkbuffer {
if ($#LMon::logbuffer != -1) {
sendbuffer;
@LMon::logbuffer = ();
alarm($LMon::waitsecsnext);
} else {
alarm($LMon::waitsecs);
}
}
sub fixpidfilename {
if ($opt_p =~ /^([-\w\.\/]+)$/) {
# untaint PID filename
$opt_p = $1;
} else {
print "Bad data $opt_p in option -p.\n";
exit(1);
}
}
sub writepid {
fixpidfilename;
if (sysopen(PID, $opt_p, O_WRONLY|O_CREAT)) {
print PID "$$\n";
} else {
errusage("ERROR: Could not write pid file.");
}
close(PID);
}
sub readconf {
sysopen(CONF, $opt_r, O_RDONLY);
@LMon::reglist = ();
my $line = 0;
while (<CONF>) {
my $rule = $_;
$line++;
next if ($rule =~ /^(#|$)/);
chomp($rule);
eval { if (/$rule/) {} };
if ($@) {
print STDERR "ABORT, syntax/regexp error on line $line in $opt_r.\n";
print STDERR "Details:\n\n";
print STDERR $@;
close(CONF);
exit(1);
}
push(@LMon::reglist, $rule);
}
close(CONF);
}
if (!$opt_f) {
errusage("ERROR: No file to monitor specified, use -f.");
} elsif(! -f $opt_f) {
errusage("ERROR: Logfile $opt_f does not exist/is not a file.");
}
if (!$opt_r) {
errusage("ERROR: No rule file specified, use -r.");
} elsif(! -f $opt_r) {
errusage("ERROR: Rule file $opt_r does not exist/is not a file.");
}
readconf;
if ($opt_d) {
# Detach from controlling terminal
exit 0 if (fork);
chdir($Bin);
close(STDERR);
close(STDOUT);
close(STDIN);
POSIX::setsid;
}
writepid;
$SIG{ALRM} = sub { checkbuffer; };
alarm($LMon::waitsecs);
#$LMon::logfile = File::Tail->new($opt_f);
$LMon::logfile = File::Tail->new(name=>$opt_f, maxinterval=>30, interval=>10, tail=>$LMon::lines, reset_tail=>$LMon::resetlines, errmode=>"return");
while(defined($line=$LMon::logfile->read)) {
if (isinteresting($line)) {
if (($#LMon::logbuffer+1) >= $LMon::maxbuffer && $LMon::maxbuffer != 0) {
if ($LMon::logbuffertruncated == 0) {
$LMon::logbuffertruncated = 1;
}
shift(@LMon::logbuffer);
push(@LMon::logbuffer, $line);
} else {
push(@LMon::logbuffer, $line);
}
}
}
syntax highlighted by Code2HTML, v. 0.9.1