using System.ComponentModel.DataAnnotations; using System.Threading; using System.Threading.Tasks; using BTCPayServer.Client.Models; using BTCPayServer.Validation; using MailKit.Net.Smtp; using Microsoft.AspNetCore.Mvc.ModelBinding; using MimeKit; using Newtonsoft.Json; namespace BTCPayServer.Plugins.Emails.Services; /// /// Email Settings gets saved to database, and is used by both the server and store settings /// public class EmailSettings { public string Server { get; set; } public int? Port { get; set; } public string Login { get; set; } public string Password { get; set; } public string From { get; set; } public bool DisableCertificateCheck { get; set; } [JsonIgnore] public bool EnabledCertificateCheck { get => !DisableCertificateCheck; set { DisableCertificateCheck = !value; } } // Conversion functions for mapping to and from the client EmailSettingsData model #nullable enable public static EmailSettings FromData(EmailSettingsData data, string? existingPassword) => new() { Server = data.Server, Port = data.Port, Login = data.Login, // If login was provided but password was not, use the existing password from database Password = !string.IsNullOrEmpty(data.Login) && string.IsNullOrEmpty(data.Password) ? existingPassword : data.Password, From = data.From, DisableCertificateCheck = data.DisableCertificateCheck }; public T ToData() where T : EmailSettingsData, new() => new T() { Server = Server, Port = Port, Login = Login, PasswordSet = !string.IsNullOrEmpty(Password), From = From, DisableCertificateCheck = DisableCertificateCheck }; #nullable restore // public bool IsComplete() { return MailboxAddressValidator.IsMailboxAddress(From) && !string.IsNullOrWhiteSpace(Server) && Port != null && !string.IsNullOrWhiteSpace(From) && MailboxAddressValidator.IsMailboxAddress(From); } public void Validate(string prefixKey, ModelStateDictionary modelState) { if (!string.IsNullOrEmpty(From) && !MailboxAddressValidator.IsMailboxAddress(From)) { modelState.AddModelError($"{prefixKey}{nameof(From)}", MailboxAddressAttribute.ErrorMessageConst); } } public MimeMessage CreateMailMessage(MailboxAddress to, string subject, string message, bool isHtml) => CreateMailMessage(new[] { to }, null, null, subject, message, isHtml); public MimeMessage CreateMailMessage(MailboxAddress[] to, MailboxAddress[] cc, MailboxAddress[] bcc, string subject, string message, bool isHtml) { var bodyBuilder = new BodyBuilder(); if (isHtml) { bodyBuilder.HtmlBody = message; } else { bodyBuilder.TextBody = message; } var mm = new MimeMessage(); mm.Body = bodyBuilder.ToMessageBody(); mm.Subject = subject; mm.From.Add(MailboxAddressValidator.Parse(From)); mm.To.AddRange(to); mm.Cc.AddRange(cc ?? System.Array.Empty()); mm.Bcc.AddRange(bcc ?? System.Array.Empty()); return mm; } public async Task CreateSmtpClient() { SmtpClient client = new SmtpClient(); using var connectCancel = new CancellationTokenSource(10000); try { if (DisableCertificateCheck) { client.CheckCertificateRevocation = false; #pragma warning disable CA5359 // Do Not Disable Certificate Validation client.ServerCertificateValidationCallback = (s, c, h, e) => true; #pragma warning restore CA5359 // Do Not Disable Certificate Validation } await client.ConnectAsync(Server, Port.Value, MailKit.Security.SecureSocketOptions.Auto, connectCancel.Token); if ((client.Capabilities & SmtpCapabilities.Authentication) != 0) await client.AuthenticateAsync(Login ?? string.Empty, Password ?? string.Empty, connectCancel.Token); } catch { client.Dispose(); throw; } return client; } }