]>
Commit | Line | Data |
---|---|---|
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 | } |