Merge pull request #1814 from Anakonda/windows_permissions

Improved handling of Windows permissions
This commit is contained in:
enkore 2017-03-25 21:43:32 +01:00 committed by GitHub
commit 2d245ec866
4 changed files with 222 additions and 56 deletions

View file

@ -342,7 +342,7 @@ if not on_rtd:
elif sys.platform == 'darwin':
ext_modules.append(Extension('borg.platform.darwin', [platform_darwin_source]))
elif sys.platform == 'win32':
ext_modules.append(Extension('borg.platform.windows', [platform_windows_source]))
ext_modules.append(Extension('borg.platform.windows', [platform_windows_source], define_macros=[('UNICODE', None), ('_UNICODE', None)]))
def parse(root, describe_command=None):

View file

@ -2,7 +2,7 @@
ITEM_KEYS = frozenset(['path', 'source', 'rdev', 'chunks', 'chunks_healthy', 'hardlink_master',
'mode', 'user', 'group', 'uid', 'gid', 'mtime', 'atime', 'ctime',
'xattrs', 'bsdflags', 'acl_nfs4', 'acl_access', 'acl_default', 'acl_extended', 'win_dacl',
'user_sid', ])
'win_sacl', 'user_sid', ])
# this is the set of keys that are always present in items:
REQUIRED_ITEM_KEYS = frozenset(['path', 'mtime', ])

View file

@ -135,6 +135,7 @@ class Item(PropDict):
acl_extended = PropDict._make_property('acl_extended', bytes)
acl_nfs4 = PropDict._make_property('acl_nfs4', bytes)
win_dacl = PropDict._make_property('win_dacl', str, 'surrogate-escaped str', encode=safe_encode, decode=safe_decode)
win_sacl = PropDict._make_property('win_sacl', str, 'surrogate-escaped str', encode=safe_encode, decode=safe_decode)
mode = PropDict._make_property('mode', int)
uid = PropDict._make_property('uid', int)

View file

@ -37,7 +37,7 @@ cdef extern from 'windows.h':
ctypedef void* HANDLE
struct _ACL:
uint16_t AceCount
cdef enum _SID_NAME_USE:
SidTypeUser,
SidTypeGroup,
@ -59,8 +59,19 @@ cdef extern from 'windows.h':
_LARGE_INTEGER StreamSize
wchar_t[296] cStreamName # MAX_PATH + 36
struct _LUID:
pass
struct _LUID_AND_ATTRIBUTES:
_LUID Luid
DWORD Attributes
struct _TOKEN_PRIVILEGES:
DWORD PrivilegeCount
_LUID_AND_ATTRIBUTES Privileges[1]
HLOCAL LocalFree(HLOCAL)
DWORD GetLastError();
DWORD GetLastError()
void SetLastError(DWORD)
DWORD FormatMessageW(DWORD, void*, DWORD, DWORD, wchar_t*, DWORD, void*)
@ -74,6 +85,15 @@ cdef extern from 'windows.h':
BOOL LookupAccountNameW(LPCTSTR, LPCTSTR, PSID, LPDWORD, LPCTSTR, LPDWORD, _SID_NAME_USE*)
BOOL GetSecurityDescriptorDacl(PSID, BOOL*, _ACL**, BOOL*)
BOOL OpenProcessToken(HANDLE, DWORD, HANDLE*)
BOOL OpenThreadToken(HANDLE, DWORD, BOOL, HANDLE*)
BOOL LookupPrivilegeValueW(wchar_t*, wchar_t*, _LUID*)
BOOL AdjustTokenPrivileges(HANDLE, BOOL, _TOKEN_PRIVILEGES*, DWORD, _TOKEN_PRIVILEGES*, DWORD*)
HANDLE GetCurrentThread()
HANDLE GetCurrentProcess()
cdef extern int ERROR_SUCCESS
cdef extern int ERROR_INSUFFICIENT_BUFFER
cdef extern int ERROR_INVALID_SID
cdef extern int ERROR_NONE_MAPPED
@ -100,6 +120,11 @@ cdef extern from 'windows.h':
cdef extern int INVALID_HANDLE_VALUE
cdef extern DWORD SE_PRIVILEGE_ENABLED
cdef extern int TOKEN_ADJUST_PRIVILEGES
cdef extern int TOKEN_QUERY
cdef extern from 'accctrl.h':
ctypedef enum _SE_OBJECT_TYPE:
SE_FILE_OBJECT
@ -119,6 +144,11 @@ cdef extern from 'accctrl.h':
cdef extern uint16_t TRUSTEE_IS_NAME
cdef extern uint16_t TRUSTEE_BAD_FORM
cdef extern int NO_INHERITANCE
cdef extern int INHERIT_NO_PROPAGATE
cdef extern int INHERIT_ONLY
cdef extern int INHERITED_ACCESS_ENTRY
DWORD GetExplicitEntriesFromAclW(_ACL*, uint32_t*, _EXPLICIT_ACCESS_W**)
@ -140,6 +170,7 @@ cdef extern from 'Aclapi.h':
DWORD GetNamedSecurityInfoW(LPCTSTR, SE_OBJECT_TYPE, DWORD, PSID*, PSID*, PACL*, PACL*, _ACL**)
DWORD SetNamedSecurityInfoW(LPCTSTR, int, int, PSID, PSID, PACL, PACL)
DWORD SetEntriesInAclW(unsigned int, _EXPLICIT_ACCESS_W*, PACL, _ACL**)
DWORD LookupSecurityDescriptorPartsW(_TRUSTEE_W**, _TRUSTEE_W**, uint32_t*, _EXPLICIT_ACCESS_W**, uint32_t*, _EXPLICIT_ACCESS_W**, PSID)
def raise_error(api, path=''):
@ -158,6 +189,52 @@ def raise_error(api, path=''):
raise OSError(error, error_string)
permissions_enabled = False # Have we tried to acquire permissions for SACL
permissions_granted = False # Did we get them
cdef enable_permissions():
global permissions_enabled
global permissions_granted
if permissions_enabled:
return
permissions_enabled = True
cdef HANDLE hToken
OpenProcessToken(GetCurrentProcess() , TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)
cdef _TOKEN_PRIVILEGES tp
cdef _LUID luid
cdef _TOKEN_PRIVILEGES tpPrevious
cdef DWORD cbPrevious=sizeof(_TOKEN_PRIVILEGES)
cdef wchar_t* privilege = PyUnicode_AsWideCharString("SeSecurityPrivilege", NULL)
if not LookupPrivilegeValueW( NULL, privilege, &luid ):
permissions_granted = False
print("Warning: permissions to read auditing settings (SACL) denied. Try running as admin.")
return
tp.PrivilegeCount = 1
tp.Privileges[0].Luid = luid
tp.Privileges[0].Attributes = 0
AdjustTokenPrivileges(hToken, 0, &tp, sizeof(_TOKEN_PRIVILEGES), &tpPrevious, &cbPrevious)
if GetLastError() != ERROR_SUCCESS:
permissions_granted = False
print("Warning: permissions to read auditing settings (SACL) denied. Try running as admin.")
return
tpPrevious.PrivilegeCount = 1
tpPrevious.Privileges[0].Luid = luid
tpPrevious.Privileges[0].Attributes = tpPrevious.Privileges[0].Attributes | SE_PRIVILEGE_ENABLED
AdjustTokenPrivileges(hToken, 0, &tpPrevious, cbPrevious, NULL, NULL)
if GetLastError() != ERROR_SUCCESS:
permissions_granted = False
print("Warning: permissions to read auditing settings (SACL) denied. Try running as admin.")
return
cdef PSID _get_file_security(filename, int request):
cdef DWORD length = 0
# N.B. This query may fail with ERROR_INVALID_FUNCTION
@ -267,93 +344,181 @@ def set_owner(path, owner, sidstring = None):
free(newsid)
def acl_get(path, item, st, numeric_owner=False):
cdef int request = DACL_SECURITY_INFORMATION
def acl_get(path, item, st, numeric_owner=False, depth = 0):
if not permissions_enabled:
enable_permissions()
pyDACL = []
pySACL = []
if not os.path.samefile(os.path.abspath(path), os.path.abspath(os.path.join(path, ".."))):
pyDACL, pySACL = acl_get(os.path.abspath(os.path.join(path, "..")), item, st, numeric_owner, depth + 1)
cdef int request = DACL_SECURITY_INFORMATION
if permissions_granted:
request = DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION
cdef BYTE* SD = _get_file_security(path, request)
if SD == NULL:
return
cdef BOOL daclFound
cdef _ACL* DACL
cdef BOOL DACLDefaulted
GetSecurityDescriptorDacl(SD, &daclFound, &DACL, &DACLDefaulted)
cdef uint32_t dacllength
cdef _EXPLICIT_ACCESS_W* DACL
cdef uint32_t sacllength
cdef _EXPLICIT_ACCESS_W* SACL
cdef uint32_t length
cdef _EXPLICIT_ACCESS_W* ACEs
# LookupSecurityDescriptorPartsW(&owner, &group, &dacllength, &DACL, &sacllength, &sacl, SD)
LookupSecurityDescriptorPartsW(NULL, NULL, &dacllength, &DACL, &sacllength, &SACL, SD)
GetExplicitEntriesFromAclW(DACL, &length, &ACEs)
pyDACL = []
cdef PSID newsid
cdef uint32_t domainlength
cdef uint32_t sidlength
cdef _SID_NAME_USE sid_type
for i in range(length):
# DACL
for i in range(dacllength):
permissions = None
name = ""
sidstr = ""
if ACEs[i].Trustee.TrusteeForm == TRUSTEE_IS_SID:
name, domain, type = _look_up_account_sid(<BYTE*>(ACEs[i].Trustee.ptstrName))
sidstr = sid2string(<PSID>(ACEs[i].Trustee.ptstrName))
if DACL[i].Trustee.TrusteeForm == TRUSTEE_IS_SID:
name, domain, type = _look_up_account_sid(<BYTE*>(DACL[i].Trustee.ptstrName))
sidstr = sid2string(<PSID>(DACL[i].Trustee.ptstrName))
elif ACEs[i].Trustee.TrusteeForm == TRUSTEE_IS_NAME:
elif DACL[i].Trustee.TrusteeForm == TRUSTEE_IS_NAME:
sid_type = SidTypeInvalid
domainlength = 0
LookupAccountNameW(NULL, ACEs[i].Trustee.ptstrName, NULL, &(length), NULL, &domainlength, &sid_type)
LookupAccountNameW(NULL, DACL[i].Trustee.ptstrName, NULL, &sidlength, NULL, &domainlength, &sid_type)
newsid = <PSID>malloc((length) * sizeof(BYTE))
newsid = <PSID>malloc((sidlength) * sizeof(BYTE))
domainlength = 0
LookupAccountNameW(NULL, ACEs[i].Trustee.ptstrName, newsid, &length, NULL, &domainlength, &sid_type)
LookupAccountNameW(NULL, DACL[i].Trustee.ptstrName, newsid, &sidlength, NULL, &domainlength, &sid_type)
trusteeName, domain, type = _look_up_account_sid(newsid)
name = trusteeName
sidstr = sid2string(newsid)
free(newsid)
elif ACEs[i].Trustee.TrusteeForm == TRUSTEE_BAD_FORM:
elif DACL[i].Trustee.TrusteeForm == TRUSTEE_BAD_FORM:
continue
permissions = {'user': {'name': name, 'sid': sidstr}, 'permissions': (ACEs[i].grfAccessPermissions, ACEs[i].grfAccessMode, ACEs[i].grfInheritance)}
pyDACL.append(permissions)
item['win_dacl'] = json.dumps(pyDACL)
if ((depth == 0 and DACL[i].grfInheritance & INHERIT_ONLY != 0)
or (DACL[i].grfInheritance & INHERIT_NO_PROPAGATE and depth == 1)
or (DACL[i].grfInheritance != NO_INHERITANCE and DACL[i].grfInheritance & INHERIT_NO_PROPAGATE == 0)):
permissions = {'user': {'name': name, 'sid': sidstr}, 'permissions': (DACL[i].grfAccessPermissions, DACL[i].grfAccessMode, NO_INHERITANCE)}
pyDACL.append(permissions)
if permissions_granted:
for i in range(sacllength):
permissions = None
name = ""
sidstr = ""
if DACL[i].Trustee.TrusteeForm == TRUSTEE_IS_SID:
name, domain, type = _look_up_account_sid(<BYTE*>(SACL[i].Trustee.ptstrName))
sidstr = sid2string(<PSID>(SACL[i].Trustee.ptstrName))
elif SACL[i].Trustee.TrusteeForm == TRUSTEE_IS_NAME:
sid_type = SidTypeInvalid
domainlength = 0
LookupAccountNameW(NULL, SACL[i].Trustee.ptstrName, NULL, &sidlength, NULL, &domainlength, &sid_type)
newsid = <PSID>malloc((sidlength) * sizeof(BYTE))
domainlength = 0
LookupAccountNameW(NULL, SACL[i].Trustee.ptstrName, newsid, &sidlength, NULL, &domainlength, &sid_type)
trusteeName, domain, type = _look_up_account_sid(newsid)
name = trusteeName
sidstr = sid2string(newsid)
free(newsid)
else:
continue
if ((depth == 0 and SACL[i].grfInheritance & INHERIT_ONLY != 0)
or (SACL[i].grfInheritance & INHERIT_NO_PROPAGATE and depth == 1)
or (SACL[i].grfInheritance != NO_INHERITANCE and SACL[i].grfInheritance & INHERIT_NO_PROPAGATE == 0)):
permissions = {'user': {'name': name, 'sid': sidstr}, 'permissions': (SACL[i].grfAccessPermissions, SACL[i].grfAccessMode, NO_INHERITANCE)}
pySACL.append(permissions)
if depth == 0:
item['win_dacl'] = json.dumps(pyDACL)
item['win_sacl'] = json.dumps(pySACL)
free(SD)
LocalFree(<HLOCAL>ACEs)
LocalFree(<HLOCAL>DACL)
LocalFree(<HLOCAL>SACL)
return pyDACL,pySACL
def acl_set(path, item, numeric_owner=False):
if 'win_dacl' not in item:
return
if not permissions_enabled:
enable_permissions()
pyDACL = json.loads(item.win_dacl)
cdef _EXPLICIT_ACCESS_W* ACEs = <_EXPLICIT_ACCESS_W*>calloc(sizeof(_EXPLICIT_ACCESS_W), len(pyDACL))
cdef _EXPLICIT_ACCESS_W* DACL
cdef wchar_t* temp
cdef PSID newsid
for i in range(len(pyDACL)):
if pyDACL[i]['user']['name'] == '' or numeric_owner:
ACEs[i].Trustee.TrusteeForm = TRUSTEE_IS_SID
temp = PyUnicode_AsWideCharString(pyDACL[i]['user']['sid'], NULL)
ConvertStringSidToSidW(temp, &newsid)
ACEs[i].Trustee.ptstrName = <LPCTSTR>newsid
PyMem_Free(temp)
else:
ACEs[i].Trustee.TrusteeForm = TRUSTEE_IS_NAME
ACEs[i].Trustee.ptstrName = PyUnicode_AsWideCharString(pyDACL[i]['user']['name'], NULL)
ACEs[i].grfAccessPermissions = pyDACL[i]['permissions'][0]
ACEs[i].grfAccessMode = pyDACL[i]['permissions'][1]
ACEs[i].grfInheritance = pyDACL[i]['permissions'][2]
cdef _ACL* newDACL
SetEntriesInAclW(len(pyDACL), ACEs, NULL, &newDACL)
cdef wchar_t* cstrPath = PyUnicode_AsWideCharString(path, NULL)
SetNamedSecurityInfoW(cstrPath, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, newDACL, NULL)
for i in range(len(pyDACL)):
if pyDACL[i]['user']['name'] == '' or numeric_owner:
LocalFree(<HLOCAL>ACEs[i].Trustee.ptstrName)
else:
PyMem_Free(ACEs[i].Trustee.ptstrName)
free(ACEs)
PyMem_Free(cstrPath)
LocalFree(<HLOCAL>newDACL)
cdef wchar_t* cstrPath
if 'win_dacl' in item:
pyDACL = json.loads(item.win_dacl)
if len(pyDACL) > 0:
DACL = <_EXPLICIT_ACCESS_W*>calloc(sizeof(_EXPLICIT_ACCESS_W), len(pyDACL))
for i in range(len(pyDACL)):
if pyDACL[i]['user']['name'] == '' or numeric_owner:
DACL[i].Trustee.TrusteeForm = TRUSTEE_IS_SID
temp = PyUnicode_AsWideCharString(pyDACL[i]['user']['sid'], NULL)
ConvertStringSidToSidW(temp, &newsid)
DACL[i].Trustee.ptstrName = <LPCTSTR>newsid
PyMem_Free(temp)
else:
DACL[i].Trustee.TrusteeForm = TRUSTEE_IS_NAME
DACL[i].Trustee.ptstrName = PyUnicode_AsWideCharString(pyDACL[i]['user']['name'], NULL)
DACL[i].grfAccessPermissions = pyDACL[i]['permissions'][0]
DACL[i].grfAccessMode = pyDACL[i]['permissions'][1]
DACL[i].grfInheritance = pyDACL[i]['permissions'][2]
SetEntriesInAclW(len(pyDACL), DACL, NULL, &newDACL)
cstrPath = PyUnicode_AsWideCharString(path, NULL)
SetNamedSecurityInfoW(cstrPath, SE_FILE_OBJECT, PROTECTED_DACL_SECURITY_INFORMATION, NULL, NULL, newDACL, NULL)
for i in range(len(pyDACL)):
if pyDACL[i]['user']['name'] == '' or numeric_owner:
LocalFree(<HLOCAL>DACL[i].Trustee.ptstrName)
else:
PyMem_Free(DACL[i].Trustee.ptstrName)
free(DACL)
PyMem_Free(cstrPath)
LocalFree(<HLOCAL>newDACL)
cdef _EXPLICIT_ACCESS_W* SACL
cdef _ACL* newSACL
if permissions_granted and 'win_sacl' in item:
pySACL = json.loads(item.win_sacl)
if len(pySACL) > 0:
SACL = <_EXPLICIT_ACCESS_W*>calloc(sizeof(_EXPLICIT_ACCESS_W), len(pySACL))
for i in range(len(pyDACL)):
if pySACL[i]['user']['name'] == '' or numeric_owner:
SACL[i].Trustee.TrusteeForm = TRUSTEE_IS_SID
temp = PyUnicode_AsWideCharString(pySACL[i]['user']['sid'], NULL)
ConvertStringSidToSidW(temp, &newsid)
SACL[i].Trustee.ptstrName = <LPCTSTR>newsid
PyMem_Free(temp)
else:
SACL[i].Trustee.TrusteeForm = TRUSTEE_IS_NAME
SACL[i].Trustee.ptstrName = PyUnicode_AsWideCharString(pySACL[i]['user']['name'], NULL)
SACL[i].grfAccessPermissions = pySACL[i]['permissions'][0]
SACL[i].grfAccessMode = pySACL[i]['permissions'][1]
SACL[i].grfInheritance = pySACL[i]['permissions'][2]
SetEntriesInAclW(len(pySACL), SACL, NULL, &newSACL)
cstrPath = PyUnicode_AsWideCharString(path, NULL)
SetNamedSecurityInfoW(cstrPath, SE_FILE_OBJECT, PROTECTED_SACL_SECURITY_INFORMATION, NULL, NULL, newSACL, NULL)
for i in range(len(pySACL)):
if pySACL[i]['user']['name'] == '' or numeric_owner:
LocalFree(<HLOCAL>SACL[i].Trustee.ptstrName)
else:
PyMem_Free(SACL[i].Trustee.ptstrName)
free(SACL)
PyMem_Free(cstrPath)
LocalFree(<HLOCAL>newSACL)
def sync_dir(path):