1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 """\
21 X2goControlSessionSTDOUT class - core functions for handling your individual X2Go sessions.
22
23 This backend handles X2Go server implementations that respond via server-side STDOUT.
24
25 """
26 __NAME__ = 'x2gocontrolsession-pylib'
27
28
29 import os
30 import types
31 import paramiko
32 import gevent
33 import copy
34 import string
35 import random
36 import re
37 import locale
38 import threading
39 import cStringIO
40
41 from gevent import socket
42
43
44 import x2go.sshproxy as sshproxy
45 import x2go.log as log
46 import x2go.utils as utils
47 import x2go.x2go_exceptions as x2go_exceptions
48 import x2go.defaults as defaults
49 import x2go.checkhosts as checkhosts
50
51 from x2go.backends.terminal import X2goTerminalSession as _X2goTerminalSession
52 from x2go.backends.info import X2goServerSessionInfo as _X2goServerSessionInfo
53 from x2go.backends.info import X2goServerSessionList as _X2goServerSessionList
54 from x2go.backends.proxy import X2goProxy as _X2goProxy
55
56 import x2go._paramiko
57 x2go._paramiko.monkey_patch_paramiko()
60 """\
61 In command strings X2Go server scripts expect blanks being rewritten to ,,X2GO_SPACE_CHAR''.
62 Commands get rewritten in the terminal sessions. This re-rewrite function helps
63 displaying command string in log output.
64
65 @param cmd: command that has to be rewritten for log output
66 @type cmd: C{str}
67
68 @return: the command with ,,X2GO_SPACE_CHAR'' re-replaced by blanks
69 @rtype: C{str}
70
71 """
72
73 if cmd:
74 cmd = cmd.replace("X2GO_SPACE_CHAR", " ")
75 return cmd
76
78 """\
79 In command strings Python X2Go replaces some macros with actual values:
80
81 - X2GO_USER -> the user name under which the user is authenticated via SSH
82 - X2GO_PASSWORD -> the password being used for SSH authentication
83
84 Both macros can be used to on-the-fly authenticate via RDP.
85
86 @param cmd: command that is to be sent to an X2Go server script
87 @type cmd: C{str}
88 @param user: the SSH authenticated user name
89 @type password: the password being used for SSH authentication
90
91 @return: the command with macros replaced
92 @rtype: C{str}
93
94 """
95
96
97 if cmd and user:
98 cmd = cmd.replace('X2GO_USER', user)
99
100
101 if cmd and password:
102 cmd = cmd.replace('X2GO_PASSWORD', password)
103 return cmd
104
107 """\
108 In the Python X2Go concept, X2Go sessions fall into two parts: a control session and one to many terminal sessions.
109
110 The control session handles the SSH based communication between server and client. It is mainly derived from
111 C{paramiko.SSHClient} and adds on X2Go related functionality.
112
113 """
114 associated_terminals = None
115
116 - def __init__(self,
117 profile_name='UNKNOWN',
118 add_to_known_hosts=False,
119 known_hosts=None,
120 forward_sshagent=False,
121 terminal_backend=_X2goTerminalSession,
122 info_backend=_X2goServerSessionInfo,
123 list_backend=_X2goServerSessionList,
124 proxy_backend=_X2goProxy,
125 client_rootdir=os.path.join(defaults.LOCAL_HOME, defaults.X2GO_CLIENT_ROOTDIR),
126 sessions_rootdir=os.path.join(defaults.LOCAL_HOME, defaults.X2GO_SESSIONS_ROOTDIR),
127 ssh_rootdir=os.path.join(defaults.LOCAL_HOME, defaults.X2GO_SSH_ROOTDIR),
128 logger=None, loglevel=log.loglevel_DEFAULT,
129 published_applications_no_submenus=0,
130 **kwargs):
131 """\
132 Initialize an X2Go control session. For each connected session profile there will be one SSH-based
133 control session and one to many terminal sessions that all server-client-communicate via this one common control
134 session.
135
136 A control session normally gets set up by an L{X2goSession} instance. Do not use it directly!!!
137
138 @param profile_name: the profile name of the session profile this control session works for
139 @type profile_name: C{str}
140 @param add_to_known_hosts: Auto-accept server host validity?
141 @type add_to_known_hosts: C{bool}
142 @param known_hosts: the underlying Paramiko/SSH systems C{known_hosts} file
143 @type known_hosts: C{str}
144 @param forward_sshagent: forward SSH agent authentication requests to the X2Go client-side
145 @type forward_sshagent: C{bool}
146 @param terminal_backend: X2Go terminal session backend to use
147 @type terminal_backend: C{class}
148 @param info_backend: backend for handling storage of server session information
149 @type info_backend: C{X2goServerSessionInfo*} instance
150 @param list_backend: backend for handling storage of session list information
151 @type list_backend: C{X2goServerSessionList*} instance
152 @param proxy_backend: backend for handling the X-proxy connections
153 @type proxy_backend: C{X2goProxy*} instance
154 @param client_rootdir: client base dir (default: ~/.x2goclient)
155 @type client_rootdir: C{str}
156 @param sessions_rootdir: sessions base dir (default: ~/.x2go)
157 @type sessions_rootdir: C{str}
158 @param ssh_rootdir: ssh base dir (default: ~/.ssh)
159 @type ssh_rootdir: C{str}
160 @param published_applications_no_submenus: published applications menus with less items than C{published_applications_no_submenus}
161 are rendered without submenus
162 @type published_applications_no_submenus: C{int}
163 @param logger: you can pass an L{X2goLogger} object to the
164 L{X2goControlSessionSTDOUT} constructor
165 @type logger: L{X2goLogger} instance
166 @param loglevel: if no L{X2goLogger} object has been supplied a new one will be
167 constructed with the given loglevel
168 @type loglevel: C{int}
169 @param kwargs: catch any non-defined parameters in C{kwargs}
170 @type kwargs: C{dict}
171
172 """
173 self.associated_terminals = {}
174 self.terminated_terminals = []
175
176 self.profile_name = profile_name
177 self.add_to_known_hosts = add_to_known_hosts
178 self.known_hosts = known_hosts
179 self.forward_sshagent = forward_sshagent
180
181 self.hostname = None
182 self.port = None
183
184 self.sshproxy_session = None
185
186 self._session_auth_rsakey = None
187 self._remote_home = None
188 self._remote_group = {}
189 self._remote_username = None
190 self._remote_peername = None
191
192 self._server_features = None
193
194 if logger is None:
195 self.logger = log.X2goLogger(loglevel=loglevel)
196 else:
197 self.logger = copy.deepcopy(logger)
198 self.logger.tag = __NAME__
199
200 self._terminal_backend = terminal_backend
201 self._info_backend = info_backend
202 self._list_backend = list_backend
203 self._proxy_backend = proxy_backend
204
205 self.client_rootdir = client_rootdir
206 self.sessions_rootdir = sessions_rootdir
207 self.ssh_rootdir = ssh_rootdir
208
209 self._published_applications_menu = {}
210
211 self.agent_chan = None
212 self.agent_handler = None
213
214 paramiko.SSHClient.__init__(self)
215 if self.add_to_known_hosts:
216 self.set_missing_host_key_policy(paramiko.AutoAddPolicy())
217
218 self.session_died = False
219
220 self.published_applications_no_submenus = published_applications_no_submenus
221 self._already_querying_published_applications = threading.Lock()
222
223 self._transport_lock = threading.Lock()
224
226 """\
227 Get the hostname as stored in the properties of this control session.
228
229 @return: the hostname of the connected X2Go server
230 @rtype: C{str}
231
232 """
233 return self.hostname
234
236 """\
237 Get the port number of the SSH connection as stored in the properties of this control session.
238
239 @return: the server-side port number of the control session's SSH connection
240 @rtype: C{str}
241
242 """
243 return self.port
244
246 """\
247 Load known SSH host keys from the C{known_hosts} file.
248
249 If the file does not exist, create it first.
250
251 """
252 if self.known_hosts is not None:
253 utils.touch_file(self.known_hosts)
254 self.load_host_keys(self.known_hosts)
255
257 """\
258 On instance descruction, do a proper session disconnect from the server.
259
260 """
261 self.disconnect()
262
264 """
265 Put a local file on the remote server via sFTP.
266
267 During sFTP operations, remote command execution gets blocked.
268
269 @param local_path: full local path name of the file to be put on the server
270 @type local_path: C{str}
271 @param remote_path: full remote path name of the server-side target location, path names have to be Unix-compliant
272 @type remote_path: C{str}
273
274 @raise X2goControlSessionException: if the SSH connection dropped out
275
276 """
277 ssh_transport = self.get_transport()
278 self._transport_lock.acquire()
279 if ssh_transport and ssh_transport.is_authenticated():
280 self.logger('sFTP-put: %s -> %s:%s' % (os.path.normpath(local_path), self.remote_peername(), remote_path), loglevel=log.loglevel_DEBUG)
281 self.sftp_client = paramiko.SFTPClient.from_transport(ssh_transport)
282 try:
283 self.sftp_client.put(os.path.normpath(local_path), remote_path)
284 except (x2go_exceptions.SSHException, socket.error, IOError):
285
286 self.session_died = True
287 self._transport_lock.release()
288 raise x2go_exceptions.X2goControlSessionException('The SSH connection was dropped during an sFTP put action.')
289 self.sftp_client = None
290 self._transport_lock.release()
291
293 """
294 Create a text file on the remote server via sFTP.
295
296 During sFTP operations, remote command execution gets blocked.
297
298 @param remote_path: full remote path name of the server-side target location, path names have to be Unix-compliant
299 @type remote_path: C{str}
300 @param content: a text file, multi-line files use Unix-link EOL style
301 @type content: C{str}
302
303 @raise X2goControlSessionException: if the SSH connection dropped out
304
305 """
306 ssh_transport = self.get_transport()
307 self._transport_lock.acquire()
308 if ssh_transport and ssh_transport.is_authenticated():
309 self.logger('sFTP-write: opening remote file %s on host %s for writing' % (remote_path, self.remote_peername()), loglevel=log.loglevel_DEBUG)
310 self.sftp_client = paramiko.SFTPClient.from_transport(ssh_transport)
311 try:
312 remote_fileobj = self.sftp_client.open(remote_path, 'w')
313 self.logger('sFTP-write: writing content: %s' % content, loglevel=log.loglevel_DEBUG_SFTPXFER)
314 remote_fileobj.write(content)
315 remote_fileobj.close()
316 except (x2go_exceptions.SSHException, socket.error, IOError):
317 self.session_died = True
318 self._transport_lock.release()
319 self.logger('sFTP-write: opening remote file %s on host %s failed' % (remote_path, self.remote_peername()), loglevel=log.loglevel_WARN)
320 raise x2go_exceptions.X2goControlSessionException('The SSH connection was dropped during an sFTP write action.')
321 self.sftp_client = None
322 self._transport_lock.release()
323
325 """
326 Remote a remote file from the server via sFTP.
327
328 During sFTP operations, remote command execution gets blocked.
329
330 @param remote_path: full remote path name of the server-side file to be removed, path names have to be Unix-compliant
331 @type remote_path: C{str}
332
333 @raise X2goControlSessionException: if the SSH connection dropped out
334
335 """
336 ssh_transport = self.get_transport()
337 self._transport_lock.acquire()
338 if ssh_transport and ssh_transport.is_authenticated():
339 self.logger('sFTP-write: removing remote file %s on host %s' % (remote_path, self.remote_peername()), loglevel=log.loglevel_DEBUG)
340 self.sftp_client = paramiko.SFTPClient.from_transport(ssh_transport)
341 try:
342 self.sftp_client.remove(remote_path)
343 except (x2go_exceptions.SSHException, socket.error, IOError):
344 self.session_died = True
345 self._transport_lock.release()
346 self.logger('sFTP-write: removing remote file %s on host %s failed' % (remote_path, self.remote_peername()), loglevel=log.loglevel_WARN)
347 raise x2go_exceptions.X2goControlSessionException('The SSH connection was dropped during an sFTP remove action.')
348 self.sftp_client = None
349 self._transport_lock.release()
350
352 """
353 Execute an X2Go server-side command via SSH.
354
355 During SSH command executions, sFTP operations get blocked.
356
357 @param cmd_line: the command to be executed on the remote server
358 @type cmd_line: C{str} or C{list}
359 @param loglevel: use this loglevel for reporting about remote command execution
360 @type loglevel: C{int}
361 @param timeout: if commands take longer than C{<timeout>} to be executed, consider the control session connection
362 to have died.
363 @type timeout: C{int}
364 @param kwargs: parameters that get passed through to the C{paramiko.SSHClient.exec_command()} method.
365 @type kwargs: C{dict}
366
367 @return: C{True} if the command could be successfully executed on the remote X2Go server
368 @rtype: C{bool}
369
370 @raise X2goControlSessionException: if the command execution failed (due to a lost connection)
371
372 """
373 if type(cmd_line) == types.ListType:
374 cmd = " ".join(cmd_line)
375 else:
376 cmd = cmd_line
377
378 cmd = 'sh -c \"%s\"' % cmd
379
380 if self.session_died:
381 self.logger("control session seams to be dead, not executing command ,,%s'' on X2Go server %s" % (_rerewrite_blanks(cmd), self.profile_name,), loglevel=loglevel)
382 return (cStringIO.StringIO(), cStringIO.StringIO(), cStringIO.StringIO('failed to execute command'))
383
384 self._transport_lock.acquire()
385
386 _retval = None
387
388 ssh_transport = self.get_transport()
389 if ssh_transport and ssh_transport.is_authenticated():
390
391 timer = gevent.Timeout(timeout)
392 timer.start()
393 try:
394 self.logger("executing command on X2Go server ,,%s'': %s" % (self.profile_name, _rerewrite_blanks(cmd)), loglevel=loglevel)
395 _retval = self.exec_command(_rewrite_password(cmd, user=self.get_transport().get_username(), password=self._session_password), **kwargs)
396 except AttributeError:
397 self.session_died = True
398 self._transport_lock.release()
399 if self.sshproxy_session:
400 self.sshproxy_session.stop_thread()
401 raise x2go_exceptions.X2goControlSessionException('the X2Go control session has died unexpectedly')
402 except EOFError:
403 self.session_died = True
404 self._transport_lock.release()
405 if self.sshproxy_session:
406 self.sshproxy_session.stop_thread()
407 raise x2go_exceptions.X2goControlSessionException('the X2Go control session has died unexpectedly')
408 except x2go_exceptions.SSHException:
409 self.session_died = True
410 self._transport_lock.release()
411 if self.sshproxy_session:
412 self.sshproxy_session.stop_thread()
413 raise x2go_exceptions.X2goControlSessionException('the X2Go control session has died unexpectedly')
414 except gevent.timeout.Timeout:
415 self.session_died = True
416 self._transport_lock.release()
417 if self.sshproxy_session:
418 self.sshproxy_session.stop_thread()
419 raise x2go_exceptions.X2goControlSessionException('the X2Go control session command timed out')
420 except socket.error:
421 self.session_died = True
422 self._transport_lock.release()
423 if self.sshproxy_session:
424 self.sshproxy_session.stop_thread()
425 raise x2go_exceptions.X2goControlSessionException('the X2Go control session has died unexpectedly')
426 finally:
427 timer.cancel()
428
429 else:
430 self._transport_lock.release()
431 raise x2go_exceptions.X2goControlSessionException('the X2Go control session is not connected')
432
433 self._transport_lock.release()
434 return _retval
435
436 @property
438 """\
439 Render a list of server-side X2Go features. Results get cached once there has been one successfull query.
440
441 """
442 if self._server_features is None:
443 (stdin, stdout, stderr) = self._x2go_exec_command('which x2gofeaturelist >/dev/null && x2gofeaturelist')
444 self._server_features = stdout.read().split('\n')
445 self.logger('server-side X2Go features are: %s' % self._server_features, loglevel=log.loglevel_DEBUG)
446 return self._server_features
447 else:
448 return self._server_features
449
451 """\
452 Do a query for the server-side list of X2Go features.
453
454 @param force: do not use the cached feature list, really ask the server (again)
455 @type force: C{bool}
456
457 @return: list of X2Go feature names
458 @rtype: C{list}
459
460 """
461 if force:
462 self._server_features = None
463 return self._x2go_server_features
464 get_server_features = query_server_features
465
466 @property
468 """\
469 Retrieve and cache the remote home directory location.
470
471 """
472 if self._remote_home is None:
473 (stdin, stdout, stderr) = self._x2go_exec_command('echo $HOME')
474 stdout_r = stdout.read()
475 if stdout_r:
476 self._remote_home = stdout_r.split()[0]
477 self.logger('remote user\' home directory: %s' % self._remote_home, loglevel=log.loglevel_DEBUG)
478 return self._remote_home
479 else:
480 return self._remote_home
481
483 """\
484 Retrieve and cache the members of a server-side POSIX group.
485
486 @param group: remote POSIX group name
487 @type group: C{str}
488
489 @return: list of POSIX group members
490 @rtype: C{list}
491
492 """
493 if not self._remote_group.has_key(group):
494 (stdin, stdout, stderr) = self._x2go_exec_command('getent group %s | cut -d":" -f4' % group)
495 self._remote_group[group] = stdout.read().split('\n')[0].split(',')
496 self.logger('remote %s group: %s' % (group, self._remote_group[group]), loglevel=log.loglevel_DEBUG)
497 return self._remote_group[group]
498 else:
499 return self._remote_group[group]
500
502 """\
503 Is the remote user allowed to launch X2Go sessions?
504
505 FIXME: this method is currently non-functional.
506
507 @param username: remote user name
508 @type username: C{str}
509
510 @return: C{True} if the remote user is allowed to launch X2Go sessions
511 @rtype: C{bool}
512
513 """
514
515
516
517
518
519
520 return True
521
523 """\
524 Check if the remote user is allowed to use SSHFS mounts.
525
526 @return: C{True} if the user is allowed to connect client-side shares to the X2Go session
527 @rtype: C{bool}
528
529 """
530 if self.remote_username() in self._x2go_remote_group('fuse'):
531 return True
532 return False
533
535 """\
536 Returns (and caches) the control session's remote username.
537
538 @return: SSH transport's user name
539 @rtype: C{str}
540
541 @raise X2goControlSessionException: on SSH connection loss
542
543 """
544 if self._remote_username is None:
545 if self.get_transport() is not None:
546 try:
547 self._remote_username = self.get_transport().get_username()
548 except:
549 self.session_died = True
550 raise x2go_exceptions.X2goControlSessionException('Lost connection to X2Go server')
551 return self._remote_username
552
554 """\
555 Returns (and caches) the control session's remote host (name or ip).
556
557 @return: SSH transport's peer name
558 @rtype: C{tuple}
559
560 @raise X2goControlSessionException: on SSH connection loss
561
562 """
563 if self._remote_peername is None:
564 if self.get_transport() is not None:
565 try:
566 self._remote_peername = self.get_transport().getpeername()
567 except:
568 self.session_died = True
569 raise x2go_exceptions.X2goControlSessionException('Lost connection to X2Go server')
570 return self._remote_peername
571
572 @property
574 """\
575 Generate (and cache) a temporary RSA host key for the lifetime of this control session.
576
577 """
578 if self._session_auth_rsakey is None:
579 self._session_auth_rsakey = paramiko.RSAKey.generate(defaults.RSAKEY_STRENGTH)
580 return self._session_auth_rsakey
581
583 """\
584 Manipulate the control session's profile name.
585
586 @param profile_name: new profile name for this control session
587 @type profile_name: C{str}
588
589 """
590 self.profile_name = profile_name
591
593 """\
594 Wraps around a Paramiko/SSH host key check.
595
596 @param hostname: the remote X2Go server's hostname
597 @type hostname: C{str}
598 @param port: the SSH port of the remote X2Go server
599 @type port: C{int}
600
601 @return: C{True} if the host key check succeeded, C{False} otherwise
602 @rtype: C{bool}
603
604 """
605
606 hostname = hostname.strip()
607
608
609 if hostname in ('localhost', 'localhost.localdomain'):
610 hostname = '127.0.0.1'
611
612 return checkhosts.check_ssh_host_key(self, hostname, port=port)
613
614 - def connect(self, hostname, port=22, username='', password='', pkey=None,
615 key_filename=None, timeout=None, allow_agent=False, look_for_keys=False,
616 use_sshproxy=False, sshproxy_host='', sshproxy_port=22, sshproxy_user='', sshproxy_password='', sshproxy_force_password_auth=False,
617 sshproxy_key_filename='', sshproxy_pkey=None, sshproxy_look_for_keys=False, sshproxy_allow_agent=False,
618 sshproxy_tunnel='',
619 forward_sshagent=None,
620 session_instance=None,
621 add_to_known_hosts=False, force_password_auth=False):
622 """\
623 Connect to an X2Go server and authenticate to it. This method is directly
624 inherited from the C{paramiko.SSHClient} class. The features of the Paramiko
625 SSH client connect method are recited here. The parameters C{add_to_known_hosts},
626 C{force_password_auth}, C{session_instance} and all SSH proxy related parameters
627 have been added as X2Go specific parameters
628
629 The server's host key is checked against the system host keys
630 (see C{load_system_host_keys}) and any local host keys (C{load_host_keys}).
631 If the server's hostname is not found in either set of host keys, the missing host
632 key policy is used (see C{set_missing_host_key_policy}). The default policy is
633 to reject the key and raise an C{SSHException}.
634
635 Authentication is attempted in the following order of priority:
636
637 - The C{pkey} or C{key_filename} passed in (if any)
638 - Any key we can find through an SSH agent
639 - Any "id_rsa" or "id_dsa" key discoverable in C{~/.ssh/}
640 - Plain username/password auth, if a password was given
641
642 If a private key requires a password to unlock it, and a password is
643 passed in, that password will be used to attempt to unlock the key.
644
645 @param hostname: the server to connect to
646 @type hostname: C{str}
647 @param port: the server port to connect to
648 @type port: C{int}
649 @param username: the username to authenticate as (defaults to the
650 current local username)
651 @type username: C{str}
652 @param password: a password to use for authentication or for unlocking
653 a private key
654 @type password: C{str}
655 @param key_filename: the filename, or list of filenames, of optional
656 private key(s) to try for authentication
657 @type key_filename: C{str} or list(str)
658 @param pkey: an optional private key to use for authentication
659 @type pkey: C{PKey}
660 @param forward_sshagent: forward SSH agent authentication requests to the X2Go client-side
661 (will update the class property of the same name)
662 @type forward_sshagent: C{bool}
663 @param timeout: an optional timeout (in seconds) for the TCP connect
664 @type timeout: float
665 @param look_for_keys: set to C{True} to enable searching for discoverable
666 private key files in C{~/.ssh/}
667 @type look_for_keys: C{bool}
668 @param allow_agent: set to C{True} to enable connecting to a local SSH agent
669 for acquiring authentication information
670 @type allow_agent: C{bool}
671 @param add_to_known_hosts: non-paramiko option, if C{True} paramiko.AutoAddPolicy()
672 is used as missing-host-key-policy. If set to C{False} paramiko.RejectPolicy()
673 is used
674 @type add_to_known_hosts: C{bool}
675 @param force_password_auth: non-paramiko option, disable pub/priv key authentication
676 completely, even if the C{pkey} or the C{key_filename} parameter is given
677 @type force_password_auth: C{bool}
678 @param session_instance: an instance L{X2goSession} using this L{X2goControlSessionSTDOUT}
679 instance.
680 @type session_instance: C{obj}
681 @param use_sshproxy: connect through an SSH proxy
682 @type use_sshproxy: C{True} if an SSH proxy is to be used for tunneling the connection
683 @param sshproxy_host: hostname of the SSH proxy server
684 @type sshproxy_host: C{str}
685 @param sshproxy_port: port of the SSH proxy server
686 @type sshproxy_port: C{int}
687 @param sshproxy_user: username that we use for authenticating against C{<sshproxy_host>}
688 @type sshproxy_user: C{str}
689 @param sshproxy_password: a password to use for SSH proxy authentication or for unlocking
690 a private key
691 @type sshproxy_password: C{str}
692 @param sshproxy_force_password_auth: enforce using a given C{sshproxy_password} even if a key(file) is given
693 @type sshproxy_force_password_auth: C{bool}
694 @param sshproxy_key_filename: local file location of the private key file
695 @type sshproxy_key_filename: C{str}
696 @param sshproxy_pkey: an optional private key to use for SSH proxy authentication
697 @type sshproxy_pkey: C{PKey}
698 @param sshproxy_look_for_keys: set to C{True} to enable connecting to a local SSH agent
699 for acquiring authentication information (for SSH proxy authentication)
700 @type sshproxy_look_for_keys: C{bool}
701 @param sshproxy_allow_agent: set to C{True} to enable connecting to a local SSH agent
702 for acquiring authentication information (for SSH proxy authentication)
703 @type sshproxy_allow_agent: C{bool}
704 @param sshproxy_tunnel: the SSH proxy tunneling parameters, format is: <local-address>:<local-port>:<remote-address>:<remote-port>
705 @type sshproxy_tunnel: C{str}
706
707 @return: C{True} if an authenticated SSH transport could be retrieved by this method
708 @rtype: C{bool}
709
710 @raise BadHostKeyException: if the server's host key could not be
711 verified
712 @raise AuthenticationException: if authentication failed
713 @raise SSHException: if there was any other error connecting or
714 establishing an SSH session
715 @raise socket.error: if a socket error occurred while connecting
716 @raise X2goSSHProxyException: any SSH proxy exception is passed through while establishing the SSH proxy connection and tunneling setup
717 @raise X2goSSHAuthenticationException: any SSH proxy authentication exception is passed through while establishing the SSH proxy connection and tunneling setup
718 @raise X2goRemoteHomeException: if the remote home directory does not exist or is not accessible
719
720 """
721 if use_sshproxy and sshproxy_host and sshproxy_user:
722 try:
723 self.sshproxy_session = sshproxy.X2goSSHProxy(known_hosts=self.known_hosts,
724 sshproxy_host=sshproxy_host,
725 sshproxy_port=sshproxy_port,
726 sshproxy_user=sshproxy_user,
727 sshproxy_password=sshproxy_password,
728 sshproxy_force_password_auth=sshproxy_force_password_auth,
729 sshproxy_key_filename=sshproxy_key_filename,
730 sshproxy_pkey=sshproxy_pkey,
731 sshproxy_look_for_keys=sshproxy_look_for_keys,
732 sshproxy_allow_agent=sshproxy_allow_agent,
733 sshproxy_tunnel=sshproxy_tunnel,
734 session_instance=session_instance,
735 logger=self.logger,
736 )
737
738 except:
739 if self.sshproxy_session:
740 self.sshproxy_session.stop_thread()
741 self.sshproxy_session = None
742 raise
743
744 if self.sshproxy_session is not None:
745 self.sshproxy_session.start()
746
747
748
749 gevent.sleep(.1)
750 port = self.sshproxy_session.get_local_proxy_port()
751
752 if not add_to_known_hosts and session_instance:
753 self.set_missing_host_key_policy(checkhosts.X2goInteractiveAddPolicy(caller=self, session_instance=session_instance))
754
755 if add_to_known_hosts:
756 self.set_missing_host_key_policy(paramiko.AutoAddPolicy())
757
758
759 if force_password_auth:
760 key_filename = None
761 pkey = None
762
763
764 hostname = hostname.strip()
765
766 self.logger('connecting to [%s]:%s' % (hostname, port), loglevel=log.loglevel_NOTICE)
767
768 self.load_session_host_keys()
769
770 _hostname = hostname
771
772 if _hostname in ('localhost', 'localhost.localdomain'):
773 _hostname = '127.0.0.1'
774
775
776 if forward_sshagent is not None:
777 self.forward_sshagent = forward_sshagent
778
779 if key_filename or pkey or look_for_keys or allow_agent or (password and force_password_auth):
780 try:
781 if password and force_password_auth:
782 self.logger('trying keyboard-interactive SSH authentication with server', loglevel=log.loglevel_DEBUG)
783 paramiko.SSHClient.connect(self, _hostname, port=port, username=username, pkey=None, password=password,
784 key_filename=None, timeout=timeout, allow_agent=False,
785 look_for_keys=False)
786 elif (key_filename and os.path.exists(os.path.normpath(key_filename))) or pkey:
787 self.logger('trying SSH pub/priv key authentication with server', loglevel=log.loglevel_DEBUG)
788 paramiko.SSHClient.connect(self, _hostname, port=port, username=username, pkey=pkey,
789 key_filename=key_filename, timeout=timeout, allow_agent=allow_agent,
790 look_for_keys=look_for_keys)
791 else:
792 self.logger('trying SSH key discovery or agent authentication with server', loglevel=log.loglevel_DEBUG)
793 try:
794 paramiko.SSHClient.connect(self, _hostname, port=port, username=username, pkey=None,
795 key_filename=None, timeout=timeout, allow_agent=allow_agent,
796 look_for_keys=look_for_keys)
797 except paramiko.SSHException, e:
798 if str(e) == 'No authentication methods available':
799 raise paramiko.AuthenticationException('Interactive password authentication required!')
800 else:
801 raise(e)
802
803
804 t = self.get_transport()
805 if x2go._paramiko.PARAMIKO_FEATURE['use-compression']:
806 t.use_compression(compress=True)
807
808 except paramiko.AuthenticationException, e:
809 self.close()
810 if password:
811 self.logger('next auth mechanism we\'ll try is keyboard-interactive authentication', loglevel=log.loglevel_DEBUG)
812 try:
813 paramiko.SSHClient.connect(self, _hostname, port=port, username=username, password=password,
814 key_filename=None, pkey=None, timeout=timeout, allow_agent=False, look_for_keys=False)
815 except:
816 self.close()
817 if self.sshproxy_session:
818 self.sshproxy_session.stop_thread()
819 raise
820 else:
821 self.close()
822 if self.sshproxy_session:
823 self.sshproxy_session.stop_thread()
824 raise(e)
825
826 except:
827 self.close()
828 if self.sshproxy_session:
829 self.sshproxy_session.stop_thread()
830 raise
831
832
833 else:
834
835 if not password:
836 password = "".join([random.choice(string.letters+string.digits) for x in range(1, 20)])
837 self.logger('performing SSH keyboard-interactive authentication with server', loglevel=log.loglevel_DEBUG)
838 try:
839 paramiko.SSHClient.connect(self, _hostname, port=port, username=username, password=password,
840 timeout=timeout, allow_agent=False, look_for_keys=False)
841 except paramiko.AuthenticationException, e:
842 self.close()
843 if self.sshproxy_session:
844 self.sshproxy_session.stop_thread()
845 raise e
846 except:
847 self.close()
848 if self.sshproxy_session:
849 self.sshproxy_session.stop_thread()
850 raise
851
852 self.set_missing_host_key_policy(paramiko.RejectPolicy())
853
854 self.hostname = hostname
855 self.port = port
856
857
858 ssh_transport = self.get_transport()
859 ssh_transport.reverse_tunnels = {}
860
861
862 ssh_transport._x2go_session_marker = True
863 self._session_password = password
864
865 if ssh_transport is not None:
866 self.session_died = False
867 self.query_server_features(force=True)
868 if self.forward_sshagent:
869 if x2go._paramiko.PARAMIKO_FEATURE['forward-ssh-agent']:
870 self.agent_chan = ssh_transport.open_session()
871 self.agent_handler = paramiko.agent.AgentRequestHandler(self.agent_chan)
872 self.logger('Requesting SSH agent forwarding for control session of connected session profile %s' % self.profile_name, loglevel=log.loglevel_INFO)
873 else:
874 self.logger('SSH agent forwarding is not available in the Paramiko version used with this instance of Python X2Go', loglevel=log.loglevel_WARN)
875
876 self._remote_home = None
877 if not self.home_exists():
878 raise x2go_exceptions.X2goRemoteHomeException('remote home directory does not exist')
879
880 return (self.get_transport() is not None)
881
883 """\
884 Drop an associated terminal session.
885
886 @param terminal_session: the terminal session object to remove from the list of associated terminals
887 @type terminal_session: C{X2goTerminalSession*}
888
889 """
890 for t_name in self.associated_terminals.keys():
891 if self.associated_terminals[t_name] == terminal_session:
892 del self.associated_terminals[t_name]
893 if self.terminated_terminals.has_key(t_name):
894 del self.terminated_terminals[t_name]
895
897 """\
898 Disconnect this control session from the remote server.
899
900 @return: report success or failure after having disconnected
901 @rtype: C{bool}
902
903 """
904 if self.associated_terminals:
905 t_names = self.associated_terminals.keys()
906 for t_obj in self.associated_terminals.values():
907 try:
908 if not self.session_died:
909 t_obj.suspend()
910 except x2go_exceptions.X2goTerminalSessionException:
911 pass
912 except x2go_exceptions.X2goControlSessionException:
913 self.session_died
914 t_obj.__del__()
915 for t_name in t_names:
916 try:
917 del self.associated_terminals[t_name]
918 except KeyError:
919 pass
920
921 self._remote_home = None
922 self._remote_group = {}
923
924 self._session_auth_rsakey = None
925
926
927 self._transport_lock.release()
928
929
930 if self.agent_handler is not None:
931 self.agent_handler.close()
932
933 if self.agent_chan is not None:
934 self.agent_chan.close()
935
936 retval = False
937 try:
938 if self.get_transport() is not None:
939 retval = self.get_transport().is_active()
940 try:
941 self.close()
942 except IOError:
943 pass
944 except AttributeError:
945
946
947 pass
948
949
950 if self.sshproxy_session is not None:
951 self.sshproxy_session.stop_thread()
952
953 return retval
954
956 """\
957 Test if the remote home directory exists.
958
959 @return: C{True} if the home directory exists, C{False} otherwise
960 @rtype: C{bool}
961
962 """
963 (_stdin, _stdout, _stderr) = self._x2go_exec_command('stat -tL "%s"' % self._x2go_remote_home, loglevel=log.loglevel_DEBUG)
964 if _stdout.read():
965 return True
966 return False
967
968
970 """\
971 Test if the connection to the remote X2Go server is still alive.
972
973 @return: C{True} if the connection is still alive, C{False} otherwise
974 @rtype: C{bool}
975
976 """
977 try:
978 if self._x2go_exec_command('echo', loglevel=log.loglevel_DEBUG):
979 return True
980 except x2go_exceptions.X2goControlSessionException:
981 self.session_died = True
982 return False
983
985 """\
986 Test if the connection to the remote X2Go server died on the way.
987
988 @return: C{True} if the connection has died, C{False} otherwise
989 @rtype: C{bool}
990
991 """
992 return self.session_died
993
995 """\
996 Retrieve the menu tree of published applications from the remote X2Go server.
997
998 The C{raw} option lets this method return a C{list} of C{dict} elements. Each C{dict} elements has a
999 C{desktop} key containing a shortened version of the text output of a .desktop file and an C{icon} key
1000 which contains the desktop base64-encoded icon data.
1001
1002 The {very_raw} lets this method return the output of the C{x2gogetapps} script as is.
1003
1004 @param lang: locale/language identifier
1005 @type lang: C{str}
1006 @param refresh: force reload of the menu tree from X2Go server
1007 @type refresh: C{bool}
1008 @param raw: retrieve a raw output of the server list of published applications
1009 @type raw: C{bool}
1010 @param very_raw: retrieve a very raw output of the server list of published applications
1011 @type very_raw: C{bool}
1012
1013 @return: an i18n capable menu tree packed as a Python dictionary
1014 @rtype: C{list}
1015
1016 """
1017 self._already_querying_published_applications.acquire()
1018
1019 if defaults.X2GOCLIENT_OS != 'Windows' and lang is None:
1020 lang = locale.getdefaultlocale()[0]
1021 elif lang is None:
1022 lang = 'en'
1023
1024 if 'X2GO_PUBLISHED_APPLICATIONS' in self.get_server_features():
1025 if self._published_applications_menu is {} or \
1026 not self._published_applications_menu.has_key(lang) or \
1027 raw or very_raw or refresh or \
1028 (self.published_applications_no_submenus != max_no_submenus):
1029
1030 self.published_applications_no_submenus = max_no_submenus
1031
1032
1033
1034 self.logger('querying server (%s) for list of published applications' % self.profile_name, loglevel=log.loglevel_NOTICE)
1035 (stdin, stdout, stderr) = self._x2go_exec_command('which x2gogetapps >/dev/null && x2gogetapps')
1036 _raw_output = stdout.read()
1037
1038 if very_raw:
1039 self.logger('published applications query for %s finished, return very raw output' % self.profile_name, loglevel=log.loglevel_NOTICE)
1040 self._already_querying_published_applications.release()
1041 return _raw_output
1042
1043
1044
1045 _raw_menu_items = _raw_output.split('</desktop>\n')
1046 _raw_menu_items = [ i.replace('<desktop>\n', '') for i in _raw_menu_items ]
1047 _menu = []
1048 for _raw_menu_item in _raw_menu_items:
1049 if '<icon>\n' in _raw_menu_item and '</icon>' in _raw_menu_item:
1050 _menu_item = _raw_menu_item.split('<icon>\n')[0] + _raw_menu_item.split('</icon>\n')[1]
1051 _icon_base64 = _raw_menu_item.split('<icon>\n')[1].split('</icon>\n')[0]
1052 else:
1053 _menu_item = _raw_menu_item
1054 _icon_base64 = None
1055 if _menu_item:
1056 _menu.append({ 'desktop': _menu_item, 'icon': _icon_base64, })
1057 _menu_item = None
1058 _icon_base64 = None
1059
1060 if raw:
1061 self.logger('published applications query for %s finished, returning raw output' % self.profile_name, loglevel=log.loglevel_NOTICE)
1062 self._already_querying_published_applications.release()
1063 return _menu
1064
1065 if len(_menu) > max_no_submenus >= 0:
1066 _render_submenus = True
1067 else:
1068 _render_submenus = False
1069
1070
1071
1072 _category_map = {
1073 lang: {
1074 'Multimedia': [],
1075 'Development': [],
1076 'Education': [],
1077 'Games': [],
1078 'Graphics': [],
1079 'Internet': [],
1080 'Office': [],
1081 'System': [],
1082 'Utilities': [],
1083 'Other Applications': [],
1084 'TOP': [],
1085 }
1086 }
1087 _empty_menus = _category_map[lang].keys()
1088
1089 for item in _menu:
1090
1091 _menu_entry_name = ''
1092 _menu_entry_fallback_name = ''
1093 _menu_entry_comment = ''
1094 _menu_entry_fallback_comment = ''
1095 _menu_entry_exec = ''
1096 _menu_entry_cat = ''
1097 _menu_entry_shell = False
1098
1099 lang_regio = lang
1100 lang_only = lang_regio.split('_')[0]
1101
1102 for line in item['desktop'].split('\n'):
1103 if re.match('^Name\[%s\]=.*' % lang_regio, line) or re.match('Name\[%s\]=.*' % lang_only, line):
1104 _menu_entry_name = line.split("=")[1].strip()
1105 elif re.match('^Name=.*', line):
1106 _menu_entry_fallback_name = line.split("=")[1].strip()
1107 elif re.match('^Comment\[%s\]=.*' % lang_regio, line) or re.match('Comment\[%s\]=.*' % lang_only, line):
1108 _menu_entry_comment = line.split("=")[1].strip()
1109 elif re.match('^Comment=.*', line):
1110 _menu_entry_fallback_comment = line.split("=")[1].strip()
1111 elif re.match('^Exec=.*', line):
1112 _menu_entry_exec = line.split("=")[1].strip()
1113 elif re.match('^Terminal=.*(t|T)(r|R)(u|U)(e|E).*', line):
1114 _menu_entry_shell = True
1115 elif re.match('^Categories=.*', line):
1116 if 'X2Go-Top' in line:
1117 _menu_entry_cat = 'TOP'
1118 elif 'Audio' in line or 'Video' in line:
1119 _menu_entry_cat = 'Multimedia'
1120 elif 'Development' in line:
1121 _menu_entry_cat = 'Development'
1122 elif 'Education' in line:
1123 _menu_entry_cat = 'Education'
1124 elif 'Game' in line:
1125 _menu_entry_cat = 'Games'
1126 elif 'Graphics' in line:
1127 _menu_entry_cat = 'Graphics'
1128 elif 'Network' in line:
1129 _menu_entry_cat = 'Internet'
1130 elif 'Office' in line:
1131 _menu_entry_cat = 'Office'
1132 elif 'Settings' in line:
1133 continue
1134 elif 'System' in line:
1135 _menu_entry_cat = 'System'
1136 elif 'Utility' in line:
1137 _menu_entry_cat = 'Utilities'
1138 else:
1139 _menu_entry_cat = 'Other Applications'
1140
1141 if not _menu_entry_exec:
1142 continue
1143 else:
1144
1145 _menu_entry_exec = _menu_entry_exec.replace('%f', '').replace('%F','').replace('%u','').replace('%U','')
1146 if _menu_entry_shell:
1147 _menu_entry_exec = "x-terminal-emulator -e '%s'" % _menu_entry_exec
1148
1149 if not _menu_entry_cat:
1150 _menu_entry_cat = 'Other Applications'
1151
1152 if not _render_submenus:
1153 _menu_entry_cat = 'TOP'
1154
1155 if _menu_entry_cat in _empty_menus:
1156 _empty_menus.remove(_menu_entry_cat)
1157
1158 if not _menu_entry_name: _menu_entry_name = _menu_entry_fallback_name
1159 if not _menu_entry_comment: _menu_entry_comment = _menu_entry_fallback_comment
1160 if not _menu_entry_comment: _menu_entry_comment = _menu_entry_name
1161
1162 _menu_entry_icon = item['icon']
1163
1164 _category_map[lang][_menu_entry_cat].append(
1165 {
1166 'name': _menu_entry_name,
1167 'comment': _menu_entry_comment,
1168 'exec': _menu_entry_exec,
1169 'icon': _menu_entry_icon,
1170 }
1171 )
1172
1173 for _cat in _empty_menus:
1174 del _category_map[lang][_cat]
1175
1176 for _cat in _category_map[lang].keys():
1177 _sorted = sorted(_category_map[lang][_cat], key=lambda k: k['name'])
1178 _category_map[lang][_cat] = _sorted
1179
1180 self._published_applications_menu.update(_category_map)
1181 self.logger('published applications query for %s finished, return menu tree' % self.profile_name, loglevel=log.loglevel_NOTICE)
1182
1183 else:
1184
1185 pass
1186
1187 self._already_querying_published_applications.release()
1188 return self._published_applications_menu
1189
1190 - def start(self, **kwargs):
1191 """\
1192 Start a new X2Go session.
1193
1194 The L{X2goControlSessionSTDOUT.start()} method accepts any parameter
1195 that can be passed to any of the C{X2goTerminalSession} backend class
1196 constructors.
1197
1198 @param kwargs: parameters that get passed through to the control session's
1199 L{resume()} method, only the C{session_name} parameter will get removed
1200 before pass-through
1201 @type kwargs: C{dict}
1202
1203 @return: return value of the cascaded L{resume()} method, denoting the success or failure
1204 of the session startup
1205 @rtype: C{bool}
1206
1207 """
1208 if 'session_name' in kwargs.keys():
1209 del kwargs['session_name']
1210 return self.resume(**kwargs)
1211
1212 - def resume(self, session_name=None, session_instance=None, session_list=None, **kwargs):
1213 """\
1214 Resume a running/suspended X2Go session.
1215
1216 The L{X2goControlSessionSTDOUT.resume()} method accepts any parameter
1217 that can be passed to any of the C{X2goTerminalSession*} backend class constructors.
1218
1219 @return: True if the session could be successfully resumed
1220 @rtype: C{bool}
1221
1222 @raise X2goUserException: if the remote user is not allowed to launch/resume X2Go sessions.
1223
1224 """
1225 if not self.is_x2gouser(self.get_transport().get_username()):
1226 raise x2go_exceptions.X2goUserException('remote user %s is not allowed to run X2Go commands' % self.get_transport().get_username())
1227
1228 try:
1229 if session_name is not None:
1230 if session_list:
1231 session_info = session_list[session_name]
1232 else:
1233 session_info = self.list_sessions()[session_name]
1234 else:
1235 session_info = None
1236 except KeyError:
1237 _success = False
1238
1239 _terminal = self._terminal_backend(self,
1240 profile_name=self.profile_name,
1241 session_info=session_info,
1242 info_backend=self._info_backend,
1243 list_backend=self._list_backend,
1244 proxy_backend=self._proxy_backend,
1245 client_rootdir=self.client_rootdir,
1246 session_instance=session_instance,
1247 sessions_rootdir=self.sessions_rootdir,
1248 **kwargs)
1249
1250 _success = False
1251 try:
1252 if session_name is not None:
1253 _success = _terminal.resume()
1254 else:
1255 _success = _terminal.start()
1256 except x2go_exceptions.X2goTerminalSessionException:
1257 _success = False
1258
1259 if _success:
1260 while not _terminal.ok():
1261 gevent.sleep(.2)
1262
1263 if _terminal.ok():
1264 self.associated_terminals[_terminal.get_session_name()] = _terminal
1265 self.get_transport().reverse_tunnels[_terminal.get_session_name()] = {
1266 'sshfs': (0, None),
1267 'snd': (0, None),
1268 }
1269
1270 return _terminal or None
1271
1272 return None
1273
1274 - def share_desktop(self, desktop=None, user=None, display=None, share_mode=0, **kwargs):
1275 """\
1276 Share another already running desktop session. Desktop sharing can be run
1277 in two different modes: view-only and full-access mode.
1278
1279 @param desktop: desktop ID of a sharable desktop in format C{<user>@<display>}
1280 @type desktop: C{str}
1281 @param user: user name and display number can be given separately, here give the
1282 name of the user who wants to share a session with you
1283 @type user: C{str}
1284 @param display: user name and display number can be given separately, here give the
1285 number of the display that a user allows you to be shared with
1286 @type display: C{str}
1287 @param share_mode: desktop sharing mode, 0 stands for VIEW-ONLY, 1 for FULL-ACCESS mode
1288 @type share_mode: C{int}
1289
1290 @return: True if the session could be successfully shared
1291 @rtype: C{bool}
1292
1293 @raise X2goDesktopSharingException: if C{username} and C{dislpay} do not relate to a
1294 sharable desktop session
1295
1296 """
1297 if desktop:
1298 user = desktop.split('@')[0]
1299 display = desktop.split('@')[1]
1300 if not (user and display):
1301 raise x2go_exceptions.X2goDesktopSharingException('Need user name and display number of sharable desktop.')
1302
1303 cmd = '%sXSHAD%sXSHAD%s' % (share_mode, user, display)
1304
1305 kwargs['cmd'] = cmd
1306 kwargs['session_type'] = 'shared'
1307
1308 return self.start(**kwargs)
1309
1311 """\
1312 List all desktop-like sessions of current user (or of users that have
1313 granted desktop sharing) on the connected server.
1314
1315 @param raw: if C{True}, the raw output of the server-side X2Go command
1316 C{x2golistdesktops} is returned.
1317 @type raw: C{bool}
1318
1319 @return: a list of X2Go desktops available for sharing
1320 @rtype: C{list}
1321
1322 @raise X2goTimeOutException: on command execution timeouts, with the server-side C{x2golistdesktops}
1323 command this can sometimes happen. Make sure you ignore these time-outs and to try again
1324
1325 """
1326 if raw:
1327 (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && x2golistdesktops")
1328 return stdout.read(), stderr.read()
1329
1330 else:
1331
1332
1333
1334
1335 timeout = gevent.Timeout(maxwait)
1336 timeout.start()
1337 try:
1338 (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && x2golistdesktops")
1339 _stdout_read = stdout.read()
1340 _listdesktops = _stdout_read.split('\n')
1341 except gevent.timeout.Timeout:
1342
1343
1344
1345 raise x2go_exceptions.X2goTimeOutException('x2golistdesktop command timed out')
1346 finally:
1347 timeout.cancel()
1348
1349 return _listdesktops
1350
1351 - def list_mounts(self, session_name, raw=False, maxwait=20):
1352 """\
1353 List all mounts for a given session of the current user on the connected server.
1354
1355 @param session_name: name of a session to query a list of mounts for
1356 @type session_name: C{str}
1357 @param raw: if C{True}, the raw output of the server-side X2Go command
1358 C{x2golistmounts} is returned.
1359 @type raw: C{bool}
1360 @param maxwait: stop processing C{x2golistmounts} after C{<maxwait>} seconds
1361 @type maxwait: C{int}
1362
1363 @return: a list of client-side mounts for X2Go session C{<session_name>} on the server
1364 @rtype: C{list}
1365
1366 @raise X2goTimeOutException: on command execution timeouts, queries with the server-side
1367 C{x2golistmounts} query should normally be processed quickly, a time-out may hint that the
1368 control session has lost its connection to the X2Go server
1369
1370 """
1371 if raw:
1372 (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && x2golistmounts %s" % session_name)
1373 return stdout.read(), stderr.read()
1374
1375 else:
1376
1377
1378
1379 timeout = gevent.Timeout(maxwait)
1380 timeout.start()
1381 try:
1382 (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && x2golistmounts %s" % session_name)
1383 _stdout_read = stdout.read()
1384 _listmounts = {session_name: [ line for line in _stdout_read.split('\n') if line ] }
1385 except gevent.timeout.Timeout:
1386
1387
1388 raise x2go_exceptions.X2goTimeOutException('x2golistmounts command timed out')
1389 finally:
1390 timeout.cancel()
1391
1392 return _listmounts
1393
1395 """\
1396 List all sessions of current user on the connected server.
1397
1398 @param raw: if C{True}, the raw output of the server-side X2Go command
1399 C{x2golistsessions} is returned.
1400 @type raw: C{bool}
1401
1402 @return: normally an instance of a C{X2goServerSessionList*} backend is returned. However,
1403 if the raw argument is set, the plain text output of the server-side C{x2golistsessions}
1404 command is returned
1405 @rtype: C{X2goServerSessionList} instance or str
1406
1407 @raise X2goControlSessionException: on command execution timeouts, if this happens the control session will
1408 be interpreted as disconnected due to connection loss
1409 """
1410 if raw:
1411 (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && x2golistsessions")
1412 return stdout.read(), stderr.read()
1413
1414 else:
1415
1416
1417
1418 _listsessions = {}
1419 _success = False
1420 _count = 0
1421 _maxwait = 20
1422
1423
1424
1425 while not _success and _count < _maxwait:
1426 _count += 1
1427 try:
1428 (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && x2golistsessions")
1429 _stdout_read = stdout.read()
1430 _listsessions = self._list_backend(_stdout_read, info_backend=self._info_backend).sessions
1431 _success = True
1432 except KeyError:
1433 gevent.sleep(1)
1434 except IndexError:
1435 gevent.sleep(1)
1436 except ValueError:
1437 gevent.sleep(1)
1438
1439 if _count >= _maxwait:
1440 self.session_died = True
1441 raise x2go_exceptions.X2goControlSessionException('x2golistsessions command failed after we have tried 20 times')
1442
1443
1444 for _session_name, _terminal in self.associated_terminals.items():
1445 if _session_name in _listsessions.keys():
1446
1447 if hasattr(self.associated_terminals[_session_name], 'session_info') and not self.associated_terminals[_session_name].is_session_info_protected():
1448 self.associated_terminals[_session_name].session_info.update(_listsessions[_session_name])
1449 else:
1450 try: del self.associated_terminals[_session_name]
1451 except KeyError: pass
1452 self.terminated_terminals.append(_session_name)
1453 if _terminal.is_suspended():
1454 try: del self.associated_terminals[_session_name]
1455 except KeyError: pass
1456
1457
1458 return _listsessions
1459
1460 - def clean_sessions(self, destroy_terminals=True, published_applications=False):
1461 """\
1462 Find X2Go terminals that have previously been started by the
1463 connected user on the remote X2Go server and terminate them.
1464
1465 @param destroy_terminals: destroy the terminal session instances after cleanup
1466 @type destroy_terminals: C{bool}
1467 @param published_applications: also clean up published applications providing sessions
1468 @type published_applications: C{bool}
1469
1470 """
1471 session_list = self.list_sessions()
1472 if published_applications:
1473 session_names = session_list.keys()
1474 else:
1475 session_names = [ _sn for _sn in session_list.keys() if not session_list[_sn].is_published_applications_provider() ]
1476 for session_name in session_names:
1477 self.terminate(session_name=session_name, destroy_terminals=destroy_terminals)
1478
1480 """\
1481 Returns C{True} if this control session is connected to the remote server (that
1482 is: if it has a valid Paramiko/SSH transport object).
1483
1484 @return: X2Go session connected?
1485 @rtype: C{bool}
1486
1487 """
1488 return self.get_transport() is not None and self.get_transport().is_authenticated()
1489
1491 """\
1492 Returns C{True} if the given X2Go session is in running state,
1493 C{False} else.
1494
1495 @param session_name: X2Go name of the session to be queried
1496 @type session_name: C{str}
1497
1498 @return: X2Go session running? If C{<session_name>} is not listable by the L{list_sessions()} method then C{None} is returned
1499 @rtype: C{bool} or C{None}
1500
1501 """
1502 session_infos = self.list_sessions()
1503 if session_name in session_infos.keys():
1504 return session_infos[session_name].is_running()
1505 return None
1506
1508 """\
1509 Returns C{True} if the given X2Go session is in suspended state,
1510 C{False} else.
1511
1512 @return: X2Go session suspended? If C{<session_name>} is not listable by the L{list_sessions()} method then C{None} is returned
1513 @rtype: C{bool} or C{None}
1514
1515 """
1516 session_infos = self.list_sessions()
1517 if session_name in session_infos.keys():
1518 return session_infos[session_name].is_suspended()
1519 return None
1520
1522 """\
1523 Returns C{True} if the X2Go session with name C{<session_name>} has been seen
1524 by this control session and--in the meantime--has been terminated.
1525
1526 If C{<session_name>} has not been seen, yet, the method will return C{None}.
1527
1528 @return: X2Go session has terminated?
1529 @rtype: C{bool} or C{None}
1530
1531 """
1532 session_infos = self.list_sessions()
1533 if session_name in self.terminated_terminals:
1534 return True
1535 if session_name not in session_infos.keys() and session_name in self.associated_terminals.keys():
1536
1537 self.terminate(session_name)
1538 return True
1539 if self.is_suspended(session_name) or self.is_running(session_name):
1540 return False
1541
1542 return None
1543
1545 """\
1546 Suspend X2Go session with name C{<session_name>} on the connected
1547 server.
1548
1549 @param session_name: X2Go name of the session to be suspended
1550 @type session_name: C{str}
1551
1552 @return: C{True} if the session could be successfully suspended
1553 @rtype: C{bool}
1554
1555 """
1556 _ret = False
1557 _session_names = [ t.get_session_name() for t in self.associated_terminals.values() ]
1558 if session_name in _session_names:
1559
1560 self.logger('suspending associated terminal session: %s' % session_name, loglevel=log.loglevel_DEBUG)
1561 (stdin, stdout, stderr) = self._x2go_exec_command("x2gosuspend-session %s" % session_name, loglevel=log.loglevel_DEBUG)
1562 stdout.read()
1563 stderr.read()
1564 if self.associated_terminals.has_key(session_name):
1565 if self.associated_terminals[session_name] is not None:
1566 self.associated_terminals[session_name].__del__()
1567 try: del self.associated_terminals[session_name]
1568 except KeyError: pass
1569 _ret = True
1570
1571 else:
1572
1573 self.logger('suspending non-associated terminal session: %s' % session_name, loglevel=log.loglevel_DEBUG)
1574 (stdin, stdout, stderr) = self._x2go_exec_command("x2gosuspend-session %s" % session_name, loglevel=log.loglevel_DEBUG)
1575 stdout.read()
1576 stderr.read()
1577 _ret = True
1578
1579 return _ret
1580
1581 - def terminate(self, session_name, destroy_terminals=True):
1582 """\
1583 Terminate X2Go session with name C{<session_name>} on the connected
1584 server.
1585
1586 @param session_name: X2Go name of the session to be terminated
1587 @type session_name: C{str}
1588
1589 @return: C{True} if the session could be successfully terminated
1590 @rtype: C{bool}
1591
1592 """
1593
1594 _ret = False
1595 _session_names = [ t.get_session_name() for t in self.associated_terminals.values() ]
1596 if session_name in _session_names:
1597
1598 self.logger('terminating associated session: %s' % session_name, loglevel=log.loglevel_DEBUG)
1599 (stdin, stdout, stderr) = self._x2go_exec_command("x2goterminate-session %s" % session_name, loglevel=log.loglevel_DEBUG)
1600 stdout.read()
1601 stderr.read()
1602 if self.associated_terminals.has_key(session_name):
1603 if self.associated_terminals[session_name] is not None and destroy_terminals:
1604 self.associated_terminals[session_name].__del__()
1605 try: del self.associated_terminals[session_name]
1606 except KeyError: pass
1607 self.terminated_terminals.append(session_name)
1608 _ret = True
1609
1610 else:
1611
1612 self.logger('terminating non-associated session: %s' % session_name, loglevel=log.loglevel_DEBUG)
1613 (stdin, stdout, stderr) = self._x2go_exec_command("x2goterminate-session %s" % session_name, loglevel=log.loglevel_DEBUG)
1614 stdout.read()
1615 stderr.read()
1616 _ret = True
1617
1618 return _ret
1619