@@ -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}' 
652- 
653658 else :
654659 self .result_type =  RESULT_FAILED
655660 self .result =  apg_exc.InterfaceError(
656661 ' unsupported authentication method requested by the ' 
657-  ' server: {}' 
662+  ' server: {!r }' 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 (
726+  raise  apg_exc.InterfaceError (
722727 ' gssapi module not found; please install asyncpg[gssapi] to ' 
723-  ' use asyncpg with Kerberos or  GSSAPI authentication' 
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[gssapi] 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