/** * @file dll/posix.c * POSIX Interop. * * This file provides routines for Windows/POSIX interoperability. It is based * on "Services for UNIX" and Cygwin. See the following documents: * * [PERMS] * https://technet.microsoft.com/en-us/library/bb463216.aspx * [WKSID] * https://support.microsoft.com/en-us/kb/243330 * [IDMAP] * https://cygwin.com/cygwin-ug-net/ntsec.html * [SNAME] * https://www.cygwin.com/cygwin-ug-net/using-specialnames.html * * @copyright 2015-2019 Bill Zissimopoulos */ /* * This file is part of WinFsp. * * You can redistribute it and/or modify it under the terms of the GNU * General Public License version 3 as published by the Free Software * Foundation. * * Licensees holding a valid commercial license may use this software * in accordance with the commercial license agreement provided in * conjunction with the software. The terms and conditions of any such * commercial license agreement shall govern, supersede, and render * ineffective any application of the GPLv3 license to this software, * notwithstanding of any reference thereto in the software or * associated repository. */ #include #include #define _NTDEF_ #include static PISID FspPosixCreateSid(BYTE Authority, ULONG Count, ...); static INIT_ONCE FspPosixInitOnce = INIT_ONCE_STATIC_INIT; union { SID V; UINT8 B[sizeof(SID) - sizeof(DWORD) + (1 * sizeof(DWORD))]; } FspUnmappedSidBuf = { /* S-1-0-65534 */ .V.Revision = SID_REVISION, .V.SubAuthorityCount = 1, .V.IdentifierAuthority.Value[5] = 0, .V.SubAuthority[0] = 65534, }; static PISID FspAccountDomainSid, FspPrimaryDomainSid; #define FspUnmappedSid (&FspUnmappedSidBuf.V) #define FspUnmappedUid (65534) static BOOL WINAPI FspPosixInitialize( PINIT_ONCE InitOnce, PVOID Parameter, PVOID *Context) { static LSA_OBJECT_ATTRIBUTES Obja; LSA_HANDLE PolicyHandle = 0; PPOLICY_ACCOUNT_DOMAIN_INFO AccountDomainInfo = 0; PPOLICY_DNS_DOMAIN_INFO PrimaryDomainInfo = 0; BYTE Count; ULONG Size; NTSTATUS Result; Result = LsaOpenPolicy(0, &Obja, POLICY_VIEW_LOCAL_INFORMATION, &PolicyHandle); if (!NT_SUCCESS(Result)) goto exit; Result = LsaQueryInformationPolicy(PolicyHandle, PolicyAccountDomainInformation, &AccountDomainInfo); if (NT_SUCCESS(Result) && 0 != AccountDomainInfo && 0 != AccountDomainInfo->DomainSid) { Count = *GetSidSubAuthorityCount(AccountDomainInfo->DomainSid); Size = sizeof(SID) - sizeof(DWORD) + (Count * sizeof(DWORD)); FspAccountDomainSid = MemAlloc(Size); if (0 != FspAccountDomainSid) memcpy(FspAccountDomainSid, AccountDomainInfo->DomainSid, Size); } Result = LsaQueryInformationPolicy(PolicyHandle, PolicyDnsDomainInformation, &PrimaryDomainInfo); if (NT_SUCCESS(Result) && 0 != PrimaryDomainInfo && 0 != PrimaryDomainInfo->Sid) { Count = *GetSidSubAuthorityCount(PrimaryDomainInfo->Sid); Size = sizeof(SID) - sizeof(DWORD) + (Count * sizeof(DWORD)); FspPrimaryDomainSid = MemAlloc(Size); if (0 != FspPrimaryDomainSid) memcpy(FspPrimaryDomainSid, PrimaryDomainInfo->Sid, Size); } exit: if (0 != PrimaryDomainInfo) LsaFreeMemory(PrimaryDomainInfo); if (0 != AccountDomainInfo) LsaFreeMemory(AccountDomainInfo); if (0 != PolicyHandle) LsaClose(PolicyHandle); return TRUE; } VOID FspPosixFinalize(BOOLEAN Dynamic) { /* * This function is called during DLL_PROCESS_DETACH. We must therefore keep * finalization tasks to a minimum. */ if (Dynamic) { MemFree(FspAccountDomainSid); MemFree(FspPrimaryDomainSid); } } FSP_API NTSTATUS FspPosixMapUidToSid(UINT32 Uid, PSID *PSid) { InitOnceExecuteOnce(&FspPosixInitOnce, FspPosixInitialize, 0, 0); *PSid = 0; /* * UID namespace partitioning (from [IDMAP] rules): * * 0x000000 + RID S-1-5-RID,S-1-5-32-RID * 0x000ffe OtherSession * 0x000fff CurrentSession * 0x001000 * X + RID S-1-5-X-RID ([WKSID]: X=1-15,17-21,32,64,80,83) * 0x010000 + 0x100 * X + Y S-1-X-Y ([WKSID]: X=1,2,3,4,5,9,16) * 0x030000 + RID S-1-5-21-X-Y-Z-RID * 0x060000 + RID S-1-16-RID * 0x100000 + RID S-1-5-21-X-Y-Z-RID */ /* [IDMAP] * Well-known SIDs in the NT_AUTHORITY domain of the S-1-5-RID type, * or aliases of the S-1-5-32-RID type are mapped to the uid/gid value RID. * Examples: * "SYSTEM" S-1-5-18 <=> uid/gid: 18 * "Users" S-1-5-32-545 <=> uid/gid: 545 */ if (0x200 > Uid || 1000 == Uid) *PSid = FspPosixCreateSid(5, 1, Uid); else if (1000 > Uid) *PSid = FspPosixCreateSid(5, 2, 32, Uid); /* [IDMAP] * Logon SIDs: The LogonSid of the current user's session is converted * to the fixed uid 0xfff == 4095 and named "CurrentSession". Any other * LogonSid is converted to the fixed uid 0xffe == 4094 and named * "OtherSession". */ else if (0xfff == Uid || 0xffe == Uid) { /* * Actually we do not support Logon SID's for translation. * We need an access token to find its Logon SID and we do not have one. */ } /* [IDMAP] * Accounts from the local machine's user DB (SAM): * S-1-5-21-X-Y-Z-RID <=> uid/gid: 0x30000 + RID * * Accounts from the machine's primary domain: * S-1-5-21-X-Y-Z-RID <=> uid/gid: 0x100000 + RID * * Accounts from a trusted domain of the machine's primary domain: * S-1-5-21-X-Y-Z-RID <=> uid/gid: trustPosixOffset(domain) + RID */ else if (0x30000 <= Uid && Uid < 0x40000) { if (0 != FspAccountDomainSid && 5 == FspAccountDomainSid->IdentifierAuthority.Value[5] && 4 == FspAccountDomainSid->SubAuthorityCount) { *PSid = FspPosixCreateSid(5, 5, 21, FspAccountDomainSid->SubAuthority[1], FspAccountDomainSid->SubAuthority[2], FspAccountDomainSid->SubAuthority[3], Uid - 0x30000); } } else if (0x100000 <= Uid && Uid < 0xff000000) { if (0 != FspPrimaryDomainSid && 5 == FspPrimaryDomainSid->IdentifierAuthority.Value[5] && 4 == FspPrimaryDomainSid->SubAuthorityCount) { *PSid = FspPosixCreateSid(5, 5, 21, FspPrimaryDomainSid->SubAuthority[1], FspPrimaryDomainSid->SubAuthority[2], FspPrimaryDomainSid->SubAuthority[3], Uid - 0x100000); } } /* * I am sorry, I am not going to bother with all that trustPosixOffset stuff. * But if you need it, I accept patches :) */ /* [IDMAP] * Mandatory Labels: * S-1-16-RID <=> uid/gid: 0x60000 + RID */ else if (0x60000 <= Uid && Uid < 0x70000) *PSid = FspPosixCreateSid(16, 1, Uid - 0x60000); /* [IDMAP] * Other well-known SIDs: * S-1-X-Y <=> uid/gid: 0x10000 + 0x100 * X + Y */ else if (0x10000 <= Uid && Uid < 0x11000) *PSid = FspPosixCreateSid((Uid - 0x10000) >> 8, 1, (Uid - 0x10000) & 0xff); /* [IDMAP] * Other well-known SIDs in the NT_AUTHORITY domain (S-1-5-X-RID): * S-1-5-X-RID <=> uid/gid: 0x1000 * X + RID */ else if (FspUnmappedUid != Uid && 0x1000 <= Uid && Uid < 0x100000) *PSid = FspPosixCreateSid(5, 2, Uid >> 12, Uid & 0xfff); if (0 == *PSid) *PSid = FspUnmappedSid; return STATUS_SUCCESS; } FSP_API NTSTATUS FspPosixMapSidToUid(PSID Sid, PUINT32 PUid) { InitOnceExecuteOnce(&FspPosixInitOnce, FspPosixInitialize, 0, 0); BYTE Authority; BYTE Count; UINT32 SubAuthority0, Rid; *PUid = -1; if (!IsValidSid(Sid) || 0 == (Count = *GetSidSubAuthorityCount(Sid))) return STATUS_INVALID_SID; Authority = GetSidIdentifierAuthority(Sid)->Value[5]; SubAuthority0 = 2 <= Count ? *GetSidSubAuthority(Sid, 0) : 0; Rid = *GetSidSubAuthority(Sid, Count - 1); if (5 == Authority) { /* [IDMAP] * Well-known SIDs in the NT_AUTHORITY domain of the S-1-5-RID type, * or aliases of the S-1-5-32-RID type are mapped to the uid/gid value RID. * Examples: * "SYSTEM" S-1-5-18 <=> uid/gid: 18 * "Users" S-1-5-32-545 <=> uid/gid: 545 */ if (1 == Count) *PUid = Rid; else if (2 == Count && 32 == SubAuthority0) *PUid = Rid; /* [IDMAP] * Logon SIDs: The LogonSid of the current user's session is converted * to the fixed uid 0xfff == 4095 and named "CurrentSession". Any other * LogonSid is converted to the fixed uid 0xffe == 4094 and named * "OtherSession". */ else if (2 <= Count && 5 == SubAuthority0) { /* * Actually we do not support Logon SID's for translation. * We need an access token to find its Logon SID and we do not have one. */ } /* [IDMAP] * Accounts from the local machine's user DB (SAM): * S-1-5-21-X-Y-Z-RID <=> uid/gid: 0x30000 + RID * * Accounts from the machine's primary domain: * S-1-5-21-X-Y-Z-RID <=> uid/gid: 0x100000 + RID * * Accounts from a trusted domain of the machine's primary domain: * S-1-5-21-X-Y-Z-RID <=> uid/gid: trustPosixOffset(domain) + RID */ else if (5 <= Count && 21 == SubAuthority0) { /* * The order is important! A server that is also a domain controller * has PrimaryDomainSid == AccountDomainSid. */ BOOL EqualDomains = FALSE; if (0 != FspPrimaryDomainSid && EqualDomainSid(FspPrimaryDomainSid, Sid, &EqualDomains) && EqualDomains) *PUid = 0x100000 + Rid; else if (0 != FspAccountDomainSid && EqualDomainSid(FspAccountDomainSid, Sid, &EqualDomains) && EqualDomains) *PUid = 0x30000 + Rid; /* * I am sorry, I am not going to bother with all that trustPosixOffset stuff. * But if you need it, I accept patches :) */ } /* [IDMAP] * Other well-known SIDs in the NT_AUTHORITY domain (S-1-5-X-RID): * S-1-5-X-RID <=> uid/gid: 0x1000 * X + RID */ else if (2 == Count) { *PUid = 0x1000 * SubAuthority0 + Rid; } } else if (16 == Authority) { /* [IDMAP] * Mandatory Labels: * S-1-16-RID <=> uid/gid: 0x60000 + RID */ *PUid = 0x60000 + Rid; } else if ( FspUnmappedSid->IdentifierAuthority.Value[5] != Authority || FspUnmappedSid->SubAuthority[0] != Rid) { /* [IDMAP] * Other well-known SIDs: * S-1-X-Y <=> uid/gid: 0x10000 + 0x100 * X + Y */ *PUid = 0x10000 + 0x100 * Authority + Rid; } if (-1 == *PUid) *PUid = FspUnmappedUid; return STATUS_SUCCESS; } static PISID FspPosixCreateSid(BYTE Authority, ULONG Count, ...) { PISID Sid; SID_IDENTIFIER_AUTHORITY IdentifierAuthority; va_list ap; Sid = MemAlloc(sizeof(SID) - sizeof(DWORD) + (Count * sizeof(DWORD))); if (0 == Sid) return 0; memset(&IdentifierAuthority, 0, sizeof IdentifierAuthority); IdentifierAuthority.Value[5] = Authority; InitializeSid(Sid, &IdentifierAuthority, (BYTE)Count); va_start(ap, Count); for (ULONG Index = 0; Count > Index; Index++) Sid->SubAuthority[Index] = va_arg(ap, DWORD); va_end(ap); return Sid; } FSP_API VOID FspDeleteSid(PSID Sid, NTSTATUS (*CreateFunc)()) { if (FspUnmappedSid == Sid) ; else if ((NTSTATUS (*)())FspPosixMapUidToSid == CreateFunc) MemFree(Sid); } /* [PERMS] * By default, all access-allowed ACEs will contain the following Windows access rights. */ #define FspPosixDefaultPerm \ (SYNCHRONIZE | READ_CONTROL | FILE_READ_ATTRIBUTES | FILE_READ_EA) /* [PERMS] * There are some additional Windows access rights that are always set in the * access-allowed ACE for the file's owner. */ #define FspPosixOwnerDefaultPerm \ (FspPosixDefaultPerm | DELETE | WRITE_DAC | WRITE_OWNER | FILE_WRITE_ATTRIBUTES | FILE_WRITE_EA) static inline ACCESS_MASK FspPosixMapPermissionToAccessMask(UINT32 Mode, UINT32 Perm) { /* * We use only the 0040000 (directory) and 0001000 (sticky) bits from Mode. * If this is a directory and it does not have the sticky bit set (and the * write permission is enabled) we add FILE_DELETE_CHILD access. * * When calling this function for computing the Owner access mask, we always * pass Mode & ~0001000 to remove the sticky bit and thus add FILE_DELETE_CHILD * access if it is a directory. For Group and World permissions we do not * remove the sticky bit as we do not want FILE_DELETE_CHILD access in these * cases. */ ACCESS_MASK DeleteChild = 0040000 == (Mode & 0041000) ? FILE_DELETE_CHILD : 0; /* [PERMS] * Additionally, if the UNIX read permission bit is set, then the Windows * File_Read access right is added to the ACE. When enabled on directories, * this allows them to be searched. When enabled on files, it allows the data * to be viewed. If the UNIX execute permission bit is set, then the Windows * File_Execute access right is added to the ACE. On directories this enables * the directory to be traversed. On files it allows the file to be executed. * * If the UNIX write permission bit is set then the following Windows access * rights are added: Write_Data, Write_Attributes, Append_Data, Delete_Child. * * Notice how Windows has four separate access rights to UNIX's single "write" * permission. In UNIX, the write permission bit on a directory permits both * the creation and removal of new files or sub-directories in the directory. * On Windows, the Write_Data access right controls the creation of new * sub-files and the Delete_Child access right controls the deletion. The * Delete_Child access right is not always present in all ACEs. In the case * where the UNIX sticky-bit is enabled, the Delete_Child bit will be set only * in the file owner ACE and no other ACEs. This will permit only the directory * owner to remove any files or sub-directories from this directory regardless * of the ownership on these sub-files. Other users will be allowed to delete * files or sub-directories only if they are granted the Delete access right * in an ACE of the file or sub-directory itself. */ return ((Perm & 4) ? FILE_READ_DATA : 0) | ((Perm & 2) ? FILE_WRITE_ATTRIBUTES | FILE_WRITE_DATA | FILE_APPEND_DATA | DeleteChild : 0) | ((Perm & 1) ? FILE_EXECUTE : 0); } FSP_API NTSTATUS FspPosixMapPermissionsToSecurityDescriptor( UINT32 Uid, UINT32 Gid, UINT32 Mode, PSECURITY_DESCRIPTOR *PSecurityDescriptor) { PSID OwnerSid = 0, GroupSid = 0, WorldSid = 0; UINT32 OwnerPerm, OwnerDeny, GroupPerm, GroupDeny, WorldPerm; PACL Acl = 0; SECURITY_DESCRIPTOR SecurityDescriptor; PSECURITY_DESCRIPTOR RelativeSecurityDescriptor = 0; ULONG Size; NTSTATUS Result; *PSecurityDescriptor = 0; Result = FspPosixMapUidToSid(Uid, &OwnerSid); if (!NT_SUCCESS(Result)) goto exit; Result = FspPosixMapUidToSid(Gid, &GroupSid); if (!NT_SUCCESS(Result)) goto exit; WorldSid = FspWksidGet(WinWorldSid); if (0 == WorldSid) { Result = STATUS_INSUFFICIENT_RESOURCES; goto exit; } OwnerPerm = (Mode & 0700) >> 6; GroupPerm = (Mode & 0070) >> 3; WorldPerm = (Mode & 0007); /* [PERMS] * What about the case where both owner and group are the same SID and * a chmod(1) request is made where the owner and the group permission * bits are different?. In this case, the most restrictive permissions * are chosen and assigned to both ACEs. */ if (EqualSid(OwnerSid, GroupSid)) OwnerPerm = GroupPerm = OwnerPerm & GroupPerm; /* [PERMS] * There are situations where one or two access-denied ACEs must be added * to the DACL. If you recall, the UNIX file access algorithm makes a * distinction between owner, group and other such that each is unique * and the ID used in an access request can only match one of them. * However, the Windows file access algorithm makes no such distinction * while scanning the DACL. If the ID in the request is granted permission * in any of the access-allowed ACEs then the request is permitted. This * is a problem when the owner permissions are specified to be more * restrictive than say the group or the other permissions (eg. when a * "chmod 577 foobar" is executed) So, to support UNIX semantics we must * examine the permissions granted to Everyone and if they are more * permissive than those in the group permissions then a special * access-denied ACE must be created for the group. And similarly, if * either the group or other permissions are more permissive than the * owner permissions, then an access-denied ACE must be created for the owner. */ OwnerDeny = (OwnerPerm ^ GroupPerm) & GroupPerm; OwnerDeny |= (OwnerPerm ^ WorldPerm) & WorldPerm; GroupDeny = (GroupPerm ^ WorldPerm) & WorldPerm; Size = sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) * 3 + sizeof(ACCESS_DENIED_ACE) * (!!OwnerDeny + !!GroupDeny); Size += GetLengthSid(OwnerSid) - sizeof(DWORD); Size += GetLengthSid(GroupSid) - sizeof(DWORD); Size += GetLengthSid(WorldSid) - sizeof(DWORD); if (OwnerDeny) Size += GetLengthSid(OwnerSid) - sizeof(DWORD); if (GroupDeny) Size += GetLengthSid(GroupSid) - sizeof(DWORD); Size += sizeof(DWORD) - 1; Size &= ~(sizeof(DWORD) - 1); Acl = MemAlloc(Size); if (0 == Acl) { Result = STATUS_INSUFFICIENT_RESOURCES; goto exit; } if (!InitializeAcl(Acl, Size, ACL_REVISION)) goto lasterror; if (!AddAccessAllowedAce(Acl, ACL_REVISION, FspPosixOwnerDefaultPerm | FspPosixMapPermissionToAccessMask(Mode & ~001000, OwnerPerm), OwnerSid)) goto lasterror; if (OwnerDeny) { if (!AddAccessDeniedAce(Acl, ACL_REVISION, ~FILE_WRITE_ATTRIBUTES & FspPosixMapPermissionToAccessMask(Mode & ~001000, OwnerDeny), OwnerSid)) goto lasterror; } if (!AddAccessAllowedAce(Acl, ACL_REVISION, FspPosixDefaultPerm | FspPosixMapPermissionToAccessMask(Mode, GroupPerm), GroupSid)) goto lasterror; if (GroupDeny) { if (!AddAccessDeniedAce(Acl, ACL_REVISION, FspPosixMapPermissionToAccessMask(Mode, GroupDeny), GroupSid)) goto lasterror; } if (!AddAccessAllowedAce(Acl, ACL_REVISION, FspPosixDefaultPerm | FspPosixMapPermissionToAccessMask(Mode, WorldPerm), WorldSid)) goto lasterror; if (!InitializeSecurityDescriptor(&SecurityDescriptor, SECURITY_DESCRIPTOR_REVISION)) goto lasterror; if (!SetSecurityDescriptorControl(&SecurityDescriptor, SE_DACL_PROTECTED, SE_DACL_PROTECTED)) goto lasterror; if (!SetSecurityDescriptorOwner(&SecurityDescriptor, OwnerSid, FALSE)) goto lasterror; if (!SetSecurityDescriptorGroup(&SecurityDescriptor, GroupSid, FALSE)) goto lasterror; if (!SetSecurityDescriptorDacl(&SecurityDescriptor, TRUE, Acl, FALSE)) goto lasterror; Size = 0; if (!MakeSelfRelativeSD(&SecurityDescriptor, 0, &Size) && ERROR_INSUFFICIENT_BUFFER != GetLastError()) goto lasterror; RelativeSecurityDescriptor = MemAlloc(Size); if (0 == RelativeSecurityDescriptor) { Result = STATUS_INSUFFICIENT_RESOURCES; goto exit; } if (!MakeSelfRelativeSD(&SecurityDescriptor, RelativeSecurityDescriptor, &Size)) goto lasterror; *PSecurityDescriptor = RelativeSecurityDescriptor; Result = STATUS_SUCCESS; exit: if (!NT_SUCCESS(Result)) MemFree(RelativeSecurityDescriptor); MemFree(Acl); if (0 != GroupSid) FspDeleteSid(GroupSid, FspPosixMapUidToSid); if (0 != OwnerSid) FspDeleteSid(OwnerSid, FspPosixMapUidToSid); return Result; lasterror: Result = FspNtStatusFromWin32(GetLastError()); goto exit; } static inline UINT32 FspPosixMapAccessMaskToPermission(ACCESS_MASK AccessMask) { /* [PERMS] * Once all the granted Windows access right bits have been collected, * then the UNIX permission bits are assembled. For each class, if the * Read_Data bit is granted, then the corresponding "r" permission bit * is set. If both the Write_Data and Append_Data access rights are * granted then the "w" permission bit is set. And finally, if the * Execute access right is granted, then the "x" permission bit is set. */ return ((AccessMask & FILE_READ_DATA) ? 4 : 0) | ((FILE_WRITE_DATA | FILE_APPEND_DATA) == (AccessMask & (FILE_WRITE_DATA | FILE_APPEND_DATA)) ? 2 : 0) | ((AccessMask & FILE_EXECUTE) ? 1 : 0); } FSP_API NTSTATUS FspPosixMapSecurityDescriptorToPermissions( PSECURITY_DESCRIPTOR SecurityDescriptor, PUINT32 PUid, PUINT32 PGid, PUINT32 PMode) { PSID OwnerSid = 0, GroupSid = 0, WorldSid = 0, AuthUsersSid = 0; BOOL Defaulted, DaclPresent; PACL Acl = 0; ACL_SIZE_INFORMATION AclSizeInfo; PACE_HEADER Ace; PSID AceSid; DWORD AceAccessMask; DWORD OwnerAllow, OwnerDeny, GroupAllow, GroupDeny, WorldAllow, WorldDeny; UINT32 Uid, Gid, Mode; NTSTATUS Result; *PUid = 0; *PGid = 0; *PMode = 0; if (!GetSecurityDescriptorOwner(SecurityDescriptor, &OwnerSid, &Defaulted)) goto lasterror; if (!GetSecurityDescriptorGroup(SecurityDescriptor, &GroupSid, &Defaulted)) goto lasterror; if (!GetSecurityDescriptorDacl(SecurityDescriptor, &DaclPresent, &Acl, &Defaulted)) goto lasterror; Result = FspPosixMapSidToUid(OwnerSid, &Uid); if (!NT_SUCCESS(Result)) goto exit; Result = FspPosixMapSidToUid(GroupSid, &Gid); if (!NT_SUCCESS(Result)) goto exit; if (0 != Acl) { WorldSid = FspWksidGet(WinWorldSid); if (0 == WorldSid) { Result = STATUS_INSUFFICIENT_RESOURCES; goto exit; } AuthUsersSid = FspWksidGet(WinAuthenticatedUserSid); if (0 == AuthUsersSid) { Result = STATUS_INSUFFICIENT_RESOURCES; goto exit; } OwnerAllow = OwnerDeny = GroupAllow = GroupDeny = WorldAllow = WorldDeny = 0; if (!GetAclInformation(Acl, &AclSizeInfo, sizeof AclSizeInfo, AclSizeInformation)) goto lasterror; /* [PERMS] * For each of the ACEs in the file's DACL */ for (ULONG Index = 0; AclSizeInfo.AceCount > Index; Index++) { if (!GetAce(Acl, Index, &Ace)) goto lasterror; /* [PERMS] * Ignore the ACE if it is not an access-denied or access-allowed type. */ if (ACCESS_ALLOWED_ACE_TYPE == Ace->AceType) { AceSid = &((PACCESS_ALLOWED_ACE)Ace)->SidStart; AceAccessMask = ((PACCESS_ALLOWED_ACE)Ace)->Mask; } else if (ACCESS_DENIED_ACE_TYPE == Ace->AceType) { AceSid = &((PACCESS_DENIED_ACE)Ace)->SidStart; AceAccessMask = ((PACCESS_DENIED_ACE)Ace)->Mask; } else continue; /* [PERMS] * If the ACE contains the Authenticated Users SID or the World SID then * add the allowed or denied access right bits into the "owner", "group" * and "other" collections. */ if (EqualSid(WorldSid, AceSid) || EqualSid(AuthUsersSid, AceSid)) { /* [PERMS] * If this is an access-denied ACE, then add each access right to the set * of denied rights in each collection but only if the access right is not * already present in the set of granted rights in that collection. Similarly * If this is an access-allowed ACE, then add each access right to the set * of granted rights in each collection but only if the access right is not * already present in the set of denied rights in that collection. */ if (ACCESS_ALLOWED_ACE_TYPE == Ace->AceType) { WorldAllow |= AceAccessMask & ~WorldDeny; GroupAllow |= AceAccessMask & ~GroupDeny; OwnerAllow |= AceAccessMask & ~OwnerDeny; } else //if (ACCESS_DENIED_ACE_TYPE == Ace->AceType) { WorldDeny |= AceAccessMask & ~WorldAllow; GroupDeny |= AceAccessMask & ~GroupAllow; OwnerDeny |= AceAccessMask & ~OwnerAllow; } } else { /* [PERMS] * Note that if the file owner and file group SIDs are the same, * then the access rights are saved in both the "owner" and "group" * collections. */ /* [PERMS] * If the ACE contains the file's group SID, then save the access rights * in the "group" collection as appropriate in the corresponding set of * granted or denied rights (as described above). */ if (EqualSid(GroupSid, AceSid)) { if (ACCESS_ALLOWED_ACE_TYPE == Ace->AceType) GroupAllow |= AceAccessMask & ~GroupDeny; else //if (ACCESS_DENIED_ACE_TYPE == Ace->AceType) GroupDeny |= AceAccessMask & ~GroupAllow; } /* [PERMS] * If the ACE contains the file's owner SID, then save the access rights * in the "owner" collection as appropriate in the corresponding set of * granted or denied rights (as described above). */ if (EqualSid(OwnerSid, AceSid)) { if (ACCESS_ALLOWED_ACE_TYPE == Ace->AceType) OwnerAllow |= AceAccessMask & ~OwnerDeny; else //if (ACCESS_DENIED_ACE_TYPE == Ace->AceType) OwnerDeny |= AceAccessMask & ~OwnerAllow; } } } Mode = (FspPosixMapAccessMaskToPermission(OwnerAllow) << 6) | (FspPosixMapAccessMaskToPermission(GroupAllow) << 3) | (FspPosixMapAccessMaskToPermission(WorldAllow)); if (0 != (OwnerAllow & FILE_DELETE_CHILD) && ( (0 == (GroupAllow & FILE_DELETE_CHILD) && 0 != (Mode & 0000020)) || (0 == (WorldAllow & FILE_DELETE_CHILD) && 0 != (Mode & 0000002)) )) Mode |= 0001000; /* sticky bit */ } else Mode = 0777; *PUid = Uid; *PGid = Gid; *PMode = Mode; Result = STATUS_SUCCESS; exit: return Result; lasterror: Result = FspNtStatusFromWin32(GetLastError()); goto exit; } /* * Services for Macintosh and Cygwin compatible filename transformation: * Transform characters invalid for Windows filenames to the Unicode * private use area in the U+F0XX range. * * The invalid maps are produced by the following Python script: * reserved = ['<', '>', ':', '"', '\\', '|', '?', '*'] * l = [str(int(0 < i < 32 or chr(i) in reserved)) for i in xrange(0, 128)] * print "0x%08x" % int("".join(l[0:32]), 2) * print "0x%08x" % int("".join(l[32:64]), 2) * print "0x%08x" % int("".join(l[64:96]), 2) * print "0x%08x" % int("".join(l[96:128]), 2) */ static UINT32 FspPosixInvalidPathChars[4] = { 0x7fffffff, 0x2020002b, 0x00000008, 0x00000008, }; FSP_API NTSTATUS FspPosixMapWindowsToPosixPathEx(PWSTR WindowsPath, char **PPosixPath, BOOLEAN Translate) { NTSTATUS Result; ULONG Size; char *PosixPath = 0, *p, *q; *PPosixPath = 0; Size = WideCharToMultiByte(CP_UTF8, 0, WindowsPath, -1, 0, 0, 0, 0); if (0 == Size) goto lasterror; PosixPath = MemAlloc(Size); if (0 == PosixPath) { Result = STATUS_INSUFFICIENT_RESOURCES; goto exit; } Size = WideCharToMultiByte(CP_UTF8, 0, WindowsPath, -1, PosixPath, Size, 0, 0); if (0 == Size) goto lasterror; if (Translate) { for (p = PosixPath, q = p; *p; p++) { unsigned char c = *p; if ('\\' == c) *q++ = '/'; /* encode characters in the Unicode private use area: U+F0XX -> XX */ else if (0xef == c && 0x80 == (0xfc & p[1]) && 0x80 == (0xc0 & p[2])) { c = ((p[1] & 0x3) << 6) | (p[2] & 0x3f); if (128 > c && (FspPosixInvalidPathChars[c >> 5] & (0x80000000 >> (c & 0x1f)))) *q++ = c, p += 2; else *q++ = *p++, *q++ = *p++, *q++ = *p; } else *q++ = c; } *q = '\0'; } *PPosixPath = PosixPath; Result = STATUS_SUCCESS; exit: if (!NT_SUCCESS(Result)) MemFree(PosixPath); return Result; lasterror: Result = FspNtStatusFromWin32(GetLastError()); goto exit; } FSP_API NTSTATUS FspPosixMapPosixToWindowsPathEx(const char *PosixPath, PWSTR *PWindowsPath, BOOLEAN Translate) { NTSTATUS Result; ULONG Size; PWSTR WindowsPath = 0, p; *PWindowsPath = 0; Size = MultiByteToWideChar(CP_UTF8, 0, PosixPath, -1, 0, 0); if (0 == Size) goto lasterror; WindowsPath = MemAlloc(Size * sizeof(WCHAR)); if (0 == PosixPath) { Result = STATUS_INSUFFICIENT_RESOURCES; goto exit; } Size = MultiByteToWideChar(CP_UTF8, 0, PosixPath, -1, WindowsPath, Size); if (0 == Size) goto lasterror; if (Translate) { for (p = WindowsPath; *p; p++) { WCHAR c = *p; if (L'/' == c) *p = L'\\'; else if (128 > c && (FspPosixInvalidPathChars[c >> 5] & (0x80000000 >> (c & 0x1f)))) *p |= 0xf000; } } *PWindowsPath = WindowsPath; Result = STATUS_SUCCESS; exit: if (!NT_SUCCESS(Result)) MemFree(WindowsPath); return Result; lasterror: Result = FspNtStatusFromWin32(GetLastError()); goto exit; } FSP_API VOID FspPosixDeletePath(void *Path) { MemFree(Path); } FSP_API VOID FspPosixEncodeWindowsPath(PWSTR WindowsPath, ULONG Size) { for (PWSTR p = WindowsPath, endp = p + Size; endp > p; p++) { WCHAR c = *p; if (L'\\' == c) *p = L'/'; /* encode characters in the Unicode private use area: U+F0XX -> XX */ else if (0xf000 <= c && c <= 0xf0ff) *p &= ~0xf000; } } FSP_API VOID FspPosixDecodeWindowsPath(PWSTR WindowsPath, ULONG Size) { for (PWSTR p = WindowsPath, endp = p + Size; endp > p; p++) { WCHAR c = *p; if (L'/' == c) *p = L'\\'; else if (128 > c && (FspPosixInvalidPathChars[c >> 5] & (0x80000000 >> (c & 0x1f)))) *p |= 0xf000; } }