package xCAT::PasswordUtils;
use xCAT::Table;
use xCAT::MsgUtils;
use xCAT::Utils;

my $ipmiuser  = "USERID";      # default username to apply if nothing specified
my $ipmipass  = "PASSW0RD";    # default password to apply if nothing specified
my $bladeuser = "USERID";      # default username to apply if nothing specified
my $bladepass = "PASSW0RD";    # default password to apply if nothing specified
my %CRYPT_METHOD = ('md5' => '$1$', 'sha256' => '$5$', 'sha512' => '$6$');


# Picks the IPMI authentication to use with or deploy to a BMC
# mandatory arguments:
# noderange: a list reference to nodes (e..g. ["node1","node2"])
# optional parameters:
# ipmihash: a prefetched hash reference of relevant ipmi table data
# mphash: a prefetched hash of relevent mp table
# RETURNS:
# A hash reference with usernames and passwords, e.g.: { 'node1' => { 'username' => 'admin', 'password' => 'reallysecure' }, 'node2' => { 'username' => 'admin', 'password' => 'reallysecure' } }
sub getIPMIAuth {

    #the algorithm intended is as follows:
    #Should the target have a valid ipmi.username/ipmi.password, that is preferred above all else
    #Otherwise, if it is a blade topology, then synchronize with the management module password parameters in mpa by default
    #if still not defined, but it is a blade topology, then use 'blade' passwd table values
    #if still not defined, use 'ipmi' table values
    #if still not defined, use the defaults hardcoded into this file
    my %args      = @_;
    my $noderange = $args{noderange};
    my $ipmihash  = $args{ipmihash};
    my $mphash    = $args{mphash};
    my $key       = $args{keytype};
    unless($key) {
        $key = "ipmi";
    }
    my $tmp;
    my %authmap;
    unless ($ipmihash) { #in the event that calling code does not pass us a prefetched set of values, pull it ourselves
        my $ipmitab = xCAT::Table->new("$key", -create => 0);
        if ($ipmitab) { $ipmihash = $ipmitab->getNodesAttribs($noderange, [ 'username', 'password' ]); }
    }
    unless ($mphash) {
        my $mptab = xCAT::Table->new('mp', -create => 0);
        if ($mptab) { $mphash = $mptab->getNodesAttribs($noderange, [ 'mpa', 'id' ]); }
    }
    my $passtab = xCAT::Table->new('passwd');
    if ($passtab) {
        ($tmp) = $passtab->getAttribs({ 'key' => "$key" }, 'username', 'password');
        if (defined($tmp)) {
            $ipmiuser = $tmp->{username};
            $ipmipass = $tmp->{password};
            if ($ipmiuser or $ipmipass) {
                unless ($ipmiuser) {
                    $ipmiuser = '';
                }
                unless ($ipmipass) {
                    $ipmipass = '';
                }
            }
        }
        ($tmp) = $passtab->getAttribs({ 'key' => 'blade' }, 'username', 'password');
        if (defined($tmp)) {
            $bladeuser = $tmp->{username};
            $bladepass = $tmp->{password};
            if ($bladeuser or $bladepass) {
                unless ($bladeuser) {
                    $bladeuser = '';
                }
                unless ($bladepass) {
                    $bladepass = '';
                }
            }
        }
    }
    my $mpatab;
    if ($mphash) { $mpatab = xCAT::Table->new('mpa', -create => 0); }
    my %mpaauth;
    foreach $node (@$noderange) {
        $authmap{$node}->{username} = $ipmiuser;
        $authmap{$node}->{password} = $ipmipass;
        if ($mphash and ref $mphash->{$node} and $mphash->{$node}->[0]->{mpa}) { #this appears to be a Flex or similar config, tend to use blade credentials
            if ($bladeuser) { $authmap{$node}->{username} = $bladeuser; $authmap{$node}->{cliusername} = $bladeuser; }
            if ($bladepass) { $authmap{$node}->{password} = $bladepass; $authmap{$node}->{clipassword} = $bladepass; }
            my $mpa = $mphash->{$node}->[0]->{mpa};
            if (not $mpaauth{$mpa} and $mpatab) {
                my $mpaent = $mpatab->getNodeAttribs($mpa, [qw/username password/], prefetchcache => 1); #TODO: this might make more sense to do as one retrieval, oh well
                if (ref $mpaent and ($mpaent->{username} or $mpaent->{password})) {
                    if (!exists($mpaent->{username})) {
                        $mpaauth{$mpa}->{username} = '';
                    } else {
                        $mpaauth{$mpa}->{username} = $mpaent->{username};
                    }
                    if (!exists($mpaent->{password})) {
                        $mpaauth{$mpa}->{password} = '';
                    } else {
                        $mpaauth{$mpa}->{password} = $mpaent->{password};
                    }
                }
                $mpaauth{$mpa}->{checked} = 1; #remember we already looked this up, to save lookup time even if search was fruitless
            }
            if ($mpaauth{$mpa}->{username}) { $authmap{$node}->{username} = $mpaauth{$mpa}->{username}; $authmap{$node}->{cliusername} = $mpaauth{$mpa}->{username}; }
            if ($mpaauth{$mpa}->{password}) { $authmap{$node}->{password} = $mpaauth{$mpa}->{password}; $authmap{$node}->{clipassword} = $mpaauth{$mpa}->{password} }
        }
        unless (ref $ipmihash and ref $ipmihash->{$node}) {
            next;
        }
        if ($ipmihash->{$node}->[0]->{username} or $ipmihash->{$node}->[0]->{password}) {
            unless ($ipmihash->{$node}->[0]->{username}) {
                $authmap{$node}->{username} = '';
            } else {
                $authmap{$node}->{username} = $ipmihash->{$node}->[0]->{username};
            }
            unless ($ipmihash->{$node}->[0]->{password}) {
                $authmap{$node}->{password} = '';
            } else {
                $authmap{$node}->{password} = $ipmihash->{$node}->[0]->{password};
            }
        }
    }
    return \%authmap;
}

# Encrypt system password based on the values in passwd table
# The values for system root user will be used if query key-pair is not defined
sub crypt_system_password {
    # Just leave these arguments here for the compability reasons in Template.pm
    # which get these values by parsing the template files.
    my ($table, $kp, $fields) = @_;
    if  (!defined($table)) {
        $table = 'passwd';
    }
    if (!defined($kp)) {
        $kp->{'key'} = 'system';
        $kp->{username} = 'root';
        $fields->[0] = 'password';
        $fields->[1] = 'cryptmethod';
    }
    my $tabh = xCAT::Table->new($table);
    unless ($tabh) {
        return undef;
    }
    $data = $tabh->getAttribs($kp, @{$fields});
    if (!defined($data)) {
        xCAT::MsgUtils->message("S",
            "ERROR: Unable to get data from database table $table, key=$key");
        return undef;
    }
    $tabh->close();
    $password = $data->{'password'};
    if (!defined($password)) {
        xCAT::MsgUtils->message("S",
            "ERROR: Unable to get password from database table $table, key=$key");
        return undef;
    }
    if (($password =~ /^\$1\$/) || ($password =~ /^\$5\$/) || ($password =~ /^\$6\$/)) {
        # $password is already hashed
        $result = $password;
    } else {
        $cryptmethod = $data->{'cryptmethod'};
        if (!$cryptmethod) {
            # Use sha256 crypt method by default
            $result = crypt($password, $CRYPT_METHOD{'sha256'} . xCAT::Utils::genpassword(8));
        } elsif( defined($CRYPT_METHOD{$cryptmethod})) {
            $result = crypt($password,
                $CRYPT_METHOD{$cryptmethod} . xCAT::Utils::genpassword(8));
        } else {
            xCAT::MsgUtils->message("S", "Unsupported crypt method $cryptmethod");
            return undef;
        }
    }
    return $result;
}
