www.pudn.com > Indy_9_00_14_src.zip > IdAuthenticationSSPI.pas


{ $HDR$} 
{**********************************************************************} 
{ Unit archived using Team Coherence                                   } 
{ Team Coherence is Copyright 2002 by Quality Software Components      } 
{                                                                      } 
{ For further information / comments, visit our WEB site at            } 
{ http://www.TeamCoherence.com                                         } 
{**********************************************************************} 
{} 
{ $Log:  10079: IdAuthenticationSSPI.pas  
{ 
{   Rev 1.1    01.2.2003 ã. 11:54:16  DBondzhev 
} 
{ 
{   Rev 1.0    2002.11.12 10:31:20 PM  czhower 
} 
{ 
 
  Implementation of the NTLM authentication with SSPI 
 
  Author: Alex Brainman 
  Copyright: (c) Chad Z. Hower and The Winshoes Working Group. 
 
} 
 
unit IdAuthenticationSSPI; 
{$DEFINE SET_ENCRYPT_IN_FT_WITH_GETPROCADDRESS_FUDGE} 
 
interface 
 
uses 
  IdAuthentication, Windows, classes, SysUtils, 
  IdSSPI; 
 
const 
  SEC_E_OK                         = 0; 
  SEC_E_INSUFFICIENT_MEMORY        = HRESULT($80090300); 
  SEC_E_INVALID_HANDLE             = HRESULT($80090301); 
  SEC_E_UNSUPPORTED_FUNCTION       = HRESULT($80090302); 
  SEC_E_TARGET_UNKNOWN             = HRESULT($80090303); 
  SEC_E_INTERNAL_ERROR             = HRESULT($80090304); 
  SEC_E_SECPKG_NOT_FOUND           = HRESULT($80090305); 
  SEC_E_NOT_OWNER                  = HRESULT($80090306); 
  SEC_E_CANNOT_INSTALL             = HRESULT($80090307); 
  SEC_E_INVALID_TOKEN              = HRESULT($80090308); 
  SEC_E_CANNOT_PACK                = HRESULT($80090309); 
  SEC_E_QOP_NOT_SUPPORTED          = HRESULT($8009030A); 
  SEC_E_NO_IMPERSONATION           = HRESULT($8009030B); 
  SEC_E_LOGON_DENIED               = HRESULT($8009030C); 
  SEC_E_UNKNOWN_CREDENTIALS        = HRESULT($8009030D); 
  SEC_E_NO_CREDENTIALS             = HRESULT($8009030E); 
  SEC_E_MESSAGE_ALTERED            = HRESULT($8009030F); 
  SEC_E_OUT_OF_SEQUENCE            = HRESULT($80090310); 
  SEC_E_NO_AUTHENTICATING_AUTHORITY = HRESULT($80090311); 
  SEC_I_CONTINUE_NEEDED            = HRESULT($00090312); 
  SEC_I_COMPLETE_NEEDED            = HRESULT($00090313); 
  SEC_I_COMPLETE_AND_CONTINUE      = HRESULT($00090314); 
  SEC_I_LOCAL_LOGON                = HRESULT($00090315); 
  SEC_E_BAD_PKGID                  = HRESULT($80090316); 
  SEC_E_CONTEXT_EXPIRED            = HRESULT($80090317); 
  SEC_E_INCOMPLETE_MESSAGE         = HRESULT($80090318); 
  SEC_E_INCOMPLETE_CREDENTIALS     = HRESULT($80090320); 
  SEC_E_BUFFER_TOO_SMALL           = HRESULT($80090321); 
  SEC_I_INCOMPLETE_CREDENTIALS     = HRESULT($00090320); 
  SEC_I_RENEGOTIATE                = HRESULT($00090321); 
  SEC_E_WRONG_PRINCIPAL            = HRESULT($80090322); 
  SEC_I_NO_LSA_CONTEXT             = HRESULT($00090323); 
  SEC_E_TIME_SKEW                  = HRESULT($80090324); 
  SEC_E_UNTRUSTED_ROOT             = HRESULT($80090325); 
  SEC_E_ILLEGAL_MESSAGE            = HRESULT($80090326); 
  SEC_E_CERT_UNKNOWN               = HRESULT($80090327); 
  SEC_E_CERT_EXPIRED               = HRESULT($80090328); 
  SEC_E_ENCRYPT_FAILURE            = HRESULT($80090329); 
  SEC_E_DECRYPT_FAILURE            = HRESULT($80090330); 
  SEC_E_ALGORITHM_MISMATCH         = HRESULT($80090331); 
  SEC_E_SECURITY_QOS_FAILED        = HRESULT($80090332); 
 
type 
  { ESSPIException } 
  ESSPIException = class(Exception) 
  public 
    class function GetErrorMessageByNo(aErrorNo: LongWord): string; 
  public 
    constructor CreateError( 
      aFailedFuncName: string; anErrorNo: Longint = SEC_E_OK); 
  end; 
 
  ESSPIInterfaceInitFailed = class(ESSPIException); 
 
  { TSSPIInterface } 
 
  TSSPIInterface = class(TObject) 
  private 
    fLoadPending, fIsAvailable: Boolean; 
    fPFunctionTable: PSecurityFunctionTableA; 
    fDLLHandle: THandle; 
    procedure releaseFunctionTable; 
    procedure checkAvailable; 
    function getFunctionTable: SecurityFunctionTableA; 
  public 
    class procedure RaiseIfError( 
      aStatus: SECURITY_STATUS; aFunctionName: string); 
    function IsAvailable: Boolean; 
    property FunctionTable: SecurityFunctionTableA read getFunctionTable; 
  public 
    constructor Create; 
    destructor Destroy; override; 
  end; 
 
  { TSSPIPackages } 
 
  TSSPIPackage = class(TObject) 
  private 
    fPSecPkginfo: PSecPkgInfo; 
    function getPSecPkgInfo: PSecPkgInfo; 
    function getMaxToken: ULONG; 
    function getName: string; 
  public 
    property MaxToken: ULONG read getMaxToken; 
    property Name: string read getName; 
  public 
    constructor Create(aPSecPkginfo: PSecPkgInfo); 
  end; 
 
  TCustomSSPIPackage = class(TSSPIPackage) 
  private 
    fInfo: PSecPkgInfo; 
  public 
    constructor Create(aPkgName: string); 
    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: PSEC_CHAR; 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; aDomain, 
      aUserName, aPassword: string); 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( 
      aTokenSourceName: PChar; 
      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 
    function UpdateAndGenerateReply( 
      const aFromPeerToken: string; var aToPeerToken: string): Boolean; 
 
  public 
    constructor Create(aCredentials: TSSPICredentials); 
 
  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 GenerateInitialChalenge( 
      const aTargetName: string; var aToPeerToken: string): Boolean; 
  public 
    constructor Create(aCredentials: TSSPICredentials); 
  end; 
 
  TIndySSPINTLMClient = class(TObject) 
  protected 
    fNTLMPackage: TSSPINTLMPackage; 
    fCredentials: TSSPIWinNTCredentials; 
    fContext: TSSPIClientConnectionContext; 
  public 
    procedure SetCredentials(aDomain, aUserName, aPassword: string); 
    procedure SetCredentialsAsCurrentUser; 
    function InitAndBuildType1Message: string; 
    function UpdateAndBuildType3Message(aServerType2Message: string): string; 
  public 
    constructor Create; 
    destructor Destroy; override; 
  end; 
 
  TIdSSPINTLMAuthentication = class(TIdAuthentication) 
  protected 
    FNTLMInfo: string; 
    FSSPIClient: TIndySSPINTLMClient; 
 
    function GetSteps: Integer; override; 
    function DoNext: TIdAuthWhatsNext; override; 
  public 
    constructor Create; override; 
    function Authentication: string; override; 
    function KeepAlive: Boolean; override; 
    procedure Reset; override; 
  end; 
 
implementation 
 
uses 
  IdGlobal, 
  IdException, 
  IdCoderMIME, 
  IdResourceStrings, Math; 
 
var 
  g: TSSPIInterface; 
 
{ ESSPIException } 
 
class function ESSPIException.GetErrorMessageByNo 
  (aErrorNo: LongWord): 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; 
  else 
    Result := RSHTTPSSPIUnknwonError; 
  end; 
end; 
 
constructor ESSPIException.CreateError 
  (aFailedFuncName: string; anErrorNo: Longint = SEC_E_OK); 
begin 
  if anErrorNo = SEC_E_OK then 
    inherited Create(aFailedFuncName) 
  else 
    inherited CreateFmt( 
      RSHTTPSSPIErrorMsg, 
      [aFailedFuncName, anErrorNo, anErrorNo, GetErrorMessageByNo(anErrorNo)]); 
end; 
 
{ TSSPIInterface } 
 
procedure TSSPIInterface.releaseFunctionTable; 
begin 
  if fPFunctionTable <> nil then begin 
    fPFunctionTable := nil; 
  end; 
end; 
 
procedure TSSPIInterface.checkAvailable; 
begin 
  if not IsAvailable then 
    raise ESSPIInterfaceInitFailed.Create( 
      RSHTTPSSPIInterfaceInitFailed); 
end; 
 
function TSSPIInterface.getFunctionTable: SecurityFunctionTableA; 
begin 
  checkAvailable; 
  Result := fPFunctionTable^; 
end; 
 
class procedure TSSPIInterface.RaiseIfError 
  (aStatus: SECURITY_STATUS; aFunctionName: string); 
begin 
  if not SEC_SUCCESS(aStatus) then 
    raise ESSPIException.CreateError(aFunctionName, aStatus); 
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_A; 
  begin 
    fIsAvailable := False; 
    if Win32Platform = VER_PLATFORM_WIN32_WINDOWS then 
      { Windows95 SSPI dll } 
      dllName := SECURITY_DLL_95 
    else 
      { WindowsNT & Windows2000 SSPI dll } 
      dllName := SECURITY_DLL_NT; 
    { load SSPI dll } 
    fDLLHandle := LoadLibrary(@dllName[1]); 
    if fDLLHandle > 0 then begin 
      { get InitSecurityInterface entry point 
        and call it to fetch SPPI function table} 
      entrypoint := GetProcAddress(fDLLHandle, SECURITY_ENTRYPOINTA); 
      fPFunctionTable := entrypoint; 
      { let's see what SSPI functions are available 
        and if we can continue on with the set } 
      with fPFunctionTable^ do begin 
        fIsAvailable := 
          Assigned(QuerySecurityPackageInfoA) and 
          Assigned(FreeContextBuffer) and 
          Assigned(DeleteSecurityContext) and 
          Assigned(FreeCredentialHandle) and 
          Assigned(AcquireCredentialsHandleA) and 
          Assigned(InitializeSecurityContextA) and 
        Assigned(AcceptSecurityContext) and 
          Assigned(ImpersonateSecurityContext) and 
          Assigned(RevertSecurityContext) and 
          Assigned(QueryContextAttributesA) and 
          Assigned(MakeSignature) and 
          Assigned(VerifySignature); 
{$IFDEF SET_ENCRYPT_IN_FT_WITH_GETPROCADDRESS_FUDGE} 
        { fudge for Encrypt/DecryptMessage } 
        if (not Assigned(EncryptMessage)) and (GetProcAddress(fDLLHandle, ENCRYPT_MESSAGE) <> nil) then 
          EncryptMessage := GetProcAddress(fDLLHandle, ENCRYPT_MESSAGE); 
        if (not Assigned(DecryptMessage)) and (GetProcAddress(fDLLHandle, DECRYPT_MESSAGE) <> nil) then 
          DecryptMessage := GetProcAddress(fDLLHandle, DECRYPT_MESSAGE); 
{$ENDIF} 
      end; 
    end; 
  end; 
 
begin 
  if fIsAvailable then 
    Result := True 
  else begin 
    if fLoadPending then begin 
      releaseFunctionTable; 
      loadDLL; 
      fLoadPending := False; 
    end; 
    Result := fIsAvailable; 
  end; 
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 } 
 
function TSSPIPackage.getPSecPkgInfo: PSecPkgInfo; 
begin 
  if fPSecPkginfo = nil then 
    raise ESSPIException.Create(RSHTTPSSPINoPkgInfoSpecified); 
  Result := fPSecPkginfo; 
end; 
 
function TSSPIPackage.getMaxToken: ULONG; 
begin 
  Result := getPSecPkgInfo^.cbMaxToken; 
end; 
 
function TSSPIPackage.getName: string; 
begin 
  Result := StrPas(getPSecPkgInfo^.Name); 
end; 
 
constructor TSSPIPackage.Create(aPSecPkginfo: PSecPkgInfo); 
begin 
  inherited Create; 
  fPSecPkginfo := aPSecPkginfo; 
end; 
 
{ TCustomSSPIPackage } 
 
constructor TCustomSSPIPackage.Create(aPkgName: string); 
begin 
  g.RaiseIfError( 
    g.FunctionTable.QuerySecurityPackageInfoA(PChar(aPkgName), @fInfo), 
    'QuerySecurityPackageInfoA');   {Do not translate} 
  inherited Create(fInfo); 
end; 
 
destructor TCustomSSPIPackage.Destroy; 
begin 
  if fInfo <> nil then 
    g.RaiseIfError( 
      g.FunctionTable.FreeContextBuffer(fInfo), 'FreeContextBuffer');  {Do not translate} 
  inherited Destroy; 
end; 
 
{ TSSPINTLMPackage } 
 
constructor TSSPINTLMPackage.Create; 
begin 
  inherited Create(NTLMSP_NAME); 
end; 
 
{ TSSPICredentials } 
 
procedure TSSPICredentials.CheckAcquired; 
begin 
  if not fAcquired then 
    raise ESSPIException.Create(RSHTTPSSPINoCredentialHandle); 
end; 
 
procedure TSSPICredentials.CheckNotAcquired; 
begin 
  if fAcquired then 
    raise ESSPIException.Create( 
     RSHTTPSSPICanNotChangeCredentials); 
end; 
 
procedure TSSPICredentials.DoAcquire 
  (pszPrincipal: PSEC_CHAR; 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; 
  g.RaiseIfError( 
    g.FunctionTable.AcquireCredentialsHandleA( 
    pszPrincipal, PSEC_CHAR(Package.Name), cu, pvLogonId, pAuthData, nil, nil, 
    @fHandle, @fExpiry), 
    'AcquireCredentialsHandleA');  {Do not translater} 
  fAcquired := True; 
end; 
 
procedure TSSPICredentials.DoRelease; 
begin 
  g.RaiseIfError( 
    g.FunctionTable.FreeCredentialHandle(@fHandle), 
    'FreeCredentialHandle');      {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; 
 
constructor TSSPICredentials.Create(aPackage: TSSPIPackage); 
begin 
  inherited Create; 
  fPackage := aPackage; 
  fUse := scuOutBound; 
  fAcquired := False; 
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; aDomain, aUserName, aPassword: string); 
var 
  ai: SEC_WINNT_AUTH_IDENTITY; 
  pai: PVOID; 
begin 
  Use := aUse; 
  if (Length(aDomain) > 0) and (Length(aUserName) > 0) then begin 
    with ai do begin 
      User := PChar(aUserName); 
      UserLength := Length(aUserName); 
      Domain := PChar(aDomain); 
      DomainLength := Length(aDomain); 
      Password := PChar(aPassword); 
      PasswordLength := Length(aPassword); 
      Flags := SEC_WINNT_AUTH_IDENTITY_ANSI; 
    end; 
    pai := @ai; 
  end else 
    pai := nil; 
  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 
    for i := Low(aErrorsToIgnore) to High(aErrorsToIgnore) do 
      if aFuncResult = aErrorsToIgnore[i] then begin 
        doRaise := False; 
        break; 
      end; 
  if doRaise then 
    raise ESSPIException.CreateError(aFuncName, aFuncResult); 
  if not fHasHandle then 
    fHasHandle := True; 
end; 
 
function TSSPIContext.DoInitialize 
  (aTokenSourceName: PChar; 
  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 := 
    g.FunctionTable.InitializeSecurityContextA( 
    Credentials.Handle, tmp, aTokenSourceName, 
    GetRequestedFlags, 0, SECURITY_NATIVE_DREP, tmp2, 0, 
    @fHandle, @aOut, @r, @fExpiry 
    ); 
  updateHasContextAndCheckForError( 
    Result, 'InitializeSecurityContextA', errorsToIgnore); {Do not translate} 
  SetEstablishedFlags(r); 
end; 
 
procedure TSSPIContext.DoRelease; 
begin 
  g.RaiseIfError( 
    g.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 
    raise ESSPIException.Create(RSHTTPSSPINoCredentialHandle); 
end; 
 
procedure TSSPIContext.CheckCredentials; 
begin 
  if (not Assigned(Credentials)) or (not Credentials.Acquired) then 
    raise ESSPIException.Create(RSHTTPSSPIDoAuquireCredentialHandle); 
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: string; var aToPeerToken: string): Boolean; 
var 
  fOutBuff: SecBuffer; 
begin 
  Result := False; 
  { check credentials } 
  CheckCredentials; 
  { prepare input buffer } 
  with fInBuff do begin 
    cbBuffer := Length(aFromPeerToken); 
    pvBuffer := @(aFromPeerToken[1]); 
  end; 
  { prepare output buffer } 
  with fOutBuff do begin 
    BufferType := SECBUFFER_TOKEN; 
    cbBuffer := Credentials.Package.MaxToken; 
    pvBuffer := AllocMem(cbBuffer); 
  end; 
  with fOutBuffDesc do begin 
    ulVersion := SECBUFFER_VERSION; 
    cBuffers := 1; 
    pBuffers := @fOutBuff; 
  end; 
  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(g.FunctionTable.CompleteAuthToken) then 
          begin 
            raise ESSPIException.Create(RSHTTPSSPICompleteTokenNotSupported); 
          end; 
          fStatus := g.FunctionTable.CompleteAuthToken(Handle, @fOutBuffDesc); 
          g.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 
      with fOutBuff do 
        SetString(aToPeerToken, PChar(pvBuffer), cbBuffer); 
  finally 
    FreeMem(fOutBuff.pvBuffer); 
  end; 
end; 
 
constructor TCustomSSPIConnectionContext.Create(aCredentials: TSSPICredentials); 
begin 
  inherited Create(aCredentials); 
  with fInBuff do begin 
    BufferType := SECBUFFER_TOKEN; 
  end; 
  with fInBuffDesc do begin 
    ulVersion := SECBUFFER_VERSION; 
    cBuffers := 1; 
    pBuffers := @fInBuff; 
  end; 
  with fOutBuffDesc do begin 
    ulVersion := SECBUFFER_VERSION; 
    cBuffers := 1; 
  end; 
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(PChar(fTargetName), aIn, aOut, []); 
end; 
 
function TSSPIClientConnectionContext.GenerateInitialChalenge 
  (const aTargetName: string; var aToPeerToken: string): Boolean; 
begin 
  Release; 
  fTargetName := aTargetName; 
  Result := UpdateAndGenerateReply('', 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 
  fContext.Free; 
  fCredentials.Free; 
  fNTLMPackage.Free; 
  inherited Destroy; 
end; 
 
procedure TIndySSPINTLMClient.SetCredentials 
  (aDomain, aUserName, aPassword: string); 
begin 
  fCredentials.Acquire(scuOutBound, aDomain, aUserName, aPassword); 
end; 
 
procedure TIndySSPINTLMClient.SetCredentialsAsCurrentUser; 
begin 
  fCredentials.Acquire(scuOutBound); 
end; 
 
function TIndySSPINTLMClient.InitAndBuildType1Message: string; 
begin 
  fContext.GenerateInitialChalenge('', Result); 
end; 
 
function TIndySSPINTLMClient.UpdateAndBuildType3Message 
  (aServerType2Message: string): string; 
begin 
  fContext.UpdateAndGenerateReply(aServerType2Message, Result); 
end; 
 
{ TIdSSPINTLMAuthentication } 
 
constructor TIdSSPINTLMAuthentication.Create; 
begin 
  inherited Create; 
  FSSPIClient := TIndySSPINTLMClient.Create; 
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; 
    3: 
      begin 
        FCurrentStep := 4; 
        result := wnDoRequest; 
      end; 
    4: 
      begin 
        FCurrentStep := 0; 
        If Username = '' then 
          result := wnAskTheProgram 
        else 
        result := wnFail; 
      end; 
  end; 
end; 
 
function TIdSSPINTLMAuthentication.Authentication: string; 
var 
  S: string; 
begin 
  result := ''; 
  case FCurrentStep of 
    1: 
      begin 
        if Length(Username) = 0 then 
          FSSPIClient.SetCredentialsAsCurrentUser 
        else 
          FSSPIClient.SetCredentials(IndyGetHostName, Username, Password); 
        result := 'NTLM ' + TIdEncoderMIME.EncodeString(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; 
 
        S := TIdDecoderMIME.DecodeString(FNTLMInfo); 
        result := 'NTLM ' + TIdEncoderMIME.EncodeString(FSSPIClient.UpdateAndBuildType3Message(S));  {Do not translate} 
        FCurrentStep := 3; 
 
        Inc(FAuthRetries); 
      end; 
    3: begin 
        FCurrentStep := 4; 
      end; 
  end; 
end; 
 
procedure TIdSSPINTLMAuthentication.Reset; 
begin 
  inherited Reset; 
  FCurrentStep := 0; 
end; 
 
function TIdSSPINTLMAuthentication.KeepAlive: Boolean; 
begin 
  result := FCurrentStep >= 1; 
end; 
 
function TIdSSPINTLMAuthentication.GetSteps: Integer; 
begin 
  result := 3; 
end; 
 
initialization 
  g := TSSPIInterface.Create; 
  if g.IsAvailable then 
    RegisterAuthenticationMethod('NTLM', TIdSSPINTLMAuthentication);  {Do not translate} 
finalization 
  g.Free; 
end.