Package node_manager_fkie :: Module ssh_handler
[frames] | no frames]

Source Code for Module node_manager_fkie.ssh_handler

  1  # Software License Agreement (BSD License) 
  2  # 
  3  # Copyright (c) 2012, Fraunhofer FKIE/US, Alexander Tiderko 
  4  # All rights reserved. 
  5  # 
  6  # Redistribution and use in source and binary forms, with or without 
  7  # modification, are permitted provided that the following conditions 
  8  # are met: 
  9  # 
 10  #  * Redistributions of source code must retain the above copyright 
 11  #    notice, this list of conditions and the following disclaimer. 
 12  #  * Redistributions in binary form must reproduce the above 
 13  #    copyright notice, this list of conditions and the following 
 14  #    disclaimer in the documentation and/or other materials provided 
 15  #    with the distribution. 
 16  #  * Neither the name of Fraunhofer nor the names of its 
 17  #    contributors may be used to endorse or promote products derived 
 18  #    from this software without specific prior written permission. 
 19  # 
 20  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 21  # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 22  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 23  # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
 24  # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
 25  # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 26  # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 27  # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 28  # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
 29  # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
 30  # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 31  # POSSIBILITY OF SUCH DAMAGE. 
 32   
 33  import sys 
 34  import shlex 
 35  import subprocess 
 36  import threading 
 37   
 38  try: 
 39    import paramiko 
 40  except Exception, e: 
 41    print  >> sys.stderr, e 
 42    sys.exit(1) 
 43   
 44  import rospy 
 45  import node_manager_fkie as nm 
 46   
47 -class AuthenticationRequest(Exception):
48 ''' ''' 49
50 - def __init__(self, user, host, error):
51 Exception.__init__(self) 52 self.user = user 53 self.host = host 54 self.error = error
55
56 - def __str__(self):
57 return "AuthenticationRequest on " + self.host + " for " + self.user + "::" + repr(self.error)
58 59
60 -class SSHhandler(object):
61 ''' 62 The class to handle the SSH sessions to the remote hosts. 63 ''' 64 USER_DEFAULT = 'robot' 65 SSH_SESSIONS = {} # host :session 66 SSH_AUTH = {} # host : user 67 68
69 - def __init__(self):
70 self.mutex = threading.RLock()
71
72 - def close(self):
73 ''' 74 Closes all open SSH sessions. Used on the closing the node manager. 75 ''' 76 # close all ssh sessions 77 for ssh in SSHhandler.SSH_SESSIONS.keys(): 78 s = SSHhandler.SSH_SESSIONS.pop(ssh) 79 if not s._transport is None: 80 s.close() 81 del s
82
83 - def ssh_exec(self, host, cmd, user=None, pw=None, auto_pw_request=True):
84 ''' 85 Executes a command on remote host. Returns the output channels with 86 execution result or None. The connection will be established using paramiko 87 SSH library. 88 @param host: the host 89 @type host: C{str} 90 @param cmd: the list with command and arguments 91 @type cmd: C{[str,...]} 92 @param user: user name 93 @param pw: the password 94 @return: the (stdin, stdout, stderr) and boolean of the executing command 95 @rtype: C{tuple(ChannelFile, ChannelFile, ChannelFile), boolean} 96 @see: U{http://www.lag.net/paramiko/docs/paramiko.SSHClient-class.html#exec_command} 97 ''' 98 with self.mutex: 99 try: 100 ssh = self._getSSH(host, self.USER_DEFAULT if user is None else user, pw, True, auto_pw_request) 101 if not ssh is None: 102 cmd_str = str(' '.join(cmd)) 103 rospy.loginfo("REMOTE execute on %s: %s", host, cmd_str) 104 (stdin, stdout, stderr) = ssh.exec_command(cmd_str) 105 stdin.close() 106 output = stdout.read() 107 error = stderr.read() 108 return output, error, True 109 else: 110 return '', '', False 111 except AuthenticationRequest as e: 112 raise 113 except Exception, e: 114 return '', str(e), False
115 116
117 - def ssh_x11_exec(self, host, cmd, title=None, user=None):
118 ''' 119 Executes a command on remote host using a terminal with X11 forwarding. 120 @todo: establish connection using paramiko SSH library. 121 @param host: the host 122 @type host: C{str} 123 @param cmd: the list with command and arguments 124 @type cmd: C{[str,...]} 125 @param title: the title of the new opened terminal, if it is None, no new terminal will be created 126 @type title: C{str} or C{None} 127 @param user: user name 128 @return: the result of C{subprocess.Popen(command)} 129 @see: U{http://docs.python.org/library/subprocess.html?highlight=subproces#subprocess} 130 ''' 131 with self.mutex: 132 try: 133 # workaround: use ssh in a terminal with X11 forward 134 user = self.USER_DEFAULT if user is None else user 135 if self.SSH_AUTH.has_key(host): 136 user = self.SSH_AUTH[host] 137 # generate string for SSH command 138 ssh_str = ' '.join(['/usr/bin/ssh', 139 '-aqtx', 140 '-oClearAllForwardings=yes', 141 '-oConnectTimeout=5', 142 '-oStrictHostKeyChecking=no', 143 '-oVerifyHostKeyDNS=no', 144 '-oCheckHostIP=no', 145 ''.join([user, '@', host])]) 146 if not title is None: 147 cmd_str = nm.terminal_cmd([ssh_str, ' '.join(cmd)], title) 148 else: 149 cmd_str = str(' '.join([ssh_str, ' '.join(cmd)])) 150 rospy.loginfo("REMOTE x11 execute on %s: %s", host, cmd_str) 151 return subprocess.Popen(shlex.split(cmd_str)) 152 except: 153 pass
154
155 - def _getSSH(self, host, user, pw=None, do_connect=True, auto_pw_request=True):
156 ''' 157 @return: the paramiko ssh client 158 @rtype: L{paramiko.SSHClient} 159 @raise BadHostKeyException: - if the server's host key could not be verified 160 @raise AuthenticationException: - if authentication failed 161 @raise SSHException: - if there was any other error connecting or establishing an SSH session 162 @raise socket.error: - if a socket error occurred while connecting 163 ''' 164 session = SSHhandler.SSH_SESSIONS.get(host, paramiko.SSHClient()) 165 if session is None or (not session.get_transport() is None and not session.get_transport().is_active()): 166 t = SSHhandler.SSH_SESSIONS.pop(host) 167 del t 168 session = SSHhandler.SSH_SESSIONS.get(host, paramiko.SSHClient()) 169 if session._transport is None: 170 session.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 171 while (session.get_transport() is None or not session.get_transport().authenticated) and do_connect: 172 try: 173 session.connect(host, username=user, password=pw, timeout=3) 174 self.SSH_AUTH[host] = user 175 except Exception, e: 176 # import traceback 177 # print traceback.format_exc() 178 if str(e) in ['Authentication failed.', 'No authentication methods available', 'Private key file is encrypted']: 179 if auto_pw_request: 180 #'print "REQUEST PW-AUTO" 181 res, user, pw = self._requestPW(user, host) 182 if not res: 183 return None 184 self.SSH_AUTH[host] = user 185 else: 186 raise AuthenticationRequest(user, host, str(e)) 187 else: 188 rospy.logwarn("ssh connection to %s failed: %s", host, str(e)) 189 raise Exception(' '.join(["ssh connection to", host, "failed:", str(e)])) 190 else: 191 SSHhandler.SSH_SESSIONS[host] = session 192 if not session.get_transport() is None: 193 session.get_transport().set_keepalive(10) 194 return session
195
196 - def _requestPW(self, user, host):
197 ''' 198 Open the dialog to input the user name and password to open an SSH connection. 199 ''' 200 from PySide import QtCore 201 from PySide import QtUiTools 202 result = False 203 pw = None 204 loader = QtUiTools.QUiLoader() 205 pwInput = loader.load(":/forms/PasswordInput.ui") 206 pwInput.setWindowTitle(''.join(['Enter the password for user ', user, ' on ', host])) 207 pwInput.userLine.setText(str(user)) 208 pwInput.pwLine.setText("") 209 pwInput.pwLine.setFocus(QtCore.Qt.OtherFocusReason) 210 if pwInput.exec_(): 211 result = True 212 user = pwInput.userLine.text() 213 pw = pwInput.pwLine.text() 214 return result, user, pw
215