1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
| using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Text;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
namespace Projet
{
/// <summary>
/// Wraps calls to <c>AccessCheck</c> and <c>GetFileSecurity</c>.
/// </summary>
public static class FileAccessCheck
{
/// <summary>
/// Wraps a token handle (there is no public such class in .Net 2.0, not before .Net 4.6)
/// </summary>
class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
{
/// <summary>Default constructor (mandated by P/Invoke).</summary>
public SafeTokenHandle() : base(true) { }
/// <summary>ReleaseHandle implementation, just calls <c>CloseHandle</c>.</summary>
protected override bool ReleaseHandle() { return NativeMethods.CloseHandle(handle); }
}
/// <summary>
/// Native method declarations.
/// </summary>
static class NativeMethods
{
//Note: Most of these declarations were taken from the .Net Framework reference source.
public const int OWNER_SECURITY_INFORMATION = 0x00000001;
public const int GROUP_SECURITY_INFORMATION = 0x00000002;
public const int DACL_SECURITY_INFORMATION = 0x00000004;
public const int SACL_SECURITY_INFORMATION = 0x00000008;
[DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
public static extern int GetFileSecurity(string filename, int requestedInformation, byte[] securityDescriptor, int length, ref int lengthNeeded);
[DllImport("advapi32.dll", SetLastError=true)]
public static extern bool
AccessCheck(
[In] byte[] SecurityDescriptor,
[In] IntPtr ClientToken, //Changed from SafeCloseHandle
[In] int DesiredAccess,
[In] GENERIC_MAPPING GenericMapping,
byte[] PrivilegeSet, //Changed from PRIVILEGE_SET reference of dubious type (double pointer?)
ref uint PrivilegeSetLength,
[Out] out uint GrantedAccess,
[Out] out bool AccessStatus);
[StructLayout(LayoutKind.Sequential)]
public class GENERIC_MAPPING
{
internal uint genericRead = 0;
internal uint genericWrite = 0;
internal uint genericExecute = 0;
internal uint genericAll = 0;
}
//These on the other hand, come from the Windows headers
public const int ERROR_INSUFFICIENT_BUFFER = 122;
/// <summary>ReadControl and Synchronize, plus Read, ReadAttributes and ReadEA.</summary>
public const uint FILE_GENERIC_READ = 0x00120089u;
/// <summary>ReadControl and Synchronize, plus Write, Append, WriteAttributes and WriteEA.</summary>
public const uint FILE_GENERIC_WRITE = 0x00120116u;
/// <summary>ReadControl and Synchronize, plus ReadAttributes and Execute.</summary>
public const uint FILE_GENERIC_EXECUTE = 0x001200A0u;
/// <summary>All standard and file access rights.</summary>
public const uint FILE_ALL_ACCESS = 0x001F01FF;
/// <summary>The impersonation level for <c>DuplicateToken</c>.</summary>
public const int SecurityImpersonation = 2;
[DllImport("advapi32.dll", SetLastError=true)]
public static extern bool DuplicateToken(IntPtr token, int impersonationLevel, out SafeTokenHandle hNewToken);
[DllImport("kernel32.dll", SetLastError=true)]
public static extern bool CloseHandle(IntPtr h);
}
private static void ThrowWin32Exception(string contextMessage)
{
int hResult = Marshal.GetHRForLastWin32Error();
Exception ex = Marshal.GetExceptionForHR(hResult);
throw new Win32Exception(contextMessage + ex.Message, ex);
}
/// <summary>
/// Wraps call to <c>GetFileSecurity</c>.
/// </summary>
/// <param name="filePath">[in] Path to the file or directory to check.</param>
/// <returns>The security descriptor as an array of bytes (<c>GetFileSecurity</c> returns it in "self-relative format").</returns>
static byte[] GetSecurityDescriptor(string filePath)
{
int infoFlags = NativeMethods.DACL_SECURITY_INFORMATION|NativeMethods.OWNER_SECURITY_INFORMATION|NativeMethods.GROUP_SECURITY_INFORMATION;
int lengthNeeded = 0;
bool bRet = NativeMethods.GetFileSecurity(filePath, infoFlags, null, 0, ref lengthNeeded) != 0;
if(!bRet && Marshal.GetLastWin32Error()!=NativeMethods.ERROR_INSUFFICIENT_BUFFER)
ThrowWin32Exception("First call to GetFileSecurity() failed: ");
byte[] descriptor = new byte[lengthNeeded];
bRet = NativeMethods.GetFileSecurity(filePath, infoFlags, descriptor, descriptor.Length, ref lengthNeeded) != 0;
if(!bRet)
ThrowWin32Exception("Second call to GetFileSecurity() failed: ");
return descriptor;
}
/// <summary>
/// Wraps call to <c>DuplicateToken</c>, necessary because <c>AccessCheck</c> requires impersonation.
/// </summary>
/// <returns>Token handle for the impersonation token.</returns>
private static SafeTokenHandle GetImpersonationToken()
{
IntPtr token = System.Security.Principal.WindowsIdentity.GetCurrent().Token;
SafeTokenHandle dupToken;
bool bRet = NativeMethods.DuplicateToken(token, NativeMethods.SecurityImpersonation, out dupToken);
if(!bRet)
ThrowWin32Exception("DuplicateToken() failed: ");
return dupToken;
}
/// <summary>
/// Wraps call to <c>AccessCheck</c>.
/// </summary>
/// <param name="desiredAccess">[in] Access to check.</param>
/// <param name="descriptor">[in] Security descriptor to read.</param>
/// <returns><c>true</c> if all desired accesses are present.</returns>
static bool HasAccess(FileAccess desiredAccess, byte[] descriptor)
{
//Build access flags and GENERIC_MAPPING
uint accessFlags = 0x00000000u;
if((desiredAccess & FileAccess.Read)!=0) { accessFlags |= NativeMethods.FILE_GENERIC_READ; }
if((desiredAccess & FileAccess.Write)!=0) { accessFlags |= NativeMethods.FILE_GENERIC_WRITE; }
NativeMethods.GENERIC_MAPPING mapping = new NativeMethods.GENERIC_MAPPING();
mapping.genericAll = NativeMethods.FILE_ALL_ACCESS;
mapping.genericExecute = NativeMethods.FILE_GENERIC_EXECUTE;
mapping.genericRead = NativeMethods.FILE_GENERIC_READ;
mapping.genericWrite = NativeMethods.FILE_GENERIC_WRITE;
bool accessStatus;
//For some reason, AccessCheck() only works when impersonating.
using(SafeTokenHandle dupToken = GetImpersonationToken())
using(System.Security.Principal.WindowsImpersonationContext ctxt = System.Security.Principal.WindowsIdentity.Impersonate(dupToken.DangerousGetHandle()))
{
uint privilegeSetLength = 0;
uint grantedAccess;
bool bRet = NativeMethods.AccessCheck(descriptor, dupToken.DangerousGetHandle(), unchecked((int)accessFlags), mapping, null, ref privilegeSetLength, out grantedAccess, out accessStatus);
if(!bRet)
{
if(Marshal.GetLastWin32Error() != NativeMethods.ERROR_INSUFFICIENT_BUFFER)
ThrowWin32Exception("First call to AccessCheck() failed: ");
byte[] privilegeSet = new byte[privilegeSetLength];
bRet = NativeMethods.AccessCheck(descriptor, dupToken.DangerousGetHandle(), unchecked((int)accessFlags), mapping, privilegeSet, ref privilegeSetLength, out grantedAccess, out accessStatus);
if(!bRet)
ThrowWin32Exception("Second call to AccessCheck() failed: ");
}
}
return accessStatus;
}
/// <summary>
/// Checks access to a file or directory.
/// </summary>
/// <param name="filePath">[in] Path to the file or directory to check.</param>
/// <param name="desiredAccess">[in] Access to check.</param>
/// <param name="throwIfNotFound">[in] If set, will throw a <c>FileNotFoundException</c> if both <c>File.Exists</c> and <c>Directory.Exists</c> fail.</param>
/// <returns><c>true</c> if the file was found and access was granted.</returns>
public static bool CheckAccess(string filePath, FileAccess desiredAccess, bool throwIfNotFound)
{
bool fileExists = File.Exists(filePath);
bool dirExists = Directory.Exists(filePath);
bool exists = fileExists || dirExists;
if(!exists)
{
if(throwIfNotFound)
throw new FileNotFoundException(string.Format("The file or directory '{0}' could not be found or has no read access.", filePath), filePath);
return false;
}
GC.KeepAlive(fileExists);
GC.KeepAlive(dirExists);
byte[] securityDescriptor = GetSecurityDescriptor(filePath);
return HasAccess(desiredAccess, securityDescriptor);
}
/// <summary>
/// Performs a few checks to see if a file can be created:
/// The directory must exist, the file must be writable.
/// </summary>
/// <param name="filePath">(in] File path to check.</param>
/// <returns><c>true</c> if the file can be created/written to.</returns>
public static bool CanWriteFile(string filePath)
{
try
{
if(File.Exists(filePath))
{
//File exists: Check that it can be written to
//(no need to check for directories, because File.Exists would have returned false)
bool isReadOnly = (File.GetAttributes(filePath) & FileAttributes.ReadOnly) != 0; //If this throws we don't have ReadAttributes access; just assume we don't have Write either.
bool hasWriteAccess = FileAccessCheck.CheckAccess(filePath, FileAccess.Write, true);
return hasWriteAccess && !isReadOnly;
}
else
{
//File does not exist. Check that the directory supposed to contain it exists,
//and check if we can write to that.
string dirPath = Path.GetDirectoryName(filePath);
if(string.IsNullOrEmpty(dirPath) || !Directory.Exists(dirPath))
return false;
return FileAccessCheck.CheckAccess(dirPath, FileAccess.Write, true);
}
}
catch(Exception) { return false; }
}
}//class
} |
Partager