The branch, twofactorauth has been updated via 79a34563e372ff34bc23ba3d6090c4175221f2ed (commit) from 2111f148a5af2349cd426f4274561fa801bca9ff (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- ----------------------------------------------------------------------- Summary of changes: x2go/__init__.py | 2 +- {tests => x2go/backends}/__init__.py | 9 +- test.py => x2go/backends/control/__init__.py | 21 +- x2go/backends/control/stdout.py | 496 ++++++++ test.py => x2go/backends/info/__init__.py | 21 +- x2go/backends/info/stdout.py | 165 +++ test.py => x2go/backends/profiles/__init__.py | 21 +- .../profiles/https_broker.py} | 18 +- .../profiles/sessions_file.py} | 18 +- .../profiles/win_registry.py} | 19 +- {tests => x2go/backends/proxy}/__init__.py | 13 +- x2go/{proxy.py => backends/proxy/base.py} | 107 +- x2go/backends/proxy/nx3.py | 128 ++ test.py => x2go/backends/terminal/__init__.py | 18 +- x2go/backends/terminal/stdout.py | 720 +++++++++++ x2go/client.py | 28 +- x2go/defaults.py | 37 +- x2go/printing.py | 2 +- x2go/registry.py | 415 +------ x2go/rforward.py | 14 +- x2go/session.py | 1298 +++++--------------- 21 files changed, 1975 insertions(+), 1595 deletions(-) copy {tests => x2go/backends}/__init__.py (80%) copy test.py => x2go/backends/control/__init__.py (67%) create mode 100644 x2go/backends/control/stdout.py copy test.py => x2go/backends/info/__init__.py (59%) create mode 100644 x2go/backends/info/stdout.py copy test.py => x2go/backends/profiles/__init__.py (63%) copy x2go/{profiles.py => backends/profiles/https_broker.py} (94%) copy x2go/{profiles.py => backends/profiles/sessions_file.py} (94%) rename x2go/{profiles.py => backends/profiles/win_registry.py} (94%) copy {tests => x2go/backends/proxy}/__init__.py (73%) rename x2go/{proxy.py => backends/proxy/base.py} (62%) create mode 100644 x2go/backends/proxy/nx3.py copy test.py => x2go/backends/terminal/__init__.py (69%) create mode 100644 x2go/backends/terminal/stdout.py The diff of changes is: diff --git a/x2go/__init__.py b/x2go/__init__.py index 77a5b2b..70c7ba2 100644 --- a/x2go/__init__.py +++ b/x2go/__init__.py @@ -166,7 +166,7 @@ _signal.signal (_signal.SIGTERM, guardian._sigterm_handle ) _signal.signal (_signal.SIGINT, guardian._sigterm_handle ) from client import X2goClient -from profiles import X2goSessionProfiles +from backends.profiles import X2goSessionProfiles from printing import X2goClientPrinting from settings import X2goClientSettings from x2go_exceptions import * diff --git a/tests/__init__.py b/x2go/backends/__init__.py similarity index 80% copy from tests/__init__.py copy to x2go/backends/__init__.py index 6e5cefb..d2ad588 100644 --- a/tests/__init__.py +++ b/x2go/backends/__init__.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- # Copyright (C) 2010 by Mike Gabriel <m.gabriel@das-netzwerkteam.de> -# +# # Python X2go is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or +# the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # Python X2go is distributed in the hope that it will be useful, @@ -15,7 +15,4 @@ # You should have received a copy of the GNU General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., -# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -import runalltests -import test_printing \ No newline at end of file +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. diff --git a/test.py b/x2go/backends/control/__init__.py similarity index 67% copy from test.py copy to x2go/backends/control/__init__.py index 34b2284..e7780c4 100644 --- a/test.py +++ b/x2go/backends/control/__init__.py @@ -1,11 +1,10 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (C) 2010 by Mike Gabriel <m.gabriel@das-netzwerkteam.de> -# +# # Python X2go is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or +# the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # Python X2go is distributed in the hope that it will be useful, @@ -16,13 +15,13 @@ # You should have received a copy of the GNU General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., -# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +from gevent import monkey +monkey.patch_all() + +from x2go.defaults import DEFAULT_CONTROLSESSION_BACKEND -""" -Unit tests for Python X2go. -""" -import os +from stdout import X2goControlSessionSTDOUT -if __name__ == "__main__": - os.chdir('tests') - os.system('./runalltests.py') \ No newline at end of file +X2goControlSession = eval(DEFAULT_CONTROLSESSION_BACKEND) diff --git a/x2go/backends/control/stdout.py b/x2go/backends/control/stdout.py new file mode 100644 index 0000000..31089c3 --- /dev/null +++ b/x2go/backends/control/stdout.py @@ -0,0 +1,496 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010 by Mike Gabriel <m.gabriel@das-netzwerkteam.de> +# +# Python X2go is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Python X2go is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +"""\ +X2goControlSessionSTDOUT class - core functions for handling your individual X2go sessions. + +This backend handles X2go server implementations that respond via server-side STDOUT. + +""" +__NAME__ = 'x2gocontrolsession-pylib' + +# modules +import types +import paramiko + +import copy + +# Python X2go modules +import x2go.log as log +import x2go.utils as utils +import x2go.x2go_exceptions as x2go_exceptions +import x2go.defaults as defaults + +from x2go.backends.terminal import X2goTerminalSession as _X2goTerminalSession +from x2go.backends.info import X2goServerSessionInfo as _X2goServerSessionInfo +from x2go.backends.info import X2goServerSessionList as _X2goServerSessionList + +class X2goControlSessionSTDOUT(paramiko.SSHClient): + """\ + STILL UNDOCUMENTED + + @param logger: you can pass an L{X2goLogger} object to the + L{X2goProxy} constructor + @type logger: L{X2goLogger} instance + @param loglevel: if no L{X2goLogger} object has been supplied a new one will be + constructed with the given loglevel + @type loglevel: int + + """ + associated_terminals = {} + terminated_terminals = [] + + _session_auth_rsakey = None + _remote_home = None + _remote_group = {} + + def __init__(self, + terminal_backend=_X2goTerminalSession, + info_backend=_X2goServerSessionInfo, + list_backend=_X2goServerSessionList, + logger=None, loglevel=log.loglevel_DEFAULT, + *args, **kwargs): + """\ + Initialize an X2go session. With the X2goSession class you can start + new X2go sessions, resume suspended sessions or suspend resp. terminate + currently running sessions on a connected X2go server. + + """ + if logger is None: + self.logger = log.X2goLogger(loglevel=loglevel) + else: + self.logger = copy.deepcopy(logger) + self.logger.tag = __NAME__ + + self._terminal_backend = terminal_backend + self._info_backend = info_backend + self._list_backend = list_backend + paramiko.SSHClient.__init__(self, *args, **kwargs) + + def __del__(self): + + self.disconnect() + + def _x2go_sftp_put(self, local_path, remote_path): + + self.logger('sFTP-put: %s -> %s:%s' % (local_path, self.get_transport().getpeername(), remote_path), loglevel=log.loglevel_DEBUG) + self.sftp_client.put(local_path, remote_path) + + def _x2go_sftp_write(self, remote_path, content): + + self.logger('sFTP-write: opening remote file %s on host %s for writing' % (remote_path, self.get_transport().getpeername()), loglevel=log.loglevel_DEBUG) + remote_fileobj = self.sftp_client.open(remote_path, 'w') + self.logger('sFTP-write: writing content: %s' % content, loglevel=log.loglevel_DEBUG_SFTPXFER) + remote_fileobj.write(content) + remote_fileobj.close() + + def _x2go_sftp_remove(self, remote_path): + + self.logger('sFTP-write: removing remote file %s on host %s' % (remote_path, self.get_transport().getpeername()), loglevel=log.loglevel_DEBUG) + self.sftp_client.remove(remote_path) + + def _x2go_exec_command(self, cmd_line, loglevel=log.loglevel_INFO, **kwargs): + + if type(cmd_line) == types.ListType: + cmd = " ".join(cmd_line) + else: + cmd = cmd_line + if self.get_transport() is not None: + + try: + self.logger('executing command on X2go server: %s' % cmd, loglevel) + return self.exec_command(cmd, **kwargs) + except AttributeError: + raise x2go_exceptions.X2goSessionException('a Paramiko/SSH control session has died') + + else: + raise x2go_exceptions.X2goSessionException('the Paramiko/SSH client is not connected') + + @property + def _x2go_remote_home(self): + + if self._remote_home is None: + (stdin, stdout, stderr) = self._x2go_exec_command('echo $HOME') + self._remote_home = stdout.read().split()[0] + self.logger('remote user\' home directory: %s' % self._remote_home, loglevel=log.loglevel_DEBUG) + return self._remote_home + else: + return self._remote_home + + def _x2go_remote_group(self, group): + + if not self._remote_group.has_key(group): + (stdin, stdout, stderr) = self._x2go_exec_command('getent group %s | cut -d":" -f4' % group) + self._remote_group[group] = stdout.read().split('\n')[0].split(',') + self.logger('remote %s group: %s' % (group, self._remote_group[group]), loglevel=log.loglevel_DEBUG) + return self._remote_group[group] + else: + return self._remote_group[group] + + @property + def _x2go_session_auth_rsakey(self): + if self._session_auth_rsakey is None: + self._session_auth_rsakey = paramiko.RSAKey.generate(defaults.RSAKEY_STRENGTH) + return self._session_auth_rsakey + + def connect(self, hostname, port=22, username=None, password=None, pkey=None, + key_filename=None, timeout=None, allow_agent=False, look_for_keys=True, + add_to_known_hosts=False, force_password_auth=False): + """\ + Connect to an X2go server and authenticate to it. This method is directly + inherited from the paramiko.SSHClient module. The features of the Paramiko + SSH client connect method are recited here. The parameters C{add_to_known_hosts} + and C{force_password_auth} have been added as a parameter for X2go. + + The server's host key + is checked against the system host keys (see C{load_system_host_keys}) + and any local host keys (C{load_host_keys}). If the server's hostname + is not found in either set of host keys, the missing host key policy + is used (see C{set_missing_host_key_policy}). The default policy is + to reject the key and raise an C{SSHException}. + + Authentication is attempted in the following order of priority: + + - The C{pkey} or C{key_filename} passed in (if any) + - Any key we can find through an SSH agent + - Any "id_rsa" or "id_dsa" key discoverable in C{~/.ssh/} + - Plain username/password auth, if a password was given + + If a private key requires a password to unlock it, and a password is + passed in, that password will be used to attempt to unlock the key. + + @param hostname: the server to connect to + @type hostname: str + @param port: the server port to connect to + @type port: int + @param username: the username to authenticate as (defaults to the + current local username) + @type username: str + @param password: a password to use for authentication or for unlocking + a private key + @type password: str + @param pkey: an optional private key to use for authentication + @type pkey: C{PKey} + @param key_filename: the filename, or list of filenames, of optional + private key(s) to try for authentication + @type key_filename: str or list(str) + @param timeout: an optional timeout (in seconds) for the TCP connect + @type timeout: float + @param allow_agent: set to False to disable connecting to the SSH agent + @type allow_agent: C{bool} + @param look_for_keys: set to False to disable searching for discoverable + private key files in C{~/.ssh/} + @type look_for_keys: C{bool} + @param add_to_known_hosts: non-paramiko option, if C{True} paramiko.AutoAddPolicy() + is used as missing-host-key-policy. If set to C{False} paramiko.RejectPolicy() + is used + @type add_to_known_hosts: C{bool} + @param force_password_auth: non-paramiko option, disable pub/priv key authentication + completely, even if the C{pkey} or the C{key_filename} parameter is given + @type force_password_auth: C{bool} + + @raise BadHostKeyException: if the server's host key could not be + verified + @raise AuthenticationException: if authentication failed + @raise SSHException: if there was any other error connecting or + establishing an SSH session + @raise socket.error: if a socket error occurred while connecting + + """ + if add_to_known_hosts: + self.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + + # disable pub/priv key authentication if forced + if force_password_auth: + key_filename = None + pkey = None + + self.logger('connecting to %s' % hostname, log.loglevel_NOTICE) + + if (key_filename or pkey): + try: + self.logger('trying SSH pub/priv key authentication with server', log.loglevel_DEBUG) + paramiko.SSHClient.connect(self, hostname, port=port, username=username, pkey=pkey, + key_filename=key_filename, timeout=timeout, allow_agent=allow_agent, + look_for_keys=look_for_keys) + except paramiko.AuthenticationException, e: + if password: + self.logger('next auth mechanism we\'ll try is keyboard-interactive authentication', log.loglevel_DEBUG) + paramiko.SSHClient.connect(self, hostname, port=port, username=username, password=password, + timeout=timeout, allow_agent=allow_agent, + look_for_keys=look_for_keys) + else: + raise(e) + + # if there is not private key, we will use the given password + elif password: + self.logger('performing SSH keyboard-interactive authentication with server', log.loglevel_DEBUG) + paramiko.SSHClient.connect(self, hostname, port=port, username=username, password=password, + timeout=timeout, allow_agent=allow_agent, look_for_keys=look_for_keys) + + # authentication failed + else: + raise paramiko.AuthenticationException() + + # if we succeed, we immediately grab us an sFTP client session + self.sftp_client = self.open_sftp() + + # preparing reverse tunnels + ssh_transport = self.get_transport() + ssh_transport.reverse_tunnels = {} + + # mark transport as X2goSession + ssh_transport._x2go_session_marker = True + self._session_password = password + + return (self.get_transport() is not None) + + def disconnect(self): + """\ + STILL UNDOCUMENTED + + """ + for t in self.associated_terminals: + del t + if self.get_transport() is not None: + self.get_transport().close() + + def start(self, **kwargs): + """\ + Start a new X2go session. + + The L{X2goControlSession.start()} method accepts any parameter + that can be passed to any of the L{X2goTerminalSession} class + constructors. + + """ + return self.resume(**kwargs) + + def resume(self, session_name=None, **kwargs): + """\ + Resume a running/suspended X2go session. + + The L{X2goControlSession.resume()} method accepts any parameter + that can be passed to the L{X2goTerminalSession} class constructor. + + @return: True if the session could be successfully resumed + @rtype: C{bool} + + """ + if self.get_transport().get_username() not in self._x2go_remote_group('x2gousers'): + raise x2go_exceptions.X2goSessionException('remote user %s is not member of X2go server group x2gousers' % self.get_transport().get_username()) + + _terminal = self._terminal_backend(self, **kwargs) + if session_name is not None: + if self.is_running(session_name): + self.suspend(session_name) + _terminal.resume() + else: + _terminal.start() + + if _terminal.ok(): + self.associated_terminals[_terminal.get_session_name()] = _terminal + self.get_transport().reverse_tunnels[_terminal.get_session_name()] = { + 'sshfs': (0, None), + 'snd': (0, None), + } + + return _terminal or None + + def list_sessions(self, raw=False): + """\ + List all sessions of current user on the connected server. + + @param raw: if C{True}, the raw output of the server-side X2go command + C{x2golistsessions} is returned. + @type raw: C{bool} + + @return: normally an instance of L{X2goServerSessionList} is returned. However, + if the raw argument is set, the plain text output of the x2golistsessions + command is returned + @rtype: L{X2goServerSessionList} instance or str + + """ + (stdin, stdout, stderr) = self._x2go_exec_command("x2golistsessions") + + if raw: + return stdout.read(), stderr.read() + + _stdout_read = stdout.read() + return self._list_backend(_stdout_read, info_backend=self._info_backend).sessions + + def clean_sessions(self): + """\ + Find X2go terminals that have previously been started by the + connected user on the remote X2go server and terminate them. + + """ + session_infos = self.list_sessions() + for session_info in session_infos.values(): + self.terminate(session_name=session_info) + + def is_connected(self): + """\ + Returns C{True} if this X2go session is connected to the remote server (that + is if it has a valid Paramiko Transport object). + + @param session_name: X2go name of the session to be queried + @type session_name: str + + @return: X2go session connected + @rtype: C{bool} + + """ + return self.get_transport() is not None and self.get_transport().is_authenticated() + + def is_running(self, session_name): + """\ + Returns C{True} if the given X2go session is in running state, + C{False} else. + + @param session_name: X2go name of the session to be queried + @type session_name: str + + @return: X2go session running? + @rtype: C{bool} + + """ + session_infos = self.list_sessions() + if session_name in session_infos.keys(): + return session_infos[session_name].is_running() + return False + + def is_suspended(self, session_name): + """\ + Returns C{True} if the given X2go session is in suspended state, + C{False} else. + + @return: X2go session suspended? + @rtype: C{bool} + + """ + session_infos = self.list_sessions() + if session_name in session_infos.keys(): + return session_infos[session_name].is_suspended() + return False + + def has_terminated(self, session_name): + """\ + Returns C{True} if this X2go session is not in the session list on the + connected server, C{False} else. + + Of course, if this command is called before session startup, it will also + return C{True}. + + @return: X2go session has terminate? + @rtype: C{bool} + + """ + session_infos = self.list_sessions() + + + if session_name not in session_infos.keys(): + if session_name in self.terminated_terminals: + return True + else: + # do a post-mortem tidy up + if session_name in self.associated_terminals.keys(): + self.terminate(session_name) + return True + + return False + + def suspend(self, session_name): + """\ + Suspend either this or another available X2go session on the connected + server. + + If L{session_name} is given, L{X2goSession.suspend()} tries to suspend the + corresponding session. + + @param session_name: X2go name of the session to be suspended + @type session_name: str + + @return: True if the session could be successfully suspended + @rtype: C{bool} + + """ + _ret = False + _session_names = [ t.get_session_name() for t in associated_terminals.values() ] + if session_name in _session_names: + + self.logger('suspending associated terminal session: %s' % session_name, log.loglevel_DEBUG) + (stdin, stdout, stderr) = self._x2go_exec_command("x2gosuspend-session %s" % session_name, loglevel=log.loglevel_DEBUG) + dummy_stdout = stdout.read() + dummy_stderr = stderr.read() + associated_terminals[session_name].__del__() + del associated_terminals[session_name] + _ret = True + + else: + + self.logger('suspending non-associated terminal session: %s' % session_name, log.loglevel_DEBUG) + (stdin, stdout, stderr) = self._x2go_exec_command("x2gosuspend-session %s" % session_name, loglevel=log.loglevel_DEBUG) + dummy_stdout = stdout.read() + dummy_stderr = stderr.read() + _ret = True + + return _ret + + def terminate(self, session_name): + """\ + Terminate either this or another available X2go session on the connected + server. + + If L{session_name} is given, L{X2goSession.terminate()} tries to terminate the + corresponding session. + + @param session_name: X2go name of the session to be terminated + @type session_name: str + + @return: True if the session could be successfully terminate + @rtype: C{bool} + + """ + + _ret = False + _session_names = [ t.get_session_name() for t in self.associated_terminals.values() ] + if session_name in _session_names: + + self.logger('suspending associated session: %s' % session_name, log.loglevel_DEBUG) + (stdin, stdout, stderr) = self._x2go_exec_command("x2goterminate-session %s" % session_name, loglevel=log.loglevel_DEBUG) + dummy_stdout = stdout.read() + dummy_stderr = stderr.read() + if self.associated_terminals[session_name] is not None: + self.associated_terminals[session_name].__del__() + del self.associated_terminals[session_name] + self.terminated_terminals.append(session_name) + _ret = True + + else: + + self.logger('suspending non-associated session: %s' % session_name, log.loglevel_DEBUG) + (stdin, stdout, stderr) = self._x2go_exec_command("x2goterminate-session %s" % session_name, loglevel=log.loglevel_DEBUG) + dummy_stdout = stdout.read() + dummy_stderr = stderr.read() + _ret = True + + return _ret + + diff --git a/test.py b/x2go/backends/info/__init__.py similarity index 59% copy from test.py copy to x2go/backends/info/__init__.py index 34b2284..0606d7b 100644 --- a/test.py +++ b/x2go/backends/info/__init__.py @@ -1,11 +1,10 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (C) 2010 by Mike Gabriel <m.gabriel@das-netzwerkteam.de> -# +# # Python X2go is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or +# the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # Python X2go is distributed in the hope that it will be useful, @@ -16,13 +15,13 @@ # You should have received a copy of the GNU General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., -# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +from x2go.defaults import DEFAULT_SERVERSESSIONINFO_BACKEND +from x2go.defaults import DEFAULT_SERVERSESSIONLIST_BACKEND -""" -Unit tests for Python X2go. -""" -import os +from stdout import X2goServerSessionInfoSTDOUT +from stdout import X2goServerSessionListSTDOUT -if __name__ == "__main__": - os.chdir('tests') - os.system('./runalltests.py') \ No newline at end of file +X2goServerSessionInfo = eval(DEFAULT_SERVERSESSIONINFO_BACKEND) +X2goServerSessionList = eval(DEFAULT_SERVERSESSIONLIST_BACKEND) diff --git a/x2go/backends/info/stdout.py b/x2go/backends/info/stdout.py new file mode 100644 index 0000000..b675bb9 --- /dev/null +++ b/x2go/backends/info/stdout.py @@ -0,0 +1,165 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010 by Mike Gabriel <m.gabriel@das-netzwerkteam.de> +# +# Python X2go is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Python X2go is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +"""\ +X2goServerSessionList and X2goServerSessionInfo classes - data handling for +X2go server sessions. + +This backend handles X2go server implementations that respond with session infos +via server-side STDOUT. + +""" +__NAME__ = 'x2goserversessioninfo-pylib' + +class X2goServerSessionInfoSTDOUT(object): + """\ + L{X2goServerSessionInfo} is used to store all information + that is retrieved from the connected X2go server on + L{X2goSession.start()} resp. L{X2goSession.resume()}. + + """ + def __str__(self): + return self.name + def __repr__(self): + return "<%s instance: %s>" % (self.__class__, self.name) + + def _parse_x2golistsessions_line(self, x2go_output): + """\ + Parse a single line of X2go's listsessions output. + + """ + l = x2go_output.split("|") + self.name = l[1] + self.cookie = l[6] + self.agent_pid = int(l[0]) + self.display = int(l[2]) + self.status = l[4] + self.graphics_port = int(l[8]) + self.snd_port = int(l[9]) + self.sshfs_port = int(l[13]) + self.username = l[11] + self.hostname = l[3] + # TODO: turn into datetime object + self.date_created = l[5] + # TODO: turn into datetime object + self.date_suspended = l[10] + self.local_container = '' + + def is_running(self): + + return self.status == 'R' + + def is_suspended(self): + + return self.status == 'S' + + def _parse_x2gostartagent_output(self, x2go_output): + """\ + Parse x2gostartagent output. + + """ + l = x2go_output.split("\n") + self.name = l[3] + self.cookie = l[1] + self.agent_pid = int(l[2]) + self.display = int(l[0]) + self.graphics_port = int(l[4]) + self.snd_port = int(l[5]) + self.sshfs_port = int(l[6]) + self.username = '' + self.hostname = '' + # TODO: we have to see how we fill these fields here... + self.date_created = '' + self.date_suspended = '' + # TODO: presume session is running after x2gostartagent, this could be better + self.status = 'R' + self.local_container = '' + self.remote_container = '' + + def initialize(self, x2go_output, username='', hostname='', local_container='', remote_container=''): + """\ + Parse X2go server's C{x2gostartagent} stdout values. + + @param x2go_output: X2go server's C{x2gostartagent} command output, each value + separated by a newline character. + @type x2go_output: str + @param username: session user name + @type username: str + @param hostname: hostname of X2go server + @type hostname: str + @param local_container: X2go client session directory for config files, cache and session logs + @type local_container: str + @param remote_container: X2go server session directory for config files, cache and session logs + @type remote_container: str + + """ + self._parse_x2gostartagent_output(x2go_output) + self.username = username + self.hostname = hostname + self.local_container = local_container + self.remote_container = remote_container + + def clear(self): + """\ + Clear all properties of a L{X2goServerSessionInfo} object. + + """ + self.name = '' + self.cookie = '' + self.agent_pid = '' + self.display = '' + self.graphics_port = '' + self.snd_port = '' + self.sshfs_port = '' + self.username = '' + self.hostname = '' + self.date_created = '' + self.date_suspended = '' + self.status = '' + self.local_container = '' + self.remote_container = '' + + __init__ = clear + + +class X2goServerSessionListSTDOUT(object): + """\ + L{X2goServerSessionList} is used to store all information + that is retrieved from a connected X2go server on a + L{X2goSession.list_sessions()} call. + + """ + def __init__(self, x2go_output, info_backend=X2goServerSessionInfoSTDOUT): + """\ + @param x2go_output: X2go server's C{x2golistsessions} command output, each + session separated by a newline character. Session values are separated + by Unix Pipe Symbols ('|') + @type x2go_output: str + + """ + self.sessions = {} + lines = x2go_output.split("\n") + for line in lines: + if not line: + continue + s_info = info_backend() + s_info._parse_x2golistsessions_line(line) + self.sessions[s_info.name] = s_info + + diff --git a/test.py b/x2go/backends/profiles/__init__.py similarity index 63% copy from test.py copy to x2go/backends/profiles/__init__.py index 34b2284..8eab147 100644 --- a/test.py +++ b/x2go/backends/profiles/__init__.py @@ -1,11 +1,10 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (C) 2010 by Mike Gabriel <m.gabriel@das-netzwerkteam.de> -# +# # Python X2go is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or +# the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # Python X2go is distributed in the hope that it will be useful, @@ -16,13 +15,13 @@ # You should have received a copy of the GNU General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., -# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +from x2go.defaults import DEFAULT_SESSIONPROFILES_BACKEND + +from sessions_file import X2goSessionProfilesFILE +from win_registry import X2goSessionProfilesWINREG +from https_broker import X2goSessionProfilesHTTP -""" -Unit tests for Python X2go. -""" -import os +X2goSessionProfiles = eval(DEFAULT_SESSIONPROFILES_BACKEND) -if __name__ == "__main__": - os.chdir('tests') - os.system('./runalltests.py') \ No newline at end of file diff --git a/x2go/profiles.py b/x2go/backends/profiles/https_broker.py similarity index 94% copy from x2go/profiles.py copy to x2go/backends/profiles/https_broker.py index b6e4e6b..8355e81 100644 --- a/x2go/profiles.py +++ b/x2go/backends/profiles/https_broker.py @@ -29,15 +29,15 @@ __NAME__ = 'x2gosessionprofiles-pylib' import copy # Python X2go modules -from defaults import X2GO_SESSIONPROFILES_CONFIGFILES -from defaults import X2GO_SESSIONPROFILE_DEFAULTS -import inifiles -import log -import utils -from x2go_exceptions import X2goProfileException +from x2go.defaults import X2GO_SESSIONPROFILES_CONFIGFILES +from x2go.defaults import X2GO_SESSIONPROFILE_DEFAULTS +import x2go.inifiles as inifiles +import x2go.log as log +import x2go.utils as utils +from x2go.x2go_exceptions import X2goProfileException -class X2goSessionProfiles(inifiles.X2goIniFile): +class X2goSessionProfilesHTTP(inifiles.X2goIniFile): defaultValues = {} defaultSessionProfile = X2GO_SESSIONPROFILE_DEFAULTS @@ -151,11 +151,13 @@ class X2goSessionProfiles(inifiles.X2goIniFile): """ return self.get_profile_config(profile_id)['name'] - def add_profile(self, profile_id, **kwargs): + def add_profile(self, profile_id=None, **kwargs): """\ STILL UNDOCUMENTED """ + if profile_id is None: + profile_id = utils._get_SessionProfileId() for key, value in kwargs.items(): if key in self.defaultSessionProfile: self.update_value(profile_id, key, value) diff --git a/x2go/profiles.py b/x2go/backends/profiles/sessions_file.py similarity index 94% copy from x2go/profiles.py copy to x2go/backends/profiles/sessions_file.py index b6e4e6b..d6fe03d 100644 --- a/x2go/profiles.py +++ b/x2go/backends/profiles/sessions_file.py @@ -29,15 +29,15 @@ __NAME__ = 'x2gosessionprofiles-pylib' import copy # Python X2go modules -from defaults import X2GO_SESSIONPROFILES_CONFIGFILES -from defaults import X2GO_SESSIONPROFILE_DEFAULTS -import inifiles -import log -import utils -from x2go_exceptions import X2goProfileException +from x2go.defaults import X2GO_SESSIONPROFILES_CONFIGFILES +from x2go.defaults import X2GO_SESSIONPROFILE_DEFAULTS +import x2go.inifiles as inifiles +import x2go.log as log +import x2go.utils as utils +from x2go.x2go_exceptions import X2goProfileException -class X2goSessionProfiles(inifiles.X2goIniFile): +class X2goSessionProfilesFILE(inifiles.X2goIniFile): defaultValues = {} defaultSessionProfile = X2GO_SESSIONPROFILE_DEFAULTS @@ -151,11 +151,13 @@ class X2goSessionProfiles(inifiles.X2goIniFile): """ return self.get_profile_config(profile_id)['name'] - def add_profile(self, profile_id, **kwargs): + def add_profile(self, profile_id=None, **kwargs): """\ STILL UNDOCUMENTED """ + if profile_id is None: + profile_id = utils._get_SessionProfileId() for key, value in kwargs.items(): if key in self.defaultSessionProfile: self.update_value(profile_id, key, value) diff --git a/x2go/profiles.py b/x2go/backends/profiles/win_registry.py similarity index 94% rename from x2go/profiles.py rename to x2go/backends/profiles/win_registry.py index b6e4e6b..380c37a 100644 --- a/x2go/profiles.py +++ b/x2go/backends/profiles/win_registry.py @@ -29,15 +29,16 @@ __NAME__ = 'x2gosessionprofiles-pylib' import copy # Python X2go modules -from defaults import X2GO_SESSIONPROFILES_CONFIGFILES -from defaults import X2GO_SESSIONPROFILE_DEFAULTS -import inifiles -import log -import utils -from x2go_exceptions import X2goProfileException +from x2go.defaults import X2GO_SESSIONPROFILES_CONFIGFILES +from x2go.defaults import X2GO_SESSIONPROFILE_DEFAULTS +import x2go.inifiles as inifiles +import x2go.log as log +import x2go.utils as hostname +from x2go.x2go_exceptions import X2goProfileException -class X2goSessionProfiles(inifiles.X2goIniFile): + +class X2goSessionProfilesWINREG(inifiles.X2goIniFile): defaultValues = {} defaultSessionProfile = X2GO_SESSIONPROFILE_DEFAULTS @@ -151,11 +152,13 @@ class X2goSessionProfiles(inifiles.X2goIniFile): """ return self.get_profile_config(profile_id)['name'] - def add_profile(self, profile_id, **kwargs): + def add_profile(self, profile_id=None, **kwargs): """\ STILL UNDOCUMENTED """ + if profile_id is None: + profile_id = utils._get_SessionProfileId() for key, value in kwargs.items(): if key in self.defaultSessionProfile: self.update_value(profile_id, key, value) diff --git a/tests/__init__.py b/x2go/backends/proxy/__init__.py similarity index 73% copy from tests/__init__.py copy to x2go/backends/proxy/__init__.py index 6e5cefb..5ce688d 100644 --- a/tests/__init__.py +++ b/x2go/backends/proxy/__init__.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- # Copyright (C) 2010 by Mike Gabriel <m.gabriel@das-netzwerkteam.de> -# +# # Python X2go is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or +# the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # Python X2go is distributed in the hope that it will be useful, @@ -15,7 +15,10 @@ # You should have received a copy of the GNU General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., -# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +from x2go.defaults import DEFAULT_PROXY_BACKEND + +from nx3 import X2goProxyNX3 -import runalltests -import test_printing \ No newline at end of file +X2goProxy = eval(DEFAULT_PROXY_BACKEND) diff --git a/x2go/proxy.py b/x2go/backends/proxy/base.py similarity index 62% rename from x2go/proxy.py rename to x2go/backends/proxy/base.py index 5dd8c35..8353d3c 100644 --- a/x2go/proxy.py +++ b/x2go/backends/proxy/base.py @@ -18,7 +18,7 @@ # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. """\ -X2goProxy classes - proxying your connection through NX3 and others. +X2goProxyBASE class - proxying your connection through NX3 and others. """ __NAME__ = 'x2goproxy-pylib' @@ -33,20 +33,20 @@ import copy import threading # Python X2go modules -import forward -import log +import x2go.forward as forward +import x2go.log as log -from defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS +from x2go.defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS if _X2GOCLIENT_OS in ("Windows"): import subprocess else: - import gevent_subprocess as subprocess + import x2go.gevent_subprocess as subprocess -from defaults import LOCAL_HOME as _LOCAL_HOME -from defaults import X2GO_SESSION_ROOTDIR as _X2GO_SESSION_ROOTDIR +from x2go.defaults import LOCAL_HOME as _LOCAL_HOME +from x2go.defaults import X2GO_SESSION_ROOTDIR as _X2GO_SESSION_ROOTDIR -class X2goProxy(threading.Thread): +class X2goProxyBASE(threading.Thread): """\ X2goProxy is an abstract class for X2go proxy connections. @@ -106,8 +106,6 @@ class X2goProxy(threading.Thread): """ self.stop_thread() - #if p.poll() is None: - # p.kill() def _tidy_up(self): """\ @@ -134,8 +132,6 @@ class X2goProxy(threading.Thread): self._keepalive = False gevent.sleep(1) self._tidy_up() - #if p.poll() is None: - # p.kill() def run(self): """\ @@ -203,90 +199,3 @@ class X2goProxy(threading.Thread): return self.proxy -class X2goNX3Proxy(X2goProxy): - """\ - X2goNX3Proxy is a NX version 3 based X2go proxy connection class. - - It basically fills X2goProxy variables with sensible content. Its - methods mostly wrap around the corresponding methods of the parent class. - - """ - def __init__(self, *args, **kwargs): - """\ - For available parameters refer to L{X2goProxy} class documentation. - - """ - X2goProxy.__init__(self, *args, **kwargs) - - # setting some default environment variables, nxproxy paths etc. - if _X2GOCLIENT_OS == "Windows": - self.PROXY_CMD = os.path.join(os.environ["ProgramFiles"], os.path.normpath("x2goclient/nxproxy.exe")) - else: - self.PROXY_CMD = "/usr/bin/nxproxy" - self.PROXY_ENV.update({ - "NX_CLIENT": "/bin/true", - "NX_ROOT": os.path.join(_LOCAL_HOME, _X2GO_SESSION_ROOTDIR) - }) - self.PROXY_MODE = '-S' - if _X2GOCLIENT_OS == "Windows": - self.PROXY_OPTIONS = [ - "nx/nx" , - "retry=5", - "composite=1", - "connect=localhost", - "cookie=%s" % self.session_info.cookie, - "port=%d" % self.session_info.graphics_port, - "errors=%s" % os.path.join(".", "..", "S-%s" % self.session_info.name, self.session_log, ), - ] - else: - self.PROXY_OPTIONS = [ - "nx/nx" , - "retry=5", - "composite=1", - "connect=localhost", - "cookie=%s" % self.session_info.cookie, - "port=%d" % self.session_info.graphics_port, - "errors=%s" % os.path.join(self.session_info.local_container, self.session_log, ), - ] - - self.PROXY_DISPLAY = self.session_info.display - - def _update_local_proxy_socket(self, port): - for idx, a in enumerate(self.PROXY_OPTIONS): - if a.startswith('port='): - self.PROXY_OPTIONS[idx] = 'port=%s' % port - - def _generate_cmdline(self): - - if (_X2GOCLIENT_OS == "Windows") and (len(",".join(self.PROXY_OPTIONS)) >= 250): - _options_filename = os.path.join(self.session_info.local_container, 'options') - options = open(_options_filename, 'w') - options.write('%s:%s' % (','.join(self.PROXY_OPTIONS), self.PROXY_DISPLAY)) - options.close() - self.PROXY_OPTIONS= [ 'nx/nx', 'options=%s' % os.path.join(".", "..", "S-%s" % self.session_info.name, 'options'), ] - - cmd_line = [ self.PROXY_CMD, ] - cmd_line.append(self.PROXY_MODE) - _proxy_options = "%s:%s" % (",".join(self.PROXY_OPTIONS), self.PROXY_DISPLAY) - cmd_line.append(_proxy_options) - return cmd_line - - - def start_proxy(self): - self.logger('starting local NX3 proxy...', loglevel=log.loglevel_INFO) - self.logger('NX3 Proxy mode is server, cookie=%s, host=localhost, port=%s.' % (self.session_info.cookie, self.session_info.graphics_port,), loglevel=log.loglevel_DEBUG) - self.logger('NX3 proxy writes session log to %s.' % os.path.join(self.session_info.local_container, 'session.log'), loglevel=log.loglevel_DEBUG) - - p = X2goProxy.start_proxy(self) - - if p is not None: - self.logger('NX3 proxy is up and running.', loglevel=log.loglevel_INFO) - else: - self.logger('Bringing up NX3 proxy failed.', loglevel=log.loglevel_ERROR) - - return p - - -# this is our default proxy: NX3 Proxy -DEFAULT_PROXY_CLASS = X2goNX3Proxy -"""Currently L{X2goNX3Proxy} is Python X2go's default proxy class.""" \ No newline at end of file diff --git a/x2go/backends/proxy/nx3.py b/x2go/backends/proxy/nx3.py new file mode 100644 index 0000000..a6b73a3 --- /dev/null +++ b/x2go/backends/proxy/nx3.py @@ -0,0 +1,128 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010 by Mike Gabriel <m.gabriel@das-netzwerkteam.de> +# +# Python X2go is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Python X2go is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +"""\ +X2goProxy classes - proxying your connection through NX3 and others. + +""" +__NAME__ = 'x2goproxynx3-pylib' + +# modules +import gevent +import os +import sys +import types +import time +import copy +import threading + +# Python X2go modules +import x2go.forward as forward +import x2go.log as log +import base + +from x2go.defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS +from x2go.defaults import LOCAL_HOME as _LOCAL_HOME +from x2go.defaults import X2GO_SESSION_ROOTDIR as _X2GO_SESSION_ROOTDIR + +class X2goProxyNX3(base.X2goProxyBASE): + """\ + X2goNX3Proxy is a NX version 3 based X2go proxy connection class. + + It basically fills L{X2goProxyBASE} variables with sensible content. Its + methods mostly wrap around the corresponding methods of the parent class. + + """ + def __init__(self, *args, **kwargs): + """\ + For available parameters refer to L{X2goProxyBASE} class documentation. + + """ + base.X2goProxyBASE.__init__(self, *args, **kwargs) + + # setting some default environment variables, nxproxy paths etc. + if _X2GOCLIENT_OS == "Windows": + self.PROXY_CMD = os.path.join(os.environ["ProgramFiles"], os.path.normpath("x2goclient/nxproxy.exe")) + else: + self.PROXY_CMD = "/usr/bin/nxproxy" + self.PROXY_ENV.update({ + "NX_CLIENT": "/bin/true", + "NX_ROOT": os.path.join(_LOCAL_HOME, _X2GO_SESSION_ROOTDIR) + }) + self.PROXY_MODE = '-S' + if _X2GOCLIENT_OS == "Windows": + self.PROXY_OPTIONS = [ + "nx/nx" , + "retry=5", + "composite=1", + "connect=localhost", + "cookie=%s" % self.session_info.cookie, + "port=%d" % self.session_info.graphics_port, + "errors=%s" % os.path.join(".", "..", "S-%s" % self.session_info.name, self.session_log, ), + ] + else: + self.PROXY_OPTIONS = [ + "nx/nx" , + "retry=5", + "composite=1", + "connect=localhost", + "cookie=%s" % self.session_info.cookie, + "port=%d" % self.session_info.graphics_port, + "errors=%s" % os.path.join(self.session_info.local_container, self.session_log, ), + ] + + self.PROXY_DISPLAY = self.session_info.display + + def _update_local_proxy_socket(self, port): + for idx, a in enumerate(self.PROXY_OPTIONS): + if a.startswith('port='): + self.PROXY_OPTIONS[idx] = 'port=%s' % port + + def _generate_cmdline(self): + + if (_X2GOCLIENT_OS == "Windows") and (len(",".join(self.PROXY_OPTIONS)) >= 250): + _options_filename = os.path.join(self.session_info.local_container, 'options') + options = open(_options_filename, 'w') + options.write('%s:%s' % (','.join(self.PROXY_OPTIONS), self.PROXY_DISPLAY)) + options.close() + self.PROXY_OPTIONS= [ 'nx/nx', 'options=%s' % os.path.join(".", "..", "S-%s" % self.session_info.name, 'options'), ] + + cmd_line = [ self.PROXY_CMD, ] + cmd_line.append(self.PROXY_MODE) + _proxy_options = "%s:%s" % (",".join(self.PROXY_OPTIONS), self.PROXY_DISPLAY) + cmd_line.append(_proxy_options) + return cmd_line + + def start_proxy(self): + self.logger('starting local NX3 proxy...', loglevel=log.loglevel_INFO) + self.logger('NX3 Proxy mode is server, cookie=%s, host=localhost, port=%s.' % (self.session_info.cookie, self.session_info.graphics_port,), loglevel=log.loglevel_DEBUG) + self.logger('NX3 proxy writes session log to %s.' % os.path.join(self.session_info.local_container, 'session.log'), loglevel=log.loglevel_DEBUG) + + p = base.X2goProxyBASE.start_proxy(self) + + if p is not None: + self.logger('NX3 proxy is up and running.', loglevel=log.loglevel_INFO) + else: + self.logger('Bringing up NX3 proxy failed.', loglevel=log.loglevel_ERROR) + + return p + + + +# diff --git a/test.py b/x2go/backends/terminal/__init__.py similarity index 69% copy from test.py copy to x2go/backends/terminal/__init__.py index 34b2284..91c158c 100644 --- a/test.py +++ b/x2go/backends/terminal/__init__.py @@ -1,11 +1,10 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (C) 2010 by Mike Gabriel <m.gabriel@das-netzwerkteam.de> -# +# # Python X2go is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or +# the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # Python X2go is distributed in the hope that it will be useful, @@ -16,13 +15,10 @@ # You should have received a copy of the GNU General Public License # along with this program; if not, write to the # Free Software Foundation, Inc., -# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +from x2go.defaults import DEFAULT_TERMINALSESSION_BACKEND -""" -Unit tests for Python X2go. -""" -import os +from stdout import X2goTerminalSessionSTDOUT -if __name__ == "__main__": - os.chdir('tests') - os.system('./runalltests.py') \ No newline at end of file +X2goTerminalSession = eval(DEFAULT_TERMINALSESSION_BACKEND) diff --git a/x2go/backends/terminal/stdout.py b/x2go/backends/terminal/stdout.py new file mode 100644 index 0000000..7e6d61a --- /dev/null +++ b/x2go/backends/terminal/stdout.py @@ -0,0 +1,720 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010 by Mike Gabriel <m.gabriel@das-netzwerkteam.de> +# +# Python X2go is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Python X2go is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the +# Free Software Foundation, Inc., +# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. + +"""\ +X2goTerminalSession class - core functions for handling your individual X2go sessions. + +This backend handles X2go server implementations that respond with session infos +via server-side STDOUT and use NX3 as graphical proxy. + +""" +__NAME__ = 'x2goterminalsession-pylib' + +# modules +import os, sys, types +import gevent +import threading +import signal +import cStringIO +import copy + +# Python X2go modules +import x2go.rforward as rforward +import x2go.sftpserver as sftpserver +import x2go.printing as printing +import x2go.log as log +import x2go.defaults as defaults +import x2go.utils as utils +import x2go.x2go_exceptions as x2go_exceptions +import x2go.guardian as guardian + +from x2go.cleanup import x2go_cleanup + +# we hide the default values from epydoc (that's why we transform them to _UNDERSCORE variables) +from x2go.defaults import LOCAL_HOME as _LOCAL_HOME +from x2go.defaults import CURRENT_LOCAL_USER as _CURRENT_LOCAL_USER +from x2go.defaults import X2GO_SESSION_ROOTDIR as _X2GO_SESSION_ROOTDIR + +from x2go.backends.info import X2goServerSessionInfo +from x2go.backends.info import X2goServerSessionList +from x2go.backends.proxy import X2goProxy + +def _rewrite_cmd(cmd): + + # start with an empty string + cmd = cmd or '' + + # find window manager commands + if cmd in defaults.window_managers.keys(): + cmd = defaults.window_managers[cmd] + + # X2go run command replace X2GO_SPACE_CHAR string with blanks + cmd.replace(" ", "X2GO_SPACE_CHAR") + + # place quot marks around cmd if not empty string + if cmd: + cmd = '"%s"' % cmd + return cmd + + +class X2goSessionParams(object): + """\ + The L{X2goSessionParams} class is used to store all parameters that + L{X2goSession} objects are constructed with. + + """ + def rewrite_session_type(self): + """\ + Rewrite the X2go session type, so that the X2go server + can understand it (C{desktop} -> C{D}). + + Also if the object's C{command} property is a known window + manager, the session type will be set to 'D' + (i.e. desktop). + + @return: 'D' if session should probably a desktop session, + 'R' (for rootless) else + @rtype: str + + """ + session_type = self.session_type + cmd = self.cmd + if session_type == "desktop": + self.session_type = 'D' + return + if cmd: + if cmd in defaults.window_managers.keys(): + self.session_type = 'D' + return + if os.path.basename(cmd) in defaults.window_managers.values(): + self.session_type = 'D' + return + self.session_type = 'R' + + def update(self, properties_to_be_updated={}): + """\ + Update all properties in the object L{X2goSessionParams} object from + the passed on dictionary. + + @param properties_to_be_updated: a dictionary with L{X2goSessionParams} + property names as keys und their values to be update in + L{X2goSessionParams} object. + @type properties_to_be_updated: dict + + """ + for key in properties_to_be_updated.keys(): + setattr(self, key, properties_to_be_updated[key] or '') + self.rewrite_session_type() + + +class X2goTerminalSessionSTDOUT(object): + """\ + Class for managing X2go sessions on a remote X2go server via Paramiko/SSH. + With the X2goSession class you can start new X2go sessions, resume suspended + sessions or suspend resp. terminate currently running sessions on a + connected X2go server. + + When suspending or terminating sessions there are two possible ways: + + 1. Initialize an X2go session object, start a new session (or resume) + and use the L{X2goSession.suspend()} or L{X2goSession.terminate()} method + to suspend/terminate the current session object. + 2. Alternatively, you can pass a session name to L{X2goSession.suspend()} + or L{X2goSession.terminate()}. If a session of this name exists on the + X2go server the respective action will be performed on the session. + + An L{X2goSession} object uses two main data structure classes: + + - L{X2goSessionParams}: stores all parameters that have been passed to the + constructor method. + + - L{X2goServerSessionInfo}: when starting or resuming a session, an object of this class + will be used to store all information retrieved from the X2go server. + + @param geometry: screen geometry of the X2go session. Can be either C{<width>x<height>} + or C{fullscreen} + @type geometry: str + @param depth: color depth in bits (common values: C{16}, C{24}) + @type depth: int + @param link: network link quality (either one of C{modem}, C{isdn}, C{adsl}, C{wan} or C{lan}) + @type link: str + @param pack: compression method for NX based session proxying + @type pack: str + @param cache_type: a dummy parameter that is passed to the L{X2goProxy}. In NX Proxy + (class C{X2goNX3Proxy}) this originally is the session name. With X2go it + defines the name of the NX cache directory. Best is to leave it untouched. + @type cache_type: str + @param kblayout: keyboard layout, e.g. C{us} (default), C{de}, C{fr}, ... + @type kblayout: str + @param kbtype: keyboard type, e.g. C{pc105/us} (default), C{pc105/de}, ... + @type kbtype: str + @param session_type: either C{desktop} or C{application} (rootless session) + @type session_type: str + @param snd_system: sound system to be used on server (C{none}, C{pulse} (default), + C{arts} (obsolete) or C{esd}) + @type snd_system: str + @param cmd: command to be run on X2go server after session start (only used + when L{X2goSession.start()} is called, ignored on resume, suspend etc. + @type cmd: str + @param rootdir: X2go session directory, normally C{~/.x2go} + @type rootdir: str + @param proxy_class: other than the default L{X2goProxy} class + @type proxy_class: L{X2goProxy} related instance + @param print_action: either a print action short name (PDFVIEW, PDFSAVE, PRINT, PRINTCMD) or the + resp. C{X2goPrintActionXXX} class (where XXX equals one of the given short names) + @type print_action: str or class + @param print_action_args: optional arguments for a given print_action (for further info refer to + L{X2goPrintActionPDFVIEW}, L{X2goPrintActionPDFSAVE}, L{X2goPrintActionPRINT} and L{X2goPrintActionPRINTCMD}) + @type print_action_args: dict + @param logger: you can pass an L{X2goLogger} object to the + L{X2goProxy} constructor + @type logger: L{X2goLogger} instance + @param loglevel: if no L{X2goLogger} object has been supplied a new one will be + constructed with the given loglevel + @type loglevel: int + + """ + params = None + session_info = None + control_session = None + + proxy_class = None + proxy = None + proxy_subprocess = None + + guardian_thread = None + reverse_tunnels = {} + + print_queue = None + + def __init__(self, control_session, session_info=None, + geometry="800x600", depth=24, link="adsl", pack="16m-jpeg-9", + cache_type="unix-kde", kblayout='us', kbtype='pc105/us', + session_type="application", snd_system='pulse', cmd=None, + rootdir=None, + profile_name='UNKNOWN', profile_id=utils._genSessionProfileId(), + print_action=None, print_action_args={}, + info_backend=X2goServerSessionInfo, + list_backend=X2goServerSessionList, + proxy_backend=X2goProxy, + logger = None, loglevel=log.loglevel_DEFAULT): + """\ + Initialize an X2go session. With the X2goSession class you can start + new X2go sessions, resume suspended sessions or suspend resp. terminate + currently running sessions on a connected X2go server. + + """ + if logger is None: + self.logger = log.X2goLogger(loglevel=loglevel) + else: + self.logger = copy.deepcopy(logger) + self.logger.tag = __NAME__ + + self.control_session = control_session + self.reverse_tunnels = self.control_session.get_transport().reverse_tunnels + + self.params = X2goSessionParams() + + if session_info is not None: + if self.session_info.name: + self.session_info.local_container = os.path.join(self.params.rootdir, 'S-%s' % self.session_info.name) + else: + raise X2goSessionException('no valid session info availble') + else: + self.session_info = info_backend() + + self.params.geometry = geometry + self.params.depth = str(depth) + self.params.link = link + self.params.pack = pack + self.params.cache_type = cache_type + self.params.session_type = session_type + self.params.kblayout = kblayout + self.params.kbtype = kbtype + self.params.snd_system = snd_system + self.params.cmd = cmd + self.params.rootdir = (type(rootdir) is types.StringType) and rootdir or os.path.join(_LOCAL_HOME,_X2GO_SESSION_ROOTDIR) + self.params.update() + + self.proxy_class = proxy_backend + + self.print_action = print_action + self.print_action_args = print_action_args + + self._mk_session_rootdir(self.params.rootdir) + + # each terminal session has its own guardian + self.guardian_thread = guardian.X2goSessionGuardian(self, logger=self.logger) + self.guardian_thread.start() + + + def __del__(self): + self._x2go_tidy_up() + + def _x2go_tidy_up(self): + + if self.proxy is not None: + self.proxy.__del__() + + try: + + if self.control_session.get_transport() is not None: + for _tunnel in [ _tun[1] for _tun in self.reverse_tunnels[self.session_info.name].values() ]: + if _tunnel is not None: + _tunnel.__del__() + + if self.print_queue is not None: + self.print_queue.__del__() + + except AttributeError: + pass + + def _mk_session_rootdir(self, d): + + try: + os.mkdir(d) + except OSError, e: + if e.errno == 17: + # file exists + pass + else: + raise OSError, e + + def get_session_name(self): + """\ + STILL UNDOCUMENTED + + """ + return self.session_info.name + + def start_sound(self): + """\ + Initialize Paramiko/SSH reverse forwarding tunnel for X2go sound. + + Currently supported audio protocols: + + - Pulse Audio + - Esound + + """ + _tunnel = None + if self.reverse_tunnels[self.session_info.name]['snd'][1] is None: + if self.params.snd_system == 'pulse': + self.logger('initializing Pulse Audio sound support in X2go session', loglevel=log.loglevel_INFO) + ### + ### PULSE AUDIO + ### + # setup pulse client config file on X2go server + cmd_line = "echo 'default-server=localhost:%s'>%s/.pulse-client.conf;" % (self.session_info.snd_port, self.session_info.remote_container) + \ + "echo 'cookie-file=%s/.pulse-cookie'>>%s/.pulse-client.conf" % (self.session_info.remote_container, self.session_info.remote_container) + (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) + + self.control_session._x2go_sftp_put(local_path='%s/.pulse-cookie' % _LOCAL_HOME, remote_path='%s/.pulse-cookie' % self.session_info.remote_container) + + # start reverse SSH tunnel for pulse stream + _tunnel = rforward.X2goRevFwTunnel(server_port=self.session_info.snd_port, + remote_host='localhost', + remote_port=4713, + ssh_transport=self.control_session.get_transport(), + logger=self.logger + ) + + elif self.params.snd_system == 'arts': + ### + ### ARTSD AUDIO + ### + self.logger('the ArtsD sound server (as in KDE3) is obsolete and will not be supported by Python X2go...', loglevel=log.loglevel_WARNING) + + elif self.params.snd_system == 'esd': + ### + ### ESD AUDIO + ### + + self.logger('initializing ESD sound support in X2go session', loglevel=log.loglevel_INFO) + self.control_session._x2go_sftp_put(local_path='%s/.esd_auth' % _LOCAL_HOME, remote_path='%s/.esd_auth' % self.control_session._x2go_remote_home) + + # start reverse SSH tunnel for pulse stream + _tunnel = rforward.X2goRevFwTunnel(server_port=self.session_info.snd_port, + remote_host='localhost', + remote_port=16001, + ssh_transport=self.control_session.get_transport(), + logger=self.logger + ) + + + if _tunnel is not None: + self.reverse_tunnels[self.session_info.name]['snd'] = (self.session_info.snd_port, _tunnel) + _tunnel.start() + self.guardian_thread.active_threads.append(_tunnel) + + else: + # tunnel has already been started and might simply need a resume call + self.reverse_tunnels[self.session_info.name]['snd'][1].resume() + + def start_sshfs(self): + """\ + Initialize Paramiko/SSH reverse forwarding tunnel for X2go folder sharing. + + """ + # start reverse SSH tunnel for sshfs (folder sharing, printing) + ssh_transport = self.control_session.get_transport() + if self.reverse_tunnels[self.session_info.name]['sshfs'][1] is None: + + _tunnel = sftpserver.X2goRevFwTunnelToSFTP(server_port=self.session_info.sshfs_port, + ssh_transport=ssh_transport, + auth_key=self.control_session._x2go_session_auth_rsakey, + logger=self.logger + ) + + if _tunnel is not None: + self.reverse_tunnels[self.session_info.name]['sshfs'] = (self.session_info.sshfs_port, _tunnel) + _tunnel.start() + self.guardian_thread.active_threads.append(_tunnel) + + else: + # tunnel has already been started and might simply need a resume call + self.reverse_tunnels[self.session_info.name]['sshfs'][1].resume() + + def _x2go_pause_rev_fw_tunnel(self, name): + # pause reverse SSH tunnel of name <name> + ssh_transport = self.get_transport() + _tunnel = self.reverse_tunnels[self.session_info.name][name][1] + if _tunnel is not None: + _tunnel.pause() + + def stop_sound(self): + """\ + Shutdown (pause) Paramiko/SSH reverse forwarding tunnel for X2go sound. + + """ + self._x2go_pause_rev_fw_tunnel('snd') + + def stop_sshfs(self): + """\ + Shutdown (pause) Paramiko/SSH reverse forwarding tunnel for X2go folder sharing. + + """ + self._x2go_pause_rev_fw_tunnel('sshfs') + + def start_printing(self): + """\ + Initialize X2go print spooling. + + """ + if self.session_info.username not in self.control_session._x2go_remote_group('x2goprint'): + raise x2go_exceptions.X2goSessionException('remote user %s is not member of X2go server group x2goprint' % self.session_info.username) + + spool_dir = os.path.join(self.session_info.local_container, 'spool') + if not os.path.exists(spool_dir): + os.mkdir(spool_dir) + self.share_local_folder(folder_name=spool_dir, folder_type='spool') + self.print_queue = printing.X2goPrintQueue(spool_dir=spool_dir, + print_action=self.print_action, + print_action_args=self.print_action_args, + logger=self.logger, + ) + self.print_queue.start() + self.guardian_thread.active_threads.append(self.print_queue) + + def set_print_action(self, print_action, **kwargs): + """\ + STILL UNDOCUMENTED + + """ + self.print_queue.set_print_action(print_action, logger=self.logger, **kwargs) + + + def stop_printing(self): + """\ + Shutdown (pause) the X2go Print Queue thread. + + """ + if self.print_queue is not None: + self.print_queue.pause() + + def share_local_folder(self, folder_name=None, folder_type='disk'): + """\ + Share a local folder with the X2go session. + + @param folder_name: the full path to an existing folder on the local + file system + @type folder_name: str + @param folder_type: one of 'disk' (a folder on your local hard drive), 'rm' (removeable device), + 'cdrom' (CD/DVD Rom) or 'spool' (for X2go print spooling) + @type folder_type: str + + @return: returns C{True} if the local folder has been successfully mounted within the X2go server session + @rtype: bool + + """ + if self.session_info.username not in self.control_session._x2go_remote_group('fuse'): + raise x2go_exceptions.X2goSessionException('remote user %s is not member of X2go server group fuse' % self.session_info.username) + + if folder_name is None: + self.logger('no folder name given...', log.loglevel_WARN) + return False + + if type(folder_name) is not types.StringType: + self.logger('folder name needs to be of type StringType...', log.loglevel_WARN) + return False + + if not os.path.exists(folder_name): + self.logger('local folder does not exist: %s' % folder_name, log.loglevel_WARN) + return False + + self.logger('sharing local folder: %s' % folder_name, log.loglevel_INFO) + + _auth_rsakey = self.control_session._x2go_session_auth_rsakey + _host_rsakey = defaults.RSAHostKey + + _tmp_io_object = cStringIO.StringIO() + _auth_rsakey.write_private_key(_tmp_io_object) + _tmp_io_object.write('----BEGIN RSA IDENTITY----') + _tmp_io_object.write('%s %s' % (_host_rsakey.get_name(),_host_rsakey.get_base64(),)) + + _x2go_key_fname = os.path.join(os.path.dirname(self.session_info.remote_container), 'ssh', 'key.z%s' % self.session_info.agent_pid) + _x2go_key_bundle = _tmp_io_object.getvalue() + + self.control_session._x2go_sftp_write(_x2go_key_fname, _x2go_key_bundle) + + if folder_type is 'disk': + + cmd_line = [ 'export HOSTNAME &&', + 'x2gomountdirs', + 'dir', + str(self.session_info.name), + _CURRENT_LOCAL_USER, + _x2go_key_fname, + '%s__REVERSESSH_PORT__%s; ' % (folder_name, self.session_info.sshfs_port), + 'rm -f %s %s.ident' % (_x2go_key_fname, _x2go_key_fname), + ] + + elif folder_type is 'spool': + + cmd_line = [ 'export HOSTNAME &&', + 'x2gomountdirs', + 'dir', + str(self.session_info.name), + _CURRENT_LOCAL_USER, + _x2go_key_fname, + '%s__PRINT_SPOOL___REVERSESSH_PORT__%s; ' % (folder_name, self.session_info.sshfs_port), + 'rm -f %s %s.ident' % (_x2go_key_fname, _x2go_key_fname), + ] + + (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) + self.logger('x2gomountdirs output is : %s' % stdout.read().split('\n'), log.loglevel_INFO) + + def run_command(self, cmd=None): + """\ + Run a command in this session. + + After L{X2goSession.start()} has been called + one or more commands can be executed with L{X2goSession.run_command()} + within the current X2go session. + + @param cmd: Command to be run + @type cmd: str + + @return: stdout.read() and stderr.read() as returned by the run command + on the X2go server + @rtype: tuple of str + + """ + if cmd in ("", None): + if self.params.cmd is None: + cmd = 'TERMINAL' + else: + cmd = self.params.cmd + + self.params.update({'cmd': cmd}) + + cmd_line = [ "setsid x2goruncommand", + str(self.session_info.display), + str(self.session_info.agent_pid), + str(self.session_info.name), + str(self.session_info.snd_port), + _rewrite_cmd(self.params.cmd), + str(self.params.snd_system), + str(self.params.session_type), + ">& /dev/null & exit", + ] + + if self.params.snd_system is 'pulse': + cmd_line = [ 'PULSE_CLIENTCONFIG=%s/.pulse-client.conf' % self.session_info.remote_container ] + cmd_line + + (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) + + return stdout.read(), stderr.read() + + def ok(self): + """\ + Returns C{True} if this X2go session is up and running, + C{False} else + + @return: X2go session OK? + @rtype: bool + + """ + return bool(self.session_info.name and (self.proxy_subprocess and self.proxy_subprocess.poll() is None)) + + def is_running(self): + """\ + Returns C{True} if this X2go session is in running state, + C{False} else. + + @return: X2go session running? + @rtype: bool + + """ + return self.session_info.is_running() + + def is_suspended(self): + """\ + Returns C{True} if this X2go session is in suspended state, + C{False} else. + + @return: X2go session suspended? + @rtype: bool + + """ + return self.session_info.is_suspended() + + def start(self): + """\ + Start a new X2go session. + + The L{X2goTerminalSession.start()} method accepts any parameter + that can be passed to the class constructor. + + """ + setkbd = "0" + if self.params.kblayout or self.params.kbtype: + setkbd = "1" + + cmd_line = [ "x2gostartagent", + str(self.params.geometry), + str(self.params.link), + str(self.params.pack), + str(self.params.cache_type+'-depth_'+self.params.depth), + str(self.params.kblayout), + str(self.params.kbtype), + str(setkbd), + str(self.params.session_type), + self.params.cmd, + ] + + (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) + + self.session_info.initialize(stdout.read(), + username=self.control_session.get_transport().get_username(), + hostname=self.control_session.get_transport().getpeername(), + ) + + # local path may be a Windows path, so we use the path separator of the local system + self.session_info.local_container = os.path.join(self.params.rootdir, 'S-%s' % self.session_info.name) + # remote path is always a UniX path... + self.session_info.remote_container = '%s/%s/C-%s' % (self.control_session._x2go_remote_home, + _X2GO_SESSION_ROOTDIR, + self.session_info.name, + ) + + # set up SSH tunnel for X11 graphical elements + self.proxy = self.proxy_class(session_info=self.session_info, ssh_transport=self.control_session.get_transport(), logger=self.logger) + self.proxy_subprocess = self.proxy.start_proxy() + self.guardian_thread.active_threads.append(self.proxy) + + self.associated = True + return self.ok() + + def resume(self): + """\ + Resume a running/suspended X2go session. + + The L{X2goSession.resume()} method accepts any parameter + that can be passed to the class constructor. + + @return: True if the session could be successfully resumed + @rtype: bool + + """ + setkbd = "0" + if self.params.kblayout or self.params.kbtype: + setkbd = "1" + + cmd_line = [ "x2goresume-session", self.session_info.name, + self.params.geometry, + self.params.link, + self.params.pack, + self.params.kblayout, + self.params.kbtype, + setkbd, + ] + + (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) + + self.proxy = self.proxy_class(self.session_info, self.control_session.get_transport(), logger=self.logger) + self.proxy_subprocess = self.proxy.start_proxy() + + # local path may be a Windows path, so we use the path separator of the local system + self.session_info.local_container = os.path.join(self.params.rootdir, 'S-%s' % self.session_info.name) + # remote path is always a UniX path... + self.session_info.remote_container = '%s/%s/C-%s' % (self.control_session._x2go_remote_home, + _X2GO_SESSION_ROOTDIR, + self.session_info.name, + ) + return self.ok() + + def suspend(self): + """\ + Suspend this X2go session terminal. + + @return: True if the session terminal could be successfully suspended + @rtype: bool + + """ + self.logger('suspending associated session: %s' % self.session_info, log.loglevel_DEBUG) + (stdin, stdout, stderr) = self.control_session._x2go_exec_command("x2gosuspend-session %s" % self.session_info, loglevel=log.loglevel_DEBUG) + dummy_stdout = stdout.read() + dummy_stderr = stderr.read() + self.associated = False + self._x2go_tidy_up() + # TODO: check if session has really suspended + _ret = True + + return _ret + + def terminate(self, session_name=None): + """\ + Terminate this X2go session. + + @return: True if the session terminal could be successfully terminate + @rtype: bool + + """ + self.logger('terminating associated session: %s' % self.session_info, log.loglevel_INFO) + (stdin, stdout, stderr) = self.control_session._x2go_exec_command("x2goterminate-session %s" % self.session_info, loglevel=log.loglevel_DEBUG) + dummy_stdout = stdout.read() + dummy_stderr = stderr.read() + self.session_info.clear() + self.associated = False + self._x2go_tidy_up() + # TODO: check if session has really suspended + _ret = True + + return _ret diff --git a/x2go/client.py b/x2go/client.py index 9f2a18d..74c813d 100644 --- a/x2go/client.py +++ b/x2go/client.py @@ -124,9 +124,7 @@ import sys # Python X2go modules from settings import X2goClientSettings from printing import X2goClientPrinting -from profiles import X2goSessionProfiles from registry import X2goSessionRegistry -from session import X2goSession, _X2GO_SESSION_OPTIONS import log import utils @@ -135,6 +133,8 @@ from defaults import LOCAL_HOME as _LOCAL_HOME from defaults import CURRENT_LOCAL_USER as _CURRENT_LOCAL_USER from defaults import X2GO_CLIENT_ROOTDIR as _X2GO_CLIENT_ROOTDIR +from x2go.backends.profiles import X2goSessionProfiles + class X2goClient(object): """\ The X2goClient implements _THE_ public Python X2go API. With it you can @@ -278,20 +278,22 @@ class X2goClient(object): if k in kwargs.keys(): _params[k] = kwargs[k] + server = _params['server'] + del _params['server'] + else: if server is None: return None _profile_id = utils._genSessionProfileId() _profile_name = profile_name or sys.argv[0] _params = kwargs - _params['server'] = server _params['printing'] = printing _params['share_local_folders'] = share_local_folders - session_uuid = self.session_registry.register(_profile_id, _profile_name, **_params ) + session_uuid = self.session_registry.register(server=server, profile_id=_profile_id, profile_name=_profile_name, **_params ) - connect_options = self.session_registry(session_uuid).connect_options - session_options = self.session_registry(session_uuid).session_options + control_params = self.session_registry(session_uuid).control_params + terminal_params = self.session_registry(session_uuid).terminal_params self.logger('initializing X2go session...', log.loglevel_NOTICE, tag=self._logger_tag) if return_object: @@ -356,7 +358,7 @@ class X2goClient(object): @rtype: obj """ - return self.session_registry(session_uuid).session_object + return self.session_registry(session_uuid) __get_session = get_session with_session = __get_session """Alias for L{get_session()}.""" @@ -548,7 +550,7 @@ class X2goClient(object): for session in self.session_registry.running_sessions: if session_name == session.get_session_name(): return session.suspend() - return self.session_registry(session_uuid).session_object.suspend(session_name=session_name) + return self.session_registry(session_uuid).suspend(session_name=session_name) __suspend_session = suspend_session def terminate_session(self, session_uuid, session_name=None): @@ -587,7 +589,7 @@ class X2goClient(object): for session in self.session_registry.running_sessions + self.session_registry.suspended_sessions: if session_name == session.get_session_name(): return session.terminate() - return self.session_registry(session_uuid).session_object.terminate(session_name=session_name) + return self.session_registry(session_uuid).terminate(session_name=session_name) __terminate_session = terminate_session def get_session_profile_name(self, session_uuid): @@ -887,10 +889,8 @@ class X2goClient(object): @type session_uuid: C{str} """ - session = self.session_registry(session_uuid).session_object - session_infos = session.list_sessions() - for session_info in session_infos.values(): - session.terminate(session_name=session_info) + session = self.session_registry(session_uuid) + session.clean_sessions() __clean_sessions = clean_sessions def list_sessions(self, session_uuid): @@ -908,7 +908,7 @@ class X2goClient(object): @type session_uuid: C{str} """ - session = self.session_registry(session_uuid).session_object + session = self.session_registry(session_uuid) return session.list_sessions() __list_sessions = list_sessions diff --git a/x2go/defaults.py b/x2go/defaults.py index 5df419c..a75e059 100644 --- a/x2go/defaults.py +++ b/x2go/defaults.py @@ -59,6 +59,23 @@ else: ## +## control and terminal session backend as well as session info backend defaults +## + +DEFAULT_CONTROLSESSION_BACKEND = 'X2goControlSessionSTDOUT' +DEFAULT_TERMINALSESSION_BACKEND = 'X2goTerminalSessionSTDOUT' +DEFAULT_SERVERSESSIONINFO_BACKEND = 'X2goServerSessionInfoSTDOUT' +DEFAULT_SERVERSESSIONLIST_BACKEND = 'X2goServerSessionListSTDOUT' +DEFAULT_PROXY_BACKEND = 'X2goProxyNX3' + +## +## profile backend defaults +## + +DEFAULT_SESSIONPROFILES_BACKEND = 'X2goSessionProfilesFILE' + + +## ## X2go Printing ## @@ -214,13 +231,6 @@ _pack_methods_nx3 = [ m for m in pack_methods_nx3 if "%" not in m ] for meth in [ m for m in pack_methods_nx3 if "%" in m ]: _pack_methods_nx3 += [ meth.replace('%','%s' % str(i)) for i in range(0,10) ] - -## -## THESE ARE NOT NEEDED!!!! THERE IS A METHOD IN utils.py that does the job... -## -X2GO_INIPARMS_TO_SESSION_PARMS = (('soundsystem','snd_system'), ('command','cmd'),('host','server'),('user', 'username'), - ('key', 'key_filename'),('layout','kblayout'),('type','kbtype'), ('sshport', 'port')) - ## ## X2go session defaults ## @@ -239,16 +249,11 @@ window_managers={ RSAKEY_STRENGTH = 1024 RSAHostKey = paramiko.RSAKey.generate(RSAKEY_STRENGTH) -from printing import X2goPrintActionPDFVIEW -from printing import X2goPrintActionPDFSAVE -from printing import X2goPrintActionPRINT -from printing import X2goPrintActionPRINTCMD - X2GO_PRINT_ACTIONS = { - 'PDFVIEW': X2goPrintActionPDFVIEW, - 'PDFSAVE': X2goPrintActionPDFSAVE, - 'PRINT': X2goPrintActionPRINT, - 'PRINTCMD': X2goPrintActionPRINTCMD, + 'PDFVIEW': 'X2goPrintActionPDFVIEW', + 'PDFSAVE': 'X2goPrintActionPDFSAVE', + 'PRINT': 'X2goPrintActionPRINT', + 'PRINTCMD': 'X2goPrintActionPRINTCMD', } """Relating print action names and classes.""" diff --git a/x2go/printing.py b/x2go/printing.py index 3091975..41bc685 100644 --- a/x2go/printing.py +++ b/x2go/printing.py @@ -531,7 +531,7 @@ class X2goPrintQueue(threading.Thread): print_action = defaults.X2GO_PRINT_ACTIONS[print_action] if print_action in defaults.X2GO_PRINT_ACTIONS.values(): - self.print_action = print_action(**kwargs) + self.print_action = eval ('%s(**kwargs)' % print_action) def run(self): """\ diff --git a/x2go/registry.py b/x2go/registry.py index 79c5da9..89727cf 100644 --- a/x2go/registry.py +++ b/x2go/registry.py @@ -29,351 +29,17 @@ import time import threading # Python X2go modules -import profiles import log import utils import session from x2go_exceptions import * - -class X2goRegisteredSession(): - - def __init__(self, logger=None, loglevel=log.loglevel_DEFAULT): - - if logger is None: - self.logger = log.X2goLogger(loglevel=loglevel) - else: - self.logger = copy.deepcopy(logger) - self.logger.tag = __NAME__ - - self._keep_alive = True - - self.uuid = uuid.uuid1() - self.connected = False - self.running = False - self.suspended = False - self.terminated = False - self.logger('starting threaded X2goRegisteredSession', loglevel=log.loglevel_DEBUG) - - def __str__(self): - return self.__get_uuid() - def __repr__(self): - result = 'X2goRegisteredSession(' - for p in dir(self): - if '__' in p or not p in self.__dict__ or type(p) is types.InstanceType: continue - result += p + '=' + str(self.__dict__[p]) + ', ' - return result + ')' - def __call__(self): - return self.__get_uuid() - - def get_uuid(self): - """\ - STILL UNDOCUMENTED - - """ - return str(self.uuid) - __get_uuid = get_uuid - - def get_username(self): - """\ - After a session has been setup up you can query the - username the sessions runs as. - - @return: the remote username the X2go session runs as - @rtype: C{str} - - """ - return self.session_object.get_transport().get_username() - __get_username = get_username - - def get_password(self): - """\ - After a session has been setup up you can query the - username's password from the session. - - @return: the username's password - @rtype: C{str} - - """ - return self.session_object._session_password - - def get_server(self): - """\ - After a session has been setup up you can query the - hostname of the host the sessions is connected to (or - about to connect to). - - @return: the hostname of the server the X2go session is - connected to (as an C{(addr,port)} tuple) - @rtype: tuple - - """ - return self.session_object.get_transport().getpeername() - __get_server = get_server - - def get_session_name(self): - """\ - Retrieve the server-side X2go session name for the session that has - been registered under C{profile_id}. - - @return: X2go session name - @rtype: C{str} - - """ - return str(self.session_object.session_info) or None - __get_session_name = get_session_name - - def connect(self, username='', password='', add_to_known_hosts=False, force_password_auth=False): - """\ - Connect to a registered X2go session with registry hash C{<session_uuid>}. - This method basically wraps around paramiko.SSHClient.connect() for the - corresponding session. - - @param username: the username for the X2go server that is going to be - connected to (as a last minute way of changing the session username) - @type username: C{str} - @param password: the user's password for the X2go server that is going to be - connected to - @type password: C{str} - @param add_to_known_hosts: non-paramiko option, if C{True} paramiko.AutoAddPolicy() - is used as missing-host-key-policy. If set to C{False} paramiko.RejectPolicy() - is used - @type add_to_known_hosts: C{bool} - @param force_password_auth: disable SSH pub/priv key authentication mechanisms - completely - @type force_password_auth: C{bool} - - """ - # do connect - connect_options = self.connect_options - connect_options['password'] = password - if username: - connect_options['username'] = username - connect_options['force_password_auth'] = force_password_auth - self.connected = self.session_object.connect(self.server, **connect_options) - return self.connected - __connect = connect - - def disconnect(self): - """\ - STILL UNDOCUMENTED - - """ - self.session_object.disconnect() - self.connected = False - self.running = False - self.suspended = False - self.terminated = False - __disconnect = disconnect - - def set_print_action(self, print_action, **kwargs): - """\ - STILL UNDOCUMENTED - - """ - if type(print_action) is not types.StringType: - return False - self.session_object.set_print_action(print_action, **kwargs) - __set_print_action = set_print_action - - def start(self): - """\ - Start a new X2go session on the remote X2go server. - - """ - session = self.session_object - if session.start(): - - if session.params.snd_system is not 'none': - session.start_sound() - - session.start_sshfs() - if self.printing: - session.start_printing() - - if self.share_local_folders: - if session.get_transport().reverse_tunnels['sshfs'][1] is not None: - for _folder in self.share_local_folders: - session.share_local_folder(_folder) - - session.run_command() - self.suspended = False - self.running = True - self.terminated = False - - return self.running - __start = start - - def resume(self, session_name): - """\ - Resume or continue a suspended / running X2go session on the - remote X2go server. - - @param session_name: the server-side name of an X2go session - @type session_name: C{str} - - """ - self.session_object.associate(session_name) - if self.session_object.resume(): - - if self.session_object.params.snd_system is not 'none': - self.session_object.start_sound() - - self.session_object.start_sshfs() - if self.printing: - self.session_object.start_printing() - - self.suspended = False - self.running = True - self.terminated = False - return True - - return False - __resume = resume - - def suspend(self): - """\ - Suspend an X2go session. - - You can either suspend a session that you have formerly - started/resumed the current X2goClient instance. - - Or you can suspend a non-attached session by simply - registering an X2go server session and then passing the - server-side X2go session name to this method. - - """ - if self.session_object.suspend(): - - self.running = False - self.suspended = True - return True - - return False - __suspend = suspend - - def terminate(self): - """\ - Terminate an X2go session. - - You can either terminate a session that you have formerly - started/resumed within the current X2goClient instance. - - Or you can terminate a non-attached session by simply - registering an X2go server session and then passing the - server-side X2go session name to this method. - - """ - if self.session_object.terminate(): - - self.running = False - self.suspended = False - self.terminated = True - return True - - return False - __terminate = terminate - - def get_profile_name(self): - """\ - Retrieve the profile name of this registered session. - - @return: X2go client profile name of the session - @rtype: C{str} - - """ - return self.profile_name - __get_profile_name = get_profile_name - - def get_profile_id(self): - """\ - Retrieve this registered session's profile id. - - @return: the session profile's id - @rtype: C{str} - - """ - return self.profile_id - __get_profile_id = get_profile_id - - ### - ### QUERYING INFORMATION - ### - - def session_ok(self): - """\ - Test if this registered X2go session is - in a healthy state. - - @return: C{True} if session is ok, C{False} otherwise - @rtype: C{bool} - - """ - return self.session_object.ok() - __session_ok = session_ok - - - def is_connected(self): - """\ - Test if this registered X2go session is connected to the - remote server. - - @return: C{True} if session is connected, C{False} otherwise - @rtype: C{bool} - - """ - return self.session_object.is_connected() - _is_connected = is_connected - - def is_running(self): - """\ - Test if this registered X2go session is up and running. - - @return: C{True} if session is running, C{False} otherwise - @rtype: C{bool} - - """ - return self.is_connected() and self.session_object.is_running() - _is_running = is_running - - def is_suspended(self): - """\ - Test if this registered X2go session is in suspended state. - - @return: C{True} if session is suspended, C{False} otherwise - @rtype: C{bool} - - """ - return self.is_connected() and self.session_object.is_suspended() - __is_suspended = is_suspended - - def has_terminated(self): - """\ - Test if this registered X2go session has terminated. - - @return: C{True} if session has terminated, C{False} otherwise - @rtype: C{bool} - - """ - return self.is_connected() and self.session_object.has_terminated() - __has_terminated = has_terminated - - def share_local_folder(self, folder_name): - """\ - Share a local folder with this registered X2go session. - - @param folder_name: the full path to an existing folder on the local - file system - @type folder_name: C{str} - - @return: returns C{True} if the local folder has been successfully mounted within - this registered X2go server session - @rtype: C{bool} - - """ - return self.session_object.share_local_folder(folder_name=folder_name) - __share_local_folder = share_local_folder - +# import the default terminal session backend +from x2go.backends.control import X2goControlSession +from x2go.backends.terminal import X2goTerminalSession +from x2go.backends.proxy import X2goProxy +from x2go.backends.info import X2goServerSessionInfo +from x2go.backends.info import X2goServerSessionList class X2goSessionRegistry(object): """\ @@ -392,6 +58,7 @@ class X2goSessionRegistry(object): self.logger.tag = __NAME__ self.registry = {} + self.control_sessions = {} def __repr__(self): result = 'X2goSessionRegistry(' @@ -421,53 +88,39 @@ class X2goSessionRegistry(object): """ return self(session_uuid).profile.profile_name - def register(self, profile_id, profile_name, **kwargs): - - _r = X2goRegisteredSession(logger=self.logger) - session_uuid = _r() - self.registry[session_uuid] = _r - self(session_uuid).profile_id = profile_id - self(session_uuid).profile_name = profile_name - self(session_uuid).session_params = kwargs - self(session_uuid).server = kwargs['server'] - del kwargs['server'] - self(session_uuid).printing = kwargs['printing'] - del kwargs['printing'] - self(session_uuid).share_local_folders = kwargs['share_local_folders'] - del kwargs['share_local_folders'] - self(session_uuid).session_params = kwargs - # differentiate SSH options from X2go options - _session_options = copy.deepcopy(kwargs) - _connect_options = copy.deepcopy(kwargs) - - for k in kwargs.keys(): - if k in session._X2GO_SESSION_OPTIONS: - del _connect_options[k] - else: - del _session_options[k] - + def register(self, server, profile_id, profile_name, + control_backend=X2goControlSession, + terminal_backend=X2goTerminalSession, + info_backend=X2goServerSessionInfo, + list_backend=X2goServerSessionList, + proxy_backend=X2goProxy, + **kwargs): + + control_session = None + if profile_id in self.control_sessions.keys(): + control_session = control_sessions[profile_id] + + s = session.X2goSession(server=server, control_session=control_session, + profile_id=profile_id, profile_name=profile_name, + control_backend=control_backend, + terminal_backend=terminal_backend, + info_backend=info_backend, + list_backend=list_backend, + proxy_backend=proxy_backend, + logger=self.logger, **kwargs) + + session_uuid = s._X2goSession__get_uuid() self.logger('registering X2go session %s...' % profile_name, log.loglevel_NOTICE) self.logger('registering X2go session with UUID %s' % session_uuid, log.loglevel_DEBUG) - self.logger('X2go session options for profile %s:' % profile_name, log.loglevel_DEBUG) - for k in _session_options: - self.logger(' %s: %s' % (k, _session_options[k]), log.loglevel_DEBUG) - - self.logger('Paramiko connect options for profile %s are:' % profile_name, log.loglevel_DEBUG) - for k in _connect_options: - self.logger(' %s: %s' % (k,_connect_options[k]), log.loglevel_DEBUG) - - self(session_uuid).session_object = session.X2goSession(logger=self.logger, **_session_options) - self(session_uuid).connected = False - self(session_uuid).running = False - self(session_uuid).suspended = False - self(session_uuid).terminated = False - self(session_uuid).connect_options = _connect_options - self(session_uuid).session_options = _session_options + + self.registry[session_uuid] = s + if profile_id not in self.control_sessions.keys(): + self.control_sessions[profile_id] = s.get_control_session() return session_uuid def _sessionsWithState(self, state): - return [ session for session in self.registry.values() if eval('session.%s' % state) ] + return [ ts for ts in self.registry.values() if eval('ts.%s' % state) ] @property def connected_sessions(self): diff --git a/x2go/rforward.py b/x2go/rforward.py index 8161338..9c9ee19 100644 --- a/x2go/rforward.py +++ b/x2go/rforward.py @@ -57,13 +57,16 @@ def x2go_transport_tcp_handler(chan, (origin_addr, origin_port), (server_addr, s transport = chan.get_transport() transport._queue_incoming_channel(chan) rev_tuns = transport.reverse_tunnels - if int(server_port) in [ int(tunnel[0]) for tunnel in rev_tuns.values() ]: - if rev_tuns['snd'] is not None and int(server_port) == int(rev_tuns['snd'][0]): - rev_tuns['snd'][1].notify() + for session_name in rev_tuns.keys(): - elif rev_tuns['sshfs'] is not None and int(server_port) == int(rev_tuns['sshfs'][0]): - rev_tuns['sshfs'][1].notify() + if int(server_port) in [ int(tunnel[0]) for tunnel in rev_tuns[session_name].values() ]: + + if rev_tuns[session_name]['snd'] is not None and int(server_port) == int(rev_tuns[session_name]['snd'][0]): + rev_tuns[session_name]['snd'][1].notify() + + elif rev_tuns[session_name]['sshfs'] is not None and int(server_port) == int(rev_tuns[session_name]['sshfs'][0]): + rev_tuns[session_name]['sshfs'][1].notify() class X2goRevFwTunnel(threading.Thread): @@ -201,6 +204,7 @@ class X2goRevFwTunnel(threading.Thread): self.logger('waiting for incoming data channel on X2go server port: [localhost]:%s' % self.server_port, loglevel=log.loglevel_DEBUG) self.incoming_channel.wait() + if self._keepalive: self.logger('detected incoming data channel on X2go server port: [localhost]:%s' % self.server_port, loglevel=log.loglevel_DEBUG) _chan = self.ssh_transport.accept() diff --git a/x2go/session.py b/x2go/session.py index dbe7ab6..77c2dfa 100644 --- a/x2go/session.py +++ b/x2go/session.py @@ -18,1143 +18,443 @@ # 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. """\ -X2goSession class - core functions for handling your individual X2go sessions. - +X2goSession class - the X2goClient's session backend """ __NAME__ = 'x2gosession-pylib' -# modules -import os, sys, types -import paramiko -import gevent -import threading -import signal -import cStringIO import copy +import types +import uuid +import time +import threading # Python X2go modules -import proxy -import rforward -import sftpserver -import printing import log -import defaults import utils -import x2go_exceptions -import guardian +import session +from x2go_exceptions import * -from cleanup import x2go_cleanup - -# for debugging code -import pprint - -# we hide the default values from epydoc (that's why we transform them to _UNDERSCORE variables) -from defaults import LOCAL_HOME as _LOCAL_HOME -from defaults import CURRENT_LOCAL_USER as _CURRENT_LOCAL_USER -from defaults import X2GO_SESSION_ROOTDIR as _X2GO_SESSION_ROOTDIR +from x2go.backends.control import X2goControlSession +from x2go.backends.terminal import X2goTerminalSession +from x2go.backends.info import X2goServerSessionInfo +from x2go.backends.info import X2goServerSessionList +from x2go.backends.proxy import X2goProxy +from x2go.backends.profiles import X2goSessionProfiles # options of the paramiko.SSHClient().connect() -_X2GO_SESSION_OPTIONS = ('geometry', 'depth', 'link', 'pack', - 'cache_type', 'kblayout', 'kbtype', - 'session_type', 'snd_system', 'cmd', - 'rootdir', 'loglevel', 'profile_name', 'profile_id', - 'print_action', 'print_action_args', - 'proxy_class', 'logger', +_X2GO_SESSION_PARAMS = ('geometry', 'depth', 'link', 'pack', + 'cache_type', 'kblayout', 'kbtype', + 'session_type', 'snd_system', 'cmd', + 'rootdir', 'loglevel', 'profile_name', 'profile_id', + 'print_action', 'print_action_args', + 'proxy_class', 'logger', ) +class X2goSession(object): -def _rewrite_cmd(cmd): - - # start with an empty string - cmd = cmd or '' - - # find window manager commands - if cmd in defaults.window_managers.keys(): - cmd = defaults.window_managers[cmd] - - # X2go run command replace X2GO_SPACE_CHAR string with blanks - cmd.replace(" ", "X2GO_SPACE_CHAR") - - # place quot marks around cmd if not empty string - if cmd: - cmd = '"%s"' % cmd - return cmd - - -class X2goSessionParams(object): - """\ - The L{X2goSessionParams} class is used to store all parameters that - L{X2goSession} objects are constructed with. - - """ - def rewrite_session_type(self): - """\ - Rewrite the X2go session type, so that the X2go server - can understand it (C{desktop} -> C{D}). - - Also if the object's C{command} property is a known window - manager, the session type will be set to 'D' - (i.e. desktop). - - @return: 'D' if session should probably a desktop session, - 'R' (for rootless) else - @rtype: str - - """ - session_type = self.session_type - cmd = self.cmd - if session_type == "desktop": - self.session_type = 'D' - return - if cmd: - if cmd in defaults.window_managers.keys(): - self.session_type = 'D' - return - if os.path.basename(cmd) in defaults.window_managers.values(): - self.session_type = 'D' - return - self.session_type = 'R' - - def update(self, properties_to_be_updated={}): - """\ - Update all properties in the object L{X2goSessionParams} object from - the passed on dictionary. - - @param properties_to_be_updated: a dictionary with L{X2goSessionParams} - property names as keys und their values to be update in - L{X2goSessionParams} object. - @type properties_to_be_updated: dict - - """ - for key in properties_to_be_updated.keys(): - setattr(self, key, properties_to_be_updated[key] or '') - self.rewrite_session_type() - - -class X2goServerSessionInfo(object): - """\ - L{X2goServerSessionInfo} is used to store all information - that is retrieved from the connected X2go server on - L{X2goSession.start()} resp. L{X2goSession.resume()}. - - """ - def __str__(self): - return self.name - def __repr__(self): - return "<%s instance: %s>" % (self.__class__, self.name) - - def _parse_x2golistsessions_line(self, x2go_output): - """\ - Parse a single line of X2go's listsessions output. - - """ - l = x2go_output.split("|") - self.name = l[1] - self.cookie = l[6] - self.agent_pid = int(l[0]) - self.display = int(l[2]) - self.status = l[4] - self.graphics_port = int(l[8]) - self.snd_port = int(l[9]) - self.sshfs_port = int(l[13]) - self.username = l[11] - self.hostname = l[3] - # TODO: turn into datetime object - self.date_created = l[5] - # TODO: turn into datetime object - self.date_suspended = l[10] - self.local_container = '' - - def _parse_x2gostartagent_output(self, x2go_output): - """\ - Parse x2gostartagent output. - - """ - l = x2go_output.split("\n") - self.name = l[3] - self.cookie = l[1] - self.agent_pid = int(l[2]) - self.display = int(l[0]) - self.graphics_port = int(l[4]) - self.snd_port = int(l[5]) - self.sshfs_port = int(l[6]) - self.username = '' - self.hostname = '' - # TODO: we have to see how we fill these fields here... - self.date_created = '' - self.date_suspended = '' - # TODO: presume session is running after x2gostartagent, this could be better - self.status = 'R' - self.local_container = '' - self.remote_container = '' - - def initialize(self, x2go_output, username='', hostname='', local_container='', remote_container=''): - """\ - Parse X2go server's C{x2gostartagent} stdout values. - - @param x2go_output: X2go server's C{x2gostartagent} command output, each value - separated by a newline character. - @type x2go_output: str - @param username: session user name - @type username: str - @param hostname: hostname of X2go server - @type hostname: str - @param local_container: X2go client session directory for config files, cache and session logs - @type local_container: str - @param remote_container: X2go server session directory for config files, cache and session logs - @type remote_container: str - - """ - self._parse_x2gostartagent_output(x2go_output) - self.username = username - self.hostname = hostname - self.local_container = local_container - self.remote_container = remote_container - - def clear(self): - """\ - Clear all properties of a L{X2goServerSessionInfo} object. - - """ - self.name = '' - self.cookie = '' - self.agent_pid = '' - self.display = '' - self.graphics_port = '' - self.snd_port = '' - self.sshfs_port = '' - self.username = '' - self.hostname = '' - self.date_created = '' - self.date_suspended = '' - self.status = '' - self.local_container = '' - self.remote_container = '' - - __init__ = clear - - -class X2goServerSessionList(object): - """\ - L{X2goServerSessionList} is used to store all information - that is retrieved from a connected X2go server on a - L{X2goSession.list_sessions()} call. - - """ - def __init__(self, x2go_output): - """\ - @param x2go_output: X2go server's C{x2golistsessions} command output, each - session separated by a newline character. Session values are separated - by Unix Pipe Symbols ('|') - @type x2go_output: str - - """ - self.sessions = {} - lines = x2go_output.split("\n") - for line in lines: - if not line: - continue - s_info = X2goServerSessionInfo() - s_info._parse_x2golistsessions_line(line) - self.sessions[s_info.name] = s_info - - -class X2goSession(paramiko.SSHClient): - """\ - Class for managing X2go sessions on a remote X2go server via Paramiko/SSH. - With the X2goSession class you can start new X2go sessions, resume suspended - sessions or suspend resp. terminate currently running sessions on a - connected X2go server. - - When suspending or terminating sessions there are two possible ways: - - 1. Initialize an X2go session object, start a new session (or resume) - and use the L{X2goSession.suspend()} or L{X2goSession.terminate()} method - to suspend/terminate the current session object. - 2. Alternatively, you can pass a session name to L{X2goSession.suspend()} - or L{X2goSession.terminate()}. If a session of this name exists on the - X2go server the respective action will be performed on the session. - - An L{X2goSession} object uses two main data structure classes: - - - L{X2goSessionParams}: stores all parameters that have been passed to the - constructor method. - - - L{X2goServerSessionInfo}: when starting or resuming a session, an object of this class - will be used to store all information retrieved from the X2go server. - - @param geometry: screen geometry of the X2go session. Can be either C{<width>x<height>} - or C{fullscreen} - @type geometry: str - @param depth: color depth in bits (common values: C{16}, C{24}) - @type depth: int - @param link: network link quality (either one of C{modem}, C{isdn}, C{adsl}, C{wan} or C{lan}) - @type link: str - @param pack: compression method for NX based session proxying - @type pack: str - @param cache_type: a dummy parameter that is passed to the L{X2goProxy}. In NX Proxy - (class C{X2goNX3Proxy}) this originally is the session name. With X2go it - defines the name of the NX cache directory. Best is to leave it untouched. - @type cache_type: str - @param kblayout: keyboard layout, e.g. C{us} (default), C{de}, C{fr}, ... - @type kblayout: str - @param kbtype: keyboard type, e.g. C{pc105/us} (default), C{pc105/de}, ... - @type kbtype: str - @param session_type: either C{desktop} or C{application} (rootless session) - @type session_type: str - @param snd_system: sound system to be used on server (C{none}, C{pulse} (default), - C{arts} (obsolete) or C{esd}) - @type snd_system: str - @param cmd: command to be run on X2go server after session start (only used - when L{X2goSession.start()} is called, ignored on resume, suspend etc. - @type cmd: str - @param rootdir: X2go session directory, normally C{~/.x2go} - @type rootdir: str - @param proxy_class: other than the default L{X2goProxy} class - @type proxy_class: L{X2goProxy} related instance - @param print_action: either a print action short name (PDFVIEW, PDFSAVE, PRINT, PRINTCMD) or the - resp. C{X2goPrintActionXXX} class (where XXX equals one of the given short names) - @type print_action: str or class - @param print_action_args: optional arguments for a given print_action (for further info refer to - L{X2goPrintActionPDFVIEW}, L{X2goPrintActionPDFSAVE}, L{X2goPrintActionPRINT} and L{X2goPrintActionPRINTCMD}) - @type print_action_args: dict - @param logger: you can pass an L{X2goLogger} object to the - L{X2goProxy} constructor - @type logger: L{X2goLogger} instance - @param loglevel: if no L{X2goLogger} object has been supplied a new one will be - constructed with the given loglevel - @type loglevel: int - - """ - associated = False - params = None - session_info = None - - proxy_class = None - proxy = None - proxy_subprocess = None - - guardian_thread = None - reverse_tunnels = None - - print_queue = None - - _session_auth_rsakey = None - _remote_home = None - _remote_group = {} - - def __init__(self, - geometry="800x600", depth=24, link="adsl", pack="16m-jpeg-9", - cache_type="unix-kde", kblayout='us', kbtype='pc105/us', - session_type="application", snd_system='pulse', cmd=None, - rootdir=None, proxy_class=None, - profile_name='UNKNOWN', profile_id=utils._genSessionProfileId(), - print_action=None, print_action_args={}, - logger = None, loglevel=log.loglevel_DEFAULT, - *args, **kwargs): - """\ - Initialize an X2go session. With the X2goSession class you can start - new X2go sessions, resume suspended sessions or suspend resp. terminate - currently running sessions on a connected X2go server. + def __init__(self, server, control_session=None, + profile_id=None, profile_name=None, + printing=None, share_local_folders=[], + control_backend=X2goControlSession, + terminal_backend=X2goTerminalSession, + info_backend=X2goServerSessionInfo, + list_backend=X2goServerSessionList, + proxy_backend=X2goProxy, + known_hosts=None, + logger=None, loglevel=log.loglevel_DEFAULT, + **params): - """ if logger is None: self.logger = log.X2goLogger(loglevel=loglevel) else: self.logger = copy.deepcopy(logger) self.logger.tag = __NAME__ - if proxy_class is None: - proxy_class = proxy.DEFAULT_PROXY_CLASS - - self.session_info = X2goServerSessionInfo() - self.params = X2goSessionParams() - - self.params.geometry = geometry - self.params.depth = str(depth) - self.params.link = link - self.params.pack = pack - self.params.cache_type = cache_type - self.params.session_type = session_type - self.params.kblayout = kblayout - self.params.kbtype = kbtype - self.params.snd_system = snd_system - self.params.cmd = cmd - self.params.rootdir = (type(rootdir) is types.StringType) and rootdir or os.path.join(_LOCAL_HOME,_X2GO_SESSION_ROOTDIR) - self.params.update() - - self.proxy_class = proxy_class - - self.print_action = print_action - self.print_action_args = print_action_args - - self._mk_session_rootdir(self.params.rootdir) - paramiko.SSHClient.__init__(self, *args, **kwargs) - - def __del__(self): - self._x2go_tidy_up() - - def _x2go_tidy_up(self): - - if self.proxy is not None: - self.proxy.__del__() - - try: - if self.get_transport() is not None: - for _tunnel in [ _tun[1] for _tun in self.get_transport().reverse_tunnels.values() ]: - if _tunnel is not None: - _tunnel.__del__() - - self.get_transport().stop_thread() - - if self.print_queue is not None: - self.print_queue.__del__() - - except AttributeError: - pass - - def _mk_session_rootdir(self, d): - - try: - os.mkdir(d) - except OSError, e: - if e.errno == 17: - # file exists - pass + self._keep_alive = True + + self.uuid = uuid.uuid1() + self.connected = False + self.running = False + self.suspended = False + self.terminated = False + + self.profile_id = profile_id + self.profile_name = profile_name + self.server = server + self.printing = printing + self.share_local_folders = share_local_folders + self._control_backend = control_backend + self._terminal_backend = terminal_backend + self._info_backend = info_backend + self._list_backend = list_backend + self._proxy_backend = proxy_backend + _terminal_params = copy.deepcopy(params) + _control_params = copy.deepcopy(params) + + for p in params.keys(): + if p in session._X2GO_SESSION_PARAMS: + del _control_params[p] else: - raise OSError, e - - def _x2go_exec_command(self, cmd_line, loglevel=log.loglevel_INFO, **kwargs): - - if type(cmd_line) == types.ListType: - cmd = " ".join(cmd_line) - else: - cmd = cmd_line - if self.get_transport() is not None: - - try: - self.logger('executing command on X2go server: %s' % cmd, loglevel) - return self.exec_command(cmd, **kwargs) - except AttributeError: - raise x2go_exceptions.X2goSessionException('the Paramiko/SSH session of X2go session %s has died' % self.session_info) - + del _terminal_params[p] + + self.logger('X2go control session parameters for profile %s:' % profile_name, log.loglevel_DEBUG) + for p in _control_params: + self.logger(' %s: %s' % (p, _control_params[p]), log.loglevel_DEBUG) + self.logger('X2go terminal session parameters for profile %s:' % profile_name, log.loglevel_DEBUG) + for p in _terminal_params: + self.logger(' %s: %s' % (p,_terminal_params[p]), log.loglevel_DEBUG) + + self.control_params = _control_params + self.terminal_params = _terminal_params + + self.logger('starting X2goSession', loglevel=log.loglevel_DEBUG) + if control_session is None: + self.control_session = control_backend(terminal_backend=terminal_backend, + info_backend=info_backend, + list_backend=list_backend, + logger=logger) else: - raise x2go_exceptions.X2goSessionException('the Paramiko/SSH client is not connected') - - def _x2go_sftp_put(self, local_path, remote_path): - - self.logger('sFTP-put: %s -> %s:%s' % (local_path, self.session_info.hostname, remote_path), loglevel=log.loglevel_DEBUG) - self.sftp_client.put(local_path, remote_path) - - def _x2go_sftp_write(self, remote_path, content): - - self.logger('sFTP-write: opening remote file %s on host %s for writing' % (remote_path, self.session_info.hostname), loglevel=log.loglevel_DEBUG) - remote_fileobj = self.sftp_client.open(remote_path, 'w') - self.logger('sFTP-write: writing content: %s' % content, loglevel=log.loglevel_DEBUG_SFTPXFER) - remote_fileobj.write(content) - remote_fileobj.close() - - def _x2go_sftp_remove(self, remote_path): + self.control_session = control_session - self.logger('sFTP-write: removing remote file %s on host %s' % (remote_path, self.session_info.hostname), loglevel=log.loglevel_DEBUG) - self.sftp_client.remove(remote_path) + self.terminal_session = None + self.logger('starting X2goSession', loglevel=log.loglevel_DEBUG) + if known_hosts: + self.control_session.load_host_keys(known_hosts) - @property - def _x2go_remote_home(self): - - if self._remote_home is None: - (stdin, stdout, stderr) = self._x2go_exec_command('echo $HOME') - self._remote_home = stdout.read().split()[0] - self.logger('remote user\' home directory: %s' % self._remote_home, loglevel=log.loglevel_DEBUG) - return self._remote_home - else: - return self._remote_home - - def _x2go_remote_group(self, group): - - if not self._remote_group.has_key(group): - (stdin, stdout, stderr) = self._x2go_exec_command('getent group %s | cut -d":" -f4' % group) - self._remote_group[group] = stdout.read().split('\n')[0].split(',') - self.logger('remote %s group: %s' % (group, self._remote_group[group]), loglevel=log.loglevel_DEBUG) - return self._remote_group[group] - else: - return self._remote_group[group] - - @property - def _x2go_session_auth_rsakey(self): - if self._session_auth_rsakey is None: - self._session_auth_rsakey = paramiko.RSAKey.generate(defaults.RSAKEY_STRENGTH) - return self._session_auth_rsakey - - def connect(self, hostname, port=22, username=None, password=None, pkey=None, - key_filename=None, timeout=None, allow_agent=False, look_for_keys=True, - add_to_known_hosts=False, force_password_auth=False): + def __str__(self): + return self.__get_uuid() + def __repr__(self): + result = 'X2goRegisteredSession(' + for p in dir(self): + if '__' in p or not p in self.__dict__ or type(p) is types.InstanceType: continue + result += p + '=' + str(self.__dict__[p]) + ', ' + return result + ')' + def __call__(self): + return self.__get_uuid() + + def get_uuid(self): """\ - Connect to an X2go server and authenticate to it. This method is directly - inherited from the paramiko.SSHClient module. The features of the Paramiko - SSH client connect method are recited here. The parameters C{add_to_known_hosts} - and C{force_password_auth} have been added as a parameter for X2go. - - The server's host key - is checked against the system host keys (see C{load_system_host_keys}) - and any local host keys (C{load_host_keys}). If the server's hostname - is not found in either set of host keys, the missing host key policy - is used (see C{set_missing_host_key_policy}). The default policy is - to reject the key and raise an C{SSHException}. - - Authentication is attempted in the following order of priority: - - - The C{pkey} or C{key_filename} passed in (if any) - - Any key we can find through an SSH agent - - Any "id_rsa" or "id_dsa" key discoverable in C{~/.ssh/} - - Plain username/password auth, if a password was given - - If a private key requires a password to unlock it, and a password is - passed in, that password will be used to attempt to unlock the key. - - @param hostname: the server to connect to - @type hostname: str - @param port: the server port to connect to - @type port: int - @param username: the username to authenticate as (defaults to the - current local username) - @type username: str - @param password: a password to use for authentication or for unlocking - a private key - @type password: str - @param pkey: an optional private key to use for authentication - @type pkey: C{PKey} - @param key_filename: the filename, or list of filenames, of optional - private key(s) to try for authentication - @type key_filename: str or list(str) - @param timeout: an optional timeout (in seconds) for the TCP connect - @type timeout: float - @param allow_agent: set to False to disable connecting to the SSH agent - @type allow_agent: bool - @param look_for_keys: set to False to disable searching for discoverable - private key files in C{~/.ssh/} - @type look_for_keys: bool - @param add_to_known_hosts: non-paramiko option, if C{True} paramiko.AutoAddPolicy() - is used as missing-host-key-policy. If set to C{False} paramiko.RejectPolicy() - is used - @type add_to_known_hosts: bool - @param force_password_auth: non-paramiko option, disable pub/priv key authentication - completely, even if the C{pkey} or the C{key_filename} parameter is given - @type force_password_auth: bool - - @raise BadHostKeyException: if the server's host key could not be - verified - @raise AuthenticationException: if authentication failed - @raise SSHException: if there was any other error connecting or - establishing an SSH session - @raise socket.error: if a socket error occurred while connecting + STILL UNDOCUMENTED """ - if add_to_known_hosts: - self.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - - # disable pub/priv key authentication if forced - if force_password_auth: - key_filename = None - pkey = None - - self.logger('connecting to %s' % hostname, log.loglevel_NOTICE) - - if (key_filename or pkey): - try: - self.logger('trying SSH pub/priv key authentication with server', log.loglevel_DEBUG) - paramiko.SSHClient.connect(self, hostname, port=port, username=username, pkey=pkey, - key_filename=key_filename, timeout=timeout, allow_agent=allow_agent, - look_for_keys=look_for_keys) - except paramiko.AuthenticationException, e: - if password: - self.logger('next auth mechanism we\'ll try is keyboard-interactive authentication', log.loglevel_DEBUG) - paramiko.SSHClient.connect(self, hostname, port=port, username=username, passwort=password, - timeout=timeout, allow_agent=allow_agent, - look_for_keys=look_for_keys) - else: - raise(e) - - # if there is not private key, we will use the given password - elif password: - self.logger('performing SSH keyboard-interactive authentication with server', log.loglevel_DEBUG) - paramiko.SSHClient.connect(self, hostname, port=port, username=username, password=password, - timeout=timeout, allow_agent=allow_agent, look_for_keys=look_for_keys) - - # authentication failed - else: - raise paramiko.AuthenticationException() - - # if we succeed, we immediately grab us an sFTP client session - self.sftp_client = self.open_sftp() + return str(self.uuid) + __get_uuid = get_uuid - # preparing reverse tunnels - ssh_transport = self.get_transport() - ssh_transport.reverse_tunnels = { - 'snd': (0, None), - 'sshfs': (0, None), - } - - # mark transport as X2goSession - ssh_transport._x2go_session_marker = True + def get_username(self): + """\ + After a session has been setup up you can query the + username the sessions runs as. - # once connected start the X2goSession guardian - self.guardian_thread = guardian.X2goSessionGuardian(self, logger=self.logger) - self.guardian_thread.start() - self.guardian_thread.active_threads.append(self.get_transport()) - self._session_password = password + @return: the remote username the X2go session runs as + @rtype: C{str} - return (self.get_transport() is not None) + """ + return self.control_session.get_transport().get_username() + __get_username = get_username - def disconnect(self): + def get_password(self): """\ - STILL UNDOCUMENTED + After a session has been setup up you can query the + username's password from the session. + + @return: the username's password + @rtype: C{str} """ - self.get_transport().close() + return self.control_session._session_password - def start(self, **kwargs): + def get_server(self): """\ - Start a new X2go session. + After a session has been setup up you can query the + hostname of the host the sessions is connected to (or + about to connect to). - The L{X2goSession.start()} method accepts any parameter - that can be passed to the class constructor. + @return: the hostname of the server the X2go session is + connected to (as an C{(addr,port)} tuple) + @rtype: tuple """ - self.params.update(kwargs) - - _remote_username = self.get_transport().get_username() - if _remote_username not in self._x2go_remote_group('x2gousers'): - raise x2go_exceptions.X2goSessionException('remote user %s is not member of X2go server group x2gousers' % _remote_username) - - setkbd = "0" - if self.params.kblayout or self.params.kbtype: - setkbd = "1" - - cmd_line = [ "x2gostartagent", - str(self.params.geometry), - str(self.params.link), - str(self.params.pack), - str(self.params.cache_type+'-depth_'+self.params.depth), - str(self.params.kblayout), - str(self.params.kbtype), - str(setkbd), - str(self.params.session_type), - self.params.cmd, - ] - - (stdin, stdout, stderr) = self._x2go_exec_command(cmd_line) - - self.session_info.initialize(stdout.read(), - username=_remote_username, - hostname=self.get_transport().getpeername(), - ) - - # local path may be a Windows path, so we use the path separator of the local system - self.session_info.local_container = os.path.join(self.params.rootdir, 'S-%s' % self.session_info.name) - # remote path is always a UniX path... - self.session_info.remote_container = '%s/%s/C-%s' % (self._x2go_remote_home, - _X2GO_SESSION_ROOTDIR, - self.session_info.name, - ) - - # set up SSH tunnel for X11 graphical elements - self.proxy = self.proxy_class(session_info=self.session_info, ssh_transport=self.get_transport(), logger=self.logger) - self.proxy_subprocess = self.proxy.start_proxy() - self.guardian_thread.active_threads.append(self.proxy) - - self.associated = True - return self.ok() - - def start_sound(self): - """\ - Initialize Paramiko/SSH reverse forwarding tunnel for X2go sound. + return self.control_session.get_transport().getpeername() + __get_server = get_server - Currently supported audio protocols: + def get_session_name(self): + """\ + Retrieve the server-side X2go session name for the session that has + been registered under C{profile_id}. - - Pulse Audio - - Esound + @return: X2go session name + @rtype: C{str} """ - _tunnel = None - ssh_transport = self.get_transport() - if ssh_transport.reverse_tunnels['snd'][1] is None: - if self.params.snd_system == 'pulse': - self.logger('initializing Pulse Audio sound support in X2go session', loglevel=log.loglevel_INFO) - ### - ### PULSE AUDIO - ### - # setup pulse client config file on X2go server - cmd_line = "echo 'default-server=localhost:%s'>%s/.pulse-client.conf;" % (self.session_info.snd_port, self.session_info.remote_container) + \ - "echo 'cookie-file=%s/.pulse-cookie'>>%s/.pulse-client.conf" % (self.session_info.remote_container, self.session_info.remote_container) - (stdin, stdout, stderr) = self._x2go_exec_command(cmd_line) - - self._x2go_sftp_put(local_path='%s/.pulse-cookie' % _LOCAL_HOME, remote_path='%s/.pulse-cookie' % self.session_info.remote_container) - - # start reverse SSH tunnel for pulse stream - _tunnel = rforward.X2goRevFwTunnel(server_port=self.session_info.snd_port, - remote_host='localhost', - remote_port=4713, - ssh_transport=ssh_transport, - logger=self.logger - ) - - elif self.params.snd_system == 'arts': - ### - ### ARTSD AUDIO - ### - self.logger('the ArtsD sound server (as in KDE3) is obsolete and will not be supported by Python X2go...', loglevel=log.loglevel_WARNING) - - elif self.params.snd_system == 'esd': - ### - ### ESD AUDIO - ### - - self.logger('initializing ESD sound support in X2go session', loglevel=log.loglevel_INFO) - self._x2go_sftp_put(local_path='%s/.esd_auth' % _LOCAL_HOME, remote_path='%s/.esd_auth' % self._x2go_remote_home) - - # start reverse SSH tunnel for pulse stream - _tunnel = rforward.X2goRevFwTunnel(server_port=self.session_info.snd_port, - remote_host='localhost', - remote_port=16001, - ssh_transport=ssh_transport, - logger=self.logger - ) - - - if _tunnel is not None: - ssh_transport.reverse_tunnels['snd'] = (self.session_info.snd_port, _tunnel) - _tunnel.start() - self.guardian_thread.active_threads.append(_tunnel) + if self.terminal_session is not None: + return self.terminal_session.get_session_name() or None + __get_session_name = get_session_name - else: - # tunnel has already been started and might simply need a resume call - ssh_transport.reverse_tunnels['snd'][1].resume() - - def start_sshfs(self): + def get_session_cmd(self): """\ - Initialize Paramiko/SSH reverse forwarding tunnel for X2go folder sharing. + STILL UNDOCUMENTED """ - # start reverse SSH tunnel for sshfs (folder sharing, printing) - ssh_transport = self.get_transport() - if ssh_transport.reverse_tunnels['sshfs'][1] is None: + if self.terminal_params.has_key('cmd'): + return self.terminal_params['cmd'] + return None - _tunnel = sftpserver.X2goRevFwTunnelToSFTP(server_port=self.session_info.sshfs_port, - ssh_transport=ssh_transport, - auth_key=self._x2go_session_auth_rsakey, - logger=self.logger - ) + def get_control_session(self): + return self.control_session + __get_control_session = get_control_session - if _tunnel is not None: - ssh_transport.reverse_tunnels['sshfs'] = (self.session_info.sshfs_port, _tunnel) - _tunnel.start() - self.guardian_thread.active_threads.append(_tunnel) - - else: - # tunnel has already been started and might simply need a resume call - ssh_transport.reverse_tunnels['sshfs'][1].resume() + def get_terminal_session(self): + return self.terminal_session + __get_terminal_session = get_terminal_session - def _x2go_pause_rev_fw_tunnel(self, name): - # pause reverse SSH tunnel of name <name> - ssh_transport = self.get_transport() - _tunnel = ssh_transport.reverse_tunnels[name][1] - if _tunnel is not None: - _tunnel.pause() - - def stop_sound(self): + def connect(self, username='', password='', add_to_known_hosts=False, force_password_auth=False): """\ - Shutdown (pause) Paramiko/SSH reverse forwarding tunnel for X2go sound. - - """ - self._x2go_pause_rev_fw_tunnel('sound') + Connect to a registered X2go session with registry hash C{<session_uuid>}. + This method basically wraps around paramiko.SSHClient.connect() for the + corresponding session. - def stop_sshfs(self): - """\ - Shutdown (pause) Paramiko/SSH reverse forwarding tunnel for X2go folder sharing. + @param username: the username for the X2go server that is going to be + connected to (as a last minute way of changing the session username) + @type username: C{str} + @param password: the user's password for the X2go server that is going to be + connected to + @type password: C{str} + @param add_to_known_hosts: non-paramiko option, if C{True} paramiko.AutoAddPolicy() + is used as missing-host-key-policy. If set to C{False} paramiko.RejectPolicy() + is used + @type add_to_known_hosts: C{bool} + @param force_password_auth: disable SSH pub/priv key authentication mechanisms + completely + @type force_password_auth: C{bool} """ - self._x2go_pause_rev_fw_tunnel('sshfs') + if self.control_session.is_connected(): + self.logger('control session is already connected, skipping authentication', loglevel=log.loglevel_DEBUG) + self.connected = True + else: + if username: + self.control_params['username'] = username + self.control_params['password'] = password + self.connected = self.control_session.connect(self.server, **self.control_params) + return self.connected + __connect = connect - def start_printing(self): + def disconnect(self): """\ - Initialize X2go print spooling. + STILL UNDOCUMENTED """ - if self.session_info.username not in self._x2go_remote_group('x2goprint'): - raise x2go_exceptions.X2goSessionException('remote user %s is not member of X2go server group x2goprint' % self.session_info.username) - - spool_dir = os.path.join(self.session_info.local_container, 'spool') - if not os.path.exists(spool_dir): - os.mkdir(spool_dir) - self.share_local_folder(folder_name=spool_dir, folder_type='spool') - self.print_queue = printing.X2goPrintQueue(spool_dir=spool_dir, - print_action=self.print_action, - print_action_args=self.print_action_args, - logger=self.logger, - ) - self.print_queue.start() - self.guardian_thread.active_threads.append(self.print_queue) + self.terminal_session.disconnect() + self.connected = False + self.running = False + self.suspended = False + self.terminated = False + __disconnect = disconnect def set_print_action(self, print_action, **kwargs): """\ STILL UNDOCUMENTED """ - self.print_queue.set_print_action(print_action, logger=self.logger, **kwargs) - + if type(print_action) is not types.StringType: + return False + self.terminal_session.set_print_action(print_action, **kwargs) + __set_print_action = set_print_action - def stop_printing(self): - """\ - Shutdown (pause) the X2go Print Queue thread. + def clean_sessions(self): + self.control_session.clean_sessions() - """ - if self.print_queue is not None: - self.print_queue.pause() + def list_sessions(self): + return self.control_session.list_sessions() - def share_local_folder(self, folder_name=None, folder_type='disk'): + def resume(self, session_name=None): """\ - Share a local folder with the X2go session. + Resume or continue a suspended / running X2go session on the + remote X2go server. - @param folder_name: the full path to an existing folder on the local - file system - @type folder_name: str - @param folder_type: one of 'disk' (a folder on your local hard drive), 'rm' (removeable device), - 'cdrom' (CD/DVD Rom) or 'spool' (for X2go print spooling) - @type folder_type: str - - @return: returns C{True} if the local folder has been successfully mounted within the X2go server session - @rtype: bool + @param session_name: the server-side name of an X2go session + @type session_name: C{str} """ - if self.session_info.username not in self._x2go_remote_group('fuse'): - raise x2go_exceptions.X2goSessionException('remote user %s is not member of X2go server group fuse' % self.session_info.username) - - if folder_name is None: - self.logger('no folder name given...', log.loglevel_WARN) - return False - - if type(folder_name) is not types.StringType: - self.logger('folder name needs to be of type StringType...', log.loglevel_WARN) - return False - - if not os.path.exists(folder_name): - self.logger('local folder does not exist: %s' % folder_name, log.loglevel_WARN) - return False + _control = self.control_session + _terminal = _control.resume(session_name=session_name, logger=self.logger, **self.terminal_params) + if _terminal is not None: - self.logger('sharing local folder: %s' % folder_name, log.loglevel_INFO) + if _terminal.params.snd_system is not 'none': + _terminal.start_sound() - _auth_rsakey = self._x2go_session_auth_rsakey - _host_rsakey = defaults.RSAHostKey + if self.printing or self.share_local_folders: + _terminal.start_sshfs() - _tmp_io_object = cStringIO.StringIO() - _auth_rsakey.write_private_key(_tmp_io_object) - _tmp_io_object.write('----BEGIN RSA IDENTITY----') - _tmp_io_object.write('%s %s' % (_host_rsakey.get_name(),_host_rsakey.get_base64(),)) + if self.printing: + _terminal.start_printing() - _x2go_key_fname = os.path.join(os.path.dirname(self.session_info.remote_container), 'ssh', 'key.z%s' % self.session_info.agent_pid) - _x2go_key_bundle = _tmp_io_object.getvalue() + if self.share_local_folders: + if _control.get_transport().reverse_tunnels[_terminal.get_session_name()]['sshfs'][1] is not None: + for _folder in self.share_local_folders: + _terminal.share_local_folder(_folder) - self._x2go_sftp_write(_x2go_key_fname, _x2go_key_bundle) + _terminal.run_command() + self.suspended = False + self.running = True + self.terminated = False - if folder_type is 'disk': + self.terminal_session = _terminal - cmd_line = [ 'export HOSTNAME &&', - 'x2gomountdirs', - 'dir', - str(self.session_info.name), - _CURRENT_LOCAL_USER, - _x2go_key_fname, - '%s__REVERSESSH_PORT__%s; ' % (folder_name, self.session_info.sshfs_port), - 'rm -f %s %s.ident' % (_x2go_key_fname, _x2go_key_fname), - ] + return self.running + __resume = resume - elif folder_type is 'spool': - - cmd_line = [ 'export HOSTNAME &&', - 'x2gomountdirs', - 'dir', - str(self.session_info.name), - _CURRENT_LOCAL_USER, - _x2go_key_fname, - '%s__PRINT_SPOOL___REVERSESSH_PORT__%s; ' % (folder_name, self.session_info.sshfs_port), - 'rm -f %s %s.ident' % (_x2go_key_fname, _x2go_key_fname), - ] + def start(self): + """\ + Start a new X2go session on the remote X2go server. - (stdin, stdout, stderr) = self._x2go_exec_command(cmd_line) - self.logger('x2gomountdirs output is : %s' % stdout.read().split('\n'), log.loglevel_INFO) + """ + self.resume() + __start = start - def run_command(self, cmd=None): + def suspend(self): """\ - Run a command in this session. + Suspend an X2go session. - After L{X2goSession.start()} has been called - one or more commands can be executed with L{X2goSession.run_command()} - within the current X2go session. + You can either suspend a session that you have formerly + started/resumed the current X2goClient instance. - @param cmd: Command to be run - @type cmd: str - - @return: stdout.read() and stderr.read() as returned by the run command - on the X2go server - @rtype: tuple of str + Or you can suspend a non-attached session by simply + registering an X2go server session and then passing the + server-side X2go session name to this method. """ - if cmd in ("", None): - if self.params.cmd is None: - cmd = 'TERMINAL' - else: - cmd = self.params.cmd + if self.terminal_session.suspend(): - self.params.update({'cmd': cmd}) + self.running = False + self.suspended = True + return True - cmd_line = [ "setsid x2goruncommand", - str(self.session_info.display), - str(self.session_info.agent_pid), - str(self.session_info.name), - str(self.session_info.snd_port), - _rewrite_cmd(self.params.cmd), - str(self.params.snd_system), - str(self.params.session_type), - ">& /dev/null & exit", - ] - - if self.params.snd_system is 'pulse': - cmd_line = [ 'PULSE_CLIENTCONFIG=%s/.pulse-client.conf' % self.session_info.remote_container ] + cmd_line - - (stdin, stdout, stderr) = self._x2go_exec_command(cmd_line) - - return stdout.read(), stderr.read() + return False + __suspend = suspend - def list_sessions(self, raw=False): + def terminate(self): """\ - List all sessions of current user on the connected server. + Terminate an X2go session. - @param raw: if C{True}, the raw output of the server-side X2go command - C{x2golistsessions} is returned. - @type raw: bool + You can either terminate a session that you have formerly + started/resumed within the current X2goClient instance. - @return: normally an instance of L{X2goServerSessionList} is returned. However, - if the raw argument is set, the plain text output of the x2golistsessions - command is returned - @rtype: L{X2goServerSessionList} instance or str + Or you can terminate a non-attached session by simply + registering an X2go server session and then passing the + server-side X2go session name to this method. """ - (stdin, stdout, stderr) = self._x2go_exec_command("x2golistsessions") + if self.terminal_session.terminate(): - if raw: - return stdout.read(), stderr.read() + self.running = False + self.suspended = False + self.terminated = True + return True - _stdout_read = stdout.read() - return X2goServerSessionList(_stdout_read).sessions + return False + __terminate = terminate - def ok(self): + def get_profile_name(self): """\ - Returns C{True} if this X2go session is up and running, - C{False} else + Retrieve the profile name of this registered session. - @return: X2go session OK? - @rtype: bool + @return: X2go client profile name of the session + @rtype: C{str} """ - return bool(self.session_info.name and (self.proxy_subprocess and self.proxy_subprocess.poll() is None)) + return self.profile_name + __get_profile_name = get_profile_name - def is_connected(self): + def get_profile_id(self): """\ - Returns C{True} if this X2go session is connected to the remote server (that - is if it has a valid Paramiko Transport object). + Retrieve this registered session's profile id. - @return: X2go session connected - @rtype: bool + @return: the session profile's id + @rtype: C{str} """ - return self.get_transport() is not None - - def is_running(self): - """\ - Returns C{True} if this X2go session is in running state ('R'), - C{False} else. + return self.profile_id + __get_profile_id = get_profile_id - @return: X2go session running? - @rtype: bool + ### + ### QUERYING INFORMATION + ### - """ - session_infos = self.list_sessions() - if self.session_info.name in session_infos.keys(): - return session_infos[self.session_info.name].status == "R" - return False - - def is_suspended(self): + def session_ok(self): """\ - Returns C{True} if this X2go session is in suspended state ('S'), - C{False} else. + Test if this registered X2go session is + in a healthy state. - @return: X2go session suspended? - @rtype: bool + @return: C{True} if session is ok, C{False} otherwise + @rtype: C{bool} """ - session_infos = self.list_sessions() - if self.session_info.name in session_infos.keys(): - return session_infos[self.session_info.name].status == "S" + if self.terminal_session is not None: + return self.terminal_session.ok() return False + __session_ok = session_ok - def has_terminated(self): - """\ - Returns C{True} if this X2go session is not in the session list on the - connected server, C{False} else. - Of course, if this command is called before session startup, it will also - return C{True}. + def is_connected(self): + """\ + Test if this registered X2go session is connected to the + remote server. - @return: X2go session has terminate? - @rtype: bool + @return: C{True} if session is connected, C{False} otherwise + @rtype: C{bool} """ - session_infos = self.list_sessions() - return self.session_info.name not in session_infos.keys() + return self.control_session.is_connected() + _is_connected = is_connected - def associate(self, session_name): + def is_running(self): """\ - Associate L{session_name} with an available (state 'R' or 'S') - X2go session on the server. + Test if this registered X2go session is up and running. - @param session_name: X2go name of an available session. - @type session_name: str + @return: C{True} if session is running, C{False} otherwise + @rtype: C{bool} """ - self.associated = False - try: - self.session_info = self.list_sessions()[session_name] - if self.session_info.name: - self.associated = True - self.session_info.local_container = os.path.join(self.params.rootdir, 'S-%s' % self.session_info.name) - except KeyError: - pass - return self.associated - - def resume(self, **kwargs): - """\ - Resume a running/suspended X2go session. + return self.is_connected() and self.terminal_session.is_running() + _is_running = is_running - The L{X2goSession.resume()} method accepts any parameter - that can be passed to the class constructor. + def is_suspended(self): + """\ + Test if this registered X2go session is in suspended state. - @return: True if the session could be successfully resumed - @rtype: bool + @return: C{True} if session is suspended, C{False} otherwise + @rtype: C{bool} """ - if self.associated: - - if self.session_info.username not in self._x2go_remote_group('x2gousers'): - raise x2go_exceptions.X2goSessionException('remote user %s is not member of X2go server group x2gousers' % self.session_info.username) - - self.params.update(kwargs) - - # if the session is still running, suspend it first - if self.session_info.status == "R": - self.suspend() - - setkbd = "0" - if self.params.kblayout or self.params.kbtype: - setkbd = "1" - - cmd_line = [ "x2goresume-session", self.session_info.name, - self.params.geometry, - self.params.link, - self.params.pack, - self.params.kblayout, - self.params.kbtype, - setkbd, - ] + return self.is_connected() and self.terminal_session.is_suspended() + __is_suspended = is_suspended - (stdin, stdout, stderr) = self._x2go_exec_command(cmd_line) - - self.proxy = self.proxy_class(self.session_info, self.get_transport(), logger=self.logger) - self.proxy_subprocess = self.proxy.start() - - # local path may be a Windows path, so we use the path separator of the local system - self.session_info.local_container = os.path.join(self.params.rootdir, 'S-%s' % self.session_info.name) - # remote path is always a UniX path... - self.session_info.remote_container = '%s/%s/C-%s' % (self._x2go_remote_home, - _X2GO_SESSION_ROOTDIR, - self.session_info.name, - ) - return self.ok() - - else: - raise x2go_exceptions.X2goSessionException('This X2go session instance is not associated to any server-side X2go session.') - - def suspend(self, session_name=None): + def has_terminated(self): """\ - Suspend either this or another available X2go session on the connected - server. - - If L{session_name} is given, L{X2goSession.suspend()} tries to suspend the - corresponding session. + Test if this registered X2go session has terminated. - @param session_name: X2go name of the session to be suspended - @type session_name: str - - @return: True if the session could be successfully suspended - @rtype: bool + @return: C{True} if session has terminated, C{False} otherwise + @rtype: C{bool} """ - _ret = False - if session_name is not None: - - self.logger('suspending non-associated session: %s' % session_name, log.loglevel_DEBUG) - (stdin, stdout, stderr) = self._x2go_exec_command("x2gosuspend-session %s" % session_name, loglevel=log.loglevel_DEBUG) - dummy_stdout = stdout.read() - dummy_stderr = stderr.read() - _ret = True + return self.is_connected() and self.control_session.has_terminated(self.get_session_name()) + __has_terminated = has_terminated - elif self.associated: - - self.logger('suspending associated session: %s' % self.session_info, log.loglevel_DEBUG) - (stdin, stdout, stderr) = self._x2go_exec_command("x2gosuspend-session %s" % self.session_info, loglevel=log.loglevel_DEBUG) - dummy_stdout = stdout.read() - dummy_stderr = stderr.read() - self.associated = False - _ret = True - - self._x2go_tidy_up() - - return _ret - - def terminate(self, session_name=None): + def share_local_folder(self, folder_name): """\ - Terminate either this or another available X2go session on the connected - server. - - If L{session_name} is given, L{X2goSession.terminate()} tries to terminate the - corresponding session. + Share a local folder with this registered X2go session. - @param session_name: X2go name of the session to be terminated - @type session_name: str + @param folder_name: the full path to an existing folder on the local + file system + @type folder_name: C{str} - @return: True if the session could be successfully terminate - @rtype: bool + @return: returns C{True} if the local folder has been successfully mounted within + this registered X2go server session + @rtype: C{bool} """ - _ret = False - if session_name is not None: - - self.logger('terminating non-associated session: %s' % session_name, log.loglevel_INFO) - (stdin, stdout, stderr) = self._x2go_exec_command("x2goterminate-session %s" % session_name, loglevel=log.loglevel_DEBUG) - dummy_stdout = stdout.read() - dummy_stderr = stderr.read() - _ret = True - - elif self.associated: - - self.logger('terminating associated session: %s' % self.session_info, log.loglevel_INFO) - (stdin, stdout, stderr) = self._x2go_exec_command("x2goterminate-session %s" % self.session_info, loglevel=log.loglevel_DEBUG) - dummy_stdout = stdout.read() - dummy_stderr = stderr.read() - self.session_info.clear() - self.associated = False - _ret = True + return self.session_object.share_local_folder(folder_name=folder_name) + __share_local_folder = share_local_folder - self._x2go_tidy_up() - return _ret hooks/post-receive -- python-x2go.git (Python X2Go Client API) This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "python-x2go.git" (Python X2Go Client API).