radiusreport-0.3b6/radiusreport-0.3b6010075500007640000012000000715440651412337300202050ustar00pgreggstaff00000400000010#!/usr/local/bin/perl # # radiusreport - Extract information from Radius 2.0 detail log # # Author: Paul Gregg # 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 # 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 / Joe Hartley # 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 ) # # 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 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/local/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) { $_ = ; 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 <) { 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 - 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; } d'}) ) { my $interested = 0; #if ($AcctStatusType eq "Start") { my $tmpm = $arg{'d'}; my @mons = split(/:/, $tmpm); my $mon = ""; radiusreport-0.3b6/radiusreport012077700007640000012000000000000651542367400226442radiusreport-0.3b6ustar00pgreggstaff00000400000010radiusreport-0.3b6/CHANGELOG010064400007640000012000000103510650730233100162000ustar00pgreggstaff0000040000001019980110 - Paul Gregg . Maintenence release before 0.4. . Fixed problem with bigger ISPs and multiple Radius clients using the same SessionID, RR now generates a uniqueID to work with based on UserName, SessionID, NAS-IP-Address, and NAS-Port. . Included diffs by kjmiller which added support for a Connect-Rate '-s' flag. . Split the Telco charge rates to allow for different Analogue and ISDN tariffs - kjmiller . Added a '-H' flag to supress 'per login' reports (suggested by Omar Thameen (sorry Omar, I didn't like -B)) . Removed Start / Stop matching code. RR only uses Stop records - Downside is that we will miss 'missing Stops', however there isn't much you can do about it even if you know they are missing. Upside RR uses less memory and is marginally faster. . RR now has unbuffered output. . Boosted version to V0.3b6 19970906 - Paul Gregg . Added support for Non-Livingston V2.0+ Radius log files. See the RECORD_DATE_FMT define near the top of the program if this applies to you. This should permit RadiusReport work with any make and model of Radius logs. NOTE: Possible COMPAT_BUSTER - Requires perl module: POSIX (mktime) Ascend logfile examples courtesy of Daniel Serena . Added check for 'Framed-Address' for Livingston Radius V1.16 for current 'Framed-IP-Address' data. . Expanded -i flag. If -i argument is 0 (i.e. -i 0 instead of -i 111.222.111.222) a complete IP useage report for all IPs is generated. Suggested by Jim Hribnak . Added extra flag -d. Use: -d e.g. -d Jan or -d 01 or -d 1 or -d 1:2 Only records which start during that month will be reported. . Boosted Version to Beta V0.3b5 19970730 - Paul Gregg . Added ability to 'calculate' the approximate telephone charges (or online charges if thats your thing). Note: In the UK, there's no such thing as free local calls, so you pay for your dialup access. This bit was intended to work out costs using one tariff. Tarriffs and times can be changed deep in the code. I'll make this more robust and configurable later. Look for the calculate_cost function for the defines. . Boosted version to Beta V0.3b4 19970720 - Paul Gregg . Added multiple login checking ability. Need some way of allowing specific users to MLPPP. To turn off see the REPORT_MULTIPLE_LOGIN define 19970716 - Paul Gregg . Added a configurable DATE_FORMAT define to allow users to specify the format that the Date is printed e.g. MM/DD/YY vs DD/MM/YY Americans! ;-) V0.3b3 19970711 - Paul Gregg . Fixed a bug in 2 function defs where I used sub func() { Removed the ()'s. Apparently this has no effect on Perl5 Unix (which is why I didn't spot them) but it caused Perl on NT to bug out. Reported by: Eric Lund V0.3b2 19970705 - Paul Gregg . Added ability to report on the cause of the connection to drop using the command line flag -q. V0.3b1 19970704 - Paul Gregg . Bumped Version number to 0.2 19970701 - Paul Gregg . Added ability to store individual user reports as a file in a dir. Use the -o flag to specify output path when used with -l all : e.g. -o STDOUT is the same as not specifying it at all (useful... not) -o /dir/path. A report for each user is placed in /dir/path/username. This is useful when you want, for example, to write a wrapper program or script to email everyone's usage to them. I'll leave you to sort out username to email address mappings as that isn't in the radius data. 19970620 - Paul Gregg . Added the ability to provide a complete list of usernames against their last login time (as far as the detail file(s) can tell). Use the -r flag on it's own to produce this report. 19970615 - Paul Gregg . Added the ability to produce a report on IP usage. -i aaa.bbb.ccc.ddd will produce a list of users/times/etc on who used that IP address (good for Dynamically allocated IP setups and when you need to track down netabusers) radiusreport-0.3b6/README010064400007640000012000000113170640437044000156530ustar00pgreggstaff00000400000010Program: RadiusReport Author: Paul Gregg Date: May 1997 (V0.1) Current Ver: V0.2 Beta Ver: V0.3b5 URL: http://www.tibus.net/pgregg/projects/radiusreport/ Brief: Radiusreport is a utility which parses RADIUS files to produce various reports on user and system usage. Licence: Basically GPL, but I ask is that if you pass it on that it is passed on in its original format and also includes all supplimentary files. I also ask that you email me to let me know you are using it. Thanks: To everyone for the support and saying how useful radiusreport is :-) Comments: If you find a version of Radius this doesn't work on then please send me details of 1) The radius server and 2) Examples of your logs (note: confidentiality of any logs are assured). Usage: ------ Radiusreport can produce many varied reports by analysing your RADIUS detail files. detail files can be uncompressed/compressed/gzipped. . User Online time reports plus extras like bandwidth used, etc. . IP address usage . Last login time reports As of V.02 radiusreport can generate individual files for each user by using the -o flag in conjunction with '-l all'. This function will enable you to 'reprocess' radiusreport's output, e.g. to email a copy of each users report to them. RadiusReport does not need special priveleges, just read access to the detail files, and if using the -o flag write access to the directory you are putting the user reports into. This README was knocked out very quickly, so I'd appreciate any comments Examples on use: 1. To produce a 'minimal' report on 'abcedf'. radiusreport -l abcdef -f /var/adm/radacct/portmaster1/detail Radius Log Report for: abcdef Date Login Logout Ontime Port ------------------------------------------------------------------------ 02/07/97 22:54:07 23:03:54 9m48s A9 ...... ------------------------------------------------------------------------ 2. To produce the same, but without that head/food text. (most minimal) radiusreport -h -l abcdef -f /var/adm/radacct/portmaster1/detail 3. To produce a 'full' report on account 'abcdef'. radiusreport -tba -l abcdef -f /var/adm/radacct/portmaster1/detail Radius Log Report for: abcdef Date Login Logout Ontime Port BW-In/Out Total ------------------------------------------------------------------------ 01/06/97 13:18:19 13:38:07 19m48s A1 120.1K/309.3K 0h19m 01/06/97 22:32:24 23:32:17 59m54s A3 218.1K/1.7M 1h19m 02/06/97 19:54:33 21:15:01 80m29s A2 396.7K/1.3M 2h40m ........ 30/06/97 19:33:53 20:05:25 31m32s A2 116.0K/1017.6K 54h24m 30/06/97 22:29:00 01:56:13 207m08s A1 1.2M/5.7M 57h51m 02/07/97 23:33:13 00:40:42 67m30s A3 276.7K/1.0M 58h59m ------------------------------------------------------------------------ Total Hours: 58h59m Average Online times: 1h52m per day, 13h08m per week Total Data transferred In/Out: 18.2M/67.8M A couple of points: Port is made up from a combination of 2 things, the login type Async or Isdn BW-In/Out is the about of data transferred during the dialup *from the perspective of the portmaster* The rest of the columns should be pretty straight forward ;) 4. To produce reports for every user for the last 2 months. (I archive my months details away, May's is gzipped, June's is plain) radiusreport -tba -l all -f /var/adm/radacct/portmaster1/archives/1997\ /06/detail:/var/adm/radacct/portmaster1/archives/1997/05/detail.gz of course this shoulds all be on one line. To make the output be placed in a file per user set up a 'clean' directory to put the files in, e.g. mkdir /tmp/reports and use the above command with the extra flag -o /tmp/reports Note: This will quickly eat memory. Unfortunately due to the memory based hash tables RadiusReport uses even a small ISP can easily overload a 64Mb system. I'll be developing file based hash tables for the next major release which will remove this problem. Speed will probably not suffer much because RadiusReport will no longer need to make extensive use of most systems swap space ;) (I'm a 200 user ISP and RadiusReport builds to 80Mb on 2 months worth of logs) 5. To produce a list of users who used IP address aaa.bbb.ccc.ddd radiusreport -i aaa.bbb.ccc.ddd -f detail 6. To produce a list of users, along with their last login times. (Only really useful if performed on at least 1 or 2 months logs) radiusreport -r -f detail:detail.lastmonth 7. To produce a complete Login User / IP address mapping list radiusreport -i 0 -f detail 8. To generate a telco cost for user 'joe' for the month of June radiusreport -tbac -l joe -f detail.june:detail.july -d Jun (Using July's logfile, in case a login rolled over to the next month). Have fun. Suggestions welcome. Paul. ity which parses RADIUS files to produce various reports on user and system usage. Licence: Basically GPL, but I ask is that if you pass it on that it is passed on in its original format and also includes all supplimentary files. I also ask that you email me to let me know you are using it. Thradiusreport-0.3b6/CHECKSUMS010064400007640000012000000003230655030102200162260ustar00pgreggstaff00000400000010MD5(CHANGELOG)= 38c0763a0917771d5374e84942d72a0c MD5(README)= d66e991a7bfe2922a8b5ab6ad3cbcb98 MD5(radiusreport-0.3b5)= f74db2f13f787e2a8cd99e3956a22873 MD5(radiusreport-0.3b6)= e447378316c665f349f1cdf2ca6d4d47 radiusreport-0.3b6/posixchk.pl010075500007640000012000000022040647227275500171750ustar00pgreggstaff00000400000010#!/usr/local/bin/perl use POSIX; $ENV{'TZ'}="GMT"; tzset(); $RECORD_DATE_FMT = "DAY MON MDAY HH:MM:SS YEAR"; $recdate = "Mon Feb 9 22:59:13 1998"; $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; } $Timestamp = calendar_time( $DAY, $MON, $MDAY, $HH, $MM, $SS, $YEAR ); if ($Timestamp eq 884386753) { print "$recdate -> $Timestamp (GMT)\n"; print "Successful, POSIX mktime() is available and working.\n"; } else { if ($Timestamp eq 0) { print "$recdate -> $Timestamp\nFailed.\n"; } else { print "$recdate -> $Timestamp ($ENV{'TZ'})\n"; print "ERROR: POSIX mktime() is exists but is returning wrong results.\n"; } } 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); my $cal = mktime($sec, $min, $hour, $mday, $monthshash{$month}, $year, $weekhash{$day}, 0, 0); return $cal; }