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); } }