Skip to content

Commit

Permalink
Added Pop3Client and ImapClient metrics/tracing for Connect and Authe…
Browse files Browse the repository at this point in the history
…nticate

Partial fix for issue #1499
  • Loading branch information
jstedfast committed Feb 24, 2024
1 parent 1117b09 commit bd0e733
Show file tree
Hide file tree
Showing 10 changed files with 815 additions and 569 deletions.
266 changes: 147 additions & 119 deletions MailKit/Net/Imap/AsyncImapClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -312,22 +312,29 @@ public override async Task AuthenticateAsync (SaslMechanism mechanism, Cancellat
await imap.Stream.FlushAsync (cmd.CancellationToken).ConfigureAwait (false);
};

detector.IsAuthenticating = true;
using var operation = engine.StartNetworkOperation (NetworkOperation.Authenticate);

try {
await engine.RunAsync (ic).ConfigureAwait (false);
} finally {
detector.IsAuthenticating = false;
}
detector.IsAuthenticating = true;

try {
await engine.RunAsync (ic).ConfigureAwait (false);
} finally {
detector.IsAuthenticating = false;
}

ProcessAuthenticateResponse (ic, mechanism);
ProcessAuthenticateResponse (ic, mechanism);

// Query the CAPABILITIES again if the server did not include an
// untagged CAPABILITIES response to the AUTHENTICATE command.
if (engine.CapabilitiesVersion == capabilitiesVersion)
await engine.QueryCapabilitiesAsync (cancellationToken).ConfigureAwait (false);
// Query the CAPABILITIES again if the server did not include an
// untagged CAPABILITIES response to the AUTHENTICATE command.
if (engine.CapabilitiesVersion == capabilitiesVersion)
await engine.QueryCapabilitiesAsync (cancellationToken).ConfigureAwait (false);

await OnAuthenticatedAsync (ic.ResponseText ?? string.Empty, cancellationToken).ConfigureAwait (false);
await OnAuthenticatedAsync (ic.ResponseText ?? string.Empty, cancellationToken).ConfigureAwait (false);
} catch (Exception ex) {
operation.SetError (ex);
throw;
}
}

/// <summary>
Expand Down Expand Up @@ -386,41 +393,84 @@ public override async Task AuthenticateAsync (Encoding encoding, ICredentials cr
{
CheckCanAuthenticate (encoding, credentials);

int capabilitiesVersion = engine.CapabilitiesVersion;
var uri = new Uri ("imap://" + engine.Uri.Host);
NetworkCredential cred;
ImapCommand ic = null;
SaslMechanism sasl;
string id;
using var operation = engine.StartNetworkOperation (NetworkOperation.Authenticate);

try {
int capabilitiesVersion = engine.CapabilitiesVersion;
var uri = new Uri ("imap://" + engine.Uri.Host);
NetworkCredential cred;
ImapCommand ic = null;
SaslMechanism sasl;
string id;

foreach (var authmech in SaslMechanism.Rank (engine.AuthenticationMechanisms)) {
cred = credentials.GetCredential (uri, authmech);
foreach (var authmech in SaslMechanism.Rank (engine.AuthenticationMechanisms)) {
cred = credentials.GetCredential (uri, authmech);

if ((sasl = SaslMechanism.Create (authmech, encoding, cred)) == null)
continue;
if ((sasl = SaslMechanism.Create (authmech, encoding, cred)) == null)
continue;

ConfigureSaslMechanism (sasl, uri);
ConfigureSaslMechanism (sasl, uri);

cancellationToken.ThrowIfCancellationRequested ();
cancellationToken.ThrowIfCancellationRequested ();

var command = string.Format ("AUTHENTICATE {0}", sasl.MechanismName);
var command = string.Format ("AUTHENTICATE {0}", sasl.MechanismName);

if ((engine.Capabilities & ImapCapabilities.SaslIR) != 0 && sasl.SupportsInitialResponse) {
string ir = await sasl.ChallengeAsync (null, cancellationToken).ConfigureAwait (false);
if ((engine.Capabilities & ImapCapabilities.SaslIR) != 0 && sasl.SupportsInitialResponse) {
string ir = await sasl.ChallengeAsync (null, cancellationToken).ConfigureAwait (false);

command += " " + ir + "\r\n";
} else {
command += "\r\n";
command += " " + ir + "\r\n";
} else {
command += "\r\n";
}

ic = engine.QueueCommand (cancellationToken, null, command);
ic.ContinuationHandler = async (imap, cmd, text, xdoAsync) => {
string challenge = await sasl.ChallengeAsync (text, cmd.CancellationToken).ConfigureAwait (false);
var buf = Encoding.ASCII.GetBytes (challenge + "\r\n");
await imap.Stream.WriteAsync (buf, 0, buf.Length, cmd.CancellationToken).ConfigureAwait (false);
await imap.Stream.FlushAsync (cmd.CancellationToken).ConfigureAwait (false);
};

detector.IsAuthenticating = true;

try {
await engine.RunAsync (ic).ConfigureAwait (false);
} finally {
detector.IsAuthenticating = false;
}

if (ic.Response != ImapCommandResponse.Ok) {
EmitAndThrowOnAlert (ic);
if (ic.Bye)
throw new ImapProtocolException (ic.ResponseText);
continue;
}

engine.State = ImapEngineState.Authenticated;

cred = credentials.GetCredential (uri, sasl.MechanismName);
id = GetSessionIdentifier (cred.UserName);
if (id != identifier) {
engine.FolderCache.Clear ();
identifier = id;
}

// Query the CAPABILITIES again if the server did not include an
// untagged CAPABILITIES response to the AUTHENTICATE command.
if (engine.CapabilitiesVersion == capabilitiesVersion)
await engine.QueryCapabilitiesAsync (cancellationToken).ConfigureAwait (false);

await OnAuthenticatedAsync (ic.ResponseText ?? string.Empty, cancellationToken).ConfigureAwait (false);
return;
}

ic = engine.QueueCommand (cancellationToken, null, command);
ic.ContinuationHandler = async (imap, cmd, text, xdoAsync) => {
string challenge = await sasl.ChallengeAsync (text, cmd.CancellationToken).ConfigureAwait (false);
var buf = Encoding.ASCII.GetBytes (challenge + "\r\n");
CheckCanLogin (ic);

await imap.Stream.WriteAsync (buf, 0, buf.Length, cmd.CancellationToken).ConfigureAwait (false);
await imap.Stream.FlushAsync (cmd.CancellationToken).ConfigureAwait (false);
};
// fall back to the classic LOGIN command...
cred = credentials.GetCredential (uri, "DEFAULT");

ic = engine.QueueCommand (cancellationToken, null, "LOGIN %S %S\r\n", cred.UserName, cred.Password);

detector.IsAuthenticating = true;

Expand All @@ -430,63 +480,27 @@ public override async Task AuthenticateAsync (Encoding encoding, ICredentials cr
detector.IsAuthenticating = false;
}

if (ic.Response != ImapCommandResponse.Ok) {
EmitAndThrowOnAlert (ic);
if (ic.Bye)
throw new ImapProtocolException (ic.ResponseText);
continue;
}
if (ic.Response != ImapCommandResponse.Ok)
throw CreateAuthenticationException (ic);

engine.State = ImapEngineState.Authenticated;

cred = credentials.GetCredential (uri, sasl.MechanismName);
id = GetSessionIdentifier (cred.UserName);
if (id != identifier) {
engine.FolderCache.Clear ();
identifier = id;
}

// Query the CAPABILITIES again if the server did not include an
// untagged CAPABILITIES response to the AUTHENTICATE command.
// untagged CAPABILITIES response to the LOGIN command.
if (engine.CapabilitiesVersion == capabilitiesVersion)
await engine.QueryCapabilitiesAsync (cancellationToken).ConfigureAwait (false);

await OnAuthenticatedAsync (ic.ResponseText ?? string.Empty, cancellationToken).ConfigureAwait (false);
return;
}

CheckCanLogin (ic);

// fall back to the classic LOGIN command...
cred = credentials.GetCredential (uri, "DEFAULT");

ic = engine.QueueCommand (cancellationToken, null, "LOGIN %S %S\r\n", cred.UserName, cred.Password);

detector.IsAuthenticating = true;

try {
await engine.RunAsync (ic).ConfigureAwait (false);
} finally {
detector.IsAuthenticating = false;
}

if (ic.Response != ImapCommandResponse.Ok)
throw CreateAuthenticationException (ic);

engine.State = ImapEngineState.Authenticated;

id = GetSessionIdentifier (cred.UserName);
if (id != identifier) {
engine.FolderCache.Clear ();
identifier = id;
} catch (Exception ex) {
operation.SetError (ex);
throw;
}

// Query the CAPABILITIES again if the server did not include an
// untagged CAPABILITIES response to the LOGIN command.
if (engine.CapabilitiesVersion == capabilitiesVersion)
await engine.QueryCapabilitiesAsync (cancellationToken).ConfigureAwait (false);

await OnAuthenticatedAsync (ic.ResponseText ?? string.Empty, cancellationToken).ConfigureAwait (false);
}

async Task SslHandshakeAsync (SslStream ssl, string host, CancellationToken cancellationToken)
Expand Down Expand Up @@ -551,9 +565,9 @@ async Task PostConnectAsync (Stream stream, string host, int port, SecureSocketO
throw ImapCommandException.Create ("STARTTLS", ic);
}
}
} catch {
} catch (Exception ex) {
secure = false;
engine.Disconnect ();
engine.Disconnect (ex);
throw;
} finally {
connecting = false;
Expand Down Expand Up @@ -639,30 +653,37 @@ public override async Task ConnectAsync (string host, int port = 0, SecureSocket

ComputeDefaultValues (host, ref port, ref options, out var uri, out var starttls);

var stream = await ConnectNetworkAsync (host, port, cancellationToken).ConfigureAwait (false);
stream.WriteTimeout = timeout;
stream.ReadTimeout = timeout;
using var operation = engine.StartNetworkOperation (NetworkOperation.Connect);

engine.Uri = uri;
try {
var stream = await ConnectNetworkAsync (host, port, cancellationToken).ConfigureAwait (false);
stream.WriteTimeout = timeout;
stream.ReadTimeout = timeout;

if (options == SecureSocketOptions.SslOnConnect) {
var ssl = new SslStream (stream, false, ValidateRemoteCertificate);
engine.Uri = uri;

try {
await SslHandshakeAsync (ssl, host, cancellationToken).ConfigureAwait (false);
} catch (Exception ex) {
ssl.Dispose ();
if (options == SecureSocketOptions.SslOnConnect) {
var ssl = new SslStream (stream, false, ValidateRemoteCertificate);

try {
await SslHandshakeAsync (ssl, host, cancellationToken).ConfigureAwait (false);
} catch (Exception ex) {
ssl.Dispose ();

throw SslHandshakeException.Create (ref sslValidationInfo, ex, false, "IMAP", host, port, 993, 143);
}

throw SslHandshakeException.Create (ref sslValidationInfo, ex, false, "IMAP", host, port, 993, 143);
secure = true;
stream = ssl;
} else {
secure = false;
}

secure = true;
stream = ssl;
} else {
secure = false;
await PostConnectAsync (stream, host, port, options, starttls, cancellationToken).ConfigureAwait (false);
} catch (Exception ex) {
operation.SetError (ex);
throw;
}

await PostConnectAsync (stream, host, port, options, starttls, cancellationToken).ConfigureAwait (false);
}

/// <summary>
Expand Down Expand Up @@ -804,36 +825,43 @@ public override async Task ConnectAsync (Stream stream, string host, int port =
{
CheckCanConnect (stream, host, port);

Stream network;

ComputeDefaultValues (host, ref port, ref options, out var uri, out var starttls);

engine.Uri = uri;
using var operation = engine.StartNetworkOperation (NetworkOperation.Connect);

if (options == SecureSocketOptions.SslOnConnect) {
var ssl = new SslStream (stream, false, ValidateRemoteCertificate);
try {
Stream network;

try {
await SslHandshakeAsync (ssl, host, cancellationToken).ConfigureAwait (false);
} catch (Exception ex) {
ssl.Dispose ();
engine.Uri = uri;

if (options == SecureSocketOptions.SslOnConnect) {
var ssl = new SslStream (stream, false, ValidateRemoteCertificate);

throw SslHandshakeException.Create (ref sslValidationInfo, ex, false, "IMAP", host, port, 993, 143);
try {
await SslHandshakeAsync (ssl, host, cancellationToken).ConfigureAwait (false);
} catch (Exception ex) {
ssl.Dispose ();

throw SslHandshakeException.Create (ref sslValidationInfo, ex, false, "IMAP", host, port, 993, 143);
}

network = ssl;
secure = true;
} else {
network = stream;
secure = false;
}

network = ssl;
secure = true;
} else {
network = stream;
secure = false;
}
if (network.CanTimeout) {
network.WriteTimeout = timeout;
network.ReadTimeout = timeout;
}

if (network.CanTimeout) {
network.WriteTimeout = timeout;
network.ReadTimeout = timeout;
await PostConnectAsync (network, host, port, options, starttls, cancellationToken).ConfigureAwait (false);
} catch (Exception ex) {
operation.SetError (ex);
throw;
}

await PostConnectAsync (network, host, port, options, starttls, cancellationToken).ConfigureAwait (false);
}

/// <summary>
Expand Down Expand Up @@ -871,7 +899,7 @@ public override async Task DisconnectAsync (bool quit, CancellationToken cancell

disconnecting = true;

engine.Disconnect ();
engine.Disconnect (null);
}

/// <summary>
Expand Down
Loading

0 comments on commit bd0e733

Please sign in to comment.