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-2014 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 self._share_local_folder_lock.release() 425 self.release_telekinesis() 426 self.release_proxy() 427 self.session_window = None 428 self.update_session_window_file() 429 430 try: 431 432 if self.control_session.get_transport() is not None: 433 try: 434 for _tunnel in [ _tun[1] for _tun in self.reverse_tunnels[self.session_info.name].values() ]: 435 if _tunnel is not None: 436 _tunnel.__del__() 437 except KeyError: 438 pass 439 440 if self.print_queue is not None: 441 self.print_queue.__del__() 442 443 if self.mimebox_queue is not None: 444 self.mimebox_queue.__del__() 445 446 except AttributeError: 447 pass 448 449 self.session_info.clear()
450
451 - def _mk_sessions_rootdir(self, rootdir):
452 """\ 453 Create the server-side session root dir (normally ~/.x2go). 454 455 @param rootdir: server-side session root directory 456 @type rootdir: C{str} 457 458 """ 459 try: 460 os.makedirs(rootdir) 461 except OSError, e: 462 if e.errno == 17: 463 # file exists 464 pass 465 else: 466 raise OSError, e
467
468 - def _rm_session_dirtree(self):
469 """\ 470 Purge client-side session dir (session cache directory). 471 472 """ 473 if self.session_info.name: 474 shutil.rmtree('%s/S-%s' % (self.params.rootdir, self.session_info), ignore_errors=True)
475
476 - def _rm_desktop_dirtree(self):
477 """\ 478 Purge client-side session dir (C-<display> directory) 479 480 """ 481 if self.session_info.display: 482 shutil.rmtree('%s/S-%s' % (self.params.rootdir, self.session_info.display), ignore_errors=True)
483
484 - def get_session_name(self):
485 """\ 486 Retrieve the X2Go session's name from the session info object. 487 488 @return: the session name 489 @rtype: C{str} 490 491 """ 492 return self.session_info.name
493
494 - def get_session_info(self):
495 """\ 496 Retrieve the X2Go session's session info object. 497 498 @return: the session info object 499 @rtype: C{X2GoServerSessionInfo*} 500 501 """ 502 return self.session_info
503
504 - def get_session_cmd(self):
505 """\ 506 Retrieve the X2Go session's command as stored in the session parameter object. 507 508 @return: the session command 509 @rtype: C{str} 510 511 """ 512 return self.params.cmd
513
514 - def get_session_type(self):
515 """\ 516 Retrieve the X2Go session's session type as stored in the session parameter object. 517 518 @return: the session type 519 @rtype: C{str} 520 521 """ 522 return self.params.session_type
523
524 - def start_sound(self):
525 """\ 526 Initialize Paramiko/SSH reverse forwarding tunnel for X2Go sound. 527 528 Currently supported audio protocols: 529 530 - PulseAudio 531 - Esound (not tested very much) 532 533 @raise X2GoControlSessionException: if the control session of this terminal session is not connected 534 535 """ 536 _tunnel = None 537 if self.reverse_tunnels[self.session_info.name]['snd'][1] is None: 538 if self.params.snd_system == 'pulse': 539 self.logger('initializing PulseAudio sound support in X2Go session', loglevel=log.loglevel_INFO) 540 ### 541 ### PULSEAUDIO 542 ### 543 cookie_filepath = None 544 if os.path.exists(os.path.normpath('%s/.pulse-cookie' % _LOCAL_HOME)): 545 cookie_filepath = os.path.normpath('%s/.pulse-cookie' % _LOCAL_HOME) 546 elif os.path.exists(os.path.normpath('%s/.config/pulse/cookie' % _LOCAL_HOME)): 547 cookie_filepath = os.path.normpath('%s/.config/pulse/cookie' % _LOCAL_HOME) 548 if cookie_filepath is not None: 549 # setup pulse client config file on X2Go server 550 cmd_line = "echo 'default-server=127.0.0.1:%s'>%s/.pulse-client.conf;" % (self.session_info.snd_port, self.session_info.remote_container) + \ 551 "echo 'cookie-file=%s/.pulse-cookie'>>%s/.pulse-client.conf" % (self.session_info.remote_container, self.session_info.remote_container) 552 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) 553 554 self.control_session._x2go_sftp_put(local_path=cookie_filepath, remote_path='%s/.pulse-cookie' % self.session_info.remote_container) 555 556 # start reverse SSH tunnel for pulse stream 557 _tunnel = rforward.X2GoRevFwTunnel(server_port=self.session_info.snd_port, 558 remote_host='127.0.0.1', 559 remote_port=self.snd_port, 560 ssh_transport=self.control_session.get_transport(), 561 session_instance=self.session_instance, 562 logger=self.logger 563 ) 564 else: 565 if self.client_instance: 566 self.client_instance.HOOK_on_sound_tunnel_failed(profile_name=self.profile_name, session_name=self.session_info.name) 567 elif self.params.snd_system == 'arts': 568 ### 569 ### ARTSD AUDIO 570 ### 571 self.logger('the ArtsD sound server (as in KDE3) is obsolete and will not be supported by Python X2Go...', loglevel=log.loglevel_WARNING) 572 573 elif self.params.snd_system == 'esd': 574 ### 575 ### ESD AUDIO 576 ### 577 578 self.logger('initializing ESD sound support in X2Go session', loglevel=log.loglevel_INFO) 579 self.control_session._x2go_sftp_put(local_path='%s/.esd_auth' % _LOCAL_HOME, remote_path='%s/.esd_auth' % self.control_session._x2go_remote_home) 580 581 # start reverse SSH tunnel for pulse stream 582 _tunnel = rforward.X2GoRevFwTunnel(server_port=self.session_info.snd_port, 583 remote_host='127.0.0.1', 584 remote_port=self.snd_port, 585 ssh_transport=self.control_session.get_transport(), 586 session_instance=self.session_instance, 587 logger=self.logger 588 ) 589 590 591 if _tunnel is not None: 592 self.reverse_tunnels[self.session_info.name]['snd'] = (self.session_info.snd_port, _tunnel) 593 _tunnel.start() 594 self.active_threads.append(_tunnel) 595 596 else: 597 # tunnel has already been started and might simply need a resume call 598 self.reverse_tunnels[self.session_info.name]['snd'][1].resume()
599
600 - def start_sshfs(self):
601 """\ 602 Initialize Paramiko/SSH reverse forwarding tunnel for X2Go folder sharing. 603 604 """ 605 if not self.control_session.is_sshfs_available(): 606 raise x2go_exceptions.X2GoUserException('Remote user %s is not allowed to share SSHFS resources with the server.' % self.session_info.username) 607 608 # start reverse SSH tunnel for sshfs (folder sharing, printing) 609 ssh_transport = self.control_session.get_transport() 610 if self.reverse_tunnels[self.session_info.name]['sshfs'][1] is None: 611 612 _tunnel = sftpserver.X2GoRevFwTunnelToSFTP(server_port=self.session_info.sshfs_port, 613 ssh_transport=ssh_transport, 614 auth_key=self.control_session._x2go_session_auth_rsakey, 615 session_instance=self.session_instance, 616 logger=self.logger 617 ) 618 619 if _tunnel is not None: 620 self.reverse_tunnels[self.session_info.name]['sshfs'] = (self.session_info.sshfs_port, _tunnel) 621 _tunnel.start() 622 self.active_threads.append(_tunnel) 623 while not _tunnel.ready: 624 gevent.sleep(.1) 625 626 else: 627 # tunnel has already been started and might simply need a resume call 628 self.reverse_tunnels[self.session_info.name]['sshfs'][1].resume()
629
630 - def _x2go_pause_rev_fw_tunnel(self, name):
631 """\ 632 Pause reverse SSH tunnel of name <name>. 633 634 @param name: tunnel name (either of C{sshfs}, C{snd}) 635 @type name: C{str} 636 637 """ 638 _tunnel = self.reverse_tunnels[self.session_info.name][name][1] 639 if _tunnel is not None: 640 _tunnel.pause()
641
642 - def stop_sound(self):
643 """\ 644 Shutdown (pause) Paramiko/SSH reverse forwarding tunnel for X2Go sound. 645 646 """ 647 self._x2go_pause_rev_fw_tunnel('snd')
648
649 - def stop_sshfs(self):
650 """\ 651 Shutdown (pause) Paramiko/SSH reverse forwarding tunnel for X2Go folder sharing. 652 653 """ 654 self._x2go_pause_rev_fw_tunnel('sshfs')
655
656 - def start_printing(self):
657 """\ 658 Initialize X2Go print spooling. 659 660 @raise X2GoUserException: if the X2Go printing feature is not available to this user 661 662 """ 663 if not self.control_session.is_sshfs_available(): 664 raise x2go_exceptions.X2GoUserException('Remote user %s is not allowed to use client-side printing.' % self.session_info.username) 665 666 spool_dir = os.path.join(self.session_info.local_container, 'spool') 667 if not os.path.exists(spool_dir): 668 os.makedirs(spool_dir) 669 self.share_local_folder(local_path=spool_dir, folder_type='spool') 670 self.print_queue = printqueue.X2GoPrintQueue(profile_name=self.profile_name, 671 session_name=self.session_info.name, 672 spool_dir=spool_dir, 673 print_action=self.print_action, 674 print_action_args=self.print_action_args, 675 client_instance=self.client_instance, 676 printing_backend=self.printing_backend, 677 logger=self.logger, 678 ) 679 self.print_queue.start() 680 self.active_threads.append(self.print_queue)
681
682 - def set_print_action(self, print_action, **kwargs):
683 """\ 684 Set a print action for the next incoming print jobs. 685 686 This method is a wrapper for L{X2GoPrintQueue}C{.set_print_action()}. 687 688 @param print_action: print action name or object (i.e. an instance of C{X2GoPrintAction*} classes) 689 @type print_action: C{str} or C{X2GoPrintAction*} 690 @param kwargs: print action specific parameters 691 @type kwargs: dict 692 693 """ 694 self.print_queue.set_print_action(print_action, logger=self.logger, **kwargs)
695
696 - def stop_printing(self):
697 """\ 698 Shutdown (pause) the X2Go Print Queue thread. 699 700 """ 701 if self.print_queue is not None: 702 self.print_queue.pause()
703
704 - def get_printing_spooldir(self):
705 """\ 706 Return the server-side printing spooldir path. 707 708 @return: the directory for remote print job spooling 709 @rtype: C{str} 710 711 """ 712 return '%s/%s' % (self.session_info.remote_container, 'spool')
713
714 - def start_mimebox(self, mimebox_extensions=[], mimebox_action=None):
715 """\ 716 Initialize the X2Go MIME box. Open/process incoming files from the server-side locally. 717 718 @param mimebox_extensions: file name extensions that are allowed for local opening/processing 719 @type mimebox_extensions: C{list} 720 @param mimebox_action: MIME box action given as name or object (i.e. an instance of C{X2GoMIMEboxAction*} classes). 721 @type mimebox_action: C{str} or C{obj} 722 723 @raise X2GoUserException: if the X2Go MIME box feature is not available to this user 724 725 """ 726 if not self.control_session.is_sshfs_available(): 727 raise x2go_exceptions.X2GoUserException('Remote user %s is not allowed to use the MIME box.' % self.session_info.username) 728 729 mimebox_dir = os.path.join(self.session_info.local_container, 'mimebox') 730 if not os.path.exists(mimebox_dir): 731 os.makedirs(mimebox_dir) 732 self.share_local_folder(local_path=mimebox_dir, folder_type='mimebox') 733 self.mimebox_queue = mimebox.X2GoMIMEboxQueue(profile_name=self.profile_name, 734 session_name=self.session_info.name, 735 mimebox_dir=mimebox_dir, 736 mimebox_extensions=mimebox_extensions, 737 mimebox_action=mimebox_action, 738 client_instance=self.client_instance, 739 logger=self.logger, 740 ) 741 self.mimebox_queue.start() 742 self.active_threads.append(self.mimebox_queue)
743
744 - def set_mimebox_action(self, mimebox_action, **kwargs):
745 """\ 746 Set a MIME box action for the next incoming MIME jobs. 747 748 This method is a wrapper for L{X2GoMIMEboxQueue}C{set_mimebox_action()}. 749 750 @param mimebox_action: MIME box action name or object (i.e. an instance of C{X2GoMIMEboxAction*} classes) 751 @type mimebox_action: C{str} or C{X2GoMIMEboxAction*} 752 @param kwargs: MIME box action specific parameters 753 @type kwargs: dict 754 755 """ 756 self.mimebox_queue.set_mimebox_action(mimebox_action, logger=self.logger, **kwargs)
757
758 - def stop_mimebox(self):
759 """\ 760 Shutdown (pause) the X2Go MIME box Queue thread. 761 762 """ 763 if self.mimebox_queue is not None: 764 self.mimebox_queue.pause()
765
766 - def get_mimebox_spooldir(self):
767 """\ 768 Return the server-side MIME box spooldir path. 769 770 @return: the directory where remote MIME box jobs are placed 771 @rtype: C{str} 772 773 """ 774 return '%s/%s' % (self.session_info.remote_container, 'mimebox')
775
776 - def start_telekinesis(self):
777 """\ 778 Initialize Telekinesis client for X2Go. 779 780 """ 781 if self.telekinesis_client is not None: 782 del self.telekinesis_client 783 self.telekinesis_client = None 784 if self.telekinesis_subprocess is not None: 785 self.telekinesis_subprocess = None 786 if self.session_info.tekictrl_port != -1 and self.session_info.tekidata_port != -1: 787 self.telekinesis_client = telekinesis.X2GoTelekinesisClient(session_info=self.session_info, 788 ssh_transport=self.control_session.get_transport(), 789 sessions_rootdir=self.sessions_rootdir, 790 session_instance=self.session_instance, 791 logger=self.logger) 792 if self.telekinesis_client.has_telekinesis_client(): 793 self.telekinesis_subprocess, telekinesis_ok = self.telekinesis_client.start_telekinesis() 794 else: 795 del self.telekinesis_client 796 self.telekinesis_client = None
797
798 - def is_session_info_protected(self):
799 """\ 800 Test if this terminal's session info object is write-protected. 801 802 @return: C{True}, if session info object is read-only, C{False} for read-write. 803 @rtype: C{bool} 804 805 """ 806 self.session_info.is_protected()
807
808 - def session_info_protect(self):
809 """\ 810 Protect this terminal session's info object against updates. 811 812 """ 813 self.session_info.protect()
814
815 - def session_info_unprotect(self):
816 """\ 817 Allow session info updates from within the list_sessions method of the control session. 818 819 """ 820 self.session_info.unprotect()
821
822 - def share_local_folder(self, local_path=None, folder_type='disk'):
823 """\ 824 Share a local folder with the X2Go session. 825 826 @param local_path: the full path to an existing folder on the local 827 file system 828 @type local_path: C{str} 829 @param folder_type: one of 'disk' (a folder on your local hard drive), 'rm' (removeable device), 830 'cdrom' (CD/DVD Rom) or 'spool' (for X2Go print spooling) 831 @type folder_type: C{str} 832 833 @return: returns C{True} if the local folder has been successfully mounted within the X2Go server session 834 @rtype: C{bool} 835 836 @raise X2GoUserException: if local folder sharing is not available to this user 837 @raise Exception: any other exception occuring on the way is passed through by this method 838 839 """ 840 if not self.control_session.is_sshfs_available(): 841 raise x2go_exceptions.X2GoUserException('Remote user %s is not allowed to share local folders with the server.' % self.session_info.username) 842 843 if local_path is None: 844 self.logger('no folder name given...', log.loglevel_WARN) 845 return False 846 847 if type(local_path) not in (types.StringType, types.UnicodeType): 848 self.logger('folder name needs to be of type StringType...', log.loglevel_WARN) 849 return False 850 851 if not os.path.exists(local_path): 852 self.logger('local folder does not exist: %s' % local_path, log.loglevel_WARN) 853 return False 854 855 local_path = os.path.normpath(local_path) 856 self.logger('sharing local folder: %s' % local_path, log.loglevel_INFO) 857 858 _auth_rsakey = self.control_session._x2go_session_auth_rsakey 859 _host_rsakey = defaults.RSAHostKey 860 861 _tmp_io_object = cStringIO.StringIO() 862 _auth_rsakey.write_private_key(_tmp_io_object) 863 _tmp_io_object.write('----BEGIN RSA IDENTITY----') 864 _tmp_io_object.write('%s %s' % (_host_rsakey.get_name(),_host_rsakey.get_base64(),)) 865 866 # _x2go_key_fname must be a UniX path 867 _x2go_key_fname = '%s/%s/%s' % (os.path.dirname(self.session_info.remote_container), 'ssh', 'key.z%s' % self.session_info.agent_pid) 868 _x2go_key_bundle = _tmp_io_object.getvalue() 869 870 # if there is another call to this method currently being processed, wait for that one to finish 871 self._share_local_folder_lock.acquire() 872 873 try: 874 self.control_session._x2go_sftp_write(_x2go_key_fname, _x2go_key_bundle) 875 876 _convert_encoding = self.params.convert_encoding 877 _client_encoding = self.params.client_encoding 878 _server_encoding = self.params.server_encoding 879 880 if _X2GOCLIENT_OS == 'Windows': 881 if local_path.startswith('\\\\'): 882 # we are on a UNC path 883 if 'X2GO_MOUNT_UNCPATHS' in self.control_session.get_server_features(): 884 local_path = local_path.repalce('\\\\', '/uncpath/') 885 else: 886 local_path = local_path.repalce('\\\\', '/windrive/') 887 local_path = local_path.replace('\\', '/') 888 else: 889 local_path = local_path.replace('\\', '/') 890 local_path = local_path.replace(':', '') 891 local_path = '/windrive/%s' % local_path 892 _convert_encoding = True 893 _client_encoding = 'WINDOWS-1252' 894 895 if _convert_encoding: 896 export_iconv_settings = 'export X2GO_ICONV=modules=iconv,from_code=%s,to_code=%s && ' % (_client_encoding, _server_encoding) 897 else: 898 export_iconv_settings = '' 899 900 if folder_type == 'disk': 901 902 cmd_line = [ '%sexport HOSTNAME &&' % export_iconv_settings, 903 'x2gomountdirs', 904 'dir', 905 str(self.session_info.name), 906 '\'%s\'' % _CURRENT_LOCAL_USER, 907 _x2go_key_fname, 908 '%s__REVERSESSH_PORT__%s; ' % (local_path, self.session_info.sshfs_port), 909 'rm -f %s %s.ident' % (_x2go_key_fname, _x2go_key_fname), 910 ] 911 912 elif folder_type == 'spool': 913 914 cmd_line = [ '%sexport HOSTNAME &&' % export_iconv_settings, 915 'x2gomountdirs', 916 'dir', 917 str(self.session_info.name), 918 '\'%s\'' % _CURRENT_LOCAL_USER, 919 _x2go_key_fname, 920 '%s__PRINT_SPOOL___REVERSESSH_PORT__%s; ' % (local_path, self.session_info.sshfs_port), 921 'rm -f %s %s.ident' % (_x2go_key_fname, _x2go_key_fname), 922 ] 923 924 elif folder_type == 'mimebox': 925 926 cmd_line = [ '%sexport HOSTNAME &&' % export_iconv_settings, 927 'x2gomountdirs', 928 'dir', 929 str(self.session_info.name), 930 '\'%s\'' % _CURRENT_LOCAL_USER, 931 _x2go_key_fname, 932 '%s__MIMEBOX_SPOOL___REVERSESSH_PORT__%s; ' % (local_path, self.session_info.sshfs_port), 933 'rm -f %s %s.ident' % (_x2go_key_fname, _x2go_key_fname), 934 ] 935 936 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) 937 _stdout = stdout.read().split('\n') 938 self.logger('x2gomountdirs output is: %s' % _stdout, log.loglevel_NOTICE) 939 940 except: 941 self._share_local_folder_lock.release() 942 raise 943 self._share_local_folder_lock.release() 944 945 if len(_stdout) >= 6 and _stdout[5].endswith('ok'): 946 return True 947 return False
948
949 - def unshare_all_local_folders(self):
950 """\ 951 Unshare all local folders mount in the X2Go session. 952 953 @return: returns C{True} if all local folders could be successfully unmounted from the X2Go server session 954 @rtype: C{bool} 955 956 """ 957 self.logger('unsharing all local folders from session %s' % self.session_info, log.loglevel_INFO) 958 959 cmd_line = [ 'export HOSTNAME &&', 960 'x2goumount-session', 961 self.session_info.name, 962 ] 963 964 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) 965 if not stderr.read(): 966 self.logger('x2goumount-session (all mounts) for session %s has been successful' % self.session_info, log.loglevel_NOTICE) 967 return True 968 else: 969 self.logger('x2goumount-session (all mounts) for session %s failed' % self.session_info, log.loglevel_ERROR) 970 return False
971
972 - def unshare_local_folder(self, local_path):
973 """\ 974 Unshare local folder given as <local_path> from X2Go session. 975 976 @return: returns C{True} if the local folder <local_path> could be successfully unmounted from the X2Go server session 977 @rtype: C{bool} 978 979 """ 980 self.logger('unsharing local folder from session %s' % self.session_info, log.loglevel_INFO) 981 982 cmd_line = [ 'export HOSTNAME &&', 983 'x2goumount-session', 984 self.session_info.name, 985 "'%s'" % local_path, 986 ] 987 988 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) 989 if not stderr.read(): 990 self.logger('x2goumount-session (%s) for session %s has been successful' % (local_path, self.session_info, ), log.loglevel_NOTICE) 991 return True 992 else: 993 self.logger('x2goumount-session (%s) for session %s failed' % (local_path, self.session_info, ), log.loglevel_ERROR) 994 return False
995
996 - def color_depth(self):
997 """\ 998 Retrieve the session's color depth. 999 1000 @return: the session's color depth 1001 @rtype: C{int} 1002 1003 """ 1004 return self.params.depth
1005
1006 - def auto_session_window_title(self, dont_set=False):
1007 """\ 1008 Automatically generate an appropriate human-readable session window title. 1009 1010 The session window title will be provider in the C{session_title} property of 1011 this method. 1012 1013 @param dont_set: generate the session window title, but do not actually set it 1014 @type dont_set: C{bool} 1015 1016 """ 1017 _generic_title = 'X2GO-%s' % self.session_info.name 1018 1019 # no blanks at beginning or end, no blanks-only... 1020 self.session_title = self.session_title.strip() 1021 1022 if self.params.session_type == 'D': 1023 if self.set_session_title: 1024 1025 if not self.session_title: 1026 self.session_title = '%s for %s@%s' % (self.params.cmd, self.control_session.remote_username(), self.control_session.get_hostname()) 1027 1028 else: 1029 # session title fallback... (like X2Go server does it...) 1030 self.session_title = _generic_title 1031 1032 elif self.params.session_type == 'S': 1033 if self.set_session_title: 1034 1035 shared_user = _generic_title.split('XSHAD')[1] 1036 shared_display = _generic_title.split('XSHAD')[2].replace('PP', ':').split("_")[0] 1037 1038 self.session_title = 'Desktop %s@%s shared with %s@%s' % (shared_user, shared_display, self.control_session.remote_username(), self.control_session.get_hostname()) 1039 1040 else: 1041 # session title fallback... (like X2Go server does it...) 1042 self.session_title = _generic_title 1043 1044 else: 1045 # do nothing for rootless sessions 1046 self.session_title = _generic_title 1047 1048 if self.session_title != _generic_title and not dont_set: 1049 self.set_session_window_title(title=self.session_title)
1050
1051 - def find_session_window(self, timeout=60):
1052 """\ 1053 Try for <timeout> seconds to find the X2Go session window of this 1054 terminal session. 1055 1056 A background thread will get spawned for this operation. 1057 1058 @param timeout: try for <timeout> seconds to find the session window 1059 @type timeout: C{int} 1060 1061 """ 1062 gevent.spawn(self._find_session_window, timeout=timeout)
1063
1064 - def _find_session_window(self, timeout=0):
1065 """\ 1066 Try for <timeout> seconds to find the X2Go session window of this 1067 terminal session. 1068 1069 @param timeout: try for <timeout> seconds to find the session window 1070 @type timeout: C{int} 1071 1072 """ 1073 self.session_window = None 1074 1075 # search for the window of our focus, do this in a loop till the window as been found 1076 # or timeout forces us to give up... 1077 timeout += 1 1078 while timeout: 1079 1080 timeout -= 1 1081 1082 window = utils.find_session_window(self.session_info.name) 1083 1084 if window is not None: 1085 if _X2GOCLIENT_OS == "Windows": 1086 self.logger('Session window handle for session %s is: %s' % (self.session_info.name, window), loglevel=log.loglevel_DEBUG) 1087 else: 1088 self.logger('Session window ID for session %s is: %s' % (self.session_info.name, window.id), loglevel=log.loglevel_DEBUG) 1089 self.session_window = window 1090 1091 self.update_session_window_file() 1092 break 1093 1094 gevent.sleep(1)
1095
1096 - def update_session_window_file(self):
1097 """\ 1098 Create a file that contains information on the session window. 1099 . 1100 If the file already exists, its content gets update. 1101 1102 """ 1103 session_window_file = os.path.join(self.session_info.local_container, 'session.window') 1104 if self.session_window is not None: 1105 f = open(session_window_file,'w') 1106 if _X2GOCLIENT_OS != "Windows": 1107 _id = self.session_window.id 1108 else: 1109 _id = self.session_window 1110 f.write('ID:{window_id}\n'.format(window_id=_id)) 1111 f.close() 1112 self.logger('Updating session.window file %s: Window-ID->%s' % (session_window_file, _id), loglevel=log.loglevel_DEBUG) 1113 else: 1114 try: 1115 os.remove(session_window_file) 1116 except OSError,e: 1117 # this is no error in most cases... 1118 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)
1119
1120 - def set_session_window_title(self, title, timeout=60):
1121 """\ 1122 Modify the session window title. 1123 1124 A background thread will get spawned for this operation. 1125 1126 @param title: new title for the terminal session's session window 1127 @type title: C{str} 1128 @param timeout: try for <timeout> seconds to find the session window 1129 @type timeout: C{int} 1130 1131 """ 1132 gevent.spawn(self._set_session_window_title, title=title.strip(), timeout=timeout)
1133
1134 - def _set_session_window_title(self, title, timeout=0):
1135 """\ 1136 Modify the session window title. 1137 1138 @param title: new title for the terminal session's session window 1139 @type title: C{str} 1140 @param timeout: try for <timeout> seconds to find the session window 1141 @type timeout: C{int} 1142 1143 """ 1144 self.session_title = title 1145 1146 if not self.session_title: 1147 self.auto_session_title(dont_set=True) 1148 1149 timeout += 1 1150 while timeout: 1151 1152 timeout -= 1 1153 1154 if self.session_window is not None: 1155 self.logger('Setting session window title for session %s is: %s' % (self.session_info.name, self.session_title), loglevel=log.loglevel_DEBUG) 1156 utils.set_session_window_title(self.session_window, self.session_title) 1157 break 1158 1159 gevent.sleep(1)
1160
1161 - def raise_session_window(self, timeout=60):
1162 """\ 1163 Try for <timeout> seconds to raise the X2Go session window of this 1164 terminal session to the top and bring it to focus. 1165 1166 A background thread will get spawned for this operation. 1167 1168 @param timeout: try for <timeout> seconds to raise the session window 1169 @type timeout: C{int} 1170 1171 """ 1172 gevent.spawn(self._raise_session_window, timeout=timeout)
1173
1174 - def _raise_session_window(self, timeout=0):
1175 """ 1176 Try for <timeout> seconds to raise the X2Go session window of this 1177 terminal session to the top and bring it to focus. 1178 1179 @param timeout: try for <timeout> seconds to raise the session window 1180 @type timeout: C{int} 1181 1182 """ 1183 timeout += 1 1184 while timeout: 1185 1186 timeout -= 1 1187 1188 if self.session_window is not None: 1189 1190 utils.raise_session_window(self.session_window) 1191 break 1192 1193 gevent.sleep(1)
1194
1195 - def has_command(self, cmd):
1196 """\ 1197 ,,Guess'' if the command C{<cmd>} exists on the X2Go server and is executable. 1198 The expected result is not 100% safe, however, it comes with a high probability to 1199 be correct. 1200 1201 @param cmd: session command 1202 @type cmd: C{str} 1203 1204 @return: C{True} if this method reckons that the command is executable on the remote X2Go server 1205 @rtype: C{bool} 1206 1207 """ 1208 test_cmd = None; 1209 1210 cmd = cmd.strip('"').strip('"') 1211 if cmd.find('RDP') != -1: 1212 cmd = 'rdesktop' 1213 1214 if cmd in _X2GO_GENERIC_APPLICATIONS: 1215 return True 1216 if cmd in _X2GO_DESKTOPSESSIONS.keys(): 1217 return True 1218 elif 'XSHAD' in cmd: 1219 return True 1220 elif 'PUBLISHED' in cmd and 'X2GO_PUBLISHED_APPLICATIONS' in self.control_session.get_server_features(): 1221 return True 1222 elif cmd and cmd.startswith('/'): 1223 # check if full path is correct _and_ if application is in server path 1224 test_cmd = 'test -x %s && which %s && echo OK' % (cmd, os.path.basename(cmd.split()[0])) 1225 elif cmd and '/' not in cmd.split()[0]: 1226 # check if application is in server path only 1227 test_cmd = 'which %s && echo OK' % os.path.basename(cmd.split()[0]) 1228 1229 if test_cmd: 1230 (stdin, stdout, stderr) = self.control_session._x2go_exec_command([test_cmd]) 1231 _stdout = stdout.read() 1232 return _stdout.find('OK') != -1 1233 else: 1234 return False
1235
1236 - def run_command(self, cmd=None, env={}):
1237 """\ 1238 Run a command in this session. 1239 1240 After L{x2go.backends.terminal.plain.X2GoTerminalSession.start()} has been called 1241 one or more commands can be executed with L{x2go.backends.terminal.plain.X2GoTerminalSession.run_command()} 1242 within the current X2Go session. 1243 1244 @param cmd: Command to be run 1245 @type cmd: C{str} 1246 @param env: add server-side environment variables 1247 @type env: C{dict} 1248 1249 @return: stdout.read() and stderr.read() as returned by the run command 1250 on the X2Go server 1251 @rtype: C{tuple} of C{str} 1252 1253 """ 1254 if not self.has_command(_rewrite_cmd(str(self.params.cmd), params=self.params)): 1255 if self.client_instance: 1256 self.client_instance.HOOK_no_such_command(profile_name=self.profile_name, session_name=self.session_info.name, cmd=self.params.cmd) 1257 return False 1258 1259 if cmd in ("", None): 1260 if self.params.cmd is None: 1261 cmd = 'TERMINAL' 1262 else: 1263 cmd = self.params.cmd 1264 1265 if cmd == 'XDMCP': 1266 # do not run command when in XDMCP mode... 1267 return None 1268 1269 if 'XSHAD' in cmd: 1270 # do not run command when in DESKTOP SHARING mode... 1271 return None 1272 1273 self.params.update(cmd=cmd) 1274 1275 # do not allow the execution of full path names 1276 if '/' in cmd: 1277 cmd = os.path.basename(cmd) 1278 1279 cmd_line = [ "setsid x2goruncommand", 1280 str(self.session_info.display), 1281 str(self.session_info.agent_pid), 1282 str(self.session_info.name), 1283 str(self.session_info.snd_port), 1284 _rewrite_blanks(_rewrite_cmd(cmd, params=self.params)), 1285 str(self.params.snd_system), 1286 str(self.params.session_type), 1287 "1>/dev/null 2>/dev/null & exit", 1288 ] 1289 1290 if self.params.snd_system == 'pulse': 1291 cmd_line = [ 'PULSE_CLIENTCONFIG=%s/.pulse-client.conf' % self.session_info.remote_container ] + cmd_line 1292 1293 if env: 1294 for env_var in env.keys(): 1295 cmd_line = [ '%s=%s' % (env_var, env[env_var]) ] + cmd_line 1296 1297 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) 1298 1299 if self.params.kbtype not in ('null/null', 'auto') and (self.params.kblayout not in ('null', '') or self.params.kbvariant not in ('null', '')): 1300 self.set_keyboard(layout=self.params.kblayout, variant=self.params.kbvariant) 1301 1302 return stdout.read(), stderr.read()
1303
1304 - def is_desktop_session(self):
1305 """\ 1306 Is this (terminal) session a desktop session? 1307 1308 @return: Returns C{True} is this session is a desktop session. 1309 @rtype: C{bool} 1310 1311 """ 1312 if self.session_info: 1313 return self.session_info.is_desktop_session() 1314 return False
1315
1317 """\ 1318 Is this (terminal) session a published applications provider? 1319 1320 @return: Returns C{True} is this session is a provider session for published applications. 1321 @rtype: C{bool} 1322 1323 """ 1324 if self.session_info and self.is_running(): 1325 return self.session_info.is_published_applications_provider() 1326 return False
1327
1328 - def set_keyboard(self, layout='null', variant='null'):
1329 """\ 1330 Set the keyboard layout and variant for this (running) session. 1331 1332 @param layout: keyboard layout to be set 1333 @type layout: C{str} 1334 @param variant: keyboard variant to be set 1335 @type variant: C{str} 1336 1337 @return: returns C{True} if the {setxkbmap} command could be executed successfully. 1338 @rtype: C{bool} 1339 1340 """ 1341 if not self.is_running(): 1342 return False 1343 1344 cmd_line = [ 'export DISPLAY=:%s && ' % str(self.session_info.display), 1345 'setxkbmap ' 1346 ] 1347 1348 if layout != 'null': 1349 self.logger('setting keyboad layout ,,%s\'\' for session %s' % (layout, self.session_info), log.loglevel_INFO) 1350 cmd_line.append('-layout %s' % layout) 1351 if variant != 'null': 1352 self.logger('setting keyboad variant ,,%s\'\' for session %s' % (variant, self.session_info), log.loglevel_INFO) 1353 cmd_line.append('-variant %s' % variant) 1354 1355 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) 1356 _stderr = stderr.read() 1357 if not _stderr: 1358 self.logger('setting keyboard layout ,,%s\'\' and variant ,,%s\'\' for session %s has been successful' % (layout, variant, self.session_info), log.loglevel_NOTICE) 1359 return True 1360 else: 1361 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) 1362 return False
1363
1364 - def exec_published_application(self, exec_name, timeout=20, env={}):
1365 """\ 1366 Executed a published application. 1367 1368 @param exec_name: application to be executed 1369 @type exec_name: C{str} 1370 @param timeout: execution timeout 1371 @type timeout: C{int} 1372 @param env: session environment dictionary 1373 @type env: C{dict} 1374 1375 """ 1376 cmd_line = [ 1377 "export DISPLAY=:%s && " % str(self.session_info.display), 1378 "export X2GO_SESSION=%s && " % str(self.get_session_name()), 1379 ] 1380 1381 if self.params.snd_system == 'pulse': 1382 cmd_line.append("export PULSE_CLIENTCONFIG=%s/.pulse-client.conf && " % self.session_info.remote_container) 1383 1384 if env: 1385 for env_var in env.keys(): 1386 cmd_line = [ 'export %s=%s && ' % (env_var, env[env_var]) ] + cmd_line 1387 1388 cmd_line.extend( 1389 [ 1390 "setsid %s" % exec_name, 1391 "1>/dev/null 2>/dev/null & exit", 1392 ] 1393 ) 1394 1395 self.logger('executing published application %s for %s with command line: %s' % (exec_name, self.profile_name, cmd_line), loglevel=log.loglevel_DEBUG) 1396 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line, timeout=timeout)
1397
1398 - def ok(self):
1399 """\ 1400 X2Go session OK? 1401 1402 @return: Returns C{True} if this X2Go (terminal) session is up and running, 1403 C{False} otherwise. 1404 @rtype: C{bool} 1405 1406 """ 1407 _ok = bool(self.session_info.name and self.proxy.ok()) 1408 return _ok
1409
1410 - def is_running(self):
1411 """\ 1412 X2Go session running? 1413 1414 @return: Returns C{True} if this X2Go (terminal) session is in running state, 1415 C{False} otherwise. 1416 @rtype: C{bool} 1417 1418 """ 1419 return self.session_info.is_running()
1420
1421 - def is_suspended(self):
1422 """\ 1423 X2Go session suspended? 1424 1425 @return: Returns C{True} if this X2Go (terminal) session is in suspended state, 1426 C{False} otherwise. 1427 @rtype: C{bool} 1428 1429 """ 1430 return self.session_info.is_suspended()
1431
1432 - def is_connected(self):
1433 """\ 1434 X2Go session connected? 1435 1436 @return: Returns C{True} if this X2Go session's Paramiko/SSH transport is 1437 connected/authenticated, C{False} else. 1438 @rtype: C{bool} 1439 1440 """ 1441 return self.control_session.is_connected()
1442
1443 - def start(self):
1444 """\ 1445 Start a new X2Go session. 1446 1447 @return: C{True} if session startup has been successful and the X2Go proxy is up-and-running 1448 @rtype: C{bool} 1449 1450 @raise X2GoTerminalSessionException: if the session startup failed 1451 @raise X2GoDesktopSharingDenied: if desktop sharing fails because of denial by the user running the desktop to be shared 1452 1453 """ 1454 self.params.rewrite_session_type() 1455 1456 if not self.has_command(_rewrite_cmd(self.params.cmd, params=self.params)): 1457 if self.client_instance: 1458 self.client_instance.HOOK_no_such_command(profile_name=self.profile_name, session_name=self.session_info.name, cmd=self.params.cmd) 1459 return False 1460 1461 setkbd = "0" 1462 if self.params.kbtype != "null/null": 1463 setkbd = "1" 1464 1465 if '/' in self.params.cmd: 1466 self.params.cmd = os.path.basename(self.params.cmd) 1467 1468 self.params.rewrite_session_type() 1469 1470 if self.params.geometry == 'maximize': 1471 _geometry = utils.get_workarea_geometry() 1472 if _geometry is None or len(_geometry) != 2: 1473 _geometry = utils.get_desktop_geometry() 1474 if _geometry and len(_geometry) == 2: 1475 self.params.geometry = "%sx%s" % _geometry 1476 else: 1477 self.logger('failed to detect best maximized geometry of your client-side desktop', loglevel=log.loglevel_WARN) 1478 self.params.geometry = "1024x768" 1479 1480 cmd_line = [ "x2gostartagent", 1481 str(self.params.geometry), 1482 str(self.params.link), 1483 str(self.params.pack), 1484 str(self.params.cache_type+'-depth_'+self.params.depth), 1485 str(self.params.kblayout), 1486 str(self.params.kbtype), 1487 str(setkbd), 1488 str(self.params.session_type), 1489 str(self.params.cmd), 1490 str(self.params.clipboard), 1491 ] 1492 1493 if self.params.cmd == 'XDMCP' and self.params.xdmcp_server: 1494 cmd_line = ['X2GOXDMCP=%s' % self.params.xdmcp_server] + cmd_line 1495 1496 if self.params.dpi: 1497 cmd_line = ['X2GODPI=%s' % self.params.dpi] + cmd_line 1498 1499 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) 1500 1501 _stdout = stdout.read() 1502 _stderr = stderr.read() 1503 1504 # if the first line of stdout is a "DEN(Y)" string then we will presume that 1505 # we tried to use X2Go desktop sharing and the sharing was rejected 1506 if "ACCESS DENIED" in _stderr and "XSHAD" in _stderr: 1507 raise x2go_exceptions.X2GoDesktopSharingDenied('X2Go desktop sharing has been denied by the remote user') 1508 1509 try: 1510 self.session_info.initialize(_stdout, 1511 username=self.control_session.remote_username(), 1512 hostname=self.control_session.remote_peername(), 1513 ) 1514 except ValueError: 1515 raise x2go_exceptions.X2GoTerminalSessionException("failed to start X2Go session") 1516 except IndexError: 1517 raise x2go_exceptions.X2GoTerminalSessionException("failed to start X2Go session") 1518 1519 # local path may be a Windows path, so we use the path separator of the local system 1520 self.session_info.local_container = os.path.join(self.params.rootdir, 'S-%s' % self.session_info.name) 1521 # remote path is always a UniX path... 1522 self.session_info.remote_container = '%s/.x2go/C-%s' % (self.control_session._x2go_remote_home, 1523 self.session_info.name, 1524 ) 1525 1526 # set up SSH tunnel for X11 graphical elements 1527 self.proxy = self.proxy_backend(session_info=self.session_info, 1528 ssh_transport=self.control_session.get_transport(), 1529 sessions_rootdir=self.sessions_rootdir, 1530 session_instance=self.session_instance, 1531 proxy_options=self.proxy_options, 1532 logger=self.logger) 1533 self.proxy_subprocess, proxy_ok = self.proxy.start_proxy() 1534 1535 if proxy_ok: 1536 self.active_threads.append(self.proxy) 1537 1538 if self.params.session_type in ('D', 'S'): 1539 self.find_session_window() 1540 self.auto_session_window_title() 1541 self.raise_session_window() 1542 1543 if self.params.published_applications: 1544 self.control_session.get_published_applications() 1545 1546 else: 1547 raise x2go_exceptions.X2GoTerminalSessionException("failed to start X2Go session") 1548 1549 return proxy_ok
1550
1551 - def resume(self):
1552 """\ 1553 Resume a running/suspended X2Go session. 1554 1555 @return: C{True} if the session could successfully be resumed 1556 @rtype: C{bool} 1557 1558 @raise X2GoTerminalSessionException: if the terminal session failed to update server-side reported port changes 1559 1560 """ 1561 setkbd = "0" 1562 if self.params.kbtype != "null/null": 1563 setkbd = "1" 1564 1565 if self.params.geometry == 'maximize': 1566 _geometry = utils.get_workarea_geometry() 1567 if _geometry is None or len(_geometry) != 2: 1568 _geometry = utils.get_desktop_geometry() 1569 if _geometry and len(_geometry) == 2: 1570 self.params.geometry = "%sx%s" % _geometry 1571 else: 1572 self.logger('failed to detect best maxmimized geometry of your client-side desktop, using 1024x768 instead', loglevel=log.loglevel_WARN) 1573 self.params.geometry = "1024x768" 1574 1575 cmd_line = [ "x2goresume-session", self.session_info.name, 1576 self.params.geometry, 1577 self.params.link, 1578 self.params.pack, 1579 self.params.kblayout, 1580 self.params.kbtype, 1581 setkbd, 1582 self.params.clipboard, 1583 ] 1584 1585 (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) 1586 1587 # re-allocate (if needed) server-side ports for graphics, sound and sshfs 1588 for stdout_line in stdout.read(): 1589 try: 1590 _new_value = stdout_line.split("=")[1].strip() 1591 if 'gr_port=' in stdout_line and _new_value != str(self.session_info.graphics_port): 1592 try: 1593 self.session_info.graphics_port = int(_new_value) 1594 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) 1595 except TypeError: 1596 # if the re-allocation fails, this is fatal!!! 1597 raise x2go_exceptions.X2GoTerminalSessionException('Failed to retrieve new graphics port from server. X2Go Session cannot be resumed.') 1598 elif 'sound_port=' in stdout_line and _new_value != str(self.session_info.snd_port): 1599 try: 1600 self.session_info.snd_port = int(_new_value) 1601 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) 1602 except TypeError: 1603 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) 1604 elif 'fs_port=' in stdout_line and _new_value != str(self.session_info.sshfs_port): 1605 try: 1606 self.session_info.sshfs_port = int(_new_value) 1607 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) 1608 except TypeError: 1609 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) 1610 except IndexError: 1611 continue 1612 1613 # local path may be a Windows path, so we use the path separator of the local system 1614 self.session_info.local_container = os.path.join(self.params.rootdir, 'S-%s' % self.session_info.name) 1615 # remote path is always a UniX path... 1616 self.session_info.remote_container = '%s/.x2go/C-%s' % (self.control_session._x2go_remote_home, 1617 self.session_info.name, 1618 ) 1619 self.proxy = self.proxy_backend(session_info=self.session_info, 1620 ssh_transport=self.control_session.get_transport(), 1621 sessions_rootdir=self.sessions_rootdir, 1622 session_instance=self.session_instance, 1623 proxy_options=self.proxy_options, 1624 logger=self.logger 1625 ) 1626 self.proxy_subprocess, proxy_ok = self.proxy.start_proxy() 1627 1628 if proxy_ok: 1629 self.params.depth = self.session_info.name.split('_')[2][2:] 1630 1631 # on a session resume the user name comes in as a user ID. We have to translate this... 1632 self.session_info.username = self.control_session.remote_username() 1633 1634 if self.params.kbtype not in ('null/null', 'auto') and (self.params.kblayout not in ('null', '') or self.params.kbvariant not in ('null', '')): 1635 self.set_keyboard(layout=self.params.kblayout, variant=self.params.kbvariant) 1636 1637 if self.params.session_type in ('D', 'S'): 1638 self.find_session_window() 1639 self.auto_session_window_title() 1640 self.raise_session_window() 1641 1642 if self.is_published_applications_provider(): 1643 self.control_session.get_published_applications() 1644 self.published_applications = True 1645 else: 1646 raise x2go_exceptions.X2GoTerminalSessionException("failed to start X2Go session") 1647 1648 return proxy_ok
1649
1650 - def suspend(self):
1651 """\ 1652 Suspend this X2Go (terminal) session. 1653 1654 @return: C{True} if the session terminal could be successfully suspended 1655 @rtype: C{bool} 1656 1657 """ 1658 self.release_telekinesis() 1659 self.control_session.suspend(session_name=self.session_info.name) 1660 self.release_proxy() 1661 1662 # TODO: check if session has really suspended 1663 _ret = True 1664 1665 return _ret
1666
1667 - def terminate(self):
1668 """\ 1669 Terminate this X2Go (terminal) session. 1670 1671 @return: C{True} if the session could be successfully terminated 1672 @rtype: C{bool} 1673 1674 """ 1675 self.release_telekinesis() 1676 self.control_session.terminate(session_name=self.session_info.name, destroy_terminals=False) 1677 self.release_proxy() 1678 self.post_terminate_cleanup() 1679 self.__del__() 1680 1681 # TODO: check if session has really suspended 1682 _ret = True 1683 1684 return _ret
1685
1686 - def release_proxy(self):
1687 """\ 1688 Let the X2Go proxy command cleanly die away... (by calling its destructor). 1689 1690 """ 1691 if self.proxy is not None: 1692 self.proxy.__del__() 1693 self.proxy = None
1694
1695 - def release_telekinesis(self):
1696 """\ 1697 Let the attached Telekinesis client cleanly die away... (by calling its destructor). 1698 1699 """ 1700 if self.telekinesis_client is not None: 1701 self.telekinesis_client.__del__() 1702 self.telekinesis_client = None
1703
1704 - def post_terminate_cleanup(self):
1705 """\ 1706 Do some cleanup after this session has terminated. 1707 1708 """ 1709 # this method might be called twice (directly and from update_status in the session 1710 # registry instance. So we have to make sure, that this code will not fail 1711 # if called twice. 1712 if not self._cleaned_up and self.session_info.name: 1713 1714 # otherwise we wipe the session files locally 1715 self.logger('cleaning up session %s after termination' % self.session_info, loglevel=log.loglevel_NOTICE) 1716 1717 # if we run in debug mode, we keep local session directories 1718 if self.logger.get_loglevel() & log.loglevel_DEBUG != log.loglevel_DEBUG: 1719 1720 self._rm_session_dirtree() 1721 self._rm_desktop_dirtree() 1722 1723 self._cleaned_up = True
1724
1725 - def is_rootless_session(self):
1726 """\ 1727 Test if this terminal session is a rootless session. 1728 1729 @return: C{True} if this session is of session type rootless ('R'). 1730 @rtype: C{bool} 1731 1732 """ 1733 self.params.rewrite_session_type() 1734 return self.params.session_type == 'R'
1735
1736 - def is_shadow_session(self):
1737 """\ 1738 Test if this terminal session is a desktop sharing (aka shadow) session. 1739 1740 @return: C{True} if this session is of session type shadow ('S'). 1741 @rtype: C{bool} 1742 1743 """ 1744 self.params.rewrite_session_type() 1745 return self.params.session_type == 'S'
1746
1747 - def is_pubapp_session(self):
1748 """\ 1749 Test if this terminal session is a published applications session. 1750 1751 @return: C{True} if this session is of session type published applications ('P'). 1752 @rtype: C{bool} 1753 1754 """ 1755 self.params.rewrite_session_type() 1756 return self.params.session_type == 'P'
1757