#!/usr/bin/perl -w
#
###
# Project:     pflogstats
# Program:     rsyncstats.pl
# Description: Main program for extract accounting information from rsync log file
#
#              Copyright (C) 2003-2003 by Dr. Peter Bieringer <pbieringer at aerasec dot de>
#               ftp://ftp.aerasec.de/pub/linux/postfix/pflogsumm/
#
# License:     GNU GPL v2
# CVS:         $Id: rsyncstats.pl,v 1.1 2003/10/20 13:14:37 rootadm Exp $
#
# See also following files: LICENSE, ChangeLog
###

###
# ChangeLog
#	0.01
#	 - copy from apachelogiostats.pl and adapt it
###
# ToDo
#	- timerange (set)
#	- implement "format"
###

use strict;
use Getopt::Long;

## Name and version
use vars qw{$release $progName};
$release = "0.01";
$progName = "rsyncstats.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-networking.pm";

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


# Local variables
my %accounting;
$accounting{'sent'} += 0;
$accounting{'rcvd'} += 0;

my @mainhelptext;
my $p_hook;
# Time range of logdata
use vars qw{$timemin $timemax};
my ($time);

# Local functions prototyping
sub print_rsync_stats();


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

	print STDERR "  Options from included modules:\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 $skip;
my ($pid, $ip, $rcvd, $sent, $date, $direction);

my %tracker;

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 rsync log

	# Todo: Datematching!!!!

	undef $pid; undef $ip; undef $rcvd; undef $sent; undef $date; undef $direction;

	# Loglines:
	# PID tracking for network:
	# 2003/10/01 05:00:01 [7831] rsync on server-rsync from client.domain.example (1.2.3.4)
	# Accounting line:
	# 2003/10/01 05:00:02 [7831] wrote 3914 bytes  read 121 bytes  total size 669824
	
	printf STDERR "DEBUG/rsync: line='" . $_ . "'\n" if ( $opts{'debug'} & 0x0040 );

	if ( /rsync: name lookup failed for /i ) {
		next;
	} elsif ( /rsync: forward name lookup for /i ) {
		next;
	} elsif ( /rsync: read error: /i ) {
		next;
	} elsif ( /rsync error: error in rsync protocol data stream /i ) {
		next;
	} elsif ( /max connections/i ) {
		next;
	} elsif ( ($date, $pid, $direction, $ip) = /^(\S+ \S+)\s+\[(\d+)\] rsync (on|to) .* from .*\s+\((\S+)\)$/ ) {
		# Get PID tracking line
	
		printf STDERR "DEBUG/rsync: ip='" . $ip . "' pid='" . $pid . "' date='" . $date . "'\n" if ( $opts{'debug'} & 0x0020 );

		if (defined $tracker{$pid}) {
			die "ERROR/rsync: pid duplication: " . $_;
		};

		# Fill information into tracker
		$tracker{$pid}->{'ip'} = $ip;
		$tracker{$pid}->{'date'} = $date;
		#$tracker{$pid}->{'direction'} = $direction;
	} elsif ( ($date, $pid, $sent, $rcvd) = /^(\S+ \S+)\s+\[(\d+)\] wrote (\d+) bytes\s+read (\d+) bytes.*$/ ) {
		# Get Accouting line

		$skip = 0;

		# Look for tracker information given
		if (defined $tracker{$pid}) {
			$ip = $tracker{$pid}->{'ip'};
			$date = $tracker{$pid}->{'date'};

			# Remove tracker entry
			undef $tracker{$pid};

			# Hook "testipaddress"
			for my $p_hook (keys %{$main::hooks{'testipaddress'}}) {
				if ( &{$main::hooks{'testipaddress'}->{$p_hook}} ($ip) != 0 ) {
					# excluded
					printf STDERR "DEBUG/rsync: excluded from accounting\n" if ($opts{'debug'} & 0x0010 ) ;
					$skip = 1;
					last;
				};
			};
		} else {
			# No tracker information, account always
			# Happen on direction: to
		};

		# Calculate Unixtime
		if (! ($date =~ /^(\d+)\/(\d+)\/(\d+) (\d+):(\d+):(\d+)$/)) {
			print STDERR "ERROR/rsync: line contains no valid date: $date\n";
		};
		$time = timelocal( $6, $5, $4, $3, $2, $1);

		# 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 ($skip == 0) {
			$accounting{'sent'} += $sent;
			$accounting{'rcvd'} += $rcvd;
		};
	} else {
		die "WARN/rsync: unrecognized log line: " . $_;
	};
};

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

&print_rsync_stats();

exit 0;

#### END

# statistics
sub print_rsync_stats() {
	print_headline("rsync logio accounting statistics", "default");

	print '='x75 . "\n";
	printf "%-50s: %6s\n", "", "BytesTraffic"; 
	print_timerange_normal();
	print '-'x75 . "\n";
	printf "%-50s: %9d     %9s\n",
			"received (requests)",
			$accounting{'rcvd'},
			$numberformat{$opts{'numberformat'}}->format_bytes($accounting{'rcvd'});
	printf "%-50s: %9d     %9s\n",
			"sent (data)",
			$accounting{'sent'},
			$numberformat{$opts{'numberformat'}}->format_bytes($accounting{'sent'});
	print '-'x75 . "\n";
	printf "%-50s: %9d     %9s\n", "Total",
		$accounting{'rcvd'} + $accounting{'sent'},
		$numberformat{$opts{'numberformat'}}->format_bytes($accounting{'rcvd'} + $accounting{'sent'});
	print '='x75 . "\n";

	print "\n";

	return 0;
};


syntax highlighted by Code2HTML, v. 0.9.1