SmtpClient.cs 37 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303
  1. //
  2. // System.Net.Mail.SmtpClient.cs
  3. //
  4. // Author:
  5. // Tim Coleman ([email protected])
  6. //
  7. // Copyright (C) Tim Coleman, 2004
  8. //
  9. //
  10. // Permission is hereby granted, free of charge, to any person obtaining
  11. // a copy of this software and associated documentation files (the
  12. // "Software"), to deal in the Software without restriction, including
  13. // without limitation the rights to use, copy, modify, merge, publish,
  14. // distribute, sublicense, and/or sell copies of the Software, and to
  15. // permit persons to whom the Software is furnished to do so, subject to
  16. // the following conditions:
  17. //
  18. // The above copyright notice and this permission notice shall be
  19. // included in all copies or substantial portions of the Software.
  20. //
  21. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  22. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  23. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  24. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  25. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  26. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  27. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  28. //
  29. #if SECURITY_DEP
  30. #if MONOTOUCH || MONODROID
  31. using System.Security.Cryptography.X509Certificates;
  32. #else
  33. extern alias PrebuiltSystem;
  34. using X509CertificateCollection = PrebuiltSystem::System.Security.Cryptography.X509Certificates.X509CertificateCollection;
  35. using System.Security.Cryptography.X509Certificates;
  36. #endif
  37. #endif
  38. using System;
  39. using System.Collections.Generic;
  40. using System.ComponentModel;
  41. using System.Globalization;
  42. using System.IO;
  43. using System.Net;
  44. using System.Net.Mime;
  45. using System.Net.Sockets;
  46. using System.Text;
  47. using System.Threading;
  48. using System.Reflection;
  49. using System.Net.Configuration;
  50. using System.Configuration;
  51. using System.Net.Security;
  52. using System.Security.Authentication;
  53. using System.Threading.Tasks;
  54. namespace System.Net.Mail {
  55. public class SmtpClient
  56. : IDisposable
  57. {
  58. #region Fields
  59. string host;
  60. int port;
  61. int timeout = 100000;
  62. ICredentialsByHost credentials;
  63. string pickupDirectoryLocation;
  64. SmtpDeliveryMethod deliveryMethod;
  65. bool enableSsl;
  66. #if SECURITY_DEP
  67. X509CertificateCollection clientCertificates;
  68. #endif
  69. TcpClient client;
  70. Stream stream;
  71. StreamWriter writer;
  72. StreamReader reader;
  73. int boundaryIndex;
  74. MailAddress defaultFrom;
  75. MailMessage messageInProcess;
  76. BackgroundWorker worker;
  77. object user_async_state;
  78. [Flags]
  79. enum AuthMechs {
  80. None = 0,
  81. Login = 0x01,
  82. Plain = 0x02,
  83. }
  84. class CancellationException : Exception
  85. {
  86. }
  87. AuthMechs authMechs;
  88. Mutex mutex = new Mutex ();
  89. #endregion // Fields
  90. #region Constructors
  91. public SmtpClient ()
  92. : this (null, 0)
  93. {
  94. }
  95. public SmtpClient (string host)
  96. : this (host, 0)
  97. {
  98. }
  99. public SmtpClient (string host, int port) {
  100. #if CONFIGURATION_DEP
  101. SmtpSection cfg = (SmtpSection) ConfigurationManager.GetSection ("system.net/mailSettings/smtp");
  102. if (cfg != null) {
  103. this.host = cfg.Network.Host;
  104. this.port = cfg.Network.Port;
  105. this.enableSsl = cfg.Network.EnableSsl;
  106. TargetName = cfg.Network.TargetName;
  107. if (this.TargetName == null)
  108. TargetName = "SMTPSVC/" + (host != null ? host : "");
  109. if (cfg.Network.UserName != null) {
  110. string password = String.Empty;
  111. if (cfg.Network.Password != null)
  112. password = cfg.Network.Password;
  113. Credentials = new CCredentialsByHost (cfg.Network.UserName, password);
  114. }
  115. if (!String.IsNullOrEmpty (cfg.From))
  116. defaultFrom = new MailAddress (cfg.From);
  117. }
  118. #else
  119. // Just to eliminate the warning, this codepath does not end up in production.
  120. defaultFrom = null;
  121. #endif
  122. if (!String.IsNullOrEmpty (host))
  123. this.host = host;
  124. if (port != 0)
  125. this.port = port;
  126. else if (this.port == 0)
  127. this.port = 25;
  128. }
  129. #endregion // Constructors
  130. #region Properties
  131. #if SECURITY_DEP
  132. [MonoTODO("Client certificates not used")]
  133. public X509CertificateCollection ClientCertificates {
  134. get {
  135. if (clientCertificates == null)
  136. clientCertificates = new X509CertificateCollection ();
  137. return clientCertificates;
  138. }
  139. }
  140. #endif
  141. public
  142. string TargetName { get; set; }
  143. public ICredentialsByHost Credentials {
  144. get { return credentials; }
  145. set {
  146. CheckState ();
  147. credentials = value;
  148. }
  149. }
  150. public SmtpDeliveryMethod DeliveryMethod {
  151. get { return deliveryMethod; }
  152. set {
  153. CheckState ();
  154. deliveryMethod = value;
  155. }
  156. }
  157. public bool EnableSsl {
  158. get { return enableSsl; }
  159. set {
  160. CheckState ();
  161. enableSsl = value;
  162. }
  163. }
  164. public string Host {
  165. get { return host; }
  166. set {
  167. if (value == null)
  168. throw new ArgumentNullException ("value");
  169. if (value.Length == 0)
  170. throw new ArgumentException ("An empty string is not allowed.", "value");
  171. CheckState ();
  172. host = value;
  173. }
  174. }
  175. public string PickupDirectoryLocation {
  176. get { return pickupDirectoryLocation; }
  177. set { pickupDirectoryLocation = value; }
  178. }
  179. public int Port {
  180. get { return port; }
  181. set {
  182. if (value <= 0)
  183. throw new ArgumentOutOfRangeException ("value");
  184. CheckState ();
  185. port = value;
  186. }
  187. }
  188. [MonoTODO]
  189. public ServicePoint ServicePoint {
  190. get { throw new NotImplementedException (); }
  191. }
  192. public int Timeout {
  193. get { return timeout; }
  194. set {
  195. if (value < 0)
  196. throw new ArgumentOutOfRangeException ("value");
  197. CheckState ();
  198. timeout = value;
  199. }
  200. }
  201. public bool UseDefaultCredentials {
  202. get { return false; }
  203. [MonoNotSupported ("no DefaultCredential support in Mono")]
  204. set {
  205. if (value)
  206. throw new NotImplementedException ("Default credentials are not supported");
  207. CheckState ();
  208. }
  209. }
  210. #endregion // Properties
  211. #region Events
  212. public event SendCompletedEventHandler SendCompleted;
  213. #endregion // Events
  214. #region Methods
  215. public void Dispose ()
  216. {
  217. Dispose (true);
  218. }
  219. [MonoTODO ("Does nothing at the moment.")]
  220. protected virtual void Dispose (bool disposing)
  221. {
  222. // TODO: We should close all the connections and abort any async operations here
  223. }
  224. private void CheckState ()
  225. {
  226. if (messageInProcess != null)
  227. throw new InvalidOperationException ("Cannot set Timeout while Sending a message");
  228. }
  229. private static string EncodeAddress(MailAddress address)
  230. {
  231. if (!String.IsNullOrEmpty (address.DisplayName)) {
  232. string encodedDisplayName = ContentType.EncodeSubjectRFC2047 (address.DisplayName, Encoding.UTF8);
  233. return "\"" + encodedDisplayName + "\" <" + address.Address + ">";
  234. }
  235. return address.ToString ();
  236. }
  237. private static string EncodeAddresses(MailAddressCollection addresses)
  238. {
  239. StringBuilder sb = new StringBuilder();
  240. bool first = true;
  241. foreach (MailAddress address in addresses) {
  242. if (!first) {
  243. sb.Append(", ");
  244. }
  245. sb.Append(EncodeAddress(address));
  246. first = false;
  247. }
  248. return sb.ToString();
  249. }
  250. private string EncodeSubjectRFC2047 (MailMessage message)
  251. {
  252. return ContentType.EncodeSubjectRFC2047 (message.Subject, message.SubjectEncoding);
  253. }
  254. private string EncodeBody (MailMessage message)
  255. {
  256. string body = message.Body;
  257. Encoding encoding = message.BodyEncoding;
  258. // RFC 2045 encoding
  259. switch (message.ContentTransferEncoding) {
  260. case TransferEncoding.SevenBit:
  261. return body;
  262. case TransferEncoding.Base64:
  263. return Convert.ToBase64String (encoding.GetBytes (body), Base64FormattingOptions.InsertLineBreaks);
  264. default:
  265. return ToQuotedPrintable (body, encoding);
  266. }
  267. }
  268. private string EncodeBody (AlternateView av)
  269. {
  270. //Encoding encoding = av.ContentType.CharSet != null ? Encoding.GetEncoding (av.ContentType.CharSet) : Encoding.UTF8;
  271. byte [] bytes = new byte [av.ContentStream.Length];
  272. av.ContentStream.Read (bytes, 0, bytes.Length);
  273. // RFC 2045 encoding
  274. switch (av.TransferEncoding) {
  275. case TransferEncoding.SevenBit:
  276. return Encoding.ASCII.GetString (bytes);
  277. case TransferEncoding.Base64:
  278. return Convert.ToBase64String (bytes, Base64FormattingOptions.InsertLineBreaks);
  279. default:
  280. return ToQuotedPrintable (bytes);
  281. }
  282. }
  283. private void EndSection (string section)
  284. {
  285. SendData (String.Format ("--{0}--", section));
  286. SendData (string.Empty);
  287. }
  288. private string GenerateBoundary ()
  289. {
  290. string output = GenerateBoundary (boundaryIndex);
  291. boundaryIndex += 1;
  292. return output;
  293. }
  294. private static string GenerateBoundary (int index)
  295. {
  296. return String.Format ("--boundary_{0}_{1}", index, Guid.NewGuid ().ToString ("D"));
  297. }
  298. private bool IsError (SmtpResponse status)
  299. {
  300. return ((int) status.StatusCode) >= 400;
  301. }
  302. protected void OnSendCompleted (AsyncCompletedEventArgs e)
  303. {
  304. try {
  305. if (SendCompleted != null)
  306. SendCompleted (this, e);
  307. } finally {
  308. worker = null;
  309. user_async_state = null;
  310. }
  311. }
  312. private void CheckCancellation ()
  313. {
  314. if (worker != null && worker.CancellationPending)
  315. throw new CancellationException ();
  316. }
  317. private SmtpResponse Read () {
  318. byte [] buffer = new byte [512];
  319. int position = 0;
  320. bool lastLine = false;
  321. do {
  322. CheckCancellation ();
  323. int readLength = stream.Read (buffer, position, buffer.Length - position);
  324. if (readLength > 0) {
  325. int available = position + readLength - 1;
  326. if (available > 4 && (buffer [available] == '\n' || buffer [available] == '\r'))
  327. for (int index = available - 3; ; index--) {
  328. if (index < 0 || buffer [index] == '\n' || buffer [index] == '\r') {
  329. lastLine = buffer [index + 4] == ' ';
  330. break;
  331. }
  332. }
  333. // move position
  334. position += readLength;
  335. // check if buffer is full
  336. if (position == buffer.Length) {
  337. byte [] newBuffer = new byte [buffer.Length * 2];
  338. Array.Copy (buffer, 0, newBuffer, 0, buffer.Length);
  339. buffer = newBuffer;
  340. }
  341. }
  342. else {
  343. break;
  344. }
  345. } while (!lastLine);
  346. if (position > 0) {
  347. Encoding encoding = new ASCIIEncoding ();
  348. string line = encoding.GetString (buffer, 0, position - 1);
  349. // parse the line to the lastResponse object
  350. SmtpResponse response = SmtpResponse.Parse (line);
  351. return response;
  352. } else {
  353. throw new System.IO.IOException ("Connection closed");
  354. }
  355. }
  356. void ResetExtensions()
  357. {
  358. authMechs = AuthMechs.None;
  359. }
  360. void ParseExtensions (string extens)
  361. {
  362. string[] parts = extens.Split ('\n');
  363. foreach (string part in parts) {
  364. if (part.Length < 4)
  365. continue;
  366. string start = part.Substring (4);
  367. if (start.StartsWith ("AUTH ", StringComparison.Ordinal)) {
  368. string[] options = start.Split (' ');
  369. for (int k = 1; k < options.Length; k++) {
  370. string option = options[k].Trim();
  371. // GSSAPI, KERBEROS_V4, NTLM not supported
  372. switch (option) {
  373. /*
  374. case "CRAM-MD5":
  375. authMechs |= AuthMechs.CramMD5;
  376. break;
  377. case "DIGEST-MD5":
  378. authMechs |= AuthMechs.DigestMD5;
  379. break;
  380. */
  381. case "LOGIN":
  382. authMechs |= AuthMechs.Login;
  383. break;
  384. case "PLAIN":
  385. authMechs |= AuthMechs.Plain;
  386. break;
  387. }
  388. }
  389. }
  390. }
  391. }
  392. public void Send (MailMessage message)
  393. {
  394. if (message == null)
  395. throw new ArgumentNullException ("message");
  396. if (deliveryMethod == SmtpDeliveryMethod.Network && (Host == null || Host.Trim ().Length == 0))
  397. throw new InvalidOperationException ("The SMTP host was not specified");
  398. else if (deliveryMethod == SmtpDeliveryMethod.PickupDirectoryFromIis)
  399. throw new NotSupportedException("IIS delivery is not supported");
  400. if (port == 0)
  401. port = 25;
  402. // Block while sending
  403. mutex.WaitOne ();
  404. try {
  405. messageInProcess = message;
  406. if (deliveryMethod == SmtpDeliveryMethod.SpecifiedPickupDirectory)
  407. SendToFile (message);
  408. else
  409. SendInternal (message);
  410. } catch (CancellationException) {
  411. // This exception is introduced for convenient cancellation process.
  412. } catch (SmtpException) {
  413. throw;
  414. } catch (Exception ex) {
  415. throw new SmtpException ("Message could not be sent.", ex);
  416. } finally {
  417. // Release the mutex to allow other threads access
  418. mutex.ReleaseMutex ();
  419. messageInProcess = null;
  420. }
  421. }
  422. private void SendInternal (MailMessage message)
  423. {
  424. CheckCancellation ();
  425. try {
  426. client = new TcpClient (host, port);
  427. stream = client.GetStream ();
  428. // FIXME: this StreamWriter creation is bogus.
  429. // It expects as if a Stream were able to switch to SSL
  430. // mode (such behavior is only in Mainsoft Socket API).
  431. writer = new StreamWriter (stream);
  432. reader = new StreamReader (stream);
  433. SendCore (message);
  434. } finally {
  435. if (writer != null)
  436. writer.Close ();
  437. if (reader != null)
  438. reader.Close ();
  439. if (stream != null)
  440. stream.Close ();
  441. if (client != null)
  442. client.Close ();
  443. }
  444. }
  445. // FIXME: simple implementation, could be brushed up.
  446. private void SendToFile (MailMessage message)
  447. {
  448. if (!Path.IsPathRooted (pickupDirectoryLocation))
  449. throw new SmtpException("Only absolute directories are allowed for pickup directory.");
  450. string filename = Path.Combine (pickupDirectoryLocation,
  451. Guid.NewGuid() + ".eml");
  452. try {
  453. writer = new StreamWriter(filename);
  454. // FIXME: See how Microsoft fixed the bug about envelope senders, and how it actually represents the info in .eml file headers
  455. // For all we know, defaultFrom may be the envelope sender
  456. // For now, we are no worse than some versions of .NET
  457. MailAddress from = message.From;
  458. if (from == null)
  459. from = defaultFrom;
  460. string dt = DateTime.Now.ToString("ddd, dd MMM yyyy HH':'mm':'ss zzz", DateTimeFormatInfo.InvariantInfo);
  461. // remove ':' from time zone offset (e.g. from "+01:00")
  462. dt = dt.Remove(dt.Length - 3, 1);
  463. SendHeader(HeaderName.Date, dt);
  464. SendHeader (HeaderName.From, EncodeAddress(from));
  465. SendHeader (HeaderName.To, EncodeAddresses(message.To));
  466. if (message.CC.Count > 0)
  467. SendHeader (HeaderName.Cc, EncodeAddresses(message.CC));
  468. SendHeader (HeaderName.Subject, EncodeSubjectRFC2047 (message));
  469. foreach (string s in message.Headers.AllKeys)
  470. SendHeader (s, message.Headers [s]);
  471. AddPriorityHeader (message);
  472. boundaryIndex = 0;
  473. if (message.Attachments.Count > 0)
  474. SendWithAttachments (message);
  475. else
  476. SendWithoutAttachments (message, null, false);
  477. } finally {
  478. if (writer != null) writer.Close(); writer = null;
  479. }
  480. }
  481. private void SendCore (MailMessage message)
  482. {
  483. SmtpResponse status;
  484. status = Read ();
  485. if (IsError (status))
  486. throw new SmtpException (status.StatusCode, status.Description);
  487. // EHLO
  488. // FIXME: parse the list of extensions so we don't bother wasting
  489. // our time trying commands if they aren't supported.
  490. // Get the FQDN of the local machine
  491. string fqdn = Dns.GetHostEntry (Dns.GetHostName ()).HostName;
  492. status = SendCommand ("EHLO " + fqdn);
  493. if (IsError (status)) {
  494. status = SendCommand ("HELO " + fqdn);
  495. if (IsError (status))
  496. throw new SmtpException (status.StatusCode, status.Description);
  497. } else {
  498. // Parse ESMTP extensions
  499. string extens = status.Description;
  500. if (extens != null)
  501. ParseExtensions (extens);
  502. }
  503. if (enableSsl) {
  504. InitiateSecureConnection ();
  505. ResetExtensions();
  506. writer = new StreamWriter (stream);
  507. reader = new StreamReader (stream);
  508. status = SendCommand ("EHLO " + fqdn);
  509. if (IsError (status)) {
  510. status = SendCommand ("HELO " + fqdn);
  511. if (IsError (status))
  512. throw new SmtpException (status.StatusCode, status.Description);
  513. } else {
  514. // Parse ESMTP extensions
  515. string extens = status.Description;
  516. if (extens != null)
  517. ParseExtensions (extens);
  518. }
  519. }
  520. if (authMechs != AuthMechs.None)
  521. Authenticate ();
  522. // The envelope sender: use 'Sender:' in preference of 'From:'
  523. MailAddress sender = message.Sender;
  524. if (sender == null)
  525. sender = message.From;
  526. if (sender == null)
  527. sender = defaultFrom;
  528. // MAIL FROM:
  529. status = SendCommand ("MAIL FROM:<" + sender.Address + '>');
  530. if (IsError (status)) {
  531. throw new SmtpException (status.StatusCode, status.Description);
  532. }
  533. // Send RCPT TO: for all recipients
  534. List<SmtpFailedRecipientException> sfre = new List<SmtpFailedRecipientException> ();
  535. for (int i = 0; i < message.To.Count; i ++) {
  536. status = SendCommand ("RCPT TO:<" + message.To [i].Address + '>');
  537. if (IsError (status))
  538. sfre.Add (new SmtpFailedRecipientException (status.StatusCode, message.To [i].Address));
  539. }
  540. for (int i = 0; i < message.CC.Count; i ++) {
  541. status = SendCommand ("RCPT TO:<" + message.CC [i].Address + '>');
  542. if (IsError (status))
  543. sfre.Add (new SmtpFailedRecipientException (status.StatusCode, message.CC [i].Address));
  544. }
  545. for (int i = 0; i < message.Bcc.Count; i ++) {
  546. status = SendCommand ("RCPT TO:<" + message.Bcc [i].Address + '>');
  547. if (IsError (status))
  548. sfre.Add (new SmtpFailedRecipientException (status.StatusCode, message.Bcc [i].Address));
  549. }
  550. if (sfre.Count >0)
  551. throw new SmtpFailedRecipientsException ("failed recipients", sfre.ToArray ());
  552. // DATA
  553. status = SendCommand ("DATA");
  554. if (IsError (status))
  555. throw new SmtpException (status.StatusCode, status.Description);
  556. // Send message headers
  557. string dt = DateTime.Now.ToString ("ddd, dd MMM yyyy HH':'mm':'ss zzz", DateTimeFormatInfo.InvariantInfo);
  558. // remove ':' from time zone offset (e.g. from "+01:00")
  559. dt = dt.Remove (dt.Length - 3, 1);
  560. SendHeader (HeaderName.Date, dt);
  561. MailAddress from = message.From;
  562. if (from == null)
  563. from = defaultFrom;
  564. SendHeader (HeaderName.From, EncodeAddress (from));
  565. SendHeader (HeaderName.To, EncodeAddresses (message.To));
  566. if (message.CC.Count > 0)
  567. SendHeader (HeaderName.Cc, EncodeAddresses (message.CC));
  568. SendHeader (HeaderName.Subject, EncodeSubjectRFC2047 (message));
  569. string v = "normal";
  570. switch (message.Priority){
  571. case MailPriority.Normal:
  572. v = "normal";
  573. break;
  574. case MailPriority.Low:
  575. v = "non-urgent";
  576. break;
  577. case MailPriority.High:
  578. v = "urgent";
  579. break;
  580. }
  581. SendHeader ("Priority", v);
  582. if (message.Sender != null)
  583. SendHeader ("Sender", EncodeAddress (message.Sender));
  584. if (message.ReplyToList.Count > 0)
  585. SendHeader ("Reply-To", EncodeAddresses (message.ReplyToList));
  586. foreach (string s in message.Headers.AllKeys)
  587. SendHeader (s, ContentType.EncodeSubjectRFC2047 (message.Headers [s], message.HeadersEncoding));
  588. AddPriorityHeader (message);
  589. boundaryIndex = 0;
  590. if (message.Attachments.Count > 0)
  591. SendWithAttachments (message);
  592. else
  593. SendWithoutAttachments (message, null, false);
  594. SendDot ();
  595. status = Read ();
  596. if (IsError (status))
  597. throw new SmtpException (status.StatusCode, status.Description);
  598. try {
  599. status = SendCommand ("QUIT");
  600. } catch (System.IO.IOException) {
  601. // We excuse server for the rude connection closing as a response to QUIT
  602. }
  603. }
  604. public void Send (string from, string to, string subject, string body)
  605. {
  606. Send (new MailMessage (from, to, subject, body));
  607. }
  608. public Task SendMailAsync (MailMessage message)
  609. {
  610. var tcs = new TaskCompletionSource<object> ();
  611. SendCompletedEventHandler handler = null;
  612. handler = (s, e) => SendMailAsyncCompletedHandler (tcs, e, handler, this);
  613. SendCompleted += handler;
  614. SendAsync (message, tcs);
  615. return tcs.Task;
  616. }
  617. public Task SendMailAsync (string from, string recipients, string subject, string body)
  618. {
  619. return SendMailAsync (new MailMessage (from, recipients, subject, body));
  620. }
  621. static void SendMailAsyncCompletedHandler (TaskCompletionSource<object> source, AsyncCompletedEventArgs e, SendCompletedEventHandler handler, SmtpClient client)
  622. {
  623. if ((object) handler != e.UserState)
  624. return;
  625. client.SendCompleted -= handler;
  626. if (e.Error != null) {
  627. source.SetException (e.Error);
  628. return;
  629. }
  630. if (e.Cancelled) {
  631. source.SetCanceled ();
  632. return;
  633. }
  634. source.SetResult (null);
  635. }
  636. private void SendDot()
  637. {
  638. writer.Write(".\r\n");
  639. writer.Flush();
  640. }
  641. private void SendData (string data)
  642. {
  643. if (String.IsNullOrEmpty (data)) {
  644. writer.Write("\r\n");
  645. writer.Flush();
  646. return;
  647. }
  648. StringReader sr = new StringReader (data);
  649. string line;
  650. bool escapeDots = deliveryMethod == SmtpDeliveryMethod.Network;
  651. while ((line = sr.ReadLine ()) != null) {
  652. CheckCancellation ();
  653. if (escapeDots) {
  654. int i;
  655. for (i = 0; i < line.Length; i++) {
  656. if (line[i] != '.')
  657. break;
  658. }
  659. if (i > 0 && i == line.Length) {
  660. line += ".";
  661. }
  662. }
  663. writer.Write (line);
  664. writer.Write ("\r\n");
  665. }
  666. writer.Flush ();
  667. }
  668. public void SendAsync (MailMessage message, object userToken)
  669. {
  670. if (worker != null)
  671. throw new InvalidOperationException ("Another SendAsync operation is in progress");
  672. worker = new BackgroundWorker ();
  673. worker.DoWork += delegate (object o, DoWorkEventArgs ea) {
  674. try {
  675. user_async_state = ea.Argument;
  676. Send (message);
  677. } catch (Exception ex) {
  678. ea.Result = ex;
  679. throw ex;
  680. }
  681. };
  682. worker.WorkerSupportsCancellation = true;
  683. worker.RunWorkerCompleted += delegate (object o, RunWorkerCompletedEventArgs ea) {
  684. // Note that RunWorkerCompletedEventArgs.UserState cannot be used (LAMESPEC)
  685. OnSendCompleted (new AsyncCompletedEventArgs (ea.Error, ea.Cancelled, user_async_state));
  686. };
  687. worker.RunWorkerAsync (userToken);
  688. }
  689. public void SendAsync (string from, string to, string subject, string body, object userToken)
  690. {
  691. SendAsync (new MailMessage (from, to, subject, body), userToken);
  692. }
  693. public void SendAsyncCancel ()
  694. {
  695. if (worker == null)
  696. throw new InvalidOperationException ("SendAsync operation is not in progress");
  697. worker.CancelAsync ();
  698. }
  699. private void AddPriorityHeader (MailMessage message) {
  700. switch (message.Priority) {
  701. case MailPriority.High:
  702. SendHeader (HeaderName.Priority, "Urgent");
  703. SendHeader (HeaderName.Importance, "high");
  704. SendHeader (HeaderName.XPriority, "1");
  705. break;
  706. case MailPriority.Low:
  707. SendHeader (HeaderName.Priority, "Non-Urgent");
  708. SendHeader (HeaderName.Importance, "low");
  709. SendHeader (HeaderName.XPriority, "5");
  710. break;
  711. }
  712. }
  713. private void SendSimpleBody (MailMessage message) {
  714. SendHeader (HeaderName.ContentType, message.BodyContentType.ToString ());
  715. if (message.ContentTransferEncoding != TransferEncoding.SevenBit)
  716. SendHeader (HeaderName.ContentTransferEncoding, GetTransferEncodingName (message.ContentTransferEncoding));
  717. SendData (string.Empty);
  718. SendData (EncodeBody (message));
  719. }
  720. private void SendBodylessSingleAlternate (AlternateView av) {
  721. SendHeader (HeaderName.ContentType, av.ContentType.ToString ());
  722. if (av.TransferEncoding != TransferEncoding.SevenBit)
  723. SendHeader (HeaderName.ContentTransferEncoding, GetTransferEncodingName (av.TransferEncoding));
  724. SendData (string.Empty);
  725. SendData (EncodeBody (av));
  726. }
  727. private void SendWithoutAttachments (MailMessage message, string boundary, bool attachmentExists)
  728. {
  729. if (message.Body == null && message.AlternateViews.Count == 1)
  730. SendBodylessSingleAlternate (message.AlternateViews [0]);
  731. else if (message.AlternateViews.Count > 0)
  732. SendBodyWithAlternateViews (message, boundary, attachmentExists);
  733. else
  734. SendSimpleBody (message);
  735. }
  736. private void SendWithAttachments (MailMessage message) {
  737. string boundary = GenerateBoundary ();
  738. // first "multipart/mixed"
  739. ContentType messageContentType = new ContentType ();
  740. messageContentType.Boundary = boundary;
  741. messageContentType.MediaType = "multipart/mixed";
  742. messageContentType.CharSet = null;
  743. SendHeader (HeaderName.ContentType, messageContentType.ToString ());
  744. SendData (String.Empty);
  745. // body section
  746. Attachment body = null;
  747. if (message.AlternateViews.Count > 0)
  748. SendWithoutAttachments (message, boundary, true);
  749. else {
  750. body = Attachment.CreateAttachmentFromString (message.Body, null, message.BodyEncoding, message.IsBodyHtml ? "text/html" : "text/plain");
  751. message.Attachments.Insert (0, body);
  752. }
  753. try {
  754. SendAttachments (message, body, boundary);
  755. } finally {
  756. if (body != null)
  757. message.Attachments.Remove (body);
  758. }
  759. EndSection (boundary);
  760. }
  761. private void SendBodyWithAlternateViews (MailMessage message, string boundary, bool attachmentExists)
  762. {
  763. AlternateViewCollection alternateViews = message.AlternateViews;
  764. string inner_boundary = GenerateBoundary ();
  765. ContentType messageContentType = new ContentType ();
  766. messageContentType.Boundary = inner_boundary;
  767. messageContentType.MediaType = "multipart/alternative";
  768. if (!attachmentExists) {
  769. SendHeader (HeaderName.ContentType, messageContentType.ToString ());
  770. SendData (String.Empty);
  771. }
  772. // body section
  773. AlternateView body = null;
  774. if (message.Body != null) {
  775. body = AlternateView.CreateAlternateViewFromString (message.Body, message.BodyEncoding, message.IsBodyHtml ? "text/html" : "text/plain");
  776. alternateViews.Insert (0, body);
  777. StartSection (boundary, messageContentType);
  778. }
  779. try {
  780. // alternate view sections
  781. foreach (AlternateView av in alternateViews) {
  782. string alt_boundary = null;
  783. ContentType contentType;
  784. if (av.LinkedResources.Count > 0) {
  785. alt_boundary = GenerateBoundary ();
  786. contentType = new ContentType ("multipart/related");
  787. contentType.Boundary = alt_boundary;
  788. contentType.Parameters ["type"] = av.ContentType.ToString ();
  789. StartSection (inner_boundary, contentType);
  790. StartSection (alt_boundary, av.ContentType, av);
  791. } else {
  792. contentType = new ContentType (av.ContentType.ToString ());
  793. StartSection (inner_boundary, contentType, av);
  794. }
  795. switch (av.TransferEncoding) {
  796. case TransferEncoding.Base64:
  797. byte [] content = new byte [av.ContentStream.Length];
  798. av.ContentStream.Read (content, 0, content.Length);
  799. SendData (Convert.ToBase64String (content, Base64FormattingOptions.InsertLineBreaks));
  800. break;
  801. case TransferEncoding.QuotedPrintable:
  802. byte [] bytes = new byte [av.ContentStream.Length];
  803. av.ContentStream.Read (bytes, 0, bytes.Length);
  804. SendData (ToQuotedPrintable (bytes));
  805. break;
  806. case TransferEncoding.SevenBit:
  807. case TransferEncoding.Unknown:
  808. content = new byte [av.ContentStream.Length];
  809. av.ContentStream.Read (content, 0, content.Length);
  810. SendData (Encoding.ASCII.GetString (content));
  811. break;
  812. }
  813. if (av.LinkedResources.Count > 0) {
  814. SendLinkedResources (message, av.LinkedResources, alt_boundary);
  815. EndSection (alt_boundary);
  816. }
  817. if (!attachmentExists)
  818. SendData (string.Empty);
  819. }
  820. } finally {
  821. if (body != null)
  822. alternateViews.Remove (body);
  823. }
  824. EndSection (inner_boundary);
  825. }
  826. private void SendLinkedResources (MailMessage message, LinkedResourceCollection resources, string boundary)
  827. {
  828. foreach (LinkedResource lr in resources) {
  829. StartSection (boundary, lr.ContentType, lr);
  830. switch (lr.TransferEncoding) {
  831. case TransferEncoding.Base64:
  832. byte [] content = new byte [lr.ContentStream.Length];
  833. lr.ContentStream.Read (content, 0, content.Length);
  834. SendData (Convert.ToBase64String (content, Base64FormattingOptions.InsertLineBreaks));
  835. break;
  836. case TransferEncoding.QuotedPrintable:
  837. byte [] bytes = new byte [lr.ContentStream.Length];
  838. lr.ContentStream.Read (bytes, 0, bytes.Length);
  839. SendData (ToQuotedPrintable (bytes));
  840. break;
  841. case TransferEncoding.SevenBit:
  842. case TransferEncoding.Unknown:
  843. content = new byte [lr.ContentStream.Length];
  844. lr.ContentStream.Read (content, 0, content.Length);
  845. SendData (Encoding.ASCII.GetString (content));
  846. break;
  847. }
  848. }
  849. }
  850. private void SendAttachments (MailMessage message, Attachment body, string boundary) {
  851. foreach (Attachment att in message.Attachments) {
  852. ContentType contentType = new ContentType (att.ContentType.ToString ());
  853. if (att.Name != null) {
  854. contentType.Name = att.Name;
  855. if (att.NameEncoding != null)
  856. contentType.CharSet = att.NameEncoding.HeaderName;
  857. att.ContentDisposition.FileName = att.Name;
  858. }
  859. StartSection (boundary, contentType, att, att != body);
  860. byte [] content = new byte [att.ContentStream.Length];
  861. att.ContentStream.Read (content, 0, content.Length);
  862. switch (att.TransferEncoding) {
  863. case TransferEncoding.Base64:
  864. SendData (Convert.ToBase64String (content, Base64FormattingOptions.InsertLineBreaks));
  865. break;
  866. case TransferEncoding.QuotedPrintable:
  867. SendData (ToQuotedPrintable (content));
  868. break;
  869. case TransferEncoding.SevenBit:
  870. case TransferEncoding.Unknown:
  871. SendData (Encoding.ASCII.GetString (content));
  872. break;
  873. }
  874. SendData (string.Empty);
  875. }
  876. }
  877. private SmtpResponse SendCommand (string command)
  878. {
  879. writer.Write (command);
  880. // Certain SMTP servers will reject mail sent with unix line-endings; see http://cr.yp.to/docs/smtplf.html
  881. writer.Write ("\r\n");
  882. writer.Flush ();
  883. return Read ();
  884. }
  885. private void SendHeader (string name, string value)
  886. {
  887. SendData (String.Format ("{0}: {1}", name, value));
  888. }
  889. private void StartSection (string section, ContentType sectionContentType)
  890. {
  891. SendData (String.Format ("--{0}", section));
  892. SendHeader ("content-type", sectionContentType.ToString ());
  893. SendData (string.Empty);
  894. }
  895. private void StartSection (string section, ContentType sectionContentType, AttachmentBase att)
  896. {
  897. SendData (String.Format ("--{0}", section));
  898. SendHeader ("content-type", sectionContentType.ToString ());
  899. SendHeader ("content-transfer-encoding", GetTransferEncodingName (att.TransferEncoding));
  900. if (!string.IsNullOrEmpty (att.ContentId))
  901. SendHeader("content-ID", "<" + att.ContentId + ">");
  902. SendData (string.Empty);
  903. }
  904. private void StartSection (string section, ContentType sectionContentType, Attachment att, bool sendDisposition) {
  905. SendData (String.Format ("--{0}", section));
  906. if (!string.IsNullOrEmpty (att.ContentId))
  907. SendHeader("content-ID", "<" + att.ContentId + ">");
  908. SendHeader ("content-type", sectionContentType.ToString ());
  909. SendHeader ("content-transfer-encoding", GetTransferEncodingName (att.TransferEncoding));
  910. if (sendDisposition)
  911. SendHeader ("content-disposition", att.ContentDisposition.ToString ());
  912. SendData (string.Empty);
  913. }
  914. // use proper encoding to escape input
  915. private string ToQuotedPrintable (string input, Encoding enc)
  916. {
  917. byte [] bytes = enc.GetBytes (input);
  918. return ToQuotedPrintable (bytes);
  919. }
  920. private string ToQuotedPrintable (byte [] bytes)
  921. {
  922. StringWriter writer = new StringWriter ();
  923. int charsInLine = 0;
  924. int curLen;
  925. StringBuilder sb = new StringBuilder("=", 3);
  926. byte equalSign = (byte)'=';
  927. char c = (char)0;
  928. foreach (byte i in bytes) {
  929. if (i > 127 || i == equalSign) {
  930. sb.Length = 1;
  931. sb.Append(Convert.ToString (i, 16).ToUpperInvariant ());
  932. curLen = 3;
  933. } else {
  934. c = Convert.ToChar (i);
  935. if (c == '\r' || c == '\n') {
  936. writer.Write (c);
  937. charsInLine = 0;
  938. continue;
  939. }
  940. curLen = 1;
  941. }
  942. charsInLine += curLen;
  943. if (charsInLine > 75) {
  944. writer.Write ("=\r\n");
  945. charsInLine = curLen;
  946. }
  947. if (curLen == 1)
  948. writer.Write (c);
  949. else
  950. writer.Write (sb.ToString ());
  951. }
  952. return writer.ToString ();
  953. }
  954. private static string GetTransferEncodingName (TransferEncoding encoding)
  955. {
  956. switch (encoding) {
  957. case TransferEncoding.QuotedPrintable:
  958. return "quoted-printable";
  959. case TransferEncoding.SevenBit:
  960. return "7bit";
  961. case TransferEncoding.Base64:
  962. return "base64";
  963. }
  964. return "unknown";
  965. }
  966. #if SECURITY_DEP
  967. RemoteCertificateValidationCallback callback = delegate (object sender,
  968. X509Certificate certificate,
  969. X509Chain chain,
  970. SslPolicyErrors sslPolicyErrors) {
  971. // honor any exciting callback defined on ServicePointManager
  972. if (ServicePointManager.ServerCertificateValidationCallback != null)
  973. return ServicePointManager.ServerCertificateValidationCallback (sender, certificate, chain, sslPolicyErrors);
  974. // otherwise provide our own
  975. if (sslPolicyErrors != SslPolicyErrors.None)
  976. throw new InvalidOperationException ("SSL authentication error: " + sslPolicyErrors);
  977. return true;
  978. };
  979. #endif
  980. private void InitiateSecureConnection () {
  981. SmtpResponse response = SendCommand ("STARTTLS");
  982. if (IsError (response)) {
  983. throw new SmtpException (SmtpStatusCode.GeneralFailure, "Server does not support secure connections.");
  984. }
  985. #if SECURITY_DEP
  986. SslStream sslStream = new SslStream (stream, false, callback, null);
  987. CheckCancellation ();
  988. sslStream.AuthenticateAsClient (Host, this.ClientCertificates, SslProtocols.Default, false);
  989. stream = sslStream;
  990. #else
  991. throw new SystemException ("You are using an incomplete System.dll build");
  992. #endif
  993. }
  994. void Authenticate ()
  995. {
  996. string user = null, pass = null;
  997. if (UseDefaultCredentials) {
  998. user = CredentialCache.DefaultCredentials.GetCredential (new System.Uri ("smtp://" + host), "basic").UserName;
  999. pass = CredentialCache.DefaultCredentials.GetCredential (new System.Uri ("smtp://" + host), "basic").Password;
  1000. } else if (Credentials != null) {
  1001. user = Credentials.GetCredential (host, port, "smtp").UserName;
  1002. pass = Credentials.GetCredential (host, port, "smtp").Password;
  1003. } else {
  1004. return;
  1005. }
  1006. Authenticate (user, pass);
  1007. }
  1008. void CheckStatus (SmtpResponse status, int i)
  1009. {
  1010. if (((int) status.StatusCode) != i)
  1011. throw new SmtpException (status.StatusCode, status.Description);
  1012. }
  1013. void ThrowIfError (SmtpResponse status)
  1014. {
  1015. if (IsError (status))
  1016. throw new SmtpException (status.StatusCode, status.Description);
  1017. }
  1018. void Authenticate (string user, string password)
  1019. {
  1020. if (authMechs == AuthMechs.None)
  1021. return;
  1022. SmtpResponse status;
  1023. /*
  1024. if ((authMechs & AuthMechs.DigestMD5) != 0) {
  1025. status = SendCommand ("AUTH DIGEST-MD5");
  1026. CheckStatus (status, 334);
  1027. string challenge = Encoding.ASCII.GetString (Convert.FromBase64String (status.Description.Substring (4)));
  1028. Console.WriteLine ("CHALLENGE: {0}", challenge);
  1029. DigestSession session = new DigestSession ();
  1030. session.Parse (false, challenge);
  1031. string response = session.Authenticate (this, user, password);
  1032. status = SendCommand (Convert.ToBase64String (Encoding.UTF8.GetBytes (response)));
  1033. CheckStatus (status, 235);
  1034. } else */
  1035. if ((authMechs & AuthMechs.Login) != 0) {
  1036. status = SendCommand ("AUTH LOGIN");
  1037. CheckStatus (status, 334);
  1038. status = SendCommand (Convert.ToBase64String (Encoding.UTF8.GetBytes (user)));
  1039. CheckStatus (status, 334);
  1040. status = SendCommand (Convert.ToBase64String (Encoding.UTF8.GetBytes (password)));
  1041. CheckStatus (status, 235);
  1042. } else if ((authMechs & AuthMechs.Plain) != 0) {
  1043. string s = String.Format ("\0{0}\0{1}", user, password);
  1044. s = Convert.ToBase64String (Encoding.UTF8.GetBytes (s));
  1045. status = SendCommand ("AUTH PLAIN " + s);
  1046. CheckStatus (status, 235);
  1047. } else {
  1048. throw new SmtpException ("AUTH types PLAIN, LOGIN not supported by the server");
  1049. }
  1050. }
  1051. #endregion // Methods
  1052. // The HeaderName struct is used to store constant string values representing mail headers.
  1053. private struct HeaderName {
  1054. public const string ContentTransferEncoding = "Content-Transfer-Encoding";
  1055. public const string ContentType = "Content-Type";
  1056. public const string Bcc = "Bcc";
  1057. public const string Cc = "Cc";
  1058. public const string From = "From";
  1059. public const string Subject = "Subject";
  1060. public const string To = "To";
  1061. public const string MimeVersion = "MIME-Version";
  1062. public const string MessageId = "Message-ID";
  1063. public const string Priority = "Priority";
  1064. public const string Importance = "Importance";
  1065. public const string XPriority = "X-Priority";
  1066. public const string Date = "Date";
  1067. }
  1068. // This object encapsulates the status code and description of an SMTP response.
  1069. private struct SmtpResponse {
  1070. public SmtpStatusCode StatusCode;
  1071. public string Description;
  1072. public static SmtpResponse Parse (string line) {
  1073. SmtpResponse response = new SmtpResponse ();
  1074. if (line.Length < 4)
  1075. throw new SmtpException ("Response is to short " +
  1076. line.Length + ".");
  1077. if ((line [3] != ' ') && (line [3] != '-'))
  1078. throw new SmtpException ("Response format is wrong.(" +
  1079. line + ")");
  1080. // parse the response code
  1081. response.StatusCode = (SmtpStatusCode) Int32.Parse (line.Substring (0, 3));
  1082. // set the raw response
  1083. response.Description = line;
  1084. return response;
  1085. }
  1086. }
  1087. }
  1088. class CCredentialsByHost : ICredentialsByHost
  1089. {
  1090. public CCredentialsByHost (string userName, string password) {
  1091. this.userName = userName;
  1092. this.password = password;
  1093. }
  1094. public NetworkCredential GetCredential (string host, int port, string authenticationType) {
  1095. return new NetworkCredential (userName, password);
  1096. }
  1097. private string userName;
  1098. private string password;
  1099. }
  1100. }