#!/p/perl5.000/perl5.000 -w

#
# Detect SATAN scan by analysing the TCP wrapper output.
#
# Usage: detect_satan -l <logfile>
#
# Author: Mark Crosbie   March 1995
#		mcrosbie@cs.purdue.edu
#
# v1.0 - Initial design using tail on the inetdlog
# Detects SATAN/SANTA scans by examining the output of the TCP wrappers
# package. Accumulates evidence for a scan and then prints a warning once
# a threshold of evidence is reached.
# These constants are easily modified.
#
# The tool is far from perfect - it will only detect HEAVY scans in its
# current form. This will be changed later as it is integrated with other
# tools.
#
# Options:
#   -v turn on verbose mode to print things as they happen
#   -l <path> gives the name of the log file containing TCP wrappers
#      output
#   -t <path> gives the path to the tail program

require 'getopts.pl';

$usage = "detect_satan [-l <Log file>] [-t <path to tail] [-v]\n";

&Getopts("vl:t:") || die $usage;

# Get the log file name and path to the tail program 
$INETDLOG = "/var/log/inetdlog"; # output of the TCP Wrappers package
$INETDLOG = $opt_l if $opt_l;

$TAILPROG = "/bin/tail";
$TAILPROG = $opt_t if $opt_t;

# install a handler for the Interrupt key. This is used to generate a
# report when the Ctrl-C key is pressed
$SIG{'INT'} = "report";

# how many pieces of evidence must be gathered before we call it a scan
$SATAN_THRESHOLD = 4;

# thresholds for each part of a scan, more than these and we count it as
# evidence. These will detect a heavy scan, and are based on empirical evidence
# gathered while evaluating SATAN.
$FINGER_THRESHOLD = 2;
$COMSAT_THRESHOLD = 3;
$WALLD_THRESHOLD = 1;
$CMSD_THRESHOLD = 1;
$SPRAYD_THRESHOLD = 1;
$REXECD_THRESHOLD = 1;
$QUOTAD_THRESHOLD = 1;

# gather statistics every X seconds
$TIMEOUT_VAL = 20;

$finger_attempts = 0;
$comsat_attempts = 0;
$rexecd_attempts = 0;
$sprayd_attempts = 0;
$walld_attempts = 0;
$cmsd_attempts = 0;
$quotad_attempts = 0;

# open the output log from the TCP Wrappers to read.
open(LOG, "$TAILPROG -1lf $INETDLOG|") || die
	"Cannot open $INETDLOG for read!\n";

# read and discard the line just output from tail
$junk = <LOG>;
$junk = '';

# match for certain specific occurances of things that indicate SATAN
# running

# default to wait for a minute
$timeout = $TIMEOUT_VAL;

# no evidence yet
$satan_evidence = 0;

print "\nWaiting for SATAN scans...\n";

# for as long as you fear SATAN...
while(1) {

# wait for either the log file to have something written to it, or for the
# timer to expire. If the timer expires, handle the aggregate statistics
# If the log file has new data, read that and update the statistics
$rin = '';
vec($rin, fileno(LOG), 1) = 1;
$rfd = $rin;

# wait for either a timeout or a line to be written to the file
($nfound, $timeleft) = select($rfd, undef, undef, $timeout);
$timeleft = $TIMEOUT_VAL;

# determine if we timed out or if there is something to read.
if($nfound == 0) {

  # there must have been a time-out, so gather some aggregate statistics
  if($finger_attempts >= $FINGER_THRESHOLD) {
    $satan_evidence++;
    $finger_attempts = 0;
  }

  if($comsat_attempts >= $COMSAT_THRESHOLD) {
    $satan_evidence++;
    $comsat_attempts = 0;
  }

  if($quotad_attempts >= $QUOTAD_THRESHOLD) {
    $satan_evidence++;
    $quotad_attempts = 0;
  }

  if($rexecd_attempts >= $REXECD_THRESHOLD) {
    $satan_evidence++;
    $rexecd_attempts = 0;
  }

  if($sprayd_attempts >= $SPRAYD_THRESHOLD) {
    $satan_evidence++;
    $sprayd_attempts = 0;
  }
  
  if($cmsd_attempts >= $CMSD_THRESHOLD) {
    $satan_evidence++;
    $cmsd_attempts = 0;
  }

  if($walld_attempts >= $WALLD_THRESHOLD) {
    $satan_evidence++;
    $walld_attempts = 0;
  }

  # determine, based on accumulated evidence, whether we have been scanned by
  # SATAN
  if($satan_evidence >= $SATAN_THRESHOLD) {
# the src array has counts for each possible scanning host
    print "\nWARNING: Possible SATAN scan.\n";
    print "Originating scanning hosts:\n";
    foreach $host (keys(%src)) {
      print "$host : ".$src{$host}."\n";
    }
    $satan_evidence = 0;
    undef %src;
  }
  $timeout = $TIMEOUT_VAL; # wait for the timeout
} else {

  # the time left to wait should be used as a new timeout
  $timeout = $TIMEOUT_VAL / 2;

  # set stdout to be autoflushed
  select((select(STDOUT), $| = 1)[0]);

  # there is something to read - read it while you can.
  $_ = <LOG>;

    chop;
    @line = split;

    if($line[4] =~ "finger") {
      $finger_attempts++;
      $src{$line[7]}++;
	print "Finger from $line[7]...\n" if $opt_d;
    }

    if($line[4] =~ "comsat") {
      $comsat_attempts++;
      $src{$line[7]}++;
	print "comsat from $line[7]...\n" if $opt_d;
    }

    if($line[4] =~ "rexecd") {
      $rexecd_attempts++;
      $src{$line[7]}++;
	print "rexecd from $line[7]...\n" if $opt_d;
    }

    if($line[4] =~ "sprayd") {
      $src{$line[7]}++;
      $sprayd_attempts++;
	print "sprayd from $line[7]...\n" if $opt_d;
    }

    if($line[4] =~ "rquotad") {
      $src{$line[7]}++;
      $quotad_attempts++;
	print "rquotad from $line[7]...\n" if $opt_d;
    }

    if($line[4] =~ "cmsd") {
      $src{$line[7]}++;
      $cmsd_attempts++;
	print "cmsd from $line[7]...\n" if $opt_d;
    }

    if($line[4] =~ "walld") {
      $src{$line[7]}++;
      $walld_attempts++;
	print "walld from $line[7]...\n" if $opt_d;
    }

}
}

close(LOG);

exit;

# interrupt handler - if Ctrl-C typed then this is called to give some
# summary stats.
sub report  {

  print "\nSATAN SCAN DETECTION REPORT:\n";

  # finger attempts, print how many and from where
  if($finger_attempts > 0) {
    print "\nFINGER ATTEMPTS: $finger_attempts\n";
  }

  # rexecd attempts, print how many and from where
  if($rexecd_attempts > 0) {
    print "\nREXECD ATTEMPTS: $rexecd_attempts\n";
  }

  # comsat attempts, print how many and from where
  if($comsat_attempts > 0) {
    print "\nCOMSAT ATTEMPTS:$comsat_attempts\n";
  }

  # sprayd attempts, print how many and from where
  if($sprayd_attempts > 0) {
    print "\nSPRAYD ATTEMPTS: $sprayd_attempts\n";
  }

  # cmsd attempts, print how many and from where
  if($cmsd_attempts > 0) {
    print "\nCMSD ATTEMPTS: $cmsd_attempts\n";
  }

  # quotad attempts, print how many and from where
  if($quotad_attempts > 0) {
    print "\nQUOTAD ATTEMPTS: $quotad_attempts\n";
  }

  #  attempts, print how many and from where
  if($walld_attempts > 0) {
    print "\nWALLD ATTEMPTS: $walld_attempts\n";
  }

  print "Suspicion level: $satan_evidence\n";

  print "\n";
  exit;
}
