# -*- coding: utf-8 -*-

# Copyright (C) 2010-2019 by Mike Gabriel <mike.gabriel@das-netzwerkteam.de>
#
# PyHoca CLI 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.
#
# PyHoca CLI 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.
#
# Contributors to the code of this programme:
#    2010 Dick Kniep <dick.kniep@lindix.nl>
#    2010 Jörg Sawatzki <joerg.sawatzki@web.de>

import x2go
import sys
import os
import copy
import getpass
from gevent import socket, sleep

# for debugging
import pprint

# use current_home as user home dir
current_home = os.path.expanduser("~")

from x2go.utils import touch_file as _touch_file

# define and create known_hosts file (if not there)
ssh_known_hosts_filename = os.path.join(x2go.LOCAL_HOME, x2go.X2GO_SSH_ROOTDIR, 'known_hosts')
if not os.path.isfile(ssh_known_hosts_filename):
    try: _touch_file(ssh_known_hosts_filename)
    except OSError: pass
# define and create ssh_config file (if not there)
ssh_config_filename = os.path.join(x2go.LOCAL_HOME, x2go.X2GO_SSH_ROOTDIR, 'config')
if not os.path.isfile(ssh_config_filename):
    try: _touch_file(ssh_config_filename)
    except OSError: pass


# sometimes we have to fail...
def runtime_error(m, parser=None, exitcode=-1):
    """\
    STILL UNDOCUMENTED
    """
    if parser is not None:
        parser.print_usage()
    sys.stderr.write ("%s: error: %s\n" % (os.path.basename(sys.argv[0]), m))
    sys.exit(exitcode)


class PyHocaCLI(x2go.X2GoClient):
    """\
    L{PyHocaCLI} is a command-line X2Go Client wrapping around Python X2Go (C{X2GoClient} class).

    """
    x2go_session_hash = None

    def _runtime_error(self, m, exitcode=-1):
        runtime_error(m, exitcode=exitcode)

    def HOOK_no_such_command(self, *args, **kwargs):
        x2go.X2GoClient.HOOK_no_such_command(self, *args, **kwargs)
        if len(args) == 0:
            cmd=kwargs['cmd']
        else:
            cmd=args[0]
        self._runtime_error("The remote X2Go Server does not know how to execute the command '{cmd}'. Aborting...".format(cmd=cmd))

    def list_sessions(self, s_hash):
        """\
        List up server-side available sessions for the logged in user.

        The list of available session is printed to stdout.

        @param s_hash: session UUID
        @type s_hash: C{str}

        """
        # retrieve a session list
        print()
        print("Available runing/suspended X2Go sessions")
        print("========================================")
        _peername = self._X2GoClient__get_session_server_peername(s_hash)
        print("Host: %s - [%s]:%s" % (self._X2GoClient__get_session_server_hostname(s_hash), _peername[0], _peername[1]))
        print("Username: %s" % self._X2GoClient__get_session_username(s_hash))
        print()
        session_infos = self._X2GoClient__list_sessions(s_hash)
        if session_infos:
            for session_info in list(session_infos.values()):
                print("Session Name: %s" % session_info)
                print("-------------")
                print("cookie: %s" % session_info.cookie)
                print("agent PID: %s" % session_info.agent_pid)
                print("display: %s" % session_info.display)
                print("status: %s" % session_info.status)
                print("graphic port: %s" % session_info.graphics_port)
                print("snd port: %s" % session_info.snd_port)
                print("sshfs port: %s" % session_info.sshfs_port)
                print("username: %s" % session_info.username)
                print("hostname: %s" % session_info.hostname)
                # TODO: turn into datetime object
                print("create date: %s" % session_info.date_created)
                # TODO: turn into datetime object
                print("suspended since: %s" % session_info.date_suspended)
                print()
        else:
            print("No running/suspended sessions found on X2Go server.")
            print()

    def list_desktops(self, s_hash):
        """\
        List up server-side available desktop sessions that are
        available for sharing to the logged in user.

        The list of sharable desktop sessions is printed to stdout.

        @param s_hash: session UUID
        @type s_hash: C{str}

        """
        # retrieve a desktop list
        print()
        print("X2Go desktops available for sharing")
        print("===================================")
        _peername = self._X2GoClient__get_session_server_peername(s_hash)
        print("Host: %s - [%s]:%s" % (self._X2GoClient__get_session_server_hostname(s_hash), _peername[0], _peername[1]))
        print("Username: %s" % self._X2GoClient__get_session_username(s_hash))
        print()
        desktop_list = self._X2GoClient__list_desktops(s_hash)
        if desktop_list:
            for desktop_name in desktop_list:
                print(desktop_name)
        else:
            print("No X2Go desktop sessions found that are available for desktop sharing.")
            print()

    def list_profiles(self):
        """\
        List up client-side configured session profile configurations.

        The list of session profiles is printed to stdout.

        """
        # retrieve a session list
        print()
        print("Available X2Go session profiles")
        print("===============================")
        _profiles = self._X2GoClient__get_profiles()
        for _profile_id in _profiles.profile_ids:
            _profile_config = _profiles.get_profile_config(_profile_id)
            _session_params = _profiles.to_session_params(_profile_id)
            print(_profile_config)
            print(_session_params)
            print()

    def clean_sessions(self, s_hash):
        """\
        Clean all running/suspended sessions on the X2Go server that the session with UUID C{s_hash}
        is connected to.

        @param s_hash: session UUID
        @type s_hash: C{str}

        """
        _profiles = self._X2GoClient__get_profiles()
        if self.args.session_profile and _profiles.has_profile(self.args.session_profile):
            _server = _profiles.get_session_param(self.args.session_profile, 'server')
        else:
            _server = self.args.server
        # clean all sessions from X2Go server
        self.logger('cleaning up all running sessions from X2Go server: %s' % _server, loglevel=x2go.loglevel_NOTICE, )
        self._X2GoClient__clean_sessions(s_hash)

    def new_session(self, s_hash):
        """\
        Launch a new X2Go session via the C{X2GoSession} object with session UUID C{s_hash}.

        @param s_hash: session UUID
        @type s_hash: C{str}

        """
        # start a new session and run a command
        self.logger('starting a new X2Go session', loglevel=x2go.loglevel_INFO, )
        if self.args.session_profile is None:
            self.logger('command for new session is: %s' % self.args.command, loglevel=x2go.loglevel_DEBUG, )
        else:
            self.logger('command from session profile to run is: %s' % self.session_registry(self.x2go_session_hash).get_session_cmd(), loglevel=x2go.loglevel_DEBUG, )
        self._X2GoClient__start_session(s_hash)

    def _auto_resume_newest(self, s_hash):
        session_infos = self._X2GoClient__list_sessions(s_hash)
        if session_infos:
            newest_session_name = x2go.utils.session_names_by_timestamp(session_infos)[-1]
            self._pyhoca_logger("Resuming newest X2Go session %s..." % newest_session_name, loglevel=x2go.loglevel_NOTICE, )
            self._X2GoClient__resume_session(s_hash, newest_session_name)
            return True
        return False

    def _auto_resume_oldest(self, s_hash):
        session_infos = self._X2GoClient__list_sessions(s_hash)
        if session_infos:
            oldest_session_name = x2go.utils.session_names_by_timestamp(session_infos)[0]
            self._pyhoca_logger("Resuming oldest X2Go session %s..." % oldest_session_name, loglevel=x2go.loglevel_NOTICE, )
            self._X2GoClient__resume_session(s_hash, oldest_session_name)
            return True
        return False

    def resume_session(self, s_hash):
        """\
        Use C{X2GoSession} object with session UUID C{s_hash} to resume a server-side suspended X2Go session.

        @param s_hash: session UUID
        @type s_hash: C{str}

        """
        # resume a running session
        self.logger('resuming X2Go session: %s' % self.args.resume, loglevel=x2go.loglevel_INFO, )
        if available_sessions is None:
            available_sessions = self._X2GoClient__list_sessions(s_hash)
        if available_sessions and self.args.resume == 'OLDEST':
            self._auto_resume_oldest(s_hash)
        elif available_sessions and self.args.resume == 'NEWEST':
            self._auto_resume_newest(s_hash)
        elif self.args.resume in list(available_sessions.keys()):
            self._X2GoClient__resume_session(s_hash, self.args.resume)
        else:
            self._runtime_error('requested session not available on X2Go server [%s]:%s' % (self.args.server, self.args.remote_ssh_port), exitcode=20)

    def try_resume_session(self, s_hash):
        """\
        Use C{X2GoSession} object with session UUID C{s_hash} to resume a server-side suspended X2Go session
        or to take over a server-side running X2Go session.

        @param s_hash: session UUID
        @type s_hash: C{str}

        """
        self.logger('trying to resume / take over X2Go session with command: %s' % self.args.command, loglevel=x2go.loglevel_INFO, )
        available_sessions = self._X2GoClient__list_sessions(s_hash, with_command=self.args.command)
        if available_sessions:
            self.logger('matching sessions found: %s' % available_sessions.keys(), loglevel=x2go.loglevel_INFO, )
            self.logger('picking the first in list: %s' % list(available_sessions.keys())[0], loglevel=x2go.loglevel_INFO, )
            # FIXME: what session do we want to resume really (oldest, newest, etc.)?
            self._X2GoClient__resume_session(s_hash, list(available_sessions.keys())[0])
            return True
        else:
            return False

    def share_desktop_session(self, s_hash):
        """\
        Use C{X2GoSession} object with session UUID C{s_hash} to share a server-side X2Go desktop session that is available for sharing.

        @param s_hash: session UUID
        @type s_hash: C{str}

        """
        # start a new session and run a command
        _desktop = self.args.share_desktop
        _share_mode = self.args.share_mode
        self.logger('sharing X2Go session: %s' % _desktop, loglevel=x2go.loglevel_INFO, )
        try:
            self._X2GoClient__share_desktop_session(s_hash, desktop=_desktop, share_mode=_share_mode)
        except x2go.X2GoDesktopSharingException as e:
            self._runtime_error('%s' % str(e), exitcode=54)


    def suspend_session(self, s_hash):
        """\
        Suspend X2Go Session object with session UUID C{s_hash}.

        @param s_hash: session UUID
        @type s_hash: C{str}

        """
        # send a suspend request to a session
        self.logger('requesting X2Go session suspend of session: %s' % self.args.suspend, loglevel=x2go.loglevel_INFO, )
        available_sessions = self._X2GoClient__list_sessions(s_hash)

        if self.args.suspend == "ALL":
            _session_names = available_sessions
        else:
            _session_names = [ self.args.suspend ]

        for _session_name in _session_names:
            if _session_name in list(available_sessions.keys()):
                self._X2GoClient__suspend_session(s_hash, _session_name)
                self._pyhoca_logger("X2Go session %s has been suspended" % _session_name, loglevel=x2go.loglevel_NOTICE, )
            else:
                _server = self.args.server
                _remote_ssh_port = self.args.remote_ssh_port
                if not self.args.server and self.args.session_profile:
                    _server = self.session_profiles.get_value(self.session_profiles.to_profile_id(self.args.session_profile), 'host')
                    _remote_ssh_port = self.session_profiles.get_value(self.session_profiles.to_profile_id(self.args.session_profile), 'sshport')
                self._runtime_error('session %s not available on X2Go server [%s]:%s' % (_session_name, _server, _remote_ssh_port), exitcode=21)

    def terminate_session(self, s_hash):
        """\
        Terminate X2Go Session with session UUID C{s_hash}.

        @param s_hash: session UUID
        @type s_hash: C{str}

        """
        # send a terminate request to a session
        self.logger('requesting X2Go session terminate of session: %s' % self.args.terminate, loglevel=x2go.loglevel_INFO, )
        available_sessions = self._X2GoClient__list_sessions(s_hash)

        if self.args.terminate == "ALL":
            _session_names = available_sessions
        else:
            _session_names = [ self.args.terminate ]

        for _session_name in _session_names:
            if _session_name in list(available_sessions.keys()):
                self._X2GoClient__terminate_session(s_hash, _session_name)
                self._pyhoca_logger("X2Go session %s has been terminated" % _session_name, loglevel=x2go.loglevel_NOTICE, )
            else:
                _server = self.args.server
                _remote_ssh_port = self.args.remote_ssh_port
                if not self.args.server and self.args.session_profile:
                    _server = self.session_profiles.get_value(self.session_profiles.to_profile_id(self.args.session_profile), 'host')
                    _remote_ssh_port = self.session_profiles.get_value(self.session_profiles.to_profile_id(self.args.session_profile), 'sshport')
                self._runtime_error('session %s not available on X2Go server [%s]:%s' % (_session_name, _server, _remote_ssh_port), exitcode=22)

    def __init__(self, args, logger=None, liblogger=None):
        """\
        L{PyHocaCLI} Class constructor.

        @param args: a class with properties representing the command-line options that are available to L{PyHocaCLI} instances.
        @type args: C{argparse.ArgumentParser} (or similar)
        @param logger: you can pass an L{X2GoLogger} object to the
            L{PyHocaCLI} constructor for logging application events
        @type logger: Python X2Go C{X2GoLogger} instance
        @param liblogger: you can pass an L{X2GoLogger} object to the
            L{PyHocaCLI} constructor for logging application events, this object is forwarded to the C{X2GoClient} class in Python X2Go
        @type liblogger: Python X2Go C{X2GoLogger} instance

        """
        self.args = args
        if logger is None:
            self._pyhoca_logger = x2go.X2GoLogger(tag='PyHocaCLI')
        else:
            self._pyhoca_logger = copy.deepcopy(logger)
            self._pyhoca_logger.tag = 'PyHocaCLI'

        _backend_kwargs = {}
        if self.args.backend_controlsession is not None:
            _backend_kwargs['control_backend'] = self.args.backend_controlsession
        if self.args.backend_terminalsession is not None:
            _backend_kwargs['terminal_backend'] = self.args.backend_terminalsession
        if self.args.backend_serversessioninfo is not None:
            _backend_kwargs['info_backend'] = self.args.backend_serversessioninfo
        if self.args.backend_serversessionlist is not None:
            _backend_kwargs['list_backend'] = self.args.backend_serversessionlist
        if self.args.backend_proxy is not None:
            _backend_kwargs['proxy_backend'] = self.args.backend_proxy
        else:
            _backend_kwargs['proxy_backend'] = 'auto-detect'
        if self.args.backend_sessionprofiles is not None:
            _backend_kwargs['profiles_backend'] = self.args.backend_sessionprofiles
        if self.args.backend_clientsettings is not None:
            _backend_kwargs['settings_backend'] = self.args.backend_clientsettings
        if self.args.backend_clientprinting is not None:
            _backend_kwargs['printing_backend'] = self.args.backend_clientprinting

        # initialize the X2GoClient context and start the connection to the X2Go server
        self._pyhoca_logger('preparing requested X2Go session', loglevel=x2go.loglevel_NOTICE, )

        x2go.X2GoClient.__init__(self, broker_url=self.args.broker_url, broker_password=self.args.broker_password, logger=liblogger, **_backend_kwargs)

        _profiles = self._X2GoClient__get_profiles()
        if self.args.session_profile and not _profiles.has_profile(self.args.session_profile):
            self._runtime_error('no such session profile of name: %s' % (self.args.session_profile), exitcode=31)

        self.auth_attempts = int(self.args.auth_attempts)


        if args.list_profiles:

            _session_profiles = self._X2GoClient__get_profiles()
            # retrieve a session list
            print()
            print("Available X2Go session profiles")
            print("===============================")
            if hasattr(_session_profiles, 'config_files') and _session_profiles.config_files is not None:
                print("configuration files: %s" % _session_profiles.config_files)
            if hasattr(_session_profiles, 'user_config_file') and _session_profiles.user_config_file is not None:
                print("user configuration file: %s" % _session_profiles.user_config_file)
            if hasattr(_session_profiles, 'broker_url') and _session_profiles.broker_url is not None:
                print("X2Go Session Broker URL: %s" % _session_profiles.broker_url)
            print()
            for _profile_id in _session_profiles.profile_ids:
                _profile_config = _session_profiles.get_profile_config(_profile_id)
                _session_params = _session_profiles.to_session_params(_profile_id)
                print('Profile ID: %s' % _profile_id)
                print('Profile Name: %s' % _session_params['profile_name'])
                print('Profile Configuration:')
                pprint.pprint(_profile_config)
                print('Derived session parameters:')
                pprint.pprint(_session_params)
                print()

            # done
            sys.exit(0)

        elif self.args.session_profile:

            _cmdlineopt_to_sessionopt = {
                'command': 'cmd',
                'kb_layout': 'kblayout',
                'kb_type': 'kbtype',
                'sound': 'snd_system',
                'ssh_privkey': 'key_filename',
                'server': 'hostname',
                'remote_ssh_port': 'port',
            }

            # override session profile options by option values from the arg parser
            kwargs={}
            if hasattr(self.args, 'parser'):
                for a, v in self.args._get_kwargs():
                    if v != self.args.parser.get_default(a):
                        try:
                            kwargs[_cmdlineopt_to_sessionopt[a]] = v
                        except KeyError:
                            kwargs[a] = v

            # setup up the session profile based X2Go session
            self.x2go_session_hash = self._X2GoClient__register_session(profile_name=self.args.session_profile,
                                                                        known_hosts=ssh_known_hosts_filename,
                                                                        **kwargs
                                                                       )

        else:

            xinerama = False
            if self.args.xinerama:
                xinerama = self.args.xinerama

            allow_share_local_folders = bool(self.args.share_local_folders)

            # setup up the manually configured X2Go session
            self.x2go_session_hash = self._X2GoClient__register_session(args.server, port=int(self.args.remote_ssh_port),
                                                           known_hosts=ssh_known_hosts_filename,
                                                           username=self.args.username,
                                                           key_filename=self.args.ssh_privkey,
                                                           add_to_known_hosts=self.args.add_to_known_hosts,
                                                           profile_name = 'Pyhoca-Client_Session',
                                                           session_type=self.args.session_type,
                                                           link=self.args.link,
                                                           xinerama=self.args.xinerama,
                                                           geometry=self.args.geometry,
                                                           pack=self.args.pack,
                                                           cache_type='unix-kde',
                                                           kblayout=self.args.kbd_layout,
                                                           kbtype=self.args.kbd_type,
                                                           snd_system=self.args.sound,
                                                           printing=self.args.printing,
                                                           print_action=self.args.print_action,
                                                           print_action_args=self.args.print_action_args,
                                                           share_local_folders=self.args.share_local_folders,
                                                           allow_share_local_folders=allow_share_local_folders,
                                                           forward_sshagent=self.args.forward_sshagent,
                                                           cmd=self.args.command)

    def authenticate(self):
        """\
        Authenticate this L{PyHocaCLI} instance with the remote X2Go server.

        """
        connected = False

        if self.args.password:
            password = self.args.password
            self.args.password = None
            cmdline_password = True
            force_password_auth = True
        else:
            password = None
            cmdline_password = False
            force_password_auth = False

        if self.args.force_password:
            force_password_auth = True

        passphrase = None
        passphrase_unlock_counter = 3

        _username = self.args.username or self._X2GoClient__get_session_username(self.x2go_session_hash)

        # if we still don't have a valid user name, try the broker...
        if not _username and hasattr(self.session_profiles, 'get_broker_username'):
            _username = self.session_profiles.get_broker_username()

        try:
            _auth_count = self.auth_attempts
            while not connected and _auth_count:

                try:

                    # decrement authentication counter...
                    _auth_count -= 1

                    # show interactive password prompt
                    if force_password_auth and not cmdline_password:

                        password = getpass.getpass()

                        # workaround for Python bug: http://bugs.python.org/issue11236
                        try:
                            if password is not None and '\x03' in password:
                                raise KeyboardInterrupt()
                        except KeyboardInterrupt:
                            self._runtime_error('Authentication cancelled by user by hitting Ctrl-C at password prompt', exitcode=-200)

                        if not password:
                            self._pyhoca_logger('password is empty, please re-try... (hit Ctrl-C plus ENTER to cancel)', loglevel=x2go.loglevel_WARN, )
                            _auth_count += 1
                            continue

                    # connection attempt with remote X2Go Server
                    self._X2GoClient__connect_session(self.x2go_session_hash, username=_username, password=password, passphrase=passphrase, force_password_auth=force_password_auth)

                    # we succeeeded
                    connected = True

                    # this will end the loop (as connected is set to True)
                    continue

                except x2go.PasswordRequiredException as e:

                    # x2go.PasswordRequiredException: This exception gets raised if an SSH pubkey is protected by a passphrase

                    if not force_password_auth and passphrase_unlock_counter >= 1:
                        if passphrase == '':
                            self._pyhoca_logger('empty SSH key passphrase (%s), try again...' % self.args.ssh_privkey, loglevel=x2go.loglevel_WARN, )
                        self._pyhoca_logger('unlock SSH key file (%s)' % self.args.ssh_privkey, loglevel=x2go.loglevel_NOTICE, )
                        passphrase = getpass.getpass('Passphrase: ')
                        passphrase_unlock_counter -= 1
                        # undo auth counter decrement
                        _auth_count += 1
                        continue

                    if not force_password_auth and _auth_count >= 1:
                        self._pyhoca_logger('unlocking of SSH key failed, proceeding with interactive login', loglevel=x2go.loglevel_WARN, )
                        force_password_auth = True
                        password = None
                        passphrase = None
                        _auth_count += 1
                        continue

                except x2go.AuthenticationException as e:

                    # x2go.AuthenticationException: This exception gets raised if the authentication failed

                    if force_password_auth:
                        self._pyhoca_logger('password based login for ,,%s\'\' failed [AuthException]' % _username, loglevel=x2go.loglevel_WARN, )
                    else:
                        self._pyhoca_logger('passwordless login for ,,%s\'\' failed [AuthException]' % _username, loglevel=x2go.loglevel_WARN, )

                    # if the previous login attempt was pubkey based, enforce interactive login for the next round...
                    if not password and _auth_count >= 1:
                        self._pyhoca_logger('proceeding to interactive login for user ,,%s\'\'' % _username, loglevel=x2go.loglevel_NOTICE, )
                        force_password_auth = True
                        # undo auth counter decrement
                        _auth_count += 1

                    # a password was provided via the command line
                    elif password and cmdline_password and _auth_count >= 1:
                        self._pyhoca_logger('cmdline provided password failed, proceeding to interactive login for user ,,%s\'\'' % _username, loglevel=x2go.loglevel_WARN, )
                        force_password_auth = True
                        cmdline_password = False

                    # else, if the previous attempt was already interactive, offer re-trying
                    elif force_password_auth and _auth_count >= 1:
                        self._pyhoca_logger('please re-try login for user ,,%s\'\'' % _username, loglevel=x2go.loglevel_NOTICE, )

                    passphrase = None
                    password = None

                except x2go.BadHostKeyException:

                    # let's bail out here immediately...

                    self._runtime_error('SSH host key verification for remote host [%s]:%s failed' % (self.args.server, self.args.remote_ssh_port), exitcode=-254)

                except x2go.SSHException as e:

                    # this bit only captures problems with the SSH key file, other
                    # SSHExceptions are simply ignored (and we proceed to interactive login)

                    if str(e).lower().startswith('could not deserialize key data')                                          \
                        :

                        # this error gets thrown when the passphrase for unlocking the SSH privkey was wrong

                        if not force_password_auth and passphrase_unlock_counter >= 1:

                            if passphrase is not None and passphrase != '':
                                self._pyhoca_logger('wrong SSH key passphrase (%s), try again...' % self.args.ssh_privkey, loglevel=x2go.loglevel_WARN, )
                            self._pyhoca_logger('unlock SSH key file (%s)' % self.args.ssh_privkey, loglevel=x2go.loglevel_NOTICE, )
                            passphrase = getpass.getpass('Passphrase: ')
                            passphrase_unlock_counter -= 1
                            # undo auth counter decrement
                            _auth_count += 1
                            continue

                        if not force_password_auth and _auth_count >= 1:

                            self._pyhoca_logger('unlocking of SSH key failed, proceeding with interactive login', loglevel=x2go.loglevel_WARN, )
                            force_password_auth = True
                            password = None
                            passphrase = None
                            _auth_count += 1
                            continue

                    elif not str(e).lower().startswith('not a valid dsa private key file') and                        \
                         not str(e).lower().startswith('not a valid rsa private key file') and                        \
                         not str(e).lower().startswith('incompatible ssh peer (no acceptable kex algorithm)') and     \
                         not str(e).lower().startswith('no authentication methods available')                         \
                        :

                        if force_password_auth:

                            self._pyhoca_logger('password based login for ,,%s\'\' failed [SSHException]' % _username, loglevel=x2go.loglevel_WARN, )
                        else:
                            self._pyhoca_logger('passwordless login for ,,%s\'\' failed [SSHException]' % _username, loglevel=x2go.loglevel_WARN, )

                        # let's bail out here...
                        self._runtime_error(str(e), exitcode=253)

                    else:

                        self._pyhoca_logger('[SSHException] the following error will be ignored: %s' % str(e), loglevel=x2go.loglevel_WARN)
                        self._pyhoca_logger('proceeding to interactive login for user ,,%s\'\'' % _username, loglevel=x2go.loglevel_NOTICE, )
                        force_password_auth = True
                        password = None
                        passphrase = None
                        _auth_count += 1

                if not connected and _auth_count <= 0:
                    if self.auth_attempts >= 2:
                        self._runtime_error('authentication failed, too many failures during interactive login', exitcode=-201)
                    elif self.auth_attempts == 1:
                        self._runtime_error('authentication failed', exitcode=-202)


        except socket.error as e:
            self._runtime_error('a socket error occured while establishing the connection: %s' % str(e), exitcode=-245)

        # drop clear text passwords...
        password = None
        passphrase = None

        if not self.is_x2goserver(self.x2go_session_hash):
            self._runtime_error('the remote server does not have the X2Go Server software installed', exitcode=1337)

        self._pyhoca_logger('authentication has been successful', loglevel=x2go.loglevel_NOTICE, )


    def MainLoop(self):
        """\
        Start the main loop of this application.

        """
        try:

            if self.args.clean_sessions:
                self.clean_sessions(self.x2go_session_hash)

            # go through the possible X2Go client modes
            if self.args.list_sessions:
                # print a beautified session list for the user
                self.list_sessions(self.x2go_session_hash)
                sys.exit(0)

            if self.args.list_desktops:
                # print a beautified desktop list for the user
                self.list_desktops(self.x2go_session_hash)
                sys.exit(0)

            if self.args.list_profiles:
                # print a beautified profile list for the user
                self.list_profiles()
                sys.exit(0)

            if self.args.resume or self.args.new:
                self._pyhoca_logger("give the X2Go session some time to come up...", loglevel=x2go.loglevel_NOTICE, )

            if self.args.resume:
                self.resume_session(self.x2go_session_hash)

            if self.args.share_desktop:
                self.share_desktop_session(self.x2go_session_hash)

            elif self.args.suspend:
                self.suspend_session(self.x2go_session_hash)

            elif self.args.terminate:
                self.terminate_session(self.x2go_session_hash)

            elif self.args.new and self.args.try_resume and self.try_resume_session(self.x2go_session_hash):
                # nothing to do here, if resumption attempt was successful
                pass

            elif self.args.new:
                self.new_session(self.x2go_session_hash)

            if not (self.args.new or self.args.resume or self.args.share_desktop or self.args.session_profile):
                sys.exit(0)
            i=0
            while 0 < self.get_session(self.x2go_session_hash).get_progress_status() < 100:
                sleep(1)
                i+=1

            session = self._X2GoClient__get_session(self.x2go_session_hash)

            if self._X2GoClient__session_ok(self.x2go_session_hash):

                session.set_master_session()
                profile_name = self._X2GoClient__get_session_profile_name(self.x2go_session_hash)
                session_name = self._X2GoClient__get_session_name(self.x2go_session_hash)
                self._pyhoca_logger("X2Go session is now running, the X2Go client's profile name is: %s" % profile_name, loglevel=x2go.loglevel_INFO, )
                self._pyhoca_logger("X2Go session name is: %s" % session_name, loglevel=x2go.loglevel_INFO, )

                if not self.args.from_stdin:

                    if self.args.share_desktop:
                        self._pyhoca_logger("Press CTRL+C to end desktop sharing for this session...", loglevel=x2go.loglevel_NOTICE, )
                    elif self.args.terminate_on_ctrl_c:
                        self._pyhoca_logger("Press CTRL+C to terminate the running session...", loglevel=x2go.loglevel_NOTICE, )
                    else:
                        self._pyhoca_logger("Press CTRL+C to suspend the running session...", loglevel=x2go.loglevel_NOTICE, )

                try:

                    session_duration = 0

                    while self._X2GoClient__session_ok(self.x2go_session_hash):
                        sleep(2)
                        session_duration +=2

                    # wait a little while before telling the user what has happened
                    sleep(2)

                    # refresh session status so we can be most accurate on what we report below
                    self._X2GoClient__list_sessions(self.x2go_session_hash)

                    # report about the session status once we get here...
                    if self._X2GoClient__has_session_terminated(self.x2go_session_hash):
                        self._pyhoca_logger("X2Go session %s has terminated" % session_name, loglevel=x2go.loglevel_NOTICE, )
                    elif self._X2GoClient__is_session_suspended(self.x2go_session_hash):
                        self._pyhoca_logger("X2Go session %s has been suspended" % session_name, loglevel=x2go.loglevel_NOTICE, )
                    elif self._X2GoClient__is_session_running(self.x2go_session_hash):
                        self._pyhoca_logger("X2Go session %s has been moved to a different screen" % session_name, loglevel=x2go.loglevel_NOTICE, )

                except KeyboardInterrupt:
                    if self.args.share_desktop:
                        self._pyhoca_logger("Terminating X2Go shared desktop session %s" % session_name, loglevel=x2go.loglevel_INFO, )
                        self._X2GoClient__terminate_session(self.x2go_session_hash)
                        sleep(2)
                        self._pyhoca_logger("X2Go session %s has been terminated" % session_name, loglevel=x2go.loglevel_NOTICE, )
                    elif self.args.terminate_on_ctrl_c:
                        self._pyhoca_logger("Terminating X2Go session %s" % session_name, loglevel=x2go.loglevel_INFO, )
                        self._X2GoClient__terminate_session(self.x2go_session_hash)
                        # giving nxproxy's SSH tunnel some time to settle
                        sleep(2)
                        self._pyhoca_logger("X2Go session %s has been terminated" % session_name, loglevel=x2go.loglevel_NOTICE, )
                    else:
                        self._pyhoca_logger("Suspending X2Go session %s" % session_name, loglevel=x2go.loglevel_INFO, )
                        self._X2GoClient__suspend_session(self.x2go_session_hash)
                        # giving nxproxy's SSH tunnel some time to settle
                        sleep(2)
                        self._pyhoca_logger("X2Go session %s has been suspended" % session_name, loglevel=x2go.loglevel_NOTICE, )

        except x2go.X2GoSessionException as e:
            self._pyhoca_logger("X2GoSessionException occured:", loglevel=x2go.loglevel_ERROR)
            self._pyhoca_logger("-> %s" % str(e), loglevel=x2go.loglevel_ERROR)
