#/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(){ # 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(){ &cksyslog($_); } # If you have the nameserver running in debug, you can do something with this # while(){ # } # # 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"); } ##########################################################################