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 <verify_acl.h>
26 
27 #include <actuator.h>
28 #include <acl_posix.h>
29 #include <files_names.h>
30 #include <promises.h>
31 #include <string_lib.h>
32 #include <rlist.h>
33 #include <eval_context.h>
34 #include <cf-agent-enterprise-stubs.h>
35 #include <cf-agent-windows-functions.h>
36 
37 // Valid operations (first char of mode)
38 #define CF_VALID_OPS_METHOD_OVERWRITE "=+-"
39 #define CF_VALID_OPS_METHOD_APPEND "=+-"
40 
41 static bool CheckACLSyntax(const char *file, Acl acl, const Promise *pp);
42 
43 static void SetACLDefaults(const char *path, Acl *acl);
44 static bool CheckACESyntax(char *ace, char *valid_nperms, char *valid_ops, int deny_support, int mask_support,
45                         const Promise *pp);
46 static bool CheckModeSyntax(char **mode_p, char *valid_nperms, char *valid_ops, const Promise *pp);
47 static bool CheckPermTypeSyntax(char *permt, int deny_support, const Promise *pp);
48 static int CheckAclDefault(const char *path, Acl *acl, const Promise *pp);
49 
50 
VerifyACL(EvalContext * ctx,const char * file,const Attributes * attr,const Promise * pp)51 PromiseResult VerifyACL(EvalContext *ctx, const char *file, const Attributes *attr, const Promise *pp)
52 {
53     assert(attr != NULL);
54     Attributes a = *attr; // TODO: Remove this local copy
55     if (!CheckACLSyntax(file, a.acl, pp))
56     {
57         RecordFailure(ctx, pp, attr, "Syntax error in access control list for '%s'", file);
58         PromiseRef(LOG_LEVEL_ERR, pp);
59         return PROMISE_RESULT_FAIL;
60     }
61 
62     SetACLDefaults(file, &a.acl);
63 
64     PromiseResult result = PROMISE_RESULT_NOOP;
65 
66 // decide which ACL API to use
67     switch (a.acl.acl_type)
68     {
69     case ACL_TYPE_NONE: // fallthrough: acl_type defaults to generic
70     case ACL_TYPE_GENERIC:
71 
72 #if defined(__linux__)
73         result = PromiseResultUpdate(result, CheckPosixLinuxACL(ctx, file, a.acl, &a, pp));
74 #elif defined(__MINGW32__)
75         result = PromiseResultUpdate(result, Nova_CheckNtACL(ctx, file, a.acl, &a, pp));
76 #else
77         RecordFailure(ctx, pp, attr, "ACLs are not yet supported on this system.");
78         result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
79 #endif
80         break;
81 
82     case ACL_TYPE_POSIX:
83 
84 #if defined(__linux__)
85         result = PromiseResultUpdate(result, CheckPosixLinuxACL(ctx, file, a.acl, &a, pp));
86 #else
87         RecordFailure(ctx, pp, attr, "Posix ACLs are not supported on this system");
88         result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
89 #endif
90         break;
91 
92     case ACL_TYPE_NTFS_:
93 #ifdef __MINGW32__
94         result = PromiseResultUpdate(result, Nova_CheckNtACL(ctx, file, a.acl, &a, pp));
95 #else
96         RecordFailure(ctx, pp, attr, "NTFS ACLs are not supported on this system");
97         result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
98 #endif
99         break;
100 
101     default:
102         assert(false);
103         RecordFailure(ctx, pp, attr, "Unknown ACL type - software error");
104         break;
105     }
106 
107     return result;
108 }
109 
CheckACLSyntax(const char * file,Acl acl,const Promise * pp)110 static bool CheckACLSyntax(const char *file, Acl acl, const Promise *pp)
111 {
112     bool valid = true;
113     int deny_support = false;
114     int mask_support = false;
115     char *valid_ops = NULL;
116     char *valid_nperms = NULL;
117     Rlist *rp;
118 
119 // set unset fields to defautls
120     SetACLDefaults(file, &acl);
121 
122 // find valid values for op
123 
124     switch (acl.acl_method)
125     {
126     case ACL_METHOD_OVERWRITE:
127         valid_ops = CF_VALID_OPS_METHOD_OVERWRITE;
128         break;
129 
130     case ACL_METHOD_APPEND:
131         valid_ops = CF_VALID_OPS_METHOD_APPEND;
132         break;
133 
134     default:
135         // never executed: should be set to a default value by now
136         break;
137     }
138 
139     switch (acl.acl_type)
140     {
141     case ACL_TYPE_GENERIC:        // generic ACL type: cannot include native or deny-type permissions
142         valid_nperms = "";
143         deny_support = false;
144         mask_support = false;
145         break;
146 
147     case ACL_TYPE_POSIX:
148         valid_nperms = CF_VALID_NPERMS_POSIX;
149         deny_support = false;   // posix does not support deny-type permissions
150         mask_support = true;    // mask-ACE is allowed in POSIX
151         break;
152 
153     case ACL_TYPE_NTFS_:
154         valid_nperms = CF_VALID_NPERMS_NTFS;
155         deny_support = true;
156         mask_support = false;
157         break;
158 
159     default:
160         // never executed: should be set to a default value by now
161         break;
162     }
163 
164 // check that acl_default is set to a valid value
165 
166     if (!CheckAclDefault(file, &acl, pp))
167     {
168         return false;
169     }
170 
171     for (rp = acl.acl_entries; rp != NULL; rp = rp->next)
172     {
173         valid = CheckACESyntax(RlistScalarValue(rp), valid_ops, valid_nperms, deny_support, mask_support, pp);
174 
175         if (!valid)             // wrong syntax in this ace
176         {
177             Log(LOG_LEVEL_ERR, "ACL: The ACE '%s' contains errors", RlistScalarValue(rp));
178             PromiseRef(LOG_LEVEL_ERR, pp);
179             break;
180         }
181     }
182 
183     for (rp = acl.acl_default_entries; rp != NULL; rp = rp->next)
184     {
185         valid = CheckACESyntax(RlistScalarValue(rp), valid_ops, valid_nperms, deny_support, mask_support, pp);
186 
187         if (!valid)             // wrong syntax in this ace
188         {
189             Log(LOG_LEVEL_ERR, "ACL: The ACE '%s' contains errors", RlistScalarValue(rp));
190             PromiseRef(LOG_LEVEL_ERR, pp);
191             break;
192         }
193     }
194 
195     return valid;
196 }
197 
198 /**
199  * Set unset fields with documented defaults, to these defaults.
200  **/
201 
SetACLDefaults(const char * path,Acl * acl)202 static void SetACLDefaults(const char *path, Acl *acl)
203 {
204 // default: acl_method => append
205 
206     if (acl->acl_method == ACL_METHOD_NONE)
207     {
208         acl->acl_method = ACL_METHOD_APPEND;
209     }
210 
211 // default: acl_type => generic
212 
213     if (acl->acl_type == ACL_TYPE_NONE)
214     {
215         acl->acl_type = ACL_TYPE_GENERIC;
216     }
217 
218 // default on directories: acl_default => nochange
219 
220     if ((acl->acl_default == ACL_DEFAULT_NONE) &&
221         (IsDir(ToChangesPath(path))))
222     {
223         acl->acl_default = ACL_DEFAULT_NO_CHANGE;
224     }
225 }
226 
CheckAclDefault(const char * path,Acl * acl,const Promise * pp)227 static int CheckAclDefault(const char *path, Acl *acl, const Promise *pp)
228 /*
229   Checks that acl_default is set to a valid value for this acl type.
230   Returns true if so, or false otherwise.
231 */
232 {
233     int valid = false;
234 
235     switch (acl->acl_default)
236     {
237     case ACL_DEFAULT_NONE:      // unset is always valid
238         valid = true;
239 
240         break;
241 
242     case ACL_DEFAULT_SPECIFY:        // NOTE: we assume all acls support specify
243 
244         // fallthrough
245     case ACL_DEFAULT_ACCESS:
246 
247         // fallthrough
248     default:
249 
250         if (IsDir(path))
251         {
252             valid = true;
253         }
254         else
255         {
256             Log(LOG_LEVEL_ERR, "acl_default can only be set on directories.");
257             PromiseRef(LOG_LEVEL_ERR, pp);
258             valid = false;
259         }
260 
261         break;
262     }
263 
264     return valid;
265 }
266 
CheckACESyntax(char * ace,char * valid_ops,char * valid_nperms,int deny_support,int mask_support,const Promise * pp)267 static bool CheckACESyntax(
268     char *ace,
269     char *valid_ops,
270     char *valid_nperms,
271     int deny_support,
272     int mask_support,
273     const Promise *pp)
274 {
275     char *str = ace;
276     bool chkid = false;
277 
278 // first element must be "user", "group" or "all"
279 
280     if (strncmp(str, "user:", 5) == 0)
281     {
282         str += 5;
283         chkid = true;
284     }
285     else if (strncmp(str, "group:", 6) == 0)
286     {
287         str += 6;
288         chkid = true;
289     }
290     else if (strncmp(str, "all:", 4) == 0)
291     {
292         str += 4;
293         chkid = false;
294     }
295     else if (strncmp(str, "mask:", 5) == 0)
296     {
297 
298         if (mask_support)
299         {
300             str += 5;
301             chkid = false;
302         }
303         else
304         {
305             Log(LOG_LEVEL_ERR, "This ACL type does not support mask ACE.");
306             PromiseRef(LOG_LEVEL_ERR, pp);
307             return false;
308         }
309 
310     }
311     else
312     {
313         Log(LOG_LEVEL_ERR, "ACL: ACE '%s' does not start with user:/group:/all", ace);
314         PromiseRef(LOG_LEVEL_ERR, pp);
315         return false;
316     }
317 
318     if (chkid)                  // look for following "id:"
319     {
320         if (*str == ':')
321         {
322             Log(LOG_LEVEL_ERR, "ACL: ACE '%s': id cannot be empty or contain ':'", ace);
323             return false;
324         }
325 
326         // skip id-string (no check: allow any id-string)
327 
328         while (true)
329         {
330             str++;
331             if (*str == ':')
332             {
333                 str++;
334                 break;
335             }
336             else if (*str == '\0')
337             {
338                 Log(LOG_LEVEL_ERR, "ACL: Nothing following id string in ACE '%s'", ace);
339                 return false;
340             }
341         }
342     }
343 
344 // check the mode-string (also skips to next field)
345     const bool valid_mode = CheckModeSyntax(&str, valid_ops, valid_nperms, pp);
346 
347     if (!valid_mode)
348     {
349         Log(LOG_LEVEL_ERR, "ACL: Malformed mode-string in ACE '%s'", ace);
350         PromiseRef(LOG_LEVEL_ERR, pp);
351         return false;
352     }
353 
354     if (*str == '\0')           // mode was the last field
355     {
356         return true;
357     }
358 
359     str++;
360 
361 // last field; must be a perm_type field
362     const bool valid_permt = CheckPermTypeSyntax(str, deny_support, pp);
363 
364     if (!valid_permt)
365     {
366         Log(LOG_LEVEL_ERR, "ACL: Malformed perm_type syntax in ACE '%s'", ace);
367         return false;
368     }
369 
370     return true;
371 }
372 
CheckModeSyntax(char ** mode_p,char * valid_ops,char * valid_nperms,const Promise * pp)373 static bool CheckModeSyntax(char **mode_p, char *valid_ops, char *valid_nperms, const Promise *pp)
374 /*
375   Checks the syntax of a ':' or NULL terminated mode string.
376   Moves the string pointer to the character following the mode
377   (i.e. ':' or '\0')
378 */
379 {
380     char *mode = *mode_p;
381     bool valid = false;
382 
383 // mode is allowed to be empty
384 
385     if ((*mode == '\0') || (*mode == ':'))
386     {
387         return true;
388     }
389 
390     while (true)
391     {
392         mode = ScanPastChars(valid_ops, mode);
393 
394         mode = ScanPastChars(CF_VALID_GPERMS, mode);
395 
396         if (*mode == CF_NATIVE_PERMS_SEP_START)
397         {
398             mode++;
399 
400             mode = ScanPastChars(valid_nperms, mode);
401 
402             if (*mode == CF_NATIVE_PERMS_SEP_END)
403             {
404                 mode++;
405             }
406             else
407             {
408                 Log(LOG_LEVEL_ERR, "ACL: Invalid native permission '%c', or missing native end separator", *mode);
409                 PromiseRef(LOG_LEVEL_ERR, pp);
410                 valid = false;
411                 break;
412             }
413         }
414 
415         if ((*mode == '\0') || (*mode == ':'))      // end of mode-string
416         {
417             valid = true;
418             break;
419         }
420         else if (*mode == ',')  // one more iteration
421         {
422             mode++;
423         }
424         else
425         {
426             Log(LOG_LEVEL_ERR, "ACL: Mode string contains invalid characters");
427             PromiseRef(LOG_LEVEL_ERR, pp);
428             valid = false;
429             break;
430         }
431     }
432 
433     *mode_p = mode;             // move pointer to past mode-field
434 
435     return valid;
436 }
437 
CheckPermTypeSyntax(char * permt,int deny_support,const Promise * pp)438 static bool CheckPermTypeSyntax(char *permt, int deny_support, const Promise *pp)
439 /*
440   Checks if the given string corresponds to the perm_type syntax.
441   Only "allow" or "deny", followed by NULL-termination are valid.
442   In addition, "deny" is only valid for ACL types supporting it.
443  */
444 {
445     bool valid = false;
446 
447     if (strcmp(permt, "allow") == 0)
448     {
449         valid = true;
450     }
451     else if (strcmp(permt, "deny") == 0)
452     {
453         if (deny_support)
454         {
455             valid = true;
456         }
457         else
458         {
459             Log(LOG_LEVEL_ERR, "Deny permission not supported by this ACL type");
460             PromiseRef(LOG_LEVEL_ERR, pp);
461         }
462     }
463 
464     return valid;
465 }
466