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