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