def get_acl_lists_from_rp(rp): """Returns (acl_list, def_acl_list) from an rpath. Call locally""" assert rp.conn is Globals.local_connection try: acl = posix1e.ACL(file=rp.path) except (FileNotFoundError, UnicodeEncodeError) as exc: log.Log( "Warning: unable to read ACL from %s: %s" % (rp.get_safepath(), exc), 3) acl = None except IOError as exc: if exc.errno == errno.EOPNOTSUPP: acl = None else: raise if rp.isdir(): try: def_acl = posix1e.ACL(filedef=os.fsdecode(rp.path)) except (FileNotFoundError, UnicodeEncodeError) as exc: log.Log( "Warning: unable to read default ACL from %s: %s" % (rp.get_safepath(), exc), 3) def_acl = None except IOError as exc: if exc.errno == errno.EOPNOTSUPP: def_acl = None else: raise else: def_acl = None return (acl and acl_to_list(acl), def_acl and acl_to_list(def_acl))
def testFromDir(self): """Test loading ACLs from a directory""" dname = self._getdir() acl1 = posix1e.ACL(file=dname) acl2 = posix1e.ACL(filedef=dname) self.assertTrue(acl1.valid(), "ACL read from directory should be valid")
def get_acl_lists_from_rp(rp): """Returns (acl_list, def_acl_list) from an rpath. Call locally""" assert rp.conn is Globals.local_connection, ( "Get ACLs of path should only be done locally not over {conn}.".format( conn=rp.conn)) try: acl = posix1e.ACL(file=rp.path) except (FileNotFoundError, UnicodeEncodeError) as exc: log.Log( "Unable to read ACL from path {pa} due to exception '{ex}'".format( pa=rp, ex=exc), log.NOTE) acl = None except OSError as exc: if exc.errno == errno.EOPNOTSUPP: acl = None else: raise if rp.isdir(): try: def_acl = posix1e.ACL(filedef=os.fsdecode(rp.path)) except (FileNotFoundError, UnicodeEncodeError) as exc: log.Log( "Unable to read default ACL from path {pa} due to " "exception '{ex}'".format(pa=rp, ex=exc), log.NOTE) def_acl = None except OSError as exc: if exc.errno == errno.EOPNOTSUPP: def_acl = None else: raise else: def_acl = None return (acl and _acl_to_list(acl), def_acl and _acl_to_list(def_acl))
def set_rp_acl(rp, entry_list=None, default_entry_list=None, map_names=1): """Set given rp with ACL that acl_text defines. rp should be local""" assert rp.conn is Globals.local_connection if entry_list: acl = list_to_acl(entry_list, map_names) else: acl = posix1e.ACL() try: acl.applyto(rp.path) except IOError as exc: if exc.errno == errno.EOPNOTSUPP: log.Log( "Warning: unable to set ACL on %s: %s" % (rp.get_safepath(), exc), 4) return else: raise if rp.isdir(): if default_entry_list: def_acl = list_to_acl(default_entry_list, map_names) else: def_acl = posix1e.ACL() def_acl.applyto(rp.path, posix1e.ACL_TYPE_DEFAULT)
def _add_posix1e_acl(self, path, st): if not posix1e or not posix1e.HAS_EXTENDED_CHECK: return if not stat.S_ISLNK(st.st_mode): acls = None def_acls = None try: if posix1e.has_extended(path): acl = posix1e.ACL(file=path) acls = [acl, acl] # txt and num are the same if stat.S_ISDIR(st.st_mode): def_acl = posix1e.ACL(filedef=path) def_acls = [def_acl, def_acl] except EnvironmentError as e: if e.errno not in (errno.EOPNOTSUPP, errno.ENOSYS): raise if acls: txt_flags = posix1e.TEXT_ABBREVIATE num_flags = posix1e.TEXT_ABBREVIATE | posix1e.TEXT_NUMERIC_IDS acl_rep = [ acls[0].to_any_text('', '\n', txt_flags), acls[1].to_any_text('', '\n', num_flags) ] if def_acls: acl_rep.append(def_acls[0].to_any_text( '', '\n', txt_flags)) acl_rep.append(def_acls[1].to_any_text( '', '\n', num_flags)) self.posix1e_acl = acl_rep
def set_rp_acl(rp, entry_list=None, default_entry_list=None, map_names=1): """Set given rp with ACL that acl_text defines. rp should be local""" assert rp.conn is Globals.local_connection, ( "Set ACLs of path should only be done locally not over {conn}.".format( conn=rp.conn)) if entry_list: acl = _list_to_acl(entry_list, map_names) else: acl = posix1e.ACL() try: acl.applyto(rp.path) except OSError as exc: if exc.errno == errno.EOPNOTSUPP: log.Log( "Unable to set ACL on path {pa} due to exception '{ex}'". format(pa=rp, ex=exc), log.INFO) return else: raise if rp.isdir(): if default_entry_list: def_acl = _list_to_acl(default_entry_list, map_names) else: def_acl = posix1e.ACL() def_acl.applyto(rp.path, posix1e.ACL_TYPE_DEFAULT)
def test_dir_posix_acl(self): ''' set POSIX acl, get remove ACL on a dir ''' os.mkdir(TESTDIR, 0755) dacl = posix1e.ACL(text=DACL_TO_SET) dacl.applyto(TESTDIR) got_acl = posix1e.ACL(file=TESTDIR) os.rmdir(TESTDIR)
def testReapply(self): """Test re-applying an ACL""" fd, fname = self._getfile() acl1 = posix1e.ACL(fd=fd) acl1.applyto(fd) acl1.applyto(fname) dname = self._getdir() acl2 = posix1e.ACL(file=fname) acl2.applyto(dname)
def testEquivMode(self): """Test the equiv_mode function""" if HAS_ACL_FROM_MODE: for mode in M0644, M0755: acl = posix1e.ACL(mode=mode) self.assertEqual(acl.equiv_mode(), mode) acl = posix1e.ACL(text="u::rw,g::r,o::r") self.assertEqual(acl.equiv_mode(), M0644) acl = posix1e.ACL(text="u::rx,g::-,o::-") self.assertEqual(acl.equiv_mode(), M0500)
def test_file_posix_acl(self): ''' set POSIX acl, get remove ACL on a file ''' f = open(TESTFILE, 'w') facl = posix1e.ACL(text=FACL_TO_SET) facl.applyto(TESTFILE) got_acl = posix1e.ACL(file=TESTFILE) f.close() safe_rm(TESTFILE)
def _add_posix1e_acl(self, path, st): if not posix1e: return if not stat.S_ISLNK(st.st_mode): try: if posix1e.has_extended(path): acl = posix1e.ACL(file=path) self.posix1e_acl = [acl, acl] # txt and num are the same if stat.S_ISDIR(st.st_mode): acl = posix1e.ACL(filedef=path) self.posix1e_acl.extend([acl, acl]) except EnvironmentError, e: if e.errno != errno.EOPNOTSUPP: raise
def acls_from_file(filename, include_standard=False): """Returns the extended ACL entries from the given file as list of the text representation. Arguments: filename -- the file name to get the ACLs from include_standard -- if True, ACL entries representing standard Linux permissions will be included""" result = [] try: acl = posix1e.ACL(file=filename) except: print 'Error getting ACLs from %s' % filename return [] text = acl.to_any_text(options=posix1e.TEXT_ABBREVIATE | posix1e.TEXT_NUMERIC_IDS) for entry in text.split("\n"): if not include_standard and \ re.search(r'^[ugo]::', entry) != None: continue result.append(entry) return result
def getACLOnShare(self, name): """ Return a list with all the groups that have rwx access to the share. @param name: name of the share (last component of the path) @type name: str @rtype: tuple @return: tuple of groups, users that have rwx access to the share. """ path = self.getContent(name, "path") ret = ([], []) ldapobj = ldapUserGroupControl() acl1 = posix1e.ACL(file=path) for e in acl1: if e.permset.write: if e.tag_type == posix1e.ACL_GROUP: res = ldapobj.getDetailedGroupById(str(e.qualifier)) if res: ret[0].append(res['cn'][0]) else: ret[0].append(grp.getgrgid(e.qualifier).gr_name) if e.tag_type == posix1e.ACL_USER: res = ldapobj.getDetailedUserById(str(e.qualifier)) if res: ret[1].append(res['uid'][0]) else: ret[1].append(pwd.getpwuid(e.qualifier).pw_name) return ret
def testExtended(self): """Test the acl_extended function""" fd, fname = self._getfile() basic_acl = posix1e.ACL(text=BASIC_ACL_TEXT) basic_acl.applyto(fd) for item in fd, fname: self.assertFalse( has_extended(item), "A simple ACL should not be reported as extended") enhanced_acl = posix1e.ACL(text="u::rw,g::-,o::-,u:root:rw,mask::r") self.assertTrue(enhanced_acl.valid(), "Failure to build an extended ACL") enhanced_acl.applyto(fd) for item in fd, fname: self.assertTrue(has_extended(item), "An extended ACL should be reported as such")
def _detect_acls(self, rp): """ Set self.acls based on rp. Does not write. Needs to be local """ if not Globals.acls_active: log.Log( "POSIX ACLs test skipped as rdiff-backup was started " "with --no-acls option", log.INFO) self.acls = None return try: import posix1e except ImportError: log.Log( "Unable to import module posix1e from pylibacl package. " "POSIX ACLs not supported on filesystem at " "path {pa}".format(pa=rp), log.INFO) self.acls = False return try: posix1e.ACL(file=rp.path) except OSError as exc: log.Log( "POSIX ACLs not supported by filesystem at path {pa} " "due to exception '{ex}'".format(pa=rp, ex=exc), log.INFO) self.acls = False else: self.acls = True
def _detect_acls(self, rp): """Set self.acls based on rp. Does not write. Needs to be local""" if Globals.acls_active == 0: log.Log( "POSIX ACLs test skipped. rdiff-backup run " "with --no-acls option.", 4) self.acls = 0 return try: import posix1e except ImportError: log.Log( "Unable to import module posix1e from pylibacl " "package.\nPOSIX ACLs not supported on filesystem at %s" % rp.get_safepath(), 4) self.acls = 0 return try: posix1e.ACL(file=rp.path) except IOError: log.Log( "POSIX ACLs not supported by filesystem at %s" % rp.get_safepath(), 4) self.acls = 0 else: self.acls = 1
def testMultipleBadEntries(self): """Test multiple invalid entries""" for tag_type in (posix1e.ACL_USER, posix1e.ACL_GROUP): acl = posix1e.ACL(text=BASIC_ACL_TEXT) self.assertTrue( acl.valid(), "ACL built from standard description" " should be valid") e1 = acl.append() e1.tag_type = tag_type e1.qualifier = 0 e1.permset.clear() acl.calc_mask() self.assertTrue(acl.valid(), "ACL should be able to add a" " user/group entry") e2 = acl.append() e2.tag_type = tag_type e2.qualifier = 0 e2.permset.clear() ignore_ioerror(errno.EINVAL, acl.calc_mask) self.assertFalse( acl.valid(), "ACL should not validate when" " containing two duplicate entries") acl.delete_entry(e1) # FreeBSD trips over itself here and can't delete the # entry, even though it still exists. ignore_ioerror(errno.EINVAL, acl.delete_entry, e2)
def testQualifierValues(self): """Tests qualifier correct store/retrieval""" acl = posix1e.ACL() e = acl.append() # work around deprecation warnings if hasattr(self, 'assertRegex'): fn = self.assertRegex else: fn = self.assertRegexpMatches for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP]: qualifier = 1 e.tag_type = tag while True: if tag == posix1e.ACL_USER: regex = re.compile("user with uid %d" % qualifier) else: regex = re.compile("group with gid %d" % qualifier) try: e.qualifier = qualifier except OverflowError: # reached overflow condition, break break self.assertEqual(e.qualifier, qualifier) fn(str(e), regex) qualifier *= 2
def testPermset(self): """Test permissions""" acl = posix1e.ACL() e = acl.append() ps = e.permset ps.clear() str_ps = str(ps) self.checkRef(str_ps) pmap = { posix1e.ACL_READ: "read", posix1e.ACL_WRITE: "write", posix1e.ACL_EXECUTE: "execute", } for perm in pmap: str_ps = str(ps) self.checkRef(str_ps) self.assertFalse( ps.test(perm), "Empty permission set should not" " have permission '%s'" % pmap[perm]) ps.add(perm) self.assertTrue( ps.test(perm), "Permission '%s' should exist" " after addition" % pmap[perm]) str_ps = str(ps) self.checkRef(str_ps) ps.delete(perm) self.assertFalse( ps.test(perm), "Permission '%s' should not exist" " after deletion" % pmap[perm])
def set_access(root, owner, read_write, read_only, public): """ Recursively changes the owner of all files to the current user, since only a file's owner can set its ACL. Clears and resets the ACL for each file/directory in the project. Recursively changes the owner of all files to the project's owner. """ # Don't allow top-level files/dirs to be symbolic links if os.path.islink(root): fail("%s is a symbolic link. Cannot update ACL") texts = [] for x in ('-', 'x'): for w in ('-', 'w'): pieces = ['u::r%s%s,g::r%s%s' % (w, x, w, x)] if public: pieces.append(',o::r-%s' % x) else: pieces.append(',o::---') # Owners and Members have read/write writers = read_write + [owner] for user in writers: pieces.append(',u:%s:r%s%s' % (user, w, x)) # Collaborators have read-only for user in read_only: pieces.append(',u:%s:r-%s' % (user, x)) texts.append(''.join(pieces)) gen = [] for text in texts: try: acl = posix1e.ACL(text=text) except: fail( "Failed to create ACL. Check project config file for non-existent users" ) acl.calc_mask() if not acl.valid(): logger.debug("Bad ACL: %s" % text) fail("Error generating ACL. Please notify system administrator.") gen.append(acl) ro, rw, rx, rwx = gen[0], gen[1], gen[2], gen[3] apply_acl(root, ro, rw, rx, rwx) for top, dirs, files in os.walk(root): for name in dirs + files: path = os.path.join(top, name) realpath = os.path.realpath(os.path.expanduser(path)) if is_subdir(PROJECT_ROOT, realpath): apply_acl(path, ro, rw, rx, rwx) else: logger.warning( "%s is actually %s, which is not in $PROJECT_ROOT" % (path, realpath))
def testAppend(self): """Test append a new Entry to the ACL""" acl = posix1e.ACL() e = acl.append() e.tag_type = posix1e.ACL_OTHER ignore_ioerror(errno.EINVAL, acl.calc_mask) str_format = str(e) self.checkRef(str_format)
def testDelete(self): """Test delete Entry from the ACL""" acl = posix1e.ACL() e = acl.append() e.tag_type = posix1e.ACL_OTHER ignore_ioerror(errno.EINVAL, acl.calc_mask) acl.delete_entry(e) ignore_ioerror(errno.EINVAL, acl.calc_mask)
def list_to_acl(entry_list, map_names=1): """Return posix1e.ACL object from list representation If map_names is true, use user_group to update the names for the current system, and drop if not available. Otherwise just use the same id. See the acl_to_list function for the format of an acllist. """ def char_to_acltag(typechar): """Given typechar, query posix1e module for appropriate constant""" if typechar == "U": return posix1e.ACL_USER_OBJ elif typechar == "u": return posix1e.ACL_USER elif typechar == "G": return posix1e.ACL_GROUP_OBJ elif typechar == "g": return posix1e.ACL_GROUP elif typechar == "M": return posix1e.ACL_MASK else: assert typechar == "O", typechar return posix1e.ACL_OTHER def warn_drop(name): """Warn about acl with name getting dropped""" global dropped_acl_names if Globals.never_drop_acls: log.Log.FatalError( "--never-drop-acls specified but cannot map name\n" "%s occurring inside an ACL." % (name, )) if dropped_acl_names.has_key(name): return log.Log( "Warning: name %s not found on system, dropping ACL entry.\n" "Further ACL entries dropped with this name will not " "trigger further warnings" % (name, ), 2) dropped_acl_names[name] = name acl = posix1e.ACL() for typechar, owner_pair, perms in entry_list: id = None if owner_pair: if map_names: if typechar == "u": id = user_group.acl_user_map(*owner_pair) else: assert typechar == "g", (typechar, owner_pair, perms) id = user_group.acl_group_map(*owner_pair) if id is None: warn_drop(owner_pair[1]) continue else: assert owner_pair[0] is not None, (typechar, owner_pair, perms) id = owner_pair[0] entry = posix1e.Entry(acl) entry.tag_type = char_to_acltag(typechar) if id is not None: entry.qualifier = id entry.permset.read = perms >> 2 entry.permset.write = perms >> 1 & 1 entry.permset.execute = perms & 1 return acl
def _list_file_acls(self, path): """ Given a path, get a dict of existing POSIX ACLs on that path. The dict keys are a tuple of (<acl type (access or default)>, <acl scope (user or group)>, <acl qualifer (the user or group it applies to)>. values are the permissions of the described ACL. """ def _process_acl(acl, atype): """ Given an ACL object, process it appropriately and add it to the return value """ try: qual = '' if acl.tag_type == posix1e.ACL_USER: qual = pwd.getpwuid(acl.qualifier)[0] elif acl.tag_type == posix1e.ACL_GROUP: qual = grp.getgrgid(acl.qualifier)[0] elif atype == "access" or acl.tag_type == posix1e.ACL_MASK: return except (OSError, KeyError): err = sys.exc_info()[1] self.logger.error("POSIX: Lookup of %s %s failed: %s" % (atype, acl.qualifier, err)) qual = acl.qualifier existing[(atype, acl.tag_type, qual)] = \ self._norm_acl_perms(acl.permset) existing = dict() try: for acl in posix1e.ACL(file=path): _process_acl(acl, "access") except IOError: err = sys.exc_info()[1] if err.errno == 95: # fs is mounted noacl self.logger.debug("POSIX: Filesystem mounted without ACL " "support: %s" % path) else: self.logger.error( "POSIX: Error getting current ACLS on %s: %s" % (path, err)) return existing if os.path.isdir(path): for acl in posix1e.ACL(filedef=path): _process_acl(acl, "default") return existing
def testQualifierOverflow(self): """Tests qualifier overflow handling""" acl = posix1e.ACL() e = acl.append() qualifier = sys.maxsize * 2 for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP]: e.tag_type = tag with self.assertRaises(OverflowError): e.qualifier = qualifier
def _reset_acl(fn, s, append): file_acl = posix1e.ACL(file=fn) acl = posix1e.ACL(text=s) for entry in acl: _remove_dup(file_acl, entry) if append: file_acl.append(entry) file_acl.calc_mask() # Przelicz maskę. if not file_acl.valid(): print("invalid ACL provided: %s" % acl) return False # Zaaplikuj przeliczone ACL. file_acl.applyto(fn) return True
def get_permissions(path, user, check_prefixes=False): user = pwd.getpwnam(user) parts = path.split(os.path.sep) prefixes = [ os.path.sep.join(parts[:i + 1]) or '/' for i in xrange(len(parts) - 1) ] if check_prefixes: for prefix in prefixes: acl = posix1e.ACL(file=prefix) st = os.stat(prefix) if not _check_permission(acl, user, st, (posix1e.ACL_EXECUTE, )): raise IOError(errno.EACCES, None, prefix) acl = posix1e.ACL(file=path.encode('utf-8')) st = os.stat(path) return set(p for p in (posix1e.ACL_READ, posix1e.ACL_WRITE, posix1e.ACL_EXECUTE) if \ _check_permission(acl, user, st, (p,)))
def testNegativeQualifier(self): """Tests negative qualifier handling""" # Note: this presumes that uid_t/gid_t in C are unsigned... acl = posix1e.ACL() e = acl.append() for tag in [posix1e.ACL_USER, posix1e.ACL_GROUP]: e.tag_type = tag for qualifier in [-10, -5, -1]: with self.assertRaises(OverflowError): e.qualifier = qualifier
def check_acl(filename, username, log): ''' Check which ACLs given user has for given file. @param filename Absolute path to file to read ACL from @param username Username to check ACL against @param log req.log_error ''' # Get ACL and traditional group lists for the file file_acl = posix1e.ACL(file=filename) stat_info = os.stat(filename) unix_group = grp.getgrgid(stat_info.st_gid).gr_name # Sanitize username before invoking shell valid_chars = "-_.() %s%s" % (string.ascii_letters, string.digits) username = ''.join(c for c in username if c in valid_chars) # List user's groups with 'id' command p = Popen(["id", "-n", "-G", "-z", username], stdin=PIPE, stdout=PIPE, stderr=PIPE) output, err = p.communicate(b'') user_groups = str(output).split("\0") # Walk through file's ACLs and compare to user's can_read = False can_write = False for l in [s.strip() for s in str(file_acl).splitlines()]: group = None perm = '' # Handle other ("everyone") if l[0:7] == 'other::': parts = l.split(':') perm = parts[2] # Handle actual group memberships elif l[0:6] == 'group:': parts = l.split(':') group = parts[1] # Check default unix group in addition to ACLs if group == '': group = unix_group if group in user_groups: perm = parts[2] can_write = can_write or ('w' in perm) can_read = can_read or ('r' in perm) return {'r': can_read, 'w': can_write}
def apply_acl(acl_rep, kind): try: acl = posix1e.ACL(text=acl_rep) except IOError, e: if e.errno == 0: # pylibacl appears to return an IOError with errno # set to 0 if a group referred to by the ACL rep # doesn't exist on the current system. raise ApplyError("POSIX1e ACL: can't create %r for %r" % (acl_rep, path)) else: raise