Skip to content

Commit

Permalink
Fix SmtpCLient's re-EHLO logic to disconnect on errors
Browse files Browse the repository at this point in the history
Also fixed the protected SendCommand() method to disconnect on errors.
  • Loading branch information
jstedfast committed Jan 15, 2024
1 parent 3d1487d commit 4310535
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 29 deletions.
38 changes: 23 additions & 15 deletions MailKit/Net/Smtp/AsyncSmtpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,10 @@ async Task<QueueResults> FlushCommandQueueAsync (MimeMessage message, MailboxAdd
/// <exception cref="System.IO.IOException">
/// An I/O error occurred.
/// </exception>
/// <exception cref="SmtpCommandException">
/// The SMTP command failed.
/// </exception>
/// <exception cref="SmtpProtocolException">
/// An SMTP protocol exception occurred.
/// </exception>
protected Task<SmtpResponse> SendCommandAsync (string command, CancellationToken cancellationToken = default)
protected async Task<SmtpResponse> SendCommandAsync (string command, CancellationToken cancellationToken = default)
{
if (command == null)
throw new ArgumentNullException (nameof (command));
Expand All @@ -125,27 +122,38 @@ protected Task<SmtpResponse> SendCommandAsync (string command, CancellationToken
if (!command.EndsWith ("\r\n", StringComparison.Ordinal))
command += "\r\n";

return Stream.SendCommandAsync (command, cancellationToken);
try {
return await Stream.SendCommandAsync (command, cancellationToken).ConfigureAwait (false);
} catch {
Disconnect (uri.Host, uri.Port, GetSecureSocketOptions (uri), false);
throw;
}
}

Task<SmtpResponse> SendEhloAsync (string helo, CancellationToken cancellationToken)
async Task<SmtpResponse> SendEhloAsync (bool connecting, string helo, CancellationToken cancellationToken)
{
var command = CreateEhloCommand (helo);

return Stream.SendCommandAsync (command, cancellationToken);
try {
return await Stream.SendCommandAsync (command, cancellationToken).ConfigureAwait (false);
} catch {
if (!connecting)
Disconnect (uri.Host, uri.Port, GetSecureSocketOptions (uri), false);
throw;
}
}

async Task EhloAsync (CancellationToken cancellationToken)
async Task EhloAsync (bool connecting, CancellationToken cancellationToken)
{
var response = await SendEhloAsync ("EHLO", cancellationToken).ConfigureAwait (false);
var response = await SendEhloAsync (connecting, "EHLO", cancellationToken).ConfigureAwait (false);

// Some SMTP servers do not accept an EHLO after authentication (despite the rfc saying it is required).
if (authenticated && response.StatusCode == SmtpStatusCode.BadCommandSequence)
if (!connecting && response.StatusCode == SmtpStatusCode.BadCommandSequence)
return;

if (response.StatusCode != SmtpStatusCode.Ok) {
// Try sending HELO instead...
response = await SendEhloAsync ("HELO", cancellationToken).ConfigureAwait (false);
response = await SendEhloAsync (connecting, "HELO", cancellationToken).ConfigureAwait (false);
if (response.StatusCode != SmtpStatusCode.Ok)
throw new SmtpCommandException (SmtpErrorCode.UnexpectedStatusCode, response.StatusCode, response.Response);
} else {
Expand Down Expand Up @@ -243,7 +251,7 @@ public override async Task AuthenticateAsync (SaslMechanism mechanism, Cancellat

if (response.StatusCode == SmtpStatusCode.AuthenticationSuccessful) {
if (mechanism.NegotiatedSecurityLayer)
await EhloAsync (cancellationToken).ConfigureAwait (false);
await EhloAsync (false, cancellationToken).ConfigureAwait (false);
authenticated = true;
OnAuthenticated (response.Response);
return;
Expand Down Expand Up @@ -379,7 +387,7 @@ public override async Task AuthenticateAsync (Encoding encoding, ICredentials cr

if (response.StatusCode == SmtpStatusCode.AuthenticationSuccessful) {
if (sasl.NegotiatedSecurityLayer)
await EhloAsync (cancellationToken).ConfigureAwait (false);
await EhloAsync (false, cancellationToken).ConfigureAwait (false);
authenticated = true;
OnAuthenticated (response.Response);
return;
Expand Down Expand Up @@ -431,7 +439,7 @@ async Task PostConnectAsync (Stream stream, string host, int port, SecureSocketO
throw new SmtpCommandException (SmtpErrorCode.UnexpectedStatusCode, response.StatusCode, response.Response);

// Send EHLO and get a list of supported extensions
await EhloAsync (cancellationToken).ConfigureAwait (false);
await EhloAsync (true, cancellationToken).ConfigureAwait (false);

if (options == SecureSocketOptions.StartTls && (capabilities & SmtpCapabilities.StartTLS) == 0)
throw new NotSupportedException ("The SMTP server does not support the STARTTLS extension.");
Expand All @@ -453,7 +461,7 @@ async Task PostConnectAsync (Stream stream, string host, int port, SecureSocketO
secure = true;

// Send EHLO again and get the new list of supported extensions
await EhloAsync (cancellationToken).ConfigureAwait (false);
await EhloAsync (true, cancellationToken).ConfigureAwait (false);
}

connected = true;
Expand Down
36 changes: 22 additions & 14 deletions MailKit/Net/Smtp/SmtpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -680,9 +680,6 @@ QueueResults FlushCommandQueue (MimeMessage message, MailboxAddress sender, ILis
/// <exception cref="System.IO.IOException">
/// An I/O error occurred.
/// </exception>
/// <exception cref="SmtpCommandException">
/// The SMTP command failed.
/// </exception>
/// <exception cref="SmtpProtocolException">
/// An SMTP protocol exception occurred.
/// </exception>
Expand All @@ -699,7 +696,12 @@ protected SmtpResponse SendCommand (string command, CancellationToken cancellati
if (!command.EndsWith ("\r\n", StringComparison.Ordinal))
command += "\r\n";

return Stream.SendCommand (command, cancellationToken);
try {
return Stream.SendCommand (command, cancellationToken);
} catch {
Disconnect (uri.Host, uri.Port, GetSecureSocketOptions (uri), false);
throw;
}
}

static bool ReadNextLine (string text, ref int index, out int lineStartIndex, out int lineEndIndex)
Expand Down Expand Up @@ -865,24 +867,30 @@ string CreateEhloCommand (string helo)
return string.Format ("{0} {1}\r\n", helo, domain);
}

SmtpResponse SendEhlo (string helo, CancellationToken cancellationToken)
SmtpResponse SendEhlo (bool connecting, string helo, CancellationToken cancellationToken)
{
var command = CreateEhloCommand (helo);

return Stream.SendCommand (command, cancellationToken);
try {
return Stream.SendCommand (command, cancellationToken);
} catch {
if (!connecting)
Disconnect (uri.Host, uri.Port, GetSecureSocketOptions (uri), false);
throw;
}
}

void Ehlo (CancellationToken cancellationToken)
void Ehlo (bool connecting, CancellationToken cancellationToken)
{
var response = SendEhlo ("EHLO", cancellationToken);
var response = SendEhlo (connecting, "EHLO", cancellationToken);

// Some SMTP servers do not accept an EHLO after authentication (despite the rfc saying it is required).
if (authenticated && response.StatusCode == SmtpStatusCode.BadCommandSequence)
if (!connecting && response.StatusCode == SmtpStatusCode.BadCommandSequence)
return;

if (response.StatusCode != SmtpStatusCode.Ok) {
// Try sending HELO instead...
response = SendEhlo ("HELO", cancellationToken);
response = SendEhlo (connecting, "HELO", cancellationToken);
if (response.StatusCode != SmtpStatusCode.Ok)
throw new SmtpCommandException (SmtpErrorCode.UnexpectedStatusCode, response.StatusCode, response.Response);
} else {
Expand Down Expand Up @@ -999,7 +1007,7 @@ public override void Authenticate (SaslMechanism mechanism, CancellationToken ca

if (response.StatusCode == SmtpStatusCode.AuthenticationSuccessful) {
if (mechanism.NegotiatedSecurityLayer)
Ehlo (cancellationToken);
Ehlo (false, cancellationToken);
authenticated = true;
OnAuthenticated (response.Response);
return;
Expand Down Expand Up @@ -1154,7 +1162,7 @@ public override void Authenticate (Encoding encoding, ICredentials credentials,

if (response.StatusCode == SmtpStatusCode.AuthenticationSuccessful) {
if (sasl.NegotiatedSecurityLayer)
Ehlo (cancellationToken);
Ehlo (false, cancellationToken);
authenticated = true;
OnAuthenticated (response.Response);
return;
Expand Down Expand Up @@ -1249,7 +1257,7 @@ void PostConnect (Stream stream, string host, int port, SecureSocketOptions opti
throw new SmtpCommandException (SmtpErrorCode.UnexpectedStatusCode, response.StatusCode, response.Response);

// Send EHLO and get a list of supported extensions
Ehlo (cancellationToken);
Ehlo (true, cancellationToken);

if (options == SecureSocketOptions.StartTls && (capabilities & SmtpCapabilities.StartTLS) == 0)
throw new NotSupportedException ("The SMTP server does not support the STARTTLS extension.");
Expand All @@ -1271,7 +1279,7 @@ void PostConnect (Stream stream, string host, int port, SecureSocketOptions opti
secure = true;

// Send EHLO again and get the new list of supported extensions
Ehlo (cancellationToken);
Ehlo (true, cancellationToken);
}

connected = true;
Expand Down

0 comments on commit 4310535

Please sign in to comment.