IdSMTP.pas 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  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. {
  17. Rev 1.47 1/7/05 3:29:34 PM RLebeau
  18. Fix for AV in Notification()
  19. Rev 1.46 11/28/04 2:31:38 PM RLebeau
  20. Updated Authenticate() to create the TIdEncoderMIME instance before sending
  21. the 'AUTH LOGIN' command.
  22. Rev 1.45 11/27/2004 8:58:14 PM JPMugaas
  23. Compile errors.
  24. Rev 1.44 11/27/04 3:21:30 AM RLebeau
  25. Fixed bug in ownership of SASLMechanisms property.
  26. Recoded Authenticate() to use a "case of" statement instead.
  27. Rev 1.43 10/26/2004 10:55:34 PM JPMugaas
  28. Updated refs.
  29. Rev 1.42 6/11/2004 9:38:40 AM DSiders
  30. Added "Do not Localize" comments.
  31. Rev 1.41 2004.03.06 1:31:52 PM czhower
  32. To match Disconnect changes to core.
  33. Rev 1.40 2/25/2004 5:41:28 AM JPMugaas
  34. Authentication bug fixed.
  35. Rev 1.39 2004.02.03 5:44:20 PM czhower
  36. Name changes
  37. Rev 1.38 1/31/2004 3:12:56 AM JPMugaas
  38. Removed dependancy on Math unit. It isn't needed and is problematic in some
  39. versions of Dlephi which don't include it.
  40. Rev 1.37 26/01/2004 01:51:38 CCostelloe
  41. Changed implementation of supressing BCC List generation
  42. Rev 1.36 25/01/2004 21:16:16 CCostelloe
  43. Added support for SuppressBCCListInHeader
  44. Rev 1.35 1/25/2004 3:11:44 PM JPMugaas
  45. SASL Interface reworked to make it easier for developers to use.
  46. SSL and SASL reenabled components.
  47. Rev 1.34 2004.01.22 10:29:56 PM czhower
  48. Now supports default login mechanism with just username and pw.
  49. Rev 1.33 1/21/2004 4:03:22 PM JPMugaas
  50. InitComponent
  51. Rev 1.32 12/28/2003 4:47:02 PM BGooijen
  52. Removed ChangeReplyClass
  53. Rev 1.31 22/12/2003 00:46:16 CCostelloe
  54. .NET fixes
  55. Rev 1.30 24/10/2003 20:53:02 CCostelloe
  56. Bug fix of LRecipients.EMailAddresses in Send.
  57. Rev 1.29 2003.10.17 6:15:16 PM czhower
  58. Bug fix with quit.
  59. Rev 1.28 10/17/2003 1:01:04 AM DSiders
  60. Added localization comments.
  61. Rev 1.27 2003.10.14 1:28:04 PM czhower
  62. DotNet
  63. Rev 1.26 10/11/2003 7:14:36 PM BGooijen
  64. Changed IdCompilerDefines.inc path
  65. Rev 1.25 10/10/2003 10:45:10 PM BGooijen
  66. DotNet
  67. Rev 1.24 2003.10.02 9:27:52 PM czhower
  68. DotNet Excludes
  69. Rev 1.23 6/15/2003 03:28:30 PM JPMugaas
  70. Minor class change.
  71. Rev 1.22 6/15/2003 01:13:40 PM JPMugaas
  72. Now uses new base class.
  73. Rev 1.21 6/5/2003 04:54:08 AM JPMugaas
  74. Reworkings and minor changes for new Reply exception framework.
  75. Rev 1.20 6/4/2003 04:10:40 PM JPMugaas
  76. Removed hacked GetInternelResponse.
  77. Updated to use Kudzu's new string reply code.
  78. Rev 1.19 5/26/2003 12:24:04 PM JPMugaas
  79. Rev 1.18 5/25/2003 03:54:48 AM JPMugaas
  80. Rev 1.17 5/25/2003 12:13:22 AM JPMugaas
  81. SMTP StartTLS code moved into IdSMTPCommon for sharing with TIdDirectSMTP.
  82. StartTLS is now called in Authenticate to prevent unintentional unencrypted
  83. password transmission (e.g. AUTH LOGIN being called before STARTTLS).
  84. Rev 1.16 5/23/2003 04:52:26 AM JPMugaas
  85. Work started on TIdDirectSMTP to support enhanced error codes.
  86. Rev 1.15 5/22/2003 05:26:16 PM JPMugaas
  87. RFC 2034
  88. Rev 1.14 5/18/2003 02:31:42 PM JPMugaas
  89. Reworked some things so IdSMTP and IdDirectSMTP can share code including
  90. stuff for pipelining.
  91. Rev 1.13 5/15/2003 11:09:46 AM JPMugaas
  92. "RFC 2197 SMTP Service Extension for Command Pipelining" now supported. It
  93. should increase efficiency in TIdSMTP.
  94. Rev 1.12 5/13/2003 07:35:06 AM JPMugaas
  95. Made UseEHLO a requirement for explicit TLS because explicit TLS using EHLO
  96. to determine if the server supports explicit TLS. Setting UseEHLO will the
  97. UseTLS property be the default (no encryption) and setting UseTLS to an
  98. explicit TLS setting will cause the UseEHLO property to be true.
  99. Rev 1.11 5/13/2003 07:03:48 AM JPMugaas
  100. Ciaran Costelloe reported a bug in the Assign method. Username and Password
  101. were still being assigned even though the SMTP component does not publish or
  102. use them. I have updated the SMTP assign method with the new properties and
  103. removed the references to Password and Username.
  104. Rev 1.10 5/10/2003 10:10:40 PM JPMugaas
  105. Bug fixes.
  106. Rev 1.9 5/8/2003 08:44:22 PM JPMugaas
  107. Moved some SASL authentication code down to an anscestor for reuse. WIll
  108. clean up soon.
  109. Rev 1.8 5/8/2003 03:18:30 PM JPMugaas
  110. Flattened ou the SASL authentication API, made a custom descendant of SASL
  111. enabled TIdMessageClient classes.
  112. Rev 1.7 5/8/2003 11:28:14 AM JPMugaas
  113. Moved feature negoation properties down to the ExplicitTLSClient level as
  114. feature negotiation goes hand in hand with explicit TLS support.
  115. Rev 1.6 5/8/2003 02:18:18 AM JPMugaas
  116. Fixed an AV in IdPOP3 with SASL list on forms. Made exceptions for SASL
  117. mechanisms missing more consistant, made IdPOP3 support feature feature
  118. negotiation, and consolidated some duplicate code.
  119. Rev 1.5 4/5/2003 02:06:32 PM JPMugaas
  120. TLS handshake itself can now be handled.
  121. Rev 1.4 3/27/2003 05:46:50 AM JPMugaas
  122. Updated framework with an event if the TLS negotiation command fails.
  123. Cleaned up some duplicate code in the clients.
  124. Rev 1.3 3/26/2003 04:19:34 PM JPMugaas
  125. Cleaned-up some code and illiminated some duplicate things.
  126. Rev 1.2 3/13/2003 09:49:32 AM JPMugaas
  127. Now uses an abstract SSL base class instead of OpenSSL so 3rd-party vendors
  128. can plug-in their products.
  129. Rev 1.1 12/15/2002 05:50:18 PM JPMugaas
  130. SMTP and IMAP4 compile. IdPOP3, IdFTP, IMAP4, and IdSMTP now restored in
  131. IdRegister.
  132. Rev 1.0 11/13/2002 08:00:48 AM JPMugaas
  133. }
  134. unit IdSMTP;
  135. interface
  136. {$i IdCompilerDefines.inc}
  137. uses
  138. Classes,
  139. IdAssignedNumbers,
  140. IdEMailAddress,
  141. IdExplicitTLSClientServerBase,
  142. IdHeaderList,
  143. IdMessage,
  144. IdMessageClient,
  145. IdSASL,
  146. IdSASLCollection,
  147. IdSMTPBase,
  148. IdBaseComponent,
  149. IdGlobal,
  150. SysUtils;
  151. type
  152. TIdSMTPAuthenticationType = (satNone, satDefault, satSASL);
  153. const
  154. DEF_SMTP_AUTH = satDefault;
  155. type
  156. //FSASLMechanisms
  157. TIdSMTP = class(TIdSMTPBase)
  158. protected
  159. FAuthType: TIdSMTPAuthenticationType;
  160. // This is just an internal flag we use to determine if we already authenticated to the server.
  161. FDidAuthenticate: Boolean;
  162. FValidateAuthLoginCapability: Boolean;
  163. // FSASLMechanisms : TIdSASLList;
  164. FSASLMechanisms : TIdSASLEntries;
  165. //
  166. procedure SetAuthType(const AValue: TIdSMTPAuthenticationType);
  167. procedure SetUseEhlo(const AValue: Boolean); override;
  168. procedure SetUseTLS(AValue: TIdUseTLS); override;
  169. procedure SetSASLMechanisms(AValue: TIdSASLEntries);
  170. procedure InitComponent; override;
  171. procedure InternalSend(AMsg: TIdMessage; const AFrom: String; ARecipients: TIdEMailAddressList); override;
  172. procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  173. //
  174. // holger: .NET compatibility change, OnConnected being reintroduced
  175. property OnConnected;
  176. public
  177. destructor Destroy; override;
  178. procedure Assign(Source: TPersistent); override;
  179. function Authenticate: Boolean; virtual;
  180. procedure Connect; override;
  181. procedure Disconnect(ANotifyPeer: Boolean); override;
  182. procedure DisconnectNotifyPeer; override;
  183. class procedure QuickSend(const AHost, ASubject, ATo, AFrom, AText: string); overload; {$IFDEF HAS_DEPRECATED}deprecated{$IFDEF HAS_DEPRECATED_MSG} 'Use ContentType overload of QuickSend()'{$ENDIF};{$ENDIF}
  184. class procedure QuickSend(const AHost, ASubject, ATo, AFrom, AText, AContentType, ACharset, AContentTransferEncoding: string); overload;
  185. procedure Expand(AUserName : String; AResults : TStrings); virtual;
  186. function Verify(AUserName : String) : String; virtual;
  187. //
  188. property DidAuthenticate: Boolean read FDidAuthenticate;
  189. published
  190. property AuthType: TIdSMTPAuthenticationType read FAuthType write FAuthType
  191. default DEF_SMTP_AUTH;
  192. property Host;
  193. property Password;
  194. property Port default IdPORT_SMTP;
  195. // property SASLMechanisms: TIdSASLList read FSASLMechanisms write FSASLMechanisms;
  196. property SASLMechanisms : TIdSASLEntries read FSASLMechanisms write SetSASLMechanisms;
  197. property UseTLS;
  198. property Username;
  199. property ValidateAuthLoginCapability: Boolean read FValidateAuthLoginCapability
  200. write FValidateAuthLoginCapability default True;
  201. //
  202. property OnTLSNotAvailable;
  203. end;
  204. implementation
  205. uses
  206. IdCoderMIME,
  207. IdGlobalProtocols,
  208. IdSSL,
  209. IdTCPConnection;
  210. { TIdSMTP }
  211. procedure TIdSMTP.Assign(Source: TPersistent);
  212. var
  213. LS: TIdSMTP;
  214. begin
  215. if Source is TIdSMTP then begin
  216. LS := Source as TIdSMTP;
  217. AuthType := LS.AuthType;
  218. HeloName := LS.HeloName;
  219. SASLMechanisms := LS.SASLMechanisms;
  220. UseEhlo := LS.UseEhlo;
  221. UseTLS := LS.UseTLS;
  222. Host := LS.Host;
  223. MailAgent := LS.MailAgent;
  224. Port := LS.Port;
  225. Username := LS.Username;
  226. Password := LS.Password;
  227. Pipeline := LS.Pipeline;
  228. end else begin
  229. inherited Assign(Source);
  230. end;
  231. end;
  232. function TIdSMTP.Authenticate : Boolean;
  233. var
  234. s : TStrings;
  235. LEncoder: TIdEncoderMIME;
  236. begin
  237. if FDidAuthenticate then
  238. begin
  239. Result := True;
  240. Exit;
  241. end;
  242. //This will look strange but we have logic in that method to make
  243. //sure that the STARTTLS command is used appropriately.
  244. //Note we put this in Authenticate only to ensure that TLS negotiation
  245. //is done before a password is sent over a network unencrypted.
  246. StartTLS;
  247. //note that we pass the reply numbers as strings so the SASL stuff can work
  248. //with IMAP4 and POP3 where non-numeric strings are used for reply codes
  249. case FAuthType of
  250. satNone:
  251. begin
  252. //do nothing
  253. FDidAuthenticate := True;
  254. end;
  255. satDefault:
  256. begin
  257. {
  258. RLebeau: TODO - implement the following code in the future instead
  259. of the code below. This way, TIdSASLLogin can be utilized here.
  260. SASLMechanisms.LoginSASL('AUTH', FHost, FPort, IdGSKSSN_smtp, 'LOGIN', ['235'], ['334'], Self, Capabilities);
  261. FDidAuthenticate := True;
  262. Or better, if the SASLMechanisms is empty, put some default entries
  263. in it, including TIdSASLLogin, and then reset the AuthType to satSASL.
  264. Maybe even do that in SetAuthType/Loaded() instead. That way, everything
  265. goes through SASLMechanisms only...
  266. }
  267. if Username <> '' then begin
  268. if FValidateAuthLoginCapability then begin
  269. s := TStringList.Create;
  270. try
  271. SASLMechanisms.ParseCapaReplyToList(Capabilities, s);
  272. //many servers today do not use username/password authentication
  273. if s.IndexOf('LOGIN') = -1 then begin
  274. Result := False;
  275. Exit;
  276. end;
  277. finally
  278. FreeAndNil(s);
  279. end;
  280. end;
  281. LEncoder := TIdEncoderMIME.Create(nil);
  282. try
  283. SendCmd('AUTH LOGIN', 334);
  284. if {$IFDEF OVERLOADED_OPENARRAY_BUG}SendCmdArr{$ELSE}SendCmd{$ENDIF}(LEncoder.Encode(Username), [235, 334]) = 334 then begin
  285. SendCmd(LEncoder.Encode(Password), 235);
  286. end;
  287. finally
  288. LEncoder.Free;
  289. end;
  290. FDidAuthenticate := True;
  291. end;
  292. end;
  293. satSASL:
  294. begin
  295. SASLMechanisms.LoginSASL('AUTH', FHost, FPort, IdGSKSSN_smtp, ['235'], ['334'], Self, Capabilities); {do not localize}
  296. FDidAuthenticate := True;
  297. end;
  298. end;
  299. Result := FDidAuthenticate;
  300. end;
  301. procedure TIdSMTP.Connect;
  302. begin
  303. FDidAuthenticate := False;
  304. inherited Connect;
  305. try
  306. GetResponse(220);
  307. SendGreeting;
  308. except
  309. Disconnect(False);
  310. raise;
  311. end;
  312. end;
  313. procedure TIdSMTP.InitComponent;
  314. begin
  315. inherited InitComponent;
  316. FSASLMechanisms := TIdSASLEntries.Create(Self);
  317. FAuthType := DEF_SMTP_AUTH;
  318. FValidateAuthLoginCapability := True;
  319. end;
  320. procedure TIdSMTP.DisconnectNotifyPeer;
  321. begin
  322. inherited DisconnectNotifyPeer;
  323. SendCmd('QUIT', 221); {Do not Localize}
  324. end;
  325. procedure TIdSMTP.Expand(AUserName: String; AResults: TStrings);
  326. begin
  327. {$IFDEF OVERLOADED_OPENARRAY_BUG}SendCmdArr{$ELSE}SendCmd{$ENDIF}('EXPN ' + AUserName, [250, 251]); {Do not Localize}
  328. end;
  329. procedure InternalQuickSend(const AHost, ASubject, ATo, AFrom, AText,
  330. AContentType, ACharset, AContentTransferEncoding: String);
  331. {$IFDEF USE_INLINE}inline;{$ENDIF}
  332. var
  333. LSMTP: TIdSMTP;
  334. LMsg: TIdMessage;
  335. begin
  336. LSMTP := TIdSMTP.Create(nil);
  337. try
  338. LMsg := TIdMessage.Create(nil);
  339. try
  340. LMsg.Subject := ASubject;
  341. LMsg.Recipients.EMailAddresses := ATo;
  342. LMsg.From.Text := AFrom;
  343. LMsg.Body.Text := AText;
  344. LMsg.ContentType := AContentType;
  345. LMsg.CharSet := ACharset;
  346. LMsg.ContentTransferEncoding := AContentTransferEncoding;
  347. LSMTP.Host := AHost;
  348. LSMTP.Connect;
  349. try;
  350. LSMTP.Send(LMsg);
  351. finally
  352. LSMTP.Disconnect;
  353. end;
  354. finally
  355. FreeAndNil(LMsg);
  356. end;
  357. finally
  358. FreeAndNil(LSMTP);
  359. end;
  360. end;
  361. {$I IdDeprecatedImplBugOff.inc}
  362. class procedure TIdSMTP.QuickSend(const AHost, ASubject, ATo, AFrom, AText: String);
  363. {$I IdDeprecatedImplBugOn.inc}
  364. begin
  365. InternalQuickSend(AHost, ASubject, ATo, AFrom, AText, '', '', '');
  366. end;
  367. {$I IdDeprecatedImplBugOff.inc}
  368. class procedure TIdSMTP.QuickSend(const AHost, ASubject, ATo, AFrom, AText,
  369. AContentType, ACharset, AContentTransferEncoding: String);
  370. {$I IdDeprecatedImplBugOn.inc}
  371. begin
  372. InternalQuickSend(AHost, ASubject, ATo, AFrom, AText, AContentType, ACharset, AContentTransferEncoding);
  373. end;
  374. procedure TIdSMTP.InternalSend(AMsg: TIdMessage; const AFrom: String; ARecipients: TIdEMailAddressList);
  375. begin
  376. //Authenticate now calls StartTLS
  377. //so that you do not send login information before TLS negotiation (big oops security wise).
  378. //It also should see if authentication should be done according to your settings.
  379. Authenticate;
  380. AMsg.ExtraHeaders.Values[XMAILER_HEADER] := MailAgent;
  381. inherited InternalSend(AMsg, AFrom, ARecipients);
  382. end;
  383. procedure TIdSMTP.SetAuthType(const AValue: TIdSMTPAuthenticationType);
  384. Begin
  385. FAuthType := AValue;
  386. if AValue = satSASL then begin
  387. FUseEhlo := True;
  388. end;
  389. end;
  390. procedure TIdSMTP.SetUseEhlo(const AValue: Boolean);
  391. Begin
  392. FUseEhlo := AValue;
  393. if not AValue then
  394. begin
  395. FAuthType := satDefault;
  396. if FUseTLS in ExplicitTLSVals then
  397. begin
  398. FUseTLS := DEF_USETLS;
  399. FPipeLine := False;
  400. end;
  401. end;
  402. End;
  403. function TIdSMTP.Verify(AUserName: string): string;
  404. begin
  405. {$IFDEF OVERLOADED_OPENARRAY_BUG}SendCmdArr{$ELSE}SendCmd{$ENDIF}('VRFY ' + AUserName, [250, 251]); {Do not Localize}
  406. Result := LastCmdResult.Text[0];
  407. end;
  408. procedure TIdSMTP.Notification(AComponent: TComponent; Operation: TOperation);
  409. begin
  410. if (Operation = opRemove) and (FSASLMechanisms <> nil) then begin
  411. FSASLMechanisms.RemoveByComp(AComponent);
  412. end;
  413. inherited Notification(AComponent, Operation);
  414. end;
  415. procedure TIdSMTP.SetUseTLS(AValue: TIdUseTLS);
  416. begin
  417. inherited SetUseTLS(AValue);
  418. if FUseTLS in ExplicitTLSVals then begin
  419. UseEhlo := True;
  420. end;
  421. end;
  422. procedure TIdSMTP.SetSASLMechanisms(AValue: TIdSASLEntries);
  423. begin
  424. FSASLMechanisms.Assign(AValue);
  425. end;
  426. destructor TIdSMTP.Destroy;
  427. begin
  428. FreeAndNil(FSASLMechanisms);
  429. inherited Destroy;
  430. end;
  431. procedure TIdSMTP.Disconnect(ANotifyPeer: Boolean);
  432. begin
  433. try
  434. inherited Disconnect(ANotifyPeer);
  435. finally
  436. FDidAuthenticate := False;
  437. end;
  438. end;
  439. end.