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