#!/usr/bin/perl

#$TCPDUMP_PROG = "/usr/sbin/tcpdump -c 100 -tt -n -l -r packets.dat";
$TCPDUMP_PROG = "/usr/sbin/tcpdump -tt -n -l";
$DEBUG = 0;
$MAX_SECS_OLD = 20; # Age of oldest packet in hash (in seconds)
$TARGET_REGEX = '(?:209\.43\.30)|(?:206\.246\.169)';
$SCAN_RATE  = 1; # Rate of "uniqe ports/second" that we warn about
$SCAN_SEC = 3; # Minimum number of seconds to report on
$WARN_DELAY = 30; # No. of seconds before we get another notice about scans

require "debug.pl";

my %IP = [];
my %timer = [];  # This will log a unix time-stamp of when first alert
                 # occurs regarding a port-scan for a particular IP.

open(PACKETS, "$TCPDUMP_PROG |") or die "Could not run `$TCPDUMP_PROG`: $!";

# The main loop -- reading the packets as they pass over the wire
while ($l = <PACKETS>) {
  ($t, $x, $x, $s, $x, $d, $p) = split(' ', $l);
  ($sa) = $s =~ m/(\d+\.\d+\.\d+\.\d+)\.[^:]+/;
  ($da, $dp) = $d =~ m/(\d+\.\d+\.\d+\.\d+)\.([^:]+):/;
  if ($dp =~ m/\D/) { $dp = getservbyname($dp, 'tcp') }
  &debug("time   = $t");
  &debug("source = $sa");
  &debug("dest   = $da $dp");
  &debug("packet type = $p");
  %item = ( 'sa' => $sa, 'da' => $da, 'time' => $t, 'port' => $dp );
  $key = "$sa -> $da";

  # Do we care about the Destination IP?
  if ($da =~ m/$TARGET_REGEX/) {
    # See if SourceAddress, DestAddress, and Port already exist
    if (! &in_array(@{$IP{$key}}, $dp)) {
      # Store packet instance into hash
      push( @{$IP{$key}}, { %item } );
      # Get rid of packets older than $MAX_SECS_OLD seconds
      &purge_old_packets( \@{$IP{$key}} );
      # print "I see: ".($#{$IP{$key}}+1)." entries\n";
      # Now, detect for port-scans!
      if (&port_scanned( @{$IP{$key}} )) {
        &notify( @{$IP{$key}} );
      }
    }
  } 
}

exit;

sub in_array {
  my $p = pop @_;
  my (@a) = @_;

  foreach $I (@a) {
    if ($dp == $I->{'port'}) { return 1; }
  }
  return 0;
}

sub purge_old_packets {
  my ($a) = @_;

  while (1) {
    $t = $$a[0]{'time'};
    last if ($#{$a} == 0);
    if (( time() - $t) > $MAX_SECS_OLD) {
      shift @{$a};
    } else {
      last;
    }
  }
}

sub port_scanned {
  my (@a) = @_;
  my $alen = $#a + 1;
  my $t = ( $a[-1]{'time'} - $a[0]{'time'} );

  # We can assume that the ports are unique, and
  # that any packets sent longer than $MAX_SECS_OLD seconds ago
  # are not in this array.

  if ($t >= $SCAN_SEC) {
    if (($alen / $t) >= $SCAN_RATE ) {
      return 1;
    }
  }
  return 0;
}

sub notify {
  my (@a) = @_;
  my $key = $a[0]{'sa'}." -> ".$a[0]{'da'};

  if ( (time - $timer{$key}) >= $WARN_DELAY) {
    print "Port-scan detected!\n";
    print "-------------------\n";
    print "Source:        ".$a[0]{'sa'}."\n";
    print "Dest:          ".$a[0]{'da'}."\n";
    print "Ports Scanned: ".$#a."\n";
    $timer{"$sa -> $da"} = time;
  }
}
