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 from node_manager_fkie.common import utf8
43
44 import Crypto.Cipher.AES
45 orig_new = Crypto.Cipher.AES.new
46
47
48
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
56 ''' '''
57
63
65 return "AuthenticationRequest on " + self.host + " for " + self.user + "::" + repr(self.error)
66
67
69 '''
70 The class to handle the SSH sessions to the remote hosts.
71 '''
72 SSH_SESSIONS = {}
73 SSH_AUTH = {}
74
76
77 Crypto.Cipher.AES.new = fixed_AES_new
78 self.mutex = threading.RLock()
79
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
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
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
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
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
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
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