blob: 4296c0c35b66bb1036252647cae9c7f934c3f8ee [file] [log] [blame]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
from pexpect import *
#import os, sys, getopt, shutil
class pxssh (spawn):
"""This class extends pexpect.spawn to specialize setting up SSH connections.
This adds methods for login, logout, and expecting the prompt.
It does various hacky things to handle any situation in the SSH login process.
For example, if the session is your first login, then it automatically
accepts the certificate; or if you have public key authentication setup
and you don't need a password then this is OK too.
Example usage that runs 'ls -l' on server and prints the result:
import pxssh
s = pxssh.pxssh()
if not s.login ('localhost', 'myusername', 'mypassword'):
print "SSH session failed on login."
print str(s)
else:
print "SSH session login successful"
s.sendline ('ls -l')
s.prompt() # match the prompt
print s.before # print everything before the prompt.
s.logout()
"""
def __init__ (self):
# SUBTLE HACK ALERT!
# Note that the command to set the prompt uses a slightly different string
# than the regular expression to match it. This is because when you set the
# prompt the command will echo back, but we don't want to match the echoed
# command. So if we make the set command slightly different than the regex
# we eliminate the problem. To make the set command different we add a
# backslash in front of $. The $ doesn't need to be escaped, but it doesn't
# hurt and serves to make the set command different than the regex.
self.PROMPT = "\[PEXPECT\][\$\#] " # used to match the command-line prompt.
# used to set shell command-line prompt to something more unique.
self.PROMPT_SET_SH = "PS1='[PEXPECT]\$ '"
self.PROMPT_SET_CSH = "set prompt='[PEXPECT]\$ '"
self.SET_TERM_DUMB_SH = "TERM=dumb"
self.SET_TERM_DUMB_CSH = "set TERM=dumb"
### cktan: spawn login process, but don't wait on it
def loginAsync (self,server,username=None,login_timeout=10, port=None):
cmd = 'ssh -o "BatchMode yes" -o "StrictHostKeyChecking no"'
if port:
cmd = cmd + ' -p %d' % port
if username:
cmd = cmd + ' -l %s' % username
cmd = cmd + ' ' + server
spawn.__init__(self, cmd, timeout=login_timeout)
# we don't need this since we are not sending
# password over (see comments in pexpect.py re: delaybeforesend)
self.delaybeforesend = 0
### cktan: wait for login
def loginWait(self, login_timeout=10, set_term_dumb=False):
#, "(?i)no route to host"])
echo = 'hello hello hello hello'
self.sendline('echo ' + echo)
exp = [echo, "(?i)permission denied", "(?i)terminal type",
TIMEOUT, "(?i)connection closed by remote host",
EOF]
try:
i = self.expect(exp)
if i == 0:
i = self.expect(exp)
if i == 0:
pass
elif i == 1: #permission denied
self.close()
return False
elif i == 2: #terminal type not handled
self.close()
return False
elif i == 3: #timeout
# This is tricky... presume that we are at the command-line prompt.
# It may be that the prompt was so weird that we couldn't match it.
pass
elif i == 4: #connection closed by remote host
self.close()
return False
elif i == 5: #EOF
self.close()
return False
else: #unexpected
self.close()
return False
self.sendline('exec bash')
if not self._set_unique_prompt():
self.close()
return False
if set_term_dumb: self._set_term_dumb()
return True
except EOF:
self.close()
return False
### cktan: added <port> parameter
def login (self,server,username,login_timeout=10, port=22):
self.loginAsync(server, username, login_timeout, port)
return self.loginWait(login_timeout)
def logout (self):
"""This sends exit. If there are stopped jobs then this sends exit twice.
"""
self.sendline("exit")
index = self.expect([EOF, "(?i)there are stopped jobs"])
if index==1:
self.sendline("exit")
self.expect(EOF)
def prompt (self, timeout=20):
"""This expects the prompt. This returns True if the prompt was matched.
This returns False if there was a timeout.
"""
i = self.expect([self.PROMPT, TIMEOUT], timeout=timeout)
if i==1:
return False
return True
def _set_unique_prompt (self, optional_prompt=None):
"""This attempts to reset the shell prompt to something more unique.
This makes it easier to match unambiguously.
"""
if optional_prompt is not None:
self.prompt = optional_prompt
self.sendline (self.PROMPT_SET_SH) # sh-style
i = self.expect ([TIMEOUT, self.PROMPT], timeout=10)
if i == 0: # csh-style
self.sendline (self.PROMPT_SET_CSH)
i = self.expect ([TIMEOUT, self.PROMPT], timeout=10)
if i == 0:
return 0
return 1
def _set_term_dumb(self):
self.sendline(self.SET_TERM_DUMB_SH)
i = self.expect([TIMEOUT, self.PROMPT], timeout=10)
if i == 0: # csh-style
self.sendline(self.SET_TERM_DUMB_CSH)
i = self.expect([TIMEOUT, self.PROMPT], timeout=10)
if i == 0:
return 0
return 1