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