#!/usr/bin/perl

use strict;
use warnings;
use LATMOS::Accounts;
use Getopt::Long;
use Pod::Usage;
use Term::ReadKey;
use Crypt::RSA;

=head1 NAME

    la-crypt-passwd - Tools to managed rsa crypted password in LATMOS Account system

=head1 SYNOPSIS

    la-crypt-passwd [options] [--genkey|--regen] [--set BASE]

=cut

GetOptions(
    'c|config=s' => \my $config,
    'help'       => sub { pod2usage(0) },
    'genkey'     => \my $genkey,
    'regen'      => \my $regen,
    'delkey'     => \my $delkey,
    'set=s'      => \my $set,
    'base=s'     => \my $base,
    'u|user=s'   => \my @users,
) or pod2usage();

=head1 OPTIONS

=over 4

=item -c|--config configdir

Use this configuration directory instead of the default one.

=item --genkey

Generate a RSA key and store it into database

If one is already present, use regen to force generation of a new one

=item --regen

Like --genkey but a new key will replace the current one if already present.
Stored password will be read and encrypted again using the new key.

=item --delkey

Delete the current peer key and all encrypted password stored.

=item --base base

Work on this specific base instead default one

=item --set BASE

Read password from database, decrypt it and then set it in BASE given as
argument.

=item -u|--user USER

Set password only for this user (can be set multiple times).

=back

=cut

my $LA = LATMOS::Accounts->new($config, noacl => 1);
my $labase = $LA->base($base);
$labase && $labase->load or die "Cannot load base";
$labase->wexported(1);

my $clear;

sub get_clear_password {
    $clear and return $clear;
    my %encpasswd = $labase->get_rsa_password;
    scalar(keys %encpasswd) or return {};
    ReadMode('noecho');
    print "Enter password for current passphrase: ";
    my $password = ReadLine(0);
    ReadMode 0;
    print "\n";
    printf "Trying to get current stored password (%d)\n", scalar(keys %encpasswd);
    my $private_key = $labase->private_key($password) or
        die "Cannot get private key\n";
    my $rsa = new Crypt::RSA ES => 'PKCS1v15';
    my %clear_passwd;
    foreach (keys %encpasswd) {
        my $clearp = $rsa->decrypt (
                Cyphertext => $encpasswd{$_},
                Key        => $private_key,
                Armour     => 1,
        );
        if (defined $clearp) {
            $clear_passwd{$_} = $clearp;
        } else {
            die "Cannot get password for $_, crypt module said :" . $rsa->errstr() .
                "Was the password correct ?\n";
        }
    }
    return \%clear_passwd;
}

if ($set) {
    if (!$labase->get_global_value('rsa_private_key')) {
        warn "No rsa key found in database\n";
    }
    my $destbase = $LA->base($set) or die "Cannot get base $set\n";
    my $clearpasswd = get_clear_password();

    my @userstoset = @users ? @users : keys %$clearpasswd;

    foreach (@userstoset) {
        $clearpasswd->{$_} or next;
        my $obj = $destbase->get_object('user', $_) or do {
            warn "Cannot find user $_ in destination base, need sync ?\n";
            next;
        };
        $obj->set_password($clearpasswd->{$_}) and
            print "Password set for $_\n";
    }
    $destbase->commit;
} elsif ($regen || $genkey) {
    if ($labase->get_global_value('rsa_private_key') && !$regen) {
        die <<EOF;
A rsa key were found in database please use --regen to force a new key
generation. Notice this will force decrypt current stored password to encrypt
them again
EOF
    }

    my $clearpasswd = get_clear_password();

    my $password;
    while (1) {
        ReadMode('noecho');
        print "Enter password for new key: ";
        $password = ReadLine(0);
        print "\n";
        print "Enter password again for new key: ";
        my $password2 = ReadLine(0);
        ReadMode 0;
        print "\n";
        if ($password eq $password2) {
            last;
        } else {
            print "Password mismatch, try again:\n";
        }
    }

    print "Generating new RSA key...\n";
    my ($public, $private) = $labase->generate_rsa_key($password);

    $labase->store_rsa_key($public, $private);
    foreach (keys %$clearpasswd) {
        my $obj = $labase->get_object('user', $_);
        $obj->setCryptPassword($clearpasswd->{$_});
    }
    $labase->commit;
} elsif ($delkey) {
    if (! $labase->get_global_value('rsa_public_key')) {
        die "There is no key in this base, not deleting nothing\n";
    }
    my %encpasswd = $labase->get_rsa_password;
    print "Deleting password...\n";
    foreach my $user (keys %encpasswd) {
        my $ouser = $labase->get_object('user', $user) or next;
        $ouser->set_c_fields('encryptedPassword' => undef) or
            die "Cannot delete encryptedPassword attribute for $user\n";
    }
    $labase->set_global_value('rsa_public_key', undef);
    $labase->set_global_value('rsa_private_key', undef);
    $labase->commit;
} else {
    if ($labase->get_global_value('rsa_private_key')) {
        my $clearpasswd = get_clear_password();
        foreach (keys %$clearpasswd) {
            printf("%s: %s\n", $_, $clearpasswd->{$_});
        }
    } else {
        warn "No rsa key found in database\n";
    }
}
