#!/usr/local/bin/perl
#
# ftp-log-convert turns your wu-ftpd xfer log into
# a common log format for input to pwebstats
#
#                 Martin Gleeson, gleeson@unimelb.edu.au
#
# (c) Copyright the University of Melbourne, 1995
#
# This program is provided free of charge provided the Copyright
# notice remains intact. No warranty is made, either expressed or
# implied. USE AT YOUR OWN RISK.
#

sub usage {
        $usagescreen = <<USAGE;
ftp-log-convert:

Usage: ftp-log-convert -l <ftp logfile> -o <output file> [-v]

*  the ftp log is specified by the -l option

*  -v for verbose output, including progress bar.   [optional]

USAGE

        print STDERR $usagescreen;
        exit(0);
}

################################################################################

# use newgetopt library to parse the options
@options = ("l:s","o:s","v");

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

################################################################################
# Check the options from the conf file and/or the command line

if( ! $opt_l || ! $opt_o ){
        &usage;
}
if( ! -e $opt_l ) {
        print STDERR "Could not find log file: $opt_l\n";
        &usage;
}
$logfile = $opt_l;

if( $opt_v ) {  $verbose = 1;  }

################################################################################

$date_now=`date +"%I:%M %p, %A %B %e %Y"`; chop($date_now);
if( $verbose ) {
        printf STDERR "===================================================================\n";
        printf STDERR "ftp-log-convert started at $date_now.\n";
        printf STDERR "===================================================================\n\n";
}

&read_and_write_logfile($logfile);

$date=`date +"%I:%M %p, %A %B %e %Y"`;chop($date);
if( $verbose ) {
        printf STDERR "===================================================================\n";
        printf STDERR "ftp-log-convert finished at $date_now.\n";
        printf STDERR "===================================================================\n";
exit(0);
}

################################################################################

sub read_and_write_logfile
{
	$file=pop(@_);
	open(LOG_FILE,"$file");

	if( $verbose )
	{
		open(COUNT,"wc -l $file |");
		while( <COUNT> ){ chop; ($line_count) = /^\s+(\d+)\s+\S+$/;  }
		close(COUNT);
		$inc = sprintf "%d", ( $line_count / 50 );
		print STDERR "The logfile has $line_count entries.\n";
		print STDERR "Processing...\n";
		print STDERR "0%                     50%                      100%\n";
		print STDERR "|-----------------------|------------------------|\n";
		$counter=0;
	}
	open(OUT,"> $opt_o");

	$counter = 0;

	while(<LOG_FILE>)
	{
		if( $verbose )
		{
			$counter++;
			if( $counter >= $inc )
			{
				$counter = 0;
				if( $verbose ) { printf STDERR "\#"; }
			}
		}
		@line = split;
		next if ($#line != 16);
		next if ($line[12] ne "a");
		if( $line[2] =~ /^\d$/) { $line[2] = "0$line[2]"; }
		print OUT "$line[6] - - [$line[2]/$line[1]/$line[4]:$line[3] +????] \"GET $line[8] HTTP/1.0\" 200 $line[7]\n";
	}
	close(OUT);
	if( $verbose ) { printf STDERR "\nDone.\n\n"; }
}
# newgetopt.pl -- new options parsing
#
#
# Included here for bug fix purposes - Martin Gleeson
#
#
# SCCS Status     : @(#)@ newgetopt.pl	1.13
# Author          : Johan Vromans
# Created On      : Tue Sep 11 15:00:12 1990
# Last Modified By: Johan Vromans
# Last Modified On: Tue Jun  2 11:24:03 1992
# Update Count    : 75
# Status          : Okay

# This package implements a new getopt function. This function adheres
# to the new syntax (long option names, no bundling).
#
# Arguments to the function are:
#
#  - a list of possible options. These should designate valid perl
#    identifiers, optionally followed by an argument specifier ("="
#    for mandatory arguments or ":" for optional arguments) and an
#    argument type specifier: "n" or "i" for integer numbers, "f" for
#    real (fix) numbers or "s" for strings.
#    If an "@" sign is appended, the option is treated as an array.
#    Value(s) are not set, but pushed.
#
#  - if the first option of the list consists of non-alphanumeric
#    characters only, it is interpreted as a generic option starter.
#    Everything starting with one of the characters from the starter
#    will be considered an option.
#    Likewise, a double occurrence (e.g. "--") signals end of
#    the options list.
#    The default value for the starter is "-", "--" or "+".
#
# Upon return, the option variables, prefixed with "opt_", are defined
# and set to the respective option arguments, if any.
# Options that do not take an argument are set to 1. Note that an
# option with an optional argument will be defined, but set to '' if
# no actual argument has been supplied.
# A return status of 0 (false) indicates that the function detected
# one or more errors.
#
# Special care is taken to give a correct treatment to optional arguments.
#
# E.g. if option "one:i" (i.e. takes an optional integer argument),
# then the following situations are handled:
#
#    -one -two		-> $opt_one = '', -two is next option
#    -one -2		-> $opt_one = -2
#
# Also, assume "foo=s" and "bar:s" :
#
#    -bar -xxx		-> $opt_bar = '', '-xxx' is next option
#    -foo -bar		-> $opt_foo = '-bar'
#    -foo --		-> $opt_foo = '--'
#
# HISTORY 
# 2-Jun-1992		Johan Vromans	
#    Do not use //o to allow multiple NGetOpt calls with different delimeters.
#    Prevent typeless option from using previous $array state.
#    Prevent empty option from being eaten as a (negative) number.

# 25-May-1992		Johan Vromans	
#    Add array options. "foo=s@" will return an array @opt_foo that
#    contains all values that were supplied. E.g. "-foo one -foo -two" will
#    return @opt_foo = ("one", "-two");
#    Correct bug in handling options that allow for a argument when followed
#    by another option.

# 4-May-1992		Johan Vromans	
#    Add $ignorecase to match options in either case.
#    Allow '' option.

# 19-Mar-1992		Johan Vromans	
#    Allow require from packages.
#    NGetOpt is now defined in the package that requires it.
#    @ARGV and $opt_... are taken from the package that calls it.
#    Use standard (?) option prefixes: -, -- and +.

# 20-Sep-1990		Johan Vromans	
#    Set options w/o argument to 1.
#    Correct the dreadful semicolon/require bug.


#{   package newgetopt;
#    $debug = 0;			# for debugging
#    $ignorecase = 0;		# ignore case when matching options
#}

sub NGetOpt {

    @newgetopt'optionlist = @_;
    *newgetopt'ARGV = *ARGV;

    package newgetopt;

    local ($[) = 0;
    local ($genprefix) = "(--|-|\\+)";
    local ($argend) = "--";
    local ($error) = 0;
    local ($opt, $optx, $arg, $type, $mand, %opctl);
    local ($pkg) = (caller)[0];

    print STDERR "NGetOpt 1.13 -- called from $pkg\n" if $debug;

    # See if the first element of the optionlist contains option
    # starter characters.
    if ( $optionlist[0] =~ /^\W+$/ ) {
	$genprefix = shift (@optionlist);
	# Turn into regexp.
	$genprefix =~ s/(\W)/\\\1/g;
	$genprefix = "[" . $genprefix . "]";
	undef $argend;
    }

    # Verify correctness of optionlist.
    %opctl = ();
    foreach $opt ( @optionlist ) {
	$opt =~ tr/A-Z/a-z/ if $ignorecase;
	if ( $opt !~ /^(\w*)([=:][infse]@?)?$/ ) {
	    print STDERR ("Error in option spec: \"", $opt, "\"\n");
	    $error++;
	    next;
	}
	$opctl{$1} = defined $2 ? $2 : "";
    }

    return 0 if $error;

    if ( $debug ) {
	local ($arrow, $k, $v);
	$arrow = "=> ";
	while ( ($k,$v) = each(%opctl) ) {
	    print STDERR ($arrow, "\$opctl{\"$k\"} = \"$v\"\n");
	    $arrow = "   ";
	}
    }

    # Process argument list

    while ( $#ARGV >= 0 ) {

	# >>> See also the continue block <<<

	# Get next argument
	$opt = shift (@ARGV);
	print STDERR ("=> option \"", $opt, "\"\n") if $debug;
	$arg = undef;

	# Check for exhausted list.
	if ( $opt =~ /^$genprefix/ ) {
	    # Double occurrence is terminator
	    return ($error == 0) 
		if ($opt eq "$+$+") || ((defined $argend) && $opt eq $argend);
	    $opt = $';		# option name (w/o prefix)
	}
	else {
	    # Apparently not an option - push back and exit.
	    unshift (@ARGV, $opt);
	    return ($error == 0);
	}

	# Look it up.
	$opt =~ tr/A-Z/a-z/ if $ignorecase;
	unless  ( defined ( $type = $opctl{$opt} ) ) {
	    print STDERR ("Unknown option: ", $opt, "\n");
	    $error++;
	    next;
	}

	# Determine argument status.
	print STDERR ("=> found \"$type\" for ", $opt, "\n") if $debug;

	# If it is an option w/o argument, we're almost finished with it.
	if ( $type eq "" ) {
	    $arg = 1;		# supply explicit value
	    $array = 0;
	    next;
	}

	# Get mandatory status and type info.
	($mand, $type, $array) = $type =~ /^(.)(.)(@?)$/;

	# Check if the argument list is exhausted.
	if ( $#ARGV < 0 ) {

	    # Complain if this option needs an argument.
	    if ( $mand eq "=" ) {
		print STDERR ("Option ", $opt, " requires an argument\n");
		$error++;
	    }
	    if ( $mand eq ":" ) {
		$arg = $type eq "s" ? "" : 0;
	    }
	    next;
	}

	# Get (possibly optional) argument.
	$arg = shift (@ARGV);

	# Check if it is a valid argument. A mandatory string takes
	# anything. 
	if ( "$mand$type" ne "=s" && $arg =~ /^$genprefix/ ) {

	    # Check for option list terminator.
	    if ( $arg eq "$+$+" || 
		 ((defined $argend) && $arg eq $argend)) {
		# Push back so the outer loop will terminate.
		unshift (@ARGV, $arg);
		# Complain if an argument is required.
		if ($mand eq "=") {
		    print STDERR ("Option ", $opt, " requires an argument\n");
		    $error++;
		    undef $arg;	# don't assign it
		}
		else {
		    # Supply empty value.
		    $arg = $type eq "s" ? "" : 0;
		}
		next;
	    }

	    # Maybe the optional argument is the next option?
	    if ( $mand eq ":" && ($' eq "" || $' =~ /[a-zA-Z_]/) ) {
		# Yep. Push back.
		unshift (@ARGV, $arg);
		$arg = $type eq "s" ? "" : 0;
		next;
	    }
	}

	if ( $type eq "n" || $type eq "i" ) { # numeric/integer
	    if ( $arg !~ /^-?[0-9]+$/ ) {
		print STDERR ("Value \"", $arg, "\" invalid for option ",
			      $opt, " (number expected)\n");
		$error++;
		undef $arg;	# don't assign it
	    }
	    next;
	}

	if ( $type eq "f" ) { # fixed real number, int is also ok
	    if ( $arg !~ /^-?[0-9.]+$/ ) {
		print STDERR ("Value \"", $arg, "\" invalid for option ",
			      $opt, " (real number expected)\n");
		$error++;
		undef $arg;	# don't assign it
	    }
	    next;
	}

	if ( $type eq "s" ) { # string
	    next;
	}

    }
    continue {
	if ( defined $arg ) {
	    if ( $array ) {
		print STDERR ('=> push (@', $pkg, '\'opt_', $opt, ", \"$arg\")\n")
		    if $debug;
	        eval ('push(@' . $pkg . '\'opt_' . $opt . ", \$arg);");
	    }
	    else {
		print STDERR ('=> $', $pkg, '\'opt_', $opt, " = \"$arg\"\n")
		    if $debug;
	        #   eval ('$' . $pkg . '\'opt_' . $opt . " = \$arg;"); # here is the error!!!
			eval ('$' . $pkg . '\'opt_' . $opt . " = \"$arg\";");
	    }
	}
    }

    return ($error == 0);
}
1;
