restemplate/indy/Protocols/IdPOP4Server.pas

372 lines
10 KiB
Plaintext

{
$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$
}
unit IdPOP4Server;
{
This is an experimental proposal based on Kudzu's idea.
}
interface
uses
Classes,
IdAssignedNumbers,
IdCmdTCPServer,
IdCommandHandlers,
IdContext,
IdCustomTCPServer, //for TIdServerContext
IdEMailAddress,
IdException,
IdExplicitTLSClientServerBase,
IdReply,
IdReplyRFC,
IdReplySMTP,
IdTCPConnection,
IdTCPServer,
IdYarn,
IdStack;
const
POP4_PORT = 1970; //my birthday
type
TIdPOP4ServerContext = class;
TOnUserLoginEvent = procedure(ASender: TIdPOP4ServerContext; const AUsername, APassword: string;
var VAuthenticated: Boolean) of object;
TIdPOP4ServerState = (Auth, Trans, Update);
TIdPOP4Server = class(TIdExplicitTLSServer)
protected
FOnUserLogin : TOnUserLoginEvent;
procedure CmdBadSequenceError(ASender: TIdCommand);
procedure CmdAuthFailed(ASender: TIdCommand);
procedure CmdSyntaxError(ASender: TIdCommand); overload;
procedure CmdSyntaxError(AContext: TIdContext; ALine: string;
const AReply: TIdReply=nil); overload;
procedure CmdMustUseTLS(ASender: TIdCommand);
procedure InvalidSyntax(ASender: TIdCommand);
function DoAuthLogin(ASender: TIdCommand; const Login:string): Boolean;
procedure InitComponent; override;
procedure InitializeCommandHandlers; override;
procedure CommandAUTH(ASender: TIdCommand);
procedure CommandCAPA(ASender: TIdCommand);
procedure CommandSTARTTLS(ASender : TIdCommand);
procedure DoReplyUnknownCommand(AContext: TIdContext; ALine: string); override;
published
property OnUserLogin : TOnUserLoginEvent read FOnUserLogin write FOnUserLogin;
property DefaultPort default IdPORT_POP3;
end;
TIdPOP4ServerContext = class(TIdServerContext)
protected
FPipeLining : Boolean;
FState :TIdPOP4ServerState;
FUser,
FPassword : String;
function GetUsingTLS: boolean;
procedure SetPipeLining(const AValue: Boolean);
public
constructor Create(
AConnection: TIdTCPConnection;
AYarn: TIdYarn;
AList: TThreadList = nil
); override;
procedure CheckPipeLine;
property State : TIdPOP4ServerState read FState write FState;
property Username : String read fUser write fUser;
property Password : String read fPassword write fPassword;
property UsingTLS:boolean read GetUsingTLS;
property PipeLining : Boolean read FPipeLining write SetPipeLining;
end;
implementation
uses IdResourceStringsProtocols, IdCoderMIME, IdGlobal, IdGlobalProtocols, IdSSL, SysUtils;
{ TIdPOP4Server }
procedure TIdPOP4Server.InitializeCommandHandlers;
var LCmd : TIdCommandHandler;
begin
inherited;
LCmd := CommandHandlers.Add;
LCmd.Command := 'CAPA'; {do not localize}
LCmd.NormalReply.Code := '211';
LCmd.OnCommand := CommandCAPA;
LCmd.Description.Text := 'Syntax: CAPA (get capabilities)';
//QUIT <CRLF>
LCmd := CommandHandlers.Add;
LCmd.Command := 'QUIT'; {Do not Localize}
LCmd.Disconnect := True;
LCmd.NormalReply.SetReply(221,RSFTPQuitGoodby); {Do not Localize}
LCmd.Description.Text := 'Syntax: QUIT (terminate service)'; {do not localize}
end;
procedure TIdPOP4Server.InitComponent;
begin
inherited;
FContextClass := TIdPOP4ServerContext;
FRegularProtPort := POP4_PORT;
DefaultPort := POP4_PORT;
Self.Greeting.Code := '200';
Self.Greeting.Text.Text := 'Your text goes here!!!';
end;
procedure TIdPOP4Server.CommandCAPA(ASender: TIdCommand);
begin
ASender.Reply.SetReply(211, RSPOP3SvrCapaList);
ASender.SendReply;
If (IOHandler is TIdServerIOHandlerSSLBase) and
(FUseTLS in ExplicitTLSVals) Then
begin
ASender.Context.Connection.IOHandler.WriteLn('STARTTLS'); {do not localize}
end;
if Assigned(FOnUserLogin) then
begin
ASender.Context.Connection.IOHandler.WriteLn('AUTH LOGIN'); {Do not Localize}
end;
ASender.Context.Connection.IOHandler.WriteLn('.');
TIdPOP4ServerContext(ASender.Context).CheckPipeLine;
end;
procedure TIdPOP4Server.CommandSTARTTLS(ASender: TIdCommand);
var LIO : TIdSSLIOHandlerSocketBase;
begin
if (IOHandler is TIdServerIOHandlerSSLBase) and (FUseTLS in ExplicitTLSVals) then begin
if TIdPOP4ServerContext(ASender.Context).UsingTLS then begin // we are already using TLS
InvalidSyntax(ASender);
Exit;
end;
if TIdPOP4ServerContext(ASender.Context).State<>Auth then begin //STLS only allowed in auth-state
ASender.Reply.SetReply(501, RSPOP3SvrNotInThisState); {Do not Localize}
Exit;
end;
ASender.Reply.SetReply(220, RSPOP3SvrBeginTLSNegotiation);
//You should never pipeline STARTTLS
TIdPOP4ServerContext(ASender.Context).PipeLining := False;
LIO := ASender.Context.Connection.IOHandler as TIdSSLIOHandlerSocketBase;
LIO.Passthrough := False;
end else begin
CmdSyntaxError(ASender);
end;
end;
procedure TIdPOP4Server.CmdBadSequenceError(ASender: TIdCommand);
begin
ASender.Reply.SetReply(503, RSSMTPSvrBadSequence);
end;
procedure TIdPOP4Server.CmdSyntaxError(AContext: TIdContext; ALine: string; const AReply: TIdReply = nil);
var
LTmp : String;
LReply : TIdReply;
begin
//First make the first word uppercase
LTmp := UpCaseFirstWord(ALine);
try
if Assigned(AReply) then begin
LReply := AReply;
end else begin
LReply := FReplyClass.Create(nil, ReplyTexts);
LReply.Assign(ReplyUnknownCommand);
end;
LReply.SetReply(500, Sys.Format(RSFTPCmdNotRecognized, [LTmp]));
AContext.Connection.IOHandler.Write(LReply.FormattedReply);
finally
if not Assigned(AReply) then begin
Sys.FreeAndNil(LReply);
end;
end;
end;
procedure TIdPOP4Server.CmdSyntaxError(ASender: TIdCommand);
begin
CmdSyntaxError(ASender.Context, ASender.RawLine, FReplyUnknownCommand );
ASender.PerformReply := False;
end;
procedure TIdPOP4Server.CmdMustUseTLS(ASender: TIdCommand);
begin
ASender.Reply.SetReply(530,RSSMTPSvrReqSTARTTLS);
end;
procedure TIdPOP4Server.InvalidSyntax(ASender: TIdCommand);
begin
ASender.Reply.SetReply( 501,RSPOP3SvrInvalidSyntax);
end;
procedure TIdPOP4Server.DoReplyUnknownCommand(AContext: TIdContext;
ALine: string);
begin
CmdSyntaxError(AContext,ALine);
end;
function TIdPOP4Server.DoAuthLogin(ASender: TIdCommand;
const Login: string): Boolean;
var
S: string;
LUsername, LPassword: string;
LAuthFailed: Boolean;
LAccepted: Boolean;
LS : TIdPOP4ServerContext;
begin
LS := ASender.Context as TIdPOP4ServerContext;
Result := False;
LAuthFailed := False;
TIdPOP4ServerContext(ASender.Context).PipeLining := False;
if UpperCase(Login) = 'LOGIN' then {Do not Localize}
begin // LOGIN USING THE LOGIN AUTH - BASE64 ENCODED
s := TIdEncoderMIME.EncodeString('Username:'); {Do not Localize}
// s := SendRequest( '334 ' + s ); {Do not Localize}
ASender.Reply.SetReply (334, s); {Do not Localize}
ASender.SendReply;
s := Trim(ASender.Context.Connection.IOHandler.ReadLn);
if s <> '' then {Do not Localize}
begin
try
s := TIdDecoderMIME.DecodeString(s);
LUsername := s;
// What? Endcode this string literal?
s := TIdEncoderMIME.EncodeString('Password:'); {Do not Localize}
// s := SendRequest( '334 ' + s ); {Do not Localize}
ASender.Reply.SetReply(334, s); {Do not Localize}
ASender.SendReply;
s := Trim(ASender.Context.Connection.IOHandler.ReadLn);
if Length(s) = 0 then
begin
LAuthFailed := True;
end
else
begin
LPassword := TIdDecoderMIME.DecodeString(s);
end;
// when TIdDecoderMime.DecodeString(s) raise a exception,catch it and set AuthFailed as true
except
LAuthFailed := true;
end;
end
else
begin
LAuthFailed := True;
end;
end;
// Add other login units here
if LAuthFailed then
begin
Result := False;
CmdAuthFailed(ASender);
end
else
begin
LAccepted := False;
if Assigned(fOnUserLogin) then
begin
fOnUserLogin(LS, LUsername, LPassword, LAccepted);
end
else
begin
LAccepted := True;
end;
if LAccepted then
begin
LS.FState := Trans;
end;
LS.Username := LUsername;
if not LAccepted then
begin
CmdAuthFailed(ASender);
end
else
begin
ASender.Reply.SetReply(235,' welcome ' + Trim(LUsername)); {Do not Localize}
ASender.SendReply;
end;
end;
end;
procedure TIdPOP4Server.CmdAuthFailed(ASender: TIdCommand);
begin
ASender.Reply.SetReply(535,RSSMTPSvrAuthFailed);
ASender.SendReply;
end;
procedure TIdPOP4Server.CommandAUTH(ASender: TIdCommand);
var
Login: string;
begin
//Note you can not use PIPELINING with AUTH
TIdPOP4ServerContext(ASender.Context).PipeLining := False;
if not ((FUseTLS=utUseRequireTLS) and not TIdSMTPServerContext(ASender.Context).UsingTLS) then
begin
Login := ASender.UnparsedParams;
DoAuthLogin(ASender, Login);
end
else
begin
MustUseTLS(ASender);
end;
end;
{ TIdPOP4ServerContext }
procedure TIdPOP4ServerContext.CheckPipeLine;
begin
if Connection.IOHandler.InputBufferIsEmpty=False then
begin
PipeLining := True;
end;
end;
constructor TIdPOP4ServerContext.Create(AConnection: TIdTCPConnection;
AYarn: TIdYarn; AList: TThreadList);
begin
inherited;
FState := Auth;
FUser := '';
fPassword := '';
end;
function TIdPOP4ServerContext.GetUsingTLS: boolean;
begin
Result:=Connection.IOHandler is TIdSSLIOHandlerSocketBase;
if result then
begin
Result:=not TIdSSLIOHandlerSocketBase(Connection.IOHandler).PassThrough;
end;
end;
procedure TIdPOP4ServerContext.SetPipeLining(const AValue: Boolean);
begin
if AValue and (not PipeLining) then
begin
Connection.IOHandler.WriteBufferOpen;
end
else if (not AValue) and PipeLining then
begin
Connection.IOHandler.WriteBufferClose;
end;
FPipeLining := AValue;
end;
end.