#!/usr/bin/perl
#
# radiusreport - Extract information from Radius 2.0 detail log
#
# Author:	Paul Gregg <pgregg@tibus.net>
# Date:		25 March 1998
# Summary:	Radius,  User's activity,  Port usage,
# Version: 	0.3 - beta 6 (messy code) Interim patch release before 1.0
# Copyright:	1997,1998, Paul Gregg <pgregg@tibus.net>
# Copy Policy:  Free to copy and distribute provided all headers are left
#		intact and no charge is made for this program.  I would
#		appreciate copies of any modifications to the script.
# Inspriation:	userlog radius parser script by
# 		Dave Andersen <angio@aros.net> / Joe Hartley <jh@brainiac.com>
#		I got fed up of trying to hack my mods into it ;-)
# URL:          http://www.tibus.net/pgregg/projects/radiusreport/
# FTP:          None yet (I'm lazy)
#
#
# Supported:	Livingston Radius V2.0+, V1.16
#		Merit Radius 
#		Ascend Radius
#		Dale Reed's RadiusNT
#		Radiator Perl Radius Server
#
# Comments on this extremly welcomed
#
# Usage:
#   radiusreport --help
#   radiusreport [-tbahrqcs] [-i a.b.c.d] [-l username|all]
#		 -f detailfile[:detailfile]
#    Flags:
#     -t Report on total online time
#     -b Report on total bandwidth passed per session
#     -q Report on how the connection was dropped.
#     -h Suppress report header information
#     -H Suppress per login reports.
#     -a Do 'Average use' report at end
#     -r Provide a list of accounts in the users file and their last login time
#     -l username is the radius username you wish a report on
#        -o If you use "-l all" and you specify -o directory then the reports
#           will be placed here instead of sending them to stdout.
#     -f detailfile is the path/filename of any detail file which may or 
#        may not be compressed (.Z or .gz).  Multiple details files may be
#        separated by a colon.
#     -i ipaddress will produce a report on users using a specific IP address.
#        or -i 0 to report on all logins.
#     -c Work out call charge by the Telco.
#     -d mon[:mon] Only report on this month (or months). mon can be in
#	 format 'Jan' or '01' or '1'. Multiple months are separated by colon
#	 characters ':'. (Caveat: Logins which roll over months are included)
#     -s connection baud rate (Contributed by kjmiller <kjmiller@pcom.net> )
#
# Todo - Suggestions welcome.
#   radiusreport [-l username|all] [-f detailfile[:detailfile]]
#                [-t yyyy[mm[dd]][-yyyy[mm[dd]]]] [-m x..y]
#    Additional Flags
#     -? Only report on the specified year/month/day (or in the yymmdd range)
#     -? Produce a modem usage table
#    Jeff Halper <jeff@ihot.com> wants daily/monthly summaries - no individual
#    login/out details.

require "ctime.pl";
use POSIX;

#Unbuffered output
$| = 1;

# Program and File locations

$USERS = "/etc/raddb/users";	# Hard coded as it is unlikely to change

# gzcat - 'cat for .gz / gzip files' 
# If you don't have gzcat and do have gzip then use: ln gzip gzcat
$GZCAT = "/usr/bin/gzcat";

# zcat - 'cat for .Z / compressed files'
$ZCAT = "/usr/bin/zcat";

# Do we want to print dates in Euro or US format?
# Euro is DD/MM/YY      US is MM/DD/YY   (You can use YYYY if you want)
# Pick one of the two formats below for your use, you can use just YY if you
# wish (year2000 alert ;)
$DATE_FORMAT = "DD/MM/YYYY";
# $DATE_FORMAT = "MM/DD/YYYY";

# Timestamp definitions for wierd Radius versions.
# For versions of radius which log Timestamp = data this is not used
# Others need this defined so RadiusReport knows how to extract your record
# times.
# Keys are: DAY MON MDAY HH MM SS YEAR.  MON can accept 'Jun', 6 or 06
# If your logs are different then simply roll your own.

# Livingston Radius V1.16+, RadiusNT, Merit Radius, and some Ascend:
# e.g. Record stamp of type: Tue Jul  1 00:28:34 1997
$RECORD_DATE_FMT = "DAY MON MDAY HH:MM:SS YEAR";

# Ascend Radius:
# e.g. Record stamp of type: 23-07-1997 00:02:55
#$RECORD_DATE_FMT = "MDAY-MON-YEAR HH:MM:SS";

# Do you want to report multiple logins to the system - tracking down shared
# accounts.
$REPORT_MULTIPLE_LOGINS = 1;

# users file format
# This is used by parts of radiusreport to extract usernames 'real names'
# If your users file supports it.
# My users file has a line before each record in the form:
# #* username:Real Name
# This enables me to quickly extract a complete list of users and real names.
# I'd be interested in knowing what other people use.
# set to 1 if using radius users file in /etc/raddb/users using #* fmt above.
# set to 0 if using unix passwd or shadow files or a /etc/raddb/users without #*
$HAVE_NICE_USERS=1;


#### You should not have to modify anything below here


@weekdays = ( "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" );
%weekhash = ( "Sun", 0, "Mon", 1, "Tue", 2, "Wed", 3, "Thu", 4,
                "Fri", 5, "Sat", 6 );
@months = (     "Jan", "Feb", "Mar", "Apr", "May", "Jun",
                "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" );
%monthshash = ( "Jan", 0, "Feb", 1, "Mar", 2, "Apr", 3, "May", 4, "Jun", 5,
                "Jul", 6, "Aug", 7, "Sep", 8, "Oct", 9, "Nov", 10, "Dec", 11);

$allsessions = "";

#User Display Format: You can customise the output display here
$display = "_DATE_ _LOGIN_ _LOGOUT_ _ONTIME_ _PORT_";
if ( $DATE_FORMAT =~ /YYYY/ ) {
  $dispwidths{'_DATE_'} = "%-10.10s";
} else {
  $dispwidths{'_DATE_'} = "%-8.8s";
}
$dispwidths{'_LOGIN_'} = "%-8.8s";
$dispwidths{'_LOGOUT_'} = "%-8.8s";
$dispwidths{'_ONTIME_'} = "%-8s";
$dispwidths{'_PORT_'} = "%-4.4s";
$dispwidths{'_BAUD_'} = "%-5.5s";
$dispwidths{'_BANDWDT_'} = "%-14s";
$dispwidths{'_TOTHRS_'} = "%-7.7s";

$usage = "Type: radiusreport --help";

$DEBUG = 0;


if (! $ARGV[0] ) {
  die "Usage: $usage\n";
}

#Extract all command line arguments
$args=1;
while ($args) {
  $flag = shift(@ARGV);
  if ($flag eq "--help") { &help; exit; }
  if (substr($flag, 0, 1) eq '-') {
    if ($flag =~ /[tbharsH]/) {
      $arg{'t'} = TRUE if ($flag =~ /t/);
      $arg{'b'} = TRUE if ($flag =~ /b/);
      $arg{'q'} = TRUE if ($flag =~ /q/);
      $arg{'h'} = TRUE if ($flag =~ /h/);
      $arg{'H'} = TRUE if ($flag =~ /H/);
      $arg{'a'} = TRUE if ($flag =~ /a/);
      $arg{'r'} = TRUE if ($flag =~ /r/);
      $arg{'c'} = TRUE if ($flag =~ /c/);
      $arg{'s'} = TRUE if ($flag =~ /s/);
    } else {
      $arg{substr($flag, 1, 1)} = shift(@ARGV);
    }
  } else {
    die "Usage: $usage\n Error in $flag - not a valid flag.\n";
  }
  $args = 0 if (!$ARGV[0]);
}

if ( (!defined($arg{'f'})) ) {
  die "Stop - missing argument: f\n$usage\n";
}
if ( ! ( defined($arg{'l'}) || defined($arg{'i'}) || defined($arg{'r'}) ) ) {
  die "Stop - missing argument: l or i\n$usage\n";
}


# Generate the sprintf string from above $display
$display_format = $display;
for (keys(%dispwidths)) {
  $display_format =~ s/$_/$dispwidths{$_}/g;
}
  
#Flag checking.
if ($DEBUG) {
  print "DEBUG: Flags:\n";
  foreach $flag (keys(%arg)) {
    print " $flag - $arg{$flag}\n";
  }
}

if (defined($arg{'r'})) {
  # read in the users file so we can extract all users and initialise the
  # last seen variable,
  &get_user_list($USERS);
}  

@detailfiles = split(/:/, $arg{'f'});
foreach $file (@detailfiles) {
  print "DEBUG: Reading detail file: $file\n" if ($DEBUG gt 2);
  &read_detailfile($file);
}

# Reports section

$send_output = "STDOUT";
if ( defined($arg{'l'}) ) {
  if ($arg{'l'} eq "all") {
    $send_output = (  (defined($arg{'o'})) ? "$arg{'o'}" : "STDOUT"  );
    print "Sending reports to: $send_output\n" if ($DEBUG ge 1);
    @users_to_report = keys(%userlist);
  } else {
    @users_to_report = ( $arg{'l'} );
  }
  for $ureports (@users_to_report) {
    user_report($ureports);
  }
}

if ( defined($arg{'i'}) ) {
  &ip_address_report($arg{'i'});
}
  
if ( defined($arg{'r'}) ) {
  &last_on_report();
}


exit 0;


sub user_report {
  my $luser = shift;
  return if ( $luser eq "" );
  my $sess = $userlist{$luser} || defined($arg{'r'});
  chop($sess);
  my @sessionsl = split(/:/, $sess);
  my @sessions = sort { $starttimestamp{$a} <=> $starttimestamp{$b} } @sessionsl;
  my $session = "";
  my $total_time = 0;
  my $total_in = 0;
  my $total_out = 0;
  my $first_time = 0;
  my $last_time = 0;
  my $previous_session_time = 0;
  my $telco_cost = 0;

  $output=( ($send_output ne "STDOUT") ? "$send_output/$luser" : $send_output );
  print "Generating report: $luser -> $output\n" if ($DEBUG ge 1);
  if ($output eq "STDOUT") {
    open (OUT, ">-") || die "Can't open stdout: $!\n";
  } else {
    open(OUT, ">$output") || die "Can't open file $output: $!\n";
  }
  $|=1;

  if ( (!defined($arg{'h'})) && (!defined($arg{'H'})) ) {
    print OUT ("Radius Log Report for: $luser\n");
    if ( $DATE_FORMAT =~ /YYYY/ ) {
      print OUT ("Date       Login    Logout    Ontime  Port");
    } else {
      print OUT ("Date     Login    Logout    Ontime  Port");
    }
    print OUT (" Baud  ") if ($arg{'s'});
    print OUT (" BandWt-In/Out ") if ($arg{'b'});
    print OUT ("  Total ") if ($arg{'t'});
    print OUT ("   Cost ") if ($arg{'c'});
    print OUT (" Closed ") if ($arg{'q'});
    print OUT ("\n");
    print OUT ("-----------------------------------------------------------------------------\n");
  }
  if (defined($arg{'H'})) {
    print OUT ("-----------------------------------------------------------------------------\nRadius Log Report for: $luser\n");
  }
  for $session (@sessions) {
    if ($arg{'a'}) {
      if (($first_time eq 0) || ($starttimestamp{$session} < $first_time)) {
        # Locate the 'first' timestamp for this user.
        $first_time = $starttimestamp{$session};
      }
      if (($last_time eq 0) || ($starttimestamp{$session} > $last_time)) {
        # Locate the 'last' timestamp for this user.
        $last_time = $starttimestamp{$session};
      }
    }
    $start_date = getdate($starttimestamp{$session});
    $start_time = gettime($starttimestamp{$session});
    if (! defined($stoptimestamp{$session}) ) {
     $end_time = " - now -";
     next; # Skip this record
     $stoptimestamp{$session} = time;
    } else {
      $end_time = gettime($stoptimestamp{$session});
    }
    $duplicate_login = (($stoptimestamp{$session} < $previous_session_time) ? 1 : 0);
    $previous_session_time = $stoptimestamp{$session};
    $port_pair = sprintf("%1.1s%-3s", $nasporttype{$session}, $nasport{$session});
    $baud = $connectinfo{$session};
    $in_out = &calculate_in_out($acctinputoctets{$session}, $acctoutputoctets{$session});
    $total_time += ( $acctsessiontime{$session} / 60 );
    $total_in += $acctinputoctets{$session};
    $total_out += $acctoutputoctets{$session};

    #Standard line. Date StartT EndTime Ontime Port
    $line = sprintf($display_format, $start_date, $start_time, $end_time,
		&convert_secs_to_mins($acctsessiontime{$session}),
		$port_pair);
    $line .= " " . sprintf($dispwidths{'_BAUD_'}, $baud) if ($arg{'s'});
    $line .= " " . sprintf($dispwidths{'_BANDWDT_'}, $in_out) if ($arg{'b'});
    $line .= " " . sprintf($dispwidths{'_TOTHRS_'}, &convert_secs_to_hours($total_time)) if ($arg{'t'});
    if ($arg{'c'}) {
      my $session_cost = &calculate_cost($starttimestamp{$session},
	  $stoptimestamp{$session}, $nasporttype{$session});
      $telco_cost += $session_cost;
      $line .= " " . sprintf("%5.5s", $session_cost);
    }
    $line .= " " . getterminatecause($acctterminatecause{$session}) if ($arg{'q'});
    print OUT "Multiple login here:\n" if (($duplicate_login eq 1) && ($REPORT_MULTIPLE_LOGINS eq 1));
    print OUT "$line\n" if (!defined($arg{'H'}));
  }
  if (! defined($arg{'h'}) ) {
    print OUT ("------------------------------------------------------------------------\n") if (!defined($arg{'H'}));
    if ( $arg{'t'} ) {
      printf OUT ("  Total Hours: %s\n",
	&convert_secs_to_hours($total_time) );
    }
    if ( $arg{'a'} ) {
      my $days = $last_time - $first_time;
      print OUT "  Average Online times: ";
      if ($days < 172800) {
        printf OUT ("Unavailable - not enough data.\n");
      } else {
        $days = $days / (3600 * 24); # Number of days
        $days = 1 if ($days eq 0);
        my $diff = $total_time / $days; # Online seconds / day
        printf OUT ("%s per day,  %s per week\n",
	  &convert_secs_to_hours($diff), &convert_secs_to_hours($diff*7));
      }
    }
    if ( $arg{'b'} ) {
      printf OUT ("  Total Data transferred In/Out: %s\n",
	&calculate_in_out($total_in, $total_out) );
    }
    if ( $arg{'c'} ) {
      printf OUT ("  Total Telephone charges for period: %s %s\n",
	$currency, (int($telco_cost*100))/100);
    }
  }
  #close(OUT);
    
}



sub ip_address_report {
  my $ipaddress = shift;
  my $sess = "";
  if ($ipaddress == "0") {
    $sess = $allsessions;
    print "IP address usage report.\n";
  } else {
    $sess = $ipaddresslist{"$ipaddress"};
    print "IP address usage report for $ipaddress\n";
  }
  chop($sess);
#print "Sessions: $sess\n";
  my @sessionsl = split(/:/, $sess);
  my @sessions = sort { $starttimestamp{$a} <=> $starttimestamp{$b} } @sessionsl
;
  my $session = "";
  print "Date       Login    Logout   User     Ontime  Port  IP address\n";
  print "--------------------------------------------------------------\n";
  for $session (@sessions) {
    next if ( $framedipaddress{$session} == "" );
    $start_date = getdate($starttimestamp{$session});
    if ( $starttimestamp{$session} ) {
      $start_time = gettime($starttimestamp{$session});
    } else {
      $start_time = "?unknown";
    }
    if ( $stoptimestamp{$session} ) {
      $end_time = gettime($stoptimestamp{$session});
    } else {
      $end_time = "still on";
    }
    $port_pair = sprintf("%1.1s%-3s", $nasporttype{$session}, $nasport{$session});
    $line = sprintf("%-10.10s %-8.8s %-8.8s %-8.8s %-8.8s %-5.5s %-15.15s",
		$start_date, $start_time, $end_time, $username{$session},
                &convert_secs_to_mins($acctsessiontime{$session}),
                $port_pair, $framedipaddress{$session} );
    print "$line\n";
  }
  print "------------------------------------------------\n";
}


sub last_on_report {

  print "Complete summary of All users last logged in times\n";
  print "Username Real Name                                Last time on.\n";
  print "===============================================================\n";
  for $id (sort(keys(%allusers))) {
    if ($lastlogtime{$id} gt 0 ) {
      $laston = getdate($lastlogtime{$id});
    } else {
      $laston = "-";
    }
    printf "%-8s %-40.40s %-10s\n", $id, $allusers{$id}, $laston;
  }

}




sub getterminatecause {
  my $reason = shift;
#print $reason;
  return "" if ($reason eq "User-Request");
  return $reason;
}

sub getdate {
  my $ltime = shift;
  ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
                        localtime($ltime);
  my $datestr = $DATE_FORMAT;
  $mday = sprintf("%-2.2d", $mday);
  $mon = sprintf("%-2.2d", ++$mon);
  $year2 = sprintf("%-4.4d", $year + 1900);
  $year = sprintf("%-2.2d", $year);
  $datestr =~ s/DD/$mday/e;
  $datestr =~ s/MM/$mon/e;
  $datestr =~ s/YYYY/$year2/e;
  $datestr =~ s/YY/$year/e;

  #return sprintf("%-2.2d/%-2.2d/%-2.2d", $mday, ++$mon, $year);
  return $datestr;
}

sub gettime {
  my $ltime = shift;
  ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
                        localtime($ltime);
  return sprintf("%-2.2d:%-2.2d:%-2.2d", $hour, $min, $sec);
}

sub convert_secs_to_mins {
  my $ltime = shift;
  return sprintf("%3dm%2.2ds", int($ltime / 60), ($ltime - ( int($ltime / 60) * 60) ));
}

sub convert_secs_to_hours {
  my $ltime = shift;
  return sprintf("%3dh%2.2dm", int($ltime / 60), ( $ltime - (int($ltime / 60)*60)));
}

sub convert_octets_to_usage {
  my $num = shift;
  my $ret = "";
  $num = $num / 1024;  # num is Kb now

  if ($num > 1048576) {
    # Shit > 1Gb in a session
    return sprintf ("%s.%sG", int($num/1048576), int(($num-(int($num/1048576)*1048576))/104857.6));
  } else {
    if ($num > 1024) {
      # More than 1 Mb
      #return sprintf ("%3.1sM", $num/1024);
      return sprintf ("%s.%sM", int($num/1024), int(($num-(int($num/1024)*1024))/102.4));
    } else {
      return sprintf ("%s.%sK", int($num), int( ($num - int($num)) * 10));
    }
  }
}
    
sub calculate_in_out {
  my $in = shift;
  my $out = shift;
  return sprintf("%s/%s", &convert_octets_to_usage($in), &convert_octets_to_usage($out));
}

  
sub init {
  # initialise all data structures
  @userlist = ();
}
  
sub read_record {
  my $keepreading = 1;
  @record = ();
  print "new record\n" if ($DEBUG ge 3);
  while ($keepreading) {
    $_ = <IN>;
    print "$_" if ($DEBUG ge 3);
    if ( /^$/ ) {
      $keepreading = 0;
    } else {
      #push (@lines, $_);
      $record[++$#record] = $_;
    }
  }
  ##return @lines;
}

sub process_record {
  #my @lines = shift;
  $AcctSessionId = ""; $UserName = ""; $NasPort=""; $NasPortType="";
  $NasIPAddress = "";
  $AcctStatusType=""; $AcctSessionTime=""; $AcctInputOctets="";
  $AcctOutputOctets=""; $AcctTerminateCause=""; $ServiceType="";
  $FramedProtocol=""; $FramedIPAddress=""; $Timestamp=""; $AcctDelayTime="";
  $ConnectInfo="";
  foreach (@record) {  # Collect data
    s/^\s+//;	#Strip leading spaces.
    print " -> $_" if ($DEBUG ge 3);
    chomp;
    $AcctSessionId = $_ if s/Acct-Session-Id = //;
    $UserName = $_ if s/User-Name = //;
    $NasPort = $_ if s/NAS-Port = //;
	    $NasPortType = $_ if s/NAS-Port-Type = //;
            $NasIPAddress = $_ if s/NAS-IP-Address = //;
	    $AcctStatusType = $_ if s/Acct-Status-Type = //;
	    $AcctSessionTime = $_ if s/Acct-Session-Time = //;
	    $AcctInputOctets = $_ if s/Acct-Input-Octets = //;
	    $AcctOutputOctets = $_ if s/Acct-Output-Octets = //;
	    $AcctTerminateCause = $_ if s/Acct-Terminate-Cause = //;
	    $AcctDelayTime = $_ if s/Acct-Delay-Time = //;
	    $ServiceType = $_ if s/Service-Type = //;
	    $FramedProtocol = $_ if s/Framed-Protocol = //;
	    $FramedIPAddress = $_ if s/Framed-IP-Address = //;
	    $FramedIPAddress = $_ if s/Framed-Address = //;
	    $Timestamp = $_ if s/Timestamp = //;
	    $ConnectInfo = $_ if s/Connect-Info = //;
	  }

	  # As of 0.3b6 we dont need Start records - It's all in Stop anyway
	  return if ($AcctStatusType eq "Start");

	  # Check for a valid Timestamp - if none, generate one from record stamp
	  if ($Timestamp == "") {
	    my $recdate = $record[0];
	    chomp $recdate;
	    $recdate =~ s/ +/ /g;
	    $recdate =~ s/:/ /g;
	    my @recdates = split(/ /, $recdate);
	    my $stdfmt = $RECORD_DATE_FMT;
	    $stdfmt =~ s/:/ /g;
	    my @stds = split(/ /, $stdfmt);
	    while ($foo = shift (@stds)) {
	      $val = shift(@recdates);
	      $$foo = $val;
	    }
	#print "$DAY, $MON, $MDAY, $HH, $MM, $SS, $YEAR\n";
	    $Timestamp = calendar_time( $DAY, $MON, $MDAY, $HH, $MM, $SS, $YEAR );
	#print "TIMESTAMP = $Timestamp\n";
	  }
	  # Remove "" marks from $AcctSessionId, $UserName and $ConnectInfo
	  $UserName =~ s/\"//g;
	  $AcctSessionId =~ s/\"//g;
	  $ConnectInfo =~ s/\"//g;

	  # And correct the Timestamp backwards if there was an accounting delay
	  $Timestamp -= $AcctDelayTime;

	  # Skip this is we arn't interested in this guy
	  return if (defined($arg{'l'}) && ($arg{'l'} ne "all") && ($UserName ne $arg{'l'}));

	  # Skip this if we aren't interested in the month
  if ( defined ($arg{'d'}) ) {
    my $interested = 0;
    #if ($AcctStatusType eq "Start") {
      my $tmpm = $arg{'d'};
      my @mons = split(/:/, $tmpm);
      my $mon = "";
      my @months = ("", "Jan", "Feb", "Mar", "Apr", "May", "Jun",
			"Jul", "Aug", "Sep", "Oct", "Nov", "Dec" );
      for $tmpm (@mons) {
        if ( ($tmpm > 0) && ($tmpm < 13) ) { #Tis a number, convert to txt.
          $mon = $months[$tmpm];
        } else {
          $mon = $tmpm;
        }
        my ($ssec,$smin,$shour,$smday,$smon,$syear,$swday,$syday,$sisdst) =
                        localtime($Timestamp);
        
        $interested = 1 if ($mon eq $months[++$smon]);
      }
      return if (! $interested);
    #}
  }

  # Store in data structures.
  if ( ! defined( $userlist{$UserName} ) ) {
    $userlist{$UserName} = "";
  }
  if ( ! defined( $ipaddresslist{"$FramedIPAddress"} ) ) {
    $ipaddresslist{"$FramedIPAddress"} = "";
  }

  # Generate a Unique ID from AcctSessionId, UserName, NasPort and NasIPAddress
  $UniqueId = "$UserName/$AcctSessionId/$NasPort/$NasIPAddress";

  print ":$AcctSessionId - $AcctStatusType:\n" if ($DEBUG ge 2);
  if ( $AcctStatusType eq "Stop" ) {
    # Check if we have already got a Stop record for this.
    return if ( $userlist{$UserName} =~ /$UniqueId:/ );
    # Build up a list of session IDs for this user
    $userlist{$UserName} .= "$UniqueId:";
    $allsessions .= "$UniqueId:";
    # Add this session to the ipaddresslist record
    $ipaddresslist{"$FramedIPAddress"} .= "$UniqueId:";
#print "Got: $AcctSessionId - $UserName $FramedIPAddress\n";
#print "$FramedIPAddress : $ipaddresslist{\"$FramedIPAddress\"}\n";
    $username{$UniqueId} = $UserName;
    $nasport{$UniqueId} = $NasPort;
    $nasporttype{$UniqueId} = $NasPortType;
    $framedipaddress{$UniqueId} = $FramedIPAddress;
    $connectinfo{$UniqueId} = $ConnectInfo;
    #$lastlogtime{$UserName} = $Timestamp if ($Timestamp ge $lastlogtime{$UserName});
    $acctinputoctets{$UniqueId} = $AcctInputOctets;
    $acctoutputoctets{$UniqueId} = $AcctOutputOctets;
    $acctsessiontime{$UniqueId} = $AcctSessionTime;
    $stoptimestamp{$UniqueId} = $Timestamp;
    $starttimestamp{$UniqueId} = $Timestamp - $AcctSessionTime;
    $acctterminatecause{$UniqueId} = $AcctTerminateCause;
    $lastlogtime{$UserName} = $Timestamp if ($Timestamp ge $lastlogtime{$UserName});
  }
}

sub read_detailfile {
  my $filename = shift;
  my @record = ();
  if ($DEBUG ge 3) { print "DEBUG: Reading records";}
  if ( $filename =~ /.gz$/ ) {
    open (IN, "$GZCAT $filename |") || warn "read_detailfile(\"$filename\"): $!\n";
  } else {
    if ( $filename =~ /.Z$/ ) {
      open (IN, "$ZCAT $filename |") || warn "read_detailfile(\"$filename\"): $!\n";
    } else {
      open (IN, "<$filename") || warn "read_detailfile(\"$filename\"): $!\n";
    }
  }
  $valid_input = (eof(IN) ? 0 : 1);
  while($valid_input) {
    $valid_input = 0 if (eof(IN));
    if ($DEBUG ge 3) { print "-Reading Record-\n"; }
    &read_record;
    print "$AcctSessionId" if ($DEBUG ge 3);
    if ($DEBUG ge 3) { print "-Process Record-\n"; }
    &process_record;
  }
}

sub help {
  print <<EOHELP;
radiusreport - Radius Reporting tool.
------------------------------------

radiusreport will:
 . produce reports on individual or all users in the radius log file
 . produce reports on IP number usage
 . calculate data transferred in any one session
 . provide online time analysis for user logins

Usage: $usage

Flags:
	Flags without arguments (may all be specified in one 'flag' e.g. -tba)
	-t Show a runtime total of online time
	-b Show a runtime total of data transferred in/out.
	-a Show 'average' usage data.
	-h Suppress header and footer data ( like /usr/bin/w -h )
	-r Produce a list of users and their last login time.

	-l userid	Produce a 'user' usage report.
	-l all		Produce a 'user' usage report for all users.
	  -o /tmp/dir	Do the above but put the reports into this directory
	-i a.b.c.d	Produce a 'ip' usage report.
	-f file		Analyse radius detail 'file'. Multiple files may be
			specified by separating them with a : i.e. file1:file2
			radiusreport will take compressed files.
	-c Work out call charge by the Telco.
	-d mon[:mon] Only report on this month (or months). mon can be in
	   formay 'Jan' or '01' or '1'. Multiple months are separated by colon
	   characters ':'. (Caveat: Logins which roll over months are included)

EOHELP
}


sub get_user_list {

  my $usersfile = shift;

  open(IN, "<$usersfile") || die "users files: $userfile not found.\n";
  if ($HAVE_NICE_USERS) {
    while (<IN>) {
      if (s/^#\* //) { # Line is a username entry.
        ($user,$name) = split(/:/);
        chop $name;
        $allusers{$user}=$name;   # Remember the 'account's 'Real owner name'
        $lastlogtime{$user}=0;   # Initialise the owner's 'last logged on' stat
      }
    }
  } else {
    while (<IN>) {
      if (/Password =/) { # Line is a username entry.
        ($user, $name) = split(/(\s)/);
        $name = "";
        $allusers{$user}=$user;
	$lastlogtime{$user}=0;
      }
    }
  }
  close(IN);
}

sub calculate_cost {

  # Function to calculate the cost of a connection
  # Arguments are the start and stop timestamp.
  my $stime = shift;
  my $etime = shift;
  my $porttype = shift;

  #Make assumptions on how long call establishment takes
  $stime = $stime - 20 if ($porttype eq "Async");
  $stime = $stime - 3 if ($porttype eq "ISDN");
  $stime = $stime - 3 if ($porttype eq "ISDN-V120");

  my ($ssec,$smin,$shour,$smday,$smon,$syear,$swday,$syday,$sisdst) =
                        localtime($stime);
  my ($esec,$emin,$ehour,$emday,$emon,$eyear,$ewday,$eyday,$eisdst) =
                        localtime($etime);

  # Telco 'minimum' charge per call - Just being initialised here, set it
  # further down.
  my $min_cost = 0.0;

  # Setup the times that costs change and what cost it is
  # The following defines the call times and costs for each day.
  # the format if the define is:  StartHour:Min=CostPerMin
  # multiple time based charge bands are separated by |
  # 0=sunday, 1=monday, ... 6=saturday
  # 
  if ($porttype == "Async") {
    $cost{'0'} = "0=0.010";
    $cost{'1'} = "0=0.017|8:00=0.040|18:00=0.017";
    $cost{'2'} = "0=0.017|8:00=0.040|18:00=0.017";
    $cost{'3'} = "0=0.017|8:00=0.040|18:00=0.017";
    $cost{'4'} = "0=0.017|8:00=0.040|18:00=0.017";
    $cost{'5'} = "0=0.017|8:00=0.040|18:00=0.017";
    $cost{'6'} = "0=0.010";
    # Telco 'minimum' charge per call - In the UK (BT) this is 5p (or 0.05 UKP)
    $min_cost = 0.050;
    $callcost = 0;
    $currency = "£";
    #$currency = "\$";
  } else { # ISDN
    $cost{'0'} = "0=0.010";
    $cost{'1'} = "0=0.017|8:00=0.040|18:00=0.017";
    $cost{'2'} = "0=0.017|8:00=0.040|18:00=0.017";
    $cost{'3'} = "0=0.017|8:00=0.040|18:00=0.017";
    $cost{'4'} = "0=0.017|8:00=0.040|18:00=0.017";
    $cost{'5'} = "0=0.017|8:00=0.040|18:00=0.017";
    $cost{'6'} = "0=0.010";
    # Telco 'minimum' charge per call - In the UK (BT) this is 5p (or 0.05 UKP)
    $min_cost = 0.050;
    $callcost = 0;
    $currency = "\043";
    #$currency = "\$";
  }


#printf "$swday:$ewday:$etime:$stime:%s\n", $etime - $stime;
 

  $callcost = calc_sub_cost( $stime, $etime );

  $callcost = $min_cost if ($callcost lt $min_cost);
  return $callcost;
}

sub calc_sub_cost {
  my $start_sec = shift;
  my $stop_sec = shift;

#print "calc_sun_cost($start_sec, $stop_sec) called...\n";
#printf "[%s -> %s]\n", ctime($start_sec), ctime($stop_sec);

  my ($ssec,$smin,$shour,$smday,$smon,$syear,$swday,$syday,$sisdst) =
                        localtime($start_sec);
  my ($esec,$emin,$ehour,$emday,$emon,$eyear,$ewday,$eyday,$eisdst) =
                        localtime($stop_sec);

  my $day_start_secs = ( $ssec + ($smin * 60) + ($shour * 3600) );
  my $day_stop_secs = ( $esec + ($emin * 60) + ($ehour * 3600) );
  my $day_end_secs = 86400;

  if ( ($swday eq $ewday) && ( ($stop_sec - $start_sec) < 86400 ) ) {
    $day_end_secs = $day_stop_secs;
  }

  #Find cost of start of call
  my @tmpcosts = split(/\|/, $cost{$swday});
  my @costs = ();
  for (@tmpcosts) {
    my ($hr, $unit) = split(/\=/);
    if ($hr =~ /:/) {
      my ($hrs, $mins) = split(/:/, $hr);
      $secs = ($hrs * 3600) + ($mins * 60);
    } else {
      $secs = $hr;
    }
    $costs{$secs} = $unit;
#print "Rate: $secs : $unit\n";
  }
#print "day_start_secs = $day_start_secs\n";
  my @tocost = ( 86400 );
  for (sort {$b <=> $a} keys(%costs)) {
    my $start_cost = $costs{$_};
    $tocost[++$#tocost] = $_;
#print "added tocost: $_\n";
    last if ( $_ le $day_start_secs );
  }
  my $call_cost = 0;
  for (sort {$a <=> $b} @tocost) {
#print "-------------------\ndoing tocost: $_\n";
    if ($_ < $day_start_secs) {
      $start_cost = $costs{$_};
      next;
    }
    if ($_ < $day_end_secs) {
#print "-1\n";
      $call_cost += (( $_ - $day_start_secs - 1) / 60.0 ) * $start_cost;
#printf "This call duration: %d (%s), cost: %s\n", ( $_ - $day_start_secs - 1), $start_cost, (( $_ - $day_start_secs - 1) / 60.0 ) * $start_cost;
      $start_sec += ( $_ - $day_start_secs);
      $day_start_secs = $_;
      $start_cost = $costs{$_};
    } else {
#print "-2\n";
      next if ($day_end_secs eq $day_start_secs);
      $call_cost += (( $day_end_secs - $day_start_secs - 1) / 60.0 ) * $start_cost;
#printf "This call duration: %d (%s), cost: %s\n", ( $day_end_secs - $day_start_secs - 1), $start_cost, (( $day_end_secs - $day_start_secs - 1) / 60.0 ) * $start_cost;
      $start_sec += ( $day_end_secs - $day_start_secs );
      $day_start_secs = $day_end_secs;
      $start_cost = $costs{$_};
    }
  }
#print "$call_cost\n";
  if ($start_sec < $stop_sec) {
    #print "looping... $start_sec -> $stop_sec\n";
    $call_cost += calc_sub_cost($start_sec, $stop_sec);
  }
  return $call_cost;
}


sub calendar_time {
  my $day = shift;
  my $month = shift;
  my $mday = shift; 
  my $hour = shift;
  my $min = shift;
  my $sec = shift;
  my $year = shift;
  $year -= 1900 if ($year >= 1900);
  #printf "$sec:$min:$hour $mday, $month=$monthshash{$month}, $year, $day=$weekhash{$day}\n";
  my $cal = mktime($sec, $min, $hour,  $mday, $monthshash{$month}, $year,
                $weekhash{$day}, 0, 0);
  return $cal;
}

