#!/usr/local/bin/perl ### # Project: pflogstats # Program: sqwmstats.pl # Description: Main program # # Copyright (C) 2002-2003 by Dr. Peter Bieringer # 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/perl5/site_perl/5.8.8/Pflogstats push @INC, "/usr/local/lib/perl5/site_perl/5.8.8/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; };