/////////////////////////////////////////////////////////////////////////////// // // Copyright (c) Microsoft Corporation // // SYNOPSIS // // Defines the class NTSamAuthentication. // /////////////////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include #include #include bool NTSamAuthentication::allowLM; STDMETHODIMP NTSamAuthentication::Initialize() { DWORD error = IASLsaInitialize(); if (error == NO_ERROR) { AccountLockoutInitialize(); } return HRESULT_FROM_WIN32(error); } STDMETHODIMP NTSamAuthentication::Shutdown() { AccountLockoutShutdown(); IASLsaUninitialize(); return S_OK; } STDMETHODIMP NTSamAuthentication::PutProperty(LONG Id, VARIANT *pValue) { if (Id == PROPERTY_NTSAM_ALLOW_LM_AUTHENTICATION && pValue != NULL && V_VT(pValue) == VT_BOOL) { allowLM = V_BOOL(pValue) ? true : false; IASTracePrintf( "Setting LM Authentication allowed to %s.", (allowLM ? "TRUE" : "FALSE") ); } return S_OK; } bool NTSamAuthentication::enforceLmRestriction( IASTL::IASRequest& request ) { if (!allowLM) { IASTraceString("LanManager authentication is not enabled."); IASProcessFailure(request, IAS_LM_NOT_ALLOWED); } return allowLM; } void NTSamAuthentication::doMsChapAuthentication( IASTL::IASRequest& request, PCWSTR domainName, PCWSTR username, BYTE identity, PBYTE challenge, PBYTE ntResponse, PBYTE lmResponse ) { DWORD status; auto_handle<> token; IAS_MSCHAP_PROFILE profile; status = IASLogonMSCHAP( username, domainName, challenge, ntResponse, lmResponse, &profile, &token ); if (status == NO_ERROR) { MSChapMPPEKeys::insert( request, profile.LanmanSessionKey, profile.UserSessionKey, challenge ); MSChapDomain::insert( request, identity, profile.LogonDomainName ); } storeLogonResult(request, status, token, profile.KickOffTime); } void NTSamAuthentication::doMsChap2Authentication( IASTL::IASRequest& request, PCWSTR domainName, PCWSTR username, BYTE identity, IAS_OCTET_STRING& challenge, PBYTE response, PBYTE peerChallenge ) { ////////// // Get the hash username. ////////// PIASATTRIBUTE attr = IASPeekAttribute( request, IAS_ATTRIBUTE_ORIGINAL_USER_NAME, IASTYPE_OCTET_STRING ); if (!attr) { attr = IASPeekAttribute( request, RADIUS_ATTRIBUTE_USER_NAME, IASTYPE_OCTET_STRING ); if (!attr) { _com_issue_error(IAS_MALFORMED_REQUEST); } } PCSTR rawUserName = IAS_OCT2ANSI(attr->Value.OctetString); PCSTR hashUserName = (PCSTR)_mbschr((const BYTE*)rawUserName, '\\'); hashUserName = hashUserName ? (hashUserName + 1) : rawUserName; ////////// // Authenticate the user. ////////// DWORD status; auto_handle<> token; IAS_MSCHAP_V2_PROFILE profile; status = IASLogonMSCHAPv2( username, domainName, hashUserName, challenge.lpValue, challenge.dwLength, response, peerChallenge, &profile, &token ); ////////// // Process the result. ////////// if (status == NO_ERROR) { MSMPPEKey::insert( request, sizeof(profile.RecvSessionKey), profile.RecvSessionKey, FALSE ); MSMPPEKey::insert( request, sizeof(profile.SendSessionKey), profile.SendSessionKey, TRUE ); MSChap2Success::insert( request, identity, profile.AuthResponse ); MSChapDomain::insert( request, identity, profile.LogonDomainName ); } storeLogonResult(request, status, token, profile.KickOffTime); } IASREQUESTSTATUS NTSamAuthentication::onSyncRequest(IRequest* pRequest) throw () { HANDLE hAccount = 0; try { IASTL::IASRequest request(pRequest); ////////// // Extract the NT4-Account-Name attribute. ////////// IASTL::IASAttribute identity; if (!identity.load( request, IAS_ATTRIBUTE_NT4_ACCOUNT_NAME, IASTYPE_STRING )) { return IAS_REQUEST_STATUS_CONTINUE; } ////////// // Convert the User-Name to SAM format. ////////// SamExtractor extractor(*identity); PCWSTR domain = extractor.getDomain(); PCWSTR username = extractor.getUsername(); IASTracePrintf( "NT-SAM Authentication handler received request for %S\\%S.", domain, username ); ////////// // Check if the account has been locked out. ////////// if (AccountLockoutOpenAndQuery( username, domain, &hAccount )) { IASTraceString("Account has been locked out locally -- rejecting."); AccountLockoutClose(hAccount); request.SetResponse(IAS_RESPONSE_ACCESS_REJECT, IAS_DIALIN_LOCKED_OUT); return IAS_REQUEST_STATUS_CONTINUE; } // Try each authentication type. if (!tryMsChap2All(request, domain, username) && !tryMsChapAll(request, domain, username) && !tryMd5Chap(request, domain, username) && !tryPap(request, domain, username)) { // Since the EAP request handler is invoked after policy // evaluation, we have to set the auth type here. if (IASPeekAttribute( request, RADIUS_ATTRIBUTE_EAP_MESSAGE, IASTYPE_OCTET_STRING )) { storeAuthenticationType(request, IAS_AUTH_EAP); } else { // Otherwise, the auth type is "Unauthenticated". storeAuthenticationType(request, IAS_AUTH_NONE); } } ////////// // Update the lockout database based on the results. ////////// if (request.get_Response() == IAS_RESPONSE_ACCESS_ACCEPT) { AccountLockoutUpdatePass(hAccount); } else if (request.get_Reason() == IAS_AUTH_FAILURE) { AccountLockoutUpdateFail(hAccount); } } catch (const _com_error& ce) { IASTraceExcept(); IASProcessFailure(pRequest, ce.Error()); } AccountLockoutClose(hAccount); return IAS_REQUEST_STATUS_CONTINUE; } void NTSamAuthentication::storeAuthenticationType( IASTL::IASRequest& request, DWORD authType ) { IASTL::IASAttribute attr(true); attr->dwId = IAS_ATTRIBUTE_AUTHENTICATION_TYPE; attr->Value.itType = IASTYPE_ENUM; attr->Value.Enumerator = authType; attr.store(request); } void NTSamAuthentication::storeLogonResult( IASTL::IASRequest& request, DWORD status, HANDLE token, const LARGE_INTEGER& kickOffTime ) { if (status == ERROR_SUCCESS) { IASTraceString("LogonUser succeeded."); storeTokenGroups(request, token); request.SetResponse(IAS_RESPONSE_ACCESS_ACCEPT, S_OK); // Add the Session-Timeout attribute to the request if it is not infinite // it'll be carried over to the response later on. InsertInternalTimeout(request, kickOffTime); } else { IASTraceFailure("LogonUser", status); IASProcessFailure(request, IASMapWin32Error(status, IAS_AUTH_FAILURE)); } } void NTSamAuthentication::storeTokenGroups( IASTL::IASRequest& request, HANDLE token ) { DWORD returnLength; ////////// // Determine the needed buffer size. ////////// BOOL success = GetTokenInformation( token, TokenGroups, NULL, 0, &returnLength ); DWORD status = GetLastError(); // Should have failed with ERROR_INSUFFICIENT_BUFFER. if (success || status != ERROR_INSUFFICIENT_BUFFER) { IASTraceFailure("GetTokenInformation", status); _com_issue_error(HRESULT_FROM_WIN32(status)); } ////////// // Allocate an attribute. ////////// IASTL::IASAttribute groups(true); ////////// // Allocate a buffer to hold the TOKEN_GROUPS array. ////////// groups->Value.OctetString.lpValue = (PBYTE)CoTaskMemAlloc(returnLength); if (!groups->Value.OctetString.lpValue) { _com_issue_error(E_OUTOFMEMORY); } ////////// // Get the Token Groups info. ////////// GetTokenInformation( token, TokenGroups, groups->Value.OctetString.lpValue, returnLength, &groups->Value.OctetString.dwLength ); ////////// // Set the id and type of the initialized attribute. ////////// groups->dwId = IAS_ATTRIBUTE_TOKEN_GROUPS; groups->Value.itType = IASTYPE_OCTET_STRING; ////////// // Inject the Token-Groups into the request. ////////// groups.store(request); } bool NTSamAuthentication::tryMsChap( IASTL::IASRequest& request, PCWSTR domainName, PCWSTR username, PBYTE challenge ) { // Is the necessary attribute present? IASAttribute attr; if (!attr.load( request, MS_ATTRIBUTE_CHAP_RESPONSE, IASTYPE_OCTET_STRING )) { return false; } MSChapResponse& response = blob_cast(attr); IASTraceString("Processing MS-CHAP v1 authentication."); storeAuthenticationType(request, IAS_AUTH_MSCHAP); if (!response.isLmPresent() || enforceLmRestriction(request)) { doMsChapAuthentication( request, domainName, username, response.get().ident, challenge, (response.isNtPresent() ? response.get().ntResponse : NULL), (response.isLmPresent() ? response.get().lmResponse : NULL) ); } return true; } bool NTSamAuthentication::tryMsChapCpw1( IASTL::IASRequest& request, PCWSTR domainName, PCWSTR username, PBYTE challenge ) { // Is the necessary attribute present ? IASAttribute attr; bool present = attr.load( request, MS_ATTRIBUTE_CHAP_CPW1, IASTYPE_OCTET_STRING ); if (present) { IASTraceString("Deferring MS-CHAP-CPW-1."); storeAuthenticationType(request, IAS_AUTH_MSCHAP_CPW); } return present; } bool NTSamAuthentication::tryMsChapCpw2( IASTL::IASRequest& request, PCWSTR domainName, PCWSTR username, PBYTE challenge ) { // Is the necessary attribute present ? IASAttribute attr; bool present = attr.load( request, MS_ATTRIBUTE_CHAP_CPW2, IASTYPE_OCTET_STRING ); if (present) { IASTraceString("Deferring MS-CHAP-CPW-2."); storeAuthenticationType(request, IAS_AUTH_MSCHAP_CPW); } return present; } bool NTSamAuthentication::tryMsChap2( IASTL::IASRequest& request, PCWSTR domainName, PCWSTR username, IAS_OCTET_STRING& challenge ) { // Is the necessary attribute present? IASAttribute attr; if (!attr.load( request, MS_ATTRIBUTE_CHAP2_RESPONSE, IASTYPE_OCTET_STRING )) { return false; } MSChap2Response& response = blob_cast(attr); IASTraceString("Processing MS-CHAP v2 authentication."); storeAuthenticationType(request, IAS_AUTH_MSCHAP2); ////////// // Authenticate the user. ////////// doMsChap2Authentication( request, domainName, username, response.get().ident, challenge, response.get().response, response.get().peerChallenge ); return true; } bool NTSamAuthentication::tryMsChap2Cpw( IASTL::IASRequest& request, PCWSTR domainName, PCWSTR username, IAS_OCTET_STRING& challenge ) { // Is the necessary attribute present ? IASAttribute attr; bool present = attr.load( request, MS_ATTRIBUTE_CHAP2_CPW, IASTYPE_OCTET_STRING ); if (present) { IASTraceString("Deferring MS-CHAP v2 change password."); storeAuthenticationType(request, IAS_AUTH_MSCHAP2_CPW); } return present; } bool NTSamAuthentication::tryMd5Chap( IASTL::IASRequest& request, PCWSTR domainName, PCWSTR username ) { // Is the necessary attribute present? IASTL::IASAttribute chapPassword; if (!chapPassword.load( request, RADIUS_ATTRIBUTE_CHAP_PASSWORD, IASTYPE_OCTET_STRING )) { return false; } IASTraceString("Processing MD5-CHAP authentication."); storeAuthenticationType(request, IAS_AUTH_MD5CHAP); // validate length of the octetstring is 17 DWORD chapPasswordLength = chapPassword->Value.OctetString.dwLength; if (chapPasswordLength != (_CHAP_RESPONSE_SIZE + 1)) { IASTracePrintf("Malformed request: Length of CHAP_PASSWORD is %ld", chapPasswordLength); _com_issue_error(IAS_MALFORMED_REQUEST); } ////////// // Split up the CHAP-Password attribute. ////////// // The ID is the first byte of the value ... BYTE challengeID = *(chapPassword->Value.OctetString.lpValue); // ... and the password is the rest. PBYTE password = chapPassword->Value.OctetString.lpValue + 1; ////////// // Use the CHAP-Challenge if available, request authenticator otherwise. ////////// IASTL::IASAttribute chapChallenge, radiusHeader; if (!chapChallenge.load( request, RADIUS_ATTRIBUTE_CHAP_CHALLENGE, IASTYPE_OCTET_STRING ) && !radiusHeader.load( request, IAS_ATTRIBUTE_CLIENT_PACKET_HEADER, IASTYPE_OCTET_STRING )) { _com_issue_error(IAS_MALFORMED_REQUEST); } PBYTE challenge; DWORD challengeLength; if (chapChallenge) { challenge = chapChallenge->Value.OctetString.lpValue; challengeLength = chapChallenge->Value.OctetString.dwLength; } else { challenge = radiusHeader->Value.OctetString.lpValue + 4; challengeLength = 16; } ////////// // Try to logon the user. ////////// IAS_CHAP_PROFILE profile; auto_handle<> token; DWORD status = IASLogonCHAP( username, domainName, challengeID, challenge, challengeLength, password, &token, &profile ); ////////// // Store the results. ////////// storeLogonResult(request, status, token, profile.KickOffTime); return true; } bool NTSamAuthentication::tryMsChapAll( IASTL::IASRequest& request, PCWSTR domainName, PCWSTR username ) { // Do we have the necessary attribute? IASTL::IASAttribute msChapChallenge; if (!msChapChallenge.load( request, MS_ATTRIBUTE_CHAP_CHALLENGE, IASTYPE_OCTET_STRING )) { return false; } if (msChapChallenge->Value.OctetString.dwLength != _MSV1_0_CHALLENGE_LENGTH) { _com_issue_error(IAS_MALFORMED_REQUEST); } PBYTE challenge = msChapChallenge->Value.OctetString.lpValue; return tryMsChap(request, domainName, username, challenge) || tryMsChapCpw2(request, domainName, username, challenge) || tryMsChapCpw1(request, domainName, username, challenge); } bool NTSamAuthentication::tryMsChap2All( IASTL::IASRequest& request, PCWSTR domainName, PCWSTR username ) { // Do we have the necessary attribute? IASTL::IASAttribute msChapChallenge; if (!msChapChallenge.load( request, MS_ATTRIBUTE_CHAP_CHALLENGE, IASTYPE_OCTET_STRING )) { return false; } IAS_OCTET_STRING& challenge = msChapChallenge->Value.OctetString; return tryMsChap2(request, domainName, username, challenge) || tryMsChap2Cpw(request, domainName, username, challenge); } bool NTSamAuthentication::tryPap( IASTL::IASRequest& request, PCWSTR domainName, PCWSTR username ) { // Do we have the necessary attribute? IASTL::IASAttribute password; if (!password.load( request, RADIUS_ATTRIBUTE_USER_PASSWORD, IASTYPE_OCTET_STRING )) { return false; } IASTraceString("Processing PAP authentication."); storeAuthenticationType(request, IAS_AUTH_PAP); ////////// // Convert the password to a string. ////////// PSTR userPwd = IAS_OCT2ANSI(password->Value.OctetString); ////////// // Try to logon the user. ////////// IAS_PAP_PROFILE profile; auto_handle<> token; DWORD status = IASLogonPAP( username, domainName, userPwd, &token, &profile ); ////////// // Store the results. ////////// storeLogonResult(request, status, token, profile.KickOffTime); return true; } void InsertInternalTimeout( IASTL::IASRequest& request, const LARGE_INTEGER& kickOffTime ) { if ((kickOffTime.QuadPart < MAXLONGLONG) && (kickOffTime.QuadPart >= 0)) { LONGLONG now; GetSystemTimeAsFileTime(reinterpret_cast(&now)); // Compute the interval in seconds. LONGLONG interval = (kickOffTime.QuadPart - now) / 10000000i64; if (interval <= 0) { interval = 1; } if (interval < 0xFFFFFFFF) { IASAttribute sessionTimeout(true); sessionTimeout->dwId = MS_ATTRIBUTE_SESSION_TIMEOUT; sessionTimeout->Value.itType = IASTYPE_INTEGER; sessionTimeout->Value.Integer = static_cast(interval); sessionTimeout.store(request); } } }