public override void Authenticate (Encoding encoding, ICredentials credentials, CancellationToken cancellationToken = default (CancellationToken))
{
if (encoding == null)
throw new ArgumentNullException (nameof (encoding));
if (credentials == null)
throw new ArgumentNullException (nameof (credentials));
CheckDisposed ();
CheckConnected ();
if (engine.State >= ImapEngineState.Authenticated)
throw new InvalidOperationException ("The ImapClient is already authenticated.");
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.AuthMechanismRank) {
if (!engine.AuthenticationMechanisms.Contains (authmech))
continue;
if ((sasl = SaslMechanism.Create (authmech, uri, encoding, credentials)) == null)
continue;
cancellationToken.ThrowIfCancellationRequested ();
var command = string.Format ("AUTHENTICATE {0}", sasl.MechanismName);
if ((engine.Capabilities & ImapCapabilities.SaslIR) != 0 && sasl.SupportsInitialResponse) {
var ir = sasl.Challenge (null);
command += " " + ir + "\r\n";
} else {
command += "\r\n";
}
ic = engine.QueueCommand (cancellationToken, null, command);
ic.ContinuationHandler = (imap, cmd, text) => {
string challenge;
if (sasl.IsAuthenticated) {
// The server claims we aren't done authenticating, but our SASL mechanism thinks we are...
// Send an empty string to abort the AUTHENTICATE command.
challenge = string.Empty;
} else {
challenge = sasl.Challenge (text);
}
var buf = Encoding.ASCII.GetBytes (challenge + "\r\n");
imap.Stream.Write (buf, 0, buf.Length, cmd.CancellationToken);
imap.Stream.Flush (cmd.CancellationToken);
};
engine.Wait (ic);
if (ic.Response != ImapCommandResponse.Ok) {
for (int i = 0; i < ic.RespCodes.Count; i++) {
if (ic.RespCodes[i].Type != ImapResponseCodeType.Alert)
continue;
OnAlert (ic.RespCodes[i].Message);
throw new AuthenticationException (ic.ResponseText ?? ic.RespCodes[i].Message);
}
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)
engine.QueryCapabilities (cancellationToken);
engine.QueryNamespaces (cancellationToken);
engine.QuerySpecialFolders (cancellationToken);
OnAuthenticated (ic.ResponseText);
return;
}
if ((Capabilities & ImapCapabilities.LoginDisabled) != 0) {
if (ic == null)
throw new AuthenticationException ("The LOGIN command is disabled.");
throw CreateAuthenticationException (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);
engine.Wait (ic);
ProcessResponseCodes (ic);
if (ic.Response != ImapCommandResponse.Ok)
throw CreateAuthenticationException (ic);
engine.State = ImapEngineState.Authenticated;
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 LOGIN command.
if (engine.CapabilitiesVersion == capabilitiesVersion)
engine.QueryCapabilities (cancellationToken);
engine.QueryNamespaces (cancellationToken);
engine.QuerySpecialFolders (cancellationToken);
OnAuthenticated (ic.ResponseText);
}