restemplate/indy/Protocols/IdPOP3.pas

628 lines
18 KiB
Plaintext
Raw Normal View History

{
$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.39 1/7/05 3:29:12 PM RLebeau
Fix for AV in Notification()
Rev 1.38 12/21/04 1:51:42 AM RLebeau
Bug fix for Capa() method.
Rev 1.37 11/27/04 2:48:22 AM RLebeau
Fixed bug in ownership of SASLMechanisms property
Rev 1.36 10/26/2004 10:35:42 PM JPMugaas
Updated ref.
Rev 1.35 2004.04.18 1:39:26 PM czhower
Bug fix for .NET with attachments, and several other issues found along the
way.
Rev 1.34 2004.04.07 6:02:42 PM czhower
Implemented AutoLogin in a better manner.
Rev 1.33 2004.04.07 5:53:56 PM czhower
.NET overload
Rev 1.32 2004.03.06 1:31:48 PM czhower
To match Disconnect changes to core.
Rev 1.31 2004.02.03 5:44:12 PM czhower
Name changes
Rev 1.30 2004.02.03 2:12:18 PM czhower
$I path change
Rev 1.29 1/25/2004 3:11:36 PM JPMugaas
SASL Interface reworked to make it easier for developers to use.
SSL and SASL reenabled components.
Rev 1.28 1/21/2004 3:27:06 PM JPMugaas
InitComponent
Rev 1.27 1/12/04 12:22:40 PM RLebeau
Updated RetrieveMailboxSize() and RetrieveMsgSize() to support responses that
contain additional data after the octet values.
Rev 1.26 22/12/2003 00:45:12 CCostelloe
.NET fixes
Rev 1.25 10/19/2003 5:42:36 PM DSiders
Added localization comments.
Rev 1.24 10/11/2003 7:14:34 PM BGooijen
Changed IdCompilerDefines.inc path
Rev 1.23 10/10/2003 11:39:40 PM BGooijen
Compiles in DotNet now
Rev 1.22 6/15/2003 01:17:10 PM JPMugaas
Intermediate class no longer used. We use the SASL functionality right from
the TIdSASLList.
Rev 1.21 6/4/2003 04:10:36 PM JPMugaas
Removed hacked GetInternelResponse.
Updated to use Kudzu's new string reply code.
Rev 1.20 5/26/2003 04:28:16 PM JPMugaas
Removed GenerateReply and ParseResponse calls because those functions are
being removed.
Rev 1.19 5/26/2003 12:23:58 PM JPMugaas
Rev 1.18 5/25/2003 03:54:46 AM JPMugaas
Rev 1.17 5/25/2003 03:45:56 AM JPMugaas
Rev 1.16 5/22/2003 05:27:52 PM JPMugaas
Rev 1.16 5/20/2003 02:29:42 PM JPMugaas
Updated with POP3Reply object.
Rev 1.15 5/10/2003 10:10:46 PM JPMugaas
Bug fixes.
Rev 1.14 5/8/2003 08:44:16 PM JPMugaas
Moved some SASL authentication code down to an anscestor for reuse. WIll
clean up soon.
Rev 1.13 5/8/2003 03:18:14 PM JPMugaas
Flattened ou the SASL authentication API, made a custom descendant of SASL
enabled TIdMessageClient classes.
Rev 1.12 5/8/2003 11:28:10 AM JPMugaas
Moved feature negoation properties down to the ExplicitTLSClient level as
feature negotiation goes hand in hand with explicit TLS support.
Rev 1.11 5/8/2003 03:03:00 AM JPMugaas
Fixed a problem with SASL. It turns out that what was being processed with
something from a previous command. It also turned out that some charactors
were being removed from the SASL processing when they shouldn't have been.
Rev 1.10 5/8/2003 02:18:08 AM JPMugaas
Fixed an AV in IdPOP3 with SASL list on forms. Made exceptions for SASL
mechanisms missing more consistant, made IdPOP3 support feature feature
negotiation, and consolidated some duplicate code.
Rev 1.9 5/7/2003 04:58:34 AM JPMugaas
We now use the initial greeting message from the server when calculating the
parameter for the APOP command. Note that we were calling CAPA before APOP
and that could throw things off.
Rev 1.8 4/5/2003 02:06:24 PM JPMugaas
TLS handshake itself can now be handled.
Rev 1.7 3/27/2003 05:46:40 AM JPMugaas
Updated framework with an event if the TLS negotiation command fails.
Cleaned up some duplicate code in the clients.
Rev 1.6 3/19/2003 08:53:40 PM JPMugaas
Now should work with new framework.
Rev 1.5 3/17/2003 02:25:26 PM JPMugaas
Updated to use new TLS framework. Now can require that users use TLS. Note
that this setting create an incompatiability with Norton AntiVirus because
that does act as a "man in the middle" when intercepting E-Mail for virus
scanning.
Rev 1.4 3/13/2003 09:49:26 AM JPMugaas
Now uses an abstract SSL base class instead of OpenSSL so 3rd-party vendors
can plug-in their products.
Rev 1.3 2/24/2003 09:27:58 PM JPMugaas
Rev 1.2 12/15/2002 04:27:10 PM JPMugaas
POP3 now compiles and works in Indy 10.
Rev 1.1 12-15-2002 12:57:40 BGooijen
Added Top-command
Rev 1.0 11/13/2002 07:58:22 AM JPMugaas
2002-08-18 - J. Berg
- implement SASL, add CAPA and STLS
02 August 2002 - A. Neillans
- Bug fix:
[ 574171 ] TIdMessage not cleared before a retreive
11-10-2001 - J. Peter Mugaas
Added suggested code from Andrew P.Rybin that does the following:
-APOP Authentication Support
-unrecognized text header now displayed in exception message
-GetUIDL method
2001-AUG-31 DSiders
Changed TIdPOP3.Connect to use ATimeout when calling
inherited Connect.
2000-SEPT-28 SG
Added GetUIDL as from code by
2000-MAY-10 HH
Added RetrieveMailBoxSize and renamed RetrieveSize to RetrieveMsgSize.
Finished Connect.
2000-MARCH-03 HH
Converted to Indy
}
unit IdPOP3;
{ POP 3 (Post Office Protocol Version 3) }
interface
{$i IdCompilerDefines.inc}
uses
Classes,
IdAssignedNumbers,
IdGlobal,
IdException,
IdExplicitTLSClientServerBase,
IdGlobalProtocols,
IdMessage,
IdMessageClient,
IdReply,
IdSASL,
IdSASLCollection,
IdBaseComponent,
IdUserPassProvider;
type
TIdPOP3AuthenticationType = (patUserPass, patAPOP, patSASL);
const
DEF_POP3USE_IMPLICIT_TLS = False;
DEF_ATYPE = patUserPass;
type
TIdPOP3 = class(TIdMessageClient)
protected
FAuthType : TIdPOP3AuthenticationType;
FAutoLogin: Boolean;
FAPOPToken : String;
FHasAPOP: Boolean;
FHasCAPA: Boolean;
FSASLMechanisms : TIdSASLEntries;
//
function GetReplyClass:TIdReplyClass; override;
function GetSupportsTLS: Boolean; override;
procedure SetSASLMechanisms(AValue: TIdSASLEntries);
procedure InitComponent; override;
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
public
function CheckMessages: Integer;
procedure Connect; override;
procedure Login; virtual;
destructor Destroy; override;
function Delete(const MsgNum: Integer): Boolean;
procedure DisconnectNotifyPeer; override;
procedure KeepAlive;
function List(const ADest: TStrings; const AMsgNum: Integer = -1): Boolean;
procedure ParseLIST(ALine: String; var VMsgNum, VMsgSize: Integer);
procedure ParseUIDL(ALine: String; var VMsgNum: Integer; var VUidl: String);
function Reset: Boolean;
function Retrieve(const MsgNum: Integer; AMsg: TIdMessage): Boolean;
function RetrieveHeader(const MsgNum: Integer; AMsg: TIdMessage): Boolean;
function RetrieveMsgSize(const MsgNum: Integer): Integer;
function RetrieveMailBoxSize: Int64;
function RetrieveRaw(const aMsgNo: Integer; const aDest: TStrings): boolean; overload;
function RetrieveRaw(const aMsgNo: Integer; const aDest: TStream): boolean; overload;
function RetrieveStats(var VMsgCount: Integer; var VMailBoxSize: Int64): Boolean;
function UIDL(const ADest: TStrings; const AMsgNum: Integer = -1): Boolean;
function Top(const AMsgNum: Integer; const ADest: TStrings; const AMaxLines: Integer = 0): boolean;
function CAPA: Boolean;
property HasAPOP: boolean read FHasAPOP;
property HasCAPA: boolean read FHasCAPA;
published
property AuthType : TIdPOP3AuthenticationType read FAuthType write FAuthType default DEF_ATYPE;
property AutoLogin: Boolean read FAutoLogin write FAutoLogin;
property Host;
property Username;
property UseTLS;
property Password;
property Port default IdPORT_POP3;
property SASLMechanisms : TIdSASLEntries read FSASLMechanisms write SetSASLMechanisms;
end;
type
EIdPOP3Exception = class(EIdException);
EIdDoesNotSupportAPOP = class(EIdPOP3Exception);
EIdUnrecognizedReply = class(EIdPOP3Exception);
implementation
uses
IdFIPS,
IdHash,
IdHashMessageDigest,
IdTCPConnection,
IdSSL,
IdResourceStringsProtocols,
IdReplyPOP3,
IdCoderMIME,
SysUtils;
{ TIdPOP3 }
function TIdPOP3.CheckMessages: Integer;
var
LIgnore: Int64;
begin
// RLebeau: for backwards compatibility, raise an exception if STAT fails
if not RetrieveStats(Result, LIgnore) then begin
RaiseExceptionForLastCmdResult;
end;
// Only gets here if exception is not raised
end;
procedure TIdPOP3.Login;
var
S: String;
LMD5: TIdHashMessageDigest5;
begin
if UseTLS in ExplicitTLSVals then begin
if SupportsTLS then begin
if SendCmd('STLS','') = ST_OK then begin {Do not translate}
TLSHandshake;
end else begin
ProcessTLSNegCmdFailed;
end;
end else begin
ProcessTLSNotAvail;
end;
end;
case FAuthType of
patAPOP: //APR
begin
if FHasAPOP then begin
CheckMD5Permitted;
LMD5 := TIdHashMessageDigest5.Create;
try
S := LowerCase(LMD5.HashStringAsHex(FAPOPToken+Password));
finally
LMD5.Free;
end;//try
SendCmd('APOP ' + Username + ' ' + S, ST_OK); {Do not Localize}
end else begin
raise EIdDoesNotSupportAPOP.Create(RSPOP3ServerDoNotSupportAPOP);
end;
end;
patUserPass:
begin //classic method
SendCmd('USER ' + Username, ST_OK); {Do not Localize}
SendCmd('PASS ' + Password, ST_OK); {Do not Localize}
end;//if APOP
patSASL:
begin
// 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, until we can handle that
// descrepency better, we will simply disable Initial-Response for now.
FSASLMechanisms.LoginSASL('AUTH', FHost, IdGSKSSN_pop, [ST_OK], [ST_SASLCONTINUE], Self, Capabilities, 'SASL', False); {do not localize}
end;
end;
end;
procedure TIdPOP3.InitComponent;
begin
inherited;
FAutoLogin := True;
FSASLMechanisms := TIdSASLEntries.Create(Self);
FRegularProtPort := IdPORT_POP3;
FImplicitTLSProtPort := IdPORT_POP3S;
Port := IdPORT_POP3;
FAuthType := DEF_ATYPE;
end;
function TIdPOP3.Delete(const MsgNum: Integer): Boolean;
begin
Result := (SendCmd('DELE ' + IntToStr(MsgNum), '') = ST_OK); {do not localize}
end;
procedure TIdPOP3.DisconnectNotifyPeer;
begin
inherited;
SendCmd('QUIT', ST_OK); {do not localize}
end;
function TIdPOP3.GetReplyClass:TIdReplyClass;
begin
Result := TIdReplyPOP3;
end;
procedure TIdPOP3.KeepAlive;
begin
SendCmd('NOOP', ST_OK); {Do not Localize}
end;
function TIdPOP3.Reset: Boolean;
begin
Result := (SendCmd('RSET', '') = ST_OK); {Do not Localize}
end;
function TIdPOP3.RetrieveRaw(const aMsgNo: Integer; const aDest: TStrings): boolean;
var
LEncoding: IIdTextEncoding;
begin
Result := (SendCmd('RETR ' + IntToStr(aMsgNo), '') = ST_OK); {Do not Localize}
if Result then begin
LEncoding := IndyTextEncoding_8Bit;
IOHandler.Capture(aDest, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
end;
end;
function TIdPOP3.RetrieveRaw(const aMsgNo: Integer; const aDest: TStream): boolean;
var
LEncoding: IIdTextEncoding;
begin
Result := (SendCmd('RETR ' + IntToStr(aMsgNo), '') = ST_OK); {Do not Localize}
if Result then begin
LEncoding := IndyTextEncoding_8Bit;
IOHandler.Capture(aDest, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
end;
end;
function TIdPOP3.Retrieve(const MsgNum: Integer; AMsg: TIdMessage): Boolean;
begin
Result := (SendCmd('RETR ' + IntToStr(MsgNum), '') = ST_OK); {Do not Localize}
if Result then begin
AMsg.Clear;
// This is because of a bug in Exchange? with empty messages. See comment in ReceiveHeader
if ReceiveHeader(AMsg) = '' then begin
// Only retreive the body if we do not already have a full RFC
ReceiveBody(AMsg);
end;
end;
end;
function TIdPOP3.RetrieveHeader(const MsgNum: Integer; AMsg: TIdMessage): Boolean;
begin
// Result := False;
AMsg.Clear;
SendCmd('TOP ' + IntToStr(MsgNum) + ' 0', ST_OK); {Do not Localize}
// Only gets here if no exception is raised
ReceiveHeader(AMsg,'.');
Result := True;
end;
function TIdPOP3.RetrieveMailBoxSize: Int64;
var
LIgnore: Integer;
begin
// RLebeau: for backwards compatibility, return -1 if STAT fails
try
if not RetrieveStats(LIgnore, Result) then begin
RaiseExceptionForLastCmdResult;
end;
except
Result := -1;
end;
end;
function TIdPOP3.RetrieveMsgSize(const MsgNum: Integer): Integer;
var
s: string;
begin
Result := -1;
// Returns the size of the message. if an error ocurrs, returns -1.
SendCmd('LIST ' + IntToStr(MsgNum), ST_OK); {Do not Localize}
s := LastCmdResult.Text[0];
if Length(s) > 0 then begin
// RL - ignore the message number, grab just the octets,
// and ignore everything else that may be present
Fetch(s);
Result := IndyStrToInt(Fetch(s), -1);
end;
end;
function TIdPOP3.RetrieveStats(var VMsgCount: Integer; var VMailBoxSize: Int64): Boolean;
var
s: string;
begin
VMsgCount := 0;
VMailBoxSize := 0;
Result := (SendCmd('STAT', '') = ST_OK); {Do not Localize}
if Result then begin
s := LastCmdResult.Text[0];
if Length(s) > 0 then begin
VMsgCount := IndyStrToInt(Fetch(s));
VMailBoxSize := IndyStrToInt64(Fetch(s));
end;
end;
end;
function TIdPOP3.List(const ADest: TStrings; const AMsgNum: Integer = -1): Boolean;
var
LEncoding: IIdTextEncoding;
begin
if AMsgNum >= 0 then begin
Result := (SendCmd('LIST ' + IntToStr(AMsgNum), '') = ST_OK); {Do not Localize}
if Result then begin
ADest.Assign(LastCmdResult.Text);
end;
end
else begin
Result := (SendCmd('LIST', '') = ST_OK); {Do not Localize}
if Result then begin
LEncoding := IndyTextEncoding_8Bit;
IOHandler.Capture(ADest, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
end;
end;
end;
procedure TIdPOP3.ParseLIST(ALine: String; var VMsgNum, VMsgSize: Integer);
begin
VMsgNum := IndyStrToInt(Fetch(ALine), -1);
VMsgSize := IndyStrToInt(Fetch(ALine), -1);
end;
function TIdPOP3.UIDL(const ADest: TStrings; const AMsgNum: Integer = -1): Boolean;
var
LEncoding: IIdTextEncoding;
begin
if AMsgNum >= 0 then begin
Result := (SendCmd('UIDL ' + IntToStr(AMsgNum), '') = ST_OK); {Do not Localize}
if Result then begin
ADest.Assign(LastCmdResult.Text);
end;
end
else begin
Result := (SendCmd('UIDL', '') = ST_OK); {Do not Localize}
if Result then begin
LEncoding := IndyTextEncoding_8Bit;
IOHandler.Capture(ADest, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
end;
end;
end;
procedure TIdPOP3.ParseUIDL(ALine: String; var VMsgNum: Integer; var VUidl: String);
begin
VMsgNum := IndyStrToInt(Fetch(ALine), -1);
VUidl := Fetch(ALine);
end;
function TIdPOP3.Top(const AMsgNum: Integer; const ADest: TStrings; const AMaxLines: Integer = 0): boolean;
var
Cmd: String;
LEncoding: IIdTextEncoding;
begin
Cmd := 'TOP ' + IntToStr(AMsgNum); {Do not Localize}
if AMaxLines <> 0 then begin
Cmd := Cmd + ' ' + IntToStr(AMaxLines); {Do not Localize}
end;
Result := (SendCmd(Cmd,'') = ST_OK);
if Result then begin
LEncoding := IndyTextEncoding_8Bit;
IOHandler.Capture(ADest, LEncoding{$IFDEF STRING_IS_ANSI}, LEncoding{$ENDIF});
end;
end;
destructor TIdPOP3.Destroy;
begin
FreeAndNil(FSASLMechanisms);
inherited;
end;
function TIdPOP3.CAPA: Boolean;
begin
FCapabilities.Clear;
Result := (SendCmd('CAPA','') = ST_OK); {Do not Localize}
if Result then begin
IOHandler.Capture(FCapabilities);
end;
// RLebeau - do not delete here! The +OK reply line is handled
// by SendCmd() above. Deleting here is removing an actual capability entry.
{
if FCapabilities.Count >0 then
begin
//dete the initial OK reply line
FCapabilities.Delete(0);
end;
FHasCapa := Result;
}
FHasCapa := (FCapabilities.Count > 0);
// ParseCapaReply(FCapabilities,'SASL');
end;
procedure TIdPOP3.Notification(AComponent: TComponent; Operation: TOperation);
begin
if (Operation = opRemove) and (FSASLMechanisms <> nil) then begin
FSASLMechanisms.RemoveByComp(AComponent);
end;
inherited Notification(AComponent,Operation);
end;
function TIdPOP3.GetSupportsTLS: Boolean;
begin
Result := ( FCapabilities.IndexOf('STLS') > -1 ); //do not localize
end;
procedure TIdPOP3.SetSASLMechanisms(AValue: TIdSASLEntries);
begin
FSASLMechanisms.Assign(AValue);
end;
procedure TIdPOP3.Connect;
var
S: String;
I: Integer;
begin
FHasAPOP := False;
FHasCAPA := False;
FAPOPToken := '';
if (IOHandler is TIdSSLIOHandlerSocketBase) then begin
(IOHandler as TIdSSLIOHandlerSocketBase).PassThrough := (FUseTLS <> utUseImplicitTLS);
end;
inherited Connect;
try
GetResponse(ST_OK);
// the initial greeting text is needed to determine APOP availability
S := LastCmdResult.Text.Strings[0]; //read response
I := Pos('<', S); {Do not Localize}
if i > 0 then begin
S := Copy(S, I, MaxInt); //?: System.Delete(S,1,i-1);
I := Pos('>', S); {Do not Localize}
if I > 0 then begin
FAPOPToken := Copy(S, 1, I);
end;
end;
FHasAPOP := (Length(FAPOPToken) > 0);
CAPA;
if FAutoLogin then begin
Login;
end;
except
Disconnect(False);
raise;
end;
end;
end.