#!/usr/bin/perl # 6/1/2004 Author: Joe Barbish, I bequeath this perl script to public domain. # It can be copied and distributed for free by anyone to anyone by any manner. # # Purpose: This is the first step of the proactive abuse reporting system # targeted at reporting all the packets denied by your firewall rules to the # ISP's who own the offending IP addresses. # # Description: This perl scripts reads your FreeBSD ipfilter # firewall ipmon log file, drops log records based the exclusion file # and converts the remaining log records to an standard record format which # gets written to a flat file and passed onto the next step in this # proactive abuse reporting system. # # The exclusion file should contain the IP address ranges owned by your ISP # as well as the IP address ranges used on your private LAN if you have any. # # Script contains user customable defaults which can be overridden with # command line flags. # # # Last verification test May 2 2004 using FreeBSD Release 4.10. # ############################################################################## # # The file containing the following items (in this order): # 1.date (YYYY-MM-DD HH:MM:SS tzHH:tzMM). tz means timezone from GMT # 2.count (number, used to summarize identical records, default=1) # 3.source IP address (in 0.0.0.0 format) # 4.source port # 5.target IP address (in 0.0.0.0 format) # 6.target port # 7.protocol (either number or text like 'TCP','UDP'...) # 8.TCP flags (S-SYN, A-ACK, F-FIN, U-URG, R-RST, P-PSH) ############################################################################## # Note1 Script must have execute permission chmod 760 # # Note2 Be careful if you edit ipfilter log file. This script expects all # log records to end with one blank and no blank records in log file. # Marker offset file will get hosed if log file ends with blank record # and not be able to position correctly on next run. Check log file # if you have positioning problems. When you look at marker file contents # first record is the number of positions indexed into file as starting # position of last log record processed. The second record is an image # copy of the last log record processed. # # Note3 The perl Net::Netmask function used below is not installed in the # perl version that is delivered as part of the standard FreeBSD # sysinstall process. # # pkg_add -rv p5-Net-Netmask will install it. # # (note capital letter N in package name so you spell it correctly) # After pkg_add completes, follow with rehash command to enable it. # # There is also an cvsup port version of p5-Net-Netmask or you can use # the perl cpan method to add the module. use Net::Netmask; use POSIX (strftime); use Getopt::Std; use IO::File; getopts("h:l:o:v:e:m:"); if ($opt_h) { print <<_EOF_; Usage: dshield-freebsd [ -h Display help info ] [ -l /path/ipflogfile ] [ -o out_put_file ] [ -v1 turns on verbose debug ] [ -e exclusion_list_file ] [ -m marker file ] _EOF_ exit } # # User customable script default Parameter definitions # # Path and file name of your ipfilter log file #$logfile="/var/log/ipf.log"; $logfile="/var/log/security.0"; # Path and file name of your marker offset file. # Used to know where last run stopped in the log. # One will be created if one does not exist. $marker="/root/bin/abuse.public.ISP1.offset.marker"; # Path and file name of your output file. # One will be created if one does not exist. $opfile="/var/log/abuse.public.ISP1.file"; # Path and file name of your exclusion file. $exclusions="/root/bin/abuse.public.ISP1.excluded.lst"; # Pattern to match ipmon lines. # Change tun0 to your firewall external interface name #$logpattern="ipmon.*tun0"; $logpattern="ipmon.*rl0"; ############################################################################## ### From here, no changes are required ####################################### ############################################################################## # All ISP's requires all submitted firewall logs include the timezone so the # abuse records can be synchronized for an world wide view. # This gets your system time zone which you set during sysinstall # or with tzsetup command. ($timezone = strftime("%z", localtime)) =~ s/(\d\d)(\d\d)/$1:$2/; debug("Checking for flag override arguments\n"); # Command line flags to over ride script default arguments if ($opt_l ne "") { $logfile=$opt_l; } if ($opt_o ne "") { $opfile=$opt_o; } if ($opt_v ne "") { $verbose=$opt_v; } if ($opt_e ne "") { $exclusions=$opt_e; } if ($opt_m ne "") { $marker=$opt_m; } # Read marker offset file & save positioning info debug("Loading offset file\n"); open(IN,$marker); $offset_line=; # offset number of text positions into file $offset_value=; # copy of last complete log line processed chop($offset_line); chop($offset_value); close(IN); debug("offset_line = $offset_line\n"); debug("offset_value = $offset_value\n"); # Read excluded file & save info debug("Loading exclusion file\n"); @excluded=(); open(IN,$exclusions); while () { chop; s/^\s+//; if ($_ eq "") { next; } if ($_ !~ /^#/) { push(@excluded,$_); } } close(IN); debug("Opening log file\n"); $fd = new IO::File; $fd->open($logfile,"r") || die "Cant open log file"; # position offset if ($offset_line != 0) # not empty or absent offset file { debug("Positioning offset ($offset_line)\n"); $fd->seek($offset_line,0); # set to old pointer value $line=<$fd>; chop($line); debug("Comparing log record: $line\n"); debug("To positioning record: $offset_value\n"); if (($line ne $offset_value) && ($offset_value ne "")) # position is incorrect, line differs, set to 0 { debug("Line differs, positioning to 0\n"); $fd->seek(0,0); } else { debug("Line positioning matches\n"); } } if ($fd->eof()) { $fd->close(); close(OUT); print "This log file has been previously processed.\n"; exit; } debug("Opening o/p file\n"); open(OUT,">$opfile"); $curpos=$fd->tell(); debug("File pointer located at $curpos, reading logs\n"); # What year are we?? $year=1900+(localtime(time)) [5]; $b4=" "; # Starting roll through log, building o/p record while (1) { # Log line format is: # Aug 14 22:49:36 hostname ipmon[xx]: 22:49:35.901496 tun0 @0:16 b 200.60.255.45,4123 -> 80.13.151.54,135 PR tcp len 20 48 -S IN $line_curpos=$fd->tell(); $line=<$fd>; chop($line); debug("log record = $line\n"); # parse line to extract relevant fields @f=split(/\s+/,$line); # re-init work fields to null $log_line_date=""; $log_line_time=""; $log_line_ip_src=""; $log_line_ip_trg=""; $log_line_port_src=""; $log_line_port_trg=""; $log_line_proto=""; $log_line_flags=""; $log_line_dupnum=""; $log_line_dupsufx=""; $month=convmonth($f[0]); $log_line_date=sprintf("%04d-%02d-%02d",$year,$month,$f[1]); $log_line_time=$f[2]; # For ipmom log records that have dup counter field the parse line # fields end up having different field displacement. $dupctr=$f[6]; debug("Log line dup counter = $dupctr\n"); $log_line_dupnum = substr($dupctr, 0, length($dupctr) -1); $log_line_dupsufx = substr($dupctr, -1, 1); debug("counter = $log_line_dupnum\n"); debug("counter = $log_line_dupsufx\n"); if ($log_line_dupsufx eq "x") { debug("this log rec has dup counter\n"); @g=split(/,/,$f[10]); $log_line_ip_src=$g[0]; $log_line_port_src=$g[1]; my(@a) = $log_line_ip_src =~ /(\d*?)\.(\d*?)\.(\d*?)\.(\d*)/; foreach (@a) { $_ = substr("000" . $_, -3) } $log_line_ip_src = join(".", @a); @g=split(/,/,$f[12]); $log_line_ip_trg=$g[0]; $log_line_port_trg=$g[1]; my(@a) = $log_line_ip_trg =~ /(\d*?)\.(\d*?)\.(\d*?)\.(\d*)/; foreach (@a) { $_ = substr("000" . $_, -3) } $log_line_ip_trg = join(".", @a); $_=$f[14]; tr/a-z/A-Z/; $log_line_proto=$_; if ($log_line_proto =~ /TCP/i) { $_=$f[18]; s/-//; $log_line_flags=$_; if ($log_line_flags =~ /frag/i) { $log_line_flags=""; } } } else { debug("this log rec does not have dup counter\n"); @g=split(/,/,$f[9]); $log_line_ip_src=$g[0]; $log_line_port_src=$g[1]; my(@a) = $log_line_ip_src =~ /(\d*?)\.(\d*?)\.(\d*?)\.(\d*)/; foreach (@a) { $_ = substr("000" . $_, -3) } $log_line_ip_src = join(".", @a); @g=split(/,/,$f[11]); $log_line_ip_trg=$g[0]; $log_line_port_trg=$g[1]; my(@a) = $log_line_ip_trg =~ /(\d*?)\.(\d*?)\.(\d*?)\.(\d*)/; foreach (@a) { $_ = substr("000" . $_, -3) } $log_line_ip_trg = join(".", @a); $_=$f[13]; tr/a-z/A-Z/; $log_line_proto=$_; if ($log_line_proto =~ /TCP/i) { $_=$f[17]; s/-//; $log_line_flags=$_; if ($log_line_flags =~ /frag/i) { $log_line_flags=""; } } } # logic to drop excluded log records based on exclusion file if (($line =~ /$log_pattern/) && ($log_line_ip_src =~ /[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/) && ($log_line_ip_trg =~ /[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/) && (NotExcluded("SI",$log_line_ip_src)) && (NotExcluded("SP",$log_line_port_src)) && (NotExcluded("TI",$log_line_ip_trg)) && (NotExcluded("TP",$log_line_port_trg))) { debug("not excluded = $log_line_ip_src\n"); # populate out put file. print(OUT "$log_line_date $b4 $log_line_time $b4 $timezone $b4"); print(OUT "$log_line_ip_src $b4 $log_line_port_src $b4"); print(OUT "$log_line_ip_trg $b4 $log_line_port_trg $b4"); print(OUT "$log_line_proto $b4 $log_line_flags $b4\n"); } else { debug("excluded = $log_line_ip_src\n"); } if ($fd->eof()) { last; } } # at this point all the ipfilter log records have been processed # and the log file has been closed. $fd->close(); close(OUT); # Now write the positioning offset info to the marker file. open(OUT2,">$marker"); print(OUT2 "$line_curpos\n"); print(OUT2 "$line\n"); close(OUT2); exit; ############# every thing beyond this point are subroutines ############ sub convmonth() { my %months_tab = (Jan=>1, Feb=>2, Mar=>3, Apr=>4, May=>5, Jun=>6, Jul=>7, Aug=>8, Sep=>9, Oct=>10, Nov=>11, Dec=>12); return $months_tab{$_[0]}; } sub NotExcluded { my ($excl_type)=@_[0]; my ($tocheck)=@_[1]; my ($i); my (@f); my ($t); for ($i=0;$i<@excluded;$i++) { @f=split(/=/,$excluded[$i]); $type=$f[0]; $value=$f[1]; if (($excl_type =~ /^SI$/i) || ($excl_type =~ /^TI$/)) { $t=new Net::Netmask($value); if (($t->match($tocheck)) && ($excl_type eq $type)) { return(0); } } elsif (($excl_type =~ /^SP$/i) || ($excl_type =~ /^TP$/)) { if (($value==$tocheck) && ($excl_type eq $type)) { return(0); } } } return(1); } sub debug { if ($verbose==1) { print(STDERR @_); } }