using S22.Sasl.Mechanisms.Ntlm; using System; namespace S22.Sasl.Mechanisms { /// /// Implements the Sasl NTLM authentication method which is used in various /// Microsoft network protocol implementations. /// /// Implemented with the help of the excellent documentation on /// NTLM composed by Eric Glass. internal class SaslNtlm : SaslMechanism { protected bool completed = false; /// /// NTLM involves several steps. /// protected int step = 0; /// /// Client sends the first message in the authentication exchange. /// public override bool HasInitial { get { return true; } } /// /// True if the authentication exchange between client and server /// has been completed. /// public override bool IsCompleted { get { return completed; } } /// /// The IANA name for the NTLM authentication mechanism. /// public override string Name { get { return "NTLM"; } } /// /// The username to authenticate with. /// protected string Username { get { return Properties.ContainsKey("Username") ? Properties["Username"] as string : null; } set { Properties["Username"] = value; } } /// /// The password to authenticate with. /// protected string Password { get { return Properties.ContainsKey("Password") ? Properties["Password"] as string : null; } set { Properties["Password"] = value; } } /// /// Private constructor for use with Sasl.SaslFactory. /// protected SaslNtlm() { // Nothing to do here. } /// /// Creates and initializes a new instance of the SaslNtlm class /// using the specified username and password. /// /// The username to authenticate with. /// The plaintext password to authenticate /// with. /// Thrown if the username /// or the password parameter is null. /// Thrown if the username /// parameter is empty. public SaslNtlm(string username, string password) { username.ThrowIfNull("username"); if (username == String.Empty) throw new ArgumentException("The username must not be empty."); password.ThrowIfNull("password"); Username = username; Password = password; } /// /// Computes the client response to the specified NTLM challenge. /// /// The challenge sent by the server /// The response to the NTLM challenge. /// Thrown if the response could not /// be computed. protected override byte[] ComputeResponse(byte[] challenge) { if (step == 1) completed = true; byte[] ret = step == 0 ? ComputeInitialResponse(challenge) : ComputeChallengeResponse(challenge); step = step + 1; return ret; } /// /// Computes the initial client response to an NTLM challenge. /// /// The challenge sent by the server. Since /// NTLM expects an initial client response, this will usually be /// empty. /// The initial response to the NTLM challenge. /// Thrown if the response could not /// be computed. protected byte[] ComputeInitialResponse(byte[] challenge) { try { string domain = Properties.ContainsKey("Domain") ? Properties["Domain"] as string : "domain"; string workstation = Properties.ContainsKey("Workstation") ? Properties["Workstation"] as string : "workstation"; Type1Message msg = new Type1Message(domain, workstation); return msg.Serialize(); } catch (Exception e) { throw new SaslException("The initial client response could not " + "be computed.", e); } } /// /// Computes the actual challenge response to an NTLM challenge /// which is sent as part of an NTLM type 2 message. /// /// The challenge sent by the server. /// The response to the NTLM challenge. /// Thrown if the challenge /// response could not be computed. protected byte[] ComputeChallengeResponse(byte[] challenge) { try { Type2Message msg = Type2Message.Deserialize(challenge); byte[] data = new Type3Message(Username, Password, msg.Challenge, "Workstation").Serialize(); return data; } catch (Exception e) { throw new SaslException("The challenge response could not be " + "computed.", e); } } } }