/* 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:
*/