x2gobroker-0.0.4.1/bin/x2gobroker0000755000000000000000000004633113457267612013432 0ustar #!/usr/bin/env python3 # -*- coding: utf-8 -*- # vim:fenc=utf-8 # This file is part of the X2Go Project - https://www.x2go.org # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. import os import sys import argparse import socket import logging import atexit import importlib try: import daemon import lockfile CAN_DAEMONIZE = True if os.path.isdir('/run'): RUNDIR = '/run' else: RUNDIR = '/var/run' pidfile = '{run}/x2gobroker/x2gobroker-daemon.pid'.format(run=RUNDIR) daemon_logdir = '/var/log/x2gobroker/' except ImportError: CAN_DAEMONIZE = False from grp import getgrnam def prep_http_mode(): global urls global settings # import classes serving the different web.py URLs import x2gobroker.web.plain import x2gobroker.web.json import x2gobroker.web.uccs import x2gobroker.web.extras # define the web.py URLs urls = ( ('/plain/(.*)', x2gobroker.web.plain.X2GoBrokerWeb,), ('/json/(.*)', x2gobroker.web.json.X2GoBrokerWeb,), ('/uccs/[a-zA-Z]*(/*)$', x2gobroker.web.uccs.X2GoBrokerWeb,), ('/uccs/(.*)/api/([0-9])(/*)$', x2gobroker.web.uccs.X2GoBrokerWebAPI,), ('/pubkeys(/*)$', x2gobroker.web.extras.X2GoBrokerPubKeyService,), ('/$', x2gobroker.web.extras.X2GoBrokerItWorks,), ) settings = { 'log_function': tornado_log_request, } def logfile_prelude(mode='HTTP'): logger_broker.info('X2Go Session Broker ({version}),'.format(version=__VERSION__)) logger_broker.info(' written by {author}'.format(author=__AUTHOR__)) logger_broker.info('Setting up the broker\'s environment...') logger_broker.info(' X2GOBROKER_USER: {value}'.format(value=x2gobroker.defaults.X2GOBROKER_USER)) logger_broker.info(' X2GOBROKER_DAEMON_USER: {value}'.format(value=x2gobroker.defaults.X2GOBROKER_DAEMON_USER)) logger_broker.info(' X2GOBROKER_DAEMON_GROUP: {value}'.format(value=x2gobroker.defaults.X2GOBROKER_DAEMON_GROUP)) logger_broker.info(' X2GOBROKER_DEBUG: {value}'.format(value=x2gobroker.defaults.X2GOBROKER_DEBUG)) logger_broker.info(' X2GOBROKER_CONFIG: {value}'.format(value=x2gobroker.defaults.X2GOBROKER_CONFIG)) logger_broker.info(' X2GOBROKER_AGENT_CMD: {value}'.format(value=x2gobroker.defaults.X2GOBROKER_AGENT_CMD)) logger_broker.info(' X2GOBROKER_DEFAULT_BACKEND: {value}'.format(value=x2gobroker.defaults.X2GOBROKER_DEFAULT_BACKEND)) if mode != 'SSH': logger_broker.info(' X2GOBROKER_AUTHSERVICE_SOCKET: {value}'.format(value=x2gobroker.defaults.X2GOBROKER_AUTHSERVICE_SOCKET)) logger_broker.info(' X2GOBROKER_SSL_CERTFILE: {value}'.format(value=x2gobroker.defaults.X2GOBROKER_SSL_CERTFILE)) logger_broker.info(' X2GOBROKER_SSL_KEYFILE: {value}'.format(value=x2gobroker.defaults.X2GOBROKER_SSL_KEYFILE)) def cleanup_on_exit(): try: os.remove(pidfile) except: pass try: import x2gobroker.defaults except ImportError: sys.path.insert(0, os.path.join(os.getcwd(), '..')) import x2gobroker.defaults from x2gobroker import __VERSION__ from x2gobroker import __AUTHOR__ import x2gobroker.loggers tornado_log_request = x2gobroker.loggers.tornado_log_request PROG_NAME = x2gobroker.loggers.PROG_NAME from x2gobroker.utils import drop_privileges, split_host_address interactive_mode_warning = False # check effective UID the broker runs as and complain appropriately... if x2gobroker.defaults.X2GOBROKER_USER != x2gobroker.defaults.X2GOBROKER_DAEMON_USER and os.geteuid() != 0: interactive_mode_warning = True # parse-in potential command line options cmdline_args = None if __name__ == "__main__": import setproctitle setproctitle.setproctitle(os.path.basename(sys.argv[0])) index = 0 argv = [] argv.append(sys.argv[0]) double_dash_pos = 0 double_dash_argv = [] for arg in sys.argv[1:]: index += 1 if double_dash_pos == 0 and arg == "--": double_dash_pos = index continue if double_dash_pos > 0: double_dash_argv.append(arg) else: argv.append(arg) sys.argv = argv general_options = [ {'args':['-M','--mode'], 'default': 'SSH', 'metavar': 'BROKER_MODE', 'help': 'Mode of the X2Go Session Broker to run in (available: SSH, HTTP)', }, {'args':['-C','--config-file'], 'default': None, 'metavar': 'CONFIG_FILE', 'help': 'Specify a special configuration file name, default is: {default}'.format(default=x2gobroker.defaults.X2GOBROKER_CONFIG), }, {'args':['-d','--debug'], 'default': False, 'action': 'store_true', 'help': 'enable debugging code; also: allow testing in web browser (make http\'s POST method available as GET method, as well)', }, {'args':['-i','--debug-interactively'], 'default': False, 'action': 'store_true', 'help': 'force output of log message to the stderr (rather than to the log files)', }, ] daemon_options = [ {'args':['-b', '--bind'], 'default': None, 'metavar': 'BIND_ADDRESS', 'help': 'The [address:]port that the web.py http-engine shall bind to (default: 127.0.0.1:8080)', }, ] if CAN_DAEMONIZE: daemon_options.extend([ {'args':['-D', '--daemonize'], 'default': False, 'action': 'store_true', 'help': 'Detach the X2Go Broker process from the current terminal and fork to background', }, {'args':['-P', '--pidfile'], 'default': pidfile, 'help': 'Alternative file path for the daemon\'s PID file', }, {'args':['-L', '--logdir'], 'default': daemon_logdir, 'help': 'Directory where log files for the process\'s stdout and stderr can be created', }, ]) if os.getuid() == 0: daemon_options.extend([ {'args':['--drop-privileges'], 'default': False, 'action': 'store_true', 'help': 'Drop privileges to uid X2GOBROKER_DAEMON_USER and gid X2GOBROKER_DAEMON_GROUP', }, ]) sshbroker_options = [ {'args':['--task'], 'default': None, 'metavar': 'BROKER_TASK', 'help': 'broker task (listsessions, selectsession, setpass, testcon)', }, {'args':['--user'], 'default': None, 'metavar': 'USER_NAME', 'help': 'Operate on behalf of this X2Go Broker user name', }, {'args':['--login'], 'default': None, 'metavar': 'LOGIN_NAME', 'help': 'Operate on behalf of this X2Go Server user name', }, {'args':['--event'], 'default': None, 'metavar': 'EVENT_NAME', 'help': 'not-yet supported feature, we simply ignore this option for now...', }, {'args':['--auth-cookie', '--next-authid', '--authid', ], 'default': None, 'metavar': 'AUTH_ID', 'help': 'Pre-shared (dynamic) authentication ID', }, {'args':['--profile-id', '--sid', ], 'default': None, 'metavar': 'PROFILE_ID', 'help': 'for task: the profile ID selected from the list of available session profiles', }, {'args':['--backend'], 'default': None, 'metavar': 'BROKER_BACKEND', 'help': 'select a non-default broker backend', }, ] p = argparse.ArgumentParser(description='X2Go Session Broker (Standalone Daemon)',\ formatter_class=argparse.RawDescriptionHelpFormatter, \ add_help=True, argument_default=None) p_general = p.add_argument_group('general arguments') p_daemon = p.add_argument_group('arguments for standalone HTTP(s) daemon mode') p_sshbroker = p.add_argument_group('arguments for command line SSH broker mode') for (p_group, opts) in ( (p_general, general_options), (p_daemon, daemon_options), (p_sshbroker, sshbroker_options), ): for opt in opts: args = opt['args'] del opt['args'] p_group.add_argument(*args, **opt) cmdline_args = p.parse_args() if cmdline_args.config_file is not None: x2gobroker.defaults.X2GOBROKER_CONFIG = cmdline_args.config_file if cmdline_args.debug_interactively: # recreate loggers... logger_broker, logger_access, logger_error = x2gobroker.loggers.init_console_loggers() # define our own debugging loggers x2gobroker.loggers.logger_broker = logger_broker x2gobroker.loggers.logger_broker = logger_access x2gobroker.loggers.logger_error = logger_error cmdline_args.debug = True else: # use already defined loggers from the x2gobroker.loggers module... logger_broker = x2gobroker.loggers.logger_broker logger_access = x2gobroker.loggers.logger_broker logger_error = x2gobroker.loggers.logger_error # override X2GOBROKER_DEBUG=0 in os.environ with the command line switch if cmdline_args.debug: x2gobroker.defaults.X2GOBROKER_DEBUG = cmdline_args.debug # daemonizing only makes sense for the HTTP broker mode... if cmdline_args.daemonize: cmdline_args.mode = 'HTTP' # evaluate other cmdline options depending on the broker mode if cmdline_args.mode.upper() not in ('SSH', 'HTTP'): logger_broker.error('Invalid mode selected. Available: SSH or HTTP.') sys.exit(-1) # ignore EVENTS for now... if cmdline_args.event and cmdline_args.mode == 'SSH': logger_broker.warning('X2Go client sent event: {event_name}. Events are not supported, yet. Ignoring that...'.format(event_name=cmdline_args.event)) logger_broker.warning('X2Go client\'s event info is: {event_info}. Events are not supported, yet. Ignoring that...'.format(event_info=double_dash_argv)) sys.exit(0) ### SSH broker elif cmdline_args.mode.upper() == 'SSH' and not PROG_NAME == 'x2gobroker-daemon': if cmdline_args.bind: logger_broker.warn('ignoring non-valid option --bind for broker mode SSH...') if cmdline_args.daemonize: logger_broker.warn('ignoring non-valid option --daemonize for broker mode SSH...') if cmdline_args.profile_id and cmdline_args.task != 'selectsession': #logger_broker.warn('ignoring option --sid as it only has a meaning with ,,--task selectsession\'\'') pass # is a specific X2Go Broker user given on the command line? if cmdline_args.user is None: cmdline_args.user = os.environ['LOGNAME'] elif os.environ['LOGNAME'] != x2gobroker.defaults.X2GOBROKER_DAEMON_USER: logger_broker.warn('denying context change to user `{user}\', only allowed for magic user `{magic_user}\''.format(user=cmdline_args.user, magic_user=x2gobroker.defaults.X2GOBROKER_DAEMON_USER)) cmdline_args.user = os.environ['LOGNAME'] # is a specific X2Go Server login name given on the command line? # if not, assume broker user and X2Go Server login are the same... if cmdline_args.login is None: cmdline_args.login = cmdline_args.user # bail out if no task is given on the command line if cmdline_args.task is None: print("") p.print_usage() print("No task specified, doing nothing..."); print("") sys.exit(-2) ### HTTP broker elif cmdline_args.mode.upper() == 'HTTP' or PROG_NAME == 'x2gobroker-daemon': logger_broker.info(' DAEMON_BIND_ADDRESS: {value}'.format(value=cmdline_args.bind)) if interactive_mode_warning: logger_broker.warn('X2Go Session Broker has been started interactively by user {username},'.format(username=x2gobroker.defaults.X2GOBROKER_USER)) logger_broker.warn(' better run as user {daemon_username}.'.format(daemon_username=x2gobroker.defaults.X2GOBROKER_DAEMON_USER)) logger_broker.warn('Automatically switching to DEBUG mode due to interactive launch of this application.') x2gobroker.defaults.X2GOBROKER_DEBUG = True if cmdline_args.bind is None: cmdline_args.bind = x2gobroker.defaults.DAEMON_BIND_ADDRESS if cmdline_args.user: logger_broker.warn('ignoring non-valid option --user for broker mode HTTP...') if cmdline_args.auth_cookie: logger_broker.warn('ignoring non-valid option --auth-cookie for broker mode HTTP...') if cmdline_args.task: logger_broker.warn('ignoring non-valid option --task for broker mode HTTP...') if cmdline_args.profile_id: logger_broker.warn('ignoring non-valid option --profile-id for broker mode HTTP...') if CAN_DAEMONIZE and cmdline_args.daemonize: pidfile = os.path.expanduser(cmdline_args.pidfile) if not os.path.isdir(os.path.dirname(pidfile)): try: os.makedirs(os.path.dirname(pidfile)) except: pass try: os.chown(os.path.dirname(pidfile), 0, getgrnam(x2gobroker.defaults.X2GOBROKER_DAEMON_GROUP).gr_gid) os.chmod(os.path.dirname(pidfile), 0o770) except OSError: pass if not (os.access(os.path.dirname(pidfile), os.W_OK) and os.access(os.path.dirname(pidfile), os.X_OK)) or (os.path.exists(pidfile) and not os.access(pidfile, os.W_OK)): print("") p.print_usage() print("Insufficent privileges. Cannot create PID file {pidfile} path".format(pidfile=pidfile)) print("") sys.exit(-3) # the log dir should really be create by distro package maintainers... daemon_logdir = os.path.expanduser(cmdline_args.logdir) if not os.path.isdir(daemon_logdir): try: os.makedirs(daemon_logdir) except: pass if not (os.access(daemon_logdir, os.W_OK) and os.access(daemon_logdir, os.X_OK)): print("") p.print_usage() print("Insufficent privileges. Cannot create directory for stdout/stderr log files: {logdir}".format(logdir=daemon_logdir)) print("") sys.exit(-3) else: if not daemon_logdir.endswith('/'): daemon_logdir += '/' bind_address, bind_port = split_host_address(cmdline_args.bind, default_address=None, default_port=8080) cmdline_args.bind = "[{address}]:{port}".format(address=bind_address, port=bind_port) if os.getuid() == 0 and cmdline_args.drop_privileges: drop_privileges(uid=x2gobroker.defaults.X2GOBROKER_DAEMON_USER, gid=x2gobroker.defaults.X2GOBROKER_DAEMON_GROUP) urls = () settings = {} # run the Python Tornado standalone daemon or handle interactive command line execution (via SSH) if __name__ == "__main__": # raise log level to DEBUG if requested... if x2gobroker.defaults.X2GOBROKER_DEBUG and not x2gobroker.defaults.X2GOBROKER_TESTSUITE: logger_broker.setLevel(logging.DEBUG) logger_access.setLevel(logging.DEBUG) logger_error.setLevel(logging.DEBUG) logfile_prelude(mode=cmdline_args.mode.upper()) if cmdline_args.mode.upper() == 'HTTP' or PROG_NAME == 'x2gobroker-daemon': ### launch as standalone HTTP daemon ### prep_http_mode() import tornado.web import tornado.httpserver import tornado.ioloop def launch_ioloop(): tornado.ioloop.IOLoop.instance().start() application = tornado.web.Application(urls, **settings) try: if x2gobroker.defaults.X2GOBROKER_SSL_CERTFILE and x2gobroker.defaults.X2GOBROKER_SSL_KEYFILE: # switch on https:// mode http_server = tornado.httpserver.HTTPServer(application, ssl_options={ "certfile": x2gobroker.defaults.X2GOBROKER_SSL_CERTFILE, "keyfile": x2gobroker.defaults.X2GOBROKER_SSL_KEYFILE, }, ) else: # run without https http_server = tornado.httpserver.HTTPServer(application) try: http_server.listen(bind_port, address=bind_address) except OSError as e: print (e) sys.exit(1) if CAN_DAEMONIZE and cmdline_args.daemonize: atexit.register(cleanup_on_exit) keep_fds = [int(fd) for fd in os.listdir('/proc/self/fd') if fd not in (0,1,2) ] daemon_stdout = open(daemon_logdir+'x2gobroker-daemon.stdout', 'w+') daemon_stderr = open(daemon_logdir+'x2gobroker-daemon.stderr', 'w+') logger_broker.info('Forking daemon to background, PID file is: {pidfile}'.format(pidfile=pidfile)) with daemon.DaemonContext(stdout=daemon_stdout, stderr=daemon_stderr, files_preserve=keep_fds, umask=0o027, pidfile=lockfile.FileLock(pidfile), detach_process=True): open(pidfile, 'w+').write(str(os.getpid())+'\n') launch_ioloop() else: launch_ioloop() except socket.error as e: print (e) elif cmdline_args.mode.upper() == 'SSH': ### run interactively from the command line (i.e. via SSH) ### import x2gobroker.client.plain cmdline_client = x2gobroker.client.plain.X2GoBrokerClient() output = cmdline_client.get(cmdline_args) if output: print(output) else: ### launch as WSGI application ### import asyncio from tornado.platform.asyncio import AnyThreadEventLoopPolicy asyncio.set_event_loop_policy(AnyThreadEventLoopPolicy()) logger_broker = x2gobroker.loggers.logger_broker logger_access = x2gobroker.loggers.logger_broker logger_error = x2gobroker.loggers.logger_error # raise log level to DEBUG if requested... if x2gobroker.defaults.X2GOBROKER_DEBUG and not x2gobroker.defaults.X2GOBROKER_TESTSUITE: logger_broker.setLevel(logging.DEBUG) logger_access.setLevel(logging.DEBUG) logger_error.setLevel(logging.DEBUG) prep_http_mode() import tornado.wsgi import wsgilog _tornado_application = tornado.wsgi.WSGIApplication(urls, **settings) def _application(environ, start_response): # some WSGI implementations do not like the SCRIPT_NAME env var if 'SCRIPT_NAME' in environ: del environ['SCRIPT_NAME'] # make sure the httpd server's environment is set as os.environ for key in environ.keys(): if key.startswith('X2GOBROKER_'): os.environ.update({ key: environ[key] }) importlib.reload(x2gobroker.defaults) logfile_prelude() return _tornado_application(environ, start_response) application = wsgilog.WsgiLog(_application, tohtml=True, tofile=True, tostream=False, toprint=False, file='/var/log/x2gobroker/wsgi.log', ) x2gobroker-0.0.4.1/bin/x2gobroker-testauth0000755000000000000000000001223513457267612015265 0ustar #!/usr/bin/env python3 # -*- coding: utf-8 -*- # vim:fenc=utf-8 # This file is part of the X2Go Project - https://www.x2go.org # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. import os import sys import setproctitle import argparse import logging import getpass # perform an authentication against the authentication mechanism configured for WSGI try: import x2gobroker.defaults except ImportError: sys.path.insert(0, os.path.join(os.getcwd(), '..')) import x2gobroker.defaults import x2gobroker.loggers PROG_NAME = os.path.basename(sys.argv[0]) PROG_OPTIONS = sys.argv[1:] try: _password_index = PROG_OPTIONS.index('--password')+1 PROG_OPTIONS[_password_index] = "XXXXXXXX" except ValueError: # ignore if --password option is not specified pass setproctitle.setproctitle("%s %s" % (PROG_NAME, " ".join(PROG_OPTIONS))) if __name__ == "__main__": auth_options = [ {'args':['-u','--username', '--user'], 'default': None, 'metavar': 'USERNAME', 'help': 'Test authentication for the account with this username', }, {'args':['-p', '--password'], 'default': None, 'metavar': 'PASSWORD', 'help': 'Test authentication using this password (Not recommended!!! Please prefer using the provided password prompt instead)', }, ] misc_options = [ {'args':['-C','--config-file'], 'default': None, 'metavar': 'CONFIG_FILE', 'help': 'Specify a special configuration file name, default is: {default}'.format(default=x2gobroker.defaults.X2GOBROKER_CONFIG), }, {'args':['-b','--backend'], 'default': x2gobroker.defaults.X2GOBROKER_DEFAULT_BACKEND, 'metavar': 'BACKEND', 'help': 'Use this specific backend for testing authentication, see x2gobroker.conf for a list of configured and enabled backends, default is: {default_backend}'.format(default_backend=x2gobroker.defaults.X2GOBROKER_DEFAULT_BACKEND), }, {'args':['-d','--debug'], 'default': False, 'action': 'store_true', 'help': 'enable debugging code', }, ] p = argparse.ArgumentParser(description='X2Go Session Broker (Authentication Test Utility)',\ formatter_class=argparse.RawDescriptionHelpFormatter, \ add_help=True, argument_default=None) p_auth = p.add_argument_group('authentication parameters') p_misc = p.add_argument_group('miscellaneous parameters') for (p_group, opts) in ( (p_auth, auth_options), (p_misc, misc_options), ): for opt in opts: args = opt['args'] del opt['args'] p_group.add_argument(*args, **opt) cmdline_args = p.parse_args() if cmdline_args.username is None: p.print_help() print() print("*** Cannot continue without username... ***") print() sys.exit(-1) if cmdline_args.config_file is not None: x2gobroker.defaults.X2GOBROKER_CONFIG = cmdline_args.config_file if cmdline_args.debug: x2gobroker.defaults.X2GOBROKER_DEBUG = cmdline_args.debug # raise log level to DEBUG if requested... if x2gobroker.defaults.X2GOBROKER_DEBUG and not x2gobroker.defaults.X2GOBROKER_TESTSUITE: x2gobroker.loggers.logger_broker.setLevel(logging.DEBUG) x2gobroker.loggers.logger_error.setLevel(logging.DEBUG) username = cmdline_args.username password = cmdline_args.password config_file = x2gobroker.defaults.X2GOBROKER_CONFIG config_defaults = x2gobroker.defaults.X2GOBROKER_CONFIG_DEFAULTS try: namespace = {} exec("import x2gobroker.brokers.{backend}_broker as _backend".format(backend=cmdline_args.backend), namespace) _backend = namespace['_backend'] except ImportError: p.print_help() print() print("*** No such backend: {backend} ***".format(backend=cmdline_args.backend)) print() sys.exit(-2) broker = _backend.X2GoBroker(config_file=config_file, config_defaults=config_defaults) if not broker.is_enabled(): p.print_help() print() print("*** Backend not enabled: {backend} ***".format(backend=cmdline_args.backend)) print() sys.exit(-3) def check_password(username, password): return broker._do_authenticate(username=username, password=password) if __name__ == "__main__": if password is None: password = getpass.getpass() if check_password(username, password): print("Authentication succeeded.") sys.exit(0) else: print("Authentication failed!") sys.exit(-1) x2gobroker-0.0.4.1/ChangeLog0000644000000000000000000014133713457267612012424 0ustar x2gobroker (0.0.4.1-0x2go1) unstable; urgency=medium [ Mike Gabriel ] * New upstream version (0.0.4.1): - Makefile.docupload: Ignore clean failures. Helpful to Debian package build chain. - Fix some man page typos. - x2gobroker/defaults.py: Support offline builds with no DNS or other means of hostname resolution.. - sbin/x2gobroker-keygen: Fix call to x2gobroker.utils.get_fingerprint_with_colons(). Thanks to Linnea Skogtvedt for spotting and reporting this. - Make builds reproducible. Thanks to Chris Lamb for providing a patch. (See Debian bug #922137). - Correctly initialize the loggers when using x2gobroker via WSGI in Apache2. - x2gobroker-wsgi: Place WSGI script symlink (pointing to /x2gobroker) into a dedicated folder and configure Apache2 to use WSGIScriptAlias from that folder. (See Debian bug #922040). - sbin/x2gobroker-testagent: Fix retrieval of available tasks. - Permit `asyncio` to create event loops on any thread (required on multithreaded WSGI servers using Python 3). - Make remote agent's SSH HostKey policy configurable globally, backend-wise and per session profile. Fallback to RejectPolicy by default. (See Debian bug #922314). - x2gobroker/agent.py: Assure that remote_agent['host_key_policy'] is always the name of the Parmiko MissingHostKeyPolicy, not the class object itself. - sbin/x2gobroker-testagent: Fix indentation, set 'host_key_policy' as a string (not class). - etc/x2gobroker.conf: Update info about default-agent-hostkey-policy parameter. * debian/po: + Adopt debconf translations from Debian. [ Linnea Skogtvedt ] * New upstream version (0.0.4.1): - x2gobroker/brokers/base_broker.py: Log IP address of authentication attempts. (See Debian bug #922458). [ Mihai Moldovan ] * New upstream version (0.0.4.1): - man/*: update date and version stamps pre-release. * x2gobroker.spec: + Install new wsgi symlink for %{_bindir}/x2gobroker-wsgi. + Fix wsgi symlink location. + Also own %{_libexecdir}/x2gobroker (in wsgi package, for now). + Also own %{_libexecdir}/x2gobroker/wsgi (in wsgi package). + Switch to python36 on EPEL 7. -- X2Go Release Manager Mon, 22 Apr 2019 09:26:58 +0200 x2gobroker (0.0.4.0-0x2go1) unstable; urgency=medium [ Mike Gabriel ] * New upstream version (0.0.4.0): - Bump upstream version to 0.0.4.0. - Port to Python 3. (Fixes: #1240). - Drop left-over debug print() call. - Makefile: Assure that setup.py is run under Python3. - Improve debugging messages during authentication phase. - x2gobroker/basicauth.py: Fix call of base64.decodestring on Python3. - Unit tests: Fix deep misunderstanding in the way allow-deny vs. deny-allow should actually work. - x2gobroker/brokers/base_broker.py: Entire rewrite of check_profile_acls() method. (Fixes: #1234). - x2gobroker/tests/test_web_plain_base.py: Add test case for passwords with accentuated characters (using the testsuite_authmech for now). - Makefile: Support skipping installation of the x2gobroker PyModule. Useful when building with CDBS on Debian. - Makefile: Compress man pages. - Makefile: Run setup.py build at build time. - tmpfiles.d utilization: Create RUNDIR/x2gobroker via tmpfiles.d system. Fixes missing dir and flawed permissions when running under systemd. - etc/x2gobroker.conf: Mention the per-profile option for enabling/disabling load checker support. - sbin/{x2gobroker-pubkeyauthorizer,x2gobroker-keygen}: Use proper octal numbers for file permissions. - sbin/x2gobroker-pubkeyauthorizer: Fix key lookup in os.environ for Python3. - sbin/x2gobroker-pubkeyauthorizer: Some string/bytecode fixes for Python3. Plus urllib -> urllib.request. - sbin/x2gobroker-pubkeyauthorizer: Improve key integrity checker and move it further up. Plus one more Python2 -> Python3 issue fixed. - sbin/x2gobroker-pubkeyauthorizer: Drop unused binascii import. - x2gobroker-pubkeyauthorizer: Tiny Python2to3 fix. - load checker integration: Make the default-use-load-checker option work like all other default-* options. - uccs frontend: Convert datetime.datetime object to string before answering the http request with it. - x2gobroker/agent (check_load()): Bail out if no remote agent is given. - x2gobroker-testagent: Convert to Python3 (using 2to3 tool). - x2gobroker-loadchecker: Python3'ify iteration over dict keys. - x2gobroker/utils.py: Provide helper functions for pretty-formatting key fingerprints. - x2gobroker-keygen: Use new fingerprint formatting functions. - x2gobroker/agent.py: Bail out if no hostaddr contained in remote_agent. - x2gobroker/agent.py: No load-checking when remote_agent is set to 'LOCAL'. - x2gobroker/agent.py: Better sanity checks for remote_agent and its dict keys hostname and hostaddr. - x2gobroker/loadchecker.py: Report properly to the logger if we fail to obtain a load factor. - x2gobroker-loadchecker.service: loadchecker service needs to chuid to system user x2gobroker. (Fixes: #1252). - x2gobroker-loadchecker.service: File ownership should be x2gobroker:x2gobroker, too. - x2gobroker-loadchecker: No chown/chmod if we are not running as root (which is mostly the case). - x2gobroker/brokers/inifile_broker.py: Make sure profile['name'] has a fallback if not given in the session profile. - x2gobroker/brokers/inifile_broker.py: Also check for presence of 'host' and 'sshport'. - UCCS API change for X2Go Sessions: Rename "SessionType" to "Command". - obligatory profile keys: Move from inifile backend to UCCS frontend, as those requirements are frontend specific. - UCCS: Start working on API version 5. - x2gobroker/uccsjson.py: Hide private Python class properties from JSON dict (like ._api_version). - UCCS frontend: Fix API version check. - UCSS frontend: Propagate API version onwards to the X2GoServer JSON generator class. - infile broker backend: Fix handling of empty lists in session profile and session profile defaults. - etc/x2gobroker-wsgi.apache.*: Drop Apache2.2 support. - Log to system broker.log file when run via x2gobroker-ssh. - Getting started documentation: Rework document, convert to markdown, install into x2gobroker bin:pkg (on DEB based systems). - Makefile.docupload: Add apidoc target (running sphinx-apidoc). - docs/source: Initialize Sphinx API documentation's .rst files. - bin/x2gobroker: If binding the http server fails, a non-zero exit code should be returned. (Fixes: #1013). - x2gobroker/loadchecker.py: Don't re-read the x2gobroker.conf during each cycle of the load checking loop. Rather read it on service startup and require a service restart when x2gobroker.conf has been changed. - x2gobroker/loadchecker.py: Avoid rare cases where at the end of a load checking cycle a negative sleep time would have been calculated. (Fixes: #1315). Thanks to Walid Moghrabi for catching this. - HTTP broker: Add &login= support to plain and json broker frontends. - SSH broker: Add --login option. This now supports X2Go Broker user and X2Go Server username being different accounts. - bin/x2gobroker: Correctly use split_host_address() function call. - bin/x2gobroker: Don't override already defined logger objects, define them properly where needed. - Convert one more unicode object into (Python3) string. - x2gobroker/tests/test_broker_agent.py: Assure that tests are run without loadchecker usage. - broker-use-load-checker profile option: Also tolerate 'TRUE' and 'True'. - x2gobroker/agent.py: Fix failing execution of LOCAL broker agent. As the LOCAL broker agent is executed setuid root, we cannot Popen.terminate() (which is unneeded anyway) the process after its execution. - Ignore SSH broker events for now. Not sure if we will ever support that. - Finalize API documentation. - Fix regression flaw in x2gobroker/web/json.py, introduced by commit 9fa371e9. * debian/*: + Trigger Makefile's install target and install those files. Drop debhelper from-source-installation magic. * debian/{control,compat}: Bump to DH version level 9. * debian/{control,x2gobroker-common.install}: + Split out common files into non-Pythonian bin:pkg. * debian/*.install: + Add EOLs at EOF. + Add tmpfiles.d files into bin:pkgs. + Fix installation to /usr/lib/python3.x paths. * debian/control: + Drop from D (several bin:pkgs): python3-argparse, argparse is shipped with Python3 core. + Switch from libapache2-mod-wsgi to libapache2-mod-wsgi-py3. + Add B-D: dh-python. + Add B-D: python3-netaddr (for unit tests). * debian/x2gobroker-loadchecker.postinst: + Do chown/chmod on the correct file (not authservice.log, but loadchecker.log). * debian/python-x2gobroker-doc.doc-base: + Drop leading white-space in Abstract: field. * x2gobroker.spec: + Adapt to Python3 port. + Bump package version. + CentOS 6 + 7 have python34-devel, not python3-devel. + Enable debug_packages for openSUSE Tumbleweed (suse_version > 1500). + CentOS 6 + 7 have python34-setuptools, not python3-setuptools. + Fix removal of conf files in tmpfiles.d where needed. + Install tmpfiles.d configs into bin:pkgs. + Only install tmpfiles.d configs on systems that support/have systemd. + Some path fixes for the new tmpfiles.d/. + Make sure the build chroot has all it needs to run the PyModule's unit tests. + Let's try to get unit tests working on Fedora first... [ Mihai Moldovan ] * New upstream version (0.0.4.0): - src/x2gobroker-{agent,ssh}.c: catch errors in setuid wrappers and add general return clause to make compilers happy. - Makefile: make sure that we actually append our custom CFLAGS and LDFLAGS values, even if passed in through the make command line. - src/x2gobroker-{agent,ssh}.c: fix compile warnings/errors. - src/x2gobroker-{agent,ssh}.c: fix more compile errors. - misc: copyright update. - misc: switch to HTTPS-based URLs where appropriate. - man/*: update date and version stamps pre-release. - misc: add missing coding modelines. * x2gobroker.spec: - Add %debug_package macro when debugging is to be enabled, hoping that it will actually generate proper debuginfo (and -source) sub packages owning files. - Whitespace only. - Remove obsolete EPEL 5 support. - Switch to HTTPS-based links. - Use more curly braces. - Pull in gcc and redhat-rpm-config. - Re-enable debug file generation to see which OS versions still fail. - %exclude does not work with curly braces, revert. - Remove %debug_package macro usage, breaks builds nowadays. - Pass down global flags in CFLAGS and LDFLAGS. - Fix %{__global_ldflags} usage if variable does not exist. - Commands don't seem to work when wrapped in curly braces (at least on *SuSE), so revert. -- X2Go Release Manager Sat, 02 Feb 2019 21:50:29 +0100 x2gobroker (0.0.3.4-0x2go1) unstable; urgency=medium [ Mihai Moldovan ] * New upstream version (0.0.3.4): - Makefile: use "python" command instead of "python3" to run tests. -- X2Go Release Manager Tue, 24 Oct 2017 06:20:10 +0200 x2gobroker (0.0.3.3-0x2go1) unstable; urgency=medium [ Mike Gabriel ] * New upstream version (0.0.3.3): - src/*.c: Fix implicit declaration of execv(). - Make hostname detection work for the default configuration (that defines localhost session profiles). - Makefile: Add check target (drop empty test target). - Makefile: Clean-up x2gobroker-ssh executable in clean-arch target. * debian/{control,compat}: + Bump to DH version level 9. * debian/rules: + Fix empty python-x2gobroker bin:packages on Debian 9 and above. + Enable unit tests at every package build. * debian/control: + Bump Standards-Version: to 3.9.8. No changes needed. [ Mihai Moldovan ] * x2gobroker.spec: - Disable generation of debug package, which will always be empty and lead to errors on certain platforms. -- X2Go Release Manager Tue, 24 Oct 2017 05:53:00 +0200 x2gobroker (0.0.3.2-0x2go1) unstable; urgency=medium [ Mike Gabriel ] * debian/x2gobroker-loadchecker.service: + Fix flawed symlink actually still pointing to the authservice's service file. Thanks to Niels Rogalla for spotting this. (Fixes: #1141). * Regression fix for d68ec35. Use hasattr() to properly test structure of pam module (required for ABI/API changes upstream). * x2gobroker-testagent: - Fix setting up remote_agent[] dictionary. Follow-up fix for a9bc46b. * src/*.c: Fix implicit declaration of execv(). [ Mihai Moldovan ] * New upstream version (0.0.3.2): - x2gobroker-authservice: fixup 60db077d1. The hasattr() call takes a string as its second parameter. - man/*: update date stamps pre-release. -- X2Go Release Manager Thu, 23 Feb 2017 02:28:56 +0100 x2gobroker (0.0.3.1-0x2go1) unstable; urgency=low [ Mike Gabriel ] * debian/python-x2gobroker.preinst: + Use proper comment header explaining about preinst script argument calls (not postinst). * x2gobroker/brokers/base_brokers.py: Remove Perl code in comment that confuses the Epydoc tool. * API Documentation: Sanitize __doc__ strings to make Epydoc happy. Provide Makefile.docupload and provide html API documentation at http://code.x2go.org/doc/python-x2gobroker/ [ Mihai Moldovan ] * New upstream version (0.0.3.1): - sbin/x2gobroker-authservice: refactor pam.pam section a bit. Always initialize opam with pam and only rewrite it with pam.pam if necessary. - x2gobroker/authmechs/pam_authmech.py: port pam.pam change to this file as well. * debian/control: - Maintainer change in package: X2Go Developers . - Uploaders: add myself. Also, force a rebuild due to the changed versioning. * x2goserver.spec: - Add mandatory perl-generators Build-Requires as per https://fedoraproject.org/wiki/Changes/Build_Root_Without_Perl [ Walid Moghrabi ] * New upstream version (0.0.3.1): - sbin/x2gobroker-authservice: adapt script to correctly use the newer python-pam module, which now exposes functionality in pam.pam with backwards compatibility. Fixes: #1056. -- X2Go Release Manager Thu, 01 Dec 2016 22:19:47 +0100 x2gobroker (0.0.3.0-0x2go1) unstable; urgency=low [ Mike Gabriel ] * New upstream version (0.0.3.0): - Add SSH support to X2Go Session Broker. (Fixes: #153). - Move x2gobroker executable to /usr/bin. - Update x2gobroker man page. - SSH broker: Only allow context change to another user for the magic user (default: x2gobroker). - Fix logrotate script: x2gobroker-wsgi. (Fixes: #275). - Get the cookie based extra-authentication working for SSH mode. - Get the cookie based extra-authentication working for HTTP mode. - Fix output of HTTP based connectivity test. - Do not let the broker crash if an agent is not reachable. Capture X2GoBrokerAgentExceptions when pinging the remote agent. (Fixes: #306). - When calling the agent's suspend_session function, make sure to pass on the remote_agent dictionary. - Provide empty directory /etc/x2go/broker/ssl. - Re-order x2gobroker main file. Move logging further to the back to allow taking command-line options into account. - Modify default x2gobroker-sessionprofiles.conf and provide something that will work with every default setup. - New broker session profile parameter: broker-agent-query-mode. Define agent query methods per session profile. - Rename base broker's use_session_autologin to get_session_autologin. - Fix Python2'isms in three exceptions. Thanks to Mathias Ewald for spotting. - Make test_suite callable via setup.py. - Provide a test function that checks if the basic broker agent setup (SSH private/public key pair) is available. If not, no SSH broker usage will be attempted. - Let a portscan preceed the SSH ping command. This notably reduces timeout duration if the host running the queried broker agent is down). - Catch RequestHandler errors and write them to the error log channel. - Raised verbosity level to INFO for session broker utilities. - Add sanity checks to x2gobroker-pubkeyauthorizer. - Report stderr results to the broker log channel (broker.log). This allows debugging of X2Go Session Broker Agent via the X2Go Session Broker logging instance. (Fixes: #217). - Fix the ping task in x2gobroker-agent.pl, process it without checking the given username. - Fix remote agent detection in case of some agents being down. - Add utils function: matching_hostnames(): test hostname lists for matching hostnames (with/without domain name). - Add fuzzy tolerance when comparing host name lists as found in session profile configuration and as reported by broker agent. - In x2gobroker.conf: describe the manifold ways of providing a second authorized_keys file location in SSH server daemon. Thanks to Stefan Heitmüller for pointing out more recent SSH server's configuration style. - WSGI implementation: keep SCRIPT_NAME in environ, as removing it causes AssertionErrors whenever we trigger a tornado.web.HTTPError. - Add password prompt to x2gobroker-testauth. Password prompt is used if the --password option is not used. - New authentication mechanism: none. Always authenticate a user, even if password is not provided or wrong. - Ship python2.6 asyncore patch (Debian squeeze python2.6 version) in python-x2gobroker's docs folder. - Show correct environment variables in log file prelude when WSGI is used. - Fix check-credentials = false for UCCS web frontend. - Add a start page (,,It works''). - Use IP addresses in apache2 config rather than hostnames. - Add new helper tool: x2gobroker-daemon-debug. - Add man page for x2gobroker-daemon-debug. - WebUI "plain": throw explainative log errors for every 404 http error. - Fix man pages (layout issues on x2gobroker-authservice man page). - Adapt man page installation to moval of x2gobroker(-testauth) from an sbin to a bin directory (executable for any user). - Make the inifile broker backend the default backend. (Fixes: #360). - Support daemonizing of the http broker. - Default to http broker mode when daemonizing the broker. - Support daemonizing of the authservice. - Detect RUNDIR in x2gobroker-authservice and use it for the default location of the authservice socket file. - Detect RUNDIR in x2gobroker Python module and use it for the default location of the authservice socket file. - Let x2gobroker-authservice take care of tidying up its own socket file. - Provide PAM config file for Debian and RHEL separately (as they differ). - Makefile: Clean up x2gobroker-agent binary. - Be more precise in Debian et al. init scripts when checking if the service is already running. - Add JSON WebUI backend for X2Go Session Broker. - JSON WebUI backend renders data of content type "text/json". - Provide configuration alternative to having /etc/defaults/* scripts parsed in by init scripts. Make X2Go Session Broker ready for being run via systemd. - Provide symlink x2gobroker-daemon. - Provide systemd service files for x2gobroker-daemon and x2gobroker-authservice. (Fixes: #379, #380). - Add --drop-privileges feature so that x2gobroker-daemon can drop root privileges when started via systemd. Only drop privileges if x2gobroker(-daemon) is run as uidNumber 0. - Implement dynamic authid for JSON WebUI frontend. Add a generic metadata top level to the JSON output tree. - Store cookies in /var/lib/x2gobroker (path is more appropriate than previously suggested path /var/log/x2gobroker). - Handle selectsessions calls with a non-existent profile ID gracefully. - Session profiles with marker user=BROKER_USER will now auto-fill-in the broker username into the session profile's 'user' option. - Provide tool: x2gobroker-testagent. - Allow for broker clients to send in public SSH keys that the client may use for authentication to X2Go Servers. - broker agent: avoid one option system() calls in Perl. (Fixes: #784). - For user context changes: set the HOME dir of the new user correctly. - Reduce Paramiko/SSH verbosity (logging.ERROR) when connecting to remote broker agents. - Support adding remote broker agent's host keys via the x2gobroker-testagent tool. - If we received an SSH public key from a broker client, mark it as ACCEPTED after we deployed it, so that the client knows that it can its corresponding private key. - Fix https brokerage in x2gobroker-daemon-debug. - Load X2GOBROKER_DAEMON_USER's known_hosts key file before doing remote agent calls. - Fully rewrite agent.py. - Fix broker crashes when no session status is available for certain session profiles. - JSON webUI: run pre and post auth scripts also via this backend. - x2gobroker-daemon: become wrapper script, enable --mode HTTP by default. Provide some intelligence when run as daemon (killing children processes on reception of a SIGTERM, SIGINT, SIGQUIT, EXIT signal). - Rename sections for broker backends in x2gobroker.conf - Make config object of x2gobroker.conf available in authentication mechanism backends. - Fix SSH based broker client. - Fix several failing tests, adapt tests to current code base. - Introduce new global parameter for x2gobroker.conf: my-cookie-file. Allow storing the initial authentication cookie/ID in a read-protected file. - Explicitly set detach_process to True when calling daemon.DaemonContext(). Otherwise the daemons start but don't return to the cmdline prompt. (Fixes: #484). - Change agent API: all functions return a tuple where the first element denotes if the underlying agent call has been successful. - Correctly detect $HOME of the user that runs x2gobroker (including setuid calls via x2gobroker-ssh). - Enforce SSH agent query mode (instead of LOCAL mode) for SSH brokerage (as LOCAL query mode won't work due to a permission koan that has not yet been solved). - Fix interpretation of SSH_CLIENT env variable. - Make x2gobroker-agent usable/installable on non-X2Go server machines. (Fixes: #493). - Provide autologin support for session profiles that have an SSH proxy host configured. (Fixes: #494). - Fix IPv6 binding of the X2Go Session Broker daemon. If no bind port is given via the cmdline, obtain it from other means (via x2gobroker.defaults). - Rename LICENSE file to COPYING. - X2Go Broker Agent: Test if queried username exists on the system before performing the query. - Make sure bind_address and bind_port are correctly detected from /etc/default/x2gobroker-daemon and /etc/x2go/broker/defaults.cfg. - Move split_host_address() code into x2gobroker.utils. - Report to log what the broker agent replied to us. - Provide support for load-balancing to hosts that are all reachable over the same IP address, but different TCP/IP ports (e.g. docker instances or hosts behind a reverse NATed IPv4 gateway). This ended up in a rewrite of the complete selection_session() method of the base broker code. - Use physical host address and port (if provided) for contacting remote broker agent via SSH. - Update README and TODO. - Update copyright holders. Copyright is held only by people who actually contributed to the current code base. - logrotate configs: Rotated logs via "su x2gobroker adm". - Use hostname as hard-coded in server_list (from session profile configuration), don't try to strip off the domain name. - Consolidate x2gobroker.utils.split_host_address() with a test and rewrite completely. - Make sure that without configuration files, the HTTP broker listens to port 8080. - Provide legacy support for deprecated x2gobroker.conf global parameter 'check-credentials'. - Configure broker / authservice environment via .service files. - Load defaults.conf via authservices and for logger configuration, as well. - x2gobroker-authservice: Make sure socket file directory is created before trying to create the socket file itself. - Don't load defaults.conf twice. Only load it when initializing the loggers. - Provide a special PAM configuration file for SUSE systems (identical to the PAM configuration file for Debian). - defaults.conf: Mention X2GOBROKER_DEBUG not only in the global section, but also in the [daemon] and [authservice] section. - x2gobroker-testauth: Don't use hard-coded default backend. Obtain X2GOBROKER_DEFAULT_BACKEND from x2gobroker.defaults instead. - x2gobroker-testauth: Improve help text of --backend option. Display the current backend default. - x2gobroker-authservice: Restructure logging. Enable log messages for authentication requests. - Get several issues around select_session fixed via tests in the broker's backend base.py. - Add tests for broker agent queries. - Fix setting the remote agent's SSH port if the host option is of style " (:)". - During select_session: Re-add subdomain (if possible) to the hostname to make sure we can detect the host's : further down in the code. - Properly set (/var)/run/x2gobroker directory permissions when started via systemd. - Fix privilege check for the broker daemon's log directory. - Enable basic/random load-balancing for UCCS broker frontend. Make UCCS frontend aware of host session profile options of the form "host= (:). - Do a portscan on the remote's SSH port before querying a remote agent via SSH. - Don't return X2Go Servers that are actually down, currently. The X2Go Servers get probed via a short portscan on the remote's SSH port. If that portscan fails, another remote X2Go Server is chosen from the list of available server (if any). This portscanning functionality can be switched off via "default-portscan-x2goservers" in x2gobroker.conf or via "broker-portscan-x2goservers" per session profile. (Fixes: #692). - When load-balancing, switch to chosen server as remote broker agent before deploying SSH keys. - Allow resuming sessions from servers even if one offline server has left bogus in the session DB (plus unit tests). - Fix remote agent detection if one ore more X2Go Servers are offline and hostname does not match host address (plus unit test). - Allow remote agent calls via hostname or host address when using the format " ()" in the session profile. This can be useful if the is a valid address on the local network (broker <-> communication), but the host address is valid for clients (client <-> server communication). - Don't check for running/suspended session if the session profile will request a shadowing session. - Disabled broker agent calls and load-balancing for session profiles that will request shadowing sessions. - Mention "usebrokerpass" session profile option in x2gobroker-sessionprofiles.conf. - Provide desktop sharing (shadow session) example in x2gobroker-sessionprofiles.conf. - Makefile: Add installation rules for x2gobroker-loadchecker. - x2gobroker.1: Since systemd there are not only init scripts. Rephrasing man page. - New feature: x2gobroker-loadchecker daemon. (Fixes: #686). - x2gobroker-agent.pl: Use var name server_usage instead of server_load. Reflects better what that var denotes. - agent.py: Completion of several __doc__ strings (missing @return:, @rtype: fields). - X2GoBroker.check_for_sessions(): Fix check for shadow / non-shadow sessions. - x2gobroker.1: Mention x2gobroker-ssh in its man page, differentiate between the different modes (http/ssh) of the x2gobroker application. - Pre-release pyflakes cleanup. - agent.py: Capture login failures in checkload() function. - agent.py: Allow providing a custom logger instance in all functions. - LoadChecker.loadchecker(): Use load checker daemon's logger instance for logging actions taken place in agent.py. - agent.py: Make agent query mode LOCAL behave similar to agent query mode SSH if things go wrong. - agent.py: Set result to None, if SSH connection to broker agent fails. - Calculate our own MemAvailable value in x2gobroker-agent.pl. Only kernels newer than v3.14 offer the MemAvailable: field in /proc/meminfo. - x2gobroker-agent.pl: Fix regexp for detecting number of CPUs and CPU frequency. - x2gobroker-agent.pl: Fall-back CPU detection for virtualized systems (e.g. QEMU hosts). - LoadChecker.loadchecker(): Report about query failures, as well, in query cycle summary. - LoadCheckerServiceHandler(): Add line breaks in per-profile output. Return nothing if the load checker service is unreachable. - agent.py: Let get_servers() return a dictionary with hostnames as keys and number of sessions as values. - Fix X2GoBroker.use_load_checker(): Obtain broker-* option via X2GoBroker.get_profile_broker(), not via X2GoBroker.get_profile(). - Various improvements / fixes for session selection via the load checker daemon. - Adapt tests to new load checker service feature. - Only check for 'load_factors' key in remote_agent dict, if agent query mode is SSH. - Fix detection of running x2gobroker-daemon process in Debian's SystemV init script. - Set default log level to "WARNING", not "DEBUG". - defaults/x2gobroker-logchecker.default: Fix copy+paste errors. - doc/README.x2goclient+broker.getting-started: Mention how to launch PyHoca-GUI in broker mode. - etc/broker/defaults.conf: Fix copy+paste errors. - etc/x2gobroker-wsgi.*.conf: Make host ACLs Apache2.4 compliant. - logrotate/x2gobroker-loadchecker: The loadchecker.log file needs to be owned by user x2gobroker. - rpm/x2gobroker-*.init: Fix copy+paste errors. - man pages: Update date. - If non-load-balanced session profiles reference a non-reachable host, hand-back the system's hostname to X2Go Client / Python X2Go. - Add security notice / disclaimer to x2gbroker.1 man page as suggested by Stefan Baur. (Fixes: #666). - Provide x2gobroker system user public keys to broker agents with SSH options--strongly restricting the key usage--now. Modify x2gobroker- pubkeyauthorizer in a way that it replaces non-option keys with the newly provided optionized/restricted pubkeys. (Fixes: #685). - etc/x2gobroker.conf: Switch over to using dynamic auth cookies by default. - X2GoBroker.get_agent_query_mode(): Immediately return overridden query mode. Avoid logging of the configured query mode. Write the overridden query mode to the logger instance instead. - Don't enforce agent query mode "SSH" for x2gobroker-ssh anymore. - If a single-host is unreachable, return the host address, not the hostname and let X2Go Client release itself, that the host is unreachable. - x2gobroker-loadchecker: Don't freeze if load information for a complete load-balanced server farm is unavailable. - x2gobroker-pubkeyauthorizer: Handle replacement of SSH pubkeys with wrong/ old SSH options. - x2gobroker-agent.pl: Add %U (uidNumber) and %G (primary gidNumber) as further possible substitutions for deriving the full path of the authorized_keys file where X2Go Broker Agent's deploys public SSH user keys to. (Fixes: #665). - agent.py: Use os.fork() instead of threading.Thread() to handle delayed executions of broker agent tasks. This assures that SSH pub keys are removed via the delauthkey broker agent task, if the SSH broker is used. (Fixes: #491). - Add run-optional-script support to SSH broker. - x2gobroker-ssh: When agent query mode is set to LOCAL, Execute x2gobroker-agent via sudo as group "X2GOBROKER_DAEMON_GROUP". (Fixes: #835). - When the x2gobroker-agent command call is shipped via $SSH_ORIGINAL_COMMAND environment var, make sure to strip-off "sh -c" from the command's beginning. - x2gobroker-agent.pl: Fix detection of X2Go's library path (x2gopath lib). - Implement "not-set" value for X2Go Client parameters. If a parameter is set to "not-set", the parameter won't be handed over to X2Go Client. (Fixes: #834, #836). - agent.py: Fix missing "task" parameter for task "ping" against a local broker agent. - Fix task ping when tested via the x2gobroker-testagent script. - Transliterate commands in session profiles to uppercase when checking if the command is supposed to launch a desktop session. * debian/control: + Provide separate bin:package for SSH brokerage: x2gobroker-ssh. + Replace LDAP support with session brokerage support in LONG_DESCRIPTION. + Fix SYNOPSIS texts. + Recommend apache2 and libapache2-mod-wsgi for x2gobroker-wsgi. + Fix position of XS-Python-Version: field. + Rework LONG_DESCRIPTION of bin:package x2gobroker-agent. Imporve line breaks, so that we now have lines that are close to 80 chars long. + Make x2gobroker-daemon a symlink and recognize HTTP mode by the executable's name. + Bump Standards: to 3.9.6. No changes needed. + Add to D (python-x2gobroker): python-urllib3. * debian/copyright: + Update file to match current status quo of upstream source files. * debian/x2gobroker-agent.dirs: + Provide empty log file directory. * debian/x2gobroker-wsgi postinst/postrm: + Make bin:package x2gobroker-wsgi compliant Debian's packaging style of Apache2.4 / Apache2.2. + On package purgal: Disable Apache2 config first and then attempt the removal of the x2gobroker user/group. + Pass $@ to our apacheconf_configure, apacheconf_remove functions to not break apache2-maintscript-helper. * debian/x2gobroker-ssh.postinst: + Assure proper file permissions, owner and group settings for x2gobroker-ssh. * debian/x2gobroker-ssh.prerm: + Drop dpkg-statoverride of /usr/bin/x2gobroker-ssh before package removal. * debian/*.postinst: + Assure that the log directory always exists (no matter what combination of packages got installed). * debian/python-x2gobroker.install: + Install defaults.conf into bin:package python-x2gobroker. * debian/source/format: + Switch to format 1.0. * rpm/*.init: + Provide initscripts that are likely to work on RHEL plus derivatives. * x2gobroker.spec: + Provide x2gobroker.spec file for building RPM packages. Inspired by the packaging work in OpenSuSE. + Split out python-x2gobroker sub-package. + Install Apache2 config symlinks to /etc/httpd (not /etc/apache2). + Make sure x2gobroker-agent wrapper gets installed into x2gobroker-agent sub-package. + Builds for EPEL-7 also have to systemd aware. + Provide separate bin:package for SSH brokerage: x2gobroker-ssh. + Adapt to building on openSUSE/SLES. + Rework Description: of bin:package x2gobroker-agent. Imporve line breaks, so that we now have lines that are close to 80 chars long. + Add x2gobroker-rpmlintrc file. + Don't package x2gobroker-daemon.1 nor x2gobroker-ssh.1 man pages twice. + On SUSE, we have /etc/apache2, not /etc/httpd. + On SUSE, we have to provide our own python-pampy package (and depend on that). In Fedora and RHEL, the same (upstream) software is named python-pam. (Fixes: #562). + For distro versions with systemd, provide /etc/x2go/broker/defaults.conf. For SysV distro versions, use /etc/defaults/* and source them via the init scripts. + No adm group on non-Debian systems by default. Using root instead on RPM based systems. + For Fedora 22 and beyond explicitly call python2 in all shebangs. + Add to BR: sudo (to have /etc/sudoers.d owned by some package). [ Josh Lukens ] * New upstream version (0.0.3.0): - Add support for dynamic cookie based auth after initial password auth. (Fixes: #447). - Add support to run pre and post authentication scripts. (Fixes: #449). - Add auth mechanism https_get. (Fixes: #450). - Change pre and post scripts to use common codebase across frontends. (Fixes: #469). - Add ability to have script run in select session after server is selected. - Add basic support for pulling https_get authmech config from configuration file. (Fixes: #470). - Fix typos and host/port mixups in the remote_sshproxy logic. (Fixes: #544). - Make sure find_busy_servers in agent.py returns a tuple (recent API change) to not break profiles with multiple servers. (Fixes: #545). - On session resumption take profile's host list into account. Don't resume sessions the profile has not been configured for. (Fixes: #553). [ Jason Alavaliant ] * New upstream version (0.0.3.0): - Handle spaces in broker login passwords when authservice is used. (Fixes: #706). - Don't strip off spaces from password strings. (Fixes: #716). [ Mihai Moldovan ] * x2gobroker.spec: + Change all python-pampy references to python-pam on non-SUSE systems. + Fix %build scriptlet: add missing "done" in while; do; done shell script part. + Don't do a weird escape slash dance in sed's replace command. Simply use another separator. * debian/rules: + Try to call common-binary-indep from common-binary-arch. -- X2Go Release Manager Sat, 20 Jun 2015 13:58:49 +0200 x2gobroker (0.0.2.3-0~x2go1) unstable; urgency=low * New upstream version (0.0.2.3): - inifile broker: Allow explicit specification combinations of » (
)« in host= session profile field. (Fixes: #218). - Add rootless=false to example session profiles for all Desktop sessions in x2gobroker-sessionprofiles.conf. - Handle the rootless property automatically for know-by-name desktop sessions. - Make enable-plain-output, enable-uccs-output functional. - Add agent-quer-mode »NONE«. Disable X2Go Broker Agent calls completely. - Add status={S,R} to session profile list items when returned through X2Go Session Broker. (Fixes: #152). Handle taking over of running sessions and resuming sessions more reliably. Provide mechanism to suspend/terminate sessions through X2Go Server's (>= 4.0.1.0) x2gocleansessions daemon. -- Mike Gabriel Fri, 07 Jun 2013 23:21:29 +0200 x2gobroker (0.0.2.2-0~x2go1) unstable; urgency=low * New upstream version (0.0.2.2): - Convert unicode type host fields into single element lists. Fix UCCS+zeroconf tests. - Correctly incorporate path to x2gobroker-agent.pl into x2gobroker-agent setuid wrapper. (Fixes: #216). -- Mike Gabriel Wed, 22 May 2013 17:32:03 +0200 x2gobroker (0.0.2.1-0~x2go1) unstable; urgency=low [ Mike Gabriel ] * New upstream version (0.0.2.1): - Remove trailing slashes from ManagementServer URLs. - In Apache2 vhost configuration example, move WSGI environment variable settings into VirtualHost setup. - Remove unused, not-yet-developed broker backends and frontends. - Remove old cruft from x2gobroker.conf. - Security fix for setuid wrapper x2gobroker-agent.c. Hard-code path to x2gobroker-agent.pl during build via defining a macro in the Makefile. Thanks to Richard Weinberger for spotting this!!! - Handle URLs in plain WebUI that have slashes (and subpaths) in the backend name. - In WSGI mode: only populate os.environ with variables matching »X2GOBROKER_*«. - Make X2GOBROKER_SESSIONPROFILES configurable via a SetEnv WSGI parameter in the httpd configuration possible. (Fixes: #210). * Provide init scripts and *.default files outside of /debian folder (as they are also relevant for non-Debian packaging). [ Jan Engelhardt ] * New upstream version (0.0.2.1): - Populate install target of Makefile. (Fixes: #201). - Install man pages and default files through Makefile, as well. (Fixes: #211). -- Mike Gabriel Sun, 19 May 2013 12:41:06 +0200 x2gobroker (0.0.2.0-0~x2go1) unstable; urgency=low * New upstream version (0.0.2.0): - Add a UCCS-like web frontend UI that allows unity greeter to offer X2Go session logins. At the time of writing this, on the system running Unity Greeter it requires a patched remote-login-service (see LP:1172943, LP:1172318) and patched unity-greeter (LP:1172928, LP:1172877). Additionally the system running Unity Greeter requires the extra packages lightdm-remote-session-x2go and libpam-x2go. -- Mike Gabriel Sat, 27 Apr 2013 12:20:22 +0200 x2gobroker (0.0.1.1-0~x2go1) unstable; urgency=low * New upstream version (0.0.1.1): - Add WSGI support to X2Go Session Broker. Allows plugging into Apache2 by using the mod_wsgi module. - Add Apache2 configuration for WSGI support that shows how to setup a VirtualHost for X2Go Session Broker. - For sessions profiles with autologin enable, add a dummy key session profile parameter that triggers key based auth in X2Go Client. (Fixes: #154). - Fix hard-coded path to x2gobroker's authservice socket. - Separate logging logic of x2gobroker-authservice from the rest of the logging in x2gobroker. (Fixes: #172). - x2gobroker-pubkeyauthorizer: no logging-to-file support anymore. (Fixes: #175). - Fix name of get() method for /pubkeys/ URL path. (Fixes: #176). - Move AuthService server code fully into x2gobroker-authservice daemon script. - Add forgotten file: x2gobroker-authservice-logger.conf. (Fixes: #180). - Add script: x2gobroker-testauth. - Add enable()/disable() methods to broker backends. - Move complete authservice logic into x2gobroker-authservice script. - Add command and directrdp session profile parameters to defaults. - Fix wrong usage of session option »cmd«, has to be »command«. * /debian/control: + Fix --root parameter in DEB_PYTHON_INSTALL_ARGS. + Let bin:package x2gobroker-authservice depend on python-x2gobroker (of the same version). (Fixes: #170). * Properly remove the X2Go Session broker log files on package purgal. * Let bin:package x2gobroker-authservice create x2gouser:x2gouser, as well. (Fixes: #171). -- Mike Gabriel Tue, 23 Apr 2013 21:38:07 +0200 x2gobroker (0.0.1.0-0~x2go1) unstable; urgency=low * New upstream version (0.0.1.0): - Only packaging issues fixed. * /debian/control, /debian/rules: + Add hack that allows building the package with python2 or pysupport, depending on what's present on the build system. (Fixes: #135). -- Mike Gabriel Sun, 10 Mar 2013 12:56:47 +0100 x2gobroker (0.0.0.7-0~x2go1) unstable; urgency=low [ Mike Gabriel ] * New upstream version (0.0.0.7): - Add algorithm to ,,normalize'' hostnames used in session profiles vs. those returned by the broker agent. (Fixes: #133). - Ignore off-line X2Go servers in multi-node load-balanced setups. (Fixes: #132). - Return some sane output to x2goclient if the / all configured X2Go server(s) is/are down. - Tornado: Use RequestHandler.set_header() instead of RequestHandler.add_header(). * /debian/control: + Build-Depend on python-paste, python-nose (testsuite needs them). -- Mike Gabriel Thu, 07 Mar 2013 07:14:31 +0100 x2gobroker (0.0.0.6-0~x2go1) unstable; urgency=low [ Mike Gabriel ] * New upstream version (0.0.0.6): - Rewrite empty user parameter in session profile. Fill in the UID that has been used for broker authentication. - Implement session autologin feature. (Fixes: #134). - If X2Go Session Broker's PAM Auth Service is not available, try to fallback to direct PAM authentication (only works if x2gobroker runs as super-user root). - Switch from webpy to using tornado as http engine. - Divert tornado log requests into the broker's logger instances. - Add workaround to handle bug #138 in x2goclient. * /debian/*.default: - Be more explanatory about the X2GOBROKER_DEBUG option and allow to enable debug mode for the different services independently. (Fixes: #126). * /debian/x2gobroker-daemon.postinst: - Fix home path for user x2gobroker. (Fixes: #127). * /debian/control: - Dependency for python-x2gobroker: python-gevent. -- Mike Gabriel Wed, 06 Mar 2013 11:46:06 +0100 x2gobroker (0.0.0.5-0~x2go1) unstable; urgency=low [ Mike Gabriel ] * New upstream version (0.0.0.5): - Prepare for WSGI based integration into an external httpd. - Monkey patch Paramiko/SSH (adopted from Python X2Go). - Add variable X2GOBROKER_AGENT_USER, so that remote broker agents can theoretically run under another (i.e. != x2gobroker) user ID. - Properly set the Paramiko/SSH missing hostkey policy. * /debian/control: + Add dependency to python-x2gobroker: python-paramiko. * /debian/x2gobroker-daemon.default: + Fix variable names for SSL suport. [ Jan Engelhardt ] * New upstream version (0.0.0.5): - Avoid using install -o/-g. -- Mike Gabriel Wed, 27 Feb 2013 23:02:33 +0100 x2gobroker (0.0.0.4-0~x2go1) unstable; urgency=low * New upstream version (0.0.0.4): - Capture DNS resolver failures on client ACLs in cases where one of the listed hostnames in one client ACL definition is not resolvable. Such a failure will deny access to the corresponding session profile. - Fix init script x2gobroker-authservice. (Fixes: #124). -- Mike Gabriel Wed, 27 Feb 2013 11:53:23 +0100 x2gobroker (0.0.0.3-0~x2go1) unstable; urgency=low * New upstream version (0.0.0.3): - Script x2gobroker-pubkeyauthorizer is now independent from Python module x2gobroker. - Word wrap config files and limit line lenght to <= 80 chars. -- Mike Gabriel Thu, 21 Feb 2013 21:44:33 +0100 x2gobroker (0.0.0.2-0~x2go1) unstable; urgency=low * New upstream version (0.0.0.2): - Make CC, CFLAGS and LDFLAGS configurable through build system. - Make host session option a (Python) list, not unicode/string. - Add load balancing support. - Add file logging support. - Add logrotate configuration for x2gobroker log files. - Make the daemon user and group ID configurable through python-x2gobroker.default. - Set log level to CRITICAL if running unit tests. - Perform PAM authentication via an authentication service (the broker runs as non-privileged user, the authentication service as root). - To make SSH pubkey deployment easier, serve the broker's public SSH key(s) under this URL http(s)://:/pubkeys/. - Add tool: x2gobroker-keygen. Generate pub/priv SSH keypair for the system user x2gobroker. - Add tool: x2gobroker-pubkeyauthorizer. Retrive broker's public SSH keys and install them (on X2Go Servers with x2gobroker-agent installed). - Add man pages for all executables in /usr/sbin. * /debian/control: + Add bin:package x2gobroker-agent. * /debian/x2gobroker-daemon.init: + Handle stale PID file and already running daemon. + Handle different situation for X2GOBROKER_DAEMON_USER. Make sure the getpass.getuser() function sees the correct effective UID. + Make sure the unprivileged daemon user (x2gobroker) has access to the PID file directory. * postinst/postrm scripts: + The user x2gobroker is required by x2gobroker-agent and x2gobroker-daemon. So now both bin:packages provide that user account. -- Mike Gabriel Thu, 21 Feb 2013 19:51:30 +0100 x2gobroker (0.0.0.1-0~x2go1) unstable; urgency=low * Initial upstream version. -- Mike Gabriel Tue, 12 Feb 2013 22:51:12 +0100 x2gobroker-0.0.4.1/COPYING0000644000000000000000000010333313457267612011677 0ustar GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU Affero General Public License is a free, copyleft license for software and other kinds of works, specifically designed to ensure cooperation with the community in the case of network server software. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. Developers that use our General Public Licenses protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License which gives you legal permission to copy, distribute and/or modify the software. A secondary benefit of defending all users' freedom is that improvements made in alternate versions of the program, if they receive widespread use, become available for other developers to incorporate. Many developers of free software are heartened and encouraged by the resulting cooperation. However, in the case of software used on network servers, this result may fail to come about. The GNU General Public License permits making a modified version and letting the public access it on a server without ever releasing its source code to the public. The GNU Affero General Public License is designed specifically to ensure that, in such cases, the modified source code becomes available to the community. It requires the operator of a network server to provide the source code of the modified version running there to the users of that server. Therefore, public use of a modified version, on a publicly accessible server, gives the public access to the source code of the modified version. An older license, called the Affero General Public License and published by Affero, was designed to accomplish similar goals. This is a different license, not a version of the Affero GPL, but Affero has released a new version of the Affero GPL which permits relicensing under this license. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU Affero General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Remote Network Interaction; Use with the GNU General Public License. Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software. This Corresponding Source shall include the Corresponding Source for any work covered by version 3 of the GNU General Public License that is incorporated pursuant to the following paragraph. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the work with which it is combined will remain governed by version 3 of the GNU General Public License. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU Affero General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU Affero General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If your software can interact with users remotely through a computer network, you should also make sure that it provides a way for users to get its source. For example, if your program is a web application, its interface could display a "Source" link that leads users to an archive of the code. There are many ways you could offer source, and different solutions will be better for different programs; see section 13 for the specific requirements. You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see . x2gobroker-0.0.4.1/defaults/python-x2gobroker.default0000644000000000000000000000335313457267612017425 0ustar # X2Go Broker Session Broker (common) configuration for # SystemV-like init systems # The posix user/group ID the broker runs under (do not change!) # if you change those nonetheless, make sure that the log file # directory (default: /var/log/x2gobroker) and files in there are # writable by that user #X2GOBROKER_DAEMON_USER=x2gobroker #X2GOBROKER_DAEMON_GROUP=x2gobroker # The posix user under which the x2gobroker-agent can be launched on # remote X2Go Servers. #X2GOBROKER_AGENT_USER=x2gobroker # Control debug mode (0=disable, 1=enable). # # Apart from verbose logging in /var/log/x2gobroker/*.log, this will # also make the broker available through http GET method requests # (otherwise: POST method requests only) and you will be able to test # the broker through your web browser # # This value has an effect on all (Python-based) X2Go Session Broker # services and can be overridden in /etc/default/x2gobroker-* files. #X2GOBROKER_DEBUG=0 # Default X2Go Session Broker backend (available: zeroconf, inifile) #X2GOBROKER_DEFAULT_BACKEND=inifile # Path to the X2Go Session Broker's configuration file #X2GOBROKER_CONFIG=/etc/x2go/x2gobroker.conf # Path to the X2Go Session Broker's session profiles file (when using the inifile backend) #X2GOBROKER_SESSIONPROFILES=/etc/x2go/broker/x2gobroker-sessionprofiles.conf # Path to the X2Go Session Broker's agent command #X2GOBROKER_AGENT_CMD=/usr/lib/x2go/x2gobroker-agent # The unix socket file for communication between the broker and the authentication service. #X2GOBROKER_AUTHSERVICE_SOCKET=/run/x2gobroker/x2gobroker-authservice.socket # The unix socket file for communication between the broker and the authentication service. #X2GOBROKER_LOADCHECKER_SOCKET=/run/x2gobroker/x2gobroker-loadchecker.socket x2gobroker-0.0.4.1/defaults/x2gobroker-authservice.default0000644000000000000000000000152313457267612020423 0ustar # X2Go Session Broker (PAM Authentication Service) configuration for # SystemV-like init systems # For PAM authentication the X2Go Session Broker needs its authentication # service. The session broker itself runs as a non-privileged user (see below) # whereas the authentication service must run as super-user root. # # If you do not use PAM as authentication mechanism with the X2Go Session Broker, # you can disable the authentication service here. START_AUTHSERVICE=true # Control debug mode (0=disable, 1=enable) of the X2Go Broker Authentication # Service. # # Logging is (by default) written to /var/log/x2gobroker/*log. # # This option can also be configured in /etc/default/python-x2go. # The value configured here overrides the value from python-x2go # defaults and only sets the x2gobroker-authservice into debug mode. #X2GOBROKER_DEBUG=0 x2gobroker-0.0.4.1/defaults/x2gobroker-daemon.default0000644000000000000000000000261313457267612017345 0ustar # X2Go Session Broker configuration for SystemV-like init systems # Uncomment to enable the X2Go Session Broker standalone daemon START_BROKER=true # Bind standalone daemon to this address:port #DAEMON_BIND_ADDRESS=127.0.0.1:8080 # Control debug mode (0=disable, 1=enable). # # Apart from verbose logging in /var/log/x2gobroker/*.log, this will # also make the broker available through http GET method requests # (otherwise: POST method requests only) and you will be able to # test the broker through your web browser. # # This option can also be configured in /etc/default/python-x2go. # The value configured here overrides the value from python-x2go # defaults and only sets the x2gobroker-daemon into debug mode. #X2GOBROKER_DEBUG=0 ########################################################## ### ### ### Enable SSL Support ### ### o You have to create your own SSL certificates ### ### o You have to actively uncomment the below SSL ### ### relevant line to enable https:// in x2gobroker ### ### ### ########################################################## # SSL certificate file #X2GOBROKER_SSL_CERTFILE=/etc/x2go/broker/ssl/broker.crt # SSL key file (ensure permissions are set to root:x2gobroker:0640) #X2GOBROKER_SSL_KEYFILE=/etc/x2go/broker/ssl/broker.key x2gobroker-0.0.4.1/defaults/x2gobroker-loadchecker.default0000644000000000000000000000140013457267612020337 0ustar # X2Go Session Broker (Load Checker Service) configuration # SystemV-like init systems # The X2Go Session Broker ships a daemon that checks X2Go Server # system metrics in the background. If you want to use this daemon, # enable it here and adapt x2gobroker.conf. # NOTE: The load checker service only makes sense with broker setups # that mediate X2Go load-balancing. START_LOADCHECKER=false # Control debug mode (0=disable, 1=enable) of the X2Go Broker Load Checker # Service. # # Logging is (by default) written to /var/log/x2gobroker/*log. # # This option can also be configured in /etc/default/python-x2go. # The value configured here overrides the value from python-x2go # defaults and only sets the x2gobroker-loadchecker into debug mode. #X2GOBROKER_DEBUG=0 x2gobroker-0.0.4.1/docs/Makefile0000644000000000000000000001713513457267612013240 0ustar # Makefile for Sphinx documentation # # required in the Python X2Go Broker code... e.g. in x2gobroker/defaults.py SPHINX_API_DOCUMENTATION_BUILD = yes # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source .PHONY: help help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " applehelp to make an Apple Help Book" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " epub3 to make an epub3" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" @echo " coverage to run coverage check of the documentation (if enabled)" @echo " dummy to check syntax errors of document sources" .PHONY: clean clean: rm -rf $(BUILDDIR)/* .PHONY: html html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." .PHONY: dirhtml dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." .PHONY: singlehtml singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." .PHONY: pickle pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." .PHONY: json json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." .PHONY: htmlhelp htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." .PHONY: qthelp qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/X2GoSessionBroker.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/X2GoSessionBroker.qhc" .PHONY: applehelp applehelp: $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp @echo @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." @echo "N.B. You won't be able to view it unless you put it in" \ "~/Library/Documentation/Help or install it in your application" \ "bundle." .PHONY: devhelp devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/X2GoSessionBroker" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/X2GoSessionBroker" @echo "# devhelp" .PHONY: epub epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." .PHONY: epub3 epub3: $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 @echo @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." .PHONY: latex latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." .PHONY: latexpdf latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." .PHONY: latexpdfja latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." .PHONY: text text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." .PHONY: man man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." .PHONY: texinfo texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." .PHONY: info info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." .PHONY: gettext gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." .PHONY: changes changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." .PHONY: linkcheck linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." .PHONY: doctest doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." .PHONY: coverage coverage: $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage @echo "Testing of coverage in the sources finished, look at the " \ "results in $(BUILDDIR)/coverage/python.txt." .PHONY: xml xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." .PHONY: pseudoxml pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." .PHONY: dummy dummy: $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy @echo @echo "Build finished. Dummy builder generates no files." x2gobroker-0.0.4.1/docs/README.x2goclient+broker.getting-started.md0000644000000000000000000000423613457267612021516 0ustar # X2Go Client / X2Go Broker Setup The easy setup for getting a first impression is this: ## Installing X2Go Client, X2Go Server and X2Go Broker Install x2goclient, x2gobroker and x2goserver(-xsession) locally. For testing purposes, you can run all three components on one host. For production environments, you distribute the services over different machines and probably run many X2Go Server instances and man X2Go Client instances. $ sudo apt install x2gobroker-daemon x2gobroker-authservice $ sudo apt install x2goclient $ sudo apt install x2goserver-xsession ## Test Configuration Edit the file /etc/default/x2gobroker-daemon. Enable the daemon and, if needed, adapt the bind address:port (DAEMON_BIND_ADDRESS) parameter. After editing, save the file and return to your terminal command line. From here, start the daemon: $ invoke-rc.d x2gobroker-daemon restart ## Approach 1: Launch X2Go Client Launch x2goclient in HTTP broker mode: $ x2goclient --broker-url=http://127.0.0.1:8080/plain/ (if you have changed the bind address, use your changed value here). ## Approach 2: Launch PyHoca-GUI Alternatively, launch pyhoca-gui in HTTP broker mode: $ pyhoca-gui --broker-url=https://127.0.0.1:8080/json/ (if you have changed the bind address, use your changed value here). ## The Broker Authentication The first authentication that the x2goclient asks for is an authentication against the X2Go Broker (a HTTP server using the Tornado web framework on default port 8080). As authentication backend the PAM setup of your system is used, investigate this further by looking at this file: $ sudo editor /etc/pam.d/x2gobroker ## The X2Go Client WebUI in Broker Mode If you have successfully authenticated against the broker, then you get offered two session profiles named ,,KDE - localhost'' and ,,MATE - localhost''. These session profiles can be modified and customized in /etc/x2go/broker/x2gobroker-sessionprofiles.conf ## Launch an X2Go Session via Broker Backend If you select either of the session profiles (KDE or MATE on localhost), you will start an X2Go session on your local machine. (Of course, either KDE or the MATE desktop needs to be installed locally). x2gobroker-0.0.4.1/docs/source/conf.py0000644000000000000000000003050713457267612014375 0ustar #!/usr/bin/env python3 # -*- coding: utf-8 -*- # vim:fenc=utf-8 # # X2Go Session Broker documentation build configuration file, created by # sphinx-quickstart on Fri Sep 7 08:45:37 2018. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # import os import sys sys.path.insert(0, os.path.abspath('../../')) import x2gobroker import distutils.version import sphinx from datetime import date sphinxver = distutils.version.LooseVersion(sphinx.__version__) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.todo', ] sphinx_want_ver = distutils.version.LooseVersion('1.0') if sphinxver >= sphinx_want_ver: extensions.append('sphinx.ext.viewcode') # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The encoding of source files. # # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = 'X2Go Session Broker' copyright = '{year}, {author}'.format(year=date.today().year, author=x2gobroker.__AUTHOR__) author = '{author}'.format(author=x2gobroker.__AUTHOR__) # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = x2gobroker.__VERSION__ # The full version, including alpha/beta/rc tags. release = x2gobroker.__VERSION__ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = 'en' # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # # today = '' # # Else, today_fmt is used as the format for a strftime call. # # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', 'tests'] # The reST default role (used for this markup: `text`) to use for all # documents. # # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = True # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # sphinx_want_ver = distutils.version.LooseVersion('1.0') if sphinxver >= sphinx_want_ver: html_theme = 'haiku' else: html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. # " v documentation" by default. # html_title = 'Python X2Go Broker API Documentation (v{ver})'.format(ver=release) # A shorter title for the navigation bar. Default is the same as html_title. # # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # # html_logo = None # The name of an image file (relative to this directory) to use as a favicon of # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # # html_extra_path = [] # If not None, a 'Last updated on:' timestamp is inserted at every page # bottom, using the given strftime format. # The empty string is equivalent to '%b %d, %Y'. # # html_last_updated_fmt = None # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # # html_additional_pages = {} # If false, no module index is generated. # # html_domain_indices = True # If false, no index is generated. # # html_use_index = True # If true, the index is split into individual pages for each letter. # # html_split_index = False # If true, links to the reST sources are added to the pages. # # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh' # # html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # 'ja' uses this config value. # 'zh' user can custom change `jieba` dictionary path. # # html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. # # html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. htmlhelp_basename = 'PythonX2GoBrokerDoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'PythonX2GoBroker.tex', 'X2Go Session Broker API Documentation', 'Mike Gabriel', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # # latex_use_parts = False # If true, show page references after internal links. # # latex_show_pagerefs = False # If true, show URL addresses after external links. # # latex_show_urls = False # Documents to append as an appendix to all manuals. # # latex_appendices = [] # It false, will not define \strong, \code, itleref, \crossref ... but only # \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added # packages. # # latex_keep_old_macro_names = True # If false, no module index is generated. # # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'x2gobroker', 'X2Go Session Broker API Documentation', [author], 1) ] # If true, show URL addresses after external links. # # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'X2GoBroker', 'X2Go Session Broker API Documentation', author, 'X2GoBroker', 'Manage an X2Go Server Farm via Session Brokerage', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # # texinfo_appendices = [] # If false, no module index is generated. # # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # # texinfo_no_detailmenu = False # -- Options for Epub output ---------------------------------------------- # Bibliographic Dublin Core info. epub_title = project epub_author = author epub_publisher = author epub_copyright = copyright # The basename for the epub file. It defaults to the project name. # epub_basename = project # The HTML theme for the epub output. Since the default themes are not # optimized for small screen space, using the same theme for HTML and epub # output is usually not wise. This defaults to 'epub', a theme designed to save # visual space. # # epub_theme = 'epub' # The language of the text. It defaults to the language option # or 'en' if the language is not set. # # epub_language = '' # The scheme of the identifier. Typical schemes are ISBN or URL. # epub_scheme = '' # The unique identifier of the text. This can be a ISBN number # or the project homepage. # # epub_identifier = '' # A unique identification for the text. # # epub_uid = '' # A tuple containing the cover image and cover page html template filenames. # # epub_cover = () # A sequence of (type, uri, title) tuples for the guide element of content.opf. # # epub_guide = () # HTML files that should be inserted before the pages created by sphinx. # The format is a list of tuples containing the path and title. # # epub_pre_files = [] # HTML files that should be inserted after the pages created by sphinx. # The format is a list of tuples containing the path and title. # # epub_post_files = [] # A list of files that should not be packed into the epub file. epub_exclude_files = ['search.html'] # The depth of the table of contents in toc.ncx. # # epub_tocdepth = 3 # Allow duplicate toc entries. # # epub_tocdup = True # Choose between 'default' and 'includehidden'. # # epub_tocscope = 'default' # Fix unsupported image types using the Pillow. # # epub_fix_images = False # Scale large images. # # epub_max_image_width = 0 # How to display URL addresses: 'footnote', 'no', or 'inline'. # # epub_show_urls = 'inline' # If false, no index is generated. # # epub_use_index = True x2gobroker-0.0.4.1/docs/source/index.rst0000644000000000000000000000767513457267612014751 0ustar .. X2Go Session Broker documentation master file, created by sphinx-quickstart on Fri Sep 7 08:45:37 2018. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to X2Go Session Broker's Documentation ============================================== Brokerage for X2Go is the add-on feature that turns X2Go into a site-wide configurable desktop solution. With brokerage support, site-admins can... * provision X2Go client session profiles on-the-fly via one or more central broker servers * provision X2Go client session profiles based on user and/or group privileges * hook X2Go client into non-PAM, non-SSH authentication mechanisms * let X2Go users resume suspended sessions on X2Go server farms * etc. There are many X2Go broker implementations out there, mostly running in commercial production environments. Mostly highly customized for the customer that ordered such a broker. The official **X2Go Session Broker** is the attempt of providing X2Go users with a generically configurable X2Go broker that is easy to administrate. This API documentation is about Python X2GoBroker. Python X2GoBroker is the brainy backend behind X2Go Session Broker. The goal of this API documentation is to provide enough information for you to allow you customizing X2Go Session Broker to your needs and also possibly contribute your code back to the X2Go developers' community. With this broker approach, we attempt at providing (a) a free and quickly-to-use broker for X2Go (b) an easy-to-extend piece of Python software that allows site admins and/or developers to adapt the current code base to their specific use cases (c) a brokerage solution hat can be used in production environments The Concept ----------- In standalone setups, an X2Go client application knows the session profiles that the user configure locally (in a file named ``~/.x2goclient/sessions`` (or in the Windows registry, for *X2Go Client for Windows*). In brokerage setups, there is one (or more) server(s) that tell the X2Go client application what X2Go servers and session types are available on the corporate network. The **authentication** to an X2Go sessions falls into two parts: (1) authentication against the X2Go Session Broker (2) authentication against the X2Go Server (where the remote session will be run) This authentication split-up adds an extra authentication step that we try to reduce by providing the so-calls broker autologon feature. An X2Go client that could successfully authenticate against an X2Go Session Broker is legitimate to launch an X2Go session on attached X2Go servers. So, the second authentication step (to the actual X2Go Server) can be handled by the broker internally. To achieve this, the X2Go Session Broker requires a tool on each attached X2Go server, the so called **X2Go Broker Agent**. X2Go Session Broker can ask the X2Go Broker Agent to perform several tasks: * temporarily deploy public SSH user keys * query X2Go server load factors * check, if a remote X2Go server is actually available for login (Down for maintenance? Maximum number of users already reached?) * query the attached servers, if logging-in broker user already has a running (or suspended) session * do some extra checks on X2Go Server integrity (site-admin hackable, e.g. file systems writeable, home directories mounted, etc.) Further Information ------------------- Please do not hesitate to ask for more information. Visit our website [1] or contact the developers [2]. References ---------- * [1] https://wiki.x2go.org/ * [2] mailto:x2go-dev@lists.x2go.org Commercial Support ------------------ Commercial support for the X2Go Session Broker is provided by: * DAS-NETZWERKTEAM, Mike Gabriel Contents -------- .. toctree:: :maxdepth: 4 x2gobroker Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` x2gobroker-0.0.4.1/docs/source/modules.rst0000644000000000000000000000010313457267612015265 0ustar x2gobroker ========== .. toctree:: :maxdepth: 4 x2gobroker x2gobroker-0.0.4.1/docs/source/_static/.placeholder0000644000000000000000000000000013457267612016770 0ustar x2gobroker-0.0.4.1/docs/source/_templates/.placeholder0000644000000000000000000000000013457267612017477 0ustar x2gobroker-0.0.4.1/docs/source/x2gobroker.agent.rst0000644000000000000000000000021313457267612017000 0ustar x2gobroker.agent module ======================= .. automodule:: x2gobroker.agent :members: :undoc-members: :show-inheritance: x2gobroker-0.0.4.1/docs/source/x2gobroker.authmechs.base_authmech.rst0000644000000000000000000000030113457267612022450 0ustar x2gobroker.authmechs.base_authmech module ========================================= .. automodule:: x2gobroker.authmechs.base_authmech :members: :undoc-members: :show-inheritance: x2gobroker-0.0.4.1/docs/source/x2gobroker.authmechs.https_get_authmech.rst0000644000000000000000000000032013457267612023540 0ustar x2gobroker.authmechs.https_get_authmech module ============================================== .. automodule:: x2gobroker.authmechs.https_get_authmech :members: :undoc-members: :show-inheritance: x2gobroker-0.0.4.1/docs/source/x2gobroker.authmechs.none_authmech.rst0000644000000000000000000000030113457267612022475 0ustar x2gobroker.authmechs.none_authmech module ========================================= .. automodule:: x2gobroker.authmechs.none_authmech :members: :undoc-members: :show-inheritance: x2gobroker-0.0.4.1/docs/source/x2gobroker.authmechs.pam_authmech.rst0000644000000000000000000000027613457267612022326 0ustar x2gobroker.authmechs.pam_authmech module ======================================== .. automodule:: x2gobroker.authmechs.pam_authmech :members: :undoc-members: :show-inheritance: x2gobroker-0.0.4.1/docs/source/x2gobroker.authmechs.rst0000644000000000000000000000064713457267612017676 0ustar x2gobroker.authmechs package ============================ Submodules ---------- .. toctree:: x2gobroker.authmechs.base_authmech x2gobroker.authmechs.https_get_authmech x2gobroker.authmechs.none_authmech x2gobroker.authmechs.pam_authmech x2gobroker.authmechs.testsuite_authmech Module contents --------------- .. automodule:: x2gobroker.authmechs :members: :undoc-members: :show-inheritance: x2gobroker-0.0.4.1/docs/source/x2gobroker.authmechs.testsuite_authmech.rst0000644000000000000000000000032013457267612023570 0ustar x2gobroker.authmechs.testsuite_authmech module ============================================== .. automodule:: x2gobroker.authmechs.testsuite_authmech :members: :undoc-members: :show-inheritance: x2gobroker-0.0.4.1/docs/source/x2gobroker.authservice.rst0000644000000000000000000000023513457267612020230 0ustar x2gobroker.authservice module ============================= .. automodule:: x2gobroker.authservice :members: :undoc-members: :show-inheritance: x2gobroker-0.0.4.1/docs/source/x2gobroker.basicauth.rst0000644000000000000000000000022713457267612017652 0ustar x2gobroker.basicauth module =========================== .. automodule:: x2gobroker.basicauth :members: :undoc-members: :show-inheritance: x2gobroker-0.0.4.1/docs/source/x2gobroker.brokers.base_broker.rst0000644000000000000000000000026513457267612021635 0ustar x2gobroker.brokers.base_broker module ===================================== .. automodule:: x2gobroker.brokers.base_broker :members: :undoc-members: :show-inheritance: x2gobroker-0.0.4.1/docs/source/x2gobroker.brokers.inifile_broker.rst0000644000000000000000000000027613457267612022344 0ustar x2gobroker.brokers.inifile_broker module ======================================== .. automodule:: x2gobroker.brokers.inifile_broker :members: :undoc-members: :show-inheritance: x2gobroker-0.0.4.1/docs/source/x2gobroker.brokers.rst0000644000000000000000000000050713457267612017357 0ustar x2gobroker.brokers package ========================== Submodules ---------- .. toctree:: x2gobroker.brokers.base_broker x2gobroker.brokers.inifile_broker x2gobroker.brokers.zeroconf_broker Module contents --------------- .. automodule:: x2gobroker.brokers :members: :undoc-members: :show-inheritance: x2gobroker-0.0.4.1/docs/source/x2gobroker.brokers.zeroconf_broker.rst0000644000000000000000000000030113457267612022537 0ustar x2gobroker.brokers.zeroconf_broker module ========================================= .. automodule:: x2gobroker.brokers.zeroconf_broker :members: :undoc-members: :show-inheritance: x2gobroker-0.0.4.1/docs/source/x2gobroker.client.plain.rst0000644000000000000000000000024013457267612020262 0ustar x2gobroker.client.plain module ============================== .. automodule:: x2gobroker.client.plain :members: :undoc-members: :show-inheritance: x2gobroker-0.0.4.1/docs/source/x2gobroker.client.rst0000644000000000000000000000036213457267612017165 0ustar x2gobroker.client package ========================= Submodules ---------- .. toctree:: x2gobroker.client.plain Module contents --------------- .. automodule:: x2gobroker.client :members: :undoc-members: :show-inheritance: x2gobroker-0.0.4.1/docs/source/x2gobroker.config.rst0000644000000000000000000000021613457267612017152 0ustar x2gobroker.config module ======================== .. automodule:: x2gobroker.config :members: :undoc-members: :show-inheritance: x2gobroker-0.0.4.1/docs/source/x2gobroker.defaults.rst0000644000000000000000000000022413457267612017513 0ustar x2gobroker.defaults module ========================== .. automodule:: x2gobroker.defaults :members: :undoc-members: :show-inheritance: x2gobroker-0.0.4.1/docs/source/x2gobroker.loadchecker.rst0000644000000000000000000000023513457267612020152 0ustar x2gobroker.loadchecker module ============================= .. automodule:: x2gobroker.loadchecker :members: :undoc-members: :show-inheritance: x2gobroker-0.0.4.1/docs/source/x2gobroker.loggers.rst0000644000000000000000000000022113457267612017343 0ustar x2gobroker.loggers module ========================= .. automodule:: x2gobroker.loggers :members: :undoc-members: :show-inheritance: x2gobroker-0.0.4.1/docs/source/x2gobroker.nameservices.base_nameservice.rst0000644000000000000000000000032313457267612023662 0ustar x2gobroker.nameservices.base_nameservice module =============================================== .. automodule:: x2gobroker.nameservices.base_nameservice :members: :undoc-members: :show-inheritance: x2gobroker-0.0.4.1/docs/source/x2gobroker.nameservices.libnss_nameservice.rst0000644000000000000000000000033113457267612024241 0ustar x2gobroker.nameservices.libnss_nameservice module ================================================= .. automodule:: x2gobroker.nameservices.libnss_nameservice :members: :undoc-members: :show-inheritance: x2gobroker-0.0.4.1/docs/source/x2gobroker.nameservices.rst0000644000000000000000000000056413457267612020377 0ustar x2gobroker.nameservices package =============================== Submodules ---------- .. toctree:: x2gobroker.nameservices.base_nameservice x2gobroker.nameservices.libnss_nameservice x2gobroker.nameservices.testsuite_nameservice Module contents --------------- .. automodule:: x2gobroker.nameservices :members: :undoc-members: :show-inheritance: x2gobroker-0.0.4.1/docs/source/x2gobroker.nameservices.testsuite_nameservice.rst0000644000000000000000000000034213457267612025002 0ustar x2gobroker.nameservices.testsuite_nameservice module ==================================================== .. automodule:: x2gobroker.nameservices.testsuite_nameservice :members: :undoc-members: :show-inheritance: x2gobroker-0.0.4.1/docs/source/x2gobroker.optional_scripts.base_script.rst0000644000000000000000000000032013457267612023572 0ustar x2gobroker.optional_scripts.base_script module ============================================== .. automodule:: x2gobroker.optional_scripts.base_script :members: :undoc-members: :show-inheritance: x2gobroker-0.0.4.1/docs/source/x2gobroker.optional_scripts.rst0000644000000000000000000000044013457267612021300 0ustar x2gobroker.optional_scripts package =================================== Submodules ---------- .. toctree:: x2gobroker.optional_scripts.base_script Module contents --------------- .. automodule:: x2gobroker.optional_scripts :members: :undoc-members: :show-inheritance: x2gobroker-0.0.4.1/docs/source/x2gobroker.rst0000644000000000000000000000116013457267612015705 0ustar x2gobroker package ================== Subpackages ----------- .. toctree:: x2gobroker.authmechs x2gobroker.brokers x2gobroker.client x2gobroker.nameservices x2gobroker.optional_scripts x2gobroker.web Submodules ---------- .. toctree:: x2gobroker.agent x2gobroker.authservice x2gobroker.basicauth x2gobroker.config x2gobroker.defaults x2gobroker.loadchecker x2gobroker.loggers x2gobroker.uccsjson x2gobroker.utils x2gobroker.x2gobroker_exceptions Module contents --------------- .. automodule:: x2gobroker :members: :undoc-members: :show-inheritance: x2gobroker-0.0.4.1/docs/source/x2gobroker.uccsjson.rst0000644000000000000000000000022413457267612017533 0ustar x2gobroker.uccsjson module ========================== .. automodule:: x2gobroker.uccsjson :members: :undoc-members: :show-inheritance: x2gobroker-0.0.4.1/docs/source/x2gobroker.utils.rst0000644000000000000000000000021313457267612017042 0ustar x2gobroker.utils module ======================= .. automodule:: x2gobroker.utils :members: :undoc-members: :show-inheritance: x2gobroker-0.0.4.1/docs/source/x2gobroker.web.extras.rst0000644000000000000000000000023213457267612017765 0ustar x2gobroker.web.extras module ============================ .. automodule:: x2gobroker.web.extras :members: :undoc-members: :show-inheritance: x2gobroker-0.0.4.1/docs/source/x2gobroker.web.json.rst0000644000000000000000000000022413457267612017431 0ustar x2gobroker.web.json module ========================== .. automodule:: x2gobroker.web.json :members: :undoc-members: :show-inheritance: x2gobroker-0.0.4.1/docs/source/x2gobroker.web.plain.rst0000644000000000000000000000022713457267612017566 0ustar x2gobroker.web.plain module =========================== .. automodule:: x2gobroker.web.plain :members: :undoc-members: :show-inheritance: x2gobroker-0.0.4.1/docs/source/x2gobroker.web.rst0000644000000000000000000000045513457267612016467 0ustar x2gobroker.web package ====================== Submodules ---------- .. toctree:: x2gobroker.web.extras x2gobroker.web.json x2gobroker.web.plain x2gobroker.web.uccs Module contents --------------- .. automodule:: x2gobroker.web :members: :undoc-members: :show-inheritance: x2gobroker-0.0.4.1/docs/source/x2gobroker.web.uccs.rst0000644000000000000000000000022413457267612017415 0ustar x2gobroker.web.uccs module ========================== .. automodule:: x2gobroker.web.uccs :members: :undoc-members: :show-inheritance: x2gobroker-0.0.4.1/docs/source/x2gobroker.x2gobroker_exceptions.rst0000644000000000000000000000027313457267612022235 0ustar x2gobroker.x2gobroker_exceptions module ======================================= .. automodule:: x2gobroker.x2gobroker_exceptions :members: :undoc-members: :show-inheritance: x2gobroker-0.0.4.1/etc/broker/defaults.conf0000644000000000000000000000652113457267612015362 0ustar [common] # X2Go Broker Session Broker (common) configuration for hosts using # systemd # The posix user/group ID the broker runs under (do not change!) # if you change those nonetheless, make sure that the log file # directory (default: /var/log/x2gobroker) and files in there are # writable by that user #X2GOBROKER_DAEMON_USER=x2gobroker #X2GOBROKER_DAEMON_GROUP=x2gobroker # The posix user under which the x2gobroker-agent can be launched on # remote X2Go Servers. #X2GOBROKER_AGENT_USER=x2gobroker # Control debug mode (0=disable, 1=enable). # # Apart from verbose logging in /var/log/x2gobroker/*.log, this will # also make the broker available through http GET method requests # (otherwise: POST method requests only) and you will be able to test # the broker through your web browser #X2GOBROKER_DEBUG=0 # Default X2Go Session Broker backend (available: zeroconf, inifile) #X2GOBROKER_DEFAULT_BACKEND=inifile # Path to the X2Go Session Broker's configuration file #X2GOBROKER_CONFIG=/etc/x2go/x2gobroker.conf # Path to the X2Go Session Broker's session profiles file (when using the inifile backend) #X2GOBROKER_SESSIONPROFILES=/etc/x2go/broker/x2gobroker-sessionprofiles.conf # Path to the X2Go Session Broker's agent command #X2GOBROKER_AGENT_CMD=/usr/lib/x2go/x2gobroker-agent # The unix socket file for communication between the broker and the authentication service. #X2GOBROKER_AUTHSERVICE_SOCKET=/run/x2gobroker/x2gobroker-authservice.socket # The unix socket file for communication between the broker and the load checker service. #X2GOBROKER_LOADCHECKER_SOCKET=/run/x2gobroker/x2gobroker-loadchecker.socket [daemon] # X2Go Session Broker configuration for hosts using systemd # Bind standalone daemon to this address:port #DAEMON_BIND_ADDRESS=127.0.0.1:8080 # Produce verbose log output in the daemon's log files only. # Enabling debug mode here does not affect other parts of the # X2Go Session Broker. #X2GOBROKER_DEBUG=0 ########################################################## ### ### ### Enable SSL Support ### ### o You have to create your own SSL certificates ### ### o You have to actively uncomment the below SSL ### ### relevant line to enable https:// in x2gobroker ### ### ### ########################################################## # SSL certificate file #X2GOBROKER_SSL_CERTFILE=/etc/x2go/broker/ssl/broker.crt # SSL key file (ensure permissions are set to root:x2gobroker:0640) #X2GOBROKER_SSL_KEYFILE=/etc/x2go/broker/ssl/broker.key [authservice] # X2Go Session Broker (PAM Authentication Service) configuration for # hosts using systemd. # # currently nothing to configure for the authentication service... # Control debug mode (0=disable, 1=enable). # # Produce verbose log output in the authservice's log file only. # Enabling debug mode here does not affect other parts of the # X2Go Session Broker. #X2GOBROKER_DEBUG=0 [loadchecker] # X2Go Session Broker (Load Checker Service) configuration for # hosts using systemd # # currently nothing to configure for the load checker service... # Control debug mode (0=disable, 1=enable). # # Produce verbose log output in the load checker's log file only. # Enabling debug mode here does not affect other parts of the # X2Go Session Broker. #X2GOBROKER_DEBUG=0 x2gobroker-0.0.4.1/etc/broker/x2gobroker.authid0000644000000000000000000000056013457267612016165 0ustar # Store the broker authentication cookie/ID in a separate file. # This allows you to hide this cookie from users that have SSH # access to your server. # # You can change it with the uuidgen. # # If the below UUID hash looks like this: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx # then it will not be used. Create your own UUID, please. xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx x2gobroker-0.0.4.1/etc/broker/x2gobroker-authservice-logger.conf0000644000000000000000000000277613457267612021444 0ustar # This file is part of the X2Go Project - https://www.x2go.org # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. # WARNING: only modify this file if you _exactly_ know what you are doing!!! [loggers] keys=root,authservice [logger_root] level=NOTSET handlers=stderrHandler [handlers] keys=stderrHandler,authserviceFileHandler [formatters] keys=authserviceFormatter [handler_stderrHandler] class=StreamHandler args=(sys.stderr,) [logger_authservice] level=WARNING handlers=authserviceFileHandler qualname=authservice propagate=0 [handler_authserviceFileHandler] class=FileHandler formatter=authserviceFormatter args=('/var/log/x2gobroker/authservice.log',) [formatter_authserviceFormatter] format=%(asctime)s - %(name)s - %(levelname)s - %(message)s datefmt= x2gobroker-0.0.4.1/etc/broker/x2gobroker-loadchecker-logger.conf0000644000000000000000000000277613457267612021366 0ustar # This file is part of the X2Go Project - https://www.x2go.org # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. # WARNING: only modify this file if you _exactly_ know what you are doing!!! [loggers] keys=root,loadchecker [logger_root] level=NOTSET handlers=stderrHandler [handlers] keys=stderrHandler,loadcheckerFileHandler [formatters] keys=loadcheckerFormatter [handler_stderrHandler] class=StreamHandler args=(sys.stderr,) [logger_loadchecker] level=WARNING handlers=loadcheckerFileHandler qualname=loadchecker propagate=0 [handler_loadcheckerFileHandler] class=FileHandler formatter=loadcheckerFormatter args=('/var/log/x2gobroker/loadchecker.log',) [formatter_loadcheckerFormatter] format=%(asctime)s - %(name)s - %(levelname)s - %(message)s datefmt= x2gobroker-0.0.4.1/etc/broker/x2gobroker-loggers.conf0000644000000000000000000000415113457267612017274 0ustar # This file is part of the X2Go Project - https://www.x2go.org # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. # WARNING: only modify this file if you _exactly_ know what you are doing!!! [loggers] keys=root,broker,access,error [logger_root] level=NOTSET handlers=stderrHandler [handlers] keys=stderrHandler,brokerFileHandler,accessFileHandler,errorFileHandler [formatters] keys=brokerFormatter,accessFormatter,errorFormatter [handler_stderrHandler] class=StreamHandler args=(sys.stderr,) [logger_broker] level=WARNING handlers=brokerFileHandler qualname=broker propagate=0 [handler_brokerFileHandler] class=FileHandler formatter=brokerFormatter args=('/var/log/x2gobroker/broker.log',) [formatter_brokerFormatter] format=%(asctime)s - %(name)s - %(levelname)s - %(message)s datefmt= [logger_access] level=WARNING handlers=accessFileHandler qualname=access propagate=0 [handler_accessFileHandler] class=FileHandler formatter=accessFormatter args=('/var/log/x2gobroker/access.log',) [formatter_accessFormatter] format=%(asctime)s - %(name)s - %(levelname)s - %(message)s datefmt= [logger_error] level=WARNING handlers=errorFileHandler qualname=error propagate=0 [handler_errorFileHandler] class=FileHandler formatter=accessFormatter args=('/var/log/x2gobroker/error.log',) [formatter_errorFormatter] format=%(asctime)s - %(name)s - %(levelname)s - %(message)s datefmt= x2gobroker-0.0.4.1/etc/broker/x2gobroker-sessionprofiles.conf0000644000000000000000000001404713457267612021066 0ustar ### X2Go Broker Session Profiles - ADAPT TO YOUR NEEDS ### # This whole file reflects a set of examplary X2Go session profiles being # provided via the X2Go Session Broker (backend: iniconf). # This whole file could be the broker setup in some university institute that # runs three server pools (pool-A, pool-B and pool-C). Though most univerities # have real IPv4 internet addresses, we use private subnets in the examples # below. # The X2Go Session Broker is served into the institutes local intranet, the # broker cannot be reached from the internet directly. # The first section [DEFAULTS] provides a set of default profile settings that # are common to all session profiles given in sections below. # The other section names can be freely chosen, however, each section name has # to be unique within this file. # IMPORTANT: in the session profiles below you will find some lines starting # with acl-... These lines do neither protect the X2Go Session Broker nor # your X2Go Servers. They simply allow for selective session profile provision # based on client address, user name and group memberships. # # For protecting the broker use iptables and ip6tables. For protecting your # X2Go Servers use iptable+ip6tables and a tightened PAM configuration (e.g. # pam_access.so). Securing X2Go Servers means securing the SSH daemon that # runs on the X2Go Server. [DEFAULT] command=TERMINAL defsndport=true useiconv=false iconvfrom=UTF-8 height=600 export= quality=9 fullscreen=false layout= useexports=true width=800 speed=2 soundsystem=pulse print=true type=auto sndport=4713 xinerama=true variant= usekbd=true fstunnel=true applications=TERMINAL,WWWBROWSER,MAILCLIENT,OFFICE multidisp=false sshproxyport=22 sound=true rootless=true iconvto=UTF-8 soundtunnel=true dpi=96 sshport=22 setdpi=0 pack=16m-jpeg directrdp=false user=BROKER_USER [localhost-kde] name=KDE - localhost host=localhost command=KDE usebrokerpass=true [localhost-mate] name=MATE - localhost host=localhost command=MATE usebrokerpass=true [localhost-shadow] name=SHADOW - localhost # don't even try load-balancing here... it makes not sense and won't work (first given host will be used!) host=localhost command=SHADOW usebrokerpass=true ### EXAMPLES: Below you find some config examples. Adapt them to your needs or ### simply write your own session profiles and remove the examples below. ## ## EXAMPLE: pool-A (staff servers) ## ## The pool-A contains three X2Go Servers (server-A, server-B and server-C). ## The staff of our example institute falls into two groups of users: ## gnome-users and kde-users. ## The gnome-users log into server-A or server-B, depending on their client ## subnet (IP configuration of the client). ## The kde-users login to server-C (server-C can be reached from the whole ## intranet). ## ## The client IP based split-up of the GNOME users allows some primitive load ## balancing. ## ## If staff people are members of both groups (kde-users, gnome-users) both ## session profiles will be shown in X2Go Client. ## #[pool-A-server-A] #user= #host=server-a.pool-a.domain.local #name=GNOME - pool-A (srv-A) #command=GNOME #rootless=false #acl-groups-allow=gnome-users,admins #acl-groups-deny=ALL #acl-clients-deny=ALL #acl-clients-allow=10.1.0.0/16 #acl-any-order=deny-allow #broker-session-autologin=true #[pool-A-server-B] #user= #host=server-b.pool-a.domain.local #name=GNOME - pool-A (srv-B) #command=GNOME #rootless=false #acl-groups-allow=gnome-users,admins #acl-groups-deny=ALL #acl-clients-deny=ALL #acl-clients-allow=10.2.0.0/16 #acl-any-order=deny-allow #broker-session-autologin=true #[pool-A-server-C] #user= #host=server-c.pool-a.domain.local #name=KDE - pool-A (srv-C) #command=KDE #rootless=false #acl-groups-allow=kde-users,admins #acl-groups-deny=ALL #acl-any-order=deny-allow #broker-session-autologin=true ## ## EXAMPLE: pool-B (e.g. webserver in the DMZ or on the internet) ## ## The pool-B is a single X2Go Server (server-D) that is ## hosted externally. The server-D has an official internet IP. ## ## The session profile for server-D shall be provided to the ## admins group only. ## ## Furthermore, the session profile for server-D shall only get ## offered to a member of the admins group if the admin is sitting ## in front of one of the admin client machines. ## #[pool-B-server-D-LXDE] #user= #host=server-d (server-d.domain.internet) #name=LXDE - srv-D #command=LXDE #rootless=false #acl-groups-allow=admins #acl-groups-deny=ALL ## make sure hostnames in client ACLs are resolvable via libnss!!! #acl-clients-deny=ALL #acl-clients-allow=admin-machine1.domain.local, admin-machine2.domain.local, admin-machine3.domain.local #acl-any-order=deny-allow ## ## EXAMPLE: pool-C (REAL LOAD BALANCING!!!) ## ## The pool-C is a server pool for students. Our example institute ## knows 200-300 students and has to offer working places for ## every student. ## ## The resource limits on these servers are pretty strict, so staff members ## normally stay away from these machines, anyway. Only two test account ## get this session profile into their X2Go Clients. ## ## The pool-C contains 6 X2Go Servers that serve all students users together ## as a load balance server farm. The servers' hostnames are s-E1, s-E2, ... ## (as found in /etc/hostname). The hosts, however, are not configured in DNS ## so we give their IPs explicitly (also works for IPv6). ## ## Make sure to install x2gobroker-agent on all these 6 X2Go Servers. Also make ## sure to once run the script x2gobroker-keygen on the broker host and once ## the script x2gobroker-pubkeyauthorizer per X2Go Server. ## ## All 6 X2Go Servers have to be configured to use the PostgreSQL X2Go session ## DB backend. ## #[pool-C-XFCE] #user= #host=s-E1 (10.0.2.11),s-E2 (10.0.2.12),s-E3 (10.0.2.13),s-E4 (10.0.2.14),s-E5 (10.0.2.15) #name=XFCE - pool-C #command=XFCE #rootless=false #acl-users-allow=testuser-A,testuser-B #acl-users-deny=ALL #acl-groups-allow=students,admins #acl-groups-deny=ALL #acl-any-order=deny-allow # this server pool has a special broker setup for SSH authorized_keys #broker-session-autologin=true #broker-authorized-keys=/var/lib/x2gobroker/ssh/%u/authorized_keys x2gobroker-0.0.4.1/etc/x2gobroker.conf0000644000000000000000000003760513457267612014362 0ustar # This file is part of the X2Go Project - https://www.x2go.org # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. ### ### GLOBAL section ### [global] # Allow unauthenticated connections? Then set both require-password and require-cookie to false. # Verify username/password combination sent by client #require-password = true # To secure server-client communication the client can start the communication # with a pre-set, agreed on authentication ID. Set the below value to true # to make the X2Go Session Broker require this feature #require-cookie = false # X2Go supports two different cookie authentication modes (static and dynamic). # Dynamic cookies send new cookie to client on every request. This could possibly # cause issues if a client ever tries multiple requests at the same time. #use-static-cookie = false # Once a client is authenticated their password is not revalidated until this # many seconds have elapsed from their initial authentication. #auth-timeout = 36000 # Client cookies (both static and dynamic) must be stored as local files. # This is the directory where those files will be stored. Please make sure # the permissions are set to allow the x2go broker process to write to this directory #cookie-directory = '/var/lib/x2gobroker/cookies' # Pre and Post authentication scripts give you the option to run outside scripts # or adjust the values of variables for users logging in. Pre scripts run just # before user authentication and Post scripts run just after even if authentication fails. # Select Session scripts run after load balancing right before the chosen server is sent # to the client. # # Set to list of scripts, comma seperated, with no spaces between. #pre_auth_scripts = #post_auth_scripts = #select_session_scripts = # Every server-client communication (between X2Go Client and broker) # has to be accompanied by this initial authentication cookie if require-cookie # is set above. This should be in the format of a UUID. #my-cookie = xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx # More secure... As this x2gobroker.conf file has 0644 file permissions, # all users with SSH access to this broker server can view this x2gobroker.conf # file and thus, the value for 'my-cookie' is exposed. # # By storing the authentication cookie/ID in a separate file, you get the chance # of securing the cookie from other users that have access to the broker machine. # # When running X2Go Session Broker as http daemon, then setting the # x2gobroker.authid file readable only by members of the $X2GO_DAEMON_GROUP # (normally POSIX group "x2gobroker") should suffice: # # $ chown :x2gobroker /etc/x2go/broker/x2gobroker.authid # $ chmod 0640 /etc/x2go/broker/x2gobroker.authid # # If you use the 'my-cookie-file' parameter below, then it supersedes the 'my-cookie' # UUID hash from above. #my-cookie-file = /etc/x2go/broker/x2gobroker.authid # By default the broker will pin user sessions to the IP address from which they # origionally authenticate. If you would like to skip that check set this to false. #verify-ip = true # X2Go Session Broker knows about two output formats: a text/plain based output # and a text/json based output that is compatible with UCCS. The different outputs # run under different URLs. # enable {base_url}/plain/ #enable-plain-output = true # enable {base_url}/json/ #enable-json-output = true # enable {base_url}/uccs/ #enable-uccs-output = false # use this URL base to create URL field in UCCS-style JSON output #my-uccs-url-base = http://localhost:8080/ # default authentication mechanism for all broker backends # Available auth mechs: pam, none, https_get #default-auth-mech = pam # how does this X2Go Session Broker instance retrieve user and group # information from the system? (defaults for all broker backends) # Available user/group db backends: libnss #default-user-db = libnss #default-group-db = libnss # on large deployments it is recommended to ignore primary group # memberships traversing into all user accounts for primary group # detection can be quite CPU intensive on the X2Go Broker server. #ignore-primary-group-memberships = true # X2Go session autologin via X2Go Session Broker # # Once authenticated against the session # broker, the user becomes a trusted user. That is, the X2Go session login can # be automatized by a very temporary SSH pub/priv key pair. Prior to the session # login the key is generated, after successful session login, the key is dropped # immediately. # # This option can be overridden by the session profile parameter # broker-session-autologin={true|false} #default-session-autologin=false # X2Go's authorized_keys file for broker mediated autologin sessions # # For the X2Go auto-login via X2Go Session Broker feature to work thoroughly, # the X2Go Session Broker has to place the temporary public SSH key into the # user's home directory. It is not recommended to use SSH's default # authorized_keys file for this but a separate and X2Go-specific authorized_keys # file ($HOME/.x2go/authorized_keys). # # Of course, the SSH daemon has to be made aware of this. This can be configured # in /etc/ssh/sshd_config like this (older SSH server versions): # # --- /etc/ssh/sshd_config.no-x2go 2013-03-01 09:57:04.000000000 +0100 # +++ /etc/ssh/sshd_config 2013-03-01 09:56:57.000000000 +0100 # @@ -28,7 +28,8 @@ # # RSAAuthentication yes # PubkeyAuthentication yes # AuthorizedKeysFile %h/.ssh/authorized_keys # +AuthorizedKeysFile2 %h/.x2go/authorized_keys # # # Don't read the user's ~/.rhosts and ~/.shosts files # IgnoreRhosts yes # # or like this (more recent SSH server versions): # # --- /etc/ssh/sshd_config.no-x2go 2013-03-01 09:57:04.000000000 +0100 # +++ /etc/ssh/sshd_config 2013-03-01 09:56:57.000000000 +0100 # @@ -28,7 +28,7 @@ # # RSAAuthentication yes # PubkeyAuthentication yes # -AuthorizedKeysFile %h/.ssh/authorized_keys # +AuthorizedKeysFile %h/.ssh/authorized_keys %h/.x2go/authorized_keys # # # Don't read the user's ~/.rhosts and ~/.shosts files # IgnoreRhosts yes # # This option can be overridden by the session profile parameter # broker-authorized-keys= # # In the given path name for the authorized_keys file, you can use these # substitutions: # # %h -> # %u -> # %U -> # %G -> # %% -> #default-authorized-keys=%h/.x2go/authorized_keys # X2Go Session Broker can also mediate logins to SSH proxy servers # # The authorized_keys file location on SSH proxy servers # for temporarily deploying SSH pubkey strings can be configured # below. # # This option can be overridden by the session profile parameter # broker-sshproxy-authorized-keys= #default-sshproxy-authorized-keys=%h/.x2go/authorized_keys # X2Go Broker Agent query mode # # The X2Go Broker Agent is needed for multi-server sites configured for # load balancing. Multi-server sites require a setup that uses the # PostgreSQL X2Go session DB backend. The X2Go Broker Agent has to be installed # on the local system (mode: LOCAL) or on all X2Go Servers (mode: SSH) in a # multi-server farm. # # So, there are three query modes for the X2GO Broker Agent: NONE, LOCAL and # SSH. # # NONE - Try to get along without X2Go Broker Agent queries. For simple # broker setups this may suffice. For load-balancing or reliable # session suspending and resuming the broker agent is a must!!! # # LOCAL - This LOCAL mode only works for _one_ configured multi-server farm. # If this X2Go Session Broker is supposed to serve many different # multi-server farms, then the LOCAL mode will not work!!! # # How it works: Assume that the local system has an X2Go Broker Agent # that knows about the multi-server setup. This means: X2Go Server # has to be installed locally and the X2Go Server has to be # configured to use the multi-server farm's PostgreSQL session DB # backend. # # The local system that is running the broker does not necessarily # have to be a real application server. It only has to be aware of # running/suspended sessions within the X2Go multi-server farm setup. # # A typical use-case is X2Go on top of a Debian Edu Terminal-Server # farm: # # TJENER -> PostgreSQL DB, X2Go Server, X2Go Session Broker + # Broker Agent # TS01 - TS0X -> X2Go Server configured to use the PostgreSQL DB # on TJENER # # SSH - The more generic approach, but also more complex. It allows that # the broker on this system may serve for many different X2Go Server # multi-server setups. # # With the SSH agent query mode, the X2Go Session Broker will query # one of the X2Go Servers in the targeted multi-server setup (through # SSH). The SSH authentication is done by a system user account # (normally UID=x2gobroker) and SSH pub/priv key authentication has # to be configured to make this work. # # All X2Go Servers in a multi-server farm need the X2Go Broker Agent # installed, whereas this local system running the X2Go Session # Broker does not need a local X2Go Broker Agent at all. # # The agent query mode can be configured on a per-broker-backend basis, the # below value is the default. #default-agent-query-mode=NONE # X2Go Broker's Host Key Policy (if agent query mode is 'SSH') # # If X2Go Broker's agent query mode is SSH, the system needs to handle # X2Go Server side's SSH host keys in a secure and verifyable manner. # # The agent-hostkey-policy is the default policy to be used and can be # either AutoAddPolicy, WarningPolicy, or RejectPolicy. The policy names # match the corresponding class names in Paramiko SSH. # # IMPORTANT: As RejectPolicy is the only safe default, please be aware that # on fresh X2Go Broker setups, SSH agent queries will always fail, until a # properly maintained ~x2gobroker/.ssh/known_hosts file is in place. # # There are two simple ways to create this known_hosts file: # # (a) su - x2gobroker -c "ssh ssh -o HostKeyAlgorithms=ssh-rsa " # # On the command line, you get prompted to confirm the remote # X2Go server's Follow OpenSSH interactive dialog for accepting # the remote host's host key. # # You will see an error coming from x2gobroker-agent.pl which can be # ignored. The important part is that you accepted the X2Go Server's # host key. # # (b) x2gobroker-testagent --add-to-known-hosts --host # # This command will populate the known_hosts file with the remote # X2Go server's hostkey while trying to hail its X2Go Broker Agent # The host key's fingerprint will not be shown on stdout and there # will be no interactive confirmation (patches welcome!!!). # If unsure about this, use approach (a) given above. # #default-agent-hostkey-policy=RejectPolicy # Probe SSH port of X2Go Servers (availability check) # # Just before offering an X2Go Server address to a broker client, the # X2Go Broker host can probe the X2Go Server's SSH port. In load balancing # setups this assures that the offered X2Go Server is really up and running. # # However, this requires that the broker host can SSH into the X2Go server # (this may not be the case in all thinkable firewall setups). # # Per default, we set this to "true" here. The portscan feature can be # deactivated on a per-session-profile basis (use: broker-portscan-x2goservers = # false in the session profile configuration). #default-portscan-x2goservers = true # Use load checker for querying X2Go Servers' loads in regular intervals # # When load-balancing shall be used, the simplest way to detect "server load" # is counting the numbers of running and suspended sessions. No extra daemon # nor service is required for this. # # However, simply counting running and suspended sessions per X2Go Server # as a representative for the server load can be highly inaccurate. A better # approach is checking each X2Go Server's load in regular intervals by a # separate daemon (running on the broker host) and querying this load checker # service before selecting the best server on session startup requests. # # The load factor calculation uses this algorithm: # # ( memAvail/1000 ) * numCPUs * typeCPUs # load-factor = -------------------------------------- + 1 # loadavg*100 * numSessions # # (memAvail in MByte, typeCPUs in MHz, loadavg is (system load *100 + 1) as # positive integer value) # # The higher the load-factor, the more likely that a server will be chosen # for the next to be allocated X2Go session. # # If you set the default-use-load-checker option here, the queries to the # x2gobroker-loadchecker daemon will be performed for all broker backends by # defaults. # # The x2gobroker-loadchecker only gets consulted, if: # # o if enabled here for all backends # o or if enabled on a per broker backend basis (see below) # o or if enabled per session profile (broker-use-load-checker = true) # # and # # o the session profile defines more than one host # o the session profile does not block queries to the load checker daemon # on a per profile basis (broker-use-load-checker = false) # #default-use-load-checker = false # If the x2gobroker-loadchecker daemon gets used, define here how # many seconds to sleep between cycles of querying system load from the # associated X2Go Servers. # #load-checker-intervals = 300 ### ### Auth Mechs section ### #[authmech_pam] # no configurable options for this authentication mechanism #[authmech_https_get] #host = my.webserver.com #path = /auth/index.html #port = 443 ### ### BACKEND section ### # Possible X2Go Session Broker backends: # # 1. backend = zeroconf (activated by default) # Use the ZeroConf X2Go Session Broker backend, this backend is for demo only # and only operates on localhost. Make sure you have x2gobroker-daemon and # and x2goserver installed on the same machine. No need to install # x2gobroker-agent. # 2. backend = infile (deactivated by default) # The IniFile X2Go Session Broker backend is for providing session profiles # to multiple users/clients on a text config file basis (.ini file format). # # The session profile setup is accomplished by an extra configuration file, # by default named /etc/x2go/broker/x2gobroker-sessionproiles.conf. # # For small-scale deployments the IniFile backend is the recommended backend. [broker_zeroconf] #enable = false #auth-mech = pam #user-db = libnss #group-db = libnss #desktop-shell = KDE [broker_inifile] #enable = true #session-profiles = /etc/x2go/broker/x2gobroker-sessionprofiles.conf #use-load-checker = false #[broker_ldap] -> MUSIC OF THE FUTURE #enable = false #auth-mech = ldap #user-db = ldap #group-db = ldap #uri = ldap://localhost:389 #base = dc=example,dc=org #user-search-filter = (&(objectClass=posixAccount)(uid=*)) #host-search-filter = (&(objectClass=ipHost)(serial=X2GoServer)(cn=*)) #group-search-filter = (&(objectClass=posifxGroup)(cn=*)) #starttls = false #agent-query-mode = SSH #use-load-checker = true x2gobroker-0.0.4.1/etc/x2gobroker-wsgi.apache.conf0000644000000000000000000000260113457267612016535 0ustar # enable debugging #SetEnv X2GOBROKER_DEBUG off # the default user/group that this WSGI application runs as #X2GOBROKER_DAEMON_USER=x2gobroker #X2GOBROKER_DAEMON_GROUP=x2gobroker WSGIDaemonProcess x2gobroker user=x2gobroker group=x2gobroker processes=5 threads=15 WSGIPassAuthorization On # default broker backend (default: zeroconf) #SetEnv X2GOBROKER_DEFAULT_BACKEND zeroconf SetEnv X2GOBROKER_DEFAULT_BACKEND inifile #SetEnv X2GOBROKER_DEFAULT_BACKEND ldap #SetEnv X2GOBROKER_DEFAULT_BACKEND # path to the X2Go Session Broker's configuration file #SetEnv X2GOBROKER_CONFIG /etc/x2go/x2gobroker.conf # path to the X2Go Session Broker's session profiles file (when using the inifile backend) #SetEnv X2GOBROKER_SESSIONPROFILES /etc/x2go/broker/x2gobroker-sessionprofiles.conf # path to the X2Go Session Broker's agent command #SetEnv X2GOBROKER_AGENT_CMD /usr/lib/x2go/x2gobroker-agent # authentication socket of the X2Go Broker's PAM Authentication Service #SetEnv X2GOBROKER_AUTHSOCKET /run/x2gobroker/x2gobroker-authservice.socket # if you have to-be-statically-served files somewhere below the broker URL #Alias /x2gobroker/static /some/static/path/ WSGIScriptAlias /x2gobroker /usr/lib/x2gobroker/wsgi/x2gobroker-wsgi WSGIProcessGroup x2gobroker Require local Options +FollowSymLinks Options -Indexes x2gobroker-0.0.4.1/etc/x2gobroker-wsgi.apache.vhost0000644000000000000000000000443513457267612016762 0ustar ### ### Virtual Host configuration for an X2Go Session Broker ### # # Make sure to disabled /etc/apache2/x2gobroker-wsgi completely if you # prefer setting up the X2Go Session Broker as a virtual host. # ServerName localhost ServerAdmin webmaster@localhost # enable debugging #SetEnv X2GOBROKER_DEBUG off # the default user/group that this WSGI application runs as #X2GOBROKER_DAEMON_USER=x2gobroker #X2GOBROKER_DAEMON_GROUP=x2gobroker WSGIDaemonProcess x2gobroker user=x2gobroker group=x2gobroker processes=5 threads=15 WSGIPassAuthorization On # default broker backend (default: inifile) #SetEnv X2GOBROKER_DEFAULT_BACKEND zeroconf #SetEnv X2GOBROKER_DEFAULT_BACKEND inifile #SetEnv X2GOBROKER_DEFAULT_BACKEND ldap #SetEnv X2GOBROKER_DEFAULT_BACKEND # path to the X2Go Session Broker's configuration file #SetEnv X2GOBROKER_CONFIG /etc/x2go/x2gobroker.conf # path to the X2Go Session Broker's session profiles file (when using the inifile backend) #SetEnv X2GOBROKER_SESSIONPROFILES /etc/x2go/broker/x2gobroker-sessionprofiles.conf # path to the X2Go Session Broker's agent command #SetEnv X2GOBROKER_AGENT_CMD /usr/lib/x2go/x2gobroker-agent # authentication socket of the X2Go Broker's PAM Authentication Service #SetEnv X2GOBROKER_AUTHSOCKET /run/x2gobroker/x2gobroker-authservice.socket # if you have to-be-statically-served files somewhere below the broker URL #Alias /x2gobroker/static /some/static/path/ WSGIScriptAlias / /usr/lib/x2gobroker/wsgi/x2goroker-wsgi WSGIProcessGroup x2gobroker Require local Options +FollowSymLinks Options -Indexes SSLOptions +StdEnvVars SSLEngine on # SSL Cipher Suite: SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL # Server Certificate: SSLCertificateFile /etc/x2go/broker/ssl/broker.crt # Server Private Key: SSLCertificateKeyFile /etc/x2go/broker/ssl/broker.key #SSLCertificateChainFile /etc/x2go/broker/ssl/cacert.key #SetEnvIf User-Agent ".*MSIE.*" \ # nokeepalive ssl-unclean-shutdown \ # downgrade-1.0 force-response-1.0 x2gobroker-0.0.4.1/init/x2gobroker-authservice.init0000755000000000000000000000614113457267612017102 0ustar #!/bin/sh # # Start the X2Go Session Broker PAM Authentication Service # # Copyright © 2014-2019 Mike Gabriel # Distributable under the terms of the GNU AGPL version 3+. # ### BEGIN INIT INFO # Provides: x2gobroker-authservice # Required-Start: $remote_fs $syslog # Required-Stop: $remote_fs $syslog # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: X2Go Session Broker PAM Authentication Service # Description: PAM authentication service for X2Go Session Broker ### END INIT INFO # set -eu AUTHSERVICE=/usr/sbin/x2gobroker-authservice test -d /run && RUNDIR=/run || RUNDIR=/var/run PIDFILE_AUTHSERVICE=$RUNDIR/x2gobroker/x2gobroker-authservice.pid DEBIANCONFIG_COMMON=/etc/default/python-x2gobroker DEBIANCONFIG_AUTHSERVICE=/etc/default/x2gobroker-authservice test -x "$AUTHSERVICE" || exit 0 START_AUTHSERVICE=false X2GOBROKER_DEBUG=0 X2GOBROKER_DAEMON_USER='x2gobroker' X2GOBROKER_DAEMON_GROUP='x2gobroker' X2GOBROKER_AUTHSERVICE_SOCKET="$RUNDIR/x2gobroker/x2gobroker-authservice.socket" test -f $DEBIANCONFIG_COMMON && . $DEBIANCONFIG_COMMON test -f $DEBIANCONFIG_AUTHSERVICE && . $DEBIANCONFIG_AUTHSERVICE if ! getent passwd $X2GOBROKER_DAEMON_USER 1>/dev/null 2>/dev/null; then X2GOBROKER_DAEMON_USER=nobody fi if ! getent group $X2GOBROKER_DAEMON_GROUP 1>/dev/null 2>/dev/null; then X2GOBROKER_DAEMON_GROUP=nogroup fi # create PID directory mkdir -p $RUNDIR/x2gobroker chown $X2GOBROKER_DAEMON_USER:$X2GOBROKER_DAEMON_GROUP $RUNDIR/x2gobroker chmod 0770 $RUNDIR/x2gobroker export X2GOBROKER_DEBUG export X2GOBROKER_DAEMON_USER export X2GOBROKER_DAEMON_GROUP export X2GOBROKER_AUTHSERVICE_SOCKET . /lib/lsb/init-functions is_true() { case "${1:-}" in [Yy]es|[Yy]|1|[Tt]|[Tt]rue) return 0;; *) return 1; esac } case "${1:-}" in start) if [ -f $PIDFILE_AUTHSERVICE ]; then if ps a -u root | grep -v egrep | egrep ".*$(basename $AUTHSERVICE).*$X2GOBROKER_AUTHSERVICE_SOCKET.*" 1>/dev/null 2>/dev/null; then log_warning_msg "X2Go Broker Authentication Service already running" else log_warning_msg "X2Go Broker Authentication Service: stale PID file ($PIDFILE_AUTHSERVICE). Delete it manually!" fi START_AUTHSERVICE=no fi if is_true $START_AUTHSERVICE; then set +e # once we are here, we have to make sure the authservice.socket does not exist rm -f $X2GOBROKER_AUTHSERVICE_SOCKET # and now we can start the auth service log_daemon_msg "Starting X2Go Broker Authentication Service" "$(basename $AUTHSERVICE)" start-stop-daemon -b -m -S -p $PIDFILE_AUTHSERVICE -x $AUTHSERVICE -- -s $X2GOBROKER_AUTHSERVICE_SOCKET -o root -g $X2GOBROKER_DAEMON_GROUP -p 0660 log_end_msg $? set -e fi ;; stop) if [ -f $PIDFILE_AUTHSERVICE ] ; then log_daemon_msg "Stopping X2Go Broker Authentication Service" "$(basename $AUTHSERVICE)" set +e start-stop-daemon -K -p $PIDFILE_AUTHSERVICE && rm -f $PIDFILE_AUTHSERVICE log_end_msg $? set -e fi ;; restart|reload|force-reload) ${0:-} stop ${0:-} start ;; *) echo "Usage: ${0:-} {start|stop|restart|reload|force-reload}" >&2 exit 1 ;; esac exit 0 x2gobroker-0.0.4.1/init/x2gobroker-daemon.init0000755000000000000000000000676713457267612016041 0ustar #!/bin/sh # # Start the X2Go Session Broker standalone daemon # # Copyright © 2012-2019 Mike Gabriel # Distributable under the terms of the GNU AGPL version 3+. # ### BEGIN INIT INFO # Provides: x2gobroker-daemon # Required-Start: $remote_fs $syslog x2gobroker-authservice # Required-Stop: $remote_fs $syslog # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: X2Go Session Broker standalone daemon # Description: X2Go Session Broker comes with its own HTTP daemon ### END INIT INFO # set -eu DAEMON=/usr/bin/x2gobroker-daemon test -d /run && RUNDIR=/run || RUNDIR=/var/run PIDFILE_BROKER=$RUNDIR/x2gobroker/x2gobroker-daemon.pid DEFAULTCONFIG_COMMON=/etc/default/python-x2gobroker DEFAULTCONFIG_DAEMON=/etc/default/x2gobroker-daemon test -x "$DAEMON" || exit 0 START_BROKER=false DAEMON_BIND_ADDRESS=127.0.0.1:8080 X2GOBROKER_DEBUG=0 X2GOBROKER_DAEMON_USER='x2gobroker' X2GOBROKER_DAEMON_GROUP='x2gobroker' X2GOBROKER_DEFAULT_BACKEND="inifile" X2GOBROKER_CONFIG="/etc/x2go/x2gobroker.conf" X2GOBROKER_SESSIONPROFILES="/etc/x2go/broker/x2gobroker-sessionprofiles.conf" X2GOBROKER_AGENT_CMD="/usr/lib/x2go/x2gobroker-agent" X2GOBROKER_AUTHSERVICE_SOCKET="$RUNDIR/x2gobroker/x2gobroker-authservice.socket" X2GOBROKER_SSL_CERTFILE= X2GOBROKER_SSL_KEYFILE= test -f $DEFAULTCONFIG_COMMON && . $DEFAULTCONFIG_COMMON test -f $DEFAULTCONFIG_DAEMON && . $DEFAULTCONFIG_DAEMON if ! getent passwd $X2GOBROKER_DAEMON_USER 1>/dev/null 2>/dev/null; then X2GOBROKER_DAEMON_USER=nobody fi if ! getent group $X2GOBROKER_DAEMON_GROUP 1>/dev/null 2>/dev/null; then X2GOBROKER_DAEMON_GROUP=nogroup fi # create PID directory mkdir -p $RUNDIR/x2gobroker chown $X2GOBROKER_DAEMON_USER:$X2GOBROKER_DAEMON_GROUP $RUNDIR/x2gobroker chmod 0770 $RUNDIR/x2gobroker # mend user ID variables when --chuid $X2GOBROKER_DAEMON_USER is used with start-stop-daemon export LOGNAME=$X2GOBROKER_DAEMON_USER export USER=$X2GOBROKER_DAEMON_USER export USERNAME=$X2GOBROKER_DAEMON_USER export X2GOBROKER_DEBUG export X2GOBROKER_DAEMON_USER export X2GOBROKER_DAEMON_GROUP export X2GOBROKER_CONFIG export X2GOBROKER_DEFAULT_BACKEND export X2GOBROKER_SESSIONPROFILES export X2GOBROKER_AGENT_CMD export X2GOBROKER_AUTHSERVICE_SOCKET export X2GOBROKER_SSL_CERTFILE export X2GOBROKER_SSL_KEYFILE . /lib/lsb/init-functions is_true() { case "${1:-}" in [Yy]es|[Yy]|1|[Tt]|[Tt]rue) return 0;; *) return 1; esac } case "${1:-}" in start) if [ -f $PIDFILE_BROKER ]; then if ps a -u $X2GOBROKER_DAEMON_USER | egrep -v "(grep|ps)" | awk '{ print $5 }' | grep "$(basename $DAEMON)" 1>/dev/null 2>/dev/null; then log_warning_msg "X2Go Session Broker already running" else log_warning_msg "X2Go Session Broker: stale PID file ($PIDFILE_BROKER). Delete it manually!" fi START_BROKER=no fi if is_true $START_BROKER; then log_daemon_msg "Starting X2Go Session Broker standalone daemon" "$(basename $DAEMON)" set +e start-stop-daemon --chuid $X2GOBROKER_DAEMON_USER -b -m -S -p $PIDFILE_BROKER -x $DAEMON -- -b $DAEMON_BIND_ADDRESS log_end_msg $? set -e fi ;; stop) if [ -f $PIDFILE_BROKER ] ; then log_daemon_msg "Stopping X2Go Session Broker standalone daemon" "$(basename $DAEMON)" set +e start-stop-daemon -K --signal TERM --quiet -p $PIDFILE_BROKER && rm -f $PIDFILE_BROKER log_end_msg $? set -e fi ;; restart|reload|force-reload) ${0:-} stop ${0:-} start ;; *) echo "Usage: ${0:-} {start|stop|restart|reload|force-reload}" >&2 exit 1 ;; esac exit 0 x2gobroker-0.0.4.1/init/x2gobroker-loadchecker.init0000755000000000000000000000621513457267612017026 0ustar #!/bin/sh # # Start the X2Go Session Broker Load Checker Service # # Copyright © 2014-2019 Mike Gabriel # Distributable under the terms of the GNU AGPL version 3+. # ### BEGIN INIT INFO # Provides: x2gobroker-loadchecker # Required-Start: $remote_fs $syslog # Required-Stop: $remote_fs $syslog # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: X2Go Session Broker Load Checker Service # Description: Load Checker service for X2Go Session Broker ### END INIT INFO # set -eu LOADCHECKER=/usr/sbin/x2gobroker-loadchecker test -d /run && RUNDIR=/run || RUNDIR=/var/run PIDFILE_LOADCHECKER=$RUNDIR/x2gobroker/x2gobroker-loadchecker.pid DEBIANCONFIG_COMMON=/etc/default/python-x2gobroker DEBIANCONFIG_LOADCHECKER=/etc/default/x2gobroker-loadchecker test -x "$LOADCHECKER" || exit 0 START_LOADCHECKER=false X2GOBROKER_DEBUG=0 X2GOBROKER_DAEMON_USER='x2gobroker' X2GOBROKER_DAEMON_GROUP='x2gobroker' X2GOBROKER_LOADCHECKER_SOCKET="$RUNDIR/x2gobroker/x2gobroker-loadchecker.socket" test -f $DEBIANCONFIG_COMMON && . $DEBIANCONFIG_COMMON test -f $DEBIANCONFIG_LOADCHECKER && . $DEBIANCONFIG_LOADCHECKER if ! getent passwd $X2GOBROKER_DAEMON_USER 1>/dev/null 2>/dev/null; then X2GOBROKER_DAEMON_USER=nobody fi if ! getent group $X2GOBROKER_DAEMON_GROUP 1>/dev/null 2>/dev/null; then X2GOBROKER_DAEMON_GROUP=nogroup fi # create PID directory mkdir -p $RUNDIR/x2gobroker chown $X2GOBROKER_DAEMON_USER:$X2GOBROKER_DAEMON_GROUP $RUNDIR/x2gobroker chmod 0770 $RUNDIR/x2gobroker export X2GOBROKER_DEBUG export X2GOBROKER_DAEMON_USER export X2GOBROKER_DAEMON_GROUP export X2GOBROKER_LOADCHECKER_SOCKET . /lib/lsb/init-functions is_true() { case "${1:-}" in [Yy]es|[Yy]|1|[Tt]|[Tt]rue) return 0;; *) return 1; esac } case "${1:-}" in start) if [ -f $PIDFILE_LOADCHECKER ]; then if ps a -u $X2GOBROKER_DAEMON_USER | grep -v egrep | egrep ".*$(basename $LOADCHECKER).*$X2GOBROKER_LOADCHECKER_SOCKET.*" 1>/dev/null 2>/dev/null; then log_warning_msg "X2Go Broker Load Checker Service already running" else log_warning_msg "X2Go Broker Load Checker Service: stale PID file ($PIDFILE_LOADCHECKER). Delete it manually!" fi START_LOADCHECKER=no fi if is_true $START_LOADCHECKER; then set +e # once we are here, we have to make sure the loadchecker.socket does not exist rm -f $X2GOBROKER_LOADCHECKER_SOCKET # and now we can start the auth service log_daemon_msg "Starting X2Go Broker Load Checker Service" "$(basename $LOADCHECKER)" start-stop-daemon --chuid $X2GOBROKER_DAEMON_USER -b -m -S -p $PIDFILE_LOADCHECKER -x $LOADCHECKER -- -s $X2GOBROKER_LOADCHECKER_SOCKET -o $X2GOBROKER_DAEMON_USER -g $X2GOBROKER_DAEMON_GROUP -p 0660 log_end_msg $? set -e fi ;; stop) if [ -f $PIDFILE_LOADCHECKER ] ; then log_daemon_msg "Stopping X2Go Broker Load Checker Service" "$(basename $LOADCHECKER)" set +e start-stop-daemon -K -p $PIDFILE_LOADCHECKER && rm -f $PIDFILE_LOADCHECKER log_end_msg $? set -e fi ;; restart|reload|force-reload) ${0:-} stop ${0:-} start ;; *) echo "Usage: ${0:-} {start|stop|restart|reload|force-reload}" >&2 exit 1 ;; esac exit 0 x2gobroker-0.0.4.1/lib/x2gobroker-agent.pl0000755000000000000000000002473713457267612015144 0ustar #!/usr/bin/perl -XU # This file is part of the X2Go Project - https://www.x2go.org # Copyright (C) 2011-2015 by Oleksandr Shneyder # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. use strict; use File::Basename; use File::Which; use POSIX; # are we running via SSH's ForceCommand? if ($ENV{"SSH_ORIGINAL_COMMAND"} =~ m/.*\/usr\/.*\/x2go\/x2gobroker-agent\ .*/ ) { my $ssh_original_command = $ENV{'SSH_ORIGINAL_COMMAND'}; my $ssh_original_command = (split(/;/, $ssh_original_command))[0]; $ssh_original_command =~ s/^sh -c (\/usr\/.*\/x2go\/x2gobroker-agent\ .*)/\1/; delete $ENV{"SSH_ORIGINAL_COMMAND"}; exit(system($ssh_original_command)); } my $username=shift or die; my $mode=shift or die; my @available_tasks = ( "availabletasks", "addauthkey", "delauthkey", ); if ( system("x2goversion x2goserver 1>/dev/null") == 0 ) { push @available_tasks, ( "listsessions", "findbusyservers", "findbusyservers_by_sessionstats", "checkload", "getservers", "suspendsession", "terminatesession", ); } sub InitX2GoUser { my ($user, $uidNumber, $gidNumber, $home)=@_; if ( -f "/etc/x2go/x2gosql/sql" ) { # if we use the PostgreSQL session db backend we may have to add the # user to the session database... open(F,"/etc/x2go/x2gosql/sql"); my @buf = ; close(F); if ( grep (/^backend=sqlite.*/, @buf) ) { #if ( ! -e "$home/.x2go/sqlpass" ) ### ### FIXME: make the below code robust if homes are on NFS ### ### #{ # open my $save_out, ">&STDOUT"; # close (STDOUT); # system "x2godbadmin", "--adduser", $user; # open STDOUT, ">&", $save_out; #} } } if (($ENV{"SUDO_USER"}) && ("$ENV{'SUDO_USER'}" ne "$username")) { die "You cannot execute x2gobroker-agent for any other user except you!"; } } sub AddAuthKey { my ($uid, $uidNumber, $gidNumber, $home, $pubkey, $authkeyfile)=@_; # rewrite %%, %u, %U, %G and %h in authkeyfile string $authkeyfile =~ s/%u/$uid/; $authkeyfile =~ s/%U/$uidNumber/; $authkeyfile =~ s/%G/$gidNumber/; $authkeyfile =~ s/%h/$home/; $authkeyfile =~ s/%%/%/; my $authkeydir = dirname($authkeyfile); if ( ! $authkeyfile =~ m/\/.*/ ) { $authkeyfile = "$home/$authkeyfile"; } # make sure dir and file for authorized_keys do exist system ("sudo", "-u", "$uid", "--", "mkdir", "-p", "$authkeydir"); system ("sudo", "-u", "$uid", "--", "touch", "$authkeyfile"); my $authorized_keys = `sudo -u $uid -- cat "$authkeyfile"`; if ( ! ( $authorized_keys =~ m/\Q$pubkey\E$/ ) ) { open my $saveout, ">&STDOUT"; open STDOUT, '>', "/dev/null"; open(TEE_STDIN, "| sudo -u $uid -- tee -a $authkeyfile"); print TEE_STDIN "$pubkey\n"; close(TEE_STDIN); open STDOUT, ">&", $saveout; } } sub DelAuthKey { my ($uid, $uidNumber, $gidNumber, $home, $pubkey, $authkeyfile)=@_; # rewrite %%, %u, %U, %G and %h in authkeyfile string $authkeyfile =~ s/%u/$uid/; $authkeyfile =~ s/%U/$uidNumber/; $authkeyfile =~ s/%G/$gidNumber/; $authkeyfile =~ s/%h/$home/; $authkeyfile =~ s/%%/%/; if ( ! $authkeyfile =~ m/\/.*/ ) { $authkeyfile = "$home/$authkeyfile"; } open my $saveout, ">&STDOUT"; open STDOUT, '>', "/dev/null"; open my $saveerr, ">&STDERR"; open STDERR, '>', "/dev/null"; system("sudo", "-u", "$uid", "--", "sed", "-e", "s!^$pubkey\$!!", "-e", "/^\$/d", "-i", "$authkeyfile"); open STDOUT, ">&", $saveout; open STDERR, ">&", $saveerr; } $< = $>; delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; $ENV{'PATH'} = '/bin:/usr/bin'; if($mode eq 'ping') { print "OK\n"; exit; } if ( $mode eq 'checkload' ) { print "OK\n"; # Read the values from /proc/loadavg and combine all three values in a linear # way and make a percent value out of the result: open FILE, "< /proc/loadavg" or die return ("Cannot open /proc/loadavg: $!"); my ($avg1, $avg5, $avg15, undef, undef) = split / /, ; close FILE; my $loadavgXX = ( $avg1 + $avg5 + $avg15 ) * 100/3 + 1; if ( $loadavgXX lt 1 ) { # if we get here, then our avgXX values were negative, # we should never get here... $loadavgXX = 1; } # calculate total memory vs. free memory my $memTotal; my $memFree; my $memAvail; my $pagesActiveFile; my $pagesInactiveFile; my $slabReclaimable; open FILE, "< /proc/meminfo" or die return ("Cannot open /proc/meminfo: $!"); foreach() { if ( m/^MemTotal:\s+(\S+)/ ) { $memTotal = $1; } if ( m/^MemFree:\s+(\S+)/ ) { $memFree = $1; } if ( m/^MemAvailable:\s+(\S+)/ ) { $memAvail = $1/1000; } if ( m/^Active\(file\):\s+(\S+)/ ) { $pagesActiveFile = $1; } if ( m/^Inactive\(file\):\s+(\S+)/ ) { $pagesInactiveFile = $1; } if ( m/^SReclaimable:\s+(\S+)/ ) { $slabReclaimable = $1; } } close(FILE); my $myMemAvail = 0; if ($myMemAvail == 0) { # taken from Linux kernel code in fs/proc/meminfo.c (since kernel version 3.14)... open FILE, "< /proc/sys/vm/min_free_kbytes" or die return ("Cannot open /proc/sys/vm/min_free_kbytes: $!"); my $memLowWatermark = ; $memLowWatermark =~ s/\n//g; close(FILE); $myMemAvail += $memFree - $memLowWatermark; my $pagecache = $pagesActiveFile + $pagesInactiveFile; $pagecache -= ($pagecache/2, $memLowWatermark) [$pagecache/2 > $memLowWatermark]; $myMemAvail += $pagecache; $myMemAvail += $slabReclaimable - ( ($slabReclaimable/2, $memLowWatermark) [$slabReclaimable/2 > $memLowWatermark]); if ($myMemAvail < 0) { $myMemAvail = 0; } $myMemAvail = $myMemAvail / 1000; } # check number and type of available CPU cores my $numCPU = 0; my $typeCPU; open FILE, "< /proc/cpuinfo" or die return ("Cannot open /proc/cpuinfo: $!"); foreach() { if ( m/model name.*CPU.*@\ ([\d\.]+)/ ) { $typeCPU = $1*1000; $numCPU += 1; } } close(FILE); # in virtual hosts, we need to obtain the CPU frequency from the cpu MHz: field. if ($typeCPU == 0) { open FILE, "< /proc/cpuinfo" or die return ("Cannot open /proc/cpuinfo: $!"); foreach() { if ( m/^cpu\ MHz\s+:\s+(\S+)/ ) { $typeCPU = ceil($1); $numCPU += 1; } } close(FILE); } print sprintf 'loadavgXX:%1$d', $loadavgXX; print "\n"; print sprintf 'memAvail:%1$d', $memAvail; print "\n"; print sprintf 'myMemAvail:%1$d', $myMemAvail; print "\n"; print sprintf 'numCPU:%1$d', $numCPU; print "\n"; print sprintf 'typeCPU:%1$d', $typeCPU; print "\n"; exit; } if($mode eq 'availabletasks') { print "OK\n"; my $available_task; foreach $available_task (@available_tasks) { print "$available_task\n"; } exit; } my ($uid, $passwd, $uidNumber, $gidNumber, $quota, $comment, $gcos, $home, $shell, $expire) = getpwnam($username); if(!defined $uidNumber) { die "no such user on system: $username"; } elsif($uidNumber < 1000) { die "operation on system user: $username (with UID number: $uidNumber)"; } if($mode eq 'listsessions' && which('x2golistsessions')) { InitX2GoUser($uid, $uidNumber, $gidNumber, $home); print "OK\n"; system("sudo", "-u", "$uid", "--", "x2golistsessions", "--all-servers"); } if( (($mode eq 'findbusyservers_by_sessionstats') || ($mode eq 'findbusyservers')) && which('x2gogetservers') ) { # Normally the session broker setup knows about all servers, # make sure your configuration of X2Go Session Broker is correct and # lists all available servers. # The findbusyservers algorithm only returns servers that are currently # in use (i.e. have running or suspended sessions on them). So the # result may be empty or contain a server list not containing all # available servers. # The logic of findbusyservers is this, then: # 1. if no server is returned, any of the configured servers is best server # 2. if some servers are returned, a best server is one that is not returned # 3. if all configured servers are returned, than evaluate the usage value # (e.g. 90:server1, 20:server2, 10:server3 -> best server is server3) # The above interpretation has to be handled by the broker implementation # calling »x2gobroker-agent findbusyservers«. InitX2GoUser($uid, $uidNumber, $gidNumber, $home); print "OK\n"; my $busy_servers = `sudo -u $uid -- x2gogetservers`; my %server_usage = (); my $num_sessions = 0; foreach (split('\n', $busy_servers)) { my ($hostname, $num_users) = split(' ', $_); $server_usage{$hostname} = $num_users; $num_sessions += $num_users; } # render the output result my @result; for my $hostname ( keys %server_usage ) { my $available = $server_usage{$hostname}/$num_sessions*100; push @result, sprintf '%1$d:%2$s', $available, $hostname; } print join("\n", sort @result); print "\n"; } if( $mode eq 'getservers' && which('x2gogetservers') ) { InitX2GoUser($uid, $uidNumber, $gidNumber, $home); print "OK\n"; exec ("sudo", "-u", "$uid", "--", "x2gogetservers"); } if($mode eq 'addauthkey') { my $pubkey = shift or die; my $authkeyfile = shift or die; InitX2GoUser($uid, $uidNumber, $gidNumber, $home); print "OK\n"; AddAuthKey($uid, $uidNumber, $gidNumber, $home, $pubkey, $authkeyfile); } if($mode eq 'delauthkey') { my $pubkey = shift or die; my $authkeyfile = shift or die; InitX2GoUser($uid, $uidNumber, $gidNumber, $home); print "OK\n"; DelAuthKey($uid, $uidNumber, $gidNumber, $home, $pubkey, $authkeyfile); } # use x2gosuspend-session to test if we can really do this... if( $mode eq 'suspendsession' && which('x2gosuspend-session') ) { InitX2GoUser($uid, $uidNumber, $gidNumber, $home); print "OK\n"; my $sid=shift; my $x2go_lib_path=`x2gopath lib`; exec ("sudo", "-u", "$uid", "--", "$x2go_lib_path/x2gochangestatus", "S", "$sid"); } # use x2goterminate-session to test if we can really do this... if( $mode eq 'terminatesession' && which('x2goterminate-session') ) { InitX2GoUser($uid, $uidNumber, $gidNumber, $home); print "OK\n"; my $sid=shift; my $x2go_lib_path=`x2gopath lib`; exec ("sudo", "-u", "$uid", "--", "$x2go_lib_path/x2gochangestatus", "T", "$sid"); } x2gobroker-0.0.4.1/logrotate/x2gobroker-authservice0000644000000000000000000000035413457267612017172 0ustar /var/log/x2gobroker/authservice.log { weekly missingok rotate 52 compress delaycompress notifempty create 640 root adm su root adm sharedscripts postrotate invoke-rc.d x2gobroker-authservice restart > /dev/null endscript } x2gobroker-0.0.4.1/logrotate/x2gobroker-daemon0000644000000000000000000000045313457267612016113 0ustar /var/log/x2gobroker/access.log /var/log/x2gobroker/broker.log /var/log/x2gobroker/error.log { weekly missingok rotate 52 compress delaycompress notifempty create 640 x2gobroker adm su x2gobroker adm sharedscripts postrotate invoke-rc.d x2gobroker-daemon restart > /dev/null endscript } x2gobroker-0.0.4.1/logrotate/x2gobroker-loadchecker0000644000000000000000000000036213457267612017113 0ustar /var/log/x2gobroker/loadchecker.log { weekly missingok rotate 52 compress delaycompress notifempty create 640 x2gobroker adm su root adm sharedscripts postrotate invoke-rc.d x2gobroker-loadchecker restart > /dev/null endscript } x2gobroker-0.0.4.1/logrotate/x2gobroker-wsgi0000644000000000000000000000033613457267612015621 0ustar /var/log/x2gobroker/broker.log /var/log/x2gobroker/error.log /var/log/x2gobroker/wsgi.log { weekly missingok rotate 52 compress delaycompress notifempty create 640 x2gobroker adm su x2gobroker adm sharedscripts } x2gobroker-0.0.4.1/Makefile0000755000000000000000000001740213457267612012310 0ustar #!/usr/bin/make -f # This file is part of the X2Go Project - https://www.x2go.org # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. SRC_DIR=$(CURDIR) SHELL=/bin/bash INSTALL_DIR=install -dm 755 INSTALL_FILE=install -m 644 INSTALL_PROGRAM=install -m 755 INSTALL_SYMLINK=ln -sf CC ?= gcc override CFLAGS += -fPIE override LDFLAGS += -pie RM_FILE=rm -f RM_DIR=rmdir -p --ignore-fail-on-non-empty GZIP = gzip -f --best DESTDIR ?= PREFIX ?= /usr/local ETCDIR=/etc/x2go BINDIR=$(PREFIX)/bin SBINDIR=$(PREFIX)/sbin LIBDIR ?= $(PREFIX)/lib DATADIR ?= $(PREFIX)/share MANDIR=$(DATADIR)/man X2GO_LIBDIR=$(LIBDIR)/x2go BIN_SCRIPTS=$(shell cd bin && echo *) SBIN_SCRIPTS=$(shell cd sbin && echo *) LIB_FILES=$(shell cd lib && echo *) PERL ?= /usr/bin/perl SKIP_INSTALL_PYMODULE ?= all: build build: build-arch build-indep build-arch: build_setuidwrappers build_pymodule build_setuidwrappers: $(CC) $(CFLAGS) $(LDFLAGS) -DTRUSTED_BINARY=\"$(X2GO_LIBDIR)/x2gobroker-agent.pl\" -o lib/x2gobroker-agent src/x2gobroker-agent.c $(CC) $(CFLAGS) $(LDFLAGS) -DTRUSTED_BINARY=\"$(BINDIR)/x2gobroker\" -o bin/x2gobroker-ssh src/x2gobroker-ssh.c build_pymodule: python3 setup.py build build-indep: distclean: clean clean: clean_arch clean_indep clean_arch: $(RM_FILE) lib/x2gobroker-agent $(RM_FILE) bin/x2gobroker-ssh clean_indep: check: python3 test.py install: mkdir -p "${DESTDIR}/var/lib/x2gobroker" \ "${DESTDIR}/var/log/x2gobroker" # python3-x2gobroker [ -z "${SKIP_INSTALL_PYMODULE}" ] && python3 setup.py install --prefix="${PREFIX}" $${DESTDIR+--root="${DESTDIR}"} || : mkdir -p "${DESTDIR}${ETCDIR}/broker" "${DESTDIR}/etc/pam.d" \ "${DESTDIR}/etc/default" ${INSTALL_FILE} defaults/python-x2gobroker.default \ "${DESTDIR}/etc/default/python-x2gobroker" ${INSTALL_FILE} etc/x2gobroker.conf "${DESTDIR}${ETCDIR}/" ${INSTALL_FILE} etc/broker/defaults.conf "${DESTDIR}${ETCDIR}/broker/" ${INSTALL_FILE} etc/broker/x2gobroker-sessionprofiles.conf "${DESTDIR}${ETCDIR}/broker/" ${INSTALL_FILE} etc/broker/x2gobroker-loggers.conf "${DESTDIR}${ETCDIR}/broker/" if [ -e /etc/debian_version ]; then ${INSTALL_FILE} pam/x2gobroker.Debian "${DESTDIR}/etc/pam.d/x2gobroker"; fi if [ -e /etc/redhat-release ]; then ${INSTALL_FILE} pam/x2gobroker.RHEL "${DESTDIR}/etc/pam.d/x2gobroker"; fi if [ -e /etc/os-release ] && cat /etc/os-release | grep "suse" 1>/dev/null || [ -d /usr/share/doc/packages/brp-check-suse ]; then ${INSTALL_FILE} pam/x2gobroker.SUSE "${DESTDIR}/etc/pam.d/x2gobroker"; fi # x2gobroker-agent mkdir -p "${DESTDIR}${X2GO_LIBDIR}" "${DESTDIR}${SBINDIR}" \ "${DESTDIR}${MANDIR}/man8" ${INSTALL_FILE} man/man8/x2gobroker-pubkeyauthorizer.8* \ "${DESTDIR}${MANDIR}/man8" $(GZIP) "${DESTDIR}${MANDIR}/man8/"*.8 ${INSTALL_PROGRAM} lib/x2gobroker-agent* "${DESTDIR}${X2GO_LIBDIR}/" ${INSTALL_PROGRAM} sbin/x2gobroker-pubkeyauthorizer "${DESTDIR}${SBINDIR}/" # x2gobroker-authservice mkdir -p "${DESTDIR}${SBINDIR}" "${DESTDIR}/etc/logrotate.d" \ "${DESTDIR}${ETCDIR}/broker" "${DESTDIR}/etc/default" \ "${DESTDIR}${MANDIR}/man8" "$(DESTDIR)$(LIBDIR)/tmpfiles.d/" ${INSTALL_FILE} defaults/x2gobroker-authservice.default \ "${DESTDIR}/etc/default/x2gobroker-authservice" ${INSTALL_FILE} man/man8/x2gobroker-authservice.8* \ "${DESTDIR}${MANDIR}/man8" $(GZIP) "${DESTDIR}${MANDIR}/man8/"*.8 ${INSTALL_PROGRAM} sbin/x2gobroker-authservice "${DESTDIR}${SBINDIR}/" ${INSTALL_FILE} logrotate/x2gobroker-authservice \ "${DESTDIR}/etc/logrotate.d/" ${INSTALL_FILE} etc/broker/x2gobroker-authservice-logger.conf \ "${DESTDIR}${ETCDIR}/broker/" ${INSTALL_FILE} tmpfiles.d/x2gobroker-authservice.conf \ "${DESTDIR}${LIBDIR}/tmpfiles.d/" # x2gobroker-loadchecker mkdir -p "${DESTDIR}${SBINDIR}" "${DESTDIR}/etc/logrotate.d" \ "${DESTDIR}${ETCDIR}/broker" "${DESTDIR}/etc/default" \ "${DESTDIR}${MANDIR}/man8" "$(DESTDIR)$(LIBDIR)/tmpfiles.d/" ${INSTALL_FILE} defaults/x2gobroker-loadchecker.default \ "${DESTDIR}/etc/default/x2gobroker-loadchecker" ${INSTALL_FILE} man/man8/x2gobroker-loadchecker.8* \ "${DESTDIR}${MANDIR}/man8" $(GZIP) "${DESTDIR}${MANDIR}/man8/"*.8 ${INSTALL_PROGRAM} sbin/x2gobroker-loadchecker "${DESTDIR}${SBINDIR}/" ${INSTALL_FILE} logrotate/x2gobroker-loadchecker \ "${DESTDIR}/etc/logrotate.d/" ${INSTALL_FILE} etc/broker/x2gobroker-loadchecker-logger.conf \ "${DESTDIR}${ETCDIR}/broker/" ${INSTALL_FILE} tmpfiles.d/x2gobroker-loadchecker.conf \ "${DESTDIR}${LIBDIR}/tmpfiles.d/" # x2gobroker-daemon mkdir -p "${DESTDIR}/etc/logrotate.d/" "${DESTDIR}/etc/default" \ "$(DESTDIR)$(LIBDIR)/tmpfiles.d/" ${INSTALL_FILE} defaults/x2gobroker-daemon.default \ "${DESTDIR}/etc/default/x2gobroker-daemon" ${INSTALL_FILE} logrotate/x2gobroker-daemon \ "${DESTDIR}/etc/logrotate.d/" mkdir -p "${DESTDIR}${BINDIR}" "${DESTDIR}${SBINDIR}" \ "${DESTDIR}${MANDIR}/man1" "${DESTDIR}${MANDIR}/man8" ${INSTALL_FILE} man/man1/x2gobroker-daemon.1* \ "${DESTDIR}${MANDIR}/man1" $(GZIP) "${DESTDIR}${MANDIR}/man1/"*.1 ${INSTALL_FILE} man/man8/x2gobroker-daemon-debug.8* \ "${DESTDIR}${MANDIR}/man8" $(GZIP) "${DESTDIR}${MANDIR}/man8/"*.8 ${INSTALL_SYMLINK} x2gobroker \ "${DESTDIR}${BINDIR}/x2gobroker-daemon" ${INSTALL_PROGRAM} sbin/x2gobroker-daemon-debug \ "${DESTDIR}${SBINDIR}/" ${INSTALL_FILE} tmpfiles.d/x2gobroker-daemon.conf \ "${DESTDIR}${LIBDIR}/tmpfiles.d/" # x2gobroker-ssh mkdir -p "${DESTDIR}${BINDIR}" "${DESTDIR}${SBINDIR}" \ "${DESTDIR}${ETCDIR}/../sudoers.d" \ "${DESTDIR}${MANDIR}/man1" ${INSTALL_FILE} man/man1/x2gobroker-ssh.1* \ "${DESTDIR}${MANDIR}/man1" ${INSTALL_PROGRAM} bin/x2gobroker-ssh \ "${DESTDIR}${BINDIR}/" ${INSTALL_FILE} x2gobroker-ssh.sudo \ "${DESTDIR}${ETCDIR}/../sudoers.d/" mv "${DESTDIR}${ETCDIR}/../sudoers.d/x2gobroker-ssh.sudo" "${DESTDIR}${ETCDIR}/../sudoers.d/x2gobroker-ssh" # x2gobroker-wsgi mkdir -p "${DESTDIR}${ETCDIR}" "${DESTDIR}/etc/logrotate.d" ${INSTALL_FILE} etc/x2gobroker-wsgi.apache.{conf,vhost} \ "${DESTDIR}${ETCDIR}/" ${INSTALL_FILE} logrotate/x2gobroker-wsgi "${DESTDIR}/etc/logrotate.d/" mkdir -p "${DESTDIR}${LIBDIR}/x2gobroker/wsgi" ${INSTALL_SYMLINK} "${BINDIR}/x2gobroker" \ "${DESTDIR}${LIBDIR}/x2gobroker/wsgi/x2gobroker-wsgi" # x2gobroker mkdir -p "${DESTDIR}${BINDIR}" "${DESTDIR}${SBINDIR}" \ "${DESTDIR}${MANDIR}/man1" mkdir -p "${DESTDIR}${BINDIR}" "${DESTDIR}${SBINDIR}" \ "${DESTDIR}${MANDIR}/man8" ${INSTALL_FILE} man/man1/x2gobroker{,-testauth}.1* \ "${DESTDIR}${MANDIR}/man1" $(GZIP) "${DESTDIR}${MANDIR}/man1/"*.1 ${INSTALL_FILE} man/man8/x2gobroker{-keygen,-testagent}.8* \ "${DESTDIR}${MANDIR}/man8" $(GZIP) "${DESTDIR}${MANDIR}/man8/"*.8 ${INSTALL_PROGRAM} bin/x2gobroker bin/x2gobroker-testauth \ "${DESTDIR}${BINDIR}/" ${INSTALL_PROGRAM} sbin/x2gobroker-keygen sbin/x2gobroker-testagent \ "${DESTDIR}${SBINDIR}/" x2gobroker-0.0.4.1/Makefile.docupload0000644000000000000000000000131113457267612014246 0ustar #!/usr/bin/make -f # Makefile.docupload file - for python-x2gobroker # Copyright 2010-2019 by Mike Gabriel , GPLv3+ applies to this file VERSION=`head -n1 debian/changelog | sed 's,.*(\(.*\)).*,\1,' | cut -d"-" -f1` DOC_HOST=code.x2go.org DOC_PATH=/srv/sites/x2go.org/code/doc/x2gobroker DOC_USER=x2go-admin doc: docbuild docupload clean: -${MAKE} -C docs/ clean apidoc: sphinx-apidoc -f -e -o docs/source/ x2gobroker x2gobroker/tests/ docbuild: clean ${MAKE} -C docs/ html SPHINXOPTS="-a -E" docupdate: ${MAKE} -C docs/ html docupload: clean docbuild ssh -l${DOC_USER} ${DOC_HOST} rm -Rfv ${DOC_PATH}/* scp -r docs/build/html/* ${DOC_USER}@${DOC_HOST}:${DOC_PATH}/ x2gobroker-0.0.4.1/man/man1/x2gobroker.10000644000000000000000000001327713457267612014430 0ustar '\" -*- coding: utf-8 -*- '\" vim:fenc=utf-8 .if \n(.g .ds T< \\FC .if \n(.g .ds T> \\F[\n[.fam]] .de URL \\$2 \(la\\$1\(ra\\$3 .. .if \n(.g .mso www.tmac .TH x2gobroker 1 "Apr 2019" "Version 0.0.4.x" "X2Go Session Broker" .SH NAME x2gobroker{,-daemon,-ssh} \- Session Broker for X2Go .SH SYNOPSIS 'nh .fi .ad l \fBx2gobroker\fR \kx .if (\nx>(\n(.l/2)) .nr x (\n(.l/5) 'in \n(.iu+\nxu [ \fIoptions\fR ] 'in \n(.iu-\nxu .ad b .PP .ad l \fBx2gobroker-daemon\fR \kx .if (\nx>(\n(.l/2)) .nr x (\n(.l/5) 'in \n(.iu+\nxu [ \fIoptions\fR ] 'in \n(.iu-\nxu .ad b .PP .ad l \fBx2gobroker-ssh\fR \kx .if (\nx>(\n(.l/2)) .nr x (\n(.l/5) 'in \n(.iu+\nxu [ \fIoptions\fR ] 'in \n(.iu-\nxu .ad b 'hy .SH DESCRIPTION \fBx2gobroker\fR (resp. \fBx2gobroker-daemon\fR) is a Python Tornado based implementation of the X2Go Session Broker API. \fBx2gobroker-ssh\fR is the same application but designed for usage via SSH (as a command line tool). .PP The HTTP(S) implementation of \fBx2gobroker\fR is normally executed either through the host's init system or via the WSGI module in your httpd server. The SSH implementation is executed by X2Go clients through SSH. .PP See the included README and TODO for further information on \fBx2gobroker\fR. .SH GENERAL OPTIONS \fBx2gobroker{,-daemon,-ssh}\fR accepts the following common options: .TP \*(T<\fB\-M, \-\-mode {HTTP|SSH}\fR\*(T> Set X2Go Session Broker into HTTP or SSH mode. If this option is omitted, then SSH is the default mode. If \fBx2gobroker-daemon\fR is used as executable name, then the default mode is HTTP(S). .TP \*(T<\fB\-C, \-\-config FILENAME\fR\*(T> Specify an alternative configuration file name, default is: \fI/etc/x2go/x2gobroker.conf\fR. .TP \*(T<\fB\-d, \-\-debug\fR\*(T> Enable debugging code. This also makes http\'s POST method available as GET method, which then allows it to test the broker API through a normal web browser. .TP \*(T<\fB\-h, \-\-help\fR\*(T> Display a help with all available command line options and exit. .SH SSH MODE COMMAND LINE OPTIONS \fBx2gobroker-ssh\fR, i.e., when run from the command line or via SSH, accepts/requires these additional options: .TP \*(T<\fB\-\-task {listsessions, selectsession, setpass, testcon}\fR\*(T> Specify the either of the known broker tasks (listsessions, selectsession, setpass, testcon). This parameter is compulsory, the command execution will fail if it is omitted. .TP \*(T<\fB\-\-user USERNAME\fR\*(T> If \fBx2gobroker\fR is run by the `magic' user (the x2gobroker account by default), then the x2gobroker is allowed to change its user context and do queries on behalf of the user specified with this parameter. .TP \*(T<\fB\-\-auth-cookie, \-\-auth-id AUTHENTICATION_COOKIE\fR\*(T> It is possible to enforce a stronger authentication via an additional pre\-shared authentication cookie. This authentication cookie must be stored in a client-side file so that X2Go Client can access it and then pass it on to the X2Go Session Broker (via the X2Go Client option \-\-auth-id=) while authenticating against the broker. The server-side cookie hash can be set in \fI/etc/x2go/x2gobroker.conf\fR (option: my-cookie). You have to set the option require-cookie-auth to true to enable the additional cookie comparison in the X2Go Session Broker. .TP \*(T<\fB\-\-profile-id, \-\-sid SESSION_PROFILE_ID\fR\*(T> For the \fIselectsession\fR task the session profile ID has to be given as an additional parameter. .TP \*(T<\fB\-\-backend BACKEND_NAME\fR\*(T> Query another than the default broker backend. .SH HTTP(S) DAEMON OPTIONS \fBx2gobroker-daemon\fR in standalone HTTP(S) daemon mode accepts these additional options: .TP \*(T<\fB\-b, \-\-bind ADDRESS:PORT\fR\*(T> The [address:]port that the Tornado http-engine will bind to (default: 127.0.0.1:8080). .TP \*(T<\fB\-D, \-\-daemonize\fR\*(T> Fork this application to background and detach from the running terminal. .TP \*(T<\fB\-P, \-\-pidfile\fR\*(T> Custom PID file location when daemonizing (default: \fI/x2gobroker/x2gobroker-daemon.pid\fR). .TP \*(T<\fB\-L, \-\-logdir\fR\*(T> Directory where stdout/stderr will be redirected after having daemonized (default: \fI/var/log/x2gobroker/\fR). .TP \*(T<\fB\-D, \-\-drop\-privileges\fR\*(T> If started as root, drop privileges to uid X2GO_DAEMON_USER and gid X2GO_DAEMON_GROUP (as configured in \fI/etc/x2go/broker/defaults.conf\fR on systemd systems or \fI/etc/defaults/python-x2gobroker\fR on SystemV systems). .SH SECURITY NOTICE / DISCLAIMER Users are advised to not misinterpret X2Go Session Broker's capabilities as a security feature. Even when using X2Go Session Broker, it is still possible for users to locally configure an X2Go Client with any settings they want, and use that to connect. So if you're trying to keep users from running a certain application on the host, using X2Go Session Broker to "lock" the configuration is the *wrong* way. The users will still be able to run that application by creating their own, local configuration file and using that. .PP To keep users from running an application on the server, you have to use \fIfilesystem permissions\fR on the X2Go Server. In the simplest case, this means setting chmod 750 or 550 on the particular application on the host, and making sure the users in question are not the owner and also not a member of the group specified for the application. .SH "FILES" /etc/x2go/x2gobroker.conf, /etc/x2go/broker/* (configuration files) .PP /etc/default/python-x2gobroker, /etc/default/x2gobroker-daemon (environment for X2Go Session Broker when run as a standalone daemon via SystemV or upstart) .PP /var/log/x2gobroker/* (log files of X2Go Session Broker) .SH "SEE ALSO" \fB/usr/share/doc/x2gobroker\fR .SH AUTHOR This manual has been written for the X2Go project by Mike Gabriel . x2gobroker-0.0.4.1/man/man1/x2gobroker-testauth.10000644000000000000000000000424713457267612016264 0ustar '\" -*- coding: utf-8 -*- '\" vim:fenc=utf-8 .if \n(.g .ds T< \\FC .if \n(.g .ds T> \\F[\n[.fam]] .de URL \\$2 \(la\\$1\(ra\\$3 .. .if \n(.g .mso www.tmac .TH x2gobroker-testauth 1 "Apr 2019" "Version 0.0.4.x" "X2Go Session Broker" .SH NAME x2gobroker-testauth \- Session Broker for X2Go (Authentication Test Utility) .SH SYNOPSIS 'nh .fi .ad l \fBx2gobroker-testauth\fR \kx .if (\nx>(\n(.l/2)) .nr x (\n(.l/5) 'in \n(.iu+\nxu [ \fIoptions\fR ] 'in \n(.iu-\nxu .ad b 'hy .SH DESCRIPTION \fBx2gobroker-testauth\fR is an authentication test utility for X2Go Session Broker. .PP WARNING: please know that the credentials you use for testing the broker authentication will get revealed in your shell's history file (e.g. \fI~/.bash_history\fR). .SH COMMON OPTIONS \fBx2gobroker-testauth\fR displays some help on command line options: .TP \*(T<\fB\-h, \-\-help\fR\*(T> Display a help with all available command line options and exit. .SH REQUIRED OPTIONS At least the username is required. The password option can be omitted, you will be queried interactively, if no password is specified on the command line. .TP \*(T<\fB\-u, \-\-user, \-\-username\fR\*(T> Username part of the credentials you want to test. .TP \*(T<\fB\-p, \-\-password\fR\*(T> Password part of the credentials (be sure you want to reveal those on the command line before doing it!!!) .SH MISC OPTIONS \fBx2gobroker-testauth\fR also accepts the following miscellaneous options: .TP \*(T<\fB\-d, \-\-debug\fR\*(T> Enable debugging code. .TP \*(T<\fB\-C, \-\-config FILENAME\fR\*(T> Specify an alternative configuration file name, default is: \fI/etc/x2go/x2gobroker.conf\fR. .TP \*(T<\fB\-b, \-\-backend BACKEND\fR\*(T> Specify another configured/enabled backend configuration to test authentication against, default is: the \fIinifile\fR backend. .SH "FILES" /etc/x2go/x2gobroker.conf, /etc/x2go/broker/* (configuration files) .PP /etc/default/x2gobroker-daemon (environment for X2Go Session Broker when run as standalone daemon) .PP /var/log/x2gobroker/* (log files of X2Go Session Broker) .SH "SEE ALSO" \fB/usr/share/doc/x2gobroker\fR .SH AUTHOR This manual has been written for the X2Go project by Mike Gabriel . x2gobroker-0.0.4.1/man/man8/x2gobroker-authservice.80000644000000000000000000000462313457267612016761 0ustar '\" -*- coding: utf-8 -*- '\" vim:fenc=utf-8 .if \n(.g .ds T< \\FC .if \n(.g .ds T> \\F[\n[.fam]] .de URL \\$2 \(la\\$1\(ra\\$3 .. .if \n(.g .mso www.tmac .TH x2gobroker-authservice 8 "Apr 2019" "Version 0.0.4.x" "X2Go Session Broker" .SH NAME x2gobroker-authservice \- PAM authentication service for X2Go Session Broker .SH SYNOPSIS 'nh .fi .ad l \fBx2gobroker-authservice\fR \kx .if (\nx>(\n(.l/2)) .nr x (\n(.l/5) 'in \n(.iu+\nxu [ \fIoptions\fR ] 'in \n(.iu-\nxu .ad b 'hy .SH DESCRIPTION \fBx2gobroker-authservice\fR is a PAM authentication service for X2Go Session Broker. Whereas the X2Go Session Broker runs as a non-privileged user (standalone daemon mode) or as the also non-privileged httpd server's system user (WSGI mode), an authentication against PAM requires root privileges in most cases (esp. for pam_unix.so). .PP Thus, the PAM authentication has been moved into a separate service. The communication between X2Go Session Broker and PAM Authentication Service is handled through a unix domain socket file (\fI/x2gobroker/x2gobroker-authservice.socket\fR). .PP This command is normally started through the host's init system. .SH COMMON OPTIONS \fBx2gobroker-authservice\fR accepts the following common options: .TP \*(T<\fB\-h, \-\-help\fR\*(T> Display a help with all available command line options and exit. .TP \*(T<\fB\-D, \-\-daemonize\fR\*(T> Fork this application to background and detach from the running terminal. .TP \*(T<\fB\-P, \-\-pidfile\fR\*(T> Custom PID file location when daemonizing (default: \fI/x2gobroker/x2gobroker-authservice.pid\fR). .TP \*(T<\fB\-L, \-\-logdir\fR\*(T> Directory where stdout/stderr will be redirected after having daemonized (default: \fI/var/log/x2gobroker/\fR). .TP \*(T<\fB\-s , \-\-socket \fR\*(T> File name of the unix domain socket file used for communication between broker and authentication service. .TP \*(T<\fB\-o , \-\-owner \fR\*(T> User ownership of the \fI\fR file. .TP \*(T<\fB\-g , \-\-group \fR\*(T> Group ownership of the \fI\fR file. .TP \*(T<\fB\-p , \-\-permissions \fR\*(T> Set these file permissions for the \fI\fR file. Use numerical permissions (e.g. 0640). .SH "FILES" /x2gobroker/x2gobroker-authservice.socket .SH AUTHOR This manual has been written for the X2Go project by Mike Gabriel . x2gobroker-0.0.4.1/man/man8/x2gobroker-daemon-debug.80000644000000000000000000000222113457267612016756 0ustar '\" -*- coding: utf-8 -*- '\" vim:fenc=utf-8 .if \n(.g .ds T< \\FC .if \n(.g .ds T> \\F[\n[.fam]] .de URL \\$2 \(la\\$1\(ra\\$3 .. .if \n(.g .mso www.tmac .TH x2gobroker-daemon-debug 8 "Apr 2019" "Version 0.0.4.x" "X2Go Session Broker" .SH NAME x2gobroker-daemon-debug \- Debug X2Go Session Broker's Standalone Daemon .SH SYNOPSIS 'nh .fi .ad l \fBx2gobroker-daemon-debug\fR \kx .if (\nx>(\n(.l/2)) .nr x (\n(.l/5) 'in \n(.iu+\nxu [ \fIoptions\fR ] 'in \n(.iu-\nxu .ad b 'hy .SH DESCRIPTION \fBx2gobroker-daemon-debug\fR is a helper tool for the site admin that launches the standalone http(s) daemon of X2Go Session Broker in debug and interactive mode. Debug mode is activated automatically, all logging is re-directed to STDERR (instead of logging into the broker's log files). .PP The \fBx2gobroker-daemon-debug\fR scripts accepts all options of \fBx2gobroker\fR except the \fI--mode\fR option (which is enforced by \fBx2gobroker-daemon-debug\fR by default). .PP \fBx2gobroker-daemon-debug\fR requires root privileges. .PP .SH "SEE ALSO" x2gobroker(1) .SH AUTHOR This manual has been written for the X2Go project by Mike Gabriel . x2gobroker-0.0.4.1/man/man8/x2gobroker-keygen.80000644000000000000000000000243213457267612015715 0ustar '\" -*- coding: utf-8 -*- '\" vim:fenc=utf-8 .if \n(.g .ds T< \\FC .if \n(.g .ds T> \\F[\n[.fam]] .de URL \\$2 \(la\\$1\(ra\\$3 .. .if \n(.g .mso www.tmac .TH x2gobroker-keygen 8 "Apr 2019" "Version 0.0.4.x" "X2Go Session Broker" .SH NAME x2gobroker-keygen \- Generate SSH keys for X2Go Session Broker .SH SYNOPSIS 'nh .fi .ad l \fBx2gobroker-keygen\fR \kx .if (\nx>(\n(.l/2)) .nr x (\n(.l/5) 'in \n(.iu+\nxu [ \fIoptions\fR ] 'in \n(.iu-\nxu .ad b 'hy .SH DESCRIPTION \fBx2gobroker-keygen\fR generates a SSH pub/priv key pair and makes it usable through X2Go Session Broker. .PP This command has to be execute once per broker installation and requires root privileges. .SH COMMON OPTIONS \fBx2gobroker-keygen\fR accepts the following common options: .TP \*(T<\fB\-h, \-\-help\fR\*(T> Display a help with all available command line options and exit. .TP \*(T<\fB\-t {RSA,DSA}, \-\-key_type {RSA,DSA}\fR\*(T> SSH pub/priv key type (allowed values: RSA, DSA). .TP \*(T<\fB\-f, \-\-force\fR\*(T> Re-generate SSH pub/priv key pair and enforce overwriting of existing key pair files. WARNING: you will loose previously create key files when this option is used. .SH "FILES" ~x2gobroker/.ssh/* .PP .SH AUTHOR This manual has been written for the X2Go project by Mike Gabriel . x2gobroker-0.0.4.1/man/man8/x2gobroker-loadchecker.80000644000000000000000000000730013457267612016676 0ustar '\" -*- coding: utf-8 -*- '\" vim:fenc=utf-8 .if \n(.g .ds T< \\FC .if \n(.g .ds T> \\F[\n[.fam]] .de URL \\$2 \(la\\$1\(ra\\$3 .. .if \n(.g .mso www.tmac .TH x2gobroker-loadchecker 8 "Apr 2019" "Version 0.0.4.x" "X2Go Session Broker" .SH NAME x2gobroker-loadchecker \- Load checker service for X2Go Session Broker .SH SYNOPSIS 'nh .fi .ad l \fBx2gobroker-loadchecker\fR \kx .if (\nx>(\n(.l/2)) .nr x (\n(.l/5) 'in \n(.iu+\nxu [ \fIoptions\fR ] 'in \n(.iu-\nxu .ad b 'hy .SH DESCRIPTION \fBx2gobroker-loadchecker\fR is a service that collects system load metrics from broker-associated X2Go Servers. .PP When load-balancing shall be used, the simplest way to detect "server load" is counting the numbers of running and suspended sessions. No extra daemon nor service is required for this. The server with the least amount of sessions will be selected for starting the next X2Go session. .PP However, simply counting running and suspended sessions per X2Go Server as a representative for the server load can be highly inaccurate. A better approach is checking each X2Go Server's load in regular intervals by the \fBx2gobroker-loadchecker\fR daemon (running on the broker host) and querying the \fBx2gobroker-loadchecker\fR daemon before selecting the best server on session startup requests. .PP The \fBx2gobroker-loadchecker\fR collects server metrics of all associated X2Go Servers and keeps the latest load factors in RAM. Once the broker needs load factors of a certain session profile (i.e., of all servers configured in that session profile), the \fBx2gobroker-loadchecker\fR delivers that info immediately. The remembered load factors may not be 100% up-to-date (default: collected within the last five minutes), but the response time of the load checker is much faster than a query to all possible X2Go Servers would be. .PP The load factor calculation uses this algorithm: .PP ( memAvail[MByte]/1000 ) * numCPUs * typeCPUs[MHz] load-factor = -------------------------------------------------- loadavg*100 * numSessions .PP The higher the load-factor, the more likely that a server will be chosen for the next to be allocated X2Go session. .PP The communication between X2Go Session Broker and the Load Checker Service is handled through a unix domain socket file (\fI/x2gobroker/x2gobroker-loadchecker.socket\fR). .PP This command is normally started through the host's init system. .SH COMMON OPTIONS \fBx2gobroker-loadchecker\fR accepts the following common options: .TP \*(T<\fB\-h, \-\-help\fR\*(T> Display a help with all available command line options and exit. .TP \*(T<\fB\-D, \-\-daemonize\fR\*(T> Fork this application to background and detach from the running terminal. .TP \*(T<\fB\-P, \-\-pidfile\fR\*(T> Custom PID file location when daemonizing (default: \fI/x2gobroker/x2gobroker-loadchecker.pid\fR). .TP \*(T<\fB\-L, \-\-logdir\fR\*(T> Directory where stdout/stderr will be redirected after having daemonized (default: \fI/var/log/x2gobroker/\fR). .TP \*(T<\fB\-s , \-\-socket \fR\*(T> File name of the unix domain socket file used for communication between broker and load checker service. .TP \*(T<\fB\-o , \-\-owner \fR\*(T> User ownership of the \fI\fR file. .TP \*(T<\fB\-g , \-\-group \fR\*(T> Group ownership of the \fI\fR file. .TP \*(T<\fB\-p , \-\-permissions \fR\*(T> Set these file permissions for the \fI\fR file. Use numerical permissions (e.g. 0640). .SH "FILES" /x2gobroker/x2gobroker-loadchecker.socket .SH AUTHOR This manual has been written for the X2Go project by Mike Gabriel . x2gobroker-0.0.4.1/man/man8/x2gobroker-pubkeyauthorizer.80000644000000000000000000000270013457267612020045 0ustar '\" -*- coding: utf-8 -*- '\" vim:fenc=utf-8 .if \n(.g .ds T< \\FC .if \n(.g .ds T> \\F[\n[.fam]] .de URL \\$2 \(la\\$1\(ra\\$3 .. .if \n(.g .mso www.tmac .TH x2gobroker-pubkeyauthorizer 8 "Apr 2019" "Version 0.0.4.x" "X2Go Session Broker" .SH NAME x2gobroker-pubkeyauthorizer \- Retrieve public SSH keys from an X2Go Session Broker .SH SYNOPSIS 'nh .fi .ad l \fBx2gobroker-pubkeyauthorizer\fR \kx .if (\nx>(\n(.l/2)) .nr x (\n(.l/5) 'in \n(.iu+\nxu [ \fIoptions\fR ] 'in \n(.iu-\nxu .ad b 'hy .SH DESCRIPTION \fBx2gobroker-pubkeyauthorizer\fR retrieves the X2Go Session Broker's public SSH key(s) and adds it to \fI~x2gobroker/.ssh/authorized_keys\fR. .PP This command has to be executed once on each X2Go Server that is to become a member of a muli-node X2Go server farm. The execution of this command requires root-privileges. .PP .SH COMMON OPTIONS \fBx2gobroker-pubkeyauthorizer\fR accepts the following common options: .TP \*(T<\fB\-h, \-\-help\fR\*(T> Display a help with all available command line options and exit. .TP \*(T<\fB\-b , \-\-broker-url \fR\*(T> The URL of the X2Go Session Broker that we want to retrieve public keys from. The common pattern for this URL is \fIhttp(s)://:/pubkeys/\fR, but may differ depending on your X2Go Session Broker setup. .SH "FILES" ~x2gobroker/.ssh/authorized_keys .SH AUTHOR This manual has been written for the X2Go project by Mike Gabriel . x2gobroker-0.0.4.1/man/man8/x2gobroker-testagent.80000644000000000000000000000744513457267612016442 0ustar '\" -*- coding: utf-8 -*- '\" vim:fenc=utf-8 .if \n(.g .ds T< \\FC .if \n(.g .ds T> \\F[\n[.fam]] .de URL \\$2 \(la\\$1\(ra\\$3 .. .if \n(.g .mso www.tmac .TH x2gobroker-testagent 8 "Apr 2019" "Version 0.0.4.x" "X2Go Session Broker" .SH NAME x2gobroker-testagent \- Session Broker for X2Go (Agent Test Utility) .SH SYNOPSIS 'nh .fi .ad l \fBx2gobroker-testagent\fR \kx .if (\nx>(\n(.l/2)) .nr x (\n(.l/5) 'in \n(.iu+\nxu [ \fIoptions\fR ] 'in \n(.iu-\nxu .ad b 'hy .SH DESCRIPTION \fBx2gobroker-testagent\fR is a test utility for local/remote X2Go Session Broker Agents. The \fBx2gobroker-testagent\fR has to run as super-user root. .PP The \fBx2gobroker-testagent\fR tool is normally run on the machine that acts as the X2Go Session Broker. .PP The \fBx2gobroker-testagent\fR tries to connect to a local or remote X2Go Session Broker Agent. X2Go Broker Agents are co-installed on X2Go Server hosts and support the broker in controlling that X2Go Server. .PP The \fBx2gobroker-testagent\fR executes a test task on the broker agent. The broker agent tries to switch to the given user's context and runs the given task on behalf of that user. .SH COMMON OPTIONS \fBx2gobroker-testagent\fR provides help on command line options: .TP \*(T<\fB\-h, \-\-help\fR\*(T> Display a help with all available command line options and exit. .SH REQUIRED OPTIONS You can either list the broker agent's tasks that are available for testing: .TP \*(T<\fB\-\-list\-tasks\fR\*(T> Render a list of available broker agent tasks. This list shows what can be tested. The capabilities of the remote agent and the broker server's test code are compared and displayed. .PP Or run a given task on behalf of an X2Go user on a local or remote broker agent: .TP \*(T<\fB\-u, \-\-user, \-\-username\fR\*(T> Instruct the broker agent to switch to this user's context when running the given task on the local/remote host. .TP \*(T<\fB\-t TASK, \-\-task TASK\fR\*(T> The task that shall be test-executed on the remote broker agent. .SH REMOTE BROKER OPTIONS The default mode for calling the X2Go Session Broker Agent is "LOCAL". Alternatively, remote broker agents can be called via mode "SSH". For this, you additionally need to specify the below options: .TP \*(T<\fB\-H HOSTNAME, \-\-host HOSTNAME\fR\*(T> Hostname to connect to via SSH for running remote broker agent test commands. .TP \*(T<\fB\-p PORT, \-\-port PORT\fR\*(T> The TCP/IP port that the remote system's SSH daemon listens on (default: 22). .SH SUPPLIMENTARY OPTIONS For some tasks, you have to provide additional parameters. .TP \*(T<\fB\-A, \-\-add-to-known-hosts\fR\*(T> When connecting to a remote broker agent via SSH, add the broker agent machine's SSH host key to the list of known hosts (normally \fI/var/lib/x2gobroker/.ssh/known_hosts\fR). This has to be done once per broker agent machine, that the X2Go Session Broker is supposed to connect to. .TP \*(T<\fB\-\-session-id SESSION_ID\fR\*(T> When testing the 'suspendsession' or the 'terminatesession' task, you have to additionally give a session ID to test those tasks on. .TP \*(T<\fB\-\-pubkey PUBKEY_AS_STRING\fR\*(T> Use your own provided SSH public key when testing the 'addauthkey' and the 'delauthkey' tasks. .SH MISC OPTIONS \fBx2gobroker-testagent\fR also accepts the following miscellaneous options: .TP \*(T<\fB\-d, \-\-debug\fR\*(T> Enable debugging code. .TP \*(T<\fB\-C, \-\-config FILENAME\fR\*(T> Specify an alternative configuration file name, default is: \fI/etc/x2go/x2gobroker.conf\fR. .SH "FILES" /etc/x2go/x2gobroker.conf, /etc/x2go/broker/* (configuration files) .PP /etc/x2go/broker/defaults.conf (environment for X2Go Session Broker) .PP /var/log/x2gobroker/* (log files of X2Go Session Broker) .SH "SEE ALSO" \fB/usr/share/doc/x2gobroker\fR .SH AUTHOR This manual has been written for the X2Go project by Mike Gabriel . x2gobroker-0.0.4.1/NEWS0000644000000000000000000000234713457267612011346 0ustar NEWS on X2Go Session Broker =========================== x2gobroker (0.0.3.0) X2Go Session Broker can now also be used directly via SSH connection. Over the SSH wire the x2gobroker executable simply gets called as a command line tool. x2goclient --broker-url=ssh://@/usr/bin/x2gobroker The default behaviour now actually is the SSH command line mode. To enable HTTP(S) standalone daemon mode, you now have to explicit add --mode HTTP as command line option. For those who package X2Go Session Broker for some GNU or BSD distribution, please make sure that you add this new compulsory command line option to the start section in your init script / service file. Furthermore, the x2gobroker executable now installs to /usr/bin (as opposed to /usr/sbin like it used to be in earlier versions). -- Mike Gabriel , Sun, 18 Jan 2013 20:20:39 +0200 x2gobroker (0.0.0.1) X2Go Session Broker is a Web project that provides session brokerage for X2Go via HTTP(S). The framework was originally designed in Perl and has been rewritten in Python using the tornado.py framework in 2012/2013. -- Mike Gabriel , Xxx, 29 Nov 2012 18:00:00 +0100 x2gobroker-0.0.4.1/pam/x2gobroker.Debian0000644000000000000000000000005613457267612014607 0ustar @include common-auth @include common-password x2gobroker-0.0.4.1/pam/x2gobroker.RHEL0000644000000000000000000000006613457267612014160 0ustar auth include system-auth password include system-auth x2gobroker-0.0.4.1/pam/x2gobroker.SUSE0000644000000000000000000000005613457267612014204 0ustar @include common-auth @include common-password x2gobroker-0.0.4.1/README0000644000000000000000000000560113457267612011523 0ustar README for X2Go Session Broker ============================== X2Go Session Broker is an X2Go component that provides session brokerage for X2Go via HTTP(S) and/or SSH. Dependencies: * This version of X2Go Session Broker requires X2Go Server 4.0.1.16 and above. * This version of X2Go Session Broker works with X2Go Client (>= 4.0.0.0) and Python X2Go (>= 0.5.0.0) as a client. * Python modules: - python-setuptools (build-dependency) - python-argparse - python-netaddr - python-pampy - python-paramiko - python-setproctitle - python-tornado - python-wsgilog Extra Unittest Dependencies: * Python modules: - python-nose, - python-paste, Available features: * easily extendible backend / submodule concept * ACL based session profile provisioning * X2Go load balancing Available backends: * ZEROCONF: the ,,zeroconf'' broker backend is for testing X2Go client applications without much hassle against the X2Go Session Broker * INIFILE (default): the ,,inifile'' broker backend scales well with small and medium X2Go deployments. It is configurable through a single file (/etc/x2go/broker/x2gobroker-sessionprofiles.conf). It provides flexible provisioning of session profiles based on user, group and host ACLs. Available authentication mechanisms: * NONE: Always authenticate every user never mind if password is correct or not. * PAM: Authenticate against the local PAM setup. The service file /etc/pam.d/x2gobroker can be used to configure the authentication. * HTTPS_GET: Perform a http GET request against a web server. If the web server returned code 200, authentication is considered a success. Available nameservice backends: * LIBNSS: Obtain uses from the broker host's libnss configuration. Configuration files: /etc/default/{x2gobroker-daemon,python-x2gobroker,x2gobroker-authservice} /etc/x2go/x2gobroker.conf /etc/x2go/broker/** Environment variables (and their defaults): # run X2Go Session Broker in debug mode, this will make the broker # available through http GET method calls (otherwise: POST method # only) and you will be able to test the broker through your web # browser X2GOBROKER_DEBUG=false # default X2Go Session Broker backend (available: zeroconf, inifile) X2GOBROKER_DEFAULT_BACKEND=inifile # path to the X2Go Session Broker's configuration file X2GOBROKER_CONFIG=/etc/x2go/x2gobroker.conf # path to the X2Go Session Broker's session profiles file (when using the inifile backend) X2GOBROKER_SESSIONPROFILES=/etc/x2go/broker/x2gobroker-sessionprofiles.conf # path to the X2Go Session Broker's agent command X2GOBROKER_AGENT_CMD=/usr/lib/x2go/x2gobroker-agent # The unix socket file for communication between the broker and the authentication service. X2GOBROKER_AUTHSERVICE_SOCKET=/run/x2gobroker/x2gobroker-authservice.socket light+love, Mike Gabriel, 20140912 x2gobroker-0.0.4.1/rpm/x2gobroker-authservice.init0000755000000000000000000000574413457267612016745 0ustar #!/bin/sh # # x2gobroker-authservice - Starts/stop the "x2gobroker-authservice" daemon # # chkconfig: 2345 99 1 # description: X2Go Session Broker's (PAM) Authentication Service ### BEGIN INIT INFO # Provides: x2gobroker-authservice # Required-Start: $local_fs # Required-Stop: $local_fs # Default-Start: 2345 # Default-Stop: 016 # Short-Description: X2Go Session Broker PAM Authentication Service # Description: PAM authentication service for X2Go Session Broker ### END INIT INFO # Source function library. . /etc/rc.d/init.d/functions set -e AUTHSERVICE=/usr/sbin/x2gobroker-authservice test -d /run && RUNDIR=/run || RUNDIR=/var/run PIDFILE_AUTHSERVICE=$RUNDIR/x2gobroker/x2gobroker-authservice.pid DEFAULTCONFIG_COMMON=/etc/default/python-x2gobroker DEFAULTCONFIG_AUTHSERVICE=/etc/default/x2gobroker-authservice test -x "$AUTHSERVICE" || exit 0 START_AUTHSERVICE=false X2GOBROKER_DEBUG=0 X2GOBROKER_DAEMON_USER='x2gobroker' X2GOBROKER_DAEMON_GROUP='x2gobroker' X2GOBROKER_AUTHSERVICE_SOCKET="$RUNDIR/x2gobroker/x2gobroker-authservice.socket" test -f $DEFAULTCONFIG_COMMON && . $DEFAULTCONFIG_COMMON test -f $DEFAULTCONFIG_AUTHSERVICE && . $DEFAULTCONFIG_AUTHSERVICE if ! getent passwd $X2GOBROKER_DAEMON_USER 1>/dev/null 2>/dev/null; then X2GOBROKER_DAEMON_USER=nobody fi if ! getent group $X2GOBROKER_DAEMON_GROUP 1>/dev/null 2>/dev/null; then X2GOBROKER_DAEMON_GROUP=nobody fi export X2GOBROKER_DEBUG export X2GOBROKER_DAEMON_USER export X2GOBROKER_DAEMON_GROUP export X2GOBROKER_AUTHSERVICE_SOCKET exec=$AUTHSERVICE prog=$(basename $AUTHSERVICE) config=$DEFAULTCONFIG_AUTHSERVICE OPTS="-D -P $PIDFILE_AUTHSERVICE -s $X2GOBROKER_AUTHSERVICE_SOCKET -o root -g $X2GOBROKER_DAEMON_GROUP -p 0660" lockfile=/var/lock/subsys/$prog is_true() { case "${1:-}" in [Yy]es|[Yy]|1|[Tt]|[Tt]rue) return 0;; *) return 1; esac } start() { [ -x $exec ] || exit 5 if is_true $START_AUTHSERVICE; then # Make sure these are created by default so that nobody else can echo -n $"Starting $prog: " set +e daemon $exec $OPTS retval=$? set -e echo [ $retval -eq 0 ] && touch $lockfile fi } stop() { echo -n $"Stopping $prog: " set +e killproc -p $PIDFILE_AUTHSERVICE $exec retval=$? set -e echo rm -f $lockfile return $retval } restart() { stop start } reload() { restart } force_reload() { restart } rh_status() { # run checks to determine if the service is running or use generic status status -p $PIDFILE_AUTHSERVICE $exec } rh_status_q() { rh_status 1>/dev/null 2>&1 } case "$1" in start) set +e rh_status_q && exit 0 set -e $1 ;; stop) set +e rh_status_q || exit 0 set -e $1 ;; restart) $1 ;; reload) set +e rh_status_q || exit 7 set -e $1 ;; force-reload) force_reload ;; status) set +e rh_status set -e ;; condrestart|try-restart) set +e rh_status_q || exit 0 set -e restart ;; *) echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload}" exit 2 ;; esac exit $? x2gobroker-0.0.4.1/rpm/x2gobroker-daemon.init0000755000000000000000000000656413457267612015667 0ustar #!/bin/sh # # x2gobroker-daemon - Starts/stop the "x2gobroker-daemon" daemon # # chkconfig: 2345 99 1 # description: X2Go Session Broker Standalone Daemon ### BEGIN INIT INFO # Provides: x2gobroker-daemon # Required-Start: $local_fs # Required-Stop: $local_fs # Default-Start: 2345 # Default-Stop: 016 # Short-Description: X2Go Session Broker Standalone Daemon # Description: X2Go Session Broker comes with its own HTTP daemon ### END INIT INFO # Source function library. . /etc/rc.d/init.d/functions set -e DAEMON=/usr/bin/x2gobroker-daemon test -d /run && RUNDIR=/run || RUNDIR=/var/run PIDFILE_BROKER=$RUNDIR/x2gobroker/x2gobroker-daemon.pid DEFAULTCONFIG_COMMON=/etc/default/python-x2gobroker DEFAULTCONFIG_DAEMON=/etc/default/x2gobroker-daemon test -x "$DAEMON" || exit 0 START_BROKER=false DAEMON_BIND_ADDRESS=127.0.0.1:8080 X2GOBROKER_DEBUG=0 X2GOBROKER_DAEMON_USER='x2gobroker' X2GOBROKER_DAEMON_GROUP='x2gobroker' X2GOBROKER_DEFAULT_BACKEND="inifile" X2GOBROKER_CONFIG="/etc/x2go/x2gobroker.conf" X2GOBROKER_SESSIONPROFILES="/etc/x2go/broker/x2gobroker-sessionprofiles.conf" X2GOBROKER_AGENT_CMD="/usr/lib/x2go/x2gobroker-agent" X2GOBROKER_AUTHSERVICE_SOCKET="$RUNDIR/x2gobroker/x2gobroker-authservice.socket" X2GOBROKER_SSL_CERTFILE= X2GOBROKER_SSL_KEYFILE= test -f $DEFAULTCONFIG_COMMON && . $DEFAULTCONFIG_COMMON test -f $DEFAULTCONFIG_DAEMON && . $DEFAULTCONFIG_DAEMON if ! getent passwd $X2GOBROKER_DAEMON_USER 1>/dev/null 2>/dev/null; then X2GOBROKER_DAEMON_USER=nobody fi if ! getent group $X2GOBROKER_DAEMON_GROUP 1>/dev/null 2>/dev/null; then X2GOBROKER_DAEMON_GROUP=nogroup fi # mend user ID variables when --chuid $X2GOBROKER_DAEMON_USER is used with start-stop-daemon export LOGNAME=$X2GOBROKER_DAEMON_USER export USER=$X2GOBROKER_DAEMON_USER export USERNAME=$X2GOBROKER_DAEMON_USER export X2GOBROKER_DEBUG export X2GOBROKER_DAEMON_USER export X2GOBROKER_DAEMON_GROUP export X2GOBROKER_CONFIG export X2GOBROKER_DEFAULT_BACKEND export X2GOBROKER_SESSIONPROFILES export X2GOBROKER_AGENT_CMD export X2GOBROKER_AUTHSERVICE_SOCKET export X2GOBROKER_SSL_CERTFILE export X2GOBROKER_SSL_KEYFILE exec=$DAEMON prog=$(basename $DAEMON) config=$DEFAULTCONFIG_DAEMON OPTS="-D -P \"$PIDFILE_BROKER\" -b \"$DAEMON_BIND_ADDRESS\"" lockfile=/var/lock/subsys/x2gobroker-daemon start() { [ -x $exec ] || exit 5 if is_true $START_BROKER; then echo -n $"Starting $prog: " set +e daemon --user $X2GOBROKER_DAEMON_USER $exec $OPTS 2>/dev/null retval=$? set -e echo [ $retval -eq 0 ] && touch $lockfile fi } stop() { echo -n $"Stopping $prog: " set +e killproc -p $PIDFILE_BROKER $exec retval=$? set -e echo rm -f $lockfile return $retval } restart() { stop start } reload() { restart } force_reload() { restart } rh_status() { # run checks to determine if the service is running or use generic status status -p $PIDFILE_BROKER $exec } rh_status_q() { rh_status >/dev/null 2>&1 } case "$1" in start) set +e rh_status_q && exit 0 set -e $1 ;; stop) set +e rh_status_q || exit 0 set -e $1 ;; restart) $1 ;; reload) set +e rh_status_q || exit 7 set -e $1 ;; force-reload) force_reload ;; status) set +e rh_status set -e ;; condrestart|try-restart) set +e rh_status_q || exit 0 set -e restart ;; *) echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload}" exit 2 esac exit $? x2gobroker-0.0.4.1/rpm/x2gobroker-loadchecker.init0000755000000000000000000000575713457267612016673 0ustar #!/bin/sh # # x2gobroker-loadchecker - Starts/stop the "x2gobroker-loadchecker" daemon # # chkconfig: 2345 99 1 # description: X2Go Session Broker's Load Checker Service ### BEGIN INIT INFO # Provides: x2gobroker-loadchecker # Required-Start: $local_fs # Required-Stop: $local_fs # Default-Start: 2345 # Default-Stop: 016 # Short-Description: X2Go Session Broker PAM Authentication Service # Description: PAM authentication service for X2Go Session Broker ### END INIT INFO # Source function library. . /etc/rc.d/init.d/functions set -e LOADCHECKER=/usr/sbin/x2gobroker-loadchecker test -d /run && RUNDIR=/run || RUNDIR=/var/run PIDFILE_LOADCHECKER=$RUNDIR/x2gobroker/x2gobroker-loadchecker.pid DEFAULTCONFIG_COMMON=/etc/default/python-x2gobroker DEFAULTCONFIG_LOADCHECKER=/etc/default/x2gobroker-loadchecker test -x "$LOADCHECKER" || exit 0 START_LOADCHECKER=false X2GOBROKER_DEBUG=0 X2GOBROKER_DAEMON_USER='x2gobroker' X2GOBROKER_DAEMON_GROUP='x2gobroker' X2GOBROKER_LOADCHECKER_SOCKET="$RUNDIR/x2gobroker/x2gobroker-loadchecker.socket" test -f $DEFAULTCONFIG_COMMON && . $DEFAULTCONFIG_COMMON test -f $DEFAULTCONFIG_LOADCHECKER && . $DEFAULTCONFIG_LOADCHECKER if ! getent passwd $X2GOBROKER_DAEMON_USER 1>/dev/null 2>/dev/null; then X2GOBROKER_DAEMON_USER=nobody fi if ! getent group $X2GOBROKER_DAEMON_GROUP 1>/dev/null 2>/dev/null; then X2GOBROKER_DAEMON_GROUP=nobody fi export X2GOBROKER_DEBUG export X2GOBROKER_DAEMON_USER export X2GOBROKER_DAEMON_GROUP export X2GOBROKER_LOADCHECKER_SOCKET exec=$LOADCHECKER prog=$(basename $LOADCHECKER) config=$DEFAULTCONFIG_LOADCHECKER OPTS="-D -P $PIDFILE_LOADCHECKER -s $X2GOBROKER_LOADCHECKER_SOCKET -o $X2GOBROKER_DAEMON_USER -g $X2GOBROKER_DAEMON_GROUP -p 0660" lockfile=/var/lock/subsys/$prog is_true() { case "${1:-}" in [Yy]es|[Yy]|1|[Tt]|[Tt]rue) return 0;; *) return 1; esac } start() { [ -x $exec ] || exit 5 if is_true $START_LOADCHECKER; then # Make sure these are created by default so that nobody else can echo -n $"Starting $prog: " set +e daemon $exec $OPTS retval=$? set -e echo [ $retval -eq 0 ] && touch $lockfile fi } stop() { echo -n $"Stopping $prog: " set +e killproc -p $PIDFILE_LOADCHECKER $exec retval=$? set -e echo rm -f $lockfile return $retval } restart() { stop start } reload() { restart } force_reload() { restart } rh_status() { # run checks to determine if the service is running or use generic status status -p $PIDFILE_LOADCHECKER $exec } rh_status_q() { rh_status 1>/dev/null 2>&1 } case "$1" in start) set +e rh_status_q && exit 0 set -e $1 ;; stop) set +e rh_status_q || exit 0 set -e $1 ;; restart) $1 ;; reload) set +e rh_status_q || exit 7 set -e $1 ;; force-reload) force_reload ;; status) set +e rh_status set -e ;; condrestart|try-restart) set +e rh_status_q || exit 0 set -e restart ;; *) echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload}" exit 2 ;; esac exit $? x2gobroker-0.0.4.1/rpm/x2gobroker-rpmlintrc0000644000000000000000000000117613457267612015463 0ustar setBadness("permissions-unauthorized-file", 0); setBadness("permissions-file-setuid-bit", 0); setBadness("permissions-directory-setuid-bit", 0); addFilter("non-standard-group"); addFilter("non-standard-uid /usr/bin/x2gobroker-ssh x2gobroker"); addFilter("non-standard-uid /var/log/x2gobroker x2gobroker"); addFilter("non-standard-uid /var/lib/x2gobroker x2gobroker"); addFilter("non-standard-gid /var/log/x2gobroker x2gobroker"); addFilter("non-standard-gid /var/lib/x2gobroker x2gobroker"); addFilter("non-standard-gid /usr/bin/x2gobroker-ssh x2gobroker-users"); addFilter("non-standard-gid /usr/lib/x2go/x2gobroker-agent x2gobroker"); x2gobroker-0.0.4.1/sbin/x2gobroker-authservice0000755000000000000000000002635113457267612016135 0ustar #!/usr/bin/env python3 # -*- coding: utf-8 -*- # vim:fenc=utf-8 # This file is part of the X2Go Project - https://www.x2go.org # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. import os import sys import setproctitle import argparse import logging import asyncore import getpass import logging.config import atexit import configparser if os.path.isdir('/run'): RUNDIR = '/run' else: RUNDIR = '/var/run' try: import daemon import lockfile CAN_DAEMONIZE = True pidfile = '{run}/x2gobroker/x2gobroker-authservice.pid'.format(run=RUNDIR) daemon_logdir = '/var/log/x2gobroker/' except ImportError: CAN_DAEMONIZE = False from pwd import getpwnam from grp import getgrnam PROG_NAME = os.path.basename(sys.argv[0]) PROG_OPTIONS = sys.argv[1:] setproctitle.setproctitle("%s %s" % (PROG_NAME, " ".join(PROG_OPTIONS))) from x2gobroker import __VERSION__ from x2gobroker import __AUTHOR__ from x2gobroker.authservice import AuthService def loop(): asyncore.loop() def cleanup_on_exit(): os.remove(X2GOBROKER_AUTHSERVICE_SOCKET) try: os.remove(pidfile) except: pass # load the defaults.conf file, if present iniconfig_loaded = None iniconfig_section = '-'.join(PROG_NAME.split('-')[1:]) X2GOBROKER_DEFAULTS = "/etc/x2go/broker/defaults.conf" if os.path.isfile(X2GOBROKER_DEFAULTS) and os.access(X2GOBROKER_DEFAULTS, os.R_OK): iniconfig = configparser.RawConfigParser() iniconfig.optionxform = str iniconfig_loaded = iniconfig.read(X2GOBROKER_DEFAULTS) # normally this would go into defaults.py, however, we do not want to pull in defaults.py here as that will create # unwanted logfiles (access.log, broker.log, error.log) when x2gobroker-authservice is installed as standalone service if 'X2GOBROKER_DEBUG' in os.environ: X2GOBROKER_DEBUG = ( os.environ['X2GOBROKER_DEBUG'].lower() in ('1', 'on', 'true', 'yes', ) ) elif iniconfig_loaded and iniconfig.has_option(iniconfig_section, 'X2GOBROKER_DEBUG'): X2GOBROKER_DEBUG=iniconfig.get(iniconfig_section, 'X2GOBROKER_DEBUG') elif iniconfig_loaded and iniconfig.has_option('common', 'X2GOBROKER_DEBUG'): X2GOBROKER_DEBUG=iniconfig.get('common', 'X2GOBROKER_DEBUG') else: X2GOBROKER_DEBUG = False if 'X2GOBROKER_DAEMON_USER' in os.environ: X2GOBROKER_DAEMON_USER=os.environ['X2GOBROKER_DAEMON_USER'] elif iniconfig_loaded and iniconfig.has_option(iniconfig_section, 'X2GOBROKER_DAEMON_USER'): X2GOBROKER_DAEMON_USER=iniconfig.get(iniconfig_section, 'X2GOBROKER_DAEMON_USER') elif iniconfig_loaded and iniconfig.has_option('common', 'X2GOBROKER_DAEMON_USER'): X2GOBROKER_DAEMON_USER=iniconfig.get('common', 'X2GOBROKER_DAEMON_USER') else: X2GOBROKER_DAEMON_USER="x2gobroker" if 'X2GOBROKER_AUTHSERVICE_LOGCONFIG' in os.environ: X2GOBROKER_AUTHSERVICE_LOGCONFIG=os.environ['X2GOBROKER_AUTHSERVICE_LOGCONFIG'] elif iniconfig_loaded and iniconfig.has_option(iniconfig_section, 'X2GOBROKER_AUTHSERVICE_LOGCONFIG'): X2GOBROKER_AUTHSERVICE_LOGCONFIG=iniconfig.get(iniconfig_section, 'X2GOBROKER_AUTHSERVICE_LOGCONFIG') elif iniconfig_loaded and iniconfig.has_option('common', 'X2GOBROKER_AUTHSERVICE_LOGCONFIG'): X2GOBROKER_AUTHSERVICE_LOGCONFIG=iniconfig.get('common', 'X2GOBROKER_AUTHSERVICE_LOGCONFIG') else: X2GOBROKER_AUTHSERVICE_LOGCONFIG="/etc/x2go/broker/x2gobroker-authservice-logger.conf" if 'X2GOBROKER_AUTHSERVICE_SOCKET' in os.environ: X2GOBROKER_AUTHSERVICE_SOCKET=os.environ['X2GOBROKER_AUTHSERVICE_SOCKET'] elif iniconfig_loaded and iniconfig.has_option(iniconfig_section, 'X2GOBROKER_AUTHSERVICE_SOCKET'): X2GOBROKER_AUTHSERVICE_SOCKET=iniconfig.get(iniconfig_section, 'X2GOBROKER_AUTHSERVICE_SOCKET') elif iniconfig_loaded and iniconfig.has_option('common', 'X2GOBROKER_AUTHSERVICE_SOCKET'): X2GOBROKER_AUTHSERVICE_SOCKET=iniconfig.get('common', 'X2GOBROKER_AUTHSERVICE_SOCKET') else: X2GOBROKER_AUTHSERVICE_SOCKET="{run}/x2gobroker/x2gobroker-authservice.socket".format(run=RUNDIR) if __name__ == '__main__': common_options = [ {'args':['-s','--socket-file'], 'default': X2GOBROKER_AUTHSERVICE_SOCKET, 'metavar': 'AUTHSOCKET', 'help': 'socket file for AuthService communication', }, {'args':['-o','--owner'], 'default': 'root', 'help': 'owner of the AuthService socket file', }, {'args':['-g','--group'], 'default': 'root', 'help': 'group ownership of the AuthService socket file', }, {'args':['-p','--permissions'], 'default': '0o660', 'help': 'set these file permissions for the AuthService socket file', }, {'args':['-d','--debug'], 'default': False, 'action': 'store_true', 'help': 'enable debugging code', }, {'args':['-i','--debug-interactively'], 'default': False, 'action': 'store_true', 'help': 'force output of log message to the stderr (rather than to the log files)', }, ] if CAN_DAEMONIZE: common_options.extend([ {'args':['-D', '--daemonize'], 'default': False, 'action': 'store_true', 'help': 'Detach the X2Go Broker process from the current terminal and fork to background', }, {'args':['-P', '--pidfile'], 'default': pidfile, 'help': 'Alternative file path for the daemon\'s PID file', }, {'args':['-L', '--logdir'], 'default': daemon_logdir, 'help': 'Directory where log files for the process\'s stdout and stderr can be created', }, ]) p = argparse.ArgumentParser(description='X2Go Session Broker (PAM Authentication Service)',\ formatter_class=argparse.RawDescriptionHelpFormatter, \ add_help=True, argument_default=None) p_common = p.add_argument_group('common parameters') for (p_group, opts) in ( (p_common, common_options), ): for opt in opts: args = opt['args'] del opt['args'] p_group.add_argument(*args, **opt) cmdline_args = p.parse_args() # standalone daemon mode (x2gobroker-authservice as daemon) or interactive mode (called from the cmdline)? if getpass.getuser() in (X2GOBROKER_DAEMON_USER, 'root') and not cmdline_args.debug_interactively: # we run in standalone daemon mode, so let's use the system configuration for logging logging.config.fileConfig(X2GOBROKER_AUTHSERVICE_LOGCONFIG) # create authservice logger logger_authservice = logging.getLogger('authservice') else: logger_root = logging.getLogger() stderr_handler = logging.StreamHandler(sys.stderr) stderr_handler.setFormatter(logging.Formatter(fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='')) # all loggers stream to stderr... logger_root.addHandler(stderr_handler) logger_authservice = logging.getLogger('authservice') logger_authservice.addHandler(stderr_handler) logger_authservice.propagate = 0 if cmdline_args.debug_interactively: cmdline_args.debug = True # raise log level to DEBUG if requested... if cmdline_args.debug or X2GOBROKER_DEBUG: logger_authservice.setLevel(logging.DEBUG) logger_authservice.info('X2Go Session Broker ({version}), written by {author}'.format(version=__VERSION__, author=__AUTHOR__)) logger_authservice.info('Setting up the PAM authentication service\'s environment...') logger_authservice.info(' X2GOBROKER_DEBUG: {value}'.format(value=X2GOBROKER_DEBUG)) logger_authservice.info(' X2GOBROKER_AUTHSERVICE_SOCKET: {value}'.format(value=X2GOBROKER_AUTHSERVICE_SOCKET)) # check effective UID the AuthService runs as and complain appropriately... if os.geteuid() != 0: logger_authservice.warn('X2Go Session Broker\'s PAM authentication service should run with root privileges to guarantee proper access to all PAM modules.') if CAN_DAEMONIZE and cmdline_args.daemonize: # create directory for the PID file pidfile = os.path.expanduser(cmdline_args.pidfile) if not os.path.isdir(os.path.dirname(pidfile)): try: os.makedirs(os.path.dirname(pidfile)) except: pass if not os.access(os.path.dirname(pidfile), os.W_OK) or (os.path.exists(pidfile) and not os.access(pidfile, os.W_OK)): print("") p.print_usage() print("Insufficent privileges. Cannot create PID file {pidfile} path".format(pidfile=pidfile)) print("") sys.exit(-3) # create directory for logging daemon_logdir = os.path.expanduser(cmdline_args.logdir) if not os.path.isdir(daemon_logdir): try: os.makedirs(daemon_logdir) except: pass if not os.access(daemon_logdir, os.W_OK): print("") p.print_usage() print("Insufficent privileges. Cannot create directory for stdout/stderr log files: {logdir}".format(logdir=daemon_logdir)) print("") sys.exit(-3) else: if not daemon_logdir.endswith('/'): daemon_logdir += '/' socket_file = cmdline_args.socket_file if os.path.exists(socket_file): os.remove(socket_file) if not os.path.exists(os.path.dirname(socket_file)): os.makedirs(os.path.dirname(socket_file)) runtimedir_permissions = int(cmdline_args.permissions, 8) if runtimedir_permissions & 0o400: runtimedir_permissions = runtimedir_permissions | 0o100 if runtimedir_permissions & 0o040: runtimedir_permissions = runtimedir_permissions | 0o010 if runtimedir_permissions & 0o004: runtimedir_permissions = runtimedir_permissions | 0o001 try: os.chown(os.path.dirname(socket_file), getpwnam(cmdline_args.owner).pw_uid, getpwnam(cmdline_args.group).pw_gid) os.chmod(os.path.dirname(socket_file), runtimedir_permissions) except OSError: pass AuthService(socket_file, owner=cmdline_args.owner, group_owner=cmdline_args.group, permissions=cmdline_args.permissions, logger=logger_authservice) atexit.register(cleanup_on_exit) try: if CAN_DAEMONIZE and cmdline_args.daemonize: keep_fds = [int(fd) for fd in os.listdir('/proc/self/fd') if fd not in (0,1,2) ] daemon_stdout = open(daemon_logdir+'x2gobroker-authservice.stdout', 'w+') daemon_stderr = open(daemon_logdir+'x2gobroker-authservice.stderr', 'w+') with daemon.DaemonContext(stdout=daemon_stdout, stderr=daemon_stderr, files_preserve=keep_fds, umask=0o027, pidfile=lockfile.FileLock(pidfile), detach_process=True): open(pidfile, 'w+').write(str(os.getpid())+"\n") loop() else: loop() except KeyboardInterrupt: pass x2gobroker-0.0.4.1/sbin/x2gobroker-daemon-debug0000755000000000000000000000316213457267612016135 0ustar #!/bin/bash # -*- coding: utf-8 -*- # vim:fenc=utf-8 # This file is part of the X2Go Project - https://www.x2go.org # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. if [[ $EUID -ne 0 ]]; then echo "$(basename $0): You must be the root super-user to launch this command..." 2>&1 exit 1 fi if [ -e /etc/default/python-x2gobroker ]; then . /etc/default/python-x2gobroker fi if [ -e /etc/default/x2gobroker-daemon ]; then . /etc/default/x2gobroker-daemon fi if [ -n "$DAEMON_BIND_ADDRESS" ]; then su - x2gobroker -c "X2GOBROKER_SSL_CERTFILE=\"$X2GOBROKER_SSL_CERTFILE\" X2GOBROKER_SSL_KEYFILE=\"$X2GOBROKER_SSL_KEYFILE\" DAEMON_BIND_ADDRESS=\"$DAEMON_BIND_ADDRESS\" x2gobroker-daemon -b $DAEMON_BIND_ADDRESS -i $@" else su - x2gobroker -c "X2GOBROKER_SSL_CERTFILE=\"$X2GOBROKER_SSL_CERTFILE\" X2GOBROKER_SSL_KEYFILE=\"$X2GOBROKER_SSL_KEYFILE\" x2gobroker-daemon -i $@" fi x2gobroker-0.0.4.1/sbin/x2gobroker-keygen0000755000000000000000000001626613457267612015101 0ustar #!/usr/bin/env python3 # -*- coding: utf-8 -*- # vim:fenc=utf-8 # This file is part of the X2Go Project - https://www.x2go.org # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. import os import sys import setproctitle import argparse import logging import paramiko import x2gobroker._paramiko x2gobroker._paramiko.monkey_patch_paramiko() from pwd import getpwnam from grp import getgrnam try: import x2gobroker.defaults except ImportError: sys.path.insert(0, os.path.join(os.getcwd(), '..')) import x2gobroker.defaults import x2gobroker._paramiko x2gobroker._paramiko.monkey_patch_paramiko() import x2gobroker.utils supported_key_types = ('RSA', 'DSA') PROG_NAME = os.path.basename(sys.argv[0]) PROG_OPTIONS = sys.argv[1:] setproctitle.setproctitle("%s %s" % (PROG_NAME, " ".join(PROG_OPTIONS))) from x2gobroker import __VERSION__ from x2gobroker import __AUTHOR__ from x2gobroker.loggers import logger_broker, logger_error if os.geteuid() == 0: # propagate msgs for the broker logger to the root logger (i.e. to stderr) logger_broker.propagate = 1 logger_error.propagate = 1 # raise log level to DEBUG if requested... if x2gobroker.defaults.X2GOBROKER_DEBUG: logger_broker.setLevel(logging.DEBUG) else: logger_broker.setLevel(logging.INFO) logger_broker.info('X2Go Session Broker ({version}), written by {author}'.format(version=__VERSION__, author=__AUTHOR__)) logger_broker.info('Setting up the key generator\'s environment...') logger_broker.info(' X2GOBROKER_DEBUG: {value}'.format(value=x2gobroker.defaults.X2GOBROKER_DEBUG)) logger_broker.info(' X2GOBROKER_DAEMON_USER: {value}'.format(value=x2gobroker.defaults.X2GOBROKER_DAEMON_USER)) logger_broker.info(' X2GOBROKER_DAEMON_GROUP: {value}'.format(value=x2gobroker.defaults.X2GOBROKER_DAEMON_GROUP)) # check effective UID the broker runs as and complain appropriately... if os.geteuid() != 0: logger_error.error('X2Go Session Broker\'s key generator has to run with root privileges. Exiting...') sys.exit(-1) if __name__ == '__main__': common_options = [ {'args':['-t','---key-type'], 'default': 'RSA', 'help': 'Choose a key type for the X2Go Session Broker pub/priv SSH key pair (available: RSA, DSA).', }, {'args':['-f','--force'], 'default': False, 'action': 'store_true', 'help': 'Enforce the creation of a public/private key pair. WARNING: This will overwrite earlier created keys.', }, ] p = argparse.ArgumentParser(description='X2Go Session Broker (Key Generator)',\ formatter_class=argparse.RawDescriptionHelpFormatter, \ add_help=True, argument_default=None) p_common = p.add_argument_group('common parameters') for (p_group, opts) in ( (p_common, common_options), ): for opt in opts: args = opt['args'] del opt['args'] p_group.add_argument(*args, **opt) cmdline_args = p.parse_args() if cmdline_args.key_type.upper() not in supported_key_types: logger_error.error('Unknown key type »{key_type}«. Possible key types are RSA and DSA. Exiting...'.format(key_type=cmdline_args.key_type.upper())) sys.exit(-2) broker_uid = x2gobroker.defaults.X2GOBROKER_DAEMON_USER broker_uidnumber = getpwnam(broker_uid).pw_uid broker_gid = x2gobroker.defaults.X2GOBROKER_DAEMON_GROUP broker_gidnumber = getgrnam(broker_gid).gr_gid broker_home = x2gobroker.defaults.X2GOBROKER_HOME if not os.path.exists(broker_home): logger_error.error('The home directory {home} of user {user} does not exists. Cannot continue. Exiting...'.format(home=broker_home, user=broker_uid)) sys.exit(-2) logger_broker.info('Creating pub/priv key pair for X2Go Session Broker...') if not os.path.exists('{home}/.ssh'.format(home=broker_home)): os.mkdir('{home}/.ssh'.format(home=broker_home)) os.chown('{home}/.ssh'.format(home=broker_home), broker_uidnumber, broker_gidnumber) os.chmod('{home}/.ssh'.format(home=broker_home), 0o0750) logger_broker.info(' Created {home}/.ssh'.format(home=broker_home)) # generate key pair if cmdline_args.key_type.upper() == 'RSA': key = paramiko.RSAKey.generate(2048) id_file = 'id_rsa' elif cmdline_args.key_type.upper() == 'DSA': key = paramiko.DSSKey.generate(1024) id_file = 'id_dsa' logger_broker.info(' The {key_type} key has been generated, fingerprint: {fingerprint}'.format(key_type=cmdline_args.key_type.upper(), fingerprint=x2gobroker.utils.get_key_fingerprint_with_colons(key))) if os.path.exists('{home}/.ssh/{id_file}'.format(home=broker_home, id_file=id_file)) and not cmdline_args.force: logger_broker.error(' Private key {home}/.ssh/{id_file} exists. Use --force to overwrite'.format(home=broker_home, id_file=id_file)) logger_broker.error(' the file with a newly generated key.') logger_broker.error(' Make sure that broker agents get the new public key file deployed with the') logger_broker.error(' x2gobroker-pubkeyauthorizer tool, if you enforce key re-generation.') logger_broker.error('Exiting...') sys.exit(-3) elif os.path.exists('{home}/.ssh/{id_file}'.format(home=broker_home, id_file=id_file)): logger_broker.warn(' WARNING: you requested to overwrite existing key files!!!') key.write_private_key_file('{home}/.ssh/{id_file}'.format(home=broker_home, id_file=id_file)) os.chown('{home}/.ssh/{id_file}'.format(home=broker_home, id_file=id_file), broker_uidnumber, broker_gidnumber) os.chmod('{home}/.ssh/{id_file}'.format(home=broker_home, id_file=id_file), 0o0600) logger_broker.info(' Private key written to file {key_file}'.format(key_file='{home}/.ssh/{id_file}'.format(home=broker_home, id_file=id_file))) pubkey_file = open('{home}/.ssh/{id_file}.pub'.format(home=broker_home, id_file=id_file),'w') if id_file == 'id_rsa': pubkey_file.write("ssh-rsa " +key.get_base64()) elif id_file == 'id_dsa': pubkey_file.write("ssh-dss " +key.get_base64()) pubkey_file.close() os.chown('{home}/.ssh/{id_file}.pub'.format(home=broker_home, id_file=id_file), broker_uidnumber, broker_gidnumber) os.chmod('{home}/.ssh/{id_file}.pub'.format(home=broker_home, id_file=id_file), 0o0644) logger_broker.info(' Public key written to file {key_file}'.format(key_file='{home}/.ssh/{id_file}.pub'.format(home=broker_home, id_file=id_file))) logger_broker.info('Key file generation has been successful!') x2gobroker-0.0.4.1/sbin/x2gobroker-loadchecker0000755000000000000000000003604213457267612016055 0ustar #!/usr/bin/env python3 # -*- coding: utf-8 -*- # vim:fenc=utf-8 # This file is part of the X2Go Project - https://www.x2go.org # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. import os import sys import setproctitle import argparse import logging import asyncore import socket import getpass import logging.config import atexit import configparser if os.path.isdir('/run'): RUNDIR = '/run' else: RUNDIR = '/var/run' try: import daemon import lockfile CAN_DAEMONIZE = True pidfile = '{run}/x2gobroker/x2gobroker-loadchecker.pid'.format(run=RUNDIR) daemon_logdir = '/var/log/x2gobroker/' except ImportError: CAN_DAEMONIZE = False from pwd import getpwnam from grp import getgrnam PROG_NAME = os.path.basename(sys.argv[0]) PROG_OPTIONS = sys.argv[1:] setproctitle.setproctitle("%s %s" % (PROG_NAME, " ".join(PROG_OPTIONS))) from x2gobroker import __VERSION__ from x2gobroker import __AUTHOR__ import x2gobroker.loadchecker global load_checker class LoadCheckerServiceHandler(asyncore.dispatcher_with_send): def __init__(self, sock, logger=None): self.logger = logger asyncore.dispatcher_with_send.__init__(self, sock) self._buf = '' def handle_read(self): data = self._buf + self.recv(1024).decode() if not data: self.close() return reqs, data = data.rsplit('\n', 1) self._buf = data output = "" for req in reqs.split('\n'): backend, profile_id, hostname = req.split('\r') if self.logger: self.logger.debug('LoadCheckServiceHandler.handle_read(): received load check query: backend={backend}, profile_id={profile_id}, hostname={hostname}'.format(backend=backend, profile_id=profile_id, hostname=hostname)) if hostname: load_factor = load_checker.get_server_load(backend, profile_id, hostname) if load_factor is not None: output += "{lf}".format(lf=load_factor) if self.logger: self.logger.info('LoadCheckServiceHandler.handle_read(): load check result for backend={backend}, profile_id={profile_id}, hostname={hostname}: {lf}'.format(backend=backend, profile_id=profile_id, hostname=hostname, lf=load_factor)) else: output += "LOAD-UNAVAILABLE" if self.logger: self.logger.warning('LoadCheckServiceHandler.handle_read(): load check failure for backend={backend}, profile_id={profile_id}, hostname={hostname}: LOAD-UNAVAILABLE'.format(backend=backend, profile_id=profile_id, hostname=hostname)) else: load_factors = load_checker.get_profile_load(backend, profile_id) if load_factors: for h in list(load_factors.keys()): if load_factors[h] is not None: output +="{hostname}:{loadfactor}\n".format(hostname=h, loadfactor=load_factors[h]) if self.logger: self.logger.info('LoadCheckServiceHandler.handle_read(): load check result for backend={backend}, profile_id={profile_id}, hostname={hostname}: {lf}'.format(backend=backend, profile_id=profile_id, hostname=h, lf=load_factors[h])) else: output += "{hostname}:LOAD-UNAVAILABLE\n".format(hostname=h) if self.logger: self.logger.warning('LoadCheckServiceHandler.handle_read(): load check failure for backend={backend}, profile_id={profile_id}, hostname={hostname}: LOAD-UNAVAILABLE'.format(backend=backend, profile_id=profile_id, hostname=h)) else: if self.logger: self.logger.warning('LoadCheckServiceHandler.handle_read(): load check failure for backend={backend}, profile_id={profile_id}: LOAD-UNAVAILABLE'.format(backend=backend, profile_id=profile_id)) output += "\n" self.send(output.encode()) def handle_close(self): self.close() class LoadCheckerService(asyncore.dispatcher_with_send): def __init__(self, socketfile, owner='root', group_owner='root', permissions='0o660', logger=None): self.logger = logger asyncore.dispatcher_with_send.__init__(self) self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM) self.set_reuse_addr() self.bind(socketfile) try: os.chown(socketfile, getpwnam(owner).pw_uid, getgrnam(group_owner).gr_gid) os.chmod(socketfile, int(permissions, 8)) except OSError: pass self.listen(1) def handle_accept(self): conn, _ = self.accept() LoadCheckerServiceHandler(conn, logger=self.logger) def loop(): ### the "loop" has two tasks... # 1. Do regular queries to remote X2Go Broker Agent instances to collect # load average, CPU usage and type, memory usage, etc. load_checker.start() # 2. Provide a listening UNIX domain socket file that can be used for querying # server states. asyncore.loop() def cleanup_on_exit(): os.remove(X2GOBROKER_LOADCHECKER_SOCKET) try: os.remove(pidfile) except: pass # load the defaults.conf file, if present iniconfig_loaded = None iniconfig_section = '-'.join(PROG_NAME.split('-')[1:]) X2GOBROKER_DEFAULTS = "/etc/x2go/broker/defaults.conf" if os.path.isfile(X2GOBROKER_DEFAULTS) and os.access(X2GOBROKER_DEFAULTS, os.R_OK): iniconfig = configparser.RawConfigParser() iniconfig.optionxform = str iniconfig_loaded = iniconfig.read(X2GOBROKER_DEFAULTS) # normally this would go into defaults.py, however, we do not want to pull in defaults.py here as that will create # unwanted logfiles (access.log, broker.log, error.log) when x2gobroker-loadchecker is installed as standalone service if 'X2GOBROKER_DEBUG' in os.environ: X2GOBROKER_DEBUG = ( os.environ['X2GOBROKER_DEBUG'].lower() in ('1', 'on', 'true', 'yes', ) ) elif iniconfig_loaded and iniconfig.has_option(iniconfig_section, 'X2GOBROKER_DEBUG'): X2GOBROKER_DEBUG=iniconfig.get(iniconfig_section, 'X2GOBROKER_DEBUG') elif iniconfig_loaded and iniconfig.has_option('common', 'X2GOBROKER_DEBUG'): X2GOBROKER_DEBUG=iniconfig.get('common', 'X2GOBROKER_DEBUG') else: X2GOBROKER_DEBUG = False if 'X2GOBROKER_DAEMON_USER' in os.environ: X2GOBROKER_DAEMON_USER=os.environ['X2GOBROKER_DAEMON_USER'] elif iniconfig_loaded and iniconfig.has_option(iniconfig_section, 'X2GOBROKER_DAEMON_USER'): X2GOBROKER_DAEMON_USER=iniconfig.get(iniconfig_section, 'X2GOBROKER_DAEMON_USER') elif iniconfig_loaded and iniconfig.has_option('common', 'X2GOBROKER_DAEMON_USER'): X2GOBROKER_DAEMON_USER=iniconfig.get('common', 'X2GOBROKER_DAEMON_USER') else: X2GOBROKER_DAEMON_USER="x2gobroker" if 'X2GOBROKER_LOADCHECKER_LOGCONFIG' in os.environ: X2GOBROKER_LOADCHECKER_LOGCONFIG=os.environ['X2GOBROKER_LOADCHECKER_LOGCONFIG'] elif iniconfig_loaded and iniconfig.has_option(iniconfig_section, 'X2GOBROKER_LOADCHECKER_LOGCONFIG'): X2GOBROKER_LOADCHECKER_LOGCONFIG=iniconfig.get(iniconfig_section, 'X2GOBROKER_LOADCHECKER_LOGCONFIG') elif iniconfig_loaded and iniconfig.has_option('common', 'X2GOBROKER_LOADCHECKER_LOGCONFIG'): X2GOBROKER_LOADCHECKER_LOGCONFIG=iniconfig.get('common', 'X2GOBROKER_LOADCHECKER_LOGCONFIG') else: X2GOBROKER_LOADCHECKER_LOGCONFIG="/etc/x2go/broker/x2gobroker-loadchecker-logger.conf" if 'X2GOBROKER_LOADCHECKER_SOCKET' in os.environ: X2GOBROKER_LOADCHECKER_SOCKET=os.environ['X2GOBROKER_LOADCHECKER_SOCKET'] elif iniconfig_loaded and iniconfig.has_option(iniconfig_section, 'X2GOBROKER_LOADCHECKER_SOCKET'): X2GOBROKER_LOADCHECKER_SOCKET=iniconfig.get(iniconfig_section, 'X2GOBROKER_LOADCHECKER_SOCKET') elif iniconfig_loaded and iniconfig.has_option('common', 'X2GOBROKER_LOADCHECKER_SOCKET'): X2GOBROKER_LOADCHECKER_SOCKET=iniconfig.get('common', 'X2GOBROKER_LOADCHECKER_SOCKET') else: X2GOBROKER_LOADCHECKER_SOCKET="{run}/x2gobroker/x2gobroker-loadchecker.socket".format(run=RUNDIR) if __name__ == '__main__': common_options = [ {'args':['-s','--socket-file'], 'default': X2GOBROKER_LOADCHECKER_SOCKET, 'metavar': 'LOADCHECKERSOCKET', 'help': 'socket file for LoadChecker communication', }, {'args':['-o','--owner'], 'default': 'root', 'help': 'owner of the LoadChecker socket file', }, {'args':['-g','--group'], 'default': 'root', 'help': 'group ownership of the LoadChecker socket file', }, {'args':['-p','--permissions'], 'default': '0660', 'help': 'set these file permissions for the LoadChecker socket file', }, {'args':['-d','--debug'], 'default': False, 'action': 'store_true', 'help': 'enable debugging code', }, {'args':['-i','--debug-interactively'], 'default': False, 'action': 'store_true', 'help': 'force output of log message to the stderr (rather than to the log files)', }, ] if CAN_DAEMONIZE: common_options.extend([ {'args':['-D', '--daemonize'], 'default': False, 'action': 'store_true', 'help': 'Detach the X2Go Broker process from the current terminal and fork to background', }, {'args':['-P', '--pidfile'], 'default': pidfile, 'help': 'Alternative file path for the daemon\'s PID file', }, {'args':['-L', '--logdir'], 'default': daemon_logdir, 'help': 'Directory where log files for the process\'s stdout and stderr can be created', }, ]) p = argparse.ArgumentParser(description='X2Go Session Broker (Load Checker Service)',\ formatter_class=argparse.RawDescriptionHelpFormatter, \ add_help=True, argument_default=None) p_common = p.add_argument_group('common parameters') for (p_group, opts) in ( (p_common, common_options), ): for opt in opts: args = opt['args'] del opt['args'] p_group.add_argument(*args, **opt) cmdline_args = p.parse_args() # standalone daemon mode (x2gobroker-loadchecker as daemon) or interactive mode (called from the cmdline)? if getpass.getuser() in (X2GOBROKER_DAEMON_USER, 'root') and not cmdline_args.debug_interactively: # we run in standalone daemon mode, so let's use the system configuration for logging logging.config.fileConfig(X2GOBROKER_LOADCHECKER_LOGCONFIG) # create loadchecker logger logger_loadchecker = logging.getLogger('loadchecker') else: logger_root = logging.getLogger() stderr_handler = logging.StreamHandler(sys.stderr) stderr_handler.setFormatter(logging.Formatter(fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='')) # all loggers stream to stderr... logger_root.addHandler(stderr_handler) logger_loadchecker = logging.getLogger('loadchecker') logger_loadchecker.addHandler(stderr_handler) logger_loadchecker.propagate = 0 if cmdline_args.debug_interactively: cmdline_args.debug = True # raise log level to DEBUG if requested... if cmdline_args.debug or X2GOBROKER_DEBUG: X2GOBROKER_DEBUG = True logger_loadchecker.setLevel(logging.DEBUG) logger_loadchecker.info('X2Go Session Broker ({version}), written by {author}'.format(version=__VERSION__, author=__AUTHOR__)) logger_loadchecker.info('Setting up the Load Checker service\'s environment...') logger_loadchecker.info(' X2GOBROKER_DEBUG: {value}'.format(value=X2GOBROKER_DEBUG)) logger_loadchecker.info(' X2GOBROKER_LOADCHECKER_SOCKET: {value}'.format(value=X2GOBROKER_LOADCHECKER_SOCKET)) load_checker = x2gobroker.loadchecker.LoadChecker(logger=logger_loadchecker) if CAN_DAEMONIZE and cmdline_args.daemonize: # create directory for the PID file pidfile = os.path.expanduser(cmdline_args.pidfile) if not os.path.isdir(os.path.dirname(pidfile)): try: os.makedirs(os.path.dirname(pidfile)) except: pass if not os.access(os.path.dirname(pidfile), os.W_OK) or (os.path.exists(pidfile) and not os.access(pidfile, os.W_OK)): print("") p.print_usage() print("Insufficent privileges. Cannot create PID file {pidfile} path".format(pidfile=pidfile)) print("") sys.exit(-3) # create directory for logging daemon_logdir = os.path.expanduser(cmdline_args.logdir) if not os.path.isdir(daemon_logdir): try: os.makedirs(daemon_logdir) except: pass if not os.access(daemon_logdir, os.W_OK): print("") p.print_usage() print("Insufficent privileges. Cannot create directory for stdout/stderr log files: {logdir}".format(logdir=daemon_logdir)) print("") sys.exit(-3) else: if not daemon_logdir.endswith('/'): daemon_logdir += '/' socket_file = cmdline_args.socket_file if os.path.exists(socket_file): os.remove(socket_file) if not os.path.exists(os.path.dirname(socket_file)): os.makedirs(os.path.dirname(socket_file)) runtimedir_permissions = int(cmdline_args.permissions, 8) if runtimedir_permissions & 0o400: runtimedir_permissions = runtimedir_permissions | 0o100 if runtimedir_permissions & 0o040: runtimedir_permissions = runtimedir_permissions | 0o010 if runtimedir_permissions & 0o004: runtimedir_permissions = runtimedir_permissions | 0o001 try: os.chown(os.path.dirname(socket_file), getpwnam(cmdline_args.owner).pw_uid, getpwnam(cmdline_args.group).pw_gid) os.chmod(os.path.dirname(socket_file), runtimedir_permissions) except OSError: pass LoadCheckerService(socket_file, owner=cmdline_args.owner, group_owner=cmdline_args.group, permissions=cmdline_args.permissions, logger=logger_loadchecker) atexit.register(cleanup_on_exit) try: if CAN_DAEMONIZE and cmdline_args.daemonize: keep_fds = [int(fd) for fd in os.listdir('/proc/self/fd') if fd not in (0,1,2) ] daemon_stdout = open(daemon_logdir+'x2gobroker-loadchecker.stdout', 'w+') daemon_stderr = open(daemon_logdir+'x2gobroker-loadchecker.stderr', 'w+') with daemon.DaemonContext(stdout=daemon_stdout, stderr=daemon_stderr, files_preserve=keep_fds, umask=0o027, pidfile=lockfile.FileLock(pidfile), detach_process=True): open(pidfile, 'w+').write(str(os.getpid())+"\n") loop() else: loop() except KeyboardInterrupt: pass x2gobroker-0.0.4.1/sbin/x2gobroker-pubkeyauthorizer0000755000000000000000000002460213457267612017224 0ustar #!/usr/bin/env python3 # -*- coding: utf-8 -*- # vim:fenc=utf-8 # This file is part of the X2Go Project - https://www.x2go.org # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. import os import sys import setproctitle import argparse import logging import urllib.request import getpass import logging import logging.config import re from pwd import getpwnam from grp import getgrnam __VERSION__ = '0.0.4.1' __AUTHOR__ = 'Mike Gabriel (X2Go Project) ' PROG_NAME = os.path.basename(sys.argv[0]) PROG_OPTIONS = sys.argv[1:] setproctitle.setproctitle("%s %s" % (PROG_NAME, " ".join(PROG_OPTIONS))) ### The following is a code duplication of x2gobroker.loggers and x2gobroker.defaults. ### Normally, we would avoid that. However, this is to make this script independent from ### the python-x2gobroker package (and its manifold python module dependencies). if 'X2GOBROKER_DAEMON_USER' in os.environ: X2GOBROKER_DAEMON_USER=os.environ['X2GOBROKER_DAEMON_USER'] else: X2GOBROKER_DAEMON_USER="x2gobroker" if 'X2GOBROKER_DAEMON_GROUP' in os.environ: X2GOBROKER_DAEMON_GROUP=os.environ['X2GOBROKER_DAEMON_GROUP'] else: X2GOBROKER_DAEMON_GROUP="x2gobroker" if 'X2GOBROKER_DEBUG' in os.environ: X2GOBROKER_DEBUG = ( os.environ['X2GOBROKER_DEBUG'].lower() in ('1', 'on', 'true', 'yes', ) ) else: X2GOBROKER_DEBUG = False # the home directory of the user that the daemon/cgi runs as X2GOBROKER_HOME = os.path.normpath(os.path.expanduser('~{broker_uid}'.format(broker_uid=X2GOBROKER_DAEMON_USER))) logger_root = logging.getLogger() stderr_handler = logging.StreamHandler(sys.stderr) stderr_handler.setFormatter(logging.Formatter(fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='')) # all loggers stream to stderr... logger_root.addHandler(stderr_handler) logger_broker = logging.getLogger('broker') logger_broker.addHandler(stderr_handler) logger_broker.propagate = 0 logger_error = logging.getLogger('error') logger_error.addHandler(stderr_handler) logger_error.propagate = 0 # raise log level to DEBUG if requested... if X2GOBROKER_DEBUG: logger_broker.setLevel(logging.DEBUG) else: logger_broker.setLevel(logging.INFO) logger_broker.info('X2Go Session Broker ({version}), written by {author}'.format(version=__VERSION__, author=__AUTHOR__)) logger_broker.info('Setting up the »PubKey Authorizer«\'s environment...') logger_broker.info(' X2GOBROKER_DEBUG: {value}'.format(value=X2GOBROKER_DEBUG)) logger_broker.info(' X2GOBROKER_DAEMON_USER: {value}'.format(value=X2GOBROKER_DAEMON_USER)) logger_broker.info(' X2GOBROKER_DAEMON_GROUP: {value}'.format(value=X2GOBROKER_DAEMON_GROUP)) # check effective UID the broker runs as and complain appropriately... if os.geteuid() != 0: logger_error.error('X2Go Session Broker\'s »PubKey Authorizer« has to run with root privileges. Exiting...') sys.exit(-1) if __name__ == '__main__': common_options = [ {'args':['-t','--broker-url'], 'default': None, 'help': 'The URL of the X2Go Session Broker that we want to retrieve public keys from. The common pattern for this URL is http(s)://:/pubkeys/.', }, ] p = argparse.ArgumentParser(description='X2Go Session Broker (PubKey Installer)',\ formatter_class=argparse.RawDescriptionHelpFormatter, \ add_help=True, argument_default=None) p_common = p.add_argument_group('common parameters') for (p_group, opts) in ( (p_common, common_options), ): for opt in opts: args = opt['args'] del opt['args'] p_group.add_argument(*args, **opt) print () cmdline_args = p.parse_args() if cmdline_args.broker_url is None: logger_error.error('Cannot proceed without having an URL specified. Use --broker-url as cmdline parameter.') logger_error.error('Exiting...') sys.exit(-2) broker_uid = X2GOBROKER_DAEMON_USER broker_uidnumber = getpwnam(broker_uid).pw_uid broker_gid = X2GOBROKER_DAEMON_GROUP broker_gidnumber = getgrnam(broker_gid).gr_gid broker_home = X2GOBROKER_HOME if not os.path.exists(broker_home): logger_error.error('The home directory {home} of user {user} does not exists.') logger_error.error('Cannot continue. Exiting...'.format(home=broker_home, user=broker_uid)) sys.exit(-2) logger_broker.info('Authorizing access to this X2Go server for X2Go Session Broker') logger_broker.info('at URL {url}'.format(url=cmdline_args.broker_url)) if not os.path.exists('{home}/.ssh'.format(home=broker_home)): os.mkdir('{home}/.ssh'.format(home=broker_home)) os.chown('{home}/.ssh'.format(home=broker_home), broker_uidnumber, broker_gidnumber) os.chmod('{home}/.ssh'.format(home=broker_home), 0o0750) logger_broker.info(' Created {home}/.ssh'.format(home=broker_home)) tmpfile_name, httpmsg = urllib.request.urlretrieve(cmdline_args.broker_url) tmpfile = open(tmpfile_name, 'rb') new_pubkeys_raw = [ k for k in tmpfile.read().decode().split('\n') if k ] i = 0 new_pubkeys = [] for new_pubkey in new_pubkeys_raw: if not new_pubkey: # fully ignore empty lines continue if re.match(r'^#.*', new_pubkey): # fully ignore commented out lines continue # check key integrity! is_key = False if re.match(r'.*ssh-dss AAAAB3NzaC1kc3MA.*', new_pubkey): is_key = True elif re.match(r'.*ssh-rsa AAAAB3NzaC1yc2EA.*', new_pubkey): is_key = True if not is_key: logger_broker.error('The broker returned something that does not look like SSH RSA/DSA keys.') logger_broker.error('Check the URL {url}'.format(url=cmdline_args.broker_url)) logger_broker.error('manually from a webbrowser.') sys.exit(-1) i += 1 new_pubkeys.append(new_pubkey) if i == 1: logger_broker.info(' Found {n} public key at URL {url}'.format(n=len(new_pubkeys), url=cmdline_args.broker_url)) elif i > 1: logger_broker.info(' Found {n} public keys at URL {url}'.format(n=len(new_pubkeys), url=cmdline_args.broker_url)) else: logger_broker.info(' No public keys found at URL {url}'.format(url=cmdline_args.broker_url)) sys.exit(0) tmpfile.close() append_newline = "" try: read_authorized_keys = open('{home}/.ssh/authorized_keys'.format(home=broker_home), 'rb') _content = read_authorized_keys.read() if _content and _content[-1] != 10: append_newline = '\n' already_authorized_keys = _content.decode().split('\n') read_authorized_keys.close() except IOError: already_authorized_keys = [] already_authorized_keys = [ k for k in already_authorized_keys if k ] append_authorized_keys = open('{home}/.ssh/authorized_keys'.format(home=broker_home), 'ab') if append_newline: logger_broker.warning(' The file {authorized_keys} does not end with a newline character. Adding it.'.format(authorized_keys='{home}/.ssh/authorized_keys'.format(home=broker_home))) append_authorized_keys.write(append_newline) to_be_removed = [] for new_pubkey in new_pubkeys: # legacy support for authorized_keys files containing SSH keys without options... # if the remote server provides an already present pubkey with options, replace the # non-option key in the authorized_keys file... keytype, pubkey, owner = new_pubkey.rsplit(" ", 2) keyopts = "" if " " in keytype: keyopts, keytype = keytype.rsplit(" ", 1) for authorized_key in already_authorized_keys: if authorized_key.endswith(" ".join([keytype, pubkey, owner])) and not authorized_key.startswith(keyopts): to_be_removed.append(authorized_key) if new_pubkey not in already_authorized_keys: append_authorized_keys.write('{k}\n'.format(k=new_pubkey).encode()) logger_broker.info(' Adding new public key (counter={i}) to {authorized_keys}.'.format(i=i, authorized_keys='{home}/.ssh/authorized_keys'.format(home=broker_home))) else: logger_broker.warning(' Skipping new public key (counter={i}), already in {authorized_keys}.'.format(i=i, authorized_keys='{home}/.ssh/authorized_keys'.format(home=broker_home))) append_authorized_keys.close() if to_be_removed: cleanup_authorized_keys = open('{home}/.ssh/authorized_keys'.format(home=broker_home), 'r+') lines = cleanup_authorized_keys.readlines() cleanup_authorized_keys.seek(0) i = 0 for line in lines: i += 1 line = line.rstrip("\n") if line not in to_be_removed: cleanup_authorized_keys.write(line+"\n") else: logger_broker.info(' Dropping public key (counter={i}) with deprecated or no options from {authorized_keys}.'.format(i=i, authorized_keys='{home}/.ssh/authorized_keys'.format(home=broker_home))) cleanup_authorized_keys.truncate() cleanup_authorized_keys.close() if i == 0: logger_broker.error('No public SSH key was processed.') logger_broker.error('Check the URL {url}'.format(url=cmdline_args.broker_url)) logger_broker.error('manually from a webbrowser.') else: # set proper file permissions os.chown('{home}/.ssh/authorized_keys'.format(home=broker_home), broker_uidnumber, broker_gidnumber) os.chmod('{home}/.ssh/authorized_keys'.format(home=broker_home), 0o0644) logger_broker.info('Completed successfully: X2Go Session Broker\'s PubKey Authorizer.'.format(url=cmdline_args.broker_url)) x2gobroker-0.0.4.1/sbin/x2gobroker-testagent0000755000000000000000000002470213457267612015607 0ustar #!/usr/bin/env python3 # -*- coding: utf-8 -*- # vim:fenc=utf-8 # This file is part of the X2Go Project - https://www.x2go.org # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. import os import sys import setproctitle import argparse import logging from paramiko import AutoAddPolicy # perform an authentication against the authentication mechanism configured for WSGI try: import x2gobroker.defaults except ImportError: sys.path.insert(0, os.path.join(os.getcwd(), '..')) import x2gobroker.defaults import x2gobroker.loggers import x2gobroker.agent from x2gobroker.utils import drop_privileges PROG_NAME = os.path.basename(sys.argv[0]) PROG_OPTIONS = sys.argv[1:] try: _password_index = PROG_OPTIONS.index('--password')+1 PROG_OPTIONS[_password_index] = "XXXXXXXX" except ValueError: # ignore if --password option is not specified pass setproctitle.setproctitle("%s %s" % (PROG_NAME, " ".join(PROG_OPTIONS))) if __name__ == "__main__": agent_options = [ {'args':['--list-tasks'], 'default': False, 'action': 'store_true', 'help': 'List available broker agent tasks', }, {'args':['-u','--username', '--user'], 'default': None, 'metavar': 'USERNAME', 'help': 'When testing the broker agent, test on behalf of this user (default: none)', }, {'args':['-t','--task'], 'default': 'ping', 'metavar': 'AGENT_TASK', 'help': 'Perform this task on the (remote) broker agent', }, {'args':['-H','--host'], 'default': 'LOCAL', 'metavar': 'HOSTNAME', 'help': 'Test X2Go Session Broker Agent on this (remote) host (default: LOCAL)', }, {'args':['-p','--port'], 'default': 22, 'metavar': 'PORT', 'help': 'For remote agent calls (via SSH) use this port as SSH port (default: 22)', }, ] supplementary_options = [ {'args':['-A', '--add-to-known-hosts'], 'default': False, 'action': 'store_true', 'help': 'For SSH connections to a remote broker agent: permanently add the remote system\'s host key to the list of known hosts', }, {'args':['--session-id'], 'default': None, 'help': 'when testing the \'suspendsession\' or the \'terminatesession\' task, you have to additionally give a session ID to test those tasks on', }, {'args':['--pubkey'], 'default': None, 'metavar': 'PUBKEY_AS_STRING', 'help': 'use this public key when testing the \'addauthkey\' and the \'delauthkey\' tasks', }, ] misc_options = [ {'args':['-C','--config-file'], 'default': None, 'metavar': 'CONFIG_FILE', 'help': 'Specify a special configuration file name, default is: {default}'.format(default=x2gobroker.defaults.X2GOBROKER_CONFIG), }, {'args':['-d','--debug'], 'default': False, 'action': 'store_true', 'help': 'enable debugging code', }, ] p = argparse.ArgumentParser(description='X2Go Session Broker (Agent Test Utility)',\ formatter_class=argparse.RawDescriptionHelpFormatter, \ add_help=True, argument_default=None) p_agent = p.add_argument_group('agent parameters') p_supplimentary = p.add_argument_group('supplimentary parameters') p_misc = p.add_argument_group('miscellaneous parameters') for (p_group, opts) in ( (p_agent, agent_options), (p_supplimentary, supplementary_options), (p_misc, misc_options), ): for opt in opts: args = opt['args'] del opt['args'] p_group.add_argument(*args, **opt) cmdline_args = p.parse_args() if os.getuid() != 0: p.print_help() print () print(("*** The {progname} tool needs to be run with super-user privileges... ***".format(progname=PROG_NAME))) print () sys.exit(-1) if cmdline_args.username is None and not cmdline_args.list_tasks and cmdline_args.task not in ('ping', 'checkload'): p.print_help() print () print ("*** Cannot continue without username... ***") print () sys.exit(-1) if cmdline_args.task in ('suspendsession', 'terminatesession') and not cmdline_args.session_id: p.print_help() print () print ("*** Cannot continue on this task without a given session ID... ***") print () sys.exit(0); if cmdline_args.config_file is not None: x2gobroker.defaults.X2GOBROKER_CONFIG = cmdline_args.config_file if cmdline_args.debug: x2gobroker.defaults.X2GOBROKER_DEBUG = cmdline_args.debug # raise log level to DEBUG if requested... if x2gobroker.defaults.X2GOBROKER_DEBUG and not x2gobroker.defaults.X2GOBROKER_TESTSUITE: x2gobroker.loggers.logger_broker.setLevel(logging.DEBUG) x2gobroker.loggers.logger_error.setLevel(logging.DEBUG) username = cmdline_args.username hostname = cmdline_args.host port = cmdline_args.port task = cmdline_args.task list_tasks = cmdline_args.list_tasks local_agent = (hostname == 'LOCAL') query_mode = local_agent and 'LOCAL' or 'SSH' if local_agent: remote_agent = None else: remote_agent = {'hostaddr': hostname, 'port': port, } if remote_agent and cmdline_args.add_to_known_hosts: remote_agent.update({ 'host_key_policy': 'AutoAddPolicy', }) def call_agent(task, **kwargs): try: _result = agent_client_tasks[task](username=username, query_mode=query_mode, remote_agent=remote_agent, **kwargs) if _result == True: print("The broker agent could be reached but the task returned no printable output (which probably is fine).") print() return _result except KeyError: print("No such task: '{task_name}'. Use --list-tasks to view information about".format(task_name=task)) print("available tasks.") print() return False if __name__ == "__main__": # drop root privileges and run as X2GOBROKER_DAEMON_USER drop_privileges(uid=x2gobroker.defaults.X2GOBROKER_DAEMON_USER, gid=x2gobroker.defaults.X2GOBROKER_DAEMON_GROUP) print() print("X2Go Session Broker (Agent Test Utility)") print("----------------------------------------") agent_client_tasks = x2gobroker.agent.tasks if 'availabletasks' in agent_client_tasks: try: (success, remote_agent_tasks) = x2gobroker.agent.tasks_available(username=username, query_mode=query_mode, remote_agent=remote_agent) except x2gobroker.x2gobroker_exceptions.X2GoBrokerAgentException as e: print("{errmsg}.".format(errmsg=e)) print() sys.exit(0) if not local_agent and not x2gobroker.agent.has_remote_broker_agent_setup(): print("This instance of X2Go Session Broker is not able to contact any remote") print("X2Go Session Broker Agent instances. Check this broker's SSH setup!!!") print() print("Aborting any futher tests...") sys.exit(-1) if list_tasks: print("The queried broker agent supports these tasks / features:") print() for task in remote_agent_tasks: try: print(" {task_name}: {task_function_obj}".format(task_name=task, task_function_obj=agent_client_tasks[task])) except KeyError: print(" {task_name}: not supported by this broker version".format(task_name=task)) print() sys.exit(0); kwargs = {} pubkey = cmdline_args.pubkey if (task == 'addauthkey' or task == 'delauthkey') and not pubkey: pubkey, privkey = x2gobroker.agent.genkeypair(local_username=username, client_address="localhost") if pubkey: kwargs.update({ 'pubkey_hash': pubkey, }) if task in ('suspendsession', 'terminatesession'): kwargs.update({ 'session_name': cmdline_args.session_id }) result = call_agent(task, **kwargs) if type(result) is dict: print("\n".join(result)) print() elif task.startswith('findbusyservers') and type(result) is dict: if result: print("\n".join([ "{host} -- {usage}%".format(host=host, usage=usage) for host, usage in list(result.items()) ])) else: print("X2Go Server busy state: All servers are idle.") # even an empty dict means, that we have been successful... result = True print() if task == 'addauthkey' and result: on_host = " on {host}".format(host=cmdline_args.host) if cmdline_args.host != 'LOCAL' else "" print("NOTE: This test-run added the below SSH public key to X2Go's authorized_keys file") print(" for user '{username}{on_host}'.".format(username=username, on_host=on_host)) print() print(" The file location for this normally is $HOME/.x2go/authorized_keys.") print(" MAKE SURE TO REMOVE THIS KEY MANUALLY (or use test the 'delauthkey' task)!!!") print() print(pubkey) print() if task == 'delauthkey' and result: on_host = " on {host}".format(host=cmdline_args.host) if cmdline_args.host != 'LOCAL' else "" print("NOTE: This test-run attempted to remove all occurences of the below SSH public key from") print(" X2Go's authorized_keys file for user '{username}{on_host}'.".format(username=username, on_host=on_host)) print() print(" The file location for this normally is $HOME/.x2go/authorized_keys.") print(" PLEASE DOUBLE-CHECK THE USER'S authorized_keys file MANUALLY!!!") print() print(pubkey) print() where = "local" if query_mode == "LOCAL" else "remote" if result: print("The task '{task_name}' could be executed successfully on the {where} broker agent.".format(task_name=task, where=where)) else: print("The task '{task_name}' failed to execute on the {where} broker agent.".format(task_name=task, where=where)) print() x2gobroker-0.0.4.1/setup.py0000755000000000000000000000314713457267612012363 0ustar #!/usr/bin/env python3 # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. from setuptools import setup, find_packages import os __VERSION__ = None for line in open(os.path.join('x2gobroker', '__init__.py')).readlines(): if (line.startswith('__VERSION__')): exec(line.strip()) __AUTHOR__ = None for line in open(os.path.join('x2gobroker', '__init__.py')).readlines(): if (line.startswith('__AUTHOR__')): exec(line.strip()) MODULE_VERSION = __VERSION__ MODULE_AUTHOR = __AUTHOR__ setup( name = "x2gobroker", version = MODULE_VERSION, description = "X2Go Session Broker", license = 'AGPLv3+', author = MODULE_AUTHOR, url = 'https://www.x2go.org', packages = find_packages('.'), package_dir = {'': '.'}, test_suite = "x2gobroker.tests.runalltests", use_2to3 = True, ) x2gobroker-0.0.4.1/src/x2gobroker-agent.c0000644000000000000000000000271313457267612014757 0ustar /* * This file is part of the X2Go Project - https://www.x2go.org * Copyright (C) 2012-2019 Mike Gabriel * * This program 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 * (at your option) any later version. * * This program 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. * */ #include #include #include #include #include int main( int argc, char *argv[] ) { char x2gobrokeragent[] = TRUSTED_BINARY; argv[0] = "x2gobroker-agent.pl"; // execute the script, running with user-rights of this binary int ret = execv(x2gobrokeragent, argv); int saved_errno = errno; if (ret) { fprintf (stderr, "unable to execute script '"); fprintf (stderr, "%s", TRUSTED_BINARY); fprintf (stderr, "': "); fprintf (stderr, "%s", strerror (saved_errno)); return (EXIT_FAILURE); } /* Should not be reached. */ return (EXIT_SUCCESS); } x2gobroker-0.0.4.1/src/x2gobroker-ssh.c0000644000000000000000000000267613457267612014466 0ustar /* * This file is part of the X2Go Project - https://www.x2go.org * Copyright (C) 2012-2019 Mike Gabriel * * This program 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 * (at your option) any later version. * * This program 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. * */ #include #include #include #include #include int main( int argc, char *argv[] ) { char x2gobrokerssh[] = TRUSTED_BINARY; argv[0] = "x2gobroker"; // execute the script, running with user-rights of this binary int ret = execv(x2gobrokerssh, argv); int saved_errno = errno; if (ret) { fprintf (stderr, "unable to execute script '"); fprintf (stderr, "%s", TRUSTED_BINARY); fprintf (stderr, "': "); fprintf (stderr, "%s", strerror (saved_errno)); return (EXIT_FAILURE); } /* Should not be reached. */ return (EXIT_SUCCESS); } x2gobroker-0.0.4.1/test.py0000755000000000000000000000220613457267612012175 0ustar #!/usr/bin/env python3 # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2014-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. """ Unit tests for Python X2GoBroker. """ import os if __name__ == "__main__": os.environ.update({'X2GOBROKER_DEBUG': "1"}) os.environ.update({'X2GOBROKER_TESTSUITE': "1"}) os.chdir(os.path.join('x2gobroker', 'tests',)) os.system('python3 ./runalltests.py') x2gobroker-0.0.4.1/tmpfiles.d/x2gobroker-authservice.conf0000644000000000000000000000022313457267612020156 0ustar #Type Path Mode UID GID Age Argument d /run/x2gobroker 0770 x2gobroker x2gobroker - - x2gobroker-0.0.4.1/tmpfiles.d/x2gobroker-daemon.conf0000644000000000000000000000022313457267612017077 0ustar #Type Path Mode UID GID Age Argument d /run/x2gobroker 0770 x2gobroker x2gobroker - - x2gobroker-0.0.4.1/tmpfiles.d/x2gobroker-loadchecker.conf0000644000000000000000000000022313457267612020100 0ustar #Type Path Mode UID GID Age Argument d /run/x2gobroker 0770 x2gobroker x2gobroker - - x2gobroker-0.0.4.1/TODO0000644000000000000000000000077213457267612011337 0ustar TODO list for X2Go Session Broker ================================= Several more thinkable broker backends: o USERGROUP backend (provide one session profiles file per user/group) o LDAP backend (provide session profiles through LDAP) o DNS backend (store X2Go server information in DNS and detect it from there) o WEBCONFIG backend (click your session together TTW) o ... For the INIFILE backend: o make use of X2Go::Server Perl API in x2gobroker-agent light+love, Mike Gabriel, 20140912 x2gobroker-0.0.4.1/x2gobroker/agent.py0000644000000000000000000006407113457267612014405 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. import os import os.path import subprocess import paramiko import io import socket import logging paramiko_logger = paramiko.util.logging.getLogger() paramiko_logger.setLevel(logging.ERROR) import x2gobroker._paramiko x2gobroker._paramiko.monkey_patch_paramiko() # X2Go Broker modules import x2gobroker.defaults import x2gobroker.x2gobroker_exceptions import x2gobroker.utils from x2gobroker.loggers import logger_broker, logger_error from x2gobroker.utils import delayed_execution tasks = {} def has_remote_broker_agent_setup(): """\ Peform some integrity checks that may indicate that a remote broker agent setup is available. - Check for available SSH private keys. - Nothing else, so far... :returns: ``True``, if the broker supports remote broker agent calls :rtype: ``bool`` """ home = os.path.expanduser("~") if os.path.exists(os.path.join(home, '.ssh', 'id_rsa')): return True elif os.path.exists(os.path.join(home, '.ssh', 'id_dsa')): return True elif os.path.exists(os.path.join(home, '.ssh', 'id_ecdsa')): return True return False def call_broker_agent(username, task, cmdline_args=[], remote_agent=None, logger=None, **kwargs): """\ Launch X2Go Broker Agent and process its output. :param username: run the broker agent for this user :type username: ``str`` :param task: task name to execute via the broker agent (listsessions, getservers, etc.) :type task: ``str`` :param cmdline_args: additional command line parameters for the broker agent :type cmdline_args: ``list`` :param remote_agent: if not ``None`` call a remote broker agent via SSH :type remote_agent: ``dict`` :param logger: logger instance to report log messages to :type logger: :class:`logging.Logger` :raises X2GoBrokerAgentException: if the call to the remote broker agents fails. :returns: ``(, )``, a tuple with the flag as first item and the data retrieved from the broker agent as second item :rtype: ``tuple`` """ if remote_agent in ('LOCAL', None): result = _call_local_broker_agent(username=username, task=task, cmdline_args=cmdline_args, logger=logger) else: result = _call_remote_broker_agent(username=username, task=task, cmdline_args=cmdline_args, remote_agent=remote_agent, logger=logger) return result def _call_local_broker_agent(username, task, cmdline_args=[], logger=None): """\ Launch X2Go Broker Agent locally and process its output. :param username: run the broker agent for this user :type username: ``str`` :param task: task name to execute via the broker agent (listsessions, getservers, etc.) :type task: ``str`` :param cmdline_args: additional command line parameters for the broker agent :type cmdline_args: ``list`` :param logger: logger instance to report log messages to :type logger: :class:`logging.Logger` :raises X2GoBrokerAgentException: if the call to the remote broker agents fails. :returns: ``(, )``, a tuple with the flag as first item and the data retrieved from the broker agent as second item :rtype: ``tuple`` """ if logger is None: logger = logger_broker cmd_line = [] try: if os.stat("/usr/local/bin/x2gobroker-ssh").st_gid in os.getgroups(): cmd_line.append(["sudo", "-g", x2gobroker.defaults.X2GOBROKER_DAEMON_GROUP]) except OSError: try: if os.stat("/usr/bin/x2gobroker-ssh").st_gid in os.getgroups(): cmd_line.extend(["sudo", "-g", x2gobroker.defaults.X2GOBROKER_DAEMON_GROUP]) except OSError: pass cmd_line.extend([ '{x2gobroker_agent_binary}'.format(x2gobroker_agent_binary=x2gobroker.defaults.X2GOBROKER_AGENT_CMD), '{username}'.format(username=username), '{task}'.format(task=task), ]) for cmdline_arg in cmdline_args: cmd_line.append('{arg}'.format(arg=cmdline_arg)) logger.info('Executing agent command locally: {cmd}'.format(cmd=" ".join(cmd_line))) ### FIXME: why do we set result to ['FAILED'] here and override it later??? result = ['FAILED'] try: agent_process = subprocess.Popen(cmd_line, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=False, ) result = agent_process.stdout.read().decode().split('\n') logger.info('Executing agent command succeeded.') # skipping process terminating (not needed and not permitted # as the broker agent is installed setuid root. agent_process.communicate() except OSError as e: logger.warning('Executing agent command failed. Error message is: {emsg}.'.format(emsg=str(e))) result = None if result: logger.info('Broker agent answered: {answer}'.format(answer="; ".join(result))) if result and result[0].startswith('OK'): return (True, [ r for r in result[1:] if r ]) else: return (False, []) raise x2gobroker.x2gobroker_exceptions.X2GoBrokerAgentException('Query to local X2Go Broker Agent failed with no response') def _call_remote_broker_agent(username, task, cmdline_args=[], remote_agent=None, logger=None): """\ Launch remote X2Go Broker Agent via SSH and process its output. :param username: run the broker agent for this user :type username: ``str`` :param task: task name to execute via the broker agent (listsessions, getservers, etc.) :type task: ``str`` :param cmdline_args: additional command line parameters for the broker agent :type cmdline_args: ``list`` :param remote_agent: information about the remote agent that is to be called :type remote_agent: ``dict`` :param logger: logger instance to report log messages to :type logger: :class:`logging.Logger` :raises X2GoBrokerAgentException: if the call to the remote broker agents fails. :returns: ``(, )``, a tuple with the flag as first item and the data retrieved from the broker agent as second item :rtype: ``tuple`` """ if logger is None: logger = logger_broker if remote_agent is None: logger_error.error('With the SSH agent-query-mode a remote agent host (hostname, hostaddr, port) has to be specified!') elif 'host_key_policy' not in remote_agent or remote_agent['host_key_policy'] == 'WarningPolicy': _hostkey_policy = paramiko.WarningPolicy() elif remote_agent['host_key_policy'] == 'RejectPolicy': _hostkey_policy = paramiko.RejectPolicy() elif remote_agent['host_key_policy'] == 'AutoAddPolicy': _hostkey_policy = paramiko.AutoAddPolicy() else: logger_error.error('Invalid SSH HostKey Policy: "{policy}", falling back to "RejectPolicy"!'.format(policy=remote_agent['host_key_policy'])) _hostkey_policy = paramiko.RejectPolicy() remote_hostaddr = None remote_hostname = None if 'hostaddr' in remote_agent: remote_hostaddr = remote_agent['hostaddr'] if 'hostname' in remote_agent: remote_hostname = remote_agent['hostname'] if remote_hostaddr is None and remote_hostname is None: raise x2gobroker.x2gobroker_exceptions.X2GoBrokerAgentException('Internal error: The remote_agent dict must always specify either a hostaddr or a hostname key!') if 'port' in remote_agent: remote_port = int(remote_agent['port']) else: remote_port = 22 cmd_line = [ '{x2gobroker_agent_binary}'.format(x2gobroker_agent_binary=x2gobroker.defaults.X2GOBROKER_AGENT_CMD), '{username}'.format(username=username), '{task}'.format(task=task), ] for cmdline_arg in cmdline_args: cmd_line.append('"{arg}"'.format(arg=cmdline_arg)) remote_username = x2gobroker.defaults.X2GOBROKER_AGENT_USER # check how we shall connect to the remote agent's SSH host... _remote_sshserver = None if x2gobroker.utils.portscan(remote_hostname, remote_port): _remote_sshserver = remote_hostname elif x2gobroker.utils.portscan(remote_hostaddr, remote_port): _remote_sshserver = remote_hostaddr if _remote_sshserver: # now, connect and use paramiko Client to negotiate SSH2 across the connection try: client = paramiko.SSHClient() client.load_system_host_keys() if os.path.exists(os.path.expanduser("~/.ssh/known_hosts")): client.load_host_keys(os.path.expanduser("~/.ssh/known_hosts")) client.set_missing_host_key_policy(_hostkey_policy) client.connect(_remote_sshserver, remote_port, remote_username, look_for_keys=True, allow_agent=True) result = [] ssh_transport = client.get_transport() if ssh_transport and ssh_transport.is_authenticated(): cmd = ' '.join(cmd_line) cmd = 'sh -c \'{cmd}\''.format(cmd=cmd) logger.info('Executing agent command on remote host {hostname} ({hostaddr}): {cmd}'.format(hostname=remote_hostname, hostaddr=remote_hostaddr, cmd=cmd)) (stdin, stdout, stderr) = client.exec_command(cmd) result = stdout.read().decode().split('\n') err = stderr.read().decode().replace('\n', ' ') if err: logger.warning('Remote agent command (host: {hostname} ({hostaddr})) reported an error: {err}'.format(hostname=remote_hostname, hostaddr=remote_hostaddr, err=err)) result = None client.close() if result: logger.info('Broker agent answered: {answer}'.format(answer="; ".join(result))) if result and result[0].startswith('OK'): return (True, [ r for r in result[1:] if r ]) else: return (False, []) except paramiko.AuthenticationException: raise x2gobroker.x2gobroker_exceptions.X2GoBrokerAgentException('Authentication to remote X2Go Broker Agent Host failed (user: {user}, hostname: {hostname}, host address: {hostaddr}, port: {port}) failed'.format(user=remote_username, hostname=remote_hostname, hostaddr=remote_hostaddr, port=remote_port)) except (paramiko.SSHException, paramiko.BadHostKeyException, socket.error): raise x2gobroker.x2gobroker_exceptions.X2GoBrokerAgentException('Query to remote X2Go Broker Agent (user: {user}, hostname: {hostname}, host address: {hostaddr}, port: {port}) failed'.format(user=remote_username, hostname=remote_hostname, hostaddr=remote_hostaddr, port=remote_port)) else: raise x2gobroker.x2gobroker_exceptions.X2GoBrokerAgentException('Could not ping remote X2Go Broker Agent host: {hostname} ({hostaddr})'.format(hostname=remote_hostname, hostaddr=remote_hostaddr)) def ping(remote_agent=None, logger=None, **kwargs): """\ Ping X2Go Broker Agent. :param remote_agent: information about the remote agent that is to be called :type remote_agent: ``dict`` :param logger: logger instance to report log messages to :type logger: :class:`logging.Logger` :returns: ``True`` if broker agent responds :rtype: ``bool`` """ if logger is None: logger = logger_broker username='foo' if remote_agent is None: return _call_local_broker_agent(username, task='ping', logger=logger)[0] else: return remote_agent is not None and \ (x2gobroker.utils.portscan(remote_agent['hostaddr'], remote_agent['port']) or x2gobroker.utils.portscan(remote_agent['hostname'], remote_agent['port'])) and \ _call_remote_broker_agent(username, task='ping', remote_agent=remote_agent, logger=logger)[0] tasks['ping'] = ping def list_sessions(username, remote_agent=None, logger=None, **kwargs): """\ Query X2Go Broker Agent for a session list for a given username. :param username: run the query on behalf of this username :type username: ``str`` :param remote_agent: information about the remote agent that is to be called :type remote_agent: ``dict`` :param logger: logger instance to report log messages to :type logger: :class:`logging.Logger` :returns: ``(, )``, a tuple with the flag as first item and a session ``list`` as second item :rtype: ``tuple`` """ if logger is None: logger = logger_broker return call_broker_agent(username, task='listsessions', remote_agent=remote_agent, logger=logger, **kwargs) tasks['listsessions'] = list_sessions def suspend_session(username, session_name, remote_agent=None, logger=None, **kwargs): """\ Trigger a session suspensions via the X2Go Broker Agent. :param username: suspend the session on behalf of this username :type username: ``str`` :param remote_agent: information about the remote agent that is to be called :type remote_agent: ``dict`` :param logger: logger instance to report log messages to :type logger: :class:`logging.Logger` :returns: ``(, [])``, a tuple with the flag as first item :rtype: ``tuple`` """ if logger is None: logger = logger_broker return call_broker_agent(username, task='suspendsession', cmdline_args=[session_name, ], remote_agent=remote_agent, logger=logger, **kwargs) tasks['suspendsession'] = suspend_session def terminate_session(username, session_name, remote_agent=None, logger=None, **kwargs): """\ Trigger a session termination via the X2Go Broker Agent. :param username: terminate the session on behalf of this username :type username: ``str`` :param remote_agent: information about the remote agent that is to be called :type remote_agent: ``dict`` :param logger: logger instance to report log messages to :type logger: :class:`logging.Logger` :returns: ``(, [])``, a tuple with the flag as first item :rtype: ``tuple`` """ if logger is None: logger = logger_broker return call_broker_agent(username, task='terminatesession', cmdline_args=[session_name, ], remote_agent=remote_agent, logger=logger, **kwargs) tasks['terminatesession'] = terminate_session def has_sessions(username, remote_agent=None, logger=None, **kwargs): """\ Query X2Go Broker Agent to detect running/suspended sessions on the remote X2Go Server (farm). :param username: run the query on behalf of this username :type username: ``str`` :param remote_agent: information about the remote agent that is to be called :type remote_agent: ``dict`` :param logger: logger instance to report log messages to :type logger: :class:`logging.Logger` :returns: ``(, , )``, a tuple of two Boolean values :rtype: ``tuple`` """ if logger is None: logger = logger_broker _success, _session_list = list_sessions(username, remote_agent=remote_agent, logger=logger, **kwargs) if type(_session_list) is list: return (_success, [ s.split('|')[3] for s in _session_list if s.split('|')[4] == 'R' ], [ s.split('|')[3] for s in _session_list if s.split('|')[4] == 'S' ]) else: return (False, [], []) tasks['has-sessions'] = has_sessions def find_busy_servers(username, remote_agent=None, logger=None, **kwargs): """\ Query X2Go Broker Agent for a list of servers with running and/or suspended sessions and a percentage that tells about the busy-state of the server. The result is independent from the username given. :param username: run the query on behalf of this username :type username: ``str`` :param remote_agent: information about the remote agent that is to be called :type remote_agent: ``dict`` :param logger: logger instance to report log messages to :type logger: :class:`logging.Logger` :returns: ``(, )``, a tuple with the flag as first item and a dict reflecting the relative server usage :rtype: ``tuple`` """ if logger is None: logger = logger_broker _success, server_list = call_broker_agent(username, task='findbusyservers', remote_agent=remote_agent, logger=logger, **kwargs) server_usage = {} if server_list and type(server_list) is list: for server_item in server_list: if ':' in server_item: usage, server = server_item.split(':') server_usage.update({ server: int(usage) }) else: _success = False return (_success, server_usage) tasks['findbusyservers'] = find_busy_servers def check_load(remote_agent=None, logger=None, **kwargs): """\ Query X2Go Broker Agent for a summary of system load specific parameters. :param remote_agent: information about the remote agent that is to be called :type remote_agent: ``dict`` :param logger: logger instance to report log messages to :type logger: :class:`logging.Logger` :returns: ``(, )``, a tuple with the flag as first item and the queried server's load factor as second item :rtype: ``tuple`` """ if logger is None: logger = logger_broker if not remote_agent: logger.error('no remote agent was given, can\'t query load') return "NO-REMOTE-AGENT" if remote_agent == 'LOCAL': logger.error('no load checking support if remote agent is set to \'LOCAL\'') return "REMOTE-AGENT-IS-SET-TO-LOCAL" try: if "username" in list(kwargs.keys()): del kwargs["username"] _success, _load_params = call_broker_agent(username='foo', task='checkload', remote_agent=remote_agent, logger=logger, **kwargs) except x2gobroker.x2gobroker_exceptions.X2GoBrokerAgentException as e: logger.error('querying remote agent on host {hostname} failed: {errmsg}'.format(hostname=remote_agent['hostname'], errmsg=str(e))) return "HOST-UNREACHABLE" p = {} for _param in _load_params: if ':' in _param: key, val = _param.split(':', 1) p[key] = float(val) load_factor = None try: if p['memAvail'] == 0: p['memAvail'] = p['myMemAvail'] load_factor = int( ( (p['memAvail']/1000) * p['numCPU'] * p['typeCPU'] * 100 ) / p['loadavgXX'] ) except KeyError: return "LOAD-DATA-BOGUS" return load_factor tasks['checkload'] = check_load def add_authorized_key(username, pubkey_hash, authorized_keys_file='%h/.x2go/authorized_keys', remote_agent=None, logger=None, **kwargs): """\ Add a public key hash to the user's authorized_keys file. :param username: run the query on behalf of this username :type username: ``str`` :param pubkey_hash: the public key hash as found in SSH authorized_keys files :type pubkey_hash: ``str`` :param authorized_keys_file: the full path to the remote X2Go server's authorized_keys file :type authorized_keys_file: ``str`` :param remote_agent: information about the remote agent that is to be called :type remote_agent: ``dict`` :param logger: logger instance to report log messages to :type logger: :class:`logging.Logger` :returns: ``(, [])``, a tuple with the flag as first item :rtype: ``tuple`` """ if logger is None: logger = logger_broker return call_broker_agent(username, task='addauthkey', cmdline_args=[pubkey_hash, authorized_keys_file, ], remote_agent=remote_agent, logger=logger, **kwargs) tasks['addauthkey'] = add_authorized_key def delete_authorized_key(username, pubkey_hash, authorized_keys_file='%h/.x2go/authorized_keys', remote_agent=None, delay_deletion=0, logger=None, **kwargs): """\ Remove a public key hash from the user's authorized_keys file. :param username: run the query on behalf of this username :type username: ``str`` :param pubkey_hash: the public key hash as found in SSH authorized_keys files :type pubkey_hash: ``str`` :param authorized_keys_file: the full path to the remote X2Go server's authorized_keys file :type authorized_keys_file: ``str`` :param remote_agent: information about the remote agent that is to be called :type remote_agent: ``dict`` :param logger: logger instance to report log messages to :type logger: :class:`logging.Logger` :returns: ``(, [])``, a tuple with the flag as first item :rtype: ``tuple`` """ if logger is None: logger = logger_broker # this is for the logger output if remote_agent in ('LOCAL', None): _hostname = _hostaddr = 'LOCAL' else: _hostname = remote_agent['hostname'] _hostaddr = remote_agent['hostaddr'] if delay_deletion > 0: delayed_execution(delete_authorized_key, delay=delay_deletion, username=username, pubkey_hash=pubkey_hash, authorized_keys_file=authorized_keys_file, remote_agent=remote_agent, ) logger.debug('Scheduled deletion of authorized key in {delay}s: user={user}, hostname={hostname}, hostaddr={hostaddr}'.format(delay=delay_deletion, user=username, hostname=_hostname, hostaddr=_hostaddr)) else: return call_broker_agent(username, task='delauthkey', cmdline_args=[pubkey_hash, authorized_keys_file, ], remote_agent=remote_agent, logger=logger, **kwargs) tasks['delauthkey'] = delete_authorized_key def get_servers(username, remote_agent=None, logger=None, **kwargs): """\ Query X2Go Broker Agent for the list of currently used servers. The result is independent from the username given. :param username: run the query on behalf of this username :type username: ``str`` :param remote_agent: information about the remote agent that is to be called :type remote_agent: ``dict`` :param logger: logger instance to report log messages to :type logger: :class:`logging.Logger` :returns: ``(, )``, a tuple with the flag as first item and the list of used X2Go Servers as second item :rtype: ``tuple`` """ if logger is None: logger = logger_broker result = {} success, lines = call_broker_agent(username, task='getservers', remote_agent=remote_agent, logger=logger, **kwargs) if success: for line in lines: try: if " " in line: server, num_sessions = line.split(" ", 1) result[server] = int(num_sessions) except ValueError: pass return success, result tasks['getservers'] = get_servers def tasks_available(username, remote_agent=None, logger=None, **kwargs): """\ Query X2Go Broker Agent for the list of available tasks. Depending on the remove broker agent's version, the result of this query can vary tremendously from X2Go Server to X2Go Server. :param username: run the query on behalf of this username :type username: ``str`` :param remote_agent: information about the remote agent that is to be called :type remote_agent: ``dict`` :param logger: logger instance to report log messages to :type logger: :class:`logging.Logger` :returns: ``(, )``, a tuple with the flag as first item and a list of available broker agent tasks as second item :rtype: ``tuple`` """ if logger is None: logger = logger_broker return call_broker_agent(username, task='availabletasks', remote_agent=remote_agent, logger=logger, **kwargs) tasks['availabletasks'] = tasks_available def genkeypair(local_username, client_address, key_type='RSA', logger=None): """\ Generate an SSH pub/priv key pair without writing the private key to file. :param local_username: the key is for this user :type local_username: ``str`` :param client_address: the key is only valid for this client :type client_address: ``str`` :param key_type: either of: RSA, DSA :type key_type: ``str`` :param logger: logger instance to report log messages to :type logger: :class:`logging.Logger` :returns: two-item tuple: ``(, )`` :rtype: ``tuple`` """ key = None pubkey = None privkey = None # generate key pair if key_type == 'RSA': key = paramiko.RSAKey.generate(2048) elif key_type == 'DSA': key = paramiko.DSSKey.generate(1024) if key: # assemble the public key if key_type == "RSA": pubkey_type = 'ssh-rsa' elif key_type == "DSA": pubkey_type = 'ssh-dss' # FIXME: the from option does not work properly by some reason. Fix it later #pubkey = "from={client_address},no-X11-forwarding,no-pty,no-user-rc {pubkey_type} {pubkey} {local_username}@{client_address}".format(pubkey=key.get_base64(), pubkey_type=pubkey_type, local_username=local_username, client_address=client_address) pubkey = "no-X11-forwarding,no-pty,no-user-rc {pubkey_type} {pubkey} {local_username}@{client_address}".format(pubkey=key.get_base64(), pubkey_type=pubkey_type, local_username=local_username, client_address=client_address) # assemble the private key privkey_obj = io.StringIO() key.write_private_key(privkey_obj) privkey = privkey_obj.getvalue() return (pubkey, privkey) x2gobroker-0.0.4.1/x2gobroker/authmechs/base_authmech.py0000644000000000000000000000427513457267612020060 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. class X2GoBrokerAuthMech(object): """\ Base *authentication mechanism* class. This class is not supposed to be used as an authentication mechanism in running setups. (It let's authentication always fail). It is rather so, that more specific authentication mechanisms should inherit from this class. All features common to more specific authentication mechanisms go in here. """ ### FIXME: Currently we don't let the other authmech classes inherit # from this class. Technically, this is ok as we override everything in # here in the sub-classes anyway. However, this should be fixed... # # E.g. the unit tests fail if tests.X2GoBrokerAuthMech inherits from # base.X2GoBrokerAuthMech. This needs some investigation. def authenticate(self, username, password, **kwargs): """\ Dummy :func:`authenticate()` method of :class:`X2GoBrokerAuthMech`. :param username: The broker username sent by the client (ignored) :type username: ``str`` :param password: The broker password sent by the client (ignored) :type password: ``str`` :param kwargs: Any other parameter (for future features' compatibility, all ignored for now) :type kwargs: ``dict`` :returns: Authentication failure (always!) :rtype: ``bool`` """ return False x2gobroker-0.0.4.1/x2gobroker/authmechs/https_get_authmech.py0000644000000000000000000001123213457267612021136 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2012-2019 by Mike Gabriel # Copyright (C) 2012-2019 by Josh Lukens # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. # Very simple authmech that requests a webpage over https with basic auth. # If the page is fetched successfully (status 200) the user is authenticated. # # Used in conjunction with something like an apache server you can get easy # access to the full handful of existing auth modules for things like radius, # RSA, etc. # # Server name and path must be hard coded below for the time being. Also note # that the httplib module used does not verify SSL certificates so be sure # you are on a trusted network as there is a possibility of a man in the middle # attack. # modules import http.client import base64 class X2GoBrokerAuthMech(object): """\ X2Go Session Broker's **https_get** *authentication mechanism*: This authentication mechanism can be attached to a web server that provides some test URL protected by http(s) Basic Authentication. When the :func:`authenticate()` function gets called, it attempts to retrieve the test URL via a http(s) GET request. The webserver serving that URL then sends a response back, demanding ``Authorization``. For the Basic Authorization request that gets sent back to the webserver, the username and password provided by the X2Go client application get used. """ def authenticate(self, username, password, config=None, **kwargs): """\ The **https_get** authentication mechanism's :func:`authenticate()` method attempts authentication against a http(s) server. It lets broker authentication succeed if the upstream webserver grants authentication to a given test URL. Otherwise, broker authencation fails. The test URL is provided as set of config parameters passed in via the ``config`` function parameter. If no config is given, the default authentication will be performed against ``http://localhost/auth``. The configuration object provided as ``config`` to this method requires to understand this API (a class from module :mod:`configparser` should do this for you):: host = config.get_value('authmech_https_get','host') path = config.get_value('authmech_https_get','path') port = config.get_value('authmech_https_get','port') :param username: The broker username sent by the client :type username: ``str`` :param password: The broker password sent by the client :type password: ``str`` :param config: A :mod:`configparser` compliant configuration object :param type: ```` :param kwargs: Any other parameter (for future features' compatibility, all ignored for now) :type kwargs: ``dict`` :returns: Authentication success or failure. :rtype: ``bool`` """ ## FIXME: these should really be specificed in master config file and have better error checking if config: host = config.get_value('authmech_https_get','host') path = config.get_value('authmech_https_get','path') port = config.get_value('authmech_https_get','port') else: host = "localhost" path = "/auth" port = "80" # base64 encode the username and password auth = base64.standard_b64encode('%s:%s' % (username, password)).replace('\n', '') https = http.client.HTTPSConnection(host,port) https.putrequest("GET", path) https.putheader("Host", host) https.putheader("User-Agent", "X2Go Session Broker") https.putheader("Authorization", "Basic %s" % auth) https.endheaders() response = https.getresponse() https.close() if response.status == 200: return True return False x2gobroker-0.0.4.1/x2gobroker/authmechs/__init__.py0000644000000000000000000000153313457267612017021 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. x2gobroker-0.0.4.1/x2gobroker/authmechs/none_authmech.py0000644000000000000000000000424013457267612020075 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. class X2GoBrokerAuthMech(object): """\ X2Go Session Broker's **none** *authentication mechanism*: Use this *authentication mechanism* for setups, where users are always granted access to the broker. No authentication is required. **WARNING:** Only use this authentication mechanism on private or VPN'ed networks. Don't use it, if your broker is reachable on the internet or in networks with non-trusted hosts. **NOTE:** The broker will not be able to distinguish between users when delivering available servers and session profiles to the user's X2Go Client application. """ def authenticate(self, username, password, **kwargs): """\ The **none** authentication mechanism's :func:`authenticate()` method always returns ``True`` to the user, so X2Go Session Broker access gets always granted. :param username: The broker username sent by the client (ignored) :type username: ``str`` :param password: The broker password sent by the client (ignored) :type password: ``str`` :param kwargs: Any other parameter (for future features' compatibility, all ignored for now) :type kwargs: ``dict`` :returns: Authentication success (always!) :rtype: ``bool`` """ return True x2gobroker-0.0.4.1/x2gobroker/authmechs/pam_authmech.py0000644000000000000000000001020313457267612017707 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. # modules from socket import error import getpass import pam # X2Go Session Broker modules import x2gobroker.authservice from x2gobroker.loggers import logger_error class X2GoBrokerAuthMech(object): """\ X2Go Session Broker's **PAM** *authentication mechanism*: This is the most commonly used and most flexible authentication mechanism in X2Go Session Broker. You can run the full scope of PAM authentication mechanisms (POSIX, LDAP, Kerberos, etc.) over it. **NOTE:** You can fine-tune PAM's authentication backends in the corresponding PAM service file ``/etc/pam.d/x2gobroker``. **WARNING:** The PAM authentication mechanism requires an extra X2Go Session Broker tool: the X2Go Session Broker's Authentication Service. Reason: Some PAM authentication modules (e.g. ``pam_unix.so``) require root privileges during the authentication process. The X2Go Session Broker's Auth Service runs with these root privileges and provides a communication socket to the X2Go Session Broker where authentication requests are proxied over. See :func:`x2gobroker.authservice.authenticate()`. If you don't need root privileges for PAM authentication (e.g. LDAP), simply don't run the X2Go Broker Auth Service and authentication against PAM are done directly by the session broker as system user ``x2gobroker``. """ def authenticate(self, username, password, **kwargs): """\ The **PAM** authentication mechanism's :func:`authenticate()` tries to proxy authentication through X2Go Session Broker's Auth Service first and, if that fails, attempts another authentication against PAM directly (which fails for some PAM modules). It returns ``True`` to the user, if authentication against PAM has been successful. :param username: The broker username sent by the client :type username: ``str`` :param password: The broker password sent by the client :type password: ``str`` :param kwargs: Any other parameter (for future features' compatibility, all ignored for now) :type kwargs: ``dict`` :returns: Authentication success or failure. :rtype: ``bool`` """ if username and password: try: # query the X2Go Session Broker's PAM Auth Service if x2gobroker.authservice.authenticate(username, password, service="x2gobroker"): return True except error: logger_error.error('pam_authmech.X2GoBrokerAuthmech.authenticate(): Authentication against authentication service failed, trying direct PAM authentication (which is likely to fail on most PAM setups).') logger_error.error('pam_authmech.X2GoBrokerAuthmech.authenticate(): Make sure the current user ({user}) is allowed to use the PAM authentication mechanism.'.format(user=getpass.getuser())) # fallback to direct PAM authentication against the PAM service ,,x2gobroker'' opam = pam if hasattr(pam, "pam"): opam = pam.pam() if opam.authenticate(username, password, service="x2gobroker"): return True return False x2gobroker-0.0.4.1/x2gobroker/authmechs/testsuite_authmech.py0000644000000000000000000000403513457267612021171 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. class X2GoBrokerAuthMech(object): """\ Unit testing *authentication mechanism* class. Used internally for running unit tests of the :mod:`x2gobroker` module's code base. Don't use this!!! """ def authenticate(self, username, password, **kwargs): """ Test function, faking sucessful authentication for user ``test`` with password ``sweet`` and user ``jacques`` with accentuated characters in the password ``thérèse``. Don't use this!!! :param username: The broker username sent by the client (ignored) :type username: ``str`` :param password: The broker password sent by the client (ignored) :type password: ``str`` :param kwargs: Any other parameter (for future features' compatibility, all ignored for now) :type kwargs: ``dict`` :returns: Authentication failure (always!) :rtype: ``bool`` """ # return ``True`` for user test with password sweet... (used by the unit tests) if username == 'test' and password == 'sweet': return True if username == "jacques" and password == 'thérèse': return True return False x2gobroker-0.0.4.1/x2gobroker/authservice.py0000644000000000000000000001575613457267612015637 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # This file is part of the X2Go Project - https://www.x2go.org # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. import asyncore import os import pam import socket from pwd import getpwnam from grp import getgrnam # X2Go Session Broker modules import x2gobroker.defaults from x2gobroker.loggers import logger_broker def authenticate(username, password, service="x2gobroker"): """\ Attempt PAM authentication proxied through X2Go Broker's Auth Service. The X2Go Broker Auth Service runs with root privileges. For PAM authentication mechanisms like the ``pam_unix.so`` PAM module, the login process requires root privileges (as, staying with the example of ``pam_unix.so``, the ``/etc/shadow`` file, where those passwords are stored, is only accessible by the root superuser). As the X2Go Session Broker runs with reduced system privileges, it has to delegate the actual PAM authentication process to the X2Go Broker Auth Service. For this, X2Go Session Broker needs to connect to the Auth Service's authentication socket (see the ``X2GOBROKER_AUTHSERVICE_SOCKET`` variable in :mod:`x2gobroker.defaults`) and send the string ``\\r\\r\\n`` to the socket (where service is the name of the PAM service file to use. :param username: username to use during authentication :type username: ``str`` :param password: password to use during authentication :type password: ``str`` :returns: Authentication success or failure :rtype: ``bool`` """ s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) logger_broker.debug('authservice.authenticate(): connecting to authentication service socket {socket}'.format(socket=x2gobroker.defaults.X2GOBROKER_AUTHSERVICE_SOCKET)) s.connect(x2gobroker.defaults.X2GOBROKER_AUTHSERVICE_SOCKET) # FIXME: somehow logging output disappears after we have connected to the socket file... logger_broker.debug('authservice.authenticate(): sending username={username}, password=, service={service} to authentication service'.format(username=username, service=service)) s.send('{username}\r{password}\r{service}\n'.format(username=username, password=password, service=service).encode()) result = s.recv(1024).decode() s.close() if result.startswith('ok'): logger_broker.info('authservice.authenticate(): authentication against PAM service »{service}« succeeded for user »{username}«'.format(username=username, service=service)) return True logger_broker.info('authservice.authenticate(): authentication against service »{service}« failed for user »{username}«'.format(username=username, service=service)) return False class AuthClient(asyncore.dispatcher_with_send): """\ Handle incoming PAM credential verification request and send a response back through the socket. :param sock: open socket connection :type sock: ```` :param logger: logger instance to report log messages to :type logger: ``obj`` """ def __init__(self, sock, logger=None): self.logger = logger asyncore.dispatcher_with_send.__init__(self, sock) self._buf = '' def handle_read(self): """\ Handle the incoming request after :func:`AuthService.accept()` and respond accordingly. The requests are expected line by line, the fields are split by "\\r":: \\r\\r\\n The reponse is sent back over the open socket connection. Possibly answers are either:: ok\\n or... fail\\n """ data = self._buf + self.recv(1024).decode() if not data: self.close() return reqs, data = data.rsplit('\n', 1) self._buf = data for req in reqs.split('\n'): try: user, passwd, service = req.split('\r') except: self.send('bad\n') self.logger.warning('bad authentication data received') else: opam = pam if hasattr(pam, "pam"): opam = pam.pam() if opam.authenticate(user, passwd, service): self.send('ok\n'.encode()) self.logger.info('successful authentication for \'{user}\' with password \'\' against PAM service \'{service}\''.format(user=user, service=service)) else: self.send('fail\n'.encode()) self.logger.info('authentication failure for \'{user}\' with password \'\' against PAM service \'{service}\''.format(user=user, service=service)) def handle_close(self): """\ Close the connected :class:``AuthClient`` connection. """ self.close() class AuthService(asyncore.dispatcher_with_send): """\ Provide an :mod:`asyncore` based authentication socket handler where client can send credential checking requests to. Access to the sockt is limited by file permissions to given owner and group. :param socketfile: file name path of the to be created Unix domain socket file. The directory in the give path must exist. :type socketfile: ``str`` :param owner: chown the socket file to this owner :type owner: ``str`` :param group: chgrp the socket file to this group :type group: ``str`` :param permissions: octal representation of the file permissions (handed over as string) :type permissions: ``str`` :param logger: logger instance to report log messages to :type logger: ```` """ def __init__(self, socketfile, owner='root', group_owner='root', permissions='0o660', logger=None): self.logger = logger asyncore.dispatcher_with_send.__init__(self) self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM) self.set_reuse_addr() self.bind(socketfile) os.chown(socketfile, getpwnam(owner).pw_uid, getgrnam(group_owner).gr_gid) os.chmod(socketfile, int(permissions, 8)) self.listen(1) def handle_accept(self): """\ Handle accepted connection requests. """ conn, _ = self.accept() AuthClient(conn, logger=self.logger) x2gobroker-0.0.4.1/x2gobroker-authservice.service0000644000000000000000000000061213457267612016626 0ustar [Unit] Description=X2Go Session Broker Authentication Service [Service] User=root Group=root Type=forking ExecStart=/usr/sbin/x2gobroker-authservice --socket-file=/run/x2gobroker/x2gobroker-authservice.socket --daemonize -o root -g x2gobroker -p 0660 --pidfile=/run/x2gobroker/x2gobroker-authservice.pid PIDFile=/run/x2gobroker/x2gobroker-authservice.pid [Install] WantedBy=multi-user.target x2gobroker-0.0.4.1/x2gobroker/basicauth.py0000644000000000000000000000627313457267612015252 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # This file is part of the X2Go Project - https://www.x2go.org # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. """\ Module providing a function for handling BasicAuth request processing. """ # modules import base64 def require_basic_auth(realm, validate_callback): """\ Handler for ``http(s)://`` BasisAuth processing. This function is used as a decorator for web request handler classes (such as tornado.web.RequestHandler). :param realm: authentication realm :type realm: ``str`` :param validate_callback: callback function for validating credentials :type validate_callback: ``func`` :returns: authentication :func:`execute()` function (or ``False``) :rtype: ``func`` or ``bool`` """ def require_basic_auth_decorator(handler_class): def wrap_execute(handler_execute): def require_basic_auth(handler, kwargs): def create_auth_header(): handler.set_status(401) handler.set_header('WWW-Authenticate', 'Basic realm="{realm}"'.format(realm=realm)) handler._transforms = [] handler.finish() kwargs['basicauth_user'], access = validate_callback('check-credentials', 'FALSE') if access: kwargs['basicauth_pass'] = 'anonymous access granted' return True auth_header = handler.request.headers.get('Authorization') if auth_header is None or not auth_header.startswith('Basic '): create_auth_header() else: auth_decoded = base64.decodestring(auth_header[6:].encode()).decode() username, kwargs['basicauth_pass'] = [ s for s in auth_decoded.split(':', 2) ] kwargs['basicauth_user'], access = validate_callback(username, kwargs['basicauth_pass']) if access: return True else: create_auth_header() def _execute(self, transforms, *args, **kwargs): if not require_basic_auth(self, kwargs): return False return handler_execute(self, transforms, *args, **kwargs) return _execute handler_class._execute = wrap_execute(handler_class._execute) return handler_class return require_basic_auth_decorator x2gobroker-0.0.4.1/x2gobroker/brokers/base_broker.py0000644000000000000000000025152613457267612017237 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2012-2019 by Mike Gabriel # Copyright (C) 2012-2019 by Josh Lukens # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. """\ :class:`x2gobroker.brokers.base_broker.X2GoBroker` class - base skeleton for X2GoBroker implementations """ __NAME__ = 'x2gobroker-pylib' # modules import copy import socket import uuid import netaddr import random import time import os.path # X2Go Broker modules import x2gobroker.config import x2gobroker.defaults import x2gobroker.agent import x2gobroker.x2gobroker_exceptions import x2gobroker.loadchecker from x2gobroker.loggers import logger_broker, logger_error from x2gobroker.defaults import X2GOBROKER_USER as _X2GOBROKER_USER from x2gobroker.defaults import X2GOBROKER_DAEMON_USER as _X2GOBROKER_DAEMON_USER class X2GoBroker(object): """\ :class:`x2gobroker.brokers.base_broker.X2GoBroker` is an abstract class for X2Go broker implementations. This class needs to be inherited from a concrete broker class. Currently available broker classes are:: :class:`zeroconf.X2GoBroker ` (working) :class:`inifile.X2GoBroker ` (working) :class:`ldap.X2GoBroker ` (in prep) """ backend_name = 'base' nameservice_module = None authmech_module = None def __init__(self, config_file=None, config_defaults=None): """\ Initialize a new X2GoBroker instance to control X2Go session through an X2Go Client with an intermediate session broker. :param config_file: path to the X2Go Session Broker configuration file (x2gobroker.conf) :type config_file: ``str`` :param config_defaults: Default settings for the broker's global configuration parameters. :type config_defaults: ``dict`` """ self.config_file = config_file if self.config_file is None: self.config_file = x2gobroker.defaults.X2GOBROKER_CONFIG if config_defaults is None: config_defaults = x2gobroker.defaults.X2GOBROKER_CONFIG_DEFAULTS self.config = x2gobroker.config.X2GoBrokerConfigFile(config_files=self.config_file, defaults=config_defaults) self.enabled = self.config.get_value('broker_{backend}'.format(backend=self.backend_name), 'enable') self._dynamic_cookie_map = {} self._client_address = None self._cookie = None def __del__(self): """\ Cleanup on destruction of an :class:`X2GoBroker ` instance. """ pass def is_enabled(self): """\ Check if this backend has been enabled in the configuration file. """ return self.enabled def get_name(self): """\ Accessor for self.backend_name property. :returns: the backend name :rtype: ``str`` """ return self.backend_name def enable(self): """\ Enable this broker backend. """ self.enabled = True def disable(self): """\ Disable this broker backend. """ self.enabled = False def set_client_address(self, address): """\ Set the client IP address. :param address: the client IP :type address: ``str`` """ if netaddr.valid_ipv6(address): pass elif netaddr.valid_ipv4(address): pass else: self._client_address = None raise ValueError('address {address} is neither a valid IPv6 nor a valid IPv4 address'.format(address=address)) self._client_address = netaddr.IPAddress(address) def get_client_address(self): """\ Get the client IP address (if set). :returns: the client IP (either IPv4 or IPv6) :rtype: ``str`` """ if self._client_address: return str(self._client_address) else: return None def get_client_address_type(self): """\ Get the client IP address type of the client address (if set). :returns: the client address type (4: IPv4, 6: IPv6) :rtype: ``int`` """ return self._client_address.version def get_global_config(self): """\ Get the global section of the configuration file. :returns: all global configuration parameters :rtype: ``dict`` """ return self.config.get_section('global') def get_global_value(self, option): """\ Get the configuration setting for an option in the global section of the configuration file. :param option: option name in the global configuration section :type option: ``str`` :returns: the value for the given global ``option`` :rtype: ``bool``, ``str``, ``int`` or ``list`` """ return self.config.get_value('global', option) def get_my_cookie(self): """\ Get the pre-set authentication cookie UUID hash that clients have to use on their first connection attempt (if the global config option "require-cookie" has been set). :returns: the pre-set authentication cookie UUID hash :rtype: ``str`` """ unconfigured_my_cookie = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' my_cookie = unconfigured_my_cookie deprecated_my_cookie = self.config.get_value('global', 'my-cookie') if deprecated_my_cookie is None: deprecated_my_cookie = uuid.uuid4() my_cookie_file = self.config.get_value('global', 'my-cookie-file') if os.path.isfile(my_cookie_file): fh = open(my_cookie_file, "r") lines = [ line for line in fh.read().split('\n') if line and not line.startswith('#') ] my_cookie = lines[0].split(' ')[0] if my_cookie != unconfigured_my_cookie: return my_cookie elif deprecated_my_cookie != unconfigured_my_cookie: return deprecated_my_cookie # instead of returning None here, we invent a cookie and return that return str(uuid.uuid4()) def get_backend_config(self): """\ Get the configuration section of a specific backend. :returns: all backend configuration parameters :rtype: ``dict`` """ return self.config.get_section('broker_{backend}'.format(backend=self.backend_name)) def get_backend_value(self, backend='zeroconf', option='enable'): """\ Get the configuration setting for backend ``backend`` and option ``option``. :param backend: the name of the backend :type backend: ``str`` :param option: option name of the backend's configuration section :type option: ``str`` :returns: the value for the given ``backend`` ``option`` :rtype: ``bool``, ``str``, ``int`` or ``list`` """ return self.config.get_value(backend, option) def get_profile_ids(self): """\ Retrieve the complete list of session profile IDs. :returns: list of profile IDs :rtype: ``list`` """ return [] def get_profile_ids_for_user(self, username): """\ Retrieve the list of session profile IDs for a given user. :param username: query profile id list for this user :type username: ``str`` :returns: list of profile IDs :rtype: ``list`` """ return [ id for id in self.get_profile_ids() if self.check_profile_acls(username, self.get_profile_acls(id)) ] def get_profile_defaults(self): """\ Get the session profile defaults, i.e. profile options that all configured session profiles have in common. The defaults are hard-coded in :mod:`x2gobroker.defaults` for class :class:`x2gobroker.brokers.base_broker.X2GoBroker`. :returns: a dictionary containing the session profile defaults :rtype: ``dict`` """ profile_defaults = copy.deepcopy(x2gobroker.defaults.X2GOBROKER_SESSIONPROFILE_DEFAULTS['DEFAULT']) for key in copy.deepcopy(profile_defaults): if key.startswith('acl-'): del profile_defaults[key] return profile_defaults def get_acl_defaults(self): """\ Get the ACL defaults for session profiles. The defaults are hard-coded in :mod:`x2gobroker.defaults` for class :class:`x2gobroker.brokers.base_broker.X2GoBroker`. :returns: a dictionary containing the ACL defaults for all session profiles :rtype: ``dict`` """ acl_defaults = copy.deepcopy(x2gobroker.defaults.X2GOBROKER_SESSIONPROFILE_DEFAULTS['DEFAULT']) for key in copy.deepcopy(acl_defaults): if not key.startswith('acl-'): del acl_defaults[key] return acl_defaults def get_profile(self, profile_id): """\ Get the session profile for profile ID . :param profile_id: the ID of a profile :type profile_id: ``str`` :returns: a dictionary representing the session profile for ID :rtype: ``dict`` """ return {} def get_profile_broker(self, profile_id): """\ Get broker-specific session profile options from the session profile with profile ID . :param profile_id: the ID of a profile :type profile_id: ``str`` :returns: a dictionary representing the session profile for ID :rtype: ``dict`` """ return {} def get_profile_acls(self, profile_id): """\ Get the ACLs for session profile with profile ID . :param profile_id: the ID of a profile :type profile_id: ``str`` :returns: a dictionary representing the ACLs for session profile with ID :rtype: ``dict`` """ return {} def check_profile_acls(self, username, acls): """\ Test if a given user can get through an ACL check using as a list of allow and deny rules. :param username: the username of interest :type username: ``str`` :param acls: a dictionary data structure containing ACL information (see :envvar:`x2gobroker.defaults.X2GOBROKER_SESSIONPROFILE_DEFAULTS`) :type acls: ``dict`` """ ### extract ACLs evaluation orders _acls = self.get_acl_defaults() _acls.update(acls) _order = {} _order['users'] = _order['groups'] = _order['clients'] = _acls['acl-any-order'] try: _order['users'] = _acls['acl-users-order'] except KeyError: pass try: _order['groups'] = _acls['acl-groups-order'] except KeyError: pass try: _order['clients'] = _acls['acl-clients-order'] except KeyError: pass # to pass an ACL test, all three keys in the dict below have to be set to True # if one stays False, the related session profile will not be returned to the querying # X2Go client... _grant_availability = { 'by_user': None, 'by_group': None, 'by_client': None, } ### CHECKING on a per-client basis... ### clients access is granted first, if that fails then we return False here... if len( _acls['acl-clients-allow'] + _acls['acl-clients-deny'] ) > 0: _acls_clients_allow = copy.deepcopy(_acls['acl-clients-allow']) _acls_clients_deny = copy.deepcopy(_acls['acl-clients-deny']) _allow_client = False _deny_client = False for idx, item in enumerate(_acls_clients_allow): if item == 'ALL': _acls_clients_allow[idx] = '0.0.0.0/0' _acls_clients_allow.insert(idx, '::/0') for idx, item in enumerate(_acls_clients_deny): if item == 'ALL': _acls_clients_deny[idx] = '0.0.0.0/0' _acls_clients_deny.insert(idx, '::/0') _allow_address_set = [] _deny_address_set = [] try: _allow_address_set = netaddr.IPSet(_acls_clients_allow) _deny_address_set = netaddr.IPSet(_acls_clients_deny) except netaddr.core.AddrFormatError as e: logger_error.error('base_broker.X2GoBroker.check_acls(): netaddr.core.AddrFormatError - {why}'.format(why=str(e))) except ValueError as e: logger_error.error('base_broker.X2GoBroker.check_acls(): ValueError - {why}'.format(why=str(e))) _allow_client = self._client_address in _allow_address_set _deny_client = self._client_address in _deny_address_set if _order['clients'] == 'allow-deny': if _allow_client: _grant_availability['by_client'] = True elif _deny_client : _grant_availability['by_client'] = False else: if _deny_client : _grant_availability['by_client'] = False elif _allow_client: _grant_availability['by_client'] = True if _grant_availability['by_client'] is not True: return False ### no user/group ACLs are in use, allow access then... if len(_acls['acl-users-allow'] + _acls['acl-users-deny'] + _acls['acl-groups-allow'] + _acls['acl-groups-deny']) == 0: return True ### CHECKING on a per-user basis... if len( _acls['acl-users-allow'] + _acls['acl-users-deny'] ) > 0: _allow_user = False _deny_user = False if username in _acls['acl-users-allow'] or 'ALL' in _acls['acl-users-allow']: _allow_user = True if username in _acls['acl-users-deny'] or 'ALL' in _acls['acl-users-deny']: _deny_user = True if _order['users'] == 'allow-deny': if _allow_user: _grant_availability['by_user'] = True elif _deny_user : _grant_availability['by_user'] = False else: if _deny_user : _grant_availability['by_user'] = False elif _allow_user: _grant_availability['by_user'] = True # if a user has been granted access directly, then the corresponding session profile(s) # will be provided to him/her, it does not matter what the group acl will have to say to this... if _grant_availability['by_user']: return True ### CHECKING on a per-group basis... if len(_acls['acl-groups-allow'] + _acls['acl-groups-deny']) > 0: _allow_group = False _deny_group = False _user_groups = ['ALL'] + self.get_user_groups(username, primary_groups=not self.get_global_value('ignore-primary-group-memberships')) _allow_group = bool(len(set(_user_groups).intersection( set(_acls['acl-groups-allow']) ))) _deny_group = bool(len(set(_user_groups).intersection( set(_acls['acl-groups-deny']) ))) if _order['groups'] == 'allow-deny': if _allow_group: _grant_availability['by_group'] = True elif _deny_group : _grant_availability['by_group'] = False else: if _deny_group : _grant_availability['by_group'] = False elif _allow_group: _grant_availability['by_group'] = True if _grant_availability['by_group'] and _grant_availability['by_user'] is not False: return True return False def test_connection(self): return 'OK' def _import_authmech_module(self, mech='pam'): try: if self.authmech_module is None: _authmech_module = None namespace = {} exec("import x2gobroker.authmechs.{mech}_authmech as _authmech_module".format(mech=mech), namespace) self.authmech_module = namespace['_authmech_module'] return True except ImportError: return False def _do_authenticate(self, username='', password=''): mech = self.get_authentication_mechanism() logger_broker.debug('base_broker.X2GoBroker._do_authenticate(): attempting authentication, will try "{mech}" mechanism for authenticating the user.'.format(mech=mech)) if self._import_authmech_module(mech=mech): logger_broker.debug('base_broker.X2GoBroker._do_authenticate(): authenticating user={username} with password= against mechanism "{mech}".'.format(username=username, mech=mech)) return self.authmech_module.X2GoBrokerAuthMech().authenticate(username, password, config=self.config) else: return False def get_authentication_mechanism(self): """\ Get the name of the authentication mechanism that is configured for this X2Go Session Broker instance. :returns: auth-mech name :rtype: ``str`` """ _default_auth_mech = "pam" _auth_mech = "" if self.config.has_value('broker_{backend}'.format(backend=self.backend_name), 'auth-mech') and self.config.get_value('broker_{backend}'.format(backend=self.backend_name), 'auth-mech'): _auth_mech = self.config.get_value('broker_{backend}'.format(backend=self.backend_name), 'auth-mech').lower() logger_broker.debug('base_broker.X2GoBroker.get_authentication_mechanism(): found auth-mech in backend config section »{backend}«: {value}. This one has precendence over the default value.'.format(backend=self.backend_name, value=_auth_mech)) elif self.config.has_value('global', 'default-auth-mech'): _default_auth_mech = self.config.get_value('global', 'default-auth-mech').lower() logger_broker.debug('base_broker.X2GoBroker.get_authentication_mechanism(): found default-auth-mech in global config section: {value}'.format(value=_default_auth_mech)) return _auth_mech or _default_auth_mech def _enforce_agent_query_mode(self, mode='LOCAL'): """\ Allow frontends to enforce a certain broker agent backend. :param mode: what agent query mode demanded :type mode: ``str`` :returns: the agent query mode we force the broker to :rtype: ``str`` """ return None def get_agent_query_mode(self, profile_id): """\ Get the agent query mode (LOCAL or SSH, normally) that is configured for this X2Go Session Broker instance. :returns: agent query mode :rtype: ``str`` """ _default_agent_query_mode = "LOCAL" _backend_agent_query_mode = "" _agent_query_mode = "" _profile = self.get_profile_broker(profile_id) if _profile and 'broker-agent-query-mode' in _profile and _profile['broker-agent-query-mode']: _agent_query_mode = _profile['broker-agent-query-mode'] logger_broker.debug('base_broker.X2GoBroker.get_agent_query_mode(): found broker-agent-query-mode in session profile with ID {id}: {value}. This one has precendence over the default and the backend value.'.format(id=profile_id, value=_agent_query_mode)) elif self.config.has_value('broker_{backend}'.format(backend=self.backend_name), 'agent-query-mode') and self.config.get_value('broker_{backend}'.format(backend=self.backend_name), 'agent-query-mode'): _backend_agent_query_mode = self.config.get_value('broker_{backend}'.format(backend=self.backend_name), 'agent-query-mode').lower() logger_broker.debug('base_broker.X2GoBroker.get_agent_query_mode(): found agent-query-mode in backend config section »{backend}«: {value}. This one has precendence over the default value.'.format(backend=self.backend_name, value=_agent_query_mode)) elif self.config.has_value('global', 'default-agent-query-mode') and self.config.get_value('global', 'default-agent-query-mode'): _default_agent_query_mode = self.config.get_value('global', 'default-agent-query-mode').lower() logger_broker.debug('base_broker.X2GoBroker.get_agent_query_mode(): found default-agent-query-mode in global config section: {value}'.format(value=_default_agent_query_mode)) _mode = _agent_query_mode or _backend_agent_query_mode or _default_agent_query_mode # if the frontend overrides the agent query mode, immediately return it here... if self._enforce_agent_query_mode(mode=_mode): _new_mode = self._enforce_agent_query_mode(mode=_mode) logger_broker.debug('base_broker.X2GoBroker.get_agent_query_mode(): broker frontend overrides configured agent query mode ("{mode}"), using mode agent query mode: "{new_mode}".'.format(mode=_mode, new_mode=_new_mode)) return _new_mode else: return _mode def get_agent_hostkey_policy(self, profile_id): """\ Get the agent hostkey policy (either of 'RejectPolicy', 'AutoAddPolicy' or 'WarningPolicy') that is configured for this X2Go Session Broker instance. The returned policy names match the MissingHostkeyPolicy class names as found in Python Paramiko. :returns: agent hostkey policy :rtype: ``str`` """ _default_agent_hostkey_policy = "RejectPolicy" _backend_agent_hostkey_policy = "" _agent_hostkey_policy = "" _profile = self.get_profile_broker(profile_id) if _profile and 'broker-agent-hostkey-policy' in _profile and _profile['broker-agent-hostkey-policy']: _agent_hostkey_policy = _profile['broker-agent-hostkey-policy'] logger_broker.debug('base_broker.X2GoBroker.get_agent_hostkey_policy(): found broker-agent-hostkey-policy in session profile with ID {id}: {value}. This one has precendence over the default and the backend value.'.format(id=profile_id, value=_agent_hostkey_policy)) elif self.config.has_value('broker_{backend}'.format(backend=self.backend_name), 'agent-hostkey-policy') and self.config.get_value('broker_{backend}'.format(backend=self.backend_name), 'agent-hostkey-policy'): _backend_agent_hostkey_policy = self.config.get_value('broker_{backend}'.format(backend=self.backend_name), 'agent-hostkey-policy') logger_broker.debug('base_broker.X2GoBroker.get_agent_hostkey_policy(): found agent-hostkey-policy in backend config section »{backend}«: {value}. This one has precendence over the default value.'.format(backend=self.backend_name, value=_agent_hostkey_policy)) elif self.config.has_value('global', 'default-agent-hostkey-policy') and self.config.get_value('global', 'default-agent-hostkey-policy'): _default_agent_hostkey_policy = self.config.get_value('global', 'default-agent-hostkey-policy') logger_broker.debug('base_broker.X2GoBroker.get_agent_hostkey_policy(): found default-agent-hostkey-policy in global config section: {value}'.format(value=_default_agent_hostkey_policy)) _policy = _agent_hostkey_policy or _backend_agent_hostkey_policy or _default_agent_hostkey_policy if _policy not in ('AutoAddPolicy', 'RejectPolicy', 'WarningPolicy'): logger_broker.warn('base_broker.X2GoBroker.get_agent_hostkey_policy(): given hostkey policy ({policy}) is invalid/unknown, falling back to default hostkey policy ({default_policy}).'.format(policy=_policy, default_policy=_default_agent_hostkey_policy)) _policy = _default_agent_hostkey_policy return _policy def get_session_autologin(self, profile_id): """\ Detect if the given profile is configured to try automatic session logons. :returns: ``True`` to denote that automatic session login should be attempted :rtype: ``bool`` """ _default_session_autologin = False _session_autologin = False _profile = self.get_profile_broker(profile_id) if _profile and 'broker-session-autologin' in _profile and _profile['broker-session-autologin']: _session_autologin = _profile['broker-session-autologin'] if type(_session_autologin) == str: _session_autologin = _session_autologin.lower() in ('1', 'true') logger_broker.debug('base_broker.X2GoBroker.get_session_autologin(): found broker-session-autologin in session profile with ID {id}: {value}. This one has precendence over the default value.'.format(id=profile_id, value=_session_autologin)) elif self.config.has_value('global', 'default-session-autologin'): _default_session_autologin = self.config.get_value('global', 'default-session-autologin') logger_broker.debug('base_broker.X2GoBroker.get_session_autologin(): found default-session-autologin in global config section: {value}'.format(value=_default_session_autologin)) return _session_autologin or _default_session_autologin # API compat name: use_session_autologin = get_session_autologin def get_portscan_x2goservers(self, profile_id): """\ Detect if the given profile is configured to try portscanning on X2Go Servers before offering an X2Go Server hostname to the client. :returns: ``True`` if X2Go Servers shall be probed before offering it to clients :rtype: ``bool`` """ _default_portscan_x2goservers = False _portscan_x2goservers = False _profile = self.get_profile_broker(profile_id) if _profile and 'broker-portscan-x2goservers' in _profile and _profile['broker-portscan-x2goservers']: _portscan_x2goservers = _profile['broker-portscan-x2goservers'] if type(_portscan_x2goservers) == str: _portscan_x2goservers = _portscan_x2goservers.lower() in ('1', 'true') logger_broker.debug('base_broker.X2GoBroker.get_portscan_x2goservers(): found broker-portscan-x2goservers in session profile with ID {id}: {value}. This one has precendence over the default value.'.format(id=profile_id, value=_portscan_x2goservers)) elif self.config.has_value('global', 'default-portscan-x2goservers'): _default_portscan_x2goservers = self.config.get_value('global', 'default-portscan-x2goservers') logger_broker.debug('base_broker.X2GoBroker.get_portscan_x2goservers(): found default-portscan-x2goservers in global config section: {value}'.format(value=_default_portscan_x2goservers)) return _portscan_x2goservers or _default_portscan_x2goservers # API compat name: use_portscan_x2goservers = get_portscan_x2goservers def get_authorized_keys_file(self, profile_id): """\ Get the default location of server-side authorized_keys files used with the X2Go Session Broker. The file location can be configured broker-wide. It is also possible to provide a broker-authorized-keys file in session profiles. The latter will override the broker-wide conigured file location. :returns: authorized_keys location on the remote server :rtype: ``str`` """ _default_authorized_keys_file = "%h/.x2go/authorized_keys" _authorized_keys_file = "" _profile = self.get_profile_broker(profile_id) if _profile and 'broker-authorized-keys' in _profile and _profile['broker-authorized-keys']: _authorized_keys_file = _profile['broker-authorized-keys'] logger_broker.debug('base_broker.X2GoBroker.get_authorized_keys_file(): found broker-authorized-keys in session profile with ID {id}: {value}. This one has precendence over the default value.'.format(id=profile_id, value=_authorized_keys_file)) elif self.config.has_value('global', 'default-authorized-keys'): _default_authorized_keys_file = self.config.get_value('global', 'default-authorized-keys') logger_broker.debug('base_broker.X2GoBroker.get_authorized_keys_file(): found default-authorized-keys in global config section: {value}'.format(value=_default_authorized_keys_file)) return _authorized_keys_file or _default_authorized_keys_file def get_sshproxy_authorized_keys_file(self, profile_id): """\ Get the default location of SSH proxy server-side authorized_keys files used with the X2Go Session Broker. The file location can be configured broker-wide. It is also possible to provide a broker-authorized-keys file in session profiles. The latter will override the broker-wide conigured file location. :returns: authorized_keys location on the remote SSH proxy server :rtype: ``str`` """ _default_authorized_keys_file = "%h/.x2go/authorized_keys" _authorized_keys_file = "" _profile = self.get_profile_broker(profile_id) if _profile and 'broker-sshproxy-authorized-keys' in _profile and _profile['broker-sshproxy-authorized-keys']: _authorized_keys_file = _profile['broker-sshproxy-authorized-keys'] logger_broker.debug('base_broker.X2GoBroker.get_sshproxy_authorized_keys_file(): found broker-sshproxy-authorized-keys in session profile with ID {id}: {value}. This one has precendence over the default value.'.format(id=profile_id, value=_authorized_keys_file)) elif self.config.has_value('global', 'default-sshproxy-authorized-keys'): _default_authorized_keys_file = self.config.get_value('global', 'default-sshproxy-authorized-keys') logger_broker.debug('base_broker.X2GoBroker.get_sshproxy_authorized_keys_file(): found default-sshproxy-authorized-keys in global config section: {value}'.format(value=_default_authorized_keys_file)) return _authorized_keys_file or _default_authorized_keys_file def get_userdb_service(self): """\ Get the name of the backend being used for retrieving user information from the system. :returns: user service name :rtype: ``str`` """ _user_db = "libnss" if self.config.has_value('global', 'default-user-db'): _user_db = self.config.get_value('global', 'default-user-db').lower() or _user_db if self.config.has_value('broker_{backend}'.format(backend=self.backend_name), 'user-db'): _user_db = self.config.get_value('broker_{backend}'.format(backend=self.backend_name), 'user-db').lower() or _user_db return _user_db def get_groupdb_service(self): """\ Get the name of the backend being used for retrieving group information from the system. :returns: group service name :rtype: ``str`` """ _group_db = "libnss" if self.config.has_value('global', 'default-group-db'): _group_db = self.config.get_value('global', 'default-group-db').lower() or _group_db if self.config.has_value('broker_{backend}'.format(backend=self.backend_name), 'group-db'): _group_db = self.config.get_value('broker_{backend}'.format(backend=self.backend_name), 'group-db').lower() or _group_db return _group_db def get_use_load_checker(self): """\ Is this broker backend configured to access an X2Go Broker LoadChecker daemon. :returns: ``True`` if there should a load checker daemon running. :rtype: ``bool`` """ _use_load_checker = False if self.config.has_value('global', 'default-use-load-checker'): _use_load_checker = self.config.get_value('global', 'default-use-load-checker') or _use_load_checker if self.config.has_value('broker_{backend}'.format(backend=self.backend_name), 'use-load-checker'): _use_load_checker = self.config.get_value('broker_{backend}'.format(backend=self.backend_name), 'use-load-checker') or _use_load_checker return _use_load_checker def use_load_checker(self, profile_id): """\ Actually query the load checker daemon for the given session profile ID. This method will check: - broker backend configured per backend or globally to use load checker daemon? - or on a per session profile basis? - plus: more than one host configured for the given session profile? :param profile_id: choose remote agent for this profile ID :type profile_id: ``str`` :returns: ``True`` if there is a load checker daemon running. :rtype: ``bool`` """ _profile_broker = self.get_profile_broker(profile_id) if not _profile_broker: return False _profile_session = self.get_profile(profile_id) # only use load checker if... # more than one host is defined in the session profile if len(_profile_session['host']) < 2: return False # if not blocked on a per session profile basis if 'broker-use-load-checker' in _profile_broker and _profile_broker['broker-use-load-checker'] not in ('1', 'true', 'TRUE', 'True'): return False # if load checking is enabled globally, for the broker backend, # or for the given session profile... if self.get_use_load_checker() or ('broker-use-load-checker' in _profile_broker and _profile_broker['broker-use-load-checker'] in ('1', 'true', 'TRUE', 'True')): return True return False def _import_nameservice_module(self, service='libnss'): try: if self.nameservice_module is None: _nameservice_module = None namespace = {} exec("import x2gobroker.nameservices.{service}_nameservice as _nameservice_module".format(service=service), namespace) self.nameservice_module = namespace['_nameservice_module'] return True except ImportError: return False def has_user(self, username): """\ Test if the broker knows user ````. :param username: test for existence of this user :type username: ``str`` :returns: returns ``True`` if a user exists :rtype: ``bool`` """ if self._import_nameservice_module(service=self.get_userdb_service()): return self.nameservice_module.X2GoBrokerNameService().has_user(username=username) else: return False def get_users(self): """\ Get list of known users. :returns: returns list of known users :rtype: ``list`` """ if self._import_nameservice_module(service=self.get_userdb_service()): return self.nameservice_module.X2GoBrokerNameService().get_users() else: return False def has_group(self, group): """\ Test if the broker knows group ````. :param group: test for existence of this group :type group: ``str`` :returns: returns ``True`` if a group exists :rtype: ``bool`` """ if self._import_nameservice_module(service=self.get_groupdb_service()): return self.nameservice_module.X2GoBrokerNameService().has_group(group=group) else: return False def get_groups(self): """\ Get list of known groups. :returns: returns list of known groups :rtype: ``list`` """ if self._import_nameservice_module(service=self.get_groupdb_service()): return self.nameservice_module.X2GoBrokerNameService().get_groups() else: return False def get_primary_group(self, username): """\ Get the primary group of a given user. :param username: get primary group for this username :type username: ``str`` :returns: returns the name of the primary group :rtype: ``str`` """ if self._import_nameservice_module(service=self.get_groupdb_service()): return self.nameservice_module.X2GoBrokerNameService().get_primary_group(username) else: return False def is_group_member(self, username, group, primary_groups=False): """\ Check if a user is member of a given group. :param username: check group membership of this user :type username: ``str`` :param group: test if user is member of this group :type group: ``str`` :param primary_groups: if ``True``, test for primary group membership, as well :type primary_groups: ``bool`` :returns: returns ``True`` if the user is member of the given group :rtype: ``bool`` """ if self._import_nameservice_module(service=self.get_groupdb_service()): return self.nameservice_module.X2GoBrokerNameService().is_group_member(username=username, group=group, primary_groups=primary_groups) else: return [] def get_group_members(self, group, primary_groups=False): """\ Get the list of members in group ````. :param group: valid group name :type group: ``str`` :param primary_groups: include primary groups found with the user db service :type primary_groups: ``bool`` :returns: list of users belonging to the given group :rtype: ``list`` """ if self._import_nameservice_module(service=self.get_groupdb_service()): return self.nameservice_module.X2GoBrokerNameService().get_group_members(group=group, primary_groups=primary_groups) else: return [] def get_user_groups(self, username, primary_groups=False): """\ Get all groups a given user is member of. :param username: get groups for this user :type username: ``str`` :param primary_groups: if ``True``, include the user's primary group in the group list :type primary_groups: ``bool`` :returns: list of groups the given user is member of :rtype: ``list`` """ if self._import_nameservice_module(service=self.get_groupdb_service()): return self.nameservice_module.X2GoBrokerNameService().get_user_groups(username=username, primary_groups=primary_groups) else: return [] def check_access(self, username='', password='', ip='', cookie=None, override_password_auth=False): """\ Check if a given user with a given password may gain access to the X2Go session broker. :param username: a username known to the session broker :type username: ``str`` :param password: a password that authenticates the user against the X2Go session broker :type password: ``str`` :param ip: the ip address of the client :type ip: ``str`` :param cookie: an extra (static or dynamic) authentication token :type cookie: ``str`` :param override_password_auth: let password auth always succeed, needed for SSH broker (where SSH handled the password (or key) based authentication :type override_password_auth: ``bool`` :returns: returns ``True`` if the authentication has been successful :rtype: ``bool``,``str`` """ require_password = self.config.get_value('global', 'require-password') require_cookie = self.config.get_value('global', 'require-cookie') # LEGACY support for X2Go Session Broker (<< 0.0.3.0) configuration files if not self.config.get_value('global', 'check-credentials'): logger_broker.warning('base_broker.X2GoBroker.check_access(): deprecated parameter \'check-credentials\' used in x2gobroker.conf (use \'require-password\' and \'require-cookie\' instead)!!!'.format(configfile=self.config_file)) require_password = False require_cookie = False ### FOR INTRANET LOAD BALANCER WE MAY JUST ALLOW ACCESS TO EVERYONE ### This is handled through the config file, normally /etc/x2go/x2gobroker.conf if not require_password and not require_cookie: logger_broker.debug('base_broker.X2GoBroker.check_access(): access is granted without checking credentials, prevent this in {configfile}'.format(configfile=self.config_file)) return True, None elif username == 'check-credentials' and password == 'FALSE': # this catches a validation check from the UCCS web frontend... return False, None ### IMPLEMENT YOUR AUTHENTICATION LOGIC IN THE self._do_authenticate(**kwargs) METHOD ### when inheriting from the x2gobroker.brokers.base_broker.X2GoBroker class. if type(cookie) is bytes: cookie = cookie if (((cookie == None) or (cookie == "")) and require_cookie): #cookie required but we did not get one - catch wrong cookie case later logger_broker.debug('base_broker.X2GoBroker.check_access(): cookie required but none given.') return False, None # check if cookie sent was our preset cookie from config file next_cookie = self.get_my_cookie() access = (cookie == next_cookie ) logger_broker.debug('base_broker.X2GoBroker.check_access(): checking if our configured cookie was submitted: {access}'.format(access=access)) # the require cookie but not password case falls through to returning value of access if require_password: # using files to store persistant cookie information because global variables do not work across threads in WSGI if _X2GOBROKER_USER == _X2GOBROKER_DAEMON_USER: cookie_directory = self.config.get_value('global', 'cookie-directory') cookie_directory = os.path.normpath(cookie_directory) else: cookie_directory=os.path.normpath(os.path.expanduser('~/.x2go/broker-cookies/')) if (not os.path.isdir(cookie_directory)): logger_broker.debug('base_broker.X2GoBroker.check_access(): cookie-directory {cookie_directory} does not exist trying to create it'.format(cookie_directory=cookie_directory)) try: os.makedirs(cookie_directory); except: logger_broker.warning('base_broker.X2GoBroker.check_access(): could not create cookie-directory {cookie_directory} failing to authenticate'.format(cookie_directory=cookie_directory)) return False, None if access or cookie == None or cookie == "": # this should be the first time we have seen this user or they are using old client so verify their passwrd ### IMPLEMENT YOUR AUTHENTICATION LOGIC IN THE self._do_authenticate(**kwargs) METHOD ### when inheriting from the x2gobroker.brokers.base_brokers.X2GoBroker class. access = self._do_authenticate(username=username, password=password) or override_password_auth ### ### if access: logger_broker.warning('base_broker.X2GoBroker.check_access(): authentication succeeded for user {username} at ip {ip}'.format(username=username, ip=ip)) #create new cookie for this user #each user gets one or more tuples of IP, time stored as username_UUID files so they can connect from multiple sessions next_cookie = str(uuid.uuid4()) if cookie_directory and username and next_cookie: fh = open(cookie_directory+"/"+username+"_"+next_cookie,"w") fh.write('{ip} {time}'.format(ip=ip, time=time.time())) fh.close() if cookie_directory and username and cookie: os.remove(cookie_directory+"/"+username+"_"+cookie) logger_broker.debug('base_broker.X2GoBroker.check_access(): Giving new cookie: {cookie} to user {username} at ip {ip}'.format(cookie=next_cookie,username=username,ip=ip)) else: logger_broker.warning('base_broker.X2GoBroker.check_access(): authentication failed for user {username} at ip {ip}'.format(username=username, ip=ip)) else: # there is a cookie but its not ours so its either wrong or subsequent password auth if os.path.isfile(cookie_directory+"/"+username+"_"+cookie): logger_broker.debug('base_broker.X2GoBroker.check_access(): found valid auth key for user cookie: {usercookie}'.format(usercookie=username+"_"+cookie)) fh=open(cookie_directory+"/"+username+"_"+cookie,"r") origip,origtime= fh.read().split() fh.close() os.unlink(cookie_directory+"/"+username+"_"+cookie) # found cookie - make sure IP and time are good if self.config.get_value('global', 'verify-ip') and (ip != origip): logger_broker.debug('base_broker.X2GoBroker.check_access(): IPs differ (new: {ip} old: {origip}) - rejecting user'.format(ip=ip,origip=origip)) return False, None if (time.time() - float(origtime)) > self.config.get_value('global', 'auth-timeout'): logger_broker.debug('base_broker.X2GoBroker.check_access(): Too much time elapsed since origional auth - rejecting user') return False, None if self.config.get_value('global', 'use-static-cookie'): #if using static cookies keep same cookie as user presented next_cookie = cookie else: #otherwise give them new random cookie next_cookie = str(uuid.uuid4()) logger_broker.debug('base_broker.X2GoBroker.check_access(): Giving cookie: {cookie} to ip {ip}'.format(cookie=next_cookie, ip=ip)) fh = open(cookie_directory+"/"+username+"_"+next_cookie,"w") fh.write('{ip} {time}'.format(ip=ip, time=origtime)) fh.close() access = True else: # FIXME: here we need some magic to remove deprecated cookie files (by their timestamp)!!! # client sent us an unknown cookie so failing auth logger_broker.debug('base_broker.X2GoBroker.check_access(): User {username} from {ip} presented cookie {cookie} which is not recognized - rejecting user'.format(username=username, cookie=cookie, ip=ip)) return False, None return access, next_cookie def get_remote_agent(self, profile_id, exclude_agents=[], ): """\ Randomly choose a remote agent for agent query. :param profile_id: choose remote agent for this profile ID :type profile_id: ``str`` :param exclude_agents: a list of remote agent dict objects to be exclude from the random choice :type exclude_agents: ``list`` :returns: remote agent to use for queries for profile ID :rtype: ``dict`` """ remote_agent = None # no remote agent needed for shadow sessions if self.is_shadow_profile(profile_id): return remote_agent agent_query_mode = self.get_agent_query_mode(profile_id).upper() if agent_query_mode == 'SSH' and x2gobroker.agent.has_remote_broker_agent_setup(): profile = self.get_profile(profile_id) server_list = profile['host'] random.shuffle(server_list) # if the load checker is in use for this profile, let's retrieve the available server loads here # because: # - it is fast... # - if hosts are marked as "HOST-UNREACHABLE", we don't have to attempt # using them as a remote agent (reduce delays at session # startup/resumption) # - the retrieved load factors can be re-used in X2GoBroker.select_session(). load_factors = {} if self.use_load_checker(profile_id): load_factors = x2gobroker.loadchecker.check_load(self.backend_name, profile_id) for h in [ _h for _h in list(load_factors.keys()) if type(load_factors[_h]) != int ]: if h in server_list: server_list.remove(h) for agent in exclude_agents: if agent['hostname'] in server_list: server_list.remove(agent['hostname']) while server_list: remote_agent_hostname = server_list[-1] remote_agent_hostaddr = remote_agent_hostname remote_agent_port = profile['sshport'] if 'sshport={hostname}'.format(hostname=remote_agent_hostname) in profile: remote_agent_port = profile["sshport={hostname}".format(hostname=remote_agent_hostname)] if 'host={hostname}'.format(hostname=remote_agent_hostname) in profile: remote_agent_hostaddr = profile["host={hostname}".format(hostname=remote_agent_hostname)] remote_agent = { 'hostname': remote_agent_hostname, 'hostaddr': remote_agent_hostaddr, 'port': remote_agent_port, 'host_key_policy': self.get_agent_hostkey_policy(profile_id), } try: if x2gobroker.agent.ping(remote_agent=remote_agent): break except x2gobroker.x2gobroker_exceptions.X2GoBrokerAgentException: # at the end of this loop, an empty dict means: no X2Go Server could be contacted!!! remote_agent = {} server_list = server_list[0:-1] if not remote_agent: logger_broker.warning('base_broker.X2GoBroker.get_remote_agent(): failed to allocate any broker agent (query-mode: {query_mode}, remote_agent: {remote_agent})'.format(query_mode=agent_query_mode, remote_agent=remote_agent)) else: # ship the load_factors retrieved from the load checker service in the remote_agent dict remote_agent['load_factors'] = load_factors elif agent_query_mode == 'LOCAL': # use a non-False value here, not used anywhere else... remote_agent = 'LOCAL' return remote_agent def get_all_remote_agents(self, profile_id): """\ Get all remote agents. :param profile_id: choose remote agent for this profile ID :type profile_id: ``str`` :returns: ``list`` of remote agents for the given profile ID :rtype: ``list`` """ remote_agents = [] # no remote agent needed for shadow sessions if self.is_shadow_profile(profile_id): return remote_agents agent_query_mode = self.get_agent_query_mode(profile_id).upper() if agent_query_mode == 'SSH' and x2gobroker.agent.has_remote_broker_agent_setup(): profile = self.get_profile(profile_id) server_list = profile['host'] while server_list: remote_agent_hostname = server_list[-1] remote_agent_hostaddr = remote_agent_hostname remote_agent_port = profile['sshport'] if 'sshport={hostname}'.format(hostname=remote_agent_hostname) in profile: remote_agent_port = profile["sshport={hostname}".format(hostname=remote_agent_hostname)] if 'host={hostname}'.format(hostname=remote_agent_hostname) in profile: remote_agent_hostaddr = profile["host={hostname}".format(hostname=remote_agent_hostname)] remote_agents.append({ 'hostname': remote_agent_hostname, 'hostaddr': remote_agent_hostaddr, 'port': remote_agent_port, } ) server_list = server_list[0:-1] return remote_agents def is_shadow_profile(self, profile_id): """\ Detect from the session profile, if it defines a desktop sharing (shadow) session. :param profile_id: ID of a valid session profile :type profile_id: ``str`` :returns: ``True`` if the session profile defines a desktop sharing (shadow) session :rtype: ``bool`` """ profile = self.get_profile(profile_id) return profile['command'] == "SHADOW" def check_for_sessions(self, profile_id): """\ Detect from the session profile, if we should query the remote broker agent for running or suspended sessions. :param profile_id: ID of a valid session profile :type profile_id: ``str`` :returns: ``True`` if the remote broker agent should be queried for running/suspended sessions :rtype: ``bool`` """ do_check = True # do check, for all commands except the "SHADOW" command do_check = do_check and not self.is_shadow_profile(profile_id) return do_check def get_profile_for_user(self, profile_id, username, broker_frontend=None): """\ Expect a profile id and perform some checks and preparations to make it ready for exporting to a broker client: - drop internal host= and sshport= keys from the profile, broker clients cannot handle those - drop keys with value "not-set" - replace BROKER_USER by the name of the authenticated user - test if autologin is possible - fix rootless session profile option for non-desktop sessions - perform an ACL check (return ``None`` if it fails) - query a remote agent (if configured) to check if we have running / suspended sessions on the remote X2Go Server :param profile_id: ID of a valid session profile :type profile_id: ``str`` :param username: prepare session profile for this (authenticated) user :type username: ``str`` :param broker_frontend: some broker frontend (e.g. UCCS) require special treatment by this method :type broker_frontend: ``str`` :returns: session profile as a dictionary (ready for sending out to a broker client) :rtype: ``dict`` """ profile = self.get_profile(profile_id) acls = self.get_profile_acls(profile_id) if self.check_profile_acls(username, acls): for key in list(copy.deepcopy(profile).keys()): if profile[key] == "not-set": del profile[key] continue if key.startswith('host=') and broker_frontend != 'uccs': del profile[key] if key.startswith('sshport=') and broker_frontend != 'uccs': del profile[key] if key == 'user' and profile[key] == 'BROKER_USER': profile[key] = username if self.get_session_autologin(profile_id): profile['autologin'] = True profile['key'] = '' # make sure that desktop sessions (that we know by name) do run with rootless=false # and that the command string is always upper case (otherwise x2goruncommand might # stumble over it...) if profile['command'].upper() in x2gobroker.defaults.X2GO_DESKTOP_SESSIONS: profile['rootless'] = False profile['command'] = profile['command'].upper() remote_agent = self.get_remote_agent(profile_id) if self.check_for_sessions(profile_id): if remote_agent: try: success, running_sessions, suspended_sessions = x2gobroker.agent.has_sessions(username, remote_agent=remote_agent) if running_sessions: logger_broker.debug('base_broker.X2GoBroker.get_profile_for_user(): found running sessions on host(s): {hosts}'.format(hosts=', '.join(running_sessions))) if suspended_sessions: logger_broker.debug('base_broker.X2GoBroker.get_profile_for_user(): found running sessions on host(s): {hosts}'.format(hosts=', '.join(suspended_sessions))) suspended_matching_hostnames = x2gobroker.utils.matching_hostnames(profile['host'], suspended_sessions) running_matching_hostnames = x2gobroker.utils.matching_hostnames(profile['host'], running_sessions) if suspended_matching_hostnames: profile['status'] = 'S' profile['host'] = [suspended_matching_hostnames[0]] elif running_matching_hostnames: profile['status'] = 'R' profile['host'] = [running_matching_hostnames[0]] else: profile['host'] = [profile['host'][0]] if 'status' in profile and profile['status']: logger_broker.debug('base_broker.X2GoBroker.get_profile_for_user(): marking session profile {name} as {status}'.format(name=profile['name'], status=profile['status'])) except x2gobroker.x2gobroker_exceptions.X2GoBrokerAgentException as e: logger_broker.warning('base_broker.X2GoBroker.get_profile_for_user(): broker agent call failed. Error message is: {errmsg}'.format(errmsg=str(e))) else: profile['host'] = [profile['host'][0]] return profile else: return None def list_profiles(self, username): """\ Retrieve a list of available session profiles for the authenticated user. :param username: query session profile list for this user :type username: ``str`` :returns: list of profile dictionaries :rtype: ``dict`` """ list_of_profiles = {} for profile_id in self.get_profile_ids_for_user(username): profile = self.get_profile_for_user(profile_id, username) if profile: list_of_profiles.update({profile_id: profile, }) return list_of_profiles def select_session(self, profile_id, username=None, pubkey=None): """\ Start/resume a session by selecting a profile name offered by the X2Go client. The X2Go server that the session is launched on is selected automatically by the X2Go session broker. :param profile_id: the selected profile ID. This matches one of the dictionary keys offered by the ``list_profiles`` method :type profile_id: ``str`` :param username: specify X2Go Server username that this operation runs for :type username: ``str`` :param pubkey: The broker clients may send us a public key that we may temporarily install into a remote X2Go Server for non-interactive login :type pubkey: ``str`` :returns: the seclected session (X2Go session ID) :rtype: ``str`` """ try: profile = self.get_profile(profile_id) except x2gobroker.x2gobroker_exceptions.X2GoBrokerProfileException: return { 'server': 'no-server-available', 'port': 22, } # if we have more than one server, pick one server randomly for X2Go Broker Agent queries server_list = profile['host'] if len(server_list) == 0: return { 'server': 'no-server-available', 'port': profile['sshport'], } # if everything below fails, this will be the X2Go Server's hostname that # we will connect to... server_name = server_list[0] server_port = profile['sshport'] # try to retrieve a remote broker agent remote_agent = self.get_remote_agent(profile_id) # check for already running sessions for the given user (if any is given) session_list = [] if remote_agent and username: try: success, session_list = x2gobroker.agent.list_sessions(username=username, remote_agent=remote_agent) except x2gobroker.x2gobroker_exceptions.X2GoBrokerAgentException: session_list = [] session_info = None selected_session = {} busy_servers = None _save_server_list = None _save_busy_servers = None initial_server_list = copy.deepcopy(server_list) agent_query_mode_is_SSH = self.get_agent_query_mode(profile_id).upper() == 'SSH' while not selected_session and server_list: # X2Go Server uses the system's hostname, so let's replace # that here automatically, if we tested things via localhost... for h in server_list: if h == 'localhost': server_list.remove(h) server_list.append(socket.gethostname()) matching_server_names = None if session_list: matching_server_names = x2gobroker.utils.matching_hostnames(server_list, [ si.split('|')[3] for si in session_list ]) if remote_agent == {}: # we failed to contact any remote agent, so it is very likely, that all servers are down... server_list = [] elif session_list and matching_server_names: # Obviously a remote broker agent reported an already running session # on the / on one the available X2Go Server host(s) # When resuming, always select the first session in the list, # there should only be one running/suspended session by design # of X2Go brokerage (this may change in the future) try: running_sessions = [] suspended_sessions = [] for session_info in session_list: if session_info.split('|')[3] in matching_server_names: if session_info.split('|')[4] == 'R': running_sessions.append(session_info) if session_info.split('|')[4] == 'S': suspended_sessions.append(session_info) if suspended_sessions or running_sessions: # we prefer suspended sessions over resuming sessions if we find sessions with both # states of activity if suspended_sessions: session_info = suspended_sessions[0] elif running_sessions: session_info = running_sessions[0] x2gobroker.agent.suspend_session(username=username, session_name=session_info.split('|')[1], remote_agent=remote_agent) # this is the turn-around in x2gocleansessions, so waiting as along as the daemon # that will suspend the session time.sleep(2) session_info = session_info.replace('|R|', '|S|') # only use the server's official hostname (as set on the server) # if we have been provided with a physical server address. # If no physical server address has been provided, we have to use # the host address as found in server_list (and hope we can connect # to that address. _session_server_name = session_info.split('|')[3] if 'host={server_name}'.format(server_name=_session_server_name) in profile: server_name = _session_server_name elif _session_server_name in server_list: server_name = _session_server_name elif x2gobroker.utils.matching_hostnames(server_list, [_session_server_name]): for _server_name in server_list: if _server_name.startswith(_session_server_name): server_name = _server_name break else: logger_broker.error('base_broker.X2GoBroker.select_session(): configuration error. Hostnames in session profile and actual server names do not match, we won\'t be able to resume/take-over a session this time') # choosing a random server from the server list, to end up anywhere at least... server_name = random.choice(server_list) except IndexError: # FIXME: if we get here, we have to deal with a broken session info # entry in the X2Go session database. -> AWFUL!!! pass # detect best X2Go server for this user if load balancing is configured elif remote_agent and len(server_list) >= 2 and username: # No running / suspended session was found on any of the available # X2Go Servers. Thus, we will try to detect the best server for this # load balanced X2Go Server farm. # query remote agent on how busy our servers are... (if a selected server is down # and we come through here again, don't query business state again, use the remembered # status) if busy_servers is None: try: if agent_query_mode_is_SSH and remote_agent['load_factors']: success, busy_servers = x2gobroker.agent.get_servers(username=username, remote_agent=remote_agent) else: success, busy_servers = x2gobroker.agent.find_busy_servers(username=username, remote_agent=remote_agent) except x2gobroker.x2gobroker_exceptions.X2GoBrokerAgentException: pass if busy_servers is not None: # if we do not get here, we failed to query a valid agent... # when detecting the server load we have to support handling of differing subdomains (config # file vs. server load returned by x2gobroker agent). Best approach: all members of a multi-node # server farm either # # (a) do not have a subdomain in their hostname or # (b) have an identical subdomain in their hostnames # Example: # # ts01, ts02 - hostnames as returned by agent # ts01.intern, ts02.intern - hostnames configured in session profile option ,,host'' # -> this will result in the subdomain .intern being stripped off from the hostnames before # detecting the best server for this user ### NORMALIZE (=reduce to hostname only) X2Go server names (as found in config) if possible server_list_normalized, subdomains_config = x2gobroker.utils.normalize_hostnames(server_list) ### NORMALIZE X2Go server names (as returned by broker agent)--only if the hostnames in # the config share the same subdomain if len(subdomains_config) == 1: busy_servers_normalized, subdomains_agent = x2gobroker.utils.normalize_hostnames(busy_servers) if len(subdomains_agent) <= 1: # all X2Go servers in the multi-node server farm are in the same DNS subdomain # we can operate on hostname-only hostnames _save_server_list = copy.deepcopy(server_list) _save_busy_servers = copy.deepcopy(busy_servers) server_list = server_list_normalized busy_servers = busy_servers_normalized # the list of busy_servers only shows servers with sessions, but not those servers that are entirely idle... for server in server_list: if server not in list(busy_servers.keys()): busy_servers[server] = 0 # we will only contact servers that are (still) in server_list for busy_server in list(busy_servers.keys()): if busy_server not in server_list: del busy_servers[busy_server] # dynamic load-balancing via load checker service if agent_query_mode_is_SSH and remote_agent['load_factors']: load_factors = remote_agent['load_factors'] busy_servers_temp = copy.deepcopy(busy_servers) for busy_server in list(busy_servers_temp.keys()): if busy_server in list(load_factors.keys()) and type(load_factors[busy_server]) is not int: # if a host cannot report its load, let's ignore it... del busy_servers_temp[busy_server] elif busy_server in list(load_factors.keys()) and ( type(load_factors[busy_server]) is int or busy_servers[busy_server] == 0): # when using the load checker service, then busy_servers contains the number of sessions per host # do the load-factor / numSessions calculation here... (avoid divison-by-zero by adding +1 to # the number of sessions here) busy_servers_temp[busy_server] = 1.0 / (load_factors[busy_server] / ( busy_servers[busy_server] +1)) else: # ignore the load checker, results are garbage... busy_servers_temp = None break if busy_servers_temp is not None: busy_servers = copy.deepcopy(busy_servers_temp) busy_server_list = [ (load, server) for server, load in list(busy_servers.items()) ] busy_server_list.sort() logger_broker.debug('base_broker.X2GoBroker.select_session(): load balancer analysis: {server_load}'.format(server_load=busy_server_list)) server_name = busy_server_list[0][1] # this makes sure we allow back-translation of hostname to host address # when the format " ()" ist used in the hosts field... if len(subdomains_config) == 1: server_name += '.{domain}'.format(domain=subdomains_config[0]) if _save_server_list: server_list = copy.deepcopy(_save_server_list) _save_server_list = None if _save_busy_servers: busy_servers = copy.deepcopy(_save_busy_servers) _save_busy_servers = None else: logger_broker.warning('base_broker.X2GoBroker.select_session(): no broker agent could be contacted, this does not look good. We tried these agent hosts: {agent_hosts}'.format(agent_hosts=initial_server_list)) # detect best X2Go server for this user if load balancing is configured elif len(server_list) >= 2: if self.is_shadow_profile(profile_id): # we will ignore load-balancing for desktop sharing profiles server_list = [server_list[0]] server_name = server_list[0] else: # no remote broker agent or no username? Let's play roulette then... server_name = random.choice(server_list) ### ### by now we should know the proper host to connect to... ### server_addr = server_name # if we have an explicit TCP/IP port server_name, let's use that instead... try: server_port = profile['sshport={hostname}'.format(hostname=server_name)] logger_broker.debug('base_broker.X2GoBroker.select_session(): use physical server port: {port}'.format(port=server_port)) except KeyError: pass # if we have an explicit TCP/IP address for server_name, let's use that instead... try: server_addr = profile['host={hostname}'.format(hostname=server_name)] logger_broker.debug('base_broker.X2GoBroker.select_session(): use physical server address: {address}'.format(address=server_addr)) except KeyError: pass if server_list: if not self.get_portscan_x2goservers(profile_id) or x2gobroker.utils.portscan(addr=server_name, port=server_port) or x2gobroker.utils.portscan(addr=server_addr, port=server_port): selected_session = { 'server': server_addr, 'port': server_port, } else: server_list.remove(server_name) # pick remaining server from server list (if any) if server_list: logger_broker.warning('base_broker.X2GoBroker.select_session(): failed to contact host \'{down_server}\', trying next server \'{next_server}\''.format(down_server=server_name, next_server=server_list[0])) server_name = server_list[0] else: logger_broker.error('base_broker.X2GoBroker.select_session(): no X2Go Server could be contacted, session startup will fail, tried these hosts: {server_list}'.format(server_list=initial_server_list)) # If we arrive here and session_list carries an entry for this user, then the session DB probably still # carries a zombie session entry (that will disappear when the down X2Go Server comes up again (cleanup # via x2gocleansessions). # # Thus, let's ignore this session and check if there is another appropriate session in session_list if session_info is not None: session_list.remove(session_info) session_info = None if not selected_session and not server_list: if len(initial_server_list) > 1: selected_session = { 'server': 'no-X2Go-Server-available', 'port': server_port, } else: # hand-over the original hostname for non-load-balanced session profiles failed_server_port = server_port failed_server_name = initial_server_list[0] try: failed_server_port = profile['port={hostname}'.format(hostname=failed_server_name)] except KeyError: pass try: failed_server_name = profile['host={hostname}'.format(hostname=failed_server_name)] except KeyError: pass selected_session = { 'server': failed_server_name, 'port': failed_server_port, } # are we resuming a running/suspended session? if session_info is not None: selected_session['session_info'] = session_info # define a remote SSH proxy agent if an SSH proxy host is used with this session profile if 'sshproxyhost' in profile and profile['sshproxyhost']: remote_sshproxy_agent = { 'hostname': profile['sshproxyhost'], 'hostaddr': profile['sshproxyhost'], 'port': "22" } if 'sshproxyport' in profile and profile['sshproxyport']: remote_sshproxy_agent['port'] = profile['sshproxyport'] else: remote_sshproxy_agent = None # session autologin feature if remote_agent and self.get_session_autologin(profile_id) and username: # let's use the chosen server_name if remote_agent is reachable via SSH if type(remote_agent) is dict: remote_agent = { 'hostname': server_name, 'hostaddr': server_addr, 'port': selected_session['port'], } if not pubkey: # if the broker client has not provided a public SSH key, we will generate one # this is the OLD style of the auto login feature # FIXME: we somehow have to find out about the username of the person at the broker client-side... # using the username used for server login for now... pubkey, privkey = x2gobroker.agent.genkeypair(local_username=username, client_address=self.get_client_address()) if remote_sshproxy_agent is not None: x2gobroker.agent.add_authorized_key(username=username, pubkey_hash=pubkey, authorized_keys_file=self.get_sshproxy_authorized_keys_file(profile_id), remote_agent=remote_sshproxy_agent, ), x2gobroker.agent.add_authorized_key(username=username, pubkey_hash=pubkey, authorized_keys_file=self.get_authorized_keys_file(profile_id), remote_agent=remote_agent, ), selected_session.update({ 'authentication_privkey': privkey, }) if remote_sshproxy_agent is not None: x2gobroker.agent.delete_authorized_key(username=username, pubkey_hash=pubkey, authorized_keys_file=self.get_sshproxy_authorized_keys_file(profile_id), remote_agent=remote_sshproxy_agent, delay_deletion=20, ) x2gobroker.agent.delete_authorized_key(username=username, pubkey_hash=pubkey, authorized_keys_file=self.get_authorized_keys_file(profile_id), remote_agent=remote_agent, delay_deletion=20, ) else: logger_broker.info('base_broker.X2GoBroker.select_session(): accepting public SSH key from broker client') if remote_sshproxy_agent is not None: x2gobroker.agent.add_authorized_key(username=username, pubkey_hash=pubkey, authorized_keys_file=self.get_sshproxy_authorized_keys_file(profile_id), remote_agent=remote_sshproxy_agent, ), x2gobroker.agent.add_authorized_key(username=username, pubkey_hash=pubkey, authorized_keys_file=self.get_authorized_keys_file(profile_id), remote_agent=remote_agent, ), selected_session.update({ 'authentication_pubkey': 'ACCEPTED', }) if remote_sshproxy_agent is not None: x2gobroker.agent.delete_authorized_key(username=username, pubkey_hash=pubkey, authorized_keys_file=self.get_sshproxy_authorized_keys_file(profile_id), remote_agent=remote_sshproxy_agent, delay_deletion=20, ) x2gobroker.agent.delete_authorized_key(username=username, pubkey_hash=pubkey, authorized_keys_file=self.get_authorized_keys_file(profile_id), remote_agent=remote_agent, delay_deletion=20, ) return selected_session def change_password(self, new='', old=''): """\ Modify the authenticated user's password on the X2Go infrastructure (normally, one user in one X2Go site setup should have the same password on all machines). This function is a dummy function and needs to be overridden in specific broker backend implementations :param new: the new password that is to be set :type new: ``str`` :param old: the currently set password :type old: ``str`` :returns: whether the password change has been successful :rtype: ``bool`` """ return False def run_optional_script(self, script_type, username, password, task, profile_id, ip, cookie, authed=None, server=None): """\ Run all optional scripts of type script_type. Called with 3 different script types: - pre_auth_scripts - before authentication happens - post_auth_scripts - after authentication but before anything else occurs - select_session_scripts - after load balancing before a specific server is sent to the client These scripts allow for both addional actions to be performed as well as the mangling of any relevant fields. :param script_type: name of the script type to be executed (``pre_auth_scripts``, ``post_auth_scripts``, ``select_session_scripts``) :type script_type: ``str`` :param username: name of the X2Go session user a script will run for :type username: ``str`` :param password: password for the X2Go session :type password: ``str`` :param task: the broker task that currently being processed :type task: ``str`` :param profile_id: the session profile ID that is being operated upon :type profile_id: ``str`` :param ip: the client machine's IP address :type ip: ``str`` :param cookie: the currently valid authentication cookie :type cookie: ``str`` :param authed: authentication status (already authenticated or not) :type authed: ``bool`` :param server: hostname or IP address of the X2Go server being operated upon :type server: ``str`` :returns: Pass-through of the return value returned by the to-be-run optional script (i.e., success or failure) :rtype: ``bool`` """ global_config = self.get_global_config() if len(global_config[script_type]) != 0: for script in global_config[script_type]: try: if script: my_script=None namespace = {} exec("import x2gobroker.optional_scripts.{script}_script\nmy_script = x2gobroker.optional_scripts.{script}_script.X2GoBrokerOptionalScript()".format(script=script), namespace) my_script = namespace['my_script'] logger_broker.debug ('Calling {script_type} {script} with username: {username}, password: {password}, task: {task}, profile_id: {profile_id}, ip: {ip}, cookie: {cookie}, authed: {authed}, server: {server}'.format(script_type=script_type,script=script,username=username, password='XXXXX', task=task, profile_id=profile_id, ip=ip, cookie=cookie, authed=authed, server=server)) username, password, task, profile_id, ip, cookie, authed, server = my_script.run_me(username=username, password=password, task=task, profile_id=profile_id, ip=ip, cookie=cookie, authed=authed, server=server) logger_broker.debug ('Finished {script_type} {script} with username: {username}, password: {password}, task: {task}, profile_id: {profile_id}, ip: {ip}, cookie: {cookie}, authed: {authed}, server: {server}'.format(script_type=script_type,script=script,username=username, password='XXXXX', task=task, profile_id=profile_id, ip=ip, cookie=cookie, authed=authed, server=server)) except ImportError: logger_error.error('No such optional script \'{script}\''.format(script=script)) return username, password, task, profile_id, ip, cookie, authed, server x2gobroker-0.0.4.1/x2gobroker/brokers/inifile_broker.py0000644000000000000000000002046213457267612017735 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. """\ :class:`x2gobroker.brokers.inifile_broker.X2GoBroker` class - a simple X2GoBroker implementations that uses text-based config files (also supports load balancing) """ __NAME__ = 'x2gobroker-pylib' # modules import copy import netaddr import re # Python X2GoBroker modules import x2gobroker.brokers.base_broker as base import x2gobroker.config import x2gobroker.defaults import x2gobroker.x2gobroker_exceptions from configparser import NoSectionError class X2GoBroker(base.X2GoBroker): """\ :class:`x2gobroker.brokers.inifile_broker.X2GoBroker` implements a broker backend retrieving its session profile and ACL configuration from a file in INI file format. """ backend_name = 'inifile' def __init__(self, profile_config_file=None, profile_config_defaults=None, **kwargs): """\ Initialize a new INI file based X2GoBroker instance to control X2Go session through an X2Go Client with an intermediate session broker :param profile_config_file: path to the backend's session profile configuration (x2gobroker-sessionprofiles.conf) :type profile_config_file: ``str`` :param profile_config_defaults: Default settings for session profile configuration parameters. :type profile_config_defaults: ``dict`` """ base.X2GoBroker.__init__(self, **kwargs) if profile_config_file is None: profile_config_file = x2gobroker.defaults.X2GOBROKER_SESSIONPROFILES if profile_config_defaults is None: profile_config_defaults = x2gobroker.defaults.X2GOBROKER_SESSIONPROFILE_DEFAULTS self.session_profiles = x2gobroker.config.X2GoBrokerConfigFile(config_files=profile_config_file, defaults=profile_config_defaults) def get_profile_ids(self): """\ Retrieve the complete list of session profile IDs. With the ``inifile`` broker backend, the profile IDs are the names of the INI file's sections. :returns: list of profile IDs :rtype: ``list`` """ return self.session_profiles.list_sections() def get_profile_defaults(self): """\ Get the session profile defaults, i.e. profile options that all configured session profiles have in common. The defaults are hard-coded in :mod:`x2gobroker.defaults` for class :class:`x2gobroker.brokers.base_broker.X2GoBroker`. With the ``inifile`` backend, they can be overridden/customized under the INI file's ``[DEFAULT]`` section. :returns: a dictionary containing the session profile defaults :rtype: ``dict`` """ profile_defaults = self.session_profiles.get_defaults() for key in list(profile_defaults.keys()): if key.startswith('acl-'): del profile_defaults[key] elif type(profile_defaults[key]) == list: profile_defaults.update({ key: [ v for v in profile_defaults[key] if v ] }) return profile_defaults def get_profile(self, profile_id): """\ Get the session profile for profile ID . With the ``inifile`` broker backend, the session profile parameters are the given ``=`` pairs under the section ``[]``. :param profile_id: the ID of a profile :type profile_id: ``str`` :returns: a dictionary representing the session profile for ID :rtype: ``dict`` """ try: profile = self.session_profiles.get_section(profile_id) except NoSectionError: raise x2gobroker.x2gobroker_exceptions.X2GoBrokerProfileException('No such session profile ID: {profile_id}'.format(profile_id=profile_id)) profile_defaults = self.get_profile_defaults() for key in list(profile_defaults.keys()): if key not in list(profile.keys()): profile.update({ key: profile_defaults[key] }) if type(profile_defaults[key]) == list: profile.update({ key: [ v for v in profile[key] if v ] }) for key in list(profile.keys()): if key.startswith('acl-'): del profile[key] if key.startswith('broker-'): del profile[key] if key == 'default': del profile[key] if key == 'host': _hosts = copy.deepcopy(profile[key]) try: _default_sshport = int(profile['sshport']) except TypeError: _default_sshport = 22 profile[key] = [] for host in _hosts: if re.match('^.*\ \(.*\)$', host): _hostname = host.split(' ')[0] _address = host.split(' ')[1][1:-1] _address, _port = x2gobroker.utils.split_host_address(_address, default_port=_default_sshport) # test if _address is a valid hostname, a valid DNS name or an IPv4/IPv6 address if (re.match('(?!-)[A-Z\d-]{1,63}(?. With the ``inifile`` broker backend, these broker specific options are ``=`` pairs prefixed like this: ``broker-=`` :param profile_id: the ID of a profile :type profile_id: ``str`` :returns: a dictionary representing the session profile for ID :rtype: ``dict`` """ profile = self.session_profiles.get_section(profile_id) for key in list(profile.keys()): if not key.startswith('broker-'): del profile[key] if key.startswith('broker-') and (profile[key] == '' or profile[key] == ['']): del profile[key] return profile def get_profile_acls(self, profile_id): """\ Get the ACLs for session profile with profile ID . With the ``inifile`` broker backend, these ACL specific options are ``=`` pairs prefixed like this: ``acl-=`` :param profile_id: the ID of a profile :type profile_id: ``str`` :returns: a dictionary representing the ACLs for session profile with ID :rtype: ``dict`` """ profile = self.session_profiles.get_section(profile_id) for key in list(profile.keys()): if not key.startswith('acl-'): del profile[key] if key.startswith('acl-') and (profile[key] == '' or profile[key] == ['']): del profile[key] return profile x2gobroker-0.0.4.1/x2gobroker/brokers/__init__.py0000644000000000000000000000153413457267612016510 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. x2gobroker-0.0.4.1/x2gobroker/brokers/zeroconf_broker.py0000644000000000000000000001037713457267612020147 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. """\ :class:`x2gobroker.brokers.zeroconf_broker.X2GoBroker` class - a demo X2GoBroker implementations that needs not configuration at all """ __NAME__ = 'x2gobroker-pylib' # modules import uuid # Python X2GoBroker modules import x2gobroker.brokers.base_broker as base class X2GoBroker(base.X2GoBroker): backend_name = 'zeroconf' def list_profiles(self, username): """\ Retrieve a list of session profiles for the authenticated user. With the ``zeroconf`` broker backend, this list of session profiles is hard-coded. This if for testing purposes, only. :param username: query session profile list for this user :type username: ``str`` :returns: list of profile dictionaries :rtype: ``dict`` """ _list_of_profiles = { uuid.uuid4(): { 'user': '', 'defsndport': True, 'useiconv': False, 'iconvfrom': 'UTF-8', 'height': 600, 'export': '', 'quality': 9, 'fullscreen': False, 'layout': '', 'useexports': 1, 'width': 800, 'speed': 2, 'soundsystem': 'pulse', 'print': True, 'type': 'auto', 'sndport': 4713, 'xinerama': True, 'variant': '', 'usekbd': True, 'fstunnel': True, 'applications': ['TERMINAL','WWWBROWSER','MAILCLIENT','OFFICE',], 'host': 'localhost', 'multidisp': 0, 'sshproxyport': 22, 'sound': True, 'rootless': 0, 'name': 'LOCALHOST', 'iconvto': 'UTF-8', 'soundtunnel': True, 'command': self.get_backend_value('broker_{backend}'.format(backend=self.backend_name), 'desktop-shell').upper(), 'dpi': 96, 'sshport': 22, 'setdpi': 0, 'pack': '16m-jpeg', }, } list_of_profiles = {} for profile_id in list(_list_of_profiles.keys()): profile = self.get_profile_defaults() profile.update(_list_of_profiles[profile_id]) list_of_profiles[profile_id] = profile return list_of_profiles def select_session(self, profile_id, username=None, **kwargs): """\ Start/resume a session by selecting a profile name offered by the X2Go client. With the ``zeroconf`` broker backend, the X2Go server that the session is launched on is hard-coded (localhost, port 22). This is for testing purposes only. :param profile_id: the selected profile ID. This matches one of the dictionary keys offered by the ``list_profiles`` method :type profile_id: ``str`` :param username: specify X2Go Server username that this operation runs for :type username: ``str`` :param pubkey: The broker clients may send us a public key that we may temporarily install into a remote X2Go Server for non-interactive login :type pubkey: ``str`` :returns: the seclected session (X2Go session ID) :rtype: ``str`` """ selectprofile_output = { 'server': 'localhost', 'port': 22, } return selectprofile_output x2gobroker-0.0.4.1/x2gobroker/client/__init__.py0000644000000000000000000000153413457267612016317 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. x2gobroker-0.0.4.1/x2gobroker/client/plain.py0000644000000000000000000001733213457267612015666 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # This file is part of the X2Go Project - https://www.x2go.org # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. # modules import os # Python X2Go Broker modules import x2gobroker.defaults from x2gobroker.loggers import logger_broker def _override_do_authenticate(username='', password=''): return True class X2GoBrokerClient(object): """\ Implementation of a command line interface to X2Go Session Broker. This CLI can be evoked over an SSH connection. This provides the so-called SSH mode for X2Go Session Broker. """ def get(self, args): """\ Analogy to the http request get method of the HTTP X2Go Session Broker, this method expects a set of arguments (i.e., an instance of :class:``argparse.ArgumentParser``) and process the given arguments. Well-known arguments are: * ``args.user`` - broker user on whose behalf to operate * ``args.login`` - X2Go Server user for whom to perform the task * ``args.auth_cookie`` - authentication cookie * ``args.task`` - broker backend task to perform * ``args.profile_id`` - session profile ID :param args: an :class:``argparse.ArgumentParser`` object provide by the ``x2gobroker`` command line script :type args: ``obj`` :returns: output as expected by the calling client side :rtype: ``str`` """ backend = args.backend if not backend: backend = x2gobroker.defaults.X2GOBROKER_DEFAULT_BACKEND # silence pyflakes... broker_backend = None try: # dynamically detect broker backend from given backend namespace = {} exec("import x2gobroker.brokers.{backend}_broker\nbroker_backend = x2gobroker.brokers.{backend}_broker.X2GoBroker()".format(backend=backend), namespace) broker_backend = namespace['broker_backend'] except ImportError: logger_broker.error('unknown backend: {backend}'.format(backend=backend)) if broker_backend.is_enabled(): if 'SSH_CLIENT' in os.environ: ip = os.environ['SSH_CLIENT'].split()[0] else: ip = '127.0.0.1' if ip: logger_broker.info('client address is {address}'.format(address=ip)) broker_backend.set_client_address(ip) broker_username = args.user server_username = args.login cookie = args.auth_cookie task = args.task profile_id = args.profile_id output = '' broker_backend._do_authenticate = _override_do_authenticate broker_username, password, task, profile_id, ip, cookie, authed, server = broker_backend.run_optional_script(script_type='pre_auth_scripts', username=broker_username, password="SSH", task=task, profile_id=profile_id, ip=ip, cookie=cookie) access, next_cookie = broker_backend.check_access(username=broker_username, ip=ip, cookie=cookie, override_password_auth=True) broker_username, password, task, profile_id, ip, cookie, authed, server = broker_backend.run_optional_script(script_type='post_auth_scripts', username=broker_username, password="SSH", task=task, profile_id=profile_id, ip=ip, cookie=cookie, authed=access) if access: logger_broker.debug ('broker_username: {broker_username}, server_username: {server_username}, task: {task}, profile_id: {profile_id}'.format(broker_username=broker_username, server_username=server_username, task=task, profile_id=profile_id)) ### ### CONFIRM SUCCESSFUL AUTHENTICATION FIRST ### if next_cookie is not None: output += "AUTHID:{authid}\n".format(authid=next_cookie) output += "Access granted\n" ### ### X2GO BROKER TASKS ### # FIXME: the ,,testcon'' task can be object to DoS attacks... if task == 'testcon': ### ### TEST THE CONNECTION ### return broker_backend.test_connection() if task == 'listsessions': profiles = broker_backend.list_profiles(broker_username) if profiles: output += "START_USER_SESSIONS\n\n" profile_ids = list(profiles.keys()) profile_ids.sort() for profile_id in profile_ids: output += "[{profile_id}]\n".format(profile_id=profile_id) for key in list(profiles[profile_id].keys()): if key == 'user' and not profiles[profile_id][key]: profiles[profile_id][key] = server_username if type(profiles[profile_id][key]) == str: output += "{key}={value}".format(key=key, value=profiles[profile_id][key]) elif type(profiles[profile_id][key]) in (list, tuple): output += "{key}={value}".format(key=key, value=",".join(profiles[profile_id][key])) else: output += "{key}={value}".format(key=key, value=int(profiles[profile_id][key])) output += "\n" output += "\n" output += "END_USER_SESSIONS\n" elif task == 'selectsession': if profile_id: profile_info = broker_backend.select_session(profile_id=profile_id, username=server_username) broker_username, password, task, profile_id, ip, cookie, authed, server = broker_backend.run_optional_script(script_type='select_session_scripts', username=broker_username, password="SSH", task=task, profile_id=profile_id, ip=ip, cookie=cookie, authed=access, server=profile_info['server']) if 'server' in profile_info: output += "SERVER:" output += profile_info['server'] if 'port' in profile_info: output += ":{port}".format(port=profile_info['port']) output += "\n" if 'authentication_privkey' in profile_info: output += profile_info['authentication_privkey'] if 'session_info' in profile_info: output += "SESSION_INFO:" output += profile_info['session_info'] + "\n" else: logger_broker.error ('cookie authentication failed') return output logger_broker.error ('broker backend ,,{backend}\'\' is disabled on this system'.format(backend=backend)) x2gobroker-0.0.4.1/x2gobroker/config.py0000644000000000000000000002667413457267612014563 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2010-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. # # This code was initially written by for the Python X2Go project: # 2010 Dick Kniep # """\ X2GoConfig - helper class for parsing files in INI file format. """ __NAME__ = 'x2goinifiles-pylib' # modules import os import configparser import io # Python X2GoBroker modules import x2gobroker.utils from x2gobroker.defaults import X2GOBROKER_HOME as _X2GOBROKER_HOME class X2GoBrokerConfigFile(object): """ Class for processing an INI-file-like configuration file. If entries are omitted in such a config file, they are filled with default values (as hard-coded in Python X2GoBroker), so the resulting objects always contain the same fields. The default values are also used to define a data type for each configuration option. An on-the-fly type conversion takes place when loading the configuration file. """ defaultValues = { 'DEFAULT': { 'none': 'empty', }, } write_user_config = False user_config_file = None def __init__(self, config_files=[], defaults={}): """\ :param config_files: a list of configuration file names (e.g. a global filename and a user's home directory filename) :type config_files: ``list`` :param defaults: a cascaded Python dicitionary structure with INI file defaults (to override Python X2GoBroker's hard-coded defaults in L{defaults} :type defaults: ``dict`` """ # allow string/unicode objects as config_files, as well if type(config_files) == str: config_files = [config_files] self.config_files = config_files if x2gobroker.utils._checkConfigFileDefaults(defaults): self.defaultValues = defaults # we purposefully do not inherit the RawConfigParser class # here as we do not want to run into name conflicts between # X2GoBroker config file options and method / property names in # RawConfigParser... This is a pre-cautious approach... self.iniConfig = configparser.RawConfigParser(self.defaultValues) self.iniConfig.optionxform = str _create_file = False for file_name in self.config_files: if file_name.startswith(_X2GOBROKER_HOME): if not os.path.exists(file_name): x2gobroker.utils.touch_file(file_name) _create_file = True break self.load() if _create_file: self.write_user_config = True self.write() def __repr__(self): result = 'X2GoConfigFile(' for p in dir(self): if '__' in p or not p in self.__dict__: continue result += p + '=' + str(self.__dict__[p]) + ',' result = result.strip(',') return result + ')' def load(self): """\ R(e-r)ead configuration file(s). """ _found_config_files = self.iniConfig.read(self.config_files) for file_name in _found_config_files: if file_name.startswith(os.path.normpath(_X2GOBROKER_HOME)): # we will use the first file found in the user's home dir for writing modifications self.user_config_file = file_name break self.config_files = _found_config_files self._fill_defaults() def _storeValue(self, section, key, value): """\ Stores a value for a given section and key. This methods affects a :class:`configparser.RawConfigParser` object held in RAM. No configuration file is affected by this method. To write the configuration to disk use the L{write()} method. :param section: the INI file section :type section: ``str`` :param key: the INI file key in the given section :type key: ``str`` :param value: the value for the given section and key :type value: ``str``, ``list``, ``bool``, ... """ if type(value) is bool: self.iniConfig.set(section, key, str(int(value))) elif type(value) in (list, tuple): self.iniConfig.set(section, key, ", ".join(value)) elif type(value) is int: self.iniConfig.set(section, key, str(value)) else: self.iniConfig.set(section, key, value) def _fill_defaults(self): """\ Fills a :class:`configparser.RawConfigParser` object with the default config file values as pre-defined in Python X2GoBroker or. This RawConfigParser object is held in RAM. No configuration file is affected by this method. """ for section, sectiondict in list(self.defaultValues.items()): if section != 'DEFAULT' and not self.iniConfig.has_section(section): self.iniConfig.add_section(section) for key, value in list(sectiondict.items()): if self.iniConfig.has_option(section, key): continue self._storeValue(section, key, value) def update_value(self, section, key, value): """\ Change a value for a given section and key. This method does not have any effect on configuration files. :param section: the INI file section :type section: ``str`` :param key: the INI file key in the given section :type key: ``str`` :param value: the value for the given section and key :type value: ``str``, ``list``, ``bool``, ... """ if not self.iniConfig.has_section(section): self.iniConfig.add_section(section) self._storeValue(section, key, value) self.write_user_config = True def write(self): """\ Write the INI file modifications (RawConfigParser object) from RAM to disk. For writing the first of the ``config_files`` specified on instance construction that is writable will be used. """ if self.user_config_file and self.write_user_config: fd = open(self.user_config_file, 'w') self.iniConfig.write(fd) fd.close() self.write_user_config = False def get_type(self, section, key): """\ Retrieve a value type for a given section and key. The returned value type is based on the default values dictionary. :param section: the INI file section :type section: ``str`` :param key: the INI file key in the given section :type key: ``str`` :returns: a Python variable type :rtype: class """ if section in list(self.defaultValues.keys()) and key in list(self.defaultValues[section].keys()): return type(self.defaultValues[section][key]) else: try: return type(self.defaultValues['DEFAULT'][key]) except KeyError: return type('') def has_value(self, section, key): """\ Test if a given ``key`` in ``section`` exists (and has some sort of a value). :param section: the INI file section :type section: ``str`` :param key: the INI file key in the given section :type key: ``str`` :returns: return ``True`` if in
exists :rtype: ``bool`` """ if section in self.iniConfig.sections(): return ( key in self.iniConfig.options(section) ) return False def get_value(self, section, key, key_type=None): """\ Retrieve a value for a given section and key. :param section: the INI file section :type section: ``str`` :param key: the INI file key in the given section :type key: ``str`` :returns: the value for the given section and key :rtype: class """ if key_type is None: key_type = self.get_type(section, key) if self.iniConfig.has_option(section, key) or section == 'DEFAULT': if key_type is None: return self.iniConfig.get(section, key) if key_type is bool: return self.iniConfig.getboolean(section, key) elif key_type is int: try: return self.iniConfig.getint(section, key) except ValueError: _val = self.iniConfig.get(section, key) if _val != "not-set": raise else: return _val elif key_type is list: _val = self.iniConfig.get(section, key) _val = _val.strip() if _val.startswith('[') and _val.endswith(']'): return eval(_val) elif ',' in _val: _val = [ v.strip() for v in _val.split(',') ] else: _val = [ _val ] return _val else: _val = self.iniConfig.get(section, key) return _val get = get_value __call__ = get_value def get_defaults(self): """\ Get all keys and values from the [DEFAULT] section of the configuration file. :returns: the defaults with all keys and values :rtype: ``dict`` """ _my_defaults = {} _ini_defaults = self.iniConfig.defaults() for option in list(_ini_defaults.keys()): try: _my_defaults[option] = self.get('DEFAULT', option, key_type=self.get_type('DEFAULT', option)) except KeyError: continue try: del _my_defaults['default'] except KeyError: pass return _my_defaults def get_section(self, section): """\ Get all keys and values for a certain section of the config file. :param section: the name of the section to get :type section: ``str`` :returns: the section with all keys and values :rtype: ``dict`` """ _section_config = {} for option in self.iniConfig.options(section): if option not in self.iniConfig.sections(): _section_config[option] = self.get(section, option, key_type=self.get_type(section, option)) return _section_config def list_sections(self): """\ Return a list of all present sections in a config file. :returns: list of sections in this config file :rtype: ``list`` """ return [ s for s in self.iniConfig.sections() ] @property def printable_config_file(self): """\ Returns a printable configuration file as a multi-line string. """ stdout = io.StringIO() self.iniConfig.write(stdout) _ret_val = stdout.getvalue() stdout.close() return _ret_val x2gobroker-0.0.4.1/x2gobroker-daemon.service0000644000000000000000000000043113457267612015546 0ustar [Unit] Description=X2Go Session Broker Daemon [Service] User=root Group=root Type=forking ExecStart=/usr/bin/x2gobroker-daemon --drop-privileges -D --pidfile /run/x2gobroker/x2gobroker-daemon.pid PIDFile=/run/x2gobroker/x2gobroker-daemon.pid [Install] WantedBy=multi-user.target x2gobroker-0.0.4.1/x2gobroker/defaults.py0000644000000000000000000003701013457267612015107 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. """ X2Go Session Brokers uses many hard-coded defaults, that can be overridden in various ways: * environment variables of the same name as the variable name in Python * for **SysV init system**: environment variables set in a default configuration file under ``/etc/default``; normally the naming scheme is ``/etc/default/`` * for **systemd init system**: in the file ``/etc/x2go/broker/defaults.conf``: this file should be installed on your system, the file needs to be provided in INI file format """ # modules import os import socket import pwd, grp import logging from x2gobroker.loggers import logger_broker, logger_access, logger_error, X2GOBROKER_DAEMON_USER from x2gobroker.loggers import iniconfig_loaded if iniconfig_loaded: from x2gobroker.loggers import iniconfig, iniconfig_section X2GOBROKER_USER = '' """The (system) user, X2Go Session Broker runs under. Whether this is a system user or e.g. your own user account depends on what component of the broker is used.""" if 'SPHINX_API_DOCUMENTATION_BUILD' in os.environ.keys(): X2GOBROKER_USER = pwd.getpwuid(os.geteuid())[0] X2GOBROKER_GROUP = '' """The (system) group, X2Go Session Broker runs under. Whether this is a system user or e.g. the x2gobroker-users group is dependent on what component of the broker is used.""" if 'SPHINX_API_DOCUMENTATION_BUILD' in os.environ.keys(): X2GOBROKER_GROUP = grp.getgrgid(pwd.getpwuid(os.geteuid())[3])[0] os.environ['HOME'] = pwd.getpwuid(os.geteuid())[5] X2GOBROKER_AGENT_USER="x2gobroker" """The system user to use when launching X2Go Broker Agent on remote X2Go Servers.""" if 'X2GOBROKER_DAEMON_GROUP' in os.environ: X2GOBROKER_DAEMON_GROUP=os.environ['X2GOBROKER_DAEMON_GROUP'] elif iniconfig_loaded and iniconfig.has_option(iniconfig_section, 'X2GOBROKER_DAEMON_GROUP'): X2GOBROKER_DAEMON_GROUP=iniconfig.get(iniconfig_section, 'X2GOBROKER_DAEMON_GROUP') elif iniconfig_loaded and iniconfig.has_option('common', 'X2GOBROKER_DAEMON_GROUP'): X2GOBROKER_DAEMON_GROUP=iniconfig.get('common', 'X2GOBROKER_DAEMON_GROUP') else: X2GOBROKER_DAEMON_GROUP="x2gobroker" if 'X2GOBROKER_AGENT_USER' in os.environ: X2GOBROKER_AGENT_USER=os.environ['X2GOBROKER_AGENT_USER'] elif iniconfig_loaded and iniconfig.has_option(iniconfig_section, 'X2GOBROKER_AGENT_USER'): X2GOBROKER_AGENT_USER=iniconfig.get(iniconfig_section, 'X2GOBROKER_AGENT_USER') elif iniconfig_loaded and iniconfig.has_option('common', 'X2GOBROKER_AGENT_USER'): X2GOBROKER_AGENT_USER=iniconfig.get('common', 'X2GOBROKER_AGENT_USER') ### ### dynamic default values, influencable through os.environ... ### X2GOBROKER_DEBUG_INTERACTIVELY = False """When set to ``True``, the X2Go Broker component this parameter is set for, runs in foreground and debugging mode.""" if 'X2GOBROKER_DEBUG' in os.environ: X2GOBROKER_DEBUG = ( os.environ['X2GOBROKER_DEBUG'].lower() in ('1', 'on', 'true', 'yes', ) ) elif iniconfig_loaded and iniconfig.has_option(iniconfig_section, 'X2GOBROKER_DEBUG'): X2GOBROKER_DEBUG=iniconfig.get(iniconfig_section, 'X2GOBROKER_DEBUG') elif iniconfig_loaded and iniconfig.has_option('common', 'X2GOBROKER_DEBUG'): X2GOBROKER_DEBUG=iniconfig.get('common', 'X2GOBROKER_DEBUG') else: X2GOBROKER_DEBUG = False if 'X2GOBROKER_DEBUG_INTERACTIVELY' in os.environ: X2GOBROKER_DEBUG_INTERACTIVELY = ( os.environ['X2GOBROKER_DEBUG_INTERACTIVELY'].lower() in ('1', 'on', 'true', 'yes', ) ) elif iniconfig_loaded and iniconfig.has_option(iniconfig_section, 'X2GOBROKER_DEBUG_INTERACTIVELY'): X2GOBROKER_DEBUG_INTERACTIVELY=iniconfig.get(iniconfig_section, 'X2GOBROKER_DEBUG_INTERACTIVELY') elif iniconfig_loaded and iniconfig.has_option('common', 'X2GOBROKER_DEBUG_INTERACTIVELY'): X2GOBROKER_DEBUG_INTERACTIVELY=iniconfig.get('common', 'X2GOBROKER_DEBUG_INTERACTIVELY') if 'X2GOBROKER_TESTSUITE' in os.environ: X2GOBROKER_TESTSUITE = ( os.environ['X2GOBROKER_TESTSUITE'].lower() in ('1', 'on', 'true', 'yes', ) ) elif iniconfig_loaded and iniconfig.has_option(iniconfig_section, 'X2GOBROKER_TESTSUITE'): X2GOBROKER_TESTSUITE=iniconfig.get(iniconfig_section, 'X2GOBROKER_TESTSUITE') elif iniconfig_loaded and iniconfig.has_option('common', 'X2GOBROKER_TESTSUITE'): X2GOBROKER_TESTSUITE=iniconfig.get('common', 'X2GOBROKER_TESTSUITE') else: X2GOBROKER_TESTSUITE = False # enforce debugging for interactive usage if X2GOBROKER_USER != X2GOBROKER_DAEMON_USER: X2GOBROKER_DEBUG = True # raise log levels to CRITICAL if we are running the unittests... if X2GOBROKER_TESTSUITE: logger_broker.setLevel(logging.CRITICAL) logger_access.setLevel(logging.CRITICAL) logger_error.setLevel(logging.CRITICAL) X2GOBROKER_CONFIG = "/etc/x2go/x2gobroker.conf" """Location of X2Go Broker\'s global configuration file.""" if 'X2GOBROKER_CONFIG' in os.environ: X2GOBROKER_CONFIG = os.environ['X2GOBROKER_CONFIG'] elif iniconfig_loaded and iniconfig.has_option(iniconfig_section, 'X2GOBROKER_CONFIG'): X2GOBROKER_CONFIG=iniconfig.get(iniconfig_section, 'X2GOBROKER_CONFIG') elif iniconfig_loaded and iniconfig.has_option('common', 'X2GOBROKER_CONFIG'): X2GOBROKER_CONFIG=iniconfig.get('common', 'X2GOBROKER_CONFIG') X2GOBROKER_SESSIONPROFILES = "/etc/x2go/broker/x2gobroker-sessionprofiles.conf" """Location of the INI file based broker backend \'s session profiles configuration file.""" if 'X2GOBROKER_SESSIONPROFILES' in os.environ: X2GOBROKER_SESSIONPROFILES = os.environ['X2GOBROKER_SESSIONPROFILES'] elif iniconfig_loaded and iniconfig.has_option(iniconfig_section, 'X2GOBROKER_SESSIONPROFILES'): X2GOBROKER_SESSIONPROFILES=iniconfig.get(iniconfig_section, 'X2GOBROKER_SESSIONPROFILES') elif iniconfig_loaded and iniconfig.has_option('common', 'X2GOBROKER_SESSIONPROFILES'): X2GOBROKER_SESSIONPROFILES=iniconfig.get('common', 'X2GOBROKER_SESSIONPROFILES') X2GOBROKER_AGENT_CMD = "/usr/lib/x2go/x2gobroker-agent" """Path to the X2Go Broker Agent executable on remote X2Go Servers.""" if 'X2GOBROKER_AGENT_CMD' in os.environ: X2GOBROKER_AGENT_CMD = os.environ['X2GOBROKER_AGENT_CMD'] elif iniconfig_loaded and iniconfig.has_option(iniconfig_section, 'X2GOBROKER_AGENT_CMD'): X2GOBROKER_AGENT_CMD=iniconfig.get(iniconfig_section, 'X2GOBROKER_AGENT_CMD') elif iniconfig_loaded and iniconfig.has_option('common', 'X2GOBROKER_AGENT_CMD'): X2GOBROKER_AGENT_CMD=iniconfig.get('common', 'X2GOBROKER_AGENT_CMD') if os.path.isdir('/run/x2gobroker'): RUNDIR = '/run' else: RUNDIR = '/var/run/x2gobroker' X2GOBROKER_AUTHSERVICE_SOCKET="{run}/x2gobroker/x2gobroker-authservice.socket".format(run=RUNDIR) """Location of the X2Go Broker Auth Service's authentication socket file.""" if 'X2GOBROKER_AUTHSERVICE_SOCKET' in os.environ: X2GOBROKER_AUTHSERVICE_SOCKET=os.environ['X2GOBROKER_AUTHSERVICE_SOCKET'] elif iniconfig_loaded and iniconfig.has_option(iniconfig_section, 'X2GOBROKER_AUTHSERVICE_SOCKET'): X2GOBROKER_AUTHSERVICE_SOCKET=iniconfig.get(iniconfig_section, 'X2GOBROKER_AUTHSERVICE_SOCKET') elif iniconfig_loaded and iniconfig.has_option('common', 'X2GOBROKER_AUTHSERVICE_SOCKET'): X2GOBROKER_AUTHSERVICE_SOCKET=iniconfig.get('common', 'X2GOBROKER_AUTHSERVICE_SOCKET') if os.path.isdir('/run/x2gobroker'): RUNDIR = '/run' else: RUNDIR = '/var/run/x2gobroker' X2GOBROKER_LOADCHECKER_SOCKET="{run}/x2gobroker/x2gobroker-loadchecker.socket".format(run=RUNDIR) """Location of the X2Go Broker Load Checker's communication socket file.""" if 'X2GOBROKER_LOADCHECKER_SOCKET' in os.environ: X2GOBROKER_LOADCHECKER_SOCKET=os.environ['X2GOBROKER_LOADCHECKER_SOCKET'] elif iniconfig_loaded and iniconfig.has_option(iniconfig_section, 'X2GOBROKER_LOADCHECKER_SOCKET'): X2GOBROKER_LOADCHECKER_SOCKET=iniconfig.get(iniconfig_section, 'X2GOBROKER_LOADCHECKER_SOCKET') elif iniconfig_loaded and iniconfig.has_option('common', 'X2GOBROKER_LOADCHECKER_SOCKET'): X2GOBROKER_LOADCHECKER_SOCKET=iniconfig.get('common', 'X2GOBROKER_LOADCHECKER_SOCKET') X2GOBROKER_DEFAULT_BACKEND = "inifile" """The broker backend to use by default.""" if 'X2GOBROKER_DEFAULT_BACKEND' in os.environ: X2GOBROKER_DEFAULT_BACKEND = os.environ['X2GOBROKER_DEFAULT_BACKEND'] elif iniconfig_loaded and iniconfig.has_option(iniconfig_section, 'X2GOBROKER_DEFAULT_BACKEND'): X2GOBROKER_DEFAULT_BACKEND=iniconfig.get(iniconfig_section, 'X2GOBROKER_DEFAULT_BACKEND') elif iniconfig_loaded and iniconfig.has_option('common', 'X2GOBROKER_DEFAULT_BACKEND'): X2GOBROKER_DEFAULT_BACKEND=iniconfig.get('common', 'X2GOBROKER_DEFAULT_BACKEND') DAEMON_BIND_ADDRESS = "" """Bind address for the X2Go Session Broker standalone daemon.""" if 'DAEMON_BIND_ADDRESS' in os.environ: DAEMON_BIND_ADDRESS = os.environ['DAEMON_BIND_ADDRESS'] elif iniconfig_loaded and iniconfig.has_option(iniconfig_section, 'DAEMON_BIND_ADDRESS'): DAEMON_BIND_ADDRESS = iniconfig.get(iniconfig_section, 'DAEMON_BIND_ADDRESS') elif iniconfig_loaded and iniconfig.has_option('daemon', 'DAEMON_BIND_ADDRESS'): DAEMON_BIND_ADDRESS = iniconfig.get('daemon', 'DAEMON_BIND_ADDRESS') X2GOBROKER_SSL_CERTFILE = "" """Path to the SSL/TLS public certificate file.""" if 'X2GOBROKER_SSL_CERTFILE' in os.environ: X2GOBROKER_SSL_CERTFILE = os.environ['X2GOBROKER_SSL_CERTFILE'] elif iniconfig_loaded and iniconfig.has_option(iniconfig_section, 'X2GOBROKER_SSL_CERTFILE'): X2GOBROKER_SSL_CERTFILE = iniconfig.get(iniconfig_section, 'X2GOBROKER_SSL_CERTFILE') elif iniconfig_loaded and iniconfig.has_option('daemon', 'X2GOBROKER_SSL_CERTFILE'): X2GOBROKER_SSL_CERTFILE = iniconfig.get('daemon', 'X2GOBROKER_SSL_CERTFILE') X2GOBROKER_SSL_KEYFILE = "" """Path to the SSL/TLS secret key file.""" if 'X2GOBROKER_SSL_KEYFILE' in os.environ: X2GOBROKER_SSL_KEYFILE = os.environ['X2GOBROKER_SSL_KEYFILE'] elif iniconfig_loaded and iniconfig.has_option(iniconfig_section, 'X2GOBROKER_SSL_KEYFILE'): X2GOBROKER_SSL_KEYFILE = iniconfig.get(iniconfig_section, 'X2GOBROKER_SSL_KEYFILE') elif iniconfig_loaded and iniconfig.has_option('daemon', 'X2GOBROKER_SSL_KEYFILE'): X2GOBROKER_SSL_KEYFILE = iniconfig.get('daemon', 'X2GOBROKER_SSL_KEYFILE') ### ### static / hard-coded defaults ### if socket.gethostname().find('.') >= 0: X2GOBROKER_HOSTNAME = socket.gethostname() else: X2GOBROKER_HOSTNAME = socket.gethostbyaddr(socket.gethostname())[0] # the home directory of the user that the daemon/cgi runs as X2GOBROKER_HOME = os.path.normpath(os.path.expanduser('~{broker_uid}'.format(broker_uid=X2GOBROKER_DAEMON_USER))) """Home directory of the user that an X2Go Broker component runs under.""" # defaults for X2Go Sessino Broker configuration file X2GOBROKER_CONFIG_DEFAULTS = { 'global': { # legacy support for X2Go Session Broker << 0.0.3.0 # the check-credentials parameter has been slit up into the two params above 'check-credentials': True, # use these two instead of check-credentials... 'require-password': True, 'require-cookie': False, 'use-static-cookie': False, 'auth-timeout': 36000, 'cookie-directory': '/var/lib/x2gobroker/cookies', 'verify-ip': True, 'pre_auth_scripts': [], 'post_auth_scripts': [], 'select_session_scripts': [], 'my-cookie': None, 'my-cookie-file': '/etc/x2go/broker/x2gobroker.authid', 'enable-plain-output': True, 'enable-json-output': True, 'enable-uccs-output': False, 'my-uccs-url-base': 'http://localhost:8080/', 'default-auth-mech': 'pam', 'default-user-db': 'libnss', 'default-group-db': 'libnss', 'ignore-primary-group-memberships': True, 'default-session-autologin': False, 'default-authorized-keys': '%h/.x2go/authorized_keys', 'default-sshproxy-authorized-keys': '%h/.x2go/authorized_keys', 'default-agent-query-mode': 'NONE', 'default-agent-hostkey-policy': 'RejectPolicy', 'default-portscan-x2goservers': True, 'default-use-load-checker': False, 'load-checker-intervals': 300, }, 'broker_base': { 'enable': False, }, 'broker_zeroconf': { 'enable': False, 'auth-mech': 'pam', 'user-db': 'libnss', 'group-db': 'libnss', 'desktop-shell': 'KDE', 'load-checker': False, }, 'broker_inifile': { 'enable': True, 'session-profiles': '/etc/x2go/broker/x2gobroker-sessionprofiles.conf', 'auth-mech': '', 'user-db': '', 'group-db': '', 'use-load-checker': True, }, 'broker_ldap': { 'enable': False, 'auth-mech': 'ldap', 'user-db': 'ldap', 'group-db': 'ldap', 'uri': 'ldap://localhost:389', 'base': 'dc=example,dc=org', 'user-search-filter': '(&(objectClass=posixAccount)(uid=*))', 'host-search-filter': '(&(objectClass=ipHost)(serial=X2GoServer)(cn=*))', 'group-search-filter': '(&(objectClass=posifxGroup)(cn=*))', 'starttls': False, 'agent-query-mode': 'SSH', 'load-checker': True, }, } """Defaults of the global configuration file, see ``X2GOBROKER_CONFIG``.""" X2GO_DESKTOP_SESSIONS= [ 'KDE', 'GNOME', 'XFCE', 'CINNAMON', 'MATE', 'XFCE', 'LXDE', 'TRINITY', 'UNITY', 'ICEWM', 'OPENBOX', 'XDMCP', ] """Desktop environment session types supported by X2Go.""" # defaults for X2Go Sessino Broker session profiles file X2GOBROKER_SESSIONPROFILE_DEFAULTS = { 'DEFAULT': { 'command': 'TERMINAL', 'defsndport': True, 'useiconv': False, 'iconvfrom': 'UTF-8', 'height': 600, 'export': '', 'quality': 9, 'fullscreen': False, 'layout': '', 'useexports': True, 'width': 800, 'speed': 2, 'soundsystem': 'pulse', 'print': True, 'type': 'auto', 'sndport': 4713, 'xinerama': True, 'variant': '', 'usekbd': True, 'fstunnel': True, 'applications': ['TERMINAL','WWWBROWSER','MAILCLIENT','OFFICE'], 'multidisp': False, 'sshproxyport': 22, 'sound': True, 'rootless': True, 'iconvto': 'UTF-8', 'soundtunnel': True, 'dpi': 96, 'sshport': 22, 'setdpi': 0, 'pack': '16m-jpeg', 'user': 'BROKER_USER', 'host': [ 'localhost', ], 'directrdp': False, 'acl-users-allow': [], 'acl-users-deny': [], 'acl-users-order': '', 'acl-groups-allow': [], 'acl-groups-deny': [], 'acl-groups-order': '', 'acl-clients-allow': [], 'acl-clients-deny': [], 'acl-clients-order': '', 'acl-any-order': 'deny-allow', }, } """Default setting of a broker'ish session profile.""" X2GOBROKER_LATEST_UCCS_API_VERSION = 5 """Latest known API of the UCCS protocol that we support.""" x2gobroker-0.0.4.1/x2gobroker/__init__.py0000644000000000000000000000170213457267612015036 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. __VERSION__ = '0.0.4.1' __AUTHOR__ = 'Mike Gabriel (X2Go Project) ' x2gobroker-0.0.4.1/x2gobroker/loadchecker.py0000644000000000000000000004156513457267612015556 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # This file is part of the X2Go Project - https://www.x2go.org # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. """\ The X2Go Broker Load Checker is an auxiliary X2Go Session Broker service that checks the load on associated X2Go Servers in regular intervals. The load of an X2Go Server gets checked by the Load Checker, if * an X2Go Server is part of a multi-server session profile * the remote X2Go Server can be queried via X2Go Broker Agent * the session profile (or the broker globally) is configured for background load checking (see global config parameter: ``load-checker`` in ``/etc/x2go/x2gobroker.conf`` In non-load-checker setups, multi-server session profiles perform a check on all configured servers during the login phase of a user. On big server farms, this check-them-all call to all members of the X2Go Server farm can be really time consuming. The solution is to run the X2Go Broker Load Checker service on the broker host and let it query server availability and load in regular intervals. It collects the server metrics and stores them in memory. If the broker receives a :func:`select_session() ` request from an X2Go client application, it will then negotiate with the load checker to work out, what X2Go Server is best for this incoming request. On the X2Go Servers, the X2Go Broker Agent calculates a ``load_factor`` that gets passed back to the X2Go Broker Load Checker when queried:: ( memAvail/1000 ) * numCPUs * typeCPUs load-factor = -------------------------------------- + 1 loadavg*100 * numSessions """ import threading import time import copy import socket # X2Go Session Broker modules import x2gobroker.defaults import x2gobroker.config from x2gobroker.loggers import logger_broker def check_load(backend, profile_id, hostname=None): """\ This function gets called from the broker daemon's side whenever the broker needs information about associated X2Go Servers. It represents the client-side of the load checking process in X2Go Session Broker. It either sends a one liner 3-tuple:: \\r\\r\\n or a one liner 2-tuple:: \\r\\n to the ``X2GOBROKER_LOADCHECKER_SOCKET`` (see :mod:`x2gobroker.defaults`) and expects a number (if the hostname was included in the query) or a Python dictionary (if only ``backend`` and ``profile_id`` had been given) as return value: the load factor(s) :param backend: the broker backend in use :type backend: ``str`` :param profile_id: the session profile's ID :type profile_id: ``str`` :param hostname: the X2Go Server's hostname as shown in ``x2golistsessions``'s output :type hostname: ``str`` :returns: either the load factor of the asked for server (as ``int``) or the load factors (as ``dict``) of all server members of the given session profile server :rtype: ``int`` or ``dict`` """ s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) logger_broker.debug('loadchecker.check_load(): connecting to load checker service socket {socket}'.format(socket=x2gobroker.defaults.X2GOBROKER_LOADCHECKER_SOCKET)) try: s.connect(x2gobroker.defaults.X2GOBROKER_LOADCHECKER_SOCKET) except socket.error as e: logger_broker.error('loadchecker.check_load(): failure when connecting to the load checker service socket {socket}: {errmsg}'.format(socket=x2gobroker.defaults.X2GOBROKER_LOADCHECKER_SOCKET, errmsg=str(e))) if hostname is not None: load_factor = 'LOAD-UNAVAILABLE' logger_broker.debug('loadchecker.check_load(): sending backend={backend}, profile_id={profile_id}, hostname={hostname} to load checker service'.format(backend=backend, profile_id=profile_id, hostname=hostname)) try: s.send('{backend}\r{profile_id}\r{hostname}\n'.format(backend=backend, profile_id=profile_id, hostname=hostname).encode()) load_factor = s.recv(1024).decode() s.close() except socket.error as e: logger_broker.error('loadchecker.check_load(): failure when sending data to the load checker service socket {socket}: {errmsg}'.format(socket=x2gobroker.defaults.X2GOBROKER_LOADCHECKER_SOCKET, errmsg=str(e))) if load_factor.startswith('LOAD-UNAVAILABLE'): logger_broker.warning('loadchecker.check_load(): load unavailable for backend={backend}, profile_id={profile_id}, hostname={hostname}'.format(backend=backend, profile_id=profile_id, hostname=hostname)) return 'LOAD-UNAVAILABLE' try: load_factor = int(load_factor) except ValueError: logger_broker.warning('loadchecker.check_load(): load data for backend={backend}, profile_id={profile_id}, hostname={hostname} contained bogus (»{lf}«)'.format(backend=backend, profile_id=profile_id, hostname=hostname, lf=load_factor)) return 'LOAD-DATA-BOGUS' logger_broker.info('loadchecker.check_load(): load factor for backend={backend}, profile_id={profile_id}, hostname={hostname} is: {lf}'.format(backend=backend, profile_id=profile_id, hostname=hostname, lf=load_factor)) return load_factor else: raw_output = "" logger_broker.debug('loadchecker.check_load(): sending backend={backend}, profile_id={profile_id} to load checker service'.format(backend=backend, profile_id=profile_id, hostname=hostname)) try: s.send('{backend}\r{profile_id}\r\n'.format(backend=backend, profile_id=profile_id).encode()) raw_output = s.recv(1024).decode() s.close() except socket.error as e: logger_broker.error('loadchecker.check_load(): failure when sending data to the load checker service socket {socket}: {errmsg}'.format(socket=x2gobroker.defaults.X2GOBROKER_LOADCHECKER_SOCKET, errmsg=str(e))) load_factors = {} items = raw_output.split('\n') for item in items: if ":" in item: key, val = item.split(':', 1) try: if val not in ('HOST-UNREACHABLE', 'LOAD-UNAVAILABLE', 'LOAD-DATA-BOGUS'): load_factors[key] = int(val) else: load_factors[key] = val except ValueError: logger_broker.warning('loadchecker.check_load(): load data for backend={backend}, profile_id={profile_id}, hostname={hostname} contained bogus (»{lf}«)'.format(backend=backend, profile_id=profile_id, hostname=hostname, lf=val)) load_factors[key] = 'LOAD-DATA-BOGUS' logger_broker.info('loadchecker.check_load(): load metrics for backend={backend}, profile_id={profile_id} are: {lf}'.format(backend=backend, profile_id=profile_id, hostname=hostname, lf=load_factors)) return load_factors class LoadChecker(threading.Thread): """\ The :class:`LoadChecker` class provides the functionality of setting up a load checker service. It is the brain of the ``x2gobroker-loadchecker`` executable. With it you can instantiate a new LoadChecker object for querying remote X2Go Broker Agent instances about server/system load, CPU usage, etc. in regular intervals. :param config_file: global ``x2gobroker`` config file :type config_file: a :mod:`configparser` compliant ```` :param config_defaults: default (hard-coded) configuration parameters for all parameters missing in the ``config_file`` :type config_defaults: ``dict`` :param logger: a :mod:`logging` instance :type logger: ```` :param kwargs: Any other parameter (for future features' compatibility, all ignored for now) :type kwargs: ``dict`` """ def __init__(self, config_file=None, config_defaults=None, logger=None, **kwargs): self.logger = logger self.config_file = config_file if self.config_file is None: self.config_file = x2gobroker.defaults.X2GOBROKER_CONFIG self.config_defaults = config_defaults if self.config_defaults is None: self.config_defaults = x2gobroker.defaults.X2GOBROKER_CONFIG_DEFAULTS self.kwargs = kwargs threading.Thread.__init__(self, target=self.loadchecker) self.server_load = {} self.keep_alive = True self.daemon = True def get_server_load(self, backend, profile_id, hostname): """\ Retrieve system load factor for a given server (via broker backend, profile ID and hostname). :param backend: broker backend to query. :type backend: ``str`` :param profile_id: profile ID of the session profile to query :type profile_id: ``str`` :param hostname: hostname of the X2Go Server :type hostname: ``str`` :returns: load factor of the given server (or None if an error occurs) :rtype: ``int`` """ try: return self.server_load[backend][profile_id][hostname] except KeyError: return None def get_profile_load(self, backend, profile_id): """\ Retrieve system load factors for all member servers of the given profile ID (and the given broker backend). :param backend: broker backend to query. :type backend: ``str`` :param profile_id: profile ID of the session profile to query :type profile_id: ``str`` :returns: load factors of the given profile ID (or None if an error occurs) :rtype: ``dict`` """ try: return self.server_load[backend][profile_id] except KeyError: return None def loadchecker(self): """\ This is the actual thread runner of the :class:`LoadChecker`` class. It queries configured / available X2Go Broker Agents in regular intervals about system load, CPU types and usage. """ time_to_sleep = 0 self.config = x2gobroker.config.X2GoBrokerConfigFile(config_files=self.config_file, defaults=self.config_defaults) self.load_checker_intervals = self.config.get_value('global', 'load-checker-intervals') self.broker_backends = [ "_".join(bs.split('_')[1:]) for bs in self.config.list_sections() if bs.startswith('broker_') and self.config.get_value(bs, 'enable') ] while self.keep_alive: # potentially, the X2Go Session Broker can manage different broker backends at the same time, so we initialize # all configured/enabled broker backends self.brokers = {} num_queries = 0 num_failed_queries = 0 if self.logger: self.logger.debug('LoadChecker.loadchecker(): load checker thread waking up...') for backend in self.broker_backends: if backend not in self.server_load: self.server_load[backend] = {} _broker_backend_module = None namespace = {} exec("import x2gobroker.brokers.{backend}_broker as _broker_backend_module".format(backend=backend), namespace) _broker_backend_module = namespace['_broker_backend_module'] self.brokers[backend] = _broker_backend_module.X2GoBroker(config_file=self.config_file, config_defaults=self.config_defaults, **self.kwargs) profile_ids_to_check = [ id for id in self.brokers[backend].get_profile_ids() if self.brokers[backend].use_load_checker(id) ] if self.logger: self.logger.debug('LoadChecker.loadchecker(): backend={backend} -> processing profiles: {profile_ids}'.format(backend=backend, profile_ids=profile_ids_to_check)) for profile_id in profile_ids_to_check: if profile_id not in self.server_load[backend]: self.server_load[backend][profile_id] = {} remote_agents = self.brokers[backend].get_all_remote_agents(profile_id) if self.logger: self.logger.debug('LoadChecker.loadchecker(): querying remote agents for backend={backend}, profile_id={profile_id}: {remote_agents}'.format(backend=backend, profile_id=profile_id, remote_agents=remote_agents)) for remote_agent in remote_agents: _load_factor = x2gobroker.agent.check_load(remote_agent, logger=self.logger) num_queries += 1 if _load_factor is None: if self.logger: self.logger.info('LoadChecker.loadchecker(): backend={backend}, profile_id={profile_id}, hostname={hostname}, load factor not available'.format(backend=backend, profile_id=profile_id, hostname=remote_agent['hostname'])) num_failed_queries += 1 elif type(_load_factor) is int: if self.logger: self.logger.info('LoadChecker.loadchecker(): contacted remote broker agent for backend={backend}, profile_id={profile_id}, hostname={hostname}, new load factor is: {lf}'.format(backend=backend, profile_id=profile_id, hostname=remote_agent['hostname'], lf=_load_factor)) else: if self.logger: self.logger.warning('LoadChecker.loadchecker(): no load factor could be obtained for backend={backend}, profile_id={profile_id}, hostname={hostname}, reason: {reason}'.format(backend=backend, profile_id=profile_id, hostname=remote_agent['hostname'], reason=_load_factor)) self.server_load[backend][profile_id][remote_agent['hostname']] = _load_factor if time_to_sleep > 0: if self.logger: self.logger.debug('LoadChecker.loadchecker(): sleeping for {secs}secs before querying next server'.format(secs=time_to_sleep)) time.sleep(time_to_sleep) # clean up vanished hostnames _hostnames = list(self.server_load[backend][profile_id].keys()) for hostname in _hostnames: if hostname not in [ ra['hostname'] for ra in remote_agents ]: del self.server_load[backend][profile_id][hostname] # clean up vanished profile IDs _profile_ids = copy.deepcopy(list(self.server_load[backend].keys())) for profile_id in _profile_ids: if profile_id not in profile_ids_to_check: del self.server_load[backend][profile_id] # clean up vanished backends _backends = copy.deepcopy(list(self.server_load.keys())) for backend in _backends: if backend not in self.broker_backends: del self.server_load[backend] # don't do all queries every 300-or-so seconds, but distribute next round of queries over the # complete load_checker_intervals range if time_to_sleep == 0: if self.logger: self.logger.debug('LoadChecker.loadchecker(): sleeping for {secs}secs before starting next query cycle'.format(secs=self.load_checker_intervals)) time.sleep(self.load_checker_intervals) if num_queries > 0: if time_to_sleep > 0: if self.logger: self.logger.debug('LoadChecker.loadchecker(): performed {num} queries (failures: {num_failures}), sleeping for {secs}secs before starting next query cycle'.format(num=num_queries, num_failures=num_failed_queries, secs=time_to_sleep)) time.sleep(time_to_sleep) time_to_sleep = self.load_checker_intervals / (num_queries +1) else: if self.logger: self.logger.warning('LoadChecker.loadchecker(): performed {num} queries (failures: {num_failures}) in this cycle, if this message keeps repeating itself, consider disabling the X2Go Broker Load Checker daemon'.format(num=num_queries, num_failures=num_failed_queries, secs=self.load_checker_intervals - time_to_sleep * num_queries)) time_to_sleep = 0 def stop_thread(self): """\ Induce a stop of the running :class:`LoadChecker`' thread. When stopped, no more queries to remote X2Go Servers will be made. """ self.keep_alive = False x2gobroker-0.0.4.1/x2gobroker-loadchecker.service0000644000000000000000000000063213457267612016552 0ustar [Unit] Description=X2Go Session Broker Load Checker Service [Service] User=x2gobroker Group=x2gobroker Type=forking ExecStart=/usr/sbin/x2gobroker-loadchecker --socket-file=/run/x2gobroker/x2gobroker-loadchecker.socket --daemonize -o x2gobroker -g x2gobroker -p 0660 --pidfile=/run/x2gobroker/x2gobroker-loadchecker.pid PIDFile=/run/x2gobroker/x2gobroker-loadchecker.pid [Install] WantedBy=multi-user.target x2gobroker-0.0.4.1/x2gobroker/loggers.py0000644000000000000000000001247713457267612014754 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. """\ The :mod:`x2gobroker.loggers` module provides three different logger objects for different purposes: * ``logger_broker``: Logging of all sort of things happening in X2Go Session Broker * ``logger_access``: Logging of http/https access of the broker's httpd daemon * ``logger_error``: Logging of errors encountered at runtime All ``logger_*`` objects are instantiated via the function :func:`logging.getLogger()` from the :mod:`logging` module.. Depending on the execution context (as a service, in foreground for debugging, as SSH broker), these logging objects are set up slighly different. """ import os import sys import pwd import getpass import logging import logging.config import configparser def init_console_loggers(): """\ Initialize loggers that log to stderr. :returns: a 3-tuple of (logger_broker, logger_access, logger_error) :rtype: ``tuple`` """ logger_root = logging.getLogger() stderr_handler = logging.StreamHandler(sys.stderr) stderr_handler.setFormatter(logging.Formatter(fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt='')) # all loggers stream to stderr... logger_root.addHandler(stderr_handler) logger_broker = logging.getLogger('broker') logger_broker.addHandler(stderr_handler) logger_broker.propagate = 0 logger_access = logging.getLogger('access') logger_access.addHandler(stderr_handler) logger_access.propagate = 0 logger_error = logging.getLogger('error') logger_error.addHandler(stderr_handler) logger_error.propagate = 0 return (logger_broker, logger_access, logger_error) PROG_NAME = os.path.basename(sys.argv[0]) # load the defaults.conf file, if present iniconfig_loaded = None iniconfig_section = '-'.join(PROG_NAME.split('-')[1:]) X2GOBROKER_DEFAULTS = "/etc/x2go/broker/defaults.conf" if os.path.isfile(X2GOBROKER_DEFAULTS) and os.access(X2GOBROKER_DEFAULTS, os.R_OK): iniconfig = configparser.RawConfigParser() iniconfig.optionxform = str iniconfig_loaded = iniconfig.read(X2GOBROKER_DEFAULTS) # normally this would go into defaults.py, however, we do not want to create a dependency loop between loggers.py and defaults.py... if 'X2GOBROKER_DAEMON_USER' in os.environ: X2GOBROKER_DAEMON_USER=os.environ['X2GOBROKER_DAEMON_USER'] elif iniconfig_loaded and iniconfig.has_option(iniconfig_section, 'X2GOBROKER_DAEMON_USER'): X2GOBROKER_DAEMON_USER=iniconfig.get(iniconfig_section, 'X2GOBROKER_DAEMON_USER') elif iniconfig_loaded and iniconfig.has_option('common', 'X2GOBROKER_DAEMON_USER'): X2GOBROKER_DAEMON_USER=iniconfig.get('common', 'X2GOBROKER_DAEMON_USER') else: X2GOBROKER_DAEMON_USER="x2gobroker" if 'X2GOBROKER_LOGCONFIG' in os.environ: X2GOBROKER_LOGCONFIG=os.environ['X2GOBROKER_LOGCONFIG'] elif iniconfig_loaded and iniconfig.has_option(iniconfig_section, 'X2GOBROKER_LOGCONFIG'): X2GOBROKER_LOGCONFIG=iniconfig.get(iniconfig_section, 'X2GOBROKER_LOGCONFIG') elif iniconfig_loaded and iniconfig.has_option('common', 'X2GOBROKER_LOGCONFIG'): X2GOBROKER_LOGCONFIG=iniconfig.get('common', 'X2GOBROKER_LOGCONFIG') else: X2GOBROKER_LOGCONFIG="/etc/x2go/broker/x2gobroker-loggers.conf" # standalone daemon mode (x2gobroker-daemon)? or x2gobroker-ssh command invocation? if ( (getpass.getuser() in (X2GOBROKER_DAEMON_USER, 'root')) or (pwd.getpwuid(os.geteuid()).pw_name in (X2GOBROKER_DAEMON_USER, 'root')) ) and os.path.exists(X2GOBROKER_LOGCONFIG): # we run in standalone daemon mode or broker via SSH (with correct setup), # so let's use the system configuration for logging logging.config.fileConfig(X2GOBROKER_LOGCONFIG) # create loggers logger_broker = logging.getLogger('broker') logger_access = logging.getLogger('access') logger_error = logging.getLogger('error') else: # or interactive mode (called from the cmdline)? logger_broker, logger_access, logger_error = init_console_loggers() def tornado_log_request(handler): """\ Function for overriding the :func:`log_request() ` method in :class:`tornado.web.RequestHandler`. :param handler: handler :type handler: ``obj`` """ if handler.get_status() < 400: log_method = logger_access.info elif handler.get_status() < 500: log_method = logger_access.warning else: log_method = logger_error.error request_time = 1000.0 * handler.request.request_time() log_method("%d %s %.2fms", handler.get_status(), handler._request_summary(), request_time) x2gobroker-0.0.4.1/x2gobroker/nameservices/base_nameservice.py0000644000000000000000000001074313457267612021263 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. class X2GoBrokerNameService(object): def has_user(self, username): """\ Provide information, if the broker knows a given user (or not). :param username: name of the user to check :type username: ``str`` :returns: ``True`` if the user is known to the broker, ``False`` if not :rtype: ``bool`` """ return username in self.get_users() def get_users(self): """\ Retrieve list of users known to the broker. :returns: list of known user names :rtype: ``list`` """ return [] def get_primary_group(self, username): """\ Get the primary group of a given user. If the nameservices backend in use does not support primary groups, an empty string will be returned. :param username: name of the user to get the primary group for :type username: ``str`` :returns: name of the primary group of the given user :rtype: ``str`` """ return '' def has_group(self, group): """\ Provide information, if the broker knows a given group (or not). :param group: name of the group to check :type group: ``str`` :returns: ``True`` if the group is known to the broker, ``False`` if not :rtype: ``bool`` """ return group in self.get_groups() def get_groups(self): """\ Retrieve list of groups known to the broker. :returns: list of known group names :rtype: ``list`` """ return [] def is_group_member(self, username, group, primary_groups=False): """\ Check, if a given user is member of a given group. Optionally, primary group memberships can be considered (or not). :param username: name of the user to check :type username: ``str`` :param group: name of the group to check :type group: ``str`` :param primary_groups: take primary group membership into consideration or not :type primary_groups: ``bool`` :returns: ``True`` if the user is member of the given group, ``False`` if not :rtype: ``bool`` """ _members = self.get_group_members(group, primary_groups=primary_groups) return username in _members def get_group_members(self, group, primary_groups=False): """\ Retrieve a list of users being members of a given group. Optionally, primary group memberships can be considered (or not). :param group: name of the group to retrieve members of :type group: ``str`` :param primary_groups: take primary group membership into consideration or not :type primary_groups: ``bool`` :returns: list of users that are members of the given group :rtype: ``list`` """ return [] def get_user_groups(self, username, primary_groups=False): """\ Retrieve a list of groups that a given user is member of. Optionally, primary group memberships can be considered (or not). :param username: name of the user to retrieve groupm memberships of :type username: ``str`` :param primary_groups: take primary group membership into consideration or not :type primary_groups: ``bool`` :returns: list of groups that the given user is member of :rtype: ``list`` """ _groups = [] for _group in self.get_groups(): if self.is_group_member(username=username, group=_group, primary_groups=primary_groups): _groups.append(_group) return _groups x2gobroker-0.0.4.1/x2gobroker/nameservices/__init__.py0000644000000000000000000000153413457267612017525 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. x2gobroker-0.0.4.1/x2gobroker/nameservices/libnss_nameservice.py0000644000000000000000000000551613457267612021645 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. # modules import pwd import grp # Python X2GoBroker modules from . import base_nameservice as base _pwd = pwd.getpwall() _grp = grp.getgrall() class X2GoBrokerNameService(base.X2GoBrokerNameService): def get_users(self): """\ Retrieve list of users from the POSIX nameservices system. :returns: list of known user names :rtype: ``list`` """ return [ p.pw_name for p in _pwd ] def get_primary_group(self, username): """\ Get the primary group of a given POSIX user. :param username: name of the user to get the primary group for :type username: ``str`` :returns: name of the primary group of the given user :rtype: ``str`` """ prim_gid_number = [ p.pw_gid for p in _pwd if p.pw_name == username ][0] return [ g.gr_name for g in _grp if g.gr_gid == prim_gid_number ][0] def get_groups(self): """\ Retrieve list of groups from the POSIX nameservices system. :returns: list of known group names :rtype: ``list`` """ return [ g.gr_name for g in _grp ] def get_group_members(self, group, primary_groups=False): """\ Retrieve a list of POSIX users being members of a given POSIX group. Optionally, primary group memberships can be considered (or not). :param group: name of the group to retrieve members of :type group: ``str`` :param primary_groups: take primary group membership into consideration or not :type primary_groups: ``bool`` :returns: list of users that are members of the given group :rtype: ``list`` """ _members_from_primgroups = [] if primary_groups: for username in self.get_users(): if group == self.get_primary_group(username): _members_from_primgroups.append(username) return [ u for u in grp.getgrnam(group).gr_mem ] + _members_from_primgroups x2gobroker-0.0.4.1/x2gobroker/nameservices/testsuite_nameservice.py0000644000000000000000000000606113457267612022400 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. # Python X2GoBroker modules from . import base_nameservice as base _users = [ 'maja', 'willi', 'flip', 'kassandra', 'thekla' ] _groups = { 'male': ['willi', 'flip'], 'female': ['maja', 'kassandra', 'thekla'], 'bees': ['maja', 'willi', 'kassandra'], 'grasshoppers': ['flip'], 'spiders': ['thekla'], } class X2GoBrokerNameService(base.X2GoBrokerNameService): def get_users(self): """\ Retrieve hard-coded list of users that we can use for unit testing. :returns: list of known user names :rtype: ``list`` """ return _users def get_primary_group(self, username): """\ In POSIX, the primary group name is equal to the user name. As this is the only straw we can grab during unit tests, we return the username here. :param username: name of the user to get the primary group for :type username: ``str`` :returns: name of the primary group of the given user :rtype: ``str`` """ return username def get_groups(self): """\ Retrieve hard-coded list of groups that we can use for unit testing. :returns: list of known group names :rtype: ``list`` """ return list(_groups.keys()) + _users def get_group_members(self, group, primary_groups=False): """\ Retrieve a list of users being members of a given group. For unit testing, the group membership relations have been hard-coded. Optionally, primary group memberships can be considered (or not). :param group: name of the group to retrieve members of :type group: ``str`` :param primary_groups: take primary group membership into consideration or not :type primary_groups: ``bool`` :returns: list of users that are members of the given group :rtype: ``list`` """ _members = [] if group in list(_groups.keys()): _members.extend(_groups[group]) if primary_groups: for username in self.get_users(): if group == self.get_primary_group(username): _members.append(username) return _members x2gobroker-0.0.4.1/x2gobroker/optional_scripts/base_script.py0000755000000000000000000000536613457267612021206 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2012-2019 by Mike Gabriel # Copyright (C) 2014 by Josh Lukens # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. class X2GoBrokerOptionalScript(object): """\ X2Go Session Broker supports running optional Python code that a site admin can add to the broker installation files. All those optional scripts need to be inherited from this class. """ def run_me(self, username, password, task, profile_id, ip, cookie, authed, server): """\ Dummy :function:``run_me()`` function. If you deploy your own optional scripts with X2Go Session Broker, make sure that your class overrides this function. The broker frontends will try to execute code presented under this method name at pre-auth, post-auth and session-selected. :param script_type: name of the script type to be executed (``pre_auth_scripts``, ``post_auth_scripts``, ``select_session_scripts``) :type script_type: ``str`` :param username: name of the X2Go session user a script will run for :type username: ``str`` :param password: password for the X2Go session :type password: ``str`` :param task: the broker task that currently being processed :type task: ``str`` :param profile_id: the session profile ID that is being operated upon :type profile_id: ``str`` :param ip: the client machine's IP address :type ip: ``str`` :param cookie: the currently valid authentication cookie :type cookie: ``str`` :param authed: authentication status (already authenticated or not) :type authed: ``bool`` :param server: hostname or IP address of the X2Go server being operated upon :type server: ``str`` :returns: Pass-through of the return value returned by the to-be-run optional script (i.e., success or failure) :rtype: ``bool`` """ return username, password, task, profile_id, ip, cookie, authed, server x2gobroker-0.0.4.1/x2gobroker/optional_scripts/__init__.py0000755000000000000000000000162413457267612020440 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2012-2019 by Mike Gabriel # Copyright (C) 2014 by Josh Lukens # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. x2gobroker-0.0.4.1/x2gobroker/_paramiko.py0000644000000000000000000001126313457267612015244 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2010-2019 by Mike Gabriel # # Python X2Go is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. """\ Monkey patches and feature map for Python Paramiko """ import paramiko import platform from x2gobroker.utils import compare_versions PARAMIKO_VERSION = paramiko.__version__.split()[0] PARAMIKO_FEATURE = { 'forward-ssh-agent': compare_versions(PARAMIKO_VERSION, ">=", '1.8.0') and (platform.system() != "Windows"), 'use-compression': compare_versions(PARAMIKO_VERSION, ">=", '1.7.7.1'), 'hash-host-entries': compare_versions(PARAMIKO_VERSION, ">=", '99'), 'host-entries-reloadable': compare_versions(PARAMIKO_VERSION, ">=", '99'), 'preserve-known-hosts': compare_versions(PARAMIKO_VERSION, ">=", '99'), } def _SSHClient_save_host_keys(self, filename): """\ FIXME!!! --- this method should become part of Paramiko This method has been taken from SSHClient class in Paramiko and has been improved and adapted to latest SSH implementations. Save the host keys back to a file. Only the host keys loaded with L{load_host_keys} (plus any added directly) will be saved -- not any host keys loaded with L{load_system_host_keys}. :param filename: the filename to save to :type filename: ``str`` :raises IOError: if the file could not be written """ # update local host keys from file (in case other SSH clients # have written to the known_hosts file meanwhile. if self.known_hosts is not None: self.load_host_keys(self.known_hosts) f = open(filename, 'w') #f.write('# SSH host keys collected by paramiko\n') _host_keys = self.get_host_keys() for hostname, keys in _host_keys.items(): for keytype, key in keys.items(): f.write('%s %s %s\n' % (hostname, keytype, key.get_base64())) f.close() def _HostKeys_load(self, filename): """\ Read a file of known SSH host keys, in the format used by openssh. This type of file unfortunately doesn't exist on Windows, but on posix, it will usually be stored in ``os.path.expanduser("~/.ssh/known_hosts")``. If this method is called multiple times, the host keys are merged, not cleared. So multiple calls to ``load`` will just call L{add}, replacing any existing entries and adding new ones. :param filename: name of the file to read host keys from :type filename: str :raises IOError: if there was an error reading the file """ f = open(filename, 'r') for line in f: line = line.strip() if (len(line) == 0) or (line[0] == '#'): continue e = paramiko.hostkeys.HostKeyEntry.from_line(line) if e is not None: _hostnames = e.hostnames for h in _hostnames: if self.check(h, e.key): e.hostnames.remove(h) if len(e.hostnames): self._entries.append(e) f.close() def _HostKeys_add(self, hostname, keytype, key, hash_hostname=True): """\ Add a host key entry to the table. Any existing entry for a ``(, )`` pair will be replaced. :param hostname: the hostname (or IP) to add :type hostname: str :param keytype: key type (``"ssh-rsa"`` or ``"ssh-dss"``) :type keytype: str :param key: the key to add :type key: :class:`PKey` """ for e in self._entries: if (hostname in e.hostnames) and (e.key.get_name() == keytype): e.key = key return if not hostname.startswith('|1|') and hash_hostname: hostname = self.hash_host(hostname) self._entries.append(paramiko.hostkeys.HostKeyEntry([hostname], key)) def monkey_patch_paramiko(): if not PARAMIKO_FEATURE['preserve-known-hosts']: paramiko.SSHClient.save_host_keys = _SSHClient_save_host_keys if not PARAMIKO_FEATURE['host-entries-reloadable']: paramiko.hostkeys.HostKeys.load = _HostKeys_load if not PARAMIKO_FEATURE['hash-host-entries']: paramiko.hostkeys.HostKeys.add = _HostKeys_add x2gobroker-0.0.4.1/x2gobroker-ssh.sudo0000644000000000000000000000024613457267612014416 0ustar # Allow members of group x2gobroker-users to execute any /usr/lib/x2go/x2gobroker-agent %x2gobroker-users ALL=(:x2gobroker) NOPASSWD: /usr/lib/x2go/x2gobroker-agent x2gobroker-0.0.4.1/x2gobroker/tests/__init__.py0000644000000000000000000000153413457267612016203 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2010-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. x2gobroker-0.0.4.1/x2gobroker/tests/runalltests.py0000644000000000000000000000350713457267612017026 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2010-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. """ This file is a default test runner as found in ZOPE/Plone products. It works fine for any kind of Python unit testing---as we do here for Python X2GoBroker. """ import os import sys if os.path.exists('runalltests.py'): # test is evoked via test.py pass else: # test is evoked via setup.py os.environ.update({'X2GOBROKER_DEBUG': "1"}) os.environ.update({'X2GOBROKER_TESTSUITE': "1"}) os.chdir(os.path.join('x2gobroker', 'tests',)) base = os.getcwd() sys.path.insert(0, os.path.join(os.path.split(os.path.split(os.getcwd())[0])[0])) # prepend the X2GoBroker path (useful for building new packages) sys.path = [os.path.normpath(base)] + sys.path import unittest TestRunner = unittest.TextTestRunner suite = unittest.TestSuite() tests = os.listdir(base) tests = [n[:-3] for n in tests if n.startswith('test') and n.endswith('.py')] for test in tests: m = __import__(test) if hasattr(m, 'test_suite'): suite.addTest(m.test_suite()) TestRunner().run(suite) x2gobroker-0.0.4.1/x2gobroker/tests/test_broker_agent.py0000644000000000000000000006451213457267612020152 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. import unittest import tempfile import time # Python X2GoBroker modules import x2gobroker.brokers.inifile_broker as inifile import x2gobroker.defaults class TestX2GoBrokerAgent(unittest.TestCase): # TEST INTERPRETATION OF REPLIES FROM (FAKED) BROKER AGENT def test_broker_agent_replies(self): _save_local_broker_agent_call = x2gobroker.agent._call_local_broker_agent _save_remote_broker_agent_call = x2gobroker.agent._call_remote_broker_agent _save_portscan = x2gobroker.utils.portscan _save_time_sleep = time.sleep def _call_testsuite_broker_agent(username, task, cmdline_args=[], remote_agent=None, logger=None): if task == 'listsessions': list_sessions = [] if username == 'foo1R': list_sessions = ['30342|foo1R-50-1414759661_stDMATE_dp24|50|host1|R|2014-10-31T13:47:41|c02c7bbe58677a2726f7e456cb398ae4|127.0.0.1|30001|30002|2014-10-31T13:47:43|foo1R|34|30003|-1|-1', ] elif username == 'foo1S': list_sessions = ['30342|foo1S-50-1414759661_stDMATE_dp24|50|host1|S|2014-10-31T13:47:41|c02c7bbe58677a2726f7e456cb398ae4|127.0.0.1|30001|30002|2014-10-31T13:47:43|foo1S|34|30003|-1|-1', ] elif username == 'foo1N': list_sessions = ['30342|foo1N-50-1414759661_stDMATE_dp24|50|host2|S|2014-10-31T13:47:41|c02c7bbe58677a2726f7e456cb398ae4|127.0.0.1|30001|30002|2014-10-31T13:47:43|foo1N|34|30003|-1|-1', ] elif username == 'foo2RS': # the session on host2 is older than the session on host1!!! list_sessions = ['30342|foo2RS-50-1414759789_stDMATE_dp24|50|host2|S|2014-10-31T13:49:51|c02c7bbe58677a2726f7e456cb398ae4|127.0.0.1|30001|30002|2014-10-31T13:49:51|foo2RS|89|30003|-1|-1', '23412|foo2RS-50-1414759661_stDMATE_dp24|50|host1|R|2014-10-31T13:47:43|fasd7asd58677a2726f7e456cb398ae4|127.0.0.1|30001|30002|2014-10-31T13:47:43|foo2RS|34|30003|-1|-1', ] elif username == 'foo3RS': # the session on host2 is older than the session on host1!!! list_sessions = ['30342|foo3RS-50-1414759789_stDMATE_dp24|50|host2|S|2014-10-31T13:49:51|c02c7bbe58677a2726f7e456cb398ae4|127.0.0.1|30001|30002|2014-10-31T13:49:51|foo3RS|89|30003|-1|-1', '23412|foo3RS-50-1414759661_stDMATE_dp24|50|host1|R|2014-10-31T13:47:43|fasd7asd58677a2726f7e456cb398ae4|127.0.0.1|30001|30002|2014-10-31T13:47:43|foo3RS|34|30003|-1|-1', ] return True, list_sessions elif task == 'suspendsession': return True, [] elif task == 'ping': return True, [] return False, [] def _fake_portscan(addr, port=22): return True def _fake_time_sleep(sec): pass x2gobroker.agent._call_local_broker_agent = _call_testsuite_broker_agent x2gobroker.agent._call_remote_broker_agent = _call_testsuite_broker_agent x2gobroker.utils.portscan = _fake_portscan time.sleep = _fake_time_sleep _session_profiles = """ [DEFAULT] command = MATE user = foo broker-agent-query-mode = NONE broker-use-load-checker = false [testprofile1] name = testprofile1 host = host1 broker-agent-query-mode = LOCAL [testprofile2] name = testprofile2 host = host1, host2, host3, host4 broker-agent-query-mode = SSH [testprofile3] name = testprofile3 host = host1.mydomain, host2.mydomain, host3.mydomain, host4.mydomain, host5.mydomain, host6.mydomain broker-agent-query-mode = LOCAL [testprofile4] name = testprofile4 host = host1.mydomain, host2.yourdomain [testprofile5] name = testprofile5 host = host1.mydomain (10.0.2.4), host2.mydomain (10.0.2.5) broker-agent-query-mode = SSH broker-agent-hostkey-policy = WarningPolicy [testprofile6] name = testprofile6 host = host1.mydomain (10.0.2.4), host2.mydomain (10.0.2.5) sshport = 23467 broker-agent-query-mode = SSH broker-agent-hostkey-policy = WarningPolicy [testprofile7] name = testprofile7 host = docker-vm-1 (docker-server:22001), docker-vm-2 (docker-server:22002) broker-agent-query-mode = SSH broker-agent-hostkey-policy = WarningPolicy [testprofile8] name = testprofile8 host = docker-vm-0 (docker-server), docker-vm-1 (docker-server:22001), docker-vm-2 (docker-server:22002) sshport = 22000 broker-agent-query-mode = SSH broker-agent-hostkey-policy = WarningPolicy [testprofile9] name = testprofile9 host = host1.mydomain (10.0.2.4) broker-agent-query-mode = SSH broker-agent-hostkey-policy = AutoAddPolicy [testprofile10] name = testprofile10 host = host1.mydomain (10.0.2.4) broker-agent-query-mode = SSH broker-agent-hostkey-policy = SomeUnkownPolicy """ tf = tempfile.NamedTemporaryFile(mode='w') print(_session_profiles, file=tf) tf.seek(0) inifile_backend = inifile.X2GoBroker(profile_config_file=tf.name) # test with 'testprofile1' # the faked broker agent should report a running session _list1 = inifile_backend.list_profiles(username='foo1R') _profile1 = _list1['testprofile1'] self.assertTrue( ( _profile1['host'] == ['host1'] ) ) self.assertTrue( ( 'status' in _profile1 and _profile1['status'] == 'R' ) ) # here will be a short pause, as we will try to suspend the faked X2Go Session _session1 = inifile_backend.select_session('testprofile1', username='foo1R') # the session broker has detected the running session on host 1 and changed its status to "S" self.assertEqual ( _session1, {'port': 22, 'server': 'host1', 'session_info': '30342|foo1R-50-1414759661_stDMATE_dp24|50|host1|S|2014-10-31T13:47:41|c02c7bbe58677a2726f7e456cb398ae4|127.0.0.1|30001|30002|2014-10-31T13:47:43|foo1R|34|30003|-1|-1', } ) # the faked broker agent should report a suspended session _list1 = inifile_backend.list_profiles(username='foo1S') _profile1 = _list1['testprofile1'] self.assertTrue( ( _profile1['host'] == ['host1'] ) ) self.assertTrue( ( 'status' in _profile1 and _profile1['status'] == 'S' ) ) _session1 = inifile_backend.select_session('testprofile1') self.assertEqual ( _session1, {'port': 22, 'server': 'host1'} ) # the faked broker agent should report a suspended session for host2, nothing for host1 _list1 = inifile_backend.list_profiles(username='foo1N') _profile1 = _list1['testprofile1'] self.assertTrue( ( _profile1['host'] == ['host1'] ) ) self.assertFalse( ( 'status' in _profile1 ) ) _session1 = inifile_backend.select_session('testprofile1') self.assertEqual ( _session1, {'port': 22, 'server': 'host1'} ) # test "testprofile2", always resume suspended sessions first # the faked broker agent should report a suspended session for host1, a running session for host2 # we resume host1 (the broker attempts resumption of suspended sessions, then the take-over of running sessions _list2 = inifile_backend.list_profiles(username='foo2RS') _profile2 = _list2['testprofile2'] _profile2['host'].sort() self.assertTrue( ( _profile2['host'] == ['host2'] ) ) self.assertTrue( ( 'status' in _profile2 and _profile2['status'] == 'S' ) ) _session2 = inifile_backend.select_session('testprofile2', 'foo2RS') self.assertEqual ( _session2, {'port': 22, 'server': 'host2', 'session_info': '30342|foo2RS-50-1414759789_stDMATE_dp24|50|host2|S|2014-10-31T13:49:51|c02c7bbe58677a2726f7e456cb398ae4|127.0.0.1|30001|30002|2014-10-31T13:49:51|foo2RS|89|30003|-1|-1', } ) # test "testprofile3", given host names with a subdomain, but subdomains not in listsessions output # the faked broker agent should report a suspended session for host1, a running session for host2 # we resume host1 (the broker attempts resumption of suspended sessions, then the take-over of running sessions _list3 = inifile_backend.list_profiles(username='foo3RS') _profile3 = _list3['testprofile3'] _profile3['host'].sort() self.assertTrue( ( _profile3['host'] == ['host2'] ) ) self.assertTrue( ( 'status' in _profile3 and _profile3['status'] == 'S' ) ) _session3 = inifile_backend.select_session('testprofile3', 'foo3RS') self.assertEqual ( _session3, {'port': 22, 'server': 'host2.mydomain', 'session_info': '30342|foo3RS-50-1414759789_stDMATE_dp24|50|host2|S|2014-10-31T13:49:51|c02c7bbe58677a2726f7e456cb398ae4|127.0.0.1|30001|30002|2014-10-31T13:49:51|foo3RS|89|30003|-1|-1', } ) _session3 = inifile_backend.select_session('testprofile3', 'foo3RS') # # test "testprofile4" # _profile4 = inifile_backend.get_profile('testprofile4') # _profile4['host'].sort() # self.assertTrue( ( _profile4['host'] == ['host1.mydomain', 'host2.yourdomain'] ) ) # for key in _profile4.keys(): # self.assertFalse( (key.startswith('host') and key != 'host' ) ) # _session4 = inifile_backend.select_session('testprofile4') # self.assertTrue ( _session4['port'] == 22 ) # self.assertTrue ( _session4['server'] in ('host1.mydomain', 'host2.yourdomain') ) # test "testprofile5", test if canonical hostnames and real hostnames differ _list5 = inifile_backend.list_profiles(username='foo5N') _profile5 = _list5['testprofile5'] _profile5['host'].sort() self.assertTrue( _profile5['host'][0] in ('host1.mydomain', 'host2.mydomain') ) self.assertTrue( 'status' not in _profile5 ) i = 0 while i < 10: _remoteagent5 = inifile_backend.get_remote_agent('testprofile5') self.assertTrue( _remoteagent5 == {'hostname': 'host1.mydomain', 'hostaddr': '10.0.2.4', 'port': 22, 'load_factors': {}, 'host_key_policy': 'WarningPolicy'} or _remoteagent5 == {'hostname': 'host2.mydomain', 'hostaddr': '10.0.2.5', 'port': 22, 'load_factors': {}, 'host_key_policy': 'WarningPolicy', } ) _session5 = inifile_backend.select_session('testprofile5', 'foo5N') self.assertTrue( _session5 == {'port': 22, 'server': '10.0.2.4', } or _session5 == {'port': 22, 'server': '10.0.2.5', } ) i += 1 # test "testprofile6", test if canonical hostnames and real hostnames differ and the default # SSH port has been adapted _list6 = inifile_backend.list_profiles(username='foo6N') _profile6 = _list6['testprofile6'] _profile6['host'].sort() self.assertTrue( _profile6['host'][0] in ('host1.mydomain', 'host2.mydomain') ) self.assertTrue( 'status' not in _profile6 ) _remoteagent6 = inifile_backend.get_remote_agent('testprofile6') self.assertTrue( _remoteagent6 == {'hostname': 'host1.mydomain', 'hostaddr': '10.0.2.4', 'port': 23467, 'load_factors': {}, 'host_key_policy': 'WarningPolicy', } or _remoteagent6 == {'hostname': 'host2.mydomain', 'hostaddr': '10.0.2.5', 'port': 23467, 'load_factors': {}, 'host_key_policy': 'WarningPolicy', } ) _session6 = inifile_backend.select_session('testprofile6', 'foo6N') self.assertTrue( _session6 == {'port': 23467, 'server': '10.0.2.4', } or _session6 == {'port': 23467, 'server': '10.0.2.5', } ) _list7 = inifile_backend.list_profiles(username='foo7N') _profile7 = _list7['testprofile7'] _profile7['host'].sort() self.assertTrue( _profile7['host'][0] in ('docker-vm-1', 'docker-vm-2') ) self.assertTrue( 'status' not in _profile7 ) i = 0 while i < 10: _remoteagent7 = inifile_backend.get_remote_agent('testprofile7') self.assertTrue( _remoteagent7 == {'hostname': 'docker-vm-1', 'hostaddr': 'docker-server', 'port': 22001, 'load_factors': {}, 'host_key_policy': 'WarningPolicy', } or _remoteagent7 == {'hostname': 'docker-vm-2', 'hostaddr': 'docker-server', 'port': 22002, 'load_factors': {}, 'host_key_policy': 'WarningPolicy', } ) _session7 = inifile_backend.select_session('testprofile7', 'foo7N') self.assertTrue( _session7 == {'port': 22001, 'server': 'docker-server', } or _session7 == {'port': 22001, 'server': 'docker-server', } ) i += 1 _list8 = inifile_backend.list_profiles(username='foo8N') _profile8 = _list8['testprofile8'] _profile8['host'].sort() self.assertTrue( _profile8['host'][0] in ('docker-vm-0', 'docker-vm-1', 'docker-vm-2') ) self.assertTrue( 'status' not in _profile8 ) i = 0 while i < 10: _remoteagent8 = inifile_backend.get_remote_agent('testprofile8') self.assertTrue( _remoteagent8 == {'hostname': 'docker-vm-0', 'hostaddr': 'docker-server', 'port': 22000, 'load_factors': {}, 'host_key_policy': 'WarningPolicy', } or _remoteagent8 == {'hostname': 'docker-vm-1', 'hostaddr': 'docker-server', 'port': 22001, 'load_factors': {}, 'host_key_policy': 'WarningPolicy', } or _remoteagent8 == {'hostname': 'docker-vm-2', 'hostaddr': 'docker-server', 'port': 22002, 'load_factors': {}, 'host_key_policy': 'WarningPolicy', } ) _session8 = inifile_backend.select_session('testprofile8', 'foo8N') self.assertTrue( _session8 == {'port': 22000, 'server': 'docker-server', } or _session8 == {'port': 22001, 'server': 'docker-server', } or _session8 == {'port': 22001, 'server': 'docker-server', } ) i += 1 # test "testprofile9", test if hostkey policy is propagated from session profile config to remote agent settings _list9 = inifile_backend.list_profiles(username='foo9N') _profile9 = _list9['testprofile9'] _profile9['host'].sort() self.assertTrue( _profile9['host'][0] in ('host1.mydomain') ) self.assertTrue( 'status' not in _profile9 ) _remoteagent9 = inifile_backend.get_remote_agent('testprofile9') self.assertTrue( _remoteagent9 == {'hostname': 'host1.mydomain', 'hostaddr': '10.0.2.4', 'port': 22, 'load_factors': {}, 'host_key_policy': 'AutoAddPolicy'}) # test "testprofile10", test if an invalid hostkey policy is propagated from session profile config to remote agent settings and ignored with RejectPolicy as fallback _list10 = inifile_backend.list_profiles(username='foo10N') _profile10 = _list10['testprofile10'] _profile10['host'].sort() self.assertTrue( _profile10['host'][0] in ('host1.mydomain') ) self.assertTrue( 'status' not in _profile10 ) _remoteagent10 = inifile_backend.get_remote_agent('testprofile10') self.assertTrue( _remoteagent10 == {'hostname': 'host1.mydomain', 'hostaddr': '10.0.2.4', 'port': 22, 'load_factors': {}, 'host_key_policy': 'RejectPolicy'}) x2gobroker.agent._call_local_broker_agent = _save_local_broker_agent_call x2gobroker.agent._call_remote_broker_agent = _save_remote_broker_agent_call x2gobroker.utils.portscan = _save_portscan time.sleep = _save_time_sleep def test_broker_agent_replies_with_offline_servers(self): _save_local_broker_agent_call = x2gobroker.agent._call_local_broker_agent _save_remote_broker_agent_call = x2gobroker.agent._call_remote_broker_agent _save_portscan = x2gobroker.utils.portscan _save_time_sleep = time.sleep self.tbarwos_session_suspended = False def _call_testsuite_broker_agent(username, task, cmdline_args=[], remote_agent=None, logger=None): if task == 'listsessions': list_sessions = [] if username == 'foo4BS1': list_sessions = ['30342|foo4BS1-50-1414759661_stDMATE_dp24|50|host3-with-session|S|2014-10-31T13:47:41|c02c7bbe58677a2726f7e456cb398ae4|127.0.0.1|30001|30002|2014-10-31T13:47:43|foo4BS1|34|30003|-1|-1', ] elif username == 'foo4BS2': list_sessions = ['30342|foo4BS2-50-1414759661_stDMATE_dp24|50|downhost1-with-session|S|2014-10-31T13:47:41|c02c7bbe58677a2726f7e456cb398ae4|127.0.0.1|30001|30002|2014-10-31T13:47:43|foo4BS2|34|30003|-1|-1', ] elif username == 'foo4BS3': list_sessions = ['30342|foo1S-50-1414759661_stDMATE_dp24|50|downhost1-with-session|R|2014-10-31T13:47:41|c02c7bbe58677a2726f7e456cb398ae4|127.0.0.1|30001|30002|2014-10-31T13:47:43|foo1S|34|30003|-1|-1', '30342|foo1S-50-1414759661_stDMATE_dp24|50|host3-with-session|S|2014-10-30T13:47:41|c02c7bbe58677a2726f7e456cb398ae4|127.0.0.1|30001|30002|2014-10-31T13:47:43|foo1S|1474|30003|-1|-1', ] elif username == 'foo4BS4': list_sessions = ['30342|foo1S-50-1414759661_stDMATE_dp24|50|downhost1-with-session|S|2014-10-31T13:47:41|c02c7bbe58677a2726f7e456cb398ae4|127.0.0.1|30001|30002|2014-10-31T13:47:43|foo1S|34|30003|-1|-1', '30342|foo1S-50-1414759661_stDMATE_dp24|50|host3-with-session|R|2014-10-30T13:47:41|c02c7bbe58677a2726f7e456cb398ae4|127.0.0.1|30001|30002|2014-10-31T13:47:43|foo1S|1474|30003|-1|-1', ] return True, list_sessions elif task == 'suspendsession': self.tbarwos_session_suspended = True return True, [] elif task == 'findbusyservers': busy_servers = [] if username == 'fooBS1': busy_servers = [ '7:host1.internal', '2:host2.internal', '1:host3.internal', ] elif username in ('foo4BS1', 'foo4BS2', 'foo4BS3'): busy_servers = [ '2:downhost1-with-session.internal', '1:host2.internal', '3:host3-with-session.internal', ] elif username in ('foo5BS1'): busy_servers = [ '2:downhost1-with-session.internal', '1:host2.internal', '3:host3-with-session.internal', '0:host4.internal', ] return True, busy_servers elif task == 'ping': return True, [] return False, [] def _fake_portscan(addr, port=22): if addr == 'host3.internal': return False if addr.startswith('downhost'): return False return True def _fake_time_sleep(sec): pass x2gobroker.agent._call_local_broker_agent = _call_testsuite_broker_agent x2gobroker.agent._call_remote_broker_agent = _call_testsuite_broker_agent x2gobroker.utils.portscan = _fake_portscan time.sleep = _fake_time_sleep _session_profiles = """ [DEFAULT] command = MATE user = foo broker-agent-query-mode = NONE broker-use-load-checker = false [testprofile1] name = testprofile1 host = host1.internal, host2.internal, host3.internal broker-agent-query-mode = LOCAL broker-portscan-x2goservers = false [testprofile2] name = testprofile1 host = host1.internal, host2.internal, host3.internal broker-agent-query-mode = LOCAL broker-portscan-x2goservers = true [testprofile3] name = testprofile3 host = downhost1.internal, downhost2.internal, downhost3.internal broker-agent-query-mode = LOCAL broker-portscan-x2goservers = true [testprofile4] name = testprofile4 host = downhost1-with-session.internal, host2.internal, host3-with-session.internal broker-agent-query-mode = LOCAL broker-portscan-x2goservers = true [testprofile5] name = testprofile5 host = downhost1-with-session.internal, host2.internal, host3-with-session.internal, host4.internal broker-agent-query-mode = SSH broker-portscan-x2goservers = true broker-autologin = true """ tf = tempfile.NamedTemporaryFile(mode='w') print(_session_profiles, file=tf) tf.seek(0) inifile_backend = inifile.X2GoBroker(profile_config_file=tf.name) i = 0 while i < 10: _session1 = inifile_backend.select_session('testprofile1', username='fooBS1') self.assertTrue ( _session1['server'] == 'host3.internal') i += 1 i = 0 while i < 10: _session2 = inifile_backend.select_session('testprofile2', username='fooBS1') self.assertTrue ( _session2['server'] == 'host2.internal') i += 1 i = 0 while i < 10: _session3 = inifile_backend.select_session('testprofile3', username='fooBS1') self.assertTrue ( _session3['server'] == 'no-X2Go-Server-available') i += 1 i = 0 while i < 10: _session4 = inifile_backend.select_session('testprofile4', username='foo4BS1') self.assertTrue ( _session4['server'] == 'host3-with-session.internal') self.assertTrue ( _session4['session_info'] == '30342|foo4BS1-50-1414759661_stDMATE_dp24|50|host3-with-session|S|2014-10-31T13:47:41|c02c7bbe58677a2726f7e456cb398ae4|127.0.0.1|30001|30002|2014-10-31T13:47:43|foo4BS1|34|30003|-1|-1' ) i += 1 i = 0 while i < 10: _session4 = inifile_backend.select_session('testprofile4', username='foo4BS2') self.assertTrue ( _session4['server'] == 'host2.internal') self.assertFalse ( 'session_info' in _session4 ) i += 1 i = 0 while i < 10: _session4 = inifile_backend.select_session('testprofile4', username='foo4BS3') self.assertTrue ( _session4['server'] == 'host3-with-session.internal') self.assertTrue ( _session4['session_info'] == '30342|foo1S-50-1414759661_stDMATE_dp24|50|host3-with-session|S|2014-10-30T13:47:41|c02c7bbe58677a2726f7e456cb398ae4|127.0.0.1|30001|30002|2014-10-31T13:47:43|foo1S|1474|30003|-1|-1' ) i += 1 i = 0 while i < 10: _session4 = inifile_backend.select_session('testprofile4', username='foo4BS4') self.assertTrue ( _session4['server'] == 'host3-with-session.internal') self.assertTrue ( _session4['session_info'] == '30342|foo1S-50-1414759661_stDMATE_dp24|50|host3-with-session|S|2014-10-30T13:47:41|c02c7bbe58677a2726f7e456cb398ae4|127.0.0.1|30001|30002|2014-10-31T13:47:43|foo1S|1474|30003|-1|-1' ) self.assertTrue ( self.tbarwos_session_suspended ) self.tbarwos_session_suspended = False i += 1 i = 0 while i < 10: _session5 = inifile_backend.select_session('testprofile5', username='foo5BS1') self.assertTrue ( _session5['server'] == 'host4.internal') self.assertFalse ( 'session_info' in _session5 ) i += 1 x2gobroker.agent._call_local_broker_agent = _save_local_broker_agent_call x2gobroker.agent._call_remote_broker_agent = _save_remote_broker_agent_call x2gobroker.utils.portscan = _save_portscan time.sleep = _save_time_sleep def test_get_remote_agent_with_offline_servers(self): _save_local_broker_agent_call = x2gobroker.agent._call_local_broker_agent _save_remote_broker_agent_call = x2gobroker.agent._call_remote_broker_agent _save_portscan = x2gobroker.utils.portscan def _call_testsuite_broker_agent(username, task, cmdline_args=[], remote_agent=None, logger=None): if task == 'ping': return True, [] return False, [] def _fake_portscan(addr, port=22): if addr == 'host3.internal': return False elif addr.startswith('downhost'): return False elif addr == '10.0.2.11': return False elif addr.endswith('.local'): return True elif addr.endswith('.external'): return False return True def _fake_time_sleep(sec): pass x2gobroker.agent._call_local_broker_agent = _call_testsuite_broker_agent x2gobroker.agent._call_remote_broker_agent = _call_testsuite_broker_agent x2gobroker.utils.portscan = _fake_portscan time.sleep = _fake_time_sleep _session_profiles = """ [DEFAULT] command = MATE user = foo broker-agent-query-mode = NONE broker-use-load-checker = false [testprofile1] name = testprofile1 host = downhost1.internal (10.0.2.11), host2.internal (10.0.2.12), host3.internal (10.0.2.13) broker-agent-query-mode = SSH broker-portscan-x2goservers = true [testprofile2] name = testprofile2 host = downhost1.local (downhost1.external), host2.local (host2.external), host3.local (host3.external) broker-agent-query-mode = SSH broker-portscan-x2goservers = true """ tf = tempfile.NamedTemporaryFile(mode='w') print(_session_profiles, file=tf) tf.seek(0) inifile_backend = inifile.X2GoBroker(profile_config_file=tf.name) i = 0 while i < 50: remote_agent = inifile_backend.get_remote_agent('testprofile1') self.assertTrue ( remote_agent['hostaddr'] != '10.0.2.11') i += 1 i = 0 while i < 50: remote_agent = inifile_backend.get_remote_agent('testprofile2') self.assertTrue ( bool(remote_agent) ) self.assertTrue ( remote_agent['hostaddr'] != 'downhost1.external') i += 1 x2gobroker.agent._call_local_broker_agent = _save_local_broker_agent_call x2gobroker.agent._call_remote_broker_agent = _save_remote_broker_agent_call x2gobroker.utils.portscan = _save_portscan def test_suite(): from unittest import TestSuite, makeSuite suite = TestSuite() suite.addTest(makeSuite(TestX2GoBrokerAgent)) return suite x2gobroker-0.0.4.1/x2gobroker/tests/test_broker_base.py0000644000000000000000000016426613457267612017775 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. import unittest import tempfile import copy # Python X2GoBroker modules import x2gobroker.brokers.base_broker as base import x2gobroker.defaults class TestX2GoBrokerBackendBase(unittest.TestCase): def _init_base_backend(self): _config_defaults = copy.deepcopy(x2gobroker.defaults.X2GOBROKER_CONFIG_DEFAULTS) _config_defaults.update({'broker_base': {'enable': True, }, }) _config = "" # use the config that derives directly from the config defaults tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) return base.X2GoBroker(config_file=tf.name, config_defaults=_config_defaults) ### TEST CONFIGURATION: >> enable = true|false def test_is_enabled(self): _config_defaults = copy.deepcopy(x2gobroker.defaults.X2GOBROKER_CONFIG_DEFAULTS) _config_defaults.update({'broker_base': {'enable': True, }, }) _config = """ [broker_base] enable = false """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) base_backend = base.X2GoBroker(config_file=tf.name, config_defaults=_config_defaults) self.assertEqual(base_backend.is_enabled(), False) _config = """ [broker_base] enable = true """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) base_backend = base.X2GoBroker(config_file=tf.name) self.assertEqual(base_backend.is_enabled(), True) tf.close() ### TEST CONFIGURATION: authentication mechanism (default-auth-mech vs. auth-mech in backend config) def test_getauthenticationmechanism(self): _config_defaults = copy.deepcopy(x2gobroker.defaults.X2GOBROKER_CONFIG_DEFAULTS) _config_defaults.update({'broker_base': {'enable': True, }, }) _config = """ [global] default-auth-mech = foo-auth-mech [broker_base] enable = true """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) base_backend = base.X2GoBroker(config_file=tf.name) self.assertEqual(base_backend.get_authentication_mechanism(), 'foo-auth-mech') _config = """ [global] default-auth-mech = foo-auth-mech [broker_base] enable = true auth-mech = bar-auth-mech """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) base_backend = base.X2GoBroker(config_file=tf.name, config_defaults=_config_defaults) self.assertEqual(base_backend.get_authentication_mechanism(), 'bar-auth-mech') tf.close() ### TEST CONFIGURATION: user DB backend (default-user-db vs. user-db in backend config) def test_getuserdbservice(self): _config_defaults = copy.deepcopy(x2gobroker.defaults.X2GOBROKER_CONFIG_DEFAULTS) _config_defaults.update({'broker_base': {'enable': True, }, }) _config = """ [global] default-user-db = foo-user-db [broker_base] enable = true """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) base_backend = base.X2GoBroker(config_file=tf.name) self.assertEqual(base_backend.get_userdb_service(), 'foo-user-db') _config = """ [global] default-user-db = foo-user-db [broker_base] enable = true user-db = bar-user-db """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) base_backend = base.X2GoBroker(config_file=tf.name, config_defaults=_config_defaults) self.assertEqual(base_backend.get_userdb_service(), 'bar-user-db') tf.close() ### TEST CONFIGURATION: group DB backend (default-group-db vs. group-db in backend config) def test_getgroupdbservice(self): _config_defaults = copy.deepcopy(x2gobroker.defaults.X2GOBROKER_CONFIG_DEFAULTS) _config_defaults.update({'broker_base': {'enable': True, }, }) _config = """ [global] default-group-db = foo-group-db [broker_base] enable = true """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) base_backend = base.X2GoBroker(config_file=tf.name) self.assertEqual(base_backend.get_groupdb_service(), 'foo-group-db') _config = """ [global] default-group-db = foo-group-db [broker_base] enable = true group-db = bar-group-db """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) base_backend = base.X2GoBroker(config_file=tf.name, config_defaults=_config_defaults) self.assertEqual(base_backend.get_groupdb_service(), 'bar-group-db') tf.close() def test_nameservicebase(self): _config_defaults = copy.deepcopy(x2gobroker.defaults.X2GOBROKER_CONFIG_DEFAULTS) _config_defaults.update({'broker_base': {'enable': True, }, }) _config = """ [global] default-user-db = base default-group-db = base [broker_base] enable = true """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) base_backend = base.X2GoBroker(config_file=tf.name) self.assertEqual(base_backend.get_users(), []) self.assertEqual(base_backend.has_user('any-user'), False) self.assertEqual(base_backend.get_groups(), []) self.assertEqual(base_backend.has_group('any-group'), False) self.assertEqual(base_backend.is_group_member('any-user', 'any-group'), False) self.assertEqual(base_backend.get_group_members('any-group'), []) def test_nameservicelibnss(self): _config_defaults = copy.deepcopy(x2gobroker.defaults.X2GOBROKER_CONFIG_DEFAULTS) _config_defaults.update({'broker_base': {'enable': True, }, }) _config = """ [global] default-user-db = libnss default-group-db = libnss [broker_base] enable = true """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) base_backend = base.X2GoBroker(config_file=tf.name) self.assertTrue( ( 'root' in base_backend.get_users() ) ) self.assertEqual(base_backend.has_user('root'), True) self.assertTrue( ( 'root' in base_backend.get_groups() ) ) self.assertEqual(base_backend.has_group('root'), True) self.assertEqual(base_backend.is_group_member('root', 'root'), False) self.assertEqual(base_backend.is_group_member('root', 'root', primary_groups=True), True) self.assertTrue( ( 'root' not in base_backend.get_group_members('root') ) ) self.assertTrue( ( 'root' in base_backend.get_group_members('root', primary_groups=True) ) ) def test_nameservicelibnss_primgroup(self): _config_defaults = copy.deepcopy(x2gobroker.defaults.X2GOBROKER_CONFIG_DEFAULTS) _config_defaults.update({'broker_base': {'enable': True, }, }) _config = """ [global] default-user-db = libnss default-group-db = libnss [broker_base] enable = true """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) base_backend = base.X2GoBroker(config_file=tf.name) self.assertTrue(type(base_backend.get_primary_group('root') in (type(''), type('')))) self.assertTrue(type(base_backend.get_primary_group('root') == 'root')) def test_nameservice_nodefaultsinconfig(self): _config_defaults = copy.deepcopy(x2gobroker.defaults.X2GOBROKER_CONFIG_DEFAULTS) _config = """ [global] default-user-db = default-group-db = [broker_base] enable = true """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) base_backend = base.X2GoBroker(config_file=tf.name, config_defaults=_config_defaults) self.assertEqual(base_backend.get_userdb_service(), 'libnss') self.assertEqual(base_backend.get_groupdb_service(), 'libnss') _config_defaults = copy.deepcopy(x2gobroker.defaults.X2GOBROKER_CONFIG_DEFAULTS) _config = """ [global] default-user-db = testsuite default-group-db = testsuite [broker_base] enable = true """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) base_backend = base.X2GoBroker(config_file=tf.name, config_defaults=_config_defaults) self.assertEqual(base_backend.get_userdb_service(), 'testsuite') self.assertEqual(base_backend.get_groupdb_service(), 'testsuite') ### TEST CONFIGURATION: global >> check-credentials = false def test_check_access_nocreds(self): _config_defaults = copy.deepcopy(x2gobroker.defaults.X2GOBROKER_CONFIG_DEFAULTS) _config_defaults.update({'broker_base': {'enable': True, }, }) _config = """ [global] require-password = false """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) base_backend = base.X2GoBroker(config_file=tf.name, config_defaults=_config_defaults) self.assertEqual(base_backend.check_access()[0], True) tf.close() ### TEST PROFILE DEFAULTS: get_profile_defaults() def test_getdefaultprofile(self): base_backend = self._init_base_backend() _expected_profile = { 'command': 'TERMINAL', 'defsndport': True, 'useiconv': False, 'iconvfrom': 'UTF-8', 'height': 600, 'export': '', 'quality': 9, 'fullscreen': False, 'layout': '', 'useexports': True, 'width': 800, 'speed': 2, 'soundsystem': 'pulse', 'print': True, 'type': 'auto', 'sndport': 4713, 'xinerama': True, 'variant': '', 'usekbd': True, 'fstunnel': True, 'applications': ['TERMINAL','WWWBROWSER','MAILCLIENT','OFFICE'], 'multidisp': False, 'sshproxyport': 22, 'sound': True, 'rootless': True, 'iconvto': 'UTF-8', 'soundtunnel': True, 'dpi': 96, 'sshport': 22, 'setdpi': 0, 'user': 'BROKER_USER', 'pack': '16m-jpeg', 'host': ['localhost'], 'directrdp': False, } _profile = base_backend.get_profile_defaults() self.assertEqual(len(list(_expected_profile.keys())), len(list(_profile.keys()))) for key in list(_expected_profile.keys()): self.assertTrue( ( key in list(_profile.keys()) ) ) for key in list(_profile.keys()): self.assertTrue( ( key in list(_expected_profile.keys()) and _profile[key] == _expected_profile[key] ) ) ### TEST ACL DEFAULTS: get_acl_defaults() def test_getdefaultacls(self): base_backend = self._init_base_backend() _expected_acls = { 'acl-users-allow': [], 'acl-users-deny': [], 'acl-users-order': '', 'acl-groups-allow': [], 'acl-groups-deny': [], 'acl-groups-order': '', 'acl-clients-allow': [], 'acl-clients-deny': [], 'acl-clients-order': '', 'acl-any-order': 'deny-allow', } _acls = base_backend.get_acl_defaults() self.assertEqual(len(list(_expected_acls.keys())), len(list(_acls.keys()))) for key in list(_expected_acls.keys()): self.assertTrue( ( key in list(_acls.keys()) ) ) for key in list(_acls.keys()): self.assertTrue( ( key in list(_expected_acls.keys()) and _acls[key] == _expected_acls[key] ) ) ### TEST ACL CHECK: check_profile_acls() def test_checkprofileacls_user_simpletests(self): base_backend = self._init_base_backend() username = 'foo' # no ACLs will grant access acls = { 'acl-users-allow': [], 'acl-users-deny': [], 'acl-users-order': 'deny-allow', } self.assertEqual(base_backend.check_profile_acls(username, acls), True) acls = { 'acl-users-allow': [], 'acl-users-deny': [], 'acl-users-order': 'allow-deny', } self.assertEqual(base_backend.check_profile_acls(username, acls), True) acls = { 'acl-users-allow': ['ALL'], 'acl-users-deny': [], 'acl-users-order': 'deny-allow', } self.assertEqual(base_backend.check_profile_acls(username, acls), True) acls = { 'acl-users-allow': ['ALL'], 'acl-users-deny': [], 'acl-users-order': 'allow-deny', } self.assertEqual(base_backend.check_profile_acls(username, acls), True) acls = { 'acl-users-allow': ['foo'], 'acl-users-deny': [], 'acl-users-order': 'deny-allow', } self.assertEqual(base_backend.check_profile_acls(username, acls), True) acls = { 'acl-users-allow': ['foo'], 'acl-users-deny': [], 'acl-users-order': 'allow-deny', } self.assertEqual(base_backend.check_profile_acls(username, acls), True) acls = { 'acl-users-allow': [], 'acl-users-deny': ['ALL'], 'acl-users-order': 'deny-allow', } self.assertEqual(base_backend.check_profile_acls(username, acls), False) acls = { 'acl-users-allow': [], 'acl-users-deny': ['ALL'], 'acl-users-order': 'allow-deny', } self.assertEqual(base_backend.check_profile_acls(username, acls), False) acls = { 'acl-users-allow': [], 'acl-users-deny': ['foo'], 'acl-users-order': 'deny-allow', } self.assertEqual(base_backend.check_profile_acls(username, acls), False) acls = { 'acl-users-allow': [], 'acl-users-deny': ['foo'], 'acl-users-order': 'allow-deny', } self.assertEqual(base_backend.check_profile_acls(username, acls), False) def test_checkprofileacls_user_combitests(self): base_backend = self._init_base_backend() username = 'foo' acls = { 'acl-users-allow': ['foo'], 'acl-users-deny': ['ALL'], 'acl-users-order': 'deny-allow', } self.assertEqual(base_backend.check_profile_acls(username, acls), False) acls = { 'acl-users-allow': ['foo'], 'acl-users-deny': ['ALL'], 'acl-users-order': 'allow-deny', } self.assertEqual(base_backend.check_profile_acls(username, acls), True) acls = { 'acl-users-allow': ['ALL'], 'acl-users-deny': ['foo'], 'acl-users-order': 'deny-allow', } self.assertEqual(base_backend.check_profile_acls(username, acls), False) acls = { 'acl-users-allow': ['ALL'], 'acl-users-deny': ['foo'], 'acl-users-order': 'allow-deny', } self.assertEqual(base_backend.check_profile_acls(username, acls), True) def test_testsuite_nameservice(self): _config_defaults = copy.deepcopy(x2gobroker.defaults.X2GOBROKER_CONFIG_DEFAULTS) _config = """ [global] default-user-db = testsuite default-group-db = testsuite [broker_base] enable = true """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) base_backend = base.X2GoBroker(config_file=tf.name, config_defaults=_config_defaults) users = base_backend.get_users() users.sort() self.assertEqual(users, ['flip', 'kassandra', 'maja', 'thekla', 'willi']) groups = base_backend.get_groups() groups.sort() _expected_groups = ['bees', 'female', 'grasshoppers', 'male', 'spiders'] + ['flip', 'kassandra', 'maja', 'thekla', 'willi'] _expected_groups.sort() self.assertEqual(groups, _expected_groups) def test_checkprofileacls_group_simpletests(self): _config_defaults = copy.deepcopy(x2gobroker.defaults.X2GOBROKER_CONFIG_DEFAULTS) _config = """ [global] default-user-db = testsuite default-group-db = testsuite [broker_base] enable = true """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) base_backend = base.X2GoBroker(config_file=tf.name, config_defaults=_config_defaults) username = 'willi' acls = { 'acl-groups-allow': ['ALL'], 'acl-groups-order': 'deny-allow', } self.assertEqual(base_backend.check_profile_acls(username, acls), True) acls = { 'acl-groups-allow': ['male'], 'acl-groups-order': 'deny-allow', } self.assertEqual(base_backend.check_profile_acls(username, acls), True) acls = { 'acl-groups-allow': ['female'], 'acl-groups-order': 'deny-allow', } self.assertEqual(base_backend.check_profile_acls(username, acls), False) acls = { 'acl-groups-allow': ['ALL'], 'acl-groups-order': 'allow-deny', } self.assertEqual(base_backend.check_profile_acls(username, acls), True) acls = { 'acl-groups-allow': ['male'], 'acl-groups-order': 'allow-deny', } self.assertEqual(base_backend.check_profile_acls(username, acls), True) acls = { 'acl-groups-allow': ['female'], 'acl-groups-order': 'allow-deny', } self.assertEqual(base_backend.check_profile_acls(username, acls), False) acls = { 'acl-groups-deny': ['ALL'], 'acl-groups-order': 'allow-deny', } self.assertEqual(base_backend.check_profile_acls(username, acls), False) acls = { 'acl-groups-deny': ['male'], 'acl-groups-order': 'allow-deny', } self.assertEqual(base_backend.check_profile_acls(username, acls), False) acls = { 'acl-groups-deny': ['female'], 'acl-groups-order': 'allow-deny', } self.assertEqual(base_backend.check_profile_acls(username, acls), False) acls = { 'acl-groups-deny': ['ALL'], 'acl-groups-order': 'deny-allow', } self.assertEqual(base_backend.check_profile_acls(username, acls), False) acls = { 'acl-groups-deny': ['male'], 'acl-groups-order': 'deny-allow', } self.assertEqual(base_backend.check_profile_acls(username, acls), False) acls = { 'acl-groups-deny': ['female'], 'acl-groups-order': 'deny-allow', } self.assertEqual(base_backend.check_profile_acls(username, acls), False) def test_checkprofileacls_group_primarygroups(self): username_f = 'flip' # is a male grasshopper username_m = 'maja' # is a female bee username_w = 'willi' # is a drone (male bee) # first with order: deny-allow _config_defaults = copy.deepcopy(x2gobroker.defaults.X2GOBROKER_CONFIG_DEFAULTS) _config = """ [global] default-user-db = testsuite default-group-db = testsuite [broker_base] enable = true """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) base_backend = base.X2GoBroker(config_file=tf.name, config_defaults=_config_defaults) acls = { 'acl-groups-deny': ['bees','flip'], 'acl-groups-allow': ['ALL'], 'acl-groups-order': 'deny-allow', } self.assertEqual(base_backend.check_profile_acls(username_m, acls), False) self.assertEqual(base_backend.check_profile_acls(username_f, acls), True) self.assertEqual(base_backend.check_profile_acls(username_w, acls), False) _config_defaults = copy.deepcopy(x2gobroker.defaults.X2GOBROKER_CONFIG_DEFAULTS) _config = """ [global] default-user-db = testsuite default-group-db = testsuite ignore-primary-group-memberships = true [broker_base] enable = true """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) base_backend = base.X2GoBroker(config_file=tf.name, config_defaults=_config_defaults) acls = { 'acl-groups-deny': ['bees','flip'], 'acl-groups-allow': ['ALL'], 'acl-groups-order': 'deny-allow', } self.assertEqual(base_backend.check_profile_acls(username_m, acls), False) self.assertEqual(base_backend.check_profile_acls(username_f, acls), True) self.assertEqual(base_backend.check_profile_acls(username_w, acls), False) _config_defaults = copy.deepcopy(x2gobroker.defaults.X2GOBROKER_CONFIG_DEFAULTS) _config = """ [global] default-user-db = testsuite default-group-db = testsuite ignore-primary-group-memberships = false [broker_base] enable = true """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) base_backend = base.X2GoBroker(config_file=tf.name, config_defaults=_config_defaults) acls = { 'acl-groups-deny': ['bees','flip'], 'acl-groups-allow': ['ALL'], 'acl-groups-order': 'deny-allow', } self.assertEqual(base_backend.check_profile_acls(username_m, acls), False) self.assertEqual(base_backend.check_profile_acls(username_f, acls), False) self.assertEqual(base_backend.check_profile_acls(username_w, acls), False) # now with order: allow-deny _config_defaults = copy.deepcopy(x2gobroker.defaults.X2GOBROKER_CONFIG_DEFAULTS) _config = """ [global] default-user-db = testsuite default-group-db = testsuite [broker_base] enable = true """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) base_backend = base.X2GoBroker(config_file=tf.name, config_defaults=_config_defaults) acls = { 'acl-groups-allow': ['bees','flip'], 'acl-groups-deny': ['ALL'], 'acl-groups-order': 'allow-deny', } self.assertEqual(base_backend.check_profile_acls(username_m, acls), True) self.assertEqual(base_backend.check_profile_acls(username_f, acls), False) self.assertEqual(base_backend.check_profile_acls(username_w, acls), True) _config_defaults = copy.deepcopy(x2gobroker.defaults.X2GOBROKER_CONFIG_DEFAULTS) _config = """ [global] default-user-db = testsuite default-group-db = testsuite ignore-primary-group-memberships = true [broker_base] enable = true """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) base_backend = base.X2GoBroker(config_file=tf.name, config_defaults=_config_defaults) acls = { 'acl-groups-allow': ['bees','flip'], 'acl-groups-deny': ['ALL'], 'acl-groups-order': 'allow-deny', } self.assertEqual(base_backend.check_profile_acls(username_m, acls), True) self.assertEqual(base_backend.check_profile_acls(username_f, acls), False) self.assertEqual(base_backend.check_profile_acls(username_w, acls), True) _config_defaults = copy.deepcopy(x2gobroker.defaults.X2GOBROKER_CONFIG_DEFAULTS) _config = """ [global] default-user-db = testsuite default-group-db = testsuite ignore-primary-group-memberships = false [broker_base] enable = true """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) base_backend = base.X2GoBroker(config_file=tf.name, config_defaults=_config_defaults) acls = { 'acl-groups-allow': ['bees','flip'], 'acl-groups-deny': ['ALL'], 'acl-groups-order': 'allow-deny', } self.assertEqual(base_backend.check_profile_acls(username_m, acls), True) self.assertEqual(base_backend.check_profile_acls(username_f, acls), True) self.assertEqual(base_backend.check_profile_acls(username_w, acls), True) def test_checkprofileacls_group_combitests(self): _config_defaults = copy.deepcopy(x2gobroker.defaults.X2GOBROKER_CONFIG_DEFAULTS) _config = """ [global] default-user-db = testsuite default-group-db = testsuite [broker_base] enable = true """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) base_backend = base.X2GoBroker(config_file=tf.name, config_defaults=_config_defaults) username_f = 'flip' # is a male grasshopper username_m = 'maja' # is a female bee username_w = 'willi' # is a drone (male bee) acls = { 'acl-groups-allow': ['bees'], 'acl-groups-deny': ['ALL'], 'acl-groups-order': 'deny-allow', } self.assertEqual(base_backend.check_profile_acls(username_m, acls), False) self.assertEqual(base_backend.check_profile_acls(username_f, acls), False) self.assertEqual(base_backend.check_profile_acls(username_w, acls), False) acls = { 'acl-groups-allow': ['ALL'], 'acl-groups-deny': ['bees'], 'acl-groups-order': 'allow-deny', } self.assertEqual(base_backend.check_profile_acls(username_m, acls), True) self.assertEqual(base_backend.check_profile_acls(username_f, acls), True) self.assertEqual(base_backend.check_profile_acls(username_w, acls), True) acls = { 'acl-groups-allow': ['ALL'], 'acl-groups-deny': ['bees'], 'acl-groups-order': 'deny-allow', } self.assertEqual(base_backend.check_profile_acls(username_m, acls), False) self.assertEqual(base_backend.check_profile_acls(username_f, acls), True) self.assertEqual(base_backend.check_profile_acls(username_w, acls), False) acls = { 'acl-groups-allow': ['bees'], 'acl-groups-deny': ['ALL'], 'acl-groups-order': 'allow-deny', } self.assertEqual(base_backend.check_profile_acls(username_m, acls), True) self.assertEqual(base_backend.check_profile_acls(username_f, acls), False) self.assertEqual(base_backend.check_profile_acls(username_w, acls), True) acls = { 'acl-groups-allow': ['male'], 'acl-groups-deny': ['bees'], 'acl-groups-order': 'deny-allow', } self.assertEqual(base_backend.check_profile_acls(username_m, acls), False) self.assertEqual(base_backend.check_profile_acls(username_f, acls), True) self.assertEqual(base_backend.check_profile_acls(username_w, acls), False) acls = { 'acl-groups-allow': ['male'], 'acl-groups-deny': ['bees'], 'acl-groups-order': 'allow-deny', } self.assertEqual(base_backend.check_profile_acls(username_m, acls), False) self.assertEqual(base_backend.check_profile_acls(username_f, acls), True) self.assertEqual(base_backend.check_profile_acls(username_w, acls), True) def test_checkprofileacls_userandgroup_combitests(self): _config_defaults = copy.deepcopy(x2gobroker.defaults.X2GOBROKER_CONFIG_DEFAULTS) _config = """ [global] default-user-db = testsuite default-group-db = testsuite [broker_base] enable = true """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) base_backend = base.X2GoBroker(config_file=tf.name, config_defaults=_config_defaults) username_f = 'flip' username_k = 'kassandra' username_m = 'maja' username_t = 'thekla' username_w = 'willi' acls = { 'acl-users-allow': ['flip'], 'acl-users-deny': [], 'acl-users-order': 'deny-allow', 'acl-groups-allow': ['female','male'], 'acl-groups-deny': ['spiders'], 'acl-groups-order': 'deny-allow', } self.assertEqual(base_backend.check_profile_acls(username_f, acls), True) self.assertEqual(base_backend.check_profile_acls(username_k, acls), True) self.assertEqual(base_backend.check_profile_acls(username_m, acls), True) self.assertEqual(base_backend.check_profile_acls(username_t, acls), False) self.assertEqual(base_backend.check_profile_acls(username_w, acls), True) acls = { 'acl-users-allow': ['flip'], 'acl-users-deny': [], 'acl-users-order': 'deny-allow', 'acl-groups-allow': ['female','male'], 'acl-groups-deny': ['spiders'], 'acl-groups-order': 'allow-deny', } self.assertEqual(base_backend.check_profile_acls(username_f, acls), True) self.assertEqual(base_backend.check_profile_acls(username_k, acls), True) self.assertEqual(base_backend.check_profile_acls(username_m, acls), True) self.assertEqual(base_backend.check_profile_acls(username_t, acls), True) self.assertEqual(base_backend.check_profile_acls(username_w, acls), True) acls = { 'acl-users-allow': ['flip'], 'acl-users-deny': [], 'acl-users-order': 'allow-deny', 'acl-groups-allow': ['male','female'], 'acl-groups-deny': ['spiders','grasshoppers'], 'acl-groups-order': 'allow-deny', } self.assertEqual(base_backend.check_profile_acls(username_f, acls), True) self.assertEqual(base_backend.check_profile_acls(username_k, acls), True) self.assertEqual(base_backend.check_profile_acls(username_m, acls), True) self.assertEqual(base_backend.check_profile_acls(username_t, acls), True) self.assertEqual(base_backend.check_profile_acls(username_w, acls), True) acls = { 'acl-users-allow': [], 'acl-users-deny': [], 'acl-users-order': 'allow-deny', 'acl-groups-allow': ['male','female'], 'acl-groups-deny': ['spiders','grasshoppers'], 'acl-groups-order': 'allow-deny', } self.assertEqual(base_backend.check_profile_acls(username_f, acls), True) self.assertEqual(base_backend.check_profile_acls(username_k, acls), True) self.assertEqual(base_backend.check_profile_acls(username_m, acls), True) self.assertEqual(base_backend.check_profile_acls(username_t, acls), True) self.assertEqual(base_backend.check_profile_acls(username_w, acls), True) acls = { 'acl-users-allow': [], 'acl-users-deny': [], 'acl-users-order': 'allow-deny', 'acl-groups-allow': ['male','female'], 'acl-groups-deny': ['spiders','grasshoppers'], 'acl-groups-order': 'deny-allow', } self.assertEqual(base_backend.check_profile_acls(username_f, acls), False) self.assertEqual(base_backend.check_profile_acls(username_k, acls), True) self.assertEqual(base_backend.check_profile_acls(username_m, acls), True) self.assertEqual(base_backend.check_profile_acls(username_t, acls), False) self.assertEqual(base_backend.check_profile_acls(username_w, acls), True) acls = { 'acl-users-allow': ['flip', 'thekla'], 'acl-users-deny': ['maja'], 'acl-users-order': 'allow-deny', 'acl-groups-allow': ['male','female'], 'acl-groups-deny': ['spiders','grasshoppers'], 'acl-groups-order': 'deny-allow', } self.assertEqual(base_backend.check_profile_acls(username_f, acls), True) self.assertEqual(base_backend.check_profile_acls(username_k, acls), True) self.assertEqual(base_backend.check_profile_acls(username_m, acls), False) self.assertEqual(base_backend.check_profile_acls(username_t, acls), True) self.assertEqual(base_backend.check_profile_acls(username_w, acls), True) acls = { 'acl-users-allow': ['flip', 'thekla'], 'acl-users-deny': ['maja'], 'acl-users-order': 'deny-allow', 'acl-groups-allow': ['female'], 'acl-groups-deny': ['spiders','grasshoppers'], 'acl-groups-order': 'deny-allow', } self.assertEqual(base_backend.check_profile_acls(username_f, acls), True) self.assertEqual(base_backend.check_profile_acls(username_k, acls), True) self.assertEqual(base_backend.check_profile_acls(username_m, acls), False) self.assertEqual(base_backend.check_profile_acls(username_t, acls), True) self.assertEqual(base_backend.check_profile_acls(username_w, acls), False) def test_clientaddress_recognition(self): base_backend = self._init_base_backend() ipv4_1 = '127.0.0.1' ipv4_2 = '10.0.0.1' ipv4_3 = '123.456.789.101' ipv6_1 = '::1' ipv6_2 = 'fe80::4f8:900:e5d:2' ipv6_3 = 'fe80:0000:0000:0000:04f8:0900:0e5d:0002' ipv6_4 = 'fe80:wxyz:0000:0000:04f8:0900:0e5d:0002' base_backend.set_client_address(ipv4_1) self.assertEqual(base_backend.get_client_address(), ipv4_1) self.assertEqual(base_backend.get_client_address_type(), 4) base_backend.set_client_address(ipv4_2) self.assertEqual(base_backend.get_client_address(), ipv4_2) self.assertEqual(base_backend.get_client_address_type(), 4) self.assertRaises(ValueError, base_backend.set_client_address, ipv4_3) base_backend.set_client_address(ipv6_1) self.assertEqual(base_backend.get_client_address(), ipv6_1) self.assertEqual(base_backend.get_client_address_type(), 6) base_backend.set_client_address(ipv6_2) self.assertEqual(base_backend.get_client_address(), ipv6_2) self.assertEqual(base_backend.get_client_address_type(), 6) base_backend.set_client_address(ipv6_3) self.assertEqual(base_backend.get_client_address(), 'fe80::4f8:900:e5d:2') self.assertEqual(base_backend.get_client_address_type(), 6) self.assertRaises(ValueError, base_backend.set_client_address, (ipv6_4)) def test_checkprofileacls_clientipv4_simpletests(self): base_backend = self._init_base_backend() username = 'foo' base_backend.set_client_address('10.0.2.14') # no ACLs will grant access acls = { 'acl-clients-allow': [], 'acl-clients-deny': [], 'acl-clients-order': 'deny-allow', } self.assertEqual(base_backend.check_profile_acls(username, acls), True) acls = { 'acl-clients-allow': [], 'acl-clients-deny': [], 'acl-clients-order': 'allow-deny', } self.assertEqual(base_backend.check_profile_acls(username, acls), True) acls = { 'acl-clients-allow': ['ALL'], 'acl-clients-deny': [], 'acl-clients-order': 'deny-allow', } self.assertEqual(base_backend.check_profile_acls(username, acls), True) acls = { 'acl-clients-allow': ['ALL'], 'acl-clients-deny': [], 'acl-clients-order': 'allow-deny', } self.assertEqual(base_backend.check_profile_acls(username, acls), True) acls = { 'acl-clients-allow': ['10.0.2.14'], 'acl-clients-deny': [], 'acl-clients-order': 'deny-allow', } self.assertEqual(base_backend.check_profile_acls(username, acls), True) acls = { 'acl-clients-allow': ['10.0.2.14'], 'acl-clients-deny': [], 'acl-clients-order': 'allow-deny', } self.assertEqual(base_backend.check_profile_acls(username, acls), True) acls = { 'acl-clients-allow': [], 'acl-clients-deny': ['ALL'], 'acl-clients-order': 'deny-allow', } self.assertEqual(base_backend.check_profile_acls(username, acls), False) acls = { 'acl-clients-allow': [], 'acl-clients-deny': ['ALL'], 'acl-clients-order': 'allow-deny', } self.assertEqual(base_backend.check_profile_acls(username, acls), False) acls = { 'acl-clients-allow': [], 'acl-clients-deny': ['10.0.2.14'], 'acl-clients-order': 'deny-allow', } self.assertEqual(base_backend.check_profile_acls(username, acls), False) acls = { 'acl-clients-allow': [], 'acl-clients-deny': ['10.0.2.14'], 'acl-clients-order': 'allow-deny', } self.assertEqual(base_backend.check_profile_acls(username, acls), False) def test_checkprofileacls_clientipv4_combitests(self): base_backend = self._init_base_backend() username = 'foo' ipv4_1 = '10.0.2.14' ipv4_2 = '10.0.3.14' ipv4_3 = '8.8.8.8' base_backend.set_client_address(ipv4_1) # no ACLs will grant access acls = { 'acl-clients-allow': ['10.0.2.0/24'], 'acl-clients-deny': ['ALL'], 'acl-clients-order': 'allow-deny', } base_backend.set_client_address(ipv4_1) self.assertEqual(base_backend.check_profile_acls(username, acls), True) base_backend.set_client_address(ipv4_2) self.assertEqual(base_backend.check_profile_acls(username, acls), False) base_backend.set_client_address(ipv4_3) self.assertEqual(base_backend.check_profile_acls(username, acls), False) acls = { 'acl-clients-allow': ['ALL'], 'acl-clients-deny': ['10.0.2.0/24'], 'acl-clients-order': 'deny-allow', } base_backend.set_client_address(ipv4_1) self.assertEqual(base_backend.check_profile_acls(username, acls), False) base_backend.set_client_address(ipv4_2) self.assertEqual(base_backend.check_profile_acls(username, acls), True) base_backend.set_client_address(ipv4_3) self.assertEqual(base_backend.check_profile_acls(username, acls), True) acls = { 'acl-clients-allow': ['10.0.2.0/24'], 'acl-clients-deny': ['10.0.0.0/16', '10.0.3.0/24'], 'acl-clients-order': 'allow-deny', } base_backend.set_client_address(ipv4_1) self.assertEqual(base_backend.check_profile_acls(username, acls), True) base_backend.set_client_address(ipv4_2) self.assertEqual(base_backend.check_profile_acls(username, acls), False) base_backend.set_client_address(ipv4_3) self.assertEqual(base_backend.check_profile_acls(username, acls), False) acls = { 'acl-clients-allow': ['10.0.0.0/16', '10.0.3.0/24'], 'acl-clients-deny': ['10.0.2.0/24'], 'acl-clients-order': 'deny-allow', } base_backend.set_client_address(ipv4_1) self.assertEqual(base_backend.check_profile_acls(username, acls), False) base_backend.set_client_address(ipv4_2) self.assertEqual(base_backend.check_profile_acls(username, acls), True) base_backend.set_client_address(ipv4_3) self.assertEqual(base_backend.check_profile_acls(username, acls), False) def test_checkprofileacls_clientipv6_simpletests(self): base_backend = self._init_base_backend() username = 'foo' base_backend.set_client_address('fe80::4f8:900:e5d:2') # no ACLs will grant access acls = { 'acl-clients-allow': [], 'acl-clients-deny': [], 'acl-clients-order': 'deny-allow', } self.assertEqual(base_backend.check_profile_acls(username, acls), True) acls = { 'acl-clients-allow': [], 'acl-clients-deny': [], 'acl-clients-order': 'allow-deny', } self.assertEqual(base_backend.check_profile_acls(username, acls), True) acls = { 'acl-clients-allow': ['ALL'], 'acl-clients-deny': [], 'acl-clients-order': 'deny-allow', } self.assertEqual(base_backend.check_profile_acls(username, acls), True) acls = { 'acl-clients-allow': ['ALL'], 'acl-clients-deny': [], 'acl-clients-order': 'allow-deny', } self.assertEqual(base_backend.check_profile_acls(username, acls), True) acls = { 'acl-clients-allow': ['fe80::4f8:900:e5d:2'], 'acl-clients-deny': [], 'acl-clients-order': 'allow-deny', } self.assertEqual(base_backend.check_profile_acls(username, acls), True) acls = { 'acl-clients-allow': ['fe80::4f8:900:e5d:2'], 'acl-clients-deny': [], 'acl-clients-order': 'deny-allow', } self.assertEqual(base_backend.check_profile_acls(username, acls), True) acls = { 'acl-clients-allow': [], 'acl-clients-deny': ['ALL'], 'acl-clients-order': 'allow-deny', } self.assertEqual(base_backend.check_profile_acls(username, acls), False) acls = { 'acl-clients-allow': [], 'acl-clients-deny': ['ALL'], 'acl-clients-order': 'deny-allow', } self.assertEqual(base_backend.check_profile_acls(username, acls), False) acls = { 'acl-clients-allow': [], 'acl-clients-deny': ['fe80::4f8:900:e5d:2'], 'acl-clients-order': 'allow-deny', } self.assertEqual(base_backend.check_profile_acls(username, acls), False) acls = { 'acl-clients-allow': [], 'acl-clients-deny': ['fe80::4f8:900:e5d:2'], 'acl-clients-order': 'deny-allow', } self.assertEqual(base_backend.check_profile_acls(username, acls), False) def test_checkprofileacls_clientipv6_combitests(self): base_backend = self._init_base_backend() username = 'foo' ipv6_1 = 'fe80::4f8:900:e5d:2' ipv6_2 = 'fe80::1:4f8:900:e5d:2' ipv6_3 = '2001:1af8:4050::2' base_backend.set_client_address(ipv6_1) # no ACLs will grant access acls = { 'acl-clients-allow': ['fe80::/64'], 'acl-clients-deny': ['ALL'], 'acl-clients-order': 'allow-deny', } base_backend.set_client_address(ipv6_1) self.assertEqual(base_backend.check_profile_acls(username, acls), True) base_backend.set_client_address(ipv6_2) self.assertEqual(base_backend.check_profile_acls(username, acls), False) base_backend.set_client_address(ipv6_3) self.assertEqual(base_backend.check_profile_acls(username, acls), False) acls = { 'acl-clients-allow': ['ALL'], 'acl-clients-deny': ['fe80::/64'], 'acl-clients-order': 'deny-allow', } base_backend.set_client_address(ipv6_1) self.assertEqual(base_backend.check_profile_acls(username, acls), False) base_backend.set_client_address(ipv6_2) self.assertEqual(base_backend.check_profile_acls(username, acls), True) base_backend.set_client_address(ipv6_3) self.assertEqual(base_backend.check_profile_acls(username, acls), True) acls = { 'acl-clients-allow': ['fe80::/64'], 'acl-clients-deny': ['fe80::/56','fe80:0:0:1::/64'], 'acl-clients-order': 'allow-deny', } base_backend.set_client_address(ipv6_1) self.assertEqual(base_backend.check_profile_acls(username, acls), True) base_backend.set_client_address(ipv6_2) self.assertEqual(base_backend.check_profile_acls(username, acls), False) base_backend.set_client_address(ipv6_3) self.assertEqual(base_backend.check_profile_acls(username, acls), False) acls = { 'acl-clients-allow': ['fe80::/56','fe80:0:0:1::/64'], 'acl-clients-deny': ['fe80::/64'], 'acl-clients-order': 'deny-allow', } base_backend.set_client_address(ipv6_1) self.assertEqual(base_backend.check_profile_acls(username, acls), False) base_backend.set_client_address(ipv6_2) self.assertEqual(base_backend.check_profile_acls(username, acls), True) base_backend.set_client_address(ipv6_3) self.assertEqual(base_backend.check_profile_acls(username, acls), False) def test_checkprofileacls_userandgroupandclient_combitests(self): _config_defaults = copy.deepcopy(x2gobroker.defaults.X2GOBROKER_CONFIG_DEFAULTS) _config = """ [global] default-user-db = testsuite default-group-db = testsuite [broker_base] enable = true """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) base_backend = base.X2GoBroker(config_file=tf.name, config_defaults=_config_defaults) username_f = 'flip' username_k = 'kassandra' username_m = 'maja' username_t = 'thekla' username_w = 'willi' ipv4_1 = '10.0.2.14' ipv4_2 = '10.0.3.14' ipv4_3 = '8.8.8.8' ipv6_1 = 'fe80::4f8:900:e5d:2' ipv6_2 = 'fe80::1:4f8:900:e5d:2' ipv6_3 = '2001:1af8:4050::2' acls = { 'acl-users-allow': ['flip'], 'acl-users-deny': [], 'acl-users-order': 'deny-allow', 'acl-groups-allow': ['female','male'], 'acl-groups-deny': ['spiders'], 'acl-groups-order': 'deny-allow', 'acl-clients-allow': ['fe80:0:0:1::/64','10.0.3.0/24'], 'acl-clients-deny': ['ALL'], 'acl-clients-order': 'allow-deny', } base_backend.set_client_address(ipv4_1) self.assertEqual(base_backend.check_profile_acls(username_f, acls), False) self.assertEqual(base_backend.check_profile_acls(username_k, acls), False) self.assertEqual(base_backend.check_profile_acls(username_m, acls), False) self.assertEqual(base_backend.check_profile_acls(username_t, acls), False) self.assertEqual(base_backend.check_profile_acls(username_w, acls), False) base_backend.set_client_address(ipv4_2) self.assertEqual(base_backend.check_profile_acls(username_f, acls), True) self.assertEqual(base_backend.check_profile_acls(username_k, acls), True) self.assertEqual(base_backend.check_profile_acls(username_m, acls), True) self.assertEqual(base_backend.check_profile_acls(username_t, acls), False) self.assertEqual(base_backend.check_profile_acls(username_w, acls), True) base_backend.set_client_address(ipv4_3) self.assertEqual(base_backend.check_profile_acls(username_f, acls), False) self.assertEqual(base_backend.check_profile_acls(username_k, acls), False) self.assertEqual(base_backend.check_profile_acls(username_m, acls), False) self.assertEqual(base_backend.check_profile_acls(username_t, acls), False) self.assertEqual(base_backend.check_profile_acls(username_w, acls), False) base_backend.set_client_address(ipv6_1) self.assertEqual(base_backend.check_profile_acls(username_f, acls), False) self.assertEqual(base_backend.check_profile_acls(username_k, acls), False) self.assertEqual(base_backend.check_profile_acls(username_m, acls), False) self.assertEqual(base_backend.check_profile_acls(username_t, acls), False) self.assertEqual(base_backend.check_profile_acls(username_w, acls), False) base_backend.set_client_address(ipv6_2) self.assertEqual(base_backend.check_profile_acls(username_f, acls), True) self.assertEqual(base_backend.check_profile_acls(username_k, acls), True) self.assertEqual(base_backend.check_profile_acls(username_m, acls), True) self.assertEqual(base_backend.check_profile_acls(username_t, acls), False) self.assertEqual(base_backend.check_profile_acls(username_w, acls), True) base_backend.set_client_address(ipv6_3) self.assertEqual(base_backend.check_profile_acls(username_f, acls), False) self.assertEqual(base_backend.check_profile_acls(username_k, acls), False) self.assertEqual(base_backend.check_profile_acls(username_m, acls), False) self.assertEqual(base_backend.check_profile_acls(username_t, acls), False) self.assertEqual(base_backend.check_profile_acls(username_w, acls), False) acls = { 'acl-users-allow': ['flip'], 'acl-users-deny': [], 'acl-users-order': 'deny-allow', 'acl-groups-allow': ['female','male'], 'acl-groups-deny': ['spiders'], 'acl-groups-order': 'allow-deny', 'acl-clients-allow': ['fe80::/64','10.0.2.0/24'], 'acl-clients-deny': ['ALL'], 'acl-clients-order': 'allow-deny', } base_backend.set_client_address(ipv4_1) self.assertEqual(base_backend.check_profile_acls(username_f, acls), True) self.assertEqual(base_backend.check_profile_acls(username_k, acls), True) self.assertEqual(base_backend.check_profile_acls(username_m, acls), True) self.assertEqual(base_backend.check_profile_acls(username_t, acls), True) self.assertEqual(base_backend.check_profile_acls(username_w, acls), True) base_backend.set_client_address(ipv4_2) self.assertEqual(base_backend.check_profile_acls(username_f, acls), False) self.assertEqual(base_backend.check_profile_acls(username_k, acls), False) self.assertEqual(base_backend.check_profile_acls(username_m, acls), False) self.assertEqual(base_backend.check_profile_acls(username_t, acls), False) self.assertEqual(base_backend.check_profile_acls(username_w, acls), False) base_backend.set_client_address(ipv4_3) self.assertEqual(base_backend.check_profile_acls(username_f, acls), False) self.assertEqual(base_backend.check_profile_acls(username_k, acls), False) self.assertEqual(base_backend.check_profile_acls(username_m, acls), False) self.assertEqual(base_backend.check_profile_acls(username_t, acls), False) self.assertEqual(base_backend.check_profile_acls(username_w, acls), False) base_backend.set_client_address(ipv6_1) self.assertEqual(base_backend.check_profile_acls(username_f, acls), True) self.assertEqual(base_backend.check_profile_acls(username_k, acls), True) self.assertEqual(base_backend.check_profile_acls(username_m, acls), True) self.assertEqual(base_backend.check_profile_acls(username_t, acls), True) self.assertEqual(base_backend.check_profile_acls(username_w, acls), True) base_backend.set_client_address(ipv6_2) self.assertEqual(base_backend.check_profile_acls(username_f, acls), False) self.assertEqual(base_backend.check_profile_acls(username_k, acls), False) self.assertEqual(base_backend.check_profile_acls(username_m, acls), False) self.assertEqual(base_backend.check_profile_acls(username_t, acls), False) self.assertEqual(base_backend.check_profile_acls(username_w, acls), False) base_backend.set_client_address(ipv6_3) self.assertEqual(base_backend.check_profile_acls(username_f, acls), False) self.assertEqual(base_backend.check_profile_acls(username_k, acls), False) self.assertEqual(base_backend.check_profile_acls(username_m, acls), False) self.assertEqual(base_backend.check_profile_acls(username_t, acls), False) self.assertEqual(base_backend.check_profile_acls(username_w, acls), False) acls = { 'acl-users-allow': [], 'acl-users-deny': [], 'acl-users-order': 'allow-deny', 'acl-groups-allow': ['male','female'], 'acl-groups-deny': ['spiders','grasshoppers'], 'acl-groups-order': 'deny-allow', 'acl-clients-allow': ['ALL'], 'acl-clients-deny': ['fe80::/56','10.0.0.0/8'], 'acl-clients-order': 'deny-allow', } base_backend.set_client_address(ipv4_1) self.assertEqual(base_backend.check_profile_acls(username_f, acls), False) self.assertEqual(base_backend.check_profile_acls(username_k, acls), False) self.assertEqual(base_backend.check_profile_acls(username_m, acls), False) self.assertEqual(base_backend.check_profile_acls(username_t, acls), False) self.assertEqual(base_backend.check_profile_acls(username_w, acls), False) base_backend.set_client_address(ipv4_2) self.assertEqual(base_backend.check_profile_acls(username_f, acls), False) self.assertEqual(base_backend.check_profile_acls(username_k, acls), False) self.assertEqual(base_backend.check_profile_acls(username_m, acls), False) self.assertEqual(base_backend.check_profile_acls(username_t, acls), False) self.assertEqual(base_backend.check_profile_acls(username_w, acls), False) base_backend.set_client_address(ipv4_3) self.assertEqual(base_backend.check_profile_acls(username_f, acls), False) self.assertEqual(base_backend.check_profile_acls(username_k, acls), True) self.assertEqual(base_backend.check_profile_acls(username_m, acls), True) self.assertEqual(base_backend.check_profile_acls(username_t, acls), False) self.assertEqual(base_backend.check_profile_acls(username_w, acls), True) base_backend.set_client_address(ipv6_1) self.assertEqual(base_backend.check_profile_acls(username_f, acls), False) self.assertEqual(base_backend.check_profile_acls(username_k, acls), False) self.assertEqual(base_backend.check_profile_acls(username_m, acls), False) self.assertEqual(base_backend.check_profile_acls(username_t, acls), False) self.assertEqual(base_backend.check_profile_acls(username_w, acls), False) base_backend.set_client_address(ipv6_2) self.assertEqual(base_backend.check_profile_acls(username_f, acls), False) self.assertEqual(base_backend.check_profile_acls(username_k, acls), False) self.assertEqual(base_backend.check_profile_acls(username_m, acls), False) self.assertEqual(base_backend.check_profile_acls(username_t, acls), False) self.assertEqual(base_backend.check_profile_acls(username_w, acls), False) base_backend.set_client_address(ipv6_3) self.assertEqual(base_backend.check_profile_acls(username_f, acls), False) self.assertEqual(base_backend.check_profile_acls(username_k, acls), True) self.assertEqual(base_backend.check_profile_acls(username_m, acls), True) self.assertEqual(base_backend.check_profile_acls(username_t, acls), False) self.assertEqual(base_backend.check_profile_acls(username_w, acls), True) ### TEST CONFIGURATION: retrieve the initial authentication ID (my-cookie) from config file ### or separate (more secure) file (my-cookie-file). def test_get_mycookie(self): _config_defaults = copy.deepcopy(x2gobroker.defaults.X2GOBROKER_CONFIG_DEFAULTS) _config_defaults.update({'broker_base': {'enable': True, }, }) _config = """ """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) base_backend = base.X2GoBroker(config_file=tf.name, config_defaults=_config_defaults) self.assertNotEqual(base_backend.get_my_cookie(), 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx') tf.close() _config = """ [global] my-cookie = f57866be-bc67-4642-86b4-f644b54031c8 """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) base_backend = base.X2GoBroker(config_file=tf.name, config_defaults=_config_defaults) self.assertEqual(base_backend.get_my_cookie(), 'f57866be-bc67-4642-86b4-f644b54031c8') tf.close() _config = """ [global] my-cookie = f57866be-bc67-4642-86b4-f644b54031c8 my-cookie-file = /etc/x2go/somefile/somewhere """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) base_backend = base.X2GoBroker(config_file=tf.name, config_defaults=_config_defaults) self.assertEqual(base_backend.get_my_cookie(), 'f57866be-bc67-4642-86b4-f644b54031c8') tf.close() temp_fh = tempfile.NamedTemporaryFile(mode='w') temp_fh.write('d1b8043e-b748-48c4-907f-7c798c4dc746') temp_fh.seek(0) _config = """ [global] my-cookie = f57866be-bc67-4642-86b4-f644b54031c8 my-cookie-file = %s """ % temp_fh.name tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) base_backend = base.X2GoBroker(config_file=tf.name, config_defaults=_config_defaults) self.assertNotEqual(base_backend.get_my_cookie(), 'f57866be-bc67-4642-86b4-f644b54031c8') self.assertEqual(base_backend.get_my_cookie(), 'd1b8043e-b748-48c4-907f-7c798c4dc746') temp_fh.close() tf.close() temp_fh = tempfile.NamedTemporaryFile(mode='w') temp_fh.write(""" # Some comment... # Another comment... d1b8043e-b748-48c4-907f-7c798c4dc746 d1b8043e-b748-48c4-907f-7c798c4dc747 # above hash is wrong... """ ) temp_fh.seek(0) _config = """ [global] my-cookie = f57866be-bc67-4642-86b4-f644b54031c8 my-cookie-file = %s """ % temp_fh.name tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) base_backend = base.X2GoBroker(config_file=tf.name, config_defaults=_config_defaults) self.assertNotEqual(base_backend.get_my_cookie(), 'f57866be-bc67-4642-86b4-f644b54031c8') self.assertEqual(base_backend.get_my_cookie(), 'd1b8043e-b748-48c4-907f-7c798c4dc746') temp_fh.close() tf.close() temp_fh = open('../../etc/broker/x2gobroker.authid') temp_fh.seek(0) _config = """ [global] my-cookie = f57866be-bc67-4642-86b4-f644b54031c8 my-cookie-file = %s """ % temp_fh.name tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) base_backend = base.X2GoBroker(config_file=tf.name, config_defaults=_config_defaults) self.assertNotEqual(base_backend.get_my_cookie(), 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx') self.assertEqual(base_backend.get_my_cookie(), 'f57866be-bc67-4642-86b4-f644b54031c8') temp_fh.close() tf.close() def test_suite(): from unittest import TestSuite, makeSuite suite = TestSuite() suite.addTest(makeSuite(TestX2GoBrokerBackendBase)) return suite x2gobroker-0.0.4.1/x2gobroker/tests/test_broker_inifile.py0000644000000000000000000006764413457267612020504 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. import unittest import tempfile import copy # Python X2GoBroker modules import x2gobroker.brokers.inifile_broker as inifile import x2gobroker.defaults class TestX2GoBrokerBackendInifile(unittest.TestCase): ### TEST SESSION PROFILES: get_profile_ids() def test_getprofileids(self): inifile_backend = inifile.X2GoBroker(profile_config_file='../../etc/broker/x2gobroker-sessionprofiles.conf') _profile_ids = inifile_backend.get_profile_ids() self.assertEqual(len(_profile_ids), 3) _expected_profile_ids = [ "localhost-kde", "localhost-mate", "localhost-shadow", ] for _id in _profile_ids: self.assertTrue( ( _id in _expected_profile_ids ) ) for _id in _expected_profile_ids: self.assertTrue( ( _id in _profile_ids ) ) ### TEST SESSION PROFILES: get_profile() (check that the default key is _not_ present) def test_getprofile_defaultkey(self): inifile_backend = inifile.X2GoBroker(profile_config_file='../../etc/broker/x2gobroker-sessionprofiles.conf') _profile_ids = inifile_backend.get_profile_ids() for _profile_id in _profile_ids: self.assertTrue( ( 'default' not in list(inifile_backend.get_profile(_profile_id).keys()) ) ) # TEST COMPLETION OF DEFAULTS FROM CODE IN defaults.py def test_getprofilecompletion(self): _session_profiles = """ [DEFAULT] exports = fullscreen = false width = 800 height = 600 applications = TERMINAL, WWWBROWSER [testprofile] user = foo command = GNOME """ tf = tempfile.NamedTemporaryFile(mode='w') print(_session_profiles, file=tf) tf.seek(0) inifile_backend = inifile.X2GoBroker(profile_config_file=tf.name) _expected_defaults = copy.deepcopy(x2gobroker.defaults.X2GOBROKER_SESSIONPROFILE_DEFAULTS['DEFAULT']) for key in list(copy.deepcopy(_expected_defaults).keys()): if key.startswith('acl-'): del _expected_defaults[key] _expected_defaults.update( { 'exports': '', 'fullscreen': False, 'width': 800, 'height': 600, 'applications': ['TERMINAL','WWWBROWSER',], 'user': 'foo', 'command': 'GNOME', } ) # just testing the directrdp hard-coded defaults _expected_defaults.update( { 'directrdp': False, } ) _expected_profile = copy.deepcopy(_expected_defaults) _profile = inifile_backend.get_profile('testprofile') for key in list(_expected_profile.keys()): self.assertTrue( ( key in list(_profile.keys()) ) ) for key in list(_profile.keys()): self.assertTrue( ( key in list(_expected_profile.keys()) and _profile[key] == _expected_profile[key] ) ) ### TEST SESSION PROFILES: get_profile_defaults() def test_getprofiledefaults(self): inifile_backend = inifile.X2GoBroker(profile_config_file='../../etc/broker/x2gobroker-sessionprofiles.conf') _profile_defaults = inifile_backend.get_profile_defaults() _expected_profile_defaults = copy.deepcopy(x2gobroker.defaults.X2GOBROKER_SESSIONPROFILE_DEFAULTS['DEFAULT']) for key in copy.deepcopy(_expected_profile_defaults): if key.startswith('acl-'): del _expected_profile_defaults[key] for _param in _profile_defaults: self.assertTrue( ( _param in list(_expected_profile_defaults.keys()) ) ) for _param in _expected_profile_defaults: self.assertTrue( ( _param in list(_profile_defaults.keys()) and _profile_defaults[_param] == _expected_profile_defaults[_param] ) ) ### TEST SESSION PROFILES: get_profile(profile_id) def test_getprofile(self): _session_profiles = """ [DEFAULT] exports = fullscreen = false width = 800 height = 600 applications = TERMINAL, WWWBROWSER [testprofile1] user = foo command = GNOME [testprofile2] user = bar command = KDE fullscreen = true [testprofile3] user = bar command = KDE fullscreen = true acl-users-deny = ALL acl-users-allow = foo,bar acl-users-order = deny-allow [testprofile4] user = bar command = gnomE """ tf = tempfile.NamedTemporaryFile(mode='w') print(_session_profiles, file=tf) tf.seek(0) inifile_backend = inifile.X2GoBroker(profile_config_file=tf.name) _expected_defaults = copy.deepcopy(x2gobroker.defaults.X2GOBROKER_SESSIONPROFILE_DEFAULTS['DEFAULT']) for key in list(copy.deepcopy(_expected_defaults).keys()): if key.startswith('acl-'): del _expected_defaults[key] _expected_defaults.update( { 'exports': '', 'fullscreen': False, 'width': 800, 'height': 600, 'applications': ['TERMINAL','WWWBROWSER',], } ) _expected_profile1 = copy.deepcopy(_expected_defaults) _expected_profile1.update({ 'user': 'foo', 'command': 'GNOME', }) _expected_profile2 = copy.deepcopy(_expected_defaults) _expected_profile2.update({ 'user': 'bar', 'command': 'KDE', 'fullscreen': True, }) _expected_profile3 = copy.deepcopy(_expected_defaults) _expected_profile3.update({ 'user': 'bar', 'command': 'KDE', 'fullscreen': True, }) _expected_profile4 = copy.deepcopy(_expected_defaults) _expected_profile4.update({ 'user': 'bar', 'command': 'gnomE', # mixture of lower and uppercase in get_profile() }) _profile1 = inifile_backend.get_profile('testprofile1') for key in list(_expected_profile1.keys()): self.assertTrue( ( key in list(_profile1.keys()) ) ) for key in list(_profile1.keys()): self.assertTrue( ( key in list(_expected_profile1.keys()) and _profile1[key] == _expected_profile1[key] ) ) _profile2 = inifile_backend.get_profile('testprofile2') for key in list(_expected_profile2.keys()): self.assertTrue( ( key in list(_profile2.keys()) ) ) for key in list(_profile2.keys()): self.assertTrue( ( key in list(_expected_profile2.keys()) ) and ( _profile2[key] == _expected_profile2[key] ) ) _profile3 = inifile_backend.get_profile('testprofile3') for key in list(_expected_profile3.keys()): self.assertTrue( ( key in list(_profile3.keys()) ) ) for key in list(_profile3.keys()): self.assertTrue( ( key in list(_expected_profile3.keys()) ) and ( _profile3[key] == _expected_profile3[key] ) ) _profile4 = inifile_backend.get_profile('testprofile4') for key in list(_expected_profile4.keys()): self.assertTrue( ( key in list(_profile4.keys()) ) ) for key in list(_profile4.keys()): self.assertTrue( ( key in list(_expected_profile4.keys()) ) and ( _profile4[key] == _expected_profile4[key] ) ) ### TEST SESSION PROFILES: get_profile_acls(profile_id) def test_getprofileacls(self): _session_profiles = """ [DEFAULT] exports = fullscreen = false width = 800 height = 600 applications = TERMINAL, WWWBROWSER acl-clients-deny = ALL acl-clients-allow = 10.0.0.0/16,10.1.0.0/16,admin-1.intern,admin-2.intern [testprofile1] user = foo command = GNOME [testprofile2] user = foo command = GNOME acl-clients-deny = 10.0.2.0/24,ALL [testprofile3] user = bar command = KDE fullscreen = true acl-users-deny = ALL acl-users-allow = foo,bar acl-users-order = deny-allow """ tf = tempfile.NamedTemporaryFile(mode='w') print(_session_profiles, file=tf) tf.seek(0) inifile_backend = inifile.X2GoBroker(profile_config_file=tf.name) _expected_acl_defaults = { 'acl-clients-deny': ['ALL'], 'acl-clients-allow': ['10.0.0.0/16','10.1.0.0/16','admin-1.intern','admin-2.intern'], 'acl-any-order': 'deny-allow', } _expected_acls_profile1 = copy.deepcopy(_expected_acl_defaults) _expected_acls_profile2 = copy.deepcopy(_expected_acl_defaults) _expected_acls_profile2.update({ 'acl-clients-deny': ['10.0.2.0/24','ALL'], 'acl-any-order': 'deny-allow', }) _expected_acls_profile3 = copy.deepcopy(_expected_acl_defaults) _expected_acls_profile3.update({ 'acl-users-deny': ['ALL'], 'acl-users-allow': ['foo','bar'], 'acl-users-order': 'deny-allow', 'acl-any-order': 'deny-allow', }) _acls_profile1 = inifile_backend.get_profile_acls('testprofile1') for key in list(_expected_acls_profile1.keys()): self.assertTrue( ( key in list(_acls_profile1.keys()) ) ) for key in list(_acls_profile1.keys()): self.assertTrue( ( key in list(_expected_acls_profile1.keys()) and _acls_profile1[key] == _expected_acls_profile1[key] ) ) _acls_profile2 = inifile_backend.get_profile_acls('testprofile2') for key in list(_expected_acls_profile2.keys()): self.assertTrue( ( key in list(_acls_profile2.keys()) ) ) for key in list(_acls_profile2.keys()): self.assertTrue( ( key in list(_expected_acls_profile2.keys()) ) and ( _acls_profile2[key] == _expected_acls_profile2[key] ) ) _acls_profile3 = inifile_backend.get_profile_acls('testprofile3') for key in list(_expected_acls_profile3.keys()): self.assertTrue( ( key in list(_acls_profile3.keys()) ) ) for key in list(_acls_profile3.keys()): self.assertTrue( ( key in list(_expected_acls_profile3.keys()) ) and ( _acls_profile3[key] == _expected_acls_profile3[key] ) ) ### TEST: list_profiles() method def test_listprofiles(self): username = 'foo' _session_profiles = """ [DEFAULT] exports = fullscreen = false width = 800 height = 600 applications = TERMINAL, WWWBROWSER [testprofile1] user = command = GNOME [testprofile2] user = command = XFCE [testprofile3] user = command = KDE fullscreen = true """ tf = tempfile.NamedTemporaryFile(mode='w') print(_session_profiles, file=tf) tf.seek(0) inifile_backend = inifile.X2GoBroker(profile_config_file=tf.name) profile_ids = inifile_backend.get_profile_ids() profile_ids.sort() list_of_profile_ids = list(inifile_backend.list_profiles(username).keys()) list_of_profile_ids.sort() self.assertEqual(profile_ids, list_of_profile_ids) ### TEST: list_profiles() method, check override of rootless param for DESKTOP sessions def test_listprofiles_rootlessoverride(self): username = 'foo' _session_profiles = """ [DEFAULT] rootless = true command = TERMINAL [testprofile1] user = [testprofile2] user = command = XFCE [testprofile3] user = command = KDE rootless = false [testprofile4] user = command = gnomE """ tf = tempfile.NamedTemporaryFile(mode='w') print(_session_profiles, file=tf) tf.seek(0) inifile_backend = inifile.X2GoBroker(profile_config_file=tf.name) profiles = inifile_backend.list_profiles(username) self.assertEqual(profiles['testprofile1']['command'], 'TERMINAL') self.assertEqual(profiles['testprofile1']['rootless'], True) self.assertEqual(profiles['testprofile2']['command'], 'XFCE') self.assertEqual(profiles['testprofile2']['rootless'], False) self.assertEqual(profiles['testprofile3']['command'], 'KDE') self.assertEqual(profiles['testprofile3']['rootless'], False) self.assertEqual(profiles['testprofile4']['command'], 'GNOME') # uppercase! self.assertEqual(profiles['testprofile4']['rootless'], False) ### TEST: list_profiles() method check acl capability def test_listprofileswithacls(self): _config_defaults = copy.deepcopy(x2gobroker.defaults.X2GOBROKER_CONFIG_DEFAULTS) _config = """ [global] default-user-db = testsuite default-group-db = testsuite [inifile] enable = true """ tfc = tempfile.NamedTemporaryFile(mode='w') print(_config, file=tfc) tfc.seek(0) _session_profiles = """ [DEFAULT] exports = fullscreen = false width = 800 height = 600 applications = TERMINAL, WWWBROWSER acl-groups-allow = bees acl-groups-deny = ALL acl-groups-order = allow-deny [testprofile1] user = command = GNOME acl-users-allow = flip acl-users-order = allow-deny [testprofile2] user = command = XFCE acl-users-allow = thekla acl-users-order = allow-deny [testprofile3] user = command = KDE fullscreen = true acl-users-deny = willi acl-users-order = deny-allow """ tfs = tempfile.NamedTemporaryFile(mode='w') print(_session_profiles, file=tfs) tfs.seek(0) inifile_backend = inifile.X2GoBroker(config_file=tfc.name, config_defaults=_config_defaults, profile_config_file=tfs.name) username_f = 'flip' list_of_profiles = inifile_backend.list_profiles(username_f) list_of_profile_ids = list(list_of_profiles.keys()) list_of_profile_ids.sort() self.assertEqual(list_of_profile_ids, ['testprofile1']) self.assertEqual(list_of_profiles['testprofile1']['command'], 'GNOME') username_m = 'maja' list_of_profiles = inifile_backend.list_profiles(username_m) list_of_profile_ids = list(list_of_profiles.keys()) list_of_profile_ids.sort() self.assertEqual(list_of_profile_ids, ['testprofile1', 'testprofile2', 'testprofile3']) self.assertEqual(list_of_profiles['testprofile1']['command'], 'GNOME') self.assertEqual(list_of_profiles['testprofile2']['command'], 'XFCE') self.assertEqual(list_of_profiles['testprofile3']['command'], 'KDE') username_k = 'kassandra' list_of_profiles = inifile_backend.list_profiles(username_k) list_of_profile_ids = list(list_of_profiles.keys()) list_of_profile_ids.sort() self.assertEqual(list_of_profile_ids, ['testprofile1', 'testprofile2', 'testprofile3']) self.assertEqual(list_of_profiles['testprofile1']['command'], 'GNOME') self.assertEqual(list_of_profiles['testprofile2']['command'], 'XFCE') self.assertEqual(list_of_profiles['testprofile3']['command'], 'KDE') username_t = 'thekla' list_of_profiles = inifile_backend.list_profiles(username_t) list_of_profile_ids = list(list_of_profiles.keys()) list_of_profile_ids.sort() self.assertEqual(list_of_profile_ids, ['testprofile2']) self.assertEqual(list_of_profiles['testprofile2']['command'], 'XFCE') username_w = 'willi' list_of_profiles = inifile_backend.list_profiles(username_w) list_of_profile_ids = list(list_of_profiles.keys()) list_of_profile_ids.sort() self.assertEqual(list_of_profile_ids, ['testprofile1', 'testprofile2']) self.assertEqual(list_of_profiles['testprofile1']['command'], 'GNOME') self.assertEqual(list_of_profiles['testprofile2']['command'], 'XFCE') ### TEST: select_session() method def test_sessionselection(self): _save_portscan = x2gobroker.utils.portscan def _fake_portscan(addr, port=22): return True x2gobroker.utils.portscan = _fake_portscan _config_defaults = copy.deepcopy(x2gobroker.defaults.X2GOBROKER_CONFIG_DEFAULTS) _config = """ [global] default-user-db = testsuite default-group-db = testsuite [inifile] enable = true """ tfc = tempfile.NamedTemporaryFile(mode='w') print(_config, file=tfc) tfc.seek(0) _session_profiles = """ [testprofile1] name = TEST-1 host = test-1.local [testprofile2] name = TEST-2 host = test-2.local [testprofile3] name = TEST-3 host = test-3.local sshport = 44566 [testprofile4] name = TEST-4 host = test-4 (10.0.2.4) [testprofile5] name = TEST-5 host = test-5.local (10.0.2.5) [testprofile6] name = TEST-6 host = test-6.local (test-6.extern) [testprofile7] name = TEST-7 host = test-7 (-test-6.extern) """ tfs = tempfile.NamedTemporaryFile(mode='w') print(_session_profiles, file=tfs) tfs.seek(0) inifile_backend = inifile.X2GoBroker(config_file=tfc.name, config_defaults=_config_defaults, profile_config_file=tfs.name) _expected_result_1 = { 'server': 'test-1.local', 'port': 22, } _expected_result_2 = { 'server': 'test-2.local', 'port': 22, } _expected_result_3 = { 'server': 'test-3.local', 'port': 44566, } _expected_result_4 = { 'server': '10.0.2.4', 'port': 22, } _expected_result_5 = { 'server': '10.0.2.5', 'port': 22, } _expected_result_6 = { 'server': 'test-6.extern', 'port': 22, } _expected_result_7 = { 'server': 'test-7', 'port': 22, } self.assertEqual(inifile_backend.select_session('testprofile1'), _expected_result_1) self.assertEqual(inifile_backend.select_session('testprofile2'), _expected_result_2) self.assertEqual(inifile_backend.select_session('testprofile3'), _expected_result_3) self.assertEqual(inifile_backend.select_session('testprofile4'), _expected_result_4) self.assertEqual(inifile_backend.select_session('testprofile5'), _expected_result_5) self.assertEqual(inifile_backend.select_session('testprofile6'), _expected_result_6) self.assertEqual(inifile_backend.select_session('testprofile7'), _expected_result_7) x2gobroker.utils.portscan = _save_portscan # TEST MULTI-HOST GET_PROFILE / SELECT_SESSION def test_multihost_profiles(self): _save_portscan = x2gobroker.utils.portscan def _fake_portscan(addr, port=22): return True x2gobroker.utils.portscan = _fake_portscan _session_profiles = """ [DEFAULT] command = MATE user = foo [testprofile1] host = host1 [testprofile2] host = host1, host2 [testprofile3] host = host1.mydomain, host2.mydomain [testprofile4] host = host1.mydomain, host2.yourdomain [testprofile5] host = host1.mydomain (10.0.2.4), host2.mydomain (10.0.2.5) [testprofile6] host = host1.mydomain (10.0.2.4), host2.mydomain (10.0.2.5) sshport = 23467 [testprofile7] host = docker-vm-1 (localhost:22001), docker-vm-2 (localhost:22002) [testprofile8] host = docker-vm-0 (localhost), docker-vm-1 (localhost:22001), docker-vm-2 (localhost:22002) sshport = 22000 """ tf = tempfile.NamedTemporaryFile(mode='w') print(_session_profiles, file=tf) tf.seek(0) inifile_backend = inifile.X2GoBroker(profile_config_file=tf.name) # test "testprofile1" _profile1 = inifile_backend.get_profile('testprofile1') self.assertTrue( ( _profile1['host'] == ['host1'] ) ) for key in list(_profile1.keys()): self.assertFalse( (key.startswith('host') and key != 'host' ) ) _session1 = inifile_backend.select_session('testprofile1') self.assertEqual ( _session1, {'port': 22, 'server': 'host1'} ) # test "testprofile2" _profile2 = inifile_backend.get_profile('testprofile2') _profile2['host'].sort() self.assertTrue( ( _profile2['host'] == ['host1', 'host2'] ) ) for key in list(_profile2.keys()): self.assertFalse( (key.startswith('host') and key != 'host' ) ) _session2 = inifile_backend.select_session('testprofile2') self.assertTrue ( _session2['port'] == 22 ) self.assertTrue ( _session2['server'] in ('host1', 'host2') ) # test "testprofile3" _profile3 = inifile_backend.get_profile('testprofile3') _profile3['host'].sort() _profile5 = inifile_backend.get_profile('testprofile5') self.assertTrue( ( _profile3['host'] == ['host1.mydomain', 'host2.mydomain'] ) ) for key in list(_profile3.keys()): self.assertFalse( (key.startswith('host') and key != 'host' ) ) _session3 = inifile_backend.select_session('testprofile3') self.assertTrue ( _session3['port'] == 22 ) self.assertTrue ( _session3['server'] in ('host1.mydomain', 'host2.mydomain') ) # test "testprofile4" _profile4 = inifile_backend.get_profile('testprofile4') _profile4['host'].sort() self.assertTrue( ( _profile4['host'] == ['host1.mydomain', 'host2.yourdomain'] ) ) for key in list(_profile4.keys()): self.assertFalse( (key.startswith('host') and key != 'host' ) ) _session4 = inifile_backend.select_session('testprofile4') self.assertTrue ( _session4['port'] == 22 ) self.assertTrue ( _session4['server'] in ('host1.mydomain', 'host2.yourdomain') ) # test "testprofile5" _profile5 = inifile_backend.get_profile('testprofile5') _profile5['host'].sort() self.assertTrue( ( _profile5['host'] == ['host1.mydomain', 'host2.mydomain'] ) ) self.assertTrue( 'host=host1.mydomain' in _profile5 ) self.assertTrue( 'host=host2.mydomain' in _profile5 ) self.assertTrue( _profile5['host=host1.mydomain'] == '10.0.2.4' ) self.assertTrue( _profile5['host=host2.mydomain'] == '10.0.2.5' ) self.assertTrue( _profile5['sshport'] == 22 ) _session5 = inifile_backend.select_session('testprofile5') self.assertTrue ( _session5['port'] == 22 ) self.assertTrue ( _session5['server'] in ('10.0.2.4', '10.0.2.5') ) # test "testprofile6" _profile6 = inifile_backend.get_profile('testprofile6') _profile6['host'].sort() self.assertTrue( ( _profile6['host'] == ['host1.mydomain', 'host2.mydomain'] ) ) self.assertTrue( 'host=host1.mydomain' in _profile6 ) self.assertTrue( 'host=host2.mydomain' in _profile6 ) self.assertTrue( _profile6['host=host1.mydomain'] == '10.0.2.4' ) self.assertTrue( _profile6['host=host2.mydomain'] == '10.0.2.5' ) self.assertTrue( _profile6['sshport'] == 23467 ) i = 0 while i < 10: _session6 = inifile_backend.select_session('testprofile6') self.assertTrue ( _session6['port'] == 23467 ) self.assertTrue ( _session6['server'] in ('10.0.2.4', '10.0.2.5') ) i += 1 # test "testprofile7" _profile7 = inifile_backend.get_profile('testprofile7') _profile7['host'].sort() self.assertTrue( ( _profile7['host'] == ['docker-vm-1', 'docker-vm-2'] ) ) self.assertTrue( 'host=docker-vm-1' in _profile7 ) self.assertTrue( 'host=docker-vm-2' in _profile7 ) self.assertTrue( _profile7['host=docker-vm-1'] == 'localhost' ) self.assertTrue( _profile7['host=docker-vm-2'] == 'localhost' ) self.assertTrue( _profile7['sshport'] == 22 ) self.assertTrue( _profile7['sshport=docker-vm-1'] == 22001 ) self.assertTrue( _profile7['sshport=docker-vm-2'] == 22002 ) i = 0 while i < 10: _session7 = inifile_backend.select_session('testprofile7') self.assertTrue ( _session7['port'] in (22001, 22002) ) self.assertTrue ( _session7['server'] == 'localhost' ) i += 1 # test "testprofile8" _profile8 = inifile_backend.get_profile('testprofile8') _profile8['host'].sort() self.assertTrue( ( _profile8['host'] == ['docker-vm-0', 'docker-vm-1', 'docker-vm-2'] ) ) self.assertTrue( 'host=docker-vm-0' in _profile8 ) self.assertTrue( 'host=docker-vm-1' in _profile8 ) self.assertTrue( 'host=docker-vm-2' in _profile8 ) self.assertTrue( _profile8['host=docker-vm-0'] == 'localhost' ) self.assertTrue( _profile8['host=docker-vm-1'] == 'localhost' ) self.assertTrue( _profile8['host=docker-vm-2'] == 'localhost' ) self.assertTrue( _profile8['sshport'] == 22000 ) self.assertFalse( 'sshport=docker-vm-0' in _profile8 ) self.assertTrue( _profile8['sshport=docker-vm-1'] == 22001 ) self.assertTrue( _profile8['sshport=docker-vm-2'] == 22002 ) i = 0 while i < 10: _session8 = inifile_backend.select_session('testprofile8', username='foo') self.assertTrue ( _session8['port'] in (22000, 22001, 22002) ) self.assertTrue ( _session8['server'] == 'localhost' ) i += 1 x2gobroker.utils.portscan = _save_portscan def test_multihost_profiles_with_offline_servers(self): _save_portscan = x2gobroker.utils.portscan def _fake_portscan(addr, port=22): if addr == 'host1.mydomain': return True if addr == 'host2.mydomain': return True if addr == 'host2.internal': return True if addr == 'host3.internal': return True return False x2gobroker.utils.portscan = _fake_portscan _session_profiles = """ [DEFAULT] command = MATE user = foo [testprofile1] host = host1.mydomain, host2.mydomain, host3.mydomain broker-portscan-x2goservers = false broker-agent-query-mode = NONE [testprofile2] host = host1.mydomain, host2.mydomain, host3.mydomain broker-portscan-x2goservers = true broker-agent-query-mode = NONE [testprofile3] host = host1.internal (host1.external), host2.internal (host2.external), host3.internal (host3.external) """ tf = tempfile.NamedTemporaryFile(mode='w') print(_session_profiles, file=tf) tf.seek(0) inifile_backend = inifile.X2GoBroker(profile_config_file=tf.name) i = 0 selected_hosts = [] # we assume that in 1000 test steps we will stumble over all three host names while i < 1000 or len(selected_hosts) != 3: _session1 = inifile_backend.select_session('testprofile1', username='foo') self.assertTrue ( _session1['server'] in ('host1.mydomain', 'host2.mydomain', 'host3.mydomain')) if _session1['server'] not in selected_hosts: selected_hosts.append(_session1['server']) i += 1 self.assertTrue ( len(selected_hosts) == 3 ) i = 0 selected_hosts = [] # we assume that if the code is broken we would receive # the down host (host3.mydomain) within 30 test steps at least once while i <= 30: _session2 = inifile_backend.select_session('testprofile2', username='foo') self.assertTrue ( _session2['server'] in ('host1.mydomain', 'host2.mydomain')) if _session2['server'] not in selected_hosts: selected_hosts.append(_session2['server']) i += 1 self.assertTrue ( len(selected_hosts) == 2 ) i = 0 selected_hosts = [] # we assume that if the code is broken we would receive # the down host (host1.mydomain/10.0.2.4) within 30 test steps at least once while i <= 30: _session3 = inifile_backend.select_session('testprofile3', username='foo') self.assertTrue ( _session3['server'] in ('host2.external', 'host3.external')) if _session3['server'] not in selected_hosts: selected_hosts.append(_session3['server']) i += 1 self.assertTrue ( len(selected_hosts) == 2 ) x2gobroker.utils.portscan = _save_portscan def test_suite(): from unittest import TestSuite, makeSuite suite = TestSuite() suite.addTest(makeSuite(TestX2GoBrokerBackendInifile)) return suite x2gobroker-0.0.4.1/x2gobroker/tests/test_broker_zeroconf.py0000644000000000000000000001017413457267612020674 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. import unittest import copy import tempfile # Python X2GoBroker modules import x2gobroker.brokers.zeroconf_broker class TestX2GoBrokerBackendZeroconf(unittest.TestCase): ### TEST: list_profiles() method def test_profilelist(self): _save_maxDiff = self.maxDiff self.maxDiff = None list_of_profiles = { 'unittest': { 'user': '', 'defsndport': True, 'useiconv': False, 'iconvfrom': 'UTF-8', 'height': 600, 'export': '', 'quality': 9, 'fullscreen': False, 'layout': '', 'useexports': 1, 'width': 800, 'speed': 2, 'soundsystem': 'pulse', 'print': True, 'type': 'auto', 'sndport': 4713, 'xinerama': True, 'variant': '', 'usekbd': True, 'fstunnel': True, 'applications': ['TERMINAL','WWWBROWSER','MAILCLIENT','OFFICE',], 'host': 'localhost', 'multidisp': 0, 'sshproxyport': 22, 'sound': True, 'rootless': 0, 'name': 'LOCALHOST', 'iconvto': 'UTF-8', 'soundtunnel': True, 'command': 'KDE', 'dpi': 96, 'sshport': 22, 'setdpi': 0, 'pack': '16m-jpeg', # make sure, hard-coded defaults end up in the list_profiles() output of the zeroconf backend, as well 'directrdp': False, }, } zeroconf_backend = x2gobroker.brokers.zeroconf_broker.X2GoBroker() _profiles = zeroconf_backend.list_profiles('user_foo') self.assertEqual(len(list(_profiles.keys())), 1) _key = list(_profiles.keys())[0] # replace profile ID ,,unittest'' with the returned random profile ID (uuid hash) _test_profiles = { _key: list_of_profiles['unittest'] } self.assertEqual(_profiles, _test_profiles) self.maxDiff = _save_maxDiff ### TEST: select_profile() method def test_desktopshell_uppercase(self): _config_defaults = copy.deepcopy(x2gobroker.defaults.X2GOBROKER_CONFIG_DEFAULTS) _config = """ [broker_zeroconf] enable = true desktop-shell = kDe """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) zeroconf_backend = x2gobroker.brokers.zeroconf_broker.X2GoBroker(config_file=tf.name, config_defaults=_config_defaults) _profiles = zeroconf_backend.list_profiles('user_foo') self.assertEqual(len(list(_profiles.keys())), 1) _key = list(_profiles.keys())[0] self.assertEqual(_profiles[_key]['command'], 'KDE') def test_sessionselection(self): _output = { 'server': 'localhost', 'port': 22, } zeroconf_backend = x2gobroker.brokers.zeroconf_broker.X2GoBroker() self.assertEqual(zeroconf_backend.select_session('any-profile-id'), _output) def test_suite(): from unittest import TestSuite, makeSuite suite = TestSuite() suite.addTest(makeSuite(TestX2GoBrokerBackendZeroconf)) return suite x2gobroker-0.0.4.1/x2gobroker/tests/test_client_plain_base.py0000644000000000000000000000631713457267612021142 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. import unittest import tempfile # Python X2GoBroker modules import x2gobroker.defaults import x2gobroker.client.plain from nose.tools import assert_equal, assert_true, assert_false class args(): def __init__(self): self.user = None self.login = None self.auth_cookie = None self.task = None self.profile_id = None self.backend = 'base' class TestX2GoBrokerClientPlainBase(unittest.TestCase): ### TEST RESPONSE: is enabled? def test_isenabled(self): _cf_bak = x2gobroker.defaults.X2GOBROKER_CONFIG a = args() _config = """ [broker_base] enable = false """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) x2gobroker.defaults.X2GOBROKER_CONFIG = tf.name r = x2gobroker.client.plain.X2GoBrokerClient().get(a) assert_equal(r, None) tf.close() _config = """ [broker_base] enable = true """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) _cf_bak = x2gobroker.defaults.X2GOBROKER_CONFIG x2gobroker.defaults.X2GOBROKER_CONFIG = tf.name r = x2gobroker.client.plain.X2GoBrokerClient().get(a) lines = r.split('\n') assert_equal(lines[1], "Access granted") tf.close() x2gobroker.defaults.X2GOBROKER_CONFIG = _cf_bak ### TEST RESPONSE: simple authentication (check_access) ### TEST TASK: listsessions (nothing should be returned for the base backend) def test_listsessions(self): _cf_bak = x2gobroker.defaults.X2GOBROKER_CONFIG a = args() _config = """ [broker_base] enable = true """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) x2gobroker.defaults.X2GOBROKER_CONFIG = tf.name r = x2gobroker.client.plain.X2GoBrokerClient().get(a) assert_true('Access granted' in r) assert_false('START_USER_SESSIONS' in r) assert_false('END_USER_SESSIONS' in r) assert_false('
' in r) assert_false('
' in r) assert_false('
' in r) assert_false('
' in r) x2gobroker.defaults.X2GOBROKER_CONFIG = _cf_bak def test_suite(): from unittest import TestSuite, makeSuite suite = TestSuite() suite.addTest(makeSuite(TestX2GoBrokerClientPlainBase)) return suite x2gobroker-0.0.4.1/x2gobroker/tests/test_uccsjson.py0000644000000000000000000000005113457267612017323 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 x2gobroker-0.0.4.1/x2gobroker/tests/test_utils.py0000644000000000000000000001774213457267612016653 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. import unittest # Python X2GoBroker modules import x2gobroker.utils class TestX2GoBrokerUtils(unittest.TestCase): ### TEST FUNCTION: normalize_hostnames() with server lists (ListType) def test_normalize_hostnames_listtype(self): server_list = ['ts01', 'ts02', 'ts03',] expected_server_list = ['ts01', 'ts02', 'ts03',] server_list_n, subdomains = x2gobroker.utils.normalize_hostnames(server_list) server_list_n.sort() self.assertEqual(expected_server_list, server_list_n) self.assertEqual([], subdomains) server_list = ['ts01.intern', 'ts02.intern', 'ts03.intern',] expected_server_list = ['ts01', 'ts02', 'ts03',] server_list_n, subdomains = x2gobroker.utils.normalize_hostnames(server_list) server_list_n.sort() self.assertEqual(expected_server_list, server_list_n) self.assertEqual(['intern'], subdomains) server_list = ['ts01.intern', 'ts02.intern', 'ts03.extern',] expected_server_list = ['ts01.intern', 'ts02.intern', 'ts03.extern',] server_list_n, subdomains = x2gobroker.utils.normalize_hostnames(server_list) server_list_n.sort() subdomains.sort() self.assertEqual(expected_server_list, server_list_n) self.assertEqual(['extern', 'intern'], subdomains) server_tuple = ('ts01.intern', 'ts02.intern', 'ts03.extern',) expected_server_list = ['ts01.intern', 'ts02.intern', 'ts03.extern',] server_list_n, subdomains = x2gobroker.utils.normalize_hostnames(server_tuple) server_list_n.sort() subdomains.sort() self.assertEqual(expected_server_list, server_list_n) self.assertEqual(['extern', 'intern'], subdomains) ### TEST FUNCTION: normalize_hostnames() with server load (DictType) def test_normalize_hostnames_dicttype(self): server_load = {'ts01': 10, 'ts02': 20, 'ts03': 30,} expected_server_load = {'ts01': 10, 'ts02': 20, 'ts03': 30,} server_load_n, subdomains = x2gobroker.utils.normalize_hostnames(server_load) sln_keys = list(server_load_n.keys()) esl_keys = list(expected_server_load.keys()) sln_keys.sort() esl_keys.sort() self.assertEqual(esl_keys, sln_keys) self.assertEqual([], subdomains) server_load = {'ts01.intern': 10, 'ts02.intern': 20, 'ts03.intern': 30,} expected_server_load = {'ts01': 10, 'ts02': 20, 'ts03': 30,} server_load_n, subdomains = x2gobroker.utils.normalize_hostnames(server_load) sln_keys = list(server_load_n.keys()) esl_keys = list(expected_server_load.keys()) sln_keys.sort() esl_keys.sort() self.assertEqual(esl_keys, sln_keys) self.assertEqual(['intern'], subdomains) server_load = {'ts01.intern': 10, 'ts02.intern': 20, 'ts03.extern': 30,} expected_server_load = {'ts01.intern': 10, 'ts02.intern': 20, 'ts03.extern': 30,} server_load_n, subdomains = x2gobroker.utils.normalize_hostnames(server_load) sln_keys = list(server_load_n.keys()) esl_keys = list(expected_server_load.keys()) sln_keys.sort() esl_keys.sort() subdomains.sort() self.assertEqual(esl_keys, sln_keys) self.assertEqual(['extern', 'intern'], subdomains) def test_matching_hostnames(self): server_list_a = ['server1', 'server2', 'server3'] server_list_b = ['server2', 'server3', 'server4'] matching_hostnames = x2gobroker.utils.matching_hostnames(server_list_a, server_list_b) self.assertEqual(matching_hostnames, ['server2', 'server3']) server_list_a = ['server1.domain1', 'server2.domain1', 'server3.domain1'] server_list_b = ['server2', 'server3', 'server4'] matching_hostnames = x2gobroker.utils.matching_hostnames(server_list_a, server_list_b) self.assertEqual(matching_hostnames, ['server2', 'server3']) server_list_a = ['server1', 'server2', 'server3'] server_list_b = ['server2.domain2', 'server3.domain2', 'server4.domain2'] matching_hostnames = x2gobroker.utils.matching_hostnames(server_list_a, server_list_b) self.assertEqual(matching_hostnames, ['server2', 'server3']) server_list_a = ['server1.domain1', 'server2.domain1', 'server3.domain1'] server_list_b = ['server2.domain2', 'server3.domain2', 'server4.domain2'] matching_hostnames = x2gobroker.utils.matching_hostnames(server_list_a, server_list_b) self.assertEqual(matching_hostnames, []) server_list_a = ['server1.domain1', 'server2.domain2', 'server3.domain1'] server_list_b = ['server2.domain2', 'server3.domain2', 'server4.domain2'] matching_hostnames = x2gobroker.utils.matching_hostnames(server_list_a, server_list_b) self.assertEqual(matching_hostnames, ['server2.domain2']) server_list_a = ['server1.domain1', 'server2', 'server3.domain1'] server_list_b = ['server2.domain2', 'server3.domain2', 'server4.domain2'] matching_hostnames = x2gobroker.utils.matching_hostnames(server_list_a, server_list_b) self.assertEqual(matching_hostnames, []) server_list_a = ['server1.domain1', 'server2', 'server3.domain1'] server_list_b = ['server2', 'server3.domain2', 'server4.domain2'] matching_hostnames = x2gobroker.utils.matching_hostnames(server_list_a, server_list_b) self.assertEqual(matching_hostnames, ['server2']) def test_split_host_address(self): host = 8080 default_address = '127.0.0.1' default_port = 22 bind_address, bind_port = x2gobroker.utils.split_host_address(host, default_address, default_port) self.assertEqual((bind_address, bind_port), (default_address, host)) host = '8080' default_address = None default_port = None bind_address, bind_port = x2gobroker.utils.split_host_address(host, default_address, default_port) self.assertEqual((bind_address, bind_port), ('0.0.0.0', int(host))) host = '0.0.0.0' default_address = '127.0.0.1' default_port = 8080 bind_address, bind_port = x2gobroker.utils.split_host_address(host, default_address, default_port) self.assertEqual((bind_address, bind_port), (host, default_port)) host = '[::1]:8221' default_address = '127.0.0.1' default_port = 8080 bind_address, bind_port = x2gobroker.utils.split_host_address(host, default_address, default_port) self.assertEqual((bind_address, bind_port), ('[::1]', 8221)) host = '[::1]' default_address = '127.0.0.1' default_port = 8080 bind_address, bind_port = x2gobroker.utils.split_host_address(host, default_address, default_port) self.assertEqual((bind_address, bind_port), (host, default_port)) host = '' default_address = '' default_port = 8080 bind_address, bind_port = x2gobroker.utils.split_host_address(host, default_address, default_port) self.assertEqual((bind_address, bind_port), ('0.0.0.0', default_port)) def test_suite(): from unittest import TestSuite, makeSuite suite = TestSuite() suite.addTest(makeSuite(TestX2GoBrokerUtils)) return suite x2gobroker-0.0.4.1/x2gobroker/tests/test_web_plain_base.py0000644000000000000000000001301213457267612020427 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. import unittest import tempfile from paste.fixture import TestApp from nose.tools import assert_equal import tornado.wsgi # Python X2GoBroker modules import x2gobroker.defaults import x2gobroker.web.plain urls = ( ('/plain/(.*)', x2gobroker.web.plain.X2GoBrokerWeb,) ,) application = tornado.wsgi.WSGIApplication(urls) class TestX2GoBrokerWebPlainBase(unittest.TestCase): ### TEST RESPONSE: is enabled? def test_isenabled(self): _config = """ [broker_base] enable = false """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) _cf_bak = x2gobroker.defaults.X2GOBROKER_CONFIG x2gobroker.defaults.X2GOBROKER_CONFIG = tf.name testApp = TestApp(application) r = testApp.get('/plain/base/', expect_errors=True) assert_equal(r.status, 404) tf.close() _config = """ [broker_base] enable = true """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) x2gobroker.defaults.X2GOBROKER_CONFIG = tf.name testApp = TestApp(application) r = testApp.get('/plain/base/', expect_errors=True) assert_equal(r.status, 401) tf.close() x2gobroker.defaults.X2GOBROKER_CONFIG = _cf_bak ### TEST RESPONSE: simple authentication (check_access) def test_checkaccess(self): testApp = TestApp(application) r = testApp.get('/plain/base/', expect_errors=True) assert_equal(r.status, 404) _config = """ [broker_base] enable = true auth-mech = testsuite """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) _cf_bak = x2gobroker.defaults.X2GOBROKER_CONFIG x2gobroker.defaults.X2GOBROKER_CONFIG = tf.name r = testApp.get('/plain/base/', params={'user': 'test', 'password': 'sweet', }, expect_errors=True) assert_equal(r.status, 200) r.mustcontain('Access granted') x2gobroker.defaults.X2GOBROKER_CONFIG = _cf_bak ### TEST RESPONSE: simple authentication with user name and login name (check_access) def test_checkaccess_user_and_login(self): testApp = TestApp(application) r = testApp.get('/plain/base/', expect_errors=True) assert_equal(r.status, 404) _config = """ [broker_base] enable = true auth-mech = testsuite """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) _cf_bak = x2gobroker.defaults.X2GOBROKER_CONFIG x2gobroker.defaults.X2GOBROKER_CONFIG = tf.name r = testApp.get('/plain/base/', params={'user': 'test', 'login': 'test_user_on_server', 'password': 'sweet', }, expect_errors=True) assert_equal(r.status, 200) r.mustcontain('Access granted') x2gobroker.defaults.X2GOBROKER_CONFIG = _cf_bak ### TEST RESPONSE: simple authentication with accentuated chars in password (check_access) def test_checkaccess_with_accentuated_chars(self): testApp = TestApp(application) r = testApp.get('/plain/base/', expect_errors=True) assert_equal(r.status, 404) _config = """ [broker_base] enable = true auth-mech = testsuite """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) _cf_bak = x2gobroker.defaults.X2GOBROKER_CONFIG x2gobroker.defaults.X2GOBROKER_CONFIG = tf.name r = testApp.get('/plain/base/', params={'user': 'jacques', 'password': 'thérèse', }, expect_errors=True) assert_equal(r.status, 200) r.mustcontain('Access granted') x2gobroker.defaults.X2GOBROKER_CONFIG = _cf_bak ### TEST TASK: listsessions (nothing should be returned for the base backend) def test_listsessions(self): _config = """ [broker_base] enable = true auth-mech = testsuite """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) _cf_bak = x2gobroker.defaults.X2GOBROKER_CONFIG x2gobroker.defaults.X2GOBROKER_CONFIG = tf.name testApp = TestApp(application) r = testApp.get('/plain/base/', params={'user': 'test', 'password': 'sweet', 'task': 'listsessions', }, expect_errors=True) assert_equal(r.status, 200) r.mustcontain('Access granted') r.mustcontain(no='START_USER_SESSIONS') r.mustcontain(no='END_USER_SESSIONS') r.mustcontain(no='
',) r.mustcontain(no='
',) r.mustcontain(no='
', ) r.mustcontain(no='
', ) x2gobroker.defaults.X2GOBROKER_CONFIG = _cf_bak def test_suite(): from unittest import TestSuite, makeSuite suite = TestSuite() suite.addTest(makeSuite(TestX2GoBrokerWebPlainBase)) return suite x2gobroker-0.0.4.1/x2gobroker/tests/test_web_plain_inifile.py0000644000000000000000000000272313457267612021143 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. import unittest #import tempfile #from paste.fixture import TestApp #from nose.tools import * import tornado.wsgi # Python X2GoBroker modules import x2gobroker.defaults import x2gobroker.web.plain urls = ( ('/plain/(.*)', x2gobroker.web.plain.X2GoBrokerWeb,) ,) application = tornado.wsgi.WSGIApplication(urls) x2gobroker.defaults.X2GOBROKER_CONFIG_DEFAULTS.update({'base': {'enable': True, },}) class TestX2GoBrokerWebPlainInifile(unittest.TestCase): pass def test_suite(): from unittest import TestSuite, makeSuite suite = TestSuite() suite.addTest(makeSuite(TestX2GoBrokerWebPlainInifile)) return suite x2gobroker-0.0.4.1/x2gobroker/tests/test_web_plain_zeroconf.py0000644000000000000000000000763613457267612021361 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. import unittest import tempfile from paste.fixture import TestApp from nose.tools import assert_equal import tornado.wsgi # Python X2GoBroker modules import x2gobroker.defaults import x2gobroker.web.plain urls = ( ('/plain/(.*)', x2gobroker.web.plain.X2GoBrokerWeb,) ,) application = tornado.wsgi.WSGIApplication(urls) class TestX2GoBrokerWebPlainZeroconf(unittest.TestCase): ### TEST TASK: listsessions (you can influence the session command via the X2Go Broker's configurationfile) def test_listsessions_checkcommand(self): _config = """ [broker_zeroconf] enable = true auth-mech = testsuite desktop-shell = KDE """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) x2gobroker.defaults.X2GOBROKER_CONFIG = tf.name testApp = TestApp(application) r = testApp.get('/plain/zeroconf/', params={'user': 'test', 'password': 'sweet', 'task': 'listsessions', }, expect_errors=True) assert_equal(r.status, 200) r.mustcontain('Access granted') r.mustcontain('START_USER_SESSIONS') r.mustcontain('command=KDE') r.mustcontain('END_USER_SESSIONS') r.mustcontain(no='
',) r.mustcontain(no='
',) r.mustcontain(no='
', ) r.mustcontain(no='
', ) tf.close() _config = """ [broker_zeroconf] enable = true auth-mech = testsuite desktop-shell = GNOME """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) x2gobroker.defaults.X2GOBROKER_CONFIG = tf.name testApp = TestApp(application) r = testApp.get('/plain/zeroconf/', params={'user': 'test', 'password': 'sweet', 'task': 'listsessions', }, expect_errors=True) assert_equal(r.status, 200) r.mustcontain('Access granted') r.mustcontain('START_USER_SESSIONS') r.mustcontain('command=GNOME') r.mustcontain('END_USER_SESSIONS') r.mustcontain(no='
',) r.mustcontain(no='
',) r.mustcontain(no='
', ) r.mustcontain(no='
', ) tf.close() ### TEST TASK: selectsession (returns localhost as the only server, no SSH key, no session info) def test_selectsession(self): _config = """ [broker_zeroconf] enable = true auth-mech = testsuite """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) x2gobroker.defaults.X2GOBROKER_CONFIG = tf.name testApp = TestApp(application) r = testApp.get('/plain/zeroconf/', params={'user': 'test', 'password': 'sweet', 'task': 'selectsession', 'sid': 'LOCALHOST',}, expect_errors=True) assert_equal(r.status, 200) r.mustcontain('Access granted') r.mustcontain('SERVER:localhost:22') r.mustcontain(no='
',) r.mustcontain(no='
',) r.mustcontain(no='
', ) r.mustcontain(no='
', ) tf.close() def test_suite(): from unittest import TestSuite, makeSuite suite = TestSuite() suite.addTest(makeSuite(TestX2GoBrokerWebPlainZeroconf)) return suite x2gobroker-0.0.4.1/x2gobroker/tests/test_web_uccs_zeroconf.py0000644000000000000000000001060313457267612021177 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. import unittest import tempfile from paste.fixture import TestApp from nose.tools import assert_equal import tornado.wsgi import json # Python X2GoBroker modules import x2gobroker.defaults import x2gobroker.web.uccs urls = ( ('/uccs/[a-zA-Z]*(/*)$', x2gobroker.web.uccs.X2GoBrokerWeb,), ('/uccs/(.*)/api/([0-9])(/*)$', x2gobroker.web.uccs.X2GoBrokerWebAPI,), ) application = tornado.wsgi.WSGIApplication(urls) class TestX2GoBrokerWebUccsZeroConf(unittest.TestCase): ### TEST TASK: listsessions (you can influence the session command via the X2Go Broker's configurationfile) def test_listsessions(self): _expected_result = { 'URL': 'http://localhost:8080/uccs/zeroconf', 'AdditionalManagementServers': [], 'Name': 'X2Go Session Broker', 'DefaultServer': 'LOCALHOST', 'RemoteDesktopServers': [ { 'Username': '', 'Protocol': 'x2go', 'Name': 'LOCALHOST', 'URL': 'http://localhost:22/', 'SessionType': 'KDE', 'SessionTypeRequired': True, 'Password': '', }, ], } _config = """ [global] enable-uccs-output=true check-credentials=false [zeroconf] enable = true auth-mech = testsuite desktop-shell = KDE """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) x2gobroker.defaults.X2GOBROKER_CONFIG = tf.name testApp = TestApp(application) #username = 'test' #password = 'sweet' #base64String = base64.encodestring('{username}:{password}'.format(username=username, password=password))[:-1] #authHeader = "Basic {authcred}".format(authcred=base64String) #headers = {'Authorization': authHeader} headers = {} r = testApp.get('/uccs/zeroconf/api/4', headers=headers, expect_errors=True) assert_equal(r.status, 200) body = r.normal_body result = json.loads(body) self.assertEqual(_expected_result, result) tf.close() def test_listsessions_uppercase_desktopcommands(self): _expected_result = { 'URL': 'http://localhost:8080/uccs/zeroconf', 'AdditionalManagementServers': [], 'Name': 'X2Go Session Broker', 'DefaultServer': 'LOCALHOST', 'RemoteDesktopServers': [ { 'Username': '', 'Protocol': 'x2go', 'Name': 'LOCALHOST', 'URL': 'http://localhost:22/', 'SessionType': 'KDE', 'SessionTypeRequired': True, 'Password': '', }, ], } _config = """ [global] enable-uccs-output=true check-credentials=false [zeroconf] enable = true auth-mech = testsuite desktop-shell = kdE """ tf = tempfile.NamedTemporaryFile(mode='w') tf.write(_config) tf.seek(0) x2gobroker.defaults.X2GOBROKER_CONFIG = tf.name testApp = TestApp(application) headers = {} r = testApp.get('/uccs/zeroconf/api/4', headers=headers, expect_errors=True) assert_equal(r.status, 200) body = r.normal_body result = json.loads(body) self.assertEqual(_expected_result, result) tf.close() def test_suite(): from unittest import TestSuite#, makeSuite suite = TestSuite() #suite.addTest(makeSuite(TestX2GoBrokerWebUccsZeroConf)) return suite x2gobroker-0.0.4.1/x2gobroker/uccsjson.py0000644000000000000000000002446013457267612015134 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. """\ This modules provides various helper functions and classes for the UCCS web frontend of the X2Go Session Broker. The UCCS protocol was originally brought to life by Canonical and was part of Unity Greeter in Ubuntu 12.10. See early blog posts about that topic: * https://sunweavers.net/blog/how-to-for-hi-jacking-the-rdp-remote-login-feature-introduced-in-ubuntu-12-10-with-x2go * https://sunweavers.net/blog/thoughts-about-canonical-s-uccs-service * https://sunweavers.net/blog/unity-greeter-with-x2go-remote-login-support The UCCS web frontend of the X2Go Session Broker offers remote logon support to the Unity Greeter fork called Arctica Greeter. """ try: import simplejson as json except ImportError: import json import re import copy from x2gobroker.defaults import X2GOBROKER_LATEST_UCCS_API_VERSION as latest_api_version def convert_to_builtin_type(obj): """\ Helper function for converting Python objects to dictionaries. Used for doing JSON dumps. """ d_obj = { } d_obj.update(obj.__dict__) d_obj_res = copy.deepcopy(d_obj) # hide private object items (starting with "_") from the resulting dict for key, val in d_obj.items(): if re.match("^_[a-zA-Z]{1}.*", key): del d_obj_res[key] return d_obj_res class ManagementServer(): """\ Generate a UCCS compatible JSON object for a UCCS management server. A :class:`ManagementServer` in UCCS terminology is a host offering a UCCS compliant API for handling remote logons from clients over the web. The :class:`ManagementServer` is the entry point for all clients. :param url: URL of the UCCS broker server :type url: ``str`` :param name: human-readable, descriptive server name :type name: ``str`` :param api_version: API version used between remote logon service and broker :type api_version: ``int`` """ def __init__(self, url, name, api_version=latest_api_version): """\ Initialize instance. """ self._api_version = api_version self.RemoteDesktopServers = [] self.AdditionalManagementServers = [] self.URL = '{url}/'.format(url=url.rstrip('/')) self.Name = name def set_default(self, ts_name): """\ Define the default (terminal) server instance. :param ts_name: name of the terminal server that is to be set as default :type ts_name: ``str`` """ if isinstance(ts_name, str): self.DefaultServer = ts_name elif isinstance(ts_name, str): self.DefaultServer = ts_name else: raise TypeError("set_default expects a string argument") def add_terminalserver(self, server): """\ Add a terminal server to this management server object. :param server: instance of class :class:`RDPServer` or :class:`X2GoServer` :type server: ``obj`` """ self.RemoteDesktopServers.append(server) def add_additional_management_server(self, amserver): """\ Add / cascade a managemnet server to this management server object. :param server: instance of class :class:`AdditionalManagementServer` :type server: ``obj`` """ if isinstance(amserver, AdditionalManagementServer): self.AdditionalManagementServers.append(amserver) else: raise TypeError("add_additional_management_server expects a "\ "AdditionalManagementServer argument") def toJson(self): """\ Dump this instance as JSON object. """ return json.dumps(self, default=convert_to_builtin_type, sort_keys=True, indent=2) class AdditionalManagementServer(): """\ Instantiate a to-be-cascaded sub-management UCCS server. In UCCS, other than terminal servers, you can add :class:`AdditionalManagementServer` instances and cascade UCCS setups. :param url: URL of the UCCS broker server :type url: ``str`` :param name: human-readable, descriptive server name :type name: ``str`` :param api_version: API version used between remote logon service and broker :type api_version: ``int`` """ pass class RDPServer(): """\ Instantiate a UCCS compatible RDP server session profile object. :param host: hostname of RDP server host :type host: ``str`` :param name: session profile name :type name: ``str`` :param username: username to be used for login :type username: ``str`` :param password: password to be used for login :type password: ``str`` :param api_version: API version used between remote logon service and broker :type api_version: ``int`` """ def __init__(self, host, name, username='', password='', api_version=latest_api_version): self._api_version = api_version self.URL = 'http://{url}/'.format(url=host) self.Name = name self.Protocol = 'rdp' self.DomainRequired = True self.Username = username self.Password = password def set_domain(self, domain): """\ Set the domain for this RDP server. :param domain: the domain name to be set :type domain: ``str`` :raises TypeError: domain has to be ``str`` """ if isinstance(domain, str): self.WindowsDomain = domain elif isinstance(domain, str): self.WindowsDomain = domain else: raise TypeError("set_domain() expects a string or unicode argument") def toJson(self): """\ Dump this instance as JSON object. """ return json.dumps(self, default=convert_to_builtin_type, sort_keys=True, indent=2) class ICAServer(): """\ Instantiate a UCCS compatible ICA server session profile object. :param host: hostname of ICA server host :type host: ``str`` :param name: session profile name :type name: ``str`` :param username: username to be used for login :type username: ``str`` :param password: password to be used for login :type password: ``str`` :param api_version: API version used between remote logon service and broker :type api_version: ``int`` """ def __init__(self, host, name, username='', password='', api_version=latest_api_version): self._api_version = api_version self.URL = 'http://{url}/'.format(url=host) self.Name = name self.Protocol = 'ica' self.DomainRequired = True self.Username = username self.Password = password def set_domain(self, domain): """\ Set the domain for this ICA server. :param domain: the domain name to be set :type domain: ``str`` :raises TypeError: domain has to be ``str`` """ if isinstance(domain, str): self.WindowsDomain = domain elif isinstance(domain, str): self.WindowsDomain = domain else: raise TypeError("set_domain() expects a string or unicode argument") def toJson(self): """\ Dump this instance as JSON object. """ return json.dumps(self, default=convert_to_builtin_type, sort_keys=True, indent=2) class X2GoServer(): """\ Instantiate a UCCS compatible X2Go Server session profile object. :param host: hostname of X2Go Server host :type host: ``str`` :param name: session profile name :type name: ``str`` :param username: username to be used for login :type username: ``str`` :param password: password to be used for login :type password: ``str`` :param api_version: API version used between remote logon service and broker :type api_version: ``int`` """ def __init__(self, host, name, username='', password='', api_version=latest_api_version): self._api_version = api_version self.URL = 'http://{url}/'.format(url=host) self.Name = name self.Protocol = 'x2go' if self._api_version == 4: self.SessionTypeRequired = True else: self.CommandRequired = True self.Username = username self.Password = password # legacy: API v4 (not used in API v5 and higher) def set_session_type(self, session_type): """\ Set the session type to be used on this X2Go Server. This method is a legacy method formerly used by APIv4 of the UCCS protocol. In APIv5, the method :func:`set_command()` gets used instead. :param command: the session type to be set :type command: ``str`` :raises TypeError: command has to be ``str`` """ #FIXME: throw an exception when used with API v5 or newer if isinstance(session_type, str): self.SessionType = session_type else: raise TypeError("set_session_type() expects a string argument") # since: API v5 def set_command(self, command): """\ Set the command to be used on this X2Go Server. Added since APIv5 of the UCCS protocol. :param command: the session type to be set :type command: ``str`` :raises TypeError: command has to be ``str`` """ #FIXME: throw an exception when used with API v4 if isinstance(command, str): self.Command = command else: raise TypeError("set_command() expects a string argument") def toJson(self): """\ Dump this instance as JSON object. """ return json.dumps(self, default=convert_to_builtin_type, sort_keys=True, indent=2) x2gobroker-0.0.4.1/x2gobroker/utils.py0000644000000000000000000003333013457267612014441 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. """\ Here you find a collection of tools that are used by X2Go Session Broker's code internally. Everything that is not directly related to specific broker code and potentially reusable at other places in the code tree, is placed into this module. """ import os import sys import locale import netaddr import distutils.version import pwd, grp import socket import binascii import time def _checkConfigFileDefaults(data_structure): """\ Check an ini-file-like data structure. :param data_structure: an ini-file-like data structure :type data_structure: ``dict`` of ``dict``s :returns: ``True`` if ``data_structure`` matches that of an ini file data structure :rtype: ``bool`` """ if data_structure is None: return False if type(data_structure) is not dict: return False for sub_dict in list(data_structure.values()): if type(sub_dict) is not dict: return False return True def touch_file(filename, mode='a'): """\ Imitates the behaviour of the GNU/touch command. :param filename: name of the file to touch :type filename: ``str`` :param mode: the file mode (as used for Python file objects) :type mode: ``str`` """ if not os.path.isdir(os.path.dirname(filename)): os.makedirs(os.path.dirname(filename), mode=0o700) f = open(filename, mode=mode) f.close() def get_encoding(): """\ Detect systems default character encoding. :returns: The system's local character encoding. :rtype: ``str`` """ try: encoding = locale.getdefaultlocale()[1] if encoding is None: raise BaseException except: try: encoding = sys.getdefaultencoding() except: encoding = 'ascii' return encoding def compare_versions(version_a, op, version_b): """\ Compare with using operator . In the background ``distutils.version.LooseVersion`` is used for the comparison operation. :param version_a: a version string :type version_a: ``str`` :param op: an operator provide as string (e.g. '<', '>', '==', '>=' etc.) :type op: ``str`` :param version_b: another version string that is to be compared with :type version_b: ``str`` :returns: if the comparison is ``True`` or ``False`` :rtype: ``bool`` """ ### FIXME: this comparison is not reliable with beta et al. version strings ver_a = distutils.version.LooseVersion(version_a) ver_b = distutils.version.LooseVersion(version_b) return eval("ver_a %s ver_b" % op) def normalize_hostnames(servers): """\ Take a ``list`` or ``dict`` of servers and check if they match in their domain part and strip the domain part finally off. E.g., for servers provided as a ``list`` (tuple would be ok, too:: ['server1', 'server2'] -> ['server1', server2'] ['server1.domain1, 'server2.domain1'] -> ['server1', server2'] ['server1.domain1, 'server2.domain2'] -> (['server1', server2'], ['domain1', 'domain2'] E.g., for servers provided as a ``dict``:: {'server1': , 'server2': } -> {'server1': , 'server2': } {'server1.domain1': , 'server2.domain1': } -> {'server1': , 'server2': } {'server1.domain1': , 'server2.domain2': } -> ({'server1': , 'server2': }, ['domain1', 'domain2'] :param servers: a ``list``, ``tuple`` or ``dict`` hash with either server hostnames as items or dictionary keys :type servers: ``list``, ``tuple`` or ``dict`` :returns: a ``list`` or a ``dict`` with server domains stripped of the items / keys :rtype: ``list``, ``dict`` or ``tuple`` """ # test the data type of servers arg_is_dict = False servers_normalized = [] if type(servers) is dict: arg_is_dict = True servers_normalized = {} elif type(servers) is tuple: servers=list(servers) elif type(servers) not in (list, tuple): raise ValueError('only lists, tuples and dictionaries are valid for x2gobroker.utils.normalize_hostnames()') subdomains = [] for server in servers: # do not deal with IPv4 or IPv6 addresses if netaddr.valid_ipv4(server) or netaddr.valid_ipv6(server): continue else: _server = server if '.' not in _server: _server += '.' hostname, subdomain = _server.split('.', 1) if arg_is_dict: servers_normalized[hostname] = servers[server] else: servers_normalized.append(hostname) # collect the list of subdomains used in all server names if subdomain and subdomain not in subdomains: subdomains.append(subdomain) # return the original servers dict/list/tuple if len(subdomains) > 1: servers_normalized = servers return servers_normalized, subdomains def matching_hostnames(server_list_a, server_list_b): """\ Compare two list of servers, if they have matching hostnames. This function tries to smoothly ship around asymmetric usage of FQDN hostnames and short hostnames in one list. :param server_list_a: list of servers :type server_list_a: ``list`` of ``str`` :param server_list_b: list of servers to compare the first list with :type server_list_b: ``list`` of ``str`` :returns: a sorted list of matching server hostnames (hostnames that appear in both provided server lists. :returns: ``list`` of ``str`` """ matching_hosts = [] ### NORMALIZE (=reduce to hostname only) server names (list A) if possible server_list_a_normalized, subdomains_a = normalize_hostnames(server_list_a) ### NORMALIZE server names (in list B), only if we have a unique domain match in list A if len(subdomains_a) <= 1: server_list_b_normalized, subdomains_b = normalize_hostnames(server_list_b) if len(subdomains_b) <= 1: if len(subdomains_a) == 0 or len(subdomains_b) == 0: matching_hosts = list(set(server_list_a_normalized).intersection(set(server_list_b_normalized))) if not matching_hosts: matching_hosts = list(set(server_list_a).intersection(set(server_list_b))) matching_hosts.sort() return matching_hosts def drop_privileges(uid, gid): """\ Drop privileges from super-user root to given ```` and ````. Only works when run as root, if run with a non-super-user account, ``None`` is returned. If privileges could be dropped, the environment's HOME variable is adapted to the new user account's home directory path. Also, the umask of the account we dropped privileges to is set to ``0o077``. :param uid: the user ID of the user account to drop privileges to :type uid: ``str`` :param gid: the group ID to drop privileges to :type gid: ``str`` """ if os.getuid() != 0: # We're not root so, like, whatever dude return # Get the uid/gid from the name running_uid = pwd.getpwnam(uid).pw_uid running_gid = grp.getgrnam(gid).gr_gid # Remove group privileges os.setgroups([]) # Try setting the new uid/gid os.setgid(running_gid) os.setuid(running_uid) # Ensure a very conservative umask os.umask(0o077) # set the new user's home directory as $HOME os.environ['HOME'] = pwd.getpwnam(uid).pw_dir def split_host_address(host, default_address=None, default_port=22): """\ Try to split a ``:`` expression into hostname and port. This function is supposed to work with DNS hostnames, IPv4 and IPv6 address. Both parts ( and ) can be omitted in the given ``host`` string. If so, ``default_address`` and ``default_port`` come into play. :param host: an expression like ``:`` (where either the host address or the port can be optional) :type host: ``str`` :param default_address: a fallback host address to be used (default: None) :type default_address: ``str`` :param default_port: a fallback port to be used (default: 22) :type default_port: ``int`` :returns: a tuple of host address and port :rtype: ``tuple(, )`` """ if type(host) is int: host = str(host) # do some stripping first... host = host.strip() host = host.lstrip('*') host = host.lstrip(':') bind_address = None bind_port = None is_ipv6 = None if host and host[0] == '[': is_ipv6 = True if ':' in host: bind_address, bind_port = host.rsplit(':', 1) try: bind_port = int(bind_port) except ValueError: # obviously we split an IPv6 address bind_address = host bind_port = int(default_port) else: try: # in host we find a port number only bind_port = int(host) except ValueError: if host: bind_address = host else: bind_address = '0.0.0.0' if type(default_port) is int: # use the given default, in host, there is an IP address or hostname bind_port = default_port else: # setting a hard-coded port bind_port = 22 if bind_address is None: # in "host" we found the bind_port, now we assign the bind_address bind_address = '0.0.0.0' if default_address: bind_address = default_address bind_address = bind_address.lstrip('[').rstrip(']') if is_ipv6: bind_address = '[{address}]'.format(address=bind_address) return bind_address, bind_port def portscan(addr, port=22): """\ Perform a port scan to the requested hostname. :param addr: address (IPv4, IPv6 or hostname) of the host we want to probe :type addr: ``str`` :param port: port number (default: 22) :type port: ``int`` :returns: ``True`` if the port is in use, else ``False`` (also on errors) :rtype: ``bool`` """ ip_proto = 0 try: socket.getaddrinfo(addr, None, socket.AF_INET6) ip_proto = 6 except socket.gaierror: try: socket.getaddrinfo(addr, None, socket.AF_INET) ip_proto = 4 except socket.gaierror: # we can't find a valid address for this host, so returning a failure... return False if ip_proto == 6 or netaddr.valid_ipv6(addr): sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) elif ip_proto == 4 or netaddr.valid_ipv4(addr): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(2) try: result = sock.connect_ex((addr, port)) if result !=0: sock.close() return False except socket.gaierror: return False except socket.error: return False finally: sock.close() return True def get_key_fingerprint(key): """\ Retrieve the host key fingerprint of the server to be validated. :param key: a Python Paramik :class:`PKey`` object :type key: ``PKey`` :returns: host key fingerprint :rtype: ``str`` """ return binascii.hexlify(key.get_fingerprint()).decode() def get_key_fingerprint_with_colons(key): """\ Retrieve the (colonized) host key fingerprint of the server to be validated. :param key: a Python Paramik :class:`PKey`` object :type key: ``PKey`` :returns: host key fingerprint (with colons) :rtype: ``str`` """ _fingerprint = get_key_fingerprint(key) _colon_fingerprint = '' idx = 0 for char in _fingerprint: idx += 1 _colon_fingerprint += char if idx % 2 == 0: _colon_fingerprint += ':' return _colon_fingerprint.rstrip(':') def delayed_execution(agent_func, delay, *args, **kwargs): """\ Delay execution of a function. :param func: function to be executed. :type func: ``func`` :param delay: delay of the function start in seconds :type delay: ``int`` :param args: arg parameters to be handed over to the to-be-delayed function :type args: ``list`` :param kwargs: kwarg parameters to be handed over to the to-be-delayed function :type kwargs: ``dict`` """ forkpid = os.fork() if forkpid == 0: # close stdin, stdout and stderr in the forked process... for nm in os.listdir("/proc/self/fd"): if nm.startswith('.'): continue fd = int(nm) if fd in (0,1,2): os.close(fd) # wait for the given delay period i = 0 while i < delay: time.sleep(1) i += 1 # execute the function requested agent_func(*args, **kwargs) os._exit(0) x2gobroker-0.0.4.1/x2gobroker/web/extras.py0000644000000000000000000000667713457267612015402 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # This file is part of the X2Go Project - https://www.x2go.org # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. # modules import os.path import paramiko import tornado.web import x2gobroker._paramiko x2gobroker._paramiko.monkey_patch_paramiko() import x2gobroker.defaults from x2gobroker.loggers import logger_error class _RequestHandler(tornado.web.RequestHandler): def _handle_request_exception(self, e): logger_error.error('HTTP request error: {error_msg}'.format(error_msg=e)) class X2GoBrokerItWorks(_RequestHandler): """\ HTTP request handler that simply replies with an "It works" page. """ http_header_items = { 'Content-Type': 'text/plain; charset=utf-8', 'Expires': '+1h', } def _gen_http_header(self): for http_header_item in list(self.http_header_items.keys()): self.set_header(http_header_item, self.http_header_items[http_header_item]) def get(self, *args, **kwargs): self.write('') self.write('

X2Go Session Broker

') self.write('

It works...

') self.write('') class X2GoBrokerPubKeyService(_RequestHandler): """\ HTTP request handler that provides X2Go Session Broker's SSH public keys, if any are configured. """ http_header_items = { 'Content-Type': 'text/plain; charset=utf-8', 'Expires': '+1h', } def _gen_http_header(self): for http_header_item in list(self.http_header_items.keys()): self.set_header(http_header_item, self.http_header_items[http_header_item]) def get(self, *args, **kwargs): output = "" broker_home = x2gobroker.defaults.X2GOBROKER_HOME if os.path.exists('{home}/.ssh/id_rsa.pub'.format(home=broker_home)): pubkey = paramiko.RSAKey(filename='{home}/.ssh/id_rsa'.format(home=broker_home)) output += 'command="/usr/lib/x2go/x2gobroker-agent",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa {pubkey} {user}@{hostname}\n'.format(pubkey=str(pubkey.get_base64()), user=x2gobroker.defaults.X2GOBROKER_DAEMON_USER, hostname=x2gobroker.defaults.X2GOBROKER_HOSTNAME) if os.path.exists('{home}/.ssh/id_dsa.pub'.format(home=broker_home)): pubkey = paramiko.DSSKey(filename='{home}/.ssh/id_dsa'.format(home=broker_home)) output += 'command="/usr/lib/x2go/x2gobroker-agent",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-dss {pubkey} {user}@{hostname}\n'.format(pubkey=str(pubkey.get_base64()), user=x2gobroker.defaults.X2GOBROKER_DAEMON_USER, hostname=x2gobroker.defaults.X2GOBROKER_HOSTNAME) self.write(output) x2gobroker-0.0.4.1/x2gobroker/web/__init__.py0000644000000000000000000000153413457267612015616 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. x2gobroker-0.0.4.1/x2gobroker/web/json.py0000644000000000000000000002324013457267612015026 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # This file is part of the X2Go Project - https://www.x2go.org # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. # modules import tornado.web from tornado.escape import native_str, parse_qs_bytes # load JSON support try: import simplejson as json except ImportError: import json # Python X2Go Broker modules import x2gobroker.defaults from x2gobroker.loggers import logger_broker, logger_error class _RequestHandler(tornado.web.RequestHandler): def _handle_request_exception(self, e): logger_error.error('HTTP request error: {error_msg}'.format(error_msg=e)) tornado.web.RequestHandler._handle_request_exception(self, e) class X2GoBrokerWeb(_RequestHandler): """\ HTTP request handler that provides the JSON web frontend of the X2Go Session Broker. Currently, Python X2Go and all derived X2Go Client applications use this web frontend / communication protocol format.. :raises tornado.web.HTTPError: on authentication failure a 401 error is raised """ http_header_items = { 'Content-Type': 'text/json; charset=utf-8', 'Expires': '+1h', } def _gen_http_header(self): for http_header_item in list(self.http_header_items.keys()): self.set_header(http_header_item, self.http_header_items[http_header_item]) def get(self, path): """\ Implementation of the JSON based broker communication protocol as used by Python X2Go (via POST requests). In debug mode you can test the broker's functionality using a normal web browser via GET requests. :param path: URL path :type path: ``str`` """ if x2gobroker.defaults.X2GOBROKER_DEBUG: logger_broker.warn('GET http request detected, if unwanted: disable X2GOBROKER_DEBUG') return self.post(path) raise tornado.web.HTTPError(405) def post(self, path): """\ Implementation of the JSON based broker communication protocol as used by Python X2Go (via POST requests). :param path: URL path :type path: ``str`` """ self._gen_http_header() if not path: backend = x2gobroker.defaults.X2GOBROKER_DEFAULT_BACKEND else: backend = path.rstrip('/') if '/' in backend: backend = backend.split('/')[0] # silence pyflakes... broker_backend = None try: # dynamically detect broker backend from given URL namespace = {} exec("import x2gobroker.brokers.{backend}_broker\nbroker_backend = x2gobroker.brokers.{backend}_broker.X2GoBroker()".format(backend=backend), namespace) broker_backend = namespace['broker_backend'] except ImportError: # throw a 404 if the backend does not exist logger_error.error('No such broker backend \'{backend}\''.format(backend=backend)) raise tornado.web.HTTPError(404) global_config = broker_backend.get_global_config() # throw a 404 if the WebUI is not enabled if not global_config['enable-json-output']: logger_error.error('The WebUI \'json\' is not enabled in the global broker configuration') raise tornado.web.HTTPError(404) # if the broker backend is disabled in the configuration, pretend to have nothing on offer if not broker_backend.is_enabled(): logger_error.error('The broker backend \'{backend}\' is not enabled'.format(backend=broker_backend.get_name())) raise tornado.web.HTTPError(404) # FIXME: this is to work around a bug in X2Go Client (https://bugs.x2go.org/138) content_type = self.request.headers.get("Content-Type", "") if not content_type.startswith("application/x-www-form-urlencoded"): for name, values in parse_qs_bytes(native_str(self.request.body)).items(): self.request.arguments.setdefault(name, []).extend(values) # set the client address for the broker backend ip = self.request.remote_ip if ip: logger_broker.info('client address is {address}'.format(address=ip)) broker_backend.set_client_address(ip) elif not x2gobroker.defaults.X2GOBROKER_DEBUG: # if the client IP is not set, we pretend to have nothing on offer logger_error.error('client could not provide an IP address, pretending: 404 Not Found') raise tornado.web.HTTPError(404) broker_username = self.get_argument('user', default='') server_username = self.get_argument('login', default='') if not server_username: server_username = broker_username password = self.get_argument('password', default='', strip=False) cookie = self.get_argument('authid', default='') pubkey = self.get_argument('pubkey', default='') task = self.get_argument('task', default='') profile_id = self.get_argument('profile-id', default='') #new_password = self.get_argument('newpass', default='') # compat stuff if task == 'listsessions': task = 'listprofiles' profile_id = self.get_argument('sid', default=profile_id) payload = { 'task': task, } broker_username, password, task, profile_id, ip, cookie, authed, server = broker_backend.run_optional_script(script_type='pre_auth_scripts', username=broker_username, password=password, task=task, profile_id=profile_id, ip=ip, cookie=cookie) logger_broker.debug ('broker_username: {broker_username}, server_username: {server_username}, password: {password}, task: {task}, profile_id: {profile_id}, cookie: {cookie}'.format(broker_username=broker_username, server_username=server_username, password='XXXXX', task=task, profile_id=profile_id, cookie=cookie)) access, next_cookie = broker_backend.check_access(username=broker_username, password=password, ip=ip, cookie=cookie) broker_username, password, task, profile_id, ip, cookie, authed, server = broker_backend.run_optional_script(script_type='post_auth_scripts', username=broker_username, password=password, task=task, profile_id=profile_id, ip=ip, cookie=cookie, authed=access) if access: ### ### CONFIRM SUCCESSFUL AUTHENTICATION FIRST ### payload.update({ 'auth-status': 'Access granted', }) if next_cookie is not None: payload.update({ 'next-authid': next_cookie, }) ### ### X2GO BROKER TASKS ### # FIXME: the ,,testcon'' task can be object to DoS attacks... if task == 'testcon': ### ### TEST THE CONNECTION ### ### FIXME: connections tests are not yet supported... #self.write(broker_backend.test_connection()) return # listsessions is old style, listprofiles semantically more correct if task == 'listsessions' or task == 'listprofiles': payload.update({ 'profiles': broker_backend.list_profiles(username=broker_username), }) elif task == 'selectsession': payload.update({ 'selected_session': {} }) if profile_id: selected_session = {} profile_info = broker_backend.select_session(profile_id=profile_id, username=server_username, pubkey=pubkey) if 'server' in profile_info: server_username, password, task, profile_id, ip, cookie, authed, server = broker_backend.run_optional_script(script_type='select_session_scripts', username=server_username, password=password, task=task, profile_id=profile_id, ip=ip, cookie=cookie, authed=access, server=profile_info['server']) selected_session['server'] = "{server}".format(server=server) if 'port' in profile_info: selected_session['port'] = "{port}".format(port=profile_info['port']) else: selected_session['port'] = "22" if 'authentication_privkey' in profile_info: selected_session['key'] = profile_info['authentication_privkey'] if 'authentication_pubkey' in profile_info: selected_session['authentication_pubkey'] = profile_info['authentication_pubkey'] if 'session_info' in profile_info: selected_session['session_info'] = profile_info['session_info'] payload['selected_session'] = selected_session output = json.dumps(payload, indent=4, sort_keys=True) self.write(output) return raise tornado.web.HTTPError(401) x2gobroker-0.0.4.1/x2gobroker/web/plain.py0000644000000000000000000002310213457267612015155 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # This file is part of the X2Go Project - https://www.x2go.org # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. # modules import tornado.web from tornado.escape import native_str, parse_qs_bytes # Python X2Go Broker modules import x2gobroker.defaults from x2gobroker.loggers import logger_broker, logger_error class _RequestHandler(tornado.web.RequestHandler): def _handle_request_exception(self, e): logger_error.error('HTTP request error: {error_msg}'.format(error_msg=e)) tornado.web.RequestHandler._handle_request_exception(self, e) class X2GoBrokerWeb(_RequestHandler): """\ HTTP request handler that provides the plain text web frontend of the X2Go Session Broker. Currently, X2Go Client uses this webfrontend / communication protocol format. :raises tornado.web.HTTPError: on authentication failure a 401 error is raised """ http_header_items = { 'Content-Type': 'text/plain; charset=utf-8', 'Expires': '+1h', } def _gen_http_header(self): for http_header_item in list(self.http_header_items.keys()): self.set_header(http_header_item, self.http_header_items[http_header_item]) def get(self, path): """\ Implementation of the plain text broker communication protocol as used by X2Go Client (via POST requests). In debug mode you can test the broker's functionality using a normal web browser via GET requests. :param path: URL path :type path: ``str`` """ if x2gobroker.defaults.X2GOBROKER_DEBUG: logger_broker.warn('GET http request detected, if unwanted: disable X2GOBROKER_DEBUG') return self.post(path) raise tornado.web.HTTPError(405) def post(self, path): """\ Implementation of the plain text broker communication protocol as used by X2Go Client (via POST requests). :param path: URL path :type path: ``str`` """ self._gen_http_header() if not path: backend = x2gobroker.defaults.X2GOBROKER_DEFAULT_BACKEND else: backend = path.rstrip('/') if '/' in backend: backend = backend.split('/')[0] # silence pyflakes... broker_backend = None try: namespace = {} # dynamically detect broker backend from given URL exec("import x2gobroker.brokers.{backend}_broker\nbroker_backend = x2gobroker.brokers.{backend}_broker.X2GoBroker()".format(backend=backend), namespace) broker_backend = namespace['broker_backend'] except ImportError: # throw a 404 if the backend does not exist logger_error.error('No such broker backend \'{backend}\''.format(backend=backend)) raise tornado.web.HTTPError(404) global_config = broker_backend.get_global_config() # throw a 404 if the WebUI is not enabled if not global_config['enable-plain-output']: logger_error.error('The WebUI \'plain\' is not enabled in the global broker configuration') raise tornado.web.HTTPError(404) # if the broker backend is disabled in the configuration, pretend to have nothing on offer if not broker_backend.is_enabled(): logger_error.error('The broker backend \'{backend}\' is not enabled'.format(backend=broker_backend.get_name())) raise tornado.web.HTTPError(404) # FIXME: this is to work around a bug in X2Go Client (https://bugs.x2go.org/138) content_type = self.request.headers.get("Content-Type", "") if not content_type.startswith("application/x-www-form-urlencoded"): for name, values in parse_qs_bytes(native_str(self.request.body)).items(): self.request.arguments.setdefault(name, []).extend(values) # set the client address for the broker backend ip = self.request.remote_ip if ip: logger_broker.info('client address is {address}'.format(address=ip)) broker_backend.set_client_address(ip) elif not x2gobroker.defaults.X2GOBROKER_DEBUG: # if the client IP is not set, we pretend to have nothing on offer logger_error.error('client could not provide an IP address, pretending: 404 Not Found') raise tornado.web.HTTPError(404) broker_username = self.get_argument('user', default='') server_username = self.get_argument('login', default='') if not server_username: server_username = broker_username password = self.get_argument('password', default='', strip=False) cookie = self.get_argument('authid', default='') pubkey = self.get_argument('pubkey', default='') task = self.get_argument('task', default='') profile_id = self.get_argument('sid', default='') #new_password = self.get_argument('newpass', default='') output = '' broker_username, password, task, profile_id, ip, cookie, authed, server = broker_backend.run_optional_script(script_type='pre_auth_scripts', username=broker_username, password=password, task=task, profile_id=profile_id, ip=ip, cookie=cookie) logger_broker.debug ('broker_username: {broker_username}, server_username: {server_username}, password: {password}, task: {task}, profile_id: {profile_id}, cookie: {cookie}'.format(broker_username=broker_username, server_username=server_username, password='XXXXX', task=task, profile_id=profile_id, cookie=cookie)) access, next_cookie = broker_backend.check_access(username=broker_username, password=password, ip=ip, cookie=cookie) broker_username, password, task, profile_id, ip, cookie, authed, server = broker_backend.run_optional_script(script_type='post_auth_scripts', username=broker_username, password=password, task=task, profile_id=profile_id, ip=ip, cookie=cookie, authed=access) if access: ### ### CONFIRM SUCCESSFUL AUTHENTICATION FIRST ### if next_cookie is not None: output += "AUTHID:{authid}\n".format(authid=next_cookie) output += "Access granted\n" ### ### X2GO BROKER TASKS ### # FIXME: the ,,testcon'' task can be object to DoS attacks... if task == 'testcon': ### ### TEST THE CONNECTION ### self.write(broker_backend.test_connection()) return if task == 'listsessions': profiles = broker_backend.list_profiles(broker_username) if profiles: output += "START_USER_SESSIONS\n\n" profile_ids = list(profiles.keys()) profile_ids.sort() for profile_id in profile_ids: output += "[{profile_id}]\n".format(profile_id=profile_id) for key in list(profiles[profile_id].keys()): if type(profiles[profile_id][key]) == str: output += "{key}={value}".format(key=key, value=profiles[profile_id][key]) elif type(profiles[profile_id][key]) in (list, tuple): output += "{key}={value}".format(key=key, value=",".join(profiles[profile_id][key])) else: output += "{key}={value}".format(key=key, value=int(profiles[profile_id][key])) output += "\n" output += "\n" output += "END_USER_SESSIONS\n" elif task == 'selectsession': if profile_id: profile_info = broker_backend.select_session(profile_id=profile_id, username=server_username, pubkey=pubkey) if 'server' in profile_info: server_username, password, task, profile_id, ip, cookie, authed, server = broker_backend.run_optional_script(script_type='select_session_scripts', username=server_username, password=password, task=task, profile_id=profile_id, ip=ip, cookie=cookie, authed=access, server=profile_info['server']) output += "SERVER:" output += server if 'port' in profile_info: output += ":{port}".format(port=profile_info['port']) output += "\n" if 'authentication_privkey' in profile_info: output += profile_info['authentication_privkey'] if 'session_info' in profile_info: output += "SESSION_INFO:" output += profile_info['session_info'] + "\n" self.write(output) return raise tornado.web.HTTPError(401) x2gobroker-0.0.4.1/x2gobroker/web/uccs.py0000644000000000000000000002263413457267612015020 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # This file is part of the X2Go Project - https://www.x2go.org # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. # modules import datetime import random import tornado.web # Python X2Go Broker modules import x2gobroker.defaults from x2gobroker.loggers import logger_broker, logger_error import x2gobroker.uccsjson import x2gobroker.basicauth def credentials_validate(username, password): """\ Helper function to validate some given credentials. :param username: the username :type username: ``str`` :param password: the user's password :type password: ``str`` :returns: ``(, )`` tuple :rtype: ``(str, bool)`` """ import x2gobroker.brokers.base_broker # FIXME: with the below hack, the backend broker detection in X2GoBrokerWeb is disabled, only global options # from x2gobroker.conf are available here... broker = x2gobroker.brokers.base_broker.X2GoBroker() broker.enable() access, next_cookie = broker.check_access(username=username, password=password) # UCCS only allows email addresses for remote login if not access and "@" in username: username = username.split('@')[0] access, next_cookie = broker.check_access(username=username, password=password) if username == 'check-credentials' and password == 'FALSE': username = 'anonymous' return username, access class _RequestHandler(tornado.web.RequestHandler): def _handle_request_exception(self, e): logger_error.error('HTTP request error: {error_msg}'.format(error_msg=e)) class X2GoBrokerWeb(_RequestHandler): def get(self, path): """\ With the UCCS protocol, a HEAD request is sent to the server's base URL (sort of as an is-alive ping). If X2Go Session Broker runs in debug mode, the processing of the HEAD request reponse is also made available to GET requests. This means, you can open the base URL in a normal web browser and get a reply. :param path: URL path :type path: ``str`` """ if x2gobroker.defaults.X2GOBROKER_DEBUG: logger_broker.warn('GET http request detected, if unwanted: disable X2GOBROKER_DEBUG') return self.head(path) raise tornado.web.HTTPError(405) def head(self, path): """\ With the UCCS protocol, a HEAD request is sent to the server's base URL (sort of as an is-alive ping). :param path: URL path :type path: ``str`` """ self.write(str(datetime.datetime.utcnow())) return @x2gobroker.basicauth.require_basic_auth('Authentication required', credentials_validate) class X2GoBrokerWebAPI(tornado.web.RequestHandler): """\ HTTP request handler that provides the UCCS web frontend of the X2Go Session Broker. Currently, Arctica Greeter with Remote Logon Service uses this webfrontend / communication protocol format. :raises tornado.web.HTTPError: on authentication failure a 401 error is raised; on invalid API versions, a 404 error is raised. """ def __init__(self, *args, **kwargs): # latest API version is 5 self.api_version = 5 self.api_versions_supported = [4, 5] self.latest_api_version = max (self.api_versions_supported) tornado.web.RequestHandler.__init__(self, *args, **kwargs) http_header_items = { 'Content-Type': 'text/plain; charset=utf-8', 'Expires': '+1h', } def _gen_http_header(self): for http_header_item in list(self.http_header_items.keys()): self.set_header(http_header_item, self.http_header_items[http_header_item]) def get(self, *args, **kwargs): """\ Implementation of the UCCS broker API versions 4 (final) and 5 (in development). :param path: URL path :type path: ``str`` """ self._gen_http_header() backend = args[0] api_version = args[1] try: self.api_version = int(api_version) except TypeError: # assume latest API, shouldn't we actually throw an error here? self.api_version = self.latest_api_version if not backend: self.backend = x2gobroker.defaults.X2GOBROKER_DEFAULT_BACKEND else: self.backend = backend.rstrip('/') try: # dynamically detect broker backend from given URL namespace = {} exec("import x2gobroker.brokers.{backend}_broker\nbroker_backend = x2gobroker.brokers.{backend}_broker.X2GoBroker()".format(backend=self.backend), namespace) self.broker_backend = namespace['broker_backend'] except ImportError: # throw a 404 if the backend does not exist raise tornado.web.HTTPError(404) global_config = self.broker_backend.get_global_config() # throw a 404 if the WebUI is not enabled if not global_config['enable-uccs-output']: raise tornado.web.HTTPError(404) # if the broker backend is disabled in the configuration, pretend to have nothing on offer if not self.broker_backend.is_enabled(): raise tornado.web.HTTPError(404) # set the client address for the broker backend ip = self.request.remote_ip if ip: self.broker_backend.set_client_address(ip) logger_broker.info('client address is {address}'.format(address=ip)) elif not x2gobroker.defaults.X2GOBROKER_DEBUG: # if the client IP is not set, we pretend to have nothing on offer logger_error.error('client could not provide an IP address, pretending: 404 Not Found') raise tornado.web.HTTPError(404) ### ### CONFIRM SUCCESSFUL AUTHENTICATION FIRST ### try: username = kwargs['basicauth_user'] except KeyError: raise tornado.web.HTTPError(401) logger_broker.debug ('Authenticated as username: {username}, with password: '.format(username=username)) ### ### GENERATE UCCS JSON TREE ### output = '' profile_ids = self.broker_backend.get_profile_ids_for_user(username) urlbase = self.broker_backend.get_global_value('my-uccs-url-base').rstrip('/') ms = x2gobroker.uccsjson.ManagementServer('{urlbase}/uccs/{backend}/'.format(urlbase=urlbase, backend=backend), 'X2Go Session Broker') profile_ids.sort() for profile_id in profile_ids: profile = self.broker_backend.get_profile_for_user(profile_id, username, broker_frontend='uccs') hosts = profile['host'] if type(hosts) == str: hosts = [hosts] if profile['directrdp']: ts = x2gobroker.uccsjson.RDPServer( host='{hostname}'.format(hostname=hosts[0]), name=profile['name'], username=profile['user'], api_version=self.api_version, ) ts.set_domain('LOCAL') else: # obligatory profile keys: if 'host' not in profile: raise x2gobroker.x2gobroker_exceptions.X2GoBrokerProfileException('Session profile ID \'{profile_id}\' lacks \'host\' key; profile is unusable'.format(profile_id=profile_id)) if 'name' not in profile: profile['name'] = profile_id if 'sshport' not in profile: profile['sshport'] = 22 _hostname = random.choice(hosts) _port = profile['sshport'] if 'sshport={hostname}'.format(hostname=_hostname) in profile: _port = profile['sshport={hostname}'.format(hostname=_hostname)] if 'host={hostname}'.format(hostname=_hostname) in profile: _hostname = profile['host={hostname}'.format(hostname=_hostname)] ts = x2gobroker.uccsjson.X2GoServer( host='{hostname}:{port}'.format(hostname=_hostname, port=_port), name=profile['name'], username=profile['user'], api_version=self.api_version, ) _cmd = profile['command'] if _cmd.upper() in x2gobroker.defaults.X2GO_DESKTOP_SESSIONS: _cmd = _cmd.upper() if self.api_version == 4: ts.set_session_type(_cmd) else: ts.set_command(_cmd) ms.add_terminalserver(ts) ms.set_default(ts.Name) output += ms.toJson() self.write(output) return x2gobroker-0.0.4.1/x2gobroker/x2gobroker_exceptions.py0000644000000000000000000000317013457267612017625 0ustar # -*- coding: utf-8 -*- # vim:fenc=utf-8 # Copyright (C) 2012-2019 by Mike Gabriel # # X2Go Session Broker is free software; you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # X2Go Session Broker 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero 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. from x2gobroker.loggers import logger_error class X2GoBrokerBaseException(BaseException): """\ Base exception for all X2Go Session Broker exceptions." """ def __init__(self, *args, **kwargs): BaseException.__init__(self, *args, **kwargs) logger_error.error('exception raised: {exception}("{msg}")'.format(exception=type(self).__name__, msg=str(self))) class X2GoBrokerAgentException(X2GoBrokerBaseException): """\ X2Go Broker Agent exception class. Used for failures during broker <-> broker agent communications. """ pass class X2GoBrokerProfileException(X2GoBrokerBaseException): """\ X2Go Broker exception class for session profile problems. Used for failures when parsing and processing session profiles. """ pass