@@ -38,7 +38,8 @@ cdef class CoreProtocol:
3838 self .encoding = ' utf-8'
3939 # type of `scram` is `SCRAMAuthentcation`
4040 self .scram = None
41- # type of `gss_ctx` is `gssapi.SecurityContext`
41+ # type of `gss_ctx` is `gssapi.SecurityContext` or
42+ # `sspilib.SecurityContext`
4243 self .gss_ctx = None
4344
4445 self ._reset_result()
@@ -635,29 +636,33 @@ cdef class CoreProtocol:
635636 )
636637 self .scram = None
637638
638- elif status == AUTH_REQUIRED_GSS:
639- self ._auth_gss_init()
640- self .auth_msg = self ._auth_gss_step(None )
639+ elif status in (AUTH_REQUIRED_GSS, AUTH_REQUIRED_SSPI):
640+ # AUTH_REQUIRED_SSPI is the same as AUTH_REQUIRED_GSS, except that
641+ # it uses protocol negotiation with SSPI clients. Both methods use
642+ # AUTH_REQUIRED_GSS_CONTINUE for subsequent authentication steps.
643+ if self .gss_ctx is not None :
644+ self .result_type = RESULT_FAILED
645+ self .result = apg_exc.InterfaceError(
646+ ' duplicate GSSAPI/SSPI authentication request' )
647+ else :
648+ if self .con_params.gsslib == ' gssapi' :
649+ self ._auth_gss_init_gssapi()
650+ else :
651+ self ._auth_gss_init_sspi(status == AUTH_REQUIRED_SSPI)
652+ self .auth_msg = self ._auth_gss_step(None )
641653
642654 elif status == AUTH_REQUIRED_GSS_CONTINUE:
643655 server_response = self .buffer.consume_message()
644656 self .auth_msg = self ._auth_gss_step(server_response)
645657
646- elif status in (AUTH_REQUIRED_KERBEROS, AUTH_REQUIRED_SCMCRED,
647- AUTH_REQUIRED_SSPI):
648- self .result_type = RESULT_FAILED
649- self .result = apg_exc.InterfaceError(
650- ' unsupported authentication method requested by the '
651- ' server: {!r}' .format(AUTH_METHOD_NAME[status]))
652-
653658 else :
654659 self .result_type = RESULT_FAILED
655660 self .result = apg_exc.InterfaceError(
656661 ' unsupported authentication method requested by the '
657- ' server: {}' .format(status))
662+ ' server: {!r }' .format(AUTH_METHOD_NAME.get( status, status) ))
658663
659- if status not in [ AUTH_SASL_CONTINUE, AUTH_SASL_FINAL,
660- AUTH_REQUIRED_GSS_CONTINUE] :
664+ if status not in ( AUTH_SASL_CONTINUE, AUTH_SASL_FINAL,
665+ AUTH_REQUIRED_GSS_CONTINUE) :
661666 self .buffer.discard_message()
662667
663668 cdef _auth_password_message_cleartext(self ):
@@ -714,25 +719,43 @@ cdef class CoreProtocol:
714719
715720 return msg
716721
717- cdef _auth_gss_init (self ):
722+ cdef _auth_gss_init_gssapi (self ):
718723 try :
719724 import gssapi
720725 except ModuleNotFoundError:
721- raise RuntimeError (
722- ' gssapi module not found; please install asyncpg[gssapi ] to '
723- ' use asyncpg with Kerberos or GSSAPI authentication'
726+ raise apg_exc.InterfaceError (
727+ ' gssapi module not found; please install asyncpg[gssauth ] to '
728+ ' use asyncpg with Kerberos/ GSSAPI/SSPI authentication'
724729 ) from None
725730
731+ self .gss_ctx = gssapi.SecurityContext(
732+ name = gssapi.Name(self ._auth_gss_get_spn()), usage = ' initiate' )
733+
734+ cdef _auth_gss_init_sspi(self , bint negotiate):
735+ try :
736+ import sspilib
737+ except ModuleNotFoundError:
738+ raise apg_exc.InterfaceError(
739+ ' sspilib module not found; please install asyncpg[gssauth] to '
740+ ' use asyncpg with Kerberos/GSSAPI/SSPI authentication'
741+ ) from None
742+
743+ self .gss_ctx = sspilib.ClientSecurityContext(
744+ target_name = self ._auth_gss_get_spn(),
745+ credential = sspilib.UserCredential(
746+ protocol = ' Negotiate' if negotiate else ' Kerberos' ))
747+
748+ cdef _auth_gss_get_spn(self ):
726749 service_name = self .con_params.krbsrvname or ' postgres'
727750 # find the canonical name of the server host
728751 if isinstance (self .address, str ):
729- raise RuntimeError (' GSSAPI authentication is only supported for '
730- ' TCP/IP connections' )
752+ raise apg_exc.InternalClientError(
753+ ' GSSAPI/SSPI authentication is only supported for TCP/IP '
754+ ' connections' )
731755
732756 host = self .address[0 ]
733757 host_cname = socket.gethostbyname_ex(host)[0 ]
734- gss_name = gssapi.Name(f' {service_name}/{host_cname}' )
735- self .gss_ctx = gssapi.SecurityContext(name = gss_name, usage = ' initiate' )
758+ return f' {service_name}/{host_cname}'
736759
737760 cdef _auth_gss_step(self , bytes server_response):
738761 cdef:
0 commit comments