#!/usr/bin/perl
# 
# From: Kelly Yancey <kbyanc@posi.net>
# Date: Mon, 23 Mar 1999 17:05:10 -0500 (EST)
#
# Somewhere along the line radiusreport and been renamed into radreport,
# as that is how I found it listed on Livingston's (Lucent) web site.
# I decided to keep the name since it is shorter :)
#
# See CHANGELOG (below) for changes
#
# --------------------------------------------------------------------------
#
# From: Vikas Aggarwal <vikas@navya.com>
# Date: Thu, 19 Feb 1998 20:43:25 -0500 (EST)
# 
# I have modified the 'radiusreport' code that is available from
# www.tibus.net so that it can:
# 
# 	- handle records from multiple NAS's (ala AimQuest)
# 	- print port usage
# 	- HTML output
# 	- prevent duplicate records in accounting (e.g. Cisco was sending
# 	  a Login START and another PPP START and the accounting was
# 	  getting really messed up).
# 	- etc.etc. (see comments)
# 
# I have sent this to the original author (Paul Gregg), but have not heard
# back from him, so I am sending directly to Carl also. I think that the
# changes are pretty useful.
# 
#!/usr/local/bin/perl5
#
# $Header: /usr/local/src/radius/RCS/radiusreport,v 1.3 1998/02/20 01:36:19 kbyanc Exp $
#
# This is a modified version of the radiusreport 0.3b5 code available
# from   http://www.tibus.net/pgregg/projects/radiusreport/
#
## This version modified by Vikas Aggarwal, vikas@navya.com, feb 1998
## (see changelog below)
#
# For detailed report (-H for HTML)
#	radiusreport -P -tqa -f /var/adm/radacct/xyz/detail
# For summary report
#	radiusreport -S -P -f /var/adm/radacct/xyz/detail
##
## CHANGELOG
#    Feb98/vikas@navya.com
#	- added P and H options for Port usage and HTML output
#	- added S option for Summary only
#	- added D option for using DBM type storage (requires DBlib)
#	- added N option for report on specific NAS ip address only
#	- now handles records from multiple terminal servers (e.g. AimQuest)
#	- all users option enabled by default
#	- added code to prevent duplicate accounting records e.g. the Cisco
#	  sends a Start on login and another Start on enabling PPP with
#	  different session ID's.
#	- now uses localtime() instead of ctime().
#	- deleted Multiple Login code (did not tell more than 2 anyway)
#
#    Mar99/kbyanc@posi.net
#	- fixed year in port report to correctly list dates in 4 digit
#	  since localtime returns years since 1900 (former would have
#	  displayed "100" in the year 2000).
#	- added maximum hourly usage to the port summary
#	- added ability to compensate for lost Stop records
#	  (does not seem to interfere with vikas's duplicate accounting
#	  check although my Cisco AS5200s don't exhibit the behavior he
#	  describes).
#	- stopped erroniously counting telnet logins in port usage stats
#	- added support for NAS-IP-Address record
#	- added T option to produce total port usage report
#	- added C option to only use data from records matching
#	  a certain Calling-Station-Id
#	- added p option to include Calling-Station-Id information in
#	  user reports
#	- fixed some ommisions in the usage display
#	- fixed width of header in user reports (now resizes based on content)
#
#    Feb00/kbyanc@posi.net
#	- fixed some cosmetic year 2000 bugs
#
# --------------------------------------------------------------------------
# radiusreport - Extract information from Radius 2.0 detail log
#
# Author:	Paul Gregg <pgregg@tibus.net>
# Date:		1 May 1997
# Summary:	Radius,  User's activity,  Port usage,
# Version: 	0.3 - beta 5 (messy code)
# Copyright:	1997, 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.
# Inspiration:	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 ;-)
#
# Supported:	Livingston Radius V2.0+, V1.16
#		Merit Radius
#		Ascend Radius
#		Dale Reed's RadiusNT
#
# Comments on this extremly welcomed
#
# Usage:
#   radiusreport --help
#   radiusreport [-tbahrqc] [-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
#     -p Report on the caller's phone number
#     -h Suppress report header information
#     -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)
#     -P produce port usage report per NAS -vikas
#     -T produce total port usage report summarizing all NASs -kbyanc
#     -H HTML table output -vikas
#     -S Summary only -vikas
#     -D dbm storage
#     -N <ipaddr> to process data for a specific NAS ip address only.
#     -C <regex> to process records with Calling-Station-Id matching
#	 the given expression only.
#
#
# 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

#require "ctime.pl";
require "timelocal.pl";		# -vikas
use POSIX;

# 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/local/bin/gunzip -c";

# 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)
$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

# Livingston Radius V1.16+, RadiusNT, Metit Radius:
# 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.
$HAVE_NICE_USERS=0;

#### 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 = "";

$syncsize = 500;	# number of records before we dump to dbm-disk

#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_'} = "%-7s";
$dispwidths{'_PORT_'} = "%-6.6s";
$dispwidths{'_BANDWDT_'} = "%-13s";
$dispwidths{'_TOTHRS_'} = "%-7.7s";

$usage = "Type: $0 --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 =~ /[tbharPHSD]/) {
      $arg{'t'} = TRUE if ($flag =~ /t/);	# show time totals
      $arg{'b'} = TRUE if ($flag =~ /b/);	# show bytes totals
      $arg{'q'} = TRUE if ($flag =~ /q/);	# show termination cause
      $arg{'p'} = TRUE if ($flag =~ /p/);	# show caller's phone number
      $arg{'h'} = TRUE if ($flag =~ /h/);	# supress header/footer
      $arg{'a'} = TRUE if ($flag =~ /a/);	# show average usage data
      $arg{'r'} = TRUE if ($flag =~ /r/);	# user list and last logins
      $arg{'P'} = TRUE if ($flag =~ /P/);	# Port usage
      $arg{'T'} = TRUE if ($flag =~ /T/);	# Port usage total
      $arg{'H'} = TRUE if ($flag =~ /H/);	# HTML output
      $arg{'S'} = TRUE if ($flag =~ /S/);	# Summary only per user
      $arg{'D'} = TRUE if ($flag =~ /D/);	# use DBM for temp storage
    } 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 <datafile>\n$usage\n";
}
if ( ! ( defined($arg{'l'}) || defined($arg{'i'}) || defined($arg{'r'}) ) ) {
    $arg{'l'} = 'all';  # all users by default
    # die "Stop - missing argument: l or i\n$usage\n";
}

$arg{'h'} = TRUE if ( defined($arg{'S'}) );	# -vikas

# HTML strings -vikas
if (defined($arg{'H'})) {
    $H_HDR1 = '<HR><CENTER><H1>'; $h_HDR1 = '</H1></CENTER>';
    $H_USRHDR = '<HR><H3><FONT COLOR=\"#000077">';
    $h_USRHDR = '</FONT></H3>';
    $H_TBL = '<TABLE BORDER=1>'; $h_TBL = '</TABLE>';
    $H_TR = '<TR>' ; $h_TR = '</TR>';
    $H_TH = '<TH>' ; $h_TH = '</TH>';
    $H_TD = '<TD>' ; $h_TD = '</TD>';
    $H_I = '<I>' ;  $h_I = '</I>';
    $H_B = '<B>' ;  $h_B = '</B>';
    $H_P = '<P>'  ; $h_P = '</P>' ;

    print "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">\n";
    print "<HTML>\n <HEAD>\n  <TITLE>Radius Log</TITLE>\n </HEAD>\n\n";
    print "<BODY bgcolor=\"ffffff\">\n";
    printf ("<P>Report generated at %s</P>\n", scalar localtime);
    print "<H2>Table of Contents</H2>\n<UL>\n";
    if (!defined($arg{'S'})) {
	print "<A href=\"#userdetails\">User Details</A> <P>\n";
    }
    print "<A href=\"#portusage\">Port Usage</A> <P>\n";
    print "<A href=\"#summary\">User Summary</A> <P>\n";
    print "</UL><hr>\n";
}

# Generate the sprintf string from above $display
$display_format = $display;
for (keys(%dispwidths)) {
  $display_format =~ s/$_/${H_TD}$dispwidths{$_}${h_TD}/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 eq 3);
  &read_detailfile($file);
}

# Reports section

$send_output = "STDOUT";

if ( defined($arg{'l'}) ) {
  if (defined($arg{'H'})) { print "<A name=\"userdetails\"></A>" ;}
  if ($arg{'l'} eq "all") {
    $send_output = (  (defined($arg{'o'})) ? "$arg{'o'}" : "STDOUT"  );
    print "Sending reports to: $send_output\n" if ($DEBUG eq 1);
    @users_to_report = sort 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();
}

## Print port usage	 -vikas
if ( defined($arg{'P'}) ) {
    &port_report();
}

## Print total port usage	-kbyanc
if ( defined($arg{'T'}) ) {
    &total_port_report();
}

## Print summary report -vikas
&summary_report();

## cleanup dbm stuff
if (defined($arg{'D'})) {
    untie %DB;
    unlink ($tdbfile);
}

exit 0;

## The port usage per hour is stored in $portsinuse{$nasid}{$hour}
## -vikas
sub port_report {
# ---------------
    my $nasid;

    for $nasid (sort keys %portsinuse)
    {
	my($cumuports, $portdiff, $maxports) = (0, 0, 0);

	# first, compute sum of all logins and logouts we have logged
	for $np (sort keys %{ $portsinuse{$nasid} }) {
	    $cumuports += $portsinuse{$nasid}{$np};
	}
	# portdiff is the number of people who were logged in when logging started
	# computed from number of people currently logged in minus total logins we know of
	$portdiff = $curlogins{$nasid} - $cumuports;
	if (defined($arg{'H'})) { print "<A name=\"portusage\"></A>" ;}
	my $nasipnum = pack('C4', split(/\./, $nasid));
	my $AF_INET = 2;
	# if (defined (AF_INET)) { $AF_INET = AF_INET };
	my $nasname = gethostbyaddr($nasipnum, $AF_INET);
	$nasname = $nasid if (!defined($nasname));
	print "\n\n${H_HDR1}PORT USAGE for ${nasname} (${nasid})${h_HDR1}\n";
	if (! defined($arg{'H'})) { print "----------\n" ;}
	print "Current Ports in use= $curlogins{$nasid}\n";
	print "${H_TBL}${H_TR}${H_TH}Date      ${h_TH}${H_TH}    Hour ${h_TH}${H_TH} Ports in Use ${h_TH}${H_TH} Max Ports ${h_TH}${h_TR}\n";

	$cumuports = $portdiff;
	for $np (sort keys %{ $portsinuse{$nasid} }) {
	    my @timestr = localtime($np);
	    $maxports = $cumuports+$maxportsinuse{$nasid}{$np};
	    $cumuports += $portsinuse{$nasid}{$np};
	    printf "${H_TR}${H_TD}%s %2d, %-4.4d  ${h_TD}${H_TD} %02d  ${h_TD}${H_TD} %10d   ${h_TD}${H_TD} %7d   ${h_TD}${h_TR}\n",
                $months[$timestr[4]], $timestr[3], $timestr[5]+1900, $timestr[2], $cumuports, $maxports;

#	    push @{ $wkvals[$timestr[6]][$timestr[2]] }, $cumuports;
	}
#	print "----Hour----";
#	for (0..6) { print "  $weekdays[$_]"; }
#	for $j (0..23) {
#	    printf "\n %02d  ", $j;
#	    for $i (0..6) { print "  @{ $wkvals[$i][$j] }  "; }
#	}
	print "$h_TBL\n";
    }
}

## Compute totals across all NAS's
sub total_port_report {
# ---------------
    my $nasid;
    my $nascount = 0;
    my $totallogins = 0;
    my %globusage;
    my %globmaxusage;

    for $nasid (sort keys %portsinuse)
    {
	my($cumuports, $portdiff, $maxports) = (0, 0, 0);

	# update counters
        $nascount++;
        $totallogins += $curlogins{$nasid};

	# compute sum of all logins and logouts we have logged
	for $np (sort keys %{ $portsinuse{$nasid} }) {
	    $cumuports += $portsinuse{$nasid}{$np};
	}
	# portdiff is the number of people who were logged in when logging started
	# computed from number of people currently logged in minus total logins we know of
	$portdiff = $curlogins{$nasid} - $cumuports;

	$cumuports = $portdiff;
	for $np (sort keys %{ $portsinuse{$nasid} }) {
	    $maxports = $cumuports+$maxportsinuse{$nasid}{$np};
	    $cumuports += $portsinuse{$nasid}{$np};

	    # compute totals across NASs
	    $globusage{$np} += $cumuports;
	    $globmaxusage{$np} += $maxports;
	}
    }
    # now we have the information to display...
    print "\n\n${H_HDR1}TOTAL PORT USAGE${h_HDR1}\n";
    print "Current Ports in use= $totallogins\n";
    print "${H_TBL}${H_TR}${H_TH}Date      ${h_TH}${H_TH}    Hour ${h_TH}${H_TH} Ports in Use ${h_TH}${H_TH} Max Ports ${h_TH}${h_TR}\n";

    for $np (sort keys %globusage) {
	my @timestr = localtime($np);
	printf "${H_TR}${H_TD}%s %2d, %-4.4d  ${h_TD}${H_TD} %02d  ${h_TD}${H_TD} %10d   ${h_TD}${H_TD} %7d   ${h_TD}${h_TR}\n",
            $months[$timestr[4]], $timestr[3], $timestr[5]+1900, $timestr[2], $globusage{$np}, $globmaxusage{$np};
    }
    print "$h_TBL\n";
}

sub summary_report {
# ------------------
    if (defined($arg{'H'})) { print "<A name=\"summary\"></A>" ;}
    print "\n${H_HDR1}TOTAL USAGE REPORTS ${h_HDR1}\n";
    if (! defined($arg{'H'})) { print "------------------\n" ;}
    if (defined(@users_to_report)) {
	print "Total number of unique users= $#users_to_report\n";
    }
    print "${H_TBL}${H_TR}${H_TH}     USER    ${h_TH}${H_TH}   Total Hrs ${h_TH}${H_TH} Avg/Day  ${h_TH}${H_TH}  Avg/Sess ${h_TH}${h_TR}\n";
    for (sort { $usertotals{$b} <=> $usertotals{$a} } keys %usertotals)
    {
	my ($t, $avgd, $avgs) = split(/\t/, $usertotals{$_});
        if($t < 0) {
           printf ("${H_TR}${H_TD}%14s ${h_TD}${H_TD} %s ${h_TD}${H_TD}  %s  ${h_TD}${H_TD}  %s${h_TD}${h_TR}\n",
  		  $_ , "unknown", "unknown", "unknown" );
        }
        else {
	  printf ("${H_TR}${H_TD}%14s ${h_TD}${H_TD} %s ${h_TD}${H_TD}  %s  ${h_TD}${H_TD}  %s${h_TD}${h_TR}\n",
  		  $_ , &convert_secs_to_hours($t), &convert_secs_to_hours($avgd),
		  &convert_secs_to_hours($avgs) );
        }
    }
    print "${h_TBL}\n";
}

# This subroutine does the calculation also, so we need to call this
# everytime.
sub user_report {
#----------------
  my $luser = shift;
  return if ( $luser eq "" );
#  my $sess = $userlist{$luser} || defined($arg{'r'});
  my $sess;
  if (defined ($arg{'D'})) {$sess = $DB{"U_$luser"}; }
  else { $sess = $userlist{$luser}; }
  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;
  my $avgperday = 0;	# average seconds per day
  my $avgperses = 0;	# average seconds per session
  my $width = 0;	# width of header bar

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

  if (!defined($arg{'h'}) ) {
    print OUT ("\n${H_USRHDR}Radius Log Report for: $luser ${h_USRHDR}\n");
    print OUT ("${H_TBL}${H_TR}${H_TH}  Date  ${h_TH}${H_TH}   Login ${h_TH}${H_TH}   Logout ${h_TH}${H_TH}  Ontime ${h_TH}${H_TH} Port  "); $width=42;
    if ($arg{'b'}) { print OUT ("${h_TH}${H_TH} BW-In/Out    ");  $width+=14; }
    if ($arg{'t'}) { print OUT ("${h_TH}${H_TH}  Total ");        $width+=8;  }
    if ($arg{'c'}) { print OUT ("${h_TH}${H_TH}   Cost ");        $width+=8;  }
    if ($arg{'p'}) { print OUT ("${h_TH}${H_TH} Calling Phone "); $width+=15; }
    if ($arg{'q'}) { print OUT ("${h_TH}${H_TH} Closed ");        $width+=17;  }
    print OUT ("${h_TH}${h_TR}\n");
    print OUT (("-" x $width) . "\n") if (! defined($arg{'H'}));
  }

  for $session (@sessions) {
    my $nasid = $1 if ($session =~ /^.*_(.*)$/);   # extract NasID
    &get_session_data($session);	# from DBM table
    $curlogins{$nasid} = 0 if (! defined($curlogins{$nasid}) );  # -vikas
    if (($first_time eq 0) || ($starttimestamp < $first_time)) {
        # Locate the 'first' timestamp for this user.
        $first_time = $starttimestamp;
    }
    if (($last_time eq 0) || ($starttimestamp > $last_time)) {
        # Locate the 'last' timestamp for this user.
        $last_time = $starttimestamp;
    }
    $start_date = getdate($starttimestamp);
    $start_time = gettime($starttimestamp);
    if (! defined($stoptimestamp) || $stoptimestamp eq "" || $stoptimestamp == 0 ) {
        # no stop record, is possible that stop record was lost or else they are
        # are still logged in. We should try to figure out which is the case. -kbyanc
	if($recordstartstop{$session} == 2) {
	    $end_time = "unknown";
        }
        else {
            $end_time = " - now -";
            ++$curlogins{$nasid};   # -vikas
            $stoptimestamp = time;
        }
    }
    else {
        $end_time = gettime($stoptimestamp);
    }
    $duplicate_login = (($stoptimestamp < $previous_session_time) ? 1 : 0);
    $previous_session_time = $stoptimestamp;
    $port_pair = sprintf("%1.1s%-s", $nasporttype, $nasport);
    $in_out = &calculate_in_out($acctinputoctets, $acctoutputoctets);
    $total_time += ( $acctsessiontime / 60 );
    $total_in += $acctinputoctets;
    $total_out += $acctoutputoctets;

    #Standard line. Date StartT EndTime Ontime Port
    if (! defined($arg{'S'})) {	# summary only
	$line = sprintf($display_format, $start_date, $start_time, $end_time,
			&convert_secs_to_mins($acctsessiontime),
			$port_pair);
	$line .= " ${H_TD}" . sprintf($dispwidths{'_BANDWDT_'}, $in_out) . "${h_TD}" if ($arg{'b'});
	$line .= " ${H_TD}" . sprintf($dispwidths{'_TOTHRS_'}, &convert_secs_to_hours($total_time)) . "${h_TD}" if ($arg{'t'});
	if ($arg{'c'}) {
	    my $session_cost = &calculate_cost($starttimestamp,
			     $stoptimestamp, $nasporttype);
	    $telco_cost += $session_cost;
	    $line .= " ${H_TD}" . sprintf(" %5.5s ", $session_cost) . "${h_TD}";
	}
	$line .= " ${H_TD}" . sprintf(" %12s ", $caller) . "${h_TD}" if ($arg{'p'});
	$line .= " ${H_TD}" . getterminatecause($acctterminatecause) . "${h_TD}" if ($arg{'q'});
#	print OUT "${H_TR}<td colspan=3>Multiple login here:</td>${h_TR}\n" if (($duplicate_login eq 1) && ($REPORT_MULTIPLE_LOGINS eq 1));
	print OUT "${H_TR}$line${h_TR}\n";
    } # ! defined summary only
  }  # end for @sessions

  print "${h_TBL}" if (! defined ($arg{'S'}));

  $avgperses = $total_time/($#sessions == -1 ? 1 : $#sessions + 1);
  $avgperday = ($last_time - $first_time)/(3600 * 24); # Number of days
  $avgperday = $total_time / ($avgperday < 1 ? 1 : $avgperday);
  $usertotals{$luser} = "$total_time\t$avgperday\t$avgperses"; #store

  if (! defined($arg{'h'})) {
    print OUT (("-" x $width) . "\n") if (! defined($arg{'H'}));
    if ( $arg{'t'} ) {
      printf OUT ("${H_P}${H_B}  Total Hours: %s ${h_B}\n",
	&convert_secs_to_hours($total_time) );
    }
    if ( $arg{'a'} ) {
      printf OUT ("${H_P}  Average Online times:${H_I} %s per day,  %s per session ${h_I}\n",
		  &convert_secs_to_hours($avgperday),
		  &convert_secs_to_hours($avgperses));
    }
    if ( $arg{'b'} ) {
      printf OUT ("${H_P}  Total Data transferred In/Out:${H_I} %s ${h_I}\n",
	&calculate_in_out($total_in, $total_out) );
    }
    if ( $arg{'c'} ) {
      printf OUT ("${H_P}  Total Telephone charges for period:${H_I} %s %s ${h_I}\n",
	$currency, (int($telco_cost*100))/100);
    }
  }
  #close(OUT);
    
}

sub ip_address_report {
# ---------------------
  my $ipaddress = shift;
  my $sess = "";
  if ($ipaddress == "0") {
#    $sess = $allsessions;
    $sess = keys %recordstartstop;
    print "IP address usage report.\n";
  }
  else {
      if (defined ($arg{'D'})) {$sess = $DB{"I_$ipaddress"}; }
      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) {
    &get_session_data($session);
    next if ( $framedipaddress == "" );
    $start_date = getdate($starttimestamp);
    if ( $starttimestamp ) {
      $start_time = gettime($starttimestamp);
    } else {
      $start_time = "?unknown";
    }
    if ( $stoptimestamp ) {
      $end_time = gettime($stoptimestamp);
    } else {
      $end_time = "still on";
    }
    $port_pair = sprintf("%1.1s%-s", $nasporttype, $nasport);
    $line = sprintf("%-10.10s %-8.8s %-8.8s %-8.8s %-8.8s %-5.5s %-15.15s",
		$start_date, $start_time, $end_time, $username,
                &convert_secs_to_mins($acctsessiontime),
                $port_pair, $framedipaddress );
    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;
  # search on AltaVista for Ascend-Disconnect-Cause
  %asnd_list = (11, "Carrier-Loss", 20, "Quit", 21, "Idle", 
		43, "Chap-failure", 45, "User-Request", 46, "User-Request",
		47, "No-NCP",
		100, "Timeout", 185, "Remote-Hangup");
  if ($reason =~ /^\d+/) {	# Ascend disconnect numbers
      my $str = $asnd_list{$reason};
      $reason = $str if ($str ne "");
  }
  return "" if ($reason eq "User-Request" || $reason eq "Remote-Hangup" ||
		$reason eq "Quit");
  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 % 100);
  $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 eq 3);
  while ($keepreading) {
    $_ = <IN>;
    print "$_" if ($DEBUG eq 3);
    if ( /^$/ ) {
      $keepreading = 0;
    } else {
      #push (@lines, $_);
      $record[++$#record] = $_;
    }
  }
  ##return @lines;
}

sub process_record {
#-------------------
  #my @lines = shift;
  $AcctSessionId = ""; $UserName = ""; $NasPort=""; $NasPortType="";
  $AcctStatusType=""; $AcctSessionTime=""; $AcctInputOctets="";
  $AcctOutputOctets=""; $AcctTerminateCause=""; $ServiceType="";
  $FramedProtocol=""; $FramedIPAddress=""; $Timestamp=""; $AcctDelayTime="";
  foreach (@record) {  # Collect data
    s/^\s+//;	#Strip leading spaces.
    print " -> $_" if ($DEBUG eq 3);
    chomp;
    if (s/Acct-Session-Id = //) { 
	$AcctSessionId = $_ ;
	return if ($AcctSessionId eq "00000000");	# invalid sessionID
    }
    $UserName = $_ if s/User-Name = //;
    $NasPort = $_ if s/NAS-Port = //;
    $NasPortType = $_ if s/NAS-Port-Type = //;
    if (s/(NAS-Identifier)|(NAS-IP-Address) = //) {
	$NasIdent = $_ ;
	return if (defined($arg{'N'}) && ! /$arg{'N'}/); # not interested
    }
    # check to see if match Calling-Station-Id	-kbyanc
    if (s/Calling-Station-Id = //) {
	$caller = $_;
	$caller =~ s/\"//g;
	return if (defined($arg{'C'}) && ! /$arg{'C'}/);
    } 
    $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 = //;
    $AcctTerminateCause = $_ if s/Ascend-Disconnect-Cause = //;
    $AcctDelayTime = $_ if s/Acct-Delay-Time = //;
    $ServiceType = $_ if s/Service-Type = //;
    $FramedProtocol = $_ if s/Framed-Protocol = //;
    $FramedIPAddress = $_ if s/Framed-Address = //;
    $FramedIPAddress = $_ if s/Framed-IP-Address = //;
    $Timestamp = $_ if s/Timestamp = //;
  }

  # Check to see if a Calling-Station-Id field was found when required	-kbyanc
  return if ( (defined($arg{'C'}) ) && (! defined($caller) ) );

  # Check for a valid Timestamp - if none, generate one from record stamp
  # Use timelocal()  -vikas
  if ($Timestamp == "") {
    my $recdate = $record[0];
    chomp $recdate;
    if($recdate =~ /^\s*(\w{3})\s(\w{3})\s{1,2}(\d{1,2})\s(\d{2}):(\d{2}):(\d{2})\s(\d{4})$/ &&
       (grep {$1 eq $_} @weekdays) && (grep {$2 eq $_} @months) )
    {
	$Timestamp = timelocal($6, $5, $4, $3, $monthshash{$2}, $7-1900);
    }
  }
  # $DEBUG && print STDERR "Timestamp= $Timestamp\n";
  # Remove "" marks from $AcctSessionId and $UserName
  $UserName =~ s/\"//g;
  $AcctSessionId =~ s/\"//g;
  $AcctSessionId .= "_$NasIdent";	# append NAS Ident, -vikas

  # avoid dup records for the same port number,,, -vikas
  my $nasportid = "$NasIdent" . "_$NasPort" ;

  # And correct the Timestamp backwards if there was an accounting delay
  $Timestamp -= $AcctDelayTime;
  return if ($Timestamp <= 0);

  # 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);	# user specified on cmd line
      my $mon = "";
      for $tmpm (@mons) {
        if ( ($tmpm > 0) && ($tmpm < 13) ) { #Tis a number, convert to txt.
          $mon = $months[$tmpm - 1];
        } 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);
    }
  }

  ## look at Nas + Port, and skip record if we already have a login or
  # logout for this Nas+port (avoid two successive entries) -vikas
  if (defined($ison{$nasportid}))
  {
      if(($ison{$nasportid} ne "0") && ($ison{$nasportid} ne "$AcctSessionId")) {
        # in the event we get a start or stop record which doesn't match
	# the session ID of the session which we thought was on the port,
	# we must assume the appropriate record was lost in transit.
	# In either event, we need to close the previous session.
        #       -kbyanc
        my $oldsession;

        $oldsession = $ison{$nasportid};
        $recordstartstop{$oldsession} = 2;	# indicate the session is closed
        $acctinputoctets{$oldsession} = 0;
        $acctoutputoctets{$oldsession} = 0;
        $stoptimestamp{$oldsession} = $Timestamp;
#        $acctsessiontime{$oldsession} = $stoptimestamep{$oldsession} - $starttimestamp{$oldsession};
        $acctsessiontime{$oldsession} = 0;
        $acctterminatecause{$oldsession} = "<No Record>";
        --$portsinuse{$NasIdent}{$Timestamp - ($Timestamp % 3600)} unless ($NasPort eq "");
      } 
##      following added by vikas, replaced by kbyanc
#      return if ($AcctStatusType eq "Start" && $ison{$nasportid} ne "0");
#      return if ($AcctStatusType eq "Stop"  &&
#		 $ison{$nasportid} ne "$AcctSessionId");
  }

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

  print ":$AcctSessionId - $AcctStatusType:\n" if ($DEBUG eq 2);

  if ( $AcctStatusType eq "Start" )
  {
    # Check if we have already got a Start or Stop record for this.
    return if ($recordstartstop{$AcctSessionId} > 0);
    $ison{$nasportid} = $AcctSessionId;
    my $hour = $Timestamp - ($Timestamp % 3600);
    ++$portsinuse{$NasIdent}{$hour} unless ($NasPort eq "");      # -vikas
    # find the peak usage level during the hour...	-kbyanc
    $maxportsinuse{$NasIdent}{$hour} = $portsinuse{$NasIdent}{$hour}
        unless $portsinuse{$NasIdent}{$hour} <= $maxportsinuse{$NasIdent}{$hour};
    # $allsessions .= "$AcctSessionId:";
    # Build up a list of session IDs for this user
    $userlist{$UserName} .= "$AcctSessionId:";
    # Add this session to the ipaddresslist record
    $ipaddresslist{"$FramedIPAddress"} .= "$AcctSessionId:";
    $username{$AcctSessionId} = $UserName;
    $caller{$AcctSessionId} = $caller;
    $nasport{$AcctSessionId} = $NasPort;
    $nasporttype{$AcctSessionId} = $NasPortType;
    $framedipaddress{$AcctSessionId} = $FramedIPAddress;
    $starttimestamp{$AcctSessionId} = $Timestamp;
    $lastlogtime{$UserName} = $Timestamp if ($Timestamp ge $lastlogtime{$UserName});
    # recordstartstop is set to 1 in Start, and 2 in Stop
    $recordstartstop{$AcctSessionId} = 1;
  }
  else {	# STOP record has everything we need, prefer this data
    print ";" if ($DEBUG eq 3);
    $ison{$nasportid} = "0";   # port is off
    return if ($recordstartstop{$AcctSessionId} == 2); # seen STOP record
    # keep track of number of ports in use  -vikas
    --$portsinuse{$NasIdent}{$Timestamp - ($Timestamp % 3600)} unless ($NasPort eq "");  # -vikas
    if ($recordstartstop{$AcctSessionId} == 0)	# Not seen Start yet
    {
	# Build up a list of session IDs for this user
	$userlist{$UserName} .= "$AcctSessionId:";
	# Add this session to the ipaddresslist record
	$ipaddresslist{"$FramedIPAddress"} .= "$AcctSessionId:";
    }
    $allsessions .= "$AcctSessionId:";
    $username{$AcctSessionId} = $UserName;
    $caller{$AcctSessionId} = $caller;
    $nasport{$AcctSessionId} = $NasPort;
    $nasporttype{$AcctSessionId} = $NasPortType;
    $framedipaddress{$AcctSessionId} = $FramedIPAddress;
    $acctinputoctets{$AcctSessionId} = $AcctInputOctets;
    $acctoutputoctets{$AcctSessionId} = $AcctOutputOctets;
    $acctsessiontime{$AcctSessionId} = $AcctSessionTime;
    $stoptimestamp{$AcctSessionId} = $Timestamp;
    $acctterminatecause{$AcctSessionId} = $AcctTerminateCause;
    $starttimestamp{$AcctSessionId} = $Timestamp - $AcctSessionTime;
    $lastlogtime{$UserName} = $Timestamp if ($Timestamp ge $lastlogtime{$UserName});
    $recordstartstop{$AcctSessionId} = 2;
  }
}

sub read_detailfile {
#--------------------
  my $filename = shift;
  my @record = ();
  my $recordnum = 0;
  if ($DEBUG eq 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 eq 3) { print "-Reading Record-\n"; }
    &read_record;
    print "$AcctSessionId" if ($DEBUG eq 3);
    if ($DEBUG eq 3) { print "-Process Record-\n"; }
    &process_record;
    if ( (++$recordnum % $syncsize) == 0) {&dump_dbm() ;}
  }
  &dump_dbm();
}

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

radreport 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
 . provide NAS port usage reports

Usage: $usage
e.g.
        $0 -tqa -l all -f /usr/adm/radacct/myts/detail

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.
	-q Show how connection was terminated
	-p Show caller's phone number
	-c Work out call charge by the Telco.
	-h Suppress header and footer data ( like /usr/bin/w -h )
	-r Produce a list of users and their last login time.
        -P Produce a port usage report per NAS
        -T Produce a port usage report summarizing all NASs
        -D use temporary DBM storage (for large amounts of data)
        -H HTML output

	-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
        -N a.b.c.d	report for NASid equal to a.b.c.d ip-address only
	-C regex	report for Calling-Station-Id matching regex only
	-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.
	-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 - In the UK (BT) this is 5p (or 0.05 UKP)
  my $min_cost = 0.050;

  # 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 |
  $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";
  $callcost = 0;
  $currency = "=A3";
  #$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;
}

##
## Have to use DB or GDBM since there are data size restrictions in NDBM
##
use DB_File;
#use GDBM_File;

sub dump_dbm {
#-------------------
    my ($key, $val);
    $tdbfile = "/tmp/radiusreport$$" ;

    return if (! defined ($arg{'D'}) );	# no temp storage to be done
    $DEBUG && print STDERR "Doing dump_dbm\n";

    if ($dbmopen != 1) {
	tie (%DB, "DB_File", $tdbfile, O_RDWR|O_CREAT, 0600, $DB_HASH)  ||
	    die "cannot open $tdbfile $!\n";
	$dbmopen = 1;
    }
    # store userlist{Username}, U_$username session : session : session
    for $u (keys %userlist) {
	$key = "U_" . "$u" ;
	if ($DB{$key} eq undef) { $DB{$key} = $userlist{$u}; }
	else { $DB{$key} .= $userlist{$u}; }
	undef $userlist{$u};
    }

    # store ipaddresslist{IPaddr}. I_$ipaddr = session : session : session
    for $u (keys %ipaddresslist) {
	$key = "I_" . "$u";
	if ($DB{$key} eq undef) { $DB{$key} = $ipaddresslist{$u}; }
	else { $DB{$key} .= $ipaddresslist{$u}; }
	undef $ipaddresslist{$u};
    }

    # store S_$session -> data for all STOP records.
    for $u ( split(/:/, $allsessions) ) {
	$key = "S_" . "$u" ;
	$DB{$key} = "$username{$u}\t$nasport{$u}\t$nasporttype{$u}\t$framedipaddress{$u}\t$starttimestamp{$u}\t$acctinputoctets{$u}\t$acctoutputoctets{$u}\t$acctsessiontime{$u}\t$stoptimestamp{$u}\t$acctterminatecause{$u}\t$caller{$u}";
	undef $username{$u}; undef $nasport{$u}; undef $nasporttype{$u};
	undef $framedipaddress{$u}; undef $starttimestamp{$u};
	undef $acctinputoctets{$u}; undef $acctoutputoctets{$u};
	undef $acctsessiontime{$u}; undef $stoptimestamp{$u};
	undef $acctterminatecause{$u}; undef $caller{$u};
    }

    undef $allsessions;

    # tie (%DB, $tdbfile, undef);
    # while (($key,$val) = each %DB) { print STDERR "$key = $val\n" ; }

}

sub get_session_data {
# --------------------
    my ($sess) = @_ ;

    if ( defined ($arg{'D'}) && !defined ($username{$sess}) )
    {
	($username, $nasport, $nasporttype, $framedipaddress, $starttimestamp,
	 $acctinputoctets, $acctoutputoctets, $acctsessiontime,
	 $stoptimestamp, $acctterminatecause, $caller) = split(/\t/, $DB{"S_$sess"});
    }
    else # not using DBM or if an open record (still logged on)
    {
	$username = $username{$sess};
	$nasport = $nasport{$sess};
	$nasporttype = $nasporttype{$sess};
	$framedipaddress = $framedipaddress{$sess};
	$starttimestamp = $starttimestamp{$sess};
	$acctinputoctets = $acctinputoctets{$sess};
	$acctoutputoctets = $acctoutputoctets{$sess};
	$acctsessiontime = $acctsessiontime{$sess};
	$stoptimestamp = $stoptimestamp{$sess};
	$acctterminatecause = $acctterminatecause{$sess};
	$caller = $caller{$sess};
    }
}