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 <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_DIFF));
341 
342     Rlist *caller_meta = PromiseGetConstraintAsList(ctx, "meta", caller);
343     if (skip_unsafe_function_calls &&
344         ((fp_type->options & FNCALL_OPTION_UNSAFE) != 0) &&
345         !RlistContainsString(caller_meta, SIMULATE_SAFE_META_TAG))
346     {
347         Log(LOG_LEVEL_WARNING, "Not calling unsafe function '%s' in simulate mode", fp->name);
348         return (FnCallResult) { FNCALL_FAILURE, { FnCallCopy(fp), RVAL_TYPE_FNCALL } };
349     }
350 
351     Rlist *expargs = NewExpArgs(ctx, policy, fp, fp_type);
352 
353     Writer *fncall_writer = NULL;
354     const char *fncall_string = "";
355     if (LogGetGlobalLevel() >= LOG_LEVEL_DEBUG)
356     {
357         fncall_writer = StringWriter();
358         FnCallWrite(fncall_writer, fp);
359         fncall_string = StringWriterData(fncall_writer);
360     }
361 
362     // Check if arguments are resolved, except for delayed evaluation functions
363     if ( ! (fp_type->options & FNCALL_OPTION_DELAYED_EVALUATION) &&
364          RlistIsUnresolved(expargs))
365     {
366         // Special case: ifelse(isvariable("x"), $(x), "default")
367         // (the first argument will come down expanded as "!any")
368         if (strcmp(fp->name, "ifelse") == 0 &&
369             RlistLen(expargs) == 3 &&
370             strcmp("!any", RlistScalarValueSafe(expargs)) == 0 &&
371             !RlistIsUnresolved(expargs->next->next))
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     Rval cached_rval;
392     if ((fp_type->options & FNCALL_OPTION_CACHED) && EvalContextFunctionCacheGet(ctx, fp, expargs, &cached_rval))
393     {
394         if (LogGetGlobalLevel() >= LOG_LEVEL_DEBUG)
395         {
396             Log(LOG_LEVEL_DEBUG,
397                 "Using previously cached result for function: %s",
398                 fncall_string);
399             WriterClose(fncall_writer);
400         }
401         Writer *w = StringWriter();
402         FnCallWrite(w, fp);
403         WriterClose(w);
404         RlistDestroy(expargs);
405 
406         return (FnCallResult) { FNCALL_SUCCESS, RvalCopy(cached_rval) };
407     }
408 
409     if (LogGetGlobalLevel() >= LOG_LEVEL_DEBUG)
410     {
411         Log(LOG_LEVEL_DEBUG, "Evaluating function: %s",
412             fncall_string);
413         WriterClose(fncall_writer);
414     }
415 
416     FnCallResult result = CallFunction(ctx, policy, fp, expargs);
417 
418     if (result.status == FNCALL_FAILURE)
419     {
420         RlistDestroy(expargs);
421         RvalDestroy(result.rval);
422         return (FnCallResult) { FNCALL_FAILURE, { FnCallCopy(fp), RVAL_TYPE_FNCALL } };
423     }
424 
425     if (fp_type->options & FNCALL_OPTION_CACHED)
426     {
427         Writer *w = StringWriter();
428         FnCallWrite(w, fp);
429         Log(LOG_LEVEL_VERBOSE, "Caching result for function '%s'", StringWriterData(w));
430         WriterClose(w);
431 
432         EvalContextFunctionCachePut(ctx, fp, expargs, &result.rval);
433     }
434 
435     RlistDestroy(expargs);
436 
437     return result;
438 }
439 
440 /*******************************************************************/
441 
FnCallTypeGet(const char * name)442 const FnCallType *FnCallTypeGet(const char *name)
443 {
444     int i;
445 
446     for (i = 0; CF_FNCALL_TYPES[i].name != NULL; i++)
447     {
448         if (strcmp(CF_FNCALL_TYPES[i].name, name) == 0)
449         {
450             return CF_FNCALL_TYPES + i;
451         }
452     }
453 
454     return NULL;
455 }
456