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