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