1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 """\
21 X2GoProxy class - proxying your connection through NX3 and others.
22
23 """
24 __NAME__ = 'x2goproxy-pylib'
25
26
27 import gevent
28 import os
29 import copy
30 import threading
31 import socket
32
33
34 import x2go.forward as forward
35 import x2go.log as log
36 import x2go.utils as utils
37 import x2go.x2go_exceptions as x2go_exceptions
38
39 from x2go.defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS
40 if _X2GOCLIENT_OS in ("Windows"):
41 import subprocess
42 else:
43 import x2go.gevent_subprocess as subprocess
44 from x2go.x2go_exceptions import WindowsError
45
46 from x2go.defaults import LOCAL_HOME as _LOCAL_HOME
47 from x2go.defaults import X2GO_SESSIONS_ROOTDIR as _X2GO_SESSIONS_ROOTDIR
48
49
51 """\
52 X2GoProxy is an abstract class for X2Go proxy connections.
53
54 This class needs to be inherited from a concrete proxy class. Only
55 currently available proxy class is: L{x2go.backends.proxy.nx3.X2GoProxy}.
56
57 """
58 PROXY_CMD = ''
59 """Proxy command. Needs to be set by a potential child class, might be OS specific."""
60 PROXY_ARGS = []
61 """Arguments to be passed to the proxy command. This needs to be set by a potential child class."""
62 PROXY_ENV = {}
63 """Provide environment variables to the proxy command. This also needs to be set by a child class."""
64
65 session_info = None
66 session_log_stdout = None
67 session_log_stderr = None
68 fw_tunnel = None
69 proxy = None
70
71 - def __init__(self, session_info=None,
72 ssh_transport=None, session_log="session.log", session_errors="session.err",
73 sessions_rootdir=os.path.join(_LOCAL_HOME, _X2GO_SESSIONS_ROOTDIR),
74 proxy_options={},
75 session_instance=None,
76 logger=None, loglevel=log.loglevel_DEFAULT, ):
77 """\
78 @param session_info: session information provided as an C{X2GoServerSessionInfo*} backend
79 instance
80 @type session_info: C{X2GoServerSessionInfo*} instance
81 @param ssh_transport: SSH transport object from C{paramiko.SSHClient}
82 @type ssh_transport: C{paramiko.Transport} instance
83 @param session_log: name of the proxy's session logfile
84 @type session_log: C{str}
85 @param sessions_rootdir: base dir where X2Go session files are stored (by default: ~/.x2go)
86 @type sessions_rootdir: C{str}
87 @param proxy_options: a set of very L{base.X2GoProxy} backend specific options; any option that is not known
88 to the L{base.X2GoProxy} backend will simply be ignored
89 @type proxy_options: C{dict}
90 @param logger: you can pass an L{X2GoLogger} object to the
91 L{base.X2GoProxy} constructor
92 @param session_instance: the L{X2GoSession} instance this L{base.X2GoProxy} instance belongs to
93 @type session_instance: L{X2GoSession} instance
94 @type logger: L{X2GoLogger} instance
95 @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be
96 constructed with the given loglevel
97 @type loglevel: int
98
99 """
100 if logger is None:
101 self.logger = log.X2GoLogger(loglevel=loglevel)
102 else:
103 self.logger = copy.deepcopy(logger)
104 self.logger.tag = __NAME__
105
106 self.sessions_rootdir = sessions_rootdir
107 self.session_info = session_info
108 self.session_name = self.session_info.name
109 self.ssh_transport = ssh_transport
110 self.session_log = session_log
111 self.session_errors = session_errors
112 self.proxy_options = proxy_options
113 self.session_instance = session_instance
114 self.PROXY_ENV = os.environ.copy()
115 self.proxy = None
116 self.subsystem = 'X2Go Proxy'
117
118 threading.Thread.__init__(self)
119 self.daemon = True
120
122 """\
123 On instance destruction make sure this proxy thread is stopped properly.
124
125 """
126 self.stop_thread()
127
149
151 """\
152 End the thread runner and tidy up.
153
154 """
155 self._keepalive = False
156
157 while self.proxy is not None:
158 gevent.sleep(.5)
159
161 """\
162 Start the X2Go proxy command. The X2Go proxy command utilizes a
163 Paramiko/SSH based forwarding tunnel (openssh -L option). This tunnel
164 gets started here and is forked into background (Greenlet/gevent).
165
166 """
167 self._keepalive = True
168 self.proxy = None
169
170 if self.session_info is None or self.ssh_transport is None:
171 return None
172
173 try:
174 os.makedirs(self.session_info.local_container)
175 except OSError, e:
176 if e.errno == 17:
177
178 pass
179
180 local_graphics_port = self.session_info.graphics_port
181 try:
182 if self.ssh_transport.getpeername()[0] in ('::1', '127.0.0.1', 'localhost', 'localhost.localdomain'):
183 local_graphics_port += 10000
184 except socket.error:
185 raise x2go_exceptions.X2GoControlSessionException('The control session has died unexpectedly.')
186 local_graphics_port = utils.detect_unused_port(preferred_port=local_graphics_port)
187
188 self.fw_tunnel = forward.start_forward_tunnel(local_port=local_graphics_port,
189 remote_port=self.session_info.graphics_port,
190 ssh_transport=self.ssh_transport,
191 session_instance=self.session_instance,
192 session_name=self.session_name,
193 subsystem=self.subsystem,
194 logger=self.logger,
195 )
196
197
198 self._update_local_proxy_socket(local_graphics_port)
199
200 self.session_log_stdout = open('%s/%s' % (self.session_info.local_container, self.session_log, ), 'a')
201 self.session_log_stderr = open('%s/%s' % (self.session_info.local_container, self.session_log, ), 'a')
202
203 _stdin = None
204 _shell = False
205 if _X2GOCLIENT_OS == 'Windows':
206 _stdin = file('nul', 'r')
207 _shell = True
208
209
210 self.process_proxy_options()
211
212
213 cmd_line = self._generate_cmdline()
214 self.logger('forking threaded subprocess: %s' % " ".join(cmd_line), loglevel=log.loglevel_DEBUG)
215
216 while not self.proxy:
217 gevent.sleep(.2)
218 p = self.proxy = subprocess.Popen(cmd_line,
219 env=self.PROXY_ENV,
220 stdin=_stdin,
221 stdout=self.session_log_stdout,
222 stderr=self.session_log_stderr,
223 shell=_shell)
224
225 while self._keepalive:
226 gevent.sleep(1)
227
228 if _X2GOCLIENT_OS == 'Windows':
229 _stdin.close()
230 try:
231 p.terminate()
232 self.logger('terminating proxy: %s' % p, loglevel=log.loglevel_DEBUG)
233 except OSError, e:
234 if e.errno == 3:
235
236 pass
237 except WindowsError:
238 pass
239
240 self._tidy_up()
241
243 """\
244 Override this method to incorporate elements from C{proxy_options}
245 into actual proxy subprocess execution.
246
247 This method (if overridden) should (by design) never fail nor raise an exception.
248 Make sure to catch all possible errors appropriately.
249
250 If you want to log ignored proxy_options then
251
252 1. remove processed proxy_options from self.proxy_options
253 2. once you have finished processing the proxy_options call
254 the parent class method L{x2go.backends.proxy.base.X2GoProxy.process_proxy_options()}
255
256 """
257
258 if self.proxy_options:
259 self.logger('ignoring non-processed proxy options: %s' % self.proxy_options, loglevel=log.loglevel_INFO)
260
263
266
268 """\
269 Start the thread runner and wait for the proxy to come up.
270
271 @return: a subprocess instance that knows about the externally started proxy command.
272 @rtype: C{obj}
273
274 """
275 threading.Thread.start(self)
276
277
278 _count = 0
279 _maxwait = 40
280 while self.proxy is None and _count < _maxwait:
281 _count += 1
282 self.logger('waiting for proxy to come up: 0.4s x %s' % _count, loglevel=log.loglevel_DEBUG)
283 gevent.sleep(.4)
284
285 if self.proxy:
286
287
288 _count = 0
289 _maxwait = 40
290 while self.fw_tunnel and (not self.fw_tunnel.is_active) and (not self.fw_tunnel.failed) and (_count < _maxwait):
291 _count += 1
292 self.logger('waiting for port fw tunnel to come up: 0.5s x %s' % _count, loglevel=log.loglevel_DEBUG)
293 gevent.sleep(.5)
294
295 return self.proxy, bool(self.proxy) and (self.fw_tunnel and self.fw_tunnel.is_active)
296
298 """\
299 Check if a proxy instance is up and running.
300
301 @return: Proxy state, C{True} for proxy being up-and-running, C{False} otherwise
302 @rtype C{bool}
303
304 """
305 return bool(self.proxy and self.proxy.poll() is None) and self.fw_tunnel.is_active
306