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 <expand.h>
26
27 #include <misc_lib.h>
28 #include <eval_context.h>
29 #include <policy.h>
30 #include <promises.h>
31 #include <vars.h>
32 #include <syntax.h>
33 #include <files_names.h>
34 #include <scope.h>
35 #include <matching.h>
36 #include <unix.h>
37 #include <attributes.h>
38 #include <fncall.h>
39 #include <iteration.h>
40 #include <audit.h>
41 #include <verify_vars.h>
42 #include <string_lib.h>
43 #include <conversion.h>
44 #include <verify_classes.h>
45
46 /**
47 * VARIABLES AND PROMISE EXPANSION
48 *
49 * Expanding variables is easy -- expanding lists automagically requires
50 * some thought. Remember that
51 *
52 * promiser <=> RVAL_TYPE_SCALAR
53 * promisee <=> RVAL_TYPE_LIST
54 *
55 * For bodies we have
56 *
57 * lval <=> RVAL_TYPE_LIST | RVAL_TYPE_SCALAR
58 *
59 * Any list or container variable occurring within a scalar or in place of a
60 * scalar is assumed to be iterated i.e. $(name). See comments in iteration.c.
61 *
62 * Any list variable @(name) is *not iterated*, but dropped into place (see
63 * DeRefCopyPromise()).
64 *
65 * Please note that bodies cannot contain iterators.
66 *
67 * The full process of promise and variable expansion is mostly outlined in
68 * ExpandPromise() and ExpandPromiseAndDo() and the basic steps are:
69 *
70 * + Skip everything if the class guard is not defined.
71 *
72 * + DeRefCopyPromise(): *Copy the promise* while expanding '@' slists and body
73 * arguments and handling body inheritance. This requires one round of
74 * expansion with scopeid "body".
75 *
76 * + Push promise frame
77 *
78 * + MapIteratorsFromRval(): Parse all strings (promiser-promisee-constraints),
79 * find all unexpanded variables, mangle them if needed (if they are
80 * namespaced/scoped), and *initialise the wheels* in the iteration engine
81 * (iterctx) to iterate over iterable variables (slists and containers). See
82 * comments in iteration.c for further details.
83 *
84 * + For every iteration:
85 *
86 * - Push iteration frame
87 *
88 * - EvalContextStackPushPromiseIterationFrame()->ExpandDeRefPromise(): Make
89 * another copy of the promise with all constraints evaluated and variables
90 * expanded.
91 *
92 * -- NOTE: As a result all *functions are also evaluated*, even if they are
93 * not to be used immediately (for example promises that the actuator skips
94 * because of ifvarclass, see promises.c:ExpandDeRefPromise() ).
95 *
96 * -- (TODO IS IT CORRECT?) In a sub-bundle, create a new context and make
97 * hashes of the the transferred variables in the temporary context
98 *
99 * - Run the actuator (=act_on_promise= i.e. =VerifyWhateverPromise()=)
100 *
101 * - Pop iteration frame
102 *
103 * + Pop promise frame
104 *
105 */
106
107 static inline char opposite(char c);
108
PutHandleVariable(EvalContext * ctx,const Promise * pp)109 static void PutHandleVariable(EvalContext *ctx, const Promise *pp)
110 {
111 char *handle_s;
112 const char *existing_handle = PromiseGetHandle(pp);
113
114 if (existing_handle != NULL)
115 {
116 // This ordering is necessary to get automated canonification
117 handle_s = ExpandScalar(ctx, NULL, "this", existing_handle, NULL);
118 CanonifyNameInPlace(handle_s);
119 }
120 else
121 {
122 handle_s = xstrdup(PromiseID(pp)); /* default handle */
123 }
124
125 EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_THIS,
126 "handle", handle_s,
127 CF_DATA_TYPE_STRING, "source=promise");
128 free(handle_s);
129 }
130
131 /**
132 * Recursively go down the #rval and run PromiseIteratorPrepare() to take note
133 * of all iterables and mangle all rvals than need to be mangled before
134 * iterating.
135 */
MapIteratorsFromRval(EvalContext * ctx,PromiseIterator * iterctx,Rval rval)136 static void MapIteratorsFromRval(EvalContext *ctx,
137 PromiseIterator *iterctx,
138 Rval rval)
139 {
140 switch (rval.type)
141 {
142
143 case RVAL_TYPE_SCALAR:
144 PromiseIteratorPrepare(iterctx, ctx, RvalScalarValue(rval));
145 break;
146
147 case RVAL_TYPE_LIST:
148 for (const Rlist *rp = RvalRlistValue(rval);
149 rp != NULL; rp = rp->next)
150 {
151 MapIteratorsFromRval(ctx, iterctx, rp->val);
152 }
153 break;
154
155 case RVAL_TYPE_FNCALL:
156 {
157 char *fn_name = RvalFnCallValue(rval)->name;
158
159 /* Check function name. */
160 PromiseIteratorPrepare(iterctx, ctx, fn_name);
161
162 /* Check each of the function arguments. */
163 /* EXCEPT on functions that use special variables: the mangled
164 * variables would never be resolved if they contain inner special
165 * variables (for example "$(bundle.A[$(this.k)])" and the returned
166 * slist would contained mangled vars like "bundle#A[1]" which would
167 * never resolve in future iterations. By skipping the iteration
168 * engine for now, the function returns an slist with unmangled
169 * entries, and the iteration engine works correctly on the next
170 * pass! */
171 if (strcmp(fn_name, "maplist") != 0 &&
172 strcmp(fn_name, "mapdata") != 0 &&
173 strcmp(fn_name, "maparray")!= 0)
174 {
175 for (Rlist *rp = RvalFnCallValue(rval)->args;
176 rp != NULL; rp = rp->next)
177 {
178 MapIteratorsFromRval(ctx, iterctx, rp->val);
179 }
180 }
181 break;
182 }
183
184 case RVAL_TYPE_CONTAINER:
185 case RVAL_TYPE_NOPROMISEE:
186 break;
187 }
188 }
189
ExpandPromiseAndDo(EvalContext * ctx,PromiseIterator * iterctx,PromiseActuator * act_on_promise,void * param,bool actuate_ifelse)190 static PromiseResult ExpandPromiseAndDo(EvalContext *ctx, PromiseIterator *iterctx,
191 PromiseActuator *act_on_promise, void *param,
192 bool actuate_ifelse)
193 {
194 PromiseResult result = PROMISE_RESULT_SKIPPED;
195
196 /* In the case of ifelse() we must always include an extra round of "actuation"
197 * in the while loop below. PromiseIteratorNext() will return false in the case
198 * that there are doubly-unresolved Rvals like $($(missing)).
199 * We can't add an empty wheel because that is skipped as well as noted in
200 * libpromises/iteration.c ShouldAddVariableAsIterationWheel(). */
201 bool ifelse_actuated = !actuate_ifelse;
202
203 /* TODO this loop could be completely skipped for for non vars/classes if
204 * act_on_promise is CommonEvalPromise(). */
205 while (PromiseIteratorNext(iterctx, ctx) || !ifelse_actuated)
206 {
207 /*
208 * ACTUAL WORK PART 1: Get a (another) copy of the promise.
209 *
210 * Basically this evaluates all constraints. As a result it evaluates
211 * all functions, even if they are not to be used immediately (for
212 * example promises that the actuator skips because of ifvarclass).
213 */
214 const Promise *pexp = /* expanded promise */
215 EvalContextStackPushPromiseIterationFrame(ctx, iterctx);
216 if (pexp == NULL) /* is the promise excluded? */
217 {
218 result = PromiseResultUpdate(result, PROMISE_RESULT_SKIPPED);
219 ifelse_actuated = true;
220 continue;
221 }
222
223 /* ACTUAL WORK PART 2: run the actuator */
224 PromiseResult iteration_result = act_on_promise(ctx, pexp, param);
225
226 /* iteration_result is always NOOP for PRE-EVAL. */
227 result = PromiseResultUpdate(result, iteration_result);
228
229 /* Redmine#6484: Do not store promise handles during PRE-EVAL, to
230 * avoid package promise always running. */
231 if (act_on_promise != &CommonEvalPromise)
232 {
233 NotifyDependantPromises(ctx, pexp, iteration_result);
234 }
235
236 /* EVALUATE VARS PROMISES again, allowing redefinition of
237 * variables. The theory behind this is that the "sampling rate" of
238 * vars promise needs to be double than the rest. */
239 if (strcmp(PromiseGetPromiseType(pexp), "vars") == 0 ||
240 strcmp(PromiseGetPromiseType(pexp), "meta") == 0)
241 {
242 if (act_on_promise != &VerifyVarPromise)
243 {
244 VerifyVarPromise(ctx, pexp, NULL);
245 }
246 }
247
248 /* Why do we push/pop an iteration frame, if all iterated variables
249 * are Put() on the previous scope? */
250 EvalContextStackPopFrame(ctx);
251 ifelse_actuated = true;
252 }
253
254 return result;
255 }
256
ExpandPromise(EvalContext * ctx,const Promise * pp,PromiseActuator * act_on_promise,void * param)257 PromiseResult ExpandPromise(EvalContext *ctx, const Promise *pp,
258 PromiseActuator *act_on_promise, void *param)
259 {
260 assert(pp != NULL);
261
262 if (!IsDefinedClass(ctx, pp->classes))
263 {
264 Log(LOG_LEVEL_DEBUG,
265 "Skipping %s promise expansion with promiser '%s' due to class guard '%s::' (pass %d)",
266 PromiseGetPromiseType(pp),
267 pp->promiser,
268 pp->classes,
269 EvalContextGetPass(ctx));
270 return PROMISE_RESULT_SKIPPED;
271 }
272
273 /* 1. Copy the promise while expanding '@' slists and body arguments
274 * (including body inheritance). */
275 Promise *pcopy = DeRefCopyPromise(ctx, pp);
276
277 EvalContextStackPushPromiseFrame(ctx, pcopy);
278 PromiseIterator *iterctx = PromiseIteratorNew(pcopy);
279
280 /* 2. Parse all strings (promiser-promisee-constraints), find all
281 unexpanded variables, mangle them if needed (if they are
282 namespaced/scoped), and start the iteration engine (iterctx) to
283 iterate over slists and containers. */
284
285 MapIteratorsFromRval(ctx, iterctx,
286 (Rval) { pcopy->promiser, RVAL_TYPE_SCALAR });
287
288 if (pcopy->promisee.item != NULL)
289 {
290 MapIteratorsFromRval(ctx, iterctx, pcopy->promisee);
291 }
292
293 bool actuate_ifelse = false;
294 for (size_t i = 0; i < SeqLength(pcopy->conlist); i++)
295 {
296 Constraint *cp = SeqAt(pcopy->conlist, i);
297 if (cp->rval.type == RVAL_TYPE_FNCALL &&
298 strcmp(RvalFnCallValue(cp->rval)->name, "ifelse") == 0)
299 {
300 actuate_ifelse = true;
301 }
302 MapIteratorsFromRval(ctx, iterctx, cp->rval);
303 }
304
305 /* 3. GO! */
306 PutHandleVariable(ctx, pcopy);
307 PromiseResult result = ExpandPromiseAndDo(ctx, iterctx,
308 act_on_promise, param, actuate_ifelse);
309
310 EvalContextStackPopFrame(ctx);
311 PromiseIteratorDestroy(iterctx);
312 PromiseDestroy(pcopy);
313
314 return result;
315 }
316
317
318 /*********************************************************************/
319 /*********************************************************************/
320
ExpandPrivateRval(const EvalContext * ctx,const char * ns,const char * scope,const void * rval_item,RvalType rval_type)321 Rval ExpandPrivateRval(const EvalContext *ctx,
322 const char *ns, const char *scope,
323 const void *rval_item, RvalType rval_type)
324 {
325 Rval returnval;
326 returnval.item = NULL;
327 returnval.type = RVAL_TYPE_NOPROMISEE;
328
329 switch (rval_type)
330 {
331 case RVAL_TYPE_SCALAR:
332 returnval.item = ExpandScalar(ctx, ns, scope, rval_item, NULL);
333 returnval.type = RVAL_TYPE_SCALAR;
334 break;
335 case RVAL_TYPE_LIST:
336 returnval.item = ExpandList(ctx, ns, scope, rval_item, true);
337 returnval.type = RVAL_TYPE_LIST;
338 break;
339
340 case RVAL_TYPE_FNCALL:
341 returnval.item = ExpandFnCall(ctx, ns, scope, rval_item);
342 returnval.type = RVAL_TYPE_FNCALL;
343 break;
344
345 case RVAL_TYPE_CONTAINER:
346 returnval = RvalNew(rval_item, RVAL_TYPE_CONTAINER);
347 break;
348
349 case RVAL_TYPE_NOPROMISEE:
350 break;
351 }
352
353 return returnval;
354 }
355
356 /**
357 * Detects a variable expansion inside of a data/list reference, for example
358 * "@(${container_name})" or "@(prefix${container_name})" or
359 * "@(nspace:${container_name})" or "@(container_name[${field}])".
360 *
361 * @note This function doesn't have to be bullet-proof, it only needs to
362 * properly detect valid cases. The rest is left to the parser and code
363 * expanding variables.
364 */
VariableDataOrListReference(const char * str)365 static inline bool VariableDataOrListReference(const char *str)
366 {
367 assert(str != NULL);
368
369 size_t len = strlen(str);
370
371 /* at least '@($(X))' is needed */
372 if (len < 7)
373 {
374 return false;
375 }
376
377 if (!((str[0] == '@') &&
378 ((str[1] == '{') || (str[1] == '('))))
379 {
380 return false;
381 }
382
383 /* Check if, after '@(', there are only
384 * - characters allowed in data/list names or
385 * - ':' to separate namespace from the name or
386 * - '.' to separate bundle and variable name or,
387 * - '[' for data/list field/index specification,
388 * followed by "$(" or "${" with a matching close bracket somewhere. */
389 for (size_t i = 2; i < len; i++)
390 {
391 if (!(isalnum((int) str[i]) || (str[i] == '_') ||
392 (str[i] == ':') || (str[i] == '$') || (str[i] == '.') || (str[i] == '[')))
393 {
394 return false;
395 }
396
397 if (str[i] == '$')
398 {
399 if (((i + 1) < len) && ((str[i + 1] == '{') || (str[i + 1] == '(')))
400 {
401 int close_bracket = (int) opposite(str[i+1]);
402 return (strchr(str + i + 2, close_bracket) != NULL);
403 }
404 else
405 {
406 return false;
407 }
408 }
409 }
410
411 return false;
412 }
413
ExpandListEntry(const EvalContext * ctx,const char * ns,const char * scope,int expandnaked,Rval entry)414 static Rval ExpandListEntry(const EvalContext *ctx,
415 const char *ns, const char *scope,
416 int expandnaked, Rval entry)
417 {
418 Rval expanded_data_list = {0};
419 /* If rval is something like '@($(container_name).field)', we need to expand
420 * the nested variable first. */
421 if (entry.type == RVAL_TYPE_SCALAR &&
422 VariableDataOrListReference(entry.item))
423 {
424 entry = ExpandPrivateRval(ctx, ns, scope, entry.item, entry.type);
425 expanded_data_list = entry;
426 }
427
428 if (entry.type == RVAL_TYPE_SCALAR &&
429 IsNakedVar(entry.item, '@'))
430 {
431 if (expandnaked)
432 {
433 char naked[CF_MAXVARSIZE];
434 GetNaked(naked, entry.item);
435
436 if (IsExpandable(naked))
437 {
438 char *exp = ExpandScalar(ctx, ns, scope, naked, NULL);
439 strlcpy(naked, exp, sizeof(naked)); /* TODO err */
440 free(exp);
441 }
442
443 /* Check again, it might have changed. */
444 if (!IsExpandable(naked))
445 {
446 VarRef *ref = VarRefParseFromScope(naked, scope);
447
448 DataType value_type;
449 const void *value = EvalContextVariableGet(ctx, ref, &value_type);
450 VarRefDestroy(ref);
451
452 if (value_type != CF_DATA_TYPE_NONE) /* variable found? */
453 {
454 Rval ret = ExpandPrivateRval(ctx, ns, scope, value,
455 DataTypeToRvalType(value_type));
456 RvalDestroy(expanded_data_list);
457 return ret;
458 }
459 }
460 }
461 else
462 {
463 Rval ret = RvalNew(entry.item, RVAL_TYPE_SCALAR);
464 RvalDestroy(expanded_data_list);
465 return ret;
466 }
467 }
468
469 Rval ret = ExpandPrivateRval(ctx, ns, scope, entry.item, entry.type);
470 RvalDestroy(expanded_data_list);
471 return ret;
472 }
473
ExpandList(const EvalContext * ctx,const char * ns,const char * scope,const Rlist * list,int expandnaked)474 Rlist *ExpandList(const EvalContext *ctx,
475 const char *ns, const char *scope,
476 const Rlist *list, int expandnaked)
477 {
478 Rlist *start = NULL;
479
480 for (const Rlist *rp = list; rp != NULL; rp = rp->next)
481 {
482 Rval returnval = ExpandListEntry(ctx, ns, scope, expandnaked, rp->val);
483 RlistAppend(&start, returnval.item, returnval.type);
484 RvalDestroy(returnval);
485 }
486
487 return start;
488 }
489
490 /*********************************************************************/
491
ExpandBundleReference(EvalContext * ctx,const char * ns,const char * scope,Rval rval)492 Rval ExpandBundleReference(EvalContext *ctx,
493 const char *ns, const char *scope,
494 Rval rval)
495 {
496 // Allocates new memory for the copy
497 switch (rval.type)
498 {
499 case RVAL_TYPE_SCALAR:
500 return (Rval) { ExpandScalar(ctx, ns, scope, RvalScalarValue(rval), NULL),
501 RVAL_TYPE_SCALAR };
502
503 case RVAL_TYPE_FNCALL:
504 return (Rval) { ExpandFnCall(ctx, ns, scope, RvalFnCallValue(rval)),
505 RVAL_TYPE_FNCALL};
506
507 case RVAL_TYPE_CONTAINER:
508 case RVAL_TYPE_LIST:
509 case RVAL_TYPE_NOPROMISEE:
510 return RvalNew(NULL, RVAL_TYPE_NOPROMISEE);
511 }
512
513 assert(false);
514 return RvalNew(NULL, RVAL_TYPE_NOPROMISEE);
515 }
516
517 /**
518 * Expand a #string into Buffer #out, returning the pointer to the string
519 * itself, inside the Buffer #out. If #out is NULL then the buffer will be
520 * created and destroyed internally.
521 *
522 * @retval NULL something went wrong
523 */
ExpandScalar(const EvalContext * ctx,const char * ns,const char * scope,const char * string,Buffer * out)524 char *ExpandScalar(const EvalContext *ctx, const char *ns, const char *scope,
525 const char *string, Buffer *out)
526 {
527 bool out_belongs_to_us = false;
528
529 if (out == NULL)
530 {
531 out = BufferNew();
532 out_belongs_to_us = true;
533 }
534
535 assert(string != NULL);
536 assert(out != NULL);
537 Buffer *current_item = BufferNew();
538
539 for (const char *sp = string; *sp != '\0'; sp++)
540 {
541 BufferClear(current_item);
542 ExtractScalarPrefix(current_item, sp, strlen(sp));
543
544 BufferAppend(out, BufferData(current_item), BufferSize(current_item));
545 sp += BufferSize(current_item);
546 if (*sp == '\0')
547 {
548 break;
549 }
550
551 BufferClear(current_item);
552 char varstring = sp[1];
553 ExtractScalarReference(current_item, sp, strlen(sp), true);
554 sp += BufferSize(current_item) + 2;
555
556 if (IsCf3VarString(BufferData(current_item)))
557 {
558 Buffer *temp = BufferCopy(current_item);
559 BufferClear(current_item);
560 ExpandScalar(ctx, ns, scope, BufferData(temp), current_item);
561 BufferDestroy(temp);
562 }
563
564 if (!IsExpandable(BufferData(current_item)))
565 {
566 VarRef *ref = VarRefParseFromNamespaceAndScope(
567 BufferData(current_item),
568 ns, scope, CF_NS, '.');
569 DataType value_type;
570 const void *value = EvalContextVariableGet(ctx, ref, &value_type);
571 VarRefDestroy(ref);
572
573 switch (DataTypeToRvalType(value_type))
574 {
575 case RVAL_TYPE_SCALAR:
576 assert(value != NULL);
577 BufferAppendString(out, value);
578 continue;
579 break;
580
581 case RVAL_TYPE_CONTAINER:
582 {
583 assert(value != NULL);
584 const JsonElement *jvalue = value; /* instead of casts */
585 if (JsonGetElementType(jvalue) == JSON_ELEMENT_TYPE_PRIMITIVE)
586 {
587 BufferAppendString(out, JsonPrimitiveGetAsString(jvalue));
588 continue;
589 }
590 break;
591 }
592 default:
593 /* TODO Log() */
594 break;
595 }
596 }
597
598 if (varstring == '{')
599 {
600 BufferAppendF(out, "${%s}", BufferData(current_item));
601 }
602 else
603 {
604 BufferAppendF(out, "$(%s)", BufferData(current_item));
605 }
606 }
607
608 BufferDestroy(current_item);
609
610 LogDebug(LOG_MOD_EXPAND, "ExpandScalar( %s : %s . %s ) => %s",
611 SAFENULL(ns), SAFENULL(scope), string, BufferData(out));
612
613 return out_belongs_to_us ? BufferClose(out) : BufferGet(out);
614 }
615
616 /*********************************************************************/
617
EvaluateFinalRval(EvalContext * ctx,const Policy * policy,const char * ns,const char * scope,Rval rval,bool forcelist,const Promise * pp)618 Rval EvaluateFinalRval(EvalContext *ctx, const Policy *policy,
619 const char *ns, const char *scope,
620 Rval rval, bool forcelist, const Promise *pp)
621 {
622 assert(ctx);
623 assert(policy);
624 Rval returnval;
625
626 /* Treat lists specially. */
627 if (rval.type == RVAL_TYPE_SCALAR && IsNakedVar(rval.item, '@'))
628 {
629 char naked[CF_MAXVARSIZE];
630 GetNaked(naked, rval.item);
631
632 if (IsExpandable(naked)) /* example: @(blah_$(blue)) */
633 {
634 returnval = ExpandPrivateRval(ctx, NULL, "this", rval.item, rval.type);
635 }
636 else
637 {
638 VarRef *ref = VarRefParseFromScope(naked, scope);
639 DataType value_type;
640 const void *value = EvalContextVariableGet(ctx, ref, &value_type);
641 VarRefDestroy(ref);
642
643 if (DataTypeToRvalType(value_type) == RVAL_TYPE_LIST)
644 {
645 returnval.item = ExpandList(ctx, ns, scope, value, true);
646 returnval.type = RVAL_TYPE_LIST;
647 }
648 else
649 {
650 returnval = ExpandPrivateRval(ctx, NULL, "this", rval.item, rval.type);
651 }
652 }
653 }
654 else if (forcelist) /* We are replacing scalar @(name) with list */
655 {
656 returnval = ExpandPrivateRval(ctx, ns, scope, rval.item, rval.type);
657 }
658 else if (FnCallIsBuiltIn(rval))
659 {
660 returnval = RvalCopy(rval);
661 }
662 else
663 {
664 returnval = ExpandPrivateRval(ctx, NULL, "this", rval.item, rval.type);
665 }
666
667 switch (returnval.type)
668 {
669 case RVAL_TYPE_SCALAR:
670 case RVAL_TYPE_CONTAINER:
671 break;
672
673 case RVAL_TYPE_LIST:
674 for (Rlist *rp = RvalRlistValue(returnval); rp; rp = rp->next)
675 {
676 switch (rp->val.type)
677 {
678 case RVAL_TYPE_FNCALL:
679 {
680 FnCall *fp = RlistFnCallValue(rp);
681 rp->val = FnCallEvaluate(ctx, policy, fp, pp).rval;
682 FnCallDestroy(fp);
683 break;
684 }
685 case RVAL_TYPE_SCALAR:
686 if (EvalContextStackCurrentPromise(ctx) &&
687 IsCf3VarString(RlistScalarValue(rp)))
688 {
689 void *prior = rp->val.item;
690 rp->val = ExpandPrivateRval(ctx, NULL, "this",
691 prior, RVAL_TYPE_SCALAR);
692 free(prior);
693 }
694 /* else: returnval unchanged. */
695 break;
696 default:
697 assert(!"Bad type for entry in Rlist");
698 }
699 }
700 break;
701
702 case RVAL_TYPE_FNCALL:
703 if (FnCallIsBuiltIn(returnval))
704 {
705 FnCall *fp = RvalFnCallValue(returnval);
706 returnval = FnCallEvaluate(ctx, policy, fp, pp).rval;
707 FnCallDestroy(fp);
708 }
709 break;
710
711 default:
712 assert(returnval.item == NULL); /* else we're leaking it */
713 returnval.item = NULL;
714 returnval.type = RVAL_TYPE_NOPROMISEE;
715 break;
716 }
717
718 return returnval;
719 }
720
721 /*********************************************************************/
722
BundleResolvePromiseType(EvalContext * ctx,const Bundle * bundle,const char * type,PromiseActuator * actuator)723 void BundleResolvePromiseType(EvalContext *ctx, const Bundle *bundle, const char *type, PromiseActuator *actuator)
724 {
725 for (size_t j = 0; j < SeqLength(bundle->sections); j++)
726 {
727 BundleSection *section = SeqAt(bundle->sections, j);
728
729 if (strcmp(section->promise_type, type) == 0)
730 {
731 EvalContextStackPushBundleSectionFrame(ctx, section);
732 for (size_t i = 0; i < SeqLength(section->promises); i++)
733 {
734 Promise *pp = SeqAt(section->promises, i);
735 ExpandPromise(ctx, pp, actuator, NULL);
736 }
737 EvalContextStackPopFrame(ctx);
738 }
739 }
740 }
741
PointerCmp(const void * a,const void * b,ARG_UNUSED void * user_data)742 static int PointerCmp(const void *a, const void *b, ARG_UNUSED void *user_data)
743 {
744 if (a < b)
745 {
746 return -1;
747 }
748 else if (a == b)
749 {
750 return 0;
751 }
752 else
753 {
754 return 1;
755 }
756 }
757
RemoveRemotelyInjectedVars(const EvalContext * ctx,const Bundle * bundle)758 static void RemoveRemotelyInjectedVars(const EvalContext *ctx, const Bundle *bundle)
759 {
760 const Seq *remote_var_promises = EvalContextGetRemoteVarPromises(ctx, bundle->name);
761 if ((remote_var_promises == NULL) || SeqLength(remote_var_promises) == 0)
762 {
763 /* nothing to do here */
764 return;
765 }
766
767 size_t promises_length = SeqLength(remote_var_promises);
768 Seq *remove_vars = SeqNew(promises_length, NULL);
769
770 /* remove variables that have been attempted to be inserted into this
771 * bundle */
772 /* TODO: this is expensive and should be removed! */
773 for (size_t i = 0; i < promises_length; i++)
774 {
775 const Promise *pp = (Promise *) SeqAt(remote_var_promises, i);
776
777 VariableTableIterator *iter = EvalContextVariableTableIteratorNew(ctx, NULL, bundle->name, NULL);
778 const Variable *var = VariableTableIteratorNext(iter);
779 while (var != NULL)
780 {
781 /* variables are stored together with their original promises (org_pp) */
782 const Promise *var_promise = VariableGetPromise(var);
783 const VarRef *var_ref = VariableGetRef(var);
784 if (var_promise && var_promise->org_pp == pp)
785 {
786 Log(LOG_LEVEL_ERR, "Ignoring remotely-injected variable '%s'",
787 var_ref->lval);
788 /* avoid modifications of the variable table being iterated
789 * over and avoid trying to remove the same variable twice */
790 SeqAppendOnce(remove_vars, (void *) var, PointerCmp);
791 }
792 var = VariableTableIteratorNext(iter);
793 }
794 VariableTableIteratorDestroy(iter);
795 }
796
797 /* iteration over the variable table done, time to remove the variables */
798 size_t remove_vars_length = SeqLength(remove_vars);
799 for (size_t i = 0; i < remove_vars_length; i++)
800 {
801 Variable *var = (Variable *) SeqAt(remove_vars, i);
802 const VarRef *var_ref = VariableGetRef(var);
803 if (var_ref != NULL)
804 {
805 EvalContextVariableRemove(ctx, var_ref);
806 }
807 }
808 SeqDestroy(remove_vars);
809 }
810
BundleResolve(EvalContext * ctx,const Bundle * bundle)811 void BundleResolve(EvalContext *ctx, const Bundle *bundle)
812 {
813 Log(LOG_LEVEL_DEBUG,
814 "Resolving classes and variables in 'bundle %s %s'",
815 bundle->type, bundle->name);
816
817 /* first check if some variables were injected remotely into this bundle and
818 * remove them (CFE-1915) */
819 RemoveRemotelyInjectedVars(ctx, bundle);
820
821 /* PRE-EVAL: evaluate classes of common bundles. */
822 if (strcmp(bundle->type, "common") == 0)
823 {
824 /* Necessary to parse vars *before* classes for cases like this:
825 * 00_basics/04_bundles/dynamic_bundlesequence/dynamic_inputs_based_on_class_set_using_variable_file_control_extends_inputs.cf.sub
826 * -- see bundle "classify". */
827 BundleResolvePromiseType(ctx, bundle, "vars", VerifyVarPromise);
828
829 BundleResolvePromiseType(ctx, bundle, "classes", VerifyClassPromise);
830 }
831
832 /* Necessary to also parse vars *after* classes,
833 * because "inputs" might be affected in cases like:
834 * 00_basics/04_bundles/dynamic_bundlesequence/dynamic_inputs_based_on_list_variable_dependent_on_class.cf */
835 BundleResolvePromiseType(ctx, bundle, "vars", VerifyVarPromise);
836 }
837
838 /**
839 * Evaluate the relevant control body, and set the
840 * relevant fields in #ctx and #config.
841 */
ResolveControlBody(EvalContext * ctx,GenericAgentConfig * config,const Body * control_body)842 static void ResolveControlBody(EvalContext *ctx, GenericAgentConfig *config,
843 const Body *control_body)
844 {
845 const char *filename = control_body->source_path;
846
847 assert(CFG_CONTROLBODY[COMMON_CONTROL_MAX].lval == NULL);
848
849 const ConstraintSyntax *body_syntax = NULL;
850 for (int i = 0; CONTROL_BODIES[i].constraints != NULL; i++)
851 {
852 body_syntax = CONTROL_BODIES[i].constraints;
853
854 if (strcmp(control_body->type, CONTROL_BODIES[i].body_type) == 0)
855 {
856 break;
857 }
858 }
859 if (body_syntax == NULL)
860 {
861 FatalError(ctx, "Unknown control body: %s", control_body->type);
862 }
863
864 char *scope;
865 assert(strcmp(control_body->name, "control") == 0);
866 xasprintf(&scope, "control_%s", control_body->type);
867
868 Log(LOG_LEVEL_DEBUG, "Initiate control variable convergence for scope '%s'", scope);
869
870 EvalContextStackPushBodyFrame(ctx, NULL, control_body, NULL);
871
872 for (size_t i = 0; i < SeqLength(control_body->conlist); i++)
873 {
874 const char *lval;
875 Rval evaluated_rval;
876 size_t lineno;
877
878 /* Use nested scope to constrain cp. */
879 {
880 Constraint *cp = SeqAt(control_body->conlist, i);
881 lval = cp->lval;
882 lineno = cp->offset.line;
883
884 if (!IsDefinedClass(ctx, cp->classes))
885 {
886 continue;
887 }
888
889 if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_BUNDLESEQUENCE].lval) == 0)
890 {
891 evaluated_rval = ExpandPrivateRval(ctx, NULL, scope,
892 cp->rval.item, cp->rval.type);
893 }
894 else
895 {
896 evaluated_rval = EvaluateFinalRval(ctx, control_body->parent_policy,
897 NULL, scope, cp->rval,
898 true, NULL);
899 }
900
901 } /* Close scope: assert we only use evaluated_rval, not cp->rval. */
902
903 VarRef *ref = VarRefParseFromScope(lval, scope);
904 EvalContextVariableRemove(ctx, ref);
905
906 DataType rval_proper_datatype =
907 ConstraintSyntaxGetDataType(body_syntax, lval);
908 if (evaluated_rval.type != DataTypeToRvalType(rval_proper_datatype))
909 {
910 Log(LOG_LEVEL_ERR,
911 "Attribute '%s' in %s:%zu is of wrong type, skipping",
912 lval, filename, lineno);
913 VarRefDestroy(ref);
914 RvalDestroy(evaluated_rval);
915 continue;
916 }
917
918 bool success = EvalContextVariablePut(
919 ctx, ref, evaluated_rval.item, rval_proper_datatype,
920 "source=promise");
921 if (!success)
922 {
923 Log(LOG_LEVEL_ERR,
924 "Attribute '%s' in %s:%zu can't be added, skipping",
925 lval, filename, lineno);
926 VarRefDestroy(ref);
927 RvalDestroy(evaluated_rval);
928 continue;
929 }
930
931 VarRefDestroy(ref);
932
933 if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_OUTPUT_PREFIX].lval) == 0)
934 {
935 strlcpy(VPREFIX, RvalScalarValue(evaluated_rval),
936 sizeof(VPREFIX));
937 }
938
939 if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_DOMAIN].lval) == 0)
940 {
941 strlcpy(VDOMAIN, RvalScalarValue(evaluated_rval),
942 sizeof(VDOMAIN));
943 Log(LOG_LEVEL_VERBOSE, "SET domain = %s", VDOMAIN);
944
945 EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_SYS, "domain");
946 EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_SYS, "fqhost");
947
948 // We don't expect hostname or domain name longer than 255,
949 // warnings are printed in sysinfo.c.
950 // Here we support up to 511 bytes, just in case, because we can:
951 snprintf(VFQNAME, CF_MAXVARSIZE, "%511s.%511s", VUQNAME, VDOMAIN);
952 EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "fqhost",
953 VFQNAME, CF_DATA_TYPE_STRING,
954 "inventory,source=agent,attribute_name=Host name");
955 EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "domain",
956 VDOMAIN, CF_DATA_TYPE_STRING,
957 "source=agent");
958 EvalContextClassPutHard(ctx, VDOMAIN, "source=agent");
959 }
960
961 if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_IGNORE_MISSING_INPUTS].lval) == 0)
962 {
963 Log(LOG_LEVEL_VERBOSE, "SET ignore_missing_inputs %s",
964 RvalScalarValue(evaluated_rval));
965 config->ignore_missing_inputs = BooleanFromString(
966 RvalScalarValue(evaluated_rval));
967 }
968
969 if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_IGNORE_MISSING_BUNDLES].lval) == 0)
970 {
971 Log(LOG_LEVEL_VERBOSE, "SET ignore_missing_bundles %s",
972 RvalScalarValue(evaluated_rval));
973 config->ignore_missing_bundles = BooleanFromString(
974 RvalScalarValue(evaluated_rval));
975 }
976
977 if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_CACHE_SYSTEM_FUNCTIONS].lval) == 0)
978 {
979 Log(LOG_LEVEL_VERBOSE, "SET cache_system_functions %s",
980 RvalScalarValue(evaluated_rval));
981 bool cache_system_functions = BooleanFromString(
982 RvalScalarValue(evaluated_rval));
983 EvalContextSetEvalOption(ctx, EVAL_OPTION_CACHE_SYSTEM_FUNCTIONS,
984 cache_system_functions);
985 }
986
987 if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_PROTOCOL_VERSION].lval) == 0)
988 {
989 config->protocol_version = ProtocolVersionParse(
990 RvalScalarValue(evaluated_rval));
991 Log(LOG_LEVEL_VERBOSE, "SET common protocol_version: %s",
992 ProtocolVersionString(config->protocol_version));
993 }
994
995 /* Those are package_inventory and package_module common control body options */
996 if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_PACKAGE_INVENTORY].lval) == 0)
997 {
998 AddDefaultInventoryToContext(ctx, RvalRlistValue(evaluated_rval));
999 Log(LOG_LEVEL_VERBOSE, "SET common package_inventory list");
1000 }
1001 if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_PACKAGE_MODULE].lval) == 0)
1002 {
1003 AddDefaultPackageModuleToContext(ctx, RvalScalarValue(evaluated_rval));
1004 Log(LOG_LEVEL_VERBOSE, "SET common package_module: %s",
1005 RvalScalarValue(evaluated_rval));
1006 }
1007
1008 if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_GOALPATTERNS].lval) == 0)
1009 {
1010 /* Ignored */
1011 }
1012
1013 RvalDestroy(evaluated_rval);
1014 }
1015
1016 EvalContextStackPopFrame(ctx);
1017 free(scope);
1018 }
1019
ResolvePackageManagerBody(EvalContext * ctx,const Body * pm_body)1020 static void ResolvePackageManagerBody(EvalContext *ctx, const Body *pm_body)
1021 {
1022 PackageModuleBody *new_manager = xcalloc(1, sizeof(PackageModuleBody));
1023 new_manager->name = SafeStringDuplicate(pm_body->name);
1024
1025 for (size_t i = 0; i < SeqLength(pm_body->conlist); i++)
1026 {
1027 Constraint *cp = SeqAt(pm_body->conlist, i);
1028
1029 Rval returnval = {0};
1030
1031 if (IsDefinedClass(ctx, cp->classes))
1032 {
1033 returnval = ExpandPrivateRval(ctx, NULL, "body",
1034 cp->rval.item, cp->rval.type);
1035 }
1036
1037 if (returnval.item == NULL || returnval.type == RVAL_TYPE_NOPROMISEE)
1038 {
1039 Log(LOG_LEVEL_VERBOSE, "have invalid constraint while resolving"
1040 "package promise body: %s", cp->lval);
1041
1042 RvalDestroy(returnval);
1043 continue;
1044 }
1045
1046 if (strcmp(cp->lval, "query_installed_ifelapsed") == 0)
1047 {
1048 new_manager->installed_ifelapsed =
1049 (int)IntFromString(RvalScalarValue(returnval));
1050 }
1051 else if (strcmp(cp->lval, "query_updates_ifelapsed") == 0)
1052 {
1053 new_manager->updates_ifelapsed =
1054 (int)IntFromString(RvalScalarValue(returnval));
1055 }
1056 else if (strcmp(cp->lval, "default_options") == 0)
1057 {
1058 new_manager->options = RlistCopy(RvalRlistValue(returnval));
1059 }
1060 else if (strcmp(cp->lval, "interpreter") == 0)
1061 {
1062 assert(new_manager->interpreter == NULL);
1063 new_manager->interpreter = SafeStringDuplicate(RvalScalarValue(returnval));
1064 }
1065 else if (strcmp(cp->lval, "module_path") == 0)
1066 {
1067 assert(new_manager->module_path == NULL);
1068 new_manager->module_path = SafeStringDuplicate(RvalScalarValue(returnval));
1069 }
1070 else
1071 {
1072 /* This should be handled by the parser. */
1073 assert(0);
1074 }
1075 RvalDestroy(returnval);
1076 }
1077 AddPackageModuleToContext(ctx, new_manager);
1078 }
1079
PolicyResolve(EvalContext * ctx,const Policy * policy,GenericAgentConfig * config)1080 void PolicyResolve(EvalContext *ctx, const Policy *policy,
1081 GenericAgentConfig *config)
1082 {
1083 /* PRE-EVAL: common bundles: classes,vars. */
1084 for (size_t i = 0; i < SeqLength(policy->bundles); i++)
1085 {
1086 Bundle *bundle = SeqAt(policy->bundles, i);
1087 if (strcmp("common", bundle->type) == 0)
1088 {
1089 EvalContextStackPushBundleFrame(ctx, bundle, NULL, false);
1090 BundleResolve(ctx, bundle); /* PRE-EVAL classes,vars */
1091 EvalContextStackPopFrame(ctx);
1092 }
1093 }
1094
1095 /*
1096 * HACK: yet another pre-eval pass here, WHY? TODO remove, but test fails:
1097 * 00_basics/03_bodies/dynamic_inputs_findfiles.cf
1098 */
1099 #if 1
1100
1101 /* PRE-EVAL: non-common bundles: only vars. */
1102 for (size_t i = 0; i < SeqLength(policy->bundles); i++)
1103 {
1104 Bundle *bundle = SeqAt(policy->bundles, i);
1105 if (strcmp("common", bundle->type) != 0)
1106 {
1107 EvalContextStackPushBundleFrame(ctx, bundle, NULL, false);
1108 BundleResolve(ctx, bundle); /* PRE-EVAL vars */
1109 EvalContextStackPopFrame(ctx);
1110 }
1111 }
1112
1113 #endif
1114
1115 for (size_t i = 0; i < SeqLength(policy->bodies); i++)
1116 {
1117 Body *bdp = SeqAt(policy->bodies, i);
1118
1119 if (strcmp(bdp->name, "control") == 0)
1120 {
1121 ResolveControlBody(ctx, config, bdp);
1122 }
1123 /* Collect all package managers data from policy as we don't know yet
1124 * which ones we will use. */
1125 else if (strcmp(bdp->type, "package_module") == 0)
1126 {
1127 ResolvePackageManagerBody(ctx, bdp);
1128 }
1129 }
1130 }
1131
IsExpandable(const char * str)1132 bool IsExpandable(const char *str)
1133 {
1134 char left = 'x', right = 'x';
1135 int dollar = false;
1136 int bracks = 0, vars = 0;
1137
1138 for (const char *sp = str; *sp != '\0'; sp++) /* check for varitems */
1139 {
1140 switch (*sp)
1141 {
1142 case '$':
1143 if (*(sp + 1) == '{' || *(sp + 1) == '(')
1144 {
1145 dollar = true;
1146 }
1147 break;
1148 case '(':
1149 case '{':
1150 if (dollar)
1151 {
1152 left = *sp;
1153 bracks++;
1154 }
1155 break;
1156 case ')':
1157 case '}':
1158 if (dollar)
1159 {
1160 bracks--;
1161 right = *sp;
1162 }
1163 break;
1164 }
1165
1166 if (left == '(' && right == ')' && dollar && (bracks == 0))
1167 {
1168 vars++;
1169 dollar = false;
1170 }
1171
1172 if (left == '{' && right == '}' && dollar && (bracks == 0))
1173 {
1174 vars++;
1175 dollar = false;
1176 }
1177 }
1178
1179 if (bracks != 0)
1180 {
1181 Log(LOG_LEVEL_DEBUG, "If this is an expandable variable string then it contained syntax errors");
1182 return false;
1183 }
1184
1185 if (vars > 0)
1186 {
1187 Log(LOG_LEVEL_DEBUG,
1188 "Expanding variable '%s': found %d variables", str, vars);
1189 }
1190 return (vars > 0);
1191 }
1192
1193 /*********************************************************************/
1194
opposite(char c)1195 static inline char opposite(char c)
1196 {
1197 switch (c)
1198 {
1199 case '(': return ')';
1200 case '{': return '}';
1201 default : ProgrammingError("Was expecting '(' or '{' but got: '%c'", c);
1202 }
1203 return 0;
1204 }
1205
1206 /**
1207 * Check if #str contains one and only one variable expansion of #vtype kind
1208 * (it's usually either '$' or '@'). It can contain nested expansions which
1209 * are not checked properly. Examples:
1210 * true: "$(whatever)", "${whatever}", "$(blah$(blue))"
1211 * false: "$(blah)blue", "blah$(blue)", "$(blah)$(blue)", "$(blah}"
1212 */
IsNakedVar(const char * str,char vtype)1213 bool IsNakedVar(const char *str, char vtype)
1214 {
1215 size_t len = strlen(str);
1216 char last = len > 0 ? str[len-1] : '\0';
1217
1218 if (len < 3
1219 || str[0] != vtype
1220 || (str[1] != '(' && str[1] != '{')
1221 || last != opposite(str[1]))
1222 {
1223 return false;
1224 }
1225
1226 /* TODO check if nesting happens correctly? Is it needed? */
1227 size_t count = 0;
1228 for (const char *sp = str; *sp != '\0'; sp++)
1229 {
1230 switch (*sp)
1231 {
1232 case '(':
1233 case '{':
1234 count++;
1235 break;
1236 case ')':
1237 case '}':
1238 count--;
1239
1240 /* Make sure the end of the variable is the last character. */
1241 if (count == 0 && sp[1] != '\0')
1242 {
1243 return false;
1244 }
1245
1246 break;
1247 }
1248 }
1249
1250 if (count != 0)
1251 {
1252 return false;
1253 }
1254
1255 return true;
1256 }
1257
1258 /*********************************************************************/
1259
1260 /**
1261 * Copy @(listname) -> listname.
1262 *
1263 * This function performs no validations, it is necessary to call the
1264 * validation functions before calling this function.
1265 *
1266 * @NOTE make sure sizeof(dst) >= sizeof(s)
1267 */
GetNaked(char * dst,const char * s)1268 void GetNaked(char *dst, const char *s)
1269 {
1270 size_t s_len = strlen(s);
1271
1272 if (s_len < 4 || s_len + 3 >= CF_MAXVARSIZE)
1273 {
1274 Log(LOG_LEVEL_ERR,
1275 "@(variable) expected, but got malformed: %s", s);
1276 strlcpy(dst, s, CF_MAXVARSIZE);
1277 return;
1278 }
1279
1280 memcpy(dst, &s[2], s_len - 3);
1281 dst[s_len - 3] = '\0';
1282 }
1283
1284 /*********************************************************************/
1285
1286 /**
1287 * Checks if a variable is an @-list and returns true or false.
1288 */
IsVarList(const char * var)1289 bool IsVarList(const char *var)
1290 {
1291 if ('@' != var[0])
1292 {
1293 return false;
1294 }
1295 /*
1296 * Minimum size for a list is 4:
1297 * '@' + '(' + name + ')'
1298 */
1299 if (strlen(var) < 4)
1300 {
1301 return false;
1302 }
1303 return true;
1304 }
1305
CommonEvalPromise(EvalContext * ctx,const Promise * pp,ARG_UNUSED void * param)1306 PromiseResult CommonEvalPromise(EvalContext *ctx, const Promise *pp,
1307 ARG_UNUSED void *param)
1308 {
1309 assert(param == NULL);
1310
1311 PromiseRecheckAllConstraints(ctx, pp);
1312
1313 return PROMISE_RESULT_NOOP;
1314 }
1315