int port;
int timeout = 100000;
ICredentialsByHost credentials;
- bool useDefaultCredentials = false;
string pickupDirectoryLocation;
SmtpDeliveryMethod deliveryMethod;
bool enableSsl;
+#if SECURITY_DEP
X509CertificateCollection clientCertificates;
+#endif
TcpClient client;
Stream stream;
}
AuthMechs authMechs = AuthMechs.None;
- bool canStartTLS = false;
-
Mutex mutex = new Mutex ();
#endregion // Fields
if (cfg != null) {
this.host = cfg.Network.Host;
this.port = cfg.Network.Port;
+ TargetName = cfg.Network.TargetName;
+ if (this.TargetName == null)
+ TargetName = "SMTPSVC/" + (host != null ? host : "");
+
+
if (cfg.Network.UserName != null) {
string password = String.Empty;
if (cfg.From != null)
defaultFrom = new MailAddress (cfg.From);
}
+#else
+ // Just to eliminate the warning, this codepath does not end up in production.
+ defaultFrom = null;
#endif
if (!String.IsNullOrEmpty (host))
#region Properties
#if SECURITY_DEP
- [MonoTODO("STARTTLS is not supported yet")]
+ [MonoTODO("Client certificates not used")]
public X509CertificateCollection ClientCertificates {
get {
if (clientCertificates == null)
}
#endif
+#if NET_4_0
+ public
+#endif
+ string TargetName { get; set; }
+
public ICredentialsByHost Credentials {
get { return credentials; }
set {
}
}
- [MonoTODO("STARTTLS is not supported yet")]
public bool EnableSsl {
- // FIXME: So... is this supposed to enable SSL port functionality? or STARTTLS? Or both?
get { return enableSsl; }
set {
CheckState ();
get { return host; }
set {
if (value == null)
- throw new ArgumentNullException ();
+ throw new ArgumentNullException ("value");
if (value.Length == 0)
- throw new ArgumentException ();
+ throw new ArgumentException ("An empty string is not allowed.", "value");
CheckState ();
host = value;
}
public int Port {
get { return port; }
set {
- if (value <= 0 || value > 65535)
- throw new ArgumentOutOfRangeException ();
+ if (value <= 0)
+ throw new ArgumentOutOfRangeException ("value");
CheckState ();
port = value;
}
get { return timeout; }
set {
if (value < 0)
- throw new ArgumentOutOfRangeException ();
+ throw new ArgumentOutOfRangeException ("value");
CheckState ();
timeout = value;
}
}
public bool UseDefaultCredentials {
- get { return useDefaultCredentials; }
+ get { return false; }
[MonoNotSupported ("no DefaultCredential support in Mono")]
set {
if (value)
if (messageInProcess != null)
throw new InvalidOperationException ("Cannot set Timeout while Sending a message");
}
+
+ private static string EncodeAddress(MailAddress address)
+ {
+ string encodedDisplayName = ContentType.EncodeSubjectRFC2047 (address.DisplayName, Encoding.UTF8);
+ return "\"" + encodedDisplayName + "\" <" + address.Address + ">";
+ }
+
+ private static string EncodeAddresses(MailAddressCollection addresses)
+ {
+ StringBuilder sb = new StringBuilder();
+ bool first = true;
+ foreach (MailAddress address in addresses) {
+ if (!first) {
+ sb.Append(", ");
+ }
+ sb.Append(EncodeAddress(address));
+ first = false;
+ }
+ return sb.ToString();
+ }
private string EncodeSubjectRFC2047 (MailMessage message)
{
void ResetExtensions()
{
- canStartTLS = false;
authMechs = AuthMechs.None;
}
break;
}
}
- } else if (start.StartsWith ("STARTTLS", StringComparison.Ordinal)) {
- canStartTLS = true;
}
}
}
if (message == null)
throw new ArgumentNullException ("message");
- if (String.IsNullOrEmpty (Host))
+ if (deliveryMethod == SmtpDeliveryMethod.Network && (Host == null || Host.Trim ().Length == 0))
throw new InvalidOperationException ("The SMTP host was not specified");
-
+ else if (deliveryMethod == SmtpDeliveryMethod.PickupDirectoryFromIis)
+ throw new NotSupportedException("IIS delivery is not supported");
+
if (port == 0)
port = 25;
mutex.WaitOne ();
try {
messageInProcess = message;
- SendInternal (message);
+ if (deliveryMethod == SmtpDeliveryMethod.SpecifiedPickupDirectory)
+ SendToFile (message);
+ else
+ SendInternal (message);
} catch (CancellationException) {
// This exception is introduced for convenient cancellation process.
+ } catch (SmtpException) {
+ throw;
+ } catch (Exception ex) {
+ throw new SmtpException ("Message could not be sent.", ex);
} finally {
// Release the mutex to allow other threads access
mutex.ReleaseMutex ();
client.Close ();
}
}
+
+ // FIXME: simple implementation, could be brushed up.
+ private void SendToFile (MailMessage message)
+ {
+ if (!Path.IsPathRooted (pickupDirectoryLocation))
+ throw new SmtpException("Only absolute directories are allowed for pickup directory.");
+
+ string filename = Path.Combine (pickupDirectoryLocation,
+ Guid.NewGuid() + ".eml");
+
+ try {
+ writer = new StreamWriter(filename);
+
+ 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 ());
+ if (message.CC.Count > 0)
+ SendHeader (HeaderName.Cc, message.CC.ToString ());
+ SendHeader (HeaderName.Subject, EncodeSubjectRFC2047 (message));
+
+ foreach (string s in message.Headers.AllKeys)
+ SendHeader (s, message.Headers [s]);
+
+ AddPriorityHeader (message);
+
+ boundaryIndex = 0;
+ if (message.Attachments.Count > 0)
+ SendWithAttachments (message);
+ else
+ SendWithoutAttachments (message, null, false);
+
+
+ } finally {
+ if (writer != null) writer.Close(); writer = null;
+ }
+ }
private void SendCore (MailMessage message)
{
}
if (enableSsl) {
-#if old_comment
- // FIXME: I get the feeling from the docs that EnableSsl is meant
- // for using the SSL-port and not STARTTLS (or, if it includes
- // STARTTLS... only use STARTTLS if the SSL-type in the certificate
- // is TLS and not SSLv2 or SSLv3)
-
- // FIXME: even tho we have a canStartTLS flag... ignore it for now
- // so that the STARTTLS command can throw the appropriate
- // SmtpException if STARTTLS is unavailable
-#else
- // SmtpClient implements STARTTLS support.
-#endif
InitiateSecureConnection ();
ResetExtensions();
writer = new StreamWriter (stream);
dt = dt.Remove (dt.Length - 3, 1);
SendHeader (HeaderName.Date, dt);
- SendHeader (HeaderName.From, from.ToString ());
- SendHeader (HeaderName.To, message.To.ToString ());
+ 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));
+ string v = "normal";
+
+ switch (message.Priority){
+ case MailPriority.Normal:
+ v = "normal";
+ break;
+
+ case MailPriority.Low:
+ v = "non-urgent";
+ break;
+
+ case MailPriority.High:
+ v = "urgent";
+ break;
+ }
+ SendHeader ("Priority", v);
+ if (message.Sender != null)
+ SendHeader ("Sender", EncodeAddress (message.Sender));
+ if (message.ReplyToList.Count > 0)
+ SendHeader ("Reply-To", EncodeAddresses (message.ReplyToList));
+
+#if NET_4_0
+ foreach (string s in message.Headers.AllKeys)
+ SendHeader (s, ContentType.EncodeSubjectRFC2047 (message.Headers [s], message.HeadersEncoding));
+#else
foreach (string s in message.Headers.AllKeys)
SendHeader (s, message.Headers [s]);
-
+#endif
+
AddPriorityHeader (message);
boundaryIndex = 0;
else
SendWithoutAttachments (message, null, false);
- SendData (".");
+ SendDot ();
status = Read ();
if (IsError (status))
Send (new MailMessage (from, to, subject, body));
}
+ private void SendDot()
+ {
+ writer.Write(".\r\n");
+ writer.Flush();
+ }
+
private void SendData (string data)
{
- CheckCancellation ();
+ if (String.IsNullOrEmpty (data)) {
+ writer.Write("\r\n");
+ writer.Flush();
+ return;
+ }
- // Certain SMTP servers will reject mail sent with unix line-endings; see http://cr.yp.to/docs/smtplf.html
- StringBuilder sb = new StringBuilder (data);
- sb.Replace ("\r\n", "\n");
- sb.Replace ('\r', '\n');
- sb.Replace ("\n", "\r\n");
- writer.Write (sb.ToString ());
- writer.Write ("\r\n");
+ StringReader sr = new StringReader (data);
+ string line;
+ bool escapeDots = deliveryMethod == SmtpDeliveryMethod.Network;
+ while ((line = sr.ReadLine ()) != null) {
+ CheckCancellation ();
+
+ if (escapeDots) {
+ int i;
+ for (i = 0; i < line.Length; i++) {
+ if (line[i] != '.')
+ break;
+ }
+ if (i > 0 && i == line.Length) {
+ line += ".";
+ }
+ }
+ writer.Write (line);
+ writer.Write ("\r\n");
+ }
writer.Flush ();
}
Send (message);
} catch (Exception ex) {
ea.Result = ex;
+ throw ex;
}
};
worker.WorkerSupportsCancellation = true;
alt_boundary = GenerateBoundary ();
contentType = new ContentType ("multipart/related");
contentType.Boundary = alt_boundary;
- contentType.Parameters ["type"] = "application/octet-stream";
+
+ contentType.Parameters ["type"] = av.ContentType.ToString ();
StartSection (inner_boundary, contentType);
+ StartSection (alt_boundary, av.ContentType, av.TransferEncoding);
} else {
contentType = new ContentType (av.ContentType.ToString ());
StartSection (inner_boundary, contentType, av.TransferEncoding);
case TransferEncoding.SevenBit:
case TransferEncoding.Unknown:
content = new byte [av.ContentStream.Length];
+ av.ContentStream.Read (content, 0, content.Length);
SendData (Encoding.ASCII.GetString (content));
break;
}
private void SendLinkedResources (MailMessage message, LinkedResourceCollection resources, string boundary)
{
foreach (LinkedResource lr in resources) {
- ContentType contentType = new ContentType (lr.ContentType.ToString ());
- StartSection (boundary, contentType, lr.TransferEncoding);
+ StartSection (boundary, lr.ContentType, lr.TransferEncoding, lr);
switch (lr.TransferEncoding) {
case TransferEncoding.Base64:
case TransferEncoding.SevenBit:
case TransferEncoding.Unknown:
content = new byte [lr.ContentStream.Length];
+ lr.ContentStream.Read (content, 0, content.Length);
SendData (Encoding.ASCII.GetString (content));
break;
}
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 + ">");
+
+ SendData (string.Empty);
+ }
+
private void StartSection (string section, ContentType sectionContentType, TransferEncoding transferEncoding, ContentDisposition contentDisposition) {
SendData (String.Format ("--{0}", section));
SendHeader ("content-type", sectionContentType.ToString ());
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors) {
+ // honor any exciting callback defined on ServicePointManager
+ if (ServicePointManager.ServerCertificateValidationCallback != null)
+ return ServicePointManager.ServerCertificateValidationCallback (sender, certificate, chain, sslPolicyErrors);
+ // otherwise provide our own
if (sslPolicyErrors != SslPolicyErrors.None)
throw new InvalidOperationException ("SSL authentication error: " + sslPolicyErrors);
return true;