Package x2go :: Package backends :: Package control :: Module _stdout
[frames] | no frames]

Source Code for Module x2go.backends.control._stdout

   1  # -*- coding: utf-8 -*- 
   2   
   3  # Copyright (C) 2010-2012 by Mike Gabriel <mike.gabriel@das-netzwerkteam.de> 
   4  # 
   5  # Python X2Go is free software; you can redistribute it and/or modify 
   6  # it under the terms of the GNU Affero General Public License as published by 
   7  # the Free Software Foundation; either version 3 of the License, or 
   8  # (at your option) any later version. 
   9  # 
  10  # Python X2Go is distributed in the hope that it will be useful, 
  11  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
  12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
  13  # GNU Affero General Public License for more details. 
  14  # 
  15  # You should have received a copy of the GNU Affero General Public License 
  16  # along with this program; if not, write to the 
  17  # Free Software Foundation, Inc., 
  18  # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. 
  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  # modules 
  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  # Python X2Go modules 
  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() 
58 59 -def _rerewrite_blanks(cmd):
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 # X2Go run command replace X2GO_SPACE_CHAR string with blanks 73 if cmd: 74 cmd = cmd.replace("X2GO_SPACE_CHAR", " ") 75 return cmd
76
77 -def _rewrite_password(cmd, user=None, password=None):
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 # if there is a ,,-u X2GO_USER'' parameter in RDP options then we will replace 96 # it by our X2Go session password 97 if cmd and user: 98 cmd = cmd.replace('X2GO_USER', user) 99 # if there is a ,,-p X2GO_PASSWORD'' parameter in RDP options then we will replace 100 # it by our X2Go session password 101 if cmd and password: 102 cmd = cmd.replace('X2GO_PASSWORD', password) 103 return cmd
104
105 106 -class X2goControlSessionSTDOUT(paramiko.SSHClient):
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
218 - def get_hostname(self):
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
228 - def get_port(self):
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
238 - def load_session_host_keys(self):
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
249 - def __del__(self):
250 """\ 251 On instance descruction, do a proper session disconnect from the server. 252 253 """ 254 self.disconnect()
255
256 - def _x2go_sftp_put(self, local_path, remote_path):
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 # react to connection dropped error for SSH connections 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
285 - def _x2go_sftp_write(self, remote_path, content):
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
317 - def _x2go_sftp_remove(self, remote_path):
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
344 - def _x2go_exec_command(self, cmd_line, loglevel=log.loglevel_INFO, timeout=20, **kwargs):
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
428 - def _x2go_server_features(self):
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
441 - def query_server_features(self):
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
453 - def _x2go_remote_home(self):
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
466 - def _x2go_remote_group(self, group):
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
485 - def is_x2gouser(self, username):
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 ### FIXME: 500 ### 501 # discussion about server-side access restriction based on posix group membership or similar currently 502 # in process (as of 20110517, mg) 503 #return username in self._x2go_remote_group('x2gousers') 504 return True
505
506 - def is_sshfs_available(self):
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
518 - def remote_username(self):
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
537 - def remote_peername(self):
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
557 - def _x2go_session_auth_rsakey(self):
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
566 - def set_profile_name(self, profile_name):
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
576 - def check_host(self, hostname, port=22):
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 # trailing whitespace tolerance 590 hostname = hostname.strip() 591 592 # force into IPv4 for localhost connections 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 # divert port to sshproxy_session's local forwarding port (it might have changed due to 710 # SSH connection errors 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 # disable pub/priv key authentication if forced 721 if force_password_auth: 722 key_filename = None 723 pkey = None 724 725 # trailing whitespace tolerance in hostname 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 # enforce IPv4 for localhost address 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 # since Paramiko 1.7.7.1 there is compression available, let's use it if present... 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 # if there is not private key, we will use the given password, if any 785 else: 786 # create a random password if password is empty to trigger host key validity check 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 # preparing reverse tunnels 810 ssh_transport = self.get_transport() 811 ssh_transport.reverse_tunnels = {} 812 813 # mark Paramiko/SSH transport as X2goControlSession 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
825 - def dissociate(self, terminal_session):
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
839 - def disconnect(self):
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 # in any case, release out internal transport lock 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 # if the Paramiko _transport object has not yet been initialized, ignore it 885 # but state that this method call did not close the SSH client, but was already closed 886 return False
887
888 - def home_exists(self):
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
902 - def is_alive(self):
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
917 - def has_session_died(self):
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
927 - def get_published_applications(self, lang=None, refresh=False, raw=False, very_raw=False, max_no_submenus=defaults.PUBAPP_MAX_NO_SUBMENUS):
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 ### STAGE 1: retrieve menu from server 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 ### STAGE 2: dissect the text file retrieved from server, cut into single menu elements 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 # STAGE 3: create menu structure in a Python dictionary 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 # FIXME: strip off any noted options (%f, %F, %u, %U, ...), this can be more intelligent 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 # FIXME: ignoring the absence of the published applications feature for now, handle it appropriately later 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
1244 - def list_desktops(self, raw=False, maxwait=20):
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 # this _success loop will catch errors in case the x2golistsessions output is corrupt 1267 # this should not be needed and is a workaround for the current X2Go server implementation 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 # if we do not get a reply here after <maxwait> seconds we will raise a time out, we have to 1277 # make sure that we catch this at places where we want to ignore timeouts (e.g. in the 1278 # desktop list cache) 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 # this _success loop will catch errors in case the x2golistmounts output is corrupt 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 # if we do not get a reply here after <maxwait> seconds we will raise a time out, we have to 1321 # make sure that we catch this at places where we want to ignore timeouts 1322 raise x2go_exceptions.X2goTimeOutException('x2golistmounts command timed out') 1323 finally: 1324 timeout.cancel() 1325 1326 return _listmounts
1327
1328 - def list_sessions(self, raw=False):
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 # this _success loop will catch errors in case the x2golistsessions output is corrupt 1351 # this should not be needed and is a workaround for the current X2Go server implementation 1352 _listsessions = {} 1353 _success = False 1354 _count = 0 1355 _maxwait = 20 1356 1357 # we will try this 20 times before giving up... we might simply catch the x2golistsessions 1358 # output in the middle of creating a session in the database... 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 # update internal variables when list_sessions() is called 1378 for _session_name, _terminal in self.associated_terminals.items(): 1379 if _session_name in _listsessions.keys(): 1380 # update the whole session_info object within the terminal session 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
1413 - def is_connected(self):
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
1424 - def is_running(self, session_name):
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
1441 - def is_suspended(self, session_name):
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
1455 - def has_terminated(self, session_name):
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 # do a post-mortem tidy up 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
1478 - def suspend(self, session_name):
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