From d041352936e900a2d88df8916d6e7497ce56251b Mon Sep 17 00:00:00 2001 From: owsla Date: Thu, 2 Oct 2008 03:46:02 +0000 Subject: Add error handling and logging to Windows ACL support; fixes Windows backup to SMB share. Improve test in fs_abilities to determine if Windows ACLs are supported. git-svn-id: http://svn.savannah.nongnu.org/svn/rdiff-backup/trunk@944 2b77aa54-bcbc-44c9-a7ec-4f6cf2b41109 --- rdiff-backup/CHANGELOG | 4 + rdiff-backup/rdiff_backup/fs_abilities.py | 48 +++++++--- rdiff-backup/rdiff_backup/win_acls.py | 140 +++++++++++++++++++----------- 3 files changed, 132 insertions(+), 60 deletions(-) diff --git a/rdiff-backup/CHANGELOG b/rdiff-backup/CHANGELOG index 5c289df..fbb6932 100644 --- a/rdiff-backup/CHANGELOG +++ b/rdiff-backup/CHANGELOG @@ -1,6 +1,10 @@ New in v1.2.2 (????/??/??) --------------------------- +Add error handling and logging to Windows ACL support; fixes Windows backup to +SMB share. Improve test in fs_abilities to determine if Windows ACLs are +supported. (Andrew Ferguson) + Add a warning message if extended attributes support is broken by the filesystem (such as with older EncFS versions). (Andrew Ferguson) diff --git a/rdiff-backup/rdiff_backup/fs_abilities.py b/rdiff-backup/rdiff_backup/fs_abilities.py index a012a04..3a3f32b 100644 --- a/rdiff-backup/rdiff_backup/fs_abilities.py +++ b/rdiff-backup/rdiff_backup/fs_abilities.py @@ -122,7 +122,7 @@ class FSAbilities: self.read_only = 1 self.set_eas(rp, 0) self.set_acls(rp) - self.set_win_acls(rp) + self.set_win_acls(rp, 0) self.set_resource_fork_readonly(rp) self.set_carbonfile() self.set_case_sensitive_readonly(rp) @@ -154,7 +154,7 @@ class FSAbilities: self.set_fsync_dirs(subdir) self.set_eas(subdir, 1) self.set_acls(subdir) - self.set_win_acls(subdir) + self.set_win_acls(subdir, 1) self.set_dir_inc_perms(subdir) self.set_resource_fork_readwrite(subdir) self.set_carbonfile() @@ -368,27 +368,53 @@ class FSAbilities: self.eas = 0 except AssertionError: log.Log("Extended attributes support is broken on filesystem at " - "%s. Please upgrade the filesystem driver, contact the " - "developers, or use the --no-eas option to disable " - "extended attributes support and suppress this message." + "%s.\nPlease upgrade the filesystem driver, contact the " + "developers,\nor use the --no-eas option to disable " + "extended attributes\nsupport and suppress this message." % (rp.path,), 1) self.eas = 0 else: self.eas = 1 - def set_win_acls(self, dir_rp): + def set_win_acls(self, dir_rp, write): """Test if windows access control lists are supported""" + assert Globals.local_connection is dir_rp.conn + assert dir_rp.lstat() try: - import win32security + import win32security, pywintypes except ImportError: log.Log("Unable to import win32security module. Windows ACLs\n" "not supported by filesystem at %s" % dir_rp.path, 4) self.win_acls = 0 return + try: + sd = win32security.GetNamedSecurityInfo(dir_rp.path, + win32security.SE_FILE_OBJECT, + win32security.OWNER_SECURITY_INFORMATION | + win32security.GROUP_SECURITY_INFORMATION | + win32security.DACL_SECURITY_INFORMATION) + acl = sd.GetSecurityDescriptorDacl() + n = acl.GetAceCount() + if write: + win32security.SetNamedSecurityInfo(dir_rp.path, + win32security.SE_FILE_OBJECT, + win32security.OWNER_SECURITY_INFORMATION | + win32security.GROUP_SECURITY_INFORMATION | + win32security.DACL_SECURITY_INFORMATION, + sd.GetSecurityDescriptorOwner(), + sd.GetSecurityDescriptorGroup(), + sd.GetSecurityDescriptorDacl(), + None) + except (OSError, AttributeError, pywintypes.error): + log.Log("Unable to load a Windows ACL.\nWindows ACLs not supported " + "by filesystem at %s" % dir_rp.path, 4) + self.win_acls = 0 + return + try: win_acls.init_acls() - except OSError: - log.Log("Windows ACLs not supported by filesystem\n" - "at %s" % dir_rp.path, 4) + except (OSError, AttributeError, pywintypes.error): + log.Log("Unable to init win_acls.\nWindows ACLs not supported by " + "filesystem at %s" % dir_rp.path, 4) self.win_acls = 0 return self.win_acls = 1 @@ -414,7 +440,7 @@ class FSAbilities: try: import Carbon.File import MacOS - except: + except (ImportError, AttributeError): self.carbonfile = 0 return diff --git a/rdiff-backup/rdiff_backup/win_acls.py b/rdiff-backup/rdiff_backup/win_acls.py index 2c2fa16..8238ebd 100644 --- a/rdiff-backup/rdiff_backup/win_acls.py +++ b/rdiff-backup/rdiff_backup/win_acls.py @@ -17,11 +17,12 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA -import C, metadata, re, rorpiter, rpath +import C, metadata, re, rorpiter, rpath, log try: from win32security import * -except: + import pywintypes +except ImportError: GROUP_SECURITY_INFORMATION = 0 OWNER_SECURITY_INFORMATION = 0 DACL_SECURITY_INFORMATION = 0 @@ -40,8 +41,11 @@ class ACL: def load_from_rp(self, rp, skip_inherit_only = True): self.index = rp.index try: - sd = rp.conn.win32security.GetNamedSecurityInfo(rp.path, SE_FILE_OBJECT, ACL.flags) - except: + sd = rp.conn.win32security. \ + GetNamedSecurityInfo(rp.path, SE_FILE_OBJECT, ACL.flags) + except (OSError, IOError, pywintypes.error), exc: + log.Log("Warning: unable to read ACL from %s: %s" + % (repr(rp.path), exc), 4) return if skip_inherit_only: @@ -70,18 +74,30 @@ class ACL: sd.SetSecurityDescriptorSacl(1, acl, 0) if not sd.GetSecurityDescriptorDacl(): - sd.SetSecurityDescriptorDacl(0, None, 0) - if not sd.GetSecurityDescriptorSacl(): + sd.SetSecurityDescriptorDacl(0, None, 0) + if (ACL.flags & SACL_SECURITY_INFORMATION) and not \ + sd.GetSecurityDescriptorSacl(): sd.SetSecurityDescriptorSacl(0, None, 0) - self.__acl = \ - rp.conn.win32security.ConvertSecurityDescriptorToStringSecurityDescriptor(sd, + try: + self.__acl = \ + rp.conn.win32security. \ + ConvertSecurityDescriptorToStringSecurityDescriptor(sd, SDDL_REVISION_1, ACL.flags) + except (OSError, IOError, pywintypes.error), exc: + log.Log("Warning: unable to convert ACL from %s to string: %s" + % (repr(rp.path), exc), 4) + self.__acl = '' def clear_rp(self, rp): # not sure how to interpret this - # I'll jus clear all acl-s from rp.path - sd = rp.conn.win32security.GetNamedSecurityInfo(rp.path, SE_FILE_OBJECT, ACL.flags) + # I'll just clear all acl-s from rp.path + try: + sd = rp.conn.win32security. \ + GetNamedSecurityInfo(rp.path, SE_FILE_OBJECT, ACL.flags) + except (OSError, IOError, pywintypes.error), exc: + log.Log("Warning: unable to read ACL from %s for clearing: %s" + % (repr(rp.path), exc), 4) acl = sd.GetSecurityDescriptorDacl() if acl: @@ -102,42 +118,65 @@ class ACL: acl.DeleteAce(n) sd.SetSecurityDescriptorSacl(0, acl, 0) - rp.conn.win32security.SetNamedSecurityInfo(rp.path, SE_FILE_OBJECT, ACL.flags, - sd.GetSecurityDescriptorOwner(), sd.GetSecurityDescriptorGroup(), - sd.GetSecurityDescriptorDacl(), sd.GetSecurityDescriptorSacl()) + try: + rp.conn.win32security. \ + SetNamedSecurityInfo(rp.path, SE_FILE_OBJECT, ACL.flags, + sd.GetSecurityDescriptorOwner(),sd.GetSecurityDescriptorGroup(), + sd.GetSecurityDescriptorDacl(), + (ACL.flags & SACL_SECURITY_INFORMATION) and + sd.GetSecurityDescriptorSacl() or None) + except (OSError, IOError, pywintypes.error), exc: + log.Log("Warning: unable to set ACL on %s after clearing: %s" + % (repr(rp.path), exc), 4) def write_to_rp(self, rp): - if self.__acl: - sd = rp.conn.win32security.ConvertStringSecurityDescriptorToSecurityDescriptor(self.__acl, - SDDL_REVISION_1) - - # Enable the next block of code for dirs after we have a mechanism in - # backup.py (and similar) to do a first pass to see if a directory - # has SE_DACL_PROTECTED. In that case, we will need to - # 1) dest_rorp.write_win_acl(source_rorp.get_win_acl()) - # --> And clear the existing dest_rorp one while doing so - # 2) Check if backup user has Admin privs to write to dest_rorp - # 3) If not, add Admin write privs to dest_rorp and add dir - # to dir_perms_list-equivalent - # 4) THEN, allow the pre_process() function to finish and the - # files be copied over. Those files which wish to - # will now inherit the correct ACE objects. - # 5) If dir was on dir_perms_list-equivalent, drop the write - # write permission we added. - # 6) When copy_attribs is called in end_process, make sure - # that the write_win_acl() call isn't made this time - # The reason we will need to do this is because otherwise, the files - # which are created during step 4 will reference the ACE entries - # which we clear during step 6. We need to clear them *before* the - # children files/subdirs are created and generate the appropriate - # DACL so the inheritance magic can happen during step 4. - (flags, revision) = sd.GetSecurityDescriptorControl() - if (not rp.isdir() and flags & SE_DACL_PROTECTED): - self.clear_rp(rp) - - rp.conn.win32security.SetNamedSecurityInfo(rp.path, SE_FILE_OBJECT, ACL.flags, - sd.GetSecurityDescriptorOwner(), sd.GetSecurityDescriptorGroup(), - sd.GetSecurityDescriptorDacl(), sd.GetSecurityDescriptorSacl()) + if not self.__acl: + return + + try: + sd = rp.conn.win32security. \ + ConvertStringSecurityDescriptorToSecurityDescriptor( + self.__acl, SDDL_REVISION_1) + except (OSError, IOError, pywintypes.error), exc: + log.Log("Warning: unable to convert string %s to ACL: %s" + % (repr(self.__acl), exc), 4) + + # Enable next block of code for dirs after we have a mechanism in + # backup.py (and similar) to do a first pass to see if a directory + # has SE_DACL_PROTECTED. In that case, we will need to + # 1) dest_rorp.write_win_acl(source_rorp.get_win_acl()) + # --> And clear existing dest_rorp one while doing so + # 2) Check if backup user has Admin privs to write dest_rorp + # --> May need to use Win32 AccessCheck() API + # 3) If not, add Admin write privs to dest_rorp and add dir + # to dir_perms_list-equivalent + # 4) THEN, allow the pre_process() function to finish and the + # files be copied over. Those files which wish to + # will now inherit the correct ACE objects. + # 5) If dir was on dir_perms_list-equivalent, drop the write + # write permission we added. + # 6) When copy_attribs is called in end_process, make sure + # that the write_win_acl() call isn't made this time + # The reason we will need to do this is because otherwise, the files + # which are created during step 4 will reference the ACE entries + # which we clear during step 6. We need to clear them *before* the + # children files/subdirs are created and generate the appropriate + # DACL so the inheritance magic can happen during step 4. + + (flags, revision) = sd.GetSecurityDescriptorControl() + if (not rp.isdir() and flags & SE_DACL_PROTECTED): + self.clear_rp(rp) + + try: + rp.conn.win32security. \ + SetNamedSecurityInfo(rp.path, SE_FILE_OBJECT, ACL.flags, + sd.GetSecurityDescriptorOwner(),sd.GetSecurityDescriptorGroup(), + sd.GetSecurityDescriptorDacl(), + (ACL.flags & SACL_SECURITY_INFORMATION) and + sd.GetSecurityDescriptorSacl() or None) + except (OSError, IOError, pywintypes.error), exc: + log.Log("Warning: unable to set ACL on %s: %s" + % (repr(rp.path), exc), 4) def __str__(self): return '# file: %s\n%s\n' % \ @@ -208,20 +247,24 @@ def init_acls(): import win32api try: hnd = OpenProcessToken(win32api.GetCurrentProcess(), - TOKEN_ADJUST_PRIVILEGES| TOKEN_QUERY) - except win32api.error: + TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY) + except win32api.error, exc: + log.Log("Warning: unable to open Windows process token: %s" + % exc, 5) return try: try: lpv = lambda priv: LookupPrivilegeValue(None, priv) - # enable the SE_*_NAME priveleges + # enable the SE_*_NAME privileges SecurityName = lpv(SE_SECURITY_NAME) AdjustTokenPrivileges(hnd, False, [ (SecurityName, SE_PRIVILEGE_ENABLED), (lpv(SE_BACKUP_NAME), SE_PRIVILEGE_ENABLED), (lpv(SE_RESTORE_NAME), SE_PRIVILEGE_ENABLED) ]) - except win32api.error: + except win32api.error, exc: + log.Log("Warning: unable to enable SE_*_NAME privileges: %s" + % exc, 5) return for name, enabled in GetTokenInformation(hnd, TokenPrivileges): if name == SecurityName and enabled: @@ -230,4 +273,3 @@ def init_acls(): break finally: win32api.CloseHandle(hnd) - -- cgit v1.2.1