Skip to content

Commit ef660d5

Browse files
committed
added Certificate factory
1 parent 7018938 commit ef660d5

18 files changed

+114
-173
lines changed

Examples/SampleApp/Program.cs

+1-72
Original file line numberDiff line numberDiff line change
@@ -31,78 +31,7 @@ static void Main(string[] args)
3131
//ServerCancellingExample.Run();
3232
//SessionTracingExample.Run();
3333
//DependencyInjectionExample.Run();
34-
//SecureServerExample.Run();
35-
36-
//_cancellationTokenSource = new CancellationTokenSource();
37-
38-
var options = new SmtpServerOptionsBuilder()
39-
.ServerName("SmtpServer SampleApp")
40-
.Port(9025)
41-
.Build();
42-
43-
var serviceProvider = new ServiceProvider();
44-
serviceProvider.Add(new SampleMessageStore(Console.Out));
45-
46-
var server = new SmtpServer.SmtpServer(options, serviceProvider);
47-
48-
server.SessionCreated += OnSessionCreated;
49-
server.SessionCompleted += OnSessionCompleted;
50-
server.SessionFaulted += OnSessionFaulted;
51-
server.SessionCancelled += OnSessionCancelled;
52-
53-
var serverTask = server.StartAsync(CancellationToken.None);
54-
55-
serverTask.WaitWithoutException();
56-
}
57-
58-
static void OnSessionFaulted(object sender, SessionFaultedEventArgs e)
59-
{
60-
Console.WriteLine("SessionFaulted: {0}", e.Exception);
61-
}
62-
63-
static void OnSessionCancelled(object sender, SessionEventArgs e)
64-
{
65-
Console.WriteLine("SessionCancelled");
66-
}
67-
68-
static void OnSessionCreated(object sender, SessionEventArgs e)
69-
{
70-
e.Context.Properties.Add("SessionID", Guid.NewGuid());
71-
72-
e.Context.CommandExecuting += OnCommandExecuting;
73-
e.Context.CommandExecuted += OnCommandExecuted;
74-
e.Context.ResponseException += OnResponseException;
75-
}
76-
77-
private static void OnResponseException(object sender, SmtpResponseExceptionEventArgs e)
78-
{
79-
Console.WriteLine("Response Exception");
80-
if (e.Exception.Properties.ContainsKey("SmtpSession:Buffer"))
81-
{
82-
var buffer = e.Exception.Properties["SmtpSession:Buffer"] as byte[];
83-
Console.WriteLine("Unknown Line: {0}", Encoding.UTF8.GetString(buffer));
84-
}
85-
}
86-
87-
static void OnCommandExecuting(object sender, SmtpCommandEventArgs e)
88-
{
89-
//Console.WriteLine("Command Executing (SessionID={0})", e.Context.Properties["SessionID"]);
90-
new TracingSmtpCommandVisitor(Console.Out).Visit(e.Command);
91-
}
92-
93-
static void OnCommandExecuted(object sender, SmtpCommandEventArgs e)
94-
{
95-
//Console.WriteLine("Command Executed (SessionID={0})", e.Context.Properties["SessionID"]);
96-
//new TracingSmtpCommandVisitor(Console.Out).Visit(e.Command);
97-
}
98-
99-
static void OnSessionCompleted(object sender, SessionEventArgs e)
100-
{
101-
Console.WriteLine("SessionCompleted: {0}", e.Context.Properties[EndpointListener.RemoteEndPointKey]);
102-
103-
e.Context.CommandExecuting -= OnCommandExecuting;
104-
e.Context.CommandExecuted -= OnCommandExecuted;
105-
e.Context.ResponseException -= OnResponseException;
34+
SecureServerExample.Run();
10635
}
10736

10837
static bool IgnoreCertificateValidationFailureForTestingOnly(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)

Examples/SampleApp/SampleMailboxFilter.cs

+9-8
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using SmtpServer.Mail;
77
using SmtpServer.Net;
88
using SmtpServer.Storage;
9+
using SmtpServer.Protocol;
910

1011
namespace SampleApp
1112
{
@@ -26,8 +27,8 @@ public SampleMailboxFilter(TimeSpan delay)
2627
/// <param name="from">The mailbox to test.</param>
2728
/// <param name="size">The estimated message size to accept.</param>
2829
/// <param name="cancellationToken">The cancellation token.</param>
29-
/// <returns>The acceptance state of the mailbox.</returns>
30-
public override async Task<MailboxFilterResult> CanAcceptFromAsync(
30+
/// <returns>Returns true if the mailbox is accepted, false if not.</returns>
31+
public override async Task<bool> CanAcceptFromAsync(
3132
ISessionContext context,
3233
IMailbox @from,
3334
int size,
@@ -37,17 +38,17 @@ public override async Task<MailboxFilterResult> CanAcceptFromAsync(
3738

3839
if (@from == Mailbox.Empty)
3940
{
40-
return MailboxFilterResult.NoPermanently;
41+
throw new SmtpResponseException(SmtpResponse.MailboxNameNotAllowed);
4142
}
4243

4344
var endpoint = (IPEndPoint)context.Properties[EndpointListener.RemoteEndPointKey];
4445

4546
if (endpoint.Address.Equals(IPAddress.Parse("127.0.0.1")))
4647
{
47-
return MailboxFilterResult.Yes;
48+
return true;
4849
}
4950

50-
return MailboxFilterResult.NoPermanently;
51+
return false;
5152
}
5253

5354
/// <summary>
@@ -57,16 +58,16 @@ public override async Task<MailboxFilterResult> CanAcceptFromAsync(
5758
/// <param name="to">The mailbox to test.</param>
5859
/// <param name="from">The sender's mailbox.</param>
5960
/// <param name="cancellationToken">The cancellation token.</param>
60-
/// <returns>The acceptance state of the mailbox.</returns>
61-
public override async Task<MailboxFilterResult> CanDeliverToAsync(
61+
/// <returns>Returns true if the mailbox can be delivered to, false if not.</returns>
62+
public override async Task<bool> CanDeliverToAsync(
6263
ISessionContext context,
6364
IMailbox to,
6465
IMailbox @from,
6566
CancellationToken cancellationToken)
6667
{
6768
await Task.Delay(_delay, cancellationToken);
6869

69-
return MailboxFilterResult.Yes;
70+
return true;
7071
}
7172
}
7273
}

Src/SmtpServer.Tests/SmtpServerTests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ public void CanReturnSmtpResponseException_DoesNotQuit()
165165
throw new SmtpResponseException(SmtpResponse.AuthenticationRequired);
166166

167167
#pragma warning disable 162
168-
return MailboxFilterResult.Yes;
168+
return true;
169169
#pragma warning restore 162
170170
});
171171

Src/SmtpServer/EndpointDefinitionBuilder.cs

+33-3
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,17 @@ public EndpointDefinitionBuilder ReadTimeout(TimeSpan value)
118118
/// <returns>A EndpointDefinitionBuilder to continue building on.</returns>
119119
public EndpointDefinitionBuilder Certificate(X509Certificate value)
120120
{
121-
_setters.Add(options => options.ServerCertificate = value);
121+
return Certificate(new StaticCertificateFactory(value));
122+
}
123+
124+
/// <summary>
125+
/// Sets the X509 certificate factory to use when starting a TLS session.
126+
/// </summary>
127+
/// <param name="certificateFactory">The certificate factory to use when creating TLS sessions.</param>
128+
/// <returns>A EndpointDefinitionBuilder to continue building on.</returns>
129+
public EndpointDefinitionBuilder Certificate(ICertificateFactory certificateFactory)
130+
{
131+
_setters.Add(options => options.CertificateFactory = certificateFactory);
122132

123133
return this;
124134
}
@@ -165,16 +175,36 @@ internal sealed class EndpointDefinition : IEndpointDefinition
165175
public TimeSpan ReadTimeout { get; set; }
166176

167177
/// <summary>
168-
/// Gets the Server Certificate to use when starting a TLS session.
178+
/// Gets the Server Certificate factory to use when starting a TLS session.
169179
/// </summary>
170-
public X509Certificate ServerCertificate { get; set; }
180+
public ICertificateFactory CertificateFactory { get; set; }
171181

172182
/// <summary>
173183
/// The supported SSL protocols.
174184
/// </summary>
175185
public SslProtocols SupportedSslProtocols { get; set; }
176186
}
177187

188+
#endregion
189+
190+
#region StaticCertificateFactory
191+
192+
internal sealed class StaticCertificateFactory : ICertificateFactory
193+
{
194+
readonly X509Certificate _serverCertificate;
195+
196+
public StaticCertificateFactory(X509Certificate serverCertificate)
197+
{
198+
_serverCertificate = serverCertificate;
199+
}
200+
201+
public X509Certificate GetServerCertificate(ISessionContext sessionContext)
202+
{
203+
return _serverCertificate;
204+
}
205+
}
206+
207+
178208
#endregion
179209
}
180210
}

Src/SmtpServer/ICertificateFactory.cs

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System.Security.Cryptography.X509Certificates;
2+
3+
namespace SmtpServer
4+
{
5+
public interface ICertificateFactory
6+
{
7+
/// <summary>
8+
/// Returns the certificate to use for the session.
9+
/// </summary>
10+
/// <param name="sessionContext">The session context.</param>
11+
/// <returns>The certificate to use when starting a TLS session.</returns>
12+
X509Certificate GetServerCertificate(ISessionContext sessionContext);
13+
}
14+
}

Src/SmtpServer/IEndpointDefinition.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ public interface IEndpointDefinition
3333
TimeSpan ReadTimeout { get; }
3434

3535
/// <summary>
36-
/// Gets the Server Certificate to use when starting a TLS session.
36+
/// Gets the Server Certificate factory to use when starting a TLS session.
3737
/// </summary>
38-
X509Certificate ServerCertificate { get; }
38+
ICertificateFactory CertificateFactory { get; }
3939

4040
/// <summary>
4141
/// The supported SSL protocols.

Src/SmtpServer/Protocol/EhloCommand.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ protected virtual IEnumerable<string> GetExtensions(ISessionContext context)
6464
yield return "8BITMIME";
6565
yield return "SMTPUTF8";
6666

67-
if (context.Pipe.IsSecure == false && context.EndpointDefinition.ServerCertificate != null)
67+
if (context.Pipe.IsSecure == false && context.EndpointDefinition.CertificateFactory != null)
6868
{
6969
yield return "STARTTLS";
7070
}

Src/SmtpServer/Protocol/MailCommand.cs

+3-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using System;
2-
using System.Collections.Generic;
1+
using System.Collections.Generic;
32
using System.Threading;
43
using System.Threading.Tasks;
54
using SmtpServer.ComponentModel;
@@ -58,22 +57,14 @@ internal override async Task<bool> ExecuteAsync(SmtpSessionContext context, Canc
5857

5958
switch (await container.Instance.CanAcceptFromAsync(context, Address, size, cancellationToken).ConfigureAwait(false))
6059
{
61-
case MailboxFilterResult.Yes:
60+
case true:
6261
context.Transaction.From = Address;
6362
await context.Pipe.Output.WriteReplyAsync(SmtpResponse.Ok, cancellationToken).ConfigureAwait(false);
6463
return true;
6564

66-
case MailboxFilterResult.NoTemporarily:
65+
case false:
6766
await context.Pipe.Output.WriteReplyAsync(SmtpResponse.MailboxUnavailable, cancellationToken).ConfigureAwait(false);
6867
return false;
69-
70-
case MailboxFilterResult.NoPermanently:
71-
await context.Pipe.Output.WriteReplyAsync(SmtpResponse.MailboxNameNotAllowed, cancellationToken).ConfigureAwait(false);
72-
return false;
73-
74-
case MailboxFilterResult.SizeLimitExceeded:
75-
await context.Pipe.Output.WriteReplyAsync(SmtpResponse.SizeLimitExceeded, cancellationToken).ConfigureAwait(false);
76-
return false;
7768
}
7869

7970
throw new SmtpResponseException(SmtpResponse.TransactionFailed);

Src/SmtpServer/Protocol/RcptCommand.cs

+2-6
Original file line numberDiff line numberDiff line change
@@ -36,18 +36,14 @@ internal override async Task<bool> ExecuteAsync(SmtpSessionContext context, Canc
3636

3737
switch (await container.Instance.CanDeliverToAsync(context, Address, context.Transaction.From, cancellationToken).ConfigureAwait(false))
3838
{
39-
case MailboxFilterResult.Yes:
39+
case true:
4040
context.Transaction.To.Add(Address);
4141
await context.Pipe.Output.WriteReplyAsync(SmtpResponse.Ok, cancellationToken).ConfigureAwait(false);
4242
return true;
4343

44-
case MailboxFilterResult.NoTemporarily:
44+
case false:
4545
await context.Pipe.Output.WriteReplyAsync(SmtpResponse.MailboxUnavailable, cancellationToken).ConfigureAwait(false);
4646
return false;
47-
48-
case MailboxFilterResult.NoPermanently:
49-
await context.Pipe.Output.WriteReplyAsync(SmtpResponse.MailboxNameNotAllowed, cancellationToken).ConfigureAwait(false);
50-
return false;
5147
}
5248

5349
throw new NotSupportedException("The Acceptance state is not supported.");

Src/SmtpServer/Protocol/StartTlsCommand.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ internal override async Task<bool> ExecuteAsync(SmtpSessionContext context, Canc
2424
{
2525
await context.Pipe.Output.WriteReplyAsync(SmtpResponse.ServiceReady, cancellationToken).ConfigureAwait(false);
2626

27-
var certificate = context.EndpointDefinition.ServerCertificate;
27+
var certificate = context.EndpointDefinition.CertificateFactory.GetServerCertificate(context);
28+
2829
var protocols = context.EndpointDefinition.SupportedSslProtocols;
2930

3031
await context.Pipe.UpgradeAsync(certificate, protocols, cancellationToken).ConfigureAwait(false);

Src/SmtpServer/SmtpServer.csproj

+7-5
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<LangVersion>8.0</LangVersion>
66
<AssemblyName>SmtpServer</AssemblyName>
77
<RootNamespace>SmtpServer</RootNamespace>
8-
<Version>9.0.3</Version>
8+
<Version>10.0.0-beta1</Version>
99
<Description>.NET SmtpServer</Description>
1010
<Authors>Cain O'Sullivan</Authors>
1111
<Company />
@@ -15,10 +15,12 @@
1515
<PackageTags>smtp smtpserver smtp server</PackageTags>
1616
<PackageLicenseUrl></PackageLicenseUrl>
1717
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
18-
<AssemblyVersion>9.1.0.0</AssemblyVersion>
19-
<FileVersion>9.1.0.0</FileVersion>
20-
<PackageReleaseNotes>Version 9.1.0
21-
Fixed a bug with the session not closing when the cancellation token was cancelled.
18+
<AssemblyVersion>10.0.0</AssemblyVersion>
19+
<FileVersion>10.0.0</FileVersion>
20+
<PackageReleaseNotes>Version 10.0.0
21+
- Removed MailboxFilterResult in favor of bool result. Impementations can throw SmtpResponseException for more control.
22+
- Handled servers that send the QUIT command and immediately close the connection.
23+
- Added an ICertificateFactory on the Endpoint that allows a new certificate to be created when required without having to restart the server.
2224
</PackageReleaseNotes>
2325
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
2426
<PackageLicenseFile>LICENSE</PackageLicenseFile>

Src/SmtpServer/SmtpSessionManager.cs

+4-2
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,11 @@ async Task UpgradeAsync(SmtpSessionHandle handle, CancellationToken cancellation
6666
{
6767
var endpoint = handle.SessionContext.EndpointDefinition;
6868

69-
if (endpoint.IsSecure && endpoint.ServerCertificate != null)
69+
if (endpoint.IsSecure && endpoint.CertificateFactory != null)
7070
{
71-
await handle.SessionContext.Pipe.UpgradeAsync(endpoint.ServerCertificate, endpoint.SupportedSslProtocols, cancellationToken).ConfigureAwait(false);
71+
var serverCertificate = endpoint.CertificateFactory.GetServerCertificate(handle.SessionContext);
72+
73+
await handle.SessionContext.Pipe.UpgradeAsync(serverCertificate, endpoint.SupportedSslProtocols, cancellationToken).ConfigureAwait(false);
7274
}
7375
}
7476

Src/SmtpServer/StateMachine/SmtpStateTable.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ static SmtpStateId WaitingForMailSecureWhenSecure(SmtpSessionContext context)
6363

6464
static bool CanAcceptStartTls(SmtpSessionContext context)
6565
{
66-
return context.EndpointDefinition.ServerCertificate != null && context.Pipe.IsSecure == false;
66+
return context.EndpointDefinition.CertificateFactory != null && context.Pipe.IsSecure == false;
6767
}
6868

6969
readonly IDictionary<SmtpStateId, SmtpState> _states = new Dictionary<SmtpStateId, SmtpState>();

0 commit comments

Comments
 (0)