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