From dima@physics.odu.edu Mon Mar  3 18:43:33 1997
Sender: dima@physics.odu.edu
Message-Id: <331B8D7E.3F54@physics.odu.edu>
Date: Mon, 03 Mar 1997 21:48:30 -0500
From: Dmitri Krioukov <dima@physics.odu.edu>
Organization: CEBAF/ODU
Mime-Version: 1.0
To: cdr@livingston.com
Subject: radlog
Content-Type: multipart/mixed; boundary="------------FF6ABD31DF"
Status: RO

This is a multi-part message in MIME format.

--------------FF6ABD31DF
Content-Type: text/plain; charset=koi8-r
Content-Transfer-Encoding: 7bit

you may find useful the following enhancement (radlog) of your radius
accounting scripts. first, it knows now about Acct-Delay-Time, which is
rather important if it's not zero. second, it introduces a number of
interesting (from my point of view) features (like checking if all
ports were busy any time during this month -> we need to buy another
portmaster?:) also, i use my radcron script to merge details from
different accounting servers into one chronologically ordered detail
file.

please feel free to modify these as much as you want.  you can also
freely distribute them, put them on your ftp site, delete them, print
them, or whatever you think may be the most strange thing to do with
them.

--------------FF6ABD31DF
Content-Type: text/plain; charset=koi8-r; name="radcron"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline; filename="radcron"

#!/usr/bin/perl
#
# radcron produce cronological radius detail file from (non-)cronological one(s)
#
# 97/02/26 Dmitri Krioukov; dima@physics.odu.edu

require 'timelocal.pl';

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

$/ = '';

while (<>) {

  $longtime = timelocal ($6, $5, $4, $3, $months{$2}, $7) if
    (/^(\w{3})\s(\w{3})\s{1,2}(\d{1,2})\s(\d{2}):(\d{2}):(\d{2})\s19(\d{2})\n/
    && (grep {$1 eq $_} @wdays) && (grep {$2 eq $_} keys %months));
  $longtime -= $1 if /Acct-Delay-Time = (\d+)/;
  $log{$_} = $longtime if $longtime;
  $longtime = 0;
}
  
for $i (sort {$log{$a}<=>$log{$b}} keys %log) {
  printf ("%s", $i);
}

--------------FF6ABD31DF
Content-Type: text/plain; charset=koi8-r; name="radlog"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline; filename="radlog"

#!/usr/bin/perl
#
# radlog - extract various type of info from RADIUS Accounting detail file(s).
# to produce more reliable results use radcron on your log file(s) first.
#
# type radlog -h for help
#
# 94/11/28  Author: Carl Rigney; cdr@livingston.com
# 95/01/22  modified
# 96/09/27  Updated to work with RADIUS 2.0
# 97/01/12  Comments added
# 97/02/24  Revised and revived by Dmitri Krioukov; dima@physics.odu.edu

require 'timelocal.pl';

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

$firstrate = 0;     # charge per minute until breakpoint minutes
$secondrate = 0;    # charge per minute after breakpoint minutes
$breakpoint = 0;    # breakpoint in minutes to change charging rates

$maxport = 9;       # ports 0..$maxport are counted in load statistics only

do {
  ($ARGV = shift) =~ /^-(.)$/;
  $option = $1;
  if    ($option eq 'a') {$allinfo++;$flag++}
  elsif ($option eq 'u') {$userinfo++;$flag++}
  elsif ($option eq 'p') {$portinfo++;$flag++}
  elsif ($option eq 'l') {$loadinfo++;$flag++}
  elsif ($option eq 'c') {$chargeinfo++;$flag++}
  elsif ($option eq 'r') {$terminfo++;$flag++}          # router
  elsif ($option eq 'i') {$termportinfo++;$flag++}      # interfaces
  elsif ($option eq 'm') {$termuserinfo++;$flag++}      # modems
  elsif ($option eq 'o') {$oneuserinfo++;$flag++;$oneuser=shift}
  elsif ($option eq 'f') {$firstinfo++;$flag++}
  elsif ($option eq 'e') {$errorinfo;$flag++}
  elsif ($option eq 't') {$table++}
  elsif ($option eq 'v') {$verbose++}
  elsif ($option eq 'h') {$help++}
  elsif ($option eq 's') {$periodinfo++;push(@period,   # search
                          timelocal(reverse split(/\s+/,shift)),
                          timelocal(reverse split(/\s+/,shift)))}
  elsif (!$flag || $help) {
    printf ("usage: %s option... file...\n", $0);
    print "  -a\t\tprint all sessions\n";
    print "  -u\t\tprint user statistics...\n";
    print "  -c\t\t...charges as well\n";
    print "  -p\t\tprint port statistics\n";
    print "  -l\t\tprint if all ports were busy sometimes\n";
    print "  -r\t\tprint termination status statistics\n";
    print "  -i\t\tprint termination status per port statistics...\n";
    print "  -m\t\tprint termination status per user statistics...\n";
    print "  -t\t\t...as a table\n";
    print "  -o username\tprint sessions of the user only\n";
    print "  -f\t\tprint first users' sessions only\n";
    print "  -e\t\tprint any inconsistencies in logs\n";
    print "  -s begin end\tsessions between begin and end only\n";
    print "    \t\t begin and end should be in the following format (cf. ctime(3))\n";
    print "    \t\t \"year(97) tm_mon tm_mday tm_hour tm_min tm_sec\"\n";
    print "  -v\t\tbe verbose\n";
    print "  -h\t\tprint this and exit na hui\n";
    exit;
  }
} until !$option;
unshift (@ARGV, $ARGV);

$/ = '';		# read paragraph at a time

# Read through accounting detail file(s)

while (<>) {
  
#	skip messages about PortMaster going down/up

  next if /Acct-Session-Id = "00000000"/;

#	only process stop messages, since they have all the info we need

  if (/Acct-Status-Type = Stop/) {
  
#	All valid user session accounting records have an Acct-Session-Id.
#	Get the ID and address of the PortMaster and combine them to create
#	a unique identifier for this session, in order to check for duplicates

    if (/Acct-Session-Id = "([^"]+)"/) {
      $id = $1;
      if (/NAS-IP-Address = (\S+)/ || /Client-Id = (\S+)/) {
        $nas = $1;
        $id .= '@'.$nas;
        if ($seen{$id}++) {
          $dup++;
          next;
        }
      }
    }
    else {
      $err{'No ID'}++;
      next;
    }

# Get time of message receipt

    $longtime = timelocal ($6, $5, $4, $3, $months{$2}, $7) if
      (/^(\w{3})\s(\w{3})\s{1,2}(\d{1,2})\s(\d{2}):(\d{2}):(\d{2})\s19(\d{2})\n/
      && (grep {$1 eq $_} @wdays) && (grep {$2 eq $_} keys %months));

# Get delay time and calculate actual stop time of the session

    $longtime -= $1 if /Acct-Delay-Time = (\d+)/;

# Get elapsed time; skip if do not fit into the time interval of interest

    if (/Acct-Session-Time = (\d+)/) {
      $elapsed = $1;
      next if $elapsed <= 0;
      @sesper = ($longtime - $elapsed, $longtime);
      next if ($periodinfo && !&overlap(\@sesper,\@period));
    }

#	Get the Username
#	Increase the number of logins and total time used, for this user
    
    if (/User-Name = "([^"]+)"/) {
      $user = $1;
      if ($userinfo || $chargeinfo) {
        $uses{$user}++;
        $used{$user} += $elapsed;
      }
    }

#	Record the number of uses and time used for the port, too.
#	Get the start and stop times

    if (/NAS-Port = (\d+)/ || /Client-Port-Id = (\d+)/) {
      $port = $1;
      if ($portinfo) {
        $npuses{$port}++;
        $npused{$port} += $elapsed;
      }
      push (@{$occ[$port]}, @sesper) if ($loadinfo && $port <= $maxport);
    }
    
# Get exit status and its statistics on per port/per user basis

    if (/Acct-Terminate-Cause = (\S+)/) {
      $cause = $1;
      $allcauses{$cause}++ if ($table || $terminfo);
      $port_cause[$port]{$cause}++ if $termportinfo;
      $user_cause{$user}{$cause}++ if $termuserinfo;
    }

# Create session @record:
# 0 - start; 1 - stop; 2 - user; 3 - time on; 4 - port; 5 - termination status

    @{$record[$entry++]} = (@sesper, $user, $elapsed, $port, $cause) if
      ($allinfo || $oneuserinfo || $firstinfo);

# In case some fields in the next record are not presented
  
    $id = $nas = $longtime = $elapsed = @sesper = $user = $port = $cause = '';
  }
}

# And print results

if ($errorinfo) {
  print "# $dup duplicates\n" if $dup;
  print "# $err{'No ID'} stop records without Acct-Session-ID\n"
    if $err{'No Id'};
  print "\n";
}

if ($allinfo) {

# print the whole logfile

  if ($verbose && $periodinfo) {
  printf ("Sessions overlapping with (%s -- %s) interval are:\n",
    scalar localtime $period[0], scalar localtime $period[1]);
  print "-----------------------------------------------------";
  print "-----------------------------------------\n";
  }
  for $i (0..$#record) {
    print_record (@{$record[$i]});
  }
  print "\n";
}

if ($userinfo || $chargeinfo) {

# print usage by user

  if ($verbose) {
    if ($chargeinfo) {
      print ("user                    total time    # of logins     charge\n");
      print ("------------------------------------------------------------\n");
    }
    else {
      print ("user                    total time    # of logins\n");
      print ("-------------------------------------------------\n");
    }
  }
        
  for $user (sort {$used{$b} <=> $used{$a}} keys %used) {
    
    if ($chargeinfo) {

# calculate charge based on minutes of usage (round down)
# charging $firstrate per minute until $breakpoint, then $secondrate

      $m = int($used{$user}/60);
      if ($m <= $breakpoint) {
        $charge = $m * $firstrate;
      }
      else {
        $charge = $breakpoint * $firstrate + ($m-$breakpoint) * $secondrate;
      }
      printf "%-16s\t%s           %4d     %6d\n",$user,&hms($used{$user}),
                                           $uses{$user}, $charge;
    }
    else {
      printf "%-16s\t%s           %4d\n",$user,&hms($used{$user}),$uses{$user};
    }
  }
  print "\n";
}

if ($oneuserinfo) {

# print dossier on particular user only

  if ($verbose) {
    printf ("Sessions of user %-22s are:\n", $oneuser);
    print "--------------------------------------------\n";
  }
  
  for $i (0..$#record) {
    print_record (@{$record[$i]}) if $record[$i][2] eq $oneuser;
  }
  print "\n";
}

if ($firstinfo) {

# print first users' sessions from logfile

  if ($verbose) {
    print "First users' sessions are:\n";
    print "--------------------------\n";
  }
  
  for $i (0..$#record) {
    print_record (@{$record[$i]}) if !$userseen{$record[$i][2]}++;
  }
  print "\n";
}

if ($portinfo) {

# print port statistics

  if ($verbose) {
    print ("port #        # of sessions           total time\n");
    print ("------------------------------------------------\n");
  }
  
  for $np (sort {$a <=> $b} keys %npused) {
    printf "%-16s%4d\t\t      %s\n",$np,$npuses{$np},&hms($npused{$np});
  }
  print "\n";
}

if ($terminfo) {

# print session termination statistics

  if ($verbose) {
    print "Status         total number\n";
    print "---------------------------\n";
  }
  
  for $i (sort {$allcauses{$b}<=>$allcauses{$a}} keys %allcauses) {
    printf ("%-22s%5d\n", $i, $allcauses{$i});
  }
  print "\n";
}

if ($termportinfo) {

# print termination per port statistics...

  if ($table) {  

# ...as a table...

    print "       ";
    for $j (sort keys %allcauses) {
      printf ("%22s", $j);
    }
    print "\n";
    for $i (0..$#port_cause) {
      printf ("Port %2d", $i);
      for $j (sort keys %allcauses) {
        printf ("%22d", $port_cause[$i]{$j});
      }
      print "\n";
    }
    print "\n";
  }
  else {

# ...or lines sorted

    for $i (0..$#port_cause) {
      printf ("Port %2d:\n", $i);
      print "--------\n";
      for $j (sort {$port_cause[$i]{$b} <=> $port_cause[$i]{$a}}
                                        keys %{$port_cause[$i]}) {
          printf ("%-22s\t%d\n", $j, $port_cause[$i]{$j});
      }
      print "\n";
    }
    print "\n";
  }
  print "\n";
}

if ($termuserinfo) {

# print termination per user statistics...

  if ($table) {
  
# ...as a big table...

    print "                      ";
    for $j (sort keys %allcauses) {
      printf ("%22s", $j);
    }
    print "\n";
    for $i (sort keys %user_cause) {
      printf ("%-22s", $i);
      for $j (sort keys %allcauses) {
        printf ("%22d", $user_cause{$i}{$j});
      }
      print "\n";
    }
    print "\n";
  }
  else {

# ...or lines sorted

    for $i (sort keys %user_cause) {
      printf ("User %s:\n", $i);
      print "---------------------------\n";
      for $j (sort {$user_cause{$i}{$b} <=> $user_cause{$i}{$a}}
                                        keys %{$user_cause{$i}}) {
          printf ("%-22s\t%3d\n", $j, $user_cause{$i}{$j});
      }
      print "\n\n";
    }
    print "\n";
  }
  print "\n";
}

if ($loadinfo) {

# calculate all ports busy times..

  @overlap = @{$occ[0]};
  for $k (1..$#occ) {
    @overlap = &overlap(\@overlap, \@{$occ[$k]});
  }
  
# and print results

  if (@overlap) {
    if ($verbose) {
      printf("All ports (0..%2d) were busy during following periods of time:\n",
                            $maxport);
      print  "-------------------------------------------------------------\n";
    }
    print_overlap (@overlap);
    print "\n";
  }
  elsif ($verbose) {
    print "There were no moments when all ports were busy.\n\n";
  }
}

# hms($seconds) returns time in hh:mm:ss format

sub hms {
	local($h,$m);
	local ($s) = shift(@_);
	$m = int($s / 60);
	$s = $s % 60; 
	$h = int($m / 60);
	$m = $m % 60;
	sprintf("%4d:%02d:%02d",$h,$m,$s);
}

# overlap(\@a,\@b):
# $a[2*$i]   are start times;
# $a[2*$i+1] are stop times.
# the same with @b.
# returns @o, where
# $o[2*$i]   are start times of periods both @a and @b are active;
# $o[2*$i+1] are corresponding stop times.                       
# records may not be in chronological order, so we're doomed (nxm times).

sub overlap {
  local($i,$j,@o);
  
  for ($i = 0; $i < $#{@_[0]}; $i += 2) {
    for ($j = 0; $j < $#{@_[1]}; $j += 2) {
      if (@{@_[1]}[$j] <= @{@_[0]}[$i+1] && @{@_[1]}[$j+1] >= @{@_[0]}[$i]) {
        push @o,(@{@_[0]}[$i] > @{@_[1]}[$j]) ? @{@_[0]}[$i] : @{@_[1]}[$j],
         (@{@_[0]}[$i+1] < @{@_[1]}[$j+1]) ? @{@_[0]}[$i+1] : @{@_[1]}[$j+1];
      }
    }
  }
  @o;
}

# print maximum session info

sub print_record {
  printf ("%s -- %s\t%-16s\t%s\t%d\t%s\n",
      scalar localtime shift, scalar localtime shift,
         shift, &hms(shift), shift, shift);
}

# print what overlap returns

sub print_overlap {
  while ($#_+1) {
    printf ("%s -- %s\n", scalar localtime shift, scalar localtime shift);
  }
}

--------------FF6ABD31DF--

