Package x2go :: Package backends :: Package terminal :: Module _stdout

[frames] | no frames]

# Source Code for Module x2go.backends.terminal._stdout

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