Initial import from FreeBSD RELENG_4:
[dragonfly.git] / contrib / ipfilter / perl / Ipfanaly.pl
1 #!/usr/local/bin/perl
2 # (C) Copyright 1998 Ivan S. Bishop (isb@notoryus.genmagic.com)
3 #
4 ############### START SUBROUTINE DECLARATIONS ###########
5
6
7 sub usage {
8     print "\n" x 24;
9     print "USAGE: ipfanalyze.pl -h  [-p port# or all] [-g] [-s] [-v] [-o] portnum -t [target ip address] [-f] logfilename\n";
10     print "\n arguments to -p -f -o REQUIRED\n";
11     print "\n -h show this help\n";
12     print "\n -p limit stats/study to this port number.(eg 25 not smtp)\n";
13     print " -g make graphs, one per 4 hour interval called outN.gif 1<=N<=5\n";
14     print " -s make  security report only (no graphical or full port info generated) \n";
15     print " -o  lowest port number incoming traffic can talk to and be regarded as safe\n";
16     print " -v verbose report with graphs and textual AND SECURITY REPORTS with -o 1024 set\n";
17     print " -t the ip address of the inerface on which you collected data!\n";
18     print " -f name ipfilter log file (compatible with V 3.2.9) [ipfilter.log]\n";
19     print " \nExample: ./ipfanalyze.pl -p all -g -f log1\n";
20     print "Will look at traffic to/from all ports and make graphs from file log1\n";
21     print " \nExample2 ./ipfanalyze.pl -p 25 -g -f log2\n";
22     print "Will look at SMTP traffic and make graphs from file log2\n";
23     print " \nExample3 ./ipfanalyze.pl -p all -g -f log3 -o 1024\n";
24     print "Will look at all traffic,make graphs from file log3 and log security info for anthing talking inwards below port 1024\n";
25     print " \nExample4 ./ipfanalyze.pl -p all -f log3 -v \n";
26     print "Report the works.....when ports below 1024 are contacted highlight (like -s -o 1024)\n";
27 }
28
29
30
31
32 sub makegifs {
33 local  ($maxin,$maxout,$lookat,$xmax)=@_;
34 $YMAX=$maxin;
35 $XMAX=$xmax;
36
37 if ($maxout > $maxin)
38   { $YMAX=$maxout;}
39
40 ($dateis,$junk)=split " " ,  @recs[0];
41 ($dayis,$monthis,$yearis)=split "/",$dateis;
42 $month=$months{$monthis};
43 $dateis="$dayis " . "$month " . "$yearis ";
44 # split graphs in to 6 four hour spans for 24 hours 
45 $numgraphs=int($XMAX/240);
46
47 $junk=0;
48 $junk=$XMAX - 240*($numgraphs);
49 if($junk gt 0 )
50 {
51 $numgraphs++;
52 }
53
54 $cnt1=0;
55 $end=0;
56 $loop=0;
57
58 while ($cnt1++ < $numgraphs)
59 {
60  $filename1="in$cnt1.dat";
61  $filename2="out$cnt1.dat";
62  $filename3="graph$cnt1.conf";
63  open(OUTDATA,"> $filename2") || die "Couldnt open $filename2 for writing \n";
64  open(INDATA,"> $filename1") || die "Couldnt open $filename1 for writing \n";
65   
66  $loop=$end;
67  $end=($end + 240);
68
69 # write all files as x time coord from 1 to 240 minutes
70 # set hour in graph via conf file
71  $arraycnt=0;
72  while ($loop++ < $end )
73   {
74    $arraycnt++;
75    $val1="";
76    $val2="";
77    $val1=$inwards[$loop] [1];
78    if($val1 eq "")
79      {$val1=0};
80    $val2=$outwards[$loop] [1];
81    if($val2 eq "")
82      {$val2=0};
83    print INDATA "$arraycnt:$val1\n";
84    print OUTDATA "$arraycnt:$val2\n";
85   }
86   close INDATA;
87   close OUTDATA;
88   $gnum=($cnt1 - 1);
89  open(INCONFIG,"> $filename3") || die "Couldnt open ./graph.conf for writing \n";
90    print INCONFIG "NUMBERYCELLGRIDSIZE:5\n";
91    print INCONFIG "MAXYVALUE:$YMAX\n";
92    print INCONFIG "MINYVALUE:0\n";
93    print INCONFIG "XCELLGRIDSIZE:1.3\n";
94    print INCONFIG "XMAX: 240\n";
95    print INCONFIG "Bar:0\n";
96    print INCONFIG "Average:0\n";
97    print INCONFIG "Graphnum:$gnum\n";
98    print INCONFIG "Title: port $lookat packets/minute to/from gatekeep on $dateis  \n";
99    print INCONFIG "Transparent:no\n";
100    print INCONFIG "Rbgcolour:0\n";
101    print INCONFIG "Gbgcolour:255\n";
102    print INCONFIG "Bbgcolour:255\n";
103    print INCONFIG "Rfgcolour:0\n";
104    print INCONFIG "Gfgcolour:0\n";
105    print INCONFIG "Bfgcolour:0\n";
106    print INCONFIG "Rcolour:0\n";
107    print INCONFIG "Gcolour:0\n";
108    print INCONFIG "Bcolour:255\n";
109    print INCONFIG "Racolour:255\n";
110    print INCONFIG "Gacolour:255\n";
111    print INCONFIG "Bacolour:0\n";
112    print INCONFIG "Rincolour:100\n";
113    print INCONFIG "Gincolour:100\n";
114    print INCONFIG "Bincolour:60\n";
115    print INCONFIG "Routcolour:60\n";
116    print INCONFIG "Goutcolour:100\n";
117    print INCONFIG "Boutcolour:100\n";
118    close INCONFIG;
119
120 }
121
122
123 $cnt1=0;
124 while ($cnt1++ < $numgraphs)
125 {
126  $filename1="in$cnt1.dat";
127  $out="out$cnt1.gif";
128  $filename2="out$cnt1.dat";
129  $filename3="graph$cnt1.conf";
130  system( "cp ./$filename1  ./in.dat;
131           cp ./$filename2  ./out.dat;
132           cp ./$filename3  ./graph.conf");
133  system( "./isbgraph -conf graph.conf;mv graphmaker.gif $out");
134  system(" cp $out /isb/local/etc/httpd/htdocs/.");
135
136 }
137
138 } # end of subroutine make gifs
139
140
141
142
143 sub packbytime {
144 local  ($xmax)=@_;
145 $XMAX=$xmax;
146 # pass in the dest port number or get graph for all packets
147 # at 1 minute intervals 
148 # @shortrecs has form 209.24.1.217 123 192.216.16.2 123 udp len 20 76
149 # @recs has form 27/07/1998 00:01:05.216596  le0 @0:2 L 192.216.21.16,2733 -> 192.216.16.2,53 PR udp len 20 62
150 #
151 # dont uses hashes to store how many packets per minite as they
152 # return random x coordinate order
153 @inwards=();
154 @outwards=();
155 $cnt=-1;
156 $value5=0;
157 $maxin=0;
158 $maxout=0;
159 $xpos=0;
160 while ($cnt++ <= $#recs )
161  {
162  ($srcip,$srcport,$destip,$destport,$pro)= split " " ,  @shortrecs[$cnt];
163   $bit=substr(@recs[$cnt],11);
164   ($bit,$junkit)= split " " , $bit ;
165  ($hour,$minute,$sec,$junk) = split ":", $bit;
166 #
167 # covert the time to decimal minutes and bucket to nearest minute
168 #
169  $xpos=($hour * 3600) + ($minute * 60) + ($sec) ;
170 # xpos is number of seconds since 00:00:00 on day......
171  $xpos=int($xpos / 60);
172 # if we just want to see all packet in/out activity
173  if("$lookat" eq "all")
174    {
175     if("$destip" eq "$gatekeep")
176       {
177 #      TO GATEKEEP port lookat
178 #        print "to gatekeep at $xpos\n"; 
179         $value5=$inwards[$xpos] [1];
180         $value5++ ; 
181 #        $maxin = $value5 if $maxin < $value5 ;
182
183          if($value5 > $maxin)
184                    {
185                         $maxin=$value5;
186                         $timemaxin="$hour:$minute";
187                    }
188         $inwards[$xpos][1]=$value5;
189         }
190     else
191       {
192 #       FROM GATEKEEP to port lookat
193 #        print "from gatekeep at $xpos\n"; 
194         $value4=$outwards[$xpos] [1];
195         $value4++ ; 
196 #        $maxout = $value4 if $maxout < $value4 ;
197         if($value4 > $maxout)
198            {
199                 $maxout=$value4;
200                 $timemaxout="$hour:$minute";
201            }
202
203         $outwards[$xpos][1]=$value4;
204       }
205     }
206
207
208
209
210  if("$destport" eq "$lookat")
211    {
212     if("$destip" eq "$gatekeep")
213       {
214 #      TO GATEKEEP port lookat
215 #        print "to gatekeep at $xpos\n"; 
216         $value5=$inwards[$xpos] [1];
217         $value5++ ; 
218         $maxin = $value5 if $maxin < $value5 ;
219         $inwards[$xpos][1]=$value5;
220         }
221     else
222       {
223 #       FROM GATEKEEP to port lookat
224 #        print "from gatekeep at $xpos\n"; 
225         $value4=$outwards[$xpos] [1];
226         $value4++ ; 
227         $maxout = $value4 if $maxout < $value4 ;
228         $outwards[$xpos][1]=$value4;
229       }
230    }
231  } # end while
232
233 # now call gif making stuff
234 if("$opt_g" eq "1")
235 {
236  print "Making plots of in files outN.gif\n";;
237  makegifs($maxin,$maxout,$lookat,$#inwards);
238 }
239 if ("$timemaxin" ne "")
240 {print "\nTime of peak packets/minute in was $timemaxin\n";}
241 if ("$timemaxout" ne "")
242 {print "\nTime of peak packets/minute OUT was $timemaxout\n";}
243
244 } # end of subroutine packets by time
245
246
247
248
249
250 sub posbadones {
251
252 $safenam="";
253 @dummy=$saferports;
254 foreach $it (split " ",$saferports) {
255 if ($it eq "icmp" )
256  {
257    $safenam = $safenam . " icmp";
258  }
259 else
260  {
261    $safenam = $safenam . " $services{$it}" ;
262  }
263
264 }
265 print "\n\n########################################################################\n";
266 print "well known ports are 0->1023\n";
267 print "Registered ports are 1024->49151\n";
268 print "Dynamic/Private ports are 49152->65535\n\n";
269 print "Sites that contacted gatekeep on 'less safe' ports  (<$ITRUSTABOVE)\n";
270
271 print " 'safe'  ports are $safenam                               \n";
272 print "\n variables  saferports and safehosts hardwire what/who we trust\n";
273 print "########################################################################\n";
274
275 $loop=-1;
276 while ($loop++ <= $#recs )
277  {
278  ($srcip,$srcport,$destip,$destport,$pro)= split " " ,  @shortrecs[$loop];
279   if ("$destip" eq "$gatekeep") 
280     {
281      if ($destport < $ITRUSTABOVE )
282      {
283 #      if index not found (ie < 0) then we have a low port attach to gatekeep
284 #      that is not to a safer port (see top of this file)
285 #      ie no ports 25 (smtp), 53 (dns) , 113 (ident), 123 (ntp), icmp
286        $where=index($saferports,$destport);
287        if ($where < 0)
288          {
289           $nameis=$services{$destport};
290           if ("$nameis" eq "" )
291             {
292               $nameis=$destport;
293             }
294               print " Warning: $srcip contacted gatekeep $nameis\n";
295          }
296       }
297     }
298   }
299 print "\n\n";
300 } # end of subroutine posbadones
301
302
303
304
305 sub toobusy_site {
306 $percsafe=1;
307 print "\n\n########################################################################\n";
308 print "# Sites sending > $percsafe % of all packets to gatekeep MAY be attacking/probing\n";
309 print "Trusted hosts are $safehosts\n";
310 print "\nTOTAL packets were $#recs \n";
311 print "########################################################################\n";
312 while(($ipadd,$numpacketsent)=each %numpacks) 
313 {
314 $perc=$numpacketsent/$#recs*100;
315 if ($perc > $percsafe) 
316 # dont believe safehosts are attacking!
317  {
318    $where=index($safehosts,$ipadd);
319 #  if not found (ie < 0 then the source host IP address
320 #  isn't in the saferhosts list, a list we trust......
321    if ($where < 0 )
322    {
323      printf "$ipadd      sent %4.1f (\045) of all packets to gatekeep\n",$perc;
324    }
325  }
326 }
327
328 print "\n\n";
329 } # end of subroutine toobusy_site 
330
331
332 ############### END SUBROUTINE DECLARATIONS ###########
333
334 use Getopt::Std;
335
336 getopt('pfot');
337
338 if("$opt_t" eq "0")
339  {usage;print "\n---->ERROR: You must psecify the IP address of the interface that collected the data!\n";
340 exit;
341 }
342  
343 if("$opt_h" eq "1")
344  {usage;exit 0};
345 if("$opt_H" eq "1")
346  {usage;exit 0};
347
348 if("$opt_v" eq "1")
349 {
350 $ITRUSTABOVE=1024;
351 $opt_s=1;
352 $opt_o=$ITRUSTABOVE;
353 print "\n" x 5;
354 print "NOTE: when the final section of the verbose report is generated\n";
355 print "      every host IP address that contacted $gatekeep has \n";
356 print "      a tally of how many times packets from a particular port on that host\n";
357 print "      reached $gatekeep, and WHICH source port or source portname \n";
358 print "      these packets originated from.\n";
359 print "      Many non RFC obeying boxes do not use high ports and respond to requests from\n";
360 print "      $gatekeep using  reserved low ports... hence you'll see things like\n";
361 print "      #### with 207.50.191.60 as the the source for packets ####\n";
362 print "      1 connections from topx to gatekeep\n\n\n\n";
363
364 }
365
366 if("$opt_o" eq "")
367  {usage;print "\n---->ERROR: Must specify lowest safe port name for incoming trafic\n";exit 0}
368 else
369 {
370 $ITRUSTABOVE=$opt_o;$opt_s=1;}
371
372 if("$opt_f" eq "")
373  {usage;print "\n---->ERROR: Must specify filename with -f \n";exit 0};
374 $FILENAME=$opt_f;
375
376 if("$opt_p" eq "")
377  {usage;print "\n---->ERROR: Must specify port number or 'all' with -p \n";exit 0};
378
379 # -p arg must be all or AN INTEGER in range 1<=N<=64K
380 if ("$opt_p" ne "all")
381  {
382    $_=$opt_p;  
383    unless (/^[+-]?\d+$/)
384    {
385      usage;
386      print "\n---->ERROR: Must specify port number (1-64K) or 'all' with -p \n";
387      exit 0;
388    }
389  }
390
391
392 # if we get here then the port option is either 'all' or an integer...
393 # good enough.....
394 $lookat=$opt_p;
395
396 # -o arg must be all or AN INTEGER in range 1<=N<=64K
397    $_=$opt_o;  
398    unless (/^[+-]?\d+$/)
399    {
400      usage;
401      print "\n---->ERROR: Must specify port number (1-64K)  with -o \n";
402      exit 0;
403    }
404
405
406 #---------------------------------------------------------------------
407
408
409 %danger=();
410 %numpacks=();
411
412 $saferports="25 53 113 123 icmp";
413 $gatekeep="192.216.16.2";
414 #genmagic is  192.216.25.254
415 $safehosts="$gatekeep 192.216.25.254";
416
417
418
419 # load hash with service numbers versus names
420
421 # hash  called $services
422 print "Creating hash of service names / numbers \n";
423 $SERV="./services";
424 open (INFILE, $SERV) || die "Cant open $SERV: $!n";
425 while(<INFILE>)
426 {
427  ($servnum,$servname,$junk)=split(/ /,$_);
428 # chop off null trailing.....
429   $servname =~ s/\n$//;
430   $services{$servnum}=$servname;
431 }
432 print "Create hash of month numbers as month names\n";
433 %months=("01","January","02","February","03","March","04","April","05","May","06","June","07","July","08","August","09","September","10","October","11","November","12","December");
434
435 print "Reading log file into an array\n";
436 #$FILENAME="./ipfilter.log";
437 open (REC, $FILENAME) || die "Cant open $FILENAME: \n";
438 ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$junk)=stat REC;
439 print "Log file $FILENAME is $size bytes in size\n";
440 #each record is an element of array rec[] now
441 while(<REC>) 
442  {
443  @recs[$numrec++]=$_;
444  }
445
446
447 # get list of UNIQUE source IP addresses now, records look like
448 # 192.216.25.254,62910 ->  192.216.16.2,113 PR tcp len 20 40 -R
449 # this is slow on big log files, about 1minute for every 2.5M log file
450 print "Making list of unique source IP addresses (1minute for every 2M log parsed)\n";
451 $loop=-1;
452 $where=-1;
453 while ($loop++ < $#recs )
454  {
455 # get the LHS = source IP address, need fiddle as icmp rcords are logged oddly
456   $bit=substr(@recs[$loop],39);
457   $bit =~ s/,/ /g;
458   ($sourceip,$junkit)= split " " , $bit ;
459   
460 # NOTE the  . is the string concat command NOT + .......!!!!
461
462   $sourceip =~ split " ", $sourceip;
463    $where=index($allips,$sourceip);
464 #  if not found (ie < 0, add it)
465    if ($where < 0 )
466    {
467      $allips = $allips . "$sourceip " ;
468    }
469  }
470   
471 print "Put all unique ip addresses into a 1D array\n";
472 @allips=split " ", $allips;
473
474 #set loop back to -1 as first array element in recs is element 0 NOT 1 !!
475 print "Making compact array of logged entries\n";
476 $loop=-1;
477 $icmp=" icmp ";
478 $ptr=" -> ";
479 $lenst=" len ";
480 $numpackets=0;
481
482 while ($loop++ < $#recs )
483  {
484 # this prints from 39 char to EOR
485  $a=substr(@recs[$loop],39);
486  ($srcip,$dummy,$destip,$dummy2,$dummy3,$dummy4,$lenicmp)= split " " ,  $a ;
487 # need to rewrite icmp ping records.... they dont have service numbers
488  $whereicmp=index($a,"PR icmp");
489  if($whereicmp > 0 )
490  {
491   $a = $srcip . $icmp .  $ptr . $destip  . $icmp . $icmp . $lenst . $lenicmp ;
492  }
493  
494 # dump the "->"  and commas from logging
495  $a =~ s/->//g;
496  $a =~ s/PR//g;
497  $a =~ s/,/ /g;
498 # shortrec has records that look like
499 # 209.24.1.217 123 192.216.16.2 123 udp len 20 76
500  @shortrecs[$loop]= "$a";
501
502 # count number packets from each IP address into hash
503  ($srcip,$junk) = split " ","$a";
504  $numpackets=$numpacks{"$srcip"};
505  $numpackets++ ;
506  $numpacks{"$srcip"}=$numpackets; 
507
508 }
509
510
511
512 # call sub to analyse packets by time
513 # @shortrecs has form 209.24.1.217 123 192.216.16.2 123 udp len 20 76
514 # @recs has form 27/07/1998 00:01:05.216596  le0 @0:2 L 192.216.21.16,2733 -> 192.216.16.2,53 PR udp len 20 62
515 packbytime($XMAX);
516
517 if("$opt_s" eq "1")
518 {
519 # call subroutine to scan for connections to ports on gatekeep
520 # other than those listed in saferports, connections to high
521 # ports are assumed OK.....
522 posbadones;
523
524 # call subroutine to print out which sites had sent more than
525 # a defined % of packets to gatekeep
526 toobusy_site;
527 }
528
529
530 # verbose reporting?
531 if ("$opt_v" eq "1")
532 {
533 $cnt=-1;
534 # loop over ALL unique IP source destinations
535 while ($cnt++ < $#allips)
536 {
537   %tally=();
538   %unknownsrcports=();
539   $uniqip=@allips[$cnt];
540   $loop=-1;
541   $value=0;
542   $value1=0;
543   $value2=0;
544   $value3=0;
545   $set="N";
546
547   while ($loop++ < $#recs )
548    {
549 #    get src IP num,    src port number, 
550 #    destination IP num, destnation port number,protocol
551      ($srcip,$srcport,$destip,$destport,$pro)= split " " ,  @shortrecs[$loop];
552 # loop over all records for the machine $uniqip
553 # NOTE THE STRINGS ARE COMPARED WITH eq NOT cmp and NOT = !!!!
554    if(  "$uniqip" eq "$srcip")
555     {
556 # look up hash of service names to get key... IF ITS NOT THERE THEN WHAT???
557 # its more than likely  a request coming back in on a high port
558 # ....So...
559 # find out the destination port from the unknown (high) src port
560 # and tally these as they may be a port attack
561   if ("$srcport" eq "icmp")
562    { $srcportnam="icmp";}
563   else
564    {
565      $srcportnam=$services{$srcport};
566    }
567 #    try and get dest portname, if not there, leave it as the 
568 #    dest portnumber
569   if ("$destport" eq "icmp")
570    { $destportnam="icmp";}
571   else
572    {
573   $destportnam=$services{$destport};
574    }
575
576   if ($destportnam eq "")
577    {
578      $destportnam=$destport;
579    }
580
581   if ($srcportnam eq "")
582     {
583 #    increment number of times a (high)/unknown port has gone to destport
584      $value1=$unknownsrcports{$destportnam}; 
585      $value1++ ; 
586      $unknownsrcports{$destportnam}=$value1;
587     }
588   else
589    {
590 #    want tally(srcport) counter to be increased by 1
591      $value3=$tally{$srcportnam};
592      $value3++ ; 
593      $tally{$srcportnam}=$value3;
594     }
595    }
596
597
598    }
599 #  end of loop over ALL IP's
600
601 if ($set eq "N")
602 {
603 $set="Y";
604
605 print "\n#### with $uniqip as the the source for packets ####\n";
606 while(($key,$value)=each %tally) 
607   {
608    if (not "$uniqip" eq "$gatekeep")
609     {
610       print "$value connections from $key to gatekeep\n";
611     }
612    else
613     {
614       print "$value connections from gatekeep to $key\n";
615      }
616   }
617
618
619
620 while(($key2,$value2)=each %unknownsrcports) 
621   {
622    if (not "$uniqip" eq "$gatekeep")
623     {
624      print "$value2 high port connections to $key2 on gatekeep\n";
625     }
626    else
627     {
628       print "$value2 high port connections to $key2 from gatekeep\n";
629      }
630   }
631
632 }
633 # print if rests for UNIQIP IF flag is set to N then toggle flag
634
635 } # end of all IPs loop 
636 } # end of if verbose option set block
637
638
639