// Spludlow Software // Copyright © Samuel P. Ludlow 2020 All Rights Reserved // Distributed under the terms of the GNU General Public License version 3 // Distributed WITHOUT ANY WARRANTY; without implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE // https://www.spludlow.co.uk/LICENCE.TXT // The Spludlow logo is a registered trademark of Samuel P. Ludlow and may not be used without permission // v1.14 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Net.Mail; using System.IO; namespace Spludlow.Net { public class Smtp { private static int AttemptsToTry = 4; private static int WaitAttemptMilliSeconds = 2000; public static bool ValidAddress(string address) { try { MailAddress mailAddress = new MailAddress(address); return (mailAddress.Address == address); } catch { return false; } } public static bool ValidateAddressMX(string address) { int index = address.IndexOf("@"); string domain = address.Substring(index + 1); Dictionary> mxs = Spludlow.Net.DnsMx.GetMXRecords(domain); return (mxs.Keys.Count > 0); } public static void Send(string fromAddress, string toAddress, string subject, string html) { Send(fromAddress, toAddress, subject, html, false); } public static void Send(string fromAddress, string toAddress, string subject, string body, bool textBody) { string smtpAddress = Spludlow.Config.Get("Spludlow.SmtpServer"); using (MailMessage message = new MailMessage(fromAddress, toAddress, subject, body)) { message.IsBodyHtml = ! textBody; Send(smtpAddress, message); } } public static void Send(string smtpAddress, MailMessage message) // Message is asumed to be in good order, to/from address validation and subject clean must be performed before hand { Send(smtpAddress, message, false); } public static void Send(string smtpAddress, MailMessage message, bool dontReTry) { Send(smtpAddress, message, dontReTry, null); } public static void Send(string smtpAddress, MailMessage message, bool dontReTry, System.Net.NetworkCredential networkCredential) { // Save to file if (smtpAddress.Contains(@"\") == true) { using (Spludlow.TempDirectory tempDir = new TempDirectory()) { using (System.Net.Mail.SmtpClient client = new System.Net.Mail.SmtpClient()) { client.DeliveryMethod = System.Net.Mail.SmtpDeliveryMethod.SpecifiedPickupDirectory; client.PickupDirectoryLocation = tempDir.Path; client.Send(message); } string[] filenames = Directory.GetFiles(tempDir.Path); if (filenames.Length != 1) throw new ApplicationException("SMTP Send to file; did not find 1 filename: " + filenames.Length); File.Copy(filenames[0], smtpAddress); } return; } string host = smtpAddress; int portNumber = 25; if (smtpAddress.Contains(":") == true) { string[] words = smtpAddress.Split(new char[] { ':' }); host = words[0]; portNumber = Int32.Parse(words[1]); } using (SmtpClient client = new SmtpClient(host, portNumber)) { if (networkCredential != null) { client.Credentials = networkCredential; client.EnableSsl = false; } if (dontReTry == true) { client.Send(message); return; } bool worked = false; for (int attempt = 0; attempt < AttemptsToTry && worked == false; ++attempt) { try { if (attempt != 0) System.Threading.Thread.Sleep(WaitAttemptMilliSeconds); client.Send(message); worked = true; } catch (System.Net.Mail.SmtpException exception) { if (exception.StatusCode != SmtpStatusCode.MailboxBusy && exception.StatusCode != SmtpStatusCode.ServiceNotAvailable) throw exception; Spludlow.Log.Warning("Smtp, Send; Failed attempt smtp:" + smtpAddress + ", " + (attempt + 1) + "/" + AttemptsToTry + ", " + exception.StatusCode.ToString() + ":" + (int)exception.StatusCode, exception); } } if (worked == false) throw new ApplicationException("Smtp, Send; Failed all attempts, See previous warnings. smtp: " + smtpAddress); } } public static string FixSubject(string input) { StringBuilder output = new StringBuilder(); foreach (char c in input) { if (Char.IsLetterOrDigit(c) == true || Char.IsPunctuation(c) == true || Char.IsSymbol(c) == true) { output.Append(c); } else { if (Char.IsWhiteSpace(c) == true) output.Append(" "); else output.Append("."); } } if (output.Length > 128) output.Length = 128; return output.ToString(); } public static string[] FixedEmailAddresses(string[] emailAddresses) { List result = new List(); foreach (string emailSet in emailAddresses) { string[] emails = Spludlow.Text.Split(emailSet, ';', true); foreach (string rawEmail in emails) { string email = rawEmail.ToLower(); if (result.Contains(email) == false) { if (Spludlow.Net.Smtp.ValidAddress(email) == true) result.Add(email); else Spludlow.Log.Warning("FixedEmailAddresses; Bad Address:\t" + email); } } } return result.ToArray(); } public static string[][] ParseMimeEmailAddresses(string mimeAddresses) { string[] parts = SplitAddresses(mimeAddresses); string[][] result = new string[parts.Length][]; for (int index = 0; index < parts.Length; ++index) result[index] = ParseMimeEmailAddress(parts[index]); return result; } public static string[] SplitAddresses(string input) { List splits = new List(); bool inQuotes = false; for (int index = 0; index < input.Length; ++index) { char ch = input[index]; if (ch == '\"' && (index > 0 && input[index - 1] == '\\') == false) inQuotes = !inQuotes; if (inQuotes == false && ch == ',') splits.Add(index); } if (splits.Count == 0) { input = input.Trim(); if (input.Length > 0) return new string[] { input }; return new string[0]; } List results = new List(); results.Add(input.Substring(0, splits[0])); for (int splitIndex = 0; splitIndex < splits.Count; ++splitIndex) { string result; if (splitIndex == (splits.Count - 1)) result = input.Substring(splits[splitIndex] + 1); else result = input.Substring(splits[splitIndex] + 1, (splits[splitIndex + 1] - splits[splitIndex]) - 1); result = result.Trim(); if (result.Length > 0) results.Add(result); } return results.ToArray(); } public static string[] ParseMimeEmailAddress(string mimeAddress) { string address = null; string display = null; char open = '<'; int lastIndex = mimeAddress.LastIndexOf('>'); if (lastIndex == -1) { address = mimeAddress; display = ""; } else { int firstIndex = mimeAddress.LastIndexOf(open, lastIndex - 1); if (firstIndex == -1) throw new ApplicationException("ParseMimeEmailAddress; Did not find opening bracket: " + mimeAddress); address = mimeAddress.Substring(firstIndex + 1, (lastIndex - firstIndex) - 1); display = mimeAddress.Substring(0, firstIndex); } // Attempt to fix cock ups int index = address.LastIndexOf(" "); if (index != -1) address = address.Substring(index + 1); display = display.Trim(); // Trim quotes if (display.Length >= 2 && display[0] == '\"' && display[display.Length - 1] == '\"') display = display.Substring(1, display.Length - 2); // Fix escaped quotes display = display.Replace("\\\"", "\""); if (display == "") display = address; return new string[] { address, display }; } } }