#
# Shorewall -- /usr/share/shorewall/action.IfEvent
#
# Perform an Action based on a Event
#
# Parameters:
#
# Event        - Must start with a letter and be composed of letters, digits,
#                '-', and '_'.
# Action       - Anything that can appear in the ACTION column of a rule.
# Duration     - Duration in seconds over which the event is to be tested.
# Hit Count    - Number of packets seen within the duration -- default is 1
# Src or Dest  - 'src' (default) or 'dst'. Determines if the event is
#                associated with the source address (src) or destination
#                address (dst)
# Command      - 'check' (default) 'reset', or 'update'. If 'reset',
#                the event will be reset before the Action is taken.
#                If 'update', the timestamp associated with the event will
#                be updated and the action taken if the time limit/hitcount
#                are matched.
#                If '-', the action will be taken if the limit/hitcount are
#                matched but the event's timestamp will not be updated.
#
#                If a duration is specified, then 'checkreap' and 'updatereap'
#                may also be used. These are like 'check' and 'update'
#                respectively, but they also remove any event entries for
#                the IP address that are older than <duration> seconds.
# Disposition  - Disposition for any event generated.
#
# For additional information, see http://www.shorewall.net/Events.html
#
###############################################################################
#                                         DO NOT REMOVE THE FOLLOWING LINE
###############################################################################
#ACTION		SOURCE		DEST		PROTO	DPORT	SPORT

DEFAULTS -,ACCEPT,-,1,src,check,-

?begin perl

use Shorewall::Config qw(:DEFAULT :internal);
use Shorewall::Chains;
use Shorewall::Rules;
use strict;

my ( $event, $action, $duration, $hitcount, $destination, $command, $disposition ) = get_action_params( 7 );

fatal_error "An event name is required"           unless supplied $event;
fatal_error "Invalid event name ($event)"         unless $event =~ /^[a-zA-z][-\w]*$/;

if ( supplied $duration ) {
    fatal_error "Invalid time limit ($duration)"  unless $duration =~ /^\d+$/;
    $duration = "--seconds $duration ";
} else {
    $duration = '';
}

fatal_error "Invalid hit count ($hitcount)"       unless $hitcount =~ /^\d+$/;
fatal_error "Invalid Src or Dest ($destination)"  unless $destination =~ /^(?:src|dst)$/;

my $srcdst = $destination eq 'src'? '--rsource' : '--rdest';

our $commands_defined;

#
# Can't 'use constant' here
#
my ( $UPDATE_CMD, $CHECK_CMD, $RESET_CMD, $REAP_OPT, $TTL_OPT ) = ( 1, 2, 4, 8, 16 );

my %command = ( check =>  $CHECK_CMD,
		update => $UPDATE_CMD,
		reset  => $RESET_CMD
	      );

my %commandopts = ( 
		    reap   => $REAP_OPT,
		    ttl    => $TTL_OPT
		  );

my @command = split(':', $command);

$command = $command{shift @command} || 0;

fatal_error "Command must be 'check', 'update' or 'reset" unless $command & ( $CHECK_CMD | $UPDATE_CMD | $RESET_CMD); 

for ( @command ) {
    fatal_error "Invalid command option ($_)" unless $commandopts{$_};
    if ( $command & $commandopts{$_} ) {
	warning_message "Duplicate command ($_)";
    } else {
	$command |= $commandopts{$_};
    }
}

my $duplicate;

set_action_disposition( $disposition) if supplied $disposition;
set_action_name_to_caller;

require_capability 'RECENT_MATCH', 'Use of events', 's';

if ( $command & $REAP_OPT ) {
    require_capability( 'REAP_OPTION', q(The 'reap' option), 's' );
    fatal_error "${command}reap requires a time limit" unless $duration;
    $duration .= '--reap ';
}

$duration .= '--rttl ' if $command & $TTL_OPT;

if ( ( $targets{$action} || 0 ) & NATRULE ) {
    perl_action_helper( "${action}-", "-m recent --rcheck ${duration}--hitcount $hitcount" );
    $action = 'ACCEPT';
}

if ( $command & $RESET_CMD ) {
    require_capability 'MARK_ANYWHERE', '"reset"', 's';

    print "Resetting....\n";
    
    my $mark = $globals{EVENT_MARK};
    #
    # The event mark bit must be within 32 bits
    #
    fatal_error "The mark layout does not permit resetting of events" unless $mark & 0xffffffff;
    #
    # Reset the event mark bit
    #
    perl_action_helper( 'INLINE', '-j MARK --and-mark '. in_hex( (~ $mark ) & 0xffffffff ) );

    $mark = in_hex $mark;
    #
    # Mark the packet if event is armed
    #
    perl_action_helper( 'INLINE', "-m recent --rcheck ${duration}--hitcount $hitcount --name $event $srcdst -j MARK --or-mark $mark" );
    #
    # if the event is armed, remove it and perform the action
    #
    perl_action_helper( $action , "-m mark --mark $mark/$mark -m recent --remove --name $event" );
} elsif ( $command & $UPDATE_CMD ) {
    perl_action_helper( $action, "-m recent --update ${duration}--hitcount $hitcount --name $event $srcdst" );
} else {
    perl_action_helper( $action, "-m recent --rcheck ${duration}--hitcount $hitcount --name $event $srcdst" );
}

1;

?end perl