diff --git a/src/Orchard.Tests/Orchard.Framework.Tests.csproj b/src/Orchard.Tests/Orchard.Framework.Tests.csproj
index 0758a2d8b..1b272e1ff 100644
--- a/src/Orchard.Tests/Orchard.Framework.Tests.csproj
+++ b/src/Orchard.Tests/Orchard.Framework.Tests.csproj
@@ -133,7 +133,6 @@
..\..\lib\sqlce\System.Data.SqlServerCe.dllTrue
- ..\packages\Microsoft.AspNet.WebApi.Client.5.2.7\lib\net45\System.Net.Http.Formatting.dll
diff --git a/src/Orchard.Web/Modules/Orchard.Email/Controllers/EmailAdminController.cs b/src/Orchard.Web/Modules/Orchard.Email/Controllers/EmailAdminController.cs
index e5d64f49e..f676bfd5f 100644
--- a/src/Orchard.Web/Modules/Orchard.Email/Controllers/EmailAdminController.cs
+++ b/src/Orchard.Web/Modules/Orchard.Email/Controllers/EmailAdminController.cs
@@ -40,9 +40,9 @@ namespace Orchard.Email.Controllers {
smtpSettings.Address = testSettings.From;
smtpSettings.Host = testSettings.Host;
smtpSettings.Port = testSettings.Port;
- smtpSettings.EnableSsl = testSettings.EnableSsl;
+ smtpSettings.AutoSelectEncryption = testSettings.AutoSelectEncryption;
+ smtpSettings.EncryptionMethod = testSettings.EncryptionMethod;
smtpSettings.RequireCredentials = testSettings.RequireCredentials;
- smtpSettings.UseDefaultCredentials = testSettings.UseDefaultCredentials;
smtpSettings.UserName = testSettings.UserName;
smtpSettings.Password = testSettings.Password;
@@ -64,10 +64,10 @@ namespace Orchard.Email.Controllers {
return Json(new { error = fakeLogger.Message });
}
- return Json(new {status = T("Message sent.").Text});
+ return Json(new { status = T("Message sent.").Text });
}
catch (Exception e) {
- return Json(new {error = e.Message});
+ return Json(new { error = e.Message });
}
finally {
var smtpChannelComponent = _smtpChannel as Component;
@@ -97,9 +97,9 @@ namespace Orchard.Email.Controllers {
public string ReplyTo { get; set; }
public string Host { get; set; }
public int Port { get; set; }
- public bool EnableSsl { get; set; }
+ public SmtpEncryptionMethod EncryptionMethod { get; set; }
+ public bool AutoSelectEncryption { get; set; }
public bool RequireCredentials { get; set; }
- public bool UseDefaultCredentials { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public string To { get; set; }
diff --git a/src/Orchard.Web/Modules/Orchard.Email/Handlers/SmtpSettingsPartHandler.cs b/src/Orchard.Web/Modules/Orchard.Email/Handlers/SmtpSettingsPartHandler.cs
index e1160ad46..be2d20010 100644
--- a/src/Orchard.Web/Modules/Orchard.Email/Handlers/SmtpSettingsPartHandler.cs
+++ b/src/Orchard.Web/Modules/Orchard.Email/Handlers/SmtpSettingsPartHandler.cs
@@ -1,12 +1,12 @@
using System;
+using System.Configuration;
using System.Text;
using Orchard.ContentManagement;
-using Orchard.Email.Models;
using Orchard.ContentManagement.Handlers;
+using Orchard.Email.Models;
using Orchard.Localization;
using Orchard.Logging;
using Orchard.Security;
-using System.Configuration;
namespace Orchard.Email.Handlers {
public class SmtpSettingsPartHandler : ContentHandler {
@@ -24,7 +24,8 @@ namespace Orchard.Email.Handlers {
OnInitializing((context, part) => {
part.Port = 25;
part.RequireCredentials = false;
- part.EnableSsl = false;
+ part.AutoSelectEncryption = true;
+ part.EncryptionMethod = SmtpEncryptionMethod.None;
});
}
diff --git a/src/Orchard.Web/Modules/Orchard.Email/Migrations.cs b/src/Orchard.Web/Modules/Orchard.Email/Migrations.cs
index 0409097b9..259974139 100644
--- a/src/Orchard.Web/Modules/Orchard.Email/Migrations.cs
+++ b/src/Orchard.Web/Modules/Orchard.Email/Migrations.cs
@@ -2,10 +2,6 @@
namespace Orchard.Email {
public class Migrations : DataMigrationImpl {
-
- public int Create() {
-
- return 1;
- }
+ public int Create() => 1;
}
}
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.Email/Models/SmtpEncryptionMethod.cs b/src/Orchard.Web/Modules/Orchard.Email/Models/SmtpEncryptionMethod.cs
new file mode 100644
index 000000000..64ed03aa4
--- /dev/null
+++ b/src/Orchard.Web/Modules/Orchard.Email/Models/SmtpEncryptionMethod.cs
@@ -0,0 +1,10 @@
+namespace Orchard.Email.Models {
+ ///
+ /// Represents an enumeration for mail encryption methods.
+ ///
+ public enum SmtpEncryptionMethod {
+ None = 0,
+ SslTls = 1,
+ StartTls = 2
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.Email/Models/SmtpSettingsPart.cs b/src/Orchard.Web/Modules/Orchard.Email/Models/SmtpSettingsPart.cs
index 3eb75d952..b710cf881 100644
--- a/src/Orchard.Web/Modules/Orchard.Email/Models/SmtpSettingsPart.cs
+++ b/src/Orchard.Web/Modules/Orchard.Email/Models/SmtpSettingsPart.cs
@@ -1,7 +1,7 @@
-using System.Configuration;
+using System;
+using System.Configuration;
using System.Net.Configuration;
using Orchard.ContentManagement;
-using System;
using Orchard.ContentManagement.Utilities;
namespace Orchard.Email.Models {
@@ -31,9 +31,23 @@ namespace Orchard.Email.Models {
set { this.Store(x => x.Port, value); }
}
- public bool EnableSsl {
- get { return this.Retrieve(x => x.EnableSsl); }
- set { this.Store(x => x.EnableSsl, value); }
+ [Obsolete($"Use {nameof(AutoSelectEncryption)} and/or {nameof(EncryptionMethod)} instead.")]
+ public bool EnableSsl => this.Retrieve(x => x.EnableSsl);
+
+ public SmtpEncryptionMethod EncryptionMethod {
+#pragma warning disable CS0618 // Type or member is obsolete
+ // Reading EnableSsl is necessary to keep the correct settings during the upgrade to MailKit.
+ get { return this.Retrieve(x => x.EncryptionMethod, EnableSsl ? SmtpEncryptionMethod.SslTls : SmtpEncryptionMethod.None); }
+#pragma warning restore CS0618 // Type or member is obsolete
+ set { this.Store(x => x.EncryptionMethod, value); }
+ }
+
+ public bool AutoSelectEncryption {
+#pragma warning disable CS0618 // Type or member is obsolete
+ // Reading EnableSsl is necessary to keep the correct settings during the upgrade to MailKit.
+ get { return this.Retrieve(x => x.AutoSelectEncryption, !EnableSsl); }
+#pragma warning restore CS0618 // Type or member is obsolete
+ set { this.Store(x => x.AutoSelectEncryption, value); }
}
public bool RequireCredentials {
@@ -41,11 +55,6 @@ namespace Orchard.Email.Models {
set { this.Store(x => x.RequireCredentials, value); }
}
- public bool UseDefaultCredentials {
- get { return this.Retrieve(x => x.UseDefaultCredentials); }
- set { this.Store(x => x.UseDefaultCredentials, value); }
- }
-
public string UserName {
get { return this.Retrieve(x => x.UserName); }
set { this.Store(x => x.UserName, value); }
diff --git a/src/Orchard.Web/Modules/Orchard.Email/Orchard.Email.csproj b/src/Orchard.Web/Modules/Orchard.Email/Orchard.Email.csproj
index f61f1f1f1..1327b1456 100644
--- a/src/Orchard.Web/Modules/Orchard.Email/Orchard.Email.csproj
+++ b/src/Orchard.Web/Modules/Orchard.Email/Orchard.Email.csproj
@@ -55,6 +55,12 @@
false
+
+ ..\..\..\packages\Portable.BouncyCastle.1.9.0\lib\net40\BouncyCastle.Crypto.dll
+
+
+ ..\..\..\packages\MailKit.3.1.1\lib\net48\MailKit.dll
+ ..\..\..\packages\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.2.0.1\lib\net45\Microsoft.CodeDom.Providers.DotNetCompilerPlatform.dll
@@ -62,14 +68,23 @@
..\..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll
+
+ ..\..\..\packages\MimeKit.3.1.1\lib\net48\MimeKit.dll
+ ..\..\..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll
+
+ ..\..\..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll
+ False
+
+
+
@@ -104,6 +119,7 @@
+
@@ -218,4 +234,4 @@
-
+
\ No newline at end of file
diff --git a/src/Orchard.Web/Modules/Orchard.Email/Services/MissingSettingsBanner.cs b/src/Orchard.Web/Modules/Orchard.Email/Services/MissingSettingsBanner.cs
index 7e2270261..e80f239e2 100644
--- a/src/Orchard.Web/Modules/Orchard.Email/Services/MissingSettingsBanner.cs
+++ b/src/Orchard.Web/Modules/Orchard.Email/Services/MissingSettingsBanner.cs
@@ -1,5 +1,4 @@
using System.Collections.Generic;
-using System.Net.Mail;
using System.Web.Mvc;
using Orchard.ContentManagement;
using Orchard.Email.Models;
@@ -24,8 +23,8 @@ namespace Orchard.Email.Services {
if (smtpSettings == null || !smtpSettings.IsValid()) {
var urlHelper = new UrlHelper(workContext.HttpContext.Request.RequestContext);
- var url = urlHelper.Action("Email", "Admin", new {Area = "Settings"});
- yield return new NotifyEntry {Message = T("The SMTP settings need to be configured.", url), Type = NotifyType.Warning};
+ var url = urlHelper.Action("Email", "Admin", new { Area = "Settings" });
+ yield return new NotifyEntry { Message = T("The SMTP settings need to be configured.", url), Type = NotifyType.Warning };
}
}
}
diff --git a/src/Orchard.Web/Modules/Orchard.Email/Services/SmtpMessageChannel.cs b/src/Orchard.Web/Modules/Orchard.Email/Services/SmtpMessageChannel.cs
index 026cc8ad9..3c6437372 100644
--- a/src/Orchard.Web/Modules/Orchard.Email/Services/SmtpMessageChannel.cs
+++ b/src/Orchard.Web/Modules/Orchard.Email/Services/SmtpMessageChannel.cs
@@ -1,14 +1,17 @@
using System;
using System.Collections.Generic;
using System.Configuration;
-using System.Net;
+using System.Linq;
using System.Net.Configuration;
using System.Net.Mail;
using System.Web.Mvc;
+using MailKit.Security;
+using MimeKit;
using Orchard.ContentManagement;
using Orchard.DisplayManagement;
-using Orchard.Logging;
using Orchard.Email.Models;
+using Orchard.Logging;
+using SmtpClient = MailKit.Net.Smtp.SmtpClient;
namespace Orchard.Email.Services {
public class SmtpMessageChannel : Component, ISmtpChannel, IDisposable {
@@ -64,67 +67,55 @@ namespace Orchard.Email.Services {
Content = new MvcHtmlString(emailMessage.Body)
}));
- var mailMessage = new MailMessage {
+ var mailMessage = new MimeMessage {
Subject = emailMessage.Subject,
- Body = _shapeDisplay.Display(template),
- IsBodyHtml = true
+ };
+ var mailBodyBuilder = new BodyBuilder {
+ HtmlBody = _shapeDisplay.Display(template),
};
- if (parameters.ContainsKey("Message")) {
+ if (parameters.TryGetValue("Message", out var possiblyMailMessage) && possiblyMailMessage is MailMessage legacyMessage) {
// A full message object is provided by the sender.
+ if (!String.IsNullOrWhiteSpace(legacyMessage.Subject)) {
+ mailMessage.Subject = legacyMessage.Subject;
+ }
- var oldMessage = mailMessage;
- mailMessage = (MailMessage)parameters["Message"];
-
- if (String.IsNullOrWhiteSpace(mailMessage.Subject))
- mailMessage.Subject = oldMessage.Subject;
-
- if (String.IsNullOrWhiteSpace(mailMessage.Body)) {
- mailMessage.Body = oldMessage.Body;
- mailMessage.IsBodyHtml = oldMessage.IsBodyHtml;
+ if (!String.IsNullOrWhiteSpace(legacyMessage.Body)) {
+ mailBodyBuilder.TextBody = legacyMessage.IsBodyHtml ? null : legacyMessage.Body;
+ mailBodyBuilder.HtmlBody = legacyMessage.IsBodyHtml ? legacyMessage.Body : null;
}
}
- try {
+ mailMessage.Body = mailBodyBuilder.ToMessageBody();
- foreach (var recipient in ParseRecipients(emailMessage.Recipients)) {
- mailMessage.To.Add(new MailAddress(recipient));
- }
+ try {
+ mailMessage.To.AddRange(ParseRecipients(emailMessage.Recipients));
if (!String.IsNullOrWhiteSpace(emailMessage.Cc)) {
- foreach (var recipient in ParseRecipients(emailMessage.Cc)) {
- mailMessage.CC.Add(new MailAddress(recipient));
- }
+ mailMessage.Cc.AddRange(ParseRecipients(emailMessage.Cc));
}
if (!String.IsNullOrWhiteSpace(emailMessage.Bcc)) {
- foreach (var recipient in ParseRecipients(emailMessage.Bcc)) {
- mailMessage.Bcc.Add(new MailAddress(recipient));
- }
+ mailMessage.Bcc.AddRange(ParseRecipients(emailMessage.Bcc));
}
+ var fromAddress = default(MailboxAddress);
if (!String.IsNullOrWhiteSpace(emailMessage.From)) {
- mailMessage.From = new MailAddress(emailMessage.From);
+ fromAddress = MailboxAddress.Parse(emailMessage.From);
}
else {
// Take 'From' address from site settings or web.config.
- mailMessage.From = !String.IsNullOrWhiteSpace(_smtpSettings.Address)
- ? new MailAddress(_smtpSettings.Address)
- : new MailAddress(((SmtpSection)ConfigurationManager.GetSection("system.net/mailSettings/smtp")).From);
+ fromAddress = !String.IsNullOrWhiteSpace(_smtpSettings.Address)
+ ? MailboxAddress.Parse(_smtpSettings.Address)
+ : MailboxAddress.Parse(((SmtpSection)ConfigurationManager.GetSection("system.net/mailSettings/smtp")).From);
}
+ mailMessage.From.Add(fromAddress);
+
if (!String.IsNullOrWhiteSpace(emailMessage.ReplyTo)) {
- foreach (var recipient in ParseRecipients(emailMessage.ReplyTo)) {
- mailMessage.ReplyToList.Add(new MailAddress(recipient));
- }
- }
- if (parameters.ContainsKey("NotifyReadEmail")) {
- if (parameters["NotifyReadEmail"] is bool) {
- if ((bool)(parameters["NotifyReadEmail"])) {
- mailMessage.Headers.Add("Disposition-Notification-To", mailMessage.From.ToString());
- }
- }
+ mailMessage.ReplyTo.AddRange(ParseRecipients(emailMessage.ReplyTo));
}
+
_smtpClientField.Value.Send(mailMessage);
}
catch (Exception e) {
@@ -133,26 +124,51 @@ namespace Orchard.Email.Services {
}
private SmtpClient CreateSmtpClient() {
+ var smtpConfiguration = new {
+ _smtpSettings.Host,
+ _smtpSettings.Port,
+ _smtpSettings.EncryptionMethod,
+ _smtpSettings.AutoSelectEncryption,
+ _smtpSettings.RequireCredentials,
+ _smtpSettings.UserName,
+ _smtpSettings.Password,
+ };
// If no properties are set in the dashboard, use the web.config value.
if (String.IsNullOrWhiteSpace(_smtpSettings.Host)) {
- return new SmtpClient();
+ var smtpSection = (SmtpSection)ConfigurationManager.GetSection("system.net/mailSettings/smtp");
+ if (smtpSection.DeliveryMethod != SmtpDeliveryMethod.Network) {
+ throw new NotSupportedException($"Only the {SmtpDeliveryMethod.Network} delivery method is supported, but "
+ + $"{smtpSection.DeliveryMethod} delivery method is configured. Please check your Web.config.");
+ }
+
+ smtpConfiguration = new {
+ smtpSection.Network.Host,
+ smtpSection.Network.Port,
+ EncryptionMethod = smtpSection.Network.EnableSsl ? SmtpEncryptionMethod.SslTls : SmtpEncryptionMethod.None,
+ AutoSelectEncryption = !smtpSection.Network.EnableSsl,
+ RequireCredentials = smtpSection.Network.DefaultCredentials || !String.IsNullOrWhiteSpace(smtpSection.Network.UserName),
+ smtpSection.Network.UserName,
+ smtpSection.Network.Password,
+ };
}
- var smtpClient = new SmtpClient {
- UseDefaultCredentials = _smtpSettings.RequireCredentials && _smtpSettings.UseDefaultCredentials
- };
-
- if (!smtpClient.UseDefaultCredentials && !String.IsNullOrWhiteSpace(_smtpSettings.UserName)) {
- smtpClient.Credentials = new NetworkCredential(_smtpSettings.UserName, _smtpSettings.Password);
+ var secureSocketOptions = SecureSocketOptions.Auto;
+ if (!smtpConfiguration.AutoSelectEncryption) {
+ secureSocketOptions = smtpConfiguration.EncryptionMethod switch {
+ SmtpEncryptionMethod.None => SecureSocketOptions.None,
+ SmtpEncryptionMethod.SslTls => SecureSocketOptions.SslOnConnect,
+ SmtpEncryptionMethod.StartTls => SecureSocketOptions.StartTls,
+ _ => SecureSocketOptions.None,
+ };
}
- if (_smtpSettings.Host != null) {
- smtpClient.Host = _smtpSettings.Host;
+ var smtpClient = new SmtpClient();
+ smtpClient.Connect(smtpConfiguration.Host, smtpConfiguration.Port, secureSocketOptions);
+
+ if (smtpConfiguration.RequireCredentials) {
+ smtpClient.Authenticate(smtpConfiguration.UserName, smtpConfiguration.Password);
}
- smtpClient.Port = _smtpSettings.Port;
- smtpClient.EnableSsl = _smtpSettings.EnableSsl;
- smtpClient.DeliveryMethod = SmtpDeliveryMethod.Network;
return smtpClient;
}
@@ -160,8 +176,16 @@ namespace Orchard.Email.Services {
return dictionary.ContainsKey(key) ? dictionary[key] as string : null;
}
- private IEnumerable ParseRecipients(string recipients) {
- return recipients.Split(new[] {',', ';'}, StringSplitOptions.RemoveEmptyEntries);
+ private IEnumerable ParseRecipients(string recipients) {
+ return recipients.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries)
+ .SelectMany(address => {
+ if (MailboxAddress.TryParse(address, out var mailboxAddress)) {
+ return new[] { mailboxAddress };
+ }
+
+ Logger.Error("Invalid email address: {0}", address);
+ return Enumerable.Empty();
+ });
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Orchard.Web/Modules/Orchard.Email/Views/EditorTemplates/Parts/SmtpSettings.cshtml b/src/Orchard.Web/Modules/Orchard.Email/Views/EditorTemplates/Parts/SmtpSettings.cshtml
index bc4c237d4..1165d0580 100644
--- a/src/Orchard.Web/Modules/Orchard.Email/Views/EditorTemplates/Parts/SmtpSettings.cshtml
+++ b/src/Orchard.Web/Modules/Orchard.Email/Views/EditorTemplates/Parts/SmtpSettings.cshtml
@@ -1,4 +1,5 @@
-@using System.Net.Mail
+@using Orchard.Email.Models
+@using System.Net.Mail
@model Orchard.Email.Models.SmtpSettingsPart
@{
var smtpClient = new SmtpClient();
@@ -20,15 +21,37 @@
- @Html.TextBoxFor(m => m.Port, new { placeholder = smtpClient.Port, @class = "text small" })
+ @Html.TextBoxFor(m => m.Port, new { type = "number", placeholder = smtpClient.Port, min = 1, max = 65535 })
@Html.ValidationMessage("Port", "*")
@T("The SMTP server port, usually 25.")
- @Html.EditorFor(m => m.EnableSsl)
-
- @Html.ValidationMessage("EnableSsl", "*")
- @T("Check if the SMTP server requires SSL communications.")
+
+
+ @Html.ValidationMessage("EncryptionMethod", "*")
+ @T("The encryption method used when connecting to mail server.")
+
+
+ @Html.EditorFor(m => m.AutoSelectEncryption)
+
+ @Html.ValidationMessage("AutoSelectEncryption", "*")
+ @T("Check to let the system select the encryption method based on port.")
- @Html.RadioButtonFor(m => m.UseDefaultCredentials, false, new { id = "customCredentialsOption", name = "UseDefaultCredentials" })
-
- @Html.ValidationMessage("UseDefaultCredentials", "*")
+
+ @Html.TextBoxFor(m => m.UserName, new { @class = "text medium" })
+ @Html.ValidationMessage("UserName", "*")
+ @T("The username for authentication.")
-
- @Html.RadioButtonFor(m => m.UseDefaultCredentials, true, new { id = "defaultCredentialsOptions", name = "UseDefaultCredentials" })
-
- @Html.ValidationMessage("UseDefaultCredentials", "*")
- @T("When this option is selected, the aplication pool or host-process identity is used to authenticate with the mail server. ")
-
-
-
-
- @Html.TextBoxFor(m => m.UserName, new { @class = "text" })
- @Html.ValidationMessage("UserName", "*")
- @T("The username for authentication.")
-
-
-
- @Html.TextBoxFor(m => m.Password, new { type = "password", @class = "text medium" })
- @Html.ValidationMessage("Password", "*")
- @T("The password for authentication.")
-
+
+ @Html.TextBoxFor(m => m.Password, new { type = "password", @class = "text medium" })
+ @Html.ValidationMessage("Password", "*")
+ @T("The password for authentication.")