#/usr/local/bin/perl
#
#    sentryd -- system monitor, v1.01
#    Copyright (C), 1993, Bill Middleton and Texas Metronet
#    (wjm@feenix.metronet.com)
#    All rights reserved.  No warranty expressed or implied.
#    Sentryd is freely distributable under the same terms as Perl.
#    Inspired by Steven Parker  (sp@feenix.metronet.com)
#
#  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");
}
##########################################################################