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