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