1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include <errno.h>
4 #include <stdbool.h>
5 #include <sys/stat.h>
6 #include <sys/types.h>
7 
8 #include "acl-util.h"
9 #include "alloc-util.h"
10 #include "string-util.h"
11 #include "strv.h"
12 #include "user-util.h"
13 #include "util.h"
14 
acl_find_uid(acl_t acl,uid_t uid,acl_entry_t * ret_entry)15 int acl_find_uid(acl_t acl, uid_t uid, acl_entry_t *ret_entry) {
16         acl_entry_t i;
17         int r;
18 
19         assert(acl);
20         assert(uid_is_valid(uid));
21         assert(ret_entry);
22 
23         for (r = acl_get_entry(acl, ACL_FIRST_ENTRY, &i);
24              r > 0;
25              r = acl_get_entry(acl, ACL_NEXT_ENTRY, &i)) {
26 
27                 acl_tag_t tag;
28                 uid_t *u;
29                 bool b;
30 
31                 if (acl_get_tag_type(i, &tag) < 0)
32                         return -errno;
33 
34                 if (tag != ACL_USER)
35                         continue;
36 
37                 u = acl_get_qualifier(i);
38                 if (!u)
39                         return -errno;
40 
41                 b = *u == uid;
42                 acl_free(u);
43 
44                 if (b) {
45                         *ret_entry = i;
46                         return 1;
47                 }
48         }
49         if (r < 0)
50                 return -errno;
51 
52         *ret_entry = NULL;
53         return 0;
54 }
55 
calc_acl_mask_if_needed(acl_t * acl_p)56 int calc_acl_mask_if_needed(acl_t *acl_p) {
57         acl_entry_t i;
58         int r;
59         bool need = false;
60 
61         assert(acl_p);
62 
63         for (r = acl_get_entry(*acl_p, ACL_FIRST_ENTRY, &i);
64              r > 0;
65              r = acl_get_entry(*acl_p, ACL_NEXT_ENTRY, &i)) {
66                 acl_tag_t tag;
67 
68                 if (acl_get_tag_type(i, &tag) < 0)
69                         return -errno;
70 
71                 if (tag == ACL_MASK)
72                         return 0;
73 
74                 if (IN_SET(tag, ACL_USER, ACL_GROUP))
75                         need = true;
76         }
77         if (r < 0)
78                 return -errno;
79 
80         if (need && acl_calc_mask(acl_p) < 0)
81                 return -errno;
82 
83         return need;
84 }
85 
add_base_acls_if_needed(acl_t * acl_p,const char * path)86 int add_base_acls_if_needed(acl_t *acl_p, const char *path) {
87         acl_entry_t i;
88         int r;
89         bool have_user_obj = false, have_group_obj = false, have_other = false;
90         struct stat st;
91         _cleanup_(acl_freep) acl_t basic = NULL;
92 
93         assert(acl_p);
94 
95         for (r = acl_get_entry(*acl_p, ACL_FIRST_ENTRY, &i);
96              r > 0;
97              r = acl_get_entry(*acl_p, ACL_NEXT_ENTRY, &i)) {
98                 acl_tag_t tag;
99 
100                 if (acl_get_tag_type(i, &tag) < 0)
101                         return -errno;
102 
103                 if (tag == ACL_USER_OBJ)
104                         have_user_obj = true;
105                 else if (tag == ACL_GROUP_OBJ)
106                         have_group_obj = true;
107                 else if (tag == ACL_OTHER)
108                         have_other = true;
109                 if (have_user_obj && have_group_obj && have_other)
110                         return 0;
111         }
112         if (r < 0)
113                 return -errno;
114 
115         r = stat(path, &st);
116         if (r < 0)
117                 return -errno;
118 
119         basic = acl_from_mode(st.st_mode);
120         if (!basic)
121                 return -errno;
122 
123         for (r = acl_get_entry(basic, ACL_FIRST_ENTRY, &i);
124              r > 0;
125              r = acl_get_entry(basic, ACL_NEXT_ENTRY, &i)) {
126                 acl_tag_t tag;
127                 acl_entry_t dst;
128 
129                 if (acl_get_tag_type(i, &tag) < 0)
130                         return -errno;
131 
132                 if ((tag == ACL_USER_OBJ && have_user_obj) ||
133                     (tag == ACL_GROUP_OBJ && have_group_obj) ||
134                     (tag == ACL_OTHER && have_other))
135                         continue;
136 
137                 r = acl_create_entry(acl_p, &dst);
138                 if (r < 0)
139                         return -errno;
140 
141                 r = acl_copy_entry(dst, i);
142                 if (r < 0)
143                         return -errno;
144         }
145         if (r < 0)
146                 return -errno;
147         return 0;
148 }
149 
acl_search_groups(const char * path,char *** ret_groups)150 int acl_search_groups(const char *path, char ***ret_groups) {
151         _cleanup_strv_free_ char **g = NULL;
152         _cleanup_(acl_freep) acl_t acl = NULL;
153         bool ret = false;
154         acl_entry_t entry;
155         int r;
156 
157         assert(path);
158 
159         acl = acl_get_file(path, ACL_TYPE_DEFAULT);
160         if (!acl)
161                 return -errno;
162 
163         r = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry);
164         for (;;) {
165                 _cleanup_(acl_free_gid_tpp) gid_t *gid = NULL;
166                 acl_tag_t tag;
167 
168                 if (r < 0)
169                         return -errno;
170                 if (r == 0)
171                         break;
172 
173                 if (acl_get_tag_type(entry, &tag) < 0)
174                         return -errno;
175 
176                 if (tag != ACL_GROUP)
177                         goto next;
178 
179                 gid = acl_get_qualifier(entry);
180                 if (!gid)
181                         return -errno;
182 
183                 if (in_gid(*gid) > 0) {
184                         if (!ret_groups)
185                                 return true;
186 
187                         ret = true;
188                 }
189 
190                 if (ret_groups) {
191                         char *name;
192 
193                         name = gid_to_name(*gid);
194                         if (!name)
195                                 return -ENOMEM;
196 
197                         r = strv_consume(&g, name);
198                         if (r < 0)
199                                 return r;
200                 }
201 
202         next:
203                 r = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry);
204         }
205 
206         if (ret_groups)
207                 *ret_groups = TAKE_PTR(g);
208 
209         return ret;
210 }
211 
parse_acl(const char * text,acl_t * acl_access,acl_t * acl_default,bool want_mask)212 int parse_acl(const char *text, acl_t *acl_access, acl_t *acl_default, bool want_mask) {
213         _cleanup_free_ char **a = NULL, **d = NULL; /* strings are not freed */
214         _cleanup_strv_free_ char **split = NULL;
215         int r = -EINVAL;
216         _cleanup_(acl_freep) acl_t a_acl = NULL, d_acl = NULL;
217 
218         split = strv_split(text, ",");
219         if (!split)
220                 return -ENOMEM;
221 
222         STRV_FOREACH(entry, split) {
223                 char *p;
224 
225                 p = STARTSWITH_SET(*entry, "default:", "d:");
226                 if (p)
227                         r = strv_push(&d, p);
228                 else
229                         r = strv_push(&a, *entry);
230                 if (r < 0)
231                         return r;
232         }
233 
234         if (!strv_isempty(a)) {
235                 _cleanup_free_ char *join = NULL;
236 
237                 join = strv_join(a, ",");
238                 if (!join)
239                         return -ENOMEM;
240 
241                 a_acl = acl_from_text(join);
242                 if (!a_acl)
243                         return -errno;
244 
245                 if (want_mask) {
246                         r = calc_acl_mask_if_needed(&a_acl);
247                         if (r < 0)
248                                 return r;
249                 }
250         }
251 
252         if (!strv_isempty(d)) {
253                 _cleanup_free_ char *join = NULL;
254 
255                 join = strv_join(d, ",");
256                 if (!join)
257                         return -ENOMEM;
258 
259                 d_acl = acl_from_text(join);
260                 if (!d_acl)
261                         return -errno;
262 
263                 if (want_mask) {
264                         r = calc_acl_mask_if_needed(&d_acl);
265                         if (r < 0)
266                                 return r;
267                 }
268         }
269 
270         *acl_access = TAKE_PTR(a_acl);
271         *acl_default = TAKE_PTR(d_acl);
272 
273         return 0;
274 }
275 
acl_entry_equal(acl_entry_t a,acl_entry_t b)276 static int acl_entry_equal(acl_entry_t a, acl_entry_t b) {
277         acl_tag_t tag_a, tag_b;
278 
279         if (acl_get_tag_type(a, &tag_a) < 0)
280                 return -errno;
281 
282         if (acl_get_tag_type(b, &tag_b) < 0)
283                 return -errno;
284 
285         if (tag_a != tag_b)
286                 return false;
287 
288         switch (tag_a) {
289         case ACL_USER_OBJ:
290         case ACL_GROUP_OBJ:
291         case ACL_MASK:
292         case ACL_OTHER:
293                 /* can have only one of those */
294                 return true;
295         case ACL_USER: {
296                 _cleanup_(acl_free_uid_tpp) uid_t *uid_a = NULL, *uid_b = NULL;
297 
298                 uid_a = acl_get_qualifier(a);
299                 if (!uid_a)
300                         return -errno;
301 
302                 uid_b = acl_get_qualifier(b);
303                 if (!uid_b)
304                         return -errno;
305 
306                 return *uid_a == *uid_b;
307         }
308         case ACL_GROUP: {
309                 _cleanup_(acl_free_gid_tpp) gid_t *gid_a = NULL, *gid_b = NULL;
310 
311                 gid_a = acl_get_qualifier(a);
312                 if (!gid_a)
313                         return -errno;
314 
315                 gid_b = acl_get_qualifier(b);
316                 if (!gid_b)
317                         return -errno;
318 
319                 return *gid_a == *gid_b;
320         }
321         default:
322                 assert_not_reached();
323         }
324 }
325 
find_acl_entry(acl_t acl,acl_entry_t entry,acl_entry_t * out)326 static int find_acl_entry(acl_t acl, acl_entry_t entry, acl_entry_t *out) {
327         acl_entry_t i;
328         int r;
329 
330         for (r = acl_get_entry(acl, ACL_FIRST_ENTRY, &i);
331              r > 0;
332              r = acl_get_entry(acl, ACL_NEXT_ENTRY, &i)) {
333 
334                 r = acl_entry_equal(i, entry);
335                 if (r < 0)
336                         return r;
337                 if (r > 0) {
338                         *out = i;
339                         return 1;
340                 }
341         }
342         if (r < 0)
343                 return -errno;
344         return 0;
345 }
346 
acls_for_file(const char * path,acl_type_t type,acl_t new,acl_t * acl)347 int acls_for_file(const char *path, acl_type_t type, acl_t new, acl_t *acl) {
348         _cleanup_(acl_freep) acl_t old;
349         acl_entry_t i;
350         int r;
351 
352         old = acl_get_file(path, type);
353         if (!old)
354                 return -errno;
355 
356         for (r = acl_get_entry(new, ACL_FIRST_ENTRY, &i);
357              r > 0;
358              r = acl_get_entry(new, ACL_NEXT_ENTRY, &i)) {
359 
360                 acl_entry_t j;
361 
362                 r = find_acl_entry(old, i, &j);
363                 if (r < 0)
364                         return r;
365                 if (r == 0)
366                         if (acl_create_entry(&old, &j) < 0)
367                                 return -errno;
368 
369                 if (acl_copy_entry(j, i) < 0)
370                         return -errno;
371         }
372         if (r < 0)
373                 return -errno;
374 
375         *acl = TAKE_PTR(old);
376 
377         return 0;
378 }
379 
380 /* POSIX says that ACL_{READ,WRITE,EXECUTE} don't have to be bitmasks. But that is a natural thing to do and
381  * all extant implementations do it. Let's make sure that we fail verbosely in the (imho unlikely) scenario
382  * that we get a new implementation that does not satisfy this. */
383 assert_cc(!(ACL_READ & ACL_WRITE));
384 assert_cc(!(ACL_WRITE & ACL_EXECUTE));
385 assert_cc(!(ACL_EXECUTE & ACL_READ));
386 assert_cc((unsigned) ACL_READ == ACL_READ);
387 assert_cc((unsigned) ACL_WRITE == ACL_WRITE);
388 assert_cc((unsigned) ACL_EXECUTE == ACL_EXECUTE);
389 
fd_add_uid_acl_permission(int fd,uid_t uid,unsigned mask)390 int fd_add_uid_acl_permission(
391                 int fd,
392                 uid_t uid,
393                 unsigned mask) {
394 
395         _cleanup_(acl_freep) acl_t acl = NULL;
396         acl_permset_t permset;
397         acl_entry_t entry;
398         int r;
399 
400         /* Adds an ACL entry for the specified file to allow the indicated access to the specified
401          * user. Operates purely incrementally. */
402 
403         assert(fd >= 0);
404         assert(uid_is_valid(uid));
405 
406         acl = acl_get_fd(fd);
407         if (!acl)
408                 return -errno;
409 
410         r = acl_find_uid(acl, uid, &entry);
411         if (r <= 0) {
412                 if (acl_create_entry(&acl, &entry) < 0 ||
413                     acl_set_tag_type(entry, ACL_USER) < 0 ||
414                     acl_set_qualifier(entry, &uid) < 0)
415                         return -errno;
416         }
417 
418         if (acl_get_permset(entry, &permset) < 0)
419                 return -errno;
420 
421         if ((mask & ACL_READ) && acl_add_perm(permset, ACL_READ) < 0)
422                 return -errno;
423         if ((mask & ACL_WRITE) && acl_add_perm(permset, ACL_WRITE) < 0)
424                 return -errno;
425         if ((mask & ACL_EXECUTE) && acl_add_perm(permset, ACL_EXECUTE) < 0)
426                 return -errno;
427 
428         r = calc_acl_mask_if_needed(&acl);
429         if (r < 0)
430                 return r;
431 
432         if (acl_set_fd(fd, acl) < 0)
433                 return -errno;
434 
435         return 0;
436 }
437