// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
-#if NET_2_0
-
#if SECURITY_DEP
+
+#if MONOTOUCH || MONODROID
+using System.Security.Cryptography.X509Certificates;
+#else
extern alias PrebuiltSystem;
+using X509CertificateCollection = PrebuiltSystem::System.Security.Cryptography.X509Certificates.X509CertificateCollection;
+using System.Security.Cryptography.X509Certificates;
+#endif
+
#endif
using System;
using System.Net;
using System.Net.Mime;
using System.Net.Sockets;
-using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Reflection;
using System.Configuration;
using System.Net.Security;
using System.Security.Authentication;
-
-#if SECURITY_DEP
-using X509CertificateCollection = PrebuiltSystem::System.Security.Cryptography.X509Certificates.X509CertificateCollection;
+#if NET_4_5
+using System.Threading.Tasks;
#endif
namespace System.Net.Mail {
public class SmtpClient
+#if NET_4_0
+ : IDisposable
+#endif
{
#region Fields
BackgroundWorker worker;
object user_async_state;
- // ESMTP state
[Flags]
enum AuthMechs {
None = 0,
- CramMD5 = 0x01,
- DigestMD5 = 0x02,
- GssAPI = 0x04,
- Kerberos4 = 0x08,
- Login = 0x10,
- Plain = 0x20,
+ Login = 0x01,
+ Plain = 0x02,
}
class CancellationException : Exception
{
}
- AuthMechs authMechs = AuthMechs.None;
+ AuthMechs authMechs;
Mutex mutex = new Mutex ();
#endregion // Fields
if (cfg != null) {
this.host = cfg.Network.Host;
this.port = cfg.Network.Port;
+#if NET_4_0
+ this.enableSsl = cfg.Network.EnableSsl;
+#endif
TargetName = cfg.Network.TargetName;
if (this.TargetName == null)
TargetName = "SMTPSVC/" + (host != null ? host : "");
Credentials = new CCredentialsByHost (cfg.Network.UserName, password);
}
- if (cfg.From != null)
+ if (!String.IsNullOrEmpty (cfg.From))
defaultFrom = new MailAddress (cfg.From);
}
#else
if (port != 0)
this.port = port;
+ else if (this.port == 0)
+ this.port = 25;
}
#endregion // Constructors
#endregion // Events
#region Methods
+#if NET_4_0
+ public void Dispose ()
+ {
+ Dispose (true);
+ }
+ [MonoTODO ("Does nothing at the moment.")]
+ protected virtual void Dispose (bool disposing)
+ {
+ // TODO: We should close all the connections and abort any async operations here
+ }
+#endif
private void CheckState ()
{
if (messageInProcess != null)
private static string EncodeAddress(MailAddress address)
{
- string encodedDisplayName = ContentType.EncodeSubjectRFC2047 (address.DisplayName, Encoding.UTF8);
- return "\"" + encodedDisplayName + "\" <" + address.Address + ">";
+ if (!String.IsNullOrEmpty (address.DisplayName)) {
+ string encodedDisplayName = ContentType.EncodeSubjectRFC2047 (address.DisplayName, Encoding.UTF8);
+ return "\"" + encodedDisplayName + "\" <" + address.Address + ">";
+ }
+ return address.ToString ();
}
private static string EncodeAddresses(MailAddressCollection addresses)
void ParseExtensions (string extens)
{
- char[] delims = new char [1] { ' ' };
string[] parts = extens.Split ('\n');
foreach (string part in parts) {
string start = part.Substring (4);
if (start.StartsWith ("AUTH ", StringComparison.Ordinal)) {
- string[] options = start.Split (delims);
+ string[] options = start.Split (' ');
for (int k = 1; k < options.Length; k++) {
string option = options[k].Trim();
+ // GSSAPI, KERBEROS_V4, NTLM not supported
switch (option) {
+ /*
case "CRAM-MD5":
authMechs |= AuthMechs.CramMD5;
break;
case "DIGEST-MD5":
authMechs |= AuthMechs.DigestMD5;
break;
- case "GSSAPI":
- authMechs |= AuthMechs.GssAPI;
- break;
- case "KERBEROS_V4":
- authMechs |= AuthMechs.Kerberos4;
- break;
+ */
case "LOGIN":
authMechs |= AuthMechs.Login;
break;
try {
writer = new StreamWriter(filename);
+ // FIXME: See how Microsoft fixed the bug about envelope senders, and how it actually represents the info in .eml file headers
+ // For all we know, defaultFrom may be the envelope sender
+ // For now, we are no worse than some versions of .NET
MailAddress from = message.From;
-
if (from == null)
from = defaultFrom;
-
- SendHeader (HeaderName.Date, DateTime.Now.ToString ("ddd, dd MMM yyyy HH':'mm':'ss zzz", DateTimeFormatInfo.InvariantInfo));
- SendHeader (HeaderName.From, from.ToString ());
- SendHeader (HeaderName.To, message.To.ToString ());
+
+ string dt = DateTime.Now.ToString("ddd, dd MMM yyyy HH':'mm':'ss zzz", DateTimeFormatInfo.InvariantInfo);
+ // remove ':' from time zone offset (e.g. from "+01:00")
+ dt = dt.Remove(dt.Length - 3, 1);
+ SendHeader(HeaderName.Date, dt);
+
+ SendHeader (HeaderName.From, EncodeAddress(from));
+ SendHeader (HeaderName.To, EncodeAddresses(message.To));
if (message.CC.Count > 0)
- SendHeader (HeaderName.Cc, message.CC.ToString ());
+ SendHeader (HeaderName.Cc, EncodeAddresses(message.CC));
SendHeader (HeaderName.Subject, EncodeSubjectRFC2047 (message));
foreach (string s in message.Headers.AllKeys)
if (authMechs != AuthMechs.None)
Authenticate ();
-
- MailAddress from = message.From;
- if (from == null)
- from = defaultFrom;
+ // The envelope sender: use 'Sender:' in preference of 'From:'
+ MailAddress sender = message.Sender;
+ if (sender == null)
+ sender = message.From;
+ if (sender == null)
+ sender = defaultFrom;
// MAIL FROM:
- status = SendCommand ("MAIL FROM:<" + from.Address + '>');
+ status = SendCommand ("MAIL FROM:<" + sender.Address + '>');
if (IsError (status)) {
throw new SmtpException (status.StatusCode, status.Description);
}
dt = dt.Remove (dt.Length - 3, 1);
SendHeader (HeaderName.Date, dt);
+ MailAddress from = message.From;
+ if (from == null)
+ from = defaultFrom;
+
SendHeader (HeaderName.From, EncodeAddress (from));
SendHeader (HeaderName.To, EncodeAddresses (message.To));
if (message.CC.Count > 0)
Send (new MailMessage (from, to, subject, body));
}
+#if NET_4_5
+ public Task SendMailAsync (MailMessage message)
+ {
+ var tcs = new TaskCompletionSource<object> ();
+ SendCompletedEventHandler handler = null;
+ handler = (s, e) => SendMailAsyncCompletedHandler (tcs, e, handler, this);
+ SendCompleted += handler;
+ SendAsync (message, tcs);
+ return tcs.Task;
+ }
+
+ public Task SendMailAsync (string from, string recipients, string subject, string body)
+ {
+ return SendMailAsync (new MailMessage (from, recipients, subject, body));
+ }
+
+ static void SendMailAsyncCompletedHandler (TaskCompletionSource<object> source, AsyncCompletedEventArgs e, SendCompletedEventHandler handler, SmtpClient client)
+ {
+ if (handler != e.UserState)
+ return;
+
+ client.SendCompleted -= handler;
+
+ if (e.Error != null) {
+ source.SetException (e.Error);
+ return;
+ }
+
+ if (e.Cancelled) {
+ source.SetCanceled ();
+ return;
+ }
+
+ source.SetResult (null);
+ }
+#endif
+
private void SendDot()
{
writer.Write(".\r\n");
contentType.Parameters ["type"] = av.ContentType.ToString ();
StartSection (inner_boundary, contentType);
- StartSection (alt_boundary, av.ContentType, av.TransferEncoding);
+ StartSection (alt_boundary, av.ContentType, av);
} else {
contentType = new ContentType (av.ContentType.ToString ());
- StartSection (inner_boundary, contentType, av.TransferEncoding);
+ StartSection (inner_boundary, contentType, av);
}
switch (av.TransferEncoding) {
private void SendLinkedResources (MailMessage message, LinkedResourceCollection resources, string boundary)
{
foreach (LinkedResource lr in resources) {
- StartSection (boundary, lr.ContentType, lr.TransferEncoding, lr);
+ StartSection (boundary, lr.ContentType, lr);
switch (lr.TransferEncoding) {
case TransferEncoding.Base64:
contentType.CharSet = att.NameEncoding.HeaderName;
att.ContentDisposition.FileName = att.Name;
}
- StartSection (boundary, contentType, att.TransferEncoding, att == body ? null : att.ContentDisposition);
+ StartSection (boundary, contentType, att, att != body);
byte [] content = new byte [att.ContentStream.Length];
att.ContentStream.Read (content, 0, content.Length);
SendData (string.Empty);
}
- private void StartSection (string section, ContentType sectionContentType,TransferEncoding transferEncoding)
+ private void StartSection (string section, ContentType sectionContentType, AttachmentBase att)
{
SendData (String.Format ("--{0}", section));
SendHeader ("content-type", sectionContentType.ToString ());
- SendHeader ("content-transfer-encoding", GetTransferEncodingName (transferEncoding));
- SendData (string.Empty);
- }
-
- private void StartSection(string section, ContentType sectionContentType, TransferEncoding transferEncoding, LinkedResource lr)
- {
- SendData (String.Format("--{0}", section));
- SendHeader ("content-type", sectionContentType.ToString ());
- SendHeader ("content-transfer-encoding", GetTransferEncodingName (transferEncoding));
-
- if (lr.ContentId != null && lr.ContentId.Length > 0)
- SendHeader("content-ID", "<" + lr.ContentId + ">");
-
+ SendHeader ("content-transfer-encoding", GetTransferEncodingName (att.TransferEncoding));
+ if (!string.IsNullOrEmpty (att.ContentId))
+ SendHeader("content-ID", "<" + att.ContentId + ">");
SendData (string.Empty);
}
- private void StartSection (string section, ContentType sectionContentType, TransferEncoding transferEncoding, ContentDisposition contentDisposition) {
+ private void StartSection (string section, ContentType sectionContentType, Attachment att, bool sendDisposition) {
SendData (String.Format ("--{0}", section));
+ if (!string.IsNullOrEmpty (att.ContentId))
+ SendHeader("content-ID", "<" + att.ContentId + ">");
SendHeader ("content-type", sectionContentType.ToString ());
- SendHeader ("content-transfer-encoding", GetTransferEncodingName (transferEncoding));
- if (contentDisposition != null)
- SendHeader ("content-disposition", contentDisposition.ToString ());
+ SendHeader ("content-transfer-encoding", GetTransferEncodingName (att.TransferEncoding));
+ if (sendDisposition)
+ SendHeader ("content-disposition", att.ContentDisposition.ToString ());
SendData (string.Empty);
}
-
+
// use proper encoding to escape input
private string ToQuotedPrintable (string input, Encoding enc)
{
Authenticate (user, pass);
}
- void Authenticate (string Username, string Password)
+ void CheckStatus (SmtpResponse status, int i)
{
- // FIXME: use the proper AuthMech
- SmtpResponse status = SendCommand ("AUTH LOGIN");
- if (((int) status.StatusCode) != 334) {
+ if (((int) status.StatusCode) != i)
throw new SmtpException (status.StatusCode, status.Description);
- }
+ }
- status = SendCommand (Convert.ToBase64String (Encoding.ASCII.GetBytes (Username)));
- if (((int) status.StatusCode) != 334) {
+ void ThrowIfError (SmtpResponse status)
+ {
+ if (IsError (status))
throw new SmtpException (status.StatusCode, status.Description);
- }
+ }
- status = SendCommand (Convert.ToBase64String (Encoding.ASCII.GetBytes (Password)));
- if (IsError (status)) {
- throw new SmtpException (status.StatusCode, status.Description);
+ void Authenticate (string user, string password)
+ {
+ if (authMechs == AuthMechs.None)
+ return;
+
+ SmtpResponse status;
+ /*
+ if ((authMechs & AuthMechs.DigestMD5) != 0) {
+ status = SendCommand ("AUTH DIGEST-MD5");
+ CheckStatus (status, 334);
+ string challenge = Encoding.ASCII.GetString (Convert.FromBase64String (status.Description.Substring (4)));
+ Console.WriteLine ("CHALLENGE: {0}", challenge);
+ DigestSession session = new DigestSession ();
+ session.Parse (false, challenge);
+ string response = session.Authenticate (this, user, password);
+ status = SendCommand (Convert.ToBase64String (Encoding.UTF8.GetBytes (response)));
+ CheckStatus (status, 235);
+ } else */
+ if ((authMechs & AuthMechs.Login) != 0) {
+ status = SendCommand ("AUTH LOGIN");
+ CheckStatus (status, 334);
+ status = SendCommand (Convert.ToBase64String (Encoding.UTF8.GetBytes (user)));
+ CheckStatus (status, 334);
+ status = SendCommand (Convert.ToBase64String (Encoding.UTF8.GetBytes (password)));
+ CheckStatus (status, 235);
+ } else if ((authMechs & AuthMechs.Plain) != 0) {
+ string s = String.Format ("\0{0}\0{1}", user, password);
+ s = Convert.ToBase64String (Encoding.UTF8.GetBytes (s));
+ status = SendCommand ("AUTH PLAIN " + s);
+ CheckStatus (status, 235);
+ } else {
+ throw new SmtpException ("AUTH types PLAIN, LOGIN not supported by the server");
}
}
-
+
#endregion // Methods
// The HeaderName struct is used to store constant string values representing mail headers.
}
}
-#endif // NET_2_0