#!/usr/bin/perl

use strict;

package OpenCA::XPG;

use Tk;

use Tk::widgets qw/Dialog Text ROText Pane/;

($OpenCA::XPG::VERSION = '$Revision: 1.1 $' )=~ s/(?:^.*: (\d+))|(?:\s+\$$)/defined $1?"0\.1":""/eg; 

our $app = OpenCA::XPG->new();
$app->openca_xpg_about_dialog();
$app->run();

## GUI stack
##
## top ::= menubar . main
## menubar ::= menu*
## main ::= text|display

sub new {

    ## create new object

    my $that = shift;
    my $class = ref($that) || $that;

    my $self = {};
    bless $self, $class;

    my $keys = { @_ };

    $self->{top} = MainWindow->new(-width => "300");
    $self->{top}->title ('OpenCA X.509 Privacy Guard (inofficial name)');

    $self->{menubar} = $self->create_menubar(); 

    $self->{PREF_HOME}      = $ENV{HOME}."/.openca_xpg";
    $self->{PREF_CHAIN}     = $self->{PREF_HOME}."/chain";
    $self->{PREF_CAS}       = $self->{PREF_HOME}."/ca_certs";
    $self->{PREF_CERTS}     = $self->{PREF_HOME}."/certs";
    $self->{PREF_KEYS}      = $self->{PREF_HOME}."/keys";
    $self->{PREF_OPENSSL}   = $self->{PREF_HOME}."/openssl";
    $self->{PREF_OPENCA_SV} = $self->{PREF_HOME}."/openca-sv";
    $self->{PREF_CONF}      = $self->{PREF_HOME}."/xpg.conf";
    if (-e $self->{PREF_HOME})
    {
        print STDERR "OpenCA X.509 Privacy Guard Home already present."."\n";
    } else {
        print STDERR "Creating OpenCA X.509 Privacy Guard Home ... ";
        if (mkdir $self->{PREF_HOME}, 0700)
        {
            print "succeeded";
        } else {
            print "failed";
        }
        print "\n";

        print STDERR "Creating OpenCA X.509 Privacy Guard Chain ... ";
        if (mkdir $self->{PREF_CHAIN}, 0700)
        {
            print "succeeded";
        } else {
            print "failed";
        }
        print "\n";

        print STDERR "Creating OpenCA X.509 Privacy Guard CA Certificate Area ... ";
        if (mkdir $self->{PREF_CAS}, 0700)
        {
            print "succeeded";
        } else {
            print "failed";
        }
        print "\n";

        print STDERR "Creating OpenCA X.509 Privacy Guard External Certificate Area ... ";
        if (mkdir $self->{PREF_CERTS}, 0700)
        {
            print "succeeded";
        } else {
            print "failed";
        }
        print "\n";

        print STDERR "Creating OpenCA X.509 Privacy Guard Keys Area ... ";
        if (mkdir $self->{PREF_KEYS}, 0700)
        {
            print "succeeded";
        } else {
            print "failed";
        }
        print "\n";

        print STDERR "Creating link to OpenSSL ... ";
        if (symlink "/usr/local/ssl/bin/openssl", $self->{PREF_OPENSSL})
        {
            print "succeeded";
        } else {
            print "failed";
        }
        print "\n";

        print STDERR "Creating link to OpenCA SV ... ";
        if (symlink "/usr/local/bin/openca-sv", $self->{PREF_OPENCA_SV})
        {
            print "succeeded";
        } else {
            print "failed";
        }
        print "\n";
    }

    $self->load_crypto_conf_main();

    return $self;
}

sub run
{
    my $self = shift;
    MainLoop;
}

sub create_menubar
{
    my $self = shift;

    my $menubar = $self->{top}->Menu();
    $self->{top}->configure (-menu => $menubar);

    my $file   = $menubar->cascade(-label => '~File');
    my $prefs  = $menubar->cascade(-label => '~Configuration');
    my $x509   = $menubar->cascade(-label => '~X.509');
    my $help   = $menubar->cascade(-label => '~Help', -tearoff => 0);

    ## File menu

    $file->command(-label => "Encrypt File", -command => [\&encrypt_dialog, $self]);
    $file->command(-label => "Decrypt File", -command => [\&decrypt_dialog, $self]);
    $file->command(-label => "Sign File",    -command => [\&sign_dialog, $self]);
    $file->command(-label => "Verify File",  -command => [\&verify_dialog, $self]);
    $file->separator();
    $file->command(-label => "Quit", -command => [\&terminate, $self]);

    ## Preferences menu

    $prefs->command(-label   => "Set signing key and certificate",
                    -command => [\&set_sign_cert_dialog, $self]);
    $prefs->command(-label   => "Set decryption key and certificate",
                    -command => [\&set_decrypt_key_dialog, $self]);
    $prefs->command(-label   => "Use internal encryption certificate",
                    -command => [\&set_encrypt_key_cert_dialog, $self]);
    $prefs->command(-label   => "Use external encryption certificate",
                    -command => [\&set_encrypt_cert_dialog, $self]);
    $prefs->command(-label   => "Show configuration",
                    -command => [\&show_crypto_conf_main, $self]);
    $prefs->command(-label   => "Save configuration",
                    -command => [\&save_crypto_conf_main, $self]);

    ## Crypto menu

    $x509->command(-label   => "Generate new key",
                   -command => [\&generate_key_dialog, $self]);
    $x509->command(-label   => "Generate request",
                   -command => [\&generate_request_dialog, $self]);
    $x509->command(-label   => "Export request",
                   -command => [\&export_request_dialog, $self]);
    $x509->command(-label   => "Import requested certificate",
                   -command => [\&import_requested_cert_dialog, $self]);
    $x509->command(-label   => "Generate self-signed certificate",
                   -command => [\&generate_selfsigned_cert_dialog, $self]);
    $x509->separator();
    $x509->command(-label   => "Import private key",
                   -command => [\&import_private_key_dialog, $self]);
    $x509->command(-label   => "Import PKCS#12 file",
                   -command => [\&import_pkcs12_dialog, $self]);
    $x509->command(-label   => "Import certificate",
                   -command => [\&import_cert_dialog, $self]);
    $x509->command(-label   => "Import CA Certificate",
                   -command => [\&import_ca_cert_dialog, $self]);
    $x509->command(-label   => "Rebuild Chain Directory",
                   -command => [\&rebuild_chain_main, $self]);
    $x509->separator();
    $x509->command(-label   => "List keys",
                   -command => [\&list_keys_main, $self]);
    $x509->command(-label   => "List certificates",
                   -command => [\&list_certs_main, $self]);
    $x509->command(-label   => "Export private key",
                   -command => [\&export_private_key_dialog, $self]);
    $x509->command(-label   => "Export certificate",
                   -command => [\&export_cert_dialog, $self]);

    ## Help menu

    $help->command(-label   => "Version",
                   -command => [\&openca_xpg_version_dialog, $self]);
    $help->command(-label   => "About",
                   -command => [\&openca_xpg_about_dialog, $self]);

    return $menubar;
}

sub set_main
{
    my $self = shift;

    my $old = $self->{main};
    $self->{main} = $self->{top}->Scrolled ("ROText", -scrollbars => 'osoe')
                                ->pack("-expand" => 1);
    $old->destroy() if ($old);

    $self->{main}->insert ("end", $_[0]);
}

sub terminate
{
    exit();
}

####################################
####################################
##        Crypto operations       ##
####################################
####################################

sub sign_dialog
{
    my $self = shift;

    return $self->set_error ("There is no signing certificate and key configured.")
        if (not $self->{PREF_SIGN_CERT} or not $self->{PREF_SIGN_KEY});

    ## build array for dialog mechanism

    my $filename = $self->{top}->getOpenFile();
    return if (not $filename);

    my ($pwd1);
    while (1)
    {
        my @params = (
                  ["Passphrase for the signing key", "text", [""], "*"],
                     );

        ($pwd1) = $self->get_data_from_dialog (@params);
        return 1 if (not $pwd1);
        last if ($pwd1);
    }

    my $command = $self->{PREF_OPENCA_SV};
    $command .= " sign";
    $command .= " -cert ".$self->{PREF_SIGN_CERT};
    $command .= " -keyfile ".$self->{PREF_SIGN_KEY};
    $command .= " -passin env:passwd";
    $command .= " -in $filename";
    $command .= " -out $filename.p7s";

    $ENV{passwd} = $pwd1;
    `$command`;
    delete $ENV{passwd};
    if ($@)
    {
        $self->set_error ($@);
        $self->set_main ($@);
        return undef;
    } else {
        $self->set_main ("The file was successfully signed.");
    }
    return 1;
}

sub verify_dialog
{
    my $self = shift;

    ## build array for dialog mechanism

    my $sigfile = $self->{top}->getOpenFile(
                      "-defaultextension" => "p7s",
                      "-title" => "Signaturefile");
    return if (not $sigfile);
    my $filename = $self->{top}->getOpenFile("-title" => "Datafile");
    return if (not $filename);

    my $command = $self->{PREF_OPENCA_SV};
    $command .= " verify -print_data -verbose";
    $command .= " -cd ".$self->{PREF_CHAIN};
    $command .= " -data $filename";
    $command .= " -in $sigfile";

    my $res = `$command`;
    if ($@)
    {
        $self->set_error ($@);
        $self->set_main ($@);
        return undef;
    } else {
        $self->set_main ("Verification result:\n".
                         "--------------------\n".$res);
    }
    return 1;
}

sub encrypt_dialog
{
    my $self = shift;

    return $self->set_error ("There is no encrpytion certificate configured.")
        if (not $self->{PREF_ENCRYPT_CERT});

    ## build array for dialog mechanism

    my $filename = $self->{top}->getOpenFile();
    return if (not $filename);

    my $command = $self->{PREF_OPENCA_SV};
    $command .= " encrypt";
    $command .= " -cert ".$self->{PREF_ENCRYPT_CERT};
    $command .= " -in $filename";
    $command .= " -out $filename.p7e";

    `$command`;
    if ($@)
    {
        $self->set_error ($@);
        $self->set_main ($@);
        return undef;
    } else {
        $self->set_main ("The file was successfully encrypted.");
    }
    return 1;
}

sub decrypt_dialog
{
    my $self = shift;

    return $self->set_error ("There is no encrpytion certificate and key configured.")
        if (not $self->{PREF_DECRYPT_CERT} or not $self->{PREF_DECRYPT_KEY});

    ## build array for dialog mechanism

    my $filename = $self->{top}->getOpenFile(
                       "-defaultextension" => "p7e");
    return if (not $filename);

    my ($pwd1);
    while (1)
    {
        my @params = (
                  ["Passphrase for the decryption key", "text", [""], "*"],
                     );

        ($pwd1) = $self->get_data_from_dialog (@params);
        return 1 if (not $pwd1);
        last if ($pwd1);
    }

    my $command = $self->{PREF_OPENCA_SV};
    $command .= " decrypt";
    $command .= " -cert ".$self->{PREF_DECRYPT_CERT};
    $command .= " -keyfile ".$self->{PREF_DECRYPT_KEY};
    $command .= " -passin env:passwd";
    $command .= " -in $filename";
    $command .= " -out $filename.org";

    $ENV{passwd} = $pwd1;
    `$command`;
    delete $ENV{passwd};
    if ($@)
    {
        $self->set_error ($@);
        $self->set_main ($@);
        return undef;
    } else {
        $self->set_main ("The file was successfully decrypted.");
    }
    return 1;
}

####################################
####################################
##         Preferences            ##
####################################
####################################

sub set_sign_cert_dialog
{
    my $self = shift;

    ($self->{PREF_SIGN_KEY}, $self->{PREF_SIGN_CERT}) =
        $self->get_key_and_cert_dialog();
    show_crypto_conf_main();
}

sub set_encrypt_cert_dialog
{
    my $self = shift;

    ($self->{PREF_ENCRYPT_CERT}) =
        $self->get_cert_dialog();
    show_crypto_conf_main();
}

sub set_encrypt_key_cert_dialog
{
    my $self = shift;

    ($self->{PREF_ENCRYPT_KEY}, $self->{PREF_ENCRYPT_CERT}) =
        $self->get_key_and_cert_dialog();
    show_crypto_conf_main();
}

sub set_decrypt_key_dialog
{
    my $self = shift;

    ($self->{PREF_DECRYPT_KEY}, $self->{PREF_DECRYPT_CERT}) =
        $self->get_key_and_cert_dialog();
    show_crypto_conf_main();
}

sub get_key_and_cert_dialog
{
    my $self = shift;

    ## build array for dialog mechanism

    my ($key, $cert);
    while (1)
    {
        my @params = (
                  ["Key", "select", [scan_dir ($self->{PREF_KEYS})], 0],
                     );

        ($key) = $self->get_data_from_dialog (@params);
        return () if (not $key);
        last if ($key);
    }
    $key = $self->{PREF_KEYS}."/$key";
    while (1)
    {
        my @params = (
                  ["Certificate", "select", [scan_dir ("$key/certs")], 0],
                     );

        ($cert) = $self->get_data_from_dialog (@params);
        return () if (not $cert);
        last if ($cert);
    }

    $cert = "$key/certs/$cert";
    $key  = "$key/key.pem";
    return ($key, $cert);
}

sub get_cert_dialog
{
    my $self = shift;

    ## build array for dialog mechanism

    my ($cert);
    while (1)
    {
        my @params = (
                  ["Certificate", "select", [scan_dir ($self->{PREF_CERTS})], 0],
                     );

        ($cert) = $self->get_data_from_dialog (@params);
        return () if (not $cert);
        last if ($cert);
    }
    return ($self->{PREF_CERTS}."/$cert");
}

sub show_crypto_conf_main
{
    my $self = shift;

    $self->set_main ($self->get_crypto_conf());
}

sub save_crypto_conf_main
{
    my $self = shift;

    my $text = $self->get_crypto_conf();

    unlink ($self->{PREF_CONF});
    open FD, ">".$self->{PREF_CONF} ||
        return $self->error_dialog ("Cannot open ".$self->{PREF_CONF}.".");
    print FD $text;
    close FD;

    show_crypto_conf_main();
}

sub load_crypto_conf_main
{
    my $self = shift;

    my $text = "";
    open FD, $self->{PREF_CONF} || return undef;
    while (<FD>)
    {
        $text .= $_;
    }
    close FD;

    my $signing = $text;
    my $decrypt = $text;
    my $encrypt = $text;

    $signing =~ s/^.*\[\s*signing\s*\]([^\[]*)(\[.*|)$/$1/is;
    $decrypt =~ s/^.*\[\s*decryption\s*\]([^\[]*)(\[.*|)$/$1/is;
    $encrypt =~ s/^.*\[\s*encryption\s*\]([^\[]*)(\[.*|)$/$1/is;

    $self->{PREF_SIGN_KEY}     = $signing;
    $self->{PREF_SIGN_CERT}    = $signing;
    $self->{PREF_DECRYPT_KEY}  = $decrypt;
    $self->{PREF_DECRYPT_CERT} = $decrypt;
    $self->{PREF_ENCRYPT_CERT} = $encrypt;

    # $self->{PREF_SIGN_KEY}     =~ s/^(.*\n|)\s*key\s*::=\s*([^\s\n]*).*$/$2/is;
    $self->{PREF_SIGN_KEY}     =~ s/^.*\n\s*key\s*::=\s*([^\s\n]*).*$/$1/is;
    $self->{PREF_DECRYPT_KEY}  =~ s/^.*\n\s*key\s*::=\s*([^\s\n]*).*$/$1/is;
    $self->{PREF_SIGN_CERT}    =~ s/^.*\n\s*certificate\s*::=\s*([^\s\n]*).*$/$1/is;
    $self->{PREF_DECRYPT_CERT} =~ s/^.*\n\s*certificate\s*::=\s*([^\s\n]*).*$/$1/is;
    $self->{PREF_ENCRYPT_CERT} =~ s/^.*\n\s*certificate\s*::=\s*([^\s\n]*).*$/$1/is;

    $self->show_crypto_conf_main();

    return 1;
}

sub get_crypto_conf
{
    my $self = shift;

    return
        "[Signing]\n".
        "Key         ::= ".$self->{PREF_SIGN_KEY}."\n".
        "Certificate ::= ".$self->{PREF_SIGN_CERT}."\n\n".
        "[Decryption]\n".
        "Key         ::= ".$self->{PREF_DECRYPT_KEY}."\n".
        "Certificate ::= ".$self->{PREF_DECRYPT_CERT}."\n\n".
        "[Encryption]\n".
        "Certificate ::= ".$self->{PREF_ENCRYPT_CERT};
}

#####################################
#####################################
##        X.509 stuff              ##
#####################################
#####################################

###############################
##     new key lifecycle     ##
###############################

sub generate_key_dialog
{
    my $self = shift;

    ## build array for dialog mechanism

    my ($alg, $length, $enc_alg, $pwd1, $pwd2, $filename);
    while (1)
    {
        my @params = (
                  ["Algorithm", "select", ["RSA", "DSA"], 0],
                  ["Key Length (only for RSA)", "select", ["1024", "2048", "4096"], 1],
                  ["Encryption Algorithm", "select", ["DES3", "IDEA"], 0],
                  ["Passphrase", "text", [""], "*"],
                  ["Passphrase again", "text", [""], "*"],
                  ["Name of the key", "text", [""]]
                     );

        ($alg, $length, $enc_alg, $pwd1, $pwd2, $filename) = 
            $self->get_data_from_dialog (@params);
        return 1
            if (not ($alg or $length or $enc_alg or $pwd1 or $pwd2 or $filename));
        $self->error_dialog ("Missing algorithm") if (not $alg);
        $self->error_dialog ("Missing key length") if (not $length and $alg =~ /RSA/i);
        $self->error_dialog ("Missing encryption algorithm") if (not $enc_alg);
        $self->error_dialog ("Missing key name") if (not $filename);
        $self->error_dialog ("Passphrases do not match") if ($pwd1 ne $pwd2);
        last if ($alg and $length and $enc_alg and $filename and $pwd1 eq $pwd2);
    }

    $filename =~ s/\s/_/g;
    $filename = $self->{PREF_KEYS}."/".$filename;
    return $self->error_dialog ("Cannot create choosen directory $filename.")
        if (not -e $filename and not mkdir $filename);
    return $self->error_dialog ("Cannot create certificate directory $filename/certs.")
        if (not -e "$filename/certs" and not mkdir "$filename/certs");

    my $command = $self->{PREF_OPENSSL};
    if ($alg =~ /DSA/i)
    {
        $command .= " gendsa ";
    } else {
        $command .= " genrsa ";
    }
    $command .= " -out $filename/key.pem ";
    $command .= " -".lc ($enc_alg);
    $command .= " -passout env:passwd";
    $ENV{passwd} = $pwd1;
    $command .= " $length" if ($alg =~ /RSA/i);

    `$command`;
    delete $ENV{passwd};
    if ($@)
    {
        $self->set_main ($@);
    } else {
        $self->set_main (get_file ("$filename/key.pem"));
    }
}

sub generate_request_dialog
{
    my $self = shift;

    ## build array for dialog mechanism

    my ($subject, $key, $pwd1);
    while (1)
    {
        my @params = (
                  ["Subject (DN)", "text", [""]],
                  ["Key", "select", [scan_dir ($self->{PREF_KEYS})], 0],
                  ["Passphrase", "text", [""], "*"],
                     );

        ($subject, $key, $pwd1) = 
            $self->get_data_from_dialog (@params);
        return 1
            if (not ($subject or $key or $pwd1));
        $self->error_dialog ("Missing subject") if (not $subject);
        $self->error_dialog ("Missing key name") if (not $key);
        $self->error_dialog ("Missing passphrase") if (not $pwd1);
        last if ($subject and $key and $pwd1);
    }

    ## reverse from RFC2253
    $subject =~ s/([^\\]),/$1$1,/g;
    $subject = join ",", reverse split /[^\\],/, $subject;

    $subject =~ s/(^|[^\\],)\s*/$1/g;      # remove space after commas
    $subject =~ s/(^|[^\\],)emailAddress=/${1}emailAddress=/g; # fix emailAddress for OpenSSL
    $subject =~ s/(^|[^\\],)email=/${1}emailAddress=/g;        # fix emailAddress for OpenSSL
    $subject =~ s/(^|[^\\],)cn=/${1}CN=/g; # fix CN for OpenSSL
    $subject =~ s/(^|[^\\],)ou=/${1}OU=/g; # fix OU for OpenSSL
    $subject =~ s/(^|[^\\],)o=/${1}O=/g;   # fix O for OpenSSL
    $subject =~ s/(^|[^\\],)c=/${1}C=/g;   # fix C for OpenSSL
    $subject =~ s/([^\\]),/$1\//g;       # replace commas by /
    $subject = "/".$subject;             # at minimum one leading /
    $subject =~ s/^\/*/\//;              # only one leading /
    $subject =~ s/"/\\"/;                # escape "
    $subject = '"'.$subject.'"';         # using " as brackets

    $key = $self->{PREF_KEYS}."/$key";

    my $command = $self->{PREF_OPENSSL};
    $command .= " req -new -batch -sha1 ";
    $command .= " -key $key/key.pem ";
    $command .= " -keyform PEM ";
    $command .= " -out $key/csr.pem ";
    $command .= " -outform PEM ";
    $command .= " -subj ".$subject;
    $command .= " -passin env:passwd";
    $ENV{passwd} = $pwd1;

    `$command`;
    delete $ENV{passwd};
    if ($@)
    {
        $self->set_main ($@);
    } else {
        $self->set_main (get_file ("$key/csr.pem"));
    }
}

sub generate_selfsigned_cert_dialog
{
    my $self = shift;

    ## build array for dialog mechanism

    my ($key, $pwd1);
    while (1)
    {
        my @params = (
                  ["Key", "select", [scan_dir ($self->{PREF_KEYS})], 0],
                  ["Passphrase", "text", [""], "*"],
                     );

        ($key, $pwd1) = 
            $self->get_data_from_dialog (@params);
        return 1
            if (not ($key or $pwd1));
        $self->error_dialog ("Missing key name") if (not $key);
        $self->error_dialog ("Missing passphrase") if (not $pwd1);
        last if ($key and $pwd1);
    }

    $key = $self->{PREF_KEYS}."/$key";

    my $command = $self->{PREF_OPENSSL};
    $command .= " req -x509 ";
    $command .= " -key $key/key.pem ";
    $command .= " -keyform PEM ";
    $command .= " -in $key/csr.pem ";
    $command .= " -inform PEM ";
    $command .= " -out $key/certs/cert_".(scalar (scan_dir ("$key/certs")) + 1).".pem ";
    $command .= " -outform PEM ";
    $command .= " -passin env:passwd";
    $ENV{passwd} = $pwd1;

    `$command`;
    delete $ENV{passwd};
    if ($@)
    {
        $self->set_main ($@);
    } else {
        $self->set_main (get_file ("$key/certs/cert_".scalar (scan_dir ("$key/certs")).".pem"));
    }
}

sub export_request_dialog
{
    my $self = shift;

    ## build array for dialog mechanism

    my ($key);
    while (1)
    {
        my @params = (
                  ["Key", "select", [scan_dir ($self->{PREF_KEYS})], 0],
                     );

        ($key) = 
            $self->get_data_from_dialog (@params);
        return 1
            if (not $key);
        $self->error_dialog ("Missing key name") if (not $key);
        last if ($key);
    }

    $key = $self->{PREF_KEYS}."/$key";

    ## get filename
    my $filename = $self->{top}->getSaveFile(
                       -defaultextension => "pem",
                       -filetypes => [["PEM", [".pem", ".PEM", ".b64", ".B64"]]]);
    return 1 if (not $filename);

    my $command = "cp $key/csr.pem $filename";

    `$command`;
    delete $ENV{passwd};
    if ($@)
    {
        $self->set_main ($@);
    } else {
        $self->set_main ("Exported request to file $filename.");
    }
}

sub import_requested_cert_dialog
{
    my $self = shift;

    ## build array for dialog mechanism

    my ($key);
    while (1)
    {
        my @params = (
                  ["Key", "select", [scan_dir ($self->{PREF_KEYS})], 0],
                     );

        ($key) = 
            $self->get_data_from_dialog (@params);
        return 1
            if (not $key);
        $self->error_dialog ("Missing key name") if (not $key);
        last if ($key);
    }

    $key = $self->{PREF_KEYS}."/$key";

    ## get filename
    my $filename = $self->{top}->getOpenFile(
                       -defaultextension => "pem",
                       -filetypes => [["PEM", [".pem", ".PEM", ".b64", ".B64", ".crt", ".CRT"]]]);
    return 1 if (not $filename);

    my $command = "cp $filename $key/cert_".(scalar (scan_dir ($key)) - 1).".pem ";

    `$command`;
    delete $ENV{passwd};
    if ($@)
    {
        $self->set_main ($@);
    } else {
        $self->set_main (get_file ("$key/certs/cert_".scalar (scan_dir ($key)).".pem"));
    }
}

#############################
##      import stuff       ##
#############################

sub import_private_key_dialog
{
    my $self = shift;

    ## build array for dialog mechanism

    my ($key);
    while (1)
    {
        my @params = (
                  ["Name of the new key", "text", [""]],
                     );

        ($key) = 
            $self->get_data_from_dialog (@params);
        return 1
            if (not $key);
        $self->error_dialog ("Missing key name") if (not $key);
        last if ($key);
    }

    $key =~ s/\s/_/g;
    $key = $self->{PREF_KEYS}."/".$key;
    return $self->error_dialog ("Cannot create choosen directory $key.")
        if (not -e $key and not mkdir $key);
    return $self->error_dialog ("Cannot create certificate directory $key/certs.")
        if (not -e "$key/certs" and not mkdir "$key/certs");

    ## get filename
    my $filename = $self->{top}->getOpenFile(
                       -defaultextension => "pem",
                       -filetypes => [["PEM", [".pem", ".PEM", ".b64", ".B64", ".crt", ".CRT"]]]);
    return 1 if (not $filename);

    my $command = "cp $filename $key/key.pem ";

    `$command`;
    delete $ENV{passwd};
    if ($@)
    {
        $self->set_main ($@);
    } else {
        $self->set_main (get_file ("$key/key.pem"));
    }
}

sub import_pkcs12_dialog
{
    my $self = shift;

    ## get filename
    my $filename = $self->{top}->getOpenFile(
                       -defaultextension => "p12",
                       -filetypes => [["PKCS 12", [".p12", ".P12"]]]);
    $filename = $self->{top}->getOpenFile();
    return 1 if (not $filename);

    ## build array for dialog mechanism

    my ($key, $pwd1);
    while (1)
    {
        my @params = (
                  ["Name of the new key", "text", [""]],
                  ["Passphrase", "text", [""], "*"],
                     );

        ($key, $pwd1) = 
            $self->get_data_from_dialog (@params);
        return 1
            if (not ($key or $pwd1));
        $self->error_dialog ("Missing key name") if (not $key);
        $self->error_dialog ("Missing passphrase") if (not $pwd1);
        last if ($key and $pwd1);
    }

    $key =~ s/\s/_/g;
    $key = $self->{PREF_KEYS}."/".$key;
    return $self->error_dialog ("Cannot create choosen directory $key.")
        if (not -e $key and not mkdir $key);
    return $self->error_dialog ("Cannot create certificate directory $key/certs.")
        if (not -e "$key/certs" and not mkdir "$key/certs");

    my $error = "";

    ## certificate import
    my $command = $self->{PREF_OPENSSL};
    $command .= " pkcs12 -nokeys -clcerts ";
    $command .= " -in $filename ";
    $command .= " -out $key/certs/cert_".(scalar (scan_dir ("$key/certs")) + 1).".pem ";
    $command .= " -passin env:passwd";
    $ENV{passwd} = $pwd1;
    `$command`;
    $error .= $@ if ($@);

    ## key import
    my $command = $self->{PREF_OPENSSL};
    $command .= " pkcs12 -nocerts -des3 ";
    $command .= " -in $filename ";
    $command .= " -out $key/key.pem ";
    $command .= " -passin env:passwd";
    $command .= " -passout env:passwd";
    `$command`;
    $error .= $@ if ($@);

    ## ca certificate import
    my $command = $self->{PREF_OPENSSL};
    $command .= " pkcs12 -nokeys -cacerts ";
    $command .= " -in $filename ";
    $command .= " -out ".$self->{PREF_CAS}."/cacert_".(scalar (scan_dir ($self->{PREF_CAS})) + 1).".pem ";
    $command .= " -passin env:passwd";
    `$command`;
    $error .= $@ if ($@);

    delete $ENV{passwd};
    if ($error)
    {
        $self->set_main ($error);
    } else {
        $self->set_main (get_file ("$key/key.pem")."\n".
                         get_file ("$key/certs/cert_".
                                    scalar (scan_dir ("$key/certs")).".pem")."\n".
                         get_file ($self->{PREF_CAS}."/cacert_".
                                    scalar (scan_dir ($self->{PREF_CAS})).".pem"));
    }
}

sub import_cert_dialog
{
    my $self = shift;

    ## get filename
    my $filename = $self->{top}->getOpenFile(
                       -defaultextension => "pem",
                       -filetypes => [["PEM", [".pem", ".PEM", ".b64", ".B64", ".crt", ".CRT"]]]);
    $filename = $self->{top}->getOpenFile();
    return 1 if (not $filename);

    my $command = "cp $filename ".$self->{PREF_CERTS}."/cert_".
                                  (scalar (scan_dir ($self->{PREF_CERTS})) + 1).".pem";

    `$command`;
    delete $ENV{passwd};
    if ($@)
    {
        $self->set_main ($@);
    } else {
        $self->set_main (get_file ($self->{PREF_CERTS}."/cert_".
                                  scalar (scan_dir ($self->{PREF_CERTS})).".pem"));
    }
}

sub import_ca_cert_dialog
{
    my $self = shift;

    ## get filename
    my $filename = $self->{top}->getOpenFile(
                       -defaultextension => "pem",
                       -filetypes => [["PEM", [".pem", ".PEM", ".b64", ".B64", ".crt", ".CRT"]]]);
    $filename = $self->{top}->getOpenFile();
    return 1 if (not $filename);

    my $command = "cp $filename ".$self->{PREF_CAS}."/cacert_".
                                  (scalar (scan_dir ($self->{PREF_CAS})) + 1).".crt";

    `$command`;
    delete $ENV{passwd};
    if ($@)
    {
        $self->set_main ($@);
    } else {
        $self->set_main (get_file ($self->{PREF_CAS}."/cacert_".
                                  scalar (scan_dir ($self->{PREF_CAS})).".crt"));
    }
}

sub rebuild_chain_main
{
    my $self = shift;

    return undef
        if (not -e $self->{PREF_CHAIN}."/Makefile" and
            not $self->setup_chain_makefile());

    my $command = "cd $self->{PREF_CHAIN}; cp ../ca_certs/* .; make";
    my $res = `$command`;
    if ($@)
    {
        $self->set_main ($@);
    } else {
        $self->set_main ($res);
    }
}
    
sub setup_chain_makefile
{
    my $self = shift;

    my @search_list = (
                       [".", "Makefile.crt"],
                       [$ENV{HOME}, "Makefile.crt"],
                       ["/usr/local", "Makefile.crt"],
                       ["/usr/local", "chain/Makefile"],
                       ["/", "Makefile.crt"],
                       ["/", "chain/Makefile"]
                      );

    ## try to find a usable makefile for the chain generation
    my $makefile = "";
    foreach my $item (@search_list)
    {
        my $base   = $item->[0];
        my $search = $item->[1];
        $search =~ s/\/.*//;
        my $res = `find $base -name $search -print`;
        next if (not $res);
        my $addon = "";
        if ($item->[1] =~ /\//)
        {
            $addon = $item->[1];
            $addon =~ s/.*\//\//;
        }
        foreach my $item (split /\n/, $res)
        {
            $item = $item.$addon;
            next if (not -e $item);
            $makefile = $item;
            last;
        }
        last if ($makefile);
    }

    if (not $makefile)
    {
        $self->set_main ("Cannot find a suitable Makefile for the chain update.");
        return undef;
    }

    my $command = "cp $makefile ".$self->{PREF_CHAIN}."/Makefile";
    `$command`;
    return 1 if (not $@);
    $self->set_main ($@);
    return undef;
}

#############################
##      info stuff         ##
#############################

sub list_keys_main
{
    my $self = shift;

    my @keys = scan_dir ($self->{PREF_KEYS});

    my $frame = $self->get_main_scroll_frame();
    $frame->Label("-text"=>"List of all keys")->pack();

    foreach my $key (@keys)
    {
        my $button = $frame->Button("-text"    => $key,
                                    "-command" => [\&show_key_main, $self, $key]);
        $button->pack();
    }
    $self->set_main_frame($frame);
}

sub show_key_main
{
    my $self = shift;
    my $key  = $_[0];

    my ($pwd1);
    while (1)
    {
        my @params = (
                  ["Passphrase for the key", "text", [""], "*"],
                     );

        ($pwd1) = $self->get_data_from_dialog (@params);
        return 1 if (not $pwd1);
        last if ($pwd1);
    }

    my $filename = $self->{PREF_KEYS}."/$key/key.pem";
    my $command = $self->{PREF_OPENSSL}." rsa -in $filename -passin env:passwd -noout -text";
    $ENV{passwd} = $pwd1;
    my $result  = `$command`;
    $result = $@ if ($@);
    delete $ENV{passwd};
    my $text_key = get_file ($self->{PREF_KEYS}."/$key/key.pem");
    $self->set_main ($result."\n\n".$text_key);
}

sub list_certs_main
{
    my $self = shift;

    my ($width, $height) = ($self->{top}->cget ("-width"), 100);
    my @keys = scan_dir ($self->{PREF_KEYS});

    my $frame = $self->get_main_scroll_frame();
    $frame->Label("-text"=>"List of all certs")->pack();
    $frame->Label("-text"=>"-----------------")->pack();

    foreach my $key (@keys)
    {
        $frame->Label("-text"=>"$key")->pack();
        my @certs = scan_dir ($self->{PREF_KEYS}."/$key/certs/");
        foreach my $cert (@certs)
        {
            my $button = $frame->Button("-text"    => $cert,
                                        "-command" => [\&show_key_cert_main, $self, $key, $cert]);
            $button->pack();
        }
    }
    $self->set_main_frame($frame);
}

sub show_key_cert_main
{
    my $self = shift;
    my $key  = $_[0];
    my $cert = $_[1];

    my $filename = $self->{PREF_KEYS}."/$key/certs/$cert";
    my $command = $self->{PREF_OPENSSL}." x509 -in $filename -noout -text";
    my $result  = `$command`;
    $result = $@ if ($@);
    my $text_cert = get_file ($self->{PREF_KEYS}."/$key/certs/$cert");
    $self->set_main ($result."\n\n".$text_cert);
}

sub export_private_key_dialog
{
    my $self = shift;

    ## build array for dialog mechanism

    my ($key);
    while (1)
    {
        my @params = (
                  ["Key", "select", [scan_dir ($self->{PREF_KEYS})], 0],
                     );

        ($key) = 
            $self->get_data_from_dialog (@params);
        return 1
            if (not $key);
        $self->error_dialog ("Missing key name") if (not $key);
        last if ($key);
    }

    $key = $self->{PREF_KEYS}."/$key";

    ## get filename
    my $filename = $self->{top}->getSaveFile(
                       -defaultextension => "pem",
                       -filetypes => [["PEM", [".pem", ".PEM", ".b64", ".B64"]]]);
    return 1 if (not $filename);

    my $command = "cp $key/key.pem $filename";

    `$command`;
    delete $ENV{passwd};
    if ($@)
    {
        $self->set_main ($@);
    } else {
        $self->set_main ("Exported private key to file $filename.");
    }
}

sub export_cert_dialog
{
    my $self = shift;

    ## build array for dialog mechanism

    my ($key);
    while (1)
    {
        my @params = (
                  ["Key", "select", [scan_dir ($self->{PREF_KEYS})], 0],
                     );

        ($key) = 
            $self->get_data_from_dialog (@params);
        return 1
            if (not $key);
        $self->error_dialog ("Missing key name") if (not $key);
        last if ($key);
    }

    $key = $self->{PREF_KEYS}."/$key";

    my $cert;
    while (1)
    {
        my @params = (
                  ["Certificate", "select", [scan_dir ($key."/certs")], 0],
                     );

        ($cert) = 
            $self->get_data_from_dialog (@params);
        return 1
            if (not $cert);
        $self->error_dialog ("Missing certificate name") if (not $cert);
        last if ($cert);
    }

    $cert = "$key/certs/$cert";

    ## get filename
    my $filename = $self->{top}->getSaveFile(
                       -defaultextension => "pem",
                       -filetypes => [["PEM", [".pem", ".PEM", ".b64", ".B64", ".crt", ".CRT"]]]);
    return 1 if (not $filename);

    my $command = "cp $cert $filename";

    `$command`;
    delete $ENV{passwd};
    if ($@)
    {
        $self->set_main ($@);
    } else {
        $self->set_main ("Exported certificate to file $filename.");
    }
}

#####################################
#####################################
##        Help functions           ##
#####################################
#####################################

sub openca_xpg_version_dialog
{
    my $self = shift;

    $self->set_main ("OpenCA X.509 Privacy Guard\n\nVersion ".$OpenCA::XPG::VERSION);
}

sub openca_xpg_about_dialog
{
    my $self = shift;

    $self->set_main (
        "This program is a small wrapper for the OpenCA program openca-sv.".
        "\n".
        "openca-sv is a tool to encrypt, sign, decrypt and verify files.".
        "\n".
        "This program bases on OpenCA PKI software and OpenSSL.".
        "\n".
        "Please check www.openssl.org and www.openca.org for more informations."
                    );
}

###################################
##           utilities           ##
###################################

sub get_data_from_dialog
{

    ## input desgin
    ##
    ## @_ = (item*)
    ## item ::= [name,type,value,default]
    ## name ::= string
    ## type ::= (select,text,area)
    ## value ::= [name*]
    ## default ::= name

    my $self = shift;

    ## build frame with grid manager

    my $main  = $self->{top}->Toplevel;
    our $main_dialog_widget = $main;
    our $main_dialog_button = "";

    ## build fields from submitted array
    my @fields = ();
    my $counter = 0;
    foreach my $field (@_)
    {
        my $name    = $field->[0];
        my $type    = $field->[1];
        my $value   = $field->[2];
        my $default = $field->[3];

        my $label = $main->Label (-text => $name);
        my $content = "";
        if ($type =~ /select/i)
        {
            ## select field

            $content = $main->Listbox(-selectmode => "single",
                                      -height     => 0,
                                      -exportselection => 0);
            $content->insert ("end", @{$value});
            $content->selection ("set", $default, $default);

        } elsif ($type =~ /area/i) {
            ## textfield

            $content = $main->Text ();
            $content->insert ("end", $value->[1]);

        } else {
            ## normal entry

            $content = $main->Entry ();
            $content->insert ("end", $value->[1]);
            $content->configure (-show => $default) if ($default);
        }
        push @fields, $content;
        $label->grid ($label, $content);
        $counter++;
    }

    ## build ok and cancel
    my $ok     = $main->Button (-text => "OK", -command => [\&close_on_ok, $self]);
    my $cancel = $main->Button (-text => "Cancel", -command => [\&close_on_cancel, $self]);
    $ok->grid ($ok, $cancel);

    ## do dialog
    $main->focus();
    $main->grab();
    $main->tkwait ("variable", \$main_dialog_button);

    my @results;
    if ($main_dialog_button =~ /ok/i)
    {
        ## build answer array
        foreach my $field (@fields)
        {
            eval {
                push @results, $field->curselection();
            };
            eval {
                push @results, $field->get();
            };
        }
    }
    $main->destroy();
    return () if ($main_dialog_button =~ /cancel/i);

    for (my $i=0; $i < scalar @results; $i++)
    {
        $results[$i] = $_[$i]->[2]->[$results[$i]]
            if ($_[$i]->[1] =~ /select/i);
    }

    return @results;
}

sub close_on_ok
{
    our $main_dialog_button = "ok";
}

sub close_on_cancel
{
    our $main_dialog_button = "cancel";
}

sub error_dialog
{
    my $self = shift;
    $self->{top}->Dialog(
                         -title   => 'OpenCA X.509 Privacy Guard',
                         -text    => $_[0],
                         -buttons => ['OK'])->Show();
    return undef;
}

sub scan_dir {

    my $dir = $_[0];
    my @result = ();

    ## get directories
    opendir( DIR, $dir );
    my @dirList = sort readdir( DIR );
    closedir( DIR );

    ## check every directory
    my $h;
    foreach $h (@dirList) {
        next if ($h eq ".");
        next if ($h eq "..");

        push @result, $h;
    }

    return @result;
}

sub get_file
{
    my $res = "";
    open FD, $_[0] || return undef;
    while (<FD>)
    {
        $res .= $_;
    }
    close FD;
    return $res;
}

sub get_main_scroll_frame
{
    my $self = shift;
    my ($width, $height) = ($self->{top}->cget ("-width"),
                            $self->{top}->cget ("-height"));
    $width  = 300 if ($width < 300);
    $height = 100 if ($height < 100);

    my $frame = $self->{top}->Scrolled ("Pane", -scrollbars => 'osoe',
                                                 -height     => $height,
                                                 -width      => $width);
    $frame->Frame (-height => $height, -width => $width);

    return $frame;
}

sub set_main_frame
{
    my $self = shift;
    my $frame = shift;

    my ($width, $height) = ($self->{top}->width(),
                            $self->{top}->height());

    $frame->pack("-expand" => 1, "-fill" => "both");
    $self->{top}->update();
    $self->{main}->destroy() if (exists $self->{main});
    $self->{main} = $frame;

}

1;
