package Guardian::Parser;
use strict;
use warnings;

use Exporter qw(import);

our @EXPORT_OK = qw(IsSupportedParser Parser);

# This hash contains all supported parsers and which function
# has to be called to parse messages in the right way.
my %logfile_parsers = (
	"httpd" => \&message_parser_httpd,
	"snort" => \&message_parser_snort,
	"ssh" => \&message_parser_ssh,
);

#
## The main parsing function.
#
## It is used to determine which sub-parser has to be used to
## parse the given message in the right way and to return if
## any action should be performed.
#
sub Parser ($$) {
        my ($parser, @message) = @_;

	# If no responsible message parser could be found, just return nothing.
	unless (exists($logfile_parsers{$parser})) {
		return;
	}

	# Call responsible message parser.
	my @actions = $logfile_parsers{$parser}->(@message);

	# In case an action has been returned, return it too. 
	if (@actions) {
		# Return which actions should be performed.
		return @actions;
	}

	# Return undef, if no actions are required.
	return undef;
}

#
## IsSupportedParser function.
#
## This very tiny function checks if a given parser name is available and
## therefore a supported parser.
#
## To perform these check, the function is going to lookup if a key in the
## hash of supported parsers is available
#
sub IsSupportedParser ($) {
	my $parser = $_[0];

	# Check if a key for the given parser exists in the hash of logfile_parsers.
	if(exists($logfile_parsers{$parser})) {
		# Found a valid parser, so return nothing.
		return 1;
	}

	# Return "False" if we got here, and therefore no parser
	# is available.
	return;
}

#
## The Snort message parser.
#
## This subfunction is responsible for parsing sort alerts and determine if
## an action should be performed.
#
## XXX Currently the parser only supports IPv4. Add support for IPv6 at a
## later time.
#
sub message_parser_snort(@) {
	my @message = @_;

	# The name of the parser module.
	my $name = "SNORT";

	# Variable to store the grabbed IP-address.
	my $address;

	# Default returned message in case no one could be grabbed
	# from the snort alert.
	my $message = "An active snort rule has matched and gained an alert.";

	# A snort alert contains multiple lines, loop through all lines
	# to parse the complete alert.
	foreach my $line (@message) {
		# Check Priority Level and skip the alert if it is to low.
		#if ($line =~ /.*\[Priority: (\d+)\].*/) {
		#	return unless($1 < $priority);
		#}

		# Search for a line like xxx.xxx.xxx.xxx -> xxx.xxx.xxx.xxx
		if ($line =~ /(\d+\.\d+\.\d+\.\d+)+ -\> (\d+\.\d+\.\d+\.\d+)+/) {
			# Store the grabbed IP-address.
			$address = $1;
		}

		# Search for a line like xxx.xxx.xxx.xxx:xxx -> xxx.xxx.xxx.xxx:xxx
		elsif ($line =~ /(\d+\.\d+\.\d+\.\d+):\d+ -\> (\d+\.\d+\.\d+\.\d+):\d+/) {
			# Store the obtained IP-address.
			$address = $1;
		}

		# Obtain the reported reason.
		if ($line =~ /.*msg:\"(.*)\".*/) {
			# Store the extracted message.
			$message = $1;
		}
	}

	# Check if at least the IP-address information are obtained from the
	# provided alert.
	if ($address) {
		# Return the extracted values.
		return "$address $name $message";
	}

	# If we got here, the alert could not be parsed correctly, return nothing.
	return;
}

#
## The SSH message parser.
#
## This subfunction is used for parsing and detecting different attacks
## against the SSH service.
#
sub message_parser_ssh (@) {
	my @message = @_;
	my @actions;

	# The name of the parser module.
	my $name = "SSH";

	# Variable to store the grabbed IP-address.
	my $address;

	# Variable to store the parsed event.
	my $message;

	# Loop through all lines, in case multiple one have
	# been passed.
	foreach my $line (@message) {
		# Check for failed password attempts.
		if ($line =~/.*sshd.*Failed password for (.*) from (.*) port.*/) {
			# Store the grabbed IP-address.
			$address = $2;

			# Set event message.
			$message = "Possible SSH-Bruteforce Attack for user: $1.";
		}

		# This should catch Bruteforce Attacks with enabled preauth
		elsif ($line =~ /.*sshd.*Received disconnect from (.*):.*\[preauth\]/) {
			# Store obtained IP-address.
			$address = $1;

			# Set event message.
			$message = "Possible SSH-Bruteforce Attack - failed preauth.";
		}

		# Check if at least the IP-address information has been extracted.
		if (defined ($address)) {
			# Add the extracted values and event message for the computed
			# event to the actions array.
			push(@actions, "count $address $name $message");
		}
	}

	# If any actions are required, return the array.
	if (@actions) {
		return (@actions);
	}

	# If we got here, the provided message is not affected by any filter and
	# therefore can be skipped. Return nothing (False) in this case.
	return;
}

#
## The HTTPD message parser.
#
## This subfunction is used for parsing and detecting different attacks
## against a running HTTPD service.
#
sub message_parser_httpd (@) {
	my @message = @_;
	my @actions;

	# The name of the parser module.
	my $name = "HTTPD";

	# Variable to store the grabbed IP-address.
	my $address;

	# Variable to store the parsed event.
	my $message;

	# Loop through all lines, in case multiple one have
	# been passed.
	foreach my $line (@message) {
		# This will catch brute-force attacks against htaccess logins (username).
		if ($line =~ /.*\[error\] \[client (.*)\] user(.*) not found:.*/) {
			# Store the grabbed IP-address.
			$address = $1;

			# Set event message.
			$message = "Possible WUI brute-force attack, wrong user: $2.";
		}

		# Detect htaccess password brute-forcing against a username.
		elsif ($line =~ /.*\[error\] \[client (.*)\] user(.*): authentication failure for.*/) {
			# Store the extracted IP-address.
			$address = $1;

			# Set event message.
			$message = "Possible WUI brute-force attack, wrong password for user: $2.";
		}

		# Check if at least the IP-address information has been extracted.
		if (defined ($address)) {
			# Add the extracted values and event message to the actions array.
			push(@actions, "count $address $name $message");
		}
	}

	# If any actions are required, return the array.
	if (@actions) {
		return @actions;
	}

	# If we got here, the provided message is not affected by any filter and
	# therefore can be skipped. Return nothing (False) in this case.
	return;
}

1;
