#!/usr/bin/perl # # From: Kelly Yancey # 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 # 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 # Date: 1 May 1997 # Summary: Radius, User's activity, Port usage, # Version: 0.3 - beta 5 (messy code) # Copyright: 1997, Paul Gregg # 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 / Joe Hartley # 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 to process data for a specific NAS ip address only. # -C 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 \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 = '

'; $h_HDR1 = '

'; $H_USRHDR = '

'; $h_USRHDR = '

'; $H_TBL = ''; $h_TBL = '
'; $H_TR = '' ; $h_TR = ''; $H_TH = '' ; $h_TH = ''; $H_TD = '' ; $h_TD = ''; $H_I = '' ; $h_I = ''; $H_B = '' ; $h_B = ''; $H_P = '

' ; $h_P = '

' ; print "\n"; print "\n \n Radius Log\n \n\n"; print "\n"; printf ("

Report generated at %s

\n", scalar localtime); print "

Table of Contents

\n
\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 "" ;} 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 "" ;} 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 "" ;} 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}Multiple login here:${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) { $_ = ; 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} = ""; --$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 <) { 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 () { 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}; } }