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 os 
 34  import sys 
 35  import shlex 
 36  import subprocess 
 37  import threading 
 38   
 39  try: 
 40    import paramiko 
 41  except Exception, e: 
 42    print  >> sys.stderr, e 
 43    sys.exit(1) 
 44   
 45  import rospy 
 46  import node_manager_fkie as nm 
 47   
48 -class AuthenticationRequest(Exception):
49 ''' ''' 50
51 - def __init__(self, user, host, error):
52 Exception.__init__(self) 53 self.user = user 54 self.host = host 55 self.error = error
56
57 - def __str__(self):
58 return "AuthenticationRequest on " + self.host + " for " + self.user + "::" + repr(self.error)
59 60
61 -class SSHhandler(object):
62 ''' 63 The class to handle the SSH sessions to the remote hosts. 64 ''' 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 transfer(self, host, local_file, remote_file, user=None, pw=None, auto_pw_request=False):
84 ''' 85 Copies a file to a remote location using paramiko sfpt. 86 @param host: the host 87 @type host: C{str} 88 @param local_file: the local file 89 @type local_file: str 90 @param remote_file: the remote file name 91 @type remote_file: str 92 @param user: user name 93 @param pw: the password 94 ''' 95 with self.mutex: 96 try: 97 ssh = self._getSSH(host, self.settings().default_user if user is None else user, pw, True, auto_pw_request) 98 if not ssh is None: 99 sftp = ssh.open_sftp() 100 try: 101 sftp.mkdir(os.path.dirname(remote_file)) 102 except: 103 pass 104 sftp.put(local_file, remote_file) 105 rospy.loginfo("SSH COPY %s -> %s@%s:%s", local_file, ssh._transport.get_username(), host, remote_file) 106 except AuthenticationRequest as e: 107 raise 108 except Exception, e: 109 raise
110
111 - def ssh_exec(self, host, cmd, user=None, pw=None, auto_pw_request=False):
112 ''' 113 Executes a command on remote host. Returns the output channels with 114 execution result or None. The connection will be established using paramiko 115 SSH library. 116 @param host: the host 117 @type host: C{str} 118 @param cmd: the list with command and arguments 119 @type cmd: C{[str,...]} 120 @param user: user name 121 @param pw: the password 122 @return: the (stdin, stdout, stderr) and boolean of the executing command 123 @rtype: C{tuple(ChannelFile, ChannelFile, ChannelFile), boolean} 124 @see: U{http://www.lag.net/paramiko/docs/paramiko.SSHClient-class.html#exec_command} 125 ''' 126 with self.mutex: 127 try: 128 ssh = self._getSSH(host, self.settings().default_user if user is None else user, pw, True, auto_pw_request) 129 if not ssh is None: 130 cmd_str = str(' '.join(cmd)) 131 rospy.loginfo("REMOTE execute on %s@%s: %s", ssh._transport.get_username(), host, cmd_str) 132 (stdin, stdout, stderr) = ssh.exec_command(cmd_str) 133 stdin.close() 134 output = stdout.read() 135 error = stderr.read() 136 return output, error, True 137 else: 138 return '', '', False 139 except AuthenticationRequest as e: 140 raise 141 except Exception, e: 142 return '', str(e), False
143 144
145 - def ssh_x11_exec(self, host, cmd, title=None, user=None):
146 ''' 147 Executes a command on remote host using a terminal with X11 forwarding. 148 @todo: establish connection using paramiko SSH library. 149 @param host: the host 150 @type host: C{str} 151 @param cmd: the list with command and arguments 152 @type cmd: C{[str,...]} 153 @param title: the title of the new opened terminal, if it is None, no new terminal will be created 154 @type title: C{str} or C{None} 155 @param user: user name 156 @return: the result of C{subprocess.Popen(command)} 157 @see: U{http://docs.python.org/library/subprocess.html?highlight=subproces#subprocess} 158 ''' 159 with self.mutex: 160 try: 161 # workaround: use ssh in a terminal with X11 forward 162 user = self.settings().default_user if user is None else user 163 if self.SSH_AUTH.has_key(host): 164 user = self.SSH_AUTH[host] 165 # generate string for SSH command 166 ssh_str = ' '.join(['/usr/bin/ssh', 167 '-aqtx', 168 '-oClearAllForwardings=yes', 169 '-oConnectTimeout=5', 170 '-oStrictHostKeyChecking=no', 171 '-oVerifyHostKeyDNS=no', 172 '-oCheckHostIP=no', 173 ''.join([user, '@', host])]) 174 if not title is None: 175 cmd_str = nm.settings().terminal_cmd([ssh_str, ' '.join(cmd)], title) 176 else: 177 cmd_str = str(' '.join([ssh_str, ' '.join(cmd)])) 178 rospy.loginfo("REMOTE x11 execute on %s: %s", host, cmd_str) 179 return subprocess.Popen(shlex.split(cmd_str)) 180 except: 181 pass
182
183 - def _getSSH(self, host, user, pw=None, do_connect=True, auto_pw_request=False):
184 ''' 185 @return: the paramiko ssh client 186 @rtype: L{paramiko.SSHClient} 187 @raise BadHostKeyException: - if the server's host key could not be verified 188 @raise AuthenticationException: - if authentication failed 189 @raise SSHException: - if there was any other error connecting or establishing an SSH session 190 @raise socket.error: - if a socket error occurred while connecting 191 ''' 192 session = SSHhandler.SSH_SESSIONS.get(host, paramiko.SSHClient()) 193 if session is None or (not session.get_transport() is None and (not session.get_transport().is_active() or session._transport.get_username() != user)): 194 t = SSHhandler.SSH_SESSIONS.pop(host) 195 del t 196 if self.SSH_AUTH.has_key(host): 197 del self.SSH_AUTH[host] 198 session = SSHhandler.SSH_SESSIONS.get(host, paramiko.SSHClient()) 199 if session._transport is None: 200 session.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 201 while (session.get_transport() is None or not session.get_transport().authenticated) and do_connect: 202 try: 203 session.connect(host, username=user, password=pw, timeout=3) 204 self.SSH_AUTH[host] = user 205 except Exception, e: 206 # import traceback 207 # print traceback.format_exc() 208 if str(e) in ['Authentication failed.', 'No authentication methods available', 'Private key file is encrypted']: 209 if auto_pw_request: 210 #'print "REQUEST PW-AUTO" 211 res, user, pw = self._requestPW(user, host) 212 if not res: 213 return None 214 self.SSH_AUTH[host] = user 215 else: 216 raise AuthenticationRequest(user, host, str(e)) 217 else: 218 rospy.logwarn("ssh connection to %s failed: %s", host, str(e)) 219 raise Exception(' '.join(["ssh connection to", host, "failed:", str(e)])) 220 else: 221 SSHhandler.SSH_SESSIONS[host] = session 222 if not session.get_transport() is None: 223 session.get_transport().set_keepalive(10) 224 return session
225
226 - def _requestPW(self, user, host):
227 ''' 228 Open the dialog to input the user name and password to open an SSH connection. 229 ''' 230 from python_qt_binding import QtCore 231 from python_qt_binding import loadUi 232 from python_qt_binding import QtGui 233 import os 234 result = False 235 pw = None 236 pwInput = QtGui.QDialog() 237 ui_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'PasswordInput.ui') 238 loadUi(ui_file, pwInput) 239 pwInput.setWindowTitle(''.join(['Enter the password for user ', user, ' on ', host])) 240 pwInput.userLine.setText(str(user)) 241 pwInput.pwLine.setText("") 242 pwInput.pwLine.setFocus(QtCore.Qt.OtherFocusReason) 243 if pwInput.exec_(): 244 result = True 245 user = pwInput.userLine.text() 246 pw = pwInput.pwLine.text() 247 return result, user, pw
248