#!/usr/bin/perl -w
#
# logscand - watches a logfile and sends mail when a matching line appears
#
#  written by Joerg Dorchain <joerg@dorchain.net>
#
# Based on code from poprelayd by Curt Sampson <cjs@cynic.net>.
# Nevertheless all bugs are mine, not his ;-)
#
#
#



#
# Configuration settings
#

$logfile = "/var/log/messages";		# File to watch
$pidfile = "/var/run/logscand.pid";	# Where to put out PID
$log_wait_interval = 10;		# Number of seconds between
					# checks of the log file
$reg = "scanlogd";			# pattern to look for


# This is called when a matching line is found
sub action($) {
	my $s = $_[0];

	open(PIPE, "|mail -s \"$reg\" root") || die "Can't fork: $!\n";
	print PIPE "\n$s\n";
	close(PIPE);
}

#
# Modules
#

use Fcntl;
use POSIX;


#
# Variables
#

undef $pid;				# Process ID
undef $lffd;				# $logfile file descriptor
undef $lfino;				# inode of $logfile when we opened it.
undef $lfbuf;				# Buffer for data from $lffd
undef @lines;				# List of line from $logfile


# getlogline()
#
# Return the next line from $logfile, or undef if one isn't currently ready.
#
# XXX Bugs in this routine:
# It ignores blank lines.
# 
sub getlogline {
	my $junk;
	my $ino;
	my $foundeof = 0;
	my $buf;
	my $count;

	# The first time we're called; open the logfile, skip to end, and
	# remember the inode we opened.
	if (!defined($lffd)) {
		$lffd = POSIX::open($logfile, O_RDONLY|O_NONBLOCK, 0);
		if (!defined($lffd)) {
			die "Can't open $logfile\n";
		}
		if (POSIX::lseek($lffd, 0, &POSIX::SEEK_END) == -1) {
			die "Can't seek to end of $logfile\n";
		}
		($junk, $lfino, $junk) = POSIX::fstat($lffd);
	}
	# Append new data, if available, to our buffer
	$count = POSIX::read($lffd, $buf, 1024);
	if ($count) {
		$lfbuf = $lfbuf . $buf;
	}

	# Return a line, if we have one
	if ($lfbuf =~ m/\n/m) {
		($buf, $lfbuf) = split(/\n/m, $lfbuf, 2);
		return $buf;
	}

	# Check  the indoe number of $logfile; if it's not the saved one,
	# the logfile has been rotated we need to reopen.
	($junk, $ino, $junk) = POSIX::stat($logfile);
	if ($ino != $lfino) {
		POSIX::close($lffd);
		undef($lffd);
		$lffd = POSIX::open($logfile, O_RDONLY|O_NONBLOCK, 0);
		if (!defined($lffd)) {
			die "Can't open $logfile\n";
		}
		($junk, $lfino, $junk) = POSIX::fstat($lffd);
	}
	return undef;
}

# scanline($line)
#
# Scan $line to see if it is an"interesting" one.
# return the line if so, undef otherwise
#
sub scanline($) {
	my $s = $_[0];

	if ($s =~ m/$reg/o) {
		return $s;
	}
	return undef;
}

# cleanup
#
# Clean up and exit; executed on receipt of a sighup.
#
sub cleanup {
	unlink $pidfile;
	exit 0;
}


#
# Main Program
#

if ($pid = fork) {
	exit 0;			# Parent
} elsif (defined($pid)) {
	$pid = getpid;		# Child
} else {
	die "Can't fork: $!\n";
}
# Catch signals
$SIG{INT} = \&cleanup;
$SIG{TERM} = \&cleanup;
$SIG{HUP} = \&cleanup;
#Write PID file
open(PIDFILE, ">$pidfile") || die "Can't open PID file: $!\n";
print PIDFILE "$pid\n";
close(PIDFILE);
chmod(0644, $pidfile);
#  Detach from terminal, etc.
setpgrp(0, 0);
close(STDIN); close(STDOUT); close(STDERR);
chdir("/");

# Main loop
while(1) {
	# Build list of interesting lines
	while ($line = getlogline) {
		undef $ret;
		if ($ret = scanline($line)) {
			push(@lines, $line);
		}
	}
	# Do action for the line
	while ($line = pop(@lines)) {
		action($line);
	}
	sleep $log_wait_interval;
}
