/*++ Copyright (c) 1989 Microsoft Corporation Module Name: psxsup.c Abstract: PSX Support Routines Author: Mark Lucovsky (markl) 27-Nov-1989 Revision History: --*/ #include "psxsrv.h" #include #include #include #include "sesport.h" #include "seposix.h" #define UNICODE #include #include #include #include // // This number will never be returned in the PosixOffset field of // a trusted domain query. // #define INVALID_POSIX_OFFSET 1 ULONG PsxStatusToErrno( IN NTSTATUS Status ) /*++ Routine Description: This procedure converts an NT status code to an equivalent errno value. The conversion is a function of the status code class. Arguments: Class - Supplies the status code class to use. Status - Supplies the status code to convert. Return Value: Returns an equivalent error code to the supplied status code. --*/ { ULONG Error; switch (Status) { case STATUS_INVALID_PARAMETER: Error = EINVAL; break; case STATUS_DIRECTORY_NOT_EMPTY: // Error = ENOTEMPTY; Error = EEXIST; break; case STATUS_OBJECT_PATH_INVALID: case STATUS_OBJECT_PATH_SYNTAX_BAD: case STATUS_NOT_A_DIRECTORY: Error = ENOTDIR; break; case STATUS_OBJECT_NAME_COLLISION: Error = EEXIST; break; case STATUS_OBJECT_PATH_NOT_FOUND: case STATUS_OBJECT_NAME_NOT_FOUND: case STATUS_DELETE_PENDING: case STATUS_NO_SUCH_FILE: Error = ENOENT; break; case STATUS_NO_MEMORY: case STATUS_INSUFFICIENT_RESOURCES: Error = ENOMEM; break; case STATUS_CANNOT_DELETE: Error = ETXTBUSY; break; case STATUS_DISK_FULL: Error = ENOSPC; break; case STATUS_MEDIA_WRITE_PROTECTED: Error = EROFS; break; case STATUS_OBJECT_NAME_INVALID: Error = ENAMETOOLONG; break; case STATUS_FILE_IS_A_DIRECTORY: Error = EISDIR; break; case STATUS_NOT_SAME_DEVICE: Error = EXDEV; break; case STATUS_INVALID_OWNER: Error = EPERM; break; case STATUS_INVALID_IMAGE_FORMAT: case STATUS_INVALID_IMAGE_LE_FORMAT: case STATUS_INVALID_IMAGE_NOT_MZ: case STATUS_INVALID_IMAGE_PROTECT: case STATUS_INVALID_IMAGE_WIN_16: Error = ENOEXEC; break; case STATUS_NOT_IMPLEMENTED: Error = ENOSYS; break; case STATUS_TOO_MANY_LINKS: Error = EMLINK; break; default: Error = EACCES; } return Error; } ULONG PsxStatusToErrnoPath( IN PUNICODE_STRING Path ) /*++ Routine Description: This procedure is called when an NtOpenFile returns STATUS_OBJECT_PATH_NOT_FOUND; this routine is to distinguish the following too cases: /file.c/foo, where file.c exists (ENOTDIR) /noent/foo, where noent doesn't exist (ENOENT) (NtOpenFile returns OBJECT_PATH_NOT_FOUND for both cases). Arguments: Path - Supplies the path that was given to NtOpenFile. The path string is destroyed by this function. Return Value: Returns an equivalent error code to the supplied status code. --*/ { NTSTATUS Status; OBJECT_ATTRIBUTES Obj; HANDLE FileHandle; ULONG DesiredAccess; IO_STATUS_BLOCK Iosb; ULONG Options; PWCHAR pwc, pwcSav; ULONG MinLen; PSX_GET_SIZEOF(DOSDEVICE_X_W,MinLen); DesiredAccess = SYNCHRONIZE; Options = FILE_SYNCHRONOUS_IO_NONALERT | FILE_DIRECTORY_FILE; pwcSav = NULL; for (;;) { // // Remove a trailing component. // pwc = wcsrchr(Path->Buffer, L'\\'); if (pwcSav) *pwcSav = L'\\'; if (NULL == pwc) { break; } *pwc = UNICODE_NULL; pwcSav = pwc; Path->Length = wcslen(Path->Buffer) * sizeof(WCHAR); if (Path->Length <= MinLen) { *pwcSav = L'\\'; break; } InitializeObjectAttributes(&Obj, Path, 0, NULL, NULL); Status = NtOpenFile(&FileHandle, DesiredAccess, &Obj, &Iosb, SHARE_ALL, Options); if (NT_SUCCESS(Status)) { NtClose(FileHandle); } if (STATUS_NOT_A_DIRECTORY == Status) { *pwcSav = L'\\'; Path->Length = wcslen(Path->Buffer) * sizeof(WCHAR); return ENOTDIR; } } Path->Length = wcslen(Path->Buffer) * sizeof(WCHAR); return ENOENT; } ULONG PsxDetermineFileClass( IN HANDLE FileHandle ) /*++ Routine Description: This function examines a file handle and returns its FileClass Arguments: FileHandle - Supplies a handle to an open file whose class is to be determined. Return Value: The file class of the specified file. Defined in . --*/ { NTSTATUS st; IO_STATUS_BLOCK Iosb; FILE_BASIC_INFORMATION BasicInfo; FILE_FS_DEVICE_INFORMATION DeviceInfo; // // Call NtQueryFile to get device type and attributes // st = NtQueryInformationFile( FileHandle, &Iosb, &BasicInfo, sizeof(BasicInfo), FileBasicInformation ); if (!NT_SUCCESS(st)) { // XXX.mjb: Sometimes fails on HPFS KdPrint(("PSXS: PsxDetermineFileClass: NtQueryInfoFile: 0x%x\n", st)); return S_IFREG; } st = NtQueryVolumeInformationFile( FileHandle, &Iosb, &DeviceInfo, sizeof(DeviceInfo), FileFsDeviceInformation ); ASSERT(NT_SUCCESS(st)); switch (DeviceInfo.DeviceType) { case FILE_DEVICE_DATALINK: case FILE_DEVICE_KEYBOARD: case FILE_DEVICE_MOUSE: case FILE_DEVICE_NETWORK: case FILE_DEVICE_NULL: case FILE_DEVICE_PHYSICAL_NETCARD: case FILE_DEVICE_PARALLEL_PORT: case FILE_DEVICE_PRINTER: case FILE_DEVICE_SOUND: case FILE_DEVICE_SCREEN: case FILE_DEVICE_SERIAL_PORT: case FILE_DEVICE_TRANSPORT: return S_IFCHR; case FILE_DEVICE_DFS: case FILE_DEVICE_DISK_FILE_SYSTEM: case FILE_DEVICE_NETWORK_FILE_SYSTEM: return S_IFBLK; case FILE_DEVICE_DISK: case FILE_DEVICE_VIRTUAL_DISK: case FILE_DEVICE_TAPE: break; default: break; // return 0; } // // The only thing left is RegularFile class. Now // determine if this is a directory, named pipe, // or regular file. // if (BasicInfo.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) { return S_IFDIR; } // // For now, anything marked as a system file is a named pipe. // In the future, this will involve checking to see if file // has the Object Bit and has the appropriate EA (named pipe // class id). // if (BasicInfo.FileAttributes & FILE_ATTRIBUTE_SYSTEM) { return S_IFIFO; } return S_IFREG; } VOID EndImpersonation( VOID ) { HANDLE Handle; NTSTATUS Status; Handle = NULL; Status = NtSetInformationThread(NtCurrentThread(), ThreadImpersonationToken, (PVOID)&Handle, sizeof(HANDLE)); ASSERT(NT_SUCCESS(Status)); } // // MakePosixId -- convert the given SID into a Posix Id. This basically // means find the Posix Offset and add it to the last sub-authority // in the SID. The Posix Id is returned. // uid_t MakePosixId(PSID Sid) { NTSTATUS Status; LSA_HANDLE PolicyHandle, TrustedDomainHandle; PTRUSTED_POSIX_OFFSET_INFO pPosixOff; OBJECT_ATTRIBUTES Obj; SECURITY_QUALITY_OF_SERVICE SecurityQoS; CHAR buf[SECURITY_DESCRIPTOR_MIN_LENGTH]; PSECURITY_DESCRIPTOR SecurityDescriptor = (PVOID)buf; PSID DomainSid; ULONG RelativeId, offset; UNICODE_STRING DCName, Domain_U; PPOLICY_ACCOUNT_DOMAIN_INFO AccountDomainInfo; PPOLICY_PRIMARY_DOMAIN_INFO PrimaryDomainInfo; UCHAR SubAuthCount; LPBYTE netbuf; ULONG i; SubAuthCount = *RtlSubAuthorityCountSid(Sid); RelativeId = *RtlSubAuthoritySid(Sid, SubAuthCount - 1); // // Map S-1-5-5-X-Y to Id 0xFFF // if (3 == SubAuthCount && 5 == RtlIdentifierAuthoritySid(Sid)->Value[5] && 5 == *RtlSubAuthoritySid(Sid, 0)) { return 0xFFF; } // // First copy the given Sid to a Sid for that domain. // DomainSid = RtlAllocateHeap(PsxHeap, 0, RtlLengthSid(Sid)); if (NULL == DomainSid) { KdPrint(("PSXSS: MakePosixId: no memory\n")); return 0; } Status = RtlCopySid(RtlLengthSid(Sid), DomainSid, Sid); ASSERT(NT_SUCCESS(Status)); --*RtlSubAuthorityCountSid(DomainSid); // // See if the offset for the domain is already known. // if (INVALID_POSIX_OFFSET != (offset = GetOffsetBySid(DomainSid))) { // XXX.mjb: close handles, free memory. RtlFreeHeap(PsxHeap, 0, (PVOID)DomainSid); return (offset | RelativeId); } // // If the Domain part of the passed-in Sid is our account domain, // then the offset is known. // SecurityQoS.ImpersonationLevel = SecurityIdentification; SecurityQoS.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING; SecurityQoS.EffectiveOnly = TRUE; InitializeObjectAttributes(&Obj, NULL, 0, NULL, NULL); Obj.SecurityQualityOfService = &SecurityQoS; Status = LsaOpenPolicy(NULL, &Obj, GENERIC_EXECUTE, &PolicyHandle); if (!NT_SUCCESS(Status)) { KdPrint(("PSXSS: Can't open policy: 0x%x\n", Status)); RtlFreeHeap(PsxHeap, 0, (PVOID)DomainSid); return 0; } Status = LsaQueryInformationPolicy(PolicyHandle, PolicyAccountDomainInformation, (PVOID *)&AccountDomainInfo); if (!NT_SUCCESS(Status)) { KdPrint(("PSXSS: Can't query info policy: 0x%x\n", Status)); return 0; } ASSERT(NULL != AccountDomainInfo->DomainSid); if (RtlEqualSid(AccountDomainInfo->DomainSid, DomainSid)) { MapSidToOffset(DomainSid, SE_ACCOUNT_DOMAIN_POSIX_OFFSET); LsaFreeMemory(AccountDomainInfo); LsaClose(PolicyHandle); return RelativeId | SE_ACCOUNT_DOMAIN_POSIX_OFFSET; } LsaFreeMemory(AccountDomainInfo); Status = LsaQueryInformationPolicy(PolicyHandle, PolicyPrimaryDomainInformation, (PVOID *)&PrimaryDomainInfo); ASSERT(NT_SUCCESS(Status)); if (NULL == PrimaryDomainInfo->Sid) { // // This machine does not have a primary domain, and the // sid we're mapping does not belong to the account domain // and is not a well-known sid. // RtlFreeHeap(PsxHeap, 0, (PVOID)DomainSid); LsaFreeMemory(PrimaryDomainInfo); LsaClose(PolicyHandle); return RelativeId; } if (NULL != PrimaryDomainInfo->Sid && RtlEqualSid(PrimaryDomainInfo->Sid, DomainSid)) { MapSidToOffset(DomainSid, SE_PRIMARY_DOMAIN_POSIX_OFFSET); LsaFreeMemory(PrimaryDomainInfo); LsaClose(PolicyHandle); return RelativeId | SE_PRIMARY_DOMAIN_POSIX_OFFSET; } Status = NetGetAnyDCName(NULL, PrimaryDomainInfo->Name.Buffer, &netbuf); if (Status != ERROR_SUCCESS) { RtlFreeHeap(PsxHeap, 0, (PVOID)DomainSid); LsaFreeMemory(PrimaryDomainInfo); LsaClose(PolicyHandle); return RelativeId; } DCName.Buffer = (PVOID)netbuf; NetApiBufferSize(netbuf, (LPDWORD)&DCName.MaximumLength); DCName.Length = wcslen((PWCHAR)netbuf) * sizeof(WCHAR); LsaClose(PolicyHandle); Status = LsaOpenPolicy(&DCName, &Obj, GENERIC_EXECUTE, &PolicyHandle); if (!NT_SUCCESS(Status)) { KdPrint(("PSXSS: Can't open policy on DC %wZ: 0x%x\n", &DCName, Status)); LsaFreeMemory(PrimaryDomainInfo); RtlFreeHeap(PsxHeap, 0, (PVOID)DomainSid); return RelativeId; } NetApiBufferFree(netbuf); LsaFreeMemory(PrimaryDomainInfo); Status = LsaOpenTrustedDomain(PolicyHandle, DomainSid, GENERIC_EXECUTE, &TrustedDomainHandle); if (!NT_SUCCESS(Status)) { KdPrint(("PSXSS: Can't open trusted domain\n")); RtlFreeHeap(PsxHeap, 0, (PVOID)DomainSid); return RelativeId; } Status = LsaQueryInfoTrustedDomain(TrustedDomainHandle, TrustedPosixOffsetInformation, (PVOID)&pPosixOff); if (!NT_SUCCESS(Status)) { KdPrint(("PSXSS: Can't query Posix offset info: 0x%x\n", Status)); LsaClose(PolicyHandle); LsaClose(TrustedDomainHandle); RtlFreeHeap(PsxHeap, 0, (PVOID)DomainSid); return RelativeId; } offset = pPosixOff->Offset; LsaFreeMemory(pPosixOff); if (offset & 0xFFFF) { KdPrint(("PSXSS: bad PsxOffset 0x%x\n", offset)); RtlFreeHeap(PsxHeap, 0, (PVOID)DomainSid); LsaClose(TrustedDomainHandle); LsaClose(PolicyHandle); offset = 0; } ASSERT(INVALID_POSIX_OFFSET != offset); MapSidToOffset(DomainSid, offset); // // Do not free DomainSid -- there is still a reference to it in // the Sid-Offset cache (put there by MapSidToOffset). // LsaClose(PolicyHandle); LsaClose(TrustedDomainHandle); return offset | RelativeId; } typedef struct _SID_AND_OFFSET { LIST_ENTRY Links; PSID Sid; ULONG Offset; } SID_AND_OFFSET, *PSID_AND_OFFSET; LIST_ENTRY SidList; RTL_CRITICAL_SECTION SidListMutex; // // GetOffsetBySid -- search the SidList for the given Sid. If we've // encountered this domain before, we'll know the Posix offset, // which is returned. If not, INVALID_POSIX_OFFSET is returned. // ULONG GetOffsetBySid(PSID Sid) { PSID_AND_OFFSET pSO; ULONG Offset = INVALID_POSIX_OFFSET; RtlEnterCriticalSection(&SidListMutex); for (pSO = (PSID_AND_OFFSET)SidList.Flink; pSO != (PSID_AND_OFFSET)&SidList; pSO = (PSID_AND_OFFSET)pSO->Links.Flink) { if (RtlEqualSid(Sid, pSO->Sid)) { Offset = pSO->Offset; break; } } RtlLeaveCriticalSection(&SidListMutex); return Offset; } // // GetSidByOffset -- search the SidList for the given offset. Called in // the process of converting a uid or gid to a Sid, as in getpwuid(). // PSID GetSidByOffset(ULONG Offset) { PSID_AND_OFFSET pSO; PSID Sid = NULL; RtlEnterCriticalSection(&SidListMutex); for (pSO = (PSID_AND_OFFSET)SidList.Flink; pSO != (PSID_AND_OFFSET)&SidList; pSO = (PSID_AND_OFFSET)pSO->Links.Flink) { if (Offset == pSO->Offset) { Sid = pSO->Sid; break; } } RtlLeaveCriticalSection(&SidListMutex); return Sid; } // // MapSidToOffset -- add the given sid and offset to the cache. If there's // an error, like no memory, it's simply dropped on the floor. // VOID MapSidToOffset(PSID Sid, ULONG Offset) { PSID_AND_OFFSET pSO; pSO = RtlAllocateHeap(PsxHeap, 0, sizeof(*pSO)); if (NULL == pSO) { return; } pSO->Sid = Sid; pSO->Offset = Offset; RtlEnterCriticalSection(&SidListMutex); InsertHeadList(&SidList, &pSO->Links); RtlLeaveCriticalSection(&SidListMutex); } // // InitSidList -- do initialization, including mapping special Sids to // their appropriate offsets. No locking is done, assumed to be // called in a single-threaded way. // VOID InitSidList(VOID) { NTSTATUS Status; PSID Sid; SID_IDENTIFIER_AUTHORITY AuthSec = SECURITY_NT_AUTHORITY, Auth0 = SECURITY_NULL_SID_AUTHORITY, Auth1 = SECURITY_WORLD_SID_AUTHORITY, Auth2 = SECURITY_LOCAL_SID_AUTHORITY, Auth3 = SECURITY_CREATOR_SID_AUTHORITY, Auth4 = SECURITY_NON_UNIQUE_AUTHORITY, Auth5 = SECURITY_NT_AUTHORITY; RtlInitializeCriticalSection(&SidListMutex); InitializeListHead(&SidList); Status = RtlAllocateAndInitializeSid(&Auth0, 0, 0, 0, 0, 0, 0, 0, 0, 0, &Sid); ASSERT(NT_SUCCESS(Status)); MapSidToOffset(Sid, SE_NULL_POSIX_ID); Status = RtlAllocateAndInitializeSid(&Auth1, 0, 0, 0, 0, 0, 0, 0, 0, 0, &Sid); ASSERT(NT_SUCCESS(Status)); MapSidToOffset(Sid, SE_WORLD_POSIX_ID); Status = RtlAllocateAndInitializeSid(&Auth2, 0, 0, 0, 0, 0, 0, 0, 0, 0, &Sid); ASSERT(NT_SUCCESS(Status)); MapSidToOffset(Sid, SE_LOCAL_POSIX_ID); Status = RtlAllocateAndInitializeSid(&Auth3, 0, 0, 0, 0, 0, 0, 0, 0, 0, &Sid); ASSERT(NT_SUCCESS(Status)); MapSidToOffset(Sid, SE_CREATOR_OWNER_POSIX_ID); Status = RtlAllocateAndInitializeSid(&Auth4, 0, 0, 0, 0, 0, 0, 0, 0, 0, &Sid); ASSERT(NT_SUCCESS(Status)); MapSidToOffset(Sid, SE_NON_UNIQUE_POSIX_ID); Status = RtlAllocateAndInitializeSid(&Auth5, 0, 0, 0, 0, 0, 0, 0, 0, 0, &Sid); ASSERT(NT_SUCCESS(Status)); MapSidToOffset(Sid, SE_AUTHORITY_POSIX_ID); // // "Builtin" domain has known offset. // Status = RtlAllocateAndInitializeSid(&AuthSec, 1, SECURITY_BUILTIN_DOMAIN_RID, 0, 0, 0, 0, 0, 0, 0, &Sid); ASSERT(NT_SUCCESS(Status)); MapSidToOffset(Sid, SE_BUILT_IN_DOMAIN_POSIX_OFFSET); } // // AccessMaskToMode -- convert a set of NT ACCESS_MASKS to the POSIX // mode_t. // mode_t AccessMaskToMode( ACCESS_MASK UserAccess, ACCESS_MASK GroupAccess, ACCESS_MASK OtherAccess ) { mode_t Mode = 0; int i; PACCESS_MASK pAM; // // Make sure that if a GENERIC_ACCESS is set, we notice that // the mask implies FILE_GENERIC_ACCESS. // for (i = 0; i < 3; ++i) { switch (i) { case 0: pAM = &UserAccess; break; case 1: pAM = &GroupAccess; break; case 2: pAM = &OtherAccess; break; } if (*pAM & GENERIC_READ) { *pAM |= FILE_GENERIC_READ; } if (*pAM & GENERIC_WRITE) { *pAM |= FILE_GENERIC_WRITE; } if (*pAM & GENERIC_EXECUTE) { *pAM |= FILE_GENERIC_EXECUTE; } if (*pAM & GENERIC_ALL) { *pAM |= FILE_ALL_ACCESS; } } if (UserAccess & FILE_READ_DATA) { Mode |= S_IRUSR; } if ((UserAccess & FILE_WRITE_DATA) && (UserAccess & FILE_APPEND_DATA)) { Mode |= S_IWUSR; } if (UserAccess & FILE_EXECUTE) { Mode |= S_IXUSR; } if (GroupAccess & FILE_READ_DATA) { Mode |= S_IRGRP; } if ((GroupAccess & FILE_WRITE_DATA) && (GroupAccess & FILE_APPEND_DATA)) { Mode |= S_IWGRP; } if (GroupAccess & FILE_EXECUTE) { Mode |= S_IXGRP; } if (OtherAccess & FILE_READ_DATA) { Mode |= S_IROTH; } if ((OtherAccess & FILE_WRITE_DATA) && (OtherAccess & FILE_APPEND_DATA)) { Mode |= S_IWOTH; } if (OtherAccess & FILE_EXECUTE) { Mode |= S_IXOTH; } return Mode; } void ModeToAccessMask( mode_t Mode, PACCESS_MASK pUserAccess, PACCESS_MASK pGroupAccess, PACCESS_MASK pOtherAccess ) { // // All ACL's have these standard permissions: // READ_ATTR and READ_EA so anybody can access() any file. // *pUserAccess = SYNCHRONIZE | READ_CONTROL | FILE_READ_ATTRIBUTES | FILE_READ_EA; *pGroupAccess = *pOtherAccess = *pUserAccess; // // The owner always gets WRITE_DAC (for chmod), FILE_WRITE_ATTR // (for utimes), and WRITE_OWNER (for chgrp). // *pUserAccess |= (WRITE_DAC | WRITE_OWNER | FILE_WRITE_ATTRIBUTES); if (Mode & S_IRUSR) { *pUserAccess |= FILE_GENERIC_READ | FILE_LIST_DIRECTORY; } if (Mode & S_IWUSR) { *pUserAccess |= FILE_GENERIC_WRITE | FILE_DELETE_CHILD; } if (Mode & S_IXUSR) { *pUserAccess |= FILE_GENERIC_EXECUTE; } if (Mode & S_IRGRP) { *pGroupAccess |= FILE_GENERIC_READ | FILE_LIST_DIRECTORY; } if (Mode & S_IWGRP) { *pGroupAccess |= FILE_GENERIC_WRITE | FILE_DELETE_CHILD; } if (Mode & S_IXGRP) { *pGroupAccess |= FILE_GENERIC_EXECUTE; } if (Mode & S_IROTH) { *pOtherAccess |= FILE_GENERIC_READ | FILE_LIST_DIRECTORY; } if (Mode & S_IWOTH) { *pOtherAccess |= FILE_GENERIC_WRITE | FILE_DELETE_CHILD; } if (Mode & S_IXOTH) { *pOtherAccess |= FILE_GENERIC_EXECUTE; } }