{
  $Project$
  $Workfile$
  $Revision$
  $DateUTC$
  $Id$

  This file is part of the Indy (Internet Direct) project, and is offered
  under the dual-licensing agreement described on the Indy website.
  (http://www.indyproject.org/)

  Copyright:
   (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
}
{
  $Log$
}
{
    Rev 1.3    6/11/2004 9:33:58 AM  DSiders
  Added "Do not Localize" comments.

  Rev 1.2    13.1.2004 ã. 17:26:06  DBondzhev
  Added Domain property

  Rev 1.1    4/12/2003 10:24:04 PM  GGrieve
  Fix to Compile

  Rev 1.0    11/14/2002 02:13:50 PM  JPMugaas
}

unit IdAuthenticationSSPI;

{
  Implementation of the NTLM authentication with SSPI
  Author: Alex Brainman
  Copyright: (c) Chad Z. Hower and The Winshoes Working Group.
}

{$DEFINE SET_ENCRYPT_IN_FT_WITH_GETPROCADDRESS_FUDGE}

interface

{$i IdCompilerDefines.inc}

uses
  IdGlobal,
  IdAuthentication,
  IdCoder,
  Windows,
  SysUtils,
  IdSSPI;

const
  SEC_E_OK                         = 0;
  {$EXTERNALSYM SEC_E_OK}
  SEC_E_INSUFFICIENT_MEMORY        = HRESULT($80090300);
  {$EXTERNALSYM SEC_E_INSUFFICIENT_MEMORY}
  SEC_E_INVALID_HANDLE             = HRESULT($80090301);
  {$EXTERNALSYM SEC_E_INVALID_HANDLE}
  SEC_E_UNSUPPORTED_FUNCTION       = HRESULT($80090302);
  {$EXTERNALSYM SEC_E_UNSUPPORTED_FUNCTION}
  SEC_E_TARGET_UNKNOWN             = HRESULT($80090303);
  {$EXTERNALSYM SEC_E_TARGET_UNKNOWN}
  SEC_E_INTERNAL_ERROR             = HRESULT($80090304);
  {$EXTERNALSYM SEC_E_INTERNAL_ERROR}
  SEC_E_SECPKG_NOT_FOUND           = HRESULT($80090305);
  {$EXTERNALSYM SEC_E_SECPKG_NOT_FOUND}
  SEC_E_NOT_OWNER                  = HRESULT($80090306);
  {$EXTERNALSYM SEC_E_NOT_OWNER}
  SEC_E_CANNOT_INSTALL             = HRESULT($80090307);
  {$EXTERNALSYM SEC_E_CANNOT_INSTALL}
  SEC_E_INVALID_TOKEN              = HRESULT($80090308);
  {$EXTERNALSYM SEC_E_INVALID_TOKEN}
  SEC_E_CANNOT_PACK                = HRESULT($80090309);
  {$EXTERNALSYM SEC_E_CANNOT_PACK}
  SEC_E_QOP_NOT_SUPPORTED          = HRESULT($8009030A);
  {$EXTERNALSYM SEC_E_QOP_NOT_SUPPORTED}
  SEC_E_NO_IMPERSONATION           = HRESULT($8009030B);
  {$EXTERNALSYM SEC_E_NO_IMPERSONATION}
  SEC_E_LOGON_DENIED               = HRESULT($8009030C);
  {$EXTERNALSYM SEC_E_LOGON_DENIED}
  SEC_E_UNKNOWN_CREDENTIALS        = HRESULT($8009030D);
  {$EXTERNALSYM SEC_E_UNKNOWN_CREDENTIALS}
  SEC_E_NO_CREDENTIALS             = HRESULT($8009030E);
  {$EXTERNALSYM SEC_E_NO_CREDENTIALS}
  SEC_E_MESSAGE_ALTERED            = HRESULT($8009030F);
  {$EXTERNALSYM SEC_E_MESSAGE_ALTERED}
  SEC_E_OUT_OF_SEQUENCE            = HRESULT($80090310);
  {$EXTERNALSYM SEC_E_OUT_OF_SEQUENCE}
  SEC_E_NO_AUTHENTICATING_AUTHORITY = HRESULT($80090311);
  {$EXTERNALSYM SEC_E_NO_AUTHENTICATING_AUTHORITY}
  SEC_I_CONTINUE_NEEDED            = HRESULT($00090312);
  {$EXTERNALSYM SEC_I_CONTINUE_NEEDED}
  SEC_I_COMPLETE_NEEDED            = HRESULT($00090313);
  {$EXTERNALSYM SEC_I_COMPLETE_NEEDED}
  SEC_I_COMPLETE_AND_CONTINUE      = HRESULT($00090314);
  {$EXTERNALSYM SEC_I_COMPLETE_AND_CONTINUE}
  SEC_I_LOCAL_LOGON                = HRESULT($00090315);
  {$EXTERNALSYM SEC_I_LOCAL_LOGON}
  SEC_E_BAD_PKGID                  = HRESULT($80090316);
  {$EXTERNALSYM SEC_E_BAD_PKGID}
  SEC_E_CONTEXT_EXPIRED            = HRESULT($80090317);
  {$EXTERNALSYM SEC_E_CONTEXT_EXPIRED}
  SEC_E_INCOMPLETE_MESSAGE         = HRESULT($80090318);
  {$EXTERNALSYM SEC_E_INCOMPLETE_MESSAGE}
  SEC_E_INCOMPLETE_CREDENTIALS     = HRESULT($80090320);
  {$EXTERNALSYM SEC_E_INCOMPLETE_CREDENTIALS}
  SEC_E_BUFFER_TOO_SMALL           = HRESULT($80090321);
  {$EXTERNALSYM SEC_E_BUFFER_TOO_SMALL}
  SEC_I_INCOMPLETE_CREDENTIALS     = HRESULT($00090320);
  {$EXTERNALSYM SEC_I_INCOMPLETE_CREDENTIALS}
  SEC_I_RENEGOTIATE                = HRESULT($00090321);
  {$EXTERNALSYM SEC_I_RENEGOTIATE}
  SEC_E_WRONG_PRINCIPAL            = HRESULT($80090322);
  {$EXTERNALSYM SEC_E_WRONG_PRINCIPAL}
  SEC_I_NO_LSA_CONTEXT             = HRESULT($00090323);
  {$EXTERNALSYM SEC_I_NO_LSA_CONTEXT}
  SEC_E_TIME_SKEW                  = HRESULT($80090324);
  {$EXTERNALSYM SEC_E_TIME_SKEW}
  SEC_E_UNTRUSTED_ROOT             = HRESULT($80090325);
  {$EXTERNALSYM SEC_E_UNTRUSTED_ROOT}
  SEC_E_ILLEGAL_MESSAGE            = HRESULT($80090326);
  {$EXTERNALSYM SEC_E_ILLEGAL_MESSAGE}
  SEC_E_CERT_UNKNOWN               = HRESULT($80090327);
  {$EXTERNALSYM SEC_E_CERT_UNKNOWN}
  SEC_E_CERT_EXPIRED               = HRESULT($80090328);
  {$EXTERNALSYM SEC_E_CERT_EXPIRED}
  SEC_E_ENCRYPT_FAILURE            = HRESULT($80090329);
  {$EXTERNALSYM SEC_E_ENCRYPT_FAILURE}
  SEC_E_DECRYPT_FAILURE            = HRESULT($80090330);
  {$EXTERNALSYM SEC_E_DECRYPT_FAILURE}
  SEC_E_ALGORITHM_MISMATCH         = HRESULT($80090331);
  {$EXTERNALSYM SEC_E_ALGORITHM_MISMATCH}
  SEC_E_SECURITY_QOS_FAILED        = HRESULT($80090332);
  {$EXTERNALSYM SEC_E_SECURITY_QOS_FAILED}
 
  SEC_E_UNFINISHED_CONTEXT_DELETED = HRESULT($80090333);
  {$EXTERNALSYM SEC_E_UNFINISHED_CONTEXT_DELETED}
  SEC_E_NO_TGT_REPLY               = HRESULT($80090334);
  {$EXTERNALSYM SEC_E_NO_TGT_REPLY}
  SEC_E_NO_IP_ADDRESSES            = HRESULT($80090335);
  {$EXTERNALSYM SEC_E_NO_IP_ADDRESSES}
  SEC_E_WRONG_CREDENTIAL_HANDLE    = HRESULT($80090336);
  {$EXTERNALSYM SEC_E_WRONG_CREDENTIAL_HANDLE}
  SEC_E_CRYPTO_SYSTEM_INVALID      = HRESULT($80090337);
  {$EXTERNALSYM SEC_E_CRYPTO_SYSTEM_INVALID}
  SEC_E_MAX_REFERRALS_EXCEEDED     = HRESULT($80090338);
  {$EXTERNALSYM SEC_E_MAX_REFERRALS_EXCEEDED}
  SEC_E_MUST_BE_KDC                = HRESULT($80090339);
  {$EXTERNALSYM SEC_E_MUST_BE_KDC}
  SEC_E_STRONG_CRYPTO_NOT_SUPPORTED = HRESULT($8009033A);
  {$EXTERNALSYM SEC_E_STRONG_CRYPTO_NOT_SUPPORTED}
  SEC_E_TOO_MANY_PRINCIPALS        = HRESULT($8009033B);
  {$EXTERNALSYM SEC_E_TOO_MANY_PRINCIPALS}
  SEC_E_NO_PA_DATA                 = HRESULT($8009033C);
  {$EXTERNALSYM SEC_E_NO_PA_DATA}
  SEC_E_PKINIT_NAME_MISMATCH       = HRESULT($8009033D);
  {$EXTERNALSYM SEC_E_PKINIT_NAME_MISMATCH}
  SEC_E_SMARTCARD_LOGON_REQUIRED   = HRESULT($8009033E);
  {$EXTERNALSYM SEC_E_SMARTCARD_LOGON_REQUIRED}
  SEC_E_SHUTDOWN_IN_PROGRESS       = HRESULT($8009033F);
  {$EXTERNALSYM SEC_E_SHUTDOWN_IN_PROGRESS}
  SEC_E_KDC_INVALID_REQUEST        = HRESULT($80090340);
  {$EXTERNALSYM SEC_E_KDC_INVALID_REQUEST}
  SEC_E_KDC_UNABLE_TO_REFER        = HRESULT($80090341);
  {$EXTERNALSYM SEC_E_KDC_UNABLE_TO_REFER}
  SEC_E_KDC_UNKNOWN_ETYPE          = HRESULT($80090342);
  {$EXTERNALSYM SEC_E_KDC_UNKNOWN_ETYPE}
  SEC_E_UNSUPPORTED_PREAUTH        = HRESULT($80090343);
  {$EXTERNALSYM SEC_E_UNSUPPORTED_PREAUTH}
  SEC_E_DELEGATION_REQUIRED        = HRESULT($80090345);
  {$EXTERNALSYM SEC_E_DELEGATION_REQUIRED}
  SEC_E_BAD_BINDINGS               = HRESULT($80090346);
  {$EXTERNALSYM SEC_E_BAD_BINDINGS}
  SEC_E_MULTIPLE_ACCOUNTS          = HRESULT($80090347);
  {$EXTERNALSYM SEC_E_MULTIPLE_ACCOUNTS}
  SEC_E_NO_KERB_KEY                = HRESULT($80090348);
  {$EXTERNALSYM SEC_E_NO_KERB_KEY}
  SEC_E_CERT_WRONG_USAGE           = HRESULT($80090349);
  {$EXTERNALSYM SEC_E_CERT_WRONG_USAGE}
  SEC_E_DOWNGRADE_DETECTED         = HRESULT($80090350);
  {$EXTERNALSYM SEC_E_DOWNGRADE_DETECTED}
  SEC_E_SMARTCARD_CERT_REVOKED     = HRESULT($80090351);
  {$EXTERNALSYM SEC_E_SMARTCARD_CERT_REVOKED}
  SEC_E_ISSUING_CA_UNTRUSTED       = HRESULT($80090352);
  {$EXTERNALSYM SEC_E_ISSUING_CA_UNTRUSTED}
  SEC_E_REVOCATION_OFFLINE_C       = HRESULT($80090353);
  {$EXTERNALSYM SEC_E_REVOCATION_OFFLINE_C}
  SEC_E_PKINIT_CLIENT_FAILURE      = HRESULT($80090354);
  {$EXTERNALSYM SEC_E_PKINIT_CLIENT_FAILURE}
  SEC_E_SMARTCARD_CERT_EXPIRED     = HRESULT($80090355);
  {$EXTERNALSYM SEC_E_SMARTCARD_CERT_EXPIRED}
  SEC_E_NO_S4U_PROT_SUPPORT        = HRESULT($80090356);
  {$EXTERNALSYM SEC_E_NO_S4U_PROT_SUPPORT}
  SEC_E_CROSSREALM_DELEGATION_FAILURE = HRESULT($80090357);
  {$EXTERNALSYM SEC_E_CROSSREALM_DELEGATION_FAILURE}
  SEC_E_REVOCATION_OFFLINE_KDC     = HRESULT($80090358);
  {$EXTERNALSYM SEC_E_REVOCATION_OFFLINE_KDC}
  SEC_E_ISSUING_CA_UNTRUSTED_KDC   = HRESULT($80090359);
  {$EXTERNALSYM SEC_E_ISSUING_CA_UNTRUSTED_KDC}
  SEC_E_KDC_CERT_EXPIRED           = HRESULT($8009035A);
  {$EXTERNALSYM SEC_E_KDC_CERT_EXPIRED}
  SEC_E_KDC_CERT_REVOKED           = HRESULT($8009035B);
  {$EXTERNALSYM SEC_E_KDC_CERT_REVOKED}
  SEC_I_SIGNATURE_NEEDED           = HRESULT($0009035C);
  {$EXTERNALSYM SEC_I_SIGNATURE_NEEDED}
  SEC_E_INVALID_PARAMETER          = HRESULT($8009035D);
  {$EXTERNALSYM SEC_E_INVALID_PARAMETER}
  SEC_E_DELEGATION_POLICY          = HRESULT($8009035E);
  {$EXTERNALSYM SEC_E_DELEGATION_POLICY}
  SEC_E_POLICY_NLTM_ONLY           = HRESULT($8009035F);
  {$EXTERNALSYM SEC_E_POLICY_NLTM_ONLY}
  SEC_I_NO_RENEGOTIATION           = HRESULT($00090360);
  {$EXTERNALSYM SEC_I_NO_RENEGOTIATION}
  SEC_E_NO_CONTEXT                 = HRESULT($80090361);
  {$EXTERNALSYM SEC_E_NO_CONTEXT}
  SEC_E_PKU2U_CERT_FAILURE         = HRESULT($80090362);
  {$EXTERNALSYM SEC_E_PKU2U_CERT_FAILURE}
  SEC_E_MUTUAL_AUTH_FAILED         = HRESULT($80090363);
  {$EXTERNALSYM SEC_E_MUTUAL_AUTH_FAILED}

type
  ESSPIException = class(Exception)
  public
    // Params must be in this order to avoid conflict with CreateHelp
    // constructor in CBuilder as CB does not differentiate constructors
    // by name as Delphi does
    constructor CreateError(const AErrorNo: Integer; const AFailedFuncName: string);
    //
    class function GetErrorMessageByNo(AErrorNo: UInt32): string;
  end;

  ESSPIInterfaceInitFailed = class(ESSPIException);

  { TSSPIInterface }

  TSSPIInterface = class(TObject)
  private
    fLoadPending, fIsAvailable: Boolean;
    fPFunctionTable: PSecurityFunctionTable;
    fDLLHandle: THandle;
    procedure ReleaseFunctionTable;
    procedure CheckAvailable;
    function GetFunctionTable: SecurityFunctionTable;
  public
    class procedure RaiseIfError(aStatus: SECURITY_STATUS; const aFunctionName: string);
    function IsAvailable: Boolean;
    property FunctionTable: SecurityFunctionTable read GetFunctionTable;
  public
    constructor Create;
    destructor Destroy; override;
  end;

  { TSSPIPackages }

  TSSPIPackage = class(TObject)
  private
    fPSecPkginfo: PSecPkgInfo;
    function GetPSecPkgInfo: PSecPkgInfo;
    function GetMaxToken: ULONG;
    function GetName: {$IFDEF SSPI_UNICODE}TIdUnicodeString{$ELSE}AnsiString{$ENDIF};
  public
    property MaxToken: ULONG read GetMaxToken;
    property Name: {$IFDEF SSPI_UNICODE}TIdUnicodeString{$ELSE}AnsiString{$ENDIF} read GetName;
  public
    constructor Create(aPSecPkginfo: PSecPkgInfo);
  end;

  TCustomSSPIPackage = class(TSSPIPackage)
  private
    fInfo: PSecPkgInfo;
  public
    constructor Create(const aPkgName: {$IFDEF SSPI_UNICODE}TIdUnicodeString{$ELSE}AnsiString{$ENDIF});
    destructor Destroy; override;
  end;

  TSSPINTLMPackage = class(TCustomSSPIPackage)
  public
    constructor Create;
  end;

  { TSSPICredentials }

  TSSPICredentialsUse = (scuInBound, scuOutBound, scuBoth);

  TSSPICredentials = class(TObject)
  private
    fPackage: TSSPIPackage;
    fHandle: CredHandle;
    fUse: TSSPICredentialsUse;
    fAcquired: Boolean;
    fExpiry: TimeStamp;
    function GetHandle: PCredHandle;
    procedure SetUse(aValue: TSSPICredentialsUse);
  protected
    procedure CheckAcquired;
    procedure CheckNotAcquired;
    procedure DoAcquire(pszPrincipal: {$IFDEF SSPI_UNICODE}PSEC_WCHAR{$ELSE}PSEC_CHAR{$ENDIF}; pvLogonId, pAuthData: PVOID);
    procedure DoRelease; virtual;
  public
    procedure Release;
    property Package: TSSPIPackage read fPackage;
    property Handle: PCredHandle read GetHandle;
    property Use: TSSPICredentialsUse read fUse write SetUse;
    property Acquired: Boolean read fAcquired;
  public
    constructor Create(aPackage: TSSPIPackage);
    destructor Destroy; override;
  end;

  { TSSPIWinNTCredentials }

  TSSPIWinNTCredentials = class(TSSPICredentials)
  protected
  public
    procedure Acquire(aUse: TSSPICredentialsUse); overload;
    procedure Acquire(aUse: TSSPICredentialsUse;
      const aDomain, aUserName, aPassword: {$IFDEF SSPI_UNICODE}TIdUnicodeString{$ELSE}AnsiString{$ENDIF}); overload;
  end;

  { TSSPIContext }

  TSSPIContext = class(TObject)
  private
    fCredentials: TSSPICredentials;
    fHandle: CtxtHandle;
    fHasHandle: Boolean;
    fExpiry: TimeStamp;
    function GetHandle: PCtxtHandle;
    function GetExpiry: TimeStamp;
    procedure UpdateHasContextAndCheckForError(
      const aFuncResult: SECURITY_STATUS; const aFuncName: string;
      const aErrorsToIgnore: array of SECURITY_STATUS);
  protected
    procedure CheckHasHandle;
    procedure CheckCredentials;
    function DoInitialize(const aTokenSourceName: {$IFDEF SSPI_UNICODE}TIdUnicodeString{$ELSE}AnsiString{$ENDIF};
      var aIn, aOut: SecBufferDesc;
      const errorsToIgnore: array of SECURITY_STATUS): SECURITY_STATUS;
    procedure DoRelease; virtual;
    function GetRequestedFlags: ULONG; virtual; abstract;
    procedure SetEstablishedFlags(aFlags: ULONG); virtual; abstract;
    function GetAuthenticated: Boolean; virtual; abstract;
    property HasHandle: Boolean read fHasHandle;
  public
    procedure Release;
    property Credentials: TSSPICredentials read fCredentials;
    property Handle: PCtxtHandle read GetHandle;
    property Authenticated: Boolean read GetAuthenticated;
    property Expiry: TimeStamp read GetExpiry;
  public
    constructor Create(aCredentials: TSSPICredentials);
    destructor Destroy; override;
  end;

  { TSSPIConnectionContext }

  TCustomSSPIConnectionContext = class(TSSPIContext)
  private
    fStatus: SECURITY_STATUS;
    fOutBuffDesc, fInBuffDesc: SecBufferDesc;
    fInBuff: SecBuffer;
  protected
    procedure DoRelease; override;
    function GetAuthenticated: Boolean; override;
    function DoUpdateAndGenerateReply(var aIn, aOut: SecBufferDesc;
      const aErrorsToIgnore: array of SECURITY_STATUS
      ): SECURITY_STATUS; virtual; abstract;
  public
    constructor Create(ACredentials: TSSPICredentials);
    function UpdateAndGenerateReply(
      const aFromPeerToken: TIdBytes; var aToPeerToken: TIdBytes): Boolean;
  end;

  TSSPIClientConnectionContext = class(TCustomSSPIConnectionContext)
  private
    fTargetName: string;
    fReqReguested, fReqEstablished: ULONG;
  protected
    function GetRequestedFlags: ULONG; override;
    procedure SetEstablishedFlags(aFlags: ULONG); override;
    function DoUpdateAndGenerateReply(var aIn, aOut: SecBufferDesc;
      const aErrorsToIgnore: array of SECURITY_STATUS
      ): SECURITY_STATUS; override;
  public
    function GenerateInitialChallenge(const aTargetName: string;
      var aToPeerToken: TIdBytes): Boolean;
  public
    constructor Create(aCredentials: TSSPICredentials);
  end;

  TIndySSPINTLMClient = class(TObject)
  protected
    fNTLMPackage: TSSPINTLMPackage;
    fCredentials: TSSPIWinNTCredentials;
    fContext: TSSPIClientConnectionContext;
  public
    procedure SetCredentials(const aDomain, aUserName, aPassword: string);
    procedure SetCredentialsAsCurrentUser;
    function InitAndBuildType1Message: TIdBytes;
    function UpdateAndBuildType3Message(const aServerType2Message: TIdBytes): TIdBytes;
  public
    constructor Create;
    destructor Destroy; override;
  end;

  TIdSSPINTLMAuthentication = class(TIdAuthentication)
  protected
    FNTLMInfo: string;
    FSSPIClient: TIndySSPINTLMClient;
    procedure SetDomain(const Value: String);
    function GetDomain: String;
    procedure SetUserName(const Value: String); override;
    function GetSteps: Integer; override;
    function DoNext: TIdAuthWhatsNext; override;
  public
    constructor Create; override;
    destructor Destroy; override;
    function Authentication: string; override;
    function KeepAlive: Boolean; override;
    property Domain: String read GetDomain write SetDomain;
  end;

  // RLebeau 4/17/10: this forces C++Builder to link to this unit so
  // RegisterAuthenticationMethod can be called correctly at program startup...

  {$IFDEF HAS_DIRECTIVE_HPPEMIT_LINKUNIT}
    {$HPPEMIT LINKUNIT}
  {$ELSE}
    {$HPPEMIT '#pragma link "IdAuthenticationSSPI"'}
  {$ENDIF}

implementation

uses
  IdGlobalCore,
  IdGlobalProtocols,
  IdException,
  IdCoderMIME,
  IdResourceStringsSSPI,
  IdHeaderList;

var
  gSSPIInterface: TSSPIInterface = nil;
  gAuthRegistered: Boolean = False;

{ ESSPIException }

class function ESSPIException.GetErrorMessageByNo(aErrorNo: UInt32): string;
begin
  case HRESULT(aErrorNo) of
    SEC_E_OK:
      Result := RSHTTPSSPISuccess;
    SEC_E_INSUFFICIENT_MEMORY:
      Result := RSHTTPSSPINotEnoughMem;
    SEC_E_INVALID_HANDLE:
      Result := RSHTTPSSPIInvalidHandle;
    SEC_E_UNSUPPORTED_FUNCTION:
      Result := RSHTTPSSPIFuncNotSupported;
    SEC_E_TARGET_UNKNOWN:
      Result := RSHTTPSSPIUnknownTarget;
    SEC_E_INTERNAL_ERROR:
      Result := RSHTTPSSPIInternalError;
    SEC_E_SECPKG_NOT_FOUND:
      Result :=  RSHTTPSSPISecPackageNotFound;
    SEC_E_NOT_OWNER:
      Result := RSHTTPSSPINotOwner;
    SEC_E_CANNOT_INSTALL:
      Result := RSHTTPSSPIPackageCannotBeInstalled;
    SEC_E_INVALID_TOKEN:
      Result := RSHTTPSSPIInvalidToken;
    SEC_E_CANNOT_PACK:
      Result := RSHTTPSSPICannotPack;
    SEC_E_QOP_NOT_SUPPORTED:
      Result := RSHTTPSSPIQOPNotSupported;
    SEC_E_NO_IMPERSONATION:
      Result := RSHTTPSSPINoImpersonation;
    SEC_E_LOGON_DENIED:
      Result := RSHTTPSSPILoginDenied;
    SEC_E_UNKNOWN_CREDENTIALS:
      Result := RSHTTPSSPIUnknownCredentials;
    SEC_E_NO_CREDENTIALS:
      Result := RSHTTPSSPINoCredentials;
    SEC_E_MESSAGE_ALTERED:
      Result := RSHTTPSSPIMessageAltered;
    SEC_E_OUT_OF_SEQUENCE:
      Result := RSHTTPSSPIOutOfSequence;
    SEC_E_NO_AUTHENTICATING_AUTHORITY:
      Result := RSHTTPSSPINoAuthAuthority;
    SEC_I_CONTINUE_NEEDED:
      Result := RSHTTPSSPIContinueNeeded;
    SEC_I_COMPLETE_NEEDED:
      Result := RSHTTPSSPICompleteNeeded;
    SEC_I_COMPLETE_AND_CONTINUE:
      Result :=RSHTTPSSPICompleteContinueNeeded;
    SEC_I_LOCAL_LOGON:
      Result := RSHTTPSSPILocalLogin;
    SEC_E_BAD_PKGID:
      Result := RSHTTPSSPIBadPackageID;
    SEC_E_CONTEXT_EXPIRED:
      Result := RSHTTPSSPIContextExpired;
    SEC_E_INCOMPLETE_MESSAGE:
      Result := RSHTTPSSPIIncompleteMessage;
    SEC_E_INCOMPLETE_CREDENTIALS:
      Result := RSHTTPSSPIIncompleteCredentialNotInit;
    SEC_E_BUFFER_TOO_SMALL:
      Result := RSHTTPSSPIBufferTooSmall;
    SEC_I_INCOMPLETE_CREDENTIALS:
      Result := RSHTTPSSPIIncompleteCredentialsInit;
    SEC_I_RENEGOTIATE:
      Result := RSHTTPSSPIRengotiate;
    SEC_E_WRONG_PRINCIPAL:
      Result := RSHTTPSSPIWrongPrincipal;
    SEC_I_NO_LSA_CONTEXT:
      Result := RSHTTPSSPINoLSACode;
    SEC_E_TIME_SKEW:
      Result := RSHTTPSSPITimeScew;
    SEC_E_UNTRUSTED_ROOT:
      Result := RSHTTPSSPIUntrustedRoot;
    SEC_E_ILLEGAL_MESSAGE:
      Result := RSHTTPSSPIIllegalMessage;
    SEC_E_CERT_UNKNOWN:
      Result := RSHTTPSSPICertUnknown;
    SEC_E_CERT_EXPIRED:
      Result := RSHTTPSSPICertExpired;
    SEC_E_ENCRYPT_FAILURE:
      Result := RSHTTPSSPIEncryptionFailure;
    SEC_E_DECRYPT_FAILURE:
      Result := RSHTTPSSPIDecryptionFailure;
    SEC_E_ALGORITHM_MISMATCH:
      Result := RSHTTPSSPIAlgorithmMismatch;
    SEC_E_SECURITY_QOS_FAILED:
      Result := RSHTTPSSPISecurityQOSFailure;
    SEC_E_UNFINISHED_CONTEXT_DELETED :
      Result := RSHTTPSSPISecCtxWasDelBeforeUpdated;
    SEC_E_NO_TGT_REPLY :
      Result := RSHTTPSSPIClientNoTGTReply;
    SEC_E_NO_IP_ADDRESSES :
      Result := RSHTTPSSPILocalNoIPAddr;
    SEC_E_WRONG_CREDENTIAL_HANDLE :
      Result := RSHTTPSSPIWrongCredHandle;
    SEC_E_CRYPTO_SYSTEM_INVALID :
      Result := RSHTTPSSPICryptoSysInvalid;
    SEC_E_MAX_REFERRALS_EXCEEDED :
      Result := RSHTTPSSPIMaxTicketRef;
    SEC_E_MUST_BE_KDC :
      Result := RSHTTPSSPIMustBeKDC;
    SEC_E_STRONG_CRYPTO_NOT_SUPPORTED :
      Result := RSHTTPSSPIStrongCryptoNotSupported;
    SEC_E_TOO_MANY_PRINCIPALS :
      Result := RSHTTPSSPIKDCReplyTooManyPrincipals;
    SEC_E_NO_PA_DATA :
      Result := RSHTTPSSPINoPAData;
    SEC_E_PKINIT_NAME_MISMATCH :
      Result := RSHTTPSSPIPKInitNameMismatch;
    SEC_E_SMARTCARD_LOGON_REQUIRED :
      Result := RSHTTPSSPISmartcardLogonReq;
    SEC_E_SHUTDOWN_IN_PROGRESS :
      Result := RSHTTPSSPISysShutdownInProg;
    SEC_E_KDC_INVALID_REQUEST :
      Result := RSHTTPSSPIKDCInvalidRequest;
    SEC_E_KDC_UNABLE_TO_REFER :
      Result := RSHTTPSSPIKDCUnableToRefer;
    SEC_E_KDC_UNKNOWN_ETYPE :
      Result := RSHTTPSSPIKDCETypeUnknown;
    SEC_E_UNSUPPORTED_PREAUTH :
      Result := RSHTTPSSPIUnsupPreauth;
    SEC_E_DELEGATION_REQUIRED :
      Result := RSHTTPSSPIDeligationReq;
    SEC_E_BAD_BINDINGS :
      Result := RSHTTPSSPIBadBindings;
    SEC_E_MULTIPLE_ACCOUNTS :
      Result := RSHTTPSSPIMultipleAccounts;
    SEC_E_NO_KERB_KEY :
      Result := RSHTTPSSPINoKerbKey;
    SEC_E_CERT_WRONG_USAGE :
      Result := RSHTTPSSPICertWrongUsage;
    SEC_E_DOWNGRADE_DETECTED :
      Result := RSHTTPSSPIDowngradeDetected;
    SEC_E_SMARTCARD_CERT_REVOKED :
      Result := RSHTTPSSPISmartcardCertRevoked;
    SEC_E_ISSUING_CA_UNTRUSTED :
      Result := RSHTTPSSPIIssuingCAUntrusted;
    SEC_E_REVOCATION_OFFLINE_C :
      Result := RSHTTPSSPIRevocationOffline;
    SEC_E_PKINIT_CLIENT_FAILURE :
      Result := RSHTTPSSPIPKInitClientFailure;
    SEC_E_SMARTCARD_CERT_EXPIRED :
      Result := RSHTTPSSPISmartcardExpired;
    SEC_E_NO_S4U_PROT_SUPPORT :
      Result := RSHTTPSSPINoS4UProtSupport;
    SEC_E_CROSSREALM_DELEGATION_FAILURE :
      Result := RSHTTPSSPICrossRealmDeligationFailure;
    SEC_E_REVOCATION_OFFLINE_KDC :
      Result := RSHTTPSSPIRevocationOfflineKDC;
    SEC_E_ISSUING_CA_UNTRUSTED_KDC :
      Result := RSHTTPSSPICAUntrustedKDC;
    SEC_E_KDC_CERT_EXPIRED :
      Result := RSHTTPSSPIKDCCertExpired;
    SEC_E_KDC_CERT_REVOKED :
      Result := RSHTTPSSPIKDCCertRevoked;
    SEC_I_SIGNATURE_NEEDED :
      Result := RSHTTPSSPISignatureNeeded;
    SEC_E_INVALID_PARAMETER :
      Result := RSHTTPSSPIInvalidParameter;
    SEC_E_DELEGATION_POLICY :
      Result := RSHTTPSSPIDeligationPolicy;
    SEC_E_POLICY_NLTM_ONLY :
      Result := RSHTTPSSPIPolicyNTLMOnly;
    SEC_I_NO_RENEGOTIATION :
      Result := RSHTTPSSPINoRenegotiation;
    SEC_E_NO_CONTEXT :
      Result := RSHTTPSSPINoContext;
    SEC_E_PKU2U_CERT_FAILURE :
      Result := RSHTTPSSPIPKU2UCertFailure;
    SEC_E_MUTUAL_AUTH_FAILED :
      Result := RSHTTPSSPIMutualAuthFailed;
  else
    Result := RSHTTPSSPIUnknwonError;
  end;
end;

constructor ESSPIException.CreateError(const AErrorNo: Integer; const AFailedFuncName: string);
begin
  if AErrorNo = SEC_E_OK then begin
    inherited Create(AFailedFuncName);
  end else begin
    inherited CreateFmt(RSHTTPSSPIErrorMsg,
      [AFailedFuncName, AErrorNo, AErrorNo, GetErrorMessageByNo(AErrorNo)]);
  end;
end;

{ TSSPIInterface }

procedure TSSPIInterface.ReleaseFunctionTable;
begin
  if fPFunctionTable <> nil then begin
    fPFunctionTable := nil;
  end;
end;

procedure TSSPIInterface.CheckAvailable;
begin
  if not IsAvailable then begin
    raise ESSPIInterfaceInitFailed.Create(RSHTTPSSPIInterfaceInitFailed);
  end;
end;

function TSSPIInterface.GetFunctionTable: SecurityFunctionTable;
begin
  CheckAvailable;
  Result := fPFunctionTable^;
end;

class procedure TSSPIInterface.RaiseIfError(aStatus: SECURITY_STATUS;
  const aFunctionName: string);
begin
  if not SEC_SUCCESS(aStatus) then begin
    raise ESSPIException.CreateError(aStatus, aFunctionName);
  end;
end;

function TSSPIInterface.IsAvailable: Boolean;

  procedure LoadDLL;
  const
    SECURITY_DLL_NT = 'security.dll';    {Do not translate}
    SECURITY_DLL_95 = 'secur32.dll';   {Do not translate}
    ENCRYPT_MESSAGE = 'EncryptMessage';   {Do not translate}
    DECRYPT_MESSAGE = 'DecryptMessage';    {Do not translate}
  var
    dllName: string;
    entrypoint: INIT_SECURITY_INTERFACE;
  begin
    fIsAvailable := False;
    if IndyWindowsPlatform = VER_PLATFORM_WIN32_WINDOWS then
      { Windows95 SSPI dll }
      dllName := SECURITY_DLL_95
    else
      { WindowsNT & Windows2000 SSPI dll }
      dllName := SECURITY_DLL_NT;
    { load SSPI dll }
    //In Windows, you should use SafeLoadLibrary instead of the LoadLibrary API
    //call because LoadLibrary messes with the FPU control word.
    fDLLHandle := SafeLoadLibrary(dllName);
    if fDLLHandle > 0 then begin
      { get InitSecurityInterface entry point
        and call it to fetch SPPI function table}
      entrypoint := GetProcAddress(fDLLHandle, SECURITY_ENTRYPOINT);
      fPFunctionTable := entrypoint();
      { let's see what SSPI functions are available
        and if we can continue on with the set }
      fIsAvailable :=
        Assigned({$IFDEF SSPI_UNICODE}fPFunctionTable^.QuerySecurityPackageInfoW{$ELSE}fPFunctionTable^.QuerySecurityPackageInfoA{$ENDIF}) and
        Assigned(fPFunctionTable^.FreeContextBuffer) and
        Assigned(fPFunctionTable^.DeleteSecurityContext) and
        Assigned(fPFunctionTable^.FreeCredentialsHandle) and
        Assigned({$IFDEF SSPI_UNICODE}fPFunctionTable^.AcquireCredentialsHandleW{$ELSE}fPFunctionTable^.AcquireCredentialsHandleA{$ENDIF}) and
        Assigned({$IFDEF SSPI_UNICODE}fPFunctionTable^.InitializeSecurityContextW{$ELSE}fPFunctionTable^.InitializeSecurityContextA{$ENDIF}) and
        Assigned(fPFunctionTable^.AcceptSecurityContext) and
        Assigned(fPFunctionTable^.ImpersonateSecurityContext) and
        Assigned(fPFunctionTable^.RevertSecurityContext) and
        Assigned({$IFDEF SSPI_UNICODE}fPFunctionTable^.QueryContextAttributesW{$ELSE}fPFunctionTable^.QueryContextAttributesA{$ENDIF}) and
        Assigned(fPFunctionTable^.MakeSignature) and
        Assigned(fPFunctionTable^.VerifySignature);
      {$IFDEF SET_ENCRYPT_IN_FT_WITH_GETPROCADDRESS_FUDGE}
      { fudge for Encrypt/DecryptMessage }
      if not Assigned(fPFunctionTable^.EncryptMessage) then begin
        fPFunctionTable^.EncryptMessage := GetProcAddress(fDLLHandle, ENCRYPT_MESSAGE);
      end;
      if not Assigned(fPFunctionTable^.DecryptMessage) then begin
        fPFunctionTable^.DecryptMessage := GetProcAddress(fDLLHandle, DECRYPT_MESSAGE);
      end;
      {$ENDIF}
    end;
  end;

begin
  if not fIsAvailable then begin
    if fLoadPending then begin
      ReleaseFunctionTable;
      LoadDLL;
      fLoadPending := False;
    end;
  end;
  Result := fIsAvailable;
end;

constructor TSSPIInterface.Create;
begin
  inherited Create;
  fLoadPending := True;
  fIsAvailable := False;
  fPFunctionTable := nil;
end;

destructor TSSPIInterface.Destroy;
begin
  ReleaseFunctionTable;
  FreeLibrary(fDLLHandle);
  inherited Destroy;
end;

{ TSSPIPackage }

constructor TSSPIPackage.Create(aPSecPkginfo: PSecPkgInfo);
begin
  inherited Create;
  fPSecPkginfo := aPSecPkginfo;
end;

function TSSPIPackage.GetPSecPkgInfo: PSecPkgInfo;
begin
  if not Assigned(fPSecPkginfo) then begin
    raise ESSPIException.Create(RSHTTPSSPINoPkgInfoSpecified);
  end;
  Result := fPSecPkginfo;
end;

function TSSPIPackage.GetMaxToken: ULONG;
begin
  Result := GetPSecPkgInfo^.cbMaxToken;
end;

function TSSPIPackage.GetName: {$IFDEF SSPI_UNICODE}TIdUnicodeString{$ELSE}AnsiString{$ENDIF};
begin
  Result := GetPSecPkgInfo^.Name;
end;

{ TCustomSSPIPackage }

constructor TCustomSSPIPackage.Create(const aPkgName: {$IFDEF SSPI_UNICODE}TIdUnicodeString{$ELSE}AnsiString{$ENDIF});
begin
  gSSPIInterface.RaiseIfError(
    {$IFDEF SSPI_UNICODE}
    gSSPIInterface.FunctionTable.QuerySecurityPackageInfoW(PWideChar(aPkgName), @fInfo),
    'QuerySecurityPackageInfoW' {Do not translate}
    {$ELSE}
    gSSPIInterface.FunctionTable.QuerySecurityPackageInfoA(PAnsiChar(aPkgName), @fInfo),
    'QuerySecurityPackageInfoA' {Do not translate}
    {$ENDIF}
    );
  inherited Create(fInfo);
end;

destructor TCustomSSPIPackage.Destroy;
begin
  if fInfo <> nil then begin
    gSSPIInterface.RaiseIfError(
      gSSPIInterface.FunctionTable.FreeContextBuffer(fInfo), 'FreeContextBuffer');  {Do not localize}
  end;
  inherited Destroy;
end;

{ TSSPINTLMPackage }

constructor TSSPINTLMPackage.Create;
begin
  inherited Create(NTLMSP_NAME);
end;

{ TSSPICredentials }

constructor TSSPICredentials.Create(aPackage: TSSPIPackage);
begin
  inherited Create;
  fPackage := aPackage;
  fUse := scuOutBound;
  fAcquired := False;
end;

procedure TSSPICredentials.CheckAcquired;
begin
  if not fAcquired then begin
    raise ESSPIException.Create(RSHTTPSSPINoCredentialHandle);
  end;
end;

procedure TSSPICredentials.CheckNotAcquired;
begin
  if fAcquired then begin
    raise ESSPIException.Create(RSHTTPSSPICanNotChangeCredentials);
  end;
end;

procedure TSSPICredentials.DoAcquire
  (pszPrincipal: {$IFDEF SSPI_UNICODE}PSEC_WCHAR{$ELSE}PSEC_CHAR{$ENDIF}; pvLogonId, pAuthData: PVOID);
var
  cu: ULONG;
begin
  Release;
  case Use of
    scuInBound:
      cu := SECPKG_CRED_INBOUND;
    scuOutBound:
      cu := SECPKG_CRED_OUTBOUND;
    scuBoth:
      cu := SECPKG_CRED_BOTH;
  else
    raise ESSPIException.Create(RSHTTPSSPIUnknwonCredentialUse);
  end;
  gSSPIInterface.RaiseIfError(
    gSSPIInterface.FunctionTable.{$IFDEF SSPI_UNICODE}AcquireCredentialsHandleW{$ELSE}AcquireCredentialsHandleA{$ENDIF}(
    pszPrincipal, {$IFDEF SSPI_UNICODE}PSEC_WCHAR{$ELSE}PSEC_CHAR{$ENDIF}(Package.Name), cu, pvLogonId, pAuthData, nil, nil,
    @fHandle, @fExpiry),
    {$IFDEF SSPI_UNICODE}
    'AcquireCredentialsHandleW' {Do not translater}
    {$ELSE}
    'AcquireCredentialsHandleA' {Do not translater}
    {$ENDIF}
    );
  fAcquired := True;
end;

procedure TSSPICredentials.DoRelease;
begin
  gSSPIInterface.RaiseIfError(
    gSSPIInterface.FunctionTable.FreeCredentialsHandle(@fHandle),
    'FreeCredentialsHandle');      {Do not translate}
  SecInvalidateHandle(fHandle);
end;

procedure TSSPICredentials.Release;
begin
  if fAcquired then begin
    DoRelease;
    fAcquired := False;
  end;
end;

function TSSPICredentials.GetHandle: PCredHandle;
begin
  CheckAcquired;
  Result := @fHandle;
end;

procedure TSSPICredentials.SetUse(aValue: TSSPICredentialsUse);
begin
  if fUse <> aValue then begin
    CheckNotAcquired;
    fUse := aValue;
  end;
end;

destructor TSSPICredentials.Destroy;
begin
  Release;
  inherited Destroy;
end;

{ TSSPIWinNTCredentials }

procedure TSSPIWinNTCredentials.Acquire(aUse: TSSPICredentialsUse);
begin
  Acquire(aUse, '', '', '');          {Do not translate}
end;

procedure TSSPIWinNTCredentials.Acquire(aUse: TSSPICredentialsUse;
  const aDomain, aUserName, aPassword: {$IFDEF SSPI_UNICODE}TIdUnicodeString{$ELSE}AnsiString{$ENDIF});
var
  ai: SEC_WINNT_AUTH_IDENTITY;
  pai: PVOID;
begin
  Use := aUse;
  if (Length(aDomain) > 0) and (Length(aUserName) > 0) then begin
    {$IFDEF SSPI_UNICODE}
    ai.User := PUSHORT(PWideChar(aUserName));
    ai.UserLength := Length(aUserName);
    ai.Domain := PUSHORT(PWideChar(aDomain));
    ai.DomainLength := Length(aDomain);
    ai.Password := PUSHORT(PWideChar(aPassword));
    ai.PasswordLength := Length(aPassword);
    ai.Flags := SEC_WINNT_AUTH_IDENTITY_UNICODE;
    {$ELSE}
    ai.User := PUCHAR(PAnsiChar(aUserName));
    ai.UserLength := Length(aUserName);
    ai.Domain := PUCHAR(PAnsiChar(aDomain));
    ai.DomainLength := Length(aDomain);
    ai.Password := PUCHAR(PAnsiChar(aPassword));
    ai.PasswordLength := Length(aPassword);
    ai.Flags := SEC_WINNT_AUTH_IDENTITY_ANSI;
    {$ENDIF}
    pai := @ai;
  end else
  begin
    pai := nil;
  end;
  DoAcquire(nil, nil, pai);
end;

{ TSSPIContext }

constructor TSSPIContext.Create(aCredentials: TSSPICredentials);
begin
  inherited Create;
  fCredentials := aCredentials;
  fHasHandle := False;
end;

destructor TSSPIContext.Destroy;
begin
  Release;
  inherited Destroy;
end;

procedure TSSPIContext.UpdateHasContextAndCheckForError(
  const aFuncResult: SECURITY_STATUS; const aFuncName: string;
  const aErrorsToIgnore: array of SECURITY_STATUS);
var
  doRaise: Boolean;
  i: Integer;
begin
  doRaise := not SEC_SUCCESS(aFuncResult);
  if doRaise then begin
    for i := Low(aErrorsToIgnore) to High(aErrorsToIgnore) do begin
      if aFuncResult = aErrorsToIgnore[i] then begin
        doRaise := False;
        Break;
      end;
    end;
  end;
  if doRaise then begin
    raise ESSPIException.CreateError(aFuncResult, aFuncName);
  end;
  fHasHandle := True;
end;

function TSSPIContext.DoInitialize(const aTokenSourceName: {$IFDEF SSPI_UNICODE}TIdUnicodeString{$ELSE}AnsiString{$ENDIF};
  var aIn, aOut: SecBufferDesc;
  const errorsToIgnore: array of SECURITY_STATUS): SECURITY_STATUS;
var
  tmp: PCtxtHandle;
  tmp2: PSecBufferDesc;
  r: ULONG;
begin
  if fHasHandle then begin
    tmp := @fHandle;
    tmp2 := @aIn;
  end else begin
    tmp := nil;
    tmp2 := nil;
  end;
  Result :=
    gSSPIInterface.FunctionTable.{$IFDEF SSPI_UNICODE}InitializeSecurityContextW{$ELSE}InitializeSecurityContextA{$ENDIF}(
    Credentials.Handle, tmp,
    {$IFDEF SSPI_UNICODE}PWideChar{$ELSE}PAnsiChar{$ENDIF}(aTokenSourceName),
    GetRequestedFlags, 0, SECURITY_NATIVE_DREP, tmp2, 0,
    @fHandle, @aOut, @r, @fExpiry
    );
  UpdateHasContextAndCheckForError(Result,
    {$IFDEF SSPI_UNICODE}'InitializeSecurityContextW'{$ELSE}'InitializeSecurityContextA'{$ENDIF}, {Do not translate}
    errorsToIgnore);
  SetEstablishedFlags(r);
end;

procedure TSSPIContext.DoRelease;
begin
  gSSPIInterface.RaiseIfError(
    gSSPIInterface.FunctionTable.DeleteSecurityContext(@fHandle), 'DeleteSecurityContext'); {Do not translate}
end;

procedure TSSPIContext.Release;
begin
  if HasHandle then begin
    DoRelease;
    fHasHandle := False;
  end;
end;

procedure TSSPIContext.CheckHasHandle;
begin
  if not HasHandle then begin
    raise ESSPIException.Create(RSHTTPSSPINoCredentialHandle);
  end;
end;

procedure TSSPIContext.CheckCredentials;
begin
  if (not Assigned(Credentials)) or (not Credentials.Acquired) then begin
    raise ESSPIException.Create(RSHTTPSSPIDoAuquireCredentialHandle);
  end;
end;

function TSSPIContext.GetExpiry: TimeStamp;
begin
  CheckHasHandle;
  Result := fExpiry;
end;

function TSSPIContext.GetHandle: PCtxtHandle;
begin
  CheckHasHandle;
  Result := @fHandle;
end;

{ TCustomSSPIConnectionContext }

procedure TCustomSSPIConnectionContext.DoRelease;
begin
  inherited DoRelease;
  fStatus := SEC_E_INVALID_HANDLE; // just to put something other then SEC_E_OK
end;

function TCustomSSPIConnectionContext.GetAuthenticated: Boolean;
begin
  CheckHasHandle;
  Result := fStatus = SEC_E_OK;
end;

function TCustomSSPIConnectionContext.UpdateAndGenerateReply
  (const aFromPeerToken: TIdBytes; var aToPeerToken: TIdBytes): Boolean;
var
  fOutBuff: SecBuffer;
begin
  Result := False;

  { check credentials }
  CheckCredentials;
  { prepare input buffer }

  fInBuff.cbBuffer := Length(aFromPeerToken);

  //Assert(Length(aFromPeerToken)>0);
  if fInBuff.cbBuffer > 0 then begin
    fInBuff.pvBuffer := @aFromPeerToken[0];
  end;

  { prepare output buffer }
  fOutBuff.BufferType := SECBUFFER_TOKEN;
  fOutBuff.cbBuffer := Credentials.Package.MaxToken;
  fOutBuff.pvBuffer := AllocMem(fOutBuff.cbBuffer);

  fOutBuffDesc.ulVersion := SECBUFFER_VERSION;
  fOutBuffDesc.cBuffers := 1;
  fOutBuffDesc.pBuffers := @fOutBuff;

  try
    { do processing }
    fStatus := DoUpdateAndGenerateReply(fInBuffDesc, fOutBuffDesc, []);
    { complete token if applicable }
    case fStatus of
      SEC_I_COMPLETE_NEEDED,
        SEC_I_COMPLETE_AND_CONTINUE:
        begin
          if not Assigned(gSSPIInterface.FunctionTable.CompleteAuthToken) then begin
            raise ESSPIException.Create(RSHTTPSSPICompleteTokenNotSupported);
          end;
          fStatus := gSSPIInterface.FunctionTable.CompleteAuthToken(Handle, @fOutBuffDesc);
          gSSPIInterface.RaiseIfError(fStatus, 'CompleteAuthToken');   {Do not translate}
        end;
    end;
    Result :=
      (fStatus = SEC_I_CONTINUE_NEEDED) or
      (fStatus = SEC_I_COMPLETE_AND_CONTINUE) or
      (fOutBuff.cbBuffer > 0);
    if Result then begin
      aToPeerToken := RawToBytes(fOutBuff.pvBuffer^, fOutBuff.cbBuffer);
    end;
  finally
    FreeMem(fOutBuff.pvBuffer);
  end;
end;

constructor TCustomSSPIConnectionContext.Create(aCredentials: TSSPICredentials);
begin
  inherited Create(aCredentials);

  fInBuff.BufferType := SECBUFFER_TOKEN;

  fInBuffDesc.ulVersion := SECBUFFER_VERSION;
  fInBuffDesc.cBuffers := 1;
  fInBuffDesc.pBuffers := @fInBuff;

  fOutBuffDesc.ulVersion := SECBUFFER_VERSION;
  fOutBuffDesc.cBuffers := 1;
end;

{ TSSPIClientConnectionContext }

constructor TSSPIClientConnectionContext.Create(aCredentials: TSSPICredentials);
begin
  inherited Create(aCredentials);
  fTargetName := '';   {Do not translate}
end;

function TSSPIClientConnectionContext.GetRequestedFlags: ULONG;
begin
  Result := fReqReguested;
end;

procedure TSSPIClientConnectionContext.SetEstablishedFlags(aFlags: ULONG);
begin
  fReqEstablished := aFlags;
end;

function TSSPIClientConnectionContext.DoUpdateAndGenerateReply
  (var aIn, aOut: SecBufferDesc;
  const aErrorsToIgnore: array of SECURITY_STATUS): SECURITY_STATUS;
begin
  Result := DoInitialize(fTargetName, aIn, aOut, []);
end;

function TSSPIClientConnectionContext.GenerateInitialChallenge
  (const aTargetName: string; var aToPeerToken: TIdBytes): Boolean;
begin
  Release;
  fTargetName := aTargetName;
  Result := UpdateAndGenerateReply(nil, aToPeerToken);   {Do not translate}
end;

{ TIndySSPINTLMClient }

constructor TIndySSPINTLMClient.Create;
begin
  inherited Create;
  fNTLMPackage := TSSPINTLMPackage.Create;
  fCredentials := TSSPIWinNTCredentials.Create(fNTLMPackage);
  fContext := TSSPIClientConnectionContext.Create(fCredentials);
end;

destructor TIndySSPINTLMClient.Destroy;
begin
  FreeAndNil(fContext);
  FreeAndNil(fCredentials);
  FreeAndNil(fNTLMPackage);
  inherited Destroy;
end;

procedure TIndySSPINTLMClient.SetCredentials(const aDomain, aUserName, aPassword: string);
begin
  fCredentials.Acquire(scuOutBound, aDomain, aUserName, aPassword);
end;

procedure TIndySSPINTLMClient.SetCredentialsAsCurrentUser;
begin
  fCredentials.Acquire(scuOutBound);
end;

function TIndySSPINTLMClient.InitAndBuildType1Message: TIdBytes;
begin
  fContext.GenerateInitialChallenge('', Result);
end;

function TIndySSPINTLMClient.UpdateAndBuildType3Message(const aServerType2Message: TIdBytes): TIdBytes;
begin
  fContext.UpdateAndGenerateReply(aServerType2Message, Result);
end;

{ TIdSSPINTLMAuthentication }

constructor TIdSSPINTLMAuthentication.Create;
begin
  inherited Create;
  FSSPIClient := TIndySSPINTLMClient.Create;
  Domain := IndyComputerName;
end;

function TIdSSPINTLMAuthentication.DoNext: TIdAuthWhatsNext;
begin
  Result := wnDoRequest;
  case FCurrentStep of
    0:
      begin
        {if (Length(Username) > 0) and (Length(Password) > 0) then
        begin}
        Result := wnDoRequest;
        FCurrentStep := 1;
        {end
        else begin
          result := wnAskTheProgram;
        end;}
      end;
    1:
      begin
        FCurrentStep := 2;
        Result := wnDoRequest;
      end;
    //Authentication does the 2>3 progression
    3:
      begin
        FCurrentStep := 4;
        Result := wnDoRequest;
      end;
    4:
      begin
        FCurrentStep := 0;
        if Username = '' then begin
          Result := wnAskTheProgram;
        end else begin
          Result := wnFail;
          Username := '';
          Password := '';
          Domain := IndyComputerName;
        end;
      end;
  end;
end;

function TIdSSPINTLMAuthentication.Authentication: string;
var
  buf: TIdBytes;
begin
  Result := '';
  buf := nil;
  case FCurrentStep of
    1:
      begin
        if Length(Username) = 0 then begin
          FSSPIClient.SetCredentialsAsCurrentUser;
        end else begin
          FSSPIClient.SetCredentials(Domain, Username, Password);
        end;
        Result := 'NTLM ' + TIdEncoderMIME.EncodeBytes(FSSPIClient.InitAndBuildType1Message);  {Do not translate}
        FNTLMInfo := '';    {Do not translate}
      end;
    2:
      begin
        if Length(FNTLMInfo) = 0 then begin
          FNTLMInfo := ReadAuthInfo('NTLM');  {Do not translate}
          Fetch(FNTLMInfo);
        end;

        if Length(FNTLMInfo) = 0 then begin
          Reset;
          Abort;
        end;

        buf := TIdDecoderMIME.DecodeBytes(FNTLMInfo);
        Result := 'NTLM ' + TIdEncoderMIME.EncodeBytes(FSSPIClient.UpdateAndBuildType3Message(buf));  {Do not translate}

        FCurrentStep := 3;
      end;
    3: begin
        FCurrentStep := 4;
      end;
  end;
end;

function TIdSSPINTLMAuthentication.KeepAlive: Boolean;
begin
  Result := FCurrentStep >= 1;
end;

function TIdSSPINTLMAuthentication.GetSteps: Integer;
begin
  Result := 3;
end;

procedure TIdSSPINTLMAuthentication.SetDomain(const Value: String);
begin
  Params.Values['Domain'] := Value; {do not localize}
end;

function TIdSSPINTLMAuthentication.GetDomain: String;
begin
  Result := Params.Values['Domain']; {do not localize}
end;

procedure TIdSSPINTLMAuthentication.SetUserName(const Value: String);
Var
  S: String;
begin
  S := Value;
  if IndyPos('\', S) > 0 then begin
    Domain := Copy(S, 1, IndyPos('\', S) - 1);
    Delete(S, 1, Length(Domain) + 1);
  end;
  inherited SetUserName(S);
end;

destructor TIdSSPINTLMAuthentication.Destroy;
begin
  FreeAndNil(FSSPIClient);
  inherited;
end;

initialization
  gSSPIInterface := TSSPIInterface.Create;
  if gSSPIInterface.IsAvailable then begin
    RegisterAuthenticationMethod('NTLM', TIdSSPINTLMAuthentication); {do not localize}
    RegisterAuthenticationMethod('Negotiate', TIdSSPINTLMAuthentication); {do not localize}
    gAuthRegistered := True;
  end;
finalization
  if gAuthRegistered then begin
    UnregisterAuthenticationMethod('NTLM'); {do not localize}
    UnregisterAuthenticationMethod('Negotiate'); {do not localize}
  end;
  FreeAndNil(gSSPIInterface);

end.

