package Guardian::Config;
use strict;
use warnings;

use Exporter qw(import);

our @EXPORT_OK = qw(CheckConfig UseConfig);

# The default config file which is used, if no one has been specified.
my $configfile = "/etc/guardian/guardian.conf";

# The maximum amount of chars, which a line in the configfile is allowed to contain.
my $maxlength = "64";

# Hash with default settings. They may be overwritten by settings of the config file.
my %defaults = (
	"LogLevel" => "info",
	"LogFacility" => "syslog",
	"BlockCount" => "3",
	"BlockTime" => "86400",
	"FirewallEngine" => "none",
);

#
## UseConfig configuration function.
#
## This function does the main work. It is responsible for calling the subfunction
## to read the given config file (or use the default one if none has been specified),
## and push the returned object to the validate subfunction. Finally the validated
## settings will be merged with the default ones (existing defaults will be overwritten).
#
sub UseConfig ($) {
	my $file = $_[0];

	# If not file has been specified, use the default one.
	unless ($file) {
		$file = $configfile;
	}

	# Call subfunction to get the settings from config file.
	# Store the options and values in a temporary hash.
	my %temp = &ReadConfig($file);

	# Validate config settings.
	my $error = &CheckConfig(\%temp);

	# As long, as no error message is returned, the config is valid.
	unless ($error) {
		# Merge hash with contains the default
		# and temporary config hash. If both hashes contains
		# the same keys, the keys+values of the first one (%defaults)
		# will be overwritten.
		my %config = (%defaults, %temp);

		# Return the final configuration hash.
		return %config;

	# If an error message is returned, exit and print the error message.
	} else {
		die "Invalid configuration: $error";
	}
}

#
## ReadConfig (configfile) function.
#
## This function is used to read a given configuration file and store the
## values into a hash which will be returned.
#
sub ReadConfig ($) {
	my $file = $_[0];

	# Hash to store the read-in configuration options and values.
	my %config = ();

	# Check if the configfile exists and is read-able.
	unless (-r "$file") {
			die "The given configfile ($file) does not exist, or is not read-able: $!";
 	}

	# Open the config file and read-in all configuration options and values.
	open(CONF, "$file") or die "Could not open $file: $!";

	# Process line by line.
	while (my $line = <CONF>) {
		# Skip comments.
		next if ($line =~ /\#/);

		# Skip blank  lines.
		next if ($line =~ /^\s*$/);

		# Remove any newlines.
		chomp($line);

		# Check line lenght, skip it, if it is longer than, the
		# allowed maximum.
		my $length = length("$line");
		next if ($length gt $maxlength);

		# Remove any whitespaces.
		$line=~ s/ //g;

		# Splitt line into two parts.
		my ($option, $value) = split (/=/, $line);

		# Add config option and value to the config hash.
		$config{$option} = $value;
	}

	# Close the config file.
	close(CONF);

	# Return the configuration hash.
	return %config;
}

#
## The CheckConfig function.
#
## This function is responsible to validate configure options which has
## to be passed as a hash. It will return an error message which provides some
## deeper details, if any problems have been detected.
#
sub CheckConfig (\%) {
	# Dereference the given hash-ref and store
	# them into a new temporary hash.
	my %config = %{ $_[0] };

	# If a BlockTime has been configured, check if the value is a natural number.
	if (exists($config{BlockTime})) {
		# Get the configured value for "BlockTime".
		my $value = $config{BlockTime};

		# Call subroutine for validation.
		my $error = &check_number("$value");

		# If the check fails, immediately return an error message.
		if ($error) {
			return "Invalid BlockTime: $error";
		}
	}

	# If a BlockCount has been configured, check if the value is a natural number.
	if (exists($config{BlockCount})) {
		# Get the configured value for "BlockCount".
		my $value = $config{BlockCount};

		# Call subroutine for validation.
		my $error = &check_number("$value");

		# If the check fails, immediately return an error message.
		if ($error) {
			return "Invalid BlockCount: $error";
		}
	}

	# Gather details about supported log levels.
	my %supported_loglevels = &Guardian::Logger::GetLogLevels();

	# Check if the configured log level is valid.
	unless (exists ($supported_loglevels{$config{LogLevel}})) {
		return "Invalid LogLevel: $config{LogLevel}";
	}

	# Check if an optional configured SocketOwner is valid.
	if (exists($config{SocketOwner})) {
		my ($user, $group) = split(/:/, $config{SocketOwner});

		# Get the ID for the given user name.
		my $uid = getpwnam($user) or return "The user $user does not exist.";

		# Get the ID for given group name.
		my $gid = getgrnam($group) or return "The group $group does not exist.";
	} 

	# The config looks good, so return nothing (no error message).
	return undef
}

#
## The check_number subroutine.
#
## This simple subroutine is used to check if a given string is numeric
## and contains a natural number which has to be greater than zero.
#
sub check_number ($) {
	my $input = $_[0];

	# Check if the input is a natural number.
	unless ($input =~ /^\d+$/) {
		return "$input is not a natural number";
	}

	# Check if the number is greater than zero.
	unless ($input gt "0") {
		return "$input has to be greater than zero";
	}

	# Input is okay, return no error message (nothing).
	return undef;
}

1;
