PageRenderTime 57ms CodeModel.GetById 26ms 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

Large files files are truncated, but you can click here to view the full file

  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. {

Large files files are truncated, but you can click here to view the full file