SRK Consulting

Firewatch

Home
Resume - Short
Resume - Long Word doc
Software & Misc
Firewatch (perl)
mssql_grep (perl)
pstree (c)
Vim syntax files for Netcool
NATCheck (expect)
Perl/Tk Screenshots
Temperature Monitor
Work & Demo sites
phpCollab
phpMyAdmin
phProjekt
Call Center Stats
Fitness
RemindMe
Cookie Test
Flatfile/form demo
Asthma/Allergy Clinic
Gary
Neil
Hobbies & Misc
Google Maps Links
Hard Drive Clocks
Theatre Props & Devices
Electronic Candles
Chase Light Controller
Magic Sword Prototype
Theatrical Lighting
South Pacific
(Fargo North High, 2004)
42nd Street
(West Fargo High, 2005)









#!/usr/bin/perl
#
#  firewatch - monitor your ipchains logfile with active notification
#
#  This code was written by Scott R. Keszler keszler@srkconsulting.com
#  and placed into the public domain in 2000 by SRK Consulting.
#
#  If you modify|improve this program, please email the above address
#  with your changes.
#
#  Based on poprelayd by Curt Sampson cjs@cynic.net which was placed into
#  the public domain in 1998 by Western Internet Portal Services, Inc.
#
#  Best edited with xterm having 155 columns :)

#
#  Configuration settings.
#

my $logfile  = "/var/log/kernlog";                # logfile to which ipchains syslogs
my $pidfile  = "/var/run/firewatch.pid";          # Where we put our PID.
my $notify   = "keszler\@srkconsulting.com";      # who gets the email notifications
my $sendmail = "/usr/sbin/sendmail";              # location of sendmail

# in the following explanations of config settings, "ignore" means that firewatch will not send email notifications
# when these protocols, ip addresses, and/or ports appear in the logfile

#                                                 # list of protocols to ignore
my @igproto = ( 89 );                             # ignore any IGP broadcasts - appear when running tcpdump (i.e. interface in promiscuous mode)
#                                                 # list of IP addresses to ignore (all four octets, haven't yet needed to ignore a range)
my @igip = ( "205.198.30.65", "207.70.233.191" ); # my gateway, my isp's DHCP server
#                                                 # list of ports to ignore
my @igport = ( 53, 68 );                          # DNS (mine is open, but sometimes see broadcasts from my gateway to 255.255.255.255:53), DHCP)

#
#  Modules
#

use strict;
use Fcntl;
use POSIX;
use Socket;

#
#  Variables
#

my $pid = undef;         # Process ID.
my $lffd = undef;        # $logfile file descriptor.
my $lfino = undef;       # Inode of $logfile when we opened it.
my $lfbuf = undef;       # Buffer for data from $lffd.
my $lasttimeout = undef; # Last time we did a timeout.

#
#  Subroutines
#

# getlogline()
#
# Return the next line from $logfile, or undef if one isn't currently ready.
#
# XXX Note that there's a bug in this routine that causes it to ignore
# blank lines. I kinda like this behaviour, so I've not fixed it.
#
sub getlogline {
    my $ino;
    my $foundeof = 0;
    my $buf;
    my $count;

    # The first time we're called; open the logfile, skip to the end,
    # and remember the inode we opened.
    if (!defined($lffd))  {
        $lffd = POSIX::open($logfile, O_RDONLY|O_NONBLOCK, 0);
        if (!defined($lffd))  {
            die "Can't open $logfile\n";
        }
        if (POSIX::lseek($lffd, 0, &POSIX::SEEK_END) == -1)  {
            die "Can't seek to end of $logfile\n";
        }
        (undef, $lfino, undef) = POSIX::fstat($lffd);
    }

    # Append new data, if available, to our buffer.
    $count = POSIX::read($lffd, $buf, 1024);
    if ($count)  {
        $lfbuf .= $buf;
    }

    # Return a line, if we have one.
    if ($lfbuf =~ m/\n/m)  {
        ($buf, $lfbuf) = split(/\n/m, $lfbuf, 2);
        return $buf;
    }

    # Check the inode number of $logfile; if it's not the saved one,
    # the logfile has been rotated and we need to reopen.
    (undef, $ino, undef) = POSIX::stat($logfile);
    if ($ino != $lfino)  {
        POSIX::close($lffd);
        $lffd = POSIX::open($logfile, O_RDONLY|O_NONBLOCK, 0);
        if (!defined($lffd))  {
            die "Can't open $logfile\n";
        }
        (undef, $lfino, undef) = POSIX::fstat($lffd);
    }

    return undef;
}

# scanaddr($line)
#
# example ipchains kernel log
# Feb 14 09:39:33 hal kernel: Packet log: input DENY eth0 PROTO=17 205.198.30.65:1887 255.255.255.255:53 L=73 S=0x00 I=0 F=0x0000 T=255 
# Feb  1 13:56:42 hal kernel: Packet log: input DENY eth0 PROTO=17 63.20.74.17:137 205.198.30.73:137 L=78 S=0x00 I=33890 F=0x0000 T=117 
# Feb  2 05:33:27 hal kernel: Packet log: input DENY eth0 PROTO=17 207.70.233.191:67 255.255.255.255:68 L=328 S=0x00 I=2547 F=0x0000 T=62 
# Jan 28 08:58:44 hal kernel: device eth0 entered promiscuous mode 
# Jan 28 08:59:09 hal kernel: Packet log: input DENY eth0 PROTO=89 205.198.30.65:65535 224.0.0.5:65535 L=64 S=0xC0 I=10 F=0x0000 T=1 
# Jan 28 08:59:22 hal kernel: device eth0 left promiscuous mode 
# Feb 10 17:27:43 hal kernel: Packet log: input DENY eth0 PROTO=6 212.204.141.76:2326 205.198.30.73:21 L=60 S=0x00 I=40896 F=0x4000 T=47 
# Feb 10 17:37:44 hal kernel: Packet log: input DENY eth0 PROTO=6 212.204.141.76:4222 205.198.30.73:12346 L=60 S=0x00 I=46909 F=0x4000 T=47 
#
# example Netgear RT314 log
# Feb 18 19:03:53 192.168.1.1 NetgearRT314: IP[Src=206.155.220.159 Dst=64.231.11.245 TCP spo=02051  dpo=27374]}S01>R04mD
# Feb 18 19:57:55 192.168.1.1 NetgearRT314: IP[Src=203.79.195.157 Dst=64.231.11.245 TCP spo=01197  dpo=00111]}S01>R01mD
#
# Scan $line to see if it's from ipchains
# Return the protocol number, origin IP, gethostbyaddr(origin IP), and destination port; or undef if not a packet log
#
# ipchains:
# $1 = numeric protocol
# $2 = origin IP
# $3 = dest port
#
# Netgear RT314:
# $1 = origin IP
# $2 = alpha protocol
# $3 = dest port

sub scanaddr ($) {
    my $s = shift;
# Netgear RT314
#   if ($s =~ /NetgearRT314.*Src=(\d+\.\d+\.\d+\.\d+).* (.*) spo=.*dpo=([0-9]+)/) {
#     my $orig = gethostbyaddr(inet_aton($1), AF_INET);
#     return ( $2, $1, $orig, $3 );
# ipchains
    if ($s =~ /Packet log.*PROTO=(\d+)\s+(\d+\.\d+\.\d+\.\d+):\d+\s+\d+\.\d+\.\d+\.\d+:(\d+)/) {
      my $orig = gethostbyaddr(inet_aton($2), AF_INET);
      return ( $1, $2, $orig, $3 );
    } else {
      return ( undef, undef, undef, undef );
    }
}

#  cleanup
#
#  Clean up and exit; executed on receipt of a sighup.
#
sub cleanup {
    unlink $pidfile;
    exit 0;
}


#
#  Main Program
#

# Check to see we can read/write the files we need.
die "Can't read $logfile: $!\n" if ! -r $logfile;

my %protohash;
{
  my ($proto, $pnum, $pdesc);
  open PROTO, "/etc/protocols" or die "cannot open /etc/protocols: $!\n";
  while () {
    next if /^#/;
    next if /^$/;
    ($proto, $pnum, undef, $pdesc) = split /\s+/, $_, 4;
    chomp $pdesc;
# Netgear RT314:
#   $protohash{uc($proto)} = $pnum . " " . $pdesc;
# ipchains
    $protohash{$pnum} = $proto . " " . $pdesc;
  }
  close PROTO;
}

my %porthash;
{
  my ($port, $pnum, $pdesc, $pproto);
  open PORT, "/etc/services" or die "cannot open /etc/services: $!\n";
  while () {
    next if /^#/;
    next if /^$/;
    ($port, $pnum, $pdesc) = split /\s+/, $_, 3;
    ($pnum, $pproto) = split '/', $pnum;
    chomp $pdesc;
    $porthash{$pproto}{$pnum} = $port . " " . $pdesc;
  }
  close PORT;
}

my %igproto;
foreach my $proto (@igproto) {
  $igproto{$proto}++;
}

my %igip;
foreach my $ip (@igip) {
  $igip{$ip}++;
}

my %igport;
foreach my $port (@igport) {
  $igport{$port}++;
}

# Become a daemon: fork, detach, cd /, set creation mode to 0.
if ($pid = fork)  {
    exit 0;                         # Parent.
} elsif (defined($pid)) {
    $pid = getpid;                  # Child.
} else {
    die "Can't fork: $!\n";
}
# Catch signals.
$SIG{INT} = \&cleanup;
$SIG{TERM} = \&cleanup;
$SIG{HUP} = \&cleanup;
# Write PID file.
open(PIDFILE, ">$pidfile") || die "Can't open PID file: $!\n";
print PIDFILE "$pid\n";
close(PIDFILE);
chmod 0644, $pidfile;
# Detach from terminal, etc.
setpgrp(0, 0);
close(STDIN); close(STDOUT); close(STDERR);
chdir("/");

my @newlines;
my $line;
my $body;
# Main loop.
while (1)  {
  while ($line = getlogline)  {
#print "gotlog: $line\n";
# Netgear RT314:
#   my ($prototxt, $orig, $origname, $dport) = scanaddr($line);
#   my ($protonum, undef) = split /\s+/, $protohash{$prototxt}, 2;
# ipchains
    my ($protonum, $orig, $origname, $dport) = scanaddr($line);
    my ($prototxt, undef) = split /\s+/, $protohash{$protonum}, 2;
#print "pn: $protonum pt: $prototxt oi:$orig on:$origname pt:$dport\n";
    if ((defined $protonum) && !((defined $igproto{$protonum}) || (defined $igip{$orig}) || (defined $igport{$dport}))) {
# Netgear RT314:
#     push @newlines, "$line\nProtocol $protonum ($protohash{$prototxt})\norigin: $orig (host: $origname)\nport = $dport ($porthash{$prototxt}{$dport})\n\n";
# ipchains
      push @newlines, "$line\nProtocol $protonum ($protohash{$protonum})\norigin: $orig (host: $origname)\nport = $dport ($porthash{$prototxt}{$dport})\n\n";
#print "pushed\n";
    }
  }
  $body = undef;
  while ($line = shift(@newlines))  {
    $body .= $line;
  }
  if (defined $body) {
    open MAIL, "|$sendmail $notify" or die "cannot open pipe to sendmail: $!\n";
    print MAIL "From: firewatch\nTo: $notify\nSubject: Firewatch notification\n", $body;
    close MAIL;
  }
  sleep 600;
}
          
For more information on SRK Consulting products and services, please e-mail keszler@srkconsulting.com or phone +1-701-361-8406.
This page, and all contents, are Copyright © 1994 by SRK Consulting, Fargo, North Dakota, 58102.