xref: /openbmc/openbmc/poky/bitbake/lib/bb/acl.py (revision ac13d5f3)
1#! /usr/bin/env python3
2#
3# Copyright 2023 by Garmin Ltd. or its subsidiaries
4#
5# SPDX-License-Identifier: MIT
6
7
8import sys
9import ctypes
10import os
11import errno
12import pwd
13import grp
14
15libacl = ctypes.CDLL("libacl.so.1", use_errno=True)
16
17
18ACL_TYPE_ACCESS = 0x8000
19ACL_TYPE_DEFAULT = 0x4000
20
21ACL_FIRST_ENTRY = 0
22ACL_NEXT_ENTRY = 1
23
24ACL_UNDEFINED_TAG = 0x00
25ACL_USER_OBJ = 0x01
26ACL_USER = 0x02
27ACL_GROUP_OBJ = 0x04
28ACL_GROUP = 0x08
29ACL_MASK = 0x10
30ACL_OTHER = 0x20
31
32ACL_READ = 0x04
33ACL_WRITE = 0x02
34ACL_EXECUTE = 0x01
35
36acl_t = ctypes.c_void_p
37acl_entry_t = ctypes.c_void_p
38acl_permset_t = ctypes.c_void_p
39acl_perm_t = ctypes.c_uint
40
41acl_tag_t = ctypes.c_int
42
43libacl.acl_free.argtypes = [acl_t]
44
45
46def acl_free(acl):
47    libacl.acl_free(acl)
48
49
50libacl.acl_get_file.restype = acl_t
51libacl.acl_get_file.argtypes = [ctypes.c_char_p, ctypes.c_uint]
52
53
54def acl_get_file(path, typ):
55    acl = libacl.acl_get_file(os.fsencode(path), typ)
56    if acl is None:
57        err = ctypes.get_errno()
58        raise OSError(err, os.strerror(err), str(path))
59
60    return acl
61
62
63libacl.acl_get_entry.argtypes = [acl_t, ctypes.c_int, ctypes.c_void_p]
64
65
66def acl_get_entry(acl, entry_id):
67    entry = acl_entry_t()
68    ret = libacl.acl_get_entry(acl, entry_id, ctypes.byref(entry))
69    if ret < 0:
70        err = ctypes.get_errno()
71        raise OSError(err, os.strerror(err))
72
73    if ret == 0:
74        return None
75
76    return entry
77
78
79libacl.acl_get_tag_type.argtypes = [acl_entry_t, ctypes.c_void_p]
80
81
82def acl_get_tag_type(entry_d):
83    tag = acl_tag_t()
84    ret = libacl.acl_get_tag_type(entry_d, ctypes.byref(tag))
85    if ret < 0:
86        err = ctypes.get_errno()
87        raise OSError(err, os.strerror(err))
88    return tag.value
89
90
91libacl.acl_get_qualifier.restype = ctypes.c_void_p
92libacl.acl_get_qualifier.argtypes = [acl_entry_t]
93
94
95def acl_get_qualifier(entry_d):
96    ret = libacl.acl_get_qualifier(entry_d)
97    if ret is None:
98        err = ctypes.get_errno()
99        raise OSError(err, os.strerror(err))
100    return ctypes.c_void_p(ret)
101
102
103libacl.acl_get_permset.argtypes = [acl_entry_t, ctypes.c_void_p]
104
105
106def acl_get_permset(entry_d):
107    permset = acl_permset_t()
108    ret = libacl.acl_get_permset(entry_d, ctypes.byref(permset))
109    if ret < 0:
110        err = ctypes.get_errno()
111        raise OSError(err, os.strerror(err))
112
113    return permset
114
115
116libacl.acl_get_perm.argtypes = [acl_permset_t, acl_perm_t]
117
118
119def acl_get_perm(permset_d, perm):
120    ret = libacl.acl_get_perm(permset_d, perm)
121    if ret < 0:
122        err = ctypes.get_errno()
123        raise OSError(err, os.strerror(err))
124    return bool(ret)
125
126
127class Entry(object):
128    def __init__(self, tag, qualifier, mode):
129        self.tag = tag
130        self.qualifier = qualifier
131        self.mode = mode
132
133    def __str__(self):
134        typ = ""
135        qual = ""
136        if self.tag == ACL_USER:
137            typ = "user"
138            qual = pwd.getpwuid(self.qualifier).pw_name
139        elif self.tag == ACL_GROUP:
140            typ = "group"
141            qual = grp.getgrgid(self.qualifier).gr_name
142        elif self.tag == ACL_USER_OBJ:
143            typ = "user"
144        elif self.tag == ACL_GROUP_OBJ:
145            typ = "group"
146        elif self.tag == ACL_MASK:
147            typ = "mask"
148        elif self.tag == ACL_OTHER:
149            typ = "other"
150
151        r = "r" if self.mode & ACL_READ else "-"
152        w = "w" if self.mode & ACL_WRITE else "-"
153        x = "x" if self.mode & ACL_EXECUTE else "-"
154
155        return f"{typ}:{qual}:{r}{w}{x}"
156
157
158class ACL(object):
159    def __init__(self, acl):
160        self.acl = acl
161
162    def __del__(self):
163        acl_free(self.acl)
164
165    def entries(self):
166        entry_id = ACL_FIRST_ENTRY
167        while True:
168            entry = acl_get_entry(self.acl, entry_id)
169            if entry is None:
170                break
171
172            permset = acl_get_permset(entry)
173
174            mode = 0
175            for m in (ACL_READ, ACL_WRITE, ACL_EXECUTE):
176                if acl_get_perm(permset, m):
177                    mode |= m
178
179            qualifier = None
180            tag = acl_get_tag_type(entry)
181
182            if tag == ACL_USER or tag == ACL_GROUP:
183                qual = acl_get_qualifier(entry)
184                qualifier = ctypes.cast(qual, ctypes.POINTER(ctypes.c_int))[0]
185
186            yield Entry(tag, qualifier, mode)
187
188            entry_id = ACL_NEXT_ENTRY
189
190    @classmethod
191    def from_path(cls, path, typ):
192        acl = acl_get_file(path, typ)
193        return cls(acl)
194
195
196def main():
197    import argparse
198    import pwd
199    import grp
200    from pathlib import Path
201
202    parser = argparse.ArgumentParser()
203    parser.add_argument("path", help="File Path", type=Path)
204
205    args = parser.parse_args()
206
207    acl = ACL.from_path(args.path, ACL_TYPE_ACCESS)
208    for entry in acl.entries():
209        print(str(entry))
210
211    return 0
212
213
214if __name__ == "__main__":
215    sys.exit(main())
216