PageRenderTime 28ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 1ms

/module/ASC.Mail/ASC.Mail.Core/Net/_Obsolete/SmtpClientEx.cs

https://github.com/dc0d/ONLYOFFICE-Server
C# | 2564 lines | 1602 code | 241 blank | 721 comment | 213 complexity | f38c6ca4b7816a9319be7da701914479 MD5 | raw file
Possible License(s): GPL-2.0, MPL-2.0-no-copyleft-exception
  1. /*
  2. (c) Copyright Ascensio System SIA 2010-2014
  3. This program is a free software product.
  4. You can redistribute it and/or modify it under the terms
  5. of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
  6. Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
  7. to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
  8. any third-party rights.
  9. This program is distributed WITHOUT ANY WARRANTY; without even the implied warranty
  10. of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
  11. the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
  12. You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
  13. The interactive user interfaces in modified source and object code versions of the Program must
  14. display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
  15. Pursuant to Section 7(b) of the License you must retain the original Product logo when
  16. distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
  17. trademark law for use of our trademarks.
  18. All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
  19. content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
  20. International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
  21. */
  22. namespace ASC.Mail.Net.SMTP.Client
  23. {
  24. #region usings
  25. using System;
  26. using System.Collections;
  27. using System.Collections.Generic;
  28. using System.IO;
  29. using System.Net;
  30. using System.Net.Sockets;
  31. using System.Security.Cryptography;
  32. using System.Text;
  33. using System.Threading;
  34. using Dns.Client;
  35. using Mime;
  36. #endregion
  37. /// <summary>
  38. /// Is called when asynchronous command had completed.
  39. /// </summary>
  40. public delegate void CommadCompleted(SocketCallBackResult result, Exception exception);
  41. /// <summary>
  42. /// SMTP client.
  43. /// </summary>
  44. [Obsolete("Use SMTP_Client instead !")]
  45. public class SmtpClientEx : IDisposable
  46. {
  47. #region Nested type: Auth_state_data
  48. /// <summary>
  49. /// Provides state date for BeginAuthenticate method.
  50. /// </summary>
  51. private class Auth_state_data
  52. {
  53. #region Members
  54. private readonly string m_Password = "";
  55. private readonly CommadCompleted m_pCallback;
  56. private readonly string m_UserName = "";
  57. #endregion
  58. #region Properties
  59. /// <summary>
  60. /// Gets user name.
  61. /// </summary>
  62. public string UserName
  63. {
  64. get { return m_UserName; }
  65. }
  66. /// <summary>
  67. /// Gets user password.
  68. /// </summary>
  69. public string Password
  70. {
  71. get { return m_Password; }
  72. }
  73. /// <summary>
  74. /// Gets callback what must be called when aynchrounous execution completes.
  75. /// </summary>
  76. public CommadCompleted Callback
  77. {
  78. get { return m_pCallback; }
  79. }
  80. /// <summary>
  81. /// Gets or sets user data.
  82. /// </summary>
  83. public object Tag { get; set; }
  84. #endregion
  85. #region Constructor
  86. /// <summary>
  87. /// Default constructor.
  88. /// </summary>
  89. /// <param name="userName">User name.</param>
  90. /// <param name="password">Password.</param>
  91. /// <param name="callback">Callback what must be called when aynchrounous execution completes.</param>
  92. public Auth_state_data(string userName, string password, CommadCompleted callback)
  93. {
  94. m_UserName = userName;
  95. m_Password = password;
  96. m_pCallback = callback;
  97. }
  98. #endregion
  99. }
  100. #endregion
  101. #region Events
  102. /// <summary>
  103. /// Occurs when SMTP session has finished and session log is available.
  104. /// </summary>
  105. public event LogEventHandler SessionLog = null;
  106. #endregion
  107. #region Members
  108. private bool m_Authenticated;
  109. private bool m_Connected;
  110. private string[] m_pDnsServers;
  111. private SocketLogger m_pLogger;
  112. private SocketEx m_pSocket;
  113. private bool m_Supports_Bdat;
  114. private bool m_Supports_CramMd5;
  115. private bool m_Supports_Login;
  116. private bool m_Supports_Size;
  117. #endregion
  118. #region Properties
  119. /// <summary>
  120. /// Gets local endpoint. Returns null if smtp client isn't connected.
  121. /// </summary>
  122. public EndPoint LocalEndpoint
  123. {
  124. get
  125. {
  126. if (m_pSocket != null)
  127. {
  128. return m_pSocket.LocalEndPoint;
  129. }
  130. else
  131. {
  132. return null;
  133. }
  134. }
  135. }
  136. /// <summary>
  137. /// Gets remote endpoint. Returns null if smtp client isn't connected.
  138. /// </summary>
  139. public EndPoint RemoteEndPoint
  140. {
  141. get
  142. {
  143. if (m_pSocket != null)
  144. {
  145. return m_pSocket.RemoteEndPoint;
  146. }
  147. else
  148. {
  149. return null;
  150. }
  151. }
  152. }
  153. /// <summary>
  154. /// Gets or sets dns servers.
  155. /// </summary>
  156. public string[] DnsServers
  157. {
  158. get { return m_pDnsServers; }
  159. set { m_pDnsServers = value; }
  160. }
  161. /// <summary>
  162. /// Gets if smtp client is connected.
  163. /// </summary>
  164. public bool Connected
  165. {
  166. get { return m_Connected; }
  167. }
  168. /// <summary>
  169. /// Gets if pop3 client is authenticated.
  170. /// </summary>
  171. public bool Authenticated
  172. {
  173. get { return m_Authenticated; }
  174. }
  175. /// <summary>
  176. /// Gets when was last activity.
  177. /// </summary>
  178. public DateTime LastDataTime
  179. {
  180. get { return m_pSocket.LastActivity; }
  181. }
  182. /// <summary>
  183. /// Gets log entries that are currently in log buffer. Returns null if socket not connected or no logging enabled.
  184. /// </summary>
  185. public SocketLogger SessionActiveLog
  186. {
  187. get
  188. {
  189. if (m_pSocket == null)
  190. {
  191. return null;
  192. }
  193. else
  194. {
  195. return m_pSocket.Logger;
  196. }
  197. }
  198. }
  199. /// <summary>
  200. /// Gets how many bytes are readed through smtp client.
  201. /// </summary>
  202. public long ReadedCount
  203. {
  204. get
  205. {
  206. if (!m_Connected)
  207. {
  208. throw new Exception("You must connect first");
  209. }
  210. return m_pSocket.ReadedCount;
  211. }
  212. }
  213. /// <summary>
  214. /// Gets how many bytes are written through smtp client.
  215. /// </summary>
  216. public long WrittenCount
  217. {
  218. get
  219. {
  220. if (!m_Connected)
  221. {
  222. throw new Exception("You must connect first");
  223. }
  224. return m_pSocket.WrittenCount;
  225. }
  226. }
  227. /// <summary>
  228. /// Gets if the connection is an SSL connection.
  229. /// </summary>
  230. public bool IsSecureConnection
  231. {
  232. get
  233. {
  234. if (!m_Connected)
  235. {
  236. throw new Exception("You must connect first");
  237. }
  238. return m_pSocket.SSL;
  239. }
  240. }
  241. #endregion
  242. #region Methods
  243. /// <summary>
  244. /// Sends specified message to specified smart host.
  245. /// </summary>
  246. /// <param name="smartHost">Smarthost name or IP.</param>
  247. /// <param name="port">SMTP port number. Normally this is 25.</param>
  248. /// <param name="hostName">Host name reported to SMTP server.</param>
  249. /// <param name="message">Mime message to send.</param>
  250. public static void QuickSendSmartHost(string smartHost, int port, string hostName, Mime message)
  251. {
  252. QuickSendSmartHost(smartHost, port, hostName, "", "", message);
  253. }
  254. /// <summary>
  255. /// Sends specified message to specified smart host.
  256. /// </summary>
  257. /// <param name="smartHost">Smarthost name or IP.</param>
  258. /// <param name="port">SMTP port number. Normally this is 25.</param>
  259. /// <param name="hostName">Host name reported to SMTP server.</param>
  260. /// <param name="userName">SMTP user name. Note: Pass empty string if no authentication wanted.</param>
  261. /// <param name="password">SMTP password.</param>
  262. /// <param name="message">Mime message to send.</param>
  263. public static void QuickSendSmartHost(string smartHost,
  264. int port,
  265. string hostName,
  266. string userName,
  267. string password,
  268. Mime message)
  269. {
  270. QuickSendSmartHost(smartHost, port, false, hostName, userName, password, message);
  271. }
  272. /// <summary>
  273. /// Sends specified message to specified smart host.
  274. /// </summary>
  275. /// <param name="smartHost">Smarthost name or IP.</param>
  276. /// <param name="port">SMTP port number. Default SMTP port is 25 and SSL port is 465.</param>
  277. /// <param name="ssl">Specifies if to connected via SSL.</param>
  278. /// <param name="hostName">Host name reported to SMTP server.</param>
  279. /// <param name="userName">SMTP user name. Note: Pass empty string if no authentication wanted.</param>
  280. /// <param name="password">SMTP password.</param>
  281. /// <param name="message">Mime message to send.</param>
  282. public static void QuickSendSmartHost(string smartHost,
  283. int port,
  284. bool ssl,
  285. string hostName,
  286. string userName,
  287. string password,
  288. Mime message)
  289. {
  290. string from = "";
  291. if (message.MainEntity.From != null)
  292. {
  293. MailboxAddress[] addresses = message.MainEntity.From.Mailboxes;
  294. if (addresses.Length > 0)
  295. {
  296. from = addresses[0].EmailAddress;
  297. }
  298. }
  299. ArrayList recipients = new ArrayList();
  300. if (message.MainEntity.To != null)
  301. {
  302. MailboxAddress[] addresses = message.MainEntity.To.Mailboxes;
  303. foreach (MailboxAddress address in addresses)
  304. {
  305. recipients.Add(address.EmailAddress);
  306. }
  307. }
  308. if (message.MainEntity.Cc != null)
  309. {
  310. MailboxAddress[] addresses = message.MainEntity.Cc.Mailboxes;
  311. foreach (MailboxAddress address in addresses)
  312. {
  313. recipients.Add(address.EmailAddress);
  314. }
  315. }
  316. if (message.MainEntity.Bcc != null)
  317. {
  318. MailboxAddress[] addresses = message.MainEntity.Bcc.Mailboxes;
  319. foreach (MailboxAddress address in addresses)
  320. {
  321. recipients.Add(address.EmailAddress);
  322. }
  323. }
  324. string[] to = new string[recipients.Count];
  325. recipients.CopyTo(to);
  326. MemoryStream messageStream = new MemoryStream();
  327. message.ToStream(messageStream);
  328. messageStream.Position = 0;
  329. // messageStream
  330. QuickSendSmartHost(smartHost, port, ssl, hostName, userName, password, from, to, messageStream);
  331. }
  332. /// <summary>
  333. /// Sends specified message to specified smart host. NOTE: Message sending starts from message stream current posision.
  334. /// </summary>
  335. /// <param name="smartHost">Smarthost name or IP.</param>
  336. /// <param name="port">SMTP port number. Normally this is 25.</param>
  337. /// <param name="hostName">Host name reported to SMTP server.</param>
  338. /// <param name="from">From address reported to SMTP server.</param>
  339. /// <param name="to">Message recipients.</param>
  340. /// <param name="messageStream">Message stream. NOTE: Message sending starts from message stream current posision.</param>
  341. public static void QuickSendSmartHost(string smartHost,
  342. int port,
  343. string hostName,
  344. string from,
  345. string[] to,
  346. Stream messageStream)
  347. {
  348. QuickSendSmartHost(smartHost, port, false, hostName, "", "", from, to, messageStream);
  349. }
  350. /// <summary>
  351. /// Sends specified message to specified smart host. NOTE: Message sending starts from message stream current posision.
  352. /// </summary>
  353. /// <param name="smartHost">Smarthost name or IP.</param>
  354. /// <param name="port">SMTP port number. Normally this is 25.</param>
  355. /// <param name="hostName">Host name reported to SMTP server.</param>
  356. /// <param name="userName">SMTP user name. Note: Pass empty string if no authentication wanted.</param>
  357. /// <param name="password">SMTP password.</param>
  358. /// <param name="from">From address reported to SMTP server.</param>
  359. /// <param name="to">Message recipients.</param>
  360. /// <param name="messageStream">Message stream. NOTE: Message sending starts from message stream current posision.</param>
  361. public static void QuickSendSmartHost(string smartHost,
  362. int port,
  363. string hostName,
  364. string userName,
  365. string password,
  366. string from,
  367. string[] to,
  368. Stream messageStream)
  369. {
  370. QuickSendSmartHost(smartHost, port, false, hostName, userName, password, from, to, messageStream);
  371. }
  372. /// <summary>
  373. /// Sends specified message to specified smart host. NOTE: Message sending starts from message stream current posision.
  374. /// </summary>
  375. /// <param name="smartHost">Smarthost name or IP.</param>
  376. /// <param name="port">SMTP port number. Default SMTP port is 25 and SSL port is 465.</param>
  377. /// <param name="ssl">Specifies if to connected via SSL.</param>
  378. /// <param name="hostName">Host name reported to SMTP server.</param>
  379. /// <param name="userName">SMTP user name. Note: Pass empty string if no authentication wanted.</param>
  380. /// <param name="password">SMTP password.</param>
  381. /// <param name="from">From address reported to SMTP server.</param>
  382. /// <param name="to">Message recipients.</param>
  383. /// <param name="messageStream">Message stream. NOTE: Message sending starts from message stream current posision.</param>
  384. public static void QuickSendSmartHost(string smartHost,
  385. int port,
  386. bool ssl,
  387. string hostName,
  388. string userName,
  389. string password,
  390. string from,
  391. string[] to,
  392. Stream messageStream)
  393. {
  394. using (SmtpClientEx smtp = new SmtpClientEx())
  395. {
  396. smtp.Connect(smartHost, port, ssl);
  397. smtp.Ehlo(hostName);
  398. if (userName.Length > 0)
  399. {
  400. smtp.Authenticate(userName, password);
  401. }
  402. smtp.SetSender(MailboxAddress.Parse(from).EmailAddress,
  403. messageStream.Length - messageStream.Position);
  404. foreach (string t in to)
  405. {
  406. smtp.AddRecipient(MailboxAddress.Parse(t).EmailAddress);
  407. }
  408. smtp.SendMessage(messageStream);
  409. }
  410. }
  411. /// <summary>
  412. /// Cleasns up resources and disconnect smtp client if open.
  413. /// </summary>
  414. public void Dispose()
  415. {
  416. try
  417. {
  418. Disconnect();
  419. }
  420. catch {}
  421. }
  422. /// <summary>
  423. /// Connects to sepcified host.
  424. /// </summary>
  425. /// <param name="host">Host name or IP address.</param>
  426. /// <param name="port">Port where to connect.</param>
  427. public void Connect(string host, int port)
  428. {
  429. Connect(null, host, port, false);
  430. }
  431. /// <summary>
  432. /// Connects to sepcified host.
  433. /// </summary>
  434. /// <param name="host">Host name or IP address.</param>
  435. /// <param name="port">Port where to connect. Default SMTP port is 25 and SSL port is 465.</param>
  436. /// <param name="ssl">Specifies if to connected via SSL.</param>
  437. public void Connect(string host, int port, bool ssl)
  438. {
  439. Connect(null, host, port, ssl);
  440. }
  441. /// <summary>
  442. /// Connects to sepcified host.
  443. /// </summary>
  444. /// <param name="localEndpoint">Sets local endpoint. Pass null, to use default.</param>
  445. /// <param name="host">Host name or IP address.</param>
  446. /// <param name="port">Port where to connect.</param>
  447. public void Connect(IPEndPoint localEndpoint, string host, int port)
  448. {
  449. Connect(localEndpoint, host, port, false);
  450. }
  451. /// <summary>
  452. /// Connects to sepcified host.
  453. /// </summary>
  454. /// <param name="localEndpoint">Sets local endpoint. Pass null, to use default.</param>
  455. /// <param name="host">Host name or IP address.</param>
  456. /// <param name="port">Port where to connect.</param>
  457. /// <param name="ssl">Specifies if to connected via SSL. Default SMTP port is 25 and SSL port is 465.</param>
  458. public void Connect(IPEndPoint localEndpoint, string host, int port, bool ssl)
  459. {
  460. m_pSocket = new SocketEx();
  461. if (localEndpoint != null)
  462. {
  463. m_pSocket.Bind(localEndpoint);
  464. }
  465. // Create logger
  466. if (SessionLog != null)
  467. {
  468. m_pLogger = new SocketLogger(m_pSocket.RawSocket, SessionLog);
  469. m_pLogger.SessionID = Guid.NewGuid().ToString();
  470. m_pSocket.Logger = m_pLogger;
  471. }
  472. if (host.IndexOf("@") == -1)
  473. {
  474. m_pSocket.Connect(host, port, ssl);
  475. }
  476. else
  477. {
  478. //---- Parse e-domain -------------------------------//
  479. string domain = host;
  480. // eg. Ivx <ivx@lumisoft.ee>
  481. if (domain.IndexOf("<") > -1 && domain.IndexOf(">") > -1)
  482. {
  483. domain = domain.Substring(domain.IndexOf("<") + 1,
  484. domain.IndexOf(">") - domain.IndexOf("<") - 1);
  485. }
  486. if (domain.IndexOf("@") > -1)
  487. {
  488. domain = domain.Substring(domain.LastIndexOf("@") + 1);
  489. }
  490. if (domain.Trim().Length == 0)
  491. {
  492. if (m_pLogger != null)
  493. {
  494. m_pLogger.AddTextEntry("Destination address '" + host + "' is invalid, aborting !");
  495. }
  496. throw new Exception("Destination address '" + host + "' is invalid, aborting !");
  497. }
  498. //--- Get MX record -------------------------------------------//
  499. Dns_Client dns = new Dns_Client();
  500. Dns_Client.DnsServers = m_pDnsServers;
  501. DnsServerResponse dnsResponse = dns.Query(domain, QTYPE.MX);
  502. bool connected = false;
  503. switch (dnsResponse.ResponseCode)
  504. {
  505. case RCODE.NO_ERROR:
  506. DNS_rr_MX[] mxRecords = dnsResponse.GetMXRecords();
  507. // Try all available hosts by MX preference order, if can't connect specified host.
  508. foreach (DNS_rr_MX mx in mxRecords)
  509. {
  510. try
  511. {
  512. if (m_pLogger != null)
  513. {
  514. m_pLogger.AddTextEntry("Connecting with mx record to: " + mx.Host);
  515. }
  516. m_pSocket.Connect(mx.Host, port, ssl);
  517. connected = true;
  518. break;
  519. }
  520. catch (Exception x)
  521. {
  522. // Just skip and let for to try next host.
  523. if (m_pLogger != null)
  524. {
  525. m_pLogger.AddTextEntry("Failed connect to: " + mx.Host + " error:" +
  526. x.Message);
  527. }
  528. }
  529. }
  530. // None of MX didn't connect
  531. if (mxRecords.Length > 0 && !connected)
  532. {
  533. throw new Exception("Destination email server is down");
  534. }
  535. /* Rfc 2821 5
  536. If no MX records are found, but an A RR is found, the A RR is treated as
  537. if it was associated with an implicit MX RR, with a preference of 0,
  538. pointing to that host.
  539. */
  540. if (!connected)
  541. {
  542. // Try to connect with A record
  543. IPAddress[] ipEntry = null;
  544. try
  545. {
  546. if (m_pLogger != null)
  547. {
  548. m_pLogger.AddTextEntry("No mx record, trying to get A record for: " +
  549. domain);
  550. }
  551. ipEntry = Dns_Client.Resolve(domain);
  552. }
  553. catch
  554. {
  555. if (m_pLogger != null)
  556. {
  557. m_pLogger.AddTextEntry("Invalid domain,no MX or A record: " + domain);
  558. }
  559. throw new Exception("Invalid domain,no MX or A record: " + domain);
  560. }
  561. try
  562. {
  563. if (m_pLogger != null)
  564. {
  565. m_pLogger.AddTextEntry("Connecting with A record to:" + domain);
  566. }
  567. m_pSocket.Connect(domain, port, ssl);
  568. }
  569. catch
  570. {
  571. if (m_pLogger != null)
  572. {
  573. m_pLogger.AddTextEntry("Failed connect to:" + domain);
  574. }
  575. throw new Exception("Destination email server is down");
  576. }
  577. }
  578. break;
  579. case RCODE.NAME_ERROR:
  580. if (m_pLogger != null)
  581. {
  582. m_pLogger.AddTextEntry("Invalid domain,no MX or A record: " + domain);
  583. }
  584. throw new Exception("Invalid domain,no MX or A record: " + domain);
  585. case RCODE.SERVER_FAILURE:
  586. if (m_pLogger != null)
  587. {
  588. m_pLogger.AddTextEntry("Dns server unvailable.");
  589. }
  590. throw new Exception("Dns server unvailable.");
  591. }
  592. }
  593. /*
  594. * Notes: Greeting may be single or multiline response.
  595. *
  596. * Examples:
  597. * 220<SP>SMTP server ready<CRLF>
  598. *
  599. * 220-SMTP server ready<CRLF>
  600. * 220-Addtitional text<CRLF>
  601. * 220<SP>final row<CRLF>
  602. *
  603. */
  604. // Read server response
  605. string responseLine = m_pSocket.ReadLine(1000);
  606. while (!responseLine.StartsWith("220 "))
  607. {
  608. // If lisne won't start with 220, then its error response
  609. if (!responseLine.StartsWith("220"))
  610. {
  611. throw new Exception(responseLine);
  612. }
  613. responseLine = m_pSocket.ReadLine(1000);
  614. }
  615. m_Connected = true;
  616. }
  617. /// <summary>
  618. /// Starts connection to specified host.
  619. /// </summary>
  620. /// <param name="host">Host name or IP address.</param>
  621. /// <param name="port">Port where to connect.</param>
  622. /// <param name="callback">Callback to be called if connect ends.</param>
  623. public void BeginConnect(string host, int port, CommadCompleted callback)
  624. {
  625. BeginConnect(null, host, port, false, callback);
  626. }
  627. /// <summary>
  628. /// Starts connection to specified host.
  629. /// </summary>
  630. /// <param name="host">Host name or IP address.</param>
  631. /// <param name="port">Port where to connect.</param>
  632. /// <param name="ssl">Specifies if to connected via SSL.</param>
  633. /// <param name="callback">Callback to be called if connect ends.</param>
  634. public void BeginConnect(string host, int port, bool ssl, CommadCompleted callback)
  635. {
  636. BeginConnect(null, host, port, ssl, callback);
  637. }
  638. /// <summary>
  639. /// Starts connection to specified host.
  640. /// </summary>
  641. /// <param name="localEndpoint">Sets local endpoint. Pass null, to use default.</param>
  642. /// <param name="host">Host name or IP address.</param>
  643. /// <param name="port">Port where to connect.</param>
  644. /// <param name="callback">Callback to be called if connect ends.</param>
  645. public void BeginConnect(IPEndPoint localEndpoint, string host, int port, CommadCompleted callback)
  646. {
  647. BeginConnect(localEndpoint, host, port, false, callback);
  648. }
  649. /// <summary>
  650. /// Starts connection to specified host.
  651. /// </summary>
  652. /// <param name="localEndpoint">Sets local endpoint. Pass null, to use default.</param>
  653. /// <param name="host">Host name or IP address.</param>
  654. /// <param name="port">Port where to connect.</param>
  655. /// <param name="ssl">Specifies if to connected via SSL.</param>
  656. /// <param name="callback">Callback to be called if connect ends.</param>
  657. public void BeginConnect(IPEndPoint localEndpoint,
  658. string host,
  659. int port,
  660. bool ssl,
  661. CommadCompleted callback)
  662. {
  663. ThreadPool.QueueUserWorkItem(BeginConnect_workerThread,
  664. new object[] {localEndpoint, host, port, ssl, callback});
  665. }
  666. /* /// <summary>
  667. /// Starts disconnecting SMTP client.
  668. /// </summary>
  669. public void BeginDisconnect()
  670. {
  671. if(!m_Connected){
  672. throw new Exception("You must connect first");
  673. }
  674. }*/
  675. /// <summary>
  676. /// Disconnects smtp client from server.
  677. /// </summary>
  678. public void Disconnect()
  679. {
  680. try
  681. {
  682. if (m_pSocket != null && m_pSocket.Connected)
  683. {
  684. m_pSocket.WriteLine("QUIT");
  685. m_pSocket.Shutdown(SocketShutdown.Both);
  686. }
  687. }
  688. catch {}
  689. m_pSocket = null;
  690. m_Connected = false;
  691. m_Supports_Size = false;
  692. m_Supports_Bdat = false;
  693. m_Supports_Login = false;
  694. m_Supports_CramMd5 = false;
  695. if (m_pLogger != null)
  696. {
  697. m_pLogger.Flush();
  698. m_pLogger = null;
  699. }
  700. }
  701. /// <summary>
  702. /// Switches SMTP connection to SSL.
  703. /// </summary>
  704. public void StartTLS()
  705. {
  706. /* RFC 2487 STARTTLS 5. STARTTLS Command.
  707. STARTTLS with no parameters.
  708. After the client gives the STARTTLS command, the server responds with
  709. one of the following reply codes:
  710. 220 Ready to start TLS
  711. 501 Syntax error (no parameters allowed)
  712. 454 TLS not available due to temporary reason
  713. */
  714. if (!m_Connected)
  715. {
  716. throw new Exception("You must connect first !");
  717. }
  718. if (m_Authenticated)
  719. {
  720. throw new Exception("The STLS command is only valid in non-authenticated state !");
  721. }
  722. if (m_pSocket.SSL)
  723. {
  724. throw new Exception("Connection is already secure !");
  725. }
  726. m_pSocket.WriteLine("STARTTLS");
  727. string reply = m_pSocket.ReadLine();
  728. if (!reply.ToUpper().StartsWith("220"))
  729. {
  730. throw new Exception("Server returned:" + reply);
  731. }
  732. m_pSocket.SwitchToSSL_AsClient();
  733. }
  734. /// <summary>
  735. /// Start TLS(SSL) negotiation asynchronously.
  736. /// </summary>
  737. /// <param name="callback">The method to be called when the asynchronous StartTLS operation is completed.</param>
  738. public void BeginStartTLS(CommadCompleted callback)
  739. {
  740. ThreadPool.QueueUserWorkItem(BeginStartTLS_workerThread, callback);
  741. }
  742. /// <summary>
  743. /// Does EHLO command. If server don't support EHLO, tries HELO.
  744. /// </summary>
  745. /// <param name="hostName">Host name which is reported to SMTP server.</param>
  746. public void Ehlo(string hostName)
  747. {
  748. if (!m_Connected)
  749. {
  750. throw new Exception("You must connect first");
  751. }
  752. /* Rfc 2821 4.1.1.1 EHLO
  753. * Syntax: "EHLO" SP Domain CRLF
  754. */
  755. if (hostName.Length == 0)
  756. {
  757. hostName = Dns.GetHostName();
  758. }
  759. // Send EHLO command to server
  760. m_pSocket.WriteLine("EHLO " + hostName);
  761. string responseLine = m_pSocket.ReadLine();
  762. // Response line must start with 250 or otherwise it's error response,
  763. // try HELO
  764. if (!responseLine.StartsWith("250"))
  765. {
  766. // Send HELO command to server
  767. m_pSocket.WriteLine("HELO " + hostName);
  768. responseLine = m_pSocket.ReadLine();
  769. // HELO failed, return error
  770. if (!responseLine.StartsWith("250"))
  771. {
  772. throw new Exception(responseLine);
  773. }
  774. }
  775. /* RFC 2821 4.1.1.1 EHLO
  776. * Examples:
  777. * 250-domain<SP>free_text<CRLF>
  778. * 250-EHLO_keyword<CRLF>
  779. * 250<SP>EHLO_keyword<CRLF>
  780. *
  781. * 250<SP> specifies that last EHLO response line.
  782. */
  783. while (!responseLine.StartsWith("250 "))
  784. {
  785. //---- Store supported ESMTP features --------------------//
  786. if (responseLine.ToLower().IndexOf("size") > -1)
  787. {
  788. m_Supports_Size = true;
  789. }
  790. else if (responseLine.ToLower().IndexOf("chunking") > -1)
  791. {
  792. m_Supports_Bdat = true;
  793. }
  794. else if (responseLine.ToLower().IndexOf("cram-md5") > -1)
  795. {
  796. m_Supports_CramMd5 = true;
  797. }
  798. else if (responseLine.ToLower().IndexOf("login") > -1)
  799. {
  800. m_Supports_Login = true;
  801. }
  802. //--------------------------------------------------------//
  803. // Read next EHLO response line
  804. responseLine = m_pSocket.ReadLine();
  805. }
  806. }
  807. /// <summary>
  808. /// Begins EHLO command.
  809. /// </summary>
  810. /// <param name="hostName">Host name which is reported to SMTP server.</param>
  811. /// <param name="callback">Callback to be called if command ends.</param>
  812. public void BeginEhlo(string hostName, CommadCompleted callback)
  813. {
  814. if (!m_Connected)
  815. {
  816. throw new Exception("You must connect first");
  817. }
  818. /* Rfc 2821 4.1.1.1 EHLO
  819. * Syntax: "EHLO" SP Domain CRLF
  820. */
  821. if (hostName.Length == 0)
  822. {
  823. hostName = Dns.GetHostName();
  824. }
  825. // Start sending EHLO command to server
  826. m_pSocket.BeginWriteLine("EHLO " + hostName, new object[] {hostName, callback}, OnEhloSendFinished);
  827. }
  828. /// <summary>
  829. /// Does AUTH command.
  830. /// </summary>
  831. /// <param name="userName">Uesr name.</param>
  832. /// <param name="password">Password.</param>
  833. public void Authenticate(string userName, string password)
  834. {
  835. if (!m_Connected)
  836. {
  837. throw new Exception("You must connect first !");
  838. }
  839. if (!(m_Supports_CramMd5 || m_Supports_Login))
  840. {
  841. throw new Exception("Authentication isn't supported.");
  842. }
  843. /* LOGIN
  844. * Example:
  845. * C: AUTH<SP>LOGIN<CRLF>
  846. * S: 334<SP>base64(USERNAME)<CRLF> // USERNAME is string constant
  847. * C: base64(username)<CRLF>
  848. * S: 334<SP>base64(PASSWORD)<CRLF> // PASSWORD is string constant
  849. * C: base64(password)<CRLF>
  850. * S: 235 Ok<CRLF>
  851. */
  852. /* Cram-M5
  853. Example:
  854. C: AUTH<SP>CRAM-MD5<CRLF>
  855. S: 334<SP>base64(md5_calculation_hash)<CRLF>
  856. C: base64(username<SP>password_hash)<CRLF>
  857. S: 235 Ok<CRLF>
  858. */
  859. if (m_Supports_CramMd5)
  860. {
  861. m_pSocket.WriteLine("AUTH CRAM-MD5");
  862. string responseLine = m_pSocket.ReadLine();
  863. // Response line must start with 334 or otherwise it's error response
  864. if (!responseLine.StartsWith("334"))
  865. {
  866. throw new Exception(responseLine);
  867. }
  868. string md5HashKey =
  869. Encoding.ASCII.GetString(Convert.FromBase64String(responseLine.Split(' ')[1]));
  870. HMACMD5 kMd5 = new HMACMD5(Encoding.ASCII.GetBytes(password));
  871. byte[] md5HashByte = kMd5.ComputeHash(Encoding.ASCII.GetBytes(md5HashKey));
  872. string hashedPwd = BitConverter.ToString(md5HashByte).ToLower().Replace("-", "");
  873. m_pSocket.WriteLine(Convert.ToBase64String(Encoding.ASCII.GetBytes(userName + " " + hashedPwd)));
  874. responseLine = m_pSocket.ReadLine();
  875. // Response line must start with 235 or otherwise it's error response
  876. if (!responseLine.StartsWith("235"))
  877. {
  878. throw new Exception(responseLine);
  879. }
  880. m_Authenticated = true;
  881. }
  882. else if (m_Supports_Login)
  883. {
  884. m_pSocket.WriteLine("AUTH LOGIN");
  885. string responseLine = m_pSocket.ReadLine();
  886. // Response line must start with 334 or otherwise it's error response
  887. if (!responseLine.StartsWith("334"))
  888. {
  889. throw new Exception(responseLine);
  890. }
  891. // Send user name to server
  892. m_pSocket.WriteLine(Convert.ToBase64String(Encoding.ASCII.GetBytes(userName)));
  893. responseLine = m_pSocket.ReadLine();
  894. // Response line must start with 334 or otherwise it's error response
  895. if (!responseLine.StartsWith("334"))
  896. {
  897. throw new Exception(responseLine);
  898. }
  899. // Send password to server
  900. m_pSocket.WriteLine(Convert.ToBase64String(Encoding.ASCII.GetBytes(password)));
  901. responseLine = m_pSocket.ReadLine();
  902. // Response line must start with 235 or otherwise it's error response
  903. if (!responseLine.StartsWith("235"))
  904. {
  905. throw new Exception(responseLine);
  906. }
  907. m_Authenticated = true;
  908. }
  909. if (m_Authenticated && m_pSocket.Logger != null)
  910. {
  911. m_pSocket.Logger.UserName = userName;
  912. }
  913. }
  914. /// <summary>
  915. /// Begins authenticate.
  916. /// </summary>
  917. /// <param name="userName">Uesr name.</param>
  918. /// <param name="password">Password.</param>
  919. /// <param name="callback">Callback to be called if command ends.</param>
  920. public void BeginAuthenticate(string userName, string password, CommadCompleted callback)
  921. {
  922. if (!m_Connected)
  923. {
  924. throw new Exception("You must connect first !");
  925. }
  926. if (!(m_Supports_CramMd5 || m_Supports_Login))
  927. {
  928. throw new Exception("Authentication isn't supported.");
  929. }
  930. /* LOGIN
  931. * Example:
  932. * C: AUTH<SP>LOGIN<CRLF>
  933. * S: 334<SP>base64(USERNAME)<CRLF> // USERNAME is string constant
  934. * C: base64(username)<CRLF>
  935. * S: 334<SP>base64(PASSWORD)<CRLF> // PASSWORD is string constant
  936. * C: base64(password)<CRLF>
  937. * S: 235 Ok<CRLF>
  938. */
  939. /* Cram-M5
  940. Example:
  941. C: AUTH<SP>CRAM-MD5<CRLF>
  942. S: 334<SP>base64(md5_calculation_hash)<CRLF>
  943. C: base64(username<SP>password_hash)<CRLF>
  944. S: 235 Ok<CRLF>
  945. */
  946. if (m_Supports_CramMd5)
  947. {
  948. m_pSocket.BeginWriteLine("AUTH CRAM-MD5",
  949. new Auth_state_data(userName, password, callback),
  950. OnAuthCramMd5SendFinished);
  951. }
  952. else if (m_Supports_Login)
  953. {
  954. m_pSocket.BeginWriteLine("AUTH LOGIN",
  955. new Auth_state_data(userName, password, callback),
  956. OnAuthLoginSendFinished);
  957. }
  958. }
  959. /// <summary>
  960. /// Does MAIL FROM: command.
  961. /// </summary>
  962. /// <param name="senderEmail">Sender email address what is reported to smtp server</param>
  963. /// <param name="messageSize">Message size in bytes or -1 if message size isn't known.</param>
  964. public void SetSender(string senderEmail, long messageSize)
  965. {
  966. if (!m_Connected)
  967. {
  968. throw new Exception("You must connect first");
  969. }
  970. /* RFC 2821 4.1.1.2 MAIL
  971. * Examples:
  972. * MAIL FROM:<ivx@lumisoft.ee>
  973. *
  974. * RFC 1870 adds optional SIZE keyword support.
  975. * SIZE keyword may only be used if it's reported in EHLO command response.
  976. * Examples:
  977. * MAIL FROM:<ivx@lumisoft.ee> SIZE=1000
  978. */
  979. if (m_Supports_Size && messageSize > -1)
  980. {
  981. m_pSocket.WriteLine("MAIL FROM:<" + senderEmail + "> SIZE=" + messageSize);
  982. }
  983. else
  984. {
  985. m_pSocket.WriteLine("MAIL FROM:<" + senderEmail + ">");
  986. }
  987. string responseLine = m_pSocket.ReadLine();
  988. // Response line must start with 250 or otherwise it's error response
  989. if (!responseLine.StartsWith("250"))
  990. {
  991. throw new Exception(responseLine);
  992. }
  993. }
  994. /// <summary>
  995. /// Begin setting sender.
  996. /// </summary>
  997. /// <param name="senderEmail">Sender email address what is reported to smtp server.</param>
  998. /// <param name="messageSize">Message size in bytes or -1 if message size isn't known.</param>
  999. /// <param name="callback">Callback to be called if command ends.</param>
  1000. public void BeginSetSender(string senderEmail, long messageSize, CommadCompleted callback)
  1001. {
  1002. if (!m_Connected)
  1003. {
  1004. throw new Exception("You must connect first");
  1005. }
  1006. /* RFC 2821 4.1.1.2 MAIL
  1007. * Examples:
  1008. * MAIL FROM:<ivx@lumisoft.ee>
  1009. *
  1010. * RFC 1870 adds optional SIZE keyword support.
  1011. * SIZE keyword may only be used if it's reported in EHLO command response.
  1012. * Examples:
  1013. * MAIL FROM:<ivx@lumisoft.ee> SIZE=1000
  1014. */
  1015. if (m_Supports_Size && messageSize > -1)
  1016. {
  1017. m_pSocket.BeginWriteLine("MAIL FROM:<" + senderEmail + "> SIZE=" + messageSize,
  1018. callback,
  1019. OnMailSendFinished);
  1020. }
  1021. else
  1022. {
  1023. m_pSocket.BeginWriteLine("MAIL FROM:<" + senderEmail + ">", callback, OnMailSendFinished);
  1024. }
  1025. }
  1026. /// <summary>
  1027. /// Does RCPT TO: command.
  1028. /// </summary>
  1029. /// <param name="recipientEmail">Recipient email address.</param>
  1030. public void AddRecipient(string recipientEmail)
  1031. {
  1032. if (!m_Connected)
  1033. {
  1034. throw new Exception("You must connect first");
  1035. }
  1036. /* RFC 2821 4.1.1.2 RCPT
  1037. * Examples:
  1038. * RCPT TO:<ivx@lumisoft.ee>
  1039. */
  1040. m_pSocket.WriteLine("RCPT TO:<" + recipientEmail + ">");
  1041. string responseLine = m_pSocket.ReadLine();
  1042. // Response line must start with 250 or otherwise it's error response
  1043. if (!responseLine.StartsWith("250"))
  1044. {
  1045. throw new Exception(responseLine);
  1046. }
  1047. }
  1048. /// <summary>
  1049. /// Begin adding recipient.
  1050. /// </summary>
  1051. /// <param name="recipientEmail">Recipient email address.</param>
  1052. /// <param name="callback">Callback to be called if command ends.</param>
  1053. public void BeginAddRecipient(string recipientEmail, CommadCompleted callback)
  1054. {
  1055. if (!m_Connected)
  1056. {
  1057. throw new Exception("You must connect first");
  1058. }
  1059. /* RFC 2821 4.1.1.2 RCPT
  1060. * Examples:
  1061. * RCPT TO:<ivx@lumisoft.ee>
  1062. */
  1063. m_pSocket.BeginWriteLine("RCPT TO:<" + recipientEmail + ">", callback, OnRcptSendFinished);
  1064. }
  1065. /// <summary>
  1066. /// Sends message to server. NOTE: Message sending starts from message stream current posision.
  1067. /// </summary>
  1068. /// <param name="message">Message what will be sent to server. NOTE: Message sending starts from message stream current posision.</param>
  1069. public void SendMessage(Stream message)
  1070. {
  1071. if (!m_Connected)
  1072. {
  1073. throw new Exception("You must connect first");
  1074. }
  1075. /* RFC 2821 4.1.1.4 DATA
  1076. * Notes:
  1077. * Message must be period handled for DATA command. This meas if message line starts with .,
  1078. * additional .(period) must be added.
  1079. * Message send is ended with <CRLF>.<CRLF>.
  1080. * Examples:
  1081. * C: DATA<CRLF>
  1082. * S: 354 Start sending message, end with <crlf>.<crlf><CRLF>
  1083. * C: send_message
  1084. * C: <CRLF>.<CRLF>
  1085. */
  1086. /* RFC 3030 BDAT
  1087. * Syntax:BDAT<SP>message_size_in_bytes<SP>LAST<CRLF>
  1088. *
  1089. * Exapmle:
  1090. * C: BDAT 1000 LAST<CRLF>
  1091. * C: send_1000_byte_message
  1092. * S: 250 OK<CRLF>
  1093. *
  1094. */
  1095. if (m_Supports_Bdat)
  1096. {
  1097. m_pSocket.WriteLine("BDAT " + (message.Length - message.Position) + " LAST");
  1098. m_pSocket.Write(message);
  1099. string responseLine = m_pSocket.ReadLine();
  1100. // Response line must start with 250 or otherwise it's error response
  1101. if (!responseLine.StartsWith("250"))
  1102. {
  1103. throw new Exception(responseLine);
  1104. }
  1105. }
  1106. else
  1107. {
  1108. m_pSocket.WriteLine("DATA");
  1109. string responseLine = m_pSocket.ReadLine();
  1110. // Response line must start with 334 or otherwise it's error response
  1111. if (!responseLine.StartsWith("354"))
  1112. {
  1113. throw new Exception(responseLine);
  1114. }
  1115. m_pSocket.WritePeriodTerminated(message);
  1116. responseLine = m_pSocket.ReadLine();
  1117. // Response line must start with 250 or otherwise it's error response
  1118. if (!responseLine.StartsWith("250"))
  1119. {
  1120. throw new Exception(responseLine);
  1121. }
  1122. }
  1123. }
  1124. /// <summary>
  1125. /// Starts sending message.
  1126. /// </summary>
  1127. /// <param name="message">Message what will be sent to server. NOTE: Message sending starts from message stream current posision.</param>
  1128. /// <param name="callback">Callback to be called if command ends.</param>
  1129. public void BeginSendMessage(Stream message, CommadCompleted callback)
  1130. {
  1131. if (!m_Connected)
  1132. {
  1133. throw new Exception("You must connect first");
  1134. }
  1135. /* RFC 2821 4.1.1.4 DATA
  1136. * Notes:
  1137. * Message must be period handled for DATA command. This meas if message line starts with .,
  1138. * additional .(period) must be added.
  1139. * Message send is ended with <CRLF>.<CRLF>.
  1140. * Examples:
  1141. * C: DATA<CRLF>
  1142. * S: 354 Start sending message, end with <crlf>.<crlf><CRLF>
  1143. * C: send_message
  1144. * C: <CRLF>.<CRLF>
  1145. */
  1146. /* RFC 3030 BDAT
  1147. * Syntax:BDAT<SP>message_size_in_bytes<SP>LAST<CRLF>
  1148. *
  1149. * Exapmle:
  1150. * C: BDAT 1000 LAST<CRLF>
  1151. * C: send_1000_byte_message
  1152. * S: 250 OK<CRLF>
  1153. *
  1154. */
  1155. if (m_Supports_Bdat)
  1156. {
  1157. m_pSocket.BeginWriteLine("BDAT " + (message.Length - message.Position) + " LAST",
  1158. new object[] {message, callback},
  1159. OnBdatSendFinished);
  1160. }
  1161. else
  1162. {
  1163. m_pSocket.BeginWriteLine("DATA", new object[] {message, callback}, OnDataSendFinished);
  1164. }
  1165. }
  1166. /// <summary>
  1167. /// Send RSET command to SMTP server, resets SMTP session.
  1168. /// </summary>
  1169. public void Reset()
  1170. {
  1171. if (!m_Connected)
  1172. {
  1173. throw new Exception("You must connect first");
  1174. }
  1175. m_pSocket.WriteLine("RSET");
  1176. string responseLine = m_pSocket.ReadLine();
  1177. if (!responseLine.StartsWith("250"))
  1178. {
  1179. throw new Exception(responseLine);
  1180. }
  1181. }
  1182. /// <summary>
  1183. /// Gets specified email domain possible connect points. Values are in priority descending order.
  1184. /// </summary>
  1185. /// <param name="domain">Email address or domain name.</param>
  1186. /// <returns>Returns possible email server connection points.</returns>
  1187. public IPAddress[] GetDestinations(string domain)
  1188. {
  1189. /*
  1190. 1) Add MX records
  1191. 2) Add A records
  1192. */
  1193. // We have email address, just get domain from it.
  1194. if (domain.IndexOf('@') > -1)
  1195. {
  1196. domain = domain.Substring(domain.IndexOf('@') + 1);
  1197. }
  1198. List<IPAddress> retVal = new List<IPAddress>();
  1199. Dns_Client dns = new Dns_Client();
  1200. Dns_Client.DnsServers = DnsServers;
  1201. DnsServerResponse response = dns.Query(domain, QTYPE.MX);
  1202. // Add MX
  1203. foreach (DNS_rr_MX mx in response.GetMXRecords())
  1204. {
  1205. try
  1206. {
  1207. IPAddress[] ips = Dns.GetHostAddresses(mx.Host);
  1208. foreach (IPAddress ip in ips)
  1209. {
  1210. if (!retVal.Contains(ip))
  1211. {
  1212. retVal.Add(ip);
  1213. }
  1214. }
  1215. }
  1216. catch
  1217. {
  1218. // Probably wrong MX record, no A reocrd for it, so we don't get IP. Just skip it.
  1219. }
  1220. }
  1221. // Add A records only if no MX records.
  1222. if (retVal.Count == 0)
  1223. {
  1224. response = dns.Query(domain, QTYPE.A);
  1225. foreach (DNS_rr_A a in response.GetARecords())
  1226. {
  1227. IPAddress ip = a.IP;
  1228. if (!retVal.Contains(ip))
  1229. {
  1230. retVal.Add(ip);
  1231. }
  1232. }
  1233. }
  1234. return retVal.ToArray();
  1235. }
  1236. #endregion
  1237. #region Utility methods
  1238. /// <summary>
  1239. /// Is called from ThreadPool Thread. This method just call synchrounous Connect.
  1240. /// </summary>
  1241. /// <param name="tag"></param>
  1242. private void BeginConnect_workerThread(object tag)
  1243. {
  1244. CommadCompleted callback = (CommadCompleted) ((object[]) tag)[4];
  1245. try
  1246. {
  1247. IPEndPoint localEndpoint = (IPEndPoint) ((object[]) tag)[0];
  1248. string host = (string) ((object[]) tag)[1];
  1249. int port = (int) ((object[]) tag)[2];
  1250. bool ssl = (bool) ((object[]) tag)[3];
  1251. Connect(localEndpoint, host, port, ssl);
  1252. // Connect completed susscessfully, call callback method.
  1253. callback(SocketCallBackResult.Ok, null);
  1254. }
  1255. catch (Exception x)
  1256. {
  1257. // Pass exception to callback method
  1258. callback(SocketCallBackResult.Exception, x);
  1259. }
  1260. }
  1261. /// <summary>
  1262. /// Is called from ThreadPool Thread. This method just call synchrounous StartTLS.
  1263. /// </summary>
  1264. /// <param name="tag">User data.</param>
  1265. private void BeginStartTLS_workerThread(object tag)
  1266. {
  1267. CommadCompleted callback = (CommadCompleted) tag;
  1268. try
  1269. {
  1270. StartTLS();
  1271. // Connect completed susscessfully, call callback method.
  1272. callback(SocketCallBackResult.Ok, null);
  1273. }
  1274. catch (Exception x)
  1275. {
  1276. // Pass exception to callback method
  1277. callback(SocketCallBackResult.Exception, x);
  1278. }
  1279. }
  1280. /// <summary>
  1281. /// Is called when smtp client has finished EHLO command sending.
  1282. /// </summary>
  1283. /// <param name="result"></param>
  1284. /// <param name="count"></param>
  1285. /// <param name="exception"></param>
  1286. /// <param name="tag"></param>
  1287. private void OnEhloSendFinished(SocketCallBackResult result,
  1288. long count,
  1289. Exception exception,
  1290. object tag)
  1291. {
  1292. CommadCompleted callback = (CommadCompleted) (((object[]) tag)[1]);
  1293. try
  1294. {
  1295. if (result == SocketCallBackResult.Ok)
  1296. {
  1297. // Begin reading server EHLO command response
  1298. MemoryStream ms = new MemoryStream();
  1299. m_pSocket.BeginReadLine(ms,
  1300. 1000,
  1301. new[] {((object[]) tag)[0], callback, ms},
  1302. OnEhloReadServerResponseFinished);
  1303. }
  1304. else
  1305. {
  1306. HandleSocketError(result, exception);
  1307. }
  1308. }
  1309. catch (Exception x)
  1310. {
  1311. // Pass exception to callback method
  1312. callback(SocketCallBackResult.Exception, x);
  1313. }
  1314. }
  1315. /// <summary>
  1316. /// Is called when smtp client has finished reading EHLO command server response line.
  1317. /// </summary>
  1318. /// <param name="result"></param>
  1319. /// <param name="count"></param>
  1320. /// <param name="exception"></param>
  1321. /// <param name="tag"></param>
  1322. private void OnEhloReadServerResponseFinished(SocketCallBackResult result,
  1323. long count,
  1324. Exception exception,
  1325. object tag)
  1326. {
  1327. CommadCompleted callback = (CommadCompleted) (((object[]) tag)[1]);
  1328. try
  1329. {
  1330. if (result == SocketCallBackResult.Ok)
  1331. {
  1332. string responseLine =
  1333. Encoding.ASCII.GetString(((MemoryStream) (((object[]) tag)[2])).ToArray());
  1334. /* RFC 2821 4.1.1.1 EHLO
  1335. * Examples:
  1336. * 250-domain<SP>free_text<CRLF>
  1337. * 250-EHLO_keyword<CRLF>
  1338. * 250<SP>EHLO_keyword<CRLF>
  1339. *
  1340. * 250<SP> specifies that last EHLO response line.
  1341. */
  1342. // Response line must start with 250 or otherwise it's error response
  1343. if (!responseLine.StartsWith("250"))
  1344. {
  1345. // Server isn't required to support EHLO, try HELO
  1346. string hostName = (string) (((object[]) tag)[0]);
  1347. m_pSocket.BeginWriteLine("HELO " + hostName, callback, OnHeloSendFinished);
  1348. }
  1349. else
  1350. {
  1351. //---- Store supported ESMTP features --------------------//
  1352. if (responseLine.ToLower().IndexOf("size") > -1)
  1353. {
  1354. m_Supports_Size = true;
  1355. }
  1356. else if (responseLine.ToLower().IndexOf("chunking") > -1)
  1357. {
  1358. m_Supports_Bdat = true;
  1359. }
  1360. else if (responseLine.ToLower().IndexOf("cram-md5") > -1)
  1361. {
  1362. m_Supports_CramMd5 = true;
  1363. }
  1364. else if (responseLine.ToLower().IndexOf("login") > -1)
  1365. {
  1366. m_Supports_Login = true;
  1367. }
  1368. //--------------------------------------------------------//
  1369. // This isn't last EHLO response line
  1370. if (!responseLine.StartsWith("250 "))
  1371. {
  1372. MemoryStream ms = new MemoryStream();
  1373. m_pSocket.BeginReadLine(ms,
  1374. 1000,
  1375. new[] {(((object[]) tag)[0]), callback, ms},
  1376. OnEhloReadServerResponseFinished);
  1377. }
  1378. else
  1379. {
  1380. // EHLO completed susscessfully, call callback method.
  1381. callback(SocketCallBackResult.Ok, null);
  1382. }
  1383. }
  1384. }
  1385. else
  1386. {
  1387. HandleSocketError(result, exception);
  1388. }
  1389. }
  1390. catch (Exception x)
  1391. {
  1392. // Pass exception to callback method
  1393. callback(SocketCallBackResult.Exception, x);
  1394. }
  1395. }
  1396. /// <summary>
  1397. /// Is called when smtp client has finished HELO command sending.
  1398. /// </summary>
  1399. /// <param name="result"></param>
  1400. /// <param name="count"></param>
  1401. /// <param name="exception"></param>
  1402. /// <param name="tag"></param>
  1403. private void OnHeloSendFinished(SocketCallBackResult result,
  1404. long count,
  1405. Exception exception,
  1406. object tag)
  1407. {
  1408. CommadCompleted callback = (CommadCompleted) tag;
  1409. try
  1410. {
  1411. if (result == SocketCallBackResult.Ok)
  1412. {
  1413. // Begin reading server HELO command response
  1414. MemoryStream ms = new MemoryStream();
  1415. m_pSocket.BeginReadLine(ms,
  1416. 1000,
  1417. new object[] {callback, ms},
  1418. OnHeloReadServerResponseFinished);
  1419. }
  1420. else
  1421. {
  1422. HandleSocketError(result, exception);
  1423. }
  1424. }
  1425. catch (Exception x)
  1426. {
  1427. // Pass exception to callback method
  1428. callback(SocketCallBackResult.Exception, x);
  1429. }
  1430. }
  1431. /// <summary>
  1432. /// Is called when smtp client has finished reading EHLO command server response line.
  1433. /// </summary>
  1434. /// <param name="result"></param>
  1435. /// <param name="count"></param>
  1436. /// <param name="exception"></param>
  1437. /// <param name="tag"></param>
  1438. private void OnHeloReadServerResponseFinished(SocketCallBackResult result,
  1439. long count,
  1440. Exception exception,
  1441. object tag)
  1442. {
  1443. CommadCompleted callback = (CommadCompleted) (((object[]) tag)[0]);
  1444. try
  1445. {
  1446. if (result == SocketCallBackResult.Ok)
  1447. {
  1448. string responseLine =
  1449. Encoding.ASCII.GetString(((MemoryStream) (((object[]) tag)[1])).ToArray());
  1450. /* RFC 2821 4.1.1.1 HELO
  1451. * Examples:
  1452. * 250<SP>domain<SP>free_text<CRLF>
  1453. *
  1454. */
  1455. // Response line must start with 250 or otherwise it's error response
  1456. if (!responseLine.StartsWith("250"))
  1457. {
  1458. throw new Exception(responseLine);
  1459. }
  1460. else
  1461. {
  1462. // EHLO completed susscessfully, call callback method.
  1463. callback(SocketCallBackResult.Ok, null);
  1464. }
  1465. }
  1466. else
  1467. {
  1468. HandleSocketError(result, exception);
  1469. }
  1470. }
  1471. catch (Exception x)
  1472. {
  1473. // Pass exception to callback method
  1474. callback(SocketCallBackResult.Exception, x);
  1475. }
  1476. }
  1477. /// <summary>
  1478. /// Is called when smtp client has finished AUTH CRAM-MD5 command sending.
  1479. /// </summary>
  1480. /// <param name="result"></param>
  1481. /// <param name="count"></param>
  1482. /// <param name="exception"></param>
  1483. /// <param name="tag"></param>
  1484. private void OnAuthCramMd5SendFinished(SocketCallBackResult result,
  1485. long count,
  1486. Exception exception,
  1487. object tag)
  1488. {
  1489. Auth_state_data stateData = (Auth_state_data) tag;
  1490. try
  1491. {
  1492. if (result == SocketCallBackResult.Ok)
  1493. {
  1494. MemoryStream ms = new MemoryStream();
  1495. stateData.Tag = ms;
  1496. m_pSocket.BeginReadLine(ms, 1000, stateData, OnAuthCramMd5ReadServerResponseFinished);
  1497. }
  1498. else
  1499. {
  1500. HandleSocketError(result, exception);
  1501. }
  1502. }
  1503. catch (Exception x)
  1504. {
  1505. // Pass exception to callback method
  1506. stateData.Callback(SocketCallBackResult.Exception, x);
  1507. }
  1508. }
  1509. /// <summary>
  1510. /// Is called when smtp client has finished reading AUTH CRAM-MD% server response line.
  1511. /// </summary>
  1512. /// <param name="result"></param>
  1513. /// <param name="count"></param>
  1514. /// <param name="exception"></param>
  1515. /// <param name="tag"></param>
  1516. private void OnAuthCramMd5ReadServerResponseFinished(SocketCallBackResult result,
  1517. long count,
  1518. Exception exception,
  1519. object tag)
  1520. {
  1521. Auth_state_data stateData = (Auth_state_data) tag;
  1522. try
  1523. {
  1524. if (result == SocketCallBackResult.Ok)
  1525. {
  1526. string responseLine = Encoding.ASCII.GetString(((MemoryStream) stateData.Tag).ToArray());
  1527. // Response line must start with 334 or otherwise it's error response
  1528. if (!responseLine.StartsWith("334"))
  1529. {
  1530. throw new Exception(responseLine);
  1531. }
  1532. else
  1533. {
  1534. string md5HashKey =
  1535. Encoding.ASCII.GetString(Convert.FromBase64String(responseLine.Split(' ')[1]));
  1536. HMACMD5 kMd5 = new HMACMD5(Encoding.ASCII.GetBytes(stateData.Password));
  1537. byte[] md5HashByte = kMd5.ComputeHash(Encoding.ASCII.GetBytes(md5HashKey));
  1538. string hashedPwd = BitConverter.ToString(md5HashByte).ToLower().Replace("-", "");
  1539. // Start sending user name to server
  1540. m_pSocket.BeginWriteLine(
  1541. Convert.ToBase64String(
  1542. Encoding.ASCII.GetBytes(stateData.UserName + " " + hashedPwd)),
  1543. stateData,
  1544. OnAuthCramMd5UserPwdSendFinished);
  1545. }
  1546. }
  1547. else
  1548. {
  1549. HandleSocketError(result, exception);
  1550. }
  1551. }
  1552. catch (Exception x)
  1553. {
  1554. // Pass exception to callback method
  1555. stateData.Callback(SocketCallBackResult.Exception, x);
  1556. }
  1557. }
  1558. /// <summary>
  1559. /// Is called when smtp client has finished sending username and password to smtp server.
  1560. /// </summary>
  1561. /// <param name="result"></param>
  1562. /// <param name="count"></param>
  1563. /// <param name="exception"></param>
  1564. /// <param name="tag"></param>
  1565. private void OnAuthCramMd5UserPwdSendFinished(SocketCallBackResult result,
  1566. long count,
  1567. Exception exception,
  1568. object tag)
  1569. {
  1570. Auth_state_data stateData = (Auth_state_data) tag;
  1571. try
  1572. {
  1573. if (result == SocketCallBackResult.Ok)
  1574. {
  1575. MemoryStream ms = new MemoryStream();
  1576. stateData.Tag = ms;
  1577. m_pSocket.BeginReadLine(ms,
  1578. 1000,
  1579. stateData,
  1580. OnAuthCramMd5UserPwdReadServerResponseFinished);
  1581. }
  1582. else
  1583. {
  1584. HandleSocketError(result, exception);
  1585. }
  1586. }
  1587. catch (Exception x)
  1588. {
  1589. // Pass exception to callback method
  1590. stateData.Callback(SocketCallBackResult.Exception, x);
  1591. }
  1592. }
  1593. /// <summary>
  1594. /// Is called when smtp client has finished reading user name and password send server response line.
  1595. /// </summary>
  1596. /// <param name="result"></param>
  1597. /// <param name="count"></param>
  1598. /// <param name="exception"></param>
  1599. /// <param name="tag"></param>
  1600. private void OnAuthCramMd5UserPwdReadServerResponseFinished(SocketCallBackResult result,
  1601. long count,
  1602. Exception exception,
  1603. object tag)
  1604. {
  1605. Auth_state_data stateData = (Auth_state_data) tag;
  1606. try
  1607. {
  1608. if (result == SocketCallBackResult.Ok)
  1609. {
  1610. string responseLine = Encoding.ASCII.GetString(((MemoryStream) stateData.Tag).ToArray());
  1611. // Response line must start with 235 or otherwise it's error response
  1612. if (!responseLine.StartsWith("235"))
  1613. {
  1614. throw new Exception(responseLine);
  1615. }
  1616. else
  1617. {
  1618. m_Authenticated = true;
  1619. if (m_Authenticated && m_pSocket.Logger != null)
  1620. {
  1621. m_pSocket.Logger.UserName = stateData.UserName;
  1622. }
  1623. // AUTH CRAM-MD5 completed susscessfully, call callback method.
  1624. stateData.Callback(SocketCallBackResult.Ok, null);
  1625. }
  1626. }
  1627. else
  1628. {
  1629. HandleSocketError(result, exception);
  1630. }
  1631. }
  1632. catch (Exception x)
  1633. {
  1634. // Pass exception to callback method
  1635. stateData.Callback(SocketCallBackResult.Exception, x);
  1636. }
  1637. }
  1638. /// <summary>
  1639. /// Is called when smtp client has finished AUTH LOGIN command sending.
  1640. /// </summary>
  1641. /// <param name="result"></param>
  1642. /// <param name="count"></param>
  1643. /// <param name="exception"></param>
  1644. /// <param name="tag"></param>
  1645. private void OnAuthLoginSendFinished(SocketCallBackResult result,
  1646. long count,
  1647. Exception exception,
  1648. object tag)
  1649. {
  1650. Auth_state_data stateData = (Auth_state_data) tag;
  1651. try
  1652. {
  1653. if (result == SocketCallBackResult.Ok)
  1654. {
  1655. MemoryStream ms = new MemoryStream();
  1656. stateData.Tag = ms;
  1657. m_pSocket.BeginReadLine(ms, 1000, stateData, OnAuthLoginReadServerResponseFinished);
  1658. }
  1659. else
  1660. {
  1661. HandleSocketError(result, exception);
  1662. }
  1663. }
  1664. catch (Exception x)
  1665. {
  1666. // Pass exception to callback method
  1667. stateData.Callback(SocketCallBackResult.Exception, x);
  1668. }
  1669. }
  1670. /// <summary>
  1671. /// Is called when smtp client has finished reading MAIL FROM: command server response line.
  1672. /// </summary>
  1673. /// <param name="result"></param>
  1674. /// <param name="count"></param>
  1675. /// <param name="exception"></param>
  1676. /// <param name="tag"></param>
  1677. private void OnAuthLoginReadServerResponseFinished(SocketCallBackResult result,
  1678. long count,
  1679. Exception exception,
  1680. object tag)
  1681. {
  1682. Auth_state_data stateData = (Auth_state_data) tag;
  1683. try
  1684. {
  1685. if (result == SocketCallBackResult.Ok)
  1686. {
  1687. string responseLine = Encoding.ASCII.GetString(((MemoryStream) (stateData.Tag)).ToArray());
  1688. // Response line must start with 334 or otherwise it's error response
  1689. if (!responseLine.StartsWith("334"))
  1690. {
  1691. throw new Exception(responseLine);
  1692. }
  1693. else
  1694. {
  1695. // Start sending user name to server
  1696. m_pSocket.BeginWriteLine(
  1697. Convert.ToBase64String(Encoding.ASCII.GetBytes(stateData.UserName)),
  1698. stateData,
  1699. OnAuthLoginUserSendFinished);
  1700. }
  1701. }
  1702. else
  1703. {
  1704. HandleSocketError(result, exception);
  1705. }
  1706. }
  1707. catch (Exception x)
  1708. {
  1709. // Pass exception to callback method
  1710. stateData.Callback(SocketCallBackResult.Exception, x);
  1711. }
  1712. }
  1713. /// <summary>
  1714. /// Is called when smtp client has finished sending user name to SMTP server.
  1715. /// </summary>
  1716. /// <param name="result"></param>
  1717. /// <param name="count"></param>
  1718. /// <param name="exception"></param>
  1719. /// <param name="tag"></param>
  1720. private void OnAuthLoginUserSendFinished(SocketCallBackResult result,
  1721. long count,
  1722. Exception exception,
  1723. object tag)
  1724. {
  1725. Auth_state_data stateData = (Auth_state_data) tag;
  1726. try
  1727. {
  1728. if (result == SocketCallBackResult.Ok)
  1729. {
  1730. MemoryStream ms = new MemoryStream();
  1731. stateData.Tag = ms;
  1732. m_pSocket.BeginReadLine(ms, 1000, stateData, OnAuthLoginUserReadServerResponseFinished);
  1733. }
  1734. else
  1735. {
  1736. HandleSocketError(result, exception);
  1737. }
  1738. }
  1739. catch (Exception x)
  1740. {
  1741. // Pass exception to callback method
  1742. stateData.Callback(SocketCallBackResult.Exception, x);
  1743. }
  1744. }
  1745. /// <summary>
  1746. /// Is called when smtp client has finished reading AUTH LOGIN user send server response line.
  1747. /// </summary>
  1748. /// <param name="result"></param>
  1749. /// <param name="count"></param>
  1750. /// <param name="exception"></param>
  1751. /// <param name="tag"></param>
  1752. private void OnAuthLoginUserReadServerResponseFinished(SocketCallBackResult result,
  1753. long count,
  1754. Exception exception,
  1755. object tag)
  1756. {
  1757. Auth_state_data stateData = (Auth_state_data) tag;
  1758. try
  1759. {
  1760. if (result == SocketCallBackResult.Ok)
  1761. {
  1762. string responseLine = Encoding.ASCII.GetString(((MemoryStream) stateData.Tag).ToArray());
  1763. // Response line must start with 334 or otherwise it's error response
  1764. if (!responseLine.StartsWith("334"))
  1765. {
  1766. throw new Exception(responseLine);
  1767. }
  1768. else
  1769. {
  1770. // Start sending password to server
  1771. m_pSocket.BeginWriteLine(
  1772. Convert.ToBase64String(Encoding.ASCII.GetBytes(stateData.Password)),
  1773. stateData,
  1774. OnAuthLoginPasswordSendFinished);
  1775. }
  1776. }
  1777. else
  1778. {
  1779. HandleSocketError(result, exception);
  1780. }
  1781. }
  1782. catch (Exception x)
  1783. {
  1784. // Pass exception to callback method
  1785. stateData.Callback(SocketCallBackResult.Exception, x);
  1786. }
  1787. }
  1788. /// <summary>
  1789. /// Is called when smtp client has finished sending password to SMTP server.
  1790. /// </summary>
  1791. /// <param name="result"></param>
  1792. /// <param name="count"></param>
  1793. /// <param name="exception"></param>
  1794. /// <param name="tag"></param>
  1795. private void OnAuthLoginPasswordSendFinished(SocketCallBackResult result,
  1796. long count,
  1797. Exception exception,
  1798. object tag)
  1799. {
  1800. Auth_state_data stateData = (Auth_state_data) tag;
  1801. try
  1802. {
  1803. if (result == SocketCallBackResult.Ok)
  1804. {
  1805. MemoryStream ms = new MemoryStream();
  1806. stateData.Tag = ms;
  1807. m_pSocket.BeginReadLine(ms, 1000, stateData, OnAuthLoginPwdReadServerResponseFinished);
  1808. }
  1809. else
  1810. {
  1811. HandleSocketError(result, exception);
  1812. }
  1813. }
  1814. catch (Exception x)
  1815. {
  1816. // Pass exception to callback method
  1817. stateData.Callback(SocketCallBackResult.Exception, x);
  1818. }
  1819. }
  1820. /// <summary>
  1821. /// Is called when smtp client has finished reading AUTH LOGIN password send server response line.
  1822. /// </summary>
  1823. /// <param name="result"></param>
  1824. /// <param name="count"></param>
  1825. /// <param name="exception"></param>
  1826. /// <param name="tag"></param>
  1827. private void OnAuthLoginPwdReadServerResponseFinished(SocketCallBackResult result,
  1828. long count,
  1829. Exception exception,
  1830. object tag)
  1831. {
  1832. Auth_state_data stateData = (Auth_state_data) tag;
  1833. try
  1834. {
  1835. if (result == SocketCallBackResult.Ok)
  1836. {
  1837. string responseLine = Encoding.ASCII.GetString(((MemoryStream) stateData.Tag).ToArray());
  1838. // Response line must start with 235 or otherwise it's error response
  1839. if (!responseLine.StartsWith("235"))
  1840. {
  1841. throw new Exception(responseLine);
  1842. }
  1843. else
  1844. {
  1845. m_Authenticated = true;
  1846. if (m_Authenticated && m_pSocket.Logger != null)
  1847. {
  1848. m_pSocket.Logger.UserName = stateData.UserName;
  1849. }
  1850. // AUTH LOGIN completed susscessfully, call callback method.
  1851. stateData.Callback(SocketCallBackResult.Ok, null);
  1852. }
  1853. }
  1854. else
  1855. {
  1856. HandleSocketError(result, exception);
  1857. }
  1858. }
  1859. catch (Exception x)
  1860. {
  1861. // Pass exception to callback method
  1862. stateData.Callback(SocketCallBackResult.Exception, x);
  1863. }
  1864. }
  1865. /// <summary>
  1866. /// Is called when smtp client has finished MAIL FROM: command sending.
  1867. /// </summary>
  1868. /// <param name="result"></param>
  1869. /// <param name="count"></param>
  1870. /// <param name="exception"></param>
  1871. /// <param name="tag"></param>
  1872. private void OnMailSendFinished(SocketCallBackResult result,
  1873. long count,
  1874. Exception exception,
  1875. object tag)
  1876. {
  1877. CommadCompleted callback = (CommadCompleted) tag;
  1878. try
  1879. {
  1880. if (result == SocketCallBackResult.Ok)
  1881. {
  1882. MemoryStream ms = new MemoryStream();
  1883. m_pSocket.BeginReadLine(ms,
  1884. 1000,
  1885. new object[] {callback, ms},
  1886. OnMailReadServerResponseFinished);
  1887. }
  1888. else
  1889. {
  1890. HandleSocketError(result, exception);
  1891. }
  1892. }
  1893. catch (Exception x)
  1894. {
  1895. // Pass exception to callback method
  1896. callback(SocketCallBackResult.Exception, x);
  1897. }
  1898. }
  1899. /// <summary>
  1900. /// Is called when smtp client has finished reading MAIL FROM: command server response line.
  1901. /// </summary>
  1902. /// <param name="result"></param>
  1903. /// <param name="count"></param>
  1904. /// <param name="exception"></param>
  1905. /// <param name="tag"></param>
  1906. private void OnMailReadServerResponseFinished(SocketCallBackResult result,
  1907. long count,
  1908. Exception exception,
  1909. object tag)
  1910. {
  1911. CommadCompleted callback = (CommadCompleted) (((object[]) tag)[0]);
  1912. try
  1913. {
  1914. if (result == SocketCallBackResult.Ok)
  1915. {
  1916. string responseLine =
  1917. Encoding.ASCII.GetString(((MemoryStream) (((object[]) tag)[1])).ToArray());
  1918. // Response line must start with 250 or otherwise it's error response
  1919. if (!responseLine.StartsWith("250"))
  1920. {
  1921. throw new Exception(responseLine);
  1922. }
  1923. else
  1924. {
  1925. // MAIL FROM: completed susscessfully, call callback method.
  1926. callback(SocketCallBackResult.Ok, null);
  1927. }
  1928. }
  1929. else
  1930. {
  1931. HandleSocketError(result, exception);
  1932. }
  1933. }
  1934. catch (Exception x)
  1935. {
  1936. // Pass exception to callback method
  1937. callback(SocketCallBackResult.Exception, x);
  1938. }
  1939. }
  1940. /// <summary>
  1941. /// Is called when smtp client has finished RCPT TO: command sending.
  1942. /// </summary>
  1943. /// <param name="result"></param>
  1944. /// <param name="count"></param>
  1945. /// <param name="exception"></param>
  1946. /// <param name="tag"></param>
  1947. private void OnRcptSendFinished(SocketCallBackResult result,
  1948. long count,
  1949. Exception exception,
  1950. object tag)
  1951. {
  1952. CommadCompleted callback = (CommadCompleted) tag;
  1953. try
  1954. {
  1955. if (result == SocketCallBackResult.Ok)
  1956. {
  1957. MemoryStream ms = new MemoryStream();
  1958. m_pSocket.BeginReadLine(ms,
  1959. 1000,
  1960. new object[] {callback, ms},
  1961. OnRcptReadServerResponseFinished);
  1962. }
  1963. else
  1964. {
  1965. HandleSocketError(result, exception);
  1966. }
  1967. }
  1968. catch (Exception x)
  1969. {
  1970. // Pass exception to callback method
  1971. callback(SocketCallBackResult.Exception, x);
  1972. }
  1973. }
  1974. /// <summary>
  1975. /// Is called when smtp client has finished reading RCPT TO: command server response line.
  1976. /// </summary>
  1977. /// <param name="result"></param>
  1978. /// <param name="count"></param>
  1979. /// <param name="exception"></param>
  1980. /// <param name="tag"></param>
  1981. private void OnRcptReadServerResponseFinished(SocketCallBackResult result,
  1982. long count,
  1983. Exception exception,
  1984. object tag)
  1985. {
  1986. CommadCompleted callback = (CommadCompleted) (((object[]) tag)[0]);
  1987. try
  1988. {
  1989. if (result == SocketCallBackResult.Ok)
  1990. {
  1991. string responseLine =
  1992. Encoding.ASCII.GetString(((MemoryStream) (((object[]) tag)[1])).ToArray());
  1993. // Response line must start with 250 or otherwise it's error response
  1994. if (!responseLine.StartsWith("250"))
  1995. {
  1996. throw new Exception(responseLine);
  1997. }
  1998. else
  1999. {
  2000. // RCPT TO: completed susscessfully, call callback method.
  2001. callback(SocketCallBackResult.Ok, null);
  2002. }
  2003. }
  2004. else
  2005. {
  2006. HandleSocketError(result, exception);
  2007. }
  2008. }
  2009. catch (Exception x)
  2010. {
  2011. // Pass exception to callback method
  2012. callback(SocketCallBackResult.Exception, x);
  2013. }
  2014. }
  2015. /// <summary>
  2016. /// Is called when smtp client has finished BDAT command sending.
  2017. /// </summary>
  2018. /// <param name="result"></param>
  2019. /// <param name="count"></param>
  2020. /// <param name="exception"></param>
  2021. /// <param name="tag"></param>
  2022. private void OnBdatSendFinished(SocketCallBackResult result,
  2023. long count,
  2024. Exception exception,
  2025. object tag)
  2026. {
  2027. CommadCompleted callback = (CommadCompleted) (((object[]) tag)[1]);
  2028. try
  2029. {
  2030. if (result == SocketCallBackResult.Ok)
  2031. {
  2032. // BDAT command successfully sent to SMTP server, start sending DATA.
  2033. m_pSocket.BeginWrite((Stream) (((object[]) tag)[0]), callback, OnBdatDataSendFinished);
  2034. }
  2035. else
  2036. {
  2037. HandleSocketError(result, exception);
  2038. }
  2039. }
  2040. catch (Exception x)
  2041. {
  2042. // Pass exception to callback method
  2043. callback(SocketCallBackResult.Exception, x);
  2044. }
  2045. }
  2046. /// <summary>
  2047. /// Is called when smtp client has finished sending BDAT message data to smtp server.
  2048. /// </summary>
  2049. /// <param name="result"></param>
  2050. /// <param name="count"></param>
  2051. /// <param name="exception"></param>
  2052. /// <param name="tag"></param>
  2053. private void OnBdatDataSendFinished(SocketCallBackResult result,
  2054. long count,
  2055. Exception exception,
  2056. object tag)
  2057. {
  2058. CommadCompleted callback = (CommadCompleted) tag;
  2059. try
  2060. {
  2061. if (result == SocketCallBackResult.Ok)
  2062. {
  2063. // BDAT message data successfully sent to SMTP server, start reading server response
  2064. MemoryStream ms = new MemoryStream();
  2065. m_pSocket.BeginReadLine(ms,
  2066. 1000,
  2067. new object[] {callback, ms},
  2068. OnBdatReadServerResponseFinished);
  2069. }
  2070. else
  2071. {
  2072. HandleSocketError(result, exception);
  2073. }
  2074. }
  2075. catch (Exception x)
  2076. {
  2077. // Pass exception to callback method
  2078. callback(SocketCallBackResult.Exception, x);
  2079. }
  2080. }
  2081. /// <summary>
  2082. /// Is called when smtp client has finished reading BDAT: command server response line.
  2083. /// </summary>
  2084. /// <param name="result"></param>
  2085. /// <param name="count"></param>
  2086. /// <param name="exception"></param>
  2087. /// <param name="tag"></param>
  2088. private void OnBdatReadServerResponseFinished(SocketCallBackResult result,
  2089. long count,
  2090. Exception exception,
  2091. object tag)
  2092. {
  2093. CommadCompleted callback = (CommadCompleted) (((object[]) tag)[0]);
  2094. try
  2095. {
  2096. if (result == SocketCallBackResult.Ok)
  2097. {
  2098. string responseLine =
  2099. Encoding.ASCII.GetString(((MemoryStream) (((object[]) tag)[1])).ToArray());
  2100. // Response line must start with 250 or otherwise it's error response
  2101. if (!responseLine.StartsWith("250"))
  2102. {
  2103. throw new Exception(responseLine);
  2104. }
  2105. else
  2106. {
  2107. // BDAT: completed susscessfully, call callback method.
  2108. callback(SocketCallBackResult.Ok, null);
  2109. }
  2110. }
  2111. else
  2112. {
  2113. HandleSocketError(result, exception);
  2114. }
  2115. }
  2116. catch (Exception x)
  2117. {
  2118. // Pass exception to callback method
  2119. callback(SocketCallBackResult.Exception, x);
  2120. }
  2121. }
  2122. /// <summary>
  2123. /// Is called when smtp client has finished DATA command sending.
  2124. /// </summary>
  2125. /// <param name="result"></param>
  2126. /// <param name="count"></param>
  2127. /// <param name="exception"></param>
  2128. /// <param name="tag"></param>
  2129. private void OnDataSendFinished(SocketCallBackResult result,
  2130. long count,
  2131. Exception exception,
  2132. object tag)
  2133. {
  2134. CommadCompleted callback = (CommadCompleted) (((object[]) tag)[1]);
  2135. try
  2136. {
  2137. if (result == SocketCallBackResult.Ok)
  2138. {
  2139. // DATA command has sent to SMTP server, start reading server response.
  2140. MemoryStream ms = new MemoryStream();
  2141. m_pSocket.BeginReadLine(ms,
  2142. 1000,
  2143. new object[] {(((object[]) tag)[0]), callback, ms},
  2144. OnDataReadServerResponseFinished);
  2145. }
  2146. else
  2147. {
  2148. HandleSocketError(result, exception);
  2149. }
  2150. }
  2151. catch (Exception x)
  2152. {
  2153. // Pass exception to callback method
  2154. callback(SocketCallBackResult.Exception, x);
  2155. }
  2156. }
  2157. /// <summary>
  2158. /// Is called when smtp client has finished reading DATA command server response line.
  2159. /// </summary>
  2160. /// <param name="result"></param>
  2161. /// <param name="count"></param>
  2162. /// <param name="exception"></param>
  2163. /// <param name="tag"></param>
  2164. private void OnDataReadServerResponseFinished(SocketCallBackResult result,
  2165. long count,
  2166. Exception exception,
  2167. object tag)
  2168. {
  2169. CommadCompleted callback = (CommadCompleted) (((object[]) tag)[1]);
  2170. try
  2171. {
  2172. if (result == SocketCallBackResult.Ok)
  2173. {
  2174. string responseLine =
  2175. Encoding.ASCII.GetString(((MemoryStream) (((object[]) tag)[2])).ToArray());
  2176. // Response line must start with 334 or otherwise it's error response
  2177. if (!responseLine.StartsWith("354"))
  2178. {
  2179. throw new Exception(responseLine);
  2180. }
  2181. else
  2182. {
  2183. Stream message = (Stream) (((object[]) tag)[0]);
  2184. // Start sending message to smtp server
  2185. m_pSocket.BeginWritePeriodTerminated(message, callback, OnDataMessageSendFinished);
  2186. }
  2187. }
  2188. else
  2189. {
  2190. HandleSocketError(result, exception);
  2191. }
  2192. }
  2193. catch (Exception x)
  2194. {
  2195. // Pass exception to callback method
  2196. callback(SocketCallBackResult.Exception, x);
  2197. }
  2198. }
  2199. /// <summary>
  2200. /// Is called when smtp client has sending MESSAGE to smtp server.
  2201. /// </summary>
  2202. /// <param name="result"></param>
  2203. /// <param name="count"></param>
  2204. /// <param name="exception"></param>
  2205. /// <param name="tag"></param>
  2206. private void OnDataMessageSendFinished(SocketCallBackResult result,
  2207. long count,
  2208. Exception exception,
  2209. object tag)
  2210. {
  2211. CommadCompleted callback = (CommadCompleted) tag;
  2212. try
  2213. {
  2214. if (result == SocketCallBackResult.Ok)
  2215. {
  2216. // Message has successfully sent to smtp server, start reading server response
  2217. MemoryStream ms = new MemoryStream();
  2218. m_pSocket.BeginReadLine(ms,
  2219. 1000,
  2220. new object[] {callback, ms},
  2221. OnDataMessageSendReadServerResponseFinished);
  2222. }
  2223. else
  2224. {
  2225. HandleSocketError(result, exception);
  2226. }
  2227. }
  2228. catch (Exception x)
  2229. {
  2230. // Pass exception to callback method
  2231. callback(SocketCallBackResult.Exception, x);
  2232. }
  2233. }
  2234. /// <summary>
  2235. /// Is called when smtp client has finished reading MESSAGE send smtp server response line.
  2236. /// </summary>
  2237. /// <param name="result"></param>
  2238. /// <param name="count"></param>
  2239. /// <param name="exception"></param>
  2240. /// <param name="tag"></param>
  2241. private void OnDataMessageSendReadServerResponseFinished(SocketCallBackResult result,
  2242. long count,
  2243. Exception exception,
  2244. object tag)
  2245. {
  2246. CommadCompleted callback = (CommadCompleted) (((object[]) tag)[0]);
  2247. try
  2248. {
  2249. // TODO: some servers close connection after DATA command, hanndle Socket closed.
  2250. if (result == SocketCallBackResult.Ok)
  2251. {
  2252. string responseLine =
  2253. Encoding.ASCII.GetString(((MemoryStream) (((object[]) tag)[1])).ToArray());
  2254. // Response line must start with 250 or otherwise it's error response
  2255. if (!responseLine.StartsWith("250"))
  2256. {
  2257. throw new Exception(responseLine);
  2258. }
  2259. else
  2260. {
  2261. // DATA: completed susscessfully, call callback method.
  2262. callback(SocketCallBackResult.Ok, null);
  2263. }
  2264. }
  2265. else
  2266. {
  2267. HandleSocketError(result, exception);
  2268. }
  2269. }
  2270. catch (Exception x)
  2271. {
  2272. // Pass exception to callback method
  2273. callback(SocketCallBackResult.Exception, x);
  2274. }
  2275. }
  2276. /// <summary>
  2277. /// Handles socket errors.
  2278. /// </summary>
  2279. /// <param name="result"></param>
  2280. /// <param name="x"></param>
  2281. private void HandleSocketError(SocketCallBackResult result, Exception x)
  2282. {
  2283. // Log socket errors to log
  2284. if (m_pSocket.Logger != null)
  2285. {
  2286. if (result == SocketCallBackResult.SocketClosed)
  2287. {
  2288. m_pSocket.Logger.AddTextEntry("Server closed socket !");
  2289. }
  2290. else if (x != null && x is SocketException)
  2291. {
  2292. SocketException socketException = (SocketException) x;
  2293. // Server disconnected or aborted connection
  2294. if (socketException.ErrorCode == 10054 || socketException.ErrorCode == 10053)
  2295. {
  2296. m_pSocket.Logger.AddTextEntry("Server closed socket or aborted connection !");
  2297. }
  2298. }
  2299. else
  2300. {
  2301. m_pSocket.Logger.AddTextEntry("Unknown error !");
  2302. }
  2303. }
  2304. if (result == SocketCallBackResult.Exception)
  2305. {
  2306. throw x;
  2307. }
  2308. else
  2309. {
  2310. throw new Exception(result.ToString());
  2311. }
  2312. }
  2313. #endregion
  2314. }
  2315. }