SmtpClient.cs 37 KB

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