]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/acl-util.c
ci: enable arm64 runner for build/unit jobs
[thirdparty/systemd.git] / src / shared / acl-util.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <sys/stat.h>
4
5 #include "acl-util.h"
6 #include "alloc-util.h"
7 #include "errno-util.h"
8 #include "extract-word.h"
9 #include "string-util.h"
10 #include "strv.h"
11 #include "user-util.h"
12
13 #if HAVE_ACL
14
15 static 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
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
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 assert(path);
95
96 for (r = acl_get_entry(*acl_p, ACL_FIRST_ENTRY, &i);
97 r > 0;
98 r = acl_get_entry(*acl_p, ACL_NEXT_ENTRY, &i)) {
99 acl_tag_t tag;
100
101 if (acl_get_tag_type(i, &tag) < 0)
102 return -errno;
103
104 if (tag == ACL_USER_OBJ)
105 have_user_obj = true;
106 else if (tag == ACL_GROUP_OBJ)
107 have_group_obj = true;
108 else if (tag == ACL_OTHER)
109 have_other = true;
110 if (have_user_obj && have_group_obj && have_other)
111 return 0;
112 }
113 if (r < 0)
114 return -errno;
115
116 r = stat(path, &st);
117 if (r < 0)
118 return -errno;
119
120 basic = acl_from_mode(st.st_mode);
121 if (!basic)
122 return -errno;
123
124 for (r = acl_get_entry(basic, ACL_FIRST_ENTRY, &i);
125 r > 0;
126 r = acl_get_entry(basic, ACL_NEXT_ENTRY, &i)) {
127 acl_tag_t tag;
128 acl_entry_t dst;
129
130 if (acl_get_tag_type(i, &tag) < 0)
131 return -errno;
132
133 if ((tag == ACL_USER_OBJ && have_user_obj) ||
134 (tag == ACL_GROUP_OBJ && have_group_obj) ||
135 (tag == ACL_OTHER && have_other))
136 continue;
137
138 r = acl_create_entry(acl_p, &dst);
139 if (r < 0)
140 return -errno;
141
142 r = acl_copy_entry(dst, i);
143 if (r < 0)
144 return -errno;
145 }
146 if (r < 0)
147 return -errno;
148 return 0;
149 }
150
151 int acl_search_groups(const char *path, char ***ret_groups) {
152 _cleanup_strv_free_ char **g = NULL;
153 _cleanup_(acl_freep) acl_t acl = NULL;
154 bool ret = false;
155 acl_entry_t entry;
156 int r;
157
158 assert(path);
159
160 acl = acl_get_file(path, ACL_TYPE_DEFAULT);
161 if (!acl)
162 return -errno;
163
164 r = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry);
165 for (;;) {
166 _cleanup_(acl_free_gid_tpp) gid_t *gid = NULL;
167 acl_tag_t tag;
168
169 if (r < 0)
170 return -errno;
171 if (r == 0)
172 break;
173
174 if (acl_get_tag_type(entry, &tag) < 0)
175 return -errno;
176
177 if (tag != ACL_GROUP)
178 goto next;
179
180 gid = acl_get_qualifier(entry);
181 if (!gid)
182 return -errno;
183
184 if (in_gid(*gid) > 0) {
185 if (!ret_groups)
186 return true;
187
188 ret = true;
189 }
190
191 if (ret_groups) {
192 char *name;
193
194 name = gid_to_name(*gid);
195 if (!name)
196 return -ENOMEM;
197
198 r = strv_consume(&g, name);
199 if (r < 0)
200 return r;
201 }
202
203 next:
204 r = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry);
205 }
206
207 if (ret_groups)
208 *ret_groups = TAKE_PTR(g);
209
210 return ret;
211 }
212
213 int parse_acl(
214 const char *text,
215 acl_t *ret_acl_access,
216 acl_t *ret_acl_access_exec, /* extra rules to apply to inodes subject to uppercase X handling */
217 acl_t *ret_acl_default,
218 bool want_mask) {
219
220 _cleanup_strv_free_ char **a = NULL, **e = NULL, **d = NULL, **split = NULL;
221 _cleanup_(acl_freep) acl_t a_acl = NULL, e_acl = NULL, d_acl = NULL;
222 int r;
223
224 assert(text);
225 assert(ret_acl_access);
226 assert(ret_acl_access_exec);
227 assert(ret_acl_default);
228
229 split = strv_split(text, ",");
230 if (!split)
231 return -ENOMEM;
232
233 STRV_FOREACH(entry, split) {
234 _cleanup_strv_free_ char **entry_split = NULL;
235 _cleanup_free_ char *entry_join = NULL;
236 int n;
237
238 n = strv_split_full(&entry_split, *entry, ":", EXTRACT_DONT_COALESCE_SEPARATORS|EXTRACT_RETAIN_ESCAPE);
239 if (n < 0)
240 return n;
241
242 if (n < 3 || n > 4)
243 return -EINVAL;
244
245 string_replace_char(entry_split[n-1], 'X', 'x');
246
247 if (n == 4) {
248 if (!STR_IN_SET(entry_split[0], "default", "d"))
249 return -EINVAL;
250
251 entry_join = strv_join(entry_split + 1, ":");
252 if (!entry_join)
253 return -ENOMEM;
254
255 r = strv_consume(&d, TAKE_PTR(entry_join));
256 } else { /* n == 3 */
257 entry_join = strv_join(entry_split, ":");
258 if (!entry_join)
259 return -ENOMEM;
260
261 if (!streq(*entry, entry_join))
262 r = strv_consume(&e, TAKE_PTR(entry_join));
263 else
264 r = strv_consume(&a, TAKE_PTR(entry_join));
265 }
266 if (r < 0)
267 return r;
268 }
269
270 if (!strv_isempty(a)) {
271 _cleanup_free_ char *join = NULL;
272
273 join = strv_join(a, ",");
274 if (!join)
275 return -ENOMEM;
276
277 a_acl = acl_from_text(join);
278 if (!a_acl)
279 return -errno;
280
281 if (want_mask) {
282 r = calc_acl_mask_if_needed(&a_acl);
283 if (r < 0)
284 return r;
285 }
286 }
287
288 if (!strv_isempty(e)) {
289 _cleanup_free_ char *join = NULL;
290
291 join = strv_join(e, ",");
292 if (!join)
293 return -ENOMEM;
294
295 e_acl = acl_from_text(join);
296 if (!e_acl)
297 return -errno;
298
299 /* The mask must be calculated after deciding whether the execute bit should be set. */
300 }
301
302 if (!strv_isempty(d)) {
303 _cleanup_free_ char *join = NULL;
304
305 join = strv_join(d, ",");
306 if (!join)
307 return -ENOMEM;
308
309 d_acl = acl_from_text(join);
310 if (!d_acl)
311 return -errno;
312
313 if (want_mask) {
314 r = calc_acl_mask_if_needed(&d_acl);
315 if (r < 0)
316 return r;
317 }
318 }
319
320 *ret_acl_access = TAKE_PTR(a_acl);
321 *ret_acl_access_exec = TAKE_PTR(e_acl);
322 *ret_acl_default = TAKE_PTR(d_acl);
323
324 return 0;
325 }
326
327 static int acl_entry_equal(acl_entry_t a, acl_entry_t b) {
328 acl_tag_t tag_a, tag_b;
329
330 if (acl_get_tag_type(a, &tag_a) < 0)
331 return -errno;
332
333 if (acl_get_tag_type(b, &tag_b) < 0)
334 return -errno;
335
336 if (tag_a != tag_b)
337 return false;
338
339 switch (tag_a) {
340 case ACL_USER_OBJ:
341 case ACL_GROUP_OBJ:
342 case ACL_MASK:
343 case ACL_OTHER:
344 /* can have only one of those */
345 return true;
346 case ACL_USER: {
347 _cleanup_(acl_free_uid_tpp) uid_t *uid_a = NULL, *uid_b = NULL;
348
349 uid_a = acl_get_qualifier(a);
350 if (!uid_a)
351 return -errno;
352
353 uid_b = acl_get_qualifier(b);
354 if (!uid_b)
355 return -errno;
356
357 return *uid_a == *uid_b;
358 }
359 case ACL_GROUP: {
360 _cleanup_(acl_free_gid_tpp) gid_t *gid_a = NULL, *gid_b = NULL;
361
362 gid_a = acl_get_qualifier(a);
363 if (!gid_a)
364 return -errno;
365
366 gid_b = acl_get_qualifier(b);
367 if (!gid_b)
368 return -errno;
369
370 return *gid_a == *gid_b;
371 }
372 default:
373 assert_not_reached();
374 }
375 }
376
377 static int find_acl_entry(acl_t acl, acl_entry_t entry, acl_entry_t *ret) {
378 acl_entry_t i;
379 int r;
380
381 for (r = acl_get_entry(acl, ACL_FIRST_ENTRY, &i);
382 r > 0;
383 r = acl_get_entry(acl, ACL_NEXT_ENTRY, &i)) {
384
385 r = acl_entry_equal(i, entry);
386 if (r < 0)
387 return r;
388 if (r > 0) {
389 if (ret)
390 *ret = i;
391 return 0;
392 }
393 }
394 if (r < 0)
395 return -errno;
396
397 return -ENOENT;
398 }
399
400 int acls_for_file(const char *path, acl_type_t type, acl_t acl, acl_t *ret) {
401 _cleanup_(acl_freep) acl_t applied = NULL;
402 acl_entry_t i;
403 int r;
404
405 assert(path);
406
407 applied = acl_get_file(path, type);
408 if (!applied)
409 return -errno;
410
411 for (r = acl_get_entry(acl, ACL_FIRST_ENTRY, &i);
412 r > 0;
413 r = acl_get_entry(acl, ACL_NEXT_ENTRY, &i)) {
414
415 acl_entry_t j;
416
417 r = find_acl_entry(applied, i, &j);
418 if (r == -ENOENT) {
419 if (acl_create_entry(&applied, &j) < 0)
420 return -errno;
421 } else if (r < 0)
422 return r;
423
424 if (acl_copy_entry(j, i) < 0)
425 return -errno;
426 }
427 if (r < 0)
428 return -errno;
429
430 if (ret)
431 *ret = TAKE_PTR(applied);
432
433 return 0;
434 }
435
436 /* POSIX says that ACL_{READ,WRITE,EXECUTE} don't have to be bitmasks. But that is a natural thing to do and
437 * all extant implementations do it. Let's make sure that we fail verbosely in the (imho unlikely) scenario
438 * that we get a new implementation that does not satisfy this. */
439 assert_cc(!(ACL_READ & ACL_WRITE));
440 assert_cc(!(ACL_WRITE & ACL_EXECUTE));
441 assert_cc(!(ACL_EXECUTE & ACL_READ));
442 assert_cc((unsigned) ACL_READ == ACL_READ);
443 assert_cc((unsigned) ACL_WRITE == ACL_WRITE);
444 assert_cc((unsigned) ACL_EXECUTE == ACL_EXECUTE);
445
446 int fd_add_uid_acl_permission(
447 int fd,
448 uid_t uid,
449 unsigned mask) {
450
451 _cleanup_(acl_freep) acl_t acl = NULL;
452 acl_permset_t permset;
453 acl_entry_t entry;
454 int r;
455
456 /* Adds an ACL entry for the specified file to allow the indicated access to the specified
457 * user. Operates purely incrementally. */
458
459 assert(fd >= 0);
460 assert(uid_is_valid(uid));
461
462 acl = acl_get_fd(fd);
463 if (!acl)
464 return -errno;
465
466 r = acl_find_uid(acl, uid, &entry);
467 if (r <= 0) {
468 if (acl_create_entry(&acl, &entry) < 0 ||
469 acl_set_tag_type(entry, ACL_USER) < 0 ||
470 acl_set_qualifier(entry, &uid) < 0)
471 return -errno;
472 }
473
474 if (acl_get_permset(entry, &permset) < 0)
475 return -errno;
476
477 if ((mask & ACL_READ) && acl_add_perm(permset, ACL_READ) < 0)
478 return -errno;
479 if ((mask & ACL_WRITE) && acl_add_perm(permset, ACL_WRITE) < 0)
480 return -errno;
481 if ((mask & ACL_EXECUTE) && acl_add_perm(permset, ACL_EXECUTE) < 0)
482 return -errno;
483
484 r = calc_acl_mask_if_needed(&acl);
485 if (r < 0)
486 return r;
487
488 if (acl_set_fd(fd, acl) < 0)
489 return -errno;
490
491 return 0;
492 }
493
494 int fd_acl_make_read_only(int fd) {
495 _cleanup_(acl_freep) acl_t acl = NULL;
496 bool changed = false;
497 acl_entry_t i;
498 int r;
499
500 assert(fd >= 0);
501
502 /* Safely drops all W bits from all relevant ACL entries of the file, without changing entries which
503 * are masked by the ACL mask */
504
505 acl = acl_get_fd(fd);
506 if (!acl) {
507
508 if (!ERRNO_IS_NOT_SUPPORTED(errno))
509 return -errno;
510
511 /* No ACLs? Then just update the regular mode_t */
512 return fd_acl_make_read_only_fallback(fd);
513 }
514
515 for (r = acl_get_entry(acl, ACL_FIRST_ENTRY, &i);
516 r > 0;
517 r = acl_get_entry(acl, ACL_NEXT_ENTRY, &i)) {
518 acl_permset_t permset;
519 acl_tag_t tag;
520 int b;
521
522 if (acl_get_tag_type(i, &tag) < 0)
523 return -errno;
524
525 /* These three control the x bits overall (as ACL_MASK affects all remaining tags) */
526 if (!IN_SET(tag, ACL_USER_OBJ, ACL_MASK, ACL_OTHER))
527 continue;
528
529 if (acl_get_permset(i, &permset) < 0)
530 return -errno;
531
532 b = acl_get_perm(permset, ACL_WRITE);
533 if (b < 0)
534 return -errno;
535
536 if (b) {
537 if (acl_delete_perm(permset, ACL_WRITE) < 0)
538 return -errno;
539
540 changed = true;
541 }
542 }
543 if (r < 0)
544 return -errno;
545
546 if (!changed)
547 return 0;
548
549 if (acl_set_fd(fd, acl) < 0) {
550 if (!ERRNO_IS_NOT_SUPPORTED(errno))
551 return -errno;
552
553 return fd_acl_make_read_only_fallback(fd);
554 }
555
556 return 1;
557 }
558
559 int fd_acl_make_writable(int fd) {
560 _cleanup_(acl_freep) acl_t acl = NULL;
561 acl_entry_t i;
562 int r;
563
564 /* Safely adds the writable bit to the owner's ACL entry of this inode. (And only the owner's! – This
565 * not the obvious inverse of fd_acl_make_read_only() hence!) */
566
567 acl = acl_get_fd(fd);
568 if (!acl) {
569 if (!ERRNO_IS_NOT_SUPPORTED(errno))
570 return -errno;
571
572 /* No ACLs? Then just update the regular mode_t */
573 return fd_acl_make_writable_fallback(fd);
574 }
575
576 for (r = acl_get_entry(acl, ACL_FIRST_ENTRY, &i);
577 r > 0;
578 r = acl_get_entry(acl, ACL_NEXT_ENTRY, &i)) {
579 acl_permset_t permset;
580 acl_tag_t tag;
581 int b;
582
583 if (acl_get_tag_type(i, &tag) < 0)
584 return -errno;
585
586 if (tag != ACL_USER_OBJ)
587 continue;
588
589 if (acl_get_permset(i, &permset) < 0)
590 return -errno;
591
592 b = acl_get_perm(permset, ACL_WRITE);
593 if (b < 0)
594 return -errno;
595
596 if (b)
597 return 0; /* Already set? Then there's nothing to do. */
598
599 if (acl_add_perm(permset, ACL_WRITE) < 0)
600 return -errno;
601
602 break;
603 }
604 if (r < 0)
605 return -errno;
606
607 if (acl_set_fd(fd, acl) < 0) {
608 if (!ERRNO_IS_NOT_SUPPORTED(errno))
609 return -errno;
610
611 return fd_acl_make_writable_fallback(fd);
612 }
613
614 return 1;
615 }
616 #endif
617
618 int fd_acl_make_read_only_fallback(int fd) {
619 struct stat st;
620
621 assert(fd >= 0);
622
623 if (fstat(fd, &st) < 0)
624 return -errno;
625
626 if ((st.st_mode & 0222) == 0)
627 return 0;
628
629 if (fchmod(fd, st.st_mode & 0555) < 0)
630 return -errno;
631
632 return 1;
633 }
634
635 int fd_acl_make_writable_fallback(int fd) {
636 struct stat st;
637
638 assert(fd >= 0);
639
640 if (fstat(fd, &st) < 0)
641 return -errno;
642
643 if ((st.st_mode & 0200) != 0) /* already set */
644 return 0;
645
646 if (fchmod(fd, (st.st_mode & 07777) | 0200) < 0)
647 return -errno;
648
649 return 1;
650 }