#!/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;

use Term::ReadKey;
use Crypt::OpenSSL::RSA;
use Crypt::CBC;
use MIME::Base64;
use IPC::ShareLite qw( :lock );
use Storable qw(freeze thaw);

BEGIN
{
    use Zymonic::Utils qw(death_handler);
    $main::SIG{__DIE__} = \&death_handler;
}

our $dir     = $ARGV[0] || '';
our $egid    = $ARGV[1] || '';
our $destroy = $ARGV[2] || '';
our $public_key  = '';
our $private_key = '';
our $encrsa      = '';
our $decrsa      = '';
my $glue = 'ZCNF';

$) = $egid if $egid;

if ( $egid and $) != $egid )
{
    print "Failed to set EGID.\n";
    usage();
    exit(0);
}

if ($destroy)
{
    my $share = IPC::ShareLite->new(
        -key     => $glue,
        -create  => 0,
        -destroy => 1,
        -mode    => 0660,
    ) or die "Shared memory allocation failed.\n";

    exit(0);
}

unless ( -d $dir )
{
    print "Directory does not exist!\n";
    print usage();
    exit(0);
}

if ( -f $dir . "/public.key" and -f $dir . "/private.key.base64" )
{
    my $success = 0;
    while ( !$success )
    {

        # Keys are present - get the keks and decrypt it.
        my $kek = get_kek_part(1) . get_kek_part(2);
        $public_key = load_file( $dir . "/public.key" );
        my $private_key_enc = load_file( $dir . "/private.key.base64", 'base64' );

        # Encrypt a fake number
        $encrsa = Crypt::OpenSSL::RSA->new_public_key($public_key);
        $encrsa->use_pkcs1_oaep_padding();
        my $encrypted = encode_base64( $encrsa->encrypt('4111111111111111') );

        # Decrypt the private key
        my $cipher = Crypt::CBC->new( -key => $kek, -cipher => 'OpenSSL::AES', );
        my $decrypted_key = $cipher->decrypt($private_key_enc);

        # Decrypt
        $decrsa = Crypt::OpenSSL::RSA->new_private_key($decrypted_key);

        # Test the result
        $success = ( $decrsa->decrypt( decode_base64($encrypted) ) eq '4111111111111111' );
        print "Failed to decrypt private key!\n" unless $success;
    }

}
else
{

    # Generate the new RSA Key
    print "Creating a new key...\n";
    my $rsa = Crypt::OpenSSL::RSA->generate_key(8192);
    $private_key = $rsa->get_private_key_string();
    $public_key  = $rsa->get_public_key_string();
    $decrsa      = Crypt::OpenSSL::RSA->new_private_key($private_key);
    $encrsa      = Crypt::OpenSSL::RSA->new_public_key($public_key);
    $encrsa->use_pkcs1_oaep_padding();

    # Generate KEK
    my $kek = '';
    while ( !$kek )
    {

        # Part 1
        my $kek1 = get_kek_part(1);
        my $kek1_conf = get_kek_part( 1, 'confirm' );
        unless ( $kek1 eq $kek1_conf )
        {
            print "Entered Part 1 KEKs do not match.\n";
            next;
        }

        # Part2
        my $kek2 = get_kek_part(2);
        my $kek2_conf = get_kek_part( 2, 'confirm' );
        unless ( $kek2 eq $kek2_conf )
        {
            print "Entered Part 2 KEKs do not match.\n";
            next;
        }

        if ( ( $kek1 eq $kek1_conf ) and ( $kek2 eq $kek2_conf ) )
        {
            $kek = $kek1 . $kek2;
        }
    }

    # Encrypt the private key
    my $cipher = Crypt::CBC->new( -key => $kek, -cipher => 'OpenSSL::AES' );
    my $encrypted_key = $cipher->encrypt($private_key);

    # Store the keys
    open( PRV, ">", $dir . '/private.key.base64' );
    print PRV encode_base64($encrypted_key);
    close(PRV);

    open( PUB, ">", $dir . "/public.key" );
    print PUB $public_key;
    close(PUB);

}

# Encrypted any unecrypted
my @unenc_files = glob( $dir . '/*.conf' );
foreach my $u_file (@unenc_files)
{
    my $indata   = load_file($u_file);
    my $file_len = length($indata);

    # Encrypt the data
    my $encfiledata = encode_base64( $encrsa->encrypt($indata) );

    # Write the new file
    open( ENC, ">", $u_file . '.enc' );
    print ENC $encfiledata;
    close(ENC);

    # Scrub the old
    foreach my $count ( 0 .. 5 )
    {

        open( UENC, ">", $u_file );
        my @chars = ( ( 'A' .. 'Z' ), ( 'a' .. 'z' ), ( 0 .. 9 ) );
        print UENC join( '', map { $chars[ rand( scalar(@chars) ) ] } ( 0 .. $file_len ) );
        close(UENC);
    }

    # unlink the old
    unlink($u_file);
}

# Check for shared memory
my $config = {};

my $share = IPC::ShareLite->new(
    -key     => $glue,
    -create  => 1,
    -destroy => 0,
    -mode    => 0660,
) or die "Shared memory allocation failed.\n";
if ( $share->version == 1 )
{
    $share->store( freeze($config) );
}

# Load all encrypted files
my @e_files = glob( $dir . '/*.conf.enc' );
foreach my $e_file ( map { load_file($_) } @e_files )
{
    my $decrypted_data = $decrsa->decrypt( decode_base64($e_file) );
    foreach my $line ( split( /\n/, $decrypted_data ) )
    {
        if ( $line =~ /(.+)###(.+)\s*=\s*(.+)/ )
        {
            my $system = $1;
            my $key    = $2;
            my $value  = $3;

            $share->lock(LOCK_EX);
            $config = thaw( $share->fetch );
            $config->{$system} = {} unless defined $config->{$system};
            $config->{$system}->{$key} = $value;
            $share->store( freeze($config) );
            $share->unlock();
        }
    }
}

sub usage
{
    return "Usage: " . $0
      . " [directory to use] [Effective GID - suggest apache/www] [destroy shared memory and exit - optional set to yes to perform]\n";
}

sub get_kek_part
{
    my $part = shift;
    my $confirm = shift || '';

    print "Please " . ( $confirm ? 'confirm' : 'enter' ) . " part " . $part . " of the Key Encrypting Key: ";

    # Set read mode as no echo
    ReadMode('noecho');
    $part = <STDIN>;
    chomp($part);
    print("\n");

    # Reset the read mode
    ReadMode(0);

    return $part;
}

sub load_file
{
    my $file = shift;
    my $base64 = shift || '';

    open( FLE, '<', $file );
    my $ret = '';
    while (<FLE>)
    {
        $ret .= ( $base64 ? decode_base64($_) : $_ );
    }
    close(FLE);

    return $ret;
}
