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 +}