codekingpro/portable-devtools
114k
1# Copyright (C) 2013-2014 science + computing ag2# Author: Sebastian Deiss <sebastian.deiss@t-online.de>3#4#5# This file is part of paramiko.6#7# Paramiko is free software; you can redistribute it and/or modify it under the8# terms of the GNU Lesser General Public License as published by the Free9# Software Foundation; either version 2.1 of the License, or (at your option)10# any later version.11#12# Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY13# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR14# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more15# details.16#17# You should have received a copy of the GNU Lesser General Public License18# along with Paramiko; if not, write to the Free Software Foundation, Inc.,19# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.20 21 22"""23This module provides GSS-API / SSPI authentication as defined in :rfc:`4462`.24 25.. note:: Credential delegation is not supported in server mode.26 27.. seealso:: :doc:`/api/kex_gss`28 29.. versionadded:: 1.1530"""31 32import struct33import os34import sys35 36 37#: A boolean constraint that indicates if GSS-API / SSPI is available.38GSS_AUTH_AVAILABLE = True39 40 41#: A tuple of the exception types used by the underlying GSSAPI implementation.42GSS_EXCEPTIONS = ()43 44 45#: :var str _API: Constraint for the used API46_API = None47 48try:49 import gssapi50 51 if hasattr(gssapi, "__title__") and gssapi.__title__ == "python-gssapi":52 # old, unmaintained python-gssapi package53 _API = "MIT" # keep this for compatibility54 GSS_EXCEPTIONS = (gssapi.GSSException,)55 else:56 _API = "PYTHON-GSSAPI-NEW"57 GSS_EXCEPTIONS = (58 gssapi.exceptions.GeneralError,59 gssapi.raw.misc.GSSError,60 )61except (ImportError, OSError):62 try:63 import pywintypes64 import sspicon65 import sspi66 67 _API = "SSPI"68 GSS_EXCEPTIONS = (pywintypes.error,)69 except ImportError:70 GSS_AUTH_AVAILABLE = False71 _API = None72 73from paramiko.common import MSG_USERAUTH_REQUEST74from paramiko.ssh_exception import SSHException75from paramiko._version import __version_info__76 77 78def GSSAuth(auth_method, gss_deleg_creds=True):79 """80 Provide SSH2 GSS-API / SSPI authentication.81 82 :param str auth_method: The name of the SSH authentication mechanism83 (gssapi-with-mic or gss-keyex)84 :param bool gss_deleg_creds: Delegate client credentials or not.85 We delegate credentials by default.86 :return: Either an `._SSH_GSSAPI_OLD` or `._SSH_GSSAPI_NEW` (Unix)87 object or an `_SSH_SSPI` (Windows) object88 :rtype: object89 90 :raises: ``ImportError`` -- If no GSS-API / SSPI module could be imported.91 92 :see: `RFC 4462 <http://www.ietf.org/rfc/rfc4462.txt>`_93 :note: Check for the available API and return either an `._SSH_GSSAPI_OLD`94 (MIT GSSAPI using python-gssapi package) object, an95 `._SSH_GSSAPI_NEW` (MIT GSSAPI using gssapi package) object96 or an `._SSH_SSPI` (MS SSPI) object.97 If there is no supported API available,98 ``None`` will be returned.99 """100 if _API == "MIT":101 return _SSH_GSSAPI_OLD(auth_method, gss_deleg_creds)102 elif _API == "PYTHON-GSSAPI-NEW":103 return _SSH_GSSAPI_NEW(auth_method, gss_deleg_creds)104 elif _API == "SSPI" and os.name == "nt":105 return _SSH_SSPI(auth_method, gss_deleg_creds)106 else:107 raise ImportError("Unable to import a GSS-API / SSPI module!")108 109 110class _SSH_GSSAuth:111 """112 Contains the shared variables and methods of `._SSH_GSSAPI_OLD`,113 `._SSH_GSSAPI_NEW` and `._SSH_SSPI`.114 """115 116 def __init__(self, auth_method, gss_deleg_creds):117 """118 :param str auth_method: The name of the SSH authentication mechanism119 (gssapi-with-mic or gss-keyex)120 :param bool gss_deleg_creds: Delegate client credentials or not121 """122 self._auth_method = auth_method123 self._gss_deleg_creds = gss_deleg_creds124 self._gss_host = None125 self._username = None126 self._session_id = None127 self._service = "ssh-connection"128 """129 OpenSSH supports Kerberos V5 mechanism only for GSS-API authentication,130 so we also support the krb5 mechanism only.131 """132 self._krb5_mech = "1.2.840.113554.1.2.2"133 134 # client mode135 self._gss_ctxt = None136 self._gss_ctxt_status = False137 138 # server mode139 self._gss_srv_ctxt = None140 self._gss_srv_ctxt_status = False141 self.cc_file = None142 143 def set_service(self, service):144 """145 This is just a setter to use a non default service.146 I added this method, because RFC 4462 doesn't specify "ssh-connection"147 as the only service value.148 149 :param str service: The desired SSH service150 """151 if service.find("ssh-"):152 self._service = service153 154 def set_username(self, username):155 """156 Setter for C{username}. If GSS-API Key Exchange is performed, the157 username is not set by C{ssh_init_sec_context}.158 159 :param str username: The name of the user who attempts to login160 """161 self._username = username162 163 def ssh_gss_oids(self, mode="client"):164 """165 This method returns a single OID, because we only support the166 Kerberos V5 mechanism.167 168 :param str mode: Client for client mode and server for server mode169 :return: A byte sequence containing the number of supported170 OIDs, the length of the OID and the actual OID encoded with171 DER172 :note: In server mode we just return the OID length and the DER encoded173 OID.174 """175 from pyasn1.type.univ import ObjectIdentifier176 from pyasn1.codec.der import encoder177 178 OIDs = self._make_uint32(1)179 krb5_OID = encoder.encode(ObjectIdentifier(self._krb5_mech))180 OID_len = self._make_uint32(len(krb5_OID))181 if mode == "server":182 return OID_len + krb5_OID183 return OIDs + OID_len + krb5_OID184 185 def ssh_check_mech(self, desired_mech):186 """187 Check if the given OID is the Kerberos V5 OID (server mode).188 189 :param str desired_mech: The desired GSS-API mechanism of the client190 :return: ``True`` if the given OID is supported, otherwise C{False}191 """192 from pyasn1.codec.der import decoder193 194 mech, __ = decoder.decode(desired_mech)195 if mech.__str__() != self._krb5_mech:196 return False197 return True198 199 # Internals200 # -------------------------------------------------------------------------201 def _make_uint32(self, integer):202 """203 Create a 32 bit unsigned integer (The byte sequence of an integer).204 205 :param int integer: The integer value to convert206 :return: The byte sequence of an 32 bit integer207 """208 return struct.pack("!I", integer)209 210 def _ssh_build_mic(self, session_id, username, service, auth_method):211 """212 Create the SSH2 MIC filed for gssapi-with-mic.213 214 :param str session_id: The SSH session ID215 :param str username: The name of the user who attempts to login216 :param str service: The requested SSH service217 :param str auth_method: The requested SSH authentication mechanism218 :return: The MIC as defined in RFC 4462. The contents of the219 MIC field are:220 string session_identifier,221 byte SSH_MSG_USERAUTH_REQUEST,222 string user-name,223 string service (ssh-connection),224 string authentication-method225 (gssapi-with-mic or gssapi-keyex)226 """227 mic = self._make_uint32(len(session_id))228 mic += session_id229 mic += struct.pack("B", MSG_USERAUTH_REQUEST)230 mic += self._make_uint32(len(username))231 mic += username.encode()232 mic += self._make_uint32(len(service))233 mic += service.encode()234 mic += self._make_uint32(len(auth_method))235 mic += auth_method.encode()236 return mic237 238 239class _SSH_GSSAPI_OLD(_SSH_GSSAuth):240 """241 Implementation of the GSS-API MIT Kerberos Authentication for SSH2,242 using the older (unmaintained) python-gssapi package.243 244 :see: `.GSSAuth`245 """246 247 def __init__(self, auth_method, gss_deleg_creds):248 """249 :param str auth_method: The name of the SSH authentication mechanism250 (gssapi-with-mic or gss-keyex)251 :param bool gss_deleg_creds: Delegate client credentials or not252 """253 _SSH_GSSAuth.__init__(self, auth_method, gss_deleg_creds)254 255 if self._gss_deleg_creds:256 self._gss_flags = (257 gssapi.C_PROT_READY_FLAG,258 gssapi.C_INTEG_FLAG,259 gssapi.C_MUTUAL_FLAG,260 gssapi.C_DELEG_FLAG,261 )262 else:263 self._gss_flags = (264 gssapi.C_PROT_READY_FLAG,265 gssapi.C_INTEG_FLAG,266 gssapi.C_MUTUAL_FLAG,267 )268 269 def ssh_init_sec_context(270 self, target, desired_mech=None, username=None, recv_token=None271 ):272 """273 Initialize a GSS-API context.274 275 :param str username: The name of the user who attempts to login276 :param str target: The hostname of the target to connect to277 :param str desired_mech: The negotiated GSS-API mechanism278 ("pseudo negotiated" mechanism, because we279 support just the krb5 mechanism :-))280 :param str recv_token: The GSS-API token received from the Server281 :raises:282 `.SSHException` -- Is raised if the desired mechanism of the client283 is not supported284 :return: A ``String`` if the GSS-API has returned a token or285 ``None`` if no token was returned286 """287 from pyasn1.codec.der import decoder288 289 self._username = username290 self._gss_host = target291 targ_name = gssapi.Name(292 "host@" + self._gss_host, gssapi.C_NT_HOSTBASED_SERVICE293 )294 ctx = gssapi.Context()295 ctx.flags = self._gss_flags296 if desired_mech is None:297 krb5_mech = gssapi.OID.mech_from_string(self._krb5_mech)298 else:299 mech, __ = decoder.decode(desired_mech)300 if mech.__str__() != self._krb5_mech:301 raise SSHException("Unsupported mechanism OID.")302 else:303 krb5_mech = gssapi.OID.mech_from_string(self._krb5_mech)304 token = None305 try:306 if recv_token is None:307 self._gss_ctxt = gssapi.InitContext(308 peer_name=targ_name,309 mech_type=krb5_mech,310 req_flags=ctx.flags,311 )312 token = self._gss_ctxt.step(token)313 else:314 token = self._gss_ctxt.step(recv_token)315 except gssapi.GSSException:316 message = "{} Target: {}".format(sys.exc_info()[1], self._gss_host)317 raise gssapi.GSSException(message)318 self._gss_ctxt_status = self._gss_ctxt.established319 return token320 321 def ssh_get_mic(self, session_id, gss_kex=False):322 """323 Create the MIC token for a SSH2 message.324 325 :param str session_id: The SSH session ID326 :param bool gss_kex: Generate the MIC for GSS-API Key Exchange or not327 :return: gssapi-with-mic:328 Returns the MIC token from GSS-API for the message we created329 with ``_ssh_build_mic``.330 gssapi-keyex:331 Returns the MIC token from GSS-API with the SSH session ID as332 message.333 """334 self._session_id = session_id335 if not gss_kex:336 mic_field = self._ssh_build_mic(337 self._session_id,338 self._username,339 self._service,340 self._auth_method,341 )342 mic_token = self._gss_ctxt.get_mic(mic_field)343 else:344 # for key exchange with gssapi-keyex345 mic_token = self._gss_srv_ctxt.get_mic(self._session_id)346 return mic_token347 348 def ssh_accept_sec_context(self, hostname, recv_token, username=None):349 """350 Accept a GSS-API context (server mode).351 352 :param str hostname: The servers hostname353 :param str username: The name of the user who attempts to login354 :param str recv_token: The GSS-API Token received from the server,355 if it's not the initial call.356 :return: A ``String`` if the GSS-API has returned a token or ``None``357 if no token was returned358 """359 # hostname and username are not required for GSSAPI, but for SSPI360 self._gss_host = hostname361 self._username = username362 if self._gss_srv_ctxt is None:363 self._gss_srv_ctxt = gssapi.AcceptContext()364 token = self._gss_srv_ctxt.step(recv_token)365 self._gss_srv_ctxt_status = self._gss_srv_ctxt.established366 return token367 368 def ssh_check_mic(self, mic_token, session_id, username=None):369 """370 Verify the MIC token for a SSH2 message.371 372 :param str mic_token: The MIC token received from the client373 :param str session_id: The SSH session ID374 :param str username: The name of the user who attempts to login375 :return: None if the MIC check was successful376 :raises: ``gssapi.GSSException`` -- if the MIC check failed377 """378 self._session_id = session_id379 self._username = username380 if self._username is not None:381 # server mode382 mic_field = self._ssh_build_mic(383 self._session_id,384 self._username,385 self._service,386 self._auth_method,387 )388 self._gss_srv_ctxt.verify_mic(mic_field, mic_token)389 else:390 # for key exchange with gssapi-keyex391 # client mode392 self._gss_ctxt.verify_mic(self._session_id, mic_token)393 394 @property395 def credentials_delegated(self):396 """397 Checks if credentials are delegated (server mode).398 399 :return: ``True`` if credentials are delegated, otherwise ``False``400 """401 if self._gss_srv_ctxt.delegated_cred is not None:402 return True403 return False404 405 def save_client_creds(self, client_token):406 """407 Save the Client token in a file. This is used by the SSH server408 to store the client credentials if credentials are delegated409 (server mode).410 411 :param str client_token: The GSS-API token received form the client412 :raises:413 ``NotImplementedError`` -- Credential delegation is currently not414 supported in server mode415 """416 raise NotImplementedError417 418 419if __version_info__ < (2, 5):420 # provide the old name for strict backward compatibility421 _SSH_GSSAPI = _SSH_GSSAPI_OLD422 423 424class _SSH_GSSAPI_NEW(_SSH_GSSAuth):425 """426 Implementation of the GSS-API MIT Kerberos Authentication for SSH2,427 using the newer, currently maintained gssapi package.428 429 :see: `.GSSAuth`430 """431 432 def __init__(self, auth_method, gss_deleg_creds):433 """434 :param str auth_method: The name of the SSH authentication mechanism435 (gssapi-with-mic or gss-keyex)436 :param bool gss_deleg_creds: Delegate client credentials or not437 """438 _SSH_GSSAuth.__init__(self, auth_method, gss_deleg_creds)439 440 if self._gss_deleg_creds:441 self._gss_flags = (442 gssapi.RequirementFlag.protection_ready,443 gssapi.RequirementFlag.integrity,444 gssapi.RequirementFlag.mutual_authentication,445 gssapi.RequirementFlag.delegate_to_peer,446 )447 else:448 self._gss_flags = (449 gssapi.RequirementFlag.protection_ready,450 gssapi.RequirementFlag.integrity,451 gssapi.RequirementFlag.mutual_authentication,452 )453 454 def ssh_init_sec_context(455 self, target, desired_mech=None, username=None, recv_token=None456 ):457 """458 Initialize a GSS-API context.459 460 :param str username: The name of the user who attempts to login461 :param str target: The hostname of the target to connect to462 :param str desired_mech: The negotiated GSS-API mechanism463 ("pseudo negotiated" mechanism, because we464 support just the krb5 mechanism :-))465 :param str recv_token: The GSS-API token received from the Server466 :raises: `.SSHException` -- Is raised if the desired mechanism of the467 client is not supported468 :raises: ``gssapi.exceptions.GSSError`` if there is an error signaled469 by the GSS-API implementation470 :return: A ``String`` if the GSS-API has returned a token or ``None``471 if no token was returned472 """473 from pyasn1.codec.der import decoder474 475 self._username = username476 self._gss_host = target477 targ_name = gssapi.Name(478 "host@" + self._gss_host,479 name_type=gssapi.NameType.hostbased_service,480 )481 if desired_mech is not None:482 mech, __ = decoder.decode(desired_mech)483 if mech.__str__() != self._krb5_mech:484 raise SSHException("Unsupported mechanism OID.")485 krb5_mech = gssapi.MechType.kerberos486 token = None487 if recv_token is None:488 self._gss_ctxt = gssapi.SecurityContext(489 name=targ_name,490 flags=self._gss_flags,491 mech=krb5_mech,492 usage="initiate",493 )494 token = self._gss_ctxt.step(token)495 else:496 token = self._gss_ctxt.step(recv_token)497 self._gss_ctxt_status = self._gss_ctxt.complete498 return token499 500 def ssh_get_mic(self, session_id, gss_kex=False):501 """502 Create the MIC token for a SSH2 message.503 504 :param str session_id: The SSH session ID505 :param bool gss_kex: Generate the MIC for GSS-API Key Exchange or not506 :return: gssapi-with-mic:507 Returns the MIC token from GSS-API for the message we created508 with ``_ssh_build_mic``.509 gssapi-keyex:510 Returns the MIC token from GSS-API with the SSH session ID as511 message.512 :rtype: str513 """514 self._session_id = session_id515 if not gss_kex:516 mic_field = self._ssh_build_mic(517 self._session_id,518 self._username,519 self._service,520 self._auth_method,521 )522 mic_token = self._gss_ctxt.get_signature(mic_field)523 else:524 # for key exchange with gssapi-keyex525 mic_token = self._gss_srv_ctxt.get_signature(self._session_id)526 return mic_token527 528 def ssh_accept_sec_context(self, hostname, recv_token, username=None):529 """530 Accept a GSS-API context (server mode).531 532 :param str hostname: The servers hostname533 :param str username: The name of the user who attempts to login534 :param str recv_token: The GSS-API Token received from the server,535 if it's not the initial call.536 :return: A ``String`` if the GSS-API has returned a token or ``None``537 if no token was returned538 """539 # hostname and username are not required for GSSAPI, but for SSPI540 self._gss_host = hostname541 self._username = username542 if self._gss_srv_ctxt is None:543 self._gss_srv_ctxt = gssapi.SecurityContext(usage="accept")544 token = self._gss_srv_ctxt.step(recv_token)545 self._gss_srv_ctxt_status = self._gss_srv_ctxt.complete546 return token547 548 def ssh_check_mic(self, mic_token, session_id, username=None):549 """550 Verify the MIC token for a SSH2 message.551 552 :param str mic_token: The MIC token received from the client553 :param str session_id: The SSH session ID554 :param str username: The name of the user who attempts to login555 :return: None if the MIC check was successful556 :raises: ``gssapi.exceptions.GSSError`` -- if the MIC check failed557 """558 self._session_id = session_id559 self._username = username560 if self._username is not None:561 # server mode562 mic_field = self._ssh_build_mic(563 self._session_id,564 self._username,565 self._service,566 self._auth_method,567 )568 self._gss_srv_ctxt.verify_signature(mic_field, mic_token)569 else:570 # for key exchange with gssapi-keyex571 # client mode572 self._gss_ctxt.verify_signature(self._session_id, mic_token)573 574 @property575 def credentials_delegated(self):576 """577 Checks if credentials are delegated (server mode).578 579 :return: ``True`` if credentials are delegated, otherwise ``False``580 :rtype: bool581 """582 if self._gss_srv_ctxt.delegated_creds is not None:583 return True584 return False585 586 def save_client_creds(self, client_token):587 """588 Save the Client token in a file. This is used by the SSH server589 to store the client credentials if credentials are delegated590 (server mode).591 592 :param str client_token: The GSS-API token received form the client593 :raises: ``NotImplementedError`` -- Credential delegation is currently594 not supported in server mode595 """596 raise NotImplementedError597 598 599class _SSH_SSPI(_SSH_GSSAuth):600 """601 Implementation of the Microsoft SSPI Kerberos Authentication for SSH2.602 603 :see: `.GSSAuth`604 """605 606 def __init__(self, auth_method, gss_deleg_creds):607 """608 :param str auth_method: The name of the SSH authentication mechanism609 (gssapi-with-mic or gss-keyex)610 :param bool gss_deleg_creds: Delegate client credentials or not611 """612 _SSH_GSSAuth.__init__(self, auth_method, gss_deleg_creds)613 614 if self._gss_deleg_creds:615 self._gss_flags = (616 sspicon.ISC_REQ_INTEGRITY617 | sspicon.ISC_REQ_MUTUAL_AUTH618 | sspicon.ISC_REQ_DELEGATE619 )620 else:621 self._gss_flags = (622 sspicon.ISC_REQ_INTEGRITY | sspicon.ISC_REQ_MUTUAL_AUTH623 )624 625 def ssh_init_sec_context(626 self, target, desired_mech=None, username=None, recv_token=None627 ):628 """629 Initialize a SSPI context.630 631 :param str username: The name of the user who attempts to login632 :param str target: The FQDN of the target to connect to633 :param str desired_mech: The negotiated SSPI mechanism634 ("pseudo negotiated" mechanism, because we635 support just the krb5 mechanism :-))636 :param recv_token: The SSPI token received from the Server637 :raises:638 `.SSHException` -- Is raised if the desired mechanism of the client639 is not supported640 :return: A ``String`` if the SSPI has returned a token or ``None`` if641 no token was returned642 """643 from pyasn1.codec.der import decoder644 645 self._username = username646 self._gss_host = target647 error = 0648 targ_name = "host/" + self._gss_host649 if desired_mech is not None:650 mech, __ = decoder.decode(desired_mech)651 if mech.__str__() != self._krb5_mech:652 raise SSHException("Unsupported mechanism OID.")653 try:654 if recv_token is None:655 self._gss_ctxt = sspi.ClientAuth(656 "Kerberos", scflags=self._gss_flags, targetspn=targ_name657 )658 error, token = self._gss_ctxt.authorize(recv_token)659 token = token[0].Buffer660 except pywintypes.error as e:661 e.strerror += ", Target: {}".format(self._gss_host)662 raise663 664 if error == 0:665 """666 if the status is GSS_COMPLETE (error = 0) the context is fully667 established an we can set _gss_ctxt_status to True.668 """669 self._gss_ctxt_status = True670 token = None671 """672 You won't get another token if the context is fully established,673 so i set token to None instead of ""674 """675 return token676 677 def ssh_get_mic(self, session_id, gss_kex=False):678 """679 Create the MIC token for a SSH2 message.680 681 :param str session_id: The SSH session ID682 :param bool gss_kex: Generate the MIC for Key Exchange with SSPI or not683 :return: gssapi-with-mic:684 Returns the MIC token from SSPI for the message we created685 with ``_ssh_build_mic``.686 gssapi-keyex:687 Returns the MIC token from SSPI with the SSH session ID as688 message.689 """690 self._session_id = session_id691 if not gss_kex:692 mic_field = self._ssh_build_mic(693 self._session_id,694 self._username,695 self._service,696 self._auth_method,697 )698 mic_token = self._gss_ctxt.sign(mic_field)699 else:700 # for key exchange with gssapi-keyex701 mic_token = self._gss_srv_ctxt.sign(self._session_id)702 return mic_token703 704 def ssh_accept_sec_context(self, hostname, username, recv_token):705 """706 Accept a SSPI context (server mode).707 708 :param str hostname: The servers FQDN709 :param str username: The name of the user who attempts to login710 :param str recv_token: The SSPI Token received from the server,711 if it's not the initial call.712 :return: A ``String`` if the SSPI has returned a token or ``None`` if713 no token was returned714 """715 self._gss_host = hostname716 self._username = username717 targ_name = "host/" + self._gss_host718 self._gss_srv_ctxt = sspi.ServerAuth("Kerberos", spn=targ_name)719 error, token = self._gss_srv_ctxt.authorize(recv_token)720 token = token[0].Buffer721 if error == 0:722 self._gss_srv_ctxt_status = True723 token = None724 return token725 726 def ssh_check_mic(self, mic_token, session_id, username=None):727 """728 Verify the MIC token for a SSH2 message.729 730 :param str mic_token: The MIC token received from the client731 :param str session_id: The SSH session ID732 :param str username: The name of the user who attempts to login733 :return: None if the MIC check was successful734 :raises: ``sspi.error`` -- if the MIC check failed735 """736 self._session_id = session_id737 self._username = username738 if username is not None:739 # server mode740 mic_field = self._ssh_build_mic(741 self._session_id,742 self._username,743 self._service,744 self._auth_method,745 )746 # Verifies data and its signature. If verification fails, an747 # sspi.error will be raised.748 self._gss_srv_ctxt.verify(mic_field, mic_token)749 else:750 # for key exchange with gssapi-keyex751 # client mode752 # Verifies data and its signature. If verification fails, an753 # sspi.error will be raised.754 self._gss_ctxt.verify(self._session_id, mic_token)755 756 @property757 def credentials_delegated(self):758 """759 Checks if credentials are delegated (server mode).760 761 :return: ``True`` if credentials are delegated, otherwise ``False``762 """763 return self._gss_flags & sspicon.ISC_REQ_DELEGATE and (764 self._gss_srv_ctxt_status or self._gss_flags765 )766 767 def save_client_creds(self, client_token):768 """769 Save the Client token in a file. This is used by the SSH server770 to store the client credentails if credentials are delegated771 (server mode).772 773 :param str client_token: The SSPI token received form the client774 :raises:775 ``NotImplementedError`` -- Credential delegation is currently not776 supported in server mode777 """778 raise NotImplementedError779 