| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594 |
- {
- $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.5 11/27/2004 8:27:14 PM JPMugaas
- Fix for compiler errors.
- Rev 1.4 11/27/04 2:56:40 AM RLebeau
- Added support for overloaded version of LoginSASL().
- Added GetDisplayName() method to TIdSASLListEntry, and FindSASL() method to
- TIdSASLEntries.
- Rev 1.3 10/26/2004 10:55:32 PM JPMugaas
- Updated refs.
- Rev 1.2 6/11/2004 9:38:38 AM DSiders
- Added "Do not Localize" comments.
- Rev 1.1 2004.02.03 5:45:50 PM czhower
- Name changes
- Rev 1.0 1/25/2004 3:09:54 PM JPMugaas
- New collection class for SASL mechanism processing.
- }
- unit IdSASLCollection;
- interface
- {$i IdCompilerDefines.inc}
- uses
- Classes,
- IdBaseComponent,
- IdCoder,
- IdException,
- IdGlobal,
- IdSASL,
- IdTCPConnection;
- type
- TIdSASLEntries = class;
- TIdSASLListEntry = class(TCollectionItem)
- protected
- {$IFDEF USE_OBJECT_ARC}[Weak]{$ENDIF} FSASL : TIdSASL;
- function GetDisplayName: String; override;
- function GetOwnerComponent: TComponent;
- function GetSASLEntries: TIdSASLEntries;
- procedure SetSASL(AValue : TIdSASL);
- public
- procedure Assign(Source: TPersistent); override;
- property OwnerComponent: TComponent read GetOwnerComponent;
- property SASLEntries: TIdSASLEntries read GetSASLEntries;
- published
- property SASL : TIdSASL read FSASL write SetSASL;
- end;
- TIdSASLEntries = class ( TOwnedCollection )
- protected
- procedure CheckIfEmpty;
- function GetItem(Index: Integer) : TIdSASLListEntry;
- function GetOwnerComponent: TComponent;
- procedure SetItem(Index: Integer; const Value: TIdSASLListEntry);
- public
- constructor Create ( AOwner : TPersistent ); reintroduce;
- function Add: TIdSASLListEntry;
- procedure LoginSASL(const ACmd, AHost, AProtocolName: String;
- const AOkReplies, AContinueReplies: array of string; AClient : TIdTCPConnection;
- ACapaReply : TStrings; const AAuthString : String = 'AUTH'; {Do not Localize}
- ACanAttemptIR: Boolean = True); overload; {$IFDEF HAS_DEPRECATED}deprecated{$IFDEF HAS_DEPRECATED_MSG} 'Use overload with APort parameter'{$ENDIF};{$ENDIF}
- procedure LoginSASL(const ACmd, AHost: String; const APort: TIdPort; const AProtocolName: String;
- const AOkReplies, AContinueReplies: array of string; AClient : TIdTCPConnection;
- ACapaReply : TStrings; const AAuthString : String = 'AUTH'; {Do not Localize}
- ACanAttemptIR: Boolean = True); overload;
- procedure LoginSASL(const ACmd, AHost, AProtocolName, AServiceName: String;
- const AOkReplies, AContinueReplies: array of string; AClient : TIdTCPConnection;
- ACapaReply : TStrings; const AAuthString : String = 'AUTH'; {Do not Localize}
- ACanAttemptIR: Boolean = True); overload; {$IFDEF HAS_DEPRECATED}deprecated{$IFDEF HAS_DEPRECATED_MSG} 'Use overload with APort parameter'{$ENDIF};{$ENDIF}
- procedure LoginSASL(const ACmd, AHost: String; const APort: TIdPort; const AProtocolName, AServiceName: String;
- const AOkReplies, AContinueReplies: array of string; AClient : TIdTCPConnection;
- ACapaReply : TStrings; const AAuthString : String = 'AUTH'; {Do not Localize}
- ACanAttemptIR: Boolean = True); overload;
- function ParseCapaReply(ACapaReply: TStrings; const AAuthString: String = 'AUTH') : TStrings; {$IFDEF HAS_DEPRECATED}deprecated{$IFDEF HAS_DEPRECATED_MSG} 'Use ParseCapaReplyToList()'{$ENDIF};{$ENDIF} {do not localize}
- procedure ParseCapaReplyToList(ACapaReply, ADestList: TStrings; const AAuthString: String = 'AUTH'); {do not localize}
- function FindSASL(const AServiceName: String): TIdSASL;
- function Insert(Index: Integer): TIdSASLListEntry;
- procedure RemoveByComp(AComponent : TComponent);
- function IndexOfComp(AItem : TIdSASL): Integer;
- property Items[Index: Integer] : TIdSASLListEntry read GetItem write SetItem; default;
- property OwnerComponent: TComponent read GetOwnerComponent;
- end;
- EIdSASLException = class(EIdException);
- EIdSASLNotSupported = class(EIdSASLException);
- EIdSASLNotReady = class(EIdSASLException);
- EIdSASLMechNeeded = class(EIdSASLException);
- implementation
- uses
- {$IFDEF HAS_UNIT_Generics_Collections}
- System.Generics.Collections,
- {$ENDIF}
- IdAssignedNumbers,
- IdCoderMIME,
- IdGlobalProtocols,
- IdReply,
- IdResourceStringsProtocols,
- SysUtils;
- { TIdSASLListEntry }
- procedure TIdSASLListEntry.Assign(Source: TPersistent);
- begin
- if Source is TIdSASLListEntry then begin
- SASL := TIdSASLListEntry(Source).SASL;
- end else begin
- inherited Assign(Source);
- end;
- end;
- function TIdSASLListEntry.GetDisplayName: String;
- begin
- if FSASL <> nil then begin
- Result := String(FSASL.ServiceName);
- end else begin
- Result := inherited GetDisplayName;
- end;
- end;
- function TIdSASLListEntry.GetOwnerComponent: TComponent;
- var
- LEntries: TIdSASLEntries;
- begin
- LEntries := SASLEntries;
- if Assigned(LEntries) then begin
- Result := LEntries.OwnerComponent;
- end else begin
- Result := nil;
- end;
- end;
- function TIdSASLListEntry.GetSASLEntries: TIdSASLEntries;
- begin
- if Collection is TIdSASLEntries then begin
- Result := TIdSASLEntries(Collection);
- end else begin
- Result := nil;
- end;
- end;
- procedure TIdSASLListEntry.SetSASL(AValue : TIdSASL);
- var
- LOwnerComp: TComponent;
- begin
- if FSASL <> AValue then begin
- LOwnerComp := OwnerComponent;
- if (FSASL <> nil) and (LOwnerComp <> nil) then begin
- FSASL.RemoveFreeNotification(LOwnerComp);
- end;
- FSASL := AValue;
- if (FSASL <> nil) and (LOwnerComp <> nil) then begin
- FSASL.FreeNotification(LOwnerComp);
- end;
- end;
- end;
- { TIdSASLEntries }
- // RLebeau 2/8/2013: WARNING!!! To work around a design limitation in the way
- // TIdIMAP4 implements SendCmd(), it cannot use TIdSASLEntries.LoginSASL() for
- // SASL authentication because the SASL commands sent in this unit will not end
- // up being IMAP-compatible! Until that can be addressed, any changes made to
- // PerformSASLLogin() or LoginSASL() in this unit need to be duplicated in the
- // IdIMAP4.pas unit for the TIdIMAP4.Login() method as well...
- function CheckStrFail(const AStr : String; const AOk, ACont: array of string) : Boolean;
- begin
- Result := (PosInStrArray(AStr, AOk) = -1) and
- (PosInStrArray(AStr, ACont) = -1);
- end;
- function PerformSASLLogin(const ACmd, AHost: string; const APort: TIdPort; const AProtocolName: String;
- ASASL: TIdSASL; AEncoder: TIdEncoder; ADecoder: TIdDecoder; const AOkReplies, AContinueReplies: array of string;
- AClient : TIdTCPConnection; ACanAttemptIR: Boolean): Boolean;
- var
- S: String;
- AuthStarted: Boolean;
- begin
- Result := False;
- AuthStarted := False;
- // TODO: handle ACanAttemptIR based on AProtocolName.
- //
- // SASL in SMTP and DICT supported Initial-Response from the beginning,
- // as should any new SASL-enabled protocol moving forward.
- //
- // SASL in IMAP did not originally support Initial-Response, but it was
- // added in RFC 4959 along with an explicit capability ('SASL-IR') to
- // indicate when Initial-Response is supported. SASL in IMAP is currently
- // handled by TIdIMAP4 directly, but should it be updated to use
- // TIdSASLEntries.LoginSASL() in the future then it will set the
- // ACanAttemptIR parameter accordingly.
- //
- // SASL in POP3 did not originally support Initial-Response. It was added
- // in RFC 2449 along with the CAPA command. If a server supports the CAPA
- // command then it *should* also support Initial-Response as well, however
- // many POP3 servers support CAPA but do not support Initial-Response
- // (which was formalized in RFC 5034). So, to handle that descrepency,
- // TIdPOP3 currently sets ACanAttemptIR to false. In the future, we could
- // let it set ACanAttemptIR to True instead, and then if Initial-Response
- // fails here for POP3 then re-attempt without Initial-Response before
- // exiting with a failure.
- // TODO: use UTF-8 when base64-encoding strings...
- if ACanAttemptIR then begin
- if ASASL.TryStartAuthenticate(AHost, APort, AProtocolName, S) then begin
- AClient.SendCmd(ACmd + ' ' + String(ASASL.ServiceName) + ' ' + AEncoder.Encode(S));//[334, 504]
- if CheckStrFail(AClient.LastCmdResult.Code, AOkReplies, AContinueReplies) then begin
- // TODO: re-attempt without IR unconditionally? Or add a callback
- // to let the caller decide whether to re-attempt or not...
- if not TextIsSame(AProtocolName, IdGSKSSN_pop) then begin
- ASASL.FinishAuthenticate;
- Exit; // this mechanism is not supported
- end;
- end else begin
- AuthStarted := True;
- end;
- end;
- end;
- if not AuthStarted then begin
- AClient.SendCmd(ACmd + ' ' + String(ASASL.ServiceName));//[334, 504]
- if CheckStrFail(AClient.LastCmdResult.Code, AOkReplies, AContinueReplies) then begin
- Exit; // this mechanism is not supported
- end;
- end;
- if (PosInStrArray(AClient.LastCmdResult.Code, AOkReplies) > -1) then begin
- if AuthStarted then begin
- ASASL.FinishAuthenticate;
- end;
- Result := True;
- Exit; // we've authenticated successfully :)
- end;
- // must be a continue reply...
- if not AuthStarted then begin
- S := ADecoder.DecodeString(TrimRight(AClient.LastCmdResult.Text.Text));
- S := ASASL.StartAuthenticate(S, AHost, APort, AProtocolName);
- AClient.SendCmd(AEncoder.Encode(S));
- if CheckStrFail(AClient.LastCmdResult.Code, AOkReplies, AContinueReplies) then
- begin
- ASASL.FinishAuthenticate;
- Exit;
- end;
- end;
- while PosInStrArray(AClient.LastCmdResult.Code, AContinueReplies) > -1 do begin
- S := ADecoder.DecodeString(TrimRight(AClient.LastCmdResult.Text.Text));
- S := ASASL.ContinueAuthenticate(S, AHost, APort, AProtocolName);
- AClient.SendCmd(AEncoder.Encode(S));
- if CheckStrFail(AClient.LastCmdResult.Code, AOkReplies, AContinueReplies) then
- begin
- ASASL.FinishAuthenticate;
- Exit;
- end;
- end;
- Result := (PosInStrArray(AClient.LastCmdResult.Code, AOkReplies) > -1);
- ASASL.FinishAuthenticate;
- end;
- function TIdSASLEntries.Add: TIdSASLListEntry;
- begin
- Result := TIdSASLListEntry(inherited Add);
- end;
- constructor TIdSASLEntries.Create(AOwner: TPersistent);
- begin
- inherited Create(AOwner, TIdSASLListEntry);
- end;
- procedure TIdSASLEntries.CheckIfEmpty;
- var
- I: Integer;
- begin
- for I := 0 to Count-1 do begin
- if Items[I].SASL <> nil then begin
- Exit;
- end;
- end;
- raise EIdSASLMechNeeded.Create(RSSASLRequired);
- end;
- function TIdSASLEntries.GetItem(Index: Integer): TIdSASLListEntry;
- begin
- Result := TIdSASLListEntry(inherited Items[Index]);
- end;
- function TIdSASLEntries.GetOwnerComponent: TComponent;
- var
- LOwner: TPersistent;
- begin
- LOwner := inherited GetOwner;
- if LOwner is TComponent then begin
- Result := TComponent(LOwner);
- end else begin
- Result := nil;
- end;
- end;
- function TIdSASLEntries.IndexOfComp(AItem: TIdSASL): Integer;
- begin
- for Result := 0 to Count -1 do
- begin
- if Items[Result].SASL = AItem then
- begin
- Exit;
- end;
- end;
- Result := -1;
- end;
- function TIdSASLEntries.Insert(Index: Integer): TIdSASLListEntry;
- begin
- Result := TIdSASLListEntry( inherited Insert(Index) );
- end;
- type
- {$IFDEF HAS_GENERICS_TList}
- TIdSASLList = TList<TIdSASL>;
- {$ELSE}
- // TODO: flesh out to match TList<TIdSASL> for non-Generics compilers
- TIdSASLList = TList;
- {$ENDIF}
- {$I IdDeprecatedImplBugOff.inc}
- procedure TIdSASLEntries.LoginSASL(const ACmd, AHost, AProtocolName: String; const AOkReplies,
- AContinueReplies: array of string; AClient: TIdTCPConnection;
- ACapaReply: TStrings; const AAuthString: String; ACanAttemptIR: Boolean);
- {$I IdDeprecatedImplBugOn.inc}
- begin
- LoginSASL(ACmd, AHost, 0, AProtocolName, AOkReplies, AContinueReplies, AClient, ACapaReply, AAuthString, ACanAttemptIR);
- end;
- procedure TIdSASLEntries.LoginSASL(const ACmd, AHost: String; const APort: TIdPort; const AProtocolName: String;
- const AOkReplies, AContinueReplies: array of string; AClient: TIdTCPConnection;
- ACapaReply: TStrings; const AAuthString: String; ACanAttemptIR: Boolean);
- var
- i : Integer;
- LE : TIdEncoderMIME;
- LD : TIdDecoderMIME;
- LSupportedSASL : TStrings;
- LSASLList: TIdSASLList;
- LSASL : TIdSASL;
- LError : TIdReply;
- function SetupErrorReply: TIdReply;
- begin
- Result := TIdReplyClass(AClient.LastCmdResult.ClassType).Create(nil);
- Result.Assign(AClient.LastCmdResult);
- end;
- begin
- // make sure the collection is not empty
- CheckIfEmpty;
- //create a list of mechanisms that both parties support
- LSASLList := TIdSASLList.Create;
- try
- LSupportedSASL := TStringList.Create;
- try
- ParseCapaReplyToList(ACapaReply, LSupportedSASL, AAuthString);
- for i := Count-1 downto 0 do begin
- LSASL := Items[i].SASL;
- if LSASL <> nil then begin
- if not LSASL.IsAuthProtocolAvailable(LSupportedSASL) then begin
- Continue;
- end;
- if LSASLList.IndexOf(LSASL) = -1 then begin
- LSASLList.Add(LSASL);
- end;
- end;
- end;
- finally
- FreeAndNil(LSupportedSASL);
- end;
- if LSASLList.Count = 0 then begin
- raise EIdSASLNotSupported.Create(RSSASLNotSupported);
- end;
- //now do it
- LE := nil;
- try
- LD := nil;
- try
- LError := nil;
- try
- for i := 0 to LSASLList.Count-1 do begin
- LSASL := {$IFDEF HAS_GENERICS_TList}LSASLList.Items[i]{$ELSE}TIdSASL(LSASLList.Items[i]){$ENDIF};
- if not LSASL.IsReadyToStart then begin
- Continue;
- end;
- if not Assigned(LE) then begin
- LE := TIdEncoderMIME.Create(nil);
- end;
- if not Assigned(LD) then begin
- LD := TIdDecoderMIME.Create(nil);
- end;
- if PerformSASLLogin(ACmd, AHost, APort, AProtocolName, LSASL, LE, LD, AOkReplies, AContinueReplies, AClient, ACanAttemptIR) then begin
- Exit;
- end;
- if not Assigned(LError) then begin
- LError := SetupErrorReply;
- end;
- end;
- if Assigned(LError) then begin
- LError.RaiseReplyError;
- end else begin
- raise EIdSASLNotReady.Create(RSSASLNotReady);
- end;
- finally
- FreeAndNil(LError);
- end;
- finally
- FreeAndNil(LD);
- end;
- finally
- FreeAndNil(LE);
- end;
- finally
- FreeAndNil(LSASLList);
- end;
- end;
- {$I IdDeprecatedImplBugOff.inc}
- procedure TIdSASLEntries.LoginSASL(const ACmd, AHost, AProtocolName, AServiceName: String;
- const AOkReplies, AContinueReplies: array of string; AClient: TIdTCPConnection;
- ACapaReply: TStrings; const AAuthString: String; ACanAttemptIR: Boolean);
- {$I IdDeprecatedImplBugOn.inc}
- begin
- LoginSASL(ACmd, AHost, 0, AProtocolName, AServiceName, AOkReplies, AContinueReplies, AClient, ACapaReply, AAuthString, ACanAttemptIR);
- end;
- procedure TIdSASLEntries.LoginSASL(const ACmd, AHost: String; const APort: TIdPort;
- const AProtocolName, AServiceName: String;
- const AOkReplies, AContinueReplies: array of string; AClient: TIdTCPConnection;
- ACapaReply: TStrings; const AAuthString: String; ACanAttemptIR: Boolean);
- var
- LE : TIdEncoderMIME;
- LD : TIdDecoderMIME;
- LSupportedSASL : TStrings;
- LSASL : TIdSASL;
- begin
- LSASL := nil;
- // make sure the collection is not empty
- CheckIfEmpty;
- //determine if both parties support the same mechanism
- LSupportedSASL := TStringList.Create;
- try
- ParseCapaReplyToList(ACapaReply, LSupportedSASL, AAuthString);
- if LSupportedSASL.IndexOf(AServiceName) <> -1 then begin
- LSASL := FindSASL(AServiceName);
- end;
- finally
- FreeAndNil(LSupportedSASL);
- end;
- if LSASL = nil then begin
- raise EIdSASLNotSupported.Create(RSSASLNotSupported);
- end;
- if not LSASL.IsReadyToStart then begin
- raise EIdSASLNotReady.Create(RSSASLNotReady);
- end;
- //now do it
- LE := TIdEncoderMIME.Create(nil);
- try
- LD := TIdDecoderMIME.Create(nil);
- try
- if not PerformSASLLogin(ACmd, AHost, APort, AProtocolName, LSASL, LE, LD, AOkReplies, AContinueReplies, AClient, ACanAttemptIR) then begin
- AClient.RaiseExceptionForLastCmdResult;
- end;
- finally
- FreeAndNil(LD);
- end;
- finally
- FreeAndNil(LE);
- end;
- end;
- {$I IdDeprecatedImplBugOff.inc}
- function TIdSASLEntries.ParseCapaReply(ACapaReply: TStrings; const AAuthString: String): TStrings;
- {$I IdDeprecatedImplBugOn.inc}
- begin
- Result := TStringList.Create;
- try
- ParseCapaReplyToList(ACapaReply, Result, AAuthString);
- except
- FreeAndNil(Result);
- raise;
- end;
- end;
- procedure TIdSASLEntries.ParseCapaReplyToList(ACapaReply, ADestList: TStrings;
- const AAuthString: String = 'AUTH'); {do not localize}
- const
- VALIDDELIMS: String = ' ='; {Do not Localize}
- var
- i: Integer;
- s: string;
- LEntry : String;
- begin
- if ACapaReply = nil then begin
- Exit;
- end;
- ADestList.BeginUpdate;
- try
- for i := 0 to ACapaReply.Count - 1 do
- begin
- s := ACapaReply[i];
- if TextStartsWith(s, AAuthString) and CharIsInSet(s, Length(AAuthString)+1, VALIDDELIMS) then
- begin
- s := UpperCase(Copy(s, Length(AAuthString)+1, MaxInt));
- s := ReplaceAll(s, '=', ' '); {Do not Localize}
- while Length(s) > 0 do
- begin
- LEntry := Fetch(s, ' '); {Do not Localize}
- if LEntry <> '' then
- begin
- if ADestList.IndexOf(LEntry) = -1 then begin
- ADestList.Add(LEntry);
- end;
- end;
- end;
- end;
- end;
- finally
- ADestList.EndUpdate;
- end;
- end;
- function TIdSASLEntries.FindSASL(const AServiceName: String): TIdSASL;
- var
- i: Integer;
- LEntry: TIdSASLListEntry;
- begin
- Result := nil;
- For i := 0 to Count-1 do begin
- LEntry := Items[i];
- if LEntry.SASL <> nil then begin
- if TextIsSame(String(LEntry.SASL.ServiceName), AServiceName) then begin
- Result := LEntry.SASL;
- Exit;
- end;
- end;
- end;
- end;
- procedure TIdSASLEntries.RemoveByComp(AComponent: TComponent);
- var
- i : Integer;
- begin
- for i := Count-1 downto 0 do
- begin
- if Items[i].SASL = AComponent then begin
- Delete(i);
- end;
- end;
- end;
- procedure TIdSASLEntries.SetItem(Index: Integer; const Value: TIdSASLListEntry);
- begin
- inherited SetItem(Index, Value);
- end;
- end.
|