package Guardian::IPtables;
use strict;
use warnings;

use Exporter qw(import);

our @EXPORT = qw(DoBlock DoUnblock DoFlush);

# Array of supported block actions.
my @supported_actions = ("DROP", "REJECT");

# The path to the iptables executeable.
my $iptables = "/usr/sbin/iptables";

# The used firewall chain.
my $chain = "INPUT";

#
## The DoBlock subroutine.
#
## This subroutine is called, when a given address should be locked by
## using IPtables.
#
## Guardian is using the "append" option from IPtables, which will add the new rule
## to the end of the chain to prevent from possible race-conditions when adding/deleting
## rules at the same time.
#
sub DoBlock (@) {
	my ($address, $action) = @_;

	# If no action has been given, default to "DROP".
	unless($action) {
		$action = "DROP";
	}

	# Check if the given action is supported.
	my $error = &_check_action($action);

	# Abort and return the recieved error.
	if ($error) {
		return $error;
	}

	# Call iptables to block the given address.
	system("$iptables --wait -A $chain -s $address -j $action");
}

#
## The DoUnblock subroutine.
#
## This subroutine can be used to delete all firewall rules (unblock)
## of a previously blocked address.
#
## To do this a subroutine will be called, which is collecting all rule
## positions in the firewall chain for the given address, which returns
## them in reversed order. This list of rules will be deleted one-by-one
## so multiple entries (if present) for the address will be deleted.
#
## This approach also eliminates the exact rule argument processing again
## for dropping it. So it is not neccessary to know the additional arguments
## like firewall action (DROP, REJECT) etc. 
#
sub DoUnblock ($) {
	my ($address) = @_;

	# Get rulepositions for the specified address.
	my @rules = &_get_rules_positions_by_address($address);

	# Loop through the rules array and drop the firewall rules.
	foreach my $rule (@rules) {
		# Call iptables to delete the rule.
		system("$iptables --wait -D $chain $rule");
	}
}

#
## The DoFlush subroutine.
#
## Call this subroutine to entirely flush the IPtables chain.
#
sub DoFlush () {
	# Call iptalbes and flush the chain.
	system("$iptables --wait -F $chain");
}

#
## Get rules subroutine.
#
## This subroutine is used to get the rule position of the active
## firewall rules for a given address. Those position will be collected
## and returned in reversed order. 
#
sub _get_rules_positions_by_address ($) {
	my ($address) = @_;

	# Array to store the rule positions.
	my @rules;

	# Call iptables and list all firewall rules.
	open(RULES, "$iptables --wait -L $chain -n -v --line-numbers |");

	# Read input line by line.
	foreach my $line (<RULES>) {
		# Skip descriptive line.
		next if ($line =~ /^Chain/);
		next if ($line =~ /^ pkts/);

		# Generate array, based on the line content
		# (seperator is a single or multiple space's)
		my @comps = split(/\s{1,}/, $line);
		my ($pos, $pkts, $bytes, $target, $prot, $opt, $in, $out, $source, $destination) = @comps;

		# Compare the current source address with the given one.
		# If they are equal, the rule position will be added to the
		# rules array.
		if ($address eq $source) {
			push(@rules, $pos);
		}
	}

	# Reverse the array.
	my @reversed_rules = reverse @rules;

	# Return the reversed array.
	return @reversed_rules;
}

#
## The _check_action function.
#
## This private function is used to check if the given action is supported by
## the firewall engine.
#
sub _check_action ($) {
	my $action = $_[0];

	# Check if the recieved action is part of the supported_actions array.
	foreach my $item (@supported_actions) {
		# Exit the loop and return "nothing" if we found a match.
		if($item eq $action) {
			return;
		}
	}

	# If we got here, the given action is not part of the array of supported
	# actions. Return an error message.
	return "Unsupported action: $action";
}

1;
