#!/usr/pkg/bin/perl

# @(#)proc-radius.pl,v 1.2 2004/04/23 06:14:32 kim Exp

#
# isdn_watcher: script responsible for parsing isdn access logs on a daily
#   basis in order to watch for high usage (expen$ive) patterns
#

$USAGE="\n$0 -n <radius logfile>\n\t-n\tDo NOT send any mail - print summary to STDOUT\n\n";

require 'ctime.pl';
require 'getopts.pl';
do Getopts('nh');

#
# Need to send syslogd a HUP in order to get it to start writing new radius
# logfile.  Should do this before spending time processing data because new
# data won't get logged to the new file until we do.
#
#if (!defined($opt_n)) {
#    system("/usr/local/bin/skill -HUP syslogd");
#}

if ( $opt_h ) {
    print $USAGE;
    exit 0;
}

if ( $#ARGV != 0 ) {
    die $USAGE;
}

open(INPUT,$ARGV[0]) || die ("Cannot open logfile $ARGV[0]: $!");

%monthval=('Jan',1,'Feb',2,'Mar',3,'Apr',4,'May',5,'Jun',6,'Jul',7,'Aug',
	8,'Sep',9,'Oct',10,'Nov',11,'Dec',12);

# This next 3 arrays keep track of the slots/ports of lines that
# terminated so that can be associated with the next "LAN session down"
@lastdownslot = ();
@lastdownport = ();
@lastdowntime = ();
$firstline = 1;

while (<INPUT>) {

    if ($firstline) {
	($init_time) = ($_ =~ /\w+\s+\d+ (\d\d:\d\d:\d\d).*/);
	$firstline = 0;
    }
    if (/(\w+)\s+(\d+) (\d\d:\d\d:\d\d)\s+([\w\.]+)\s+ASCEND: slot (\d+) port (\d+), Call Terminated$/) {

	# Record slot and port information because if these values are 0
	# for the next "LAN session down" entry, these values will apply.
	# Might be possible to compare this time stamp the next
	# "LAN session down" entry to be doubly sure they go together,
	# but I'm not 100% sure the timestamp will match to the second.
	#
	# ... since 2 lines can go down at once, it is necessary to keep
	# track of multiple slots/ports and their timstamps.  Technically,
	# we shouldn't use LIFO, but it doesn't matter.

	($mon,$date,$time,$tshost,$slnum,$ptnum) = ($1,$2,$3,$4,$5,$6);
	push @lastdownslot, $slnum;
	push @lastdownport, $ptnum;
	push @lastdowntime, $time;

    } elsif (/(\w+)\s+(\d+) (\d\d:\d\d:\d\d)\s+([\w\.]+)\s+ASCEND: slot (\d+) port (\d+), LAN session (up|down), ([\w\.\-]+)$/) {
	($mon,$date,$time,$tshost,$slnum,$ptnum,$state,$rmhost) =
				    ($1,$2,$3,$4,$5,$6,$7,$8);

	if ($rmhost =~ /^(.*)-out$/) {
		$rmhost = $1;
		$logname = "outeventlog";
	} else {
		$logname = "ineventlog";
	}

	if ($slnum eq "0" && $ptnum eq "0" && $state eq "down") {
		# See if we have a saved slot/port number within last
		# 3 seconds (semi-random choice, must be > 0).
		if (defined($lasttime = pop(@lastdowntime)) &&
		    deltatime($lasttime, $time) < 3) {
			# Get real slot and port number
			$slnum = pop(@lastdownslot);
			$ptnum = pop(@lastdownport);
		} else {
			@lastdownslot = ();
			@lastdownport = ();
			@lastdowntime = ();
		}
	}
	$$logname{$rmhost} .=
		"$monthval{$mon} $date $time $tshost $slnum $ptnum $state\n";
    }
}
$end_time = $time;

sub reporttime {
    #
    # Takes seconds as an argument, converts to English
    #
    $h = 0; $m = 0; $s = 0;
    $t = $_[0];
    while ($t > 3600) { $h++; $t -= 3600; }
    while ($t > 60) { $m++; $t -= 60; }
    $s = $t;
    return sprintf("%d:%02d:%02d",$h,$m,$s);
}

# This routine used to use a bogus calculation with months and days.
# Now it is simplified.  There is still a boundary case I suppose.
sub deltatime {
    my ($starttime, $stoptime) = @_;
    my ($starthour, $startmin, $startsec) = split(/:/, $starttime);
    my ($stophour, $stopmin, $stopsec) = split(/:/, $stoptime);
    my $delta =
		($stophour - $starthour) * 3600 +
		($stopmin - $startmin) * 60 +
		($stopsec - $startsec);
    return (($delta < 0) ? ($delta + 86400) : $delta);
}

#
# Make a list of all users in the {in|out}eventlogs
#

foreach $rmhost ((keys %ineventlog),(keys %outeventlog)) {
    $keylist{$rmhost} = 1;
}

#
# Now, process the entries:
#

sub add_call {
    # This routine inherits all its variables from process_host.
    # Not pretty, but it is just here to avoid code duplication.

    $elapsed = &deltatime($stime{$key}, $time);
    $$call_detail_ref .= "\t$smon{$key}/$sdate{$key} $stime{$key} - $mon/$date $time\t= ";
    $$call_detail_ref .= sprintf("%s\n",&reporttime($elapsed));
    $total_time += $elapsed;
    $num_calls++;
    $curstate{$key} = "down";
}

sub process_host {
    my $events_ref = $_[0];
    local $call_detail_ref = $_[1];
    local ($total_time, $num_calls, $elapsed) = (0, 0, 0);
    local %curstate = "";
    local %smon, %sdate, %stime;
    local $key;

    chop $$events_ref;
    foreach $_ (split(/\n/,$$events_ref)) {
	($mon,$date,$time,$tshost,$slnum,$ptnum,$state) = split (/ /, $_);
	$key = $slnum . ":" . $ptnum;
	if ($state eq "down") {
	    if (!$curstate{$key}) {	# Line was up last time script ran
		$stime{$key} = $init_time;
		$smon{$key} = substr("  ", 0, length($mon));
		$sdate{$key} = substr("  ", 0, length($date));

		# decrement num_calls since this call was actually
		# counted during the last script run.
		$num_calls--;
	    } elsif ($curstate{$key} ne "up") {
		# This _really_ shouldn't happen
		next;
	    }
	    &add_call;
	} else {		# $state eq "up"
	    ($smon{$key}, $sdate{$key}, $stime{$key}) = ($mon,$date,$time);
	    $curstate{$key} = "up";
	}
    }
    # For lines that are up at this time, count them ksessions.
    foreach $key (keys %curstate) {
	next if ($curstate{$key} ne "up");
	$time = $end_time;
	$date = substr("  ", 0, length($date));
	$mon = substr("  ", 0, length($mon));
	&add_call;
    }
    return ($total_time, $num_calls);
}

%call_detail = ();
foreach $rmhost (sort keys %keylist) {
    $tot_callsin{$rmhost}=0;
    $tot_callsout{$rmhost}=0;
    $intotal=0;
    $outtotal=0;

    $call_detail{$rmhost} = "dialin usage pattern for $rmhost\n";
    ($intotal, $tot_callsin{$rmhost}) =
	process_host (\$ineventlog{$rmhost}, \$call_detail{$rmhost});

    $call_detail{$rmhost} .= "dialout usage pattern for $rmhost\n";
    ($outtotal, $tot_callsout{$rmhost}) =
	process_host (\$outeventlog{$rmhost}, \$call_detail{$rmhost});

    $tot_dialin{$rmhost} = $intotal;
    $tot_dialout{$rmhost} = $outtotal;
    $tot_usage{$rmhost} = $intotal + $outtotal;
}

sub byrevusage {
    $tot_usage{$b} <=> $tot_usage{$a};
}

format TOP = 
ISDN usage for @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
$date

                      Total              Inbound            Outbound
                 ================     ==============     ==============
 User            Calls    Time        Calls   Time       Calls   Time
 --------------  ----- ----------     ----- --------     ----- --------
.

format DETAIL =
 @<<<<<<<<<<<<<< @>>>> @>>>>>>>>>     @>>>> @>>>>>>>     @>>>> @>>>>>>>
$rmhost,$tot_callsin{$rmhost}+$tot_callsout{$rmhost}$tot,$tot_callsin{$rmhost},$in,$tot_callsout{$rmhost},$out
.

select (STDOUT);
$^ = "TOP";
$~ = "DETAIL";

if (!defined($opt_n)) {
    open (SYSREADERS, "| Mail -s 'Ascend (ISDN) statistics' systems-readers") || 
	die "Could not fork mail: $!";
    select (SYSREADERS);
    $^ = "TOP";
    $~ = "DETAIL";
}

$summary_details = "";
$date = &ctime(time);
foreach $rmhost (sort byrevusage keys %tot_usage) {
    $in = &reporttime($tot_dialin{$rmhost});
    $out = &reporttime($tot_dialout{$rmhost});
    $tot = &reporttime($tot_usage{$rmhost});
    $summary_details .= "$call_detail{$rmhost}\n";
    if ($opt_n) {
	write(STDOUT);
    } else {
	write(SYSREADERS);
	if ( $rmhost =~ /(\w+)-.+/ ) {
	    $user = $1;
	} else {
	    $user = $rmhost;
	}
        open (USERMAIL, "| Mail -s 'Ascend (ISDN) statistics for $user' $user") || 
	    die "Could not fork mail: $!";
        select (USERMAIL);
	$^ = "TOP";
	$- = 0;			# Force top of form for this file descriptor
        $~ = "DETAIL";
	write(USERMAIL);
	print USERMAIL "\n\n$call_detail{$rmhost}\n";
	close(USERMAIL);
    }
}

if ($opt_n) {
    print STDOUT "\n\n$summary_details";
} else {
    print SYSREADERS "\n\n$summary_details";
    close (SYSREADERS);
}
