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 <fncall.h>
26 
27 #include <eval_context.h>
28 #include <files_names.h>
29 #include <expand.h>
30 #include <vars.h>
31 #include <evalfunction.h>
32 #include <policy.h>
33 #include <string_lib.h>
34 #include <promises.h>
35 #include <syntax.h>
36 #include <audit.h>
37 #include <cleanup.h>
38 
39 #define SIMULATE_SAFE_META_TAG "simulate_safe"
40 
41 /******************************************************************/
42 /* Argument propagation                                           */
43 /******************************************************************/
44 
45 /*
46 
47 When formal parameters are passed, they should be literal strings, i.e.
48 values (check for this). But when the values are received the
49 receiving body should state only variable names without literal quotes.
50 That way we can feed in the received parameter name directly in as an lvalue
51 
52 e.g.
53        access => myaccess("$(person)"),
54 
55        body files myaccess(user)
56 
57 leads to Hash Association (lval,rval) => (user,"$(person)")
58 
59 */
60 
61 /******************************************************************/
62 
NewExpArgs(EvalContext * ctx,const Policy * policy,const FnCall * fp,const FnCallType * fp_type)63 Rlist *NewExpArgs(EvalContext *ctx, const Policy *policy, const FnCall *fp, const FnCallType *fp_type)
64 {
65     // Functions with delayed evaluation will call this themselves later
66     if (fp_type && fp_type->options & FNCALL_OPTION_DELAYED_EVALUATION)
67     {
68         return RlistCopy(fp->args);
69     }
70 
71     const FnCallType *fn = FnCallTypeGet(fp->name);
72     if (fn == NULL)
73     {
74         FatalError(ctx, "Function call '%s' has unknown type", fp->name);
75     }
76     else
77     {
78         int len = RlistLen(fp->args);
79 
80         if (!(fn->options & FNCALL_OPTION_VARARG))
81         {
82             if (len != FnNumArgs(fn))
83             {
84                 Log(LOG_LEVEL_ERR, "Arguments to function '%s' do not tally. Expected %d not %d",
85                       fp->name, FnNumArgs(fn), len);
86                 PromiseRef(LOG_LEVEL_ERR, fp->caller);
87                 DoCleanupAndExit(EXIT_FAILURE);
88             }
89         }
90     }
91 
92     Rlist *expanded_args = NULL;
93     for (const Rlist *rp = fp->args; rp != NULL; rp = rp->next)
94     {
95         Rval rval;
96 
97         if (rp->val.type == RVAL_TYPE_FNCALL)
98         {
99             FnCall *subfp = RlistFnCallValue(rp);
100             rval = FnCallEvaluate(ctx, policy, subfp, fp->caller).rval;
101         }
102         else
103         {
104             rval = ExpandPrivateRval(ctx, NULL, NULL, rp->val.item, rp->val.type);
105             assert(rval.item);
106         }
107 
108         /*
109 
110           Collect compound values into containers only if the function
111           supports it.
112 
113           Functions without FNCALL_OPTION_COLLECTING don't collect
114           Rlist elements. So in the policy, you call
115           and(splitstring("a b")) and it ends up as and("a", "b").
116           This expansion happens once per FnCall, not for all
117           arguments.
118 
119           Functions with FNCALL_OPTION_COLLECTING instead collect all
120           the results of a FnCall into a single JSON array object. It
121           requires functions to expect it, but it's the only
122           reasonable way to preserve backwards compatibility for
123           functions like and() and allow nesting of calls to functions
124           that take and return compound data types.
125 
126         */
127         RlistAppendAllTypes(&expanded_args, rval.item, rval.type,
128                             (fn->options & FNCALL_OPTION_COLLECTING));
129         RvalDestroy(rval);
130     }
131 
132     return expanded_args;
133 }
134 
135 /*******************************************************************/
136 
FnCallIsBuiltIn(Rval rval)137 bool FnCallIsBuiltIn(Rval rval)
138 {
139     FnCall *fp;
140 
141     if (rval.type != RVAL_TYPE_FNCALL)
142     {
143         return false;
144     }
145 
146     fp = (FnCall *) rval.item;
147 
148     if (FnCallTypeGet(fp->name))
149     {
150         return true;
151     }
152     else
153     {
154         return false;
155     }
156 }
157 
158 /*******************************************************************/
159 
FnCallNew(const char * name,Rlist * args)160 FnCall *FnCallNew(const char *name, Rlist *args)
161 {
162     FnCall *fp = xmalloc(sizeof(FnCall));
163 
164     fp->name = xstrdup(name);
165     fp->args = args;
166 
167     return fp;
168 }
169 
170 /*******************************************************************/
171 
FnCallCopyRewriter(const FnCall * f,JsonElement * map)172 FnCall *FnCallCopyRewriter(const FnCall *f, JsonElement *map)
173 {
174     return FnCallNew(f->name, RlistCopyRewriter(f->args, map));
175 }
176 
FnCallCopy(const FnCall * f)177 FnCall *FnCallCopy(const FnCall *f)
178 {
179     return FnCallCopyRewriter(f, NULL);
180 }
181 
182 /*******************************************************************/
183 
FnCallDestroy(FnCall * fp)184 void FnCallDestroy(FnCall *fp)
185 {
186     if (fp)
187     {
188         free(fp->name);
189         RlistDestroy(fp->args);
190     }
191     free(fp);
192 }
193 
FnCallHash(const FnCall * fp,unsigned seed)194 unsigned FnCallHash(const FnCall *fp, unsigned seed)
195 {
196     unsigned hash = StringHash(fp->name, seed);
197     return RlistHash(fp->args, hash);
198 }
199 
200 
ExpandFnCall(const EvalContext * ctx,const char * ns,const char * scope,const FnCall * f)201 FnCall *ExpandFnCall(const EvalContext *ctx, const char *ns, const char *scope, const FnCall *f)
202 {
203     FnCall *result = NULL;
204     if (IsCf3VarString(f->name))
205     {
206         // e.g. usebundle => $(m)(arg0, arg1);
207         Buffer *buf = BufferNewWithCapacity(CF_MAXVARSIZE);
208         ExpandScalar(ctx, ns, scope, f->name, buf);
209 
210         result = FnCallNew(BufferData(buf), ExpandList(ctx, ns, scope, f->args, false));
211         BufferDestroy(buf);
212     }
213     else
214     {
215         result = FnCallNew(f->name, ExpandList(ctx, ns, scope, f->args, false));
216     }
217 
218     return result;
219 }
220 
FnCallWrite(Writer * writer,const FnCall * call)221 void FnCallWrite(Writer *writer, const FnCall *call)
222 {
223     WriterWrite(writer, call->name);
224     WriterWriteChar(writer, '(');
225 
226     for (const Rlist *rp = call->args; rp != NULL; rp = rp->next)
227     {
228         switch (rp->val.type)
229         {
230         case RVAL_TYPE_SCALAR:
231             ScalarWrite(writer, RlistScalarValue(rp), true);
232             break;
233 
234         case RVAL_TYPE_FNCALL:
235             FnCallWrite(writer, RlistFnCallValue(rp));
236             break;
237 
238         default:
239             WriterWrite(writer, "(** Unknown argument **)\n");
240             break;
241         }
242 
243         if (rp->next != NULL)
244         {
245             WriterWriteChar(writer, ',');
246         }
247     }
248 
249     WriterWriteChar(writer, ')');
250 }
251 
252 /*******************************************************************/
253 
CallFunction(EvalContext * ctx,const Policy * policy,const FnCall * fp,const Rlist * expargs)254 static FnCallResult CallFunction(EvalContext *ctx, const Policy *policy, const FnCall *fp, const Rlist *expargs)
255 {
256     const Rlist *rp = fp->args;
257     const FnCallType *fncall_type = FnCallTypeGet(fp->name);
258     if (fncall_type == NULL)
259     {
260         FatalError(ctx, "Function call '%s' has unknown type", fp->name);
261     }
262 
263     int argnum = 0;
264     for (argnum = 0; rp != NULL && fncall_type->args[argnum].pattern != NULL; argnum++)
265     {
266         if (rp->val.type != RVAL_TYPE_FNCALL)
267         {
268             /* Nested functions will not match to lval so don't bother checking */
269             SyntaxTypeMatch err = CheckConstraintTypeMatch(fp->name, rp->val,
270                                                            fncall_type->args[argnum].dtype,
271                                                            fncall_type->args[argnum].pattern, 1);
272             if (err != SYNTAX_TYPE_MATCH_OK && err != SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED)
273             {
274                 FatalError(ctx, "In function '%s', error in variable '%s', '%s'", fp->name, (const char *)rp->val.item, SyntaxTypeMatchToString(err));
275             }
276         }
277 
278         rp = rp->next;
279     }
280 
281     if (argnum != RlistLen(expargs) && !(fncall_type->options & FNCALL_OPTION_VARARG))
282     {
283         char *args_str = RlistToString(expargs);
284         Log(LOG_LEVEL_ERR, "Argument template mismatch handling function %s(%s)", fp->name, args_str);
285         free(args_str);
286 
287         rp = expargs;
288         for (int i = 0; i < argnum; i++)
289         {
290             if (rp != NULL)
291             {
292                 char *rval_str = RvalToString(rp->val);
293                 Log(LOG_LEVEL_ERR, "  arg[%d] range %s\t %s ", i, fncall_type->args[i].pattern, rval_str);
294                 free(rval_str);
295             }
296             else
297             {
298                 Log(LOG_LEVEL_ERR, "  arg[%d] range %s\t ? ", i, fncall_type->args[i].pattern);
299             }
300         }
301 
302         FatalError(ctx, "Bad arguments");
303     }
304 
305     return (*fncall_type->impl) (ctx, policy, fp, expargs);
306 }
307 
FnCallEvaluate(EvalContext * ctx,const Policy * policy,FnCall * fp,const Promise * caller)308 FnCallResult FnCallEvaluate(EvalContext *ctx, const Policy *policy, FnCall *fp, const Promise *caller)
309 {
310     assert(ctx != NULL);
311     assert(policy != NULL);
312     assert(fp != NULL);
313     fp->caller = caller;
314 
315     if (!EvalContextGetEvalOption(ctx, EVAL_OPTION_EVAL_FUNCTIONS))
316     {
317         Log(LOG_LEVEL_VERBOSE, "Skipping function '%s', because evaluation was turned off in the evaluator",
318             fp->name);
319         return (FnCallResult) { FNCALL_FAILURE, { FnCallCopy(fp), RVAL_TYPE_FNCALL } };
320     }
321 
322     const FnCallType *fp_type = FnCallTypeGet(fp->name);
323 
324     if (!fp_type)
325     {
326         if (caller)
327         {
328             Log(LOG_LEVEL_ERR, "No such FnCall '%s' in promise '%s' near line %zu",
329                   fp->name, PromiseGetBundle(caller)->source_path, caller->offset.line);
330         }
331         else
332         {
333             Log(LOG_LEVEL_ERR, "No such FnCall '%s', context info unavailable", fp->name);
334         }
335 
336         return (FnCallResult) { FNCALL_FAILURE, { FnCallCopy(fp), RVAL_TYPE_FNCALL } };
337     }
338 
339     const bool skip_unsafe_function_calls = ((EVAL_MODE == EVAL_MODE_SIMULATE_MANIFEST) ||
340                                              (EVAL_MODE == EVAL_MODE_SIMULATE_MANIFEST_FULL) ||
341                                              (EVAL_MODE == EVAL_MODE_SIMULATE_DIFF));
342 
343     Rlist *caller_meta = PromiseGetConstraintAsList(ctx, "meta", caller);
344     if (skip_unsafe_function_calls &&
345         ((fp_type->options & FNCALL_OPTION_UNSAFE) != 0) &&
346         !RlistContainsString(caller_meta, SIMULATE_SAFE_META_TAG))
347     {
348         Log(LOG_LEVEL_WARNING, "Not calling unsafe function '%s' in simulate mode", fp->name);
349         return (FnCallResult) { FNCALL_FAILURE, { FnCallCopy(fp), RVAL_TYPE_FNCALL } };
350     }
351 
352     Rlist *expargs = NewExpArgs(ctx, policy, fp, fp_type);
353 
354     Writer *fncall_writer = NULL;
355     const char *fncall_string = "";
356     if (LogGetGlobalLevel() >= LOG_LEVEL_DEBUG)
357     {
358         fncall_writer = StringWriter();
359         FnCallWrite(fncall_writer, fp);
360         fncall_string = StringWriterData(fncall_writer);
361     }
362 
363     // Check if arguments are resolved, except for delayed evaluation functions
364     if ( ! (fp_type->options & FNCALL_OPTION_DELAYED_EVALUATION) &&
365          RlistIsUnresolved(expargs))
366     {
367         // Special case where a three argument ifelse call must
368         // be allowed to have undefined variables.
369         if (strcmp(fp->name, "ifelse") == 0 &&
370             expargs->val.type != RVAL_TYPE_FNCALL &&
371             RlistLen(expargs) == 3)
372         {
373                 Log(LOG_LEVEL_DEBUG, "Allowing ifelse() function evaluation even"
374                     " though its arguments contain unresolved variables: %s",
375                     fncall_string);
376         }
377         else
378         {
379             if (LogGetGlobalLevel() >= LOG_LEVEL_DEBUG)
380             {
381                 Log(LOG_LEVEL_DEBUG, "Skipping function evaluation for now,"
382                     " arguments contain unresolved variables: %s",
383                     fncall_string);
384                 WriterClose(fncall_writer);
385             }
386             RlistDestroy(expargs);
387             return (FnCallResult) { FNCALL_FAILURE, { FnCallCopy(fp), RVAL_TYPE_FNCALL } };
388         }
389     }
390 
391     /* Call functions in promises with 'ifelapsed => "0"' (e.g. with
392      * 'action => immediate') [ENT-7478] */
393     const int if_elapsed = PromiseGetConstraintAsInt(ctx, "ifelapsed", caller);
394     if (if_elapsed != 0)
395     {
396         Rval cached_rval;
397         if ((fp_type->options & FNCALL_OPTION_CACHED) && EvalContextFunctionCacheGet(ctx, fp, expargs, &cached_rval))
398         {
399             if (LogGetGlobalLevel() >= LOG_LEVEL_DEBUG)
400             {
401                 Log(LOG_LEVEL_DEBUG,
402                     "Using previously cached result for function: %s",
403                     fncall_string);
404                 WriterClose(fncall_writer);
405             }
406             Writer *w = StringWriter();
407             FnCallWrite(w, fp);
408             WriterClose(w);
409             RlistDestroy(expargs);
410 
411             return (FnCallResult) { FNCALL_SUCCESS, RvalCopy(cached_rval) };
412         }
413     }
414 
415     if (LogGetGlobalLevel() >= LOG_LEVEL_DEBUG)
416     {
417         Log(LOG_LEVEL_DEBUG, "Evaluating function: %s%s",
418             fncall_string, (if_elapsed == 0) ? " (because of ifelapsed => \"0\")" : "");
419         WriterClose(fncall_writer);
420     }
421 
422     FnCallResult result = CallFunction(ctx, policy, fp, expargs);
423 
424     if (result.status == FNCALL_FAILURE)
425     {
426         RlistDestroy(expargs);
427         RvalDestroy(result.rval);
428         return (FnCallResult) { FNCALL_FAILURE, { FnCallCopy(fp), RVAL_TYPE_FNCALL } };
429     }
430 
431     if (fp_type->options & FNCALL_OPTION_CACHED)
432     {
433         Writer *w = StringWriter();
434         FnCallWrite(w, fp);
435         Log(LOG_LEVEL_VERBOSE, "Caching result for function '%s'", StringWriterData(w));
436         WriterClose(w);
437 
438         EvalContextFunctionCachePut(ctx, fp, expargs, &result.rval);
439     }
440 
441     RlistDestroy(expargs);
442 
443     return result;
444 }
445 
446 /*******************************************************************/
447 
FnCallTypeGet(const char * name)448 const FnCallType *FnCallTypeGet(const char *name)
449 {
450     int i;
451 
452     for (i = 0; CF_FNCALL_TYPES[i].name != NULL; i++)
453     {
454         if (strcmp(CF_FNCALL_TYPES[i].name, name) == 0)
455         {
456             return CF_FNCALL_TYPES + i;
457         }
458     }
459 
460     return NULL;
461 }
462