#/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");
}
##########################################################################