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