#!/usr/bin/perl -w
##
## Analyse.pl 
##
## Made by Vincent HANQUEZ <rv@crans.org> 
##
## Started on  Tue 09 Oct 2001 01:28:25 AM CEST tab 
## Last Update dim 10 fév 2002 16:55:24 CET Nicolas STRANSKY
##
##    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; only version 2 of the License
## 
##    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., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
##
##
## AUTEUR: Vincent HANQUEZ 
## MAINTAINERS: Vincent HANQUEZ, Nicolas STRANSKY
##
## DESCRIPTION: analyse permet de creer des resumes des fichiers de 
## log cree par net-acct. Net-acct est un daemon qui permet de logguer toutes
## les connexions effectues. 
##
## SYNOPSIS: analyse [-d] [-h <host>][-m <to>][-n <nb>][-f <file>]
##
## VERSION: 0.35
##
##

use strict;
use File::stat;
use POSIX qw(strftime);
use AppConfig qw(:expand :argcount);
require Mail::Send;

my $ip_interne = "138\.231\.1(3[6-9]|4[0-3])\.";

## Do not modify after

my $VERSION = 0.35;
my $AUTEUR = "Vincent HANQUEZ (aka Tab)";

my $pattern_ip =
  "[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}\.[[:digit:]]{1,3}";

my ($opt_download, $opt_upload) = 0;
my $opt_dnsresolve = 1;
my $opt_normalize = 1;
#my $opt_config = 1;
my $opt_config = 0;
my $opt_file = "/var/log/net-acct/net-acct.log.0";
my $opt_configfile = "/etc/analyse.conf";
my $opt_host = "";
my $opt_mail = "";
my $opt_number = -1;
my $bad_arg = 0;
my $period = 1;
$opt_upload = 1;

######## ERROR GEST ########
# USAGE: affiche en cas d'erreur dans les arguments.

sub usage
{
  print "Usage:\tanalyse (-d) [-h <host>][-m <to>][-n <nb>][-f <file>]\n\n";

  print "\t-d, --download\tTri la base sur le download\n";
  print "\t-h, --host\tResume des connexions effectues par la machine <host>\n";
  print "\t-n, --nombre\tChoisi le nombre de lignes affichees\n";
  print "\t-m, --mail\tEnvoie la sortie par mail a <to>\n";
  print "\t-f, --file\tSpecifie le fichier qui sera analyse\n";
  print "\t-c, --config\tSpecifie le fichier de configuration\n";
  print "\t    --nodns\tNe resout pas les noms DNS\n";
  print "\t    --nonorm\tNe transforme pas les nombres en forme plus lisible (Ko, Mo, Go)\n";
  print "\t    --today\tChiffres du jour, analyse de /var/log/net-acct/net-acct.log\n";
  print "\t    --noconfig\tNe charge pas les options du fichier de config\n";
  print "\t    --noperiod\tN'affiche pas la période sur laquelle porte l'analyse\n";
  print "\t    --help\tAffiche cette aide\n";
  print "\n";
  print "par defaut si l'option n'est pas utilise:\n";
  print "\tdownload = on tri sur l'upload\n";
  print "\tnombre = tout est affiche\n";
  print "\tfile = analyse de /var/log/net-acct/net-acct.log.0\n"; 
  print "\tconfig = par defaut /etc/analyse.conf";

  exit (1);
}

# SETOPT: gerer les options..
sub setopt
{
  my ($opt_name, $opt_param) = @_;
  if ($opt_name =~ /f/)
  { $opt_file = $opt_param; }
  elsif ($opt_name =~ /h/)
  { $opt_host = $opt_param; }
  elsif ($opt_name =~ /n/)
  { $opt_number = $opt_param; }
  elsif ($opt_name =~ /c/)
  { $opt_configfile = $opt_param; }
  elsif ($opt_name =~ /m/)
  { $opt_mail = $opt_param; }
}

# Readconfig

sub read_configfile
{
  my $info = stat($opt_configfile) || die("Unable to open $opt_configfile");
  my $config = AppConfig->new(
      'normalize',
      'dnsresolve',
      'network' => { ARGCOUNT => 1 }
      );
  $config->file($opt_configfile);
  $opt_normalize = $config->normalize();
  $opt_dnsresolve = $config->dnsresolve();
  $ip_interne = $config->network();
}


########## FCT ###########
# SHOW_ALL: affiche toutes les machines

sub show_all
{
  my %db;

  my ($nb_to_print, $sort_by_upload) = @_;

# creation de la base
  open (INPUT, $opt_file) || die ("Unable to open $opt_file");
  while (my $line = <INPUT>) {
    my ($src_ip, $dst_ip, $size) = (split "\t", $line)[2,4,6];
    if ($src_ip =~ /$ip_interne/ ) 
    { $db{$src_ip}->{up} += $size; }
    if ($dst_ip =~ /$ip_interne/ ) 
    { $db{$dst_ip}->{down} += $size; }
  }
  close(INPUT);

# Nombre d'entree a afficher
  if ($nb_to_print == -1)
  { $nb_to_print = (my @nb = (keys %db)); }

  my $max = (keys %db)[0];

# tris sur upload ou download
  my $way1 = "up";
  my $way2 = "down";
  if ($sort_by_upload == 0)
  { $way1 = "down"; $way2 = "up"; }

# on affiche $nb_to_print entree, en ayant trier les machines
if ($period)  
{
  open (INPUT, $opt_file) || die ("Unable to open $opt_file"); 
  my $line1 = <INPUT>;
  my $firstdate = (split "\t", $line1)[0];
  my $firstdate2 = strftime("Analyse depuis le %d-%m-%Y %H:%M:%S", localtime($firstdate));
  print $firstdate2;
  print "\n";
  
  seek(INPUT, -90, 2);
  my $lastlineX = <INPUT>;
  my $lastline = <INPUT>;
  my $lastdate = (split "\t", $lastline)[0];
  my $lastdate2 = strftime("          jusq'au %d-%m-%Y %H:%M:%S", localtime($lastdate));
  print $lastdate2;
  print "\n";
  close(INPUT);
  
  print "\n";}


  for (my $dec = $nb_to_print; $dec > 0; $dec--) 
  {
    foreach my $ip (keys %db) 
    {
      if (defined($db{$ip}->{$way1}) && 
	  $db{$max}->{$way1} <= $db{$ip}->{$way1}) 
      { $max = $ip; }
    }
    print normalize($db{$max}->{$way1})." (".
      normalize($db{$max}->{$way2}).")\t".
      ip_to_name($max)."\n" if ($db{$max}->{$way1} > 0);
    $db{$max}->{$way1} = 0;
  }
}

#############################
# GET_HOST_INFO: Affiche les informations sur une machine
sub get_host_info
{
  my $total_size;
  my %db;
  my ($ip_search) = @_;

  $total_size->{UPLOAD} = 0; $total_size->{DOWNLOAD} = 0;

# Must unresolve name 
  if (!($ip_search =~ /$pattern_ip/))
  { $ip_search = name_to_ip($ip_search); if ($ip_search eq "0") { return (-1); } }

# create db
  open (INPUT, $opt_file) || die ("Unable to open $opt_file"); 
  while (my $line = <INPUT>) {
    my ($src_ip, $src_port, $dst_ip, $dst_port, $size) = 
      (split "\t", $line)[2,3,4,5,6];
    if ($src_ip eq $ip_search) {
      $total_size->{UPLOAD} += $size;
      $db{$src_port}->{UPLOAD_LOCAL} += $size;
      $db{$dst_port}->{UPLOAD_DIST} += $size;
    } 
    if ($dst_ip eq $ip_search) {
      $total_size->{DOWNLOAD} += $size;
      $db{$dst_port}->{DOWNLOAD_LOCAL} += $size;
      $db{$src_port}->{DOWNLOAD_DIST} += $size;
    }
  }
  close (INPUT);

# printing info
if ($period)
{
  open (INPUT, $opt_file) || die ("Unable to open $opt_file"); 
  my $line1 = <INPUT>;
  my $firstdate = (split "\t", $line1)[0];
  my $firstdate2 = strftime("Analyse depuis le %d-%m-%Y %H:%M:%S", localtime($firstdate));
  print $firstdate2;
  print "\n";
  
  seek(INPUT, -90, 2);
  my $lastlineX = <INPUT>;
  my $lastline = <INPUT>;
  my $lastdate = (split "\t", $lastline)[0];
  my $lastdate2 = strftime("         jusqu'au %d-%m-%Y %H:%M:%S", localtime($lastdate));
  print $lastdate2;
  print "\n";
  close(INPUT);
  
  print "\n";}
  print "--- Info for $ip_search ---";
  print "\n";

  foreach my $type ("UPLOAD", "DOWNLOAD")
  {
    print $type.":  \t".normalize($total_size->{$type})."\n";
    foreach my $location ("LOCAL", "DIST")
    {
      print "PORT $location: ";

      my $max = (keys %db)[0];
      for (my $dec=5; $dec > 0; $dec--) {
	foreach my $port (keys %db) {
	  if (defined($db{$port}->{$type."_".$location}) &&
	      (! defined($db{$max}->{$type."_".$location}) ||
	       $db{$max}->{$type."_".$location} <= 
	       $db{$port}->{$type."_".$location}))
	  { $max = $port; }
	}
	print "$max(".normalize($db{$max}->{$type."_".$location}).") " 
	  if ($db{$max}->{$type."_".$location} > 0);
	$db{$max} ->{$type."_".$location} = 0;
      }
      print "\n";
    }
    print "\n";
  }

  print "--- Calculs ---\n";
  print "Upload pur:\t".
    normalize($total_size->{UPLOAD} - $total_size->{DOWNLOAD}/20);
  foreach my $type ("UPLOAD", "DOWNLOAD")
  {
    print "\nTAUX $type:\t";
    print normalize($total_size->{$type}/24)."/h  -  ";
    print normalize($total_size->{$type}/1440)."/min  -  ";
    print normalize($total_size->{$type}/86400)."/s";
  }
  print "\n";
}

###########################
######### TOOLKIT #########
# normalize: transforme un nombre, en un nombre suivi d'un prefixe
# Go, Mo, Ko, Octets.

sub normalize
{
  my ($nb) = @_;
  if ($opt_normalize)
  {
    if (defined $nb) {
      if ($nb < 0)
      { return ("0o"); }
      if ($nb > (1024*1024*1024))
      { return ((int($nb*100/(1024*1024*1024))/100)."Go"); }
      elsif ($nb > (1024*1024))
      { return (int($nb/(1024*1024))."Mo"); }
      elsif ($nb > 1024)
      { return (int($nb/(1024))."Ko"); }
      else
      { return ($nb."o"); }
    }
    return ("0");
  } else
  { 
    if (defined $nb)
    { return ($nb); }
    else
    { return (0); }
  }
}

# ip_to_name: resout le nom associe a l'ip donne en argument
sub ip_to_name
{
  my ($ip) = @_;
  my $ret;
  my $host_name;
  my $aliases;
  my $addrtype;
  my $length;
  my @addrs;

  if (!($ip =~ /$pattern_ip/))
  { return ($ip); }

  if ($opt_dnsresolve) {
    my $ipaddr = pack("C4", split(/\./, $ip));
    if (($host_name, $aliases, $addrtype, $length, @addrs) 
	= gethostbyaddr($ipaddr, 2)) 
    { return ($host_name); } 
    else
    { return ("$ip [lookup failed]"); }
  }
  return ($ip);
}

# name_to_ip: deresout le nom
sub name_to_ip
{
  my ($host) = @_;
  my $line = "";
  if ($host =~ /$pattern_ip/)
  { return ($host); }
  open (COM, "host $host | awk '{print \$4}' |");
  $line = <COM>;
  close (COM);
  if (! ($line =~ /$pattern_ip/))
  { print "host $host don't exist aborting\n"; return ("0");}
  chomp($line);
  return ($line);
}

##########################################################################

# Lit les options de la ligne de commande
use Getopt::Long;
Getopt::Long::config ("bundling");
Getopt::Long::GetOptions
(
 'v|version'	=> sub { print "analyse version $VERSION\n$AUTEUR\n"; },
 'd|download'	=> sub { $opt_download = 1; $opt_upload = 0; },
 'u|upload'	=> sub { $opt_upload = 1; $opt_download = 0; },
 'c|config=s'	=> \&setopt,
 'h|host=s'	=> \&setopt,
 'f|file=s'	=> \&setopt,
 'm|mail=s'	=> \&setopt,
 'n|number=s'	=> \&setopt,
 'help'		=> \&usage,
 'nodns'	=> sub { $opt_dnsresolve = 0; },
 'nonorm'	=> sub { $opt_normalize = 0; },
 'noconfig'	=> sub { $opt_config = 0; },
 'noperiod'	=> sub { $period = 0; },
 'today'	=> sub { $opt_file = "/var/log/net-acct/net-acct.log"; }
) or $bad_arg = 1;

if ($bad_arg == 1)
{ usage(); }

my ($mail_msg, $mail_fh);

# Lit les options du fichier de config 
# sauf si --noconfig est utilise
if ($opt_config)
{ read_configfile(); }

# Begin Mail ?
if ($opt_mail ne "")
{
  $mail_msg = new Mail::Send;
  $mail_msg->to($opt_mail);
  $mail_msg->subject("[ANALYSE]");
  $mail_fh = $mail_msg->open();
  close (STDOUT);
  open (STDOUT, ">/tmp/analyse_mail");
}

# Main Part
if ($opt_host ne "")
{ get_host_info($opt_host); } 
else 
{ show_all($opt_number, $opt_upload); }

# Close Mail 
if ($opt_mail ne "")
{
  close (STDOUT);
  open (FH, "/tmp/analyse_mail") || die ("argh not possible !\n");
  while (my $line = <FH>)
  { print $mail_fh $line; }
  close (FH);
  $mail_fh->close();
}

exit (0);
