Merge pull request #6277 from duplicati/feature/issue2833
Set VSS to Auto if user has permissions
This commit is contained in:
@@ -109,6 +109,13 @@ namespace Duplicati.Library.Main
|
||||
/// </summary>
|
||||
private const long DEFAULT_BACKUP_TEST_SAMPLES = 1;
|
||||
|
||||
/// <summary>
|
||||
/// The default snapshot policy
|
||||
/// </summary>
|
||||
private static readonly OptimizationStrategy DEFAULT_SNAPSHOT_POLICY = PermissionHelper.HasSeBackupPrivilege()
|
||||
? OptimizationStrategy.Auto
|
||||
: OptimizationStrategy.Off;
|
||||
|
||||
/// <summary>
|
||||
/// The default number of compressor instances
|
||||
/// </summary>
|
||||
@@ -372,7 +379,7 @@ namespace Duplicati.Library.Main
|
||||
|
||||
new CommandLineArgument("upload-unchanged-backups", CommandLineArgument.ArgumentType.Boolean, Strings.Options.UploadUnchangedBackupsShort, Strings.Options.UploadUnchangedBackupsLong, "false"),
|
||||
|
||||
new CommandLineArgument("snapshot-policy", CommandLineArgument.ArgumentType.Enumeration, Strings.Options.SnapshotpolicyShort, Strings.Options.SnapshotpolicyLong, "off", null, Enum.GetNames(typeof(OptimizationStrategy))),
|
||||
new CommandLineArgument("snapshot-policy", CommandLineArgument.ArgumentType.Enumeration, Strings.Options.SnapshotpolicyShort, Strings.Options.SnapshotpolicyLong, DEFAULT_SNAPSHOT_POLICY.ToString(), null, Enum.GetNames(typeof(OptimizationStrategy))),
|
||||
new CommandLineArgument("snapshot-provider", CommandLineArgument.ArgumentType.Enumeration, Strings.Options.SnapshotproviderShort, Strings.Options.SnapshotproviderLong, OperatingSystem.IsWindows() ? Snapshots.SnapshotProvider.AlphaVSS.ToString() : Snapshots.SnapshotProvider.LVM.ToString(), null, (OperatingSystem.IsWindows() ? [Snapshots.SnapshotProvider.AlphaVSS, Snapshots.SnapshotProvider.Wmic] : new [] { Snapshots.SnapshotProvider.LVM }).Select(x => x.ToString()).ToArray()),
|
||||
new CommandLineArgument("vss-exclude-writers", CommandLineArgument.ArgumentType.String, Strings.Options.VssexcludewritersShort, Strings.Options.VssexcludewritersLong, "{e8132975-6f93-4464-a53e-1050253ae220}"),
|
||||
new CommandLineArgument("vss-use-mapping", CommandLineArgument.ArgumentType.Boolean, Strings.Options.VssusemappingShort, Strings.Options.VssusemappingLong, "false"),
|
||||
@@ -894,7 +901,7 @@ namespace Duplicati.Library.Main
|
||||
/// <summary>
|
||||
/// Gets the snapshot strategy to use
|
||||
/// </summary>
|
||||
public OptimizationStrategy SnapShotStrategy => GetEnum("snapshot-policy", OptimizationStrategy.Off);
|
||||
public OptimizationStrategy SnapShotStrategy => GetEnum("snapshot-policy", DEFAULT_SNAPSHOT_POLICY);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the snapshot strategy to use
|
||||
|
||||
@@ -0,0 +1,356 @@
|
||||
// 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;
|
||||
|
||||
/// <summary>
|
||||
/// Collection of helper methods to check for permissions.
|
||||
/// </summary>
|
||||
public static class PermissionHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if the current process has the Backup Privilege.
|
||||
/// </summary>
|
||||
/// <returns>True if has Backup Privilege, false otherwise.</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the current process is running as an administrator, LocalSystem or root.
|
||||
/// </summary>
|
||||
/// <returns>True if running as administrator, LocalSystem or root, false otherwise.</returns>
|
||||
public static bool IsRunningAsAdministratorOrLocalSystem()
|
||||
{
|
||||
if (OperatingSystem.IsWindows())
|
||||
return IsRunningAsAdministrator() || IsRunningAsLocalSystem();
|
||||
if (OperatingSystem.IsLinux())
|
||||
return IsRunningAsRoot();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the current process is running as an administrator.
|
||||
/// </summary>
|
||||
/// <returns>True if running as administrator, false otherwise.</returns>
|
||||
[SupportedOSPlatform("windows")]
|
||||
private static bool IsRunningAsAdministrator()
|
||||
{
|
||||
using var identity = WindowsIdentity.GetCurrent();
|
||||
var principal = new WindowsPrincipal(identity);
|
||||
return principal.IsInRole(WindowsBuiltInRole.Administrator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the current process is running as LocalSystem.
|
||||
/// </summary>
|
||||
/// <returns>True if running as LocalSystem, false otherwise.</returns>
|
||||
[SupportedOSPlatform("windows")]
|
||||
private static bool IsRunningAsLocalSystem()
|
||||
{
|
||||
using (var identity = WindowsIdentity.GetCurrent())
|
||||
return identity.User?.Value == "S-1-5-18"; // SID for LocalSystem
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the effective user ID of the calling process.
|
||||
/// </summary>
|
||||
/// <returns>The effective user ID of the calling process.</returns>
|
||||
[SupportedOSPlatform("linux")]
|
||||
[DllImport("libc")]
|
||||
public static extern uint geteuid();
|
||||
|
||||
/// <summary>
|
||||
/// Check if the current process is running as root (user ID 0).
|
||||
/// </summary>
|
||||
/// <returns>True if running as root, false otherwise.</returns>
|
||||
[SupportedOSPlatform("linux")]
|
||||
private static bool IsRunningAsRoot()
|
||||
{
|
||||
return geteuid() == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper class for checking Windows privileges.
|
||||
/// </summary>
|
||||
private class PrivilegeChecker
|
||||
{
|
||||
/// <summary>
|
||||
/// Constant for adjusting token privileges
|
||||
/// </summary>
|
||||
const int TOKEN_ADJUST_PRIVILEGES = 0x0020;
|
||||
/// <summary>
|
||||
/// Constants for token access rights.
|
||||
/// </summary>
|
||||
const int TOKEN_QUERY = 0x0008;
|
||||
/// <summary>
|
||||
/// Constant for privilege enabled attribute.
|
||||
/// </summary>
|
||||
const int SE_PRIVILEGE_ENABLED = 0x00000002;
|
||||
|
||||
/// <summary>
|
||||
/// Structure that contains a locally unique identifier (LUID).
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct LUID
|
||||
{
|
||||
public uint LowPart;
|
||||
public int HighPart;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is LUID other &&
|
||||
LowPart == other.LowPart &&
|
||||
HighPart == other.HighPart;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(LowPart, HighPart);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Structure that contains a locally unique identifier (LUID) and its attributes.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct LUID_AND_ATTRIBUTES
|
||||
{
|
||||
/// <summary>
|
||||
/// The locally unique identifier (LUID) for the privilege.
|
||||
/// </summary>
|
||||
public LUID Luid;
|
||||
/// <summary>
|
||||
/// The attributes for the privilege.
|
||||
/// </summary>
|
||||
public int Attributes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Structure that contains the privileges associated with a token.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
struct TOKEN_PRIVILEGES
|
||||
{
|
||||
/// <summary>
|
||||
/// The number of privileges in the Privileges array.
|
||||
/// </summary>
|
||||
public int PrivilegeCount;
|
||||
/// <summary>
|
||||
/// The privileges associated with the token.
|
||||
/// </summary>
|
||||
/// <remarks>This field is actually an array of ANY size — we handle this dynamically</remarks>
|
||||
public LUID_AND_ATTRIBUTES Privileges;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the access token associated with a process.
|
||||
/// </summary>
|
||||
/// <param name="ProcessHandle">The handle of the process whose access token is to be opened.</param>
|
||||
/// <param name="DesiredAccess">The access rights to the token.</param>
|
||||
/// <param name="TokenHandle"> A pointer to a variable that receives the handle to the opened access token.</param>
|
||||
/// <returns><c>true</c> if the function succeeds; otherwise, <c>false</c>.</returns>
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
static extern bool OpenProcessToken(IntPtr ProcessHandle, int DesiredAccess, out IntPtr TokenHandle);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a handle to the access token associated with the current process.
|
||||
/// /// </summary>
|
||||
/// <returns>A handle to the access token associated with the current process.</returns>
|
||||
[DllImport("kernel32.dll")]
|
||||
static extern IntPtr GetCurrentProcess();
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a specified type of information about an access token.
|
||||
/// </summary>
|
||||
/// <param name="TokenHandle">A handle to the access token from which information is being retrieved.</param>
|
||||
/// <param name="TokenInformationClass">The type of information to be retrieved.</param>
|
||||
/// <param name="TokenInformation">A pointer to a buffer that receives the requested information.</param>
|
||||
/// <param name="TokenInformationLength">The size of the buffer pointed to by the TokenInformation parameter.</param>
|
||||
/// <param name="ReturnLength">A pointer to a variable that receives the size of the data returned in the TokenInformation buffer.</param>
|
||||
/// <returns><c>true</c> if the function succeeds; otherwise, <c>false</c>.</returns>
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
static extern bool GetTokenInformation(
|
||||
IntPtr TokenHandle,
|
||||
int TokenInformationClass,
|
||||
IntPtr TokenInformation,
|
||||
int TokenInformationLength,
|
||||
out int ReturnLength
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the locally unique identifier (LUID) for a specified privilege name.
|
||||
/// </summary>
|
||||
/// <param name="lpSystemName">The name of the system on which the privilege is being looked up. This parameter can be null.</param>
|
||||
/// <param name="lpName">The name of the privilege to look up. This parameter cannot be null.</param>
|
||||
/// <param name="lpLuid">A pointer to a LUID structure that receives the LUID for the specified privilege.</param>
|
||||
/// <returns><c>true</c> if the function succeeds; otherwise, <c>false</c>.</returns>
|
||||
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
static extern bool LookupPrivilegeValue(string? lpSystemName, string lpName, out LUID lpLuid);
|
||||
|
||||
/// <summary>
|
||||
/// Adjusts the privileges of a specified access token.
|
||||
/// </summary>
|
||||
/// <param name="TokenHandle">A handle to the access token whose privileges are to be adjusted.</param>
|
||||
/// <param name="DisableAllPrivileges">Specifies whether all privileges are to be disabled.</param>
|
||||
/// <param name="NewState">A pointer to a TOKEN_PRIVILEGES structure that specifies the new privileges.</param>
|
||||
/// <param name="BufferLength">The size of the buffer pointed to by the PreviousState parameter.</param>
|
||||
/// <param name="PreviousState">A pointer to a buffer that receives the previous state of the privileges.</param>
|
||||
/// <param name="ReturnLength">A pointer to a variable that receives the size of the data returned in the PreviousState buffer.</param>
|
||||
/// <returns><c>true</c> if the function succeeds; otherwise, <c>false</c>.</returns>
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
static extern bool AdjustTokenPrivileges(
|
||||
IntPtr TokenHandle,
|
||||
bool DisableAllPrivileges,
|
||||
ref TOKEN_PRIVILEGES NewState,
|
||||
int BufferLength,
|
||||
IntPtr PreviousState,
|
||||
IntPtr ReturnLength
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the calling thread's last-error code value.
|
||||
/// </summary>
|
||||
/// <returns>The last-error code value.</returns>
|
||||
[DllImport("kernel32.dll")]
|
||||
static extern uint GetLastError();
|
||||
|
||||
/// <summary>
|
||||
/// Flag tracking if backup privilege is enabled
|
||||
/// </summary>
|
||||
private static bool? _isPrivilegeEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// Tries to enable a specific privilege in the current process token.
|
||||
/// </summary>
|
||||
/// <param name="privilegeName">The name of the privilege (e.g., "SeBackupPrivilege").</param>
|
||||
/// <returns>
|
||||
/// True if the privilege is present and was successfully enabled.
|
||||
/// False if the privilege is not held or could not be enabled.
|
||||
/// </returns>
|
||||
public static bool TryEnablePrivilege(string privilegeName)
|
||||
{
|
||||
if (_isPrivilegeEnabled.HasValue)
|
||||
return _isPrivilegeEnabled.Value;
|
||||
|
||||
try
|
||||
{
|
||||
_isPrivilegeEnabled = false;
|
||||
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out IntPtr tokenHandle))
|
||||
return false;
|
||||
|
||||
if (!LookupPrivilegeValue(null, privilegeName, out LUID luid))
|
||||
return false;
|
||||
|
||||
var tp = new TOKEN_PRIVILEGES
|
||||
{
|
||||
PrivilegeCount = 1,
|
||||
Privileges = new LUID_AND_ATTRIBUTES()
|
||||
{
|
||||
Luid = luid,
|
||||
Attributes = SE_PRIVILEGE_ENABLED
|
||||
}
|
||||
};
|
||||
|
||||
if (!AdjustTokenPrivileges(tokenHandle, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero))
|
||||
return false;
|
||||
|
||||
_isPrivilegeEnabled = (GetLastError() == 0); // If not zero, then the privilege wasn't assigned (e.g., ERROR_NOT_ALL_ASSIGNED)
|
||||
return _isPrivilegeEnabled.Value;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Ignore this error
|
||||
}
|
||||
|
||||
return _isPrivilegeEnabled ?? false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the current process token has the specified Windows privilege enabled.
|
||||
/// </summary>
|
||||
/// <param name="privilegeName">
|
||||
/// The name of the privilege to check (e.g., "SeBackupPrivilege", "SeRestorePrivilege").
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// <c>true</c> if the privilege is present and enabled in the current process token; otherwise, <c>false</c>.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// 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.
|
||||
/// </remarks>
|
||||
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<LUID_AND_ATTRIBUTES>();
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
LUID_AND_ATTRIBUTES laa = Marshal.PtrToStructure<LUID_AND_ATTRIBUTES>(luidPtr + i * structSize);
|
||||
if (laa.Luid.Equals(targetLuid))
|
||||
return (laa.Attributes & SE_PRIVILEGE_ENABLED) != 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user