Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
auth_handler.py1093 linesDownload Raw Back to paramiko
1# Copyright (C) 2003-2007  Robey Pointer <robeypointer@gmail.com>2#3# This file is part of paramiko.4#5# Paramiko is free software; you can redistribute it and/or modify it under the6# terms of the GNU Lesser General Public License as published by the Free7# Software Foundation; either version 2.1 of the License, or (at your option)8# any later version.9#10# Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY11# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR12# A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more13# details.14#15# You should have received a copy of the GNU Lesser General Public License16# along with Paramiko; if not, write to the Free Software Foundation, Inc.,17# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.18 19"""20`.AuthHandler`21"""22 23import weakref24import threading25import time26import re27 28from paramiko.common import (29    cMSG_SERVICE_REQUEST,30    cMSG_DISCONNECT,31    DISCONNECT_SERVICE_NOT_AVAILABLE,32    DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE,33    cMSG_USERAUTH_REQUEST,34    cMSG_SERVICE_ACCEPT,35    DEBUG,36    AUTH_SUCCESSFUL,37    INFO,38    cMSG_USERAUTH_SUCCESS,39    cMSG_USERAUTH_FAILURE,40    AUTH_PARTIALLY_SUCCESSFUL,41    cMSG_USERAUTH_INFO_REQUEST,42    WARNING,43    AUTH_FAILED,44    cMSG_USERAUTH_PK_OK,45    cMSG_USERAUTH_INFO_RESPONSE,46    MSG_SERVICE_REQUEST,47    MSG_SERVICE_ACCEPT,48    MSG_USERAUTH_REQUEST,49    MSG_USERAUTH_SUCCESS,50    MSG_USERAUTH_FAILURE,51    MSG_USERAUTH_BANNER,52    MSG_USERAUTH_INFO_REQUEST,53    MSG_USERAUTH_INFO_RESPONSE,54    cMSG_USERAUTH_GSSAPI_RESPONSE,55    cMSG_USERAUTH_GSSAPI_TOKEN,56    cMSG_USERAUTH_GSSAPI_MIC,57    MSG_USERAUTH_GSSAPI_RESPONSE,58    MSG_USERAUTH_GSSAPI_TOKEN,59    MSG_USERAUTH_GSSAPI_ERROR,60    MSG_USERAUTH_GSSAPI_ERRTOK,61    MSG_USERAUTH_GSSAPI_MIC,62    MSG_NAMES,63    cMSG_USERAUTH_BANNER,64)65from paramiko.message import Message66from paramiko.util import b, u67from paramiko.ssh_exception import (68    SSHException,69    AuthenticationException,70    BadAuthenticationType,71    PartialAuthentication,72)73from paramiko.server import InteractiveQuery74from paramiko.ssh_gss import GSSAuth, GSS_EXCEPTIONS75 76 77class AuthHandler:78    """79    Internal class to handle the mechanics of authentication.80    """81 82    def __init__(self, transport):83        self.transport = weakref.proxy(transport)84        self.username = None85        self.authenticated = False86        self.auth_event = None87        self.auth_method = ""88        self.banner = None89        self.password = None90        self.private_key = None91        self.interactive_handler = None92        self.submethods = None93        # for server mode:94        self.auth_username = None95        self.auth_fail_count = 096        # for GSSAPI97        self.gss_host = None98        self.gss_deleg_creds = True99 100    def _log(self, *args):101        return self.transport._log(*args)102 103    def is_authenticated(self):104        return self.authenticated105 106    def get_username(self):107        if self.transport.server_mode:108            return self.auth_username109        else:110            return self.username111 112    def auth_none(self, username, event):113        self.transport.lock.acquire()114        try:115            self.auth_event = event116            self.auth_method = "none"117            self.username = username118            self._request_auth()119        finally:120            self.transport.lock.release()121 122    def auth_publickey(self, username, key, event):123        self.transport.lock.acquire()124        try:125            self.auth_event = event126            self.auth_method = "publickey"127            self.username = username128            self.private_key = key129            self._request_auth()130        finally:131            self.transport.lock.release()132 133    def auth_password(self, username, password, event):134        self.transport.lock.acquire()135        try:136            self.auth_event = event137            self.auth_method = "password"138            self.username = username139            self.password = password140            self._request_auth()141        finally:142            self.transport.lock.release()143 144    def auth_interactive(self, username, handler, event, submethods=""):145        """146        response_list = handler(title, instructions, prompt_list)147        """148        self.transport.lock.acquire()149        try:150            self.auth_event = event151            self.auth_method = "keyboard-interactive"152            self.username = username153            self.interactive_handler = handler154            self.submethods = submethods155            self._request_auth()156        finally:157            self.transport.lock.release()158 159    def auth_gssapi_with_mic(self, username, gss_host, gss_deleg_creds, event):160        self.transport.lock.acquire()161        try:162            self.auth_event = event163            self.auth_method = "gssapi-with-mic"164            self.username = username165            self.gss_host = gss_host166            self.gss_deleg_creds = gss_deleg_creds167            self._request_auth()168        finally:169            self.transport.lock.release()170 171    def auth_gssapi_keyex(self, username, event):172        self.transport.lock.acquire()173        try:174            self.auth_event = event175            self.auth_method = "gssapi-keyex"176            self.username = username177            self._request_auth()178        finally:179            self.transport.lock.release()180 181    def abort(self):182        if self.auth_event is not None:183            self.auth_event.set()184 185    # ...internals...186 187    def _request_auth(self):188        m = Message()189        m.add_byte(cMSG_SERVICE_REQUEST)190        m.add_string("ssh-userauth")191        self.transport._send_message(m)192 193    def _disconnect_service_not_available(self):194        m = Message()195        m.add_byte(cMSG_DISCONNECT)196        m.add_int(DISCONNECT_SERVICE_NOT_AVAILABLE)197        m.add_string("Service not available")198        m.add_string("en")199        self.transport._send_message(m)200        self.transport.close()201 202    def _disconnect_no_more_auth(self):203        m = Message()204        m.add_byte(cMSG_DISCONNECT)205        m.add_int(DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE)206        m.add_string("No more auth methods available")207        m.add_string("en")208        self.transport._send_message(m)209        self.transport.close()210 211    def _get_key_type_and_bits(self, key):212        """213        Given any key, return its type/algorithm & bits-to-sign.214 215        Intended for input to or verification of, key signatures.216        """217        # Use certificate contents, if available, plain pubkey otherwise218        if key.public_blob:219            return key.public_blob.key_type, key.public_blob.key_blob220        else:221            return key.get_name(), key222 223    def _get_session_blob(self, key, service, username, algorithm):224        m = Message()225        m.add_string(self.transport.session_id)226        m.add_byte(cMSG_USERAUTH_REQUEST)227        m.add_string(username)228        m.add_string(service)229        m.add_string("publickey")230        m.add_boolean(True)231        _, bits = self._get_key_type_and_bits(key)232        m.add_string(algorithm)233        m.add_string(bits)234        return m.asbytes()235 236    def wait_for_response(self, event):237        max_ts = None238        if self.transport.auth_timeout is not None:239            max_ts = time.time() + self.transport.auth_timeout240        while True:241            event.wait(0.1)242            if not self.transport.is_active():243                e = self.transport.get_exception()244                if (e is None) or issubclass(e.__class__, EOFError):245                    e = AuthenticationException(246                        "Authentication failed: transport shut down or saw EOF"247                    )248                raise e249            if event.is_set():250                break251            if max_ts is not None and max_ts <= time.time():252                raise AuthenticationException("Authentication timeout.")253 254        if not self.is_authenticated():255            e = self.transport.get_exception()256            if e is None:257                e = AuthenticationException("Authentication failed.")258            # this is horrible.  Python Exception isn't yet descended from259            # object, so type(e) won't work. :(260            # TODO 4.0: lol. just lmao.261            if issubclass(e.__class__, PartialAuthentication):262                return e.allowed_types263            raise e264        return []265 266    def _parse_service_request(self, m):267        service = m.get_text()268        if self.transport.server_mode and (service == "ssh-userauth"):269            # accepted270            m = Message()271            m.add_byte(cMSG_SERVICE_ACCEPT)272            m.add_string(service)273            self.transport._send_message(m)274            banner, language = self.transport.server_object.get_banner()275            if banner:276                m = Message()277                m.add_byte(cMSG_USERAUTH_BANNER)278                m.add_string(banner)279                m.add_string(language)280                self.transport._send_message(m)281            return282        # dunno this one283        self._disconnect_service_not_available()284 285    def _generate_key_from_request(self, algorithm, keyblob):286        # For use in server mode.287        options = self.transport.preferred_pubkeys288        if algorithm.replace("-cert-v01@openssh.com", "") not in options:289            err = (290                "Auth rejected: pubkey algorithm '{}' unsupported or disabled"291            )292            self._log(INFO, err.format(algorithm))293            return None294        return self.transport._key_info[algorithm](Message(keyblob))295 296    def _choose_fallback_pubkey_algorithm(self, key_type, my_algos):297        # Fallback: first one in our (possibly tweaked by caller) list298        pubkey_algo = my_algos[0]299        msg = "Server did not send a server-sig-algs list; defaulting to our first preferred algo ({!r})"  # noqa300        self._log(DEBUG, msg.format(pubkey_algo))301        self._log(302            DEBUG,303            "NOTE: you may use the 'disabled_algorithms' SSHClient/Transport init kwarg to disable that or other algorithms if your server does not support them!",  # noqa304        )305        return pubkey_algo306 307    def _finalize_pubkey_algorithm(self, key_type):308        # Short-circuit for non-RSA keys309        if "rsa" not in key_type:310            return key_type311        self._log(312            DEBUG,313            "Finalizing pubkey algorithm for key of type {!r}".format(314                key_type315            ),316        )317        # NOTE re #2017: When the key is an RSA cert and the remote server is318        # OpenSSH 7.7 or earlier, always use ssh-rsa-cert-v01@openssh.com.319        # Those versions of the server won't support rsa-sha2 family sig algos320        # for certs specifically, and in tandem with various server bugs321        # regarding server-sig-algs, it's impossible to fit this into the rest322        # of the logic here.323        if key_type.endswith("-cert-v01@openssh.com") and re.search(324            r"-OpenSSH_(?:[1-6]|7\.[0-7])", self.transport.remote_version325        ):326            pubkey_algo = "ssh-rsa-cert-v01@openssh.com"327            self.transport._agreed_pubkey_algorithm = pubkey_algo328            self._log(DEBUG, "OpenSSH<7.8 + RSA cert = forcing ssh-rsa!")329            self._log(330                DEBUG, "Agreed upon {!r} pubkey algorithm".format(pubkey_algo)331            )332            return pubkey_algo333        # Normal attempts to handshake follow from here.334        # Only consider RSA algos from our list, lest we agree on another!335        my_algos = [x for x in self.transport.preferred_pubkeys if "rsa" in x]336        self._log(DEBUG, "Our pubkey algorithm list: {}".format(my_algos))337        # Short-circuit negatively if user disabled all RSA algos (heh)338        if not my_algos:339            raise SSHException(340                "An RSA key was specified, but no RSA pubkey algorithms are configured!"  # noqa341            )342        # Check for server-sig-algs if supported & sent343        server_algo_str = u(344            self.transport.server_extensions.get("server-sig-algs", b(""))345        )346        pubkey_algo = None347        # Prefer to match against server-sig-algs348        if server_algo_str:349            server_algos = server_algo_str.split(",")350            self._log(351                DEBUG, "Server-side algorithm list: {}".format(server_algos)352            )353            # Only use algos from our list that the server likes, in our own354            # preference order. (NOTE: purposefully using same style as in355            # Transport...expect to refactor later)356            agreement = list(filter(server_algos.__contains__, my_algos))357            if agreement:358                pubkey_algo = agreement[0]359                self._log(360                    DEBUG,361                    "Agreed upon {!r} pubkey algorithm".format(pubkey_algo),362                )363            else:364                self._log(DEBUG, "No common pubkey algorithms exist! Dying.")365                # TODO: MAY want to use IncompatiblePeer again here but that's366                # technically for initial key exchange, not pubkey auth.367                err = "Unable to agree on a pubkey algorithm for signing a {!r} key!"  # noqa368                raise AuthenticationException(err.format(key_type))369        # Fallback to something based purely on the key & our configuration370        else:371            pubkey_algo = self._choose_fallback_pubkey_algorithm(372                key_type, my_algos373            )374        if key_type.endswith("-cert-v01@openssh.com"):375            pubkey_algo += "-cert-v01@openssh.com"376        self.transport._agreed_pubkey_algorithm = pubkey_algo377        return pubkey_algo378 379    def _parse_service_accept(self, m):380        service = m.get_text()381        if service == "ssh-userauth":382            self._log(DEBUG, "userauth is OK")383            m = Message()384            m.add_byte(cMSG_USERAUTH_REQUEST)385            m.add_string(self.username)386            m.add_string("ssh-connection")387            m.add_string(self.auth_method)388            if self.auth_method == "password":389                m.add_boolean(False)390                password = b(self.password)391                m.add_string(password)392            elif self.auth_method == "publickey":393                m.add_boolean(True)394                key_type, bits = self._get_key_type_and_bits(self.private_key)395                algorithm = self._finalize_pubkey_algorithm(key_type)396                m.add_string(algorithm)397                m.add_string(bits)398                blob = self._get_session_blob(399                    self.private_key,400                    "ssh-connection",401                    self.username,402                    algorithm,403                )404                sig = self.private_key.sign_ssh_data(blob, algorithm)405                m.add_string(sig)406            elif self.auth_method == "keyboard-interactive":407                m.add_string("")408                m.add_string(self.submethods)409            elif self.auth_method == "gssapi-with-mic":410                sshgss = GSSAuth(self.auth_method, self.gss_deleg_creds)411                m.add_bytes(sshgss.ssh_gss_oids())412                # send the supported GSSAPI OIDs to the server413                self.transport._send_message(m)414                ptype, m = self.transport.packetizer.read_message()415                if ptype == MSG_USERAUTH_BANNER:416                    self._parse_userauth_banner(m)417                    ptype, m = self.transport.packetizer.read_message()418                if ptype == MSG_USERAUTH_GSSAPI_RESPONSE:419                    # Read the mechanism selected by the server. We send just420                    # the Kerberos V5 OID, so the server can only respond with421                    # this OID.422                    mech = m.get_string()423                    m = Message()424                    m.add_byte(cMSG_USERAUTH_GSSAPI_TOKEN)425                    try:426                        m.add_string(427                            sshgss.ssh_init_sec_context(428                                self.gss_host, mech, self.username429                            )430                        )431                    except GSS_EXCEPTIONS as e:432                        return self._handle_local_gss_failure(e)433                    self.transport._send_message(m)434                    while True:435                        ptype, m = self.transport.packetizer.read_message()436                        if ptype == MSG_USERAUTH_GSSAPI_TOKEN:437                            srv_token = m.get_string()438                            try:439                                next_token = sshgss.ssh_init_sec_context(440                                    self.gss_host,441                                    mech,442                                    self.username,443                                    srv_token,444                                )445                            except GSS_EXCEPTIONS as e:446                                return self._handle_local_gss_failure(e)447                            # After this step the GSSAPI should not return any448                            # token. If it does, we keep sending the token to449                            # the server until no more token is returned.450                            if next_token is None:451                                break452                            else:453                                m = Message()454                                m.add_byte(cMSG_USERAUTH_GSSAPI_TOKEN)455                                m.add_string(next_token)456                                self.transport.send_message(m)457                    else:458                        raise SSHException(459                            "Received Package: {}".format(MSG_NAMES[ptype])460                        )461                    m = Message()462                    m.add_byte(cMSG_USERAUTH_GSSAPI_MIC)463                    # send the MIC to the server464                    m.add_string(sshgss.ssh_get_mic(self.transport.session_id))465                elif ptype == MSG_USERAUTH_GSSAPI_ERRTOK:466                    # RFC 4462 says we are not required to implement GSS-API467                    # error messages.468                    # See RFC 4462 Section 3.8 in469                    # http://www.ietf.org/rfc/rfc4462.txt470                    raise SSHException("Server returned an error token")471                elif ptype == MSG_USERAUTH_GSSAPI_ERROR:472                    maj_status = m.get_int()473                    min_status = m.get_int()474                    err_msg = m.get_string()475                    m.get_string()  # Lang tag - discarded476                    raise SSHException(477                        """GSS-API Error:478Major Status: {}479Minor Status: {}480Error Message: {}481""".format(482                            maj_status, min_status, err_msg483                        )484                    )485                elif ptype == MSG_USERAUTH_FAILURE:486                    self._parse_userauth_failure(m)487                    return488                else:489                    raise SSHException(490                        "Received Package: {}".format(MSG_NAMES[ptype])491                    )492            elif (493                self.auth_method == "gssapi-keyex"494                and self.transport.gss_kex_used495            ):496                kexgss = self.transport.kexgss_ctxt497                kexgss.set_username(self.username)498                mic_token = kexgss.ssh_get_mic(self.transport.session_id)499                m.add_string(mic_token)500            elif self.auth_method == "none":501                pass502            else:503                raise SSHException(504                    'Unknown auth method "{}"'.format(self.auth_method)505                )506            self.transport._send_message(m)507        else:508            self._log(509                DEBUG, 'Service request "{}" accepted (?)'.format(service)510            )511 512    def _send_auth_result(self, username, method, result):513        # okay, send result514        m = Message()515        if result == AUTH_SUCCESSFUL:516            self._log(INFO, "Auth granted ({}).".format(method))517            m.add_byte(cMSG_USERAUTH_SUCCESS)518            self.authenticated = True519        else:520            self._log(INFO, "Auth rejected ({}).".format(method))521            m.add_byte(cMSG_USERAUTH_FAILURE)522            m.add_string(523                self.transport.server_object.get_allowed_auths(username)524            )525            if result == AUTH_PARTIALLY_SUCCESSFUL:526                m.add_boolean(True)527            else:528                m.add_boolean(False)529                self.auth_fail_count += 1530        self.transport._send_message(m)531        if self.auth_fail_count >= 10:532            self._disconnect_no_more_auth()533        if result == AUTH_SUCCESSFUL:534            self.transport._auth_trigger()535 536    def _interactive_query(self, q):537        # make interactive query instead of response538        m = Message()539        m.add_byte(cMSG_USERAUTH_INFO_REQUEST)540        m.add_string(q.name)541        m.add_string(q.instructions)542        m.add_string(bytes())543        m.add_int(len(q.prompts))544        for p in q.prompts:545            m.add_string(p[0])546            m.add_boolean(p[1])547        self.transport._send_message(m)548 549    def _parse_userauth_request(self, m):550        if not self.transport.server_mode:551            # er, uh... what?552            m = Message()553            m.add_byte(cMSG_USERAUTH_FAILURE)554            m.add_string("none")555            m.add_boolean(False)556            self.transport._send_message(m)557            return558        if self.authenticated:559            # ignore560            return561        username = m.get_text()562        service = m.get_text()563        method = m.get_text()564        self._log(565            DEBUG,566            "Auth request (type={}) service={}, username={}".format(567                method, service, username568            ),569        )570        if service != "ssh-connection":571            self._disconnect_service_not_available()572            return573        if (self.auth_username is not None) and (574            self.auth_username != username575        ):576            self._log(577                WARNING,578                "Auth rejected because the client attempted to change username in mid-flight",  # noqa579            )580            self._disconnect_no_more_auth()581            return582        self.auth_username = username583        # check if GSS-API authentication is enabled584        gss_auth = self.transport.server_object.enable_auth_gssapi()585 586        if method == "none":587            result = self.transport.server_object.check_auth_none(username)588        elif method == "password":589            changereq = m.get_boolean()590            password = m.get_binary()591            try:592                password = password.decode("UTF-8")593            except UnicodeError:594                # some clients/servers expect non-utf-8 passwords!595                # in this case, just return the raw byte string.596                pass597            if changereq:598                # always treated as failure, since we don't support changing599                # passwords, but collect the list of valid auth types from600                # the callback anyway601                self._log(DEBUG, "Auth request to change passwords (rejected)")602                newpassword = m.get_binary()603                try:604                    newpassword = newpassword.decode("UTF-8", "replace")605                except UnicodeError:606                    pass607                result = AUTH_FAILED608            else:609                result = self.transport.server_object.check_auth_password(610                    username, password611                )612        elif method == "publickey":613            sig_attached = m.get_boolean()614            # NOTE: server never wants to guess a client's algo, they're615            # telling us directly. No need for _finalize_pubkey_algorithm616            # anywhere in this flow.617            algorithm = m.get_text()618            keyblob = m.get_binary()619            try:620                key = self._generate_key_from_request(algorithm, keyblob)621            except SSHException as e:622                self._log(INFO, "Auth rejected: public key: {}".format(str(e)))623                key = None624            except Exception as e:625                msg = "Auth rejected: unsupported or mangled public key ({}: {})"  # noqa626                self._log(INFO, msg.format(e.__class__.__name__, e))627                key = None628            if key is None:629                self._disconnect_no_more_auth()630                return631            # first check if this key is okay... if not, we can skip the verify632            result = self.transport.server_object.check_auth_publickey(633                username, key634            )635            if result != AUTH_FAILED:636                # key is okay, verify it637                if not sig_attached:638                    # client wants to know if this key is acceptable, before it639                    # signs anything...  send special "ok" message640                    m = Message()641                    m.add_byte(cMSG_USERAUTH_PK_OK)642                    m.add_string(algorithm)643                    m.add_string(keyblob)644                    self.transport._send_message(m)645                    return646                sig = Message(m.get_binary())647                blob = self._get_session_blob(648                    key, service, username, algorithm649                )650                if not key.verify_ssh_sig(blob, sig):651                    self._log(INFO, "Auth rejected: invalid signature")652                    result = AUTH_FAILED653        elif method == "keyboard-interactive":654            submethods = m.get_string()655            result = self.transport.server_object.check_auth_interactive(656                username, submethods657            )658            if isinstance(result, InteractiveQuery):659                # make interactive query instead of response660                self._interactive_query(result)661                return662        elif method == "gssapi-with-mic" and gss_auth:663            sshgss = GSSAuth(method)664            # Read the number of OID mechanisms supported by the client.665            # OpenSSH sends just one OID. It's the Kerveros V5 OID and that's666            # the only OID we support.667            mechs = m.get_int()668            # We can't accept more than one OID, so if the SSH client sends669            # more than one, disconnect.670            if mechs > 1:671                self._log(672                    INFO,673                    "Disconnect: Received more than one GSS-API OID mechanism",674                )675                self._disconnect_no_more_auth()676            desired_mech = m.get_string()677            mech_ok = sshgss.ssh_check_mech(desired_mech)678            # if we don't support the mechanism, disconnect.679            if not mech_ok:680                self._log(681                    INFO,682                    "Disconnect: Received an invalid GSS-API OID mechanism",683                )684                self._disconnect_no_more_auth()685            # send the Kerberos V5 GSSAPI OID to the client686            supported_mech = sshgss.ssh_gss_oids("server")687            # RFC 4462 says we are not required to implement GSS-API error688            # messages. See section 3.8 in http://www.ietf.org/rfc/rfc4462.txt689            m = Message()690            m.add_byte(cMSG_USERAUTH_GSSAPI_RESPONSE)691            m.add_bytes(supported_mech)692            self.transport.auth_handler = GssapiWithMicAuthHandler(693                self, sshgss694            )695            self.transport._expected_packet = (696                MSG_USERAUTH_GSSAPI_TOKEN,697                MSG_USERAUTH_REQUEST,698                MSG_SERVICE_REQUEST,699            )700            self.transport._send_message(m)701            return702        elif method == "gssapi-keyex" and gss_auth:703            mic_token = m.get_string()704            sshgss = self.transport.kexgss_ctxt705            if sshgss is None:706                # If there is no valid context, we reject the authentication707                result = AUTH_FAILED708                self._send_auth_result(username, method, result)709            try:710                sshgss.ssh_check_mic(711                    mic_token, self.transport.session_id, self.auth_username712                )713            except Exception:714                result = AUTH_FAILED715                self._send_auth_result(username, method, result)716                raise717            result = AUTH_SUCCESSFUL718            self.transport.server_object.check_auth_gssapi_keyex(719                username, result720            )721        else:722            result = self.transport.server_object.check_auth_none(username)723        # okay, send result724        self._send_auth_result(username, method, result)725 726    def _parse_userauth_success(self, m):727        self._log(728            INFO, "Authentication ({}) successful!".format(self.auth_method)729        )730        self.authenticated = True731        self.transport._auth_trigger()732        if self.auth_event is not None:733            self.auth_event.set()734 735    def _parse_userauth_failure(self, m):736        authlist = m.get_list()737        # TODO 4.0: we aren't giving callers access to authlist _unless_ it's738        # partial authentication, so eg authtype=none can't work unless we739        # tweak this.740        partial = m.get_boolean()741        if partial:742            self._log(INFO, "Authentication continues...")743            self._log(DEBUG, "Methods: " + str(authlist))744            self.transport.saved_exception = PartialAuthentication(authlist)745        elif self.auth_method not in authlist:746            for msg in (747                "Authentication type ({}) not permitted.".format(748                    self.auth_method749                ),750                "Allowed methods: {}".format(authlist),751            ):752                self._log(DEBUG, msg)753            self.transport.saved_exception = BadAuthenticationType(754                "Bad authentication type", authlist755            )756        else:757            self._log(758                INFO, "Authentication ({}) failed.".format(self.auth_method)759            )760        self.authenticated = False761        self.username = None762        if self.auth_event is not None:763            self.auth_event.set()764 765    def _parse_userauth_banner(self, m):766        banner = m.get_string()767        self.banner = banner768        self._log(INFO, "Auth banner: {}".format(banner))769        # who cares.770 771    def _parse_userauth_info_request(self, m):772        if self.auth_method != "keyboard-interactive":773            raise SSHException("Illegal info request from server")774        title = m.get_text()775        instructions = m.get_text()776        m.get_binary()  # lang777        prompts = m.get_int()778        prompt_list = []779        for i in range(prompts):780            prompt_list.append((m.get_text(), m.get_boolean()))781        response_list = self.interactive_handler(782            title, instructions, prompt_list783        )784 785        m = Message()786        m.add_byte(cMSG_USERAUTH_INFO_RESPONSE)787        m.add_int(len(response_list))788        for r in response_list:789            m.add_string(r)790        self.transport._send_message(m)791 792    def _parse_userauth_info_response(self, m):793        if not self.transport.server_mode:794            raise SSHException("Illegal info response from server")795        n = m.get_int()796        responses = []797        for i in range(n):798            responses.append(m.get_text())799        result = self.transport.server_object.check_auth_interactive_response(800            responses801        )802        if isinstance(result, InteractiveQuery):803            # make interactive query instead of response804            self._interactive_query(result)805            return806        self._send_auth_result(807            self.auth_username, "keyboard-interactive", result808        )809 810    def _handle_local_gss_failure(self, e):811        self.transport.saved_exception = e812        self._log(DEBUG, "GSSAPI failure: {}".format(e))813        self._log(INFO, "Authentication ({}) failed.".format(self.auth_method))814        self.authenticated = False815        self.username = None816        if self.auth_event is not None:817            self.auth_event.set()818        return819 820    # TODO 4.0: MAY make sense to make these tables into actual821    # classes/instances that can be fed a mode bool or whatever. Or,822    # alternately (both?) make the message types small classes or enums that823    # embed this info within themselves (which could also then tidy up the824    # current 'integer -> human readable short string' stuff in common.py).825    # TODO: if we do that, also expose 'em publicly.826 827    # Messages which should be handled _by_ servers (sent by clients)828    @property829    def _server_handler_table(self):830        return {831            # TODO 4.0: MSG_SERVICE_REQUEST ought to eventually move into832            # Transport's server mode like the client side did, just for833            # consistency.834            MSG_SERVICE_REQUEST: self._parse_service_request,835            MSG_USERAUTH_REQUEST: self._parse_userauth_request,836            MSG_USERAUTH_INFO_RESPONSE: self._parse_userauth_info_response,837        }838 839    # Messages which should be handled _by_ clients (sent by servers)840    @property841    def _client_handler_table(self):842        return {843            MSG_SERVICE_ACCEPT: self._parse_service_accept,844            MSG_USERAUTH_SUCCESS: self._parse_userauth_success,845            MSG_USERAUTH_FAILURE: self._parse_userauth_failure,846            MSG_USERAUTH_BANNER: self._parse_userauth_banner,847            MSG_USERAUTH_INFO_REQUEST: self._parse_userauth_info_request,848        }849 850    # NOTE: prior to the fix for #1283, this was a static dict instead of a851    # property. Should be backwards compatible in most/all cases.852    @property853    def _handler_table(self):854        if self.transport.server_mode:855            return self._server_handler_table856        else:857            return self._client_handler_table858 859 860class GssapiWithMicAuthHandler:861    """A specialized Auth handler for gssapi-with-mic862 863    During the GSSAPI token exchange we need a modified dispatch table,864    because the packet type numbers are not unique.865    """866 867    method = "gssapi-with-mic"868 869    def __init__(self, delegate, sshgss):870        self._delegate = delegate871        self.sshgss = sshgss872 873    def abort(self):874        self._restore_delegate_auth_handler()875        return self._delegate.abort()876 877    @property878    def transport(self):879        return self._delegate.transport880 881    @property882    def _send_auth_result(self):883        return self._delegate._send_auth_result884 885    @property886    def auth_username(self):887        return self._delegate.auth_username888 889    @property890    def gss_host(self):891        return self._delegate.gss_host892 893    def _restore_delegate_auth_handler(self):894        self.transport.auth_handler = self._delegate895 896    def _parse_userauth_gssapi_token(self, m):897        client_token = m.get_string()898        # use the client token as input to establish a secure899        # context.900        sshgss = self.sshgss901        try:902            token = sshgss.ssh_accept_sec_context(903                self.gss_host, client_token, self.auth_username904            )905        except Exception as e:906            self.transport.saved_exception = e907            result = AUTH_FAILED908            self._restore_delegate_auth_handler()909            self._send_auth_result(self.auth_username, self.method, result)910            raise911        if token is not None:912            m = Message()913            m.add_byte(cMSG_USERAUTH_GSSAPI_TOKEN)914            m.add_string(token)915            self.transport._expected_packet = (916                MSG_USERAUTH_GSSAPI_TOKEN,917                MSG_USERAUTH_GSSAPI_MIC,918                MSG_USERAUTH_REQUEST,919            )920            self.transport._send_message(m)921 922    def _parse_userauth_gssapi_mic(self, m):923        mic_token = m.get_string()924        sshgss = self.sshgss925        username = self.auth_username926        self._restore_delegate_auth_handler()927        try:928            sshgss.ssh_check_mic(929                mic_token, self.transport.session_id, username930            )931        except Exception as e:932            self.transport.saved_exception = e933            result = AUTH_FAILED934            self._send_auth_result(username, self.method, result)935            raise936        # TODO: Implement client credential saving.937        # The OpenSSH server is able to create a TGT with the delegated938        # client credentials, but this is not supported by GSS-API.939        result = AUTH_SUCCESSFUL940        self.transport.server_object.check_auth_gssapi_with_mic(941            username, result942        )943        # okay, send result944        self._send_auth_result(username, self.method, result)945 946    def _parse_service_request(self, m):947        self._restore_delegate_auth_handler()948        return self._delegate._parse_service_request(m)949 950    def _parse_userauth_request(self, m):951        self._restore_delegate_auth_handler()952        return self._delegate._parse_userauth_request(m)953 954    __handler_table = {955        MSG_SERVICE_REQUEST: _parse_service_request,956        MSG_USERAUTH_REQUEST: _parse_userauth_request,957        MSG_USERAUTH_GSSAPI_TOKEN: _parse_userauth_gssapi_token,958        MSG_USERAUTH_GSSAPI_MIC: _parse_userauth_gssapi_mic,959    }960 961    @property962    def _handler_table(self):963        # TODO: determine if we can cut this up like we did for the primary964        # AuthHandler class.965        return self.__handler_table966 967 968class AuthOnlyHandler(AuthHandler):969    """970    AuthHandler, and just auth, no service requests!971 972    .. versionadded:: 3.2973    """974 975    # NOTE: this purposefully duplicates some of the parent class in order to976    # modernize, refactor, etc. The intent is that eventually we will collapse977    # this one onto the parent in a backwards incompatible release.978 979    @property980    def _client_handler_table(self):981        my_table = super()._client_handler_table.copy()982        del my_table[MSG_SERVICE_ACCEPT]983        return my_table984 985    def send_auth_request(self, username, method, finish_message=None):986        """987        Submit a userauth request message & wait for response.988 989        Performs the transport message send call, sets self.auth_event, and990        will lock-n-block as necessary to both send, and wait for response to,991        the USERAUTH_REQUEST.992 993        Most callers will want to supply a callback to ``finish_message``,994        which accepts a Message ``m`` and may call mutator methods on it to add995        more fields.996        """997        # Store a few things for reference in handlers, including auth failure998        # handler (which needs to know if we were using a bad method, etc)999        self.auth_method = method1000        self.username = username1001        # Generic userauth request fields1002        m = Message()1003        m.add_byte(cMSG_USERAUTH_REQUEST)1004        m.add_string(username)1005        m.add_string("ssh-connection")1006        m.add_string(method)1007        # Caller usually has more to say, such as injecting password, key etc1008        finish_message(m)1009        # TODO 4.0: seems odd to have the client handle the lock and not1010        # Transport; that _may_ have been an artifact of allowing user1011        # threading event injection? Regardless, we don't want to move _this_1012        # locking into Transport._send_message now, because lots of other1013        # untouched code also uses that method and we might end up1014        # double-locking (?) but 4.0 would be a good time to revisit.1015        with self.transport.lock:1016            self.transport._send_message(m)1017        # We have cut out the higher level event args, but self.auth_event is1018        # still required for self.wait_for_response to function correctly (it's1019        # the mechanism used by the auth success/failure handlers, the abort1020        # handler, and a few other spots like in gssapi.1021        # TODO: interestingly, wait_for_response itself doesn't actually1022        # enforce that its event argument and self.auth_event are the same...1023        self.auth_event = threading.Event()1024        return self.wait_for_response(self.auth_event)1025 1026    def auth_none(self, username):1027        return self.send_auth_request(username, "none")1028 1029    def auth_publickey(self, username, key):1030        key_type, bits = self._get_key_type_and_bits(key)1031        algorithm = self._finalize_pubkey_algorithm(key_type)1032        blob = self._get_session_blob(1033            key,1034            "ssh-connection",1035            username,1036            algorithm,1037        )1038 1039        def finish(m):1040            # This field doesn't appear to be named, but is False when querying1041            # for permission (ie knowing whether to even prompt a user for1042            # passphrase, etc) or True when just going for it. Paramiko has1043            # never bothered with the former type of message, apparently.1044            m.add_boolean(True)1045            m.add_string(algorithm)1046            m.add_string(bits)1047            m.add_string(key.sign_ssh_data(blob, algorithm))1048 1049        return self.send_auth_request(username, "publickey", finish)1050 1051    def auth_password(self, username, password):1052        def finish(m):1053            # Unnamed field that equates to "I am changing my password", which1054            # Paramiko clientside never supported and serverside only sort of1055            # supported.1056            m.add_boolean(False)1057            m.add_string(b(password))1058 1059        return self.send_auth_request(username, "password", finish)1060 1061    def auth_interactive(self, username, handler, submethods=""):1062        """1063        response_list = handler(title, instructions, prompt_list)1064        """1065        # Unlike most siblings, this auth method _does_ require other1066        # superclass handlers (eg userauth info request) to understand1067        # what's going on, so we still set some self attributes.1068        self.auth_method = "keyboard_interactive"1069        self.interactive_handler = handler1070 1071        def finish(m):1072            # Empty string for deprecated language tag field, per RFC 4256:1073            # https://www.rfc-editor.org/rfc/rfc4256#section-3.11074            m.add_string("")1075            m.add_string(submethods)1076 1077        return self.send_auth_request(username, "keyboard-interactive", finish)1078 1079    # NOTE: not strictly 'auth only' related, but allows users to opt-in.1080    def _choose_fallback_pubkey_algorithm(self, key_type, my_algos):1081        msg = "Server did not send a server-sig-algs list; defaulting to something in our preferred algorithms list"  # noqa1082        self._log(DEBUG, msg)1083        noncert_key_type = key_type.replace("-cert-v01@openssh.com", "")1084        if key_type in my_algos or noncert_key_type in my_algos:1085            actual = key_type if key_type in my_algos else noncert_key_type1086            msg = f"Current key type, {actual!r}, is in our preferred list; using that"  # noqa1087            algo = actual1088        else:1089            algo = my_algos[0]1090            msg = f"{key_type!r} not in our list - trying first list item instead, {algo!r}"  # noqa1091        self._log(DEBUG, msg)1092        return algo1093