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