jstedfast/MailKit · error · SaslException
MissingChallenge
MissingChallenge
Error message
Server response did not contain any authentication data.
What it means
In the NTLM SASL mechanism's Challenge step, after sending the NEGOTIATE message the client expects the server's CHALLENGE (TYPE 2) message. If the server replies with empty/absent authentication data (token == null), SaslException with SaslErrorCode.MissingChallenge is thrown. NTLM authentication cannot continue without the server challenge.
Solutions
- Log the server's raw reply to the NEGOTIATE message to see whether it sent an error instead of a challenge
- Confirm the server truly supports NTLM (check CAPABILITY/AUTH mechanisms) and that a security layer isn't required (use TLS)
- Fall back to another mechanism (e.g. PLAIN over TLS) if NTLM negotiation fails
- Catch SaslException with ErrorCode == SaslErrorCode.MissingChallenge and retry authentication with a different mechanism
Example fix
// before
client.Authenticate (new SaslMechanismNtlm ("user", "pass")); // server aborts NTLM
// after
try {
client.Authenticate (new SaslMechanismNtlm ("user", "pass"));
} catch (SaslException ex) when (ex.ErrorCode == SaslErrorCode.MissingChallenge) {
client.Authenticate (new SaslMechanismLogin ("user", "pass")); // over TLS
} Defensive patterns
Strategy: try-catch
Validate before calling
// Verify the server actually advertises NTLM before attempting it:
if (!client.AuthenticationMechanisms.Contains ("NTLM"))
useFallback = true; Try / catch
try {
client.Authenticate (new SaslMechanismNtlm ("user", "pass"));
} catch (SaslException ex) when (ex.ErrorCode == SaslErrorCode.MissingChallenge) {
// server sent no NTLM challenge; fall back to PLAIN over TLS
} Prevention
- Check client.AuthenticationMechanisms before choosing NTLM
- Use TLS so a fallback to PLAIN is safe
- Enable protocol logging to inspect the server's reply to NEGOTIATE
- Test NTLM with a known-good client to isolate server vs client issues
When it happens
Trigger: Calling Challenge() on SaslMechanismNtlm when state == LoginState.Challenge and token is null — i.e. the server responded to the NTLM NEGOTIATE with no data (e.g. an empty continuation).
Common situations: Servers that advertise NTLM but abort the exchange, protocol servers replying with an empty continuation line instead of the base64 TYPE 2 message, or middleware stripping the challenge.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- MissingChallenge
- IncompleteChallenge
- InvalidChallenge
- message
- Value cannot be null. (Parameter 'encoding')
AI-assisted analysis of jstedfast/MailKit@9d3859a785 (2026-09-15).
Data as JSON: /api/errors/967b216de9e53615.
Report an issue: GitHub.
Appendix: source
Thrown at MailKit/Security/SaslMechanismNtlm.cs:297
} else {
if ((index = userName.IndexOf ('\\')) == -1)
index = userName.IndexOf ('/');
if (index >= 0) {
domain = userName.Substring (0, index);
userName = userName.Substring (index + 1);
}
}
}
switch (state) {
case LoginState.Negotiate:
message = negotiate = new NtlmNegotiateMessage (domain, Workstation, OSVersion);
state = LoginState.Challenge;
break;
case LoginState.Challenge:
if (token == null)
throw new SaslException (MechanismName, SaslErrorCode.MissingChallenge, "Server response did not contain any authentication data.");
var password = Credentials.Password;
message = GetChallengeResponse (domain, userName, password, token, startIndex, length);
IsAuthenticated = true;
break;
}
return message?.Encode ();
}
NtlmAuthenticateMessage GetChallengeResponse (string domain, string userName, string password, byte[] token, int startIndex, int length)
{
var challenge = new NtlmChallengeMessage (token, startIndex, length);
var authenticate = new NtlmAuthenticateMessage (negotiate!, challenge, userName, password, domain, Workstation) {
ClientChallenge = Nonce,
Timestamp = Timestamp
};
byte[]? channelBindingToken = null;View on GitHub (pinned to 9d3859a785)