From 2ef29ab52e321104a1ee863f4c1f19948f7da66f Mon Sep 17 00:00:00 2001 From: Ophestra Umiker Date: Thu, 11 Jul 2024 00:38:21 +0900 Subject: [PATCH] acl: implement acl utils via libacl The library is simple and should be easy to port to Go, however correctness matters more in this case and overhead from cgo is negligible for our usage scenario. Signed-off-by: Ophestra Umiker --- acl.go | 176 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 acl.go diff --git a/acl.go b/acl.go new file mode 100644 index 0000000..f1f496e --- /dev/null +++ b/acl.go @@ -0,0 +1,176 @@ +package main + +import "C" +import ( + "errors" + "fmt" + "syscall" + "unsafe" +) + +//#include +//#include +//#include +//#cgo linux LDFLAGS: -lacl +import "C" + +const ( + aclRead = C.ACL_READ + aclWrite = C.ACL_WRITE + aclExecute = C.ACL_EXECUTE + + aclTypeDefault = C.ACL_TYPE_DEFAULT + aclTypeAccess = C.ACL_TYPE_ACCESS + + aclUndefinedTag = C.ACL_UNDEFINED_TAG + aclUserObj = C.ACL_USER_OBJ + aclUser = C.ACL_USER + aclGroupObj = C.ACL_GROUP_OBJ + aclGroup = C.ACL_GROUP + aclMask = C.ACL_MASK + aclOther = C.ACL_OTHER +) + +type acl struct { + val C.acl_t + freed bool +} + +func aclUpdatePerm(path string, uid int, perms ...C.acl_perm_t) error { + // read acl from file + a, err := aclGetFile(path, aclTypeAccess) + if err != nil { + return err + } + // free acl on return if get is successful + defer a.free() + + // remove existing entry + if err = a.removeEntry(aclUser, uid); err != nil { + return err + } + + // create new entry if perms are passed + if len(perms) > 0 { + // create new acl entry + var e C.acl_entry_t + if _, err = C.acl_create_entry(&a.val, &e); err != nil { + return err + } + + // get perm set of new entry + var p C.acl_permset_t + if _, err = C.acl_get_permset(e, &p); err != nil { + return err + } + + // add target perms + for _, perm := range perms { + if _, err = C.acl_add_perm(p, perm); err != nil { + return err + } + } + + // set perm set to new entry + if _, err = C.acl_set_permset(e, p); err != nil { + return err + } + + // set user tag to new entry + if _, err = C.acl_set_tag_type(e, aclUser); err != nil { + return err + } + + // set qualifier (uid) to new entry + if _, err = C.acl_set_qualifier(e, unsafe.Pointer(&uid)); err != nil { + return err + } + } + + // calculate mask after update + if _, err = C.acl_calc_mask(&a.val); err != nil { + return err + } + + // write acl to file + return a.setFile(path, aclTypeAccess) +} + +func aclGetFile(path string, t C.acl_type_t) (*acl, error) { + p := C.CString(path) + a, err := C.acl_get_file(p, t) + C.free(unsafe.Pointer(p)) + + if errors.Is(err, syscall.ENODATA) { + err = nil + } + return &acl{val: a, freed: false}, err +} + +func (a *acl) setFile(path string, t C.acl_type_t) error { + if C.acl_valid(a.val) != 0 { + return fmt.Errorf("invalid acl") + } + + p := C.CString(path) + _, err := C.acl_set_file(p, t, a.val) + C.free(unsafe.Pointer(p)) + return err +} + +func (a *acl) removeEntry(tt C.acl_tag_t, tq int) error { + var e C.acl_entry_t + + // get first entry + if r, err := C.acl_get_entry(a.val, C.ACL_FIRST_ENTRY, &e); err != nil { + return err + } else if r == 0 { + // return on acl with no entries + return nil + } + + for { + if r, err := C.acl_get_entry(a.val, C.ACL_NEXT_ENTRY, &e); err != nil { + return err + } else if r == 0 { + // return on drained acl + return nil + } + + var ( + q int + t C.acl_tag_t + ) + + // get current entry tag type + if _, err := C.acl_get_tag_type(e, &t); err != nil { + return err + } + + // get current entry qualifier + if rq, err := C.acl_get_qualifier(e); err != nil { + // neither ACL_USER nor ACL_GROUP + if errors.Is(err, syscall.EINVAL) { + continue + } + + return err + } else { + q = *(*int)(rq) + } + + // delete on match + if t == tt && q == tq { + _, err := C.acl_delete_entry(a.val, e) + return err + } + } +} + +func (a *acl) free() { + if a.freed { + panic("acl already freed") + } + C.acl_free(unsafe.Pointer(a.val)) + a.freed = true +}