1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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
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
55 ''' '''
56
62
64 return "AuthenticationRequest on " + self.host + " for " + self.user + "::" + repr(self.error)
65
66
68 '''
69 The class to handle the SSH sessions to the remote hosts.
70 '''
71 SSH_SESSIONS = {}
72 SSH_AUTH = {}
73
75
76 Crypto.Cipher.AES.new = fixed_AES_new
77 self.mutex = threading.RLock()
78
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
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
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
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
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
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
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