Skip to content

Commit

Permalink
Added support and unit tests for SMTP REQUIRETLS
Browse files Browse the repository at this point in the history
  • Loading branch information
jstedfast committed Jan 16, 2024
1 parent 4ceafb7 commit a3ec8f0
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 0 deletions.
8 changes: 8 additions & 0 deletions MailKit/Net/Smtp/SmtpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1905,6 +1905,14 @@ string CreateMailFromCommand (FormatOptions options, MimeMessage message, Mailbo
}
}

if ((Capabilities & SmtpCapabilities.RequireTLS) != 0) {
// Check to see if the message has a TLS-Required header. If it does, then the only defined value it can have is "No".
var index = message.Headers.IndexOf (HeaderId.TLSRequired);

if (index == -1)
builder.Append (" REQUIRETLS");
}

builder.Append ("\r\n");

return builder.ToString ();
Expand Down
8 changes: 8 additions & 0 deletions UnitTests/Net/Smtp/Resources/comcast-ehlo+requiretls.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
250-omta06.westchester.pa.mail.comcast.net hello [127.0.0.1], pleased to meet you
250-HELP
250-AUTH LOGIN PLAIN
250-SIZE 36700160
250-ENHANCEDSTATUSCODES
250-8BITMIME
250-REQUIRETLS
250 STARTTLS
132 changes: 132 additions & 0 deletions UnitTests/Net/Smtp/SmtpClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4750,6 +4750,138 @@ public async Task TestDeliveryStatusNotificationWithHexEncodeAsync ()
}
}

static List<SmtpReplayCommand> CreateRequireTlsCommands ()
{
return new List<SmtpReplayCommand> {
new SmtpReplayCommand ("", "comcast-greeting.txt"),
new SmtpReplayCommand ($"EHLO {SmtpClient.DefaultLocalDomain}\r\n", "comcast-ehlo+requiretls.txt"),
new SmtpReplayCommand ("AUTH PLAIN AHVzZXJuYW1lAHBhc3N3b3Jk\r\n", "comcast-auth-plain.txt"),
new SmtpReplayCommand ("MAIL FROM:<[email protected]> BODY=8BITMIME REQUIRETLS\r\n", "comcast-mail-from.txt"),
new SmtpReplayCommand ("RCPT TO:<[email protected]>\r\n", "comcast-rcpt-to.txt"),
new SmtpReplayCommand ("DATA\r\n", "comcast-data.txt"),
new SmtpReplayCommand (".\r\n", "comcast-data-done.txt"),
new SmtpReplayCommand ("MAIL FROM:<[email protected]> BODY=8BITMIME\r\n", "comcast-mail-from.txt"),
new SmtpReplayCommand ("RCPT TO:<[email protected]>\r\n", "comcast-rcpt-to.txt"),
new SmtpReplayCommand ("DATA\r\n", "comcast-data.txt"),
new SmtpReplayCommand (".\r\n", "comcast-data-done.txt"),
new SmtpReplayCommand ("QUIT\r\n", "comcast-quit.txt")
};
}

[Test]
public void TestRequireTls ()
{
var commands = CreateRequireTlsCommands ();

using (var client = new SmtpClient ()) {
try {
client.Connect (new SmtpReplayStream (commands, false), "localhost", 25, SecureSocketOptions.None);
} catch (Exception ex) {
Assert.Fail ($"Did not expect an exception in Connect: {ex}");
}

Assert.That (client.IsConnected, Is.True, "Client failed to connect.");

Assert.That (client.Capabilities.HasFlag (SmtpCapabilities.Authentication), Is.True, "Failed to detect AUTH extension");
Assert.That (client.AuthenticationMechanisms.Contains ("LOGIN"), Is.True, "Failed to detect the LOGIN auth mechanism");
Assert.That (client.AuthenticationMechanisms.Contains ("PLAIN"), Is.True, "Failed to detect the PLAIN auth mechanism");

Assert.That (client.Capabilities.HasFlag (SmtpCapabilities.EightBitMime), Is.True, "Failed to detect 8BITMIME extension");
Assert.That (client.Capabilities.HasFlag (SmtpCapabilities.EnhancedStatusCodes), Is.True, "Failed to detect ENHANCEDSTATUSCODES extension");
Assert.That (client.Capabilities.HasFlag (SmtpCapabilities.Size), Is.True, "Failed to detect SIZE extension");
Assert.That (client.MaxSize, Is.EqualTo (36700160), "Failed to parse SIZE correctly");
Assert.That (client.Capabilities.HasFlag (SmtpCapabilities.StartTLS), Is.True, "Failed to detect STARTTLS extension");
Assert.That (client.Capabilities.HasFlag (SmtpCapabilities.RequireTLS), Is.True, "Failed to detect REQUIRETLS extension");

try {
client.Authenticate ("username", "password");
} catch (Exception ex) {
Assert.Fail ($"Did not expect an exception in Authenticate: {ex}");
}

try {
using (var message = CreateEightBitMessage ())
client.Send (message);
} catch (Exception ex) {
Assert.Fail ($"Did not expect an exception in Send: {ex}");
}

try {
using (var message = CreateEightBitMessage ()) {
message.Headers.Add (HeaderId.TLSRequired, "No");
client.Send (message);
}
} catch (Exception ex) {
Assert.Fail ($"Did not expect an exception in Send: {ex}");
}

try {
client.Disconnect (true);
} catch (Exception ex) {
Assert.Fail ($"Did not expect an exception in Disconnect: {ex}");
}

Assert.That (client.IsConnected, Is.False, "Failed to disconnect");
}
}

[Test]
public async Task TestRequireTlsAsync ()
{
var commands = CreateRequireTlsCommands ();

using (var client = new SmtpClient ()) {
try {
await client.ConnectAsync (new SmtpReplayStream (commands, true), "localhost", 25, SecureSocketOptions.None);
} catch (Exception ex) {
Assert.Fail ($"Did not expect an exception in Connect: {ex}");
}

Assert.That (client.IsConnected, Is.True, "Client failed to connect.");

Assert.That (client.Capabilities.HasFlag (SmtpCapabilities.Authentication), Is.True, "Failed to detect AUTH extension");
Assert.That (client.AuthenticationMechanisms.Contains ("LOGIN"), Is.True, "Failed to detect the LOGIN auth mechanism");
Assert.That (client.AuthenticationMechanisms.Contains ("PLAIN"), Is.True, "Failed to detect the PLAIN auth mechanism");

Assert.That (client.Capabilities.HasFlag (SmtpCapabilities.EightBitMime), Is.True, "Failed to detect 8BITMIME extension");
Assert.That (client.Capabilities.HasFlag (SmtpCapabilities.EnhancedStatusCodes), Is.True, "Failed to detect ENHANCEDSTATUSCODES extension");
Assert.That (client.Capabilities.HasFlag (SmtpCapabilities.Size), Is.True, "Failed to detect SIZE extension");
Assert.That (client.MaxSize, Is.EqualTo (36700160), "Failed to parse SIZE correctly");
Assert.That (client.Capabilities.HasFlag (SmtpCapabilities.StartTLS), Is.True, "Failed to detect STARTTLS extension");
Assert.That (client.Capabilities.HasFlag (SmtpCapabilities.RequireTLS), Is.True, "Failed to detect REQUIRETLS extension");

try {
await client.AuthenticateAsync ("username", "password");
} catch (Exception ex) {
Assert.Fail ($"Did not expect an exception in Authenticate: {ex}");
}

try {
using (var message = CreateEightBitMessage ())
await client.SendAsync (message);
} catch (Exception ex) {
Assert.Fail ($"Did not expect an exception in Send: {ex}");
}

try {
using (var message = CreateEightBitMessage ()) {
message.Headers.Add (HeaderId.TLSRequired, "No");
await client.SendAsync (message);
}
} catch (Exception ex) {
Assert.Fail ($"Did not expect an exception in Send: {ex}");
}

try {
await client.DisconnectAsync (true);
} catch (Exception ex) {
Assert.Fail ($"Did not expect an exception in Disconnect: {ex}");
}

Assert.That (client.IsConnected, Is.False, "Failed to disconnect");
}
}

class CustomSmtpClient : SmtpClient
{
public SmtpResponse SendCommand (string command)
Expand Down

0 comments on commit a3ec8f0

Please sign in to comment.