diff --git a/Mechanisms/SaslDigestMd5.cs b/Mechanisms/SaslDigestMd5.cs
index 78749af..3c34a05 100644
--- a/Mechanisms/SaslDigestMd5.cs
+++ b/Mechanisms/SaslDigestMd5.cs
@@ -78,6 +78,20 @@ string Password {
}
}
+ ///
+ /// The protocol to use. If none is specified, this
+ /// implementation uses "imap".
+ ///
+ string Protocol {
+ get {
+ return Properties.ContainsKey("Protocol") ?
+ Properties["Protocol"] as string : null;
+ }
+ set {
+ Properties["Protocol"] = value;
+ }
+ }
+
///
/// Private constructor for use with Sasl.SaslFactory.
///
@@ -151,10 +165,22 @@ private byte[] ComputeDigestResponse(byte[] challenge) {
// Parse the challenge-string and construct the "response-value" from it.
string decoded = Encoding.ASCII.GetString(challenge);
NameValueCollection fields = ParseDigestChallenge(decoded);
- string digestUri = "imap/" + fields["realm"];
+ string protocol = Protocol;
+ if (string.IsNullOrEmpty(protocol))
+ protocol = "imap";
+ string digestUri = protocol + "/" + fields["realm"];
string responseValue = ComputeDigestResponseValue(fields, Cnonce, digestUri,
Username, Password);
+ // RFC 2831 says that qop is optional, and Java's SASL
+ // impl chokes on empty directives. Let's specify the
+ // default, which is 'auth'.
+ //
+ // javax.security.sasl.SaslException: Valueless directive found: qop
+ string qop = fields["qop"];
+ if (string.IsNullOrEmpty(qop))
+ qop = "auth";
+
// Create the challenge-response string.
string[] directives = new string[] {
// We don't use UTF-8 in the current implementation.
@@ -166,7 +192,7 @@ private byte[] ComputeDigestResponse(byte[] challenge) {
"cnonce=" + Dquote(Cnonce),
"digest-uri=" + Dquote(digestUri),
"response=" + responseValue,
- "qop=" + fields["qop"]
+ "qop=" + qop
};
string challengeResponse = String.Join(",", directives);
// Finally, return the response as a byte array.
@@ -229,10 +255,14 @@ private static string ComputeDigestResponseValue(NameValueCollection challenge,
cnonce;
// Construct A2.
string A2 = "AUTHENTICATE:" + digestUri;
- if (!"auth".Equals(challenge["qop"]))
+ string qop = challenge["qop"];
+ // RFC 2831: If not present, [qop] defaults to "auth".
+ if (string.IsNullOrEmpty(qop))
+ qop = "auth";
+ if (!"auth".Equals(qop))
A2 = A2 + ":00000000000000000000000000000000";
string ret = MD5(A1, enc) + ":" + challenge["nonce"] + ":" + ncValue +
- ":" + cnonce + ":" + challenge["qop"] + ":" + MD5(A2, enc);
+ ":" + cnonce + ":" + qop + ":" + MD5(A2, enc);
return MD5(ret, enc);
}
}