#! /usr/bin/perl

#  flowscan - a utility to analyze and report on Cflowd flow files
#  Copyright (C) 1998-2001  Dave Plonka
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

# $Id: flowscan.in,v 1.20 2001/02/16 21:17:26 dplonka Exp $
# Dave Plonka <plonka@doit.wisc.edu>

require 5.004; # for UNIVERSAL::can method
use FindBin;
use Cflow qw(:flowvars 1.017);
use Benchmark;
use Getopt::Std;
use POSIX; # for strftime
use File::Basename;
use ConfigReader::DirectiveStyle;
use lib $FindBin::Bin;
use FlowScan; # for mutt_mktime, etc.

'$Revision: 1.20 $' =~ m/(\d+)\.(\d+)/ && (( $VERSION ) = sprintf("%d.%03d", $1, $2));

# Set the default options from the configuration file:
$c = new ConfigReader::DirectiveStyle;
$c->directive('Verbose');
$c->directive('WaitSeconds');
$c->required('FlowFileGlob');
$c->required('ReportClasses');
#$c->load("${FindBin::Bin}/${FindBin::Script}.cf");
$c->load("/etc/${FindBin::Script}/${FindBin::Script}.cf");
$flowfileglob = $c->value('FlowFileGlob');
$opt_w = $c->value('WaitSeconds');
$opt_v = $c->value('Verbose');
@classes = split(m/\s*,\s*/, $c->value('ReportClasses'));

if (!getopts('hvw:g:s:') || $opt_h) {
   print STDERR <<_EOF_
usage: $FindBin::Script [-hv] [-w secs] [-s bytes] FlowScanClass [...]
       -g - use this glob (file pattern match) when looking for raw
	    flow files to be processed.  Defaults to: '$flowfileglob'
	    (mnemonic: 'g'lob)
       -h - shows this usage information (mnemonic: 'h'elp)
       -v - verbose - show warnings (mnemonic: 'v'erbose)
       -w secs - process the flow files, and wait secs seconds for new ones
		 to appear.  Flow file will be globbed using
		 "$flowfileglob".  (Don't pass flow file names as arguments
		 when using this option.)
       -s bytes - skip processing of files of size greater than bytes
		  (mnemonic: 's'kip 's'ize)
_EOF_
   ;
   exit($opt_h? 0 : 2)
}

if ($opt_g) {
   $flowfileglob = $opt_g
}

if (@ARGV) {
   @classes = @ARGV
}

foreach my $class (@classes) {
   eval "use $class";
   die "$@" if $@
}

Cflow::verbose($opt_v);

   while (1) {
      my @files = sort by_timestamp <${flowfileglob}>;
      if (@files) {
         my $file;
	 foreach $file (@files) {
	    my @s = stat($file);
	    my $dirname = dirname $file;
	    my $basename = basename $file;

	    if (!$opt_s || $s[7] <= $opt_s) {

            foreach (@classes) {
               push(@objects, $_->new || die "$_->new failed\n")
	    }
	    # note that "perfile" sets $router which is used by "wanted"...
	    my $t0 = new Benchmark;
	    my $size = 0;
	    foreach ($file) {
	       if (@_ = stat) {
	          $size += $_[7]
	       }
	    }
            my $result = Cflow::find(\&wanted, \&perfile, $file);
	    my $t1 = new Benchmark;
	    warn(strftime("%Y/%m/%d %H:%M:%S", localtime),
		 " $FindBin::Script-$VERSION @classes: Cflow::find took ",
		 timestr(timediff($t1, $t0)),
		 " for $size flow file bytes, flow hit ratio: ${result}\n")
		    if $opt_v;
            &report;
	    my $t2 = new Benchmark;
	    warn(strftime("%Y/%m/%d %H:%M:%S", localtime),
		 " $FindBin::Script-$VERSION @classes: report took ",
		 timestr(timediff($t2, $t1)), "\n") if $opt_v;
	    } else {
               warn(strftime("%Y/%m/%d %H:%M:%S", localtime),
	            " skipping file ${file} of size $s[7] bytes.\n") if $opt_v;
	    }

	    if (-d "$dirname/saved") {
	       goto ok_label if rename($file, "$dirname/saved/$basename");
               warn strftime("%Y/%m/%d %H:%M:%S", localtime),
		  " rename \"$file\", \"$dirname/saved/$basename\": $!\n";
	    }
	    if (!unlink($file)) {
               warn strftime("%Y/%m/%d %H:%M:%S", localtime),
		  " unlink \"$file\": $!\n";
	    }
ok_label:
	    # &zero # clear out the totals
	    @objects = () # DESTROY all objects
	 }
      } else {
	 last unless $opt_w;
	 warn("sleep ${opt_w}...\n") if ($opt_v);
         sleep $opt_w;
      }
   }

exit 0;

sub perfile {
   my $file = shift;
   warn(strftime("%Y/%m/%d %H:%M:%S", localtime),
	" working on file ${file}...\n") if $opt_v;
   foreach (@objects) {
      next unless ($_->can('perfile'));
      $_->perfile($file)
   }
}

sub wanted {
   my $rv = 0; # boolean return value (for "hit ratio" feature of Cflow)
   foreach (@objects) {
      next unless ($_->can('wanted'));
      if ($_->wanted) {
	 $rv = 1
      }
   }
   return $rv
}

sub report {
   foreach (@objects) {
      next unless ($_->can('report'));
      $_->report
   }
}

sub by_timestamp {
   FlowScan::file2time_t($a) <=> FlowScan::file2time_t($b)
}
