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