#!/usr/bin/perl
# IBM(c) 2012 EPL license http://www.eclipse.org/legal/epl-v10.html
package xCAT::RShellAPI;

BEGIN
{
    $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat';
}
use lib "$::XCATROOT/lib/perl";
use xCAT::MsgUtils;

#use Data::Dumper;

#######################################################

=head3
        remote_shell_command

        This routine constructs an remote shell command using the
        given arguments
        Arguments:
        	$class - Calling module name (discarded)
        	$config - Reference to remote shell command configuration hash table
        	$exec_path - Path to ssh executable
        Returns:
        	A command array for the ssh command with the appropriate
        	arguments as defined in the $config hash table
=cut

#####################################################
sub remote_shell_command {
    my ($class, $config, $exec_path) = @_;

    #print Dumper($config);

    my @command = ();

    push @command, $exec_path;

    if ($$config{'options'}) {
        my @options = split ' ', $$config{'options'};
        push @command, @options;
    }

    my @tmp;
    if ($$config{'trace'}) {
        push @command, "-v";
    }
    if ($$config{'remotecmdproto'} && ($$config{'remotecmdproto'} =~ /^telnet$/)) {
        push @command, "-t";
    }
    if ($$config{'user'} && ($$config{'user'} !~ /^none$/i)) {
        @tmp = split(' ', "-l $$config{'user'}");
        push @command, @tmp;
    }
    if ($$config{'password'} && ($$config{'password'} !~ /^none$/i)) {
        @tmp = split(' ', "-p $$config{'password'}");
        push @command, @tmp;
    }
    push @command, "$$config{'hostname'}";
    push @command, $$config{'command'};

    return @command;
}

##################################################################

=head3
        run_remote_shell_api

        This routine tried ssh then telnet to logon to a node and
        run a sequence of commands.
        Arguments:
        	$node - node name
        	$user - user login name
                $passed - user login password
                $cmds - a list of commands seperated by semicolon.
        Returns:
        	[error code, output]
                error code: 0 sucess
                            non-zero: failed. the output contains the error message.
=cut

#########################################################################
sub run_remote_shell_api {
    require xCAT::SSHInteract;
    my $node    = shift;
    my $user    = shift;
    my $passwd  = shift;
    my $telnet  = shift;
    my $verbose = shift;
    my $args    = join(" ", @_);
    my $t;
    my $prompt      = '.*[\>\#\$]\s*$';
    my $more_prompt = '(.*key to continue.*|.*--More--\s*|.*--\(more.*\)--.*$)';
    my $output;
    my $errmsg;
    my $ssh_tried = 0;
    my $nl_tried  = 0;

    if (!$telnet) {
        eval {
            $output    = "start SSH session...\n";
            $ssh_tried = 1;
            $t         = new xCAT::SSHInteract(
                -username                => $user,
                -password                => $passwd,
                -host                    => $node,
                -nokeycheck              => 1,
                -output_record_separator => "\r",
                Timeout                  => 10,
                Errmode                  => 'return',
                Prompt                   => "/$prompt/",
            );
        };
        $errmsg = $@;
        $errmsg =~ s/ at (.*) line (\d)+//g;
        $output .= "$errmsg\n";
    }

    my $rc = 1;
    if ($t) {

        #Wait for command prompt
        $t->print("\t");
        my ($prematch, $match) = $t->waitfor(Match => '/login[: ]*$/i',
            Match   => '/username[: ]*$/i',
            Match   => '/password[: ]*$/i',
            Match   => "/$prompt/",
            Errmode => "return");
        if ($verbose) {
            print "0. prematch=$prematch\n match=$match\n";
        }

        if ($match !~ /$prompt/) {
            return [ 1, $output ];
        }

    } else {

        #ssh failed.. fallback to a telnet attempt
        if ($ssh_tried) {
            $output .= "Warning: SSH failed, will try Telnet. Please set switches.protocol=telnet next time if you wish to use telnet directly.\n";
        }
        $output .= "start Telnet session...\n";
        require Net::Telnet;
        $t = new Net::Telnet(
            Timeout => 10,
            Errmode => 'return',
            Prompt  => "/$prompt/",
        );
        $rc = $t->open($node);
        if ($rc) {
            my $pw_tried   = 0;
            my $login_done = 0;
            my ($prematch, $match) = $t->waitfor(Match => '/login[: ]*$/i',
                Match   => '/username[: ]*$/i',
                Match   => '/User Name[: ]*$/i',
                Match   => '/password[: ]*$/i',
                Match   => "/$prompt/",
                Errmode => "return");
            if ($verbose) {
                print "1. prematch=$prematch\n match=$match\n";
            }
            if ($match =~ /$prompt/) {
                $login_done = 1;
            } elsif (($match =~ /User Name[: ]*$/i) || ($match =~ /username[: ]*$/i) || ($match =~ /login[: ]*$/i)) {

                # user name
                if ($user) {
                    if (!$t->put(String => "$user\n",
                            Errmode => "return")) {
                        $output .= "login disconnected\n";
                        return [ 1, $output ];
                    }
                } else {
                    $output .= "Username is required.\n";
                    return [ 1, $output ];
                }
            } elsif ($match =~ /password[: ]*$/i) {
                if ($passwd) {
                    $pw_tried = 1;
                    if (!$t->put(String => "$passwd\n",
                            Errmode => "return")) {
                        $output .= "Login disconnected\n";
                        return [ 1, $output ];
                    }
                } else {
                    $output .= "Password is required.\n";
                    return [ 1, $output ];
                }
            }

            if (!$login_done) {
                ($prematch, $match) = $t->waitfor(Match => '/login[: ]*$/i',
                    Match   => '/username[: ]*$/i',
                    Match   => '/password[: ]*$/i',
                    Match   => "/$prompt/",
                    Errmode => "return");

                if ($verbose) {
                    print "2. prematch=$prematch\n match=$match\n";
                }
                if ($match =~ /$prompt/) {
                    $login_done = 1;
                } elsif (($match =~ /username[: ]*$/i) || ($match =~ /login[: ]*$/i)) {
                    $output .= "Incorrect username.\n";
                    return [ 1, $output ];
                } elsif ($match =~ /password[: ]*$/i) {
                    if ($pw_tried) {
                        $output .= "Incorrect password.\n";
                        return [ 1, $output ];
                    }
                    if ($passwd) {
                        if (!$t->put(String => "$passwd\n",
                                Errmode => "return")) {
                            $output .= "Login disconnected\n";
                            return [ 1, $output ];
                        }
                    } else {
                        $output .= "Password is required.\n";
                        return [ 1, $output ];
                    }
                } else {

                    # for some switches like BNT, user has to type an extra new line
                    # in order to get the prompt.
                    if ($verbose) {
                        print " add a newline\n";
                    }
                    $nl_tried = 1;
                    if (!$t->put(String => "\n",
                            Errmode => "return")) {
                        $output .= "Login disconnected\n";
                        return [ 1, $output ];
                    }
                }

                if (!$login_done) {

                    #Wait for command prompt
                    ($prematch, $match) = $t->waitfor(Match => '/login[: ]*$/i',
                        Match   => '/username[: ]*$/i',
                        Match   => '/password[: ]*$/i',
                        Match   => "/$prompt/",
                        Errmode => "return");
                    if ($verbose) {
                        print "3. prematch=$prematch\n match=$match\n";
                    }

                    if ($match =~ /$prompt/) {
                        $login_done = 1;
                    } elsif ($match =~ /login[: ]*$/i or $match =~ /username[: ]*$/i or $match =~ /password[: ]*$/i) {
                        $output .= "Login failed: bad login name or password\n";
                        return [ 1, $output ];
                    } else {
                        if (!$nl_tried) {

                            # for some switches like BNT, user has to type an extra new line
                            # in order to get the prompt.
                            if ($verbose) {
                                print " add a newline\n";
                            }
                            $nl_tried = 1;
                            if (!$t->put(String => "\n",
                                    Errmode => "return")) {
                                $output .= "Login disconnected\n";
                                return [ 1, $output ];
                            }
                        }
                        else {
                            if ($t->errmsg) {
                                $output .= $t->errmsg . "\n";
                                return [ 1, $output ];

                            }
                        }
                    }
                }

                #check if the extra newline helps or not
                if (!$login_done) {

                    #Wait for command prompt
                    ($prematch, $match) = $t->waitfor(Match => "/$prompt/",
                        Errmode => "return");
                    if ($verbose) {
                        print "4. prematch=$prematch\n match=$match\n";
                    }

                    if ($match =~ /$prompt/) {
                        $login_done = 1;
                    } else {
                        if ($t->errmsg) {
                            $output .= $t->errmsg . "\n";
                            return [ 1, $output ];
                        }
                    }
                }

            }
        }
    }

    if (!$rc) {
        $output .= $t->errmsg . "\n";
        return [ 1, $output ];
    }

    $rc = 0;
    my $try_more = 0;
    my @cmd_array = split(';', $args);

    foreach my $cmd (@cmd_array) {
        if ($verbose) {
            print "command:$cmd\n";
        }

        while (1) {
            if ($try_more) {

                #This is for second and consequent pages.
                #if the user disables the paging, then this code will never run.
                #To disable paging (which is recommended),
                #they need to add a command before any other commands
                #For Cisco switch: terminal length 0
                #For BNT switch: terminal-length 0
                #For example:
                #   xdsh <swname> --type EthSwitch "terminal length 0;show vlan"
                if (!$t->put(String => " ",
                        Errmode => "return")) {
                    $output .= "Command $cmd failed: " . $t->errmsg() . "\n";
                    return [ 1, $output ];
                }
                if ($verbose) {
                    my $lastline = $t->lastline();
                    print "---lastline=$lastline\n";
                }
                ($prematch, $match) = $t->waitfor(Match => "/$more_prompt/i",
                    Match   => "/$prompt/",
                    Errmode => "return",
                    Timeout => 10);
            } else {

                # for the first page which may contian all
                if (!$t->put(String => "$cmd\n",
                        Errmode => "return")) {
                    $output .= "Command $cmd failed." . $t->errmsg() . "\n";
                    return [ 1, $output ];
                }
                if ($verbose) {
                    my $lastline = $t->lastline();
                    print "lastline=$lastline\n";
                }
                ($prematch, $match) = $t->waitfor(Match => "/$more_prompt/i",
                    Match   => "/$prompt/",
                    Match   => '/password:\s*$/i',
                    Errmode => "return",
                    Timeout => 10);
            }

            if ($verbose) {
                print "-----prematch=$prematch\nmatch=$match\n";
            }

            my $error = $t->errmsg();
            if ($error) {
                $output .= "Command $cmd failed: $error\n";
                return [ 1, $output ];
            }

            #
            if ($try_more) {

                #my @data=split("\n", $prematch);
                #shift @data;
                #shift @data;
                #shift @data;
                #$prematch=join("\n", @data);
                #add a newline at the end if not there
                my $lastchar = substr($prematch, -1, 1);
                if ($lastchar ne "\n") {
                    $prematch .= "\n";
                }
            }
            $output .= $prematch;

            if ($match =~ /$more_prompt/i) {
                $try_more = 1;
            } else {
                last;
            }
        }
    }
    $t->close();
    return [ 0, $output ];
}





1;
