diff --git a/DNN Platform/Library/Services/Mail/CoreMailProvider.cs b/DNN Platform/Library/Services/Mail/CoreMailProvider.cs index f795021905d..b53e6a05de1 100644 --- a/DNN Platform/Library/Services/Mail/CoreMailProvider.cs +++ b/DNN Platform/Library/Services/Mail/CoreMailProvider.cs @@ -9,6 +9,8 @@ namespace DotNetNuke.Services.Mail using System.Net; using System.Net.Mail; using System.Text.RegularExpressions; + using System.Threading; + using System.Threading.Tasks; using DotNetNuke.Common.Utilities; using DotNetNuke.Entities.Host; @@ -24,24 +26,91 @@ public class CoreMailProvider : MailProvider /// public override string SendMail(MailInfo mailInfo, SmtpInfo smtpInfo = null) { - // validate smtp server - if (smtpInfo == null || string.IsNullOrEmpty(smtpInfo.Server)) + var (host, port, errorMessage) = ParseSmtpServer(ref smtpInfo); + if (errorMessage != null) { - if (string.IsNullOrWhiteSpace(Host.SMTPServer)) + return errorMessage; + } + + using (var mailMessage = CreateMailMessage(mailInfo, smtpInfo)) + { + try { - return "SMTP Server not configured"; + using (var smtpClient = CreateSmtpClient(smtpInfo, host, port)) + { + smtpClient.Send(mailMessage); + } + + return string.Empty; } + catch (Exception exc) + { + return HandleException(exc); + } + } + } - smtpInfo = new SmtpInfo + /// + public override async Task SendMailAsync(MailInfo mailInfo, SmtpInfo smtpInfo = null, CancellationToken cancellationToken = default(CancellationToken)) + { + var (host, port, errorMessage) = ParseSmtpServer(ref smtpInfo); + if (errorMessage != null) + { + return errorMessage; + } + + using (var mailMessage = CreateMailMessage(mailInfo, smtpInfo)) + { + try + { + using (var smtpClient = CreateSmtpClient(smtpInfo, host, port)) + { + await smtpClient.SendMailAsync(mailMessage); + } + + return string.Empty; + } + catch (Exception exc) { - Server = Host.SMTPServer, - Authentication = Host.SMTPAuthentication, - Username = Host.SMTPUsername, - Password = Host.SMTPPassword, - EnableSSL = Host.EnableSMTPSSL, - }; + return HandleException(exc); + } + } + } + + private static string ValidateSmtpInfo(SmtpInfo smtpInfo) + { + if (smtpInfo != null && !string.IsNullOrEmpty(smtpInfo.Server)) + { + return null; + } + + if (!string.IsNullOrWhiteSpace(Host.SMTPServer)) + { + return null; + } + + return "SMTP Server not configured"; + } + + private static SmtpInfo GetDefaultSmtpInfo(SmtpInfo smtpInfo) + { + if (smtpInfo != null && !string.IsNullOrEmpty(smtpInfo.Server)) + { + return smtpInfo; } + return new SmtpInfo + { + Server = Host.SMTPServer, + Authentication = Host.SMTPAuthentication, + Username = Host.SMTPUsername, + Password = Host.SMTPPassword, + EnableSSL = Host.EnableSMTPSSL, + }; + } + + private static MailMessage CreateMailMessage(MailInfo mailInfo, SmtpInfo smtpInfo) + { // translate semi-colon delimiters to commas as ASP.NET 2.0 does not support semi-colons if (!string.IsNullOrEmpty(mailInfo.To)) { @@ -81,8 +150,9 @@ public override string SendMail(MailInfo mailInfo, SmtpInfo smtpInfo = null) var senderAddress = mailInfo.Sender; var senderDisplayName = mailInfo.FromName; var needUpdateSender = false; - if (smtpInfo.Username.Contains("@") && senderAddress == Host.HostEmail && - !senderAddress.Equals(smtpInfo.Username, StringComparison.InvariantCultureIgnoreCase)) + if (smtpInfo.Username.Contains("@") + && senderAddress == Host.HostEmail + && !senderAddress.Equals(smtpInfo.Username, StringComparison.InvariantCultureIgnoreCase)) { senderAddress = smtpInfo.Username; needUpdateSender = true; @@ -101,7 +171,9 @@ public override string SendMail(MailInfo mailInfo, SmtpInfo smtpInfo = null) } else if (smtpInfo.Username.Contains("@")) { - mailMessage.Sender = new MailAddress(smtpInfo.Username, Host.SMTPPortalEnabled ? PortalSettings.Current.PortalName : Host.HostTitle); + mailMessage.Sender = new MailAddress( + smtpInfo.Username, + Host.SMTPPortalEnabled ? PortalSettings.Current.PortalName : Host.HostTitle); } } @@ -121,81 +193,107 @@ public override string SendMail(MailInfo mailInfo, SmtpInfo smtpInfo = null) // message mailMessage.Subject = HtmlUtils.StripWhiteSpace(mailInfo.Subject, true); mailMessage.Body = mailInfo.Body; + return mailMessage; + } + + private static (string host, int? port, string errorMessage) ParseSmtpServer(ref SmtpInfo smtpInfo) + { + var errorMessage = ValidateSmtpInfo(smtpInfo); + if (errorMessage != null) + { + return (null, null, errorMessage); + } + + smtpInfo = GetDefaultSmtpInfo(smtpInfo); + smtpInfo.Server = smtpInfo.Server.Trim(); if (!SmtpServerRegex.IsMatch(smtpInfo.Server)) { - return Localize.GetString("SMTPConfigurationProblem"); + return (null, null, Localize.GetString("SMTPConfigurationProblem")); + } + + var smtpHostParts = smtpInfo.Server.Split(':'); + var host = smtpHostParts[0]; + if (smtpHostParts.Length <= 1) + { + return (host, null, null); + } + + // port is guaranteed to be of max 5 digits numeric by the RegEx check + var port = int.Parse(smtpHostParts[1]); + if (port < 1 || port > 65535) + { + return (null, null, Localize.GetString("SmtpInvalidPort")); } + return (host, port, null); + } + + private static SmtpClient CreateSmtpClient(SmtpInfo smtpInfo, string host, int? port) + { + SmtpClient client = null; try { - // to workaround problem in 4.0 need to specify host name - using (var smtpClient = new SmtpClient()) + client = new SmtpClient(); + client.Host = host; + if (port != null) { - var smtpHostParts = smtpInfo.Server.Split(':'); - smtpClient.Host = smtpHostParts[0]; - if (smtpHostParts.Length > 1) - { - // port is guaranteed to be of max 5 digits numeric by the RegEx check - var port = int.Parse(smtpHostParts[1]); - if (port < 1 || port > 65535) - { - return Localize.GetString("SmtpInvalidPort"); - } - - smtpClient.Port = port; - } + client.Port = port.Value; + } - switch (smtpInfo.Authentication) - { - case "": - case "0": // anonymous - break; - case "1": // basic - if (!string.IsNullOrEmpty(smtpInfo.Username) - && !string.IsNullOrEmpty(smtpInfo.Password)) - { - smtpClient.UseDefaultCredentials = false; - smtpClient.Credentials = new NetworkCredential( - smtpInfo.Username, - smtpInfo.Password); - } - - break; - case "2": // NTLM - smtpClient.UseDefaultCredentials = true; - break; - } + SetSmtpClientAuthentication(smtpInfo, client); - smtpClient.EnableSsl = smtpInfo.EnableSSL; - smtpClient.Send(mailMessage); - smtpClient.Dispose(); - } + client.EnableSsl = smtpInfo.EnableSSL; - return string.Empty; + var returnedClient = client; + client = null; + + return returnedClient; } - catch (Exception exc) + finally { - var retValue = Localize.GetString("SMTPConfigurationProblem") + " "; + client?.Dispose(); + } + } - // mail configuration problem - if (exc.InnerException != null) - { - retValue += string.Concat(exc.Message, Environment.NewLine, exc.InnerException.Message); - Exceptions.Exceptions.LogException(exc.InnerException); - } - else - { - retValue += exc.Message; - Exceptions.Exceptions.LogException(exc); - } + private static void SetSmtpClientAuthentication(SmtpInfo smtpInfo, SmtpClient smtpClient) + { + switch (smtpInfo.Authentication) + { + case "": + case "0": // anonymous + break; + case "1": // basic + if (!string.IsNullOrEmpty(smtpInfo.Username) && !string.IsNullOrEmpty(smtpInfo.Password)) + { + smtpClient.UseDefaultCredentials = false; + smtpClient.Credentials = new NetworkCredential(smtpInfo.Username, smtpInfo.Password); + } - return retValue; + break; + case "2": // NTLM + smtpClient.UseDefaultCredentials = true; + break; } - finally + } + + private static string HandleException(Exception exc) + { + var retValue = Localize.GetString("SMTPConfigurationProblem") + " "; + + // mail configuration problem + if (exc.InnerException != null) { - mailMessage.Dispose(); + retValue += string.Concat(exc.Message, Environment.NewLine, exc.InnerException.Message); + Exceptions.Exceptions.LogException(exc.InnerException); } + else + { + retValue += exc.Message; + Exceptions.Exceptions.LogException(exc); + } + + return retValue; } } } diff --git a/DNN Platform/Library/Services/Mail/MailKitMailProvider.cs b/DNN Platform/Library/Services/Mail/MailKitMailProvider.cs index 644d4c0cc4d..da581c6b5be 100644 --- a/DNN Platform/Library/Services/Mail/MailKitMailProvider.cs +++ b/DNN Platform/Library/Services/Mail/MailKitMailProvider.cs @@ -6,6 +6,8 @@ namespace DotNetNuke.Services.Mail using System; using System.Linq; using System.Text.RegularExpressions; + using System.Threading; + using System.Threading.Tasks; using DotNetNuke.Common.Utilities; using DotNetNuke.Entities.Host; @@ -26,24 +28,140 @@ public class MailKitMailProvider : MailProvider /// public override string SendMail(MailInfo mailInfo, SmtpInfo smtpInfo = null) { - // validate smtp server + try + { + var (host, port, errorMessage) = ParseSmtpServer(ref smtpInfo); + if (errorMessage != null) + { + return errorMessage; + } + + var mailMessage = CreateMailMessage(mailInfo, smtpInfo); + + using (var smtpClient = new SmtpClient()) + { + smtpClient.Connect(host, port, SecureSocketOptions.Auto); + + if (smtpInfo.Authentication == "1" && !string.IsNullOrEmpty(smtpInfo.Username) && !string.IsNullOrEmpty(smtpInfo.Password)) + { + smtpClient.Authenticate(smtpInfo.Username, smtpInfo.Password); + } + + smtpClient.Send(mailMessage); + smtpClient.Disconnect(true); + } + + return string.Empty; + } + catch (Exception exc) + { + return HandleException(exc); + } + } + + /// + public override async Task SendMailAsync(MailInfo mailInfo, SmtpInfo smtpInfo = null, CancellationToken cancellationToken = default(CancellationToken)) + { + var (host, port, errorMessage) = ParseSmtpServer(ref smtpInfo); + if (errorMessage != null) + { + return errorMessage; + } + + var mailMessage = CreateMailMessage(mailInfo, smtpInfo); + + try + { + using (var smtpClient = new SmtpClient()) + { + await smtpClient.ConnectAsync(host, port, SecureSocketOptions.Auto, cancellationToken); + + if (smtpInfo.Authentication == "1" && !string.IsNullOrEmpty(smtpInfo.Username) && !string.IsNullOrEmpty(smtpInfo.Password)) + { + await smtpClient.AuthenticateAsync(smtpInfo.Username, smtpInfo.Password, cancellationToken); + } + + await smtpClient.SendAsync(mailMessage, cancellationToken); + await smtpClient.DisconnectAsync(true, cancellationToken); + } + + return string.Empty; + } + catch (Exception exc) + { + return HandleException(exc); + } + } + + private static (string host, int port, string errorMessage) ParseSmtpServer(ref SmtpInfo smtpInfo) + { + var port = 25; if (smtpInfo == null || string.IsNullOrEmpty(smtpInfo.Server)) { if (string.IsNullOrWhiteSpace(Host.SMTPServer)) { - return "SMTP Server not configured"; + return (null, port, "SMTP Server not configured"); } smtpInfo = new SmtpInfo - { - Server = Host.SMTPServer, - Authentication = Host.SMTPAuthentication, - Username = Host.SMTPUsername, - Password = Host.SMTPPassword, - EnableSSL = Host.EnableSMTPSSL, - }; + { + Server = Host.SMTPServer, + Authentication = Host.SMTPAuthentication, + Username = Host.SMTPUsername, + Password = Host.SMTPPassword, + EnableSSL = Host.EnableSMTPSSL, + }; + } + + if (smtpInfo.Authentication == "2") + { + throw new NotSupportedException("NTLM authentication is not supported by MailKit provider"); + } + + smtpInfo.Server = smtpInfo.Server.Trim(); + if (!SmtpServerRegex.IsMatch(smtpInfo.Server)) + { + return (null, port, Localize.GetString("SMTPConfigurationProblem")); } + var smtpHostParts = smtpInfo.Server.Split(':'); + var host = smtpHostParts[0]; + if (smtpHostParts.Length <= 1) + { + return (host, port, null); + } + + // port is guaranteed to be of max 5 digits numeric by the RegEx check + port = int.Parse(smtpHostParts[1]); + if (port < 1 || port > 65535) + { + return (host, port, Localize.GetString("SmtpInvalidPort")); + } + + return (host, port, null); + } + + private static string HandleException(Exception exc) + { + var retValue = Localize.GetString("SMTPConfigurationProblem") + " "; + + // mail configuration problem + if (exc.InnerException != null) + { + retValue += string.Concat(exc.Message, Environment.NewLine, exc.InnerException.Message); + Exceptions.Exceptions.LogException(exc.InnerException); + } + else + { + retValue += exc.Message; + Exceptions.Exceptions.LogException(exc); + } + + return retValue; + } + + private static MimeMessage CreateMailMessage(MailInfo mailInfo, SmtpInfo smtpInfo) + { var mailMessage = new MimeMessage(); mailMessage.From.Add(ParseAddressWithDisplayName(displayName: mailInfo.FromName, address: mailInfo.From)); @@ -84,8 +202,9 @@ public override string SendMail(MailInfo mailInfo, SmtpInfo smtpInfo = null) var senderAddress = mailInfo.Sender; var senderDisplayName = mailInfo.FromName; var needUpdateSender = false; - if (smtpInfo.Username.Contains("@") && senderAddress == Host.HostEmail && - !senderAddress.Equals(smtpInfo.Username, StringComparison.InvariantCultureIgnoreCase)) + if (smtpInfo.Username.Contains("@") + && senderAddress == Host.HostEmail + && !senderAddress.Equals(smtpInfo.Username, StringComparison.InvariantCultureIgnoreCase)) { senderAddress = smtpInfo.Username; needUpdateSender = true; @@ -99,7 +218,9 @@ public override string SendMail(MailInfo mailInfo, SmtpInfo smtpInfo = null) if (needUpdateSender) { - mailMessage.Sender = ParseAddressWithDisplayName(displayName: senderDisplayName, address: senderAddress); + mailMessage.Sender = ParseAddressWithDisplayName( + displayName: senderDisplayName, + address: senderAddress); } } else if (smtpInfo.Username.Contains("@")) @@ -110,10 +231,7 @@ public override string SendMail(MailInfo mailInfo, SmtpInfo smtpInfo = null) } } - var builder = new BodyBuilder - { - TextBody = Mail.ConvertToText(mailInfo.Body), - }; + var builder = new BodyBuilder { TextBody = Mail.ConvertToText(mailInfo.Body), }; if (mailInfo.BodyFormat == MailFormat.Html) { @@ -132,76 +250,7 @@ public override string SendMail(MailInfo mailInfo, SmtpInfo smtpInfo = null) // message mailMessage.Subject = HtmlUtils.StripWhiteSpace(mailInfo.Subject, true); mailMessage.Body = builder.ToMessageBody(); - - smtpInfo.Server = smtpInfo.Server.Trim(); - - if (!SmtpServerRegex.IsMatch(smtpInfo.Server)) - { - return Localize.GetString("SMTPConfigurationProblem"); - } - - try - { - var smtpHostParts = smtpInfo.Server.Split(':'); - var host = smtpHostParts[0]; - var port = 25; - - if (smtpHostParts.Length > 1) - { - // port is guaranteed to be of max 5 digits numeric by the RegEx check - port = int.Parse(smtpHostParts[1]); - if (port < 1 || port > 65535) - { - return Localize.GetString("SmtpInvalidPort"); - } - } - - // to workaround problem in 4.0 need to specify host name - using (var smtpClient = new SmtpClient()) - { - smtpClient.Connect(host, port, SecureSocketOptions.Auto); - - switch (smtpInfo.Authentication) - { - case "": - case "0": // anonymous - break; - case "1": // basic - if (!string.IsNullOrEmpty(smtpInfo.Username) - && !string.IsNullOrEmpty(smtpInfo.Password)) - { - smtpClient.Authenticate(smtpInfo.Username, smtpInfo.Password); - } - - break; - case "2": // NTLM (Not Supported by MailKit) - throw new NotSupportedException("NTLM authentication is not supported by MailKit provider"); - } - - smtpClient.Send(mailMessage); - smtpClient.Disconnect(true); - } - - return string.Empty; - } - catch (Exception exc) - { - var retValue = Localize.GetString("SMTPConfigurationProblem") + " "; - - // mail configuration problem - if (exc.InnerException != null) - { - retValue += string.Concat(exc.Message, Environment.NewLine, exc.InnerException.Message); - Exceptions.Exceptions.LogException(exc.InnerException); - } - else - { - retValue += exc.Message; - Exceptions.Exceptions.LogException(exc); - } - - return retValue; - } + return mailMessage; } private static MailboxAddress ParseAddressWithDisplayName(string displayName, string address) diff --git a/DNN Platform/Library/Services/Mail/MailProvider.cs b/DNN Platform/Library/Services/Mail/MailProvider.cs index d3220397869..163f4334847 100644 --- a/DNN Platform/Library/Services/Mail/MailProvider.cs +++ b/DNN Platform/Library/Services/Mail/MailProvider.cs @@ -3,22 +3,35 @@ // See the LICENSE file in the project root for more information namespace DotNetNuke.Services.Mail { + using System.Threading; + using System.Threading.Tasks; + using DotNetNuke.ComponentModel; /// A provider with the ability to send emails. public abstract class MailProvider - { - /// Sends an email. - /// Information about the message to send. - /// Information about the SMTP server via which to send the message. - /// if the message send successfully, otherwise an error message. - public abstract string SendMail(MailInfo mailInfo, SmtpInfo smtpInfo = null); - - /// Gets the currently configured instance. + { + /// Gets the currently configured instance. /// A instance. public static MailProvider Instance() { return ComponentFactory.GetComponent(); } + + /// Sends an email. + /// Information about the message to send. + /// Information about the SMTP server via which to send the message. + /// if the message send successfully, otherwise an error message. + public abstract string SendMail(MailInfo mailInfo, SmtpInfo smtpInfo = null); + + /// Sends an email. + /// Information about the message to send. + /// Information about the SMTP server via which to send the message. + /// The cancellation token. + /// if the message send successfully, otherwise an error message. + public virtual async Task SendMailAsync(MailInfo mailInfo, SmtpInfo smtpInfo = null, CancellationToken cancellationToken = default(CancellationToken)) + { + return this.SendMail(mailInfo, smtpInfo); + } } }