/* 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 library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the license, or (at * your option) any later version. * * This library 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 Lesser * General Public License for more details. You should have received a copy * of the GNU Lesser 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 * LESSER 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: * * lwmapsecurity-lsass.c * * Abstract: * * Likewise Security and Authentication Subsystem (LSASS) * * Access Token Create Information * * Authors: Danilo Almeida (dalmeida@likewise.com) */ #include #include #include #include #include #include #include #include #include #include #include #include #include "lwmem.h" #include "lwstr.h" #include "lwsecurityidentifier.h" #include "lsautils.h" #include #include "lsass-calls.h" typedef struct _LW_MAP_SECURITY_PLUGIN_CONTEXT { // TODO-Add connection caching using TLS, a connection pool, // or somesuch. It may be useful to change the calls to LSASS to // go through a LsaMapSecurityCallLsass() that is like // LsaDmConnectDomain() so we can automatically retry on // connection-type errors. HANDLE hUnusedConnection; } LW_MAP_SECURITY_PLUGIN_CONTEXT; typedef UCHAR LSA_MAP_SECURITY_OBJECT_INFO_FLAGS, *PLSA_MAP_SECURITY_OBJECT_INFO_FLAGS; #define LSA_MAP_SECURITY_OBJECT_INFO_FLAG_IS_USER 0x01 #define LSA_MAP_SECURITY_OBJECT_INFO_FLAG_VALID_UID 0x02 #define LSA_MAP_SECURITY_OBJECT_INFO_FLAG_VALID_GID 0x04 typedef struct _LSA_MAP_SECURITY_OBJECT_INFO { LSA_MAP_SECURITY_OBJECT_INFO_FLAGS Flags; ULONG Uid; ULONG Gid; PSID Sid; PSID PrimaryGroupSid; } LSA_MAP_SECURITY_OBJECT_INFO, *PLSA_MAP_SECURITY_OBJECT_INFO; #define IS_NOT_FOUND_ERROR(lsaError) ( \ (LW_ERROR_NO_SUCH_USER == (lsaError)) || \ (LW_ERROR_NO_SUCH_GROUP == (lsaError)) || \ (LW_ERROR_NO_SUCH_OBJECT == (lsaError)) || \ 0 ) static NTSTATUS LsaMapSecurityCreateTokenDefaultDacl( PACL *ppDacl, PSID pOwnerSid ); static NTSTATUS LsaMapSecurityOpenConnection( IN PLW_MAP_SECURITY_PLUGIN_CONTEXT Context, OUT PHANDLE phConnection ) { NTSTATUS status = STATUS_SUCCESS; DWORD dwError = LW_ERROR_SUCCESS; HANDLE hConnection = NULL; dwError = LsaOpenServer(&hConnection); status = LsaLsaErrorToNtStatus(dwError); *phConnection = hConnection; return status; } static VOID LsaMapSecurityCloseConnection( IN PLW_MAP_SECURITY_PLUGIN_CONTEXT Context, IN OUT PHANDLE phConnection ) { HANDLE hConnection = *phConnection; if (hConnection) { NTSTATUS status = STATUS_SUCCESS; DWORD dwError = LW_ERROR_SUCCESS; dwError = LsaCloseServer(hConnection); status = LsaLsaErrorToNtStatus(dwError); *phConnection = NULL; } } static VOID LsaMapSecurityFreeObjectInfo( IN OUT PLSA_MAP_SECURITY_OBJECT_INFO pObjectInfo ) { RTL_FREE(&pObjectInfo->Sid); RTL_FREE(&pObjectInfo->PrimaryGroupSid); RtlZeroMemory(pObjectInfo, sizeof(*pObjectInfo)); } // // Use a single "resolve" function so that we can centralize any code // to (re)connect as well as special-case certain errors. // static NTSTATUS LsaMapSecurityResolveObjectInfo( IN PLW_MAP_SECURITY_PLUGIN_CONTEXT Context, IN BOOLEAN IsUser, IN OPTIONAL PCSTR pszName, IN OPTIONAL PULONG Id, OUT PLSA_MAP_SECURITY_OBJECT_INFO pObjectInfo ) { NTSTATUS status = STATUS_SUCCESS; DWORD dwError = LW_ERROR_SUCCESS; LSA_MAP_SECURITY_OBJECT_INFO objectInfo = { 0 }; ULONG id = Id ? *Id : (ULONG) -1; HANDLE hConnection = NULL; PLSA_SECURITY_OBJECT* ppObjects = NULL; LSA_QUERY_LIST QueryList; LSA_OBJECT_TYPE ObjectType = LSA_OBJECT_TYPE_UNDEFINED; if (IS_BOTH_OR_NEITHER(pszName, Id)) { assert(FALSE); status = STATUS_INVALID_PARAMETER; GOTO_CLEANUP(); } if (IsUser) { ObjectType = LSA_OBJECT_TYPE_USER; } else { ObjectType = LSA_OBJECT_TYPE_GROUP; } status = LsaMapSecurityOpenConnection(Context, &hConnection); if (NT_SUCCESS(status)) { if (pszName) { QueryList.ppszStrings = (PCSTR*) &pszName; dwError = LsaFindObjects( hConnection, NULL, 0, ObjectType, LSA_QUERY_TYPE_BY_NAME, 1, QueryList, &ppObjects); } else { QueryList.pdwIds = &id; dwError = LsaFindObjects( hConnection, NULL, 0, ObjectType, LSA_QUERY_TYPE_BY_UNIX_ID, 1, QueryList, &ppObjects); } if (dwError == LW_ERROR_SUCCESS && (ppObjects[0] == NULL || !ppObjects[0]->enabled)) { dwError = LW_ERROR_NO_SUCH_OBJECT; } LsaMapSecurityCloseConnection(Context, &hConnection); } else { dwError = LW_ERROR_NO_SUCH_OBJECT; } if (IS_NOT_FOUND_ERROR(dwError)) { union { SID Sid; BYTE Buffer[SID_MAX_SIZE]; } sidBuffer; if (pszName) { status = STATUS_NOT_FOUND; GOTO_CLEANUP(); } status = LwMapSecurityInitializeSidFromUnmappedId( sizeof(sidBuffer), &sidBuffer.Sid, IsUser, id); GOTO_CLEANUP_ON_STATUS(status); if (IsUser) { SetFlag(objectInfo.Flags, LSA_MAP_SECURITY_OBJECT_INFO_FLAG_IS_USER); SetFlag(objectInfo.Flags, LSA_MAP_SECURITY_OBJECT_INFO_FLAG_VALID_UID); objectInfo.Uid = id; } else { SetFlag(objectInfo.Flags, LSA_MAP_SECURITY_OBJECT_INFO_FLAG_VALID_GID); objectInfo.Gid = id; } status = RtlDuplicateSid(&objectInfo.Sid, &sidBuffer.Sid); GOTO_CLEANUP_ON_STATUS(status); status = STATUS_SUCCESS; GOTO_CLEANUP(); } status = LsaLsaErrorToNtStatus(dwError); GOTO_CLEANUP_ON_STATUS(status); if (IsUser) { assert(pszName || ppObjects[0]->userInfo.uid); SetFlag(objectInfo.Flags, LSA_MAP_SECURITY_OBJECT_INFO_FLAG_IS_USER); SetFlag(objectInfo.Flags, LSA_MAP_SECURITY_OBJECT_INFO_FLAG_VALID_UID); SetFlag(objectInfo.Flags, LSA_MAP_SECURITY_OBJECT_INFO_FLAG_VALID_GID); objectInfo.Uid = ppObjects[0]->userInfo.uid; objectInfo.Gid = ppObjects[0]->userInfo.gid; status = RtlAllocateSidFromCString(&objectInfo.Sid, ppObjects[0]->pszObjectSid); GOTO_CLEANUP_ON_STATUS(status); status = RtlAllocateSidFromCString( &objectInfo.PrimaryGroupSid, ppObjects[0]->userInfo.pszPrimaryGroupSid); GOTO_CLEANUP_ON_STATUS(status); } else { assert(pszName || ppObjects[0]->groupInfo.gid); SetFlag(objectInfo.Flags, LSA_MAP_SECURITY_OBJECT_INFO_FLAG_VALID_GID); objectInfo.Gid = ppObjects[0]->groupInfo.gid; status = RtlAllocateSidFromCString(&objectInfo.Sid, ppObjects[0]->pszObjectSid); GOTO_CLEANUP_ON_STATUS(status); } cleanup: if (!NT_SUCCESS(status)) { LsaMapSecurityFreeObjectInfo(&objectInfo); } LsaUtilFreeSecurityObjectList(1, ppObjects); *pObjectInfo = objectInfo; return status; } static NTSTATUS LsaMapSecurityResolveObjectInfoBySid( IN PLW_MAP_SECURITY_PLUGIN_CONTEXT Context, IN PSID Sid, OUT PLSA_MAP_SECURITY_OBJECT_INFO pObjectInfo ) { NTSTATUS status = STATUS_SUCCESS; DWORD dwError = LW_ERROR_SUCCESS; LSA_MAP_SECURITY_OBJECT_INFO objectInfo = { 0 }; PSTR pszSid = NULL; PLSA_SECURITY_OBJECT* ppObjects = NULL; LSA_QUERY_LIST QueryList; HANDLE hConnection = NULL; status = RtlAllocateCStringFromSid(&pszSid, Sid); GOTO_CLEANUP_ON_STATUS(status); status = LsaMapSecurityOpenConnection(Context, &hConnection); GOTO_CLEANUP_ON_STATUS(status); QueryList.ppszStrings = (PCSTR*) &pszSid; dwError = LsaFindObjects( hConnection, NULL, 0, LSA_OBJECT_TYPE_UNDEFINED, LSA_QUERY_TYPE_BY_SID, 1, QueryList, &ppObjects); if (dwError == LW_ERROR_SUCCESS && (ppObjects[0] == NULL || !ppObjects[0]->enabled)) { dwError = LW_ERROR_NO_SUCH_OBJECT; } status = LsaLsaErrorToNtStatus(dwError); assert(STATUS_NOT_FOUND != status); GOTO_CLEANUP_ON_STATUS(status); LsaMapSecurityCloseConnection(Context, &hConnection); if (ppObjects[0]->type == LSA_OBJECT_TYPE_USER) { SetFlag(objectInfo.Flags, LSA_MAP_SECURITY_OBJECT_INFO_FLAG_IS_USER); SetFlag(objectInfo.Flags, LSA_MAP_SECURITY_OBJECT_INFO_FLAG_VALID_UID); SetFlag(objectInfo.Flags, LSA_MAP_SECURITY_OBJECT_INFO_FLAG_VALID_GID); objectInfo.Uid = ppObjects[0]->userInfo.uid; objectInfo.Gid = ppObjects[0]->userInfo.gid; status = RtlAllocateSidFromCString(&objectInfo.Sid, ppObjects[0]->pszObjectSid); GOTO_CLEANUP_ON_STATUS(status); } else { SetFlag(objectInfo.Flags, LSA_MAP_SECURITY_OBJECT_INFO_FLAG_VALID_GID); objectInfo.Gid = ppObjects[0]->groupInfo.gid; status = RtlAllocateSidFromCString(&objectInfo.Sid, ppObjects[0]->pszObjectSid); GOTO_CLEANUP_ON_STATUS(status); } cleanup: if (!NT_SUCCESS(status)) { LsaMapSecurityFreeObjectInfo(&objectInfo); } LW_SAFE_FREE_STRING(pszSid); LsaUtilFreeSecurityObjectList(1, ppObjects); *pObjectInfo = objectInfo; return status; } static NTSTATUS LsaMapSecurityConstructSid( IN PSID pDomainSid, IN ULONG rid, OUT PSID* ppSid ) { NTSTATUS status = STATUS_SUCCESS; PSID pSid = NULL; status = RTL_ALLOCATE(&pSid, SID, SID_MAX_SIZE); BAIL_ON_NT_STATUS(status); status = RtlCopySid(SID_MAX_SIZE, pSid, pDomainSid); BAIL_ON_NT_STATUS(status); status = RtlAppendRidSid(SID_MAX_SIZE, pSid, rid); BAIL_ON_NT_STATUS(status); *ppSid = pSid; cleanup: return status; error: *ppSid = NULL; RTL_FREE(&pSid); goto cleanup; } static NTSTATUS LsaMapSecurityResolveObjectInfoFromPac( IN PLW_MAP_SECURITY_PLUGIN_CONTEXT Context, IN PAC_LOGON_INFO* pPac, OUT PLSA_MAP_SECURITY_OBJECT_INFO pObjectInfo ) { NTSTATUS status = STATUS_SUCCESS; DWORD dwError = LW_ERROR_SUCCESS; LSA_MAP_SECURITY_OBJECT_INFO objectInfo = { 0 }; PSTR pszSid = NULL; PLSA_SECURITY_OBJECT* ppObjects = NULL; LSA_QUERY_LIST QueryList; HANDLE hConnection = NULL; status = LsaMapSecurityConstructSid(pPac->info3.base.domain_sid, pPac->info3.base.rid, &objectInfo.Sid); GOTO_CLEANUP_ON_STATUS(status); status = RtlAllocateCStringFromSid(&pszSid, objectInfo.Sid); GOTO_CLEANUP_ON_STATUS(status); status = LsaMapSecurityOpenConnection(Context, &hConnection); GOTO_CLEANUP_ON_STATUS(status); QueryList.ppszStrings = (PCSTR*) &pszSid; dwError = LsaFindObjects( hConnection, NULL, 0, LSA_OBJECT_TYPE_UNDEFINED, LSA_QUERY_TYPE_BY_SID, 1, QueryList, &ppObjects); LsaMapSecurityCloseConnection(Context, &hConnection); status = LsaMapSecurityConstructSid( pPac->info3.base.domain_sid, pPac->info3.base.primary_gid, &objectInfo.PrimaryGroupSid); GOTO_CLEANUP_ON_STATUS(status); SetFlag(objectInfo.Flags, LSA_MAP_SECURITY_OBJECT_INFO_FLAG_IS_USER); if (ppObjects[0] && ppObjects[0]->enabled) { assert(ppObjects[0]->type == LSA_OBJECT_TYPE_USER); SetFlag(objectInfo.Flags, LSA_MAP_SECURITY_OBJECT_INFO_FLAG_VALID_UID); SetFlag(objectInfo.Flags, LSA_MAP_SECURITY_OBJECT_INFO_FLAG_VALID_GID); objectInfo.Uid = ppObjects[0]->userInfo.uid; objectInfo.Gid = ppObjects[0]->userInfo.gid; } cleanup: if (!NT_SUCCESS(status)) { LsaMapSecurityFreeObjectInfo(&objectInfo); } LW_SAFE_FREE_STRING(pszSid); LsaUtilFreeSecurityObjectList(1, ppObjects); *pObjectInfo = objectInfo; return status; } static NTSTATUS LsaMapSecurityDuplicateSid( IN PLW_MAP_SECURITY_PLUGIN_CONTEXT Context, IN OUT PSID* Sid, IN PSID OriginalSid ) { return RtlDuplicateSid(Sid, OriginalSid); } static VOID LsaMapSecurityFreeSid( IN PLW_MAP_SECURITY_PLUGIN_CONTEXT Context, IN OUT PSID* Sid ) { RTL_FREE(Sid); } static NTSTATUS LsaMapSecurityGetIdFromSid( IN PLW_MAP_SECURITY_PLUGIN_CONTEXT Context, OUT PBOOLEAN IsUser, OUT PULONG Id, IN PSID Sid ) { NTSTATUS status = STATUS_SUCCESS; LSA_MAP_SECURITY_OBJECT_INFO objectInfo = { 0 }; BOOLEAN isUser = FALSE; ULONG id = 0; status = LsaMapSecurityResolveObjectInfoBySid(Context, Sid, &objectInfo); GOTO_CLEANUP_ON_STATUS(status); isUser = IsSetFlag(objectInfo.Flags, LSA_MAP_SECURITY_OBJECT_INFO_FLAG_IS_USER); id = isUser ? objectInfo.Uid : objectInfo.Gid; cleanup: if (!NT_SUCCESS(status)) { isUser = FALSE; id = 0; } LsaMapSecurityFreeObjectInfo(&objectInfo); *IsUser = isUser; *Id = id; return status; } static NTSTATUS LsaMapSecurityGetSidFromId( IN PLW_MAP_SECURITY_PLUGIN_CONTEXT Context, OUT PSID* Sid, IN BOOLEAN IsUser, IN ULONG Id ) { NTSTATUS status = STATUS_SUCCESS; LSA_MAP_SECURITY_OBJECT_INFO objectInfo = { 0 }; PSID sid = NULL; status = LsaMapSecurityResolveObjectInfo(Context, IsUser, NULL, &Id, &objectInfo); GOTO_CLEANUP_ON_STATUS(status); sid = objectInfo.Sid; objectInfo.Sid = NULL; cleanup: LsaMapSecurityFreeObjectInfo(&objectInfo); *Sid = sid; return status; } static VOID LsaMapSecurityFreeAccessTokenCreateInformation( IN PLW_MAP_SECURITY_PLUGIN_CONTEXT Context, IN OUT PACCESS_TOKEN_CREATE_INFORMATION* CreateInformation ) { PACCESS_TOKEN_CREATE_INFORMATION createInformation = *CreateInformation; if (createInformation) { ULONG i = 0; RTL_FREE(&createInformation->User->User.Sid); for (i = 0; i < createInformation->Groups->GroupCount; i++) { RTL_FREE(&createInformation->Groups->Groups[i].Sid); } RTL_FREE(&createInformation->Owner->Owner); RTL_FREE(&createInformation->PrimaryGroup->PrimaryGroup); RTL_FREE(&createInformation->DefaultDacl->DefaultDacl); RTL_FREE(&createInformation); *CreateInformation = NULL; } } static NTSTATUS LsaMapSecurityAllocateAccessTokenCreateInformation( OUT PACCESS_TOKEN_CREATE_INFORMATION* CreateInformation, IN ULONG GroupCount ) { NTSTATUS status = STATUS_SUCCESS; PACCESS_TOKEN_CREATE_INFORMATION createInformation = NULL; ULONG size = 0; PVOID location = NULL; // // Compute size for everything except the actual SIDs. This includes // the flexible array using GroupCount. // status = RtlSafeMultiplyULONG(&size, GroupCount, sizeof(createInformation->Groups->Groups[0])); GOTO_CLEANUP_ON_STATUS(status); status = RtlSafeAddULONG(&size, size, sizeof(*createInformation)); GOTO_CLEANUP_ON_STATUS(status); status = RtlSafeAddULONG(&size, size, sizeof(*createInformation->User)); GOTO_CLEANUP_ON_STATUS(status); status = RtlSafeAddULONG(&size, size, sizeof(*createInformation->Groups)); GOTO_CLEANUP_ON_STATUS(status); status = RtlSafeAddULONG(&size, size, sizeof(*createInformation->Owner)); GOTO_CLEANUP_ON_STATUS(status); status = RtlSafeAddULONG(&size, size, sizeof(*createInformation->PrimaryGroup)); GOTO_CLEANUP_ON_STATUS(status); status = RtlSafeAddULONG(&size, size, sizeof(*createInformation->DefaultDacl)); GOTO_CLEANUP_ON_STATUS(status); status = RtlSafeAddULONG(&size, size, sizeof(*createInformation->Unix)); GOTO_CLEANUP_ON_STATUS(status); status = RTL_ALLOCATE(&createInformation, ACCESS_TOKEN_CREATE_INFORMATION, size); GOTO_CLEANUP_ON_STATUS(status); location = createInformation; location = LwRtlOffsetToPointer(location, sizeof(*createInformation)); createInformation->User = location; location = LwRtlOffsetToPointer(location, sizeof(*createInformation->User)); createInformation->Groups = location; location = LwRtlOffsetToPointer(location, sizeof(*createInformation->Groups)); location = LwRtlOffsetToPointer(location, GroupCount * sizeof(createInformation->Groups->Groups[0])); createInformation->Owner = location; location = LwRtlOffsetToPointer(location, sizeof(*createInformation->Owner)); createInformation->PrimaryGroup = location; location = LwRtlOffsetToPointer(location, sizeof(*createInformation->PrimaryGroup)); createInformation->DefaultDacl = location; location = LwRtlOffsetToPointer(location, sizeof(*createInformation->DefaultDacl)); createInformation->Unix = location; location = LwRtlOffsetToPointer(location, sizeof(*createInformation->Unix)); assert(LwRtlOffsetToPointer(createInformation, size) == location); cleanup: if (!NT_SUCCESS(status)) { RTL_FREE(&createInformation); } *CreateInformation = createInformation; return status; } static BOOLEAN LsaMapSecurityIsGidInGidList( IN ULONG Gid, IN ULONG GidCount, IN PULONG pGidList ) { BOOLEAN isFound = FALSE; ULONG i = 0; for (i = 0; i < GidCount; i++) { if (Gid == pGidList[i]) { isFound = TRUE; break; } } return isFound; } static BOOLEAN LsaMapSecurityIsGidInGroupInfoList( IN ULONG Gid, IN ULONG GroupInfoCount, IN PLSA_SECURITY_OBJECT* ppGroupObjects ) { BOOLEAN isFound = FALSE; ULONG i = 0; for (i = 0; i < GroupInfoCount; i++) { if (ppGroupObjects[i] && Gid == ppGroupObjects[i]->groupInfo.gid) { isFound = TRUE; break; } } return isFound; } static VOID LsaMapSecurityAddExtraGid( IN ULONG Gid, IN OUT PULONG ExtraGidCount, IN OUT PULONG ExtraGidList, IN ULONG ExtraGidMaximumCount, IN ULONG GroupInfoCount, IN PLSA_SECURITY_OBJECT* ppGroupObjects ) { ULONG extraGidCount = *ExtraGidCount; if (extraGidCount < ExtraGidMaximumCount) { if (!LsaMapSecurityIsGidInGidList(Gid, extraGidCount, ExtraGidList) && !LsaMapSecurityIsGidInGroupInfoList(Gid, GroupInfoCount, ppGroupObjects)) { ExtraGidList[extraGidCount] = Gid; *ExtraGidCount = extraGidCount + 1; } } } static NTSTATUS LsaMapSecurityGetAccessTokenCreateInformationFromObjectInfo( IN PLW_MAP_SECURITY_PLUGIN_CONTEXT Context, OUT PACCESS_TOKEN_CREATE_INFORMATION* CreateInformation, IN PLSA_MAP_SECURITY_OBJECT_INFO pObjectInfo, IN OPTIONAL PULONG Gid ) { NTSTATUS status = STATUS_SUCCESS; DWORD dwError = LW_ERROR_SUCCESS; PACCESS_TOKEN_CREATE_INFORMATION createInformation = NULL; ULONG i = 0; ULONG gid = Gid ? *Gid : 0; ULONG extraGidList[2] = { 0 }; PSID extraGidSidList[LW_ARRAY_SIZE(extraGidList)] = { 0 }; ULONG extraGidCount = 0; HANDLE hConnection = NULL; DWORD dwGroupCount = 0; PSTR* ppszGroupSids = NULL; PSTR pszSid = NULL; PLSA_SECURITY_OBJECT* ppObjects = NULL; LSA_QUERY_LIST QueryList; status = RtlAllocateCStringFromSid(&pszSid, pObjectInfo->Sid); GOTO_CLEANUP_ON_STATUS(status); status = LsaMapSecurityOpenConnection(Context, &hConnection); GOTO_CLEANUP_ON_STATUS(status); dwError = LsaQueryMemberOf( hConnection, NULL, 0, 1, &pszSid, &dwGroupCount, &ppszGroupSids); if (IS_NOT_FOUND_ERROR(dwError)) { dwError = 0; dwGroupCount = 0; } else { status = LsaLsaErrorToNtStatus(dwError); GOTO_CLEANUP_ON_STATUS(status); } if (dwGroupCount) { QueryList.ppszStrings = (PCSTR*) ppszGroupSids; dwError = LsaFindObjects( hConnection, NULL, 0, LSA_OBJECT_TYPE_GROUP, LSA_QUERY_TYPE_BY_SID, dwGroupCount, QueryList, &ppObjects); status = LsaLsaErrorToNtStatus(dwError); GOTO_CLEANUP_ON_STATUS(status); } LsaMapSecurityCloseConnection(Context, &hConnection); // // Take into account extra GIDs that came as primary GID from // object info or from passed in primary GID. // if (IsSetFlag(pObjectInfo->Flags, LSA_MAP_SECURITY_OBJECT_INFO_FLAG_VALID_GID)) { LsaMapSecurityAddExtraGid( pObjectInfo->Gid, &extraGidCount, extraGidList, LW_ARRAY_SIZE(extraGidList), dwGroupCount, ppObjects); } if (Gid) { LsaMapSecurityAddExtraGid( gid, &extraGidCount, extraGidList, LW_ARRAY_SIZE(extraGidList), dwGroupCount, ppObjects); } // // Resolve extra GIDs into SIDs // for (i = 0; i < extraGidCount; i++) { status = LsaMapSecurityGetSidFromId( Context, &extraGidSidList[i], FALSE, extraGidList[i]); GOTO_CLEANUP_ON_STATUS(status); } // // Allocate token create information with enough space // for any potential extra GIDs. // status = LsaMapSecurityAllocateAccessTokenCreateInformation( &createInformation, dwGroupCount + extraGidCount); GOTO_CLEANUP_ON_STATUS(status); // // TOKEN_UNIX // createInformation->Unix->Uid = pObjectInfo->Uid; // TODO-Should the passed in GID take precedence over the LSASS one? if (IsSetFlag(pObjectInfo->Flags, LSA_MAP_SECURITY_OBJECT_INFO_FLAG_VALID_GID)) { createInformation->Unix->Gid = pObjectInfo->Gid; } else if (Gid) { createInformation->Unix->Gid = gid; } else { // TODO-Would need to put in some nobody-like gid. assert(FALSE); status = STATUS_ASSERTION_FAILURE; GOTO_CLEANUP(); } // TODO-Should the token even have a umask, and if so, // where should it come from? createInformation->Unix->Umask = 0022; // // TOKEN_USER // status = RtlDuplicateSid(&createInformation->User->User.Sid, pObjectInfo->Sid); GOTO_CLEANUP_ON_STATUS(status); // // TOKEN_GROUPS // for (i = 0; i < dwGroupCount; i++) { PSID_AND_ATTRIBUTES group = &createInformation->Groups->Groups[createInformation->Groups->GroupCount]; if (ppObjects[i]) { status = RtlAllocateSidFromCString( &group->Sid, ppObjects[i]->pszObjectSid); GOTO_CLEANUP_ON_STATUS(status); group->Attributes = SE_GROUP_ENABLED; createInformation->Groups->GroupCount++; } } for (i = 0; i < extraGidCount; i++) { PSID_AND_ATTRIBUTES group = &createInformation->Groups->Groups[createInformation->Groups->GroupCount]; group->Sid = extraGidSidList[i]; extraGidSidList[i] = NULL; group->Attributes = SE_GROUP_ENABLED; createInformation->Groups->GroupCount++; } // // TOKEN_OWNER // status = RtlDuplicateSid(&createInformation->Owner->Owner, pObjectInfo->Sid); GOTO_CLEANUP_ON_STATUS(status); // // TOKEN_PRIMARY_GROUP // if (pObjectInfo->PrimaryGroupSid) { status = RtlDuplicateSid( &createInformation->PrimaryGroup->PrimaryGroup, pObjectInfo->PrimaryGroupSid); GOTO_CLEANUP_ON_STATUS(status); } else { status = LsaMapSecurityGetSidFromId( Context, &createInformation->PrimaryGroup->PrimaryGroup, FALSE, createInformation->Unix->Gid); GOTO_CLEANUP_ON_STATUS(status); } // // TOKEN_DEFAULT_DACL // status = LsaMapSecurityCreateTokenDefaultDacl( &createInformation->DefaultDacl->DefaultDacl, createInformation->Owner->Owner); GOTO_CLEANUP_ON_STATUS(status); cleanup: if (!NT_SUCCESS(status)) { LsaMapSecurityFreeAccessTokenCreateInformation(Context, &createInformation); } LwFreeStringArray(ppszGroupSids, dwGroupCount); LsaUtilFreeSecurityObjectList(dwGroupCount, ppObjects); for (i = 0; i < extraGidCount; i++) { RTL_FREE(&extraGidSidList[i]); } *CreateInformation = createInformation; return status; } static NTSTATUS LsaMapSecurityMergeStringLists( IN DWORD dwCount1, IN PSTR* ppszStrings1, IN DWORD dwCount2, IN PSTR* ppszStrings2, OUT PDWORD pdwMergedCount, OUT PSTR** pppszMergedStrings ) { NTSTATUS status = STATUS_SUCCESS; PSTR* ppszMergedStrings = NULL; DWORD dwMergedCount = dwCount1; DWORD dwSecondIndex = 0; DWORD dwFirstIndex = 0; BOOLEAN bFound = FALSE; status = RTL_ALLOCATE(&ppszMergedStrings, PSTR, sizeof(PSTR) * (dwCount1 + dwCount2)); BAIL_ON_NT_STATUS(status); memcpy(ppszMergedStrings, ppszStrings1, sizeof(*ppszStrings1) * dwCount1); for (dwSecondIndex = 0; dwSecondIndex < dwCount2; dwSecondIndex++) { bFound = FALSE; for (dwFirstIndex = 0; dwFirstIndex < dwCount1; dwFirstIndex++) { if (!strcasecmp(ppszStrings1[dwFirstIndex], ppszStrings2[dwSecondIndex])) { bFound = TRUE; break; } } if (!bFound) { ppszMergedStrings[dwMergedCount++] = ppszStrings2[dwSecondIndex]; } } *pdwMergedCount = dwMergedCount; *pppszMergedStrings = ppszMergedStrings; cleanup: return status; error: RTL_FREE(ppszMergedStrings); goto cleanup; } static NTSTATUS LsaMapSecurityGetAccessTokenCreateInformationFromObjectInfoAndGroups( IN PLW_MAP_SECURITY_PLUGIN_CONTEXT Context, OUT PACCESS_TOKEN_CREATE_INFORMATION* CreateInformation, IN PLSA_MAP_SECURITY_OBJECT_INFO pObjectInfo, IN DWORD dwInputSidCount, IN PSID* ppInputSids ) { NTSTATUS status = STATUS_SUCCESS; DWORD dwError = LW_ERROR_SUCCESS; PACCESS_TOKEN_CREATE_INFORMATION createInformation = NULL; ULONG i = 0; HANDLE hConnection = NULL; PSTR* ppszInputSids = NULL; PSTR* ppszSuppGroupSids = NULL; PSTR* ppszGroupSids = NULL; DWORD dwGroupCount = 0; DWORD dwSuppGroupCount = 0; PSID pPrimaryGidSid = NULL; PSTR pszPrimaryGidSid = NULL; /* Allocate array for string forms of sids, plus extra slots for user sid and sid from primary UNIX gid */ status = RTL_ALLOCATE(&ppszInputSids, PSTR, sizeof(*ppszInputSids) * (dwInputSidCount + 2)); GOTO_CLEANUP_ON_STATUS(status); for (i = 0; i < dwInputSidCount; i++) { status = RtlAllocateCStringFromSid(&ppszInputSids[i], ppInputSids[i]); GOTO_CLEANUP_ON_STATUS(status); } /* Add user SID itself to list */ status = RtlAllocateCStringFromSid(&ppszInputSids[dwInputSidCount++], pObjectInfo->Sid); GOTO_CLEANUP_ON_STATUS(status); status = LsaMapSecurityOpenConnection(Context, &hConnection); GOTO_CLEANUP_ON_STATUS(status); if (IsSetFlag(pObjectInfo->Flags, LSA_MAP_SECURITY_OBJECT_INFO_FLAG_VALID_GID)) { status = LsaMapSecurityGetSidFromId( Context, &pPrimaryGidSid, FALSE, pObjectInfo->Gid); GOTO_CLEANUP_ON_STATUS(status); status = RtlAllocateCStringFromSid(&pszPrimaryGidSid, pPrimaryGidSid); GOTO_CLEANUP_ON_STATUS(status); for (i = 0; i < dwInputSidCount; i++) { if (!strcasecmp(ppszInputSids[i], pszPrimaryGidSid)) { break; } } if (i == dwInputSidCount) { ppszInputSids[dwInputSidCount++] = pszPrimaryGidSid; pszPrimaryGidSid = NULL; } } dwError = LsaQueryMemberOf( hConnection, NULL, LSA_FIND_FLAGS_LOCAL, dwInputSidCount, ppszInputSids, &dwSuppGroupCount, &ppszSuppGroupSids); if (IS_NOT_FOUND_ERROR(dwError)) { dwError = 0; dwGroupCount = 0; } else { status = LsaLsaErrorToNtStatus(dwError); GOTO_CLEANUP_ON_STATUS(status); } LsaMapSecurityCloseConnection(Context, &hConnection); status = LsaMapSecurityMergeStringLists( dwInputSidCount, ppszInputSids, dwSuppGroupCount, ppszSuppGroupSids, &dwGroupCount, &ppszGroupSids); GOTO_CLEANUP_ON_STATUS(status); // // Allocate token create information with enough space // for any potential extra GIDs. // status = LsaMapSecurityAllocateAccessTokenCreateInformation( &createInformation, dwGroupCount); GOTO_CLEANUP_ON_STATUS(status); // // TOKEN_UNIX // if (IsSetFlag(pObjectInfo->Flags, LSA_MAP_SECURITY_OBJECT_INFO_FLAG_VALID_UID)) { createInformation->Unix->Uid = pObjectInfo->Uid; } else { status = STATUS_NO_SUCH_USER; GOTO_CLEANUP(); } if (IsSetFlag(pObjectInfo->Flags, LSA_MAP_SECURITY_OBJECT_INFO_FLAG_VALID_GID)) { createInformation->Unix->Gid = pObjectInfo->Gid; } else { status = STATUS_NO_SUCH_GROUP; GOTO_CLEANUP(); } // TODO-Should the token even have a umask, and if so, // where should it come from? createInformation->Unix->Umask = 0022; // // TOKEN_USER // status = RtlDuplicateSid(&createInformation->User->User.Sid, pObjectInfo->Sid); GOTO_CLEANUP_ON_STATUS(status); // // TOKEN_GROUPS // for (i = 0; i < dwGroupCount; i++) { PSID_AND_ATTRIBUTES group = &createInformation->Groups->Groups[createInformation->Groups->GroupCount]; status = RtlAllocateSidFromCString( &group->Sid, ppszGroupSids[i]); GOTO_CLEANUP_ON_STATUS(status); group->Attributes = SE_GROUP_ENABLED; createInformation->Groups->GroupCount++; } // // TOKEN_OWNER // status = RtlDuplicateSid(&createInformation->Owner->Owner, pObjectInfo->Sid); GOTO_CLEANUP_ON_STATUS(status); // // TOKEN_PRIMARY_GROUP // if (pObjectInfo->PrimaryGroupSid) { status = RtlDuplicateSid( &createInformation->PrimaryGroup->PrimaryGroup, pObjectInfo->PrimaryGroupSid); GOTO_CLEANUP_ON_STATUS(status); } else { status = LsaMapSecurityGetSidFromId( Context, &createInformation->PrimaryGroup->PrimaryGroup, FALSE, createInformation->Unix->Gid); GOTO_CLEANUP_ON_STATUS(status); } // // TOKEN_DEFAULT_DACL // status = LsaMapSecurityCreateTokenDefaultDacl( &createInformation->DefaultDacl->DefaultDacl, createInformation->Owner->Owner); GOTO_CLEANUP_ON_STATUS(status); cleanup: if (!NT_SUCCESS(status)) { LsaMapSecurityFreeAccessTokenCreateInformation(Context, &createInformation); } RTL_FREE(&pszPrimaryGidSid); RTL_FREE(&pPrimaryGidSid); RTL_FREE(&ppszGroupSids); LwFreeStringArray(ppszSuppGroupSids, dwSuppGroupCount); LwFreeStringArray(ppszInputSids, dwInputSidCount); *CreateInformation = createInformation; return status; } static NTSTATUS LsaMapSecurityGetAccessTokenCreateInformation( IN PLW_MAP_SECURITY_PLUGIN_CONTEXT Context, OUT PACCESS_TOKEN_CREATE_INFORMATION* CreateInformation, IN OPTIONAL PUNICODE_STRING Username, IN OPTIONAL PULONG Uid, IN OPTIONAL PULONG Gid ) { NTSTATUS status = STATUS_SUCCESS; PACCESS_TOKEN_CREATE_INFORMATION createInformation = NULL; LSA_MAP_SECURITY_OBJECT_INFO objectInfo = { 0 }; PSTR pszUsername = NULL; if (IS_BOTH_OR_NEITHER(Username, Uid) || (Gid && !Uid)) { status = STATUS_INVALID_PARAMETER; GOTO_CLEANUP(); } if (Username) { status = LwRtlCStringAllocateFromUnicodeString(&pszUsername, Username); GOTO_CLEANUP_ON_STATUS(status); } status = LsaMapSecurityResolveObjectInfo( Context, TRUE, pszUsername, Uid, &objectInfo); GOTO_CLEANUP_ON_STATUS(status); status = LsaMapSecurityGetAccessTokenCreateInformationFromObjectInfo( Context, &createInformation, &objectInfo, Gid); GOTO_CLEANUP_ON_STATUS(status); cleanup: if (!NT_SUCCESS(status)) { LsaMapSecurityFreeAccessTokenCreateInformation(Context, &createInformation); } LwRtlCStringFree(&pszUsername); LsaMapSecurityFreeObjectInfo(&objectInfo); *CreateInformation = createInformation; return status; } static NTSTATUS LsaMapSecurityGetPacInfoFromGssContext( IN PLW_MAP_SECURITY_PLUGIN_CONTEXT Context, OUT PAC_LOGON_INFO** ppPac, IN LW_MAP_SECURITY_GSS_CONTEXT GssContext ) { static const CHAR szLogonInfoUrn[] = "urn:mspac:logon-info"; NTSTATUS status = STATUS_SUCCESS; OM_uint32 minorStatus = 0; OM_uint32 majorStatus = 0; error_status_t dceStatus = 0; gss_name_t srcName = GSS_C_NO_NAME; gss_buffer_desc logonInfoUrn = { .length = sizeof(szLogonInfoUrn) - 1, .value = (PBYTE) szLogonInfoUrn }; gss_buffer_desc pacData = {0}; gss_buffer_desc displayData = {0}; PAC_LOGON_INFO *pPac = NULL; int more = -1; majorStatus = gss_inquire_context( &minorStatus, GssContext, &srcName, NULL, NULL, NULL, NULL, NULL, NULL); if (majorStatus != GSS_S_COMPLETE) { status = STATUS_UNSUCCESSFUL; BAIL_ON_NT_STATUS(status); } majorStatus = gss_get_name_attribute( &minorStatus, srcName, &logonInfoUrn, NULL, NULL, &pacData, &displayData, &more); if (majorStatus != GSS_S_COMPLETE) { status = STATUS_UNSUCCESSFUL; BAIL_ON_NT_STATUS(status); } if (pacData.value == NULL) { status = STATUS_UNSUCCESSFUL; BAIL_ON_NT_STATUS(status); } dceStatus = DecodePacLogonInfo( pacData.value, pacData.length, &pPac); if (dceStatus) { status = STATUS_UNSUCCESSFUL; BAIL_ON_NT_STATUS(status); } *ppPac = pPac; cleanup: if (pacData.value) { gss_release_buffer(&minorStatus, &pacData); } if (displayData.value) { gss_release_buffer(&minorStatus, &displayData); } if (srcName) { gss_release_name(&minorStatus, &srcName); } return status; error: *ppPac = NULL; if (pPac) { FreePacLogonInfo(pPac); } goto cleanup; } static NTSTATUS LsaMapSecurityGetAccessTokenCreateInformationFromUid( IN PLW_MAP_SECURITY_PLUGIN_CONTEXT Context, OUT PACCESS_TOKEN_CREATE_INFORMATION* CreateInformation, IN ULONG Uid, IN PULONG Gid ) { return LsaMapSecurityGetAccessTokenCreateInformation( Context, CreateInformation, NULL, &Uid, Gid); } static NTSTATUS LsaMapSecurityGetAccessTokenCreateInformationFromUsername( IN PLW_MAP_SECURITY_PLUGIN_CONTEXT Context, OUT PACCESS_TOKEN_CREATE_INFORMATION* CreateInformation, IN PUNICODE_STRING Username ) { return LsaMapSecurityGetAccessTokenCreateInformation( Context, CreateInformation, Username, NULL, NULL); } static NTSTATUS LsaMapSecurityGetAccessTokenCreateInformationFromGssContext( IN PLW_MAP_SECURITY_PLUGIN_CONTEXT Context, OUT PACCESS_TOKEN_CREATE_INFORMATION* CreateInformation, IN LW_MAP_SECURITY_GSS_CONTEXT GssContext ) { NTSTATUS status = STATUS_SUCCESS; PAC_LOGON_INFO* pPac = NULL; LSA_MAP_SECURITY_OBJECT_INFO objectInfo = {0}; DWORD dwInputSidCount = 0; PSID* ppInputSids = NULL; DWORD dwIndex = 0; PSID pSid = NULL; status = LsaMapSecurityGetPacInfoFromGssContext( Context, &pPac, GssContext); BAIL_ON_NT_STATUS(status); status = LsaMapSecurityResolveObjectInfoFromPac(Context, pPac, &objectInfo); BAIL_ON_NT_STATUS(status); status = RTL_ALLOCATE(&ppInputSids, PSID, sizeof(PSID) * (pPac->info3.base.groups.count + pPac->res_groups.count + pPac->info3.sidcount)); BAIL_ON_NT_STATUS(status); for (dwIndex = 0; dwIndex < pPac->info3.base.groups.count; dwIndex++) { status = LsaMapSecurityConstructSid( pPac->info3.base.domain_sid, pPac->info3.base.groups.rids[dwIndex].rid, &pSid); BAIL_ON_NT_STATUS(status); ppInputSids[dwInputSidCount++] = pSid; pSid = NULL; } for (dwIndex = 0; dwIndex < pPac->res_groups.count; dwIndex++) { status = LsaMapSecurityConstructSid( pPac->res_group_dom_sid, pPac->res_groups.rids[dwIndex].rid, &pSid); BAIL_ON_NT_STATUS(status); ppInputSids[dwInputSidCount++] = pSid; pSid = NULL; } for (dwIndex = 0; dwIndex < pPac->info3.sidcount; dwIndex++) { status = RtlDuplicateSid(&pSid, pPac->info3.sids[dwIndex].sid); BAIL_ON_NT_STATUS(status); ppInputSids[dwInputSidCount++] = pSid; pSid = NULL; } status = LsaMapSecurityGetAccessTokenCreateInformationFromObjectInfoAndGroups( Context, CreateInformation, &objectInfo, dwInputSidCount, ppInputSids); BAIL_ON_NT_STATUS(status); cleanup: RTL_FREE(&pSid); for (dwIndex = 0; dwIndex < dwInputSidCount; dwIndex++) { RTL_FREE(&ppInputSids[dwIndex]); } RTL_FREE(&ppInputSids); if (pPac) { FreePacLogonInfo(pPac); } LsaMapSecurityFreeObjectInfo(&objectInfo); return status; error: goto cleanup; } static VOID LsaMapSecurityFreeContext( IN OUT PLW_MAP_SECURITY_PLUGIN_CONTEXT* Context ) { PLW_MAP_SECURITY_PLUGIN_CONTEXT context = *Context; if (context) { RTL_FREE(&context); *Context = NULL; } } static LW_MAP_SECURITY_PLUGIN_INTERFACE gLsaMapSecurityPluginInterface = { .FreeContext = LsaMapSecurityFreeContext, .GetIdFromSid = LsaMapSecurityGetIdFromSid, .GetSidFromId = LsaMapSecurityGetSidFromId, .DuplicateSid = LsaMapSecurityDuplicateSid, .FreeSid = LsaMapSecurityFreeSid, .GetAccessTokenCreateInformationFromUid = LsaMapSecurityGetAccessTokenCreateInformationFromUid, .GetAccessTokenCreateInformationFromUsername = LsaMapSecurityGetAccessTokenCreateInformationFromUsername, .GetAccessTokenCreateInformationFromGssContext = LsaMapSecurityGetAccessTokenCreateInformationFromGssContext, .FreeAccessTokenCreateInformation = LsaMapSecurityFreeAccessTokenCreateInformation, }; NTSTATUS MapSecurityPluginCreateContext( OUT PLW_MAP_SECURITY_PLUGIN_CONTEXT* Context, OUT PLW_MAP_SECURITY_PLUGIN_INTERFACE* Interface ) { NTSTATUS status = STATUS_SUCCESS; PLW_MAP_SECURITY_PLUGIN_CONTEXT context = NULL; // compiler type check for this function LWMSP_CREATE_CONTEXT_CALLBACK unused = MapSecurityPluginCreateContext; assert(unused); status = RTL_ALLOCATE(&context, LW_MAP_SECURITY_PLUGIN_CONTEXT, sizeof(*context)); GOTO_CLEANUP_ON_STATUS(status); cleanup: if (!NT_SUCCESS(status)) { LsaMapSecurityFreeContext(&context); } *Context = context; *Interface = NT_SUCCESS(status) ? &gLsaMapSecurityPluginInterface : NULL; return status; } static NTSTATUS LsaMapSecurityCreateTokenDefaultDacl( OUT PACL *ppDacl, IN PSID pOwnerSid ) { NTSTATUS status = STATUS_SUCCESS; ULONG ulDaclSize = 0; PACL pDacl = NULL; ulDaclSize = ACL_HEADER_SIZE + sizeof(ACCESS_ALLOWED_ACE) + RtlLengthSid(pOwnerSid) + sizeof(ULONG); status = LW_RTL_ALLOCATE(&pDacl, VOID, ulDaclSize); GOTO_CLEANUP_ON_STATUS(status); status = RtlCreateAcl(pDacl, ulDaclSize, ACL_REVISION); GOTO_CLEANUP_ON_STATUS(status); status = RtlAddAccessAllowedAceEx( pDacl, ACL_REVISION, 0, GENERIC_ALL, pOwnerSid); GOTO_CLEANUP_ON_STATUS(status); *ppDacl = pDacl; cleanup: if (!NT_SUCCESS(status)) { LW_RTL_FREE(&pDacl); } return status; } /* local variables: mode: c c-basic-offset: 4 indent-tabs-mode: nil tab-width: 4 end: */