lsmtp.pp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688
  1. { lNet SMTP unit
  2. CopyRight (C) 2005-2006 Ales Katona
  3. This library is Free software; you can rediStribute it and/or modify it
  4. under the terms of the GNU Library General Public License as published by
  5. the Free Software Foundation; either version 2 of the License, or (at your
  6. option) any later version.
  7. This program is diStributed in the hope that it will be useful, but WITHOUT
  8. ANY WARRANTY; withOut even the implied warranty of MERCHANTABILITY or
  9. FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
  10. for more details.
  11. You should have received a Copy of the GNU Library General Public License
  12. along with This library; if not, Write to the Free Software Foundation,
  13. Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  14. This license has been modified. See File LICENSE.ADDON for more inFormation.
  15. Should you find these sources without a LICENSE File, please contact
  16. me at [email protected]
  17. }
  18. unit lsmtp;
  19. {$mode objfpc}{$H+}
  20. {$inline on}
  21. interface
  22. uses
  23. Classes, Contnrs, lNet, lEvents, lCommon;
  24. type
  25. TLSMTP = class;
  26. TLSMTPClient = class;
  27. TLSMTPStatus = (ssNone, ssCon, ssHelo, ssEhlo, ssMail,
  28. ssRcpt, ssData, ssRset, ssQuit);
  29. TLSMTPStatusSet = set of TLSMTPStatus;
  30. TLSMTPStatusRec = record
  31. Status: TLSMTPStatus;
  32. Args: array[1..2] of string;
  33. end;
  34. { TLSMTPStatusFront }
  35. {$DEFINE __front_type__ := TLSMTPStatusRec}
  36. {$i lcontainersh.inc}
  37. TLSMTPStatusFront = TLFront;
  38. TLSMTPClientStatusEvent = procedure (aSocket: TLSocket;
  39. const aStatus: TLSMTPStatus) of object;
  40. { TAttachment }
  41. TAttachment = class
  42. protected
  43. FData: TStringList;
  44. function GetAsText: string; virtual;
  45. public
  46. constructor Create;
  47. destructor Destroy; override;
  48. function LoadFromFile(const aFileName: string): Boolean;
  49. public
  50. property AsText: string read GetAsText;
  51. end;
  52. { TAttachmentList }
  53. TAttachmentList = class
  54. protected
  55. FItems: TFPObjectList;
  56. function GetCount: Integer;
  57. function GetItem(i: Integer): TAttachment;
  58. procedure SetItem(i: Integer; const AValue: TAttachment);
  59. public
  60. constructor Create;
  61. destructor Destroy; override;
  62. function Add(anAttachment: TAttachment): Integer;
  63. function AddFromFile(const aFileName: string): Integer;
  64. function Remove(anAttachment: TAttachment): Integer;
  65. procedure Delete(const i: Integer);
  66. procedure Clear;
  67. public
  68. property Count: Integer read GetCount;
  69. property Items[i: Integer]: TAttachment read GetItem write SetItem; default;
  70. end;
  71. { TMail }
  72. TMail = class
  73. protected
  74. FMailText: string;
  75. FRecipients: string;
  76. FSender: string;
  77. FSubject: string;
  78. FAttachments: TAttachmentList;
  79. public
  80. constructor Create;
  81. destructor Destroy; override;
  82. public
  83. property Attachments: TAttachmentList read FAttachments;
  84. property MailText: string read FMailText write FMailText;
  85. property Sender: string read FSender write FSender;
  86. property Recipients: string read FRecipients write FRecipients;
  87. property Subject: string read FSubject write FSubject;
  88. end;
  89. TLSMTP = class(TLComponent)
  90. protected
  91. FConnection: TLTcp;
  92. protected
  93. function GetTimeout: DWord;
  94. procedure SetTimeout(const AValue: DWord);
  95. function GetConnected: Boolean;
  96. function GetSocketClass: TLSocketClass;
  97. procedure SetSocketClass(const AValue: TLSocketClass);
  98. function GetEventer: TLEventer;
  99. procedure SetEventer(Value: TLEventer);
  100. public
  101. constructor Create(aOwner: TComponent); override;
  102. destructor Destroy; override;
  103. public
  104. property Connected: Boolean read GetConnected;
  105. property Connection: TLTcp read FConnection;
  106. property SocketClass: TLSocketClass read GetSocketClass write SetSocketClass;
  107. property Eventer: TLEventer read GetEventer write SetEventer;
  108. property Timeout: DWord read GetTimeout write SetTimeout;
  109. end;
  110. { TLSMTPClient }
  111. TLSMTPClient = class(TLSMTP, ILClient)
  112. protected
  113. FStatus: TLSMTPStatusFront;
  114. FCommandFront: TLSMTPStatusFront;
  115. FPipeLine: Boolean;
  116. FOnConnect: TLSocketEvent;
  117. FOnReceive: TLSocketEvent;
  118. FOnDisconnect: TLSocketEvent;
  119. FOnSuccess: TLSMTPClientStatusEvent;
  120. FOnFailure: TLSMTPClientStatusEvent;
  121. FOnError: TLSocketErrorEvent;
  122. FSL: TStringList;
  123. FStatusSet: TLSMTPStatusSet;
  124. FMessage: string;
  125. protected
  126. procedure OnEr(const msg: string; aSocket: TLSocket);
  127. procedure OnRe(aSocket: TLSocket);
  128. procedure OnCo(aSocket: TLSocket);
  129. procedure OnDs(aSocket: TLSocket);
  130. protected
  131. function CanContinue(const aStatus: TLSMTPStatus; const Arg1, Arg2: string): Boolean;
  132. function CleanInput(var s: string): Integer;
  133. procedure EvaluateAnswer(const Ans: string);
  134. procedure ExecuteFrontCommand;
  135. public
  136. constructor Create(aOwner: TComponent); override;
  137. destructor Destroy; override;
  138. function Connect(const aHost: string; const aPort: Word = 25): Boolean; virtual; overload;
  139. function Connect: Boolean; virtual; overload;
  140. function Get(var aData; const aSize: Integer; aSocket: TLSocket = nil): Integer; virtual;
  141. function GetMessage(out msg: string; aSocket: TLSocket = nil): Integer; virtual;
  142. procedure SendMail(const From, Recipients, Subject, Msg: string);
  143. procedure SendMail(aMail: TMail);
  144. procedure Helo(aHost: string = '');
  145. procedure Ehlo(aHost: string = '');
  146. procedure Mail(const From: string);
  147. procedure Rcpt(const RcptTo: string);
  148. procedure Data(const Msg: string);
  149. procedure Rset;
  150. procedure Quit;
  151. procedure Disconnect; override;
  152. procedure CallAction; override;
  153. public
  154. property PipeLine: Boolean read FPipeLine write FPipeLine;
  155. property StatusSet: TLSMTPStatusSet read FStatusSet write FStatusSet;
  156. property OnConnect: TLSocketEvent read FOnConnect write FOnConnect;
  157. property OnReceive: TLSocketEvent read FOnReceive write FOnReceive;
  158. property OnDisconnect: TLSocketEvent read FOnDisconnect write FOnDisconnect;
  159. property OnSuccess: TLSMTPClientStatusEvent read FOnSuccess write FOnSuccess;
  160. property OnFailure: TLSMTPClientStatusEvent read FOnFailure write FOnFailure;
  161. property OnError: TLSocketErrorEvent read FOnError write FOnError;
  162. end;
  163. implementation
  164. uses
  165. SysUtils;
  166. const
  167. EMPTY_REC: TLSMTPStatusRec = (Status: ssNone; Args: ('', ''));
  168. SLE = #13#10;
  169. {$i lcontainers.inc}
  170. function StatusToStr(const aStatus: TLSMTPStatus): string;
  171. const
  172. STATAR: array[ssNone..ssQuit] of string = ('ssNone', 'ssCon', 'ssHelo', 'ssEhlo', 'ssMail',
  173. 'ssRcpt', 'ssData', 'ssRset', 'ssQuit');
  174. begin
  175. Result := STATAR[aStatus];
  176. end;
  177. function MakeStatusRec(const aStatus: TLSMTPStatus; const Arg1, Arg2: string): TLSMTPStatusRec;
  178. begin
  179. Result.Status := aStatus;
  180. Result.Args[1] := Arg1;
  181. Result.Args[2] := Arg2;
  182. end;
  183. { TLSMTP }
  184. function TLSMTP.GetTimeout: DWord;
  185. begin
  186. Result := FConnection.Timeout;
  187. end;
  188. procedure TLSMTP.SetTimeout(const AValue: DWord);
  189. begin
  190. FConnection.Timeout := aValue;
  191. end;
  192. function TLSMTP.GetConnected: Boolean;
  193. begin
  194. Result := FConnection.Connected;
  195. end;
  196. function TLSMTP.GetSocketClass: TLSocketClass;
  197. begin
  198. Result := FConnection.SocketClass;
  199. end;
  200. procedure TLSMTP.SetSocketClass(const AValue: TLSocketClass);
  201. begin
  202. FConnection.SocketClass := AValue;
  203. end;
  204. function TLSMTP.GetEventer: TLEventer;
  205. begin
  206. Result := FConnection.Eventer;
  207. end;
  208. procedure TLSMTP.SetEventer(Value: TLEventer);
  209. begin
  210. FConnection.Eventer := Value;
  211. end;
  212. constructor TLSMTP.Create(aOwner: TComponent);
  213. begin
  214. inherited Create(aOwner);
  215. FConnection := TLTcp.Create(nil);
  216. end;
  217. destructor TLSMTP.Destroy;
  218. begin
  219. FConnection.Free;
  220. inherited Destroy;
  221. end;
  222. { TLSMTPClient }
  223. constructor TLSMTPClient.Create(aOwner: TComponent);
  224. begin
  225. inherited Create(aOwner);
  226. FPort := 25;
  227. FStatusSet := []; // empty set for "ok/not-ok" Event
  228. FSL := TStringList.Create;
  229. FHost := '';
  230. FMessage := '';
  231. // {$warning TODO: fix pipelining support when server does it}
  232. FPipeLine := False;
  233. FConnection.OnError := @OnEr;
  234. FConnection.OnReceive := @OnRe;
  235. FConnection.OnConnect := @OnCo;
  236. FStatus := TLSMTPStatusFront.Create(EMPTY_REC);
  237. FCommandFront := TLSMTPStatusFront.Create(EMPTY_REC);
  238. end;
  239. destructor TLSMTPClient.Destroy;
  240. begin
  241. Quit;
  242. FSL.Free;
  243. FStatus.Free;
  244. FCommandFront.Free;
  245. inherited Destroy;
  246. end;
  247. procedure TLSMTPClient.OnEr(const msg: string; aSocket: TLSocket);
  248. begin
  249. if Assigned(FOnError) then
  250. FOnError(msg, aSocket);
  251. end;
  252. procedure TLSMTPClient.OnRe(aSocket: TLSocket);
  253. begin
  254. if Assigned(FOnReceive) then
  255. FOnReceive(aSocket);
  256. end;
  257. procedure TLSMTPClient.OnCo(aSocket: TLSocket);
  258. begin
  259. if Assigned(FOnConnect) then
  260. FOnConnect(aSocket);
  261. end;
  262. procedure TLSMTPClient.OnDs(aSocket: TLSocket);
  263. begin
  264. if Assigned(FOnDisconnect) then
  265. FOnDisconnect(aSocket);
  266. end;
  267. function TLSMTPClient.CanContinue(const aStatus: TLSMTPStatus; const Arg1, Arg2: string): Boolean;
  268. begin
  269. Result := FPipeLine or FStatus.Empty;
  270. if not Result then
  271. FCommandFront.Insert(MakeStatusRec(aStatus, Arg1, Arg2));
  272. end;
  273. function TLSMTPClient.CleanInput(var s: string): Integer;
  274. var
  275. i: Integer;
  276. begin
  277. FSL.Text := s;
  278. if FSL.Count > 0 then
  279. for i := 0 to FSL.Count-1 do
  280. if Length(FSL[i]) > 0 then EvaluateAnswer(FSL[i]);
  281. s := StringReplace(s, SLE, LineEnding, [rfReplaceAll]);
  282. i := Pos('PASS', s);
  283. if i > 0 then
  284. s := Copy(s, 1, i-1) + 'PASS';
  285. Result := Length(s);
  286. end;
  287. procedure TLSMTPClient.EvaluateAnswer(const Ans: string);
  288. function GetNum: Integer;
  289. begin
  290. try
  291. Result := StrToInt(Copy(Ans, 1, 3));
  292. except
  293. Result := -1;
  294. end;
  295. end;
  296. function ValidResponse(const Answer: string): Boolean; inline;
  297. begin
  298. Result := (Length(Ans) >= 3) and
  299. (Ans[1] in ['1'..'5']) and
  300. (Ans[2] in ['0'..'9']) and
  301. (Ans[3] in ['0'..'9']);
  302. if Result then
  303. Result := (Length(Ans) = 3) or ((Length(Ans) > 3) and (Ans[4] = ' '));
  304. end;
  305. procedure Eventize(const aStatus: TLSMTPStatus; const Res: Boolean);
  306. begin
  307. if Res then begin
  308. if Assigned(FOnSuccess) and (aStatus in FStatusSet) then
  309. FOnSuccess(FConnection.Iterator, aStatus);
  310. end else begin
  311. if Assigned(FOnFailure) and (aStatus in FStatusSet) then
  312. FOnFailure(FConnection.Iterator, aStatus);
  313. end;
  314. end;
  315. var
  316. x: Integer;
  317. begin
  318. x := GetNum;
  319. if ValidResponse(Ans) and not FStatus.Empty then
  320. case FStatus.First.Status of
  321. ssCon,
  322. ssHelo,
  323. ssEhlo: case x of
  324. 200..299: begin
  325. Eventize(FStatus.First.Status, True);
  326. FStatus.Remove;
  327. end;
  328. else begin
  329. Eventize(FStatus.First.Status, False);
  330. Disconnect;
  331. end;
  332. end;
  333. ssMail,
  334. ssRcpt: begin
  335. Eventize(FStatus.First.Status, (x >= 200) and (x < 299));
  336. FStatus.Remove;
  337. end;
  338. ssData: case x of
  339. 200..299: begin
  340. Eventize(FStatus.First.Status, True);
  341. FStatus.Remove;
  342. end;
  343. 300..399: if Length(FMessage) > 0 then begin
  344. FConnection.SendMessage(FMessage);
  345. FMessage := '';
  346. end;
  347. else begin
  348. Eventize(FStatus.First.Status, False);
  349. FStatus.Remove;
  350. end;
  351. end;
  352. ssRset: begin
  353. Eventize(FStatus.First.Status, (x >= 200) and (x < 299));
  354. FStatus.Remove;
  355. end;
  356. ssQuit: begin
  357. Eventize(FStatus.First.Status, (x >= 200) and (x < 299));
  358. FStatus.Remove;
  359. if Assigned(FOnDisconnect) then
  360. FOnDisconnect(FConnection.Iterator);
  361. Disconnect;
  362. end;
  363. end;
  364. if FStatus.Empty and not FCommandFront.Empty then
  365. ExecuteFrontCommand;
  366. end;
  367. procedure TLSMTPClient.ExecuteFrontCommand;
  368. begin
  369. with FCommandFront.First do
  370. case Status of
  371. ssHelo: Helo(Args[1]);
  372. ssEhlo: Ehlo(Args[1]);
  373. ssMail: Mail(Args[1]);
  374. ssRcpt: Rcpt(Args[1]);
  375. ssData: Data(Args[1]);
  376. ssRset: Rset;
  377. ssQuit: Quit;
  378. end;
  379. FCommandFront.Remove;
  380. end;
  381. function TLSMTPClient.Connect(const aHost: string; const aPort: Word = 25): Boolean;
  382. begin
  383. Result := False;
  384. Disconnect;
  385. if FConnection.Connect(aHost, aPort) then begin
  386. FHost := aHost;
  387. FPort := aPort;
  388. FStatus.Insert(MakeStatusRec(ssCon, '', ''));
  389. Result := True;
  390. end;
  391. end;
  392. function TLSMTPClient.Connect: Boolean;
  393. begin
  394. Result := Connect(FHost, FPort);
  395. end;
  396. function TLSMTPClient.Get(var aData; const aSize: Integer; aSocket: TLSocket): Integer;
  397. var
  398. s: string;
  399. begin
  400. Result := FConnection.Get(aData, aSize, aSocket);
  401. if Result > 0 then begin
  402. SetLength(s, Result);
  403. Move(aData, PChar(s)^, Result);
  404. CleanInput(s);
  405. end;
  406. end;
  407. function TLSMTPClient.GetMessage(out msg: string; aSocket: TLSocket): Integer;
  408. begin
  409. Result := FConnection.GetMessage(msg, aSocket);
  410. if Result > 0 then
  411. Result := CleanInput(msg);
  412. end;
  413. procedure TLSMTPClient.SendMail(const From, Recipients, Subject, Msg: string);
  414. var
  415. i: Integer;
  416. begin
  417. if (Length(Recipients) > 0) and (Length(From) > 0) then begin
  418. Mail(From);
  419. FSL.CommaText := StringReplace(Recipients, ' ', ',', [rfReplaceAll]);
  420. for i := 0 to FSL.Count-1 do
  421. Rcpt(FSL[i]);
  422. Data('From: ' + From + SLE + 'Subject: ' + Subject + SLE + 'To: ' + FSL.CommaText + SLE + Msg);
  423. Rset;
  424. end;
  425. end;
  426. procedure TLSMTPClient.SendMail(aMail: TMail);
  427. begin
  428. // TODO: incorporate attachments + encoding
  429. SendMail(aMail.Sender, aMail.Recipients, aMail.Subject, aMail.MailText);
  430. end;
  431. procedure TLSMTPClient.Helo(aHost: string = '');
  432. begin
  433. if Length(Host) = 0 then
  434. aHost := FHost;
  435. if CanContinue(ssHelo, aHost, '') then begin
  436. FConnection.SendMessage('HELO ' + aHost + SLE);
  437. FStatus.Insert(MakeStatusRec(ssHelo, '', ''));
  438. end;
  439. end;
  440. procedure TLSMTPClient.Ehlo(aHost: string = '');
  441. begin
  442. if Length(aHost) = 0 then
  443. aHost := FHost;
  444. if CanContinue(ssEhlo, aHost, '') then begin
  445. FConnection.SendMessage('EHLO ' + aHost + SLE);
  446. FStatus.Insert(MakeStatusRec(ssEhlo, '', ''));
  447. end;
  448. end;
  449. procedure TLSMTPClient.Mail(const From: string);
  450. begin
  451. if CanContinue(ssMail, From, '') then begin
  452. FConnection.SendMessage('MAIL FROM:' + '<' + From + '>' + SLE);
  453. FStatus.Insert(MakeStatusRec(ssMail, '', ''));
  454. end;
  455. end;
  456. procedure TLSMTPClient.Rcpt(const RcptTo: string);
  457. begin
  458. if CanContinue(ssRcpt, RcptTo, '') then begin
  459. FConnection.SendMessage('RCPT TO:' + '<' + RcptTo + '>' + SLE);
  460. FStatus.Insert(MakeStatusRec(ssRcpt, '', ''));
  461. end;
  462. end;
  463. procedure TLSMTPClient.Data(const Msg: string);
  464. begin
  465. if CanContinue(ssData, Msg, '') then begin
  466. // TODO: clean SLEs and '.' on line starts
  467. FMessage := Msg + SLE + '.' + SLE;
  468. FConnection.SendMessage('DATA' + SLE);
  469. FStatus.Insert(MakeStatusRec(ssData, '', ''));
  470. end;
  471. end;
  472. procedure TLSMTPClient.Rset;
  473. begin
  474. if CanContinue(ssRset, '', '') then begin
  475. FConnection.SendMessage('RSET' + SLE);
  476. FStatus.Insert(MakeStatusRec(ssRset, '', ''));
  477. end;
  478. end;
  479. procedure TLSMTPClient.Quit;
  480. begin
  481. if CanContinue(ssQuit, '', '') then begin
  482. FConnection.SendMessage('QUIT' + SLE);
  483. FStatus.Insert(MakeStatusRec(ssQuit, '', ''));
  484. end;
  485. end;
  486. procedure TLSMTPClient.Disconnect;
  487. begin
  488. FConnection.Disconnect;
  489. FStatus.Clear;
  490. FCommandFront.Clear;
  491. end;
  492. procedure TLSMTPClient.CallAction;
  493. begin
  494. FConnection.CallAction;
  495. end;
  496. { TMail }
  497. constructor TMail.Create;
  498. begin
  499. end;
  500. destructor TMail.Destroy;
  501. begin
  502. end;
  503. { TAttachment }
  504. function TAttachment.GetAsText: string;
  505. begin
  506. Result := '';
  507. raise Exception.Create('Not yet implemented');
  508. end;
  509. constructor TAttachment.Create;
  510. begin
  511. FData := TStringList.Create;
  512. end;
  513. destructor TAttachment.Destroy;
  514. begin
  515. FData.Free;
  516. inherited Destroy;
  517. end;
  518. function TAttachment.LoadFromFile(const aFileName: string): Boolean;
  519. begin
  520. Result := False;
  521. raise Exception.Create('Not yet implemented');
  522. end;
  523. { TAttachmentList }
  524. function TAttachmentList.GetCount: Integer;
  525. begin
  526. Result := FItems.Count;
  527. end;
  528. function TAttachmentList.GetItem(i: Integer): TAttachment;
  529. begin
  530. Result := TAttachment(FItems[i]);
  531. end;
  532. procedure TAttachmentList.SetItem(i: Integer; const AValue: TAttachment);
  533. begin
  534. FItems[i] := aValue;
  535. end;
  536. constructor TAttachmentList.Create;
  537. begin
  538. FItems := TFPObjectList.Create(True);
  539. end;
  540. destructor TAttachmentList.Destroy;
  541. begin
  542. FItems.Free;
  543. inherited Destroy;
  544. end;
  545. function TAttachmentList.Add(anAttachment: TAttachment): Integer;
  546. begin
  547. Result := FItems.Add(anAttachment);
  548. end;
  549. function TAttachmentList.AddFromFile(const aFileName: string): Integer;
  550. var
  551. Tmp: TAttachment;
  552. begin
  553. Tmp := TAttachment.Create;
  554. if Tmp.LoadFromFile(aFileName) then
  555. Result := FItems.Add(Tmp);
  556. end;
  557. function TAttachmentList.Remove(anAttachment: TAttachment): Integer;
  558. begin
  559. Result := FItems.Remove(anAttachment);
  560. end;
  561. procedure TAttachmentList.Delete(const i: Integer);
  562. begin
  563. FItems.Delete(i);
  564. end;
  565. procedure TAttachmentList.Clear;
  566. begin
  567. FItems.Clear;
  568. end;
  569. end.