Mirror

Setting NTFS File/Folder Security (Views: 711)

Problem/Question/Abstract:

I saw an article that helps setting NTFS security on file or folder. There are many problems with different NTFS (NT4, NT2000, XP) especially with inheritance. The code I am posting is too long, but works fine.

Answer:

Some constants and type definitions are copied from JCL, some from Microsoft C++ headers, but unit doesn't need of any additional code to be compiled. Try it on your own risk. There are additional functions that can help: IsNT, IsNT4, IsAdmin, etc. Some constants are not used.

unit NTSecurityU;

interface
uses Windows, AclApi, AccCtrl;

const

SECURITY_NULL_SID_AUTHORITY: _SID_IDENTIFIER_AUTHORITY = (Value: (0, 0, 0, 0, 0,
0));
SECURITY_WORLD_SID_AUTHORITY: _SID_IDENTIFIER_AUTHORITY = (Value: (0, 0, 0, 0, 0,
1));
SECURITY_LOCAL_SID_AUTHORITY: _SID_IDENTIFIER_AUTHORITY = (Value: (0, 0, 0, 0, 0,
2));
SECURITY_CREATOR_SID_AUTHORITY: _SID_IDENTIFIER_AUTHORITY = (Value: (0, 0, 0, 0, 0,
3));
SECURITY_NON_UNIQUE_AUTHORITY: _SID_IDENTIFIER_AUTHORITY = (Value: (0, 0, 0, 0, 0,
4));
SECURITY_NT_AUTHORITY: _SID_IDENTIFIER_AUTHORITY = (Value: (0, 0, 0, 0, 0, 5));

SECURITY_WORLD_RID: CARDINAL = $00000000;
SECURITY_BUILTIN_DOMAIN_RID: CARDINAL = $00000020;
DOMAIN_ALIAS_RID_ADMINS: CARDINAL = $00000220;
DOMAIN_ALIAS_RID_USERS: CARDINAL = $00000221;
DOMAIN_ALIAS_RID_GUESTS: CARDINAL = $00000222;

STANDARD_RIGHTS_ALL: CARDINAL = $001F0000;
ACL_REVISION: CARDINAL = 2; // current revision;

const
ACCESS_MIN_MS_ACE_TYPE = ($0);
{$EXTERNALSYM ACCESS_MIN_MS_ACE_TYPE}
ACCESS_ALLOWED_ACE_TYPE = ($0);
{$EXTERNALSYM ACCESS_ALLOWED_ACE_TYPE}
ACCESS_DENIED_ACE_TYPE = ($1);
{$EXTERNALSYM ACCESS_DENIED_ACE_TYPE}
SYSTEM_AUDIT_ACE_TYPE = ($2);
{$EXTERNALSYM SYSTEM_AUDIT_ACE_TYPE}
SYSTEM_ALARM_ACE_TYPE = ($3);
{$EXTERNALSYM SYSTEM_ALARM_ACE_TYPE}
ACCESS_MAX_MS_V2_ACE_TYPE = ($3);
{$EXTERNALSYM ACCESS_MAX_MS_V2_ACE_TYPE}

ACCESS_ALLOWED_COMPOUND_ACE_TYPE = ($4);
{$EXTERNALSYM ACCESS_ALLOWED_COMPOUND_ACE_TYPE}
ACCESS_MAX_MS_V3_ACE_TYPE = ($4);
{$EXTERNALSYM ACCESS_MAX_MS_V3_ACE_TYPE}

ACCESS_MIN_MS_OBJECT_ACE_TYPE = ($5);
{$EXTERNALSYM ACCESS_MIN_MS_OBJECT_ACE_TYPE}
ACCESS_ALLOWED_OBJECT_ACE_TYPE = ($5);
{$EXTERNALSYM ACCESS_ALLOWED_OBJECT_ACE_TYPE}
ACCESS_DENIED_OBJECT_ACE_TYPE = ($6);
{$EXTERNALSYM ACCESS_DENIED_OBJECT_ACE_TYPE}
SYSTEM_AUDIT_OBJECT_ACE_TYPE = ($7);
{$EXTERNALSYM SYSTEM_AUDIT_OBJECT_ACE_TYPE}
SYSTEM_ALARM_OBJECT_ACE_TYPE = ($8);
{$EXTERNALSYM SYSTEM_ALARM_OBJECT_ACE_TYPE}
ACCESS_MAX_MS_OBJECT_ACE_TYPE = ($8);
{$EXTERNALSYM ACCESS_MAX_MS_OBJECT_ACE_TYPE}

ACCESS_MAX_MS_V4_ACE_TYPE = ($8);
{$EXTERNALSYM ACCESS_MAX_MS_V4_ACE_TYPE}
ACCESS_MAX_MS_ACE_TYPE = ($8);
{$EXTERNALSYM ACCESS_MAX_MS_ACE_TYPE}

ACCESS_ALLOWED_CALLBACK_ACE_TYPE = $9;
{$EXTERNALSYM ACCESS_ALLOWED_CALLBACK_ACE_TYPE}
ACCESS_DENIED_CALLBACK_ACE_TYPE = $A;
{$EXTERNALSYM ACCESS_DENIED_CALLBACK_ACE_TYPE}
ACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPE = $B;
{$EXTERNALSYM ACCESS_ALLOWED_CALLBACK_OBJECT_ACE_TYPE}
ACCESS_DENIED_CALLBACK_OBJECT_ACE_TYPE = $C;
{$EXTERNALSYM ACCESS_DENIED_CALLBACK_OBJECT_ACE_TYPE}
SYSTEM_AUDIT_CALLBACK_ACE_TYPE = $D;
{$EXTERNALSYM SYSTEM_AUDIT_CALLBACK_ACE_TYPE}
SYSTEM_ALARM_CALLBACK_ACE_TYPE = $E;
{$EXTERNALSYM SYSTEM_ALARM_CALLBACK_ACE_TYPE}
SYSTEM_AUDIT_CALLBACK_OBJECT_ACE_TYPE = $F;
{$EXTERNALSYM SYSTEM_AUDIT_CALLBACK_OBJECT_ACE_TYPE}
SYSTEM_ALARM_CALLBACK_OBJECT_ACE_TYPE = $10;
{$EXTERNALSYM SYSTEM_ALARM_CALLBACK_OBJECT_ACE_TYPE}

ACCESS_MAX_MS_V5_ACE_TYPE = $10;
{$EXTERNALSYM ACCESS_MAX_MS_V5_ACE_TYPE}

SUCCESSFUL_ACCESS_ACE_FLAG = ($40);
{$EXTERNALSYM SUCCESSFUL_ACCESS_ACE_FLAG}
FAILED_ACCESS_ACE_FLAG = ($80);
{$EXTERNALSYM FAILED_ACCESS_ACE_FLAG}

type

PACE_HEADER = ^ACE_HEADER;
_ACE_HEADER = record
AceType: Byte;
AceFlags: Byte;
AceSize: Word;
end;

ACE_HEADER = _ACE_HEADER;

TAceHeader = ACE_HEADER;
PAceHeader = PACE_HEADER;

//Access Allowed ACE
PAccessAllowedAce = ^TAccessAllowedAce;
_ACCESS_ALLOWED_ACE = record
Header: ACE_HEADER;
Mask: DWORD;
SidStart: DWORD;
end;
TAccessAllowedAce = _ACCESS_ALLOWED_ACE;

type
//=== ACL (Access Control List)==============================
//Size information
PACL_SIZE_INFORMATION = ^ACL_SIZE_INFORMATION;
_ACL_SIZE_INFORMATION = record
AceCount,
AclBytesInUse,
AclBytesFree: DWORD
end;
ACL_SIZE_INFORMATION = _ACL_SIZE_INFORMATION;
TAclSizeInformation = ACL_SIZE_INFORMATION;
PAclSizeInformation = PACL_SIZE_INFORMATION;

//Revision Information
PACL_REVISION_INFORMATION = ^ACL_REVISION_INFORMATION;
_ACL_REVISION_INFORMATION = record
AclRevision: DWORD
end;
ACL_REVISION_INFORMATION = _ACL_REVISION_INFORMATION;
TAclRevisionInformation = ACL_REVISION_INFORMATION;
PAclRevisionInformation = PACL_REVISION_INFORMATION;

function IsAdmin: Boolean; stdcall; //is logged user is member of admins or
//domain admins
function IsNT: Boolean; stdcall; //is system NT based
function IsNT4: Boolean; stdcall; //is system NT 4
function GetEveryOneSid: Pointer; stdcall; //Security identifier of well known
// group Everyone
function GetAccountSID(anAccountName: string): Pointer; stdcall;

function SetFileObjectAccessRights(aFileObject: string;
aSID: Pointer; anAccess: CARDINAL; isInheritedAccess: BOOLEAN): BOOLEAN; stdcall;

function SetFileObjectAndSubobjectsAccessRights(aFileObject: string;
aSID: Pointer; anAccess: CARDINAL): BOOLEAN; stdcall;

function SetEveryoneRWEDAccessToFileOrFolder(aFileOrFolder: string): BOOLEAN; stdcall;
function SetEveryoneRWEDAccessToFileOrFolderAndSubobjects(aFileOrFolder: string):
BOOLEAN; stdcall;

function VolumeSupportsPersistentACLs(aPath: string): Boolean; stdcall;

implementation
uses
SysUtils, ComObj;

function VolumeSupportsPersistentACLs(aPath: string): Boolean;
var
maxClen,
driveFlags: Cardinal;
i: Integer;
VolName, FSysName: array[0..MAX_PATH] of Char;
begin
aPath := ExtractFileDrive(aPath) + '\';
Result := FALSE;
if GetVolumeInformation(
PChar(aPath),
VolName,
SizeOf(VolName),
nil,
maxClen,
driveFlags,
FsysName,
SizeOf(FsysName)
) then
Result := driveFlags and FS_PERSISTENT_ACLS = FS_PERSISTENT_ACLS;
end;

function CheckCARDINALRslt(aCardinal: DWORD): Boolean;
begin
Result := aCardinal = ERROR_SUCCESS;
if not Result then
SetLastError(aCardinal);
end;

function IsAdmin: Boolean;
var
ntauth: SID_IDENTIFIER_AUTHORITY;
psidAdmin: Pointer;
bIsAdmin: Boolean;
htok: THandle;
cb: DWORD;
ptg: ^TOKEN_GROUPS;
i: Integer;
grp: PSIDAndAttributes;
begin
Result := FALSE;
if not IsNT then
Result := TRUE
else
begin
bIsAdmin := FALSE;
ntauth := SECURITY_NT_AUTHORITY;
psidAdmin := nil;
AllocateAndInitializeSid(
ntauth, 2,
SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS,
0, 0, 0, 0, 0, 0, psidAdmin
);

htok := 0;
OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, htok);
GetTokenInformation(htok, TokenGroups, nil, 0, cb);
GetMem(ptg, cb);
GetTokenInformation(htok, TokenGroups, ptg, cb, cb);

grp := @(ptg.Groups[0]);

for i := 0 to ptg.GroupCount - 1 do
begin
if EqualSid(psidAdmin, grp.Sid) then
begin
bIsAdmin := TRUE;
Break;
end;
Inc(grp); //, SizeOf( TSIDAndAttributes));
end;
freemem(ptg);
CloseHandle(htok);
FreeSid(psidAdmin);
Result := bIsAdmin;
end; // else of : if not IsNT
end;

function IsNT: Boolean;
var
ovi: TOSVersionInfo;
begin
FillChar(ovi, SizeOf(Ovi), 0);
ovi.dwOSVersionInfoSize := SizeOf(Ovi);
GetVersionEx(ovi);
Result := ovi.dwPlatformId = VER_PLATFORM_WIN32_NT;
end;

function IsNT4: Boolean;
var
ovi: TOSVersionInfo;
begin
FillChar(ovi, SizeOf(Ovi), 0);
ovi.dwOSVersionInfoSize := SizeOf(Ovi);
GetVersionEx(ovi);
Result := (ovi.dwPlatformId = VER_PLATFORM_WIN32_NT) and (ovi.dwMajorVersion = 4);
end;

function GetEveryOneSid: Pointer;
begin
AllocateAndInitializeSid(
SECURITY_WORLD_SID_AUTHORITY,
1,
SECURITY_WORLD_RID,
0,
0, 0, 0, 0, 0, 0,
Result
);
end;

function GetAccountSID(anAccountName: string): Pointer;
var
cb: CARDINAL;
refDomainName: array[0..1024] of Char;
cbRefDomainName: Cardinal;
peUse: Cardinal;
SD: Pointer;
begin
SD := nil;
try
cbRefDomainName := SizeOf(refDomainName);
FillChar(refDomainName, cbRefDomainName, 0);
cb := 0;
LookupAccountName(nil, PChar(anAccountName), nil, cb, refDomainName,
cbRefDomainName, peUse);
if cb > 0 then
begin
GetMem(SD, cb);
FillChar(SD^, cb, 0);
if not LookupAccountName(nil, PChar(anAccountName), SD, cb, refDomainName,
cbRefDomainName, peUse) then
begin
FreeMem(SD, cb);
SD := nil;
end;
end
else
begin
SD := nil;
end;
finally
Result := SD;
end;
end;

function SetFileObjectAndSubobjectsAccessRights(aFileObject: string;
aSID: Pointer; anAccess: CARDINAL): BOOLEAN;
function RecursiveSet(aPath: string): Boolean;
var
F: TSearchRec;
i: Integer;
begin
Result := SetFileObjectAccessRights(aPath, aSID, anAccess, TRUE);
i := FindFirst(aPath + '\*.*', faAnyFile, F);
try
while i = 0 do
begin
if (F.Name <> '') and (F.Name[1] <> '.') then
begin
if F.Attr and faDirectory = faDirectory then
Result := Result and RecursiveSet(aPath + '\' + F.Name)
else
Result := Result and SetFileObjectAccessRights(aPath + '\' + F.Name, aSID,
anAccess, TRUE);
if not Result then
Exit;
end;
i := FindNext(F);
end;
finally
FindClose(F);
end;
end;

begin
Result := FALSE;
aFileObject := TRIM(aFileObject);
if aFileObject <> '' then
begin
if DirectoryExists(aFileObject) then
begin
if aFileObject[Length(aFileObject)] = '\' then
Delete(aFileObject, Length(aFileObject), 1);
Result := RecursiveSet(aFileObject);
Result := Result and SetFileObjectAccessRights(aFileObject, aSID, anAccess,
FALSE);
end
else
Result := SetFileObjectAccessRights(aFileObject, aSID, anAccess, FALSE);
end;
end;

function SetEveryoneRWEDAccessToFileOrFolder(aFileOrFolder: string): BOOLEAN;
var
SID: Pointer;
begin
Result := FALSE;
AllocateAndInitializeSid(
SECURITY_WORLD_SID_AUTHORITY,
1,
SECURITY_WORLD_RID,
0,
0, 0, 0, 0, 0, 0,
SID
);
if IsValidSid(SID) then
try
Result := SetFileObjectAccessRights(aFileOrFolder,
SID,
GENERIC_READ + GENERIC_WRITE + GENERIC_EXECUTE + _DELETE,
FALSE
);
finally
FreeSid(SID);
end;
end;

function SetEveryoneRWEDAccessToFileOrFolderAndSubobjects(aFileOrFolder: string):
BOOLEAN;
var
SID: Pointer;
begin
SID := GetEveryOneSid;
try
Result := SetFileObjectAndSubobjectsAccessRights(aFileOrFolder,
SID,
GENERIC_READ + GENERIC_WRITE + GENERIC_EXECUTE + _DELETE
);
finally
FreeSid(SID);
end;
end;

function SetFileObjectAccessRights(aFileObject: string;
aSID: Pointer; anAccess: CARDINAL; isInheritedAccess: BOOLEAN): BOOLEAN;
var
PPACL, PPACL2: PACL;
newDacl: PACL;
SecDescPtr, SD2: PSECURITY_DESCRIPTOR;
needed: Cardinal;
SD_Control: WORD;
SD_Revision: Cardinal;
aTrustee: TRUSTEE;
expAccess: PExplicit_Access;
isFile: Boolean;
CurACEBr, CurACEInd: CARDINAL;
OldAclSI: TAclSizeInformation;
OldAclRI: TAclRevisionInformation;
anACE: PAccessAllowedAce;
i: Integer;
oldACLSize, newACLSize, newACESize: Cardinal;
bPresent, bDefaulted: LongBool;
begin
Result := false;
if not IsValidSid(aSID) then
Exit;

isFile := FileExists(aFileObject);

PPACL := nil;
if not IsNT4 then
begin
if not CheckCardinalRslt(
GetNamedSecurityInfo(PChar(aFileObject), SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION, nil, nil, PACL(@PPACL), nil, SecDescPtr)
) then
Exit;
end
else
begin
GetFileSecurity(PChar(aFileObject), DACL_SECURITY_INFORMATION, nil, 0, needed);
GetMem(SecDescPtr, needed);
FillChar(SecDescPtr^, needed, 0);
if not GetFileSecurity(PChar(aFileObject), DACL_SECURITY_INFORMATION, SecDescPtr,
needed, needed) then
Exit;
if not GetSecurityDescriptorDacl(SecDescPtr, bPresent, PPACL, bDefaulted) then
Exit;
end;
try
if not Assigned(PPACL) then
Exit;
if not GetSecurityDescriptorControl(SecDescPtr, SD_Control, SD_Revision) then
Exit;
if SD_Control and SE_DACL_PRESENT <> SE_DACL_PRESENT then
Exit;

if not GetAclInformation(PPACL^, @oldAclSI, SizeOF(TAclSizeInformation),
AclSizeInformation) then
Exit;
if not GetAclInformation(PPACL^, @oldAclRI, SizeOf(TAclRevisionInformation),
AclRevisionInformation) then
Exit;

//Delete previous ACE, for a given aSID
CurACEBr := oldAclSI.AceCount;
for i := oldAclSI.AceCount - 1 downto 0 do
begin
if GetAce(PPACL^, i, Pointer(anAce)) then
begin
if EqualSID(@(anACE.SidStart), aSID) then
begin
DeleteAce(PPACL^, i);
CurACEBr := CurACEBr - 1;
end;
end;
end;

if not GetAclInformation(PPACL^, @oldAclSI, SizeOF(TAclSizeInformation),
AclSizeInformation) then
Exit;
if not GetAclInformation(PPACL^, @oldAclRI, SizeOf(TAclRevisionInformation),
AclRevisionInformation) then
Exit;

NewACESize := SizeOf(TAccessAllowedACE) + GetLengthSid(aSID) - SizeOf(DWORD);
OldACLSize := oldAclSI.AclBytesInUse + oldAclSI.AclBytesFree;
NewACLSize := oldAclSI.AclBytesInUse + NewAceSize * 2 - oldAclSI.AclBytesFree;

if NewAclSize < OldAclSize then
NewAclSize := OldAclSize;

GetMem(PPACL2, NewACLSize);
try
FillChar(PPACL2^, NewACLSize, 0);

Move(PPACL^, PPACL2^, oldACLSize);
PPACL2.AclSize := newACLSize;

if not GetAclInformation(PPACL2^, @oldAclSI, SizeOF(TAclSizeInformation),
AclSizeInformation) then
Exit;

CurACEInd := 0;

if not IsNT4 then
begin
//Construct Our Ace
GetMem(anACE, newACESize);
try
FillChar(anACE^, newACESize, 0);
anACE.Header.AceType := ACCESS_ALLOWED_ACE_TYPE;

if not isFile then //demek e folder
begin
anACE.Header.AceFlags := SUB_CONTAINERS_ONLY_INHERIT +
SUB_OBJECTS_ONLY_INHERIT;
end;
if isInheritedAccess then
begin
if not IsNt4 then
anACE.Header.AceFlags := anACE.Header.AceFlags + INHERITED_ACCESS_ENTRY;
end;

anACE.Header.AceSize := newACESize;
anAce.Mask := anAccess;
Move(aSID^, anAce.SidStart, GetLengthSid(aSID));

if not AddAce(PPACL2^, OldAclRI.AclRevision, CurACEInd, anACE, newACESize)
then
Exit;
finally
FreeMem(anACE, newACESize);
end;
end
else
begin
CurACEInd := 0;
if not isFile then
begin
GetMem(anACE, newACESize);
try
FillChar(anACE^, newACESize, 0);
anACE.Header.AceType := ACCESS_ALLOWED_ACE_TYPE;

anACE.Header.AceFlags := SUB_CONTAINERS_ONLY_INHERIT +
SUB_OBJECTS_ONLY_INHERIT + INHERIT_ONLY;

anACE.Header.AceSize := newACESize;
anAce.Mask := anAccess;
Move(aSID^, anAce.SidStart, GetLengthSid(aSID));

if not AddAce(PPACL2^, OldAclRI.AclRevision, CurACEInd, anACE, newACESize)
then
Exit;
finally
FreeMem(anACE, newACESize);
end;
end;

//Add ACE for Files
GetMem(anACE, newACESize);
try
FillChar(anACE^, newACESize, 0);
anACE.Header.AceType := ACCESS_ALLOWED_ACE_TYPE;

anACE.Header.AceFlags := 0; // Empty flags, but ACE
anACE.Header.AceSize := newACESize;
anAce.Mask := anAccess;
Move(aSID^, anAce.SidStart, GetLengthSid(aSID));

if not AddAce(PPACL2^, OldAclRI.AclRevision, CurACEInd, anACE, newACESize)
then
Exit;
finally
FreeMem(anACE, newACESize);
end;
end;

if not GetAclInformation(PPACL2^, @oldAclSI, SizeOF(TAclSizeInformation),
AclSizeInformation) then
Exit;

if not IsNT4 then
begin
Result := CheckCARDINALRslt(
SetNamedSecurityInfo(PChar(aFileObject), SE_FILE_OBJECT,
DACL_SECURITY_INFORMATION, nil, nil, PPACL2, nil)
);
end
else
begin
GetMem(SD2, SizeOf(TSecurityDescriptor));
try
if not InitializeSecurityDescriptor(SD2, SECURITY_DESCRIPTOR_REVISION) then
Exit;
if not SetSecurityDescriptorDacl(SD2, bPresent, PPACL2, bDefaulted) then
Exit;
Result := SetFileSecurity(PChar(aFileObject), DACL_SECURITY_INFORMATION,
SD2);
finally
FreeMem(SD2, SizeOf(TSecurityDescriptor));
end;
end;
finally
FreeMem(PPACL2, NewACLSize);
end;
finally
LocalFree(HLOCAL(SecDescPtr));
end;
end;

end.


<< Back to main page