unit IdSASL_NTLM;

interface

{$i IdCompilerDefines.inc}

uses
  IdGlobal,
  IdSASL,
  IdSASLUserPass;

const
  DEF_LMCompatibility = 0;

type
  TIdSASLNTLM = class(TIdSASLUserPass)
  protected
    FDomain : String;
    FLMCompatibility : UInt32;
    procedure InitComponent; override;
  public
    class function ServiceName: TIdSASLServiceName; override;
    function TryStartAuthenticate(const AHost, AProtocolName: string; var VInitialResponse: string): Boolean; override;
    function StartAuthenticate(const AChallenge, AHost, AProtocolName:string) : String; override;
    function ContinueAuthenticate(const ALastResponse, AHost, AProtocolName: String): string; override;
    function IsReadyToStart: Boolean; override;

    property Domain : String read FDomain write FDomain;
{
The LMCompatibility property is designed to work directly with the "HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\LSA\LMCompatibilityLevel"
and will act as this key is documented.  This effects how NTLM authentication
is done on the server.  We do not pull the value from the registry because other systems
don't have a registry and you may want to set this to a value that's different from
your registry.

http://davenport.sourceforge.net/ntlm.html describes these like this:

=======================================================================
Level        |	Sent by Client               |	Accepted by Server
=======================================================================
0            | LM NTLM                       | LM NTLM /LMv2 NTLMv2
1            | LM NTLM                       | LM NTLM /LMv2 NTLMv2
2            | NTLM (is sent in both feilds) | LM NTLM /LMv2 NTLMv2
3            | LMv2 NTLMv2                   | LM NTLM /LMv2 NTLMv2
4            | LMv2 NTLMv2                   | NTLM /LMv2 NTLMv2
5            | LMv2 NTLMv2                   | LMv2 NTLMv2

}
    property LMCompatibility : UInt32 read FLMCompatibility write FLMCompatibility default DEF_LMCompatibility;
  end;

implementation

uses
  IdFIPS, IdNTLMv2;
  
//uses IdNTLM;

{ TIdSASLNTLM }

function TIdSASLNTLM.ContinueAuthenticate(const ALastResponse, AHost,
  AProtocolName: String): string;
var
  LMsg : TIdBytes;
  LNonce : TIdBytes; //this is also called the challange
  LTargetName, LTargetInfo : TIdBytes;
  LFlags : UInt32;
  LDomain, LUserName : String;
  LEncoding: IIdTextEncoding;
begin
  LEncoding := IndyTextEncoding_8Bit;
  LMsg := ToBytes(ALastResponse, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
  IdNTLMv2.ReadType2Msg(LMsg, LFlags, LTargetName, LTargetInfo, LNonce);
  IdGlobal.DebugOutput('Type 2 Flags = '+ DumpFlags(LFlags));
  GetDomain(GetUsername, LUsername, LDomain);
  Result := BytesToStringRaw( BuildType3Msg(LDomain, LDomain, GetUsername, GetPassword,
    LFlags, LNonce, LTargetName, LTargetInfo, FLMCompatibility) );
end;

procedure TIdSASLNTLM.InitComponent;
begin
  inherited InitComponent;
  Self.FLMCompatibility := DEF_LMCompatibility;
end;

function TIdSASLNTLM.IsReadyToStart: Boolean;
begin
  Result := (not GetFIPSMode) and (inherited IsReadyToStart) and
     NTLMFunctionsLoaded;
end;

class function TIdSASLNTLM.ServiceName: TIdSASLServiceName;
begin
  Result := 'NTLM';   {Do not localize}
end;

function TIdSASLNTLM.TryStartAuthenticate(const AHost, AProtocolName: string;
  var VInitialResponse: string): Boolean;
var
  LDomain, LUsername : String;
begin
  GetDomain(GetUsername, LUsername, LDomain);
  if LDomain = '' then begin
    LDomain := FDomain;
  end;
  VInitialResponse := BytesToStringRaw(IdNTLMv2.BuildType1Msg(LDomain, LDomain, FLMCompatibility));
  Result := True;
end;

function TIdSASLNTLM.StartAuthenticate(const AChallenge, AHost, AProtocolName: string): String;
begin
  TryStartAuthenticate(AHost, AProtocolName, Result);
end;

end.
