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