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

Source Code for Module x2go.backends.terminal.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  X2GoTerminalSession class - core functions for handling your individual X2Go sessions. 
  22   
  23  This backend handles X2Go server implementations that respond with session infos 
  24  via server-side PLAIN text output. 
  25   
  26  """ 
  27  __NAME__ = 'x2goterminalsession-pylib' 
  28   
  29  # modules 
  30  import os 
  31  import types 
  32  import gevent 
  33  import cStringIO 
  34  import copy 
  35  import shutil 
  36  import threading 
  37   
  38  # Python X2Go modules 
  39  import x2go.rforward as rforward 
  40  import x2go.sftpserver as sftpserver 
  41  import x2go.printqueue as printqueue 
  42  import x2go.mimebox as mimebox 
  43  import x2go.telekinesis as telekinesis 
  44  import x2go.log as log 
  45  import x2go.defaults as defaults 
  46  import x2go.utils as utils 
  47  import x2go.x2go_exceptions as x2go_exceptions 
  48   
  49  # we hide the default values from epydoc (that's why we transform them to _UNDERSCORE variables) 
  50  from x2go.defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS 
  51  from x2go.defaults import LOCAL_HOME as _LOCAL_HOME 
  52  from x2go.defaults import CURRENT_LOCAL_USER as _CURRENT_LOCAL_USER 
  53  from x2go.defaults import X2GO_CLIENT_ROOTDIR as _X2GO_CLIENT_ROOTDIR 
  54  from x2go.defaults import X2GO_SESSIONS_ROOTDIR as _X2GO_SESSIONS_ROOTDIR 
  55  from x2go.defaults import X2GO_GENERIC_APPLICATIONS as _X2GO_GENERIC_APPLICATIONS 
  56  from x2go.defaults import X2GO_DESKTOPSESSIONS as _X2GO_DESKTOPSESSIONS 
  57   
  58  from x2go.defaults import BACKENDS as _BACKENDS 
  59   
  60  _local_color_depth = utils.local_color_depth() 
  61   
62 -def _rewrite_cmd(cmd, params=None):
63 """\ 64 Mechansim that rewrites X2Go server commands into something that gets understood by 65 the server-side script C{x2goruncommand}. 66 67 @param cmd: the current command for execution (as found in the session profile parameter C{cmd}) 68 @type cmd: C{str} 69 @param params: an session paramter object 70 @type params: L{X2GoSessionParams} 71 72 @return: the rewritten command for server-side execution 73 @rtype: C{str} 74 75 """ 76 # start with an empty string 77 cmd = cmd or '' 78 79 # find window manager commands 80 if cmd in _X2GO_DESKTOPSESSIONS.keys(): 81 cmd = _X2GO_DESKTOPSESSIONS[cmd] 82 83 if (cmd == 'RDP') and (type(params) == X2GoSessionParams): 84 _depth = params.depth 85 if int(_depth) == 17: 86 _depth = 16 87 if params.geometry == 'fullscreen': 88 cmd = 'rdesktop -f -N %s %s -a %s' % (params.rdp_options, params.rdp_server, _depth) 89 else: 90 cmd = 'rdesktop -g %s -N %s %s -a %s' % (params.geometry, params.rdp_options, params.rdp_server, _depth) 91 92 # place quot marks around cmd if not empty string 93 if cmd: 94 cmd = '"%s"' % cmd 95 96 if ((type(params) == X2GoSessionParams) and params.published_applications and cmd == ''): 97 cmd = 'PUBLISHED' 98 99 return cmd
100 101
102 -def _rewrite_blanks(cmd):
103 """\ 104 In command strings X2Go server scripts expect blanks being rewritten to ,,X2GO_SPACE_CHAR''. 105 106 @param cmd: command that has to be rewritten for passing to the server 107 @type cmd: C{str} 108 109 @return: the command with blanks rewritten to ,,X2GO_SPACE_CHAR'' 110 @rtype: C{str} 111 112 """ 113 # X2Go run command replace X2GO_SPACE_CHAR string with blanks 114 if cmd: 115 cmd = cmd.replace(" ", "X2GO_SPACE_CHAR") 116 return cmd
117 118
119 -class X2GoSessionParams(object):
120 """\ 121 The L{X2GoSessionParams} class is used to store all parameters that 122 C{X2GoTerminalSession} backend objects are constructed with. 123 124 """
125 - def rewrite_session_type(self):
126 """\ 127 Rewrite the X2Go session type, so that the X2Go server 128 can understand it (C{desktop} -> C{D}, etc.). 129 130 Also if the object's C{command} property is a known window 131 manager, the session type will be set to 'D' 132 (i.e. desktop). 133 134 @return: 'D' if session should probably a desktop session, 135 'R' for rootless sessions, 'P' for sessions providing published 136 applications, and 'S' for desktop sharing sessions 137 @rtype: C{str} 138 139 """ 140 cmd = self.cmd 141 published = self.published_applications 142 143 if published and self.cmd in ('', 'PUBLISHED'): 144 self.session_type = 'P' 145 self.cmd = 'PUBLISHED' 146 else: 147 if cmd == 'RDP' or cmd.startswith('rdesktop') or cmd.startswith('xfreedrp'): 148 if self.geometry == 'fullscreen': self.session_type = 'D' 149 else: self.session_type = 'R' 150 elif cmd == 'XDMCP': 151 self.session_type = 'D' 152 elif cmd in _X2GO_DESKTOPSESSIONS.keys(): 153 self.session_type = 'D' 154 elif os.path.basename(cmd) in _X2GO_DESKTOPSESSIONS.values(): 155 self.session_type = 'D' 156 157 if self.session_type in ("D", "desktop"): 158 self.session_type = 'D' 159 elif self.session_type in ("S", "shared", "shadow"): 160 self.session_type = 'S' 161 elif self.session_type in ("R", "rootless", "application"): 162 self.session_type = 'R' 163 elif self.session_type in ("P", "published", "published_applications"): 164 self.session_type = 'P' 165 166 return self.session_type
167
168 - def update(self, **properties_to_be_updated):
169 """\ 170 Update all properties in the object L{X2GoSessionParams} object from 171 the passed on dictionary. 172 173 @param properties_to_be_updated: a dictionary with L{X2GoSessionParams} 174 property names as keys und their values to be update in 175 L{X2GoSessionParams} object. 176 @type properties_to_be_updated: C{dict} 177 178 """ 179 for key in properties_to_be_updated.keys(): 180 setattr(self, key, properties_to_be_updated[key] or '') 181 self.rewrite_session_type()
182 183
184 -class X2GoTerminalSession(object):
185 """\ 186 Class for managing X2Go terminal sessions on a remote X2Go server via Paramiko/SSH. 187 188 With the L{x2go.backends.terminal.plain.X2GoTerminalSession} class you can start new X2Go sessions, resume suspended 189 sessions or suspend resp. terminate currently running sessions on a 190 connected X2Go server. 191 192 An L{x2go.backends.terminal.plain.X2GoTerminalSession} object uses two main data structure classes: 193 194 - L{X2GoSessionParams}: stores all parameters that have been passed to the 195 constructor method. 196 197 - C{X2GoServerSessionInfo*} backend class: when starting or resuming a session, an object of this class 198 will be used to store all information retrieved from the X2Go server. 199 200 The terminal session instance works closely together (i.e. depends on) a connected control 201 session instance (e.g. L{x2go.backends.control.plain.X2GoControlSession}). You never should use either of them as a standalone 202 instance. Both, control session and terminal session(s) get managed/controlled via L{X2GoSession} instances. 203 204 """
205 - def __init__(self, control_session, session_info=None, 206 geometry="800x600", depth=_local_color_depth, link="adsl", pack="16m-jpeg-9", dpi='', 207 cache_type="unix-kde", 208 kbtype='null/null', kblayout='null', kbvariant='null', 209 clipboard='both', 210 session_type="application", snd_system='pulse', snd_port=4713, cmd=None, 211 published_applications=False, 212 set_session_title=False, session_title="", applications=[], 213 rdp_server=None, rdp_options=None, 214 xdmcp_server=None, 215 convert_encoding=False, server_encoding='UTF-8', client_encoding='UTF-8', 216 rootdir=None, 217 profile_name='UNKNOWN', profile_id=utils._genSessionProfileId(), 218 print_action=None, print_action_args={}, 219 info_backend=_BACKENDS['X2GoServerSessionInfo']['default'], 220 list_backend=_BACKENDS['X2GoServerSessionList']['default'], 221 proxy_backend=_BACKENDS['X2GoProxy']['default'], proxy_options={}, 222 printing_backend=_BACKENDS['X2GoClientPrinting']['default'], 223 client_rootdir=os.path.join(_LOCAL_HOME, _X2GO_CLIENT_ROOTDIR), 224 sessions_rootdir=os.path.join(_LOCAL_HOME, _X2GO_SESSIONS_ROOTDIR), 225 session_instance=None, 226 logger=None, loglevel=log.loglevel_DEFAULT):
227 """\ 228 Initialize an X2Go session. With the L{x2go.backends.terminal.plain.X2GoTerminalSession} class you can start 229 new X2Go sessions, resume suspended sessions or suspend resp. terminate 230 currently running sessions on a connected X2Go server. 231 232 @param geometry: screen geometry of the X2Go session. Can be either C{<width>x<height>}, 233 C{maximize} or C{fullscreen} 234 @type geometry: C{str} 235 @param depth: color depth in bits (common values: C{16}, C{24}) 236 @type depth: C{int} 237 @param link: network link quality (either one of C{modem}, C{isdn}, C{adsl}, C{wan} or C{lan}) 238 @type link: C{str} 239 @param pack: compression method for NX based session proxying 240 @type pack: C{str} 241 @param dpi: dots-per-inch value for the session screen (has an impact on the font size on screen) 242 @type dpi: C{str} 243 @param cache_type: a dummy parameter that is passed to the L{x2go.backends.proxy.base.X2GoProxy}. In NX Proxy 244 (class C{X2GoProxyNX3}) this originally is the session name. With X2Go it 245 defines the name of the NX cache directory. Best is to leave it untouched. 246 @type cache_type: C{str} 247 @param kbtype: keyboard type, e.g. C{pc105/us} (default), C{pc105/de}, ... 248 @type kbtype: C{str} 249 @param kblayout: keyboard layout, e.g. C{us} (default), C{de}, C{fr}, ... 250 @type kblayout: C{str} 251 @param kbvariant: keyboard variant, e.g. C{nodeadkeys} (for C{de} layout), C{intl} (for C{us} layout), etc. 252 @type kbvariant: C{str} 253 @param clipboard: clipboard mode (C{both}: bidirectional copy+paste, C{server}: copy+paste from server to 254 client, C{client}: copy+paste from client to server, C{none}: disable clipboard completely 255 @type clipboard: C{str} 256 @param session_type: either C{desktop}, C{application} (rootless session) or C{shared} 257 @type session_type: C{str} 258 @param snd_system: sound system to be used on server (C{none}, C{pulse} (default), 259 C{arts} (obsolete) or C{esd}) 260 @type snd_system: C{str} 261 @param snd_port: local sound port for network capable audio system 262 @type snd_port: C{int} 263 @param cmd: command to be run on X2Go server after session start (only used 264 when L{x2go.backends.terminal.plain.X2GoTerminalSession.start()} is called, ignored on resume, suspend etc. 265 @type cmd: C{str} 266 @param published_applications: session is published applications provider 267 @type published_applications: C{bool} 268 @param set_session_title: modify the session title (i.e. the Window title) of desktop or shared desktop sessions 269 @type set_session_title: C{bool} 270 @param session_title: session title for this (desktop or shared desktop) session 271 @type session_title: C{str} 272 @param applications: applications available for rootless application execution 273 @type applications: C{list} 274 @param rdp_server: host name of server-side RDP server 275 @type rdp_server: C{str} 276 @param rdp_options: options for the C{rdesktop} command executed on the X2Go server (RDP proxy mode of X2Go) 277 @type rdp_options: C{str} 278 @param xdmcp_server: XDMCP server to connect to 279 @type xdmcp_server: C{str} 280 @param convert_encoding: convert file system encodings between server and client (for client-side shared folders) 281 @type convert_encoding: C{bool} 282 @param server_encoding: server-side file system / session encoding 283 @type server_encoding: C{str} 284 @param client_encoding: client-side file system encoding (if client-side is MS Windows, this parameter gets overwritten to WINDOWS-1252) 285 @type client_encoding: C{str} 286 @param rootdir: X2Go session directory, normally C{~/.x2go} 287 @type rootdir: C{str} 288 @param profile_name: the session profile name for this terminal session 289 @type profile_name: C{str} 290 @param profile_id: the session profile ID for this terminal session 291 @type profile_id: C{str} 292 @param print_action: either a print action short name (PDFVIEW, PDFSAVE, PRINT, PRINTCMD) or the 293 resp. C{X2GoPrintActionXXX} class (where XXX equals one of the given short names) 294 @type print_action: C{str} or C{class} 295 @param print_action_args: optional arguments for a given print_action (for further info refer to 296 L{X2GoPrintActionPDFVIEW}, L{X2GoPrintActionPDFSAVE}, L{X2GoPrintActionPRINT} and L{X2GoPrintActionPRINTCMD}) 297 @type print_action_args: dict 298 @param info_backend: backend for handling storage of server session information 299 @type info_backend: C{X2GoServerSessionInfo*} instance 300 @param list_backend: backend for handling storage of session list information 301 @type list_backend: C{X2GoServerSessionList*} instance 302 @param proxy_backend: backend for handling the X-proxy connections 303 @type proxy_backend: C{X2GoProxy*} instance 304 @param proxy_options: a set of very C{X2GoProxy} backend specific options; any option that is not known 305 to the C{X2GoProxy} backend will simply be ignored 306 @type proxy_options: C{dict} 307 @param client_rootdir: client base dir (default: ~/.x2goclient) 308 @type client_rootdir: C{str} 309 @param sessions_rootdir: sessions base dir (default: ~/.x2go) 310 @type sessions_rootdir: C{str} 311 @param session_instance: the L{X2GoSession} instance that is parent to this terminal session 312 @type session_instance: C{obj} 313 @param logger: you can pass an L{X2GoLogger} object to the 314 L{x2go.backends.terminal.plain.X2GoTerminalSession} constructor 315 @type logger: L{X2GoLogger} instance 316 @param loglevel: if no L{X2GoLogger} object has been supplied a new one will be 317 constructed with the given loglevel 318 @type loglevel: C{int} 319 320 """ 321 self.proxy = None 322 self.proxy_subprocess = None 323 self.proxy_options = proxy_options 324 325 self.telekinesis_client = None 326 327 self.active_threads = [] 328 self.reverse_tunnels = {} 329 330 self.print_queue = None 331 self.mimebox_queue = None 332 333 if logger is None: 334 self.logger = log.X2GoLogger(loglevel=loglevel) 335 else: 336 self.logger = copy.deepcopy(logger) 337 self.logger.tag = __NAME__ 338 339 self.control_session = control_session 340 self.reverse_tunnels = self.control_session.get_transport().reverse_tunnels 341 342 self.client_rootdir = client_rootdir 343 self.sessions_rootdir = sessions_rootdir 344 345 self.params = X2GoSessionParams() 346 347 self.params.geometry = str(geometry) 348 self.params.link = str(link) 349 self.params.pack = str(pack) 350 self.params.dpi = str(dpi) 351 self.params.cache_type = str(cache_type) 352 self.params.session_type = str(session_type) 353 self.params.kbtype = str(kbtype) 354 self.params.kblayout = str(kblayout) 355 self.params.kbvariant = str(kbvariant) 356 self.params.snd_system = str(snd_system) 357 self.params.cmd = str(cmd) 358 self.params.depth = str(depth) 359 self.params.clipboard = str(clipboard) 360 361 self.params.published_applications = published_applications 362 self.published_applications = published_applications 363 364 self.params.rdp_server = str(rdp_server) 365 self.params.rdp_options = str(rdp_options) 366 self.params.xdmcp_server = str(xdmcp_server) 367 368 self.params.convert_encoding = convert_encoding 369 self.params.client_encoding = str(client_encoding) 370 self.params.server_encoding = str(server_encoding) 371 372 self.params.rootdir = (type(rootdir) is types.StringType) and rootdir or self.sessions_rootdir 373 self.params.update() 374 375 self.profile_name = profile_name 376 self.set_session_title = set_session_title 377 self.session_title = session_title 378 self.session_window = None 379 self.proxy_backend = utils._get_backend_class(proxy_backend, "X2GoProxy") 380 381 self.snd_port = snd_port 382 self.print_action = print_action 383 self.print_action_args = print_action_args 384 self.printing_backend = utils._get_backend_class(printing_backend, "X2GoClientPrinting") 385 self.session_instance = session_instance 386 if self.session_instance: 387 self.client_instance = self.session_instance.client_instance 388 else: 389 self.client_instance = None 390 391 self._share_local_folder_busy = False 392 self._mk_sessions_rootdir(self.params.rootdir) 393 394 self.session_info = session_info 395 if self.session_info is not None: 396 if self.session_info.name: 397 self.session_info.local_container = os.path.join(self.params.rootdir, 'S-%s' % self.session_info.name) 398 else: 399 raise x2go_exceptions.X2GoTerminalSessionException('no valid session info availble') 400 else: 401 self.session_info = info_backend() 402 403 self._share_local_folder_lock = threading.Lock() 404 self._cleaned_up = False 405 406 self.telekinesis_subprocess = None
407
408 - def __del__(self):
409 """\ 410 Tidy up if terminal session gets destructed. 411 412 """ 413 self._x2go_tidy_up()
414
415 - def _x2go_tidy_up(self):
416 """\ 417 Tidy up this terminal session... 418 - shutdown all forwarding and reverse forwarding tunnels 419 - shutdown the print queue (if running) 420 - shutdown the MIME box queue (if running) 421 - clear the session info 422 423 """ 424 if self._share_local_folder_lock.locked(): 425 self._share_local_folder_lock.release() 426 self.release_telekinesis() 427 self.release_proxy() 428 self.session_window = None 429 self.update_session_window_file() 430 431 try: 432 433 if self.control_session.get_transport() is not None: 434 try: 435 for _tunnel in [ _tun[1] for _tun in self.reverse_tunnels[self.session_info.name].values() ]: 436 if _tunnel is not None: 437 _tunnel.__del__() 438 except KeyError: 439 pass 440 441 if self.print_queue is not None: 442 self.print_queue.__del__() 443 444 if self.mimebox_queue is not None: 445 self.mimebox_queue.__del__() 446 447 except AttributeError: 448 pass 449 450 self.session_info.clear()
451
452 - def _mk_sessions_rootdir(self, rootdir):
453 """\ 454 Create the server-side session root dir (normally ~/.x2go). 455 456 @param rootdir: server-side session root directory 457 @type rootdir: C{str} 458 459 """ 460 try: 461 os.makedirs(rootdir) 462 except OSError, e: 463 if e.errno == 17: 464 # file exists 465 pass 466 else: 467 raise OSError, e
468
469 - def _rm_session_dirtree(self):
470 """\ 471 Purge client-side session dir (session cache directory). 472 473 """ 474 if self.session_info.name: 475 shutil.rmtree('%s/S-%s' % (self.params.rootdir, self.session_info), ignore_errors=True)
476
477 - def _rm_desktop_dirtree(self):
478 """\ 479 Purge client-side session dir (C-<display> directory) 480 481 """ 482 if self.session_info.display: 483 shutil.rmtree('%s/S-%s' % (self.params.rootdir, self.session_info.display), ignore_errors=True)
484
485 - def get_session_name(self):
486 """\ 487 Retrieve the X2Go session's name from the session info object. 488 489 @return: the session name 490 @rtype: C{str} 491 492 """ 493 return self.session_info.name
494
495 - def get_session_info(self):
496 """\ 497 Retrieve the X2Go session's session info object. 498 499 @return: the session info object 500 @rtype: C{X2GoServerSessionInfo*} 501 502 """ 503 return self.session_info
504
505 - def get_session_cmd(self):
506 """\ 507 Retrieve the X2Go session's command as stored in the session parameter object. 508 509 @return: the session command 510 @rtype: C{str} 511 512 """ 513 return self.params.cmd
514
515 - def get_session_type(self):
516 """\ 517 Retrieve the X2Go session's session type as stored in the session parameter object. 518 519 @return: the session type 520 @rtype: C{str} 521 522 """ 523 return self.params.session_type
524
525 - def start_sound(self):
526 """\ 527 Initialize Paramiko/SSH reverse forwarding tunnel for X2Go sound. 528 529 Currently supported audio protocols: 530 531 - PulseAudio 532 - Esound (not tested very much) 533 534 @raise X2GoControlSessionException: if the control session of this terminal session is not connected 535 536 """ 537 _tunnel = None 538 if self.reverse_tunnels[self.session_info.name]['snd'][1] is None: 539 if self.params.snd_system == 'pulse': 540 self.logger('initializing PulseAudio sound support in X2Go session', loglevel=log.loglevel_INFO) 541 ### 542 ### PULSEAUDIO 543 ### 544 cookie_filepath = None 545 if os.path.exists(os.path.normpath('%s/.pulse-cookie' % _LOCAL_HOME)): 546 cookie_filepath = os.path.normpath('%s/.pulse-cookie' % _LOCAL_HOME) 547 elif os.path.exists(os.path.normpath('%s/.config/pulse/cookie' % _LOCAL_HOME)): 548 cookie_filepath = os.path.normpath('%s/.config/pulse/cookie' % _LOCAL_HOME) 549 if cookie_filepath is not None: 550 # setup pulse client config file on X2Go server 551 cmd_line = "echo 'default-server=127.0.0.1:%s'>%s/.pulse-client.conf;" % (self.session_info.snd_port, self.session_info.remote_container) + \ 552 "echo 'cookie-file=%s/.pulse-cookie'>>%s/.pulse-client.conf" % (self.session_info.remote_container, self.session_info.remote_container) 553 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) 554 555 self.control_session._x2go_sftp_put(local_path=cookie_filepath, remote_path='%s/.pulse-cookie' % self.session_info.remote_container) 556 557 # start reverse SSH tunnel for pulse stream 558 _tunnel = rforward.X2GoRevFwTunnel(server_port=self.session_info.snd_port, 559 remote_host='127.0.0.1', 560 remote_port=self.snd_port, 561 ssh_transport=self.control_session.get_transport(), 562 session_instance=self.session_instance, 563 logger=self.logger 564 ) 565 else: 566 if self.client_instance: 567 self.client_instance.HOOK_on_sound_tunnel_failed(profile_name=self.profile_name, session_name=self.session_info.name) 568 elif self.params.snd_system == 'arts': 569 ### 570 ### ARTSD AUDIO 571 ### 572 self.logger('the ArtsD sound server (as in KDE3) is obsolete and will not be supported by Python X2Go...', loglevel=log.loglevel_WARN) 573 574 elif self.params.snd_system == 'esd': 575 ### 576 ### ESD AUDIO 577 ### 578 579 self.logger('initializing ESD sound support in X2Go session', loglevel=log.loglevel_INFO) 580 self.control_session._x2go_sftp_put(local_path='%s/.esd_auth' % _LOCAL_HOME, remote_path='%s/.esd_auth' % self.control_session._x2go_remote_home) 581 582 # start reverse SSH tunnel for pulse stream 583 _tunnel = rforward.X2GoRevFwTunnel(server_port=self.session_info.snd_port, 584 remote_host='127.0.0.1', 585 remote_port=self.snd_port, 586 ssh_transport=self.control_session.get_transport(), 587 session_instance=self.session_instance, 588 logger=self.logger 589 ) 590 591 592 if _tunnel is not None: 593 self.reverse_tunnels[self.session_info.name]['snd'] = (self.session_info.snd_port, _tunnel) 594 _tunnel.start() 595 self.active_threads.append(_tunnel) 596 597 else: 598 # tunnel has already been started and might simply need a resume call 599 self.reverse_tunnels[self.session_info.name]['snd'][1].resume()
600
601 - def start_sshfs(self):
602 """\ 603 Initialize Paramiko/SSH reverse forwarding tunnel for X2Go folder sharing. 604 605 """ 606 if not self.control_session.is_sshfs_available(): 607 raise x2go_exceptions.X2GoUserException('Remote user %s is not allowed to share SSHFS resources with the server.' % self.session_info.username) 608 609 # start reverse SSH tunnel for sshfs (folder sharing, printing) 610 ssh_transport = self.control_session.get_transport() 611 if self.reverse_tunnels[self.session_info.name]['sshfs'][1] is None: 612 613 _tunnel = sftpserver.X2GoRevFwTunnelToSFTP(server_port=self.session_info.sshfs_port, 614 ssh_transport=ssh_transport, 615 auth_key=self.control_session._x2go_session_auth_rsakey, 616 session_instance=self.session_instance, 617 logger=self.logger 618 ) 619 620 if _tunnel is not None: 621 self.reverse_tunnels[self.session_info.name]['sshfs'] = (self.session_info.sshfs_port, _tunnel) 622 _tunnel.start() 623 self.active_threads.append(_tunnel) 624 while not _tunnel.ready: 625 gevent.sleep(.1) 626 627 else: 628 # tunnel has already been started and might simply need a resume call 629 self.reverse_tunnels[self.session_info.name]['sshfs'][1].resume()
630
631 - def _x2go_pause_rev_fw_tunnel(self, name):
632 """\ 633 Pause reverse SSH tunnel of name <name>. 634 635 @param name: tunnel name (either of C{sshfs}, C{snd}) 636 @type name: C{str} 637 638 """ 639 _tunnel = self.reverse_tunnels[self.session_info.name][name][1] 640 if _tunnel is not None: 641 _tunnel.pause()
642
643 - def stop_sound(self):
644 """\ 645 Shutdown (pause) Paramiko/SSH reverse forwarding tunnel for X2Go sound. 646 647 """ 648 self._x2go_pause_rev_fw_tunnel('snd')
649
650 - def stop_sshfs(self):
651 """\ 652 Shutdown (pause) Paramiko/SSH reverse forwarding tunnel for X2Go folder sharing. 653 654 """ 655 self._x2go_pause_rev_fw_tunnel('sshfs')
656
657 - def start_printing(self):
658 """\ 659 Initialize X2Go print spooling. 660 661 @raise X2GoUserException: if the X2Go printing feature is not available to this user 662 663 """ 664 if not self.control_session.is_sshfs_available(): 665 raise x2go_exceptions.X2GoUserException('Remote user %s is not allowed to use client-side printing.' % self.session_info.username) 666 667 spool_dir = os.path.join(self.session_info.local_container, 'spool') 668 if not os.path.exists(spool_dir): 669 os.makedirs(spool_dir) 670 self.share_local_folder(local_path=spool_dir, folder_type='spool') 671 self.print_queue = printqueue.X2GoPrintQueue(profile_name=self.profile_name, 672 session_name=self.session_info.name, 673 spool_dir=spool_dir, 674 print_action=self.print_action, 675 print_action_args=self.print_action_args, 676 client_instance=self.client_instance, 677 printing_backend=self.printing_backend, 678 logger=self.logger, 679 ) 680 self.print_queue.start() 681 self.active_threads.append(self.print_queue)
682
683 - def set_print_action(self, print_action, **kwargs):
684 """\ 685 Set a print action for the next incoming print jobs. 686 687 This method is a wrapper for L{X2GoPrintQueue}C{.set_print_action()}. 688 689 @param print_action: print action name or object (i.e. an instance of C{X2GoPrintAction*} classes) 690 @type print_action: C{str} or C{X2GoPrintAction*} 691 @param kwargs: print action specific parameters 692 @type kwargs: dict 693 694 """ 695 self.print_queue.set_print_action(print_action, logger=self.logger, **kwargs)
696
697 - def stop_printing(self):
698 """\ 699 Shutdown (pause) the X2Go Print Queue thread. 700 701 """ 702 if self.print_queue is not None: 703 self.print_queue.pause()
704
705 - def get_printing_spooldir(self):
706 """\ 707 Return the server-side printing spooldir path. 708 709 @return: the directory for remote print job spooling 710 @rtype: C{str} 711 712 """ 713 return '%s/%s' % (self.session_info.remote_container, 'spool')
714
715 - def start_mimebox(self, mimebox_extensions=[], mimebox_action=None):
716 """\ 717 Initialize the X2Go MIME box. Open/process incoming files from the server-side locally. 718 719 @param mimebox_extensions: file name extensions that are allowed for local opening/processing 720 @type mimebox_extensions: C{list} 721 @param mimebox_action: MIME box action given as name or object (i.e. an instance of C{X2GoMIMEboxAction*} classes). 722 @type mimebox_action: C{str} or C{obj} 723 724 @raise X2GoUserException: if the X2Go MIME box feature is not available to this user 725 726 """ 727 if not self.control_session.is_sshfs_available(): 728 raise x2go_exceptions.X2GoUserException('Remote user %s is not allowed to use the MIME box.' % self.session_info.username) 729 730 mimebox_dir = os.path.join(self.session_info.local_container, 'mimebox') 731 if not os.path.exists(mimebox_dir): 732 os.makedirs(mimebox_dir) 733 self.share_local_folder(local_path=mimebox_dir, folder_type='mimebox') 734 self.mimebox_queue = mimebox.X2GoMIMEboxQueue(profile_name=self.profile_name, 735 session_name=self.session_info.name, 736 mimebox_dir=mimebox_dir, 737 mimebox_extensions=mimebox_extensions, 738 mimebox_action=mimebox_action, 739 client_instance=self.client_instance, 740 logger=self.logger, 741 ) 742 self.mimebox_queue.start() 743 self.active_threads.append(self.mimebox_queue)
744
745 - def set_mimebox_action(self, mimebox_action, **kwargs):
746 """\ 747 Set a MIME box action for the next incoming MIME jobs. 748 749 This method is a wrapper for L{X2GoMIMEboxQueue}C{set_mimebox_action()}. 750 751 @param mimebox_action: MIME box action name or object (i.e. an instance of C{X2GoMIMEboxAction*} classes) 752 @type mimebox_action: C{str} or C{X2GoMIMEboxAction*} 753 @param kwargs: MIME box action specific parameters 754 @type kwargs: dict 755 756 """ 757 self.mimebox_queue.set_mimebox_action(mimebox_action, logger=self.logger, **kwargs)
758
759 - def stop_mimebox(self):
760 """\ 761 Shutdown (pause) the X2Go MIME box Queue thread. 762 763 """ 764 if self.mimebox_queue is not None: 765 self.mimebox_queue.pause()
766
767 - def get_mimebox_spooldir(self):
768 """\ 769 Return the server-side MIME box spooldir path. 770 771 @return: the directory where remote MIME box jobs are placed 772 @rtype: C{str} 773 774 """ 775 return '%s/%s' % (self.session_info.remote_container, 'mimebox')
776
777 - def start_telekinesis(self):
778 """\ 779 Initialize Telekinesis client for X2Go. 780 781 """ 782 if self.telekinesis_client is not None: 783 del self.telekinesis_client 784 self.telekinesis_client = None 785 if self.telekinesis_subprocess is not None: 786 self.telekinesis_subprocess = None 787 if self.session_info.tekictrl_port != -1 and self.session_info.tekidata_port != -1: 788 self.telekinesis_client = telekinesis.X2GoTelekinesisClient(session_info=self.session_info, 789 ssh_transport=self.control_session.get_transport(), 790 sessions_rootdir=self.sessions_rootdir, 791 session_instance=self.session_instance, 792 logger=self.logger) 793 if self.telekinesis_client.has_telekinesis_client(): 794 self.telekinesis_subprocess, telekinesis_ok = self.telekinesis_client.start_telekinesis() 795 else: 796 del self.telekinesis_client 797 self.telekinesis_client = None
798
799 - def is_session_info_protected(self):
800 """\ 801 Test if this terminal's session info object is write-protected. 802 803 @return: C{True}, if session info object is read-only, C{False} for read-write. 804 @rtype: C{bool} 805 806 """ 807 self.session_info.is_protected()
808
809 - def session_info_protect(self):
810 """\ 811 Protect this terminal session's info object against updates. 812 813 """ 814 self.session_info.protect()
815
816 - def session_info_unprotect(self):
817 """\ 818 Allow session info updates from within the list_sessions method of the control session. 819 820 """ 821 self.session_info.unprotect()
822
823 - def share_local_folder(self, local_path=None, folder_type='disk'):
824 """\ 825 Share a local folder with the X2Go session. 826 827 @param local_path: the full path to an existing folder on the local 828 file system 829 @type local_path: C{str} 830 @param folder_type: one of 'disk' (a folder on your local hard drive), 'rm' (removeable device), 831 'cdrom' (CD/DVD Rom) or 'spool' (for X2Go print spooling) 832 @type folder_type: C{str} 833 834 @return: returns C{True} if the local folder has been successfully mounted within the X2Go server session 835 @rtype: C{bool} 836 837 @raise X2GoUserException: if local folder sharing is not available to this user 838 @raise Exception: any other exception occuring on the way is passed through by this method 839 840 """ 841 if not self.control_session.is_sshfs_available(): 842 raise x2go_exceptions.X2GoUserException('Remote user %s is not allowed to share local folders with the server.' % self.session_info.username) 843 844 if local_path is None: 845 self.logger('no folder name given...', log.loglevel_WARN) 846 return False 847 848 if type(local_path) not in (types.StringType, types.UnicodeType): 849 self.logger('folder name needs to be of type StringType...', log.loglevel_WARN) 850 return False 851 852 if not os.path.exists(local_path): 853 self.logger('local folder does not exist: %s' % local_path, log.loglevel_WARN) 854 return False 855 856 local_path = os.path.normpath(local_path) 857 self.logger('sharing local folder: %s' % local_path, log.loglevel_INFO) 858 859 _auth_rsakey = self.control_session._x2go_session_auth_rsakey 860 _host_rsakey = defaults.RSAHostKey 861 862 _tmp_io_object = cStringIO.StringIO() 863 _auth_rsakey.write_private_key(_tmp_io_object) 864 _tmp_io_object.write('----BEGIN RSA IDENTITY----') 865 _tmp_io_object.write('%s %s' % (_host_rsakey.get_name(),_host_rsakey.get_base64(),)) 866 867 # _x2go_key_fname must be a UniX path 868 _x2go_key_fname = '%s/%s/%s' % (os.path.dirname(self.session_info.remote_container), 'ssh', 'key.z%s' % self.session_info.agent_pid) 869 _x2go_key_bundle = _tmp_io_object.getvalue() 870 871 # if there is another call to this method currently being processed, wait for that one to finish 872 self._share_local_folder_lock.acquire() 873 874 try: 875 self.control_session._x2go_sftp_write(_x2go_key_fname, _x2go_key_bundle) 876 877 _convert_encoding = self.params.convert_encoding 878 _client_encoding = self.params.client_encoding 879 _server_encoding = self.params.server_encoding 880 881 if _X2GOCLIENT_OS == 'Windows': 882 if local_path.startswith('\\\\'): 883 # we are on a UNC path 884 if 'X2GO_MOUNT_UNCPATHS' in self.control_session.get_server_features(): 885 local_path = local_path.repalce('\\\\', '/uncpath/') 886 else: 887 local_path = local_path.repalce('\\\\', '/windrive/') 888 local_path = local_path.replace('\\', '/') 889 else: 890 local_path = local_path.replace('\\', '/') 891 local_path = local_path.replace(':', '') 892 local_path = '/windrive/%s' % local_path 893 _convert_encoding = True 894 _client_encoding = 'WINDOWS-1252' 895 896 if _convert_encoding: 897 export_iconv_settings = 'export X2GO_ICONV=modules=iconv,from_code=%s,to_code=%s && ' % (_client_encoding, _server_encoding) 898 else: 899 export_iconv_settings = '' 900 901 if folder_type == 'disk': 902 903 cmd_line = [ '%sexport HOSTNAME &&' % export_iconv_settings, 904 'x2gomountdirs', 905 'dir', 906 str(self.session_info.name), 907 '\'%s\'' % _CURRENT_LOCAL_USER, 908 _x2go_key_fname, 909 '%s__REVERSESSH_PORT__%s; ' % (local_path, self.session_info.sshfs_port), 910 'rm -f %s %s.ident' % (_x2go_key_fname, _x2go_key_fname), 911 ] 912 913 elif folder_type == 'spool': 914 915 cmd_line = [ '%sexport HOSTNAME &&' % export_iconv_settings, 916 'x2gomountdirs', 917 'dir', 918 str(self.session_info.name), 919 '\'%s\'' % _CURRENT_LOCAL_USER, 920 _x2go_key_fname, 921 '%s__PRINT_SPOOL___REVERSESSH_PORT__%s; ' % (local_path, self.session_info.sshfs_port), 922 'rm -f %s %s.ident' % (_x2go_key_fname, _x2go_key_fname), 923 ] 924 925 elif folder_type == 'mimebox': 926 927 cmd_line = [ '%sexport HOSTNAME &&' % export_iconv_settings, 928 'x2gomountdirs', 929 'dir', 930 str(self.session_info.name), 931 '\'%s\'' % _CURRENT_LOCAL_USER, 932 _x2go_key_fname, 933 '%s__MIMEBOX_SPOOL___REVERSESSH_PORT__%s; ' % (local_path, self.session_info.sshfs_port), 934 'rm -f %s %s.ident' % (_x2go_key_fname, _x2go_key_fname), 935 ] 936 937 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) 938 _stdout = stdout.read().split('\n') 939 if _stdout[0]: 940 self.logger('x2gomountdirs stdout is: %s' % _stdout, log.loglevel_NOTICE) 941 _stderr = stderr.read().split('\n') 942 if _stderr[0]: 943 self.logger('x2gomountdirs stderr is: %s' % _stderr, log.loglevel_WARN) 944 945 except: 946 self._share_local_folder_lock.release() 947 raise 948 self._share_local_folder_lock.release() 949 950 if len(_stdout) >= 6 and _stdout[5].endswith('ok'): 951 return True 952 return False
953
954 - def unshare_all_local_folders(self):
955 """\ 956 Unshare all local folders mount in the X2Go session. 957 958 @return: returns C{True} if all local folders could be successfully unmounted from the X2Go server session 959 @rtype: C{bool} 960 961 """ 962 self.logger('unsharing all local folders from session %s' % self.session_info, log.loglevel_INFO) 963 964 cmd_line = [ 'export HOSTNAME &&', 965 'x2goumount-session', 966 self.session_info.name, 967 ] 968 969 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) 970 if not stderr.read(): 971 self.logger('x2goumount-session (all mounts) for session %s has been successful' % self.session_info, log.loglevel_NOTICE) 972 return True 973 else: 974 self.logger('x2goumount-session (all mounts) for session %s failed' % self.session_info, log.loglevel_ERROR) 975 return False
976
977 - def unshare_local_folder(self, local_path):
978 """\ 979 Unshare local folder given as <local_path> from X2Go session. 980 981 @return: returns C{True} if the local folder <local_path> could be successfully unmounted from the X2Go server session 982 @rtype: C{bool} 983 984 """ 985 self.logger('unsharing local folder from session %s' % self.session_info, log.loglevel_INFO) 986 987 cmd_line = [ 'export HOSTNAME &&', 988 'x2goumount-session', 989 self.session_info.name, 990 "'%s'" % local_path, 991 ] 992 993 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) 994 if not stderr.read(): 995 self.logger('x2goumount-session (%s) for session %s has been successful' % (local_path, self.session_info, ), log.loglevel_NOTICE) 996 return True 997 else: 998 self.logger('x2goumount-session (%s) for session %s failed' % (local_path, self.session_info, ), log.loglevel_ERROR) 999 return False
1000
1001 - def color_depth(self):
1002 """\ 1003 Retrieve the session's color depth. 1004 1005 @return: the session's color depth 1006 @rtype: C{int} 1007 1008 """ 1009 return self.params.depth
1010
1011 - def auto_session_window_title(self, dont_set=False):
1012 """\ 1013 Automatically generate an appropriate human-readable session window title. 1014 1015 The session window title will be provider in the C{session_title} property of 1016 this method. 1017 1018 @param dont_set: generate the session window title, but do not actually set it 1019 @type dont_set: C{bool} 1020 1021 """ 1022 _generic_title = 'X2GO-%s' % self.session_info.name 1023 1024 # no blanks at beginning or end, no blanks-only... 1025 self.session_title = self.session_title.strip() 1026 1027 if self.params.session_type == 'D': 1028 if self.set_session_title: 1029 1030 if not self.session_title: 1031 self.session_title = '%s for %s@%s' % (self.params.cmd, self.control_session.remote_username(), self.control_session.get_hostname()) 1032 1033 else: 1034 # session title fallback... (like X2Go server does it...) 1035 self.session_title = _generic_title 1036 1037 elif self.params.session_type == 'S': 1038 if self.set_session_title: 1039 1040 shared_user = _generic_title.split('XSHAD')[1] 1041 shared_display = _generic_title.split('XSHAD')[2].replace('PP', ':').split("_")[0] 1042 1043 self.session_title = 'Desktop %s@%s shared with %s@%s' % (shared_user, shared_display, self.control_session.remote_username(), self.control_session.get_hostname()) 1044 1045 else: 1046 # session title fallback... (like X2Go server does it...) 1047 self.session_title = _generic_title 1048 1049 else: 1050 # do nothing for rootless sessions 1051 self.session_title = _generic_title 1052 1053 if self.session_title != _generic_title and not dont_set: 1054 self.set_session_window_title(title=self.session_title)
1055
1056 - def find_session_window(self, timeout=60):
1057 """\ 1058 Try for <timeout> seconds to find the X2Go session window of this 1059 terminal session. 1060 1061 A background thread will get spawned for this operation. 1062 1063 @param timeout: try for <timeout> seconds to find the session window 1064 @type timeout: C{int} 1065 1066 """ 1067 gevent.spawn(self._find_session_window, timeout=timeout)
1068
1069 - def _find_session_window(self, timeout=0):
1070 """\ 1071 Try for <timeout> seconds to find the X2Go session window of this 1072 terminal session. 1073 1074 @param timeout: try for <timeout> seconds to find the session window 1075 @type timeout: C{int} 1076 1077 """ 1078 self.session_window = None 1079 1080 # search for the window of our focus, do this in a loop till the window as been found 1081 # or timeout forces us to give up... 1082 timeout += 1 1083 while timeout: 1084 1085 timeout -= 1 1086 1087 window = utils.find_session_window(self.session_info.name) 1088 1089 if window is not None: 1090 if _X2GOCLIENT_OS == "Windows": 1091 self.logger('Session window handle for session %s is: %s' % (self.session_info.name, window), loglevel=log.loglevel_DEBUG) 1092 else: 1093 self.logger('Session window ID for session %s is: %s' % (self.session_info.name, window.id), loglevel=log.loglevel_DEBUG) 1094 self.session_window = window 1095 1096 self.update_session_window_file() 1097 break 1098 1099 gevent.sleep(1)
1100
1101 - def update_session_window_file(self):
1102 """\ 1103 Create a file that contains information on the session window. 1104 . 1105 If the file already exists, its content gets update. 1106 1107 """ 1108 session_window_file = os.path.join(self.session_info.local_container, 'session.window') 1109 if self.session_window is not None: 1110 f = open(session_window_file,'w') 1111 if _X2GOCLIENT_OS != "Windows": 1112 _id = self.session_window.id 1113 else: 1114 _id = self.session_window 1115 f.write('ID:{window_id}\n'.format(window_id=_id)) 1116 f.close() 1117 self.logger('Updating session.window file %s: Window-ID->%s' % (session_window_file, _id), loglevel=log.loglevel_DEBUG) 1118 else: 1119 try: 1120 os.remove(session_window_file) 1121 except OSError,e: 1122 # this is no error in most cases... 1123 self.logger('The session window file %s is already gone (we failed to remove it with error: %s). In most cases this can be safely ignored.' % (session_window_file, str(e)), loglevel=log.loglevel_INFO)
1124
1125 - def set_session_window_title(self, title, timeout=60):
1126 """\ 1127 Modify the session window title. 1128 1129 A background thread will get spawned for this operation. 1130 1131 @param title: new title for the terminal session's session window 1132 @type title: C{str} 1133 @param timeout: try for <timeout> seconds to find the session window 1134 @type timeout: C{int} 1135 1136 """ 1137 gevent.spawn(self._set_session_window_title, title=title.strip(), timeout=timeout)
1138
1139 - def _set_session_window_title(self, title, timeout=0):
1140 """\ 1141 Modify the session window title. 1142 1143 @param title: new title for the terminal session's session window 1144 @type title: C{str} 1145 @param timeout: try for <timeout> seconds to find the session window 1146 @type timeout: C{int} 1147 1148 """ 1149 self.session_title = title 1150 1151 if not self.session_title: 1152 self.auto_session_title(dont_set=True) 1153 1154 timeout += 1 1155 while timeout: 1156 1157 timeout -= 1 1158 1159 if self.session_window is not None: 1160 self.logger('Setting session window title for session %s is: %s' % (self.session_info.name, self.session_title), loglevel=log.loglevel_DEBUG) 1161 utils.set_session_window_title(self.session_window, self.session_title) 1162 break 1163 1164 gevent.sleep(1)
1165
1166 - def raise_session_window(self, timeout=60):
1167 """\ 1168 Try for <timeout> seconds to raise the X2Go session window of this 1169 terminal session to the top and bring it to focus. 1170 1171 A background thread will get spawned for this operation. 1172 1173 @param timeout: try for <timeout> seconds to raise the session window 1174 @type timeout: C{int} 1175 1176 """ 1177 gevent.spawn(self._raise_session_window, timeout=timeout)
1178
1179 - def _raise_session_window(self, timeout=0):
1180 """ 1181 Try for <timeout> seconds to raise the X2Go session window of this 1182 terminal session to the top and bring it to focus. 1183 1184 @param timeout: try for <timeout> seconds to raise the session window 1185 @type timeout: C{int} 1186 1187 """ 1188 timeout += 1 1189 while timeout: 1190 1191 timeout -= 1 1192 1193 if self.session_window is not None: 1194 1195 utils.raise_session_window(self.session_window) 1196 break 1197 1198 gevent.sleep(1)
1199
1200 - def has_command(self, cmd):
1201 """\ 1202 ,,Guess'' if the command C{<cmd>} exists on the X2Go server and is executable. 1203 The expected result is not 100% safe, however, it comes with a high probability to 1204 be correct. 1205 1206 @param cmd: session command 1207 @type cmd: C{str} 1208 1209 @return: C{True} if this method reckons that the command is executable on the remote X2Go server 1210 @rtype: C{bool} 1211 1212 """ 1213 test_cmd = None; 1214 1215 cmd = cmd.strip('"').strip('"') 1216 if cmd.find('RDP') != -1: 1217 cmd = 'rdesktop' 1218 1219 if cmd in _X2GO_GENERIC_APPLICATIONS: 1220 return True 1221 if cmd in _X2GO_DESKTOPSESSIONS.keys(): 1222 return True 1223 elif 'XSHAD' in cmd: 1224 return True 1225 elif 'PUBLISHED' in cmd and 'X2GO_PUBLISHED_APPLICATIONS' in self.control_session.get_server_features(): 1226 return True 1227 elif cmd and cmd.startswith('/'): 1228 # check if full path is correct _and_ if application is in server path 1229 test_cmd = 'test -x %s && which %s && echo OK' % (cmd, os.path.basename(cmd.split()[0])) 1230 elif cmd and '/' not in cmd.split()[0]: 1231 # check if application is in server path only 1232 test_cmd = 'which %s && echo OK' % os.path.basename(cmd.split()[0]) 1233 1234 if test_cmd: 1235 (stdin, stdout, stderr) = self.control_session._x2go_exec_command([test_cmd]) 1236 _stdout = stdout.read() 1237 return _stdout.find('OK') != -1 1238 else: 1239 return False
1240
1241 - def run_command(self, cmd=None, env={}):
1242 """\ 1243 Run a command in this session. 1244 1245 After L{x2go.backends.terminal.plain.X2GoTerminalSession.start()} has been called 1246 one or more commands can be executed with L{x2go.backends.terminal.plain.X2GoTerminalSession.run_command()} 1247 within the current X2Go session. 1248 1249 @param cmd: Command to be run 1250 @type cmd: C{str} 1251 @param env: add server-side environment variables 1252 @type env: C{dict} 1253 1254 @return: stdout.read() and stderr.read() as returned by the run command 1255 on the X2Go server 1256 @rtype: C{tuple} of C{str} 1257 1258 """ 1259 if not self.has_command(_rewrite_cmd(str(self.params.cmd), params=self.params)): 1260 if self.client_instance: 1261 self.client_instance.HOOK_no_such_command(profile_name=self.profile_name, session_name=self.session_info.name, cmd=self.params.cmd) 1262 return False 1263 1264 if cmd in ("", None): 1265 if self.params.cmd is None: 1266 cmd = 'TERMINAL' 1267 else: 1268 cmd = self.params.cmd 1269 1270 if cmd == 'XDMCP': 1271 # do not run command when in XDMCP mode... 1272 return None 1273 1274 if 'XSHAD' in cmd: 1275 # do not run command when in DESKTOP SHARING mode... 1276 return None 1277 1278 self.params.update(cmd=cmd) 1279 1280 # do not allow the execution of full path names 1281 if '/' in cmd: 1282 cmd = os.path.basename(cmd) 1283 1284 cmd_line = [ "setsid x2goruncommand", 1285 str(self.session_info.display), 1286 str(self.session_info.agent_pid), 1287 str(self.session_info.name), 1288 str(self.session_info.snd_port), 1289 _rewrite_blanks(_rewrite_cmd(cmd, params=self.params)), 1290 str(self.params.snd_system), 1291 str(self.params.session_type), 1292 "1>/dev/null 2>/dev/null & exit", 1293 ] 1294 1295 if self.params.snd_system == 'pulse': 1296 cmd_line = [ 'PULSE_CLIENTCONFIG=%s/.pulse-client.conf' % self.session_info.remote_container ] + cmd_line 1297 1298 if env: 1299 for env_var in env.keys(): 1300 cmd_line = [ '%s=%s' % (env_var, env[env_var]) ] + cmd_line 1301 1302 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) 1303 1304 if self.params.kbtype not in ('null/null', 'auto') and (self.params.kblayout not in ('null', '') or self.params.kbvariant not in ('null', '')): 1305 self.set_keyboard(layout=self.params.kblayout, variant=self.params.kbvariant) 1306 1307 return stdout.read(), stderr.read()
1308
1309 - def is_desktop_session(self):
1310 """\ 1311 Is this (terminal) session a desktop session? 1312 1313 @return: Returns C{True} is this session is a desktop session. 1314 @rtype: C{bool} 1315 1316 """ 1317 if self.session_info: 1318 return self.session_info.is_desktop_session() 1319 return False
1320
1322 """\ 1323 Is this (terminal) session a published applications provider? 1324 1325 @return: Returns C{True} is this session is a provider session for published applications. 1326 @rtype: C{bool} 1327 1328 """ 1329 if self.session_info and self.is_running(): 1330 return self.session_info.is_published_applications_provider() 1331 return False
1332
1333 - def set_keyboard(self, layout='null', variant='null'):
1334 """\ 1335 Set the keyboard layout and variant for this (running) session. 1336 1337 @param layout: keyboard layout to be set 1338 @type layout: C{str} 1339 @param variant: keyboard variant to be set 1340 @type variant: C{str} 1341 1342 @return: returns C{True} if the {setxkbmap} command could be executed successfully. 1343 @rtype: C{bool} 1344 1345 """ 1346 if not self.is_running(): 1347 return False 1348 1349 cmd_line = [ 'export DISPLAY=:%s && ' % str(self.session_info.display), 1350 'setxkbmap ' 1351 ] 1352 1353 if layout != 'null': 1354 self.logger('setting keyboad layout ,,%s\'\' for session %s' % (layout, self.session_info), log.loglevel_INFO) 1355 cmd_line.append('-layout %s' % layout) 1356 if variant != 'null': 1357 self.logger('setting keyboad variant ,,%s\'\' for session %s' % (variant, self.session_info), log.loglevel_INFO) 1358 cmd_line.append('-variant %s' % variant) 1359 1360 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) 1361 _stderr = stderr.read() 1362 if not _stderr: 1363 self.logger('setting keyboard layout ,,%s\'\' and variant ,,%s\'\' for session %s has been successful' % (layout, variant, self.session_info), log.loglevel_NOTICE) 1364 return True 1365 else: 1366 self.logger('setting keyboard layout ,,%s\'\' and variant ,,%s\'\' for session %s failed: %s' % (layout, variant, self.session_info, _stderr.replace('\n', ' ')), log.loglevel_ERROR) 1367 return False
1368
1369 - def exec_published_application(self, exec_name, timeout=20, env={}):
1370 """\ 1371 Executed a published application. 1372 1373 @param exec_name: application to be executed 1374 @type exec_name: C{str} 1375 @param timeout: execution timeout 1376 @type timeout: C{int} 1377 @param env: session environment dictionary 1378 @type env: C{dict} 1379 1380 """ 1381 cmd_line = [ 1382 "export DISPLAY=:%s && " % str(self.session_info.display), 1383 "export X2GO_SESSION=%s && " % str(self.get_session_name()), 1384 ] 1385 1386 if self.params.snd_system == 'pulse': 1387 cmd_line.append("export PULSE_CLIENTCONFIG=%s/.pulse-client.conf && " % self.session_info.remote_container) 1388 1389 if env: 1390 for env_var in env.keys(): 1391 cmd_line = [ 'export %s=%s && ' % (env_var, env[env_var]) ] + cmd_line 1392 1393 cmd_line.extend( 1394 [ 1395 "setsid %s" % exec_name, 1396 "1>/dev/null 2>/dev/null & exit", 1397 ] 1398 ) 1399 1400 self.logger('executing published application %s for %s with command line: %s' % (exec_name, self.profile_name, cmd_line), loglevel=log.loglevel_DEBUG) 1401 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line, timeout=timeout)
1402
1403 - def ok(self):
1404 """\ 1405 X2Go session OK? 1406 1407 @return: Returns C{True} if this X2Go (terminal) session is up and running, 1408 C{False} otherwise. 1409 @rtype: C{bool} 1410 1411 """ 1412 _ok = bool(self.session_info.name and self.proxy.ok()) 1413 return _ok
1414
1415 - def is_running(self):
1416 """\ 1417 X2Go session running? 1418 1419 @return: Returns C{True} if this X2Go (terminal) session is in running state, 1420 C{False} otherwise. 1421 @rtype: C{bool} 1422 1423 """ 1424 return self.session_info.is_running()
1425
1426 - def is_suspended(self):
1427 """\ 1428 X2Go session suspended? 1429 1430 @return: Returns C{True} if this X2Go (terminal) session is in suspended state, 1431 C{False} otherwise. 1432 @rtype: C{bool} 1433 1434 """ 1435 return self.session_info.is_suspended()
1436
1437 - def is_connected(self):
1438 """\ 1439 X2Go session connected? 1440 1441 @return: Returns C{True} if this X2Go session's Paramiko/SSH transport is 1442 connected/authenticated, C{False} else. 1443 @rtype: C{bool} 1444 1445 """ 1446 return self.control_session.is_connected()
1447
1448 - def start(self):
1449 """\ 1450 Start a new X2Go session. 1451 1452 @return: C{True} if session startup has been successful and the X2Go proxy is up-and-running 1453 @rtype: C{bool} 1454 1455 @raise X2GoTerminalSessionException: if the session startup failed 1456 @raise X2GoDesktopSharingDenied: if desktop sharing fails because of denial by the user running the desktop to be shared 1457 1458 """ 1459 self.params.rewrite_session_type() 1460 1461 if not self.has_command(_rewrite_cmd(self.params.cmd, params=self.params)): 1462 if self.client_instance: 1463 self.client_instance.HOOK_no_such_command(profile_name=self.profile_name, session_name=self.session_info.name, cmd=self.params.cmd) 1464 return False 1465 1466 setkbd = "0" 1467 if self.params.kbtype != "null/null": 1468 setkbd = "1" 1469 1470 if '/' in self.params.cmd: 1471 self.params.cmd = os.path.basename(self.params.cmd) 1472 1473 self.params.rewrite_session_type() 1474 1475 if self.params.geometry == 'maximize': 1476 _geometry = utils.get_workarea_geometry() 1477 if _geometry is None or len(_geometry) != 2: 1478 _geometry = utils.get_desktop_geometry() 1479 if _geometry and len(_geometry) == 2: 1480 self.params.geometry = "%sx%s" % _geometry 1481 else: 1482 self.logger('failed to detect best maximized geometry of your client-side desktop', loglevel=log.loglevel_WARN) 1483 self.params.geometry = "1024x768" 1484 1485 cmd_line = [ "x2gostartagent", 1486 str(self.params.geometry), 1487 str(self.params.link), 1488 str(self.params.pack), 1489 str(self.params.cache_type+'-depth_'+self.params.depth), 1490 str(self.params.kblayout), 1491 str(self.params.kbtype), 1492 str(setkbd), 1493 str(self.params.session_type), 1494 str(self.params.cmd), 1495 ] 1496 if self.params.session_type != 'S': 1497 cmd_line.append( 1498 str(self.params.clipboard), 1499 ) 1500 1501 if self.params.cmd == 'XDMCP' and self.params.xdmcp_server: 1502 cmd_line = ['X2GOXDMCP=%s' % self.params.xdmcp_server] + cmd_line 1503 1504 if self.params.dpi: 1505 cmd_line = ['X2GODPI=%s' % self.params.dpi] + cmd_line 1506 1507 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) 1508 1509 _stdout = stdout.read() 1510 _stderr = stderr.read() 1511 1512 # if the first line of stdout is a "DEN(Y)" string then we will presume that 1513 # we tried to use X2Go desktop sharing and the sharing was rejected 1514 if "ACCESS DENIED" in _stderr and "XSHAD" in _stderr: 1515 raise x2go_exceptions.X2GoDesktopSharingDenied('X2Go desktop sharing has been denied by the remote user') 1516 1517 try: 1518 self.session_info.initialize(_stdout, 1519 username=self.control_session.remote_username(), 1520 hostname=self.control_session.remote_peername(), 1521 ) 1522 except ValueError: 1523 raise x2go_exceptions.X2GoTerminalSessionException("failed to start X2Go session") 1524 except IndexError: 1525 raise x2go_exceptions.X2GoTerminalSessionException("failed to start X2Go session") 1526 1527 # local path may be a Windows path, so we use the path separator of the local system 1528 self.session_info.local_container = os.path.join(self.params.rootdir, 'S-%s' % self.session_info.name) 1529 # remote path is always a UniX path... 1530 self.session_info.remote_container = '%s/.x2go/C-%s' % (self.control_session._x2go_remote_home, 1531 self.session_info.name, 1532 ) 1533 1534 # set up SSH tunnel for X11 graphical elements 1535 self.proxy = self.proxy_backend(session_info=self.session_info, 1536 ssh_transport=self.control_session.get_transport(), 1537 sessions_rootdir=self.sessions_rootdir, 1538 session_instance=self.session_instance, 1539 proxy_options=self.proxy_options, 1540 logger=self.logger) 1541 self.proxy_subprocess, proxy_ok = self.proxy.start_proxy() 1542 1543 if proxy_ok: 1544 self.active_threads.append(self.proxy) 1545 1546 if self.params.session_type in ('D', 'S'): 1547 self.find_session_window() 1548 self.auto_session_window_title() 1549 self.raise_session_window() 1550 1551 if self.params.published_applications: 1552 self.control_session.get_published_applications() 1553 1554 else: 1555 raise x2go_exceptions.X2GoTerminalSessionException("failed to start X2Go session") 1556 1557 return proxy_ok
1558
1559 - def resume(self):
1560 """\ 1561 Resume a running/suspended X2Go session. 1562 1563 @return: C{True} if the session could successfully be resumed 1564 @rtype: C{bool} 1565 1566 @raise X2GoTerminalSessionException: if the terminal session failed to update server-side reported port changes 1567 1568 """ 1569 setkbd = "0" 1570 if self.params.kbtype != "null/null": 1571 setkbd = "1" 1572 1573 if self.params.geometry == 'maximize': 1574 _geometry = utils.get_workarea_geometry() 1575 if _geometry is None or len(_geometry) != 2: 1576 _geometry = utils.get_desktop_geometry() 1577 if _geometry and len(_geometry) == 2: 1578 self.params.geometry = "%sx%s" % _geometry 1579 else: 1580 self.logger('failed to detect best maxmimized geometry of your client-side desktop, using 1024x768 instead', loglevel=log.loglevel_WARN) 1581 self.params.geometry = "1024x768" 1582 1583 cmd_line = [ "x2goresume-session", self.session_info.name, 1584 self.params.geometry, 1585 self.params.link, 1586 self.params.pack, 1587 self.params.kblayout, 1588 self.params.kbtype, 1589 setkbd, 1590 self.params.clipboard, 1591 ] 1592 1593 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) 1594 1595 # re-allocate (if needed) server-side ports for graphics, sound and sshfs 1596 for stdout_line in stdout.read(): 1597 try: 1598 _new_value = stdout_line.split("=")[1].strip() 1599 if 'gr_port=' in stdout_line and _new_value != str(self.session_info.graphics_port): 1600 try: 1601 self.session_info.graphics_port = int(_new_value) 1602 self.logger('re-allocating graphics port for session %s, old server-side port is in use; new graphics port is %s' % (self.session_info, self.session_info.graphics_port), loglevel=log.loglevel_NOTICE) 1603 except TypeError: 1604 # if the re-allocation fails, this is fatal!!! 1605 raise x2go_exceptions.X2GoTerminalSessionException('Failed to retrieve new graphics port from server. X2Go Session cannot be resumed.') 1606 elif 'sound_port=' in stdout_line and _new_value != str(self.session_info.snd_port): 1607 try: 1608 self.session_info.snd_port = int(_new_value) 1609 self.logger('re-allocating sound port for session %s, old server-side port is in use; new sound port is %s' % (self.session_info, self.session_info.snd_port), loglevel=log.loglevel_NOTICE) 1610 except TypeError: 1611 self.logger('Failed to retrieve new sound port from server for session %s, session will be without sound.' % self.session_info, loglevel=log.loglevel_WARN) 1612 elif 'fs_port=' in stdout_line and _new_value != str(self.session_info.sshfs_port): 1613 try: 1614 self.session_info.sshfs_port = int(_new_value) 1615 self.logger('re-allocating sshfs port for session %s, old server-side port is in use; new sshfs port is %s' % (self.session_info, self.session_info.sshfs_port), loglevel=log.loglevel_NOTICE) 1616 except TypeError: 1617 self.logger('Failed to retrieve new sshfs port from server for session %s, session will be without client-side folder sharing. Neither will there be X2Go printing nor X2Go MIME box support.' % self.session_info, loglevel=log.loglevel_WARN) 1618 except IndexError: 1619 continue 1620 1621 # local path may be a Windows path, so we use the path separator of the local system 1622 self.session_info.local_container = os.path.join(self.params.rootdir, 'S-%s' % self.session_info.name) 1623 # remote path is always a UniX path... 1624 self.session_info.remote_container = '%s/.x2go/C-%s' % (self.control_session._x2go_remote_home, 1625 self.session_info.name, 1626 ) 1627 self.proxy = self.proxy_backend(session_info=self.session_info, 1628 ssh_transport=self.control_session.get_transport(), 1629 sessions_rootdir=self.sessions_rootdir, 1630 session_instance=self.session_instance, 1631 proxy_options=self.proxy_options, 1632 logger=self.logger 1633 ) 1634 self.proxy_subprocess, proxy_ok = self.proxy.start_proxy() 1635 1636 if proxy_ok: 1637 self.params.depth = self.session_info.name.split('_')[2][2:] 1638 1639 # on a session resume the user name comes in as a user ID. We have to translate this... 1640 self.session_info.username = self.control_session.remote_username() 1641 1642 if self.params.kbtype not in ('null/null', 'auto') and (self.params.kblayout not in ('null', '') or self.params.kbvariant not in ('null', '')): 1643 self.set_keyboard(layout=self.params.kblayout, variant=self.params.kbvariant) 1644 1645 if self.params.session_type in ('D', 'S'): 1646 self.find_session_window() 1647 self.auto_session_window_title() 1648 self.raise_session_window() 1649 1650 if self.is_published_applications_provider(): 1651 self.control_session.get_published_applications() 1652 self.published_applications = True 1653 else: 1654 raise x2go_exceptions.X2GoTerminalSessionException("failed to start X2Go session") 1655 1656 return proxy_ok
1657
1658 - def suspend(self):
1659 """\ 1660 Suspend this X2Go (terminal) session. 1661 1662 @return: C{True} if the session terminal could be successfully suspended 1663 @rtype: C{bool} 1664 1665 """ 1666 self.release_telekinesis() 1667 self.control_session.suspend(session_name=self.session_info.name) 1668 self.release_proxy() 1669 1670 # TODO: check if session has really suspended 1671 _ret = True 1672 1673 return _ret
1674
1675 - def terminate(self):
1676 """\ 1677 Terminate this X2Go (terminal) session. 1678 1679 @return: C{True} if the session could be successfully terminated 1680 @rtype: C{bool} 1681 1682 """ 1683 self.release_telekinesis() 1684 self.control_session.terminate(session_name=self.session_info.name, destroy_terminals=False) 1685 self.release_proxy() 1686 self.post_terminate_cleanup() 1687 self.__del__() 1688 1689 # TODO: check if session has really suspended 1690 _ret = True 1691 1692 return _ret
1693
1694 - def release_proxy(self):
1695 """\ 1696 Let the X2Go proxy command cleanly die away... (by calling its destructor). 1697 1698 """ 1699 if self.proxy is not None: 1700 self.proxy.__del__() 1701 self.proxy = None
1702
1703 - def release_telekinesis(self):
1704 """\ 1705 Let the attached Telekinesis client cleanly die away... (by calling its destructor). 1706 1707 """ 1708 if self.telekinesis_client is not None: 1709 self.telekinesis_client.__del__() 1710 self.telekinesis_client = None
1711
1712 - def post_terminate_cleanup(self):
1713 """\ 1714 Do some cleanup after this session has terminated. 1715 1716 """ 1717 # this method might be called twice (directly and from update_status in the session 1718 # registry instance. So we have to make sure, that this code will not fail 1719 # if called twice. 1720 if not self._cleaned_up and self.session_info.name: 1721 1722 # otherwise we wipe the session files locally 1723 self.logger('cleaning up session %s after termination' % self.session_info, loglevel=log.loglevel_NOTICE) 1724 1725 # if we run in debug mode, we keep local session directories 1726 if self.logger.get_loglevel() & log.loglevel_DEBUG != log.loglevel_DEBUG: 1727 1728 self._rm_session_dirtree() 1729 self._rm_desktop_dirtree() 1730 1731 self._cleaned_up = True
1732
1733 - def is_rootless_session(self):
1734 """\ 1735 Test if this terminal session is a rootless session. 1736 1737 @return: C{True} if this session is of session type rootless ('R'). 1738 @rtype: C{bool} 1739 1740 """ 1741 self.params.rewrite_session_type() 1742 return self.params.session_type == 'R'
1743
1744 - def is_shadow_session(self):
1745 """\ 1746 Test if this terminal session is a desktop sharing (aka shadow) session. 1747 1748 @return: C{True} if this session is of session type shadow ('S'). 1749 @rtype: C{bool} 1750 1751 """ 1752 self.params.rewrite_session_type() 1753 return self.params.session_type == 'S'
1754
1755 - def is_pubapp_session(self):
1756 """\ 1757 Test if this terminal session is a published applications session. 1758 1759 @return: C{True} if this session is of session type published applications ('P'). 1760 @rtype: C{bool} 1761 1762 """ 1763 self.params.rewrite_session_type() 1764 return self.params.session_type == 'P'
1765