| Index: third_party/pexpect/pxssh.py
|
| diff --git a/third_party/pexpect/pxssh.py b/third_party/pexpect/pxssh.py
|
| index d3f46ab6fec69b8fd2abbd82adc170f13cd82280..b1354b4db94518b5922c7954da9842cb3f5a8bd6 100644
|
| --- a/third_party/pexpect/pxssh.py
|
| +++ b/third_party/pexpect/pxssh.py
|
| @@ -1,12 +1,29 @@
|
| """This class extends pexpect.spawn to specialize setting up SSH connections.
|
| This adds methods for login, logout, and expecting the shell prompt.
|
|
|
| -$Id: pxssh.py 487 2007-08-29 22:33:29Z noah $
|
| +PEXPECT LICENSE
|
| +
|
| + This license is approved by the OSI and FSF as GPL-compatible.
|
| + http://opensource.org/licenses/isc-license.txt
|
| +
|
| + Copyright (c) 2012, Noah Spurrier <noah@noah.org>
|
| + PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY
|
| + PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE
|
| + COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES.
|
| + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
| + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
| + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
| + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
| + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
| + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
| + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
| +
|
| """
|
|
|
| from pexpect import *
|
| import pexpect
|
| import time
|
| +import os
|
|
|
| __all__ = ['ExceptionPxssh', 'pxssh']
|
|
|
| @@ -30,10 +47,10 @@ class pxssh (spawn):
|
| shells.
|
|
|
| Example that runs a few commands on a remote server and prints the result::
|
| -
|
| +
|
| import pxssh
|
| import getpass
|
| - try:
|
| + try:
|
| s = pxssh.pxssh()
|
| hostname = raw_input('hostname: ')
|
| username = raw_input('username: ')
|
| @@ -71,11 +88,12 @@ class pxssh (spawn):
|
| """
|
|
|
| def __init__ (self, timeout=30, maxread=2000, searchwindowsize=None, logfile=None, cwd=None, env=None):
|
| +
|
| spawn.__init__(self, None, timeout=timeout, maxread=maxread, searchwindowsize=searchwindowsize, logfile=logfile, cwd=cwd, env=env)
|
|
|
| self.name = '<pxssh>'
|
| -
|
| - #SUBTLE HACK ALERT! Note that the command to set the prompt uses a
|
| +
|
| + #SUBTLE HACK ALERT! Note that the command that SETS 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
|
| @@ -91,14 +109,18 @@ class pxssh (spawn):
|
| # used to set shell command-line prompt to UNIQUE_PROMPT.
|
| self.PROMPT_SET_SH = "PS1='[PEXPECT]\$ '"
|
| self.PROMPT_SET_CSH = "set prompt='[PEXPECT]\$ '"
|
| - self.SSH_OPTS = "-o'RSAAuthentication=no' -o 'PubkeyAuthentication=no'"
|
| + self.SSH_OPTS = ("-o'RSAAuthentication=no'"
|
| + + " -o 'PubkeyAuthentication=no'")
|
| +# Disabling host key checking, makes you vulnerable to MITM attacks.
|
| +# + " -o 'StrictHostKeyChecking=no'"
|
| +# + " -o 'UserKnownHostsFile /dev/null' ")
|
| # Disabling X11 forwarding gets rid of the annoying SSH_ASKPASS from
|
| # displaying a GUI password dialog. I have not figured out how to
|
| # disable only SSH_ASKPASS without also disabling X11 forwarding.
|
| # Unsetting SSH_ASKPASS on the remote side doesn't disable it! Annoying!
|
| #self.SSH_OPTS = "-x -o'RSAAuthentication=no' -o 'PubkeyAuthentication=no'"
|
| self.force_password = False
|
| - self.auto_prompt_reset = True
|
| + self.auto_prompt_reset = True
|
|
|
| def levenshtein_distance(self, a,b):
|
|
|
| @@ -120,18 +142,25 @@ class pxssh (spawn):
|
| current[j] = min(add, delete, change)
|
| return current[n]
|
|
|
| - def synch_original_prompt (self):
|
| + def sync_original_prompt (self):
|
|
|
| """This attempts to find the prompt. Basically, press enter and record
|
| the response; press enter again and record the response; if the two
|
| - responses are similar then assume we are at the original prompt. """
|
| + responses are similar then assume we are at the original prompt. This
|
| + is a slow function. It can take over 10 seconds. """
|
|
|
| # All of these timing pace values are magic.
|
| # I came up with these based on what seemed reliable for
|
| # connecting to a heavily loaded machine I have.
|
| + self.sendline()
|
| + time.sleep(0.1)
|
| # If latency is worse than these values then this will fail.
|
|
|
| - self.read_nonblocking(size=10000,timeout=1) # GAS: Clear out the cache before getting the prompt
|
| + try:
|
| + # Clear the buffer before getting the prompt.
|
| + self.read_nonblocking(size=10000,timeout=1)
|
| + except TIMEOUT:
|
| + pass
|
| time.sleep(0.1)
|
| self.sendline()
|
| time.sleep(0.5)
|
| @@ -154,7 +183,7 @@ class pxssh (spawn):
|
|
|
| ### TODO: This is getting messy and I'm pretty sure this isn't perfect.
|
| ### TODO: I need to draw a flow chart for this.
|
| - def login (self,server,username,password='',terminal_type='ansi',original_prompt=r"[#$]",login_timeout=10,port=None,auto_prompt_reset=True):
|
| + def login (self,server,username,password='',terminal_type='ansi',original_prompt=r"[#$]",login_timeout=10,port=None,auto_prompt_reset=True,ssh_key=None):
|
|
|
| """This logs the user into the given server. It uses the
|
| 'original_prompt' to try to find the prompt right after login. When it
|
| @@ -171,7 +200,7 @@ class pxssh (spawn):
|
| to guess when we have reached the prompt. Then we hope for the best and
|
| blindly try to reset the prompt to something more unique. If that fails
|
| then login() raises an ExceptionPxssh exception.
|
| -
|
| +
|
| In some situations it is not possible or desirable to reset the
|
| original prompt. In this case, set 'auto_prompt_reset' to False to
|
| inhibit setting the prompt to the UNIQUE_PROMPT. Remember that pxssh
|
| @@ -184,6 +213,12 @@ class pxssh (spawn):
|
| ssh_options = ssh_options + ' ' + self.SSH_OPTS
|
| if port is not None:
|
| ssh_options = ssh_options + ' -p %s'%(str(port))
|
| + if ssh_key is not None:
|
| + try:
|
| + os.path.isfile(ssh_key)
|
| + except:
|
| + raise ExceptionPxssh ('private ssh key does not exist')
|
| + ssh_options = ssh_options + ' -i %s' % (ssh_key)
|
| cmd = "ssh %s -l %s %s" % (ssh_options, username, server)
|
|
|
| # This does not distinguish between a remote server 'password' prompt
|
| @@ -192,7 +227,7 @@ class pxssh (spawn):
|
| i = self.expect(["(?i)are you sure you want to continue connecting", original_prompt, "(?i)(?:password)|(?:passphrase for key)", "(?i)permission denied", "(?i)terminal type", TIMEOUT, "(?i)connection closed by remote host"], timeout=login_timeout)
|
|
|
| # First phase
|
| - if i==0:
|
| + if i==0:
|
| # New certificate -- always accept it.
|
| # This is what you get if SSH does not have the remote host's
|
| # public key stored in the 'known_hosts' cache.
|
| @@ -210,14 +245,14 @@ class pxssh (spawn):
|
| # This is weird. This should not happen twice in a row.
|
| self.close()
|
| raise ExceptionPxssh ('Weird error. Got "are you sure" prompt twice.')
|
| - elif i==1: # can occur if you have a public key pair set to authenticate.
|
| + elif i==1: # can occur if you have a public key pair set to authenticate.
|
| ### TODO: May NOT be OK if expect() got tricked and matched a false prompt.
|
| pass
|
| elif i==2: # password prompt again
|
| # For incorrect passwords, some ssh servers will
|
| # ask for the password again, others return 'denied' right away.
|
| # If we get the password prompt again then this means
|
| - # we didn't get the password right the first time.
|
| + # we didn't get the password right the first time.
|
| self.close()
|
| raise ExceptionPxssh ('password refused')
|
| elif i==3: # permission denied -- password was bad.
|
| @@ -237,10 +272,10 @@ class pxssh (spawn):
|
| elif i==6: # Connection closed by remote host
|
| self.close()
|
| raise ExceptionPxssh ('connection closed')
|
| - else: # Unexpected
|
| + else: # Unexpected
|
| self.close()
|
| raise ExceptionPxssh ('unexpected login response')
|
| - if not self.synch_original_prompt():
|
| + if not self.sync_original_prompt():
|
| self.close()
|
| raise ExceptionPxssh ('could not synchronize with original prompt')
|
| # We appear to be in.
|
| @@ -263,20 +298,25 @@ class pxssh (spawn):
|
| self.expect(EOF)
|
| self.close()
|
|
|
| - def prompt (self, timeout=20):
|
| + def prompt (self, timeout=-1):
|
|
|
| """This matches the shell prompt. This is little more than a short-cut
|
| to the expect() method. This returns True if the shell prompt was
|
| - matched. This returns False if there was a timeout. Note that if you
|
| - called login() with auto_prompt_reset set to False then you should have
|
| - manually set the PROMPT attribute to a regex pattern for matching the
|
| - prompt. """
|
| + matched. This returns False if a timeout was raised. Note that if you
|
| + called login() with auto_prompt_reset set to False then before calling
|
| + prompt() you must set the PROMPT attribute to a regex that prompt()
|
| + will use for matching the prompt. Calling prompt() will erase the
|
| + contents of the 'before' attribute even if no prompt is ever matched.
|
| + If timeout is not given or it is set to -1 then self.timeout is used.
|
| + """
|
|
|
| + if timeout == -1:
|
| + timeout = self.timeout
|
| i = self.expect([self.PROMPT, TIMEOUT], timeout=timeout)
|
| if i==1:
|
| return False
|
| return True
|
| -
|
| +
|
| def set_unique_prompt (self):
|
|
|
| """This sets the remote prompt to something more unique than # or $.
|
|
|