IdPOP4Server.pas 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  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 InitComponent; override;
  57. procedure InitializeCommandHandlers; override;
  58. procedure CommandAUTH(ASender: TIdCommand);
  59. procedure CommandCAPA(ASender: TIdCommand);
  60. procedure CommandSTARTTLS(ASender : TIdCommand);
  61. procedure DoReplyUnknownCommand(AContext: TIdContext; ALine: string); override;
  62. published
  63. property OnUserLogin : TOnUserLoginEvent read FOnUserLogin write FOnUserLogin;
  64. property DefaultPort default IdPORT_POP3;
  65. end;
  66. TIdPOP4ServerContext = class(TIdServerContext)
  67. protected
  68. FPipeLining : Boolean;
  69. FState :TIdPOP4ServerState;
  70. FUser,
  71. FPassword : String;
  72. function GetUsingTLS: boolean;
  73. procedure SetPipeLining(const AValue: Boolean);
  74. public
  75. constructor Create(
  76. AConnection: TIdTCPConnection;
  77. AYarn: TIdYarn;
  78. AList: TThreadList = nil
  79. ); override;
  80. procedure CheckPipeLine;
  81. property State : TIdPOP4ServerState read FState write FState;
  82. property Username : String read fUser write fUser;
  83. property Password : String read fPassword write fPassword;
  84. property UsingTLS:boolean read GetUsingTLS;
  85. property PipeLining : Boolean read FPipeLining write SetPipeLining;
  86. end;
  87. implementation
  88. uses IdResourceStringsProtocols, IdCoderMIME, IdGlobal, IdGlobalProtocols, IdSSL, SysUtils;
  89. { TIdPOP4Server }
  90. procedure TIdPOP4Server.InitializeCommandHandlers;
  91. var LCmd : TIdCommandHandler;
  92. begin
  93. inherited;
  94. LCmd := CommandHandlers.Add;
  95. LCmd.Command := 'CAPA'; {do not localize}
  96. LCmd.NormalReply.Code := '211';
  97. LCmd.OnCommand := CommandCAPA;
  98. LCmd.Description.Text := 'Syntax: CAPA (get capabilities)';
  99. //QUIT <CRLF>
  100. LCmd := CommandHandlers.Add;
  101. LCmd.Command := 'QUIT'; {Do not Localize}
  102. LCmd.Disconnect := True;
  103. LCmd.NormalReply.SetReply(221,RSFTPQuitGoodby); {Do not Localize}
  104. LCmd.Description.Text := 'Syntax: QUIT (terminate service)'; {do not localize}
  105. end;
  106. procedure TIdPOP4Server.InitComponent;
  107. begin
  108. inherited;
  109. FContextClass := TIdPOP4ServerContext;
  110. FRegularProtPort := POP4_PORT;
  111. DefaultPort := POP4_PORT;
  112. Self.Greeting.Code := '200';
  113. Self.Greeting.Text.Text := 'Your text goes here!!!';
  114. end;
  115. procedure TIdPOP4Server.CommandCAPA(ASender: TIdCommand);
  116. begin
  117. ASender.Reply.SetReply(211, RSPOP3SvrCapaList);
  118. ASender.SendReply;
  119. If (IOHandler is TIdServerIOHandlerSSLBase) and
  120. (FUseTLS in ExplicitTLSVals) Then
  121. begin
  122. ASender.Context.Connection.IOHandler.WriteLn('STARTTLS'); {do not localize}
  123. end;
  124. if Assigned(FOnUserLogin) then
  125. begin
  126. ASender.Context.Connection.IOHandler.WriteLn('AUTH LOGIN'); {Do not Localize}
  127. end;
  128. ASender.Context.Connection.IOHandler.WriteLn('.');
  129. TIdPOP4ServerContext(ASender.Context).CheckPipeLine;
  130. end;
  131. procedure TIdPOP4Server.CommandSTARTTLS(ASender: TIdCommand);
  132. begin
  133. if (ASender.Context.Connection.IOHandler is TIdSSLIOHandlerSocketBase) and (FUseTLS in ExplicitTLSVals) then begin
  134. if TIdPOP4ServerContext(ASender.Context).UsingTLS then begin // we are already using TLS
  135. InvalidSyntax(ASender);
  136. Exit;
  137. end;
  138. if TIdPOP4ServerContext(ASender.Context).State <> Auth then begin //STLS only allowed in auth-state
  139. ASender.Reply.SetReply(501, RSPOP3SvrNotInThisState); {Do not Localize}
  140. Exit;
  141. end;
  142. ASender.Reply.SetReply(220, RSPOP3SvrBeginTLSNegotiation);
  143. ASender.SendReply;
  144. //You should never pipeline STARTTLS
  145. TIdPOP4ServerContext(ASender.Context).PipeLining := False;
  146. (ASender.Context.Connection.IOHandler as TIdSSLIOHandlerSocketBase).PassThrough := False;
  147. end else begin
  148. CmdSyntaxError(ASender);
  149. end;
  150. end;
  151. procedure TIdPOP4Server.CmdBadSequenceError(ASender: TIdCommand);
  152. begin
  153. ASender.Reply.SetReply(503, RSSMTPSvrBadSequence);
  154. end;
  155. procedure TIdPOP4Server.CmdSyntaxError(AContext: TIdContext; ALine: string; const AReply: TIdReply = nil);
  156. var
  157. LTmp : String;
  158. LReply : TIdReply;
  159. begin
  160. //First make the first word uppercase
  161. LTmp := UpCaseFirstWord(ALine);
  162. try
  163. if Assigned(AReply) then begin
  164. LReply := AReply;
  165. end else begin
  166. LReply := FReplyClass.Create(nil, ReplyTexts);
  167. LReply.Assign(ReplyUnknownCommand);
  168. end;
  169. LReply.SetReply(500, Sys.Format(RSFTPCmdNotRecognized, [LTmp]));
  170. AContext.Connection.IOHandler.Write(LReply.FormattedReply);
  171. finally
  172. if not Assigned(AReply) then begin
  173. Sys.FreeAndNil(LReply);
  174. end;
  175. end;
  176. end;
  177. procedure TIdPOP4Server.CmdSyntaxError(ASender: TIdCommand);
  178. begin
  179. CmdSyntaxError(ASender.Context, ASender.RawLine, FReplyUnknownCommand );
  180. ASender.PerformReply := False;
  181. end;
  182. procedure TIdPOP4Server.CmdMustUseTLS(ASender: TIdCommand);
  183. begin
  184. ASender.Reply.SetReply(530,RSSMTPSvrReqSTARTTLS);
  185. end;
  186. procedure TIdPOP4Server.InvalidSyntax(ASender: TIdCommand);
  187. begin
  188. ASender.Reply.SetReply( 501,RSPOP3SvrInvalidSyntax);
  189. end;
  190. procedure TIdPOP4Server.DoReplyUnknownCommand(AContext: TIdContext;
  191. ALine: string);
  192. begin
  193. CmdSyntaxError(AContext,ALine);
  194. end;
  195. function TIdPOP4Server.DoAuthLogin(ASender: TIdCommand;
  196. const Login: string): Boolean;
  197. var
  198. S: string;
  199. LUsername, LPassword: string;
  200. LAuthFailed: Boolean;
  201. LAccepted: Boolean;
  202. LS : TIdPOP4ServerContext;
  203. begin
  204. LS := ASender.Context as TIdPOP4ServerContext;
  205. Result := False;
  206. LAuthFailed := False;
  207. TIdPOP4ServerContext(ASender.Context).PipeLining := False;
  208. if UpperCase(Login) = 'LOGIN' then {Do not Localize}
  209. begin // LOGIN USING THE LOGIN AUTH - BASE64 ENCODED
  210. s := TIdEncoderMIME.EncodeString('Username:'); {Do not Localize}
  211. // s := SendRequest( '334 ' + s ); {Do not Localize}
  212. ASender.Reply.SetReply (334, s); {Do not Localize}
  213. ASender.SendReply;
  214. s := Trim(ASender.Context.Connection.IOHandler.ReadLn);
  215. if s <> '' then {Do not Localize}
  216. begin
  217. try
  218. s := TIdDecoderMIME.DecodeString(s);
  219. LUsername := s;
  220. // What? Endcode this string literal?
  221. s := TIdEncoderMIME.EncodeString('Password:'); {Do not Localize}
  222. // s := SendRequest( '334 ' + s ); {Do not Localize}
  223. ASender.Reply.SetReply(334, s); {Do not Localize}
  224. ASender.SendReply;
  225. s := Trim(ASender.Context.Connection.IOHandler.ReadLn);
  226. if Length(s) = 0 then
  227. begin
  228. LAuthFailed := True;
  229. end
  230. else
  231. begin
  232. LPassword := TIdDecoderMIME.DecodeString(s);
  233. end;
  234. // when TIdDecoderMime.DecodeString(s) raise a exception,catch it and set AuthFailed as true
  235. except
  236. LAuthFailed := true;
  237. end;
  238. end
  239. else
  240. begin
  241. LAuthFailed := True;
  242. end;
  243. end;
  244. // Add other login units here
  245. if LAuthFailed then
  246. begin
  247. Result := False;
  248. CmdAuthFailed(ASender);
  249. end
  250. else
  251. begin
  252. LAccepted := False;
  253. if Assigned(fOnUserLogin) then
  254. begin
  255. fOnUserLogin(LS, LUsername, LPassword, LAccepted);
  256. end
  257. else
  258. begin
  259. LAccepted := True;
  260. end;
  261. if LAccepted then
  262. begin
  263. LS.FState := Trans;
  264. end;
  265. LS.Username := LUsername;
  266. if not LAccepted then
  267. begin
  268. CmdAuthFailed(ASender);
  269. end
  270. else
  271. begin
  272. ASender.Reply.SetReply(235,' welcome ' + Trim(LUsername)); {Do not Localize}
  273. ASender.SendReply;
  274. end;
  275. end;
  276. end;
  277. procedure TIdPOP4Server.CmdAuthFailed(ASender: TIdCommand);
  278. begin
  279. ASender.Reply.SetReply(535,RSSMTPSvrAuthFailed);
  280. ASender.SendReply;
  281. end;
  282. procedure TIdPOP4Server.CommandAUTH(ASender: TIdCommand);
  283. var
  284. Login: string;
  285. begin
  286. //Note you can not use PIPELINING with AUTH
  287. TIdPOP4ServerContext(ASender.Context).PipeLining := False;
  288. if not ((FUseTLS=utUseRequireTLS) and not TIdSMTPServerContext(ASender.Context).UsingTLS) then
  289. begin
  290. Login := ASender.UnparsedParams;
  291. DoAuthLogin(ASender, Login);
  292. end
  293. else
  294. begin
  295. MustUseTLS(ASender);
  296. end;
  297. end;
  298. { TIdPOP4ServerContext }
  299. procedure TIdPOP4ServerContext.CheckPipeLine;
  300. begin
  301. if Connection.IOHandler.InputBufferIsEmpty=False then
  302. begin
  303. PipeLining := True;
  304. end;
  305. end;
  306. constructor TIdPOP4ServerContext.Create(AConnection: TIdTCPConnection;
  307. AYarn: TIdYarn; AList: TThreadList);
  308. begin
  309. inherited;
  310. FState := Auth;
  311. FUser := '';
  312. fPassword := '';
  313. end;
  314. function TIdPOP4ServerContext.GetUsingTLS: boolean;
  315. begin
  316. Result := Connection.IOHandler is TIdSSLIOHandlerSocketBase;
  317. if Result then begin
  318. Result := not TIdSSLIOHandlerSocketBase(Connection.IOHandler).PassThrough;
  319. end;
  320. end;
  321. procedure TIdPOP4ServerContext.SetPipeLining(const AValue: Boolean);
  322. begin
  323. if AValue and (not PipeLining) then
  324. begin
  325. Connection.IOHandler.WriteBufferOpen;
  326. end
  327. else if (not AValue) and PipeLining then
  328. begin
  329. Connection.IOHandler.WriteBufferClose;
  330. end;
  331. FPipeLining := AValue;
  332. end;
  333. end.