Example #1
0
void
mschap_auth_response(u_int8_t *password, int passwordlen,
    u_int8_t *ntresponse, u_int8_t *auth_challenge, u_int8_t *peer_challenge,
    u_int8_t *username, int usernamelen, u_int8_t *auth_response)
{
	EVP_MD_CTX	 ctx;
	u_int8_t	 password_hash[MSCHAP_HASH_SZ];
	u_int8_t	 password_hash2[MSCHAP_HASH_SZ];
	u_int8_t	 challenge[MSCHAP_CHALLENGE_SZ];
	u_int8_t	 md[SHA_DIGEST_LENGTH], *ptr;
	u_int		 mdlen;
	int		 i;
	const u_int8_t	 hex[] = "0123456789ABCDEF";
	static u_int8_t	 magic1[39] = {
		0x4D, 0x61, 0x67, 0x69, 0x63, 0x20, 0x73, 0x65, 0x72, 0x76,
		0x65, 0x72, 0x20, 0x74, 0x6F, 0x20, 0x63, 0x6C, 0x69, 0x65,
		0x6E, 0x74, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x69, 0x6E, 0x67,
		0x20, 0x63, 0x6F, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x74
	};
	static u_int8_t	 magic2[41] = {
		0x50, 0x61, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x6D, 0x61, 0x6B,
		0x65, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6F, 0x20, 0x6D, 0x6F,
		0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x6E, 0x20, 0x6F, 0x6E,
		0x65, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6F,
		0x6E
	};

	mschap_ntpassword_hash(password, passwordlen, password_hash);
	mschap_ntpassword_hash(password_hash, MSCHAP_HASH_SZ, password_hash2);

	EVP_DigestInit(&ctx, EVP_sha1());
	EVP_DigestUpdate(&ctx, password_hash2, sizeof(password_hash2));
	EVP_DigestUpdate(&ctx, ntresponse, 24);
	EVP_DigestUpdate(&ctx, magic1, 39);
	EVP_DigestFinal(&ctx, md, &mdlen);

	mschap_challenge_hash(peer_challenge, auth_challenge,
	    username, usernamelen, challenge);

	EVP_DigestInit(&ctx, EVP_sha1());
	EVP_DigestUpdate(&ctx, md, sizeof(md));
	EVP_DigestUpdate(&ctx, challenge, sizeof(challenge));
	EVP_DigestUpdate(&ctx, magic2, 41);
	EVP_DigestFinal(&ctx, md, &mdlen);

	/*
	 * Encode the value of 'Digest' as "S=" followed by
	 * 40 ASCII hexadecimal digits and return it in
	 * AuthenticatorResponse.
	 * For example,
	 *   "S=0123456789ABCDEF0123456789ABCDEF01234567"
	 */
	ptr = auth_response;
	*ptr++ = 'S';
	*ptr++ = '=';
	for (i = 0; i < SHA_DIGEST_LENGTH; i++) {
		*ptr++ = hex[md[i] >> 4];
		*ptr++ = hex[md[i] & 0x0f];
	}
}
Example #2
0
/*
 *	auth_response() generates MS-CHAP v2 SUCCESS response
 *	according to RFC 2759 GenerateAuthenticatorResponse()
 *	returns 42-octet response string
 */
void mschap_auth_response(char const *username,
                          uint8_t const *nt_hash_hash,
                          uint8_t const *ntresponse,
                          uint8_t const *peer_challenge, uint8_t const *auth_challenge,
                          char *response)
{
    fr_SHA1_CTX Context;
    static const uint8_t magic1[39] =
    {   0x4D, 0x61, 0x67, 0x69, 0x63, 0x20, 0x73, 0x65, 0x72, 0x76,
        0x65, 0x72, 0x20, 0x74, 0x6F, 0x20, 0x63, 0x6C, 0x69, 0x65,
        0x6E, 0x74, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x69, 0x6E, 0x67,
        0x20, 0x63, 0x6F, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x74
    };

    static const uint8_t magic2[41] =
    {   0x50, 0x61, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x6D, 0x61, 0x6B,
        0x65, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6F, 0x20, 0x6D, 0x6F,
        0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x6E, 0x20, 0x6F, 0x6E,
        0x65, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6F,
        0x6E
    };

    static char const hex[16] = "0123456789ABCDEF";

    size_t i;
    uint8_t challenge[8];
    uint8_t digest[20];

    fr_SHA1Init(&Context);
    fr_SHA1Update(&Context, nt_hash_hash, 16);
    fr_SHA1Update(&Context, ntresponse, 24);
    fr_SHA1Update(&Context, magic1, 39);
    fr_SHA1Final(digest, &Context);
    mschap_challenge_hash(peer_challenge, auth_challenge, username, challenge);
    fr_SHA1Init(&Context);
    fr_SHA1Update(&Context, digest, 20);
    fr_SHA1Update(&Context, challenge, 8);
    fr_SHA1Update(&Context, magic2, 41);
    fr_SHA1Final(digest, &Context);

    /*
     *	Encode the value of 'Digest' as "S=" followed by
     *	40 ASCII hexadecimal digits and return it in
     *	AuthenticatorResponse.
     *	For example,
     *	"S=0123456789ABCDEF0123456789ABCDEF01234567"
     */
    response[0] = 'S';
    response[1] = '=';

    /*
     *	The hexadecimal digits [A-F] MUST be uppercase.
     */
    for (i = 0; i < sizeof(digest); i++) {
        response[2 + (i * 2)] = hex[(digest[i] >> 4) & 0x0f];
        response[3 + (i * 2)] = hex[digest[i] & 0x0f];
    }
}
Example #3
0
void
mschap_nt_response(u_int8_t *auth_challenge, u_int8_t *peer_challenge,
    u_int8_t *username, int usernamelen, u_int8_t *password, int passwordlen,
    u_int8_t *response)
{
	u_int8_t challenge[MSCHAP_CHALLENGE_SZ];
	u_int8_t password_hash[MSCHAP_HASH_SZ];

	mschap_challenge_hash(peer_challenge, auth_challenge,
	    username, usernamelen, challenge);

	mschap_ntpassword_hash(password, passwordlen, password_hash);
	mschap_challenge_response(challenge, password_hash, response);
}
/*
 *	mschap_authenticate() - authenticate user based on given
 *	attributes and configuration.
 *	We will try to find out password in configuration
 *	or in configured passwd file.
 *	If one is found we will check paraneters given by NAS.
 *
 *	If PW_SMB_ACCOUNT_CTRL is not set to ACB_PWNOTREQ we must have
 *	one of:
 *		PAP:      PW_USER_PASSWORD or
 *		MS-CHAP:  PW_MSCHAP_CHALLENGE and PW_MSCHAP_RESPONSE or
 *		MS-CHAP2: PW_MSCHAP_CHALLENGE and PW_MSCHAP2_RESPONSE
 *	In case of password mismatch or locked account we MAY return
 *	PW_MSCHAP_ERROR for MS-CHAP or MS-CHAP v2
 *	If MS-CHAP2 succeeds we MUST return
 *	PW_MSCHAP2_SUCCESS
 */
static int mschap_authenticate(void * instance, REQUEST *request)
{
#define inst ((rlm_mschap_t *)instance)
	VALUE_PAIR *challenge = NULL;
	VALUE_PAIR *response = NULL;
	VALUE_PAIR *password = NULL;
	VALUE_PAIR *lm_password, *nt_password, *smb_ctrl;
	VALUE_PAIR *username;
	uint8_t nthashhash[16];
	char msch2resp[42];
	char *username_string;
	int chap = 0;
	int		do_ntlm_auth;

	/*
	 *	If we have ntlm_auth configured, use it unless told
	 *	otherwise
	 */
	do_ntlm_auth = (inst->ntlm_auth != NULL);

	/*
	 *	If we have an ntlm_auth configuration, then we may
	 *	want to suppress it.
	 */
	if (do_ntlm_auth) {
		VALUE_PAIR *vp = pairfind(request->config_items,
					  PW_MS_CHAP_USE_NTLM_AUTH);
		if (vp) do_ntlm_auth = vp->vp_integer;
	}

	/*
	 *	Find the SMB-Account-Ctrl attribute, or the
	 *	SMB-Account-Ctrl-Text attribute.
	 */
	smb_ctrl = pairfind(request->config_items, PW_SMB_ACCOUNT_CTRL);
	if (!smb_ctrl) {
		password = pairfind(request->config_items,
				    PW_SMB_ACCOUNT_CTRL_TEXT);
		if (password) {
			smb_ctrl = radius_pairmake(request,
						   &request->config_items,
						   "SMB-Account-CTRL", "0",
						   T_OP_SET);
			if (smb_ctrl) {
				smb_ctrl->vp_integer = pdb_decode_acct_ctrl(password->vp_strvalue);
			}
		}
	}

	/*
	 *	We're configured to do MS-CHAP authentication.
	 *	and account control information exists.  Enforce it.
	 */
	if (smb_ctrl) {
		/*
		 *	Password is not required.
		 */
		if ((smb_ctrl->vp_integer & ACB_PWNOTREQ) != 0) {
			RDEBUG2("SMB-Account-Ctrl says no password is required.");
			return RLM_MODULE_OK;
		}
	}

	/*
	 *	Decide how to get the passwords.
	 */
	password = pairfind(request->config_items, PW_CLEARTEXT_PASSWORD);

	/*
	 *	We need an LM-Password.
	 */
	lm_password = pairfind(request->config_items, PW_LM_PASSWORD);
	if (lm_password) {
		/*
		 *	Allow raw octets.
		 */
		if ((lm_password->length == 16) ||
		    ((lm_password->length == 32) &&
		     (fr_hex2bin(lm_password->vp_strvalue,
				 lm_password->vp_octets, 16) == 16))) {
			RDEBUG2("Found LM-Password");
			lm_password->length = 16;

		} else {
			radlog_request(L_ERR, 0, request, "Invalid LM-Password");
			lm_password = NULL;
		}

	} else if (!password) {
		if (!do_ntlm_auth) RDEBUG2("No Cleartext-Password configured.  Cannot create LM-Password.");

	} else {		/* there is a configured Cleartext-Password */
		lm_password = radius_pairmake(request, &request->config_items,
					      "LM-Password", "", T_OP_EQ);
		if (!lm_password) {
			radlog_request(L_ERR, 0, request, "No memory");
		} else {
			smbdes_lmpwdhash(password->vp_strvalue,
					 lm_password->vp_octets);
			lm_password->length = 16;
		}
	}

	/*
	 *	We need an NT-Password.
	 */
	nt_password = pairfind(request->config_items, PW_NT_PASSWORD);
	if (nt_password) {
		if ((nt_password->length == 16) ||
		    ((nt_password->length == 32) &&
		     (fr_hex2bin(nt_password->vp_strvalue,
				 nt_password->vp_octets, 16) == 16))) {
			RDEBUG2("Found NT-Password");
			nt_password->length = 16;

                } else {
			radlog_request(L_ERR, 0, request, "Invalid NT-Password");
			nt_password = NULL;
		}
	} else if (!password) {
		if (!do_ntlm_auth) RDEBUG2("No Cleartext-Password configured.  Cannot create NT-Password.");

	} else {		/* there is a configured Cleartext-Password */
		nt_password = radius_pairmake(request, &request->config_items,
					      "NT-Password", "", T_OP_EQ);
		if (!nt_password) {
			radlog_request(L_ERR, 0, request, "No memory");
			return RLM_MODULE_FAIL;
		} else {
			mschap_ntpwdhash(nt_password->vp_octets,
				  password->vp_strvalue);
			nt_password->length = 16;
		}
	}

	challenge = pairfind(request->packet->vps, PW_MSCHAP_CHALLENGE);
	if (!challenge) {
		RDEBUG("ERROR: You set 'Auth-Type = MS-CHAP' for a request that does not contain any MS-CHAP attributes!");
		return RLM_MODULE_REJECT;
	}

	/*
	 *	We also require an MS-CHAP-Response.
	 */
	response = pairfind(request->packet->vps, PW_MSCHAP_RESPONSE);

	/*
	 *	MS-CHAP-Response, means MS-CHAPv1
	 */
	if (response) {
		int offset;

		/*
		 *	MS-CHAPv1 challenges are 8 octets.
		 */
		if (challenge->length < 8) {
			radlog_request(L_AUTH, 0, request, "MS-CHAP-Challenge has the wrong format.");
			return RLM_MODULE_INVALID;
		}

		/*
		 *	Responses are 50 octets.
		 */
		if (response->length < 50) {
			radlog_request(L_AUTH, 0, request, "MS-CHAP-Response has the wrong format.");
			return RLM_MODULE_INVALID;
		}

		/*
		 *	We are doing MS-CHAP.  Calculate the MS-CHAP
		 *	response
		 */
		if (response->vp_octets[1] & 0x01) {
			RDEBUG2("Client is using MS-CHAPv1 with NT-Password");
			password = nt_password;
			offset = 26;
		} else {
			RDEBUG2("Client is using MS-CHAPv1 with LM-Password");
			password = lm_password;
			offset = 2;
		}

		/*
		 *	Do the MS-CHAP authentication.
		 */
		if (do_mschap(inst, request, password, challenge->vp_octets,
			      response->vp_octets + offset, nthashhash,
			      do_ntlm_auth) < 0) {
			RDEBUG2("MS-CHAP-Response is incorrect.");
			goto do_error;
		}

		chap = 1;

	} else if ((response = pairfind(request->packet->vps, PW_MSCHAP2_RESPONSE)) != NULL) {
		uint8_t	mschapv1_challenge[16];
		VALUE_PAIR *name_attr, *response_name;

		/*
		 *	MS-CHAPv2 challenges are 16 octets.
		 */
		if (challenge->length < 16) {
			radlog_request(L_AUTH, 0, request, "MS-CHAP-Challenge has the wrong format.");
			return RLM_MODULE_INVALID;
		}

		/*
		 *	Responses are 50 octets.
		 */
		if (response->length < 50) {
			radlog_request(L_AUTH, 0, request, "MS-CHAP-Response has the wrong format.");
			return RLM_MODULE_INVALID;
		}

		/*
		 *	We also require a User-Name
		 */
		username = pairfind(request->packet->vps, PW_USER_NAME);
		if (!username) {
			radlog_request(L_AUTH, 0, request, "We require a User-Name for MS-CHAPv2");
			return RLM_MODULE_INVALID;
		}

		/*
		 *      Check for MS-CHAP-User-Name and if found, use it
		 *      to construct the MSCHAPv1 challenge.  This is
		 *      set by rlm_eap_mschap to the MS-CHAP Response
		 *      packet Name field.
		 *
		 *	We prefer this to the User-Name in the
		 *	packet.
		 */
		response_name = pairfind(request->packet->vps, PW_MS_CHAP_USER_NAME);
		if (response_name) {
			name_attr = response_name;
		} else {
			name_attr = username;
		}
		
		/*
		 *	with_ntdomain_hack moved here, too.
		 */
		if ((username_string = strchr(name_attr->vp_strvalue, '\\')) != NULL) {
			if (inst->with_ntdomain_hack) {
				username_string++;
			} else {
				RDEBUG2("NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
				username_string = name_attr->vp_strvalue;
			}
		} else {
			username_string = name_attr->vp_strvalue;
		}
		
		if (response_name &&
		    ((username->length != response_name->length) ||
		     (strncasecmp(username->vp_strvalue, response_name->vp_strvalue, username->length) != 0))) {
			RDEBUG("ERROR: User-Name (%s) is not the same as MS-CHAP Name (%s) from EAP-MSCHAPv2", username->vp_strvalue, response_name->vp_strvalue);
			return RLM_MODULE_REJECT;
		}

		/*
		 *	The old "mschapv2" function has been moved to
		 *	here.
		 *
		 *	MS-CHAPv2 takes some additional data to create an
		 *	MS-CHAPv1 challenge, and then does MS-CHAPv1.
		 */
		RDEBUG2("Creating challenge hash with username: %s",
			username_string);
		mschap_challenge_hash(response->vp_octets + 2, /* peer challenge */
			       challenge->vp_octets, /* our challenge */
			       username_string,	/* user name */
			       mschapv1_challenge); /* resulting challenge */

		RDEBUG2("Client is using MS-CHAPv2 for %s, we need NT-Password",
		       username_string);

#ifdef __APPLE__
		if (inst->open_directory) {
			return do_od_mschap(request, response, challenge, username_string);
		}
#endif

		if (do_mschap(inst, request, nt_password, mschapv1_challenge,
			      response->vp_octets + 26, nthashhash,
			      do_ntlm_auth) < 0) {
			int i;
			char buffer[128];

			RDEBUG2("FAILED: MS-CHAP2-Response is incorrect");

		do_error:
			snprintf(buffer, sizeof(buffer), "E=691 R=%d",
				 inst->allow_retry);

			if (inst->retry_msg) {
				snprintf(buffer + 9, sizeof(buffer) - 9, " C=");
				for (i = 0; i < 16; i++) {
					snprintf(buffer + 12 + i*2,
						 sizeof(buffer) - 12 - i*2, "%02x",
						 fr_rand() & 0xff);
				}
				snprintf(buffer + 44, sizeof(buffer) - 44,
					 " V=3 M=%s", inst->retry_msg);
			}
			mschap_add_reply(request, &request->reply->vps,
					 *response->vp_octets, "MS-CHAP-Error",
					 buffer, strlen(buffer));
			return RLM_MODULE_REJECT;
		}

		mschap_auth_response(username_string, /* without the domain */
			      nthashhash, /* nt-hash-hash */
			      response->vp_octets + 26, /* peer response */
			      response->vp_octets + 2, /* peer challenge */
			      challenge->vp_octets, /* our challenge */
			      msch2resp); /* calculated MPPE key */
		mschap_add_reply(request, &request->reply->vps, *response->vp_octets,
				 "MS-CHAP2-Success", msch2resp, 42);
		chap = 2;

	} else {		/* Neither CHAPv1 or CHAPv2 response: die */
		RDEBUG("ERROR: You set 'Auth-Type = MS-CHAP' for a request that does not contain any MS-CHAP attributes!");
		return RLM_MODULE_INVALID;
	}

	/*
	 *	We have a CHAP response, but the account may be
	 *	disabled.  Reject the user with the same error code
	 *	we use when their password is invalid.
	 */
	if (smb_ctrl) {
		/*
		 *	Account is disabled.
		 *
		 *	They're found, but they don't exist, so we
		 *	return 'not found'.
		 */
		if (((smb_ctrl->vp_integer & ACB_DISABLED) != 0) ||
		    ((smb_ctrl->vp_integer & (ACB_NORMAL|ACB_WSTRUST)) == 0)) {
			RDEBUG2("SMB-Account-Ctrl says that the account is disabled, or is not a normal or workstatin trust account.");
			mschap_add_reply(request, &request->reply->vps,
					  *response->vp_octets,
					  "MS-CHAP-Error", "E=691 R=1", 9);
			return RLM_MODULE_NOTFOUND;
		}

		/*
		 *	User is locked out.
		 */
		if ((smb_ctrl->vp_integer & ACB_AUTOLOCK) != 0) {
			RDEBUG2("SMB-Account-Ctrl says that the account is locked out.");
			mschap_add_reply(request, &request->reply->vps,
					  *response->vp_octets,
					  "MS-CHAP-Error", "E=647 R=0", 9);
			return RLM_MODULE_USERLOCK;
		}
	}

	/* now create MPPE attributes */
	if (inst->use_mppe) {
		uint8_t mppe_sendkey[34];
		uint8_t mppe_recvkey[34];

		if (chap == 1){
			RDEBUG2("adding MS-CHAPv1 MPPE keys");
			memset(mppe_sendkey, 0, 32);
			if (lm_password) {
				memcpy(mppe_sendkey, lm_password->vp_octets, 8);
			}

			/*
			 *	According to RFC 2548 we
			 *	should send NT hash.  But in
			 *	practice it doesn't work.
			 *	Instead, we should send nthashhash
			 *
			 *	This is an error on RFC 2548.
			 */
			/*
			 *	do_mschap cares to zero nthashhash if NT hash
			 *	is not available.
			 */
			memcpy(mppe_sendkey + 8,
			       nthashhash, 16);
			mppe_add_reply(request,
				       "MS-CHAP-MPPE-Keys",
				       mppe_sendkey, 32);
		} else if (chap == 2) {
			RDEBUG2("adding MS-CHAPv2 MPPE keys");
			mppe_chap2_gen_keys128(nthashhash,
					       response->vp_octets + 26,
					       mppe_sendkey, mppe_recvkey);

			mppe_add_reply(request,
				       "MS-MPPE-Recv-Key",
				       mppe_recvkey, 16);
			mppe_add_reply(request,
				       "MS-MPPE-Send-Key",
				       mppe_sendkey, 16);

		}
		radius_pairmake(request, &request->reply->vps,
				"MS-MPPE-Encryption-Policy",
				(inst->require_encryption)? "0x00000002":"0x00000001",
				T_OP_EQ);
		radius_pairmake(request, &request->reply->vps,
				"MS-MPPE-Encryption-Types",
				(inst->require_strong)? "0x00000004":"0x00000006",
				T_OP_EQ);
	} /* else we weren't asked to use MPPE */

	return RLM_MODULE_OK;
#undef inst
}
/*
 *	Does dynamic translation of strings.
 *
 *	Pulls NT-Response, LM-Response, or Challenge from MSCHAP
 *	attributes.
 */
static size_t mschap_xlat(void *instance, REQUEST *request,
		       char *fmt, char *out, size_t outlen,
		       RADIUS_ESCAPE_STRING func)
{
	size_t		i, data_len;
	uint8_t		*data = NULL;
	uint8_t		buffer[32];
	VALUE_PAIR	*user_name;
	VALUE_PAIR	*chap_challenge, *response;
	rlm_mschap_t	*inst = instance;

	response = NULL;

	func = func;		/* -Wunused */

	/*
	 *	Challenge means MS-CHAPv1 challenge, or
	 *	hash of MS-CHAPv2 challenge, and peer challenge.
	 */
	if (strncasecmp(fmt, "Challenge", 9) == 0) {
		chap_challenge = pairfind(request->packet->vps,
					  PW_MSCHAP_CHALLENGE);
		if (!chap_challenge) {
			RDEBUG2("No MS-CHAP-Challenge in the request.");
			return 0;
		}

		/*
		 *	MS-CHAP-Challenges are 8 octets,
		 *	for MS-CHAPv2
		 */
		if (chap_challenge->length == 8) {
			RDEBUG2(" mschap1: %02x",
			       chap_challenge->vp_octets[0]);
			data = chap_challenge->vp_octets;
			data_len = 8;

			/*
			 *	MS-CHAP-Challenges are 16 octets,
			 *	for MS-CHAPv2.
			 */
		} else if (chap_challenge->length == 16) {
			VALUE_PAIR *name_attr, *response_name;
			char *username_string;

			response = pairfind(request->packet->vps,
					    PW_MSCHAP2_RESPONSE);
			if (!response) {
				RDEBUG2("MS-CHAP2-Response is required to calculate MS-CHAPv1 challenge.");
				return 0;
			}

			/*
			 *	FIXME: Much of this is copied from
			 *	below.  We should put it into a
			 *	separate function.
			 */

			/*
			 *	Responses are 50 octets.
			 */
			if (response->length < 50) {
				radlog_request(L_AUTH, 0, request, "MS-CHAP-Response has the wrong format.");
				return 0;
			}

			user_name = pairfind(request->packet->vps,
					     PW_USER_NAME);
			if (!user_name) {
				RDEBUG2("User-Name is required to calculate MS-CHAPv1 Challenge.");
				return 0;
			}

 			/*
			 *      Check for MS-CHAP-User-Name and if found, use it
			 *      to construct the MSCHAPv1 challenge.  This is
			 *      set by rlm_eap_mschap to the MS-CHAP Response
			 *      packet Name field.
			 *
			 *	We prefer this to the User-Name in the
			 *	packet.
			 */
			response_name = pairfind(request->packet->vps, PW_MS_CHAP_USER_NAME);
			if (response_name) {
				name_attr = response_name;
			} else {
				name_attr = user_name;
			}

			/*
			 *	with_ntdomain_hack moved here, too.
			 */
			if ((username_string = strchr(name_attr->vp_strvalue, '\\')) != NULL) {
				if (inst->with_ntdomain_hack) {
					username_string++;
				} else {
					RDEBUG2("NT Domain delimeter found, should we have enabled with_ntdomain_hack?");
					username_string = name_attr->vp_strvalue;
				}
			} else {
				username_string = name_attr->vp_strvalue;
			}

			if (response_name &&
			    ((user_name->length != response_name->length) ||
			     (strncasecmp(user_name->vp_strvalue, response_name->vp_strvalue, user_name->length) != 0))) {
				RDEBUG("WARNING: User-Name (%s) is not the same as MS-CHAP Name (%s) from EAP-MSCHAPv2", user_name->vp_strvalue, response_name->vp_strvalue);
			}

			/*
			 *	Get the MS-CHAPv1 challenge
			 *	from the MS-CHAPv2 peer challenge,
			 *	our challenge, and the user name.
			 */
			RDEBUG2("Creating challenge hash with username: %s",
				username_string);
			mschap_challenge_hash(response->vp_octets + 2,
				       chap_challenge->vp_octets,
				       username_string, buffer);
			data = buffer;
			data_len = 8;
		} else {
			RDEBUG2("Invalid MS-CHAP challenge length");
			return 0;
		}

		/*
		 *	Get the MS-CHAPv1 response, or the MS-CHAPv2
		 *	response.
		 */
	} else if (strncasecmp(fmt, "NT-Response", 11) == 0) {
		response = pairfind(request->packet->vps,
				    PW_MSCHAP_RESPONSE);
		if (!response) response = pairfind(request->packet->vps,
						   PW_MSCHAP2_RESPONSE);
		if (!response) {
			RDEBUG2("No MS-CHAP-Response or MS-CHAP2-Response was found in the request.");
			return 0;
		}

		/*
		 *	For MS-CHAPv1, the NT-Response exists only
		 *	if the second octet says so.
		 */
		if ((response->attribute == PW_MSCHAP_RESPONSE) &&
		    ((response->vp_octets[1] & 0x01) == 0)) {
			RDEBUG2("No NT-Response in MS-CHAP-Response");
			return 0;
		}

		/*
		 *	MS-CHAP-Response and MS-CHAP2-Response have
		 *	the NT-Response at the same offset, and are
		 *	the same length.
		 */
		data = response->vp_octets + 26;
		data_len = 24;

		/*
		 *	LM-Response is deprecated, and exists only
		 *	in MS-CHAPv1, and not often there.
		 */
	} else if (strncasecmp(fmt, "LM-Response", 11) == 0) {
		response = pairfind(request->packet->vps,
				    PW_MSCHAP_RESPONSE);
		if (!response) {
			RDEBUG2("No MS-CHAP-Response was found in the request.");
			return 0;
		}

		/*
		 *	For MS-CHAPv1, the NT-Response exists only
		 *	if the second octet says so.
		 */
		if ((response->vp_octets[1] & 0x01) != 0) {
			RDEBUG2("No LM-Response in MS-CHAP-Response");
			return 0;
		}
		data = response->vp_octets + 2;
		data_len = 24;

		/*
		 *	Pull the NT-Domain out of the User-Name, if it exists.
		 */
	} else if (strncasecmp(fmt, "NT-Domain", 9) == 0) {
		char *p, *q;

		user_name = pairfind(request->packet->vps, PW_USER_NAME);
		if (!user_name) {
			RDEBUG2("No User-Name was found in the request.");
			return 0;
		}

		/*
		 *	First check to see if this is a host/ style User-Name
		 *	(a la Kerberos host principal)
		 */
		if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
			/*
			 *	If we're getting a User-Name formatted in this way,
			 *	it's likely due to PEAP.  The Windows Domain will be
			 *	the first domain component following the hostname,
			 *	or the machine name itself if only a hostname is supplied
			 */
			p = strchr(user_name->vp_strvalue, '.');
			if (!p) {
				RDEBUG2("setting NT-Domain to same as machine name");
				strlcpy(out, user_name->vp_strvalue + 5, outlen);
			} else {
				p++;	/* skip the period */
				q = strchr(p, '.');
				/*
				 * use the same hack as below
				 * only if another period was found
				 */
				if (q) *q = '\0';
				strlcpy(out, p, outlen);
				if (q) *q = '.';
			}
		} else {
			p = strchr(user_name->vp_strvalue, '\\');
			if (!p) {
				RDEBUG2("No NT-Domain was found in the User-Name.");
				return 0;
			}

			/*
			 *	Hack.  This is simpler than the alternatives.
			 */
			*p = '\0';
			strlcpy(out, user_name->vp_strvalue, outlen);
			*p = '\\';
		}

		return strlen(out);

		/*
		 *	Pull the User-Name out of the User-Name...
		 */
	} else if (strncasecmp(fmt, "User-Name", 9) == 0) {
		char *p;

		user_name = pairfind(request->packet->vps, PW_USER_NAME);
		if (!user_name) {
			RDEBUG2("No User-Name was found in the request.");
			return 0;
		}

		/*
		 *	First check to see if this is a host/ style User-Name
		 *	(a la Kerberos host principal)
		 */
		if (strncmp(user_name->vp_strvalue, "host/", 5) == 0) {
			/*
			 *	If we're getting a User-Name formatted in this way,
			 *	it's likely due to PEAP.  When authenticating this against
			 *	a Domain, Windows will expect the User-Name to be in the
			 *	format of hostname$, the SAM version of the name, so we
			 *	have to convert it to that here.  We do so by stripping
			 *	off the first 5 characters (host/), and copying everything
			 *	from that point to the first period into a string and appending
			 * 	a $ to the end.
			 */
			p = strchr(user_name->vp_strvalue, '.');
			/*
			 * use the same hack as above
			 * only if a period was found
			 */
			if (p) *p = '\0';
			snprintf(out, outlen, "%s$", user_name->vp_strvalue + 5);
			if (p) *p = '.';
		} else {
			p = strchr(user_name->vp_strvalue, '\\');
			if (p) {
				p++;	/* skip the backslash */
			} else {
				p = user_name->vp_strvalue; /* use the whole User-Name */
			}
			strlcpy(out, p, outlen);
		}

		return strlen(out);

		/*
		 * Return the NT-Hash of the passed string
		 */
	} else if (strncasecmp(fmt, "NT-Hash ", 8) == 0) {
		char *p;
		char buf2[1024];

		p = fmt + 8;	/* 7 is the length of 'NT-Hash' */
		if ((p == '\0')	 || (outlen <= 32))
			return 0;

		while (isspace(*p)) p++;

		if (!radius_xlat(buf2, sizeof(buf2),p,request,NULL)) {
			RDEBUG("xlat failed");
			*buffer = '\0';
			return 0;
		}

		mschap_ntpwdhash(buffer,buf2);

		fr_bin2hex(buffer, out, 16);
		out[32] = '\0';
		RDEBUG("NT-Hash of %s = %s", buf2, out);
		return 32;

		/*
		 * Return the LM-Hash of the passed string
		 */
	} else if (strncasecmp(fmt, "LM-Hash ", 8) == 0) {
		char *p;
		char buf2[1024];

		p = fmt + 8;	/* 7 is the length of 'LM-Hash' */
		if ((p == '\0') || (outlen <= 32))
			return 0;

		while (isspace(*p)) p++;

		if (!radius_xlat(buf2, sizeof(buf2),p,request,NULL)) {
			RDEBUG("xlat failed");
			*buffer = '\0';
			return 0;
		}

		smbdes_lmpwdhash(buf2, buffer);
		fr_bin2hex(buffer, out, 16);
		out[32] = '\0';
		RDEBUG("LM-Hash of %s = %s", buf2, out);
		return 32;
	} else {
		RDEBUG2("Unknown expansion string \"%s\"",
		       fmt);
		return 0;
	}

	if (outlen == 0) return 0; /* nowhere to go, don't do anything */

	/*
	 *	Didn't set anything: this is bad.
	 */
	if (!data) {
		RDEBUG2("Failed to do anything intelligent");
		return 0;
	}

	/*
	 *	Check the output length.
	 */
	if (outlen < ((data_len * 2) + 1)) {
		data_len = (outlen - 1) / 2;
	}

	/*
	 *
	 */
	for (i = 0; i < data_len; i++) {
		sprintf(out + (2 * i), "%02x", data[i]);
	}
	out[data_len * 2] = '\0';

	return data_len * 2;
}