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

Source Code for Module x2go.backends.control.plain

   1  # -*- coding: utf-8 -*- 
   2   
   3  # Copyright (C) 2010-2016 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  L{X2GoControlSession} class - core functions for handling your individual X2Go sessions. 
  22   
  23  This backend handles X2Go server implementations that respond via server-side PLAIN text output. 
  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  import base64 
  41  import uuid 
  42   
  43  from gevent import socket 
  44   
  45  # Python X2Go modules 
  46  import x2go.sshproxy as sshproxy 
  47  import x2go.log as log 
  48  import x2go.utils as utils 
  49  import x2go.x2go_exceptions as x2go_exceptions 
  50  import x2go.defaults as defaults 
  51  import x2go.checkhosts as checkhosts 
  52   
  53  from x2go.defaults import BACKENDS as _BACKENDS 
  54   
  55  import x2go._paramiko 
  56  x2go._paramiko.monkey_patch_paramiko() 
57 58 -def _rerewrite_blanks(cmd):
59 """\ 60 In command strings X2Go server scripts expect blanks being rewritten to ,,X2GO_SPACE_CHAR''. 61 Commands get rewritten in the terminal sessions. This re-rewrite function helps 62 displaying command string in log output. 63 64 @param cmd: command that has to be rewritten for log output 65 @type cmd: C{str} 66 67 @return: the command with ,,X2GO_SPACE_CHAR'' re-replaced by blanks 68 @rtype: C{str} 69 70 """ 71 # X2Go run command replace X2GO_SPACE_CHAR string with blanks 72 if cmd: 73 cmd = cmd.replace("X2GO_SPACE_CHAR", " ") 74 return cmd
75
76 -def _rewrite_password(cmd, user=None, password=None):
77 """\ 78 In command strings Python X2Go replaces some macros with actual values: 79 80 - X2GO_USER -> the user name under which the user is authenticated via SSH 81 - X2GO_PASSWORD -> the password being used for SSH authentication 82 83 Both macros can be used to on-the-fly authenticate via RDP. 84 85 @param cmd: command that is to be sent to an X2Go server script 86 @type cmd: C{str} 87 @param user: the SSH authenticated user name 88 @type password: the password being used for SSH authentication 89 90 @return: the command with macros replaced 91 @rtype: C{str} 92 93 """ 94 # if there is a ,,-u X2GO_USER'' parameter in RDP options then we will replace 95 # it by our X2Go session password 96 if cmd and user: 97 cmd = cmd.replace('X2GO_USER', user) 98 # if there is a ,,-p X2GO_PASSWORD'' parameter in RDP options then we will replace 99 # it by our X2Go session password 100 if cmd and password: 101 cmd = cmd.replace('X2GO_PASSWORD', password) 102 return cmd
103
104 105 -class X2GoControlSession(paramiko.SSHClient):
106 """\ 107 In the Python X2Go concept, X2Go sessions fall into two parts: a control session and one to many terminal sessions. 108 109 The control session handles the SSH based communication between server and client. It is mainly derived from 110 C{paramiko.SSHClient} and adds on X2Go related functionality. 111 112 """
113 - def __init__(self, 114 profile_name='UNKNOWN', 115 add_to_known_hosts=False, 116 known_hosts=None, 117 forward_sshagent=False, 118 unique_hostkey_aliases=False, 119 terminal_backend=_BACKENDS['X2GoTerminalSession']['default'], 120 info_backend=_BACKENDS['X2GoServerSessionInfo']['default'], 121 list_backend=_BACKENDS['X2GoServerSessionList']['default'], 122 proxy_backend=_BACKENDS['X2GoProxy']['default'], 123 client_rootdir=os.path.join(defaults.LOCAL_HOME, defaults.X2GO_CLIENT_ROOTDIR), 124 sessions_rootdir=os.path.join(defaults.LOCAL_HOME, defaults.X2GO_SESSIONS_ROOTDIR), 125 ssh_rootdir=os.path.join(defaults.LOCAL_HOME, defaults.X2GO_SSH_ROOTDIR), 126 logger=None, loglevel=log.loglevel_DEFAULT, 127 published_applications_no_submenus=0, 128 low_latency=False, 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 forward_sshagent: forward SSH agent authentication requests to the X2Go client-side 144 @type forward_sshagent: C{bool} 145 @param unique_hostkey_aliases: instead of storing [<hostname>]:<port> in known_hosts file, use the 146 (unique-by-design) profile ID 147 @type unique_hostkey_aliases: C{bool} 148 @param terminal_backend: X2Go terminal session backend to use 149 @type terminal_backend: C{str} 150 @param info_backend: backend for handling storage of server session information 151 @type info_backend: C{X2GoServerSessionInfo*} instance 152 @param list_backend: backend for handling storage of session list information 153 @type list_backend: C{X2GoServerSessionList*} instance 154 @param proxy_backend: backend for handling the X-proxy connections 155 @type proxy_backend: C{X2GoProxy*} instance 156 @param client_rootdir: client base dir (default: ~/.x2goclient) 157 @type client_rootdir: C{str} 158 @param sessions_rootdir: sessions base dir (default: ~/.x2go) 159 @type sessions_rootdir: C{str} 160 @param ssh_rootdir: ssh base dir (default: ~/.ssh) 161 @type ssh_rootdir: C{str} 162 @param published_applications_no_submenus: published applications menus with less items than C{published_applications_no_submenus} 163 are rendered without submenus 164 @type published_applications_no_submenus: C{int} 165 @param logger: you can pass an L{X2GoLogger} object to the 166 L{X2GoControlSession} constructor 167 @type logger: L{X2GoLogger} instance 168 @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be 169 constructed with the given loglevel 170 @type loglevel: C{int} 171 @param low_latency: set this boolean switch for weak connections, it will double all timeout values. 172 @type low_latency: C{bool} 173 @param kwargs: catch any non-defined parameters in C{kwargs} 174 @type kwargs: C{dict} 175 176 """ 177 self.associated_terminals = {} 178 self.terminated_terminals = [] 179 180 self.profile_name = profile_name 181 self.add_to_known_hosts = add_to_known_hosts 182 self.known_hosts = known_hosts 183 self.forward_sshagent = forward_sshagent 184 self.unique_hostkey_aliases = unique_hostkey_aliases 185 186 self.hostname = None 187 self.port = None 188 189 self.sshproxy_session = None 190 191 self._session_auth_rsakey = None 192 self._remote_home = None 193 self._remote_group = {} 194 self._remote_username = None 195 self._remote_peername = None 196 197 self._server_versions = None 198 self._server_features = None 199 200 if logger is None: 201 self.logger = log.X2GoLogger(loglevel=loglevel) 202 else: 203 self.logger = copy.deepcopy(logger) 204 self.logger.tag = __NAME__ 205 206 self._terminal_backend = terminal_backend 207 self._info_backend = info_backend 208 self._list_backend = list_backend 209 self._proxy_backend = proxy_backend 210 211 self.client_rootdir = client_rootdir 212 self.sessions_rootdir = sessions_rootdir 213 self.ssh_rootdir = ssh_rootdir 214 215 self._published_applications_menu = {} 216 217 self.agent_chan = None 218 self.agent_handler = None 219 220 paramiko.SSHClient.__init__(self) 221 if self.add_to_known_hosts: 222 self.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 223 224 self.session_died = False 225 226 self.low_latency = low_latency 227 228 self.published_applications_no_submenus = published_applications_no_submenus 229 self._already_querying_published_applications = threading.Lock() 230 231 self._transport_lock = threading.Lock()
232
233 - def get_hostname(self):
234 """\ 235 Get the hostname as stored in the properties of this control session. 236 237 @return: the hostname of the connected X2Go server 238 @rtype: C{str} 239 240 """ 241 return self.hostname
242
243 - def get_port(self):
244 """\ 245 Get the port number of the SSH connection as stored in the properties of this control session. 246 247 @return: the server-side port number of the control session's SSH connection 248 @rtype: C{str} 249 250 """ 251 return self.port
252
253 - def load_session_host_keys(self):
254 """\ 255 Load known SSH host keys from the C{known_hosts} file. 256 257 If the file does not exist, create it first. 258 259 """ 260 if self.known_hosts is not None: 261 utils.touch_file(self.known_hosts) 262 self.load_host_keys(self.known_hosts)
263
264 - def __del__(self):
265 """\ 266 On instance descruction, do a proper session disconnect from the server. 267 268 """ 269 self.disconnect()
270
271 - def test_sftpclient(self):
272 ssh_transport = self.get_transport() 273 try: 274 self.sftp_client = paramiko.SFTPClient.from_transport(ssh_transport) 275 except (AttributeError, paramiko.SFTPError): 276 raise x2go_exceptions.X2GoSFTPClientException('failed to initialize SFTP channel')
277
278 - def _x2go_sftp_put(self, local_path, remote_path, timeout=20):
279 """ 280 Put a local file on the remote server via sFTP. 281 282 During sFTP operations, remote command execution gets blocked. 283 284 @param local_path: full local path name of the file to be put on the server 285 @type local_path: C{str} 286 @param remote_path: full remote path name of the server-side target location, path names have to be Unix-compliant 287 @type remote_path: C{str} 288 @param timeout: this SFTP put action should not take longer then the given value 289 @type timeout: C{int} 290 291 @raise X2GoControlSessionException: if the SSH connection dropped out 292 293 """ 294 ssh_transport = self.get_transport() 295 self._transport_lock.acquire() 296 if ssh_transport and ssh_transport.is_authenticated(): 297 self.logger('sFTP-put: %s -> %s:%s' % (os.path.normpath(local_path), self.remote_peername(), remote_path), loglevel=log.loglevel_DEBUG) 298 299 if self.low_latency: timeout = timeout * 2 300 timer = gevent.Timeout(timeout) 301 timer.start() 302 303 try: 304 try: 305 self.sftp_client = paramiko.SFTPClient.from_transport(ssh_transport) 306 except paramiko.SFTPError: 307 self._transport_lock.release() 308 raise x2go_exceptions.X2GoSFTPClientException('failed to initialize SFTP channel') 309 try: 310 self.sftp_client.put(os.path.normpath(local_path), remote_path) 311 except (x2go_exceptions.SSHException, socket.error, IOError): 312 # react to connection dropped error for SSH connections 313 self.session_died = True 314 self._transport_lock.release() 315 raise x2go_exceptions.X2GoControlSessionException('The SSH connection was dropped during an sFTP put action.') 316 317 except gevent.timeout.Timeout: 318 self.session_died = True 319 self._transport_lock.release() 320 if self.sshproxy_session: 321 self.sshproxy_session.stop_thread() 322 raise x2go_exceptions.X2GoControlSessionException('the X2Go control session timed out during an SFTP write command') 323 finally: 324 timer.cancel() 325 326 self.sftp_client = None 327 if self._transport_lock.locked(): 328 self._transport_lock.release()
329
330 - def _x2go_sftp_write(self, remote_path, content, timeout=20):
331 """ 332 Create a text file on the remote server via sFTP. 333 334 During sFTP operations, remote command execution gets blocked. 335 336 @param remote_path: full remote path name of the server-side target location, path names have to be Unix-compliant 337 @type remote_path: C{str} 338 @param content: a text file, multi-line files use Unix-link EOL style 339 @type content: C{str} 340 @param timeout: this SFTP write action should not take longer then the given value 341 @type timeout: C{int} 342 343 @raise X2GoControlSessionException: if the SSH connection dropped out 344 345 """ 346 ssh_transport = self.get_transport() 347 self._transport_lock.acquire() 348 if ssh_transport and ssh_transport.is_authenticated(): 349 self.logger('sFTP-write: opening remote file %s on host %s for writing' % (remote_path, self.remote_peername()), loglevel=log.loglevel_DEBUG) 350 351 if self.low_latency: timeout = timeout * 2 352 timer = gevent.Timeout(timeout) 353 timer.start() 354 355 try: 356 try: 357 self.sftp_client = paramiko.SFTPClient.from_transport(ssh_transport) 358 except paramiko.SFTPError: 359 self._transport_lock.release() 360 raise x2go_exceptions.X2GoSFTPClientException('failed to initialize SFTP channel') 361 try: 362 remote_fileobj = self.sftp_client.open(remote_path, 'w') 363 self.logger('sFTP-write: writing content: %s' % content, loglevel=log.loglevel_DEBUG_SFTPXFER) 364 remote_fileobj.write(content) 365 remote_fileobj.close() 366 except (x2go_exceptions.SSHException, socket.error, IOError): 367 self.session_died = True 368 self._transport_lock.release() 369 self.logger('sFTP-write: opening remote file %s on host %s failed' % (remote_path, self.remote_peername()), loglevel=log.loglevel_WARN) 370 if self.sshproxy_session: 371 self.sshproxy_session.stop_thread() 372 raise x2go_exceptions.X2GoControlSessionException('The SSH connection was dropped during an sFTP write action.') 373 374 except gevent.timeout.Timeout: 375 self.session_died = True 376 self._transport_lock.release() 377 if self.sshproxy_session: 378 self.sshproxy_session.stop_thread() 379 raise x2go_exceptions.X2GoControlSessionException('the X2Go control session timed out during an SFTP write command') 380 finally: 381 timer.cancel() 382 383 self.sftp_client = None 384 if self._transport_lock.locked(): 385 self._transport_lock.release()
386
387 - def _x2go_sftp_remove(self, remote_path, timeout=20):
388 """ 389 Remote a remote file from the server via sFTP. 390 391 During sFTP operations, remote command execution gets blocked. 392 393 @param remote_path: full remote path name of the server-side file to be removed, path names have to be Unix-compliant 394 @type remote_path: C{str} 395 @param timeout: this SFTP remove action should not take longer then the given value 396 @type timeout: C{int} 397 398 @raise X2GoControlSessionException: if the SSH connection dropped out 399 400 """ 401 ssh_transport = self.get_transport() 402 self._transport_lock.acquire() 403 if ssh_transport and ssh_transport.is_authenticated(): 404 self.logger('sFTP-write: removing remote file %s on host %s' % (remote_path, self.remote_peername()), loglevel=log.loglevel_DEBUG) 405 406 if self.low_latency: timeout = timeout * 2 407 timer = gevent.Timeout(timeout) 408 timer.start() 409 410 try: 411 try: 412 self.sftp_client = paramiko.SFTPClient.from_transport(ssh_transport) 413 except paramiko.SFTPError: 414 self._transport_lock.release() 415 raise x2go_exceptions.X2GoSFTPClientException('failed to initialize SFTP channel') 416 try: 417 self.sftp_client.remove(remote_path) 418 except (x2go_exceptions.SSHException, socket.error, IOError): 419 self.session_died = True 420 self._transport_lock.release() 421 self.logger('sFTP-write: removing remote file %s on host %s failed' % (remote_path, self.remote_peername()), loglevel=log.loglevel_WARN) 422 if self.sshproxy_session: 423 self.sshproxy_session.stop_thread() 424 raise x2go_exceptions.X2GoControlSessionException('The SSH connection was dropped during an sFTP remove action.') 425 426 except gevent.timeout.Timeout: 427 self.session_died = True 428 self._transport_lock.release() 429 if self.sshproxy_session: 430 self.sshproxy_session.stop_thread() 431 raise x2go_exceptions.X2GoControlSessionException('the X2Go control session timed out during an SFTP write command') 432 finally: 433 timer.cancel() 434 435 self.sftp_client = None 436 if self._transport_lock.locked(): 437 self._transport_lock.release()
438
439 - def _x2go_exec_command(self, cmd_line, loglevel=log.loglevel_INFO, timeout=20, **kwargs):
440 """ 441 Execute an X2Go server-side command via SSH. 442 443 During SSH command executions, sFTP operations get blocked. 444 445 @param cmd_line: the command to be executed on the remote server 446 @type cmd_line: C{str} or C{list} 447 @param loglevel: use this loglevel for reporting about remote command execution 448 @type loglevel: C{int} 449 @param timeout: if commands take longer than C{<timeout>} to be executed, consider the control session connection 450 to have died. 451 @type timeout: C{int} 452 @param kwargs: parameters that get passed through to the C{paramiko.SSHClient.exec_command()} method. 453 @type kwargs: C{dict} 454 455 @return: C{True} if the command could be successfully executed on the remote X2Go server 456 @rtype: C{bool} 457 458 @raise X2GoControlSessionException: if the command execution failed (due to a lost connection) 459 460 """ 461 if type(cmd_line) == types.ListType: 462 cmd = " ".join(cmd_line) 463 else: 464 cmd = cmd_line 465 466 cmd_uuid = str(uuid.uuid1()) 467 cmd = 'echo X2GODATABEGIN:%s; PATH=/usr/local/bin:/usr/bin:/bin sh -c \"%s\"; echo X2GODATAEND:%s' % (cmd_uuid, cmd, cmd_uuid) 468 469 if self.session_died: 470 self.logger("control session seams to be dead, not executing command ,,%s'' on X2Go server %s" % (_rerewrite_blanks(cmd), self.profile_name,), loglevel=loglevel) 471 return (cStringIO.StringIO(), cStringIO.StringIO(), cStringIO.StringIO('failed to execute command')) 472 473 self._transport_lock.acquire() 474 475 _retval = None 476 _password = None 477 478 ssh_transport = self.get_transport() 479 if ssh_transport and ssh_transport.is_authenticated(): 480 481 if self.low_latency: timeout = timeout * 2 482 timer = gevent.Timeout(timeout) 483 timer.start() 484 try: 485 self.logger("executing command on X2Go server ,,%s'': %s" % (self.profile_name, _rerewrite_blanks(cmd)), loglevel=loglevel) 486 if self._session_password: 487 _password = base64.b64decode(self._session_password) 488 _retval = self.exec_command(_rewrite_password(cmd, user=self.get_transport().get_username(), password=_password), **kwargs) 489 except AttributeError: 490 self.session_died = True 491 self._transport_lock.release() 492 if self.sshproxy_session: 493 self.sshproxy_session.stop_thread() 494 raise x2go_exceptions.X2GoControlSessionException('the X2Go control session has died unexpectedly') 495 except EOFError: 496 self.session_died = True 497 self._transport_lock.release() 498 if self.sshproxy_session: 499 self.sshproxy_session.stop_thread() 500 raise x2go_exceptions.X2GoControlSessionException('the X2Go control session has died unexpectedly') 501 except x2go_exceptions.SSHException: 502 self.session_died = True 503 self._transport_lock.release() 504 if self.sshproxy_session: 505 self.sshproxy_session.stop_thread() 506 raise x2go_exceptions.X2GoControlSessionException('the X2Go control session has died unexpectedly') 507 except gevent.timeout.Timeout: 508 self.session_died = True 509 self._transport_lock.release() 510 if self.sshproxy_session: 511 self.sshproxy_session.stop_thread() 512 raise x2go_exceptions.X2GoControlSessionException('the X2Go control session command timed out') 513 except socket.error: 514 self.session_died = True 515 self._transport_lock.release() 516 if self.sshproxy_session: 517 self.sshproxy_session.stop_thread() 518 raise x2go_exceptions.X2GoControlSessionException('the X2Go control session has died unexpectedly') 519 finally: 520 timer.cancel() 521 522 else: 523 self._transport_lock.release() 524 raise x2go_exceptions.X2GoControlSessionException('the X2Go control session is not connected (while issuing SSH command=%s)' % cmd) 525 526 if self._transport_lock.locked(): 527 self._transport_lock.release() 528 529 # sanitized X2Go relevant data, protect against data injection via .bashrc files 530 (_stdin, _stdout, _stderr) = _retval 531 raw_stdout = _stdout.read() 532 533 sanitized_stdout = '' 534 is_x2go_data = False 535 for line in raw_stdout.split('\n'): 536 if line.startswith('X2GODATABEGIN:'+cmd_uuid): 537 is_x2go_data = True 538 continue 539 if not is_x2go_data: continue 540 if line.startswith('X2GODATAEND:'+cmd_uuid): break 541 sanitized_stdout += line + "\n" 542 543 _stdout_new = cStringIO.StringIO(sanitized_stdout) 544 545 _retval = (_stdin, _stdout_new, _stderr) 546 return _retval
547 548 @property
549 - def _x2go_server_versions(self):
550 """\ 551 Render a dictionary of server-side X2Go components and their versions. Results get cached 552 once there has been one successful query. 553 554 """ 555 if self._server_versions is None: 556 self._server_versions = {} 557 (stdin, stdout, stderr) = self._x2go_exec_command('which x2goversion >/dev/null && x2goversion') 558 _lines = stdout.read().split('\n') 559 for _line in _lines: 560 if ':' not in _line: continue 561 comp = _line.split(':')[0].strip() 562 version = _line.split(':')[1].strip() 563 self._server_versions.update({comp: version}) 564 self.logger('server-side X2Go components and their versions are: %s' % self._server_versions, loglevel=log.loglevel_DEBUG) 565 return self._server_versions
566
567 - def query_server_versions(self, force=False):
568 """\ 569 Do a query for the server-side list of X2Go components and their versions. 570 571 @param force: do not use the cached component list, really ask the server (again) 572 @type force: C{bool} 573 574 @return: dictionary of X2Go components (as keys) and their versions (as values) 575 @rtype: C{list} 576 577 """ 578 if force: 579 self._server_versions = None 580 return self._x2go_server_versions
581 get_server_versions = query_server_versions 582 583 @property
584 - def _x2go_server_features(self):
585 """\ 586 Render a list of server-side X2Go features. Results get cached once there has been one successful query. 587 588 """ 589 if self._server_features is None: 590 (stdin, stdout, stderr) = self._x2go_exec_command('which x2gofeaturelist >/dev/null && x2gofeaturelist') 591 self._server_features = stdout.read().split('\n') 592 self._server_features = [ f for f in self._server_features if f ] 593 self._server_features.sort() 594 self.logger('server-side X2Go features are: %s' % self._server_features, loglevel=log.loglevel_DEBUG) 595 return self._server_features
596
597 - def query_server_features(self, force=False):
598 """\ 599 Do a query for the server-side list of X2Go features. 600 601 @param force: do not use the cached feature list, really ask the server (again) 602 @type force: C{bool} 603 604 @return: list of X2Go feature names 605 @rtype: C{list} 606 607 """ 608 if force: 609 self._server_features = None 610 return self._x2go_server_features
611 get_server_features = query_server_features 612 613 @property
614 - def _x2go_remote_home(self):
615 """\ 616 Retrieve and cache the remote home directory location. 617 618 """ 619 if self._remote_home is None: 620 (stdin, stdout, stderr) = self._x2go_exec_command('echo $HOME') 621 stdout_r = stdout.read() 622 if stdout_r: 623 self._remote_home = stdout_r.split()[0] 624 self.logger('remote user\' home directory: %s' % self._remote_home, loglevel=log.loglevel_DEBUG) 625 return self._remote_home 626 else: 627 return self._remote_home
628
629 - def _x2go_remote_group(self, group):
630 """\ 631 Retrieve and cache the members of a server-side POSIX group. 632 633 @param group: remote POSIX group name 634 @type group: C{str} 635 636 @return: list of POSIX group members 637 @rtype: C{list} 638 639 """ 640 if not self._remote_group.has_key(group): 641 (stdin, stdout, stderr) = self._x2go_exec_command('getent group %s | cut -d":" -f4' % group) 642 self._remote_group[group] = stdout.read().split('\n')[0].split(',') 643 self.logger('remote %s group: %s' % (group, self._remote_group[group]), loglevel=log.loglevel_DEBUG) 644 return self._remote_group[group] 645 else: 646 return self._remote_group[group]
647
648 - def is_x2gouser(self, username):
649 """\ 650 Is the remote user allowed to launch X2Go sessions? 651 652 FIXME: this method is currently non-functional. 653 654 @param username: remote user name 655 @type username: C{str} 656 657 @return: C{True} if the remote user is allowed to launch X2Go sessions 658 @rtype: C{bool} 659 660 """ 661 ### 662 ### FIXME: 663 ### 664 # discussion about server-side access restriction based on posix group membership or similar currently 665 # in process (as of 20110517, mg) 666 #return username in self._x2go_remote_group('x2gousers') 667 return True
668
669 - def is_sshfs_available(self):
670 """\ 671 Check if the remote user is allowed to use SSHFS mounts. 672 673 @return: C{True} if the user is allowed to connect client-side shares to the X2Go session 674 @rtype: C{bool} 675 676 """ 677 (stdin, stdout, stderr) = self._x2go_exec_command('which fusermount') 678 679 # if which returns the full path of fusermount, the current use is allowed to execute it 680 return bool(stdout.read())
681
682 - def remote_username(self):
683 """\ 684 Returns (and caches) the control session's remote username. 685 686 @return: SSH transport's user name 687 @rtype: C{str} 688 689 @raise X2GoControlSessionException: on SSH connection loss 690 691 """ 692 if self._remote_username is None: 693 if self.get_transport() is not None: 694 try: 695 self._remote_username = self.get_transport().get_username() 696 except: 697 self.session_died = True 698 raise x2go_exceptions.X2GoControlSessionException('Lost connection to X2Go server') 699 return self._remote_username
700
701 - def remote_peername(self):
702 """\ 703 Returns (and caches) the control session's remote host (name or ip). 704 705 @return: SSH transport's peer name 706 @rtype: C{tuple} 707 708 @raise X2GoControlSessionException: on SSH connection loss 709 710 """ 711 if self._remote_peername is None: 712 if self.get_transport() is not None: 713 try: 714 self._remote_peername = self.get_transport().getpeername() 715 except: 716 self.session_died = True 717 raise x2go_exceptions.X2GoControlSessionException('Lost connection to X2Go server') 718 return self._remote_peername
719 720 @property
721 - def _x2go_session_auth_rsakey(self):
722 """\ 723 Generate (and cache) a temporary RSA host key for the lifetime of this control session. 724 725 """ 726 if self._session_auth_rsakey is None: 727 self._session_auth_rsakey = paramiko.RSAKey.generate(defaults.RSAKEY_STRENGTH) 728 return self._session_auth_rsakey
729
730 - def set_profile_name(self, profile_name):
731 """\ 732 Manipulate the control session's profile name. 733 734 @param profile_name: new profile name for this control session 735 @type profile_name: C{str} 736 737 """ 738 self.profile_name = profile_name
739
740 - def check_host(self, hostname, port=22):
741 """\ 742 Wraps around a Paramiko/SSH host key check. 743 744 @param hostname: the remote X2Go server's hostname 745 @type hostname: C{str} 746 @param port: the SSH port of the remote X2Go server 747 @type port: C{int} 748 749 @return: C{True} if the host key check succeeded, C{False} otherwise 750 @rtype: C{bool} 751 752 """ 753 # trailing whitespace tolerance 754 hostname = hostname.strip() 755 756 # force into IPv4 for localhost connections 757 if hostname in ('localhost', 'localhost.localdomain'): 758 hostname = '127.0.0.1' 759 760 return checkhosts.check_ssh_host_key(self, hostname, port=port)
761
762 - def connect(self, hostname, port=22, username=None, password=None, passphrase=None, pkey=None, 763 key_filename=None, timeout=None, allow_agent=False, look_for_keys=False, 764 use_sshproxy=False, sshproxy_host=None, sshproxy_port=22, sshproxy_user=None, sshproxy_password=None, sshproxy_force_password_auth=False, 765 sshproxy_key_filename=None, sshproxy_pkey=None, sshproxy_look_for_keys=False, sshproxy_passphrase='', sshproxy_allow_agent=False, 766 sshproxy_tunnel=None, 767 add_to_known_hosts=None, 768 forward_sshagent=None, 769 unique_hostkey_aliases=None, 770 force_password_auth=False, 771 session_instance=None, 772 ):
773 """\ 774 Connect to an X2Go server and authenticate to it. This method is directly 775 inherited from the C{paramiko.SSHClient} class. The features of the Paramiko 776 SSH client connect method are recited here. The parameters C{add_to_known_hosts}, 777 C{force_password_auth}, C{session_instance} and all SSH proxy related parameters 778 have been added as X2Go specific parameters 779 780 The server's host key is checked against the system host keys 781 (see C{load_system_host_keys}) and any local host keys (C{load_host_keys}). 782 If the server's hostname is not found in either set of host keys, the missing host 783 key policy is used (see C{set_missing_host_key_policy}). The default policy is 784 to reject the key and raise an C{SSHException}. 785 786 Authentication is attempted in the following order of priority: 787 788 - The C{pkey} or C{key_filename} passed in (if any) 789 - Any key we can find through an SSH agent 790 - Any "id_rsa" or "id_dsa" key discoverable in C{~/.ssh/} 791 - Plain username/password auth, if a password was given 792 793 If a private key requires a password to unlock it, and a password is 794 passed in, that password will be used to attempt to unlock the key. 795 796 @param hostname: the server to connect to 797 @type hostname: C{str} 798 @param port: the server port to connect to 799 @type port: C{int} 800 @param username: the username to authenticate as (defaults to the 801 current local username) 802 @type username: C{str} 803 @param password: a password to use for authentication or for unlocking 804 a private key 805 @type password: C{str} 806 @param passphrase: a passphrase to use for unlocking 807 a private key in case the password is already needed for two-factor 808 authentication 809 @type passphrase: C{str} 810 @param key_filename: the filename, or list of filenames, of optional 811 private key(s) to try for authentication 812 @type key_filename: C{str} or list(str) 813 @param pkey: an optional private key to use for authentication 814 @type pkey: C{PKey} 815 @param forward_sshagent: forward SSH agent authentication requests to the X2Go client-side 816 (will update the class property of the same name) 817 @type forward_sshagent: C{bool} 818 @param unique_hostkey_aliases: update the unique_hostkey_aliases class property 819 @type unique_hostkey_aliases: C{bool} 820 @param timeout: an optional timeout (in seconds) for the TCP connect 821 @type timeout: float 822 @param look_for_keys: set to C{True} to enable searching for discoverable 823 private key files in C{~/.ssh/} 824 @type look_for_keys: C{bool} 825 @param allow_agent: set to C{True} to enable connecting to a local SSH agent 826 for acquiring authentication information 827 @type allow_agent: C{bool} 828 @param add_to_known_hosts: non-paramiko option, if C{True} paramiko.AutoAddPolicy() 829 is used as missing-host-key-policy. If set to C{False} paramiko.RejectPolicy() 830 is used 831 @type add_to_known_hosts: C{bool} 832 @param force_password_auth: non-paramiko option, disable pub/priv key authentication 833 completely, even if the C{pkey} or the C{key_filename} parameter is given 834 @type force_password_auth: C{bool} 835 @param session_instance: an instance L{X2GoSession} using this L{X2GoControlSession} 836 instance. 837 @type session_instance: C{obj} 838 @param use_sshproxy: connect through an SSH proxy 839 @type use_sshproxy: C{True} if an SSH proxy is to be used for tunneling the connection 840 @param sshproxy_host: hostname of the SSH proxy server 841 @type sshproxy_host: C{str} 842 @param sshproxy_port: port of the SSH proxy server 843 @type sshproxy_port: C{int} 844 @param sshproxy_user: username that we use for authenticating against C{<sshproxy_host>} 845 @type sshproxy_user: C{str} 846 @param sshproxy_password: a password to use for SSH proxy authentication or for unlocking 847 a private key 848 @type sshproxy_password: C{str} 849 @param sshproxy_passphrase: a passphrase to use for unlocking 850 a private key needed for the SSH proxy host in case the sshproxy_password is already needed for 851 two-factor authentication 852 @type sshproxy_passphrase: C{str} 853 @param sshproxy_force_password_auth: enforce using a given C{sshproxy_password} even if a key(file) is given 854 @type sshproxy_force_password_auth: C{bool} 855 @param sshproxy_key_filename: local file location of the private key file 856 @type sshproxy_key_filename: C{str} 857 @param sshproxy_pkey: an optional private key to use for SSH proxy authentication 858 @type sshproxy_pkey: C{PKey} 859 @param sshproxy_look_for_keys: set to C{True} to enable connecting to a local SSH agent 860 for acquiring authentication information (for SSH proxy authentication) 861 @type sshproxy_look_for_keys: C{bool} 862 @param sshproxy_allow_agent: set to C{True} to enable connecting to a local SSH agent 863 for acquiring authentication information (for SSH proxy authentication) 864 @type sshproxy_allow_agent: C{bool} 865 @param sshproxy_tunnel: the SSH proxy tunneling parameters, format is: <local-address>:<local-port>:<remote-address>:<remote-port> 866 @type sshproxy_tunnel: C{str} 867 868 @return: C{True} if an authenticated SSH transport could be retrieved by this method 869 @rtype: C{bool} 870 871 @raise BadHostKeyException: if the server's host key could not be 872 verified 873 @raise AuthenticationException: if authentication failed 874 @raise SSHException: if there was any other error connecting or 875 establishing an SSH session 876 @raise socket.error: if a socket error occurred while connecting 877 @raise X2GoSSHProxyException: any SSH proxy exception is passed through while establishing the SSH proxy connection and tunneling setup 878 @raise X2GoSSHAuthenticationException: any SSH proxy authentication exception is passed through while establishing the SSH proxy connection and tunneling setup 879 @raise X2GoRemoteHomeException: if the remote home directory does not exist or is not accessible 880 @raise X2GoControlSessionException: if the remote peer has died unexpectedly 881 882 """ 883 _fake_hostname = None 884 885 if hostname and type(hostname) not in (types.UnicodeType, types.StringType): 886 hostname = [hostname] 887 if hostname and type(hostname) is types.ListType: 888 hostname = random.choice(hostname) 889 890 if not username: 891 self.logger('no username specified, cannot connect without username', loglevel=log.loglevel_ERROR) 892 raise paramiko.AuthenticationException('no username specified, cannot connect without username') 893 894 if type(password) not in (types.StringType, types.UnicodeType): 895 password = '' 896 if type(sshproxy_password) not in (types.StringType, types.UnicodeType): 897 sshproxy_password = '' 898 899 if unique_hostkey_aliases is None: 900 unique_hostkey_aliases = self.unique_hostkey_aliases 901 # prep the fake hostname with the real hostname, so we trigger the corresponding code path in 902 # x2go.checkhosts and either of its missing host key policies 903 if unique_hostkey_aliases: 904 if port != 22: _fake_hostname = "[%s]:%s" % (hostname, port) 905 else: _fake_hostname = hostname 906 907 if add_to_known_hosts is None: 908 add_to_known_hosts = self.add_to_known_hosts 909 910 if forward_sshagent is None: 911 forward_sshagent = self.forward_sshagent 912 913 if look_for_keys: 914 key_filename = None 915 pkey = None 916 917 _twofactorauth = False 918 if password and (passphrase is None) and not force_password_auth: passphrase = password 919 920 if use_sshproxy and sshproxy_host and sshproxy_user: 921 try: 922 if not sshproxy_tunnel: 923 sshproxy_tunnel = "localhost:44444:%s:%s" % (hostname, port) 924 self.sshproxy_session = sshproxy.X2GoSSHProxy(known_hosts=self.known_hosts, 925 add_to_known_hosts=add_to_known_hosts, 926 sshproxy_host=sshproxy_host, 927 sshproxy_port=sshproxy_port, 928 sshproxy_user=sshproxy_user, 929 sshproxy_password=sshproxy_password, 930 sshproxy_passphrase=sshproxy_passphrase, 931 sshproxy_force_password_auth=sshproxy_force_password_auth, 932 sshproxy_key_filename=sshproxy_key_filename, 933 sshproxy_pkey=sshproxy_pkey, 934 sshproxy_look_for_keys=sshproxy_look_for_keys, 935 sshproxy_allow_agent=sshproxy_allow_agent, 936 sshproxy_tunnel=sshproxy_tunnel, 937 session_instance=session_instance, 938 logger=self.logger, 939 ) 940 hostname = self.sshproxy_session.get_local_proxy_host() 941 port = self.sshproxy_session.get_local_proxy_port() 942 _fake_hostname = self.sshproxy_session.get_remote_host() 943 _fake_port = self.sshproxy_session.get_remote_port() 944 if _fake_port != 22: 945 _fake_hostname = "[%s]:%s" % (_fake_hostname, _fake_port) 946 947 except: 948 if self.sshproxy_session: 949 self.sshproxy_session.stop_thread() 950 self.sshproxy_session = None 951 raise 952 953 if self.sshproxy_session is not None: 954 self.sshproxy_session.start() 955 956 # divert port to sshproxy_session's local forwarding port (it might have changed due to 957 # SSH connection errors 958 gevent.sleep(.1) 959 port = self.sshproxy_session.get_local_proxy_port() 960 961 if not add_to_known_hosts and session_instance: 962 self.set_missing_host_key_policy(checkhosts.X2GoInteractiveAddPolicy(caller=self, session_instance=session_instance, fake_hostname=_fake_hostname)) 963 964 if add_to_known_hosts: 965 self.set_missing_host_key_policy(checkhosts.X2GoAutoAddPolicy(caller=self, session_instance=session_instance, fake_hostname=_fake_hostname)) 966 967 # trailing whitespace tolerance in hostname 968 hostname = hostname.strip() 969 970 self.logger('connecting to [%s]:%s' % (hostname, port), loglevel=log.loglevel_NOTICE) 971 972 self.load_session_host_keys() 973 974 _hostname = hostname 975 # enforce IPv4 for localhost address 976 if _hostname in ('localhost', 'localhost.localdomain'): 977 _hostname = '127.0.0.1' 978 979 # update self.forward_sshagent via connect method parameter 980 if forward_sshagent is not None: 981 self.forward_sshagent = forward_sshagent 982 983 if timeout and self.low_latency: 984 timeout = timeout * 2 985 986 if key_filename and "~" in key_filename: 987 key_filename = os.path.expanduser(key_filename) 988 989 if key_filename or pkey or look_for_keys or allow_agent or (password and force_password_auth): 990 try: 991 if password and force_password_auth: 992 self.logger('trying password based SSH authentication with server', loglevel=log.loglevel_DEBUG) 993 paramiko.SSHClient.connect(self, _hostname, port=port, username=username, password=password, pkey=None, 994 key_filename=None, timeout=timeout, allow_agent=False, 995 look_for_keys=False) 996 elif (key_filename and os.path.exists(os.path.normpath(key_filename))) or pkey: 997 self.logger('trying SSH pub/priv key authentication with server', loglevel=log.loglevel_DEBUG) 998 paramiko.SSHClient.connect(self, _hostname, port=port, username=username, pkey=pkey, 999 key_filename=key_filename, timeout=timeout, allow_agent=False, 1000 look_for_keys=False) 1001 else: 1002 self.logger('trying SSH key discovery or agent authentication with server', loglevel=log.loglevel_DEBUG) 1003 paramiko.SSHClient.connect(self, _hostname, port=port, username=username, pkey=None, 1004 key_filename=None, timeout=timeout, allow_agent=allow_agent, 1005 look_for_keys=look_for_keys) 1006 1007 except (paramiko.PasswordRequiredException, paramiko.SSHException), e: 1008 self.close() 1009 if type(e) == paramiko.SSHException and str(e).startswith('Two-factor authentication requires a password'): 1010 self.logger('X2Go Server requests two-factor authentication', loglevel=log.loglevel_NOTICE) 1011 _twofactorauth = True 1012 if passphrase is not None: 1013 self.logger('unlock SSH private key file with provided password', loglevel=log.loglevel_INFO) 1014 try: 1015 if not password: password = None 1016 if (key_filename and os.path.exists(os.path.normpath(key_filename))) or pkey: 1017 self.logger('re-trying SSH pub/priv key authentication with server', loglevel=log.loglevel_DEBUG) 1018 try: 1019 paramiko.SSHClient.connect(self, _hostname, port=port, username=username, password=password, passphrase=passphrase, pkey=pkey, 1020 key_filename=key_filename, timeout=timeout, allow_agent=False, 1021 look_for_keys=False) 1022 except TypeError: 1023 if _twofactorauth and password and passphrase and password != passphrase: 1024 self.logger('your version of Paramiko/SSH does not support authentication workflows which require SSH key decryption in combination with two-factor authentication', loglevel=log.loglevel_WARN) 1025 paramiko.SSHClient.connect(self, _hostname, port=port, username=username, password=password, pkey=pkey, 1026 key_filename=key_filename, timeout=timeout, allow_agent=False, 1027 look_for_keys=False) 1028 else: 1029 self.logger('re-trying SSH key discovery now with passphrase for unlocking the key(s)', loglevel=log.loglevel_DEBUG) 1030 try: 1031 paramiko.SSHClient.connect(self, _hostname, port=port, username=username, password=password, passphrase=passphrase, pkey=None, 1032 key_filename=None, timeout=timeout, allow_agent=allow_agent, 1033 look_for_keys=look_for_keys) 1034 except TypeError: 1035 if _twofactorauth and password and passphrase and password != passphrase: 1036 self.logger('your version of Paramiko/SSH does not support authentication workflows which require SSH key decryption in combination with two-factor authentication', loglevel=log.loglevel_WARN) 1037 paramiko.SSHClient.connect(self, _hostname, port=port, username=username, password=password, pkey=None, 1038 key_filename=None, timeout=timeout, allow_agent=allow_agent, 1039 look_for_keys=look_for_keys) 1040 1041 except paramiko.AuthenticationException, auth_e: 1042 # the provided password cannot be used to unlock any private SSH key file (i.e. wrong password) 1043 raise paramiko.AuthenticationException(str(auth_e)) 1044 1045 except paramiko.SSHException, auth_e: 1046 if str(auth_e) == 'No authentication methods available': 1047 raise paramiko.AuthenticationException('Interactive password authentication required!') 1048 else: 1049 self.close() 1050 if self.sshproxy_session: 1051 self.sshproxy_session.stop_thread() 1052 raise auth_e 1053 1054 else: 1055 self.close() 1056 if self.sshproxy_session: 1057 self.sshproxy_session.stop_thread() 1058 raise e 1059 1060 except paramiko.AuthenticationException, e: 1061 self.close() 1062 if password: 1063 self.logger('next auth mechanism we\'ll try is password authentication', loglevel=log.loglevel_DEBUG) 1064 try: 1065 paramiko.SSHClient.connect(self, _hostname, port=port, username=username, password=password, 1066 key_filename=None, pkey=None, timeout=timeout, allow_agent=False, look_for_keys=False) 1067 except: 1068 self.close() 1069 if self.sshproxy_session: 1070 self.sshproxy_session.stop_thread() 1071 raise 1072 else: 1073 self.close() 1074 if self.sshproxy_session: 1075 self.sshproxy_session.stop_thread() 1076 raise e 1077 1078 except paramiko.SSHException, e: 1079 if str(e) == 'No authentication methods available': 1080 raise paramiko.AuthenticationException('Interactive password authentication required!') 1081 else: 1082 self.close() 1083 if self.sshproxy_session: 1084 self.sshproxy_session.stop_thread() 1085 raise e 1086 1087 except: 1088 self.close() 1089 if self.sshproxy_session: 1090 self.sshproxy_session.stop_thread() 1091 raise 1092 1093 # if there is no private key (and no agent auth), we will use the given password, if any 1094 else: 1095 # create a random password if password is empty to trigger host key validity check 1096 if not password: 1097 password = "".join([random.choice(string.letters+string.digits) for x in range(1, 20)]) 1098 self.logger('performing SSH password authentication with server', loglevel=log.loglevel_DEBUG) 1099 #try: 1100 paramiko.SSHClient.connect(self, _hostname, port=port, username=username, password=password, 1101 timeout=timeout, allow_agent=False, look_for_keys=False) 1102 #except paramiko.AuthenticationException, e: 1103 # self.close() 1104 # if self.sshproxy_session: 1105 # self.sshproxy_session.stop_thread() 1106 # raise e 1107 #except: 1108 # self.close() 1109 # if self.sshproxy_session: 1110 # self.sshproxy_session.stop_thread() 1111 # raise 1112 1113 self.set_missing_host_key_policy(paramiko.RejectPolicy()) 1114 1115 self.hostname = hostname 1116 self.port = port 1117 1118 # preparing reverse tunnels 1119 ssh_transport = self.get_transport() 1120 ssh_transport.reverse_tunnels = {} 1121 1122 # mark Paramiko/SSH transport as X2GoControlSession 1123 ssh_transport._x2go_session_marker = True 1124 try: 1125 self._session_password = base64.b64encode(password) 1126 except TypeError: 1127 self._session_password = None 1128 1129 if ssh_transport is not None: 1130 1131 # since Paramiko 1.7.7.1 there is compression available, let's use it if present... 1132 if x2go._paramiko.PARAMIKO_FEATURE['use-compression']: 1133 ssh_transport.use_compression(compress=False) 1134 # enable keep alive callbacks 1135 ssh_transport.set_keepalive(5) 1136 1137 self.session_died = False 1138 self.query_server_features(force=True) 1139 if self.forward_sshagent: 1140 if x2go._paramiko.PARAMIKO_FEATURE['forward-ssh-agent']: 1141 try: 1142 self.agent_chan = ssh_transport.open_session() 1143 self.agent_handler = paramiko.agent.AgentRequestHandler(self.agent_chan) 1144 self.logger('Requesting SSH agent forwarding for control session of connected session profile %s' % self.profile_name, loglevel=log.loglevel_INFO) 1145 except EOFError, e: 1146 # if we come across an EOFError here, we must assume the session is dead... 1147 self.session_died = True 1148 raise x2go_exceptions.X2GoControlSessionException('The SSH connection was dropped while setting up SSH agent forwarding socket.') 1149 else: 1150 self.logger('SSH agent forwarding is not available in the Paramiko version used with this instance of Python X2Go', loglevel=log.loglevel_WARN) 1151 1152 else: 1153 self.close() 1154 if self.sshproxy_session: 1155 self.sshproxy_session.stop_thread() 1156 1157 self._remote_home = None 1158 if not self.home_exists(): 1159 self.close() 1160 if self.sshproxy_session: 1161 self.sshproxy_session.stop_thread() 1162 raise x2go_exceptions.X2GoRemoteHomeException('remote home directory does not exist') 1163 1164 return (self.get_transport() is not None)
1165
1166 - def dissociate(self, terminal_session):
1167 """\ 1168 Drop an associated terminal session. 1169 1170 @param terminal_session: the terminal session object to remove from the list of associated terminals 1171 @type terminal_session: C{X2GoTerminalSession*} 1172 1173 """ 1174 for t_name in self.associated_terminals.keys(): 1175 if self.associated_terminals[t_name] == terminal_session: 1176 del self.associated_terminals[t_name] 1177 if self.terminated_terminals.has_key(t_name): 1178 del self.terminated_terminals[t_name]
1179
1180 - def disconnect(self):
1181 """\ 1182 Disconnect this control session from the remote server. 1183 1184 @return: report success or failure after having disconnected 1185 @rtype: C{bool} 1186 1187 """ 1188 if self.associated_terminals: 1189 t_names = self.associated_terminals.keys() 1190 for t_obj in self.associated_terminals.values(): 1191 try: 1192 if not self.session_died: 1193 t_obj.suspend() 1194 except x2go_exceptions.X2GoTerminalSessionException: 1195 pass 1196 except x2go_exceptions.X2GoControlSessionException: 1197 self.session_died 1198 t_obj.__del__() 1199 for t_name in t_names: 1200 try: 1201 del self.associated_terminals[t_name] 1202 except KeyError: 1203 pass 1204 1205 self._remote_home = None 1206 self._remote_group = {} 1207 1208 self._session_auth_rsakey = None 1209 1210 # in any case, release out internal transport lock 1211 if self._transport_lock.locked(): 1212 self._transport_lock.release() 1213 1214 # close SSH agent auth forwarding objects 1215 if self.agent_handler is not None: 1216 self.agent_handler.close() 1217 1218 if self.agent_chan is not None: 1219 try: 1220 self.agent_chan.close() 1221 except EOFError: 1222 pass 1223 1224 retval = False 1225 try: 1226 if self.get_transport() is not None: 1227 retval = self.get_transport().is_active() 1228 try: 1229 self.close() 1230 except IOError: 1231 pass 1232 except AttributeError: 1233 # if the Paramiko _transport object has not yet been initialized, ignore it 1234 # but state that this method call did not close the SSH client, but was already closed 1235 pass 1236 1237 # take down sshproxy_session no matter what happened to the control session itself 1238 if self.sshproxy_session is not None: 1239 self.sshproxy_session.stop_thread() 1240 1241 return retval
1242
1243 - def home_exists(self):
1244 """\ 1245 Test if the remote home directory exists. 1246 1247 @return: C{True} if the home directory exists, C{False} otherwise 1248 @rtype: C{bool} 1249 1250 """ 1251 (_stdin, _stdout, _stderr) = self._x2go_exec_command('stat -tL "%s"' % self._x2go_remote_home, loglevel=log.loglevel_DEBUG) 1252 if _stdout.read(): 1253 return True 1254 return False
1255 1256
1257 - def is_alive(self):
1258 """\ 1259 Test if the connection to the remote X2Go server is still alive. 1260 1261 @return: C{True} if the connection is still alive, C{False} otherwise 1262 @rtype: C{bool} 1263 1264 """ 1265 try: 1266 if self._x2go_exec_command('echo', loglevel=log.loglevel_DEBUG): 1267 return True 1268 except x2go_exceptions.X2GoControlSessionException: 1269 self.session_died = True 1270 self.disconnect() 1271 return False
1272
1273 - def has_session_died(self):
1274 """\ 1275 Test if the connection to the remote X2Go server died on the way. 1276 1277 @return: C{True} if the connection has died, C{False} otherwise 1278 @rtype: C{bool} 1279 1280 """ 1281 return self.session_died
1282
1283 - def get_published_applications(self, lang=None, refresh=False, raw=False, very_raw=False, max_no_submenus=defaults.PUBAPP_MAX_NO_SUBMENUS):
1284 """\ 1285 Retrieve the menu tree of published applications from the remote X2Go server. 1286 1287 The C{raw} option lets this method return a C{list} of C{dict} elements. Each C{dict} elements has a 1288 C{desktop} key containing a shortened version of the text output of a .desktop file and an C{icon} key 1289 which contains the desktop base64-encoded icon data. 1290 1291 The {very_raw} lets this method return the output of the C{x2gogetapps} script as is. 1292 1293 @param lang: locale/language identifier 1294 @type lang: C{str} 1295 @param refresh: force reload of the menu tree from X2Go server 1296 @type refresh: C{bool} 1297 @param raw: retrieve a raw output of the server list of published applications 1298 @type raw: C{bool} 1299 @param very_raw: retrieve a very raw output of the server list of published applications 1300 @type very_raw: C{bool} 1301 1302 @return: an i18n capable menu tree packed as a Python dictionary 1303 @rtype: C{list} 1304 1305 """ 1306 self._already_querying_published_applications.acquire() 1307 1308 if defaults.X2GOCLIENT_OS != 'Windows' and lang is None: 1309 lang = locale.getdefaultlocale()[0] 1310 elif lang is None: 1311 lang = 'en' 1312 1313 if 'X2GO_PUBLISHED_APPLICATIONS' in self.get_server_features(): 1314 if self._published_applications_menu is {} or \ 1315 not self._published_applications_menu.has_key(lang) or \ 1316 raw or very_raw or refresh or \ 1317 (self.published_applications_no_submenus != max_no_submenus): 1318 1319 self.published_applications_no_submenus = max_no_submenus 1320 1321 ### STAGE 1: retrieve menu from server 1322 1323 self.logger('querying server (%s) for list of published applications' % self.profile_name, loglevel=log.loglevel_NOTICE) 1324 (stdin, stdout, stderr) = self._x2go_exec_command('which x2gogetapps >/dev/null && x2gogetapps') 1325 _raw_output = stdout.read() 1326 1327 if very_raw: 1328 self.logger('published applications query for %s finished, return very raw output' % self.profile_name, loglevel=log.loglevel_NOTICE) 1329 self._already_querying_published_applications.release() 1330 return _raw_output 1331 1332 ### STAGE 2: dissect the text file retrieved from server, cut into single menu elements 1333 1334 _raw_menu_items = _raw_output.split('</desktop>\n') 1335 _raw_menu_items = [ i.replace('<desktop>\n', '') for i in _raw_menu_items ] 1336 _menu = [] 1337 for _raw_menu_item in _raw_menu_items: 1338 if '<icon>\n' in _raw_menu_item and '</icon>' in _raw_menu_item: 1339 _menu_item = _raw_menu_item.split('<icon>\n')[0] + _raw_menu_item.split('</icon>\n')[1] 1340 _icon_base64 = _raw_menu_item.split('<icon>\n')[1].split('</icon>\n')[0] 1341 else: 1342 _menu_item = _raw_menu_item 1343 _icon_base64 = None 1344 if _menu_item: 1345 _menu.append({ 'desktop': _menu_item, 'icon': _icon_base64, }) 1346 _menu_item = None 1347 _icon_base64 = None 1348 1349 if raw: 1350 self.logger('published applications query for %s finished, returning raw output' % self.profile_name, loglevel=log.loglevel_NOTICE) 1351 self._already_querying_published_applications.release() 1352 return _menu 1353 1354 if len(_menu) > max_no_submenus >= 0: 1355 _render_submenus = True 1356 else: 1357 _render_submenus = False 1358 1359 # STAGE 3: create menu structure in a Python dictionary 1360 1361 _category_map = { 1362 lang: { 1363 'Multimedia': [], 1364 'Development': [], 1365 'Education': [], 1366 'Games': [], 1367 'Graphics': [], 1368 'Internet': [], 1369 'Office': [], 1370 'System': [], 1371 'Utilities': [], 1372 'Other Applications': [], 1373 'TOP': [], 1374 } 1375 } 1376 _empty_menus = _category_map[lang].keys() 1377 1378 for item in _menu: 1379 1380 _menu_entry_name = '' 1381 _menu_entry_fallback_name = '' 1382 _menu_entry_comment = '' 1383 _menu_entry_fallback_comment = '' 1384 _menu_entry_exec = '' 1385 _menu_entry_cat = '' 1386 _menu_entry_shell = False 1387 1388 lang_regio = lang 1389 lang_only = lang_regio.split('_')[0] 1390 1391 for line in item['desktop'].split('\n'): 1392 if re.match('^Name\[%s\]=.*' % lang_regio, line) or re.match('Name\[%s\]=.*' % lang_only, line): 1393 _menu_entry_name = line.split("=")[1].strip() 1394 elif re.match('^Name=.*', line): 1395 _menu_entry_fallback_name = line.split("=")[1].strip() 1396 elif re.match('^Comment\[%s\]=.*' % lang_regio, line) or re.match('Comment\[%s\]=.*' % lang_only, line): 1397 _menu_entry_comment = line.split("=")[1].strip() 1398 elif re.match('^Comment=.*', line): 1399 _menu_entry_fallback_comment = line.split("=")[1].strip() 1400 elif re.match('^Exec=.*', line): 1401 _menu_entry_exec = line.split("=")[1].strip() 1402 elif re.match('^Terminal=.*(t|T)(r|R)(u|U)(e|E).*', line): 1403 _menu_entry_shell = True 1404 elif re.match('^Categories=.*', line): 1405 if 'X2Go-Top' in line: 1406 _menu_entry_cat = 'TOP' 1407 elif 'Audio' in line or 'Video' in line: 1408 _menu_entry_cat = 'Multimedia' 1409 elif 'Development' in line: 1410 _menu_entry_cat = 'Development' 1411 elif 'Education' in line: 1412 _menu_entry_cat = 'Education' 1413 elif 'Game' in line: 1414 _menu_entry_cat = 'Games' 1415 elif 'Graphics' in line: 1416 _menu_entry_cat = 'Graphics' 1417 elif 'Network' in line: 1418 _menu_entry_cat = 'Internet' 1419 elif 'Office' in line: 1420 _menu_entry_cat = 'Office' 1421 elif 'Settings' in line: 1422 continue 1423 elif 'System' in line: 1424 _menu_entry_cat = 'System' 1425 elif 'Utility' in line: 1426 _menu_entry_cat = 'Utilities' 1427 else: 1428 _menu_entry_cat = 'Other Applications' 1429 1430 if not _menu_entry_exec: 1431 continue 1432 else: 1433 # FIXME: strip off any noted options (%f, %F, %u, %U, ...), this can be more intelligent 1434 _menu_entry_exec = _menu_entry_exec.replace('%f', '').replace('%F','').replace('%u','').replace('%U','') 1435 if _menu_entry_shell: 1436 _menu_entry_exec = "x-terminal-emulator -e '%s'" % _menu_entry_exec 1437 1438 if not _menu_entry_cat: 1439 _menu_entry_cat = 'Other Applications' 1440 1441 if not _render_submenus: 1442 _menu_entry_cat = 'TOP' 1443 1444 if _menu_entry_cat in _empty_menus: 1445 _empty_menus.remove(_menu_entry_cat) 1446 1447 if not _menu_entry_name: _menu_entry_name = _menu_entry_fallback_name 1448 if not _menu_entry_comment: _menu_entry_comment = _menu_entry_fallback_comment 1449 if not _menu_entry_comment: _menu_entry_comment = _menu_entry_name 1450 1451 _menu_entry_icon = item['icon'] 1452 1453 _category_map[lang][_menu_entry_cat].append( 1454 { 1455 'name': _menu_entry_name, 1456 'comment': _menu_entry_comment, 1457 'exec': _menu_entry_exec, 1458 'icon': _menu_entry_icon, 1459 } 1460 ) 1461 1462 for _cat in _empty_menus: 1463 del _category_map[lang][_cat] 1464 1465 for _cat in _category_map[lang].keys(): 1466 _sorted = sorted(_category_map[lang][_cat], key=lambda k: k['name']) 1467 _category_map[lang][_cat] = _sorted 1468 1469 self._published_applications_menu.update(_category_map) 1470 self.logger('published applications query for %s finished, return menu tree' % self.profile_name, loglevel=log.loglevel_NOTICE) 1471 1472 else: 1473 # FIXME: ignoring the absence of the published applications feature for now, handle it appropriately later 1474 pass 1475 1476 self._already_querying_published_applications.release() 1477 return self._published_applications_menu
1478
1479 - def start(self, **kwargs):
1480 """\ 1481 Start a new X2Go session. 1482 1483 The L{X2GoControlSession.start()} method accepts any parameter 1484 that can be passed to any of the C{X2GoTerminalSession} backend class 1485 constructors. 1486 1487 @param kwargs: parameters that get passed through to the control session's 1488 L{resume()} method, only the C{session_name} parameter will get removed 1489 before pass-through 1490 @type kwargs: C{dict} 1491 1492 @return: return value of the cascaded L{resume()} method, denoting the success or failure 1493 of the session startup 1494 @rtype: C{bool} 1495 1496 """ 1497 if 'session_name' in kwargs.keys(): 1498 del kwargs['session_name'] 1499 return self.resume(**kwargs)
1500
1501 - def resume(self, session_name=None, session_instance=None, session_list=None, **kwargs):
1502 """\ 1503 Resume a running/suspended X2Go session. 1504 1505 The L{X2GoControlSession.resume()} method accepts any parameter 1506 that can be passed to any of the C{X2GoTerminalSession*} backend class constructors. 1507 1508 @return: True if the session could be successfully resumed 1509 @rtype: C{bool} 1510 1511 @raise X2GoUserException: if the remote user is not allowed to launch/resume X2Go sessions. 1512 1513 """ 1514 if self.get_transport() is not None: 1515 1516 if not self.is_x2gouser(self.get_transport().get_username()): 1517 raise x2go_exceptions.X2GoUserException('remote user %s is not allowed to run X2Go commands' % self.get_transport().get_username()) 1518 1519 session_info = None 1520 try: 1521 if session_name is not None: 1522 if session_list: 1523 session_info = session_list[session_name] 1524 else: 1525 session_info = self.list_sessions()[session_name] 1526 except KeyError: 1527 _success = False 1528 1529 _terminal = self._terminal_backend(self, 1530 profile_name=self.profile_name, 1531 session_info=session_info, 1532 info_backend=self._info_backend, 1533 list_backend=self._list_backend, 1534 proxy_backend=self._proxy_backend, 1535 client_rootdir=self.client_rootdir, 1536 session_instance=session_instance, 1537 sessions_rootdir=self.sessions_rootdir, 1538 **kwargs) 1539 1540 _success = False 1541 try: 1542 if session_name is not None: 1543 _success = _terminal.resume() 1544 else: 1545 _success = _terminal.start() 1546 except x2go_exceptions.X2GoTerminalSessionException: 1547 _success = False 1548 1549 if _success: 1550 while not _terminal.ok(): 1551 gevent.sleep(.2) 1552 1553 if _terminal.ok(): 1554 self.associated_terminals[_terminal.get_session_name()] = _terminal 1555 self.get_transport().reverse_tunnels[_terminal.get_session_name()] = { 1556 'sshfs': (0, None), 1557 'snd': (0, None), 1558 } 1559 1560 return _terminal or None 1561 1562 return None
1563
1564 - def share_desktop(self, desktop=None, user=None, display=None, share_mode=0, **kwargs):
1565 """\ 1566 Share another already running desktop session. Desktop sharing can be run 1567 in two different modes: view-only and full-access mode. 1568 1569 @param desktop: desktop ID of a sharable desktop in format C{<user>@<display>} 1570 @type desktop: C{str} 1571 @param user: user name and display number can be given separately, here give the 1572 name of the user who wants to share a session with you 1573 @type user: C{str} 1574 @param display: user name and display number can be given separately, here give the 1575 number of the display that a user allows you to be shared with 1576 @type display: C{str} 1577 @param share_mode: desktop sharing mode, 0 stands for VIEW-ONLY, 1 for FULL-ACCESS mode 1578 @type share_mode: C{int} 1579 1580 @return: True if the session could be successfully shared 1581 @rtype: C{bool} 1582 1583 @raise X2GoDesktopSharingException: if C{username} and C{dislpay} do not relate to a 1584 sharable desktop session 1585 1586 """ 1587 if desktop: 1588 user = desktop.split('@')[0] 1589 display = desktop.split('@')[1] 1590 if not (user and display): 1591 raise x2go_exceptions.X2GoDesktopSharingException('Need user name and display number of shared desktop.') 1592 1593 cmd = '%sXSHAD%sXSHAD%s' % (share_mode, user, display) 1594 1595 kwargs['cmd'] = cmd 1596 kwargs['session_type'] = 'shared' 1597 1598 return self.start(**kwargs)
1599
1600 - def list_desktops(self, raw=False, maxwait=20):
1601 """\ 1602 List all desktop-like sessions of current user (or of users that have 1603 granted desktop sharing) on the connected server. 1604 1605 @param raw: if C{True}, the raw output of the server-side X2Go command 1606 C{x2golistdesktops} is returned. 1607 @type raw: C{bool} 1608 1609 @return: a list of X2Go desktops available for sharing 1610 @rtype: C{list} 1611 1612 @raise X2GoTimeOutException: on command execution timeouts, with the server-side C{x2golistdesktops} 1613 command this can sometimes happen. Make sure you ignore these time-outs and to try again 1614 1615 """ 1616 if raw: 1617 (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && x2golistdesktops") 1618 return stdout.read(), stderr.read() 1619 1620 else: 1621 1622 # this _success loop will catch errors in case the x2golistsessions output is corrupt 1623 # this should not be needed and is a workaround for the current X2Go server implementation 1624 1625 if self.low_latency: 1626 maxwait = maxwait * 2 1627 1628 timeout = gevent.Timeout(maxwait) 1629 timeout.start() 1630 try: 1631 (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && x2golistdesktops") 1632 _stdout_read = stdout.read() 1633 _listdesktops = _stdout_read.split('\n') 1634 except gevent.timeout.Timeout: 1635 # if we do not get a reply here after <maxwait> seconds we will raise a time out, we have to 1636 # make sure that we catch this at places where we want to ignore timeouts (e.g. in the 1637 # desktop list cache) 1638 raise x2go_exceptions.X2GoTimeOutException('x2golistdesktop command timed out') 1639 finally: 1640 timeout.cancel() 1641 1642 return _listdesktops
1643
1644 - def list_mounts(self, session_name, raw=False, maxwait=20):
1645 """\ 1646 List all mounts for a given session of the current user on the connected server. 1647 1648 @param session_name: name of a session to query a list of mounts for 1649 @type session_name: C{str} 1650 @param raw: if C{True}, the raw output of the server-side X2Go command 1651 C{x2golistmounts} is returned. 1652 @type raw: C{bool} 1653 @param maxwait: stop processing C{x2golistmounts} after C{<maxwait>} seconds 1654 @type maxwait: C{int} 1655 1656 @return: a list of client-side mounts for X2Go session C{<session_name>} on the server 1657 @rtype: C{list} 1658 1659 @raise X2GoTimeOutException: on command execution timeouts, queries with the server-side 1660 C{x2golistmounts} query should normally be processed quickly, a time-out may hint that the 1661 control session has lost its connection to the X2Go server 1662 1663 """ 1664 if raw: 1665 (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && x2golistmounts %s" % session_name) 1666 return stdout.read(), stderr.read() 1667 1668 else: 1669 1670 if self.low_latency: 1671 maxwait = maxwait * 2 1672 1673 # this _success loop will catch errors in case the x2golistmounts output is corrupt 1674 1675 timeout = gevent.Timeout(maxwait) 1676 timeout.start() 1677 try: 1678 (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && x2golistmounts %s" % session_name) 1679 _stdout_read = stdout.read() 1680 _listmounts = {session_name: [ line for line in _stdout_read.split('\n') if line ] } 1681 except gevent.timeout.Timeout: 1682 # if we do not get a reply here after <maxwait> seconds we will raise a time out, we have to 1683 # make sure that we catch this at places where we want to ignore timeouts 1684 raise x2go_exceptions.X2GoTimeOutException('x2golistmounts command timed out') 1685 finally: 1686 timeout.cancel() 1687 1688 return _listmounts
1689
1690 - def list_sessions(self, raw=False):
1691 """\ 1692 List all sessions of current user on the connected server. 1693 1694 @param raw: if C{True}, the raw output of the server-side X2Go command 1695 C{x2golistsessions} is returned. 1696 @type raw: C{bool} 1697 1698 @return: normally an instance of a C{X2GoServerSessionList*} backend is returned. However, 1699 if the raw argument is set, the plain text output of the server-side C{x2golistsessions} 1700 command is returned 1701 @rtype: C{X2GoServerSessionList} instance or str 1702 1703 @raise X2GoControlSessionException: on command execution timeouts, if this happens the control session will 1704 be interpreted as disconnected due to connection loss 1705 """ 1706 if raw: 1707 if 'X2GO_LIST_SHADOWSESSIONS' in self._x2go_server_features: 1708 (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && { x2golistsessions; x2golistshadowsessions; }") 1709 else: 1710 (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && x2golistsessions") 1711 return stdout.read(), stderr.read() 1712 1713 else: 1714 1715 # this _success loop will catch errors in case the x2golistsessions output is corrupt 1716 # this should not be needed and is a workaround for the current X2Go server implementation 1717 _listsessions = {} 1718 _success = False 1719 _count = 0 1720 _maxwait = 20 1721 1722 # we will try this 20 times before giving up... we might simply catch the x2golistsessions 1723 # output in the middle of creating a session in the database... 1724 while not _success and _count < _maxwait: 1725 _count += 1 1726 try: 1727 if 'X2GO_LIST_SHADOWSESSIONS' in self._x2go_server_features: 1728 (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && { x2golistsessions; x2golistshadowsessions; }") 1729 else: 1730 (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && x2golistsessions") 1731 _stdout_read = stdout.read() 1732 _listsessions = self._list_backend(_stdout_read, info_backend=self._info_backend).sessions 1733 _success = True 1734 except KeyError: 1735 gevent.sleep(1) 1736 except IndexError: 1737 gevent.sleep(1) 1738 except ValueError: 1739 gevent.sleep(1) 1740 1741 if _count >= _maxwait: 1742 self.session_died = True 1743 self.disconnect() 1744 raise x2go_exceptions.X2GoControlSessionException('x2golistsessions command failed after we have tried 20 times') 1745 1746 # update internal variables when list_sessions() is called 1747 if _success and not self.session_died: 1748 for _session_name, _terminal in self.associated_terminals.items(): 1749 if _session_name in _listsessions.keys(): 1750 # update the whole session_info object within the terminal session 1751 if hasattr(self.associated_terminals[_session_name], 'session_info') and not self.associated_terminals[_session_name].is_session_info_protected(): 1752 self.associated_terminals[_session_name].session_info.update(_listsessions[_session_name]) 1753 else: 1754 self.associated_terminals[_session_name].__del__() 1755 try: del self.associated_terminals[_session_name] 1756 except KeyError: pass 1757 self.terminated_terminals.append(_session_name) 1758 if _terminal.is_suspended(): 1759 self.associated_terminals[_session_name].__del__() 1760 try: del self.associated_terminals[_session_name] 1761 except KeyError: pass 1762 1763 return _listsessions
1764
1765 - def clean_sessions(self, destroy_terminals=True, published_applications=False):
1766 """\ 1767 Find X2Go terminals that have previously been started by the 1768 connected user on the remote X2Go server and terminate them. 1769 1770 @param destroy_terminals: destroy the terminal session instances after cleanup 1771 @type destroy_terminals: C{bool} 1772 @param published_applications: also clean up published applications providing sessions 1773 @type published_applications: C{bool} 1774 1775 """ 1776 session_list = self.list_sessions() 1777 if published_applications: 1778 session_names = session_list.keys() 1779 else: 1780 session_names = [ _sn for _sn in session_list.keys() if not session_list[_sn].is_published_applications_provider() ] 1781 for session_name in session_names: 1782 if self.associated_terminals.has_key(session_name): 1783 self.associated_terminals[session_name].terminate() 1784 if destroy_terminals: 1785 if self.associated_terminals[session_name] is not None: 1786 self.associated_terminals[session_name].__del__() 1787 try: del self.associated_terminals[session_name] 1788 except KeyError: pass 1789 else: 1790 self.terminate(session_name=session_name)
1791
1792 - def is_connected(self):
1793 """\ 1794 Returns C{True} if this control session is connected to the remote server (that 1795 is: if it has a valid Paramiko/SSH transport object). 1796 1797 @return: X2Go session connected? 1798 @rtype: C{bool} 1799 1800 """ 1801 return self.get_transport() is not None and self.get_transport().is_authenticated()
1802
1803 - def is_running(self, session_name):
1804 """\ 1805 Returns C{True} if the given X2Go session is in running state, 1806 C{False} else. 1807 1808 @param session_name: X2Go name of the session to be queried 1809 @type session_name: C{str} 1810 1811 @return: X2Go session running? If C{<session_name>} is not listable by the L{list_sessions()} method then C{None} is returned 1812 @rtype: C{bool} or C{None} 1813 1814 """ 1815 session_infos = self.list_sessions() 1816 if session_name in session_infos.keys(): 1817 return session_infos[session_name].is_running() 1818 return None
1819
1820 - def is_suspended(self, session_name):
1821 """\ 1822 Returns C{True} if the given X2Go session is in suspended state, 1823 C{False} else. 1824 1825 @return: X2Go session suspended? If C{<session_name>} is not listable by the L{list_sessions()} method then C{None} is returned 1826 @rtype: C{bool} or C{None} 1827 1828 """ 1829 session_infos = self.list_sessions() 1830 if session_name in session_infos.keys(): 1831 return session_infos[session_name].is_suspended() 1832 return None
1833
1834 - def has_terminated(self, session_name):
1835 """\ 1836 Returns C{True} if the X2Go session with name C{<session_name>} has been seen 1837 by this control session and--in the meantime--has been terminated. 1838 1839 If C{<session_name>} has not been seen, yet, the method will return C{None}. 1840 1841 @return: X2Go session has terminated? 1842 @rtype: C{bool} or C{None} 1843 1844 """ 1845 session_infos = self.list_sessions() 1846 if session_name in self.terminated_terminals: 1847 return True 1848 if session_name not in session_infos.keys() and session_name in self.associated_terminals.keys(): 1849 # do a post-mortem tidy up 1850 self.terminate(session_name) 1851 return True 1852 if self.is_suspended(session_name) or self.is_running(session_name): 1853 return False 1854 1855 return None
1856
1857 - def suspend(self, session_name):
1858 """\ 1859 Suspend X2Go session with name C{<session_name>} on the connected 1860 server. 1861 1862 @param session_name: X2Go name of the session to be suspended 1863 @type session_name: C{str} 1864 1865 @return: C{True} if the session could be successfully suspended 1866 @rtype: C{bool} 1867 1868 """ 1869 _ret = False 1870 _session_names = [ t.get_session_name() for t in self.associated_terminals.values() ] 1871 if session_name in _session_names: 1872 1873 self.logger('suspending associated terminal session: %s' % session_name, loglevel=log.loglevel_DEBUG) 1874 (stdin, stdout, stderr) = self._x2go_exec_command("x2gosuspend-session %s" % session_name, loglevel=log.loglevel_DEBUG) 1875 stdout.read() 1876 stderr.read() 1877 if self.associated_terminals.has_key(session_name): 1878 if self.associated_terminals[session_name] is not None: 1879 self.associated_terminals[session_name].__del__() 1880 try: del self.associated_terminals[session_name] 1881 except KeyError: pass 1882 _ret = True 1883 1884 else: 1885 1886 self.logger('suspending non-associated terminal session: %s' % session_name, loglevel=log.loglevel_DEBUG) 1887 (stdin, stdout, stderr) = self._x2go_exec_command("x2gosuspend-session %s" % session_name, loglevel=log.loglevel_DEBUG) 1888 stdout.read() 1889 stderr.read() 1890 _ret = True 1891 1892 return _ret
1893
1894 - def terminate(self, session_name, destroy_terminals=True):
1895 """\ 1896 Terminate X2Go session with name C{<session_name>} on the connected 1897 server. 1898 1899 @param session_name: X2Go name of the session to be terminated 1900 @type session_name: C{str} 1901 1902 @return: C{True} if the session could be successfully terminated 1903 @rtype: C{bool} 1904 1905 """ 1906 1907 _ret = False 1908 if session_name in self.associated_terminals.keys(): 1909 1910 self.logger('terminating associated session: %s' % session_name, loglevel=log.loglevel_DEBUG) 1911 (stdin, stdout, stderr) = self._x2go_exec_command("x2goterminate-session %s" % session_name, loglevel=log.loglevel_DEBUG) 1912 stdout.read() 1913 stderr.read() 1914 1915 if destroy_terminals: 1916 if self.associated_terminals[session_name] is not None: 1917 self.associated_terminals[session_name].__del__() 1918 try: del self.associated_terminals[session_name] 1919 except KeyError: pass 1920 1921 self.terminated_terminals.append(session_name) 1922 _ret = True 1923 1924 else: 1925 1926 self.logger('terminating non-associated session: %s' % session_name, loglevel=log.loglevel_DEBUG) 1927 (stdin, stdout, stderr) = self._x2go_exec_command("x2goterminate-session %s" % session_name, loglevel=log.loglevel_DEBUG) 1928 stdout.read() 1929 stderr.read() 1930 _ret = True 1931 1932 return _ret
1933