1#!/usr/local/bin/perl 2 3############################################################################## 4# 5# 6/1/2004 Author: Joe Barbish, I bequeath this perl script to public domain. 6# It can be copied and distributed for free by anyone to anyone by any manner. 7# 8# Description: This perl script purpose is to read your FreeBSD ipfilter 9# firewall ipmon log file and convert the log records to an standard reporting 10# record format, and imbed the converted records into the body of an email that 11# gets sent to the abuse email address for your ISP. 12# 13# The exclude file is used to identify your ISP owned IP address range. 14# 15# Script contains user customable defaults which can be overridden with 16# command line flags. 17# 18# Last verification test June 5 2004 using FreeBSD Release 4.9 & 4.10 19# Last verification test June 5 2014 using FreeBSD Release 10.0 20# 21############################################################################## 22# 23# Report format is a tab delimited format containing the following 24# items (in this order): 25# 1.date (YYYY-MM-DD HH:MM:SS tzHH:tzMM). tz means timezone from GMT 26# 2.count (number, used to summarize identical records, default=1) 27# 3.source IP address (in 000.000.000.000 format) 28# 4.source port 29# 5.target IP address (in 000.000.000.000 format) 30# 6.target port 31# 7.protocol (either number or text like 'TCP','UDP'...) 32# 8.TCP flags (S-SYN, A-ACK, F-FIN, U-URG, R-RST, P-PSH) 33############################################################################## 34 35# Note1 Script must have execute permission chmod 760 36 37# Note2 Be careful if you edit ipfilter log file. This script expects all 38# log records to end with one blank and no blank records in log file. 39# Marker offset file will get hosed if log file ends with blank record 40# and not be able to position correctly on next run. Check log file 41# if you have positioning problems. When you look at marker file contents 42# first record is the number of positions indexed into file as starting 43# position of last log record processed. The second record is an image 44# copy of the last log record processed. 45# 46# Note3 The perl Net::Netmask function used below is not installed in the 47# perl version that is delivered as part of the standard FreeBSD 48# sysinstall process. 49# 50# pkg install p5-Net-Netmask will install it. 51# 52# (note capital letter N in package name so you spell it correctly) 53# 54# There is also an svn port version of p5-Net-Netmask or you can use 55# the perl cpam method to add the module. 56# 57############################################################################## 58 59use Net::Netmask; 60use POSIX (strftime); 61use Getopt::Std; 62use IO::File; 63 64getopts("h:f:t:c:l:m:e:v:s:"); 65$verbose=$opt_v; 66 67 68if ($opt_h) 69{ 70 print <<_EOF_; 71 72 73Usage: abuse.myisp.pl [ -h Display help info ] [ -f email_from ] 74 [ -t email_to ] [ -c email_cc ] 75 [ -l /path/ipflogfile ] 76 [ -m log_offset_file ] [ -e exclusion_list_file ] 77 [ -s sendmail_location ] [ -v1 turns on verbose debug ] 78_EOF_ 79 exit 80} 81 82# 83# User customable script default Parameter definitions 84# 85 86# Sender email address (don't forget backslash before @) 87#$email_from="root\@this.machine"; 88 89 90# The "To: email address" to send your ipfilter converted log records 91# to as content in the body of the email. (don't forget backslash before @) 92# Testing send it to your self so you can verify things are working. 93#$email_to="root\@this.machine"; 94#$email_to="abuse\@myisp.net"; 95 96 97# The CC email address (don't forget backslash before @) 98# Send copy of email to your self to verify and track regular submissions. 99#$email_cc="root\@this.machine"; 100$email_cc=""; 101 102 103# Path and file name of your ipfilter log file 104#$logfile="/var/log/ipf.log"; 105#$logfile="/var/log/security.0"; 106 107# Path and file name of your marker offset file. 108# Used to know where last abuse run stopped in the log. 109# One will be created if one does not exist. 110$marker="/usr/local/etc/abuse.myisp.offset.marker"; 111 112# Exclusion list file (regex lines) 113# Used to exclude ipfilter log records containing LAN IP address 114# or any other log records you want excluded from 115# being processed and sent to your ISP. 116# 117# If you have no exclusion file all the records in the ipfilter 118# log file will be processed and emailed. 119# 120# Report exclusion list file command syntax. 121# 122# Format: 123# Type=IP 124# 125# with type: 126# SI (Source IP) 127# TI (Target IP) 128# SP (Source Port) 129# TP (Target port) 130# port=single value between 1-65535 131# and IP: 132# 216.240.32.0/24 The preferred form. 133# 216.240.32.0:255.255.255.0 134# 216.240.32.0-255.255.255.0 135# 216.240.32.0', '255.255.255.0 136# 216.240.32.0', '0xffffff00 137# 216.240.32.0 - 216.240.32.255 138# 216.240.32.4 A /32 block. 139# 216.240.32 Always a /24 block. 140# 216.240 Always a /16 block. 141# 140 Always a /8 block. 142# 216.240.32/24 143# 216.240/16 144# 145# Example file content: 146#SI=0.0.0.0 147#TI=255.255.255.255 148#SI=192.168.0.0/24 149#TI=192.168.0.0/24 150#SI=192.168.1.0/24 151#TI=192.168.1.0/24 152#SP=4661 153#TP=4661 154#TP=4662 155 156# Path and file name of your exclusion file. 157$exclusions="/usr/local/etc/abuse.myisp.excluded.lst"; 158 159# Pattern to match ipmon lines. 160# Change tun0 to your firewall external interface name 161#$logpattern="ipmon.*tun0"; 162$logpattern="ipmon.*xl0"; 163 164# Default path to FreeBSD install delivered sendmail. 165# Option to change path in case you installed newer version. 166$sendmail="/usr/sbin/sendmail"; 167 168 169############################################################################## 170### From here, no changes are required ####################################### 171############################################################################## 172 173# All submitted firewall logs include the timezone so the 174# records can be synchronized for an world wide view. 175# This gets your system time zone which you set during bsdinstall 176# or with tzsetup command. 177 178($timezone = strftime("%z", localtime)) =~ s/(\d\d)(\d\d)/$1:$2/; 179 180 181debug("Checking for flag override arguments\n"); 182# Command line flags to over ride script default arguments 183 184if ($opt_f ne "") 185{ $email_from=$opt_f; } 186 187if ($opt_t ne "") 188{ $email_to=$opt_t; } 189 190if ($opt_c ne "") 191{ $email_cc=$opt_c; } 192 193if ($opt_l ne "") 194{ $logfile=$opt_l; } 195 196if ($opt_m ne "") 197{ $marker=$opt_m; } 198 199if ($opt_e ne "") 200{ $exclusions=$opt_e; } 201 202if ($opt_s ne "") 203{ $sendmail=$opt_s; } 204 205# Read marker offset file & save positioning info 206debug("Loading offset file\n"); 207open(IN,$marker); 208$offset_line=<IN>; # offset number of text positiones into file 209$offset_value=<IN>; # copy of last ipfilter log record processed 210chop($offset_line); 211chop($offset_value); 212close(IN); 213debug("offset_line = $offset_line\n"); 214debug("offset_value = $offset_value\n"); 215 216# Read excluded file & save info 217debug("Loading exclusion file\n"); 218@excluded=(); 219open(IN,$exclusions); 220while (<IN>) 221{ 222 chop; s/^\s+//; 223 if ($_ eq "") { next; } 224 if ($_ !~ /^#/) { push(@excluded,$_); } 225} 226close(IN); 227 228 229debug("Opening log file\n"); 230$fd = new IO::File; 231$fd->open($logfile,"r") || die "Cant open log file"; 232 233# position offset 234if ($offset_line != 0) # not empty or absent offset file 235{ 236 debug("Positioning offset ($offset_line)\n"); 237 $fd->seek($offset_line,0); # set to old pointer value 238 $line=<$fd>; 239 chop($line); 240 debug("Comparing log record: $line\n"); 241 debug("To positioning record: $offset_value\n"); 242 243 if (($line ne $offset_value) && ($offset_value ne "")) 244 # position is incorrect, line differs, set to 0 245 { 246 debug("Line differs, positioning to 0\n"); 247 $fd->seek(0,0); 248 } 249 else 250 { 251 debug("Line positioning matches\n"); 252 } 253} 254 255if ($fd->eof()) 256{ 257 $fd->close(); 258 close(OUT); 259 print "This log file has been previously processed.\n"; 260 exit; 261} 262 263# Prepare sendmail 264debug("Piping to sendmail\n"); 265if ($verbose==1) 266{ open(OUT,"| /bin/cat") || die "Cant open sendmail pipe"; } 267else 268{ open(OUT,"| $sendmail -t -oi") || die "Cant open sendmail pipe"; } 269print(OUT "From: $email_from\n"); 270print(OUT "To: $email_to\n"); 271print(OUT "Cc: $email_cc\n"); 272print(OUT "Subject: Abusive Customers Traffic Report\n\n"); 273print(OUT "listing of your customers who tried to penetrate my system\n\n"); 274print(OUT "Report format contains the following tab delimited items in this order\n"); 275print(OUT "1. Date and Time as YYYY-MM-DD HH:MM:SS of abuse access attempt:\n"); 276print(OUT "2. tzHH:tzMM). timezone offset from GMT:\n"); 277print(OUT "3. source IP address:\n"); 278print(OUT "4. source port:\n"); 279print(OUT "5. target IP address:\n"); 280print(OUT "6. target port:\n"); 281print(OUT "7. protocol (text like 'TCP','UDP'...):\n"); 282print(OUT "8. TCP flags (S-SYN, A-ACK, F-FIN, U-URG, R-RST, P-PSH):\n"); 283print(OUT " \n"); 284 285 286$curpos=$fd->tell(); 287debug("File pointer located at $curpos, reading logs\n"); 288 289# What year are we?? 290$year=1900+(localtime(time)) [5]; 291 292 293# Starting roll through log, building record in email body 294while (1) 295{ 296 # Log line format is: 297 # 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 298 299 $line_curpos=$fd->tell(); 300 $line=<$fd>; 301 chop($line); 302 debug("log record = $line\n"); 303 304 # parse line to extract relevant fields 305 @f=split(/\s+/,$line); 306 307 # re-init work fields to null 308 $log_line_date=""; 309 $log_line_time=""; 310 $log_line_ip_src=""; 311 $log_line_ip_trg=""; 312 $log_line_port_src=""; 313 $log_line_port_trg=""; 314 $log_line_proto=""; 315 $log_line_flags=""; 316 $log_line_dupnum=""; 317 $log_line_dupsufx=""; 318 319 320 $month=convmonth($f[0]); 321 $log_line_date=sprintf("%04d-%02d-%02d",$year,$month,$f[1]); 322 323 $log_line_time=$f[2]; 324 325 $dupctr=$f[6]; 326 debug("Log line dup counter = $dupctr\n"); 327 $log_line_dupnum = substr($dupctr, 0, length($dupctr) -1); 328 $log_line_dupsufx = substr($dupctr, -1, 1); 329 debug("counter = $log_line_dupnum\n"); 330 debug("counter = $log_line_dupsufx\n"); 331 332 # For ipmom log records that have dup counter field the parse line 333 # fields end up having different field displacement. 334 if ($log_line_dupsufx eq "x") 335 { 336 debug("this log rec has dup counter\n"); 337 @g=split(/,/,$f[10]); 338 $log_line_ip_src=$g[0]; 339 $log_line_port_src=$g[1]; 340 341 @g=split(/,/,$f[12]); 342 $log_line_ip_trg=$g[0]; 343 $log_line_port_trg=$g[1]; 344 345 $_=$f[14]; 346 tr/a-z/A-Z/; 347 $log_line_proto=$_; 348 349 if ($log_line_proto =~ /TCP/i) 350 { 351 $_=$f[18]; 352 s/-//; 353 $log_line_flags=$_; 354 if ($log_line_flags =~ /frag/i) { $log_line_flags=""; } 355 } 356 } 357 else 358 { 359 debug("this log rec does not have dup counter\n"); 360 @g=split(/,/,$f[9]); 361 $log_line_ip_src=$g[0]; 362 $log_line_port_src=$g[1]; 363 364 @g=split(/,/,$f[11]); 365 $log_line_ip_trg=$g[0]; 366 $log_line_port_trg=$g[1]; 367 368 $_=$f[13]; 369 tr/a-z/A-Z/; 370 $log_line_proto=$_; 371 372 if ($log_line_proto =~ /TCP/i) 373 { 374 $_=$f[17]; 375 s/-//; 376 $log_line_flags=$_; 377 if ($log_line_flags =~ /frag/i) { $log_line_flags=""; } 378 } 379 } 380 381 # logic to drop excluded log records based on exclution file 382 if (($line =~ /$log_pattern/) 383 && ($log_line_ip_src =~ /[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/) 384 && ($log_line_ip_trg =~ /[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/) 385 && (NotExcluded("SI",$log_line_ip_src)) 386 && (NotExcluded("SP",$log_line_port_src)) 387 && (NotExcluded("TI",$log_line_ip_trg)) 388 && (NotExcluded("TP",$log_line_port_trg))) 389 { 390 debug("not excluded = $log_line_ip_src\n"); 391 } 392 else 393 { 394 debug("excluded = $log_line_ip_src\n"); 395 print(OUT "$log_line_date\t$log_line_time\t$timezone\t"); 396 print(OUT "$log_line_ip_src\t$log_line_port_src\t"); 397 print(OUT "$log_line_ip_trg\t$log_line_port_trg\t"); 398 print(OUT "$log_line_proto\t$log_line_flags\n"); 399 } 400 401 if ($fd->eof()) 402 { 403 last; 404 } 405} 406 407$fd->close(); 408close(OUT); 409 410# at this point all the ipfilter log records have been processed 411# and the log file has been closed. 412# Now write the positioning offset info to the marker file. 413open(OUT,">$marker"); 414print(OUT "$line_curpos\n"); 415print(OUT "$line\n"); 416close(OUT); 417exit; 418 419 420############# every thing beyond this point are subroutines ############ 421 422sub convmonth() 423{ 424 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); 425 426 return $months_tab{$_[0]}; 427} 428 429sub NotExcluded 430{ 431 my ($excl_type)=@_[0]; 432 my ($tocheck)=@_[1]; 433 my ($i); 434 my (@f); 435 my ($t); 436 437 for ($i=0;$i<@excluded;$i++) 438 { 439 @f=split(/=/,$excluded[$i]); 440 $type=$f[0]; 441 $value=$f[1]; 442 443 if (($excl_type =~ /^SI$/i) || ($excl_type =~ /^TI$/)) 444 { 445 $t=new Net::Netmask($value); 446 if (($t->match($tocheck)) && ($excl_type eq $type)) { return(0); } 447 } 448 elsif (($excl_type =~ /^SP$/i) || ($excl_type =~ /^TP$/)) 449 { 450 if (($value==$tocheck) && ($excl_type eq $type)) { return(0); } 451 } 452 } 453 return(1); 454} 455 456sub debug 457{ 458 if ($verbose==1) 459 { print(STDERR @_); } 460} 461 462