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