IdPOP4Server.pas 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. {
  2. $Project$
  3. $Workfile$
  4. $Revision$
  5. $DateUTC$
  6. $Id$
  7. This file is part of the Indy (Internet Direct) project, and is offered
  8. under the dual-licensing agreement described on the Indy website.
  9. (http://www.indyproject.org/)
  10. Copyright:
  11. (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
  12. }
  13. {
  14. $Log$
  15. }
  16. unit IdPOP4Server;
  17. {
  18. This is an experimental proposal based on Kudzu's idea.
  19. }
  20. interface
  21. uses
  22. Classes,
  23. IdAssignedNumbers,
  24. IdCmdTCPServer,
  25. IdCommandHandlers,
  26. IdContext,
  27. IdCustomTCPServer, //for TIdServerContext
  28. IdEMailAddress,
  29. IdException,
  30. IdExplicitTLSClientServerBase,
  31. IdReply,
  32. IdReplyRFC,
  33. IdReplySMTP,
  34. IdTCPConnection,
  35. IdTCPServer,
  36. IdYarn,
  37. IdStack;
  38. const
  39. POP4_PORT = 1970; //my birthday
  40. type
  41. TIdPOP4ServerContext = class;
  42. TOnUserLoginEvent = procedure(ASender: TIdPOP4ServerContext; const AUsername, APassword: string;
  43. var VAuthenticated: Boolean) of object;
  44. TIdPOP4ServerState = (Auth, Trans, Update);
  45. TIdPOP4Server = class(TIdExplicitTLSServer)
  46. protected
  47. FOnUserLogin : TOnUserLoginEvent;
  48. procedure CmdBadSequenceError(ASender: TIdCommand);
  49. procedure CmdAuthFailed(ASender: TIdCommand);
  50. procedure CmdSyntaxError(ASender: TIdCommand); overload;
  51. procedure CmdSyntaxError(AContext: TIdContext; ALine: string;
  52. const AReply: TIdReply=nil); overload;
  53. procedure CmdMustUseTLS(ASender: TIdCommand);
  54. procedure InvalidSyntax(ASender: TIdCommand);
  55. function DoAuthLogin(ASender: TIdCommand; const Login:string): Boolean;
  56. procedure InitializeCommandHandlers; override;
  57. procedure CommandAUTH(ASender: TIdCommand);
  58. procedure CommandCAPA(ASender: TIdCommand);
  59. procedure CommandSTARTTLS(ASender : TIdCommand);
  60. procedure DoReplyUnknownCommand(AContext: TIdContext; ALine: string); override;
  61. public
  62. constructor Create(AOwner: TComponent); override;
  63. published
  64. property OnUserLogin : TOnUserLoginEvent read FOnUserLogin write FOnUserLogin;
  65. property DefaultPort default IdPORT_POP3;
  66. end;
  67. TIdPOP4ServerContext = class(TIdServerContext)
  68. protected
  69. FPipeLining : Boolean;
  70. FState :TIdPOP4ServerState;
  71. FUser,
  72. FPassword : String;
  73. function GetUsingTLS: boolean;
  74. procedure SetPipeLining(const AValue: Boolean);
  75. public
  76. constructor Create(
  77. AConnection: TIdTCPConnection;
  78. AYarn: TIdYarn;
  79. AList: TThreadList = nil
  80. ); override;
  81. procedure CheckPipeLine;
  82. property State : TIdPOP4ServerState read FState write FState;
  83. property Username : String read fUser write fUser;
  84. property Password : String read fPassword write fPassword;
  85. property UsingTLS:boolean read GetUsingTLS;
  86. property PipeLining : Boolean read FPipeLining write SetPipeLining;
  87. end;
  88. implementation
  89. uses
  90. IdResourceStringsProtocols, IdCoderMIME, IdGlobal, IdGlobalProtocols, IdSSL, SysUtils;
  91. { TIdPOP4Server }
  92. constructor TIdPOP4Server.Create(AOwner: TComponent);
  93. begin
  94. inherited Create(AOwner);
  95. FContextClass := TIdPOP4ServerContext;
  96. FRegularProtPort := POP4_PORT;
  97. DefaultPort := POP4_PORT;
  98. Self.Greeting.Code := '200';
  99. Self.Greeting.Text.Text := 'Your text goes here!!!';
  100. end;
  101. procedure TIdPOP4Server.InitializeCommandHandlers;
  102. var
  103. LCmd : TIdCommandHandler;
  104. begin
  105. inherited;
  106. LCmd := CommandHandlers.Add;
  107. LCmd.Command := 'CAPA'; {do not localize}
  108. LCmd.NormalReply.Code := '211';
  109. LCmd.OnCommand := CommandCAPA;
  110. LCmd.Description.Text := 'Syntax: CAPA (get capabilities)';
  111. //QUIT <CRLF>
  112. LCmd := CommandHandlers.Add;
  113. LCmd.Command := 'QUIT'; {Do not Localize}
  114. LCmd.Disconnect := True;
  115. LCmd.NormalReply.SetReply(221,RSFTPQuitGoodby); {Do not Localize}
  116. LCmd.Description.Text := 'Syntax: QUIT (terminate service)'; {do not localize}
  117. end;
  118. procedure TIdPOP4Server.CommandCAPA(ASender: TIdCommand);
  119. begin
  120. ASender.Reply.SetReply(211, RSPOP3SvrCapaList);
  121. ASender.SendReply;
  122. If (IOHandler is TIdServerIOHandlerSSLBase) and
  123. (FUseTLS in ExplicitTLSVals) Then
  124. begin
  125. ASender.Context.Connection.IOHandler.WriteLn('STARTTLS'); {do not localize}
  126. end;
  127. if Assigned(FOnUserLogin) then
  128. begin
  129. ASender.Context.Connection.IOHandler.WriteLn('AUTH LOGIN'); {Do not Localize}
  130. end;
  131. ASender.Context.Connection.IOHandler.WriteLn('.');
  132. TIdPOP4ServerContext(ASender.Context).CheckPipeLine;
  133. end;
  134. procedure TIdPOP4Server.CommandSTARTTLS(ASender: TIdCommand);
  135. begin
  136. if (ASender.Context.Connection.IOHandler is TIdSSLIOHandlerSocketBase) and (FUseTLS in ExplicitTLSVals) then begin
  137. if TIdPOP4ServerContext(ASender.Context).UsingTLS then begin // we are already using TLS
  138. InvalidSyntax(ASender);
  139. Exit;
  140. end;
  141. if TIdPOP4ServerContext(ASender.Context).State <> Auth then begin //STLS only allowed in auth-state
  142. ASender.Reply.SetReply(501, RSPOP3SvrNotInThisState); {Do not Localize}
  143. Exit;
  144. end;
  145. ASender.Reply.SetReply(220, RSPOP3SvrBeginTLSNegotiation);
  146. ASender.SendReply;
  147. //You should never pipeline STARTTLS
  148. TIdPOP4ServerContext(ASender.Context).PipeLining := False;
  149. (ASender.Context.Connection.IOHandler as TIdSSLIOHandlerSocketBase).PassThrough := False;
  150. end else begin
  151. CmdSyntaxError(ASender);
  152. end;
  153. end;
  154. procedure TIdPOP4Server.CmdBadSequenceError(ASender: TIdCommand);
  155. begin
  156. ASender.Reply.SetReply(503, RSSMTPSvrBadSequence);
  157. end;
  158. procedure TIdPOP4Server.CmdSyntaxError(AContext: TIdContext; ALine: string; const AReply: TIdReply = nil);
  159. var
  160. LTmp : String;
  161. LReply : TIdReply;
  162. begin
  163. //First make the first word uppercase
  164. LTmp := UpCaseFirstWord(ALine);
  165. if Assigned(AReply) then begin
  166. LReply := AReply;
  167. end else begin
  168. LReply := FReplyClass.Create(nil, ReplyTexts);
  169. end;
  170. try
  171. if not Assigned(AReply) then begin
  172. LReply.Assign(ReplyUnknownCommand);
  173. end;
  174. LReply.SetReply(500, Format(RSFTPCmdNotRecognized, [LTmp]));
  175. AContext.Connection.IOHandler.Write(LReply.FormattedReply);
  176. finally
  177. if not Assigned(AReply) then begin
  178. LReply.Free;
  179. end;
  180. end;
  181. end;
  182. procedure TIdPOP4Server.CmdSyntaxError(ASender: TIdCommand);
  183. begin
  184. CmdSyntaxError(ASender.Context, ASender.RawLine, FReplyUnknownCommand );
  185. ASender.PerformReply := False;
  186. end;
  187. procedure TIdPOP4Server.CmdMustUseTLS(ASender: TIdCommand);
  188. begin
  189. ASender.Reply.SetReply(530,RSSMTPSvrReqSTARTTLS);
  190. end;
  191. procedure TIdPOP4Server.InvalidSyntax(ASender: TIdCommand);
  192. begin
  193. ASender.Reply.SetReply( 501,RSPOP3SvrInvalidSyntax);
  194. end;
  195. procedure TIdPOP4Server.DoReplyUnknownCommand(AContext: TIdContext;
  196. ALine: string);
  197. begin
  198. CmdSyntaxError(AContext,ALine);
  199. end;
  200. function TIdPOP4Server.DoAuthLogin(ASender: TIdCommand;
  201. const Login: string): Boolean;
  202. var
  203. S: string;
  204. LUsername, LPassword: string;
  205. LAuthFailed: Boolean;
  206. LAccepted: Boolean;
  207. LS : TIdPOP4ServerContext;
  208. begin
  209. LS := ASender.Context as TIdPOP4ServerContext;
  210. Result := False;
  211. LAuthFailed := False;
  212. TIdPOP4ServerContext(ASender.Context).PipeLining := False;
  213. if UpperCase(Login) = 'LOGIN' then {Do not Localize}
  214. begin // LOGIN USING THE LOGIN AUTH - BASE64 ENCODED
  215. s := TIdEncoderMIME.EncodeString('Username:'); {Do not Localize}
  216. // s := SendRequest( '334 ' + s ); {Do not Localize}
  217. ASender.Reply.SetReply (334, s); {Do not Localize}
  218. ASender.SendReply;
  219. s := Trim(ASender.Context.Connection.IOHandler.ReadLn);
  220. if s <> '' then {Do not Localize}
  221. begin
  222. try
  223. s := TIdDecoderMIME.DecodeString(s);
  224. LUsername := s;
  225. // What? Endcode this string literal?
  226. s := TIdEncoderMIME.EncodeString('Password:'); {Do not Localize}
  227. // s := SendRequest( '334 ' + s ); {Do not Localize}
  228. ASender.Reply.SetReply(334, s); {Do not Localize}
  229. ASender.SendReply;
  230. s := Trim(ASender.Context.Connection.IOHandler.ReadLn);
  231. if s = '' then
  232. begin
  233. LAuthFailed := True;
  234. end
  235. else
  236. begin
  237. LPassword := TIdDecoderMIME.DecodeString(s);
  238. end;
  239. // when TIdDecoderMime.DecodeString(s) raise a exception,catch it and set AuthFailed as true
  240. except
  241. LAuthFailed := true;
  242. end;
  243. end
  244. else
  245. begin
  246. LAuthFailed := True;
  247. end;
  248. end;
  249. // Add other login units here
  250. if LAuthFailed then
  251. begin
  252. Result := False;
  253. CmdAuthFailed(ASender);
  254. end
  255. else
  256. begin
  257. LAccepted := False;
  258. if Assigned(fOnUserLogin) then
  259. begin
  260. fOnUserLogin(LS, LUsername, LPassword, LAccepted);
  261. end
  262. else
  263. begin
  264. LAccepted := True;
  265. end;
  266. if LAccepted then
  267. begin
  268. LS.FState := Trans;
  269. end;
  270. LS.Username := LUsername;
  271. if not LAccepted then
  272. begin
  273. CmdAuthFailed(ASender);
  274. end
  275. else
  276. begin
  277. ASender.Reply.SetReply(235,' welcome ' + Trim(LUsername)); {Do not Localize}
  278. ASender.SendReply;
  279. end;
  280. end;
  281. end;
  282. procedure TIdPOP4Server.CmdAuthFailed(ASender: TIdCommand);
  283. begin
  284. ASender.Reply.SetReply(535,RSSMTPSvrAuthFailed);
  285. ASender.SendReply;
  286. end;
  287. procedure TIdPOP4Server.CommandAUTH(ASender: TIdCommand);
  288. var
  289. Login: string;
  290. begin
  291. //Note you can not use PIPELINING with AUTH
  292. TIdPOP4ServerContext(ASender.Context).PipeLining := False;
  293. if not ((FUseTLS=utUseRequireTLS) and not TIdSMTPServerContext(ASender.Context).UsingTLS) then
  294. begin
  295. Login := ASender.UnparsedParams;
  296. DoAuthLogin(ASender, Login);
  297. end
  298. else
  299. begin
  300. MustUseTLS(ASender);
  301. end;
  302. end;
  303. { TIdPOP4ServerContext }
  304. procedure TIdPOP4ServerContext.CheckPipeLine;
  305. begin
  306. if Connection.IOHandler.InputBufferIsEmpty=False then
  307. begin
  308. PipeLining := True;
  309. end;
  310. end;
  311. constructor TIdPOP4ServerContext.Create(AConnection: TIdTCPConnection;
  312. AYarn: TIdYarn; AList: TThreadList);
  313. begin
  314. inherited;
  315. FState := Auth;
  316. FUser := '';
  317. fPassword := '';
  318. end;
  319. function TIdPOP4ServerContext.GetUsingTLS: boolean;
  320. begin
  321. Result := Connection.IOHandler is TIdSSLIOHandlerSocketBase;
  322. if Result then begin
  323. Result := not TIdSSLIOHandlerSocketBase(Connection.IOHandler).PassThrough;
  324. end;
  325. end;
  326. procedure TIdPOP4ServerContext.SetPipeLining(const AValue: Boolean);
  327. begin
  328. if AValue and (not PipeLining) then
  329. begin
  330. Connection.IOHandler.WriteBufferOpen;
  331. end
  332. else if (not AValue) and PipeLining then
  333. begin
  334. Connection.IOHandler.WriteBufferClose;
  335. end;
  336. FPipeLining := AValue;
  337. end;
  338. end.