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