#!/usr/bin/perl
#$Id: RFCcontrol-ssl-server,v 1.9 2021/07/14 05:13:23 sqko Exp $
############################################################
############################################################
###                                                      ###
###   Originally designed by Karin Vels Hansen,          ###
###   Soeren Koch and Dorthe Lybye.                      ###
###                                                      ###
###   Impelemented by Soeren Koch                        ###
###                                                      ###
###   Copyringt by Soeren Koch January 2005 ->           ###
###                                                      ###
############################################################
############################################################

use strict;
use IO::Socket::INET;
use Sys::Hostname;
use Encode;
use Crypt::OpenSSL::Random;
use Crypt::OpenSSL::RSA;
use MIME::Base64;
use RFC::Header;
use RFC::Main;
use SemaforeFile;
my $pwdhost = get_cv('passwds','passwd_server');
my $VERSION = '$Revision: 1.9 $';
my $PROGRAM = $0;
our $debug = 0;
our $this_host = "localhost";
$this_host = hostname;
if (scalar(@ARGV) > 0)
{
    for(my $x = 0; $x < scalar(@ARGV);$x++)
    {
	if ($ARGV[$x] =~ /--version/)
	{
	    print "$PROGRAM is: $VERSION\n";
	    exit 0;
	}
	if ($ARGV[$x] =~ /--debug/)
	{
	    $debug = 1;
	}
	if ($ARGV[$x] =~ /--host/)
	{
	    $this_host = $ARGV[$x+1];
	}
    } 
}
if ($this_host eq $pwdhost || $pwdhost eq 'localhost')
{
    print "Exiting as this host is the password server host or system is stand-alone!\n";
    exit;
}
### Setup hash of functions ###
our %cmdlst = ();
$cmdlst{'debug'} = \&debug;
$cmdlst{'public_key'} = \&pubkey;
$cmdlst{'ping'} = \&ping;
$cmdlst{'exit'} = \&shutdown;
#### End hash ####

our $port      = &get_cv('passwds','passwd_server_port');
our $user      = "nobody";
$| = 1;
print "Using host $this_host and port $port\n" if ($debug);

# Get userid for $user. Abort if userid is zero (superuser) or
# non-existent.
unless ( our $uid = ( getpwnam($user) )[2] ) 
{
    die "Attempt to run server as non-existent or superuser\n";
}
my $server = IO::Socket::INET->new(
				  LocalAddr => $this_host,
				  LocalPort => $port,
				  Proto => 'tcp',
				  Listen => 10,
				  Reuse => 1,
				  );
die "Could not create socket $!\n" unless ($server);
my $ip = inet_ntoa(inet_aton($this_host));
print "$this_host has ip: $ip\n" if ($debug);

my $rsa = undef;
## Create RSA keypair and store keys in /home/ABFdb/ ###
my $ssl_lock = $RFC::Header::homedir.'ssl.lock';
my $pubfile = SemaforeFile->new($RFC::Header::homedir.'public.key',$ssl_lock);
my $prifile = SemaforeFile->new($RFC::Header::homedir.'secret.key',$ssl_lock);
my $known = SemaforeFile->new($RFC::Header::homedir.'known_hosts',$ssl_lock);
unless ($known->exist)
{
    $known->chmod(0666);
}
foreach ($pubfile,$prifile,$known)
{
    $_->debug($debug);
}
unless ($prifile->exist)
{
    my $rsa = Crypt::OpenSSL::RSA->generate_key(2048);
    $pubfile->writeline($rsa->get_public_key_string());
    $prifile->writeline($rsa->get_private_key_string());
    chmod 0640,$prifile->filename();
}
my $rsa_pub = Crypt::OpenSSL::RSA->new_public_key(join("\n",$pubfile->readlines()));
my $rsa_priv = Crypt::OpenSSL::RSA->new_private_key(join("\n",$prifile->readlines()));
## Setup hash for key management ###
my %keys = ($ip=>$rsa_pub);
&load_known_hosts();

# Deal with requests #
our $remote_host;
while (1) {

    # Grab next pending request
    #
    $remote_host = $server->accept();
    die "accept() error: $!\n" unless ($remote_host);
    my $client_address = $remote_host->peerhost();
    my $client_port = $remote_host->peerport();
    my $ltime = localtime;    
    print "$ltime: Connection from $client_address:$client_port\n" if ($debug);
    ### Process request ###
    &request($client_address);
}

exit;


sub load_known_hosts()
{
    my $state = 0;
    my @list = ();
    my $host_name = '';
    foreach my $line ($known->readlines())
    {
	if ($state)
	{
	    if ($line =~ /^\s*$/)
	    {
		$state = 0;
		$keys{$host_name} = Crypt::OpenSSL::RSA->new_public_key(join("\n",@list));
		next;
	    }
	    push @list,$line;
	    next;	
	}
	next if ($line =~ /^\s*$/);
	@list = ();
	$host_name = $line;
	$state = 1;
    }
}

sub request
{
    my $client = shift;
    # Read client request and get $path
    our @args = ();
    our $cmd = undef;
    my $str = '';
    my $line = <$remote_host>;
    while ($line && $line !~ /^\s*\n$/)
    {
	$str .= $line;
	$line = <$remote_host>;
    }
    chomp $str;
    ### Handle requests without encryption ###
    if ($str && $str =~ /public_key/)
    {
	### Send public key ###
	print "'$str'\n" if ($debug);
	print $remote_host $rsa_pub->get_public_key_string();
	shutdown($remote_host,1);
	return;
    }
    if ($str && $str =~ /is_ssl/)
    {
	### Send public key ###
	print "'$str'\n" if ($debug);
	print $remote_host 1;
	shutdown($remote_host,1);
	return;
    }
    if ($str && $str =~ /ping/)
    {
	### Send public key ###
	print "'$str'\n" if ($debug);
	print $remote_host ping();
	shutdown($remote_host,1);
	return;
    }
    ### Get public key of client if not already fetched ##
    if (!$keys{$client})
    {
	$known->lock_ex;
	### First reload known_host file and recheck ###
	&load_known_hosts();
	if (!$keys{$client})
	{	    
	    ## Apparently doing reverse check here causes deadlock!
	    ### If still no match, get key from host ###
#	    my $str = passwd_client($client,$port,'public_key');
#	    if ($str && $str =~ /RSA/)
#	    {
#		$keys{$client} = Crypt::OpenSSL::RSA->new_public_key($str);
#		$known->append($client,$str,'');
#	    }
	}
	$known->lock_un;
    }
    ### Decrypt and process request ###
    if ($str)
    {
	eval {
	    $str = $rsa_priv->decrypt(decode_base64($str));
	    ($cmd,@args) = split(/\t+/,$str);
	    if ($cmdlst{$cmd})
	    {
		my $ok = $cmdlst{$cmd}->(@args);
		print "'$cmd'\tResult: $ok\n" if ($debug);
		print $remote_host encode_base64($keys{$client}->encrypt($ok));
	    } 
	    else
	    {
		print "Unknown command: '$cmd'\n";
		print $remote_host encode_base64($keys{$client}->encrypt(-1));
	    }
	};
	if ($@)
	{
	    &errorlog($@);
	    warn $@;
	}
    }
    shutdown($remote_host,1);
}

sub ping
{
    return "$PROGRAM on ".hostname." listening on port $port";
}

sub pubkey
{
    return 0;
}

sub debug()
{
    $debug = !$debug;
    foreach ($pubfile,$prifile,$known)
    {
	$_->debug($debug);
    }
    return "Debug: $debug";
}

sub shutdown
{
    print "\n\nShutting down server!\n";
    print $remote_host "Shuttind down server!\n";
    exit;
}


