#!/usr/bin/perl
# !/usr/bin/env perl
# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html
BEGIN
{
    $::XCATROOT =
        $ENV{'XCATROOT'} ? $ENV{'XCATROOT'}
      : -d '/opt/xcat'   ? '/opt/xcat'
      : '/usr';
}
use lib "$::XCATROOT/lib/perl";
use strict;
use File::Basename;
use Cwd;

#use Data::Dumper;
use Getopt::Long;
require xCAT::MsgUtils;
require xCAT::DSHCLI;
use xCAT::Utils;
require xCAT::Client;
my $bname = basename($0);

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

=head1    xdsh/xdcp

This program is the client interface for xdsh/xdcp.


   xdsh/xdcp command

   This is the interface to for xdsh/xdsp
   The command can run in client/server mode (default) or in bypass mode
   where it does not use the xcat daemon xcatd.
   Bypass mode is useful, when executing the command on the Management Server
   and in particular if you want to run as a non-root id.
   Call parse_args to verify mode (client/server or  bypass)
	   and whether to use Env Variables
   Build hash and submit request
   See man page for options

=cut

#-----------------------------------------------------------------------------
# Main

# report unsupported dsh exports
&check_invalid_exports;

my $cmdref;
my $arg;
my @SaveARGV = @ARGV;
$cmdref->{cwd}->[0]     = cwd();
$cmdref->{command}->[0] = $bname;    # save my command name

my $arg = @SaveARGV[0];

if ($arg =~ /^-/)                    # no noderange
{

    # check for help, bypass, other client flags
    if ($bname eq "xdsh")
    {
        &parse_args_xdsh;
    }
    else
    {                                # xdcp
        &parse_args_xdcp;
    }
    if ($::ROOTIMG != 1)
    {    # if not running against rootimg then noderange required
        xCAT::MsgUtils->message("I",
                 "Node range not specified, see $bname man page for syntax.\n");
        exit 1;
    }
}
else
{
    $cmdref->{noderange}->[0] = $arg;    # save noderange
    my $tmp = shift(@SaveARGV);          # take if off the argument list
    if (!($cmdref->{noderange}->[0]))
    {
        xCAT::MsgUtils->message("I",
                        "Node range not specified, see man page for syntax.\n");
        exit 1;
    }
    @ARGV = @SaveARGV;                   # noderange removed for parsing
    if ($bname eq "xdsh")
    {
        &parse_args_xdsh;
    }
    else
    {                                    # xdcp
        &parse_args_xdcp;
    }
}

foreach (@SaveARGV)
{
    push(@{$cmdref->{arg}}, $_);
}

#  add environment variables, if they have not already been assigned with
#  command line flags
if (!($::NODE_RSH))
{
    if ($ENV{'DSH_NODE_RSH'})
    {
        push(@{$cmdref->{env}}, "DSH_NODE_RSH=$ENV{'DSH_NODE_RSH'}");

    }
}
if (!($::NODE_RCP))
{
    if ($ENV{'DSH_NODE_RCP'})
    {
        push(@{$cmdref->{env}}, "DSH_NODE_RCP=$ENV{'DSH_NODE_RCP'}");

    }
}
if (!($::NODE_OPTS))
{
    if ($ENV{'DSH_NODE_OPTS'})
    {
        push(@{$cmdref->{env}}, "DSH_NODE_OPTS=$ENV{'DSH_NODE_OPTS'}");

    }
}
if (!($::FANOUT))
{
    if ($ENV{'DSH_FANOUT'})
    {
        push(@{$cmdref->{env}}, "DSH_FANOUT=$ENV{'DSH_FANOUT'}");

    }
}
if (!($::TIMEOUT))
{
    if ($ENV{'DSH_TIMEOUT'})
    {
        push(@{$cmdref->{env}}, "DSH_TIMEOUT=$ENV{'DSH_TIMEOUT'}");

    }
}
if (!($::CONTEXT_SET))
{

    if ($ENV{'DSH_CONTEXT'})
    {
        push(@{$cmdref->{env}}, "DSH_CONTEXT=$ENV{'DSH_CONTEXT'}");
    }
}
if ($ENV{'DSH_REMOTE_PASSWORD'})
{
    push(@{$cmdref->{env}}, "DSH_REMOTE_PASSWORD=$ENV{'DSH_REMOTE_PASSWORD'}");
}

if ($ENV{'DSH_FROM_USERID'})
{
    push(@{$cmdref->{env}}, "DSH_FROM_USERID=$ENV{'DSH_FROM_USERID'}");
}

if ($ENV{'DSH_TO_USERID'})
{
    push(@{$cmdref->{env}}, "DSH_TO_USERID=$ENV{'DSH_TO_USERID'}");
}

if ($ENV{'DEVICETYPE'})
{
    push(@{$cmdref->{env}}, "DEVICETYPE=$ENV{'DEVICETYPE'}");
}

if ($ENV{'DSH_RSYNC_FILE'})
{
    push(@{$cmdref->{env}}, "DSH_RSYNC_FILE=$ENV{'DSH_RSYNC_FILE'}");
}
if ($ENV{'RSYNCSN'})
{
    push(@{$cmdref->{env}}, "RSYNCSN=$ENV{'RSYNCSN'}");
}

xCAT::Client::submit_request($cmdref, \&xCAT::Client::handle_response);
exit $xCAT::Client::EXITCODE;

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

=head3 parse_args_xdsh

  Parses for dsh input
  Check if the command  ask for help and display usage
  Need to check only for the -X flag
  Need to check -B flag to determine mode

=cut

#-----------------------------------------------------------------------------
sub parse_args_xdsh
{

    Getopt::Long::Configure("posix_default");
    Getopt::Long::Configure("no_gnu_compat");
    Getopt::Long::Configure("bundling");
    my %options = ();
    if (
        !GetOptions(
            'e|execute'                => \$options{'execute'},
            'f|fanout=i'               => \$options{'fanout'},
            'h|help'                   => \$options{'help'},
            'l|user=s'                 => \$options{'user'},
            'm|monitor'                => \$options{'monitor'},
            'o|node-options=s'         => \$options{'node-options'},
            'q|show-config'            => \$options{'show-config'},
            'r|node-rsh=s'             => \$options{'node-rsh'},
            'i|rootimg=s'              => \$options{'rootimg'},
            's|stream'                 => \$options{'streaming'},
            't|timeout=i'              => \$options{'timeout'},
            'v|verify'                 => \$options{'verify'},
            'z|exit-status'            => \$options{'exit-status'},
            'B|bypass'                 => \$options{'bypass'},
            'C|context=s'              => \$options{'context'},
            'E|environment=s'          => \$options{'environment'},
            'I|ignore-sig|ignoresig=s' => \$options{'ignore-signal'},
            'K|keysetup'               => \$options{'ssh-setup'},
            'L|no-locale'              => \$options{'no-locale'},
            'Q|silent'                 => \$options{'silent'},
            'S|syntax=s'               => \$options{'syntax'},
            'T|trace'                  => \$options{'trace'},
            'V|version'                => \$options{'version'},

            'devicetype=s'               => \$options{'devicetype'},
            'command-name|commandName=s' => \$options{'command-name'},
            'command-description|commandDescription=s' =>
              \$options{'command-description'},
            'X:s' => \$options{'ignore_env'}

        )
      )
    {

        xCAT::DSHCLI->usage_dsh;
        exit 1;
    }
    if ($options{'help'})
    {
        xCAT::DSHCLI->usage_dsh;
        exit 0;
    }
    if ($options{'bypass'})
    {
        $ENV{XCATBYPASS} = "yes";    # bypass xcatd
    }
    if ($options{'show-config'})
    {
        xCAT::DSHCLI->show_dsh_config;
        exit 0;
    }

    # find out who is the current user running xdsh
    #my $current_userid = getlogin(); # does not work for su
    my $current_userid = getpwuid($>);

    $ENV{DSH_FROM_USERID} = $current_userid;

    # find out who we are going to log on to the node as

    my $to_userid;
    if ($options{'user'})    # if -l option
    {
        $to_userid = $options{'user'};
    }
    else
    {
        $to_userid = $current_userid;
    }
    $ENV{DSH_TO_USERID} = $to_userid;

    # only allow -K with -l if --devicetype defined
    if (   (($options{'user'}) && ($options{'ssh-setup'}))
        && (!($options{'devicetype'})))
    {
        my $msg =
          "The -K and -l flag may only be used if --devicetype is specified\n.";
        xCAT::MsgUtils->message("E", $msg);
        exit 2;
    }
    if ($options{'ssh-setup'})    # if going to setup ssh keys
    {

        # prompt for the password for the userid on the node that will be setup
        my $userpw;
        my $msg =
          "Enter the password for the userid: $to_userid on the node where the ssh keys \nwill be updated:\n";
        xCAT::MsgUtils->message("I", $msg);
        system("stty -echo");     # turn off keyboard
        chop($userpw = <STDIN>);
        system("stty echo");      # turn on keyboard

        if ($userpw eq "")
        {                         # did not enter a password
            $msg = "Did not enter a password must abort the key exchange";
            xCAT::MsgUtils->message("E", $msg);
            exit 2;
        }
        else
        {                         # password entered pass to the server
            $ENV{DSH_REMOTE_PASSWORD} = $userpw;

        }

        # Get the home directory
        my $home = xCAT::Utils->getHomeDir($current_userid);
        $ENV{'DSH_FROM_USERID_HOME'} = $home;

        #  add config file with strict host checking no
        my $cmd = "echo \"StrictHostKeyChecking no\" >> $home/.ssh/config";
        xCAT::Utils->runcmd($cmd, 0);
        if ($::RUNCMD_RC != 0)
        {    # error
            $msg = "Error from $cmd\n";
            xCAT::MsgUtils->message("E", $msg);
        }

        my $cmd = "chmod 0600 $home/.ssh/config";
        xCAT::Utils->runcmd($cmd, 0);
        if ($::RUNCMD_RC != 0)
        {    # error
            $msg = "Error from $cmd\n";
            xCAT::MsgUtils->message("E", $msg);
        }

        # if current_userid is not "root", we need to generate the keys
        # here before becoming root while running under xcatd
        #
        if ($current_userid ne "root")
        {
            if ($::XCATROOT)
            {
                $::REMOTESHELL_EXPECT = "$::XCATROOT/sbin/remoteshell.expect";
            }
            else
            {
                $::REMOTESHELL_EXPECT = "/opt/xcat/sbin/remoteshell.expect";
            }
            $::REMOTE_SHELL = "/usr/bin/ssh";

            # generates new keys, if they do not already exist
            xCAT::Utils->runcmd("$::REMOTESHELL_EXPECT -k", 0);
            if ($::RUNCMD_RC != 0)
            {    # error
                $msg = "remoteshell.expect failed generating keys.";
                xCAT::MsgUtils->message("E", $msg);
                exit 2;

            }

        }
    }    # end setup of ssh
    if ($options{'version'})
    {
        my $version = xCAT::Utils->Version();
        $version .= "\n";
        xCAT::MsgUtils->message("N", $version);
        exit 0;
    }
    if ($options{'rootimg'})
    {
        $::ROOTIMG = 1;
    }
    if ($options{'node-rsh'})    # if set on command line, use it
    {
        $::NODE_RSH = 1;
    }
    if ($options{'node-opts'})    # if set on command line, use it
    {
        $::NODE_OPTS = 1;
    }
    if ($options{'fanout'})       # if set on command line, use it
    {
        $::FANOUT = 1;
    }
    if ($options{'timeout'})      # if set on command line, use it
    {
        $::TIMEOUT = 1;
    }
    if ($options{'context'})      # if a context is specified, use it
    {
        $::CONTEXT_SET = 1;
    }
    if (defined $options{'ignore_env'})
    {
        xCAT::DSHCLI->ignoreEnv($options{'ignore_env'});
    }

}

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

=head3 parse_args_xdcp

  Parses for dcp input
  Check if the command  ask for help and display usage
  Need to check -X flag to determine how to set Environment Variables
  Need to check -B flag to determine mode

=cut

#-----------------------------------------------------------------------------
sub parse_args_xdcp
{

    my %options = ();
    Getopt::Long::Configure("posix_default");
    Getopt::Long::Configure("no_gnu_compat");
    Getopt::Long::Configure("bundling");

    if (
        !GetOptions(
                    'f|fanout=i'       => \$options{'fanout'},
                    'F|File=s'         => \$options{'File'},
                    'h|help'           => \$options{'help'},
                    'i|rootimg=s'      => \$options{'rootimg'},
                    'l|user=s'         => \$options{'user'},
                    'n|nodes=s'        => \$options{'nodes'},
                    'o|node-options=s' => \$options{'node-options'},
                    'q|show-config'    => \$options{'show-config'},
                    'p|preserve'       => \$options{'preserve'},
                    'r|c|node-rcp=s'   => \$options{'node-rcp'},
                    's'                => \$options{'rsyncSN'},
                    't|timeout=i'      => \$options{'timeout'},
                    'v|verify'         => \$options{'verify'},
                    'B|bypass'         => \$options{'bypass'},
                    'C|context=s'      => \$options{'context'},
                    'Q|silent'         => \$options{'silent'},
                    'P|pull'           => \$options{'pull'},
                    'R|recursive'      => \$options{'recursive'},
                    'T|trace'          => \$options{'trace'},
                    'V|version'        => \$options{'version'},
                    'X:s'              => \$options{'ignore_env'}
        )
      )
    {
        xCAT::DSHCLI->usage_dcp;
        exit 1;
    }
    if ($options{'help'})
    {
        xCAT::DSHCLI->usage_dcp;
        exit 0;
    }
    if ($options{'show-config'})
    {
        xCAT::DSHCLI->show_dsh_config;
        exit 0;
    }
    if ($options{'rsyncSN'})
    {
        $ENV{'RSYNCSN'} = "yes";    # rsync file to SN
    }
    if ($options{'File'})
    {
        $ENV{'DSH_RSYNC_FILE'} = $options{'File'} ;    # rsync file
    }
    if ($options{'version'})
    {
        my $version = xCAT::Utils->Version();
        $version .= "\n";
        xCAT::MsgUtils->message("I", $version);
        exit 0;
    }
    if ($options{'rootimg'})
    {
        $::ROOTIMG = 1;
    }
    if (($options{'rootimg'}) && (!($options{'File'})))
    {
        xCAT::MsgUtils->message("E",
                               "To use -i flag you must supply the -F flag\n.");
        exit 1;
    }
    if ($options{'node-rcp'})    # if set on command line, use it
    {
        $::NODE_RCP = 1;
    }

    if ($options{'bypass'})
    {
        $ENV{XCATBYPASS} = "yes";    # bypass xcatd
    }
}

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

=head3  check_invalid_exports
   Check for unsupported dsh exports and warns

=cut

#-----------------------------------------------------------------------------
sub check_invalid_exports

{
    ##
    #  Check for unsupported Environment Variables
    #  DSH_DEVICE_LIST, DSH_DEVICE_OPTS, DSH_DEVICE_RCP,DSH_DEVICE_RSH,
    #   DSH_NODEGROUP_PATH
    #  For support Env Variables tell them to use the command line flag
    ##
    if ($ENV{'DSH_LIST'})    # if file of nodes input
    {
        xCAT::MsgUtils->message("I",
                 "DSH_LIST is set but is not supported. It will be ignored.\n");

    }
    if ($ENV{'DSH_NODE_LIST'})
    {
        xCAT::MsgUtils->message(
              "I",
              "DSH_NODE_LIST is set but is not supported. It will be ignored.\n"
              );
    }
    if ($ENV{'WCOLL'})
    {
        xCAT::MsgUtils->message("I",
                    "WCOLL is set but is not supported. It will be ignored.\n");
    }
    if ($ENV{'DSH_DEVICE_LIST'})
    {
        xCAT::MsgUtils->message(
            "I",
            "DSH_DEVICE_LIST is set but is not supported. It will be ignored.\n"
            );
    }
    if ($ENV{'DSH_DEVICE_OPTS'})
    {
        xCAT::MsgUtils->message(
            "I",
            "DSH_DEVICE_OPTS is set but is not supported. It will be ignored.\n"
            );
    }
    if ($ENV{'DSH_DEVICE_RCP'})
    {
        xCAT::MsgUtils->message(
             "I",
             "DSH_DEVICE_RCP is set but is not supported. It will be ignored.\n"
             );
    }
    if ($ENV{'DSH_DEVICE_RSH'})
    {
        xCAT::MsgUtils->message(
             "I",
             "DSH_DEVICE_RSH is set but is not supported. It will be ignored.\n"
             );
    }
    if ($ENV{'DSH_NODEGROUP_PATH'})
    {
        xCAT::MsgUtils->message(
            "I",
            "DSH_NODEGROUP_PATH is set but is not supported. It will be ignored.\n"
            );
    }
    if ($ENV{'RSYNC_RSH'})
    {
        xCAT::MsgUtils->message("I",
               " RSYNC_RSH is set but is not supported. It will be ignored.\n");
    }
    if ($ENV{'DSH_REPORT'})
    {
        xCAT::MsgUtils->message("I",
              " DSH_REPORT is set but is not supported. It will be ignored.\n");
    }
}