1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 """\
21 L{X2goSSHProxy} class - providing a forwarding tunnel for connecting to servers behind firewalls.
22
23 """
24 __NAME__ = 'x2gosshproxy-pylib'
25
26
27 import gevent
28 import os
29 import copy
30 import paramiko
31 import threading
32 import socket
33
34 import string
35 import random
36
37
38 import x2go.forward as forward
39 import x2go.checkhosts as checkhosts
40 import x2go.log as log
41 import x2go.utils as utils
42 from x2go.x2go_exceptions import *
43
44 from x2go.defaults import CURRENT_LOCAL_USER as _CURRENT_LOCAL_USER
45 from x2go.defaults import LOCAL_HOME as _LOCAL_HOME
46 from x2go.defaults import X2GO_SSH_ROOTDIR as _X2GO_SSH_ROOTDIR
47
48
50 """\
51 X2goSSHProxy can be used to proxy X2go connections through a firewall via SSH.
52
53 """
54 fw_tunnel = None
55
56 - def __init__(self, hostname=None, port=22, username=None, password=None, key_filename=None,
57 local_host='localhost', local_port=22022, remote_host='localhost', remote_port=22,
58 known_hosts=None, add_to_known_hosts=False, pkey=None,
59 sshproxy_host=None, sshproxy_port=22, sshproxy_user=None,
60 sshproxy_password=None, sshproxy_key_filename=None, sshproxy_pkey=None,
61 sshproxy_tunnel=None,
62 ssh_rootdir=os.path.join(_LOCAL_HOME, _X2GO_SSH_ROOTDIR),
63 session_instance=None,
64 logger=None, loglevel=log.loglevel_DEFAULT, ):
65 """\
66 Initialize an X2goSSHProxy instance. Use an instance of this class to tunnel X2go requests through
67 a proxying SSH server (i.e. to subLANs that are separated by firewalls or to private IP subLANs that
68 are NATted behind routers).
69
70 @param username: login user name to be used on the SSH proxy host
71 @type username: C{str}
72 @param password: user's password on the SSH proxy host
73 @type password: C{str}
74 @param key_filename: name of a SSH private key file
75 @type key_filename: C{str}
76 @param pkey: a private DSA/RSA key object (as provided by Paramiko/SSH)
77 @type pkey: C{RSA/DSA key instance}
78 @param local_host: bind SSH tunnel to the C{local_host} IP socket address (default: localhost)
79 @type local_host: C{str}
80 @param local_port: IP socket port to bind the SSH tunnel to (default; 22022)
81 @type local_port: C{int}
82 @param remote_host: remote endpoint of the SSH proxying/forwarding tunnel (default: localhost)
83 @type remote_host: C{str}
84 @param remote_port: remote endpoint's IP socket port for listening SSH daemon (default: 22)
85 @type remote_port: C{int}
86 @param known_hosts: full path to a custom C{known_hosts} file
87 @type known_hosts: C{str}
88 @param add_to_known_hosts: automatically add host keys of unknown SSH hosts to the C{known_hosts} file
89 @type add_to_known_hosts: C{bool}
90 @param hostname: alias for C{local_host}
91 @type hostname: C{str}
92 @param port: alias for C{local_port}
93 @type port: C{int}
94 @param sshproxy_host: alias for C{remote_host}
95 @type sshproxy_host: C{str}
96 @param sshproxy_port: alias for C{remote_port}
97 @type sshproxy_port: C{int}
98 @param sshproxy_user: alias for C{username}
99 @type sshproxy_user: C{str}
100 @param sshproxy_password: alias for C{password}
101 @type sshproxy_password: C{str}
102 @param sshproxy_key_filename: alias for C{key_filename}
103 @type sshproxy_key_filename: C{str}
104 @param sshproxy_pkey: alias for C{pkey}
105 @type sshproxy_pkey: C{RSA/DSA key instance} (Paramiko)
106
107 @param sshproxy_tunnel: a string of the format <local_host>:<local_port>:<remote_host>:<remote_port>
108 which will override---if used---the options: C{local_host}, C{local_port}, C{remote_host} and C{remote_port}
109 @type sshproxy_tunnel: C{str}
110
111 @param ssh_rootdir: local user's SSH base directory (default: ~/.ssh)
112 @type ssh_rootdir: C{str}
113 @param session_instance: the L{X2goSession} instance that builds up this SSH proxying tunnel
114 @type session_instance: L{X2goSession} instance
115 @param logger: you can pass an L{X2goLogger} object to the
116 L{X2goSSHProxy} constructor
117 @type logger: L{X2goLogger} instance
118 @param loglevel: if no L{X2goLogger} object has been supplied a new one will be
119 constructed with the given loglevel
120 @type loglevel: int
121
122 """
123 if logger is None:
124 self.logger = log.X2goLogger(loglevel=loglevel)
125 else:
126 self.logger = copy.deepcopy(logger)
127 self.logger.tag = __NAME__
128
129 self.hostname, self.port, self.username = hostname, port, username
130
131
132 if sshproxy_host:
133 if sshproxy_host.find(':'):
134 self.hostname = sshproxy_host.split(':')[0]
135 try: self.port = int(sshproxy_host.split(':')[1])
136 except IndexError: pass
137 else:
138 self.hostname = sshproxy_host
139
140 if sshproxy_user: self.username = sshproxy_user
141 if sshproxy_password: password = sshproxy_password
142 if sshproxy_key_filename: key_filename = sshproxy_key_filename
143 if sshproxy_pkey: pkey = sshproxy_pkey
144 if sshproxy_tunnel:
145 self.local_host, self.local_port, self.remote_host, self.remote_port = sshproxy_tunnel.split(':')
146 self.local_port = int(self.local_port)
147 self.remote_port = int(self.remote_port)
148 else:
149 self.local_host = local_host
150 self.local_port = int(local_port)
151 self.remote_host = remote_host
152 self.remote_port = int(remote_port)
153
154
155 _hostname = self.hostname
156 if _hostname == 'localhost':
157 _hostname = '127.0.0.1'
158 if self.local_host == 'localhost':
159 self.local_host = '127.0.0.1'
160 if self.remote_host == 'localhost':
161 self.remote_host = '127.0.0.1'
162
163 if username is None:
164 username = _CURRENT_LOCAL_USER
165
166 self._keepalive = True
167
168 self.ssh_rootdir = ssh_rootdir
169 paramiko.SSHClient.__init__(self)
170
171 if known_hosts:
172 utils.touch_file(known_hosts)
173 self.load_host_keys(known_hosts)
174
175 if not add_to_known_hosts and session_instance:
176 self.set_missing_host_key_policy(checkhosts.X2goInteractiveAddPolicy(caller=self, session_instance=session_instance))
177
178 if add_to_known_hosts:
179 self.set_missing_host_key_policy(paramiko.AutoAddPolicy())
180
181 try:
182 if (key_filename and os.path.exists(os.path.normpath(key_filename))) or pkey:
183 try:
184 self.connect(_hostname, port=self.port,
185 username=self.username,
186 key_filename=key_filename,
187 pkey=pkey,
188 look_for_keys=False,
189 allow_agent=False,
190 )
191 except AuthenticationException, e:
192 self.close()
193 raise X2goSSHProxyAuthenticationException('pubkey auth mechanisms both failed')
194 except:
195 self.close()
196 raise
197
198
199 else:
200
201 if not password:
202 password = "".join([random.choice(string.letters+string.digits) for x in range(1, 20)])
203 try:
204 self.connect(_hostname, port=self.port,
205 username=self.username,
206 password=password,
207 look_for_keys=False,
208 allow_agent=False,
209 )
210 except AuthenticationException:
211 self.close()
212 raise X2goSSHProxyAuthenticationException('interactive auth mechanisms failed')
213 except:
214 self.close()
215 raise
216
217 except paramiko.SSHException, e:
218 self.close()
219 raise X2goSSHProxyException(str(e))
220 except:
221 self.close()
222 raise
223
224 self.set_missing_host_key_policy(paramiko.RejectPolicy())
225 threading.Thread.__init__(self)
226 self.daemon = True
227
229 """\
230 Wraps around a Paramiko/SSH host key check.
231
232 """
233 _valid = False
234 (_valid, _hostname, _port, _fingerprint, _fingerprint_type) = checkhosts.check_ssh_host_key(self, self.hostname, port=self.port)
235 if not _valid and self.session_instance:
236 _valid = self.session_instance.HOOK_check_host_dialog(_hostname, _port, fingerprint=_fingerprint, fingerprint_type=_fingerprint_type)
237 return _valid
238
240 """\
241 Start the SSH proxying tunnel...
242
243 """
244 if self.get_transport() is not None and self.get_transport().is_authenticated():
245 self.local_port = utils.detect_unused_port(bind_address=self.local_host, preferred_port=self.local_port)
246 self.fw_tunnel = forward.start_forward_tunnel(local_host=self.local_host,
247 local_port=self.local_port,
248 remote_host=self.remote_host,
249 remote_port=self.remote_port,
250 ssh_transport=self.get_transport(),
251 logger=self.logger, )
252 self.logger('SSH proxy tunnel via [%s]:%s has been set up' % (self.hostname, self.port), loglevel=log.loglevel_NOTICE)
253 self.logger('SSH proxy tunnel startpoint is [%s]:%s, endpoint is [%s]:%s' % (self.local_host, self.local_port, self.remote_host, self.remote_port), loglevel=log.loglevel_NOTICE)
254
255 while self._keepalive:
256 gevent.sleep(.1)
257
258 else:
259 raise X2goSSHProxyException('SSH proxy connection could not retrieve an SSH transport')
260
262 """\
263 Retrieve the local IP socket port this SSH proxying tunnel is (about to) bind/bound to.
264
265 @return: local IP socket port
266 @rtype: C{int}
267 """
268 return self.local_port
269
271 """\
272 Tear down the SSH proxying tunnel.
273
274 """
275 if self.fw_tunnel is not None and self.fw_tunnel.is_active:
276 self.logger('taking down SSH proxy tunnel via [%s]:%s' % (self.hostname, self.port), loglevel=log.loglevel_NOTICE)
277 try: forward.stop_forward_tunnel(self.fw_tunnel)
278 except: pass
279 self.fw_tunnel = None
280 self._keepalive = False
281 if self.get_transport() is not None:
282 self.logger('closing SSH proxy connection to [%s]:%s' % (self.hostname, self.port), loglevel=log.loglevel_NOTICE)
283 self.close()
284 self.password = self.sshproxy_password = None
285
287 """\
288 Class desctructor.
289
290 """
291 self.stop_thread()
292