# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html
#-------------------------------------------------------

=head1
  xCAT plugin package to handle makeknownhosts 

   Supported command:
         makenownhosts-> makeknownhosts 

=cut

#-------------------------------------------------------
package xCAT_plugin::makeknownhosts;
use strict;
require xCAT::Table;

require xCAT::Utils;

require xCAT::MsgUtils;
use Getopt::Long;
use Socket;
require xCAT::DSHCLI;
1;

#-------------------------------------------------------

=head3  handled_commands

Return list of commands handled by this plugin

=cut

#-------------------------------------------------------

sub handled_commands
{
    return {makeknownhosts => "makeknownhosts"};
}

#-------------------------------------------------------

=head3  process_request

  Process the command
  Get list of nodes and for each node, find all possible 
  names and ipaddresses and add an entry into the users
  /.ssh knownhost file.

=cut

#-------------------------------------------------------
sub process_request
{

    Getopt::Long::Configure("bundling");
    $Getopt::Long::ignorecase = 0;
    Getopt::Long::Configure("no_pass_through");
    my $request  = shift;
    my $callback = shift;
    my $nodes    = $request->{node};
    my $rc       = 0;

    # parse the input
    if ($request && $request->{arg}) { @ARGV = @{$request->{arg}}; }
    else { @ARGV = (); }

    my $usage = "Usage: makeknownhosts <noderange> [-r] [-V]\n       makeknownhosts -h";

    # print "argv=@ARGV\n";
    if (!GetOptions(
                     'h|help'    => \$::opt_h,
                     'V|verbose' => \$::opt_V,
                     'r|remove'  => \$::opt_r
                   )) 
    {
        my $rsp = {};
        $rsp->{data}->[0] = $usage;
        xCAT::MsgUtils->message("E", $rsp, $callback, 1);
        return 1;
    }

    # display the usage if -h
    if ($::opt_h)
    {
        my $rsp = {};
        $rsp->{data}->[0] = $usage;
        xCAT::MsgUtils->message("I", $rsp, $callback, 1);
        return 0;
    }
    if ($nodes eq "")
    {    # no noderange
        my $rsp = {};
        $rsp->{data}->[0] = "The Noderange is missing.";
        xCAT::MsgUtils->message("E", $rsp, $callback, 1);
        return 1;
    }
    my $hostkey = "/etc/xcat/hostkeys/ssh_host_rsa_key.pub";
    if (!(-e $hostkey))
    {    # the key is missing, cannot create known_hosts
        my $rsp = {};
        $rsp->{data}->[0] =
          "The keyfile:$hostkey is missing. Cannot create the known_hosts file.";
        xCAT::MsgUtils->message("E", $rsp, $callback, 1);
        return 1;
    }

    # Backup the existing known_hosts file to known_hosts.backup
    $rc = backup_known_hosts_file($callback);
    if ($rc != 0)
    {
        my $rsp = {};
        $rsp->{data}->[0] = "Error backing up known_hosts file.";
        xCAT::MsgUtils->message("E", $rsp, $callback, 1);
        return 1;

    }

    # Remove the nodes from knownhosts file
    $rc = remove_nodes_from_knownhosts($callback, $nodes);
    if ($rc != 0)
    {
        my $rsp = {};
        $rsp->{data}->[0] = "Error backing up known_hosts file.";
        xCAT::MsgUtils->message("E", $rsp, $callback, 1);
        return 1;

    }

    # if -r flag is not specified, adding the nodes back to known_hosts file
    if (!$::opt_r)
    {
        my @nodelist = @$nodes;
        foreach my $node (@nodelist)
        {
            $rc = add_known_host($node, $callback);
            if ($rc != 0)
            {
                my $rsp = {};
                $rsp->{data}->[0] = "Error building known_hosts file.";
                xCAT::MsgUtils->message("E", $rsp, $callback, 1);
                return 1;
            }
        }
     }
    return 0;
}

#-------------------------------------------------------

=head3  backup_known_hosts file 

  Backs up the old known_hosts file in roots  .ssh directory,
  if it exists. 



=cut

#-------------------------------------------------------
sub backup_known_hosts_file
{

    my ($callback) = @_;

    # Get the home directory
    my $home = xCAT::Utils->getHomeDir("root");
    if (!-d "$home/.ssh")
    {    # ssh has not been setup
        my $rsp = {};
        $rsp->{data}->[0] =
          "ssh has not been setup on this machine. .ssh directory does not existfor root id";
        xCAT::MsgUtils->message("E", $rsp, $callback, 1);
        return 1;
    }
    else
    {
        my $cmd;
        my $file = "$home/.ssh/known_hosts";
        if (-e $file)
        {
            my $newfile = $file;
            $newfile .= ".backup";
            $cmd = "cat $file > $newfile";
            xCAT::Utils->runcmd($cmd, -1);
            if ($::RUNCMD_RC != 0)
            {
                my $rsp = {};
                $rsp->{data}->[0] = "$cmd failed";
                xCAT::MsgUtils->message("E", $rsp, $callback, 1);
                return 1;
            }

        }
    }
    return 0;
}

#-------------------------------------------------------

=head3  add_known_host 

  Adds entires to $ROOTHOME/.ssh/known_hosts file 



=cut

#-------------------------------------------------------
sub add_known_host
{
    my ($node, $callback) = @_;
    my $cmd;
    my $line;
    my @ip_address;
    my $home        = xCAT::Utils->getHomeDir("root");
    my $known_hosts = "$home/.ssh/known_hosts";

    my $hostkey = "/etc/xcat/hostkeys/ssh_host_rsa_key.pub";
    my $hostname;
    my $aliases;
    my $addrtype;
    my $length;
    my @addrs;

    # get the key
    $cmd = "cat $hostkey";
    if ($::opt_V)
    {
        my $rsp = {};
        $rsp->{data}->[0] = "Running command: $cmd";
        xCAT::MsgUtils->message("I", $rsp, $callback);
    }
    my @output = xCAT::Utils->runcmd($cmd, 0);
    if ($::RUNCMD_RC != 0)
    {
        my $rsp = {};
        $rsp->{data}->[0] = "$cmd failed, cannot build known_hosts file";
        xCAT::MsgUtils->message("E", $rsp, $callback, 1);
        return 1;
    }
    chop($output[0]);
    my $ip_address = "";
    if (($hostname, $aliases, $addrtype, $length, @addrs) =
        gethostbyname($node))
    {
        foreach my $ipaddr (@addrs)
        {
            $ip_address .= inet_ntoa($ipaddr);
            $ip_address .= ",";
        }
    }
    chop($ip_address);
    my @newaliaslist = split (/ /,$aliases);

    my $aliaslist="";
    foreach my $entry (@newaliaslist) {
        $aliaslist .= "$entry,";
    }
    
    if (defined $hostname)
    {
        $line = "\"";
        $line .= "$hostname,";
        $line .= "$aliaslist";
        $line .= "$ip_address";
        $line .= " ";
        $line .= $output[0];
        $line .= "\"";
        $cmd = "echo  $line >> $known_hosts";
        if ($::opt_V)
        {
            my $rsp = {};
            $rsp->{data}->[0] = "Running command: $cmd";
            xCAT::MsgUtils->message("I", $rsp, $callback);
        }
        xCAT::Utils->runcmd($cmd, 0);

        if ($::RUNCMD_RC != 0)
        {
            my $rsp = {};
            $rsp->{data}->[0] = "$cmd failed, cannot create known_hosts";
            xCAT::MsgUtils->message("E", $rsp, $callback, 1);
            return 1;
        }
    }
    return 0;
}

#--------------------------------------------------------------------------------

=head3    remove_nodes_from_knownhosts

        Removes the nodes from SSH known hosts

        Arguments:
                \@node_hostnames

        Returns:
                1 - error
                0 - success
        Globals:
                none
        Example:
                remove_nodes_from_knownhosts(\@nodes);
        Comments:
                none

=cut

#--------------------------------------------------------------------------------

sub remove_nodes_from_knownhosts
{
    my ($callback, $ref_nodes) = @_;
    my @node_hostnames = @$ref_nodes;
    my $home           = xCAT::Utils->getHomeDir("root");
    
    my @all_names;
    
    my ($hostname, $aliases, $addrtype, $length, @addrs);
    
    # Put all the possible knownhosts entries 
    # for the nodes into @all_names
    foreach my $node (@node_hostnames)
    {
        if (!grep(/^$node$/, @all_names))
        { 
            push @all_names, $node;
        }
        if (($hostname, $aliases, $addrtype, $length, @addrs) =
            gethostbyname($node))
        {
            if (!grep(/^$hostname$/, @all_names))
            {
                push @all_names, $hostname;
            }
            foreach my $ipaddr (@addrs)
            {
                my $realip = inet_ntoa($ipaddr);
                if (!grep(/^$realip$/, @all_names))
                {
                    push @all_names, $realip;
                }
            }
            my @newaliaslist = split (/ /,$aliases);
        
            foreach my $entry (@newaliaslist) {
                if (!grep(/^$entry$/, @all_names))
                {
                    push @all_names, $entry;
                }
            }
        }
    }

    #create the sed command
    my $sed = "/bin/sed -e ";
    $sed .= "\"";
    foreach my $n (@all_names)
    {
        $sed .= "/^$n\[,| ]/d; ";
    }
    chop $sed;    #get rid of last space
    $sed .= "\"";
    my $file = "/tmp/$$";
    while (-e $file)
    {
        $file = xCAT::Utils->CreateRandomName($file);
    }
    if (-e "$home/.ssh/known_hosts")
    {
        $sed .= " $home/.ssh/known_hosts";
        $sed .= " > $file";
        my $printsed = $sed;
        $printsed =~ s/"//g;    #"
        if ($::opt_V)
        {
            my $rsp = {};
            $rsp->{data}->[0] = "Running command: $printsed";
            xCAT::MsgUtils->message("I", $rsp, $callback);
        }
        xCAT::Utils->runcmd($sed, -1);
        if ($::RUNCMD_RC != 0)
        {
            my $rsp = {};
            $rsp->{data}->[0] = "Command \"$printsed\" failed.";
            xCAT::MsgUtils->message("I", $rsp, $callback, 1);
            return 1;
        }

        my $cp = "cat $file > $home/.ssh/known_hosts";
        if ($::opt_V)
        {
            my $rsp = {};
            $rsp->{data}->[0] = "Running command: $cp";
            xCAT::MsgUtils->message("I", $rsp, $callback);
        }

        xCAT::Utils->runcmd($cp, -1);
        if ($::RUNCMD_RC != 0)
        {
            my $rsp = {};
            $rsp->{data}->[0] = "Command \"$cp\" failed.";
            xCAT::MsgUtils->message("I", $rsp, $callback, 1);
            return 1;
        }
    }
    xCAT::Utils->runcmd("rm -f $file", -1);
    return 0;
}