1 /*
2 Copyright 2020 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 /**
48 * VARIABLES AND PROMISE EXPANSION
49 *
50 * Expanding variables is easy -- expanding lists automagically requires
51 * some thought. Remember that
52 *
53 * promiser <=> RVAL_TYPE_SCALAR
54 * promisee <=> RVAL_TYPE_LIST
55 *
56 * For bodies we have
57 *
58 * lval <=> RVAL_TYPE_LIST | RVAL_TYPE_SCALAR
59 *
60 * Any list or container variable occurring within a scalar or in place of a
61 * scalar is assumed to be iterated i.e. $(name). See comments in iteration.c.
62 *
63 * Any list variable @(name) is *not iterated*, but dropped into place (see
64 * DeRefCopyPromise()).
65 *
66 * Please note that bodies cannot contain iterators.
67 *
68 * The full process of promise and variable expansion is mostly outlined in
69 * ExpandPromise() and ExpandPromiseAndDo() and the basic steps are:
70 *
71 * + Skip everything if the class guard is not defined.
72 *
73 * + DeRefCopyPromise(): *Copy the promise* while expanding '@' slists and body
74 * arguments and handling body inheritance. This requires one round of
75 * expansion with scopeid "body".
76 *
77 * + Push promise frame
78 *
79 * + MapIteratorsFromRval(): Parse all strings (promiser-promisee-constraints),
80 * find all unexpanded variables, mangle them if needed (if they are
81 * namespaced/scoped), and *initialise the wheels* in the iteration engine
82 * (iterctx) to iterate over iterable variables (slists and containers). See
83 * comments in iteration.c for further details.
84 *
85 * + For every iteration:
86 *
87 * - Push iteration frame
88 *
89 * - EvalContextStackPushPromiseIterationFrame()->ExpandDeRefPromise(): Make
90 * another copy of the promise with all constraints evaluated and variables
91 * expanded.
92 *
93 * -- NOTE: As a result all *functions are also evaluated*, even if they are
94 * not to be used immediately (for example promises that the actuator skips
95 * because of ifvarclass, see promises.c:ExpandDeRefPromise() ).
96 *
97 * -- (TODO IS IT CORRECT?) In a sub-bundle, create a new context and make
98 * hashes of the the transferred variables in the temporary context
99 *
100 * - Run the actuator (=act_on_promise= i.e. =VerifyWhateverPromise()=)
101 *
102 * - Pop iteration frame
103 *
104 * + Pop promise frame
105 *
106 */
107
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(pexp->parent_section->promise_type, "vars") == 0 ||
240 strcmp(pexp->parent_section->promise_type, "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 if (!IsDefinedClass(ctx, pp->classes))
261 {
262 return PROMISE_RESULT_SKIPPED;
263 }
264
265 /* 1. Copy the promise while expanding '@' slists and body arguments
266 * (including body inheritance). */
267 Promise *pcopy = DeRefCopyPromise(ctx, pp);
268
269 EvalContextStackPushPromiseFrame(ctx, pcopy);
270 PromiseIterator *iterctx = PromiseIteratorNew(pcopy);
271
272 /* 2. Parse all strings (promiser-promisee-constraints), find all
273 unexpanded variables, mangle them if needed (if they are
274 namespaced/scoped), and start the iteration engine (iterctx) to
275 iterate over slists and containers. */
276
277 MapIteratorsFromRval(ctx, iterctx,
278 (Rval) { pcopy->promiser, RVAL_TYPE_SCALAR });
279
280 if (pcopy->promisee.item != NULL)
281 {
282 MapIteratorsFromRval(ctx, iterctx, pcopy->promisee);
283 }
284
285 bool actuate_ifelse = false;
286 for (size_t i = 0; i < SeqLength(pcopy->conlist); i++)
287 {
288 Constraint *cp = SeqAt(pcopy->conlist, i);
289 if (cp->rval.type == RVAL_TYPE_FNCALL &&
290 strcmp(RvalFnCallValue(cp->rval)->name, "ifelse") == 0)
291 {
292 actuate_ifelse = true;
293 }
294 MapIteratorsFromRval(ctx, iterctx, cp->rval);
295 }
296
297 /* 3. GO! */
298 PutHandleVariable(ctx, pcopy);
299 PromiseResult result = ExpandPromiseAndDo(ctx, iterctx,
300 act_on_promise, param, actuate_ifelse);
301
302 EvalContextStackPopFrame(ctx);
303 PromiseIteratorDestroy(iterctx);
304 PromiseDestroy(pcopy);
305
306 return result;
307 }
308
309
310 /*********************************************************************/
311 /*********************************************************************/
312
ExpandPrivateRval(EvalContext * ctx,const char * ns,const char * scope,const void * rval_item,RvalType rval_type)313 Rval ExpandPrivateRval(EvalContext *ctx,
314 const char *ns, const char *scope,
315 const void *rval_item, RvalType rval_type)
316 {
317 Rval returnval;
318 returnval.item = NULL;
319 returnval.type = RVAL_TYPE_NOPROMISEE;
320
321 switch (rval_type)
322 {
323 case RVAL_TYPE_SCALAR:
324 returnval.item = ExpandScalar(ctx, ns, scope, rval_item, NULL);
325 returnval.type = RVAL_TYPE_SCALAR;
326 break;
327 case RVAL_TYPE_LIST:
328 returnval.item = ExpandList(ctx, ns, scope, rval_item, true);
329 returnval.type = RVAL_TYPE_LIST;
330 break;
331
332 case RVAL_TYPE_FNCALL:
333 returnval.item = ExpandFnCall(ctx, ns, scope, rval_item);
334 returnval.type = RVAL_TYPE_FNCALL;
335 break;
336
337 case RVAL_TYPE_CONTAINER:
338 returnval = RvalNew(rval_item, RVAL_TYPE_CONTAINER);
339 break;
340
341 case RVAL_TYPE_NOPROMISEE:
342 break;
343 }
344
345 return returnval;
346 }
347
ExpandListEntry(EvalContext * ctx,const char * ns,const char * scope,int expandnaked,Rval entry)348 static Rval ExpandListEntry(EvalContext *ctx,
349 const char *ns, const char *scope,
350 int expandnaked, Rval entry)
351 {
352 if (entry.type == RVAL_TYPE_SCALAR &&
353 IsNakedVar(entry.item, '@'))
354 {
355 if (expandnaked)
356 {
357 char naked[CF_MAXVARSIZE];
358 GetNaked(naked, entry.item);
359
360 if (IsExpandable(naked))
361 {
362 char *exp = ExpandScalar(ctx, ns, scope, naked, NULL);
363 strlcpy(naked, exp, sizeof(naked)); /* TODO err */
364 free(exp);
365 }
366
367 /* Check again, it might have changed. */
368 if (!IsExpandable(naked))
369 {
370 VarRef *ref = VarRefParseFromScope(naked, scope);
371
372 DataType value_type;
373 const void *value = EvalContextVariableGet(ctx, ref, &value_type);
374 VarRefDestroy(ref);
375
376 if (value_type != CF_DATA_TYPE_NONE) /* variable found? */
377 {
378 return ExpandPrivateRval(ctx, ns, scope, value,
379 DataTypeToRvalType(value_type));
380 }
381 }
382 }
383 else
384 {
385 return RvalNew(entry.item, RVAL_TYPE_SCALAR);
386 }
387 }
388
389 return ExpandPrivateRval(ctx, ns, scope, entry.item, entry.type);
390 }
391
ExpandList(EvalContext * ctx,const char * ns,const char * scope,const Rlist * list,int expandnaked)392 Rlist *ExpandList(EvalContext *ctx,
393 const char *ns, const char *scope,
394 const Rlist *list, int expandnaked)
395 {
396 Rlist *start = NULL;
397
398 for (const Rlist *rp = list; rp != NULL; rp = rp->next)
399 {
400 Rval returnval = ExpandListEntry(ctx, ns, scope, expandnaked, rp->val);
401 RlistAppend(&start, returnval.item, returnval.type);
402 RvalDestroy(returnval);
403 }
404
405 return start;
406 }
407
408 /*********************************************************************/
409
ExpandBundleReference(EvalContext * ctx,const char * ns,const char * scope,Rval rval)410 Rval ExpandBundleReference(EvalContext *ctx,
411 const char *ns, const char *scope,
412 Rval rval)
413 {
414 // Allocates new memory for the copy
415 switch (rval.type)
416 {
417 case RVAL_TYPE_SCALAR:
418 return (Rval) { ExpandScalar(ctx, ns, scope, RvalScalarValue(rval), NULL),
419 RVAL_TYPE_SCALAR };
420
421 case RVAL_TYPE_FNCALL:
422 return (Rval) { ExpandFnCall(ctx, ns, scope, RvalFnCallValue(rval)),
423 RVAL_TYPE_FNCALL};
424
425 case RVAL_TYPE_CONTAINER:
426 case RVAL_TYPE_LIST:
427 case RVAL_TYPE_NOPROMISEE:
428 return RvalNew(NULL, RVAL_TYPE_NOPROMISEE);
429 }
430
431 assert(false);
432 return RvalNew(NULL, RVAL_TYPE_NOPROMISEE);
433 }
434
435 /**
436 * Expand a #string into Buffer #out, returning the pointer to the string
437 * itself, inside the Buffer #out. If #out is NULL then the buffer will be
438 * created and destroyed internally.
439 *
440 * @retval NULL something went wrong
441 */
ExpandScalar(const EvalContext * ctx,const char * ns,const char * scope,const char * string,Buffer * out)442 char *ExpandScalar(const EvalContext *ctx, const char *ns, const char *scope,
443 const char *string, Buffer *out)
444 {
445 bool out_belongs_to_us = false;
446
447 if (out == NULL)
448 {
449 out = BufferNew();
450 out_belongs_to_us = true;
451 }
452
453 assert(string != NULL);
454 assert(out != NULL);
455 Buffer *current_item = BufferNew();
456
457 for (const char *sp = string; *sp != '\0'; sp++)
458 {
459 BufferClear(current_item);
460 ExtractScalarPrefix(current_item, sp, strlen(sp));
461
462 BufferAppend(out, BufferData(current_item), BufferSize(current_item));
463 sp += BufferSize(current_item);
464 if (*sp == '\0')
465 {
466 break;
467 }
468
469 BufferClear(current_item);
470 char varstring = sp[1];
471 ExtractScalarReference(current_item, sp, strlen(sp), true);
472 sp += BufferSize(current_item) + 2;
473
474 if (IsCf3VarString(BufferData(current_item)))
475 {
476 Buffer *temp = BufferCopy(current_item);
477 BufferClear(current_item);
478 ExpandScalar(ctx, ns, scope, BufferData(temp), current_item);
479 BufferDestroy(temp);
480 }
481
482 if (!IsExpandable(BufferData(current_item)))
483 {
484 VarRef *ref = VarRefParseFromNamespaceAndScope(
485 BufferData(current_item),
486 ns, scope, CF_NS, '.');
487 DataType value_type;
488 const void *value = EvalContextVariableGet(ctx, ref, &value_type);
489 VarRefDestroy(ref);
490
491 switch (DataTypeToRvalType(value_type))
492 {
493 case RVAL_TYPE_SCALAR:
494 assert(value != NULL);
495 BufferAppendString(out, value);
496 continue;
497 break;
498
499 case RVAL_TYPE_CONTAINER:
500 {
501 assert(value != NULL);
502 const JsonElement *jvalue = value; /* instead of casts */
503 if (JsonGetElementType(jvalue) == JSON_ELEMENT_TYPE_PRIMITIVE)
504 {
505 BufferAppendString(out, JsonPrimitiveGetAsString(jvalue));
506 continue;
507 }
508 break;
509 }
510 default:
511 /* TODO Log() */
512 break;
513 }
514 }
515
516 if (varstring == '{')
517 {
518 BufferAppendF(out, "${%s}", BufferData(current_item));
519 }
520 else
521 {
522 BufferAppendF(out, "$(%s)", BufferData(current_item));
523 }
524 }
525
526 BufferDestroy(current_item);
527
528 LogDebug(LOG_MOD_EXPAND, "ExpandScalar( %s : %s . %s ) => %s",
529 SAFENULL(ns), SAFENULL(scope), string, BufferData(out));
530
531 return out_belongs_to_us ? BufferClose(out) : BufferGet(out);
532 }
533
534 /*********************************************************************/
535
EvaluateFinalRval(EvalContext * ctx,const Policy * policy,const char * ns,const char * scope,Rval rval,bool forcelist,const Promise * pp)536 Rval EvaluateFinalRval(EvalContext *ctx, const Policy *policy,
537 const char *ns, const char *scope,
538 Rval rval, bool forcelist, const Promise *pp)
539 {
540 assert(ctx);
541 assert(policy);
542 Rval returnval;
543
544 /* Treat lists specially. */
545 if (rval.type == RVAL_TYPE_SCALAR && IsNakedVar(rval.item, '@'))
546 {
547 char naked[CF_MAXVARSIZE];
548 GetNaked(naked, rval.item);
549
550 if (IsExpandable(naked)) /* example: @(blah_$(blue)) */
551 {
552 returnval = ExpandPrivateRval(ctx, NULL, "this", rval.item, rval.type);
553 }
554 else
555 {
556 VarRef *ref = VarRefParseFromScope(naked, scope);
557 DataType value_type;
558 const void *value = EvalContextVariableGet(ctx, ref, &value_type);
559 VarRefDestroy(ref);
560
561 if (DataTypeToRvalType(value_type) == RVAL_TYPE_LIST)
562 {
563 returnval.item = ExpandList(ctx, ns, scope, value, true);
564 returnval.type = RVAL_TYPE_LIST;
565 }
566 else
567 {
568 returnval = ExpandPrivateRval(ctx, NULL, "this", rval.item, rval.type);
569 }
570 }
571 }
572 else if (forcelist) /* We are replacing scalar @(name) with list */
573 {
574 returnval = ExpandPrivateRval(ctx, ns, scope, rval.item, rval.type);
575 }
576 else if (FnCallIsBuiltIn(rval))
577 {
578 returnval = RvalCopy(rval);
579 }
580 else
581 {
582 returnval = ExpandPrivateRval(ctx, NULL, "this", rval.item, rval.type);
583 }
584
585 switch (returnval.type)
586 {
587 case RVAL_TYPE_SCALAR:
588 case RVAL_TYPE_CONTAINER:
589 break;
590
591 case RVAL_TYPE_LIST:
592 for (Rlist *rp = RvalRlistValue(returnval); rp; rp = rp->next)
593 {
594 switch (rp->val.type)
595 {
596 case RVAL_TYPE_FNCALL:
597 {
598 FnCall *fp = RlistFnCallValue(rp);
599 rp->val = FnCallEvaluate(ctx, policy, fp, pp).rval;
600 FnCallDestroy(fp);
601 break;
602 }
603 case RVAL_TYPE_SCALAR:
604 if (EvalContextStackCurrentPromise(ctx) &&
605 IsCf3VarString(RlistScalarValue(rp)))
606 {
607 void *prior = rp->val.item;
608 rp->val = ExpandPrivateRval(ctx, NULL, "this",
609 prior, RVAL_TYPE_SCALAR);
610 free(prior);
611 }
612 /* else: returnval unchanged. */
613 break;
614 default:
615 assert(!"Bad type for entry in Rlist");
616 }
617 }
618 break;
619
620 case RVAL_TYPE_FNCALL:
621 if (FnCallIsBuiltIn(returnval))
622 {
623 FnCall *fp = RvalFnCallValue(returnval);
624 returnval = FnCallEvaluate(ctx, policy, fp, pp).rval;
625 FnCallDestroy(fp);
626 }
627 break;
628
629 default:
630 assert(returnval.item == NULL); /* else we're leaking it */
631 returnval.item = NULL;
632 returnval.type = RVAL_TYPE_NOPROMISEE;
633 break;
634 }
635
636 return returnval;
637 }
638
639 /*********************************************************************/
640
BundleResolvePromiseType(EvalContext * ctx,const Bundle * bundle,const char * type,PromiseActuator * actuator)641 void BundleResolvePromiseType(EvalContext *ctx, const Bundle *bundle, const char *type, PromiseActuator *actuator)
642 {
643 for (size_t j = 0; j < SeqLength(bundle->sections); j++)
644 {
645 BundleSection *section = SeqAt(bundle->sections, j);
646
647 if (strcmp(section->promise_type, type) == 0)
648 {
649 EvalContextStackPushBundleSectionFrame(ctx, section);
650 for (size_t i = 0; i < SeqLength(section->promises); i++)
651 {
652 Promise *pp = SeqAt(section->promises, i);
653 ExpandPromise(ctx, pp, actuator, NULL);
654 }
655 EvalContextStackPopFrame(ctx);
656 }
657 }
658 }
659
PointerCmp(const void * a,const void * b,ARG_UNUSED void * user_data)660 static int PointerCmp(const void *a, const void *b, ARG_UNUSED void *user_data)
661 {
662 if (a < b)
663 {
664 return -1;
665 }
666 else if (a == b)
667 {
668 return 0;
669 }
670 else
671 {
672 return 1;
673 }
674 }
675
RemoveRemotelyInjectedVars(const EvalContext * ctx,const Bundle * bundle)676 static void RemoveRemotelyInjectedVars(const EvalContext *ctx, const Bundle *bundle)
677 {
678 const Seq *remote_var_promises = EvalContextGetRemoteVarPromises(ctx, bundle->name);
679 if ((remote_var_promises == NULL) || SeqLength(remote_var_promises) == 0)
680 {
681 /* nothing to do here */
682 return;
683 }
684
685 size_t promises_length = SeqLength(remote_var_promises);
686 Seq *remove_vars = SeqNew(promises_length, NULL);
687
688 /* remove variables that have been attempted to be inserted into this
689 * bundle */
690 /* TODO: this is expensive and should be removed! */
691 for (size_t i = 0; i < promises_length; i++)
692 {
693 const Promise *pp = (Promise *) SeqAt(remote_var_promises, i);
694
695 VariableTableIterator *iter = EvalContextVariableTableIteratorNew(ctx, NULL, bundle->name, NULL);
696 const Variable *var = VariableTableIteratorNext(iter);
697 while (var != NULL)
698 {
699 /* variables are stored together with their original promises (org_pp) */
700 const Promise *var_promise = VariableGetPromise(var);
701 const VarRef *var_ref = VariableGetRef(var);
702 if (var_promise && var_promise->org_pp == pp)
703 {
704 Log(LOG_LEVEL_ERR, "Ignoring remotely-injected variable '%s'",
705 var_ref->lval);
706 /* avoid modifications of the variable table being iterated
707 * over and avoid trying to remove the same variable twice */
708 SeqAppendOnce(remove_vars, (void *) var, PointerCmp);
709 }
710 var = VariableTableIteratorNext(iter);
711 }
712 VariableTableIteratorDestroy(iter);
713 }
714
715 /* iteration over the variable table done, time to remove the variables */
716 size_t remove_vars_length = SeqLength(remove_vars);
717 for (size_t i = 0; i < remove_vars_length; i++)
718 {
719 Variable *var = (Variable *) SeqAt(remove_vars, i);
720 const VarRef *var_ref = VariableGetRef(var);
721 if (var_ref != NULL)
722 {
723 EvalContextVariableRemove(ctx, var_ref);
724 }
725 }
726 SeqDestroy(remove_vars);
727 }
728
BundleResolve(EvalContext * ctx,const Bundle * bundle)729 void BundleResolve(EvalContext *ctx, const Bundle *bundle)
730 {
731 Log(LOG_LEVEL_DEBUG,
732 "Resolving classes and variables in 'bundle %s %s'",
733 bundle->type, bundle->name);
734
735 /* first check if some variables were injected remotely into this bundle and
736 * remove them (CFE-1915) */
737 RemoveRemotelyInjectedVars(ctx, bundle);
738
739 /* PRE-EVAL: evaluate classes of common bundles. */
740 if (strcmp(bundle->type, "common") == 0)
741 {
742 /* Necessary to parse vars *before* classes for cases like this:
743 * 00_basics/04_bundles/dynamic_bundlesequence/dynamic_inputs_based_on_class_set_using_variable_file_control_extends_inputs.cf.sub
744 * -- see bundle "classify". */
745 BundleResolvePromiseType(ctx, bundle, "vars", VerifyVarPromise);
746
747 BundleResolvePromiseType(ctx, bundle, "classes", VerifyClassPromise);
748 }
749
750 /* Necessary to also parse vars *after* classes,
751 * because "inputs" might be affected in cases like:
752 * 00_basics/04_bundles/dynamic_bundlesequence/dynamic_inputs_based_on_list_variable_dependent_on_class.cf */
753 BundleResolvePromiseType(ctx, bundle, "vars", VerifyVarPromise);
754 }
755
756 /**
757 * Evaluate the relevant control body, and set the
758 * relevant fields in #ctx and #config.
759 */
ResolveControlBody(EvalContext * ctx,GenericAgentConfig * config,const Body * control_body)760 static void ResolveControlBody(EvalContext *ctx, GenericAgentConfig *config,
761 const Body *control_body)
762 {
763 const char *filename = control_body->source_path;
764
765 assert(CFG_CONTROLBODY[COMMON_CONTROL_MAX].lval == NULL);
766
767 const ConstraintSyntax *body_syntax = NULL;
768 for (int i = 0; CONTROL_BODIES[i].constraints != NULL; i++)
769 {
770 body_syntax = CONTROL_BODIES[i].constraints;
771
772 if (strcmp(control_body->type, CONTROL_BODIES[i].body_type) == 0)
773 {
774 break;
775 }
776 }
777 if (body_syntax == NULL)
778 {
779 FatalError(ctx, "Unknown control body: %s", control_body->type);
780 }
781
782 char *scope;
783 assert(strcmp(control_body->name, "control") == 0);
784 xasprintf(&scope, "control_%s", control_body->type);
785
786 Log(LOG_LEVEL_DEBUG, "Initiate control variable convergence for scope '%s'", scope);
787
788 EvalContextStackPushBodyFrame(ctx, NULL, control_body, NULL);
789
790 for (size_t i = 0; i < SeqLength(control_body->conlist); i++)
791 {
792 const char *lval;
793 Rval evaluated_rval;
794 size_t lineno;
795
796 /* Use nested scope to constrain cp. */
797 {
798 Constraint *cp = SeqAt(control_body->conlist, i);
799 lval = cp->lval;
800 lineno = cp->offset.line;
801
802 if (!IsDefinedClass(ctx, cp->classes))
803 {
804 continue;
805 }
806
807 if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_BUNDLESEQUENCE].lval) == 0)
808 {
809 evaluated_rval = ExpandPrivateRval(ctx, NULL, scope,
810 cp->rval.item, cp->rval.type);
811 }
812 else
813 {
814 evaluated_rval = EvaluateFinalRval(ctx, control_body->parent_policy,
815 NULL, scope, cp->rval,
816 true, NULL);
817 }
818
819 } /* Close scope: assert we only use evaluated_rval, not cp->rval. */
820
821 VarRef *ref = VarRefParseFromScope(lval, scope);
822 EvalContextVariableRemove(ctx, ref);
823
824 DataType rval_proper_datatype =
825 ConstraintSyntaxGetDataType(body_syntax, lval);
826 if (evaluated_rval.type != DataTypeToRvalType(rval_proper_datatype))
827 {
828 Log(LOG_LEVEL_ERR,
829 "Attribute '%s' in %s:%zu is of wrong type, skipping",
830 lval, filename, lineno);
831 VarRefDestroy(ref);
832 RvalDestroy(evaluated_rval);
833 continue;
834 }
835
836 bool success = EvalContextVariablePut(
837 ctx, ref, evaluated_rval.item, rval_proper_datatype,
838 "source=promise");
839 if (!success)
840 {
841 Log(LOG_LEVEL_ERR,
842 "Attribute '%s' in %s:%zu can't be added, skipping",
843 lval, filename, lineno);
844 VarRefDestroy(ref);
845 RvalDestroy(evaluated_rval);
846 continue;
847 }
848
849 VarRefDestroy(ref);
850
851 if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_OUTPUT_PREFIX].lval) == 0)
852 {
853 strlcpy(VPREFIX, RvalScalarValue(evaluated_rval),
854 sizeof(VPREFIX));
855 }
856
857 if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_DOMAIN].lval) == 0)
858 {
859 strlcpy(VDOMAIN, RvalScalarValue(evaluated_rval),
860 sizeof(VDOMAIN));
861 Log(LOG_LEVEL_VERBOSE, "SET domain = %s", VDOMAIN);
862
863 EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_SYS, "domain");
864 EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_SYS, "fqhost");
865 snprintf(VFQNAME, CF_MAXVARSIZE, "%s.%s", VUQNAME, VDOMAIN);
866 EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "fqhost",
867 VFQNAME, CF_DATA_TYPE_STRING,
868 "inventory,source=agent,attribute_name=Host name");
869 EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, "domain",
870 VDOMAIN, CF_DATA_TYPE_STRING,
871 "source=agent");
872 EvalContextClassPutHard(ctx, VDOMAIN, "source=agent");
873 }
874
875 if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_IGNORE_MISSING_INPUTS].lval) == 0)
876 {
877 Log(LOG_LEVEL_VERBOSE, "SET ignore_missing_inputs %s",
878 RvalScalarValue(evaluated_rval));
879 config->ignore_missing_inputs = BooleanFromString(
880 RvalScalarValue(evaluated_rval));
881 }
882
883 if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_IGNORE_MISSING_BUNDLES].lval) == 0)
884 {
885 Log(LOG_LEVEL_VERBOSE, "SET ignore_missing_bundles %s",
886 RvalScalarValue(evaluated_rval));
887 config->ignore_missing_bundles = BooleanFromString(
888 RvalScalarValue(evaluated_rval));
889 }
890
891 if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_CACHE_SYSTEM_FUNCTIONS].lval) == 0)
892 {
893 Log(LOG_LEVEL_VERBOSE, "SET cache_system_functions %s",
894 RvalScalarValue(evaluated_rval));
895 bool cache_system_functions = BooleanFromString(
896 RvalScalarValue(evaluated_rval));
897 EvalContextSetEvalOption(ctx, EVAL_OPTION_CACHE_SYSTEM_FUNCTIONS,
898 cache_system_functions);
899 }
900
901 if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_PROTOCOL_VERSION].lval) == 0)
902 {
903 config->protocol_version = ProtocolVersionParse(
904 RvalScalarValue(evaluated_rval));
905 Log(LOG_LEVEL_VERBOSE, "SET common protocol_version: %s",
906 ProtocolVersionString(config->protocol_version));
907 }
908
909 /* Those are package_inventory and package_module common control body options */
910 if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_PACKAGE_INVENTORY].lval) == 0)
911 {
912 AddDefaultInventoryToContext(ctx, RvalRlistValue(evaluated_rval));
913 Log(LOG_LEVEL_VERBOSE, "SET common package_inventory list");
914 }
915 if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_PACKAGE_MODULE].lval) == 0)
916 {
917 AddDefaultPackageModuleToContext(ctx, RvalScalarValue(evaluated_rval));
918 Log(LOG_LEVEL_VERBOSE, "SET common package_module: %s",
919 RvalScalarValue(evaluated_rval));
920 }
921
922 if (strcmp(lval, CFG_CONTROLBODY[COMMON_CONTROL_GOALPATTERNS].lval) == 0)
923 {
924 /* Ignored */
925 }
926
927 RvalDestroy(evaluated_rval);
928 }
929
930 EvalContextStackPopFrame(ctx);
931 free(scope);
932 }
933
ResolvePackageManagerBody(EvalContext * ctx,const Body * pm_body)934 static void ResolvePackageManagerBody(EvalContext *ctx, const Body *pm_body)
935 {
936 PackageModuleBody *new_manager = xcalloc(1, sizeof(PackageModuleBody));
937 new_manager->name = SafeStringDuplicate(pm_body->name);
938
939 for (size_t i = 0; i < SeqLength(pm_body->conlist); i++)
940 {
941 Constraint *cp = SeqAt(pm_body->conlist, i);
942
943 Rval returnval = {0};
944
945 if (IsDefinedClass(ctx, cp->classes))
946 {
947 returnval = ExpandPrivateRval(ctx, NULL, "body",
948 cp->rval.item, cp->rval.type);
949 }
950
951 if (returnval.item == NULL || returnval.type == RVAL_TYPE_NOPROMISEE)
952 {
953 Log(LOG_LEVEL_VERBOSE, "have invalid constraint while resolving"
954 "package promise body: %s", cp->lval);
955
956 RvalDestroy(returnval);
957 continue;
958 }
959
960 if (strcmp(cp->lval, "query_installed_ifelapsed") == 0)
961 {
962 new_manager->installed_ifelapsed =
963 (int)IntFromString(RvalScalarValue(returnval));
964 }
965 else if (strcmp(cp->lval, "query_updates_ifelapsed") == 0)
966 {
967 new_manager->updates_ifelapsed =
968 (int)IntFromString(RvalScalarValue(returnval));
969 }
970 else if (strcmp(cp->lval, "default_options") == 0)
971 {
972 new_manager->options = RlistCopy(RvalRlistValue(returnval));
973 }
974 else if (strcmp(cp->lval, "interpreter") == 0)
975 {
976 new_manager->interpreter = SafeStringDuplicate(RvalScalarValue(returnval));
977 }
978 else if (strcmp(cp->lval, "module_path") == 0)
979 {
980 new_manager->module_path = SafeStringDuplicate(RvalScalarValue(returnval));
981 }
982 else
983 {
984 /* This should be handled by the parser. */
985 assert(0);
986 }
987 RvalDestroy(returnval);
988 }
989 AddPackageModuleToContext(ctx, new_manager);
990 }
991
PolicyResolve(EvalContext * ctx,const Policy * policy,GenericAgentConfig * config)992 void PolicyResolve(EvalContext *ctx, const Policy *policy,
993 GenericAgentConfig *config)
994 {
995 /* PRE-EVAL: common bundles: classes,vars. */
996 for (size_t i = 0; i < SeqLength(policy->bundles); i++)
997 {
998 Bundle *bundle = SeqAt(policy->bundles, i);
999 if (strcmp("common", bundle->type) == 0)
1000 {
1001 EvalContextStackPushBundleFrame(ctx, bundle, NULL, false);
1002 BundleResolve(ctx, bundle); /* PRE-EVAL classes,vars */
1003 EvalContextStackPopFrame(ctx);
1004 }
1005 }
1006
1007 /*
1008 * HACK: yet another pre-eval pass here, WHY? TODO remove, but test fails:
1009 * 00_basics/03_bodies/dynamic_inputs_findfiles.cf
1010 */
1011 #if 1
1012
1013 /* PRE-EVAL: non-common bundles: only vars. */
1014 for (size_t i = 0; i < SeqLength(policy->bundles); i++)
1015 {
1016 Bundle *bundle = SeqAt(policy->bundles, i);
1017 if (strcmp("common", bundle->type) != 0)
1018 {
1019 EvalContextStackPushBundleFrame(ctx, bundle, NULL, false);
1020 BundleResolve(ctx, bundle); /* PRE-EVAL vars */
1021 EvalContextStackPopFrame(ctx);
1022 }
1023 }
1024
1025 #endif
1026
1027 for (size_t i = 0; i < SeqLength(policy->bodies); i++)
1028 {
1029 Body *bdp = SeqAt(policy->bodies, i);
1030
1031 if (strcmp(bdp->name, "control") == 0)
1032 {
1033 ResolveControlBody(ctx, config, bdp);
1034 }
1035 /* Collect all package managers data from policy as we don't know yet
1036 * which ones we will use. */
1037 else if (strcmp(bdp->type, "package_module") == 0)
1038 {
1039 ResolvePackageManagerBody(ctx, bdp);
1040 }
1041 }
1042 }
1043
IsExpandable(const char * str)1044 bool IsExpandable(const char *str)
1045 {
1046 char left = 'x', right = 'x';
1047 int dollar = false;
1048 int bracks = 0, vars = 0;
1049
1050 for (const char *sp = str; *sp != '\0'; sp++) /* check for varitems */
1051 {
1052 switch (*sp)
1053 {
1054 case '$':
1055 if (*(sp + 1) == '{' || *(sp + 1) == '(')
1056 {
1057 dollar = true;
1058 }
1059 break;
1060 case '(':
1061 case '{':
1062 if (dollar)
1063 {
1064 left = *sp;
1065 bracks++;
1066 }
1067 break;
1068 case ')':
1069 case '}':
1070 if (dollar)
1071 {
1072 bracks--;
1073 right = *sp;
1074 }
1075 break;
1076 }
1077
1078 if (left == '(' && right == ')' && dollar && (bracks == 0))
1079 {
1080 vars++;
1081 dollar = false;
1082 }
1083
1084 if (left == '{' && right == '}' && dollar && (bracks == 0))
1085 {
1086 vars++;
1087 dollar = false;
1088 }
1089 }
1090
1091 if (bracks != 0)
1092 {
1093 Log(LOG_LEVEL_DEBUG, "If this is an expandable variable string then it contained syntax errors");
1094 return false;
1095 }
1096
1097 if (vars > 0)
1098 {
1099 Log(LOG_LEVEL_DEBUG,
1100 "Expanding variable '%s': found %d variables", str, vars);
1101 }
1102 return (vars > 0);
1103 }
1104
1105 /*********************************************************************/
1106
opposite(char c)1107 static char opposite(char c)
1108 {
1109 switch (c)
1110 {
1111 case '(': return ')';
1112 case '{': return '}';
1113 default : ProgrammingError("Was expecting '(' or '{' but got: '%c'", c);
1114 }
1115 return 0;
1116 }
1117
1118 /**
1119 * Check if #str contains one and only one variable expansion of #vtype kind
1120 * (it's usually either '$' or '@'). It can contain nested expansions which
1121 * are not checked properly. Examples:
1122 * true: "$(whatever)", "${whatever}", "$(blah$(blue))"
1123 * false: "$(blah)blue", "blah$(blue)", "$(blah)$(blue)", "$(blah}"
1124 */
IsNakedVar(const char * str,char vtype)1125 bool IsNakedVar(const char *str, char vtype)
1126 {
1127 size_t len = strlen(str);
1128 char last = len > 0 ? str[len-1] : 0;
1129
1130 if (len < 3
1131 || str[0] != vtype
1132 || (str[1] != '(' && str[1] != '{')
1133 || last != opposite(str[1]))
1134 {
1135 return false;
1136 }
1137
1138 /* TODO check if nesting happens correctly? Is it needed? */
1139 size_t count = 0;
1140 for (const char *sp = str; *sp != '\0'; sp++)
1141 {
1142 switch (*sp)
1143 {
1144 case '(':
1145 case '{':
1146 count++;
1147 break;
1148 case ')':
1149 case '}':
1150 count--;
1151
1152 /* Make sure the end of the variable is the last character. */
1153 if (count == 0 && sp[1] != '\0')
1154 {
1155 return false;
1156 }
1157
1158 break;
1159 }
1160 }
1161
1162 if (count != 0)
1163 {
1164 return false;
1165 }
1166
1167 return true;
1168 }
1169
1170 /*********************************************************************/
1171
1172 /**
1173 * Copy @(listname) -> listname.
1174 *
1175 * This function performs no validations, it is necessary to call the
1176 * validation functions before calling this function.
1177 *
1178 * @NOTE make sure sizeof(dst) >= sizeof(s)
1179 */
GetNaked(char * dst,const char * s)1180 void GetNaked(char *dst, const char *s)
1181 {
1182 size_t s_len = strlen(s);
1183
1184 if (s_len < 4 || s_len + 3 >= CF_MAXVARSIZE)
1185 {
1186 Log(LOG_LEVEL_ERR,
1187 "@(variable) expected, but got malformed: %s", s);
1188 strlcpy(dst, s, CF_MAXVARSIZE);
1189 return;
1190 }
1191
1192 memcpy(dst, &s[2], s_len - 3);
1193 dst[s_len - 3] = '\0';
1194 }
1195
1196 /*********************************************************************/
1197
1198 /**
1199 * Checks if a variable is an @-list and returns true or false.
1200 */
IsVarList(const char * var)1201 bool IsVarList(const char *var)
1202 {
1203 if ('@' != var[0])
1204 {
1205 return false;
1206 }
1207 /*
1208 * Minimum size for a list is 4:
1209 * '@' + '(' + name + ')'
1210 */
1211 if (strlen(var) < 4)
1212 {
1213 return false;
1214 }
1215 return true;
1216 }
1217
CommonEvalPromise(EvalContext * ctx,const Promise * pp,ARG_UNUSED void * param)1218 PromiseResult CommonEvalPromise(EvalContext *ctx, const Promise *pp,
1219 ARG_UNUSED void *param)
1220 {
1221 assert(param == NULL);
1222
1223 PromiseRecheckAllConstraints(ctx, pp);
1224
1225 return PROMISE_RESULT_NOOP;
1226 }
1227