| /** |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with this |
| * work for additional information regarding copyright ownership. The ASF |
| * licenses this file to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| |
| #pragma comment(lib, "authz.lib") |
| #pragma comment(lib, "netapi32.lib") |
| #include "winutils.h" |
| #include <authz.h> |
| #include <sddl.h> |
| |
| /* |
| * The array of 12 months' three-letter abbreviations |
| */ |
| const LPCWSTR MONTHS[] = { L"Jan", L"Feb", L"Mar", L"Apr", L"May", L"Jun", |
| L"Jul", L"Aug", L"Sep", L"Oct", L"Nov", L"Dec" }; |
| |
| /* |
| * The WindowsAclMask and WinMasks contain the definitions used to establish |
| * the mapping between Unix and Windows. |
| * We set up the mapping with the following rules. |
| * 1. Everyone will have WIN_ALL permissions; |
| * 2. Owner will always have WIN_OWNER_SE permissions in addition; |
| * 2. When Unix read/write/excute permission is set on the file, the |
| * corresponding Windows allow ACE will be added to the file. |
| * More details and explaination can be found in the following white paper: |
| * http://technet.microsoft.com/en-us/library/bb463216.aspx |
| */ |
| const ACCESS_MASK WinMasks[WIN_MASKS_TOTAL] = |
| { |
| /* WIN_READ */ |
| FILE_READ_DATA, |
| /* WIN_WRITE */ |
| FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES | FILE_APPEND_DATA | FILE_WRITE_EA | |
| FILE_DELETE_CHILD, |
| /* WIN_EXECUTE */ |
| FILE_EXECUTE, |
| /* WIN_OWNER_SE */ |
| DELETE | WRITE_DAC | WRITE_OWNER | FILE_WRITE_EA | FILE_WRITE_ATTRIBUTES, |
| /* WIN_ALL */ |
| READ_CONTROL | FILE_READ_EA | FILE_READ_ATTRIBUTES | SYNCHRONIZE, |
| }; |
| |
| //---------------------------------------------------------------------------- |
| // Function: GetFileInformationByName |
| // |
| // Description: |
| // To retrieve the by handle file information given the file name |
| // |
| // Returns: |
| // ERROR_SUCCESS: on success |
| // error code: otherwise |
| // |
| // Notes: |
| // If followLink parameter is set to TRUE, we will follow the symbolic link |
| // or junction point to get the target file information. Otherwise, the |
| // information for the symbolic link or junction point is retrieved. |
| // |
| DWORD GetFileInformationByName( |
| __in LPCWSTR pathName, |
| __in BOOL followLink, |
| __out LPBY_HANDLE_FILE_INFORMATION lpFileInformation) |
| { |
| HANDLE fileHandle = INVALID_HANDLE_VALUE; |
| BOOL isSymlink = FALSE; |
| BOOL isJunction = FALSE; |
| DWORD dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS; |
| DWORD dwErrorCode = ERROR_SUCCESS; |
| |
| assert(lpFileInformation != NULL); |
| |
| if (!followLink) |
| { |
| if ((dwErrorCode = SymbolicLinkCheck(pathName, &isSymlink)) != ERROR_SUCCESS) |
| return dwErrorCode; |
| if ((dwErrorCode = JunctionPointCheck(pathName, &isJunction)) != ERROR_SUCCESS) |
| return dwErrorCode; |
| if (isSymlink || isJunction) |
| dwFlagsAndAttributes |= FILE_FLAG_OPEN_REPARSE_POINT; |
| } |
| |
| fileHandle = CreateFileW( |
| pathName, |
| FILE_READ_ATTRIBUTES, |
| FILE_SHARE_READ, |
| NULL, |
| OPEN_EXISTING, |
| dwFlagsAndAttributes, |
| NULL); |
| if (fileHandle == INVALID_HANDLE_VALUE) |
| { |
| dwErrorCode = GetLastError(); |
| return dwErrorCode; |
| } |
| |
| if (!GetFileInformationByHandle(fileHandle, lpFileInformation)) |
| { |
| dwErrorCode = GetLastError(); |
| CloseHandle(fileHandle); |
| return dwErrorCode; |
| } |
| |
| CloseHandle(fileHandle); |
| |
| return dwErrorCode; |
| } |
| |
| //---------------------------------------------------------------------------- |
| // Function: IsLongWindowsPath |
| // |
| // Description: |
| // Checks if the path is longer than MAX_PATH in which case it needs to be |
| // prepended with \\?\ for Windows OS to understand it. |
| // |
| // Returns: |
| // TRUE long path |
| // FALSE otherwise |
| static BOOL IsLongWindowsPath(__in PCWSTR path) |
| { |
| return (wcslen(path) + 1) > MAX_PATH; |
| } |
| |
| //---------------------------------------------------------------------------- |
| // Function: IsPrefixedAlready |
| // |
| // Description: |
| // Checks if the given path is already prepended with \\?\. |
| // |
| // Returns: |
| // TRUE if yes |
| // FALSE otherwise |
| static BOOL IsPrefixedAlready(__in PCWSTR path) |
| { |
| static const PCWSTR LongPathPrefix = L"\\\\?\\"; |
| size_t Prefixlen = wcslen(LongPathPrefix); |
| size_t i = 0; |
| |
| if (path == NULL || wcslen(path) < Prefixlen) |
| { |
| return FALSE; |
| } |
| |
| for (i = 0; i < Prefixlen; ++i) |
| { |
| if (path[i] != LongPathPrefix[i]) |
| { |
| return FALSE; |
| } |
| } |
| |
| return TRUE; |
| } |
| |
| //---------------------------------------------------------------------------- |
| // Function: ConvertToLongPath |
| // |
| // Description: |
| // Prepends the path with the \\?\ prefix if the path is longer than MAX_PATH. |
| // On success, newPath should be freed with LocalFree(). Given that relative |
| // paths cannot be longer than MAX_PATH, we will never prepend the prefix |
| // to relative paths. |
| // |
| // Returns: |
| // ERROR_SUCCESS on success |
| // error code on failure |
| DWORD ConvertToLongPath(__in PCWSTR path, __deref_out PWSTR *newPath) |
| { |
| DWORD dwErrorCode = ERROR_SUCCESS; |
| static const PCWSTR LongPathPrefix = L"\\\\?\\"; |
| BOOL bAppendPrefix = IsLongWindowsPath(path) && !IsPrefixedAlready(path); |
| HRESULT hr = S_OK; |
| |
| size_t newPathLen = wcslen(path) + (bAppendPrefix ? wcslen(LongPathPrefix) : 0); |
| |
| // Allocate the buffer for the output path (+1 for terminating NULL char) |
| // |
| PWSTR newPathValue = (PWSTR)LocalAlloc(LPTR, (newPathLen + 1) * sizeof(WCHAR)); |
| if (newPathValue == NULL) |
| { |
| dwErrorCode = GetLastError(); |
| goto ConvertToLongPathExit; |
| } |
| |
| if (bAppendPrefix) |
| { |
| // Append the prefix to the path |
| // |
| hr = StringCchPrintfW(newPathValue, newPathLen + 1, L"%s%s", |
| LongPathPrefix, path); |
| if (FAILED(hr)) |
| { |
| dwErrorCode = HRESULT_CODE(hr); |
| goto ConvertToLongPathExit; |
| } |
| } |
| else |
| { |
| // Just copy the original value into the output path. In this scenario |
| // we are doing extra buffer copy. We decided to trade code simplicity |
| // on the call site for small performance impact (extra allocation and |
| // buffer copy). As paths are short, the impact is generally small. |
| // |
| hr = StringCchPrintfW(newPathValue, newPathLen + 1, L"%s", path); |
| if (FAILED(hr)) |
| { |
| dwErrorCode = HRESULT_CODE(hr); |
| goto ConvertToLongPathExit; |
| } |
| } |
| |
| *newPath = newPathValue; |
| |
| ConvertToLongPathExit: |
| if (dwErrorCode != ERROR_SUCCESS) |
| { |
| LocalFree(newPathValue); |
| } |
| |
| return dwErrorCode; |
| } |
| |
| //---------------------------------------------------------------------------- |
| // Function: IsDirFileInfo |
| // |
| // Description: |
| // Test if the given file information is a directory |
| // |
| // Returns: |
| // TRUE if it is a directory |
| // FALSE otherwise |
| // |
| // Notes: |
| // |
| BOOL IsDirFileInfo(const BY_HANDLE_FILE_INFORMATION *fileInformation) |
| { |
| if ((fileInformation->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) |
| == FILE_ATTRIBUTE_DIRECTORY) |
| return TRUE; |
| return FALSE; |
| } |
| |
| //---------------------------------------------------------------------------- |
| // Function: CheckFileAttributes |
| // |
| // Description: |
| // Check if the given file has all the given attribute(s) |
| // |
| // Returns: |
| // ERROR_SUCCESS on success |
| // error code otherwise |
| // |
| // Notes: |
| // |
| static DWORD FileAttributesCheck( |
| __in LPCWSTR path, __in DWORD attr, __out PBOOL res) |
| { |
| DWORD attrs = INVALID_FILE_ATTRIBUTES; |
| *res = FALSE; |
| if ((attrs = GetFileAttributes(path)) != INVALID_FILE_ATTRIBUTES) |
| *res = ((attrs & attr) == attr); |
| else |
| return GetLastError(); |
| return ERROR_SUCCESS; |
| } |
| |
| //---------------------------------------------------------------------------- |
| // Function: IsDirectory |
| // |
| // Description: |
| // Check if the given file is a directory |
| // |
| // Returns: |
| // ERROR_SUCCESS on success |
| // error code otherwise |
| // |
| // Notes: |
| // |
| DWORD DirectoryCheck(__in LPCWSTR pathName, __out PBOOL res) |
| { |
| return FileAttributesCheck(pathName, FILE_ATTRIBUTE_DIRECTORY, res); |
| } |
| |
| //---------------------------------------------------------------------------- |
| // Function: IsReparsePoint |
| // |
| // Description: |
| // Check if the given file is a reparse point |
| // |
| // Returns: |
| // ERROR_SUCCESS on success |
| // error code otherwise |
| // |
| // Notes: |
| // |
| static DWORD ReparsePointCheck(__in LPCWSTR pathName, __out PBOOL res) |
| { |
| return FileAttributesCheck(pathName, FILE_ATTRIBUTE_REPARSE_POINT, res); |
| } |
| |
| //---------------------------------------------------------------------------- |
| // Function: CheckReparseTag |
| // |
| // Description: |
| // Check if the given file is a reparse point of the given tag. |
| // |
| // Returns: |
| // ERROR_SUCCESS on success |
| // error code otherwise |
| // |
| // Notes: |
| // |
| static DWORD ReparseTagCheck(__in LPCWSTR path, __in DWORD tag, __out PBOOL res) |
| { |
| BOOL isReparsePoint = FALSE; |
| HANDLE hFind = INVALID_HANDLE_VALUE; |
| WIN32_FIND_DATA findData; |
| DWORD dwRtnCode; |
| |
| if ((dwRtnCode = ReparsePointCheck(path, &isReparsePoint)) != ERROR_SUCCESS) |
| return dwRtnCode; |
| |
| if (!isReparsePoint) |
| { |
| *res = FALSE; |
| } |
| else |
| { |
| if ((hFind = FindFirstFile(path, &findData)) == INVALID_HANDLE_VALUE) |
| { |
| return GetLastError(); |
| } |
| else |
| { |
| *res = (findData.dwReserved0 == tag); |
| FindClose(hFind); |
| } |
| } |
| return ERROR_SUCCESS; |
| } |
| |
| //---------------------------------------------------------------------------- |
| // Function: IsSymbolicLink |
| // |
| // Description: |
| // Check if the given file is a symbolic link. |
| // |
| // Returns: |
| // ERROR_SUCCESS on success |
| // error code otherwise |
| // |
| // Notes: |
| // |
| DWORD SymbolicLinkCheck(__in LPCWSTR pathName, __out PBOOL res) |
| { |
| return ReparseTagCheck(pathName, IO_REPARSE_TAG_SYMLINK, res); |
| } |
| |
| //---------------------------------------------------------------------------- |
| // Function: IsJunctionPoint |
| // |
| // Description: |
| // Check if the given file is a junction point. |
| // |
| // Returns: |
| // ERROR_SUCCESS on success |
| // error code otherwise |
| // |
| // Notes: |
| // |
| DWORD JunctionPointCheck(__in LPCWSTR pathName, __out PBOOL res) |
| { |
| return ReparseTagCheck(pathName, IO_REPARSE_TAG_MOUNT_POINT, res); |
| } |
| |
| //---------------------------------------------------------------------------- |
| // Function: GetSidFromAcctNameW |
| // |
| // Description: |
| // To retrieve the SID for a user account |
| // |
| // Returns: |
| // ERROR_SUCCESS: on success |
| // Other error code: otherwise |
| // |
| // Notes: |
| // Caller needs to destroy the memory of Sid by calling LocalFree() |
| // |
| DWORD GetSidFromAcctNameW(__in PCWSTR acctName, __out PSID *ppSid) |
| { |
| DWORD dwSidSize = 0; |
| DWORD cchDomainName = 0; |
| DWORD dwDomainNameSize = 0; |
| LPWSTR domainName = NULL; |
| SID_NAME_USE eSidType; |
| |
| DWORD dwErrorCode = ERROR_SUCCESS; |
| |
| // Validate the input parameters. |
| // |
| assert (acctName != NULL && ppSid != NULL); |
| |
| // Empty name is invalid. However, LookupAccountName() function will return a |
| // false Sid, i.e. Sid for 'BUILDIN', for an empty name instead failing. We |
| // report the error before calling LookupAccountName() function for this |
| // special case. The error code returned here is the same as the last error |
| // code set by LookupAccountName() function for an invalid name. |
| // |
| if (wcslen(acctName) == 0) |
| return ERROR_NONE_MAPPED; |
| |
| // First pass to retrieve the buffer size. |
| // |
| LookupAccountName( |
| NULL, // Computer name. NULL for the local computer |
| acctName, |
| NULL, // pSid. NULL to retrieve buffer size |
| &dwSidSize, |
| NULL, // Domain Name. NULL to retrieve buffer size |
| &cchDomainName, |
| &eSidType); |
| |
| if((dwErrorCode = GetLastError()) != ERROR_INSUFFICIENT_BUFFER) |
| { |
| return dwErrorCode; |
| } |
| else |
| { |
| // Reallocate memory for the buffers. |
| // |
| *ppSid = (PSID)LocalAlloc(LPTR, dwSidSize); |
| if (*ppSid == NULL) |
| { |
| return GetLastError(); |
| } |
| dwDomainNameSize = (cchDomainName + 1) * sizeof(wchar_t); |
| domainName = (LPWSTR)LocalAlloc(LPTR, dwDomainNameSize); |
| if (domainName == NULL) |
| { |
| return GetLastError(); |
| } |
| |
| // Second pass to retrieve the SID and domain name. |
| // |
| if (!LookupAccountNameW( |
| NULL, // Computer name. NULL for the local computer |
| acctName, |
| *ppSid, |
| &dwSidSize, |
| domainName, |
| &cchDomainName, |
| &eSidType)) |
| { |
| LocalFree(domainName); |
| return GetLastError(); |
| } |
| |
| assert(IsValidSid(*ppSid)); |
| } |
| |
| LocalFree(domainName); |
| return ERROR_SUCCESS; |
| } |
| |
| //---------------------------------------------------------------------------- |
| // Function: GetUnixAccessMask |
| // |
| // Description: |
| // Compute the 3 bit Unix mask for the owner, group, or, others |
| // |
| // Returns: |
| // The 3 bit Unix mask in INT |
| // |
| // Notes: |
| // |
| static INT GetUnixAccessMask(ACCESS_MASK Mask) |
| { |
| static const INT exe = 0x0001; |
| static const INT write = 0x0002; |
| static const INT read = 0x0004; |
| INT mask = 0; |
| |
| if ((Mask & WinMasks[WIN_READ]) == WinMasks[WIN_READ]) |
| mask |= read; |
| if ((Mask & WinMasks[WIN_WRITE]) == WinMasks[WIN_WRITE]) |
| mask |= write; |
| if ((Mask & WinMasks[WIN_EXECUTE]) == WinMasks[WIN_EXECUTE]) |
| mask |= exe; |
| return mask; |
| } |
| |
| //---------------------------------------------------------------------------- |
| // Function: GetAccess |
| // |
| // Description: |
| // Get Windows acces mask by AuthZ methods |
| // |
| // Returns: |
| // ERROR_SUCCESS: on success |
| // |
| // Notes: |
| // |
| static DWORD GetAccess(AUTHZ_CLIENT_CONTEXT_HANDLE hAuthzClient, |
| PSECURITY_DESCRIPTOR psd, PACCESS_MASK pAccessRights) |
| { |
| AUTHZ_ACCESS_REQUEST AccessRequest = {0}; |
| AUTHZ_ACCESS_REPLY AccessReply = {0}; |
| BYTE Buffer[1024]; |
| |
| assert (pAccessRights != NULL); |
| |
| // Do AccessCheck |
| AccessRequest.DesiredAccess = MAXIMUM_ALLOWED; |
| AccessRequest.PrincipalSelfSid = NULL; |
| AccessRequest.ObjectTypeList = NULL; |
| AccessRequest.ObjectTypeListLength = 0; |
| AccessRequest.OptionalArguments = NULL; |
| |
| RtlZeroMemory(Buffer, sizeof(Buffer)); |
| AccessReply.ResultListLength = 1; |
| AccessReply.GrantedAccessMask = (PACCESS_MASK) (Buffer); |
| AccessReply.Error = (PDWORD) (Buffer + sizeof(ACCESS_MASK)); |
| |
| if (!AuthzAccessCheck(0, |
| hAuthzClient, |
| &AccessRequest, |
| NULL, |
| psd, |
| NULL, |
| 0, |
| &AccessReply, |
| NULL)) |
| { |
| return GetLastError(); |
| } |
| *pAccessRights = (*(const ACCESS_MASK *)(AccessReply.GrantedAccessMask)); |
| return ERROR_SUCCESS; |
| } |
| |
| //---------------------------------------------------------------------------- |
| // Function: GetEffectiveRightsForSid |
| // |
| // Description: |
| // Get Windows acces mask by AuthZ methods |
| // |
| // Returns: |
| // ERROR_SUCCESS: on success |
| // |
| // Notes: |
| // We run into problems for local user accounts when using the method |
| // GetEffectiveRightsFromAcl(). We resort to using AuthZ methods as |
| // an alternative way suggested on MSDN: |
| // http://msdn.microsoft.com/en-us/library/windows/desktop/aa446637.aspx |
| // |
| static DWORD GetEffectiveRightsForSid(PSECURITY_DESCRIPTOR psd, |
| PSID pSid, |
| PACCESS_MASK pAccessRights) |
| { |
| AUTHZ_RESOURCE_MANAGER_HANDLE hManager = NULL; |
| LUID unusedId = { 0 }; |
| AUTHZ_CLIENT_CONTEXT_HANDLE hAuthzClientContext = NULL; |
| DWORD dwRtnCode = ERROR_SUCCESS; |
| DWORD ret = ERROR_SUCCESS; |
| |
| assert (pAccessRights != NULL); |
| |
| if (!AuthzInitializeResourceManager(AUTHZ_RM_FLAG_NO_AUDIT, |
| NULL, NULL, NULL, NULL, &hManager)) |
| { |
| return GetLastError(); |
| } |
| |
| // Pass AUTHZ_SKIP_TOKEN_GROUPS to the function to avoid querying user group |
| // information for access check. This allows us to model POSIX permissions |
| // on Windows, where a user can have less permissions than a group it |
| // belongs to. |
| if(!AuthzInitializeContextFromSid(AUTHZ_SKIP_TOKEN_GROUPS, |
| pSid, hManager, NULL, unusedId, NULL, &hAuthzClientContext)) |
| { |
| ret = GetLastError(); |
| goto GetEffectiveRightsForSidEnd; |
| } |
| |
| if ((dwRtnCode = GetAccess(hAuthzClientContext, psd, pAccessRights)) |
| != ERROR_SUCCESS) |
| { |
| ret = dwRtnCode; |
| goto GetEffectiveRightsForSidEnd; |
| } |
| |
| GetEffectiveRightsForSidEnd: |
| if (hManager != NULL) |
| { |
| (void)AuthzFreeResourceManager(hManager); |
| } |
| if (hAuthzClientContext != NULL) |
| { |
| (void)AuthzFreeContext(hAuthzClientContext); |
| } |
| |
| return ret; |
| } |
| |
| //---------------------------------------------------------------------------- |
| // Function: CheckAccessForCurrentUser |
| // |
| // Description: |
| // Checks if the current process has the requested access rights on the given |
| // path. Based on the following MSDN article: |
| // http://msdn.microsoft.com/en-us/library/windows/desktop/ff394771(v=vs.85).aspx |
| // |
| // Returns: |
| // ERROR_SUCCESS: on success |
| // |
| DWORD CheckAccessForCurrentUser( |
| __in PCWSTR pathName, |
| __in ACCESS_MASK requestedAccess, |
| __out BOOL *allowed) |
| { |
| DWORD dwRtnCode = ERROR_SUCCESS; |
| |
| LPWSTR longPathName = NULL; |
| HANDLE hProcessToken = NULL; |
| PSECURITY_DESCRIPTOR pSd = NULL; |
| |
| AUTHZ_RESOURCE_MANAGER_HANDLE hManager = NULL; |
| AUTHZ_CLIENT_CONTEXT_HANDLE hAuthzClientContext = NULL; |
| LUID Luid = {0, 0}; |
| |
| ACCESS_MASK currentUserAccessRights = 0; |
| |
| // Prepend the long path prefix if needed |
| dwRtnCode = ConvertToLongPath(pathName, &longPathName); |
| if (dwRtnCode != ERROR_SUCCESS) |
| { |
| goto CheckAccessEnd; |
| } |
| |
| // Get SD of the given path. OWNER and DACL security info must be |
| // requested, otherwise, AuthzAccessCheck fails with invalid parameter |
| // error. |
| dwRtnCode = GetNamedSecurityInfo(longPathName, SE_FILE_OBJECT, |
| OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | |
| DACL_SECURITY_INFORMATION, |
| NULL, NULL, NULL, NULL, &pSd); |
| if (dwRtnCode != ERROR_SUCCESS) |
| { |
| goto CheckAccessEnd; |
| } |
| |
| // Get current process token |
| if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hProcessToken)) |
| { |
| dwRtnCode = GetLastError(); |
| goto CheckAccessEnd; |
| } |
| |
| if (!AuthzInitializeResourceManager(AUTHZ_RM_FLAG_NO_AUDIT, NULL, NULL, |
| NULL, NULL, &hManager)) |
| { |
| dwRtnCode = GetLastError(); |
| goto CheckAccessEnd; |
| } |
| |
| if(!AuthzInitializeContextFromToken(0, hProcessToken, hManager, NULL, |
| Luid, NULL, &hAuthzClientContext)) |
| { |
| dwRtnCode = GetLastError(); |
| goto CheckAccessEnd; |
| } |
| |
| dwRtnCode = GetAccess(hAuthzClientContext, pSd, ¤tUserAccessRights); |
| if (dwRtnCode != ERROR_SUCCESS) |
| { |
| goto CheckAccessEnd; |
| } |
| |
| *allowed = ((currentUserAccessRights & requestedAccess) == requestedAccess); |
| |
| CheckAccessEnd: |
| LocalFree(longPathName); |
| LocalFree(pSd); |
| if (hProcessToken != NULL) |
| { |
| CloseHandle(hProcessToken); |
| } |
| if (hManager != NULL) |
| { |
| (void)AuthzFreeResourceManager(hManager); |
| } |
| if (hAuthzClientContext != NULL) |
| { |
| (void)AuthzFreeContext(hAuthzClientContext); |
| } |
| |
| return dwRtnCode; |
| } |
| |
| |
| //---------------------------------------------------------------------------- |
| // Function: FindFileOwnerAndPermissionByHandle |
| // |
| // Description: |
| // Find the owner, primary group and permissions of a file object given the |
| // the file object handle. The function will always follow symbolic links. |
| // |
| // Returns: |
| // ERROR_SUCCESS: on success |
| // Error code otherwise |
| // |
| // Notes: |
| // - Caller needs to destroy the memeory of owner and group names by calling |
| // LocalFree() function. |
| // |
| // - If the user or group name does not exist, the user or group SID will be |
| // returned as the name. |
| // |
| DWORD FindFileOwnerAndPermissionByHandle( |
| __in HANDLE fileHandle, |
| __out_opt LPWSTR *pOwnerName, |
| __out_opt LPWSTR *pGroupName, |
| __out_opt PINT pMask) |
| { |
| LPWSTR path = NULL; |
| DWORD cchPathLen = 0; |
| DWORD dwRtnCode = ERROR_SUCCESS; |
| |
| DWORD ret = ERROR_SUCCESS; |
| |
| dwRtnCode = GetFinalPathNameByHandle(fileHandle, path, cchPathLen, 0); |
| if (dwRtnCode == 0) |
| { |
| ret = GetLastError(); |
| goto FindFileOwnerAndPermissionByHandleEnd; |
| } |
| cchPathLen = dwRtnCode; |
| path = (LPWSTR) LocalAlloc(LPTR, cchPathLen * sizeof(WCHAR)); |
| if (path == NULL) |
| { |
| ret = GetLastError(); |
| goto FindFileOwnerAndPermissionByHandleEnd; |
| } |
| |
| dwRtnCode = GetFinalPathNameByHandle(fileHandle, path, cchPathLen, 0); |
| if (dwRtnCode != cchPathLen - 1) |
| { |
| ret = GetLastError(); |
| goto FindFileOwnerAndPermissionByHandleEnd; |
| } |
| |
| dwRtnCode = FindFileOwnerAndPermission(path, TRUE, pOwnerName, pGroupName, pMask); |
| if (dwRtnCode != ERROR_SUCCESS) |
| { |
| ret = dwRtnCode; |
| goto FindFileOwnerAndPermissionByHandleEnd; |
| } |
| |
| FindFileOwnerAndPermissionByHandleEnd: |
| LocalFree(path); |
| return ret; |
| } |
| |
| |
| //---------------------------------------------------------------------------- |
| // Function: FindFileOwnerAndPermission |
| // |
| // Description: |
| // Find the owner, primary group and permissions of a file object |
| // |
| // Returns: |
| // ERROR_SUCCESS: on success |
| // Error code otherwise |
| // |
| // Notes: |
| // - Caller needs to destroy the memeory of owner and group names by calling |
| // LocalFree() function. |
| // |
| // - If the user or group name does not exist, the user or group SID will be |
| // returned as the name. |
| // |
| DWORD FindFileOwnerAndPermission( |
| __in LPCWSTR pathName, |
| __in BOOL followLink, |
| __out_opt LPWSTR *pOwnerName, |
| __out_opt LPWSTR *pGroupName, |
| __out_opt PINT pMask) |
| { |
| DWORD dwRtnCode = 0; |
| |
| PSECURITY_DESCRIPTOR pSd = NULL; |
| |
| PSID psidOwner = NULL; |
| PSID psidGroup = NULL; |
| PSID psidEveryone = NULL; |
| DWORD cbSid = SECURITY_MAX_SID_SIZE; |
| PACL pDacl = NULL; |
| |
| BOOL isSymlink; |
| BY_HANDLE_FILE_INFORMATION fileInformation; |
| |
| ACCESS_MASK ownerAccessRights = 0; |
| ACCESS_MASK groupAccessRights = 0; |
| ACCESS_MASK worldAccessRights = 0; |
| |
| DWORD ret = ERROR_SUCCESS; |
| |
| // Do nothing if the caller request nothing |
| // |
| if (pOwnerName == NULL && pGroupName == NULL && pMask == NULL) |
| { |
| return ret; |
| } |
| |
| dwRtnCode = GetNamedSecurityInfo(pathName, SE_FILE_OBJECT, |
| OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | |
| DACL_SECURITY_INFORMATION, |
| &psidOwner, &psidGroup, &pDacl, NULL, &pSd); |
| if (dwRtnCode != ERROR_SUCCESS) |
| { |
| ret = dwRtnCode; |
| goto FindFileOwnerAndPermissionEnd; |
| } |
| |
| if (pOwnerName != NULL) |
| { |
| dwRtnCode = GetAccntNameFromSid(psidOwner, pOwnerName); |
| if (dwRtnCode == ERROR_NONE_MAPPED) |
| { |
| if (!ConvertSidToStringSid(psidOwner, pOwnerName)) |
| { |
| ret = GetLastError(); |
| goto FindFileOwnerAndPermissionEnd; |
| } |
| } |
| else if (dwRtnCode != ERROR_SUCCESS) |
| { |
| ret = dwRtnCode; |
| goto FindFileOwnerAndPermissionEnd; |
| } |
| } |
| |
| if (pGroupName != NULL) |
| { |
| dwRtnCode = GetAccntNameFromSid(psidGroup, pGroupName); |
| if (dwRtnCode == ERROR_NONE_MAPPED) |
| { |
| if (!ConvertSidToStringSid(psidGroup, pGroupName)) |
| { |
| ret = GetLastError(); |
| goto FindFileOwnerAndPermissionEnd; |
| } |
| } |
| else if (dwRtnCode != ERROR_SUCCESS) |
| { |
| ret = dwRtnCode; |
| goto FindFileOwnerAndPermissionEnd; |
| } |
| } |
| |
| if (pMask == NULL) goto FindFileOwnerAndPermissionEnd; |
| |
| dwRtnCode = GetFileInformationByName(pathName, |
| followLink, &fileInformation); |
| if (dwRtnCode != ERROR_SUCCESS) |
| { |
| ret = dwRtnCode; |
| goto FindFileOwnerAndPermissionEnd; |
| } |
| |
| dwRtnCode = SymbolicLinkCheck(pathName, &isSymlink); |
| if (dwRtnCode != ERROR_SUCCESS) |
| { |
| ret = dwRtnCode; |
| goto FindFileOwnerAndPermissionEnd; |
| } |
| |
| if (isSymlink) |
| *pMask |= UX_SYMLINK; |
| else if (IsDirFileInfo(&fileInformation)) |
| *pMask |= UX_DIRECTORY; |
| else |
| *pMask |= UX_REGULAR; |
| |
| if ((dwRtnCode = GetEffectiveRightsForSid(pSd, |
| psidOwner, &ownerAccessRights)) != ERROR_SUCCESS) |
| { |
| ret = dwRtnCode; |
| goto FindFileOwnerAndPermissionEnd; |
| } |
| |
| if ((dwRtnCode = GetEffectiveRightsForSid(pSd, |
| psidGroup, &groupAccessRights)) != ERROR_SUCCESS) |
| { |
| ret = dwRtnCode; |
| goto FindFileOwnerAndPermissionEnd; |
| } |
| |
| if ((psidEveryone = LocalAlloc(LPTR, cbSid)) == NULL) |
| { |
| ret = GetLastError(); |
| goto FindFileOwnerAndPermissionEnd; |
| } |
| if (!CreateWellKnownSid(WinWorldSid, NULL, psidEveryone, &cbSid)) |
| { |
| ret = GetLastError(); |
| goto FindFileOwnerAndPermissionEnd; |
| } |
| if ((dwRtnCode = GetEffectiveRightsForSid(pSd, |
| psidEveryone, &worldAccessRights)) != ERROR_SUCCESS) |
| { |
| ret = dwRtnCode; |
| goto FindFileOwnerAndPermissionEnd; |
| } |
| |
| *pMask |= GetUnixAccessMask(ownerAccessRights) << 6; |
| *pMask |= GetUnixAccessMask(groupAccessRights) << 3; |
| *pMask |= GetUnixAccessMask(worldAccessRights); |
| |
| FindFileOwnerAndPermissionEnd: |
| LocalFree(psidEveryone); |
| LocalFree(pSd); |
| |
| return ret; |
| } |
| |
| //---------------------------------------------------------------------------- |
| // Function: GetWindowsAccessMask |
| // |
| // Description: |
| // Get the Windows AccessMask for user, group and everyone based on the Unix |
| // permission mask |
| // |
| // Returns: |
| // none |
| // |
| // Notes: |
| // none |
| // |
| static void GetWindowsAccessMask(INT unixMask, |
| ACCESS_MASK *userAllow, |
| ACCESS_MASK *userDeny, |
| ACCESS_MASK *groupAllow, |
| ACCESS_MASK *groupDeny, |
| ACCESS_MASK *otherAllow) |
| { |
| assert (userAllow != NULL && userDeny != NULL && |
| groupAllow != NULL && groupDeny != NULL && |
| otherAllow != NULL); |
| |
| *userAllow = WinMasks[WIN_ALL] | WinMasks[WIN_OWNER_SE]; |
| if ((unixMask & UX_U_READ) == UX_U_READ) |
| *userAllow |= WinMasks[WIN_READ]; |
| |
| if ((unixMask & UX_U_WRITE) == UX_U_WRITE) |
| *userAllow |= WinMasks[WIN_WRITE]; |
| |
| if ((unixMask & UX_U_EXECUTE) == UX_U_EXECUTE) |
| *userAllow |= WinMasks[WIN_EXECUTE]; |
| |
| *userDeny = 0; |
| if ((unixMask & UX_U_READ) != UX_U_READ && |
| ((unixMask & UX_G_READ) == UX_G_READ || |
| (unixMask & UX_O_READ) == UX_O_READ)) |
| *userDeny |= WinMasks[WIN_READ]; |
| |
| if ((unixMask & UX_U_WRITE) != UX_U_WRITE && |
| ((unixMask & UX_G_WRITE) == UX_G_WRITE || |
| (unixMask & UX_O_WRITE) == UX_O_WRITE)) |
| *userDeny |= WinMasks[WIN_WRITE]; |
| |
| if ((unixMask & UX_U_EXECUTE) != UX_U_EXECUTE && |
| ((unixMask & UX_G_EXECUTE) == UX_G_EXECUTE || |
| (unixMask & UX_O_EXECUTE) == UX_O_EXECUTE)) |
| *userDeny |= WinMasks[WIN_EXECUTE]; |
| |
| *groupAllow = WinMasks[WIN_ALL]; |
| if ((unixMask & UX_G_READ) == UX_G_READ) |
| *groupAllow |= FILE_GENERIC_READ; |
| |
| if ((unixMask & UX_G_WRITE) == UX_G_WRITE) |
| *groupAllow |= WinMasks[WIN_WRITE]; |
| |
| if ((unixMask & UX_G_EXECUTE) == UX_G_EXECUTE) |
| *groupAllow |= WinMasks[WIN_EXECUTE]; |
| |
| *groupDeny = 0; |
| if ((unixMask & UX_G_READ) != UX_G_READ && |
| (unixMask & UX_O_READ) == UX_O_READ) |
| *groupDeny |= WinMasks[WIN_READ]; |
| |
| if ((unixMask & UX_G_WRITE) != UX_G_WRITE && |
| (unixMask & UX_O_WRITE) == UX_O_WRITE) |
| *groupDeny |= WinMasks[WIN_WRITE]; |
| |
| if ((unixMask & UX_G_EXECUTE) != UX_G_EXECUTE && |
| (unixMask & UX_O_EXECUTE) == UX_O_EXECUTE) |
| *groupDeny |= WinMasks[WIN_EXECUTE]; |
| |
| *otherAllow = WinMasks[WIN_ALL]; |
| if ((unixMask & UX_O_READ) == UX_O_READ) |
| *otherAllow |= WinMasks[WIN_READ]; |
| |
| if ((unixMask & UX_O_WRITE) == UX_O_WRITE) |
| *otherAllow |= WinMasks[WIN_WRITE]; |
| |
| if ((unixMask & UX_O_EXECUTE) == UX_O_EXECUTE) |
| *otherAllow |= WinMasks[WIN_EXECUTE]; |
| } |
| |
| //---------------------------------------------------------------------------- |
| // Function: GetWindowsDACLs |
| // |
| // Description: |
| // Get the Windows DACs based the Unix access mask |
| // |
| // Returns: |
| // ERROR_SUCCESS: on success |
| // Error code: otherwise |
| // |
| // Notes: |
| // - Administrators and SYSTEM are always given full permission to the file, |
| // unless Administrators or SYSTEM itself is the file owner and the user |
| // explictly set the permission to something else. For example, file 'foo' |
| // belongs to Administrators, 'chmod 000' on the file will not directly |
| // assign Administrators full permission on the file. |
| // - Only full permission for Administrators and SYSTEM are inheritable. |
| // - CREATOR OWNER is always given full permission and the permission is |
| // inheritable, more specifically OBJECT_INHERIT_ACE, CONTAINER_INHERIT_ACE |
| // flags are set. The reason is to give the creator of child file full |
| // permission, i.e., the child file will have permission mode 700 for |
| // a user other than Administrator or SYSTEM. |
| // |
| static DWORD GetWindowsDACLs(__in INT unixMask, |
| __in PSID pOwnerSid, __in PSID pGroupSid, __out PACL *ppNewDACL) |
| { |
| DWORD winUserAccessDenyMask; |
| DWORD winUserAccessAllowMask; |
| DWORD winGroupAccessDenyMask; |
| DWORD winGroupAccessAllowMask; |
| DWORD winOtherAccessAllowMask; |
| |
| PSID pEveryoneSid = NULL; |
| DWORD cbEveryoneSidSize = SECURITY_MAX_SID_SIZE; |
| |
| PSID pSystemSid = NULL; |
| DWORD cbSystemSidSize = SECURITY_MAX_SID_SIZE; |
| BOOL bAddSystemAcls = FALSE; |
| |
| PSID pAdministratorsSid = NULL; |
| DWORD cbAdministratorsSidSize = SECURITY_MAX_SID_SIZE; |
| BOOL bAddAdministratorsAcls = FALSE; |
| |
| PSID pCreatorOwnerSid = NULL; |
| DWORD cbCreatorOwnerSidSize = SECURITY_MAX_SID_SIZE; |
| |
| PACL pNewDACL = NULL; |
| DWORD dwNewAclSize = 0; |
| |
| DWORD ret = ERROR_SUCCESS; |
| |
| GetWindowsAccessMask(unixMask, |
| &winUserAccessAllowMask, &winUserAccessDenyMask, |
| &winGroupAccessAllowMask, &winGroupAccessDenyMask, |
| &winOtherAccessAllowMask); |
| |
| // Create a well-known SID for the Everyone group |
| // |
| if ((pEveryoneSid = LocalAlloc(LPTR, cbEveryoneSidSize)) == NULL) |
| { |
| ret = GetLastError(); |
| goto GetWindowsDACLsEnd; |
| } |
| if (!CreateWellKnownSid(WinWorldSid, NULL, pEveryoneSid, &cbEveryoneSidSize)) |
| { |
| ret = GetLastError(); |
| goto GetWindowsDACLsEnd; |
| } |
| |
| // Create a well-known SID for the Administrators group |
| // |
| if ((pAdministratorsSid = LocalAlloc(LPTR, cbAdministratorsSidSize)) == NULL) |
| { |
| ret = GetLastError(); |
| goto GetWindowsDACLsEnd; |
| } |
| if (!CreateWellKnownSid(WinBuiltinAdministratorsSid, NULL, |
| pAdministratorsSid, &cbAdministratorsSidSize)) |
| { |
| ret = GetLastError(); |
| goto GetWindowsDACLsEnd; |
| } |
| if (!EqualSid(pAdministratorsSid, pOwnerSid) |
| && !EqualSid(pAdministratorsSid, pGroupSid)) |
| bAddAdministratorsAcls = TRUE; |
| |
| // Create a well-known SID for the SYSTEM |
| // |
| if ((pSystemSid = LocalAlloc(LPTR, cbSystemSidSize)) == NULL) |
| { |
| ret = GetLastError(); |
| goto GetWindowsDACLsEnd; |
| } |
| if (!CreateWellKnownSid(WinLocalSystemSid, NULL, |
| pSystemSid, &cbSystemSidSize)) |
| { |
| ret = GetLastError(); |
| goto GetWindowsDACLsEnd; |
| } |
| if (!EqualSid(pSystemSid, pOwnerSid) |
| && !EqualSid(pSystemSid, pGroupSid)) |
| bAddSystemAcls = TRUE; |
| |
| // Create a well-known SID for the Creator Owner |
| // |
| if ((pCreatorOwnerSid = LocalAlloc(LPTR, cbCreatorOwnerSidSize)) == NULL) |
| { |
| ret = GetLastError(); |
| goto GetWindowsDACLsEnd; |
| } |
| if (!CreateWellKnownSid(WinCreatorOwnerSid, NULL, |
| pCreatorOwnerSid, &cbCreatorOwnerSidSize)) |
| { |
| ret = GetLastError(); |
| goto GetWindowsDACLsEnd; |
| } |
| |
| // Create the new DACL |
| // |
| dwNewAclSize = sizeof(ACL); |
| dwNewAclSize += sizeof(ACCESS_ALLOWED_ACE) + |
| GetLengthSid(pOwnerSid) - sizeof(DWORD); |
| if (winUserAccessDenyMask) |
| dwNewAclSize += sizeof(ACCESS_DENIED_ACE) + |
| GetLengthSid(pOwnerSid) - sizeof(DWORD); |
| dwNewAclSize += sizeof(ACCESS_ALLOWED_ACE) + |
| GetLengthSid(pGroupSid) - sizeof(DWORD); |
| if (winGroupAccessDenyMask) |
| dwNewAclSize += sizeof(ACCESS_DENIED_ACE) + |
| GetLengthSid(pGroupSid) - sizeof(DWORD); |
| dwNewAclSize += sizeof(ACCESS_ALLOWED_ACE) + |
| GetLengthSid(pEveryoneSid) - sizeof(DWORD); |
| |
| if (bAddSystemAcls) |
| { |
| dwNewAclSize += sizeof(ACCESS_ALLOWED_ACE) + |
| cbSystemSidSize - sizeof(DWORD); |
| } |
| |
| if (bAddAdministratorsAcls) |
| { |
| dwNewAclSize += sizeof(ACCESS_ALLOWED_ACE) + |
| cbAdministratorsSidSize - sizeof(DWORD); |
| } |
| |
| dwNewAclSize += sizeof(ACCESS_ALLOWED_ACE) + |
| cbCreatorOwnerSidSize - sizeof(DWORD); |
| |
| pNewDACL = (PACL)LocalAlloc(LPTR, dwNewAclSize); |
| if (pNewDACL == NULL) |
| { |
| ret = GetLastError(); |
| goto GetWindowsDACLsEnd; |
| } |
| if (!InitializeAcl(pNewDACL, dwNewAclSize, ACL_REVISION)) |
| { |
| ret = GetLastError(); |
| goto GetWindowsDACLsEnd; |
| } |
| |
| if (!AddAccessAllowedAceEx(pNewDACL, ACL_REVISION, |
| CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE, |
| GENERIC_ALL, pCreatorOwnerSid)) |
| { |
| ret = GetLastError(); |
| goto GetWindowsDACLsEnd; |
| } |
| |
| if (bAddSystemAcls && |
| !AddAccessAllowedAceEx(pNewDACL, ACL_REVISION, |
| CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE, |
| GENERIC_ALL, pSystemSid)) |
| { |
| ret = GetLastError(); |
| goto GetWindowsDACLsEnd; |
| } |
| |
| if (bAddAdministratorsAcls && |
| !AddAccessAllowedAceEx(pNewDACL, ACL_REVISION, |
| CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE, |
| GENERIC_ALL, pAdministratorsSid)) |
| { |
| ret = GetLastError(); |
| goto GetWindowsDACLsEnd; |
| } |
| |
| if (winUserAccessDenyMask && |
| !AddAccessDeniedAceEx(pNewDACL, ACL_REVISION, |
| NO_PROPAGATE_INHERIT_ACE, |
| winUserAccessDenyMask, pOwnerSid)) |
| { |
| ret = GetLastError(); |
| goto GetWindowsDACLsEnd; |
| } |
| if (!AddAccessAllowedAceEx(pNewDACL, ACL_REVISION, |
| NO_PROPAGATE_INHERIT_ACE, |
| winUserAccessAllowMask, pOwnerSid)) |
| { |
| ret = GetLastError(); |
| goto GetWindowsDACLsEnd; |
| } |
| if (winGroupAccessDenyMask && |
| !AddAccessDeniedAceEx(pNewDACL, ACL_REVISION, |
| NO_PROPAGATE_INHERIT_ACE, |
| winGroupAccessDenyMask, pGroupSid)) |
| { |
| ret = GetLastError(); |
| goto GetWindowsDACLsEnd; |
| } |
| if (!AddAccessAllowedAceEx(pNewDACL, ACL_REVISION, |
| NO_PROPAGATE_INHERIT_ACE, |
| winGroupAccessAllowMask, pGroupSid)) |
| { |
| ret = GetLastError(); |
| goto GetWindowsDACLsEnd; |
| } |
| if (!AddAccessAllowedAceEx(pNewDACL, ACL_REVISION, |
| NO_PROPAGATE_INHERIT_ACE, |
| winOtherAccessAllowMask, pEveryoneSid)) |
| { |
| ret = GetLastError(); |
| goto GetWindowsDACLsEnd; |
| } |
| |
| *ppNewDACL = pNewDACL; |
| |
| GetWindowsDACLsEnd: |
| LocalFree(pEveryoneSid); |
| LocalFree(pAdministratorsSid); |
| LocalFree(pSystemSid); |
| LocalFree(pCreatorOwnerSid); |
| if (ret != ERROR_SUCCESS) LocalFree(pNewDACL); |
| |
| return ret; |
| } |
| |
| //---------------------------------------------------------------------------- |
| // Function: ChangeFileModeByMask |
| // |
| // Description: |
| // Change a file or direcotry at the path to Unix mode |
| // |
| // Returns: |
| // ERROR_SUCCESS: on success |
| // Error code: otherwise |
| // |
| // Notes: |
| // This function is long path safe, i.e. the path will be converted to long |
| // path format if not already converted. So the caller does not need to do |
| // the converstion before calling the method. |
| // |
| DWORD ChangeFileModeByMask(__in LPCWSTR path, INT mode) |
| { |
| LPWSTR longPathName = NULL; |
| PACL pNewDACL = NULL; |
| PSID pOwnerSid = NULL; |
| PSID pGroupSid = NULL; |
| PSECURITY_DESCRIPTOR pSD = NULL; |
| |
| SECURITY_DESCRIPTOR_CONTROL control; |
| DWORD revision = 0; |
| |
| PSECURITY_DESCRIPTOR pAbsSD = NULL; |
| PSECURITY_DESCRIPTOR pNonNullSD = NULL; |
| PACL pAbsDacl = NULL; |
| PACL pAbsSacl = NULL; |
| PSID pAbsOwner = NULL; |
| PSID pAbsGroup = NULL; |
| |
| DWORD dwRtnCode = 0; |
| DWORD dwErrorCode = 0; |
| |
| DWORD ret = ERROR_SUCCESS; |
| |
| dwRtnCode = ConvertToLongPath(path, &longPathName); |
| if (dwRtnCode != ERROR_SUCCESS) |
| { |
| ret = dwRtnCode; |
| goto ChangeFileModeByMaskEnd; |
| } |
| |
| // Get owner and group Sids |
| // |
| dwRtnCode = GetNamedSecurityInfoW( |
| longPathName, |
| SE_FILE_OBJECT, |
| OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION, |
| &pOwnerSid, |
| &pGroupSid, |
| NULL, |
| NULL, |
| &pSD); |
| if (ERROR_SUCCESS != dwRtnCode) |
| { |
| ret = dwRtnCode; |
| goto ChangeFileModeByMaskEnd; |
| } |
| |
| // SetSecurityDescriptorDacl function used below only accepts security |
| // descriptor in absolute format, meaning that its members must be pointers to |
| // other structures, rather than offsets to contiguous data. |
| // To determine whether a security descriptor is self-relative or absolute, |
| // call the GetSecurityDescriptorControl function and check the |
| // SE_SELF_RELATIVE flag of the SECURITY_DESCRIPTOR_CONTROL parameter. |
| // |
| if (!GetSecurityDescriptorControl(pSD, &control, &revision)) |
| { |
| ret = GetLastError(); |
| goto ChangeFileModeByMaskEnd; |
| } |
| |
| // If the security descriptor is self-relative, we use MakeAbsoluteSD function |
| // to convert it to absolute format. |
| // |
| if ((control & SE_SELF_RELATIVE) == SE_SELF_RELATIVE) |
| { |
| DWORD absSDSize = 0; |
| DWORD daclSize = 0; |
| DWORD saclSize = 0; |
| DWORD ownerSize = 0; |
| DWORD primaryGroupSize = 0; |
| MakeAbsoluteSD(pSD, NULL, &absSDSize, NULL, &daclSize, NULL, |
| &saclSize, NULL, &ownerSize, NULL, &primaryGroupSize); |
| if ((dwErrorCode = GetLastError()) != ERROR_INSUFFICIENT_BUFFER) |
| { |
| ret = dwErrorCode; |
| goto ChangeFileModeByMaskEnd; |
| } |
| |
| if ((pAbsSD = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR, absSDSize)) == NULL) |
| { |
| ret = GetLastError(); |
| goto ChangeFileModeByMaskEnd; |
| } |
| if ((pAbsDacl = (PACL) LocalAlloc(LPTR, daclSize)) == NULL) |
| { |
| ret = GetLastError(); |
| goto ChangeFileModeByMaskEnd; |
| } |
| if ((pAbsSacl = (PACL) LocalAlloc(LPTR, saclSize)) == NULL) |
| { |
| ret = GetLastError(); |
| goto ChangeFileModeByMaskEnd; |
| } |
| if ((pAbsOwner = (PSID) LocalAlloc(LPTR, ownerSize)) == NULL) |
| { |
| ret = GetLastError(); |
| goto ChangeFileModeByMaskEnd; |
| } |
| if ((pAbsGroup = (PSID) LocalAlloc(LPTR, primaryGroupSize)) == NULL) |
| { |
| ret = GetLastError(); |
| goto ChangeFileModeByMaskEnd; |
| } |
| |
| if (!MakeAbsoluteSD(pSD, pAbsSD, &absSDSize, pAbsDacl, &daclSize, pAbsSacl, |
| &saclSize, pAbsOwner, &ownerSize, pAbsGroup, &primaryGroupSize)) |
| { |
| ret = GetLastError(); |
| goto ChangeFileModeByMaskEnd; |
| } |
| } |
| |
| // Get Windows DACLs based on Unix access mask |
| // |
| if ((dwRtnCode = GetWindowsDACLs(mode, pOwnerSid, pGroupSid, &pNewDACL)) |
| != ERROR_SUCCESS) |
| { |
| ret = dwRtnCode; |
| goto ChangeFileModeByMaskEnd; |
| } |
| |
| // Set the DACL information in the security descriptor; if a DACL is already |
| // present in the security descriptor, the DACL is replaced. The security |
| // descriptor is then used to set the security of a file or directory. |
| // |
| pNonNullSD = (pAbsSD != NULL) ? pAbsSD : pSD; |
| if (!SetSecurityDescriptorDacl(pNonNullSD, TRUE, pNewDACL, FALSE)) |
| { |
| ret = GetLastError(); |
| goto ChangeFileModeByMaskEnd; |
| } |
| |
| // MSDN states "This function is obsolete. Use the SetNamedSecurityInfo |
| // function instead." However we have the following problem when using |
| // SetNamedSecurityInfo: |
| // - When PROTECTED_DACL_SECURITY_INFORMATION is not passed in as part of |
| // security information, the object will include inheritable permissions |
| // from its parent. |
| // - When PROTECTED_DACL_SECURITY_INFORMATION is passsed in to set |
| // permissions on a directory, the child object of the directory will lose |
| // inheritable permissions from their parent (the current directory). |
| // By using SetFileSecurity, we have the nice property that the new |
| // permissions of the object does not include the inheritable permissions from |
| // its parent, and the child objects will not lose their inherited permissions |
| // from the current object. |
| // |
| if (!SetFileSecurity(longPathName, DACL_SECURITY_INFORMATION, pNonNullSD)) |
| { |
| ret = GetLastError(); |
| goto ChangeFileModeByMaskEnd; |
| } |
| |
| ChangeFileModeByMaskEnd: |
| pNonNullSD = NULL; |
| LocalFree(longPathName); |
| LocalFree(pSD); |
| LocalFree(pNewDACL); |
| LocalFree(pAbsDacl); |
| LocalFree(pAbsSacl); |
| LocalFree(pAbsOwner); |
| LocalFree(pAbsGroup); |
| LocalFree(pAbsSD); |
| |
| return ret; |
| } |
| |
| //---------------------------------------------------------------------------- |
| // Function: GetAccntNameFromSid |
| // |
| // Description: |
| // To retrieve an account name given the SID |
| // |
| // Returns: |
| // ERROR_SUCCESS: on success |
| // Other error code: otherwise |
| // |
| // Notes: |
| // Caller needs to destroy the memory of account name by calling LocalFree() |
| // |
| DWORD GetAccntNameFromSid(__in PSID pSid, __out PWSTR *ppAcctName) |
| { |
| LPWSTR lpName = NULL; |
| DWORD cchName = 0; |
| LPWSTR lpDomainName = NULL; |
| DWORD cchDomainName = 0; |
| SID_NAME_USE eUse = SidTypeUnknown; |
| DWORD cchAcctName = 0; |
| DWORD dwErrorCode = ERROR_SUCCESS; |
| HRESULT hr = S_OK; |
| |
| DWORD ret = ERROR_SUCCESS; |
| |
| assert(ppAcctName != NULL); |
| |
| // NOTE: |
| // MSDN says the length returned for the buffer size including the terminating |
| // null character. However we found it is not true during debuging. |
| // |
| LookupAccountSid(NULL, pSid, NULL, &cchName, NULL, &cchDomainName, &eUse); |
| if ((dwErrorCode = GetLastError()) != ERROR_INSUFFICIENT_BUFFER) |
| return dwErrorCode; |
| lpName = (LPWSTR) LocalAlloc(LPTR, (cchName + 1) * sizeof(WCHAR)); |
| if (lpName == NULL) |
| { |
| ret = GetLastError(); |
| goto GetAccntNameFromSidEnd; |
| } |
| lpDomainName = (LPWSTR) LocalAlloc(LPTR, (cchDomainName + 1) * sizeof(WCHAR)); |
| if (lpDomainName == NULL) |
| { |
| ret = GetLastError(); |
| goto GetAccntNameFromSidEnd; |
| } |
| |
| if (!LookupAccountSid(NULL, pSid, |
| lpName, &cchName, lpDomainName, &cchDomainName, &eUse)) |
| { |
| ret = GetLastError(); |
| goto GetAccntNameFromSidEnd; |
| } |
| |
| // Buffer size = name length + 1 for '\' + domain length + 1 for NULL |
| cchAcctName = cchName + cchDomainName + 2; |
| *ppAcctName = (LPWSTR) LocalAlloc(LPTR, cchAcctName * sizeof(WCHAR)); |
| if (*ppAcctName == NULL) |
| { |
| ret = GetLastError(); |
| goto GetAccntNameFromSidEnd; |
| } |
| |
| hr = StringCchCopyW(*ppAcctName, cchAcctName, lpDomainName); |
| if (FAILED(hr)) |
| { |
| ret = HRESULT_CODE(hr); |
| goto GetAccntNameFromSidEnd; |
| } |
| |
| hr = StringCchCatW(*ppAcctName, cchAcctName, L"\\"); |
| if (FAILED(hr)) |
| { |
| ret = HRESULT_CODE(hr); |
| goto GetAccntNameFromSidEnd; |
| } |
| |
| hr = StringCchCatW(*ppAcctName, cchAcctName, lpName); |
| if (FAILED(hr)) |
| { |
| ret = HRESULT_CODE(hr); |
| goto GetAccntNameFromSidEnd; |
| } |
| |
| GetAccntNameFromSidEnd: |
| LocalFree(lpName); |
| LocalFree(lpDomainName); |
| if (ret != ERROR_SUCCESS) |
| { |
| LocalFree(*ppAcctName); |
| *ppAcctName = NULL; |
| } |
| return ret; |
| } |
| |
| //---------------------------------------------------------------------------- |
| // Function: GetLocalGroupsForUser |
| // |
| // Description: |
| // Get an array of groups for the given user. |
| // |
| // Returns: |
| // ERROR_SUCCESS on success |
| // Other error code on failure |
| // |
| // Notes: |
| // - NetUserGetLocalGroups() function only accepts full user name in the format |
| // [domain name]\[username]. The user input to this function can be only the |
| // username. In this case, NetUserGetLocalGroups() will fail on the first try, |
| // and we will try to find full user name using LookupAccountNameW() method, |
| // and call NetUserGetLocalGroups() function again with full user name. |
| // However, it is not always possible to find full user name given only user |
| // name. For example, a computer named 'win1' joined domain 'redmond' can have |
| // two different users, 'win1\alex' and 'redmond\alex'. Given only 'alex', we |
| // cannot tell which one is correct. |
| // |
| // - Caller needs to destroy the memory of groups by using the |
| // NetApiBufferFree() function |
| // |
| DWORD GetLocalGroupsForUser( |
| __in LPCWSTR user, |
| __out LPLOCALGROUP_USERS_INFO_0 *groups, |
| __out LPDWORD entries) |
| { |
| DWORD dwEntriesRead = 0; |
| DWORD dwTotalEntries = 0; |
| NET_API_STATUS nStatus = NERR_Success; |
| |
| PSID pUserSid = NULL; |
| LPWSTR fullName = NULL; |
| |
| DWORD dwRtnCode = ERROR_SUCCESS; |
| |
| DWORD ret = ERROR_SUCCESS; |
| |
| *groups = NULL; |
| *entries = 0; |
| |
| nStatus = NetUserGetLocalGroups(NULL, |
| user, |
| 0, |
| 0, |
| (LPBYTE *) groups, |
| MAX_PREFERRED_LENGTH, |
| &dwEntriesRead, |
| &dwTotalEntries); |
| |
| if (nStatus == NERR_Success) |
| { |
| *entries = dwEntriesRead; |
| return ERROR_SUCCESS; |
| } |
| else if (nStatus != NERR_UserNotFound) |
| { |
| return nStatus; |
| } |
| |
| if ((dwRtnCode = GetSidFromAcctNameW(user, &pUserSid)) != ERROR_SUCCESS) |
| { |
| ret = dwRtnCode; |
| goto GetLocalGroupsForUserEnd; |
| } |
| |
| if ((dwRtnCode = GetAccntNameFromSid(pUserSid, &fullName)) != ERROR_SUCCESS) |
| { |
| ret = dwRtnCode; |
| goto GetLocalGroupsForUserEnd; |
| } |
| |
| nStatus = NetUserGetLocalGroups(NULL, |
| fullName, |
| 0, |
| 0, |
| (LPBYTE *) groups, |
| MAX_PREFERRED_LENGTH, |
| &dwEntriesRead, |
| &dwTotalEntries); |
| if (nStatus != NERR_Success) |
| { |
| // NERR_DCNotFound (2453) and NERR_UserNotFound (2221) are not published |
| // Windows System Error Code. All other error codes returned by |
| // NetUserGetLocalGroups() are valid System Error Codes according to MSDN. |
| ret = nStatus; |
| goto GetLocalGroupsForUserEnd; |
| } |
| |
| *entries = dwEntriesRead; |
| |
| GetLocalGroupsForUserEnd: |
| LocalFree(pUserSid); |
| LocalFree(fullName); |
| return ret; |
| } |
| |
| //---------------------------------------------------------------------------- |
| // Function: EnablePrivilege |
| // |
| // Description: |
| // Check if the process has the given privilege. If yes, enable the privilege |
| // to the process's access token. |
| // |
| // Returns: |
| // TRUE: on success |
| // |
| // Notes: |
| // |
| BOOL EnablePrivilege(__in LPCWSTR privilegeName) |
| { |
| HANDLE hToken = INVALID_HANDLE_VALUE; |
| TOKEN_PRIVILEGES tp = { 0 }; |
| DWORD dwErrCode = ERROR_SUCCESS; |
| |
| if (!OpenProcessToken(GetCurrentProcess(), |
| TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) |
| { |
| ReportErrorCode(L"OpenProcessToken", GetLastError()); |
| return FALSE; |
| } |
| |
| tp.PrivilegeCount = 1; |
| if (!LookupPrivilegeValueW(NULL, |
| privilegeName, &(tp.Privileges[0].Luid))) |
| { |
| ReportErrorCode(L"LookupPrivilegeValue", GetLastError()); |
| CloseHandle(hToken); |
| return FALSE; |
| } |
| tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; |
| |
| // As stated on MSDN, we need to use GetLastError() to check if |
| // AdjustTokenPrivileges() adjusted all of the specified privileges. |
| // |
| AdjustTokenPrivileges(hToken, FALSE, &tp, 0, NULL, NULL); |
| dwErrCode = GetLastError(); |
| CloseHandle(hToken); |
| |
| return dwErrCode == ERROR_SUCCESS; |
| } |
| |
| //---------------------------------------------------------------------------- |
| // Function: ReportErrorCode |
| // |
| // Description: |
| // Report an error. Use FormatMessage function to get the system error message. |
| // |
| // Returns: |
| // None |
| // |
| // Notes: |
| // |
| // |
| void ReportErrorCode(LPCWSTR func, DWORD err) |
| { |
| DWORD len = 0; |
| LPWSTR msg = NULL; |
| |
| assert(func != NULL); |
| |
| len = FormatMessageW( |
| FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, |
| NULL, err, |
| MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), |
| (LPWSTR)&msg, 0, NULL); |
| if (len > 0) |
| { |
| fwprintf(stderr, L"%s error (%d): %s\n", func, err, msg); |
| } |
| else |
| { |
| fwprintf(stderr, L"%s error code: %d.\n", func, err); |
| } |
| if (msg != NULL) LocalFree(msg); |
| } |
| |
| //---------------------------------------------------------------------------- |
| // Function: GetLibraryName |
| // |
| // Description: |
| // Given an address, get the file name of the library from which it was loaded. |
| // |
| // Returns: |
| // None |
| // |
| // Notes: |
| // - The function allocates heap memory and points the filename out parameter to |
| // the newly allocated memory, which will contain the name of the file. |
| // |
| // - If there is any failure, then the function frees the heap memory it |
| // allocated and sets the filename out parameter to NULL. |
| // |
| void GetLibraryName(LPCVOID lpAddress, LPWSTR *filename) |
| { |
| SIZE_T ret = 0; |
| DWORD size = MAX_PATH; |
| HMODULE mod = NULL; |
| DWORD err = ERROR_SUCCESS; |
| |
| MEMORY_BASIC_INFORMATION mbi; |
| ret = VirtualQuery(lpAddress, &mbi, sizeof(mbi)); |
| if (ret == 0) goto cleanup; |
| mod = mbi.AllocationBase; |
| |
| do { |
| *filename = (LPWSTR) realloc(*filename, size * sizeof(WCHAR)); |
| if (*filename == NULL) goto cleanup; |
| GetModuleFileName(mod, *filename, size); |
| size <<= 1; |
| err = GetLastError(); |
| } while (err == ERROR_INSUFFICIENT_BUFFER); |
| |
| if (err != ERROR_SUCCESS) goto cleanup; |
| |
| return; |
| |
| cleanup: |
| if (*filename != NULL) |
| { |
| free(*filename); |
| *filename = NULL; |
| } |
| } |