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-2015 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_WARN)
     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              if _stdout[0]:
     939                  self.logger('x2gomountdirs stdout is: %s' % _stdout, log.loglevel_NOTICE)
     940              _stderr = stderr.read().split('\n')
     941              if _stderr[0]:
     942                  self.logger('x2gomountdirs stderr is: %s' % _stderr, log.loglevel_WARN)
     943
     944          except:
     945              self._share_local_folder_lock.release()
     946              raise
     947          self._share_local_folder_lock.release()
     948
     949          if len(_stdout) >= 6 and _stdout[5].endswith('ok'):
     950              return True
     951          return False
     952
     953 -    def unshare_all_local_folders(self):
     954          """\
     955          Unshare all local folders mount in the X2Go session.
     956
     957          @return: returns C{True} if all local folders could be successfully unmounted from the X2Go server session
     958          @rtype: C{bool}
     959
     960          """
     961          self.logger('unsharing all local folders from session %s' % self.session_info, log.loglevel_INFO)
     962
     963          cmd_line = [ 'export HOSTNAME &&',
     964                       'x2goumount-session',
     965                       self.session_info.name,
     966                     ]
     967
     968          (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line)
     969          if not stderr.read():
     970              self.logger('x2goumount-session (all mounts) for session %s has been successful' % self.session_info, log.loglevel_NOTICE)
     971              return True
     972          else:
     973              self.logger('x2goumount-session (all mounts) for session %s failed' % self.session_info, log.loglevel_ERROR)
     974              return False
     975
     976 -    def unshare_local_folder(self, local_path):
     977          """\
     978          Unshare local folder given as <local_path> from X2Go session.
     979
     980          @return: returns C{True} if the local folder <local_path> could be successfully unmounted from the X2Go server session
     981          @rtype: C{bool}
     982
     983          """
     984          self.logger('unsharing local folder from session %s' % self.session_info, log.loglevel_INFO)
     985
     986          cmd_line = [ 'export HOSTNAME &&',
     987                       'x2goumount-session',
     988                       self.session_info.name,
     989                       "'%s'" % local_path,
     990                     ]
     991
     992          (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line)
     993          if not stderr.read():
     994              self.logger('x2goumount-session (%s) for session %s has been successful' % (local_path, self.session_info, ), log.loglevel_NOTICE)
     995              return True
     996          else:
     997              self.logger('x2goumount-session (%s) for session %s failed' % (local_path, self.session_info, ), log.loglevel_ERROR)
     998              return False
     999
    1000 -    def color_depth(self):
    1001          """\
    1002          Retrieve the session's color depth.
    1003
    1004          @return: the session's color depth
    1005          @rtype: C{int}
    1006
    1007          """
    1008          return self.params.depth
    1009
    1010 -    def auto_session_window_title(self, dont_set=False):
    1011          """\
    1012          Automatically generate an appropriate human-readable session window title.
    1013
    1014          The session window title will be provider in the C{session_title} property of
    1015          this method.
    1016
    1017          @param dont_set: generate the session window title, but do not actually set it
    1018          @type dont_set: C{bool}
    1019
    1020          """
    1021          _generic_title = 'X2GO-%s' % self.session_info.name
    1022
    1023          # no blanks at beginning or end, no blanks-only...
    1024          self.session_title = self.session_title.strip()
    1025
    1026          if self.params.session_type == 'D':
    1027              if self.set_session_title:
    1028
    1029                  if not self.session_title:
    1030                      self.session_title = '%s for %s@%s' % (self.params.cmd, self.control_session.remote_username(), self.control_session.get_hostname())
    1031
    1032              else:
    1033                  # session title fallback... (like X2Go server does it...)
    1034                  self.session_title = _generic_title
    1035
    1036          elif self.params.session_type == 'S':
    1037              if self.set_session_title:
    1038
    1039                  shared_user = _generic_title.split('XSHAD')[1]
    1040                  shared_display = _generic_title.split('XSHAD')[2].replace('PP', ':').split("_")[0]
    1041
    1042                  self.session_title = 'Desktop %s@%s shared with %s@%s' % (shared_user, shared_display, self.control_session.remote_username(), self.control_session.get_hostname())
    1043
    1044              else:
    1045                  # session title fallback... (like X2Go server does it...)
    1046                  self.session_title = _generic_title
    1047
    1048          else:
    1049              # do nothing for rootless sessions
    1050              self.session_title = _generic_title
    1051
    1052          if self.session_title != _generic_title and not dont_set:
    1053              self.set_session_window_title(title=self.session_title)
    1054
    1055 -    def find_session_window(self, timeout=60):
    1056          """\
    1057          Try for <timeout> seconds to find the X2Go session window of this
    1058          terminal session.
    1059
    1060          A background thread will get spawned for this operation.
    1061
    1062          @param timeout: try for <timeout> seconds to find the session window
    1063          @type timeout: C{int}
    1064
    1065          """
    1066          gevent.spawn(self._find_session_window, timeout=timeout)
    1067
    1068 -    def _find_session_window(self, timeout=0):
    1069          """\
    1070          Try for <timeout> seconds to find the X2Go session window of this
    1071          terminal session.
    1072
    1073          @param timeout: try for <timeout> seconds to find the session window
    1074          @type timeout: C{int}
    1075
    1076          """
    1077          self.session_window = None
    1078
    1079          # search for the window of our focus, do this in a loop till the window as been found
    1080          # or timeout forces us to give up...
    1081          timeout += 1
    1082          while timeout:
    1083
    1084              timeout -= 1
    1085
    1086              window = utils.find_session_window(self.session_info.name)
    1087
    1088              if window is not None:
    1089                  if _X2GOCLIENT_OS == "Windows":
    1090                      self.logger('Session window handle for session %s is: %s' % (self.session_info.name, window), loglevel=log.loglevel_DEBUG)
    1091                  else:
    1092                      self.logger('Session window ID for session %s is: %s' % (self.session_info.name, window.id), loglevel=log.loglevel_DEBUG)
    1093                  self.session_window = window
    1094
    1095                  self.update_session_window_file()
    1096                  break
    1097
    1098              gevent.sleep(1)
    1099
    1100 -    def update_session_window_file(self):
    1101          """\
    1102          Create a file that contains information on the session window.
    1103          .
    1104          If the file already exists, its content gets update.
    1105
    1106          """
    1107          session_window_file = os.path.join(self.session_info.local_container, 'session.window')
    1108          if self.session_window is not None:
    1109              f = open(session_window_file,'w')
    1110              if _X2GOCLIENT_OS != "Windows":
    1111                  _id = self.session_window.id
    1112              else:
    1113                  _id = self.session_window
    1114              f.write('ID:{window_id}\n'.format(window_id=_id))
    1115              f.close()
    1116              self.logger('Updating session.window file %s: Window-ID->%s' % (session_window_file, _id), loglevel=log.loglevel_DEBUG)
    1117          else:
    1118              try:
    1119                  os.remove(session_window_file)
    1120              except OSError,e:
    1121                  # this is no error in most cases...
    1122                  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)
    1123
    1124 -    def set_session_window_title(self, title, timeout=60):
    1125          """\
    1126          Modify the session window title.
    1127
    1128          A background thread will get spawned for this operation.
    1129
    1130          @param title: new title for the terminal session's session window
    1131          @type title: C{str}
    1132          @param timeout: try for <timeout> seconds to find the session window
    1133          @type timeout: C{int}
    1134
    1135          """
    1136          gevent.spawn(self._set_session_window_title, title=title.strip(), timeout=timeout)
    1137
    1138 -    def _set_session_window_title(self, title, timeout=0):
    1139          """\
    1140          Modify the session window title.
    1141
    1142          @param title: new title for the terminal session's session window
    1143          @type title: C{str}
    1144          @param timeout: try for <timeout> seconds to find the session window
    1145          @type timeout: C{int}
    1146
    1147          """
    1148          self.session_title = title
    1149
    1150          if not self.session_title:
    1151              self.auto_session_title(dont_set=True)
    1152
    1153          timeout += 1
    1154          while timeout:
    1155
    1156              timeout -= 1
    1157
    1158              if self.session_window is not None:
    1159                  self.logger('Setting session window title for session %s is: %s' % (self.session_info.name, self.session_title), loglevel=log.loglevel_DEBUG)
    1160                  utils.set_session_window_title(self.session_window, self.session_title)
    1161                  break
    1162
    1163              gevent.sleep(1)
    1164
    1165 -    def raise_session_window(self, timeout=60):
    1166          """\
    1167          Try for <timeout> seconds to raise the X2Go session window of this
    1168          terminal session to the top and bring it to focus.
    1169
    1170          A background thread will get spawned for this operation.
    1171
    1172          @param timeout: try for <timeout> seconds to raise the session window
    1173          @type timeout: C{int}
    1174
    1175          """
    1176          gevent.spawn(self._raise_session_window, timeout=timeout)
    1177
    1178 -    def _raise_session_window(self, timeout=0):
    1179          """
    1180          Try for <timeout> seconds to raise the X2Go session window of this
    1181          terminal session to the top and bring it to focus.
    1182
    1183          @param timeout: try for <timeout> seconds to raise the session window
    1184          @type timeout: C{int}
    1185
    1186          """
    1187          timeout += 1
    1188          while timeout:
    1189
    1190              timeout -= 1
    1191
    1192              if self.session_window is not None:
    1193
    1194                  utils.raise_session_window(self.session_window)
    1195                  break
    1196
    1197              gevent.sleep(1)
    1198
    1199 -    def has_command(self, cmd):
    1200          """\
    1201          ,,Guess'' if the command C{<cmd>} exists on the X2Go server and is executable.
    1202          The expected result is not 100% safe, however, it comes with a high probability to
    1203          be correct.
    1204
    1205          @param cmd: session command
    1206          @type cmd: C{str}
    1207
    1208          @return: C{True} if this method reckons that the command is executable on the remote X2Go server
    1209          @rtype: C{bool}
    1210
    1211          """
    1212          test_cmd = None;
    1213
    1214          cmd = cmd.strip('"').strip('"')
    1215          if cmd.find('RDP') != -1:
    1216              cmd = 'rdesktop'
    1217
    1218          if cmd in _X2GO_GENERIC_APPLICATIONS:
    1219              return True
    1220          if cmd in _X2GO_DESKTOPSESSIONS.keys():
    1221              return True
    1222          elif 'XSHAD' in cmd:
    1223              return True
    1224          elif 'PUBLISHED' in cmd and 'X2GO_PUBLISHED_APPLICATIONS' in self.control_session.get_server_features():
    1225              return True
    1226          elif cmd and cmd.startswith('/'):
    1227              # check if full path is correct _and_ if application is in server path
    1228              test_cmd = 'test -x %s && which %s && echo OK' % (cmd, os.path.basename(cmd.split()[0]))
    1229          elif cmd and '/' not in cmd.split()[0]:
    1230              # check if application is in server path only
    1231              test_cmd = 'which %s && echo OK' % os.path.basename(cmd.split()[0])
    1232
    1233          if test_cmd:
    1234              (stdin, stdout, stderr) = self.control_session._x2go_exec_command([test_cmd])
    1235              _stdout = stdout.read()
    1236              return _stdout.find('OK') != -1
    1237          else:
    1238              return False
    1239
    1240 -    def run_command(self, cmd=None, env={}):
    1241          """\
    1242          Run a command in this session.
    1243
    1244          After L{x2go.backends.terminal.plain.X2GoTerminalSession.start()} has been called
    1245          one or more commands can be executed with L{x2go.backends.terminal.plain.X2GoTerminalSession.run_command()}
    1246          within the current X2Go session.
    1247
    1248          @param cmd: Command to be run
    1249          @type cmd: C{str}
    1250          @param env: add server-side environment variables
    1251          @type env: C{dict}
    1252
    1253          @return: stdout.read() and stderr.read() as returned by the run command
    1254              on the X2Go server
    1255          @rtype: C{tuple} of C{str}
    1256
    1257          """
    1258          if not self.has_command(_rewrite_cmd(str(self.params.cmd), params=self.params)):
    1259              if self.client_instance:
    1260                  self.client_instance.HOOK_no_such_command(profile_name=self.profile_name, session_name=self.session_info.name, cmd=self.params.cmd)
    1261              return False
    1262
    1263          if cmd in ("", None):
    1264              if self.params.cmd is None:
    1265                  cmd = 'TERMINAL'
    1266              else:
    1267                  cmd = self.params.cmd
    1268
    1269          if cmd == 'XDMCP':
    1270              # do not run command when in XDMCP mode...
    1271              return None
    1272
    1273          if 'XSHAD' in cmd:
    1274              # do not run command when in DESKTOP SHARING mode...
    1275              return None
    1276
    1277          self.params.update(cmd=cmd)
    1278
    1279          # do not allow the execution of full path names
    1280          if '/' in cmd:
    1281              cmd = os.path.basename(cmd)
    1282
    1283          cmd_line = [ "setsid x2goruncommand",
    1284                       str(self.session_info.display),
    1285                       str(self.session_info.agent_pid),
    1286                       str(self.session_info.name),
    1287                       str(self.session_info.snd_port),
    1288                       _rewrite_blanks(_rewrite_cmd(cmd, params=self.params)),
    1289                       str(self.params.snd_system),
    1290                       str(self.params.session_type),
    1291                       "1>/dev/null 2>/dev/null & exit",
    1292                     ]
    1293
    1294          if self.params.snd_system == 'pulse':
    1295              cmd_line = [ 'PULSE_CLIENTCONFIG=%s/.pulse-client.conf' % self.session_info.remote_container ] + cmd_line
    1296
    1297          if env:
    1298              for env_var in env.keys():
    1299                  cmd_line = [ '%s=%s' % (env_var, env[env_var]) ] + cmd_line
    1300
    1301          (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line)
    1302
    1303          if self.params.kbtype not in ('null/null', 'auto') and (self.params.kblayout not in ('null', '') or self.params.kbvariant not in ('null', '')):
    1304              self.set_keyboard(layout=self.params.kblayout, variant=self.params.kbvariant)
    1305
    1306          return stdout.read(), stderr.read()
    1307
    1308 -    def is_desktop_session(self):
    1309          """\
    1310          Is this (terminal) session a desktop session?
    1311
    1312          @return: Returns C{True} is this session is a desktop session.
    1313          @rtype: C{bool}
    1314
    1315          """
    1316          if self.session_info:
    1317              return self.session_info.is_desktop_session()
    1318          return False
    1319
    1320 -    def is_published_applications_provider(self):
    1321          """\
    1322          Is this (terminal) session a published applications provider?
    1323
    1324          @return: Returns C{True} is this session is a provider session for published applications.
    1325          @rtype: C{bool}
    1326
    1327          """
    1328          if self.session_info and self.is_running():
    1329              return self.session_info.is_published_applications_provider()
    1330          return False
    1331
    1332 -    def set_keyboard(self, layout='null', variant='null'):
    1333          """\
    1334          Set the keyboard layout and variant for this (running) session.
    1335
    1336          @param layout: keyboard layout to be set
    1337          @type layout: C{str}
    1338          @param variant: keyboard variant to be set
    1339          @type variant: C{str}
    1340
    1341          @return: returns C{True} if the {setxkbmap} command could be executed successfully.
    1342          @rtype: C{bool}
    1343
    1344          """
    1345          if not self.is_running():
    1346              return False
    1347
    1348          cmd_line = [ 'export DISPLAY=:%s && ' % str(self.session_info.display),
    1349                       'setxkbmap '
    1350                     ]
    1351
    1352          if layout != 'null':
    1353              self.logger('setting keyboad layout ,,%s\'\' for session %s' % (layout, self.session_info), log.loglevel_INFO)
    1354              cmd_line.append('-layout %s' % layout)
    1355          if variant != 'null':
    1356              self.logger('setting keyboad variant ,,%s\'\' for session %s' % (variant, self.session_info), log.loglevel_INFO)
    1357              cmd_line.append('-variant %s' % variant)
    1358
    1359          (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line)
    1360          _stderr = stderr.read()
    1361          if not _stderr:
    1362              self.logger('setting keyboard layout ,,%s\'\' and variant ,,%s\'\' for session %s has been successful' % (layout, variant, self.session_info), log.loglevel_NOTICE)
    1363              return True
    1364          else:
    1365              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)
    1366              return False
    1367
    1368 -    def exec_published_application(self, exec_name, timeout=20, env={}):
    1369          """\
    1370          Executed a published application.
    1371
    1372          @param exec_name: application to be executed
    1373          @type exec_name: C{str}
    1374          @param timeout: execution timeout
    1375          @type timeout: C{int}
    1376          @param env: session environment dictionary
    1377          @type env: C{dict}
    1378
    1379          """
    1380          cmd_line = [
    1381              "export DISPLAY=:%s && " % str(self.session_info.display),
    1382              "export X2GO_SESSION=%s && " % str(self.get_session_name()),
    1383          ]
    1384
    1385          if self.params.snd_system == 'pulse':
    1386              cmd_line.append("export PULSE_CLIENTCONFIG=%s/.pulse-client.conf && " % self.session_info.remote_container)
    1387
    1388          if env:
    1389              for env_var in env.keys():
    1390                  cmd_line = [ 'export %s=%s && ' % (env_var, env[env_var]) ] + cmd_line
    1391
    1392          cmd_line.extend(
    1393              [
    1394                  "setsid %s" % exec_name,
    1395                  "1>/dev/null 2>/dev/null & exit",
    1396              ]
    1397          )
    1398
    1399          self.logger('executing published application %s for %s with command line: %s' % (exec_name, self.profile_name, cmd_line), loglevel=log.loglevel_DEBUG)
    1400          (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line, timeout=timeout)
    1401
    1402 -    def ok(self):
    1403          """\
    1404          X2Go session OK?
    1405
    1406          @return: Returns C{True} if this X2Go (terminal) session is up and running,
    1407              C{False} otherwise.
    1408          @rtype: C{bool}
    1409
    1410          """
    1411          _ok = bool(self.session_info.name and self.proxy.ok())
    1412          return _ok
    1413
    1414 -    def is_running(self):
    1415          """\
    1416          X2Go session running?
    1417
    1418          @return: Returns C{True} if this X2Go (terminal) session is in running state,
    1419              C{False} otherwise.
    1420          @rtype: C{bool}
    1421
    1422          """
    1423          return self.session_info.is_running()
    1424
    1425 -    def is_suspended(self):
    1426          """\
    1427          X2Go session suspended?
    1428
    1429          @return: Returns C{True} if this X2Go (terminal) session is in suspended state,
    1430              C{False} otherwise.
    1431          @rtype: C{bool}
    1432
    1433          """
    1434          return self.session_info.is_suspended()
    1435
    1436 -    def is_connected(self):
    1437          """\
    1438          X2Go session connected?
    1439
    1440          @return: Returns C{True} if this X2Go session's Paramiko/SSH transport is
    1441              connected/authenticated, C{False} else.
    1442          @rtype: C{bool}
    1443
    1444          """
    1445          return self.control_session.is_connected()
    1446
    1447 -    def start(self):
    1448          """\
    1449          Start a new X2Go session.
    1450
    1451          @return: C{True} if session startup has been successful and the X2Go proxy is up-and-running
    1452          @rtype: C{bool}
    1453
    1454          @raise X2GoTerminalSessionException: if the session startup failed
    1455          @raise X2GoDesktopSharingDenied: if desktop sharing fails because of denial by the user running the desktop to be shared
    1456
    1457          """
    1458          self.params.rewrite_session_type()
    1459
    1460          if not self.has_command(_rewrite_cmd(self.params.cmd, params=self.params)):
    1461              if self.client_instance:
    1462                  self.client_instance.HOOK_no_such_command(profile_name=self.profile_name, session_name=self.session_info.name, cmd=self.params.cmd)
    1463              return False
    1464
    1465          setkbd = "0"
    1466          if self.params.kbtype != "null/null":
    1467              setkbd = "1"
    1468
    1469          if '/' in self.params.cmd:
    1470              self.params.cmd = os.path.basename(self.params.cmd)
    1471
    1472          self.params.rewrite_session_type()
    1473
    1474          if self.params.geometry == 'maximize':
    1475              _geometry = utils.get_workarea_geometry()
    1476              if _geometry is None or len(_geometry) != 2:
    1477                  _geometry = utils.get_desktop_geometry()
    1478              if _geometry and len(_geometry) == 2:
    1479                  self.params.geometry = "%sx%s" % _geometry
    1480              else:
    1481                  self.logger('failed to detect best maximized geometry of your client-side desktop', loglevel=log.loglevel_WARN)
    1482                  self.params.geometry = "1024x768"
    1483
    1484          cmd_line = [ "x2gostartagent",
    1485                       str(self.params.geometry),
    1486                       str(self.params.link),
    1487                       str(self.params.pack),
    1488                       str(self.params.cache_type+'-depth_'+self.params.depth),
    1489                       str(self.params.kblayout),
    1490                       str(self.params.kbtype),
    1491                       str(setkbd),
    1492                       str(self.params.session_type),
    1493                       str(self.params.cmd),
    1494                     ]
    1495          if self.params.session_type != 'S':
    1496              cmd_line.append(
    1497                       str(self.params.clipboard),
    1498              )
    1499
    1500          if self.params.cmd == 'XDMCP' and self.params.xdmcp_server:
    1501              cmd_line = ['X2GOXDMCP=%s' % self.params.xdmcp_server] + cmd_line
    1502
    1503          if self.params.dpi:
    1504              cmd_line = ['X2GODPI=%s' % self.params.dpi] + cmd_line
    1505
    1506          (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line)
    1507
    1508          _stdout = stdout.read()
    1509          _stderr = stderr.read()
    1510
    1511          # if the first line of stdout is a "DEN(Y)" string then we will presume that
    1512          # we tried to use X2Go desktop sharing and the sharing was rejected
    1513          if "ACCESS DENIED" in _stderr and "XSHAD" in _stderr:
    1514              raise x2go_exceptions.X2GoDesktopSharingDenied('X2Go desktop sharing has been denied by the remote user')
    1515
    1516          try:
    1517              self.session_info.initialize(_stdout,
    1518                                           username=self.control_session.remote_username(),
    1519                                           hostname=self.control_session.remote_peername(),
    1520                                          )
    1521          except ValueError:
    1522              raise x2go_exceptions.X2GoTerminalSessionException("failed to start X2Go session")
    1523          except IndexError:
    1524              raise x2go_exceptions.X2GoTerminalSessionException("failed to start X2Go session")
    1525
    1526          # local path may be a Windows path, so we use the path separator of the local system
    1527          self.session_info.local_container = os.path.join(self.params.rootdir, 'S-%s' % self.session_info.name)
    1528          # remote path is always a UniX path...
    1529          self.session_info.remote_container = '%s/.x2go/C-%s' % (self.control_session._x2go_remote_home,
    1530                                                                  self.session_info.name,
    1531                                                                 )
    1532
    1533          # set up SSH tunnel for X11 graphical elements
    1534          self.proxy = self.proxy_backend(session_info=self.session_info,
    1535                                          ssh_transport=self.control_session.get_transport(),
    1536                                          sessions_rootdir=self.sessions_rootdir,
    1537                                          session_instance=self.session_instance,
    1538                                          proxy_options=self.proxy_options,
    1539                                          logger=self.logger)
    1540          self.proxy_subprocess, proxy_ok = self.proxy.start_proxy()
    1541
    1542          if proxy_ok:
    1543              self.active_threads.append(self.proxy)
    1544
    1545              if self.params.session_type in ('D', 'S'):
    1546                  self.find_session_window()
    1547                  self.auto_session_window_title()
    1548                  self.raise_session_window()
    1549
    1550              if self.params.published_applications:
    1551                  self.control_session.get_published_applications()
    1552
    1553          else:
    1554              raise x2go_exceptions.X2GoTerminalSessionException("failed to start X2Go session")
    1555
    1556          return proxy_ok
    1557
    1558 -    def resume(self):
    1559          """\
    1560          Resume a running/suspended X2Go session.
    1561
    1562          @return: C{True} if the session could successfully be resumed
    1563          @rtype: C{bool}
    1564
    1565          @raise X2GoTerminalSessionException: if the terminal session failed to update server-side reported port changes
    1566
    1567          """
    1568          setkbd = "0"
    1569          if self.params.kbtype != "null/null":
    1570              setkbd = "1"
    1571
    1572          if self.params.geometry == 'maximize':
    1573              _geometry = utils.get_workarea_geometry()
    1574              if _geometry is None or len(_geometry) != 2:
    1575                  _geometry = utils.get_desktop_geometry()
    1576              if _geometry and len(_geometry) == 2:
    1577                  self.params.geometry = "%sx%s" % _geometry
    1578              else:
    1579                  self.logger('failed to detect best maxmimized geometry of your client-side desktop, using 1024x768 instead', loglevel=log.loglevel_WARN)
    1580                  self.params.geometry = "1024x768"
    1581
    1582          cmd_line = [ "x2goresume-session", self.session_info.name,
    1583                       self.params.geometry,
    1584                       self.params.link,
    1585                       self.params.pack,
    1586                       self.params.kblayout,
    1587                       self.params.kbtype,
    1588                       setkbd,
    1589                       self.params.clipboard,
    1590                     ]
    1591
    1592          (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line)
    1593
    1594          # re-allocate (if needed) server-side ports for graphics, sound and sshfs
    1595          for stdout_line in stdout.read():
    1596              try:
    1597                  _new_value = stdout_line.split("=")[1].strip()
    1598                  if 'gr_port=' in stdout_line and _new_value != str(self.session_info.graphics_port):
    1599                      try:
    1600                          self.session_info.graphics_port = int(_new_value)
    1601                          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)
    1602                      except TypeError:
    1603                          # if the re-allocation fails, this is fatal!!!
    1604                          raise x2go_exceptions.X2GoTerminalSessionException('Failed to retrieve new graphics port from server. X2Go Session cannot be resumed.')
    1605                  elif 'sound_port=' in stdout_line and _new_value != str(self.session_info.snd_port):
    1606                      try:
    1607                          self.session_info.snd_port = int(_new_value)
    1608                          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)
    1609                      except TypeError:
    1610                          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)
    1611                  elif 'fs_port=' in stdout_line and _new_value != str(self.session_info.sshfs_port):
    1612                      try:
    1613                          self.session_info.sshfs_port = int(_new_value)
    1614                          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)
    1615                      except TypeError:
    1616                          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)
    1617              except IndexError:
    1618                  continue
    1619
    1620          # local path may be a Windows path, so we use the path separator of the local system
    1621          self.session_info.local_container = os.path.join(self.params.rootdir, 'S-%s' % self.session_info.name)
    1622          # remote path is always a UniX path...
    1623          self.session_info.remote_container = '%s/.x2go/C-%s' % (self.control_session._x2go_remote_home,
    1624                                                                  self.session_info.name,
    1625                                                                 )
    1626          self.proxy = self.proxy_backend(session_info=self.session_info,
    1627                                          ssh_transport=self.control_session.get_transport(),
    1628                                          sessions_rootdir=self.sessions_rootdir,
    1629                                          session_instance=self.session_instance,
    1630                                          proxy_options=self.proxy_options,
    1631                                          logger=self.logger
    1632                                         )
    1633          self.proxy_subprocess, proxy_ok = self.proxy.start_proxy()
    1634
    1635          if proxy_ok:
    1636              self.params.depth = self.session_info.name.split('_')[2][2:]
    1637
    1638              # on a session resume the user name comes in as a user ID. We have to translate this...
    1639              self.session_info.username = self.control_session.remote_username()
    1640
    1641              if self.params.kbtype not in ('null/null', 'auto') and (self.params.kblayout not in ('null', '') or self.params.kbvariant not in ('null', '')):
    1642                  self.set_keyboard(layout=self.params.kblayout, variant=self.params.kbvariant)
    1643
    1644              if self.params.session_type in ('D', 'S'):
    1645                  self.find_session_window()
    1646                  self.auto_session_window_title()
    1647                  self.raise_session_window()
    1648
    1649              if self.is_published_applications_provider():
    1650                  self.control_session.get_published_applications()
    1651                  self.published_applications = True
    1652          else:
    1653              raise x2go_exceptions.X2GoTerminalSessionException("failed to start X2Go session")
    1654
    1655          return proxy_ok
    1656
    1657 -    def suspend(self):
    1658          """\
    1659          Suspend this X2Go (terminal) session.
    1660
    1661          @return: C{True} if the session terminal could be successfully suspended
    1662          @rtype: C{bool}
    1663
    1664          """
    1665          self.release_telekinesis()
    1666          self.control_session.suspend(session_name=self.session_info.name)
    1667          self.release_proxy()
    1668
    1669          # TODO: check if session has really suspended
    1670          _ret = True
    1671
    1672          return _ret
    1673
    1674 -    def terminate(self):
    1675          """\
    1676          Terminate this X2Go (terminal) session.
    1677
    1678          @return: C{True} if the session could be successfully terminated
    1679          @rtype: C{bool}
    1680
    1681          """
    1682          self.release_telekinesis()
    1683          self.control_session.terminate(session_name=self.session_info.name, destroy_terminals=False)
    1684          self.release_proxy()
    1685          self.post_terminate_cleanup()
    1686          self.__del__()
    1687
    1688          # TODO: check if session has really suspended
    1689          _ret = True
    1690
    1691          return _ret
    1692
    1693 -    def release_proxy(self):
    1694          """\
    1695          Let the X2Go proxy command cleanly die away... (by calling its destructor).
    1696
    1697          """
    1698          if self.proxy is not None:
    1699              self.proxy.__del__()
    1700              self.proxy = None
    1701
    1702 -    def release_telekinesis(self):
    1703          """\
    1704          Let the attached Telekinesis client cleanly die away... (by calling its destructor).
    1705
    1706          """
    1707          if self.telekinesis_client is not None:
    1708              self.telekinesis_client.__del__()
    1709              self.telekinesis_client = None
    1710
    1711 -    def post_terminate_cleanup(self):
    1712          """\
    1713          Do some cleanup after this session has terminated.
    1714
    1715          """
    1716          # this method might be called twice (directly and from update_status in the session
    1717          # registry instance. So we have to make sure, that this code will not fail
    1718          # if called twice.
    1719          if not self._cleaned_up and self.session_info.name:
    1720
    1721              # otherwise we wipe the session files locally
    1722              self.logger('cleaning up session %s after termination' % self.session_info, loglevel=log.loglevel_NOTICE)
    1723
    1724              # if we run in debug mode, we keep local session directories
    1725              if self.logger.get_loglevel() & log.loglevel_DEBUG != log.loglevel_DEBUG:
    1726
    1727                  self._rm_session_dirtree()
    1728                  self._rm_desktop_dirtree()
    1729
    1730              self._cleaned_up = True
    1731
    1732 -    def is_rootless_session(self):
    1733          """\
    1734          Test if this terminal session is a rootless session.
    1735
    1736          @return: C{True} if this session is of session type rootless ('R').
    1737          @rtype: C{bool}
    1738
    1739          """
    1740          self.params.rewrite_session_type()
    1741          return self.params.session_type == 'R'
    1742
    1743 -    def is_shadow_session(self):
    1744          """\
    1745          Test if this terminal session is a desktop sharing (aka shadow) session.
    1746
    1747          @return: C{True} if this session is of session type shadow ('S').
    1748          @rtype: C{bool}
    1749
    1750          """
    1751          self.params.rewrite_session_type()
    1752          return self.params.session_type == 'S'
    1753
    1754 -    def is_pubapp_session(self):
    1755          """\
    1756          Test if this terminal session is a published applications session.
    1757
    1758          @return: C{True} if this session is of session type published applications ('P').
    1759          @rtype: C{bool}
    1760
    1761          """
    1762          self.params.rewrite_session_type()
    1763          return self.params.session_type == 'P'
    1764