MBoxDataModule.pas 58 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572
  1. unit MBoxDataModule;
  2. { this was originally the Indy Pop3Server demo, but I filled
  3. the skeleton with a little bit of flesh...
  4. 2005
  5. Jörg Meier (Bob)
  6. [email protected]
  7. }
  8. { $Log: 22918: MainFrm.pas
  9. {
  10. { Rev 1.2 25/10/2004 22:49:28 ANeillans Version: 9.0.17
  11. { Verified
  12. }
  13. {
  14. { Rev 1.1 12/09/2003 21:18:36 ANeillans
  15. { Verified with Indy 9 on D7.
  16. { Added instruction memo.
  17. }
  18. {
  19. { Rev 1.0 10/09/2003 20:40:48 ANeillans
  20. { Initial Import (Used updated version - not original 9 Demo)
  21. }
  22. {
  23. Demo Name: POP3 Server
  24. Created By: Siamak Sarmady
  25. On: 27/10/2002
  26. Notes:
  27. Demonstrates POP3 server events (by way of comment - NOT functional!)
  28. Version History:
  29. 31st Dec 04: Andy Neillans
  30. Fixed for current Indy 10, and migrated to support Delphi 2005.
  31. 12th Sept 03: Andy Neillans
  32. Added the comments memo on the form for information.
  33. 8th July 03: Andy Neillans
  34. Fixed the demo for I9.014
  35. Unknown: Allen O'Neill
  36. Added in some missing command handler comments
  37. Tested:
  38. 31st Dec 04: D2005: Andy Neillans
  39. }
  40. {@$Define WithDatabase} // to use alternate modules for the userauthentication
  41. interface
  42. uses
  43. Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  44. IdPOP3Server, IdSMTPServer, IdBaseComponent, IdComponent,
  45. IdCustomTCPServer, IdTCPServer, IdCmdTCPServer, idthread,
  46. IdExplicitTLSClientServerBase, IdCommandHandlers, IDContext,
  47. IdMessage, IdGlobal, IDSys, IdPOP3, IdTCPConnection, IdTCPClient,
  48. IdMessageClient, IdSMTPBase, IdSMTP, RAS, IdAntiFreezeBase, IdAntiFreeze;
  49. type
  50. TMBoxDataMod = class(TDataModule)
  51. InternalPOP3: TIdPOP3Server;
  52. InternalSMTP: TIdSMTPServer;
  53. ExternalSMTP: TIdSMTP;
  54. ExternalPOP3: TIdPOP3;
  55. IdAntiFreeze1: TIdAntiFreeze;
  56. IdPOP3Server1: TIdPOP3Server;
  57. procedure DataModuleCreate(Sender: TObject);
  58. procedure DataModuleDestroy(Sender: TObject);
  59. // procedure InternalPOP3DELE(ASender: TIdCommand; AMessageNum: Integer);
  60. procedure InternalPOP3LIST(ASender: TIdCommand; AMessageNum: Integer);
  61. procedure InternalPOP3QUIT(ASender: TIdCommand);
  62. procedure InternalPOP3RETR(ASender: TIdCommand; AMessageNum: Integer);
  63. procedure InternalPOP3RSET(ASender: TIdCommand);
  64. procedure InternalPOP3TOP(ASender: TIdCommand; AMessageNum,
  65. ANumLines: Integer);
  66. procedure InternalPOP3UIDL(ASender: TIdCommand; AMessageNum: Integer);
  67. procedure InternalPOP3Connect(AContext: TIdContext);
  68. procedure InternalPOP3Exception(AContext: TIdContext;
  69. AException: Exception);
  70. procedure InternalPOP3Disconnect(AContext: TIdContext);
  71. procedure InternalPOP3BeforeCommandHandler(ASender: TIdCmdTCPServer;
  72. var AData: String; AContext: TIdContext);
  73. procedure InternalPOP3Status(ASender: TObject;
  74. const AStatus: TIdStatus; const AStatusText: String);
  75. procedure InternalPOP3APOP(ASender: TIdCommand; AMailboxID: String;
  76. var VUsersPassword: String);
  77. procedure InternalSMTPUserLogin(ASender: TIdSMTPServerContext;
  78. const AUsername, APassword: String; var VAuthenticated: Boolean);
  79. procedure InternalSMTPConnect(AContext: TIdContext);
  80. procedure InternalSMTPDisconnect(AContext: TIdContext);
  81. procedure InternalSMTPException(AContext: TIdContext;
  82. AException: Exception);
  83. procedure InternalSMTPExecute(AContext: TIdContext);
  84. procedure InternalSMTPListenException(AThread: TIdListenerThread;
  85. AException: Exception);
  86. procedure InternalSMTPMailFrom(ASender: TIdSMTPServerContext;
  87. const AAddress: String; var VAction: TIdMailFromReply);
  88. procedure InternalSMTPRcptTo(ASender: TIdSMTPServerContext;
  89. const AAddress: String; var VAction: TIdRCPToReply;
  90. var VForward: String);
  91. // procedure InternalSMTPReceived(ASender: TIdSMTPServerContext;
  92. // AReceived: String);
  93. procedure InternalSMTPMsgReceive(ASender: TIdSMTPServerContext;
  94. AMsg: TStream; var LAction: TIdDataReply);
  95. procedure InternalSMTPStatus(ASender: TObject;
  96. const AStatus: TIdStatus; const AStatusText: String);
  97. procedure InternalPOP3Delete(aCmd: TIdCommand; AMsgNo: Integer);
  98. procedure InternalPOP3Retrieve(aCmd: TIdCommand; AMsgNo: Integer);
  99. procedure InternalPOP3Stat(aCmd: TIdCommand; out oCount,
  100. oSize: Integer);
  101. procedure InternalSMTPReceived(ASender: TIdSMTPServerContext;
  102. var AReceived: String);
  103. procedure InternalSMTPAfterCommandHandler(ASender: TIdCmdTCPServer;
  104. AContext: TIdContext);
  105. procedure InternalSMTPBeforeCommandHandler(ASender: TIdCmdTCPServer;
  106. var AData: String; AContext: TIdContext);
  107. procedure InternalSMTPBeforeConnect(AContext: TIdContext);
  108. procedure InternalSMTPBeforeListenerRun(AThread: TIdThread);
  109. procedure InternalPOP3BeforeConnect(AContext: TIdContext);
  110. procedure InternalPOP3BeforeListenerRun(AThread: TIdThread);
  111. procedure InternalPOP3Execute(AContext: TIdContext);
  112. procedure InternalPOP3ListenException(AThread: TIdListenerThread;
  113. AException: Exception);
  114. procedure InternalPOP3Reset(aCmd: TIdCommand);
  115. procedure InternalPOP3CheckUser(aContext: TIdContext;
  116. aServerContext: TIdPOP3ServerContext);
  117. private
  118. { Private-Deklarationen }
  119. fMBoxRoot : String;
  120. fMailIDs : tStringList; // list of downloaded mails not deleted on Server
  121. fDoHangup : Boolean; // for dial-up connections
  122. fNewConnection : Boolean; // ditto
  123. RasConn : tRasConnection;
  124. Procedure InitMailBoxes;
  125. Procedure DebugOutput(Const Command:String;ASender:tIDCommand);
  126. public
  127. { Public-Deklarationen }
  128. Procedure RunServer;
  129. Procedure StopServer;
  130. Procedure SetupExternals;
  131. Procedure GetSendMail;
  132. Function GoOnline(Const Provider:String):Boolean;
  133. Property MBoxRoot : String read FMBoxRoot;
  134. Procedure GetMailInfos(var MailList:tstringList);
  135. Procedure GetAllMail(MailList:tStringList;Const MBoxName:String='');
  136. Procedure SendAllMail;
  137. Property MailIDs:tstringlist read fMailIDs;
  138. end;
  139. var
  140. MBoxDataMod: TMBoxDataMod;
  141. implementation
  142. Uses FileCtrl, Pop3DBModule, SyncObjs, Pop3MainUnit, ProviderUnit;
  143. {$R *.DFM}
  144. Type tUserData = Class(tObject)
  145. MailList : tStringList;
  146. UsrName : String;
  147. MBoxPath : String;
  148. MBoxSize : Integer;
  149. Constructor Create(Const AUsrName:String);
  150. Destructor Destroy;Override;
  151. Procedure FillMailList;
  152. End;
  153. tMailData = Class(tObject)
  154. FName : String;
  155. DoDelete : Boolean;
  156. DoSend : Boolean;
  157. MailNumber : Integer;
  158. MailSize : Integer;
  159. Constructor Create(Const AFileName:String);
  160. Destructor Destroy;Override;
  161. End;
  162. // This object is used to specify the received mail from an Internet-Mailserver
  163. tServerMail = Class(tObject)
  164. Mailsentby : String; // From
  165. MailSentto : tStringList; // To
  166. MailSubject : String; // Ref
  167. MailSize : Integer; // Bytes
  168. MsgID : String; // Unique MessageID (Set by the Mailserver)
  169. public
  170. Constructor create;
  171. Destructor destroy; Override;
  172. end;
  173. Var DBSection : tCriticalSection;
  174. LogSection : tCriticalSection;
  175. {*************************************************************************}
  176. {* *}
  177. {* Some File-Routines from my FileUtils Unit needed here *}
  178. {* (slightly modified to be used with Indy *}
  179. {* *}
  180. {*************************************************************************}
  181. Type FileFunction = Function(Const Filename:String):Boolean;
  182. function GetFileSize(const FileName: string): LongInt;
  183. var
  184. SearchRec: TSearchRec;
  185. begin
  186. if FindFirst(ExpandFileName(FileName), faAnyFile, SearchRec) = 0 then
  187. begin
  188. Result := SearchRec.Size;
  189. With Searchrec.FindData do If (nFileSizeHigh <> 0) or ((nFileSizeLow and $80000000) <> 0) then
  190. Result := -1; // Indicate Size Overflow
  191. end
  192. else Result := -1;
  193. SysUtils.FindClose(Searchrec);
  194. end;
  195. function GetFileSize64(const FileName: string): Int64;
  196. var
  197. SearchRec: TSearchRec;
  198. begin
  199. if FindFirst(ExpandFileName(FileName), faAnyFile, SearchRec) = 0 then
  200. Result := SearchRec.FindData.nFileSizeHigh shl 32 or SearchRec.FindData.nFileSizeLow
  201. else Result := -1;
  202. SysUtils.FindClose(Searchrec);
  203. end;
  204. Function GetFilesInDirEX(InitDir:String;Var DirList : tStringList;Fnc:FileFunction;Const Pattern : String = '*.*'):Integer;
  205. Var R : Integer;
  206. Sr : tSearchrec;
  207. Begin
  208. Result := 0;
  209. If Not assigned(Dirlist) Then Begin
  210. Exit;
  211. End;
  212. InitDir := Sys.IncludeTrailingPathDelimiter(InitDir);
  213. { Search for Files }
  214. R := FindFirst(InitDir+Pattern,faAnyFile{ and not faDirectory},Sr);
  215. While R = 0 Do Begin
  216. If (Sr.Attr and faDirectory) = 0 Then Begin
  217. If (Assigned(Fnc) And Fnc(InitDir+Sr.Name))
  218. Or Not Assigned(Fnc) Then Begin
  219. Dirlist.AddObject(InitDir+Sr.Name,Pointer(Sr.Size));
  220. Inc(Result);
  221. Sleep(0);
  222. End;
  223. End;
  224. R := FindNext(SR);
  225. End;
  226. SysUtils.FindClose(Sr);
  227. End;
  228. Function GetFilesInDir(InitDir:String;Var DirList : tStringList;Const Pattern : String = '*.*'):Integer;
  229. Begin
  230. Result := GetFilesInDirEx(InitDir,Dirlist,nil,Pattern);
  231. end;
  232. Function GetRawFileName(Const Root:String;MailInfo:tServerMail):String;
  233. Var ii : Integer;
  234. Nr : Integer;
  235. MBEntry : Integer;
  236. begin
  237. {
  238. Here we must decide for which user the mail actually is.
  239. What we do need is a correspondance between Email-Address - Username
  240. Which is maintained in POP3DBModule (because it could be a database-Function)
  241. Here we will scan every recipient given in the mail to find at least one user this mail is for.
  242. If we do not find any, we have a problem of delivering: the mail was in our provider's mailbox,
  243. so apparently for us, but we cannot deliver. This is usually the place where an
  244. Administrator-Account is required to collect such mails and deliver them manually.
  245. This is a demo, so we will return the first mailbox the mail is for and not returning a List.
  246. (The mail could be addressed to more than one address in our server, in which case it
  247. should be copied to every recipient. This coul be done easily when GetRawFilename returns a StringList)
  248. }
  249. For ii := 0 to MailInfo.MailSentto.count-1 do
  250. begin
  251. Nr := Pop3DBMod.EmailAddrs.IndexOf(MailInfo.MailSentto[ii]);
  252. If Nr >= 0 then With POP3DBMod do
  253. begin
  254. // We found one! Get MailboxName
  255. // ... looks a bit complicated with one list indexing the other ...
  256. MBEntry := Integer(EmailAddrs.Objects[Nr]);
  257. Result := GetRecvFileName(Root+GetMBoxName(MailBoxList[MBEntry]));
  258. end;
  259. end;
  260. end;
  261. {*************************************************************************}
  262. {* *}
  263. {* tServerMail Methods *}
  264. {* *}
  265. {* *}
  266. {*************************************************************************}
  267. Constructor tServerMail.create;
  268. begin
  269. Inherited create;
  270. end;
  271. Destructor tServerMail.destroy;
  272. begin
  273. MailsentBy := '';
  274. FreeAndNil(MailSentto);
  275. MailSubject := '';
  276. MsgID := '';
  277. Inherited Destroy;
  278. end;
  279. {*************************************************************************}
  280. {* *}
  281. {* DataModul initializations *}
  282. {* *}
  283. {* *}
  284. {*************************************************************************}
  285. Procedure DebugString(const S : String);
  286. {
  287. This outputs a line in the memobox of the mainform.
  288. Simple? Not at all! Indy is using threads to perform the jobs
  289. Because a memobox (and many other VCL-Components) is not thread-save,
  290. this turns out to be something complicated, but there is
  291. a solution for this:
  292. We will use the Windows Message system to send the message to the
  293. POP3MainForm, which in turn picks up the message and does what
  294. we'd like to have done in the Forms WndProc.
  295. There are two mechanisms : Sendmessage and PostMessage. Whilst the first
  296. just executes the called WndProc it will run in our thread's context
  297. (which we do NOT want to) the second will put the message into the Queue
  298. and return immediately. In the context of the Forms MainThread the message
  299. will be picked up and the job will be done.
  300. This is why we have to allocate the message on the stack and free it in
  301. the WndProc or else our parameter 'S' is already gone when the WndProc
  302. executes.
  303. Long description, short procedure....
  304. }
  305. Var MyMessage : PChar;
  306. Begin
  307. MyMessage := StrNew(PChar(S));
  308. PostMessage(POP3Main.Handle,LogMessageNo,LogString,Integer(MyMessage));
  309. end;
  310. Procedure DebugException;
  311. Var Buffer : String;
  312. begin
  313. SetLength(Buffer,1024);
  314. ExceptionErrorMessage(ExceptObject,ExceptAddr,PChar(Buffer),1024);
  315. DebugString(Buffer);
  316. end;
  317. procedure TMBoxDataMod.DebugOutput(const Command: String;
  318. ASender: tIDCommand);
  319. begin
  320. DebugString(Command);
  321. DebugString(ASender.Reply.FormattedReply.Text);
  322. DebugString(ASender.Response.Text);
  323. end;
  324. procedure TMBoxDataMod.DataModuleCreate(Sender: TObject);
  325. begin
  326. Pop3DBMod := tPop3DBMod.Create(Self);
  327. fMBoxRoot := Sys.IncludeTrailingPathDelimiter(Sys.IncludeTrailingPathDelimiter(ExtractFilePath(Application.ExeName)) +MBoxFolder);
  328. DBSection := tCriticalSection.Create;
  329. LogSection := tCriticalSection.Create;
  330. fMailIDs := tStringList.Create;
  331. fMailIDs.Sorted := True;
  332. fMailIDs.Duplicates := DupIgnore;
  333. If FileExists(ChangeFileExt(Application.ExeName,'.MList')) then
  334. begin
  335. MailIDs.LoadFromFile(ChangeFileExt(Application.ExeName,'.MList'));
  336. end;
  337. InitMailBoxes;
  338. RasConn := tRASConnection.Create(Self);
  339. end;
  340. procedure TMBoxDataMod.DataModuleDestroy(Sender: TObject);
  341. begin
  342. MailIDs.SaveToFile(ChangeFileExt(Application.ExeName,'.MList'));
  343. if InternalPop3.Active=True then InternalPop3.Active:=False;
  344. FreeAndNil(RasConn);
  345. FreeAndNil(DBSection);
  346. FreeAndNil(LogSection);
  347. end;
  348. {*************************************************************************}
  349. {* *}
  350. {* Dial-up a provider *}
  351. {* *}
  352. {* *}
  353. {*************************************************************************}
  354. {
  355. Rather often I read in the newsgroups I'm in
  356. the question "How to connect to...".
  357. I have written an object which handles most of this:
  358. tRASConnection.
  359. You'll find it in the RAS-Module
  360. }
  361. Function TMBoxDataMod.GoOnline(Const Provider:String):Boolean;
  362. {
  363. Simply connect to internet - Server
  364. The connection was selected in the option-form.
  365. If connecting via LAN the Provider must be an empty string!!
  366. This is for dialling out via a Modem or ISDN
  367. Phone number, account and password are taken from the entries
  368. given in your connection information.
  369. ***** could somebody insert the right phrases here, please. I do not
  370. know the translations for the phone-entries in windows. *******
  371. }
  372. Const ConnRetries = 3; // Max number of dial up retries
  373. Var I : Integer;
  374. PrvName : string;
  375. Count : Integer;
  376. Connected : Boolean;
  377. begin
  378. fDoHangup := false;
  379. fNewConnection := False;
  380. I := 0;
  381. Connected := False;
  382. prvName := 'unknown';
  383. Count := RasCountConnections;
  384. if (Count = 0) and (Provider <> '') then
  385. begin
  386. // Logmessage('connecting ...',2);
  387. While (I < ConnRetries) and not Connected do
  388. begin
  389. Connected := RasConn.Connectwith(Provider);
  390. fNewConnection := True;
  391. if connected then begin
  392. prvName := Provider;
  393. fdoHangup := true;
  394. // LogMessage('Connection with '+prvName + ' established.',2);
  395. end;
  396. Inc(I);
  397. end;
  398. end
  399. else { Count > 0}
  400. begin
  401. If Provider = '' then
  402. begin
  403. // LogMessage('Used a LAN connection',2);
  404. PrvName := 'Network';
  405. end
  406. else {LogMessage('Used already established connection',2)};
  407. fDoHangup := False;
  408. Count := 1; //That's <> 0
  409. Connected := true;
  410. end;
  411. // if didn't work retry later
  412. Result := Connected or (Count > 0);
  413. If Connected then
  414. begin
  415. If fNewConnection Then
  416. {LogMessage('connected',2)};
  417. end;
  418. end;
  419. {*************************************************************************}
  420. {* *}
  421. {* Command responses from the Pop3 Server *}
  422. {* *}
  423. {* *}
  424. {*************************************************************************}
  425. // This is where the client program issues a delete command for a particular message
  426. procedure TMBoxDataMod.InternalPOP3Delete(aCmd: TIdCommand; AMsgNo: Integer);
  427. Var UserData : tUserData;
  428. MailData : tMailData;
  429. Reason : String;
  430. begin
  431. // if the message has been deleted, then return a success command as follows;
  432. // ASender.Thread.Connection.Writeln('+OK - Message ' + IntToStr(AMessageNum) + ' Deleted')
  433. // otherwise, if there was an error in deleting the message, or the message number
  434. // did not exist in the first place, then return the following:
  435. // ASender.Thread.Connection.Writeln('-ERR - Message ' + IntToStr(AMessageNum) + ' not deleted because.... [reason]')
  436. // Usually, messages are deleted after being retrieved from pop3 server
  437. // This is done when client sents DELE command after retrieving a message
  438. // Client command is something like DELE 1 which means delete message 1
  439. // Note, you should not actually delete the message at this point, just mark it as deleted.
  440. // Deletions should be handled at the QUIT event.
  441. Try
  442. Reason := '';
  443. // UserData := tUserData(ASender.Context.Data);
  444. UserData := tUserData(ACmd.Context.Data);
  445. If (AMsgNo >= 1) and (AMsgNo <= UserData.MailList.Count) then
  446. begin
  447. MailData := tMailData(UserData.MailList.Objects[AMsgNo-1]);
  448. If Not MailData.DoDelete then
  449. begin
  450. MailData.DoDelete := True;
  451. aCmd.Reply.SetReply(OK,Format(' - Message %d deleted',[AMsgNo]));
  452. Exit;
  453. end
  454. Else Reason := ' because Message %d IS already deleted.';
  455. end
  456. else Reason := ' because Messagenumber %d is out of range.';
  457. // We 're here when there was an error
  458. aCmd.Reply.SetReply(ERR,Format(' -Message not deleted'+Reason,[AMsgNo]));
  459. Finally
  460. aCmd.Response.Clear;
  461. DebugOutput(Format('DELE %d',[AMsgNo]),aCmd);
  462. End;
  463. end;
  464. //before retrieving messages, client asks for a list of messages
  465. //Server responds with a +OK followed by number of deliverable
  466. //messages and length of messages in bytes. After this a separate
  467. //list of each message and its length is sent to client.
  468. //here we have only one message, but we can continue with message
  469. //number and its length , one per line and finally a '.' character.
  470. //Format of client command is LIST
  471. procedure TMBoxDataMod.InternalPop3LIST(ASender: TIdCommand;AMessageNum: Integer);
  472. Var UserData : tUserData;
  473. MailData : tMailData;
  474. ii : Integer;
  475. Start : Integer;
  476. Stop : Integer;
  477. TotSize : Integer;
  478. Undeled : Integer;
  479. begin
  480. // Here you return a list of available messages to the client
  481. Try
  482. UserData := tUserData(ASender.Context.Data);
  483. UserData.FillMailList;
  484. TotSize := 0;
  485. If AMessageNum > 0 then
  486. begin
  487. Start := AMessageNum-1;
  488. Stop := Start;
  489. end
  490. Else
  491. Begin
  492. Start := 0;
  493. Stop := UserData.MailList.Count-1;
  494. end;
  495. Undeled := 0; // Mailcount
  496. For ii := Start to Stop do
  497. begin
  498. MailData := tMailData(UserData.MailList.Objects[ii]);
  499. If not MailData.DoDelete Then
  500. begin
  501. TotSize := TotSize + MailData.MailSize;
  502. Inc(Undeled);
  503. ASender{.CommandHandler}.Response.Add(Format('%d %d',[Succ(II),MailData.MailSize]));
  504. end;
  505. end;
  506. ASender.Reply.SetReply(OK,Format('%d %d',[Undeled,TotSize]));
  507. Finally
  508. DebugOutput(Format('LIST %d',[AMessageNum]),ASender);
  509. End;
  510. end;
  511. procedure TMBoxDataMod.InternalPop3QUIT(ASender: TIdCommand);
  512. Var UserData : tUserData;
  513. MAilData : tMailData;
  514. ii : Integer;
  515. begin
  516. // This event is triggered on a client QUIT (a correct disconnect)
  517. // Here you should delete any messages that have been marked with DELE.
  518. // NOTE: The +OK response is AUTOMATICALLY sent back to the client, and the
  519. // connection is dropped.
  520. Try
  521. UserData := tUserData(ASender.Context.Data);
  522. For ii := 0 to UserData.MailList.Count-1 do
  523. begin
  524. MailData := tMailData(UserData.MailList.Objects[ii]);
  525. If MailData.DoDelete then
  526. begin
  527. Try
  528. DeleteFile(UserData.MailList[ii]);
  529. except;
  530. end;
  531. end;
  532. end;
  533. FreeAndNil(UserData);
  534. ASender.Context.Data := Nil;
  535. Finally
  536. DebugOutput('QUIT',ASender);
  537. End;
  538. end;
  539. procedure TMBoxDataMod.InternalPOP3Retrieve(aCmd: TIdCommand;
  540. AMsgNo: Integer);
  541. Var UserData : tUserData;
  542. MailData : tMailData;
  543. begin
  544. // Client initiates retrieving each message by issuing a RETR command
  545. // to server. Server will respond by +OK and will continue by sending
  546. // message itself. Each message is saved in a database uppon arival
  547. // by smtp server and is now delivered to user mail agent by pop3 server.
  548. // Format of RETR command is something like
  549. // RETR 1 or RETR 2 etc.
  550. // First, set the response header - this basically tells the client how big the message is.
  551. Try
  552. UserData := tUserData(aCmd.Context.Data);
  553. If (AMsgNO >= 1) and (AMsgNO <= USerData.MailList.Count) then
  554. begin
  555. MailData := tMailData(UserData.MailList.Objects[AMsgNO-1]);
  556. aCmd.Reply.SetReply(OK,Format('%d octets',[MailData.MailSize]));
  557. // Now populate aCmd.Response with the data to be returned.
  558. aCmd.Response.LoadFromFile(UserData.MailList[AMsgNO-1]);
  559. end
  560. Else aCmd.Reply.SetReply(ERR,Format(' -Message %d Does not exist.',[AMsgNO]));
  561. Finally
  562. DebugOutput(Format('RETR %d',[AMsgNO]),aCmd);
  563. End;
  564. end;
  565. procedure TMBoxDataMod.InternalPop3RETR(ASender: TIdCommand;
  566. AMessageNum: Integer);
  567. Var UserData : tUserData;
  568. MailData : tMailData;
  569. begin
  570. // Client initiates retrieving each message by issuing a RETR command
  571. // to server. Server will respond by +OK and will continue by sending
  572. // message itself. Each message is saved in a database uppon arival
  573. // by smtp server and is now delivered to user mail agent by pop3 server.
  574. // Format of RETR command is something like
  575. // RETR 1 or RETR 2 etc.
  576. // First, set the response header - this basically tells the client how big the message is.
  577. Try
  578. UserData := tUserData(ASender.Context.Data);
  579. If (AMessageNum >= 1) and (AMessageNum <= USerData.MailList.Count) then
  580. begin
  581. MailData := tMailData(UserData.MailList.Objects[AMessageNum-1]);
  582. ASender.Reply.SetReply(OK,Format('%d octets',[MailData.MailSize]));
  583. // Now populate ASender.Response with the data to be returned.
  584. ASender.Response.LoadFromFile(UserData.MailList[AMessageNum-1]);
  585. end
  586. Else ASender.Reply.SetReply(ERR,Format(' -Message %d Does not exist.',[AMessageNum]));
  587. Finally
  588. DebugOutput(Format('RETR %d',[AMessageNum]),ASender);
  589. End;
  590. end;
  591. procedure TMBoxDataMod.InternalPop3RSET(ASender: TIdCommand);
  592. Var UserData : tUserData;
  593. MailData : tMailData;
  594. ii : Integer;
  595. begin
  596. // here is where the client wishes to reset the current state
  597. // This may be used to reset a list of pending deletes, etc.
  598. // Set Reply ???
  599. Try
  600. UserData := tUserData(ASender.Context.Data);
  601. for ii := 0 to UserData.MailList.Count-1 do
  602. begin
  603. MailData := tMailData(UserData.MailList.Objects[ii]);
  604. If MailData.DoDelete then MailData.DoDelete := False;
  605. end;
  606. Finally
  607. DebugOutput('RSET',ASender);
  608. End;
  609. end;
  610. procedure TMBoxDataMod.InternalPOP3Stat(aCmd: TIdCommand; out oCount,
  611. oSize: Integer);
  612. Var UserData : tUserData;
  613. begin
  614. // here is where the client has asked for the Status of the mailbox
  615. //When client asks for a statistic of messages server will answer
  616. //by sending an +OK followed by number of messages and length of them
  617. //Format of client message is STAT
  618. Try
  619. UserData := tUserData(aCmd.Context.Data);
  620. UserData.FillMailList;
  621. oCount := UserData.MailList.Count;
  622. oSize := UserData.MBoxSize;
  623. Finally
  624. DebugOutput('STAT',aCmd);
  625. End;
  626. end;
  627. procedure TMBoxDataMod.InternalPop3TOP(ASender: TIdCommand; AMessageNum,
  628. ANumLines: Integer);
  629. (* * )
  630. Var UserData : tUserData;
  631. MailData : tMailData;
  632. Line : String;
  633. F : TextFile;
  634. ii : Integer;
  635. InternalMessage : tIDMessage;
  636. (* *)
  637. begin
  638. // This is where the client has requested the TOP X lines of a particular
  639. // message to be sent to them
  640. // InternalMessage := tIdMessage.Create;
  641. Try
  642. (* *)
  643. ASender.Reply.SetReply(ERR,' -TOP not supported');
  644. (* * )
  645. // This code didn't work, I've to check out the RFC to see how2do that
  646. UserData := tUserData(ASender.Context.Data);
  647. If (AMessageNum >= 1) and (AMessageNum <= USerData.MailList.Count) then
  648. begin
  649. MailData := tMailData(UserData.MailList.Objects[AMessageNum-1]);
  650. ASender.Reply.SetReply(OK,Format('%d octets',[MailData.MailSize]));
  651. AssignFile(F,UserData.MailList[AMessageNum-1]);
  652. Reset(F);
  653. For ii := 1 to ANumLines do
  654. begin
  655. If EOF(F) then Exit;
  656. ReadLn(F,Line);
  657. // Now populate ASender.Response with the data to be returned.
  658. ASender.Response.Add(Line);
  659. end;
  660. end
  661. Else ASender.Reply.SetReply(ERR,Format(' -Message %d Does not exist.',[AMessageNum]));
  662. (* *)
  663. Finally
  664. DebugOutput(Format('TOP NR=%d Lns=%d',[AMessageNum,ANumLines]),ASender);
  665. End;
  666. end;
  667. procedure TMBoxDataMod.InternalPop3UIDL(ASender: TIdCommand;
  668. AMessageNum: Integer);
  669. Var UserData : tUserData;
  670. MailData : tMailData;
  671. ii : Integer;
  672. Start : Integer;
  673. Stop : Integer;
  674. InternalMessage : tIDMessage;
  675. MyReply : String;
  676. SingleLine : Boolean;
  677. Function RemoveAngels(Const MsgString:String) : String;
  678. Var Nrs : Integer;
  679. Begin
  680. Result := MSGString;
  681. Nrs := Length(Result);
  682. If Nrs < 2 then Exit;
  683. If Result[1] = '<' then
  684. begin
  685. Delete(Result,1,1);
  686. Dec(Nrs);
  687. end;
  688. If Result[Nrs] = '>' then Delete(Result,Nrs,1);
  689. end;
  690. begin
  691. // This is where the client has requested the unique identifier (UIDL) of each
  692. // message, or a particular message to be sent to it.
  693. InternalMessage := tIdMessage.Create;
  694. try
  695. UserData := tUserData(ASender.Context.Data);
  696. UserData.FillMailList;
  697. SingleLine := AMessageNum > 0;
  698. If SingleLine then
  699. begin
  700. Start := AMessageNum-1;
  701. Stop := Start;
  702. end
  703. Else
  704. Begin
  705. Start := 0;
  706. Stop := UserData.MailList.Count-1;
  707. end;
  708. If (AMessageNum = 0) or (AMessageNum > UserData.MailList.Count) then
  709. begin
  710. ASender.Reply.SetReply(ERR,Format('Message %d does not exist.',[UserData.MailList.Count]));
  711. Exit;
  712. end;
  713. For ii := Start to Stop do
  714. begin
  715. MailData := tMailData(UserData.MailList.Objects[ii]);
  716. If not MailData.DoDelete Then
  717. begin
  718. InternalMessage.LoadFromFile(UserData.MailList[ii],true);
  719. MyReply := (Format('%d %s',[Succ(II),RemoveAngels(InternalMessage.MsgId)]));
  720. If not SingleLine then ASender.Response.Add(Format('%d %s',[Succ(II),MyReply]));
  721. end
  722. Else
  723. Begin
  724. If SingleLine then ASender.Reply.SetReply(ERR,Format(' Message %d already deleted',[UserData.MailList.Count]));
  725. end;
  726. end;
  727. If SingleLine then
  728. begin
  729. ASender.Reply.SetReply(OK,MyReply);
  730. end
  731. else
  732. begin
  733. end;
  734. finally
  735. FreeAndNil(InternalMessage);
  736. DebugOutput(Format('UIDL %d',[AMessageNum]),ASender);
  737. end;
  738. end;
  739. procedure TMBoxDataMod.InternalPOP3CheckUser(aContext: TIdContext;
  740. aServerContext: TIdPOP3ServerContext);
  741. Var UserData : tUserData;
  742. MBox : String;
  743. begin
  744. // aServerContext.Username -> examine this for valid username
  745. // aServerContext.Password -> examine this for valid password
  746. // if the user/pass pair are valid, then respond with
  747. // aServerContext.State := Trans
  748. // to reject the user/pass pair, do not change the state
  749. { This was changed recently, now just return form this proc.
  750. if the user is not authenticated, throw exception
  751. }
  752. DBSection.Enter;
  753. Try
  754. MBox := GetValidMailBoxName(aServerContext.Username,aServerContext.Password);
  755. If MBox <> '' Then
  756. begin
  757. // LThread.Authenticated := true;
  758. // LThread.State := Trans;
  759. UserData := tUserData.Create(aServerContext.UserName);
  760. UserData.MBoxPath := MBoxRoot+MBox;
  761. aContext.Data := UserData;
  762. end
  763. else begin
  764. Raise Exception.Create('Invalid username or password');
  765. end;
  766. Finally
  767. DBSection.Leave;
  768. End;
  769. end;
  770. procedure TMBoxDataMod.InternalPop3Connect(AContext: TIdContext);
  771. begin
  772. // When a client connects to our server we must reply with +OK, or -ERR
  773. // Set this via Greeting.Text at runtime, or possibly in OnBeforeCommandHandler?
  774. // You may also wish to initialise some global vars here, set the POP3 box to locked state, etc.
  775. end;
  776. procedure TMBoxDataMod.InternalPop3Exception(AContext: TIdContext;
  777. AException: Exception);
  778. begin
  779. // Handle any exceptions given by the thread here
  780. end;
  781. {*************************************************************************}
  782. {* *}
  783. {* *}
  784. {* Housekeeping *}
  785. {* *}
  786. {*************************************************************************}
  787. procedure TMBoxDataMod.InitMailBoxes;
  788. Var BoxList : tStringList;
  789. ii : integer;
  790. Fn : String;
  791. begin
  792. // Verify, that all mailboxes exist.
  793. // a Mailbox is only a folder where all the mail is stored.
  794. DBSection.Enter;
  795. try
  796. BoxList := GetMailBoxList; // Get a List of all Mailboxnames
  797. For ii := 0 to Boxlist.Count-1 do
  798. begin
  799. ForceDirectories(MBoxRoot+BoxList[ii]);
  800. end;
  801. Finally
  802. FreeAndNil(BoxList);
  803. DBSection.Leave;
  804. end;
  805. {
  806. I decided to have a folder where all sent mail is stored
  807. }
  808. Fn := MBoxRoot + SentArchive;
  809. ForceDirectories(Fn);
  810. end;
  811. {*************************************************************************}
  812. {* *}
  813. {* tMailData *}
  814. {* *}
  815. {* *}
  816. {*************************************************************************}
  817. {
  818. tMailData is used to keep a little bit of information for a User's mail.
  819. As usual I store this as an object into a StringList. (one of my favorite
  820. programming techniques) It is allocated and freed as needed with the help
  821. of its Cunstructor and Destructor.
  822. }
  823. constructor tMailData.Create(const AFileName: String);
  824. begin
  825. Self.FName := AFilename;
  826. Self.DoDelete := False;
  827. Self.DoSend := False;
  828. Self.MailSize := GetFileSize(FName);
  829. Self.MailNumber := 0;
  830. end;
  831. destructor tMailData.Destroy;
  832. begin
  833. inherited Destroy;
  834. end;
  835. {*************************************************************************}
  836. {* *}
  837. {* TUSERDATA *}
  838. {* *}
  839. {* *}
  840. {*************************************************************************}
  841. {
  842. The above applies to tUserData as well.
  843. }
  844. constructor tUserData.Create(const AUsrName:String);
  845. begin
  846. Inherited Create;
  847. Self.MailList := tStringList.Create;
  848. Self.UsrName := AUsrName;
  849. Self.MBoxSize := 0;
  850. end;
  851. destructor tUserData.Destroy;
  852. Var MailData : tMailData;
  853. II : Integer;
  854. begin
  855. For ii := 0 to Self.MailList.Count-1 do
  856. begin
  857. MailData := (tMailData(Self.MailList.Objects[ii]));
  858. MailData.Free;
  859. Self.MailList.Objects[ii] := nil;
  860. end;
  861. Self.MailList.Free;
  862. Inherited Destroy;
  863. end;
  864. procedure tUserData.FillMailList;
  865. {
  866. Here we look into the User's mailbox and aquire all the
  867. mails into his(!) MailList
  868. }
  869. Var MailData : tMailData;
  870. FileList : tStringList;
  871. ii : Integer;
  872. TotSize : Integer;
  873. begin
  874. If Self.MailList.Count > 0 then Exit; // Do not generate twice!
  875. FileList := tStringList.Create;
  876. TotSize := 0;
  877. try
  878. GetFilesInDir(Self.MBoxPath,FileList,'*.Raw');
  879. for ii := 0 to FileList.Count-1 do
  880. begin
  881. MailData := tMailData.Create(FileList[ii]);
  882. MailData.MailNumber := ii;
  883. Self.MailList.AddObject(FileList[ii],MailData);
  884. TotSize := TotSize + Integer(FileList.Objects[II]);
  885. end;
  886. finally
  887. Self.MBoxSize := TotSize;
  888. FileList.Free;
  889. end;
  890. end;
  891. procedure TMBoxDataMod.InternalPOP3Disconnect(AContext: TIdContext);
  892. { when Pop3 disconnects, the very last thing to do is to
  893. call this procedure. Here the cleanup is done.
  894. As I was told recently, Indy itself will free AContext.Data
  895. so this will work fine if (and only if!!!) it is a decendant of tObject.
  896. This is true for us, but I kept it freeing myself.
  897. }
  898. Var UserData : tUserData;
  899. begin
  900. If Assigned(AContext.Data) then
  901. begin
  902. // User did NOT disconnect properly
  903. UserData := tUserData(AContext.Data);
  904. FreeAndNil(UserData);
  905. AContext.Data := nil;
  906. end;
  907. end;
  908. procedure TMBoxDataMod.RunServer;
  909. {
  910. Start both internal servers.
  911. The Internal servers are ment to communicate with you on your
  912. own LAN (or your own Computer if you use 127.0.0.1
  913. }
  914. begin
  915. InternalPop3.Active := True;
  916. InternalSMTP.Active := True;
  917. end;
  918. procedure TMBoxDataMod.StopServer;
  919. // Nothing to say to this
  920. begin
  921. try
  922. InternalPop3.Active := False;
  923. except
  924. end;
  925. try
  926. InternalSMTP.Active := False;
  927. except
  928. end;
  929. end;
  930. procedure TMBoxDataMod.InternalPOP3BeforeCommandHandler(
  931. ASender: TIdCmdTCPServer; var AData: String; AContext: TIdContext);
  932. begin
  933. // TestPoint
  934. end;
  935. procedure TMBoxDataMod.InternalPOP3Status(ASender: TObject;
  936. const AStatus: TIdStatus; const AStatusText: String);
  937. begin
  938. // TestPoint
  939. end;
  940. procedure TMBoxDataMod.InternalPOP3APOP(ASender: TIdCommand;
  941. AMailboxID: String; var VUsersPassword: String);
  942. begin
  943. // TestPoint
  944. end;
  945. {*************************************************************************}
  946. {* *}
  947. {* InternalSMTP - Server *}
  948. {* *}
  949. {* *}
  950. {*************************************************************************}
  951. { This is the Internal SMTP-Server Part. Originally supplied as
  952. Demo Name: SMTP Server
  953. Created By: Andy Neillans
  954. On: 27/10/2002
  955. Notes:
  956. Demonstration of SMTPServer (by use of comments only!!)
  957. Read the RFC to understand how to store and manage server data, and
  958. therefore be able to use this component effectivly.
  959. }
  960. procedure TMBoxDataMod.InternalSMTPMsgReceive(ASender: TIdSMTPServerContext;
  961. AMsg: TStream; var LAction: TIdDataReply);
  962. var
  963. // LMsg : TIdMessage;
  964. LStream : TFileStream;
  965. UserData : tUserData;
  966. CurrMailFName : String;
  967. begin
  968. // When a message is received by the server, this event fires.
  969. // The message data is made available in the AMsg : TStream.
  970. // In this example, we will save it to a temporary file, and then load it using
  971. // IdMessage and parse some header elements.
  972. { Well, now we will accept this message quietly, save it and put it
  973. into a queue from which a SMTP - client can read it
  974. and send it off to the provider's SMTP Server.
  975. }
  976. UserData := tUserData(ASender.Data);
  977. CurrMailFName := Pop3DBMod.GetSendFileName(UserData.MBoxPath);
  978. LStream := TFileStream.Create(CurrMailFName, fmCreate);
  979. Try
  980. LStream.CopyFrom(AMsg, 0);
  981. DebugString('SMTP: Mail received from '+UserData.UsrName);
  982. Finally
  983. FreeAndNil(LStream);
  984. End;
  985. Pop3DBMod.EnterToSendMail(CurrMailFName);
  986. end;
  987. procedure TMBoxDataMod.InternalSMTPRcptTo(ASender: TIdSMTPServerContext;
  988. const AAddress: String; var VAction: TIdRCPToReply;
  989. var VForward: String);
  990. begin
  991. // Here we are testing the RCPT TO lines sent to the server.
  992. // These commands denote where the e-mail should be sent.
  993. // RCPT To address comes in via AAddress. VAction sets the return action to the server.
  994. // Here, you would normally do:
  995. // Check if the user has relay rights, if the e-mail address is not local
  996. // If the e-mail domain is local, does the address exist?
  997. // The following actions can be returned to the server:
  998. {
  999. rAddressOk, //address is okay
  1000. rRelayDenied, //we do not relay for third-parties
  1001. rInvalid, //invalid address
  1002. rWillForward, //not local - we will forward
  1003. rNoForward, //not local - will not forward - please use
  1004. rTooManyAddresses, //too many addresses
  1005. rDisabledPerm, //disabled permentantly - not accepting E-Mail
  1006. rDisabledTemp //disabled temporarily - not accepting E-Mail
  1007. }
  1008. // For now, we will just always allow the rcpt address.
  1009. VAction := rAddressOk;
  1010. end;
  1011. procedure TMBoxDataMod.InternalSMTPUserLogin(ASender: TIdSMTPServerContext;
  1012. const AUsername, APassword: String; var VAuthenticated: Boolean);
  1013. Var UserData : tUserData;
  1014. MBox : String;
  1015. begin
  1016. // This event is fired if a user attempts to login to the server
  1017. // Normally used to grant relay access to specific users etc.
  1018. { we use the very same mechanism as in POP3Login to grant access for the user }
  1019. DBSection.Enter;
  1020. Try
  1021. MBox := GetValidMailBoxName(AUsername,APassword);
  1022. If MBox <> '' Then
  1023. begin
  1024. VAuthenticated := True;
  1025. UserData := tUserData.Create(AUserName);
  1026. UserData.MBoxPath := MBoxRoot+MBox;
  1027. ASender.Data := UserData;
  1028. DebugString('SMTP: User '+AUserName+' logged in');
  1029. end
  1030. else begin
  1031. DebugString('SMTP: User '+AUserName+' rejected');
  1032. end;
  1033. Finally
  1034. DBSection.Leave;
  1035. End;
  1036. end;
  1037. procedure TMBoxDataMod.InternalSMTPMailFrom(ASender: TIdSMTPServerContext;
  1038. const AAddress: String; var VAction: TIdMailFromReply);
  1039. begin
  1040. // Here we are testing the MAIL FROM line sent to the server.
  1041. // MAIL FROM address comes in via AAddress. VAction sets the return action to the server.
  1042. // The following actions can be returned to the server:
  1043. { mAccept, mReject }
  1044. // For now, we will just always allow the mail from address.
  1045. VAction := mAccept;
  1046. end;
  1047. (*
  1048. procedure TMBoxDataMod.InternalSMTPReceived(ASender: TIdSMTPServerContext;
  1049. AReceived: String);
  1050. begin
  1051. // This is a new event in the rewrite of IdSMTPServer for Indy 10.
  1052. // It lets you control the Received: header that is added to the e-mail.
  1053. // If you do not want a Received here to be added, set AReceived := '';
  1054. // Formatting 'keys' are available in the received header -- please check
  1055. // the IdSMTPServer source for more detail.
  1056. AReceived := 'Mail received by internal server using Indy '+ASender.Connection.Version;
  1057. end;
  1058. *)
  1059. procedure TMBoxDataMod.InternalSMTPConnect(AContext: TIdContext);
  1060. begin
  1061. // TP
  1062. end;
  1063. procedure TMBoxDataMod.InternalSMTPDisconnect(AContext: TIdContext);
  1064. Var UserData : tUserData;
  1065. begin
  1066. UserData := tUserData(AContext.Data);
  1067. FreeAndNil(UserData);
  1068. AContext.Data := Nil;
  1069. end;
  1070. procedure TMBoxDataMod.InternalSMTPException(AContext: TIdContext;
  1071. AException: Exception);
  1072. begin
  1073. // TP
  1074. end;
  1075. procedure TMBoxDataMod.InternalSMTPExecute(AContext: TIdContext);
  1076. begin
  1077. // TP
  1078. end;
  1079. procedure TMBoxDataMod.InternalSMTPListenException(
  1080. AThread: TIdListenerThread; AException: Exception);
  1081. begin
  1082. // TP
  1083. end;
  1084. procedure TMBoxDataMod.InternalSMTPStatus(ASender: TObject;
  1085. const AStatus: TIdStatus; const AStatusText: String);
  1086. begin
  1087. // TP
  1088. end;
  1089. {*************************************************************************}
  1090. {* *}
  1091. {* Sending and receiving mail *}
  1092. {* *}
  1093. {* *}
  1094. {* This part looks a bit complicated and if you count the lines *}
  1095. {* you get up to more than 500 just for sending and retrieving a mail. *}
  1096. {* Well, both can be doe with just a limerick (5 lines) but I'd like *}
  1097. {* to show you a bit more. Not only the communication with the provider *}
  1098. {* is handled here, but most of that nasty annoying houskeeping *}
  1099. {* jobs including checking for errors (oh, yes there are often problems *}
  1100. {* in the internet communication). *}
  1101. {* Well, and last (not least hopefully) some comments blowing *}
  1102. {* the files up a bit, too. *}
  1103. {* *}
  1104. {*************************************************************************}
  1105. {
  1106. 1. Receiving
  1107. We do not do it the easy way, just getting the mail. We first ask our provider
  1108. for all currently available mail, put it into a list and then working on
  1109. the list. Some features we have to care for: it is pretty good for Tesing when
  1110. you can keep the mail at the provider's while in normal work, you will delete the mail.
  1111. So it would be good to prevent to receive the same mail multiple times.
  1112. }
  1113. procedure TMBoxDataMod.SetupExternals;
  1114. {
  1115. Just set up SMTP and Pop3 clients with the information from the
  1116. Options window
  1117. }
  1118. begin
  1119. With ExternalSMTP, ProviderForm do
  1120. begin
  1121. if Connected then Disconnect;
  1122. If not SMTPLogin.Checked then AuthType := atNone;
  1123. Host := SMTPName.Text;
  1124. PassWord := SMTPPWd.Text;
  1125. Port := StrToInt(SMTPPort.Text);
  1126. UserName := SMTPAccnt.Text;
  1127. end;
  1128. With ExternalPOP3, ProviderForm do
  1129. begin
  1130. if Connected then Disconnect;
  1131. AuthType := atUserPass;
  1132. Host := POP3Name.Text;
  1133. PassWord := POP3PWd.Text;
  1134. Port := StrToInt(POP3Port.Text);
  1135. UserName := POP3Accnt.Text;
  1136. end;
  1137. end;
  1138. Procedure TMBoxDataMod.GetMailInfos(var MailList:tstringList);
  1139. Var Number,
  1140. I : Integer;
  1141. ii : Integer;
  1142. MyMsg : tServerMail;
  1143. Header : tIDMessage;
  1144. Begin
  1145. { Here we just get infos about all the mails. The connection to the provider's mailserver
  1146. has been established and will not be closed.
  1147. You could load here all the headers and store them (or look at them)
  1148. and decide later which mail you'd like to get and which not. that could make
  1149. a mail-client more convinient.
  1150. }
  1151. If Assigned(MailList) then FreeAndNil(Maillist); // if there's an old one
  1152. MailList := tStringList.Create; // now its a brand new
  1153. ExternalPOP3.ReadTimeout := 3*1000; // thirty seconds timeout after dialling
  1154. Number := ExternalPOP3.CheckMessages; // How many maild are at the Provider's
  1155. For I := 1 to Number do // MessageNumbers start with 1 !!!
  1156. begin
  1157. Header := tIdMessage.Create(Nil);
  1158. Header.Clear;
  1159. MyMsg := tServermail.Create;
  1160. try
  1161. MyMsg.MailSize := ExternalPOP3.RetrieveMsgSize(I);
  1162. ExternalPOP3.RetrieveHeader(I,Header); // Headers only
  1163. MyMsg.MailSubject := Header.Subject; // Ref
  1164. MyMsg.Mailsentby := Header.Sender.Text; // Sender
  1165. MyMsg.MailSentto := tStringList.Create; // To
  1166. for ii := 0 to Header.Recipients.Count-1 do
  1167. begin
  1168. MyMsg.MailSentto.Add(Header.Recipients.Items[ii].Address);
  1169. end;
  1170. MyMsg.MsgId := Header.MsgId; // Unique MessageNumber
  1171. except
  1172. DebugException;
  1173. FreeAndNil(MyMsg); // oops, an error
  1174. end;
  1175. MailList.AddObject(Header.Sender.Address,Pointer(MyMsg));
  1176. FreeAndNil(Header);
  1177. end;
  1178. end;
  1179. Procedure TMBoxDataMod.GetAllMail(MailList:tStringList;Const MBoxName:String='');
  1180. {
  1181. Now get all the mails from the provider's server which are listed
  1182. in MailList. The connection to the provider is still open and
  1183. will be closed by the calling program
  1184. }
  1185. Type tDBMailState = (dbNewMail,dbHeaderOnly,dbOldMail);
  1186. Var I,N,
  1187. MsgNum : Integer;
  1188. MyMsg : tServerMail;
  1189. RawFName : String;
  1190. dbState : tDBMailState;
  1191. MyStrings : tStringList;
  1192. {
  1193. Here we will get all the mails. Beforehand, we have looked at the Server's site and
  1194. got all the (unique) Mail-IDs.
  1195. }
  1196. Function CheckDBMail(MailID:String):tDBMailState;
  1197. { Check for completely/partly/unknown mail in Database
  1198. We do not have a database yet, so we have to check for a known
  1199. Mail-ID right now. To do this, we build a List of all maintained
  1200. Mail - IDs and search for a known one. This could be done easily with a StringList
  1201. Of course you can keep this information in a database (as I did, as you can guess
  1202. from the names 'dbOldMail' and 'dbNewMail') The state dbHeaderOnly is not
  1203. handled in this demo, I used it, when I loaded the headers, but was not
  1204. able to load the body of a mail (which happened sometimes). Because we do
  1205. not keep track of loaded Headers, tis is ommitted here.
  1206. }
  1207. begin
  1208. if MailIDs.IndexOf(MailID) >= 0 then Result := dbOldMail
  1209. Else Result := dbNewMail;
  1210. end;
  1211. Procedure DeleteMail(const MsgNum:Integer);
  1212. { Delete a mail on the provider's server
  1213. this is done, after retrieval of the mail
  1214. and only if allowed (which is the usual case)
  1215. in the server-options-form
  1216. }
  1217. begin
  1218. if ProviderForm.DelMail.Checked then // Option: Delete Mail on Server
  1219. Begin
  1220. ExternalPOP3.Delete(MsgNum);
  1221. end
  1222. Else Begin
  1223. // We should remember having received this mail, so we
  1224. // will not get it a second time. Just add it to the MailIDs - List
  1225. MailIDs.Add(tServerMail(MailList.Objects[Pred(MsgNum)]).MsgId); // MsgNum starts at 1
  1226. end;
  1227. end;
  1228. Begin {GetAllMail}
  1229. If Not assigned(MailList) then Exit; // that would be an error
  1230. If Maillist.Count <=0 then exit; // nothing to do?
  1231. try // outermost block
  1232. DebugString(Format('%d message(s) found',[Maillist.Count]));
  1233. For I := 0 to MailList.Count-1 do
  1234. begin
  1235. MsgNum := I+1; {********* Messagenumbers Start with 1 !!!! *************}
  1236. MyMsg := tServerMail(MailList.Objects[I]); // get info
  1237. If MyMsg <> nil then
  1238. begin { Get one Mail and Save it }
  1239. try
  1240. { look for Mail already received. Skip if it is there }
  1241. dbState := CheckDBMail(MyMsg.MsgID);
  1242. If dbState = dbOldMail then
  1243. begin
  1244. // We've seen this one before
  1245. DeleteMail(MsgNum); // will be deleted only if set in Options
  1246. Continue; // Skip processing of this mail
  1247. end;
  1248. // retrieve Message
  1249. {
  1250. we retrieve the message as-it-is (raw), no interpretation is performed.
  1251. Then we adjust for some extras and save the mail to a file.
  1252. }
  1253. MyStrings := tStringList.Create;
  1254. try
  1255. ExternalPOP3.RetrieveRaw(MsgNum,MyStrings);
  1256. { there is one special character, we have to care for : the dot ('.')
  1257. It is used to indicate the end of a message, so Indy will "byte off" some of them
  1258. }
  1259. // Workaround for a line containing one single '.'
  1260. For N := 0 to MyStrings.Count-1 do
  1261. begin
  1262. if MyStrings[N] = '.' then MyStrings[N] := '..';
  1263. end;
  1264. // Workaround for last dot not saved in mail
  1265. MyStrings.Add('.');
  1266. // Workarounds end
  1267. {
  1268. Now we get the path where to store this mail this includes the mailbox
  1269. name which is guessed from the list the mail is for.
  1270. This could be a file list, then we'd have to save the mail several times.
  1271. }
  1272. RAWFname := GetRawFileName(MBoxRoot,MyMsg);
  1273. MyStrings.SavetoFile(RAWFname);
  1274. {*********** Done with this mail *********************************}
  1275. finally
  1276. MyStrings.Free;
  1277. end;
  1278. DeleteMail(MsgNum);
  1279. except
  1280. DebugException;
  1281. Exit;
  1282. end;
  1283. end { get one mail, MyMsg <> nil ... }
  1284. else
  1285. begin { get one mail, MyMsg = nil ... }
  1286. { there was an error retrieving the header
  1287. Do whatever you whish to do here
  1288. }
  1289. end;
  1290. FreeAndNil(MyMsg);
  1291. MailList.Objects[I] := nil;
  1292. end; { for all mails }
  1293. finally
  1294. end;
  1295. end; { GetAllMail }
  1296. procedure TMBoxDataMod.SendAllMail;
  1297. var MyList : tList;
  1298. ReschL : tStringList;
  1299. FN : String;
  1300. P : PChar;
  1301. Arch : String;
  1302. EMail : tIdMessage;
  1303. ii : Integer;
  1304. begin
  1305. {
  1306. Sending mail is a pretty easy job to do now: everything is set up already,
  1307. mails to send are stored in a threaded list so we can work on it,
  1308. the mail itself is stored as a file so we just transmit it
  1309. }
  1310. Reschl := tStringlist.Create;
  1311. EMail := tIdMessage.Create;
  1312. While (Pop3DBMod.GetSendMailCount > 0) do // While there is mail to send
  1313. Begin
  1314. MyList := Pop3DBMod.SendQueue.LockList;
  1315. P := PChar(Mylist[0]);
  1316. Pop3DBMod.SendQueue.UnlockList;
  1317. Pop3DBMod.SendQueue.Remove(P);
  1318. Fn := String(P);
  1319. StrDispose(P);
  1320. If FileExists(Fn) then
  1321. begin
  1322. Email.LoadFromFile(Fn);
  1323. try
  1324. ExternalSMTP.Send(Email);
  1325. {
  1326. I decided to save the sent mails in an archive folder.
  1327. in this version, the root of the archieve and the mail is the same,
  1328. so we can use a simple ReName to have the mail where we want it to be.
  1329. }
  1330. Arch := MBoxRoot + SentArchive + ExtractFileName(Fn);
  1331. RenameFile(Fn,Arch);
  1332. except
  1333. DebugException;
  1334. { There was an error in sending a mail. I'd like to re-schedule the file
  1335. but we cannot insert it right now into the Sendqueue or the while-loop
  1336. we are in may run indefinetively
  1337. }
  1338. ReschL.Add(Fn);
  1339. end;
  1340. end
  1341. else begin
  1342. // We've got an error here, file to send not found. Handle as you want to...
  1343. end;
  1344. End; {While};
  1345. { Now we can insert the re-scheduled files into the queue }
  1346. For ii := 0 to ReschL.Count-1 do
  1347. begin
  1348. Pop3DBMod.SendQueue.Add(StrNew(PChar(ReSchL[ii])));
  1349. end;
  1350. FreeAndNil(EMail);
  1351. FreeAndNil(ReschL);
  1352. end;
  1353. procedure TMBoxDataMod.GetSendMail;
  1354. { Here we get and send the mail to our InternetProvider
  1355. For debugging purposes, I heavily used try-except blocks
  1356. }
  1357. Var Prvdr : String;
  1358. MyMailList : tStringList;
  1359. begin
  1360. MyMailList := Nil;
  1361. SetupExternals;
  1362. // Do we have to dial or connect via LAN
  1363. If ProviderForm.LanChk.Checked then Prvdr := ''
  1364. Else Prvdr := ProviderForm.PhoneList.Text;
  1365. // Go Online
  1366. If GoOnline(Prvdr) then
  1367. begin
  1368. DebugString('Looking for mail on '+ExternalPop3.Host);
  1369. Try
  1370. ExternalPop3.Connect;
  1371. Except
  1372. DebugException;
  1373. Exit; // If connect didn't work don't try anything else
  1374. End;
  1375. Try
  1376. GetMailInfos(MyMailList);
  1377. GetAllMail(MyMailList);
  1378. Except
  1379. DebugException;
  1380. End;
  1381. Try
  1382. ExternalPop3.Disconnect;
  1383. Except
  1384. DebugException;
  1385. End;
  1386. If Pop3DBMod.GetSendMailCount > 0 then
  1387. begin
  1388. Try
  1389. DebugString('Sending mail To '+ExternalSMTP.Host);
  1390. ExternalSMTP.Connect;
  1391. Except
  1392. DebugException;
  1393. Exit; // If connect didn't work don't try anything else
  1394. End;
  1395. Try
  1396. SendAllMail;
  1397. Except
  1398. DebugException;
  1399. End;
  1400. Try
  1401. ExternalSMTP.DisConnect;
  1402. Except
  1403. DebugException;
  1404. End;
  1405. end; { If }
  1406. end;
  1407. If fDoHangup then RasConn.Hangup;
  1408. end;
  1409. procedure TMBoxDataMod.InternalSMTPReceived(ASender: TIdSMTPServerContext;
  1410. var AReceived: String);
  1411. begin
  1412. // TP
  1413. end;
  1414. procedure TMBoxDataMod.InternalSMTPAfterCommandHandler(
  1415. ASender: TIdCmdTCPServer; AContext: TIdContext);
  1416. begin
  1417. //
  1418. end;
  1419. procedure TMBoxDataMod.InternalSMTPBeforeCommandHandler(
  1420. ASender: TIdCmdTCPServer; var AData: String; AContext: TIdContext);
  1421. begin
  1422. //
  1423. end;
  1424. procedure TMBoxDataMod.InternalSMTPBeforeConnect(AContext: TIdContext);
  1425. begin
  1426. //
  1427. end;
  1428. procedure TMBoxDataMod.InternalSMTPBeforeListenerRun(AThread: TIdThread);
  1429. begin
  1430. //
  1431. end;
  1432. procedure TMBoxDataMod.InternalPOP3BeforeConnect(AContext: TIdContext);
  1433. begin
  1434. //
  1435. end;
  1436. procedure TMBoxDataMod.InternalPOP3BeforeListenerRun(AThread: TIdThread);
  1437. begin
  1438. //
  1439. end;
  1440. procedure TMBoxDataMod.InternalPOP3Execute(AContext: TIdContext);
  1441. begin
  1442. //
  1443. end;
  1444. procedure TMBoxDataMod.InternalPOP3ListenException(
  1445. AThread: TIdListenerThread; AException: Exception);
  1446. begin
  1447. //
  1448. end;
  1449. procedure TMBoxDataMod.InternalPOP3Reset(aCmd: TIdCommand);
  1450. begin
  1451. //
  1452. end;
  1453. end.