/* Editor Settings: expandtabs and use 4 spaces for indentation * ex: set softtabstop=4 tabstop=8 expandtab shiftwidth=4: * */ /* * Copyright Likewise Software * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or (at * your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. You should have received a copy of the GNU General * Public License along with this program. If not, see * . * * LIKEWISE SOFTWARE MAKES THIS SOFTWARE AVAILABLE UNDER OTHER LICENSING * TERMS AS WELL. IF YOU HAVE ENTERED INTO A SEPARATE LICENSE AGREEMENT * WITH LIKEWISE SOFTWARE, THEN YOU MAY ELECT TO USE THE SOFTWARE UNDER THE * TERMS OF THAT SOFTWARE LICENSE AGREEMENT INSTEAD OF THE TERMS OF THE GNU * GENERAL PUBLIC LICENSE, NOTWITHSTANDING THE ABOVE NOTICE. IF YOU * HAVE QUESTIONS, OR WISH TO REQUEST A COPY OF THE ALTERNATE LICENSING * TERMS OFFERED BY LIKEWISE SOFTWARE, PLEASE CONTACT LIKEWISE SOFTWARE AT * license@likewisesoftware.com */ /* * Copyright (C) Likewise Software. All rights reserved. * * Module Name: * * lpauthex.c * * Abstract: * * Likewise Security and Authentication Subsystem (LSASS) * * AuthenticateUserEx routines * * Authors: Gerald Carter */ #include "includes.h" /* Forward Declarations */ static DWORD FillAuthUserInfo( HANDLE hProvider, PLSA_AUTH_USER_INFO pAuthInfo, PLSA_SECURITY_OBJECT pObject, PCSTR pszMachineName ); static DWORD SidSplitString( IN OUT PSTR pszSidString, OUT PDWORD pdwRid ); static DWORD AuthenticateNTLMv1( IN PLSA_AUTH_USER_PARAMS pUserParams, IN PLSA_SECURITY_OBJECT pObject, OUT PLSA_DATA_BLOB *ppSessionKeyBlob ); static DWORD AuthenticateNTLMv2( IN PLSA_AUTH_USER_PARAMS pUserParams, IN PLSA_SECURITY_OBJECT pObject, OUT PLSA_DATA_BLOB *ppSessionKeyBlob ); /* Code */ /******************************************************** *******************************************************/ DWORD LocalAuthenticateUserExInternal( HANDLE hProvider, PLSA_AUTH_USER_PARAMS pUserParams, PLSA_AUTH_USER_INFO* ppUserInfo ) { DWORD dwError = LW_ERROR_INTERNAL; PCSTR pszDomain = NULL; PSTR pszAccountName = NULL; PLSA_SECURITY_OBJECT* ppObjects = NULL; PLSA_AUTH_USER_INFO pUserInfo = NULL; BOOLEAN bUsingNTLMv2 = FALSE; BOOLEAN bAcceptNTLMv1 = TRUE; PLSA_DATA_BLOB pSessionKey = NULL; LSA_QUERY_LIST QueryList; BAIL_ON_INVALID_POINTER(pUserParams->pszAccountName); dwError = LocalCheckForQueryAccess(hProvider); BAIL_ON_LSA_ERROR(dwError); /* Assume the local domain (localhost) if we don't have one */ if (pUserParams->pszDomain) { pszDomain = pUserParams->pszDomain; } else { pszDomain = gLPGlobals.pszLocalDomain; } /* Allow the next provider to continue if we don't handle this domain */ if (!LocalServicesDomain(pszDomain)) { dwError = LW_ERROR_NOT_HANDLED; BAIL_ON_LSA_ERROR(dwError); } dwError = LwAllocateStringPrintf(&pszAccountName, "%s\\%s", pszDomain, pUserParams->pszAccountName); BAIL_ON_LSA_ERROR(dwError); QueryList.ppszStrings = (PCSTR*) &pszAccountName; dwError = LocalFindObjects( hProvider, 0, LSA_OBJECT_TYPE_USER, LSA_QUERY_TYPE_BY_NT4, 1, QueryList, &ppObjects); BAIL_ON_LSA_ERROR(dwError); if (ppObjects[0] == NULL) { dwError = LW_ERROR_NO_SUCH_USER; BAIL_ON_LSA_ERROR(dwError); } dwError = LocalCheckAccountFlags(ppObjects[0]); BAIL_ON_LSA_ERROR(dwError); dwError = LocalCfgAcceptNTLMv1(&bAcceptNTLMv1); BAIL_ON_LSA_ERROR(dwError); /* generate the responses and compare */ if (LsaDataBlobLength(pUserParams->pass.chap.pNT_resp) == 24) { bUsingNTLMv2 = FALSE; if (!bAcceptNTLMv1) { dwError = ERROR_INVALID_LOGON_TYPE; BAIL_ON_LSA_ERROR(dwError); } dwError = AuthenticateNTLMv1(pUserParams, ppObjects[0], &pSessionKey); BAIL_ON_LSA_ERROR(dwError); } else { bUsingNTLMv2 = TRUE; dwError = AuthenticateNTLMv2(pUserParams, ppObjects[0], &pSessionKey); BAIL_ON_LSA_ERROR(dwError); } /* Fill in the LSA_AUTH_USER_INF0 data now */ dwError = LwAllocateMemory(sizeof(LSA_AUTH_USER_INFO), (PVOID*)&pUserInfo); BAIL_ON_LSA_ERROR(dwError); dwError = FillAuthUserInfo(hProvider, pUserInfo, ppObjects[0], pszDomain); BAIL_ON_LSA_ERROR(dwError); pUserInfo->pSessionKey = pSessionKey; pSessionKey = NULL; *ppUserInfo = pUserInfo; pUserInfo = NULL; cleanup: LsaUtilFreeSecurityObjectList(1, ppObjects); LsaFreeAuthUserInfo(&pUserInfo); if (pSessionKey) { LsaDataBlobFree(&pSessionKey); } LW_SAFE_FREE_MEMORY(pszAccountName); return dwError; error: goto cleanup; } /******************************************************** *******************************************************/ static DWORD AuthenticateNTLMv1( IN PLSA_AUTH_USER_PARAMS pUserParams, IN PLSA_SECURITY_OBJECT pObject, OUT PLSA_DATA_BLOB *ppSessionKeyBlob ) { NTSTATUS ntError = STATUS_UNSUCCESSFUL; DWORD dwError = LW_ERROR_INTERNAL; BYTE NTResponse[24] = { 0 }; PBYTE pChal = NULL; PBYTE pNTresp = NULL; PBYTE pSessKeyBuf = NULL; PLSA_DATA_BLOB pSessKeyBlob = NULL; /* Authenticate */ pChal = LsaDataBlobBuffer(pUserParams->pass.chap.pChallenge); BAIL_ON_INVALID_POINTER(pChal); ntError = NTLMv1EncryptChallenge(pChal, NULL, /* ignore LM hash */ pObject->userInfo.pNtHash, NULL, NTResponse); if (ntError != STATUS_SUCCESS) { dwError = LW_ERROR_INVALID_PARAMETER; BAIL_ON_LSA_ERROR(dwError); } pNTresp = LsaDataBlobBuffer(pUserParams->pass.chap.pNT_resp); BAIL_ON_INVALID_POINTER(pNTresp); if (memcmp(pNTresp, NTResponse, 24) != 0) { dwError = LW_ERROR_PASSWORD_MISMATCH; BAIL_ON_LSA_ERROR(dwError); } /* Calculate the session Key */ dwError = LsaDataBlobAllocate(&pSessKeyBlob, 16); BAIL_ON_LSA_ERROR(dwError); pSessKeyBuf = LsaDataBlobBuffer(pSessKeyBlob); MD4(pObject->userInfo.pNtHash, 16, pSessKeyBuf); *ppSessionKeyBlob = pSessKeyBlob; pSessKeyBlob = NULL; /* Done */ dwError = LW_ERROR_SUCCESS; cleanup: if (pSessKeyBlob) { LsaDataBlobFree(&pSessKeyBlob); } return dwError; error: goto cleanup; } /******************************************************** *******************************************************/ static DWORD AuthenticateNTLMv2( IN PLSA_AUTH_USER_PARAMS pUserParams, IN PLSA_SECURITY_OBJECT pObject, OUT PLSA_DATA_BLOB *ppSessionKeyBlob ) { DWORD dwError = LW_ERROR_PASSWORD_MISMATCH; PWSTR pwszAccountName = NULL; PWSTR pwszDestination = NULL; BYTE pNTLMv2Hash[16]; DWORD dwNTLMv2HashLen = 16; PBYTE pBuffer = NULL; DWORD dwBufferLen = 0; DWORD dwAcctNameSize = 0; DWORD dwDestNameSize = 0; PBYTE pClientNonce = NULL; DWORD dwClientNonceLen = 0; PBYTE pChal = NULL; PBYTE pNTresp = NULL; DWORD dwNTrespLen = 0; PBYTE pData = NULL; BYTE pNTLMv2Resp[EVP_MAX_MD_SIZE]; DWORD dwNTLMv2RespLen = 0; PLSA_DATA_BLOB pSessKeyBlob = NULL; PBYTE pSessKeyBuf = NULL; DWORD dwSessKeyLen = 0; /* Sanity Check */ BAIL_ON_INVALID_POINTER(pUserParams->pass.chap.pNT_resp); BAIL_ON_INVALID_POINTER(pUserParams->pass.chap.pChallenge); pChal = LsaDataBlobBuffer(pUserParams->pass.chap.pChallenge); pNTresp = LsaDataBlobBuffer(pUserParams->pass.chap.pNT_resp); BAIL_ON_INVALID_POINTER(pChal); BAIL_ON_INVALID_POINTER(pNTresp); dwNTrespLen = LsaDataBlobLength(pUserParams->pass.chap.pNT_resp); memset(pNTLMv2Hash, 0, 16); /* First calculate the NTLMv2 Hash */ dwError = LsaMbsToWc16s(pUserParams->pszAccountName, &pwszAccountName); BAIL_ON_LSA_ERROR(dwError); dwError = LsaMbsToWc16s(pUserParams->pszDomain, &pwszDestination); BAIL_ON_LSA_ERROR(dwError); /* Emperical testing from WinXP sp3 shows that only the username need be upper cased */ dwError = LsaWc16ToUpper(pwszAccountName); BAIL_ON_LSA_ERROR(dwError); dwAcctNameSize = RtlWC16StringNumChars(pwszAccountName) * sizeof(WCHAR); dwDestNameSize = RtlWC16StringNumChars(pwszDestination) * sizeof(WCHAR); dwBufferLen = dwAcctNameSize + dwDestNameSize; dwError = LwAllocateMemory(dwBufferLen, (PVOID*)&pBuffer); BAIL_ON_LSA_ERROR(dwError); memcpy(pBuffer, pwszAccountName, dwAcctNameSize); memcpy(pBuffer+dwAcctNameSize, pwszDestination, dwDestNameSize); HMAC(EVP_md5(), pObject->userInfo.pNtHash, 16, pBuffer, dwBufferLen, pNTLMv2Hash, &dwNTLMv2HashLen); /* grab the client nonce (starts at offset 16 following the HMAC) */ pClientNonce = pNTresp + 16; dwClientNonceLen = dwNTrespLen - 16; /* generate the Response for comparing the MAC */ dwError = LwAllocateMemory(dwClientNonceLen + 8, (PVOID*)&pData); BAIL_ON_LSA_ERROR(dwError); memcpy(pData, pChal, 8); memcpy(pData+8, pClientNonce, dwClientNonceLen); HMAC(EVP_md5(), pNTLMv2Hash, 16, pData, dwClientNonceLen + 8, pNTLMv2Resp, &dwNTLMv2RespLen); if (memcmp(pNTLMv2Resp, pNTresp, 16) != 0) { dwError = LW_ERROR_PASSWORD_MISMATCH; BAIL_ON_LSA_ERROR(dwError); } /* Calculate the session Key */ dwError = LsaDataBlobAllocate(&pSessKeyBlob, 16); BAIL_ON_LSA_ERROR(dwError); pSessKeyBuf = LsaDataBlobBuffer(pSessKeyBlob); HMAC(EVP_md5(), pNTLMv2Hash, 16, pNTresp, 16, pSessKeyBuf, &dwSessKeyLen); *ppSessionKeyBlob = pSessKeyBlob; pSessKeyBlob = NULL; /* Done */ dwError = LW_ERROR_SUCCESS; cleanup: LW_SAFE_FREE_MEMORY(pBuffer); LW_SAFE_FREE_MEMORY(pData); LW_SAFE_FREE_MEMORY(pwszAccountName); LW_SAFE_FREE_MEMORY(pwszDestination); if (pSessKeyBlob) { LsaDataBlobFree(&pSessKeyBlob); } return dwError; error: goto cleanup; } /******************************************************** *******************************************************/ static DWORD SidSplitString( IN OUT PSTR pszSidString, OUT PDWORD pdwRid ) { DWORD dwError = LW_ERROR_INTERNAL; PSTR p = NULL; PSTR q = NULL; DWORD dwRid = 0; BAIL_ON_INVALID_POINTER(pszSidString); p = strrchr(pszSidString, '-'); if (p == NULL) { dwError = LW_ERROR_INVALID_SID; BAIL_ON_LSA_ERROR(dwError); } /* Get the RID */ p++; dwRid = strtol(p, &q, 10); if ((dwRid == 0) || (*q != '\0')) { dwError = LW_ERROR_INVALID_SID; BAIL_ON_LSA_ERROR(dwError); } /* Split the string now that we know the RID is valid */ *pdwRid = dwRid; p--; *p = '\0'; dwError = LW_ERROR_SUCCESS; cleanup: return dwError; error: goto cleanup; } /******************************************************** *******************************************************/ static DWORD FillAuthUserInfo( HANDLE hProvider, OUT PLSA_AUTH_USER_INFO pAuthInfo, IN PLSA_SECURITY_OBJECT pObject, IN PCSTR pszMachineName ) { DWORD dwError = LW_ERROR_INTERNAL; DWORD dwNumGroups = 0; PSTR* ppszGroups = NULL; int i = 0; /* Find the user's groups */ dwError = LocalQueryMemberOf( hProvider, 0, 1, &pObject->pszObjectSid, &dwNumGroups, &ppszGroups); BAIL_ON_LSA_ERROR(dwError); /* leave user flags empty for now. But fill in account flags */ pAuthInfo->dwAcctFlags = ACB_NORMAL; if (pObject->userInfo.bAccountDisabled) { pAuthInfo->dwAcctFlags |= ACB_DISABLED; } if (pObject->userInfo.bAccountExpired) { pAuthInfo->dwAcctFlags |= ACB_PW_EXPIRED; } if (pObject->userInfo.bPasswordNeverExpires) { pAuthInfo->dwAcctFlags |= ACB_PWNOEXP; } /* Copy strings */ dwError = LwStrDupOrNull(pObject->userInfo.pszUnixName, &pAuthInfo->pszAccount); BAIL_ON_LSA_ERROR(dwError); dwError = LwStrDupOrNull(pObject->userInfo.pszGecos, &pAuthInfo->pszFullName); BAIL_ON_LSA_ERROR(dwError); dwError = LwStrDupOrNull(pszMachineName, &pAuthInfo->pszDomain); BAIL_ON_LSA_ERROR(dwError); dwError = LwStrDupOrNull(pObject->pszObjectSid, &pAuthInfo->pszDomainSid); BAIL_ON_LSA_ERROR(dwError); dwError = SidSplitString(pAuthInfo->pszDomainSid, &pAuthInfo->dwUserRid); BAIL_ON_LSA_ERROR(dwError); /* This really needs a check to ensure that the primaryGroup SID is in the machine's domain */ dwError = SidSplitString(pObject->userInfo.pszPrimaryGroupSid, &pAuthInfo->dwPrimaryGroupRid); BAIL_ON_LSA_ERROR(dwError); /* Since all the groups we get back have full SIDs, place them in the SidAttributeList rather that filtering the ones in the host's own SAM domain to the RidAttributeList */ pAuthInfo->dwNumRids = 0; pAuthInfo->dwNumSids = dwNumGroups; dwError = LwAllocateMemory(sizeof(LSA_SID_ATTRIB)*dwNumGroups, (PVOID*)&pAuthInfo->pSidAttribList); BAIL_ON_LSA_ERROR(dwError); for (i=0; idwNumSids; i++) { dwError = LwStrDupOrNull(ppszGroups[i], &pAuthInfo->pSidAttribList[i].pszSid); BAIL_ON_LSA_ERROR(dwError); pAuthInfo->pSidAttribList[i].dwAttrib = LSA_SID_ATTR_GROUP_MANDATORY | LSA_SID_ATTR_GROUP_ENABLED_BY_DEFAULT | LSA_SID_ATTR_GROUP_ENABLED; } dwError = LW_ERROR_SUCCESS; cleanup: LwFreeStringArray(ppszGroups, dwNumGroups); return dwError; error: goto cleanup; } /* local variables: mode: c c-basic-offset: 4 indent-tabs-mode: nil tab-width: 4 end: */