#!/usr/local/bin/perl
#
#  Copyright (c) 2004, SWITCH - Teleinformatikdienste fuer Lehre und Forschung
#  All rights reserved.
#
#  Redistribution and use in source and binary forms, with or without
#  modification, are permitted provided that the following conditions are met:
#
#   * Redistributions of source code must retain the above copyright notice,
#	 this list of conditions and the following disclaimer.
#   * Redistributions in binary form must reproduce the above copyright notice,
#	 this list of conditions and the following disclaimer in the documentation
#	 and/or other materials provided with the distribution.
#   * Neither the name of SWITCH nor the names of its contributors may be
#	 used to endorse or promote products derived from this software without
#	 specific prior written permission.
#
#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
#  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
#  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
#  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
#  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
#  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
#  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
#  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
#  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
#  POSSIBILITY OF SUCH DAMAGE.
#
#  $Author: peter $
#
#  $Id: Nfcomm.pm 59 2006-08-10 17:47:53Z peter $
#
#  $LastChangedRevision: 59 $
package Nfcomm;

use strict;

# BEGIN { $ENV{PATH} = '/usr/ucb:/bin' }

use Socket;
use Carp;
use POSIX 'setsid';
use POSIX ":sys_wait_h";
use Sys::Syslog;

use Log;
use NfProfile;

my %CMD_lookup = ( 
	'query'		=> 	{ 'run'	=>	\&NfProfile::QueryProfiles},
	'frontend'	=> 	{ 'run'	=>	\&NfSen::QueryFrontends},
	'list'		=> 	{ 'run'	=>	\&NfProfile::ListProfile},
	'add'		=> 	{ 'run'	=>	\&NfProfile::AddProfile},
	'delete'	=> 	{ 'run'	=>	\&NfProfile::DeleteProfile},
	'du'		=> 	{ 'run'	=>	\&NfProfile::DiskUsage},
	'globals'   =>  { 'run' =>  \&GetGlobals},
	'modify'	=> 	{ 'run'	=>	\&NfProfile::ModifyProfile},
	'nfdump'	=> 	{ 'run'	=>	\&RunNfdump},
	'rebuild'	=> 	{ 'run'	=>	\&NfProfile::RebuildProfile},
	'expire'	=> 	{ 'run'	=>	\&NfProfile::ExpireProfile},
	'reconfig'	=> 	{ 'run'	=>	\&Nfsources::Reconfig},
);

# my $EOL = "\015\012";
my $EOL = "\012";

sub spawn;  # forward declaration

my $waitedpid = 0;
my $done = 0;

sub SIG_CHILD {
	my $child;
	while (($waitedpid = waitpid(-1,WNOHANG)) > 0) {
		syslog('debug', "comm child $waitedpid terminated" . ($? ? " with exit $?" : ''));
	}
	$SIG{CHLD} = \&SIG_CHILD;  # loathe sysV
} # End of SIG_CHILD

sub SIG_DIE {
	my $why = shift;
	syslog('err', "PANIC Comm Server dies: $why");
} # End of SIG_DIE

sub GetGlobals {
	my $socket  = shift;
	my $opts	= shift;

#	print $socket "\$VARDIR	 = \"$NfConf::VARDIR\";\n";
#	print $socket "\$DOCURL	 = \"/nfsen/doc\";\n";
#	print $socket "\$PROFILESTATDIR = \"$NfConf::PROFILESTATDIR\";\n";
#	print $socket "\$PROFILEDATADIR = \"$NfConf::PROFILEDATADIR\";\n";
	print $socket "\$SUBDIRLAYOUT = \"$NfConf::SUBDIRLAYOUT\";\n";
#	print $socket "\$FRONTEND_PLUGINDIR = \"$NfConf::FRONTEND_PLUGINDIR\";\n";
#	print $socket "\$LIBEXECDIR = \"$NfConf::LIBEXECDIR\";\n";

	print $socket "OK nfdump completed.\n";
} # End of GetGlobals

sub RunNfdump {
	my $socket 	= shift;
	my $opts 	= shift;

	my $args = $$opts{'args'};
	my @lines;
	local $SIG{CHLD} = 'DEFAULT';
	print $socket ".run nfdump $args\n";
	my $pid = open(NFDUMP, "$NfConf::PREFIX/nfdump $args 2>&1|");
	if ( !$pid ) {
		my $err = "ERR nfdump run error: $!\n";
		print $socket $err;
		return;
	}
	print $socket ".pid: $pid\n";
	while ( <NFDUMP> ) {
		print $socket $_;
	}
	close NFDUMP;

	print $socket "OK nfdump completed.\n";

} # End of RunNfdump

sub Setup_Server {
	# in case we want INET sockets:
	my $port = shift;

	my $server;

	if ( 1 ) {
		# UNIX socket
		if ( !socket($server, PF_UNIX, SOCK_STREAM, 0) ) {
			$Log::ERROR = $!;
			return undef;
		}
		my $socket_path = "$NfConf::VARDIR/run/nfsen.comm";
		unlink $socket_path;
		my $uaddr = sockaddr_un($socket_path);
	
		my $ok = bind($server, $uaddr);
			if ( !$ok ) {
			$Log::ERROR = $!;
			close $server;
			return undef;
		}
		chmod 0664, $socket_path;

	} else {
		# TCP Internet socket
		my $proto_tcp = getprotobyname('tcp');
		if ( !socket($server, PF_UNIX, SOCK_STREAM, $proto_tcp) ) {
			$Log::ERROR = $!;
			return undef;
		}
		my $ok = setsockopt($server, SOL_SOCKET, SO_REUSEADDR, pack("l", 1));
		if ( !$ok ) {
			$Log::ERROR = $!;
			close $server;
			return undef;
		}
	
		$ok = bind($server, sockaddr_in($port, INADDR_ANY));
		if ( !$ok ) {
			$Log::ERROR = $!;
			close $server;
			return undef;
		}
	}

	listen($server,SOMAXCONN);

	$Log::ERROR = undef;

	$SIG{TERM} 		= sub { $done = 1; };
	$SIG{CHLD} 		= \&SIG_CHILD;
	# $SIG{CHLD} 		= 'DEFAULT';
	$SIG{'__DIE__'} = \&SIG_DIE;

	return $server;

} # End of Setup_Server

sub spawn {
	my $coderef = shift;

	unless (@_ == 0 && $coderef && ref($coderef) eq 'CODE') {
		confess "usage: spawn CODEREF";
	}

	my $pid;
	if (!defined($pid = fork)) {
		print STDERR "comm server: cannot fork: $!";
		return;
	} elsif ($pid) {
		syslog('info', "comm server started: $pid");
		return; # I'm the parent
	}
	# else I'm the child -- go spawn

	open(STDIN,  "<&Client")   || die "can't dup client to stdin";
	open(STDOUT, ">&Client")   || die "can't dup client to stdout";
	## open(STDERR, ">&STDOUT") || die "can't dup stdout to stderr";
	exit &$coderef();

} # End of spawn

sub CmdDecode {
	my $cmd = shift;

	syslog('debug', "Cmd Decode: $cmd");
	if ( $cmd eq '' ) {
		print "OK I'm here", $EOL;
		return 0;
	}
	my ($command, $args) = $cmd =~ /^\s*([^\s]+)\s*(.*)/;
	if ( !defined $command ) {
		print "ERR Failed to decode '$cmd'", $EOL;
		return 1;
	}
print ".Command is '$command' args: '$args'", $EOL;
	$command = lc $command;
	if ( $cmd eq 'quit' ) {
		print "OK Bye Bye", $EOL;
		return 1;
	}
	$cmd = $CMD_lookup{$command}{'run'};
	if ( !defined $cmd ) {
		print "ERR Command unknown: '$command'", $EOL;
		return 0;
	} else {
		my %opts;
		ArgDecode($args, \%opts);
foreach my $key ( keys %opts ) {
	my $value = $opts{$key};
	print ".Key: '$key', value: '$value'", $EOL;
}
		my $socket = *STDOUT;
		&$cmd($socket, \%opts);
	}
	return 0;

} # End of CmdDecode

sub ArgDecode {
	my $argstr = shift;
	my $optref = shift;

	$$optref{'quiet'} = 1;
	my @arglist = split /\s*;\s*/, $argstr;
	foreach my $arg ( @arglist ) {
		my ($key, $value) = split/\s*=\s*/, $arg;
		$key   =~ s/^\s+//;
		$value =~ s/\s+$//;
		$$optref{$key} = $value;
	}

} # End of ArgDecode

sub RunServer {
	my $server = shift;

	my $paddr;
	for($waitedpid = 0; ($paddr = accept(Client,$server)) || $waitedpid; $waitedpid = 0, close Client) {

		next if $waitedpid and not $paddr;
#		my($iport,$iaddr) = sockaddr_in($paddr);
#		my $name = gethostbyaddr($iaddr,AF_INET);

		syslog('debug', "connection on UNIX socket ");

		spawn sub {
			$|=1;
			my $timeout;
			my $quit = 0;
	
			print "220 ", time(), " nfsend v0.1 ready ", $EOL;
			while ( !$quit && !$done ) {
				my $input;
				eval {
					local $SIG{'__DIE__'} = 'DEFAULT';
					local $SIG{ALRM} = sub { die; };
					alarm 10;
					$input = <STDIN>;
					alarm 0;
				};
				if ($@) {
					print "OK Timeout", $EOL;
					syslog('debug', "connection timeout");
					$quit = 1;
				} else {
					if ( !defined $input ) {
						$quit = 1;
					} else {
						$input =~ s/$EOL$|\n$//;
						$quit = CmdDecode($input);
					}
				}
			}
			# system("echo hallo");
		} # End of anonymous sub
		;

	}

} # end of RunServer

sub Close_server {
	my $server = shift;
	close $server;
} # End of Close_server

1;

