#!/usr/local/bin/perl
### ---------------------------------------------------------------------
#
# dopen  - this program prints a log of a detail file, with specs on 
#	   usernames and dates
#
# Copyright (C) 1997 Random Communications Inc.
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
#
### ---------------------------------------------------------------------
#
# $Id; d_open,v 1.06 10.1.1996, Richard Dows <marius@randomc.com> $
#
$version = '1.06';
#
###
#
# Explanation of detail file:
#
#   <Day> <Month> <day of month> <hr:min:secs> <year>
#     Acct-Session-Id 		= "string"
#  (*)User-Name			= "string"
#     Client-Id			= some Ip address
#     Client-Port-Id		= 25
#     NAS-Port-Type		= Async
#  (*)Acct-Status-Type  	= Stop|Start
#  (*)Acct-Session-Time 	= amt of seconds
#     Acct-Authentic    	= RADIUS
#  (*)Acct-Input-Octets 	= amt of octets
#  (*)Acct-Output-Octets	= amt of octets
#  (*)Acct-Terminate-Cause	= why ?
#  (*)User-Service-Type 	= integer
#     Framed-Protocol		= string ( ie PPP, SLIP, ISDN )
#     Framed-Address		= IP address
#     Acct-Delay-Time		= integer
#
#
# The lines marked with (*) are the ones we will be looking for at some
# point in this program.  If you want to search for others, mail me with
# the patch file, or if you can't code it, mail the suggestion and I'll
# write it in.
#
# Copyright (C) 1997, Random Communications Incorporated, Richard Dows,
# Permission to copy or distribute this verbatim copy is granted.
#
# Revision History:
#
# 1.11.97,  Options '-o' and '-u' can now be used together.
#
# 1.21.97,  Added '-s' ( summary ) option for use with '-u <>'
#
### ---------------------------------------------------------------------
#

package main;

require 'newgetopt.pl';
require 'm1.pl';

push( @INC, '\.' ) || warn "$!\n";

### ---------------------------------------------------------------------
#    Usage Parameters for the program.
### ---------------------------------------------------------------------

sub usage {
  
  $usage = <<USAGE;

d_open;

Usage: d_open	-u <username, defaults to all>
		-d <detail file, mandatory>
		-o [octet search]
		-t [termination cause search, must have -u option]
		-r [rotate after the progam has finished]
		-m [mail output to $sysadmin]
		-s [summary, can't use with -t or -o]

<> indicates string arg to flag, [] indicates explanation of flag

d_open, version $version

USAGE

  warn $usage;
  exit(0);
}

### ---------------------------------------------------------------------
#    Configuration options, and variable definitions.
### ---------------------------------------------------------------------

@options 	= ( "u:s", "d:s", "o", "t", "m", "r", "s", );

$debug 		= 1;			# Debug flag.
$username	= '';			# Username define.
$DbmDir		= '/export/home/gregm/pack/dbm/';	# The Database directory
$DataDir	= '/usr/adm/radacct/';	# The dir to read from.
$TmpDir		= '/export/home/gregm/pack/out/';	# The tmp dir.
$TmpFile	= '';			# A definition to be used later.
$LogFile	= $TmpDir.'log';	# The logfile to create.
$tempvar	= 0;			# Tempvar used for username.
$tu 		= '';			# Temporary name variable.
$pmname		= '';			# Current pm detail file.
$os		= 0;			# Octet search (only !!).
$ts		= 0;			# Termination search.
$count		= 0;			# Temp counter for $ts option.
$sysadmin	= 'marius@randomc.com';	# For the '-M' option.
$MAILER		= '/usr/lib/sendmail';	# The mailer.
@files		= ();			# Array, to be defined later.

if ( ! &NGetOpt(@options) ) { &usage; };

### ---------------------------------------------------------------------
#    Database declarations.
### ---------------------------------------------------------------------

BEGIN {
}

system( "rm -f ${DbmDir}map1.dir ${DbmDir}map1.pag" );
$ENV{'DBMNAME'} = "${DbmDir}map1";
&dbm'create();

### ---------------------------------------------------------------------
#    Check the options from the command line
### ---------------------------------------------------------------------

if ( $opt_o ) {
  $os = 1;
} else {
  undef $os;				# Undefine it.
}

if( $opt_u ) {
  $username 	= $opt_u;
} else {
  undef $username;			# Undefine it.
}

if( ! $opt_d ) {
  opendir(DIR, "${DataDir}")	|| die;
  @files = readdir(DIR);

  foreach ( @files ) {
    $_ = ${DataDir} . '/' . $_ . '/detail';
  }
  closedir(DIR);

} elsif ( $opt_d =~ /\*/ ) {
  opendir(DIR, "$DataDir")	|| die;
  @files = readdir(DIR);

  foreach ( @files ) { $_ = ${DataDir} . '/' . $_ . '/detail'; }

  closedir(DIR);
} else {
  @files = ( $DataDir . '/' . $opt_d . '/detail' );
}

if ( $opt_t ) {
  &usage,	if ( ! $opt_u || $opt_o );
  $ts	= 1;
} else {
  undef $ts;
}

#
# Option '-s' is for summarizing the log reports in one single file.
# This option is to be used with the '-u <username>' option.
#
if ( $opt_s ) {
  &usage,	if ( $opt_t || $opt_o );
}

### ---------------------------------------------------------------------
#    Debugging.
### ---------------------------------------------------------------------

sub D {
  if ( $::debug ) {
    open(LOG, ">>$LogFile")	|| die "$!\n";
    print LOG @_, "\n";
    close(LOG);
  } else {
    print STDOUT @_;
  }
}

### ---------------------------------------------------------------------
#    Signal Catching.
### ---------------------------------------------------------------------

sub handler {
  local($sig) = @_;
  print "Caught SIG$sig - shutting down.\n";
  exit(0);
}

$SIG{'INT'} 	= 'handler';
$SIG{'KILL'} 	= 'handler';
$SIG{'QUIT'} 	= 'handler';

### ---------------------------------------------------------------------
#    Main program.
### ---------------------------------------------------------------------

system("rm -f $TmpFile"), if ( -e $TmpFile );

foreach ( @files ) {
  $pmname	= ( split(/\//, $_) )[$#_ - 1];

  next, if ( ! -e $_ );		# The 'detail' file doesn't exist.

  open( READ, "<$_")	|| die;	# Open file for reading.

  undef $count, if ( defined $count );
  $count 	= 0;

  while(<READ>) {
    last, if(eof);		# End of file ?  End the loop.

    if ( /^$/ ) {
      undef $tempvar, 	if ( defined $tempvar );
      undef $tu, 	if ( defined $tu );
      next;
    }

    if ( /^([\S]*) (\w+)/ ) {
      ($wday, $mo, $mday, $dtime, $tr) = split(/\s+/, $_);
      next;
    }

    if ( /User-Name/ ) {
      s/[^\"]*"([^\"]*).*/$1/;
      s/\s+//g;
   
      if ( defined $username ) {
        if ( $_ =~ /$username/i ) {
	  $tempvar 	= 1;
        } else {
	  $tempvar 	= 0;
	  next;
	}
      } else {
	$tu = $_;
      }
      next;
    }

    if ( /Acct-Session-Time/ ) {
      s/Acct-Session-Time = //;
      s/\s+//g;

      next, if ( ( defined $username && $tempvar == 0 ) || defined $ts ||
	defined $os );

      &dbm'add( defined $username ? $username : $tu,
	&dbm'query( defined $username ? $username : $tu ) + $_ );
      next;
    }

    if ( /Acct-(.*)-Octets/ && defined $os ) {

      $type = $1;

      s/Acct-(.*)-Octets = //;
      s/\s+//g;

      my @tmp = &dbm'Query( defined $username ? $username : $tu );

      &dbm'del( defined $username ? $username : $tu );

      if ( $type =~ /input/i ) {
        &dbm'Add( defined $username ? $username : $tu, ( $tmp[0] + $_ ) );
	&dbm'Add( defined $username ? $username : $tu, ( $tmp[1] ) );
      }

      if ( $type =~ /output/i ) {
	&dbm'Add( defined $username ? $username : $tu, $tmp[0] );
  	&dbm'Add( defined $username ? $username : $tu, $tmp[1] + $_ );
      }

      undef @tmp && next;
    }

    if ( /Acct-Terminate-Cause/ && defined $ts ) {
      s/Acct-Terminate-Cause = //;

      next, if ( ! defined $username || $tempvar == 0 );

      &dbm'Add( $count, join( ' ', ( $wday, $mo, $mday, $dtime ) ) );
      &dbm'Add( $count, $_ );

      $count++ && next;		# Increment counter, and next.
    }

    next;
  }

  &WriteAll( $pmname ),	if ( ! $opt_s );

  if ( ! $opt_s ) {		# Ok, clobber and create.
    &dbm'create(),	if ( &dbm'clobber() );
  } 
}

&WriteAll(),	if ( $opt_s );	# Write the summary file out.

&Clean();
exit(0);

### ---------------------------------------------------------------------
#    More subs.
### ---------------------------------------------------------------------

sub Clean {
  # system( "rm -f ${LogFile}" ),	if ( ! $debug );
  system( "rm -f ${DbmDir}map1.dir ${DbmDir}map1.pag" );
  1;
}

#
# The sub that writes out the reports, or mails out.
#
sub WriteAll {
  local($name)	= "@_";
  local($type);
  local($c)	= 0;		# Temp var, for counting total hours.

  if ( ! $opt_s ) {
    open( WRITE, ">${LogFile}\.$name")		|| die;
  } else {
    open( WRITE, ">${LogFile}\.summary" )	|| die;
  }

  #
  # &dbm'Query() for the $os option will return like this:
  #
  # %dbm	= ( '$username', 'input_octets|output_octets', )
  #
  # So when we come to write it out, we make a small temporary array
  # of the Query value, split it, and then reference via the array.
  #
  if ( defined $os && ! defined $ts ) {
    printf WRITE "Portmaster detail log for %s\n\n", $name;
    printf WRITE "%12.12s %s %-15s %-15s\n", "Login", " " x 20, "Input",
	"Output";
    printf WRITE "%12.12s %s %-15s %-15s\n", "-----", " " x 20, "-----",
	"------";

    @keys	= sort &dbm'keys;

    # Sort the entries by login name, then print out.
    foreach ( @keys ) {
      my @tmp	= &dbm'Query( $_ );

      printf WRITE "%12.12s %s %-15d %-15d\n", $_, " " x 20, $tmp[$[], 
	$tmp[$#tmp];
    }

    close( WRITE );
  }

  #
  # The $ts option searches for termination strings and causes.  We know
  # the username since the script can only use it with the '-u' option,
  # so it will be easier to manage.  The format of the database is as so:
  #
  # %dbm	= ( '$count', '$datestamp|$reason', );
  #
  elsif( defined $ts && ! defined $os ) {
    printf WRITE "Portmaster detail log for %s, username '%s'.\n\n", $name,
	$username;
    printf WRITE "%20.20s %26.26s %-20.20s\n", "Date", " ", "Reason";
    printf WRITE "%20.20s %26.26s %-20.20s\n", "----", " ", "------";

    foreach ( sort &dbm'keys() ) {
      my @tmp	= &dbm'Query( $_ );

      printf WRITE "%20.20s %20.20s %-20.20s\n", $tmp[$[], " ", $tmp[$#tmp];
    }

    close( WRITE );
  }

  #
  # The $opt_s means summarize so the program loops through every detail
  # file without resetting it's dbm's.  Then here, we simply write out
  # how many hours online that person has spent total.
  #
  elsif ( $opt_s ) {

    print  WRITE "Summary file: \n\n";
    printf WRITE "%-12.12s %15.15s %-10.10s\n", "Username", " ", "Time (hrs)";
    printf WRITE "%-12.12s %15.15s %-10.10s\n\n", "--------", " ", "----------";

    foreach ( sort &dbm'keys() ) {
      $tmp	= &dbm'query( $_ );

      printf WRITE "%-12.12s %15.15s %-8d\n", $_, " ",
	( ($tmp / 3600) == 0 ? 1 : int($tmp / 3600) ), 
	unless (int($tmp / 3600) == 0 && ! defined $username );
    }

    close( WRITE );
  }

  # 
  # We know the $os option isn't defined or being used so the &dbm'Query()
  # will return something like this:
  #
  # %dbm	= ( '$username', '$time' );
  #
  else {
   
    printf WRITE "Portmaster detail log for %s\n\n", $name;
    printf WRITE "%-12.12s %15.15s %-10.10s\n", "Username", " ", "Time (hrs)";
    printf WRITE "%-12.12s %15.15s %-10.10s\n\n", "--------", " ", "----------";

    @keys	= sort &dbm'keys;

    foreach ( @keys ) {
      my @tmp 	= &dbm'Query( $_ );
      $c += $tmp[$[];

      printf WRITE "%-12.12s %15.15s %-8d\n", $_, " ",
	( ($tmp[$[] / 3600) == 0 ? 1 : int($tmp[$[] / 3600)),
	unless (int($tmp[$[] / 3600) == 0);
    }

    printf WRITE "\nTotal online hours: %d\n", int($c / 3600);

    close( WRITE );
  }
 
  # Call to the mailing function.
  &Mail( $name );
}

sub Mail {
  local($name)	= "@_";

  #
  # Did we specify that the results be mailed ?  If so - mail them !
  if ( $opt_m ) {
    return,	if ( ! defined $sysadmin || ! defined $MAILER ||
	! -e $MAILER );
    
    open ( EMAIL, "| $MAILER $sysadmin")	|| 
	die "Unable to open $MAILER to deliver mail.\n";
    print EMAIL <<"EOF";
Subject: Portmaster detail parser.

This file was generated by dopen.pl and as instructed, is being mailed to
you ($sysadmin).

EOF

    print EMAIL `cat '$LogFile\.$name'`;

    close( EMAIL );
  }
  return;
}

#
# The end routine. ( The _very_ last thing executed )
#
END { 
  if ( $opt_r ) {
    exec 'rotate.pl' || warn "'rotate.pl' not found.\n";
  }
}
