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 sys
30 import types
31 import time
32 import copy
33 import threading
34 import cStringIO
35
36
37 import x2go.forward as forward
38 import x2go.log as log
39 import x2go.utils as utils
40 import x2go.x2go_exceptions as x2go_exceptions
41
42 from x2go.defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS
43 if _X2GOCLIENT_OS in ("Windows"):
44 import subprocess
45 else:
46 import x2go.gevent_subprocess as subprocess
47 from x2go.x2go_exceptions import WindowsError
48
49 from x2go.defaults import LOCAL_HOME as _LOCAL_HOME
50 from x2go.defaults import X2GO_SESSIONS_ROOTDIR as _X2GO_SESSIONS_ROOTDIR
51
52
54 """\
55 X2goProxy is an abstract class for X2go proxy connections.
56
57 This class needs to be inherited from a concrete proxy class. Only
58 currently available proxy class is: L{X2goProxyNX3}.
59
60 """
61 PROXY_CMD = ''
62 """Proxy command. Needs to be set by a potential child class, might be OS specific."""
63 PROXY_ARGS = []
64 """Arguments to be passed to the proxy command. This needs to be set by a potential child class."""
65 PROXY_ENV = {}
66 """Provide environment variables to the proxy command. This also needs to be set by a child class."""
67
68 session_info = None
69 session_log_stdout = None
70 session_log_stderr = None
71 fw_tunnel = None
72 proxy = None
73
74 - def __init__(self, session_info=None,
75 ssh_transport=None, session_log="session.log",
76 sessions_rootdir=os.path.join(_LOCAL_HOME, _X2GO_SESSIONS_ROOTDIR),
77 proxy_options={},
78 session_instance=None,
79 logger=None, loglevel=log.loglevel_DEFAULT, ):
80 """\
81 @param session_info: session information provided as an C{X2goServerSessionInfo*} backend
82 instance
83 @type session_info: C{X2goServerSessionInfo*} instance
84 @param ssh_transport: SSH transport object from C{paramiko.SSHClient}
85 @type ssh_transport: C{paramiko.Transport} instance
86 @param session_log: name of the proxy's session logfile
87 @type session_log: C{str}
88 @param sessions_rootdir: base dir where X2go session files are stored (by default: ~/.x2go)
89 @type sessions_rootdir: C{str}
90 @param proxy_options: a set of very C{X2goProxy*} backend specific options; any option that is not known
91 to the C{X2goProxy*} backend will simply be ignored
92 @type proxy_options: C{dict}
93 @param logger: you can pass an L{X2goLogger} object to the
94 L{X2goProxy} constructor
95 @param session_instance: the L{X2goSession} instance this C{X2goProxy*} instance belongs to
96 @type session_instance: L{X2goSession} instance
97 @type logger: L{X2goLogger} instance
98 @param loglevel: if no L{X2goLogger} object has been supplied a new one will be
99 constructed with the given loglevel
100 @type loglevel: int
101
102 """
103 if logger is None:
104 self.logger = log.X2goLogger(loglevel=loglevel)
105 else:
106 self.logger = copy.deepcopy(logger)
107 self.logger.tag = __NAME__
108
109 self.sessions_rootdir = sessions_rootdir
110 self.session_info = session_info
111 self.ssh_transport = ssh_transport
112 self.session_log = session_log
113 self.proxy_options = proxy_options
114 self.session_instance = session_instance
115 self.PROXY_ENV = os.environ.copy()
116 self.proxy = None
117
118 threading.Thread.__init__(self)
119 self.daemon = True
120
122 """\
123 On instance destruction make sure the 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 gevent.sleep(.5)
158 self._tidy_up()
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.mkdir(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 local_graphics_port = utils.detect_unused_port(bind_address='', preferred_port=local_graphics_port)
182 self.fw_tunnel = forward.start_forward_tunnel(local_port=local_graphics_port,
183 remote_port=self.session_info.graphics_port,
184 ssh_transport=self.ssh_transport,
185 session_instance=self.session_instance,
186 logger=self.logger,
187 )
188
189
190 self._update_local_proxy_socket(local_graphics_port)
191 cmd_line = self._generate_cmdline()
192
193 self.session_log_stdout = open('%s/%s' % (self.session_info.local_container, self.session_log, ), 'a')
194 self.session_log_stderr = open('%s/%s' % (self.session_info.local_container, self.session_log, ), 'a')
195 self.logger('forking threaded subprocess: %s' % " ".join(cmd_line), loglevel=log.loglevel_DEBUG)
196
197 _stdin = None
198 _shell = False
199 if _X2GOCLIENT_OS == 'Windows':
200 _stdin = file('nul', 'r')
201 _shell = True
202
203
204 self.process_proxy_options()
205
206 while not self.proxy:
207 gevent.sleep(.2)
208 p = self.proxy = subprocess.Popen(cmd_line,
209 env=self.PROXY_ENV,
210 stdin=_stdin,
211 stdout=self.session_log_stdout,
212 stderr=self.session_log_stderr,
213 shell=_shell)
214
215 while self._keepalive:
216 gevent.sleep(.2)
217
218 if _X2GOCLIENT_OS == 'Windows':
219 _stdin.close()
220 try:
221 p.terminate()
222 self.logger('terminating proxy: %s' % p, loglevel=log.loglevel_DEBUG)
223 except OSError, e:
224 if e.errno == 3:
225
226 pass
227 except WindowsError:
228 pass
229
231 """\
232 Override this method to incorporate elements from C{proxy_options}
233 into actual proxy subprocess execution.
234
235 This method (if overridden) should (by design) never fail nor raise an exception.
236 Make sure to catch all possible errors appropriately.
237
238 If you want to log ignored proxy_options then
239
240 1. remove processed proxy_options from self.proxy_options
241 2. once you have finished processing the proxy_options call
242 the parent class method X2goProxyBASE.process_proxy_options()
243
244 """
245
246 if self.proxy_options:
247 self.logger('ignoring non-processed proxy options: %s' % self.proxy_options, loglevel=log.loglevel_INFO)
248
251
254
256 """\
257 Start the thread runner and wait for the proxy to come up.
258
259 @return: a subprocess instance that knows about the externally started proxy command.
260 @rtype: C{instance}
261
262 """
263 threading.Thread.start(self)
264
265
266 _count = 0
267 _maxwait = 40
268 while self.proxy is None and _count < _maxwait:
269 _count += 1
270 self.logger('waiting for proxy to come up: 0.4s x %s' % _count, loglevel=log.loglevel_DEBUG)
271 gevent.sleep(.4)
272
273
274 _count = 0
275 _maxwait = 40
276 while not self.fw_tunnel.is_active and _count < _maxwait:
277 _count += 1
278 self.logger('waiting for port fw tunnel to come up: 0.5s x %s' % _count, loglevel=log.loglevel_DEBUG)
279 gevent.sleep(.5)
280
281 return self.proxy
282
284 """\
285 Check if a proxy instance is up and running.
286
287 @return: Proxy state (C{True} or C{False})
288 @rtype C{bool}
289
290 """
291 return bool(self.proxy and self.proxy.poll() is None)
292