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