package ACL::Regex;
# This is a little package used to handle ACLs in a friendly,
# sysadmin like regex enabled manner.

use strict;

use vars qw( $VERSION @ISA @EXPORT @EXPORT_OK);
require Exporter;

@EXPORT = qw( new parse_acl_from_file match );
$VERSION = '0.0001_01';

sub new {
my $type = shift;
	bless {}, $type;
}

# This variable stores all of the required fields
# for the ACL.  If a required field is not in a
# given ACL or action, then it is autogenerated
# with the defaults (enabled).
my @required = qw(
	account
	action
	ip
	group
	dow
	time
);

sub generate_required( $$ ){

	my ( $acl, $required_file ) = @_;

	open FD, "<$required_file" or die("Cannot open $required_file: $!\n" );
	while( <FD> ){
		next if /^#/;
		if( /(\S+?)=(\S+)/ ){
			my @a = split( /,/, $2 );	
			$acl->{req}->{$1} = \@a;
		}
	}
	return ($acl);
}

sub sanitize_acl ($$) {
	my ( $self, $acl ) = @_;

	# Split up the ACL
	my %hash = $acl =~ /(\S+?)=\[([^\[^\]].+?)\]/g;

	my @acl_array;
	my @local_required = sort( keys %hash );

	my $action = $hash{action};

	return -1,'ERR','Action not defined' 
		unless defined $hash{action};

	#return 0,'WARN','Action not defined in required fields'
	#	unless defined $self->{req}->{$action};
	if( defined $self->{req}->{$action} ){
		#print "Using pre-defined requirements for $action from file\n";
		@local_required = @{$self->{req}->{$action}};
	}

	# Regenerate the hash
	for my $key ( sort ( @local_required ) ) {
        	unless ( defined $hash{$key} ) {
			# Uh-oh, it wasn't specified
			my $acl_element = "$key=\\\[(.*?)\\\]";
			push ( @acl_array, $acl_element );
		} else {
			my $acl_element = "$key=\\\[$hash{$key}\\\]";
			push ( @acl_array, $acl_element );
		}
	} ## end for my $key ( sort ( @required...
	return 0,'OK',join ( " ", @acl_array );
} ## end sub sanitize_acl ($)

sub sanitize_action ($$) {
	my ( $self, $acl ) = @_;

	# Split up the ACL
	my %hash = $acl =~ /(\S+?)=\[([^\[^\]].+?)\]/g;

	my @acl_array;
	my @local_required = sort( keys %hash );
	
	my $action = $hash{action};
	return -1,'ERR',"Action [$action] not defined"
		unless defined $hash{action};

	#return 0,'WARN','Action not defined in required fields'
	#	unless defined $self->{req}->{$action};
	if( defined $self->{req}->{$action} ){
			@local_required = @{$self->{req}->{$action}};
	}

	my $action = $hash{action};

	# Regenerate the hash
	for my $key ( sort ( @local_required ) ) {
		unless ( defined $hash{$key} ) {
			# Uh-oh, it wasn't specified
			my $acl_element = "$key=\[]";
			push ( @acl_array, $acl_element );
		} else {
			my $acl_element = "$key=\[$hash{$key}\]";
			push ( @acl_array, $acl_element );
		}
	} ## end for my $key ( sort ( @required...
	return 0,'OK',join ( " ", @acl_array );
} ## end sub sanitize_action ($)

sub parse_acl_from_file( $$ ) {
	my ( $self, $hash ) = @_;

	die ( "Please give a filename as an option!\n" )
		unless defined $hash->{Filename};

	open FD, "<$hash->{Filename}"
		or die ( "Cannot open $hash->{Filename}: $!\n" );

	ENTRY: while ( <FD> ) {
		chomp;
		s/^.*?(\s*#.*)//;    # Get rid of comments
		next ENTRY if /^$/;
		if( /^\/(.+?)\/\s+?(.*)/ ){
			my ($regex, $comment) = ($1,$2);
    			my ($rc,$rs,$sanitized) = $self->sanitize_acl( $regex );
    			next ENTRY
    				if $rc < 0;
    			$self->{message}->{"$sanitized"} = $comment;
    			push ( @{ $self->{ACL} }, $sanitized );
    		}
	} ## end while ( <FD> )
	close( FD );
	return( $self );
} ## end sub parse_acl_from_file( $$ )

sub match ($$) {
	my ( $self, $action ) = @_;

	my ($rc,$rs,$sanitized) = $self->sanitize_action( $action );

	return( $rc,$rs,'')
		if $rc < 0;

	for my $regex ( @{ $self->{ACL} } ) {
		return ( 1, $regex, $self->{message}->{"$regex"} ) if ( $sanitized =~ /$regex/i );
	}

	return ( 0, '', '' );
} ## end sub match ($$)

1;
# vim: set ai ts=4 nu:

__END__

=head1 NAME

ACL::Regex - Process arbitrary events with regular expressions.

=head1 SYNOPSIS

   use ACL::Regex;

   # Instantiate a reject object
   my $reject_acl = ACL::Regex->new->
           generate_required( 'required.txt' )->
           parse_acl_from_file( { Filename => "acl.reject.txt" } );

   while( <> ){
           chomp;
           my ($rc,$regex,$comment) = $reject_acl->match( $_ );
           if( $rc ){
                  print "\t! Rejected against $regex\n";
                  print "\t: Reason: $comment\n";
                  next;
           }
   }

=head1 DESCRIPTION

ACL::Regex allows you to parse a series of actions, key/value pairs through
an object containing a series of regular expressions.

=head2 OBJECT ORIENTED INTERFACE

The module is written with an object oriented interface.  There is no function
interface to choose from.  To streamline many of the initial operations of the
object, many of the initialization methods return the object reference, allowing
the programmer to chain the commands together.

=over 4

=item B<:generate_required>

This method pulls in a I<:file> containing a series of required keys.

=item B<:sanitize_acl>

This method re-sorts the keys in alphabetical order.

=item B<:sanitize_action>

This method accomplishes the same thing as B<:sanitize_acl>
but for actions.

=item B<:parse_acl_from_file>

This method takes a hash as a parameter:

  parse_acl_from_file( { Filename => "acl.reject.txt" } )

=item B<:match>

This method takes an action as a parameter, and returns a triplet
containing the return code, matched regex, and any comment associated
with the regex.
