#!/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;