#!/usr/bin/perl -wT
######################################################################
#
# $Id: webjob-log-create-access-list,v 1.6 2012/01/07 08:01:20 mavrik Exp $
#
######################################################################
#
# Copyright 2006-2012 The WebJob Project, All Rights Reserved.
#
######################################################################
#
# Purpose: Create an access list based on entries in a log file.
#
######################################################################

use strict;
use File::Basename;
use Getopt::Std;

######################################################################
#
# Main Routine
#
######################################################################

  ####################################################################
  #
  # Punch in and go to work.
  #
  ####################################################################

  my ($sProgram);

  $sProgram = basename(__FILE__);

  ####################################################################
  #
  # Validation expressions.
  #
  ####################################################################

  my $sClientIdRegex = qq((?:[A-Za-z](?:(?:[0-9A-Za-z]|[_-](?=[^.]))){0,62})(?:[.][A-Za-z](?:(?:[0-9A-Za-z]|[_-](?=[^.]))){0,62}){0,127});
  my $sIpRegex = qq((?:\\d{1,3}\\.){3}\\d{1,3});
  my $sStatusRegex = qq(2\\d{2}); # Match any status code in the range [200-299].

  ####################################################################
  #
  # Get Options.
  #
  ####################################################################

  my (%hOptions);

  if (!getopts('f:', \%hOptions))
  {
    Usage($sProgram);
  }

  ####################################################################
  #
  # A filename, '-f', is required, and can be '-' or a regular file.
  #
  ####################################################################

  my ($sFileHandle, $sFilename);

  if (!exists($hOptions{'f'}))
  {
    Usage($sProgram);
  }
  else
  {
    $sFilename = $hOptions{'f'};
    if (!defined($sFilename) || length($sFilename) < 1)
    {
      Usage($sProgram);
    }
    if (-f $sFilename)
    {
      if (!open(FH, "< $sFilename"))
      {
        print STDERR "$sProgram: File='$sFilename' Error='$!'\n";
        exit(2);
      }
      $sFileHandle = \*FH;
    }
    else
    {
      if ($sFilename ne '-')
      {
        print STDERR "$sProgram: File='$sFilename' Error='File must be regular.'\n";
        exit(2);
      }
      $sFileHandle = \*STDIN;
    }
  }

  ####################################################################
  #
  # If there's any arguments left, it's an error.
  #
  ####################################################################

  if (scalar(@ARGV) > 0)
  {
    Usage($sProgram);
  }

  ####################################################################
  #
  # Loop over the input. Skip records that don't match the criteria.
  #
  ####################################################################

  my (%hAccessList);

  while (my $sLine = <$sFileHandle>)
  {
    $sLine =~ s/[\r\n]+$//;
    my ($sClientId, $sIp, $sStatus) = (split(/\s+/, $sLine))[3,4,11];
    next unless (defined($sClientId) && defined($sIp) && defined($sStatus));
    next unless ($sClientId =~ /^$sClientIdRegex$/ && $sIp =~ /^$sIpRegex$/ && $sStatus =~ /^$sStatusRegex$/);
    $hAccessList{$sClientId}{$sIp}++;
  }

  foreach my $sClientId (sort(keys(%hAccessList)))
  {
    my (@aIps) = ();
    foreach my $sIp (sort(keys(%{$hAccessList{$sClientId}})))
    {
      my $sCidr = $sIp . "/32";
      push(@aIps, $sCidr);
    }
    print "$sClientId=", join(",", @aIps), "\n";
  }

  ####################################################################
  #
  # Cleanup and go home.
  #
  ####################################################################

  close($sFileHandle);

  1;


######################################################################
#
# Usage
#
######################################################################

sub Usage
{
  my ($sProgram) = @_;
  print STDERR "\n";
  print STDERR "Usage: $sProgram -f {file|-}\n";
  print STDERR "\n";
  exit(1);
}


=pod

=head1 NAME

webjob-log-create-access-list - Create an access list based on entries in a log file.

=head1 SYNOPSIS

B<webjob-log-create-access-list> B<-f {file|-}>

=head1 DESCRIPTION

This utility processes entries in the specified log B<file> and
generates output that can be used to create an IP-based access list.
Not every entry in the log B<file> will be recognized as coming from a
valid client.  Only those clients that match a set of internally
defined regular expressions will be considered valid clients.
Basically, the client's ID and IP address need to be valid, and the
entry must have a status code in the range [200-299].

The format of the list is key/value pair as follows:

    <client_id> = <cidr>[,<cidr>]

=head1 OPTIONS

=over 4

=item B<-f {file|-}>

Specifies the name of the log file to process.  Typically, this file
is called nph-webjob.log.

=back

=head1 AUTHOR

Klayton Monroe

=head1 SEE ALSO

webjob(1)

=head1 LICENSE

All documentation and code are distributed under same terms and
conditions as B<WebJob>.

=cut
