#!/usr/bin/perl -w
# Accounting controll deamon
# ~AsmodeuS~ (2006.02.03)
# Work only with Dv module



use vars  qw(%RAD %conf @MODULES $db $DATE $GZIP $MYSQLDUMP %ADMIN_REPORT $IPFW
 $IFCONFIG
 @START_FW
 $SNMPWALK
 $SNMPSET
 );
use strict;

use FindBin '$Bin';
require $Bin . '/config.pl';
unshift(@INC, $Bin . "/../Abills/", $Bin . '/../', $Bin . "/../Abills/$conf{dbtype}");

require "Abills/defs.conf";
require "Abills/nas.pl";
require Abills::Base;
Abills::Base->import();
my $begin_time = check_time();

my $debug=0;
my $ARGV = parse_arguments(\@ARGV);

if (defined($ARGV->{debug})) {
  $debug=$ARGV->{debug} || 1;
  print "Debug mode $debug\n";
}
elsif(defined($ARGV->{help})){
  print "Help:
  checkppp    - check Active ppp session and disconnect unknown ip
  checkspeed  - check cure speed for user. Arguments NAS_ID=xx
  checklines  - 
  debug       - debug mode

\n";
  exit;
}



require Abills::SQL;
Abills::SQL->import();
require Users;
Users->import();
require Admins;
Admins->import();

my $sql = Abills::SQL->connect($conf{dbtype}, $conf{dbhost}, $conf{dbname}, $conf{dbuser}, $conf{dbpasswd});
my $db = $sql->{db};
my $admin = Admins->new($db, \%conf);
$admin->info($conf{SYSTEM_ADMIN_ID}, { IP => '127.0.0.1' });

my $default_alive_interval = 120;
my $exppp_hanguplimit = 3500000000;
my $error_alive_count = $conf{ERROR_ALIVE_COUNT} || 5;


if ($admin->{errno}) {
	print  $admin->{errno};
	exit 0;
}

my %LIST_PARAMS = ();
require Dv_Sessions;
my $sessions = Dv_Sessions->new($db, $admin, \%conf);
require Nas;
my $nas = Nas->new($db, \%conf);
my %stats = ();


if (defined($ARGV->{checkppp})) {
  check_ppp();
 }
elsif (defined($ARGV->{checkspeed})){
	check_speed();
 }
else {
  check_lines();
}


 if ($begin_time > 0 && $debug > 0)  {
   Time::HiRes->import(qw(gettimeofday));
   my $end_time = gettimeofday();
   my $gen_time = $end_time - $begin_time;
   printf(" GT: %2.5f\n", $gen_time);
  }


#***********************************************************
# check_lines
#***********************************************************
sub check_lines {
	require Billing;
	Billing->import();

	#$LIST_PARAMS{ROWS}=2;
  $sessions->{debug}=1 if ($debug > 2);
	$sessions->online( { %LIST_PARAMS, ALL => 1, FIELDS => [0, 1, 2, 3, 4, 5, 6, 7, 8, 26] } );	
	
  print "==> check_lines\n" if ($debug > 1);
	
	my $online = $sessions->{nas_sorted};
  $LIST_PARAMS{DISABLE} = 0;
  my $nas_list = $nas->list({ %LIST_PARAMS });
  my %NAS  = ();

  require Acct;
  Acct->import();
  my $Acct = Acct->new($db, \%conf);


  foreach my $nas_row (@$nas_list) {

    my $l = $online->{$nas_row->[0]};
    print "NAS IP: $nas_row->[3]\n NAS_TYPE: $nas_row->[4]\n STATUS: $nas_row->[6]\n Alive: $nas_row->[8]/\n " if ($debug > 0);
    $NAS{NAS_ID}    = $nas_row->[0];
    $NAS{NAS_IP}    = $nas_row->[3];
    $NAS{NAS_TYPE}  = $nas_row->[4];
    $NAS{NAS_ALIVE} = $nas_row->[8] || 0;
    $NAS{NAS_MNG_IP_PORT}  = $nas_row->[9];
    $NAS{NAS_MNG_USER}     = $nas_row->[10]; 
    $NAS{NAS_MNG_PASSWORD} = $nas_row->[11]; 

    foreach my $line (@$l) {
	    my $acct_input_octets       = $line->[5];
	    my $acct_output_octets      = $line->[6];
	    my $acct_input_octets_ext   = $line->[7];
	    my $acct_output_octets_ext  = $line->[8];
	    my $nas_port_id             = $line->[2];
	    my $acct_session_id         = $line->[22];

      if ($acct_input_octets_ext > $exppp_hanguplimit || $acct_output_octets_ext > $exppp_hanguplimit) {
        my $ret = hangup(\%NAS, "$nas_port_id", "$line->[0]", { ACCT_SESSION_ID   => "$acct_session_id",
        	                                                      FRAMED_IP_ADDRESS => int2ip($line->[3])
        	                                                      });
        print "Hangup '$line->[0]' $acct_input_octets, $acct_output_octets, $acct_input_octets_ext, $acct_output_octets_ext, Session-ID: $acct_session_id\n"; 
       }     


      #Get stats from nas servers which don't support rad Alive
      my $external_stats = get_stats(\%NAS, $line->[2]);


      if ( $NAS{NAS_ALIVE} > 0 
           && ( $line->[17] > $NAS{NAS_ALIVE} * $error_alive_count )) {

         if($line->[17] > ($NAS{NAS_ALIVE} * $error_alive_count * 2) ) {
           
           my $ACCT_INFO = $sessions->online_info({ NAS_ID          => $nas_row->[0],
                                                    NAS_PORT        => $line->[2],
                                                    ACCT_SESSION_ID => $acct_session_id
                                                  });

           
           $ACCT_INFO->{INBYTE}            = $acct_input_octets;
           $ACCT_INFO->{OUTBYTE}           = $acct_output_octets;
           $ACCT_INFO->{INBYTE2}           = $acct_input_octets_ext;
           $ACCT_INFO->{OUTBYTE2}          = $acct_output_octets_ext;
           $ACCT_INFO->{ACCT_STATUS_TYPE}  = 'Stop';
           $ACCT_INFO->{ACCT_SESSION_TIME} = $line->[9];
           $ACCT_INFO->{ACCT_TERMINATE_CAUSE} = 15;

           my $r = $Acct->accounting($ACCT_INFO, \%NAS);
           my $info = '';
           
           if ($debug > 0) {
             
   
           	 foreach my $k (sort keys %$ACCT_INFO) {
 	  	          $info .= sprintf("%-28s | %-30s |\n", $k, (defined($ACCT_INFO->{$k})) ? $ACCT_INFO->{$k} : '' );
	           }

             $info .= sprintf("%-28s\n", '----------------------------------------------------');

	           foreach my $k (sort keys %$Acct) {
 		           $info .= sprintf("%-28s | %-30s |\n", $k, (defined($Acct->{$k})) ? $Acct->{$k} : '' );
  	         }
            }

           print "Error: Session Calculate / Login: $line->[0], Last Alive: $line->[17], Session-ID: $acct_session_id\n$info"; # if ( $debug == 1 );
          }
         else {
           print "Error: Lost Alive / Login: $line->[0], Last Alive: $line->[16], Session-ID: $acct_session_id\n"; # if ( $debug == 1 );
           $sessions->zap($nas_row->[0], $line->[2], $acct_session_id);
          }
         
       } 
      elsif (defined($external_stats->{error})) {
      	print "Error:  Login: $line->[0], NAS-IP: $NAS{NAS_IP}, Port: $line->[2], Session-ID: $acct_session_id\n" if ($debug == 1);
      	$sessions->online_update({ USER_NAME       => $line->[0],
      		                         ACCT_SESSION_ID => $acct_session_id,
      		                         STATUS          => 5
      		                      });
       }
      elsif (defined($external_stats->{in})) {
        $sessions->online_update({ USER_NAME => $line->[0], ACCT_SESSION_ID => $acct_session_id, %$external_stats });
       }
      
      # periodic check deposit stats
      if (defined($conf{periodic_check}) && $conf{periodic_check} eq 'yes') {

        my %RAD = (USER_NAME        => "$line->[0]",
                  SESSION_START     => time - time2sec($line->[4]),
                  ACCT_SESSION_TIME => time2sec($line->[4]),
                  INBYTE         => $line->[5],
                  OUTBYTE        => $line->[6],
                  INBYTE2        => $line->[7],
                  OUTBYTE2       => $line->[8]);

        my $Billing = Billing->new($db, \%conf);	
        my $self;

        ($self->{UID}, 
         $self->{SUM}, 
         $self->{BILL_ID}, 
         $self->{TARIF_PLAN}, 
         $self->{TIME_TARIF}, 
         $self->{TRAF_TARIF}) = $Billing->session_sum("$RAD{USER_NAME}", 
                                                    $RAD{SESSION_START}, 
                                                    $RAD{ACCT_SESSION_TIME}, 
                                                    \%RAD);

        next if ($self->{UID} < 1);
        
        print "> Login: $RAD{USER_NAME} START: $RAD{SESSION_START} DURATION: $RAD{ACCT_SESSION_TIME}\n".
         "UID: $self->{UID} SUM: $self->{SUM} BILL_ID: $self->{BILL_ID} TARIF_PLAN: $self->{TARIF_PLAN} TRAF_TARIF: $self->{TRAF_TARIF}\n" if ($debug == 1);

        if ( ($Billing->{PAYMENT_TYPE}==0) && ($line->[14] + $line->[15] < $self->{SUM}) ) {
           my $ret = hangup(\%NAS, "$nas_port_id", "$line->[0]", { ACCT_SESSION_IS   => $acct_session_id,
           	                                                       FRAMED_IP_ADDRESS => int2ip($line->[3])
           	                                                      });
           print "Hanguped Login: $RAD{USER_NAME} DEPOSIT: $line->[14] CREDIT: $line->[14] SESSION_SUM: $self->{SUM} SESSION_ID: $acct_session_id\n"; # if ($debug == 1);
          }
       }


     }
   }
}

#**********************************************************
# Ceck current speed for exppp
#**********************************************************
sub check_speed {		
	
	if (! $ARGV->{NAS_ID}) {
		print "NAS not specify\n";
		exit;
	 }
	
	
	my $result = `$IPFW show; $IPFW pipe show`;
	my @rules = split(/\n/, $result);
	# key1 interface; key2 rule number; value speed
	my %rule_speed = ();
	my %interfaces = ();
	my %speeds_rotations = ('Kbit' => 1,
	                        'Mbit' => 1000);
	
	#Parce all ipfw rules
	foreach my $line (@rules) {
     #Parce ipfw pipe rules
     if ($line =~ /(\d+):\s+(\S+) (\w+)/) {
       my $speed = $2 * $speeds_rotations{$3};
       if (defined($interfaces{$1})) {
         $rule_speed{$interfaces{$1}}{int($1)} = "$speed";
        }
     	}
	   # parce ipfw show
	   elsif ($line =~ /(\d+).+tun(\d+)/){
	     $interfaces{$1}=$2;
	    }
	 }


  #debug 
  if ($debug > 0) {
    while(my($iface, $rules)=each %rule_speed) {
  	  print "$iface\n" if ($debug > 1);
  	  
  	  foreach my $line ( sort keys %$rules ) {
  	 	  # ID -> Speed
  	 	  print " $line -> $rules->{$line}\n" if ($debug == 2);
  	   }
     }
   }

	$sessions->online( { %LIST_PARAMS, NAS_ID => $ARGV->{NAS_ID}, FIELDS => [0,2,11,13]   } );	
	my $online = $sessions->{nas_sorted};
  $LIST_PARAMS{DISABLE} = 0;

  my $nas_list = $nas->list({ PAGE_ROWS => 1000  });

  #my %TP_ARR = ();
  my %IFACES=();
  my %TP_HASH = ();

  foreach my $nas_row (@$nas_list) {
    next if (! $online->{$nas_row->[0]});

    my $l = $online->{$nas_row->[0]};
    foreach my $line (@$l) {
                           #IFACE : TP : SPEED  :IP
       print"$line->[0] IF:$line->[1] TP:$line->[2] SPEED:$line->[3] IP:$line->[7]\n" if ($debug > 1);
       $IFACES{$line->[0]}="$line->[1]:$line->[2]:$line->[3]:$line->[7]";
       $TP_HASH{$line->[2]}=1;
     }
  }



  require Billing;
  Billing->import();
  my $Billing = Billing->new($db, \%conf);
  my $user = $Billing->get_timeinfo();

  require Tariffs;
  Tariffs->import();
  my $tariffs = Tariffs->new($db, $admin);
  my %speeds = ();
  
  
  #GET TP speed
  foreach my $tp (keys (%TP_HASH)) {
  	print "TP: $tp\n" if ($debug == 2);
  
    my ($remaining_time, $ret_attr); 

    ($user->{TIME_INTERVALS},
     $user->{INTERVAL_TIME_TARIF}, 
     $user->{INTERVAL_TRAF_TARIF}) = $Billing->time_intervals($tp);

    ($remaining_time, $ret_attr) = $Billing->remaining_time(0, {
    	    TIME_INTERVALS      => $user->{TIME_INTERVALS},
          INTERVAL_TIME_TARIF => $user->{INTERVAL_TIME_TARIF},
          INTERVAL_TRAF_TARIF => $user->{INTERVAL_TRAF_TARIF},
          SESSION_START       => $user->{SESSION_START},
          DAY_BEGIN           => $user->{DAY_BEGIN},
          DAY_OF_WEEK         => $user->{DAY_OF_WEEK},
          DAY_OF_YEAR         => $user->{DAY_OF_YEAR},
          REDUCTION           => 0,
          POSTPAID            => 1
         });

#    print "RT: $remaining_time\n"  if ($debug == 1);
    my %TT_IDS = %$ret_attr;


    if (keys %TT_IDS > 0) {
      #Get intervals
      while(my($k, $v)=each( %TT_IDS)) {
 	      print " INTERVAL: $k, $v\n" if ($debug == 2);

 	      my $list = $tariffs->tt_list({ TI_ID => $v });
 	      foreach my $line (@$list)  {
 	    	  print "   $line->[0] IN: $line->[4] OUT: $line->[5]\n" if ($debug == 2);
 	    	  $speeds{$tp}{IN}{$line->[0]}="$line->[4]";
 	    	  $speeds{$tp}{OUT}{$line->[0]}="$line->[5]";
 	      }
      }
    }
  
   }


  
 
  my $debug_outputs = '';
  my $cmd_debug='';

  if ($debug > 1) {
  	$cmd_debug='debug';
   }
  
  while(my($user, $other)=each %IFACES) {
  	 my($IFACE, $TP, $SPEED, $IP)=split(/:/, $other);

  	 $debug_outputs = "$user -> IFACE: $IFACE TP: $TP IP: $IP\n";
  	 
     if (! $rule_speed{$IFACE}) {
        $debug_outputs .= "  ! No interface\n";	
        print $debug_outputs if ($debug > 0); 
        next;
      }

  	 
     if ($SPEED > 0)  {
     	  my $fw_num = $START_FW[0] + $IFACE * 2;
     	  $debug_outputs .= " IN:  0, $SPEED ipfw: $fw_num / $rule_speed{$IFACE}{$fw_num}\n".
     	                    " OUT: 0, $SPEED ipfw: ". $rule_speed{$IFACE}{$fw_num + 1} ."\n";
        
     	  if ($SPEED != $rule_speed{$IFACE}{$fw_num} || $SPEED != $rule_speed{$IFACE}{$fw_num + 1} ) {
           my $cmd=`$Bin/linkupdown down tun$IFACE $user $IP $cmd_debug; $Bin/linkupdown up tun$IFACE $user $IP $cmd_debug`;
           $debug_outputs .= $cmd;
           next;
     	   }

     	}
  	 elsif(defined($speeds{$TP})) {
  	 	 $debug_outputs .= " IN:  ";


  	 	       while(my($k, $v)=each %{ $speeds{$TP}{OUT} }) {
  	 	       	  next if ($v == 0);
  	 	       	  my $fw_num = $START_FW[$k] + $IFACE * 2;
  	 	       	  $debug_outputs .= "  $k, $v ipfw: $fw_num";
  	 	       	  if (! $rule_speed{$IFACE}{$fw_num}) {
                  my $cmd=`$Bin/linkupdown down tun$IFACE $user $IP $cmd_debug; $Bin/linkupdown up tun$IFACE $user $IP $cmd_debug;`;
                  $debug_outputs .= "\n". $cmd;
                  goto ENDWHILE;
                  next;
  	 	       	   }
  	 	       	  else {
  	 	       	    $debug_outputs .= "/ $rule_speed{$IFACE}{$fw_num}\n";
     	            
     	            if ($rule_speed{$IFACE}{$fw_num} != $v) {
                    my $cmd=`$Bin/linkupdown down tun$IFACE $user $IP $cmd_debug; $Bin/linkupdown up tun$IFACE $user $IP $cmd_debug`;
                    $debug_outputs .= $cmd;
                    goto ENDWHILE;
                    next;
     	             }
                 }
  	 	        }

   	 	 $debug_outputs .= " OUT: ";
  	 	       while(my($k, $v)=each %{ $speeds{$TP}{OUT} }) {
  	 	       	  next if ($v == 0);
  	 	       	  my $fw_num = $START_FW[$k] + $IFACE * 2 + 1 ;
  	 	       	  $debug_outputs .= "  $k, $v ipfw: $fw_num";

  	 	       	  if (! defined($rule_speed{$IFACE}{$fw_num})) {
                    my $cmd=`$Bin/linkupdown down tun$IFACE $user $IP $cmd_debug; $Bin/linkupdown up tun$IFACE $user $IP $cmd_debug;`;
                    $debug_outputs .= "\n". $cmd;
                    next;
  	 	       	   }
  	 	       	  else {
  	 	       	    $debug_outputs .= "/ $rule_speed{$IFACE}{$fw_num}\n";
     	            
     	            if ($rule_speed{$IFACE}{$fw_num} != $v) {
                    my $cmd=`$Bin/linkupdown down tun$IFACE $user $IP $cmd_debug; $Bin/linkupdown up tun$IFACE $user $IP $cmd_debug;`;
                    $debug_outputs .= $cmd;
                    next;
     	             }
    	 	       	 }
  	 	        }

  	  }
  	 else {
  	 	 $debug_outputs .= " IN:  N/S (TP: $TP)\n";
  	  }

  	 ENDWHILE:
  	 print $debug_outputs if ($debug == 2); 
   }




}


#**********************************************************
# check Active ppp session and disconnect unknown ip
#**********************************************************
sub check_ppp {
  my $ip_pid_hash = get_if_info();
  get_db_info($ip_pid_hash);


while(my($ip_address, $info)=each %$ip_pid_hash) {
  my($pid, $iface)=split(/:/, $info, 2);
  if ($conf{DV_PPP_UNCHECKED}) {
    next if ($ip_address =~ /$conf{DV_PPP_UNCHECKED}/);
   }
  
  my $res = '';  
  if ($pid > 0) {
    $res = `/bin/kill -9 $pid; /sbin/ifconfig tun$iface delete`;
    # /sbin/ifconfig tun$iface delete
   }
  else {
    $res = `/sbin/ifconfig tun$iface delete`;
   }

  print "Killed IP: $ip_address PID: '$pid' -- $res\n";  

  get_pid_info($pid);
}

}

#**********************************************************
#Get ipaddreses of active sessions
#**********************************************************
sub get_db_info {
 my ($ip_pid_hash) = @_;
 my $list = $sessions->online( { FIELDS => [3] } );	
 foreach my $line (@$list) {
   delete $ip_pid_hash->{int2ip("$line->[0]")};
  }
}


#**********************************************************
#Get pid info
#**********************************************************
sub get_pid_info {
 my ($pid, $attr) = @_;
 my $log_file='/var/log/ppp.log';

 print "\nShow log file info:\n";
 open(FILE, "<$log_file") || die "Can't open log file '$log_file' $! " ;
   while(<FILE>) {
     if (/\[$pid\]/) {
       print $_;
      }
    }
 close(FILE);
}

#**********************************************************
# Get tun interface info
#**********************************************************
sub get_if_info {
 my $res = '';
 my $count = 0;
 my $ifconfig = '';

 my %ip_pid_hash = ();

 open (IFC, "$IFCONFIG -u |") || die "Can't open file '$IFCONFIG' $!";
  while (my $l = <IFC>) {
     $ifconfig .= $l;
    }
 close(IFC);


 while($ifconfig =~ /tun(\d+): (.+\n\s.+\n\s.*[\n\sa-zA-Z0-9]+\n)/gi) {
    my $tunnum = $1;
    my $res    = $2;

    my $pid    = 0;
    my $addr   = '0.0.0.0';

    my @ifs = split(/\n/, $res);

    foreach my $line (@ifs) {
      print $line."\n" if ($debug > 1);
      if ($line =~ /^(\d+)/) {
         $tunnum=$1;
        }
      elsif ($line =~ m/\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3} --> (\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3})/g) {
         $addr = $1;
        }
      elsif ($line =~ /PID (\d+)/gi) {
         $pid = $1;
         #chomp($pid);
         #chop($pid);
       }
     }
    print "$tunnum $addr $pid\n" if ($debug == 1);
    $ip_pid_hash{"$addr"}="$pid:$tunnum";
  }

  return \%ip_pid_hash;
}


__END__

