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 <verify_vars.h>
26 
27 #include <actuator.h>
28 #include <attributes.h>
29 #include <regex.h>      /* CompileRegex,StringMatchFullWithPrecompiledRegex */
30 #include <buffer.h>
31 #include <misc_lib.h>
32 #include <fncall.h>
33 #include <rlist.h>
34 #include <conversion.h>
35 #include <expand.h>
36 #include <scope.h>
37 #include <promises.h>
38 #include <vars.h>
39 #include <matching.h>
40 #include <syntax.h>
41 #include <audit.h>
42 #include <string_lib.h>
43 #include <cleanup.h>
44 
45 typedef struct
46 {
47     bool should_converge;
48     bool ok_redefine;
49     bool drop_undefined;
50     Constraint *cp_save; // e.g. string => "foo"
51 } ConvergeVariableOptions;
52 
53 
54 static ConvergeVariableOptions CollectConvergeVariableOptions(EvalContext *ctx, const Promise *pp);
55 static bool Epimenides(EvalContext *ctx, const char *ns, const char *scope, const char *var, Rval rval, int level);
56 static bool CompareRval(const void *rval1_item, RvalType rval1_type, const void *rval2_item, RvalType rval2_type);
57 
IsLegalVariableName(EvalContext * ctx,const Promise * pp)58 static bool IsLegalVariableName(EvalContext *ctx, const Promise *pp)
59 {
60     const char *var_name = pp->promiser;
61 
62     /* TODO: remove at some point (global, leaked), but for now
63      * this offers an attractive speedup. */
64     static pcre *rx = NULL;
65     if (!rx)
66     {
67         /* \200-\377 is there for multibyte unicode characters */
68         rx = CompileRegex("[a-zA-Z0-9_\200-\377.]+(\\[.+\\])*"); /* Known leak, see TODO. */
69     }
70 
71     if (!StringMatchFullWithPrecompiledRegex(rx, var_name))
72     {
73         return false;
74     }
75 
76     char *bracket = strchr(var_name, '[');
77     char *dot = strchr(var_name, '.');
78     /* we only care about variable name prefix, not dots inside array keys */
79     if ((dot != NULL) && ((bracket == NULL) || (dot < bracket)))
80     {
81         /* detect and prevent remote bundle variable injection (CFE-1915) */
82         char *prefix = xstrndup(var_name, dot - var_name);
83         const Bundle *cur_bundle = PromiseGetBundle(pp);
84 
85         if (!StringEqual(prefix, cur_bundle->name))
86         {
87             Log(LOG_LEVEL_VERBOSE,
88                 "Variable '%s' may be attempted to be injected into a remote bundle",
89                 var_name);
90             if (StringSetContains(EvalContextGetBundleNames(ctx), prefix))
91             {
92                 Log(LOG_LEVEL_ERR, "Remote bundle variable injection detected!");
93                 free(prefix);
94                 return false;
95             }
96             /* the conflicting bundle may be defined later, we need to remember
97              * this promise as potentially dangerous */
98             EvalContextPushRemoteVarPromise(ctx, prefix, pp->org_pp);
99         }
100         free(prefix);
101     }
102 
103     return true;
104 }
105 
106 // TODO why not printing that new definition is skipped?
107 // TODO what with ifdefined?
108 
VerifyVarPromise(EvalContext * ctx,const Promise * pp,ARG_UNUSED void * param)109 PromiseResult VerifyVarPromise(EvalContext *ctx, const Promise *pp,
110                                ARG_UNUSED void *param)
111 {
112     ConvergeVariableOptions opts = CollectConvergeVariableOptions(ctx, pp);
113 
114     Log(LOG_LEVEL_DEBUG, "Evaluating vars promise: %s", pp->promiser);
115     LogDebug(LOG_MOD_VARS,
116              "ok_redefine=%d, drop_undefined=%d, should_converge=%d",
117              opts.ok_redefine, opts.drop_undefined, opts.should_converge);
118 
119     if (!opts.should_converge)
120     {
121         LogDebug(LOG_MOD_VARS,
122                  "Skipping vars promise because should_converge=false");
123         return PROMISE_RESULT_NOOP;
124     }
125 
126 //    opts.drop_undefined = true;         /* always remove @{unresolved_list} */
127 
128     Attributes a = ZeroAttributes;
129     // More consideration needs to be given to using these
130     //a.transaction = GetTransactionConstraints(pp);
131 
132     /* Warn if promise locking was used with a promise that doesn't support it
133      * (which applies to all of 'vars', 'meta' and 'defaults' promises handled
134      * by this code).
135      * (Only do this in the first pass in cf-promises, we don't have to repeat
136      * the warning over and over.) */
137     if (EvalContextGetPass(ctx) == 0 && THIS_AGENT_TYPE == AGENT_TYPE_COMMON)
138     {
139         int ifelapsed = PromiseGetConstraintAsInt(ctx, "ifelapsed", pp);
140         if (ifelapsed != CF_NOINT)
141         {
142             Log(LOG_LEVEL_WARNING,
143                 "ifelapsed attribute specified in action body for %s promise '%s',"
144                 " but %s promises do not support promise locking",
145                 pp->parent_section->promise_type, pp->promiser,
146                 pp->parent_section->promise_type);
147         }
148         int expireafter = PromiseGetConstraintAsInt(ctx, "expireafter", pp);
149         if (expireafter != CF_NOINT)
150         {
151             Log(LOG_LEVEL_WARNING,
152                 "expireafter attribute specified in action body for %s promise '%s',"
153                 " but %s promises do not support promise locking",
154                 pp->parent_section->promise_type, pp->promiser,
155                 pp->parent_section->promise_type);
156         }
157     }
158 
159     a.classes = GetClassDefinitionConstraints(ctx, pp);
160 
161     VarRef *ref = VarRefParseFromBundle(pp->promiser, PromiseGetBundle(pp));
162     if (strcmp("meta", pp->parent_section->promise_type) == 0)
163     {
164         VarRefSetMeta(ref, true);
165     }
166 
167     DataType existing_value_type = CF_DATA_TYPE_NONE;
168     const void *existing_value;
169     if (IsExpandable(pp->promiser))
170     {
171         existing_value = NULL;
172     }
173     else
174     {
175         existing_value = EvalContextVariableGet(ctx, ref, &existing_value_type);
176     }
177 
178     Rval rval = opts.cp_save->rval;
179     PromiseResult result;
180 
181     if (rval.item != NULL || rval.type == RVAL_TYPE_LIST)
182     {
183         DataType data_type = DataTypeFromString(opts.cp_save->lval);
184 
185         if (opts.cp_save->rval.type == RVAL_TYPE_FNCALL)
186         {
187             FnCall *fp = RvalFnCallValue(rval);
188             const FnCallType *fn = FnCallTypeGet(fp->name);
189             if (!fn)
190             {
191                 assert(false && "Canary: should have been caught before this point");
192                 FatalError(ctx, "While setting variable '%s' in bundle '%s', unknown function '%s'",
193                            pp->promiser, PromiseGetBundle(pp)->name, fp->name);
194             }
195 
196             if (fn->dtype != DataTypeFromString(opts.cp_save->lval))
197             {
198                 FatalError(ctx, "While setting variable '%s' in bundle '%s', variable declared type '%s' but function '%s' returns type '%s'",
199                            pp->promiser, PromiseGetBundle(pp)->name, opts.cp_save->lval,
200                            fp->name, DataTypeToString(fn->dtype));
201             }
202 
203             if (existing_value_type != CF_DATA_TYPE_NONE)
204             {
205                 // Already did this
206                 VarRefDestroy(ref);
207                 return PROMISE_RESULT_NOOP;
208             }
209 
210             FnCallResult res = FnCallEvaluate(ctx, PromiseGetPolicy(pp), fp, pp);
211 
212             if (res.status == FNCALL_FAILURE)
213             {
214                 /* We do not assign variables to failed fn calls */
215                 if (EvalContextGetPass(ctx) == CF_DONEPASSES-1) {
216                     // If we still fail at last pass, make a log
217                     Log(LOG_LEVEL_VERBOSE, "While setting variable '%s' in bundle '%s', function '%s' failed - skipping",
218                                        pp->promiser, PromiseGetBundle(pp)->name, fp->name);
219                 }
220                 RvalDestroy(res.rval);
221                 VarRefDestroy(ref);
222                 return PROMISE_RESULT_NOOP;
223             }
224             else
225             {
226                 rval = res.rval;
227             }
228         }
229         else
230         {
231             Buffer *conv = BufferNew();
232             bool malformed = false, misprint = false;
233 
234             if (strcmp(opts.cp_save->lval, "int") == 0)
235             {
236                 long int asint = IntFromString(opts.cp_save->rval.item);
237                 if (asint == CF_NOINT)
238                 {
239                     malformed = true;
240                 }
241                 else if (0 > BufferPrintf(conv, "%ld", asint))
242                 {
243                     misprint = true;
244                 }
245                 else
246                 {
247                     rval = RvalNew(BufferData(conv), opts.cp_save->rval.type);
248                 }
249             }
250             else if (strcmp(opts.cp_save->lval, "real") == 0)
251             {
252                 double real_value;
253                 if (!DoubleFromString(opts.cp_save->rval.item, &real_value))
254                 {
255                     malformed = true;
256                 }
257                 else if (0 > BufferPrintf(conv, "%lf", real_value))
258                 {
259                     misprint = true;
260                 }
261                 else
262                 {
263                     rval = RvalNew(BufferData(conv), opts.cp_save->rval.type);
264                 }
265             }
266             else
267             {
268                 rval = RvalCopy(opts.cp_save->rval);
269             }
270             BufferDestroy(conv);
271 
272             if (malformed)
273             {
274                 /* Arises when opts->cp_save->rval.item isn't yet expanded. */
275                 /* Has already been logged by *FromString */
276                 VarRefDestroy(ref);
277                 return PROMISE_RESULT_FAIL;
278             }
279             else if (misprint)
280             {
281                 /* Even though no problems with memory allocation can
282                  * get here, there might be other problems. */
283                 UnexpectedError("Problems writing to buffer");
284                 VarRefDestroy(ref);
285                 return PROMISE_RESULT_FAIL;
286             }
287             else if (rval.type == RVAL_TYPE_LIST)
288             {
289                 Rlist *rval_list = RvalRlistValue(rval);
290                 RlistFlatten(ctx, &rval_list);
291                 rval.item = rval_list;
292             }
293         }
294 
295         if (Epimenides(ctx, PromiseGetBundle(pp)->ns, PromiseGetBundle(pp)->name, pp->promiser, rval, 0))
296         {
297             Log(LOG_LEVEL_ERR, "Variable '%s' contains itself indirectly - an unkeepable promise", pp->promiser);
298             DoCleanupAndExit(EXIT_FAILURE);
299         }
300         else
301         {
302             /* See if the variable needs recursively expanding again */
303 
304             Rval returnval = EvaluateFinalRval(ctx, PromiseGetPolicy(pp), ref->ns, ref->scope, rval, true, pp);
305 
306             RvalDestroy(rval);
307 
308             // freed before function exit
309             rval = returnval;
310         }
311 
312         /* If variable did resolve but we're not allowed to modify it. */
313         /* ok_redefine: only on second iteration, else we ignore broken promises. TODO wat? */
314         if (existing_value_type != CF_DATA_TYPE_NONE &&
315             !opts.ok_redefine)
316         {
317             if (!CompareRval(existing_value, DataTypeToRvalType(existing_value_type),
318                              rval.item, rval.type))
319             {
320                 switch (rval.type)
321                 {
322                     /* TODO redefinition shouldn't be mentioned. Maybe handle like normal variable definition/ */
323                 case RVAL_TYPE_SCALAR:
324                     Log(LOG_LEVEL_VERBOSE, "V: Skipping redefinition of constant scalar '%s': from '%s' to '%s'",
325                         pp->promiser, (const char *)existing_value, RvalScalarValue(rval));
326                     PromiseRef(LOG_LEVEL_VERBOSE, pp);
327                     break;
328 
329                 case RVAL_TYPE_LIST:
330                 {
331                     Log(LOG_LEVEL_VERBOSE, "V: Skipping redefinition of constant list '%s'", pp->promiser);
332                     Writer *w = StringWriter();
333                     RlistWrite(w, existing_value);
334                     char *oldstr = StringWriterClose(w);
335                     Log(LOG_LEVEL_DEBUG, "Old value:         %s", oldstr);
336                     free(oldstr);
337 
338                     w = StringWriter();
339                     RlistWrite(w, rval.item);
340                     char *newstr = StringWriterClose(w);
341                     Log(LOG_LEVEL_DEBUG, "Skipped new value: %s", newstr);
342                     free(newstr);
343 
344                     PromiseRef(LOG_LEVEL_VERBOSE, pp);
345                 }
346                 break;
347 
348                 case RVAL_TYPE_CONTAINER:
349                 case RVAL_TYPE_FNCALL:
350                 case RVAL_TYPE_NOPROMISEE:
351                     break;
352                 }
353             }
354 
355             RvalDestroy(rval);
356             VarRefDestroy(ref);
357             return PROMISE_RESULT_NOOP;
358         }
359 
360         if (IsCf3VarString(pp->promiser))
361         {
362             // Unexpanded variables, we don't do anything with
363             RvalDestroy(rval);
364             VarRefDestroy(ref);
365             return PROMISE_RESULT_NOOP;
366         }
367 
368         if (!IsLegalVariableName(ctx, pp))
369         {
370             Log(LOG_LEVEL_ERR, "Variable identifier '%s' is not legal", pp->promiser);
371             PromiseRef(LOG_LEVEL_ERR, pp);
372             RvalDestroy(rval);
373             VarRefDestroy(ref);
374             return PROMISE_RESULT_NOOP;
375         }
376 
377         if (rval.type == RVAL_TYPE_LIST)
378         {
379             if (opts.drop_undefined)
380             {
381                 Rlist *stripped = RvalRlistValue(rval);
382                 Rlist *entry = stripped;
383                 while (entry)
384                 {
385                     Rlist *delete_me = NULL;
386                     if (IsNakedVar(RlistScalarValue(entry), '@'))
387                     {
388                         delete_me = entry;
389                     }
390                     entry = entry->next;
391                     RlistDestroyEntry(&stripped, delete_me);
392                 }
393                 rval.item = stripped;
394             }
395 
396             for (const Rlist *rp = RvalRlistValue(rval); rp; rp = rp->next)
397             {
398                 switch (rp->val.type)
399                 {
400                 case RVAL_TYPE_SCALAR:
401                     break;
402 
403                 default:
404                     // Cannot assign variable because value is a list containing a non-scalar item
405                     VarRefDestroy(ref);
406                     RvalDestroy(rval);
407                     return PROMISE_RESULT_NOOP;
408                 }
409             }
410         }
411 
412         if (ref->num_indices > 0)
413         {
414             if (data_type == CF_DATA_TYPE_CONTAINER)
415             {
416                 char *lval_str = VarRefToString(ref, true);
417                 Log(LOG_LEVEL_ERR, "Cannot assign a container to an indexed variable name '%s'. Should be assigned to '%s' instead",
418                     lval_str, ref->lval);
419                 free(lval_str);
420                 VarRefDestroy(ref);
421                 RvalDestroy(rval);
422                 return PROMISE_RESULT_NOOP;
423             }
424             else
425             {
426                 DataType existing_type;
427                 VarRef *base_ref = VarRefCopyIndexless(ref);
428                 if (EvalContextVariableGet(ctx, ref, &existing_type) && existing_type == CF_DATA_TYPE_CONTAINER)
429                 {
430                     char *lval_str = VarRefToString(ref, true);
431                     char *base_ref_str = VarRefToString(base_ref, true);
432                     Log(LOG_LEVEL_ERR, "Cannot assign value to indexed variable name '%s', because a container is already assigned to the base name '%s'",
433                         lval_str, base_ref_str);
434                     free(lval_str);
435                     free(base_ref_str);
436                     VarRefDestroy(base_ref);
437                     VarRefDestroy(ref);
438                     RvalDestroy(rval);
439                     return PROMISE_RESULT_NOOP;
440                 }
441                 VarRefDestroy(base_ref);
442             }
443         }
444 
445 
446         DataType required_datatype = DataTypeFromString(opts.cp_save->lval);
447         if (rval.type != DataTypeToRvalType(required_datatype))
448         {
449             char *ref_str = VarRefToString(ref, true);
450             char *value_str = RvalToString(rval);
451             Log(LOG_LEVEL_ERR, "Variable '%s' expected a variable of type '%s', but was given incompatible value '%s'",
452                 ref_str, DataTypeToString(required_datatype), value_str);
453             PromiseRef(LOG_LEVEL_ERR, pp);
454 
455             free(ref_str);
456             free(value_str);
457             VarRefDestroy(ref);
458             RvalDestroy(rval);
459             return PROMISE_RESULT_FAIL;
460         }
461 
462         /* WRITE THE VARIABLE AT LAST. */
463         bool success = EvalContextVariablePut(ctx, ref, rval.item, required_datatype, "source=promise");
464 
465         if (!success)
466         {
467             Log(LOG_LEVEL_VERBOSE,
468                 "Unable to converge %s.%s value (possibly empty or infinite regression)",
469                 ref->scope, pp->promiser);
470             PromiseRef(LOG_LEVEL_VERBOSE, pp);
471 
472             VarRefDestroy(ref);
473             RvalDestroy(rval);
474             return PROMISE_RESULT_FAIL;
475         }
476 
477         Rlist *promise_meta = PromiseGetConstraintAsList(ctx, "meta", pp);
478         if (promise_meta)
479         {
480             StringSet *class_meta = EvalContextVariableTags(ctx, ref);
481             Buffer *print;
482             for (const Rlist *rp = promise_meta; rp; rp = rp->next)
483             {
484                 StringSetAdd(class_meta, xstrdup(RlistScalarValue(rp)));
485                 print = StringSetToBuffer(class_meta, ',');
486                 Log(LOG_LEVEL_DEBUG,
487                     "Added tag %s to class %s, tags now [%s]",
488                     RlistScalarValue(rp), pp->promiser, BufferData(print));
489                 BufferDestroy(print);
490             }
491         }
492 
493         result = PROMISE_RESULT_NOOP;
494     }
495     else
496     {
497         Log(LOG_LEVEL_ERR, "Variable %s has no promised value", pp->promiser);
498         Log(LOG_LEVEL_ERR, "Rule from %s at/before line %zu", PromiseGetBundle(pp)->source_path, opts.cp_save->offset.line);
499         result = PROMISE_RESULT_FAIL;
500     }
501 
502     /*
503      * FIXME: Variable promise are exempt from normal evaluation logic still, so
504      * they are not pushed to evaluation stack before being evaluated. Due to
505      * this reason, we cannot call cfPS here to set classes, as it will error
506      * out with ProgrammingError.
507      *
508      * In order to support 'classes' body for variables as well, we call
509      * ClassAuditLog explicitly.
510      */
511     ClassAuditLog(ctx, pp, &a, result);
512 
513     VarRefDestroy(ref);
514     RvalDestroy(rval);
515 
516     return result;
517 }
518 
CompareRval(const void * rval1_item,RvalType rval1_type,const void * rval2_item,RvalType rval2_type)519 static bool CompareRval(const void *rval1_item, RvalType rval1_type,
520                         const void *rval2_item, RvalType rval2_type)
521 {
522     if (rval1_type != rval2_type)
523     {
524         return false;
525     }
526 
527     switch (rval1_type)
528     {
529     case RVAL_TYPE_SCALAR:
530 
531         if (IsCf3VarString(rval1_item) || IsCf3VarString(rval2_item))
532         {
533             return false;          // inconclusive
534         }
535 
536         if (strcmp(rval1_item, rval2_item) != 0)
537         {
538             return false;
539         }
540 
541         break;
542 
543     case RVAL_TYPE_LIST:
544         return RlistEqual(rval1_item, rval2_item);
545 
546     case RVAL_TYPE_FNCALL:
547         return false;
548 
549     default:
550         return false;
551     }
552 
553     return true;
554 }
555 
Epimenides(EvalContext * ctx,const char * ns,const char * scope,const char * var,Rval rval,int level)556 static bool Epimenides(EvalContext *ctx, const char *ns, const char *scope, const char *var, Rval rval, int level)
557 {
558     switch (rval.type)
559     {
560     case RVAL_TYPE_SCALAR:
561 
562         if (StringContainsVar(RvalScalarValue(rval), var))
563         {
564             Log(LOG_LEVEL_ERR, "Scalar variable '%s' contains itself (non-convergent) '%s'", var, RvalScalarValue(rval));
565             return true;
566         }
567 
568         if (IsCf3VarString(RvalScalarValue(rval)))
569         {
570             Buffer *exp = BufferNew();
571             ExpandScalar(ctx, ns, scope, RvalScalarValue(rval), exp);
572 
573             if (strcmp(BufferData(exp), RvalScalarValue(rval)) == 0)
574             {
575                 BufferDestroy(exp);
576                 return false;
577             }
578 
579             if (level > 3)
580             {
581                 BufferDestroy(exp);
582                 return false;
583             }
584 
585             if (Epimenides(ctx, ns, scope, var, (Rval) { BufferGet(exp), RVAL_TYPE_SCALAR}, level + 1))
586             {
587                 BufferDestroy(exp);
588                 return true;
589             }
590 
591             BufferDestroy(exp);
592         }
593 
594         break;
595 
596     case RVAL_TYPE_LIST:
597         for (const Rlist *rp = RvalRlistValue(rval); rp != NULL; rp = rp->next)
598         {
599             if (Epimenides(ctx, ns, scope, var, rp->val, level))
600             {
601                 return true;
602             }
603         }
604         break;
605 
606     case RVAL_TYPE_CONTAINER:
607     case RVAL_TYPE_FNCALL:
608     case RVAL_TYPE_NOPROMISEE:
609         return false;
610     }
611 
612     return false;
613 }
614 
615 /**
616  * @brief Collects variable constraints controlling how the promise should be converged
617  */
CollectConvergeVariableOptions(EvalContext * ctx,const Promise * pp)618 static ConvergeVariableOptions CollectConvergeVariableOptions(EvalContext *ctx, const Promise *pp)
619 {
620     ConvergeVariableOptions opts;
621     opts.drop_undefined = false;
622     opts.cp_save = NULL;                             /* main variable value */
623     /* By default allow variable redefinition, use "policy" constraint
624      * to override. */
625     opts.ok_redefine = true;
626     /* Main return value: becomes true at the end of the function. */
627     opts.should_converge = false;
628 
629     if (!IsDefinedClass(ctx, pp->classes))
630     {
631         return opts;
632     }
633 
634     int num_values = 0;
635     for (size_t i = 0; i < SeqLength(pp->conlist); i++)
636     {
637         Constraint *cp = SeqAt(pp->conlist, i);
638 
639         if (strcmp(cp->lval, "comment") == 0)
640         {
641             // Comments don't affect convergence
642             // Unclear why this is in the constraint list in the first place?
643             continue;
644         }
645         else if (cp->rval.item == NULL && cp->rval.type != RVAL_TYPE_LIST)
646         {
647             // No right value, considered empty
648             continue;
649         }
650         else if (strcmp(cp->lval, "ifvarclass") == 0 ||
651                  strcmp(cp->lval, "if")         == 0)
652         {
653             switch (cp->rval.type)
654             {
655             case RVAL_TYPE_SCALAR:
656                 if (!IsDefinedClass(ctx, cp->rval.item))
657                 {
658                     return opts;
659                 }
660 
661                 break;
662 
663             case RVAL_TYPE_FNCALL:
664             {
665                 bool excluded = false;
666 
667                 /* eval it: e.g. ifvarclass => not("a_class") */
668 
669                 Rval res = FnCallEvaluate(ctx, PromiseGetPolicy(pp), cp->rval.item, pp).rval;
670 
671                 /* Don't continue unless function was evaluated properly */
672                 if (res.type != RVAL_TYPE_SCALAR)
673                 {
674                     RvalDestroy(res);
675                     return opts;
676                 }
677 
678                 excluded = !IsDefinedClass(ctx, res.item);
679 
680                 RvalDestroy(res);
681 
682                 if (excluded)
683                 {
684                     return opts;
685                 }
686             }
687             break;
688 
689             default:
690                 Log(LOG_LEVEL_ERR, "Invalid if/ifvarclass type '%c': should be string or function", cp->rval.type);
691             }
692         }
693         else if (strcmp(cp->lval, "policy") == 0)
694         {
695             if (strcmp(cp->rval.item, "ifdefined") == 0)
696             {
697                 opts.drop_undefined = true;
698             }
699             else if (strcmp(cp->rval.item, "constant") == 0)
700             {
701                 opts.ok_redefine = false;
702             }
703         }
704         else if (DataTypeFromString(cp->lval) != CF_DATA_TYPE_NONE)
705         {
706             num_values++;
707             opts.cp_save = cp;
708         }
709     }
710 
711     if (opts.cp_save == NULL)
712     {
713         Log(LOG_LEVEL_WARNING, "Incomplete vars promise: %s",
714             pp->promiser);
715         PromiseRef(LOG_LEVEL_INFO, pp);
716         return opts;
717     }
718 
719     if (num_values > 2)
720     {
721         Log(LOG_LEVEL_ERR,
722             "Variable '%s' breaks its own promise with multiple (%d) values",
723             pp->promiser, num_values);
724         PromiseRef(LOG_LEVEL_ERR, pp);
725         return opts;
726     }
727 
728     /* All constraints look OK, and classes are defined. Move forward with
729      * this promise. */
730     opts.should_converge = true;
731 
732     return opts;
733 }
734