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