#!/usr/bin/perl -w

# pppusage
#
# - get ppp usage information by grep'ing ppp log
# - save the data in a Berkeley DB file
# - spit out some ppp usage summaries
#
# should work with user-ppp on NetBSD, FreeBSD and OpenBSD
# and with the standard pppd on Linux
#
# Holger Weiss <holger@jhweiss.de>
# http://code.jhweiss.de/pppusage/

# ------------------------------------------------------------------------
# Copyright (c) 2003, Holger Weiss
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
# OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# ------------------------------------------------------------------------

# -----> Modules
#
use strict;
use Getopt::Std;
use PPPUsage::Parse;
use PPPUsage::Res2Term;
use PPPUsage::Lib qw(usage version);

# -----> Variables
#
use vars qw($opt_b $opt_c $opt_f $opt_h $opt_q $opt_t $opt_v);
use vars qw($me $linux $gzip $logfiles $datafile $configured);

my $conffile = '/etc/pppusagerc';  # set at make time
   $logfiles = `ls /var/log/messages*`;      # ppp syslog files
   $datafile = '/tmp/pppusage.db';           # database file

my @pppfiles;

my $val      =  0;                           # exit value
   $gzip     =  'gzip';                      # gzip binary
   $me       =  $0;                          # that's me
   $me       =~ s!^.*/!!g;                   # my basename
   $linux    =  $^O eq "linux" ? 1 : 0;      # Linux or BSD?

# -----> Command line and pppusagerc
#
do "$conffile"              if -r "$conffile";
do "$ENV{HOME}/.pppusagerc" if -r "$ENV{HOME}/.pppusagerc";

getopts('bcf:hqt:v');

if (@ARGV) { undef @pppfiles; } else { @pppfiles = split(/\s/,$logfiles); }

foreach (@ARGV) {

	die "$me: Can't parse $_: $!\n" unless -r $_;
	push(@pppfiles,$_);
}

firstrun() if (!-r $datafile && defined($configured) && $configured == 0);
 upgrade() if ( -r $datafile && defined($configured) && $configured == 0);

# -----> Main
#
       usage(0)                    if ( $opt_h);
       version()                   if ( $opt_v);
       $datafile = $opt_f          if ( $opt_f);
       $opt_q    = 1               if ( $opt_t);
       $opt_c    = $opt_q = 1      if (!$opt_c && !$opt_q);
$val = parse($datafile, @pppfiles) if ( $opt_c);
$val = results2term($datafile)     if ( $opt_q);

# -----> Bye
#
exit $val;

# ----------------------------------------------------------------------
# Documentation
#

=head1 NAME

pppusage - generate PPP connection summaries

=head1 SYNOPSIS

B<pppusage> [B<-bcq>] [B<-f>S< >I<datafile>] [B<-t>S< >I<yyyy>[I<mm>[I<dd>]][-I<yyyy>[I<mm>[I<dd>]]]] [I<logfile>...]

B<pppusage> [B<-hv>]

=head1 DESCRIPTION

B<pppusage> summarizes average and total transfer volumes, number of
connections and average and total online time for all logged PPP
connections.  The data is collected by reading the I<logfile>s which
contain the ppp[d] messages.  Multiple I<logfile>s (that is, the active
and the rotated ppp[d] logfiles) can be read in order to make sure that
new ppp[d] entries aren't missed when the relevant logfile gets rotated
before B<pppusage> has seen the entries.  However, please see
L<"CAVEATS"> below!  B<pppusage> saves the relevant data in I<datafile>
(a Berkeley DB database) which can be updated automatically on a regular
basis by using I<cron(5)> or something similar.

=head1 OPTIONS

By default, B<pppusage> does all four steps: It parses the PPP syslog
files, saves the data, reads the saved data and spits out the summaries
for all available data.  You can split up building the database on the
one hand and querying it on the other by using some of the following
options.

=over 6

=item B<-b>

Don't use ANSI colors and attributes for the B<pppusage> output.

=item B<-c>

Parse the PPP syslog files, update the Berkeley DB database file (or
create it, if it doesn't exist), and exit.  This option is especially
useful if you want the database updated automatically in the background
on a regular basis.  For example:

   05 14 * * * nice /usr/bin/core_perl/pppusage -c > /dev/null

This I<crontab(5)> entry would update the database once a day (at 2:05
pm).

=item B<-f> I<datafile>

Specify the database file that should be used by B<pppusage>.

=item B<-q>

Query the database: Read the database file and spit out summaries.  If
the time range is not specified with B<-t>, all available data will be
processed.

=item B<-h>

Print out a quick overview of all available options and exit.

=item B<-t> I<yyyy>[I<mm>[I<dd>]][-<yyyy>[I<mm>[I<dd>]]]

Specify the time period for a query.  This can be done either by
specifying a single year, month or day, or by specifying a time range.
A time range is used by specifying two dates, seperated with a `-'.
The order in which the two dates are given is irrelevant.  Any
combination of days, months or years may be specified.  Implies B<-q>.

=item B<-v>

Print version information and exit.

=back

=head1 CONFIGURATION

The configuration file used by B<pppusage> simply is a perl file that is
read on startup.  Both the logfiles to parse and the database file to
use may be adjusted by setting the following variables.  However,
specifying them on the command line will overwrite these settings.  The
default values are shown in brackets.

=over 6

=item B<$logfiles> = I<'logfile...'>;  [`ls /var/log/messages*`]

The ppp[d] B<logfile>s to parse.  A space delimited list must be used to
specify multiple files.  Note that if you specify the files to parse
directly (as opposed to using the output of a command like ls), you must
use normal "quotation marks", not `backticks`!

=item B<$datafile> = I<'datafile'>;    ['/tmp/pppusage.db']

The file that is used by B<pppusage> to store it's data.  Normally it
makes sense to set this to a different path, especially if the /tmp
directory gets wiped out every now an then (by, for example, a cron(5)
job or on boot).

=item B<$configured> = I<0|1>;         [0]

Forget about this one.  We needed it to make sure that those of you who
upgraded from older releases of B<pppusage> have set B<$logfiles>
correctly by forcing them to open the configuration file and setting
B<$configured> to 1 ;-)  Actually, you can simply delete this variable
completely.  It will be removed from future releases of B<pppusage>.

=back

=head1 EXAMPLES

    pppusage

Parses the syslog files that ppp[d] logs it's messages to, saves the
relevant data to a Berkeley DB file and writes summaries regarding all
available data to STDOUT.

    pppusage -t 2003

Searches the database file for all PPP connections logged in 2003 and
writes the results to STDOUT.

    pppusage -t 200305

Searches the database file for all PPP connections logged in May 2003
and writes the results to STDOUT.

    pppusage -t 2003-200303

Searches the database file for all PPP connections logged in the first
quarter of 2003 and writes the results to STDOUT.

    pppusage -t 20030915-20030931

Searches the database file for all PPP connections logged in the second
half of September 2003 and writes the results to STDOUT.

    pppusage -b -f /tmp/pppusage.2002.db -t 20020501

Searches the database file /tmp/pppusage.2002.db for all PPP connections
logged at May 1st, 2002 and spits out the results without using any ANSI
colors or attributes.

=head1 FILES

F<~/.pppusagerc>
    User configuration file.

F</etc/pppusagerc>
    System-wide configuration file.

=head1 CAVEATS

Since B<pppusage> tries to get multiple logfiles in the right order
simply by sorting them by their last modification time, feeding multiple
logfiles to B<pppusage> will most probably produce a broken database if
they were modified after they have been rotated.

Even worse: Updating an I<existing> database with logfiles that contain
data which is I<older> than the already parsed data won't work either.
This would again result in a broken database.

In both cases the database file must be deleted and recreated.

=head1 BUGS

Most probably, there are some.  Please let me know if you find them!

=head1 SEE ALSO

ppp(8), pppd(8), syslog(8), syslog.conf(5), cron(5), crontab(1), crontab(5)

=head1 AUTHOR

Holger Weiss E<lt>holger@jhweiss.deE<gt>

=cut

# ----------------------------------------------------------------------
# Set $configured to 1 if it's a fresh install
#

sub firstrun {

	my $newconf;

	open(CONF,"+< $conffile")
		or die "$me: Please set \$configured = 1; in /etc/pppusagerc!\n";

	flock(CONF,2)
		or die "$me: Please set \$configured = 1; in /etc/pppusagerc!\n";

		while(<CONF>) {
			s/\$configured = 0;/\$configured = 1;/;
			$newconf .= $_;
		}

	seek(CONF,0,0)              or die "$me: Can't reset position in $conffile: $!\n";
	print CONF $newconf         or die "$me: Can't update $conffile: $!\n";
	truncate(CONF,tell(CONF))   or die "$me: Can't truncate $conffile: $!\n";
	close(CONF)                 or die "$me: Can't close $conffile: $!\n";
}

# ----------------------------------------------------------------------
# Warn user if upgrading from < 0.2.3
#

sub upgrade {

	open(MESSAGE, "|more")
		or die "$me: Please read UPGRADE and set \$configured = 1; in /etc/pppusagerc!\n";

	print MESSAGE <<"EOF";

*** STOP! ***

In pppusage versions prior to 0.2.3, the syslog files to parse were
specified directly in the script (by editing the variable \@logfiles).
In order to make *future* upgrades easier, the files to parse are now
specified in

   /etc/pppusagerc

The new default setting is to parse /var/log/messages and all of the
rotated files (such as messages.0.gz etc.):

   \$logfiles = `ls /var/log/messages*`;

What to do now? Depends on which logfiles were used in the past:

o *If*, in the past, /var/log/messages and *all* of the rotated
  messages.* files were specified, everything is fine.

o *If*, in the past, /var/log/messages and only *some* of the rotated
  files were parsed, you *must* change the variable \$logfiles in the
  pppusagerc if you want to keep your old pppusage database file. For
  example:

     \$logfiles = "/var/log/messages /var/log/messages.0.gz";

  Note that if you don't specify exactly the same files as before, the
  pppusage data *will* be messed up, the results will be *wrong*. Also
  note that if you specify files this way (instead of using `ls') you
  *must* use normal "quotation marks", *not* the `backticks` that are
  used by default.

o *If* some other files were parsed, you must of course change
  \$logfiles. For example:

     \$logfiles = "/var/log/syslog /var/log/syslog.0.gz";

o *If*, in the past, only *one* file was parsed (as opposed to also
  parsing at least *some* of the rotated files), you should move your
  old pppusage.db out of the way and let pppusage create a new one (by
  reading more than one file). This actually has nothing to do with this
  upgrade, I'm just taking the chance to warn you once more. It can
  easily happen that you miss ppp[d] entries when the logfile get's
  rotated before pppusage has seen the newest entry! Thus, the pppusage
  results would *not* be correct.

As soon as you have set \$logfiles appropriately by editing pppusagerc,
you can set the variable \$configured to 1 (in pppusagerc) in order to
get rid of this message.

Sorry for the inconvenience. If you have any questions, comments, or bug
reports, feel free to drop me an e-mail.

Holger Weiss <holger\@jhweiss.de>

EOF

	close(MESSAGE);
	exit 1;
}
