#/usr/local/bin/perl
#
# sentryd -- system monitor, v1.01
# Copyright (C), 1993, Bill Middleton and Texas Metronet
# (
[email protected])
# All rights reserved. No warranty expressed or implied.
# Sentryd is freely distributable under the same terms as Perl.
# Inspired by Steven Parker (
[email protected])
#
# 03/01/93 - fixed a little bug which deleted users from the
# currently-logged-in-user array, when they had
# multiple logins.
#
# This is the sentryd. It does lots of stuff for us here at feenix,
# and can be configured to do lots more. Please send suggestions.
# The sentryd will monitor your log files, and report to online users/admins
# based on the hidden files in a hidden directory in their $HOME. The script
# is currently released to monitor the sendmail syslog, and report new mail to
# online users, monitor the syslog and report strangeness to admins, monitor
# /etc/wtmp, and report new logins, /etc/btmp to report bad login attempts,
# It will also monitor the size of any logfile, and report when it exceeds
# some preconfigured amount. Other errors as well. Each report (broadcast)
# is done on a per-user basis, depending on whether the admin/user has the
# necessary zero-length hidden file in the hidden .sentry directory in their
# home.
#
# Heres a listing of my .sentry directory, to show which files you'll need to
# see all the reporting that the sentryd does. Note that all the files
# are zero-length, and only used to determine whether the admin/user wants
# messages about the given activity/problem.
#
# .users # report logins/logouts
# .ftp # report ftp logins
# .mail # report my mail
# .trouble # report funkiness in logs, and engorged logs
#
# There is also an optional "off" file, in the .sentry directory,
# to shutoff your messages temporarily.
#
############################## CONFIGURE ################################
#
# These are all the ttys we wanna keep track of. They are stat'd each time
# the wtmp file gets a new line, to see who is on what device, and their
# idle time, etc. If ownership changes on a device, we broadcast a user
# login or logout.
$ttys = "tty1M01 tty1M02 tty1M03 tty1M04 tty1M05
tty1M06 tty1M07 tty1M08 tty1M09 tty1M10
tty1M11 tty1M17 tty1M18 tty1M19 tty1M20
tty1M21 tty1M22 tty1M23 tty1M24 tty1M25
tty1M26 tty1M27 tty1M28 ttys0 ttys1
ttys2 pty/ttys3 pty/ttys4 pty/ttys5
pty/ttys6 pty/ttys7 pty/ttys8";
#
# This is an associative array of logfiles that we wanna keep track of.
# Each one is filename, maximum file size and default action to perform
# when the file reaches the maximum size. The userlog and troublelog are
# used by the sentryd to record time in and out, as well as trouble warnings.
#
%logfiles = (
'maillog', "/usr/spool/mqueue/syslog:2000000:bcast",
'syslog', "/usr/adm/syslog:250000:bcast",
'dnslog', "/usr/tmp/named.run:100000:bcast",
'wtmp', "/etc/wtmp:20000000:bcast",
'btmp', "/etc/btmp:100000:bcast",
'pacct', "/usr/adm/pacct:20000000:bcast",
'newslog', "/usr/lib/news/log:20000000:bcast",
'nntplog', "/usr/lib/news/nntp.log:20000:bcast",
# the next 2 are the sentryd's logfiles, opened to write.
'troublelog', "/admin/info/sentrylog.trouble:200000:bcast",
'userlog', "/admin/info/sentrylog.users:200000:bcast"
);
######################## END CONFIGURATION ###############################
&catch_signals(); # smooth exit
local($logpath,$wtmpcurr,$wtmpnew,$btmpcurr,$btmpnew);
#
# Open each logfile we are interested in monitoring by line,
# and seek to eof, to begin. Also open the sentry logfiles.
&openm();
#
# Now get an initial size on the btmp and wtmp files.
($logpath)= split(':',$logfiles{'wtmp'});
$wtmpcurr = (-s $logpath); # size change will cause user update
($logpath)= split(':',$logfiles{'btmp'});
$btmpcurr = (-s $logpath); # watch for bad btmp entries
#
@previous = &statm($ttys); # who is on the ttys ?
&update_users();
$count=0;
&bcast("[ Sentry restarted ]\n $line",".trouble");
#
# Until cows come home, or Buffalo wins. :{)
#
for(;;){
# count can be changed to do stuff every five minutes or more, or less
while($count < 30){
# if the size of /etc/wtmp changes, possible login/logout
($logpath)= split(':',$logfiles{'wtmp'});
if(($wtmpnew = (-s $logpath)) != $wtmpcurr){
&update_users();
$wtmpcurr = $wtmpnew;
}
# if the size of /etc/btmp changes, possible bad login attempt
($logpath)= split(':',$logfiles{'btmp'});
if(($btmpnew = (-s $logpath)) != $btmpcurr){
&bcast("[ Bad login attempt ]\n $line",".trouble");
$btmpcurr = $btmpnew;
}
# get a from line, and a to line from the mail log, and report
# no biff here, so inform users with .mail of new mail, and report errors
while(<NMAILLOG>){ # get a from line, then get a to line, and tell em
@line = split; # if the to is a logged in user.
if($line[6] =~ /from/){$from=$_;next;}
if($line[6]=~/to/){$to=$_;&ckmail($from,$to);}
}
# check the syslog for new lines, report if interesting
while(<NSYSLOG>){
&cksyslog($_);
}
# If you have the nameserver running in debug, you can do something with this
# while(<NNSERVERLOG>){
# }
#
# seek to new eof for each file
seek(NMAILLOG,0,1);
seek(NSYSLOG,0,1);
# seek(NNSERVERLOG,0,1);
#
# and have a little nap
sleep 4;
$count++; # for time-based stuff
}
# time-based stuff here (~5 minutes)
$count=0;
&idlecheck(); # knock em off if they aint doin nuthin. (optional)
&logfile_size_ck(); # whine if the logfiles become engorged
&flush(); # flush the logfile write buffers
}
############################################################################
sub logfile_size_ck{ # broadcast a trouble warning if the logfiles
# get too big.
local(@logs,$log);
local($logpath,$maxlogsize,$action);
@logs = keys %logfiles;
foreach $log (@logs){
($logpath,$maxlogsize,$action) = split(':',$logfiles{$log});
if ((-s $logpath) > $maxlogsize){
&$action("[$log is getting too big, probably]",".trouble");
}
}
}
##########################################################################
sub cksyslog{ # report on syslog funkiness
local($line) = @_[0];
if($line =~ /unknown/){&bcast("[Syslog Unknown]\n $line",".trouble");
return;}
if($line =~ /refused/){&bcast("[Syslog Refused]\n $line",".trouble");
return;}
if($line =~ /(ftp)|(FTP)/){
if($line =~/(Login)|(LOGIN)|(logged out)/){
$line =~ s/^.*://;
&bcast("[Syslog Ftp]\n $line",".ftp");
}
return;
}
# skip past stuff we dont care about
if($line =~ /finger/) {return;}
if($line =~ /login/) { return;}
if($line=~ /ntalk/) {return;}
if($line=~ /telnet/) {return;}
if($line=~ /gopher/) {
if($line=~/from feenix/){ # a local connection
return;
}
&bcast("[ gopher! ]\n",".gopher");return;
}
# report otherwise
&bcast("[Possible Syslog Trouble]\n $line",".trouble");
}
##############################################################################
sub ckmail{ # trouble report for mailer errors, local users
# get informed of their mail, and who its from
local($from,$to) = @_; # if they have a .mail file in their .sentry dir
local(@fields,$junk,$junk1,$junk2,$tty,$tmp,$dir);
if(($from =~ /(MAILER-DAEMON)/)){ # handle mailer errors
&bcast("[ Possible Mailer Trouble ]\n",".trouble");
return;
}
if(!($to =~ /local/)){return;}
@fields = split(' ',$to);
$to = $fields[6];
if($to =~ procmail){$to =~ s/^.*procmail (.*)["].*/$1/;}
@fields = split(' ',$from);
$from = $fields[6];
$to =~ s/^.*to=//;
$to =~ s/[>]|[<]//g;
@fields = split(/[,]/, $to);
for $tmp (@fields){
$tmp =~ s/[@].*//;
$tmp =~ s/[ ]//g;
if($users{$tmp}){
($tty,$junk1,$junk2,$dir) = split(/:/,$users{$tmp});
if((-f "$dir/.sentry/.mail" ) && (!(-f "$dir/.sentry/off"))){
open(TELLMAIL,"> /dev/$tty");
$from =~ s/[,]//;
$from =~ s/[=]/: /;
print TELLMAIL "\n[ New mail $from ]\n";
close TELLMAIL;
}
}
}
}
##########################################################################
sub update_users{
local($junk,$tty,@t1,@t2,@current,@id);
local($i,$current);
@current = &statm($ttys);
for($i=0;$i<=$#current;$i++){ # for each tty
@t2 = split(' ',$current[$i]);
if($t2[1]!=0){ # if the tty is not owned by root
@t1 = split(' ', $previous[$i]); # get previous values
@id = getpwuid($t2[1]);
if($flag == 0){ &adduser($id[0],$t2[0],$id[7]); }
if ($t2[1]!=$t1[1]){ # not same user
&adduser($id[0],$t2[0],$id[7]); # broadcast a message update current users
&bcast("[ $id[0] just logged in on $t2[0] ]",".users");
}
}
else{ # is owned by root
@t1 = split(' ',$previous[$i]);
if($t1[1]>0){ # logout if previously owned by someone
$id = getpwuid($t1[1]);
($tty)=split(':',$users{$id});
&deluser($id,$tty);
&bcast("[ $id just logged out from $t2[0] ]",".users");
}
}
}
@previous=();
@previous=@current;
$flag=1;
}
############################################################################
sub deluser{ # remove the associative array entry
local($id,$tty)=@_; # put a line in the userlog
local($act_tty,$idle,$time,$home)=split(':',$users{$id});
local($timeout) = time;
if($tty ne $act_tty){return;} #heh
close $tty;
undef $users{$id} ;
delete $users{$id} ;
print NUSERLOG " $id Time in: $time Time out: $timeout\n";
}
############################################################################
sub adduser{ # update the assoc array of users
local($id,$tty,$home)=@_; # cleaned up a bit for 1.01
if(defined $users{$id}){ # handle simultaneous logins
local($act_tty,$idle,$time,$same_id)=split(':',$users{$id});
if ($id =~/(info)|(signup)/){return;} # don't gripe at info user
open(WARN,">/dev/$act_tty");
print WARN "(sentryd) Hey, you have more than one login!\n";
close WARN;
return;
}
local($time) = time;
$idle_warn_num = 0;
$users{$id} = join(':',($tty,$idle_warn_num,$time,$home));
}
############################################################################
sub statm{ # who's on the ttys?
local($tmp)=@_;
local(@tmp,$tmp2);
local(@files) = split(' ',$tmp);
local($i);
for($i=0;$i<=$#files;$i++){
#if(-O $files[$i] ){$files[$i] .= " "."0 0";next;} # if running as root
@tmp=stat("/dev/$files[$i]");
$tmp2 = join(' ',($tmp[4],$tmp[5]));
$files[$i] .= " "."$tmp2".":";
}
@files;
}
#############################################################################
sub bcast{
local($message,$tellfile) = @_;
local($i) = 0;
local($tty,$junk1,$junk2,$home);
local( @users) = keys(%users);
for $i (@users){
($tty,$junk1,$junk2,$home) = split(/:/,$users{$i});
next if (-f "$home/.sentry/off");
if(-f "$home/.sentry/$tellfile"){
open (BCAST, "> /dev/$tty");
print BCAST "$message\n";
}
}
close BCAST;
if($tellfile eq '.trouble'){
print NTROUBLELOG "$message\n";}
}
########################################################################
sub idlecheck{ # 86 'em (optional, insert your own code)
#local($home,$idle,$junk2);
#local($ticks,@ticksarray,@ps,$totalticks)
#local( @users) = keys(%users);
#for $i (@users){
# ($tty,$idle,$junk2,$home) = split(/:/,$users{$i});
# @tmp=stat($tty);
# if ((time - $tmp[9]) > 100){ # simple atime test
# next if (!(-f "$home/.sentry/.test"));
# @ps = `ps -u $i`;
# foreach ($ps[1]..$ps[$#ps]){ # add up their clock ticks
# ($pid) = split;
# chop($ticks=`/admin/bin/ticks`);
# @ticksarray=split(' ',$ticks);
# $totalticks+=$ticksarray[$#ticksarray];
# }
# open (WARNING, "> /dev/$tty");
# print WARNING "idle!";
# }
#}
}
##########################################################################
sub catch_signals { # more could be done here
$SIG{'HUP'} = 'cleanup';
$SIG{'INT'} = 'cleanup';
$SIG{'TERM'} = 'cleanup';
$SIG{'QUIT'} = 'cleanup';
$SIG{'ALRM'} = 'cleanup';
}
#######################################################################
sub cleanup { # broadcast the kill signal and close up shop
local($i) = 0;
local($date)= `date`;
chop $date;
&bcast("[ Sentryd killed at $date ]",".trouble");
local( @users) = keys(%users);
for $i (@users){
&deluser($i);
}
print NTROUBLELOG "\n[Sentryd killed at: $date]\n";
print NUSERLOG "\n[Sentryd killed at: $date]\n";
close NTROUBLELOG;
close NUSERLOG;
exit 0;
}
#######################################################################
sub openm{ # open various files and print startup records to the
# sentry logs,
local($logpath,$date);
($logpath)= split(/:/,$logfiles{'maillog'});
open(NMAILLOG,$logpath);
seek(NMAILLOG,0,2);
($logpath)= split(':',$logfiles{'syslog'});
open(NSYSLOG,$logpath);
seek(NSYSLOG,0,2);
#($logpath)= split(':',$logfiles{'dnslog'});
#open(NNSERVERLOG,$logpath);
#seek(NNSERVERLOG,0,2);
($logpath)= split(':',$logfiles{'userlog'});
open(NUSERLOG,">> $logpath");
($logpath)= split(':',$logfiles{'troublelog'});
open(NTROUBLELOG,">> $logpath");
$date = `date`;
print NUSERLOG "\n[Sentry started : $date]\n";
print NTROUBLELOG "\n[Sentry started : $date]\n";
}
#########################################################################
sub flush{ # twiddle the logfiles to flush the buffers
local($logpath)= split(':',$logfiles{'userlog'});
close NUSERLOG;
open(NUSERLOG,">> $logpath");
($logpath)= split(':',$logfiles{'troublelog'});
close NTROUBLELOG;
open(NTROUBLELOG,">> $logpath");
}
##########################################################################