#region Disclaimer / License // Copyright (C) 2015, The Duplicati Team // http://www.duplicati.com, info@duplicati.com // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // #endregion using System; using System.Collections.Generic; using System.Text; using System.Runtime.InteropServices; using System.ComponentModel; using Microsoft.Win32.SafeHandles; namespace Duplicati.Library.Snapshots { /// /// This class contains various Windows system calls related to reading locked files. /// Some PInvoke signatures are from http://www.pinvoke.net /// public static class WinNativeMethods { [Flags] private enum FileAccess : uint { GenericNone = 0x00000000, GenericRead = 0x80000000, GenericWrite = 0x40000000, GenericExecute = 0x20000000, GenericAll = 0x10000000, ReadControl = 0x00020000 } [Flags] private enum FileShare : uint { None = 0x00000000, Read = 0x00000001, Write = 0x00000002, ReadWrite = 0x00000003, Delete = 0x00000004 } private enum CreationDisposition : uint { New = 1, CreateAlways = 2, OpenExisting = 3, OpenAlways = 4, TruncateExisting = 5 } [Flags] private enum FileAttributes : uint { Readonly = 0x00000001, Hidden = 0x00000002, System = 0x00000004, Directory = 0x00000010, Archive = 0x00000020, Device = 0x00000040, Normal = 0x00000080, Temporary = 0x00000100, SparseFile = 0x00000200, ReparsePoint = 0x00000400, Compressed = 0x00000800, Offline = 0x00001000, NotContentIndexed = 0x00002000, Encrypted = 0x00004000, Write_Through = 0x80000000, Overlapped = 0x40000000, NoBuffering = 0x20000000, RandomAccess = 0x10000000, SequentialScan = 0x08000000, DeleteOnClose = 0x04000000, BackupSemantics = 0x02000000, PosixSemantics = 0x01000000, OpenReparsePoint = 0x00200000, OpenNoRecall = 0x00100000, FirstPipeInstance = 0x00080000 } private const int TOKEN_ADJUST_PRIVILEGES = 0x00000020; private const int TOKEN_QUERY = 0x00000008; private const int SE_PRIVILEGE_ENABLED = 0x00000002; private const string SE_BACKUP_NAME = "SeBackupPrivilege"; private const string SE_RESTORE_NAME = "SeRestorePrivilege"; private const int TOKEN_INFORMATION_CLASS_PRIVILEGES = 0x3; private const int ATTRIBUTE_FIXED_SIZE = 200; [StructLayout(LayoutKind.Sequential)] private struct LUID { public UInt32 LowPart; public Int32 HighPart; } [StructLayout(LayoutKind.Sequential)] private struct LUID_AND_ATTRIBUTES { public LUID Luid; public UInt32 Attributes; } [StructLayout(LayoutKind.Sequential)] private struct TOKEN_PRIVILEGES { public UInt32 PrivilegeCount; [MarshalAs(UnmanagedType.ByValArray, SizeConst = ATTRIBUTE_FIXED_SIZE)] //We make sure there is enough room public LUID_AND_ATTRIBUTES[] Privileges; } [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern int OpenProcessToken(int ProcessHandle, int DesiredAccess, ref int tokenhandle); [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern int GetCurrentProcess(); [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern int GetTokenInformation(int tokenhandle, int tokeninformationclass, [MarshalAs(UnmanagedType.Struct)]ref TOKEN_PRIVILEGES state, int bufferlength, ref int actualsize); [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern int LookupPrivilegeValue(string lpsystemname, string lpname, [MarshalAs(UnmanagedType.Struct)] ref LUID lpLuid); [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern int AdjustTokenPrivileges(int tokenhandle, bool disableprivs, [MarshalAs(UnmanagedType.Struct)]ref TOKEN_PRIVILEGES newState, int bufferlength, [MarshalAs(UnmanagedType.Struct)]ref TOKEN_PRIVILEGES previousState, ref int actualsize); [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern int LookupPrivilegeName(string lpSystemName, [MarshalAs(UnmanagedType.Struct)] ref LUID lpLuid, System.Text.StringBuilder lpName, ref int cchName); [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] private static extern SafeFileHandle CreateFile( string lpFileName, FileAccess dwDesiredAccess, FileShare dwShareMode, IntPtr lpSecurityAttributes, CreationDisposition dwCreationDisposition, FileAttributes dwFlagsAndAttributes, IntPtr hTemplateFile ); /// /// Opens a file with backup semantics, only works if the caller has the SeBackupPrivilege /// /// The file to open /// The opened backup filestream public static System.IO.Stream OpenAsBackupFile(string filename) { SafeFileHandle hFile = CreateFile(filename, FileAccess.ReadControl, FileShare.None, IntPtr.Zero, CreationDisposition.OpenExisting, FileAttributes.BackupSemantics | FileAttributes.SequentialScan, IntPtr.Zero); if (hFile.IsInvalid) throw new Win32Exception(Marshal.GetLastWin32Error()); //TODO: We need to be able to seek to pos 0 return new Alphaleonis.Win32.Filesystem.BackupFileStream(hFile, Alphaleonis.Win32.Filesystem.FileSystemRights.Read); } /// /// Internal helper function that returns two values relating to the SeBackupPrivilege /// /// True if the calling proccess has the SeBackupPrivilege, false otherwise /// True if the SeBackupPrivilege is enabled, false otherwise private static void GetBackupPrivilege(out bool hasPrivilege, out bool isEnabled) { int token = 0; int outsize = 0; TOKEN_PRIVILEGES TP = new TOKEN_PRIVILEGES(); LUID backupLuid = new LUID(); if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref token) == 0) throw new Win32Exception(Marshal.GetLastWin32Error()); if (LookupPrivilegeValue(null, SE_BACKUP_NAME, ref backupLuid) == 0) throw new Win32Exception(Marshal.GetLastWin32Error()); if (GetTokenInformation(token, TOKEN_INFORMATION_CLASS_PRIVILEGES, ref TP, Marshal.SizeOf(typeof(TOKEN_PRIVILEGES)), ref outsize) == 0) throw new Win32Exception(Marshal.GetLastWin32Error()); #if DEBUG Console.WriteLine("Read token information:"); PrintTokenInformation(TP); #endif for (int i = 0; i < TP.PrivilegeCount; i++) if (TP.Privileges[i].Luid.LowPart == backupLuid.LowPart && TP.Privileges[i].Luid.HighPart == backupLuid.HighPart) { isEnabled = (TP.Privileges[i].Attributes & SE_PRIVILEGE_ENABLED) == SE_PRIVILEGE_ENABLED; hasPrivilege = true; return; } hasPrivilege = false; isEnabled = false; } /// /// Gets a value indicating if the current process can set the SeBackupPrivilege value /// public static bool CanEnableBackupPrivilege { get { bool hasPrivilege, isEnabled; GetBackupPrivilege(out hasPrivilege, out isEnabled); return hasPrivilege; } } /// /// Gets or sets a value indicating if the current process has the backup privilege (SE_BACKUP_NAME) /// public static bool BackupPrivilege { get { bool hasPrivilege, isEnabled; GetBackupPrivilege(out hasPrivilege, out isEnabled); if (!hasPrivilege) throw new Exception(Strings.WinNativeMethod.MissingBackupPrivilegeError); return isEnabled; } set { if (!CanEnableBackupPrivilege) throw new Exception(Strings.WinNativeMethod.MissingBackupPrivilegeError); int token = 0; int outsize = 0; TOKEN_PRIVILEGES TP = new TOKEN_PRIVILEGES(); LUID backupLuid = new LUID(); LUID restoreLuid = new LUID(); if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref token) == 0) throw new Win32Exception(Marshal.GetLastWin32Error()); if (LookupPrivilegeValue(null, SE_BACKUP_NAME, ref backupLuid) == 0) throw new Win32Exception(Marshal.GetLastWin32Error()); if (LookupPrivilegeValue(null, SE_RESTORE_NAME, ref restoreLuid) == 0) throw new Win32Exception(Marshal.GetLastWin32Error()); TP.PrivilegeCount = 1; TP.Privileges = new LUID_AND_ATTRIBUTES[ATTRIBUTE_FIXED_SIZE]; TP.Privileges[0].Attributes = value ? SE_PRIVILEGE_ENABLED : 0u; TP.Privileges[0].Luid = backupLuid; TP.Privileges[1].Attributes = value ? SE_PRIVILEGE_ENABLED : 0u; TP.Privileges[1].Luid = restoreLuid; TOKEN_PRIVILEGES TPOut = new TOKEN_PRIVILEGES(); if (AdjustTokenPrivileges(token, false, ref TP, Marshal.SizeOf(typeof(TOKEN_PRIVILEGES)), ref TPOut, ref outsize) == 0) throw new Win32Exception(Marshal.GetLastWin32Error()); #if DEBUG Console.WriteLine("Modified token information:"); PrintTokenInformation(TPOut); #endif if (Marshal.GetLastWin32Error() != 0) throw new Win32Exception(Marshal.GetLastWin32Error()); } } #if DEBUG private static void PrintTokenInformation(TOKEN_PRIVILEGES tp) { int outsize; StringBuilder sbname = new StringBuilder(1024 * 4); for (int i = 0; i < tp.PrivilegeCount; i++) { LUID id = tp.Privileges[i].Luid; outsize = sbname.Capacity; if (LookupPrivilegeName(null, ref id, sbname, ref outsize) == 0) Console.WriteLine("Invalid LUID {0}:{1}: {2}", id.HighPart, id.LowPart, new Win32Exception(Marshal.GetLastWin32Error()).Message); else Console.WriteLine("{0}, status {1}", sbname, tp.Privileges[i].Attributes); } } #endif } }