// Copyright (C) 2025, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. #nullable enable using System; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security.Principal; namespace Duplicati.Library.Utility; /// /// Collection of helper methods to check for permissions. /// public static class PermissionHelper { /// /// Checks if the current process has the Backup Privilege. /// /// True if has Backup Privilege, false otherwise. public static bool HasSeBackupPrivilege() { if (OperatingSystem.IsWindows()) try { return PrivilegeChecker.TryEnablePrivilege("SeBackupPrivilege"); } catch (DllNotFoundException) { // If the function is not available, we assume the privilege is not present. } return false; } /// /// Check if the current process is running as an administrator, LocalSystem or root. /// /// True if running as administrator, LocalSystem or root, false otherwise. public static bool IsRunningAsAdministratorOrLocalSystem() { if (OperatingSystem.IsWindows()) return IsRunningAsAdministrator() || IsRunningAsLocalSystem(); if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) return IsRunningAsRoot(); return false; } /// /// Check if the current process is running as an administrator. /// /// True if running as administrator, false otherwise. [SupportedOSPlatform("windows")] private static bool IsRunningAsAdministrator() { using var identity = WindowsIdentity.GetCurrent(); var principal = new WindowsPrincipal(identity); return principal.IsInRole(WindowsBuiltInRole.Administrator); } /// /// Check if the current process is running as LocalSystem. /// /// True if running as LocalSystem, false otherwise. [SupportedOSPlatform("windows")] private static bool IsRunningAsLocalSystem() { using (var identity = WindowsIdentity.GetCurrent()) return identity.User?.Value == "S-1-5-18"; // SID for LocalSystem } /// /// Gets the effective user ID of the calling process. /// /// The effective user ID of the calling process. [SupportedOSPlatform("linux")] [SupportedOSPlatform("macos")] [DllImport("libc")] public static extern uint geteuid(); /// /// Check if the current process is running as root (user ID 0). /// /// True if running as root, false otherwise. [SupportedOSPlatform("linux")] [SupportedOSPlatform("macos")] private static bool IsRunningAsRoot() { return geteuid() == 0; } /// /// Helper class for checking Windows privileges. /// private class PrivilegeChecker { /// /// Constant for adjusting token privileges /// const int TOKEN_ADJUST_PRIVILEGES = 0x0020; /// /// Constants for token access rights. /// const int TOKEN_QUERY = 0x0008; /// /// Constant for privilege enabled attribute. /// const int SE_PRIVILEGE_ENABLED = 0x00000002; /// /// The TOKEN_INFORMATION_CLASS.TokenPrivileges value /// const int TOKEN_INFORMATION_TOKEN_PRIVILEGES = 3; /// /// Structure that contains a locally unique identifier (LUID). /// [StructLayout(LayoutKind.Sequential)] struct LUID { public uint LowPart; public int HighPart; /// public override bool Equals(object? obj) { return obj is LUID other && LowPart == other.LowPart && HighPart == other.HighPart; } /// public override int GetHashCode() { return HashCode.Combine(LowPart, HighPart); } } /// /// Structure that contains a locally unique identifier (LUID) and its attributes. /// [StructLayout(LayoutKind.Sequential)] struct LUID_AND_ATTRIBUTES { /// /// The locally unique identifier (LUID) for the privilege. /// public LUID Luid; /// /// The attributes for the privilege. /// public int Attributes; } /// /// Structure that contains the privileges associated with a token. /// [StructLayout(LayoutKind.Sequential)] struct TOKEN_PRIVILEGES { /// /// The number of privileges in the Privileges array. /// public int PrivilegeCount; /// /// The privileges associated with the token. /// /// This field is actually an array of ANY size — we handle this dynamically public LUID_AND_ATTRIBUTES Privileges; } /// /// Opens the access token associated with a process. /// /// The handle of the process whose access token is to be opened. /// The access rights to the token. /// A pointer to a variable that receives the handle to the opened access token. /// true if the function succeeds; otherwise, false. [DllImport("advapi32.dll", SetLastError = true)] static extern bool OpenProcessToken(IntPtr ProcessHandle, int DesiredAccess, out IntPtr TokenHandle); /// /// Retrieves a handle to the access token associated with the current process. /// /// /// A handle to the access token associated with the current process. [DllImport("kernel32.dll")] static extern IntPtr GetCurrentProcess(); /// /// Retrieves a specified type of information about an access token. /// /// A handle to the access token from which information is being retrieved. /// The type of information to be retrieved. /// A pointer to a buffer that receives the requested information. /// The size of the buffer pointed to by the TokenInformation parameter. /// A pointer to a variable that receives the size of the data returned in the TokenInformation buffer. /// true if the function succeeds; otherwise, false. [DllImport("advapi32.dll", SetLastError = true)] static extern bool GetTokenInformation( IntPtr TokenHandle, int TokenInformationClass, IntPtr TokenInformation, int TokenInformationLength, out int ReturnLength ); /// /// Retrieves the locally unique identifier (LUID) for a specified privilege name. /// /// The name of the system on which the privilege is being looked up. This parameter can be null. /// The name of the privilege to look up. This parameter cannot be null. /// A pointer to a LUID structure that receives the LUID for the specified privilege. /// true if the function succeeds; otherwise, false. [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] static extern bool LookupPrivilegeValue(string? lpSystemName, string lpName, out LUID lpLuid); /// /// Adjusts the privileges of a specified access token. /// /// A handle to the access token whose privileges are to be adjusted. /// Specifies whether all privileges are to be disabled. /// A pointer to a TOKEN_PRIVILEGES structure that specifies the new privileges. /// The size of the buffer pointed to by the PreviousState parameter. /// A pointer to a buffer that receives the previous state of the privileges. /// A pointer to a variable that receives the size of the data returned in the PreviousState buffer. /// true if the function succeeds; otherwise, false. [DllImport("advapi32.dll", SetLastError = true)] static extern bool AdjustTokenPrivileges( IntPtr TokenHandle, bool DisableAllPrivileges, ref TOKEN_PRIVILEGES NewState, int BufferLength, IntPtr PreviousState, out uint ReturnLength ); /// /// Retrieves the calling thread's last-error code value. /// /// The last-error code value. [DllImport("kernel32.dll")] static extern uint GetLastError(); /// /// Closes an open object handle. /// /// The handle to the object to be closed. /// true if the function succeeds; otherwise, false. [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool CloseHandle(IntPtr hObject); /// /// Flag tracking if backup privilege is enabled /// private static bool? _isPrivilegeEnabled; /// /// Tries to enable a specific privilege in the current process token. /// /// The name of the privilege (e.g., "SeBackupPrivilege"). /// /// True if the privilege is present and was successfully enabled. /// False if the privilege is not held or could not be enabled. /// public static bool TryEnablePrivilege(string privilegeName) { if (_isPrivilegeEnabled.HasValue) return _isPrivilegeEnabled.Value; _isPrivilegeEnabled = false; // Open the primary token for this process if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out var hTok)) return false; try { // Translate the textual privilege name (e.g. "SeBackupPrivilege") to a LUID if (!LookupPrivilegeValue(null, privilegeName, out var luid)) return false; // Build TOKEN_PRIVILEGES {1 entry, SE_PRIVILEGE_ENABLED} var tp = new TOKEN_PRIVILEGES { PrivilegeCount = 1, Privileges = new LUID_AND_ATTRIBUTES { Luid = luid, Attributes = SE_PRIVILEGE_ENABLED } }; // Query the size of the current privileges set GetTokenInformation(hTok, TOKEN_INFORMATION_TOKEN_PRIVILEGES, IntPtr.Zero, 0, out int requiredLen); // Allocate a buffer of the requested size var prevStatePtr = Marshal.AllocHGlobal(requiredLen); try { // Enable the privilege, capturing the previous state if (!AdjustTokenPrivileges(hTok, false, ref tp, requiredLen, prevStatePtr, out var _)) return false; // If GetLastError() == ERROR_SUCCESS (0), the privilege is now enabled _isPrivilegeEnabled = Marshal.GetLastWin32Error() == 0; // Restore the original state var oldState = Marshal.PtrToStructure(prevStatePtr); AdjustTokenPrivileges(hTok, false, ref oldState, 0, IntPtr.Zero, out var _); } finally { Marshal.FreeHGlobal(prevStatePtr); } return _isPrivilegeEnabled.Value; } finally { CloseHandle(hTok); } } /// /// Determines whether the current process token has the specified Windows privilege enabled. /// /// /// The name of the privilege to check (e.g., "SeBackupPrivilege", "SeRestorePrivilege"). /// /// /// true if the privilege is present and enabled in the current process token; otherwise, false. /// /// /// This function does not attempt to enable the privilege, it only verifies whether it is enabled. /// Requires that the calling process has permission to query its own token. /// public static bool IsPrivilegeEnabled(string privilegeName) { if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, out IntPtr tokenHandle)) return false; if (!LookupPrivilegeValue(null, privilegeName, out LUID targetLuid)) return false; // First call to get required buffer size GetTokenInformation(tokenHandle, 3, IntPtr.Zero, 0, out int bufferSize); // TokenPrivileges = 3 IntPtr buffer = Marshal.AllocHGlobal(bufferSize); try { if (!GetTokenInformation(tokenHandle, 3, buffer, bufferSize, out _)) return false; int count = Marshal.ReadInt32(buffer); IntPtr luidPtr = buffer + sizeof(int); int structSize = Marshal.SizeOf(); for (int i = 0; i < count; i++) { LUID_AND_ATTRIBUTES laa = Marshal.PtrToStructure(luidPtr + i * structSize); if (laa.Luid.Equals(targetLuid)) return (laa.Attributes & SE_PRIVILEGE_ENABLED) != 0; } return false; } finally { Marshal.FreeHGlobal(buffer); } } } }