python-x2go-0.6.1.4/ChangeLog0000644000000000000000000054764614470264762012560 0ustar 2023-08-19 23:15:13 +0200 Mihai Moldovan (6196c1b) * python-x2go.spec: remove pycache handling from the files list. 2023-08-19 07:32:31 +0200 Mihai Moldovan (4dc0805) * python-x2go.spec: correctly drop python-configparser BR on *SuSE if we're building against Python 3 only. 2023-08-19 06:46:44 +0200 Mihai Moldovan (24142ae) * python-x2go.spec: only BR python-configparser on *SuSE, if we actually build a Python 2 variant. 2023-08-19 05:56:26 +0200 Mihai Moldovan (6eb8ca2) * python-x2go.spec: drop Python 2 support for OpenSuSE Leap 15.4+ and Tumbleweed (and ALP). 2023-08-19 04:28:28 +0200 Mihai Moldovan (444a8ad) * misc: pre-release copyright update. 2022-06-21 14:46:48 +0200 Tomáš Cerha (8dc074e) * Prevent infinite loop in X2GoRevFwTunnel handler 2022-06-21 12:43:45 +0200 Tomáš Cerha (a6d338c) * Respect NXPROXY_BINARY also on non-Windows systems 2022-06-21 12:23:15 +0200 Tomáš Cerha (8ace8f2) * Fix Mac OS recognition 2021-11-17 21:41:00 +0100 Mike Gabriel (39ef6c5) * x2go/log.py: Start logging to stdout (rather than stderr). 2021-09-02 16:27:49 +0200 Mike Gabriel (ce28c92) * Rewrite string variables to '' if they are set to None during instance construction. 2021-09-02 16:25:46 +0200 Mike Gabriel (b75c802) * debian/*: Drop Python2 builds. 2021-08-20 08:09:18 +0200 Mike Gabriel (789c74b) * x2go/utils.py: Comment 'dpi' param change above and explain that this might cause problems elsewhere. However, the applied change (setting the 'dpi' default value rather than not setting it if setdpi is False is the currently best thinkable approach). 2021-08-19 17:07:13 +0200 Mike Gabriel (e316516) * x2go/client.py: Catch some (more) x2go_exceptions.X2GoSessionRegistryException occurrences. 2021-08-19 17:06:13 +0200 Mike Gabriel (b69c5d5) * x2go/utils.py: If setdpi is false, we need to enforce the default DPI (instead of dropping it from _params). Otherwise, no DPI can be set at all. 2020-11-10 08:36:47 +0100 Mike Gabriel (fa6c97a) * x2go/session.py: Fix SyntaxWarning ('is not' used on a literal). 2020-01-02 16:19:05 +0100 Mike Gabriel (b85b239) * x2go/client.py: typo fix for previous commit 2020-01-02 07:38:33 +0100 Mike Gabriel (fe1e296) * Make build (more) reproducible: no builddir in python-x2go-doc. 2020-01-01 20:27:37 +0100 X2Go Release Manager (c592859) * Continue development 2020-01-01 14:06:40 +0100 X2Go Release Manager (c300f8b) * release 0.6.1.3 (tag: 0.6.1.3, origin/build-main, build-main) 2020-01-01 13:03:24 +0100 Mihai Moldovan (3a7aae7) * misc: pre-release copyright update. 2020-01-01 10:21:08 +0100 Mihai Moldovan (dcc3c76) * python-x2go.spec: fix sphinx-{apidoc,build} calls by using the new shell variables, not RPM macros. 2020-01-01 10:20:05 +0100 Mihai Moldovan (b2c50d9) * python-x2go.spec: fix fallback detection. 2020-01-01 10:01:37 +0100 Mihai Moldovan (d3b0ce5) * python-x2go.spec: add Python-3-autodetection via %{python3_version} and fallbacks to %{py3_ver} and yet another to the plain "3" postfix. 2020-01-01 09:53:30 +0100 Mihai Moldovan (8f3f340) * python-x2go.spec: correctly check for existance of %{python2_version}. 2020-01-01 07:32:04 +0100 Mihai Moldovan (68cebc8) * x2go/_paramiko.py: stop monkey-patching very old paramiko versions with the private key fix for Python 3. 2020-01-01 06:41:34 +0100 Mihai Moldovan (fe9a762) * python-x2go.spec: also use the same detection mechanism for sphinx-build. 2020-01-01 06:39:36 +0100 Mihai Moldovan (b2015af) * python-x2go.spec: reflow comments (only). 2019-12-31 21:19:54 +0100 Mihai Moldovan (84a6e51) * python-x2go.spec: fix condition in doc build. 2019-12-31 19:52:06 +0100 Mihai Moldovan (b00ab6e) * python-x2go.spec: call a more specialized version of sphinx-apidoc depending on the Python version we're building against. 2019-12-31 19:13:12 +0100 Mihai Moldovan (4b8ec8d) * Makefile.docupload: make sphinx-apidoc overridable/configurable. 2019-12-27 14:27:07 +0000 Mike Gabriel (4cf1023) * x2go/_paramiko.py: Monkey-patch PKey._write_private_key() method as it is broken under Python3. 2019-12-27 14:08:30 +0000 Mike Gabriel (4f03a83) * Check availability of X2Go KDrive base support server-side before firing up x2gokdriveclient. 2019-12-27 13:22:13 +0100 Mike Gabriel (594bfd0) * x2go/backends/terminal/plain.py: Fix keyboard layout setting under Python3. .decode() does not modify the variable in place but needs an assignment. 2019-12-26 10:37:44 +0100 X2Go Release Manager (454a4ec) * Continue development 2019-12-26 08:21:57 +0100 X2Go Release Manager (31ae597) * release 0.6.1.2 (tag: 0.6.1.2) 2019-12-18 09:49:18 +0100 Mihai Moldovan (8c5371b) * python-x2go: fix name checks. 2019-12-18 08:55:09 +0100 Mihai Moldovan (61c9361) * python-x2go.spec: fix syntax error. 2019-12-18 08:38:30 +0100 Mihai Moldovan (bfa8961) * python-x2go.spec: don't override sections if the target name is python-x2go. 2019-12-18 08:26:53 +0100 Mihai Moldovan (27c2321) * python-x2go.spec: macros with empty bodies are not allowed, define them to be %{nil}. 2019-12-18 08:21:43 +0100 Mihai Moldovan (b3782a1) * python-x2go.spec: same for %{name_helper_python2}. 2019-12-18 08:20:57 +0100 Mihai Moldovan (336ed1b) * python-x2go.spec: switch to actually using %{name_helper_python3}. 2019-12-18 07:49:57 +0100 Mihai Moldovan (039135b) * python-x2go.spec: same thing as name_helper_python2 for Python 2 names. 2019-12-18 07:49:07 +0100 Mihai Moldovan (ce77535) * python-x2go.spec: add name_helper_python3 macro, simplifying Python 3 package name usage a lot. 2019-12-18 07:42:11 +0100 Mihai Moldovan (29cc9b6) * python-x2go.spec: rework python_module compat section. 2019-12-02 16:54:35 +0100 Mike Gabriel (5f6d607) * x2go/inifiles.py: No need to import future's print_function() anymore. 2019-12-02 16:32:18 +0100 Mike Gabriel (b879f7a) * Fix writing of inifiles when running under Python3. 2019-11-22 08:20:37 +0100 X2Go Release Manager (b282311) * Continue development 2019-11-22 06:54:25 +0100 X2Go Release Manager (5f26a5a) * release 0.6.1.1 (tag: 0.6.1.1) 2019-11-21 15:59:01 +0100 Mihai Moldovan (988b5ac) * misc: pre-release copyright update. 2019-11-20 13:30:12 +0100 X2Go Release Manager (132f8a2) * Continue development 2019-11-20 11:44:49 +0100 Mihai Moldovan (9e693b9) * debian/control: make x2gokdriveclient a recommendation rather than a dependency because we haven't had a proper release of that package yet. 2019-11-20 08:09:50 +0100 Mike Gabriel (9e4face) * release 0.6.1.0 (tag: 0.6.1.0) 2019-11-20 08:02:19 +0100 Mike Gabriel (8a0d34a) * x2go/log.py: Fix exception triggered in Python2'ish pyhoca-gui due to wrong string encoding stemming for Python X2Go's logger code. 2019-09-03 02:40:18 +0200 Mihai Moldovan (4e80e42) * python-x2go.spec: fix syntax error: if => %if. 2019-09-03 02:23:47 +0200 Mihai Moldovan (cb6ac40) * python-x2go.spec: disable python2 builds on FC31+. 2019-09-03 00:59:28 +0200 Mihai Moldovan (989bd72) * python-x2go.spec: newer RPM versions bail out on extra data (even comments) after an %endif clause, so move the comment to a separate line. 2019-07-23 15:55:53 +0200 Mike Gabriel (797dc15) * x2go/{backends/printing/file.py,defaults.py}: Fix type mismatch (str vs. unicode) when opening print settings dialog in PyHoca-GUI (still using Python2). 2019-07-19 20:33:39 +0200 Mike Gabriel (5a6552f) * Fix CINNAMON (2d) support. 2019-07-19 19:53:16 +0200 Mike Gabriel (e4f0bf9) * debian/control: Add x2gokdriveclient as hard dependency for both bin:pkgs python{,3}-x2go. 2019-07-19 19:42:21 +0200 Mike Gabriel (34e571a) * Bump upstream version to 0.6.1.0. 2019-07-19 19:35:09 +0200 Mike Gabriel (3af7450) * X2GoServerSessionList: Provide proper accessor method for getting the complete list of sessions. 2019-07-19 17:32:03 +0200 Mike Gabriel (a330b42) * Add X2Go KDrive graphical rendering backend support. 2019-07-19 17:31:28 +0200 Mike Gabriel (208a084) * X2GoTerminalSession: Fully delegate the session type handling and remembering to the session's X2GoSessionInfo object. 2019-07-19 17:29:09 +0200 Mike Gabriel (89dd84d) * X2GoSessionInfo: Add is_initialized() method. 2019-07-19 17:17:56 +0200 Mike Gabriel (c2ff226) * X2GoTerminalSession: Fix __doc__ string of constructor. 2019-07-19 17:12:13 +0200 Mike Gabriel (0fbaa83) * X2GoSessionInfo: Cache virtually immutable session type. 2019-07-19 17:06:16 +0200 Mike Gabriel (cf364ae) * X2GoSessionInfo: Handle non-initialized X2GoSessionInfo instance more gracefully. 2019-07-19 17:01:23 +0200 Mike Gabriel (3cdd66f) * X2GoSessionInfo: Drop debug print command. 2018-12-01 02:17:45 +0100 X2Go Release Manager (68bfe85) * Continue development 2018-12-01 00:45:46 +0100 X2Go Release Manager (d5b4d6d) * release 0.6.0.2 (tag: 0.6.0.2) 2018-11-30 04:30:51 +0100 Mihai Moldovan (23cab03) * python-x2go.spec: only fetch python(2)-configparser on *SUSE, not python3-configparser, since that doesn't exist. 2018-11-30 03:39:47 +0100 Mihai Moldovan (e14d865) * python-x2go.spec: fix FC python3-enabling via correct macro and for 26+, OpenSuSE will actually be enabled for 13.2+ and add RHEL 8+ condition. 2018-11-30 03:15:22 +0100 Mihai Moldovan (0a5a090) * python-x2go.spec: enable python3 builds on OpenSuSE 13.1+ and Fedora 20+. 2018-11-30 03:01:47 +0100 Mihai Moldovan (43d0bd6) * misc: update copyright notices. 2018-11-26 10:22:59 +0100 Mike Gabriel (308dde2) * typo fix for prev commit 2018-11-26 10:16:34 +0100 Mike Gabriel (6e452d7) * x2go/defaults.py: Support desktop session type "IceWM". 2018-11-11 13:44:53 +0100 Mihai Moldovan (6122dd2) * python-x2go.spec: fetch files under %{python(2)_sitelib} more fine-grained. 2018-11-11 13:43:15 +0100 Mihai Moldovan (919ca14) * debian/changelog: fixup previous entries. 2018-11-10 22:48:30 +0100 Mike Gabriel (99d08fe) * x2go/client.py: Add is_x2goserver() method to X2GoClient class API. 2018-11-10 22:45:46 +0100 Mike Gabriel (d383eb7) * X2GoControlSession.get_server_versions(): Fix another Py3 issue where we forgot decoding stdout before applying string operations. 2018-09-18 21:19:32 +0200 Mike Gabriel (ea67d2f) * debian/changelog: Fix description of a fix. 2018-09-18 21:18:43 +0200 Mike Gabriel (4108c6a) * Continue development... 2018-09-18 21:09:54 +0200 Mike Gabriel (022fc5d) * Support resuming of rootless or published applications sessions. 2018-09-18 20:52:02 +0200 Mike Gabriel (da3853a) * release 0.6.0.1 (tag: 0.6.0.1) 2018-09-18 20:46:51 +0200 Mike Gabriel (c0f5406) * Revert "x2go/backends/terminal/plain.py: Correctly update session window file in Python3." 2018-09-18 18:52:06 +0200 Mike Gabriel (dd894b0) * x2go/backends/control/plain.py: Drop one line of debug code. 2018-09-18 18:44:22 +0200 Mike Gabriel (4ba693e) * x2go/backends/terminal/plain.py: Correctly update session window file in Python3. 2018-09-18 16:26:40 +0000 Mike Gabriel (3fbee6a) * Correctly handle IO objects (BytesIO in Python3, StringIO in Python2). 2018-09-18 16:24:56 +0000 Mike Gabriel (90db159) * InstanceType does not exist in Python3. Let's omit it in __repr__ method. 2018-09-18 16:24:08 +0000 Mike Gabriel (29006c6) * x2go/inifiles.py: Correctly import ConfigParser as configparser on Python2. 2018-09-18 16:23:16 +0000 Mike Gabriel (118f014) * x2go/*.py: Drop 'from builtins import '. Not required. 2018-09-18 12:38:57 +0000 Mike Gabriel (bcdc1b5) * x2go/guardian.py: White-space cleanup. 2018-09-18 12:38:39 +0000 Mike Gabriel (b779a02) * x2go/session.py: Don't fail in set_master_session() if the client instance lacks a session profile config. 2018-09-18 12:04:06 +0000 Mike Gabriel (6d7f154) * x2go/backends/control/plain.py: Raise log priority for reporting that the user enabled SSH agent forwarding. 2018-09-11 16:20:16 +0200 Mike Gabriel (55027e3) * docs/source/index.rst: Move introduction text from x2go/__init__.py into the index.rst file. 2018-09-11 16:15:35 +0200 Mike Gabriel (1b190ab) * docs/source/conf.py: Switch to Sphinx theme 'haiku'. 2018-09-11 14:15:33 +0200 Mike Gabriel (a499749) * Some extra empty lines in __doc__ strings. 2018-09-07 09:08:20 +0200 Mike Gabriel (d157b22) * docs/source/conf.py: Fix project name in comment. White-space change. 2018-09-07 09:03:41 +0200 Mike Gabriel (5e6ace1) * docs/source/conf.py: Drop unicode markers from strings, rephrase more doc type specific titles. 2018-09-07 08:58:45 +0200 Mike Gabriel (8610ca4) * debian/python-x2go-doc.lintian-overrides: Removed. Not required anymore. 2018-09-07 08:58:21 +0200 Mike Gabriel (5cfe22d) * docs/source/_: Add empty _static and _templates dir. 2018-09-07 08:57:44 +0200 Mike Gabriel (eb8aff8) * docs/source/conf.py: Customize html_title. 2018-09-06 19:35:53 +0200 Mike Gabriel (d5fb567) * Continue development... 2018-09-06 19:33:28 +0200 Mike Gabriel (71df03f) * release 0.6.0.0 (tag: 0.6.0.0) 2018-09-06 19:31:14 +0200 Mike Gabriel (b871e69) * docs/source/: Update API doc tree, winreg modules removed. 2018-09-06 19:30:54 +0200 Mike Gabriel (486500a) * Makefile.docupload: Add apidoc target. 2018-09-06 17:11:06 +0000 Mike Gabriel (6180ace) * x2go/__init__.py: Avoid ares resolver for now, as it currently is broken in Debian testing. 2018-09-06 18:33:56 +0200 Mike Gabriel (f0dd125) * x2go/client.py: Update list of supported features. 2018-09-06 18:31:35 +0200 Mike Gabriel (be5b42d) * x2go/session.py: Re-tested our interactive PyShell session launch with Python3. Works! 2018-09-06 18:18:56 +0200 Mike Gabriel (6d0c979) * documentation: typo fix in class name. 2018-09-06 18:17:14 +0200 Mike Gabriel (eba55ce) * documentation: Correctly refrence X2GoClientPrinting and X2GoClientSettings. Also mention broker based X2GoSessionProfiles backends. 2018-09-06 18:09:48 +0200 Mike Gabriel (a5c1537) * x2go/backends/*: Drop winreg backends. Never worked on them, will probably never come. 2018-09-06 18:00:30 +0200 Mike Gabriel (7e79d07) * x2go/log.py: Fix documentation of default log level ('or'ed not 'and'ed). 2018-09-06 17:58:22 +0200 Mike Gabriel (3b8e552) * sphinxy docs: Convert bold / italic from Epydoc to Sphinxy markup. 2018-09-06 17:54:36 +0200 Mike Gabriel (a36fd9f) * sphinx docs: The logging module is x2go.log, not x2go.logger. 2018-09-06 17:52:11 +0200 Mike Gabriel (ac545a7) * sphinx docs: It needs to be :mod:, not :module:. 2018-09-06 17:51:40 +0200 Mike Gabriel (945066e) * sphinx docs: Update loads of param and type fields to sphinxy format. 2018-09-06 16:02:21 +0200 Mike Gabriel (a5b26e8) * sphinx hyperrefs: Fix all remaining hyperrefs that are still in epydoc format. 2018-06-02 07:08:31 +0200 Mihai Moldovan (1cf4fb2) * python-x2go.spec: %{sle_version} is not defined on TW, so use a %{suse_version} check additionally to catch this variant. 2018-06-02 06:38:50 +0200 Mihai Moldovan (b7549f5) * python-x2go.spec: override python_module macro for OpenSuSE Leap 42.3, the shipped version does not what later versions do correctly. 2018-06-02 06:33:06 +0200 Mihai Moldovan (e5596d2) * python-x2go.spec: document %(python_module} definitions. 2018-06-02 05:59:43 +0200 Mihai Moldovan (2a1907b) * python-x2go.spec: older OpenSuSE Leap versions don't support %{python2_sitelib} - use %{python_sitelib} instead. 2018-06-02 05:14:25 +0200 Mihai Moldovan (7b57e17) * docs/source/conf.py: import sphinx module. 2018-06-02 05:05:32 +0200 Mihai Moldovan (de4af10) * docs/source/conf.py: fix parse error due to wrong else usage. 2018-06-02 04:53:31 +0200 Mihai Moldovan (26bfe2d) * docs/source/conf.py: drop minimum sphinx version requirement again, detect version manually and adapt config based on this information. 2018-06-02 04:32:02 +0200 Mihai Moldovan (316ad95) * docs/source/conf.py: set minimum sphinx version to 1.0. 2018-06-02 04:04:18 +0200 Mihai Moldovan (00ee1ce) * python-x2go.spec: merge python(2)-x2go section in main section, since we cannot redefine subpackages. 2018-06-02 02:09:11 +0200 Mihai Moldovan (71bff06) * python-x2go.spec: local %defines are buggy in older RPM implementations, work around by using %global instead. 2018-06-02 02:00:24 +0200 Mihai Moldovan (1ca6c7e) * python-x2go.spec: OpenSuSE Leap 15 and TumbleWeed renamed python-xlib to python{2,3}-python-xlib. 2018-06-02 01:40:17 +0200 Mihai Moldovan (7937eef) * python-x2go.spec: actually disable python3 builds on OpenSuSE. 2018-06-02 01:30:20 +0200 Mihai Moldovan (b486004) * python-x2go.spec: add dependency on the python-configparser backport. 2018-06-02 01:11:06 +0200 Mihai Moldovan (e3d2245) * python-x2go.spec: fix package names in %package (and its %description) and %files sections. 2018-06-02 00:42:13 +0200 Mihai Moldovan (ba08522) * python-x2go.spec: correctly pythonize package, for Fedora, RHEL/EPEL and OpenSuSE. 2018-06-01 21:18:44 +0200 Mihai Moldovan (e02f26e) * python-x2go.spec: also add fdupes package as a build dependency for RHEL-based distros. 2018-05-15 14:58:14 +0200 Mike Gabriel (e275a12) * x2go/backends/terminal/plain.py: All command parameters need to be strings. 2018-05-15 14:55:30 +0200 Mike Gabriel (f9ab897) * x2go/backends/terminal/plain.py: x2gosession-resume handles xinerama support differently from x2gostartagent. Adding it as cmdline parameter No. 9. 2018-05-15 14:52:31 +0200 Mike Gabriel (5166f57) * x2go/backends/terminal/plain.py: Initialize cmd_line list before prepending env vars. 2018-05-15 13:25:51 +0200 Mike Gabriel (413c85a) * x2go/utils.py: Python3 fix in get_workarea_geometry(). 2018-05-14 16:30:02 +0200 Mike Gabriel (7ed68ae) * Natively support xinerama option. 2018-05-14 15:06:44 +0200 Mike Gabriel (75fb1d9) * x2go/checkhosts.py: Python3 fix in get_key_fingerprint(). 2018-05-14 10:52:42 +0200 Mike Gabriel (07e5a86) * python-x2go.spec: At least in openSUSE, Sphinx is python3-Sphinx (with capital S). 2018-05-12 23:32:32 +0200 Mike Gabriel (63a003f) * version bump to 0.5.99.1, due to slightly changed API 2018-05-12 23:31:35 +0200 Mike Gabriel (f89baa4) * x2go/client.py: Add with_command option to list_sessions() method. 2018-03-10 11:44:53 +0100 Mike Gabriel (a2e69d5) * Update docs/source/*.rst. Non-used unit tests removed from source code. 2018-03-06 20:33:17 +0100 Mike Gabriel (7646426) * Drop unit test framework. Never really used (though we should have). (Fixes: #1259). 2018-03-06 20:31:35 +0100 Mike Gabriel (f405e8a) * Makefile.docupload: whitespace fix. 2018-03-06 20:07:07 +0100 Mike Gabriel (0f97e8f) * Makefile.docupload: Add clean target. 2018-03-06 19:08:33 +0100 Mike Gabriel (435c0bb) * x2go/client.py: Fix a __doc__ string head line. 2018-03-06 17:06:47 +0100 Mike Gabriel (aca2c5b) * API doc: Switch to 'classic' theme. 2018-03-06 17:03:03 +0100 Mike Gabriel (146ffb4) * API documentation: Convert full class name path links to reST links. 2018-03-06 16:59:36 +0100 Mike Gabriel (e769baf) * API documentation: Convert class name only links to reST links. 2018-03-06 13:57:02 +0100 Mike Gabriel (49409b1) * python-x2go.spec: Blind shot at attempting API doc build for RPM packages. 2018-03-06 13:53:49 +0100 Mike Gabriel (605aec3) * debian/control: Add to Build-Conflicts: python-sphinx. We need Python3 Sphinx for building the API docs. 2018-03-06 13:53:05 +0100 Mike Gabriel (a912f9f) * docs/Makefile: Use sphinx-build command as found in PATH. 2018-03-06 13:52:33 +0100 Mike Gabriel (07b865a) * API doc: another white-space cleanup + fixes for all open Sphinx Build warnings. 2018-03-06 13:52:01 +0100 Mike Gabriel (d83973b) * Makefile.docupload: Add docupdate target. Don't rebuild API doc for all files, only for files that changed. 2018-03-05 22:32:26 +0100 Mike Gabriel (967cd2d) * update debian/changelog 2018-03-05 22:31:42 +0100 Mike Gabriel (1a5796a) * debian/: Adapt to building API docs with Sphinx. 2018-03-05 22:23:03 +0100 Mike Gabriel (8e7a939) * API Epydoc2Sphinx conversion: Replace 'C{...}' by ... (monospaced text). 2018-03-05 22:10:25 +0100 Mike Gabriel (f96c17e) * API __doc__ strings: Rough / automated switch from Epydoc to Sphinx/reST. 2018-03-05 22:09:23 +0100 Mike Gabriel (113b0a1) * x2go/__init__.py: Add __AUTHOR__, so we can use it in API documentation. 2018-03-05 22:08:03 +0100 Mike Gabriel (58c6884) * Makefile.docupload: Wrap around Sphinx's Makefile in docs/ subfolder. 2018-03-05 22:07:24 +0100 Mike Gabriel (b0d1441) * Start using Sphinx API documentation tool. 2018-03-05 16:48:08 +0100 Mike Gabriel (31054c0) * Some more __doc__ string fixes. 2018-03-05 16:46:26 +0100 Mike Gabriel (825b697) * Various __doc__ string improvements and fixes. 2018-03-05 14:13:13 +0100 Mike Gabriel (e45962c) * x2go/backends/terminal/plain.py: With Python3, decode stderr reads. 2018-01-12 11:10:31 +0100 Mihai Moldovan (a8b4ad8) * x2go/backends/terminal/plain.py: supply xinerama parameters to x2gostartagent and x2goresume-session, currently defaulting to false since this feature is not implemented. 2018-01-12 11:09:57 +0100 Mihai Moldovan (d76d768) * debian/changelog: fixup. 2017-10-26 01:19:33 +0200 Mike Gabriel (d3b6389) * x2go/session.py: Avoid variable name collisions in nested exception catching. 2017-10-26 01:18:15 +0200 Mike Gabriel (f3d1e0e) * x2go/pulseaudio.py: Esound is soooo old-school. Dropping it. 2017-09-26 13:15:47 +0200 Mike Gabriel (1ae1590) * Various I/O + Unicode fixes for various write() calls. 2017-09-25 13:46:46 +0200 Mike Gabriel (cae233b) * python-x2go.spec: Disable epydoc API documentation builds for now on RPM based distros, too. We will probably switch to Sphinx for API documentation generation. 2017-09-25 13:41:31 +0200 Mike Gabriel (d68f4d4) * debian/control: We need python2 and python3 related packages alike under Build-Depends:. 2017-09-25 12:44:32 +0200 Mike Gabriel (d5f3e2b) * debian/rules: Temporarily disable API documentation builds. Switching to Sphinx is probably the way to go. 2017-09-25 12:43:12 +0200 Mike Gabriel (9187855) * x2go/backends/profiles/httpbroker.py: Fix broker URL assembling when no port is given (i.e. convert None to '' manually). 2017-09-25 12:41:54 +0200 Mike Gabriel (b4eb88b) * x2go/cleanup.py: As we need to avoid circular imports, figure out with a work-around if to-be-stopped thread is of X2GoGuardianSession type or other. 2017-09-24 21:05:33 +0200 Mike Gabriel (1623d96) * x2go/backends/control/plain.py: Resolve Python 2 vs. 3 type issue on returned data from SSHClient.exec_command(). 2017-09-24 19:56:10 +0200 Mike Gabriel (57114f0) * x2go/backends/control/plain.py: Handle password based authentication failures graceful. Uncomment previously commented out code. 2017-09-24 19:29:16 +0200 Mike Gabriel (abdf531) * python-x2go.spec: Move python*-foo dependencies to %package sections. Add python*-future as a runtime requirement. 2017-09-24 19:24:55 +0200 Mike Gabriel (1762b36) * python-x2go.spec: Add B-R python*-future. 2017-09-24 19:23:39 +0200 Mike Gabriel (e12cc7c) * debian/control: Add D (python*-x2go): python*-future. 2017-09-24 19:23:08 +0200 Mike Gabriel (d06810e) * x2go/tests/runalltests.py: Fix runalltests.py (permissions, header). 2017-09-24 19:22:36 +0200 Mike Gabriel (14e42ae) * x2go/backends/control/plain.p: Fix passphrase unlocking of private keys. 2017-09-24 11:30:42 +0200 Mike Gabriel (c13c03a) * x2go/backends/control/plain.py: Drop debug print output. 2017-09-24 10:51:20 +0200 Mike Gabriel (ab93199) * python-x2go.spec: No need to run 2to3 at build time anymore. Python X2Go now natively supports Python2 and Python3. 2017-09-24 10:48:05 +0200 Mike Gabriel (c72289e) * backends: No need to ship a stub gconf backend anymore. GConf is dead. 2017-09-24 10:47:38 +0200 Mike Gabriel (a6cdbec) * x2go/**.py: Add __package__ and __name__ definitions to all Python files. 2017-09-23 23:06:35 +0200 Mike Gabriel (fb1abc4) * Futurize: Re-add Python2 support back into python-x2go. 2017-09-22 18:28:01 +0200 Mike Gabriel (18d330e) * x2go/xserver.py: whitespace fix at EOF. 2017-09-22 18:27:30 +0200 Mike Gabriel (300b150) * Obviously, gevent from Python3 is quite critical towards circular import models. 2017-09-22 14:59:16 +0200 Mike Gabriel (c6a6dc0) * debian/rules: Don't build API documentation twice. 2017-09-22 14:40:37 +0200 Mike Gabriel (a9c6d71) * debian/python-x2go-doc.lintian-overrides: Add EOL at EOF. 2017-09-22 14:16:33 +0200 Mike Gabriel (a18646c) * Bump upstream version to 0.5.99.0. 2017-09-22 13:20:38 +0200 Mike Gabriel (327fa5e) * Port to Python3. 2017-09-23 22:57:09 +0200 X2Go Release Manager (b159a5c) * Continue development 2017-09-23 21:50:40 +0200 X2Go Release Manager (fa04b4f) * release 0.5.0.6 (tag: 0.5.0.6) 2017-09-23 21:48:49 +0200 X2Go Release Manager (a631161) * debian/changelog: fixup. 2017-08-31 15:22:28 +0200 Mike Gabriel (bd2d333) * x2go/session.py: Drop duplicated method is_locked(). 2017-08-31 15:21:52 +0200 Mike Gabriel (7cae4ec) * x2go/session.py: Only release locks if actually locked. 2017-08-25 12:06:44 +0200 Mike Gabriel (d8b6737) * x2go/defaults.py: Support LXQt sessions by default. 2017-05-15 20:02:21 +0200 X2Go Release Manager (f974da6) * Continue development 2017-05-15 16:38:42 +0200 X2Go Release Manager (3d07c77) * release 0.5.0.5 (tag: 0.5.0.5) 2017-05-09 12:35:05 +0200 Mike Gabriel (58bc670) * my English is still bad this morning... fixing changelog entry for last commit... 2017-05-09 12:24:20 +0200 Mike Gabriel (3ed4fed) * Don't blindly release gevent locked. We need to checked if a semaphore is locked in some case and only then release it. (Fixes: #1016). 2016-06-28 04:15:59 +0200 Mihai Moldovan (a2d2fb3) * debian/changelog: add entry for last change. 2016-06-27 04:12:36 +0200 Mihai Moldovan (04c3f3c) * debian/control: add myself as uploader. 2016-06-09 03:15:21 +0200 Mihai Moldovan (abdcf43) * debian/control: maintainer change in package: X2Go Developers . 2016-06-09 03:15:03 +0200 Mihai Moldovan (0e11a6b) * x2go/__init__.py: update mailing list address. 2016-06-09 03:20:44 +0200 Mihai Moldovan (e3e7ea3) * debian/changelog: fix latest changelog entry. 2016-02-01 10:34:26 +0100 Mike Gabriel (8e2500a) * documentation: Fix wording in docstring. 2016-01-28 16:39:01 +0100 Mihai Moldovan (dae18fc) * misc: whitespace only. 2016-01-28 15:41:54 +0100 Mihai Moldovan (3ca7e32) * common: update copyright notices. Happy new year! 2016-01-26 21:39:14 +0100 Mihai Moldovan (7dab4e8) * debian/control: whitespace only. 2015-07-28 22:36:15 +0200 X2Go Release Manager (d917703) * Continue development 2015-07-28 21:34:22 +0200 X2Go Release Manager (59e42be) * release 0.5.0.4 (tag: 0.5.0.4) 2015-07-22 00:16:05 +0200 Mike Gabriel (4ca968c) * Don't check for "fuse" group membership to decide if a user can use sshfs. The "fuse" group has been dropped from Debian jessie and beyond. Furthermore, some openSUSE versions use the "trusted" group for granting (and never used a group named fuse). 2015-06-19 21:53:51 +0200 Mike Gabriel (860fa98) * debian/control: Allow qvd-nxproxy as an alternative to nxproxy. Allows co-installation of PyHoca-CLI+GUI and the TheQVD client. 2015-03-09 13:02:50 +0100 Mike Gabriel (be80cd9) * improve last commit 2015-03-09 13:00:37 +0100 Mike Gabriel (19feecc) * End session gracefully if we fail setting up the SSH agent forwarding socket. 2015-02-26 10:17:55 +0100 Mike Gabriel (db715ac) * No such constant loglevel_WARNING, must be logolevel_WARN. 2015-02-26 10:17:21 +0100 Mike Gabriel (62a7ea7) * Continue development... 2015-01-25 13:21:20 +0100 Mike Gabriel (3481e61) * release 0.5.0.3 (tag: 0.5.0.3) 2015-01-24 09:13:59 -0500 Mike DePaulo (f956f10) * Windows: Update from VcXsrv-1.15.2.1-xp+vc2013+x2go1 to VcXsrv-1.15.2.2-xp+vc2013+x2go1 2015-01-15 11:04:20 +0100 Mike Gabriel (ecea56a) * Catch yet another X2GoControlSessionException. This time during control_backend.remote_peer() during X2GoSession._resume(). 2015-01-09 01:15:34 +0100 Mike Gabriel (ef9d36f) * After calling x2gomountdirs, write stdout and _stderr_ to the logging instance. 2015-01-08 19:30:48 +0100 Mihai Moldovan (0610722) * common: update copyright notices, whitespace fixes. Happy new year! 2014-12-17 16:30:51 +0100 Mike Gabriel (a74403b) * Only sync password and passphrase if we do not enforce password authentication. 2014-12-09 09:42:52 +0100 Mike Gabriel (b7b43de) * Catch EOFError in x2go_forward_tunnel_handle(). 2014-12-09 09:42:06 +0100 Mike Gabriel (c6b4f2a) * fix changelog 2014-12-02 22:48:10 +0100 Mike Gabriel (03ecca6) * Fix catching control session exceptions in X2GoSession class. 2014-11-27 13:16:10 +0100 Mike Gabriel (a6d6efa) * Continue development... 2014-11-27 13:03:42 +0100 Mike Gabriel (2a2385b) * release 0.5.0.2 (tag: 0.5.0.2) 2014-11-15 21:31:13 +0100 Mike Gabriel (d5472a9) * Fix cross-user desktop sharing feature since introduction of clipboard mode feature. 2014-11-12 06:16:40 +0100 Mike Gabriel (6c6c3fa) * Catch control session disconnects during session's run_command call. 2014-10-21 01:07:10 +0200 Mike Gabriel (4d52c91) * Mention "client side printing support" in LONG_DESCRIPTION. 2014-10-21 01:06:34 +0200 Mike Gabriel (e015a95) * debian/copyright: Relicense packaging files under same license as upstream files. 2014-10-20 18:14:27 -0400 Mike DePaulo (caa093e) * Windows: Update from VcXsrv-xp-1.14.3.2 to VcXsrv-1.15.2.1-xp+vc2013+x2go1 2014-10-20 18:16:35 -0400 Mike DePaulo (826b025) * Update nxproxy from 3.5.0.12 to 3.5.0.27_cygwin-2014-10-18 2014-10-20 23:30:54 +0200 Mike Gabriel (fef7629) * debian/control: Bump Standards: to 3.9.6. No changes needed. 2014-10-20 23:30:24 +0200 Mike Gabriel (71b5f47) * Continue development... 2014-10-20 22:53:01 +0200 Mike Gabriel (c35d660) * release 0.5.0.1 (tag: 0.5.0.1) 2014-10-20 22:52:18 +0200 Mike Gabriel (1e11f64) * Provide API inline documentation for http(s) session broker client implementation. 2014-10-20 22:28:45 +0200 Mike Gabriel (3a3751e) * Update README file. 2014-10-20 22:24:38 +0200 Mike Gabriel (01d8e27) * Update TODO file. 2014-10-20 22:24:17 +0200 Mike Gabriel (10a9dbf) * Continue development... 2014-10-20 12:41:40 +0200 Mike Gabriel (7757563) * release 0.5.0.0 (tag: 0.5.0.0) 2014-10-20 12:39:31 +0200 Mike Gabriel (1ad9c40) * X2GoSessionRegistry: Don't report about sessions that have a not yet fully assigned session name / profile name / profile id. 2014-10-20 10:34:49 +0200 Mike Gabriel (65f298a) * fix for d90c4b182 2014-10-20 10:20:40 +0200 Mike Gabriel (712582c) * formalistic clean-up using pyflakes 2014-10-20 09:55:24 +0200 Mike Gabriel (0874534) * Capture X2GoControlSessionException occurrences during client-side folder sharing initializaation while starting/resuming a session. 2014-10-20 09:54:08 +0200 Mike Gabriel (81b9089) * indentation fix 2014-10-20 09:24:37 +0200 Mike Gabriel (d90c4b1) * Only convert the value of the export session profile option if not already a Python dictionary. 2014-10-20 09:23:21 +0200 Mike Gabriel (2fc8ab6) * Detect more exceptions in the requests module when authenticating against a session broker. 2014-10-17 13:31:24 +0200 Mike Gabriel (d3273c0) * debian/control / python-x2go.spec: Update D (python-x2go): python-paramiko (>= 1.15.1-0~). Update R for python-x2go: python-paramiko >= 1.15.1. (Fixes: #602). 2014-10-16 16:34:12 +0200 Mike Gabriel (af9972a) * Make sure we do a deepcopy of the default session profile parameters. 2014-10-14 22:10:29 +0200 Mike Gabriel (a0d270f) * Fix creating/renaming/reconfiguring session profiles. Handle host option properly (as list). 2014-10-14 21:36:42 +0200 Mike Gabriel (191b92e) * Fix various hrefs in __doc__ strings. 2014-10-14 21:18:32 +0200 Mike Gabriel (c58c211) * Add all python packages under R to BR (for epydoc run). 2014-10-14 21:13:41 +0200 Mike Gabriel (bf1fb49) * python-x2go.spec: Avoid duplicate files in openSUSE/SLES. 2014-10-14 18:52:25 +0200 Mike Gabriel (de52983) * python-x2go.spec: fix bad %if condition 2014-10-14 18:27:33 +0200 Mike Gabriel (7f0888c) * python-x2go.spec: some rpmlint sedatives... 2014-10-14 18:19:27 +0200 Mike Gabriel (286ae13) * python-x2go.spec: typo 2014-10-14 18:16:57 +0200 Mike Gabriel (7ba06fa) * python-x2go.spec: Additionally adapt to building on openSUSE/SLES. 2014-10-09 18:38:45 +0200 Mike Gabriel (9fbb26f) * If the listsessions command detects a terminated or suspended session, we have to destroy the corresponding X2GoTerminalSession() to trigger a proper cleanup of that instance. 2014-10-09 18:37:23 +0200 Mike Gabriel (4661991) * improve log message 2014-10-09 05:46:35 +0200 Mike Gabriel (9e05ee5) * X2GoControlSession: Don't mess with the associated_terminals dict if the control session has already died away (i.e. been forcefully disconnect). 2014-10-09 05:45:09 +0200 Mike Gabriel (efe7226) * X2GoTelekinesis class: initial properties during instantiation 2014-10-07 23:03:24 +0200 Mike Gabriel (02531f1) * Detect non-installed sshfs (required for Telekinesis). 2014-10-07 14:34:28 +0200 Mike Gabriel (32f828f) * Release _share_local_folder_lock on instance X2GoTerminalSession destruction. 2014-10-07 14:13:02 +0200 Mike Gabriel (457dcb6) * Fix session lock release in various methods of the X2GoSession class. 2014-10-07 14:11:39 +0200 Mike Gabriel (9fd867a) * more work on transport lock release 2014-10-07 14:01:40 +0200 Mike Gabriel (920d12e) * Fix transport lock release in X2GoControlSession._x2go_sftp_put(). 2014-10-03 13:35:22 +0200 Mike Gabriel (f5e9380) * be more verbose on socket errors in rforward.py 2014-10-03 13:18:34 +0200 Mike Gabriel (e3f748b) * fix faulty loglevel 2014-10-03 13:11:56 +0200 Mike Gabriel (0a4c773) * Don't choke on non-initialized SSH transport objects when initializing SFTP client. 2014-10-03 13:11:16 +0200 Mike Gabriel (e6145a1) * rebase 2014-10-03 13:10:25 +0200 Mike Gabriel (5c8d005) * Handle sudden control session death during local folder sharing 2014-10-03 13:03:09 +0200 Mike Gabriel (e5a44d5) * Handle socket errors on the reverse port forwarding tunnels more gracefully. 2014-10-03 13:02:37 +0200 Mike Gabriel (05643fc) * Use session_name, not session_info object's __str__() method to obtain session name (in X2GoTelekinesis). 2014-10-01 11:29:25 +0200 Mike Gabriel (f6b226d) * typo fix for last commit 2014-10-01 11:01:37 +0200 Mike Gabriel (2ff5f60) * Support server-side Telekinesis versions that ship their own (teki-)sftpserver. 2014-09-10 12:04:15 +0200 Mike Gabriel (7cb2ca7) * Handle duplicate profile names gracefully (i.e. append a " (1)", " (2)", ... to the session profile name). (Fixes: #500). 2014-09-10 09:57:26 +0200 Mike Gabriel (f308782) * test if DISPLAY is accessible before detecting the local desktop geometry 2014-09-05 15:56:35 +0200 Mike Gabriel (5392d3c) * Don't use compression on TeKi sshfs mounts. 2014-09-05 15:19:54 +0200 Mike Gabriel (b801156) * Assure proper NX Proxy cleanup when sessions suspends/terminates. 2014-09-05 15:18:21 +0200 Mike Gabriel (6f0f1ae) * don't mention in __doc__ string what TeKi applications exist 2014-09-05 15:17:39 +0200 Mike Gabriel (a40d73f) * Clean up terminal sessions properly when the clean_sessions() method of the control session has got called. 2014-09-05 15:16:26 +0200 Mike Gabriel (d5fcb46) * Assure proper Telekinesis client cleanup when sessions suspends/terminates. 2014-08-31 17:09:12 +0200 Mike Gabriel (64ff151) * For reverse port forwardings use IPv4 localhost address only. 2014-08-30 17:12:53 +0200 Mike Gabriel (b950f16) * fix __doc__ string for last commit 2014-08-30 17:09:55 +0200 Mike Gabriel (b1292aa) * Use Xlib to detect client-side destop geometry. 2014-08-29 22:08:34 +0200 Mike Gabriel (56dd121) * fix for last commit 2014-08-29 21:43:54 +0200 Mike Gabriel (54fddb2) * On non-Windows platforms, enforce usage of the "ares" DNS resolver in python-gevent (which is available since Python gevent 1.0~). (Fixes: #588). 2014-08-29 21:39:16 +0200 Mike Gabriel (454659b) * Enforce usage of the "ares" DNS resolver in python-gevent (which is available since Python gevent 1.0~). (Fixes: #588). 2014-08-28 10:58:32 +0200 Mike Gabriel (c09f73a) * Be more exact when detecting the NX proxy window id. 2014-07-09 20:06:54 +0200 Mike Gabriel (74c5b68) * Rename LICENSE.txt to COPYING. 2014-07-02 22:53:13 +0200 Mike Gabriel (d68f163) * make the TeKi client startup once more more robust... 2014-07-07 19:00:44 -0400 Mike DePaulo (837a73c) * Windows: On XP & Server 2003 (R2), prevent high PulseAudio CPU usage by lowering its CPU priority to "normal" (Fixes: #537) 2014-07-06 16:31:48 -0400 Mike DePaulo (8d8f2a3) * use VcXsrv-xp-1.14.3.2 when run in development mode 2014-07-05 08:54:13 -0400 Mike DePaulo (166607d) * Windows: Fix compatibility with PulseAudio 3.0 & later (Fixes: #532) 2014-06-29 00:20:36 +0200 Mike Gabriel (2f57967) * start becoming aware of ECDSA keys 2014-06-29 00:20:10 +0200 Mike Gabriel (2aa779e) * use x2gostartagent / x2goresume-session to pass-through the clipboard parameter (requires X2Go Server 4.0.1.16 or higher) 2014-06-25 01:20:15 +0200 Mike Gabriel (0d155fe) * take down telekinesis before taking down nxproxy when sessions suspend/terminate 2014-06-25 01:19:20 +0200 Mike Gabriel (cea41b3) * Newly understand our own Paramiko/SSH forwarding tunnel code. Become aware of handling multiple connects on the same tunnel. 2014-06-24 16:49:45 +0200 Mike Gabriel (16814f4) * remove debug code from forward.py 2014-06-24 16:22:06 +0200 Mike Gabriel (894917e) * Better control the startup bootstrap of the Telekinesis client subsystem. 2014-06-24 16:16:00 +0200 Mike Gabriel (dd92083) * Performance tests have shown, that enabling SSH compression is not a good idea. NX should handle that instead (and does). 2014-06-24 16:14:38 +0200 Mike Gabriel (a9caabf) * Only enable Telekinesis client debugging if the logger instance is in debug mode. 2014-06-24 16:09:06 +0200 Mike Gabriel (ce50d23) * Create a "session.window" file in the session directory. This file for now contains one line "ID:". The file appears once a session window comes up (start/resume), and disappears once the session window closes (suspend/terminate). 2014-06-16 10:46:00 +0200 Mike Gabriel (61de9d7) * Make sure that the x2gosuspend-session/x2goterminate-session commands are sent to the X2Go Server before we take down the NX proxy subprocess. 2014-06-16 10:10:20 +0200 Mike Gabriel (cb8f176) * Revert "Suspend/terminate terminal session instance first, before sending the x2gosuspend-session/x2goterminate-session command to the X2Go Server." 2014-06-16 09:25:53 +0200 Mike Gabriel (5284ca2) * Suspend/terminate terminal session instance first, before sending the x2gosuspend-session/x2goterminate-session command to the X2Go Server. 2014-06-16 09:07:41 +0200 Mike Gabriel (9b48f6f) * Handle non-available color depth in X2Go session name gracefully. (Fixes: #358). 2014-06-16 08:34:16 +0200 Mike Gabriel (754bf16) * Disallow server-side users to override X2Go Server commands via ~/bin (or similar). (Fixes: #334). 2014-06-11 22:47:12 +0200 Mike Gabriel (2529d5c) * Don't start telekinesis client if not support server-side. Don't attempt at starting telekinesis client, if it is not installed. 2014-06-11 13:34:03 +0200 Mike Gabriel (08ff742) * Provide support for new session parameter: clipboard. (Fixes: #508). 2014-06-11 13:33:29 +0200 Mike Gabriel (8883680) * Revert "Provide support for new session parameter: clipboard. (Fixes: #508)." 2014-06-06 03:10:37 +0200 Mike Gabriel (74792e6) * Provide support for new session parameter: clipboard. (Fixes: #508). 2014-06-11 13:18:35 +0200 Mike Gabriel (5c63c6b) * Revert "Provide support for new session parameter: clipboard." 2014-06-11 13:09:47 +0200 Mike Gabriel (38d492c) * Silent ignore it if we cannot detect the local Xlib.display.Display() instance (happens with polyinstantiated /tmp dirs). 2014-06-11 13:08:36 +0200 Mike Gabriel (fc9a84a) * changelog entry for last commit 2014-06-11 10:59:28 +0200 Mike Gabriel (0184629) * Split up NX output and NX errors into two separate files. 2014-06-08 01:29:58 +0200 Mike Gabriel (fe68c91) * raise sleep period of proxy backend 2014-06-07 22:51:48 +0200 Mike Gabriel (645f105) * typo fix in log msg 2014-06-07 22:50:46 +0200 Mike Gabriel (3f2b693) * improve debugging of SFTP server authentication 2014-06-06 03:10:37 +0200 Mike Gabriel (50c0bdd) * Provide support for new session parameter: clipboard. 2014-06-06 03:09:40 +0200 Mike Gabriel (fbc6745) * fix __doc__ string 2014-06-06 03:08:17 +0200 Mike Gabriel (555eaa3) * fix __doc__ string 2014-06-06 03:06:24 +0200 Mike Gabriel (e113954) * make telekinesis-client support more robust 2014-06-06 03:05:50 +0200 Mike Gabriel (9701d66) * Use gevent to spawn the TeKi client start-up process (instead of waiting for it to return). 2014-06-06 03:04:19 +0200 Mike Gabriel (70f0136) * Add support for a subsystem string when setting up port forwarding tunnels. 2014-06-03 19:15:58 +0200 Mike Gabriel (4c4e2f9) * Type-hardening of X2GoControlSession class's C{connect()} method. Handle hostnames that come in as lists gracefully. 2014-06-03 19:14:43 +0200 Mike Gabriel (5c0d29d) * Don't construct the sshproxy_tunnel parameter in x2go/utils.py. Leave that to higher level classes that know more about X2Go internals. 2014-06-03 19:13:47 +0200 Mike Gabriel (77d0d0c) * follow-up commit for last commit 2014-06-03 18:53:37 +0200 Mike Gabriel (6d4119c) * Type-hardening of X2GoSshProxy class. Accept hosts as list and strings. If hosts are given as a list, a random list element will be taken as host (for connecting and for the SSH proxy tunnel setup). 2014-06-03 18:48:09 +0200 Mike Gabriel (b84b608) * Stop manipulating session profiles in X2GoSshProxy class. Esp. stop manipulating session profiles with deprecated session options. 2014-05-28 12:09:11 +0200 Mike Gabriel (e30e1bb) * Provide Telekinesis support in Python X2Go. 2014-05-09 18:34:10 +0200 Mike Gabriel (d143c72) * Support SSH proxy autologin feature of X2Go Session Broker. 2014-04-16 12:56:26 +0200 Mike Gabriel (9eb874f) * fix for last commit 2014-04-16 12:49:33 +0200 Mike Gabriel (9f2d9ec) * Windows: Fix crash while attempting to find the session window. 2014-04-16 10:27:43 +0200 Mike Gabriel (f4e64e8) * Make X2GoClient's constructor aware of non-usable X-Server ports. 2014-04-16 10:05:22 +0200 Mike Gabriel (634988e) * Session profiles: default value type for exports session profile option is an empty dictionary. 2014-04-15 21:23:47 +0200 Mike Gabriel (b4f2297) * typo fix 2014-04-15 20:53:19 +0200 Mike Gabriel (c462c9a) * Only check running X-Servers that have the same WMI SessionId as the current X2Go application. 2014-04-15 08:31:43 +0200 Mike Gabriel (3138ba4) * typo fix in log message 2014-04-15 08:23:38 +0200 Mike Gabriel (e58cebf) * Rename hook method HOOK_no_known_xserver_found to HOOK_no_installed_xservers_found. Call this new hook if no installed X-Servers could be found on the system. 2014-04-14 16:41:29 +0200 Mike Gabriel (db061d8) * return from X2GoClientXConfig.write() method 2014-04-14 16:39:18 +0200 Mike Gabriel (e630ea6) * Make sure X2GoClientXConfig config file really gets written to disk (after we changed the internas of X2GoIniFile for this new major release). 2014-04-14 16:38:35 +0200 Mike Gabriel (8204e4b) * Fix detection of matching path names in X2GoIniFiles. 2014-04-14 16:26:56 +0200 Mike Gabriel (5bf0169) * no xconfig_config_file on non-Windows systems 2014-04-14 16:26:28 +0200 Mike Gabriel (6f43617) * typo fix 2014-04-05 09:02:09 +0200 Mike Gabriel (7686e68) * Handle broker setups that don't require credentials. Connection can be established simply by leaving the password (and authid) empty. 2014-04-05 01:15:47 +0200 Mike Gabriel (275bea4) * fix some HOOKs' __doc__ strings 2014-04-05 01:13:03 +0200 Mike Gabriel (db36b70) * Allow user interaction via a HOOK if broker connection problems occur. 2014-04-05 01:12:19 +0200 Mike Gabriel (b462004) * httpbroker: fix handling of session profile ID/name cache 2014-04-03 16:30:26 +0200 Mike Gabriel (857f5f3) * Capture broker connection problems during selectsession calls to the broker via a HOOK method. 2014-03-31 12:57:20 +0200 Mike Gabriel (6bdf737) * On Windows: Improve debugging when a new X-Server port has to be allocated. 2014-03-24 15:25:51 +0100 Mike Gabriel (7b061bf) * Support cookie based authentication against a http(s) session broker. 2014-03-24 13:49:55 +0100 Mike Gabriel (2cc0568) * Allow catching "connection refused" errors while talking to an X2Go Session Broker (X2GoBrokerConnectionException). 2014-03-21 01:31:43 +0100 Mike Gabriel (66e96d2) * fix profile COPYing and hostname changes 2014-03-20 08:36:28 +0100 Mike Gabriel (ce97f34) * Handle injection of PKey (Paramiko SSH key) objects for authentication from the broker session profiles backend. 2014-03-20 08:34:42 +0100 Mike Gabriel (3259daf) * another one: optimize session profile option retrieval 2014-03-19 15:26:37 +0100 Mike Gabriel (66cecf3) * Speed-optimize session profile ID <-> name mapping. 2014-03-18 10:21:26 +0100 Mike Gabriel (80e2752) * fix for last commit 2014-03-18 09:42:20 +0100 Mike Gabriel (5eea83a) * fix method name 2014-03-18 09:42:08 +0100 Mike Gabriel (580cb0e) * X2GoSessionProfile.get_server_hostname must return unicode objects. 2014-03-18 09:40:14 +0100 Mike Gabriel (0ac8563) * fix changelog entry 2014-03-18 01:11:03 +0100 Mike Gabriel (921ee10) * python-x2go.spec: Add dependencies: python-requests, python-simplejson. 2014-03-18 01:10:09 +0100 Mike Gabriel (41395ef) * debian/control: Add dependencies: python-requests, python-simplejson. 2014-03-04 13:58:21 +0100 Mike Gabriel (f5f6f6d) * Provide session profile backend for a http broker. 2014-03-04 13:57:43 +0100 Mike Gabriel (86d185a) * Default to xdg-open as default PDF viewer command. 2014-03-04 13:56:49 +0100 Mike Gabriel (3088eda) * some code beautifications (tag: CVix/1.99.3) 2014-02-10 17:01:05 +0100 Mike Gabriel (9fc8c3e) * fix _get_profile_parameter() in profile backend's base.py file 2014-02-10 17:00:25 +0100 Mike Gabriel (6b2929a) * Preserve class-wide control session options (like add_to_known_hosts, forward_sshagent, etc.). Thanks to Dick Kniep for reporting this. 2014-02-07 13:31:17 +0100 Mike Gabriel (26ad1cd) * Merge branch 'brokerclient' 2014-01-08 15:14:30 +0100 Mike Gabriel (a08fa96) * release 0.4.0.9 2014-02-07 13:28:47 +0100 Mike Gabriel (e3ce176) * Merge branch 'release/0.4.0.x' 2014-02-07 13:09:43 +0100 Mike Gabriel (9f1bd9b) * Return color depth on MS Windows machines. 2014-02-07 13:09:43 +0100 Mike Gabriel (4ba8ae7) * Return color depth on MS Windows machines. 2014-02-05 22:32:07 +0100 Mike Gabriel (c583edc) * typo fix 2014-02-05 16:23:14 +0100 Mike Gabriel (023800e) * don't raise an exception if tf-auth is requested 2014-01-08 22:18:44 +0100 Mike Gabriel (9557481) * Don't parse default values to ConfigParser constructor when initializing an INI file. 2014-02-05 22:32:41 +0100 Mike Gabriel (a423bfe) * Fix setting default values in X2GoClientXConfig class. 2014-02-05 22:32:07 +0100 Mike Gabriel (49650d9) * typo fix 2014-02-05 16:23:40 +0100 Mike Gabriel (a764584) * provide BACKENDS in main module 2014-02-05 16:23:14 +0100 Mike Gabriel (bb4b79e) * don't raise an exception if tf-auth is requested 2014-01-09 12:17:50 +0100 Mike Gabriel (1ea5f92) * Fully rework backend concept in Python X2Go. Breaks compatibility with earlier versions of Python X2Go concerning backends (probably not really used by third-party products, if at all). 2014-01-09 09:47:47 +0100 Mike Gabriel (a328215) * bump version to 0.5.0.0 2014-01-08 22:18:44 +0100 Mike Gabriel (7815d13) * Don't parse default values to ConfigParser constructor when initializing an INI file. 2014-01-08 15:31:23 +0100 Mike Gabriel (24836a9) * Merge branch 'release/0.4.0.x' 2014-01-08 15:14:30 +0100 Mike Gabriel (eb2f3b2) * release 0.4.0.9 (tag: 0.4.0.9) 2014-01-07 16:13:38 +0100 Mike Gabriel (65d661a) * happy new year 2014-01-07 16:13:38 +0100 Mike Gabriel (5e46458) * happy new year 2014-01-07 16:13:38 +0100 Mike Gabriel (ca7464b) * happy new year 2014-01-05 16:38:33 +0100 Mike Gabriel (2cef942) * debian/changelog: typo 2014-01-05 16:38:21 +0100 Mike Gabriel (167ec13) * Split up session profile backend into generic and storage specific parts. 2014-01-03 11:59:16 +0100 Mike Gabriel (4808f51) * Handle host key checks for hosts that do not have a port specified. 2013-12-17 21:33:37 +0100 Mike Gabriel (8dd9b31) * Only use []: if is not 22. 2013-12-17 21:25:50 +0100 Mike Gabriel (b98840c) * fix for previous+1 commit 2013-12-17 21:21:57 +0100 Mike Gabriel (155379e) * Set keepalive on proxy channel. 2013-12-17 21:21:42 +0100 Mike Gabriel (73e0f19) * Make channel compression to all authentication methods. 2013-12-17 20:54:11 +0100 Mike Gabriel (d7ba282) * Fix regression: Make password logins with PyHoca-CLI succeed again. 2013-12-17 11:36:11 +0100 Mike Gabriel (3d3a140) * Fix tests for two-factor authentication in control session and SSH proxy code. 2013-12-10 20:26:35 +0100 Mike Gabriel (0756645) * whitespace fix 2013-12-08 12:07:38 +0100 Mike Gabriel (135ba5c) * Clear (Fedora package) changelog. 2013-12-08 11:48:21 +0100 Mike Gabriel (d22af9e) * python-x2go.spec: Drop dependency on python-cups. 2013-12-08 01:25:21 +0100 Mike Gabriel (e1b6bf0) * make sure our RPM package version is lower than a package version from EPEL 2013-11-30 22:54:54 +0100 Mike Gabriel (0f42c31) * typo fix 2013-11-30 22:53:37 +0100 Mike Gabriel (4120df4) * debian/changelog: wrap too-long lines 2013-11-30 22:51:44 +0100 Mike Gabriel (7a11d53) * Improve setup.py script: make it run with Python3 and older Python2 versions. 2013-11-30 22:46:36 +0100 Mike Gabriel (71ebec2) * improve python-x2go.spec: LDAP support is deprecated and replaced by session broker, build for HEAD, python-x2go-py3.patch is already applied upstream 2013-11-30 22:42:12 +0100 Mike Gabriel (aa287d9) * Ship python-x2go.spec (RPM package definitions) in upstream project. (Thanks to the Fedora package maintainers). 2013-11-30 22:39:56 +0100 Mike Gabriel (b9985b4) * Import python-x2go-py3.patch from Fedora. Thanks to Orion!!! 2013-11-29 13:26:53 +0100 Mike Gabriel (4cf74c4) * change versioning scheme 2013-11-27 14:57:56 +0100 Mike Gabriel (5419bcb) * debian/source/format: Switch to format 1.0. 2013-11-27 14:28:34 +0100 Mike Gabriel (696f1b0) * Check for pulse cookie file in old (~/.pulse-cookie) and new (~/.config/pulse/cookie) location. 2013-11-22 00:08:26 +0100 Mike Gabriel (596f0cb) * Fix session window detection when local session manager is the i3 session manager (which uses _NET_CLIENT_LIST_STACKING instead of _NET_CLIENT_LIST). 2013-11-22 00:07:28 +0100 Mike Gabriel (1032da4) * Report about found session window / session window retitling in debug mode. 2013-11-13 08:55:27 +0100 Mike Gabriel (22e1546) * Differentiate between desktop sharing errors and desktop sharing access that gets denied by the other/remote user. 2013-11-05 17:54:27 +0100 Mike Gabriel (0bf478f) * fix for last commit 2013-11-05 10:12:25 +0100 Mike Gabriel (6e04237) * Properly handle (=expand) the "~" character in key filenames. (Brought to attention by Eldamir on IRC. Thanks!). 2013-10-29 18:36:06 +0100 Mike Gabriel (5b8164d) * Handle echoing ~/.*shrc files gracefully via SSH client connections. Do not allow data injections via ~/.*shrc files. (Fixes: #335). 2013-09-14 15:54:57 +0200 Mike Gabriel (e92d6b6) * Implement two-factor authentication. 2013-10-24 09:54:59 +0200 Orion Poplawski (6bc5020) * debian/control: Drop python-cups from Depends: field. Python CUPS is no dependency if Python X2Go. (Fixes: #329). 2013-10-24 09:50:00 +0200 Kenneth Pedersen (732635e) * Color depth detection: Stop using win32api.GetSystemMetrics(2) which actually returns the width of a vertical scroll bar in pixels. Instead, create a screen display context and query it for the color depth. (Fixes: #330). 2013-10-04 16:12:22 +0200 Mike Gabriel (058bc7b) * No Unicode chars in log messages. Eliminated one more in checkhosts.py. 2013-09-09 21:49:28 +0200 Mike Gabriel (0ed9ba9) * Rewrite passwords that are not string/unicode to an empty string. 2013-09-08 01:14:04 +0200 Mike Gabriel (1b463da) * Fix parameter handling in X2GoSession.connect(). 2013-09-08 01:13:24 +0200 Mike Gabriel (d3378ca) * Keep private key information even if force_password_auth is set in the control session's connect() method. 2013-09-08 01:11:31 +0200 Mike Gabriel (52b1c85) * slightly modify exception messages 2013-09-04 10:59:22 +0200 Mike Gabriel (b443155) * change from empty string to None as default values 2013-09-04 10:58:45 +0200 Mike Gabriel (77bf9f6) * Invalidate SSH private keys (filename, pkey object) when look_for_keys is requested. 2013-09-03 18:21:47 +0200 Mike Gabriel (9a1a4ca) * fine-tuning SSH proxy auth 2013-09-03 13:14:10 +0200 Mike Gabriel (c74e718) * Support encryption passphrases on SSH private key files (X2Go SSH connections as well as SSH proxy connections). 2013-09-03 10:22:54 +0200 Mike Gabriel (8a48422) * Support encryption passphrases on SSH private key files. 2013-09-03 09:16:31 +0200 Mike Gabriel (c69719a) * Store the session password in base64 encoded string in order to make it harder spotting the long term stored (for the duration of the session) plain text password. 2013-08-28 10:15:48 +0200 Mike Gabriel (19da038) * Agent channels in Paramiko can raise an EOFError if the connection has got disrupted. Ignoring this. 2013-08-07 12:18:57 +0200 Mike Gabriel (ed9005d) * Continue development... 2013-08-07 12:17:44 +0200 Mike Gabriel (000e5e3) * release 0.4.0.8 (tag: 0.4.0.8) 2013-08-07 11:46:23 +0200 Mike Gabriel (486fa4f) * Remove shbang from x2go/tests/runalltests.py, as well. 2013-08-05 13:57:24 +0200 Mike Gabriel (436a770) * Do not overwrite not-yet-suspended terminal session objects during session resumption. 2013-08-05 12:52:19 +0200 Mike Gabriel (b435cdc) * Use the session object's lock to detect if updating the session status in the session registry is something appropriate to do. 2013-08-05 10:58:43 +0200 Mike Gabriel (81c41bd) * follow-up commit for last commit 2013-08-05 10:53:57 +0200 Mike Gabriel (60eebc8) * Fix session profile updates with changes to the host parameter. 2013-08-03 22:32:45 +0200 Mike Gabriel (0c579d2) * Continue development... 2013-08-03 22:28:41 +0200 Mike Gabriel (669b839) * release 0.4.0.7 (tag: 0.4.0.7) 2013-08-03 22:27:52 +0200 Orion Poplawski (b16e046) * Remove shbangs from python-x2go library code. (Fixes: #283). 2013-08-03 18:52:17 +0200 Mike Gabriel (fab3e25) * /debian/changelog: typo fix 2013-08-03 18:36:36 +0200 Mike Gabriel (ffff661) * Add some sanity checks before actually starting to add a session profile. 2013-07-31 09:33:29 +0200 Mike Gabriel (3847b19) * Make _update_mounts in session registry cache more failsafe, this probably fixes an accumulation of server disconnects observed in recent version of Python X2Go. 2013-07-31 09:31:40 +0200 Mike Gabriel (963eaed) * Ignore non-registered session UUIDs in X2GoClient.clean_sessions() method. 2013-07-30 22:54:50 +0200 Mike Gabriel (7d7ec5f) * Drop duplicate method in terminal backend: is_desktop_session(). 2013-07-30 22:54:02 +0200 Mike Gabriel (7f3bb41) * Revert "Drop duplicate method in terminal backend: is_desktop_session()." 2013-07-30 22:53:26 +0200 Mike Gabriel (6d25309) * Drop duplicate method in terminal backend: is_desktop_session(). 2013-07-30 22:44:26 +0200 Mike Gabriel (1a3160a) * debian/control: Replace LDAP support with session brokerage support in LONG_DESCRIPTION. 2013-07-28 19:49:06 +0200 Mike Gabriel (b141dfb) * Continue development... 2013-07-28 19:43:06 +0200 Mike Gabriel (3dfee63) * release 0.4.0.6 (tag: 0.4.0.6) 2013-07-28 15:04:20 +0200 Mike Gabriel (a919329) * When resuming session be tolerant towards disrupted connections. 2013-07-23 23:10:04 +0200 Mike Gabriel (07a1fd1) * Make Python X2Go aware of the MATE desktop environment. 2013-07-23 23:09:20 +0200 Mike Gabriel (6c7e103) * Use XFCE as desktop environment in example scripts. 2013-07-19 23:15:48 +0200 Mike Gabriel (c735e6a) * use VcXsrv-1.14.2.0 when run in development mode 2013-07-19 17:45:58 +0200 Mike Gabriel (a72c2d2) * Continue development... 2013-07-18 22:36:28 +0200 Mike Gabriel (f64a407) * release 0.4.0.5 (tag: 0.4.0.5) 2013-07-18 22:34:42 +0200 Mike Gabriel (c0a3832) * remove debug code 2013-07-18 22:32:07 +0200 Mike Gabriel (2fa59c4) * remove exception raisal 2013-07-18 22:30:55 +0200 Mike Gabriel (a0f8350) * Improve log message when setting up port forwarding tunnel. 2013-06-25 22:26:36 +0200 Mike Gabriel (501a5bd) * Paramiko monkey patch: Hostnames with the default SSH_PORT are stored in hostname-only format to the known_hosts file. Fixes redundant requests for confirming the remote host's fingerprint if port 22 is used. 2013-06-18 20:29:28 +0200 Mike Gabriel (7c131c8) * Continue development... 2013-06-18 20:27:59 +0200 Mike Gabriel (349717f) * release 0.4.0.4 (tag: 0.4.0.4) 2013-06-18 20:27:12 +0200 Mike Gabriel (512b72c) * Fix renaming of profile names. 2013-06-14 15:15:22 +0200 Mike Gabriel (22da6ea) * fix for last commit 2013-06-06 13:06:34 +0200 Mike Gabriel (a67628e) * Only do x2golistmounts calls for the session cache on running sessions. 2013-05-31 08:41:43 +0200 Mike Gabriel (989c3d6) * Enable keepalive callbacks on open SSH client connections. 2013-05-29 00:19:15 +0200 Mike Gabriel (4ef900d) * Support mounting client-side folders on UNC paths. 2013-05-28 22:54:50 +0200 Mike Gabriel (f1dc232) * Ignore KeyError exceptions in session cache for suddenly removed cache items. Silence/fix some race conditions on connection failures. 2013-05-08 13:22:30 +0200 Mike Gabriel (b433567) * Become aware of fixed paramiko features since paramiko 1.11.0. Stop monkey patching those methods that got fixed in 1.11.0. 2013-04-29 13:58:46 +0200 Mike Gabriel (5b554af) * Fix the restoreexports logic on mount/unmount/unmount all requests. Make sure client-side offline network shares do not get purged from the session profile configuration if unavailable. (Fixes: #192). 2013-04-26 16:18:32 +0200 Mike Gabriel (24543dc) * fix last commit 2013-04-26 11:12:56 +0200 Mike Gabriel (c0c6a03) * Save exports in session profile directly after mounting/unmounting a share if the session profile parameter restoreexports is set. 2013-04-21 23:28:25 +0200 Mike Gabriel (3f1f84b) * increment egg version 2013-04-21 23:21:43 +0200 Mike Gabriel (64bf1d0) * Continue development... 2013-04-21 22:40:58 +0200 Mike Gabriel (e05c33e) * release 0.4.0.3 (tag: 0.4.0.3) 2013-04-21 17:38:59 +0200 Mike Gabriel (d5df830) * Prevent Exception when creating new session profiles. Spotted by Dick Kniep. Thanks! 2013-04-15 11:13:06 +0200 Mike Gabriel (7792ed6) * Fix faulty value creations for the export session profile parameter. (Fixes: #162). This issue occured when the restoreexports feature had been activated in the session profile. 2013-04-15 10:43:48 +0200 Mike Gabriel (e5f05db) * after thorough testing: fix for #130 also fixes #147 2013-03-26 21:24:41 +0100 Mike Gabriel (e6964f1) * fix version in changelog 2013-03-26 00:36:33 +0100 Mike Gabriel (33167fc) * Make Python X2Go aware of the Cinnamon desktop shell. 2013-03-07 05:07:39 +0100 Mike Gabriel (9ac619d) * Fix inheritance of Paramiko/SSH exception. 2013-03-03 15:57:57 +0100 Mike Gabriel (ec2880a) * Continue development... 2013-03-03 15:56:40 +0100 Mike Gabriel (450da43) * release 0.4.0.2 (tag: 0.4.0.2) 2013-03-03 15:52:58 +0100 Mike Gabriel (d761e7f) * Empty session profile name/id cache when adding new profiles. (Fixes: #130). 2013-02-14 05:25:55 +0100 Mike Gabriel (938bb81) * sort session list fields 2013-02-13 12:49:50 +0100 Mike Gabriel (4af3162) * Continue development... 2013-02-13 12:48:51 +0100 Mike Gabriel (752880e) * release 0.4.0.1 (tag: 0.4.0.1) 2013-02-13 12:36:42 +0100 Mike Gabriel (9375394) * Documentation fix and typo fix relevant for win32 build. 2013-02-12 09:00:29 +0100 Mike Gabriel (a73faa4) * fix __doc__ strings 2013-02-12 08:55:54 +0100 Mike Gabriel (75dbd94) * Continue development... 2013-02-12 08:44:13 +0100 Mike Gabriel (720f39f) * release 0.4.0.0 (tag: 0.4.0.0) 2013-02-12 08:44:02 +0100 Mike Gabriel (1ddcf94) * add a note about the class name changes to the __doc__ string based documentation 2013-02-12 08:41:43 +0100 Mike Gabriel (810c1e9) * modify low_latency for already initialized control sessions 2013-02-12 06:33:27 +0100 Mike Gabriel (a93d4f2) * Add low latency support for link types 'modem' and 'isdn'. Selecting either link quality will double nearly all connection timeout values. (Fixes: #53). 2013-02-12 00:43:14 +0100 Mike Gabriel (79f1296) * add fixure for X2Go BTS issue #23 (2) 2013-02-12 00:35:52 +0100 Mike Gabriel (5e85771) * add fixure for X2Go BTS issue #23 2013-02-12 00:28:26 +0100 Mike Gabriel (4793f3a) * add fixure for X2Go BTS issue #18 2013-02-12 00:27:26 +0100 Mike Gabriel (86055d5) * skip undefined print actions that get returned from HOOK_open_print_dialog hook 2013-02-12 00:25:36 +0100 Mike Gabriel (e1f9afd) * whitespace fixes 2013-01-28 15:45:10 +0100 Mike Gabriel (2fcc357) * README update 2013-01-28 15:39:49 +0100 Mike Gabriel (eafc5bb) * happy new year 2013-01-21 10:34:32 +0100 Mike Gabriel (cb527b4) * Sort X2Go feature list, add force option for X2GoClient queries of server features and server components. Add alias get_server_components (for get_server_versions). 2013-01-21 05:32:13 +0100 Mike Gabriel (1ddcfcf) * Add session type filter for list of sharable desktops. 2013-01-20 12:14:34 +0100 Mike Gabriel (01ea13f) * Improve desktop sharing code. Add code to obtain version information of server-side X2Go components. 2013-01-17 11:24:29 +0100 Mike Gabriel (3133baf) * return success of writing a session profile configuration to disk 2013-01-17 11:17:08 +0100 Mike Gabriel (eb0cf27) * only prep fake_hostname if unique_hostkey_aliases is set to True 2013-01-14 02:55:49 +0100 Mike Gabriel (5415362) * make it possible to use the unique host key aliases code without SSH proxy, as well 2013-01-14 02:37:38 +0100 Mike Gabriel (4557a94) * fix __doc__ strings 2013-01-14 02:34:43 +0100 Mike Gabriel (c30e67e) * Avoid false-positive notifications of dead control session directly after a disconnect request from the user. 2013-01-14 01:41:12 +0100 Mike Gabriel (7c04590) * Fix auto-starting and auto-resuming of sessions. 2013-01-14 00:46:28 +0100 Mike Gabriel (9a9712d) * fix modifying existing profiles 2013-01-13 23:59:26 +0100 Mike Gabriel (42b3141) * Bump version to 0.4.0.0. WARNING: starting with version 0.4.0.0 of PyHoca-GUI, PyHoca-CLI and Python X2Go, all class identifiers are now X2Go..., not X2go... anymore. 2013-01-13 23:57:32 +0100 Mike Gabriel (2e09bca) * add compat code to let Python X2Go at least appear a little tolerant after the API change 2013-01-13 23:29:58 +0100 Mike Gabriel (4b05f61) * rename every occurence from X2go to X2Go, including class names 2013-01-13 23:27:34 +0100 Mike Gabriel (b014bae) * fix renaming of profile ID 2013-01-13 23:27:11 +0100 Mike Gabriel (1d86929) * fix unique host key aliases feature, do not use it for SSH proxy connections 2013-01-13 21:54:32 +0100 Mike Gabriel (7914c64) * Add session profile option: uniquehostkeyaliases. Allow the (by-design-unique) X2Go session profile ID to be a representative for :. Update session profile IDs on hostname changes. Re-arrange class structure for MissingHostKey policies, also provide an X2goAutoAddPolicy class. 2013-01-13 15:54:50 +0100 Mike Gabriel (e1b34e0) * bump version to 0.2.2.0 2013-01-13 15:54:09 +0100 Mike Gabriel (cd14d38) * make SSH proxy setting migration more smooth 2013-01-13 02:44:00 +0100 Mike Gabriel (4de963f) * fix __doc__ strings 2013-01-13 02:42:15 +0100 Mike Gabriel (b1c28c7) * Avoid the known_hosts file being flushed with localhost:[] entries. Store host keys of SSH-proxied hosts under the [
]: the system has _behind_ the SSH proxy gateway. 2013-01-13 01:02:43 +0100 Mike Gabriel (f787a06) * fix name of Python X2Go in epydoc build 2013-01-13 00:49:54 +0100 Mike Gabriel (a986b60) * fix faulty commit of partial new sshproxy code 2013-01-13 00:23:45 +0100 Mike Gabriel (60047c1) * ignore the session profile option display 2013-01-13 00:46:38 +0100 Mike Gabriel (2a9b274) * Revert "ignore the session profile option display" 2013-01-13 00:23:45 +0100 Mike Gabriel (c4a24c0) * ignore the session profile option display 2013-01-13 00:22:37 +0100 Mike Gabriel (61dc41b) * Revert "ignore the session profile option display" 2013-01-11 12:22:09 +0100 Mike Gabriel (2dad379) * ignore the session profile option display 2013-01-11 10:55:31 +0100 Mike Gabriel (3710451) * Catch any kind of exception when writing session profile files and return True or False in cases where I/O errors occur. 2013-01-11 09:56:42 +0100 Mike Gabriel (cee0cac) * fix __doc__ string 2013-01-11 09:56:29 +0100 Mike Gabriel (917cca2) * Add session profile option ,,display'' to default session profile options. 2013-01-04 12:14:23 +0100 Mike Gabriel (fe173b2) * fix typo in SSH port number 2012-12-28 07:16:04 +0100 Mike Gabriel (35f6b96) * fix link in __doc__ string 2012-12-20 09:19:45 +0100 Mike Gabriel (2a1d3db) * fix changelog entry 2012-12-20 09:01:51 +0100 Mike Gabriel (c6f5f43) * fix missing os module 2012-12-20 09:00:39 +0100 Orion Poplawski (6618320) * Importing all of pyhoca.wxgui in setup.py causes rpmbuild problems due to DISPLAY not being set. It is overkill as well, causing extra dependencies to be installed at build time. (Fixes: #91). 2012-12-18 12:37:29 +0100 Mike Gabriel (36689cb) * release 0.2.1.1 (tag: 0.2.1.1) 2012-12-17 21:32:27 +0100 Mike Gabriel (453d11e) * Make connection disruptures more robust. 2012-12-17 15:55:32 +0100 Mike Gabriel (6efedf0) * In cases where several session profiles connect to the same machine under the same user ID, we have to strictly differentiate between running/suspend sessions associated to the several connected session profiles. 2012-12-17 15:53:08 +0100 Mike Gabriel (a68e15b) * follow-up for 3622a7fd: turn AttributeError into X2goControlSessionException 2012-12-17 15:32:01 +0100 Mike Gabriel (3622a7f) * Handle control sessions being None in session list cache. 2012-12-16 21:22:58 +0100 Mike Gabriel (b82e7b9) * typo 2012-12-16 21:22:36 +0100 Mike Gabriel (7be3540) * Rewrite colour depth of 17 bits to 16bits when invoking rdesktop. Relevant for X2Go-proxied RDP sessions started with PyHoca-GUI under Windows. 2012-12-16 15:40:45 +0100 Mike Gabriel (971b6aa) * rewrite bug closures from Closes: -> Fixes: 2012-12-16 00:16:26 +0100 Mike Gabriel (a30ff31) * Make sure that internal calls to _X2goClient__list_sessions are not overridden by other method definitions in classes that inherit from X2goClient class. (Closes: #89). 2012-12-13 13:33:17 +0100 Mike Gabriel (b47b28f) * Continue development... 2012-12-10 13:02:24 +0100 Mike Gabriel (86a4bfd) * release 0.2.1.0 (tag: 0.2.1.0) 2012-11-29 23:37:20 +0100 Mike Gabriel (3bb8542) * fix indentations in default.py 2012-11-29 09:59:13 +0100 Mike Gabriel (a853ca8) * fix license in setup.py 2012-11-28 08:28:00 +0100 Mike Gabriel (901b388) * fix faulty boolean expression in auto-connect detection code 2012-11-26 14:17:35 +0100 Mike Gabriel (64dbd1b) * new connect option sshproxy_force_password_auth, improve detection code that tells if a session can auto-connect or not 2012-11-26 11:06:00 +0100 Mike Gabriel (8b51f44) * Fix password authentication in case no private RSA/DSA key for a client user exists. 2012-11-26 11:05:09 +0100 Mike Gabriel (54e4dbc) * cleanup X2goSession.connect() 2012-11-23 15:24:41 +0100 Mike Gabriel (384130b) * mark a session startup as PENDING asap 2012-11-23 11:14:58 +0100 Mike Gabriel (78a8ce9) * Before suspending/terminating a session, make sure that the list of shared folders is up-to-date. 2012-11-22 15:01:42 +0100 Mike Gabriel (1d46f7b) * fix add_profile in X2goSessionProfilesFILE, thanks to Dick Kniep for noticing this issue 2012-11-21 10:33:29 +0100 Mike Gabriel (f7bffc7) * more work on the folder mounting code 2012-11-20 22:21:52 +0100 Mike Gabriel (fc27736) * Consolidating management of shared and unshared client-side folders. 2012-11-20 15:42:41 +0100 Mike Gabriel (9818ac0) * Catch exceptions where a user tries to resume a session that has just been removed from the session list on the server (race condition). 2012-11-20 13:25:03 +0100 Mike Gabriel (2e69094) * improve share local folders code 2012-11-19 22:37:19 +0100 Mike Gabriel (8d835dc) * Implement functionality for restoring mounted shares on session resumption / re-start. Sponsored by Dick Kniep, LinDix NL. 2012-11-17 02:42:32 +0100 Mike Gabriel (19142c8) * fix skip_pubapp_sessions param 2012-11-17 02:37:08 +0100 Mike Gabriel (b4b82d6) * re-fix connect after pyflakes cleanup 2012-11-17 02:31:31 +0100 Mike Gabriel (dd46f32) * code cleanup using pyflakes 2012-11-17 01:43:49 +0100 Mike Gabriel (5035773) * whitespace fix 2012-11-17 01:40:42 +0100 Mike Gabriel (5ba7271) * Add option to disable auto-registration of pubapp sessions. 2012-11-17 01:09:51 +0100 Mike Gabriel (564bc7c) * use non-locking terminate method 2012-11-17 00:24:53 +0100 Mike Gabriel (14bdff2) * use non-locking _suspend method for transferring sessions from one client to another 2012-11-17 00:23:25 +0100 Mike Gabriel (2067913) * try a more intelligent approach for method locking 2012-11-17 00:04:07 +0100 Mike Gabriel (da000f3) * prevent blocking ourselves 2012-11-16 23:53:36 +0100 Mike Gabriel (bda44ea) * Implement some internal locking for X2goSession objects. 2012-11-16 21:09:51 +0100 Mike Gabriel (c008047) * fix for last commit 2012-11-16 20:54:16 +0100 Mike Gabriel (5b35eee) * Use threading.Lock to prohibit simultaneous calls of get_published_applications() of control sessions. 2012-11-15 22:38:59 +0100 Mike Gabriel (b37028b) * Allow usernames containing space characters in user names (common on MS Windows). 2012-11-14 21:19:10 +0100 Mike Gabriel (dcf2f7a) * Disable SSH agent forwarding on MS Windows platform for current versions of Python Paramiko. 2012-11-12 11:15:13 +0100 Mike Gabriel (6445a86) * fix allow_printing in session registration of X2goClient 2012-11-12 10:29:51 +0100 Mike Gabriel (5b5d71d) * Versioned depend on python-paramiko (>= 1.8.0-0~). 2012-11-12 10:29:28 +0100 Mike Gabriel (92939b0) * Revert "reduce version of python-paramiko" 2012-11-12 10:29:12 +0100 Mike Gabriel (c536288) * reduce version of python-paramiko 2012-11-12 09:03:00 +0100 Mike Gabriel (e5a1796) * add __doc__ strings for progress status methods in X2goSession class 2012-11-10 20:48:13 +0100 Mike Gabriel (539c961) * fix syntax error 2012-11-10 20:46:37 +0100 Mike Gabriel (84bca5b) * convert self.params.cmd to string when testing if command exists 2012-11-10 20:41:15 +0100 Mike Gabriel (b3a01ec) * convert the command to be run to string 2012-11-10 20:13:41 +0100 Mike Gabriel (b94f818) * Catch rare condition in utils.find_session_window in case where the list of window IDs is not available. 2012-11-10 20:09:47 +0100 Mike Gabriel (66b5505) * Give the session window more time to appear. 2012-11-10 17:15:25 +0100 Mike Gabriel (fdfc794) * Fall back to password auth if agent auth and key discovery fail. 2012-11-08 13:48:27 +0100 Mike Gabriel (f602855) * clean up code 2012-11-08 13:41:31 +0100 Mike Gabriel (8439aa2) * fix for last commit(2) 2012-11-08 13:28:24 +0100 Mike Gabriel (6365a4c) * fix for last commit 2012-11-08 10:04:41 +0100 Mike Gabriel (8eafb11) * Set the session name in case a session start failed due to lack of forwarding tunneling support in the server's SSH daemon. 2012-11-07 20:52:05 +0100 Mike Gabriel (9006f4a) * white space fix 2012-11-06 16:26:04 +0100 Mike Gabriel (e6d43d9) * consolidate progress bar support in Python X2Go 2012-11-05 23:03:03 +0100 Mike Gabriel (d89340e) * Add progress bar support for session startup / resuming. Closes upstream issue #14. 2012-11-05 21:04:07 +0100 Mike Gabriel (8ba5b25) * Allow mixing key file, key object, key discovery and agent authentication. 2012-11-05 12:53:14 +0100 Mike Gabriel (6c1c0d2) * also release profile locks during session registration if a session_uuid is re-used 2012-11-03 22:35:30 +0100 Mike Gabriel (861e37b) * typo fix 2012-11-03 21:57:48 +0100 Mike Gabriel (75cba9e) * Wait for mounting of print and mimebox spooling share. 2012-11-03 21:55:36 +0100 Mike Gabriel (f7b6768) * Introduce locks for session registrations. 2012-11-03 16:29:25 +0100 Mike Gabriel (e96a2aa) * improve __repr__ methods 2012-11-03 12:32:37 +0100 Mike Gabriel (3499e74) * fix: session_uid referenced before assignment 2012-11-03 12:30:19 +0100 Mike Gabriel (04ab4fe) * fix for last commit 2012-11-03 12:28:51 +0100 Mike Gabriel (c1c3fdc) * trying to activate a session asap 2012-11-03 12:25:36 +0100 Mike Gabriel (3b3c145) * fix wrong class property name 2012-11-03 11:45:25 +0100 Mike Gabriel (559b9c8) * Avoid false positive notifications about started-by-other sessions. 2012-10-21 16:23:11 +0200 Mike Gabriel (4d14558) * Rename session type XFCE4 to XFCE (using an unversioned name). 2012-10-12 15:19:39 +0200 Mike Gabriel (d4c2a7c) * Only monkey patch Python Paramiko based on the currently used Paramiko version (our monkey patches have been sent upstream, so we might not need the monkey patching for paramiko >= 1.8.0 anymore). 2012-10-12 15:18:19 +0200 Mike Gabriel (265398a) * close agent forwarding objects explicitly 2012-10-11 11:53:30 +0200 Mike Gabriel (c005bbc) * fix __doc__ string 2012-10-11 11:51:44 +0200 Mike Gabriel (6e31c25) * Enable autologin and sshproxyautologin for new session profiles. 2012-10-11 10:45:55 +0200 Mike Gabriel (b75628b) * Allow post-initialization updating of forward_sshagent class property. 2012-10-11 10:11:05 +0200 Mike Gabriel (b1b4c6b) * fix-up after some tests for look_for_keys and allow_agent feature 2012-10-11 10:08:38 +0200 Mike Gabriel (c8d8cf7) * Make X2goClient instance available in initial X2goSession instances. 2012-10-11 10:07:03 +0200 Mike Gabriel (5da7378) * /debian/pyversions: Drop file as it is deprecated. 2012-10-09 22:48:06 +0200 Mike Gabriel (b72afbf) * /debian/control: Versioned depend on python-paramiko (>= 1.8-0~). 2012-10-09 15:18:24 +0200 Mike Gabriel (092b64a) * add _paramiko.py (renamed) file 2012-10-09 15:17:46 +0200 Mike Gabriel (c94349b) * fix last two commits 2012-10-09 14:01:43 +0200 Mike Gabriel (4f91518) * Implement X2Go session profile features ,,autologin'' and ,,sshproxyautologin'' (meaning: look_for_keys and allow_agent in Python Paramiko terms). 2012-10-09 13:16:37 +0200 Mike Gabriel (2c2e237) * bump version towards 0.2.1.0 2012-10-09 13:15:54 +0200 Mike Gabriel (cac70a5) * Implement SSH agent authentication forwarding. 2012-10-08 09:33:43 +0200 Mike Gabriel (54ba077) * Allow smooth session profile migration, prefer : (set via sshproxy_host) for SSH proxy port detection. 2012-10-03 19:46:43 +0200 Mike Gabriel (b898e68) * Mention ,,maximize'' as possible value for session option geometry in __doc__string of class X2goTerminalSessionSTDOUT. 2012-10-02 17:54:11 +0200 Mike Gabriel (5a2665a) * fix __doc__ string indentation(2) 2012-10-02 17:51:39 +0200 Mike Gabriel (64324ba) * fix __doc__ string indentation 2012-10-02 17:49:07 +0200 Mike Gabriel (c226acf) * set fallback geometry if maximize is not available, fix for if clause 2012-10-02 17:41:55 +0200 Mike Gabriel (cf9e6e3) * follow-up fix for last commit 2012-10-02 17:24:07 +0200 Mike Gabriel (a737395) * For ,,maxdim'' session property fallback to _NET_DESKTOP_GEOMETRY if _NET_WORKAREA is not available from the window manager. 2012-09-29 20:51:53 +0200 Mike Gabriel (d71817b) * Fix category ,,Utility'' when parsing .desktop files. 2012-09-28 23:32:46 +0200 Mike Gabriel (cb51eaa) * move session window maximization into terminal backend 2012-09-28 17:35:59 +0200 Mike Gabriel (1b4e804) * add closure of issue #12 to changelog 2012-09-28 17:34:40 +0200 Mike Gabriel (b5f0772) * Add support for starting maximized session windows. 2012-09-28 15:31:07 +0200 Mike Gabriel (53ef903) * Revert "Add x2goclient sessions parameter ,,usebrokerpass''." 2012-09-28 15:22:49 +0200 Mike Gabriel (0a195f2) * Add x2goclient sessions parameter ,,usebrokerpass''. 2012-09-28 10:20:43 +0200 Mike Gabriel (ef0cd78) * Fixing typos in __doc__ strings. 2012-09-28 10:03:10 +0200 Mike Gabriel (f7d6e95) * Implementation of session profile parameters ,,sshproxysameuser'' and ,,sshproxysameauth''. 2012-09-27 16:40:07 +0200 Mike Gabriel (feac4d0) * /debian/rules: Allow package build on systems with missing dh_python2. 2012-09-27 10:18:32 +0200 Mike Gabriel (b08887f) * Add sshproxy_port option to session (SSH proxy) options. 2012-09-26 21:06:13 +0200 Mike Gabriel (80bec3b) * changelog fix 2012-09-26 21:04:39 +0200 Mike Gabriel (302db0f) * increment egg version 2012-09-26 21:03:20 +0200 Mike Gabriel (f0c1eb5) * typo fix 2012-09-26 20:50:28 +0200 Mike Gabriel (a180527) * Prepare for staying compatible with new SSH proxy feature in X2Go Client. 2012-09-25 20:15:01 +0200 Mike Gabriel (38373f3) * package revision 0~x2go2 2012-09-25 20:13:57 +0200 Mike Gabriel (bea7041) * /debian/control: Allow build on Ubuntu 10.04 (reduce Python version in Build-Depends). 2012-09-25 16:09:35 +0200 Mike Gabriel (0773ad2) * release 0.2.0.10 (tag: 0.2.0.10) 2012-09-21 20:40:00 +0200 Mike Gabriel (3283c83) * Handle 16bit and 17bit colour depth (Windows clients) as equal when resuming sessions. 2012-09-17 13:59:09 +0200 Mike Gabriel (2ab6c71) * Revert "NX proxy backend: only accept connections from 127.0.0.1." 2012-09-17 13:40:58 +0200 Mike Gabriel (5a0dd6c) * NX proxy backend: only accept connections from 127.0.0.1. 2012-09-15 21:28:38 +0200 Mike Gabriel (2442e74) * /debian/control: fix typo in longdescs 2012-09-15 20:08:22 +0200 Mike Gabriel (b9fffc0) * Maintainer change in package: X2Go Developers . 2012-09-14 17:42:44 +0200 Mike Gabriel (06e08a4) * Remove bashism from X2Go server command calls. Caused servers with no installed x2goserver-xsession package to crash desktop sessions (observed with KDE4 on Ubuntu 12.04). 2012-09-14 09:14:04 +0200 Mike Gabriel (aa9d494) * Ignore DirectRDP options in session profile config for now. 2012-09-14 09:11:38 +0200 Mike Gabriel (f60c4d3) * Make Python X2Go aware of DirectRDP settings in session profiles. 2012-09-14 09:09:19 +0200 Mike Gabriel (8c8e216) * Indentation fix in defaults.py. 2012-09-11 21:28:53 +0200 Mike Gabriel (bcd2472) * Use session type 'D' for X2Go-proxied RDP session when run in fullscreen mode. Fixes upstream issue #27. 2012-08-17 16:29:23 +0200 Mike Gabriel (60d5a88) * Continue development... 2012-08-17 18:13:43 +0200 Mike Gabriel (187ce28) * package revision 0~x2go2 2012-08-17 18:13:18 +0200 Mike Gabriel (8429902) * Make sure we can build against python-gevent provided on packages.x2go.org. 2012-08-17 18:12:22 +0200 Mike Gabriel (05040a9) * Revert "Continue development..." 2012-08-17 16:29:23 +0200 Mike Gabriel (70df0a5) * Continue development... 2012-08-17 16:17:36 +0200 Mike Gabriel (0d756ed) * release 0.2.0.9 (tag: 0.2.0.9) 2012-08-17 15:31:16 +0200 Mike Gabriel (2b318ad) * no extra upstream changelog, we are upstream...(2) 2012-08-17 14:27:04 +0200 Mike Gabriel (6fe6730) * no extra upstream changelog, we are upstream... 2012-08-17 14:14:37 +0200 Mike Gabriel (3c19716) * do not bump python2.6 dependency to allow build against Debian squeeze 2012-08-17 14:13:08 +0200 Mike Gabriel (7afa80a) * add missing new files (forgotten in last commit) 2012-08-17 14:12:36 +0200 Mike Gabriel (1abe05c) * Import packaging file from Debian package. 2012-07-25 22:23:46 +0200 Mike Gabriel (588ba61) * Become fully functional for users that have SHELLs than sh or bash on remote X2Go server. 2012-07-23 21:20:56 +0200 Mike Gabriel (288950f) * Continue development... 2012-07-23 21:16:39 +0200 Mike Gabriel (e5ddf49) * release 0.2.0.8 (tag: 0.2.0.8) 2012-07-23 21:14:11 +0200 Mike Gabriel (b097b88) * Catch IOError exceptions during SFTP client operations. Proper use of except statement for multiple exception catching. 2012-07-12 21:28:50 +0200 Mike Gabriel (3b530bf) * Continue development... 2012-07-12 21:27:38 +0200 Mike Gabriel (1b376af) * release 0.2.0.7 (tag: 0.2.0.7) 2012-07-12 21:21:06 +0200 Mike Gabriel (8e5e79a) * Make sure SSH proxy sessions get torn down on control session disconnect no matter what happens to the control session itself. 2012-07-04 01:02:06 +0200 Mike Gabriel (bc957c4) * Fix property method X2goControlSession._x2go_remote_home on broken connections. 2012-07-03 09:41:10 +0200 Mike Gabriel (4d9aad3) * Refresh server feature list on re-connecting (log-off, log-on). 2012-07-02 21:19:32 +0200 Mike Gabriel (1a965b5) * typo fix in changelog 2012-07-02 20:57:55 +0200 Mike Gabriel (3c94299) * Continue development... 2012-07-02 20:54:48 +0200 Mike Gabriel (7fc00a2) * release 0.2.0.6 (tag: 0.2.0.6) 2012-07-02 20:54:20 +0200 Mike Gabriel (31a4b88) * increment egg version 2012-07-02 20:35:52 +0200 Mike Gabriel (a5b0cbb) * X2goSession.HOOK_printing_not_available(): Fix log message. 2012-07-02 20:34:38 +0200 Mike Gabriel (e42f321) * Avoid double notifications on SSHFS being unavailable for the authenticated user. 2012-06-30 13:51:58 +0200 Mike Gabriel (69b60b4) * typo 2012-06-30 13:43:29 +0200 Mike Gabriel (8acf5a8) * Add several double underscore method aliases in X2goClient class. 2012-06-30 13:38:28 +0200 Mike Gabriel (ca6ee3d) * Add several double underscore method aliases in X2goSession class. 2012-06-30 13:28:53 +0200 Mike Gabriel (74e94b3) * Ignore non-master sessions before calling the foldersharing-not-available hook. 2012-06-12 15:52:03 +0200 Mike Gabriel (1cfff12) * Continue development... 2012-06-12 15:50:32 +0200 Mike Gabriel (3b1c605) * release 0.2.0.5 (tag: 0.2.0.5) 2012-06-12 15:50:08 +0200 Mike Gabriel (6bd9816) * Fix for building Python X2Go in pbuilder environment. Catch Xlib.error.DisplayConnectionError and ignore it. Now the real fix!!! 2012-06-12 10:11:12 +0200 Mike Gabriel (4113c80) * Continue development... 2012-06-12 10:09:35 +0200 Mike Gabriel (b7d446e) * release 0.2.0.4 (tag: 0.2.0.4) 2012-06-12 10:57:59 +0200 Mike Gabriel (70ed6b9) * typo fix 2012-06-12 10:57:43 +0200 Mike Gabriel (1e0507a) * Revert "release 0.2.0.4" 2012-06-12 10:57:38 +0200 Mike Gabriel (5f24287) * Revert "Continue development..." 2012-06-12 10:11:12 +0200 Mike Gabriel (18821af) * Continue development... 2012-06-12 10:09:35 +0200 Mike Gabriel (0f58032) * release 0.2.0.4 2012-06-12 10:09:24 +0200 Mike Gabriel (b529607) * Fix for building Python X2Go in pbuilder environment. Catch Xlib.error.DisplayConnectionError and ignore it. 2012-06-10 21:56:27 +0200 Mike Gabriel (a4374c6) * Continue development... 2012-06-10 21:22:57 +0200 Mike Gabriel (ac55fa9) * release 0.2.0.3 (tag: 0.2.0.3) 2012-06-10 09:04:18 +0200 Mike Gabriel (c821324) * whitespace fix 2012-06-10 05:22:45 +0200 Mike Gabriel (593db5e) * Only notify HOOK_auto_connect, if the session really is configured to auto-connect (while at the same time no SSH key is present). 2012-06-08 09:42:53 +0200 Mike Gabriel (d615efb) * fix __doc__ string of X2goSession.HOOK_on_control_session_death() 2012-06-08 09:41:28 +0200 Mike Gabriel (a3ab4c5) * Continue development... 2012-06-08 09:21:05 +0200 Mike Gabriel (1e28d70) * release 0.2.0.2 (tag: 0.2.0.2) 2012-06-08 00:26:34 +0200 Mike Gabriel (ccec42e) * The master_sessions dict may never have None values. 2012-06-07 21:27:38 +0200 Mike Gabriel (9070692) * Do not create a high CPU load after a network failure, do not try to execute a remote command if the session has already died away. 2012-06-07 20:53:51 +0200 Mike Gabriel (cf525d5) * Catch control session deaths when querying X2goSession.is_alive(). 2012-06-07 19:18:18 +0200 Mike Gabriel (a58f12c) * revert a tiny bit of commit 0983bbcce7c7ff1cfd98b9dd96045607fc2a8a1d, that introduced profile disconnections after sessions had been suspended / terminated 2012-06-07 09:02:54 +0200 Mike Gabriel (ab5473d) * Mark sessions as dead whenever an X2goControlSessionException occurs. 2012-06-07 09:02:11 +0200 Mike Gabriel (699da4c) * Add support to X2goSession class to launch sessions for the Python command line in five steps. 2012-06-06 22:31:47 +0200 Mike Gabriel (6184896) * Ignoring timeouts for x2golistmounts and x2golistdesktops. 2012-06-06 22:30:10 +0200 Mike Gabriel (0983bbc) * Improve session management, handle exceptions more gracefully. 2012-06-01 02:07:27 +0200 Mike Gabriel (44542ee) * Be tolerant if we can not terminate a session after failure of the forwarding tunnel. 2012-05-30 15:16:27 +0200 Mike Gabriel (683be10) * version increment 2012-05-30 00:27:16 +0200 Mike Gabriel (3b29079) * Continue development... 2012-05-30 00:26:18 +0200 Mike Gabriel (b53cbef) * release 0.2.0.1 (tag: 0.2.0.1) 2012-05-30 00:25:49 +0200 Mike Gabriel (0974e80) * Re-add lost line in control session's connect method that let SSH host key checks fail. 2012-05-29 21:09:35 +0200 Mike Gabriel (ed3dd9b) * Continue development... 2012-05-29 17:38:39 +0200 Mike Gabriel (9ab80b6) * release 0.2.0.0 (tag: 0.2.0.0) 2012-05-29 17:09:05 +0200 Mike Gabriel (7e7017e) * last pre-release checks with pyflakes 2012-05-29 16:51:10 +0200 Mike Gabriel (b3923e7) * set activated property to False on session cleanup 2012-05-29 16:32:10 +0200 Mike Gabriel (61a2a2e) * cosmetic fixes 2012-05-29 16:31:45 +0200 Mike Gabriel (37890ac) * HOOK_forward_tunnel_setup_failed fixed 2012-05-29 16:30:29 +0200 Mike Gabriel (9e147d7) * Fix example file x2go_resume_session.py 2012-05-29 16:00:17 +0200 Mike Gabriel (086c072) * reintroduce compat X2goClient parameter ,,use_cache'' as alias for use_listsessions_cache 2012-05-29 15:59:39 +0200 Mike Gabriel (e62710c) * bump upstream version towards 0.2.0.0 2012-05-29 15:59:00 +0200 Mike Gabriel (64280dc) * update README/TODO file 2012-05-29 15:30:45 +0200 Mike Gabriel (991f0a1) * work on the terminal session __doc__ strings 2012-05-29 13:50:47 +0200 Mike Gabriel (7fdd38c) * doc string work on the control session backend 2012-05-29 11:39:24 +0200 Mike Gabriel (e891bfa) * only reported virgin sessions to have been started by someone else 2012-05-29 03:13:38 +0200 Mike Gabriel (0042fba) * more work on __doc__ strings (backend classes) 2012-05-29 02:09:59 +0200 Mike Gabriel (d4ec52b) * fix __doc__ strings 2012-05-29 02:09:44 +0200 Mike Gabriel (1a37bcd) * re-add a lost line (HOOK_on_control_session_death) 2012-05-29 01:54:10 +0200 Mike Gabriel (034f7d6) * reworked all non-backend __doc__ strings 2012-05-28 16:55:11 +0200 Mike Gabriel (469afde) * Use proper locking of session actions that are critical to being executed in parallel. 2012-05-28 16:31:33 +0200 Mike Gabriel (144f7a4) * __doc__ string fixes for terminal backend 2012-05-28 16:28:37 +0200 Mike Gabriel (3799cc5) * Give functionality to the ,,setdpi'' and the ,,dpi'' session profile parameter (setting the DPI allows font scaling). 2012-05-28 16:08:27 +0200 Mike Gabriel (0294d5b) * Add new session profile parameter: ,,variant''. Add support to set the keyboard layout _and_ the keyboard variant from the client-side. 2012-05-28 16:06:23 +0200 Mike Gabriel (b4a9e5f) * drop experimental keyboard setting code 2012-05-28 16:05:04 +0200 Mike Gabriel (86aa11e) * Add support for re-registering sessions after session profile changes. 2012-05-27 15:44:18 +0200 Mike Gabriel (476c6f3) * wait 2 seconds before invoking share_all_local_folders script 2012-05-27 15:43:54 +0200 Mike Gabriel (9dc305a) * recognize session parameter type during import 2012-05-26 22:16:08 +0200 Mike Gabriel (05f9803) * use gevent.spawn for wait=0 and gevent.spawn_later for wait>0 2012-05-26 21:08:33 +0200 Mike Gabriel (0e87534) * typo fix 2012-05-26 19:56:23 +0200 Mike Gabriel (f3de332) * Fix local folder sharing when the master session changes during runtime. 2012-05-26 11:35:06 +0200 Mike Gabriel (63ac948) * Bundle commit, fixing several issues: 2012-05-24 22:47:14 +0200 Mike Gabriel (97c0d9d) * do not import unused module 2012-05-24 22:33:17 +0200 Mike Gabriel (5820e62) * Be more tolerant against suspension failures while taking over a session. 2012-05-24 22:27:42 +0200 Mike Gabriel (1908ff8) * X2goSession instances cannot raise X2goClientExceptions. 2012-05-23 11:00:36 +0200 Mike Gabriel (5165769) * fix detection of rootless sessions 2012-05-22 00:08:27 +0200 Mike Gabriel (274f40e) * Allow custom commands to be desktop sessions. 2012-05-15 09:40:38 +0200 Mike Gabriel (406d9c0) * Fix control session failure notifications. Show them immediately after the connection broke. 2012-05-15 09:18:08 +0200 Mike Gabriel (91247a4) * missing colons (typos) 2012-05-15 09:16:52 +0200 Mike Gabriel (a27e513) * minor change in listsessions cache 2012-05-08 10:00:09 +0200 Mike Gabriel (18c58ed) * Catch exceptions while calling SSH transport's getpeername() method. 2012-05-06 23:32:22 +0200 Mike Gabriel (3e00713) * Ignore X windows with empty title while finding session window.. 2012-04-29 16:23:05 +0200 Mike Gabriel (203f5fe) * Adapt python-x2go to launching Unity-2d on Ubuntu precise. 2012-04-29 15:13:43 +0200 Mike Gabriel (fccfb5c) * handle import and usage of defautls.X2GO_DESKTOPSESSIONS differently 2012-04-24 21:32:02 +0200 Mike Gabriel (301f531) * Protect session cache from deletion while being processed. 2012-04-24 16:40:07 +0200 Mike Gabriel (fe53e1d) * fix undefined protected property in X2Go session info 2012-04-24 16:13:47 +0200 Mike Gabriel (02b0cc3) * cleanup commit for last commit 2012-04-24 16:12:23 +0200 Mike Gabriel (e03f3f4) * Not using gevent to spawn MIME box default applications. 2012-04-24 09:36:52 +0200 Mike Gabriel (7397c54) * Make sure path names in X2goPrintActions and X2goMIMEboxActions get transformed to OS-specific path names. 2012-04-20 20:45:38 +0200 Mike Gabriel (831b545) * fix __doc__ string indentation 2012-04-20 17:07:55 +0200 Mike Gabriel (dc31536) * fix missing kwarg timeout in X2goSession.exec_published_applications() 2012-04-19 23:22:01 +0200 Mike Gabriel (5ac79cf) * Fix SSHException usage. 2012-04-19 23:08:45 +0200 Mike Gabriel (5468a31) * fix typo 2012-04-19 22:56:39 +0200 Mike Gabriel (b772385) * Make timeout on command execution customizable. 2012-04-19 22:35:48 +0200 Mike Gabriel (10d5af9) * Try to derive language information from X2goClient instance. 2012-04-19 22:27:47 +0200 Mike Gabriel (f232adf) * Do not call HOOK method if self.allow_share_local_folders is False. 2012-04-18 18:33:22 +0200 Mike Gabriel (f1f0a7c) * fix for last commit 2012-04-18 18:26:50 +0200 Mike Gabriel (a936ea3) * typo fix 2012-04-18 18:25:37 +0200 Mike Gabriel (c23d9f4) * Provide X2goSession.get_session_type() method. 2012-04-18 17:25:13 +0200 Mike Gabriel (ef4ae50) * finally fix last 2-3 commits 2012-04-18 16:22:19 +0200 Mike Gabriel (ac5b31b) * On unused port detection bind to 127.0.0.1 by default. 2012-04-18 16:14:37 +0200 Mike Gabriel (5fd75e3) * fix syntax and attribute errors 2012-04-18 15:24:45 +0200 Mike Gabriel (392629e) * Handle detection of free TCP/IP X display port far more intelligently. 2012-04-18 10:36:47 +0200 Mike Gabriel (98ba5f2) * os.environ elements can only be strings 2012-04-18 10:30:53 +0200 Mike Gabriel (38a2aa4) * Re-use a left behind stray X-server that might have not get killed by a previous instance of Python X2Go Client. This trick is nasty, but works around faulty abortion of client implementations. 2012-04-18 10:09:23 +0200 Mike Gabriel (393c129) * Make transitions of master sessions more robust. Only allow local folder sharing for running sessions. 2012-04-18 10:08:46 +0200 Mike Gabriel (4982c13) * If configured X-server display port is already in use, try to detect the next available display number. 2012-04-17 00:29:53 +0200 Mike Gabriel (29315a2) * Use VcXsrv-1.12.0.1 when testing Python X2Go applications. 2012-04-17 00:00:24 +0200 Mike Gabriel (2b79c7e) * Use nxproxy-3.5.0.12 when testing Python X2Go applications. 2012-04-16 23:48:24 +0200 Mike Gabriel (e80e656) * no GenericName field evaluation 2012-04-16 17:34:22 +0200 Mike Gabriel (c92a57c) * Silence warnings that occur during session info queries in case a session startup has not yet been completed fully. 2012-04-16 17:26:14 +0200 Mike Gabriel (aef55f5) * use makedirs instead of mkdir 2012-04-16 17:24:25 +0200 Mike Gabriel (af2749d) * make sure the LOCAL_HOME path is normalized 2012-04-16 17:22:44 +0200 Mike Gabriel (617f7b4) * typo fix 2 2012-04-16 17:22:12 +0200 Mike Gabriel (ab26a87) * typo fix 2012-04-16 17:21:30 +0200 Mike Gabriel (fda139b) * Make sure that pulseaudio.exe has its PID directory. Otherwise it will fail to start the first time if the user is new to X2Go. 2012-04-16 15:29:32 +0200 Mike Gabriel (6d336a7) * Support published applications that have to be run in a terminal window. 2012-04-16 10:24:35 +0200 Mike Gabriel (28dfe66) * Fill session profile configurations with missing default values and then detect the profile meta type. 2012-04-14 22:11:23 +0200 Mike Gabriel (90aad3a) * Provide X2goClient.get_published_applications() method. 2012-04-14 15:41:22 +0200 Mike Gabriel (978b280) * Add support for Windows for bringing X2Go session windows to foreground. 2012-04-14 15:22:36 +0200 Mike Gabriel (dd0a73b) * fix for last commit 2012-04-14 14:46:03 +0200 Mike Gabriel (40d727e) * Add support for renaming X2Go session windows on Windows. 2012-04-14 00:58:57 +0200 Mike Gabriel (1bd0f3c) * Transform blanks in mount points into underscores. 2012-04-14 00:57:35 +0200 Mike Gabriel (c2ffbe6) * Fix automatic mounting of Windows-stylish shared local folders. 2012-04-13 23:35:52 +0200 Mike Gabriel (f8455f9) * fix for last commit 2012-04-13 22:30:41 +0200 Mike Gabriel (f863f72) * add some capturing code for bad implementations of published applications menu tree queries in client applications 2012-04-13 13:20:01 +0200 Mike Gabriel (bcf4404) * set pulseaudio exit-idle-time to -1 2012-04-12 22:02:45 +0200 Mike Gabriel (cd23929) * No list_sessions() calls on server when querying the status of an X2goSession instance. 2012-04-12 20:39:12 +0200 Mike Gabriel (b75923d) * try to sedate a gevent IOError (no error) 2012-04-12 17:07:43 +0200 Mike Gabriel (2e7e522) * make sure backslashed Windows paths arrive properly in x2goumount-session script 2012-04-12 15:31:28 +0200 Mike Gabriel (cd63f79) * fix detection of shared folders on Windows 2012-04-12 15:08:29 +0200 Mike Gabriel (1724680) * typo fix 2012-04-12 15:06:47 +0200 Mike Gabriel (ac9bcec) * Fix X2goSession.get_shared_folders() method on Windows. 2012-04-12 14:26:40 +0200 Mike Gabriel (295486c) * fix for last commit 2012-04-12 14:18:45 +0200 Mike Gabriel (556b1fc) * When starting pulseaudio on Windows use --exit-idle-time=0. 2012-04-12 13:56:39 +0200 Mike Gabriel (2b4f8d4) * Export X2GO_SESSION and PULSE_CLIENTCONFIG to published applications. 2012-04-12 12:57:04 +0200 Mike Gabriel (5b425f7) * Use double-quotes for pulseaudio options. 2012-04-12 12:13:44 +0200 Mike Gabriel (572d145) * If the SSH proxy connection tries to bind to a used port, detect an unused local port and write this port change to the session profile. 2012-04-12 11:25:19 +0200 Mike Gabriel (98dfd35) * lintian issue fixed: extra-license-file 2012-04-12 01:44:51 +0200 Mike Gabriel (88f4b2f) * Implement X2Go-Top category in .desktop files that get used in the context of published applications. 2012-04-12 01:06:53 +0200 Mike Gabriel (74cdf07) * fix license / copyright passages 2012-04-11 10:08:22 +0200 Mike Gabriel (68d7a9d) * happy new year 2012-04-11 02:44:13 +0200 Mike Gabriel (7409baa) * Fix metatype detection of session profiles. 2012-04-11 01:17:39 +0200 Mike Gabriel (e47f538) * whitespace/tab fix 2012-04-10 23:35:36 +0200 Mike Gabriel (e661c8d) * Fix unexpected keyword error during connect() in X2goSession instance. 2012-04-03 14:06:40 +0200 Mike Gabriel (13dadf1) * Provide X2goSession method get_session_profile_option(). 2012-04-02 17:30:15 +0200 Mike Gabriel (893b629) * fix lintian issues (debian-changelog-line-too-long and out-of-date-standards-version) 2012-04-01 21:17:53 +0200 Mike Gabriel (9787315) * setup.py: fix typos, license info 2012-03-27 02:34:41 +0200 Mike Gabriel (215aa30) * stabilizing code 2012-03-26 17:47:18 +0200 Mike Gabriel (521d91d) * Provide X2goClient method get_session_info(), do not auto start/resume sessions in published applications mode, provide hook method for auto-connecting interactively. 2012-03-26 17:50:17 +0200 Mike Gabriel (41e9e07) * Docstring fix, add X2goClient method is_session_profile(), return registered session for a specific session name if it has already been registered instead of registering a new session. 2012-03-23 10:11:06 +0100 Mike Gabriel (5337f67) * Make sure xconfig configuration changes provided by defaults.py get written to the config file. 2012-03-23 09:51:19 +0100 Mike Gabriel (83a1d29) * Provide function merge_ordered_lists in utils.py, merge list of default known_xservers with configured known_xservers. 2012-03-23 09:39:33 +0100 Mike Gabriel (7729459) * Provide function merge_sorted_lists in utils.py. 2012-03-23 09:01:08 +0100 Mike Gabriel (1fb1392) * Make new ini config defaults available in configurations, update list of known X-Servers if new ones are provided in defaults.py 2012-03-23 00:29:38 +0100 Mike Gabriel (f7f2e07) * more path normalizing 2012-03-23 00:24:09 +0100 Mike Gabriel (66ad55f) * fix call of os.path.normpath 2012-03-23 00:21:36 +0100 Mike Gabriel (81d6688) * Normalize paths to configuration files. 2012-03-22 23:54:30 +0100 Mike Gabriel (fdfd1f3) * handle thread cleanup and Xserver process on Windows 2012-03-21 23:45:40 +0100 Mike Gabriel (451748c) * slight change in logging X-Server activities 2012-03-21 21:51:46 +0100 Mike Gabriel (dc15312) * add missing VcXsrv_development to known_xservers 2012-03-21 21:51:14 +0100 Mike Gabriel (aafd328) * add newline at EOF 2012-03-22 15:37:49 +0100 Mike Gabriel (29e4a2a) * For the Windows version, make VcXsrv-Server at the development location known to Python X2Go. 2012-03-22 15:27:48 +0100 Mike Gabriel (ee38308) * typo fix 2012-03-22 15:26:51 +0100 Mike Gabriel (07cdbad) * For the Windows version, make shipped VcXsrv-Server known to Python X2Go. The shipped VcXsrv has to rest in $CLIENTAPPDIR/VcXsrv/vcxsrv.exe. 2012-03-21 22:51:01 +0100 Mike Gabriel (7067c12) * Fix auto_start_or_resume method when using SSH proxy with interactive authentication. Provide default value for PUBAPP_MAX_NO_SUBMENUS in defaults.py. 2012-03-21 15:20:02 +0100 Mike Gabriel (c7e4817) * Add X2goClient method is_profile_connected. 2012-03-21 14:17:32 +0100 Mike Gabriel (73ef001) * Include development location of nxproxy in possible file locations. 2012-03-21 14:15:44 +0100 Mike Gabriel (a40721c) * changelog cleanup 2012-03-21 09:23:10 +0100 Mike Gabriel (df5827e) * typo fix in log output 2012-03-20 15:37:05 +0100 Mike Gabriel (6a73ef5) * Support auto-resuming and auto-starting of session with Python X2Go. 2012-03-18 21:56:55 +0100 Mike Gabriel (cb74f7e) * Make published_applications_no_submenus an session option that controls how many menu items will be shown without rendering category based submenus. 2012-03-18 21:34:49 +0100 Mike Gabriel (1f56909) * Fix X2Go printing, do not spawn a gevent process for printing. 2012-03-17 19:15:36 +0100 Mike Gabriel (ea7ea65) * Add support for published applications with no category submenus. Fix default language in published applications menu tree. 2012-03-16 23:18:10 +0100 Mike Gabriel (5c9619b) * Fix availablity check of client-side folder sharing. 2012-03-16 18:00:15 +0100 Mike Gabriel (e6596c5) * Render and cache dictionary based published applications menu tree in Python X2Go. Cache the tree once rendered. 2012-03-14 17:08:45 +0100 Mike Gabriel (83b74e3) * remove debug code 2012-03-14 17:03:31 +0100 Mike Gabriel (b190513) * bundle commit, stabilize code 2012-03-13 16:09:43 +0100 Mike Gabriel (93a1d5c) * fix for last commit 2012-03-13 14:31:24 +0100 Mike Gabriel (a34ffe9) * Handle empty control session in the session list cache. 2012-03-12 17:14:00 +0100 Mike Gabriel (2592608) * silence log output 2012-03-12 16:22:03 +0100 Mike Gabriel (58289bf) * Fix master session recognition. 2012-03-12 13:11:41 +0100 Mike Gabriel (73f108b) * Fix base64 encoded icon string. 2012-03-10 15:31:30 +0100 Mike Gabriel (0eff893) * Add published applications support. 2012-03-10 15:03:58 +0100 Mike Gabriel (f2e8361) * fix x2gofeaturelist query 2012-03-10 13:41:27 +0100 Mike Gabriel (3dc28f3) * silence which command 2012-03-10 13:38:24 +0100 Mike Gabriel (2a96e1e) * Retrieve feature list from X2Go server per session. 2012-03-10 12:47:13 +0100 Mike Gabriel (8d90554) * Update list of unsupported session options. 2012-03-10 12:29:31 +0100 Mike Gabriel (ec47047) * Amend list of default session options. 2012-03-08 00:37:56 +0100 Mike Gabriel (b9421ed) * fix setting of session window title on Unix platforms (after changing stuff on Windows) 2012-03-06 12:36:00 +0100 Mike Gabriel (a181afe) * __doc__ string fixes 2012-03-06 12:30:30 +0100 Mike Gabriel (b4189b0) * fix faking of WindowsError 2012-03-04 21:58:47 +0100 Mike Gabriel (8cfeead) * complete background printing feature 2012-03-04 21:57:49 +0100 Mike Gabriel (f6eae3f) * Run MIME box actions in background (gevent.spawn). 2012-03-04 21:20:21 +0100 Mike Gabriel (ed6cc9d) * Run print actions in background (gevent.spawn). 2012-03-04 20:15:20 +0100 Mike Gabriel (f374c3b) * Draw all Xlib code into utils.py 2012-03-04 20:11:00 +0100 Mike Gabriel (57027a9) * Fix faking of WindowsError exception object in printactions.py and mimeboxactions.py. 2012-03-04 20:06:12 +0100 Mike Gabriel (ca2f9e2) * Make x2go module importable on Win32 platforms again. 2012-02-25 13:21:29 +0100 Mike Gabriel (7eee17c) * Rename control session method is_folder_sharing_available to is_sshfs_available. 2012-02-23 23:55:21 +0100 Mike Gabriel (4f182b4) * fine-tune new hook methods 2012-02-23 23:32:50 +0100 Mike Gabriel (a8385b0) * Code cleanup: remove all unnecessary imports. Stop defining variables that get never used. 2012-02-23 23:03:51 +0100 Mike Gabriel (30887ea) * fix undefined names in terminal backend 2012-02-23 23:03:22 +0100 Mike Gabriel (84683b8) * Provide hook methods for SSHFS failures (local folder sharing, printing, MIME box). 2012-02-22 00:31:56 +0100 Mike Gabriel (5aa1c6f) * Tolerate names containing "-" characters. 2012-02-18 21:28:02 +0100 Mike Gabriel (e41db1b) * fix for commit b3bf6d63c5914984585e64d68630e0ac9bced9ec 2012-02-18 01:59:18 +0100 Mike Gabriel (a2ea32c) * New feature, allow/fix sessions on localhost system. 2012-02-18 01:21:25 +0100 Mike Gabriel (b3bf6d6) * Fix X2Go desktop sharing support. 2012-02-16 14:06:35 +0100 Mike Gabriel (5c92407) * Handle session titles that just contain blanks (e.g. " ") gracefully. 2012-02-11 23:19:00 +0100 Mike Gabriel (b1b59d1) * Fix IndexError if x2gomountdirs did not deliver any of the expected results to stdout. 2012-02-10 12:12:23 +0100 Mike Gabriel (b0cad03) * fix __doc__ strings 2012-02-10 12:04:39 +0100 Mike Gabriel (254a627) * remove debug code 2012-02-10 00:22:36 +0100 Mike Gabriel (2f1c22d) * Introduce concept of master sessions per profile to X2goClient class. Only the master session can mount/unmount client-side shared folders. 2012-02-08 17:16:01 +0100 Mike Gabriel (2093439) * Provide client-side cache of shared local folders, detect server-side unsharing of client-side folders. 2012-02-04 12:34:19 +0100 Mike Gabriel (37cbc1f) * Add support for session port re-allocation on session resume (feature of x2goserver >= 3.1.0.0). 2012-01-31 23:44:28 +0100 Mike Gabriel (986103a) * more X2go -> X2Go 2012-01-31 23:31:54 +0100 Mike Gabriel (504ab9a) * more X2go -> X2Go replacements 2012-01-31 23:29:16 +0100 Mike Gabriel (da34d5d) * Replace any non-code string ,,X2go'' by ,,X2Go''. 2012-01-31 20:40:18 +0100 Mike Gabriel (10bb244) * Add default value for new session profile parameter xinerama (ignored by Python X2Go for now). 2012-01-30 22:46:08 +0100 Mike Gabriel (3c5a4da) * typo fix 2012-01-30 22:44:27 +0100 Mike Gabriel (15dea9c) * The Python setuptools modules does not have to be installed as dependency with python-x2go. 2012-01-28 01:48:13 +0100 Mike Gabriel (a2e0625) * Fix many undefined symbols reported by Debian developer Jakub Wilk. (THANKS!). Changelog cleanup+fix after release of version 0.1.1.9. 2011-12-27 03:05:39 +0100 Mike Gabriel (159dd4b) * whitespace fix in changelog 2011-12-27 02:37:07 +0100 Mike Gabriel (e76d8c9) * control file, copyright file update 2011-12-19 17:20:31 +0100 Mike Gabriel (f427b71) * fix simultaneous X_DISPLAY.sync() calls on session resuming 2011-12-19 15:48:10 +0100 Mike Gabriel (5748487) * handle missing X display on package build 2011-12-19 15:45:22 +0100 Mike Gabriel (3db6c39) * Terminal session now remember the X window of a terminal session in as an internal property. 2011-12-14 12:05:52 +0100 Mike Gabriel (1ea418b) * add __doc__ strings to new session_window functions in utils.py, make sure the Xlib.display is only initialized once on every call. 2011-12-10 09:53:46 +0100 Mike Gabriel (39e5ef4) * fix for last commit 2011-12-10 09:49:06 +0100 Mike Gabriel (8ea6a89) * new Xlib code shall only be used on non-Windows platforms 2011-12-08 19:39:48 +0100 Mike Gabriel (fb94f41) * Depend on python-xlib. 2011-12-08 19:29:03 +0100 Mike Gabriel (8c2c5b4) * Add support for bringing session windows on top. 2011-12-08 14:49:49 +0100 Mike Gabriel (02d00cf) * Add support for session window title renaming. 2011-12-08 13:48:51 +0100 Mike Gabriel (998741e) * use control session method to access port of remote server 2011-12-08 13:43:32 +0100 Mike Gabriel (af2a1f8) * use control session method to access hostname of remote server 2011-12-07 13:10:45 +0100 Mike Gabriel (584c531) * remove debug code 2011-12-07 12:51:37 +0100 Mike Gabriel (8cdb797) * Allow session parameter change for already registered sessions. 2011-12-07 10:01:24 +0100 Mike Gabriel (c1e20ef) * Make terminal backend ,,applications'' aware. 2011-12-07 10:00:38 +0100 Mike Gabriel (6bdd1a0) * Fix for list processing in INI files. 2011-11-30 15:21:09 +0100 Mike Gabriel (05d0d38) * Introduce additional session profile parameter: setsessiontitle. 2011-11-30 09:27:15 +0100 Mike Gabriel (533f7b6) * Add support for session window title renaming from client-side. 2011-11-30 08:59:07 +0100 Mike Gabriel (8471f9a) * Add ,,autostart'' parameter to default session profile parameters. 2011-11-16 08:06:52 +0100 Mike Gabriel (4a4167b) * use public control session method to detect availability of local folder sharing 2011-11-08 23:04:37 +0100 Mike Gabriel (d121b90) * Add XFCE4 support. 2011-11-08 13:53:02 +0100 Mike Gabriel (5ac2ca9) * Print access to an X2Go server is not controlled by x2goprint group membership, but by fuse membership. 2011-11-02 12:28:24 +0100 Mike Gabriel (40b2d08) * Fix exception raisal in X2goTerminalSessionSTDOUT. 2011-11-01 14:23:52 +0100 Mike Gabriel (f1ac3a1) * Ignore session registry exceptions for profiles that just got disconnected. 2011-10-12 11:01:14 +0200 Mike Gabriel (a44d730) * continue development (0.1.1.x branch) 2011-10-10 20:04:34 +0200 Mike Gabriel (5d87bf5) * Fix duplication of SSH keys in known_hosts file, use hashed hostnames in known_hosts file. Make sure SSH keys written to known_hosts file are available to other SSHClient instances immediately. 2011-10-10 20:00:35 +0200 Mike Gabriel (b18df40) * change random pwd mechanism in checkhosts.py 2011-10-10 19:58:59 +0200 Mike Gabriel (637fa24) * fix hostname parameter in checkhosts.py 2011-10-09 15:26:30 +0200 Mike Gabriel (42e073c) * Use random passwords for checking SSH host keys. 2011-09-29 12:54:37 +0200 Mike Gabriel (8966252) * fix SSH proxy authentication 2011-09-27 20:48:38 +0200 Mike Gabriel (6126afe) * Always disconnect from X2goSession instance. 2011-09-27 20:47:53 +0200 Mike Gabriel (ee2ce40) * Catch failures on sftp_write in control session instance. 2011-09-26 20:27:05 +0200 Mike Gabriel (7a7a930) * Fix missing import of socket module in backends/control/_stdout.py. 2011-09-25 23:40:02 +0200 Mike Gabriel (1b8e6a3) * Improve local session cache dir removal. 2011-09-25 23:38:56 +0200 Mike Gabriel (a04eec2) * provide better __repr__ output for debugging session infos 2011-09-25 21:35:06 +0200 Mike Gabriel (b2ba91b) * Remove local session cache folders after sessions have terminated. 2011-09-25 21:09:42 +0200 Mike Gabriel (8f5a27d) * remove code duplications 2011-09-25 21:01:50 +0200 Mike Gabriel (523dce8) * Unshare local folders during session cleanup. 2011-09-25 02:41:09 +0200 Mike Gabriel (152e901) * Bugfix for: Test for existence of remote home directory on connect. 2011-09-25 02:10:34 +0200 Mike Gabriel (cf6f644) * re-add version 0.1.2.0 header to changelog 2011-09-25 02:08:47 +0200 Mike Gabriel (3134686) * release 0.1.1.7 2011-09-24 22:05:38 +0200 Mike Gabriel (7f9ddc0) * fix for boolean condition, related to folder sharing 2011-09-24 11:49:19 +0200 Mike Gabriel (4940e7a) * remove code duplications 2011-09-24 09:27:22 +0200 Mike Gabriel (4d3232c) * be more precise, when allowing local folder sharing 2011-09-24 04:29:52 +0200 Mike Gabriel (17a51ab) * log message fix 2011-09-24 04:28:30 +0200 Mike Gabriel (e79d565) * Provide test method to query server if folder sharing is available. Test for existence of remote home directory on connect. 2011-09-24 01:08:17 +0200 Mike Gabriel (57b55fd) * minor method name extension in X2goClient class, indent fix 2011-09-24 01:07:45 +0200 Mike Gabriel (d74935d) * fix for new/compat export field interpretation 2011-09-23 19:30:42 +0200 Mike Gabriel (3656749) * Compatibility fix for X2go folder sharing (session profile attribute: export). 2011-09-23 11:22:10 +0200 Mike Gabriel (b5501a1) * Typo fixes in session.py, related to calling _X2goSession__disconnect method. 2011-09-23 10:47:32 +0200 Mike Gabriel (286a07f) * changelog cleanup 2011-09-23 10:44:59 +0200 Mike Gabriel (0cebec3) * Use TCP_NODELAY socket option for graphics forwarding tunnels. 2011-09-23 10:44:25 +0200 Mike Gabriel (bec5751) * changelog fix 2011-09-20 12:41:55 +0200 Mike Gabriel (fce7910) * comment fix 2011-09-20 10:23:19 +0200 Mike Gabriel (eeb2212) * changelog fixes 2011-09-20 10:20:46 +0200 Mike Gabriel (4050299) * Use TCP_NODELAY option for audio rev forwarding tunnels. 2011-09-19 15:27:46 +0200 Mike Gabriel (e10d0ab) * Differentiate between spool folders and data folders when unsharing all folders, return exitcode from X2goTerminalSessionSTDOUT.unshare_* methods. 2011-09-19 14:57:15 +0200 Dick Kniep (0aa87b1) * Typo in utils.py (true instead of True). 2011-09-17 02:26:24 +0200 Mike Gabriel (9ecb391) * remove debug code 2011-09-17 02:14:24 +0200 Mike Gabriel (ae89c0f) * Add support for x2goumount-session calls. 2011-09-14 21:38:04 +0200 Mike Gabriel (019e478) * release version 0.1.1.6 2011-09-14 16:18:24 +0200 Mike Gabriel (cf40811) * Fix handling of lists in session profiles (i.e. ini files). Fixes breakage with x2goclient's rootless vs. desktop mode after pyhoca-gui has been used. 2011-09-13 11:35:47 +0200 Mike Gabriel (a20193b) * * New upstream version (0.1.1.6), bugfix release for 0.1.1.x series: - Fix IPv4 enforcement for localhost connections. - Be tolerant against trailing whitespaces in hostnames. 2011-09-11 18:32:21 +0200 Mike Gabriel (552dff7) * release 0.1.1.5 2011-09-10 01:21:02 +0200 Mike Gabriel (8a9eb20) * Catching fautly x2gostartagent behaviour. 2011-09-09 16:17:59 +0200 Mike Gabriel (0389489) * Make X2goTerminalSessionSTDOUT.has_command resistable against empty command strings. 2011-09-09 14:47:19 +0200 Mike Gabriel (1b32dfd) * Fix for executing commands with arguments that contain a slash (thanks to Dick Kniep for digging this out). 2011-09-08 13:34:42 +0200 Mike Gabriel (43fdebe) * log message fix in x2gosession.py 2011-08-17 01:33:42 +0200 Mike Gabriel (45ef555) * Fix for X2goSession.has_terminated method. 2011-08-17 00:30:49 +0200 Mike Gabriel (746fb6e) * cosmetic fix for share_desktop method 2011-08-15 21:49:49 +0200 Mike Gabriel (55cd3f8) * typo fix (with impact) 2011-08-15 21:17:29 +0200 Mike Gabriel (48798d6) * launch the x2goagent+command earlier during session creation... 2011-07-28 16:15:25 +0200 Mike Gabriel (f66e485) * Do not ignore usekbd session profile option anymore, closes upstream issue #81. 2011-07-28 15:47:15 +0200 Mike Gabriel (0be3949) * add some logging to failing sshfs sessions 2011-07-28 15:44:31 +0200 Mike Gabriel (a64da71) * Stabilize sshfs related problems in case remote user is not in fuse group. 2011-07-28 15:36:59 +0200 Mike Gabriel (850c0bb) * If sound is set to false in session profile use snd_system='none' in terminal session. 2011-07-28 15:23:14 +0200 Mike Gabriel (085f038) * fix for Makefile.docupload 2011-07-28 15:23:01 +0200 Mike Gabriel (1301b38) * changelog update 2011-07-28 15:22:35 +0200 Mike Gabriel (ccbc9a1) * Fix for X2goSessionRegistry.forget method, do not complain if session_uuid is already forgotten. 2011-07-25 08:23:27 +0200 Mike Gabriel (47c2a3e) * catching a socket.error exception during command execution 2011-07-21 23:28:25 +0200 Mike Gabriel (7db6539) * fix for link quality patch 2011-07-21 00:29:06 +0200 Mike Gabriel (afd1e96) * Renamed/restructured NX compression pack method names. 2011-07-20 23:20:30 +0200 Mike Gabriel (9cabef2) * remove interpreter from defaults.py 2011-07-20 19:41:56 +0200 Mike Gabriel (aa33a93) * manually merged in changelog entries from release/0.1.1.x branch 2011-07-20 19:36:09 +0200 Mike Gabriel (07dc777) * Handle full path cmd strings adequately: check existence of full path, but use basename for cmd startup. 2011-07-20 19:35:35 +0200 Mike Gabriel (3c50a9d) * Call a hook method in case a session startup has failed (was: exception raisal). 2011-07-20 18:36:37 +0200 Mike Gabriel (4f2a412) * tab fix 2011-07-18 15:16:44 +0200 Mike Gabriel (406675d) * License change upstream: GPLv3+ -> AGPLv3+ 2011-07-18 15:16:16 +0200 Mike Gabriel (8106f08) * cosmetics 2011-07-18 15:16:08 +0200 Mike Gabriel (98fd462) * doc update 2011-07-18 13:35:13 +0200 Mike Gabriel (66f0ca8) * License Change!!! GPLv3 -> AGPLv3 2011-07-13 20:38:44 +0200 Mike Gabriel (ecc7779) * released 0.1.1.4 (tag: 0.1.1.4) 2011-07-13 20:37:33 +0200 Mike Gabriel (f83ed02) * bugfix for x2gmountdirs calls that occurred if the client username contained blanks. 2011-07-06 22:16:41 +0200 Mike Gabriel (0a5516e) * version increment 2011-07-06 22:15:10 +0200 Mike Gabriel (1e88fc4) * release 0.1.1.3 (tag: 0.1.1.3) 2011-07-06 21:25:21 +0200 Mike Gabriel (d843f23) * Detect SFTP client connections failures, abort session if that happens. 2011-07-06 21:22:32 +0200 Mike Gabriel (dfb613d) * Enforce IPv4 on all SSH proxy and other SSHClient connections when connecting to ''localhost''. 2011-07-06 17:21:00 +0200 Mike Gabriel (d5f871b) * Stabilize desktop sharing if the remote session is not available. 2011-07-06 17:20:09 +0200 Mike Gabriel (c84844d) * Do not allow any interruption during X2go cleanup calls. 2011-07-06 17:18:27 +0200 Mike Gabriel (481acc6) * Fix AttributeError if no graphical proxy instance has been declared yet. 2011-07-06 14:26:02 +0200 Mike Gabriel (6192724) * Desktop sharing: try ''@.0'' additionally to ''@'' when trying to find a desktop for sharing. 2011-07-01 14:57:35 +0200 Mike Gabriel (07f7380) * version increment, README/TODO update 2011-06-28 21:01:08 +0200 Mike Gabriel (3fb1a45) * Fix for session status notification for sessions with PENDING terminal session. (tag: 0.1.1.2) 2011-06-28 20:59:52 +0200 Mike Gabriel (a7e043c) * Getting log msg more precise... 2011-06-28 16:04:44 +0200 Mike Gabriel (9ab5890) * minor fix for SAVEAS MIME box action 2011-06-28 15:59:20 +0200 Mike Gabriel (56bb7e4) * Minor fix for X2go printing 2011-06-28 15:55:35 +0200 Mike Gabriel (5b87155) * Fix for MIME box action SAVEAS 2011-06-28 13:22:43 +0200 Mike Gabriel (15598f1) * Add X2goSession method that detects if auto-connecting a session profile is probably possible. 2011-06-27 11:32:56 +0200 Mike Gabriel (77a00fa) * fix for last commit 2011-06-27 11:31:02 +0200 Mike Gabriel (470dda0) * Improve error handling / logging in forward.py. 2011-06-24 17:14:58 +0200 Mike Gabriel (6d9eef2) * egg version increment 2011-06-24 17:07:27 +0200 Mike Gabriel (5d875b7) * Fix desktop sharing. 2011-06-24 16:52:42 +0200 Mike Gabriel (dafc0ae) * fix for last commit 2011-06-24 16:43:34 +0200 Mike Gabriel (2c39d86) * Use X2goRegistryException for session query for non-existing sessions. Catch this exception in X2goClient. 2011-06-24 02:25:50 +0200 Mike Gabriel (4f78eea) * __doc__ string fixes. (tag: 0.1.1.1) 2011-06-24 02:22:11 +0200 Mike Gabriel (10eebc6) * Epydoc fixes. 2011-06-24 02:14:09 +0200 Mike Gabriel (6c668be) * fixing package build 2011-06-24 02:08:23 +0200 Mike Gabriel (1f32d84) * Fix for local_color_depth function if no $DISPLAY is set. Making a bugfix release. 2011-06-24 01:49:25 +0200 Mike Gabriel (0b8784c) * releasing version 0.1.1.0 (tag: 0.1.1.0) 2011-06-24 01:43:55 +0200 Mike Gabriel (fd7f5a8) * fix/improval of color depth check code 2011-06-24 01:11:15 +0200 Mike Gabriel (c86a2cd) * Provide X2goClient method for retrieval of session by session name. 2011-06-24 00:51:17 +0200 Mike Gabriel (a27899e) * Add compatibility check methods for color depth. 2011-06-23 23:51:18 +0200 Mike Gabriel (f684138) * Detect local color depth and use it as default for new sessions. 2011-06-23 21:28:47 +0200 Mike Gabriel (1876820) * revert fixed color depth for RDP, now using default again: 24bit 2011-06-23 21:11:34 +0200 Mike Gabriel (aab4248) * property fix... 2011-06-23 20:51:05 +0200 Mike Gabriel (fe00a4d) * re-fix session resume/suspend functionality 2011-06-23 16:37:01 +0200 Mike Gabriel (bcbc75f) * remove unnecessary brackets 2011-06-23 16:09:57 +0200 Mike Gabriel (009a7d9) * Faulty sessions (without a NX proxy fw tunnel) will get terminated whenever the X2go server (SSHd) denies the tunnel setup. 2011-06-23 15:29:10 +0200 Mike Gabriel (c935798) * Force 16bit colour depth for RDP-proxy sessions. 2011-06-23 15:23:11 +0200 Mike Gabriel (bb675f7) * typo fix 2011-06-23 15:07:29 +0200 Mike Gabriel (ff2be38) * tidy up usage of .release_proxy() method 2011-06-23 14:28:39 +0200 Mike Gabriel (51fcf91) * syntax fix (missing bracket) 2011-06-23 14:27:19 +0200 Mike Gabriel (aaa397d) * Fix NX proxy startup post-check. 2011-06-23 14:26:42 +0200 Mike Gabriel (ddce72c) * Do not start X2go service tunnels (audio, sshfs) if session startup failed. 2011-06-23 13:23:58 +0200 Mike Gabriel (912ce42) * Use X2goSessionRegistryException here... 2011-06-23 08:35:57 +0200 Mike Gabriel (ccbabb8) * Skip session auto registration during startups of new sessions (avoids duplicate sessions in the session registry. 2011-06-23 01:48:14 +0200 Mike Gabriel (f046c26) * Add X2goSession lock support. 2011-06-23 01:47:02 +0200 Mike Gabriel (b60599d) * typo fix 2011-06-23 01:08:07 +0200 Mike Gabriel (4ccf6c2) * Re-introduce fix for failing forwarding tunnels (on tunnel shutdown). 2011-06-23 00:57:29 +0200 Mike Gabriel (cf7fdf0) * Session notification fixes, session resume/suspend fixes, fw tunnel stabalized. 2011-06-22 18:13:22 +0200 Mike Gabriel (a7da6a7) * Change of sleep times when starting/stopping NX proxy. 2011-06-22 18:10:42 +0200 Mike Gabriel (21c4a93) * Mark terminal session as PENDING even before actual X2goTerminalSession object get created. 2011-06-22 02:35:41 +0200 Mike Gabriel (2051ce5) * Wait for 10 secs when taking over an active session, remove debug output. 2011-06-22 01:22:33 +0200 Mike Gabriel (d43d6a8) * Fix multiple notifications for the same session state change, reliably differentiate between found sessions after connect and newly started sessions from another client. 2011-06-21 20:11:23 +0200 Mike Gabriel (982d86f) * Add X2goTimeOutException 2011-06-21 19:50:10 +0200 Mike Gabriel (1864f20) * Make cache more configurable (session list updates, desktop list updates), add an auto_update_listdesktops_cache to X2goClient constructor argvs. 2011-06-21 15:39:47 +0200 Mike Gabriel (40032c7) * Explicitly tunnel over IPv4 for NX proxy. 2011-06-21 15:24:35 +0200 Mike Gabriel (81b1dbb) * Assure that rev forwarding tunnels use IPv4 (replace localhost with 127.0.0.1) 2011-06-21 15:08:36 +0200 Mike Gabriel (b43c50f) * Rely on X2goSessionListInfo backend to handle exceptions appropriately. 2011-06-21 14:23:59 +0200 Mike Gabriel (5a148fd) * condition fix 2011-06-21 14:21:19 +0200 Mike Gabriel (9dbd64e) * Make sure list session and list desktop commands always return. 2011-06-21 14:20:27 +0200 Mike Gabriel (d5f0709) * Add debug output that signals that the X2goGuardian loop is running... 2011-06-21 14:18:38 +0200 Mike Gabriel (476adef) * Make stylish adjustments... 2011-06-21 12:40:30 +0200 Mike Gabriel (7f32339) * Initialize list sessions/desktops cache with a more precise default value. 2011-06-21 12:25:38 +0200 Mike Gabriel (b310692) * Reduce delays during authentication / session list cache updates. 2011-06-21 12:24:55 +0200 Mike Gabriel (2e6f672) * Forget SSH proxy password after auth failures, fix local folder sharing. 2011-06-21 12:23:33 +0200 Mike Gabriel (49301f6) * dropping redundant code in cache.py 2011-06-20 23:24:03 +0200 Mike Gabriel (a93a351) * Add X2goSession status property ,,faulty''. 2011-06-20 22:07:42 +0200 Mike Gabriel (116df2c) * import fix 2011-06-20 22:06:38 +0200 Mike Gabriel (8da942d) * Fix SSH proxy stop_thread on Windows. 2011-06-20 21:38:09 +0200 Mike Gabriel (972d8cf) * Make sure SSH proxy password gets forgotten between two sessions, fix for mutiple open SSH client sessions. 2011-06-20 20:59:20 +0200 Mike Gabriel (ec6440b) * call desctructor of mimebox_queue on object destruction 2011-06-20 20:43:11 +0200 Mike Gabriel (160d07f) * calling update_status only once after all sessions have been registered 2011-06-20 14:17:32 +0200 Mike Gabriel (5014b32) * Close SSH connection first, then close down SSH proxy. 2011-06-20 12:48:20 +0200 Mike Gabriel (fa0d562) * not closing ssh proxy on AuthenticationExceptions 2011-06-20 11:44:49 +0200 Mike Gabriel (27d7b87) * not closing SSH transport on authentication failures 2011-06-20 10:13:16 +0200 Mike Gabriel (94a3a09) * catch SSHException during x2go_exec_command 2011-06-19 22:01:09 +0200 Mike Gabriel (e0a1241) * another type fix 2011-06-19 21:58:50 +0200 Mike Gabriel (04ecca3) * fixed type 2011-06-19 21:54:11 +0200 Mike Gabriel (2eb1547) * Fix SSH authentication failures (close session on failure). 2011-06-14 17:09:42 +0200 Mike Gabriel (81d1fe7) * Typo fix 2011-06-14 17:02:20 +0200 Mike Gabriel (e3aec58) * Fix version strings 2011-06-13 23:48:54 +0200 Mike Gabriel (5af424a) * New upstream version (0.1.1.0): -Add X2go desktop sharing support. 2011-06-08 16:25:22 +0200 Mike Gabriel (84a760d) * Preparing for release, version increment. (tag: 0.1.0.3) 2011-06-08 16:24:55 +0200 Mike Gabriel (c1dfbd5) * Slight fix for the keyboard file. 2011-06-08 16:24:12 +0200 Mike Gabriel (0a3dd9e) * Make SSH and SSH proxy connection more robust against link line failures 2011-05-28 01:19:09 +0200 Mike Gabriel (f457a2a) * adds defkeymap / xkb support for NX backend 2011-05-28 00:11:32 +0200 Mike Gabriel (4a64420) * adds utils function that wraps around xprop -root _XKB_RULES_NAMES 2011-05-27 23:44:55 +0200 Mike Gabriel (b05f63f) * adds infrastructure for proxy_options class constructor kwarg 2011-05-27 14:22:51 +0200 Mike Gabriel (1a5aed7) * version increment 2011-05-27 14:19:40 +0200 Mike Gabriel (ef0811d) * fixes profile creation for empty session lists (tag: 0.1.0.2) 2011-05-27 13:18:37 +0200 Mike Gabriel (0ba8dfe) * fix for locale LANG=C (tag: 0.1.0.1) 2011-05-27 13:10:02 +0200 Mike Gabriel (2a0dbf7) * bugfix for systems without locales 2011-05-27 08:19:14 +0200 Mike Gabriel (10f9684) * adds folder location for PDFSAVE print action to example configs 2011-05-27 08:15:13 +0200 Mike Gabriel (73b0531) * updates README.Trinity-Desktop (symlink placing in /usr/local/bin/ not /usr/bin) 2011-05-26 02:16:55 +0200 Mike Gabriel (389eb6a) * fixes epydoc syntax for __doc__ string (tag: 0.1.0.0) 2011-05-25 19:58:39 +0200 Mike Gabriel (58bd193) * version increment, preparing for release 0.1.0.0 2011-05-25 19:52:51 +0200 Mike Gabriel (4ef2323) * fixes multiply notified sessions 2011-05-25 19:07:25 +0200 Mike Gabriel (314423e) * new lines added in __doc__ strings where missing 2011-05-25 19:07:10 +0200 Mike Gabriel (23b3853) * adds __doc__ strings to session.py 2011-05-25 14:43:36 +0200 Mike Gabriel (8f96ae7) * __doc__ string update in registry.py, property converted to method 2011-05-25 13:12:33 +0200 Mike Gabriel (13a3bb4) * __doc__ string proof reading... 2011-05-25 12:48:14 +0200 Mike Gabriel (afbac21) * __doc__ str fixup 2011-05-25 12:47:00 +0200 Mike Gabriel (3d5df6a) * __doc__ string fixup 2011-05-25 12:45:43 +0200 Mike Gabriel (86dd49d) * grammar fixup 2011-05-25 12:44:34 +0200 Mike Gabriel (d6f3ff3) * no plural for "information" 2011-05-25 12:43:34 +0200 Mike Gabriel (908efcb) * renaming "list sessions cache" to "session list cache" 2011-05-25 03:46:17 +0200 Mike Gabriel (78a5515) * fix for new timestamp attribute 2011-05-25 03:42:03 +0200 Mike Gabriel (605a0d3) * __doc__ string fixes, might break code (removed @property markers) 2011-05-25 03:25:15 +0200 Mike Gabriel (2e0232b) * updates __doc__ strings of client.py 2011-05-25 01:43:37 +0200 Mike Gabriel (99ade31) * spelling error 2011-05-25 01:29:46 +0200 Mike Gabriel (273e67e) * updates __doc__ strings in checkhosts.py 2011-05-25 01:29:24 +0200 Mike Gabriel (f085f47) * adds __doc__ strings for hook methods in client.py 2011-05-25 00:30:21 +0200 Mike Gabriel (49529de) * adds __doc__ strings to cache.py 2011-05-25 00:13:14 +0200 Mike Gabriel (10931a3) * updates __doc__ strings of cleanup.py 2011-05-25 00:12:17 +0200 Mike Gabriel (a145630) * updates __doc__ string if checkhosts.py 2011-05-24 23:36:27 +0200 Mike Gabriel (2b60b2b) * updates __doc__ strings in default.py 2011-05-24 23:29:19 +0200 Mike Gabriel (d3c36fa) * updates __doc__ strings in forward.py 2011-05-24 23:25:04 +0200 Mike Gabriel (2137a28) * whitespace cleanup 2011-05-24 23:23:33 +0200 Mike Gabriel (8c1d94e) * updates __doc__ strings in guardian.py 2011-05-24 23:13:46 +0200 Mike Gabriel (9a96522) * updates __doc__ strings for MIME box code 2011-05-24 23:13:17 +0200 Mike Gabriel (c00b539) * updates __doc__ strings in inifiles.py 2011-05-24 23:13:00 +0200 Mike Gabriel (f3153d0) * updates __doc__ strings in log.py 2011-05-24 23:01:18 +0200 Mike Gabriel (e6713d9) * updates __doc__ strings of printactions.py 2011-05-24 22:51:21 +0200 Mike Gabriel (612d10e) * updates __doc__ strings of printqueue.py 2011-05-24 22:43:23 +0200 Mike Gabriel (5237fa7) * adds __doc__ strings to pulseaudio.py 2011-05-24 22:43:06 +0200 Mike Gabriel (46008e4) * fixes epydoc errors on sftpserver.py 2011-05-24 22:35:42 +0200 Mike Gabriel (96ea578) * updates __doc__ strings of rforward.py 2011-05-24 22:19:41 +0200 Mike Gabriel (01d8f6f) * updates __doc__ strings for sftpserver.py 2011-05-24 21:25:36 +0200 Mike Gabriel (6e4e92f) * adds __doc__ strings to sshproxy.py 2011-05-24 21:02:02 +0200 Mike Gabriel (8060237) * adds __doc__ strings to utils.py 2011-05-24 20:42:51 +0200 Mike Gabriel (565d53c) * adds __doc__ strings to xserver.py 2011-05-24 20:38:16 +0200 Mike Gabriel (f1d83c2) * fixes method constructor 2011-05-24 20:12:03 +0200 Mike Gabriel (a9398f1) * __doc__ string update of __init__.py 2011-05-24 19:52:52 +0200 Mike Gabriel (25ae8bd) * skipping session status updates if not longer than at leas 1sec ago 2011-05-24 19:52:26 +0200 Mike Gabriel (8cc7705) * removes redundant calls of session status updates 2011-05-23 22:49:45 +0200 Mike Gabriel (6e682c9) * version increment, changelog update 2011-05-23 22:48:23 +0200 Mike Gabriel (27abd53) * fixes local folder sharing after session resumption 2011-05-22 21:11:49 +0200 Mike Gabriel (878e1c9) * nxproxy moved to Depends section 2011-05-22 21:11:01 +0200 Mike Gabriel (125e7a5) * old version headers still refering to GPLv2, updated to GPLv3 2011-05-21 23:21:07 +0200 Mike Gabriel (b754993) * version increment (tag: 0.0.44.2) 2011-05-21 23:14:57 +0200 Mike Gabriel (a0095f6) * FSF address updates for stray files 2011-05-21 23:12:36 +0200 Mike Gabriel (3c2a725) * allowing for config_files = None (using defaults instead) 2011-05-21 23:12:20 +0200 Mike Gabriel (f21814d) * removed empty line 2011-05-21 23:10:44 +0200 Mike Gabriel (fc59469) * repaired test suite scripts, solved namespace interference with other Python projects 2011-05-21 20:45:42 +0200 Mike Gabriel (e90fee4) * misplaced char... 2011-05-19 09:07:57 +0200 Mike Gabriel (227d335) * adds VCS information to control file (tag: 0.0.44.1) 2011-05-17 12:25:34 +0200 Mike Gabriel (d395b2f) * changelog entry 2011-05-17 12:24:19 +0200 Mike Gabriel (e0666e6) * removes dropbox patch from doc section 2011-05-17 12:23:56 +0200 Mike Gabriel (49ddf41) * updates Makefile.docupload (by docbuild stanza) 2011-05-17 12:11:36 +0200 Mike Gabriel (9d8b582) * changelog update (tag: 0.0.44.0) 2011-05-17 11:27:42 +0200 Mike Gabriel (90837cb) * python-x2go does currently not check server access permissions (as group membership in group x2gousers is not a server-side requirement anymore) 2011-05-17 10:36:28 +0200 Mike Gabriel (7eda742) * version increment 2011-05-17 10:34:40 +0200 Mike Gabriel (2c6b536) * adds get_session_server_hostname method to X2goClient class 2011-05-17 09:38:09 +0200 Mike Gabriel (2583a0e) * be able to return a session username before connect 2011-05-17 09:01:43 +0200 Mike Gabriel (a432308) * adds line at end of file 2011-05-16 22:03:56 +0200 Mike Gabriel (6db1950) * enabling build for different python versions 2011-05-15 22:18:08 +0200 Mike Gabriel (fdaed69) * fix for last commit 2011-05-13 12:07:14 +0200 Mike Gabriel (8c55218) * fixes missing WindowsError dummy when running on Linux 2011-05-12 11:43:58 +0200 Stéphane Graber (7d1b15b) * prevent crashes on proxy disconnect, thanks to Stéphane Graber 2011-05-12 10:00:08 +0200 Mike Gabriel (0c4561b) * updates control file description 2011-05-12 08:57:26 +0200 Mike Gabriel (87e2fcb) * now really fixes build dependency for Ubuntu Launchpad (lucid build) 2011-05-12 08:28:33 +0200 Mike Gabriel (db4cf7f) * fixes dependency for building on Ubuntu Launchpad (lucid build) 2011-04-26 08:12:13 +0200 Mike Gabriel (a44c121) * removes old build script 2011-04-26 01:00:35 +0200 Mike Gabriel (8f71739) * increment package revision 2011-04-26 00:59:19 +0200 Mike Gabriel (61d7ad7) * fixes control file 2011-04-26 00:48:51 +0200 Mike Gabriel (0df7120) * incrementing changelog for nightly-builds 2011-04-26 00:46:22 +0200 Mike Gabriel (a7834f9) * release 0.0.43.0 (tag: 0.0.43.0) 2011-04-25 23:13:06 +0200 Mike Gabriel (807dd06) * moving to Recommends: nxproxy, cups-bsd|lpr 2011-04-25 21:57:56 +0200 Mike Gabriel (974a4bb) * incrementing package revision 2011-04-25 21:53:06 +0200 Mike Gabriel (ee09229) * simplified build stanza in rules file 2011-04-25 21:52:51 +0200 Mike Gabriel (b41b9ee) * being less strict on gevent build-dep 2011-04-25 21:52:24 +0200 Mike Gabriel (3a622d7) * moved Makefile out of the way 2011-04-25 21:51:12 +0200 Mike Gabriel (4b35da9) * building for unstable only, shortened one line 2011-04-25 21:26:41 +0200 Mike Gabriel (e05c1fa) * updated to new epydoc build location 2011-04-25 21:25:49 +0200 Mike Gabriel (d445bcb) * adds docupload section to Makefile 2011-04-25 21:23:25 +0200 Mike Gabriel (8bc9be4) * adds dummy Makefile for later use 2011-04-25 21:22:54 +0200 Mike Gabriel (e2304fb) * python-x2go now also builds for squeeze 2011-04-25 15:12:27 +0200 Mike Gabriel (766fa52) * skipping texlive build-deps for now... 2011-04-25 15:10:55 +0200 Mike Gabriel (82bac3c) * changing from 0~nwt to 0~x2go revision 2011-04-25 15:08:25 +0200 Mike Gabriel (65a3d08) * fixes dh_installdocs paths 2011-04-25 15:07:36 +0200 Mike Gabriel (828b3a0) * disabling epydoc/pdf for now as it causes tex dependency problems during build 2011-04-25 14:54:59 +0200 Mike Gabriel (de4dc2f) * adds texlive-fonts-recommended as build-dep 2011-04-25 14:34:25 +0200 Mike Gabriel (535be64) * beautified control file 2011-04-25 14:33:12 +0200 Mike Gabriel (6e69f01) * adds texlive packages as build-dep 2011-04-25 14:21:24 +0200 Mike Gabriel (15cfcf7) * running epydoc in debug mode 2011-04-25 11:26:22 +0200 Mike Gabriel (9eb75ca) * fixes rules file 2011-04-25 11:15:54 +0200 Mike Gabriel (e047e66) * removes doc folder 2011-04-25 11:14:25 +0200 Mike Gabriel (5bb4b2e) * epydoc not part of source tree anymore, now builds for Debian package 2011-04-19 23:04:53 +0200 Mike Gabriel (5de9a49) * moving to X2go build system 2011-04-19 16:08:07 +0200 X2go Administrator (0c373d1) * switching to source format 3.0 (native) 2011-04-19 14:06:10 +0200 Mike Gabriel (81a7c10) * adds python modules (gevent, paramiko) to build dependencies 2011-04-19 10:49:06 +0200 Mike Gabriel (2df7422) * fixes profile-already-exists validation 2011-04-19 09:39:08 +0200 Mike Gabriel (6fa4503) * renamed X2goDropbox to X2goMIMEbox 2011-04-18 16:18:18 +0200 Mike Gabriel (46ec399) * pre-release commit (tag: 0.0.42.0) 2011-04-18 15:56:51 +0200 Mike Gabriel (7766484) * no epydoc upload when building for NWT repository 2011-04-18 15:55:45 +0200 Mike Gabriel (502c18b) * build script for X2go reprepro 2011-04-13 23:41:21 +0200 Mike Gabriel (8ee2d1a) * UNITY now starts unity-2d-launcher 2011-04-13 15:26:48 +0200 Mike Gabriel (b22a39a) * adds support for X2go servers that offer Ubuntu's unity desktop shell 2011-03-30 12:48:55 +0200 Mike Gabriel (313fe03) * validating profile name changes 2011-03-30 12:48:20 +0200 Mike Gabriel (b1f9027) * providing default profile name for new session profiles 2011-03-28 09:13:48 +0200 Mike Gabriel (4b0fdae) * updating control session status directly after connect 2011-03-26 20:58:24 +0100 Mike Gabriel (21c47df) * rebuilt x2go API documentation (tag: 0.0.41.0) 2011-03-26 20:57:59 +0100 Mike Gabriel (4b6d1b7) * adding README.Trinity-Desktop to Debian doc folder 2011-03-26 20:57:43 +0100 Mike Gabriel (dcaf909) * version increment 2011-03-26 20:41:02 +0100 Mike Gabriel (98740c3) * improved session state recognition 2011-03-26 20:12:15 +0100 Mike Gabriel (0d27cd4) * removed debug code 2011-03-26 16:50:03 +0100 Mike Gabriel (7cde3c5) * speeding up bootstrap of X2goClient instance with many session profiles 2011-03-26 15:10:00 +0100 Mike Gabriel (5fd3692) * making sure that a missing dropbox/spool folder in session cache dir does not throw an error 2011-03-22 11:34:31 +0100 Mike Gabriel (e8dde86) * typo mended... 2011-03-22 11:32:37 +0100 Mike Gabriel (7fa40ac) * added short Trinity-Desktop howto 2011-03-22 11:28:32 +0100 Mike Gabriel (212c545) * Trinity desktop support for Python X2go API 2011-03-11 00:44:40 +0100 Mike Gabriel (56e8cc6) * Merge commit '0.0.40.0' 2011-03-11 00:44:39 +0100 mike.gabriel@das-netzwerkteam.de (6ef53e8) * Released 0.0.40.0 (tag: 0.0.40.0) 2011-03-11 00:44:07 +0100 Mike Gabriel (82b51c2) * repaired changelog 2011-03-11 00:43:24 +0100 Mike Gabriel (41af62a) * Merge commit '0.0.40.0' 2011-03-11 00:42:33 +0100 mike.gabriel@das-netzwerkteam.de (bdb3c5c) * Released 0.0.40.0 2011-03-11 00:42:07 +0100 Mike Gabriel (4a961ba) * pre-release commit 2011-03-10 21:53:06 +0100 Mike Gabriel (ba5268c) * silencing unwanted HOOK_* notifications even more 2011-03-10 21:20:26 +0100 Mike Gabriel (b942b22) * silencing HOOK_* notification calls after a re-connect to a server 2011-03-10 20:40:02 +0100 Mike Gabriel (e527c1b) * removed menu=0 option from nxproxy command options list 2011-03-09 13:52:03 +0100 Mike Gabriel (3fc4866) * catching a on-purpose-raised X2goControlSessionException 2011-03-09 13:51:40 +0100 Mike Gabriel (d99c649) * fixing path name in debug output 2011-03-07 19:49:01 +0100 Mike Gabriel (c2ff89b) * updateing session status after disconnect (should solve problem with unnecessary and confusing HOOK messages that are shown after network interruption) 2011-03-07 19:44:28 +0100 Mike Gabriel (246d3fa) * adding gevent.Timeout around paramiko.Transport.cancel_port_forward 2011-03-07 18:21:03 +0100 Mike Gabriel (0667e65) * typo fix 2011-03-07 18:18:10 +0100 Mike Gabriel (aab9bfe) * making gsprint.exe path configurable in printing config file (Win32), this should close issue #56 2011-03-07 18:01:05 +0100 Mike Gabriel (84dc17b) * preventing pulseaudio from running in console window (Win32 issue) 2011-03-07 14:24:50 +0100 Mike Gabriel (bb5da67) * typo fix 2011-03-07 14:22:41 +0100 Mike Gabriel (92ac045) * fixing File-Not-Found error for pulseaudio log file 2011-03-07 14:18:42 +0100 Mike Gabriel (ade6b69) * added win32security sa handle 2011-03-07 14:16:09 +0100 Mike Gabriel (3775aef) * wrong brackets... 2011-03-07 14:07:37 +0100 Mike Gabriel (901176d) * pulseaudio needs to log stdout/stderr to file 2011-03-07 13:53:40 +0100 Mike Gabriel (1030c99) * disabled -wgl option for VcXsrv, closes issue #48, renew your .x2goclient/xconfig file 2011-03-07 12:13:24 +0100 Mike Gabriel (16d6079) * exchanged xserver statup (from subprocess.Popen to win32process.CreateProcess) 2011-03-07 12:02:47 +0100 Mike Gabriel (b78559c) * fixes problem of remaining pulseaudio processes after application exit (closes issue #29). 2011-03-07 10:39:05 +0100 Mike Gabriel (58bf7d1) * work on pulseaudio.py, breaks code functionality, transfer to Win32 dev system 2011-03-03 13:55:17 +0100 Mike Gabriel (a3949b4) * added empty lines between HOOK methods 2011-03-03 13:46:04 +0100 Mike Gabriel (0939306) * incrementing version number 2011-03-01 16:18:05 +0100 Mike Gabriel (fa24fec) * removed superfluous return value 2011-03-01 15:24:25 +0100 Mike Gabriel (de1c01f) * prohibiting dotfiles in X2go dropboxes, fixes issue #46 2011-03-01 13:26:43 +0100 Mike Gabriel (0165289) * logging blacklisted dropbox extensions properly 2011-03-01 13:07:14 +0100 Mike Gabriel (40b6b7d) * added a hard-coded file extension blacklist for the X2goDropBox 2011-03-01 12:48:00 +0100 m.gabriel (0d27975) * change of author's email address 2011-03-01 00:46:23 +0100 Mike Gabriel (789796a) * Merge commit '0.0.39.0' into develop 2011-03-01 00:46:16 +0100 mike.gabriel@das-netzwerkteam.de (46378ed) * Released 0.0.39.0 (tag: 0.0.39.0) 2011-03-01 00:45:40 +0100 Mike Gabriel (7fc7291) * new API doc files... 2011-03-01 00:43:31 +0100 Mike Gabriel (30f60e3) * pre-release commit 2011-03-01 00:25:38 +0100 Mike Gabriel (4a081cd) * incrementing version number 2011-03-01 00:17:13 +0100 Mike Gabriel (3a23cbf) * necessary changes for interactively choosing a print action from a GUI 2011-02-28 13:10:00 +0100 Mike Gabriel (7f51af9) * cleaning up logger call (non-ascii characters not allowed in log) 2011-02-28 12:23:04 +0100 Mike Gabriel (3f8753a) * added is_abs_path function to the utils.py helper library 2011-02-28 09:04:20 +0100 Mike Gabriel (07fc893) * extended file backend of X2goClientPrinting class 2011-02-28 09:04:00 +0100 Mike Gabriel (92ff5a7) * allowing relative and absolute paths in save_to_folder location 2011-02-28 00:04:29 +0100 Mike Gabriel (efd458b) * removed command interpreter from first lines 2011-02-28 00:01:52 +0100 Mike Gabriel (db0e361) * syntax fixes 2011-02-27 23:49:45 +0100 Mike Gabriel (ec03b42) * implemented a set_print_action method for X2goClientPrinting config class 2011-02-27 19:59:39 +0100 Mike Gabriel (347395a) * fix for last commit... 2011-02-27 19:39:54 +0100 Mike Gabriel (6ae080e) * providing means for re-loading print action configuration 2011-02-27 18:39:06 +0100 Mike Gabriel (721adf6) * typo fix 2011-02-27 18:37:22 +0100 Mike Gabriel (20e27fe) * introduced get_print_action function-method 2011-02-27 18:33:47 +0100 Mike Gabriel (0ad799e) * adding some helper options for PyHoca-GUI's printingprefs dialog window 2011-02-27 02:50:09 +0100 Mike Gabriel (e067379) * Merge commit '0.0.38.0' into develop 2011-02-27 02:50:08 +0100 mike.gabriel@das-netzwerkteam.de (b0943dc) * Released 0.0.38.0 (tag: 0.0.38.0) 2011-02-27 02:49:14 +0100 Mike Gabriel (6a48186) * stray API doc file 2011-02-27 02:45:04 +0100 Mike Gabriel (b7f4518) * pre-release commit 2011-02-27 02:05:39 +0100 Mike Gabriel (fe0b633) * comment update 2011-02-27 01:02:12 +0100 Mike Gabriel (35b1dee) * check host key fix for IPv4 addresses instead of DNS hostnames 2011-02-27 01:47:38 +0100 Mike Gabriel (1fdccab) * some Paramiko versions forget the SSH port on connection with default SSH port set to 22 2011-02-27 01:21:42 +0100 Mike Gabriel (2be37b5) * SSH host key verification framework ready for testing 2011-02-26 15:07:01 +0100 Mike Gabriel (88a3386) * being a bit more explicit in log output (check hosts now names hostname, port and fingerprint in log file) 2011-02-26 15:04:53 +0100 Mike Gabriel (b963574) * bugfix, used wrong method call 2011-02-26 14:38:32 +0100 Mike Gabriel (ecd66a9) * incrementing version number 2011-02-26 14:30:12 +0100 Mike Gabriel (f41550a) * completed Paramiko/SSH host key authorization dialog framework 2011-02-26 14:22:49 +0100 Mike Gabriel (12bb02a) * making sure that missing host key policy is paramiko.RejectPolicy() most of the time 2011-02-26 02:30:42 +0100 Mike Gabriel (9069d0c) * first draft for SSH host key checks (still missing: fingerprint / fingerprint type query of host) 2011-02-25 21:35:06 +0100 Mike Gabriel (c17955c) * adding basic infrastructure for host key checks 2011-02-25 18:45:07 +0100 Mike Gabriel (d9cb960) * Merge commit '0.0.37.0' into develop 2011-02-25 18:45:03 +0100 mike.gabriel@das-netzwerkteam.de (4c83012) * Released 0.0.37.0 (tag: 0.0.37.0) 2011-02-25 18:44:27 +0100 Mike Gabriel (9d3c630) * one more .gitignore entry... 2011-02-25 18:43:38 +0100 Mike Gabriel (3520355) * ignoring dpkg build 2011-02-25 18:40:42 +0100 Mike Gabriel (8c89d74) * pre-release commit 2011-02-25 17:29:46 +0100 Mike Gabriel (9558f2d) * allowing dropbox extensions without preceeding dot (,,.'') 2011-02-25 17:26:13 +0100 Mike Gabriel (580436d) * added basic support for environment variable injection into server session 2011-02-25 17:25:48 +0100 Mike Gabriel (a7c90da) * adding patches dir to doc section of Debianp package 2011-02-25 17:25:05 +0100 Mike Gabriel (617488c) * added x2goserver patches for X2goDropbox feature in Python X2go 2011-02-25 16:45:56 +0100 Mike Gabriel (e77f35f) * removed tabs, replaced by blanks 2011-02-25 16:36:24 +0100 Mike Gabriel (937c799) * handling ,,('' and ,,)'' in slugify function 2011-02-25 16:33:04 +0100 Mike Gabriel (72e9bf3) * removed tabs, replaced by blanks 2011-02-25 16:29:00 +0100 Mike Gabriel (a838bb5) * updated README (explaining about Ghostscript/GSview printing on Windows) 2011-02-25 15:22:55 +0100 Mike Gabriel (8007fb6) * printing with gsprint.exe works now... 2011-02-25 15:19:15 +0100 Mike Gabriel (d280d9a) * passing X2goSession instance object to X2goRevFwTunnelSFTP class instances 2011-02-25 14:39:09 +0100 Mike Gabriel (997fa26) * typo fix 2011-02-25 14:38:11 +0100 Mike Gabriel (6822c55) * tweaking gsprint.exe printing 2011-02-25 14:27:15 +0100 Mike Gabriel (e3fab9a) * trying to print with gsprint.exe first (if installed), then falling back to win32api 2011-02-25 12:59:30 +0100 Mike Gabriel (cd85ebe) * trying ,,printto'' verb for ShellExecute 2011-02-25 12:40:11 +0100 Mike Gabriel (b8456f5) * trying to retrieve error message from win32api 2011-02-25 12:31:21 +0100 Mike Gabriel (443ebca) * juggled variables around (X2goPrintActionPRINT for Windows) 2011-02-25 12:23:42 +0100 Mike Gabriel (3569969) * using win32print to set and re-set default Windows printer in printaction PRINT 2011-02-25 01:03:32 +0100 Mike Gabriel (ae0bf57) * fixes to-early-removed print job files bug 2011-02-25 00:55:07 +0100 Mike Gabriel (efe9e59) * replacing gevent.sleep with time.sleep 2011-02-25 00:44:48 +0100 Mike Gabriel (5854eff) * catching control session deaths on several client commands 2011-02-25 00:33:16 +0100 Mike Gabriel (fdd7a2b) * checking is self.client_instance exists, otherwise logging to stdout 2011-02-25 00:14:36 +0100 Mike Gabriel (cae0b41) * fixes wrong method kwargs 2011-02-24 23:59:31 +0100 Mike Gabriel (6bf5b1b) * bugfix for win32api.error 2011-02-24 23:42:44 +0100 Mike Gabriel (7531933) * catching printaction error on Windows 2011-02-24 23:07:06 +0100 Mike Gabriel (136a8e1) * missing gevent module import 2011-02-24 23:02:57 +0100 Mike Gabriel (3e323cf) * fixed a non-occurring Windows error, checking for exceptions more globally 2011-02-24 22:55:06 +0100 Mike Gabriel (2841cbe) * using win32print to detect Windows default printer, added some debug logging 2011-02-24 22:37:43 +0100 Mike Gabriel (00a7b3d) * fixing non-normalized path in pdf_file (occurs on Windows) 2011-02-24 18:30:33 +0100 Mike Gabriel (b0a0a80) * adding ,,printing'' config examples 2011-02-24 16:48:09 +0100 Mike Gabriel (866e7a3) * fixes PDFSAVE print action: (a) dest folder is created if not exists, (b) pdf_file removal now handled properly 2011-02-24 16:38:07 +0100 Mike Gabriel (b69db12) * Merge branch 'develop' of ssh://code.x2go.org:32032/srv/git/code.x2go.org/python-x2go into develop 2011-02-24 16:37:21 +0100 Mike Gabriel (5e21f99) * handling file not found error if PDF viewer command is not installed on the client 2011-02-24 16:36:53 +0100 Mike Gabriel (10c4634) * adding .gitignore file (*.pyc ignored for now) 2011-02-23 21:33:16 +0100 Mike Gabriel (a6d7462) * allowing pulseaudio binary paths with blank characters 2011-02-23 13:08:14 +0100 Mike Gabriel (632bd07) * updated README 2011-02-23 12:43:05 +0100 Mike Gabriel (79aee8f) * removed tar files from master (???) 2011-02-23 12:40:03 +0100 Mike Gabriel (506400a) * removing tar files from master (???) 2011-02-23 12:30:39 +0100 Mike Gabriel (892d3bd) * fixing pulsaudio call (using subprocess.Popen again...) 2011-02-23 12:29:16 +0100 Mike Gabriel (4f732ee) * Merge branch 'pristine-tar' of ssh://code.x2go.org:32032/srv/git/code.x2go.org/python-x2go into develop 2011-02-22 20:00:30 +0100 Mike Gabriel (a971a34) * incrementing verion 2011-02-22 19:32:30 +0100 Mike Gabriel (d39fca8) * pristine-tar data for python-x2go_0.0.36.1.orig.tar.gz 2011-02-22 19:32:28 +0100 mike.gabriel@das-netzwerkteam.de (d3d6fd2) * Released 0.0.36.1 (tag: 0.0.36.1) 2011-02-22 19:28:39 +0100 Mike Gabriel (de2e5d9) * pre-release commit (0.0.36.1) 2011-02-22 19:14:09 +0100 Mike Gabriel (71accf0) * calling set_dropbox_action also for unicode action names (fixes issue #39). 2011-02-22 18:55:56 +0100 Mike Gabriel (fc32cd5) * Merge branch 'pristine-tar' of ssh://code.x2go.org:32032/srv/git/code.x2go.org/python-x2go into develop 2011-02-22 16:37:52 +0100 Mike Gabriel (f250d19) * test-wise using pulseaudio daemon mode 2011-02-22 13:27:33 +0100 Mike Gabriel (d81cc04) * incrementing version, using 4 digits now 2011-02-22 13:23:42 +0100 Mike Gabriel (b52a9f1) * pristine-tar data for python-x2go_0.0.36.0.orig.tar.gz 2011-02-22 13:23:41 +0100 mike.gabriel@das-netzwerkteam.de (8d473c1) * Released 0.0.36.0 (tag: 0.0.36.0) 2011-02-22 13:22:53 +0100 Mike Gabriel (487d466) * merged develop to master manually 2011-02-22 13:21:26 +0100 Mike Gabriel (dea864f) * Pre-merge branch 'develop' (doing things manually) 2011-02-22 13:19:13 +0100 Mike Gabriel (cb9bfef) * Merge branch 'develop' of ssh://code.x2go.org:32032/srv/git/code.x2go.org/python-x2go into develop 2011-02-22 13:18:27 +0100 Mike Gabriel (b722ac6) * last fixes before release 0.0.36.0, epydoc rebuilt 2011-02-21 17:00:02 +0100 Mike Gabriel (70192bc) * checking for RDP sessions in case PulseAudio startup fails (on Windows clients) 2011-02-15 22:39:31 +0100 Mike Gabriel (2c402b8) * * self.session_instance has not been defined in X2goRevFwTunnelToSFTP so far 2011-02-03 12:54:39 +0100 Mike Gabriel (1cd9f8a) * ignoring .pyc and .pyo files in Git (added .gitignore) 2011-02-03 12:53:33 +0100 Mike Gabriel (f4887ff) * added x2goclient-qt trayicon settings to default X2goClientSettings 2011-01-31 01:01:49 +0100 Mike Gabriel (cfed966) * * fixed deletion of known_hosts file 2011-01-29 01:28:08 +0100 Mike Gabriel (0ecf2a7) * * removed README.init 2011-01-29 01:27:57 +0100 Mike Gabriel (3f30aab) * Merge git://code.x2go.org/python-x2go 2011-01-29 01:20:23 +0100 Mike Gabriel (6ab6469) * * python-x2go code -> develop branch, emptied master branch 2011-01-27 18:20:15 +0100 Mike Gabriel (96522c7) * Initializing repository 2011-01-25 15:20:39 +0000 mike (a9852ea) * * trying to improve Windows stability around XServer problems 2011-01-25 14:14:26 +0000 mike (714dd05) * * added -swcursor as XServer cmdline option 2011-01-25 10:38:57 +0000 mike (e1654ad) * * slightly modifying default XServer options (when used on Windows) 2011-01-22 19:51:30 +0000 mike (6a2e21f) * * creating config files in user's .x2goclient dir if they not already exist * added config files as examples 2011-01-22 14:51:53 +0000 mike (f305e1f) * * improved handling of network failures (i.e. when triggering disconnect events) 2011-01-22 11:53:33 +0000 mike (76ef55a) * * not explicitly passing the client_instance to the X2goTerminalSession instance 2011-01-22 11:52:36 +0000 mike (ccea5a2) * * making sure that an empty ,,export'' session option results in an empty list of shared folders 2011-01-22 09:11:12 +0000 mike (0343db2) * * fixing inheritance of session_instance * fixing unicode problem with folder names 2011-01-21 00:56:02 +0000 mike (94a1c67) * * trying lowercase for vcxsrv process name 2011-01-21 00:37:10 +0000 mike (4217dbf) * * being more generous with command timeouts (helpful on slow uplinks) * catching another exception 2011-01-20 23:33:10 +0000 mike (6c87cc5) * * fixed local folder sharing * renamed X2goTerminalSession.proxy_class to .proxy_backend * catching forwarding tunnel setup failures with an X2goSession.HOOK_* method 2011-01-20 23:28:21 +0000 mike (b54973e) * * experimentally adding VcXsrv configuration 2011-01-19 17:44:31 +0000 mike (e868f6e) * * fixed missing import line 2011-01-19 17:42:58 +0000 mike (a6607d9) * * stopping PulseAudio daemon on app shutdown 2011-01-19 17:40:46 +0000 mike (ffdec7f) * * built .deb packages Python X2go 0.0.35 PyHoca-GUI 0.0.26 2011-01-19 17:17:00 +0000 mike (736c25e) * * fixed soundsystem startup after Unicode adaptations 2011-01-19 14:36:00 +0000 mike (7394717) * * not daemonizing PulseAudio for Windows * rebuilt wininstaller for PyHoca-GUI 0.0.26-testing 2011-01-19 11:01:59 +0000 mike (21c8f1e) * * introduced first X2goSession.HOOK_* method * catching SSHException ,,TCP forward request denied'' as hook in rforward.py 2011-01-19 06:40:51 +0000 mike (b5333ae) * * trying to silence pulseaudio STDOUT 2011-01-18 22:09:27 +0000 mike (9b6ad6b) * * slight session for has_command() method 2011-01-18 20:15:49 +0000 mike (09728ed) * * fixed loading order of config files * daemonizing PulseAudio 2011-01-18 20:05:18 +0000 mike (3f31de1) * * fixed Unicode vs. string issue in nxproxy call 2011-01-18 19:19:17 +0000 mike (290a45a) * * fixed launching of generic X2go applications (WWWBROWSER, TERMINAL, ...) 2011-01-18 18:31:21 +0000 mike (58ece9e) * * fixed huge Unicode encoding issue when using i18n on Windows 2011-01-18 13:41:04 +0000 mike (84aaee3) * * fix for last commit 2011-01-18 13:38:49 +0000 mike (6afbdcf) * * changing to unicode when processing inifiles 2011-01-18 10:51:51 +0000 mike (0a1b275) * * incrementing version numbers... 2011-01-18 10:16:14 +0000 mike (37bd263) * * more work on Windows audio support 2011-01-18 09:44:39 +0000 mike (05dbe37) * * assuring that launched Xserver remains silent 2011-01-18 08:28:50 +0000 mike (203b7cb) * * Windows: adding support for launching a PulseAudio server 2011-01-17 00:03:26 +0000 mike (1327224) * * build .deb packages Python X2go 0.0.34 PyHoca-GUI 0.0.25 2011-01-16 21:22:25 +0000 mike (0eed5dc) * * typo fix 2011-01-16 21:18:53 +0000 mike (fa7770f) * * catching pulse audio failure on Windows * checking if self.client_instance exists before addressing it... 2011-01-16 21:00:52 +0000 mike (7037e4a) * * fixing path for Windows clients 2011-01-14 03:14:39 +0000 mike (a8c7281) * * built .deb packages - Python X2go 0.0.33 - PyHoca-GUI 0.0.24 2011-01-12 18:20:33 +0000 mike (8dfd1f3) * * fixed has_command() method for RDP-proxy sessions 2011-01-12 15:46:17 +0000 mike (9ecbb8f) * * catching commands that are not installed on X2go server as an X2goClient hook 2011-01-12 15:45:29 +0000 mike (98bc65e) * * catching exceptions that occur if port forwarding is not possible * allowing max. 50 tries if local bind port for X2go proxy is not available 2011-01-12 00:13:35 +0000 mike (9687ed3) * * commiting after .deb package build PyHoca-GUI 0.0.23 Python X2go 0.0.32 2011-01-10 18:15:57 +0000 mike (6a8ee17) * * fixes in _nx3 backend * added forgotten API documentation file * grabbing version from debian/changelog in Makefile.pre-debuild 2011-01-06 02:35:59 +0000 mike (dbea10b) * * forgotten check-in after last release 2011-01-06 02:31:37 +0000 mike (1347d69) * * presuming a patched x2goserver package while testing the X2goDropbox code 2011-01-06 00:41:15 +0000 mike (9b06fd4) * * fixed a rarely occurring exception when calling X2goControlSession.terminate() * changing the server name between connects now gets recognized * fixed link speed issue (was complete rubbish so far...) * thanks to Gerry Reno for testing 2011-01-05 17:54:53 +0000 mike (b76266a) * * added X2goDropbox support (local import of server-side files that are dropped in a DropBox folder 2011-01-05 11:29:24 +0000 mike (e3fa36a) * * happy new year... 2011-01-03 08:52:31 +0000 mike (baa434a) * * fixing connected status for auto-registered sessions in X2goSessionRegistry * now X2goSessionRegistry.update_status() can differentiate between ,,already running'' sessions (directly after connect and newly appearing running sessions while having been connected for quite a while * added ,,return_session_names'' to X2goSessionRegistry's enumerating methods * PyHoca-GUI: added ,,Transfer session'' menu item for running sessions that are not associated (i.e. running from within PyHoca-GUI) 2011-01-01 01:30:07 +0000 mike (af69549) * * fixing Linux compat for printactions 2011-01-01 01:06:18 +0000 mike (9950236) * * PDFVIEW printaction for MS Windows * PRINT (on default printer) action for MS Windows 2010-12-31 23:54:39 +0000 mike (219a8e7) * * implemented PDFVIEW printaction for windows 2010-12-31 23:27:10 +0000 mike (db512a5) * * typo fix 2010-12-31 23:25:16 +0000 mike (37e42dd) * * on Windows: catching print action error for PDFVIEW printaction when no PDF reader is installed * on Windows: catching an error in case the Xserver has died unexpectedly 2010-12-31 21:52:46 +0000 mike (7858034) * * release: Python X2go 0.0.30 PyHoca-GUI 0.0.20 2010-12-31 21:29:17 +0000 mike (ebba8f1) * * now really ignoring unknown session profile options 2010-12-31 21:18:23 +0000 mike (aa52dbb) * * really ignoring unknown session profile options now... 2010-12-31 21:12:25 +0000 mike (a3aeee6) * * added sshfs encoding support * introduced ,,useexports'' session profile option * added some utils functions 2010-12-31 17:31:27 +0000 mike (7c1a074) * * re-fixed local folder sharing on LInux * some minor changes about log messages 2010-12-31 15:19:25 +0000 mike (577aa38) * * removed debugging code 2010-12-31 15:17:54 +0000 mike (ffada7f) * * added support for local file sharing on Windows platforms 2010-12-31 12:48:31 +0000 mike (c5136ce) * * checking if SSH key file exists before setting up Paramiko/SSH connections 2010-12-31 12:38:32 +0000 mike (8a48d5b) * * fixing profilemanager issues * fixing add_to_known_hosts / known_hosts issues when using X2goProxySSH * bugfix release / Debian+Ubuntu package build: Python X2go 0.0.29 PyHoca-GUI 0.0.19 2010-12-31 12:24:14 +0000 mike (bbf9fa2) * * re-fixing profile manager for Linux/wxGTK * fixing sshproxy add_to_known_hosts problem 2010-12-31 01:57:47 +0000 mike (36dda74) * * making sure that known_hosts files exist before the file is loaded into paramiko.SSHClient instances 2010-12-31 01:20:56 +0000 mike (610fb42) * * built Debian/Ubuntu Packages - Python X2go 0.0.28 - PyHoca-GUI 0.0.18 - PyHoca-CLI 0.1.3 2010-12-30 23:08:52 +0000 mike (41cd50d) * * fixing handling of add_to_known_hosts and known_hosts parameters 2010-12-30 20:29:26 +0000 mike (2fff247) * * added XDMCP support 2010-12-30 18:58:03 +0000 mike (77c5ce1) * * explicitly disabled and cleaned up not yet implemented backends 2010-12-30 18:50:45 +0000 mike (83aac18) * * completed local folder sharing support * some menu changes * some notification changes 2010-12-30 16:52:06 +0000 mike (fff4c5d) * * pretty much stabilized SSH proxy support * found many bugs around paramiko / X2goControlSession * after server disconnect no more remaining open server or client ports * extended PyHoca-GUI password dialog (for SSH proxy credentials) 2010-12-30 06:21:04 +0000 mike (cb16e37) * * added very experimental sshproxy support (tunneling X2go sessions through a firewall host via SSH) 2010-12-30 03:49:55 +0000 mike (1e1f71b) * * more work on SSH proxy support (profile manager widgets) 2010-12-29 22:02:22 +0000 mike (09da9d4) * * fixed detection of died Paramiko/SSH sessions 2010-12-29 17:06:42 +0000 mike (1b39b26) * * added support for session params / profile setting updates while session is online * disabling profile manager for connected sessions * offering a ,,customize profile'' menu for connected sessions * added ,,share local folder'' menu item for connected sessions (not implemented yet) 2010-12-29 14:11:03 +0000 mike (c4b7015) * Python X2go 2010-12-20 15:28:40 +0000 mike (f630352) * * removed debug code 2010-12-20 15:27:19 +0000 mike (fcec1f1) * * fixes for Windows related code (backend implementation, portable rootdir implementation) 2010-12-20 13:57:14 +0000 mike (473fd70) * * PyHoca-GUI + Python X2go: portable application support now completed * Python X2go: backend support for client applications now completed 2010-12-18 23:42:21 +0000 mike (926ebbb) * * work on client portability (allow setting path variables from the command line) 2010-12-18 03:48:21 +0000 mike (3e9bd01) * * added HOOK_open_print_dialog * added printer icon from Gnome icon set * re-arranged folders and files * separated X2goPrintActionXXX and X2goPrintQueue into two different files 2010-12-17 21:27:06 +0000 mike (eb792d5) * * added command on gevent_subprocess copyright 2010-12-17 21:17:31 +0000 mike (f23de90) * * complete implementation of backends (now available as client options in PyHoca-GUI) * added PyHoca-GUI options that allow portable usage scenarios 2010-12-17 16:09:30 +0000 mike (f39ec24) * * rearranged directories and files * built Python X2go 0.0.25 (Linux) * built PyHoca-GUI 0.0.15 (Linux) 2010-12-17 15:06:05 +0000 mike (124af81) * * fixed epydoc build problems * removed debug code 2010-12-17 13:14:48 +0000 mike (120f7ca) * * renamed set_loglevel_none() method to set_loglevel_quiet() 2010-12-17 02:09:44 +0000 mike (e342b3a) * * fix 2010-12-17 02:03:27 +0000 mike (d1fd589) * * on Unix subprocess needs a shell=False, on Windows: shell=True * found out why pyhoca-gui left defunct nxproxy processes behind 2010-12-17 01:39:17 +0000 mike (d15600a) * * got rid of nxproxy DOS window (running nxproxy in shell) 2010-12-17 00:27:22 +0000 mike (8384119) * * introducing dummy stdin 2010-12-16 15:54:35 +0000 mike (12eee2f) * * minor fix for Linux version * removed debug output 2010-12-16 03:19:03 +0000 mike (5df89ce) * * fixing things around py2exe 2010-12-16 00:26:04 +0000 mike (056f7f1) * * working on py2exe build of PyHoca-GUI 2010-12-15 21:41:28 +0000 mike (67e7103) * * added nxproxy relevant sources from NOMACHINE 2010-12-15 21:37:42 +0000 mike (123ecb5) * * added NX proxy from NOMACHINE 2010-12-15 21:25:25 +0000 mike (3705d9e) * added nxproxy as contrib 2010-12-15 19:57:40 +0000 mike (ca49f12) * * fixed typo 2010-12-15 19:17:27 +0000 mike (0eb30cd) * * added --display option * XServer on MS Windows now starts on :40 screen number 2010-12-15 18:38:30 +0000 mike (e0fdd56) * * added setproctitle module (now the process name on Linux is pyhoca-gui, not python) * only one instance of pyhoca-gui is now allowed, starting a second instance will fail with a warning 2010-12-15 15:24:26 +0000 mike (7d7bb6b) * * fixing issues that came up while coding for MS Windows 2010-12-15 12:47:43 +0000 mike (9aef83f) * * on MS Windows PyHoca-GUI now launches Xming or Cygwin-X 2010-12-15 09:41:05 +0000 mike (441b857) * * added more method to xserver.py 2010-12-14 23:25:40 +0000 mike (da8d017) * * added methods that detect if a configured XServer is already running 2010-12-14 21:51:49 +0000 mike (41869db) * * bugfix commit 2010-12-14 21:43:19 +0000 mike (4590e1d) * * starting work on xserver.py (XServer detection and automatic startup on Windows systems) 2010-12-14 17:34:30 +0000 mike (68ced82) * * rebuild Python X2go API documentation 2010-12-14 13:09:32 +0000 mike (45beb31) * * rebuilt Python X2go (for v0.0.24) 2010-12-12 23:45:32 +0000 mike (638391e) * * reset NXCLIENT env var to /bin/true * introducing wx.lib.agw.balloontip as notification engine 2010-12-12 20:47:24 +0000 mike (d4095d0) * * nx3 proxy adaptations for MS Windows 2010-12-12 18:46:28 +0000 mike (5164340) * * added support for profile metatype information (analyzing session profile and summarizing the session profile type in a short string) 2010-12-12 16:51:45 +0000 mike (829d095) * * * re-compiled python-gevent 2010-12-12 16:46:53 +0000 mike (01598c8) * * catching a dead control session exception when updating the list sessions cache * changed X2GO_PASSWORD macro name for RDP/proxy sessions 2010-12-10 20:22:47 +0000 mike (caed97e) * * fixing pyhoca-gui .deb dependency * adding changelog entry for Python X2go 2010-12-10 20:20:29 +0000 mike (a786acc) * * add MS remote desktop (RDP) support 2010-12-10 12:37:20 +0000 mike (1226072) * * built Python X2go 0.0.22 2010-12-10 12:28:18 +0000 mike (7058128) * * fixed example scripts 2010-12-10 11:55:05 +0000 mike (3faad6a) * * adding more debug code to trace the parse_x2golistsessions issue 2010-12-10 11:39:01 +0000 mike (6fb22cf) * * desktop icon now working * reducing PyHoca-GUI CPU usage by factor 3 * code cleanup 2010-12-10 07:50:57 +0000 mike (cb2e61e) * * added missing files 2010-12-10 07:46:59 +0000 mike (048d4e1) * * prepared for intermediate release * updated documentation (not readable, full of errors, but epydoc builds doc again) * renamed icons' path * disabling options and profilemanager menu for release builds * next commit will add new files... 2010-12-09 19:37:01 +0000 mike (ed29b33) * * catching KeyError exception in cache.py, code was disabled for debugging... re-enabled now. 2010-12-09 10:45:36 +0000 mike (0355662) * * quite some work on stability and non-blocking I/O has been done... * greying out session profiles if a connect operation is in progress * changing taskbar icon alt text while connecting * fixing rev forwarding failure on repetetive session resume/suspend actions * server and network failures are not properly handled (client disconnects) 2010-12-08 15:21:29 +0000 mike (35ec074) * * there now is only one guardian thread (associated to the X2goClient instance) * there is only one big X2goListSessionCache (associated to the X2goClient instance) * the guardian thread does now update the X2goListSessionsCache * the guardian thread is now able to call HOOK_* methods in the X2goClient instance on notification events * X2goTerminaSession keep track of their sub-threads by themselves * session_summary() method added to X2goSessionRegistry * the ,,default'' option has been removed from X2goSessionProfile class 2010-12-07 22:56:50 +0000 mike (e3fe57d) * * changed loglevel of incoming print jobs to NOTICE 2010-12-07 17:28:17 +0000 mike (efa3c77) * * fixed many issues around authentication * started a session watching mechanism that detects what happens to sessions if session commands are not coming from this client * moved to BaseException as basis for X2go exceptions * added code that detects if the remote user may start X2go sessions 2010-12-07 00:40:38 +0000 mike (1e9e565) * * PyHocaGUI is now catching many errors/failures that might happen during a session 2010-12-06 20:54:49 +0000 mike (bcb1bc0) * * implemented X2goListSessionsCache, caching x2golistsessions information to make PyHocaGUI menus render more smoothly 2010-12-05 20:33:58 +0000 mike (6108168) * * now the session profile manager appears on ,,Add profile'' 2010-12-04 10:33:31 +0000 mike (86e65d4) * * added python-notify as debian dependency * reverting nxproxy.exe path to X2goClient path for now * (we have to ship our own nxproxy.exe) 2010-12-04 02:18:09 +0000 mike (470ed73) * * trying different nxproxy.exe on Windows OS 2010-12-03 23:56:50 +0000 mike (f3e977b) * * setting display variable for Windows OS 2010-12-03 23:12:05 +0000 mike (545184b) * * added SUPPORTED_* variables to defaults.py 2010-12-03 20:17:09 +0000 mike (ba2a2f8) * * fix for last fix 2010-12-03 20:12:52 +0000 mike (25b0e1c) * * fix for last commit 2010-12-03 20:09:42 +0000 mike (798c461) * * fixed session query methods 2010-12-03 03:11:28 +0000 mike (1998b07) * * checking in status quo, work goes on tomorrow 2010-12-02 22:16:55 +0000 mike (85f28c3) * * add-ons, new methods etc. needed for PyHocaGUI 2010-12-02 22:15:16 +0000 mike (9ce76c3) * * catching exception if user is not in x2goprint group 2010-12-02 22:14:01 +0000 mike (ae691be) * * added function that can sort session_names by age 2010-12-02 19:24:59 +0000 mike (2aeda62) * * fixing session resume * other bugfixes 2010-12-02 15:31:32 +0000 mike (d9715a4) * * passing proxy_backend through control session to terminal session 2010-12-02 14:16:15 +0000 mike (79a3456) * * big step: - split up X2goSession into X2goControlSession and X2goTerminalSession - established backend concept for control, terminal, info, proxy and profiles - several terminal sessions to the same server now share the same control session (i.e. the Paramiko/SSH transport) 2010-11-21 18:15:10 +0000 mike (2111f14) * * remove faulty method kwargs from method call 2010-11-21 16:18:31 +0000 mike (a065dc3) * * all nxproxy.exe path names have to be relative to NX_ROOT ENV variable * all paths have to start with a dotbackslash 2010-11-21 16:00:31 +0000 mike (ba8aaea) * * fixing options path for windows (nearly there...) 2010-11-21 15:57:23 +0000 mike (7f0b079) * * lost underscore 2010-11-21 15:55:49 +0000 mike (314d472) * * misspelled variable name on import 2010-11-21 15:54:03 +0000 mike (f498a46) * * trying to get nxproxy running on Windows, problematic path replacement in nxproxy.exe (relative path for options needed) 2010-11-21 15:22:07 +0000 mike (08ae756) * * using built-in open function to create proxy's options file 2010-11-21 15:17:23 +0000 mike (4b8cecd) * * trying to fix a weird TypeError on Windows 2010-11-21 15:11:04 +0000 mike (3cf4722) * * forgotten quotation marks 2010-11-21 15:09:37 +0000 mike (0557c89) * * fix for last commit 2010-11-21 15:07:30 +0000 mike (99b5cd7) * * proxy uses options file if nx options string is > 250 characters 2010-11-21 14:35:08 +0000 mike (d0258dd) * * added temp debug code 2010-11-21 14:19:40 +0000 mike (06780bb) * * another os.path.join bug 2010-11-21 14:17:29 +0000 mike (2212b03) * * fixed more os.path.join issues 2010-11-21 14:11:26 +0000 mike (7fc45e0) * * fix for last commit 2010-11-21 14:08:02 +0000 mike (3106c46) * * using os.path.join to generate local and remote session container path 2010-11-21 14:01:34 +0000 mike (f358d7e) * * fixed X2goPrintActionPDFVIEW call in _detect_print_action 2010-11-21 13:56:12 +0000 mike (0e24148) * * added SSH rootdir variable to Python X2go 2010-11-21 13:33:13 +0000 mike (9acddea) * * added support for retrieving current local user from x2go module 2010-11-21 13:22:11 +0000 mike (9f02d57) * * remove unused module pwd from sftpserver 2010-11-21 13:20:33 +0000 mike (12376de) * * forgot to remove an import line in printing.py 2010-11-21 13:18:51 +0000 mike (557ea72) * * added variable X2GOCLIENT_OS * not using gevent subprocess module on windows (as of lacking fcntl support on Windows) 2010-11-21 12:48:00 +0000 mike (3d0f0b6) * * removing .pyc files * added local socket detection for nx3 proxy subprocess * fixed an X2goException raisal 2010-11-20 12:30:44 +0000 mike (d0e7bbc) * * bugfix (removing test remnant test code) 2010-11-20 10:09:07 +0000 mike (07e7b87) * * modified license header for gevent_subprocess.py 2010-11-20 02:46:10 +0000 mike (134506d) * * added SSH/Paramiko disconnect support to Python X2go / pyhoca-gui 2010-11-19 22:26:44 +0000 mike (efa7e70) * * added new files (forgotten in last commnit) 2010-11-19 22:21:05 +0000 mike (3ba3834) * * changes for implementing PyHocaGUI - X2goProxy is now a sub-thread of X2goSession - many additional methods - fixed resume() in X2goRegisteredSession - etc. 2010-11-16 21:44:50 +0000 mike (fcb891c) * * __doc__ string writing... 2010-11-16 21:44:13 +0000 mike (df35eae) * * providing dirty fix for pyhoca-gui 2010-11-15 20:21:53 +0000 mike (3446edd) * * __doc__ writing 2010-11-15 14:45:57 +0000 dick (fd693f0) * Modified several defaults 2010-11-13 23:29:05 +0000 mike (c86fe24) * * checking for also for fuse group membership (throws an X2goSessionException) and x2goprint membership (silently ignored) 2010-11-13 22:59:19 +0000 dick (ae260e3) * several changes for robustness and addition of several extra functions 2010-11-13 21:34:39 +0000 mike (cce7282) * * little __doc__ string adaptation 2010-11-13 21:33:44 +0000 mike (2af3a94) * * allowing X2goLogger instances without tag name * change handling of tag slightly 2010-11-13 21:32:36 +0000 mike (c5da60f) * * moved session status declarations from X2goRegistry to X2goRegisteredSession * making sure that a success result is returned from connect/start/suspend/resume/terminate * swapping _doubleunderscore method names with their aliases 2010-11-13 21:30:22 +0000 mike (63c93d1) * * making sure that session start/resume/suspend/terminate action do return a success result * added checking of x2gousers membership on the remote server * sftp methods: remove loglevel from kwargs 2010-11-13 21:28:22 +0000 mike (78f0a40) * * adding session profile option override support (by register_session() kwargs) * many __doc__ strings rewrites * swapping of double __UNDERSCORE method names with their non-double-underscore aliases 2010-11-10 15:43:14 +0000 mike (2b25b15) * * more __doc__ fixes (on client.py) 2010-11-10 15:42:34 +0000 mike (afe8776) * * more __doc__ writing (on __init__.py) 2010-11-10 15:15:40 +0000 dick (d2fa372) * Several updates and corrections on X2go and updates on the gui 2010-11-10 14:50:55 +0000 mike (db9631e) * * improved error handling for crashing X2go sessions 2010-11-10 14:50:24 +0000 mike (846da07) * * wrote an introduction (beginning to rewrite __doc__ strings) 2010-11-09 22:00:09 +0000 mike (4b4fe7d) * * renamed X2goIniFiles.update() to X2goIniFiles.update_value() 2010-11-09 21:47:53 +0000 mike (aaeba00) * * moved several session relate methods into X2goRegisteredSession * X2goClient now contains wrapper methods for X2goRegisteredSession methods * added get_session_param(...) method to X2goSessionProfiles to convert config options to an X2goSession parameter individually 2010-11-09 09:14:37 +0000 mike (f4ff086) * * shuffling around with method names (@Dick: please take a look at the diffs) 2010-11-09 08:59:06 +0000 mike (507bf24) * * added method to acces an X2goClient's associated X2goClientSettings instance * added method to acces an X2goClient's associated X2goClientPrinting instance * added method to acces an X2goClient's associated X2goSessionProfiles instance 2010-11-09 08:51:48 +0000 mike (a2194be) * * added __double underscore method names, to make non-ambiguous method calls possible: ._X2goClient__ These method calls should be used in user applications 2010-11-09 08:49:10 +0000 mike (9e319fe) * * added cups-bsd or lpr as Debian package dependency 2010-11-08 17:35:27 +0000 mike (6607484) * * fixed CUPS printing (first time now that it works reliantly) 2010-11-08 16:26:28 +0000 mike (29b4c7f) * * fixed local folder sharing problem that occurred when the remote and the local user name were different 2010-11-07 01:17:34 +0000 mike (4bc5bfc) * * fixing __repr__ methods * adding X2goClientPrinting, X2goClientSettings and X2goSessionProfiles as public API classes 2010-11-07 00:32:51 +0000 mike (eff0612) * * basic session profile support added * still very BETA... * still missing features needed by PyHoca GUI * already functional with PyHoca CLI (-P option) 2010-11-06 15:01:29 +0000 mike (1838585) * * added forgotten new files (registry.py and inifiles.py) 2010-11-05 23:49:59 +0000 mike (ce98168) * * intermediate check-in as a preview for Dick 2010-11-01 18:36:52 +0000 mike (62cdc5c) * * removed double class X2goClientPrinting from settings.py * commented out development profiles code to make Python X2go usable again 2010-11-01 18:35:12 +0000 mike (3734cd9) * * using double underscores for core method names in class X2goClient 2010-10-28 22:12:02 +0000 mike (7084133) * * released an intermediate code state as 0.0.14, though work on X2go printing has not yet been finished * rebuilt API documentation * 0.0.14 is needed to run with PyHoca-Cli 0.1.0 2010-10-24 15:41:55 +0000 mike (dcb2544) * * restructuring X2go related work python-x2go-0.6.1.4/COPYING0000644000000000000000000010333014470264762012013 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 . python-x2go-0.6.1.4/docs/Makefile0000644000000000000000000001671714470264762013364 0ustar # Makefile for Sphinx documentation # # 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/PythonX2Go.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PythonX2Go.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/PythonX2Go" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PythonX2Go" @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." python-x2go-0.6.1.4/docs/source/conf.py0000644000000000000000000003031414470264762014510 0ustar # -*- coding: utf-8 -*- # # Python X2Go documentation build configuration file, created by # sphinx-quickstart on Mon Mar 5 21:36:47 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 x2go 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 = 'Python X2Go' copyright = '{year}, {author}'.format(year=date.today().year, author=x2go.__AUTHOR__) author = '{author}'.format(author=x2go.__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 = x2go.__VERSION__ # The full version, including alpha/beta/rc tags. release = x2go.__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'] # 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 (Client) 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', 'hu', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'ru', '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 = 'x2godoc' # -- 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, 'x2go.tex', 'Python X2Go (Client) API Documentation', 'Author', '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, 'x2go', 'Python X2Go (Client) 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, 'x2go', 'Python X2Go (Client) API Documentation', author, 'x2go', 'Module for easily writing X2Go Client applications in Python.', '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 python-x2go-0.6.1.4/docs/source/index.rst0000644000000000000000000002061014470264762015050 0ustar .. Python X2Go documentation master file, created by sphinx-quickstart on Mon Mar 5 21:36:47 2018. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to Python X2Go's documentation! ======================================= Python X2Go is a Python module that implements X2Go client support for the free X2Go Server project (http://wiki.x2go.org) in Python. Introduction ------------ With Python X2Go you can write your own X2Go clients or embed X2Go client features into already existing application environments. NOTE: Beginning with v0.4.0.0 of Python X2Go all class names start with X2Go***. Earlier versions used X2go***. As X2Go is the official name of the project (i.e. with a capital X and a capital G) we have adapted the class names to this circumstance. API Concept ----------- Python X2Go consists of quite a few classes. Furthermore, Python X2Go is quite heavily taking advantage of Python\'s threading features. When providing a library like Python X2Go, it is always quite a task to keep the library code compatible with former versions of the same library. This is intended for Python X2Go, but with some restraints. Python X2Go only offers five public API classes. With the release of version 0.1.0.0, we will try to keep these five public API classes of future releases as compatible as possible with versions of Python X2Go greater/equal than v0.1.0.0. The seven public API classes are: - :class:`x2go.client.X2GoClient` --- a whole X2Go client API - :class:`x2go.session.X2GoSession` --- management of an individual X2Go session--either started standalone or from within an :class:`x2go.client.X2GoClient` instance - :class:`x2go.backends.settings.file.X2GoClientSettings` --- provide access to X2Go (global and user) configuration node »settings« - :class:`x2go.backends.printing.file.X2GoClientPrinting` --- provide access to X2Go (global and user) configuration node »printing« - :class:`x2go.backends.profiles.file.X2GoSessionProfiles` --- provide access to X2Go (global and user) configuration node »sessions« - :class:`x2go.backends.profiles.httpbroker.X2GoSessionProfiles` --- provide API for obtaining session profiles from a http(s) based X2Go Session Broker - :class:`x2go.backends.profiles.sshbroker.X2GoSessionProfiles` --- provide API for obtaining session profiles from an SSH based X2Go Session Broker Plus two extra classes on MS Windows platforms: - :class:`x2go.xserver.X2GoClientXConfig` and :class:`x2go.xserver.X2GoXServer` --- these classes will be initialized during :class:`x2go.client.X2GoClient` instantiation on MS Windows platforms and start an installed XServer Any other of the Python X2Go classes may be subject to internal changes and the way of addressing these classes in code may vary between different versions of Python X2Go. If you directly use other than the five public API classes in your own applications, so please be warned. API Structure ------------- When using Python X2Go in your applications, the basic idea is that you create your own class and inherit the X2GoClient class in that:: import x2go class MyX2GoClient(x2go.X2GoClient): ... Python X2Go is capable of handling multiple running/suspended sessions within the same client instance, so for your application, there should not be any need of instantiating more than one :class:`x2go.client.X2GoClient` object in parallel. NOTE: Doing so is--herewith--fully disrecommended. The :class:`x2go.client.X2GoClient` class flattens the complex structure of Python X2Go into many :class:`x2go.client.X2GoClient` methods that you can use in your own ``MyX2GoClient`` instance. However, it might be handy to retrieve a whole X2Go session instance from the :class:`x2go.client.X2GoClient` instance. This can be achieved by the :func:`X2GoClient.register_session() ` method:: import x2go my_x2gocli = MyX2GoClient() reg_session_instance = my_x2gocli.register_session(, return_object=True) Whereas can be as simple as:: »profile_name=« or contain a whole set of :class:`x2go.session.X2GoSession` parameters that can be used to start a session manually (i.e. a session that is based on a pre-configured session profile in either of the »sessions« config files). The :func:`X2GoClient.register_session() ` method---in object-retrieval-mode---returns an :class:`x2go.session.X2GoSession` instance. With this instance you can then manage your X2Go session:: import gevent, getpass pw=getpass.getpass() # authenticate reg_session_instance.connect(password=pw, ) # then launch the session window with either a new session if start: reg_session_instance.start() # or resume a session if resume: reg_session_instance.resume(session_name=) # leave it runnint for 60 seconds gevent.sleep(60) # and then suspend if suspend: reg_session_instance.suspend() # or alternatively terminate it elif terminate: reg_session_instance.terminate() How to access---especially how to modify---the X2Go client configuration files »settings«, »printing«, »sessions« and »xconfig« (Windows only) is explained in detail with each class declaration in this API documentation. Please refer to the class docs of :class:`x2go.backends.settings.file.X2GoClientSettings`, :class:`x2go.backends.printing.file.X2GoClientPrinting`, :class:`x2go.backends.profiles.file.X2GoSessionProfiles` and :class:`x2go.xserver.X2GoXServer`. Configuration and Session Management ------------------------------------ Python X2Go strictly separates configuration management from session management. The classes needed for session management / administration are supposed to only gain »read access« to the classes handling the X2Go client configuration nodes. A configuration node in Python X2Go can be a file, a gconf database section, a section in the Windows registry, etc. NOTE: Each configuration node will be re-read whenever it is needed by an X2Go sesion or the X2GoClient class itself. Conclusively, any change to either of the configuration nodes will be reflected as a change in your X2Go client behaviour: - :class:`x2go.backends.profiles.file.X2GoSessionProfiles`: changes to a session profile in the »sessions« node will be available for the next registered :class:`x2go.session.X2GoSession` instance - :class:`x2go.backends.printing.file.X2GoClientPrinting`: on each incoming X2Go print job the »printing« configuration node will be re-read, thus you can change your X2Go client's print setup during a running session - :class:`x2go.backends.settings.file.X2GoClientSettings`: also the configuration node »settings« is re-read whenever needed in the course of X2Go session management - :class:`x2go.xserver.X2GoClientXConfig` and :class:`x2go.xserver.X2GoXServer` (Windows only): these classes will only be initialized once (starting the XServer on Windows platforms) on construction of an :class:`x2go.client.X2GoClient` instance Dependencies ------------ Python X2Go takes advantage of the libevent/greenlet implementation gevent (http://www.gevent.org). The least needed version of Python gevent is 0.13.0. On MS Windows Python gevent 1.0 is highly recommended. Python X2Go (because of gevent) requires at least Python 2.6 or Python 3.4. Further recent information on Python X2Go is available at: https://wiki.x2go.org/doku.php/wiki:libs:python-x2go Contact ------- If you have any questions concerning Python X2Go, please sign up for the x2go-dev list (https://lists.x2go.org/listinfo/x2go-dev) and post your questions, requests and feedbacks there. Contents -------- .. toctree:: :maxdepth: 4 x2go Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` python-x2go-0.6.1.4/docs/source/modules.rst0000644000000000000000000000006114470264762015407 0ustar x2go ==== .. toctree:: :maxdepth: 4 x2go python-x2go-0.6.1.4/docs/source/_static/.placeholder0000644000000000000000000000000014470264762017107 0ustar python-x2go-0.6.1.4/docs/source/_templates/.placeholder0000644000000000000000000000000014470264762017616 0ustar python-x2go-0.6.1.4/docs/source/x2go.backends.control.plain.rst0000644000000000000000000000025414470264762021154 0ustar x2go.backends.control.plain module ================================== .. automodule:: x2go.backends.control.plain :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.backends.control.rst0000644000000000000000000000040214470264762020045 0ustar x2go.backends.control package ============================= Submodules ---------- .. toctree:: x2go.backends.control.plain Module contents --------------- .. automodule:: x2go.backends.control :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.backends.info.plain.rst0000644000000000000000000000024314470264762020425 0ustar x2go.backends.info.plain module =============================== .. automodule:: x2go.backends.info.plain :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.backends.info.rst0000644000000000000000000000036614470264762017331 0ustar x2go.backends.info package ========================== Submodules ---------- .. toctree:: x2go.backends.info.plain Module contents --------------- .. automodule:: x2go.backends.info :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.backends.printing.file.rst0000644000000000000000000000025414470264762021142 0ustar x2go.backends.printing.file module ================================== .. automodule:: x2go.backends.printing.file :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.backends.printing.rst0000644000000000000000000000040514470264762020222 0ustar x2go.backends.printing package ============================== Submodules ---------- .. toctree:: x2go.backends.printing.file Module contents --------------- .. automodule:: x2go.backends.printing :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.backends.profiles.base.rst0000644000000000000000000000025414470264762021126 0ustar x2go.backends.profiles.base module ================================== .. automodule:: x2go.backends.profiles.base :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.backends.profiles.file.rst0000644000000000000000000000025414470264762021133 0ustar x2go.backends.profiles.file module ================================== .. automodule:: x2go.backends.profiles.file :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.backends.profiles.httpbroker.rst0000644000000000000000000000027614470264762022404 0ustar x2go.backends.profiles.httpbroker module ======================================== .. automodule:: x2go.backends.profiles.httpbroker :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.backends.profiles.rst0000644000000000000000000000055514470264762020221 0ustar x2go.backends.profiles package ============================== Submodules ---------- .. toctree:: x2go.backends.profiles.base x2go.backends.profiles.file x2go.backends.profiles.httpbroker x2go.backends.profiles.sshbroker Module contents --------------- .. automodule:: x2go.backends.profiles :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.backends.profiles.sshbroker.rst0000644000000000000000000000027314470264762022217 0ustar x2go.backends.profiles.sshbroker module ======================================= .. automodule:: x2go.backends.profiles.sshbroker :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.backends.proxy.base.rst0000644000000000000000000000024314470264762020462 0ustar x2go.backends.proxy.base module =============================== .. automodule:: x2go.backends.proxy.base :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.backends.proxy.nx3.rst0000644000000000000000000000024014470264762020255 0ustar x2go.backends.proxy.nx3 module ============================== .. automodule:: x2go.backends.proxy.nx3 :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.backends.proxy.rst0000644000000000000000000000042414470264762017552 0ustar x2go.backends.proxy package =========================== Submodules ---------- .. toctree:: x2go.backends.proxy.base x2go.backends.proxy.nx3 Module contents --------------- .. automodule:: x2go.backends.proxy :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.backends.rst0000644000000000000000000000060214470264762016370 0ustar x2go.backends package ===================== Subpackages ----------- .. toctree:: x2go.backends.control x2go.backends.info x2go.backends.printing x2go.backends.profiles x2go.backends.proxy x2go.backends.settings x2go.backends.terminal Module contents --------------- .. automodule:: x2go.backends :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.backends.settings.file.rst0000644000000000000000000000025414470264762021150 0ustar x2go.backends.settings.file module ================================== .. automodule:: x2go.backends.settings.file :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.backends.settings.rst0000644000000000000000000000040514470264762020230 0ustar x2go.backends.settings package ============================== Submodules ---------- .. toctree:: x2go.backends.settings.file Module contents --------------- .. automodule:: x2go.backends.settings :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.backends.terminal.plain.rst0000644000000000000000000000025714470264762021312 0ustar x2go.backends.terminal.plain module =================================== .. automodule:: x2go.backends.terminal.plain :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.backends.terminal.rst0000644000000000000000000000040614470264762020204 0ustar x2go.backends.terminal package ============================== Submodules ---------- .. toctree:: x2go.backends.terminal.plain Module contents --------------- .. automodule:: x2go.backends.terminal :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.cache.rst0000644000000000000000000000017114470264762015662 0ustar x2go.cache module ================= .. automodule:: x2go.cache :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.checkhosts.rst0000644000000000000000000000021014470264762016747 0ustar x2go.checkhosts module ====================== .. automodule:: x2go.checkhosts :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.cleanup.rst0000644000000000000000000000017714470264762016254 0ustar x2go.cleanup module =================== .. automodule:: x2go.cleanup :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.client.rst0000644000000000000000000000017414470264762016100 0ustar x2go.client module ================== .. automodule:: x2go.client :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.defaults.rst0000644000000000000000000000020214470264762016421 0ustar x2go.defaults module ==================== .. automodule:: x2go.defaults :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.forward.rst0000644000000000000000000000017714470264762016271 0ustar x2go.forward module =================== .. automodule:: x2go.forward :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.gevent_subprocess.rst0000644000000000000000000000023514470264762020360 0ustar x2go.gevent_subprocess module ============================= .. automodule:: x2go.gevent_subprocess :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.guardian.rst0000644000000000000000000000020214470264762016404 0ustar x2go.guardian module ==================== .. automodule:: x2go.guardian :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.inifiles.rst0000644000000000000000000000020214470264762016414 0ustar x2go.inifiles module ==================== .. automodule:: x2go.inifiles :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.log.rst0000644000000000000000000000016314470264762015401 0ustar x2go.log module =============== .. automodule:: x2go.log :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.mimeboxactions.rst0000644000000000000000000000022414470264762017637 0ustar x2go.mimeboxactions module ========================== .. automodule:: x2go.mimeboxactions :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.mimebox.rst0000644000000000000000000000017714470264762016265 0ustar x2go.mimebox module =================== .. automodule:: x2go.mimebox :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.printactions.rst0000644000000000000000000000021614470264762017334 0ustar x2go.printactions module ======================== .. automodule:: x2go.printactions :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.printqueue.rst0000644000000000000000000000021014470264762017012 0ustar x2go.printqueue module ====================== .. automodule:: x2go.printqueue :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.pulseaudio.rst0000644000000000000000000000021014470264762016763 0ustar x2go.pulseaudio module ====================== .. automodule:: x2go.pulseaudio :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.registry.rst0000644000000000000000000000020214470264762016462 0ustar x2go.registry module ==================== .. automodule:: x2go.registry :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.rforward.rst0000644000000000000000000000020214470264762016440 0ustar x2go.rforward module ==================== .. automodule:: x2go.rforward :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.rst0000644000000000000000000000122514470264762014621 0ustar x2go package ============ Subpackages ----------- .. toctree:: x2go.backends Submodules ---------- .. toctree:: x2go.cache x2go.checkhosts x2go.cleanup x2go.client x2go.defaults x2go.forward x2go.gevent_subprocess x2go.guardian x2go.inifiles x2go.log x2go.mimebox x2go.mimeboxactions x2go.printactions x2go.printqueue x2go.pulseaudio x2go.registry x2go.rforward x2go.session x2go.sftpserver x2go.sshproxy x2go.telekinesis x2go.utils x2go.x2go_exceptions x2go.xserver Module contents --------------- .. automodule:: x2go :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.session.rst0000644000000000000000000000017714470264762016310 0ustar x2go.session module =================== .. automodule:: x2go.session :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.sftpserver.rst0000644000000000000000000000021014470264762017014 0ustar x2go.sftpserver module ====================== .. automodule:: x2go.sftpserver :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.sshproxy.rst0000644000000000000000000000020214470264762016511 0ustar x2go.sshproxy module ==================== .. automodule:: x2go.sshproxy :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.telekinesis.rst0000644000000000000000000000021314470264762017133 0ustar x2go.telekinesis module ======================= .. automodule:: x2go.telekinesis :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.utils.rst0000644000000000000000000000017114470264762015757 0ustar x2go.utils module ================= .. automodule:: x2go.utils :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.x2go_exceptions.rst0000644000000000000000000000022714470264762017741 0ustar x2go.x2go_exceptions module =========================== .. automodule:: x2go.x2go_exceptions :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/docs/source/x2go.xserver.rst0000644000000000000000000000017714470264762016323 0ustar x2go.xserver module =================== .. automodule:: x2go.xserver :members: :undoc-members: :show-inheritance: python-x2go-0.6.1.4/examples/config/printing.DIALOG0000644000000000000000000000026614470264762016562 0ustar [General] showdialog=true pdfview=false [print] startcmd=false command=lpr stdin=false ps=false [save] folder = PDF [view] open=true command=xpdf [CUPS] defaultprinter=myprinter python-x2go-0.6.1.4/examples/config/printing.PDFSAVE0000644000000000000000000000026714470264762016714 0ustar [General] showdialog=false pdfview=true [print] startcmd=false command=lpr stdin=false ps=false [save] folder = PDF [view] open=false command=xpdf [CUPS] defaultprinter=myprinter python-x2go-0.6.1.4/examples/config/printing.PDFVIEW0000644000000000000000000000027214470264762016724 0ustar [General] showdialog=false pdfview=true [print] startcmd=false command=lpr stdin=false ps=false [save] folder = PDF [view] open=true command=acroread [CUPS] defaultprinter=myprinter python-x2go-0.6.1.4/examples/config/printing.PRINT0000644000000000000000000000027014470264762016512 0ustar [General] showdialog=false pdfview=false [print] startcmd=false command=lpr stdin=false ps=false [save] folder = PDF [view] open=false command=xpdf [CUPS] defaultprinter=myprinter python-x2go-0.6.1.4/examples/config/printing.PRINTCMD0000644000000000000000000000026614470264762017043 0ustar [General] showdialog=false pdfview=false [print] startcmd=true command=lpr stdin=false ps=false [save] folder = PDF [view] open=true command=xpdf [CUPS] defaultprinter=myprinter python-x2go-0.6.1.4/examples/x2go_resume_session.py0000644000000000000000000000357414470264762017163 0ustar #!/usr/bin/python # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. ### ### short example for python-x2go usage ### # import x2go before you import other thread based modules (e.g. paramiko) import x2go import sys import getpass import gevent # modify to your needs... server = "server.mydomain.tld" port = 22 username = "foo" password = getpass.getpass() cli = x2go.X2GoClient(use_cache=False, loglevel=x2go.log.loglevel_DEBUG) s_uuid = cli.register_session(server, port=port, username=username, geometry="800x600", add_to_known_hosts=True) cli.connect_session(s_uuid, password=password) # the next lines will resume the first session in the avail_sessions dictionary avail_sessions = cli.list_sessions(s_uuid) if avail_sessions: session_info = list(avail_sessions.values())[0] sys.stdout.write("Trying to resume session %s\n" % (session_info.name,)) cli.resume_session(s_uuid, session_name=session_info.name) try: while cli.session_ok(s_uuid): gevent.sleep(2) except KeyboardInterrupt: pass cli.terminate_session(s_uuid) else: print("No sessions to be resumed.") python-x2go-0.6.1.4/examples/x2go_start_session.py0000644000000000000000000000336314470264762017014 0ustar #!/usr/bin/python # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. ### ### short example for python-phoca usage ### # import x2go before you import other thread based modules (e.g. paramiko) import x2go import gevent import getpass # modify to your needs... server = "server.mydomain.tld" port = 22 username = "foo" command = "XFCE" password = getpass.getpass() cli = x2go.X2GoClient(use_cache=False, loglevel=x2go.log.loglevel_DEBUG) s_uuid = cli.register_session(server, port=port, username=username, cmd=command, add_to_known_hosts=True, ) cli.connect_session(s_uuid, password=password) # clean sessions and check the result cli.clean_sessions(s_uuid) # start the session and run a command cli.start_session(s_uuid) try: while cli.session_ok(s_uuid): gevent.sleep(2) except KeyboardInterrupt: pass # suspend the session cli.suspend_session(s_uuid) python-x2go-0.6.1.4/examples/x2go_start_session_with_progress_status.py0000644000000000000000000000464614470264762023403 0ustar #!/usr/bin/python # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. ### ### short example for python-phoca usage ### # import x2go before you import other thread based modules (e.g. paramiko) import x2go import gevent import getpass import threading # modify to your needs... server = "server.mydomain.tld" port = 22 username = "foo" command = "XFCE" def my_progress_bar(ps): for status in ps: print('---------------') print('SESSION STATUS: ' + '#' * status + "(" + str(status) + "%%)") print('---------------') password = getpass.getpass() cli = x2go.X2GoClient(use_cache=False, loglevel=x2go.log.loglevel_DEBUG) s_uuid = cli.register_session(server, port=port, username=username, cmd=command, add_to_known_hosts=True, ) cli.connect_session(s_uuid, password=password) # clean sessions and check the result cli.clean_sessions(s_uuid) # initialize a ProgressStatus event and iterator progress_event = threading.Event() progress_status = x2go.utils.ProgressStatus(progress_event, cli.get_session(s_uuid).get_progress_status) # start the status bar gevent.spawn(my_progress_bar, progress_status) # start the session gevent.spawn(cli.start_session, s_uuid, progress_event=progress_event) # wait long enough for session to come up completely while (cli.get_session(s_uuid).get_progress_status() < 100) and (cli.get_session(s_uuid).get_progress_status() != -1): gevent.sleep(1) try: while cli.session_ok(s_uuid): gevent.sleep(2) except KeyboardInterrupt: pass # suspend the session cli.suspend_session(s_uuid) python-x2go-0.6.1.4/Makefile.docupload0000644000000000000000000000131214470264762014366 0ustar #!/usr/bin/make -f # Makefile.docupload file - for python-x2go # Copyright 2010-2023 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/python-x2go DOC_USER=x2go-admin SPHINX_APIDOC=sphinx-apidoc doc: docbuild docupload clean: ${MAKE} -C docs/ clean apidoc: $(SPHINX_APIDOC) -f -e -o docs/source/ x2go 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}/ python-x2go-0.6.1.4/README0000644000000000000000000001165514470264762011650 0ustar python-x2go - Copyright (C) 2010-2023 by Mike Gabriel Published under the terms of the GNU Affero General Public License. See http://www.gnu.org/licenses/agpl.html for a recent copy. === What is Python X2Go? === The Python X2Go module integrates X2Go client support into your python programmes. Python X2Go is used in the headless X2Go client »pyhoca-cli« and by the tiny systray GUI applet »pyhoca-gui« Python X2Go takes advantages of the NX Proxy by NoMachine published under GPL. On Windows and MacOS X systems you have to make sure that you have installed a current NX Proxy executable somewhere on your system. It probably will be sufficient to have the X2Go project's X2Go Client application installed on your client machine. On Debian / Ubuntu systems there is an nxproxy package available within the distribution. Python X2Go defaults to using the distro nxproxy binary. Python X2Go was originally inspired by work of Jörg Sawatzki . Unfortunately, Jörg had to give up work on his ideas due to licensing issues with his contractor. To make his ideas furthermore available to the OSS community this complete rewrite of Jörg's ideas has been called into life. In case you have the opinion that parts of the presented code are not as much a rewrite as they should be, may I ask you to contact me directly and in person. I am sure, things can be sorted out in a non-complicated and heartful fashion. Thanks in advance for your directness and wholeheartedness concerning this inner process. === Requirements === * X2Go Server - you need a working X2Go server to connect to. In order to use shadowing or mTelePlayer make sure you have at least X2Go Server (>= 4.1.0.0) installed * on the client system you will need - the python-gevent library (for details refer to: http://www.gevent.org/) - the python-paramiko library (SSH implementation in Python, http://www.lag.net/paramiko/) - NX Proxy for non-Debianized systems * further recommendations about printing on Windows systems - optimal: install Ghostscript and GSView in the default paths. If these tools are installed they will be used for printing. Further infos and downloads: http://www.ghostscript.com/ - also working: if no Ghostscript/GSView is installed the win32api ,,print'' command will be executed on incoming PDF print spool files. Win32api will try launch the print function of the default PDF viewer application (mostly Adobe Acrobat Reader) - if you have a choice: install Ghostscript/GSview on the system that uses Python X2Go applications... it's highly recommended === Current features === * start X2Go agent session * suspend X2Go session * resume X2Go session * terminate X2Go session * clean user's X2Go sessions * list user's X2Go sessions * use Pulse Audio sound * X2Go printing * reading/writing session profiles from file * sharing of local (client-side) folders (SFTP server is integrated in Python X2Go) * connect via proxy SSH server * X2Go MIME box support * color depth auto-recognition * X2Go desktop sharing support * X2Go published applications support * Session window re-titling for desktop and shared desktop sessions * X2Go Session Brokerage (HTTP or HTTPS) * Telekinesis Client support included (required for multimedia support inside the X2Go session using mTelePlayer as media player) === Installation === Ubuntu: ------- We use Launchpad for Ubuntu packaging: $ add-apt-repository ppa:x2go/stable $ apt-get update $ apt-get install python-x2go From Ubuntu oneiric on python-x2go is also available as a part of the Ubuntu GNU/Linux distribution. Debian: ------- create /etc/apt/sources.list.d/x2go.list and put the following line into it: deb http://packages.x2go.org/debian {squeeze|wheezy|sid} main Then run the following commands as super-user root: $ apt-get update $ apt-get install python-x2go Windos: ------- Testing and writing a short howto for this section is very welcome. MacOS X: -------- Testing and writing a short howto for this section is very welcome. === How to use it from the command line? === A good example for the usage of Python X2Go is the pyhoca-cli programme, a headless X2Go client that aims at 100% compatibility with X2Go Client applications released in the X2Go project. On Debian/Ubuntu you can easily install the X2Go Client by running APT again: $ apt-get install pyhoca-cli === How to use it with a GUI? === There is also a Python GUI application called PyHoca-GUI. PyHoca-GUI is an applet that docks into your desktop's system tray. $ apt-get install pyhoca-gui === Repository and Feedback === Before giving feedback take a look at http://git.x2go.org/ (project: python-x2go.git) and check if your issues have already been solved in the HEAD of python-x2go code. For now, bugs can be reported via mail to mike.gabriel@das-netzwerkteam.de light+love, 20142010 Mike Gabriel python-x2go-0.6.1.4/README.Trinity-Desktop0000644000000000000000000000070114470264762014706 0ustar Trinity Deskop (KDE3.5 fork) and (Python) X2Go ============================================== Python X2Go brings support to start Trinity Desktops on remote X2Go servers. For this to work let a symbolic link on the X2Go server point from /usr/local/bin/starttrinity to Trinity's startkde script: root@x2goserver:~ ln -s /opt/trinity/bin/startkde /usr/local/bin/starttrinity light+love, 20110527 Mike Gabriel python-x2go-0.6.1.4/setup.py0000755000000000000000000000341214470264762012475 0ustar #!/usr/bin/env python3 # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. import os from setuptools import setup, find_packages # silence pyflakes, the correct __VERSION__ will be detected below... __VERSION__ = "0.0.0.0" try: # for python3.x for line in open(os.path.join('x2go', '__init__.py'),encoding='utf-8').readlines(): if (line.startswith('__VERSION__')): exec(line.strip()) except TypeError: # for older python2.x versions for line in file(os.path.join('x2go', '__init__.py')).readlines(): if (line.startswith('__VERSION__')): exec(line.strip()) MODULE_VERSION = __VERSION__ setup( name = "x2go", version = MODULE_VERSION, description = "Python X2Go implements an X2Go client/session library in Python based on the Python Paramiko SSH module.", license = 'AGPLv3+', author = 'Mike Gabriel', url = 'http://www.x2go.org', packages = find_packages('.'), package_dir = {'': '.'}, install_requires = ['gevent', 'paramiko', ] ) python-x2go-0.6.1.4/TODO0000644000000000000000000000150414470264762011450 0ustar python-x2go - Copyright (C) 2010-2023 by Mike Gabriel Published under the terms of the GNU Affero General Public License. See http://www.gnu.org/licenses/agpl.html for a recent copy. === python-x2go TODOs === as of 20141020 -------------- * add SSH broker support * drop Python Paramiko and wrap around openSSH * become ready for X2Go Server 5 (openSSH with socket tunnel endpoints plus event based session management) * low priority: add Windows Registry backend (session profiles, client configs) * even lower priority: add gconf backend (session profiles, client configs) === Python X2Go wishlist === * SOCKS client support To report bugs and ideas, please add your contributions and comments on http://code.x2go.org/ light+love, 20141020 Mike Gabriel python-x2go-0.6.1.4/x2go/backends/control/__init__.py0000644000000000000000000000160714470264762017206 0ustar # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. __package__ = 'x2go.backends.control' __name__ = 'x2go.backends.control' python-x2go-0.6.1.4/x2go/backends/control/plain.py0000644000000000000000000026261514470264762016562 0ustar # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. """\ :class:`x2go.backends.control.plain.X2GoControlSession` class - core functions for handling your individual X2Go sessions. This backend handles X2Go server implementations that respond via server-side PLAIN text output. """ __NAME__ = 'x2gocontrolsession-pylib' __package__ = 'x2go.backends.control' __name__ = 'x2go.backends.control.plain' # modules import os import sys import types import paramiko import gevent import copy import string import random import re import locale import threading import io import base64 import uuid from gevent import socket # Python X2Go modules import x2go.sshproxy as sshproxy import x2go.log as log import x2go.utils as utils import x2go.x2go_exceptions as x2go_exceptions import x2go.defaults as defaults import x2go.checkhosts as checkhosts from x2go.defaults import BACKENDS as _BACKENDS import x2go._paramiko x2go._paramiko.monkey_patch_paramiko() def _rerewrite_blanks(cmd): """\ In command strings X2Go server scripts expect blanks being rewritten to ,,X2GO_SPACE_CHAR''. Commands get rewritten in the terminal sessions. This re-rewrite function helps displaying command string in log output. :param cmd: command that has to be rewritten for log output :type cmd: ``str`` :returns: the command with ,,X2GO_SPACE_CHAR'' re-replaced by blanks :rtype: ``str`` """ # X2Go run command replace X2GO_SPACE_CHAR string with blanks if cmd: cmd = cmd.replace("X2GO_SPACE_CHAR", " ") return cmd def _rewrite_password(cmd, user=None, password=None): """\ In command strings Python X2Go replaces some macros with actual values: - X2GO_USER -> the user name under which the user is authenticated via SSH - X2GO_PASSWORD -> the password being used for SSH authentication Both macros can be used to on-the-fly authenticate via RDP. :param cmd: command that is to be sent to an X2Go server script :type cmd: ``str`` :param user: the SSH authenticated user name (Default value = None) :type user: ``str`` :param password: the password being used for SSH authentication (Default value = None) :type password: ``str`` :returns: the command with macros replaced :rtype: ``str`` """ # if there is a ,,-u X2GO_USER'' parameter in RDP options then we will replace # it by our X2Go session password if cmd and user: cmd = cmd.replace('X2GO_USER', user) # if there is a ,,-p X2GO_PASSWORD'' parameter in RDP options then we will replace # it by our X2Go session password if cmd and password: cmd = cmd.replace('X2GO_PASSWORD', password) return cmd class X2GoControlSession(paramiko.SSHClient): """\ In the Python X2Go concept, X2Go sessions fall into two parts: a control session and one to many terminal sessions. The control session handles the SSH based communication between server and client. It is mainly derived from ``paramiko.SSHClient`` and adds on X2Go related functionality. """ def __init__(self, profile_name='UNKNOWN', add_to_known_hosts=False, known_hosts=None, forward_sshagent=False, unique_hostkey_aliases=False, terminal_backend=_BACKENDS['X2GoTerminalSession']['default'], info_backend=_BACKENDS['X2GoServerSessionInfo']['default'], list_backend=_BACKENDS['X2GoServerSessionList']['default'], proxy_backend=_BACKENDS['X2GoProxy']['default'], client_rootdir=os.path.join(defaults.LOCAL_HOME, defaults.X2GO_CLIENT_ROOTDIR), sessions_rootdir=os.path.join(defaults.LOCAL_HOME, defaults.X2GO_SESSIONS_ROOTDIR), ssh_rootdir=os.path.join(defaults.LOCAL_HOME, defaults.X2GO_SSH_ROOTDIR), logger=None, loglevel=log.loglevel_DEFAULT, published_applications_no_submenus=0, low_latency=False, **kwargs): """\ Initialize an X2Go control session. For each connected session profile there will be one SSH-based control session and one to many terminal sessions that all server-client-communicate via this one common control session. A control session normally gets set up by an :class:`x2go.session.X2GoSession` instance. Do not use it directly!!! :param profile_name: the profile name of the session profile this control session works for :type profile_name: ``str`` :param add_to_known_hosts: Auto-accept server host validity? :type add_to_known_hosts: ``bool`` :param known_hosts: the underlying Paramiko/SSH systems ``known_hosts`` file :type known_hosts: ``str`` :param forward_sshagent: forward SSH agent authentication requests to the X2Go client-side :type forward_sshagent: ``bool`` :param unique_hostkey_aliases: instead of storing []: in known_hosts file, use the (unique-by-design) profile ID :type unique_hostkey_aliases: ``bool`` :param terminal_backend: X2Go terminal session backend to use :type terminal_backend: ``str`` :param info_backend: backend for handling storage of server session information :type info_backend: ``X2GoServerSessionInfo*`` instance :param list_backend: backend for handling storage of session list information :type list_backend: ``X2GoServerSessionList*`` instance :param proxy_backend: backend for handling the X-proxy connections :type proxy_backend: ``X2GoProxy*`` instance :param client_rootdir: client base dir (default: ~/.x2goclient) :type client_rootdir: ``str`` :param sessions_rootdir: sessions base dir (default: ~/.x2go) :type sessions_rootdir: ``str`` :param ssh_rootdir: ssh base dir (default: ~/.ssh) :type ssh_rootdir: ``str`` :param published_applications_no_submenus: published applications menus with less items than ``published_applications_no_submenus`` are rendered without submenus :type published_applications_no_submenus: ``int`` :param logger: you can pass an :class:`x2go.log.X2GoLogger` object to the :class:`x2go.backends.control.plain.X2GoControlSession` constructor :type logger: :class:`x2go.log.X2GoLogger` instance :param loglevel: if no :class:`x2go.log.X2GoLogger` object has been supplied a new one will be constructed with the given loglevel :type loglevel: ``int`` :param low_latency: set this boolean switch for weak connections, it will double all timeout values. :type low_latency: ``bool`` :param kwargs: catch any non-defined parameters in ``kwargs`` :type kwargs: ``dict`` """ self.associated_terminals = {} self.terminated_terminals = [] self.profile_name = profile_name self.add_to_known_hosts = add_to_known_hosts self.known_hosts = known_hosts self.forward_sshagent = forward_sshagent self.unique_hostkey_aliases = unique_hostkey_aliases self.hostname = None self.port = None self.sshproxy_session = None self._session_auth_rsakey = None self._remote_home = None self._remote_group = {} self._remote_username = None self._remote_peername = None self._server_versions = None self._server_features = None if logger is None: self.logger = log.X2GoLogger(loglevel=loglevel) else: self.logger = copy.deepcopy(logger) self.logger.tag = __NAME__ self._terminal_backend = terminal_backend self._info_backend = info_backend self._list_backend = list_backend self._proxy_backend = proxy_backend self.client_rootdir = client_rootdir self.sessions_rootdir = sessions_rootdir self.ssh_rootdir = ssh_rootdir self._published_applications_menu = {} self.agent_chan = None self.agent_handler = None paramiko.SSHClient.__init__(self) if self.add_to_known_hosts: self.set_missing_host_key_policy(paramiko.AutoAddPolicy()) self.session_died = False self.low_latency = low_latency self.published_applications_no_submenus = published_applications_no_submenus self._already_querying_published_applications = threading.Lock() self._transport_lock = threading.Lock() def get_hostname(self): """\ Get the hostname as stored in the properties of this control session. :returns: the hostname of the connected X2Go server :rtype: ``str`` """ return self.hostname def get_port(self): """\ Get the port number of the SSH connection as stored in the properties of this control session. :returns: the server-side port number of the control session's SSH connection :rtype: ``str`` """ return self.port def load_session_host_keys(self): """\ Load known SSH host keys from the ``known_hosts`` file. If the file does not exist, create it first. """ if self.known_hosts is not None: utils.touch_file(self.known_hosts) self.load_host_keys(self.known_hosts) def __del__(self): """\ On instance descruction, do a proper session disconnect from the server. """ self.disconnect() def test_sftpclient(self): ssh_transport = self.get_transport() try: self.sftp_client = paramiko.SFTPClient.from_transport(ssh_transport) except (AttributeError, paramiko.SFTPError): raise x2go_exceptions.X2GoSFTPClientException('failed to initialize SFTP channel') def _x2go_sftp_put(self, local_path, remote_path, timeout=20): """\ Put a local file on the remote server via sFTP. During sFTP operations, remote command execution gets blocked. :param local_path: full local path name of the file to be put on the server :type local_path: ``str`` :param remote_path: full remote path name of the server-side target location, path names have to be Unix-compliant :type remote_path: ``str`` :param timeout: this SFTP put action should not take longer then the given value (Default value = 20) :type timeout: ``int`` :raises X2GoControlSessionException: if the SSH connection dropped out """ ssh_transport = self.get_transport() self._transport_lock.acquire() if ssh_transport and ssh_transport.is_authenticated(): self.logger('sFTP-put: %s -> %s:%s' % (os.path.normpath(local_path), self.remote_peername(), remote_path), loglevel=log.loglevel_DEBUG) if self.low_latency: timeout = timeout * 2 timer = gevent.Timeout(timeout) timer.start() try: try: self.sftp_client = paramiko.SFTPClient.from_transport(ssh_transport) except paramiko.SFTPError: self._transport_lock.release() raise x2go_exceptions.X2GoSFTPClientException('failed to initialize SFTP channel') try: self.sftp_client.put(os.path.normpath(local_path), remote_path) except (x2go_exceptions.SSHException, socket.error, IOError): # react to connection dropped error for SSH connections self.session_died = True self._transport_lock.release() raise x2go_exceptions.X2GoControlSessionException('The SSH connection was dropped during an sFTP put action.') except gevent.timeout.Timeout: self.session_died = True self._transport_lock.release() if self.sshproxy_session: self.sshproxy_session.stop_thread() raise x2go_exceptions.X2GoControlSessionException('the X2Go control session timed out during an SFTP write command') finally: timer.cancel() self.sftp_client = None if self._transport_lock.locked(): self._transport_lock.release() def _x2go_sftp_write(self, remote_path, content, timeout=20): """\ Create a text file on the remote server via sFTP. During sFTP operations, remote command execution gets blocked. :param remote_path: full remote path name of the server-side target location, path names have to be Unix-compliant :type remote_path: ``str`` :param content: a text file, multi-line files use Unix-link EOL style :type content: ``str`` :param timeout: this SFTP write action should not take longer then the given value (Default value = 20) :type timeout: ``int`` :raises X2GoControlSessionException: if the SSH connection dropped out """ ssh_transport = self.get_transport() self._transport_lock.acquire() if ssh_transport and ssh_transport.is_authenticated(): self.logger('sFTP-write: opening remote file %s on host %s for writing' % (remote_path, self.remote_peername()), loglevel=log.loglevel_DEBUG) if self.low_latency: timeout = timeout * 2 timer = gevent.Timeout(timeout) timer.start() try: try: self.sftp_client = paramiko.SFTPClient.from_transport(ssh_transport) except paramiko.SFTPError: self._transport_lock.release() raise x2go_exceptions.X2GoSFTPClientException('failed to initialize SFTP channel') try: remote_fileobj = self.sftp_client.open(remote_path, 'w') self.logger('sFTP-write: writing content: %s' % content, loglevel=log.loglevel_DEBUG_SFTPXFER) remote_fileobj.write(content) remote_fileobj.close() except (x2go_exceptions.SSHException, socket.error, IOError): self.session_died = True self._transport_lock.release() self.logger('sFTP-write: opening remote file %s on host %s failed' % (remote_path, self.remote_peername()), loglevel=log.loglevel_WARN) if self.sshproxy_session: self.sshproxy_session.stop_thread() raise x2go_exceptions.X2GoControlSessionException('The SSH connection was dropped during an sFTP write action.') except gevent.timeout.Timeout: self.session_died = True self._transport_lock.release() if self.sshproxy_session: self.sshproxy_session.stop_thread() raise x2go_exceptions.X2GoControlSessionException('the X2Go control session timed out during an SFTP write command') finally: timer.cancel() self.sftp_client = None if self._transport_lock.locked(): self._transport_lock.release() def _x2go_sftp_remove(self, remote_path, timeout=20): """\ Remote a remote file from the server via sFTP. During sFTP operations, remote command execution gets blocked. :param remote_path: full remote path name of the server-side file to be removed, path names have to be Unix-compliant :type remote_path: ``str`` :param timeout: this SFTP remove action should not take longer then the given value (Default value = 20) :type timeout: ``int`` :raises X2GoControlSessionException: if the SSH connection dropped out """ ssh_transport = self.get_transport() self._transport_lock.acquire() if ssh_transport and ssh_transport.is_authenticated(): self.logger('sFTP-write: removing remote file %s on host %s' % (remote_path, self.remote_peername()), loglevel=log.loglevel_DEBUG) if self.low_latency: timeout = timeout * 2 timer = gevent.Timeout(timeout) timer.start() try: try: self.sftp_client = paramiko.SFTPClient.from_transport(ssh_transport) except paramiko.SFTPError: self._transport_lock.release() raise x2go_exceptions.X2GoSFTPClientException('failed to initialize SFTP channel') try: self.sftp_client.remove(remote_path) except (x2go_exceptions.SSHException, socket.error, IOError): self.session_died = True self._transport_lock.release() self.logger('sFTP-write: removing remote file %s on host %s failed' % (remote_path, self.remote_peername()), loglevel=log.loglevel_WARN) if self.sshproxy_session: self.sshproxy_session.stop_thread() raise x2go_exceptions.X2GoControlSessionException('The SSH connection was dropped during an sFTP remove action.') except gevent.timeout.Timeout: self.session_died = True self._transport_lock.release() if self.sshproxy_session: self.sshproxy_session.stop_thread() raise x2go_exceptions.X2GoControlSessionException('the X2Go control session timed out during an SFTP write command') finally: timer.cancel() self.sftp_client = None if self._transport_lock.locked(): self._transport_lock.release() def _x2go_exec_command(self, cmd_line, loglevel=log.loglevel_INFO, timeout=20, **kwargs): """\ Execute an X2Go server-side command via SSH. During SSH command executions, sFTP operations get blocked. :param cmd_line: the command to be executed on the remote server :type cmd_line: ``str`` or ``list`` :param loglevel: use this loglevel for reporting about remote command execution (Default value = log.loglevel_INFO) :type loglevel: ``int`` :param timeout: if commands take longer than ```` to be executed, consider the control session connection to have died. (Default value = 20) :type timeout: ``int`` :param kwargs: parameters that get passed through to the ``paramiko.SSHClient.exec_command()`` method. :type kwargs: ``dict`` :returns: ``True`` if the command could be successfully executed on the remote X2Go server :rtype: ``bool`` :raises X2GoControlSessionException: if the command execution failed (due to a lost connection) """ if type(cmd_line) == list: cmd = " ".join(cmd_line) else: cmd = cmd_line cmd_uuid = str(uuid.uuid1()) cmd = 'echo X2GODATABEGIN:%s; PATH=/usr/local/bin:/usr/bin:/bin sh -c \"%s\"; echo X2GODATAEND:%s' % (cmd_uuid, cmd, cmd_uuid) if self.session_died: self.logger("control session seams to be dead, not executing command ,,%s'' on X2Go server %s" % (_rerewrite_blanks(cmd), self.profile_name,), loglevel=loglevel) if sys.version_info[0] >= 3: return (io.BytesIO(), io.BytesIO(), io.BytesIO(b'failed to execute command')) else: return (io.StringIO(), io.StringIO(), io.StringIO(u'failed to execute command')) self._transport_lock.acquire() _retval = None _password = None ssh_transport = self.get_transport() if ssh_transport and ssh_transport.is_authenticated(): if self.low_latency: timeout = timeout * 2 timer = gevent.Timeout(timeout) timer.start() try: self.logger("executing command on X2Go server ,,%s'': %s" % (self.profile_name, _rerewrite_blanks(cmd)), loglevel=loglevel) if self._session_password: _password = base64.b64decode(self._session_password) _retval = self.exec_command(_rewrite_password(cmd, user=self.get_transport().get_username(), password=_password), **kwargs) except AttributeError: self.session_died = True self._transport_lock.release() if self.sshproxy_session: self.sshproxy_session.stop_thread() raise x2go_exceptions.X2GoControlSessionException('the X2Go control session has died unexpectedly') except EOFError: self.session_died = True self._transport_lock.release() if self.sshproxy_session: self.sshproxy_session.stop_thread() raise x2go_exceptions.X2GoControlSessionException('the X2Go control session has died unexpectedly') except x2go_exceptions.SSHException: self.session_died = True self._transport_lock.release() if self.sshproxy_session: self.sshproxy_session.stop_thread() raise x2go_exceptions.X2GoControlSessionException('the X2Go control session has died unexpectedly') except gevent.timeout.Timeout: self.session_died = True self._transport_lock.release() if self.sshproxy_session: self.sshproxy_session.stop_thread() raise x2go_exceptions.X2GoControlSessionException('the X2Go control session command timed out') except socket.error: self.session_died = True self._transport_lock.release() if self.sshproxy_session: self.sshproxy_session.stop_thread() raise x2go_exceptions.X2GoControlSessionException('the X2Go control session has died unexpectedly') finally: timer.cancel() else: self._transport_lock.release() raise x2go_exceptions.X2GoControlSessionException('the X2Go control session is not connected (while issuing SSH command=%s)' % cmd) if self._transport_lock.locked(): self._transport_lock.release() sanitized_stdout = u'' is_x2go_data = False # sanitized X2Go relevant data, protect against data injection via .bashrc files (stdin, stdout, stderr) = _retval raw_stdout = stdout.read() # Python 3 needs a decoding from bytestring to string if sys.version_info[0] >= 3: raw_stdout = raw_stdout.decode() else: if type(raw_stdout) is not types.UnicodeType: raw_stdout = raw_stdout.decode('utf-8') for line in raw_stdout.split('\n'): if line.startswith('X2GODATABEGIN:'+cmd_uuid): is_x2go_data = True continue if not is_x2go_data: continue if line.startswith('X2GODATAEND:'+cmd_uuid): break sanitized_stdout += line + "\n" if sys.version_info[0] >= 3: _stdout_new = io.BytesIO(sanitized_stdout.encode()) else: _stdout_new = io.StringIO(sanitized_stdout) _retval = (stdin, _stdout_new, stderr) return _retval @property def _x2go_server_versions(self): """\ Render a dictionary of server-side X2Go components and their versions. Results get cached once there has been one successful query. """ if self._server_versions is None: self._server_versions = {} (stdin, stdout, stderr) = self._x2go_exec_command('which x2goversion >/dev/null && x2goversion') if sys.version_info[0] >= 3: _lines = stdout.read().decode().split('\n') else: _lines = stdout.read().split('\n') for _line in _lines: if ':' not in _line: continue comp = _line.split(':')[0].strip() version = _line.split(':')[1].strip() self._server_versions.update({comp: version}) self.logger('server-side X2Go components and their versions are: %s' % self._server_versions, loglevel=log.loglevel_DEBUG) return self._server_versions def query_server_versions(self, force=False): """\ Do a query for the server-side list of X2Go components and their versions. :param force: do not use the cached component list, really ask the server (again) (Default value = False) :type force: ``bool`` :returns: dictionary of X2Go components (as keys) and their versions (as values) :rtype: ``list`` """ if force: self._server_versions = None return self._x2go_server_versions get_server_versions = query_server_versions @property def _x2go_server_features(self): """\ Render a list of server-side X2Go features. Results get cached once there has been one successful query. """ if self._server_features is None: (stdin, stdout, stderr) = self._x2go_exec_command('which x2gofeaturelist >/dev/null && x2gofeaturelist') _stdout = stdout.read() if sys.version_info[0] >= 3: _stdout = _stdout.decode() self._server_features = _stdout.split('\n') self._server_features = [ f for f in self._server_features if f ] self._server_features.sort() self.logger('server-side X2Go features are: %s' % self._server_features, loglevel=log.loglevel_DEBUG) return self._server_features def query_server_features(self, force=False): """\ Do a query for the server-side list of X2Go features. :param force: do not use the cached feature list, really ask the server (again) (Default value = False) :type force: ``bool`` :returns: list of X2Go feature names :rtype: ``list`` """ if force: self._server_features = None return self._x2go_server_features get_server_features = query_server_features @property def _x2go_remote_home(self): """\ Retrieve and cache the remote home directory location. """ if self._remote_home is None: (stdin, stdout, stderr) = self._x2go_exec_command('echo $HOME') _stdout = stdout.read() if sys.version_info[0] >= 3: _stdout = _stdout.decode() if _stdout: self._remote_home = _stdout.split()[0] self.logger('remote user\' home directory: %s' % self._remote_home, loglevel=log.loglevel_DEBUG) return self._remote_home else: return self._remote_home def _x2go_remote_group(self, group): """\ Retrieve and cache the members of a server-side POSIX group. :param group: remote POSIX group name :type group: ``str`` :returns: list of POSIX group members :rtype: ``list`` """ if group not in self._remote_group: (stdin, stdout, stderr) = self._x2go_exec_command('getent group %s | cut -d":" -f4' % group) self._remote_group[group] = stdout.read().split('\n')[0].split(',') self.logger('remote %s group: %s' % (group, self._remote_group[group]), loglevel=log.loglevel_DEBUG) return self._remote_group[group] else: return self._remote_group[group] def is_x2gouser(self, username): """\ Is the remote user allowed to launch X2Go sessions? FIXME: this method is currently non-functional. :param username: remote user name :type username: ``str`` :returns: ``True`` if the remote user is allowed to launch X2Go sessions :rtype: ``bool`` """ ### ### FIXME: ### # discussion about server-side access restriction based on posix group membership or similar currently # in process (as of 20110517, mg) #return username in self._x2go_remote_group('x2gousers') return True def is_sshfs_available(self): """\ Check if the remote user is allowed to use SSHFS mounts. :returns: ``True`` if the user is allowed to connect client-side shares to the X2Go session :rtype: ``bool`` """ (stdin, stdout, stderr) = self._x2go_exec_command('which fusermount') # if which returns the full path of fusermount, the current use is allowed to execute it return bool(stdout.read()) def remote_username(self): """\ Returns (and caches) the control session's remote username. :returns: SSH transport's user name :rtype: ``str`` :raises X2GoControlSessionException: on SSH connection loss """ if self._remote_username is None: if self.get_transport() is not None: try: self._remote_username = self.get_transport().get_username() except: self.session_died = True raise x2go_exceptions.X2GoControlSessionException('Lost connection to X2Go server') return self._remote_username def remote_peername(self): """\ Returns (and caches) the control session's remote host (name or ip). :returns: SSH transport's peer name :rtype: ``tuple`` :raises X2GoControlSessionException: on SSH connection loss """ if self._remote_peername is None: if self.get_transport() is not None: try: self._remote_peername = self.get_transport().getpeername() except: self.session_died = True raise x2go_exceptions.X2GoControlSessionException('Lost connection to X2Go server') return self._remote_peername @property def _x2go_session_auth_rsakey(self): """\ Generate (and cache) a temporary RSA host key for the lifetime of this control session. """ if self._session_auth_rsakey is None: self._session_auth_rsakey = paramiko.RSAKey.generate(defaults.RSAKEY_STRENGTH) return self._session_auth_rsakey def set_profile_name(self, profile_name): """\ Manipulate the control session's profile name. :param profile_name: new profile name for this control session :type profile_name: ``str`` """ self.profile_name = profile_name def check_host(self, hostname, port=22): """\ Wraps around a Paramiko/SSH host key check. :param hostname: the remote X2Go server's hostname :type hostname: ``str`` :param port: the SSH port of the remote X2Go server (Default value = 22) :type port: ``int`` :returns: ``True`` if the host key check succeeded, ``False`` otherwise :rtype: ``bool`` """ # trailing whitespace tolerance hostname = hostname.strip() # force into IPv4 for localhost connections if hostname in ('localhost', 'localhost.localdomain'): hostname = '127.0.0.1' return checkhosts.check_ssh_host_key(self, hostname, port=port) def connect(self, hostname, port=22, username=None, password=None, passphrase=None, pkey=None, key_filename=None, timeout=None, allow_agent=False, look_for_keys=False, use_sshproxy=False, sshproxy_host=None, sshproxy_port=22, sshproxy_user=None, sshproxy_password=None, sshproxy_force_password_auth=False, sshproxy_key_filename=None, sshproxy_pkey=None, sshproxy_look_for_keys=False, sshproxy_passphrase='', sshproxy_allow_agent=False, sshproxy_tunnel=None, add_to_known_hosts=None, forward_sshagent=None, unique_hostkey_aliases=None, force_password_auth=False, session_instance=None, ): """\ Connect to an X2Go server and authenticate to it. This method is directly inherited from the ``paramiko.SSHClient`` class. The features of the Paramiko SSH client connect method are recited here. The parameters ``add_to_known_hosts``, ``force_password_auth``, ``session_instance`` and all SSH proxy related parameters have been added as X2Go specific parameters The server's host key is checked against the system host keys (see ``load_system_host_keys``) and any local host keys (``load_host_keys``). If the server's hostname is not found in either set of host keys, the missing host key policy is used (see ``set_missing_host_key_policy``). The default policy is to reject the key and raise an ``SSHException``. Authentication is attempted in the following order of priority: - The ``pkey`` or ``key_filename`` passed in (if any) - Any key we can find through an SSH agent - Any "id_rsa" or "id_dsa" key discoverable in ``~/.ssh/`` - Plain username/password auth, if a password was given If a private key requires a password to unlock it, and a password is passed in, that password will be used to attempt to unlock the key. :param hostname: the server to connect to :type hostname: ``str`` :param port: the server port to connect to (Default value = 22) :type port: ``int`` :param username: the username to authenticate as (defaults to the current local username) :type username: ``str`` :param password: a password to use for authentication or for unlocking a private key (Default value = None) :type password: ``str`` :param passphrase: a passphrase to use for unlocking a private key in case the password is already needed for two-factor authentication (Default value = None) :type passphrase: ``str`` :param key_filename: the filename, or list of filenames, of optional private key(s) to try for authentication (Default value = None) :type key_filename: ``str`` or list(str) :param pkey: an optional private key to use for authentication (Default value = None) :type pkey: ``PKey`` :param forward_sshagent: forward SSH agent authentication requests to the X2Go client-side (will update the class property of the same name) (Default value = None) :type forward_sshagent: ``bool`` :param unique_hostkey_aliases: update the unique_hostkey_aliases class property (Default value = None) :type unique_hostkey_aliases: ``bool`` :param timeout: an optional timeout (in seconds) for the TCP connect (Default value = None) :type timeout: float :param look_for_keys: set to ``True`` to enable searching for discoverable private key files in ``~/.ssh/`` (Default value = False) :type look_for_keys: ``bool`` :param allow_agent: set to ``True`` to enable connecting to a local SSH agent for acquiring authentication information (Default value = False) :type allow_agent: ``bool`` :param add_to_known_hosts: non-paramiko option, if ``True`` paramiko.AutoAddPolicy() is used as missing-host-key-policy. If set to ``False`` paramiko.RejectPolicy() is used (Default value = None) :type add_to_known_hosts: ``bool`` :param force_password_auth: non-paramiko option, disable pub/priv key authentication completely, even if the ``pkey`` or the ``key_filename`` parameter is given (Default value = False) :type force_password_auth: ``bool`` :param session_instance: an instance :class:`x2go.session.X2GoSession` using this :class:`x2go.backends.control.plain.X2GoControlSession` instance. (Default value = None) :type session_instance: ``obj`` :param use_sshproxy: connect through an SSH proxy (Default value = False) :type use_sshproxy: ``True`` if an SSH proxy is to be used for tunneling the connection :param sshproxy_host: hostname of the SSH proxy server (Default value = None) :type sshproxy_host: ``str`` :param sshproxy_port: port of the SSH proxy server (Default value = 22) :type sshproxy_port: ``int`` :param sshproxy_user: username that we use for authenticating against ```` (Default value = None) :type sshproxy_user: ``str`` :param sshproxy_password: a password to use for SSH proxy authentication or for unlocking a private key (Default value = None) :type sshproxy_password: ``str`` :param sshproxy_passphrase: a passphrase to use for unlocking a private key needed for the SSH proxy host in case the sshproxy_password is already needed for two-factor authentication (Default value = '') :type sshproxy_passphrase: ``str`` :param sshproxy_force_password_auth: enforce using a given ``sshproxy_password`` even if a key(file) is given (Default value = False) :type sshproxy_force_password_auth: ``bool`` :param sshproxy_key_filename: local file location of the private key file (Default value = None) :type sshproxy_key_filename: ``str`` :param sshproxy_pkey: an optional private key to use for SSH proxy authentication (Default value = None) :type sshproxy_pkey: ``PKey`` :param sshproxy_look_for_keys: set to ``True`` to enable connecting to a local SSH agent for acquiring authentication information (for SSH proxy authentication) (Default value = False) :type sshproxy_look_for_keys: ``bool`` :param sshproxy_allow_agent: set to ``True`` to enable connecting to a local SSH agent for acquiring authentication information (for SSH proxy authentication) (Default value = False) :type sshproxy_allow_agent: ``bool`` :param sshproxy_tunnel: the SSH proxy tunneling parameters, format is: ::: (Default value = None) :type sshproxy_tunnel: ``str`` :returns: ``True`` if an authenticated SSH transport could be retrieved by this method :rtype: ``bool`` :raises BadHostKeyException: if the server's host key could not be verified :raises AuthenticationException: if authentication failed :raises SSHException: if there was any other error connecting or establishing an SSH session :raises socket.error: if a socket error occurred while connecting :raises X2GoSSHProxyException: any SSH proxy exception is passed through while establishing the SSH proxy connection and tunneling setup :raises X2GoSSHAuthenticationException: any SSH proxy authentication exception is passed through while establishing the SSH proxy connection and tunneling setup :raises X2GoRemoteHomeException: if the remote home directory does not exist or is not accessible :raises X2GoControlSessionException: if the remote peer has died unexpectedly """ _fake_hostname = None if hostname and type(hostname) not in (str, bytes): hostname = [hostname] if hostname and type(hostname) is list: hostname = random.choice(hostname) if not username: self.logger('no username specified, cannot connect without username', loglevel=log.loglevel_ERROR) raise paramiko.AuthenticationException('no username specified, cannot connect without username') if type(password) not in (bytes, str): password = '' if type(sshproxy_password) not in (bytes, str): sshproxy_password = '' if unique_hostkey_aliases is None: unique_hostkey_aliases = self.unique_hostkey_aliases # prep the fake hostname with the real hostname, so we trigger the corresponding code path in # x2go.checkhosts and either of its missing host key policies if unique_hostkey_aliases: if port != 22: _fake_hostname = "[%s]:%s" % (hostname, port) else: _fake_hostname = hostname if add_to_known_hosts is None: add_to_known_hosts = self.add_to_known_hosts if forward_sshagent is None: forward_sshagent = self.forward_sshagent if look_for_keys: key_filename = None pkey = None _twofactorauth = False if password and (passphrase is None) and not force_password_auth: passphrase = password if use_sshproxy and sshproxy_host and sshproxy_user: try: if not sshproxy_tunnel: sshproxy_tunnel = "localhost:44444:%s:%s" % (hostname, port) self.sshproxy_session = sshproxy.X2GoSSHProxy(known_hosts=self.known_hosts, add_to_known_hosts=add_to_known_hosts, sshproxy_host=sshproxy_host, sshproxy_port=sshproxy_port, sshproxy_user=sshproxy_user, sshproxy_password=sshproxy_password, sshproxy_passphrase=sshproxy_passphrase, sshproxy_force_password_auth=sshproxy_force_password_auth, sshproxy_key_filename=sshproxy_key_filename, sshproxy_pkey=sshproxy_pkey, sshproxy_look_for_keys=sshproxy_look_for_keys, sshproxy_allow_agent=sshproxy_allow_agent, sshproxy_tunnel=sshproxy_tunnel, session_instance=session_instance, logger=self.logger, ) hostname = self.sshproxy_session.get_local_proxy_host() port = self.sshproxy_session.get_local_proxy_port() _fake_hostname = self.sshproxy_session.get_remote_host() _fake_port = self.sshproxy_session.get_remote_port() if _fake_port != 22: _fake_hostname = "[%s]:%s" % (_fake_hostname, _fake_port) except: if self.sshproxy_session: self.sshproxy_session.stop_thread() self.sshproxy_session = None raise if self.sshproxy_session is not None: self.sshproxy_session.start() # divert port to sshproxy_session's local forwarding port (it might have changed due to # SSH connection errors gevent.sleep(.1) port = self.sshproxy_session.get_local_proxy_port() if not add_to_known_hosts and session_instance: self.set_missing_host_key_policy(checkhosts.X2GoInteractiveAddPolicy(caller=self, session_instance=session_instance, fake_hostname=_fake_hostname)) if add_to_known_hosts: self.set_missing_host_key_policy(checkhosts.X2GoAutoAddPolicy(caller=self, session_instance=session_instance, fake_hostname=_fake_hostname)) # trailing whitespace tolerance in hostname hostname = hostname.strip() self.logger('connecting to [%s]:%s' % (hostname, port), loglevel=log.loglevel_NOTICE) self.load_session_host_keys() _hostname = hostname # enforce IPv4 for localhost address if _hostname in ('localhost', 'localhost.localdomain'): _hostname = '127.0.0.1' # update self.forward_sshagent via connect method parameter if forward_sshagent is not None: self.forward_sshagent = forward_sshagent if timeout and self.low_latency: timeout = timeout * 2 if key_filename and "~" in key_filename: key_filename = os.path.expanduser(key_filename) if key_filename or pkey or look_for_keys or allow_agent or (password and force_password_auth): try: if password and force_password_auth: self.logger('trying password based SSH authentication with server', loglevel=log.loglevel_DEBUG) paramiko.SSHClient.connect(self, _hostname, port=port, username=username, password=password, pkey=None, key_filename=None, timeout=timeout, allow_agent=False, look_for_keys=False) elif (key_filename and os.path.exists(os.path.normpath(key_filename))) or pkey: self.logger('trying SSH pub/priv key authentication with server', loglevel=log.loglevel_DEBUG) paramiko.SSHClient.connect(self, _hostname, port=port, username=username, pkey=pkey, password=passphrase, key_filename=key_filename, timeout=timeout, allow_agent=False, look_for_keys=False) else: self.logger('trying SSH key discovery or agent authentication with server', loglevel=log.loglevel_DEBUG) paramiko.SSHClient.connect(self, _hostname, port=port, username=username, pkey=None, password=passphrase, key_filename=None, timeout=timeout, allow_agent=allow_agent, look_for_keys=look_for_keys) except (paramiko.PasswordRequiredException, paramiko.SSHException) as e: self.close() if type(e) == paramiko.SSHException and str(e).startswith('Two-factor authentication requires a password'): self.logger('X2Go Server requests two-factor authentication', loglevel=log.loglevel_NOTICE) _twofactorauth = True if passphrase is not None: self.logger('unlock SSH private key file with provided password', loglevel=log.loglevel_INFO) try: if not password: password = None if (key_filename and os.path.exists(os.path.normpath(key_filename))) or pkey: self.logger('re-trying SSH pub/priv key authentication with server', loglevel=log.loglevel_DEBUG) try: paramiko.SSHClient.connect(self, _hostname, port=port, username=username, password=password, passphrase=passphrase, pkey=pkey, key_filename=key_filename, timeout=timeout, allow_agent=False, look_for_keys=False) except TypeError: if _twofactorauth and password and passphrase and password != passphrase: self.logger('your version of Paramiko/SSH does not support authentication workflows which require SSH key decryption in combination with two-factor authentication', loglevel=log.loglevel_WARN) paramiko.SSHClient.connect(self, _hostname, port=port, username=username, password=password, pkey=pkey, key_filename=key_filename, timeout=timeout, allow_agent=False, look_for_keys=False) else: self.logger('re-trying SSH key discovery now with passphrase for unlocking the key(s)', loglevel=log.loglevel_DEBUG) try: paramiko.SSHClient.connect(self, _hostname, port=port, username=username, password=password, passphrase=passphrase, pkey=None, key_filename=None, timeout=timeout, allow_agent=allow_agent, look_for_keys=look_for_keys) except TypeError: if _twofactorauth and password and passphrase and password != passphrase: self.logger('your version of Paramiko/SSH does not support authentication workflows which require SSH key decryption in combination with two-factor authentication', loglevel=log.loglevel_WARN) paramiko.SSHClient.connect(self, _hostname, port=port, username=username, password=password, pkey=None, key_filename=None, timeout=timeout, allow_agent=allow_agent, look_for_keys=look_for_keys) except paramiko.AuthenticationException as auth_e: # the provided password cannot be used to unlock any private SSH key file (i.e. wrong password) raise paramiko.AuthenticationException(str(auth_e)) except paramiko.SSHException as auth_e: if str(auth_e) == 'No authentication methods available': raise paramiko.AuthenticationException('Interactive password authentication required!') else: self.close() if self.sshproxy_session: self.sshproxy_session.stop_thread() raise auth_e else: self.close() if self.sshproxy_session: self.sshproxy_session.stop_thread() raise e except paramiko.AuthenticationException as e: self.close() if password: self.logger('next auth mechanism we\'ll try is password authentication', loglevel=log.loglevel_DEBUG) try: paramiko.SSHClient.connect(self, _hostname, port=port, username=username, password=password, key_filename=None, pkey=None, timeout=timeout, allow_agent=False, look_for_keys=False) except: self.close() if self.sshproxy_session: self.sshproxy_session.stop_thread() raise else: self.close() if self.sshproxy_session: self.sshproxy_session.stop_thread() raise e except paramiko.SSHException as e: if str(e) == 'No authentication methods available': raise paramiko.AuthenticationException('Interactive password authentication required!') else: self.close() if self.sshproxy_session: self.sshproxy_session.stop_thread() raise e except: self.close() if self.sshproxy_session: self.sshproxy_session.stop_thread() raise # if there is no private key (and no agent auth), we will use the given password, if any else: # create a random password if password is empty to trigger host key validity check if not password: password = "".join([random.choice(string.letters+string.digits) for x in range(1, 20)]) self.logger('performing SSH password authentication with server', loglevel=log.loglevel_DEBUG) try: paramiko.SSHClient.connect(self, _hostname, port=port, username=username, password=password, timeout=timeout, allow_agent=False, look_for_keys=False) except: self.close() if self.sshproxy_session: self.sshproxy_session.stop_thread() raise self.set_missing_host_key_policy(paramiko.RejectPolicy()) self.hostname = hostname self.port = port # preparing reverse tunnels ssh_transport = self.get_transport() ssh_transport.reverse_tunnels = {} # mark Paramiko/SSH transport as X2GoControlSession ssh_transport._x2go_session_marker = True try: self._session_password = base64.b64encode(password) except TypeError: self._session_password = None if ssh_transport is not None: # since Paramiko 1.7.7.1 there is compression available, let's use it if present... if x2go._paramiko.PARAMIKO_FEATURE['use-compression']: ssh_transport.use_compression(compress=False) # enable keep alive callbacks ssh_transport.set_keepalive(5) self.session_died = False self.query_server_features(force=True) if self.forward_sshagent: if x2go._paramiko.PARAMIKO_FEATURE['forward-ssh-agent']: try: self.agent_chan = ssh_transport.open_session() self.agent_handler = paramiko.agent.AgentRequestHandler(self.agent_chan) self.logger('Requesting SSH agent forwarding for control session of connected session profile %s' % self.profile_name, loglevel=log.loglevel_NOTICE) except EOFError as e: # if we come across an EOFError here, we must assume the session is dead... self.session_died = True raise x2go_exceptions.X2GoControlSessionException('The SSH connection was dropped while setting up SSH agent forwarding socket.') else: self.logger('SSH agent forwarding is not available in the Paramiko version used with this instance of Python X2Go', loglevel=log.loglevel_WARN) else: self.close() if self.sshproxy_session: self.sshproxy_session.stop_thread() self._remote_home = None if not self.home_exists(): self.close() if self.sshproxy_session: self.sshproxy_session.stop_thread() raise x2go_exceptions.X2GoRemoteHomeException('remote home directory does not exist') return (self.get_transport() is not None) def dissociate(self, terminal_session): """\ Drop an associated terminal session. :param terminal_session: the terminal session object to remove from the list of associated terminals :type terminal_session: ``X2GoTerminalSession*`` """ for t_name in list(self.associated_terminals.keys()): if self.associated_terminals[t_name] == terminal_session: del self.associated_terminals[t_name] if t_name in self.terminated_terminals: del self.terminated_terminals[t_name] def disconnect(self): """\ Disconnect this control session from the remote server. :returns: report success or failure after having disconnected :rtype: ``bool`` """ if self.associated_terminals: t_names = list(self.associated_terminals.keys()) for t_obj in list(self.associated_terminals.values()): try: if not self.session_died: t_obj.suspend() except x2go_exceptions.X2GoTerminalSessionException: pass except x2go_exceptions.X2GoControlSessionException: self.session_died t_obj.__del__() for t_name in t_names: try: del self.associated_terminals[t_name] except KeyError: pass self._remote_home = None self._remote_group = {} self._session_auth_rsakey = None # in any case, release out internal transport lock if self._transport_lock.locked(): self._transport_lock.release() # close SSH agent auth forwarding objects if self.agent_handler is not None: self.agent_handler.close() if self.agent_chan is not None: try: self.agent_chan.close() except EOFError: pass retval = False try: if self.get_transport() is not None: retval = self.get_transport().is_active() try: self.close() except IOError: pass except AttributeError: # if the Paramiko _transport object has not yet been initialized, ignore it # but state that this method call did not close the SSH client, but was already closed pass # take down sshproxy_session no matter what happened to the control session itself if self.sshproxy_session is not None: self.sshproxy_session.stop_thread() return retval def home_exists(self): """\ Test if the remote home directory exists. :returns: ``True`` if the home directory exists, ``False`` otherwise :rtype: ``bool`` """ (stdin, stdout, stderr) = self._x2go_exec_command('stat -tL "%s"' % self._x2go_remote_home, loglevel=log.loglevel_DEBUG) _stdout = stdout.read() if _stdout: return True return False def is_alive(self): """\ Test if the connection to the remote X2Go server is still alive. :returns: ``True`` if the connection is still alive, ``False`` otherwise :rtype: ``bool`` """ try: if self._x2go_exec_command('echo', loglevel=log.loglevel_DEBUG): return True except x2go_exceptions.X2GoControlSessionException: self.session_died = True self.disconnect() return False def has_session_died(self): """\ Test if the connection to the remote X2Go server died on the way. :returns: ``True`` if the connection has died, ``False`` otherwise :rtype: ``bool`` """ return self.session_died def get_published_applications(self, lang=None, refresh=False, raw=False, very_raw=False, max_no_submenus=defaults.PUBAPP_MAX_NO_SUBMENUS): """\ Retrieve the menu tree of published applications from the remote X2Go server. The ``raw`` option lets this method return a ``list`` of ``dict`` elements. Each ``dict`` elements has a ``desktop`` key containing a shortened version of the text output of a .desktop file and an ``icon`` key which contains the desktop base64-encoded icon data. The {very_raw} lets this method return the output of the ``x2gogetapps`` script as is. :param lang: locale/language identifier (Default value = None) :type lang: ``str`` :param refresh: force reload of the menu tree from X2Go server (Default value = False) :type refresh: ``bool`` :param raw: retrieve a raw output of the server list of published applications (Default value = False) :type raw: ``bool`` :param very_raw: retrieve a very raw output of the server list of published applications (Default value = False) :type very_raw: ``bool`` :param max_no_submenus: Number of applications before applications are put into XDG category submenus (Default value = defaults.PUBAPP_MAX_NO_SUBMENUS) :type max_no_submenus: ``int`` :returns: an i18n capable menu tree packed as a Python dictionary :rtype: ``list`` """ self._already_querying_published_applications.acquire() if defaults.X2GOCLIENT_OS != 'Windows' and lang is None: lang = locale.getdefaultlocale()[0] elif lang is None: lang = 'en' if 'X2GO_PUBLISHED_APPLICATIONS' in self.get_server_features(): if self._published_applications_menu is {} or \ lang not in self._published_applications_menu or \ raw or very_raw or refresh or \ (self.published_applications_no_submenus != max_no_submenus): self.published_applications_no_submenus = max_no_submenus ### STAGE 1: retrieve menu from server self.logger('querying server (%s) for list of published applications' % self.profile_name, loglevel=log.loglevel_NOTICE) (stdin, stdout, stderr) = self._x2go_exec_command('which x2gogetapps >/dev/null && x2gogetapps') _raw_output = stdout.read() if very_raw: self.logger('published applications query for %s finished, return very raw output' % self.profile_name, loglevel=log.loglevel_NOTICE) self._already_querying_published_applications.release() return _raw_output ### STAGE 2: dissect the text file retrieved from server, cut into single menu elements _raw_menu_items = _raw_output.split('\n') _raw_menu_items = [ i.replace('\n', '') for i in _raw_menu_items ] _menu = [] for _raw_menu_item in _raw_menu_items: if '\n' in _raw_menu_item and '' in _raw_menu_item: _menu_item = _raw_menu_item.split('\n')[0] + _raw_menu_item.split('\n')[1] _icon_base64 = _raw_menu_item.split('\n')[1].split('\n')[0] else: _menu_item = _raw_menu_item _icon_base64 = None if _menu_item: _menu.append({ 'desktop': _menu_item, 'icon': _icon_base64, }) _menu_item = None _icon_base64 = None if raw: self.logger('published applications query for %s finished, returning raw output' % self.profile_name, loglevel=log.loglevel_NOTICE) self._already_querying_published_applications.release() return _menu if len(_menu) > max_no_submenus >= 0: _render_submenus = True else: _render_submenus = False # STAGE 3: create menu structure in a Python dictionary _category_map = { lang: { 'Multimedia': [], 'Development': [], 'Education': [], 'Games': [], 'Graphics': [], 'Internet': [], 'Office': [], 'System': [], 'Utilities': [], 'Other Applications': [], 'TOP': [], } } _empty_menus = list(_category_map[lang].keys()) for item in _menu: _menu_entry_name = '' _menu_entry_fallback_name = '' _menu_entry_comment = '' _menu_entry_fallback_comment = '' _menu_entry_exec = '' _menu_entry_cat = '' _menu_entry_shell = False lang_regio = lang lang_only = lang_regio.split('_')[0] for line in item['desktop'].split('\n'): if re.match('^Name\[%s\]=.*' % lang_regio, line) or re.match('Name\[%s\]=.*' % lang_only, line): _menu_entry_name = line.split("=")[1].strip() elif re.match('^Name=.*', line): _menu_entry_fallback_name = line.split("=")[1].strip() elif re.match('^Comment\[%s\]=.*' % lang_regio, line) or re.match('Comment\[%s\]=.*' % lang_only, line): _menu_entry_comment = line.split("=")[1].strip() elif re.match('^Comment=.*', line): _menu_entry_fallback_comment = line.split("=")[1].strip() elif re.match('^Exec=.*', line): _menu_entry_exec = line.split("=")[1].strip() elif re.match('^Terminal=.*(t|T)(r|R)(u|U)(e|E).*', line): _menu_entry_shell = True elif re.match('^Categories=.*', line): if 'X2Go-Top' in line: _menu_entry_cat = 'TOP' elif 'Audio' in line or 'Video' in line: _menu_entry_cat = 'Multimedia' elif 'Development' in line: _menu_entry_cat = 'Development' elif 'Education' in line: _menu_entry_cat = 'Education' elif 'Game' in line: _menu_entry_cat = 'Games' elif 'Graphics' in line: _menu_entry_cat = 'Graphics' elif 'Network' in line: _menu_entry_cat = 'Internet' elif 'Office' in line: _menu_entry_cat = 'Office' elif 'Settings' in line: continue elif 'System' in line: _menu_entry_cat = 'System' elif 'Utility' in line: _menu_entry_cat = 'Utilities' else: _menu_entry_cat = 'Other Applications' if not _menu_entry_exec: continue else: # FIXME: strip off any noted options (%f, %F, %u, %U, ...), this can be more intelligent _menu_entry_exec = _menu_entry_exec.replace('%f', '').replace('%F','').replace('%u','').replace('%U','') if _menu_entry_shell: _menu_entry_exec = "x-terminal-emulator -e '%s'" % _menu_entry_exec if not _menu_entry_cat: _menu_entry_cat = 'Other Applications' if not _render_submenus: _menu_entry_cat = 'TOP' if _menu_entry_cat in _empty_menus: _empty_menus.remove(_menu_entry_cat) if not _menu_entry_name: _menu_entry_name = _menu_entry_fallback_name if not _menu_entry_comment: _menu_entry_comment = _menu_entry_fallback_comment if not _menu_entry_comment: _menu_entry_comment = _menu_entry_name _menu_entry_icon = item['icon'] _category_map[lang][_menu_entry_cat].append( { 'name': _menu_entry_name, 'comment': _menu_entry_comment, 'exec': _menu_entry_exec, 'icon': _menu_entry_icon, } ) for _cat in _empty_menus: del _category_map[lang][_cat] for _cat in list(_category_map[lang].keys()): _sorted = sorted(_category_map[lang][_cat], key=lambda k: k['name']) _category_map[lang][_cat] = _sorted self._published_applications_menu.update(_category_map) self.logger('published applications query for %s finished, return menu tree' % self.profile_name, loglevel=log.loglevel_NOTICE) else: # FIXME: ignoring the absence of the published applications feature for now, handle it appropriately later pass self._already_querying_published_applications.release() return self._published_applications_menu def start(self, **kwargs): """\ Start a new X2Go session. The :func:`X2GoControlSession.start() ` method accepts any parameter that can be passed to any of the ``X2GoTerminalSession`` backend class constructors. :param kwargs: parameters that get passed through to the control session's :func:`resume()` method, only the ``session_name`` parameter will get removed before pass-through :type kwargs: ``dict`` :returns: return: return value of the cascaded :func:`resume()` method, denoting the success or failure of the session startup :rtype: ``bool`` """ if 'session_name' in list(kwargs.keys()): del kwargs['session_name'] return self.resume(**kwargs) def resume(self, session_name=None, session_instance=None, session_list=None, **kwargs): """\ Resume a running/suspended X2Go session. The :func:`X2GoControlSession.resume() ` method accepts any parameter that can be passed to any of the ``X2GoTerminalSession*`` backend class constructors. :param session_name: the X2Go session name (Default value = None) :type session_name: ``str`` :param session_instance: a Python X2Go session instance (Default value = None) :type session_instance: :class:`x2go.session.X2GoSession` :param session_list: Default value = None) :param kwargs: catch any non-defined param in kwargs :type kwargs: ``dict`` :returns: True if the session could be successfully resumed :rtype: ``bool`` :raises X2GoUserException: if the remote user is not allowed to launch/resume X2Go sessions. """ if self.get_transport() is not None: if not self.is_x2gouser(self.get_transport().get_username()): raise x2go_exceptions.X2GoUserException('remote user %s is not allowed to run X2Go commands' % self.get_transport().get_username()) session_info = None try: if session_name is not None: if session_list: session_info = session_list[session_name] else: session_info = self.list_sessions()[session_name] except KeyError: _success = False _terminal = self._terminal_backend(self, profile_name=self.profile_name, session_info=session_info, info_backend=self._info_backend, list_backend=self._list_backend, proxy_backend=self._proxy_backend, client_rootdir=self.client_rootdir, session_instance=session_instance, sessions_rootdir=self.sessions_rootdir, **kwargs) _success = False try: if session_name is not None: _success = _terminal.resume() else: _success = _terminal.start() except x2go_exceptions.X2GoTerminalSessionException: _success = False if _success: while not _terminal.ok(): gevent.sleep(.2) if _terminal.ok(): self.associated_terminals[_terminal.get_session_name()] = _terminal self.get_transport().reverse_tunnels[_terminal.get_session_name()] = { 'sshfs': (0, None), 'snd': (0, None), } return _terminal or None return None def share_desktop(self, desktop=None, user=None, display=None, share_mode=0, **kwargs): """\ Share another already running desktop session. Desktop sharing can be run in two different modes: view-only and full-access mode. :param desktop: desktop ID of a sharable desktop in format ``@`` (Default value = None) :type desktop: ``str`` :param user: user name and display number can be given separately, here give the name of the user who wants to share a session with you (Default value = None) :type user: ``str`` :param display: user name and display number can be given separately, here give the number of the display that a user allows you to be shared with (Default value = None) :type display: ``str`` :param share_mode: desktop sharing mode, 0 stands for VIEW-ONLY, 1 for FULL-ACCESS mode (Default value = 0) :type share_mode: ``int`` :param kwargs: catch any non-defined param in kwargs :type kwargs: ``dict`` :returns: True if the session could be successfully shared :rtype: ``bool`` :raises X2GoDesktopSharingException: if ``username`` and ``dislpay`` do not relate to a sharable desktop session """ if desktop: user = desktop.split('@')[0] display = desktop.split('@')[1] if not (user and display): raise x2go_exceptions.X2GoDesktopSharingException('Need user name and display number of shared desktop.') cmd = '%sXSHAD%sXSHAD%s' % (share_mode, user, display) kwargs['cmd'] = cmd kwargs['session_type'] = 'shared' return self.start(**kwargs) def list_desktops(self, raw=False, maxwait=20): """\ List all desktop-like sessions of current user (or of users that have granted desktop sharing) on the connected server. :param raw: if ``True``, the raw output of the server-side X2Go command ``x2golistdesktops`` is returned. (Default value = False) :type raw: ``bool`` :param maxwait: time in secs to wait for server query to reply (Default value = 20) :type maxwait: ``int`` :returns: a list of X2Go desktops available for sharing :rtype: ``list`` :raises X2GoTimeOutException: on command execution timeouts, with the server-side ``x2golistdesktops`` command this can sometimes happen. Make sure you ignore these time-outs and to try again """ if raw: (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && x2golistdesktops") return stdout.read(), stderr.read() else: # this _success loop will catch errors in case the x2golistsessions output is corrupt # this should not be needed and is a workaround for the current X2Go server implementation if self.low_latency: maxwait = maxwait * 2 timeout = gevent.Timeout(maxwait) timeout.start() try: (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && x2golistdesktops") _stdout = stdout.read() if sys.version_info[0] >= 3: _stdout = _stdout.decode() _listdesktops = _stdout.split('\n') except gevent.timeout.Timeout: # if we do not get a reply here after seconds we will raise a time out, we have to # make sure that we catch this at places where we want to ignore timeouts (e.g. in the # desktop list cache) raise x2go_exceptions.X2GoTimeOutException('x2golistdesktop command timed out') finally: timeout.cancel() return _listdesktops def list_mounts(self, session_name, raw=False, maxwait=20): """\ List all mounts for a given session of the current user on the connected server. :param session_name: name of a session to query a list of mounts for :type session_name: ``str`` :param raw: if ``True``, the raw output of the server-side X2Go command ``x2golistmounts`` is returned. (Default value = False) :type raw: ``bool`` :param maxwait: stop processing ``x2golistmounts`` after ```` seconds (Default value = 20) :type maxwait: ``int`` :returns: a list of client-side mounts for X2Go session ```` on the server :rtype: ``list`` :raises X2GoTimeOutException: on command execution timeouts, queries with the server-side ``x2golistmounts`` query should normally be processed quickly, a time-out may hint that the control session has lost its connection to the X2Go server """ if raw: (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && x2golistmounts %s" % session_name) return stdout.read(), stderr.read() else: if self.low_latency: maxwait = maxwait * 2 # this _success loop will catch errors in case the x2golistmounts output is corrupt timeout = gevent.Timeout(maxwait) timeout.start() try: (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && x2golistmounts %s" % session_name) _stdout = stdout.read() if sys.version_info[0] >= 3: _stdout = _stdout.decode() _listmounts = {session_name: [ line for line in _stdout.split('\n') if line ] } except gevent.timeout.Timeout: # if we do not get a reply here after seconds we will raise a time out, we have to # make sure that we catch this at places where we want to ignore timeouts raise x2go_exceptions.X2GoTimeOutException('x2golistmounts command timed out') finally: timeout.cancel() return _listmounts def list_sessions(self, raw=False): """\ List all sessions of current user on the connected server. :param raw: if ``True``, the raw output of the server-side X2Go command ``x2golistsessions`` is returned. (Default value = False) :type raw: ``bool`` :returns: normally an instance of a ``X2GoServerSessionList*`` backend is returned. However, if the raw argument is set, the plain text output of the server-side ``x2golistsessions`` command is returned :rtype: ``X2GoServerSessionList`` instance or str :raises X2GoControlSessionException: on command execution timeouts, if this happens the control session will be interpreted as disconnected due to connection loss """ if raw: if 'X2GO_LIST_SHADOWSESSIONS' in self._x2go_server_features: (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && { x2golistsessions; x2golistshadowsessions; }") else: (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && x2golistsessions") return stdout.read(), stderr.read() else: # this _success loop will catch errors in case the x2golistsessions output is corrupt # this should not be needed and is a workaround for the current X2Go server implementation _listsessions = {} _success = False _count = 0 _maxwait = 20 # we will try this 20 times before giving up... we might simply catch the x2golistsessions # output in the middle of creating a session in the database... while not _success and _count < _maxwait: _count += 1 try: if 'X2GO_LIST_SHADOWSESSIONS' in self._x2go_server_features: (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && { x2golistsessions; x2golistshadowsessions; }") else: (stdin, stdout, stderr) = self._x2go_exec_command("export HOSTNAME && x2golistsessions") _stdout = stdout.read() if sys.version_info[0] >= 3: _stdout = _stdout.decode() _l = self._list_backend(_stdout, info_backend=self._info_backend) _listsessions = _l.get_sessions() _success = True except KeyError: gevent.sleep(1) except IndexError: gevent.sleep(1) except ValueError: gevent.sleep(1) if _count >= _maxwait: self.session_died = True self.disconnect() raise x2go_exceptions.X2GoControlSessionException('x2golistsessions command failed after we have tried 20 times') # update internal variables when list_sessions() is called if _success and not self.session_died: for _session_name, _terminal in list(self.associated_terminals.items()): if _session_name in list(_listsessions.keys()): # update the whole session_info object within the terminal session if hasattr(self.associated_terminals[_session_name], 'session_info') and not self.associated_terminals[_session_name].is_session_info_protected(): self.associated_terminals[_session_name].session_info.update(_listsessions[_session_name]) else: self.associated_terminals[_session_name].__del__() try: del self.associated_terminals[_session_name] except KeyError: pass self.terminated_terminals.append(_session_name) if _terminal.is_suspended(): self.associated_terminals[_session_name].__del__() try: del self.associated_terminals[_session_name] except KeyError: pass return _listsessions def clean_sessions(self, destroy_terminals=True, published_applications=False): """\ Find X2Go terminals that have previously been started by the connected user on the remote X2Go server and terminate them. :param destroy_terminals: destroy the terminal session instances after cleanup (Default value = True) :type destroy_terminals: ``bool`` :param published_applications: also clean up published applications providing sessions (Default value = False) :type published_applications: ``bool`` """ session_list = self.list_sessions() if published_applications: session_names = list(session_list.keys()) else: session_names = [ _sn for _sn in list(session_list.keys()) if not session_list[_sn].is_published_applications_provider() ] for session_name in session_names: if session_name in self.associated_terminals: self.associated_terminals[session_name].terminate() if destroy_terminals: if self.associated_terminals[session_name] is not None: self.associated_terminals[session_name].__del__() try: del self.associated_terminals[session_name] except KeyError: pass else: self.terminate(session_name=session_name) def is_connected(self): """\ Returns ``True`` if this control session is connected to the remote server (that is: if it has a valid Paramiko/SSH transport object). :returns: X2Go session connected? :rtype: ``bool`` """ return self.get_transport() is not None and self.get_transport().is_authenticated() def is_running(self, session_name): """\ Returns ``True`` if the given X2Go session is in running state, ``False`` else. :param session_name: X2Go name of the session to be queried :type session_name: ``str`` :returns: X2Go session running? If ```` is not listable by the :func:`list_sessions()` method then ``None`` is returned :rtype: ``bool`` or ``None`` """ session_infos = self.list_sessions() if session_name in list(session_infos.keys()): return session_infos[session_name].is_running() return None def is_suspended(self, session_name): """\ Returns ``True`` if the given X2Go session is in suspended state, ``False`` else. :param session_name: X2Go name of the session to be queried :type session_name: ``str`` :returns: X2Go session suspended? If ```` is not listable by the :func:`list_sessions()` method then ``None`` is returned :rtype: ``bool`` or ``None`` """ session_infos = self.list_sessions() if session_name in list(session_infos.keys()): return session_infos[session_name].is_suspended() return None def has_terminated(self, session_name): """\ Returns ``True`` if the X2Go session with name ```` has been seen by this control session and--in the meantime--has been terminated. If ```` has not been seen, yet, the method will return ``None``. :param session_name: X2Go name of the session to be queried :type session_name: ``str`` :returns: X2Go session has terminated? :rtype: ``bool`` or ``None`` """ session_infos = self.list_sessions() if session_name in self.terminated_terminals: return True if session_name not in list(session_infos.keys()) and session_name in list(self.associated_terminals.keys()): # do a post-mortem tidy up self.terminate(session_name) return True if self.is_suspended(session_name) or self.is_running(session_name): return False return None def suspend(self, session_name): """\ Suspend X2Go session with name ```` on the connected server. :param session_name: X2Go name of the session to be suspended :type session_name: ``str`` :returns: ``True`` if the session could be successfully suspended :rtype: ``bool`` """ _ret = False _session_names = [ t.get_session_name() for t in list(self.associated_terminals.values()) ] if session_name in _session_names: self.logger('suspending associated terminal session: %s' % session_name, loglevel=log.loglevel_DEBUG) (stdin, stdout, stderr) = self._x2go_exec_command("x2gosuspend-session %s" % session_name, loglevel=log.loglevel_DEBUG) stdout.read() stderr.read() if session_name in self.associated_terminals: if self.associated_terminals[session_name] is not None: self.associated_terminals[session_name].__del__() try: del self.associated_terminals[session_name] except KeyError: pass _ret = True else: self.logger('suspending non-associated terminal session: %s' % session_name, loglevel=log.loglevel_DEBUG) (stdin, stdout, stderr) = self._x2go_exec_command("x2gosuspend-session %s" % session_name, loglevel=log.loglevel_DEBUG) stdout.read() stderr.read() _ret = True return _ret def terminate(self, session_name, destroy_terminals=True): """\ Terminate X2Go session with name ```` on the connected server. :param session_name: X2Go name of the session to be terminated :type session_name: ``str`` :param destroy_terminals: destroy all terminal sessions associated to this control session (Default value = True) :type destroy_terminals: ``bool`` :returns: ``True`` if the session could be successfully terminated :rtype: ``bool`` """ _ret = False if session_name in list(self.associated_terminals.keys()): self.logger('terminating associated session: %s' % session_name, loglevel=log.loglevel_DEBUG) (stdin, stdout, stderr) = self._x2go_exec_command("x2goterminate-session %s" % session_name, loglevel=log.loglevel_DEBUG) stdout.read() stderr.read() if destroy_terminals: if self.associated_terminals[session_name] is not None: self.associated_terminals[session_name].__del__() try: del self.associated_terminals[session_name] except KeyError: pass self.terminated_terminals.append(session_name) _ret = True else: self.logger('terminating non-associated session: %s' % session_name, loglevel=log.loglevel_DEBUG) (stdin, stdout, stderr) = self._x2go_exec_command("x2goterminate-session %s" % session_name, loglevel=log.loglevel_DEBUG) stdout.read() stderr.read() _ret = True return _ret python-x2go-0.6.1.4/x2go/backends/info/__init__.py0000644000000000000000000000160114470264762016453 0ustar # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. __package__ = 'x2go.backends.info' __name__ = 'x2go.backends.info' python-x2go-0.6.1.4/x2go/backends/info/plain.py0000644000000000000000000003641714470264762016034 0ustar # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. """\ X2GoServerSessionList and X2GoServerSessionInfo classes - data handling for X2Go server sessions. This backend handles X2Go server implementations that respond with session infos via server-side PLAIN text output. """ from __future__ import print_function __NAME__ = 'x2goserversessioninfo-pylib' __package__ = 'x2go.backends.info' __name__ = 'x2go.backends.info.plain' # modules import types import re class X2GoServerSessionInfo(object): """\ :class:`x2go.backends.info.plain.X2GoServerSessionInfo` is used to store all information that is retrieved from the connected X2Go server on ``X2GoTerminalSession.start()`` resp. ``X2GoTerminalSession.resume()``. """ def __init__(self): self.initialized = False self.protected = False self.session_type_cached = None def __str__(self): return self.name def __repr__(self): result = 'X2GoServerSessionInfo(' for p in dir(self): if '__' in p or not p in self.__dict__: continue result += p + '=' + str(self.__dict__[p]) +',' return result.strip(',') + ')' def _parse_x2golistsessions_line(self, x2go_output): """\ Parse a single line of X2Go's listsessions output. :param x2go_output: output from ,,x2golistsessions'' command (as list of strings/lines) :type x2go_output: ``list`` """ try: l = x2go_output.split("|") self.agent_pid = int(l[0]) self.name = l[1] self.display = int(l[2]) self.hostname = l[3] self.status = l[4] # TODO: turn into datetime object self.date_created = l[5] self.cookie = l[6] self.graphics_port = int(l[8]) self.snd_port = int(l[9]) # TODO: turn into datetime object self.date_suspended = l[10] self.username = l[11] self.sshfs_port = int(l[13]) self.local_container = '' self.initialized = True except IndexError as e: # DEBUGGING CODE raise e except ValueError as e: # DEBUGGING CODE raise e # retrieve Telekinesis ports from list of sessions... try: self.tekictrl_port = int(l[14]) except (IndexError, ValueError) as e: self.tekictrl_port = -1 try: self.tekidata_port = int(l[15]) except (IndexError, ValueError) as e: self.tekidata_port = -1 def is_published_applications_provider(self): """\ Detect from session info if this session is a published applications provider. :returns: returns ``True`` if this session is a published applications provider :rtype: ``bool`` or ``None`` (if not initialized) """ if self.initialized: return bool(re.match('.*_stRPUBLISHED_.*', self.name)) else: return None def is_running(self): """\ Is this session running? :returns: ``True`` if the session is running, ``False`` otherwise :rtype: ``bool`` or ``None`` (if not initialized) """ if self.initialized: return self.status == 'R' else: return None def get_session_type(self): """\ Get the session type (i.e. 'D', 'K', 'R', 'S' or 'P'). :returns: session type :rtype: ``str`` or ``None`` (if not initialized) """ if self.session_type_cached is not None: return self.session_type_cached if self.initialized: cmd = self.name.split('_')[1] session_type = cmd[2] if session_type == 'R' and self.is_published_applications_provider(): session_type = 'P' self.session_type_cached = session_type return session_type else: return None def get_share_mode(self): """\ Get the share mode of a shadow session. :returns: share mode (0: view-only, 1: full access), ``None`` when used for non-desktop-sharing sessions :rtype: ``str`` or ``None`` (if not initialized) """ if self.initialized: share_mode = None cmd = self.name.split('_')[1] session_type = cmd[2] if session_type == 'S': share_mode = cmd[3] return share_mode else: return None def is_suspended(self): """\ Is this session suspended? :returns: ``True`` if the session is suspended, ``False`` otherwise :rtype: ``bool`` or ``None`` (if not initialized) """ if self.initialized: return self.status == 'S' else: return None def is_desktop_session(self): """\ Is this session a desktop session? :returns: ``True`` if this session is a desktop session, ``False`` otherwise :rtype: ``bool`` or ``None`` (if not initialized) """ if self.initialized: return self.get_session_type() in ('D', 'K') else: return None def is_kdrive_session(self): """\ Is this session a KDrive based desktop session? :returns: ``True`` if this session is a KDrive based desktop session, ``False`` otherwise :rtype: ``bool`` """ if self.initialized: return self.get_session_type() == 'K' else: return None def is_nx3_session(self): """\ Is this session an NXv3 based desktop/rootless/shadow session? :returns: ``True`` if this session is a NXv3 based desktop, rootless or shadow session, ``False`` otherwise :rtype: ``bool`` """ if self.initialized: return self.get_session_type() in ('D', 'S', 'R', 'P') else: return None def is_kdrive_session(self): """\ Is this session a KDrive based desktop session? :returns: ``True`` if this session is a KDrive based desktop session, ``False`` otherwise :rtype: ``bool`` """ return self.get_session_type() == 'K' def is_nx3_session(self): """\ Is this session an NXv3 based desktop/rootless session? :returns: ``True`` if this session is a NXv3 based desktop/rootless session, ``False`` otherwise :rtype: ``bool`` """ return self.get_session_type() in ('D','R','P') def _parse_x2gostartagent_output(self, x2go_output): """\ Parse x2gostartagent output. :param x2go_output: output from ,,x2gostartagent'' command (as list of strings/lines) :type x2go_output: ``list`` """ try: l = x2go_output.split("\n") self.name = l[3] self.cookie = l[1] self.agent_pid = int(l[2]) self.display = int(l[0]) self.graphics_port = int(l[4]) self.snd_port = int(l[5]) self.sshfs_port = int(l[6]) self.username = '' self.hostname = '' # TODO: we have to see how we fill these fields here... self.date_created = '' self.date_suspended = '' # TODO: presume session is running after x2gostartagent, this could be better self.status = 'R' self.local_container = '' self.remote_container = '' self.initialized = True except IndexError as e: # DEBUGGING CODE raise e except ValueError as e: # DEBUGGING CODE raise e # retrieve Telekinesis ports from x2gostartagent output try: self.tekictrl_port = int(l[7]) except (IndexError, ValueError) as e: self.tekictrl_port = -1 try: self.tekidata_port = int(l[8]) except (IndexError, ValueError) as e: self.tekidata_port = -1 def initialize(self, x2go_output, username='', hostname='', local_container='', remote_container=''): """\ Setup a a session info data block, includes parsing of X2Go server's ``x2gostartagent`` stdout values. :param x2go_output: X2Go server's ``x2gostartagent`` command output, each value separated by a newline character. :type x2go_output: str :param username: session user name (Default value = '') :type username: str :param hostname: hostname of X2Go server (Default value = '') :type hostname: str :param local_container: X2Go client session directory for config files, cache and session logs (Default value = '') :type local_container: str :param remote_container: X2Go server session directory for config files, cache and session logs (Default value = '') :type remote_container: str """ self.protect() self._parse_x2gostartagent_output(x2go_output) self.username = username self.hostname = hostname self.local_container = local_container self.remote_container = remote_container self.initialized = True def protect(self): """\ Write-protect this session info data structure. """ self.protected = True def unprotect(self): """\ Remove write-protection from this session info data structure. """ self.protected = False def is_protected(self): """\ Check if this session info data structure is Write-protected. """ return self.protected def is_initialized(self): """\ Check if this session info data structure has been initialized. """ return self.initialized def get_status(self): """\ Retrieve the session's status from this session info data structure. :returns: session status :rtype: ``str`` or ``None`` (if not initalized) """ if self.initialized: return self.status else: return None def clear(self): """\ Clear all properties of a :class:`x2go.backends.info.plain.X2GoServerSessionInfo` object. """ self.name = '' self.cookie = '' self.agent_pid = '' self.display = '' self.graphics_port = '' self.snd_port = '' self.sshfs_port = '' self.tekictrl_port = '' self.tekidata_port = '' self.username = '' self.hostname = '' self.date_created = '' self.date_suspended = '' self.status = '' self.local_container = '' self.remote_container = '' self.protected = False self.initialized = False self.session_type_cached = None def update(self, session_info): """\ Update all properties of a :class:`x2go.backends.info.plain.X2GoServerSessionInfo` object. :param session_info: a provided session info data structure :type session_info: ``X2GoServerSessionInfo*`` """ if type(session_info) == type(self): for prop in ('graphics_port', 'snd_port', 'sshfs_port', 'tekictrl_port', 'tekidata_port', 'date_suspended', 'status', ): if hasattr(session_info, prop): _new = getattr(session_info, prop) _current = getattr(self, prop) if _new != _current: setattr(self, prop, _new) def __init__(self): """\ Class constructor, identical to :func:`clear()` method. """ self.clear() class X2GoServerSessionList(object): """\ :class:`x2go.backends.info.plain.X2GoServerSessionList` is used to store all information that is retrieved from a connected X2Go server on a ``X2GoControlSession.list_sessions()`` call. """ def __init__(self, x2go_output=None, info_backend=X2GoServerSessionInfo): """\ :param x2go_output: X2Go server's ``x2golistsessions`` command output, each session separated by a newline character. Session values are separated by Unix Pipe Symbols ('|') :type x2go_output: str :param info_backend: the session info backend to use :type info_backend: ``X2GoServerSessionInfo*`` """ self.sessions = {} if x2go_output is not None: lines = x2go_output.split("\n") for line in lines: if not line: continue s_info = info_backend() s_info._parse_x2golistsessions_line(line) self.sessions[s_info.name] = s_info def __call__(self): return self.sessions def get_sessions(self): """\ Get the complete sessions property. :return: get this instance's dictionary of sessions :rtype: ``dict`` """ return self.sessions def set_sessions(self, sessions): """\ Set the sessions property directly by parsing a complete data structure. :param sessions: set this instance's list of sessions directly :type sessions: ``dict`` """ self.sessions = sessions def get_session_info(self, session_name): """\ Retrieve the session information for ````. :param session_name: the queried session name :type session_name: ``str`` :returns: the session info of ```` :rtype: ``X2GoServerSessionInfo*`` or ``None`` """ try: return self.sessions[session_name] except KeyError: return None def get_session_with(self, property_name, value, hostname=None): """\ Find session with a given display number on a given host. :param property_name: match a session based on this property name :type property_name: ``str`` :param value: the resulting session has to match this value for ```` :type value: ``str`` :param hostname: the result has to match this hostname (Default value = None) :type hostname: ``str`` """ if property_name == 'display': value = value.lstrip(':') if '.' in value: value = value.split('.')[0] for session in list(self.sessions.values()): try: if str(getattr(session, property_name)) == str(value): if hostname is None or session.hostname == hostname: return session except AttributeError: pass python-x2go-0.6.1.4/x2go/backends/__init__.py0000644000000000000000000000156714470264762015533 0ustar # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. __package__ = 'x2go.backends' __name__ = 'x2go.backends' python-x2go-0.6.1.4/x2go/backends/printing/file.py0000644000000000000000000002465114470264762016544 0ustar # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. """\ :class:`x2go.backends.printing.file.X2GoClientPrinting` class is one of Python X2Go's public API classes. Retrieve an instance of this class from your :class:`x2go.client.X2GoClient` instance. Use this class in your Python X2Go based applications to access the »printing« configuration of your X2Go client application. """ __NAME__ = 'x2goprinting-pylib' __package__ = 'x2go.backends.printing' __name__ = 'x2go.backends.printing.file' # modules import types import sys # Python X2Go modules import x2go.log as log import x2go.printactions as printactions # we hide the default values from epydoc (that's why we transform them to _UNDERSCORE variables) from x2go.defaults import X2GO_CLIENTPRINTING_DEFAULTS as _X2GO_CLIENTPRINTING_DEFAULTS from x2go.defaults import X2GO_PRINTING_CONFIGFILES as _X2GO_PRINTING_CONFIGFILES import x2go.inifiles as inifiles import x2go.x2go_exceptions as x2go_exceptions _print_property_map = { 'pdfview_cmd': { 'ini_section': 'view', 'ini_option': 'command', }, 'save_to_folder': { 'ini_section': 'save', 'ini_option': 'folder', }, 'printer': { 'ini_section': 'CUPS', 'ini_option': 'defaultprinter', }, 'print_cmd': { 'ini_section': 'print', 'ini_option': 'command', }, } class X2GoClientPrinting(inifiles.X2GoIniFile): """\ :class:`x2go.backends.printing.file.X2GoClientPrinting` provides access to the X2Go ini-like file »printing« as stored in ``~/.x2goclient/printing`` resp. globally ``/etc/x2goclient/printing``. An instance of :class:`x2go.backends.printing.file.X2GoClientPrinting` is created on each incoming print job. This facilitates that on every print job the print action for this job is derived from the »printing« configuration file. Thus, changes on the file are active for the next incoming print job. """ config_files = [] _print_action = None def __init__(self, config_files=_X2GO_PRINTING_CONFIGFILES, defaults=_X2GO_CLIENTPRINTING_DEFAULTS, client_instance=None, logger=None, loglevel=log.loglevel_DEFAULT): """\ :param config_files: a list of configuration files 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 X2Go's hard coded defaults in :mod:`x2go.defaults` :type defaults: ``dict`` :param logger: you can pass an :class:`x2go.log.X2GoLogger` object to the :class:`x2go.printactions.X2GoPrintAction` constructor :type logger: ``obj`` :param loglevel: if no :class:`x2go.log.X2GoLogger` object has been supplied a new one will be constructed with the given loglevel :type loglevel: ``int`` """ self.client_instance = client_instance inifiles.X2GoIniFile.__init__(self, config_files, defaults=defaults, logger=logger, loglevel=loglevel) self._detect_print_action() def _detect_print_action(self): """\ Derive a print action from sections, keys and their values in a typical X2Go client »printing« configuration file. """ _general_pdfview = self.get('General', 'pdfview', key_type=bool) _view_open = self.get('view', 'open', key_type=bool) _print_startcmd = self.get('print', 'startcmd', key_type=bool) _show_dialog = self.get('General', 'showdialog', key_type=bool) if _show_dialog and self.client_instance is not None: self._print_action = printactions.X2GoPrintActionDIALOG(client_instance=self.client_instance, logger=self.logger) elif _general_pdfview and _view_open: _view_command = self.get('view', 'command') self._print_action = printactions.X2GoPrintActionPDFVIEW(client_instance=self.client_instance, pdfview_cmd=_view_command, logger=self.logger) elif _general_pdfview and not _view_open: _safe_folder = self.get('save', 'folder') self._print_action = printactions.X2GoPrintActionPDFSAVE(client_instance=self.client_instance, save_to_folder=_safe_folder, logger=self.logger) elif not _general_pdfview and not _print_startcmd: _cups_defaultprinter = self.get('CUPS', 'defaultprinter') self._print_action = printactions.X2GoPrintActionPRINT(client_instance=self.client_instance, printer=_cups_defaultprinter, logger=self.logger) elif not _general_pdfview and _print_startcmd: _print_command = self.get('print', 'command') self._print_action = printactions.X2GoPrintActionPRINTCMD(client_instance=self.client_instance, print_cmd=_print_command, logger=self.logger) @property def print_action(self): """\ Return the print action described by the »printing« configuration file. This method has property status and wraps around the :func:`get_print_action()` :returns: Returns the print action object :rtype: ``obj`` or ``str`` """ return self.get_print_action() def get_print_action(self, reload=False, reinit=False, return_name=False): """\ Return the print action described by the »printing« configuration file. :param reload: reload the configuration file before retrieving the print action? (Default value = False) :type reload: ``bool`` :param reinit: re-detect the print action from what is stored in cache? (Default value = False) :type reinit: ``bool`` :param return_name: return the print action name, not the class (Default value = False) :type return_name: ``bool`` :returns: the configured print action :rtype: ``obj`` or ``str`` """ if reload: self.load() if reinit: self._detect_print_action() if return_name: return self._print_action.__name__ else: return self._print_action def get_property(self, print_property): """\ Retrieve a printing property as mapped by the :func:`_print_property_map()` dictionary. :param print_property: a printing property :type print_property: ``str`` :returns: the stored value for ```` :rtype: ``str`` :raises X2GoClientPrintingException: if the printing property does not exist """ if print_property in list(_print_property_map.keys()): _ini_section = _print_property_map[print_property]['ini_section'] _ini_option = _print_property_map[print_property]['ini_option'] return self.get_value(_ini_section, _ini_option) else: raise x2go_exceptions.X2GoClientPrintingException('No such X2Go client printing property ,,%s\'\'' % print_property) def set_property(self, print_property, value): """\ Set a printing property as mapped by the :func:`_print_property_map()` dictionary. :param print_property: a printing property :type print_property: ``str`` :param value: the value to be stored as ```` :type value: ``str`` :raises X2GoClientPrintingException: if the printing property does not exist or if there is a type mismatch """ if print_property in list(_print_property_map.keys()): _ini_section = _print_property_map[print_property]['ini_section'] _ini_option = _print_property_map[print_property]['ini_option'] _default_type = self.get_type(_ini_section, _ini_option) if sys.version_info[0] < 3: if type(value) is str: value = value.encode('utf-8') if _default_type != type(value): raise x2go_exceptions.X2GoClientPrintingException('Type mismatch error for property ,,%s\'\' - is: %s, should be: %s' % (print_property, str(type(value)), str(_default_type))) self.update_value(_ini_section, _ini_option, value) else: raise x2go_exceptions.X2GoClientPrintingException('No such X2Go client printing property ,,%s\'\'' % print_property) def store_print_action(self, print_action, **print_properties): """\ Accept a new print action configuration. This includes the print action itself (DIALOG, PDFVIEW, PDFSAVE, PRINT or PRINTCMD) and related printing properties as mapped by the :func:`_print_property_map()` dictionary. :param print_action: the print action name :type print_action: ``str`` :param print_properties: the printing properties to set for the given print action :type print_properties: ``dict`` """ if print_action == 'DIALOG': self.update_value('General', 'showdialog', True) else: self.update_value('General', 'showdialog', False) if print_action == 'PDFVIEW': self.update_value('General', 'pdfview', True) self.update_value('view', 'open', True) elif print_action == 'PDFSAVE': self.update_value('General', 'pdfview', True) self.update_value('view', 'open', False) elif print_action == 'PRINT': self.update_value('General', 'pdfview', False) self.update_value('print', 'startcmd', False) elif print_action == 'PRINTCMD': self.update_value('General', 'pdfview', False) self.update_value('print', 'startcmd', True) for print_property in list(print_properties.keys()): self.set_property(print_property, print_properties[print_property]) python-x2go-0.6.1.4/x2go/backends/printing/__init__.py0000644000000000000000000000161114470264762017353 0ustar # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. __package__ = 'x2go.backends.printing' __name__ = 'x2go.backends.printing' python-x2go-0.6.1.4/x2go/backends/profiles/base.py0000644000000000000000000007057714470264762016540 0ustar # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. """\ :class:`x2go.backends.profiles.base.X2GoSessionProfiles` class - managing X2Go Client session profiles, base API. :class:`x2go.backends.profiles.base.X2GoSessionProfiles` is a public API class. Use this class in your Python X2Go based applications. """ __NAME__ = 'x2gosessionprofiles-pylib' __package__ = 'x2go.backends.profiles' __name__ = 'x2go.backends.profiles.base' import copy import types import re # Python X2Go modules from x2go.defaults import X2GO_SESSIONPROFILE_DEFAULTS as _X2GO_SESSIONPROFILE_DEFAULTS from x2go.defaults import X2GO_DESKTOPSESSIONS as _X2GO_DESKTOPSESSIONS import x2go.log as log import x2go.utils as utils from x2go.x2go_exceptions import X2GoProfileException class X2GoSessionProfiles(object): defaultSessionProfile = copy.deepcopy(_X2GO_SESSIONPROFILE_DEFAULTS) _non_profile_sections = ('embedded') def __init__(self, session_profile_defaults=None, logger=None, loglevel=log.loglevel_DEFAULT, **kwargs): """\ Retrieve X2Go session profiles. Base class for the different specific session profile configuration backends. :param session_profile_defaults: a default session profile :type session_profile_defaults: ``dict`` :param logger: you can pass an :class:`x2go.log.X2GoLogger` object to the :class:`x2go.backends.profiles.httpbroker.X2GoSessionProfiles` constructor :type logger: :class:`x2go.log.X2GoLogger` instance :param loglevel: if no :class:`x2go.log.X2GoLogger` object has been supplied a new one will be constructed with the given loglevel :type loglevel: ``int`` """ self.defaultValues = {} self._profile_metatypes = {} self._cached_profile_ids = {} self.__useexports = {} self._profiles_need_profile_id_renewal = [] self.write_user_config = False if logger is None: self.logger = log.X2GoLogger(loglevel=loglevel) else: self.logger = copy.deepcopy(logger) self.logger.tag = __NAME__ if utils._checkSessionProfileDefaults(session_profile_defaults): self.defaultSessionProfile = session_profile_defaults self.populate_session_profiles() def __call__(self, profile_id_or_name): """\ Retrieve the session profile configuration for a given session profile ID (or name) :param profile_id_or_name: profile ID or profile name :type profile_id_or_name: ``str`` :returns: the profile ID's / name's profile configuration :rtype: ``dict`` """ _profile_id = self.check_profile_id_or_name(self, profile_id_or_name) return self.get_profile_config(profile_id=_profile_id) def init_profile_cache(self, profile_id_or_name): """\ Some session profile backends (e.g. the broker backends cache dynamic session profile data). On new connections, it is recommented to (re-)initialize these caches. :param profile_id_or_name: profile ID or profile name :type profile_id_or_name: ``str`` """ profile_id = self.check_profile_id_or_name(profile_id_or_name) # allow backend specific clean-up self._init_profile_cache(profile_id) def _init_profile_cache(self, profile_id): """\ Inherit from this class to (re-)initialize profile ID based cache storage. :param profile_id: profile ID :type profile_id: ``str`` """ pass def populate_session_profiles(self): """\ Load a session profile set from the configuration storage backend and make it available for this class. :returns: a set of session profiles :rtype: ``dict`` """ self.session_profiles = self. _populate_session_profiles() # scan for duplicate profile names and handle them... scan_profile_names = {} for profile_id in list(self.session_profiles.keys()): profile_name = self.to_profile_name(profile_id) if profile_name not in list(scan_profile_names.keys()): scan_profile_names[profile_name] = [profile_id] else: scan_profile_names[profile_name].append(profile_id) _duplicates = {} for profile_name in list(scan_profile_names.keys()): if len(scan_profile_names[profile_name]) > 1: _duplicates[profile_name] = scan_profile_names[profile_name] for profile_name in list(_duplicates.keys()): i = 1 for profile_id in _duplicates[profile_name]: self.update_value(None, 'name', '{name} ({i})'.format(name=profile_name, i=i), profile_id=profile_id) i += 1 def _populate_session_profiles(self): """\ Inherit from this class and provide the backend specific way of loading / populating a set of session profile via this method. :returns: a set of session profiles :rtype: ``dict`` """ return {} def get_profile_metatype(self, profile_id_or_name, force=False): """\ Detect a human readable session profile type from the session profile configuration. :param profile_id_or_name: profile ID or profile name :type profile_id_or_name: ``str`` :param force: re-detect the meta type, otherwise use a cached result (Default value = False) :type force: ``bool`` :returns: the profile ID's / name's meta type :rtype: ``str`` """ _profile_id = self.check_profile_id_or_name(profile_id_or_name) if _profile_id not in self._profile_metatypes or force: _config = self.get_profile_config(_profile_id) if _config['host']: if _config['rdpserver'] and _config['command'] == 'RDP': _metatype = 'RDP/proxy' elif _config['published']: if _config['command'] in list(_X2GO_DESKTOPSESSIONS.keys()): _metatype = '%s + Published Applications' % _config['command'] else: _metatype = 'Published Applications' elif _config['rootless']: _metatype = 'Single Applications' elif _config['command'] in list(_X2GO_DESKTOPSESSIONS.keys()): _metatype = '%s Desktop' % _config['command'] elif _config['command'] in list(_X2GO_DESKTOPSESSIONS.values()): _metatype = '%s Desktop' % [ s for s in list(_X2GO_DESKTOPSESSIONS.keys()) if _config['command'] == _X2GO_DESKTOPSESSIONS[s] ][0] else: _metatype = 'CUSTOM Desktop' else: if _config['rdpserver'] and _config['command'] == 'RDP': _metatype = 'RDP/direct' else: _metatype = 'not supported' self._profile_metatypes[_profile_id] = str(_metatype) else: return self._profile_metatypes[_profile_id] def is_mutable(self, profile_id_or_name=None, profile_id=None): """\ Check if a given profile name (or ID) is mutable or not. :param profile_id_or_name: profile name or profile ID (Default value = None) :type profile_id_or_name: ``str`` :param profile_id: if the profile ID is known, pass it in directly and skip the :func:`check_profile_id_or_name()` call (Default value = None) :type profile_id: ``str`` :returns: ``True`` if the session profile of the specified name/ID is mutable :rtype: ``bool`` :raises X2GoProfileException: if no such session profile exists """ try: profile_id = profile_id or self.check_profile_id_or_name(profile_id_or_name) return self._is_mutable(profile_id) except X2GoProfileException: return None def _is_mutable(self, profile_id): """\ Inherit from this base class and provide your own decision making code here if a given profile ID is mutable or not. :param profile_id: profile ID :type profile_id: ``str`` :returns: ``True`` if the session profile of the specified ID is mutable :rtype: ``bool`` """ return False def supports_mutable_profiles(self): """\ Check if the current session profile backend supports mutable session profiles. :returns: list of mutable profiles :rtype: ``list`` """ return self._supports_mutable_profiles() def _supports_mutable_profiles(self): """\ Inherit from this base class and provide your own decision making code here if a your session profile backend supports mutable session profiles or not. :returns: list of mutable profiles :rtype: ``list`` """ return False def mutable_profile_ids(self): """\ List all mutable session profiles. :returns: List up all session profile IDs of mutable session profiles. :rtype: ``bool`` """ return [ p for p in self.profile_ids if self._is_mutable(p) ] def write(self): """\ Store session profile data to the storage backend. :returns: ``True`` if the write process has been successfull, ``False`` otherwise :rtype: ``bool`` """ # then update profile IDs for profiles that have a renamed host attribute... for profile_id in self._profiles_need_profile_id_renewal: _config = self.get_profile_config(profile_id=profile_id) self._delete_profile(profile_id) try: del self._cached_profile_ids[profile_id] except KeyError: pass self.add_profile(profile_id=None, force_add=True, **_config) self._profiles_need_profile_id_renewal = [] self._cached_profile_ids = {} return self._write() def _write(self): """\ Write session profiles back to session profile storage backend. Inherit from this class and adapt to the session profile backend via this method. """ return True def get_profile_option_type(self, option): """\ Get the data type for a specific session profile option. :param option: the option to get the data type for :type option: will be detected by this method :returns: the data type of ``option`` :rtype: ``type`` """ try: return type(self.defaultSessionProfile[option]) except KeyError: return bytes def get_profile_config(self, profile_id_or_name=None, parameter=None, profile_id=None): """\ The configuration options for a single session profile. :param profile_id_or_name: either profile ID or profile name is accepted (Default value = None) :type profile_id_or_name: ``str`` :param parameter: if specified, only the value for the given parameter is returned (Default value = None) :type parameter: ``str`` :param profile_id: profile ID (faster than specifying ``profile_id_or_name``) (Default value = None) :type profile_id: ``str`` :returns: the session profile configuration for the given profile ID (or name) :rtype: ``dict`` """ _profile_id = profile_id or self.check_profile_id_or_name(profile_id_or_name) _profile_config = {} if parameter is None: parameters = self._get_profile_options(_profile_id) else: parameters = [parameter] for option in parameters: value = self._get_profile_parameter(_profile_id, option, key_type=self.get_profile_option_type(option)) if type(value) is bytes: value = str(value) # the type check is a nasty Python2/Python3 hack around unicodes and strings if option == 'export' and (type(value) is type(u'') or type(value) is type('')): _value = value.replace(',', ';').strip().strip('"').strip().strip(';').strip() value = {} if _value: _export_paths = _value.split(';') for _path in _export_paths: if not re.match('.*:(0|1)$', _path): _path = '%s:1' % _path _auto_export_path = re.match('.*:1$', _path) and True or False _export_path = ':'.join(_path.split(':')[:-1]) value[_export_path] = _auto_export_path _profile_config[option] = value if parameter is not None: if parameter in list(_profile_config.keys()): value = _profile_config[parameter] return value else: raise X2GoProfileException('no such session profile parameter: %s' % parameter) return _profile_config def default_profile_config(self): """\ Return a default session profile. :returns: default session profile :rtype: ``dict`` """ return copy.deepcopy(self.defaultSessionProfile) def has_profile(self, profile_id_or_name): """\ Does a session profile of a given profile ID or profile name exist? :param profile_id_or_name: profile ID or profile name :type profile_id_or_name: ``str`` :returns: ``True`` if there is such a session profile, ``False`` otherwise :rtype: ``bool`` """ try: self.check_profile_id_or_name(profile_id_or_name) return True except X2GoProfileException: return False def _update_profile_ids_cache(self): for p in self._get_profile_ids(): if p not in self._non_profile_sections: self._cached_profile_ids[p] = self.to_profile_name(p) @property def profile_ids(self): """\ Render a list of all profile IDs found in the session profiles configuration. """ if not self._cached_profile_ids: self._update_profile_ids_cache() return list(self._cached_profile_ids.keys()) def _get_profile_ids(self): """\ Inherit from this class and provide a way for actually getting a list of session profile IDs from the storage backend via this method. :returns: list of available session profile IDs :rtype: ``list`` """ return [] def has_profile_id(self, profile_id): """\ Does a session profile of a given profile ID exist? (Faster than :func:`has_profile()`.) :param profile_id: profile ID :type profile_id: ``str`` :returns: ``True`` if there is such a session profile, ``False`` otherwise :rtype: ``bool`` """ return str(profile_id) in self.profile_ids @property def profile_names(self): """\ Render a list of all profile names found in the session profiles configuration. """ if not self._cached_profile_ids: self._update_profile_ids_cache() return list(self._cached_profile_ids.values()) def has_profile_name(self, profile_name): """\ Does a session profile of a given profile name exist? (Faster than :func:`has_profile()`.) :param profile_name: profile name :type profile_name: ``str`` :returns: ``True`` if there is such a session profile, ``False`` otherwise :rtype: ``bool`` """ return str(profile_name) in self.profile_names def to_profile_id(self, profile_name): """\ Convert profile name to profile ID. :param profile_name: profile name :type profile_name: ``str`` :returns: profile ID :rtype: ``str`` """ _profile_ids = [ p for p in self.profile_ids if self._cached_profile_ids[p] == profile_name ] if len(_profile_ids) == 1: return str(_profile_ids[0]) elif len(_profile_ids) == 0: return None else: raise X2GoProfileException('The sessions config file contains multiple session profiles with name: %s' % profile_name) def to_profile_name(self, profile_id): """\ Convert profile ID to profile name. :param profile_id: profile ID :type profile_id: ``str`` :returns: profile name :rtype: ``str`` """ try: _profile_name = self.get_profile_config(profile_id=profile_id, parameter='name') return str(_profile_name) except: return '' def add_profile(self, profile_id=None, force_add=False, **kwargs): """\ Add a new session profile. :param profile_id: a custom profile ID--if left empty a profile ID will be auto-generated (Default value = None) :type profile_id: ``str`` :param kwargs: session profile options for this new session profile :type kwargs: ``dict`` :param force_add: enforce adding of the given profile (Default value = False) :type force_add: ``bool`` :returns: the (auto-generated) profile ID of the new session profile :rtype: ``str`` """ if profile_id is None or profile_id in self.profile_ids: profile_id = utils._genSessionProfileId() self.session_profiles[profile_id] = self.default_profile_config() if 'name' not in list(kwargs.keys()): raise X2GoProfileException('session profile parameter ,,name\'\' is missing in method parameters') if kwargs['name'] in self.profile_names and not force_add: raise X2GoProfileException('a profile of name ,,%s\'\' already exists' % kwargs['name']) self._cached_profile_ids[profile_id] = kwargs['name'] for key, value in list(kwargs.items()): self.update_value(None, key, value, profile_id=profile_id) _default_session_profile = self.default_profile_config() for key, value in list(_default_session_profile.items()): if key in kwargs: continue self.update_value(None, key, value, profile_id=profile_id) self._cached_profile_ids = {} return str(profile_id) def delete_profile(self, profile_id_or_name): """\ Delete a session profile from the configuration file. :param profile_id_or_name: profile ID or profile name :type profile_id_or_name: ``str`` """ _profile_id = self.check_profile_id_or_name(profile_id_or_name) self._delete_profile(_profile_id) self.write_user_config = True self.write() self._cached_profile_ids = {} def _delete_profile(self, profile_id): """\ Inherit from this class and provide a way for actually deleting a complete session profile from the storage backend via this method. :param profile_id: Profile ID of the profile to be deleted :type profile_id: ``str`` """ pass def update_value(self, profile_id_or_name, option, value, profile_id=None): """\ Update a value in a session profile. :param profile_id_or_name: the profile ID :type profile_id_or_name: ``str`` :param option: the session profile option of the given profile ID :type option: ``str`` :param value: the value to update the session profile option with :type value: any type, depends on the session profile option :param profile_id: if the profile ID is known, pass it in directly and skip the :func:`check_profile_id_or_name()` call (Default value = None) :type profile_id: ``str`` """ try: profile_id = profile_id or self.check_profile_id_or_name(profile_id_or_name) except X2GoProfileException: profile_id = profile_id_or_name if not self.is_mutable(profile_id=profile_id): raise X2GoProfileException("session profile cannot be modified, it is marked as immutable") if option == 'name': profile_name = value current_profile_name = self.get_value(profile_id, option) if not profile_name: raise X2GoProfileException('profile name for profile id %s must not be empty' % profile_id) else: if profile_name != current_profile_name: try: del self._cached_profile_ids[profile_id] except KeyError: pass if profile_name in self.profile_names: raise X2GoProfileException('a profile of name ,,%s\'\' already exists' % profile_name) self._cached_profile_ids[profile_id] = profile_name if option == 'export' and type(value) == dict: _strvalue = '"' for folder in list(value.keys()): _strvalue += "%s:%s;" % (folder, int(value[folder])) _strvalue += '"' _strvalue = _strvalue.replace('""', '') value = _strvalue if option == 'host': _host = self.get_profile_config(profile_id=profile_id, parameter='host') if _host != value and _host is not None: self._profiles_need_profile_id_renewal.append(profile_id) if type(value) is tuple: value = list(value) if type(value) is not list: value = value.split(',') self._update_value(profile_id, option, value) def _update_value(self, profile_id, option, value): """\ Inherit from this class and provide for actually updating a session profile's value in the storage backend via this method. :param profile_id: the profile ID of the profile to be updated :type profile_id: ``str`` :param option: the option to be updated :type option: ``str`` :param value: the value to be updated for the given option :type value: ``str`` or ``list`` or ``int`` or ``bool`` """ pass def check_profile_id_or_name(self, profile_id_or_name): """\ Detect the profile ID from a given string which maybe profile ID or profile name. :param profile_id_or_name: profile ID or profile name :type profile_id_or_name: ``str`` :returns: profile ID :rtype: ``str`` :raises X2GoProfileException: if no such session profile exists """ _profile_id = None if self.has_profile_name(profile_id_or_name): # we were given a sesion profile name... _profile_id = self.to_profile_id(profile_id_or_name) elif self.has_profile_id(profile_id_or_name): # we were given a session profile id... _profile_id = profile_id_or_name else: raise X2GoProfileException('No session profile with id or name ,,%s\'\' exists.' % profile_id_or_name) if _profile_id is not None: _profile_id = str(_profile_id) return _profile_id def to_session_params(self, profile_id_or_name=None, profile_id=None): """\ Convert session profile options to :class:`x2go.session.X2GoSession` constructor method parameters. :param profile_id_or_name: either profile ID or profile name is accepted (Default value = None) :type profile_id_or_name: ``str`` :param profile_id: profile ID (fast than specifying ``profile_id_or_name``) (Default value = None) :type profile_id: ``str`` :returns: a dictionary of :class:`x2go.session.X2GoSession` constructor method parameters :rtype: ``dict`` """ _profile_id = profile_id or self.check_profile_id_or_name(profile_id_or_name) return utils._convert_SessionProfileOptions_2_SessionParams(self.get_profile_config(_profile_id)) def get_session_param(self, profile_id_or_name, param): """\ Get a single :class:`x2go.session.X2GoSession` parameter from a specific session profile. :param profile_id_or_name: either profile ID or profile name is accepted :type profile_id_or_name: ``str`` :param param: the parameter name in the :class:`x2go.session.X2GoSession` constructor method :type param: ``str`` :returns: the value of the session profile option represented by ``param`` :rtype: depends on the session profile option requested """ return self.to_session_params(profile_id_or_name)[param] def _get_profile_parameter(self, profile_id, option, key_type): """\ Inherit from this class and provide a way for actually obtaining the value of a specific profile parameter. :param profile_id: the profile's unique ID :type profile_id: ``str`` :param option: the session profile option for which to retrieve its value :type option: ``str`` :param key_type: type of the value to return :type key_type: ``typeobject`` :returns: value of a session profile parameter :rtype: ``various types`` """ return None def _get_profile_options(self, profile_id): """\ Inherit from this class and provide a way for actually obtaining a list of available profile options of a given session profile. :param profile_id: the profile ID of the profile to operate on :type profile_id: ``str`` :returns: list of available option is the given session profile :rtype: ``list`` """ return [] def get_server_hostname(self, profile_id): """\ Retrieve host name of the X2Go Server configured in a session profile. :param profile_id: the profile's unique ID :type profile_id: ``str`` :returns: the host name of the X2Go Server configured by the session profile of the given profile ID :rtype: ``list`` """ return str(self._get_server_hostname(profile_id)) def _get_server_hostname(self, profile_id): """\ Inherit from this class and provide a way for actually obtaining a the server host name for a given profile ID. :param profile_id: the profile's unique ID :type profile_id: ``str`` :returns: the host name of the X2Go Server configured by the session profile of the given profile ID :rtype: ``list`` """ return 'localhost' def get_server_port(self, profile_id): """\ Retrieve SSH port of the X2Go Server configured in a session profile. :param profile_id: the profile's unique ID :type profile_id: ``str`` :returns: the SSH port of the X2Go Server configured by the session profile of the given profile ID :rtype: ``list`` """ return self._get_server_port(profile_id) def _get_server_port(self, profile_id): """\ Inherit from this class and provide a way for actually obtaining a the server SSH port for a given profile ID. :param profile_id: the profile's unique ID :type profile_id: ``str`` :returns: the SSH port of the X2Go Server configured by the session profile of the given profile ID :rtype: ``list`` """ return 22 def get_pkey_object(self, profile_id): """\ If available, return a PKey (Paramiko/SSH private key) object. :param profile_id: the profile's unique ID :type profile_id: ``str`` :returns: a Paramiko/SSH PKey object :rtype: ``obj`` """ return self._get_pkey_object(profile_id) def _get_pkey_object(self, profile_id): """\ Inherit from this class and provide a way for actually providing such a PKey object. :param profile_id: the profile ID for which to retrieve the PKey object :type profile_id: ``str`` """ return None python-x2go-0.6.1.4/x2go/backends/profiles/file.py0000644000000000000000000001315414470264762016531 0ustar # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. """\ :class:`x2go.backends.profiles.file.X2GoSessionProfiles` class - managing X2Go Client session profiles read from a file (``~/.x2goclient/sessions``). :class:`x2go.backends.profiles.base.X2GoSessionProfiles` is a public API class. Use this class in your Python X2Go based applications. """ __NAME__ = 'x2gosessionprofiles-pylib' __package__ = 'x2go.backends.profiles' __name__ = 'x2go.backends.profiles.file' import random # Python X2Go modules from x2go.defaults import X2GO_SESSIONPROFILES_CONFIGFILES as _X2GO_SESSIONPROFILES_CONFIGFILES import x2go.backends.profiles.base as base import x2go.inifiles as inifiles import x2go.log as log class X2GoSessionProfiles(base.X2GoSessionProfiles, inifiles.X2GoIniFile): def __init__(self, config_files=_X2GO_SESSIONPROFILES_CONFIGFILES, session_profile_defaults=None, logger=None, loglevel=log.loglevel_DEFAULT, **kwargs): """\ Retrieve X2Go session profiles from a file, typically ``~/.x2goclient/sessions``. :param config_files: a list of config file locations, the first file name in this list the user has write access to will be the user configuration file :type config_files: ``list`` :param session_profile_defaults: a default session profile :type session_profile_defaults: ``dict`` :param logger: you can pass an :class:`x2go.log.X2GoLogger` object to the :class:`x2go.backends.profiles.file.X2GoSessionProfiles` constructor :type logger: :class:`x2go.log.X2GoLogger` instance :param loglevel: if no :class:`x2go.log.X2GoLogger` object has been supplied a new one will be constructed with the given loglevel :type loglevel: ``int`` """ # providing defaults for an X2GoSessionProfiles instance will---in the worst case---override your # existing sessions file in your home directory once you write the sessions back to file... inifiles.X2GoIniFile.__init__(self, config_files=config_files, logger=logger, loglevel=loglevel) base.X2GoSessionProfiles.__init__(self, session_profile_defaults=session_profile_defaults, logger=logger, loglevel=loglevel) def get_type(self, section, key): """\ Override the inifile class's get_type method due to the special layout of the session profile class. :param section: INI file section :type section: ``str`` :param key: key in INI file section :type key: ``str`` :returns: the data type of ``key`` in ``section`` :rtype: ``type`` """ # we have to handle the get_type method separately... return self.get_profile_option_type(key) def _populate_session_profiles(self): """\ Populate the set of session profiles by loading the session profile configuration from a file in INI format. :returns: a set of session profiles :rtype: ``dict`` """ session_profiles = [ p for p in self.iniConfig.sections() if p not in self._non_profile_sections and p != 'none' ] _session_profiles_dict = {} for session_profile in session_profiles: for key, default_value in list(self.defaultSessionProfile.items()): if not self.iniConfig.has_option(session_profile, key): self._storeValue(session_profile, key, default_value) # update cached meta type session profile information self.get_profile_metatype(session_profile) _session_profiles_dict[session_profile] = self.get_profile_config(session_profile) return _session_profiles_dict def _is_mutable(self, profile_id): return True def _supports_mutable_profiles(self): return True def _write(self): self._write_user_config = self.write_user_config return inifiles.X2GoIniFile.write(self) def _delete_profile(self, profile_id): self.iniConfig.remove_section(profile_id) try: del self.session_profiles[profile_id] except KeyError: pass def _update_value(self, profile_id, option, value): self.session_profiles[profile_id][option] = value if option == 'host': value = ','.join(value) self._X2GoIniFile__update_value(profile_id, option, value) def _get_profile_parameter(self, profile_id, option, key_type): return self.get(profile_id, option, key_type) def _get_profile_options(self, profile_id): return [ o for o in self.iniConfig.options(profile_id) if o != "none" ] def _get_profile_ids(self): return [ s for s in self.iniConfig.sections() if s != "none" ] def _get_server_hostname(self, profile_id): return random.choice(self.get_profile_config(profile_id, 'host')) def _get_server_port(self, profile_id): return self.get_profile_config(profile_id, 'sshport') python-x2go-0.6.1.4/x2go/backends/profiles/httpbroker.py0000644000000000000000000004371414470264762020003 0ustar # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. """\ :class:`x2go.backends.profiles.httpbroker.X2GoSessionProfiles` class - managing X2Go Client session profiles obtain from an HTTP based X2Go Session Broker. :class:`x2go.backends.profiles.httpbroker.X2GoSessionProfiles` is a public API class. Use this class in your Python X2Go based applications. """ from __future__ import print_function __NAME__ = 'x2gosessionprofiles-pylib' __package__ = 'x2go.backends.profiles' __name__ = 'x2go.backends.profiles.httpbroker' import re import requests import urllib3.exceptions import copy import types import time try: import simplejson as json except ImportError: import json # Python X2Go modules from x2go.defaults import X2GO_SESSIONPROFILE_DEFAULTS as _X2GO_SESSIONPROFILE_DEFAULTS from x2go.defaults import CURRENT_LOCAL_USER as _CURRENT_LOCAL_USER import x2go.backends.profiles.base as base import x2go.log as log from x2go.utils import genkeypair import x2go.x2go_exceptions class X2GoSessionProfiles(base.X2GoSessionProfiles): defaultSessionProfile = copy.deepcopy(_X2GO_SESSIONPROFILE_DEFAULTS) def __init__(self, session_profile_defaults=None, broker_url="http://localhost:8080/json/", broker_username=None, broker_password=None, logger=None, loglevel=log.loglevel_DEFAULT, **kwargs): """\ Retrieve X2Go session profiles from a HTTP(S) session broker. :param session_profile_defaults: a default session profile :type session_profile_defaults: ``dict`` :param broker_url: URL for accessing the X2Go Session Broker :type broker_url: ``str`` :param broker_password: use this password for authentication against the X2Go Session Broker (avoid password string in the ``broker_URL`` parameter is highly recommended) :type broker_password: ``str`` :param logger: you can pass an :class:`x2go.log.X2GoLogger` object to the :class:`x2go.backends.profiles.httpbroker.X2GoSessionProfiles` constructor :type logger: :class:`x2go.log.X2GoLogger` instance :param loglevel: if no :class:`x2go.log.X2GoLogger` object has been supplied a new one will be constructed with the given loglevel :type loglevel: ``int`` """ if broker_url.upper() != "HTTP": match = re.match('^(?P(http(|s)))://(|(?P[a-zA-Z0-9_\.-]+)(|:(?P.*))@)(?P[a-zA-Z0-9\.-]+)(|:(?P[0-9]+))($|/(?P.*)$)', broker_url) p = match.groupdict() if p['user']: self.broker_username = p['user'] else: self.broker_username = broker_username if p['password']: self.broker_password = p['password'] elif broker_password: self.broker_password = broker_password else: self.broker_password = None # fine-tune the URL p['path'] = "/{path}".format(**p) if p['port'] is not None: p['port'] = ":{port}".format(**p) else: p['port'] = '' self.broker_url = "{protocol}://{hostname}{port}{path}".format(**p) else: self.broker_username = broker_username self.broker_password = broker_password self.broker_url = broker_url self.broker_noauth = False self.broker_authid = None self._broker_profile_cache = {} self._mutable_profile_ids = None self._broker_auth_successful = None self._broker_type = "http" base.X2GoSessionProfiles.__init__(self, session_profile_defaults=session_profile_defaults, logger=logger, loglevel=loglevel) if self.broker_url != "HTTP": self.logger("Using session broker at URL: %s" % self.broker_url, log.loglevel_NOTICE) # for broker based autologin, we have to be able to provide public/private key pair self.broker_my_pubkey, self.broker_my_privkey = genkeypair(local_username=_CURRENT_LOCAL_USER, client_address='127.0.0.1') def get_broker_noauth(self): """\ Accessor for the class's ``broker_noauth`` property. :returns: ``True`` if the broker probably does not expect authentication. :rtype: ``bool`` """ return self.broker_noauth def get_broker_username(self): """\ Accessor for the class's ``broker_username`` property. :returns: the username used for authentication against the session broker URL :rtype: ``str`` """ return self.broker_username def get_broker_url(self): """\ Accessor for the class's ``broker_url`` property. :returns: the session broker URL that was used at broker session instantiation :rtype: ``str`` """ return self.broker_url def set_broker_url(self, broker_url): """\ Mutator for the class's ``broker_url`` property. :param broker_url: A new broker URL to use with this instance. Format is ``://:/`` (where protocol has to be ``http`` or ``https``. :type broker_url: ``str`` :returns: the session broker URL that was used at broker session instantiation :rtype: ``str`` """ self.broker_url = broker_url def get_broker_type(self): """\ Accessor of the class's {_broker_type} property. :returns: either ``http`` or ``https``. :rtype: ``str`` """ return self._broker_type def broker_simpleauth(self, broker_username, broker_password): """\ Attempt a username / password authentication against the instance's broker URL. :param broker_username: username to use for authentication :type broker_username: ``str`` :param broker_password: password to use for authentication :type broker_password: ``str`` :returns: ``True`` if authentication has been successful :rtype: ``bool`` :raises X2GoBrokerConnectionException: Raised on any kind of connection / authentication failure. """ if self.broker_url is not None: request_data = { 'user': broker_username or '', } if self.broker_authid is not None: request_data['authid'] = self.broker_authid self.logger("Sending request to broker: user: {user}, authid: {authid}".format(**request_data), log.loglevel_DEBUG) else: if broker_password: request_data['password'] = "" else: request_data['password'] = "" self.logger("Sending request to broker: user: {user}, password: {password}".format(**request_data), log.loglevel_DEBUG) request_data['password'] = broker_password or '' try: r = requests.post(self.broker_url, data=request_data) except (requests.exceptions.ConnectionError, requests.exceptions.MissingSchema, urllib3.exceptions.LocationParseError): raise x2go.x2go_exceptions.X2GoBrokerConnectionException('Failed to connect to URL %s' % self.broker_url) if r.status_code == 200: payload = json.loads(r.text) if not self.broker_authid and not self.broker_password: self.broker_noauth = True elif 'next-authid' in payload: self.broker_authid = payload['next-authid'] self.broker_username = broker_username or '' self.broker_password = broker_password or '' self._broker_auth_successful = True self.populate_session_profiles() return True self._broker_auth_successful = False self.broker_authid = None return False def broker_disconnect(self): """\ Disconnect from an (already) authenticated broker session. All authentication parameters will be dropped (forgotten) and this instance has to re-authenticate against / re-connect to the session broker before any new interaction with the broker is possible. """ _profile_ids = copy.deepcopy(self.profile_ids) # forget nearly everything... for profile_id in _profile_ids: self.init_profile_cache(profile_id) try: del self._profile_metatypes[profile_id] except KeyError: pass try: self._profiles_need_profile_id_renewal.remove(profile_id) except ValueError: pass try: del self._cached_profile_ids[profile_id] except KeyError: pass del self.session_profiles[profile_id] self._mutable_profile_ids = None self._broker_auth_successful = False self.broker_authid = None self.broker_password = None self.broker_noauth = False def is_broker_authenticated(self): """\ Detect if an authenticated broker session has already been initiated. Todo so, a simple re-authentication (username, password) will be attempted. If that fails, user credentials are not provided / valid. :returns: ``True`` if the broker session has already been authenticated and user credentials are known / valid :rtype: ``bool`` """ if self._broker_auth_successful is None: # do a test auth against the given broker URL try: self.broker_simpleauth(self.broker_username, self.broker_password) except x2go.x2go_exceptions.X2GoBrokerConnectionException: self._broker_auth_successful = False return self._broker_auth_successful def broker_listprofiles(self): """\ Obtain a session profile list from the X2Go Session Broker. :returns: session profiles as a Python dictionary. :rtype: ``dict`` """ if self.broker_url is not None: request_data = { 'task': 'listprofiles', 'user': self.broker_username, } if self.broker_authid is not None: request_data['authid'] = self.broker_authid self.logger("Sending request to broker: user: {user}, authid: {authid}, task: {task}".format(**request_data), log.loglevel_DEBUG) else: if self.broker_password: request_data['password'] = "" else: request_data['password'] = "" self.logger("Sending request to broker: user: {user}, password: {password}, task: {task}".format(**request_data), log.loglevel_DEBUG) request_data['password'] = self.broker_password or '' try: r = requests.post(self.broker_url, data=request_data) except requests.exceptions.ConnectionError: raise x2go.x2go_exceptions.X2GoBrokerConnectionException('Failed to connect to URL %s' % self.broker_url) if r.status_code == 200 and r.headers['content-type'].startswith("text/json"): payload = json.loads(r.text) if 'next-authid' in payload: self.broker_authid = payload['next-authid'] if 'mutable_profile_ids' in payload: self._mutable_profile_ids = payload['mutable_profile_ids'] self._broker_auth_successful = True return payload['profiles'] if payload['task'] == 'listprofiles' else {} self._broker_auth_successful = False self.broker_authid = None return {} def broker_selectsession(self, profile_id): """\ Select a session from the list of available session profiles (presented by :func:`broker_listprofiles()`). This method requests a session information dictionary (server, port, SSH keys, already running / suspended sessions, etc.) from the session broker for the provided ``profile_id``. :param profile_id: profile ID of the selected session profile :type profile_id: ``str`` :returns: session information (server, port, SSH keys, etc.) for a selected session profile (i.e. ``profile_id``) :rtype: ``dict`` """ if self.broker_url is not None: if profile_id not in self._broker_profile_cache or not self._broker_profile_cache[profile_id]: request_data = { 'task': 'selectsession', 'profile-id': profile_id, 'user': self.broker_username, 'pubkey': self.broker_my_pubkey, } if self.broker_authid is not None: request_data['authid'] = self.broker_authid self.logger("Sending request to broker: user: {user}, authid: {authid}, task: {task}".format(**request_data), log.loglevel_DEBUG) else: if self.broker_password: request_data['password'] = "" else: request_data['password'] = "" self.logger("Sending request to broker: user: {user}, password: {password}, task: {task}".format(**request_data), log.loglevel_DEBUG) request_data['password'] = self.broker_password or '' try: r = requests.post(self.broker_url, data=request_data) except requests.exceptions.ConnectionError: raise x2go.x2go_exceptions.X2GoBrokerConnectionException('Failed to connect to URL %s' % self.broker_url) if r.status_code == 200 and r.headers['content-type'].startswith("text/json"): payload = json.loads(r.text) if 'next-authid' in payload: self.broker_authid = payload['next-authid'] self._broker_profile_cache[profile_id] = payload['selected_session'] if payload['task'] == 'selectsession' else {} self._broker_auth_successful = True else: self.broker_authid = None self._broker_auth_successful = False self._broker_profile_cache[profile_id] return self._broker_profile_cache[profile_id] return {} def _init_profile_cache(self, profile_id): if str(profile_id) in self._broker_profile_cache: del self._broker_profile_cache[str(profile_id)] def _populate_session_profiles(self): """\ Populate the set of session profiles by loading the session profile configuration from a file in INI format. :returns: a set of session profiles :rtype: ``dict`` """ if self.is_broker_authenticated() and \ self.broker_noauth or \ self.broker_username and self.broker_password: session_profiles = self.broker_listprofiles() _session_profiles = copy.deepcopy(session_profiles) for session_profile in _session_profiles: session_profile = str(session_profile) for key, default_value in list(self.defaultSessionProfile.items()): key = str(key) if type(default_value) is bytes: default_value = str(default_value) if key not in session_profiles[session_profile]: session_profiles[session_profile][key] = default_value else: session_profiles = {} return session_profiles def _is_mutable(self, profile_id): if type(self._mutable_profile_ids) is list and profile_id in self._mutable_profile_ids: return True return False def _supports_mutable_profiles(self): if type(self._mutable_profile_ids) is list: return True return False def _write(self): print("not suported, yet") def _delete_profile(self, profile_id): del self.session_profiles[str(profile_id)] def _update_value(self, profile_id, option, value): if type(value) is bytes: value = str(value) self.session_profiles[str(profile_id)][str(option)] = value def _get_profile_parameter(self, profile_id, option, key_type): return key_type(self.session_profiles[str(profile_id)][str(option)]) def _get_profile_options(self, profile_id): return list(self.session_profiles[str(profile_id)].keys()) def _get_profile_ids(self): list(self.session_profiles.keys()) return list(self.session_profiles.keys()) def _get_server_hostname(self, profile_id): selected_session = self.broker_selectsession(profile_id) return selected_session['server'] def _get_server_port(self, profile_id): selected_session = self.broker_selectsession(profile_id) return int(selected_session['port']) def _get_pkey_object(self, profile_id): selected_session = self.broker_selectsession(profile_id) if 'authentication_pubkey' in selected_session and selected_session['authentication_pubkey'] == 'ACCEPTED': time.sleep(2) return self.broker_my_privkey return None python-x2go-0.6.1.4/x2go/backends/profiles/__init__.py0000644000000000000000000000160614470264762017350 0ustar # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. __package__ = 'x2go.backends.profiles' __name__ = 'x2go.backends.profiles' python-x2go-0.6.1.4/x2go/backends/profiles/sshbroker.py0000644000000000000000000000475414470264762017622 0ustar # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. """\ :class:`x2go.backends.profiles.sshbroker.X2GoSessionProfiles` class - managing X2Go Client session profiles obtained from an SSH based X2Go Session Broker. :class:`x2go.backends.profiles.sshbroker.X2GoSessionProfiles` is a public API class. Use this class in your Python X2Go based applications. """ __NAME__ = 'x2gosessionprofiles-pylib' __package__ = 'x2go.backends.profiles' __name__ = 'x2go.backends.profiles.sshbroker' # modules import copy # Python X2Go modules import x2go.backends.profiles.base as base import x2go.log as log from x2go.defaults import X2GO_SESSIONPROFILE_DEFAULTS as _X2GO_SESSIONPROFILE_DEFAULTS from x2go.x2go_exceptions import X2GoNotImplementedYetException class X2GoSessionProfiles(base.X2GoSessionProfiles): defaultSessionProfile = copy.deepcopy(_X2GO_SESSIONPROFILE_DEFAULTS) _non_profile_sections = ('embedded') def __init__(self, session_profile_defaults=_X2GO_SESSIONPROFILE_DEFAULTS, logger=None, loglevel=log.loglevel_DEFAULT): """\ Retrieve X2Go session profiles from a SSH session broker. :param session_profile_defaults: a default session profile :type session_profile_defaults: ``dict`` :param logger: you can pass an :class:`x2go.log.X2GoLogger` object to the :class:`x2go.backends.profiles.httpbroker.X2GoSessionProfiles` constructor :type logger: :class:`x2go.log.X2GoLogger` instance :param loglevel: if no :class:`x2go.log.X2GoLogger` object has been supplied a new one will be constructed with the given loglevel :type loglevel: ``int`` """ raise X2GoNotImplementedYetException('HTTPSBROKER backend support is not implemented yet') python-x2go-0.6.1.4/x2go/backends/proxy/base.py0000644000000000000000000002750014470264762016062 0ustar # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. """\ X2GoProxy class - proxying/rendering your graphical connection through NX3, KDRIVE and others. """ __NAME__ = 'x2goproxy-pylib' __package__ = 'x2go.backends.proxy' __name__ = 'x2go.backends.proxy.base' # modules import gevent import os import copy import threading import socket # Python X2Go modules import x2go.forward as forward import x2go.log as log import x2go.utils as utils import x2go.x2go_exceptions as x2go_exceptions from x2go.defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS if _X2GOCLIENT_OS in ("Windows"): import subprocess else: import x2go.gevent_subprocess as subprocess from x2go.x2go_exceptions import WindowsError from x2go.defaults import LOCAL_HOME as _LOCAL_HOME from x2go.defaults import X2GO_SESSIONS_ROOTDIR as _X2GO_SESSIONS_ROOTDIR class X2GoProxy(threading.Thread): """\ X2GoProxy is an abstract class for X2Go proxy connections. This class needs to be inherited from a concrete proxy class. Only currently available proxy class is: :class:`x2go.backends.proxy.nx3.X2GoProxy`. """ PROXY_CMD = '' """Proxy command. Needs to be set by a potential child class, might be OS specific.""" PROXY_ARGS = [] """Arguments to be passed to the proxy command. This needs to be set by a potential child class.""" PROXY_ENV = {} """Provide environment variables to the proxy command. This also needs to be set by a child class.""" session_info = None session_log_stdout = None session_log_stderr = None fw_tunnel = None proxy = None def __init__(self, session_info=None, ssh_transport=None, session_log="session.log", session_errors="session.err", sessions_rootdir=os.path.join(_LOCAL_HOME, _X2GO_SESSIONS_ROOTDIR), proxy_options={}, session_instance=None, logger=None, loglevel=log.loglevel_DEFAULT, ): """\ :param session_info: session information provided as an ``X2GoServerSessionInfo*`` backend instance :type session_info: ``X2GoServerSessionInfo*`` instance :param ssh_transport: SSH transport object from ``paramiko.SSHClient`` :type ssh_transport: ``paramiko.Transport`` instance :param session_log: name of the proxy's session logfile :type session_log: ``str`` :param sessions_rootdir: base dir where X2Go session files are stored (by default: ~/.x2go) :type sessions_rootdir: ``str`` :param proxy_options: a set of very :class:`X2GoProxy ` backend specific options; any option that is not known to the :class:`x2go.backends.proxy.base.X2GoProxy` backend will simply be ignored :type proxy_options: ``dict`` :param logger: you can pass an :class:`x2go.log.X2GoLogger` object to the :class:`x2go.backends.proxy.base.X2GoProxy` constructor :param session_instance: the :class:`x2go.session.X2GoSession` instance this :class:`x2go.backends.proxy.base.X2GoProxy` instance belongs to :type session_instance: :class:`x2go.session.X2GoSession` instance :type logger: :class:`x2go.log.X2GoLogger` instance :param loglevel: if no :class:`x2go.log.X2GoLogger` object has been supplied a new one will be constructed with the given loglevel :type loglevel: int """ if logger is None: self.logger = log.X2GoLogger(loglevel=loglevel) else: self.logger = copy.deepcopy(logger) self.logger.tag = __NAME__ self.sessions_rootdir = sessions_rootdir self.session_info = session_info self.session_name = self.session_info.name self.ssh_transport = ssh_transport self.session_log = session_log self.session_errors = session_errors self.proxy_options = proxy_options self.session_instance = session_instance self.PROXY_ENV = os.environ.copy() self.proxy = None self.subsystem = 'X2Go Proxy' threading.Thread.__init__(self) self.daemon = True def __del__(self): """\ On instance destruction make sure this proxy thread is stopped properly. """ self.stop_thread() def _tidy_up(self): """\ Close any left open port forwarding tunnel, also close session log file, if left open. """ if self.proxy: self.logger('Shutting down X2Go proxy subprocess', loglevel=log.loglevel_DEBUG) try: self.proxy.kill() except OSError as e: self.logger('X2Go proxy shutdown gave a message that we may ignore: %s' % str(e), loglevel=log.loglevel_WARN) self.proxy = None if self.fw_tunnel is not None: self.logger('Shutting down Paramiko/SSH forwarding tunnel', loglevel=log.loglevel_DEBUG) forward.stop_forward_tunnel(self.fw_tunnel) self.fw_tunnel = None if self.session_log_stdout is not None: self.session_log_stdout.close() if self.session_log_stderr is not None: self.session_log_stderr.close() def stop_thread(self): """\ End the thread runner and tidy up. """ self._keepalive = False # wait for thread loop to finish... while self.proxy is not None: gevent.sleep(.5) def run(self): """\ Start the X2Go proxy command. The X2Go proxy command utilizes a Paramiko/SSH based forwarding tunnel (openssh -L option). This tunnel gets started here and is forked into background (Greenlet/gevent). """ self._keepalive = True self.proxy = None if self.session_info is None or self.ssh_transport is None: return None try: os.makedirs(self.session_info.local_container) except OSError as e: if e.errno == 17: # file exists pass local_graphics_port = self.session_info.graphics_port try: if self.ssh_transport.getpeername()[0] in ('::1', '127.0.0.1', 'localhost', 'localhost.localdomain'): local_graphics_port += 10000 except socket.error: raise x2go_exceptions.X2GoControlSessionException('The control session has died unexpectedly.') local_graphics_port = utils.detect_unused_port(preferred_port=local_graphics_port) self.fw_tunnel = forward.start_forward_tunnel(local_port=local_graphics_port, remote_port=self.session_info.graphics_port, ssh_transport=self.ssh_transport, session_instance=self.session_instance, session_name=self.session_name, subsystem=self.subsystem, logger=self.logger, ) # update the proxy port in PROXY_ARGS self._update_local_proxy_socket(local_graphics_port) self.session_log_stdout = open('%s/%s' % (self.session_info.local_container, self.session_log, ), 'a') self.session_log_stderr = open('%s/%s' % (self.session_info.local_container, self.session_log, ), 'a') _stdin = None _shell = False if _X2GOCLIENT_OS == 'Windows': _stdin = file('nul', 'r') _shell = True # allow inheriting classes to do something with backend specific proxy_options... self.process_proxy_options() # if everything is in place, generate the command line for the subprocess call cmd_line = self._generate_cmdline() self.logger('forking threaded subprocess: %s' % " ".join(cmd_line), loglevel=log.loglevel_DEBUG) while not self.proxy: gevent.sleep(.2) p = self.proxy = subprocess.Popen(cmd_line, env=self.PROXY_ENV, stdin=_stdin, stdout=self.session_log_stdout, stderr=self.session_log_stderr, shell=_shell) while self._keepalive: gevent.sleep(1) if _X2GOCLIENT_OS == 'Windows': _stdin.close() try: p.terminate() self.logger('terminating proxy: %s' % p, loglevel=log.loglevel_DEBUG) except OSError as e: if e.errno == 3: # No such process pass except WindowsError: pass self._tidy_up() def process_proxy_options(self): """\ Override this method to incorporate elements from ``proxy_options`` into actual proxy subprocess execution. This method (if overridden) should (by design) never fail nor raise an exception. Make sure to catch all possible errors appropriately. If you want to log ignored proxy_options then 1. remove processed proxy_options from self.proxy_options 2. once you have finished processing the proxy_options call the parent class method :class:`x2go.backends.proxy.base.X2GoProxy.process_proxy_options()` """ # do the logging of remaining options if self.proxy_options: self.logger('ignoring non-processed proxy options: %s' % self.proxy_options, loglevel=log.loglevel_INFO) def _update_local_proxy_socket(self, port): pass def _generate_cmdline(self): return '' def start_proxy(self): """\ Start the thread runner and wait for the proxy to come up. :returns: a subprocess instance that knows about the externally started proxy command. :rtype: ``obj`` """ threading.Thread.start(self) # wait for proxy to get started _count = 0 _maxwait = 40 while self.proxy is None and _count < _maxwait: _count += 1 self.logger('waiting for proxy to come up: 0.4s x %s' % _count, loglevel=log.loglevel_DEBUG) gevent.sleep(.4) if self.proxy: # also wait for fw_tunnel to become active _count = 0 _maxwait = 40 while self.fw_tunnel and (not self.fw_tunnel.is_active) and (not self.fw_tunnel.failed) and (_count < _maxwait): _count += 1 self.logger('waiting for port fw tunnel to come up: 0.5s x %s' % _count, loglevel=log.loglevel_DEBUG) gevent.sleep(.5) return self.proxy, bool(self.proxy) and (self.fw_tunnel and self.fw_tunnel.is_active) def ok(self): """\ Check if a proxy instance is up and running. :returns: Proxy state, ``True`` for proxy being up-and-running, ``False`` otherwise :rtype: ``bool`` """ return bool(self.proxy and self.proxy.poll() is None) and self.fw_tunnel.is_active python-x2go-0.6.1.4/x2go/backends/proxy/__init__.py0000644000000000000000000000160314470264762016703 0ustar # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. __package__ = 'x2go.backends.proxy' __name__ = 'x2go.backends.proxy' python-x2go-0.6.1.4/x2go/backends/proxy/kdrive.py0000644000000000000000000001457214470264762016441 0ustar # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. """\ X2GoProxy classes - rendering your graphical connection through KDRIVE. """ __NAME__ = 'x2goproxykdrive-pylib' __package__ = 'x2go.backends.proxy' __name__ = 'x2go.backends.proxy.kdrive' # modules import os # Python X2Go modules import x2go.log as log import x2go.backends.proxy.base as base from x2go.defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS class X2GoProxy(base.X2GoProxy): """\ This :class:`x2go.backends.proxy.kdrive.X2GoProxy` class is a X2Go KDrive Client connection class. It basically fills :class:`x2go.backends.proxy.base.X2GoProxy` variables with sensible content. Its methods mostly wrap around the corresponding methods of the parent class. """ def __init__(self, *args, **kwargs): """\ For available parameters refer to :class:`x2go.backends.proxy.base.X2GoProxy` class documentation. """ base.X2GoProxy.__init__(self, *args, **kwargs) self.subsystem = 'X2Go KDrive Client' # setting some default environment variables, nxproxy paths etc. if _X2GOCLIENT_OS == "Windows": _x2gokdriveclient_paths = [ os.path.join(os.environ["ProgramFiles"], os.path.normpath("PyHoca-GUI/x2gokdrive/x2gokdriveclient.exe")), os.path.join(os.environ["ProgramFiles"], os.path.normpath("x2goclient/x2gokdriveclient.exe")), os.path.normpath("../pyhoca-contrib/mswin/nxproxy-mswin/nxproxy-3.5.0.27_cygwin-2015-10-18/nxproxy.exe"), os.path.normpath("../pyhoca-contrib/mswin/x2gokrive-mswin/x2gokdriveclient-0.0.0.1_cygwin-NOT-BUILT-YET/x2gokdriveclient.exe"), ] if 'X2GOKDRIVECLIENT_BINARY' in os.environ: _x2gokdriveclient_paths.insert(0, os.environ['X2GOKDRIVECLIENT_BINARY']) for _x2gokdriveclient_cmd in _x2gokdriveclient_paths: if os.path.exists(_x2gokdriveclient_cmd): break self.PROXY_CMD = _x2gokdriveclient_cmd else: self.PROXY_CMD = "/usr/bin/x2gokdriveclient" # from here, all options are similar to NX options, as X2GoKDrive is fully NX Proxy compatible # (although, some options being no-op in X2Go KDrive) self.PROXY_ENV.update({ "NX_ROOT": self.sessions_rootdir }) self.PROXY_MODE = '-S' if _X2GOCLIENT_OS == "Windows": self.PROXY_OPTIONS = [ "nx/nx" , "retry=5", "composite=1", "connect=127.0.0.1", "clipboard=1", "cookie=%s" % self.session_info.cookie, "port=%s" % self.session_info.graphics_port, "errors=%s" % os.path.join(".", "..", "S-%s" % self.session_info.name, self.session_errors, ), ] else: self.PROXY_OPTIONS = [ "nx/nx" , "retry=5", "composite=1", "connect=127.0.0.1", "clipboard=1", "cookie=%s" % self.session_info.cookie, "port=%s" % self.session_info.graphics_port, "errors=%s" % os.path.join(self.session_info.local_container, self.session_errors, ), ] self.PROXY_DISPLAY = self.session_info.display def _update_local_proxy_socket(self, port): """\ Update the local proxy socket on port changes due to already-bound-to local TCP/IP port sockets. :param port: new local TCP/IP socket port :type port: ``int`` """ for idx, a in enumerate(self.PROXY_OPTIONS): if a.startswith('port='): self.PROXY_OPTIONS[idx] = 'port=%s' % port def _generate_cmdline(self): """\ Generate the NX proxy command line for execution. """ _options_filename = os.path.join(self.session_info.local_container, 'options') options = open(_options_filename, 'w') options.write(u'%s:%s' % (','.join(self.PROXY_OPTIONS), self.PROXY_DISPLAY)) options.close() if _X2GOCLIENT_OS == "Windows": self.PROXY_OPTIONS= [ 'nx/nx', 'options=%s' % os.path.join(".", "..", "S-%s" % self.session_info.name, 'options'), ] else: self.PROXY_OPTIONS= [ 'nx/nx', 'options=%s' % os.path.join(self.session_info.local_container, 'options'), ] cmd_line = [ self.PROXY_CMD, ] cmd_line.append(self.PROXY_MODE) _proxy_options = "%s:%s" % (",".join(self.PROXY_OPTIONS), self.PROXY_DISPLAY) cmd_line.append(_proxy_options) return cmd_line def process_proxy_options(self): base.X2GoProxy.process_proxy_options(self) def start_proxy(self): """\ Start the thread runner and wait for the proxy to come up. :returns: a subprocess instance that knows about the externally started proxy command. :rtype: ``obj`` """ self.logger('starting local X2Go KDrive Client...', loglevel=log.loglevel_INFO) self.logger('X2Go KDrive Client mode is server, cookie=%s, host=127.0.0.1, port=%s.' % (self.session_info.cookie, self.session_info.graphics_port,), loglevel=log.loglevel_DEBUG) self.logger('X2Go KDrive Client writes session log to %s.' % os.path.join(self.session_info.local_container, 'session.log'), loglevel=log.loglevel_DEBUG) p, p_ok = base.X2GoProxy.start_proxy(self) if self.ok(): self.logger('X2Go KDrive Client is up and running.', loglevel=log.loglevel_INFO) else: self.logger('Bringing up X2Go KDrive Client failed.', loglevel=log.loglevel_ERROR) return p, self.ok() python-x2go-0.6.1.4/x2go/backends/proxy/nx3.py0000644000000000000000000001404514470264762015660 0ustar # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. """\ X2GoProxy class - proxying your graphical connection through NX3. """ __NAME__ = 'x2goproxynx3-pylib' __package__ = 'x2go.backends.proxy' __name__ = 'x2go.backends.proxy.nx3' # modules import os # Python X2Go modules import x2go.log as log import x2go.backends.proxy.base as base from x2go.defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS class X2GoProxy(base.X2GoProxy): """\ This :class:`x2go.backends.proxy.nx3.X2GoProxy` class is a NX version 3 based X2Go proxy connection class. It basically fills :class:`x2go.backends.proxy.base.X2GoProxy` variables with sensible content. Its methods mostly wrap around the corresponding methods of the parent class. """ def __init__(self, *args, **kwargs): """\ For available parameters refer to :class:`x2go.backends.proxy.base.X2GoProxy` class documentation. """ base.X2GoProxy.__init__(self, *args, **kwargs) self.subsystem = 'NX Proxy' # setting some default environment variables, nxproxy paths etc. if _X2GOCLIENT_OS == "Windows": _nxproxy_paths = [ os.path.join(os.environ["ProgramFiles"], os.path.normpath("PyHoca-GUI/nxproxy/nxproxy.exe")), os.path.join(os.environ["ProgramFiles"], os.path.normpath("x2goclient/nxproxy.exe")), os.path.join(os.environ["ProgramFiles"], os.path.normpath("NX Client for Windows/bin/nxproxy.exe")), os.path.normpath("../pyhoca-contrib/mswin/nxproxy-mswin/nxproxy-3.5.0.27_cygwin-2015-10-18/nxproxy.exe"), ] if 'NXPROXY_BINARY' in os.environ: _nxproxy_paths.insert(0, os.environ['NXPROXY_BINARY']) for _nxproxy_cmd in _nxproxy_paths: if os.path.exists(_nxproxy_cmd): break self.PROXY_CMD = _nxproxy_cmd elif 'NXPROXY_BINARY' in os.environ: self.PROXY_CMD = os.environ['NXPROXY_BINARY'] else: self.PROXY_CMD = "/usr/bin/nxproxy" self.PROXY_ENV.update({ "NX_CLIENT": "/bin/true", "NX_ROOT": self.sessions_rootdir }) self.PROXY_MODE = '-S' if _X2GOCLIENT_OS == "Windows": self.PROXY_OPTIONS = [ "nx/nx" , "retry=5", "composite=1", "connect=127.0.0.1", "clipboard=1", "cookie=%s" % self.session_info.cookie, "port=%s" % self.session_info.graphics_port, "errors=%s" % os.path.join(".", "..", "S-%s" % self.session_info.name, self.session_errors, ), ] else: self.PROXY_OPTIONS = [ "nx/nx" , "retry=5", "composite=1", "connect=127.0.0.1", "clipboard=1", "cookie=%s" % self.session_info.cookie, "port=%s" % self.session_info.graphics_port, "errors=%s" % os.path.join(self.session_info.local_container, self.session_errors, ), ] self.PROXY_DISPLAY = self.session_info.display def _update_local_proxy_socket(self, port): """\ Update the local proxy socket on port changes due to already-bound-to local TCP/IP port sockets. :param port: new local TCP/IP socket port :type port: ``int`` """ for idx, a in enumerate(self.PROXY_OPTIONS): if a.startswith('port='): self.PROXY_OPTIONS[idx] = 'port=%s' % port def _generate_cmdline(self): """\ Generate the NX proxy command line for execution. """ if _X2GOCLIENT_OS == "Windows": _options_filename = os.path.join(self.session_info.local_container, 'options') options = open(_options_filename, 'w') options.write(u'%s:%s' % (','.join(self.PROXY_OPTIONS), self.PROXY_DISPLAY)) options.close() self.PROXY_OPTIONS= [ 'nx/nx', 'options=%s' % os.path.join(".", "..", "S-%s" % self.session_info.name, 'options'), ] cmd_line = [ self.PROXY_CMD, ] cmd_line.append(self.PROXY_MODE) _proxy_options = "%s:%s" % (",".join(self.PROXY_OPTIONS), self.PROXY_DISPLAY) cmd_line.append(_proxy_options) return cmd_line def process_proxy_options(self): base.X2GoProxy.process_proxy_options(self) def start_proxy(self): """\ Start the thread runner and wait for the proxy to come up. :returns: a subprocess instance that knows about the externally started proxy command. :rtype: ``obj`` """ self.logger('starting local NX3 proxy...', loglevel=log.loglevel_INFO) self.logger('NX3 Proxy mode is server, cookie=%s, host=127.0.0.1, port=%s.' % (self.session_info.cookie, self.session_info.graphics_port,), loglevel=log.loglevel_DEBUG) self.logger('NX3 proxy writes session log to %s.' % os.path.join(self.session_info.local_container, 'session.log'), loglevel=log.loglevel_DEBUG) p, p_ok = base.X2GoProxy.start_proxy(self) if self.ok(): self.logger('NX3 proxy is up and running.', loglevel=log.loglevel_INFO) else: self.logger('Bringing up NX3 proxy failed.', loglevel=log.loglevel_ERROR) return p, self.ok() python-x2go-0.6.1.4/x2go/backends/settings/file.py0000644000000000000000000000562614470264762016553 0ustar # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. """\ X2GoClientSettings class - managing x2goclient settings file. The :class:`x2go.backends.settings.file.X2GoClientSettings` class one of Python X2Go's a public API classes. Use this class (indirectly by retrieving it from an :class:`x2go.client.X2GoClient` instance) in your Python X2Go based applications to access the »settings« configuration file of your X2Go client application. This class supports reading the »settings« configuration from a file (``~/.x2goclient/settings``). """ __NAME__ = 'x2gosettings-pylib' __package__ = 'x2go.backends.settings' __name__ = 'x2go.backends.settings.file' # Python X2Go modules import x2go.log as log from x2go.defaults import X2GO_SETTINGS_CONFIGFILES as _X2GO_SETTINGS_CONFIGFILES from x2go.defaults import X2GO_CLIENTSETTINGS_DEFAULTS as _X2GO_CLIENTSETTINGS_DEFAULTS import x2go.inifiles as inifiles class X2GoClientSettings(inifiles.X2GoIniFile): """\ Configuration file based settings for :class:`x2go.client.X2GoClient` instances. """ def __init__(self, config_files=_X2GO_SETTINGS_CONFIGFILES, defaults=_X2GO_CLIENTSETTINGS_DEFAULTS, logger=None, loglevel=log.loglevel_DEFAULT): """\ Constructs an :class:`x2go.backends.settings.file.X2GoClientSettings` instance. This is normally done from within an :class:`x2go.client.X2GoClient` instance. You can retrieve this :class:`x2go.backends.settings.file.X2GoClientSettings` instance with the :func:`X2GoClient.get_client_settings() ` method. On construction the :class:`x2go.backends.settings.file.X2GoClientSettings` object is filled with values from the configuration files:: /etc/x2goclient/settings ~/.x2goclient/settings The files are read in the specified order and config options of both files are merged. Options set in the user configuration file (``~/.x2goclient/settings``) override global options set in ``/etc/x2goclient/settings``. """ inifiles.X2GoIniFile.__init__(self, config_files, defaults=defaults, logger=logger, loglevel=loglevel) python-x2go-0.6.1.4/x2go/backends/settings/__init__.py0000644000000000000000000000161114470264762017361 0ustar # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. __package__ = 'x2go.backends.settings' __name__ = 'x2go.backends.settings' python-x2go-0.6.1.4/x2go/backends/terminal/__init__.py0000644000000000000000000000161114470264762017334 0ustar # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. __package__ = 'x2go.backends.terminal' __name__ = 'x2go.backends.terminal' python-x2go-0.6.1.4/x2go/backends/terminal/plain.py0000644000000000000000000023550214470264762016710 0ustar # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. """\ X2GoTerminalSession class - core functions for handling your individual X2Go sessions. This backend handles X2Go server implementations that respond with session infos via server-side PLAIN text output. """ __NAME__ = 'x2goterminalsession-pylib' __package__ = 'x2go.backends.terminal' __name__ = 'x2go.backends.terminal.plain' # modules import os import sys import types import gevent import io import copy import shutil import threading # Python X2Go modules import x2go.rforward as rforward import x2go.sftpserver as sftpserver import x2go.printqueue as printqueue import x2go.mimebox as mimebox import x2go.telekinesis as telekinesis import x2go.log as log import x2go.defaults as defaults import x2go.utils as utils import x2go.x2go_exceptions as x2go_exceptions # we hide the default values from epydoc (that's why we transform them to _UNDERSCORE variables) from x2go.defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS from x2go.defaults import LOCAL_HOME as _LOCAL_HOME from x2go.defaults import CURRENT_LOCAL_USER as _CURRENT_LOCAL_USER from x2go.defaults import X2GO_CLIENT_ROOTDIR as _X2GO_CLIENT_ROOTDIR from x2go.defaults import X2GO_SESSIONS_ROOTDIR as _X2GO_SESSIONS_ROOTDIR from x2go.defaults import X2GO_GENERIC_APPLICATIONS as _X2GO_GENERIC_APPLICATIONS from x2go.defaults import X2GO_DESKTOPSESSIONS as _X2GO_DESKTOPSESSIONS from x2go.defaults import X2GO_DESKTOPSESSIONS_NX3_PREFERRED as _X2GO_DESKTOPSESSIONS_NX3_PREFERRED from x2go.defaults import X2GO_DESKTOPSESSIONS_KDRIVE_PREFERRED as _X2GO_DESKTOPSESSIONS_KDRIVE_PREFERRED from x2go.defaults import BACKENDS as _BACKENDS _local_color_depth = utils.local_color_depth() def _rewrite_cmd(cmd, params=None): """\ Mechansim that rewrites X2Go server commands into something that gets understood by the server-side script ``x2goruncommand``. :param cmd: the current command for execution (as found in the session profile parameter ``cmd``) :type cmd: ``str`` :param params: an session paramter object (Default value = None) :type params: :class:`x2go.backends.terminal.plain.X2GoSessionParams` :returns: the rewritten command for server-side execution :rtype: ``str`` """ # start with an empty string cmd = cmd or '' # find window manager commands if cmd in list(_X2GO_DESKTOPSESSIONS.keys()): cmd = _X2GO_DESKTOPSESSIONS[cmd] if (cmd == 'RDP') and (type(params) == X2GoSessionParams): _depth = params.depth if int(_depth) == 17: _depth = 16 if params.geometry == 'fullscreen': cmd = 'rdesktop -f -N %s %s -a %s' % (params.rdp_options, params.rdp_server, _depth) else: cmd = 'rdesktop -g %s -N %s %s -a %s' % (params.geometry, params.rdp_options, params.rdp_server, _depth) # place quot marks around cmd if not empty string if cmd: cmd = '"%s"' % cmd if ((type(params) == X2GoSessionParams) and params.published_applications and cmd == ''): cmd = 'PUBLISHED' return cmd def _rewrite_blanks(cmd): """\ In command strings X2Go server scripts expect blanks being rewritten to ,,X2GO_SPACE_CHAR''. :param cmd: command that has to be rewritten for passing to the server :type cmd: ``str`` :returns: the command with blanks rewritten to ,,X2GO_SPACE_CHAR'' :rtype: ``str`` """ # X2Go run command replace X2GO_SPACE_CHAR string with blanks if cmd: cmd = cmd.replace(" ", "X2GO_SPACE_CHAR") return cmd class X2GoSessionParams(object): """\ The :class:`x2go.backends.terminal.plain.X2GoSessionParams` class is used to store all parameters that ``X2GoTerminalSession`` backend objects are constructed with. """ def rewrite_session_type(self): """\ Rewrite the X2Go session type, so that the X2Go server can understand it (``desktop`` -> ``D`` or ``K``, depending on the proxy backend, etc.). Also if the object's ``command`` property is a known window manager, the session type will be set to 'D' (i.e. desktop with NX3 backend). :returns: D' if session should probably a desktop session, 'R' for rootless sessions, 'P' for sessions providing published applications, and 'S' for desktop sharing sessions :rtype: ``str`` """ cmd = self.cmd published = self.published_applications if published and self.cmd in ('', 'PUBLISHED'): self.session_type = 'P' self.cmd = 'PUBLISHED' else: if cmd == 'RDP' or cmd.startswith('rdesktop') or cmd.startswith('xfreedrp'): if self.geometry == 'fullscreen': self.session_type = 'D' else: self.session_type = 'R' elif cmd == 'XDMCP': self.session_type = 'desktop' elif cmd in list(_X2GO_DESKTOPSESSIONS.keys()): self.session_type = 'desktop' elif os.path.basename(cmd) in list(_X2GO_DESKTOPSESSIONS.values()): self.session_type = 'desktop' if self.session_type == "D" or ( self.session_type == "desktop" and self.kdrive == False ): self.session_type = 'D' elif self.session_type == "K" or ( self.session_type == "desktop" and self.kdrive == True ): self.session_type = 'K' elif self.session_type in ("S", "shared", "shadow"): self.session_type = 'S' elif self.session_type in ("R", "rootless", "application"): self.session_type = 'R' elif self.session_type in ("P", "published", "published_applications"): self.session_type = 'P' return self.session_type def update(self, **properties_to_be_updated): """\ Update all properties in the object :class:`x2go.backends.terminal.plain.X2GoSessionParams` object from the passed on dictionary. :param properties_to_be_updated: a dictionary with :class:`x2go.backends.terminal.plain.X2GoSessionParams` property names as keys und their values to be update in :class:`x2go.backends.terminal.plain.X2GoSessionParams` object. :type properties_to_be_updated: ``dict`` """ for key in list(properties_to_be_updated.keys()): setattr(self, key, properties_to_be_updated[key] or '') self.rewrite_session_type() class X2GoTerminalSession(object): """\ Class for managing X2Go terminal sessions on a remote X2Go server via Paramiko/SSH. With the :class:`x2go.backends.terminal.plain.X2GoTerminalSession` class you can start new X2Go sessions, resume suspended sessions or suspend resp. terminate currently running sessions on a connected X2Go server. An :class:`x2go.backends.terminal.plain.X2GoTerminalSession` object uses two main data structure classes: - :class:`x2go.backends.terminal.plain.X2GoSessionParams`: stores all parameters that have been passed to the constructor method. - ``X2GoServerSessionInfo*`` backend class: when starting or resuming a session, an object of this class will be used to store all information retrieved from the X2Go server. The terminal session instance works closely together (i.e. depends on) a connected control session instance (e.g. :class:`x2go.backends.control.plain.X2GoControlSession`). You never should use either of them as a standalone instance. Both, control session and terminal session(s) get managed/controlled via :class:`x2go.session.X2GoSession` instances. """ def __init__(self, control_session, session_info=None, geometry="800x600", depth=_local_color_depth, link="adsl", pack="16m-jpeg-9", dpi='', cache_type="unix-kde", kbtype='null/null', kblayout='null', kbvariant='null', clipboard='both', xinerama=False, kdrive=False, session_type="application", snd_system='pulse', snd_port=4713, cmd=None, published_applications=False, set_session_title=False, session_title="", applications=[], rdp_server=None, rdp_options=None, xdmcp_server=None, convert_encoding=False, server_encoding='UTF-8', client_encoding='UTF-8', rootdir=None, profile_name='UNKNOWN', profile_id=utils._genSessionProfileId(), print_action=None, print_action_args={}, info_backend=_BACKENDS['X2GoServerSessionInfo']['default'], list_backend=_BACKENDS['X2GoServerSessionList']['default'], proxy_backend=_BACKENDS['X2GoProxy']['default'], proxy_options={}, printing_backend=_BACKENDS['X2GoClientPrinting']['default'], client_rootdir=os.path.join(_LOCAL_HOME, _X2GO_CLIENT_ROOTDIR), sessions_rootdir=os.path.join(_LOCAL_HOME, _X2GO_SESSIONS_ROOTDIR), session_instance=None, logger=None, loglevel=log.loglevel_DEFAULT): """\ Initialize an X2Go session. With the :class:`x2go.backends.terminal.plain.X2GoTerminalSession` class you can start new X2Go sessions, resume suspended sessions or suspend resp. terminate currently running sessions on a connected X2Go server. :param geometry: screen geometry of the X2Go session. Can be either ``x``, ``maximize`` or ``fullscreen`` :type geometry: ``str`` :param depth: color depth in bits (common values: ``16``, ``24``) :type depth: ``int`` :param link: network link quality (either one of ``modem``, ``isdn``, ``adsl``, ``wan`` or ``lan``) :type link: ``str`` :param pack: compression method for NX based session proxying :type pack: ``str`` :param dpi: dots-per-inch value for the session screen (has an impact on the font size on screen) :type dpi: ``str`` :param cache_type: a dummy parameter that is passed to the :class:`x2go.backends.proxy.base.X2GoProxy`. In NX Proxy (class ``X2GoProxyNX3``) this originally is the session name. With X2Go it defines the name of the NX cache directory. Best is to leave it untouched. :type cache_type: ``str`` :param kbtype: keyboard type, e.g. ``pc105/us`` (default), ``pc105/de``, ... :type kbtype: ``str`` :param kblayout: keyboard layout, e.g. ``us`` (default), ``de``, ``fr``, ... :type kblayout: ``str`` :param kbvariant: keyboard variant, e.g. ``nodeadkeys`` (for ``de`` layout), ``intl`` (for ``us`` layout), etc. :type kbvariant: ``str`` :param clipboard: clipboard mode (``both``: bidirectional copy+paste, ``server``: copy+paste from server to client, ``client``: copy+paste from client to server, ``none``: disable clipboard completely :type clipboard: ``str`` :param xinerama: enable/disable Xinerama support in remote X2Go session :type xinerama: ``bool`` :param session_type: either ``desktop``, ``application`` (rootless session) or ``shared`` (only set for new sessions, ignored for to-be-resumed sessions) :type session_type: ``str`` :param snd_system: sound system to be used on server (``none``, ``pulse`` (default), ``arts`` (obsolete) or ``esd``) :type snd_system: ``str`` :param snd_port: local sound port for network capable audio system :type snd_port: ``int`` :param cmd: command to be run on X2Go server after session start (only used when :class:`x2go.backends.terminal.plain.X2GoTerminalSession.start()` is called, ignored on resume, suspend etc. :type cmd: ``str`` :param published_applications: session is published applications provider :type published_applications: ``bool`` :param set_session_title: modify the session title (i.e. the Window title) of desktop or shared desktop sessions :type set_session_title: ``bool`` :param session_title: session title for this (desktop or shared desktop) session :type session_title: ``str`` :param applications: applications available for rootless application execution :type applications: ``list`` :param rdp_server: host name of server-side RDP server :type rdp_server: ``str`` :param rdp_options: options for the ``rdesktop`` command executed on the X2Go server (RDP proxy mode of X2Go) :type rdp_options: ``str`` :param xdmcp_server: XDMCP server to connect to :type xdmcp_server: ``str`` :param convert_encoding: convert file system encodings between server and client (for client-side shared folders) :type convert_encoding: ``bool`` :param server_encoding: server-side file system / session encoding :type server_encoding: ``str`` :param client_encoding: client-side file system encoding (if client-side is MS Windows, this parameter gets overwritten to WINDOWS-1252) :type client_encoding: ``str`` :param rootdir: X2Go session directory, normally ``~/.x2go`` :type rootdir: ``str`` :param profile_name: the session profile name for this terminal session :type profile_name: ``str`` :param profile_id: the session profile ID for this terminal session :type profile_id: ``str`` :param print_action: either a print action short name (PDFVIEW, PDFSAVE, PRINT, PRINTCMD) or the resp. ``X2GoPrintActionXXX`` class (where XXX equals one of the given short names) :type print_action: ``str`` or ``class`` :param print_action_args: optional arguments for a given print_action (for further info refer to :class:`x2go.printactions.X2GoPrintActionPDFVIEW`, :class:`x2go.printactions.X2GoPrintActionPDFSAVE`, :class:`x2go.printactions.X2GoPrintActionPRINT` and :class:`x2go.printactions.X2GoPrintActionPRINTCMD`) :type print_action_args: ``dict`` :param info_backend: backend for handling storage of server session information :type info_backend: ``X2GoServerSessionInfo*`` instance :param list_backend: backend for handling storage of session list information :type list_backend: ``X2GoServerSessionList*`` instance :param proxy_backend: backend for handling the X-proxy connections :type proxy_backend: ``X2GoProxy*`` instance :param proxy_options: a set of very ``X2GoProxy`` backend specific options; any option that is not known to the ``X2GoProxy`` backend will simply be ignored :type proxy_options: ``dict`` :param client_rootdir: client base dir (default: ~/.x2goclient) :type client_rootdir: ``str`` :param sessions_rootdir: sessions base dir (default: ~/.x2go) :type sessions_rootdir: ``str`` :param session_instance: the :class:`x2go.session.X2GoSession` instance that is parent to this terminal session :type session_instance: ``obj`` :param logger: you can pass an :class:`x2go.log.X2GoLogger` object to the :class:`x2go.backends.terminal.plain.X2GoTerminalSession` constructor :type logger: :class:`x2go.log.X2GoLogger` instance :param loglevel: if no :class:`x2go.log.X2GoLogger` object has been supplied a new one will be constructed with the given loglevel :type loglevel: ``int`` """ self.proxy = None self.proxy_subprocess = None self.proxy_options = proxy_options self.telekinesis_client = None self.active_threads = [] self.reverse_tunnels = {} self.print_queue = None self.mimebox_queue = None if logger is None: self.logger = log.X2GoLogger(loglevel=loglevel) else: self.logger = copy.deepcopy(logger) self.logger.tag = __NAME__ self.control_session = control_session self.reverse_tunnels = self.control_session.get_transport().reverse_tunnels self.client_rootdir = client_rootdir self.sessions_rootdir = sessions_rootdir self.params = X2GoSessionParams() self.params.geometry = str(geometry) self.params.link = str(link) self.params.pack = str(pack) if dpi: self.params.dpi = str(dpi) else: self.params.dpi = '' self.params.cache_type = str(cache_type) self.params.session_type = str(session_type) self.params.kbtype = str(kbtype) self.params.kblayout = str(kblayout) self.params.kbvariant = str(kbvariant) self.params.snd_system = str(snd_system) self.params.cmd = str(cmd) self.params.depth = str(depth) self.params.clipboard = str(clipboard) self.params.xinerama = bool(xinerama) self.params.published_applications = published_applications self.published_applications = published_applications self.params.rdp_server = str(rdp_server) self.params.rdp_options = str(rdp_options) self.params.xdmcp_server = str(xdmcp_server) self.params.convert_encoding = convert_encoding self.params.client_encoding = str(client_encoding) self.params.server_encoding = str(server_encoding) self.params.rootdir = (type(rootdir) is bytes) and rootdir or self.sessions_rootdir self.params.kdrive = False # enforce X2GoKDrive usage? if kdrive: proxy_backend = "KDRIVE" self.params.kdrive = True self.params.update() self.profile_name = profile_name self.set_session_title = set_session_title if session_title: self.session_title = session_title else: self.session_title = '' self.session_window = None # auto-detect graphical proxy/kdrive backend? if proxy_backend == 'auto-detect': # we need to defer this at this point # until we know the exact session command # to be launched... (i.e. what desktop environment # the user wants to launch via X2Go self.proxy_backend = proxy_backend else: self.proxy_backend = utils._get_backend_class(proxy_backend, "X2GoProxy") self.snd_port = snd_port self.print_action = print_action self.print_action_args = print_action_args self.printing_backend = utils._get_backend_class(printing_backend, "X2GoClientPrinting") self.session_instance = session_instance if self.session_instance: self.client_instance = self.session_instance.client_instance else: self.client_instance = None self._share_local_folder_busy = False self._mk_sessions_rootdir(self.params.rootdir) self.session_info = session_info if self.session_info is not None: if self.session_info.name: self.session_info.local_container = os.path.join(self.params.rootdir, 'S-%s' % self.session_info.name) else: raise x2go_exceptions.X2GoTerminalSessionException('no valid session info availble') else: self.session_info = info_backend() self._share_local_folder_lock = threading.Lock() self._cleaned_up = False self.telekinesis_subprocess = None def __del__(self): """\ Tidy up if terminal session gets destructed. """ self._x2go_tidy_up() def _x2go_tidy_up(self): """\ Tidy up this terminal session... - shutdown all forwarding and reverse forwarding tunnels - shutdown the print queue (if running) - shutdown the MIME box queue (if running) - clear the session info """ try: if self._share_local_folder_lock and self._share_local_folder_lock.locked(): self._share_local_folder_lock.release() except AttributeError: pass self.release_telekinesis() self.release_proxy() self.session_window = None try: self.update_session_window_file() except AttributeError: pass try: if self.control_session.get_transport() is not None: try: for _tunnel in [ _tun[1] for _tun in list(self.reverse_tunnels[self.session_info.name].values()) ]: if _tunnel is not None: _tunnel.__del__() except KeyError: pass if self.print_queue is not None: self.print_queue.__del__() if self.mimebox_queue is not None: self.mimebox_queue.__del__() except AttributeError: pass try: self.session_info.clear() except AttributeError: pass def _mk_sessions_rootdir(self, rootdir): """\ Create the server-side session root dir (normally ~/.x2go). :param rootdir: server-side session root directory :type rootdir: ``str`` """ try: os.makedirs(rootdir) except OSError as e: if e.errno == 17: # file exists pass else: raise OSError(e) def _rm_session_dirtree(self): """\ Purge client-side session dir (session cache directory). """ if self.session_info.name: shutil.rmtree('%s/S-%s' % (self.params.rootdir, self.session_info), ignore_errors=True) def _rm_desktop_dirtree(self): """\ Purge client-side session dir (C- directory) """ if self.session_info.display: shutil.rmtree('%s/S-%s' % (self.params.rootdir, self.session_info.display), ignore_errors=True) def get_session_name(self): """\ Retrieve the X2Go session's name from the session info object. :returns: the session name :rtype: ``str`` """ return self.session_info.name def get_session_info(self): """\ Retrieve the X2Go session's session info object. :returns: the session info object :rtype: ``X2GoServerSessionInfo*`` """ return self.session_info def get_session_cmd(self): """\ Retrieve the X2Go session's command as stored in the session parameter object. :returns: the session command :rtype: ``str`` """ return self.params.cmd def get_session_type(self): """\ Retrieve the X2Go session's session type as stored either in the parameter object (for sessions not yet launched) or in the ``session_info`` object (for already launched / to-be-resumed sessions). :returns: the session type :rtype: ``str`` """ if self.session_info.is_initialized(): return self.session_info.get_session_type() else: return self.params.session_type def start_sound(self): """\ Initialize Paramiko/SSH reverse forwarding tunnel for X2Go sound. Currently supported audio protocols: - PulseAudio - Esound (not tested very much) :raises X2GoControlSessionException: if the control session of this terminal session is not connected """ _tunnel = None if self.reverse_tunnels[self.session_info.name]['snd'][1] is None: if self.params.snd_system == 'pulse': self.logger('initializing PulseAudio sound support in X2Go session', loglevel=log.loglevel_INFO) ### ### PULSEAUDIO ### cookie_filepath = None if os.path.exists(os.path.normpath('%s/.pulse-cookie' % _LOCAL_HOME)): cookie_filepath = os.path.normpath('%s/.pulse-cookie' % _LOCAL_HOME) elif os.path.exists(os.path.normpath('%s/.config/pulse/cookie' % _LOCAL_HOME)): cookie_filepath = os.path.normpath('%s/.config/pulse/cookie' % _LOCAL_HOME) if cookie_filepath is not None: # setup pulse client config file on X2Go server cmd_line = "echo 'default-server=127.0.0.1:%s'>%s/.pulse-client.conf;" % (self.session_info.snd_port, self.session_info.remote_container) + \ "echo 'cookie-file=%s/.pulse-cookie'>>%s/.pulse-client.conf" % (self.session_info.remote_container, self.session_info.remote_container) (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) self.control_session._x2go_sftp_put(local_path=cookie_filepath, remote_path='%s/.pulse-cookie' % self.session_info.remote_container) # start reverse SSH tunnel for pulse stream _tunnel = rforward.X2GoRevFwTunnel(server_port=self.session_info.snd_port, remote_host='127.0.0.1', remote_port=self.snd_port, ssh_transport=self.control_session.get_transport(), session_instance=self.session_instance, logger=self.logger ) else: if self.client_instance: self.client_instance.HOOK_on_sound_tunnel_failed(profile_name=self.profile_name, session_name=self.session_info.name) elif self.params.snd_system == 'arts': ### ### ARTSD AUDIO ### self.logger('the ArtsD sound server (as in KDE3) is obsolete and will not be supported by Python X2Go...', loglevel=log.loglevel_WARN) elif self.params.snd_system == 'esd': ### ### ESD AUDIO ### self.logger('initializing ESD sound support in X2Go session', loglevel=log.loglevel_INFO) self.control_session._x2go_sftp_put(local_path='%s/.esd_auth' % _LOCAL_HOME, remote_path='%s/.esd_auth' % self.control_session._x2go_remote_home) # start reverse SSH tunnel for pulse stream _tunnel = rforward.X2GoRevFwTunnel(server_port=self.session_info.snd_port, remote_host='127.0.0.1', remote_port=self.snd_port, ssh_transport=self.control_session.get_transport(), session_instance=self.session_instance, logger=self.logger ) if _tunnel is not None: self.reverse_tunnels[self.session_info.name]['snd'] = (self.session_info.snd_port, _tunnel) _tunnel.start() self.active_threads.append(_tunnel) else: # tunnel has already been started and might simply need a resume call self.reverse_tunnels[self.session_info.name]['snd'][1].resume() def start_sshfs(self): """\ Initialize Paramiko/SSH reverse forwarding tunnel for X2Go folder sharing. """ if not self.control_session.is_sshfs_available(): raise x2go_exceptions.X2GoUserException('Remote user %s is not allowed to share SSHFS resources with the server.' % self.session_info.username) # start reverse SSH tunnel for sshfs (folder sharing, printing) ssh_transport = self.control_session.get_transport() if self.reverse_tunnels[self.session_info.name]['sshfs'][1] is None: _tunnel = sftpserver.X2GoRevFwTunnelToSFTP(server_port=self.session_info.sshfs_port, ssh_transport=ssh_transport, auth_key=self.control_session._x2go_session_auth_rsakey, session_instance=self.session_instance, logger=self.logger ) if _tunnel is not None: self.reverse_tunnels[self.session_info.name]['sshfs'] = (self.session_info.sshfs_port, _tunnel) _tunnel.start() self.active_threads.append(_tunnel) while not _tunnel.ready: gevent.sleep(.1) else: # tunnel has already been started and might simply need a resume call self.reverse_tunnels[self.session_info.name]['sshfs'][1].resume() def _x2go_pause_rev_fw_tunnel(self, name): """\ Pause reverse SSH tunnel of name . :param name: tunnel name (either of ``sshfs``, ``snd``) :type name: ``str`` """ _tunnel = self.reverse_tunnels[self.session_info.name][name][1] if _tunnel is not None: _tunnel.pause() def stop_sound(self): """\ Shutdown (pause) Paramiko/SSH reverse forwarding tunnel for X2Go sound. """ self._x2go_pause_rev_fw_tunnel('snd') def stop_sshfs(self): """\ Shutdown (pause) Paramiko/SSH reverse forwarding tunnel for X2Go folder sharing. """ self._x2go_pause_rev_fw_tunnel('sshfs') def start_printing(self): """\ Initialize X2Go print spooling. :raises X2GoUserException: if the X2Go printing feature is not available to this user """ if not self.control_session.is_sshfs_available(): raise x2go_exceptions.X2GoUserException('Remote user %s is not allowed to use client-side printing.' % self.session_info.username) spool_dir = os.path.join(self.session_info.local_container, 'spool') if not os.path.exists(spool_dir): os.makedirs(spool_dir) self.share_local_folder(local_path=spool_dir, folder_type='spool') self.print_queue = printqueue.X2GoPrintQueue(profile_name=self.profile_name, session_name=self.session_info.name, spool_dir=spool_dir, print_action=self.print_action, print_action_args=self.print_action_args, client_instance=self.client_instance, printing_backend=self.printing_backend, logger=self.logger, ) self.print_queue.start() self.active_threads.append(self.print_queue) def set_print_action(self, print_action, **kwargs): """\ Set a print action for the next incoming print jobs. This method is a wrapper for :class:`x2go.printing.X2GoPrintQueue`.set_print_action()``. :param print_action: print action name or object (i.e. an instance of ``X2GoPrintAction*`` classes) :type print_action: ``str`` or ``X2GoPrintAction*`` :param kwargs: print action specific parameters :type kwargs: ``dict`` """ self.print_queue.set_print_action(print_action, logger=self.logger, **kwargs) def stop_printing(self): """\ Shutdown (pause) the X2Go Print Queue thread. """ if self.print_queue is not None: self.print_queue.pause() def get_printing_spooldir(self): """\ Return the server-side printing spooldir path. :returns: the directory for remote print job spooling :rtype: ``str`` """ return '%s/%s' % (self.session_info.remote_container, 'spool') def start_mimebox(self, mimebox_extensions=[], mimebox_action=None): """\ Initialize the X2Go MIME box. Open/process incoming files from the server-side locally. :param mimebox_extensions: file name extensions that are allowed for local opening/processing (Default value = []) :type mimebox_extensions: ``list`` :param mimebox_action: MIME box action given as name or object (i.e. an instance of ``X2GoMIMEboxAction*`` classes). (Default value = None) :type mimebox_action: ``str`` or ``obj`` :raises X2GoUserException: if the X2Go MIME box feature is not available to this user """ if not self.control_session.is_sshfs_available(): raise x2go_exceptions.X2GoUserException('Remote user %s is not allowed to use the MIME box.' % self.session_info.username) mimebox_dir = os.path.join(self.session_info.local_container, 'mimebox') if not os.path.exists(mimebox_dir): os.makedirs(mimebox_dir) self.share_local_folder(local_path=mimebox_dir, folder_type='mimebox') self.mimebox_queue = mimebox.X2GoMIMEboxQueue(profile_name=self.profile_name, session_name=self.session_info.name, mimebox_dir=mimebox_dir, mimebox_extensions=mimebox_extensions, mimebox_action=mimebox_action, client_instance=self.client_instance, logger=self.logger, ) self.mimebox_queue.start() self.active_threads.append(self.mimebox_queue) def set_mimebox_action(self, mimebox_action, **kwargs): """\ Set a MIME box action for the next incoming MIME jobs. This method is a wrapper for :class:`x2go.mimebox.X2GoMIMEboxQueue```set_mimebox_action()``. :param mimebox_action: MIME box action name or object (i.e. an instance of ``X2GoMIMEboxAction*`` classes) :type mimebox_action: ``str`` or ``X2GoMIMEboxAction*`` :param kwargs: MIME box action specific parameters :type kwargs: ``dict`` """ self.mimebox_queue.set_mimebox_action(mimebox_action, logger=self.logger, **kwargs) def stop_mimebox(self): """\ Shutdown (pause) the X2Go MIME box Queue thread. """ if self.mimebox_queue is not None: self.mimebox_queue.pause() def get_mimebox_spooldir(self): """\ Return the server-side MIME box spooldir path. :returns: the directory where remote MIME box jobs are placed :rtype: ``str`` """ return '%s/%s' % (self.session_info.remote_container, 'mimebox') def start_telekinesis(self): """\ Initialize Telekinesis client for X2Go. """ if self.telekinesis_client is not None: del self.telekinesis_client self.telekinesis_client = None if self.telekinesis_subprocess is not None: self.telekinesis_subprocess = None if self.session_info.tekictrl_port != -1 and self.session_info.tekidata_port != -1: self.telekinesis_client = telekinesis.X2GoTelekinesisClient(session_info=self.session_info, ssh_transport=self.control_session.get_transport(), sessions_rootdir=self.sessions_rootdir, session_instance=self.session_instance, logger=self.logger) if self.telekinesis_client.has_telekinesis_client(): self.telekinesis_subprocess, telekinesis_ok = self.telekinesis_client.start_telekinesis() else: del self.telekinesis_client self.telekinesis_client = None def is_session_info_protected(self): """\ Test if this terminal's session info object is write-protected. :returns: ``True``, if session info object is read-only, ``False`` for read-write. :rtype: ``bool`` """ self.session_info.is_protected() def session_info_protect(self): """\ Protect this terminal session's info object against updates. """ self.session_info.protect() def session_info_unprotect(self): """\ Allow session info updates from within the list_sessions method of the control session. """ self.session_info.unprotect() def share_local_folder(self, local_path=None, folder_type='disk'): """\ Share a local folder with the X2Go session. :param local_path: the full path to an existing folder on the local file system (Default value = None) :type local_path: ``str`` :param folder_type: one of 'disk' (a folder on your local hard drive), 'rm' (removeable device), 'cdrom' (CD/DVD Rom) or 'spool' (for X2Go print spooling) (Default value = 'disk') :type folder_type: ``str`` :returns: returns ``True`` if the local folder has been successfully mounted within the X2Go server session :rtype: ``bool`` :raises X2GoUserException: if local folder sharing is not available to this user :raises Exception: any other exception occuring on the way is passed through by this method """ if not self.control_session.is_sshfs_available(): raise x2go_exceptions.X2GoUserException('Remote user %s is not allowed to share local folders with the server.' % self.session_info.username) if local_path is None: self.logger('no folder name given...', log.loglevel_WARN) return False if type(local_path) not in (bytes, str): self.logger('folder name needs to be of type StringType...', log.loglevel_WARN) return False if not os.path.exists(local_path): self.logger('local folder does not exist: %s' % local_path, log.loglevel_WARN) return False local_path = os.path.normpath(local_path) self.logger('sharing local folder: %s' % local_path, log.loglevel_INFO) _auth_rsakey = self.control_session._x2go_session_auth_rsakey _host_rsakey = defaults.RSAHostKey if sys.version_info[0] >= 3: _tmp_io_object = io.BytesIO() else: _tmp_io_object = io.StringIO() _auth_rsakey.write_private_key(_tmp_io_object) if sys.version_info[0] >= 3: _tmp_io_object.write(b'----BEGIN RSA IDENTITY----') _tmp_io_object.write(b'%b %b' % (_host_rsakey.get_name().encode(),_host_rsakey.get_base64().encode(),)) else: _tmp_io_object.write(u'----BEGIN RSA IDENTITY----') _tmp_io_object.write(u'%s %s' % (_host_rsakey.get_name(),_host_rsakey.get_base64(),)) # _x2go_key_fname must be a UniX path _x2go_key_fname = '%s/%s/%s' % (os.path.dirname(self.session_info.remote_container), 'ssh', 'key.z%s' % self.session_info.agent_pid) _x2go_key_bundle = _tmp_io_object.getvalue() # if there is another call to this method currently being processed, wait for that one to finish self._share_local_folder_lock.acquire() try: self.control_session._x2go_sftp_write(_x2go_key_fname, _x2go_key_bundle) _convert_encoding = self.params.convert_encoding _client_encoding = self.params.client_encoding _server_encoding = self.params.server_encoding if _X2GOCLIENT_OS == 'Windows': if local_path.startswith('\\\\'): # we are on a UNC path if 'X2GO_MOUNT_UNCPATHS' in self.control_session.get_server_features(): local_path = local_path.repalce('\\\\', '/uncpath/') else: local_path = local_path.repalce('\\\\', '/windrive/') local_path = local_path.replace('\\', '/') else: local_path = local_path.replace('\\', '/') local_path = local_path.replace(':', '') local_path = '/windrive/%s' % local_path _convert_encoding = True _client_encoding = 'WINDOWS-1252' if _convert_encoding: export_iconv_settings = 'export X2GO_ICONV=modules=iconv,from_code=%s,to_code=%s && ' % (_client_encoding, _server_encoding) else: export_iconv_settings = '' if folder_type == 'disk': cmd_line = [ '%sexport HOSTNAME &&' % export_iconv_settings, 'x2gomountdirs', 'dir', str(self.session_info.name), '\'%s\'' % _CURRENT_LOCAL_USER, _x2go_key_fname, '%s__REVERSESSH_PORT__%s; ' % (local_path, self.session_info.sshfs_port), 'rm -f %s %s.ident' % (_x2go_key_fname, _x2go_key_fname), ] elif folder_type == 'spool': cmd_line = [ '%sexport HOSTNAME &&' % export_iconv_settings, 'x2gomountdirs', 'dir', str(self.session_info.name), '\'%s\'' % _CURRENT_LOCAL_USER, _x2go_key_fname, '%s__PRINT_SPOOL___REVERSESSH_PORT__%s; ' % (local_path, self.session_info.sshfs_port), 'rm -f %s %s.ident' % (_x2go_key_fname, _x2go_key_fname), ] elif folder_type == 'mimebox': cmd_line = [ '%sexport HOSTNAME &&' % export_iconv_settings, 'x2gomountdirs', 'dir', str(self.session_info.name), '\'%s\'' % _CURRENT_LOCAL_USER, _x2go_key_fname, '%s__MIMEBOX_SPOOL___REVERSESSH_PORT__%s; ' % (local_path, self.session_info.sshfs_port), 'rm -f %s %s.ident' % (_x2go_key_fname, _x2go_key_fname), ] (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) _stdout = stdout.read() _stderr = stderr.read() if sys.version_info[0] >= 3: _stdout = _stdout.decode() _stderr = _stderr.decode() _stdout = _stdout.split('\n') if _stdout[0]: self.logger('x2gomountdirs stdout is: %s' % _stdout, log.loglevel_NOTICE) _stderr = _stderr.split('\n') if _stderr[0]: self.logger('x2gomountdirs stderr is: %s' % _stderr, log.loglevel_WARN) except: self._share_local_folder_lock.release() raise self._share_local_folder_lock.release() if len(_stdout) >= 6 and _stdout[5].endswith('ok'): return True return False def unshare_all_local_folders(self): """\ Unshare all local folders mount in the X2Go session. :returns: returns ``True`` if all local folders could be successfully unmounted from the X2Go server session :rtype: ``bool`` """ self.logger('unsharing all local folders from session %s' % self.session_info, log.loglevel_INFO) cmd_line = [ 'export HOSTNAME &&', 'x2goumount-session', self.session_info.name, ] (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) _stderr = stderr.read() if sys.version_info[0] >= 3: _stderr = _stderr.decode() if not _stderr: self.logger('x2goumount-session (all mounts) for session %s has been successful' % self.session_info, log.loglevel_NOTICE) return True else: self.logger('x2goumount-session (all mounts) for session %s failed' % self.session_info, log.loglevel_ERROR) return False def unshare_local_folder(self, local_path): """\ Unshare local folder given as from X2Go session. :param local_path: the full path to an existing folder on the local file system (Default value = None) :returns: returns ``True`` if the local folder could be successfully unmounted from the X2Go server session :rtype: ``bool`` """ self.logger('unsharing local folder from session %s' % self.session_info, log.loglevel_INFO) cmd_line = [ 'export HOSTNAME &&', 'x2goumount-session', self.session_info.name, "'%s'" % local_path, ] (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) _stderr = stderr.read() if sys.version_info[0] >= 3: _stderr = _stderr.decode() if not _stderr: self.logger('x2goumount-session (%s) for session %s has been successful' % (local_path, self.session_info, ), log.loglevel_NOTICE) return True else: self.logger('x2goumount-session (%s) for session %s failed' % (local_path, self.session_info, ), log.loglevel_ERROR) return False def color_depth(self): """\ Retrieve the session's color depth. :returns: the session's color depth :rtype: ``int`` """ return self.params.depth def auto_session_window_title(self, dont_set=False): """\ Automatically generate an appropriate human-readable session window title. The session window title will be provider in the ``session_title`` property of this method. :param dont_set: generate the session window title, but do not actually set it (Default value = False) :type dont_set: ``bool`` """ _generic_title = 'X2GO-%s' % self.session_info.name # no blanks at beginning or end, no blanks-only... self.session_title = self.session_title.strip() if self.get_session_type() == 'D': if self.set_session_title: if not self.session_title: self.session_title = '%s for %s@%s' % (self.params.cmd, self.control_session.remote_username(), self.control_session.get_hostname()) else: # session title fallback... (like X2Go server does it...) self.session_title = _generic_title elif self.get_session_type() == 'S': if self.set_session_title: shared_user = _generic_title.split('XSHAD')[1] shared_display = _generic_title.split('XSHAD')[2].replace('PP', ':').split("_")[0] self.session_title = 'Desktop %s@%s shared with %s@%s' % (shared_user, shared_display, self.control_session.remote_username(), self.control_session.get_hostname()) else: # session title fallback... (like X2Go server does it...) self.session_title = _generic_title else: # do nothing for rootless sessions self.session_title = _generic_title if self.session_title != _generic_title and not dont_set: self.set_session_window_title(title=self.session_title) def find_session_window(self, timeout=60): """\ Try for seconds to find the X2Go session window of this terminal session. A background thread will get spawned for this operation. :param timeout: try for seconds to find the session window (Default value = 60) :type timeout: ``int`` """ gevent.spawn(self._find_session_window, timeout=timeout) def _find_session_window(self, timeout=0): """\ Try for seconds to find the X2Go session window of this terminal session. :param timeout: try for seconds to find the session window (Default value = 0) :type timeout: ``int`` """ self.session_window = None # search for the window of our focus, do this in a loop till the window as been found # or timeout forces us to give up... timeout += 1 while timeout: timeout -= 1 window = utils.find_session_window(self.session_info.name) if window is not None: if _X2GOCLIENT_OS == "Windows": self.logger('Session window handle for session %s is: %s' % (self.session_info.name, window), loglevel=log.loglevel_DEBUG) else: self.logger('Session window ID for session %s is: %s' % (self.session_info.name, window.id), loglevel=log.loglevel_DEBUG) self.session_window = window self.update_session_window_file() break gevent.sleep(1) def update_session_window_file(self): """\ Create a file that contains information on the session window. . If the file already exists, its content gets update. """ session_window_file = os.path.join(self.session_info.local_container, 'session.window') if self.session_window is not None: f = open(session_window_file,'w') if _X2GOCLIENT_OS != "Windows": _id = self.session_window.id else: _id = self.session_window f.write(u'ID:{window_id}\n'.format(window_id=_id)) f.close() self.logger('Updating session.window file %s: Window-ID->%s' % (session_window_file, _id), loglevel=log.loglevel_DEBUG) else: try: os.remove(session_window_file) except OSError as e: # this is no error in most cases... self.logger('The session window file %s is already gone (we failed to remove it with error: %s). In most cases this can be safely ignored.' % (session_window_file, str(e)), loglevel=log.loglevel_INFO) def set_session_window_title(self, title, timeout=60): """\ Modify the session window title. A background thread will get spawned for this operation. :param title: new title for the terminal session's session window :type title: ``str`` :param timeout: try for seconds to find the session window (Default value = 60) :type timeout: ``int`` """ gevent.spawn(self._set_session_window_title, title=title.strip(), timeout=timeout) def _set_session_window_title(self, title, timeout=0): """\ Modify the session window title. :param title: new title for the terminal session's session window :type title: ``str`` :param timeout: try for seconds to find the session window (Default value = 0) :type timeout: ``int`` """ self.session_title = title if not self.session_title: self.auto_session_title(dont_set=True) timeout += 1 while timeout: timeout -= 1 if self.session_window is not None: self.logger('Setting session window title for session %s is: %s' % (self.session_info.name, self.session_title), loglevel=log.loglevel_DEBUG) utils.set_session_window_title(self.session_window, self.session_title) break gevent.sleep(1) def raise_session_window(self, timeout=60): """\ Try for seconds to raise the X2Go session window of this terminal session to the top and bring it to focus. A background thread will get spawned for this operation. :param timeout: try for seconds to raise the session window (Default value = 60) :type timeout: ``int`` """ gevent.spawn(self._raise_session_window, timeout=timeout) def _raise_session_window(self, timeout=0): """\ Try for seconds to raise the X2Go session window of this terminal session to the top and bring it to focus. :param timeout: try for seconds to raise the session window (Default value = 0) :type timeout: ``int`` """ timeout += 1 while timeout: timeout -= 1 if self.session_window is not None: utils.raise_session_window(self.session_window) break gevent.sleep(1) def has_command(self, cmd): """\ ,,Guess'' if the command ```` exists on the X2Go server and is executable. The expected result is not 100% safe, however, it comes with a high probability to be correct. :param cmd: session command :type cmd: ``str`` :returns: ``True`` if this method reckons that the command is executable on the remote X2Go server :rtype: ``bool`` """ test_cmd = None; cmd = cmd.strip('"').strip('"') if cmd.find('RDP') != -1: cmd = 'rdesktop' if cmd in _X2GO_GENERIC_APPLICATIONS: return True if cmd in list(_X2GO_DESKTOPSESSIONS.keys()): return True elif 'XSHAD' in cmd: return True elif 'PUBLISHED' in cmd and 'X2GO_PUBLISHED_APPLICATIONS' in self.control_session.get_server_features(): return True elif cmd and cmd.startswith('/'): # check if full path is correct _and_ if application is in server path test_cmd = 'test -x %s && which %s && echo OK' % (cmd, os.path.basename(cmd.split()[0])) elif cmd and '/' not in cmd.split()[0]: # check if application is in server path only test_cmd = 'which %s && echo OK' % os.path.basename(cmd.split()[0]) if test_cmd: (stdin, stdout, stderr) = self.control_session._x2go_exec_command([test_cmd]) _stdout = stdout.read() if sys.version_info[0] >= 3: _stdout = _stdout.decode() return _stdout.find('OK') != -1 else: return False def run_command(self, cmd=None, env={}): """\ Run a command in this session. After :class:`x2go.backends.terminal.plain.X2GoTerminalSession.start()` has been called one or more commands can be executed with :class:`x2go.backends.terminal.plain.X2GoTerminalSession.run_command()` within the current X2Go session. :param cmd: Command to be run (Default value = None) :type cmd: ``str`` :param env: add server-side environment variables (Default value = {}) :type env: ``dict`` :returns: stdout.read() and stderr.read() as returned by the run command on the X2Go server :rtype: ``tuple`` of ``str`` """ if not self.has_command(_rewrite_cmd(str(self.params.cmd), params=self.params)): if self.client_instance: self.client_instance.HOOK_no_such_command(profile_name=self.profile_name, session_name=self.session_info.name, cmd=self.params.cmd) return False if cmd in ("", None): if self.params.cmd is None: cmd = 'TERMINAL' else: cmd = self.params.cmd if cmd == 'XDMCP': # do not run command when in XDMCP mode... return None if 'XSHAD' in cmd: # do not run command when in DESKTOP SHARING mode... return None self.params.update(cmd=cmd) # do not allow the execution of full path names if '/' in cmd: cmd = os.path.basename(cmd) cmd_line = [ "setsid x2goruncommand", str(self.session_info.display), str(self.session_info.agent_pid), str(self.session_info.name), str(self.session_info.snd_port), _rewrite_blanks(_rewrite_cmd(cmd, params=self.params)), str(self.params.snd_system), str(self.get_session_type()), "1>/dev/null 2>/dev/null & exit", ] if self.params.snd_system == 'pulse': cmd_line = [ 'PULSE_CLIENTCONFIG=%s/.pulse-client.conf' % self.session_info.remote_container ] + cmd_line if env: for env_var in list(env.keys()): cmd_line = [ '%s=%s' % (env_var, env[env_var]) ] + cmd_line (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) if self.params.kbtype not in ('null/null', 'auto') and (self.params.kblayout not in ('null', '') or self.params.kbvariant not in ('null', '')): self.set_keyboard(layout=self.params.kblayout, variant=self.params.kbvariant) if sys.version_info[0] >= 3: return stdout.read().decode(), stderr.read().decode() else: return stdout.read(), stderr.read() def is_desktop_session(self): """\ Is this (terminal) session a desktop session? :returns: Returns ``True`` is this session is a desktop session. :rtype: ``bool`` """ if self.session_info: return self.session_info.is_desktop_session() return False def is_published_applications_provider(self): """\ Is this (terminal) session a published applications provider? :returns: Returns ``True`` is this session is a provider session for published applications. :rtype: ``bool`` """ if self.session_info and self.is_running(): return self.session_info.is_published_applications_provider() return False def set_keyboard(self, layout='null', variant='null'): """\ Set the keyboard layout and variant for this (running) session. :param layout: keyboard layout to be set (Default value = 'null') :type layout: ``str`` :param variant: keyboard variant to be set (Default value = 'null') :type variant: ``str`` :returns: returns ``True`` if the {setxkbmap} command could be executed successfully. :rtype: ``bool`` """ if not self.is_running(): return False cmd_line = [ 'export DISPLAY=:%s && ' % str(self.session_info.display), 'setxkbmap ' ] if layout != 'null': self.logger('setting keyboad layout ,,%s\'\' for session %s' % (layout, self.session_info), log.loglevel_INFO) cmd_line.append('-layout %s' % layout) if variant != 'null': self.logger('setting keyboad variant ,,%s\'\' for session %s' % (variant, self.session_info), log.loglevel_INFO) cmd_line.append('-variant %s' % variant) (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) _stderr = stderr.read() if sys.version_info[0] >= 3: _stderr = _stderr.decode() if not _stderr: self.logger('setting keyboard layout ,,%s\'\' and variant ,,%s\'\' for session %s has been successful' % (layout, variant, self.session_info), log.loglevel_NOTICE) return True else: self.logger('setting keyboard layout ,,%s\'\' and variant ,,%s\'\' for session %s failed: %s' % (layout, variant, self.session_info, _stderr.replace('\n', ' ')), log.loglevel_ERROR) return False def exec_published_application(self, exec_name, timeout=20, env={}): """\ Executed a published application. :param exec_name: application to be executed :type exec_name: ``str`` :param timeout: execution timeout (Default value = 20) :type timeout: ``int`` :param env: session environment dictionary (Default value = {}) :type env: ``dict`` """ cmd_line = [ "export DISPLAY=:%s && " % str(self.session_info.display), "export X2GO_SESSION=%s && " % str(self.get_session_name()), ] if self.params.snd_system == 'pulse': cmd_line.append("export PULSE_CLIENTCONFIG=%s/.pulse-client.conf && " % self.session_info.remote_container) if env: for env_var in list(env.keys()): cmd_line = [ 'export %s=%s && ' % (env_var, env[env_var]) ] + cmd_line cmd_line.extend( [ "setsid %s" % exec_name, "1>/dev/null 2>/dev/null & exit", ] ) self.logger('executing published application %s for %s with command line: %s' % (exec_name, self.profile_name, cmd_line), loglevel=log.loglevel_DEBUG) (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line, timeout=timeout) def ok(self): """\ X2Go session OK? :returns: Returns ``True`` if this X2Go (terminal) session is up and running, ``False`` otherwise. :rtype: ``bool`` """ _ok = bool(self.session_info.name and self.proxy.ok()) return _ok def is_running(self): """\ X2Go session running? :returns: Returns ``True`` if this X2Go (terminal) session is in running state, ``False`` otherwise. :rtype: ``bool`` """ return self.session_info.is_running() def is_suspended(self): """\ X2Go session suspended? :returns: Returns ``True`` if this X2Go (terminal) session is in suspended state, ``False`` otherwise. :rtype: ``bool`` """ return self.session_info.is_suspended() def is_connected(self): """\ X2Go session connected? :returns: Returns ``True`` if this X2Go session's Paramiko/SSH transport is connected/authenticated, ``False`` else. :rtype: ``bool`` """ return self.control_session.is_connected() def start(self): """\ Start a new X2Go session. :returns: ``True`` if session startup has been successful and the X2Go proxy is up-and-running :rtype: ``bool`` :raises X2GoTerminalSessionException: if the session startup failed :raises X2GoDesktopSharingDenied: if desktop sharing fails because of denial by the user running the desktop to be shared """ self.params.rewrite_session_type() if not self.has_command(_rewrite_cmd(self.params.cmd, params=self.params)): if self.client_instance: self.client_instance.HOOK_no_such_command(profile_name=self.profile_name, session_name=self.session_info.name, cmd=self.params.cmd) return False setkbd = "0" if self.params.kbtype != "null/null": setkbd = "1" if '/' in self.params.cmd: self.params.cmd = os.path.basename(self.params.cmd) self.params.rewrite_session_type() # auto-detect proxy backend, if requested if self.proxy_backend == 'auto-detect': if self.params.cmd in list(_X2GO_DESKTOPSESSIONS_KDRIVE_PREFERRED.keys()): self.proxy_backend = utils._get_backend_class("KDRIVE", "X2GoProxy") self.params.kdrive = True elif os.path.basename(self.params.cmd) in list(_X2GO_DESKTOPSESSIONS_KDRIVE_PREFERRED.values()): self.proxy_backend = utils._get_backend_class("KDRIVE", "X2GoProxy") self.params.kdrive = True elif self.params.cmd in list(_X2GO_DESKTOPSESSIONS_NX3_PREFERRED.keys()): self.proxy_backend = utils._get_backend_class("NX3", "X2GoProxy") elif os.path.basename(self.params.cmd) in list(_X2GO_DESKTOPSESSIONS_NX3_PREFERRED.values()): self.proxy_backend = utils._get_backend_class("NX3", "X2GoProxy") else: self.proxy_backend = utils._get_backend_class("default", "X2GoProxy") if self.params.kdrive and not 'X2GOKDRIVE_BASE_SUPPORT' in self.control_session.get_server_features(): self.logger('X2Go KDrive is not supported on the remote X2Go Server', loglevel=log.loglevel_ERROR) raise x2go_exceptions.X2GoTerminalSessionException('X2Go KDrive is not support server-side') self.params.rewrite_session_type() if self.params.geometry == 'maximize': _geometry = utils.get_workarea_geometry() if _geometry is None or len(_geometry) != 2: _geometry = utils.get_desktop_geometry() if _geometry and len(_geometry) == 2: self.params.geometry = "%sx%s" % _geometry else: self.logger('failed to detect best maximized geometry of your client-side desktop', loglevel=log.loglevel_WARN) self.params.geometry = "1024x768" cmd_line = [ "x2gostartagent", str(self.params.geometry), str(self.params.link), str(self.params.pack), str(self.params.cache_type+'-depth_'+self.params.depth), str(self.params.kblayout), str(self.params.kbtype), str(setkbd), str(self.get_session_type()), str(self.params.cmd), ] if self.get_session_type() != 'S': cmd_line.append( str(self.params.clipboard), ) if self.params.cmd == 'XDMCP' and self.params.xdmcp_server: cmd_line = ['X2GOXDMCP=%s' % self.params.xdmcp_server] + cmd_line if self.params.dpi: cmd_line = ['X2GODPI=%s' % self.params.dpi] + cmd_line if self.params.xinerama: cmd_line = ['X2GO_XINERAMA=true'] + cmd_line else: cmd_line = ['X2GO_XINERAMA=false'] + cmd_line (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) _stdout = stdout.read() _stderr = stderr.read() if sys.version_info[0] >= 3: _stdout = _stdout.decode() _stderr = _stderr.decode() # if the first line of stdout is a "DEN(Y)" string then we will presume that # we tried to use X2Go desktop sharing and the sharing was rejected if "ACCESS DENIED" in _stderr and "XSHAD" in _stderr: raise x2go_exceptions.X2GoDesktopSharingDenied('X2Go desktop sharing has been denied by the remote user') try: self.session_info.initialize(_stdout, username=self.control_session.remote_username(), hostname=self.control_session.remote_peername(), ) except ValueError: raise x2go_exceptions.X2GoTerminalSessionException("failed to start X2Go session") except IndexError: raise x2go_exceptions.X2GoTerminalSessionException("failed to start X2Go session") # local path may be a Windows path, so we use the path separator of the local system self.session_info.local_container = os.path.join(self.params.rootdir, 'S-%s' % self.session_info.name) # remote path is always a UniX path... self.session_info.remote_container = '%s/.x2go/C-%s' % (self.control_session._x2go_remote_home, self.session_info.name, ) # set up SSH tunnel for X11 graphical elements and launch client-side proxy/kdrive client self.proxy = self.proxy_backend(session_info=self.session_info, ssh_transport=self.control_session.get_transport(), sessions_rootdir=self.sessions_rootdir, session_instance=self.session_instance, proxy_options=self.proxy_options, logger=self.logger) self.proxy_subprocess, proxy_ok = self.proxy.start_proxy() if proxy_ok: self.active_threads.append(self.proxy) if self.get_session_type() in ('D', 'S', 'K'): self.find_session_window() self.auto_session_window_title() self.raise_session_window() if self.params.published_applications: self.control_session.get_published_applications() else: raise x2go_exceptions.X2GoTerminalSessionException("failed to start X2Go session") return proxy_ok def resume(self): """\ Resume a running/suspended X2Go session. :returns: ``True`` if the session could successfully be resumed :rtype: ``bool`` :raises X2GoTerminalSessionException: if the terminal session failed to update server-side reported port changes """ setkbd = "0" if self.params.kbtype != "null/null": setkbd = "1" if self.params.geometry == 'maximize': _geometry = utils.get_workarea_geometry() if _geometry is None or len(_geometry) != 2: _geometry = utils.get_desktop_geometry() if _geometry and len(_geometry) == 2: self.params.geometry = "%sx%s" % _geometry else: self.logger('failed to detect best maxmimized geometry of your client-side desktop, using 1024x768 instead', loglevel=log.loglevel_WARN) self.params.geometry = "1024x768" cmd_line = [ "x2goresume-session", self.session_info.name, self.params.geometry, self.params.link, self.params.pack, self.params.kblayout, self.params.kbtype, setkbd, self.params.clipboard, str(self.params.xinerama), ] (stdin, stdout, stderr) = self.control_session._x2go_exec_command(cmd_line) _stdout = stdout.read() if sys.version_info[0] >= 3: _stdout = _stdout.decode() # re-allocate (if needed) server-side ports for graphics, sound and sshfs for stdout_line in _stdout.split('\n'): try: _new_value = stdout_line.split("=")[1].strip() if 'gr_port=' in stdout_line and _new_value != str(self.session_info.graphics_port): try: self.session_info.graphics_port = int(_new_value) self.logger('re-allocating graphics port for session %s, old server-side port is in use; new graphics port is %s' % (self.session_info, self.session_info.graphics_port), loglevel=log.loglevel_NOTICE) except TypeError: # if the re-allocation fails, this is fatal!!! raise x2go_exceptions.X2GoTerminalSessionException('Failed to retrieve new graphics port from server. X2Go Session cannot be resumed.') elif 'sound_port=' in stdout_line and _new_value != str(self.session_info.snd_port): try: self.session_info.snd_port = int(_new_value) self.logger('re-allocating sound port for session %s, old server-side port is in use; new sound port is %s' % (self.session_info, self.session_info.snd_port), loglevel=log.loglevel_NOTICE) except TypeError: self.logger('Failed to retrieve new sound port from server for session %s, session will be without sound.' % self.session_info, loglevel=log.loglevel_WARN) elif 'fs_port=' in stdout_line and _new_value != str(self.session_info.sshfs_port): try: self.session_info.sshfs_port = int(_new_value) self.logger('re-allocating sshfs port for session %s, old server-side port is in use; new sshfs port is %s' % (self.session_info, self.session_info.sshfs_port), loglevel=log.loglevel_NOTICE) except TypeError: self.logger('Failed to retrieve new sshfs port from server for session %s, session will be without client-side folder sharing. Neither will there be X2Go printing nor X2Go MIME box support.' % self.session_info, loglevel=log.loglevel_WARN) except IndexError: continue # local path may be a Windows path, so we use the path separator of the local system self.session_info.local_container = os.path.join(self.params.rootdir, 'S-%s' % self.session_info.name) # remote path is always a UniX path... self.session_info.remote_container = '%s/.x2go/C-%s' % (self.control_session._x2go_remote_home, self.session_info.name, ) # make sure to use the correct proxy backend if self.is_kdrive_session(): self.proxy_backend = utils._get_backend_class("KDRIVE", "X2GoProxy") else: self.proxy_backend = utils._get_backend_class("NX3", "X2GoProxy") self.proxy = self.proxy_backend(session_info=self.session_info, ssh_transport=self.control_session.get_transport(), sessions_rootdir=self.sessions_rootdir, session_instance=self.session_instance, proxy_options=self.proxy_options, logger=self.logger, ) self.proxy_subprocess, proxy_ok = self.proxy.start_proxy() if proxy_ok: self.params.depth = self.session_info.name.split('_')[2][2:] # on a session resume the user name comes in as a user ID. We have to translate this... self.session_info.username = self.control_session.remote_username() if self.params.kbtype not in ('null/null', 'auto') and (self.params.kblayout not in ('null', '') or self.params.kbvariant not in ('null', '')): self.set_keyboard(layout=self.params.kblayout, variant=self.params.kbvariant) if self.get_session_type() in ('D', 'S'): self.find_session_window() self.auto_session_window_title() self.raise_session_window() if self.is_published_applications_provider(): self.control_session.get_published_applications() self.published_applications = True else: raise x2go_exceptions.X2GoTerminalSessionException("failed to start X2Go session") return proxy_ok def suspend(self): """\ Suspend this X2Go (terminal) session. :returns: ``True`` if the session terminal could be successfully suspended :rtype: ``bool`` """ self.release_telekinesis() self.control_session.suspend(session_name=self.session_info.name) self.release_proxy() # TODO: check if session has really suspended _ret = True return _ret def terminate(self): """\ Terminate this X2Go (terminal) session. :returns: ``True`` if the session could be successfully terminated :rtype: ``bool`` """ self.release_telekinesis() self.control_session.terminate(session_name=self.session_info.name, destroy_terminals=False) self.release_proxy() self.post_terminate_cleanup() self.__del__() # TODO: check if session has really suspended _ret = True return _ret def release_proxy(self): """\ Let the X2Go proxy command cleanly die away... (by calling its destructor). """ if self.proxy is not None: self.proxy.__del__() self.proxy = None def release_telekinesis(self): """\ Let the attached Telekinesis client cleanly die away... (by calling its destructor). """ if self.telekinesis_client is not None: self.telekinesis_client.__del__() self.telekinesis_client = None def post_terminate_cleanup(self): """\ Do some cleanup after this session has terminated. """ # this method might be called twice (directly and from update_status in the session # registry instance. So we have to make sure, that this code will not fail # if called twice. if not self._cleaned_up and self.session_info.name: # otherwise we wipe the session files locally self.logger('cleaning up session %s after termination' % self.session_info, loglevel=log.loglevel_NOTICE) # if we run in debug mode, we keep local session directories if self.logger.get_loglevel() & log.loglevel_DEBUG != log.loglevel_DEBUG: self._rm_session_dirtree() self._rm_desktop_dirtree() self._cleaned_up = True def is_kdrive_session(self): """\ Test if this terminal session is a KDrive based desktop session. :returns: ``True`` if this session is of session type KDrive ('K'). :rtype: ``bool`` """ if not self.session_info.is_initialized(): self.params.rewrite_session_type() return self.get_session_type() == 'K' def is_rootless_session(self): """\ Test if this terminal session is a rootless session. :returns: ``True`` if this session is of session type rootless ('R'). :rtype: ``bool`` """ if not self.session_info.is_initialized(): self.params.rewrite_session_type() return self.get_session_type() == 'R' def is_shadow_session(self): """\ Test if this terminal session is a desktop sharing (aka shadow) session. :returns: ``True`` if this session is of session type shadow ('S'). :rtype: ``bool`` """ if not self.session_info.is_initialized(): self.params.rewrite_session_type() return self.get_session_type() == 'S' def is_pubapp_session(self): """\ Test if this terminal session is a published applications session. :returns: ``True`` if this session is of session type published applications ('P'). :rtype: ``bool`` """ if not self.session_info.is_initialized(): self.params.rewrite_session_type() return self.get_session_type() == 'P' python-x2go-0.6.1.4/x2go/cache.py0000644000000000000000000003230714470264762013261 0ustar # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. """\ X2GoListSessionCache class - caching X2Go session information. """ __NAME__ = 'x2gocache-pylib' __package__ = 'x2go' __name__ = 'x2go.cache' # modules import copy import gevent # Python X2Go modules from . import log from . import x2go_exceptions class X2GoListSessionsCache(object): """\ For non-blocking operations in client applications using Python X2Go, it is recommended to enable the :class:`x2go.cache.X2GoListSessionsCache`. This can be done by calling the constructor of the :class:`x2go.client.X2GoClient` class. The session list and desktop cache gets updated in regular intervals by a threaded :class:`x2go.guardian.X2GoSessionGuardian` instance. For the session list and desktop list update, the X2Go server commands ``x2golistsessions`` and ``x2godesktopsessions`` are called and the command's stdout is cached in the session list cache. Whenever your client application needs access to either the server's session list or the server's desktop list the session cache is queried instead. This assures that the server's session/desktop list is available without delay, even on slow internet connections. """ x2go_listsessions_cache = {} def __init__(self, client_instance, logger=None, loglevel=log.loglevel_DEFAULT): """\ :param client_instance: the :class:`x2go.client.X2GoClient` instance that uses this :class:`x2go.cache.X2GoListSessionsCache` :type client_instance: ``obj`` :param logger: you can pass an :class:`x2go.log.X2GoLogger` object to the :class:`x2go.cache.X2GoListSessionsCache` constructor :type logger: ``obj`` :param loglevel: if no :class:`x2go.log.X2GoLogger` object has been supplied a new one will be constructed with the given loglevel :type loglevel: ``int`` """ self.x2go_listsessions_cache = {} self.last_listsessions_cache = {} self.protected = False if logger is None: self.logger = log.X2GoLogger(loglevel=loglevel) else: self.logger = copy.deepcopy(logger) self.logger.tag = __NAME__ self.client_instance = client_instance def delete(self, profile_name): """\ Remove session list from cache for a given profile. :param profile_name: name of profile to operate on :type profile_name: ``str`` """ while self.protected: gevent.sleep(.1) try: del self.x2go_listsessions_cache[profile_name] except KeyError: pass def check_cache(self): """\ Check if session list cache elements are still valid (i.e. if all corresponding session profiles are still connected). If not so, remove invalid cache entries from the session list cache. """ for profile_name in list(self.x2go_listsessions_cache.keys()): if profile_name not in self.client_instance.client_connected_profiles(return_profile_names=True): del self.x2go_listsessions_cache[profile_name] def update_all(self, update_sessions=True, update_desktops=False): """\ Update :class:`x2go.cache.X2GoListSessionsCache` for all connected session profiles. :param update_sessions: cache recent session lists from all connected servers (Default value = True) :type update_sessions: ``bool`` :param update_desktops: cache recent desktop lists from all connected servers (Default value = False) :type update_desktops: ``bool`` """ for profile_name in self.client_instance.client_connected_profiles(return_profile_names=True): self.update(profile_name, update_sessions=update_sessions, update_desktops=update_desktops) self.check_cache() def update(self, profile_name, update_sessions=True, update_desktops=False, update_mounts=False): """\ Update :class:`x2go.cache.X2GoListSessionsCache` (i.e. session/desktops) for session profile ``profile_name``. :param profile_name: name of profile to update :type profile_name: ``str`` :param update_sessions: cache recent session list from server (Default value = True) :type update_sessions: ``bool`` :param update_desktops: cache recent desktop list from server (Default value = False) :type update_desktops: ``bool`` :param update_mounts: cache list of client-side mounts on server (Default value = False) :type update_mounts: ``bool`` """ self.protected = True self.last_listsessions_cache = copy.deepcopy(self.x2go_listsessions_cache) control_session = self.client_instance.client_control_session_of_profile_name(profile_name) if profile_name not in self.x2go_listsessions_cache: self.x2go_listsessions_cache[profile_name] = {'sessions': None, 'desktops': None, 'mounts': {}, } if update_sessions: self._update_sessions(profile_name, control_session) if update_desktops: self._update_desktops(profile_name, control_session) if update_mounts: self._update_mounts(profile_name, control_session) self.protected = False def _update_mounts(self, profile_name, control_session): """\ Update mounts list of :class:`x2go.cache.X2GoListSessionsCache` for session profile ``profile_name``. :param profile_name: name of profile to update :type profile_name: ``str`` :param control_session: X2Go control session instance :raises X2GoControlSessionException: if the control session's ``list_mounts`` method fails """ try: self.x2go_listsessions_cache[profile_name]['mounts'] = {} if self.x2go_listsessions_cache[profile_name]['sessions']: for session_name in self.x2go_listsessions_cache[profile_name]['sessions']: session = self.client_instance.get_session_of_session_name(session_name, return_object=True, match_profile_name=profile_name) if session is not None and session.is_running(): if control_session is not None and not control_session.has_session_died(): self.x2go_listsessions_cache[profile_name]['mounts'].update(control_session.list_mounts(session_name)) except (x2go_exceptions.X2GoControlSessionException, AttributeError) as e: if profile_name in list(self.x2go_listsessions_cache.keys()): del self.x2go_listsessions_cache[profile_name] self.protected = False raise x2go_exceptions.X2GoControlSessionException(str(e)) except x2go_exceptions.X2GoTimeOutException: pass except KeyError: pass def _update_desktops(self, profile_name, control_session): """\ Update session lists of :class:`x2go.cache.X2GoListSessionsCache` for session profile ``profile_name``. :param profile_name: name of profile to update :type profile_name: ``str`` :param control_session: X2Go control session instance :type control_session: ``obj`` :raises X2GoControlSessionException: if the control session's ``list_desktop`` method fails """ try: if control_session is not None and not control_session.has_session_died(): self.x2go_listsessions_cache[profile_name]['desktops'] = control_session.list_desktops() except (x2go_exceptions.X2GoControlSessionException, AttributeError) as e: if profile_name in list(self.x2go_listsessions_cache.keys()): del self.x2go_listsessions_cache[profile_name] self.protected = False raise x2go_exceptions.X2GoControlSessionException(str(e)) except x2go_exceptions.X2GoTimeOutException: pass except KeyError: pass def _update_sessions(self, profile_name, control_session): """\ Update desktop list of :class:`x2go.cache.X2GoListSessionsCache` for session profile ``profile_name``. :param profile_name: name of profile to update :type profile_name: ``str`` :param control_session: X2Go control session instance :type control_session: ``obj`` :raises X2GoControlSessionException: if the control session's ``list_sessions`` method fails """ try: if control_session is not None and not control_session.has_session_died(): self.x2go_listsessions_cache[profile_name]['sessions'] = control_session.list_sessions() except (x2go_exceptions.X2GoControlSessionException, AttributeError) as e: if profile_name in list(self.x2go_listsessions_cache.keys()): del self.x2go_listsessions_cache[profile_name] self.protected = False raise x2go_exceptions.X2GoControlSessionException(str(e)) except KeyError: pass def list_sessions(self, session_uuid): """\ Retrieve a session list from the current cache content of :class:`x2go.cache.X2GoListSessionsCache` for a given :class:`x2go.session.X2GoSession` instance (specified by its unique session UUID). :param session_uuid: unique identifier of session to query cache for :type session_uuid: ``str`` :returns: a data object containing available session information :rtype: ``X2GoServerSessionList*`` instance (or ``None``) """ profile_name = self.client_instance.get_session_profile_name(session_uuid) if self.is_cached(session_uuid=session_uuid): return self.x2go_listsessions_cache[profile_name]['sessions'] else: return None def list_desktops(self, session_uuid): """\ Retrieve a list of available desktop sessions from the current cache content of :class:`x2go.cache.X2GoListSessionsCache` for a given :class:`x2go.session.X2GoSession` instance (specified by its unique session UUID). :param session_uuid: unique identifier of session to query cache for :type session_uuid: ``str`` :returns: a list of strings representing X2Go desktop sessions available for sharing :rtype: ``list`` (or ``None``) """ profile_name = self.client_instance.get_session_profile_name(session_uuid) if self.is_cached(session_uuid=session_uuid): return self.x2go_listsessions_cache[profile_name]['desktops'] else: return None def list_mounts(self, session_uuid): """\ Retrieve a list of mounted client shares from the current cache content of :class:`x2go.cache.X2GoListSessionsCache` for a given :class:`x2go.session.X2GoSession` instance (specified by its unique session UUID). :param session_uuid: unique identifier of session to query cache for :type session_uuid: ``str`` :returns: a list of strings representing mounted client shares :rtype: ``list`` (or ``None``) """ profile_name = self.client_instance.get_session_profile_name(session_uuid) if self.is_cached(session_uuid=session_uuid): return self.x2go_listsessions_cache[profile_name]['mounts'] else: return None def is_cached(self, profile_name=None, session_uuid=None, cache_type=None): """\ Check if session information is cached. :param profile_name: name of profile to update (Default value = None) :type profile_name: ``str`` :param session_uuid: unique identifier of session to query cache for (Default value = None) :type session_uuid: ``str`` :param cache_type: cache type (e.g. 'mounts', 'desktops', 'sessions') (Default value = None) :type cache_type: ``str`` :returns: ``True`` if session information is cached :rtype: ``bool`` """ if profile_name is None and session_uuid and self.client_instance: try: profile_name = self.client_instance.get_session_profile_name(session_uuid) except x2go_exceptions.X2GoSessionRegistryException: raise x2go_exceptions.X2GoSessionCacheException("requested session UUID is not valid anymore") _is_profile_cached = profile_name in self.x2go_listsessions_cache _is_cache_type_cached = _is_profile_cached and cache_type in self.x2go_listsessions_cache[profile_name] if cache_type is None: return _is_profile_cached else: return _is_cache_type_cached python-x2go-0.6.1.4/x2go/checkhosts.py0000644000000000000000000003341314470264762014353 0ustar # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. """\ Providing mechanisms to ``X2GoControlSession*`` backends for checking host validity. """ __NAME__ = 'x2gocheckhosts-pylib' __package__ = 'x2go' __name__ = 'x2go.checkhosts' # modules import paramiko import binascii import sys # Python X2Go modules from . import log from . import x2go_exceptions import random import string class X2GoMissingHostKeyPolicy(paramiko.MissingHostKeyPolicy): """\ Skeleton class for Python X2Go's missing host key policies. """ def __init__(self, caller=None, session_instance=None, fake_hostname=None): """\ :param caller: calling instance :type caller: ``class`` :param session_instance: an X2Go session instance :type session_instance: :class:`x2go.session.X2GoSession` instance """ self.caller = caller self.session_instance = session_instance self.fake_hostname = fake_hostname def get_client(self): """\ Retrieve the Paramiko SSH/Client. :returns: the associated X2Go control session instance. :rtype: ``X2GoControlSession*`` instance """ return self.client def get_hostname(self): """\ Retrieve the server hostname:port expression of the server to be validated. :returns: hostname:port :rtype: ``str`` """ return self.fake_hostname or self.hostname def get_hostname_name(self): """\ Retrieve the server hostname string of the server to be validated. :returns: hostname :rtype: ``str`` """ if ":" in self.get_hostname(): return self.get_hostname().split(':')[0].lstrip('[').rstrip(']') else: return self.get_hostname().lstrip('[').rstrip(']') def get_hostname_port(self): """\ Retrieve the server port of the server to be validated. :returns: port :rtype: ``str`` """ if ":" in self.get_hostname(): return int(self.get_hostname().split(':')[1]) else: return 22 def get_key(self): """\ Retrieve the host key of the server to be validated. :returns: host key :rtype: Paramiko/SSH key instance """ return self.key def get_key_name(self): """\ Retrieve the host key name of the server to be validated. :returns: host key name (RSA, DSA, ECDSA...) :rtype: ``str`` """ return self.key.get_name().upper() def get_key_fingerprint(self): """\ Retrieve the host key fingerprint of the server to be validated. :returns: host key fingerprint :rtype: ``str`` """ if sys.version_info[0] >= 3: return binascii.hexlify(self.key.get_fingerprint()).decode() else: return binascii.hexlify(self.key.get_fingerprint()) def get_key_fingerprint_with_colons(self): """\ Retrieve the (colonized) host key fingerprint of the server to be validated. :returns: host key fingerprint (with colons) :rtype: ``str`` """ _fingerprint = self.get_key_fingerprint() _colon_fingerprint = '' idx = 0 for char in _fingerprint: idx += 1 _colon_fingerprint += char if idx % 2 == 0: _colon_fingerprint += ':' return _colon_fingerprint.rstrip(':') class X2GoAutoAddPolicy(X2GoMissingHostKeyPolicy): def missing_host_key(self, client, hostname, key): self.client = client self.hostname = hostname self.key = key if self.session_instance and self.session_instance.control_session.unique_hostkey_aliases: self.client._host_keys.add(self.session_instance.get_profile_id(), self.key.get_name(), self.key) else: self.client._host_keys.add(self.get_hostname(), self.key.get_name(), self.key) if self.client._host_keys_filename is not None: self.client.save_host_keys(self.client._host_keys_filename) self.client._log(paramiko.common.DEBUG, 'Adding %s host key for %s: %s' % (self.key.get_name(), self.get_hostname(), binascii.hexlify(self.key.get_fingerprint()))) class X2GoInteractiveAddPolicy(X2GoMissingHostKeyPolicy): """\ Policy for making host key information available to Python X2Go after a Paramiko/SSH connect has been attempted. This class needs information about the associated :class:`x2go.session.X2GoSession` instance. Once called, the :func:`missing_host_key()` method of this class will try to call :func:`X2GoSession.HOOK_check_host_dialog() `. This hook method---if not re-defined in your application---will then try to call the :func:`X2GoClient.HOOK_check_host_dialog() `, which then will return ``True`` by default if not customized in your application. To accept host key checks, make sure to either customize the :func:`X2GoClient.HOOK_check_host_dialog() ` method or the :func:`X2GoSession.HOOK_check_host_dialog() ` method and hook some interactive user dialog to either of them. """ def missing_host_key(self, client, hostname, key): """\ Handle a missing host key situation. This method calls Once called, the :func:`missing_host_key()` method will try to call :func:`X2GoSession.HOOK_check_host_dialog() `. This hook method---if not re-defined in your application---will then try to call the :func:`X2GoClient.HOOK_check_host_dialog() `, which then will return ``True`` by default if not customized in your application. To accept host key checks, make sure to either customize the :func:`X2GoClient.HOOK_check_host_dialog() ` method or the :func:`X2GoSession.HOOK_check_host_dialog() ` method and hook some interactive user dialog to either of them. :param client: SSH client (``X2GoControlSession*``) instance :type client: ``X2GoControlSession*`` instance :param hostname: remote hostname :type hostname: ``str`` :param key: host key to validate :type key: Paramiko/SSH key instance :raises X2GoHostKeyException: if the X2Go server host key is not in the ``known_hosts`` file :raises X2GoSSHProxyHostKeyException: if the SSH proxy host key is not in the ``known_hosts`` file :raises SSHException: if this instance does not know its {self.session_instance} """ self.client = client self.hostname = hostname if (self.hostname.find(']') == -1) and (self.hostname.find(':') == -1): # if hostname is an IPv4 quadruple with standard SSH port... self.hostname = '[%s]:22' % self.hostname self.key = key self.client._log(paramiko.common.DEBUG, 'Interactively Checking %s host key for %s: %s' % (self.key.get_name(), self.get_hostname(), binascii.hexlify(self.key.get_fingerprint()))) if self.session_instance: if self.fake_hostname is not None: server_key = client.get_transport().get_remote_server_key() keytype = server_key.get_name() our_server_key = client._system_host_keys.get(self.fake_hostname, {}).get(keytype, None) if our_server_key is None: if self.session_instance.control_session.unique_hostkey_aliases: our_server_key = client._host_keys.get(self.session_instance.get_profile_id(), {}).get(keytype, None) if our_server_key is not None: self.session_instance.logger('SSH host key verification for SSH-proxied host %s with %s fingerprint ,,%s\'\' succeeded. This host is known by the X2Go session profile ID of profile ,,%s\'\'.' % (self.fake_hostname, self.get_key_name(), self.get_key_fingerprint_with_colons(), self.session_instance.profile_name), loglevel=log.loglevel_NOTICE) return else: our_server_key = client._host_keys.get(self.fake_hostname, {}).get(keytype, None) if our_server_key is not None: self.session_instance.logger('SSH host key verification for SSH-proxied host %s with %s fingerprint ,,%s\'\' succeeded. This host is known by the address it has behind the SSH proxy host.' % (self.fake_hostname, self.get_key_name(), self.get_key_fingerprint_with_colons()), loglevel=log.loglevel_NOTICE) return self.session_instance.logger('SSH host key verification for host %s with %s fingerprint ,,%s\'\' initiated. We are seeing this X2Go server for the first time.' % (self.get_hostname(), self.get_key_name(), self.get_key_fingerprint_with_colons()), loglevel=log.loglevel_NOTICE) _valid = self.session_instance.HOOK_check_host_dialog(self.get_hostname_name(), port=self.get_hostname_port(), fingerprint=self.get_key_fingerprint_with_colons(), fingerprint_type=self.get_key_name(), ) if _valid: if self.session_instance.control_session.unique_hostkey_aliases and type(self.caller) not in (sshproxy.X2GoSSHProxy, ): paramiko.AutoAddPolicy().missing_host_key(client, self.session_instance.get_profile_id(), key) else: paramiko.AutoAddPolicy().missing_host_key(client, self.get_hostname(), key) else: if type(self.caller) in (sshproxy.X2GoSSHProxy, ): raise x2go_exceptions.X2GoSSHProxyHostKeyException('Invalid host %s is not authorized for access. Add the host to Paramiko/SSH\'s known_hosts file.' % self.get_hostname()) else: raise x2go_exceptions.X2GoHostKeyException('Invalid host %s is not authorized for access. Add the host to Paramiko/SSH\'s known_hosts file.' % self.get_hostname()) else: raise x2go_exceptions.SSHException('Policy has collected host key information on %s for further introspection' % self.get_hostname()) def check_ssh_host_key(x2go_sshclient_instance, hostname, port=22): """\ Perform a Paramiko/SSH host key check by connecting to the host and validating the results (i.e. by validating raised exceptions during the connect process). :param x2go_sshclient_instance: a Paramiko/SSH client instance to be used for testing host key validity. :type x2go_sshclient_instance: ``X2GoControlSession*`` instance :param hostname: hostname of server to validate :type hostname: ``str`` :param port: port of server to validate (Default value = 22) :type port: ``int`` :returns: returns a tuple with the following components (, , , , ) :rtype: ``tuple`` :raises SSHException: if an SSH exception occurred, that we did not provocate in :func:`X2GoInteractiveAddPolicy.missing_host_key() # # 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. """\ A recommended X2Go session clean up helper function. """ __package__ = 'x2go' __name__ = 'x2go.cleanup' import gevent import paramiko import threading # Python X2Go modules from . import rforward from .defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS if _X2GOCLIENT_OS == 'Windows': from . import xserver from . import pulseaudio def x2go_cleanup(e=None, threads=None): """\ For every Python X2Go application you write, please make sure to capture the ``KeyboardInterrupt`` and the ``SystemExit`` exceptions and call this function if either of the exceptions occurs. Example:: import x2go try: my_x2goclient = x2go.X2GoClient(...) [... your code ...] sys.exit(0) except (KeyboardInterrupt, SystemExit): x2go.x2go_cleanup() :param e: if :func:`x2go_cleanup()` got called as you caught an exception in your code this can be the ``Exception`` that we will process at the end of the clean-up (or if clean-up failed or was not appropriate) (Default value = None) :type e: ``exception`` :param threads: a list of threads to clean up (Default value = None) :type threads: ``list`` """ try: if threads is None: threads = threading.enumerate() else: threads = threads # stop X2Go reverse forwarding tunnels for t in threads: if type(t) == rforward.X2GoRevFwTunnel: t.stop_thread() del t # stop X2Go paramiko transports used by X2GoTerminalSession objects for t in threads: if type(t) == paramiko.Transport: if hasattr(t, '_x2go_session_marker'): t.stop_thread() del t # on Windows: stop the XServer that we evoked if _X2GOCLIENT_OS == 'Windows': for t in threads: if type(t) == xserver.X2GoXServer: t.stop_thread() del t # on Windows: stop the PulseAudio daemon that we evoked if _X2GOCLIENT_OS == 'Windows': for t in threads: if type(t) == pulseaudio.X2GoPulseAudio: t.stop_thread() del t for t in threads: # now let's catch X2GoSessionGuardian, which is a bit tricky # as we need to avoid circular imports if hasattr(t, 'stop_thread') and hasattr(t, 'guardian'): t.stop_thread() del t gevent.sleep(1) if e is not None: raise e except KeyboardInterrupt: # do not allow keyboard interrupts during Python X2Go cleanup pass except SystemExit: # neither do we allow SIGTERM signals during cleanup... pass python-x2go-0.6.1.4/x2go/client.py0000644000000000000000000047715614470264762013513 0ustar # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. """\ :class:`x2go.client.X2GoClient` is a public API class. Use this class in your Python X2Go based applications. Use it as a parent class for your own object oriented :class:`x2go.client.X2GoClient`'ish class implementation. Supported Features ================== Supported features are: - X2Go multi-session management - keep track of initiated sessions - grant access to X2Go client config files: ``settings``, ``printing``, ``sessions`` and ``xconfig`` (Windows only) as normally found in ``~/.x2goclient`` - communicate with X2Go Session Broker via http(s) or SSH - instantiate an X2Go session by a set of Python parameters - load a session profile from x2goclient's ``sessions`` configuration file and start the---profile-based pre-configured---session - sharing of local folders with remote X2Go sessions - enabling and mangaging X2Go printing (real printing, viewing as PDF, saving to a local folder or executing a custom »print« command - transparent tunneling of audio (Pulseaudio, ESD) - sharing of other desktops - published applications support Non-Profile Sessions ==================== A new non-profile based X2Go session within an :class:`x2go.client.X2GoClient` instance is setup in the following way: - import the Python X2Go module and call the session constructor:: import x2go x2go_client = x2go.X2GoClient() - register a new :class:`x2go.client.X2GoClient` session; this creates an :class:`x2go.session.X2GoSession` instance and calls its constructor method:: x2go_sess_uuid = x2go_client.register_session() - connect to the session's remote X2Go server (SSH/Paramiko):: x2go_client.connect_session(x2go_sess_uuid) - via the connected X2Go client session you can start or resume a remote X-windows session on an X2Go server now:: x2go_client.start_session(x2go_sess_uuid) resp.:: x2go_client.resume_session(x2go_sess_uuid, session_name=) - a list of available sessions on the respective server (for resuming) can be obtained in this way:: x2go_client.list_sessions(x2go_sess_uuid, session_name=) Profiled Sessions ================= A new profile based X2Go session (i.e. using pre-defined session profiles) within an :class:`x2go.client.X2GoClient` instance is setup in a much easier way: - import the Python X2Go module and call the session constructor:: import x2go x2go_client = x2go.X2GoClient() - register an X2GoClient session based on a pre-configured session profile:: x2go_sess_uuid = x2go_client.register_session(profile_name=) - or alternatively by the profile id in the »sessions« file (the name of the [
] in the »sessions« file:: x2go_sess_uuid = x2go_client.register_session(profile_id=) - now you proceed in a similar way as shown above:: x2go_client.connect_session(x2go_sess_uuid) x2go_client.start_session(x2go_sess_uuid) resp.:: x2go_client.resume_session(x2go_sess_uuid, session_name=) Session Suspending / Terminating ================================ You can suspend or terminate your sessions by calling the follwing commands:: x2go_client.suspend_session(x2go_sess_uuid) resp.:: x2go_client.terminate_session(x2go_sess_uuid) """ __NAME__ = 'x2goclient-pylib' __package__ = 'x2go' __name__ = 'x2go.client' #modules import copy import sys import types import os import re # Python X2Go modules from .registry import X2GoSessionRegistry from .guardian import X2GoSessionGuardian from .cache import X2GoListSessionsCache from . import x2go_exceptions from . import log from . import utils # we hide the default values from epydoc (that's why we transform them to _UNDERSCORE variables) from .defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS from .defaults import LOCAL_HOME as _LOCAL_HOME from .defaults import CURRENT_LOCAL_USER as _CURRENT_LOCAL_USER from .defaults import X2GO_CLIENT_ROOTDIR as _X2GO_CLIENT_ROOTDIR from .defaults import X2GO_SESSIONS_ROOTDIR as _X2GO_SESSIONS_ROOTDIR from .defaults import X2GO_SSH_ROOTDIR as _X2GO_SSH_ROOTDIR from .defaults import X2GO_SESSIONPROFILES_FILENAME as _X2GO_SESSIONPROFILES_FILENAME from .defaults import X2GO_SETTINGS_FILENAME as _X2GO_SETTINGS_FILENAME from .defaults import X2GO_PRINTING_FILENAME as _X2GO_PRINTING_FILENAME from .defaults import X2GO_XCONFIG_FILENAME as _X2GO_XCONFIG_FILENAME from .defaults import PUBAPP_MAX_NO_SUBMENUS as _PUBAPP_MAX_NO_SUBMENUS from .defaults import BACKENDS as _BACKENDS if _X2GOCLIENT_OS == 'Windows': from .xserver import X2GoClientXConfig, X2GoXServer from .pulseaudio import X2GoPulseAudio class X2GoClient(object): """\ The X2GoClient implements _THE_ public Python X2Go API. With it you can construct your own X2Go client application in Python. Most methods in this class require that you have registered a session with a remote X2Go server (passing of session options, initialization of the session object etc.) and connected to it (authentication). For these two steps use these methods: :func:`X2GoClient.register_session() ` and :func:`X2GoClient.connect_session() `. """ lang = 'en' def __init__(self, control_backend=_BACKENDS['X2GoControlSession']['default'], terminal_backend=_BACKENDS['X2GoTerminalSession']['default'], info_backend=_BACKENDS['X2GoServerSessionInfo']['default'], list_backend=_BACKENDS['X2GoServerSessionList']['default'], proxy_backend=_BACKENDS['X2GoProxy']['default'], profiles_backend=_BACKENDS['X2GoSessionProfiles']['default'], settings_backend=_BACKENDS['X2GoClientSettings']['default'], printing_backend=_BACKENDS['X2GoClientPrinting']['default'], broker_url=None, broker_password=None, broker_noauth=False, client_rootdir=None, sessions_rootdir=None, ssh_rootdir=None, start_xserver=False, start_pulseaudio=False, use_cache=False, use_listsessions_cache=False, auto_update_listsessions_cache=False, auto_update_listdesktops_cache=False, auto_update_listmounts_cache=False, auto_update_sessionregistry=False, auto_register_sessions=False, no_auto_reg_pubapp_sessions=False, refresh_interval=5, pulseaudio_installdir=None, logger=None, loglevel=log.loglevel_DEFAULT): """\ :param control_backend: X2Go control session backend to use :type control_backend: ``str`` :param terminal_backend: X2Go terminal session backend to use :type terminal_backend: ``str`` :param info_backend: X2Go session info backend to use :type info_backend: ``str`` :param list_backend: X2Go session list backend to use :type list_backend: ``str`` :param proxy_backend: X2Go proxy backend to use :type proxy_backend: ``str`` :param profiles_backend: X2Go session profiles backend to use :type profiles_backend: ``str`` :param settings_backend: X2Go client settings backend to use :type settings_backend: ``str`` :param printing_backend: X2Go client printing backend to use :type printing_backend: ``str`` :param broker_url: URL pointing to the X2Go Session Broker :type broker_url: ``str`` :param broker_password: use this password for authentication against the X2Go Session Broker :type broker_password: ``str`` :param broker_noauth: accessing the X2Go Session Broker works without credentials :type broker_noauth: ``bool`` :param client_rootdir: client base dir (default: ~/.x2goclient) :type client_rootdir: ``str`` :param sessions_rootdir: sessions base dir (default: ~/.x2go) :type sessions_rootdir: ``str`` :param ssh_rootdir: ssh base dir (default: ~/.ssh) :type ssh_rootdir: ``str`` :param start_xserver: start XServer when registering an :class:`x2go.client.X2GoClient` instance :type start_xserver: ``bool`` :param start_pulseaudio: start Pulseaudio daemon when registering an :class:`x2go.client.X2GoClient` instance :type start_pulseaudio: ``bool`` :param use_cache: alias for ``use_listsessions_cache`` :type use_cache: ``bool`` :param use_listsessions_cache: activate the X2Go session list cache in (:class:`x2go.cache.X2GoListSessionsCache`) :type use_listsessions_cache: ``bool`` :param auto_update_listsessions_cache: activate automatic updates of the X2Go session list cache (:class:`x2go.cache.X2GoListSessionsCache`) :type auto_update_listsessions_cache: ``bool`` :param auto_update_listdesktops_cache: activate automatic updates of desktop lists in (:class:`x2go.cache.X2GoListSessionsCache`) :type auto_update_listdesktops_cache: ``bool`` :param auto_update_listmounts_cache: activate automatic updates of mount lists in (:class:`x2go.cache.X2GoListSessionsCache`) :type auto_update_listmounts_cache: ``bool`` :param auto_update_sessionregistry: activate automatic updates of the X2Go session registry :type auto_update_sessionregistry: ``bool`` :param auto_register_sessions: activate automatic X2Go session registration :type auto_register_sessions: ``bool`` :param no_auto_reg_pubapp_sessions: skip automatic X2Go session registration for suspended/running published applications sessions :type no_auto_reg_pubapp_sessions: ``bool`` :param refresh_interval: refresh session list cache and session status every ``refresh_interval`` seconds :type refresh_interval: ``int`` :param pulseaudio_installdir: install path of Pulseaudio binary :type pulseaudio_installdir: ``str`` :param logger: you can pass an :class:`x2go.log.X2GoLogger` object to the :class:`x2go.client.X2GoClient` constructor :type logger: :class:`x2go.log.X2GoLogger` instance :param loglevel: if no X2GoLogger object has been supplied a new one will be constructed with the given loglevel :type loglevel: ``int`` """ self.listsessions_cache = None if logger is None: self.logger = log.X2GoLogger(loglevel=loglevel) else: self.logger = copy.deepcopy(logger) self._logger_tag = __NAME__ if self.logger.tag is None: self.logger.tag = self._logger_tag self.control_backend = utils._get_backend_class(control_backend, "X2GoControlSession") self.terminal_backend = utils._get_backend_class(terminal_backend, "X2GoTerminalSession") self.info_backend = utils._get_backend_class(info_backend, "X2GoServerSessionInfo") self.list_backend = utils._get_backend_class(list_backend, "X2GoServerSessionList") if proxy_backend == 'auto-detect': # we need to defer this at this point # until we know the exact session command # to be launched... (i.e. what desktop environment # the user wants to launch via X2Go self.proxy_backend = proxy_backend else: self.proxy_backend = utils._get_backend_class(proxy_backend, "X2GoProxy") if broker_url is not None: if broker_url.lower().startswith('ssh'): profiles_backend = 'sshbroker' elif broker_url.lower().startswith('http'): profiles_backend = 'httpbroker' self.profiles_backend = utils._get_backend_class(profiles_backend, "X2GoSessionProfiles") self.settings_backend = utils._get_backend_class(settings_backend, "X2GoClientSettings") self.printing_backend = utils._get_backend_class(printing_backend, "X2GoClientPrinting") self.client_rootdir = client_rootdir or os.path.normpath(os.path.join(_LOCAL_HOME, _X2GO_CLIENT_ROOTDIR)) self.sessions_rootdir = sessions_rootdir or os.path.normpath(os.path.join(_LOCAL_HOME, _X2GO_SESSIONS_ROOTDIR)) self.ssh_rootdir = ssh_rootdir or os.path.normpath(os.path.join(_LOCAL_HOME, _X2GO_SSH_ROOTDIR)) self.client_rootdir = os.path.normpath(self.client_rootdir) self.sessions_rootdir = os.path.normpath(self.sessions_rootdir) self.ssh_rootdir = os.path.normpath(self.ssh_rootdir) if pulseaudio_installdir == None: pulseaudio_installdir = os.path.join(os.getcwd(), 'pulseaudio') self.pulseaudio_installdir = os.path.normpath(pulseaudio_installdir) if self.client_rootdir is not None: self._has_custom_client_rootdir = True _sessions_config_file = os.path.join(self.client_rootdir, _X2GO_SESSIONPROFILES_FILENAME) _settings_config_file = os.path.join(self.client_rootdir, _X2GO_SETTINGS_FILENAME) _printing_config_file = os.path.join(self.client_rootdir, _X2GO_PRINTING_FILENAME) self.session_profiles = self.profiles_backend(config_files=[_sessions_config_file], logger=self.logger, broker_url=broker_url, broker_password=broker_password, broker_noauth=broker_noauth) self.client_settings = self.settings_backend(config_files=[_settings_config_file], logger=self.logger) self.client_printing = self.printing_backend(config_files=[_printing_config_file], client_instance=self, logger=self.logger) else: self.session_profiles = self.profiles_backend(logger=self.logger) self.client_settings = self.settings_backend(logger=self.logger) self.client_printing = self.printing_backend(client_instance=self, logger=self.logger) if _X2GOCLIENT_OS == 'Windows' and start_xserver: if self.client_rootdir: _xconfig_config_file = os.path.join(self.client_rootdir, _X2GO_XCONFIG_FILENAME) self.client_xconfig = X2GoClientXConfig(config_files=[_xconfig_config_file], logger=self.logger) else: self.client_xconfig = X2GoClientXConfig(logger=self.logger) if not self.client_xconfig.installed_xservers: self.HOOK_no_installed_xservers_found() else: _last_display = None if type(start_xserver) is bool: p_xs_name = self.client_xconfig.preferred_xserver_names[0] _last_display = self.client_xconfig.get_xserver_config(p_xs_name)['last_display'] _new_display = self.client_xconfig.detect_unused_xdisplay_port(p_xs_name) p_xs = (p_xs_name, self.client_xconfig.get_xserver_config(p_xs_name)) elif type(start_xserver) is bytes: _last_display = self.client_xconfig.get_xserver_config(start_xserver)['last_display'] _new_display = self.client_xconfig.detect_unused_xdisplay_port(start_xserver) p_xs = (start_xserver, self.client_xconfig.get_xserver_config(start_xserver)) if not self.client_xconfig.running_xservers: if p_xs is not None: self.xserver = X2GoXServer(p_xs[0], p_xs[1], logger=self.logger) else: if p_xs is not None and _last_display is not None: if _last_display == _new_display: # # FIXME: this trick is nasty, client implementation should rather cleanly shutdown launch X-server processes # # re-use a left behind X-server instance of a previous/crashed run of Python X2Go Client self.logger('found a running (and maybe stray) X-server, trying to re-use it on X DISPLAY port: %s' % _last_display, loglevel=log.loglevel_WARN) os.environ.update({'DISPLAY': str(_last_display)}) else: # presume the running XServer listens on :0 self.logger('using fallback display for X-server: localhost:0', loglevel=log.loglevel_WARN) os.environ.update({'DISPLAY': 'localhost:0'}) if _X2GOCLIENT_OS == 'Windows' and start_pulseaudio: self.pulseaudio = X2GoPulseAudio(path=self.pulseaudio_installdir, client_instance=self, logger=self.logger) self.auto_register_sessions = auto_register_sessions self.no_auto_reg_pubapp_sessions = no_auto_reg_pubapp_sessions self.session_registry = X2GoSessionRegistry(self, logger=self.logger) self.session_guardian = X2GoSessionGuardian(self, auto_update_listsessions_cache=auto_update_listsessions_cache & (use_listsessions_cache|use_cache), auto_update_listdesktops_cache=auto_update_listdesktops_cache & use_listsessions_cache, auto_update_listmounts_cache=auto_update_listmounts_cache & use_listsessions_cache, auto_update_sessionregistry=auto_update_sessionregistry, auto_register_sessions=auto_register_sessions, no_auto_reg_pubapp_sessions=no_auto_reg_pubapp_sessions, refresh_interval=refresh_interval, logger=self.logger ) self.auto_update_sessionregistry = auto_update_sessionregistry if use_listsessions_cache: self.listsessions_cache = X2GoListSessionsCache(self, logger=self.logger) self.use_listsessions_cache = use_listsessions_cache | use_cache self.auto_update_listsessions_cache = auto_update_listsessions_cache self.auto_update_listdesktops_cache = auto_update_listdesktops_cache self.auto_update_listmounts_cache = auto_update_listmounts_cache def HOOK_profile_auto_connect(self, profile_name='UNKNOWN'): """\ HOOK method: called if a session demands to auto connect the session profile. :param profile_name: profile name of session that called this hook method (Default value = 'UNKNOWN') :type profile_name: ``str`` """ self.logger('HOOK_profile_auto_connect: profile ,,%s'' wants to be auto-connected to the X2Go server.' % profile_name, loglevel=log.loglevel_WARN) def HOOK_broker_connection_exception(self, profile_name='UNKNOWN'): """\ HOOK method: called if a session demands to auto connect the session profile. :param profile_name: profile name of a session that triggered this hook method (Default value = 'UNKNOWN') :type profile_name: ``str`` """ self.logger('HOOK_broker_connection_exception: a broker connection problem occurred triggered by an action on profile ,,%s''.' % profile_name, loglevel=log.loglevel_WARN) def HOOK_broker_ignore_connection_problems(self, profile_name='UNKNOWN', is_profile_connected=False): """\ HOOK method: called after a broker connection failed for a certain profile. This hook can be used to allow the user to decide how to proceed after connection problems with the broker. :param profile_name: profile name of a session that triggered this hook method (Default value = 'UNKNOWN') :type profile_name: ``str`` :param is_profile_connected: ``True`` if the given session profile is already conneced to the server (Default value = False) :type is_profile_connected: ``bool`` :returns: If this hook returns ``True``, the session startup/resumption will be continued, even if the broker connection is down. (Default: broker connection problems cause session start-up to fail). :rtype: ``bool`` """ self.logger('HOOK_broker_ignore_connection_problems: use this hook to let the user to decide how to proceed on connection failures (profile name: %s, connected: %s)' % (profile_name, is_profile_connected), loglevel=log.loglevel_WARN) return False def HOOK_session_startup_failed(self, profile_name='UNKNOWN'): """\ HOOK method: called if the startup of a session failed. :param profile_name: profile name of session that called this hook method (Default value = 'UNKNOWN') :type profile_name: ``str`` """ self.logger('HOOK_session_startup_failed: session startup for session profile ,,%s'' failed.' % profile_name, loglevel=log.loglevel_WARN) def HOOK_desktop_sharing_denied(self, profile_name='UNKNOWN'): """\ HOOK method: called if the startup of a shadow session was denied by the other user. :param profile_name: profile name of session that called this hook method (Default value = 'UNKNOWN') :type profile_name: ``str`` """ self.logger('HOOK_desktop_sharing_failed: desktop sharing for profile ,,%s'' was denied by the other user.' % profile_name, loglevel=log.loglevel_WARN) def HOOK_list_desktops_timeout(self, profile_name='UNKNOWN'): """\ HOOK method: called if the x2golistdesktops command generates a timeout due to long execution time. :param profile_name: profile name of session that called this hook method (Default value = 'UNKNOWN') :type profile_name: ``str`` """ self.logger('HOOK_list_desktops_timeout: the server-side x2golistdesktops command for session profile %s took too long to return results. This can happen from time to time, please try again.' % profile_name, loglevel=log.loglevel_WARN) def HOOK_no_such_desktop(self, profile_name='UNKNOWN', desktop='UNKNOWN'): """\ HOOK method: called if it is tried to connect to a (seen before) sharable desktop that's not available (anymore). :param profile_name: profile name of session that called this hook method (Default value = 'UNKNOWN') :type profile_name: ``str`` :param desktop: desktop identifier (the X session's $DISPLAY) (Default value = 'UNKNOWN') :type desktop: ``str`` """ self.logger('HOOK_no_such_desktop: the desktop %s (via session profile %s) is not available for sharing (anymore).' % (desktop, profile_name), loglevel=log.loglevel_WARN) def HOOK_no_known_xserver_found(self): self.logger('DEPRECATION WARNING: The hook method HOOK_no_known_xserver_found is obsolete. Use HOOK_no_installed_xservers_found instead', loglevel=log.loglevel_WARN) self.HOOk_no_installed_xservers_found() def HOOK_no_installed_xservers_found(self): """\ HOOK method: called if the Python X2Go module could not find any usable XServer application to start. You will not be able to start X2Go sessions without an XServer. """ self.logger('the Python X2Go module could not find any usable XServer application, you will not be able to start X2Go sessions without an XServer', loglevel=log.loglevel_WARN) def HOOK_open_print_dialog(self, profile_name='UNKNOWN', session_name='UNKNOWN'): """\ HOOK method: called if an incoming print job has been detected by :class:`x2go.printing.X2GoPrintQueue` and a print dialog box is requested. :param profile_name: profile name of session that called this hook method (Default value = 'UNKNOWN') :type profile_name: ``str`` :param session_name: X2Go session name (Default value = 'UNKNOWN') :type session_name: ``str`` """ self.logger('HOOK_open_print_dialog: incoming print job detected by X2GoClient hook method', loglevel=log.loglevel_WARN) def HOOK_no_such_command(self, cmd, profile_name='UNKNOWN', session_name='UNKNOWN'): """\ HOOK: the command is not available on the connected X2Go server. :param cmd: the command that failed :type cmd: ``str`` :param profile_name: profile name of session that called this hook method (Default value = 'UNKNOWN') :type profile_name: ``str`` :param session_name: X2Go session name (Default value = 'UNKNOWN') :type session_name: ``str`` """ self.logger('HOOK_no_such_command: the command %s is not available for X2Go server (profile: %s, session: %s)' % (cmd, profile_name, session_name), loglevel=log.loglevel_WARN) def HOOK_open_mimebox_saveas_dialog(self, filename, profile_name='UNKNOWN', session_name='UNKNOWN'): """\ HOOK method: called on detection of an incoming MIME box job ,,''. :param filename: file name of the incoming MIME box job :type filename: ``str`` :param profile_name: profile name of session that called this hook method (Default value = 'UNKNOWN') :type profile_name: ``str`` :param session_name: X2Go session name (Default value = 'UNKNOWN') :type session_name: ``str`` """ self.logger('HOOK_open_mimebox_saveas_dialog: incoming MIME box job ,, %s'' detected by X2GoClient hook method' % filename, loglevel=log.loglevel_WARN) def HOOK_printaction_error(self, filename, profile_name='UNKNOWN', session_name='UNKNOWN', err_msg='GENERIC_ERROR', printer=None): """\ HOOK method: called if an incoming print job caused an error. :param filename: file name of the print job that failed :type filename: ``str`` :param profile_name: profile name of session that called this hook method (Default value = 'UNKNOWN') :type profile_name: ``str`` :param session_name: X2Go session name (Default value = 'UNKNOWN') :type session_name: ``str`` :param err_msg: if available, an appropriate error message (Default value = 'GENERIC_ERROR') :type err_msg: ``str`` :param printer: if available, the printer name the print job failed on (Default value = None) :type printer: ``str`` """ if printer: self.logger('HOOK_printaction_error: incoming print job ,, %s'' on printer %s caused error: %s' % (filename, printer, err_msg), loglevel=log.loglevel_ERROR) else: self.logger('HOOK_printaction_error: incoming print job ,, %s'' caused error: %s' % (filename, err_msg), loglevel=log.loglevel_ERROR) def HOOK_check_host_dialog(self, profile_name='UNKNOWN', host='UNKNOWN', port=22, fingerprint='no fingerprint', fingerprint_type='UNKNOWN'): """\ HOOK method: called if a host check is requested. This hook has to either return ``True`` (default) or ``False``. :param profile_name: profile name of session that called this hook method (Default value = 'UNKNOWN') :type profile_name: ``str`` :param host: SSH server name to validate (Default value = 'UNKNOWN') :type host: ``str`` :param port: SSH server port to validate (Default value = 22) :type port: ``int`` :param fingerprint: the server's fingerprint (Default value = 'no fingerprint') :type fingerprint: ``str`` :param fingerprint_type: finger print type (like RSA, DSA, ...) (Default value = 'UNKNOWN') :type fingerprint_type: ``str`` :returns: if host validity is verified, this hook method should return ``True`` :rtype: ``bool`` """ self.logger('HOOK_check_host_dialog: host check requested for session profile %s: Automatically adding host [%s]:%s with fingerprint: ,,%s\'\' as a known host.' % (profile_name, host, port, fingerprint), loglevel=log.loglevel_WARN) # this HOOK has to return either True (accept host connection) or False (deny host conection) return True def HOOK_on_control_session_death(self, profile_name): """\ HOOK method: called if a control session (server connection) has unexpectedly encountered a failure. :param profile_name: profile name of session that called this hook method :type profile_name: ``str`` """ self.logger('HOOK_on_control_session_death: the control session of profile %s has died unexpectedly' % profile_name, loglevel=log.loglevel_WARN) def HOOK_on_failing_SFTP_client(self, profile_name, session_name): """\ HOOK method: called SFTP client support is unavailable for the session. :param profile_name: profile name of the session that experiences failing SFTP client support :type profile_name: ``str`` :param session_name: name of session experiencing failing SFTP client support :type session_name: ``str`` """ self.logger('HOOK_on_failing_SFTP_client: new session for profile %s will lack SFTP client support. Check your server setup. Avoid echoing ~/.bashrc files on server.' % profile_name, loglevel=log.loglevel_ERROR) def HOOK_pulseaudio_not_supported_in_RDPsession(self): """HOOK method: called if trying to run the Pulseaudio daemon within an RDP session, which is not supported by Pulseaudio.""" self.logger('HOOK_pulseaudio_not_supported_in_RDPsession: The pulseaudio daemon cannot be used within RDP sessions', loglevel=log.loglevel_WARN) def HOOK_pulseaudio_server_startup_failed(self): """HOOK method: called if the Pulseaudio daemon startup failed.""" self.logger('HOOK_pulseaudio_server_startup_failed: The pulseaudio daemon could not be started', loglevel=log.loglevel_ERROR) def HOOK_pulseaudio_server_died(self): """HOOK method: called if the Pulseaudio daemon has died away unexpectedly.""" self.logger('HOOK_pulseaudio_server_died: The pulseaudio daemon has just died away', loglevel=log.loglevel_ERROR) def HOOK_on_sound_tunnel_failed(self, profile_name='UNKNOWN', session_name='UNKNOWN'): """\ HOOK method: called if a sound tunnel setup failed. :param profile_name: profile name of session that called this hook method (Default value = 'UNKNOWN') :type profile_name: ``str`` :param session_name: X2Go session name (Default value = 'UNKNOWN') :type session_name: ``str`` """ self.logger('HOOK_on_sound_tunnel_failed: setting up X2Go sound for %s (%s) support failed' % (profile_name, session_name)) def HOOK_rforward_request_denied(self, profile_name='UNKNOWN', session_name='UNKNOWN', server_port=0): """\ HOOK method: called if a reverse port forwarding request has been denied. :param profile_name: profile name of session that called this hook method (Default value = 'UNKNOWN') :type profile_name: ``str`` :param session_name: X2Go session name (Default value = 'UNKNOWN') :type session_name: ``str`` :param server_port: remote server port (starting point of reverse forwarding tunnel) (Default value = 0) :type server_port: ``str`` """ self.logger('TCP port (reverse) forwarding request for session %s to server port %s has been denied by the X2Go server. This is a common issue with SSH, it might help to restart the X2Go server\'s SSH daemon.' % (session_name, server_port), loglevel=log.loglevel_WARN) def HOOK_forwarding_tunnel_setup_failed(self, profile_name='UNKNOWN', session_name='UNKNOWN', chain_host='UNKNOWN', chain_port=0, subsystem=None): """\ HOOK method: called if a port forwarding tunnel setup failed. :param profile_name: profile name of session that called this hook method (Default value = 'UNKNOWN') :type profile_name: ``str`` :param session_name: X2Go session name (Default value = 'UNKNOWN') :type session_name: ``str`` :param chain_host: hostname of chain host (forwarding tunnel end point) (Default value = 'UNKNOWN') :type chain_host: ``str`` :param chain_port: port of chain host (forwarding tunnel end point) (Default value = 0) :type chain_port: ``str`` :param subsystem: information on the subsystem that provoked this hook call (Default value = None) :type subsystem: ``str`` """ if type(subsystem) in (bytes, str): _subsystem = '(%s) ' % subsystem else: _subsystem = '' self.logger('Forwarding tunnel request to [%s]:%s for session %s (%s) was denied by remote X2Go/SSH server. Subsystem %s startup failed.' % (chain_host, chain_port, session_name, profile_name, _subsystem), loglevel=log.loglevel_ERROR) def HOOK_on_session_has_started_by_me(self, session_uuid='UNKNOWN', profile_name='UNKNOWN', session_name='UNKNOWN'): """\ HOOK method: called if a session has been started by this instance of :class:`x2go.client.X2GoClient`. :param session_uuid: unique session identifier of the calling session (Default value = 'UNKNOWN') :type session_uuid: ``str`` :param profile_name: profile name of session that called this hook method (Default value = 'UNKNOWN') :type profile_name: ``str`` :param session_name: X2Go session name (Default value = 'UNKNOWN') :type session_name: ``str`` """ self.logger('HOOK_on_session_has_started_by_me (session_uuid: %s, profile_name: %s): a new session %s has been started by this application' % (session_uuid, profile_name, session_name), loglevel=log.loglevel_NOTICE) def HOOK_on_session_has_started_by_other(self, session_uuid='UNKNOWN', profile_name='UNKNOWN', session_name='UNKNOWN'): """\ HOOK method: called if a session has been started by another ``x2goclient``. :param session_uuid: unique session identifier of the calling session (Default value = 'UNKNOWN') :type session_uuid: ``str`` :param profile_name: profile name of session that called this hook method (Default value = 'UNKNOWN') :type profile_name: ``str`` :param session_name: X2Go session name (Default value = 'UNKNOWN') :type session_name: ``str`` """ self.logger('HOOK_on_session_has_started (session_uuid: %s, profile_name: %s): a new session %s has started been started by other application' % (session_uuid, profile_name, session_name), loglevel=log.loglevel_NOTICE) def HOOK_on_session_has_resumed_by_me(self, session_uuid='UNKNOWN', profile_name='UNKNOWN', session_name='UNKNOWN'): """\ HOOK method: called if a session has been resumed by this instance of :class:`x2go.client.X2GoClient`. :param session_uuid: unique session identifier of the calling session (Default value = 'UNKNOWN') :type session_uuid: ``str`` :param profile_name: profile name of session that called this hook method (Default value = 'UNKNOWN') :type profile_name: ``str`` :param session_name: X2Go session name (Default value = 'UNKNOWN') :type session_name: ``str`` """ self.logger('HOOK_on_session_has_resumed_by_me (session_uuid: %s, profile_name: %s): suspended session %s has been resumed by this application' % (session_uuid, profile_name, session_name), loglevel=log.loglevel_NOTICE) def HOOK_on_session_has_resumed_by_other(self, session_uuid='UNKNOWN', profile_name='UNKNOWN', session_name='UNKNOWN'): """\ HOOK method: called if a session has been resumed by another ``x2goclient``. :param session_uuid: unique session identifier of the calling session (Default value = 'UNKNOWN') :type session_uuid: ``str`` :param profile_name: profile name of session that called this hook method (Default value = 'UNKNOWN') :type profile_name: ``str`` :param session_name: X2Go session name (Default value = 'UNKNOWN') :type session_name: ``str`` """ self.logger('HOOK_on_session_has_resumed_by_other (session_uuid: %s, profile_name: %s): suspended session %s has been resumed by other application' % (session_uuid, profile_name, session_name), loglevel=log.loglevel_NOTICE) def HOOK_on_found_session_running_after_connect(self, session_uuid='UNKNOWN', profile_name='UNKNOWN', session_name='UNKNOWN'): """\ HOOK method: called after server connect if an already running session has been found. :param session_uuid: unique session identifier of the calling session (Default value = 'UNKNOWN') :type session_uuid: ``str`` :param profile_name: profile name of session that called this hook method (Default value = 'UNKNOWN') :type profile_name: ``str`` :param session_name: X2Go session name (Default value = 'UNKNOWN') :type session_name: ``str`` """ self.logger('HOOK_found_session_running_after_connect (session_uuid: %s, profile_name: %s): running session %s has been found after connecting to session profile %s' % (session_uuid, profile_name, session_name, profile_name), loglevel=log.loglevel_NOTICE) def HOOK_on_session_has_been_suspended(self, session_uuid='UNKNOWN', profile_name='UNKNOWN', session_name='UNKNOWN'): """\ HOOK method: called if a session has been suspended by this instance of :class:`x2go.client.X2GoClient`. :param session_uuid: unique session identifier of the calling session (Default value = 'UNKNOWN') :type session_uuid: ``str`` :param profile_name: profile name of session that called this hook method (Default value = 'UNKNOWN') :type profile_name: ``str`` :param session_name: X2Go session name (Default value = 'UNKNOWN') :type session_name: ``str`` """ self.logger('HOOK_on_session_has_been_suspended (session_uuid: %s, profile_name: %s): session %s has been suspended' % (session_uuid, profile_name, session_name), loglevel=log.loglevel_NOTICE) def HOOK_on_session_has_terminated(self, session_uuid='UNKNOWN', profile_name='UNKNOWN', session_name='UNKNOWN'): """\ HOOK method: called if a session has been suspended by another ``x2goclient``. :param session_uuid: unique session identifier of the calling session (Default value = 'UNKNOWN') :type session_uuid: ``str`` :param profile_name: profile name of session that called this hook method (Default value = 'UNKNOWN') :type profile_name: ``str`` :param session_name: X2Go session name (Default value = 'UNKNOWN') :type session_name: ``str`` """ self.logger('HOOK_on_session_has_terminated (session_uuid: %s, profile_name: %s): session %s has terminated' % (session_uuid, profile_name, session_name), loglevel=log.loglevel_NOTICE) def HOOK_printing_not_available(self, profile_name='UNKNOWN', session_name='UNKNOWN'): """\ HOOK method: called if X2Go client-side printing is not available. :param profile_name: profile name of session that called this hook method (Default value = 'UNKNOWN') :type profile_name: ``str`` :param session_name: X2Go session name (Default value = 'UNKNOWN') :type session_name: ``str`` """ self.logger('HOOK_foldersharing_not_available: X2Go\'s client-side printing feature is not available with this session (%s) of profile %s.' % (session_name, profile_name), loglevel=log.loglevel_WARN) def HOOK_mimebox_not_available(self, profile_name='UNKNOWN', session_name='UNKNOWN'): """\ HOOK method: called if the X2Go MIME box is not available. :param profile_name: profile name of session that called this hook method (Default value = 'UNKNOWN') :type profile_name: ``str`` :param session_name: X2Go session name (Default value = 'UNKNOWN') :type session_name: ``str`` """ self.logger('HOOK_mimebox_not_available: X2Go\'s MIME box feature is not available with this session (%s) of profile %s.' % (session_name, profile_name), loglevel=log.loglevel_WARN) def HOOK_foldersharing_not_available(self, profile_name='UNKNOWN', session_name='UNKNOWN'): """\ HOOK method: called if X2Go client-side folder-sharing is not available. :param profile_name: profile name of session that called this hook method (Default value = 'UNKNOWN') :type profile_name: ``str`` :param session_name: X2Go session name (Default value = 'UNKNOWN') :type session_name: ``str`` """ self.logger('HOOK_foldersharing_not_available: X2Go\'s client-side folder sharing feature is not available with this session (%s) of profile %s.' % (session_name, profile_name), loglevel=log.loglevel_WARN) def HOOK_sshfs_not_available(self, profile_name='UNKNOWN', session_name='UNKNOWN'): """\ HOOK method: called if the X2Go server denies SSHFS access. :param profile_name: profile name of session that called this hook method (Default value = 'UNKNOWN') :type profile_name: ``str`` :param session_name: X2Go session name (Default value = 'UNKNOWN') :type session_name: ``str`` """ self.logger('HOOK_sshfs_not_available: the remote X2Go server (%s) denies SSHFS access for session %s. This will result in client-side folder sharing, printing and the MIME box feature being unavailable' % (session_name, profile_name), loglevel=log.loglevel_WARN) def get_client_rootdir(self): """\ Retrieve the settings root directory of this :class:`x2go.client.X2GoClient` instance. :returns: X2Go client root directory :rtype: ``str`` """ return os.path.normpath(self.client_rootdir) __get_client_rootdir = get_client_rootdir @property def has_custom_client_rootdir(self): """\ Does this :class:`x2go.client.X2GoClient` instance have a customized root dir path? Equals ``True`` in case it has. """ return self._has_custom_client_rootdir __has_custom_client_rootdir = has_custom_client_rootdir def get_sessions_rootdir(self): """\ Retrieve the sessions root directory of this :class:`x2go.client.X2GoClient` instance. :returns: X2Go sessions root directory :rtype: ``str`` """ return os.path.normpath(self.sessions_rootdir) __get_sessions_rootdir = get_sessions_rootdir def get_ssh_rootdir(self): """\ Retrieve the SSH client root dir used with this :class:`x2go.client.X2GoClient` instance. :returns: SSH client root directory :rtype: ``str`` """ return os.path.normpath(self.ssh_rootdir) __get_ssh_rootdir = get_ssh_rootdir def get_client_username(self): """\ Query the local user's username (i.e. the user running the X2Go client). :returns: the local username this :class:`x2go.client.X2GoClient` instance runs as :rtype: ``str`` """ return _CURRENT_LOCAL_USER __get_client_username = get_client_username def register_all_session_profiles(self, return_objects=False): """\ Register all session profiles found in the ``sessions`` configuration node as potential X2Go sessions. :param return_objects: if set to ``True`` this methods returns a list of :class:`x2go.session.X2GoSession` instances, otherwise a list of session UUIDs representing the corresponding registered sessions is returned (Default value = False) :type return_objects: ``bool`` :returns: a Python dictionary containing one registered session for each available session profile configuration, whereas the profile names are used as dictionary keys and :class:`x2go.session.X2GoSession` instances as their values :rtype: ``list`` """ sessions = {} for profile_name in self.session_profiles.profile_names: _obj = self._X2GoClient__register_session(profile_name=profile_name, return_object=True) sessions[_obj.get_profile_name()] = _obj return sessions __register_all_session_profiles = register_all_session_profiles def register_session(self, server=None, profile_id=None, profile_name=None, session_name=None, allow_printing=False, allow_share_local_folders=False, share_local_folders=[], allow_mimebox=False, mimebox_extensions=[], mimebox_action='OPEN', add_to_known_hosts=False, known_hosts=None, forward_sshagent=False, proxy_options={}, return_object=False, **kwargs): """\ Register a new :class:`x2go.session.X2GoSession`. Within one :class:`x2go.client.X2GoClient` instance you can manage several :class:`x2go.session.X2GoSession` instances on serveral remote X2Go servers under different user names. These sessions can be instantiated by passing direct :class:`x2go.session.X2GoSession` parameters to this method or by specifying the name of an existing session profile (as found in the :class:`x2go.client.X2GoClient`'s ``sessions`` configuration node. A session profile is a pre-defined set of session options stored in a sessions profile node (e.g. a configuration file). With the FILE backend such session profiles are stored as a file (by default: ``~/.x2goclient/sessions`` or globally (for all users on the client) in ``/etc/x2goclient/sessions``). Python X2Go also supports starting multiple X2Go sessions for the same session profile simultaneously. This method (:func:`X2GoClient.register_session() `) accepts a similar set of parameters as the :class:`x2go.session.X2GoSession` constructor itself. For a complete set of session options refer there. Alternatively, you can also pass a profile name or a profile id to this method. If you do this, Python X2Go tries to find the specified session in the ``sessions`` configuration node and then derives the necessary session parameters from the session profile configuration. Additional :class:`x2go.session.X2GoSession` parameters can also be passed to this method---they will override the option values retrieved from the session profile. :param server: hostname of the remote X2Go server (Default value = None) :type server: ``str`` :param profile_id: id (config section name) of a session profile to load from your session config (Default value = None) :type profile_id: ``str`` :param profile_name: name of a session profile to load from your session config (Default value = None) :type profile_name: ``str`` :param session_name: session name to register (by its name) (Default value = None) :type session_name: ``str`` :param allow_printing: enable X2Go printing support for the to-be-registered X2Go session (Default value = False) :type allow_printing: ``bool`` :param allow_share_local_folders: set local folder sharing to enabled/disabled (Default value = False) :type allow_share_local_folders: ``bool`` :param share_local_folders: a list of local folders (as strings) to be shared directly after session start up (Default value = []) :type share_local_folders: ``list`` :param allow_mimebox: enable X2Go MIME box support for the to-be-registered X2Go session (Default value = False) :type allow_mimebox: ``bool`` :param mimebox_extensions: MIME box support is only allowed for the given file extensions (Default value = []) :type mimebox_extensions: ``list`` :param mimebox_action: MIME box action to use on incoming MIME job files (Default value = 'OPEN') :type mimebox_action: ``str`` :param add_to_known_hosts: add unknown host keys to the ``known_hosts`` file and accept the connection automatically (Default value = False) :type add_to_known_hosts: ``bool`` :param known_hosts: full path to ``known_hosts`` file (Default value = None) :type known_hosts: ``str`` :param forward_sshagent: forward SSH agent authentication requests to the X2Go client-side (Default value = False) :type forward_sshagent: ``bool`` :param proxy_options: a set of very ``X2GoProxy*`` backend specific options; any option that is not known to the ``X2GoProxy*`` backend will simply be ignored (Default value = {}) :type proxy_options: ``dict`` :param return_object: normally this method returns a unique session UUID. If ``return_object`` is set to ``True`` an X2GoSession object will be returned instead (Default value = False) :type return_object: ``bool`` :param kwargs: any option that is also valid for the :class:`x2go.session.X2GoSession` constructor :type kwargs: ``dict`` :returns: a unique identifier (UUID) for the newly registered X2Go session (or an X2GoSession object if ``return_object`` is set to True :rtype: ``str`` """ _p = None # detect profile name and profile id properly if profile_id and self.session_profiles.has_profile_id(profile_id): _p = profile_id elif profile_name and self.session_profiles.has_profile_name(profile_name): _p = profile_name elif profile_id: try: _p = self.session_profiles.check_profile_id_or_name(profile_id) except x2go_exceptions.X2GoProfileException: pass elif profile_name: try: _p = self.session_profiles.check_profile_id_or_name(profile_name) except x2go_exceptions.X2GoProfileException: pass if _p: _profile_id = self.session_profiles.check_profile_id_or_name(_p) _profile_name = self.session_profiles.to_profile_name(_profile_id) else: _profile_id = None # test if session_name has already been registered. If yes, return it immediately. if type(session_name) is bytes: _retval = self.get_session_of_session_name(session_name, return_object=return_object, match_profile_name=profile_name) if _retval is not None: return _retval if known_hosts is None: known_hosts = os.path.join(_LOCAL_HOME, self.ssh_rootdir, 'known_hosts') if _profile_id: # initialize session profile cache self.session_profiles.init_profile_cache(_profile_id) _params = self.session_profiles.to_session_params(profile_id=_profile_id) del _params['profile_name'] # override any available session parameter passed to this method for k in list(_params.keys()): if k in list(kwargs.keys()): _params[k] = kwargs[k] _pkey = None try: server = self.session_profiles.get_server_hostname(_profile_id) _params['port'] = self.session_profiles.get_server_port(_profile_id) _pkey = self.session_profiles.get_pkey_object(_profile_id) except x2go_exceptions.X2GoBrokerConnectionException as e: _profile_name = self.to_profile_name(_profile_id) self.HOOK_broker_connection_exception(_profile_name) if not self.HOOK_broker_ignore_connection_problems(_profile_name, is_profile_connected=self.is_profile_connected(_profile_name)): raise e server = self.session_profiles.get_profile_config(_profile_name, parameter='host')[0] _params['port'] = self.session_profiles.get_profile_config(_profile_name, parameter='sshport') if _pkey is not None: self.logger('received PKey object for authentication, ignoring all other auth mechanisms', log.loglevel_NOTICE, tag=self._logger_tag) _params['pkey'] = _pkey _params['sshproxy_pkey'] = _pkey _params['allow_agent'] = False _params['look_for_keys'] = False _params['key_filename'] = [] del _params['server'] _params['client_instance'] = self else: if server is None: return None _profile_id = utils._genSessionProfileId() _profile_name = profile_name or sys.argv[0] _params = kwargs _params['printing'] = allow_printing _params['allow_share_local_folders'] = allow_share_local_folders _params['share_local_folders'] = share_local_folders _params['allow_mimebox'] = allow_mimebox _params['mimebox_extensions'] = mimebox_extensions _params['mimebox_action'] = mimebox_action _params['client_instance'] = self _params['proxy_options'] = proxy_options _params['forward_sshagent'] = forward_sshagent session_uuid = self.session_registry.register(server=server, profile_id=_profile_id, profile_name=_profile_name, session_name=session_name, control_backend=self.control_backend, terminal_backend=self.terminal_backend, info_backend=self.info_backend, list_backend=self.list_backend, proxy_backend=self.proxy_backend, settings_backend=self.settings_backend, printing_backend=self.printing_backend, client_rootdir=self.client_rootdir, sessions_rootdir=self.sessions_rootdir, ssh_rootdir=self.ssh_rootdir, keep_controlsession_alive=True, add_to_known_hosts=add_to_known_hosts, known_hosts=known_hosts, **_params) self.logger('initializing X2Go session...', log.loglevel_NOTICE, tag=self._logger_tag) if return_object: return self.session_registry(session_uuid) else: return session_uuid __register_session = register_session ### ### WRAPPER METHODS FOR X2GoSessionRegistry objects ### def get_session_summary(self, session_uuid): """\ Retrieves a Python dictionary, containing a short session summary (session status, names, etc.) :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` """ return self.session_registry.session_summary(session_uuid) __get_session_summary = get_session_summary ### ### WRAPPER METHODS FOR X2GoSession objects ### def get_session_username(self, session_uuid): """\ After an :class:`x2go.session.X2GoSession` has been set up you can query the username that the remote sessions runs as. :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :returns: the remote username the X2Go session runs as :rtype: ``str`` """ return self.session_registry(session_uuid).get_username() __get_session_username = get_session_username def get_session_server_peername(self, session_uuid): """\ After a session has been set up you can query the hostname of the host the session is connected to (or about to connect to). :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :returns: the host an X2Go session is connected to (as an ``(addr,port)`` tuple) :rtype: tuple """ return self.session_registry(session_uuid).get_server_peername() __get_session_server_peername = get_session_server_peername def get_session_server_hostname(self, session_uuid): """\ Retrieve the server hostname as provided by the calling application (e.g. like it has been specified in the session profile). :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :returns: the hostname for the queried X2Go session as specified by the calling application :rtype: str """ return self.session_registry(session_uuid).get_server_hostname() __get_session_server_hostname = get_session_server_hostname def get_session(self, session_uuid): """\ Retrieve the complete :class:`x2go.session.X2GoSession` object that has been registered under the given session registry hash. :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :returns: the :class:`x2go.session.X2GoSession` instance :rtype: obj """ return self.session_registry(session_uuid) __get_session = get_session with_session = __get_session """Alias for :func:`get_session()`.""" def get_session_of_session_name(self, session_name, return_object=False, match_profile_name=None): """\ Retrieve session UUID or :class:`x2go.session.X2GoSession` for session name from the session registry. :param session_name: the X2Go session's UUID registry hash :type session_name: ``str`` :param return_object: session UUID hash or :class:`x2go.session.X2GoSession` instance wanted? (Default value = False) :type return_object: ``bool`` :param match_profile_name: only return sessions that match this profile name (Default value = None) :type match_profile_name: ``str`` :returns: the X2Go session's UUID registry hash or :class:`x2go.session.X2GoSession` instance :rtype: ``str`` or :class:`x2go.session.X2GoSession` instance """ try: return self.session_registry.get_session_of_session_name(session_name=session_name, return_object=return_object, match_profile_name=match_profile_name) except x2go_exceptions.X2GoSessionRegistryException: return None __get_session_of_session_name = get_session_of_session_name def get_session_name(self, session_uuid): """\ Retrieve the server-side X2Go session name for the session that has been registered under ``session_uuid``. :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :returns: X2Go session name :rtype: ``str`` """ return self.session_registry(session_uuid).get_session_name() __get_session_name = get_session_name def get_session_info(self, session_uuid): """\ Retrieve the server-side X2Go session information object for the session that has been registered under ``session_uuid``. :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :returns: X2Go session info :rtype: ``obj`` """ return self.session_registry(session_uuid).get_session_info() __get_session_info = get_session_info def get_published_applications(self, session_uuid=None, profile_name=None, lang=None, refresh=False, raw=False, very_raw=False, max_no_submenus=_PUBAPP_MAX_NO_SUBMENUS): """\ Retrieve the server-side X2Go published applications menu for the session registered under ``session_uuid`` or for profile name ``profile_name``. :param session_uuid: the X2Go session's UUID registry hash (Default value = None) :type session_uuid: ``str`` :param profile_name: a valid session profile name (Default value = None) :type profile_name: ``str`` :param lang: locale/language identifier (Default value = None) :type lang: ``str`` :param refresh: force reload of the menu tree from X2Go server (Default value = False) :type refresh: ``bool`` :param raw: retrieve a raw output of the server list of published applications (Default value = False) :type raw: ``bool`` :param very_raw: retrieve a very raw output of the server list of published applications (Default value = False) :type very_raw: ``bool`` :param max_no_submenus: Number of applications before applications are put into XDG category submenus (Default value = defaults.PUBAPP_MAX_NO_SUBMENUS) :type max_no_submenus: ``int`` :returns: an i18n capable menu tree packed as a Python dictionary :rtype: ``list`` """ if session_uuid is None and profile_name: _session_uuids = self._X2GoClient__client_pubapp_sessions_of_profile_name(profile_name, return_objects=False) if len(_session_uuids): session_uuid = _session_uuids[0] if session_uuid: try: if self.session_registry(session_uuid).is_published_applications_provider(): return self.session_registry(session_uuid).get_published_applications(lang=lang, refresh=refresh, raw=raw, very_raw=False, max_no_submenus=max_no_submenus) except x2go_exceptions.X2GoSessionRegistryException: pass else: self.logger('Cannot find a terminal session for profile ,,%s\'\' that can be used to query a published applications menu tree' % profile_name, loglevel=log.loglevel_INFO) return None __get_published_applications = get_published_applications profile_get_published_applications = get_published_applications __profile_get_published_applications = get_published_applications def set_session_username(self, session_uuid, username): """\ Set the session username for the :class:`x2go.session.X2GoSession` that has been registered under ``session_uuid``. This can be helpful for modifying user credentials during an authentication phase. :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :param username: new user name to be used for session authentication :type username: ``str`` :returns: returns ``True`` on success :rtype: ``bool`` """ return self.session_registry(session_uuid).set_username(username=username) __set_session_username = set_session_username def check_session_host(self, session_uuid): """\ Provide a mechanism to evaluate the validity of an X2Go server host. :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :returns: returns ``True`` if host validation has been successful. :rtype: ``bool`` """ return self.session_registry(session_uuid).check_host() __check_session_host = check_session_host def session_reuses_sshproxy_authinfo(self, session_uuid): """\ Check if session with unique identifier is configured to re-use the X2Go session's password / key for proxy authentication, as well. :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :returns: returns ``True`` if the session is configured to re-use session password / key for proxy authentication :rtype: ``bool`` """ return self.session_registry(session_uuid).reuses_sshproxy_authinfo() __session_reuses_sshproxy_authinfo = session_reuses_sshproxy_authinfo def session_uses_sshproxy(self, session_uuid): """\ Check if session with unique identifier is configured to use an intermediate SSH proxy server. :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :returns: returns ``True`` if the session is configured to use an SSH proxy, ``False`` otherwise. :rtype: ``bool`` """ return self.session_registry(session_uuid).uses_sshproxy() __session_uses_sshproxy = session_uses_sshproxy def session_can_sshproxy_auto_connect(self, session_uuid): """\ Check if the SSH proxy of session with unique identifier is configured adequately to be able to auto-connect to the SSH proxy server (e.g. by public key authentication). :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :returns: returns ``True`` if the session's SSH proxy can auto-connect, ``False`` otherwise, ``None`` if no control session has been set up yet. :rtype: ``bool`` """ return self.session_registry(session_uuid).can_sshproxy_auto_connect() __session_can_sshproxy_auto_connect = session_can_sshproxy_auto_connect def session_can_auto_connect(self, session_uuid): """\ Check if session with unique identifier is configured adequately to be able to auto-connect to the X2Go server (e.g. by public key authentication). :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :returns: returns ``True`` if the session can auto-connect, ``False`` otherwise, ``None`` if no control session has been set up yet. :rtype: ``bool`` """ return self.session_registry(session_uuid).can_auto_connect() __session_can_auto_connect = session_can_auto_connect # user hooks for detecting/notifying what happened during application runtime def session_auto_connect(self, session_uuid): """\ Auto-connect a given session. This method is called from within the session itself and can be used to override the auto-connect procedure from within your client implementation. :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :returns: returns ``True`` if the session could be auto-connected. :rtype: ``bool`` """ self.session_registry(session_uuid).do_auto_connect(redirect_to_client=False) __session_auto_connect = session_auto_connect def connect_session(self, session_uuid, username=None, password=None, passphrase=None, sshproxy_user=None, sshproxy_password=None, sshproxy_passphrase=None, add_to_known_hosts=False, force_password_auth=False, sshproxy_force_password_auth=False, ): """\ Connect to a registered X2Go session with registry hash ``session_uuid`` This method basically wraps around paramiko.SSHClient.connect() for the corresponding session. :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :param username: user name to be used for session authentication (Default value = None) :type username: ``str`` :param password: the user's password for the X2Go server that is going to be connected to (Default value = None) :type password: ``str`` :param passphrase: a passphrase to use for unlocking a private key in case the password is already needed for two-factor authentication (Default value = None) :type passphrase: ``str`` :param sshproxy_user: user name to be used for SSH proxy authentication (Default value = None) :type sshproxy_user: ``str`` :param sshproxy_password: the SSH proxy user's password (Default value = None) :type sshproxy_password: ``str`` :param sshproxy_passphrase: a passphrase to use for unlocking a private key needed for the SSH proxy host in case the sshproxy_password is already needed for two-factor authentication (Default value = None) :type sshproxy_passphrase: ``str`` :param add_to_known_hosts: non-Paramiko option, if ``True`` ``paramiko.AutoAddPolicy()`` is used as missing-host-key-policy. If set to ``False`` :func:`checkhosts.X2GoInteractiveAddPolicy() ` is used (Default value = False) :type add_to_known_hosts: ``bool`` :param force_password_auth: disable SSH pub/priv key authentication mechanisms completely (Default value = False) :type force_password_auth: ``bool`` :param sshproxy_force_password_auth: disable SSH pub/priv key authentication mechanisms completely for SSH proxy connection (Default value = False) :type sshproxy_force_password_auth: ``bool`` :returns: returns True if this method has been successful :rtype: ``bool`` """ _success = self.session_registry(session_uuid).connect(username=username, password=password, passphrase=passphrase, sshproxy_user=sshproxy_user, sshproxy_password=sshproxy_password, sshproxy_passphrase=sshproxy_passphrase, add_to_known_hosts=add_to_known_hosts, force_password_auth=force_password_auth, sshproxy_force_password_auth=sshproxy_force_password_auth, ) if self.auto_register_sessions: self.session_registry.register_available_server_sessions(profile_name=self.get_session_profile_name(session_uuid), newly_connected=True, ) return _success __connect_session = connect_session def disconnect_session(self, session_uuid): """\ Disconnect an :class:`x2go.session.X2GoSession` by closing down its Paramiko/SSH Transport thread. :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` """ self.session_registry(session_uuid).disconnect() if self.use_listsessions_cache: self.__update_cache_all_profiles() __disconnect_session = disconnect_session def set_session_print_action(self, session_uuid, print_action, **kwargs): """\ If X2Go client-side printing is enable within an X2Go session you can use this method to alter the way how incoming print spool jobs are handled/processed. Currently, there are five different print actions available, each defined as an individual print action class: - **PDFVIEW** (:class:`x2go.printactions.X2GoPrintActionPDFVIEW`): view an incoming spool job (a PDF file) locally in a PDF viewer - **PDFSAVE** (:class:`x2go.printactions.X2GoPrintActionPDFSAVE`): save an incoming spool job (a PDF file) under a nice name in a designated folder - **PRINT** (:class:`x2go.printactions.X2GoPrintActionPRINT`): really print the incoming spool job on a real printing device - **PRINTCMD** :class:`x2go.printactions.X2GoPrintActionPRINTCMD`: on each incoming spool job execute an external command that lets the client user handle the further processing of the print job (PDF) file - **DIALOG** (:class:`x2go.printactions.X2GoPrintActionDIALOG`): on each incoming spool job this print action will call :func:`X2GoClient.HOOK_open_print_dialog() ` Each of the print action classes accepts different print action arguments. For detail information on these print action arguments please refer to the constructor methods of each class individually. :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :param print_action: one of the named above print actions, either as string or class instance :type print_action: ``str`` or ``instance`` :param kwargs: additional information for the given print action (print action arguments), for possible print action arguments and their values see each individual print action class :type kwargs: ``dict`` """ self.session_registry(session_uuid).set_print_action(print_action=print_action, **kwargs) __set_session_print_action = set_session_print_action def set_session_window_title(self, session_uuid, title=''): """\ Modify session window title. If the session ID does not occur in the given title, it will be prepended, so that every X2Go session window always contains the X2Go session ID of that window. :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :param title: new title for session window (Default value = '') :type title: ``str`` """ self.session_registry(session_uuid).set_session_window_title(title=title) __set_session_window_title = set_session_window_title def raise_session_window(self, session_uuid): """\ Try to lift the session window above all other windows and bring it to focus. :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` """ self.session_registry(session_uuid).raise_session_window() __raise_session_window = raise_session_window def session_auto_start_or_resume(self, session_uuid, newest=True, oldest=False, all_suspended=False, start=True): """\ Automatically start or resume one or several sessions. This method is called from within the session itself on session registration, so this method can be used to handle auto-start/-resume events. :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :param newest: if resuming, only resume newest/youngest session (Default value = True) :type newest: ``bool`` :param oldest: if resuming, only resume oldest session (Default value = False) :type oldest: ``bool`` :param all_suspended: if resuming, resume all suspended sessions (Default value = False) :type all_suspended: ``bool`` :param start: if no session is to be resumed, start a new session (Default value = True) :type start: ``bool`` """ self.session_registry(session_uuid).do_auto_start_or_resume(newest=newest, oldest=oldest, all_suspended=all_suspended, start=start, redirect_to_client=False) __session_auto_start_or_resume = session_auto_start_or_resume def start_session(self, session_uuid, **sessionopts): """\ Start a new X2Go session on the remote X2Go server. This method will open---if everything has been successful till here---the X2Go session window. Before calling this method you have to register your desired session with :func:`register_session()` (initialization of session parameters) and connect to it with :func:`connect_session()` (authentication). :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :param sessionopts: pass-through of options directly to the session instance's :func:`X2GoSession.start() ` method :type sessionopts: ``dict`` :returns: returns True if this method has been successful :rtype: ``bool`` """ # prevent the newly started session from being registered twice if self.auto_register_sessions: self.session_registry.disable_session_auto_registration() # start the actual session _retval = self.session_registry(session_uuid).start(**sessionopts) # re-enable session auto-registration... if self.auto_register_sessions: self.session_registry.enable_session_auto_registration() return _retval __start_session = start_session def share_desktop_session(self, session_uuid, desktop=None, user=None, display=None, share_mode=0, check_desktop_list=False, **sessionopts): """\ Share another already running desktop session. Desktop sharing can be run in two different modes: view-only and full-access mode. Like new sessions a to-be-shared session has be registered first with the :class:`x2go.client.X2GoClient` instance. :param session_uuid: the X2Go session's UUID registry hash (Default value = None) :type session_uuid: ``str`` :param desktop: desktop ID of a sharable desktop in format @ (Default value = None) :type desktop: ``str`` :param user: user name and display number can be given separately, here give the name of the user who wants to share a session with you. (Default value = None) :type user: ``str`` :param display: user name and display number can be given separately, here give the number of the display that a user allows you to be shared with. (Default value = None) :type display: ``str`` :param share_mode: desktop sharing mode, 0 is VIEW-ONLY, 1 is FULL-ACCESS. (Default value = 0) :type share_mode: ``int`` :param sessionopts: pass-through of options directly to the session instance's :func:`X2GoSession.share_desktop() ` method :type sessionopts: ``dict`` :param check_desktop_list: check if the given desktop is available on the X2Go server; handle with care as the server-side ``x2golistdesktops`` command might block client I/O. (Default value = False) :returns: True if the session could be successfully shared. :rtype: ``bool`` :raises X2GoDesktopSharingException: if a given desktop ID does not specify an available desktop session """ # X2GoClient.list_desktops() uses caching (if enabled, so we prefer lookups here...) if desktop: _desktop = desktop user = None display = None else: _desktop = '%s@%s' % (user, display) if not _desktop in self._X2GoClient__list_desktops(session_uuid): _desktop = '%s.0' % _desktop return self.session_registry(session_uuid).share_desktop(desktop=_desktop, share_mode=share_mode, check_desktop_list=check_desktop_list, **sessionopts) __share_desktop_session = share_desktop_session def resume_session(self, session_uuid=None, session_name=None, match_profile_name=None, **sessionopts): """\ Resume or continue a suspended / running X2Go session on a remote X2Go server (as specified when :func:`register_session()` was called). :param session_uuid: the X2Go session's UUID registry hash (Default value = None) :type session_uuid: ``str`` :param session_name: the server-side name of an X2Go session (Default value = None) :type session_name: ``str`` :param match_profile_name: only resume a session if this profile name matches (Default value = None) :type match_profile_name: ``str`` :param sessionopts: pass-through of options directly to the session instance's :func:`X2GoSession.resume() ` method :type sessionopts: ``dict`` :returns: returns True if this method has been successful :rtype: ``bool`` :raises X2GoClientException: if the method does not know what session to resume """ try: if session_uuid is None and session_name is None: raise x2go_exceptions.X2GoClientException('can\'t resume a session without either session_uuid or session_name') if session_name is None and self.session_registry(session_uuid).session_name is None: raise x2go_exceptions.X2GoClientException('don\'t know which session to resume') if session_uuid is None: session_uuid = self.session_registry.get_session_of_session_name(session_name=session_name, return_object=False, match_profile_name=match_profile_name) return self.session_registry(session_uuid).resume(session_list=self._X2GoClient__list_sessions(session_uuid=session_uuid), **sessionopts) else: return self.session_registry(session_uuid).resume(session_name=session_name, session_list=self._X2GoClient__list_sessions(session_uuid=session_uuid), **sessionopts) except x2go_exceptions.X2GoControlSessionException: profile_name = self.get_session_profile_name(session_uuid) if self.session_registry(session_uuid).connected: self.HOOK_on_control_session_death(profile_name) self.disconnect_profile(profile_name) __resume_session = resume_session def suspend_session(self, session_uuid, session_name=None, match_profile_name=None, **sessionopts): """\ Suspend an X2Go session. Normally, you will use this method to suspend a registered session that you have formerly started/resumed from within your recent :class:`x2go.client.X2GoClient` instance. For this you simply call this method using the session's ``session_uuid``, leave the ``session_name`` empty. Alternatively, you can suspend a non-associated X2Go session: To do this you simply neeed to register (with the :func:`register_session()` method) an X2Go session on the to-be-addressed remote X2Go server and connect (:func:`connect_session()`) to it. Then call this method with the freshly obtained ``session_uuid`` and the remote X2Go session name (as shown e.g. in x2golistsessions output). :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :param session_name: the server-side name of an X2Go session (for non-associated session suspend) (Default value = None) :type session_name: ``str`` :param match_profile_name: only suspend a session if this profile name matches (Default value = None) :type match_profile_name: ``str`` :param sessionopts: pass-through of options directly to the session instance's :func:`X2GoSession.suspend() ` method :type sessionopts: ``dict`` :returns: returns True if this method has been successful :rtype: ``bool`` """ try: if session_name is None: # make sure that the current list of shared folders is up-to-date before the session suspends self.get_shared_folders(session_uuid, check_list_mounts=True) return self.session_registry(session_uuid).suspend(**sessionopts) else: if match_profile_name is None: running_sessions = self.session_registry.running_sessions() else: running_sessions = self.session_registry.running_sessions_of_profile_name(match_profile_name) for session in running_sessions: if session_name == session.get_session_name(): return session.suspend() return self.session_registry(session_uuid).control_session.suspend(session_name=session_name, **sessionopts) except x2go_exceptions.X2GoControlSessionException: profile_name = self.get_session_profile_name(session_uuid) if self.session_registry(session_uuid).conntected: self.HOOK_on_control_session_death(profile_name) self.disconnect_profile(profile_name) __suspend_session = suspend_session def terminate_session(self, session_uuid, session_name=None, match_profile_name=None, **sessionopts): """\ Terminate an X2Go session. Normally you will use this method to terminate a registered session that you have formerly started/resumed from within your recent :class:`x2go.client.X2GoClient` instance. For this you simply call this method using the session's ``session_uuid``, leave the ``session_name`` empty. Alternatively, you can terminate a non-associated X2Go session: To do this you simply neeed to register (:func:`register_session()`) an X2Go session on the to-be-addressed remote X2Go server and connect (:func:`connect_session()`) to it. Then call this method with the freshly obtained ``session_uuid`` and the remote X2Go session name (as shown in e.g. x2golistsessions output). :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :param session_name: the server-side name of an X2Go session (Default value = None) :type session_name: ``str`` :param match_profile_name: only terminate a session if this profile name matches (Default value = None) :type match_profile_name: ``str`` :param sessionopts: pass-through of options directly to the session instance's :func:`X2GoSession.terminate() ` method :type sessionopts: ``dict`` :returns: returns True if this method has been successful :rtype: ``bool`` """ try: if session_name is None: # make sure that the current list of shared folders is up-to-date before the session terminates self.get_shared_folders(session_uuid, check_list_mounts=True) return self.session_registry(session_uuid).terminate(**sessionopts) else: if match_profile_name is None: terminatable_sessions = self.session_registry.running_sessions() + self.session_registry.suspended_sessions() else: terminatable_sessions = self.session_registry.running_sessions_of_profile_name(match_profile_name) + self.session_registry.suspended_sessions_of_profile_name(match_profile_name) for session in terminatable_sessions: if session_name == session.get_session_name(): return session.terminate() return self.session_registry(session_uuid).control_session.terminate(session_name=session_name, **sessionopts) except x2go_exceptions.X2GoControlSessionException: profile_name = self.get_session_profile_name(session_uuid) if self.session_registry(session_uuid).conntected: self.HOOK_on_control_session_death(profile_name) self.disconnect_profile(profile_name) __terminate_session = terminate_session def get_session_profile_name(self, session_uuid): """\ Retrieve the profile name of the session that has been registered under ``session_uuid``. For profile based sessions this will be the profile name as used in x2goclient's »sessions« configuration file. For non-profile based session this will either be a ``profile_name`` that was passed to :func:`register_session()` or it will be the application that instantiated this :class:`x2go.client.X2GoClient` instance. :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :returns: X2Go session profile name :rtype: ``str`` """ return self.session_registry(session_uuid).get_profile_name() __get_session_profile_name = get_session_profile_name def get_session_profile_id(self, session_uuid): """\ Retrieve the profile id of the session that has been registered under ``session_uuid``. For profile based sessions this will be the profile id as used in x2goclient's »sessions« configuration node (section header of a session profile in the config, normally a timestamp created on session profile creation/modification). For non-profile based sessions this will be a timestamp created on X2Go session registration by ``register_session``. :param session_uuid: the session profile name :type session_uuid: ``str`` :returns: the X2Go session profile's id :rtype: ``str`` """ return self.session_registry(session_uuid).profile_id __get_session_profile_id = get_session_profile_id def session_ok(self, session_uuid): """\ Test if the X2Go session registered as ``session_uuid`` is in a healthy state. :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :returns: ``True`` if session is ok, ``False`` otherwise :rtype: ``bool`` """ return self.session_registry(session_uuid).session_ok() __session_ok = session_ok def is_session_connected(self, session_uuid): """\ Test if the X2Go session registered as ``session_uuid`` connected to the X2Go server. :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :returns: ``True`` if session is connected, ``False`` otherwise :rtype: ``bool`` """ return self.session_registry(session_uuid).is_connected() __is_session_connected = is_session_connected def is_profile_connected(self, profile_name): """\ Test if the X2Go given session profile has open connections to the X2Go server. :param profile_name: a valid session profile name :type profile_name: ``str`` :returns: ``True`` if profile has a connected session, ``False`` otherwise :rtype: ``bool`` """ return bool(self.client_connected_sessions_of_profile_name(profile_name=profile_name)) __is_profile_connected = is_profile_connected def is_session_profile(self, profile_id_or_name): """\ Test if the X2Go given session profile is configured in the client's ``sessions`` file. :param profile_id_or_name: test existence of this session profile name (or id) :type profile_id_or_name: ``str`` :returns: ``True`` if session profile exists, ``False`` otherwise :rtype: ``bool`` """ return self.session_profiles.has_profile(profile_id_or_name) __is_session_profile = is_session_profile def is_session_running(self, session_uuid, session_name=None): """\ Test if the X2Go session registered as ``session_uuid`` is up and running. :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :param session_name: the server-side name of an X2Go session (Default value = None) :type session_name: ``str`` :returns: ``True`` if session is running, ``False`` otherwise :rtype: ``bool`` """ if session_name is None: try: return self.session_registry(session_uuid).is_running() except x2go_exceptions.X2GoSessionRegistryException: return None else: return session_name in [ s for s in self.server_running_sessions(session_uuid) ] __is_session_running = is_session_running def is_session_suspended(self, session_uuid, session_name=None): """\ Test if the X2Go session registered as ``session_uuid`` is in suspended state. :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :param session_name: the server-side name of an X2Go session (Default value = None) :type session_name: ``str`` :returns: ``True`` if session is suspended, ``False`` otherwise :rtype: ``bool`` """ if session_name is None: try: return self.session_registry(session_uuid).is_suspended() except x2go_exceptions.X2GoSessionRegistryException: return None else: return session_name in [ s for s in self.server_suspended_sessions(session_uuid) ] __is_session_suspended = is_session_suspended def has_session_terminated(self, session_uuid, session_name=None): """\ Test if the X2Go session registered as ``session_uuid`` has terminated. :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :param session_name: the server-side name of an X2Go session (Default value = None) :type session_name: ``str`` :returns: ``True`` if session has terminated, ``False`` otherwise :rtype: ``bool`` """ if session_name is None: try: return self.session_registry(session_uuid).has_terminated() except x2go_exceptions.X2GoSessionRegistryException: return None else: return session_name not in [ s for s in self.server_running_sessions(session_uuid) + self.server_suspended_sessions(session_uuid) ] __has_session_terminated = has_session_terminated def is_folder_sharing_available(self, session_uuid=None, profile_name=None): """\ Test if local folder sharing is available for X2Go session with unique ID or session profile . :param session_uuid: the X2Go session's UUID registry hash (Default value = None) :type session_uuid: ``str`` :param profile_name: alternatively, the profile name can be used to perform this query (Default value = None) :type profile_name: ``str`` :returns: returns ``True`` if the profile/session supports local folder sharing :rtype: ``bool`` """ if session_uuid is None and profile_name: session_uuid = self._X2GoClient__get_master_session(profile_name, return_object=False) if session_uuid: try: return self.session_registry(session_uuid).is_folder_sharing_available() except x2go_exceptions.X2GoSessionRegistryException: return False else: self.logger('Cannot find a terminal session for profile ,,%s\'\' that can be used to query folder sharing capabilities' % profile_name, loglevel=log.loglevel_INFO) return False __is_folder_sharing_available = is_folder_sharing_available __profile_is_folder_sharing_available = is_folder_sharing_available __session_is_folder_sharing_available = is_folder_sharing_available def share_local_folder(self, session_uuid=None, local_path=None, profile_name=None, folder_name=None): """\ Share a local folder with the X2Go session registered as ``session_uuid``. When calling this method the given client-side folder is mounted on the X2Go server (via sshfs) and (if in desktop mode) provided as a desktop icon on your remote session's desktop. :param session_uuid: the X2Go session's UUID registry hash (Default value = None) :type session_uuid: ``str`` :param local_path: the full path to an existing folder on the local (client-side) file system (Default value = None) :type local_path: ``str`` :param folder_name: synonymous to ``local_path`` (Default value = None) :type folder_name: ``str`` :param profile_name: alternatively, the profile name can be used to share local folders (Default value = None) :type profile_name: ``str`` :returns: returns ``True`` if the local folder has been successfully mounted :rtype: ``bool`` """ # compat for Python-X2Go (<=0.1.1.6) if folder_name: local_path = folder_name if session_uuid is None and profile_name: session_uuid = self._X2GoClient__get_master_session(profile_name, return_object=False) if session_uuid: try: return self.session_registry(session_uuid).share_local_folder(local_path=local_path) except x2go_exceptions.X2GoSessionException: return False else: self.logger('Cannot find a terminal session for profile ,,%s\'\' to share a local folder with' % profile_name, loglevel=log.loglevel_WARN) return False __share_local_folder = share_local_folder __share_local_folder_with_session = share_local_folder __share_local_folder_with_profile = share_local_folder def unshare_all_local_folders(self, session_uuid=None, profile_name=None): """\ Unshare all local folders mounted in X2Go session registered as ``session_uuid``. When calling this method all client-side mounted folders on the X2Go server (via sshfs) for session with ID will get unmounted. :param session_uuid: the X2Go session's UUID registry hash (Default value = None) :type session_uuid: ``str`` :param profile_name: alternatively, the profile name can be used to unshare mounted folders (Default value = None) :type profile_name: ``str`` :returns: returns ``True`` if all local folders could be successfully unmounted :rtype: ``bool`` """ if session_uuid is None and profile_name: session_uuid = self._X2GoClient__get_master_session(profile_name, return_object=False) if session_uuid: return self.session_registry(session_uuid).unshare_all_local_folders() else: self.logger('Cannot find a terminal session for profile ,,%s\'\' from which to unmount local folders' % profile_name, loglevel=log.loglevel_WARN) return False unshare_all_local_folders_from_session = unshare_all_local_folders unshare_all_local_folders_from_profile = unshare_all_local_folders __unshare_all_local_folders_from_session = unshare_all_local_folders __unshare_all_local_folders_from_profile = unshare_all_local_folders def unshare_local_folder(self, session_uuid=None, profile_name=None, local_path=None): """\ Unshare local folder that is mounted in the X2Go session registered as ``session_uuid``. When calling this method the given client-side mounted folder on the X2Go server (via sshfs) for session with ID will get unmounted. :param session_uuid: the X2Go session's UUID registry hash (Default value = None) :type session_uuid: ``str`` :param profile_name: alternatively, the profile name can be used to unshare mounted folders (Default value = None) :type profile_name: ``str`` :param local_path: the full path of a local folder that is mounted within X2Go session with session ID (or recognized via profile name) and that shall be unmounted from that session. (Default value = None) :type local_path: ``str`` :returns: returns ``True`` if all local folders could be successfully unmounted :rtype: ``bool`` """ if session_uuid is None and profile_name: session_uuid = self._X2GoClient__get_master_session(profile_name, return_object=False) if session_uuid: return self.session_registry(session_uuid).unshare_local_folder(local_path=local_path) else: self.logger('Cannot find a terminal session for profile ,,%s\'\' from which to unmount local folders' % profile_name, loglevel=log.loglevel_WARN) return False unshare_local_folder_from_session = unshare_local_folder unshare_local_folder_from_profile = unshare_local_folder __unshare_local_folder_from_session = unshare_local_folder __unshare_local_folder_from_profile = unshare_local_folder def get_shared_folders(self, session_uuid=None, profile_name=None, check_list_mounts=False): """\ Get a list of local folders mounted within X2Go session with session hash from this client. :param session_uuid: the X2Go session's UUID registry hash (Default value = None) :type session_uuid: ``str`` :param profile_name: alternatively, the profile name can be used to get mounted folders of a session connected profile (Default value = None) :type profile_name: ``str`` :param check_list_mounts: query the server-side mount list for up-to-date information (Default value = False) :type check_list_mounts: ``bool`` :returns: returns a ``list`` of those local folder names that are mounted within X2Go session . :rtype: ``list`` """ if session_uuid is None and profile_name: session_uuid = self._X2GoClient__get_master_session(profile_name, return_object=False) if session_uuid and profile_name is None: profile_name = self.session_registry(session_uuid).get_profile_name() if session_uuid and profile_name: mounts = None if check_list_mounts: _mounts = self.list_mounts_by_profile_name(profile_name) mounts = [] for mount_list in list(_mounts.values()): mounts.extend(mount_list) return self.session_registry(session_uuid).get_shared_folders(check_list_mounts=check_list_mounts, mounts=mounts) session_get_shared_folders = get_shared_folders profile_get_shared_folders = get_shared_folders __session_get_shared_folders = get_shared_folders __profile_get_shared_folders = get_shared_folders def get_master_session(self, profile_name, return_object=True, return_session_name=False): """\ Retrieve the master session of a specific profile. :param profile_name: the profile name that we query the master session of :type profile_name: ``str`` :param return_object: return :class:`x2go.session.X2GoSession` instance (Default value = True) :type return_object: ``bool`` :param return_session_name: return X2Go session name (Default value = False) :type return_session_name: ``bool`` :returns: a session list (as UUID hashes, objects, profile names/IDs or session names) :rtype: ``list`` """ return self.session_registry.get_master_session(profile_name, return_object=return_object, return_session_name=return_session_name) profile_master_session = get_master_session __get_master_session = get_master_session __profile_master_session = profile_master_session ### ### Provide access to the X2GoClient's session registry ### def client_connected_sessions(self, return_objects=False, return_profile_names=False, return_profile_ids=False, return_session_names=False): """\ Retrieve a list of X2Go sessions that this :class:`x2go.client.X2GoClient` instance is connected to. :param return_objects: return as list of X2Go session objects (Default value = False) :type return_objects: ``bool`` :param return_profile_names: return as list of session profile names (Default value = False) :type return_profile_names: ``bool`` :param return_profile_ids: return as list of session profile IDs (Default value = False) :type return_profile_ids: ``bool`` :param return_session_names: return as list of session names (Default value = False) :type return_session_names: ``bool`` :returns: list of connected sessions :rtype: ``list`` """ return self.session_registry.connected_sessions(return_objects=return_objects, return_profile_names=return_profile_names, return_profile_ids=return_profile_ids, return_session_names=return_session_names) __client_connected_sessions = client_connected_sessions @property def client_has_connected_sessions(self): """\ Equals ``True`` if there are any connected sessions with this :class:`x2go.client.X2GoClient` instance. """ return self.session_registry.has_connected_sessions __client_has_connected_sessions = client_has_connected_sessions def client_associated_sessions(self, return_objects=False, return_profile_names=False, return_profile_ids=False, return_session_names=False): """\ Retrieve a list of X2Go sessions associated to this :class:`x2go.client.X2GoClient` instance. :param return_objects: return as list of X2Go session objects (Default value = False) :type return_objects: ``bool`` :param return_profile_names: return as list of session profile names (Default value = False) :type return_profile_names: ``bool`` :param return_profile_ids: return as list of session profile IDs (Default value = False) :type return_profile_ids: ``bool`` :param return_session_names: return as list of session names (Default value = False) :type return_session_names: ``bool`` :returns: list of associated sessions :rtype: ``list`` """ return self.session_registry.associated_sessions(return_objects=return_objects, return_profile_names=return_profile_names, return_profile_ids=return_profile_ids, return_session_names=return_session_names) __client_associated_sessions = client_associated_sessions @property def client_has_associated_sessions(self): """\ Equals ``True`` if there are any associated sessions with this :class:`x2go.client.X2GoClient` instance. """ return self.session_registry.has_associated_sessions __client_has_associated_sessions = client_has_associated_sessions def client_running_sessions(self, return_objects=False, return_profile_names=False, return_profile_ids=False, return_session_names=False): """\ Retrieve a list of running X2Go sessions. :param return_objects: return as list of X2Go session objects (Default value = False) :type return_objects: ``bool`` :param return_profile_names: return as list of session profile names (Default value = False) :type return_profile_names: ``bool`` :param return_profile_ids: return as list of session profile IDs (Default value = False) :type return_profile_ids: ``bool`` :param return_session_names: return as list of session names (Default value = False) :type return_session_names: ``bool`` :returns: list of running sessions :rtype: ``list`` """ return self.session_registry.running_sessions(return_objects=return_objects, return_profile_names=return_profile_names, return_profile_ids=return_profile_ids, return_session_names=return_session_names) __client_running_sessions = client_running_sessions @property def client_has_running_sessions(self): """\ Equals ``True`` if there are any running sessions with this :class:`x2go.client.X2GoClient` instance. """ return self.session_registry.has_running_sessions __client_has_running_sessions = client_has_running_sessions def client_suspended_sessions(self, return_objects=False, return_profile_names=False, return_profile_ids=False, return_session_names=False): """\ Retrieve a list of suspended X2Go sessions. :param return_objects: return as list of X2Go session objects (Default value = False) :type return_objects: ``bool`` :param return_profile_names: return as list of session profile names (Default value = False) :type return_profile_names: ``bool`` :param return_profile_ids: return as list of session profile IDs (Default value = False) :type return_profile_ids: ``bool`` :param return_session_names: return as list of session names (Default value = False) :type return_session_names: ``bool`` :returns: list of suspended sessions :rtype: ``list`` """ return self.session_registry.running_sessions(return_objects=return_objects, return_profile_names=return_profile_names, return_profile_ids=return_profile_ids, return_session_names=return_session_names) __client_suspended_sessions = client_suspended_sessions @property def client_has_suspended_sessions(self): """\ Equals ``True`` if there are any suspended sessions with this :class:`x2go.client.X2GoClient` instance. """ return self.session_registry.has_suspended_sessions __client_has_suspended_sessions = client_has_suspended_sessions def client_registered_sessions(self, return_objects=True, return_profile_names=False, return_profile_ids=False, return_session_names=False): """\ Retrieve a list of registered X2Go sessions. :param return_objects: return as list of X2Go session objects (Default value = True) :type return_objects: ``bool`` :param return_profile_names: return as list of session profile names (Default value = False) :type return_profile_names: ``bool`` :param return_profile_ids: return as list of session profile IDs (Default value = False) :type return_profile_ids: ``bool`` :param return_session_names: return as list of session names (Default value = False) :type return_session_names: ``bool`` :returns: list of registered sessions :rtype: ``list`` """ return self.session_registry.registered_sessions(return_objects=return_objects, return_profile_names=return_profile_names, return_profile_ids=return_profile_ids, return_session_names=return_session_names) __client_registered_sessions = client_registered_sessions @property def client_control_sessions(self): """\ Equals a list of all registered X2Go control sessions. """ return self.session_registry.control_sessions __client_control_sessions = client_control_sessions def client_control_session_of_profile_name(self, profile_name): """\ Retrieve control session for profile name . :param profile_name: profile name :type profile_name: ``str`` :returns: control session instance :rtype: ``X2GoControlSession`` instance """ return self.session_registry.control_session_of_profile_name(profile_name) __client_control_session_of_profile_name = client_control_session_of_profile_name def is_x2goserver(self, session_uuid, force=False): """\ Check a remote and connected server (identified by the session_uuid hash) if it really is an X2Go Server (i.e., if it has the X2Go Server software installed. :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :param force: reinforce the lookup, don't use cached information :type force: ``bool`` :returns: ``True`` if the remote server has X2Go Server installed, ``False`` otherwise. :rtype: ``bool`` """ profile_name = self.session_registry(session_uuid).get_profile_name() return ('x2goserver' in self.get_server_versions(profile_name, force=force).keys()) def get_server_versions(self, profile_name, component=None, force=False): """\ Query the server configured in session profile for the list of install X2Go components and its versions. :param profile_name: use the control session of this profile to query the X2Go server for its component list :type profile_name: ``str`` :param component: only return the version of a specific component (Default value = None) :type component: ``str`` :param force: refresh component/version data by a query to the server (Default value = False) :type force: ``bool`` :returns: dictionary of server components (as keys) and their versions (as values) or the version of the given :rtype: ``dict`` or ``str`` :raises X2GoClientException: if component is not available on the X2Go Server. """ control_session = self.client_control_session_of_profile_name(profile_name) if component is None: return control_session.get_server_versions(force=force) else: try: return control_session.get_server_versions(force=force)[component] except KeyError: raise x2go_exceptions.X2GoClientException('No such component on X2Go Server') __get_server_versions = get_server_versions get_server_components = get_server_versions __get_server_components = get_server_components def get_server_features(self, profile_name, force=False): """\ Query the server configured in session profile for the list of server-side X2Go features. :param profile_name: use the control session of this profile to query the X2Go server for its feature list :type profile_name: ``str`` :param force: refresh feature list by a query to the server (Default value = False) :type force: ``bool`` :returns: list of server feature names (as returned by server-side command ,,x2gofeaturelist'' :rtype: ``list`` """ control_session = self.client_control_session_of_profile_name(profile_name) return control_session.get_server_features(force=force) __get_server_features = get_server_features def has_server_feature(self, profile_name, feature): """\ Query the server configured in session profile for the availability of a certain server feature. :param profile_name: use the control session of this profile to query the X2Go server for its feature :type profile_name: ``str`` :param feature: test the availability of this feature on the X2Go server :type feature: ``str`` :returns: ``True`` if the feature is available on the queried server :rtype: ``bool`` """ control_session = self.client_control_session_of_profile_name(profile_name) return feature in control_session.get_server_features() __has_server_feature = has_server_feature def client_registered_session_of_name(self, session_name, return_object=False): """\ Retrieve X2Go session of a given session name. :param session_name: session name :type session_name: ``str`` :param return_object: return as X2Go session object (Default value = False) :type return_object: ``bool`` :returns: session instance of the given name :rtype: ``X2GoSession`` or ``str`` """ return self.session_registry.get_session_of_session_name(session_name, return_object=return_object) __client_registered_session_of_name = client_registered_session_of_name def client_has_registered_session_of_name(self, session_name): """\ Equals ``True`` if there is a registered session of name . :param session_name: session name :type session_name: ``str`` :returns: ``True`` if the given session is registered :rtype: ``bool`` """ return self.client_registered_session_of_name(session_name) is not None __client_has_registered_session_of_name = client_registered_session_of_name def client_registered_sessions_of_profile_name(self, profile_name, return_objects=False, return_session_names=False): """\ Retrieve registered X2Go sessions of profile name . :param profile_name: profile name :type profile_name: ``str`` :param return_objects: return as list of X2Go session objects (Default value = False) :type return_objects: ``bool`` :param return_session_names: return as list of session names (Default value = False) :type return_session_names: ``bool`` :returns: list of registered sessions of profile name :rtype: ``list`` """ return self.session_registry.registered_sessions_of_profile_name(profile_name, return_objects=return_objects, return_session_names=return_session_names) __client_registered_sessions_of_profile_name = client_registered_sessions_of_profile_name def client_connected_sessions_of_profile_name(self, profile_name, return_objects=False, return_session_names=False): """\ Retrieve connected X2Go sessions of profile name . :param profile_name: profile name :type profile_name: ``str`` :param return_objects: return as list of X2Go session objects (Default value = False) :type return_objects: ``bool`` :param return_session_names: return as list of session names (Default value = False) :type return_session_names: ``bool`` :returns: list of connected sessions of profile name :rtype: ``list`` """ return self.session_registry.connected_sessions_of_profile_name(profile_name, return_objects=return_objects, return_session_names=return_session_names) __client_connected_sessions_of_profile_name = client_connected_sessions_of_profile_name def client_associated_sessions_of_profile_name(self, profile_name, return_objects=False, return_session_names=False): """\ Retrieve associated X2Go sessions of profile name . :param profile_name: profile name :type profile_name: ``str`` :param return_objects: return as list of X2Go session objects (Default value = False) :type return_objects: ``bool`` :param return_session_names: return as list of session names (Default value = False) :type return_session_names: ``bool`` :returns: list of associated sessions of profile name :rtype: ``list`` """ return self.session_registry.associated_sessions_of_profile_name(profile_name, return_objects=return_objects, return_session_names=return_session_names) __client_associated_sessions_of_profile_name = client_associated_sessions_of_profile_name def client_pubapp_sessions_of_profile_name(self, profile_name, return_objects=False, return_session_names=False): """\ Retrieve X2Go sessions of profile name that provide published applications. :param profile_name: profile name :type profile_name: ``str`` :param return_objects: return as list of X2Go session objects (Default value = False) :type return_objects: ``bool`` :param return_session_names: return as list of session names (Default value = False) :type return_session_names: ``bool`` :returns: list of application publishing sessions of profile name :rtype: ``list`` """ return self.session_registry.pubapp_sessions_of_profile_name(profile_name, return_objects=return_objects, return_session_names=return_session_names) __client_pubapp_sessions_of_profile_name = client_pubapp_sessions_of_profile_name def client_running_sessions_of_profile_name(self, profile_name, return_objects=False, return_session_names=False): """\ Retrieve running X2Go sessions of profile name . :param profile_name: profile name :type profile_name: ``str`` :param return_objects: return as list of X2Go session objects (Default value = False) :type return_objects: ``bool`` :param return_session_names: return as list of session names (Default value = False) :type return_session_names: ``bool`` :returns: list of running sessions of profile name :rtype: ``list`` """ return self.session_registry.running_sessions_of_profile_name(profile_name, return_objects=return_objects, return_session_names=return_session_names) __client_running_sessions_of_profile_name = client_running_sessions_of_profile_name def client_suspended_sessions_of_profile_name(self, profile_name, return_objects=False, return_session_names=False): """\ Retrieve suspended X2Go sessions of profile name . :param profile_name: profile name :type profile_name: ``str`` :param return_objects: return as list of X2Go session objects (Default value = False) :type return_objects: ``bool`` :param return_session_names: return as list of session names (Default value = False) :type return_session_names: ``bool`` :returns: list of suspended sessions of profile name :rtype: ``list`` """ return self.session_registry.suspended_sessions_of_profile_name(profile_name, return_objects=return_objects, return_session_names=return_session_names) __client_suspended_sessions_of_profile_name = client_suspended_sessions_of_profile_name ### ### Provide access to the X2Go server's sessions DB ### def server_is_alive(self, session_uuid): """\ Test if server that corresponds to the terminal session ``session_uuid`` is alive. If the session is not connected anymore the :func:`X2GoClient.HOOK_on_control_session_death() ` gets called. :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :returns: ``True`` if X2Go server connection for :class:`x2go.session.X2GoSession` instance with is alive. :rtype: ``bool`` :raises X2GoControlSessionException: if the session is not connected anymore; in that case the :func:`HOOK_on_control_session_death()` gets called. """ try: return self.session_registry(session_uuid).is_alive() except x2go_exceptions.X2GoControlSessionException: profile_name = self.get_session_profile_name(session_uuid) if self.session_registry(session_uuid).conntected: self.HOOK_on_control_session_death(profile_name) self.disconnect_profile(profile_name) return False __server_is_alive = server_is_alive def all_servers_are_alive(self): """\ Test vitality of all connected X2Go servers. :returns: ``True`` if all connected X2Go servers are alive. :rtype: ``bool`` """ _all_alive = True for session_uuid in self.client_connected_sessions(): _all_alive = _all_alive and self.server_is_alive(session_uuid) return _all_alive __all_servers_are_alive = all_servers_are_alive def server_valid_x2gouser(self, session_uuid, username=None): """\ Check if user is allowed to start an X2Go session on a remote server. :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :param username: user name to test validity for (Default value = None) :type username: ``str`` :returns: Is remote user allowed to start an X2Go session? :rtype: ``str`` """ return self.session_registry(session_uuid).user_is_x2gouser(username=username) __server_valid_x2gouser = server_valid_x2gouser def server_running_sessions(self, session_uuid): """\ Retrieve a list of session names of all server-side running sessions (including those not instantiated by our :class:`x2go.client.X2GoClient` instance). :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :returns: list of session names :rtype: ``list`` :raises X2GoClientException: if the session with UUID ``session_uuid`` is not connected """ if self._X2GoClient__is_session_connected(session_uuid): session_list = self._X2GoClient__list_sessions(session_uuid) return [ key for key in list(session_list.keys()) if session_list[key].status == 'R' ] else: raise x2go_exceptions.X2GoClientException('X2Go session with UUID %s is not connected' % session_uuid) __server_running_sessions = server_running_sessions def server_has_running_sessions(self, session_uuid): """\ Equals ``True`` if the X2Go server has any running sessions. :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :returns: ``True``, if there are running sessions :rtype: ``bool`` """ return len(self._X2GoClient__server_running_sessions(session_uuid)) > 0 __server_has_running_sessions = server_has_running_sessions def server_has_running_session_of_name(self, session_uuid, session_name): """\ Equals ``True`` if the X2Go server has a running session of name . :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :param session_name: session name :type session_name: ``str`` """ return session_name in self._X2GoClient__server_running_sessions(session_uuid) __server_has_running_session_of_name = server_has_running_session_of_name def server_suspended_sessions(self, session_uuid): """\ Retrieve a list of session names of all server-side suspended sessions (including those not instantiated by our :class:`x2go.client.X2GoClient` instance). :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :returns: list of session names :rtype: ``list`` :raises X2GoClientException: if the session with UUID ``session_uuid`` is not connected """ if self._X2GoClient__is_session_connected(session_uuid): session_list = self._X2GoClient__list_sessions(session_uuid) return [ key for key in list(session_list.keys()) if session_list[key].status == 'S' ] else: raise x2go_exceptions.X2GoClientException('X2Go session with UUID %s is not connected' % session_uuid) __server_suspended_sessions = server_suspended_sessions def server_has_suspended_sessions(self, session_uuid): """\ Equals ``True`` if the X2Go server has any suspended sessions. :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` """ return len(self._X2GoClient__server_suspended_sessions(session_uuid)) > 0 __server_has_suspended_sessions = server_has_suspended_sessions def server_has_suspended_session_of_name(self, session_uuid, session_name): """\ Equals ``True`` if the X2Go server has a suspended session of name . :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :param session_name: session name :type session_name: ``str`` :returns: ``True``, if there are running sessions :rtype: ``bool`` """ return session_name in self._X2GoClient__server_suspended_sessions(session_uuid) __server_has_suspended_session_of_name = server_has_suspended_session_of_name ### ### CLIENT OPERATIONS ON SESSIONS (listing sessions, terminating non-associated sessions etc.) ### def clean_sessions(self, session_uuid, published_applications=False): """\ Find running X2Go sessions that have previously been started by the connected user on the remote X2Go server and terminate them. Before calling this method you have to setup a pro forma remote X2Go session with :func:`X2GoClient.register_session() ` (even if you do not intend to open a real X2Go session window on the remote server) and connect to this session (with :func:`X2GoClient.connect_session() `. :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :param published_applications: if ``True``, also terminate sessions that are published applications provider (Default value = False) :type published_applications: ``bool`` """ _destroy_terminals = not ( self.auto_update_sessionregistry == True) try: session = self.session_registry(session_uuid) session.clean_sessions(destroy_terminals=_destroy_terminals, published_applications=published_applications) except x2go_exceptions.X2GoSessionRegistryException: # silently ignore a non-registered session UUID (mostly occurs during disconnects) pass __clean_sessions = clean_sessions def list_sessions(self, session_uuid=None, profile_name=None, profile_id=None, no_cache=False, refresh_cache=False, update_sessionregistry=True, register_sessions=False, with_command=None, raw=False): """\ Use the X2Go session registered under ``session_uuid`` to retrieve a list of running or suspended X2Go sessions from the connected X2Go server (for the authenticated user). Before calling this method you have to setup a pro forma remote X2Go session with :func:`X2GoClient.register_session() ` (even if you do not intend to open a real X2Go session window on the remote server) and connect to this session (with :func:`X2GoClient.connect_session() `. :param session_uuid: the X2Go session's UUID registry hash (Default value = None) :type session_uuid: ``str`` :param profile_name: use profile name instead of (Default value = None) :type profile_name: ``str`` :param profile_id: use profile id instead of or (Default value = None) :type profile_id: ``str`` :param no_cache: do not get the session list from cache, query the X2Go server directly (Default value = False) :type no_cache: ``bool`` :param refresh_cache: query the X2Go server directly and update the session list cache with the new information (Default value = False) :type refresh_cache: ``bool`` :param update_sessionregistry: query the X2Go server directly and update the session registry according to the obtained information (Default value = True) :type update_sessionregistry: ``bool`` :param register_sessions: query the X2Go server directly and register newly found X2Go session as :class:`x2go.session.X2GoSession` instances associated to this :class:`x2go.client.X2GoClient` instance (Default value = False) :type register_sessions: ``bool`` :param raw: output the session list in X2Go's raw ``x2golistsessions`` format (Default value = False) :type raw: ``bool`` :raises X2GoClientException: if the session profile specified by ``session_uuid``, ``profile_name`` or ``profile_id`` is not connected or if none of the named parameters has been specified """ if profile_id is not None: profile_name = self.to_profile_name(profile_id) if profile_name is not None: _connected_sessions = self.client_connected_sessions_of_profile_name(profile_name, return_objects=True) if _connected_sessions: # it does not really matter which session to use for getting a server-side session list # thus, we simply grab the first that comes in... session_uuid = _connected_sessions[0].get_uuid() else: raise x2go_exceptions.X2GoClientException('profile ,,%s\'\' is not connected' % profile_name) elif session_uuid is not None: pass else: raise x2go_exceptions.X2GoClientException('must either specify session UUID or profile name') if raw: return self.session_registry(session_uuid).list_sessions(raw=raw) if not self.use_listsessions_cache or not self.auto_update_listsessions_cache or no_cache: _session_list = self.session_registry(session_uuid).list_sessions() elif refresh_cache: self.update_cache_by_session_uuid(session_uuid) _session_list = self.listsessions_cache.list_sessions(session_uuid) else: # if there is no cache for this session_uuid available, make sure the cache gets updated # before reading from it... if self.use_listsessions_cache and (not self.listsessions_cache.is_cached(session_uuid=session_uuid, cache_type='sessions') or refresh_cache): self.__update_cache_by_session_uuid(session_uuid) _session_list = self.listsessions_cache.list_sessions(session_uuid) if update_sessionregistry: self.update_sessionregistry_status_by_profile_name(profile_name=self.get_session_profile_name(session_uuid), session_list=_session_list) if register_sessions: self.session_registry.register_available_server_sessions(profile_name=self.get_session_profile_name(session_uuid), session_list=_session_list) # filter out sessions that are not of a given command if with_command: _filtered_session_list = copy.deepcopy(_session_list) for _session in list(_session_list.keys()): if not re.match('.*_st(D|K|R|P){cmd}_dp.*'.format(cmd=with_command), _session): del _filtered_session_list[_session] _session_list = _filtered_session_list return _session_list __list_sessions = list_sessions def list_desktops(self, session_uuid=None, profile_name=None, profile_id=None, no_cache=False, refresh_cache=False, exclude_session_types=[], raw=False): """\ Use the X2Go session registered under ``session_uuid`` to retrieve a list of X2Go desktop sessions that are available for desktop sharing. Before calling this method you have to setup a pro forma remote X2Go session with :func:`X2GoClient.register_session() ` (even if you do not intend to open a real X2Go session window on the remote server) and connect to this session (with :func:`X2GoClient.connect_session() `. :param session_uuid: the X2Go session's UUID registry hash (Default value = None) :type session_uuid: ``str`` :param profile_name: use profile name instead of (Default value = None) :type profile_name: ``str`` :param profile_id: use profile id instead of or (Default value = None) :type profile_id: ``str`` :param no_cache: do not get the desktop list from cache, query the X2Go server directly (Default value = False) :type no_cache: ``bool`` :param refresh_cache: query the X2Go server directly and update the desktop list cache with the new information (Default value = False) :type refresh_cache: ``bool`` :param exclude_session_types: session types (e.g. "D", "K", "R", "S" or "P") to be excluded from the returned list of sharable desktops (this only works for sharing someone's own sessions, for sharing other users' sessions, the X2Go Desktop Sharing decides on what is sharable and what not). (Default value = []) :type exclude_session_types: ``list`` :param raw: output the session list in X2Go's raw ``x2golistdesktops`` format (Default value = False) :type raw: ``bool`` :returns: a list of available desktops to be shared :rtype: ``list`` :raises X2GoClientException: if the session profile specified by ``session_uuid``, ``profile_name`` or ``profile_id`` is not connected or if none of the named parameters has been specified """ if profile_id is not None: profile_name = self.to_profile_name(profile_id) if profile_name is not None: _connected_sessions = self.client_connected_sessions_of_profile_name(profile_name, return_objects=True) if _connected_sessions: # it does not really matter which session to use for getting a server-side session list # thus, we simply grab the first that comes in... session_uuid = _connected_sessions[0].get_uuid() else: raise x2go_exceptions.X2GoClientException('profile ,,%s\'\' is not connected' % profile_name) elif session_uuid is not None: pass else: raise x2go_exceptions.X2GoClientException('must either specify session UUID or profile name') if raw: return self.session_registry(session_uuid).list_desktops(raw=raw) if not self.use_listsessions_cache or not self.auto_update_listdesktops_cache or no_cache: _desktop_list = self.session_registry(session_uuid).list_desktops() else: if self.use_listsessions_cache and (not self.listsessions_cache.is_cached(session_uuid=session_uuid, cache_type='desktops') or refresh_cache): self.__update_cache_by_session_uuid(session_uuid, update_sessions=False, update_desktops=True) _desktop_list = self.listsessions_cache.list_desktops(session_uuid) # attempt to exclude session types that are requested to be excluded if exclude_session_types: # create an X2GoServerSessionList* instance and operate on that session_list = self.list_backend() session_list.set_sessions(self._X2GoClient__list_sessions(session_uuid)) # search for a match among the listed sessions for desktop in copy.deepcopy(_desktop_list): user = desktop.split('@')[0] if user == self.get_session_username(session_uuid): display = desktop.split('@')[1] session = session_list.get_session_with('display', display, hostname=self.get_session_server_hostname(session_uuid)) if session is None: continue if session.get_session_type() in exclude_session_types: _desktop_list.remove(desktop) return _desktop_list __list_desktops = list_desktops def list_mounts_by_profile_name(self, profile_name, no_cache=False, refresh_cache=False, raw=False): """\ For a given profil ``profile_name`` to retrieve its list of mounted client shares for that session. :param profile_name: a valid profile name :type profile_name: ``str`` :param no_cache: do not get the session list from cache, query the X2Go server directly (Default value = False) :type no_cache: ``bool`` :param raw: output the session list in X2Go's raw ``x2golistmounts`` format (Default value = False) :type raw: ``bool`` :param refresh_cache: Default value = False) :returns: list of server-side mounted shares for a given profile name :rtype: ``list`` """ sessions = [ s for s in self.client_running_sessions(return_objects=True) if s.get_profile_name() == profile_name ] if raw: _list_mounts = "" for session in sessions: _list_mounts += self.__list_mounts(session_uuid=session(), no_cache=no_cache, refresh_cache=refresh_cache, raw=True) else: _list_mounts = {} for session in sessions: _list_mounts.update(self.__list_mounts(session_uuid=session(), no_cache=no_cache, refresh_cache=refresh_cache, raw=False)) return _list_mounts __list_mounts_by_profile_name = list_mounts_by_profile_name def list_mounts(self, session_uuid, no_cache=False, refresh_cache=False, raw=False): """\ Use the X2Go session registered under ``session_uuid`` to retrieve its list of mounted client shares for that session. :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :param no_cache: do not get the session list from cache, query the X2Go server directly (Default value = False) :type no_cache: ``bool`` :param raw: output the session list in X2Go's raw ``x2golistmounts`` format (Default value = False) :type raw: ``bool`` :param refresh_cache: Default value = False) :returns: list of server-side mounted shares for a given session UUID :rtype: ``list`` """ if raw: return self.session_registry(session_uuid).list_mounts(raw=raw) if not self.use_listsessions_cache or not self.auto_update_listmounts_cache or no_cache: _mounts_list = self.session_registry(session_uuid).list_mounts() else: if self.use_listsessions_cache and (not self.listsessions_cache.is_cached(session_uuid=session_uuid, cache_type='mounts') or refresh_cache): self.__update_cache_by_session_uuid(session_uuid, update_sessions=False, update_mounts=True) _mounts_list = self.listsessions_cache.list_mounts(session_uuid) return _mounts_list __list_mounts = list_mounts ### ### Provide access to config file class objects ### def get_profiles(self): """\ Returns the :class:`x2go.client.X2GoClient` instance's ``X2GoSessionProfiles*`` object. Use this method for object retrieval if you want to modify the »sessions« configuration node (e.g. in ~/.x2goclient with the FILE backend) from within your Python X2Go based application. :returns: a ``X2GoSessionProfiles*`` instance :rtype: ``obj`` """ return self.session_profiles __get_profiles = get_profiles get_session_profiles = get_profiles """Alias for :func:`get_profiles()`.""" @property def profile_names(self): """\ Equals a list of all profile names that are known to this :class:`x2go.client.X2GoClient` instance. """ return self.session_profiles.profile_names __profile_names = profile_names def get_client_settings(self): """\ Returns the :class:`x2go.client.X2GoClient` instance's ``X2GoClientSettings*`` object. Use this method for object retrieval if you want to modify the »settings« configuration node (e.g. in ~/.x2goclient with the FILE backend) from within your Python X2Go based application. :returns: a ``X2GoClientSettings*`` instance :rtype: ``obj`` """ return self.client_settings __get_client_settings = get_client_settings def get_client_printing(self): """\ Returns the :class:`x2go.client.X2GoClient` instance's ``X2GoClientPrinting*`` object. Use this method for object retrieval if you want to modify the printing configuration node (e.g. in ~/.x2goclient with the FILE backend) from within your Python X2Go based application. :returns: a ``X2GoClientPrinting*`` instance :rtype: ``bool`` """ return self.client_printing __get_client_printing = get_client_printing ### ### Session profile oriented methods ### def get_profile_config(self, profile_id_or_name, parameter=None): """\ Returns a dictionary with session options and values that represent the session profile for ``profile_id_or_name``. :param profile_id_or_name: name or id of an X2Go session profile as found in the sessions configuration file :type profile_id_or_name: ``str`` :param parameter: if specified, only the value for the given parameter is returned (Default value = None) :type parameter: ``str`` :returns: a Python dictionary with session profile options :rtype: ``dict`` or ``bool``, ``int``, ``str`` """ return self.session_profiles.get_profile_config(profile_id_or_name, parameter=parameter) __get_profile_config = get_profile_config with_profile_config = get_profile_config def set_profile_config(self, profile_id_or_name, parameter, value): """\ Set individual session profile parameters for session profile ``profile_id_or_name``. :param profile_id_or_name: name or id of an X2Go session profile as found in the sessions configuration file :type profile_id_or_name: ``str`` :param parameter: set this parameter with the given ``value`` :type parameter: ``str`` :param value: set this value for the given ``parameter`` :type value: ``bool``, ``int``, ``str``, ``list`` or ``dict`` :returns: returns ``True`` if this operation has been successful :rtype: ``dict`` """ self.session_profiles.update_value(profile_id_or_name, parameter, value) self.session_profiles.write_user_config = True self.session_profiles.write() __set_profile_config = set_profile_config def to_profile_id(self, profile_name): """\ Retrieve the session profile ID of the session whose profile name is ``profile_name`` :param profile_name: the session profile name :type profile_name: ``str`` :returns: the session profile's ID :rtype: ``str`` """ return self.session_profiles.to_profile_id(profile_name) __to_profile_id = to_profile_id def to_profile_name(self, profile_id): """\ Retrieve the session profile name of the session whose profile ID is ``profile_id`` :param profile_id: the session profile ID :type profile_id: ``str`` :returns: the session profile's name :rtype: ``str`` """ return self.session_profiles.to_profile_name(profile_id) __to_profile_name = to_profile_name def get_profile_metatype(self, profile_name): """\ Evaluate a session profile and return a human readable meta type (classification) for the session profile ``profile_name``. :param profile_name: a profile name :type profile_name: ``str`` :returns: the profile's meta type :rtype: ``str`` """ return self.session_profiles.get_profile_metatype(profile_name) __get_profile_metatype = get_profile_metatype def client_connected_profiles(self, return_profile_names=False): """\ Retrieve a list of session profiles that are currently connected to an X2Go server. :param return_profile_names: return as list of session profile names (Default value = False) :type return_profile_names: ``bool`` :returns: a list of profile names or IDs :rtype: ``list`` """ if return_profile_names: return [ self.to_profile_name(p_id) for p_id in self.session_registry.connected_profiles() ] else: return self.session_registry.connected_profiles() __client_connected_profiles = client_connected_profiles def disconnect_profile(self, profile_name): """\ Disconnect all :class:`x2go.session.X2GoSession` instances that relate to ``profile_name`` by closing down their Paramiko/SSH Transport thread. :param profile_name: the X2Go session profile name :type profile_name: ``str`` :returns: a return value :rtype: ``bool`` """ _retval = False _session_uuid_list = [] # disconnect individual sessions and make a list of session UUIDs for later cleanup (s. below) for s in self.session_registry.registered_sessions_of_profile_name(profile_name, return_objects=True): _session_uuid_list.append(s.get_uuid()) _retval = s.disconnect() | _retval # tell session registry to forget attached sessions completely on disconnect action for uuid in _session_uuid_list: self.session_registry.forget(uuid) # clear cache, as well... if self.use_listsessions_cache: self.listsessions_cache.delete(profile_name) return _retval __disconnect_profile = disconnect_profile def update_sessionregistry_status_by_profile_name(self, profile_name, session_list=None): """\ Update the session registry stati by profile name. :param profile_name: the X2Go session profile name :type profile_name: ``str`` :param session_list: a manually passed on list of X2Go sessions (Default value = None) :type session_list: ``X2GoServerList*`` instances """ session_uuids = self.client_registered_sessions_of_profile_name(profile_name, return_objects=False) if session_uuids: if session_list is None: session_list = self._X2GoClient__list_sessions(session_uuids[0], update_sessionregistry=False, register_sessions=False, ) try: self.session_registry.update_status(profile_name=profile_name, session_list=session_list) except x2go_exceptions.X2GoControlSessionException: if self.session_registry(session_uuids[0]).connected: self.HOOK_on_control_session_death(profile_name) self.disconnect_profile(profile_name) __update_sessionregistry_status_by_profile_name = update_sessionregistry_status_by_profile_name def update_sessionregistry_status_by_session_uuid(self, session_uuid): """\ Update the session registry status of a specific :class:`x2go.session.X2GoSession` instance with session identifier . :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` """ session_list = self._X2GoClient__list_sessions(session_uuid, update_sessionregistry=False, register_sessions=False) if session_list: self.session_registry.update_status(session_uuid=session_uuid, session_list=session_list) __update_sessionregistry_status_by_session_uuid = update_sessionregistry_status_by_session_uuid def update_sessionregistry_status_all_profiles(self): """\ Update the session registry stati of all session profiles. """ for profile_name in self.client_connected_profiles(return_profile_names=True): self.__update_sessionregistry_status_by_profile_name(profile_name) __update_sessionregistry_status_all_profiles = update_sessionregistry_status_all_profiles def update_cache_by_profile_name(self, profile_name, cache_types=('sessions'), update_sessions=None, update_desktops=None, update_mounts=None): """\ Update the session list cache by profile name. :param profile_name: the X2Go session profile name :type profile_name: ``str`` :param cache_types: specify what cache type to update (available: ``sessions``, ``desktops``, ``mounts``) (Default value = ('sessions') :type cache_types: ``tuple`` or ``list`` :param update_sessions: instead of giving a list of cache types, plainly say ``True`` here, if you want to update sessions in the session list cache. :type update_sessions: ``bool`` :param update_desktops: instead of giving a list of cache types, plainly say ``True`` here, if you want to update available desktops in the desktop list cache. :type update_desktops: ``bool`` :param update_mounts: instead of giving a list of cache types, plainly say ``True`` here, if you want to update mounted shares in the mount list cache. :type update_mounts: ``bool`` """ if self.listsessions_cache is not None: _update_sessions = ('sessions' in cache_types) or update_sessions _update_desktops = ('desktops' in cache_types) or update_desktops _update_mounts = ('mounts' in cache_types) or update_mounts try: self.listsessions_cache.update(profile_name, update_sessions=_update_sessions, update_desktops=_update_desktops, update_mounts=_update_mounts, ) except x2go_exceptions.X2GoControlSessionException: c_sessions = self.client_connected_sessions_of_profile_name(profile_name, return_objects=True) if len(c_sessions) and c_sessions[0].connected: self.HOOK_on_control_session_death(profile_name) self.disconnect_profile(profile_name) __update_cache_by_profile_name = update_cache_by_profile_name def update_cache_by_session_uuid(self, session_uuid, cache_types=('sessions'), update_sessions=None, update_desktops=None, update_mounts=None): """\ Update the session list cache of a specific :class:`x2go.session.X2GoSession` instance with session identifier . :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :param cache_types: specify what cache type to update (available: ``sessions``, ``desktops``, ``mounts``) (Default value = ('sessions') :type cache_types: ``tuple`` or ``list`` :param update_sessions: instead of giving a list of cache types, plainly say ``True`` here, if you want to update sessions in the session list cache. :type update_sessions: ``bool`` :param update_desktops: instead of giving a list of cache types, plainly say ``True`` here, if you want to update available desktops in the desktop list cache. :type update_desktops: ``bool`` :param update_mounts: instead of giving a list of cache types, plainly say ``True`` here, if you want to update mounted shares in the mount list cache. :type update_mounts: ``bool`` """ profile_name = self.get_session_profile_name(session_uuid) self.__update_cache_by_profile_name(profile_name, cache_types=cache_types, update_sessions=update_sessions, update_desktops=update_desktops, update_mounts=update_mounts, ) __update_cache_by_session_uuid = update_cache_by_session_uuid def update_cache_all_profiles(self, cache_types=('sessions'), update_sessions=None, update_desktops=None, update_mounts=None): """\ Update the session list cache of all session profiles. :param cache_types: specify what cache type to update (available: ``sessions``, ``desktops``, ``mounts``) (Default value = ('sessions') :type cache_types: ``tuple`` or ``list`` :param update_sessions: instead of giving a list of cache types, plainly say ``True`` here, if you want to update sessions in the session list cache. :type update_sessions: ``bool`` :param update_desktops: instead of giving a list of cache types, plainly say ``True`` here, if you want to update available desktops in the desktop list cache. :type update_desktops: ``bool`` :param update_mounts: instead of giving a list of cache types, plainly say ``True`` here, if you want to update mounted shares in the mount list cache. :type update_mounts: ``bool`` """ if self.listsessions_cache is not None: for profile_name in self.client_connected_profiles(return_profile_names=True): self.__update_cache_by_profile_name(profile_name, cache_types=cache_types, update_sessions=update_sessions, update_desktops=update_desktops, update_mounts=update_mounts, ) # remove profiles that are not connected any more from cache object self.listsessions_cache.check_cache() __update_cache_all_profiles = update_cache_all_profiles def register_available_server_sessions_by_profile_name(self, profile_name, re_register=False, skip_pubapp_sessions=False): """\ Register available sessions that are found on the X2Go server the profile of name ``profile_name`` is connected to. :param profile_name: the X2Go session profile name :type profile_name: ``str`` :param re_register: re-register available sessions, needs to be done after session profile changes (Default value = False) :type re_register: ``bool`` :param skip_pubapp_sessions: Do not auto-register published applications sessions. (Default value = False) :type skip_pubapp_sessions: ``bool`` """ if profile_name not in self.client_connected_profiles(return_profile_names=True): return session_list = self._X2GoClient__list_sessions(profile_name=profile_name, update_sessionregistry=False, register_sessions=False, ) try: self.session_registry.register_available_server_sessions(profile_name, session_list=session_list, re_register=re_register, skip_pubapp_sessions=skip_pubapp_sessions) except x2go_exceptions.X2GoControlSessionException as e: c_sessions = self.client_connected_sessions_of_profile_name(profile_name, return_objects=True) if len(c_sessions) and c_sessions[0].connected: self.HOOK_on_control_session_death(profile_name) self.disconnect_profile(profile_name) raise e __register_available_server_sessions_by_profile_name = register_available_server_sessions_by_profile_name def register_available_server_sessions_by_session_uuid(self, session_uuid, skip_pubapp_sessions=False): """\ Register available sessions that are found on the X2Go server that the :class:`x2go.session.X2GoSession` instance with session identifier is connected to. :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :param skip_pubapp_sessions: Do not auto-register published applications sessions. (Default value = False) :type skip_pubapp_sessions: ``bool`` """ profile_name = self.get_session_profile_name(session_uuid) self.__register_available_server_sessions_by_profile_name(profile_name, skip_pubapp_sessions=skip_pubapp_sessions) __register_available_server_sessions_by_session_uuid = register_available_server_sessions_by_session_uuid def register_available_server_sessions_all_profiles(self, skip_pubapp_sessions=False): """\ Register all available sessions found on an X2Go server for each session profile. :param skip_pubapp_sessions: Do not auto-register published applications sessions. (Default value = False) :type skip_pubapp_sessions: ``bool`` """ for profile_name in self.client_connected_profiles(return_profile_names=True): try: self.__register_available_server_sessions_by_profile_name(profile_name, skip_pubapp_sessions=skip_pubapp_sessions) except x2go_exceptions.X2GoSessionRegistryException: pass __register_available_server_sessions_all_profiles = register_available_server_sessions_all_profiles python-x2go-0.6.1.4/x2go/defaults.py0000644000000000000000000004153214470264762014025 0ustar # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. """\ Default variables and values for Python X2Go. """ __NAME__ = 'x2godefaults-pylib' __package__ = 'x2go' __name__ = 'x2go.defaults' import os import paramiko import platform import sys ## ## Common X2Go defaults ## X2GOCLIENT_OS = platform.system() if X2GOCLIENT_OS != 'Windows': import Xlib.display import Xlib.error # handle missing X displays on package build try: X_DISPLAY = Xlib.display.Display() except Xlib.error.DisplayNameError: X_DISPLAY = None except Xlib.error.DisplayConnectionError: X_DISPLAY = None LOCAL_HOME = os.path.normpath(os.path.expanduser('~')) X2GO_SESSIONS_ROOTDIR = '.x2go' X2GO_CLIENT_ROOTDIR = '.x2goclient' X2GO_SSH_ROOTDIR = os.path.join('.x2go','.ssh') # setting OS dependent variables if X2GOCLIENT_OS == "Windows": # on Windows we will use the current directory as ,,ROOTDIR'' which # will normally be the application directory ROOT_DIR = os.path.abspath(os.path.curdir) ETC_DIR = os.path.join(ROOT_DIR, 'etc') import win32api CURRENT_LOCAL_USER = win32api.GetUserName() X2GO_SSH_ROOTDIR = '.ssh' SUPPORTED_SOUND = True SUPPORTED_PRINTING = True SUPPORTED_FOLDERSHARING = True SUPPORTED_MIMEBOX = True SUPPORTED_TELEKINESIS = False elif X2GOCLIENT_OS == "Linux": ROOT_DIR = '/' ETC_DIR = os.path.join(ROOT_DIR, 'etc', 'x2goclient') import getpass CURRENT_LOCAL_USER = getpass.getuser() X2GO_SSH_ROOTDIR = '.ssh' SUPPORTED_SOUND = True SUPPORTED_PRINTING = True SUPPORTED_FOLDERSHARING = True SUPPORTED_MIMEBOX = True SUPPORTED_TELEKINESIS = True elif X2GOCLIENT_OS == "Darwin": ROOT_DIR = '/' ETC_DIR = os.path.join(ROOT_DIR, 'etc', 'x2goclient') import getpass CURRENT_LOCAL_USER = getpass.getuser() X2GO_SSH_ROOTDIR = '.ssh' SUPPORTED_SOUND = True SUPPORTED_PRINTING = True SUPPORTED_FOLDERSHARING = True SUPPORTED_MIMEBOX = True SUPPORTED_TELEKINESIS = False else: import exceptions class OSNotSupportedException(exceptions.Exception): pass raise OSNotSupportedException('Platform %s is not supported' % platform.system()) ## ## backends of Python X2Go ## BACKENDS = { 'X2GoControlSession': { 'default': 'PLAIN', 'PLAIN': 'x2go.backends.control.plain', }, 'X2GoTerminalSession': { 'default': 'PLAIN', 'PLAIN': 'x2go.backends.terminal.plain', }, 'X2GoServerSessionInfo': { 'default': 'PLAIN', 'PLAIN': 'x2go.backends.info.plain', }, 'X2GoServerSessionList': { 'default': 'PLAIN', 'PLAIN': 'x2go.backends.info.plain', }, 'X2GoProxy': { 'default': 'NX3', 'NX3': 'x2go.backends.proxy.nx3', 'KDRIVE': 'x2go.backends.proxy.kdrive', }, 'X2GoSessionProfiles': { 'default': 'FILE', 'FILE': 'x2go.backends.profiles.file', 'GCONF': 'x2go.backends.profiles.gconf', 'HTTPBROKER': 'x2go.backends.profiles.httpbroker', 'SSHBROKER': 'x2go.backends.profiles.sshbroker', }, 'X2GoClientSettings': { 'default': 'FILE', 'FILE': 'x2go.backends.settings.file', 'GCONF': 'x2go.backends.settings.gconf', }, 'X2GoClientPrinting': { 'default': 'FILE', 'FILE': 'x2go.backends.printing.file', 'GCONF': 'x2go.backends.printing.gconf', } } ## ## X2Go Printing ## X2GO_SETTINGS_FILENAME = 'settings' X2GO_SETTINGS_CONFIGFILES = [ os.path.normpath(os.path.join(LOCAL_HOME, X2GO_CLIENT_ROOTDIR, 'settings')), os.path.normpath(os.path.join(ETC_DIR,X2GO_SETTINGS_FILENAME)), ] X2GO_PRINTING_FILENAME = 'printing' X2GO_PRINTING_CONFIGFILES = [ os.path.normpath(os.path.join(LOCAL_HOME, X2GO_CLIENT_ROOTDIR, 'printing')), os.path.normpath(os.path.join(ETC_DIR,X2GO_PRINTING_FILENAME)), ] X2GO_SESSIONPROFILES_FILENAME = 'sessions' X2GO_SESSIONPROFILES_CONFIGFILES = [ os.path.normpath(os.path.join(LOCAL_HOME, X2GO_CLIENT_ROOTDIR, 'sessions')), os.path.normpath(os.path.join(ETC_DIR,X2GO_SESSIONPROFILES_FILENAME)), ] X2GO_XCONFIG_FILENAME = 'xconfig' X2GO_XCONFIG_CONFIGFILES = [ os.path.normpath(os.path.join(LOCAL_HOME, X2GO_CLIENT_ROOTDIR, 'xconfig')), os.path.normpath(os.path.join(ETC_DIR,X2GO_XCONFIG_FILENAME)), ] X2GO_CLIENTSETTINGS_DEFAULTS = { 'LDAP': { 'useldap': False, 'port': 389, 'server': 'localhost', 'port1': 0, 'port2': 0, }, 'General': { # clientport is not needed for Python X2Go 'clientport': 22, 'autoresume': True, }, 'Authorization': { 'newprofile': True, 'suspend': True, 'editprofile': True, 'resume': True }, 'trayicon': { 'enabled': True, 'mintotray': True, 'noclose': True, 'mincon': True, 'maxdiscon': True, }, } X2GO_CLIENTPRINTING_DEFAULTS = { 'General': { # showdialog will result in a print action that allows opening a print dialog box 'showdialog': False, # if true, open a PDF viewer (or save as PDF file). If false, print via CUPS or print command 'pdfview': True, }, 'print': { # If false, print via CUPS. If true, run "command" to process the print job 'startcmd': False, # print command for non-CUPS printing 'command': 'lpr', # ignored in Python X2Go 'stdin': False, # ignored in Python X2Go 'ps': False, }, 'save': { # a path relative to the user's home directory 'folder': 'PDF', }, 'view': { # If General->pdfview is true: # if open is true, the PDF viewer command is executed # if open is false, the incoming print job is saved in ~/PDF folder 'open': True, # command to execute as PDF viewer 'command': 'xdg-open', }, 'CUPS': { # default print queue for CUPS, if print queue does not exist, the default # CUPS queue is detected 'defaultprinter': 'PDF', }, } # enforce printing defaults (when of str type) to be of unicode type if sys.version_info[0] < 3: X2GO_CLIENTPRINTING_DEFAULTS['print']['command'] = u'lpr' X2GO_CLIENTPRINTING_DEFAULTS['save']['folder'] = u'PDF' X2GO_CLIENTPRINTING_DEFAULTS['view']['command'] = u'xdg-open' X2GO_CLIENTPRINTING_DEFAULTS['CUPS']['defaultprinter'] = u'PDF' if X2GOCLIENT_OS == 'Windows': X2GO_CLIENTPRINTING_DEFAULTS['print'].update({'gsprint': os.path.join(os.environ['ProgramFiles'], 'GhostGum', 'gsview', 'gsprint.exe'), }) if X2GOCLIENT_OS == 'Windows': X2GO_CLIENTXCONFIG_DEFAULTS = { 'XServers': { 'known_xservers': ['VcXsrv_development', 'VcXsrv_shipped', 'VcXsrv', 'Xming', 'Cygwin-X', ], }, 'Cygwin-X': { 'display': 'localhost:40', 'last_display': 'localhost:40', 'process_name': 'XWin.exe', 'test_installed': os.path.join(os.environ['SystemDrive'], '\\', 'cygwin', 'bin', 'XWin.exe'), 'run_command': os.path.join(os.environ['SystemDrive'], '\\', 'cygwin', 'bin', 'XWin.exe'), 'parameters': [':40', '-clipboard', '-multiwindow', '-notrayicon', '-nowinkill', '-nounixkill', '-swcursor', ], }, 'VcXsrv': { 'display': 'localhost:40', 'last_display': 'localhost:40', 'process_name': 'vcxsrv.exe', 'test_installed': os.path.join(os.environ['ProgramFiles'], 'VcXsrv', 'vcxsrv.exe'), 'run_command': os.path.join(os.environ['ProgramFiles'], 'VcXsrv', 'vcxsrv.exe'), 'parameters': [':40', '-clipboard', '-multiwindow', '-notrayicon', '-nowinkill', '-nounixkill', '-swcursor', ], }, 'VcXsrv_shipped': { 'display': 'localhost:40', 'last_display': 'localhost:40', 'process_name': 'vcxsrv.exe', 'test_installed': os.path.join(os.getcwd(), 'VcXsrv', 'vcxsrv.exe'), 'run_command': os.path.join(os.getcwd(), 'VcXsrv', 'vcxsrv.exe'), 'parameters': [':40', '-clipboard', '-multiwindow', '-notrayicon', '-nowinkill', '-nounixkill', '-swcursor', ], }, 'VcXsrv_development': { 'display': 'localhost:40', 'last_display': 'localhost:40', 'process_name': 'vcxsrv.exe', 'test_installed': os.path.join(os.getcwd(), '..', 'pyhoca-contrib', 'mswin', 'vcxsrv-mswin', 'VcXsrv-1.15.2.2-xp+vc2013+x2go1_bin', 'vcxsrv.exe'), 'run_command': os.path.join(os.getcwd(), '..', 'pyhoca-contrib', 'mswin', 'vcxsrv-mswin', 'VcXsrv-1.15.2.2-xp+vc2013+x2go1_bin', 'vcxsrv.exe'), 'parameters': [':40', '-clipboard', '-multiwindow', '-notrayicon', '-nowinkill', '-nounixkill', '-swcursor', ], }, 'Xming': { 'display': 'localhost:40', 'last_display': 'localhost:40', 'process_name': 'Xming.exe', 'test_installed': os.path.join(os.environ['ProgramFiles'], 'Xming', 'Xming.exe'), 'run_command': os.path.join(os.environ['ProgramFiles'], 'Xming', 'Xming.exe'), 'parameters': [':40', '-clipboard', '-multiwindow', '-notrayicon', '-nowinkill', '-nounixkill', '-swcursor', ], }, } else: # make the variable available when building API documentation with epydoc X2GO_CLIENTXCONFIG_DEFAULTS = {} X2GO_GENERIC_APPLICATIONS = [ 'WWWBROWSER', 'MAILCLIENT', 'OFFICE', 'TERMINAL', ] """X2Go's generic applications.""" X2GO_SESSIONPROFILE_DEFAULTS = { 'autologin': True, 'autoconnect': False, 'autostart': False, 'setsessiontitle': False, 'sessiontitle': "", 'speed': 2, 'pack': '16m-jpeg', 'quality': 9, 'iconvto': 'UTF-8', 'iconvfrom': 'UTF-8', 'useiconv': False, 'usesshproxy': False, 'sshproxyhost': 'proxyhost.mydomain', 'sshproxyport': 22, 'sshproxyuser': '', 'sshproxykeyfile': '', 'sshproxytype': 'SSH', 'sshproxysameuser': False, 'sshproxysamepass': False, 'sshproxyautologin': True, 'uniquehostkeyaliases': False, 'useexports': True, 'restoreexports': False, 'fstunnel': True, 'export': {}, 'usemimebox': False, 'mimeboxextensions': '', 'mimeboxaction': 'OPEN', 'fullscreen': False, 'clipboard': 'both', 'width': 800,'height': 600, 'maxdim': False, 'dpi': 96, 'setdpi': False, 'xinerama': False, 'multidisp': False, 'display': 1, 'usekbd': True, 'layout': 'us', 'type': 'pc105/us', 'variant': '', 'sound': False, 'soundsystem': 'pulse', 'startsoundsystem': False, 'soundtunnel':True, 'defsndport':True, 'sndport':4713, 'name': 'NEW_PROFILE', 'icon': ':icons/128x128/x2gosession.png', 'host': ['server.mydomain'], 'user': CURRENT_LOCAL_USER, 'key': '', 'sshport': 22, 'krblogin': False, 'forwardsshagent': False, 'rootless': True, 'applications': X2GO_GENERIC_APPLICATIONS, 'command':'TERMINAL', 'published': False, 'directrdp': False, 'directrdpsettings': '', 'rdpclient': 'rdesktop', 'rdpport': 3389, 'rdpoptions': '-u X2GO_USER -p X2GO_PASSWORD', 'rdpserver': '', 'print': False, 'xdmcpserver': 'localhost', 'kdrive': False, } """:class:`x2go.backends.profiles.base.X2GoSessionProfiles` default values to fill a new session profile with.""" ## ## X2Go Proxy defaults ## # here is a list of NX 3.x compression methods, this is the "%"-hashed list that # can also be used for printing in help texts, docs etc. # The "%"-sign can be replaced by digits 0-9. pack_methods_nx3_noqual = ['nopack','8','64','256','512','4k','32k','64k','256k','2m','16m', '256-rdp','256-rdp-compressed','32k-rdp','32k-rdp-compressed','64k-rdp', '64k-rdp-compressed','16m-rdp','16m-rdp-compressed', 'rfb-hextile','rfb-tight','rfb-tight-compressed', '8-tight','64-tight','256-tight','512-tight','4k-tight','32k-tight', '64k-tight','256k-tight','2m-tight','16m-tight', '8-jpeg-%','64-jpeg','256-jpeg','512-jpeg','4k-jpeg','32k-jpeg', '64k-jpeg','256k-jpeg','2m-jpeg','16m-jpeg-%', '8-png-jpeg-%','64-png-jpeg','256-png-jpeg','512-png-jpeg','4k-png-jpeg', '32k-png-jpeg','64k-png-jpeg','256k-png-jpeg','2m-png-jpeg','16m-png-jpeg-%', '8-png-%','64-png','256-png','512-png','4k-png', '32k-png','64k-png','256k-png','2m-png','16m-png-%', '16m-rgb-%','16m-rle-%',] """Available NX3 compression methods.""" # use for printing on screen... pack_methods_nx3_formatted=""" \'%s\' \'%s\' \'%s\' \'%s\' \'%s\' \'%s\' \'%s\' \'%s\' \'%s\' \'%s\' \'%s\' \'%s\' \'%s\' """ % ('\', \''.join(pack_methods_nx3_noqual[0:11]), \ '\', \''.join(pack_methods_nx3_noqual[11:16]), \ '\', \''.join(pack_methods_nx3_noqual[16:19]), \ '\', \''.join(pack_methods_nx3_noqual[19:22]), \ '\', \''.join(pack_methods_nx3_noqual[22:28]), \ '\', \''.join(pack_methods_nx3_noqual[28:32]), \ '\', \''.join(pack_methods_nx3_noqual[32:38]), \ '\', \''.join(pack_methods_nx3_noqual[38:42]), \ '\', \''.join(pack_methods_nx3_noqual[42:47]), \ '\', \''.join(pack_methods_nx3_noqual[47:52]), \ '\', \''.join(pack_methods_nx3_noqual[52:57]), \ '\', \''.join(pack_methods_nx3_noqual[57:62]), \ '\', \''.join(pack_methods_nx3_noqual[62:])) # pack_methods_nx3 is the complete list of NX3 pack methods that can be used to check options # against pack_methods_nx3 = [ m for m in pack_methods_nx3_noqual if "%" not in m ] for meth in [ m for m in pack_methods_nx3_noqual if "%" in m ]: pack_methods_nx3 += [ meth.replace('%','%s' % str(i)) for i in range(0,10) ] pack_methods_nx3.sort() ## ## X2Go session defaults ## X2GO_DESKTOPSESSIONS_NX3_PREFERRED={ 'IceWM': 'icewm', 'KDE': 'startkde', 'MATE': 'mate-session', 'XFCE': 'xfce4-session', 'LXDE': 'startlxde', 'LXQt': 'startlxqt', 'TRINITY': 'starttrinity', } """A dictionary with meta-commands for X2Go's window manager sessions, usage of NXv3 preferred.""" X2GO_DESKTOPSESSIONS_KDRIVE_PREFERRED={ 'CINNAMON': 'cinnamon-session-cinnamon2d', 'GNOME': 'gnome-session', 'UNITY': 'unity', } """A dictionary with meta-commands for X2Go's window manager sessions, usage of X2GoKDrive preferred.""" X2GO_DESKTOPSESSIONS={} """A dictionary with meta-commands for X2Go's window manager sessions. Will be populated by the concatenation of ``X2GO_DESKTOPSESSIONS__PREFERRED``.""" X2GO_DESKTOPSESSIONS.update(X2GO_DESKTOPSESSIONS_NX3_PREFERRED) X2GO_DESKTOPSESSIONS.update(X2GO_DESKTOPSESSIONS_KDRIVE_PREFERRED) ## ## X2Go SFTP server defaults ## RSAKEY_STRENGTH = 1024 RSAHostKey = paramiko.RSAKey.generate(RSAKEY_STRENGTH) """\ An RSA host key for this client session. Python X2Go does not use the system's host key but generates its own host key for each running application instance. """ X2GO_PRINT_ACTIONS = { 'PDFVIEW': 'X2GoPrintActionPDFVIEW', 'PDFSAVE': 'X2GoPrintActionPDFSAVE', 'PRINT': 'X2GoPrintActionPRINT', 'PRINTCMD': 'X2GoPrintActionPRINTCMD', 'DIALOG': 'X2GoPrintActionDIALOG', } """Relating print action names and classes.""" DEFAULT_PDFVIEW_CMD = 'xdg-open' """Default PDF viewer command for Linux systems (PDFVIEW print action).""" DEFAULT_PDFSAVE_LOCATION = 'PDF' """Default location for saving PDF files (PDFSAVE print action).""" DEFAULT_PRINTCMD_CMD = 'lpr' """Default command for the PRINTCMD print action.""" X2GO_MIMEBOX_ACTIONS = { 'OPEN': 'X2GoMIMEboxActionOPEN', 'OPENWITH': 'X2GoMIMEboxActionOPENWITH', 'SAVEAS': 'X2GoMIMEboxActionSAVEAS', } """Relating MIME box action names and classes.""" X2GO_MIMEBOX_EXTENSIONS_BLACKLIST = [ 'LOCK', 'SYS', 'SWP', 'EXE', 'COM', 'CMD', 'PS1', 'PS2', 'BAT', 'JS', 'PY', 'PL', 'SH', ] """Black-listed MIME box file extenstions.""" # X2Go desktop sharing X2GO_SHARE_VIEWONLY=0 """Constant representing read-only access to shared desktops.""" X2GO_SHARE_FULLACCESS=1 """Constant representing read-write (full) access to shared desktops.""" PUBAPP_MAX_NO_SUBMENUS=10 """Less than ten applications will not get rendered into submenus.""" python-x2go-0.6.1.4/x2go/forward.py0000644000000000000000000002713114470264762013661 0ustar # Copyright (C) 2010-2023 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. """\ Python Gevent based port forwarding server (openssh -L option) for the proxying of graphical X2Go elements. """ __NAME__ = "x2gofwtunnel-pylib" __package__ = 'x2go' __name__ = 'x2go.forward' # modules import copy # gevent/greenlet import gevent from gevent import select, socket from gevent.server import StreamServer # Python X2Go modules from . import log from .defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS class X2GoFwServer(StreamServer): """\ :class:`x2go.forward.X2GoFwServer` implements a gevent's StreamServer based Paramiko/SSH port forwarding server. An :class:`x2go.forward.X2GoFwServer` class object is used to tunnel graphical trafic through an external proxy command launched by a ``X2GoProxy*`` backend. """ def __init__ (self, listener, remote_host, remote_port, ssh_transport, session_instance=None, session_name=None, subsystem=None, logger=None, loglevel=log.loglevel_DEFAULT,): """\ :param listener: listen on TCP/IP socket ``(, )`` :type listener: ``tuple`` :param remote_host: hostname or IP of remote host (in case of X2Go mostly 127.0.0.1) :type remote_host: ``str`` :param remote_port: port of remote host :type remote_port: ``int`` :param ssh_transport: a valid Paramiko/SSH transport object :type ssh_transport: ``obj`` :param session_instance: the complete :class:`x2go.session.X2GoSession` instance of the X2Go session this port forwarding server belongs to. Note: for new :class:`x2go.session.X2GoSession` instances the object has the session name not yet set(!!!) :type session_instance: ``obj`` :param session_name: the session name of the X2Go session this port forwarding server belongs to :type session_name: ``str`` :param logger: you can pass an :class:`x2go.log.X2GoLogger` object to the :class:`x2go.forward.X2GoFwServer` constructor :type logger: ``obj`` :param loglevel: if no :class:`x2go.log.X2GoLogger` object has been supplied a new one will be constructed with the given loglevel :type loglevel: ``int`` """ if logger is None: self.logger = log.X2GoLogger(loglevel=loglevel) else: self.logger = copy.deepcopy(logger) self.logger.tag = __NAME__ self.chan = None self.is_active = False self.failed = False self.keepalive = None self.listener = listener self.chain_host = remote_host self.chain_port = remote_port self.ssh_transport = ssh_transport self.session_name = session_name self.session_instance = session_instance self.subsystem = subsystem self.fw_socket = None StreamServer.__init__(self, self.listener, self.x2go_forward_tunnel_handle) def start(self): self.keepalive = True return StreamServer.start(self) def x2go_forward_tunnel_handle(self, fw_socket, address): """\ Handle for SSH/Paramiko forwarding tunnel. :param fw_socket: local end of the forwarding tunnel :type fw_socket: ``obj`` :param address: unused/ignored :type address: ``tuple`` """ self.fw_socket = fw_socket # disable Nagle algorithm in TCP/IP protocol self.fw_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) _success = False _count = 0 _maxwait = 20 while not _success and _count < _maxwait and self.keepalive: _count += 1 try: self.chan = self.ssh_transport.open_channel('direct-tcpip', (self.chain_host, self.chain_port), self.fw_socket.getpeername()) chan_peername = self.chan.getpeername() _success = True except Exception as e: if self.keepalive: self.logger('incoming request to %s:%d failed on attempt %d of %d: %s' % (self.chain_host, self.chain_port, _count, _maxwait, repr(e)), loglevel=log.loglevel_WARN) gevent.sleep(.4) if not _success: if self.keepalive: self.logger('incoming request to %s:%d failed after %d attempts' % (self.chain_host, self.chain_port, _count), loglevel=log.loglevel_ERROR) if self.session_instance: self.session_instance.set_session_name(self.session_name) self.session_instance.HOOK_forwarding_tunnel_setup_failed(chain_host=self.chain_host, chain_port=self.chain_port, subsystem=self.subsystem) self.failed = True else: self.logger('connected! Tunnel open %r -> %r (on master connection %r -> %r)' % ( self.listener, (self.chain_host, self.chain_port), self.fw_socket.getpeername(), chan_peername), loglevel=log.loglevel_INFO) # once we are here, we can presume the tunnel to be active... self.is_active = True try: while self.keepalive: r, w, x = select.select([self.fw_socket, self.chan], [], []) if fw_socket in r: data = fw_socket.recv(1024) if len(data) == 0: break self.chan.send(data) if self.chan in r: data = self.chan.recv(1024) if len(data) == 0: break fw_socket.send(data) self.close_channel() self.close_socket() except (socket.error, EOFError): pass self.logger('Tunnel closed from %r' % (chan_peername,), loglevel=log.loglevel_INFO) def close_channel(self): """\ Close an open channel again. """ #if self.chan is not None and _X2GOCLIENT_OS != "Windows": if self.chan is not None: try: if _X2GOCLIENT_OS != 'Windows': self.chan.close() self.chan = None except EOFError: pass def close_socket(self): """\ Close the forwarding tunnel's socket again. """ _success = False _count = 0 _maxwait = 20 # try at least <_maxwait> times while not _success and _count < _maxwait: _count += 1 try: self.close_channel() if self.fw_socket is not None: self.fw_socket.close() _success = True except socket.error: gevent.sleep(.2) self.logger('could not close fw_tunnel socket, try again (%s of %s)' % (_count, _maxwait), loglevel=log.loglevel_WARN) if _count >= _maxwait: self.logger('forwarding tunnel to [%s]:%d could not be closed properly' % (self.chain_host, self.chain_port), loglevel=log.loglevel_WARN) def stop(self): """\ Stop the forwarding tunnel. """ self.is_active = False self.close_socket() StreamServer.stop(self) def start_forward_tunnel(local_host='127.0.0.1', local_port=22022, remote_host='127.0.0.1', remote_port=22, ssh_transport=None, session_instance=None, session_name=None, subsystem=None, logger=None, ): """\ Setup up a Paramiko/SSH port forwarding tunnel (like openssh -L option). The tunnel is used to transport X2Go graphics data through a proxy application like nxproxy. :param local_host: local starting point of the forwarding tunnel (Default value = '127.0.0.1') :type local_host: ``int`` :param local_port: listen port of the local starting point (Default value = 22022) :type local_port: ``int`` :param remote_host: from the endpoint of the tunnel, connect to host ````... (Default value = '127.0.0.1') :type remote_host: ``str`` :param remote_port: on port ```` (Default value = 22) :type remote_port: ``int`` :param ssh_transport: the Paramiko/SSH transport (i.e. the X2Go session's Paramiko/SSH transport object) (Default value = None) :type ssh_transport: ``obj`` :param session_instance: the :class:`x2go.session.X2GoSession` instance that initiates this tunnel (Default value = None) :type session_instance: ``obj`` :param session_name: the session name of the X2Go session this port forwarding server belongs to (Default value = None) :type session_name: ``str`` :param subsystem: a custom string with a component name that tries to evoke a new tunnel setup (Default value = None) :type subsystem: ``str`` :param logger: an X2GoLogger object (Default value = None) :type logger: ``obj`` :returns: returns an :class:`x2go.forward.X2GoFwServer` instance :rtype: ``obj`` """ fw_server = X2GoFwServer(listener=(local_host, local_port), remote_host=remote_host, remote_port=remote_port, ssh_transport=ssh_transport, session_instance=session_instance, session_name=session_name, subsystem=subsystem, logger=logger, ) try: fw_server.start() except socket.error: fw_server.failed = True fw_server.is_active = False return fw_server def stop_forward_tunnel(fw_server): """\ Tear down a given Paramiko/SSH port forwarding tunnel. :param fw_server: an :class:`x2go.forward.X2GoFwServer` instance as returned by the :func:`start_forward_tunnel()` function :type fw_server: ``obj`` """ if fw_server is not None: fw_server.keepalive = False gevent.sleep(.5) fw_server.stop() if __name__ == '__main__': pass python-x2go-0.6.1.4/x2go/gevent_subprocess.py0000644000000000000000000001640314470264762015755 0ustar # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. # gevent_subprocess was found here: http://groups.google.com/group/gevent/browse_thread/thread/dba1a5d29e0a60ff # Mark Visser ### ### Mark Visser, Sat, 20 Nov 2010 13:30:16 -0500 ### ### ### Hi Mike, ### ### I hereby place that code snippet in the public domain, feel free to apply any license that is appropriate! ### ### cheers, ### -Mark ### ### ### Thus, I place myself as the copyright holder for code in this file ### for cases in that it is used in context of the X2Go project. ### __package__ = 'x2go' __name__ = 'x2go.gevent_subprocess' """Implementation of the standard :mod:`subprocess` module that spawns greenlets""" import errno import sys import fcntl, os _subprocess = __import__('subprocess') from gevent import socket, select, hub # identical to original CalledProcessError = _subprocess.CalledProcessError PIPE = _subprocess.PIPE STDOUT = _subprocess.STDOUT call = _subprocess.call check_call = _subprocess.check_call list2cmdline = _subprocess.list2cmdline class Popen(object): def __init__(self, *args, **kwargs): # delegate to an actual Popen object self.__p = _subprocess.Popen(*args, **kwargs) # make the file handles nonblocking if self.stdin is not None: fcntl.fcntl(self.stdin, fcntl.F_SETFL, os.O_NONBLOCK) if self.stdout is not None: fcntl.fcntl(self.stdout, fcntl.F_SETFL, os.O_NONBLOCK) if self.stderr is not None: fcntl.fcntl(self.stderr, fcntl.F_SETFL, os.O_NONBLOCK) def __getattr__(self, name): # delegate attribute lookup to the real Popen object return getattr(self.__p, name) def _write_pipe(self, f, input): # writes the given input to f without blocking if input: bytes_total = len(input) bytes_written = 0 while bytes_written < bytes_total: try: # f.write() doesn't return anything, so use os.write. bytes_written += os.write(f.fileno(), input[bytes_written:]) except IOError as ex: if ex[0] != errno.EAGAIN: raise sys.exc_clear() socket.wait_write(f.fileno()) f.close() def _read_pipe(self, f): # reads output from f without blocking # returns output chunks = [] while True: try: chunk = f.read(4096) if not chunk: break chunks.append(chunk) except IOError as ex: if ex[0] != errno.EAGAIN: raise sys.exc_clear() socket.wait_read(f.fileno()) f.close() return ''.join(chunks) def communicate(self, input=None): # Optimization: If we are only using one pipe, or no pipe at # all, using select() is unnecessary. if [self.stdin, self.stdout, self.stderr].count(None) >= 2: stdout = None stderr = None if self.stdin: self._write_pipe(self.stdin, input) elif self.stdout: stdout = self._read_pipe(self.stdout) elif self.stderr: stderr = self._read_pipe(self.stderr) self.wait() return (stdout, stderr) else: return self._communicate(input) def _communicate(self, input): # identical to original... all the heavy lifting is done # in gevent.select.select read_set = [] write_set = [] stdout = None # Return stderr = None # Return if self.stdin: # Flush stdin buffer. self.stdin.flush() if input: write_set.append(self.stdin) else: self.stdin.close() if self.stdout: read_set.append(self.stdout) stdout = [] if self.stderr: read_set.append(self.stderr) stderr = [] input_offset = 0 while read_set or write_set: try: rlist, wlist, xlist = select.select(read_set, write_set, []) except select.error as e: if e.args[0] == errno.EINTR: continue raise if self.stdin in wlist: # When select has indicated that the file is writable, # we can write up to PIPE_BUF bytes without risk # blocking. POSIX defines PIPE_BUF >= 512 bytes_written = os.write(self.stdin.fileno(), buffer(input, input_offset, 512)) input_offset += bytes_written if input_offset >= len(input): self.stdin.close() write_set.remove(self.stdin) if self.stdout in rlist: data = os.read(self.stdout.fileno(), 1024) if data == "": self.stdout.close() read_set.remove(self.stdout) stdout.append(data) if self.stderr in rlist: data = os.read(self.stderr.fileno(), 1024) if data == "": self.stderr.close() read_set.remove(self.stderr) stderr.append(data) # All data exchanged. Translate lists into strings. if stdout is not None: stdout = ''.join(stdout) if stderr is not None: stderr = ''.join(stderr) # Translate newlines, if requested. We cannot let the file # object do the translation: It is based on stdio, which is # impossible to combine with select (unless forcing no # buffering). if self.universal_newlines and hasattr(file, 'newlines'): if stdout: stdout = self._translate_newlines(stdout) if stderr: stderr = self._translate_newlines(stderr) self.wait() return (stdout, stderr) def wait(self, check_interval=0.01): # non-blocking, use hub.sleep try: while True: status = self.poll() if status >= 0: return status hub.sleep(check_interval) except OSError as e: if e.errno == errno.ECHILD: # no child process, this happens if the child process # already died and has been cleaned up return -1 else: raise python-x2go-0.6.1.4/x2go/guardian.py0000644000000000000000000001552614470264762014014 0ustar # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. """\ X2GoSessionGuardian class - a guardian thread that controls X2Go session threads and their sub-threads (like reverse forwarding tunnels, Paramiko transport threads, etc.). """ __NAME__ = 'x2goguardian-pylib' __package__ = 'x2go' __name__ = 'x2go.guardian' # modules import gevent import threading import copy # Python X2Go modules from .cleanup import x2go_cleanup from . import log class X2GoSessionGuardian(threading.Thread): """\ :class:`x2go.guardian.X2GoSessionGuardian` thread controls X2Go session threads and their sub-threads (like reverse forwarding tunnels, Paramiko transport threads, etc.). Its main function is to tidy up once a session gets interrupted (SIGTERM, SIGINT). There is one :class:`x2go.guardian.X2GoSessionGuardian` for each :class:`x2go.client.X2GoClient` instance (thus: for normal setups there should be _one_ :class:`x2go.client.X2GoClient` and _one_ :class:`x2go.guardian.X2GoSessionGuardian` in use). """ def __init__(self, client_instance, auto_update_listsessions_cache=False, auto_update_listdesktops_cache=False, auto_update_listmounts_cache=False, auto_update_sessionregistry=False, auto_register_sessions=False, no_auto_reg_pubapp_sessions=False, refresh_interval=5, logger=None, loglevel=log.loglevel_DEFAULT): """\ :param auto_update_listsessions_cache: let :class:`x2go.guardian.X2GoSessionGuardian` refresh the session list cache for all :class:`x2go.session.X2GoSession` objects :type auto_update_listsessions_cache: ``bool`` :param auto_update_listdesktops_cache: let :class:`x2go.guardian.X2GoSessionGuardian` refresh desktop lists in the session list cache for all :class:`x2go.session.X2GoSession` objects :type auto_update_listdesktops_cache: ``bool`` :param auto_update_listmounts_cache: let :class:`x2go.guardian.X2GoSessionGuardian` refresh mount lists in the session list cache for all :class:`x2go.session.X2GoSession` objects :type auto_update_listmounts_cache: ``bool`` :param auto_update_sessionregistry: if set to ``True`` the session status will be updated in regular intervals :type auto_update_sessionregistry: ``bool`` :param auto_register_sessions: register new sessions automatically once they appear in the X2Go session (e.g. instantiated by another client that is connected to the same X2Go server under same user ID) :type auto_register_sessions: ``bool`` :param no_auto_reg_pubapp_sessions: do not auto-register published applications sessions :type no_auto_reg_pubapp_sessions: ``bool`` :param refresh_interval: refresh cache and session registry every seconds :type refresh_interval: ``int`` :param logger: you can pass an :class:`x2go.log.X2GoLogger` object to the :class:`x2go.guardian.X2GoSessionGuardian` constructor :type logger: ``obj`` :param loglevel: if no :class:`x2go.log.X2GoLogger` object has been supplied a new one will be constructed with the given loglevel :type loglevel: ``int`` """ if logger is None: self.logger = log.X2GoLogger(loglevel=loglevel) else: self.logger = copy.deepcopy(logger) self.logger.tag = __NAME__ self.client_instance = client_instance self.auto_update_listsessions_cache = auto_update_listsessions_cache self.auto_update_listdesktops_cache = auto_update_listdesktops_cache self.auto_update_listmounts_cache = auto_update_listmounts_cache self.auto_update_sessionregistry = auto_update_sessionregistry self.auto_register_sessions = auto_register_sessions self.no_auto_reg_pubapp_sessions = no_auto_reg_pubapp_sessions self.refresh_interval = refresh_interval threading.Thread.__init__(self, target=self.guardian) self.daemon = True self.start() def guardian(self): """\ The handler of this :class:`x2go.guardian.X2GoSessionGuardian` thread. """ seconds = 0 self._keepalive = True while self._keepalive: gevent.sleep(1) seconds += 1 if seconds % self.refresh_interval == 0: self.logger('Entering X2Go Guardian client management loop...', loglevel=log.loglevel_DEBUG) if self.auto_update_listsessions_cache: self.client_instance.update_cache_all_profiles(update_sessions=self.auto_update_listsessions_cache, update_desktops=self.auto_update_listdesktops_cache, update_mounts=self.auto_update_listmounts_cache, ) if self.auto_update_sessionregistry and not self.auto_register_sessions: self.client_instance.update_sessionregistry_status_all_profiles() # session auto-registration will automatically trigger an update of the session registry status if self.auto_register_sessions: self.client_instance.register_available_server_sessions_all_profiles(skip_pubapp_sessions=self.no_auto_reg_pubapp_sessions) self.logger('X2Go session guardian thread waking up after %s seconds' % seconds, loglevel=log.loglevel_DEBUG) for session_uuid in list(self.client_instance.session_registry.keys()): session_summary = self.client_instance.get_session_summary(session_uuid) self.logger('calling session cleanup on profile %s for terminal session: %s' % (session_summary['profile_name'], session_summary['session_name']), loglevel=log.loglevel_DEBUG) x2go_cleanup(threads=session_summary['active_threads']) def stop_thread(self): """\ Stop this :class:`x2go.guardian.X2GoSessionGuardian` thread. """ self._keepalive = False python-x2go-0.6.1.4/x2go/inifiles.py0000644000000000000000000002446214470264762014023 0ustar # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. # # This code was initially written by: # 2010 Dick Kniep # # Other contributors: # none so far """\ X2GoProcessIniFile - helper class for parsing .ini files """ __NAME__ = 'x2goinifiles-pylib' __package__ = 'x2go' __name__ = 'x2go.inifiles' # modules import os import sys try: import configparser except: import ConfigParser as configparser import types import io import copy # Python X2Go modules from .defaults import LOCAL_HOME as _current_home from . import log from . import utils class X2GoIniFile(object): """\ Base class for processing the different ini files used by X2Go clients. Primarily used to standardize the content of the different X2Go client ini file (settings, printing, sessions, xconfig). If entries are omitted in an ini file, they are filled with default values (as hard coded in Python X2Go), so the resulting objects always contain the same fields. :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 X2Go's hard coded defaults in :mod:`x2go.defaults` :type defaults: ``dict`` :param logger: you can pass an :class:`x2go.log.X2GoLogger` object to the :class:`x2go.inifiles.X2GoIniFile` constructor :type logger: :class:`x2go.log.X2GoLogger` instance :param loglevel: if no :class:`x2go.log.X2GoLogger` object has been supplied a new one will be constructed with the given loglevel :type loglevel: ``int`` """ def __init__(self, config_files, defaults=None, logger=None, loglevel=log.loglevel_DEFAULT): self.user_config_file = None self._write_user_config = True # make sure a None type gets turned into list type if not config_files: config_files = [] if logger is None: self.logger = log.X2GoLogger(loglevel=loglevel) else: self.logger = copy.deepcopy(logger) self.logger.tag = __NAME__ self.config_files = config_files if utils._checkIniFileDefaults(defaults): self.defaultValues = defaults else: self.defaultValues = {} # we purposefully do not inherit the SafeConfigParser class # here as we do not want to run into name conflicts between # X2Go ini file options and method / property names in # SafeConfigParser... This is a pre-cautious approach... self.iniConfig = configparser.SafeConfigParser() self.iniConfig.optionxform = str _create_file = False for file_name in self.config_files: file_name = os.path.normpath(file_name) if file_name.startswith(_current_home): if not os.path.exists(file_name): utils.touch_file(file_name) _create_file = True self.user_config_file = file_name break self.load() if _create_file: self._write_user_config = True self._X2GoIniFile__write() def load(self): """\ R(e-r)ead configuration file(s). """ self.logger('proposed config files are %s' % self.config_files, loglevel=log.loglevel_INFO, ) _found_config_files = self.iniConfig.read(self.config_files) self.logger('config files found: %s' % _found_config_files or 'none', loglevel=log.loglevel_INFO, ) for file_name in _found_config_files: if file_name.startswith(os.path.normpath(_current_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 __repr__(self): result = 'X2GoIniFile(' 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 _storeValue(self, section, key, value): """\ Stores a value for a given section and key. This methods affects a SafeConfigParser object held in RAM. No configuration file is affected by this method. To write the configuration to disk use the :func:`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``, ``booAl``, ... """ 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)) else: self.iniConfig.set(section, key, str(value)) def _fill_defaults(self): """\ Fills a ``SafeConfigParser`` object with the default ini file values as pre-defined in Python X2Go or. This SafeConfigParser object is held in RAM. No configuration file is affected by this method. """ for section, sectionvalue in list(self.defaultValues.items()): for key, value in list(sectionvalue.items()): if self.iniConfig.has_option(section, key): continue if not self.iniConfig.has_section(section): self.iniConfig.add_section(section) 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 __update_value = update_value def write(self): """\ Write the ini file modifications (SafeConfigParser object) from RAM to disk. For writing the first of the ``config_files`` specified on instance construction that is writable will be used. :returns: ``True`` if the user config file has been successfully written, ``False`` otherwise. :rtype: ``bool`` """ if self.user_config_file and self._write_user_config: try: if sys.version_info[0] >= 3: fd = open(self.user_config_file.encode(), 'w') else: fd = open(self.user_config_file.encode(), 'wb') self.iniConfig.write(fd) fd.close() self._write_user_config = False return True except Exception as e: self.logger('failure during write operation of %s; reported error: %s' % (self.user_config_file, e), loglevel=log.loglevel_ERROR, ) return False __write = write 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 """ return type(self.defaultValues[section][key]) 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`` :param key_type: Python data type of the given key (Default value = None) :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): if key_type is bool: return self.iniConfig.getboolean(section, key) elif key_type is int: return self.iniConfig.getint(section, key) 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 @property def printable_config_file(self): """\ Returns a printable configuration file as a multi-line string. """ if sys.version_info[0] >= 3: stdout = io.BytesIO() else: stdout = io.StringIO() self.iniConfig.write(stdout) _ret_val = stdout.getvalue() stdout.close() return _ret_val python-x2go-0.6.1.4/x2go/__init__.py0000644000000000000000000000454714470264762013762 0ustar # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. __NAME__ = 'python-x2go' __VERSION__ = '0.6.1.4' __AUTHOR__ = 'Mike Gabriel' __package__ = 'x2go' __name__ = 'x2go' import os from .defaults import X2GOCLIENT_OS if X2GOCLIENT_OS != 'Windows': # in Debian unstable, the ares resolver is broken, see # https://bugs.debian.org/908144 #os.environ["GEVENT_RESOLVER"] = "ares" # we could use the dnspython resolver... #os.environ["GEVENT_RESOLVER"] = "dnspython" # however, this is unsupported in Debian 9, so we do nothing here... pass from gevent import monkey monkey.patch_all() from . import utils from .client import X2GoClient from .backends.profiles.file import X2GoSessionProfiles from .backends.printing.file import X2GoClientPrinting from .backends.settings.file import X2GoClientSettings from .session import X2GoSession from .sshproxy import X2GoSSHProxy from .x2go_exceptions import * from .log import * from .cleanup import x2go_cleanup from .defaults import CURRENT_LOCAL_USER from .defaults import LOCAL_HOME from .defaults import X2GO_CLIENT_ROOTDIR from .defaults import X2GO_SESSIONS_ROOTDIR from .defaults import X2GO_SSH_ROOTDIR from .defaults import BACKENDS if X2GOCLIENT_OS == 'Windows': from .xserver import X2GoClientXConfig, X2GoXServer # compat section X2goClient = X2GoClient X2goSessionProfiles = X2GoSessionProfiles X2goClientPrinting = X2GoClientPrinting X2goClientSettings = X2GoClientSettings X2goSession = X2GoSession X2goSSHProxy = X2GoSSHProxy if X2GOCLIENT_OS == 'Windows': X2goClientXConfig = X2GoClientXConfig X2goXServer = X2GoXServer python-x2go-0.6.1.4/x2go/log.py0000644000000000000000000001243414470264762012776 0ustar # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. """\ X2GoLogger class - flexible handling of log and debug output. """ __NAME__ = 'x2gologger-pylib' __package__ = 'x2go' __name__ = 'x2go.log' # modules import os import sys import types loglevel_NONE = 0 loglevel_ERROR = 8 loglevel_WARN = 16 loglevel_NOTICE = 32 loglevel_INFO = 64 loglevel_DEBUG = 128 loglevel_DEBUG_SFTPXFER = 1024 loglevel_DEFAULT = loglevel_ERROR | loglevel_WARN | loglevel_NOTICE """\ Default loglevel of X2GoLogger objects is: NOTICE | WARN | ERROR """ # Python X2Go modules from . import utils class X2GoLogger(object): """\ A simple logger class, that is used by all Python X2Go classes. """ name = '' tag = '' progpid = -1 level = -1 destination = sys.stdout _loglevel_NAMES = {8: 'error', 16: 'warn', 32: 'notice', 64: 'info', 128: 'debug', 1024: 'debug-sftpxfer', } def __init__(self, name=sys.argv[0], loglevel=loglevel_DEFAULT, tag=None): """\ :param name: name of the programme that uses Python X2Go :type name: ``str`` :param loglevel: log level for Python X2Go :type loglevel: ``int`` :param tag: additional tag for all log entries :type tag: ``str`` """ self.name = os.path.basename(name) self.tag = tag self.loglevel = loglevel self.progpid = os.getpid() def message(self, msg, loglevel=loglevel_NONE, tag=None): """\ Log a message. :param msg: log message text :type msg: ``str`` :param loglevel: log level of this message (Default value = loglevel_NONE) :type loglevel: ``int`` :param tag: additional tag for this log entry (Default value = None) :type tag: ``str`` """ if tag is None: tag = self.tag if loglevel & self.loglevel: msg = msg.replace('\n', ' ') if sys.version_info[0] < 3: msg = msg.decode(utils.get_encoding()) if self.tag is not None: self.destination.write(u'%s[%s] (%s) %s: %s\n' % (self.name, self.progpid, tag, self._loglevel_NAMES[loglevel].upper(), msg)) else: self.destination.write(u'%s[%s] %s: %s\n' % (self.name, self.progpid, self._loglevel_NAMES[loglevel].upper(), msg)) __call__ = message def get_loglevel(self): """\ Get the current loglevel. :returns: current log level :rtype: ``int`` """ return self.loglevel def set_loglevel(self, loglevel_name='none'): """\ Set log level by name. :param loglevel_name: name of loglevel to be set (Default value = 'none') :type loglevel_name: ``str`` """ if type(loglevel_name) is types.IntegerType: self.loglevel = loglevel_name elif type(loglevel_name) is bytes and loglevel_name in list(self._loglevel_NAMES.values()): _method = getattr(self, 'self.set_loglevel_%s' % loglevel_name) _method() else: self.loglevel = loglevel_DEFAULT def set_loglevel_quiet(self): """\ Silence logging completely. """ self.loglevel = 0 def set_loglevel_error(self): """\ Set log level to *ERROR*. """ self.loglevel = loglevel_ERROR def set_loglevel_warn(self): """\ Set log level to *WARN*. """ self.loglevel = loglevel_ERROR | loglevel_WARN def set_loglevel_notice(self): """\ Set log level to *NOTICE* (default). """ self.loglevel = loglevel_ERROR | loglevel_WARN | loglevel_NOTICE def set_loglevel_info(self): """\ Set log level to *INFO*. """ self.loglevel = loglevel_ERROR | loglevel_WARN | loglevel_NOTICE | loglevel_INFO def set_loglevel_debug(self): """\ Set log level to *DEBUG*. """ self.loglevel = loglevel_ERROR | loglevel_WARN | loglevel_NOTICE | loglevel_INFO | loglevel_DEBUG def enable_debug_sftpxfer(self): """\ Additionally, switch on sFTP data transfer debugging """ self.loglevel = self.loglevel | loglevel_DEBUG_SFTPXFER def disable_debug_sftpxfer(self): """\ Switch off sFTP data transfer debugging. """ self.loglevel = self.loglevel ^ loglevel_DEBUG_SFTPXFER # compat section X2goLogger = X2GoLogger python-x2go-0.6.1.4/x2go/mimeboxactions.py0000644000000000000000000003002514470264762015232 0ustar # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. """\ For MIME box jobs there are currently three handling actions available: :class:`x2go.mimeboxactions.X2GoMIMEboxActionOPEN`, :class:`x2go.mimeboxactions.X2GoMIMEboxActionOPENWITH` and :class:`x2go.mimeboxactions.X2GoMIMEboxActionSAVEAS`. """ __NAME__ = 'x2gomimeboxactions-pylib' __package__ = 'x2go' __name__ = 'x2go.mimeboxactions' # modules import os import copy import time from .defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS if _X2GOCLIENT_OS in ("Windows"): import subprocess import win32api else: from . import gevent_subprocess as subprocess from . import x2go_exceptions WindowsErrror = x2go_exceptions.WindowsError # Python X2Go modules from . import log from . import x2go_exceptions _MIMEBOX_ENV = os.environ.copy() class X2GoMIMEboxAction(object): __name__ = 'NAME' __description__ = 'DESCRIPTION' def __init__(self, client_instance=None, logger=None, loglevel=log.loglevel_DEFAULT): """\ This is a meta class and has no functionality as such. It is used as parent class by »real« X2Go MIME box actions. :param client_instance: the underlying :class:`x2go.client.X2GoClient` instance :type client_instance: ``obj`` :param logger: you can pass an :class:`x2go.log.X2GoLogger` object to the :class:`x2go.mimeboxactions.X2GoMIMEboxAction` constructor :type logger: ``obj`` :param loglevel: if no :class:`x2go.log.X2GoLogger` object has been supplied a new one will be constructed with the given loglevel :type loglevel: ``int`` """ if logger is None: self.logger = log.X2GoLogger(loglevel=loglevel) else: self.logger = copy.deepcopy(logger) self.logger.tag = __NAME__ # these get set from within the X2GoMIMEboxQueue class self.profile_name = 'UNKNOWN' self.session_name = 'UNKNOWN' self.client_instance = client_instance @property def name(self): """\ Return the X2Go MIME box action's name. :returns: MIME box action name :rtype: ``str`` """ return self.__name__ @property def description(self): """\ Return the X2Go MIME box action's description text. :returns: MIME box action's description :rtype: ``str`` """ return self.__description__ def _do_process(self, mimebox_file, mimebox_dir, ): """\ Perform the defined MIME box action (doing nothing in :class:`x2go.mimeboxactions.X2GoMIMEboxAction` parent class). :param mimebox_file: file name as placed in to the X2Go MIME box directory :type mimebox_file: ``str`` :param mimebox_dir: location of the X2Go session's MIME box directory :type mimebox_dir: ``str`` """ pass def do_process(self, mimebox_file, mimebox_dir, ): """\ Wrapper method for the actual processing of MIME box actions. :param mimebox_file: file name as placed in to the X2Go MIME box directory :type mimebox_file: ``str`` :param mimebox_dir: location of the X2Go session's MIME box directory :type mimebox_dir: ``str`` """ mimebox_file = os.path.normpath(mimebox_file) mimebox_dir = os.path.normpath(mimebox_dir) self._do_process(mimebox_file, mimebox_dir) class X2GoMIMEboxActionOPEN(X2GoMIMEboxAction): """\ MIME box action that opens incoming files in the system's default application. """ __name__= 'OPEN' __decription__= 'Open incoming file with local system\'s default application.' def __init__(self, client_instance=None, logger=None, loglevel=log.loglevel_DEFAULT): """\ :param client_instance: the underlying :class:`x2go.client.X2GoClient` instance :type client_instance: ``obj`` :param logger: you can pass an :class:`x2go.log.X2GoLogger` object to the :class:`x2go.mimeboxactions.X2GoMIMEboxActionOPEN` constructor :type logger: ``obj`` :param loglevel: if no :class:`x2go.log.X2GoLogger` object has been supplied a new one will be constructed with the given loglevel :type loglevel: ``int`` """ self.client_instance = client_instance X2GoMIMEboxAction.__init__(self, logger=logger, loglevel=loglevel) def _do_process(self, mimebox_file, mimebox_dir, ): """\ Open an incoming MIME box file in the system's default application. :param mimebox_file: file name as placed in to the MIME box directory :type mimebox_file: ``str`` :param mimebox_dir: location of the X2Go session's MIME box directory :type mimebox_dir: ``str`` """ mimebox_file = os.path.normpath(mimebox_file) mimebox_dir = os.path.normpath(mimebox_dir) if _X2GOCLIENT_OS == "Windows": self.logger('opening incoming MIME box file with Python\'s os.startfile() command: %s' % mimebox_file, loglevel=log.loglevel_DEBUG) try: os.startfile(os.path.join(mimebox_dir, mimebox_file)) except WindowsError as win_err: if self.client_instance: self.client_instance.HOOK_mimeboxaction_error(mimebox_file, profile_name=self.profile_name, session_name=self.session_name, err_msg=str(win_err) ) else: self.logger('Encountered WindowsError: %s' % str(win_err), loglevel=log.loglevel_ERROR) time.sleep(20) else: cmd_line = [ 'xdg-open', os.path.join(mimebox_dir, mimebox_file), ] self.logger('opening MIME box file with command: %s' % ' '.join(cmd_line), loglevel=log.loglevel_DEBUG) subprocess.Popen(cmd_line, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=_MIMEBOX_ENV) time.sleep(20) class X2GoMIMEboxActionOPENWITH(X2GoMIMEboxAction): """\ MIME box action that calls the system's ,,Open with...'' dialog on incoming files. Currently only properly implementable on Windows platforms. """ __name__= 'OPENWITH' __decription__= 'Evoke ,,Open with...\'\' dialog on incoming MIME box files.' def __init__(self, client_instance=None, logger=None, loglevel=log.loglevel_DEFAULT): """\ :param client_instance: the underlying :class:`x2go.client.X2GoClient` instance :type client_instance: ``obj`` :param logger: you can pass an :class:`x2go.log.X2GoLogger` object to the :class:`x2go.mimeboxactions.X2GoMIMEboxActionOPENWITH` constructor :type logger: ``obj`` :param loglevel: if no :class:`x2go.log.X2GoLogger` object has been supplied a new one will be constructed with the given loglevel :type loglevel: ``int`` """ self.client_instance = client_instance X2GoMIMEboxAction.__init__(self, logger=logger, loglevel=loglevel) def _do_process(self, mimebox_file, mimebox_dir, ): """\ Open an incoming MIME box file in the system's default application. :param mimebox_file: file name as placed in to the MIME box directory :type mimebox_file: ``str`` :param mimebox_dir: location of the X2Go session's MIME box directory :type mimebox_dir: ``str`` """ mimebox_file = os.path.normpath(mimebox_file) mimebox_dir = os.path.normpath(mimebox_dir) if _X2GOCLIENT_OS == "Windows": self.logger('evoking Open-with dialog on incoming MIME box file: %s' % mimebox_file, loglevel=log.loglevel_DEBUG) win32api.ShellExecute ( 0, "open", "rundll32.exe", "shell32.dll,OpenAs_RunDLL %s" % os.path.join(mimebox_dir, mimebox_file), None, 0, ) time.sleep(20) else: self.logger('the evocation of the Open-with dialog box is currently not available on Linux, falling back to MIME box action OPEN', loglevel=log.loglevel_WARN) cmd_line = [ 'xdg-open', os.path.join(mimebox_dir, mimebox_file), ] self.logger('opening MIME box file with command: %s' % ' '.join(cmd_line), loglevel=log.loglevel_DEBUG) subprocess.Popen(cmd_line, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=_MIMEBOX_ENV) time.sleep(20) class X2GoMIMEboxActionSAVEAS(X2GoMIMEboxAction): """\ MIME box action that allows saving incoming MIME box files to a local folder. What this MIME box actually does is calling a hook method in the :class:`x2go.client.X2GoClient` instance that can be hi-jacked by one of your application's methods which then can handle the ,,Save as...'' request. """ __name__ = 'SAVEAS' __decription__= 'Save incoming file as...' def __init__(self, client_instance=None, logger=None, loglevel=log.loglevel_DEFAULT): """\ :param client_instance: an :class:`x2go.client.X2GoClient` instance, within your customized :class:`x2go.client.X2GoClient` make sure you have a ``HOOK_open_mimebox_saveas_dialog(filename=)`` method defined that will actually handle the incoming mimebox file. :type client_instance: ``obj`` :param logger: you can pass an :class:`x2go.log.X2GoLogger` object to the :class:`x2go.mimeboxactions.X2GoMIMEboxActionSAVEAS` constructor :type logger: ``obj`` :param loglevel: if no :class:`x2go.log.X2GoLogger` object has been supplied a new one will be constructed with the given loglevel :type loglevel: ``int`` :raises X2GoMIMEboxActionException: if the client_instance has not been passed to the SAVEAS MIME box action """ if client_instance is None: raise x2go_exceptions.X2GoMIMEboxActionException('the SAVEAS MIME box action needs to know the X2GoClient instance (client=)') X2GoMIMEboxAction.__init__(self, client_instance=client_instance, logger=logger, loglevel=loglevel) def _do_process(self, mimebox_file, mimebox_dir): """\ Call an :class:`x2go.client.X2GoClient` hook method (``HOOK_open_mimebox_saveas_dialog``) that can handle the MIME box's SAVEAS action. :param mimebox_file: file name as placed in to the MIME box directory :type mimebox_file: ``str`` :param mimebox_dir: location of the X2Go session's MIME box directory :type mimebox_dir: ``str`` :param mimebox_file: PDF file name as placed in to the X2Go spool directory """ mimebox_file = os.path.normpath(mimebox_file) mimebox_dir = os.path.normpath(mimebox_dir) self.logger('Session %s (%s) is calling X2GoClient class hook method .HOOK_open_mimebox_saveas_dialog(%s)' % (self.session_name, self.profile_name, mimebox_file), loglevel=log.loglevel_NOTICE) self.client_instance.HOOK_open_mimebox_saveas_dialog(os.path.join(mimebox_dir, mimebox_file), profile_name=self.profile_name, session_name=self.session_name) time.sleep(60) python-x2go-0.6.1.4/x2go/mimebox.py0000644000000000000000000002743414470264762013663 0ustar # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. """\ :class:`x2go.mimebox.X2GoMIMEboxQueue` sets up a thread that listens for incoming files that shall be opened locally on the client. For each file that gets dropped in the MIME box an individual thread is started (:class:`x2go.mimebox.X2GoMIMEboxJob`) that handles the processing of the incoming file. """ __NAME__ = 'x2gomimeboxqueue-pylib' __package__ = 'x2go' __name__ = 'x2go.mimebox' # modules import os import copy import types import threading import gevent # Python X2Go modules from . import defaults from . import utils from . import log from . import mimeboxactions class X2GoMIMEboxQueue(threading.Thread): """\ If the X2Go MIME box is supported in a particaluar :class:`x2go.session.X2GoSession` instance this class provides a sub-thread for handling incoming files in the MIME box directory. The actual handling of a dropped file is handled by the classes :class:`x2go.mimeboxactions.X2GoMIMEboxActionOPEN`, :class:`x2go.mimeboxactions.X2GoMIMEboxActionOPENWITH` and :class:`x2go.mimeboxactions.X2GoMIMEboxActionSAVEAS`. """ mimebox_action = None mimebox = None active_jobs = {} mimebox_history = [] def __init__(self, profile_name='UNKNOWN', session_name='UNKNOWN', mimebox_dir=None, mimebox_action=None, mimebox_extensions=[], client_instance=None, logger=None, loglevel=log.loglevel_DEFAULT): """\ :param profile_name: name of the session profile this X2Go MIME box belongs to :type profile_name: ``str`` :param mimebox_dir: local directory for incoming MIME box files :type mimebox_dir: ``str`` :param mimebox_action: name or instance of either of the possible X2Go MIME box action classes :type mimebox_action: ``str`` or instance :param client_instance: the underlying :class:`x2go.client.X2GoClient` instance :type client_instance: ``obj`` :param logger: you can pass an :class:`x2go.log.X2GoLogger` object to the :class:`x2go.mimebox.X2GoMIMEboxQueue` constructor :type logger: ``obj`` :param loglevel: if no :class:`x2go.log.X2GoLogger` object has been supplied a new one will be constructed with the given loglevel :type loglevel: ``int`` """ if logger is None: self.logger = log.X2GoLogger(loglevel=loglevel) else: self.logger = copy.deepcopy(logger) self.logger.tag = __NAME__ self.profile_name = profile_name self.session_name = session_name self.mimebox_dir = mimebox_dir if self.mimebox_dir: self.mimebox_dir = os.path.normpath(self.mimebox_dir) self.mimebox_extensions = mimebox_extensions self.client_instance = client_instance self.client_rootdir = client_instance.get_client_rootdir() # this has to be set before we set the MIME box action... self._accept_jobs = False if mimebox_action is None: mimebox_action = mimeboxactions.X2GoMIMEboxActionOPEN(client_instance=self.client_instance, logger=self.logger) elif type(mimebox_action) in (bytes, str): mimebox_action = self.set_mimebox_action(mimebox_action, client_instance=self.client_instance, logger=self.logger) else: # hope it's already an instance... self.mimebox_action = mimebox_action threading.Thread.__init__(self) self.daemon = True self._accept_jobs = True def __del__(self): """\ Class destructor. """ self.stop_thread() def pause(self): """\ Prevent acceptance of new incoming files. The processing of MIME box jobs that are currently still active will be completed, though. """ if self._accept_jobs == True: self._accept_jobs = False self.logger('paused thread: %s' % repr(self), loglevel=log.loglevel_DEBUG) def resume(self): """\ Resume operation of the X2Go MIME box queue and continue accepting new incoming files. """ if self._accept_jobs == False: self._accept_jobs = True self.logger('resumed thread: %s' % repr(self), loglevel=log.loglevel_DEBUG) def stop_thread(self): """\ Stops this :class:`x2go.mimebox.X2GoMIMEboxQueue` thread completely. """ self.pause() self._keepalive = False self.logger('stopping thread: %s' % repr(self), loglevel=log.loglevel_DEBUG) @property def _incoming_mimebox_jobs(self): if os.path.exists(self.mimebox_dir): l = os.listdir(self.mimebox_dir) mimebox_jobs = [] for _ext in self.mimebox_extensions: mimebox_jobs.extend([ dj for dj in l if dj.upper().endswith(_ext.upper()) ]) else: mimebox_jobs = l return [ dj for dj in mimebox_jobs if dj not in list(self.active_jobs.keys()) ] else: return [] def set_mimebox_action(self, mimebox_action, **kwargs): """\ Modify the MIME box action of this :class:`x2go.mimebox.X2GoMIMEboxQueue` thread during runtime. The change of the MIME box action will be valid for the next incoming file in the MIME box directory. :param mimebox_action: the MIME box action to execute for incoming files :type mimebox_action: ``str`` or ``obj`` :param kwargs: extra options for the specified MIME box action :type kwargs: ``dict`` """ if mimebox_action in list(defaults.X2GO_MIMEBOX_ACTIONS.keys()): mimebox_action = defaults.X2GO_MIMEBOX_ACTIONS[mimebox_action] if mimebox_action in list(defaults.X2GO_MIMEBOX_ACTIONS.values()): self.mimebox_action = eval ('mimeboxactions.%s(**kwargs)' % mimebox_action) def run(self): """\ This method gets called once the :class:`x2go.mimebox.X2GoMIMEboxQueue` thread is started by the ``X2GoMIMEboxQueue.start()`` method. """ self.logger('starting MIME box queue thread: %s' % repr(self), loglevel=log.loglevel_DEBUG) self._keepalive = True while self._keepalive: while self._accept_jobs: if self._incoming_mimebox_jobs: for _job in self._incoming_mimebox_jobs: self.logger('processing incoming X2Go MIME box job: %s' % _job, loglevel=log.loglevel_NOTICE) _new_mimeboxjob_thread = X2GoMIMEboxJob(target=x2go_mimeboxjob_handler, kwargs={ 'mimebox_file': _job, 'mimebox_extensions': self.mimebox_extensions, 'mimebox_action': self.mimebox_action, 'parent_thread': self, 'logger': self.logger, } ) self.active_jobs['%s' % _job] = _new_mimeboxjob_thread _new_mimeboxjob_thread.start() gevent.sleep(3) gevent.sleep(1) def x2go_mimeboxjob_handler(mimebox_file=None, mimebox_extensions=[], mimebox_action=None, parent_thread=None, logger=None, ): """\ This function is called as a handler function for each incoming X2Go MIME box file represented by the class :class:`x2go.mimebox.X2GoMIMEboxJob`. :param mimebox_file: MIME box file name as placed in to the X2Go MIME box spool directory (Default value = None) :type mimebox_file: ``str`` :param mimebox_action: an instance of either of the possible ``X2GoMIMEboxActionXXX`` classes (Default value = None) :type mimebox_action: ``X2GoMIMEboxActionXXX`` nstance :param mimebox_extensions: filter out files whose file extension is not in this list (Default value = [], means: no filtering) :type mimebox_extensions: ``list`` :param parent_thread: the :class:`x2go.mimebox.X2GoMIMEboxQueue` thread that actually created this handler's :class:`x2go.mimebox.X2GoMIMEboxJob` instance (Default value = None) :type parent_thread: ``obj`` :param logger: the :class:`x2go.mimebox.X2GoMIMEboxQueue`'s logging instance (Default value = None) :type logger: ``obj`` """ mimebox_action.profile_name = parent_thread.profile_name mimebox_action.session_name = parent_thread.session_name logger('action for MIME box is: %s' % mimebox_action, loglevel=log.loglevel_DEBUG) _dotfile = mimebox_file.startswith('.') _blacklisted = mimebox_file.upper().split('.')[-1] in defaults.X2GO_MIMEBOX_EXTENSIONS_BLACKLIST _really_process = bool(not _blacklisted and ((not mimebox_extensions) or [ ext for ext in mimebox_extensions if mimebox_file.upper().endswith('%s' % ext.upper()) ])) if _really_process and not _blacklisted and not _dotfile: mimebox_action.do_process(mimebox_file=mimebox_file, mimebox_dir=parent_thread.mimebox_dir, ) elif not _blacklisted and not _dotfile: logger('file extension of MIME box file %s is prohibited by session profile configuration' % mimebox_file, loglevel=log.loglevel_NOTICE) elif _dotfile: logger('placing files starting with a dot (.) into the X2Go MIME box is prohibited, ignoring the file ,,%s\'\'' % mimebox_file, loglevel=log.loglevel_WARN) else: logger('file extension of MIME box file %s has been found in Python X2Go\' hardcoded MIME box extenstions blacklist' % mimebox_file, loglevel=log.loglevel_WARN) logger('removing MIME box file %s' % mimebox_file, loglevel=log.loglevel_DEBUG) utils.patiently_remove_file(parent_thread.mimebox_dir, mimebox_file) logger('removed MIME box job file %s' % mimebox_file, loglevel=log.loglevel_DEBUG) del parent_thread.active_jobs['%s' % mimebox_file] parent_thread.mimebox_history.append(mimebox_file) # in case we do a lot of mimebox file exports we do not want to risk an # endlessly growing mimebox job history if len(parent_thread.mimebox_history) > 100: parent_thread.mimebox_history = parent_thread.mimebox_history[-100:] class X2GoMIMEboxJob(threading.Thread): """\ For each X2Go MIME box job we create a sub-thread that let's the MIME box job be processed in the background. As a handler for this class the function :func:`x2go_mimeboxjob_handler()` is used. """ def __init__(self, **kwargs): """\ Construct the X2Go MIME box job thread... All parameters (**kwargs) are passed through to the constructor of ``threading.Thread()``. """ threading.Thread.__init__(self, **kwargs) self.daemon = True python-x2go-0.6.1.4/x2go/_paramiko.py0000644000000000000000000001502414470264762014155 0ustar # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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 Patch and feature map for Python Paramiko """ __package__ = 'x2go' __name__ = 'x2go._paramiko' import paramiko import re import sys try: from paramiko.config import SSH_PORT except ImportError: SSH_PORT=22 import platform from x2go.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, ">=", '1.11.0'), 'preserve-known-hosts': compare_versions(PARAMIKO_VERSION, ">=", '1.11.0'), 'ecdsa-hostkeys': compare_versions(PARAMIKO_VERSION, ">=", '1.11.6'), # Older versions implement the serialization directly, and we don't really # care for broken or working Python-3-implementations with those. 'write-private-key-py3': compare_versions(PARAMIKO_VERSION, '<', '2.0.0'), } if not PARAMIKO_FEATURE['write-private-key-py3']: from cryptography.hazmat.primitives import serialization def _Pkey_write_private_key(self, f, key, format, password=None): """\ See https://github.com/paramiko/paramiko/pull/1583/ The upstream version has a bug when run under Python3. Monkey patching it until it has been fixed upstream. """ if password is None: encryption = serialization.NoEncryption() else: encryption = serialization.BestAvailableEncryption(b(password)) if sys.version_info[0] == 2: f.write( key.private_bytes( serialization.Encoding.PEM, format, encryption ) ).decode() else: f.write( key.private_bytes( serialization.Encoding.PEM, format, encryption ) ) def _SSHClient_save_host_keys(self, filename): """\ Available since paramiko 1.11.0... 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 :func:`load_host_keys()` (plus any added directly) will be saved -- not any host keys loaded with :func:`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 list(_host_keys.items()): for keytype, key in list(keys.items()): f.write('%s %s %s\n' % (hostname, keytype, key.get_base64())) f.close() def _HostKeys_load(self, filename): """\ Available since paramiko 1.11.0... 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 :func:`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 ``(hostname, keytype)`` pair will be replaced. :param hostname: the hostname (or IP) to add :type hostname: str :param keytype: key type (``"ssh-rsa"``, ``"ssh-dss"`` or ``"ecdsa-sha2-nistp256"``) :type keytype: str :param key: the key to add :type key: :class:`PKey` :param hash_hostname: hash the system's hostname (Default value = True) :type hash_hostname: ``bool`` """ # IPv4 and IPv6 addresses using the SSH default port need to be stripped off the port number if re.match('^\[.*\]\:'+str(SSH_PORT)+'$', hostname): # so stripping off the port and the square brackets here... hostname = hostname.split(':')[-2].lstrip('[').rstrip(']') 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 if not PARAMIKO_FEATURE['write-private-key-py3']: paramiko.pkey.PKey._write_private_key = _Pkey_write_private_key python-x2go-0.6.1.4/x2go/printactions.py0000644000000000000000000005562014470264762014736 0ustar # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. """\ Print jobs can either be sent to any of the local print queues (CUPS, Win32API), be opened in an external PDF viewer, be saved to a local folder or be handed over to a custom (print) command. This is defined by four print action classes (:class:`x2go.printactions.X2GoPrintActionDIALOG`, :class:`x2go.printactions.X2GoPrintActionPDFVIEW`, :class:`x2go.printactions.X2GoPrintActionPDFSAVE`, :class:`x2go.printactions.X2GoPrintActionPRINT` and :class:`x2go.printactions.X2GoPrintActionPRINTCMD`). """ __NAME__ = 'x2goprintactions-pylib' __package__ = 'x2go' __name__ = 'x2go.printactions' # modules import os import shutil import copy import time import gevent from .defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS if _X2GOCLIENT_OS in ("Windows"): import subprocess import win32api import win32print else: from . import gevent_subprocess as subprocess from . import x2go_exceptions WindowsError = x2go_exceptions.WindowsError # Python X2Go modules from . import log from . import defaults # we hide the default values from epydoc (that's why we transform them to _UNDERSCORE variables) from . import utils from . import x2go_exceptions _PRINT_ENV = os.environ.copy() class X2GoPrintAction(object): __name__ = 'NAME' __description__ = 'DESCRIPTION' def __init__(self, client_instance=None, logger=None, loglevel=log.loglevel_DEFAULT): """\ This is a meta class and has no functionality as such. It is used as parent class by »real« X2Go print actions. :param client_instance: the underlying :class:`x2go.client.X2GoClient` instance :type client_instance: ``obj`` :param logger: you can pass an :class:`x2go.log.X2GoLogger` object to the :class:`x2go.printactions.X2GoPrintAction` constructor :type logger: ``obj`` :param loglevel: if no :class:`x2go.log.X2GoLogger` object has been supplied a new one will be constructed with the given loglevel :type loglevel: ``int`` """ if logger is None: self.logger = log.X2GoLogger(loglevel=loglevel) else: self.logger = copy.deepcopy(logger) self.logger.tag = __NAME__ # these get set from within the X2GoPrintQueue class self.profile_name = 'UNKNOWN' self.session_name = 'UNKNOWN' self.client_instance = client_instance @property def name(self): """\ Return the X2Go print action's name. :returns: print action name :rtype: ``str`` """ return self.__name__ @property def description(self): """\ Return the X2Go print action's description text. :returns: print action's description :rtype: ``str`` """ return self.__description__ def _do_print(self, pdf_file, job_title, spool_dir, ): """\ Perform the defined print action (doing nothing in :class:`x2go.printactions.X2GoPrintAction` parent class). :param pdf_file: PDF file name as placed in to the X2Go spool directory :type pdf_file: ``str`` :param job_title: human readable print job title :type job_title: ``str`` :param spool_dir: location of the X2Go client's spool directory :type spool_dir: ``str`` """ pass def do_print(self, pdf_file, job_title, spool_dir, ): """\ Wrap around the actual print action (``self._do_print``) with gevent.spawn(). :param pdf_file: PDF file name as placed in to the X2Go spool directory :type pdf_file: ``str`` :param job_title: human readable print job title :type job_title: ``str`` :param spool_dir: location of the X2Go client's spool directory :type spool_dir: ``str`` """ pdf_file = os.path.normpath(pdf_file) spool_dir = os.path.normpath(spool_dir) self._do_print(pdf_file, job_title, spool_dir) def _humanreadable_filename(self, pdf_file, job_title, target_path): """\ Extract a human readable filename for the X2Go print job file. :param pdf_file: PDF file name as placed in to the X2Go spool directory :type pdf_file: ``str`` :param job_title: human readable print job title :type job_title: ``str`` :param target_path: target path for human readable file :type target_path: ``str`` :returns: full readable file name path :rtype: ``str`` """ _hr_path = os.path.normpath(os.path.expanduser(os.path.join(os.path.normpath(target_path), '%s.pdf' % utils.slugify(job_title)))) i = 0 while os.path.exists(_hr_path): i += 1 _hr_path = os.path.normpath(os.path.expanduser(os.path.join(os.path.normpath(target_path), '%s(%s).pdf' % (utils.slugify(job_title), i)))) return _hr_path class X2GoPrintActionPDFVIEW(X2GoPrintAction): """\ Print action that views incoming print job in an external PDF viewer application. """ __name__= 'PDFVIEW' __decription__= 'View as PDF document' pdfview_cmd = None def __init__(self, client_instance=None, pdfview_cmd=None, logger=None, loglevel=log.loglevel_DEFAULT): """\ :param client_instance: the underlying :class:`x2go.client.X2GoClient` instance :type client_instance: ``obj`` :param pdfview_cmd: command that starts the external PDF viewer application :type pdfview_cmd: ``str`` :param logger: you can pass an :class:`x2go.log.X2GoLogger` object to the :class:`x2go.printactions.X2GoPrintActionPDFVIEW` constructor :type logger: ``obj`` :param loglevel: if no :class:`x2go.log.X2GoLogger` object has been supplied a new one will be constructed with the given loglevel :type loglevel: ``int`` """ if pdfview_cmd is None: pdfview_cmd = defaults.DEFAULT_PDFVIEW_CMD self.pdfview_cmd = pdfview_cmd X2GoPrintAction.__init__(self, client_instance=client_instance, logger=logger, loglevel=loglevel) def _do_print(self, pdf_file, job_title, spool_dir, ): """\ Open an incoming X2Go print job (PDF file) in an external PDF viewer application. :param pdf_file: PDF file name as placed in to the X2Go spool directory :type pdf_file: ``str`` :param job_title: human readable print job title :type job_title: ``str`` :param spool_dir: location of the X2Go client's spool directory :type spool_dir: ``str`` :raises OSError: pass through all ``OSError``s except no. 2 """ pdf_file = os.path.normpath(pdf_file) spool_dir = os.path.normpath(spool_dir) if _X2GOCLIENT_OS == "Windows": self.logger('viewing incoming job in PDF viewer with Python\'s os.startfile(command): %s' % pdf_file, loglevel=log.loglevel_DEBUG) try: gevent.spawn(os.startfile, pdf_file) except WindowsError as win_err: if self.client_instance: self.client_instance.HOOK_printaction_error(pdf_file, profile_name=self.profile_name, session_name=self.session_name, err_msg=str(win_err) ) else: self.logger('Encountered WindowsError: %s' % str(win_err), loglevel=log.loglevel_ERROR) time.sleep(20) else: _hr_filename = self._humanreadable_filename(pdf_file, job_title, spool_dir, ) shutil.copy2(pdf_file, _hr_filename) cmd_line = [ self.pdfview_cmd, _hr_filename, ] self.logger('viewing incoming PDF with command: %s' % ' '.join(cmd_line), loglevel=log.loglevel_DEBUG) try: subprocess.Popen(cmd_line, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=_PRINT_ENV) except OSError as e: if e.errno == 2: cmd_line = [ defaults.DEFAULT_PDFVIEW_CMD, _hr_filename ] subprocess.Popen(cmd_line, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=_PRINT_ENV) else: raise(e) self.logger('waiting 20s longer before deleting the PDF file ,,%s\'\'' % _hr_filename, loglevel=log.loglevel_DEBUG) time.sleep(20) os.remove(_hr_filename) class X2GoPrintActionPDFSAVE(X2GoPrintAction): """\ Print action that saves incoming print jobs to a local folder. """ __name__ = 'PDFSAVE' __decription__= 'Save as PDF' save_to_folder = None def __init__(self, client_instance=None, save_to_folder=None, logger=None, loglevel=log.loglevel_DEFAULT): """\ :param client_instance: the underlying :class:`x2go.client.X2GoClient` instance :type client_instance: ``obj`` :param save_to_folder: saving location for incoming print jobs (PDF files) :type save_to_folder: ``str`` :param logger: you can pass an :class:`x2go.log.X2GoLogger` object to the :class:`x2go.printactions.X2GoPrintActionPDFSAVE` constructor :type logger: ``obj`` :param loglevel: if no :class:`x2go.log.X2GoLogger` object has been supplied a new one will be constructed with the given loglevel :type loglevel: ``int`` """ if save_to_folder is None: save_to_folder = defaults.DEFAULT_PDFSAVE_LOCATION if not utils.is_abs_path(save_to_folder): if not save_to_folder.startswith('~'): save_to_folder = os.path.normpath('~/%s' % save_to_folder) save_to_folder = os.path.expanduser(save_to_folder) self.save_to_folder = save_to_folder X2GoPrintAction.__init__(self, client_instance=client_instance, logger=None, loglevel=loglevel) self.logger('Save location for incoming PDFs is: %s' % self.save_to_folder, loglevel=log.loglevel_DEBUG) if not os.path.exists(self.save_to_folder): os.makedirs(self.save_to_folder, mode=0o755) def _do_print(self, pdf_file, job_title, spool_dir): """\ Save an incoming X2Go print job (PDF file) to a local folder. :param pdf_file: PDF file name as placed in to the X2Go spool directory :type pdf_file: ``str`` :param job_title: human readable print job title :type job_title: ``str`` :param spool_dir: location of the X2Go client's spool directory :type spool_dir: ``str`` """ pdf_file = os.path.normpath(pdf_file) spool_dir = os.path.normpath(spool_dir) dest_file = self._humanreadable_filename(pdf_file, job_title, target_path=self.save_to_folder) shutil.copy2(pdf_file, dest_file) class X2GoPrintActionPRINT(X2GoPrintAction): """\ Print action that actually prints an incoming print job file. """ __name__ = 'PRINT' __decription__= 'UNIX/Win32GDI printing' def __init__(self, client_instance=None, printer=None, logger=None, loglevel=log.loglevel_DEFAULT): """\ :param client_instance: the underlying :class:`x2go.client.X2GoClient` instance :type client_instance: ``obj`` :param printer: name of the preferred printer, if ``None`` the system's/user's default printer will be used :type printer: ``str`` :param logger: you can pass an :class:`x2go.log.X2GoLogger` object to the :class:`x2go.printactions.X2GoPrintActionPRINT` constructor :type logger: ``obj`` :param loglevel: if no :class:`x2go.log.X2GoLogger` object has been supplied a new one will be constructed with the given loglevel :type loglevel: ``int`` """ self.printer = printer X2GoPrintAction.__init__(self, client_instance=client_instance, logger=logger, loglevel=loglevel) def _do_print(self, pdf_file, job_title, spool_dir, ): """\ Really print an incoming X2Go print job (PDF file) to a local printer device. :param pdf_file: PDF file name as placed in to the X2Go spool directory :type pdf_file: ``str`` :param job_title: human readable print job title :type job_title: ``str`` :param spool_dir: location of the X2Go client's spool directory :type spool_dir: ``str`` """ pdf_file = os.path.normpath(pdf_file) spool_dir = os.path.normpath(spool_dir) _hr_filename = self._humanreadable_filename(pdf_file, job_title, spool_dir) if _X2GOCLIENT_OS == 'Windows': _default_printer = win32print.GetDefaultPrinter() if self.printer: _printer = self.printer win32print.SetDefaultPrinter(_printer) else: _printer = _default_printer self.logger('printing incoming PDF file %s' % pdf_file, loglevel=log.loglevel_NOTICE) self.logger('printer name is ,,%s\'\'' % _printer, loglevel=log.loglevel_DEBUG) try: _stdin = file('nul', 'r') _shell = True if self.client_instance: _gsprint_bin = self.client_instance.client_printing.get_value('print', 'gsprint') self.logger('Using gsprint.exe path from printing config file: %s' % _gsprint_bin, loglevel=log.loglevel_DEBUG) else: _program_files = os.environ['ProgramFiles'] _gsprint_bin = os.path.normpath(os.path.join(_program_files, 'ghostgum', 'gsview', 'gsprint.exe',)) self.logger('Using hard-coded gsprint.exe path: %s' % _gsprint_bin, loglevel=log.loglevel_DEBUG) self.logger('Trying Ghostgum tool ,,gsprint.exe'' for printing first (full path: %s)' % _gsprint_bin, loglevel=log.loglevel_DEBUG) subprocess.Popen([_gsprint_bin, pdf_file, ], stdin=_stdin, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=_shell, ) # give gsprint.exe a little time to find our printer time.sleep(10) except: self.logger('Falling back to win32api printing...', loglevel=log.loglevel_DEBUG) try: win32api.ShellExecute ( 0, "print", pdf_file, None, ".", 0 ) # give the win32api some time to find our printer... time.sleep(10) except win32api.error as e: if self.client_instance: self.client_instance.HOOK_printaction_error(filename=_hr_filename, printer=_printer, err_msg=e.message, profile_name=self.profile_name, session_name=self.session_name) else: self.logger('Encountered win32api.error: %s' % str(e), loglevel=log.loglevel_ERROR) if self.printer: win32print.SetDefaultPrinter(_default_printer) time.sleep(60) else: _hr_filename = self._humanreadable_filename(pdf_file, job_title, spool_dir) self.logger('printing incoming PDF file %s' % _hr_filename, loglevel=log.loglevel_NOTICE) if self.printer: self.logger('printer name is %s' % self.printer, loglevel=log.loglevel_DEBUG) else: self.logger('using default CUPS printer', loglevel=log.loglevel_DEBUG) shutil.copy2(pdf_file, _hr_filename) if self.printer is None: cmd_line = [ 'lpr', '-h', '-r', '-J%s' % job_title, '%s' % _hr_filename, ] else: cmd_line = [ 'lpr', '-h', '-r', '-P%s' % self.printer, '-J%s' % job_title, '%s' % _hr_filename, ] self.logger('executing local print command: %s' % " ".join(cmd_line), loglevel=log.loglevel_DEBUG) subprocess.Popen(cmd_line, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=_PRINT_ENV) # this is nasty!!!! self.logger('waiting 20s longer before deleting the PDF file ,,%s\'\'' % _hr_filename, loglevel=log.loglevel_DEBUG) time.sleep(20) try: os.remove(_hr_filename) except OSError: pass class X2GoPrintActionPRINTCMD(X2GoPrintAction): """\ Print action that calls an external command for further processing of incoming print jobs. The print job's PDF filename will be prepended as last argument to the print command used in :class:`x2go.printactions.X2GoPrintActionPRINTCMD` instances. """ __name__ = 'PRINTCMD' __decription__= 'Print via a command (like LPR)' def __init__(self, client_instance=None, print_cmd=None, logger=None, loglevel=log.loglevel_DEFAULT): """\ :param client_instance: the underlying :class:`x2go.client.X2GoClient` instance :type client_instance: ``obj`` :param print_cmd: external command to be called on incoming print jobs :type print_cmd: ``str`` :param logger: you can pass an :class:`x2go.log.X2GoLogger` object to the :class:`x2go.printactions.X2GoPrintActionPRINTCMD` constructor :type logger: ``obj`` :param loglevel: if no :class:`x2go.log.X2GoLogger` object has been supplied a new one will be constructed with the given loglevel :type loglevel: ``int`` """ if print_cmd is None: print_cmd = defaults.DEFAULT_PRINTCMD_CMD self.print_cmd = print_cmd X2GoPrintAction.__init__(self, client_instance=client_instance, logger=logger, loglevel=loglevel) def _do_print(self, pdf_file, job_title, spool_dir): """\ Execute an external command that has been defined on construction of this :class:`x2go.printactions.X2GoPrintActionPRINTCMD` instance. :param pdf_file: PDF file name as placed in to the X2Go spool directory :type pdf_file: ``str`` :param job_title: human readable print job title :type job_title: ``str`` :param spool_dir: location of the X2Go client's spool directory :type spool_dir: ``str`` """ pdf_file = os.path.normpath(pdf_file) spool_dir = os.path.normpath(spool_dir) _hr_filename = self._humanreadable_filename(pdf_file, job_title, spool_dir) shutil.copy2(pdf_file, _hr_filename) self.logger('executing external command ,,%s\'\' on PDF file %s' % (self.print_cmd, _hr_filename), loglevel=log.loglevel_NOTICE) cmd_line = self.print_cmd.split() cmd_line.append(_hr_filename) self.logger('executing external command: %s' % " ".join(cmd_line), loglevel=log.loglevel_DEBUG) subprocess.Popen(cmd_line, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=_PRINT_ENV) # this is nasty!!!! self.logger('waiting 20s longer before deleting the PDF file ,,%s\'\'' % _hr_filename, loglevel=log.loglevel_DEBUG) time.sleep(20) try: os.remove(_hr_filename) except OSError: pass class X2GoPrintActionDIALOG(X2GoPrintAction): """\ Print action that mediates opening a print dialog window. This class is rather empty, the actual print dialog box must be implemented in our GUI application (with the application's :class:`x2go.client.X2GoClient` instance. """ __name__ = 'DIALOG' __decription__= 'Open a print dialog box' def __init__(self, client_instance=None, logger=None, loglevel=log.loglevel_DEFAULT): """\ :param client_instance: an :class:`x2go.client.X2GoClient` instance, within your customized :class:`x2go.client.X2GoClient` make sure you have a ``HOOK_open_print_dialog(filename=)`` method defined that will actually open the print dialog. :type client_instance: ``obj`` :param logger: you can pass an :class:`x2go.log.X2GoLogger` object to the :class:`x2go.printactions.X2GoPrintActionDIALOG` constructor :type logger: ``obj`` :param loglevel: if no :class:`x2go.log.X2GoLogger` object has been supplied a new one will be constructed with the given loglevel :type loglevel: ``int`` :raises X2GoPrintActionException: if the client_instance has not been passed to the DIALOG print action """ if client_instance is None: raise x2go_exceptions.X2GoPrintActionException('the DIALOG print action needs to know the X2GoClient instance (client=)') X2GoPrintAction.__init__(self, client_instance=client_instance, logger=logger, loglevel=loglevel) def _do_print(self, pdf_file, job_title, spool_dir): """\ Execute an external command that has been defined on construction of this :class:`x2go.printactions.X2GoPrintActionPRINTCMD` instance. :param pdf_file: PDF file name as placed in to the X2Go spool directory :type pdf_file: ``str`` :param job_title: human readable print job title :type job_title: ``str`` :param spool_dir: location of the X2Go client's spool directory :type spool_dir: ``str`` """ pdf_file = os.path.normpath(pdf_file) spool_dir = os.path.normpath(spool_dir) self.logger('Session %s (%s) is calling X2GoClient class hook method .HOOK_open_print_dialog' % (self.session_name, self.profile_name), loglevel=log.loglevel_NOTICE) _new_print_action = self.client_instance.HOOK_open_print_dialog(profile_name=self.profile_name, session_name=self.session_name) if _new_print_action and type(_new_print_action) != type(self): _new_print_action._do_print(pdf_file, job_title, spool_dir) python-x2go-0.6.1.4/x2go/printqueue.py0000644000000000000000000003100014470264762014404 0ustar # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. """\ :class:`x2go.printing.X2GoPrintQueue` sets up a thread that listens for incoming print jobs. For each incoming print job in an X2Go session's spool directory an individual thread is started (:class:`x2go.printqueue.X2GoPrintJob`) that handles the processing of the incoming print job. """ __NAME__ = 'x2goprintqueue-pylib' __package__ = 'x2go' __name_ = 'x2go.printqueue' # modules import os import copy import threading import gevent # Python X2Go modules from . import defaults from . import utils from . import log from .defaults import X2GO_PRINTING_FILENAME as _X2GO_PRINTING_FILENAME from .defaults import BACKENDS as _BACKENDS class X2GoPrintQueue(threading.Thread): """\ If X2Go printing is supported in a particular :class:`x2go.session.X2GoSession` instance this class provides a sub-thread for handling incoming X2Go print jobs. """ print_action = None spooldir = None active_jobs = {} job_history = [] def __init__(self, profile_name='UNKNOWN', session_name='UNKNOWN', spool_dir=None, print_action=None, print_action_args={}, client_instance=None, printing_backend=_BACKENDS['X2GoClientPrinting']['default'], logger=None, loglevel=log.loglevel_DEFAULT): """\ :param profile_name: name of the session profile this print queue belongs to :type profile_name: ``str`` :param spool_dir: local spool directory for incoming print job files :type spool_dir: ``str`` :param print_action: name or instance of either of the possible X2Go print action classes :type print_action: ``str`` or instance :param print_action_args: depending of the chosen ``print_action`` this dictionary may contain different values; the ``print_action_args`` will be passed on to the X2Go print action instance constructor, so refer to either of these: :class:`x2go.printactions.X2GoPrintActionPDFVIEW`, :class:`x2go.printactions.X2GoPrintActionPRINT` et al. :param client_instance: the underlying :class:`x2go.client.X2GoClient` instance :type client_instance: ``obj`` :param printing_backend: the client printing configuration backend class :type printing_backend: ``obj`` :param logger: you can pass an :class:`x2go.log.X2GoLogger` object to the :class:`x2go.printing.X2GoPrintQueue` constructor :type logger: ``obj`` :param loglevel: if no :class:`x2go.log.X2GoLogger` object has been supplied a new one will be constructed with the given loglevel :type loglevel: ``int`` """ if logger is None: self.logger = log.X2GoLogger(loglevel=loglevel) else: self.logger = copy.deepcopy(logger) self.logger.tag = __NAME__ self.profile_name = profile_name self.session_name = session_name self.spool_dir = spool_dir if self.spool_dir: self.spool_dir = os.path.normpath(self.spool_dir) self.client_instance = client_instance self.client_rootdir = client_instance.get_client_rootdir() self.printing_backend = utils._get_backend_class(printing_backend, "X2GoClientPrinting") if print_action is not None: self.set_print_action(print_action, client_instance=self.client_instance, logger=logger, **print_action_args) threading.Thread.__init__(self) self.daemon = True self._accept_jobs = True def __del__(self): """\ Class destructor. """ self.stop_thread() def pause(self): """\ Prevent acceptance of new incoming print jobs. The processing of print jobs that are currently still active will be completed, though. """ if self._accept_jobs == True: self._accept_jobs = False self.logger('paused thread: %s' % repr(self), loglevel=log.loglevel_DEBUG) def resume(self): """\ Resume operation of the X2Go print spooler and continue accepting new incoming print jobs. """ if self._accept_jobs == False: self._accept_jobs = True self.logger('resumed thread: %s' % repr(self), loglevel=log.loglevel_DEBUG) def stop_thread(self): """\ Stops this :class:`x2go.printing.X2GoPrintQueue` thread completely. """ self.pause() self._keepalive = False self.logger('stopping thread: %s' % repr(self), loglevel=log.loglevel_DEBUG) @property def _incoming_print_jobs(self): if os.path.exists(self.spool_dir): l = os.listdir(self.spool_dir) job_files = [ jf for jf in l if jf.endswith('.ready') ] jobs = [] for _job_file in job_files: _job_file_handle = open(os.path.join(self.spool_dir, _job_file), 'r') content = _job_file_handle.read() try: (pdf_filename, job_title) = content.split('\n')[0:2] except ValueError: pdf_filename = content job_title = 'X2Go Print Job' _job_file_handle.close() jobs.append((_job_file, pdf_filename, job_title)) return [ j for j in jobs if j[1] not in list(self.active_jobs.keys()) ] else: return [] def set_print_action(self, print_action, **kwargs): """\ Modify the print action of this :class:`x2go.printing.X2GoPrintQueue` thread during runtime. The change of print action will be valid for the next incoming print job. As kwargs you can pass arguments for the print action class to be set. Refer to the class descriptions of :class:`x2go.printactions.X2GoPrintActionDIALOG`, :class:`x2go.printactions.X2GoPrintActionPDFVIEW`, :class:`x2go.printactions.X2GoPrintActionPRINT`, etc. :param print_action: new print action to be valid for incoming print jobs :type print_action: ``str`` or ``class`` :param kwargs: extra options for the specified print action :type kwargs: ``dict`` """ if print_action in list(defaults.X2GO_PRINT_ACTIONS.keys()): print_action = defaults.X2GO_PRINT_ACTIONS[print_action] if print_action in list(defaults.X2GO_PRINT_ACTIONS.values()): self.print_action = eval ('printactions.%s(**kwargs)' % print_action) def run(self): """\ Start this :class:`x2go.printing.X2GoPrintQueue` thread... """ self.logger('starting print queue thread: %s' % repr(self), loglevel=log.loglevel_DEBUG) self._keepalive = True while self._keepalive: while self._accept_jobs: if self._incoming_print_jobs: for _job in self._incoming_print_jobs: self.logger('processing incoming X2Go print job: %s' % _job[1], loglevel=log.loglevel_NOTICE) _new_printjob_thread = X2GoPrintJob(target=x2go_printjob_handler, kwargs={ 'job_file': _job[0], 'pdf_file': _job[1], 'job_title': _job[2], 'print_action': self.print_action, 'parent_thread': self, 'logger': self.logger, } ) self.active_jobs['%s' % _job[1]] = _new_printjob_thread _new_printjob_thread.start() gevent.sleep(3) gevent.sleep(1) def x2go_printjob_handler(job_file=None, pdf_file=None, job_title=None, print_action=None, parent_thread=None, logger=None, ): """\ This function is called as a handler function for each incoming X2Go print job represented by the class :class:`x2go.printqueue.X2GoPrintJob`. The handler function will (re-)read the »printing« configuration file (if no explicit ``print_action`` is passed to this function...). It then will execute the ``.do_print()`` command. :param job_file: file name of this print job's Job File (Default value = None) :type job_file: ``str`` :param pdf_file: PDF file name as placed in to the X2Go spool directory (Default value = None) :type pdf_file: ``str`` :param job_title: human readable print job title (Default value = None) :type job_title: ``str`` :param print_action: an instance of either of the possible ``X2GoPrintActionXXX`` classes (Default value = None) :type print_action: ``X2GoPrintActionXXX`` nstance :param parent_thread: the :class:`x2go.printing.X2GoPrintQueue` thread that actually created this handler's :class:`x2go.printqueue.X2GoPrintJob` instance (Default value = None) :type parent_thread: ``obj`` :param logger: the :class:`x2go.printing.X2GoPrintQueue`'s logging instance (Default value = None) :type logger: ``obj`` """ if print_action is None: if parent_thread.client_instance is not None and parent_thread.client_instance.has_custom_client_rootdir: _printing = parent_thread.printing_backend(config_files=[os.path.join(parent_thread.client_instance.get_client_rootdir(), _X2GO_PRINTING_FILENAME)], client_instance=parent_thread.client_instance, logger=logger ) else: _printing = parent_thread.printing_backend(client_instance=parent_thread.client_instance, logger=logger ) print_action = _printing.print_action print_action.profile_name = parent_thread.profile_name print_action.session_name = parent_thread.session_name logger('action for printing is: %s' % print_action, loglevel=log.loglevel_DEBUG) print_action.do_print(pdf_file=os.path.normpath(os.path.join(parent_thread.spool_dir, pdf_file)), job_title=job_title, spool_dir=parent_thread.spool_dir, ) logger('removing print job files for %s' % pdf_file, loglevel=log.loglevel_DEBUG) utils.patiently_remove_file(parent_thread.spool_dir, job_file) logger('removed print job file %s' % job_file, loglevel=log.loglevel_DEBUG) utils.patiently_remove_file(parent_thread.spool_dir, pdf_file) logger('removed print pdf file %s' % pdf_file, loglevel=log.loglevel_DEBUG) del parent_thread.active_jobs['%s' % pdf_file] parent_thread.job_history.append(pdf_file) # in case we print a lot we do not want to risk an endlessly growing # print job history if len(parent_thread.job_history) > 100: parent_thread.job_history = parent_thread.job_history[-100:] class X2GoPrintJob(threading.Thread): """\ For each X2Go print job we create a sub-thread that let's the print job be processed in the background. As a handler for this class the function :func:`x2go_printjob_handler()` is used. """ def __init__(self, **kwargs): """\ Construct the X2Go print job thread... All parameters (**kwargs) are passed through to the constructor of ``threading.Thread()``. """ threading.Thread.__init__(self, **kwargs) self.daemon = True python-x2go-0.6.1.4/x2go/pulseaudio.py0000644000000000000000000001564014470264762014371 0ustar # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. # # Other contributors: # none so far """\ X2GoPulseAudio class - a Pulseaudio daemon guardian thread. """ __NAME__ = 'x2gopulseaudio-pylib' __package__ = 'x2go' __name__ = 'x2go.pulseaudio' from .defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS if _X2GOCLIENT_OS == 'Windows': import win32process import win32con import win32event # modules import os import sys import threading import gevent import copy import socket from .defaults import LOCAL_HOME as _LOCAL_HOME # Python X2Go modules from . import log class OSNotSupportedException(BaseException): pass """ Exception denoting that this operating system is not supported. """ class X2GoPulseAudio(threading.Thread): """This class controls the Pulse Audio daemon.""" def __init__(self, path=None, client_instance=None, logger=None, loglevel=log.loglevel_DEFAULT): """\ Initialize a Pulse Audio daemon instance. :param path: full path to pulseaudio.exe :type path: ``str`` :param client_instance: the calling :class:`x2go.client.X2GoClient` instance :type client_instance: :class:`x2go.client.X2GoClient` instance :param logger: you can pass an :class:`x2go.log.X2GoLogger` object to the :class:`x2go.xserver.X2GoClientXConfig` constructor :type logger: ``obj`` :param loglevel: if no :class:`x2go.log.X2GoLogger` object has been supplied a new one will be constructed with the given loglevel :type loglevel: ``int`` :raises OSNotSupportedException: on non-Windows platforms Python X2Go presumes that pulseaudio is already launched """ if _X2GOCLIENT_OS not in ("Windows"): raise OSNotSupportedException('classes of x2go.pulseaudio module are for Windows only') if logger is None: self.logger = log.X2GoLogger(loglevel=loglevel) else: self.logger = copy.deepcopy(logger) self.logger.tag = __NAME__ self.path = path self.client_instance = client_instance self._keepalive = None threading.Thread.__init__(self) self.daemon = True self.start() def run(self): """\ This method is called once the ``X2GoPulseAudio.start()`` method has been called. To tear down the Pulseaudio daemon call the :func:`X2GoPulseAudio.stop_thread() # # 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. """\ X2GoSessionRegistry class - the X2GoClient's session registry backend """ __NAME__ = 'x2gosessregistry-pylib' __package__ = 'x2go' __name__ = 'x2go.registry' import os import copy import types import time import threading import re # Python X2Go modules from . import log from . import utils from . import session from . import x2go_exceptions from .defaults import LOCAL_HOME as _LOCAL_HOME from .defaults import X2GO_CLIENT_ROOTDIR as _X2GO_CLIENT_ROOTDIR from .defaults import X2GO_SESSIONS_ROOTDIR as _X2GO_SESSIONS_ROOTDIR from .defaults import X2GO_SESSIONPROFILE_DEFAULTS as _X2GO_SESSIONPROFILE_DEFAULTS from .defaults import X2GO_SSH_ROOTDIR as _X2GO_SSH_ROOTDIR from .defaults import BACKENDS as _BACKENDS class X2GoSessionRegistry(object): """\ This class is utilized by :class:`x2go.client.X2GoClient` instances to maintain a good overview on session status of all associated :class:`x2go.session.X2GoSession` instances. """ def __init__(self, client_instance, logger=None, loglevel=log.loglevel_DEFAULT): """\ :param client_instance: the :class:`x2go.client.X2GoClient` instance that instantiated this :class:`x2go.registry.X2GoSessionRegistry` instance. :type client_instance: :class:`x2go.client.X2GoClient` instance :param logger: you can pass an :class:`x2go.log.X2GoLogger` object to the :class:`x2go.xserver.X2GoClientXConfig` constructor :type logger: ``obj`` :param loglevel: if no :class:`x2go.log.X2GoLogger` object has been supplied a new one will be constructed with the given loglevel :type loglevel: ``int`` """ if logger is None: self.logger = log.X2GoLogger(loglevel=loglevel) else: self.logger = copy.deepcopy(logger) self.logger.tag = __NAME__ self.client_instance = client_instance self.registry = {} self.control_sessions = {} self.master_sessions = {} self._last_available_session_registration = None self._skip_auto_registration = False self._profile_locks = {} def keys(self): """\ A list of session registry keys. :returns: session registry key list :rtype: ``list`` """ return list(self.registry.keys()) def __repr__(self): result = 'X2GoSessionRegistry(' 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 __call__(self, session_uuid): """\ Returns the :class:`x2go.session.X2GoSession` instance for a given session UUID hash. :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :returns: the corresponding :class:`x2go.session.X2GoSession` instance :rtype: :class:`x2go.session.X2GoSession` instance :raises X2GoSessionRegistryException: if the given session UUID could not be found """ try: return self.registry[session_uuid] except KeyError: raise x2go_exceptions.X2GoSessionRegistryException('No session found for UUID %s' % session_uuid) def disable_session_auto_registration(self): """\ This method is used to temporarily skip auto-registration of newly appearing X2Go session on the server side. This is necessary during session startups to assure that the session registry does not get filled with session UUID duplicates. """ self._skip_auto_registration = True def enable_session_auto_registration(self): """\ This method is used to temporarily (re-)enable auto-registration of newly appearing X2Go session on the server side. """ self._skip_auto_registration = False def forget(self, session_uuid): """\ Forget the complete record for session UUID ``session_uuid``. :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` """ try: del self.registry[session_uuid] self.logger('Forgetting session UUID %s' % session_uuid, loglevel=log.loglevel_DEBUG) except KeyError: pass def get_profile_id(self, session_uuid): """\ Retrieve the profile ID of a given session UUID hash. :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :returns: profile ID :rtype: ``str`` """ return self(session_uuid).get_profile_id() def get_profile_name(self, session_uuid): """\ Retrieve the profile name of a given session UUID hash. :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :returns: profile name :rtype: ``str`` """ return self(session_uuid).get_profile_name() def session_summary(self, session_uuid, status_only=False): """\ Compose a session summary (as Python dictionary). :param session_uuid: the X2Go session's UUID registry hash :type session_uuid: ``str`` :param status_only: short summary, include session status only (Default value = False) :type status_only: ``bool`` :returns: session summary dictionary :rtype: ``dict`` """ _session_summary = {} _r = False if session_uuid in [ s() for s in self.registered_sessions() ]: _r = True if not status_only: _session_summary['uuid'] = _r and session_uuid or None _session_summary['profile_id'] = _r and self.get_profile_id(session_uuid) or '' _session_summary['profile_name'] = _r and self.get_profile_name(session_uuid) or '' _session_summary['session_name'] = _r and self(session_uuid).get_session_name() or '' _session_summary['control_session'] = _r and self(session_uuid).get_control_session() or None _session_summary['control_params'] = _r and self(session_uuid).control_params or {} _session_summary['terminal_session'] = _r and self(session_uuid).get_terminal_session() or None _session_summary['terminal_params'] = _r and self(session_uuid).terminal_params or {} _session_summary['active_threads'] = _r and bool(self(session_uuid).get_terminal_session()) and self(session_uuid).get_terminal_session().active_threads or [] _session_summary['backends'] = { 'control': _r and self(session_uuid).control_backend or None, 'terminal': _r and self(session_uuid).terminal_backend or None, 'info': _r and self(session_uuid).info_backend or None, 'list': _r and self(session_uuid).list_backend or None, 'proxy': _r and self(session_uuid).proxy_backend or None, } if _r: _session_summary['virgin'] = self(session_uuid).virgin _session_summary['connected'] = self(session_uuid).connected _session_summary['running'] = self(session_uuid).running _session_summary['suspended'] = self(session_uuid).suspended _session_summary['terminated'] = self(session_uuid).terminated else: _session_summary['virgin'] = None _session_summary['connected'] = None _session_summary['running'] = None _session_summary['suspended'] = None _session_summary['terminated'] = None return _session_summary def update_status(self, session_uuid=None, profile_name=None, profile_id=None, session_list=None, force_update=False, newly_connected=False): """\ Update the session status for :class:`x2go.session.X2GoSession` that is represented by a given session UUID hash, profile name or profile ID. :param session_uuid: the X2Go session's UUID registry hash (Default value = None) :type session_uuid: ``str`` :param profile_name: alternatively, a profile name can be specified (the stati of all registered sessions for this session profile will be updated) (Default value = None) :type profile_name: ``str`` :param profile_id: alternatively, a profile ID can be given (the stati of all registered sessions for this session profile will be updated) (Default value = None) :type profile_id: ``str`` :param session_list: an optional ``X2GoServerSessionList*`` instance (as returned by the :func:`X2GoClient.list_sessions() ` command can be passed to this method. (Default value = None) :type session_list: ``X2GoServerSessionList*`` instance :param force_update: make sure the session status gets really updated (Default value = False) :type force_update: ``bool`` :param newly_connected: set this to ``True``, if the control session has just been connected (Default value = False) :param newly_connected: ``bool`` (Default value = False) :returns: ``True`` if this method has been successful :rtype: ``bool`` :raises X2GoSessionRegistryException: if the combination of ``session_uuid``, ``profile_name`` and ``profile_id`` does not match the requirement: only one of them """ if session_uuid and profile_name or session_uuid and profile_id or profile_name and profile_id: raise x2go_exceptions.X2GoSessionRegistryException('only one of the possible method parameters is allowed (session_uuid, profile_name or profile_id)') elif session_uuid is None and profile_name is None and profile_id is None: raise x2go_exceptions.X2GoSessionRegistryException('at least one of the method parameters session_uuid, profile_name or profile_id must be given') if session_uuid: session_uuids = [ session_uuid ] elif profile_name: session_uuids = [ s() for s in self.registered_sessions_of_profile_name(profile_name, return_objects=True) ] elif profile_id: session_uuids = [ s() for s in self.registered_sessions_of_profile_name(self.client_instance.to_profile_name(profile_id), return_objects=True) ] for _session_uuid in session_uuids: # only operate on instantiated X2GoSession objects if type(self(_session_uuid)) != session.X2GoSession: continue if self(_session_uuid).is_locked(): continue if not self(_session_uuid).update_status(session_list=session_list, force_update=force_update): # skip this run, as nothing has changed since the last time... continue _last_status = copy.deepcopy(self(_session_uuid)._last_status) _current_status = copy.deepcopy(self(_session_uuid)._current_status) # at this point we hook into the X2GoClient instance and call notification methods # that can be used to inform an application that something has happened _profile_name = self(_session_uuid).get_profile_name() _session_name = self(_session_uuid).get_session_name() if self(_session_uuid).get_server_hostname() != _current_status['server']: # if the server (hostname) has changed due to a configuration change we skip all notifications self(_session_uuid).session_cleanup() self(_session_uuid).__del__() if len(self.virgin_sessions_of_profile_name(profile_name)) > 1: del self.registry[_session_uuid] elif not _last_status['running'] and _current_status['running'] and not _current_status['faulty']: # session has started if newly_connected: # from a suspended state self.client_instance.HOOK_on_found_session_running_after_connect(session_uuid=_session_uuid, profile_name=_profile_name, session_name=_session_name) else: # explicitly ask for the terminal_session object directly here, so we also get 'PENDING' terminal sessions here... if self(_session_uuid).terminal_session: # declare as master session if appropriate if _profile_name not in list(self.master_sessions.keys()): self.master_sessions[_profile_name] = self(_session_uuid) self(_session_uuid).set_master_session() elif (not self.master_sessions[_profile_name].is_desktop_session() and self(_session_uuid).is_desktop_session()) or \ (not self.master_sessions[_profile_name].is_desktop_session() and self(_session_uuid).is_published_applications_provider()): self(self.master_sessions[_profile_name]()).unset_master_session() self.master_sessions[_profile_name] = self(_session_uuid) self(_session_uuid).set_master_session() if _last_status['suspended']: # from a suspended state self.client_instance.HOOK_on_session_has_resumed_by_me(session_uuid=_session_uuid, profile_name=_profile_name, session_name=_session_name) elif _last_status['virgin']: # as a new session self.client_instance.HOOK_on_session_has_started_by_me(session_uuid=_session_uuid, profile_name=_profile_name, session_name=_session_name) else: if _last_status['suspended']: # from a suspended state self.client_instance.HOOK_on_session_has_resumed_by_other(session_uuid=_session_uuid, profile_name=_profile_name, session_name=_session_name) elif _last_status['connected'] and _last_status['virgin']: # as a new session, do not report directly after connect due to many false positives then... self.client_instance.HOOK_on_session_has_started_by_other(session_uuid=_session_uuid, profile_name=_profile_name, session_name=_session_name) elif _last_status['connected'] and (not _last_status['suspended'] and _current_status['suspended']) and not _current_status['faulty'] and _session_name: # unregister as master session if _profile_name in list(self.master_sessions.keys()): if self.master_sessions[_profile_name] == self(_session_uuid): self(_session_uuid).unset_master_session() del self.master_sessions[_profile_name] # session has been suspended self(_session_uuid).session_cleanup() self.client_instance.HOOK_on_session_has_been_suspended(session_uuid=_session_uuid, profile_name=_profile_name, session_name=_session_name) elif _last_status['connected'] and (not _last_status['terminated'] and _current_status['terminated']) and not _current_status['faulty'] and _session_name: # unregister as master session if _profile_name in list(self.master_sessions.keys()): if self.master_sessions[_profile_name] == self(_session_uuid): self(_session_uuid).unset_master_session() del self.master_sessions[_profile_name] # session has terminated self.client_instance.HOOK_on_session_has_terminated(session_uuid=_session_uuid, profile_name=_profile_name, session_name=_session_name) try: self(_session_uuid).session_cleanup() except x2go_exceptions.X2GoSessionException: pass try: self(_session_uuid).__del__() except x2go_exceptions.X2GoSessionException: pass if len(self.virgin_sessions_of_profile_name(profile_name)) > 1: self.forget(_session_uuid) # detect master sessions for connected profiles that have lost (suspend/terminate) their master session or never had a master session for _profile_name in [ p for p in self.connected_profiles(return_profile_names=True) if p not in list(self.master_sessions.keys()) ]: _running_associated_sessions = [ _s for _s in self.running_sessions_of_profile_name(_profile_name, return_objects=True) if _s.is_associated() ] if _running_associated_sessions: for _r_a_s in _running_associated_sessions: if _r_a_s.is_desktop_session(): self.master_sessions[_profile_name] = _r_a_s _r_a_s.set_master_session(wait=1) break if _profile_name not in self.master_sessions: _pubapp_associated_sessions = self.pubapp_sessions_of_profile_name(_profile_name, return_objects=True) if _pubapp_associated_sessions: self.master_sessions[_profile_name] = _pubapp_associated_sessions[0] _pubapp_associated_sessions[0].set_master_session(wait=2) else: self.master_sessions[_profile_name] = _running_associated_sessions[0] _running_associated_sessions[0].set_master_session(wait=2) return True def register_available_server_sessions(self, profile_name, session_list=None, newly_connected=False, re_register=False, skip_pubapp_sessions=False): """\ Register server-side available X2Go sessions with this :class:`x2go.registry.X2GoSessionRegistry` instance for a given profile name. :param profile_name: session profile name to register available X2Go sessions for :type profile_name: ``str`` :param session_list: an optional ``X2GoServerSessionList*`` instance (as returned by the :func:`X2GoClient.list_sessions() ` command can be passed to this method. (Default value = None) :type session_list: ``X2GoServerSessionList*`` instance :param newly_connected: give a hint that the session profile got newly connected (Default value = False) :type newly_connected: ``bool`` :param re_register: re-register available sessions, needs to be done after changes to the session profile (Default value = False) :type re_register: ``bool`` :param skip_pubapp_sessions: Do not register published applications sessions (Default value = False) :type skip_pubapp_sessions: ``bool`` """ if self._last_available_session_registration is not None: _now = time.time() _time_delta = _now - self._last_available_session_registration if _time_delta < 2 and not re_register: self.logger('registration interval too short (%s), skipping automatic session registration...' % _time_delta, loglevel=log.loglevel_DEBUG) return self._last_available_session_registration = _now _connected_sessions = self.connected_sessions_of_profile_name(profile_name=profile_name, return_objects=False) _registered_sessions = self.registered_sessions_of_profile_name(profile_name=profile_name, return_objects=False) _session_names = [ self(s_uuid).session_name for s_uuid in _registered_sessions if self(s_uuid).session_name is not None ] if _connected_sessions: # any of the connected sessions is valuable for accessing the profile's control # session commands, so we simply take the first that comes in... _ctrl_session = self(_connected_sessions[0]) if session_list is None: session_list = _ctrl_session.list_sessions() # make sure the session registry gets updated before registering new session # (if the server name has changed, this will kick out obsolete X2GoSessions) self.update_status(profile_name=profile_name, session_list=session_list, force_update=True) for session_name in list(session_list.keys()): if (session_name not in _session_names and not self._skip_auto_registration) or re_register: server = _ctrl_session.get_server_hostname() profile_id = _ctrl_session.get_profile_id() # reconstruct all session options of _ctrl_session to auto-register a suspended session # found on the _ctrl_session's connected server _clone_kwargs = _ctrl_session.__dict__ kwargs = {} kwargs.update(self.client_instance.session_profiles.to_session_params(profile_id)) kwargs['client_instance'] = self.client_instance kwargs['control_backend'] = _clone_kwargs['control_backend'] kwargs['terminal_backend'] = _clone_kwargs['terminal_backend'] kwargs['proxy_backend'] = _clone_kwargs['proxy_backend'] kwargs['info_backend'] = _clone_kwargs['info_backend'] kwargs['list_backend'] = _clone_kwargs['list_backend'] kwargs['settings_backend'] = _clone_kwargs['settings_backend'] kwargs['printing_backend'] = _clone_kwargs['printing_backend'] kwargs['keep_controlsession_alive'] = _clone_kwargs['keep_controlsession_alive'] kwargs['client_rootdir'] = _clone_kwargs['client_rootdir'] kwargs['sessions_rootdir'] = _clone_kwargs['sessions_rootdir'] try: del kwargs['server'] except: pass try: del kwargs['profile_name'] except: pass try: del kwargs['profile_id'] except: pass # this if clause catches problems when x2golistsessions commands give weird results if not self.has_session_of_session_name(session_name) or re_register: if not (skip_pubapp_sessions and re.match('.*_stRPUBLISHED_.*', session_name)): session_uuid = self.register(server, profile_id, profile_name, session_name=session_name, virgin=False, **kwargs ) self(session_uuid).connected = True self.update_status(session_uuid=session_uuid, force_update=True, newly_connected=newly_connected) def register(self, server, profile_id, profile_name, session_name=None, control_backend=_BACKENDS['X2GoControlSession']['default'], terminal_backend=_BACKENDS['X2GoTerminalSession']['default'], info_backend=_BACKENDS['X2GoServerSessionInfo']['default'], list_backend=_BACKENDS['X2GoServerSessionList']['default'], proxy_backend=_BACKENDS['X2GoProxy']['default'], settings_backend=_BACKENDS['X2GoClientSettings']['default'], printing_backend=_BACKENDS['X2GoClientPrinting']['default'], client_rootdir=os.path.join(_LOCAL_HOME,_X2GO_CLIENT_ROOTDIR), sessions_rootdir=os.path.join(_LOCAL_HOME,_X2GO_SESSIONS_ROOTDIR), ssh_rootdir=os.path.join(_LOCAL_HOME,_X2GO_SSH_ROOTDIR), keep_controlsession_alive=True, add_to_known_hosts=False, known_hosts=None, **kwargs): """\ Register a new :class:`x2go.session.X2GoSession` instance with this :class:`x2go.registry.X2GoSessionRegistry`. :param server: hostname of X2Go server :type server: ``str`` :param profile_id: profile ID :type profile_id: ``str`` :param profile_name: profile name :type profile_name: ``str`` :param session_name: session name (if available) (Default value = None) :type session_name: ``str`` :param control_backend: X2Go control session backend to use (Default value = _BACKENDS['X2GoControlSession']['default']) :type control_backend: ``str`` :param terminal_backend: X2Go terminal session backend to use (Default value = _BACKENDS['X2GoTerminalSession']['default']) :type terminal_backend: ``str`` :param info_backend: X2Go session info backend to use (Default value = _BACKENDS['X2GoServerSessionInfo']['default']) :type info_backend: ``str`` :param list_backend: X2Go session list backend to use (Default value = _BACKENDS['X2GoServerSessionList']['default']) :type list_backend: ``str`` :param proxy_backend: X2Go proxy backend to use (Default value = _BACKENDS['X2GoProxy']['default']) :type proxy_backend: ``str`` :param settings_backend: X2Go client settings backend to use (Default value = _BACKENDS['X2GoClientSettings']['default']) :type settings_backend: ``str`` :param printing_backend: X2Go client printing backend to use (Default value = _BACKENDS['X2GoClientPrinting']['default']) :type printing_backend: ``str`` :param client_rootdir: client base dir (default: ~/.x2goclient) :type client_rootdir: ``str`` :param sessions_rootdir: sessions base dir (default: ~/.x2go) :type sessions_rootdir: ``str`` :param ssh_rootdir: ssh base dir (default: ~/.ssh) :type ssh_rootdir: ``str`` :param keep_controlsession_alive: On last :func:`X2GoSession.disconnect() ` keep the associated ``X2GoControlSession`` instance alive? :type keep_controlsession_alive: ``bool`` :param add_to_known_hosts: Auto-accept server host validity? :type add_to_known_hosts: ``bool`` :param known_hosts: the underlying Paramiko/SSH systems ``known_hosts`` file :type known_hosts: ``str`` :param kwargs: all other options will be passed on to the constructor of the to-be-instantiated :class:`x2go.session.X2GoSession` instance :type kwargs: ``dict`` :param _X2GO_CLIENT_ROOTDIR: :returns: the session UUID of the newly registered (or re-registered) session :rtype: ``str`` """ if profile_id not in list(self._profile_locks.keys()): self._profile_locks[profile_id] = threading.Lock() self._profile_locks[profile_id].acquire() control_session = None if profile_id in list(self.control_sessions.keys()): control_session = self.control_sessions[profile_id] try: _params = self.client_instance.session_profiles.to_session_params(profile_id) except x2go_exceptions.X2GoProfileException: _params = utils._convert_SessionProfileOptions_2_SessionParams(_X2GO_SESSIONPROFILE_DEFAULTS) for _k in list(_params.keys()): if _k in list(kwargs.keys()): _params[_k] = kwargs[_k] # allow injection of PKey objects (Paramiko's private SSH keys) if 'pkey' in kwargs: _params['pkey'] = kwargs['pkey'] if 'sshproxy_pkey' in kwargs: _params['sshproxy_pkey'] = kwargs['sshproxy_pkey'] # when starting a new session, we will try to use unused registered virgin sessions # depending on your application layout, there should either be one or no such virgin session at all _virgin_sessions = [ s for s in self.virgin_sessions_of_profile_name(profile_name, return_objects=True) if not s.activated ] if _virgin_sessions and not session_name: session_uuid = _virgin_sessions[0].get_uuid() self(session_uuid).activated = True self.logger('using already initially-registered yet-unused session %s' % session_uuid, loglevel=log.loglevel_NOTICE) else: session_uuid = self.get_session_of_session_name(session_name, match_profile_name=profile_name) if session_uuid is not None: self.logger('using already registered-by-session-name session %s' % session_uuid, loglevel=log.loglevel_NOTICE) if session_uuid is not None: self(session_uuid).activated = True self(session_uuid).update_params(_params) self(session_uuid).set_server(server) self(session_uuid).set_profile_name(profile_name) self._profile_locks[profile_id].release() return session_uuid try: del _params['server'] except: pass try: del _params['profile_name'] except: pass try: del _params['profile_id'] except: pass s = session.X2GoSession(server=server, control_session=control_session, profile_id=profile_id, profile_name=profile_name, session_name=session_name, control_backend=control_backend, terminal_backend=terminal_backend, info_backend=info_backend, list_backend=list_backend, proxy_backend=proxy_backend, settings_backend=settings_backend, printing_backend=printing_backend, client_rootdir=client_rootdir, sessions_rootdir=sessions_rootdir, ssh_rootdir=ssh_rootdir, keep_controlsession_alive=keep_controlsession_alive, add_to_known_hosts=add_to_known_hosts, known_hosts=known_hosts, client_instance=self.client_instance, logger=self.logger, **_params) session_uuid = s._X2GoSession__get_uuid() self.logger('registering X2Go session %s...' % profile_name, log.loglevel_NOTICE) self.logger('registering X2Go session with UUID %s' % session_uuid, log.loglevel_DEBUG) self.registry[session_uuid] = s if profile_id not in list(self.control_sessions.keys()): self.control_sessions[profile_id] = s.get_control_session() # make sure a new session is a non-master session unless promoted in update_status method self(session_uuid).unset_master_session() if control_session is None: self(session_uuid).do_auto_connect() self._profile_locks[profile_id].release() return session_uuid def has_session_of_session_name(self, session_name, match_profile_name=None): """\ Detect if we know about an :class:`x2go.session.X2GoSession` of name ````. :param session_name: name of session to be searched for :type session_name: ``str`` :param match_profile_name: a session's profile_name must match this profile name (Default value = None) :type match_profile_name: ``str`` :returns: ``True`` if a session of ```` has been found :rtype: ``bool`` """ return bool(self.get_session_of_session_name(session_name, match_profile_name=match_profile_name)) def get_session_of_session_name(self, session_name, return_object=False, match_profile_name=None): """\ Retrieve the :class:`x2go.session.X2GoSession` instance with session name ````. :param session_name: name of session to be retrieved :type session_name: ``str`` :param return_object: if ``False`` the session UUID hash will be returned, if ``True`` the :class:`x2go.session.X2GoSession` instance will be returned (Default value = False) :type return_object: ``bool`` :param match_profile_name: returned sessions must match this profile name (Default value = None) :type match_profile_name: ``str`` :returns: :class:`x2go.session.X2GoSession` object or its representing session UUID hash :rtype: :class:`x2go.session.X2GoSession` instance or ``str`` :raises X2GoSessionRegistryException: if there is more than one :class:`x2go.session.X2GoSession` registered for ```` within the same :class:`x2go.client.X2GoClient` instance. This should never happen! """ if match_profile_name is None: reg_sessions = self.registered_sessions() else: reg_sessions = self.registered_sessions_of_profile_name(match_profile_name) found_sessions = [ s for s in reg_sessions if s.session_name == session_name and s.session_name is not None ] if len(found_sessions) == 1: session = found_sessions[0] if return_object: return session else: return session.get_uuid() elif len(found_sessions) > 1: raise x2go_exceptions.X2GoSessionRegistryException('there should only be one registered session of name ,,%s\'\'' % session_name) else: return None def _sessionsWithState(self, state, return_objects=True, return_profile_names=False, return_profile_ids=False, return_session_names=False): if state == 'associated': sessions = [ ts for ts in list(self.registry.values()) if ts.has_terminal_session() ] elif state == 'registered': sessions = [ ts for ts in list(self.registry.values()) ] else: sessions = [ ts for ts in list(self.registry.values()) if eval('ts.%s' % state) ] if return_profile_names: profile_names = [] for this_session in sessions: if this_session.profile_name and this_session.profile_name not in profile_names: profile_names.append(this_session.profile_name) return profile_names elif return_profile_ids: profile_ids = [] for this_session in sessions: if this_session.profile_id and this_session.profile_id not in profile_ids: profile_ids.append(this_session.profile_id) return profile_ids elif return_session_names: session_names = [] for this_session in sessions: if this_session.session_name and this_session.session_name not in session_names: session_names.append(this_session.session_name) return session_names elif return_objects: return sessions else: return [s.get_uuid() for s in sessions ] def connected_sessions(self, return_objects=True, return_profile_names=False, return_profile_ids=False, return_session_names=False): """\ Retrieve a list of sessions that the underlying :class:`x2go.client.X2GoClient` instances is currently connected to. If none of the ``return_*`` options is specified a list of session UUID hashes will be returned. :param return_objects: return as list of :class:`x2go.session.X2GoSession` instances (Default value = True) :type return_objects: ``bool`` :param return_profile_names: return as list of profile names (Default value = False) :type return_profile_names: ``bool`` :param return_profile_ids: return as list of profile IDs (Default value = False) :type return_profile_ids: ``bool`` :param return_session_names: return as list of X2Go session names (Default value = False) :type return_session_names: ``bool`` :returns: a session list (as UUID hashes, objects, profile names/IDs or session names) :rtype: ``list`` """ return self._sessionsWithState('connected', return_objects=return_objects, return_profile_names=return_profile_names, return_profile_ids=return_profile_ids, return_session_names=return_session_names) def associated_sessions(self, return_objects=True, return_profile_names=False, return_profile_ids=False, return_session_names=False): """\ Retrieve a list of sessions that are currently associated by an ``X2GoTerminalSession*`` to the underlying :class:`x2go.client.X2GoClient` instance. If none of the ``return_*`` options is specified a list of session UUID hashes will be returned. :param return_objects: return as list of :class:`x2go.session.X2GoSession` instances (Default value = True) :type return_objects: ``bool`` :param return_profile_names: return as list of profile names (Default value = False) :type return_profile_names: ``bool`` :param return_profile_ids: return as list of profile IDs (Default value = False) :type return_profile_ids: ``bool`` :param return_session_names: return as list of X2Go session names (Default value = False) :type return_session_names: ``bool`` :returns: a session list (as UUID hashes, objects, profile names/IDs or session names) :rtype: ``list`` """ return self._sessionsWithState('associated', return_objects=return_objects, return_profile_names=return_profile_names, return_profile_ids=return_profile_ids, return_session_names=return_session_names) def virgin_sessions(self, return_objects=True, return_profile_names=False, return_profile_ids=False, return_session_names=False): """\ Retrieve a list of sessions that are currently still in virgin state (not yet connected, associated etc.). If none of the ``return_*`` options is specified a list of session UUID hashes will be returned. :param return_objects: return as list of :class:`x2go.session.X2GoSession` instances (Default value = True) :type return_objects: ``bool`` :param return_profile_names: return as list of profile names (Default value = False) :type return_profile_names: ``bool`` :param return_profile_ids: return as list of profile IDs (Default value = False) :type return_profile_ids: ``bool`` :param return_session_names: return as list of X2Go session names (Default value = False) :type return_session_names: ``bool`` :returns: a session list (as UUID hashes, objects, profile names/IDs or session names) :rtype: ``list`` """ return self._sessionsWithState('virgin', return_objects=return_objects, return_profile_names=return_profile_names, return_profile_ids=return_profile_ids, return_session_names=return_session_names) def running_sessions(self, return_objects=True, return_profile_names=False, return_profile_ids=False, return_session_names=False): """\ Retrieve a list of sessions that are currently in running state. If none of the ``return_*`` options is specified a list of session UUID hashes will be returned. :param return_objects: return as list of :class:`x2go.session.X2GoSession` instances (Default value = True) :type return_objects: ``bool`` :param return_profile_names: return as list of profile names (Default value = False) :type return_profile_names: ``bool`` :param return_profile_ids: return as list of profile IDs (Default value = False) :type return_profile_ids: ``bool`` :param return_session_names: return as list of X2Go session names (Default value = False) :type return_session_names: ``bool`` :returns: a session list (as UUID hashes, objects, profile names/IDs or session names) :rtype: ``list`` """ return self._sessionsWithState('running', return_objects=return_objects, return_profile_names=return_profile_names, return_profile_ids=return_profile_ids, return_session_names=return_session_names) def suspended_sessions(self, return_objects=True, return_profile_names=False, return_profile_ids=False, return_session_names=False): """\ Retrieve a list of sessions that are currently in suspended state. If none of the ``return_*`` options is specified a list of session UUID hashes will be returned. :param return_objects: return as list of :class:`x2go.session.X2GoSession` instances (Default value = True) :type return_objects: ``bool`` :param return_profile_names: return as list of profile names (Default value = False) :type return_profile_names: ``bool`` :param return_profile_ids: return as list of profile IDs (Default value = False) :type return_profile_ids: ``bool`` :param return_session_names: return as list of X2Go session names (Default value = False) :type return_session_names: ``bool`` :returns: a session list (as UUID hashes, objects, profile names/IDs or session names) :rtype: ``list`` """ return self._sessionsWithState('suspended', return_objects=return_objects, return_profile_names=return_profile_names, return_profile_ids=return_profile_ids, return_session_names=return_session_names) def terminated_sessions(self, return_objects=True, return_profile_names=False, return_profile_ids=False, return_session_names=False): """\ Retrieve a list of sessions that have terminated recently. If none of the ``return_*`` options is specified a list of session UUID hashes will be returned. :param return_objects: return as list of :class:`x2go.session.X2GoSession` instances (Default value = True) :type return_objects: ``bool`` :param return_profile_names: return as list of profile names (Default value = False) :type return_profile_names: ``bool`` :param return_profile_ids: return as list of profile IDs (Default value = False) :type return_profile_ids: ``bool`` :param return_session_names: return as list of X2Go session names (Default value = False) :type return_session_names: ``bool`` :returns: a session list (as UUID hashes, objects, profile names/IDs or session names) :rtype: ``list`` """ return self._sessionsWithState('terminated', return_objects=return_objects, return_profile_names=return_profile_names, return_profile_ids=return_profile_ids, return_session_names=return_session_names) @property def has_running_sessions(self): """\ Equals ``True`` if the underlying :class:`x2go.client.X2GoClient` instance has any running sessions at hand. """ return self.running_sessions() and len(self.running_sessions()) > 0 @property def has_suspended_sessions(self): """\ Equals ``True`` if the underlying :class:`x2go.client.X2GoClient` instance has any suspended sessions at hand. """ return self.suspended_sessions and len(self.suspended_sessions) > 0 def registered_sessions(self, return_objects=True, return_profile_names=False, return_profile_ids=False, return_session_names=False): """\ Retrieve a list of all registered sessions. If none of the ``return_*`` options is specified a list of session UUID hashes will be returned. :param return_objects: return as list of :class:`x2go.session.X2GoSession` instances (Default value = True) :type return_objects: ``bool`` :param return_profile_names: return as list of profile names (Default value = False) :type return_profile_names: ``bool`` :param return_profile_ids: return as list of profile IDs (Default value = False) :type return_profile_ids: ``bool`` :param return_session_names: return as list of X2Go session names (Default value = False) :type return_session_names: ``bool`` :returns: a session list (as UUID hashes, objects, profile names/IDs or session names) :rtype: ``list`` """ return self._sessionsWithState('registered', return_objects=return_objects, return_profile_names=return_profile_names, return_profile_ids=return_profile_ids, return_session_names=return_session_names) def non_running_sessions(self, return_objects=True, return_profile_names=False, return_profile_ids=False, return_session_names=False): """\ Retrieve a list of sessions that are currently _NOT_ in running state. If none of the ``return_*`` options is specified a list of session UUID hashes will be returned. :param return_objects: return as list of :class:`x2go.session.X2GoSession` instances (Default value = True) :type return_objects: ``bool`` :param return_profile_names: return as list of profile names (Default value = False) :type return_profile_names: ``bool`` :param return_profile_ids: return as list of profile IDs (Default value = False) :type return_profile_ids: ``bool`` :param return_session_names: return as list of X2Go session names (Default value = False) :type return_session_names: ``bool`` :returns: a session list (as UUID hashes, objects, profile names/IDs or session names) :rtype: ``list`` """ return [ s for s in self.registered_sessions(return_objects=return_objects, return_profile_names=return_profile_names, return_profile_ids=return_profile_ids, return_session_names=return_session_names) if s not in self.running_sessions(return_objects=return_objects, return_profile_names=return_profile_names, return_profile_ids=return_profile_ids, return_session_names=return_session_names) ] def connected_sessions_of_profile_name(self, profile_name, return_objects=True, return_session_names=False): """\ For a given session profile name retrieve a list of sessions that are currently connected to the profile's X2Go server. If none of the ``return_*`` options is specified a list of session UUID hashes will be returned. :param profile_name: session profile name :type profile_name: ``str`` :param return_objects: return as list of :class:`x2go.session.X2GoSession` instances (Default value = True) :type return_objects: ``bool`` :param return_session_names: return as list of X2Go session names (Default value = False) :type return_session_names: ``bool`` :returns: a session list (as UUID hashes, objects or session names) :rtype: ``list`` """ if return_objects: return self.connected_sessions() and [ s for s in self.connected_sessions() if s.get_profile_name() == profile_name ] elif return_session_names: return self.connected_sessions() and [ s.session_name for s in self.connected_sessions() if s.get_profile_name() == profile_name ] else: return self.connected_sessions() and [ s.get_uuid() for s in self.connected_sessions() if s.get_profile_name() == profile_name ] def associated_sessions_of_profile_name(self, profile_name, return_objects=True, return_session_names=False): """\ For a given session profile name retrieve a list of sessions that are currently associated by an ``X2GoTerminalSession*`` to this :class:`x2go.client.X2GoClient` instance. If none of the ``return_*`` options is specified a list of session UUID hashes will be returned. :param profile_name: session profile name :type profile_name: ``str`` :param return_objects: return as list of :class:`x2go.session.X2GoSession` instances (Default value = True) :type return_objects: ``bool`` :param return_session_names: return as list of X2Go session names (Default value = False) :type return_session_names: ``bool`` :returns: a session list (as UUID hashes, objects or session names) :rtype: ``list`` """ if return_objects: return self.associated_sessions() and [ s for s in self.associated_sessions() if s.get_profile_name() == profile_name ] elif return_session_names: return self.associated_sessions() and [ s.session_name for s in self.associated_sessions() if s.get_profile_name() == profile_name ] else: return self.associated_sessions() and [ s.get_uuid() for s in self.associated_sessions() if s.get_profile_name() == profile_name ] def pubapp_sessions_of_profile_name(self, profile_name, return_objects=True, return_session_names=False): """\ For a given session profile name retrieve a list of sessions that can be providers for published application list. If none of the ``return_*`` options is specified a list of session UUID hashes will be returned. :param profile_name: session profile name :type profile_name: ``str`` :param return_objects: return as list of :class:`x2go.session.X2GoSession` instances (Default value = True) :type return_objects: ``bool`` :param return_session_names: return as list of X2Go session names (Default value = False) :type return_session_names: ``bool`` :returns: a session list (as UUID hashes, objects or session names) :rtype: ``list`` """ if return_objects: return self.associated_sessions_of_profile_name(profile_name) and [ s for s in self.associated_sessions_of_profile_name(profile_name) if s.is_published_applications_provider() ] elif return_session_names: return self.associated_sessions_of_profile_name(profile_name) and [ s.session_name for s in self.associated_sessions_of_profile_name(profile_name) if s.is_published_applications_provider() ] else: return self.associated_sessions_of_profile_name(profile_name) and [ s.get_uuid() for s in self.associated_sessions_of_profile_name(profile_name) if s.is_published_applications_provider() ] def registered_sessions_of_profile_name(self, profile_name, return_objects=True, return_session_names=False): """\ For a given session profile name retrieve a list of sessions that are currently registered with this :class:`x2go.client.X2GoClient` instance. If none of the ``return_*`` options is specified a list of session UUID hashes will be returned. :param profile_name: session profile name :type profile_name: ``str`` :param return_objects: return as list of :class:`x2go.session.X2GoSession` instances (Default value = True) :type return_objects: ``bool`` :param return_session_names: return as list of X2Go session names (Default value = False) :type return_session_names: ``bool`` :returns: a session list (as UUID hashes, objects or session names) :rtype: ``list`` """ if return_objects: return self.registered_sessions() and [ s for s in self.registered_sessions() if s.get_profile_name() == profile_name ] elif return_session_names: return self.registered_sessions() and [ s.session_name for s in self.registered_sessions() if s.get_profile_name() == profile_name ] else: return self.registered_sessions() and [ s.get_uuid() for s in self.registered_sessions() if s.get_profile_name() == profile_name ] def virgin_sessions_of_profile_name(self, profile_name, return_objects=True, return_session_names=False): """\ For a given session profile name retrieve a list of sessions that are registered with this :class:`x2go.client.X2GoClient` instance but have not yet been started (i.e. sessions that are in virgin state). If none of the ``return_*`` options is specified a list of session UUID hashes will be returned. :param profile_name: session profile name :type profile_name: ``str`` :param return_objects: return as list of :class:`x2go.session.X2GoSession` instances (Default value = True) :type return_objects: ``bool`` :param return_session_names: return as list of X2Go session names (Default value = False) :type return_session_names: ``bool`` :returns: a session list (as UUID hashes, objects or session names) :rtype: ``list`` """ if return_objects: return self.virgin_sessions() and [ s for s in self.virgin_sessions() if s.get_profile_name() == profile_name ] elif return_session_names: return self.virgin_sessions() and [ s.session_name for s in self.virgin_sessions() if s.get_profile_name() == profile_name ] else: return self.virgin_sessions() and [ s.get_uuid() for s in self.virgin_sessions() if s.get_profile_name() == profile_name ] def running_sessions_of_profile_name(self, profile_name, return_objects=True, return_session_names=False): """\ For a given session profile name retrieve a list of sessions that are currently running. If none of the ``return_*`` options is specified a list of session UUID hashes will be returned. :param profile_name: session profile name :type profile_name: ``str`` :param return_objects: return as list of :class:`x2go.session.X2GoSession` instances (Default value = True) :type return_objects: ``bool`` :param return_session_names: return as list of X2Go session names (Default value = False) :type return_session_names: ``bool`` :returns: a session list (as UUID hashes, objects or session names) :rtype: ``list`` """ if return_objects: return self.running_sessions() and [ s for s in self.running_sessions() if s.get_profile_name() == profile_name ] elif return_session_names: return self.running_sessions() and [ s.session_name for s in self.running_sessions() if s.get_profile_name() == profile_name ] else: return self.running_sessions() and [ s.get_uuid() for s in self.running_sessions() if s.get_profile_name() == profile_name ] def suspended_sessions_of_profile_name(self, profile_name, return_objects=True, return_session_names=False): """\ For a given session profile name retrieve a list of sessions that are currently in suspended state. If none of the ``return_*`` options is specified a list of session UUID hashes will be returned. :param profile_name: session profile name :type profile_name: ``str`` :param return_objects: return as list of :class:`x2go.session.X2GoSession` instances (Default value = True) :type return_objects: ``bool`` :param return_session_names: return as list of X2Go session names (Default value = False) :type return_session_names: ``bool`` :returns: a session list (as UUID hashes, objects or session names) :rtype: ``list`` """ if return_objects: return self.suspended_sessions() and [ s for s in self.suspended_sessions() if s.get_profile_name() == profile_name ] elif return_session_names: return self.suspended_sessions() and [ s.session_name for s in self.suspended_sessions() if s.get_profile_name() == profile_name ] else: return self.suspended_sessions() and [ s.get_uuid() for s in self.suspended_sessions() if s.get_profile_name() == profile_name ] def control_session_of_profile_name(self, profile_name): """\ For a given session profile name retrieve a the corresponding ``X2GoControlSession*`` instance. :param profile_name: session profile name :type profile_name: ``str`` :returns: contol session instance :rtype: ``X2GoControlSession*`` instance """ _sessions = self.registered_sessions_of_profile_name(profile_name, return_objects=True) if _sessions: session = _sessions[0] return session.control_session return None @property def connected_control_sessions(self): """\ Equals a list of all currently connected control sessions. """ return [ c for c in list(self.control_sessions.values()) if c.is_connected() ] def connected_profiles(self, use_paramiko=False, return_profile_ids=True, return_profile_names=False): """\ Retrieve a list of all currently connected session profiles. :param use_paramiko: send query directly to the Paramiko/SSH layer (Default value = False) :type use_paramiko: ``bool`` :param return_profile_names: return as list of profile names (Default value = False) :type return_profile_names: ``bool`` :param return_profile_ids: return as list of profile IDs (Default value = True) :type return_profile_ids: ``bool`` :returns: list of connected session profiles :rtype: ``list`` """ if use_paramiko: return [ p for p in list(self.control_sessions.keys()) if self.control_sessions[p].is_connected() ] else: return self.connected_sessions(return_profile_ids=return_profile_ids, return_profile_names=return_profile_names) def get_master_session(self, profile_name, return_object=True, return_session_name=False): """\ Retrieve the master session of a specific profile. :param profile_name: the profile name that we query the master session of :type profile_name: ``str`` :param return_object: return :class:`x2go.session.X2GoSession` instance (Default value = True) :type return_object: ``bool`` :param return_session_name: return X2Go session name (Default value = False) :type return_session_name: ``bool`` :returns: a session list (as UUID hashes, objects, profile names/IDs or session names) :rtype: ``list`` """ if profile_name not in self.connected_profiles(return_profile_names=True): return None if profile_name not in list(self.master_sessions.keys()) or self.master_sessions[profile_name] is None: return None _session = self.master_sessions[profile_name] if not _session.is_master_session(): del self.master_sessions[profile_name] return None if return_object: return _session elif return_session_name: return _session.get_session_name() else: return _session.get_uuid() python-x2go-0.6.1.4/x2go/rforward.py0000644000000000000000000004126714470264762014051 0ustar # Copyright (C) 2010-2023 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. """\ X2Go reverse SSH/Paramiko tunneling provides X2Go sound, X2Go printing and X2Go sshfs for folder sharing and mounting remote devices in X2Go terminal server sessions. """ __NAME__ = 'x2gorevtunnel-pylib' __package__ = 'x2go' __name__ = 'x2go.rforward' # modules import copy import threading import gevent import paramiko # gevent/greenlet from gevent import select, socket, Timeout # Python X2Go modules from . import log def x2go_transport_tcp_handler(chan, origin, server): """\ An X2Go customized TCP handler for the Paramiko/SSH ``Transport()`` class. Incoming channels will be put into Paramiko's default accept queue. This corresponds to the default behaviour of Paramiko's ``Transport`` class. However, additionally this handler function checks the server port of the incoming channel and detects if there are Paramiko/SSH reverse forwarding tunnels waiting for the incoming channels. The Paramiko/SSH reverse forwarding tunnels are initiated by an :class:`x2go.session.X2GoSession` instance (currently supported: reverse tunneling auf audio data, reverse tunneling of SSH requests). If the server port of an incoming Paramiko/SSH channel matches the configured port of an :class:`x2go.rforward.X2GoRevFwTunnel` instance, this instance gets notified of the incoming channel and a new :class:`x2go.rforward.X2GoRevFwChannelThread` is started. This :class:`x2go.rforward.X2GoRevFwChannelThread` then takes care of the new channel's incoming data stream. :param chan: a Paramiko channel object :type chan: ``paramiko.Channel`` object :param origin: host/port tuple where a connection originates from :type origin: ``tuple`` :param server: host/port tuple where to connect to :type server: ``tuple`` """ (origin_addr, origin_port) = origin (server_addr, server_port) = server transport = chan.get_transport() transport._queue_incoming_channel(chan) rev_tuns = transport.reverse_tunnels for session_name in list(rev_tuns.keys()): if int(server_port) in [ int(tunnel[0]) for tunnel in list(rev_tuns[session_name].values()) ]: if rev_tuns[session_name]['snd'] is not None and int(server_port) == int(rev_tuns[session_name]['snd'][0]): rev_tuns[session_name]['snd'][1].notify() elif rev_tuns[session_name]['sshfs'] is not None and int(server_port) == int(rev_tuns[session_name]['sshfs'][0]): rev_tuns[session_name]['sshfs'][1].notify() class X2GoRevFwTunnel(threading.Thread): """\ :class:`x2go.rforward.X2GoRevFwTunnel` class objects are used to reversely tunnel X2Go audio, X2Go printing and X2Go folder sharing / device mounting through Paramiko/SSH. """ def __init__(self, server_port, remote_host, remote_port, ssh_transport, session_instance=None, logger=None, loglevel=log.loglevel_DEFAULT): """\ Setup a reverse tunnel through Paramiko/SSH. After the reverse tunnel has been setup up with :func:`X2GoRevFwTunnel.start() ` it waits for notification from :func:`X2GoRevFwTunnel.notify() ` to accept incoming channels. This notification (:func:`X2GoRevFwTunnel.notify() ` gets called from within the transport's TCP handler function :func:`x2go_transport_tcp_handler()` of the :class:`x2go.session.X2GoSession` instance. :param server_port: the TCP/IP port on the X2Go server (starting point of the tunnel), normally some number above 30000 :type server_port: int :param remote_host: the target address for reversely tunneled traffic. With X2Go this should always be set to the localhost (IPv4) address. :type remote_host: str :param remote_port: the TCP/IP port on the X2Go client (end point of the tunnel), normally an application's standard port (22 for SSH, 4713 for pulse audio, etc.) :type remote_port: int :param ssh_transport: the :class:`x2go.session.X2GoSession`'s Paramiko/SSH transport instance :type ssh_transport: ``paramiko.Transport`` instance :param logger: you can pass an :class:`x2go.log.X2GoLogger` object to the :class:`x2go.rforward.X2GoRevFwTunnel` constructor :type logger: :class:`x2go.log.X2GoLogger` instance :param loglevel: if no :class:`x2go.log.X2GoLogger` object has been supplied a new one will be constructed with the given loglevel :type loglevel: int """ if logger is None: self.logger = log.X2GoLogger(loglevel=loglevel) else: self.logger = copy.deepcopy(logger) self.logger.tag = __NAME__ self.server_port = server_port self.remote_host = remote_host self.remote_port = remote_port self.ssh_transport = ssh_transport self.session_instance = session_instance self.open_channels = {} self.incoming_channel = threading.Condition() threading.Thread.__init__(self) self.daemon = True self._accept_channels = True def __del__(self): """\ Class destructor. """ self.stop_thread() self.cancel_port_forward('', self.server_port) def cancel_port_forward(self, address, port): """\ Cancel a port forwarding request. This cancellation request is sent to the server and on the server the port forwarding should be unregistered. :param address: remote server address :type address: ``str`` :param port: remote port :type port: ``int`` """ timeout = Timeout(10) timeout.start() try: self.ssh_transport.global_request('cancel-tcpip-forward', (address, port), wait=True) except: pass finally: timeout.cancel() def pause(self): """\ Prevent acceptance of new incoming connections through the Paramiko/SSH reverse forwarding tunnel. Also, any active connection on this :class:`x2go.rforward.X2GoRevFwTunnel` instance will be closed immediately, if this method is called. """ if self._accept_channels == True: self.cancel_port_forward('', self.server_port) self._accept_channels = False self.logger('paused thread: %s' % repr(self), loglevel=log.loglevel_DEBUG) def resume(self): """\ Resume operation of the Paramiko/SSH reverse forwarding tunnel and continue accepting new incoming connections. """ if self._accept_channels == False: self._accept_channels = True self._requested_port = self.ssh_transport.request_port_forward('127.0.0.1', self.server_port, handler=x2go_transport_tcp_handler) self.logger('resumed thread: %s' % repr(self), loglevel=log.loglevel_DEBUG) def notify(self): """\ Notify an :class:`x2go.rforward.X2GoRevFwTunnel` instance of an incoming Paramiko/SSH channel. If an incoming reverse tunnel channel appropriate for this instance has been detected, this method gets called from the :class:`x2go.session.X2GoSession`'s transport TCP handler. The sent notification will trigger a ``thread.Condition()`` waiting for notification in :func:`X2GoRevFwTunnel.run() `. """ self.incoming_channel.acquire() self.logger('notifying thread of incoming channel: %s' % repr(self), loglevel=log.loglevel_DEBUG) self.incoming_channel.notify() self.incoming_channel.release() def stop_thread(self): """\ Stops this :class:`x2go.rforward.X2GoRevFwTunnel` thread completely. """ self.pause() self._keepalive = False self.logger('stopping thread: %s' % repr(self), loglevel=log.loglevel_DEBUG) self.notify() def _request_port_forwarding(self): try: self._requested_port = self.ssh_transport.request_port_forward('127.0.0.1', self.server_port, handler=x2go_transport_tcp_handler) except paramiko.SSHException: # if port forward request fails, we try to tell the server to cancel all foregoing port forward requests on # self.server_port self.cancel_port_forward('', self.server_port) gevent.sleep(1) try: self._requested_port = self.ssh_transport.request_port_forward('127.0.0.1', self.server_port, handler=x2go_transport_tcp_handler) except paramiko.SSHException as e: if self.session_instance: self.session_instance.HOOK_rforward_request_denied(server_port=self.server_port) else: self.logger('Encountered SSHException: %s (for reverse TCP port forward with local destination port %s' % (str(e), self.server_port), loglevel=log.loglevel_WARN) def run(self): """\ This method gets run once an :class:`x2go.rforward.X2GoRevFwTunnel` has been started with its :func:`start()` method. Use :class:`x2go.rforward.X2GoRevFwTunnel`.stop_thread() to stop the reverse forwarding tunnel again. You can also temporarily lock the tunnel down with :func:`X2GoRevFwTunnel.pause() ` and :func:`X2GoRevFwTunnel.resume() `). :func:`X2GoRevFwTunnel.run() ` waits for notifications of an appropriate incoming Paramiko/SSH channel (issued by :func:`X2GoRevFwTunnel.notify() `). Appropriate in this context means, that its start point on the X2Go server matches the class's property ``server_port``. Once a new incoming channel gets announced by the :func:`notify()` method, a new :class:`x2go.rforward.X2GoRevFwChannelThread` instance will be initialized. As a data stream handler, the function :func:`x2go_rev_forward_channel_handler()` will be used. The channel will last till the connection gets dropped on the X2Go server side or until the tunnel gets paused by an :func:`X2GoRevFwTunnel.pause() ` call or stopped via the :func:`X2GoRevFwTunnel.stop_thread() ` method. """ self._request_port_forwarding() self._keepalive = True while self._keepalive: self.incoming_channel.acquire() self.logger('waiting for incoming data channel on X2Go server port: [127.0.0.1]:%s' % self.server_port, loglevel=log.loglevel_DEBUG) self.incoming_channel.wait() if self._keepalive: self.logger('detected incoming data channel on X2Go server port: [127.0.0.1]:%s' % self.server_port, loglevel=log.loglevel_DEBUG) _chan = self.ssh_transport.accept() self.logger('data channel %s for server port [127.0.0.1]:%s is up' % (_chan, self.server_port), loglevel=log.loglevel_DEBUG) else: self.logger('closing down rev forwarding tunnel on remote end [127.0.0.1]:%s' % self.server_port, loglevel=log.loglevel_DEBUG) self.incoming_channel.release() if self._accept_channels and self._keepalive: _new_chan_thread = X2GoRevFwChannelThread(_chan, (self.remote_host, self.remote_port), target=x2go_rev_forward_channel_handler, kwargs={ 'chan': _chan, 'addr': self.remote_host, 'port': self.remote_port, 'parent_thread': self, 'logger': self.logger, } ) _new_chan_thread.start() self.open_channels['[%s]:%s' % _chan.origin_addr] = _new_chan_thread def x2go_rev_forward_channel_handler(chan=None, addr='', port=0, parent_thread=None, logger=None, ): """\ Handle the data stream of a requested channel that got set up by a :class:`x2go.rforward.X2GoRevFwTunnel` (Paramiko/SSH reverse forwarding tunnel). The channel (and the corresponding connections) close either ... - ... if the connecting application closes the connection and thus, drops the channel, or - ... if the :class:`x2go.rforward.X2GoRevFwTunnel` parent thread gets paused. The call of :func:`X2GoRevFwTunnel.pause() ` on the instance can be used to shut down all incoming tunneled SSH connections associated to this :class:`x2go.rforward.X2GoRevFwTunnel` instance from within a Python X2Go application. :param chan: channel (Default value = None) :type chan: ``class`` :param addr: bind address (Default value = '') :type addr: ``str`` :param port: bind port (Default value = 0) :type port: ``int`` :param parent_thread: the calling :class:`x2go.rforward.X2GoRevFwTunnel` instance (Default value = None) :type parent_thread: :class:`x2go.rforward.X2GoRevFwTunnel` instance :param logger: you can pass an :class:`x2go.log.X2GoLogger` object to the :class:`x2go.rforward.X2GoRevFwTunnel` constructor (Default value = None) :type logger: :class:`x2go.log.X2GoLogger` instance """ fw_socket = socket.socket() fw_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) if logger is None: def _dummy_logger(msg, l): pass logger = _dummy_logger try: fw_socket.connect((addr, port)) except Exception as e: logger('Reverse forwarding request to %s:%d failed: %r' % (addr, port, e), loglevel=log.loglevel_INFO) return logger('Connected! Reverse tunnel open %r -> %r -> %r' % (chan.origin_addr, chan.getpeername(), (addr, port)), loglevel=log.loglevel_INFO) n = 0 while parent_thread._accept_channels: r, w, x = select.select([fw_socket, chan], [], []) try: if fw_socket in r: data = fw_socket.recv(1024) if len(data) == 0: break chan.send(data) if chan in r: data = chan.recv(1024) if len(data) == 0: break fw_socket.send(data) except socket.error as e: logger('Reverse tunnel %s encoutered socket error: %s' % (chan, str(e)), loglevel=log.loglevel_WARN) n += 1 if n >= 1024: break chan.close() fw_socket.close() logger('Reverse tunnel %s closed from %r' % (chan, chan.origin_addr,), loglevel=log.loglevel_INFO) class X2GoRevFwChannelThread(threading.Thread): """\ Starts a thread for each incoming Paramiko/SSH data channel trough the reverse forwarding tunnel. """ def __init__(self, channel, remote=None, **kwargs): """\ Initializes a reverse forwarding channel thread. :param channel: incoming Paramiko/SSH channel from the :class:`x2go.session.X2GoSession`'s transport accept queue :type channel: class :param remote: tuple (addr, port) that specifies the data endpoint of the channel :type remote: ``tuple(str, int)`` """ self.channel = channel if remote is not None: self.remote_host = remote[0] self.remote_port = remote[1] threading.Thread.__init__(self, **kwargs) self.daemon = True python-x2go-0.6.1.4/x2go/session.py0000644000000000000000000037004314470264762013703 0ustar # -*- coding: utf-8 -*- # Copyright (C) 2010-2023 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. """\ X2GoSession class - a public API of Python X2Go, handling standalone X2Go sessions. This class is normally embedded into the context of an :class:`x2go.client.X2GoClient` instance, but it is also possible to address an :class:`x2go.session.X2GoSession` directly via this class. To launch a session manually from the Python interactive shell, perform these simple steps:: Python 3.5.3 (default, Jan 19 2017, 14:11:04) [GCC 6.3.0 20170118] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import x2go >>> import gevent >>> s = x2go.session.X2GoSession() >>> s.set_server('') >>> s.set_port() >>> s.connect('', '') [] (x2gocontrolsession-pylib) NOTICE: connecting to []: [] (x2gosession-pylib) NOTICE: SSH host key verification for host []: with SSH-RSA fingerprint ,,'' initiated. We are seeing this X2Go server for the first time. [] (x2gosession-pylib) WARN: HOOK_check_host_dialog: host check requested for []: with SSH-RSA fingerprint: ,,''. Automatically adding host as known host. True >>> s.start(cmd="LXDE") True >>> while True: gevent.sleep(1) """ __NAME__ = 'x2gosession-pylib' __package__ = 'x2go' __name__ = 'x2go.session' import os import copy import types import uuid import time import gevent import re import threading import base64 # FIXME: we need the list of keys from a potentially used SSH agent. This part of code has to be moved into the control session code import paramiko # Python X2Go modules from . import defaults from . import log from . import utils from . import x2go_exceptions from .defaults import X2GOCLIENT_OS as _X2GOCLIENT_OS from .defaults import LOCAL_HOME as _LOCAL_HOME from .defaults import X2GO_CLIENT_ROOTDIR as _X2GO_CLIENT_ROOTDIR from .defaults import X2GO_SESSIONS_ROOTDIR as _X2GO_SESSIONS_ROOTDIR from .defaults import X2GO_SSH_ROOTDIR as _X2GO_SSH_ROOTDIR from .defaults import BACKENDS as _BACKENDS from .defaults import SUPPORTED_SOUND, SUPPORTED_PRINTING, SUPPORTED_FOLDERSHARING, SUPPORTED_MIMEBOX, SUPPORTED_TELEKINESIS _X2GO_SESSION_PARAMS = ('use_sshproxy', 'sshproxy_reuse_authinfo', 'profile_id', 'session_name', 'auto_start_or_resume', 'auto_connect', 'printing', 'allow_mimebox', 'mimebox_extensions', 'mimebox_action', 'allow_share_local_folders', 'share_local_folders', 'restore_shared_local_folders', 'control_backend', 'terminal_backend', 'info_backend', 'list_backend', 'proxy_backend', 'settings_backend', 'printing_backend', 'client_rootdir', 'sessions_rootdir', 'ssh_rootdir', 'keep_controlsession_alive', 'add_to_known_hosts', 'known_hosts', 'forward_sshagent', 'connected', 'virgin', 'running', 'suspended', 'terminated', 'faulty' 'client_instance', ) """A list of allowed X2Go pure session parameters (i.e. parameters that are passed on neither to an X2GoControlSession, X2GoSSHProxy nor an X2GoControlSession object.""" # options of the paramiko.SSHClient().connect() method, any option that is allowed for a terminal session instance _X2GO_TERMINAL_PARAMS = ('geometry', 'depth', 'link', 'pack', 'dpi', 'cache_type', 'kbtype', 'kblayout', 'kbvariant', 'clipboard', 'xinerama', 'session_type', 'kdrive', 'snd_system', 'snd_port', 'cmd', 'set_session_title', 'session_title', 'rdp_server', 'rdp_options', 'applications', 'xdmcp_server', 'rootdir', 'loglevel', 'profile_name', 'profile_id', 'print_action', 'print_action_args', 'convert_encoding', 'client_encoding', 'server_encoding', 'proxy_options', 'published_applications', 'published_applications_no_submenus', 'logger', 'control_backend', 'terminal_backend', 'proxy_backend', 'profiles_backend', 'settings_backend', 'printing_backend', ) """A list of allowed X2Go terminal session parameters.""" _X2GO_SSHPROXY_PARAMS = ('sshproxy_host', 'sshproxy_port', 'sshproxy_user', 'sshproxy_password', 'sshproxy_key_filename', 'sshproxy_pkey', 'sshproxy_passphrase', 'sshproxy_look_for_keys', 'sshproxy_allow_agent', 'sshproxy_tunnel', ) """A list of allowed X2Go SSH proxy parameters.""" class X2GoSession(object): """\ Public API class for launching X2Go sessions. Recommended is to manage X2Go sessions from within an :class:`x2go.client.X2GoClient` instance. However, Python X2Go is designed in a way that it also allows the management of singel :class:`x2go.session.X2GoSession` instance. Thus, you can use the :class:`x2go.session.X2GoSession` class to manually set up X2Go sessions without :class:`x2go.client.X2GoClient` context (session registry, session list cache, auto-registration of new sessions etc.). """ def __init__(self, server=None, port=22, control_session=None, use_sshproxy=False, sshproxy_reuse_authinfo=False, profile_id=None, profile_name='UNKNOWN', session_name=None, auto_start_or_resume=False, auto_connect=False, printing=False, allow_mimebox=False, mimebox_extensions=[], mimebox_action='OPEN', allow_share_local_folders=False, share_local_folders=[], restore_shared_local_folders=False, control_backend=_BACKENDS['X2GoControlSession']['default'], terminal_backend=_BACKENDS['X2GoTerminalSession']['default'], info_backend=_BACKENDS['X2GoServerSessionInfo']['default'], list_backend=_BACKENDS['X2GoServerSessionList']['default'], proxy_backend=_BACKENDS['X2GoProxy']['default'], settings_backend=_BACKENDS['X2GoClientSettings']['default'], printing_backend=_BACKENDS['X2GoClientPrinting']['default'], client_rootdir=os.path.join(_LOCAL_HOME, _X2GO_CLIENT_ROOTDIR), sessions_rootdir=os.path.join(_LOCAL_HOME, _X2GO_SESSIONS_ROOTDIR), ssh_rootdir=os.path.join(_LOCAL_HOME, _X2GO_SSH_ROOTDIR), keep_controlsession_alive=False, add_to_known_hosts=False, known_hosts=None, forward_sshagent=False, logger=None, loglevel=log.loglevel_DEFAULT, connected=False, activated=False, virgin=True, running=None, suspended=None, terminated=None, faulty=None, client_instance=None, **params): """\ :param server: hostname of X2Go server :type server: ``str`` :param control_session: an already initialized ``X2GoControlSession*`` instance :type control_session: ``X2GoControlSession*`` instance :param use_sshproxy: for communication with X2Go server use an SSH proxy host :type use_sshproxy: ``bool`` :param sshproxy_reuse_authinfo: for proxy authentication re-use the X2Go session's password / key file :type sshproxy_reuse_authinfo: ``bool`` :param profile_id: profile ID :type profile_id: ``str`` :param profile_name: profile name :type profile_name: ``str`` :param session_name: session name (if available) :type session_name: ``str`` :param auto_start_or_resume: automatically start a new or resume latest session after connect :type auto_start_or_resume: ``bool`` :param auto_connect: call a hook method that handles connecting the session profile automatically after a session for this profile has been registered :type auto_connect: ``bool`` :param printing: enable X2Go printing :type printing: ``bool`` :param allow_mimebox: enable X2Go MIME box support :type allow_mimebox: ``bool`` :param mimebox_extensions: whitelist of allowed X2Go MIME box extensions :type mimebox_extensions: ``list`` :param mimebox_action: action for incoming X2Go MIME box files :type mimebox_action: ``X2GoMimeBoxAction*`` or ``str`` :param allow_share_local_folders: enable local folder sharing support :type allow_share_local_folders: ``bool`` :param share_local_folders: list of local folders to share with the remote X2Go session :type share_local_folders: ``list`` :param restore_shared_local_folders: store actual list of shared local folders after session has been suspended or terminated :type restore_shared_local_folders: ``bool`` :param control_backend: X2Go control session backend to use :type control_backend: ``str`` :param terminal_backend: X2Go terminal session backend to use :type terminal_backend: ``str`` :param info_backend: X2Go session info backend to use :type info_backend: ``str`` :param list_backend: X2Go session list backend to use :type list_backend: ``str`` :param proxy_backend: X2Go proxy backend to use :type proxy_backend: ``str`` :param settings_backend: X2Go client settings backend to use :type settings_backend: ``str`` :param printing_backend: X2Go client printing backend to use :type printing_backend: ``str`` :param client_rootdir: client base dir (default: ~/.x2goclient) :type client_rootdir: ``str`` :param sessions_rootdir: sessions base dir (default: ~/.x2go) :type sessions_rootdir: ``str`` :param ssh_rootdir: ssh base dir (default: ~/.ssh) :type ssh_rootdir: ``str`` :param keep_controlsession_alive: On last :func:`X2GoSession.disconnect() ` keep the associated ``X2GoControlSession*`` instance alive? @ŧype keep_controlsession_alive: ``bool`` :param add_to_known_hosts: Auto-accept server host validity? :type add_to_known_hosts: ``bool`` :param known_hosts: the underlying Paramiko/SSH systems ``known_hosts`` file :type known_hosts: ``str`` :param forward_sshagent: forward SSH agent authentication requests to the SSH agent on the X2Go client-side :type forward_sshagent: ``bool`` :param connected: manipulate session state »connected« by giving a pre-set value :type connected: ``bool`` :param activated: normal leave this untouched, an activated session is a session that is about to be used :type activated: ``bool`` :param virgin: manipulate session state »virgin« by giving a pre-set value :type virgin: ``bool`` :param running: manipulate session state »running« by giving a pre-set value :type running: ``bool`` :param suspended: manipulate session state »suspended« by giving a pre-set value :type suspended: ``bool`` :param terminated: manipulate session state »terminated« by giving a pre-set value :type terminated: ``bool`` :param faulty: manipulate session state »faulty« by giving a pre-set value :type faulty: ``bool`` :param client_instance: if available, the underlying :class:`x2go.client.X2GoClient` instance :type client_instance: ``X2GoClient`` instance :param params: further control session, terminal session and SSH proxy class options :type params: ``dict`` """ if logger is None: self.logger = log.X2GoLogger(loglevel=loglevel) else: self.logger = copy.deepcopy(logger) self.logger.tag = __NAME__ self._keep = None self.uuid = uuid.uuid1() self.connected = connected self.activated = activated self.virgin = virgin self.running = running self.suspended = suspended self.terminated = terminated self.faulty = faulty self.keep_controlsession_alive = keep_controlsession_alive self.profile_id = profile_id self.profile_name = profile_name self.session_name = session_name self.server = server self.port = port self._last_status = None self.auto_start_or_resume = auto_start_or_resume self.auto_connect = auto_connect self.printing = printing self.allow_share_local_folders = allow_share_local_folders self.share_local_folders = share_local_folders self.restore_shared_local_folders = restore_shared_local_folders self.allow_mimebox = allow_mimebox self.mimebox_extensions = mimebox_extensions self.mimebox_action = mimebox_action self.control_backend = utils._get_backend_class(control_backend, "X2GoControlSession") self.terminal_backend = utils._get_backend_class(terminal_backend, "X2GoTerminalSession") self.info_backend = utils._get_backend_class(info_backend, "X2GoServerSessionInfo") self.list_backend = utils._get_backend_class(list_backend, "X2GoServerSessionList") if proxy_backend == 'auto-detect': # we need to defer this at this point # until we know the exact session command # to be launched... (i.e. what desktop environment # the user wants to launch via X2Go self.proxy_backend = proxy_backend else: self.proxy_backend = utils._get_backend_class(proxy_backend, "X2GoProxy") self.settings_backend = utils._get_backend_class(settings_backend, "X2GoClientSettings") self.printing_backend = utils._get_backend_class(printing_backend, "X2GoClientPrinting") self.client_rootdir = client_rootdir self.sessions_rootdir = sessions_rootdir self.ssh_rootdir = ssh_rootdir self.control_session = control_session if 'published_applications' in params: self.published_applications = params['published_applications'] if self.published_applications: params['cmd'] = 'PUBLISHED' else: self.published_applications = params['published_applications'] = False if 'cmd' in params and params['cmd'] != 'PUBLISHED': self.published_applications = params['published_applications'] = False self.published_applications_menu = None if self.session_name: if not re.match('.*_stRPUBLISHED_.*',self.session_name): self.published_applications = params['published_applications'] = False self.use_sshproxy = use_sshproxy self.sshproxy_reuse_authinfo = sshproxy_reuse_authinfo self.control_params = {} self.terminal_params = {} self.sshproxy_params = {} self.update_params(params) self.shared_folders = {} self.session_environment = {} self.server_features = [] try: del self.control_params['server'] except: pass self.client_instance = client_instance if self.logger.get_loglevel() & log.loglevel_DEBUG: self.logger('X2Go control session parameters for profile %s:' % profile_name, loglevel=log.loglevel_DEBUG) for p in [ _p for _p in self.control_params if not _p.endswith('pkey') ]: self.logger(' %s: %s' % (p, self.control_params[p]), log.loglevel_DEBUG) self.logger('X2Go terminal session parameters for profile %s:' % profile_name, loglevel=log.loglevel_DEBUG) for p in self.terminal_params: self.logger(' %s: %s' % (p,self.terminal_params[p]), log.loglevel_DEBUG) self.logger('X2Go sshproxy parameters for profile %s:' % profile_name, loglevel=log.loglevel_DEBUG) for p in self.sshproxy_params: self.logger(' %s: %s' % (p,self.sshproxy_params[p]), loglevel=log.loglevel_DEBUG) self.add_to_known_hosts = add_to_known_hosts self.known_hosts = known_hosts self.forward_sshagent = forward_sshagent self._current_status = { 'timestamp': time.time(), 'server': self.server, 'virgin': self.virgin, 'connected': self.connected, 'running': self.running, 'suspended': self.suspended, 'terminated': self.terminated, 'faulty': self.faulty, } self._SUPPORTED_SOUND = SUPPORTED_SOUND self._SUPPORTED_PRINTING = SUPPORTED_PRINTING self._SUPPORTED_MIMEBOX = SUPPORTED_MIMEBOX self._SUPPORTED_TELEKINESIS = SUPPORTED_TELEKINESIS self._SUPPORTED_FOLDERSHARING = SUPPORTED_FOLDERSHARING self.master_session = None self.init_control_session() self.terminal_session = None if self.is_connected(): self.retrieve_server_features() self._progress_status = 0 self._lock = threading.Lock() self._restore_exported_folders = {} if self.client_instance and self.restore_shared_local_folders: self._restore_exported_folders = self.client_instance.get_profile_config(self.profile_name, 'export') def __str__(self): return self.__get_uuid() def __repr__(self): result = 'X2GoSession(' 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 __call__(self): return self.__get_uuid() def __del__(self): """\ Class destructor. """ if self.has_control_session() and self.has_terminal_session(): self.get_control_session().dissociate(self.get_terminal_session()) if self.has_control_session(): if self.keep_controlsession_alive: # regenerate this session instance for re-usage if this is the last session for a certain session profile # and keep_controlsession_alive is set to True... self.virgin = True self.activated = False self.connected = self.is_connected() self.running = None self.suspended = None self.terminated = None self._current_status = { 'timestamp': time.time(), 'server': self.server, 'virgin': self.virgin, 'connected': self.connected, 'running': self.running, 'suspended': self.suspended, 'terminated': self.terminated, 'faulty': self.faulty, } self._last_status = None self.session_name = None else: self.get_control_session().__del__() self.control_session = None if self.has_terminal_session(): self.get_terminal_session().__del__() self.terminal_session = None def get_client_instance(self): """\ Return parent :class:`x2go.client.X2GoClient` instance if avaiable. :returns: :class:`x2go.client.X2GoClient` instance this session is associated with :rtype: ``obj`` """ return self.client_instance __get_client_instance = get_client_instance def HOOK_on_control_session_death(self): """\ HOOK method: called if a control session (server connection) has unexpectedly encountered a failure. """ if self.client_instance: self.client_instance.HOOK_on_control_session_death(profile_name=self.profile_name) else: self.logger('HOOK_on_control_session_death: the control session of profile %s has died unexpectedly' % self.profile_name, loglevel=log.loglevel_WARN) def HOOK_on_failing_SFTP_client(self): """\ HOOK method: called SFTP client support is unavailable for the session. """ if self.client_instance: self.client_instance.HOOK_on_failing_SFTP_client(profile_name=self.profile_name) else: self.logger('HOOK_on_failing_SFTP_client: new session for profile: %s will lack SFTP client support. Check your server setup. Avoid echoing ~/.bashrc files on server.' % self.profile_name, loglevel=log.loglevel_ERROR) def HOOK_auto_connect(self): """\ HOOK method: called if the session demands to auto connect. """ if self.client_instance: self.client_instance.HOOK_profile_auto_connect(profile_name=self.profile_name) else: self.logger('HOOK_auto_connect: profile ,,%s\'\' wants to auto-connect to the X2Go server.' % self.profile_name, loglevel=log.loglevel_WARN) def HOOK_session_startup_failed(self): """\ HOOK method: called if the startup of a session failed. """ if self.client_instance: self.client_instance.HOOK_session_startup_failed(profile_name=self.profile_name) else: self.logger('HOOK_session_startup_failed: session startup for session profile ,,%s\'\' failed.' % self.profile_name, loglevel=log.loglevel_WARN) def HOOK_desktop_sharing_denied(self): """\ HOOK method: called if the startup of a shadow session was denied by the other user. """ if self.client_instance: self.client_instance.HOOK_desktop_sharing_denied(profile_name=self.profile_name) else: self.logger('HOOK_desktop_sharing_denied: desktop sharing for session profile ,,%s\'\' was denied by the other user.' % self.profile_name, loglevel=log.loglevel_WARN) def HOOK_list_desktops_timeout(self): """\ HOOK method: called if the x2golistdesktops command generates a timeout due to long execution time. """ if self.client_instance: self.client_instance.HOOK_list_desktops_timeout(profile_name=self.profile_name) else: self.logger('HOOK_list_desktops_timeout: the server-side x2golistdesktops command for session profile %s took too long to return results. This can happen from time to time, please try again.' % self.profile_name, loglevel=log.loglevel_WARN) def HOOK_no_such_desktop(self, desktop='UNKNOWN'): """\ HOOK method: called if it is tried to connect to a shared desktop that's not available (anymore). :param desktop: the could-not-be-shared desktop's name or other identifier (Default value = 'UNKNOWN') :type desktop: ``str`` """ if self.client_instance: self.client_instance.HOOK_no_such_desktop(profile_name=self.profile_name, desktop=desktop) else: self.logger('HOOK_no_such_desktop: the desktop %s (via session profile %s) is not available for sharing (anymore).' % (desktop, self.profile_name), loglevel=log.loglevel_WARN) def HOOK_rforward_request_denied(self, server_port=0): """\ HOOK method: called if a reverse port forwarding request has been denied. :param server_port: remote server port (starting point of reverse forwarding tunnel) (Default value = 0) :type server_port: ``str`` """ if self.client_instance: self.client_instance.HOOK_rforward_request_denied(profile_name=self.profile_name, session_name=self.session_name, server_port=server_port) else: self.logger('HOOK_rforward_request_denied: TCP port (reverse) forwarding request for session %s to server port %s has been denied by server %s. This is a common issue with SSH, it might help to restart the server\'s SSH daemon.' % (self.session_name, server_port, self.profile_name), loglevel=log.loglevel_WARN) def HOOK_forwarding_tunnel_setup_failed(self, chain_host='UNKNOWN', chain_port=0, subsystem=None): """\ HOOK method: called if a port forwarding tunnel setup failed. :param chain_host: hostname of chain host (forwarding tunnel end point) (Default value = 'UNKNOWN') :type chain_host: ``str`` :param chain_port: port of chain host (forwarding tunnel end point) (Default value = 0) :type chain_port: ``str`` :param subsystem: information on the subsystem that provoked this hook call (Default value = None) :type subsystem: ``str`` """ if type(subsystem) in (bytes, str): _subsystem = '(%s) ' % subsystem else: _subsystem = '' if subsystem.endswith('Proxy'): self.faulty = True if self.client_instance: self.client_instance.HOOK_forwarding_tunnel_setup_failed(profile_name=self.profile_name, session_name=self.session_name, chain_host=chain_host, chain_port=chain_port, subsystem=subsystem) else: self.logger('HOOK_forwarding_tunnel_setup_failed: Forwarding tunnel request to [%s]:%s for session %s (%s) was denied by remote X2Go/SSH server. Subsystem (%s) startup failed.' % (chain_host, chain_port, self.session_name, self.profile_name, _subsystem), loglevel=log.loglevel_WARN) def HOOK_printing_not_available(self): """\ HOOK method: called if X2Go client-side printing is not available. """ if self.client_instance: self.client_instance.HOOK_printing_not_available(profile_name=self.profile_name, session_name=self.session_name) else: self.logger('HOOK_printing_not_available: X2Go\'s client-side printing feature is not available with this session (%s) of profile %s.' % (self.session_name, self.profile_name), loglevel=log.loglevel_WARN) def HOOK_mimebox_not_available(self): """\ HOOK method: called if the X2Go MIME box is not available. """ if self.client_instance: self.client_instance.HOOK_mimebox_not_available(profile_name=self.profile_name, session_name=self.session_name) else: self.logger('HOOK_mimebox_not_available: X2Go\'s MIME box feature is not available with this session (%s) of profile %s.' % (self.session_name, self.profile_name), loglevel=log.loglevel_WARN) def HOOK_foldersharing_not_available(self): """\ HOOK method: called if X2Go client-side folder-sharing is not available. """ if self.client_instance: self.client_instance.HOOK_foldersharing_not_available(profile_name=self.profile_name, session_name=self.session_name) else: self.logger('HOOK_foldersharing_not_available: X2Go\'s client-side folder sharing feature is not available with this session (%s) of profile %s.' % (self.session_name, self.profile_name), loglevel=log.loglevel_WARN) def HOOK_sshfs_not_available(self): """\ HOOK method: called if the X2Go server denies SSHFS access. """ if self.client_instance: self.client_instance.HOOK_sshfs_not_available(profile_name=self.profile_name, session_name=self.session_name) else: self.logger('HOOK_sshfs_not_available: the remote X2Go server (%s) denies SSHFS access for session %s. This will result in client-side folder sharing, printing and the MIME box feature being unavailable' % (self.profile_name, self.session_name), loglevel=log.loglevel_WARN) def HOOK_check_host_dialog(self, host, port, fingerprint='no fingerprint', fingerprint_type='UNKNOWN'): """\ HOOK method: called if a host check is requested. This hook has to either return ``True`` (default) or ``False``. :param host: SSH server name to validate :type host: ``str`` :param port: SSH server port to validate :type port: ``int`` :param fingerprint: the server's fingerprint (Default value = 'no fingerprint') :type fingerprint: ``str`` :param fingerprint_type: finger print type (like RSA, DSA, ...) (Default value = 'UNKNOWN') :type fingerprint_type: ``str`` :returns: if host validity is verified, this hook method should return ``True`` :rtype: ``bool`` """ if self.client_instance: return self.client_instance.HOOK_check_host_dialog(profile_name=self.profile_name, host=host, port=port, fingerprint=fingerprint, fingerprint_type=fingerprint_type) else: self.logger('HOOK_check_host_dialog: host check requested for [%s]:%s with %s fingerprint: ,,%s\'\'. Automatically adding host as known host.' % (host, port, fingerprint_type, fingerprint), loglevel=log.loglevel_WARN) return True def init_control_session(self): """\ Initialize a new control session (``X2GoControlSession*``). """ low_latency = 'link' in self.terminal_params and self.terminal_params['link'].lower() in ('modem', 'isdn') if self.control_session is None: self.logger('initializing X2GoControlSession', loglevel=log.loglevel_DEBUG) self.control_session = self.control_backend(profile_name=self.profile_name, add_to_known_hosts=self.add_to_known_hosts, known_hosts=self.known_hosts, forward_sshagent=self.forward_sshagent, terminal_backend=self.terminal_backend, info_backend=self.info_backend, list_backend=self.list_backend, proxy_backend=self.proxy_backend, client_rootdir=self.client_rootdir, sessions_rootdir=self.sessions_rootdir, ssh_rootdir=self.ssh_rootdir, low_latency=low_latency, logger=self.logger) else: self.control_session.low_latency = low_latency __init_control_session = init_control_session def is_master_session(self): """\ Is this session a/the master session of sessions. The master session is the session has been launched first for a specific connection, it also is _the_ session that controls the client-side shared folders. If this :class:`x2go.session.X2GoSession` instance is a standalone instance (without parent :class:`x2go.client.X2GoClient`) this method will always return ``True``. :returns: returns ``True`` if this session is a master session :rtype: ``bool`` """ if self.master_session is None and self.client_instance is None: return True return bool(self.master_session) __is_master_session = is_master_session def set_master_session(self, wait=0, max_wait=20): """\ Declare this as a master session of a connection channel. This method gets called by the :class:`x2go.registry.X2GoSessionRegistry` while sessions are starting or resuming and it relies on an already set-up terminal session. :param wait: wait for seconds before sharing local folders via the new master session of the corresponding session profile. (Default value = 0) :type wait: ``int`` :param max_wait: wait for seconds for the terminal session to appear (Default value = 20) :type max_wait: ``int`` """ self.logger('Using session %s as master session for profile %s.' % (self.get_session_name(), self.get_profile_name()), loglevel=log.loglevel_NOTICE) self.master_session = True # retrieve an up-to-date list of sharable local folders from the client instance if self.client_instance: try: _exports = self.client_instance.get_profile_config(self.profile_name, 'export') self.share_local_folders = [ sf for sf in list(_exports.keys()) if _exports[sf] ] except x2go_exceptions.X2GoProfileException: # not all client instances may necessarily have a sesion profile config... pass i = 0 while i < max_wait: i += 1 if self.has_terminal_session(): break gevent.sleep(1) if wait: gevent.spawn_later(wait, self.share_all_local_folders, update_exported_folders=False) else: gevent.spawn(self.share_all_local_folders, update_exported_folders=False) __set_master_session = set_master_session def unset_master_session(self): """\ Declare this as a non-master session of a connection channel. """ # unmount shared folders if self.has_terminal_session(): self.unshare_all_local_folders(update_exported_folders=False) self.master_session = False __unset_master_session = unset_master_session def set_server(self, server): """\ Modify server name after :class:`x2go.session.X2GoSession` has already been initialized. :param server: new server name :type server: ``str`` """ self.server = server __set_server = set_server def set_port(self, port): """\ Modify server port after :class:`x2go.session.X2GoSession` has already been initialized. :param port: socket port of server to connect to :type port: ``int`` """ self.port = port __set_port = set_port def set_profile_name(self, profile_name): """\ Modify session profile name after :class:`x2go.session.X2GoSession` has already been initialized. :param profile_name: new session profile name :type profile_name: ``str`` """ self.profile_name = profile_name self.control_session.set_profile_name(profile_name) __set_profile_name = set_profile_name def get_session_profile_option(self, option): """\ Retrieve a specific profile parameter for this session. :param option: name of a specific profile option to be queried. :type option: ``str`` :returns: value for profile option ``