15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""This class extends pexpect.spawn to specialize setting up SSH connections.
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)This adds methods for login, logout, and expecting the shell prompt.
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
42a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)PEXPECT LICENSE
52a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
62a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    This license is approved by the OSI and FSF as GPL-compatible.
72a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        http://opensource.org/licenses/isc-license.txt
82a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
92a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    Copyright (c) 2012, Noah Spurrier <noah@noah.org>
102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    PERMISSION TO USE, COPY, MODIFY, AND/OR DISTRIBUTE THIS SOFTWARE FOR ANY
112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    PURPOSE WITH OR WITHOUT FEE IS HEREBY GRANTED, PROVIDED THAT THE ABOVE
122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    COPYRIGHT NOTICE AND THIS PERMISSION NOTICE APPEAR IN ALL COPIES.
132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)"""
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)from pexpect import *
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import pexpect
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)import time
262a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)import os
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)__all__ = ['ExceptionPxssh', 'pxssh']
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# Exception classes used by this module.
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class ExceptionPxssh(ExceptionPexpect):
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """Raised for pxssh exceptions.
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class pxssh (spawn):
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """This class extends pexpect.spawn to specialize setting up SSH
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    connections. This adds methods for login, logout, and expecting the shell
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    prompt. It does various tricky things to handle many situations in the SSH
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    login process. For example, if the session is your first login, then pxssh
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    automatically accepts the remote certificate; or if you have public key
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    authentication setup then pxssh won't wait for the password prompt.
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    pxssh uses the shell prompt to synchronize output from the remote host. In
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    order to make this more robust it sets the shell prompt to something more
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    unique than just $ or #. This should work on most Borne/Bash or Csh style
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    shells.
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Example that runs a few commands on a remote server and prints the result::
502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        import pxssh
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        import getpass
532a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        try:
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            s = pxssh.pxssh()
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            hostname = raw_input('hostname: ')
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            username = raw_input('username: ')
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            password = getpass.getpass('password: ')
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            s.login (hostname, username, password)
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            s.sendline ('uptime')  # run a command
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            s.prompt()             # match the prompt
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            print s.before         # print everything before the prompt.
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            s.sendline ('ls -l')
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            s.prompt()
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            print s.before
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            s.sendline ('df')
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            s.prompt()
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            print s.before
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            s.logout()
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        except pxssh.ExceptionPxssh, e:
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            print "pxssh failed on login."
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            print str(e)
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    Note that if you have ssh-agent running while doing development with pxssh
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    then this can lead to a lot of confusion. Many X display managers (xdm,
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    gdm, kdm, etc.) will automatically start a GUI agent. You may see a GUI
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    dialog box popup asking for a password during development. You should turn
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    off any key agents during testing. The 'force_password' attribute will turn
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    off public key authentication. This will only work if the remote SSH server
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    is configured to allow password logins. Example of using 'force_password'
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    attribute::
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            s = pxssh.pxssh()
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            s.force_password = True
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            hostname = raw_input('hostname: ')
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            username = raw_input('username: ')
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            password = getpass.getpass('password: ')
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            s.login (hostname, username, password)
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    """
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def __init__ (self, timeout=30, maxread=2000, searchwindowsize=None, logfile=None, cwd=None, env=None):
912a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        spawn.__init__(self, None, timeout=timeout, maxread=maxread, searchwindowsize=searchwindowsize, logfile=logfile, cwd=cwd, env=env)
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.name = '<pxssh>'
952a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
962a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        #SUBTLE HACK ALERT! Note that the command that SETS the prompt uses a
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        #slightly different string than the regular expression to match it. This
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        #is because when you set the prompt the command will echo back, but we
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        #don't want to match the echoed command. So if we make the set command
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        #slightly different than the regex we eliminate the problem. To make the
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        #set command different we add a backslash in front of $. The $ doesn't
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        #need to be escaped, but it doesn't hurt and serves to make the set
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        #prompt command different than the regex.
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # used to match the command-line prompt
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.UNIQUE_PROMPT = "\[PEXPECT\][\$\#] "
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.PROMPT = self.UNIQUE_PROMPT
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # used to set shell command-line prompt to UNIQUE_PROMPT.
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.PROMPT_SET_SH = "PS1='[PEXPECT]\$ '"
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.PROMPT_SET_CSH = "set prompt='[PEXPECT]\$ '"
1122a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        self.SSH_OPTS = ("-o'RSAAuthentication=no'"
1132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                + " -o 'PubkeyAuthentication=no'")
1142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)# Disabling host key checking, makes you vulnerable to MITM attacks.
1152a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#                + " -o 'StrictHostKeyChecking=no'"
1162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#                + " -o 'UserKnownHostsFile /dev/null' ")
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # Disabling X11 forwarding gets rid of the annoying SSH_ASKPASS from
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # displaying a GUI password dialog. I have not figured out how to
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # disable only SSH_ASKPASS without also disabling X11 forwarding.
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # Unsetting SSH_ASKPASS on the remote side doesn't disable it! Annoying!
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        #self.SSH_OPTS = "-x -o'RSAAuthentication=no' -o 'PubkeyAuthentication=no'"
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.force_password = False
1232a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        self.auto_prompt_reset = True
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def levenshtein_distance(self, a,b):
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        """This calculates the Levenshtein distance between a and b.
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        """
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        n, m = len(a), len(b)
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if n > m:
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            a,b = b,a
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            n,m = m,n
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        current = range(n+1)
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        for i in range(1,m+1):
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            previous, current = current, [i]+[0]*n
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            for j in range(1,n+1):
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                add, delete = previous[j]+1, current[j-1]+1
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                change = previous[j-1]
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                if a[j-1] != b[i-1]:
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                    change = change + 1
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                current[j] = min(add, delete, change)
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return current[n]
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1452a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    def sync_original_prompt (self):
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        """This attempts to find the prompt. Basically, press enter and record
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        the response; press enter again and record the response; if the two
1492a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        responses are similar then assume we are at the original prompt. This
1502a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        is a slow function. It can take over 10 seconds. """
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # All of these timing pace values are magic.
1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # I came up with these based on what seemed reliable for
1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # connecting to a heavily loaded machine I have.
1552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        self.sendline()
1562a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        time.sleep(0.1)
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # If latency is worse than these values then this will fail.
1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1592a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        try:
1602a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            # Clear the buffer before getting the prompt.
1612a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            self.read_nonblocking(size=10000,timeout=1)
1622a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        except TIMEOUT:
1632a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            pass
1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        time.sleep(0.1)
1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.sendline()
1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        time.sleep(0.5)
1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        x = self.read_nonblocking(size=1000,timeout=1)
1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        time.sleep(0.1)
1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.sendline()
1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        time.sleep(0.5)
1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        a = self.read_nonblocking(size=1000,timeout=1)
1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        time.sleep(0.1)
1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.sendline()
1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        time.sleep(0.5)
1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        b = self.read_nonblocking(size=1000,timeout=1)
1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        ld = self.levenshtein_distance(a,b)
1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        len_a = len(a)
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if len_a == 0:
1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            return False
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if float(ld)/len_a < 0.4:
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            return True
1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return False
1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ### TODO: This is getting messy and I'm pretty sure this isn't perfect.
1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ### TODO: I need to draw a flow chart for this.
1862a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    def login (self,server,username,password='',terminal_type='ansi',original_prompt=r"[#$]",login_timeout=10,port=None,auto_prompt_reset=True,ssh_key=None):
1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        """This logs the user into the given server. It uses the
1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        'original_prompt' to try to find the prompt right after login. When it
1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        finds the prompt it immediately tries to reset the prompt to something
1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        more easily matched. The default 'original_prompt' is very optimistic
1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        and is easily fooled. It's more reliable to try to match the original
1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        prompt as exactly as possible to prevent false matches by server
1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        strings such as the "Message Of The Day". On many systems you can
1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        disable the MOTD on the remote server by creating a zero-length file
1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        called "~/.hushlogin" on the remote server. If a prompt cannot be found
1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        then this will not necessarily cause the login to fail. In the case of
1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        a timeout when looking for the prompt we assume that the original
1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        prompt was so weird that we could not match it, so we use a few tricks
2005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        to guess when we have reached the prompt. Then we hope for the best and
2015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        blindly try to reset the prompt to something more unique. If that fails
2025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        then login() raises an ExceptionPxssh exception.
2032a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
2045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        In some situations it is not possible or desirable to reset the
2055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        original prompt. In this case, set 'auto_prompt_reset' to False to
2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        inhibit setting the prompt to the UNIQUE_PROMPT. Remember that pxssh
2075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        uses a unique prompt in the prompt() method. If the original prompt is
2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        not reset then this will disable the prompt() method unless you
2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        manually set the PROMPT attribute. """
2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        ssh_options = '-q'
2125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if self.force_password:
2135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            ssh_options = ssh_options + ' ' + self.SSH_OPTS
2145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if port is not None:
2155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            ssh_options = ssh_options + ' -p %s'%(str(port))
2162a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        if ssh_key is not None:
2172a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            try:
2182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                os.path.isfile(ssh_key)
2192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            except:
2202a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)                raise ExceptionPxssh ('private ssh key does not exist')
2212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            ssh_options = ssh_options + ' -i %s' % (ssh_key)
2225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        cmd = "ssh %s -l %s %s" % (ssh_options, username, server)
2235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # This does not distinguish between a remote server 'password' prompt
2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # and a local ssh 'passphrase' prompt (for unlocking a private key).
2265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        spawn._spawn(self, cmd)
2275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        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)
2285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # First phase
2302a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        if i==0:
2315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            # New certificate -- always accept it.
2325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            # This is what you get if SSH does not have the remote host's
2335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            # public key stored in the 'known_hosts' cache.
2345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            self.sendline("yes")
2355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            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])
2365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if i==2: # password or passphrase
2375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            self.sendline(password)
2385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            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])
2395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if i==4:
2405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            self.sendline(terminal_type)
2415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            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])
2425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # Second phase
2445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if i==0:
2455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            # This is weird. This should not happen twice in a row.
2465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            self.close()
2475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            raise ExceptionPxssh ('Weird error. Got "are you sure" prompt twice.')
2482a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        elif i==1: # can occur if you have a public key pair set to authenticate.
2495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            ### TODO: May NOT be OK if expect() got tricked and matched a false prompt.
2505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            pass
2515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        elif i==2: # password prompt again
2525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            # For incorrect passwords, some ssh servers will
2535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            # ask for the password again, others return 'denied' right away.
2545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            # If we get the password prompt again then this means
2552a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            # we didn't get the password right the first time.
2565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            self.close()
2575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            raise ExceptionPxssh ('password refused')
2585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        elif i==3: # permission denied -- password was bad.
2595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            self.close()
2605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            raise ExceptionPxssh ('permission denied')
2615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        elif i==4: # terminal type again? WTF?
2625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            self.close()
2635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            raise ExceptionPxssh ('Weird error. Got "terminal type" prompt twice.')
2645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        elif i==5: # Timeout
2655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            #This is tricky... I presume that we are at the command-line prompt.
2665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            #It may be that the shell prompt was so weird that we couldn't match
2675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            #it. Or it may be that we couldn't log in for some other reason. I
2685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            #can't be sure, but it's safe to guess that we did login because if
2695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            #I presume wrong and we are not logged in then this should be caught
2705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            #later when I try to set the shell prompt.
2715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            pass
2725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        elif i==6: # Connection closed by remote host
2735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            self.close()
2745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            raise ExceptionPxssh ('connection closed')
2752a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        else: # Unexpected
2765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            self.close()
2775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            raise ExceptionPxssh ('unexpected login response')
2782a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        if not self.sync_original_prompt():
2795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            self.close()
2805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            raise ExceptionPxssh ('could not synchronize with original prompt')
2815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # We appear to be in.
2825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        # set shell prompt to something unique.
2835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if auto_prompt_reset:
2845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            if not self.set_unique_prompt():
2855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                self.close()
2865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                raise ExceptionPxssh ('could not set shell prompt\n'+self.before)
2875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return True
2885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def logout (self):
2905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        """This sends exit to the remote shell. If there are stopped jobs then
2925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        this automatically sends exit twice. """
2935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.sendline("exit")
2955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        index = self.expect([EOF, "(?i)there are stopped jobs"])
2965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if index==1:
2975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            self.sendline("exit")
2985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            self.expect(EOF)
2995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.close()
3005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3012a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)    def prompt (self, timeout=-1):
3025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        """This matches the shell prompt. This is little more than a short-cut
3045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        to the expect() method. This returns True if the shell prompt was
3052a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        matched. This returns False if a timeout was raised. Note that if you
3062a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        called login() with auto_prompt_reset set to False then before calling
3072a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        prompt() you must set the PROMPT attribute to a regex that prompt()
3082a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        will use for matching the prompt. Calling prompt() will erase the
3092a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        contents of the 'before' attribute even if no prompt is ever matched.
3102a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        If timeout is not given or it is set to -1 then self.timeout is used.
3112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        """
3125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3132a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)        if timeout == -1:
3142a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)            timeout = self.timeout
3155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        i = self.expect([self.PROMPT, TIMEOUT], timeout=timeout)
3165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if i==1:
3175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            return False
3185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return True
3192a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)
3205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    def set_unique_prompt (self):
3215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        """This sets the remote prompt to something more unique than # or $.
3235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        This makes it easier for the prompt() method to match the shell prompt
3245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        unambiguously. This method is called automatically by the login()
3255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        method, but you may want to call it manually if you somehow reset the
3265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        shell prompt. For example, if you 'su' to a different user then you
3275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        will need to manually reset the prompt. This sends shell commands to
3285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        the remote host to set the prompt, so this assumes the remote host is
3295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        ready to receive commands.
3305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        Alternatively, you may use your own prompt pattern. Just set the PROMPT
3325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        attribute to a regular expression that matches it. In this case you
3335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        should call login() with auto_prompt_reset=False; then set the PROMPT
3345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        attribute. After that the prompt() method will try to match your prompt
3355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        pattern."""
3365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.sendline ("unset PROMPT_COMMAND")
3385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.sendline (self.PROMPT_SET_SH) # sh-style
3395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        i = self.expect ([TIMEOUT, self.PROMPT], timeout=10)
3405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if i == 0: # csh-style
3415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            self.sendline (self.PROMPT_SET_CSH)
3425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            i = self.expect ([TIMEOUT, self.PROMPT], timeout=10)
3435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            if i == 0:
3445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                return False
3455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        return True
3465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)# vi:ts=4:sw=4:expandtab:ft=python:
348