package Guardian::Socket;
use strict;
use warnings;

use Exporter qw(import);

our @EXPORT_OK = qw(Server Client Message_Parser RemoveSocketFile);

use IO::Socket::UNIX qw( SOCK_STREAM SOMAXCONN );

# The path and filename to the UNIX socket.
my $socketpath = "/run/guardian";
my $socketfile = "/run/guardian/guardian.sock";

# Hash with all supported socket commands and events which should
# be returned by the parser.
my %supported_commands = (
	'block' => 'block',
	'unblock' => 'unblock',
	'flush' => 'flush',
	'reload' => 'reload',
	'reload-ignore-list' => 'reload-ignore-list',
	'logrotate' => 'logrotate',
);

#
## Socket server function.
#
## It is used to create the server component to provide an IPC
## mechanism for guardian.  The server function creates an UNIX
## socket.
#
sub Server ($) {
	my $socket_owner = shift;

	# If the path for the socketfile does not exist, try to
	# create it.
	unless (-d "$socketpath") {
		mkdir("$socketpath") or die "Could not create $socketpath: $!";
	}

	# Delete an existing socket file.
	if (-e "$socketfile") {
		unlink $socketfile
		or die "Could not release existing socket file: $!";
	}

	# Create a new UNIX socket.
	my $server = IO::Socket::UNIX->new(
		Local => $socketfile,
		Listen => SOMAXCONN,
		Type => SOCK_STREAM,
	) or die "Could not create socket: $!";

	
	# Translate the given user/group name into ID values.
	if (defined ($socket_owner)) {
		# Splitt provided user/group into single arguments.
		my ($username, $groupname) = split(/:/, $socket_owner);

		# Get the ID for the given user name.
		my $uid = getpwnam($username) or die "Could not get an UID for $username: $!";

		# Get the ID for given group name.
		my $gid = getgrnam($groupname) or die "Could not get a GID for $groupname: $!";

		# Set new ownership for the socket file.
		chown($uid, $gid, "$socketfile") or die "Could not change ownership to ($uid:$gid) for $socketfile: $!";
	}

	# Return the server object.
	return $server;
}

#
## A client for the socket server.
#
## This function provides a simple client to connect to the server socket
## and send messages through it to a running guardian process.
#
sub Client($) {
	my ($message) = @_;

	# Create the client and connect to the server socket.
	my $client = IO::Socket::UNIX->new(
		Type => SOCK_STREAM,
		Peer => $socketfile,
	) or die "Could not connect to socketfile: $!\n";

	# Remove any newline.
	chomp($message);

	# Send the message through the socket.
	print $client "$message\n";
}

#
## The Socket message parser.
#
## This subfunction is responsible for parsing any data or messages
## which have been recieved through the server socket.
#
sub Message_Parser ($) {
	# Splitt the message into single parts.
	# The first part contains the command, the second
	# one an optional argument.
	my @message = split(/ /, $_[0]);
	my ($command, $optarg, $unsupported) = @message;

	# Currently only socket messages with two arguments (command and
	# the additional argument which is required for some commands)
	# are supported. If a third argument is passed, the message
	# is invalid and the parser returns false.
	if ($unsupported) {
		return undef;
	}

	# Check if we got a supported command.
	if (exists $supported_commands{$command}) {
		# Check an optional argument has been given.
		if ($optarg) {
			# The message is valid, return the event and
			# the optional argument.
			return "$supported_commands{$command} $optarg Socket User-requested action.";
		} else {
			# Only return the event for the recieved command.
			return "$supported_commands{$command}";
		}
	}

	# If we got here, the command was not supported or something different
	# went wrong. In such cases the function will return undef (False).
	return undef;
}

#
## RemoveSocketFile function.
#
## A tiny function which just removes an existing Socket file.
#
sub RemoveSocketFile () {
	# Check if a socketfile exists.
	if (-e $socketfile) {
		# Delete the socket file.
		unlink($socketfile);
	}
}

1;
