1 /*
2 Copyright 2021 Northern.tech AS
3
4 This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation; version 3.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
18
19 To the extent this program is licensed as part of the Enterprise
20 versions of CFEngine, the applicable Commercial Open Source License
21 (COSL) may apply to this file if you as a licensee so wish it. See
22 included file COSL.txt.
23 */
24
25 #include <cf3.defs.h>
26
27 #include <actuator.h>
28 #include <verify_acl.h>
29 #include <acl_posix.h>
30 #include <promises.h>
31 #include <files_names.h>
32 #include <misc_lib.h>
33 #include <rlist.h>
34 #include <eval_context.h>
35
36 #ifdef HAVE_ACL_H
37 # include <acl.h>
38 #endif
39
40 #ifdef HAVE_SYS_ACL_H
41 # include <sys/acl.h>
42 #endif
43
44 #ifdef HAVE_ACL_LIBACL_H
45 # include <acl/libacl.h>
46 #endif
47
48 #ifdef HAVE_LIBACL
49
50 static bool CheckPosixLinuxAccessACEs(EvalContext *ctx, Rlist *aces, AclMethod method, const char *file_path,
51 const Attributes *a, const Promise *pp, PromiseResult *result);
52 static bool CheckPosixLinuxDefaultACEs(EvalContext *ctx, Rlist *aces, AclMethod method, AclDefault acl_default,
53 const char *file_path, const Attributes *a, const Promise *pp, PromiseResult *result);
54 static bool CheckPosixLinuxACEs(EvalContext *ctx, Rlist *aces, AclMethod method, const char *file_path, acl_type_t acl_type, const Attributes *a,
55 const Promise *pp, PromiseResult *result);
56 static bool CheckDefaultEqualsAccessACL(EvalContext *ctx, const char *file_path, const Attributes *a, const Promise *pp, PromiseResult *result);
57 static bool CheckDefaultClearACL(EvalContext *ctx, const char *file_path, const Attributes *a, const Promise *pp, PromiseResult *result);
58 static bool ParseEntityPosixLinux(char **str, acl_entry_t ace, int *is_mask);
59 static bool ParseModePosixLinux(char *mode, acl_permset_t old_perms);
60 static acl_entry_t FindACE(acl_t acl, acl_entry_t ace_find);
61 static int ACLEquals(acl_t first, acl_t second);
62 static int ACECount(acl_t acl);
63 static int PermsetEquals(acl_permset_t first, acl_permset_t second);
64
65
CheckPosixLinuxACL(EvalContext * ctx,const char * file_path,Acl acl,const Attributes * a,const Promise * pp)66 PromiseResult CheckPosixLinuxACL(EvalContext *ctx, const char *file_path, Acl acl, const Attributes *a, const Promise *pp)
67 {
68 assert(a != NULL);
69 PromiseResult result = PROMISE_RESULT_NOOP;
70
71 if (!CheckPosixLinuxAccessACEs(ctx, acl.acl_entries, acl.acl_method, file_path, a, pp, &result))
72 {
73 PromiseRef(LOG_LEVEL_ERR, pp);
74 return result;
75 }
76
77 if (IsDir(ToChangesPath(file_path)))
78 {
79 if (!CheckPosixLinuxDefaultACEs(ctx, acl.acl_default_entries, acl.acl_method, acl.acl_default,
80 file_path, a, pp, &result))
81 {
82 PromiseRef(LOG_LEVEL_ERR, pp);
83 return result;
84 }
85 }
86 return result;
87 }
88
CheckPosixLinuxAccessACEs(EvalContext * ctx,Rlist * aces,AclMethod method,const char * file_path,const Attributes * a,const Promise * pp,PromiseResult * result)89 static bool CheckPosixLinuxAccessACEs(EvalContext *ctx, Rlist *aces, AclMethod method, const char *file_path,
90 const Attributes *a, const Promise *pp, PromiseResult *result)
91 {
92 assert(a != NULL);
93 return CheckPosixLinuxACEs(ctx, aces, method, file_path, ACL_TYPE_ACCESS, a, pp, result);
94 }
95
CheckPosixLinuxDefaultACEs(EvalContext * ctx,Rlist * aces,AclMethod method,AclDefault acl_default,const char * file_path,const Attributes * a,const Promise * pp,PromiseResult * result)96 static bool CheckPosixLinuxDefaultACEs(EvalContext *ctx, Rlist *aces, AclMethod method, AclDefault acl_default,
97 const char *file_path, const Attributes *a, const Promise *pp, PromiseResult *result)
98 {
99 assert(a != NULL);
100 bool retval;
101
102 switch (acl_default)
103 {
104 case ACL_DEFAULT_NO_CHANGE: // no change always succeeds
105
106 RecordNoChange(ctx, pp, a, "No change required for '%s' with acl_default=nochange", file_path);
107 retval = true;
108 break;
109
110 case ACL_DEFAULT_SPECIFY: // default ACL is specified in promise
111
112 /* CheckPosixLinuxACEs() records changes and updates 'result' */
113 retval = CheckPosixLinuxACEs(ctx, aces, method, file_path, ACL_TYPE_DEFAULT, a, pp, result);
114 break;
115
116 case ACL_DEFAULT_ACCESS: // default ACL should be the same as access ACL
117
118 retval = CheckDefaultEqualsAccessACL(ctx, file_path, a, pp, result);
119 break;
120
121 case ACL_DEFAULT_CLEAR: // default ACL should be empty
122
123 retval = CheckDefaultClearACL(ctx, file_path, a, pp, result);
124 break;
125
126 default: // unknown inheritance policy
127 Log(LOG_LEVEL_ERR, "Unknown ACL inheritance policy - shouldn't happen");
128 debug_abort_if_reached();
129 retval = false;
130 break;
131 }
132
133 return retval;
134 }
135
136 /**
137 * Takes as input CFEngine-syntax ACEs and a path to a file. Checks if the
138 * CFEngine-syntax ACL translates to the POSIX Linux ACL set on the given
139 * file. If it doesn't, the ACL on the file is updated.
140 */
CheckPosixLinuxACEs(EvalContext * ctx,Rlist * aces,AclMethod method,const char * file_path,acl_type_t acl_type,const Attributes * a,const Promise * pp,PromiseResult * result)141 static bool CheckPosixLinuxACEs(EvalContext *ctx, Rlist *aces, AclMethod method, const char *file_path, acl_type_t acl_type,
142 const Attributes *a, const Promise *pp, PromiseResult *result)
143 {
144 assert(a != NULL);
145 acl_t acl_existing;
146 acl_t acl_new;
147 acl_t acl_tmp;
148 acl_entry_t ace_parsed;
149 acl_entry_t ace_current;
150 acl_permset_t perms;
151 char *cf_ace;
152 int retv;
153 int has_mask;
154 Rlist *rp;
155 char *acl_type_str;
156 char *acl_text_str;
157
158 acl_new = NULL;
159 acl_existing = NULL;
160 acl_tmp = NULL;
161 has_mask = false;
162
163 acl_type_str = acl_type == ACL_TYPE_ACCESS ? "Access" : "Default";
164
165 const char *changes_path = file_path;
166 if (ChrootChanges())
167 {
168 changes_path = ToChangesChroot(file_path);
169 }
170
171 // read existing acl
172
173 if ((acl_existing = acl_get_file(changes_path, acl_type)) == NULL)
174 {
175 RecordFailure(ctx, pp, a,
176 "No %s ACL for '%s' could be read. (acl_get_file: %s)",
177 acl_type_str, file_path, GetErrorStr());
178 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
179 return false;
180 }
181
182 // allocate memory for temp ace (it needs to reside in a temp acl)
183
184 if ((acl_tmp = acl_init(1)) == NULL)
185 {
186 RecordFailure(ctx, pp, a,
187 "New ACL could not be allocated (acl_init: %s)",
188 GetErrorStr());
189 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
190 acl_free(acl_existing);
191 return false;
192 }
193
194 if (acl_create_entry(&acl_tmp, &ace_parsed) != 0)
195 {
196 RecordFailure(ctx, pp, a,
197 "New ACL could not be allocated (acl_create_entry: %s)", GetErrorStr());
198 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
199 acl_free(acl_existing);
200 acl_free(acl_tmp);
201 return false;
202 }
203
204 // copy existing aces if we are appending
205
206 if (method == ACL_METHOD_APPEND)
207 {
208
209 if ((acl_new = acl_dup(acl_existing)) == NULL)
210 {
211 RecordFailure(ctx, pp, a,
212 "Error copying existing ACL (acl_dup: %s)", GetErrorStr());
213 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
214 acl_free(acl_existing);
215 acl_free(acl_tmp);
216 return false;
217 }
218 }
219 else // overwrite existing acl
220 {
221 if ((acl_new = acl_init(5)) == NULL) // TODO: Always OK with 5 here ?
222 {
223 RecordFailure(ctx, pp, a,
224 "New ACL could not be allocated (acl_init: %s)", GetErrorStr());
225 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
226 acl_free(acl_existing);
227 acl_free(acl_tmp);
228 return false;
229 }
230 }
231
232 for (rp = aces; rp != NULL; rp = rp->next)
233 {
234 cf_ace = RlistScalarValue(rp);
235
236 if (!ParseEntityPosixLinux(&cf_ace, ace_parsed, &has_mask))
237 {
238 RecordFailure(ctx, pp, a, "ACL: Error parsing entity in '%s'", cf_ace);
239 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
240 acl_free(acl_existing);
241 acl_free(acl_tmp);
242 acl_free(acl_new);
243 return false;
244 }
245
246 // check if an ACE with this entity-type and id already exist in the Posix Linux ACL
247
248 ace_current = FindACE(acl_new, ace_parsed);
249
250 // create new entry in ACL if it did not exist
251
252 if (ace_current == NULL)
253 {
254 if (acl_create_entry(&acl_new, &ace_current) != 0)
255 {
256 RecordFailure(ctx, pp, a,
257 "Failed to allocate ace (acl_create_entry: %s)", GetErrorStr());
258 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
259 acl_free(acl_existing);
260 acl_free(acl_tmp);
261 acl_free(acl_new);
262 return false;
263 }
264
265 // copy parsed entity-type and id
266
267 if (acl_copy_entry(ace_current, ace_parsed) != 0)
268 {
269 RecordFailure(ctx, pp, a,
270 "Error copying Linux ACL entry for '%s' (acl_copy_entry: %s)",
271 file_path, GetErrorStr());
272 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
273 acl_free(acl_existing);
274 acl_free(acl_tmp);
275 acl_free(acl_new);
276 return false;
277 }
278
279 // clear ace_current's permissions to avoid ace_parsed from last
280 // loop iteration to be taken into account when applying mode below
281 if ((acl_get_permset(ace_current, &perms) != 0))
282 {
283 RecordFailure(ctx, pp, a,
284 "Error obtaining permission set for 'ace_current' (acl_get_permset: %s)",
285 GetErrorStr());
286 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
287 acl_free(acl_existing);
288 acl_free(acl_tmp);
289 acl_free(acl_new);
290 return false;
291 }
292
293 if (acl_clear_perms(perms) != 0)
294 {
295 RecordFailure(ctx, pp, a,
296 "Error clearing permission set for 'ace_current'. (acl_clear_perms: %s)",
297 GetErrorStr());
298 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
299 acl_free(acl_existing);
300 acl_free(acl_tmp);
301 acl_free(acl_new);
302 return false;
303 }
304 }
305
306 // mode string should be prefixed with an entry seperator
307
308 if (*cf_ace != ':')
309 {
310 RecordFailure(ctx, pp, a, "ACL: No separator before mode-string '%s'", cf_ace);
311 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
312 acl_free(acl_existing);
313 acl_free(acl_tmp);
314 acl_free(acl_new);
315 return false;
316 }
317
318 cf_ace += 1;
319
320 if (acl_get_permset(ace_current, &perms) != 0)
321 {
322 RecordFailure(ctx, pp, a,
323 "ACL: Error obtaining permission set for '%s'. (acl_get_permset: %s)",
324 cf_ace, GetErrorStr());
325 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
326 acl_free(acl_existing);
327 acl_free(acl_tmp);
328 acl_free(acl_new);
329 return false;
330 }
331
332 if (!ParseModePosixLinux(cf_ace, perms))
333 {
334 RecordFailure(ctx, pp, a, "ACL: Error parsing mode-string in '%s'", cf_ace);
335 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
336 acl_free(acl_existing);
337 acl_free(acl_tmp);
338 acl_free(acl_new);
339 return false;
340 }
341
342 // only allow permissions exist on posix acls, so we do
343 // not check what follows next
344 }
345
346 // if no mask exists, calculate one (or both?): run acl_calc_mask and add one
347 if (!has_mask)
348 {
349 if (acl_calc_mask(&acl_new) != 0)
350 {
351 RecordFailure(ctx, pp, a, "Error calculating new ACL mask");
352 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
353 acl_free(acl_existing);
354 acl_free(acl_tmp);
355 acl_free(acl_new);
356 return false;
357 }
358 }
359
360 if ((retv = ACLEquals(acl_existing, acl_new)) == -1)
361 {
362 RecordFailure(ctx, pp, a, "Error while comparing existing and new ACL, unable to repair");
363 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
364 acl_free(acl_existing);
365 acl_free(acl_tmp);
366 acl_free(acl_new);
367 return false;
368 }
369
370 if (retv == 1) // existing and new acl differ, update existing
371 {
372 if (MakingChanges(ctx, pp, a, result, "update ACL %s on file '%s'", acl_type_str, file_path))
373 {
374 int last = -1;
375 acl_text_str = acl_to_any_text(acl_new, NULL, ',', 0);
376 Log(LOG_LEVEL_DEBUG, "ACL: new acl is `%s'", acl_text_str);
377
378 if ((retv = acl_check(acl_new, &last)) != 0)
379 {
380 RecordFailure(ctx, pp, a, "Invalid ACL in '%s' at index %d (acl_check: %s)",
381 acl_text_str,
382 last,
383 acl_error(retv));
384 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
385 acl_free(acl_existing);
386 acl_free(acl_tmp);
387 acl_free(acl_new);
388 acl_free(acl_text_str);
389 return false;
390 }
391 if ((retv = acl_set_file(changes_path, acl_type, acl_new)) != 0)
392 {
393 RecordFailure(ctx, pp, a,
394 "Error setting new %s ACL(%s) on file '%s' (acl_set_file: %s), are required ACEs present ?",
395 acl_type_str,
396 acl_text_str,
397 file_path,
398 GetErrorStr());
399 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
400 acl_free(acl_existing);
401 acl_free(acl_tmp);
402 acl_free(acl_new);
403 acl_free(acl_text_str);
404 return false;
405 }
406 acl_free(acl_text_str);
407
408 RecordChange(ctx, pp, a, "%s ACL on '%s' successfully changed", acl_type_str, file_path);
409 *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
410 }
411 }
412 else
413 {
414 RecordNoChange(ctx, pp, a, "'%s' ACL on '%s' needs no modification.", acl_type_str, file_path);
415 }
416
417 acl_free(acl_existing);
418 acl_free(acl_new);
419 acl_free(acl_tmp);
420 return true;
421 }
422
423 /**
424 * Checks if the default ACL of the given file is the same as the access ACL. If
425 * not, the default ACL is set in this way.
426 *
427 * @return 0 on success and -1 on failure.
428 */
CheckDefaultEqualsAccessACL(EvalContext * ctx,const char * file_path,const Attributes * a,const Promise * pp,PromiseResult * result)429 static bool CheckDefaultEqualsAccessACL(EvalContext *ctx, const char *file_path, const Attributes *a, const Promise *pp, PromiseResult *result)
430 {
431 assert(a != NULL);
432 acl_t acl_access;
433 acl_t acl_default;
434 int equals;
435 bool retval = false;
436
437 const char *changes_path = file_path;
438 if (ChrootChanges())
439 {
440 changes_path = ToChangesChroot(file_path);
441 }
442
443 acl_access = NULL;
444 acl_default = NULL;
445
446 if ((acl_access = acl_get_file(changes_path, ACL_TYPE_ACCESS)) == NULL)
447 {
448 RecordFailure(ctx, pp, a, "Could not find an ACL for '%s'. (acl_get_file: %s)",
449 file_path, GetErrorStr());
450 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
451 return false;
452 }
453
454 acl_default = acl_get_file(changes_path, ACL_TYPE_DEFAULT);
455
456 if (acl_default == NULL)
457 {
458 RecordFailure(ctx, pp, a, "Could not find default ACL for '%s'. (acl_get_file: %s)",
459 file_path, GetErrorStr());
460 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
461 acl_free(acl_access);
462 return false;
463 }
464
465 equals = ACLEquals(acl_access, acl_default);
466
467 switch (equals)
468 {
469 case 0: // they equal, as desired
470 RecordNoChange(ctx, pp, a, "Default ACL on '%s' as promised", file_path);
471 retval = true;
472 break;
473
474 case 1: // set access ACL as default ACL
475
476 if (MakingChanges(ctx, pp, a, result, "copy default ACL on '%s' from access ACL", file_path))
477 {
478 if ((acl_set_file(changes_path, ACL_TYPE_DEFAULT, acl_access)) != 0)
479 {
480 RecordFailure(ctx, pp, a,
481 "Could not set default ACL to access on '%s'. (acl_set_file: %s)",
482 file_path, GetErrorStr());
483 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
484 acl_free(acl_access);
485 acl_free(acl_default);
486 return false;
487 }
488 RecordChange(ctx, pp, a,
489 "Default ACL on '%s' successfully copied from access ACL.",
490 file_path);
491 *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
492 retval = true;
493 }
494
495 break;
496
497 default:
498 RecordFailure(ctx, pp, a, "ACL: Unable to compare access and default ACEs");
499 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
500 retval = false;
501 }
502
503 acl_free(acl_access);
504 acl_free(acl_default);
505 return retval;
506 }
507
508 /*
509 Checks if the default ACL is empty. If not, it is cleared.
510 */
511
CheckDefaultClearACL(EvalContext * ctx,const char * file_path,const Attributes * a,const Promise * pp,PromiseResult * result)512 static bool CheckDefaultClearACL(EvalContext *ctx, const char *file_path, const Attributes *a, const Promise *pp, PromiseResult *result)
513 {
514 acl_t acl_existing;
515 acl_t acl_empty;
516 acl_entry_t ace_dummy;
517 int retv;
518 bool retval = false;
519
520 const char *changes_path = file_path;
521 if (ChrootChanges())
522 {
523 changes_path = ToChangesChroot(file_path);
524 }
525
526 acl_existing = NULL;
527 acl_empty = NULL;
528
529 if ((acl_existing = acl_get_file(changes_path, ACL_TYPE_DEFAULT)) == NULL)
530 {
531 RecordFailure(ctx, pp, a, "Unable to read default acl for '%s'. (acl_get_file: %s)", file_path, GetErrorStr());
532 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
533 return false;
534 }
535
536 retv = acl_get_entry(acl_existing, ACL_FIRST_ENTRY, &ace_dummy);
537
538 switch (retv)
539 {
540 case -1:
541 RecordFailure(ctx, pp, a, "Couldn't retrieve ACE for '%s'. (acl_get_entry: %s)",
542 file_path, GetErrorStr());
543 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
544 retval = false;
545 break;
546
547 case 0: // no entries, as desired
548 RecordNoChange(ctx, pp, a, "Default ACL on '%s' as promised", file_path);
549 retval = true;
550 break;
551
552 case 1: // entries exist, set empty ACL
553
554 if ((acl_empty = acl_init(0)) == NULL)
555 {
556 RecordFailure(ctx, pp, a, "Could not reinitialize ACL for '%s'. (acl_init: %s)",
557 file_path, GetErrorStr());
558 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
559 retval = false;
560 break;
561 }
562
563 if (MakingChanges(ctx, pp, a, result, "clean default ACL on '%s'", file_path))
564 {
565 if (acl_set_file(changes_path, ACL_TYPE_DEFAULT, acl_empty) != 0)
566 {
567 RecordFailure(ctx, pp, a, "Could not reset ACL for '%s'. (acl_set_file: %s)",
568 file_path, GetErrorStr());
569 *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
570 retval = false;
571 }
572
573 RecordChange(ctx, pp, a, "Default ACL on '%s' successfully cleared", file_path);
574 *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
575 retval = true;
576 }
577
578 break;
579
580 default:
581 retval = false;
582 }
583
584 acl_free(acl_empty);
585 acl_free(acl_existing);
586 return retval;
587 }
588
589 /*
590 Walks through the acl given as the first parameter, looking for an
591 ACE that has the same entity type and id as the ace in the second
592 parameter. If a match is found, then the matching ace is returned.
593 Else, NULL is returned.
594 */
595
FindACE(acl_t acl,acl_entry_t ace_find)596 static acl_entry_t FindACE(acl_t acl, acl_entry_t ace_find)
597 {
598 acl_entry_t ace_curr;
599 acl_tag_t tag_curr;
600 acl_tag_t tag_find;
601 id_t *id_curr;
602 id_t *id_find;
603 int more_aces;
604 int retv_tag;
605
606 id_find = NULL;
607
608 more_aces = acl_get_entry(acl, ACL_FIRST_ENTRY, &ace_curr);
609
610 if (more_aces == -1)
611 {
612 Log(LOG_LEVEL_ERR, "Error reading ACL. (acl_get_entry: %s)", GetErrorStr());
613 return NULL;
614 }
615 else if (more_aces == 0)
616 {
617 return NULL;
618 }
619
620 /* find the tag type and id we are looking for */
621
622 if (acl_get_tag_type(ace_find, &tag_find) != 0)
623 {
624 Log(LOG_LEVEL_ERR, "Error reading tag type. (acl_get_tag_type: %s)", GetErrorStr());
625 return NULL;
626 }
627
628 if (tag_find == ACL_USER || tag_find == ACL_GROUP)
629 {
630 id_find = acl_get_qualifier(ace_find);
631
632 if (id_find == NULL)
633 {
634 Log(LOG_LEVEL_ERR, "Error reading tag type. (acl_get_qualifier: %s)", GetErrorStr());
635 return NULL;
636 }
637 }
638
639 /* check if any of the aces match */
640
641 while (more_aces)
642 {
643 if ((retv_tag = acl_get_tag_type(ace_curr, &tag_curr)) != 0)
644 {
645 Log(LOG_LEVEL_ERR, "Unable to get tag type. (acl_get_tag_type: %s)", GetErrorStr());
646 acl_free(id_find);
647 return NULL;
648 }
649
650 if (tag_curr == tag_find)
651 {
652 if (id_find == NULL)
653 {
654 return ace_curr;
655 }
656
657 id_curr = acl_get_qualifier(ace_curr);
658
659 if (id_curr == NULL)
660 {
661 Log(LOG_LEVEL_ERR, "Couldn't extract qualifier. (acl_get_qualifier: %s)", GetErrorStr());
662 return NULL;
663 }
664
665 if (*id_curr == *id_find)
666 {
667 acl_free(id_find);
668 acl_free(id_curr);
669 return ace_curr;
670 }
671
672 acl_free(id_curr);
673 }
674
675 more_aces = acl_get_entry(acl, ACL_NEXT_ENTRY, &ace_curr);
676 }
677
678 if (id_find != NULL)
679 {
680 acl_free(id_find);
681 }
682
683 return NULL;
684 }
685
686 /*
687 Checks if the two ACLs contain equal ACEs and equally many.
688 Does not assume that the ACEs lie in the same order.
689 Returns 0 if so, and 1 if they differ, and -1 on error.
690 */
691
ACLEquals(acl_t first,acl_t second)692 static int ACLEquals(acl_t first, acl_t second)
693 {
694 acl_entry_t ace_first;
695 acl_entry_t ace_second;
696 acl_permset_t perms_first;
697 acl_permset_t perms_second;
698 int first_cnt;
699 int second_cnt;
700 int more_aces;
701 int retv_perms;
702
703 if ((first_cnt = ACECount(first)) == -1)
704 {
705 Log(LOG_LEVEL_ERR, "Couldn't count ACEs while comparing ACLs");
706 return -1;
707 }
708
709 if ((second_cnt = ACECount(second)) == -1)
710 {
711 Log(LOG_LEVEL_ERR, "Couldn't count ACEs while comparing ACLs");
712 return -1;
713 }
714
715 if (first_cnt != second_cnt)
716 {
717 return 1;
718 }
719
720 if (first_cnt == 0)
721 {
722 return 0;
723 }
724
725 // check that every ace of first acl exist in second acl
726
727 more_aces = acl_get_entry(first, ACL_FIRST_ENTRY, &ace_first);
728
729 if (more_aces != 1) // first must contain at least one entry
730 {
731 Log(LOG_LEVEL_ERR, "Unable to read ACE. (acl_get_entry: %s)", GetErrorStr());
732 return -1;
733 }
734
735 while (more_aces)
736 {
737 /* no ace in second match entity-type and id of first */
738
739 if ((ace_second = FindACE(second, ace_first)) == NULL)
740 {
741 return 1;
742 }
743
744 /* permissions must also match */
745
746 if (acl_get_permset(ace_first, &perms_first) != 0)
747 {
748 Log(LOG_LEVEL_ERR, "Unable to read permissions. (acl_get_permset: %s)", GetErrorStr());
749 return -1;
750 }
751
752 if (acl_get_permset(ace_second, &perms_second) != 0)
753 {
754 Log(LOG_LEVEL_ERR, "Unable to read permissions. (acl_get_permset: %s)", GetErrorStr());
755 return -1;
756 }
757
758 retv_perms = PermsetEquals(perms_first, perms_second);
759
760 if (retv_perms == -1)
761 {
762 return -1;
763 }
764 else if (retv_perms == 1) // permissions differ
765 {
766 return 1;
767 }
768
769 more_aces = acl_get_entry(first, ACL_NEXT_ENTRY, &ace_first);
770 }
771
772 return 0;
773 }
774
ACECount(acl_t acl)775 static int ACECount(acl_t acl)
776 {
777 int more_aces;
778 int count;
779 acl_entry_t ace;
780
781 count = 0;
782
783 more_aces = acl_get_entry(acl, ACL_FIRST_ENTRY, &ace);
784
785 if (more_aces <= 0)
786 {
787 return more_aces;
788 }
789
790 while (more_aces)
791 {
792 more_aces = acl_get_entry(acl, ACL_NEXT_ENTRY, &ace);
793 count++;
794 }
795
796 return count;
797 }
798
PermsetEquals(acl_permset_t first,acl_permset_t second)799 static int PermsetEquals(acl_permset_t first, acl_permset_t second)
800 {
801 acl_perm_t perms_avail[3] = { ACL_READ, ACL_WRITE, ACL_EXECUTE };
802 int first_set;
803 int second_set;
804 int i;
805
806 for (i = 0; i < 3; i++)
807 {
808 first_set = acl_get_perm(first, perms_avail[i]);
809
810 if (first_set == -1)
811 {
812 return -1;
813 }
814
815 second_set = acl_get_perm(second, perms_avail[i]);
816
817 if (second_set == -1)
818 {
819 return -1;
820 }
821
822 if (first_set != second_set)
823 {
824 return 1;
825 }
826 }
827
828 return 0;
829 }
830
831 /*
832 Takes a ':' or null-terminated string "entity-type:id", and
833 converts and stores these into a Posix Linux ace data structure
834 (with unset permissions). Sets is_mask to true if this is a mask entry.
835 */
836
ParseEntityPosixLinux(char ** str,acl_entry_t ace,int * is_mask)837 static bool ParseEntityPosixLinux(char **str, acl_entry_t ace, int *is_mask)
838 {
839 struct passwd *pwd;
840 struct group *grp;
841 acl_tag_t etype;
842 size_t idsz;
843 id_t id;
844 char *ids;
845 char *id_end;
846 bool result = true;
847 size_t i;
848
849 ids = NULL;
850
851 // TODO: Support numeric id in addition to (user/group) name ?
852
853 // Posix language: tag type, qualifier, permissions
854
855 if (strncmp(*str, "user:", 5) == 0)
856 {
857 *str += 5;
858
859 // create null-terminated string for entity id
860 id_end = strchr(*str, ':');
861
862 if (id_end == NULL) // entity id already null-terminated
863 {
864 idsz = strlen(*str);
865 }
866 else // copy entity-id to new null-terminated string
867 {
868 idsz = id_end - *str;
869 }
870
871 ids = xmalloc(idsz + 1);
872 for (i = 0; i < idsz; i++)
873 ids[i] = (*str)[i];
874 ids[idsz] = '\0';
875
876 *str += idsz;
877
878 // file object owner
879
880 if (strncmp(ids, "*", 2) == 0)
881 {
882 etype = ACL_USER_OBJ;
883 id = 0;
884 }
885 else
886 {
887 etype = ACL_USER;
888 pwd = getpwnam(ids);
889
890 if (pwd == NULL)
891 {
892 Log(LOG_LEVEL_ERR, "Couldn't find user id for '%s'. (getpwnnam: %s)", ids, GetErrorStr());
893 free(ids);
894 return false;
895 }
896
897 id = pwd->pw_uid;
898 }
899 }
900 else if (strncmp(*str, "group:", 6) == 0)
901 {
902 *str += 6;
903
904 // create null-terminated string for entity id
905 id_end = strchr(*str, ':');
906
907 if (id_end == NULL) // entity id already null-terminated
908 {
909 idsz = strlen(*str);
910 }
911 else // copy entity-id to new null-terminated string
912 {
913 idsz = id_end - *str;
914 }
915
916 ids = xmalloc(idsz + 1);
917 for (i = 0; i < idsz; i++)
918 ids[i] = (*str)[i];
919 ids[idsz] = '\0';
920
921 *str += idsz;
922
923 // file group
924 if (strncmp(ids, "*", 2) == 0)
925 {
926 etype = ACL_GROUP_OBJ;
927 id = 0; // TODO: Correct file group id ??
928 }
929 else
930 {
931 etype = ACL_GROUP;
932 grp = getgrnam(ids);
933
934 if (grp == NULL)
935 {
936 Log(LOG_LEVEL_ERR, "Error looking up group id for %s", ids);
937 free(ids);
938 return false;
939 }
940
941 id = grp->gr_gid;
942 }
943
944 }
945 else if (strncmp(*str, "all:", 4) == 0)
946 {
947 *str += 3;
948 etype = ACL_OTHER;
949 }
950 else if (strncmp(*str, "mask:", 5) == 0)
951 {
952 *str += 4;
953 etype = ACL_MASK;
954 *is_mask = true;
955 }
956 else
957 {
958 Log(LOG_LEVEL_ERR, "ACE does not start with user:/group:/all:/mask:");
959 return false;
960 }
961
962 if (acl_set_tag_type(ace, etype) != 0)
963 {
964 Log(LOG_LEVEL_ERR, "Could not set ACE tag type. (acl_set_tag_type: %s)", GetErrorStr());
965 result = false;
966 }
967 else if (etype == ACL_USER || etype == ACL_GROUP)
968 {
969 if ((acl_set_qualifier(ace, &id)) != 0)
970 {
971 Log(LOG_LEVEL_ERR, "Could not set ACE qualifier. (acl_set_qualifier: %s)", GetErrorStr());
972 result = false;
973 }
974 }
975
976 if (ids != NULL)
977 {
978 free(ids);
979 }
980
981 return result;
982 }
983
984 /*
985 Takes a CFEngine-syntax mode string and existing Posix
986 Linux-formatted permissions on the file system object as
987 arguments. The mode-string will be applied on the permissions.
988 Returns true on success, false otherwise.
989 */
990
ParseModePosixLinux(char * mode,acl_permset_t perms)991 static bool ParseModePosixLinux(char *mode, acl_permset_t perms)
992 {
993 int retv;
994 int more_entries;
995 acl_perm_t perm;
996 enum { add, del } op;
997
998 op = add;
999
1000 if (*mode == '\0' || *mode == ':')
1001 {
1002 if (acl_clear_perms(perms) != 0)
1003 {
1004 Log(LOG_LEVEL_ERR, "Error clearing permissions. (acl_clear_perms: %s)", GetErrorStr());
1005 return false;
1006 }
1007 else
1008 {
1009 return true;
1010 }
1011 }
1012
1013 more_entries = true;
1014
1015 while (more_entries)
1016 {
1017 switch (*mode)
1018 {
1019 case '+':
1020 op = add;
1021 mode++;
1022 break;
1023
1024 case '-':
1025 op = del;
1026 mode++;
1027 break;
1028
1029 case '=':
1030 mode++;
1031 // fallthrough
1032
1033 default:
1034 // if mode does not start with + or -, we clear existing perms
1035 op = add;
1036
1037 if (acl_clear_perms(perms) != 0)
1038 {
1039 Log(LOG_LEVEL_ERR, "Unable to clear ACL permissions. (acl_clear_perms: %s)", GetErrorStr());
1040 return false;
1041 }
1042 }
1043
1044 // parse generic perms (they are 1-1 on Posix)
1045
1046 while (*mode != '\0' && strchr(CF_VALID_GPERMS, *mode))
1047 {
1048 if (*mode == '\0')
1049 {
1050 break;
1051 }
1052 switch (*mode)
1053 {
1054 case 'r':
1055 perm = ACL_READ;
1056 break;
1057
1058 case 'w':
1059 perm = ACL_WRITE;
1060 break;
1061
1062 case 'x':
1063 perm = ACL_EXECUTE;
1064 break;
1065
1066 default:
1067 Log(LOG_LEVEL_ERR, "No Linux support for generic permission flag '%c'", *mode);
1068 return false;
1069 }
1070
1071 if (op == add)
1072 {
1073 retv = acl_add_perm(perms, perm);
1074 }
1075 else
1076 {
1077 retv = acl_delete_perm(perms, perm);
1078 }
1079
1080 if (retv != 0)
1081 {
1082 Log(LOG_LEVEL_ERR, "Could not change ACE permission. (acl_[add|delete]_perm: %s)", GetErrorStr());
1083 return false;
1084 }
1085 mode++;
1086 }
1087
1088 // parse native perms
1089
1090 if (*mode == CF_NATIVE_PERMS_SEP_START)
1091 {
1092 mode++;
1093
1094 while (*mode != '\0' && strchr(CF_VALID_NPERMS_POSIX, *mode))
1095 {
1096 switch (*mode)
1097 {
1098 case 'r':
1099 perm = ACL_READ;
1100 break;
1101
1102 case 'w':
1103 perm = ACL_WRITE;
1104 break;
1105
1106 case 'x':
1107 perm = ACL_EXECUTE;
1108 break;
1109
1110 default:
1111 Log(LOG_LEVEL_ERR, "No Linux support for native permission flag '%c'", *mode);
1112 return false;
1113 }
1114
1115 if (op == add)
1116 {
1117 retv = acl_add_perm(perms, perm);
1118 }
1119 else
1120 {
1121 retv = acl_delete_perm(perms, perm);
1122 }
1123
1124 if (retv != 0)
1125 {
1126 Log(LOG_LEVEL_ERR, "Could not change ACE permission. (acl_[add|delete]_perm: %s)", GetErrorStr());
1127 return false;
1128 }
1129 mode++;
1130 }
1131
1132 // scan past native perms end seperator
1133 mode++;
1134 }
1135
1136 if (*mode == ',')
1137 {
1138 more_entries = true;
1139 mode++;
1140 }
1141 else
1142 {
1143 more_entries = false;
1144 }
1145 }
1146
1147 return true;
1148 }
1149
1150 #else /* !HAVE_LIBACL */
1151
CheckPosixLinuxACL(EvalContext * ctx,ARG_UNUSED const char * file_path,ARG_UNUSED Acl acl,const Attributes * a,const Promise * pp)1152 PromiseResult CheckPosixLinuxACL(EvalContext *ctx, ARG_UNUSED const char *file_path, ARG_UNUSED Acl acl, const Attributes *a, const Promise *pp)
1153 {
1154 cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a,
1155 "Posix ACLs are not supported on this Linux system - install the Posix acl library");
1156 PromiseRef(LOG_LEVEL_ERR, pp);
1157 return PROMISE_RESULT_FAIL;
1158 }
1159
1160 #endif
1161