1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 """\
21 X2GoProxyBASE 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{X2GoProxyNX3}.
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",
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 C{X2GoProxy*} backend specific options; any option that is not known
88 to the C{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{X2GoProxy} constructor
92 @param session_instance: the L{X2GoSession} instance this C{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.proxy_options = proxy_options
112 self.session_instance = session_instance
113 self.PROXY_ENV = os.environ.copy()
114 self.proxy = None
115
116 threading.Thread.__init__(self)
117 self.daemon = True
118
120 """\
121 On instance destruction make sure this proxy thread is stopped properly.
122
123 """
124 self.stop_thread()
125
147
149 """\
150 End the thread runner and tidy up.
151
152 """
153 self._keepalive = False
154
155 gevent.sleep(.5)
156 self._tidy_up()
157
159 """\
160 Start the X2Go proxy command. The X2Go proxy command utilizes a
161 Paramiko/SSH based forwarding tunnel (openssh -L option). This tunnel
162 gets started here and is forked into background (Greenlet/gevent).
163
164 """
165 self._keepalive = True
166 self.proxy = None
167
168 if self.session_info is None or self.ssh_transport is None:
169 return None
170
171 try:
172 os.makedirs(self.session_info.local_container)
173 except OSError, e:
174 if e.errno == 17:
175
176 pass
177
178 local_graphics_port = self.session_info.graphics_port
179 try:
180 if self.ssh_transport.getpeername() in ('::1', '127.0.0.1', 'localhost', 'localhost.localdomain'):
181 local_graphics_port += 10000
182 except socket.error:
183 raise x2go_exceptions.X2GoControlSessionException('The control session has died unexpectedly.')
184 local_graphics_port = utils.detect_unused_port(preferred_port=local_graphics_port)
185
186 self.fw_tunnel = forward.start_forward_tunnel(local_port=local_graphics_port,
187 remote_port=self.session_info.graphics_port,
188 ssh_transport=self.ssh_transport,
189 session_instance=self.session_instance,
190 session_name=self.session_name,
191 logger=self.logger,
192 )
193
194
195 self._update_local_proxy_socket(local_graphics_port)
196 cmd_line = self._generate_cmdline()
197
198 self.session_log_stdout = open('%s/%s' % (self.session_info.local_container, self.session_log, ), 'a')
199 self.session_log_stderr = open('%s/%s' % (self.session_info.local_container, self.session_log, ), 'a')
200 self.logger('forking threaded subprocess: %s' % " ".join(cmd_line), loglevel=log.loglevel_DEBUG)
201
202 _stdin = None
203 _shell = False
204 if _X2GOCLIENT_OS == 'Windows':
205 _stdin = file('nul', 'r')
206 _shell = True
207
208
209 self.process_proxy_options()
210
211 while not self.proxy:
212 gevent.sleep(.2)
213 p = self.proxy = subprocess.Popen(cmd_line,
214 env=self.PROXY_ENV,
215 stdin=_stdin,
216 stdout=self.session_log_stdout,
217 stderr=self.session_log_stderr,
218 shell=_shell)
219
220 while self._keepalive:
221 gevent.sleep(.2)
222
223 if _X2GOCLIENT_OS == 'Windows':
224 _stdin.close()
225 try:
226 p.terminate()
227 self.logger('terminating proxy: %s' % p, loglevel=log.loglevel_DEBUG)
228 except OSError, e:
229 if e.errno == 3:
230
231 pass
232 except WindowsError:
233 pass
234
236 """\
237 Override this method to incorporate elements from C{proxy_options}
238 into actual proxy subprocess execution.
239
240 This method (if overridden) should (by design) never fail nor raise an exception.
241 Make sure to catch all possible errors appropriately.
242
243 If you want to log ignored proxy_options then
244
245 1. remove processed proxy_options from self.proxy_options
246 2. once you have finished processing the proxy_options call
247 the parent class method X2GoProxyBASE.process_proxy_options()
248
249 """
250
251 if self.proxy_options:
252 self.logger('ignoring non-processed proxy options: %s' % self.proxy_options, loglevel=log.loglevel_INFO)
253
256
259
261 """\
262 Start the thread runner and wait for the proxy to come up.
263
264 @return: a subprocess instance that knows about the externally started proxy command.
265 @rtype: C{obj}
266
267 """
268 threading.Thread.start(self)
269
270
271 _count = 0
272 _maxwait = 40
273 while self.proxy is None and _count < _maxwait:
274 _count += 1
275 self.logger('waiting for proxy to come up: 0.4s x %s' % _count, loglevel=log.loglevel_DEBUG)
276 gevent.sleep(.4)
277
278 if self.proxy:
279
280
281 _count = 0
282 _maxwait = 40
283 while (not self.fw_tunnel.is_active) and (not self.fw_tunnel.failed) and (_count < _maxwait):
284 _count += 1
285 self.logger('waiting for port fw tunnel to come up: 0.5s x %s' % _count, loglevel=log.loglevel_DEBUG)
286 gevent.sleep(.5)
287
288 return self.proxy, bool(self.proxy) and self.fw_tunnel.is_active
289
291 """\
292 Check if a proxy instance is up and running.
293
294 @return: Proxy state, C{True} for proxy being up-and-running, C{False} otherwise
295 @rtype C{bool}
296
297 """
298 return bool(self.proxy and self.proxy.poll() is None) and self.fw_tunnel.is_active
299