#!/usr/bin/perl -w 
#
# Zymonic Business Process and Information Management System
# Copyright Zednax Limited 2008 -
# For Authors and Changelog see the subversion history
use strict;

package main;

# Library path
BEGIN
{
    use Zymonic::Utils qw(clean debug death_handler get_array rethrow_exception);
    use sigtrap 'handler' => \&global_handler;

    sub global_handler
    {
        debug( "SIGNAL: $_[0]", 'error' );
        exit(0) if $_[0] eq 'TERM';
        death_handler;
    }
    no warnings;
    $SIG{CHLD}  = 'IGNORE';
    $SIG{CHILD} = 'IGNORE';    # Second instance for Solaris compatibility
    use warnings;
}

my $fh;
open $fh, ">&STDOUT";
$Zymonic::Utils::debugfile = $fh;
$|                         = 1;

# Script 'globals'
my @servers = ();

# Modules
use Zymonic::Decryptor::Server;
use Zymonic::Config;
use Zymonic::Decryptor::Message;
use Module::Reload;
use Zymonic;
$Zymonic::session = '';
$Zymonic::system  = '';

# Clean path
$ENV{PATH} = "/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/sbin:/bin:/usr/bin:/sbin:/usr/sbin";

$Zymonic::system = $ARGV[0] || '';
my $hostname = $ARGV[1] || '';
my $ports    = $ARGV[2] || '1';
my $nofork   = $ARGV[3] || 0;

unless ( ( $ports =~ /^\d+$/ ) and ( -d '/etc/zymonic/' . $Zymonic::system ) and $hostname )
{
    print "usage: zymonic_decryptor.pl [decryptor system] [hostname] [ports to use (integer)]\n";
    exit(0);
}

foreach my $i ( 1 .. $ports )
{
    my $sc_pid;

    unless ($nofork)
    {
        die "Can't create server child process: $!" unless defined( $sc_pid = fork() );
    }

    if ( !$sc_pid )
    {

        # Child process...
        server_child( $Zymonic::system, $hostname, $sc_pid );

        exit(0) if defined $sc_pid;

    }
    else
    {
        push( @servers, $sc_pid );

        # Sleep to avoid port allocation conflicts
        sleep(1);
    }
}

# Wait for all children to exit - although this should never happen...
foreach (@servers) { waitpid( $_, 0 ); }

exit(0);

sub server_child
{
    my $system   = shift;
    my $hostname = shift;
    my $sc_pid   = shift;

    # setup the field factory
    $Zymonic::field_factory = Zymonic::FieldFactory->new();

    # Load a decryptor object
    $Zymonic::ZCONFIG{$system} = Zymonic::Config->new( system_name => $system, config_dir => "/etc/zymonic", );
    my $dso = Zymonic::Decryptor::Server->new(
        config   => $Zymonic::ZCONFIG{$system},
        db       => $Zymonic::ZCONFIG{$system}->{DB},
        hostname => $hostname,
    );

    # Wait for connections...
    while (1)
    {
        eval {
            my $connection = $dso->accept;
            my $log_id     = '';
            $log_id = $dso->connection_start( $connection->peerhost );

            my $c_child = undef;
            $c_child = fork() unless $nofork or $dso->nofork();

            # If we can't fork we will have to handle the connection in 'blocking' mode -
            # hence we don't bother checking for child pid == 0 here...
            if ($c_child)
            {

                # Do something here?
            }
            else
            {

                # Handle the connection
                connection_child( $log_id, $dso, $connection, $c_child );
                $connection->close();

                # Only exit if we're really a child.
                if ( defined $c_child )
                {
                    $dso->disconnect;
                    $Zymonic::ZCONFIG{$system}->{DB}->close_db_connection;
                    exit(0);
                }
            }
            1;
        }
          or do
        {
            death_handler( $@, '', 'return' );
        };

    }
}

sub connection_child
{
    my $log_id     = shift;
    my $dso        = shift;
    my $connection = shift;
    my $c_child    = shift;

    eval {

        # Re-establish the DB connection etc.
        $dso->postfork($c_child);

        my $in = '';
        eval {
            # Get the data
            $in = $dso->receive($connection);

            # Pass to the message handler
            Module::Reload->check;
            my $mh = Zymonic::Decryptor::Message->new(
                xmldef           => { class => $in->{messagetype} },
                zname            => $in->{messagetype},
                decryptor_server => $dso
            );
            my $response = $mh->respond($in);

            # return the response.
            print $connection $dso->freeze_and_pack($response);

            1;
        } or do
        {
            my $exception = $@;
            my $error     = '';
            if ( ref($exception) and $exception->isa('Zymonic::Exception::Decryptor::Server::NoKey') )
            {
                $error = 'Replacement failed - no key.';
            }
            elsif ( ref($exception) and $exception->isa('Zymonic::Exception::Decryptor::Server::DataNotFound') )
            {
                $error = 'Replacement failed - not found (id ' . $exception->{encrypted_id} . ')';
            }
            elsif ( ref($exception) and $exception->isa('Zymonic::Exception::Decryptor::Server::DecryptFailed') )
            {
                $error =
                    'Replacement failed - decrypt failed '
                  . "- error: $exception->{enc_error} - id: $exception->{encrypted_id} - data: $exception->{encrypted_data}";
            }
            elsif ( ref($exception) and $exception->isa('Zymonic::Exception::Decryptor::Server::InvalidSystem') )
            {
                $error = 'Replacement failed - system not recognised.';
            }
            else
            {
                rethrow_exception($exception);
            }

            $dso->connection_log($error);

            print $connection $dso->freeze_and_pack( { DSO_error => $error } );
        };

        # Mark the connection closed in the log
        $dso->connection_close($log_id);

        1;
    }
      or do
    {
        my $err = death_handler( $@, '', 'return' );
        eval { print $connection $dso->freeze_and_pack( { DSO_error => $err->{message}->{content} } ); };
    };

}

