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
    1321 -    def is_published_applications_provider(self):
    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