From 7188a72939d7c271c50995bc7fd99baa40c18ed3 Mon Sep 17 00:00:00 2001 From: Antti Aalto Date: Tue, 10 May 2016 20:05:57 +0300 Subject: [PATCH 1/2] Windows DACLs --- .gitignore | 1 + borg/archive.py | 35 +++-- borg/archiver.py | 8 +- borg/constants.py | 2 +- borg/platform.py | 2 + borg/platform_windows.pyx | 291 ++++++++++++++++++++++++++++++++++++++ setup.py | 11 +- 7 files changed, 337 insertions(+), 13 deletions(-) create mode 100644 borg/platform_windows.pyx diff --git a/.gitignore b/.gitignore index 0e13ddabf..ef9536948 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ crypto.c platform_darwin.c platform_freebsd.c platform_linux.c +platform_windows.c *.egg-info *.pyc *.pyo diff --git a/borg/archive.py b/borg/archive.py index b5a105b1a..feb5f1dfd 100644 --- a/borg/archive.py +++ b/borg/archive.py @@ -25,6 +25,8 @@ from .helpers import Chunk, Error, uid2user, user2uid, gid2group, group2gid, \ CompressionDecider1, CompressionDecider2, CompressionSpec from .repository import Repository from .platform import acl_get, acl_set +if sys.platform == 'win32': + from .platform import get_owner, set_owner from .chunker import Chunker from .hashindex import ChunkIndex, ChunkIndexEntry from .cache import ChunkListEntry @@ -423,6 +425,11 @@ Number of files: {0.stats.nfiles}'''.format( os.lchown(path, uid, gid) except OSError: pass + else: + try: + set_owner(path, item[b'user'], safe_decode(item[b'uid'])) + except OSError: + pass if sys.platform != 'win32': if fd: os.fchmod(fd, item[b'mode']) @@ -501,14 +508,26 @@ Number of files: {0.stats.nfiles}'''.format( del self.manifest.archives[self.name] def stat_attrs(self, st, path): - item = { - b'mode': st.st_mode, - b'uid': st.st_uid, b'user': uid2user(st.st_uid), - b'gid': st.st_gid, b'group': gid2group(st.st_gid), - b'atime': int_to_bigint(st.st_atime_ns), - b'ctime': int_to_bigint(st.st_ctime_ns), - b'mtime': int_to_bigint(st.st_mtime_ns), - } + item = {} + if sys.platform == 'win32': + owner = get_owner(path) + item = { + b'mode': st.st_mode, + b'uid': owner[1], b'user': owner[0], + b'gid': st.st_gid, b'group': gid2group(st.st_gid), + b'atime': int_to_bigint(st.st_atime_ns), + b'ctime': int_to_bigint(st.st_ctime_ns), + b'mtime': int_to_bigint(st.st_mtime_ns), + } + else: + item = { + b'mode': st.st_mode, + b'uid': st.st_uid, b'user': uid2user(st.st_uid), + b'gid': st.st_gid, b'group': gid2group(st.st_gid), + b'atime': int_to_bigint(st.st_atime_ns), + b'ctime': int_to_bigint(st.st_ctime_ns), + b'mtime': int_to_bigint(st.st_mtime_ns), + } if self.numeric_owner: item[b'user'] = item[b'group'] = None xattrs = xattr.get_all(path, follow_symlinks=False) diff --git a/borg/archiver.py b/borg/archiver.py index 7c378567e..4ff3e3547 100644 --- a/borg/archiver.py +++ b/borg/archiver.py @@ -735,7 +735,10 @@ class Archiver: elif args.short: format = "{path}{NL}" else: - format = "{mode} {user:6} {group:6} {size:8} {isomtime} {path}{extra}{NL}" + if sys.platform == 'win32': + format = "{user:15} {size:8} {isomtime} {path}{extra}{NL}" + else: + format = "{mode} {user:6} {group:6} {size:8} {isomtime} {path}{extra}{NL}" formatter = ItemFormatter(archive, format) if not hasattr(sys.stdout, 'buffer'): @@ -2009,7 +2012,8 @@ class Archiver: def prerun_checks(self, logger): check_extension_modules() - selftest(logger) + if sys.platform != 'win32': + selftest(logger) def run(self, args): os.umask(args.umask) # early, before opening files diff --git a/borg/constants.py b/borg/constants.py index 95b16c47a..105b447de 100644 --- a/borg/constants.py +++ b/borg/constants.py @@ -1,7 +1,7 @@ # this set must be kept complete, otherwise the RobustUnpacker might malfunction: ITEM_KEYS = set([b'path', b'source', b'rdev', b'chunks', b'hardlink_master', b'mode', b'user', b'group', b'uid', b'gid', b'mtime', b'atime', b'ctime', - b'xattrs', b'bsdflags', b'acl_nfs4', b'acl_access', b'acl_default', b'acl_extended', ]) + b'xattrs', b'bsdflags', b'acl_nfs4', b'acl_access', b'acl_default', b'acl_extended', b'win_dacl']) ARCHIVE_TEXT_KEYS = (b'name', b'comment', b'hostname', b'username', b'time', b'time_end') ITEM_TEXT_KEYS = (b'path', b'source', b'user', b'group') diff --git a/borg/platform.py b/borg/platform.py index 1bc8ee5e4..cb35121c0 100644 --- a/borg/platform.py +++ b/borg/platform.py @@ -6,6 +6,8 @@ elif sys.platform.startswith('freebsd'): # pragma: freebsd only from .platform_freebsd import acl_get, acl_set, API_VERSION elif sys.platform == 'darwin': # pragma: darwin only from .platform_darwin import acl_get, acl_set, API_VERSION +elif sys.platform == 'win32': # pragma: windows only + from .platform_windows import acl_get, acl_set, API_VERSION, get_owner, set_owner else: # pragma: unknown platform only API_VERSION = 2 diff --git a/borg/platform_windows.pyx b/borg/platform_windows.pyx new file mode 100644 index 000000000..d6525970a --- /dev/null +++ b/borg/platform_windows.pyx @@ -0,0 +1,291 @@ +import json +from libc.stddef cimport wchar_t +from libc.stdint cimport uint16_t, uint32_t, uint64_t +cimport cpython.array +import array + +import platform +from .helpers import safe_decode, safe_encode + +API_VERSION = 2 + +cdef extern from 'stdlib.h': + void free(void* ptr) + void* malloc(size_t) + void* calloc(size_t, size_t) + +cdef extern from 'Python.h': + wchar_t* PyUnicode_AsWideCharString(object, Py_ssize_t *) + object PyUnicode_FromWideChar(const wchar_t*, Py_ssize_t) + void* PyMem_Malloc(int) + void PyMem_Free(void*) + +cdef extern from 'windows.h': + ctypedef int HLOCAL + ctypedef wchar_t* LPCTSTR + ctypedef char BYTE + ctypedef int HLOCAL + ctypedef uint32_t DWORD + ctypedef DWORD* LPDWORD + ctypedef int BOOL + ctypedef BYTE* PSID + struct _ACL: + uint16_t AceCount + + HLOCAL LocalFree(HLOCAL) + DWORD GetLastError(); + void SetLastError(DWORD) + + BOOL InitializeSecurityDescriptor(BYTE*, DWORD) + + BOOL LookupAccountNameW(LPCTSTR, LPCTSTR, PSID, LPDWORD, LPCTSTR, LPDWORD, LPDWORD) + BOOL GetSecurityDescriptorDacl(PSID, BOOL*, _ACL**, BOOL*) + + cdef extern int ERROR_INSUFFICIENT_BUFFER + cdef extern int ERROR_INVALID_SID + + cdef extern int OWNER_SECURITY_INFORMATION + cdef extern int GROUP_SECURITY_INFORMATION + cdef extern int DACL_SECURITY_INFORMATION + cdef extern int SACL_SECURITY_INFORMATION + cdef extern int LABEL_SECURITY_INFORMATION + cdef extern int ATTRIBUTE_SECURITY_INFORMATION + cdef extern int SCOPE_SECURITY_INFORMATION + cdef extern int BACKUP_SECURITY_INFORMATION + cdef extern int UNPROTECTED_SACL_SECURITY_INFORMATION + cdef extern int UNPROTECTED_DACL_SECURITY_INFORMATION + cdef extern int PROTECTED_SACL_SECURITY_INFORMATION + cdef extern int PROTECTED_DACL_SECURITY_INFORMATION + + cdef extern int SECURITY_DESCRIPTOR_MIN_LENGTH + +cdef extern from 'accctrl.h': + ctypedef enum _SE_OBJECT_TYPE: + SE_FILE_OBJECT + ctypedef _SE_OBJECT_TYPE SE_OBJECT_TYPE + struct _TRUSTEE_W: + uint16_t TrusteeForm + uint16_t TrusteeType + LPCTSTR ptstrName + + struct _EXPLICIT_ACCESS_W: + DWORD grfAccessPermissions + uint16_t grfAccessMode + DWORD grfInheritance + _TRUSTEE_W Trustee + + cdef extern uint16_t TRUSTEE_IS_SID + cdef extern uint16_t TRUSTEE_IS_NAME + cdef extern uint16_t TRUSTEE_BAD_FORM + + DWORD GetExplicitEntriesFromAclW(_ACL*, uint32_t*, _EXPLICIT_ACCESS_W**) + +cdef extern from 'Sddl.h': + ctypedef int* LPBOOL + + BOOL GetFileSecurityW(LPCTSTR, int, PSID, DWORD, LPDWORD) + BOOL GetSecurityDescriptorOwner(PSID, PSID*, LPBOOL) + BOOL LookupAccountSidW(LPCTSTR, PSID, LPCTSTR, LPDWORD, LPCTSTR, LPDWORD, uint16_t*) + BOOL ConvertSidToStringSidW(PSID, LPCTSTR*) + BOOL ConvertStringSidToSidW(LPCTSTR, PSID*) + BOOL ConvertSecurityDescriptorToStringSecurityDescriptorW(BYTE*, DWORD, int, LPCTSTR*, int*) + + cdef extern int SDDL_REVISION_1 + +cdef extern from 'Aclapi.h': + ctypedef void* PACL + 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**) + +cdef PSID _get_file_security(filename, int request): + cdef DWORD length = 0 + # N.B. This query may fail with ERROR_INVALID_FUNCTION + # for some filesystems. + cdef wchar_t* wcharfilename = PyUnicode_AsWideCharString(filename, NULL) + if wcharfilename == NULL: + print 'filename is NULL' + GetFileSecurityW(wcharfilename, request, NULL, 0, &length) + if GetLastError() == ERROR_INSUFFICIENT_BUFFER: + SetLastError(0) + else: + print '_get_file_security failed. Windows error:', GetLastError() + return NULL + cdef BYTE* sd = malloc((length) * sizeof(BYTE)) + GetFileSecurityW(wcharfilename, request, sd, length, &length) + PyMem_Free(wcharfilename) + return sd + +cdef PSID _get_security_descriptor_owner(PSID sd): + cdef PSID sid + cdef BOOL sid_defaulted + GetSecurityDescriptorOwner(sd, &sid, &sid_defaulted) + return (sid) + +cdef _look_up_account_sid(PSID sid): + cdef int SIZE = 256 + cdef wchar_t* name = malloc((SIZE) * sizeof(wchar_t)) + cdef wchar_t* domain = malloc((SIZE) * sizeof(wchar_t)) + cdef DWORD cch_name = SIZE + cdef DWORD cch_domain = SIZE + cdef uint16_t sid_type = 0 + + cdef BOOL ret = LookupAccountSidW(NULL, sid, name, &cch_name, domain, &cch_domain, &sid_type) + if ret == 0: + lasterror = GetLastError() + if lasterror == 1332: + # Unknown (removed?) user or file from another windows installation + free(name) + free(domain) + return 'unknown', 'unknown', 0 + else: + print 'Windows error:', lasterror + + pystrName = PyUnicode_FromWideChar(name, -1) + pystrDomain = PyUnicode_FromWideChar(domain, -1) + + free(name) + free(domain) + return pystrName, pystrDomain, sid_type + +cdef sid2string(PSID sid): + cdef wchar_t* sidstr + ConvertSidToStringSidW(sid, &sidstr) + ret = PyUnicode_FromWideChar(sidstr, -1) + LocalFree(sidstr) + return ret + +def get_owner(path): + cdef int request = OWNER_SECURITY_INFORMATION + cdef BYTE* sd = _get_file_security(path, request) + if sd == NULL: + return 'unknown', 'S-1-0-0' + cdef PSID sid = _get_security_descriptor_owner(sd) + if sid == NULL: + print 'sid is null' + return 'unknown', 'S-1-0-0' + name, domain, sid_type = _look_up_account_sid(sid) + free(sd) + if domain and domain.lower() != platform.node().lower() and domain != 'BUILTIN': + return '{0}\\{1}'.format(domain, name), sid2string(sid) + else: + return name, sid2string(sid) + +def set_owner(path, owner, sidstring = None): + cdef PSID newsid + cdef wchar_t* temp + cdef DWORD sid_type = 0 + cdef DWORD length = 0 + cdef DWORD domainlength = 0 + if sidstring is not None: + temp = PyUnicode_AsWideCharString(sidstring, NULL) + ConvertStringSidToSidW(temp, &newsid) + if sidstring is None or GetLastError() == ERROR_INVALID_SID: + temp = PyUnicode_AsWideCharString(owner, NULL) + + LookupAccountNameW(NULL, temp, NULL, &length, NULL, &domainlength, &sid_type) + + newsid = malloc((length) * sizeof(BYTE)) + SetLastError(0) + domainlength = 0 + LookupAccountNameW(NULL, temp, newsid, &length, NULL, &domainlength, &sid_type) + if GetLastError() != 0: + PyMem_Free(temp) + return + + PyMem_Free(temp) + + cdef wchar_t* cstrPath = PyUnicode_AsWideCharString(path, NULL) + SetNamedSecurityInfoW(cstrPath, SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, newsid, NULL, NULL, NULL) + PyMem_Free(cstrPath) + if length == 0: + LocalFree(newsid) + else: + free(newsid) + +def acl_get(path, item, st, numeric_owner=False): + cdef int request = DACL_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 length + cdef _EXPLICIT_ACCESS_W* ACEs + + GetExplicitEntriesFromAclW(DACL, &length, &ACEs) + + pyDACL = [] + cdef PSID newsid + cdef uint32_t domainlength + cdef uint32_t sid_type + for i in range(0, length): + permissions = None + name = "" + sidstr = "" + if ACEs[i].Trustee.TrusteeForm == TRUSTEE_IS_SID: + name, domain, type = _look_up_account_sid((ACEs[i].Trustee.ptstrName)) + sidstr = sid2string((ACEs[i].Trustee.ptstrName)) + + elif ACEs[i].Trustee.TrusteeForm == TRUSTEE_IS_NAME: + sid_type = 0 + domainlength = 0 + LookupAccountNameW(NULL, ACEs[i].Trustee.ptstrName, NULL, &(length), NULL, &domainlength, &sid_type) + + newsid = malloc((length) * sizeof(BYTE)) + domainlength = 0 + LookupAccountNameW(NULL, ACEs[i].Trustee.ptstrName, newsid, &length, 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: + continue + permissions = {'user': {'name': name, 'sid': sidstr}, 'permissions': (ACEs[i].grfAccessPermissions, ACEs[i].grfAccessMode, ACEs[i].grfInheritance)} + pyDACL.append(permissions) + item[b'win_dacl'] = safe_encode(json.dumps(pyDACL)) + + free(SD) + LocalFree(ACEs) + +def acl_set(path, item, numeric_owner=False): + if b'dacl' not in item: + return + + pyDACL = json.loads(safe_decode(item[b'win_dacl'])) + cdef _EXPLICIT_ACCESS_W* ACEs = <_EXPLICIT_ACCESS_W*>calloc(sizeof(_EXPLICIT_ACCESS_W), len(pyDACL)) + cdef wchar_t* temp + cdef PSID newsid + for i in range(0, 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 = 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(0, len(pyDACL)): + if pyDACL[i]['user']['name'] == '' or numeric_owner: + LocalFree(ACEs[i].Trustee.ptstrName) + else: + PyMem_Free(ACEs[i].Trustee.ptstrName) + free(ACEs) + PyMem_Free(cstrPath) + LocalFree(newDACL) diff --git a/setup.py b/setup.py index 5ad58c0a5..c1601c4f6 100644 --- a/setup.py +++ b/setup.py @@ -44,6 +44,7 @@ hashindex_source = 'borg/hashindex.pyx' platform_linux_source = 'borg/platform_linux.pyx' platform_darwin_source = 'borg/platform_darwin.pyx' platform_freebsd_source = 'borg/platform_freebsd.pyx' +platform_windows_source = 'borg/platform_windows.pyx' try: from Cython.Distutils import build_ext @@ -52,7 +53,9 @@ try: class Sdist(sdist): def __init__(self, *args, **kwargs): for src in glob('borg/*.pyx'): - cython_compiler.compile(src, cython_compiler.default_options) + options = cython_compiler.default_options + options['language_level'] = 3 + cython_compiler.compile(src, options) super().__init__(*args, **kwargs) def make_distribution(self): @@ -64,6 +67,7 @@ try: 'borg/platform_linux.c', 'borg/platform_freebsd.c', 'borg/platform_darwin.c', + 'borg/platform_windows.c', ]) super().make_distribution() @@ -79,10 +83,11 @@ except ImportError: platform_linux_source = platform_linux_source.replace('.pyx', '.c') platform_freebsd_source = platform_freebsd_source.replace('.pyx', '.c') platform_darwin_source = platform_darwin_source.replace('.pyx', '.c') + platform_windows_source = platform_windows_source.replace('.pyx', '.c') from distutils.command.build_ext import build_ext if not on_rtd and not all(os.path.exists(path) for path in [ compress_source, crypto_source, chunker_source, hashindex_source, - platform_linux_source, platform_freebsd_source]): + platform_linux_source, platform_freebsd_source, platform_windows_source]): raise ImportError('The GIT version of Borg needs Cython. Install Cython or use a released version.') @@ -312,6 +317,8 @@ if not on_rtd: ext_modules.append(Extension('borg.platform_freebsd', [platform_freebsd_source])) 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])) def parse(root, describe_command=None): From 15279b00da38bae634714464ca8b8d031f0e7934 Mon Sep 17 00:00:00 2001 From: Antti Aalto Date: Thu, 19 May 2016 13:38:04 +0300 Subject: [PATCH 2/2] platform_windows.pyx error handling. --- borg/platform_windows.pyx | 58 ++++++++++++++++++++++++++++++++------- setup.py | 1 - 2 files changed, 48 insertions(+), 11 deletions(-) diff --git a/borg/platform_windows.pyx b/borg/platform_windows.pyx index d6525970a..0d367667c 100644 --- a/borg/platform_windows.pyx +++ b/borg/platform_windows.pyx @@ -1,3 +1,5 @@ +#cython: language_level=3 + import json from libc.stddef cimport wchar_t from libc.stdint cimport uint16_t, uint32_t, uint64_t @@ -9,17 +11,20 @@ from .helpers import safe_decode, safe_encode API_VERSION = 2 + cdef extern from 'stdlib.h': void free(void* ptr) void* malloc(size_t) void* calloc(size_t, size_t) + cdef extern from 'Python.h': wchar_t* PyUnicode_AsWideCharString(object, Py_ssize_t *) object PyUnicode_FromWideChar(const wchar_t*, Py_ssize_t) void* PyMem_Malloc(int) void PyMem_Free(void*) + cdef extern from 'windows.h': ctypedef int HLOCAL ctypedef wchar_t* LPCTSTR @@ -36,6 +41,9 @@ cdef extern from 'windows.h': DWORD GetLastError(); void SetLastError(DWORD) + DWORD FormatMessageW(DWORD, void*, DWORD, DWORD, wchar_t**, DWORD, void*) + + BOOL InitializeSecurityDescriptor(BYTE*, DWORD) BOOL LookupAccountNameW(LPCTSTR, LPCTSTR, PSID, LPDWORD, LPCTSTR, LPDWORD, LPDWORD) @@ -43,6 +51,7 @@ cdef extern from 'windows.h': cdef extern int ERROR_INSUFFICIENT_BUFFER cdef extern int ERROR_INVALID_SID + cdef extern int ERROR_NONE_MAPPED cdef extern int OWNER_SECURITY_INFORMATION cdef extern int GROUP_SECURITY_INFORMATION @@ -59,6 +68,11 @@ cdef extern from 'windows.h': cdef extern int SECURITY_DESCRIPTOR_MIN_LENGTH + cdef extern int FORMAT_MESSAGE_ALLOCATE_BUFFER + cdef extern int FORMAT_MESSAGE_FROM_SYSTEM + cdef extern int FORMAT_MESSAGE_IGNORE_INSERTS + + cdef extern from 'accctrl.h': ctypedef enum _SE_OBJECT_TYPE: SE_FILE_OBJECT @@ -80,6 +94,7 @@ cdef extern from 'accctrl.h': DWORD GetExplicitEntriesFromAclW(_ACL*, uint32_t*, _EXPLICIT_ACCESS_W**) + cdef extern from 'Sddl.h': ctypedef int* LPBOOL @@ -92,36 +107,54 @@ cdef extern from 'Sddl.h': cdef extern int SDDL_REVISION_1 + cdef extern from 'Aclapi.h': ctypedef void* PACL 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**) + +def raise_error(api, path=''): + cdef wchar_t *error_message + error = GetLastError() + if not error: + return + FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, error, 0, &error_message, 0, NULL) + error_string = PyUnicode_FromWideChar(error_message, -1) + LocalFree(error_message) + error_string = api + ': ' + error_string + if path: + raise OSError(error, error_string, path) + else: + raise OSError(error, error_string) + + cdef PSID _get_file_security(filename, int request): cdef DWORD length = 0 # N.B. This query may fail with ERROR_INVALID_FUNCTION # for some filesystems. cdef wchar_t* wcharfilename = PyUnicode_AsWideCharString(filename, NULL) - if wcharfilename == NULL: - print 'filename is NULL' GetFileSecurityW(wcharfilename, request, NULL, 0, &length) if GetLastError() == ERROR_INSUFFICIENT_BUFFER: SetLastError(0) else: - print '_get_file_security failed. Windows error:', GetLastError() + raise_error('GetFileSecurityW', filename) return NULL cdef BYTE* sd = malloc((length) * sizeof(BYTE)) GetFileSecurityW(wcharfilename, request, sd, length, &length) PyMem_Free(wcharfilename) return sd + cdef PSID _get_security_descriptor_owner(PSID sd): cdef PSID sid cdef BOOL sid_defaulted GetSecurityDescriptorOwner(sd, &sid, &sid_defaulted) return (sid) + cdef _look_up_account_sid(PSID sid): cdef int SIZE = 256 cdef wchar_t* name = malloc((SIZE) * sizeof(wchar_t)) @@ -133,13 +166,13 @@ cdef _look_up_account_sid(PSID sid): cdef BOOL ret = LookupAccountSidW(NULL, sid, name, &cch_name, domain, &cch_domain, &sid_type) if ret == 0: lasterror = GetLastError() - if lasterror == 1332: + if lasterror == ERROR_NONE_MAPPED: # Unknown (removed?) user or file from another windows installation free(name) free(domain) return 'unknown', 'unknown', 0 else: - print 'Windows error:', lasterror + raise_error('LookupAccountSidW') pystrName = PyUnicode_FromWideChar(name, -1) pystrDomain = PyUnicode_FromWideChar(domain, -1) @@ -148,6 +181,7 @@ cdef _look_up_account_sid(PSID sid): free(domain) return pystrName, pystrDomain, sid_type + cdef sid2string(PSID sid): cdef wchar_t* sidstr ConvertSidToStringSidW(sid, &sidstr) @@ -155,6 +189,7 @@ cdef sid2string(PSID sid): LocalFree(sidstr) return ret + def get_owner(path): cdef int request = OWNER_SECURITY_INFORMATION cdef BYTE* sd = _get_file_security(path, request) @@ -162,7 +197,6 @@ def get_owner(path): return 'unknown', 'S-1-0-0' cdef PSID sid = _get_security_descriptor_owner(sd) if sid == NULL: - print 'sid is null' return 'unknown', 'S-1-0-0' name, domain, sid_type = _look_up_account_sid(sid) free(sd) @@ -171,6 +205,7 @@ def get_owner(path): else: return name, sid2string(sid) + def set_owner(path, owner, sidstring = None): cdef PSID newsid cdef wchar_t* temp @@ -190,6 +225,7 @@ def set_owner(path, owner, sidstring = None): domainlength = 0 LookupAccountNameW(NULL, temp, newsid, &length, NULL, &domainlength, &sid_type) if GetLastError() != 0: + raise_error('LookupAccountNameW', owner) PyMem_Free(temp) return @@ -203,6 +239,7 @@ def set_owner(path, owner, sidstring = None): else: free(newsid) + def acl_get(path, item, st, numeric_owner=False): cdef int request = DACL_SECURITY_INFORMATION @@ -224,7 +261,7 @@ def acl_get(path, item, st, numeric_owner=False): cdef PSID newsid cdef uint32_t domainlength cdef uint32_t sid_type - for i in range(0, length): + for i in range(length): permissions = None name = "" sidstr = "" @@ -255,15 +292,16 @@ def acl_get(path, item, st, numeric_owner=False): free(SD) LocalFree(ACEs) + def acl_set(path, item, numeric_owner=False): - if b'dacl' not in item: + if b'win_dacl' not in item: return pyDACL = json.loads(safe_decode(item[b'win_dacl'])) cdef _EXPLICIT_ACCESS_W* ACEs = <_EXPLICIT_ACCESS_W*>calloc(sizeof(_EXPLICIT_ACCESS_W), len(pyDACL)) cdef wchar_t* temp cdef PSID newsid - for i in range(0, len(pyDACL)): + 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) @@ -281,7 +319,7 @@ def acl_set(path, item, numeric_owner=False): cdef wchar_t* cstrPath = PyUnicode_AsWideCharString(path, NULL) SetNamedSecurityInfoW(cstrPath, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, newDACL, NULL) - for i in range(0, len(pyDACL)): + for i in range(len(pyDACL)): if pyDACL[i]['user']['name'] == '' or numeric_owner: LocalFree(ACEs[i].Trustee.ptstrName) else: diff --git a/setup.py b/setup.py index c1601c4f6..a06d7c6d3 100644 --- a/setup.py +++ b/setup.py @@ -54,7 +54,6 @@ try: def __init__(self, *args, **kwargs): for src in glob('borg/*.pyx'): options = cython_compiler.default_options - options['language_level'] = 3 cython_compiler.compile(src, options) super().__init__(*args, **kwargs)