mirror of
https://github.com/winfsp/winfsp.git
synced 2025-04-22 16:33:02 -05:00
953 lines
32 KiB
C
953 lines
32 KiB
C
/**
|
|
* @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-2017 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 file in
|
|
* accordance with the commercial license agreement provided with the
|
|
* software.
|
|
*/
|
|
|
|
#include <dll/library.h>
|
|
#include <aclapi.h>
|
|
#define _NTDEF_
|
|
#include <ntsecapi.h>
|
|
|
|
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 < 0x200000)
|
|
{
|
|
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;
|
|
|
|
Result = FspPosixMapUidToSid(0x10100, &WorldSid);
|
|
if (!NT_SUCCESS(Result))
|
|
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 != WorldSid)
|
|
FspDeleteSid(WorldSid, FspPosixMapUidToSid);
|
|
|
|
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)
|
|
{
|
|
Result = FspPosixMapUidToSid(0x10100, &WorldSid);
|
|
if (!NT_SUCCESS(Result))
|
|
goto exit;
|
|
|
|
Result = FspPosixMapUidToSid(11, &AuthUsersSid);
|
|
if (!NT_SUCCESS(Result))
|
|
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:
|
|
if (0 != AuthUsersSid)
|
|
FspDeleteSid(AuthUsersSid, FspPosixMapUidToSid);
|
|
|
|
if (0 != WorldSid)
|
|
FspDeleteSid(WorldSid, FspPosixMapUidToSid);
|
|
|
|
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;
|
|
}
|
|
}
|