2 // System.Net.Mail.SmtpClient.cs
5 // Tim Coleman (tim@timcoleman.com)
7 // Copyright (C) Tim Coleman, 2004
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34 extern alias PrebuiltSystem;
38 using System.Collections.Generic;
39 using System.ComponentModel;
40 using System.Globalization;
43 using System.Net.Mime;
44 using System.Net.Sockets;
45 using System.Security.Cryptography.X509Certificates;
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;
55 using X509CertificateCollection = PrebuiltSystem::System.Security.Cryptography.X509Certificates.X509CertificateCollection;
58 namespace System.Net.Mail {
59 public class SmtpClient
66 ICredentialsByHost credentials;
67 bool useDefaultCredentials = false;
68 string pickupDirectoryLocation;
69 SmtpDeliveryMethod deliveryMethod;
71 X509CertificateCollection clientCertificates;
78 MailAddress defaultFrom;
80 MailMessage messageInProcess;
82 BackgroundWorker worker;
83 object user_async_state;
96 class CancellationException : Exception
100 AuthMechs authMechs = AuthMechs.None;
101 bool canStartTLS = false;
103 Mutex mutex = new Mutex ();
114 public SmtpClient (string host)
119 public SmtpClient (string host, int port) {
120 #if CONFIGURATION_DEP
121 SmtpSection cfg = (SmtpSection) ConfigurationManager.GetSection ("system.net/mailSettings/smtp");
124 this.host = cfg.Network.Host;
125 this.port = cfg.Network.Port;
126 if (cfg.Network.UserName != null) {
127 string password = String.Empty;
129 if (cfg.Network.Password != null)
130 password = cfg.Network.Password;
132 Credentials = new CCredentialsByHost (cfg.Network.UserName, password);
135 if (cfg.From != null)
136 defaultFrom = new MailAddress (cfg.From);
140 if (!String.IsNullOrEmpty (host))
147 #endregion // Constructors
152 [MonoTODO("STARTTLS is not supported yet")]
153 public X509CertificateCollection ClientCertificates {
155 if (clientCertificates == null)
156 clientCertificates = new X509CertificateCollection ();
157 return clientCertificates;
162 public ICredentialsByHost Credentials {
163 get { return credentials; }
170 public SmtpDeliveryMethod DeliveryMethod {
171 get { return deliveryMethod; }
174 deliveryMethod = value;
178 [MonoTODO("STARTTLS is not supported yet")]
179 public bool EnableSsl {
180 // FIXME: So... is this supposed to enable SSL port functionality? or STARTTLS? Or both?
181 get { return enableSsl; }
192 throw new ArgumentNullException ();
193 if (value.Length == 0)
194 throw new ArgumentException ();
200 public string PickupDirectoryLocation {
201 get { return pickupDirectoryLocation; }
202 set { pickupDirectoryLocation = value; }
208 if (value <= 0 || value > 65535)
209 throw new ArgumentOutOfRangeException ();
216 public ServicePoint ServicePoint {
217 get { throw new NotImplementedException (); }
221 get { return timeout; }
224 throw new ArgumentOutOfRangeException ();
230 public bool UseDefaultCredentials {
231 get { return useDefaultCredentials; }
232 [MonoNotSupported ("no DefaultCredential support in Mono")]
235 throw new NotImplementedException ("Default credentials are not supported");
240 #endregion // Properties
244 public event SendCompletedEventHandler SendCompleted;
250 private void CheckState ()
252 if (messageInProcess != null)
253 throw new InvalidOperationException ("Cannot set Timeout while Sending a message");
256 private string EncodeSubjectRFC2047 (MailMessage message)
258 return ContentType.EncodeSubjectRFC2047 (message.Subject, message.SubjectEncoding);
261 private string EncodeBody (MailMessage message)
263 string body = message.Body;
264 Encoding encoding = message.BodyEncoding;
266 switch (message.ContentTransferEncoding) {
267 case TransferEncoding.SevenBit:
269 case TransferEncoding.Base64:
270 return Convert.ToBase64String (encoding.GetBytes (body), Base64FormattingOptions.InsertLineBreaks);
272 return ToQuotedPrintable (body, encoding);
276 private string EncodeBody (AlternateView av)
278 //Encoding encoding = av.ContentType.CharSet != null ? Encoding.GetEncoding (av.ContentType.CharSet) : Encoding.UTF8;
280 byte [] bytes = new byte [av.ContentStream.Length];
281 av.ContentStream.Read (bytes, 0, bytes.Length);
284 switch (av.TransferEncoding) {
285 case TransferEncoding.SevenBit:
286 return Encoding.ASCII.GetString (bytes);
287 case TransferEncoding.Base64:
288 return Convert.ToBase64String (bytes, Base64FormattingOptions.InsertLineBreaks);
290 return ToQuotedPrintable (bytes);
295 private void EndSection (string section)
297 SendData (String.Format ("--{0}--", section));
298 SendData (string.Empty);
301 private string GenerateBoundary ()
303 string output = GenerateBoundary (boundaryIndex);
308 private static string GenerateBoundary (int index)
310 return String.Format ("--boundary_{0}_{1}", index, Guid.NewGuid ().ToString ("D"));
313 private bool IsError (SmtpResponse status)
315 return ((int) status.StatusCode) >= 400;
318 protected void OnSendCompleted (AsyncCompletedEventArgs e)
321 if (SendCompleted != null)
322 SendCompleted (this, e);
325 user_async_state = null;
329 private void CheckCancellation ()
331 if (worker != null && worker.CancellationPending)
332 throw new CancellationException ();
335 private SmtpResponse Read () {
336 byte [] buffer = new byte [512];
338 bool lastLine = false;
341 CheckCancellation ();
343 int readLength = stream.Read (buffer, position, buffer.Length - position);
344 if (readLength > 0) {
345 int available = position + readLength - 1;
346 if (available > 4 && (buffer [available] == '\n' || buffer [available] == '\r'))
347 for (int index = available - 3; ; index--) {
348 if (index < 0 || buffer [index] == '\n' || buffer [index] == '\r') {
349 lastLine = buffer [index + 4] == ' ';
355 position += readLength;
357 // check if buffer is full
358 if (position == buffer.Length) {
359 byte [] newBuffer = new byte [buffer.Length * 2];
360 Array.Copy (buffer, 0, newBuffer, 0, buffer.Length);
370 Encoding encoding = new ASCIIEncoding ();
372 string line = encoding.GetString (buffer, 0, position - 1);
374 // parse the line to the lastResponse object
375 SmtpResponse response = SmtpResponse.Parse (line);
379 throw new System.IO.IOException ("Connection closed");
383 void ParseExtensions (string extens)
385 char []delims = new char [1] { ' ' };
392 if (ln + 4 >= extens.Length)
395 if (extens.Substring (ln, 4) == "AUTH" &&
396 (extens[ln + 4] == ' ' || extens[ln + 4] == '=')) {
397 int eoln = extens.IndexOf ('\n', ln + 4);
398 string mechlist = extens.Substring (ln, eoln);
399 string []mechs = mechlist.Split (delims);
403 for (int i = 0; i < mechs.Length; i++) {
406 authMechs |= AuthMechs.CramMD5;
409 authMechs |= AuthMechs.DigestMD5;
412 authMechs |= AuthMechs.GssAPI;
415 authMechs |= AuthMechs.Kerberos4;
418 authMechs |= AuthMechs.Login;
421 authMechs |= AuthMechs.Plain;
425 } else if (ln + 8 < extens.Length && extens.Substring (ln, 8) == "STARTTLS") {
428 } while (ln < extens.Length && ((ln = extens.IndexOf ('\n', ln)) != -1));
431 public void Send (MailMessage message)
434 throw new ArgumentNullException ("message");
436 if (String.IsNullOrEmpty (Host))
437 throw new InvalidOperationException ("The SMTP host was not specified");
442 // Block while sending
445 messageInProcess = message;
447 } catch (CancellationException) {
448 // This exception is introduced for convenient cancellation process.
450 // Release the mutex to allow other threads access
451 mutex.ReleaseMutex ();
452 messageInProcess = null;
456 private void SendCore (MailMessage message)
460 CheckCancellation ();
462 client = new TcpClient (host, port);
463 stream = client.GetStream ();
464 // FIXME: this StreamWriter creation is bogus.
465 // It expects as if a Stream were able to switch to SSL
466 // mode (such behavior is only in Mainsoft Socket API).
467 writer = new StreamWriter (stream);
468 reader = new StreamReader (stream);
471 if (IsError (status))
472 throw new SmtpException (status.StatusCode, status.Description);
476 // FIXME: parse the list of extensions so we don't bother wasting
477 // our time trying commands if they aren't supported.
478 status = SendCommand ("EHLO " + Dns.GetHostName ());
480 if (IsError (status)) {
481 status = SendCommand ("HELO " + Dns.GetHostName ());
483 if (IsError (status))
484 throw new SmtpException (status.StatusCode, status.Description);
486 // Parse ESMTP extensions
487 string extens = status.Description;
490 ParseExtensions (extens);
495 // FIXME: I get the feeling from the docs that EnableSsl is meant
496 // for using the SSL-port and not STARTTLS (or, if it includes
497 // STARTTLS... only use STARTTLS if the SSL-type in the certificate
498 // is TLS and not SSLv2 or SSLv3)
500 // FIXME: even tho we have a canStartTLS flag... ignore it for now
501 // so that the STARTTLS command can throw the appropriate
502 // SmtpException if STARTTLS is unavailable
504 // SmtpClient implements STARTTLS support.
506 InitiateSecureConnection ();
511 if (authMechs != AuthMechs.None)
514 MailAddress from = message.From;
520 status = SendCommand ("MAIL FROM:<" + from.Address + '>');
521 if (IsError (status)) {
522 throw new SmtpException (status.StatusCode, status.Description);
525 // Send RCPT TO: for all recipients
526 List<SmtpFailedRecipientException> sfre = new List<SmtpFailedRecipientException> ();
528 for (int i = 0; i < message.To.Count; i ++) {
529 status = SendCommand ("RCPT TO:<" + message.To [i].Address + '>');
530 if (IsError (status))
531 sfre.Add (new SmtpFailedRecipientException (status.StatusCode, message.To [i].Address));
533 for (int i = 0; i < message.CC.Count; i ++) {
534 status = SendCommand ("RCPT TO:<" + message.CC [i].Address + '>');
535 if (IsError (status))
536 sfre.Add (new SmtpFailedRecipientException (status.StatusCode, message.CC [i].Address));
538 for (int i = 0; i < message.Bcc.Count; i ++) {
539 status = SendCommand ("RCPT TO:<" + message.Bcc [i].Address + '>');
540 if (IsError (status))
541 sfre.Add (new SmtpFailedRecipientException (status.StatusCode, message.Bcc [i].Address));
544 #if TARGET_JVM // List<T>.ToArray () is not supported
545 if (sfre.Count > 0) {
546 SmtpFailedRecipientException[] xs = new SmtpFailedRecipientException[sfre.Count];
548 throw new SmtpFailedRecipientsException ("failed recipients", xs);
552 throw new SmtpFailedRecipientsException ("failed recipients", sfre.ToArray ());
556 status = SendCommand ("DATA");
557 if (IsError (status))
558 throw new SmtpException (status.StatusCode, status.Description);
560 // Send message headers
561 string dt = DateTime.Now.ToString ("ddd, dd MMM yyyy HH':'mm':'ss zzz", DateTimeFormatInfo.InvariantInfo);
562 // remove ':' from time zone offset (e.g. from "+01:00")
563 dt = dt.Remove (dt.Length - 3, 1);
564 SendHeader (HeaderName.Date, dt);
566 SendHeader (HeaderName.From, from.ToString ());
567 SendHeader (HeaderName.To, message.To.ToString ());
568 if (message.CC.Count > 0)
569 SendHeader (HeaderName.Cc, message.CC.ToString ());
570 SendHeader (HeaderName.Subject, EncodeSubjectRFC2047 (message));
572 foreach (string s in message.Headers.AllKeys)
573 SendHeader (s, message.Headers [s]);
575 AddPriorityHeader (message);
578 if (message.Attachments.Count > 0)
579 SendWithAttachments (message);
581 SendWithoutAttachments (message, null, false);
586 if (IsError (status))
587 throw new SmtpException (status.StatusCode, status.Description);
590 status = SendCommand ("QUIT");
591 } catch (System.IO.IOException) {
592 // We excuse server for the rude connection closing as a response to QUIT
601 public void Send (string from, string to, string subject, string body)
603 Send (new MailMessage (from, to, subject, body));
606 private void SendData (string data)
608 CheckCancellation ();
610 // Certain SMTP servers will reject mail sent with unix line-endings; see http://cr.yp.to/docs/smtplf.html
611 StringBuilder sb = new StringBuilder (data);
612 sb.Replace ("\r\n", "\n");
613 sb.Replace ('\r', '\n');
614 sb.Replace ("\n", "\r\n");
615 writer.Write (sb.ToString ());
616 writer.Write ("\r\n");
620 public void SendAsync (MailMessage message, object userToken)
623 throw new InvalidOperationException ("Another SendAsync operation is in progress");
625 worker = new BackgroundWorker ();
626 worker.DoWork += delegate (object o, DoWorkEventArgs ea) {
628 user_async_state = ea.Argument;
630 } catch (Exception ex) {
634 worker.WorkerSupportsCancellation = true;
635 worker.RunWorkerCompleted += delegate (object o, RunWorkerCompletedEventArgs ea) {
636 // Note that RunWorkerCompletedEventArgs.UserState cannot be used (LAMESPEC)
637 OnSendCompleted (new AsyncCompletedEventArgs (ea.Error, ea.Cancelled, user_async_state));
639 worker.RunWorkerAsync (userToken);
642 public void SendAsync (string from, string to, string subject, string body, object userToken)
644 SendAsync (new MailMessage (from, to, subject, body), userToken);
647 public void SendAsyncCancel ()
650 throw new InvalidOperationException ("SendAsync operation is not in progress");
651 worker.CancelAsync ();
654 private void AddPriorityHeader (MailMessage message) {
655 switch (message.Priority) {
656 case MailPriority.High:
657 SendHeader (HeaderName.Priority, "Urgent");
658 SendHeader (HeaderName.Importance, "high");
659 SendHeader (HeaderName.XPriority, "1");
661 case MailPriority.Low:
662 SendHeader (HeaderName.Priority, "Non-Urgent");
663 SendHeader (HeaderName.Importance, "low");
664 SendHeader (HeaderName.XPriority, "5");
669 private void SendSimpleBody (MailMessage message) {
670 SendHeader (HeaderName.ContentType, message.BodyContentType.ToString ());
671 if (message.ContentTransferEncoding != TransferEncoding.SevenBit)
672 SendHeader (HeaderName.ContentTransferEncoding, GetTransferEncodingName (message.ContentTransferEncoding));
673 SendData (string.Empty);
675 SendData (EncodeBody (message));
678 private void SendBodylessSingleAlternate (AlternateView av) {
679 SendHeader (HeaderName.ContentType, av.ContentType.ToString ());
680 if (av.TransferEncoding != TransferEncoding.SevenBit)
681 SendHeader (HeaderName.ContentTransferEncoding, GetTransferEncodingName (av.TransferEncoding));
682 SendData (string.Empty);
684 SendData (EncodeBody (av));
687 private void SendWithoutAttachments (MailMessage message, string boundary, bool attachmentExists)
689 if (message.Body == null && message.AlternateViews.Count == 1)
690 SendBodylessSingleAlternate (message.AlternateViews [0]);
691 else if (message.AlternateViews.Count > 0)
692 SendBodyWithAlternateViews (message, boundary, attachmentExists);
694 SendSimpleBody (message);
698 private void SendWithAttachments (MailMessage message) {
699 string boundary = GenerateBoundary ();
701 // first "multipart/mixed"
702 ContentType messageContentType = new ContentType ();
703 messageContentType.Boundary = boundary;
704 messageContentType.MediaType = "multipart/mixed";
705 messageContentType.CharSet = null;
707 SendHeader (HeaderName.ContentType, messageContentType.ToString ());
708 SendData (String.Empty);
711 Attachment body = null;
713 if (message.AlternateViews.Count > 0)
714 SendWithoutAttachments (message, boundary, true);
716 body = Attachment.CreateAttachmentFromString (message.Body, null, message.BodyEncoding, message.IsBodyHtml ? "text/html" : "text/plain");
717 message.Attachments.Insert (0, body);
721 SendAttachments (message, body, boundary);
724 message.Attachments.Remove (body);
727 EndSection (boundary);
730 private void SendBodyWithAlternateViews (MailMessage message, string boundary, bool attachmentExists)
732 AlternateViewCollection alternateViews = message.AlternateViews;
734 string inner_boundary = GenerateBoundary ();
736 ContentType messageContentType = new ContentType ();
737 messageContentType.Boundary = inner_boundary;
738 messageContentType.MediaType = "multipart/alternative";
740 if (!attachmentExists) {
741 SendHeader (HeaderName.ContentType, messageContentType.ToString ());
742 SendData (String.Empty);
746 AlternateView body = null;
747 if (message.Body != null) {
748 body = AlternateView.CreateAlternateViewFromString (message.Body, message.BodyEncoding, message.IsBodyHtml ? "text/html" : "text/plain");
749 alternateViews.Insert (0, body);
750 StartSection (boundary, messageContentType);
754 // alternate view sections
755 foreach (AlternateView av in alternateViews) {
757 string alt_boundary = null;
758 ContentType contentType;
759 if (av.LinkedResources.Count > 0) {
760 alt_boundary = GenerateBoundary ();
761 contentType = new ContentType ("multipart/related");
762 contentType.Boundary = alt_boundary;
763 contentType.Parameters ["type"] = "application/octet-stream";
764 StartSection (inner_boundary, contentType);
766 contentType = new ContentType (av.ContentType.ToString ());
767 StartSection (inner_boundary, contentType, av.TransferEncoding);
770 switch (av.TransferEncoding) {
771 case TransferEncoding.Base64:
772 byte [] content = new byte [av.ContentStream.Length];
773 av.ContentStream.Read (content, 0, content.Length);
775 SendData (Convert.ToBase64String (content));
777 SendData (Convert.ToBase64String (content, Base64FormattingOptions.InsertLineBreaks));
780 case TransferEncoding.QuotedPrintable:
781 byte [] bytes = new byte [av.ContentStream.Length];
782 av.ContentStream.Read (bytes, 0, bytes.Length);
783 SendData (ToQuotedPrintable (bytes));
785 case TransferEncoding.SevenBit:
786 case TransferEncoding.Unknown:
787 content = new byte [av.ContentStream.Length];
788 SendData (Encoding.ASCII.GetString (content));
792 if (av.LinkedResources.Count > 0) {
793 SendLinkedResources (message, av.LinkedResources, alt_boundary);
794 EndSection (alt_boundary);
797 if (!attachmentExists)
798 SendData (string.Empty);
803 alternateViews.Remove (body);
805 EndSection (inner_boundary);
808 private void SendLinkedResources (MailMessage message, LinkedResourceCollection resources, string boundary)
810 foreach (LinkedResource lr in resources) {
811 ContentType contentType = new ContentType (lr.ContentType.ToString ());
812 StartSection (boundary, contentType, lr.TransferEncoding);
814 switch (lr.TransferEncoding) {
815 case TransferEncoding.Base64:
816 byte [] content = new byte [lr.ContentStream.Length];
817 lr.ContentStream.Read (content, 0, content.Length);
819 SendData (Convert.ToBase64String (content));
821 SendData (Convert.ToBase64String (content, Base64FormattingOptions.InsertLineBreaks));
824 case TransferEncoding.QuotedPrintable:
825 byte [] bytes = new byte [lr.ContentStream.Length];
826 lr.ContentStream.Read (bytes, 0, bytes.Length);
827 SendData (ToQuotedPrintable (bytes));
829 case TransferEncoding.SevenBit:
830 case TransferEncoding.Unknown:
831 content = new byte [lr.ContentStream.Length];
832 SendData (Encoding.ASCII.GetString (content));
838 private void SendAttachments (MailMessage message, Attachment body, string boundary) {
839 foreach (Attachment att in message.Attachments) {
840 ContentType contentType = new ContentType (att.ContentType.ToString ());
841 if (att.Name != null) {
842 contentType.Name = att.Name;
843 if (att.NameEncoding != null)
844 contentType.CharSet = att.NameEncoding.HeaderName;
845 att.ContentDisposition.FileName = att.Name;
847 StartSection (boundary, contentType, att.TransferEncoding, att == body ? null : att.ContentDisposition);
849 switch (att.TransferEncoding) {
850 case TransferEncoding.Base64:
851 byte [] content = new byte [att.ContentStream.Length];
852 att.ContentStream.Read (content, 0, content.Length);
854 SendData (Convert.ToBase64String (content));
856 SendData (Convert.ToBase64String (content, Base64FormattingOptions.InsertLineBreaks));
859 case TransferEncoding.QuotedPrintable:
860 byte [] bytes = new byte [att.ContentStream.Length];
861 att.ContentStream.Read (bytes, 0, bytes.Length);
862 SendData (ToQuotedPrintable (bytes));
864 case TransferEncoding.SevenBit:
865 case TransferEncoding.Unknown:
866 content = new byte [att.ContentStream.Length];
867 SendData (Encoding.ASCII.GetString (content));
871 SendData (string.Empty);
875 private SmtpResponse SendCommand (string command)
877 writer.Write (command);
878 // Certain SMTP servers will reject mail sent with unix line-endings; see http://cr.yp.to/docs/smtplf.html
879 writer.Write ("\r\n");
884 private void SendHeader (string name, string value)
886 SendData (String.Format ("{0}: {1}", name, value));
889 private void StartSection (string section, ContentType sectionContentType)
891 SendData (String.Format ("--{0}", section));
892 SendHeader ("content-type", sectionContentType.ToString ());
893 SendData (string.Empty);
896 private void StartSection (string section, ContentType sectionContentType,TransferEncoding transferEncoding)
898 SendData (String.Format ("--{0}", section));
899 SendHeader ("content-type", sectionContentType.ToString ());
900 SendHeader ("content-transfer-encoding", GetTransferEncodingName (transferEncoding));
901 SendData (string.Empty);
904 private void StartSection (string section, ContentType sectionContentType, TransferEncoding transferEncoding, ContentDisposition contentDisposition) {
905 SendData (String.Format ("--{0}", section));
906 SendHeader ("content-type", sectionContentType.ToString ());
907 SendHeader ("content-transfer-encoding", GetTransferEncodingName (transferEncoding));
908 if (contentDisposition != null)
909 SendHeader ("content-disposition", contentDisposition.ToString ());
910 SendData (string.Empty);
913 // use proper encoding to escape input
914 private string ToQuotedPrintable (string input, Encoding enc)
916 byte [] bytes = enc.GetBytes (input);
917 return ToQuotedPrintable (bytes);
920 private string ToQuotedPrintable (byte [] bytes)
922 StringWriter writer = new StringWriter ();
925 StringBuilder sb = new StringBuilder("=", 3);
926 byte equalSign = (byte)'=';
929 foreach (byte i in bytes) {
930 if (i > 127 || i == equalSign) {
932 sb.Append(Convert.ToString (i, 16).ToUpperInvariant ());
935 c = Convert.ToChar (i);
936 if (c == '\r' || c == '\n') {
944 charsInLine += curLen;
945 if (charsInLine > 75) {
946 writer.Write ("=\r\n");
947 charsInLine = curLen;
952 writer.Write (sb.ToString ());
955 return writer.ToString ();
957 private static string GetTransferEncodingName (TransferEncoding encoding)
960 case TransferEncoding.QuotedPrintable:
961 return "quoted-printable";
962 case TransferEncoding.SevenBit:
964 case TransferEncoding.Base64:
971 RemoteCertificateValidationCallback callback = delegate (object sender,
972 X509Certificate certificate,
974 SslPolicyErrors sslPolicyErrors) {
975 if (sslPolicyErrors != SslPolicyErrors.None)
976 throw new InvalidOperationException ("SSL authentication error: " + sslPolicyErrors);
981 private void InitiateSecureConnection () {
982 SmtpResponse response = SendCommand ("STARTTLS");
984 if (IsError (response)) {
985 throw new SmtpException (SmtpStatusCode.GeneralFailure, "Server does not support secure connections.");
989 ((NetworkStream) stream).ChangeToSSLSocket ();
991 SslStream sslStream = new SslStream (stream, false, callback, null);
992 CheckCancellation ();
993 sslStream.AuthenticateAsClient (Host, this.ClientCertificates, SslProtocols.Default, false);
996 throw new NotImplementedException ();
998 throw new SystemException ("You are using an incomplete System.dll build");
1002 void Authenticate ()
1004 string user = null, pass = null;
1006 if (UseDefaultCredentials) {
1007 user = CredentialCache.DefaultCredentials.GetCredential (new System.Uri ("smtp://" + host), "basic").UserName;
1008 pass = CredentialCache.DefaultCredentials.GetCredential (new System.Uri ("smtp://" + host), "basic").Password;
1009 } else if (Credentials != null) {
1010 user = Credentials.GetCredential (host, port, "smtp").UserName;
1011 pass = Credentials.GetCredential (host, port, "smtp").Password;
1016 Authenticate (user, pass);
1019 void Authenticate (string Username, string Password)
1021 // FIXME: use the proper AuthMech
1022 SmtpResponse status = SendCommand ("AUTH LOGIN");
1023 if (((int) status.StatusCode) != 334) {
1024 throw new SmtpException (status.StatusCode, status.Description);
1027 status = SendCommand (Convert.ToBase64String (Encoding.ASCII.GetBytes (Username), Base64FormattingOptions.InsertLineBreaks));
1028 if (((int) status.StatusCode) != 334) {
1029 throw new SmtpException (status.StatusCode, status.Description);
1032 status = SendCommand (Convert.ToBase64String (Encoding.ASCII.GetBytes (Password), Base64FormattingOptions.InsertLineBreaks));
1033 if (IsError (status)) {
1034 throw new SmtpException (status.StatusCode, status.Description);
1038 #endregion // Methods
1040 // The HeaderName struct is used to store constant string values representing mail headers.
1041 private struct HeaderName {
1042 public const string ContentTransferEncoding = "Content-Transfer-Encoding";
1043 public const string ContentType = "Content-Type";
1044 public const string Bcc = "Bcc";
1045 public const string Cc = "Cc";
1046 public const string From = "From";
1047 public const string Subject = "Subject";
1048 public const string To = "To";
1049 public const string MimeVersion = "MIME-Version";
1050 public const string MessageId = "Message-ID";
1051 public const string Priority = "Priority";
1052 public const string Importance = "Importance";
1053 public const string XPriority = "X-Priority";
1054 public const string Date = "Date";
1057 // This object encapsulates the status code and description of an SMTP response.
1058 private struct SmtpResponse {
1059 public SmtpStatusCode StatusCode;
1060 public string Description;
1062 public static SmtpResponse Parse (string line) {
1063 SmtpResponse response = new SmtpResponse ();
1065 if (line.Length < 4)
1066 throw new SmtpException ("Response is to short " +
1069 if ((line [3] != ' ') && (line [3] != '-'))
1070 throw new SmtpException ("Response format is wrong.(" +
1073 // parse the response code
1074 response.StatusCode = (SmtpStatusCode) Int32.Parse (line.Substring (0, 3));
1076 // set the raw response
1077 response.Description = line;
1084 class CCredentialsByHost : ICredentialsByHost
1086 public CCredentialsByHost (string userName, string password) {
1087 this.userName = userName;
1088 this.password = password;
1091 public NetworkCredential GetCredential (string host, int port, string authenticationType) {
1092 return new NetworkCredential (userName, password);
1095 private string userName;
1096 private string password;