18a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen"""This class extends pexpect.spawn to specialize setting up SSH connections.
28a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny ChenThis adds methods for login, logout, and expecting the shell prompt.
38a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
48a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen$Id: pxssh.py 513 2008-02-09 18:26:13Z noah $
58a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen"""
68a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
78a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chenfrom pexpect import *
88a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chenimport pexpect
98a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chenimport time
108a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
118a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen__all__ = ['ExceptionPxssh', 'pxssh']
128a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
138a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen# Exception classes used by this module.
148a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chenclass ExceptionPxssh(ExceptionPexpect):
158a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen    """Raised for pxssh exceptions.
168a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen    """
178a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
188a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chenclass pxssh (spawn):
198a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
208a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen    """This class extends pexpect.spawn to specialize setting up SSH
218a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen    connections. This adds methods for login, logout, and expecting the shell
228a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen    prompt. It does various tricky things to handle many situations in the SSH
238a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen    login process. For example, if the session is your first login, then pxssh
248a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen    automatically accepts the remote certificate; or if you have public key
258a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen    authentication setup then pxssh won't wait for the password prompt.
268a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
278a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen    pxssh uses the shell prompt to synchronize output from the remote host. In
288a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen    order to make this more robust it sets the shell prompt to something more
298a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen    unique than just $ or #. This should work on most Borne/Bash or Csh style
308a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen    shells.
318a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
328a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen    Example that runs a few commands on a remote server and prints the result::
338a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
348a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        import pxssh
358a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        import getpass
368a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        try:
378a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            s = pxssh.pxssh()
388a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            hostname = raw_input('hostname: ')
398a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            username = raw_input('username: ')
408a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            password = getpass.getpass('password: ')
418a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            s.login (hostname, username, password)
428a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            s.sendline ('uptime')  # run a command
438a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            s.prompt()             # match the prompt
448a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            print s.before         # print everything before the prompt.
458a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            s.sendline ('ls -l')
468a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            s.prompt()
478a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            print s.before
488a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            s.sendline ('df')
498a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            s.prompt()
508a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            print s.before
518a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            s.logout()
528a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        except pxssh.ExceptionPxssh, e:
538a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            print "pxssh failed on login."
548a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            print str(e)
558a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
568a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen    Note that if you have ssh-agent running while doing development with pxssh
578a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen    then this can lead to a lot of confusion. Many X display managers (xdm,
588a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen    gdm, kdm, etc.) will automatically start a GUI agent. You may see a GUI
598a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen    dialog box popup asking for a password during development. You should turn
608a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen    off any key agents during testing. The 'force_password' attribute will turn
618a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen    off public key authentication. This will only work if the remote SSH server
628a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen    is configured to allow password logins. Example of using 'force_password'
638a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen    attribute::
648a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
658a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            s = pxssh.pxssh()
668a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            s.force_password = True
678a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            hostname = raw_input('hostname: ')
688a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            username = raw_input('username: ')
698a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            password = getpass.getpass('password: ')
708a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            s.login (hostname, username, password)
718a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen    """
728a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
738a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen    def __init__ (self, timeout=30, maxread=2000, searchwindowsize=None, logfile=None, cwd=None, env=None):
748a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        spawn.__init__(self, None, timeout=timeout, maxread=maxread, searchwindowsize=searchwindowsize, logfile=logfile, cwd=cwd, env=env)
758a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
768a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        self.name = '<pxssh>'
778a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
788a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        #SUBTLE HACK ALERT! Note that the command to set the prompt uses a
798a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        #slightly different string than the regular expression to match it. This
808a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        #is because when you set the prompt the command will echo back, but we
818a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        #don't want to match the echoed command. So if we make the set command
828a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        #slightly different than the regex we eliminate the problem. To make the
838a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        #set command different we add a backslash in front of $. The $ doesn't
848a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        #need to be escaped, but it doesn't hurt and serves to make the set
858a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        #prompt command different than the regex.
868a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
878a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        # used to match the command-line prompt
888a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        self.UNIQUE_PROMPT = "\[PEXPECT\][\$\#] "
898a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        self.PROMPT = self.UNIQUE_PROMPT
908a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
918a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        # used to set shell command-line prompt to UNIQUE_PROMPT.
928a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        self.PROMPT_SET_SH = "PS1='[PEXPECT]\$ '"
938a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        self.PROMPT_SET_CSH = "set prompt='[PEXPECT]\$ '"
948a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        self.SSH_OPTS = "-o'RSAAuthentication=no' -o 'PubkeyAuthentication=no'"
958a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        # Disabling X11 forwarding gets rid of the annoying SSH_ASKPASS from
968a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        # displaying a GUI password dialog. I have not figured out how to
978a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        # disable only SSH_ASKPASS without also disabling X11 forwarding.
988a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        # Unsetting SSH_ASKPASS on the remote side doesn't disable it! Annoying!
998a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        #self.SSH_OPTS = "-x -o'RSAAuthentication=no' -o 'PubkeyAuthentication=no'"
1008a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        self.force_password = False
1018a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        self.auto_prompt_reset = True
1028a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
1038a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen    def levenshtein_distance(self, a,b):
1048a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
1058a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        """This calculates the Levenshtein distance between a and b.
1068a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        """
1078a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
1088a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        n, m = len(a), len(b)
1098a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        if n > m:
1108a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            a,b = b,a
1118a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            n,m = m,n
1128a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        current = range(n+1)
1138a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        for i in range(1,m+1):
1148a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            previous, current = current, [i]+[0]*n
1158a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            for j in range(1,n+1):
1168a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen                add, delete = previous[j]+1, current[j-1]+1
1178a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen                change = previous[j-1]
1188a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen                if a[j-1] != b[i-1]:
1198a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen                    change = change + 1
1208a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen                current[j] = min(add, delete, change)
1218a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        return current[n]
1228a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
1238a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen    def sync_original_prompt (self):
1248a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
1258a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        """This attempts to find the prompt. Basically, press enter and record
1268a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        the response; press enter again and record the response; if the two
1278a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        responses are similar then assume we are at the original prompt. This
1288a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        is a slow function. It can take over 10 seconds. """
1298a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
1308a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        # All of these timing pace values are magic.
1318a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        # I came up with these based on what seemed reliable for
1328a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        # connecting to a heavily loaded machine I have.
1338a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        # If latency is worse than these values then this will fail.
1348a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
1358a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        try:
1368a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            self.read_nonblocking(size=10000,timeout=1) # GAS: Clear out the cache before getting the prompt
1378a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        except TIMEOUT:
1388a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            pass
1398a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        time.sleep(0.1)
1408a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        self.sendline()
1418a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        time.sleep(0.5)
1428a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        x = self.read_nonblocking(size=1000,timeout=1)
1438a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        time.sleep(0.1)
1448a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        self.sendline()
1458a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        time.sleep(0.5)
1468a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        a = self.read_nonblocking(size=1000,timeout=1)
1478a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        time.sleep(0.1)
1488a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        self.sendline()
1498a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        time.sleep(0.5)
1508a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        b = self.read_nonblocking(size=1000,timeout=1)
1518a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        ld = self.levenshtein_distance(a,b)
1528a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        len_a = len(a)
1538a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        if len_a == 0:
1548a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            return False
1558a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        if float(ld)/len_a < 0.4:
1568a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            return True
1578a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        return False
1588a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
1598a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen    ### TODO: This is getting messy and I'm pretty sure this isn't perfect.
1608a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen    ### TODO: I need to draw a flow chart for this.
1618a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen    def login (self,server,username,password='',terminal_type='ansi',original_prompt=r"[#$]",login_timeout=10,port=None,auto_prompt_reset=True):
1628a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
1638a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        """This logs the user into the given server. It uses the
1648a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        'original_prompt' to try to find the prompt right after login. When it
1658a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        finds the prompt it immediately tries to reset the prompt to something
1668a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        more easily matched. The default 'original_prompt' is very optimistic
1678a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        and is easily fooled. It's more reliable to try to match the original
1688a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        prompt as exactly as possible to prevent false matches by server
1698a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        strings such as the "Message Of The Day". On many systems you can
1708a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        disable the MOTD on the remote server by creating a zero-length file
1718a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        called "~/.hushlogin" on the remote server. If a prompt cannot be found
1728a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        then this will not necessarily cause the login to fail. In the case of
1738a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        a timeout when looking for the prompt we assume that the original
1748a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        prompt was so weird that we could not match it, so we use a few tricks
1758a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        to guess when we have reached the prompt. Then we hope for the best and
1768a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        blindly try to reset the prompt to something more unique. If that fails
1778a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        then login() raises an ExceptionPxssh exception.
1788a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
1798a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        In some situations it is not possible or desirable to reset the
1808a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        original prompt. In this case, set 'auto_prompt_reset' to False to
1818a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        inhibit setting the prompt to the UNIQUE_PROMPT. Remember that pxssh
1828a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        uses a unique prompt in the prompt() method. If the original prompt is
1838a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        not reset then this will disable the prompt() method unless you
1848a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        manually set the PROMPT attribute. """
1858a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
1868a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        ssh_options = '-q'
1878a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        if self.force_password:
1888a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            ssh_options = ssh_options + ' ' + self.SSH_OPTS
1898a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        if port is not None:
1908a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            ssh_options = ssh_options + ' -p %s'%(str(port))
1918a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        cmd = "ssh %s -l %s %s" % (ssh_options, username, server)
1928a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
1938a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        # This does not distinguish between a remote server 'password' prompt
1948a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        # and a local ssh 'passphrase' prompt (for unlocking a private key).
1958a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        spawn._spawn(self, cmd)
1968a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        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)
1978a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
1988a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        # First phase
1998a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        if i==0:
2008a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            # New certificate -- always accept it.
2018a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            # This is what you get if SSH does not have the remote host's
2028a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            # public key stored in the 'known_hosts' cache.
2038a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            self.sendline("yes")
2048a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            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])
2058a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        if i==2: # password or passphrase
2068a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            self.sendline(password)
2078a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            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])
2088a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        if i==4:
2098a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            self.sendline(terminal_type)
2108a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            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])
2118a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
2128a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        # Second phase
2138a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        if i==0:
2148a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            # This is weird. This should not happen twice in a row.
2158a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            self.close()
2168a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            raise ExceptionPxssh ('Weird error. Got "are you sure" prompt twice.')
2178a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        elif i==1: # can occur if you have a public key pair set to authenticate.
2188a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            ### TODO: May NOT be OK if expect() got tricked and matched a false prompt.
2198a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            pass
2208a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        elif i==2: # password prompt again
2218a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            # For incorrect passwords, some ssh servers will
2228a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            # ask for the password again, others return 'denied' right away.
2238a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            # If we get the password prompt again then this means
2248a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            # we didn't get the password right the first time.
2258a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            self.close()
2268a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            raise ExceptionPxssh ('password refused')
2278a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        elif i==3: # permission denied -- password was bad.
2288a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            self.close()
2298a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            raise ExceptionPxssh ('permission denied')
2308a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        elif i==4: # terminal type again? WTF?
2318a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            self.close()
2328a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            raise ExceptionPxssh ('Weird error. Got "terminal type" prompt twice.')
2338a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        elif i==5: # Timeout
2348a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            #This is tricky... I presume that we are at the command-line prompt.
2358a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            #It may be that the shell prompt was so weird that we couldn't match
2368a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            #it. Or it may be that we couldn't log in for some other reason. I
2378a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            #can't be sure, but it's safe to guess that we did login because if
2388a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            #I presume wrong and we are not logged in then this should be caught
2398a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            #later when I try to set the shell prompt.
2408a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            pass
2418a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        elif i==6: # Connection closed by remote host
2428a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            self.close()
2438a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            raise ExceptionPxssh ('connection closed')
2448a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        else: # Unexpected
2458a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            self.close()
2468a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            raise ExceptionPxssh ('unexpected login response')
2478a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        if not self.sync_original_prompt():
2488a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            self.close()
2498a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            raise ExceptionPxssh ('could not synchronize with original prompt')
2508a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        # We appear to be in.
2518a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        # set shell prompt to something unique.
2528a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        if auto_prompt_reset:
2538a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            if not self.set_unique_prompt():
2548a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen                self.close()
2558a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen                raise ExceptionPxssh ('could not set shell prompt\n'+self.before)
2568a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        return True
2578a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
2588a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen    def logout (self):
2598a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
2608a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        """This sends exit to the remote shell. If there are stopped jobs then
2618a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        this automatically sends exit twice. """
2628a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
2638a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        self.sendline("exit")
2648a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        index = self.expect([EOF, "(?i)there are stopped jobs"])
2658a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        if index==1:
2668a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            self.sendline("exit")
2678a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            self.expect(EOF)
2688a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        self.close()
2698a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
2708a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen    def prompt (self, timeout=20):
2718a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
2728a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        """This matches the shell prompt. This is little more than a short-cut
2738a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        to the expect() method. This returns True if the shell prompt was
2748a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        matched. This returns False if there was a timeout. Note that if you
2758a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        called login() with auto_prompt_reset set to False then you should have
2768a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        manually set the PROMPT attribute to a regex pattern for matching the
2778a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        prompt. """
2788a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
2798a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        i = self.expect([self.PROMPT, TIMEOUT], timeout=timeout)
2808a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        if i==1:
2818a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            return False
2828a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        return True
2838a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
2848a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen    def set_unique_prompt (self):
2858a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
2868a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        """This sets the remote prompt to something more unique than # or $.
2878a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        This makes it easier for the prompt() method to match the shell prompt
2888a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        unambiguously. This method is called automatically by the login()
2898a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        method, but you may want to call it manually if you somehow reset the
2908a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        shell prompt. For example, if you 'su' to a different user then you
2918a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        will need to manually reset the prompt. This sends shell commands to
2928a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        the remote host to set the prompt, so this assumes the remote host is
2938a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        ready to receive commands.
2948a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
2958a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        Alternatively, you may use your own prompt pattern. Just set the PROMPT
2968a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        attribute to a regular expression that matches it. In this case you
2978a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        should call login() with auto_prompt_reset=False; then set the PROMPT
2988a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        attribute. After that the prompt() method will try to match your prompt
2998a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        pattern."""
3008a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
3018a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        self.sendline ("unset PROMPT_COMMAND")
3028a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        self.sendline (self.PROMPT_SET_SH) # sh-style
3038a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        i = self.expect ([TIMEOUT, self.PROMPT], timeout=10)
3048a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        if i == 0: # csh-style
3058a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            self.sendline (self.PROMPT_SET_CSH)
3068a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            i = self.expect ([TIMEOUT, self.PROMPT], timeout=10)
3078a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen            if i == 0:
3088a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen                return False
3098a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen        return True
3108a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen
3118a3c0430323c28c1fbe8ceecd2cd8e58b64a9295Johnny Chen# vi:ts=4:sw=4:expandtab:ft=python:
312