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 sys
34 import shlex
35 import subprocess
36 import threading
37
38 try:
39 import paramiko
40 except Exception, e:
41 print >> sys.stderr, e
42 sys.exit(1)
43
44 import rospy
45 import node_manager_fkie as nm
46
48 ''' '''
49
51 Exception.__init__(self)
52 self.user = user
53 self.host = host
54 self.error = error
55
57 return "AuthenticationRequest on " + self.host + " for " + self.user + "::" + repr(self.error)
58
59
61 '''
62 The class to handle the SSH sessions to the remote hosts.
63 '''
64 USER_DEFAULT = 'robot'
65 SSH_SESSIONS = {}
66 SSH_AUTH = {}
67
68
70 self.mutex = threading.RLock()
71
82
83 - def ssh_exec(self, host, cmd, user=None, pw=None, auto_pw_request=True):
84 '''
85 Executes a command on remote host. Returns the output channels with
86 execution result or None. The connection will be established using paramiko
87 SSH library.
88 @param host: the host
89 @type host: C{str}
90 @param cmd: the list with command and arguments
91 @type cmd: C{[str,...]}
92 @param user: user name
93 @param pw: the password
94 @return: the (stdin, stdout, stderr) and boolean of the executing command
95 @rtype: C{tuple(ChannelFile, ChannelFile, ChannelFile), boolean}
96 @see: U{http://www.lag.net/paramiko/docs/paramiko.SSHClient-class.html#exec_command}
97 '''
98 with self.mutex:
99 try:
100 ssh = self._getSSH(host, self.USER_DEFAULT if user is None else user, pw, True, auto_pw_request)
101 if not ssh is None:
102 cmd_str = str(' '.join(cmd))
103 rospy.loginfo("REMOTE execute on %s: %s", host, cmd_str)
104 (stdin, stdout, stderr) = ssh.exec_command(cmd_str)
105 stdin.close()
106 output = stdout.read()
107 error = stderr.read()
108 return output, error, True
109 else:
110 return '', '', False
111 except AuthenticationRequest as e:
112 raise
113 except Exception, e:
114 return '', str(e), False
115
116
118 '''
119 Executes a command on remote host using a terminal with X11 forwarding.
120 @todo: establish connection using paramiko SSH library.
121 @param host: the host
122 @type host: C{str}
123 @param cmd: the list with command and arguments
124 @type cmd: C{[str,...]}
125 @param title: the title of the new opened terminal, if it is None, no new terminal will be created
126 @type title: C{str} or C{None}
127 @param user: user name
128 @return: the result of C{subprocess.Popen(command)}
129 @see: U{http://docs.python.org/library/subprocess.html?highlight=subproces#subprocess}
130 '''
131 with self.mutex:
132 try:
133
134 user = self.USER_DEFAULT if user is None else user
135 if self.SSH_AUTH.has_key(host):
136 user = self.SSH_AUTH[host]
137
138 ssh_str = ' '.join(['/usr/bin/ssh',
139 '-aqtx',
140 '-oClearAllForwardings=yes',
141 '-oConnectTimeout=5',
142 '-oStrictHostKeyChecking=no',
143 '-oVerifyHostKeyDNS=no',
144 '-oCheckHostIP=no',
145 ''.join([user, '@', host])])
146 if not title is None:
147 cmd_str = nm.terminal_cmd([ssh_str, ' '.join(cmd)], title)
148 else:
149 cmd_str = str(' '.join([ssh_str, ' '.join(cmd)]))
150 rospy.loginfo("REMOTE x11 execute on %s: %s", host, cmd_str)
151 return subprocess.Popen(shlex.split(cmd_str))
152 except:
153 pass
154
155 - def _getSSH(self, host, user, pw=None, do_connect=True, auto_pw_request=True):
156 '''
157 @return: the paramiko ssh client
158 @rtype: L{paramiko.SSHClient}
159 @raise BadHostKeyException: - if the server's host key could not be verified
160 @raise AuthenticationException: - if authentication failed
161 @raise SSHException: - if there was any other error connecting or establishing an SSH session
162 @raise socket.error: - if a socket error occurred while connecting
163 '''
164 session = SSHhandler.SSH_SESSIONS.get(host, paramiko.SSHClient())
165 if session is None or (not session.get_transport() is None and not session.get_transport().is_active()):
166 t = SSHhandler.SSH_SESSIONS.pop(host)
167 del t
168 session = SSHhandler.SSH_SESSIONS.get(host, paramiko.SSHClient())
169 if session._transport is None:
170 session.set_missing_host_key_policy(paramiko.AutoAddPolicy())
171 while (session.get_transport() is None or not session.get_transport().authenticated) and do_connect:
172 try:
173 session.connect(host, username=user, password=pw, timeout=3)
174 self.SSH_AUTH[host] = user
175 except Exception, e:
176
177
178 if str(e) in ['Authentication failed.', 'No authentication methods available', 'Private key file is encrypted']:
179 if auto_pw_request:
180
181 res, user, pw = self._requestPW(user, host)
182 if not res:
183 return None
184 self.SSH_AUTH[host] = user
185 else:
186 raise AuthenticationRequest(user, host, str(e))
187 else:
188 rospy.logwarn("ssh connection to %s failed: %s", host, str(e))
189 raise Exception(' '.join(["ssh connection to", host, "failed:", str(e)]))
190 else:
191 SSHhandler.SSH_SESSIONS[host] = session
192 if not session.get_transport() is None:
193 session.get_transport().set_keepalive(10)
194 return session
195
197 '''
198 Open the dialog to input the user name and password to open an SSH connection.
199 '''
200 from PySide import QtCore
201 from PySide import QtUiTools
202 result = False
203 pw = None
204 loader = QtUiTools.QUiLoader()
205 pwInput = loader.load(":/forms/PasswordInput.ui")
206 pwInput.setWindowTitle(''.join(['Enter the password for user ', user, ' on ', host]))
207 pwInput.userLine.setText(str(user))
208 pwInput.pwLine.setText("")
209 pwInput.pwLine.setFocus(QtCore.Qt.OtherFocusReason)
210 if pwInput.exec_():
211 result = True
212 user = pwInput.userLine.text()
213 pw = pwInput.pwLine.text()
214 return result, user, pw
215