#!/usr/bin/perl -w

###
# Project:     pflogstats
# Program:     sqwmstats.pl
# Description: Main program
#
#              Copyright (C) 2002-2003 by Dr. Peter Bieringer <pbieringer at aerasec dot de>
#               ftp://ftp.aerasec.de/pub/linux/postfix/pflogsumm/
#
# License:     GNU GPL v2
# CVS:         $Id: sqwmstats.pl,v 1.11 2005/04/26 16:04:34 peter Exp $
#
# See also following files: LICENSE, ChangeLog
###

###
# ChangeLog
#	0.01
#	 - split-off related code from non-modular version
#	0.02
#	 - bring to work
#	0.03
#	 - adjustments for external network check module
#       0.04
#        - make Perl 5.0 compatible
#	0.05
#	 - account only request with valid return codes
#	 - fix bugs in user rewriting on sqwebmail login
#	 - add support for timerange (get)
#	 - add support for addresmappingtable
#	0.06
#	 - add network exclusion support
#	0.07
#	 - replace number format function call
###
# ToDo
#	- timerange (set)
#	- use show_users
#	- implement "format"
#	- implement fallback accounting (request size)
###

use strict;
use Getopt::Long;

## Name and version
use vars qw{$release $progName};
$release = "0.07";
$progName = "sqwmstats.pl";


## Define global variables

# option handling
use vars qw{%options %opts};

$options{'help|h|?'}  = \$opts{'help'};
$options{"version"}  = \$opts{'version'};

# module hooks
use vars qw{%hooks};

# Number formats
use vars qw{%numberformat};

## Module loader
# 1st: look into current directory
push @INC, ".";

# 2nd: look into /usr/local/lib/pflogstats
push @INC, "/usr/local/lib/pflogstats";

# 3rd: look into /usr/lib/pflogstats
push @INC, "/usr/lib/pflogstats";

# General
require "pflogstats-common-support.pm";
require "pflogstats-extensions-addressmapping.pm";
require "pflogstats-extensions-networking.pm";

## Print options (debug)
#for my $key (keys %options) {
#       print $key . "\n";
#};
#exit 0;


# Local variables
my %accounting_sqwebmail;
my @mainhelptext;
my $p_hook;
# Time range of logdata
use vars qw{$timemin $timemax};
my ($time);
my %monthNums = qw(
    Jan  0 Feb  1 Mar  2 Apr  3 May  4 Jun  5
    Jul  6 Aug  7 Sep  8 Oct  9 Nov 10 Dec 11);


# Local functions prototyping
sub print_sqwm_stats();


## Help function
sub print_help() {
	print "$progName $release\n\n";

	print STDERR "  Options from included modules (new style):\n\n";

	## Hook 'help'
	for my $p_hook (sort keys %{$hooks{'help'}}) {
		my $helpstring = &{$hooks{'help'}->{$p_hook}};
		print STDERR "    Options from module '" . $p_hook . "':";
		print STDERR $helpstring . "\n";
	};
};

## Hook 'early_begin'
for my $p_hook (keys %{$hooks{'early_begin'}}) {
	&{$hooks{'early_begin'}->{$p_hook}};
};


## Get options
my $ret = GetOptions(%options);

if (! $ret ) {
	print_help();
	exit 1;
};

# Print help or version
if(defined($opts{'help'})) {
	print_help();
	exit 0;
};
if(defined($opts{'version'})) {
	print "$progName $release\n";
	exit 0;
};


## Hook 'checkoptions'
for $p_hook (keys %{$hooks{'checkoptions'}}) {
	&{$hooks{'checkoptions'}->{$p_hook}};
};

## Hook 'beforemainloopstarts'
for $p_hook (keys %{$hooks{'beforemainloopstarts'}}) {
	&{$hooks{'beforemainloopstarts'}->{$p_hook}};
};

print "DEBUG: start parsing logfile\n" if ($opts{'debug'});

## Start parsing logfile #################################################
my ($user, $ip, $rcvd, $sent, $returncode, $request, $url, $date, $size);
my $skip;

while(<>) {
	chomp;
	$_ =~ s/^M$//g; # Remove trailing CR
	$~ =~ s/^[[:space:][:cntrl:]]+$//g; # Remove spaces and ctrl chars only

	next if (length($_) == 0); # skip empty lines


	# Parsing web log for sqwebmail

	# Todo: Datematching!!!!

	undef $user; undef $ip; undef $rcvd; undef $sent; undef $returncode; undef $url; undef $date; undef $size;

	# Logline: 1.2.3.4 - peter@domain.example [04/Jun/2003:11:42:46 +0200] "GET /webmail/cgi-bin/sqwebmail/login/peter%40domain.example.authdaemon/01362C3A300354FF66A540CB6D4BC929/1054719691?folder=INBOX&form=quickadd&pos=15&newname= HTTP/1.1" 200 6701 "https://server.domain.example/webmail/cgi-bin/sqwebmail/login/peter%40domain.example.authdaemon/01362C3A300354FF66A540CB6D4BC929/1054719691" "Opera/7.11 (Linux 2.4.20-13.7. i686; U)  [en]" IN=1327 OUT=9296

	# Get content
	#printf STDERR "DEBUG: ACC_SQWEBMAIL: line: " . $_ . "\n" if ( $opts{'debug'} & 0x0020 ) ;
	($ip, $user, $date, $request, $url, $returncode, $size) = /^([^\s]+)\s+\-\s+([^\s]+)\s+\[(.*)\]\s+\"(GET|HEAD|PUT|POST) ([^"]*)\"\s+(\d+)\s+(\d+|\-)\s+/;
	#printf STDERR "DEBUG: ACC_SQWEBMAIL: returncode: " . $returncode . "\n" if ( $opts{'debug'} & 0x0020 ) ;

	if (! defined $request) {
		# Skip undefined requests
		next;
	};

	if ((! defined $returncode) || ($returncode >= 400)) {
		# Skip errors
		next;
	};


	# Calculate Unixtime
	if (! ($date =~ /^(\d+)\/(.*)\/(\d+):(\d+):(\d+):(\d+) /)) {
		print STDERR "ERROR while parsing date: $date\n";
	};
        $time = timelocal( $6, $5, $4, $1, $monthNums{$2}, $3);

        # Catch min/max times for late timerange display
        if (! defined $timemin || ! defined $timemax ) {
                # initial values
                if (! defined $timemin) { $timemin = $time };
                if (! defined $timemax) { $timemax = $time };
        } else {
                # get min/max
                if    ($time < $timemin) { $timemin = $time; }
                elsif ($time > $timemax) { $timemax = $time; };
        };


	if ( /\s+IN=([0-9]+).*$/ ) {
		$rcvd = $1;
	};
	if ( /\s+OUT=([0-9]+).*$/ ) {
		$sent = $1;
	};

	if ((! defined $sent) && (! defined $rcvd)) {
		# logio values without any prefix tokens at the end of the log line
		if ( /([0-9]+)\s+([0-9]+)$/ ) {
			$rcvd = $1;
			$sent = $2;
		};
	};

	if ((! defined $sent) && (! defined $rcvd)) {
		# use request size, we still have nothing else
		if ($size =~ /^[0-9]+$/) {
			$sent = $size;
			$rcvd = 0;
		};
	};

	if ( ! (defined $user && defined $ip && defined $rcvd && defined $sent ) ) {
		# not a proper accounting line
		print STDERR "DEBUG: ACC_SQWEBMAIL: not a proper line\n" if ( $opts{'debug'} & 0x0010 ) ;
		next;
	};

	# Get URL
	if (defined $request && defined $url ) {
		#print STDERR "DEBUG: ACC_SQWEBMAIL/URL: $url \n" if ( $opts{'debug'} & 0x0040 ) ;

		# Extract user
		if ($url =~ /sqwebmail\/login\/(.*)\.authdaemon\//o) {
			if ((defined $1) && ($1 ne "")) {
				print STDERR "DEBUG: ACC_SQWEBMAIL: user replaced: " . $user . " -> " if ( $opts{'debug'} & 0x0040 ) ;
				$user = $1;
				# Replace %xx tags
				$user =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
				print STDERR $user . "\n" if ( $opts{'debug'} & 0x0040 ) ;
			};
		} elsif ($url !~ /\/webmail\//o) {
			next;
		};
	};

	if (! defined $user ) { next; };
	if ( $user eq "-" ) { next; };

	# Append domain to user
	if (! ( $user =~ /\@/ ) ) {
		$user .= "@" . $opts{'mydomainname'};
	};

	## Hook 'modifyaddress'
	for $p_hook (keys %{$hooks{'modifyaddress'}}) {
		$user = &{$hooks{'modifyaddress'}->{$p_hook}} ($user);
	};

	printf STDERR "DEBUG: ACC_SQWEBMAIL: user=%s ip=[%s] rcvd=%s sent=%s\n", $user, $ip, $rcvd, $sent if ($opts{'debug'} & 0x0010 ) ;

	# Hook "testipaddress"
	$skip = 0;
	for my $p_hook (keys %{$main::hooks{'testipaddress'}}) {
		if ( &{$main::hooks{'testipaddress'}->{$p_hook}} ($ip) != 0 ) {
			# excluded
			printf STDERR "DEBUG: ACC_SQWEBMAIL: excluded from accounting\n" if ($opts{'debug'} & 0x0010 );
			$skip = 1;
			last;
		};
	};

	if ($skip == 0) {
		$accounting_sqwebmail{'user'}->{$user} += $rcvd + $sent;
		$accounting_sqwebmail{'domain'}->{extract_domain($user)} += $rcvd + $sent;
	};
};

print "DEBUG: end parsing logfile\n" if ($opts{'debug'});

print_sqwm_stats();


### END

# Sqwebmail statistics
sub print_sqwm_stats() {
	my $sum;
	my %accounting;
	my $p_hash;

	print_headline("SqWebMail accounting statistics", "default");

	$p_hash = $accounting_sqwebmail{'user'};
	%accounting = %$p_hash;
	$sum = 0;

	print '='x75 . "\n";
	printf "%-50s: %6s\n", "User", "BytesTraffic"; 
	print_timerange_normal();
	print '-'x75 . "\n";
	for my $user (sort keys %accounting) {
		if ( ! defined $accounting{$user} ) { $accounting{$user} = 0; };
		
		$sum += $accounting{$user};

		printf "%-50s: %9d     %9s\n",
			substr ($user, 0, 50),
			$accounting{$user},
			format_number($accounting{$user});
	};
	print '-'x75 . "\n";
	printf "%-50s: %9d     %9s\n", "Total",
		$sum,
		format_number($sum);
	print '='x75 . "\n";

	print "\n";

	$p_hash = $accounting_sqwebmail{'domain'};
	%accounting = %$p_hash;
	$sum = 0;
	print '='x75 . "\n";
	printf "%-50s: %6s\n", "Domain", "BytesTraffic"; 
	print_timerange_normal();
	print '-'x75 . "\n";
	for my $domain (sort keys %accounting) {
		if ( ! defined $accounting{$domain} ) { $accounting{$domain} = 0; };
		
		$sum += $accounting{$domain};

		printf "%-50s: %9d     %9s\n",
			substr ($domain, 0, 50),
			$accounting{$domain},
			format_number($accounting{$domain});
	};
	print '-'x75 . "\n";
	printf "%-50s: %9d     %9s\n", "Total",
		$sum,
		format_number($sum);
	print '='x75 . "\n";
	return 0;
};


syntax highlighted by Code2HTML, v. 0.9.1