From edd98ca2df76ca17b99af1854b997d36e3533788 Mon Sep 17 00:00:00 2001 From: Tino Hager Date: Fri, 25 Jul 2025 23:38:22 +0200 Subject: [PATCH 1/2] Add support for custom SMTP greeting message Introduces a CustomGreetingMessage property to ISmtpServerOptions and SmtpServerOptionsBuilder, allowing configuration of the initial SMTP greeting sent to clients. SmtpSession now uses this custom message if set, otherwise defaults to the standard greeting with server name and version. --- Src/SmtpServer/ISmtpServerOptions.cs | 7 ++++++ Src/SmtpServer/SmtpServerOptionsBuilder.cs | 23 +++++++++++++++++- Src/SmtpServer/SmtpSession.cs | 27 ++++++++++++++-------- 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/Src/SmtpServer/ISmtpServerOptions.cs b/Src/SmtpServer/ISmtpServerOptions.cs index f253484..8ec9cf8 100644 --- a/Src/SmtpServer/ISmtpServerOptions.cs +++ b/Src/SmtpServer/ISmtpServerOptions.cs @@ -42,5 +42,12 @@ public interface ISmtpServerOptions /// The size of the buffer that is read from each call to the underlying network client. /// int NetworkBufferSize { get; } + + /// + /// Gets the custom greeting message sent by the server in response to the initial SMTP connection. + /// This message is returned after the client connects and before any commands are issued (e.g., "220 mail.example.com v1.0 ESMTP ready"). + /// If not set, a default greeting will be used. + /// + string CustomGreetingMessage { get; } } } diff --git a/Src/SmtpServer/SmtpServerOptionsBuilder.cs b/Src/SmtpServer/SmtpServerOptionsBuilder.cs index 322a565..2123dda 100644 --- a/Src/SmtpServer/SmtpServerOptionsBuilder.cs +++ b/Src/SmtpServer/SmtpServerOptionsBuilder.cs @@ -22,7 +22,8 @@ public ISmtpServerOptions Build() MaxRetryCount = 5, MaxAuthenticationAttempts = 3, NetworkBufferSize = 128, - CommandWaitTimeout = TimeSpan.FromMinutes(5) + CommandWaitTimeout = TimeSpan.FromMinutes(5), + CustomGreetingMessage = null }; _setters.ForEach(setter => setter(serverOptions)); @@ -155,6 +156,19 @@ public SmtpServerOptionsBuilder CommandWaitTimeout(TimeSpan value) return this; } + /// + /// Sets the custom SMTP greeting message sent to the client upon connection, + /// typically returned as the initial "220" response. + /// + /// The greeting message to send to the client (e.g., "220 mail.example.com v1.0 ESMTP ready"). + /// An OptionsBuilder to continue building on. + public SmtpServerOptionsBuilder CustomGreetingMessage(string value) + { + _setters.Add(options => options.CustomGreetingMessage = value); + + return this; + } + #region SmtpServerOptions class SmtpServerOptions : ISmtpServerOptions @@ -198,6 +212,13 @@ class SmtpServerOptions : ISmtpServerOptions /// The size of the buffer that is read from each call to the underlying network client. /// public int NetworkBufferSize { get; set; } + + /// + /// Gets or sets the custom greeting message sent by the server in response to the initial SMTP connection. + /// This message is returned after the client connects and before any commands are issued (e.g., "220 mail.example.com v1.0 ESMTP ready"). + /// If not set, a default greeting will be used. + /// + public string CustomGreetingMessage { get; set; } } #endregion diff --git a/Src/SmtpServer/SmtpSession.cs b/Src/SmtpServer/SmtpSession.cs index 9daaec0..7e13b43 100644 --- a/Src/SmtpServer/SmtpSession.cs +++ b/Src/SmtpServer/SmtpSession.cs @@ -1,20 +1,21 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using SmtpServer.Protocol; -using System.Reflection; +using SmtpServer.ComponentModel; using SmtpServer.IO; -using System.IO.Pipelines; +using SmtpServer.Protocol; using SmtpServer.StateMachine; -using SmtpServer.ComponentModel; +using System; using System.Buffers; using System.Collections.Generic; +using System.IO.Pipelines; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; namespace SmtpServer { internal sealed class SmtpSession { const string BufferKey = "SmtpSession:Buffer"; + static readonly Version AssemblyVersion = typeof(SmtpSession).GetTypeInfo().Assembly.GetName().Version; readonly SmtpStateMachine _stateMachine; readonly SmtpSessionContext _context; @@ -187,9 +188,15 @@ static async Task ExecuteAsync(SmtpCommand command, SmtpSessionContext con /// A task which performs the operation. ValueTask OutputGreetingAsync(CancellationToken cancellationToken) { - var version = typeof(SmtpSession).GetTypeInfo().Assembly.GetName().Version; - - _context.Pipe.Output.WriteLine($"220 {_context.ServerOptions.ServerName} v{version} ESMTP ready"); + if (_context.ServerOptions.CustomGreetingMessage is null) + { + var serverVersion = AssemblyVersion; + _context.Pipe.Output.WriteLine($"220 {_context.ServerOptions.ServerName} v{serverVersion} ESMTP ready"); + } + else + { + _context.Pipe.Output.WriteLine($"220 {_context.ServerOptions.ServerName} {_context.ServerOptions.CustomGreetingMessage}"); + } return _context.Pipe.Output.FlushAsync(cancellationToken); } From 9636c14930052071fddd24cf4c1a9ac488eb714c Mon Sep 17 00:00:00 2001 From: Tino Hager Date: Sun, 3 Aug 2025 09:15:47 +0200 Subject: [PATCH 2/2] optimize code --- Src/SmtpServer/ISmtpServerOptions.cs | 8 ++++---- Src/SmtpServer/SmtpServerOptionsBuilder.cs | 14 +++++++++----- Src/SmtpServer/SmtpSession.cs | 4 ++-- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/Src/SmtpServer/ISmtpServerOptions.cs b/Src/SmtpServer/ISmtpServerOptions.cs index a75ba3b..01a6266 100644 --- a/Src/SmtpServer/ISmtpServerOptions.cs +++ b/Src/SmtpServer/ISmtpServerOptions.cs @@ -44,10 +44,10 @@ public interface ISmtpServerOptions int NetworkBufferSize { get; } /// - /// Gets the custom greeting message sent by the server in response to the initial SMTP connection. - /// This message is returned after the client connects and before any commands are issued (e.g., "220 mail.example.com v1.0 ESMTP ready"). - /// If not set, a default greeting will be used. + /// Gets the custom SMTP greeting message that the server sends immediately after a client connects, + /// typically as the initial "220" response. The message can be dynamically generated based on the session context. + /// If not set, a default greeting will be used (e.g., "220 mail.example.com ESMTP ready"). /// - string CustomGreetingMessage { get; } + Func CustomSmtpGreeting { get; } } } diff --git a/Src/SmtpServer/SmtpServerOptionsBuilder.cs b/Src/SmtpServer/SmtpServerOptionsBuilder.cs index dd8fe3b..7516728 100644 --- a/Src/SmtpServer/SmtpServerOptionsBuilder.cs +++ b/Src/SmtpServer/SmtpServerOptionsBuilder.cs @@ -24,7 +24,7 @@ public ISmtpServerOptions Build() MaxAuthenticationAttempts = 3, NetworkBufferSize = 128, CommandWaitTimeout = TimeSpan.FromMinutes(5), - CustomGreetingMessage = null, + CustomSmtpGreeting = null, }; _setters.ForEach(setter => setter(serverOptions)); @@ -162,11 +162,15 @@ public SmtpServerOptionsBuilder CommandWaitTimeout(TimeSpan value) /// Sets the custom SMTP greeting message sent to the client upon connection, /// typically returned as the initial "220" response. /// - /// The greeting message to send to the client (e.g., "220 mail.example.com v1.0 ESMTP ready"). + /// + /// A delegate that returns the greeting message to send to the client, + /// based on the (e.g., client IP, TLS state). + /// Example: ctx => $"220 {sessionContext.ServerOptions.ServerName} ESMTP ready" + /// /// An OptionsBuilder to continue building on. - public SmtpServerOptionsBuilder CustomGreetingMessage(string value) + public SmtpServerOptionsBuilder CustomGreetingMessage(Func smtpGreetingFunc) { - _setters.Add(options => options.CustomGreetingMessage = value); + _setters.Add(options => options.CustomSmtpGreeting = smtpGreetingFunc); return this; } @@ -220,7 +224,7 @@ class SmtpServerOptions : ISmtpServerOptions /// This message is returned after the client connects and before any commands are issued (e.g., "220 mail.example.com v1.0 ESMTP ready"). /// If not set, a default greeting will be used. /// - public string CustomGreetingMessage { get; set; } + public Func CustomSmtpGreeting { get; set; } } #endregion diff --git a/Src/SmtpServer/SmtpSession.cs b/Src/SmtpServer/SmtpSession.cs index 210d8d1..dfc8461 100644 --- a/Src/SmtpServer/SmtpSession.cs +++ b/Src/SmtpServer/SmtpSession.cs @@ -189,14 +189,14 @@ static async Task ExecuteAsync(SmtpCommand command, SmtpSessionContext con /// A task which performs the operation. ValueTask OutputGreetingAsync(CancellationToken cancellationToken) { - if (_context.ServerOptions.CustomGreetingMessage is null) + if (_context.ServerOptions.CustomSmtpGreeting is null) { var serverVersion = AssemblyVersion; _context.Pipe.Output.WriteLine($"220 {_context.ServerOptions.ServerName} v{serverVersion} ESMTP ready"); } else { - _context.Pipe.Output.WriteLine($"220 {_context.ServerOptions.ServerName} {_context.ServerOptions.CustomGreetingMessage}"); + _context.Pipe.Output.WriteLine(_context.ServerOptions.CustomSmtpGreeting(_context)); } return _context.Pipe.Output.FlushAsync(cancellationToken);