From 40437982ebd90245c82f2ad1e2d137521cccf387 Mon Sep 17 00:00:00 2001 From: Paul Schneider Date: Sat, 29 Aug 2026 17:21:40 +0100 Subject: [PATCH] ixes invalid registration email formats are still rejected before the user is created SMTP recipient rejections are logged and treated as a soft failure instead of crashing the request --- .../NonRegression/EMailling.cs | 78 +++++++++++++++++++ src/Yavsc.Server/Services/MailSender.cs | 8 +- 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/src/Yavsc.Org.Tests/NonRegression/EMailling.cs b/src/Yavsc.Org.Tests/NonRegression/EMailling.cs index d5599842..fd1fe501 100644 --- a/src/Yavsc.Org.Tests/NonRegression/EMailling.cs +++ b/src/Yavsc.Org.Tests/NonRegression/EMailling.cs @@ -1,9 +1,18 @@ using System.ComponentModel.DataAnnotations; +using System.Globalization; +using MailKit.Net.Smtp; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using MimeKit; using Yavsc.Interface; using Yavsc.Interfaces; +using Yavsc.Models.Relationship; using Yavsc.Org.Tests.Fakes; +using Yavsc.Services; +using Yavsc.Settings; using Yavsc.ViewModels.Account; namespace Yavsc.Org.Tests @@ -80,5 +89,74 @@ namespace Yavsc.Org.Tests Assert.Contains(results, r => r.MemberNames.Contains(nameof(RegisterModel.Email))); } + [Fact] + public async Task SendEmailAsync_ignores_smtp_recipient_rejection() + { + var sender = new MailSender( + Options.Create(new SiteSettings + { + Title = "Test", + Authority = "example.com", + Owner = new StaticContact { Name = "Test Owner", EMail = "owner@example.com" } + }), + Options.Create(new SmtpSettings + { + Host = "smtp.test.local", + Port = 465, + UserName = "test-user", + Password = "secret" + }), + NullLoggerFactory.Instance, + new TestStringLocalizer(), + new RejectingSmtpClientFactory()); + + var result = await sender.SendEmailAsync( + "Alice", + "contact@pschneider.fr", + "Welcome", + "hello"); + + Assert.Equal(string.Empty, result); + } + + private sealed class RejectingSmtpClientFactory : ISmtpClientFactory + { + public Yavsc.Interfaces.ISmtpClient CreateClient() => new RejectingSmtpClient(); + } + + private sealed class RejectingSmtpClient : Yavsc.Interfaces.ISmtpClient + { + public int Timeout { get; set; } + public void Connect(string host, int port, MailKit.Security.SecureSocketOptions options) { } + public void Authenticate(string userName, string password) { } + public Task SendAsync(MimeMessage message, CancellationToken cancellationToken = default) + { + throw new SmtpCommandException( + SmtpErrorCode.RecipientNotAccepted, + SmtpStatusCode.MailboxUnavailable, + "Recipient address rejected: User unknown in local recipient table"); + } + public void Disconnect(bool quit) { } + public void Dispose() { } + } + + private sealed class TestStringLocalizer : IStringLocalizer + { + public LocalizedString this[string name] => new(name, name); + public LocalizedString this[string name, params object[] arguments] => new(name, string.Format(CultureInfo.InvariantCulture, name, arguments)); + + public IEnumerable GetAllStrings(bool includeParentCultures) + => Enumerable.Empty(); + + public LocalizedString GetString(string name) + => new(name, name); + + public LocalizedString GetString(string name, params object[] arguments) + => new(name, string.Format(CultureInfo.InvariantCulture, name, arguments)); + + public IStringLocalizer WithCulture(CultureInfo culture) + => this; + } + } } diff --git a/src/Yavsc.Server/Services/MailSender.cs b/src/Yavsc.Server/Services/MailSender.cs index d8b3c46c..592a43ba 100644 --- a/src/Yavsc.Server/Services/MailSender.cs +++ b/src/Yavsc.Server/Services/MailSender.cs @@ -1,3 +1,4 @@ +using MailKit.Net.Smtp; using MailKit.Security; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -114,7 +115,7 @@ namespace Yavsc.Services msg.MessageId = MimeKit.Utils.MimeUtils.GenerateMessageId( siteSettings.Authority ); - using ISmtpClient sc = _smtpClientFactory.CreateClient(); + using Yavsc.Interfaces.ISmtpClient sc = _smtpClientFactory.CreateClient(); { sc.Timeout = 30000; sc.Connect( @@ -139,6 +140,11 @@ namespace Yavsc.Services logger.LogError(ex, "Refusing to send email because the recipient or sender address is malformed. To={To}, From={From}", email, siteSettings.Owner.EMail); return string.Empty; } + catch (SmtpCommandException ex) + { + logger.LogError(ex, "SMTP rejected the recipient or sender address. To={To}, Subject={Subject}, Status={Status}, Error={Error}", email, subject, ex.StatusCode, ex.Message); + return string.Empty; + } catch (Exception ex) { logger.LogError(ex, "Failed to send email. To={To}, Subject={Subject}", email, subject);