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 <evalfunction.h>
26
27 #include <policy_server.h>
28 #include <promises.h>
29 #include <dir.h>
30 #include <dbm_api.h>
31 #include <lastseen.h>
32 #include <files_copy.h>
33 #include <files_names.h>
34 #include <files_interfaces.h>
35 #include <hash.h>
36 #include <vars.h>
37 #include <addr_lib.h>
38 #include <syntax.h>
39 #include <item_lib.h>
40 #include <conversion.h>
41 #include <expand.h>
42 #include <scope.h>
43 #include <keyring.h>
44 #include <matching.h>
45 #include <unix.h>
46 #include <string_lib.h>
47 #include <regex.h> /* CompileRegex,StringMatchWithPrecompiledRegex */
48 #include <net.h> /* SocketConnect */
49 #include <communication.h>
50 #include <classic.h> /* SendSocketStream */
51 #include <pipes.h>
52 #include <exec_tools.h>
53 #include <policy.h>
54 #include <misc_lib.h>
55 #include <fncall.h>
56 #include <audit.h>
57 #include <sort.h>
58 #include <logging.h>
59 #include <set.h>
60 #include <buffer.h>
61 #include <files_lib.h>
62 #include <connection_info.h>
63 #include <printsize.h>
64 #include <csv_parser.h>
65 #include <json.h>
66 #include <json-yaml.h>
67 #include <json-utils.h>
68 #include <known_dirs.h>
69 #include <mustache.h>
70 #include <processes_select.h>
71 #include <sysinfo.h>
72 #include <string_sequence.h>
73 #include <string_lib.h>
74 #include <version_comparison.h>
75
76 #include <math_eval.h>
77
78 #include <libgen.h>
79
80 #include <ctype.h>
81
82 #ifdef HAVE_LIBCURL
83 #include <curl/curl.h>
84 #endif
85
86 #ifdef HAVE_LIBCURL
87 static bool CURL_INITIALIZED = false; /* GLOBAL */
88 static JsonElement *CURL_CACHE = NULL;
89 #endif
90
91 #define SPLAY_PSEUDO_RANDOM_CONSTANT 8192
92
93 static FnCallResult FilterInternal(EvalContext *ctx, const FnCall *fp, const char *regex, const Rlist* rp, bool do_regex, bool invert, long max);
94
95 static char *StripPatterns(char *file_buffer, const char *pattern, const char *filename);
96 static int BuildLineArray(EvalContext *ctx, const Bundle *bundle, const char *array_lval, const char *file_buffer,
97 const char *split, int maxent, DataType type, bool int_index);
98 static JsonElement* BuildData(EvalContext *ctx, const char *file_buffer, const char *split, int maxent, bool make_array);
99 static bool ExecModule(EvalContext *ctx, char *command);
100
101 static bool CheckIDChar(const char ch);
102 static bool CheckID(const char *id);
103 static const Rlist *GetListReferenceArgument(const EvalContext *ctx, const FnCall *fp, const char *lval_str, DataType *datatype_out);
104 static char *CfReadFile(const char *filename, size_t maxsize);
105
106 /*******************************************************************/
107
FnNumArgs(const FnCallType * call_type)108 int FnNumArgs(const FnCallType *call_type)
109 {
110 for (int i = 0;; i++)
111 {
112 if (call_type->args[i].pattern == NULL)
113 {
114 return i;
115 }
116 }
117 }
118
119 /*******************************************************************/
120
121 /* assume args are all scalar literals by the time we get here
122 and each handler allocates the memory it returns. There is
123 a protocol to be followed here:
124 Set args,
125 Eval Content,
126 Set rtype,
127 ErrorFlags
128
129 returnval = FnCallXXXResult(fp)
130
131 */
132
133 /*
134 * Return successful FnCallResult with copy of str retained.
135 */
FnReturn(const char * str)136 static FnCallResult FnReturn(const char *str)
137 {
138 return (FnCallResult) { FNCALL_SUCCESS, { xstrdup(str), RVAL_TYPE_SCALAR } };
139 }
140
141 /*
142 * Return successful FnCallResult with str as is.
143 */
FnReturnNoCopy(char * str)144 static FnCallResult FnReturnNoCopy(char *str)
145 {
146 return (FnCallResult) { FNCALL_SUCCESS, { str, RVAL_TYPE_SCALAR } };
147 }
148
FnReturnBuffer(Buffer * buf)149 static FnCallResult FnReturnBuffer(Buffer *buf)
150 {
151 return (FnCallResult) { FNCALL_SUCCESS, { BufferClose(buf), RVAL_TYPE_SCALAR } };
152 }
153
FnReturnContainerNoCopy(JsonElement * container)154 static FnCallResult FnReturnContainerNoCopy(JsonElement *container)
155 {
156 return (FnCallResult) { FNCALL_SUCCESS, (Rval) { container, RVAL_TYPE_CONTAINER }};
157 }
158
159 // Currently only used for LIBCURL function, macro can be removed later
160 #ifdef HAVE_LIBCURL
FnReturnContainer(JsonElement * container)161 static FnCallResult FnReturnContainer(JsonElement *container)
162 {
163 return FnReturnContainerNoCopy(JsonCopy(container));
164 }
165 #endif // HAVE_LIBCURL
166
167 static FnCallResult FnReturnF(const char *fmt, ...) FUNC_ATTR_PRINTF(1, 2);
168
FnReturnF(const char * fmt,...)169 static FnCallResult FnReturnF(const char *fmt, ...)
170 {
171 va_list ap;
172 va_start(ap, fmt);
173 char *buffer;
174 xvasprintf(&buffer, fmt, ap);
175 va_end(ap);
176 return FnReturnNoCopy(buffer);
177 }
178
FnReturnContext(bool result)179 static FnCallResult FnReturnContext(bool result)
180 {
181 return FnReturn(result ? "any" : "!any");
182 }
183
FnFailure(void)184 static FnCallResult FnFailure(void)
185 {
186 return (FnCallResult) { FNCALL_FAILURE, { 0 } };
187 }
188
ResolveAndQualifyVarName(const FnCall * fp,const char * varname)189 static VarRef* ResolveAndQualifyVarName(const FnCall *fp, const char *varname)
190 {
191 VarRef *ref = NULL;
192 if (varname != NULL &&
193 IsVarList(varname) &&
194 strlen(varname) < CF_MAXVARSIZE)
195 {
196 char naked[CF_MAXVARSIZE] = "";
197 GetNaked(naked, varname);
198 ref = VarRefParse(naked);
199 }
200 else
201 {
202 ref = VarRefParse(varname);
203 }
204
205 if (!VarRefIsQualified(ref))
206 {
207 if (fp->caller)
208 {
209 const Bundle *caller_bundle = PromiseGetBundle(fp->caller);
210 VarRefQualify(ref, caller_bundle->ns, caller_bundle->name);
211 }
212 else
213 {
214 Log(LOG_LEVEL_WARNING,
215 "Function '%s' was not called from a promise; "
216 "the unqualified variable reference %s cannot be qualified automatically.",
217 fp->name,
218 varname);
219 VarRefDestroy(ref);
220 return NULL;
221 }
222 }
223
224 return ref;
225 }
226
VarRefValueToJson(const EvalContext * ctx,const FnCall * fp,const VarRef * ref,const DataType disallowed_datatypes[],size_t disallowed_count,bool allow_scalars,bool * allocated)227 static JsonElement* VarRefValueToJson(const EvalContext *ctx, const FnCall *fp, const VarRef *ref,
228 const DataType disallowed_datatypes[], size_t disallowed_count,
229 bool allow_scalars, bool *allocated)
230 {
231 assert(ref);
232
233 DataType value_type = CF_DATA_TYPE_NONE;
234 const void *value = EvalContextVariableGet(ctx, ref, &value_type);
235 bool want_type = true;
236
237 // Convenience storage for the name of the function, since fp can be NULL
238 const char* fp_name = (fp ? fp->name : "VarRefValueToJson");
239
240 for (size_t di = 0; di < disallowed_count; di++)
241 {
242 if (disallowed_datatypes[di] == value_type)
243 {
244 want_type = false;
245 break;
246 }
247 }
248
249 JsonElement *convert = NULL;
250 if (want_type)
251 {
252 switch (DataTypeToRvalType(value_type))
253 {
254 case RVAL_TYPE_LIST:
255 convert = JsonArrayCreate(RlistLen(value));
256 for (const Rlist *rp = value; rp != NULL; rp = rp->next)
257 {
258 if (rp->val.type == RVAL_TYPE_SCALAR) /* TODO what if it's an ilist */
259 {
260 JsonArrayAppendString(convert, RlistScalarValue(rp));
261 }
262 else
263 {
264 ProgrammingError("Ignored Rval of list type: %s",
265 RvalTypeToString(rp->val.type));
266 }
267 }
268
269 *allocated = true;
270 break;
271
272 case RVAL_TYPE_CONTAINER:
273 // TODO: look into optimizing this if necessary
274 convert = JsonCopy(value);
275 *allocated = true;
276 break;
277
278 case RVAL_TYPE_SCALAR:
279 {
280 const char* data = value;
281 if (allow_scalars)
282 {
283 convert = JsonStringCreate(value);
284 *allocated = true;
285 break;
286 }
287 else
288 {
289 /* regarray,mergedata,maparray,mapdata only care for arrays
290 * and ignore strings, so they go through this path. */
291 Log(LOG_LEVEL_DEBUG,
292 "Skipping scalar '%s' because 'allow_scalars' is false",
293 data);
294 }
295 }
296 // fallthrough
297 default:
298 *allocated = true;
299
300 {
301 VariableTableIterator *iter = EvalContextVariableTableFromRefIteratorNew(ctx, ref);
302 convert = JsonObjectCreate(10);
303 const size_t ref_num_indices = ref->num_indices;
304 char *last_key = NULL;
305 Variable *var;
306
307 while ((var = VariableTableIteratorNext(iter)) != NULL)
308 {
309 JsonElement *holder = convert;
310 JsonElement *holder_parent = NULL;
311 const VarRef *var_ref = VariableGetRef(var);
312 if (var_ref->num_indices - ref_num_indices == 1)
313 {
314 last_key = var_ref->indices[ref_num_indices];
315 }
316 else if (var_ref->num_indices - ref_num_indices > 1)
317 {
318 Log(LOG_LEVEL_DEBUG, "%s: got ref with starting depth %zu and index count %zu",
319 fp_name, ref_num_indices, var_ref->num_indices);
320 for (size_t index = ref_num_indices; index < var_ref->num_indices-1; index++)
321 {
322 JsonElement *local = JsonObjectGet(holder, var_ref->indices[index]);
323 if (local == NULL)
324 {
325 local = JsonObjectCreate(1);
326 JsonObjectAppendObject(holder, var_ref->indices[index], local);
327 }
328
329 last_key = var_ref->indices[index+1];
330 holder_parent = holder;
331 holder = local;
332 }
333 }
334
335 if (last_key != NULL && holder != NULL)
336 {
337 Rval var_rval = VariableGetRval(var, true);
338 switch (var_rval.type)
339 {
340 case RVAL_TYPE_SCALAR:
341 if (JsonGetElementType(holder) != JSON_ELEMENT_TYPE_CONTAINER)
342 {
343 Log(LOG_LEVEL_WARNING,
344 "Replacing a non-container JSON element '%s' with a new empty container"
345 " (for the '%s' subkey)",
346 JsonGetPropertyAsString(holder), last_key);
347
348 assert(holder_parent != NULL);
349
350 JsonElement *empty_container = JsonObjectCreate(10);
351
352 /* we have to duplicate 'holder->propertyName'
353 * instead of just using a pointer to it here
354 * because 'holder' is destroyed as part of the
355 * JsonObjectAppendElement() call below */
356 char *element_name = xstrdup(JsonGetPropertyAsString(holder));
357 JsonObjectAppendElement(holder_parent,
358 element_name,
359 empty_container);
360 free (element_name);
361 holder = empty_container;
362 JsonObjectAppendString(holder, last_key, var_rval.item);
363 }
364 else
365 {
366 JsonElement *child = JsonObjectGet(holder, last_key);
367 if (child != NULL && JsonGetElementType(child) == JSON_ELEMENT_TYPE_CONTAINER)
368 {
369 Rval var_rval_secret = VariableGetRval(var, false);
370 Log(LOG_LEVEL_WARNING,
371 "Not replacing the container '%s' with a non-container value '%s'",
372 JsonGetPropertyAsString(child), (char*) var_rval_secret.item);
373 }
374 else
375 {
376 /* everything ok, just append the string */
377 JsonObjectAppendString(holder, last_key, var_rval.item);
378 }
379 }
380 break;
381
382 case RVAL_TYPE_LIST:
383 {
384 JsonElement *array = JsonArrayCreate(10);
385 for (const Rlist *rp = RvalRlistValue(var_rval); rp != NULL; rp = rp->next)
386 {
387 if (rp->val.type == RVAL_TYPE_SCALAR)
388 {
389 JsonArrayAppendString(array, RlistScalarValue(rp));
390 }
391 }
392 JsonObjectAppendArray(holder, last_key, array);
393 }
394 break;
395
396 default:
397 break;
398 }
399 }
400 }
401
402 VariableTableIteratorDestroy(iter);
403
404 if (JsonLength(convert) < 1)
405 {
406 char *varname = VarRefToString(ref, true);
407 Log(LOG_LEVEL_VERBOSE, "%s: argument '%s' does not resolve to a container or a list or a CFEngine array",
408 fp_name, varname);
409 free(varname);
410 JsonDestroy(convert);
411 return NULL;
412 }
413
414 break;
415 } // end of default case
416 } // end of data type switch
417 }
418 else // !wanted_type
419 {
420 char *varname = VarRefToString(ref, true);
421 Log(LOG_LEVEL_DEBUG, "%s: argument '%s' resolved to an undesired data type",
422 fp_name, varname);
423 free(varname);
424 }
425
426 return convert;
427 }
428
LookupVarRefToJson(void * ctx,const char ** data)429 static JsonElement *LookupVarRefToJson(void *ctx, const char **data)
430 {
431 Buffer* varname = NULL;
432 Seq *s = StringMatchCaptures("^(([a-zA-Z0-9_]+\\.)?[a-zA-Z0-9._]+)(\\[[^\\[\\]]+\\])?", *data, false);
433
434 if (s && SeqLength(s) > 0) // got a variable name
435 {
436 varname = BufferCopy((const Buffer*) SeqAt(s, 0));
437 }
438
439 if (s)
440 {
441 SeqDestroy(s);
442 }
443
444 VarRef *ref = NULL;
445 if (varname)
446 {
447 ref = VarRefParse(BufferData(varname));
448 // advance to the last character of the matched variable name
449 *data += strlen(BufferData(varname))-1;
450 BufferDestroy(varname);
451 }
452
453 if (!ref)
454 {
455 return NULL;
456 }
457
458 bool allocated = false;
459 JsonElement *vardata = VarRefValueToJson(ctx, NULL, ref, NULL, 0, true, &allocated);
460 VarRefDestroy(ref);
461
462 // This should always return a copy
463 if (!allocated)
464 {
465 vardata = JsonCopy(vardata);
466 }
467
468 return vardata;
469 }
470
VarNameOrInlineToJson(EvalContext * ctx,const FnCall * fp,const Rlist * rp,bool allow_scalars,bool * allocated)471 static JsonElement* VarNameOrInlineToJson(EvalContext *ctx, const FnCall *fp, const Rlist* rp, bool allow_scalars, bool *allocated)
472 {
473 JsonElement *inline_data = NULL;
474
475 assert(rp);
476
477 if (rp->val.type == RVAL_TYPE_CONTAINER)
478 {
479 return (JsonElement*) rp->val.item;
480 }
481
482 const char* data = RlistScalarValue(rp);
483
484 JsonParseError res = JsonParseWithLookup(ctx, &LookupVarRefToJson, &data, &inline_data);
485
486 if (res == JSON_PARSE_OK)
487 {
488 if (JsonGetElementType(inline_data) == JSON_ELEMENT_TYPE_PRIMITIVE)
489 {
490 JsonDestroy(inline_data);
491 inline_data = NULL;
492 }
493 else
494 {
495 *allocated = true;
496 return inline_data;
497 }
498 }
499
500 VarRef *ref = ResolveAndQualifyVarName(fp, data);
501 if (!ref)
502 {
503 return NULL;
504 }
505
506 JsonElement *vardata = VarRefValueToJson(ctx, fp, ref, NULL, 0, allow_scalars, allocated);
507 VarRefDestroy(ref);
508
509 return vardata;
510 }
511
GetHostsFromLastseenDB(Item * addresses,time_t horizon,bool return_address,bool return_recent)512 static Rlist *GetHostsFromLastseenDB(Item *addresses, time_t horizon, bool return_address, bool return_recent)
513 {
514 Rlist *recent = NULL, *aged = NULL;
515 Item *ip;
516 time_t now = time(NULL);
517 double entrytime;
518 char address[CF_MAXVARSIZE]; // TODO: Could this be 1025 / NI_MAXHOST ?
519
520 for (ip = addresses; ip != NULL; ip = ip->next)
521 {
522 if (sscanf(ip->classes, "%lf", &entrytime) != 1)
523 {
524 Log(LOG_LEVEL_ERR, "Could not get host entry age");
525 continue;
526 }
527
528 if (return_address)
529 {
530 StringCopy(ip->name, address, sizeof(address));
531 }
532 else
533 {
534 char hostname[NI_MAXHOST];
535 if (IPString2Hostname(hostname, ip->name, sizeof(hostname)) != -1)
536 {
537 StringCopy(hostname, address, sizeof(address));
538 }
539 else
540 {
541 /* Not numeric address was requested, but IP was unresolvable. */
542 StringCopy(ip->name, address, sizeof(address));
543 }
544 }
545
546 if (entrytime < now - horizon)
547 {
548 Log(LOG_LEVEL_DEBUG, "Old entry");
549
550 if (RlistKeyIn(recent, address))
551 {
552 Log(LOG_LEVEL_DEBUG, "There is recent entry for this address. Do nothing.");
553 }
554 else
555 {
556 Log(LOG_LEVEL_DEBUG, "Adding to list of aged hosts.");
557 RlistPrependScalarIdemp(&aged, address);
558 }
559 }
560 else
561 {
562 Log(LOG_LEVEL_DEBUG, "Recent entry");
563
564 Rlist *r = RlistKeyIn(aged, address);
565 if (r)
566 {
567 Log(LOG_LEVEL_DEBUG, "Purging from list of aged hosts.");
568 RlistDestroyEntry(&aged, r);
569 }
570
571 Log(LOG_LEVEL_DEBUG, "Adding to list of recent hosts.");
572 RlistPrependScalarIdemp(&recent, address);
573 }
574 }
575
576 if (return_recent)
577 {
578 RlistDestroy(aged);
579 return recent;
580 }
581 else
582 {
583 RlistDestroy(recent);
584 return aged;
585 }
586 }
587
588 /*********************************************************************/
589
FnCallAnd(EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)590 static FnCallResult FnCallAnd(EvalContext *ctx,
591 ARG_UNUSED const Policy *policy,
592 ARG_UNUSED const FnCall *fp,
593 const Rlist *finalargs)
594 {
595
596 for (const Rlist *arg = finalargs; arg; arg = arg->next)
597 {
598 SyntaxTypeMatch err = CheckConstraintTypeMatch(fp->name, arg->val, CF_DATA_TYPE_STRING, "", 1);
599 if (err != SYNTAX_TYPE_MATCH_OK && err != SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED)
600 {
601 FatalError(ctx, "Function '%s', %s", fp->name, SyntaxTypeMatchToString(err));
602 }
603 }
604
605 for (const Rlist *arg = finalargs; arg; arg = arg->next)
606 {
607 if (!IsDefinedClass(ctx, RlistScalarValue(arg)))
608 {
609 return FnReturnContext(false);
610 }
611 }
612
613 return FnReturnContext(true);
614 }
615
616 /*******************************************************************/
617
CallHostsSeenCallback(const char * hostkey,const char * address,ARG_UNUSED bool incoming,const KeyHostSeen * quality,void * ctx)618 static bool CallHostsSeenCallback(const char *hostkey, const char *address,
619 ARG_UNUSED bool incoming, const KeyHostSeen *quality,
620 void *ctx)
621 {
622 Item **addresses = ctx;
623
624 if (HostKeyAddressUnknown(hostkey))
625 {
626 return true;
627 }
628
629 char buf[PRINTSIZE(uintmax_t)];
630 xsnprintf(buf, sizeof(buf), "%ju", (uintmax_t) quality->lastseen);
631
632 PrependItem(addresses, address, buf);
633
634 return true;
635 }
636
637 /*******************************************************************/
638
FnCallHostsSeen(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)639 static FnCallResult FnCallHostsSeen(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
640 {
641 Item *addresses = NULL;
642
643 int horizon = IntFromString(RlistScalarValue(finalargs)) * 3600;
644 char *hostseen_policy = RlistScalarValue(finalargs->next);
645 char *format = RlistScalarValue(finalargs->next->next);
646
647 Log(LOG_LEVEL_DEBUG, "Calling hostsseen(%d,%s,%s)",
648 horizon, hostseen_policy, format);
649
650 if (!ScanLastSeenQuality(&CallHostsSeenCallback, &addresses))
651 {
652 return FnFailure();
653 }
654
655 Rlist *returnlist = GetHostsFromLastseenDB(addresses, horizon,
656 strcmp(format, "address") == 0,
657 strcmp(hostseen_policy, "lastseen") == 0);
658
659 DeleteItemList(addresses);
660
661 {
662 Writer *w = StringWriter();
663 WriterWrite(w, "hostsseen return values:");
664 for (Rlist *rp = returnlist; rp; rp = rp->next)
665 {
666 WriterWriteF(w, " '%s'", RlistScalarValue(rp));
667 }
668 Log(LOG_LEVEL_DEBUG, "%s", StringWriterData(w));
669 WriterClose(w);
670 }
671
672 if (returnlist == NULL)
673 {
674 return FnFailure();
675 }
676
677 return (FnCallResult) { FNCALL_SUCCESS, { returnlist, RVAL_TYPE_LIST } };
678 }
679
680 /*********************************************************************/
681
FnCallHostsWithClass(EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)682 static FnCallResult FnCallHostsWithClass(EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
683 {
684 Rlist *returnlist = NULL;
685
686 char *class_name = RlistScalarValue(finalargs);
687 char *return_format = RlistScalarValue(finalargs->next);
688
689 if (!ListHostsWithClass(ctx, &returnlist, class_name, return_format))
690 {
691 return FnFailure();
692 }
693
694 return (FnCallResult) { FNCALL_SUCCESS, { returnlist, RVAL_TYPE_LIST } };
695 }
696
697 /*********************************************************************/
698
699 /** @brief Convert function call from/to variables to range
700 *
701 * Swap the two integers in place if the first is bigger
702 * Check for CF_NOINT, indicating invalid arguments
703 *
704 * @return Absolute (positive) difference, -1 for error (0 for equal)
705 */
int_range_convert(int * from,int * to)706 static int int_range_convert(int *from, int *to)
707 {
708 int old_from = *from;
709 int old_to = *to;
710 if (old_from == CF_NOINT || old_to == CF_NOINT)
711 {
712 return -1;
713 }
714 if (old_from == old_to)
715 {
716 return 0;
717 }
718 if (old_from > old_to)
719 {
720 *from = old_to;
721 *to = old_from;
722 }
723 assert(*to > *from);
724 return (*to) - (*from);
725 }
726
FnCallRandomInt(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)727 static FnCallResult FnCallRandomInt(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
728 {
729 if (finalargs->next == NULL)
730 {
731 return FnFailure();
732 }
733
734 int from = IntFromString(RlistScalarValue(finalargs));
735 int to = IntFromString(RlistScalarValue(finalargs->next));
736
737 int range = int_range_convert(&from, &to);
738 if (range == -1)
739 {
740 return FnFailure();
741 }
742 if (range == 0)
743 {
744 return FnReturnF("%d", from);
745 }
746
747 assert(range > 0);
748
749 int result = from + (int) (drand48() * (double) range);
750
751 return FnReturnF("%d", result);
752 }
753
754 // Read an array of bytes as unsigned integers
755 // Convert to 64 bit unsigned integer
756 // Cross platform/arch, bytes[0] is always LSB of result
BytesToUInt64(uint8_t * bytes)757 static uint64_t BytesToUInt64(uint8_t *bytes)
758 {
759 uint64_t result = 0;
760 size_t n = 8;
761 for (size_t i = 0; i<n; ++i)
762 {
763 result += ((uint64_t)(bytes[i])) << (8 * i);
764 }
765 return result;
766 }
767
FnCallHashToInt(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)768 static FnCallResult FnCallHashToInt(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
769 {
770 if (finalargs->next == NULL || finalargs->next->next == NULL)
771 {
772 return FnFailure();
773 }
774 signed int from = IntFromString(RlistScalarValue(finalargs));
775 signed int to = IntFromString(RlistScalarValue(finalargs->next));
776
777 signed int range = int_range_convert(&from, &to);
778 if (range == -1)
779 {
780 return FnFailure();
781 }
782 if (range == 0)
783 {
784 return FnReturnF("%d", from);
785 }
786 assert(range > 0);
787
788 const unsigned char * const inp = RlistScalarValue(finalargs->next->next);
789
790 // Use beginning of SHA checksum as basis:
791 unsigned char digest[EVP_MAX_MD_SIZE + 1];
792 memset(digest, 0, sizeof(digest));
793 HashString(inp, strlen(inp), digest, HASH_METHOD_SHA256);
794 uint64_t converted_sha = BytesToUInt64((uint8_t*)digest);
795
796 // Limit using modulo:
797 signed int result = from + (converted_sha % range);
798 return FnReturnF("%d", result);
799 }
800
801 /*********************************************************************/
802
FnCallGetEnv(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)803 static FnCallResult FnCallGetEnv(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
804 {
805 char buffer[CF_BUFSIZE] = "", ctrlstr[CF_SMALLBUF];
806
807 char *name = RlistScalarValue(finalargs);
808 int limit = IntFromString(RlistScalarValue(finalargs->next));
809
810 snprintf(ctrlstr, CF_SMALLBUF, "%%.%ds", limit); // -> %45s
811
812 if (getenv(name))
813 {
814 snprintf(buffer, CF_BUFSIZE - 1, ctrlstr, getenv(name));
815 }
816
817 return FnReturn(buffer);
818 }
819
820 /*********************************************************************/
821
822 #if defined(HAVE_GETPWENT) && !defined(__ANDROID__)
823
FnCallGetUsers(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)824 static FnCallResult FnCallGetUsers(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
825 {
826 const char *except_name = RlistScalarValue(finalargs);
827 const char *except_uid = RlistScalarValue(finalargs->next);
828
829 Rlist *except_names = RlistFromSplitString(except_name, ',');
830 Rlist *except_uids = RlistFromSplitString(except_uid, ',');
831
832 setpwent();
833
834 Rlist *newlist = NULL;
835 struct passwd *pw;
836 while ((pw = getpwent()))
837 {
838 char *pw_uid_str = StringFromLong((int)pw->pw_uid);
839
840 if (!RlistKeyIn(except_names, pw->pw_name) && !RlistKeyIn(except_uids, pw_uid_str))
841 {
842 RlistAppendScalarIdemp(&newlist, pw->pw_name);
843 }
844
845 free(pw_uid_str);
846 }
847
848 endpwent();
849
850 RlistDestroy(except_names);
851 RlistDestroy(except_uids);
852
853 return (FnCallResult) { FNCALL_SUCCESS, { newlist, RVAL_TYPE_LIST } };
854 }
855
856 #else
857
FnCallGetUsers(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,ARG_UNUSED const Rlist * finalargs)858 static FnCallResult FnCallGetUsers(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, ARG_UNUSED const Rlist *finalargs)
859 {
860 Log(LOG_LEVEL_ERR, "getusers is not implemented");
861 return FnFailure();
862 }
863
864 #endif
865
866 /*********************************************************************/
867
FnCallEscape(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)868 static FnCallResult FnCallEscape(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
869 {
870 char buffer[CF_BUFSIZE];
871
872 buffer[0] = '\0';
873
874 char *name = RlistScalarValue(finalargs);
875
876 EscapeSpecialChars(name, buffer, CF_BUFSIZE - 1, "", "");
877
878 return FnReturn(buffer);
879 }
880
881 /*********************************************************************/
882
FnCallHost2IP(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)883 static FnCallResult FnCallHost2IP(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
884 {
885 char *name = RlistScalarValue(finalargs);
886 char ipaddr[CF_MAX_IP_LEN];
887
888 if (Hostname2IPString(ipaddr, name, sizeof(ipaddr)) != -1)
889 {
890 return FnReturn(ipaddr);
891 }
892 else
893 {
894 /* Retain legacy behaviour,
895 return hostname in case resolution fails. */
896 return FnReturn(name);
897 }
898
899 }
900
901 /*********************************************************************/
902
FnCallIP2Host(ARG_UNUSED EvalContext * ctx,ARG_UNUSED ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)903 static FnCallResult FnCallIP2Host(ARG_UNUSED EvalContext *ctx,
904 ARG_UNUSED ARG_UNUSED const Policy *policy,
905 ARG_UNUSED const FnCall *fp,
906 const Rlist *finalargs)
907 {
908 char hostname[NI_MAXHOST];
909 char *ip = RlistScalarValue(finalargs);
910
911 if (IPString2Hostname(hostname, ip, sizeof(hostname)) != -1)
912 {
913 return FnReturn(hostname);
914 }
915 else
916 {
917 /* Retain legacy behaviour,
918 return ip address in case resolution fails. */
919 return FnReturn(ip);
920 }
921 }
922
923 /*********************************************************************/
924
FnCallSysctlValue(ARG_UNUSED EvalContext * ctx,ARG_UNUSED ARG_UNUSED const Policy * policy,ARG_LINUX_ONLY const FnCall * fp,ARG_LINUX_ONLY const Rlist * finalargs)925 static FnCallResult FnCallSysctlValue(ARG_UNUSED EvalContext *ctx,
926 ARG_UNUSED ARG_UNUSED const Policy *policy,
927 ARG_LINUX_ONLY const FnCall *fp,
928 ARG_LINUX_ONLY const Rlist *finalargs)
929 {
930 #ifdef __linux__
931 const bool sysctlvalue_mode = (strcmp(fp->name, "sysctlvalue") == 0);
932
933 size_t max_sysctl_data = 16 * 1024;
934 Buffer *procrootbuf = BufferNew();
935 // Assumes that FILE_SEPARATOR is /
936 BufferAppendString(procrootbuf, GetRelocatedProcdirRoot());
937 BufferAppendString(procrootbuf, "/proc/sys");
938
939 if (sysctlvalue_mode)
940 {
941 Buffer *key = BufferNewFrom(RlistScalarValue(finalargs),
942 strlen(RlistScalarValue(finalargs)));
943
944 // Note that in the single-key mode, we just reuse procrootbuf.
945 Buffer *filenamebuf = procrootbuf;
946 // Assumes that FILE_SEPARATOR is /
947 BufferAppendChar(filenamebuf, '/');
948 BufferSearchAndReplace(key, "\\.", "/", "gT");
949 BufferAppendString(filenamebuf, BufferData(key));
950 BufferDestroy(key);
951
952 if (IsDir(BufferData(filenamebuf)))
953 {
954 Log(LOG_LEVEL_INFO, "Error while reading file '%s' because it's a directory (%s)",
955 BufferData(filenamebuf), GetErrorStr());
956 BufferDestroy(filenamebuf);
957 return FnFailure();
958 }
959
960 Writer *w = NULL;
961 bool truncated = false;
962 int fd = safe_open(BufferData(filenamebuf), O_RDONLY | O_TEXT);
963 if (fd >= 0)
964 {
965 w = FileReadFromFd(fd, max_sysctl_data, &truncated);
966 close(fd);
967 }
968
969 if (w == NULL)
970 {
971 Log(LOG_LEVEL_INFO, "Error while reading file '%s' (%s)",
972 BufferData(filenamebuf), GetErrorStr());
973 BufferDestroy(filenamebuf);
974 return FnFailure();
975 }
976
977 BufferDestroy(filenamebuf);
978
979 char *result = StringWriterClose(w);
980 StripTrailingNewline(result, max_sysctl_data);
981 return FnReturnNoCopy(result);
982 }
983
984 JsonElement *sysctl_data = JsonObjectCreate(10);
985
986 // For the remaining operations, we want the trailing slash on this.
987 BufferAppendChar(procrootbuf, '/');
988
989 Buffer *filematchbuf = BufferCopy(procrootbuf);
990 BufferAppendString(filematchbuf, "**/*");
991
992 StringSet *sysctls = GlobFileList(BufferData(filematchbuf));
993 BufferDestroy(filematchbuf);
994
995 StringSetIterator it = StringSetIteratorInit(sysctls);
996 const char *filename = NULL;
997 while ((filename = StringSetIteratorNext(&it)))
998 {
999 Writer *w = NULL;
1000 bool truncated = false;
1001
1002 if (IsDir(filename))
1003 {
1004 // No warning: this is normal as we match wildcards.
1005 continue;
1006 }
1007
1008 int fd = safe_open(filename, O_RDONLY | O_TEXT);
1009 if (fd >= 0)
1010 {
1011 w = FileReadFromFd(fd, max_sysctl_data, &truncated);
1012 close(fd);
1013 }
1014
1015 if (!w)
1016 {
1017 Log(LOG_LEVEL_INFO, "Error while reading file '%s' (%s)",
1018 filename, GetErrorStr());
1019 continue;
1020 }
1021
1022 char *result = StringWriterClose(w);
1023 StripTrailingNewline(result, max_sysctl_data);
1024
1025 Buffer *var = BufferNewFrom(filename, strlen(filename));
1026 BufferSearchAndReplace(var, BufferData(procrootbuf), "", "T");
1027 BufferSearchAndReplace(var, "/", ".", "gT");
1028 JsonObjectAppendString(sysctl_data, BufferData(var), result);
1029 free(result);
1030 BufferDestroy(var);
1031 }
1032
1033 StringSetDestroy(sysctls);
1034 BufferDestroy(procrootbuf);
1035 return FnReturnContainerNoCopy(sysctl_data);
1036 #else
1037 return FnFailure();
1038 #endif
1039 }
1040
1041 /*********************************************************************/
1042
1043 /* TODO move platform-specific code to libenv. */
1044
FnCallGetUserInfo(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)1045 static FnCallResult FnCallGetUserInfo(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
1046 {
1047 #ifdef __MINGW32__
1048 // TODO NetUserGetInfo(NULL, username, 1, &buf), see:
1049 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa370654(v=vs.85).aspx
1050 return FnFailure();
1051
1052 #else /* !__MINGW32__ */
1053
1054 struct passwd *pw = NULL;
1055
1056 if (finalargs == NULL)
1057 {
1058 pw = getpwuid(getuid());
1059 }
1060 else
1061 {
1062 char *arg = RlistScalarValue(finalargs);
1063 if (StringIsNumeric(arg))
1064 {
1065 uid_t uid = Str2Uid(arg, NULL, NULL);
1066 if (uid == CF_SAME_OWNER) // user "*"
1067 {
1068 uid = getuid();
1069 }
1070 else if (uid == CF_UNKNOWN_OWNER)
1071 {
1072 return FnFailure();
1073 }
1074
1075 pw = getpwuid(uid);
1076 }
1077 else
1078 {
1079 pw = getpwnam(arg);
1080 }
1081 }
1082
1083 JsonElement *result = GetUserInfo(pw);
1084
1085 if (result == NULL)
1086 {
1087 return FnFailure();
1088 }
1089
1090 return FnReturnContainerNoCopy(result);
1091 #endif
1092 }
1093
1094 /*********************************************************************/
1095
FnCallGetUid(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)1096 static FnCallResult FnCallGetUid(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
1097 {
1098 #ifdef __MINGW32__
1099 return FnFailure(); /* TODO */
1100
1101 #else /* !__MINGW32__ */
1102
1103 struct passwd *pw = getpwnam(RlistScalarValue(finalargs));
1104
1105 if (pw == NULL)
1106 {
1107 return FnFailure();
1108 }
1109
1110 return FnReturnF("%ju", (uintmax_t)pw->pw_uid);
1111 #endif
1112 }
1113
1114 /*********************************************************************/
1115
FnCallGetGid(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)1116 static FnCallResult FnCallGetGid(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
1117 {
1118 #ifdef __MINGW32__
1119 return FnFailure(); /* TODO */
1120
1121 #else /* !__MINGW32__ */
1122
1123 struct group *gr = getgrnam(RlistScalarValue(finalargs));
1124
1125 if (gr == NULL)
1126 {
1127 return FnFailure();
1128 }
1129
1130 return FnReturnF("%ju", (uintmax_t)gr->gr_gid);
1131 #endif
1132 }
1133
1134 /*********************************************************************/
1135
FnCallHandlerHash(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)1136 static FnCallResult FnCallHandlerHash(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
1137 /* Hash(string,md5|sha1|crypt) */
1138 {
1139 unsigned char digest[EVP_MAX_MD_SIZE + 1];
1140 HashMethod type;
1141
1142 char *string_or_filename = RlistScalarValue(finalargs);
1143 char *typestring = RlistScalarValue(finalargs->next);
1144 const bool filehash_mode = strcmp(fp->name, "file_hash") == 0;
1145
1146 type = HashIdFromName(typestring);
1147
1148 if (FIPS_MODE && type == HASH_METHOD_MD5)
1149 {
1150 Log(LOG_LEVEL_ERR, "FIPS mode is enabled, and md5 is not an approved algorithm in call to %s()", fp->name);
1151 }
1152
1153 if (filehash_mode)
1154 {
1155 HashFile(string_or_filename, digest, type, false);
1156 }
1157 else
1158 {
1159 HashString(string_or_filename, strlen(string_or_filename), digest, type);
1160 }
1161
1162 char hashbuffer[CF_HOSTKEY_STRING_SIZE];
1163 HashPrintSafe(hashbuffer, sizeof(hashbuffer),
1164 digest, type, true);
1165
1166 return FnReturn(SkipHashType(hashbuffer));
1167 }
1168
1169 /*********************************************************************/
1170
FnCallHashMatch(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)1171 static FnCallResult FnCallHashMatch(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
1172 /* HashMatch(string,md5|sha1|crypt,"abdxy98edj") */
1173 {
1174 unsigned char digest[EVP_MAX_MD_SIZE + 1];
1175 HashMethod type;
1176
1177 char *string = RlistScalarValue(finalargs);
1178 char *typestring = RlistScalarValue(finalargs->next);
1179 char *compare = RlistScalarValue(finalargs->next->next);
1180
1181 type = HashIdFromName(typestring);
1182 HashFile(string, digest, type, false);
1183
1184 char hashbuffer[CF_HOSTKEY_STRING_SIZE];
1185 HashPrintSafe(hashbuffer, sizeof(hashbuffer),
1186 digest, type, true);
1187
1188 Log(LOG_LEVEL_VERBOSE,
1189 "File '%s' hashes to '%s', compare to '%s'",
1190 string, hashbuffer, compare);
1191
1192 return FnReturnContext(strcmp(hashbuffer + 4, compare) == 0);
1193 }
1194
1195 /*********************************************************************/
1196
FnCallConcat(EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)1197 static FnCallResult FnCallConcat(EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
1198 {
1199 char id[CF_BUFSIZE];
1200 char result[CF_BUFSIZE] = "";
1201
1202 snprintf(id, CF_BUFSIZE, "built-in FnCall concat-arg");
1203
1204 /* We need to check all the arguments, ArgTemplate does not check varadic functions */
1205 for (const Rlist *arg = finalargs; arg; arg = arg->next)
1206 {
1207 SyntaxTypeMatch err = CheckConstraintTypeMatch(id, arg->val, CF_DATA_TYPE_STRING, "", 1);
1208 if (err != SYNTAX_TYPE_MATCH_OK && err != SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED)
1209 {
1210 FatalError(ctx, "in %s: %s", id, SyntaxTypeMatchToString(err));
1211 }
1212 }
1213
1214 for (const Rlist *arg = finalargs; arg; arg = arg->next)
1215 {
1216 if (strlcat(result, RlistScalarValue(arg), CF_BUFSIZE) >= CF_BUFSIZE)
1217 {
1218 /* Complain */
1219 Log(LOG_LEVEL_ERR, "Unable to evaluate concat() function, arguments are too long");
1220 return FnFailure();
1221 }
1222 }
1223
1224 return FnReturn(result);
1225 }
1226
1227 /*********************************************************************/
1228
FnCallIfElse(EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)1229 static FnCallResult FnCallIfElse(EvalContext *ctx,
1230 ARG_UNUSED const Policy *policy,
1231 ARG_UNUSED const FnCall *fp,
1232 const Rlist *finalargs)
1233 {
1234 unsigned int argcount = 0;
1235 char id[CF_BUFSIZE];
1236
1237 snprintf(id, CF_BUFSIZE, "built-in FnCall ifelse-arg");
1238
1239 /* We need to check all the arguments, ArgTemplate does not check varadic functions */
1240 for (const Rlist *arg = finalargs; arg; arg = arg->next)
1241 {
1242 SyntaxTypeMatch err = CheckConstraintTypeMatch(id, arg->val, CF_DATA_TYPE_STRING, "", 1);
1243 if (err != SYNTAX_TYPE_MATCH_OK && err != SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED)
1244 {
1245 FatalError(ctx, "in %s: %s", id, SyntaxTypeMatchToString(err));
1246 }
1247 argcount++;
1248 }
1249
1250 /* Require an odd number of arguments. We will always return something. */
1251 if ((argcount % 2) == 0)
1252 {
1253 FatalError(ctx, "in built-in FnCall ifelse: even number of arguments");
1254 }
1255
1256 const Rlist *arg;
1257 for (arg = finalargs; /* Start with arg set to finalargs. */
1258 arg && arg->next; /* We must have arg and arg->next to proceed. */
1259 arg = arg->next->next) /* arg steps forward *twice* every time. */
1260 {
1261 /* Similar to classmatch(), we evaluate the first of the two
1262 * arguments as a class. */
1263 if (IsDefinedClass(ctx, RlistScalarValue(arg)))
1264 {
1265 /* If the evaluation returned true in the current context,
1266 * return the second of the two arguments. */
1267 return FnReturn(RlistScalarValue(arg->next));
1268 }
1269 }
1270
1271 /* If we get here, we've reached the last argument (arg->next is NULL). */
1272 return FnReturn(RlistScalarValue(arg));
1273 }
1274
1275 /*********************************************************************/
1276
FnCallClassesMatching(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)1277 static FnCallResult FnCallClassesMatching(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
1278 {
1279 bool count_only = false;
1280 bool check_only = false;
1281 unsigned count = 0;
1282
1283 if (StringEqual(fp->name, "classesmatching"))
1284 {
1285 // Expected / default case
1286 }
1287 else if (StringEqual(fp->name, "classmatch"))
1288 {
1289 check_only = true;
1290 }
1291 else if (StringEqual(fp->name, "countclassesmatching"))
1292 {
1293 count_only = true;
1294 }
1295 else
1296 {
1297 FatalError(ctx, "FnCallClassesMatching: got unknown function name '%s', aborting", fp->name);
1298 }
1299
1300 if (!finalargs)
1301 {
1302 FatalError(ctx, "Function '%s' requires at least one argument", fp->name);
1303 }
1304
1305 for (const Rlist *arg = finalargs; arg; arg = arg->next)
1306 {
1307 SyntaxTypeMatch err = CheckConstraintTypeMatch(fp->name, arg->val, CF_DATA_TYPE_STRING, "", 1);
1308 if (err != SYNTAX_TYPE_MATCH_OK && err != SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED)
1309 {
1310 FatalError(ctx, "in function '%s', '%s'", fp->name, SyntaxTypeMatchToString(err));
1311 }
1312 }
1313
1314 Rlist *matches = NULL;
1315
1316 {
1317 ClassTableIterator *iter = EvalContextClassTableIteratorNewGlobal(ctx, NULL, true, true);
1318 StringSet *global_matches = ClassesMatching(ctx, iter, RlistScalarValue(finalargs), finalargs->next, check_only);
1319
1320 StringSetIterator it = StringSetIteratorInit(global_matches);
1321 const char *element = NULL;
1322 while ((element = StringSetIteratorNext(&it)))
1323 {
1324 if (count_only || check_only)
1325 {
1326 count++;
1327 }
1328 else
1329 {
1330 RlistPrepend(&matches, element, RVAL_TYPE_SCALAR);
1331 }
1332 }
1333
1334 StringSetDestroy(global_matches);
1335 ClassTableIteratorDestroy(iter);
1336 }
1337
1338 if (check_only && count >= 1)
1339 {
1340 return FnReturnContext(true);
1341 }
1342
1343 {
1344 ClassTableIterator *iter = EvalContextClassTableIteratorNewLocal(ctx);
1345 StringSet *local_matches = ClassesMatching(ctx, iter, RlistScalarValue(finalargs), finalargs->next, check_only);
1346
1347 StringSetIterator it = StringSetIteratorInit(local_matches);
1348 const char *element = NULL;
1349 while ((element = StringSetIteratorNext(&it)))
1350 {
1351 if (count_only || check_only)
1352 {
1353 count++;
1354 }
1355 else
1356 {
1357 RlistPrepend(&matches, element, RVAL_TYPE_SCALAR);
1358 }
1359 }
1360
1361 StringSetDestroy(local_matches);
1362 ClassTableIteratorDestroy(iter);
1363 }
1364
1365 if (check_only)
1366 {
1367 return FnReturnContext(count >= 1);
1368 }
1369 else if (count_only)
1370 {
1371 return FnReturnF("%u", count);
1372 }
1373
1374 // else, this is classesmatching()
1375 return (FnCallResult) { FNCALL_SUCCESS, { matches, RVAL_TYPE_LIST } };
1376 }
1377
1378
VariablesMatching(const EvalContext * ctx,const FnCall * fp,VariableTableIterator * iter,const Rlist * args,bool collect_full_data)1379 static JsonElement *VariablesMatching(const EvalContext *ctx, const FnCall *fp, VariableTableIterator *iter, const Rlist *args, bool collect_full_data)
1380 {
1381 JsonElement *matching = JsonObjectCreate(10);
1382
1383 const char *regex = RlistScalarValue(args);
1384 pcre *rx = CompileRegex(regex);
1385
1386 Variable *v = NULL;
1387 while ((v = VariableTableIteratorNext(iter)))
1388 {
1389 const VarRef *var_ref = VariableGetRef(v);
1390 char *expr = VarRefToString(var_ref, true);
1391
1392 if (rx != NULL && StringMatchFullWithPrecompiledRegex(rx, expr))
1393 {
1394 StringSet *tagset = EvalContextVariableTags(ctx, var_ref);
1395 bool pass = false;
1396
1397 if ((tagset != NULL) && (args->next != NULL))
1398 {
1399 for (const Rlist *arg = args->next; arg; arg = arg->next)
1400 {
1401 const char* tag_regex = RlistScalarValue(arg);
1402 const char *element = NULL;
1403 StringSetIterator it = StringSetIteratorInit(tagset);
1404 while ((element = SetIteratorNext(&it)))
1405 {
1406 if (StringMatchFull(tag_regex, element))
1407 {
1408 pass = true;
1409 break;
1410 }
1411 }
1412 }
1413 }
1414 else // without any tags queried, accept variable
1415 {
1416 pass = true;
1417 }
1418
1419 if (pass)
1420 {
1421 JsonElement *data = NULL;
1422 bool allocated = false;
1423 if (collect_full_data)
1424 {
1425 data = VarRefValueToJson(ctx, fp, var_ref, NULL, 0, true, &allocated);
1426 }
1427
1428 /*
1429 * When we don't collect the full variable data
1430 * (collect_full_data is false), we still create a JsonObject
1431 * with empty strings as the values. It will be destroyed soon
1432 * afterwards, but the code is cleaner if we do it this way than
1433 * if we make a JsonArray in one branch and a JsonObject in the
1434 * other branch. The empty strings provide assurance that
1435 * serializing this JsonObject (e.g. for logging) will not
1436 * create problems. The extra memory usage from the empty
1437 * strings is negligible.
1438 */
1439 if (data == NULL)
1440 {
1441 JsonObjectAppendString(matching, expr, "");
1442 }
1443 else
1444 {
1445 if (!allocated)
1446 {
1447 data = JsonCopy(data);
1448 }
1449 JsonObjectAppendElement(matching, expr, data);
1450 }
1451 }
1452 }
1453 free(expr);
1454 }
1455
1456 if (rx)
1457 {
1458 pcre_free(rx);
1459 }
1460
1461 return matching;
1462 }
1463
FnCallVariablesMatching(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)1464 static FnCallResult FnCallVariablesMatching(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
1465 {
1466 bool fulldata = (strcmp(fp->name, "variablesmatching_as_data") == 0);
1467
1468 if (!finalargs)
1469 {
1470 FatalError(ctx, "Function '%s' requires at least one argument", fp->name);
1471 }
1472
1473 for (const Rlist *arg = finalargs; arg; arg = arg->next)
1474 {
1475 SyntaxTypeMatch err = CheckConstraintTypeMatch(fp->name, arg->val, CF_DATA_TYPE_STRING, "", 1);
1476 if (err != SYNTAX_TYPE_MATCH_OK && err != SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED)
1477 {
1478 FatalError(ctx, "In function '%s', %s", fp->name, SyntaxTypeMatchToString(err));
1479 }
1480 }
1481
1482 Rlist *matches = NULL;
1483
1484 {
1485 VariableTableIterator *iter = EvalContextVariableTableIteratorNew(ctx, NULL, NULL, NULL);
1486 JsonElement *global_matches = VariablesMatching(ctx, fp, iter, finalargs, fulldata);
1487 VariableTableIteratorDestroy(iter);
1488
1489 assert (JsonGetContainerType(global_matches) == JSON_CONTAINER_TYPE_OBJECT);
1490
1491 if (fulldata)
1492 {
1493 return FnReturnContainerNoCopy(global_matches);
1494
1495 }
1496
1497 JsonIterator jiter = JsonIteratorInit(global_matches);
1498 const char *key;
1499 while ((key = JsonIteratorNextKey(&jiter)) != NULL)
1500 {
1501 assert (key != NULL);
1502 RlistPrepend(&matches, key, RVAL_TYPE_SCALAR);
1503 }
1504
1505 JsonDestroy(global_matches);
1506 }
1507
1508 return (FnCallResult) { FNCALL_SUCCESS, { matches, RVAL_TYPE_LIST } };
1509 }
1510
1511 /*********************************************************************/
1512
FnCallGetMetaTags(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)1513 static FnCallResult FnCallGetMetaTags(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
1514 {
1515 if (!finalargs)
1516 {
1517 FatalError(ctx, "Function '%s' requires at least one argument", fp->name);
1518 }
1519
1520 Rlist *tags = NULL;
1521 StringSet *tagset = NULL;
1522
1523 if (strcmp(fp->name, "getvariablemetatags") == 0)
1524 {
1525 VarRef *ref = VarRefParse(RlistScalarValue(finalargs));
1526 tagset = EvalContextVariableTags(ctx, ref);
1527 VarRefDestroy(ref);
1528 }
1529 else if (strcmp(fp->name, "getclassmetatags") == 0)
1530 {
1531 ClassRef ref = ClassRefParse(RlistScalarValue(finalargs));
1532 tagset = EvalContextClassTags(ctx, ref.ns, ref.name);
1533 ClassRefDestroy(ref);
1534 }
1535 else
1536 {
1537 FatalError(ctx, "FnCallGetMetaTags: got unknown function name '%s', aborting", fp->name);
1538 }
1539
1540 if (tagset == NULL)
1541 {
1542 Log(LOG_LEVEL_VERBOSE, "%s found variable or class %s without a tagset", fp->name, RlistScalarValue(finalargs));
1543 return (FnCallResult) { FNCALL_FAILURE, { 0 } };
1544 }
1545
1546 char *key = NULL;
1547 if (finalargs->next != NULL)
1548 {
1549 Buffer *keybuf = BufferNew();
1550 BufferPrintf(keybuf, "%s=", RlistScalarValue(finalargs->next));
1551 key = BufferClose(keybuf);
1552 }
1553
1554 char *element;
1555 StringSetIterator it = StringSetIteratorInit(tagset);
1556 while ((element = SetIteratorNext(&it)))
1557 {
1558 if (key != NULL)
1559 {
1560 if (StringStartsWith(element, key))
1561 {
1562 RlistAppendScalar(&tags, element+strlen(key));
1563 }
1564 }
1565 else
1566 {
1567 RlistAppendScalar(&tags, element);
1568 }
1569 }
1570
1571 free(key);
1572 return (FnCallResult) { FNCALL_SUCCESS, { tags, RVAL_TYPE_LIST } };
1573 }
1574
1575 /*********************************************************************/
1576
FnCallBasename(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * args)1577 static FnCallResult FnCallBasename(ARG_UNUSED EvalContext *ctx,
1578 ARG_UNUSED const Policy *policy,
1579 const FnCall *fp,
1580 const Rlist *args)
1581 {
1582 assert(fp != NULL);
1583 assert(fp->name != NULL);
1584 if (args == NULL)
1585 {
1586 Log(LOG_LEVEL_ERR, "Function %s requires a filename as first arg!",
1587 fp->name);
1588 return FnFailure();
1589 }
1590
1591 char dir[PATH_MAX];
1592 strlcpy(dir, RlistScalarValue(args), PATH_MAX);
1593 if (dir[0] == '\0')
1594 {
1595 return FnReturn(dir);
1596 }
1597
1598 char *base = basename(dir);
1599
1600 if (args->next != NULL)
1601 {
1602 char *suffix = RlistScalarValue(args->next);
1603 if (StringEndsWith(base, suffix))
1604 {
1605 size_t base_len = strlen(base);
1606 size_t suffix_len = strlen(suffix);
1607
1608 // Remove only if actually a suffix, not the same string
1609 if (suffix_len < base_len)
1610 {
1611 // On Solaris, trying to edit the buffer returned by basename
1612 // causes segfault(!)
1613 base = xstrndup(base, base_len - suffix_len);
1614 return FnReturnNoCopy(base);
1615 }
1616 }
1617 }
1618
1619 return FnReturn(base);
1620 }
1621
1622 /*********************************************************************/
1623
FnCallBundlesMatching(EvalContext * ctx,const Policy * policy,const FnCall * fp,const Rlist * finalargs)1624 static FnCallResult FnCallBundlesMatching(EvalContext *ctx, const Policy *policy, const FnCall *fp, const Rlist *finalargs)
1625 {
1626 if (!finalargs)
1627 {
1628 return FnFailure();
1629 }
1630
1631 const char *regex = RlistScalarValue(finalargs);
1632 pcre *rx = CompileRegex(regex);
1633 if (!rx)
1634 {
1635 return FnFailure();
1636 }
1637
1638 const Rlist *tag_args = finalargs->next;
1639
1640 Rlist *matches = NULL;
1641 for (size_t i = 0; i < SeqLength(policy->bundles); i++)
1642 {
1643 const Bundle *bp = SeqAt(policy->bundles, i);
1644
1645 char *bundle_name = BundleQualifiedName(bp);
1646 if (StringMatchFullWithPrecompiledRegex(rx, bundle_name))
1647 {
1648 VarRef *ref = VarRefParseFromBundle("tags", bp);
1649 VarRefSetMeta(ref, true);
1650 DataType type;
1651 const void *bundle_tags = EvalContextVariableGet(ctx, ref, &type);
1652 VarRefDestroy(ref);
1653
1654 bool found = false; // case where tag_args are given and the bundle has no tags
1655
1656 if (tag_args == NULL)
1657 {
1658 // we declare it found if no tags were requested
1659 found = true;
1660 }
1661 /* was the variable "tags" found? */
1662 else if (type != CF_DATA_TYPE_NONE)
1663 {
1664 switch (DataTypeToRvalType(type))
1665 {
1666 case RVAL_TYPE_SCALAR:
1667 {
1668 Rlist *searched = RlistFromSplitString(bundle_tags, ',');
1669 found = RlistMatchesRegexRlist(searched, tag_args);
1670 RlistDestroy(searched);
1671 }
1672 break;
1673
1674 case RVAL_TYPE_LIST:
1675 found = RlistMatchesRegexRlist(bundle_tags, tag_args);
1676 break;
1677
1678 default:
1679 Log(LOG_LEVEL_WARNING, "Function '%s' only matches tags defined as a scalar or a list. "
1680 "Bundle '%s' had meta defined as '%s'", fp->name, bundle_name, DataTypeToString(type));
1681 found = false;
1682 break;
1683 }
1684 }
1685
1686 if (found)
1687 {
1688 RlistPrepend(&matches, bundle_name, RVAL_TYPE_SCALAR);
1689 }
1690 }
1691
1692 free(bundle_name);
1693 }
1694
1695 pcre_free(rx);
1696
1697 return (FnCallResult) { FNCALL_SUCCESS, { matches, RVAL_TYPE_LIST } };
1698 }
1699
1700 /*********************************************************************/
1701
AddPackagesMatchingJsonLine(pcre * matcher,JsonElement * json,char * line)1702 static bool AddPackagesMatchingJsonLine(pcre *matcher, JsonElement *json, char *line)
1703 {
1704 const size_t line_length = strlen(line);
1705 if (line_length > CF_BUFSIZE - 80)
1706 {
1707 Log(LOG_LEVEL_ERR,
1708 "Line from package inventory is too long (%zu) to be sensible",
1709 line_length);
1710 return false;
1711 }
1712
1713
1714 if (StringMatchFullWithPrecompiledRegex(matcher, line))
1715 {
1716 Seq *list = SeqParseCsvString(line);
1717 if (SeqLength(list) != 4)
1718 {
1719 Log(LOG_LEVEL_ERR,
1720 "Line from package inventory '%s' did not yield correct number of elements.",
1721 line);
1722 SeqDestroy(list);
1723 return true;
1724 }
1725
1726 JsonElement *line_obj = JsonObjectCreate(4);
1727 JsonObjectAppendString(line_obj, "name", SeqAt(list, 0));
1728 JsonObjectAppendString(line_obj, "version", SeqAt(list, 1));
1729 JsonObjectAppendString(line_obj, "arch", SeqAt(list, 2));
1730 JsonObjectAppendString(line_obj, "method", SeqAt(list, 3));
1731
1732 SeqDestroy(list);
1733 JsonArrayAppendObject(json, line_obj);
1734 }
1735
1736 return true;
1737 }
1738
GetLegacyPackagesMatching(pcre * matcher,JsonElement * json,const bool installed_mode)1739 static bool GetLegacyPackagesMatching(pcre *matcher, JsonElement *json, const bool installed_mode)
1740 {
1741 char filename[CF_MAXVARSIZE];
1742 if (installed_mode)
1743 {
1744 GetSoftwareCacheFilename(filename);
1745 }
1746 else
1747 {
1748 GetSoftwarePatchesFilename(filename);
1749 }
1750
1751 Log(LOG_LEVEL_DEBUG, "Reading inventory from '%s'", filename);
1752
1753 FILE *const fin = fopen(filename, "r");
1754 if (fin == NULL)
1755 {
1756 Log(LOG_LEVEL_VERBOSE,
1757 "Cannot open the %s packages inventory '%s' - "
1758 "This is not necessarily an error. "
1759 "Either the inventory policy has not been included, "
1760 "or it has not had time to have an effect yet or you are using"
1761 "new package promise and check for legacy promise is made."
1762 "A future call may still succeed. (fopen: %s)",
1763 installed_mode ? "installed" : "available",
1764 filename,
1765 GetErrorStr());
1766
1767 return true;
1768 }
1769
1770 char *line;
1771 while ((line = GetCsvLineNext(fin)) != NULL)
1772 {
1773 if (!AddPackagesMatchingJsonLine(matcher, json, line))
1774 {
1775 free(line);
1776 break;
1777 }
1778 free(line);
1779 }
1780
1781 bool ret = (feof(fin) != 0);
1782 fclose(fin);
1783
1784 return ret;
1785 }
1786
GetPackagesMatching(pcre * matcher,JsonElement * json,const bool installed_mode,Rlist * default_inventory)1787 static bool GetPackagesMatching(pcre *matcher, JsonElement *json, const bool installed_mode, Rlist *default_inventory)
1788 {
1789 dbid database = (installed_mode == true ? dbid_packages_installed : dbid_packages_updates);
1790
1791 for (const Rlist *rp = default_inventory; rp != NULL; rp = rp->next)
1792 {
1793 const char *pm_name = RlistScalarValue(rp);
1794 size_t pm_name_size = strlen(pm_name);
1795
1796 Log(LOG_LEVEL_DEBUG, "Reading packages (%d) for package module [%s]",
1797 database, pm_name);
1798
1799 CF_DB *db_cached;
1800 if (!OpenSubDB(&db_cached, database, pm_name))
1801 {
1802 Log(LOG_LEVEL_ERR, "Can not open database %d to get packages data.", database);
1803 return false;
1804 }
1805
1806 char *key = "<inventory>";
1807 int data_size = ValueSizeDB(db_cached, key, strlen(key) + 1);
1808
1809 Log(LOG_LEVEL_DEBUG, "Reading inventory from database: %d", data_size);
1810
1811 /* For empty list we are storing one byte value in database. */
1812 if (data_size > 1)
1813 {
1814 char *buff = xmalloc(data_size + 1);
1815 buff[data_size] = '\0';
1816 if (!ReadDB(db_cached, key, buff, data_size))
1817 {
1818 Log(LOG_LEVEL_WARNING, "Can not read installed packages database "
1819 "for '%s' package module.", pm_name);
1820 continue;
1821 }
1822
1823 Seq *packages_from_module = SeqStringFromString(buff, '\n');
1824 free(buff);
1825
1826 if (packages_from_module)
1827 {
1828 // Iterate over and see where match is.
1829 for (size_t i = 0; i < SeqLength(packages_from_module); i++)
1830 {
1831 // With the new package promise we are storing inventory
1832 // information it the database. This set of lines ('\n' separated)
1833 // containing packages information. Each line is comma
1834 // separated set of data containing name, version and architecture.
1835 //
1836 // Legacy package promise is using 4 values, where the last one
1837 // is package method. In our case, method is simply package
1838 // module name. To make sure regex matching is working as
1839 // expected (we are comparing whole lines, containing package
1840 // method) we need to extend the line to contain package
1841 // module before regex match is taking place.
1842 char *line = SeqAt(packages_from_module, i);
1843 size_t new_line_size = strlen(line) + pm_name_size + 2; // we need coma and terminator
1844 char new_line[new_line_size];
1845 strcpy(new_line, line);
1846 strcat(new_line, ",");
1847 strcat(new_line, pm_name);
1848
1849 if (!AddPackagesMatchingJsonLine(matcher, json, new_line))
1850 {
1851 break;
1852 }
1853 }
1854 SeqDestroy(packages_from_module);
1855 }
1856 else
1857 {
1858 Log(LOG_LEVEL_WARNING, "Can not parse packages database for '%s' "
1859 "package module.", pm_name);
1860
1861 }
1862 }
1863 CloseDB(db_cached);
1864 }
1865 return true;
1866 }
1867
FnCallPackagesMatching(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)1868 static FnCallResult FnCallPackagesMatching(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
1869 {
1870 const bool installed_mode = (strcmp(fp->name, "packagesmatching") == 0);
1871 pcre *matcher;
1872 {
1873 const char *regex_package = RlistScalarValue(finalargs);
1874 const char *regex_version = RlistScalarValue(finalargs->next);
1875 const char *regex_arch = RlistScalarValue(finalargs->next->next);
1876 const char *regex_method = RlistScalarValue(finalargs->next->next->next);
1877 char regex[CF_BUFSIZE];
1878
1879 // Here we will truncate the regex if the parameters add up to over CF_BUFSIZE
1880 snprintf(regex, sizeof(regex), "^%s,%s,%s,%s$",
1881 regex_package, regex_version, regex_arch, regex_method);
1882 matcher = CompileRegex(regex);
1883 if (matcher == NULL)
1884 {
1885 return FnFailure();
1886 }
1887 }
1888
1889 JsonElement *json = JsonArrayCreate(50);
1890 bool ret = false;
1891
1892 Rlist *default_inventory = GetDefaultInventoryFromContext(ctx);
1893 if (!default_inventory)
1894 {
1895 // Legacy package promise
1896 ret = GetLegacyPackagesMatching(matcher, json, installed_mode);
1897 }
1898 else
1899 {
1900 // We are using package modules.
1901 ret = GetPackagesMatching(matcher, json, installed_mode, default_inventory);
1902 }
1903
1904 pcre_free(matcher);
1905
1906 if (ret == false)
1907 {
1908 Log(LOG_LEVEL_ERR,
1909 "%s: Unable to read package inventory.", fp->name);
1910 JsonDestroy(json);
1911 return FnFailure();
1912 }
1913
1914 return FnReturnContainerNoCopy(json);
1915 }
1916
1917 /*********************************************************************/
1918
FnCallCanonify(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)1919 static FnCallResult FnCallCanonify(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
1920 {
1921 char buf[CF_BUFSIZE];
1922 char *string = RlistScalarValue(finalargs);
1923
1924 buf[0] = '\0';
1925
1926 if (!strcmp(fp->name, "canonifyuniquely"))
1927 {
1928 char hashbuffer[CF_HOSTKEY_STRING_SIZE];
1929 unsigned char digest[EVP_MAX_MD_SIZE + 1];
1930 HashMethod type;
1931
1932 type = HashIdFromName("sha1");
1933 HashString(string, strlen(string), digest, type);
1934 snprintf(buf, CF_BUFSIZE, "%s_%s", string,
1935 SkipHashType(HashPrintSafe(hashbuffer, sizeof(hashbuffer),
1936 digest, type, true)));
1937 }
1938 else
1939 {
1940 snprintf(buf, CF_BUFSIZE, "%s", string);
1941 }
1942
1943 return FnReturn(CanonifyName(buf));
1944 }
1945
1946 /*********************************************************************/
1947
FnCallTextXform(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)1948 static FnCallResult FnCallTextXform(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
1949 {
1950 char *string = RlistScalarValue(finalargs);
1951 const size_t len = strlen(string);
1952 /* In case of string_length(), buf needs enough space to hold a number. */
1953 const size_t bufsiz = MAX(len + 1, PRINTSIZE(len));
1954 char *buf = xcalloc(bufsiz, sizeof(char));
1955 memcpy(buf, string, len + 1);
1956
1957 if (StringEqual(fp->name, "string_downcase"))
1958 {
1959 for (size_t pos = 0; pos < len; pos++)
1960 {
1961 buf[pos] = tolower(buf[pos]);
1962 }
1963 }
1964 else if (StringEqual(fp->name, "string_upcase"))
1965 {
1966 for (size_t pos = 0; pos < len; pos++)
1967 {
1968 buf[pos] = toupper(buf[pos]);
1969 }
1970 }
1971 else if (StringEqual(fp->name, "string_reverse"))
1972 {
1973 if (len > 1) {
1974 size_t c, i, j;
1975 for (i = 0, j = len - 1; i < j; i++, j--)
1976 {
1977 c = buf[i];
1978 buf[i] = buf[j];
1979 buf[j] = c;
1980 }
1981 }
1982 }
1983 else if (StringEqual(fp->name, "string_length"))
1984 {
1985 xsnprintf(buf, bufsiz, "%zu", len);
1986 }
1987 else if (StringEqual(fp->name, "string_head"))
1988 {
1989 long max = IntFromString(RlistScalarValue(finalargs->next));
1990 // A negative offset -N on string_head() means the user wants up to the Nth from the end
1991 if (max < 0)
1992 {
1993 max = len - labs(max);
1994 }
1995
1996 // If the negative offset was too big, return an empty string
1997 if (max < 0)
1998 {
1999 max = 0;
2000 }
2001
2002 if ((size_t) max < bufsiz)
2003 {
2004 buf[max] = '\0';
2005 }
2006 }
2007 else if (StringEqual(fp->name, "string_tail"))
2008 {
2009 const long max = IntFromString(RlistScalarValue(finalargs->next));
2010 // A negative offset -N on string_tail() means the user wants up to the Nth from the start
2011
2012 if (max < 0)
2013 {
2014 size_t offset = MIN(labs(max), len);
2015 memcpy(buf, string + offset , len - offset + 1);
2016 }
2017 else if ((size_t) max < len)
2018 {
2019 memcpy(buf, string + len - max, max + 1);
2020 }
2021 }
2022 else
2023 {
2024 Log(LOG_LEVEL_ERR, "text xform with unknown call function %s, aborting", fp->name);
2025 free(buf);
2026 return FnFailure();
2027 }
2028
2029 return FnReturnNoCopy(buf);
2030 }
2031
2032 /*********************************************************************/
2033
FnCallLastNode(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)2034 static FnCallResult FnCallLastNode(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
2035 {
2036 char *name = RlistScalarValue(finalargs);
2037 char *split = RlistScalarValue(finalargs->next);
2038
2039 Rlist *newlist = RlistFromSplitRegex(name, split, 100, true);
2040 if (newlist != NULL)
2041 {
2042 char *res = NULL;
2043 const Rlist *rp = newlist;
2044 while (rp->next != NULL)
2045 {
2046 rp = rp->next;
2047 }
2048 assert(rp && !rp->next);
2049
2050 if (rp->val.item)
2051 {
2052 res = xstrdup(RlistScalarValue(rp));
2053 }
2054
2055 RlistDestroy(newlist);
2056 if (res)
2057 {
2058 return FnReturnNoCopy(res);
2059 }
2060 }
2061 return FnFailure();
2062 }
2063
2064 /*******************************************************************/
2065
FnCallDirname(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)2066 static FnCallResult FnCallDirname(ARG_UNUSED EvalContext *ctx,
2067 ARG_UNUSED const Policy *policy,
2068 ARG_UNUSED const FnCall *fp,
2069 const Rlist *finalargs)
2070 {
2071 char dir[PATH_MAX];
2072 strlcpy(dir, RlistScalarValue(finalargs), PATH_MAX);
2073
2074 DeleteSlash(dir);
2075 ChopLastNode(dir);
2076
2077 return FnReturn(dir);
2078 }
2079
2080 /*********************************************************************/
2081
FnCallClassify(EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)2082 static FnCallResult FnCallClassify(EvalContext *ctx,
2083 ARG_UNUSED const Policy *policy,
2084 ARG_UNUSED const FnCall *fp,
2085 const Rlist *finalargs)
2086 {
2087 bool is_defined = IsDefinedClass(ctx, CanonifyName(RlistScalarValue(finalargs)));
2088
2089 return FnReturnContext(is_defined);
2090 }
2091
2092 /*********************************************************************/
2093
GenericVersionCheck(const FnCall * fp,const Rlist * args)2094 static VersionComparison GenericVersionCheck(
2095 const FnCall *fp,
2096 const Rlist *args)
2097 {
2098 assert(fp != NULL);
2099 assert(fp->name != NULL);
2100 if (args == NULL)
2101 {
2102 Log(LOG_LEVEL_ERR,
2103 "Policy fuction %s requires version to compare against",
2104 fp->name);
2105 return VERSION_ERROR;
2106 }
2107
2108 const char *ver_string = RlistScalarValue(args);
2109 VersionComparison comparison = CompareVersion(Version(), ver_string);
2110 if (comparison == VERSION_ERROR)
2111 {
2112 Log(LOG_LEVEL_ERR,
2113 "%s: Format of version comparison string '%s' is incorrect",
2114 fp->name, ver_string);
2115 return VERSION_ERROR;
2116 }
2117
2118 return comparison;
2119 }
2120
FnCallVersionMinimum(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * args)2121 static FnCallResult FnCallVersionMinimum(
2122 ARG_UNUSED EvalContext *ctx,
2123 ARG_UNUSED const Policy *policy,
2124 const FnCall *fp,
2125 const Rlist *args)
2126 {
2127 const VersionComparison comparison = GenericVersionCheck(fp, args);
2128 if (comparison == VERSION_ERROR)
2129 {
2130 return FnFailure();
2131 }
2132
2133 return FnReturnContext(comparison == VERSION_GREATER ||
2134 comparison == VERSION_EQUAL);
2135 }
2136
FnCallVersionAfter(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * args)2137 static FnCallResult FnCallVersionAfter(
2138 ARG_UNUSED EvalContext *ctx,
2139 ARG_UNUSED const Policy *policy,
2140 const FnCall *fp,
2141 const Rlist *args)
2142 {
2143 const VersionComparison comparison = GenericVersionCheck(fp, args);
2144 if (comparison == VERSION_ERROR)
2145 {
2146 return FnFailure();
2147 }
2148
2149 return FnReturnContext(comparison == VERSION_GREATER);
2150 }
2151
FnCallVersionMaximum(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * args)2152 static FnCallResult FnCallVersionMaximum(
2153 ARG_UNUSED EvalContext *ctx,
2154 ARG_UNUSED const Policy *policy,
2155 const FnCall *fp,
2156 const Rlist *args)
2157 {
2158 const VersionComparison comparison = GenericVersionCheck(fp, args);
2159 if (comparison == VERSION_ERROR)
2160 {
2161 return FnFailure();
2162 }
2163
2164 return FnReturnContext(comparison == VERSION_SMALLER ||
2165 comparison == VERSION_EQUAL);
2166 }
2167
FnCallVersionBefore(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * args)2168 static FnCallResult FnCallVersionBefore(
2169 ARG_UNUSED EvalContext *ctx,
2170 ARG_UNUSED const Policy *policy,
2171 const FnCall *fp,
2172 const Rlist *args)
2173 {
2174 const VersionComparison comparison = GenericVersionCheck(fp, args);
2175 if (comparison == VERSION_ERROR)
2176 {
2177 return FnFailure();
2178 }
2179
2180 return FnReturnContext(comparison == VERSION_SMALLER);
2181 }
2182
FnCallVersionAt(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * args)2183 static FnCallResult FnCallVersionAt(
2184 ARG_UNUSED EvalContext *ctx,
2185 ARG_UNUSED const Policy *policy,
2186 const FnCall *fp,
2187 const Rlist *args)
2188 {
2189 const VersionComparison comparison = GenericVersionCheck(fp, args);
2190 if (comparison == VERSION_ERROR)
2191 {
2192 return FnFailure();
2193 }
2194
2195 return FnReturnContext(comparison == VERSION_EQUAL);
2196 }
2197
FnCallVersionBetween(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * args)2198 static FnCallResult FnCallVersionBetween(
2199 ARG_UNUSED EvalContext *ctx,
2200 ARG_UNUSED const Policy *policy,
2201 const FnCall *fp,
2202 const Rlist *args)
2203 {
2204 assert(fp != NULL);
2205 assert(fp->name != NULL);
2206 if (args == NULL || args->next == NULL)
2207 {
2208 Log(LOG_LEVEL_ERR,
2209 "Policy fuction %s requires lower "
2210 "and upper versions to compare against",
2211 fp->name);
2212 return FnFailure();
2213 }
2214
2215 const char *ver_string_lower = RlistScalarValue(args);
2216 const VersionComparison lower_comparison =
2217 CompareVersion(Version(), ver_string_lower);
2218 if (lower_comparison == VERSION_ERROR)
2219 {
2220 Log(LOG_LEVEL_ERR,
2221 "%s: Format of lower version comparison string '%s' is incorrect",
2222 fp->name, ver_string_lower);
2223 return FnFailure();
2224 }
2225
2226 const char *ver_string_upper = RlistScalarValue(args->next);
2227 const VersionComparison upper_comparison =
2228 CompareVersion(Version(), ver_string_upper);
2229 if (upper_comparison == VERSION_ERROR)
2230 {
2231 Log(LOG_LEVEL_ERR,
2232 "%s: Format of upper version comparison string '%s' is incorrect",
2233 fp->name, ver_string_upper);
2234 return FnFailure();
2235 }
2236
2237 return FnReturnContext((lower_comparison == VERSION_GREATER ||
2238 lower_comparison == VERSION_EQUAL) &&
2239 (upper_comparison == VERSION_SMALLER ||
2240 upper_comparison == VERSION_EQUAL));
2241 }
2242
2243 /*********************************************************************/
2244 /* Executions */
2245 /*********************************************************************/
2246
FnCallReturnsZero(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)2247 static FnCallResult FnCallReturnsZero(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
2248 {
2249 char comm[CF_BUFSIZE];
2250 const char *shell_option = RlistScalarValue(finalargs->next);
2251 ShellType shelltype = SHELL_TYPE_NONE;
2252 bool need_executable_check = false;
2253
2254 if (strcmp(shell_option, "useshell") == 0)
2255 {
2256 shelltype = SHELL_TYPE_USE;
2257 }
2258 else if (strcmp(shell_option, "powershell") == 0)
2259 {
2260 shelltype = SHELL_TYPE_POWERSHELL;
2261 }
2262
2263 if (IsAbsoluteFileName(RlistScalarValue(finalargs)))
2264 {
2265 need_executable_check = true;
2266 }
2267 else if (shelltype == SHELL_TYPE_NONE)
2268 {
2269 Log(LOG_LEVEL_ERR, "returnszero '%s' does not have an absolute path", RlistScalarValue(finalargs));
2270 return FnReturnContext(false);
2271 }
2272
2273 if (need_executable_check && !IsExecutable(CommandArg0(RlistScalarValue(finalargs))))
2274 {
2275 Log(LOG_LEVEL_ERR, "returnszero '%s' is assumed to be executable but isn't", RlistScalarValue(finalargs));
2276 return FnReturnContext(false);
2277 }
2278
2279 snprintf(comm, CF_BUFSIZE, "%s", RlistScalarValue(finalargs));
2280
2281 if (ShellCommandReturnsZero(comm, shelltype))
2282 {
2283 Log(LOG_LEVEL_VERBOSE, "%s ran '%s' successfully and it returned zero", fp->name, RlistScalarValue(finalargs));
2284 return FnReturnContext(true);
2285 }
2286 else
2287 {
2288 Log(LOG_LEVEL_VERBOSE, "%s ran '%s' successfully and it did not return zero", fp->name, RlistScalarValue(finalargs));
2289 return FnReturnContext(false);
2290 }
2291 }
2292
2293 /*********************************************************************/
2294
FnCallExecResult(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)2295 static FnCallResult FnCallExecResult(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
2296 {
2297 assert(fp != NULL);
2298
2299 const char *const function = fp->name;
2300 size_t args = RlistLen(finalargs);
2301 if (args == 0)
2302 {
2303 FatalError(ctx, "Missing argument to %s() - Must specify command", function);
2304 }
2305 else if (args == 1)
2306 {
2307 FatalError(ctx, "Missing argument to %s() - Must specify 'noshell', 'useshell', or 'powershell'", function);
2308 }
2309 else if (args > 3)
2310 {
2311 FatalError(ctx, "Too many arguments to %s() - Maximum 3 allowed", function);
2312 }
2313 const char *shell_option = RlistScalarValue(finalargs->next);
2314 ShellType shelltype = SHELL_TYPE_NONE;
2315 bool need_executable_check = false;
2316
2317 if (strcmp(shell_option, "useshell") == 0)
2318 {
2319 shelltype = SHELL_TYPE_USE;
2320 }
2321 else if (strcmp(shell_option, "powershell") == 0)
2322 {
2323 shelltype = SHELL_TYPE_POWERSHELL;
2324 }
2325
2326 const char *const command = RlistScalarValue(finalargs);
2327 if (IsAbsoluteFileName(command))
2328 {
2329 need_executable_check = true;
2330 }
2331 else if (shelltype == SHELL_TYPE_NONE)
2332 {
2333 Log(LOG_LEVEL_ERR, "%s '%s' does not have an absolute path", fp->name, command);
2334 return FnFailure();
2335 }
2336
2337 if (need_executable_check && !IsExecutable(CommandArg0(command)))
2338 {
2339 Log(LOG_LEVEL_ERR, "%s '%s' is assumed to be executable but isn't", fp->name, command);
2340 return FnFailure();
2341 }
2342
2343 size_t buffer_size = CF_EXPANDSIZE;
2344 char *buffer = xcalloc(1, buffer_size);
2345
2346 OutputSelect output_select = OUTPUT_SELECT_BOTH;
2347
2348 if (args >= 3)
2349 {
2350 const char *output = RlistScalarValue(finalargs->next->next);
2351 if (StringEqual(output, "stderr"))
2352 {
2353 output_select = OUTPUT_SELECT_STDERR;
2354 }
2355 else if (StringEqual(output, "stdout"))
2356 {
2357 output_select = OUTPUT_SELECT_STDOUT;
2358 }
2359 else
2360 {
2361 assert(StringEqual(output, "both"));
2362 assert(output_select == OUTPUT_SELECT_BOTH);
2363 }
2364 }
2365
2366 int exit_code;
2367
2368 if (GetExecOutput(command, &buffer, &buffer_size, shelltype, output_select, &exit_code))
2369 {
2370 Log(LOG_LEVEL_VERBOSE, "%s ran '%s' successfully", fp->name, command);
2371 if (StringEqual(function, "execresult"))
2372 {
2373 FnCallResult res = FnReturn(buffer);
2374 free(buffer);
2375 return res;
2376 }
2377 else
2378 {
2379 assert(StringEqual(function, "execresult_as_data"));
2380 JsonElement *result = JsonObjectCreate(2);
2381 JsonObjectAppendInteger(result, "exit_code", exit_code);
2382 JsonObjectAppendString(result, "output", buffer);
2383 free(buffer);
2384 return FnReturnContainerNoCopy(result);
2385 }
2386 }
2387 else
2388 {
2389 Log(LOG_LEVEL_VERBOSE, "%s could not run '%s' successfully", fp->name, command);
2390 free(buffer);
2391 return FnFailure();
2392 }
2393 }
2394
2395 /*********************************************************************/
2396
FnCallUseModule(EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)2397 static FnCallResult FnCallUseModule(EvalContext *ctx,
2398 ARG_UNUSED const Policy *policy,
2399 ARG_UNUSED const FnCall *fp,
2400 const Rlist *finalargs)
2401 /* usemodule("/programpath",varargs) */
2402 {
2403 char modulecmd[CF_BUFSIZE];
2404 struct stat statbuf;
2405
2406 char *command = RlistScalarValue(finalargs);
2407 char *args = RlistScalarValue(finalargs->next);
2408 const char* const workdir = GetWorkDir();
2409
2410 snprintf(modulecmd, CF_BUFSIZE, "\"%s%cmodules%c%s\"",
2411 workdir, FILE_SEPARATOR, FILE_SEPARATOR, command);
2412
2413 if (stat(CommandArg0(modulecmd), &statbuf) == -1)
2414 {
2415 Log(LOG_LEVEL_ERR, "Plug-in module '%s' not found", modulecmd);
2416 return FnFailure();
2417 }
2418
2419 if ((statbuf.st_uid != 0) && (statbuf.st_uid != getuid()))
2420 {
2421 Log(LOG_LEVEL_ERR, "Module '%s' was not owned by uid %ju who is executing agent", modulecmd, (uintmax_t)getuid());
2422 return FnFailure();
2423 }
2424
2425 snprintf(modulecmd, CF_BUFSIZE, "\"%s%cmodules%c%s\" %s",
2426 workdir, FILE_SEPARATOR, FILE_SEPARATOR, command, args);
2427
2428 Log(LOG_LEVEL_VERBOSE, "Executing and using module [%s]", modulecmd);
2429
2430 if (!ExecModule(ctx, modulecmd))
2431 {
2432 return FnFailure();
2433 }
2434
2435 return FnReturnContext(true);
2436 }
2437
2438 /*********************************************************************/
2439 /* Misc */
2440 /*********************************************************************/
2441
FnCallSplayClass(EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)2442 static FnCallResult FnCallSplayClass(EvalContext *ctx,
2443 ARG_UNUSED const Policy *policy,
2444 ARG_UNUSED const FnCall *fp,
2445 const Rlist *finalargs)
2446 {
2447 char class_name[CF_MAXVARSIZE];
2448
2449 Interval splay_policy = IntervalFromString(RlistScalarValue(finalargs->next));
2450
2451 if (splay_policy == INTERVAL_HOURLY)
2452 {
2453 /* 12 5-minute slots in hour */
2454 int slot = StringHash(RlistScalarValue(finalargs), 0);
2455 slot &= (SPLAY_PSEUDO_RANDOM_CONSTANT - 1);
2456 slot = slot * 12 / SPLAY_PSEUDO_RANDOM_CONSTANT;
2457 snprintf(class_name, CF_MAXVARSIZE, "Min%02d_%02d", slot * 5, ((slot + 1) * 5) % 60);
2458 }
2459 else
2460 {
2461 /* 12*24 5-minute slots in day */
2462 int dayslot = StringHash(RlistScalarValue(finalargs), 0);
2463 dayslot &= (SPLAY_PSEUDO_RANDOM_CONSTANT - 1);
2464 dayslot = dayslot * 12 * 24 / SPLAY_PSEUDO_RANDOM_CONSTANT;
2465 int hour = dayslot / 12;
2466 int slot = dayslot % 12;
2467
2468 snprintf(class_name, CF_MAXVARSIZE, "Min%02d_%02d.Hr%02d", slot * 5, ((slot + 1) * 5) % 60, hour);
2469 }
2470
2471 Log(LOG_LEVEL_VERBOSE, "Computed context for '%s' splayclass: '%s'", RlistScalarValue(finalargs), class_name);
2472 return FnReturnContext(IsDefinedClass(ctx, class_name));
2473 }
2474
2475 /*********************************************************************/
2476
2477 #ifdef HAVE_LIBCURL
2478 struct _curl_userdata
2479 {
2480 const FnCall *fp;
2481 const char *desc;
2482 size_t max_size;
2483 Buffer* content;
2484 };
2485
cfengine_curl_write_callback(char * ptr,size_t size,size_t nmemb,void * userdata)2486 static size_t cfengine_curl_write_callback(char *ptr, size_t size, size_t nmemb, void *userdata)
2487 {
2488 struct _curl_userdata *options = (struct _curl_userdata*) userdata;
2489 unsigned int old = BufferSize(options->content);
2490 size_t requested = size*nmemb;
2491 size_t granted = requested;
2492
2493 if (old + requested > options->max_size)
2494 {
2495 granted = options->max_size - old;
2496 Log(LOG_LEVEL_VERBOSE,
2497 "%s: while receiving %s, current %u + requested %zu bytes would be over the maximum %zu; only accepting %zu bytes",
2498 options->fp->name, options->desc, old, requested, options->max_size, granted);
2499 }
2500
2501 BufferAppend(options->content, ptr, granted);
2502
2503 // `written` is actually (BufferSize(options->content) - old) but
2504 // libcurl doesn't like that
2505 size_t written = requested;
2506
2507 // extra caution
2508 BufferTrimToMaxLength(options->content, options->max_size);
2509 return written;
2510 }
2511
CurlCleanup()2512 static void CurlCleanup()
2513 {
2514 if (CURL_CACHE == NULL)
2515 {
2516 JsonElement *temp = CURL_CACHE;
2517 CURL_CACHE = NULL;
2518 JsonDestroy(temp);
2519 }
2520
2521 if (CURL_INITIALIZED)
2522 {
2523 curl_global_cleanup();
2524 CURL_INITIALIZED = false;
2525 }
2526
2527 }
2528 #endif
2529
FnCallUrlGet(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)2530 static FnCallResult FnCallUrlGet(ARG_UNUSED EvalContext *ctx,
2531 ARG_UNUSED const Policy *policy,
2532 const FnCall *fp,
2533 const Rlist *finalargs)
2534 {
2535
2536 #ifdef HAVE_LIBCURL
2537
2538 char *url = RlistScalarValue(finalargs);
2539 bool allocated = false;
2540 JsonElement *options = VarNameOrInlineToJson(ctx, fp, finalargs->next, false, &allocated);
2541
2542 if (options == NULL)
2543 {
2544 return FnFailure();
2545 }
2546
2547 if (JsonGetElementType(options) != JSON_ELEMENT_TYPE_CONTAINER ||
2548 JsonGetContainerType(options) != JSON_CONTAINER_TYPE_OBJECT)
2549 {
2550 JsonDestroyMaybe(options, allocated);
2551 return FnFailure();
2552 }
2553
2554 Writer *cache_w = StringWriter();
2555 WriterWriteF(cache_w, "url = %s; options = ", url);
2556 JsonWriteCompact(cache_w, options);
2557
2558 if (CURL_CACHE == NULL)
2559 {
2560 CURL_CACHE = JsonObjectCreate(10);
2561 atexit(&CurlCleanup);
2562 }
2563
2564 JsonElement *old_result = JsonObjectGetAsObject(CURL_CACHE, StringWriterData(cache_w));
2565
2566 if (old_result != NULL)
2567 {
2568 Log(LOG_LEVEL_VERBOSE, "%s: found cached request for %s", fp->name, url);
2569 WriterClose(cache_w);
2570 JsonDestroyMaybe(options, allocated);
2571 return FnReturnContainer(old_result);
2572 }
2573
2574 if (!CURL_INITIALIZED && curl_global_init(CURL_GLOBAL_DEFAULT) != 0)
2575 {
2576 Log(LOG_LEVEL_ERR, "%s: libcurl initialization failed, sorry", fp->name);
2577
2578 WriterClose(cache_w);
2579 JsonDestroyMaybe(options, allocated);
2580 return FnFailure();
2581 }
2582
2583 CURL_INITIALIZED = true;
2584
2585 CURL *curl = curl_easy_init();
2586 if (!curl)
2587 {
2588 Log(LOG_LEVEL_ERR, "%s: libcurl easy_init failed, sorry", fp->name);
2589
2590 WriterClose(cache_w);
2591 JsonDestroyMaybe(options, allocated);
2592 return FnFailure();
2593 }
2594
2595 Buffer *content = BufferNew();
2596 Buffer *headers = BufferNew();
2597 curl_easy_setopt(curl, CURLOPT_URL, url);
2598 curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); // do not use signals
2599 curl_easy_setopt(curl, CURLOPT_TIMEOUT, 3L); // set default timeout
2600 curl_easy_setopt(curl, CURLOPT_VERBOSE, 0);
2601 curl_easy_setopt(curl,
2602 CURLOPT_PROTOCOLS,
2603
2604 // Allowed protocols
2605 CURLPROTO_FILE |
2606 CURLPROTO_FTP |
2607 CURLPROTO_FTPS |
2608 CURLPROTO_HTTP |
2609 CURLPROTO_HTTPS);
2610
2611 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, cfengine_curl_write_callback);
2612 curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, cfengine_curl_write_callback);
2613
2614 size_t max_content = 4096;
2615 size_t max_headers = 4096;
2616 JsonIterator iter = JsonIteratorInit(options);
2617 const JsonElement *e;
2618 while ((e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true)))
2619 {
2620 const char *key = JsonIteratorCurrentKey(&iter);
2621 const char *value = JsonPrimitiveGetAsString(e);
2622
2623 if (strcmp(key, "url.timeout") == 0)
2624 {
2625 Log(LOG_LEVEL_VERBOSE, "%s: setting timeout to %ld seconds", fp->name, IntFromString(value));
2626 curl_easy_setopt(curl, CURLOPT_TIMEOUT, IntFromString(value));
2627 }
2628 else if (strcmp(key, "url.verbose") == 0)
2629 {
2630 Log(LOG_LEVEL_VERBOSE, "%s: setting verbosity to %ld", fp->name, IntFromString(value));
2631 curl_easy_setopt(curl, CURLOPT_VERBOSE, IntFromString(value));
2632 }
2633 else if (strcmp(key, "url.header") == 0)
2634 {
2635 Log(LOG_LEVEL_VERBOSE, "%s: setting inline headers to %ld", fp->name, IntFromString(value));
2636 curl_easy_setopt(curl, CURLOPT_HEADER, IntFromString(value));
2637 }
2638 else if (strcmp(key, "url.referer") == 0)
2639 {
2640 Log(LOG_LEVEL_VERBOSE, "%s: setting referer to %s", fp->name, value);
2641 curl_easy_setopt(curl, CURLOPT_REFERER, value);
2642 }
2643 else if (strcmp(key, "url.user-agent") == 0)
2644 {
2645 Log(LOG_LEVEL_VERBOSE, "%s: setting user agent string to %s", fp->name, value);
2646 curl_easy_setopt(curl, CURLOPT_USERAGENT, value);
2647 }
2648 else if (strcmp(key, "url.max_content") == 0)
2649 {
2650 Log(LOG_LEVEL_VERBOSE, "%s: setting max contents to %ld", fp->name, IntFromString(value));
2651 max_content = IntFromString(value);
2652 }
2653 else if (strcmp(key, "url.max_headers") == 0)
2654 {
2655 Log(LOG_LEVEL_VERBOSE, "%s: setting max headers to %ld", fp->name, IntFromString(value));
2656 max_headers = IntFromString(value);
2657 }
2658 else
2659 {
2660 Log(LOG_LEVEL_INFO, "%s: unknown option %s", fp->name, key);
2661 }
2662 }
2663
2664 struct _curl_userdata data = { fp, "content", max_content, content };
2665 curl_easy_setopt(curl, CURLOPT_WRITEDATA, &data);
2666
2667 struct _curl_userdata header_data = { fp, "headers", max_headers, headers };
2668 curl_easy_setopt(curl, CURLOPT_HEADERDATA, &header_data);
2669
2670 JsonElement *options_headers = JsonObjectGetAsArray(options, "url.headers");
2671 struct curl_slist *header_list = NULL;
2672
2673 if (options_headers != NULL)
2674 {
2675 iter = JsonIteratorInit(options_headers);
2676 while ((e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true)))
2677 {
2678 header_list = curl_slist_append(header_list, JsonPrimitiveGetAsString(e));
2679 }
2680 }
2681
2682 if (header_list != NULL)
2683 {
2684 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header_list);
2685 }
2686
2687 JsonElement *result = JsonObjectCreate(10);
2688 CURLcode res = curl_easy_perform(curl);
2689 if (header_list != NULL)
2690 {
2691 curl_slist_free_all(header_list);
2692 header_list = NULL;
2693 }
2694
2695 long returncode = 0;
2696 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &returncode);
2697 JsonObjectAppendInteger(result, "returncode", returncode);
2698
2699 curl_easy_cleanup(curl);
2700
2701 JsonObjectAppendInteger(result, "rc", 0+res);
2702
2703 bool success = (CURLE_OK == res);
2704 JsonObjectAppendBool(result, "success", success);
2705
2706 if (!success)
2707 {
2708 JsonObjectAppendString(result, "error_message", curl_easy_strerror(res));
2709 }
2710
2711
2712
2713 BufferTrimToMaxLength(content, max_content);
2714 JsonObjectAppendString(result, "content", BufferData(content));
2715 BufferDestroy(content);
2716
2717 BufferTrimToMaxLength(headers, max_headers);
2718 JsonObjectAppendString(result, "headers", BufferData(headers));
2719 BufferDestroy(headers);
2720
2721 JsonObjectAppendObject(CURL_CACHE, StringWriterData(cache_w), JsonCopy(result));
2722 WriterClose(cache_w);
2723
2724 JsonDestroyMaybe(options, allocated);
2725 return FnReturnContainerNoCopy(result);
2726
2727 #else
2728
2729 UNUSED(finalargs); /* suppress unused parameter warning */
2730 Log(LOG_LEVEL_ERR,
2731 "%s: libcurl integration is not compiled into CFEngine, sorry", fp->name);
2732 return FnFailure();
2733
2734 #endif
2735 }
2736
2737 /*********************************************************************/
2738
2739 /* ReadTCP(localhost,80,'GET index.html',1000) */
FnCallReadTcp(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)2740 static FnCallResult FnCallReadTcp(ARG_UNUSED EvalContext *ctx,
2741 ARG_UNUSED const Policy *policy,
2742 ARG_UNUSED const FnCall *fp,
2743 const Rlist *finalargs)
2744 {
2745 char *hostnameip = RlistScalarValue(finalargs);
2746 char *port = RlistScalarValue(finalargs->next);
2747 char *sendstring = RlistScalarValue(finalargs->next->next);
2748 ssize_t maxbytes = IntFromString(RlistScalarValue(finalargs->next->next->next));
2749
2750 if (THIS_AGENT_TYPE == AGENT_TYPE_COMMON)
2751 {
2752 return FnFailure();
2753 }
2754
2755 if (maxbytes < 0 || maxbytes > CF_BUFSIZE - 1)
2756 {
2757 Log(LOG_LEVEL_VERBOSE,
2758 "readtcp: invalid number of bytes %zd to read, defaulting to %d",
2759 maxbytes, CF_BUFSIZE - 1);
2760 maxbytes = CF_BUFSIZE - 1;
2761 }
2762
2763 char txtaddr[CF_MAX_IP_LEN] = "";
2764 int sd = SocketConnect(hostnameip, port, CONNTIMEOUT, false,
2765 txtaddr, sizeof(txtaddr));
2766 if (sd == -1)
2767 {
2768 Log(LOG_LEVEL_INFO, "readtcp: Couldn't connect. (socket: %s)",
2769 GetErrorStr());
2770 return FnFailure();
2771 }
2772
2773 if (strlen(sendstring) > 0)
2774 {
2775 int sent = 0;
2776 int result = 0;
2777 size_t length = strlen(sendstring);
2778 do
2779 {
2780 result = send(sd, sendstring, length, 0);
2781 if (result < 0)
2782 {
2783 cf_closesocket(sd);
2784 return FnFailure();
2785 }
2786 else
2787 {
2788 sent += result;
2789 }
2790 } while ((size_t) sent < length);
2791 }
2792
2793 char recvbuf[CF_BUFSIZE];
2794 ssize_t n_read = recv(sd, recvbuf, maxbytes, 0);
2795 cf_closesocket(sd);
2796
2797 if (n_read < 0)
2798 {
2799 Log(LOG_LEVEL_INFO, "readtcp: Error while receiving (%s)",
2800 GetErrorStr());
2801 return FnFailure();
2802 }
2803
2804 assert((size_t) n_read < sizeof(recvbuf));
2805 recvbuf[n_read] = '\0';
2806
2807 Log(LOG_LEVEL_VERBOSE,
2808 "readtcp: requested %zd maxbytes, got %zd bytes from %s",
2809 maxbytes, n_read, txtaddr);
2810
2811 return FnReturn(recvbuf);
2812 }
2813
2814 /*********************************************************************/
2815
2816 /**
2817 * Look for the indices of a variable in #finalargs if it is an array.
2818 *
2819 * @return *Always* return an slist of the indices; if the variable is not an
2820 * array or does not resolve at all, return an empty slist.
2821 *
2822 * @NOTE
2823 * This is needed for literally one acceptance test:
2824 * 01_vars/02_functions/getindices_returns_expected_list_from_array.cf
2825 *
2826 * The case is that we have a[x] = "1" AND a[x][y] = "2" which is
2827 * ambiguous, but classic CFEngine arrays allow it. So we want the
2828 * classic getindices("a[x]") to return "y" in this case.
2829 */
FnCallGetIndicesClassic(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)2830 static FnCallResult FnCallGetIndicesClassic(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
2831 {
2832 VarRef *ref = VarRefParse(RlistScalarValue(finalargs));
2833 if (!VarRefIsQualified(ref))
2834 {
2835 if (fp->caller)
2836 {
2837 const Bundle *caller_bundle = PromiseGetBundle(fp->caller);
2838 VarRefQualify(ref, caller_bundle->ns, caller_bundle->name);
2839 }
2840 else
2841 {
2842 Log(LOG_LEVEL_WARNING,
2843 "Function '%s' was given an unqualified variable reference, "
2844 "and it was not called from a promise. "
2845 "No way to automatically qualify the reference '%s'",
2846 fp->name, RlistScalarValue(finalargs));
2847 VarRefDestroy(ref);
2848 return FnFailure();
2849 }
2850 }
2851
2852 Rlist *keys = NULL;
2853
2854 VariableTableIterator *iter = EvalContextVariableTableFromRefIteratorNew(ctx, ref);
2855 const Variable *itervar;
2856 while ((itervar = VariableTableIteratorNext(iter)) != NULL)
2857 {
2858 const VarRef *itervar_ref = VariableGetRef(itervar);
2859 /*
2860 Log(LOG_LEVEL_DEBUG,
2861 "%s(%s): got itervar->ref->num_indices %zu while ref->num_indices is %zu",
2862 fp->name, RlistScalarValue(finalargs),
2863 itervar->ref->num_indices, ref->num_indices);
2864 */
2865 /* Does the variable we found have more indices than the one we
2866 * requested? For example, if we requested the variable "blah", it has
2867 * 0 indices, so a found variable blah[i] will be acceptable. */
2868 if (itervar_ref->num_indices > ref->num_indices)
2869 {
2870 RlistAppendScalarIdemp(&keys, itervar_ref->indices[ref->num_indices]);
2871 }
2872 }
2873
2874 VariableTableIteratorDestroy(iter);
2875 VarRefDestroy(ref);
2876
2877 return (FnCallResult) { FNCALL_SUCCESS, { keys, RVAL_TYPE_LIST } };
2878 }
2879
2880 /*********************************************************************/
2881
FnCallGetIndices(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)2882 static FnCallResult FnCallGetIndices(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
2883 {
2884 const char *name_str = RlistScalarValueSafe(finalargs);
2885 bool allocated = false;
2886 JsonElement *json = NULL;
2887
2888 // Protect against collected args (their rval type will be data
2889 // container). This is a special case to preserve legacy behavior
2890 // for array lookups that requires a scalar in finalargs.
2891 if (RlistValueIsType(finalargs, RVAL_TYPE_SCALAR))
2892 {
2893 VarRef *ref = ResolveAndQualifyVarName(fp, name_str);
2894 DataType type;
2895 EvalContextVariableGet(ctx, ref, &type);
2896
2897 /* A variable holding a data container. */
2898 if (type == CF_DATA_TYPE_CONTAINER)
2899 {
2900 json = VarRefValueToJson(ctx, fp, ref, NULL, 0, true, &allocated);
2901 }
2902 /* Resolves to a different type or does not resolve at all. It's
2903 * normal not to resolve, for example "blah" will not resolve if the
2904 * variable table only contains "blah[1]"; we have to go through
2905 * FnCallGetIndicesClassic() to extract these indices. */
2906 else
2907 {
2908 JsonParseError res = JsonParseWithLookup(ctx, &LookupVarRefToJson, &name_str, &json);
2909 if (res == JSON_PARSE_OK)
2910 {
2911 if (JsonGetElementType(json) == JSON_ELEMENT_TYPE_PRIMITIVE)
2912 {
2913 // VarNameOrInlineToJson() would now look up this primitive
2914 // in the variable table, returning a JSON container for
2915 // whatever type it is, but since we already know that it's
2916 // not a native container type (thanks to the
2917 // CF_DATA_TYPE_CONTAINER check above) we skip that, and
2918 // stick to the legacy data types.
2919 JsonDestroy(json);
2920 VarRefDestroy(ref);
2921 return FnCallGetIndicesClassic(ctx, policy, fp, finalargs);
2922 }
2923 else
2924 {
2925 // Inline JSON of some sort.
2926 allocated = true;
2927 }
2928 }
2929 else
2930 {
2931 /* Invalid inline JSON. */
2932 VarRefDestroy(ref);
2933 return FnCallGetIndicesClassic(ctx, policy, fp, finalargs);
2934 }
2935 }
2936
2937 VarRefDestroy(ref);
2938 }
2939 else
2940 {
2941 json = VarNameOrInlineToJson(ctx, fp, finalargs, true, &allocated);
2942 }
2943
2944 // we failed to produce a valid JsonElement, so give up
2945 if (json == NULL)
2946 {
2947 return FnFailure();
2948 }
2949
2950 Rlist *keys = NULL;
2951
2952 if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER)
2953 {
2954 JsonDestroyMaybe(json, allocated);
2955 return (FnCallResult) { FNCALL_SUCCESS, { keys, RVAL_TYPE_LIST } };
2956 }
2957
2958 if (JsonGetContainerType(json) == JSON_CONTAINER_TYPE_OBJECT)
2959 {
2960 JsonIterator iter = JsonIteratorInit(json);
2961 const char *key;
2962 while ((key = JsonIteratorNextKey(&iter)))
2963 {
2964 RlistAppendScalar(&keys, key);
2965 }
2966 }
2967 else
2968 {
2969 for (size_t i = 0; i < JsonLength(json); i++)
2970 {
2971 Rval key = (Rval) { StringFromLong(i), RVAL_TYPE_SCALAR };
2972 RlistAppendRval(&keys, key);
2973 }
2974 }
2975
2976 JsonDestroyMaybe(json, allocated);
2977 return (FnCallResult) { FNCALL_SUCCESS, { keys, RVAL_TYPE_LIST } };
2978 }
2979
2980 /*********************************************************************/
2981
CollectContainerValues(EvalContext * ctx,Rlist ** values,const JsonElement * container)2982 void CollectContainerValues(EvalContext *ctx, Rlist **values, const JsonElement *container)
2983 {
2984 if (JsonGetElementType(container) == JSON_ELEMENT_TYPE_CONTAINER)
2985 {
2986 JsonIterator iter = JsonIteratorInit(container);
2987 const JsonElement *el;
2988 while ((el = JsonIteratorNextValue(&iter)))
2989 {
2990 if (JsonGetElementType(el) == JSON_ELEMENT_TYPE_CONTAINER)
2991 {
2992 CollectContainerValues(ctx, values, el);
2993 }
2994 else
2995 {
2996 char *value = JsonPrimitiveToString(el);
2997 if (value != NULL)
2998 {
2999 RlistAppendScalar(values, value);
3000 free(value);
3001 }
3002 }
3003 }
3004 }
3005 else if (JsonGetElementType(container) == JSON_ELEMENT_TYPE_PRIMITIVE)
3006 {
3007 char *value = JsonPrimitiveToString(container);
3008 if (value != NULL)
3009 {
3010 RlistAppendScalar(values, value);
3011 free(value);
3012 }
3013 }
3014 }
3015
3016 /*********************************************************************/
3017
FnCallGetValues(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)3018 static FnCallResult FnCallGetValues(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
3019 {
3020 // try to load directly
3021 bool allocated = false;
3022 JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs, true, &allocated);
3023
3024 // we failed to produce a valid JsonElement, so give up
3025 if (json == NULL)
3026 {
3027 /* CFE-2479: Inexistent variable, return an empty slist. */
3028 Log(LOG_LEVEL_DEBUG, "getvalues('%s'):"
3029 " unresolvable variable, returning an empty list",
3030 RlistScalarValueSafe(finalargs));
3031 return (FnCallResult) { FNCALL_SUCCESS, { NULL, RVAL_TYPE_LIST } };
3032 }
3033
3034 Rlist *values = NULL; /* start with an empty Rlist */
3035 CollectContainerValues(ctx, &values, json);
3036
3037 JsonDestroyMaybe(json, allocated);
3038 return (FnCallResult) { FNCALL_SUCCESS, { values, RVAL_TYPE_LIST } };
3039 }
3040
3041 /*********************************************************************/
3042
FnCallGrep(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)3043 static FnCallResult FnCallGrep(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
3044 {
3045 return FilterInternal(ctx,
3046 fp,
3047 RlistScalarValue(finalargs), // regex
3048 finalargs->next, // list identifier
3049 1, // regex match = TRUE
3050 0, // invert matches = FALSE
3051 LONG_MAX); // max results = max int
3052 }
3053
3054 /*********************************************************************/
3055
FnCallRegList(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)3056 static FnCallResult FnCallRegList(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
3057 {
3058 return FilterInternal(ctx,
3059 fp,
3060 RlistScalarValue(finalargs->next), // regex or string
3061 finalargs, // list identifier
3062 1,
3063 0,
3064 LONG_MAX);
3065 }
3066
3067 /*********************************************************************/
3068
JoinContainer(const JsonElement * container,const char * delimiter)3069 static FnCallResult JoinContainer(const JsonElement *container, const char *delimiter)
3070 {
3071 JsonIterator iter = JsonIteratorInit(container);
3072 const JsonElement *e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true);
3073 if (!e)
3074 {
3075 return FnReturn("");
3076 }
3077
3078 Buffer *result = BufferNew();
3079 BufferAppendString(result, JsonPrimitiveGetAsString(e));
3080
3081 while ((e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true)))
3082 {
3083 BufferAppendString(result, delimiter);
3084 BufferAppendString(result, JsonPrimitiveGetAsString(e));
3085 }
3086
3087 return FnReturnBuffer(result);
3088 }
3089
FnCallJoin(EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)3090 static FnCallResult FnCallJoin(EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
3091 {
3092 const char *delimiter = RlistScalarValue(finalargs);
3093 const char *name_str = RlistScalarValueSafe(finalargs->next);
3094
3095 // try to load directly
3096 bool allocated = false;
3097 JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs->next, false, &allocated);
3098
3099 // we failed to produce a valid JsonElement, so give up
3100 if (json == NULL)
3101 {
3102 return FnFailure();
3103 }
3104 else if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER)
3105 {
3106 Log(LOG_LEVEL_VERBOSE, "Function '%s', argument '%s' was not a data container or list",
3107 fp->name, name_str);
3108 JsonDestroyMaybe(json, allocated);
3109 return FnFailure();
3110 }
3111
3112 FnCallResult result = JoinContainer(json, delimiter);
3113 JsonDestroyMaybe(json, allocated);
3114 return result;
3115
3116 }
3117
3118 /*********************************************************************/
3119
FnCallGetFields(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)3120 static FnCallResult FnCallGetFields(EvalContext *ctx,
3121 ARG_UNUSED const Policy *policy,
3122 const FnCall *fp,
3123 const Rlist *finalargs)
3124 {
3125 pcre *rx = CompileRegex(RlistScalarValue(finalargs));
3126 if (!rx)
3127 {
3128 return FnFailure();
3129 }
3130
3131 const char *filename = RlistScalarValue(finalargs->next);
3132 const char *split = RlistScalarValue(finalargs->next->next);
3133 const char *array_lval = RlistScalarValue(finalargs->next->next->next);
3134
3135 FILE *fin = safe_fopen(filename, "rt");
3136 if (!fin)
3137 {
3138 Log(LOG_LEVEL_ERR, "File '%s' could not be read in getfields(). (fopen: %s)", filename, GetErrorStr());
3139 pcre_free(rx);
3140 return FnFailure();
3141 }
3142
3143 size_t line_size = CF_BUFSIZE;
3144 char *line = xmalloc(CF_BUFSIZE);
3145
3146 int line_count = 0;
3147
3148 while (CfReadLine(&line, &line_size, fin) != -1)
3149 {
3150 if (!StringMatchFullWithPrecompiledRegex(rx, line))
3151 {
3152 continue;
3153 }
3154
3155 if (line_count == 0)
3156 {
3157 Rlist *newlist = RlistFromSplitRegex(line, split, 31, true);
3158 int vcount = 1;
3159
3160 for (const Rlist *rp = newlist; rp != NULL; rp = rp->next)
3161 {
3162 char name[CF_MAXVARSIZE];
3163 snprintf(name, CF_MAXVARSIZE - 1, "%s[%d]", array_lval, vcount);
3164 VarRef *ref = VarRefParse(name);
3165 if (!VarRefIsQualified(ref))
3166 {
3167 if (fp->caller)
3168 {
3169 const Bundle *caller_bundle = PromiseGetBundle(fp->caller);
3170 VarRefQualify(ref, caller_bundle->ns, caller_bundle->name);
3171 }
3172 else
3173 {
3174 Log(LOG_LEVEL_WARNING,
3175 "Function '%s' was given an unqualified variable reference, "
3176 "and it was not called from a promise. No way to automatically qualify the reference '%s'.",
3177 fp->name, RlistScalarValue(finalargs));
3178 VarRefDestroy(ref);
3179 free(line);
3180 RlistDestroy(newlist);
3181 pcre_free(rx);
3182 return FnFailure();
3183 }
3184 }
3185
3186 EvalContextVariablePut(ctx, ref, RlistScalarValue(rp), CF_DATA_TYPE_STRING, "source=function,function=getfields");
3187 VarRefDestroy(ref);
3188 Log(LOG_LEVEL_VERBOSE, "getfields: defining '%s' => '%s'", name, RlistScalarValue(rp));
3189 vcount++;
3190 }
3191
3192 RlistDestroy(newlist);
3193 }
3194
3195 line_count++;
3196 }
3197
3198 pcre_free(rx);
3199 free(line);
3200
3201 if (!feof(fin))
3202 {
3203 Log(LOG_LEVEL_ERR, "Unable to read data from file '%s'. (fgets: %s)", filename, GetErrorStr());
3204 fclose(fin);
3205 return FnFailure();
3206 }
3207
3208 fclose(fin);
3209
3210 return FnReturnF("%d", line_count);
3211 }
3212
3213 /*********************************************************************/
3214
FnCallCountLinesMatching(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)3215 static FnCallResult FnCallCountLinesMatching(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
3216 {
3217 pcre *rx = CompileRegex(RlistScalarValue(finalargs));
3218 if (!rx)
3219 {
3220 return FnFailure();
3221 }
3222
3223 char *filename = RlistScalarValue(finalargs->next);
3224
3225 FILE *fin = safe_fopen(filename, "rt");
3226 if (!fin)
3227 {
3228 Log(LOG_LEVEL_ERR, "File '%s' could not be read in countlinesmatching(). (fopen: %s)", filename, GetErrorStr());
3229 pcre_free(rx);
3230 return FnReturn("0");
3231 }
3232
3233 int lcount = 0;
3234 {
3235 size_t line_size = CF_BUFSIZE;
3236 char *line = xmalloc(line_size);
3237
3238 while (CfReadLine(&line, &line_size, fin) != -1)
3239 {
3240 if (StringMatchFullWithPrecompiledRegex(rx, line))
3241 {
3242 lcount++;
3243 Log(LOG_LEVEL_VERBOSE, "countlinesmatching: matched '%s'", line);
3244 continue;
3245 }
3246 }
3247
3248 free(line);
3249 }
3250
3251 pcre_free(rx);
3252
3253 if (!feof(fin))
3254 {
3255 Log(LOG_LEVEL_ERR, "Unable to read data from file '%s'. (fgets: %s)", filename, GetErrorStr());
3256 fclose(fin);
3257 return FnFailure();
3258 }
3259
3260 fclose(fin);
3261
3262 return FnReturnF("%d", lcount);
3263 }
3264
3265 /*********************************************************************/
3266
FnCallLsDir(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)3267 static FnCallResult FnCallLsDir(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
3268 {
3269 Rlist *newlist = NULL;
3270
3271 char *dirname = RlistScalarValue(finalargs);
3272 char *regex = RlistScalarValue(finalargs->next);
3273 int includepath = BooleanFromString(RlistScalarValue(finalargs->next->next));
3274
3275 Dir *dirh = DirOpen(dirname);
3276 if (dirh == NULL)
3277 {
3278 Log(LOG_LEVEL_ERR, "Directory '%s' could not be accessed in lsdir(), (opendir: %s)", dirname, GetErrorStr());
3279 return (FnCallResult) { FNCALL_SUCCESS, { newlist, RVAL_TYPE_LIST } };
3280 }
3281
3282 const struct dirent *dirp;
3283 for (dirp = DirRead(dirh); dirp != NULL; dirp = DirRead(dirh))
3284 {
3285 if (strlen(regex) == 0 || StringMatchFull(regex, dirp->d_name))
3286 {
3287 if (includepath)
3288 {
3289 char line[CF_BUFSIZE];
3290 snprintf(line, CF_BUFSIZE, "%s/%s", dirname, dirp->d_name);
3291 MapName(line);
3292 RlistPrepend(&newlist, line, RVAL_TYPE_SCALAR);
3293 }
3294 else
3295 {
3296 RlistPrepend(&newlist, dirp->d_name, RVAL_TYPE_SCALAR);
3297 }
3298 }
3299 }
3300 DirClose(dirh);
3301
3302 return (FnCallResult) { FNCALL_SUCCESS, { newlist, RVAL_TYPE_LIST } };
3303 }
3304
3305 /*********************************************************************/
3306
EvalContextVariablePutSpecialEscaped(EvalContext * ctx,SpecialScope scope,const char * lval,const void * value,DataType type,const char * tags,bool escape)3307 bool EvalContextVariablePutSpecialEscaped(EvalContext *ctx, SpecialScope scope, const char *lval, const void *value, DataType type, const char *tags, bool escape)
3308 {
3309 if (escape)
3310 {
3311 char *escaped = EscapeCharCopy(value, '"', '\\');
3312 bool ret = EvalContextVariablePutSpecial(ctx, scope, lval, escaped, type, tags);
3313 free(escaped);
3314 return ret;
3315 }
3316
3317 return EvalContextVariablePutSpecial(ctx, scope, lval, value, type, tags);
3318 }
3319
3320 /*********************************************************************/
3321
ExecJSON_Pipe(const char * cmd,JsonElement * container)3322 static JsonElement* ExecJSON_Pipe(const char *cmd, JsonElement *container)
3323 {
3324 IOData io = cf_popen_full_duplex(cmd, false, false);
3325
3326 if (io.write_fd == -1 || io.read_fd == -1)
3327 {
3328 Log(LOG_LEVEL_INFO, "An error occurred while communicating with '%s'", cmd);
3329
3330 return NULL;
3331 }
3332
3333 Log(LOG_LEVEL_DEBUG, "Opened fds %d and %d for command '%s'.",
3334 io.read_fd, io.write_fd, cmd);
3335
3336 // write the container to a string
3337 Writer *w = StringWriter();
3338 JsonWrite(w, container, 0);
3339 char *container_str = StringWriterClose(w);
3340
3341 ssize_t written = PipeWrite(&io, container_str);
3342 if (written < 0)
3343 {
3344 Log(LOG_LEVEL_ERR, "Failed to write to pipe (fd = %d): %s",
3345 io.write_fd, GetErrorStr());
3346 free(container_str);
3347
3348 container_str = NULL;
3349 }
3350 else if ((size_t) written != strlen(container_str))
3351 {
3352 Log(LOG_LEVEL_VERBOSE, "Couldn't send whole container data to '%s'.", cmd);
3353 free(container_str);
3354
3355 container_str = NULL;
3356 }
3357
3358 Rlist *returnlist = NULL;
3359 if (container_str)
3360 {
3361 free(container_str);
3362 /* We can have some error message here. */
3363 returnlist = PipeReadData(&io, 5, 5);
3364 }
3365
3366 /* If script returns non 0 status */
3367 int close = cf_pclose_full_duplex(&io);
3368 if (close != EXIT_SUCCESS)
3369 {
3370 Log(LOG_LEVEL_VERBOSE,
3371 "%s returned with non zero return code: %d",
3372 cmd, close);
3373 }
3374
3375 // Exit if no data was obtained from the pipe
3376 if (returnlist == NULL)
3377 {
3378 return NULL;
3379 }
3380
3381 JsonElement *returnjq = JsonArrayCreate(5);
3382
3383 Buffer *buf = BufferNew();
3384 for (const Rlist *rp = returnlist; rp != NULL; rp = rp->next)
3385 {
3386 const char *data = RlistScalarValue(rp);
3387
3388 if (BufferSize(buf) != 0)
3389 {
3390 // simulate the newline
3391 BufferAppendString(buf, "\n");
3392 }
3393
3394 BufferAppendString(buf, data);
3395 const char *bufdata = BufferData(buf);
3396 JsonElement *parsed = NULL;
3397 if (JsonParse(&bufdata, &parsed) == JSON_PARSE_OK)
3398 {
3399 JsonArrayAppendElement(returnjq, parsed);
3400 BufferClear(buf);
3401 }
3402 else
3403 {
3404 Log(LOG_LEVEL_DEBUG, "'%s' generated invalid JSON '%s', appending next line", cmd, data);
3405 }
3406 }
3407
3408 BufferDestroy(buf);
3409
3410 RlistDestroy(returnlist);
3411
3412 return returnjq;
3413 }
3414
3415 /*********************************************************************/
3416
FnCallMapData(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,ARG_UNUSED const Rlist * finalargs)3417 static FnCallResult FnCallMapData(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, ARG_UNUSED const Rlist *finalargs)
3418 {
3419 if (!fp->caller)
3420 {
3421 Log(LOG_LEVEL_ERR, "Function '%s' must be called from a promise", fp->name);
3422 return FnFailure();
3423 }
3424
3425 bool mapdatamode = (strcmp(fp->name, "mapdata") == 0);
3426 Rlist *returnlist = NULL;
3427
3428 // This is a delayed evaluation function, so we have to resolve arguments ourselves
3429 // We resolve them once now, to get the second or third argument with the iteration data
3430 Rlist *expargs = NewExpArgs(ctx, policy, fp, NULL);
3431
3432 Rlist *varpointer = NULL;
3433 const char* conversion = NULL;
3434
3435 if (mapdatamode)
3436 {
3437 if (expargs == NULL || RlistIsUnresolved(expargs->next->next))
3438 {
3439 RlistDestroy(expargs);
3440 return FnFailure();
3441 }
3442
3443 conversion = RlistScalarValue(expargs);
3444 varpointer = expargs->next->next;
3445 }
3446 else
3447 {
3448 if (expargs == NULL || RlistIsUnresolved(expargs->next))
3449 {
3450 RlistDestroy(expargs);
3451 return FnFailure();
3452 }
3453
3454 conversion = "none";
3455 varpointer = expargs->next;
3456 }
3457
3458 const char* varname = RlistScalarValueSafe(varpointer);
3459
3460 bool jsonmode = (strcmp(conversion, "json") == 0);
3461 bool canonifymode = (strcmp(conversion, "canonify") == 0);
3462 bool json_pipemode = (strcmp(conversion, "json_pipe") == 0);
3463
3464 bool allocated = false;
3465 JsonElement *container = VarNameOrInlineToJson(ctx, fp, varpointer, false, &allocated);
3466
3467 if (container == NULL)
3468 {
3469 RlistDestroy(expargs);
3470 return FnFailure();
3471 }
3472
3473 if (JsonGetElementType(container) != JSON_ELEMENT_TYPE_CONTAINER)
3474 {
3475 Log(LOG_LEVEL_ERR, "Function '%s' got an unexpected non-container from argument '%s'", fp->name, varname);
3476 JsonDestroyMaybe(container, allocated);
3477
3478 RlistDestroy(expargs);
3479 return FnFailure();
3480 }
3481
3482 if (mapdatamode && json_pipemode)
3483 {
3484 JsonElement *returnjson_pipe = ExecJSON_Pipe(RlistScalarValue(expargs->next), container);
3485
3486 RlistDestroy(expargs);
3487
3488 if (returnjson_pipe == NULL)
3489 {
3490 Log(LOG_LEVEL_ERR, "Function %s failed to get output from 'json_pipe' execution", fp->name);
3491 return FnFailure();
3492 }
3493
3494 JsonDestroyMaybe(container, allocated);
3495
3496 return FnReturnContainerNoCopy(returnjson_pipe);
3497 }
3498
3499 Buffer *expbuf = BufferNew();
3500 if (JsonGetContainerType(container) != JSON_CONTAINER_TYPE_OBJECT)
3501 {
3502 JsonElement *temp = JsonObjectCreate(0);
3503 JsonElement *temp2 = JsonMerge(temp, container);
3504 JsonDestroy(temp);
3505 JsonDestroyMaybe(container, allocated);
3506
3507 container = temp2;
3508 allocated = true;
3509 }
3510
3511 JsonIterator iter = JsonIteratorInit(container);
3512 const JsonElement *e;
3513
3514 while ((e = JsonIteratorNextValue(&iter)) != NULL)
3515 {
3516 EvalContextVariablePutSpecialEscaped(ctx, SPECIAL_SCOPE_THIS, "k", JsonGetPropertyAsString(e),
3517 CF_DATA_TYPE_STRING, "source=function,function=maparray",
3518 jsonmode);
3519
3520 switch (JsonGetElementType(e))
3521 {
3522 case JSON_ELEMENT_TYPE_PRIMITIVE:
3523 BufferClear(expbuf);
3524 EvalContextVariablePutSpecialEscaped(ctx, SPECIAL_SCOPE_THIS, "v", JsonPrimitiveGetAsString(e),
3525 CF_DATA_TYPE_STRING, "source=function,function=maparray",
3526 jsonmode);
3527
3528 // This is a delayed evaluation function, so we have to resolve arguments ourselves
3529 // We resolve them every time now, to get the arg_map argument
3530 Rlist *local_expargs = NewExpArgs(ctx, policy, fp, NULL);
3531 const char *arg_map = RlistScalarValueSafe(mapdatamode ? local_expargs->next : local_expargs);
3532 ExpandScalar(ctx, PromiseGetBundle(fp->caller)->ns, PromiseGetBundle(fp->caller)->name, arg_map, expbuf);
3533 RlistDestroy(local_expargs);
3534
3535 if (strstr(BufferData(expbuf), "$(this.k)") || strstr(BufferData(expbuf), "${this.k}") ||
3536 strstr(BufferData(expbuf), "$(this.v)") || strstr(BufferData(expbuf), "${this.v}"))
3537 {
3538 RlistDestroy(returnlist);
3539 EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_THIS, "k");
3540 EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_THIS, "v");
3541 BufferDestroy(expbuf);
3542 JsonDestroyMaybe(container, allocated);
3543 RlistDestroy(expargs);
3544 return FnFailure();
3545 }
3546
3547 if (canonifymode)
3548 {
3549 BufferCanonify(expbuf);
3550 }
3551
3552 RlistAppendScalar(&returnlist, BufferData(expbuf));
3553 EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_THIS, "v");
3554
3555 break;
3556
3557 case JSON_ELEMENT_TYPE_CONTAINER:
3558 {
3559 const JsonElement *e2;
3560 JsonIterator iter2 = JsonIteratorInit(e);
3561 int position = 0;
3562 while ((e2 = JsonIteratorNextValueByType(&iter2, JSON_ELEMENT_TYPE_PRIMITIVE, true)) != NULL)
3563 {
3564 char *key = (char*) JsonGetPropertyAsString(e2);
3565 bool havekey = (key != NULL);
3566 if (havekey)
3567 {
3568 EvalContextVariablePutSpecialEscaped(ctx, SPECIAL_SCOPE_THIS, "k[1]", key,
3569 CF_DATA_TYPE_STRING, "source=function,function=maparray",
3570 jsonmode);
3571 }
3572
3573 BufferClear(expbuf);
3574
3575 EvalContextVariablePutSpecialEscaped(ctx, SPECIAL_SCOPE_THIS, "v", JsonPrimitiveGetAsString(e2),
3576 CF_DATA_TYPE_STRING, "source=function,function=maparray",
3577 jsonmode);
3578
3579 // This is a delayed evaluation function, so we have to resolve arguments ourselves
3580 // We resolve them every time now, to get the arg_map argument
3581 Rlist *local_expargs = NewExpArgs(ctx, policy, fp, NULL);
3582 const char *arg_map = RlistScalarValueSafe(mapdatamode ? local_expargs->next : local_expargs);
3583 ExpandScalar(ctx, PromiseGetBundle(fp->caller)->ns, PromiseGetBundle(fp->caller)->name, arg_map, expbuf);
3584 RlistDestroy(local_expargs);
3585
3586 if (strstr(BufferData(expbuf), "$(this.k)") || strstr(BufferData(expbuf), "${this.k}") ||
3587 (havekey && (strstr(BufferData(expbuf), "$(this.k[1])") || strstr(BufferData(expbuf), "${this.k[1]}"))) ||
3588 strstr(BufferData(expbuf), "$(this.v)") || strstr(BufferData(expbuf), "${this.v}"))
3589 {
3590 RlistDestroy(returnlist);
3591 EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_THIS, "k");
3592 if (havekey)
3593 {
3594 EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_THIS, "k[1]");
3595 }
3596 EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_THIS, "v");
3597 BufferDestroy(expbuf);
3598 JsonDestroyMaybe(container, allocated);
3599 RlistDestroy(expargs);
3600 return FnFailure();
3601 }
3602
3603 if (canonifymode)
3604 {
3605 BufferCanonify(expbuf);
3606 }
3607
3608 RlistAppendScalarIdemp(&returnlist, BufferData(expbuf));
3609 if (havekey)
3610 {
3611 EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_THIS, "k[1]");
3612 }
3613 EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_THIS, "v");
3614 position++;
3615 }
3616 }
3617 break;
3618
3619 default:
3620 break;
3621 }
3622 EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_THIS, "k");
3623 }
3624
3625 BufferDestroy(expbuf);
3626 JsonDestroyMaybe(container, allocated);
3627 RlistDestroy(expargs);
3628
3629 JsonElement *returnjson = NULL;
3630
3631 // this is mapdata()
3632 if (mapdatamode)
3633 {
3634 returnjson = JsonArrayCreate(RlistLen(returnlist));
3635 for (const Rlist *rp = returnlist; rp != NULL; rp = rp->next)
3636 {
3637 const char *data = RlistScalarValue(rp);
3638 if (jsonmode)
3639 {
3640 JsonElement *parsed = NULL;
3641 if (JsonParseWithLookup(ctx, &LookupVarRefToJson, &data, &parsed) == JSON_PARSE_OK)
3642 {
3643 JsonArrayAppendElement(returnjson, parsed);
3644 }
3645 else
3646 {
3647 Log(LOG_LEVEL_VERBOSE, "Function '%s' could not parse dynamic JSON '%s', skipping it", fp->name, data);
3648 }
3649 }
3650 else
3651 {
3652 JsonArrayAppendString(returnjson, data);
3653 }
3654 }
3655
3656 RlistDestroy(returnlist);
3657 return FnReturnContainerNoCopy(returnjson);
3658 }
3659
3660 // this is maparray()
3661 return (FnCallResult) { FNCALL_SUCCESS, { returnlist, RVAL_TYPE_LIST } };
3662 }
3663
3664 /*********************************************************************/
3665
FnCallMapList(EvalContext * ctx,const Policy * policy,const FnCall * fp,ARG_UNUSED const Rlist * finalargs)3666 static FnCallResult FnCallMapList(EvalContext *ctx,
3667 const Policy *policy,
3668 const FnCall *fp,
3669 ARG_UNUSED const Rlist *finalargs)
3670 {
3671 // This is a delayed evaluation function, so we have to resolve arguments ourselves
3672 // We resolve them once now, to get the second argument
3673 Rlist *expargs = NewExpArgs(ctx, policy, fp, NULL);
3674
3675 if (expargs == NULL || RlistIsUnresolved(expargs->next))
3676 {
3677 RlistDestroy(expargs);
3678 return FnFailure();
3679 }
3680
3681 const char *name_str = RlistScalarValueSafe(expargs->next);
3682
3683 // try to load directly
3684 bool allocated = false;
3685 JsonElement *json = VarNameOrInlineToJson(ctx, fp, expargs->next, false, &allocated);
3686
3687 // we failed to produce a valid JsonElement, so give up
3688 if (json == NULL)
3689 {
3690 RlistDestroy(expargs);
3691 return FnFailure();
3692 }
3693 else if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER)
3694 {
3695 Log(LOG_LEVEL_VERBOSE, "Function '%s', argument '%s' was not a data container or list",
3696 fp->name, name_str);
3697 JsonDestroyMaybe(json, allocated);
3698 RlistDestroy(expargs);
3699 return FnFailure();
3700 }
3701
3702 Rlist *newlist = NULL;
3703 Buffer *expbuf = BufferNew();
3704 JsonIterator iter = JsonIteratorInit(json);
3705 const JsonElement *e;
3706 while ((e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true)))
3707 {
3708 const char* value = JsonPrimitiveGetAsString(e);
3709
3710 BufferClear(expbuf);
3711 EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_THIS, "this", value, CF_DATA_TYPE_STRING, "source=function,function=maplist");
3712
3713 // This is a delayed evaluation function, so we have to resolve arguments ourselves
3714 // We resolve them every time now, to get the first argument
3715 Rlist *local_expargs = NewExpArgs(ctx, policy, fp, NULL);
3716 const char *arg_map = RlistScalarValueSafe(local_expargs);
3717 ExpandScalar(ctx, NULL, "this", arg_map, expbuf);
3718 RlistDestroy(local_expargs);
3719
3720 if (strstr(BufferData(expbuf), "$(this)") || strstr(BufferData(expbuf), "${this}"))
3721 {
3722 RlistDestroy(newlist);
3723 EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_THIS, "this");
3724 BufferDestroy(expbuf);
3725 JsonDestroyMaybe(json, allocated);
3726 RlistDestroy(expargs);
3727 return FnFailure();
3728 }
3729
3730 RlistAppendScalar(&newlist, BufferData(expbuf));
3731 EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_THIS, "this");
3732 }
3733 BufferDestroy(expbuf);
3734 JsonDestroyMaybe(json, allocated);
3735 RlistDestroy(expargs);
3736
3737 return (FnCallResult) { FNCALL_SUCCESS, { newlist, RVAL_TYPE_LIST } };
3738 }
3739
3740 /******************************************************************************/
3741
FnCallExpandRange(EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)3742 static FnCallResult FnCallExpandRange(EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
3743 {
3744 Rlist *newlist = NULL;
3745 const char *template = RlistScalarValue(finalargs);
3746 char *step = RlistScalarValue(finalargs->next);
3747 size_t template_size = strlen(template) + 1;
3748 char *before = xstrdup(template);
3749 char *after = xcalloc(template_size, 1);
3750 char *work = xstrdup(template);
3751 int from = CF_NOINT, to = CF_NOINT, step_size = atoi(step);
3752
3753 if (*template == '[')
3754 {
3755 *before = '\0';
3756 sscanf(template, "[%d-%d]%[^\n]", &from, &to, after);
3757 }
3758 else
3759 {
3760 sscanf(template, "%[^[\[][%d-%d]%[^\n]", before, &from, &to, after);
3761 }
3762
3763 if (step_size < 1 || abs(from-to) < step_size)
3764 {
3765 FatalError(ctx, "EXPANDRANGE Step size cannot be less than 1 or greater than the interval");
3766 }
3767
3768 if (from == CF_NOINT || to == CF_NOINT)
3769 {
3770 FatalError(ctx, "EXPANDRANGE malformed range expression");
3771 }
3772
3773 if (from > to)
3774 {
3775 for (int i = from; i >= to; i -= step_size)
3776 {
3777 xsnprintf(work, template_size, "%s%d%s",before,i,after);;
3778 RlistAppendScalar(&newlist, work);
3779 }
3780 }
3781 else
3782 {
3783 for (int i = from; i <= to; i += step_size)
3784 {
3785 xsnprintf(work, template_size, "%s%d%s",before,i,after);;
3786 RlistAppendScalar(&newlist, work);
3787 }
3788 }
3789
3790 free(before);
3791 free(after);
3792 free(work);
3793
3794 return (FnCallResult) { FNCALL_SUCCESS, { newlist, RVAL_TYPE_LIST } };
3795 }
3796
3797 /*****************************************************************************/
3798
FnCallMergeData(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * args)3799 static FnCallResult FnCallMergeData(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *args)
3800 {
3801 if (RlistLen(args) == 0)
3802 {
3803 Log(LOG_LEVEL_ERR, "%s needs at least one argument, a reference to a container variable", fp->name);
3804 return FnFailure();
3805 }
3806
3807 for (const Rlist *arg = args; arg; arg = arg->next)
3808 {
3809 if (args->val.type != RVAL_TYPE_SCALAR &&
3810 args->val.type != RVAL_TYPE_CONTAINER)
3811 {
3812 Log(LOG_LEVEL_ERR, "%s: argument is not a variable reference", fp->name);
3813 return FnFailure();
3814 }
3815 }
3816
3817 Seq *containers = SeqNew(10, &JsonDestroy);
3818
3819 for (const Rlist *arg = args; arg; arg = arg->next)
3820 {
3821 // try to load directly
3822 bool allocated = false;
3823 JsonElement *json = VarNameOrInlineToJson(ctx, fp, arg, false, &allocated);
3824
3825 // we failed to produce a valid JsonElement, so give up
3826 if (json == NULL)
3827 {
3828 SeqDestroy(containers);
3829
3830 return FnFailure();
3831 }
3832
3833 // Fail on json primitives, only merge containers
3834 if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER)
3835 {
3836 if (allocated)
3837 {
3838 JsonDestroy(json);
3839 }
3840 char *const as_string = RvalToString(arg->val);
3841 Log(LOG_LEVEL_ERR, "%s is not mergeable as it it not a container", as_string);
3842 free(as_string);
3843 SeqDestroy(containers);
3844 return FnFailure();
3845 }
3846
3847 // This can be optimized better
3848 if (allocated)
3849 {
3850 SeqAppend(containers, json);
3851 }
3852 else
3853 {
3854 SeqAppend(containers, JsonCopy(json));
3855 }
3856
3857 } // end of args loop
3858
3859 if (SeqLength(containers) == 1)
3860 {
3861 JsonElement *first = JsonCopy(SeqAt(containers, 0));
3862 SeqDestroy(containers);
3863 return FnReturnContainerNoCopy(first);
3864 }
3865 else
3866 {
3867 JsonElement *first = SeqAt(containers, 0);
3868 JsonElement *second = SeqAt(containers, 1);
3869 JsonElement *result = JsonMerge(first, second);
3870
3871 for (size_t i = 2; i < SeqLength(containers); i++)
3872 {
3873 JsonElement *cur = SeqAt(containers, i);
3874 JsonElement *tmp = JsonMerge(result, cur);
3875 JsonDestroy(result);
3876 result = tmp;
3877 }
3878
3879 SeqDestroy(containers);
3880 return FnReturnContainerNoCopy(result);
3881 }
3882
3883 assert(false);
3884 }
3885
DefaultTemplateData(const EvalContext * ctx,const char * wantbundle)3886 JsonElement *DefaultTemplateData(const EvalContext *ctx, const char *wantbundle)
3887 {
3888 JsonElement *hash = JsonObjectCreate(30);
3889 JsonElement *classes = NULL;
3890 JsonElement *bundles = NULL;
3891
3892 bool want_all_bundles = (wantbundle == NULL);
3893
3894 if (want_all_bundles) // no specific bundle
3895 {
3896 classes = JsonObjectCreate(50);
3897 bundles = JsonObjectCreate(50);
3898 JsonObjectAppendObject(hash, "classes", classes);
3899 JsonObjectAppendObject(hash, "vars", bundles);
3900
3901 ClassTableIterator *it = EvalContextClassTableIteratorNewGlobal(ctx, NULL, true, true);
3902 Class *cls;
3903 while ((cls = ClassTableIteratorNext(it)))
3904 {
3905 char *key = ClassRefToString(cls->ns, cls->name);
3906 JsonObjectAppendBool(classes, key, true);
3907 free(key);
3908 }
3909 ClassTableIteratorDestroy(it);
3910
3911 it = EvalContextClassTableIteratorNewLocal(ctx);
3912 while ((cls = ClassTableIteratorNext(it)))
3913 {
3914 char *key = ClassRefToString(cls->ns, cls->name);
3915 JsonObjectAppendBool(classes, key, true);
3916 free(key);
3917 }
3918 ClassTableIteratorDestroy(it);
3919 }
3920
3921 {
3922 VariableTableIterator *it = EvalContextVariableTableIteratorNew(ctx, NULL, NULL, NULL);
3923 Variable *var;
3924 while ((var = VariableTableIteratorNext(it)))
3925 {
3926 const VarRef *var_ref = VariableGetRef(var);
3927 // TODO: need to get a CallRef, this is bad
3928 char *scope_key = ClassRefToString(var_ref->ns, var_ref->scope);
3929
3930 JsonElement *scope_obj = NULL;
3931 if (want_all_bundles)
3932 {
3933 scope_obj = JsonObjectGetAsObject(bundles, scope_key);
3934 if (!scope_obj)
3935 {
3936 scope_obj = JsonObjectCreate(50);
3937 JsonObjectAppendObject(bundles, scope_key, scope_obj);
3938 }
3939 }
3940 else if (StringEqual(scope_key, wantbundle))
3941 {
3942 scope_obj = hash;
3943 }
3944
3945 free(scope_key);
3946
3947 if (scope_obj != NULL)
3948 {
3949 char *lval_key = VarRefToString(var_ref, false);
3950 Rval var_rval = VariableGetRval(var, true);
3951 // don't collect mangled refs
3952 if (strchr(lval_key, CF_MANGLED_SCOPE) == NULL)
3953 {
3954 JsonObjectAppendElement(scope_obj, lval_key, RvalToJson(var_rval));
3955 }
3956 free(lval_key);
3957 }
3958 }
3959 VariableTableIteratorDestroy(it);
3960 }
3961
3962 Writer *w = StringWriter();
3963 JsonWrite(w, hash, 0);
3964 Log(LOG_LEVEL_DEBUG, "Generated DefaultTemplateData '%s'", StringWriterData(w));
3965 WriterClose(w);
3966
3967 return hash;
3968 }
3969
FnCallDatastate(EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,ARG_UNUSED const Rlist * args)3970 static FnCallResult FnCallDatastate(EvalContext *ctx,
3971 ARG_UNUSED const Policy *policy,
3972 ARG_UNUSED const FnCall *fp,
3973 ARG_UNUSED const Rlist *args)
3974 {
3975 JsonElement *state = DefaultTemplateData(ctx, NULL);
3976 return FnReturnContainerNoCopy(state);
3977 }
3978
FnCallBundlestate(EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,ARG_UNUSED const Rlist * args)3979 static FnCallResult FnCallBundlestate(EvalContext *ctx,
3980 ARG_UNUSED const Policy *policy,
3981 ARG_UNUSED const FnCall *fp,
3982 ARG_UNUSED const Rlist *args)
3983 {
3984 JsonElement *state = DefaultTemplateData(ctx, RlistScalarValue(args));
3985
3986 if (state == NULL ||
3987 JsonGetElementType(state) != JSON_ELEMENT_TYPE_CONTAINER ||
3988 JsonLength(state) < 1)
3989 {
3990 if (state != NULL)
3991 {
3992 JsonDestroy(state);
3993 }
3994
3995 return FnFailure();
3996 }
3997 else
3998 {
3999 return FnReturnContainerNoCopy(state);
4000 }
4001 }
4002
4003
FnCallSelectServers(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)4004 static FnCallResult FnCallSelectServers(EvalContext *ctx,
4005 ARG_UNUSED const Policy *policy,
4006 const FnCall *fp,
4007 const Rlist *finalargs)
4008 {
4009 const char *listvar = RlistScalarValue(finalargs);
4010 const char *port = RlistScalarValue(finalargs->next);
4011 const char *sendstring = RlistScalarValue(finalargs->next->next);
4012 const char *regex = RlistScalarValue(finalargs->next->next->next);
4013 ssize_t maxbytes = IntFromString(RlistScalarValue(finalargs->next->next->next->next));
4014 char *array_lval = xstrdup(RlistScalarValue(finalargs->next->next->next->next->next));
4015
4016 if (!IsQualifiedVariable(array_lval))
4017 {
4018 if (fp->caller)
4019 {
4020 VarRef *ref = VarRefParseFromBundle(array_lval, PromiseGetBundle(fp->caller));
4021 free(array_lval);
4022 array_lval = VarRefToString(ref, true);
4023 VarRefDestroy(ref);
4024 }
4025 else
4026 {
4027 Log(LOG_LEVEL_ERR, "Function '%s' called with an unqualifed array reference '%s', "
4028 "and the reference could not be automatically qualified as the function was not called from a promise.",
4029 fp->name, array_lval);
4030 free(array_lval);
4031 return FnFailure();
4032 }
4033 }
4034
4035 char naked[CF_MAXVARSIZE] = "";
4036
4037 if (IsVarList(listvar))
4038 {
4039 GetNaked(naked, listvar);
4040 }
4041 else
4042 {
4043 Log(LOG_LEVEL_VERBOSE,
4044 "Function selectservers was promised a list called '%s' but this was not found",
4045 listvar);
4046 return FnFailure();
4047 }
4048
4049 VarRef *ref = VarRefParse(naked);
4050 DataType value_type;
4051 const Rlist *hostnameip = EvalContextVariableGet(ctx, ref, &value_type);
4052 if (value_type == CF_DATA_TYPE_NONE)
4053 {
4054 Log(LOG_LEVEL_VERBOSE,
4055 "Function selectservers was promised a list called '%s' but this was not found from context '%s.%s'",
4056 listvar, ref->scope, naked);
4057 VarRefDestroy(ref);
4058 free(array_lval);
4059 return FnFailure();
4060 }
4061 VarRefDestroy(ref);
4062
4063 if (DataTypeToRvalType(value_type) != RVAL_TYPE_LIST)
4064 {
4065 Log(LOG_LEVEL_VERBOSE,
4066 "Function selectservers was promised a list called '%s' but this variable is not a list",
4067 listvar);
4068 free(array_lval);
4069 return FnFailure();
4070 }
4071
4072 if (maxbytes < 0 || maxbytes > CF_BUFSIZE - 1)
4073 {
4074 Log(LOG_LEVEL_VERBOSE,
4075 "selectservers: invalid number of bytes %zd to read, defaulting to %d",
4076 maxbytes, CF_BUFSIZE - 1);
4077 maxbytes = CF_BUFSIZE - 1;
4078 }
4079
4080 if (THIS_AGENT_TYPE != AGENT_TYPE_AGENT)
4081 {
4082 free(array_lval);
4083 return FnReturnF("%d", 0);
4084 }
4085
4086 Policy *select_server_policy = PolicyNew();
4087 {
4088 Bundle *bp = PolicyAppendBundle(select_server_policy, NamespaceDefault(),
4089 "select_server_bundle", "agent", NULL, NULL);
4090 BundleSection *sp = BundleAppendSection(bp, "select_server");
4091
4092 BundleSectionAppendPromise(sp, "function", (Rval) { NULL, RVAL_TYPE_NOPROMISEE }, NULL, NULL);
4093 }
4094
4095 size_t count = 0;
4096 for (const Rlist *rp = hostnameip; rp != NULL; rp = rp->next)
4097 {
4098 const char *host = RlistScalarValue(rp);
4099 Log(LOG_LEVEL_DEBUG, "Want to read %zd bytes from %s port %s",
4100 maxbytes, host, port);
4101
4102 char txtaddr[CF_MAX_IP_LEN] = "";
4103 int sd = SocketConnect(host, port, CONNTIMEOUT, false,
4104 txtaddr, sizeof(txtaddr));
4105 if (sd == -1)
4106 {
4107 continue;
4108 }
4109
4110 if (strlen(sendstring) > 0)
4111 {
4112 if (SendSocketStream(sd, sendstring, strlen(sendstring)) != -1)
4113 {
4114 char recvbuf[CF_BUFSIZE];
4115 ssize_t n_read = recv(sd, recvbuf, maxbytes, 0);
4116
4117 if (n_read >= 0)
4118 {
4119 /* maxbytes was checked earlier, but just make sure... */
4120 assert((size_t) n_read < sizeof(recvbuf));
4121 recvbuf[n_read] = '\0';
4122
4123 if (strlen(regex) == 0 || StringMatchFull(regex, recvbuf))
4124 {
4125 Log(LOG_LEVEL_VERBOSE,
4126 "selectservers: Got matching reply from host %s address %s",
4127 host, txtaddr);
4128
4129 char buffer[CF_MAXVARSIZE] = "";
4130 snprintf(buffer, sizeof(buffer), "%s[%zu]", array_lval, count);
4131 VarRef *ref = VarRefParse(buffer);
4132 EvalContextVariablePut(ctx, ref, host, CF_DATA_TYPE_STRING,
4133 "source=function,function=selectservers");
4134 VarRefDestroy(ref);
4135
4136 count++;
4137 }
4138 }
4139 }
4140 }
4141 else /* If query is empty, all hosts are added */
4142 {
4143 Log(LOG_LEVEL_VERBOSE,
4144 "selectservers: Got reply from host %s address %s",
4145 host, txtaddr);
4146
4147 char buffer[CF_MAXVARSIZE] = "";
4148 snprintf(buffer, sizeof(buffer), "%s[%zu]", array_lval, count);
4149 VarRef *ref = VarRefParse(buffer);
4150 EvalContextVariablePut(ctx, ref, host, CF_DATA_TYPE_STRING,
4151 "source=function,function=selectservers");
4152 VarRefDestroy(ref);
4153
4154 count++;
4155 }
4156
4157 cf_closesocket(sd);
4158 }
4159
4160 PolicyDestroy(select_server_policy);
4161 free(array_lval);
4162
4163 Log(LOG_LEVEL_VERBOSE, "selectservers: found %zu servers", count);
4164 return FnReturnF("%zu", count);
4165 }
4166
4167
FnCallShuffle(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)4168 static FnCallResult FnCallShuffle(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
4169 {
4170 const char *seed_str = RlistScalarValue(finalargs->next);
4171
4172 const char *name_str = RlistScalarValueSafe(finalargs);
4173
4174 // try to load directly
4175 bool allocated = false;
4176 JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs, false, &allocated);
4177
4178 // we failed to produce a valid JsonElement, so give up
4179 if (json == NULL)
4180 {
4181 return FnFailure();
4182 }
4183 else if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER)
4184 {
4185 Log(LOG_LEVEL_VERBOSE, "Function '%s', argument '%s' was not a data container or list",
4186 fp->name, name_str);
4187 JsonDestroyMaybe(json, allocated);
4188 return FnFailure();
4189 }
4190
4191 Seq *seq = SeqNew(100, NULL);
4192 JsonIterator iter = JsonIteratorInit(json);
4193 const JsonElement *e;
4194 while ((e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true)))
4195 {
4196 SeqAppend(seq, (void*)JsonPrimitiveGetAsString(e));
4197 }
4198
4199 SeqShuffle(seq, StringHash(seed_str, 0));
4200
4201 Rlist *shuffled = NULL;
4202 for (size_t i = 0; i < SeqLength(seq); i++)
4203 {
4204 RlistPrepend(&shuffled, SeqAt(seq, i), RVAL_TYPE_SCALAR);
4205 }
4206
4207 SeqDestroy(seq);
4208 JsonDestroyMaybe(json, allocated);
4209 return (FnCallResult) { FNCALL_SUCCESS, (Rval) { shuffled, RVAL_TYPE_LIST } };
4210 }
4211
FnCallInt(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)4212 static FnCallResult FnCallInt(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
4213 {
4214 assert(finalargs != NULL);
4215
4216 char *str = RlistScalarValueSafe(finalargs);
4217
4218 double val;
4219 bool ok = DoubleFromString(str, &val);
4220 if (!ok)
4221 {
4222 // Log from DoubleFromString
4223 return FnFailure();
4224 }
4225
4226 return FnReturnF("%jd", (intmax_t) val); // Discard decimals
4227 }
4228
FnCallIsNewerThan(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)4229 static FnCallResult FnCallIsNewerThan(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
4230 {
4231 struct stat frombuf, tobuf;
4232
4233 if (stat(RlistScalarValue(finalargs), &frombuf) == -1 ||
4234 stat(RlistScalarValue(finalargs->next), &tobuf) == -1)
4235 {
4236 return FnFailure();
4237 }
4238
4239 return FnReturnContext(frombuf.st_mtime > tobuf.st_mtime);
4240 }
4241
4242 /*********************************************************************/
4243
FnCallIsAccessedBefore(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)4244 static FnCallResult FnCallIsAccessedBefore(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
4245 {
4246 struct stat frombuf, tobuf;
4247
4248 if (stat(RlistScalarValue(finalargs), &frombuf) == -1 ||
4249 stat(RlistScalarValue(finalargs->next), &tobuf) == -1)
4250 {
4251 return FnFailure();
4252 }
4253
4254 return FnReturnContext(frombuf.st_atime < tobuf.st_atime);
4255 }
4256
4257 /*********************************************************************/
4258
FnCallIsChangedBefore(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)4259 static FnCallResult FnCallIsChangedBefore(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
4260 {
4261 struct stat frombuf, tobuf;
4262
4263 if (stat(RlistScalarValue(finalargs), &frombuf) == -1 ||
4264 stat(RlistScalarValue(finalargs->next), &tobuf) == -1)
4265 {
4266 return FnFailure();
4267 }
4268
4269 return FnReturnContext(frombuf.st_ctime > tobuf.st_ctime);
4270 }
4271
4272 /*********************************************************************/
4273
FnCallFileStat(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)4274 static FnCallResult FnCallFileStat(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
4275 {
4276 char *path = RlistScalarValue(finalargs);
4277 struct stat statbuf;
4278
4279 if (lstat(path, &statbuf) == -1)
4280 {
4281 if (StringEqual(fp->name, "filesize"))
4282 {
4283 return FnFailure();
4284 }
4285 return FnReturnContext(false);
4286 }
4287
4288 if (!strcmp(fp->name, "isexecutable"))
4289 {
4290 if (S_ISLNK(statbuf.st_mode) && stat(path, &statbuf) == -1)
4291 {
4292 // stat on link target failed - probably broken link
4293 return FnReturnContext(false);
4294 }
4295 return FnReturnContext(IsExecutable(path));
4296 }
4297 if (!strcmp(fp->name, "isdir"))
4298 {
4299 return FnReturnContext(S_ISDIR(statbuf.st_mode));
4300 }
4301 if (!strcmp(fp->name, "islink"))
4302 {
4303 return FnReturnContext(S_ISLNK(statbuf.st_mode));
4304 }
4305 if (!strcmp(fp->name, "isplain"))
4306 {
4307 return FnReturnContext(S_ISREG(statbuf.st_mode));
4308 }
4309 if (!strcmp(fp->name, "fileexists"))
4310 {
4311 return FnReturnContext(true);
4312 }
4313 if (!strcmp(fp->name, "filesize"))
4314 {
4315 return FnReturnF("%ju", (uintmax_t) statbuf.st_size);
4316 }
4317
4318 ProgrammingError("Unexpected function name in FnCallFileStat: %s", fp->name);
4319 }
4320
4321 /*********************************************************************/
4322
FnCallFileStatDetails(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)4323 static FnCallResult FnCallFileStatDetails(ARG_UNUSED EvalContext *ctx,
4324 ARG_UNUSED const Policy *policy,
4325 const FnCall *fp,
4326 const Rlist *finalargs)
4327 {
4328 char buffer[CF_BUFSIZE];
4329 const char *path = RlistScalarValue(finalargs);
4330 char *detail = RlistScalarValue(finalargs->next);
4331 struct stat statbuf;
4332
4333 buffer[0] = '\0';
4334
4335 if (lstat(path, &statbuf) == -1)
4336 {
4337 return FnFailure();
4338 }
4339 else if (!strcmp(detail, "xattr"))
4340 {
4341 #if defined(WITH_XATTR)
4342 // Extended attributes include both POSIX ACLs and SELinux contexts.
4343 char attr_raw_names[CF_BUFSIZE];
4344 ssize_t attr_raw_names_size = llistxattr(path, attr_raw_names, sizeof(attr_raw_names));
4345
4346 if (attr_raw_names_size < 0)
4347 {
4348 if (errno != ENOTSUP && errno != ENODATA)
4349 {
4350 Log(LOG_LEVEL_ERR, "Can't read extended attributes of '%s'. (llistxattr: %s)",
4351 path, GetErrorStr());
4352 }
4353 }
4354 else
4355 {
4356 Buffer *printattr = BufferNew();
4357 for (int pos = 0; pos < attr_raw_names_size;)
4358 {
4359 const char *current = attr_raw_names + pos;
4360 pos += strlen(current) + 1;
4361
4362 if (!StringIsPrintable(current))
4363 {
4364 Log(LOG_LEVEL_INFO, "Skipping extended attribute of '%s', it has a non-printable name: '%s'",
4365 path, current);
4366 continue;
4367 }
4368
4369 char data[CF_BUFSIZE];
4370 int datasize = lgetxattr(path, current, data, sizeof(data));
4371 if (datasize < 0)
4372 {
4373 if (errno == ENOTSUP)
4374 {
4375 continue;
4376 }
4377 else
4378 {
4379 Log(LOG_LEVEL_ERR, "Can't read extended attribute '%s' of '%s'. (lgetxattr: %s)",
4380 path, current, GetErrorStr());
4381 }
4382 }
4383 else
4384 {
4385 if (!StringIsPrintable(data))
4386 {
4387 Log(LOG_LEVEL_INFO, "Skipping extended attribute of '%s', it has non-printable data: '%s=%s'",
4388 path, current, data);
4389 continue;
4390 }
4391
4392 BufferPrintf(printattr, "%s=%s", current, data);
4393
4394 // Append a newline for multiple attributes.
4395 if (attr_raw_names_size > 0)
4396 {
4397 BufferAppendChar(printattr, '\n');
4398 }
4399 }
4400 }
4401
4402 snprintf(buffer, CF_MAXVARSIZE, "%s", BufferData(printattr));
4403 BufferDestroy(printattr);
4404 }
4405 #else // !WITH_XATTR
4406 // do nothing, leave the buffer empty
4407 #endif
4408 }
4409 else if (!strcmp(detail, "size"))
4410 {
4411 snprintf(buffer, CF_MAXVARSIZE, "%ju", (uintmax_t) statbuf.st_size);
4412 }
4413 else if (!strcmp(detail, "gid"))
4414 {
4415 snprintf(buffer, CF_MAXVARSIZE, "%ju", (uintmax_t) statbuf.st_gid);
4416 }
4417 else if (!strcmp(detail, "uid"))
4418 {
4419 snprintf(buffer, CF_MAXVARSIZE, "%ju", (uintmax_t) statbuf.st_uid);
4420 }
4421 else if (!strcmp(detail, "ino"))
4422 {
4423 snprintf(buffer, CF_MAXVARSIZE, "%ju", (uintmax_t) statbuf.st_ino);
4424 }
4425 else if (!strcmp(detail, "nlink"))
4426 {
4427 snprintf(buffer, CF_MAXVARSIZE, "%ju", (uintmax_t) statbuf.st_nlink);
4428 }
4429 else if (!strcmp(detail, "ctime"))
4430 {
4431 snprintf(buffer, CF_MAXVARSIZE, "%jd", (intmax_t) statbuf.st_ctime);
4432 }
4433 else if (!strcmp(detail, "mtime"))
4434 {
4435 snprintf(buffer, CF_MAXVARSIZE, "%jd", (intmax_t) statbuf.st_mtime);
4436 }
4437 else if (!strcmp(detail, "atime"))
4438 {
4439 snprintf(buffer, CF_MAXVARSIZE, "%jd", (intmax_t) statbuf.st_atime);
4440 }
4441 else if (!strcmp(detail, "permstr"))
4442 {
4443 snprintf(buffer, CF_MAXVARSIZE,
4444 "%c%c%c%c%c%c%c%c%c%c",
4445 S_ISDIR(statbuf.st_mode) ? 'd' : '-',
4446 (statbuf.st_mode & S_IRUSR) ? 'r' : '-',
4447 (statbuf.st_mode & S_IWUSR) ? 'w' : '-',
4448 (statbuf.st_mode & S_IXUSR) ? 'x' : '-',
4449 (statbuf.st_mode & S_IRGRP) ? 'r' : '-',
4450 (statbuf.st_mode & S_IWGRP) ? 'w' : '-',
4451 (statbuf.st_mode & S_IXGRP) ? 'x' : '-',
4452 (statbuf.st_mode & S_IROTH) ? 'r' : '-',
4453 (statbuf.st_mode & S_IWOTH) ? 'w' : '-',
4454 (statbuf.st_mode & S_IXOTH) ? 'x' : '-');
4455 }
4456 else if (!strcmp(detail, "permoct"))
4457 {
4458 snprintf(buffer, CF_MAXVARSIZE, "%jo", (uintmax_t) (statbuf.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)));
4459 }
4460 else if (!strcmp(detail, "modeoct"))
4461 {
4462 snprintf(buffer, CF_MAXVARSIZE, "%jo", (uintmax_t) statbuf.st_mode);
4463 }
4464 else if (!strcmp(detail, "mode"))
4465 {
4466 snprintf(buffer, CF_MAXVARSIZE, "%ju", (uintmax_t) statbuf.st_mode);
4467 }
4468 else if (!strcmp(detail, "type"))
4469 {
4470 switch (statbuf.st_mode & S_IFMT)
4471 {
4472 case S_IFBLK: snprintf(buffer, CF_MAXVARSIZE, "%s", "block device"); break;
4473 case S_IFCHR: snprintf(buffer, CF_MAXVARSIZE, "%s", "character device"); break;
4474 case S_IFDIR: snprintf(buffer, CF_MAXVARSIZE, "%s", "directory"); break;
4475 case S_IFIFO: snprintf(buffer, CF_MAXVARSIZE, "%s", "FIFO/pipe"); break;
4476 case S_IFLNK: snprintf(buffer, CF_MAXVARSIZE, "%s", "symlink"); break;
4477 case S_IFREG: snprintf(buffer, CF_MAXVARSIZE, "%s", "regular file"); break;
4478 case S_IFSOCK: snprintf(buffer, CF_MAXVARSIZE, "%s", "socket"); break;
4479 default: snprintf(buffer, CF_MAXVARSIZE, "%s", "unknown"); break;
4480 }
4481 }
4482 else if (!strcmp(detail, "dev_minor"))
4483 {
4484 #if !defined(__MINGW32__)
4485 snprintf(buffer, CF_MAXVARSIZE, "%ju", (uintmax_t) minor(statbuf.st_dev) );
4486 #else
4487 snprintf(buffer, CF_MAXVARSIZE, "Not available on Windows");
4488 #endif
4489 }
4490 else if (!strcmp(detail, "dev_major"))
4491 {
4492 #if !defined(__MINGW32__)
4493 snprintf(buffer, CF_MAXVARSIZE, "%ju", (uintmax_t) major(statbuf.st_dev) );
4494 #else
4495 snprintf(buffer, CF_MAXVARSIZE, "Not available on Windows");
4496 #endif
4497 }
4498 else if (!strcmp(detail, "devno"))
4499 {
4500 #if !defined(__MINGW32__)
4501 snprintf(buffer, CF_MAXVARSIZE, "%ju", (uintmax_t) statbuf.st_dev );
4502 #else
4503 snprintf(buffer, CF_MAXVARSIZE, "%c:", statbuf.st_dev + 'A');
4504 #endif
4505 }
4506 else if (!strcmp(detail, "dirname"))
4507 {
4508 snprintf(buffer, CF_MAXVARSIZE, "%s", path);
4509 ChopLastNode(buffer);
4510 MapName(buffer);
4511 }
4512 else if (!strcmp(detail, "basename"))
4513 {
4514 snprintf(buffer, CF_MAXVARSIZE, "%s", ReadLastNode(path));
4515 }
4516 else if (!strcmp(detail, "linktarget") || !strcmp(detail, "linktarget_shallow"))
4517 {
4518 #if !defined(__MINGW32__)
4519 char path_buffer[CF_BUFSIZE];
4520 bool recurse = !strcmp(detail, "linktarget");
4521 int cycles = 0;
4522 int max_cycles = 30; // This allows for up to 31 levels of indirection.
4523
4524 strlcpy(path_buffer, path, CF_MAXVARSIZE);
4525
4526 // Iterate while we're looking at a link.
4527 while (S_ISLNK(statbuf.st_mode))
4528 {
4529 if (cycles > max_cycles)
4530 {
4531 Log(LOG_LEVEL_INFO,
4532 "%s bailing on link '%s' (original '%s') because %d cycles were chased",
4533 fp->name, path_buffer, path, cycles + 1);
4534 break;
4535 }
4536
4537 Log(LOG_LEVEL_VERBOSE, "%s cycle %d, resolving link: %s", fp->name, cycles+1, path_buffer);
4538
4539 /* Note we subtract 1 since we may need an extra char for '\0'. */
4540 ssize_t got = readlink(path_buffer, buffer, CF_BUFSIZE - 1);
4541 if (got < 0)
4542 {
4543 // An error happened. Empty the buffer (don't keep the last target).
4544 Log(LOG_LEVEL_ERR, "%s could not readlink '%s'", fp->name, path_buffer);
4545 path_buffer[0] = '\0';
4546 break;
4547 }
4548 buffer[got] = '\0'; /* readlink() doesn't terminate */
4549
4550 /* If it is a relative path, then in order to follow it further we
4551 * need to prepend the directory. */
4552 if (!IsAbsoluteFileName(buffer) &&
4553 strcmp(detail, "linktarget") == 0)
4554 {
4555 DeleteSlash(path_buffer);
4556 ChopLastNode(path_buffer);
4557 AddSlash(path_buffer);
4558 strlcat(path_buffer, buffer, sizeof(path_buffer));
4559 /* Use buffer again as a tmp buffer. */
4560 CompressPath(buffer, sizeof(buffer), path_buffer);
4561 }
4562
4563 // We got a good link target into buffer. Copy it to path_buffer.
4564 strlcpy(path_buffer, buffer, CF_MAXVARSIZE);
4565
4566 Log(LOG_LEVEL_VERBOSE, "Link resolved to: %s", path_buffer);
4567
4568 if (!recurse)
4569 {
4570 Log(LOG_LEVEL_VERBOSE,
4571 "%s bailing on link '%s' (original '%s') because linktarget_shallow was requested",
4572 fp->name, path_buffer, path);
4573 break;
4574 }
4575 else if (lstat(path_buffer, &statbuf) == -1)
4576 {
4577 Log(LOG_LEVEL_INFO,
4578 "%s bailing on link '%s' (original '%s') because it could not be read",
4579 fp->name, path_buffer, path);
4580 break;
4581 }
4582
4583 // At this point we haven't bailed, path_buffer has the link target
4584 cycles++;
4585 }
4586
4587 // Get the path_buffer back into buffer.
4588 strlcpy(buffer, path_buffer, CF_MAXVARSIZE);
4589 #else
4590 // Always return the original path on W32.
4591 strlcpy(buffer, path, CF_MAXVARSIZE);
4592 #endif
4593 }
4594
4595 return FnReturn(buffer);
4596 }
4597
4598 /*********************************************************************/
4599
FnCallFindfiles(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)4600 static FnCallResult FnCallFindfiles(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
4601 {
4602 Rlist *returnlist = NULL;
4603 char id[CF_BUFSIZE];
4604
4605 snprintf(id, CF_BUFSIZE, "built-in FnCall %s-arg", fp->name);
4606
4607 /* We need to check all the arguments, ArgTemplate does not check varadic functions */
4608 for (const Rlist *arg = finalargs; arg; arg = arg->next)
4609 {
4610 SyntaxTypeMatch err = CheckConstraintTypeMatch(id, arg->val, CF_DATA_TYPE_STRING, "", 1);
4611 if (err != SYNTAX_TYPE_MATCH_OK && err != SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED)
4612 {
4613 FatalError(ctx, "in %s: %s", id, SyntaxTypeMatchToString(err));
4614 }
4615 }
4616
4617 for (const Rlist *arg = finalargs; /* Start with arg set to finalargs. */
4618 arg; /* We must have arg to proceed. */
4619 arg = arg->next) /* arg steps forward every time. */
4620 {
4621 const char *pattern = RlistScalarValue(arg);
4622
4623 if (!IsAbsoluteFileName(pattern))
4624 {
4625 Log(LOG_LEVEL_WARNING,
4626 "Non-absolute path in findfiles(), skipping: %s",
4627 pattern);
4628 continue;
4629 }
4630
4631 StringSet *found = GlobFileList(pattern);
4632
4633 char fname[CF_BUFSIZE];
4634
4635 StringSetIterator it = StringSetIteratorInit(found);
4636 const char *element = NULL;
4637 while ((element = StringSetIteratorNext(&it)))
4638 {
4639 // TODO: this truncates the filename and may be removed
4640 // if Rlist and the core are OK with that possibility
4641 strlcpy(fname, element, CF_BUFSIZE);
4642 Log(LOG_LEVEL_VERBOSE, "%s pattern '%s' found match '%s'", fp->name, pattern, fname);
4643 RlistAppendScalarIdemp(&returnlist, fname);
4644 }
4645 StringSetDestroy(found);
4646 }
4647
4648 return (FnCallResult) { FNCALL_SUCCESS, { returnlist, RVAL_TYPE_LIST } };
4649 }
4650
4651 /*********************************************************************/
4652
FnCallFilter(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)4653 static FnCallResult FnCallFilter(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
4654 {
4655 return FilterInternal(ctx,
4656 fp,
4657 RlistScalarValue(finalargs), // regex or string
4658 finalargs->next, // list identifier
4659 BooleanFromString(RlistScalarValue(finalargs->next->next)), // match as regex or exactly
4660 BooleanFromString(RlistScalarValue(finalargs->next->next->next)), // invert matches
4661 IntFromString(RlistScalarValue(finalargs->next->next->next->next))); // max results
4662 }
4663
4664 /*********************************************************************/
4665
GetListReferenceArgument(const EvalContext * ctx,const FnCall * fp,const char * lval_str,DataType * datatype_out)4666 static const Rlist *GetListReferenceArgument(const EvalContext *ctx, const FnCall *fp, const char *lval_str, DataType *datatype_out)
4667 {
4668 VarRef *ref = VarRefParse(lval_str);
4669 DataType value_type;
4670 const Rlist *value = EvalContextVariableGet(ctx, ref, &value_type);
4671 VarRefDestroy(ref);
4672
4673 /* Error 1: variable not found. */
4674 if (value_type == CF_DATA_TYPE_NONE)
4675 {
4676 Log(LOG_LEVEL_VERBOSE,
4677 "Could not resolve expected list variable '%s' in function '%s'",
4678 lval_str, fp->name);
4679 assert(value == NULL);
4680 }
4681 /* Error 2: variable is not a list. */
4682 else if (DataTypeToRvalType(value_type) != RVAL_TYPE_LIST)
4683 {
4684 Log(LOG_LEVEL_ERR, "Function '%s' expected a list variable,"
4685 " got variable of type '%s'",
4686 fp->name, DataTypeToString(value_type));
4687
4688 value = NULL;
4689 value_type = CF_DATA_TYPE_NONE;
4690 }
4691
4692 if (datatype_out)
4693 {
4694 *datatype_out = value_type;
4695 }
4696 return value;
4697 }
4698
4699 /*********************************************************************/
4700
FilterInternal(EvalContext * ctx,const FnCall * fp,const char * regex,const Rlist * rp,bool do_regex,bool invert,long max)4701 static FnCallResult FilterInternal(EvalContext *ctx,
4702 const FnCall *fp,
4703 const char *regex,
4704 const Rlist* rp,
4705 bool do_regex,
4706 bool invert,
4707 long max)
4708 {
4709 pcre *rx = NULL;
4710 if (do_regex)
4711 {
4712 rx = CompileRegex(regex);
4713 if (!rx)
4714 {
4715 return FnFailure();
4716 }
4717 }
4718
4719 bool allocated = false;
4720 JsonElement *json = VarNameOrInlineToJson(ctx, fp, rp, false, &allocated);
4721
4722 // we failed to produce a valid JsonElement, so give up
4723 if (json == NULL)
4724 {
4725 pcre_free(rx);
4726 return FnFailure();
4727 }
4728 else if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER)
4729 {
4730 Log(LOG_LEVEL_VERBOSE, "Function '%s', argument '%s' was not a data container or list",
4731 fp->name, RlistScalarValueSafe(rp));
4732 JsonDestroyMaybe(json, allocated);
4733 pcre_free(rx);
4734 return FnFailure();
4735 }
4736
4737 Rlist *returnlist = NULL;
4738
4739 long match_count = 0;
4740 long total = 0;
4741
4742 JsonIterator iter = JsonIteratorInit(json);
4743 const JsonElement *el = NULL;
4744 while ((el = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true)) &&
4745 match_count < max)
4746 {
4747 char *val = JsonPrimitiveToString(el);
4748 if (val != NULL)
4749 {
4750 bool found;
4751 if (do_regex)
4752 {
4753 found = StringMatchFullWithPrecompiledRegex(rx, val);
4754 }
4755 else
4756 {
4757 found = (0==strcmp(regex, val));
4758 }
4759
4760 if (invert ? !found : found)
4761 {
4762 RlistAppendScalar(&returnlist, val);
4763 match_count++;
4764
4765 if (strcmp(fp->name, "some") == 0 ||
4766 strcmp(fp->name, "regarray") == 0)
4767 {
4768 free(val);
4769 break;
4770 }
4771 }
4772 else if (strcmp(fp->name, "every") == 0)
4773 {
4774 total++;
4775 free(val);
4776 break;
4777 }
4778
4779 total++;
4780 free(val);
4781 }
4782 }
4783
4784 JsonDestroyMaybe(json, allocated);
4785
4786 if (rx)
4787 {
4788 pcre_free(rx);
4789 }
4790
4791 bool contextmode = false;
4792 bool ret = false;
4793 if (strcmp(fp->name, "every") == 0)
4794 {
4795 contextmode = true;
4796 ret = (match_count == total && total > 0);
4797 }
4798 else if (strcmp(fp->name, "none") == 0)
4799 {
4800 contextmode = true;
4801 ret = (match_count == 0);
4802 }
4803 else if (strcmp(fp->name, "some") == 0 ||
4804 strcmp(fp->name, "regarray") == 0 ||
4805 strcmp(fp->name, "reglist") == 0)
4806 {
4807 contextmode = true;
4808 ret = (match_count > 0);
4809 }
4810 else if (strcmp(fp->name, "grep") != 0 &&
4811 strcmp(fp->name, "filter") != 0)
4812 {
4813 ProgrammingError("built-in FnCall %s: unhandled FilterInternal() contextmode", fp->name);
4814 }
4815
4816 if (contextmode)
4817 {
4818 RlistDestroy(returnlist);
4819 return FnReturnContext(ret);
4820 }
4821
4822 // else, return the list itself
4823 return (FnCallResult) { FNCALL_SUCCESS, { returnlist, RVAL_TYPE_LIST } };
4824 }
4825
4826 /*********************************************************************/
4827
FnCallSublist(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)4828 static FnCallResult FnCallSublist(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
4829 {
4830 const char *name_str = RlistScalarValueSafe(finalargs);
4831
4832 // try to load directly
4833 bool allocated = false;
4834 JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs, false, &allocated);
4835
4836 // we failed to produce a valid JsonElement, so give up
4837 if (json == NULL)
4838 {
4839 return FnFailure();
4840 }
4841 else if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER)
4842 {
4843 Log(LOG_LEVEL_VERBOSE, "Function '%s', argument '%s' was not a data container or list",
4844 fp->name, name_str);
4845 JsonDestroyMaybe(json, allocated);
4846 return FnFailure();
4847 }
4848
4849 bool head = (strcmp(RlistScalarValue(finalargs->next), "head") == 0); // heads or tails
4850 long max = IntFromString(RlistScalarValue(finalargs->next->next)); // max results
4851
4852 Rlist *input_list = NULL;
4853 Rlist *returnlist = NULL;
4854
4855 JsonIterator iter = JsonIteratorInit(json);
4856 const JsonElement *e;
4857 while ((e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true)))
4858 {
4859 RlistAppendScalar(&input_list, JsonPrimitiveGetAsString(e));
4860 }
4861
4862 JsonDestroyMaybe(json, allocated);
4863
4864 if (head)
4865 {
4866 long count = 0;
4867 for (const Rlist *rp = input_list; rp != NULL && count < max; rp = rp->next)
4868 {
4869 RlistAppendScalar(&returnlist, RlistScalarValue(rp));
4870 count++;
4871 }
4872 }
4873 else if (max > 0) // tail mode
4874 {
4875 const Rlist *rp = input_list;
4876 int length = RlistLen((const Rlist *) rp);
4877
4878 int offset = max >= length ? 0 : length-max;
4879
4880 for (; rp != NULL && offset--; rp = rp->next)
4881 {
4882 /* skip to offset */
4883 }
4884
4885 for (; rp != NULL; rp = rp->next)
4886 {
4887 RlistAppendScalar(&returnlist, RlistScalarValue(rp));
4888 }
4889 }
4890
4891 RlistDestroy(input_list);
4892 return (FnCallResult) { FNCALL_SUCCESS, { returnlist, RVAL_TYPE_LIST } };
4893 }
4894
4895 /*********************************************************************/
4896
4897 // TODO: This monstrosity needs refactoring
FnCallSetop(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)4898 static FnCallResult FnCallSetop(EvalContext *ctx,
4899 ARG_UNUSED const Policy *policy,
4900 const FnCall *fp, const Rlist *finalargs)
4901 {
4902 bool difference_mode = (strcmp(fp->name, "difference") == 0);
4903 bool unique_mode = (strcmp(fp->name, "unique") == 0);
4904
4905 const char *name_str = RlistScalarValueSafe(finalargs);
4906 bool allocated = false;
4907 JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs, false, &allocated);
4908
4909 // we failed to produce a valid JsonElement, so give up
4910 if (json == NULL)
4911 {
4912 return FnFailure();
4913 }
4914 else if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER)
4915 {
4916 Log(LOG_LEVEL_VERBOSE, "Function '%s', argument '%s' was not a data container or list",
4917 fp->name, name_str);
4918 JsonDestroyMaybe(json, allocated);
4919 return FnFailure();
4920 }
4921
4922 JsonElement *json_b = NULL;
4923 bool allocated_b = false;
4924 if (!unique_mode)
4925 {
4926 const char *name_str_b = RlistScalarValueSafe(finalargs->next);
4927 json_b = VarNameOrInlineToJson(ctx, fp, finalargs->next, false, &allocated_b);
4928
4929 // we failed to produce a valid JsonElement, so give up
4930 if (json_b == NULL)
4931 {
4932 JsonDestroyMaybe(json, allocated);
4933 return FnFailure();
4934 }
4935 else if (JsonGetElementType(json_b) != JSON_ELEMENT_TYPE_CONTAINER)
4936 {
4937 Log(LOG_LEVEL_VERBOSE, "Function '%s', argument '%s' was not a data container or list",
4938 fp->name, name_str_b);
4939 JsonDestroyMaybe(json, allocated);
4940 JsonDestroyMaybe(json_b, allocated_b);
4941 return FnFailure();
4942 }
4943 }
4944
4945 StringSet *set_b = StringSetNew();
4946 if (!unique_mode)
4947 {
4948 JsonIterator iter = JsonIteratorInit(json_b);
4949 const JsonElement *e;
4950 while ((e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true)))
4951 {
4952 StringSetAdd(set_b, xstrdup(JsonPrimitiveGetAsString(e)));
4953 }
4954 }
4955
4956 Rlist *returnlist = NULL;
4957
4958 JsonIterator iter = JsonIteratorInit(json);
4959 const JsonElement *e;
4960 while ((e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true)))
4961 {
4962 const char *value = JsonPrimitiveGetAsString(e);
4963
4964 // Yes, this is an XOR. But it's more legible this way.
4965 if (!unique_mode && difference_mode && StringSetContains(set_b, value))
4966 {
4967 continue;
4968 }
4969
4970 if (!unique_mode && !difference_mode && !StringSetContains(set_b, value))
4971 {
4972 continue;
4973 }
4974
4975 RlistAppendScalarIdemp(&returnlist, value);
4976 }
4977
4978 JsonDestroyMaybe(json, allocated);
4979 if (json_b != NULL)
4980 {
4981 JsonDestroyMaybe(json_b, allocated_b);
4982 }
4983
4984
4985 StringSetDestroy(set_b);
4986
4987 return (FnCallResult) { FNCALL_SUCCESS, (Rval) { returnlist, RVAL_TYPE_LIST } };
4988 }
4989
FnCallLength(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)4990 static FnCallResult FnCallLength(EvalContext *ctx,
4991 ARG_UNUSED const Policy *policy,
4992 const FnCall *fp, const Rlist *finalargs)
4993 {
4994 const char *name_str = RlistScalarValueSafe(finalargs);
4995
4996 // try to load directly
4997 bool allocated = false;
4998 JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs, false, &allocated);
4999
5000 // we failed to produce a valid JsonElement, so give up
5001 if (json == NULL)
5002 {
5003 return FnFailure();
5004 }
5005 else if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER)
5006 {
5007 Log(LOG_LEVEL_VERBOSE, "Function '%s', argument '%s' was not a data container or list",
5008 fp->name, name_str);
5009 JsonDestroyMaybe(json, allocated);
5010 return FnFailure();
5011 }
5012
5013 size_t len = JsonLength(json);
5014 JsonDestroyMaybe(json, allocated);
5015 return FnReturnF("%zu", len);
5016 }
5017
FnCallFold(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)5018 static FnCallResult FnCallFold(EvalContext *ctx,
5019 ARG_UNUSED const Policy *policy,
5020 const FnCall *fp, const Rlist *finalargs)
5021 {
5022 const char *sort_type = finalargs->next ? RlistScalarValue(finalargs->next) : NULL;
5023
5024 size_t count = 0;
5025 double product = 1.0; // this could overflow
5026 double sum = 0; // this could overflow
5027 double mean = 0;
5028 double M2 = 0;
5029 char* min = NULL;
5030 char* max = NULL;
5031 bool variance_mode = strcmp(fp->name, "variance") == 0;
5032 bool mean_mode = strcmp(fp->name, "mean") == 0;
5033 bool max_mode = strcmp(fp->name, "max") == 0;
5034 bool min_mode = strcmp(fp->name, "min") == 0;
5035 bool sum_mode = strcmp(fp->name, "sum") == 0;
5036 bool product_mode = strcmp(fp->name, "product") == 0;
5037
5038 bool allocated = false;
5039 JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs, false, &allocated);
5040
5041 if (!json)
5042 {
5043 return FnFailure();
5044 }
5045
5046 JsonIterator iter = JsonIteratorInit(json);
5047 const JsonElement *el;
5048 while ((el = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true)))
5049 {
5050 char *value = JsonPrimitiveToString(el);
5051
5052 if (value != NULL)
5053 {
5054 if (sort_type)
5055 {
5056 if (min_mode && (min == NULL || !GenericStringItemLess(sort_type, min, value)))
5057 {
5058 free(min);
5059 min = xstrdup(value);
5060 }
5061
5062 if (max_mode && (max == NULL || GenericStringItemLess(sort_type, max, value)))
5063 {
5064 free(max);
5065 max = xstrdup(value);
5066 }
5067 }
5068
5069 count++;
5070
5071 if (mean_mode || variance_mode || sum_mode || product_mode)
5072 {
5073 double x;
5074 if (sscanf(value, "%lf", &x) != 1)
5075 {
5076 x = 0; /* treat non-numeric entries as zero */
5077 }
5078
5079 // Welford's algorithm
5080 double delta = x - mean;
5081 mean += delta/count;
5082 M2 += delta * (x - mean);
5083 sum += x;
5084 product *= x;
5085 }
5086
5087 free(value);
5088 }
5089 }
5090
5091 JsonDestroyMaybe(json, allocated);
5092
5093 if (mean_mode)
5094 {
5095 return count == 0 ? FnFailure() : FnReturnF("%lf", mean);
5096 }
5097 else if (sum_mode)
5098 {
5099 return FnReturnF("%lf", sum);
5100 }
5101 else if (product_mode)
5102 {
5103 return FnReturnF("%lf", product);
5104 }
5105 else if (variance_mode)
5106 {
5107 double variance = 0;
5108
5109 if (count == 0)
5110 {
5111 return FnFailure();
5112 }
5113
5114 // if count is 1, variance is 0
5115
5116 if (count > 1)
5117 {
5118 variance = M2/(count - 1);
5119 }
5120
5121 return FnReturnF("%lf", variance);
5122 }
5123 else if (max_mode)
5124 {
5125 return max == NULL ? FnFailure() : FnReturnNoCopy(max);
5126 }
5127 else if (min_mode)
5128 {
5129 return min == NULL ? FnFailure() : FnReturnNoCopy(min);
5130 }
5131
5132 // else, we don't know this fp->name
5133 ProgrammingError("Unknown function call %s to FnCallFold", fp->name);
5134 return FnFailure();
5135 }
5136
5137 /*********************************************************************/
5138
FnCallDatatype(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)5139 static FnCallResult FnCallDatatype(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
5140 {
5141 assert(fp != NULL);
5142 assert(fp->name != NULL);
5143
5144 if (finalargs == NULL)
5145 {
5146 Log(LOG_LEVEL_ERR,
5147 "Function %s requires variable identifier as first argument",
5148 fp->name);
5149 return FnFailure();
5150 }
5151 const char* const var_name = RlistScalarValue(finalargs);
5152
5153 VarRef* const var_ref = VarRefParse(var_name);
5154 DataType type;
5155 const void *value = EvalContextVariableGet(ctx, var_ref, &type);
5156 VarRefDestroy(var_ref);
5157
5158 /* detail argument defaults to false */
5159 bool detail = false;
5160 if (finalargs->next != NULL)
5161 {
5162 detail = BooleanFromString(RlistScalarValue(finalargs->next));
5163 }
5164
5165 const char *const type_str =
5166 (type == CF_DATA_TYPE_NONE) ? "none" : DataTypeToString(type);
5167
5168 if (!detail)
5169 {
5170 return FnReturn(type_str);
5171 }
5172
5173 if (type == CF_DATA_TYPE_CONTAINER)
5174 {
5175 const char *subtype_str;
5176 const JsonElement *const element = value;
5177
5178 switch (JsonGetType(element))
5179 {
5180 case JSON_CONTAINER_TYPE_OBJECT:
5181 subtype_str = "object";
5182 break;
5183 case JSON_CONTAINER_TYPE_ARRAY:
5184 subtype_str = "array";
5185 break;
5186 case JSON_PRIMITIVE_TYPE_STRING:
5187 subtype_str = "string";
5188 break;
5189 case JSON_PRIMITIVE_TYPE_INTEGER:
5190 subtype_str = "int";
5191 break;
5192 case JSON_PRIMITIVE_TYPE_REAL:
5193 subtype_str = "real";
5194 break;
5195 case JSON_PRIMITIVE_TYPE_BOOL:
5196 subtype_str = "boolean";
5197 break;
5198 case JSON_PRIMITIVE_TYPE_NULL:
5199 subtype_str = "null";
5200 break;
5201 default:
5202 Log(LOG_LEVEL_ERR,
5203 "Function %s failed to get subtype of type data", fp->name);
5204 return FnFailure();
5205 }
5206
5207 return FnReturnF("%s %s", type_str, subtype_str);
5208 }
5209
5210 return FnReturnF("policy %s", type_str);
5211 }
5212
5213 /*********************************************************************/
5214
FnCallNth(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)5215 static FnCallResult FnCallNth(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
5216 {
5217 const char* const key = RlistScalarValue(finalargs->next);
5218
5219 const char *name_str = RlistScalarValueSafe(finalargs);
5220
5221 // try to load directly
5222 bool allocated = false;
5223 JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs, false, &allocated);
5224
5225 // we failed to produce a valid JsonElement, so give up
5226 if (json == NULL)
5227 {
5228 return FnFailure();
5229 }
5230 else if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER)
5231 {
5232 Log(LOG_LEVEL_VERBOSE, "Function '%s', argument '%s' was not a data container or list",
5233 fp->name, name_str);
5234 JsonDestroyMaybe(json, allocated);
5235 return FnFailure();
5236 }
5237
5238 const char *jstring = NULL;
5239 FnCallResult result;
5240 if (JsonGetElementType(json) == JSON_ELEMENT_TYPE_CONTAINER)
5241 {
5242 JsonContainerType ct = JsonGetContainerType(json);
5243 JsonElement* jelement = NULL;
5244
5245 if (JSON_CONTAINER_TYPE_OBJECT == ct)
5246 {
5247 jelement = JsonObjectGet(json, key);
5248 }
5249 else if (JSON_CONTAINER_TYPE_ARRAY == ct)
5250 {
5251 long index = IntFromString(key);
5252 if (index < 0)
5253 {
5254 index += JsonLength(json);
5255 }
5256
5257 if (index >= 0 && index < (long) JsonLength(json))
5258 {
5259 jelement = JsonAt(json, index);
5260 }
5261 }
5262 else
5263 {
5264 ProgrammingError("JSON Container is neither array nor object but type %d", (int) ct);
5265 }
5266
5267 if (jelement != NULL &&
5268 JsonGetElementType(jelement) == JSON_ELEMENT_TYPE_PRIMITIVE)
5269 {
5270 jstring = JsonPrimitiveGetAsString(jelement);
5271 if (jstring != NULL)
5272 {
5273 result = FnReturn(jstring);
5274 }
5275 }
5276 }
5277
5278 JsonDestroyMaybe(json, allocated);
5279
5280 if (jstring == NULL)
5281 {
5282 return FnFailure();
5283 }
5284
5285 return result;
5286 }
5287
5288 /*********************************************************************/
5289
FnCallEverySomeNone(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)5290 static FnCallResult FnCallEverySomeNone(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
5291 {
5292 return FilterInternal(ctx,
5293 fp,
5294 RlistScalarValue(finalargs), // regex or string
5295 finalargs->next, // list identifier
5296 1,
5297 0,
5298 LONG_MAX);
5299 }
5300
FnCallSort(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)5301 static FnCallResult FnCallSort(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
5302 {
5303 if (finalargs == NULL)
5304 {
5305 FatalError(ctx, "in built-in FnCall %s: missing first argument, a list name", fp->name);
5306 }
5307
5308 const char *sort_type = NULL;
5309
5310 if (finalargs->next)
5311 {
5312 sort_type = RlistScalarValue(finalargs->next); // sort mode
5313 }
5314 else
5315 {
5316 sort_type = "lex";
5317 }
5318
5319 const char *name_str = RlistScalarValueSafe(finalargs);
5320
5321 // try to load directly
5322 bool allocated = false;
5323 JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs, false, &allocated);
5324
5325 // we failed to produce a valid JsonElement, so give up
5326 if (json == NULL)
5327 {
5328 return FnFailure();
5329 }
5330 else if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER)
5331 {
5332 Log(LOG_LEVEL_VERBOSE, "Function '%s', argument '%s' was not a data container or list",
5333 fp->name, name_str);
5334 JsonDestroyMaybe(json, allocated);
5335 return FnFailure();
5336 }
5337
5338 Rlist *sorted = NULL;
5339 JsonIterator iter = JsonIteratorInit(json);
5340 const JsonElement *e;
5341 while ((e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true)))
5342 {
5343 RlistAppendScalar(&sorted, JsonPrimitiveGetAsString(e));
5344 }
5345 JsonDestroyMaybe(json, allocated);
5346
5347 if (strcmp(sort_type, "int") == 0)
5348 {
5349 sorted = IntSortRListNames(sorted);
5350 }
5351 else if (strcmp(sort_type, "real") == 0)
5352 {
5353 sorted = RealSortRListNames(sorted);
5354 }
5355 else if (strcmp(sort_type, "IP") == 0 || strcmp(sort_type, "ip") == 0)
5356 {
5357 sorted = IPSortRListNames(sorted);
5358 }
5359 else if (strcmp(sort_type, "MAC") == 0 || strcmp(sort_type, "mac") == 0)
5360 {
5361 sorted = MACSortRListNames(sorted);
5362 }
5363 else // "lex"
5364 {
5365 sorted = AlphaSortRListNames(sorted);
5366 }
5367
5368 return (FnCallResult) { FNCALL_SUCCESS, (Rval) { sorted, RVAL_TYPE_LIST } };
5369 }
5370
5371 /*********************************************************************/
5372
FnCallFormat(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)5373 static FnCallResult FnCallFormat(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
5374 {
5375 const char *const id = "built-in FnCall format-arg";
5376
5377 /* We need to check all the arguments, ArgTemplate does not check varadic functions */
5378 for (const Rlist *arg = finalargs; arg; arg = arg->next)
5379 {
5380 SyntaxTypeMatch err = CheckConstraintTypeMatch(id, arg->val, CF_DATA_TYPE_STRING, "", 1);
5381 if (err != SYNTAX_TYPE_MATCH_OK && err != SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED)
5382 {
5383 FatalError(ctx, "in %s: %s", id, SyntaxTypeMatchToString(err));
5384 }
5385 }
5386
5387 if (finalargs == NULL)
5388 {
5389 return FnFailure();
5390 }
5391
5392 char *format = RlistScalarValue(finalargs);
5393
5394 if (format == NULL)
5395 {
5396 return FnFailure();
5397 }
5398
5399 const Rlist *rp = finalargs->next;
5400
5401 char *check = strchr(format, '%');
5402 char check_buffer[CF_BUFSIZE];
5403 Buffer *buf = BufferNew();
5404
5405 if (check != NULL)
5406 {
5407 BufferAppend(buf, format, check - format);
5408 Seq *s;
5409
5410 while (check != NULL &&
5411 (s = StringMatchCaptures("^(%%|%[^diouxXeEfFgGaAcsCSpnm%]*?[diouxXeEfFgGaAcsCSpnm])([^%]*)(.*)$", check, false)))
5412 {
5413 {
5414 if (SeqLength(s) >= 2)
5415 {
5416 const char *format_piece = BufferData(SeqAt(s, 1));
5417 bool percent = StringEqualN(format_piece, "%%", 2);
5418 char *data = NULL;
5419
5420 if (percent)
5421 {
5422 // "%%" in format string
5423 }
5424 else if (rp != NULL)
5425 {
5426 data = RlistScalarValue(rp);
5427 rp = rp->next;
5428 }
5429 else // not %% and no data
5430 {
5431 Log(LOG_LEVEL_ERR, "format() didn't have enough parameters");
5432 BufferDestroy(buf);
5433 SeqDestroy(s);
5434 return FnFailure();
5435 }
5436
5437 char piece[CF_BUFSIZE];
5438 memset(piece, 0, CF_BUFSIZE);
5439
5440 const char bad_modifiers[] = "hLqjzt";
5441 const size_t length = strlen(bad_modifiers);
5442 for (size_t b = 0; b < length; b++)
5443 {
5444 if (strchr(format_piece, bad_modifiers[b]) != NULL)
5445 {
5446 Log(LOG_LEVEL_ERR, "format() does not allow modifier character '%c' in format specifier '%s'.",
5447 bad_modifiers[b],
5448 format_piece);
5449 BufferDestroy(buf);
5450 SeqDestroy(s);
5451 return FnFailure();
5452 }
5453 }
5454
5455 if (strrchr(format_piece, 'd') != NULL || strrchr(format_piece, 'o') != NULL || strrchr(format_piece, 'x') != NULL)
5456 {
5457 long x = 0;
5458 sscanf(data, "%ld", &x);
5459 snprintf(piece, CF_BUFSIZE, format_piece, x);
5460 BufferAppend(buf, piece, strlen(piece));
5461 }
5462 else if (percent)
5463 {
5464 // "%%" -> "%"
5465 BufferAppend(buf, "%", 1);
5466 }
5467 else if (strrchr(format_piece, 'f') != NULL)
5468 {
5469 double x = 0;
5470 sscanf(data, "%lf", &x);
5471 snprintf(piece, CF_BUFSIZE, format_piece, x);
5472 BufferAppend(buf, piece, strlen(piece));
5473 }
5474 else if (strrchr(format_piece, 's') != NULL)
5475 {
5476 BufferAppendF(buf, format_piece, data);
5477 }
5478 else if (strrchr(format_piece, 'S') != NULL)
5479 {
5480 char *found_format_spec = NULL;
5481 char format_rewrite[CF_BUFSIZE];
5482
5483 strlcpy(format_rewrite, format_piece, CF_BUFSIZE);
5484 found_format_spec = strrchr(format_rewrite, 'S');
5485
5486 if (found_format_spec != NULL)
5487 {
5488 *found_format_spec = 's';
5489 }
5490 else
5491 {
5492 ProgrammingError("Couldn't find the expected S format spec in %s", format_piece);
5493 }
5494
5495 const char* const varname = data;
5496 VarRef *ref = VarRefParse(varname);
5497 DataType type;
5498 const void *value = EvalContextVariableGet(ctx, ref, &type);
5499 VarRefDestroy(ref);
5500
5501 if (type == CF_DATA_TYPE_CONTAINER)
5502 {
5503 Writer *w = StringWriter();
5504 JsonWriteCompact(w, value);
5505 BufferAppendF(buf, format_rewrite, StringWriterData(w));
5506 WriterClose(w);
5507 }
5508 else // it might be a list reference
5509 {
5510 DataType data_type;
5511 const Rlist *list = GetListReferenceArgument(ctx, fp, varname, &data_type);
5512 if (data_type == CF_DATA_TYPE_STRING_LIST)
5513 {
5514 Writer *w = StringWriter();
5515 WriterWrite(w, "{ ");
5516 for (const Rlist *rp = list; rp; rp = rp->next)
5517 {
5518 char *escaped = EscapeCharCopy(RlistScalarValue(rp), '"', '\\');
5519 WriterWriteF(w, "\"%s\"", escaped);
5520 free(escaped);
5521
5522 if (rp != NULL && rp->next != NULL)
5523 {
5524 WriterWrite(w, ", ");
5525 }
5526 }
5527 WriterWrite(w, " }");
5528
5529 BufferAppendF(buf, format_rewrite, StringWriterData(w));
5530 WriterClose(w);
5531 }
5532 else // whatever this is, it's not a list reference or a data container
5533 {
5534 Log(LOG_LEVEL_VERBOSE, "format() with %%S specifier needs a data container or a list instead of '%s'.",
5535 varname);
5536 BufferDestroy(buf);
5537 SeqDestroy(s);
5538 return FnFailure();
5539 }
5540 }
5541 }
5542 else
5543 {
5544 char error[] = "(unhandled format)";
5545 BufferAppend(buf, error, strlen(error));
5546 }
5547 }
5548 else
5549 {
5550 check = NULL;
5551 }
5552 }
5553
5554 {
5555 if (SeqLength(s) >= 3)
5556 {
5557 BufferAppend(buf, BufferData(SeqAt(s, 2)), BufferSize(SeqAt(s, 2)));
5558 }
5559 else
5560 {
5561 check = NULL;
5562 }
5563 }
5564
5565 {
5566 if (SeqLength(s) >= 4)
5567 {
5568 strlcpy(check_buffer, BufferData(SeqAt(s, 3)), CF_BUFSIZE);
5569 check = check_buffer;
5570 }
5571 else
5572 {
5573 check = NULL;
5574 }
5575 }
5576
5577 SeqDestroy(s);
5578 }
5579 }
5580 else
5581 {
5582 BufferAppend(buf, format, strlen(format));
5583 }
5584
5585 return FnReturnBuffer(buf);
5586 }
5587
5588 /*********************************************************************/
5589
FnCallIPRange(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)5590 static FnCallResult FnCallIPRange(EvalContext *ctx, ARG_UNUSED const Policy *policy,
5591 const FnCall *fp, const Rlist *finalargs)
5592 {
5593 const char *range = RlistScalarValue(finalargs);
5594 const Rlist *ifaces = finalargs->next;
5595
5596 if (!FuzzyMatchParse(range))
5597 {
5598 Log(LOG_LEVEL_VERBOSE,
5599 "%s(%s): argument is not a valid address range",
5600 fp->name, range);
5601 return FnFailure();
5602 }
5603
5604 for (const Item *ip = EvalContextGetIpAddresses(ctx);
5605 ip != NULL;
5606 ip = ip->next)
5607 {
5608 if (FuzzySetMatch(range, ip->name) == 0)
5609 {
5610 /*
5611 * MODE1: iprange(range)
5612 * Match range on the address of any interface.
5613 */
5614 if (ifaces == NULL)
5615 {
5616 Log(LOG_LEVEL_DEBUG, "%s(%s): Match on IP '%s'",
5617 fp->name, range, ip->name);
5618 return FnReturnContext(true);
5619 }
5620 /*
5621 * MODE2: iprange(range, args...)
5622 * Match range only on the addresses of args interfaces.
5623 */
5624 else
5625 {
5626 for (const Rlist *i = ifaces; i != NULL; i = i->next)
5627 {
5628 char *iface = xstrdup(RlistScalarValue(i));
5629 CanonifyNameInPlace(iface);
5630
5631 const char *ip_iface = ip->classes;
5632
5633 if (ip_iface != NULL &&
5634 strcmp(iface, ip_iface) == 0)
5635 {
5636 Log(LOG_LEVEL_DEBUG,
5637 "%s(%s): Match on IP '%s' interface '%s'",
5638 fp->name, range, ip->name, ip->classes);
5639
5640 free(iface);
5641 return FnReturnContext(true);
5642 }
5643 free(iface);
5644 }
5645 }
5646 }
5647 }
5648
5649 Log(LOG_LEVEL_DEBUG, "%s(%s): no match", fp->name, range);
5650 return FnReturnContext(false);
5651 }
5652
FnCallIsIpInSubnet(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)5653 static FnCallResult FnCallIsIpInSubnet(ARG_UNUSED EvalContext *ctx,
5654 ARG_UNUSED const Policy *policy,
5655 const FnCall *fp, const Rlist *finalargs)
5656 {
5657 const char *range = RlistScalarValue(finalargs);
5658 const Rlist *ips = finalargs->next;
5659
5660 if (!FuzzyMatchParse(range))
5661 {
5662 Log(LOG_LEVEL_VERBOSE,
5663 "%s(%s): argument is not a valid address range",
5664 fp->name, range);
5665 return FnFailure();
5666 }
5667
5668 for (const Rlist *ip = ips; ip != NULL; ip = ip->next)
5669 {
5670 const char *ip_s = RlistScalarValue(ip);
5671
5672 if (FuzzySetMatch(range, ip_s) == 0)
5673 {
5674 Log(LOG_LEVEL_DEBUG, "%s(%s): Match on IP '%s'",
5675 fp->name, range, ip_s);
5676 return FnReturnContext(true);
5677 }
5678 }
5679
5680 Log(LOG_LEVEL_DEBUG, "%s(%s): no match", fp->name, range);
5681 return FnReturnContext(false);
5682 }
5683 /*********************************************************************/
5684
FnCallHostRange(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)5685 static FnCallResult FnCallHostRange(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
5686 {
5687 char *prefix = RlistScalarValue(finalargs);
5688 char *range = RlistScalarValue(finalargs->next);
5689
5690 if (!FuzzyHostParse(range))
5691 {
5692 return FnFailure();
5693 }
5694
5695 return FnReturnContext(FuzzyHostMatch(prefix, range, VUQNAME) == 0);
5696 }
5697
5698 /*********************************************************************/
5699
FnCallHostInNetgroup(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)5700 FnCallResult FnCallHostInNetgroup(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
5701 {
5702 setnetgrent(RlistScalarValue(finalargs));
5703
5704 bool found = false;
5705 char *host, *user, *domain;
5706 while (getnetgrent(&host, &user, &domain))
5707 {
5708 if (host == NULL)
5709 {
5710 Log(LOG_LEVEL_VERBOSE, "Matched '%s' in netgroup '%s'",
5711 VFQNAME, RlistScalarValue(finalargs));
5712 found = true;
5713 break;
5714 }
5715
5716 if (strcmp(host, VFQNAME) == 0 ||
5717 strcmp(host, VUQNAME) == 0)
5718 {
5719 Log(LOG_LEVEL_VERBOSE, "Matched '%s' in netgroup '%s'",
5720 host, RlistScalarValue(finalargs));
5721 found = true;
5722 break;
5723 }
5724 }
5725
5726 endnetgrent();
5727
5728 return FnReturnContext(found);
5729 }
5730
5731 /*********************************************************************/
5732
FnCallIsVariable(EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)5733 static FnCallResult FnCallIsVariable(EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
5734 {
5735 const char *lval = RlistScalarValue(finalargs);
5736 bool found = false;
5737
5738 if (lval)
5739 {
5740 VarRef *ref = VarRefParse(lval);
5741 DataType value_type;
5742 EvalContextVariableGet(ctx, ref, &value_type);
5743 if (value_type != CF_DATA_TYPE_NONE)
5744 {
5745 found = true;
5746 }
5747 VarRefDestroy(ref);
5748 }
5749
5750 return FnReturnContext(found);
5751 }
5752
5753 /*********************************************************************/
5754
FnCallStrCmp(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)5755 static FnCallResult FnCallStrCmp(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
5756 {
5757 return FnReturnContext(strcmp(RlistScalarValue(finalargs), RlistScalarValue(finalargs->next)) == 0);
5758 }
5759
5760 /*********************************************************************/
5761
FnCallTranslatePath(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)5762 static FnCallResult FnCallTranslatePath(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
5763 {
5764 char buffer[MAX_FILENAME];
5765
5766 strlcpy(buffer, RlistScalarValue(finalargs), sizeof(buffer));
5767 MapName(buffer);
5768
5769 return FnReturn(buffer);
5770 }
5771
5772 /*********************************************************************/
5773
5774 #if defined(__MINGW32__)
5775
FnCallRegistryValue(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)5776 static FnCallResult FnCallRegistryValue(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
5777 {
5778 char buffer[CF_BUFSIZE] = "";
5779
5780 const char *const key = RlistScalarValue(finalargs);
5781 const char *const value = RlistScalarValue(finalargs->next);
5782 if (GetRegistryValue(key, value, buffer, sizeof(buffer)))
5783 {
5784 return FnReturn(buffer);
5785 }
5786
5787 Log(LOG_LEVEL_ERR, "Could not read existing registry data for key '%s' and value '%s'.",
5788 key, value);
5789 return FnFailure();
5790 }
5791
5792 #else /* !__MINGW32__ */
5793
FnCallRegistryValue(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,ARG_UNUSED const Rlist * finalargs)5794 static FnCallResult FnCallRegistryValue(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, ARG_UNUSED const Rlist *finalargs)
5795 {
5796 return FnFailure();
5797 }
5798
5799 #endif /* !__MINGW32__ */
5800
5801 /*********************************************************************/
5802
FnCallRemoteScalar(EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)5803 static FnCallResult FnCallRemoteScalar(EvalContext *ctx,
5804 ARG_UNUSED const Policy *policy,
5805 ARG_UNUSED const FnCall *fp,
5806 const Rlist *finalargs)
5807 {
5808 char *handle = RlistScalarValue(finalargs);
5809 char *server = RlistScalarValue(finalargs->next);
5810 int encrypted = BooleanFromString(RlistScalarValue(finalargs->next->next));
5811
5812 if (strcmp(server, "localhost") == 0)
5813 {
5814 /* The only reason for this is testing... */
5815 server = "127.0.0.1";
5816 }
5817
5818 if (THIS_AGENT_TYPE == AGENT_TYPE_COMMON)
5819 {
5820 return FnReturn("<remote scalar>");
5821 }
5822 else
5823 {
5824 char buffer[CF_BUFSIZE];
5825 buffer[0] = '\0';
5826
5827 char *ret = GetRemoteScalar(ctx, "VAR", handle, server,
5828 encrypted, buffer);
5829 if (ret == NULL)
5830 {
5831 return FnFailure();
5832 }
5833
5834 if (strncmp(buffer, "BAD:", 4) == 0)
5835 {
5836 if (RetrieveUnreliableValue("remotescalar", handle, buffer) == 0)
5837 {
5838 // This function should never fail
5839 buffer[0] = '\0';
5840 }
5841 }
5842 else
5843 {
5844 CacheUnreliableValue("remotescalar", handle, buffer);
5845 }
5846
5847 return FnReturn(buffer);
5848 }
5849 }
5850
5851 /*********************************************************************/
5852
FnCallHubKnowledge(EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)5853 static FnCallResult FnCallHubKnowledge(EvalContext *ctx,
5854 ARG_UNUSED const Policy *policy,
5855 ARG_UNUSED const FnCall *fp,
5856 const Rlist *finalargs)
5857 {
5858 char *handle = RlistScalarValue(finalargs);
5859
5860 if (THIS_AGENT_TYPE != AGENT_TYPE_AGENT)
5861 {
5862 return FnReturn("<inaccessible remote scalar>");
5863 }
5864 else
5865 {
5866 char buffer[CF_BUFSIZE];
5867 buffer[0] = '\0';
5868
5869 Log(LOG_LEVEL_VERBOSE, "Accessing hub knowledge base for '%s'", handle);
5870 char *ret = GetRemoteScalar(ctx, "VAR", handle, PolicyServerGetIP(),
5871 true, buffer);
5872 if (ret == NULL)
5873 {
5874 return FnFailure();
5875 }
5876
5877
5878 // This should always be successful - and this one doesn't cache
5879
5880 if (strncmp(buffer, "BAD:", 4) == 0)
5881 {
5882 return FnReturn("0");
5883 }
5884
5885 return FnReturn(buffer);
5886 }
5887 }
5888
5889 /*********************************************************************/
5890
FnCallRemoteClassesMatching(EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)5891 static FnCallResult FnCallRemoteClassesMatching(EvalContext *ctx,
5892 ARG_UNUSED const Policy *policy,
5893 ARG_UNUSED const FnCall *fp,
5894 const Rlist *finalargs)
5895 {
5896 char *regex = RlistScalarValue(finalargs);
5897 char *server = RlistScalarValue(finalargs->next);
5898 int encrypted = BooleanFromString(RlistScalarValue(finalargs->next->next));
5899 char *prefix = RlistScalarValue(finalargs->next->next->next);
5900
5901 if (strcmp(server, "localhost") == 0)
5902 {
5903 /* The only reason for this is testing... */
5904 server = "127.0.0.1";
5905 }
5906
5907 if (THIS_AGENT_TYPE == AGENT_TYPE_COMMON)
5908 {
5909 return FnReturn("remote_classes");
5910 }
5911 else
5912 {
5913 char buffer[CF_BUFSIZE];
5914 buffer[0] = '\0';
5915
5916 char *ret = GetRemoteScalar(ctx, "CONTEXT", regex, server,
5917 encrypted, buffer);
5918 if (ret == NULL)
5919 {
5920 return FnFailure();
5921 }
5922
5923 if (strncmp(buffer, "BAD:", 4) == 0)
5924 {
5925 return FnFailure();
5926 }
5927
5928 Rlist *classlist = RlistFromSplitString(buffer, ',');
5929 if (classlist)
5930 {
5931 for (const Rlist *rp = classlist; rp != NULL; rp = rp->next)
5932 {
5933 char class_name[CF_MAXVARSIZE];
5934 snprintf(class_name, sizeof(class_name), "%s_%s",
5935 prefix, RlistScalarValue(rp));
5936 EvalContextClassPutSoft(ctx, class_name, CONTEXT_SCOPE_BUNDLE,
5937 "source=function,function=remoteclassesmatching");
5938 }
5939 RlistDestroy(classlist);
5940 }
5941
5942 return FnReturnContext(true);
5943 }
5944 }
5945
5946 /*********************************************************************/
5947
FnCallPeers(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)5948 static FnCallResult FnCallPeers(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
5949 {
5950 int maxent = 100000, maxsize = 100000;
5951
5952 char *filename = RlistScalarValue(finalargs);
5953 char *comment = RlistScalarValue(finalargs->next);
5954 int groupsize = IntFromString(RlistScalarValue(finalargs->next->next));
5955
5956 if (2 > groupsize)
5957 {
5958 Log(LOG_LEVEL_WARNING, "Function %s: called with a nonsensical group size of %d, failing", fp->name, groupsize);
5959 return FnFailure();
5960 }
5961
5962 char *file_buffer = CfReadFile(filename, maxsize);
5963
5964 if (file_buffer == NULL)
5965 {
5966 return FnFailure();
5967 }
5968
5969 file_buffer = StripPatterns(file_buffer, comment, filename);
5970
5971 Rlist *const newlist =
5972 file_buffer ? RlistFromSplitRegex(file_buffer, "\n", maxent, true) : NULL;
5973
5974 /* Slice up the list and discard everything except our slice */
5975
5976 int i = 0;
5977 bool found = false;
5978 Rlist *pruned = NULL;
5979
5980 for (const Rlist *rp = newlist; rp != NULL; rp = rp->next)
5981 {
5982 const char *s = RlistScalarValue(rp);
5983 if (EmptyString(s))
5984 {
5985 continue;
5986 }
5987
5988 if (strcmp(s, VFQNAME) == 0 || strcmp(s, VUQNAME) == 0)
5989 {
5990 found = true;
5991 }
5992 else
5993 {
5994 RlistPrepend(&pruned, s, RVAL_TYPE_SCALAR);
5995 }
5996
5997 if (i++ % groupsize == groupsize - 1)
5998 {
5999 if (found)
6000 {
6001 break;
6002 }
6003 else
6004 {
6005 RlistDestroy(pruned);
6006 pruned = NULL;
6007 }
6008 }
6009 }
6010
6011 RlistDestroy(newlist);
6012 free(file_buffer); // OK if it's NULL
6013
6014 if (pruned && found)
6015 {
6016 RlistReverse(&pruned);
6017 }
6018 else
6019 {
6020 RlistDestroy(pruned);
6021 pruned = NULL;
6022 }
6023 return (FnCallResult) { FNCALL_SUCCESS, { pruned, RVAL_TYPE_LIST } };
6024 }
6025
6026 /*********************************************************************/
6027
FnCallPeerLeader(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)6028 static FnCallResult FnCallPeerLeader(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
6029 {
6030 int maxent = 100000, maxsize = 100000;
6031
6032 char *filename = RlistScalarValue(finalargs);
6033 char *comment = RlistScalarValue(finalargs->next);
6034 int groupsize = IntFromString(RlistScalarValue(finalargs->next->next));
6035
6036 if (2 > groupsize)
6037 {
6038 Log(LOG_LEVEL_WARNING, "Function %s: called with a nonsensical group size of %d, failing", fp->name, groupsize);
6039 return FnFailure();
6040 }
6041
6042 char *file_buffer = CfReadFile(filename, maxsize);
6043 if (file_buffer == NULL)
6044 {
6045 return FnFailure();
6046 }
6047
6048 file_buffer = StripPatterns(file_buffer, comment, filename);
6049
6050 Rlist *const newlist =
6051 file_buffer ? RlistFromSplitRegex(file_buffer, "\n", maxent, true) : NULL;
6052
6053 /* Slice up the list and discard everything except our slice */
6054
6055 int i = 0;
6056 bool found = false;
6057 char buffer[CF_MAXVARSIZE];
6058 buffer[0] = '\0';
6059
6060 for (const Rlist *rp = newlist; !found && rp != NULL; rp = rp->next)
6061 {
6062 const char *s = RlistScalarValue(rp);
6063 if (EmptyString(s))
6064 {
6065 continue;
6066 }
6067
6068 found = (strcmp(s, VFQNAME) == 0 || strcmp(s, VUQNAME) == 0);
6069 if (i % groupsize == 0)
6070 {
6071 strlcpy(buffer, found ? "localhost" : s, CF_MAXVARSIZE);
6072 }
6073
6074 i++;
6075 }
6076
6077 RlistDestroy(newlist);
6078 free(file_buffer);
6079
6080 if (found)
6081 {
6082 return FnReturn(buffer);
6083 }
6084
6085 return FnFailure();
6086 }
6087
6088 /*********************************************************************/
6089
FnCallPeerLeaders(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)6090 static FnCallResult FnCallPeerLeaders(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
6091 {
6092 int maxent = 100000, maxsize = 100000;
6093
6094 char *filename = RlistScalarValue(finalargs);
6095 char *comment = RlistScalarValue(finalargs->next);
6096 int groupsize = IntFromString(RlistScalarValue(finalargs->next->next));
6097
6098 if (2 > groupsize)
6099 {
6100 Log(LOG_LEVEL_WARNING, "Function %s: called with a nonsensical group size of %d, failing", fp->name, groupsize);
6101 return FnFailure();
6102 }
6103
6104 char *file_buffer = CfReadFile(filename, maxsize);
6105 if (file_buffer == NULL)
6106 {
6107 return FnFailure();
6108 }
6109
6110 file_buffer = StripPatterns(file_buffer, comment, filename);
6111
6112 Rlist *const newlist =
6113 file_buffer ? RlistFromSplitRegex(file_buffer, "\n", maxent, true) : NULL;
6114
6115 /* Slice up the list and discard everything except our slice */
6116
6117 int i = 0;
6118 Rlist *pruned = NULL;
6119
6120 for (const Rlist *rp = newlist; rp != NULL; rp = rp->next)
6121 {
6122 const char *s = RlistScalarValue(rp);
6123 if (EmptyString(s))
6124 {
6125 continue;
6126 }
6127
6128 if (i % groupsize == 0)
6129 {
6130 if (strcmp(s, VFQNAME) == 0 || strcmp(s, VUQNAME) == 0)
6131 {
6132 RlistPrepend(&pruned, "localhost", RVAL_TYPE_SCALAR);
6133 }
6134 else
6135 {
6136 RlistPrepend(&pruned, s, RVAL_TYPE_SCALAR);
6137 }
6138 }
6139
6140 i++;
6141 }
6142
6143 RlistDestroy(newlist);
6144 free(file_buffer);
6145
6146 RlistReverse(&pruned);
6147 return (FnCallResult) { FNCALL_SUCCESS, { pruned, RVAL_TYPE_LIST } };
6148
6149 }
6150
6151 /*********************************************************************/
6152
FnCallRegCmp(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)6153 static FnCallResult FnCallRegCmp(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
6154 {
6155 char *argv0 = RlistScalarValue(finalargs);
6156 char *argv1 = RlistScalarValue(finalargs->next);
6157
6158 return FnReturnContext(StringMatchFull(argv0, argv1));
6159 }
6160
6161 /*********************************************************************/
6162
FnCallRegReplace(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)6163 static FnCallResult FnCallRegReplace(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
6164 {
6165 const char *data = RlistScalarValue(finalargs);
6166 const char *regex = RlistScalarValue(finalargs->next);
6167 const char *replacement = RlistScalarValue(finalargs->next->next);
6168 const char *options = RlistScalarValue(finalargs->next->next->next);
6169
6170 Buffer *rewrite = BufferNewFrom(data, strlen(data));
6171 const char* error = BufferSearchAndReplace(rewrite, regex, replacement, options);
6172
6173 if (error)
6174 {
6175 BufferDestroy(rewrite);
6176 Log(LOG_LEVEL_ERR, "%s: couldn't use regex '%s', replacement '%s', and options '%s': error=%s",
6177 fp->name, regex, replacement, options, error);
6178 return FnFailure();
6179 }
6180
6181 return FnReturnBuffer(rewrite);
6182 }
6183
6184 /*********************************************************************/
6185
FnCallRegExtract(EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)6186 static FnCallResult FnCallRegExtract(EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
6187 {
6188 const bool container_mode = strcmp(fp->name, "data_regextract") == 0;
6189
6190 const char *regex = RlistScalarValue(finalargs);
6191 const char *data = RlistScalarValue(finalargs->next);
6192 char *arrayname = NULL;
6193
6194 if (!container_mode)
6195 {
6196 arrayname = xstrdup(RlistScalarValue(finalargs->next->next));
6197
6198 if (!IsQualifiedVariable(arrayname))
6199 {
6200 if (fp->caller)
6201 {
6202 VarRef *ref = VarRefParseFromBundle(arrayname, PromiseGetBundle(fp->caller));
6203 free(arrayname);
6204 arrayname = VarRefToString(ref, true);
6205 VarRefDestroy(ref);
6206 }
6207 else
6208 {
6209 Log(LOG_LEVEL_ERR, "Function '%s' called with an unqualifed array reference '%s', "
6210 "and the reference could not be automatically qualified as the function was not called from a promise.",
6211 fp->name, arrayname);
6212 free(arrayname);
6213 return FnFailure();
6214 }
6215 }
6216 }
6217
6218 Seq *s = StringMatchCaptures(regex, data, true);
6219
6220 if (!s || SeqLength(s) == 0)
6221 {
6222 SeqDestroy(s);
6223 free(arrayname);
6224 return container_mode ? FnFailure() : FnReturnContext(false);
6225 }
6226
6227 JsonElement *json = NULL;
6228
6229 if (container_mode)
6230 {
6231 json = JsonObjectCreate(SeqLength(s)/2);
6232 }
6233
6234 for (size_t i = 0; i < SeqLength(s); i+=2)
6235 {
6236 Buffer *key = SeqAt(s, i);
6237 Buffer *value = SeqAt(s, i+1);
6238
6239
6240 if (container_mode)
6241 {
6242 JsonObjectAppendString(json, BufferData(key), BufferData(value));
6243 }
6244 else
6245 {
6246 char var[CF_MAXVARSIZE] = "";
6247 snprintf(var, CF_MAXVARSIZE - 1, "%s[%s]", arrayname, BufferData(key));
6248 VarRef *new_ref = VarRefParse(var);
6249 EvalContextVariablePut(ctx, new_ref, BufferData(value),
6250 CF_DATA_TYPE_STRING,
6251 "source=function,function=regextract");
6252 VarRefDestroy(new_ref);
6253 }
6254 }
6255
6256 free(arrayname);
6257 SeqDestroy(s);
6258
6259 if (container_mode)
6260 {
6261 return FnReturnContainerNoCopy(json);
6262 }
6263 else
6264 {
6265 return FnReturnContext(true);
6266 }
6267 }
6268
6269 /*********************************************************************/
6270
FnCallRegLine(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)6271 static FnCallResult FnCallRegLine(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
6272 {
6273 pcre *rx = CompileRegex(RlistScalarValue(finalargs));
6274 if (!rx)
6275 {
6276 return FnFailure();
6277 }
6278
6279 const char *arg_filename = RlistScalarValue(finalargs->next);
6280
6281 FILE *fin = safe_fopen(arg_filename, "rt");
6282 if (!fin)
6283 {
6284 pcre_free(rx);
6285 return FnReturnContext(false);
6286 }
6287
6288 size_t line_size = CF_BUFSIZE;
6289 char *line = xmalloc(line_size);
6290
6291 while (CfReadLine(&line, &line_size, fin) != -1)
6292 {
6293 if (StringMatchFullWithPrecompiledRegex(rx, line))
6294 {
6295 free(line);
6296 fclose(fin);
6297 pcre_free(rx);
6298 return FnReturnContext(true);
6299 }
6300 }
6301
6302 pcre_free(rx);
6303 free(line);
6304
6305 if (!feof(fin))
6306 {
6307 Log(LOG_LEVEL_ERR, "In function '%s', error reading from file. (getline: %s)",
6308 fp->name, GetErrorStr());
6309 }
6310
6311 fclose(fin);
6312 return FnReturnContext(false);
6313 }
6314
6315 /*********************************************************************/
6316
FnCallIsLessGreaterThan(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)6317 static FnCallResult FnCallIsLessGreaterThan(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
6318 {
6319 char *argv0 = RlistScalarValue(finalargs);
6320 char *argv1 = RlistScalarValue(finalargs->next);
6321 bool rising = (strcmp(fp->name, "isgreaterthan") == 0);
6322
6323 if (IsRealNumber(argv0) && IsRealNumber(argv1))
6324 {
6325 double a = 0, b = 0;
6326 if (!DoubleFromString(argv0, &a) ||
6327 !DoubleFromString(argv1, &b))
6328 {
6329 return FnFailure();
6330 }
6331
6332 if (rising)
6333 {
6334 return FnReturnContext(a > b);
6335 }
6336 else
6337 {
6338 return FnReturnContext(a < b);
6339 }
6340 }
6341
6342 if (rising)
6343 {
6344 return FnReturnContext(strcmp(argv0, argv1) > 0);
6345 }
6346 else
6347 {
6348 return FnReturnContext(strcmp(argv0, argv1) < 0);
6349 }
6350 }
6351
6352 /*********************************************************************/
6353
FnCallIRange(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)6354 static FnCallResult FnCallIRange(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
6355 {
6356 long from = IntFromString(RlistScalarValue(finalargs));
6357 long to = IntFromString(RlistScalarValue(finalargs->next));
6358
6359 if (from == CF_NOINT || to == CF_NOINT)
6360 {
6361 return FnFailure();
6362 }
6363
6364 if (from > to)
6365 {
6366 long tmp = to;
6367 to = from;
6368 from = tmp;
6369 }
6370
6371 return FnReturnF("%ld,%ld", from, to);
6372 }
6373
6374 /*********************************************************************/
6375
FnCallRRange(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)6376 static FnCallResult FnCallRRange(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
6377 {
6378 double from = 0;
6379 if (!DoubleFromString(RlistScalarValue(finalargs), &from))
6380 {
6381 Log(LOG_LEVEL_ERR,
6382 "Function rrange, error reading assumed real value '%s'",
6383 RlistScalarValue(finalargs));
6384 return FnFailure();
6385 }
6386
6387 double to = 0;
6388 if (!DoubleFromString(RlistScalarValue(finalargs), &to))
6389 {
6390 Log(LOG_LEVEL_ERR,
6391 "Function rrange, error reading assumed real value '%s'",
6392 RlistScalarValue(finalargs->next));
6393 return FnFailure();
6394 }
6395
6396 if (from > to)
6397 {
6398 int tmp = to;
6399 to = from;
6400 from = tmp;
6401 }
6402
6403 return FnReturnF("%lf,%lf", from, to);
6404 }
6405
FnCallReverse(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)6406 static FnCallResult FnCallReverse(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
6407 {
6408 const char *name_str = RlistScalarValueSafe(finalargs);
6409
6410 // try to load directly
6411 bool allocated = false;
6412 JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs, false, &allocated);
6413
6414 // we failed to produce a valid JsonElement, so give up
6415 if (json == NULL)
6416 {
6417 return FnFailure();
6418 }
6419 else if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER)
6420 {
6421 Log(LOG_LEVEL_VERBOSE, "Function '%s', argument '%s' was not a data container or list",
6422 fp->name, name_str);
6423 JsonDestroyMaybe(json, allocated);
6424 return FnFailure();
6425 }
6426
6427 Rlist *returnlist = NULL;
6428
6429 JsonIterator iter = JsonIteratorInit(json);
6430 const JsonElement *e;
6431 while ((e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true)))
6432 {
6433 RlistPrepend(&returnlist, JsonPrimitiveGetAsString(e), RVAL_TYPE_SCALAR);
6434 }
6435 JsonDestroyMaybe(json, allocated);
6436
6437 return (FnCallResult) { FNCALL_SUCCESS, (Rval) { returnlist, RVAL_TYPE_LIST } };
6438 }
6439
6440
6441 /* Convert y/m/d/h/m/s 6-tuple */
FnArgsToTm(const Rlist * rp)6442 static struct tm FnArgsToTm(const Rlist *rp)
6443 {
6444 struct tm ret = { .tm_isdst = -1 };
6445 /* .tm_year stores year - 1900 */
6446 ret.tm_year = IntFromString(RlistScalarValue(rp)) - 1900;
6447 rp = rp->next;
6448 /* .tm_mon counts from Jan = 0 to Dec = 11 */
6449 ret.tm_mon = IntFromString(RlistScalarValue(rp));
6450 rp = rp->next;
6451 /* .tm_mday is day of month, 1 to 31, but we use 0 through 30 (for now) */
6452 ret.tm_mday = IntFromString(RlistScalarValue(rp)) + 1;
6453 rp = rp->next;
6454 ret.tm_hour = IntFromString(RlistScalarValue(rp));
6455 rp = rp->next;
6456 ret.tm_min = IntFromString(RlistScalarValue(rp));
6457 rp = rp->next;
6458 ret.tm_sec = IntFromString(RlistScalarValue(rp));
6459 return ret;
6460 }
6461
FnCallOn(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)6462 static FnCallResult FnCallOn(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
6463 {
6464 struct tm tmv = FnArgsToTm(finalargs);
6465 time_t cftime = mktime(&tmv);
6466
6467 if (cftime == -1)
6468 {
6469 Log(LOG_LEVEL_INFO, "Illegal time value");
6470 }
6471
6472 return FnReturnF("%jd", (intmax_t) cftime);
6473 }
6474
6475 /*********************************************************************/
6476
FnCallOr(EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)6477 static FnCallResult FnCallOr(EvalContext *ctx,
6478 ARG_UNUSED const Policy *policy,
6479 ARG_UNUSED const FnCall *fp,
6480 const Rlist *finalargs)
6481 {
6482 char id[CF_BUFSIZE];
6483
6484 snprintf(id, CF_BUFSIZE, "built-in FnCall or-arg");
6485
6486 /* We need to check all the arguments, ArgTemplate does not check varadic functions */
6487 for (const Rlist *arg = finalargs; arg; arg = arg->next)
6488 {
6489 SyntaxTypeMatch err = CheckConstraintTypeMatch(id, arg->val, CF_DATA_TYPE_STRING, "", 1);
6490 if (err != SYNTAX_TYPE_MATCH_OK && err != SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED)
6491 {
6492 FatalError(ctx, "in %s: %s", id, SyntaxTypeMatchToString(err));
6493 }
6494 }
6495
6496 for (const Rlist *arg = finalargs; arg; arg = arg->next)
6497 {
6498 if (IsDefinedClass(ctx, RlistScalarValue(arg)))
6499 {
6500 return FnReturnContext(true);
6501 }
6502 }
6503
6504 return FnReturnContext(false);
6505 }
6506
6507 /*********************************************************************/
6508
FnCallLaterThan(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)6509 static FnCallResult FnCallLaterThan(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
6510 {
6511 time_t now = time(NULL);
6512 struct tm tmv = FnArgsToTm(finalargs);
6513 /* Adjust to 1-based counting (input) for month and day of month
6514 * (0-based in mktime): */
6515 tmv.tm_mon--;
6516 tmv.tm_mday--;
6517 time_t cftime = mktime(&tmv);
6518
6519 if (cftime == -1)
6520 {
6521 Log(LOG_LEVEL_INFO, "Illegal time value");
6522 }
6523
6524 return FnReturnContext(now > cftime);
6525 }
6526
FnCallAgoDate(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)6527 static FnCallResult FnCallAgoDate(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
6528 {
6529 struct tm ago = FnArgsToTm(finalargs);
6530 time_t now = time(NULL);
6531 struct tm t;
6532 localtime_r(&now, &t);
6533
6534 t.tm_year -= ago.tm_year + 1900; /* tm.tm_year stores year - 1900 */
6535 t.tm_mon -= ago.tm_mon;
6536 t.tm_mday -= ago.tm_mday - 1;
6537 t.tm_hour -= ago.tm_hour;
6538 t.tm_min -= ago.tm_min;
6539 t.tm_sec -= ago.tm_sec;
6540
6541 time_t cftime = mktime(&t);
6542 if (cftime < 0)
6543 {
6544 return FnReturn("0");
6545 }
6546
6547 return FnReturnF("%jd", (intmax_t) cftime);
6548 }
6549
6550 /*********************************************************************/
6551
FnCallAccumulatedDate(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)6552 static FnCallResult FnCallAccumulatedDate(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
6553 {
6554 struct tm tmv = FnArgsToTm(finalargs);
6555
6556 intmax_t cftime = 0;
6557 cftime = 0;
6558 cftime += tmv.tm_sec;
6559 cftime += (intmax_t) tmv.tm_min * 60;
6560 cftime += (intmax_t) tmv.tm_hour * 3600;
6561 cftime += (intmax_t) (tmv.tm_mday - 1) * 24 * 3600;
6562 cftime += (intmax_t) tmv.tm_mon * 30 * 24 * 3600;
6563 cftime += (intmax_t) (tmv.tm_year + 1900) * 365 * 24 * 3600;
6564
6565 return FnReturnF("%jd", (intmax_t) cftime);
6566 }
6567
6568 /*********************************************************************/
6569
FnCallNot(EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)6570 static FnCallResult FnCallNot(EvalContext *ctx,
6571 ARG_UNUSED const Policy *policy,
6572 ARG_UNUSED const FnCall *fp,
6573 const Rlist *finalargs)
6574 {
6575 return FnReturnContext(!IsDefinedClass(ctx, RlistScalarValue(finalargs)));
6576 }
6577
6578 /*********************************************************************/
6579
FnCallNow(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,ARG_UNUSED const Rlist * finalargs)6580 static FnCallResult FnCallNow(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, ARG_UNUSED const Rlist *finalargs)
6581 {
6582 return FnReturnF("%jd", (intmax_t)CFSTARTTIME);
6583 }
6584
6585 /*********************************************************************/
6586
6587 #ifdef __sun /* Lacks %P and */
6588 #define STRFTIME_F_HACK
6589 #define STRFTIME_s_HACK
6590 #define STRFTIME_R_HACK
6591 #endif /* http://www.unix.com/man-page/opensolaris/3c/strftime/ */
6592
6593 #ifdef __hpux /* Unknown gaps, aside from: */
6594 #define STRFTIME_F_HACK
6595 #endif
6596
6597 #ifdef _WIN32 /* Has non-standard %z, lacks %[CDeGghklnPrRtTuV] and: */
6598 #define STRFTIME_F_HACK
6599 #define STRFTIME_R_HACK
6600 #define STRFTIME_s_HACK
6601 #endif /* http://msdn.microsoft.com/en-us/library/fe06s4ak.aspx */
6602
PortablyFormatTime(char * buffer,size_t bufsiz,const char * format,ARG_UNUSED time_t when,const struct tm * tm)6603 bool PortablyFormatTime(char *buffer, size_t bufsiz,
6604 const char *format,
6605 #ifndef STRFTIME_s_HACK
6606 ARG_UNUSED
6607 #endif
6608 time_t when,
6609 const struct tm *tm)
6610 {
6611 /* TODO: might be better done in a libcompat wrapper.
6612 *
6613 * The following GNU extensions may be worth adding at some point;
6614 * see individual platforms for lists of which they lack.
6615 *
6616 * %C (century)
6617 * %D => %m/%d/%y
6618 * %e: as %d but s/^0/ /
6619 * %G: like %Y but frobbed for ISO week numbers
6620 * %g: last two digits of %G
6621 * %h => %b
6622 * %k: as %H but s/^0/ /
6623 * %l: as %I but s/^0/ /
6624 * %n => \n
6625 * %P: as %p but lower-cased
6626 * %r => %I:%M:%S %p
6627 * %s: seconds since epoch
6628 * %t => \t
6629 * %T => %H:%M:%S
6630 * %u: dow, {1: Mon, ..., Sun: 7}
6631 * %V: ISO week number within year %G
6632 *
6633 * The "=>" ones can all be done by extending expansion[], below;
6634 * the rest would require actually implementing GNU strftime()
6635 * properly.
6636 */
6637
6638 #ifdef STRFTIME_s_HACK /* %s: seconds since epoch */
6639 char epoch[PRINTSIZE(when)];
6640 xsnprintf(epoch, sizeof(epoch), "%jd", (intmax_t) when);
6641 #endif /* STRFTIME_s_HACK */
6642
6643 typedef char * SearchReplacePair[2];
6644 SearchReplacePair expansion[] =
6645 {
6646 /* Each pair is { search, replace }. */
6647 #ifdef STRFTIME_F_HACK
6648 { "%F", "%Y-%m-%d" },
6649 #endif
6650 #ifdef STRFTIME_R_HACK /* %R => %H:%M:%S */
6651 { "%R", "%H:%M:%S" },
6652 #endif
6653 #ifdef STRFTIME_s_HACK
6654 { "%s", epoch },
6655 #endif
6656
6657 /* Order as in GNU strftime's man page. */
6658 { NULL, NULL }
6659 };
6660
6661 char *delenda = NULL; /* in need of destruction */
6662 /* No-op when no STRFTIME_*_HACK were defined. */
6663 for (size_t i = 0; expansion[i][0]; i++)
6664 {
6665 char *tmp = SearchAndReplace(format, expansion[i][0], expansion[i][1]);
6666 free(delenda);
6667 format = delenda = tmp;
6668 }
6669
6670 size_t ans = strftime(buffer, bufsiz, format, tm);
6671 free(delenda);
6672 return ans > 0;
6673 }
6674 #undef STRFTIME_F_HACK
6675 #undef STRFTIME_R_HACK
6676 #undef STRFTIME_s_HACK
6677
6678 /*********************************************************************/
6679
FnCallStrftime(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)6680 static FnCallResult FnCallStrftime(ARG_UNUSED EvalContext *ctx,
6681 ARG_UNUSED const Policy *policy,
6682 const FnCall *fp,
6683 const Rlist *finalargs)
6684 {
6685 /* begin fn-specific content */
6686
6687 char *mode = RlistScalarValue(finalargs);
6688 char *format_string = RlistScalarValue(finalargs->next);
6689 // this will be a problem on 32-bit systems...
6690 const time_t when = IntFromString(RlistScalarValue(finalargs->next->next));
6691
6692 struct tm tm_value;
6693 struct tm *tm_pointer;
6694
6695 if (strcmp("gmtime", mode) == 0)
6696 {
6697 tm_pointer = gmtime_r(&when, &tm_value);
6698 }
6699 else
6700 {
6701 tm_pointer = localtime_r(&when, &tm_value);
6702 }
6703
6704 char buffer[CF_BUFSIZE];
6705 if (tm_pointer == NULL)
6706 {
6707 Log(LOG_LEVEL_WARNING,
6708 "Function %s, the given time stamp '%ld' was invalid. (strftime: %s)",
6709 fp->name, when, GetErrorStr());
6710 }
6711 else if (PortablyFormatTime(buffer, sizeof(buffer),
6712 format_string, when, tm_pointer))
6713 {
6714 return FnReturn(buffer);
6715 }
6716
6717 return FnFailure();
6718 }
6719
6720 /*********************************************************************/
6721
FnCallEval(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)6722 static FnCallResult FnCallEval(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
6723 {
6724 if (finalargs == NULL)
6725 {
6726 FatalError(ctx, "in built-in FnCall %s: missing first argument, an evaluation input", fp->name);
6727 }
6728
6729 const char *input = RlistScalarValue(finalargs);
6730
6731 const char *type = NULL;
6732
6733 if (finalargs->next)
6734 {
6735 type = RlistScalarValue(finalargs->next);
6736 }
6737 else
6738 {
6739 type = "math";
6740 }
6741
6742 /* Third argument can currently only be "infix". */
6743 // So we completely ignore finalargs->next->next
6744
6745 const bool context_mode = (strcmp(type, "class") == 0);
6746
6747 char failure[CF_BUFSIZE];
6748 memset(failure, 0, sizeof(failure));
6749
6750 double result = EvaluateMathInfix(ctx, input, failure);
6751 if (context_mode)
6752 {
6753 // see CLOSE_ENOUGH in math.peg
6754 return FnReturnContext(strlen(failure) == 0 &&
6755 !(result < 0.00000000000000001 &&
6756 result > -0.00000000000000001));
6757 }
6758
6759 if (strlen(failure) > 0)
6760 {
6761 Log(LOG_LEVEL_INFO, "%s error: %s (input '%s')", fp->name, failure, input);
6762 return FnReturn(""); /* TODO: why not FnFailure() ? */
6763 }
6764
6765 return FnReturnF("%lf", result);
6766 }
6767
6768 /*********************************************************************/
6769 /* Read functions */
6770 /*********************************************************************/
6771
FnCallReadFile(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)6772 static FnCallResult FnCallReadFile(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
6773 {
6774 char *filename = RlistScalarValue(finalargs);
6775 const Rlist *next = finalargs->next; // max_size argument, default to inf:
6776 long maxsize = next ? IntFromString(RlistScalarValue(next)) : IntFromString("inf");
6777
6778 if (maxsize == CF_INFINITY) /* "inf" in the policy */
6779 {
6780 maxsize = 0;
6781 }
6782
6783 if (maxsize < 0)
6784 {
6785 Log(LOG_LEVEL_ERR, "%s: requested max size %li is less than 0", fp->name, maxsize);
6786 return FnFailure();
6787 }
6788
6789 // Read once to validate structure of file in itemlist
6790 char *contents = CfReadFile(filename, maxsize);
6791 if (contents)
6792 {
6793 return FnReturnNoCopy(contents);
6794 }
6795
6796 Log(LOG_LEVEL_VERBOSE, "Function '%s' failed to read file: %s",
6797 fp->name, filename);
6798 return FnFailure();
6799 }
6800
6801 /*********************************************************************/
6802
ReadList(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const FnCall * fp,const Rlist * finalargs,DataType type)6803 static FnCallResult ReadList(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const FnCall *fp, const Rlist *finalargs, DataType type)
6804 {
6805 const char *filename = RlistScalarValue(finalargs);
6806 const char *comment = RlistScalarValue(finalargs->next);
6807 const char *split = RlistScalarValue(finalargs->next->next);
6808 const int maxent = IntFromString(RlistScalarValue(finalargs->next->next->next));
6809 const int maxsize = IntFromString(RlistScalarValue(finalargs->next->next->next->next));
6810
6811 char *file_buffer = CfReadFile(filename, maxsize);
6812 if (!file_buffer)
6813 {
6814 Log(LOG_LEVEL_VERBOSE, "Function '%s' failed to read file: %s",
6815 fp->name, filename);
6816 return FnFailure();
6817 }
6818
6819 bool blanks = false;
6820 Rlist *newlist = NULL;
6821 file_buffer = StripPatterns(file_buffer, comment, filename);
6822 if (!file_buffer)
6823 {
6824 return (FnCallResult) { FNCALL_SUCCESS, { NULL, RVAL_TYPE_LIST } };
6825 }
6826 else
6827 {
6828 newlist = RlistFromSplitRegex(file_buffer, split, maxent, blanks);
6829 }
6830
6831 bool noerrors = true;
6832
6833 switch (type)
6834 {
6835 case CF_DATA_TYPE_STRING:
6836 break;
6837
6838 case CF_DATA_TYPE_INT:
6839 for (Rlist *rp = newlist; rp != NULL; rp = rp->next)
6840 {
6841 if (IntFromString(RlistScalarValue(rp)) == CF_NOINT)
6842 {
6843 Log(LOG_LEVEL_ERR, "Presumed int value '%s' read from file '%s' has no recognizable value",
6844 RlistScalarValue(rp), filename);
6845 noerrors = false;
6846 }
6847 }
6848 break;
6849
6850 case CF_DATA_TYPE_REAL:
6851 for (Rlist *rp = newlist; rp != NULL; rp = rp->next)
6852 {
6853 double real_value = 0;
6854 if (!DoubleFromString(RlistScalarValue(rp), &real_value))
6855 {
6856 Log(LOG_LEVEL_ERR, "Presumed real value '%s' read from file '%s' has no recognizable value",
6857 RlistScalarValue(rp), filename);
6858 noerrors = false;
6859 }
6860 }
6861 break;
6862
6863 default:
6864 ProgrammingError("Unhandled type in switch: %d", type);
6865 }
6866
6867 free(file_buffer);
6868
6869 if (noerrors)
6870 {
6871 return (FnCallResult) { FNCALL_SUCCESS, { newlist, RVAL_TYPE_LIST } };
6872 }
6873
6874 RlistDestroy(newlist);
6875 return FnFailure();
6876 }
6877
FnCallReadStringList(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * args)6878 static FnCallResult FnCallReadStringList(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *args)
6879 {
6880 return ReadList(ctx, fp, args, CF_DATA_TYPE_STRING);
6881 }
6882
FnCallReadIntList(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * args)6883 static FnCallResult FnCallReadIntList(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *args)
6884 {
6885 return ReadList(ctx, fp, args, CF_DATA_TYPE_INT);
6886 }
6887
FnCallReadRealList(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * args)6888 static FnCallResult FnCallReadRealList(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *args)
6889 {
6890 return ReadList(ctx, fp, args, CF_DATA_TYPE_REAL);
6891 }
6892
ReadDataGeneric(const char * const fname,const char * const input_path,const size_t size_max,const DataFileType requested_mode)6893 static FnCallResult ReadDataGeneric(const char *const fname,
6894 const char *const input_path,
6895 const size_t size_max,
6896 const DataFileType requested_mode)
6897 {
6898 assert(fname != NULL);
6899 assert(input_path != NULL);
6900
6901 JsonElement *json = JsonReadDataFile(fname, input_path, requested_mode, size_max);
6902 if (json == NULL)
6903 {
6904 return FnFailure();
6905 }
6906
6907 return FnReturnContainerNoCopy(json);
6908 }
6909
FnCallReadData(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * args)6910 static FnCallResult FnCallReadData(ARG_UNUSED EvalContext *ctx,
6911 ARG_UNUSED const Policy *policy,
6912 const FnCall *fp,
6913 const Rlist *args)
6914 {
6915 assert(fp != NULL);
6916 if (args == NULL)
6917 {
6918 Log(LOG_LEVEL_ERR, "Function '%s' requires at least one argument", fp->name);
6919 return FnFailure();
6920 }
6921
6922 const char *input_path = RlistScalarValue(args);
6923 const char *const mode_string = RlistScalarValue(args->next);
6924 DataFileType requested_mode = DATAFILETYPE_UNKNOWN;
6925 if (StringEqual("auto", mode_string))
6926 {
6927 requested_mode = GetDataFileTypeFromSuffix(input_path);
6928 Log(LOG_LEVEL_VERBOSE,
6929 "%s: automatically selected data type %s from filename %s",
6930 fp->name, DataFileTypeToString(requested_mode), input_path);
6931 }
6932 else
6933 {
6934 requested_mode = GetDataFileTypeFromString(mode_string);
6935 }
6936
6937 return ReadDataGeneric(fp->name, input_path, CF_INFINITY, requested_mode);
6938 }
6939
ReadGenericDataType(const FnCall * fp,const Rlist * args,const DataFileType requested_mode)6940 static FnCallResult ReadGenericDataType(const FnCall *fp,
6941 const Rlist *args,
6942 const DataFileType requested_mode)
6943 {
6944 assert(fp != NULL);
6945 if (args == NULL)
6946 {
6947 Log(LOG_LEVEL_ERR,
6948 "Function '%s' requires at least one argument",
6949 fp->name);
6950 return FnFailure();
6951 }
6952
6953 const char *const input_path = RlistScalarValue(args);
6954 size_t size_max = args->next ?
6955 IntFromString(RlistScalarValue(args->next)) :
6956 CF_INFINITY;
6957 return ReadDataGeneric(fp->name, input_path, size_max, requested_mode);
6958 }
6959
FnCallReadCsv(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * args)6960 static FnCallResult FnCallReadCsv(ARG_UNUSED EvalContext *ctx,
6961 ARG_UNUSED const Policy *policy,
6962 const FnCall *fp,
6963 const Rlist *args)
6964 {
6965 return ReadGenericDataType(fp, args, DATAFILETYPE_CSV);
6966 }
6967
FnCallReadEnvFile(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * args)6968 static FnCallResult FnCallReadEnvFile(ARG_UNUSED EvalContext *ctx,
6969 ARG_UNUSED const Policy *policy,
6970 const FnCall *fp,
6971 const Rlist *args)
6972 {
6973 return ReadGenericDataType(fp, args, DATAFILETYPE_ENV);
6974 }
6975
FnCallReadYaml(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * args)6976 static FnCallResult FnCallReadYaml(ARG_UNUSED EvalContext *ctx,
6977 ARG_UNUSED const Policy *policy,
6978 const FnCall *fp,
6979 const Rlist *args)
6980 {
6981 return ReadGenericDataType(fp, args, DATAFILETYPE_YAML);
6982 }
6983
FnCallReadJson(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * args)6984 static FnCallResult FnCallReadJson(ARG_UNUSED EvalContext *ctx,
6985 ARG_UNUSED const Policy *policy,
6986 const FnCall *fp,
6987 const Rlist *args)
6988 {
6989 return ReadGenericDataType(fp, args, DATAFILETYPE_JSON);
6990 }
6991
ValidateDataGeneric(const char * const fname,const char * data,const DataFileType requested_mode)6992 static FnCallResult ValidateDataGeneric(const char *const fname,
6993 const char *data,
6994 const DataFileType requested_mode)
6995 {
6996 assert(data != NULL);
6997 if (requested_mode != DATAFILETYPE_JSON)
6998 {
6999 Log(LOG_LEVEL_ERR,
7000 "%s: Data type %s is not supported by this function",
7001 fname, DataFileTypeToString(requested_mode));
7002 return FnFailure();
7003 }
7004
7005 JsonElement *json = NULL;
7006 JsonParseError err = JsonParse(&data, &json);
7007 if (err != JSON_PARSE_OK)
7008 {
7009 Log(LOG_LEVEL_VERBOSE, "%s: %s", fname, JsonParseErrorToString(err));
7010 }
7011
7012 FnCallResult ret = FnReturnContext(json != NULL);
7013 JsonDestroy(json);
7014 return ret;
7015 }
7016
FnCallValidData(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * args)7017 static FnCallResult FnCallValidData(ARG_UNUSED EvalContext *ctx,
7018 ARG_UNUSED const Policy *policy,
7019 const FnCall *fp,
7020 const Rlist *args)
7021 {
7022 assert(fp != NULL);
7023 if (args == NULL || args->next == NULL)
7024 {
7025 Log(LOG_LEVEL_ERR, "Function '%s' requires two arguments", fp->name);
7026 return FnFailure();
7027 }
7028
7029 const char *data = RlistScalarValue(args);
7030 const char *const mode_string = RlistScalarValue(args->next);
7031 DataFileType requested_mode = GetDataFileTypeFromString(mode_string);
7032
7033 return ValidateDataGeneric(fp->name, data, requested_mode);
7034 }
7035
FnCallValidJson(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * args)7036 static FnCallResult FnCallValidJson(ARG_UNUSED EvalContext *ctx,
7037 ARG_UNUSED const Policy *policy,
7038 const FnCall *fp,
7039 const Rlist *args)
7040 {
7041 assert(fp != NULL);
7042 if (args == NULL)
7043 {
7044 Log(LOG_LEVEL_ERR, "Function '%s' requires one argument", fp->name);
7045 return FnFailure();
7046 }
7047
7048 const char *data = RlistScalarValue(args);
7049 return ValidateDataGeneric(fp->name, data, DATAFILETYPE_JSON);
7050 }
7051
FnCallReadModuleProtocol(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * args)7052 static FnCallResult FnCallReadModuleProtocol(
7053 ARG_UNUSED EvalContext *ctx,
7054 ARG_UNUSED const Policy *policy,
7055 const FnCall *fp,
7056 const Rlist *args)
7057 {
7058 assert(fp != NULL);
7059
7060 if (args == NULL)
7061 {
7062 Log(LOG_LEVEL_ERR, "Function '%s' requires at least one argument", fp->name);
7063 return FnFailure();
7064 }
7065
7066 const char *input_path = RlistScalarValue(args);
7067
7068 char module_context[CF_BUFSIZE] = {0};
7069
7070 FILE *file = safe_fopen(input_path, "rt");
7071 if (file == NULL)
7072 {
7073 return FnReturnContext(false);
7074 }
7075
7076 StringSet *module_tags = StringSetNew();
7077 long persistence = 0;
7078
7079 size_t line_size = CF_BUFSIZE;
7080 char *line = xmalloc(line_size);
7081
7082 bool success = true;
7083 for (;;)
7084 {
7085 const ssize_t res = CfReadLine(&line, &line_size, file);
7086 if (res == -1)
7087 {
7088 if (!feof(file))
7089 {
7090 Log(LOG_LEVEL_ERR, "Unable to read from file '%s'. (fread: %s)", input_path, GetErrorStr());
7091 success = false;
7092 }
7093 break;
7094 }
7095
7096 ModuleProtocol(ctx, input_path, line, false, module_context, sizeof(module_context), module_tags, &persistence);
7097 }
7098
7099 StringSetDestroy(module_tags);
7100 free(line);
7101 fclose(file);
7102
7103 return FnReturnContext(success);
7104 }
7105
JsonPrimitiveComparator(JsonElement const * left_obj,JsonElement const * right_obj,void * user_data)7106 static int JsonPrimitiveComparator(JsonElement const *left_obj,
7107 JsonElement const *right_obj,
7108 void *user_data)
7109 {
7110 size_t const index = *(size_t *)user_data;
7111
7112 char const *left = JsonPrimitiveGetAsString(JsonAt(left_obj, index));
7113 char const *right = JsonPrimitiveGetAsString(JsonAt(right_obj, index));
7114 return StringSafeCompare(left, right);
7115 }
7116
FnCallClassFilterCsv(EvalContext * ctx,ARG_UNUSED Policy const * policy,FnCall const * fp,Rlist const * args)7117 static FnCallResult FnCallClassFilterCsv(EvalContext *ctx,
7118 ARG_UNUSED Policy const *policy,
7119 FnCall const *fp,
7120 Rlist const *args)
7121 {
7122 if (args == NULL || args->next == NULL || args->next->next == NULL)
7123 {
7124 FatalError(ctx, "Function %s requires at least 3 arguments",
7125 fp->name);
7126 }
7127
7128 char const *path = RlistScalarValue(args);
7129 bool const has_heading = BooleanFromString(RlistScalarValue(args->next));
7130 size_t const class_index = IntFromString(RlistScalarValue(args->next->next));
7131 Rlist const *sort_arg = args->next->next->next;
7132
7133 FILE *csv_file = safe_fopen(path, "r");
7134 if (csv_file == NULL)
7135 {
7136 Log(LOG_LEVEL_ERR,
7137 "%s: Failed to read file %s: %s",
7138 fp->name, path, GetErrorStrFromCode(errno));
7139 return FnFailure();
7140 }
7141
7142 Seq *heading = NULL;
7143 JsonElement *json = JsonArrayCreate(50);
7144 char *line;
7145 size_t num_columns = 0;
7146
7147 // Current line number, for debugging
7148 size_t line_number = 0;
7149
7150 while ((line = GetCsvLineNext(csv_file)) != NULL)
7151 {
7152 ++line_number;
7153 if (line[0] == '#')
7154 {
7155 Log(LOG_LEVEL_DEBUG, "%s: Ignoring comment at line %zu",
7156 fp->name, line_number);
7157 free(line);
7158 continue;
7159 }
7160
7161 Seq *list = SeqParseCsvString(line);
7162 free(line);
7163 if (list == NULL)
7164 {
7165 Log(LOG_LEVEL_WARNING,
7166 "%s: Failed to parse line %zu, line ignored.",
7167 fp->name, line_number);
7168 continue;
7169 }
7170
7171 if (SeqLength(list) == 1 &&
7172 strlen(SeqAt(list, 0)) == 0)
7173 {
7174 Log(LOG_LEVEL_DEBUG,
7175 "%s: Found empty line at line %zu, line ignored",
7176 fp->name, line_number);
7177 SeqDestroy(list);
7178 continue;
7179 }
7180
7181 if (num_columns == 0)
7182 {
7183 num_columns = SeqLength(list);
7184 assert(num_columns != 0);
7185
7186 if (class_index >= num_columns)
7187 {
7188 Log(LOG_LEVEL_ERR,
7189 "%s: Class expression index is out of bounds. "
7190 "Row length %zu, index %zu",
7191 fp->name, num_columns, class_index);
7192 SeqDestroy(list);
7193 JsonDestroy(json);
7194 return FnFailure();
7195 }
7196 }
7197 else if (num_columns != SeqLength(list))
7198 {
7199 Log(LOG_LEVEL_WARNING,
7200 "%s: Line %zu has incorrect amount of elements, "
7201 "%zu instead of %zu. Line ignored.",
7202 fp->name, line_number, SeqLength(list), num_columns);
7203 SeqDestroy(list);
7204 continue;
7205 }
7206
7207 // First parsed line is set to be heading if has_heading is true
7208 if (has_heading && heading == NULL)
7209 {
7210 Log(LOG_LEVEL_DEBUG, "%s: Found header at line %zu",
7211 fp->name, line_number);
7212 heading = list;
7213 SeqRemove(heading, class_index);
7214 }
7215 else
7216 {
7217 if (!IsDefinedClass(ctx, SeqAt(list, class_index)))
7218 {
7219 SeqDestroy(list);
7220 continue;
7221 }
7222
7223 SeqRemove(list, class_index);
7224 JsonElement *class_container = JsonObjectCreate(num_columns);
7225
7226 size_t const num_fields = SeqLength(list);
7227 for (size_t i = 0; i < num_fields; i++)
7228 {
7229 if (has_heading)
7230 {
7231 JsonObjectAppendString(class_container,
7232 SeqAt(heading, i),
7233 SeqAt(list, i));
7234 }
7235 else
7236 {
7237 size_t const key_len = PRINTSIZE(size_t);
7238 char key[key_len];
7239 xsnprintf(key, key_len, "%zu", i);
7240
7241 JsonObjectAppendString(class_container,
7242 key,
7243 SeqAt(list, i));
7244 }
7245 }
7246
7247 JsonArrayAppendObject(json, class_container);
7248 SeqDestroy(list);
7249 }
7250 }
7251
7252 if (sort_arg != NULL)
7253 {
7254 size_t sort_index = IntFromString(RlistScalarValue(sort_arg));
7255 if (sort_index == class_index)
7256 {
7257 Log(LOG_LEVEL_WARNING,
7258 "%s: sorting column (%zu) is the same as class "
7259 "expression column (%zu). Not sorting data container.",
7260 fp->name, sort_index, class_index);
7261 }
7262 else if (sort_index >= num_columns)
7263 {
7264 Log(LOG_LEVEL_WARNING,
7265 "%s: sorting index %zu out of bounds. "
7266 "Not sorting data container.",
7267 fp->name, sort_index);
7268 }
7269 else
7270 {
7271 /* The sorting index needs to be decremented if it is higher than
7272 * the class expression index, since the class column is removed
7273 * in the data containers. */
7274 if (sort_index > class_index)
7275 {
7276 sort_index--;
7277 }
7278
7279 JsonSort(json, JsonPrimitiveComparator, &sort_index);
7280 }
7281 }
7282
7283 fclose(csv_file);
7284 if (heading != NULL)
7285 {
7286 SeqDestroy(heading);
7287 }
7288
7289 return FnReturnContainerNoCopy(json);
7290 }
7291
FnCallParseJson(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * args)7292 static FnCallResult FnCallParseJson(ARG_UNUSED EvalContext *ctx,
7293 ARG_UNUSED const Policy *policy,
7294 ARG_UNUSED const FnCall *fp,
7295 const Rlist *args)
7296 {
7297 const char *data = RlistScalarValue(args);
7298 JsonElement *json = NULL;
7299 bool yaml_mode = (strcmp(fp->name, "parseyaml") == 0);
7300 const char* data_type = yaml_mode ? "YAML" : "JSON";
7301 JsonParseError res;
7302
7303 if (yaml_mode)
7304 {
7305 res = JsonParseYamlString(&data, &json);
7306 }
7307 else
7308 {
7309 res = JsonParseWithLookup(ctx, &LookupVarRefToJson, &data, &json);
7310 }
7311
7312 if (res != JSON_PARSE_OK)
7313 {
7314 Log(LOG_LEVEL_ERR, "Error parsing %s expression '%s': %s",
7315 data_type, data, JsonParseErrorToString(res));
7316 }
7317 else if (JsonGetElementType(json) == JSON_ELEMENT_TYPE_PRIMITIVE)
7318 {
7319 Log(LOG_LEVEL_ERR, "Non-container from parsing %s expression '%s'", data_type, data);
7320 JsonDestroy(json);
7321 }
7322 else
7323 {
7324 return FnReturnContainerNoCopy(json);
7325 }
7326
7327 return FnFailure();
7328 }
7329
7330 /*********************************************************************/
7331
FnCallStoreJson(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)7332 static FnCallResult FnCallStoreJson(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
7333 {
7334 const char *name_str = RlistScalarValueSafe(finalargs);
7335
7336 // try to load directly
7337 bool allocated = false;
7338 JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs, false, &allocated);
7339
7340 // we failed to produce a valid JsonElement, so give up
7341 if (json == NULL)
7342 {
7343 return FnFailure();
7344 }
7345 else if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER)
7346 {
7347 Log(LOG_LEVEL_VERBOSE, "Function '%s', argument '%s' was not a data container or list",
7348 fp->name, name_str);
7349 JsonDestroyMaybe(json, allocated);
7350 return FnFailure();
7351 }
7352
7353 Writer *w = StringWriter();
7354
7355 JsonWrite(w, json, 0);
7356 JsonDestroyMaybe(json, allocated);
7357 Log(LOG_LEVEL_DEBUG, "%s: from data container %s, got JSON data '%s'", fp->name, name_str, StringWriterData(w));
7358
7359 return FnReturnNoCopy(StringWriterClose(w));
7360 }
7361
7362
7363 /*********************************************************************/
7364
7365 // this function is separate so other data container readers can use it
DataRead(EvalContext * ctx,const FnCall * fp,const Rlist * finalargs)7366 static FnCallResult DataRead(EvalContext *ctx, const FnCall *fp, const Rlist *finalargs)
7367 {
7368 /* 5 args: filename,comment_regex,split_regex,max number of entries,maxfilesize */
7369
7370 const char *filename = RlistScalarValue(finalargs);
7371 const char *comment = RlistScalarValue(finalargs->next);
7372 const char *split = RlistScalarValue(finalargs->next->next);
7373 int maxent = IntFromString(RlistScalarValue(finalargs->next->next->next));
7374 int maxsize = IntFromString(RlistScalarValue(finalargs->next->next->next->next));
7375
7376 bool make_array = (strcmp(fp->name, "data_readstringarrayidx") == 0);
7377 JsonElement *json = NULL;
7378
7379 // Read once to validate structure of file in itemlist
7380 char *file_buffer = CfReadFile(filename, maxsize);
7381 if (file_buffer)
7382 {
7383 file_buffer = StripPatterns(file_buffer, comment, filename);
7384
7385 if (file_buffer != NULL)
7386 {
7387 json = BuildData(ctx, file_buffer, split, maxent, make_array);
7388 }
7389 }
7390
7391 free(file_buffer);
7392
7393 if (json == NULL)
7394 {
7395 Log(LOG_LEVEL_ERR, "%s: error reading from file '%s'", fp->name, filename);
7396 return FnFailure();
7397 }
7398
7399 return FnReturnContainerNoCopy(json);
7400 }
7401
7402 /*********************************************************************/
7403
FnCallDataExpand(EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * args)7404 static FnCallResult FnCallDataExpand(EvalContext *ctx,
7405 ARG_UNUSED const Policy *policy,
7406 ARG_UNUSED const FnCall *fp,
7407 const Rlist *args)
7408 {
7409 bool allocated = false;
7410 JsonElement *json = VarNameOrInlineToJson(ctx, fp, args, false, &allocated);
7411
7412 if (json == NULL)
7413 {
7414 return FnFailure();
7415 }
7416
7417 JsonElement *expanded = JsonExpandElement(ctx, json);
7418 JsonDestroyMaybe(json, allocated);
7419
7420 return FnReturnContainerNoCopy(expanded);
7421 }
7422
7423 /*********************************************************************/
7424
FnCallDataRead(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * args)7425 static FnCallResult FnCallDataRead(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *args)
7426 {
7427 return DataRead(ctx, fp, args);
7428 }
7429
7430 /*********************************************************************/
7431
ReadArray(EvalContext * ctx,const FnCall * fp,const Rlist * finalargs,DataType type,bool int_index)7432 static FnCallResult ReadArray(EvalContext *ctx, const FnCall *fp, const Rlist *finalargs, DataType type, bool int_index)
7433 /* lval,filename,separator,comment,Max number of bytes */
7434 {
7435 if (!fp->caller)
7436 {
7437 Log(LOG_LEVEL_ERR, "Function '%s' can only be called from a promise", fp->name);
7438 return FnFailure();
7439 }
7440
7441 /* 6 args: array_lval,filename,comment_regex,split_regex,max number of entries,maxfilesize */
7442
7443 const char *array_lval = RlistScalarValue(finalargs);
7444 const char *filename = RlistScalarValue(finalargs->next);
7445 const char *comment = RlistScalarValue(finalargs->next->next);
7446 const char *split = RlistScalarValue(finalargs->next->next->next);
7447 int maxent = IntFromString(RlistScalarValue(finalargs->next->next->next->next));
7448 int maxsize = IntFromString(RlistScalarValue(finalargs->next->next->next->next->next));
7449
7450 // Read once to validate structure of file in itemlist
7451 char *file_buffer = CfReadFile(filename, maxsize);
7452 int entries = 0;
7453 if (file_buffer)
7454 {
7455 file_buffer = StripPatterns(file_buffer, comment, filename);
7456
7457 if (file_buffer)
7458 {
7459 entries = BuildLineArray(ctx, PromiseGetBundle(fp->caller), array_lval, file_buffer, split, maxent, type, int_index);
7460 }
7461 }
7462
7463 switch (type)
7464 {
7465 case CF_DATA_TYPE_STRING:
7466 case CF_DATA_TYPE_INT:
7467 case CF_DATA_TYPE_REAL:
7468 break;
7469
7470 default:
7471 ProgrammingError("Unhandled type in switch: %d", type);
7472 }
7473
7474 free(file_buffer);
7475
7476 return FnReturnF("%d", entries);
7477 }
7478
7479 /*********************************************************************/
7480
FnCallReadStringArray(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * args)7481 static FnCallResult FnCallReadStringArray(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *args)
7482 {
7483 return ReadArray(ctx, fp, args, CF_DATA_TYPE_STRING, false);
7484 }
7485
7486 /*********************************************************************/
7487
FnCallReadStringArrayIndex(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * args)7488 static FnCallResult FnCallReadStringArrayIndex(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *args)
7489 {
7490 return ReadArray(ctx, fp, args, CF_DATA_TYPE_STRING, true);
7491 }
7492
7493 /*********************************************************************/
7494
FnCallReadIntArray(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * args)7495 static FnCallResult FnCallReadIntArray(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *args)
7496 {
7497 return ReadArray(ctx, fp, args, CF_DATA_TYPE_INT, false);
7498 }
7499
7500 /*********************************************************************/
7501
FnCallReadRealArray(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * args)7502 static FnCallResult FnCallReadRealArray(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *args)
7503 {
7504 return ReadArray(ctx, fp, args, CF_DATA_TYPE_REAL, false);
7505 }
7506
7507 /*********************************************************************/
7508
ParseArray(EvalContext * ctx,const FnCall * fp,const Rlist * finalargs,DataType type,int intIndex)7509 static FnCallResult ParseArray(EvalContext *ctx, const FnCall *fp, const Rlist *finalargs, DataType type, int intIndex)
7510 /* lval,filename,separator,comment,Max number of bytes */
7511 {
7512 if (!fp->caller)
7513 {
7514 Log(LOG_LEVEL_ERR, "Function '%s' can only be called from a promise", fp->name);
7515 return FnFailure();
7516 }
7517
7518 /* 6 args: array_lval,instring,comment_regex,split_regex,max number of entries,maxtextsize */
7519
7520 const char *array_lval = RlistScalarValue(finalargs);
7521 int maxsize = IntFromString(RlistScalarValue(finalargs->next->next->next->next->next));
7522 char *instring = xstrndup(RlistScalarValue(finalargs->next), maxsize);
7523 const char *comment = RlistScalarValue(finalargs->next->next);
7524 const char *split = RlistScalarValue(finalargs->next->next->next);
7525 int maxent = IntFromString(RlistScalarValue(finalargs->next->next->next->next));
7526
7527 // Read once to validate structure of file in itemlist
7528
7529 Log(LOG_LEVEL_DEBUG, "Parse string data from string '%s' - , maxent %d, maxsize %d", instring, maxent, maxsize);
7530
7531 int entries = 0;
7532 if (instring)
7533 {
7534 instring = StripPatterns(instring, comment, "string argument 2");
7535
7536 if (instring)
7537 {
7538 entries = BuildLineArray(ctx, PromiseGetBundle(fp->caller), array_lval, instring, split, maxent, type, intIndex);
7539 }
7540 }
7541
7542 switch (type)
7543 {
7544 case CF_DATA_TYPE_STRING:
7545 case CF_DATA_TYPE_INT:
7546 case CF_DATA_TYPE_REAL:
7547 break;
7548
7549 default:
7550 ProgrammingError("Unhandled type in switch: %d", type);
7551 }
7552
7553 free(instring);
7554
7555 return FnReturnF("%d", entries);
7556 }
7557
7558 /*********************************************************************/
7559
FnCallParseStringArray(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * args)7560 static FnCallResult FnCallParseStringArray(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *args)
7561 {
7562 return ParseArray(ctx, fp, args, CF_DATA_TYPE_STRING, false);
7563 }
7564
7565 /*********************************************************************/
7566
FnCallParseStringArrayIndex(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * args)7567 static FnCallResult FnCallParseStringArrayIndex(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *args)
7568 {
7569 return ParseArray(ctx, fp, args, CF_DATA_TYPE_STRING, true);
7570 }
7571
7572 /*********************************************************************/
7573
FnCallParseIntArray(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * args)7574 static FnCallResult FnCallParseIntArray(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *args)
7575 {
7576 return ParseArray(ctx, fp, args, CF_DATA_TYPE_INT, false);
7577 }
7578
7579 /*********************************************************************/
7580
FnCallParseRealArray(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * args)7581 static FnCallResult FnCallParseRealArray(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *args)
7582 {
7583 return ParseArray(ctx, fp, args, CF_DATA_TYPE_REAL, false);
7584 }
7585
7586 /*********************************************************************/
7587
FnCallStringMustache(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)7588 static FnCallResult FnCallStringMustache(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
7589 {
7590 if (!finalargs)
7591 {
7592 return FnFailure();
7593 }
7594
7595 const char* const mustache_template = RlistScalarValue(finalargs);
7596 JsonElement *json = NULL;
7597 bool allocated = false;
7598
7599 if (finalargs->next) // we have a variable name...
7600 {
7601 // try to load directly
7602 json = VarNameOrInlineToJson(ctx, fp, finalargs->next, false, &allocated);
7603
7604 // we failed to produce a valid JsonElement, so give up
7605 if (json == NULL)
7606 {
7607 return FnFailure();
7608 }
7609 }
7610 else
7611 {
7612 allocated = true;
7613 json = DefaultTemplateData(ctx, NULL);
7614 }
7615
7616 Buffer *result = BufferNew();
7617 bool success = MustacheRender(result, mustache_template, json);
7618
7619 JsonDestroyMaybe(json, allocated);
7620
7621 if (success)
7622 {
7623 return FnReturnBuffer(result);
7624 }
7625 else
7626 {
7627 BufferDestroy(result);
7628 return FnFailure();
7629 }
7630 }
7631
7632 /*********************************************************************/
7633
FnCallSplitString(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)7634 static FnCallResult FnCallSplitString(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
7635 {
7636 /* 2args: string,split_regex,max */
7637
7638 char *string = RlistScalarValue(finalargs);
7639 char *split = RlistScalarValue(finalargs->next);
7640 int max = IntFromString(RlistScalarValue(finalargs->next->next));
7641
7642 // Read once to validate structure of file in itemlist
7643 Rlist *newlist = RlistFromSplitRegex(string, split, max, true);
7644
7645 return (FnCallResult) { FNCALL_SUCCESS, { newlist, RVAL_TYPE_LIST } };
7646 }
7647
7648 /*********************************************************************/
7649
FnCallStringSplit(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)7650 static FnCallResult FnCallStringSplit(ARG_UNUSED EvalContext *ctx,
7651 ARG_UNUSED const Policy *policy,
7652 ARG_UNUSED const FnCall *fp,
7653 const Rlist *finalargs)
7654 {
7655 /* 3 args: string, split_regex, max */
7656 char *string = RlistScalarValue(finalargs);
7657 char *split = RlistScalarValue(finalargs->next);
7658 int max = IntFromString(RlistScalarValue(finalargs->next->next));
7659
7660 if (max < 1)
7661 {
7662 Log(LOG_LEVEL_VERBOSE, "Function '%s' called with invalid maxent argument: '%d' (should be > 0).", fp->name, max);
7663 return FnFailure();
7664 }
7665
7666 Rlist *newlist = RlistFromRegexSplitNoOverflow(string, split, max);
7667
7668 if (newlist == NULL)
7669 {
7670 /* We are logging error in RlistFromRegexSplitNoOverflow() so no need to do it here as well. */
7671 return FnFailure();
7672 }
7673
7674 return (FnCallResult) { FNCALL_SUCCESS, { newlist, RVAL_TYPE_LIST } };
7675 }
7676
7677 /*********************************************************************/
7678
FnCallStringReplace(ARG_UNUSED EvalContext * ctx,ARG_UNUSED Policy const * policy,ARG_UNUSED FnCall const * fp,Rlist const * finalargs)7679 static FnCallResult FnCallStringReplace(ARG_UNUSED EvalContext *ctx,
7680 ARG_UNUSED Policy const *policy,
7681 ARG_UNUSED FnCall const *fp,
7682 Rlist const *finalargs)
7683 {
7684 if (finalargs->next == NULL || finalargs->next->next == NULL)
7685 {
7686 Log(LOG_LEVEL_WARNING,
7687 "Incorrect number of arguments for function '%s'",
7688 fp->name);
7689 return FnFailure();
7690 }
7691
7692 char *string = RlistScalarValue(finalargs);
7693 char *match = RlistScalarValue(finalargs->next);
7694 char *substitute = RlistScalarValue(finalargs->next->next);
7695
7696 char *ret = SearchAndReplace(string, match, substitute);
7697
7698 if (ret == NULL)
7699 {
7700 Log(LOG_LEVEL_WARNING,
7701 "Failed to replace with function '%s', string: '%s', match: '%s', "
7702 "substitute: '%s'",
7703 fp->name, string, match, substitute);
7704 return FnFailure();
7705 }
7706
7707 return FnReturnNoCopy(ret);
7708 }
7709
7710 /*********************************************************************/
7711
FnCallStringTrim(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)7712 static FnCallResult FnCallStringTrim(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
7713 {
7714 char *string = RlistScalarValue(finalargs);
7715
7716 return FnReturn(TrimWhitespace(string));
7717 }
7718
7719 /*********************************************************************/
7720
FnCallFileSexist(EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)7721 static FnCallResult FnCallFileSexist(EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
7722 {
7723 bool allocated = false;
7724 JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs, false, &allocated);
7725
7726 // we failed to produce a valid JsonElement, so give up
7727 if (json == NULL)
7728 {
7729 Log(LOG_LEVEL_VERBOSE,
7730 "Cannot produce valid JSON from the argument '%s' of the function '%s'",
7731 fp->name, RlistScalarValueSafe(finalargs));
7732 return FnFailure();
7733 }
7734 else if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER)
7735 {
7736 Log(LOG_LEVEL_VERBOSE, "Function '%s', argument '%s' was not a data container or list",
7737 fp->name, RlistScalarValueSafe(finalargs));
7738 JsonDestroyMaybe(json, allocated);
7739 return FnFailure();
7740 }
7741
7742 JsonIterator iter = JsonIteratorInit(json);
7743 const JsonElement *el = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true);
7744
7745 /* no elements mean 'false' should be returned, otherwise let's see if the files exist */
7746 bool file_found = el != NULL;
7747
7748 while (file_found && (el != NULL))
7749 {
7750 char *val = JsonPrimitiveToString(el);
7751 struct stat sb;
7752 if (stat(val, &sb) == -1)
7753 {
7754 file_found = false;
7755 }
7756 free(val);
7757 el = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true);
7758 }
7759
7760 JsonDestroyMaybe(json, allocated);
7761 return FnReturnContext(file_found);
7762 }
7763
7764 /*********************************************************************/
7765 /* LDAP Nova features */
7766 /*********************************************************************/
7767
FnCallLDAPValue(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)7768 static FnCallResult FnCallLDAPValue(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
7769 {
7770 char buffer[CF_BUFSIZE], handle[CF_BUFSIZE];
7771
7772 char *uri = RlistScalarValue(finalargs);
7773 char *dn = RlistScalarValue(finalargs->next);
7774 char *filter = RlistScalarValue(finalargs->next->next);
7775 char *name = RlistScalarValue(finalargs->next->next->next);
7776 char *scope = RlistScalarValue(finalargs->next->next->next->next);
7777 char *sec = RlistScalarValue(finalargs->next->next->next->next->next);
7778
7779 snprintf(handle, CF_BUFSIZE, "%s_%s_%s_%s", dn, filter, name, scope);
7780
7781 void *newval = CfLDAPValue(uri, dn, filter, name, scope, sec);
7782 if (newval)
7783 {
7784 CacheUnreliableValue("ldapvalue", handle, newval);
7785 }
7786 else if (RetrieveUnreliableValue("ldapvalue", handle, buffer) > 0)
7787 {
7788 newval = xstrdup(buffer);
7789 }
7790
7791 if (newval)
7792 {
7793 return FnReturnNoCopy(newval);
7794 }
7795
7796 return FnFailure();
7797 }
7798
7799 /*********************************************************************/
7800
FnCallLDAPArray(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)7801 static FnCallResult FnCallLDAPArray(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
7802 {
7803 if (!fp->caller)
7804 {
7805 Log(LOG_LEVEL_ERR, "Function '%s' can only be called from a promise", fp->name);
7806 return FnFailure();
7807 }
7808
7809 char *array = RlistScalarValue(finalargs);
7810 char *uri = RlistScalarValue(finalargs->next);
7811 char *dn = RlistScalarValue(finalargs->next->next);
7812 char *filter = RlistScalarValue(finalargs->next->next->next);
7813 char *scope = RlistScalarValue(finalargs->next->next->next->next);
7814 char *sec = RlistScalarValue(finalargs->next->next->next->next->next);
7815
7816 void *newval = CfLDAPArray(ctx, PromiseGetBundle(fp->caller), array, uri, dn, filter, scope, sec);
7817 if (newval)
7818 {
7819 return FnReturnNoCopy(newval);
7820 }
7821
7822 return FnFailure();
7823 }
7824
7825 /*********************************************************************/
7826
FnCallLDAPList(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)7827 static FnCallResult FnCallLDAPList(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
7828 {
7829 char *uri = RlistScalarValue(finalargs);
7830 char *dn = RlistScalarValue(finalargs->next);
7831 char *filter = RlistScalarValue(finalargs->next->next);
7832 char *name = RlistScalarValue(finalargs->next->next->next);
7833 char *scope = RlistScalarValue(finalargs->next->next->next->next);
7834 char *sec = RlistScalarValue(finalargs->next->next->next->next->next);
7835
7836 void *newval = CfLDAPList(uri, dn, filter, name, scope, sec);
7837 if (newval)
7838 {
7839 return (FnCallResult) { FNCALL_SUCCESS, { newval, RVAL_TYPE_LIST } };
7840 }
7841
7842 return FnFailure();
7843 }
7844
7845 /*********************************************************************/
7846
FnCallRegLDAP(EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)7847 static FnCallResult FnCallRegLDAP(EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
7848 {
7849 char *uri = RlistScalarValue(finalargs);
7850 char *dn = RlistScalarValue(finalargs->next);
7851 char *filter = RlistScalarValue(finalargs->next->next);
7852 char *name = RlistScalarValue(finalargs->next->next->next);
7853 char *scope = RlistScalarValue(finalargs->next->next->next->next);
7854 char *regex = RlistScalarValue(finalargs->next->next->next->next->next);
7855 char *sec = RlistScalarValue(finalargs->next->next->next->next->next->next);
7856
7857 void *newval = CfRegLDAP(ctx, uri, dn, filter, name, scope, regex, sec);
7858 if (newval)
7859 {
7860 return FnReturnNoCopy(newval);
7861 }
7862
7863 return FnFailure();
7864 }
7865
7866 /*********************************************************************/
7867
7868 #define KILOBYTE 1024
7869
FnCallDiskFree(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)7870 static FnCallResult FnCallDiskFree(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
7871 {
7872 off_t df = GetDiskUsage(RlistScalarValue(finalargs), CF_SIZE_ABS);
7873
7874 if (df == CF_INFINITY)
7875 {
7876 df = 0;
7877 }
7878
7879 return FnReturnF("%jd", (intmax_t) (df / KILOBYTE));
7880 }
7881
7882
FnCallMakerule(EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)7883 static FnCallResult FnCallMakerule(EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
7884 {
7885 const char *target = RlistScalarValue(finalargs);
7886
7887 const char *name_str = RlistScalarValueSafe(finalargs->next);
7888
7889 time_t target_time = 0;
7890 bool stale = false;
7891 struct stat statbuf;
7892 if (lstat(target, &statbuf) == -1)
7893 {
7894 stale = true;
7895 }
7896 else
7897 {
7898 if (!S_ISREG(statbuf.st_mode))
7899 {
7900 Log(LOG_LEVEL_WARNING, "Function '%s' target-file '%s' exists and is not a plain file", fp->name, target);
7901 // Not a probe's responsibility to fix - but have this for debugging
7902 }
7903
7904 target_time = statbuf.st_mtime;
7905 }
7906
7907 // Check if the file name (which should be a scalar if given directly) is explicit
7908 if (RlistValueIsType(finalargs->next, RVAL_TYPE_SCALAR) &&
7909 lstat(name_str, &statbuf) != -1)
7910 {
7911 if (statbuf.st_mtime > target_time)
7912 {
7913 stale = true;
7914 }
7915 }
7916 else
7917 {
7918 // try to load directly from a container of collected values
7919 bool allocated = false;
7920 JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs->next, false, &allocated);
7921
7922 // we failed to produce a valid JsonElement, so give up
7923 if (json == NULL)
7924 {
7925 return FnFailure();
7926 }
7927 else if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER)
7928 {
7929 Log(LOG_LEVEL_VERBOSE, "Function '%s', argument '%s' was not a data container or list",
7930 fp->name, name_str);
7931 JsonDestroyMaybe(json, allocated);
7932 return FnFailure();
7933 }
7934
7935 JsonIterator iter = JsonIteratorInit(json);
7936 const JsonElement *e;
7937 while ((e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true)))
7938 {
7939 const char *value = JsonPrimitiveGetAsString(e);
7940 if (lstat(value, &statbuf) == -1)
7941 {
7942 Log(LOG_LEVEL_VERBOSE, "Function '%s', source dependency %s was not (yet) readable", fp->name, value);
7943 JsonDestroyMaybe(json, allocated);
7944 return FnReturnContext(false);
7945 }
7946 else
7947 {
7948 if (statbuf.st_mtime > target_time)
7949 {
7950 stale = true;
7951 }
7952 }
7953 }
7954
7955 JsonDestroyMaybe(json, allocated);
7956 }
7957
7958 return stale ? FnReturnContext(true) : FnReturnContext(false);
7959 }
7960
7961
7962 #if !defined(__MINGW32__)
7963
FnCallUserExists(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)7964 FnCallResult FnCallUserExists(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
7965 {
7966 char *arg = RlistScalarValue(finalargs);
7967
7968 if (StringIsNumeric(arg))
7969 {
7970 uid_t uid = Str2Uid(arg, NULL, NULL);
7971 if (uid == CF_SAME_OWNER || uid == CF_UNKNOWN_OWNER)
7972 {
7973 return FnFailure();
7974 }
7975
7976 if (getpwuid(uid) == NULL)
7977 {
7978 return FnReturnContext(false);
7979 }
7980 }
7981 else if (getpwnam(arg) == NULL)
7982 {
7983 return FnReturnContext(false);
7984 }
7985
7986 return FnReturnContext(true);
7987 }
7988
7989 /*********************************************************************/
7990
FnCallGroupExists(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,const Rlist * finalargs)7991 FnCallResult FnCallGroupExists(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
7992 {
7993 char *arg = RlistScalarValue(finalargs);
7994
7995 if (StringIsNumeric(arg))
7996 {
7997 gid_t gid = Str2Gid(arg, NULL, NULL);
7998 if (gid == CF_SAME_GROUP || gid == CF_UNKNOWN_GROUP)
7999 {
8000 return FnFailure();
8001 }
8002
8003 if (getgrgid(gid) == NULL)
8004 {
8005 return FnReturnContext(false);
8006 }
8007 }
8008 else if (getgrnam(arg) == NULL)
8009 {
8010 return FnReturnContext(false);
8011 }
8012
8013 return FnReturnContext(true);
8014 }
8015
8016 #endif /* !defined(__MINGW32__) */
8017
SingleLine(const char * s)8018 static bool SingleLine(const char *s)
8019 {
8020 size_t length = strcspn(s, "\n\r");
8021 #ifdef __MINGW32__ /* Treat a CRLF as a single line-ending: */
8022 if (s[length] == '\r' && s[length + 1] == '\n')
8023 {
8024 length++;
8025 }
8026 #endif
8027 /* [\n\r] followed by EOF */
8028 return s[length] && !s[length+1];
8029 }
8030
CfReadFile(const char * filename,size_t maxsize)8031 static char *CfReadFile(const char *filename, size_t maxsize)
8032 {
8033 /* TODO remove this stat() call, it's a remnant from old code
8034 that examined sb.st_size. */
8035 struct stat sb;
8036 if (stat(filename, &sb) == -1)
8037 {
8038 if (THIS_AGENT_TYPE == AGENT_TYPE_COMMON)
8039 {
8040 Log(LOG_LEVEL_ERR, "Could not examine file '%s'", filename);
8041 }
8042 else
8043 {
8044 if (IsCf3VarString(filename))
8045 {
8046 Log(LOG_LEVEL_VERBOSE, "Cannot converge/reduce variable '%s' yet .. assuming it will resolve later",
8047 filename);
8048 }
8049 else
8050 {
8051 Log(LOG_LEVEL_ERR, "CfReadFile: Could not examine file '%s' (stat: %s)",
8052 filename, GetErrorStr());
8053 }
8054 }
8055 return NULL;
8056 }
8057
8058 /* 0 means 'read until the end of file' */
8059 size_t limit = maxsize > 0 ? maxsize : SIZE_MAX;
8060 bool truncated = false;
8061 Writer *w = NULL;
8062 int fd = safe_open(filename, O_RDONLY | O_TEXT);
8063 if (fd >= 0)
8064 {
8065 w = FileReadFromFd(fd, limit, &truncated);
8066 close(fd);
8067 }
8068
8069 if (!w)
8070 {
8071 Log(LOG_LEVEL_ERR, "CfReadFile: Error while reading file '%s' (%s)",
8072 filename, GetErrorStr());
8073 return NULL;
8074 }
8075
8076 if (truncated)
8077 {
8078 Log(LOG_LEVEL_VERBOSE, "CfReadFile: Truncating file '%s' to %zu bytes as "
8079 "requested by maxsize parameter", filename, maxsize);
8080 }
8081
8082 size_t size = StringWriterLength(w);
8083 char *result = StringWriterClose(w);
8084
8085 /* FIXME: Is it necessary here? Move to caller(s) */
8086 if (SingleLine(result))
8087 {
8088 StripTrailingNewline(result, size);
8089 }
8090 return result;
8091 }
8092
8093 /*********************************************************************/
8094
StripPatterns(char * file_buffer,const char * pattern,const char * filename)8095 static char *StripPatterns(char *file_buffer, const char *pattern, const char *filename)
8096 {
8097 if (NULL_OR_EMPTY(pattern))
8098 {
8099 return file_buffer;
8100 }
8101
8102 pcre *rx = CompileRegex(pattern);
8103 if (!rx)
8104 {
8105 return file_buffer;
8106 }
8107
8108 size_t start, end, count = 0;
8109 const size_t original_length = strlen(file_buffer);
8110 while (StringMatchWithPrecompiledRegex(rx, file_buffer, &start, &end))
8111 {
8112 StringCloseHole(file_buffer, start, end);
8113
8114 if (start == end)
8115 {
8116 Log(LOG_LEVEL_WARNING,
8117 "Comment regex '%s' matched empty string in '%s'",
8118 pattern,
8119 filename);
8120 break;
8121 }
8122 assert(start < end);
8123 if (count++ > original_length)
8124 {
8125 debug_abort_if_reached();
8126 Log(LOG_LEVEL_ERR,
8127 "Comment regex '%s' was irreconcilable reading input '%s' probably because it legally matches nothing",
8128 pattern, filename);
8129 break;
8130 }
8131 }
8132
8133 pcre_free(rx);
8134 return file_buffer;
8135 }
8136
8137 /*********************************************************************/
8138
BuildData(ARG_UNUSED EvalContext * ctx,const char * file_buffer,const char * split,int maxent,bool make_array)8139 static JsonElement* BuildData(ARG_UNUSED EvalContext *ctx, const char *file_buffer, const char *split, int maxent, bool make_array)
8140 {
8141 JsonElement *ret = make_array ? JsonArrayCreate(10) : JsonObjectCreate(10);
8142 Seq *lines = SeqStringFromString(file_buffer, '\n');
8143
8144 char *line;
8145 int hcount = 0;
8146
8147 for (size_t i = 0; i < SeqLength(lines) && hcount < maxent; i++)
8148 {
8149 line = (char*) SeqAt(lines, i);
8150 size_t line_len = strlen(line);
8151
8152 if (line_len == 0 || (line_len == 1 && line[0] == '\r'))
8153 {
8154 continue;
8155 }
8156
8157 if (line[line_len - 1] == '\r')
8158 {
8159 line[line_len - 1] = '\0';
8160 }
8161
8162 Rlist *tokens = RlistFromSplitRegex(line, split, 99999, true);
8163 JsonElement *linearray = JsonArrayCreate(10);
8164
8165 for (const Rlist *rp = tokens; rp; rp = rp->next)
8166 {
8167 const char *token = RlistScalarValue(rp);
8168 JsonArrayAppendString(linearray, token);
8169 }
8170
8171 RlistDestroy(tokens);
8172
8173 if (JsonLength(linearray) > 0)
8174 {
8175 if (make_array)
8176 {
8177 JsonArrayAppendArray(ret, linearray);
8178 }
8179 else
8180 {
8181 char *key = xstrdup(JsonArrayGetAsString(linearray, 0));
8182 JsonArrayRemoveRange(linearray, 0, 0);
8183 JsonObjectAppendArray(ret, key, linearray);
8184 free(key);
8185 }
8186
8187 // only increase hcount if we actually got something
8188 hcount++;
8189 }
8190 }
8191
8192 SeqDestroy(lines);
8193
8194 return ret;
8195 }
8196
8197 /*********************************************************************/
8198
BuildLineArray(EvalContext * ctx,const Bundle * bundle,const char * array_lval,const char * file_buffer,const char * split,int maxent,DataType type,bool int_index)8199 static int BuildLineArray(EvalContext *ctx, const Bundle *bundle,
8200 const char *array_lval, const char *file_buffer,
8201 const char *split, int maxent, DataType type,
8202 bool int_index)
8203 {
8204 Rlist *lines = RlistFromSplitString(file_buffer, '\n');
8205 int hcount = 0;
8206
8207 for (Rlist *it = lines; it && hcount < maxent; it = it->next)
8208 {
8209 char *line = RlistScalarValue(it);
8210 size_t line_len = strlen(line);
8211
8212 if (line_len == 0 || (line_len == 1 && line[0] == '\r'))
8213 {
8214 continue;
8215 }
8216
8217 if (line[line_len - 1] == '\r')
8218 {
8219 line[line_len - 1] = '\0';
8220 }
8221
8222 char* first_index = NULL;
8223 int vcount = 0;
8224
8225 Rlist *tokens = RlistFromSplitRegex(line, split, 99999, true);
8226
8227 for (const Rlist *rp = tokens; rp; rp = rp->next)
8228 {
8229 const char *token = RlistScalarValue(rp);
8230 char *converted = NULL;
8231
8232 switch (type)
8233 {
8234 case CF_DATA_TYPE_STRING:
8235 converted = xstrdup(token);
8236 break;
8237
8238 case CF_DATA_TYPE_INT:
8239 {
8240 long value = IntFromString(token);
8241 if (value == CF_NOINT)
8242 {
8243 FatalError(ctx, "Could not convert token to int");
8244 }
8245 converted = StringFormat("%ld", value);
8246 }
8247 break;
8248
8249 case CF_DATA_TYPE_REAL:
8250 {
8251 double real_value = 0;
8252 if (!DoubleFromString(token, &real_value))
8253 {
8254 FatalError(ctx, "Could not convert token to double");
8255 }
8256 converted = xstrdup(token);
8257 }
8258 break;
8259
8260 default:
8261 ProgrammingError("Unhandled type in switch: %d", type);
8262 }
8263
8264 if (first_index == NULL)
8265 {
8266 first_index = xstrdup(converted);
8267 }
8268
8269 char *name;
8270 if (int_index)
8271 {
8272 xasprintf(&name, "%s[%d][%d]", array_lval, hcount, vcount);
8273 }
8274 else
8275 {
8276 xasprintf(&name, "%s[%s][%d]", array_lval, first_index, vcount);
8277 }
8278
8279 VarRef *ref = VarRefParseFromBundle(name, bundle);
8280 EvalContextVariablePut(ctx, ref, converted, type, "source=function,function=buildlinearray");
8281 VarRefDestroy(ref);
8282
8283 free(name);
8284 free(converted);
8285
8286 vcount++;
8287 }
8288
8289 free(first_index);
8290 RlistDestroy(tokens);
8291
8292 hcount++;
8293 line++;
8294 }
8295
8296 RlistDestroy(lines);
8297
8298 return hcount;
8299 }
8300
8301 /*********************************************************************/
8302
FnCallProcessExists(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)8303 static FnCallResult FnCallProcessExists(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
8304 {
8305 char *regex = RlistScalarValue(finalargs);
8306
8307 const bool is_context_processexists = strcmp(fp->name, "processexists") == 0;
8308
8309 if (!LoadProcessTable())
8310 {
8311 Log(LOG_LEVEL_ERR, "%s: could not load the process table?!?!", fp->name);
8312 return FnFailure();
8313 }
8314
8315 ProcessSelect ps = PROCESS_SELECT_INIT;
8316 ps.owner = NULL;
8317 ps.process_result = "";
8318
8319 // ps is unused because attrselect = false below
8320 Item *matched = SelectProcesses(regex, &ps, false);
8321 ClearProcessTable();
8322
8323 if (is_context_processexists)
8324 {
8325 const bool ret = (matched != NULL);
8326 DeleteItemList(matched);
8327 return FnReturnContext(ret);
8328 }
8329
8330 JsonElement *json = JsonArrayCreate(50);
8331 // we're in process gathering mode
8332 for (Item *ip = matched; ip != NULL; ip = ip->next)
8333 {
8334 // we only have the ps line and PID
8335
8336 // TODO: this properly, by including more properties of the
8337 // processes, when the underlying code stops using a plain
8338 // ItemList
8339 JsonElement *pobj = JsonObjectCreate(2);
8340 JsonObjectAppendString(pobj, "line", ip->name);
8341 JsonObjectAppendInteger(pobj, "pid", ip->counter);
8342
8343 JsonArrayAppendObject(json, pobj);
8344 }
8345 DeleteItemList(matched);
8346
8347 return FnReturnContainerNoCopy(json);
8348 }
8349
8350 /*********************************************************************/
8351
FnCallNetworkConnections(EvalContext * ctx,ARG_UNUSED const Policy * policy,ARG_UNUSED const FnCall * fp,ARG_UNUSED const Rlist * finalargs)8352 static FnCallResult FnCallNetworkConnections(EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, ARG_UNUSED const Rlist *finalargs)
8353 {
8354 JsonElement *json = GetNetworkingConnections(ctx);
8355
8356 if (json == NULL)
8357 {
8358 // nothing was collected, this is a failure
8359 return FnFailure();
8360 }
8361
8362 return FnReturnContainerNoCopy(json);
8363 }
8364
8365 /*********************************************************************/
8366
FnCallFindfilesUp(ARG_UNUSED EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)8367 static FnCallResult FnCallFindfilesUp(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
8368 {
8369 assert(fp != NULL);
8370 assert(fp->name != NULL);
8371
8372 const Rlist *const path_arg = finalargs;
8373 if (path_arg == NULL)
8374 {
8375 Log(LOG_LEVEL_ERR, "Function %s requires path as first argument",
8376 fp->name);
8377 return FnFailure();
8378 }
8379
8380 const Rlist *const glob_arg = finalargs->next;
8381 if (glob_arg == NULL)
8382 {
8383 Log(LOG_LEVEL_ERR, "Function %s requires glob as second argument",
8384 fp->name);
8385 return FnFailure();
8386 }
8387
8388 const Rlist *const level_arg = finalargs->next->next;
8389
8390 char path[PATH_MAX];
8391 size_t copied = strlcpy(path, RlistScalarValue(path_arg), sizeof(path));
8392 if (copied >= sizeof(path))
8393 {
8394 Log(LOG_LEVEL_ERR, "Function %s, path is too long (%zu > %zu)",
8395 fp->name, copied, sizeof(path));
8396 return FnFailure();
8397 }
8398
8399 if (!IsAbsoluteFileName(path))
8400 {
8401 Log(LOG_LEVEL_ERR, "Function %s, not an absolute path '%s'",
8402 fp->name, path);
8403 return FnFailure();
8404 }
8405
8406 if (!IsDir(path))
8407 {
8408 Log(LOG_LEVEL_ERR, "Function %s, path '%s' is not a directory",
8409 fp->name, path);
8410 return FnFailure();
8411 }
8412
8413 DeleteRedundantSlashes(path);
8414
8415 size_t len = strlen(path);
8416 if (path[len - 1] == FILE_SEPARATOR)
8417 {
8418 path[len - 1] = '\0';
8419 }
8420
8421 char glob[PATH_MAX];
8422 copied = strlcpy(glob, RlistScalarValue(glob_arg), sizeof(glob));
8423 if (copied >= sizeof(glob))
8424 {
8425 Log(LOG_LEVEL_ERR, "Function %s, glob is too long (%zu > %zu)",
8426 fp->name, copied, sizeof(glob));
8427 return FnFailure();
8428 }
8429
8430 if (IsAbsoluteFileName(glob))
8431 {
8432 Log(LOG_LEVEL_ERR,
8433 "Function %s, glob '%s' cannot be an absolute path", fp->name,
8434 glob);
8435 return FnFailure();
8436 }
8437
8438 DeleteRedundantSlashes(glob);
8439
8440 /* level defaults to inf */
8441 long level = IntFromString("inf");
8442 if (level_arg != NULL)
8443 {
8444 level = IntFromString(RlistScalarValue(level_arg));
8445 }
8446
8447 JsonElement *json = JsonArrayCreate(1);
8448
8449 while (level-- >= 0)
8450 {
8451 char filepath[PATH_MAX];
8452 copied = strlcpy(filepath, path, sizeof(filepath));
8453 assert(copied < sizeof(path));
8454 if (JoinPaths(filepath, sizeof(filepath), glob) == NULL)
8455 {
8456 Log(LOG_LEVEL_ERR,
8457 "Function %s, failed to join path '%s' and glob '%s'",
8458 fp->name, path, glob);
8459 }
8460
8461 StringSet *matches = GlobFileList(filepath);
8462 JsonElement *matches_json = StringSetToJson(matches);
8463 JsonArrayExtend(json, matches_json);
8464 StringSetDestroy(matches);
8465
8466 char *sep = strrchr(path, FILE_SEPARATOR);
8467 if (sep == NULL)
8468 {
8469 /* don't search beyond root directory */
8470 break;
8471 }
8472 *sep = '\0';
8473 }
8474
8475 return FnReturnContainerNoCopy(json);
8476 }
8477
8478 /*********************************************************************/
8479
ExecModule(EvalContext * ctx,char * command)8480 static bool ExecModule(EvalContext *ctx, char *command)
8481 {
8482 FILE *pp = cf_popen(command, "rt", true);
8483 if (!pp)
8484 {
8485 Log(LOG_LEVEL_ERR, "Couldn't open pipe from '%s'. (cf_popen: %s)", command, GetErrorStr());
8486 return false;
8487 }
8488
8489 char context[CF_BUFSIZE] = "";
8490 StringSet *tags = StringSetNew();
8491 long persistence = 0;
8492
8493 size_t line_size = CF_BUFSIZE;
8494 char *line = xmalloc(line_size);
8495
8496 while (CfReadLine(&line, &line_size, pp) != -1)
8497 {
8498 bool print = false;
8499 for (const char *sp = line; *sp != '\0'; sp++)
8500 {
8501 if (!isspace((unsigned char) *sp))
8502 {
8503 print = true;
8504 break;
8505 }
8506 }
8507
8508 ModuleProtocol(ctx, command, line, print, context, sizeof(context), tags, &persistence);
8509 }
8510 bool atend = feof(pp);
8511 cf_pclose(pp);
8512 free(line);
8513 StringSetDestroy(tags);
8514
8515 if (!atend)
8516 {
8517 Log(LOG_LEVEL_ERR, "Unable to read output from '%s'. (fread: %s)", command, GetErrorStr());
8518 return false;
8519 }
8520
8521 return true;
8522 }
8523
ModuleProtocol(EvalContext * ctx,const char * command,const char * line,int print,char * context,size_t context_size,StringSet * tags,long * persistence)8524 void ModuleProtocol(EvalContext *ctx, const char *command, const char *line, int print, char* context, size_t context_size, StringSet *tags, long *persistence)
8525 {
8526 assert(tags);
8527
8528 // see the sscanf() limit below
8529 if(context_size < 51)
8530 {
8531 ProgrammingError("ModuleProtocol: context_size too small (%zu)",
8532 context_size);
8533 }
8534
8535 if (*context == '\0')
8536 {
8537 /* Infer namespace from script name */
8538 char arg0[CF_BUFSIZE];
8539 strlcpy(arg0, CommandArg0(command), CF_BUFSIZE);
8540 char *filename = basename(arg0);
8541
8542 /* Canonicalize filename into acceptable namespace name */
8543 CanonifyNameInPlace(filename);
8544 strlcpy(context, filename, context_size);
8545 Log(LOG_LEVEL_VERBOSE, "Module context '%s'", context);
8546 }
8547
8548 char name[CF_BUFSIZE], content[CF_BUFSIZE];
8549 name[0] = content[0] = '\0';
8550
8551 size_t length = strlen(line);
8552 switch (*line)
8553 {
8554 case '^':
8555 // Allow modules to set their variable context (up to 50 characters)
8556 if (sscanf(line + 1, "context=%50[^\n]", content) == 1 &&
8557 content[0] != '\0')
8558 {
8559 /* Symbol ID without \200 to \377: */
8560 pcre *context_name_rx = CompileRegex("[a-zA-Z0-9_]+");
8561 if (!context_name_rx)
8562 {
8563 Log(LOG_LEVEL_ERR,
8564 "Internal error compiling module protocol context regex, aborting!!!");
8565 }
8566 else if (StringMatchFullWithPrecompiledRegex(context_name_rx, content))
8567 {
8568 Log(LOG_LEVEL_VERBOSE, "Module changed variable context from '%s' to '%s'", context, content);
8569 strlcpy(context, content, context_size);
8570 }
8571 else
8572 {
8573 Log(LOG_LEVEL_ERR,
8574 "Module protocol was given an unacceptable ^context directive '%s', skipping", content);
8575 }
8576
8577 if (context_name_rx)
8578 {
8579 pcre_free(context_name_rx);
8580 }
8581 }
8582 else if (sscanf(line + 1, "meta=%1024[^\n]", content) == 1 &&
8583 content[0] != '\0')
8584 {
8585 Log(LOG_LEVEL_VERBOSE, "Module set meta tags to '%s'", content);
8586 StringSetClear(tags);
8587
8588 StringSetAddSplit(tags, content, ',');
8589 StringSetAdd(tags, xstrdup("source=module"));
8590 }
8591 else if (sscanf(line + 1, "persistence=%ld", persistence) == 1)
8592 {
8593 Log(LOG_LEVEL_VERBOSE, "Module set persistence to %ld minutes", *persistence);
8594 }
8595 else
8596 {
8597 Log(LOG_LEVEL_INFO, "Unknown extended module command '%s'", line);
8598 }
8599 break;
8600
8601 case '+':
8602 if (length > CF_MAXVARSIZE)
8603 {
8604 Log(LOG_LEVEL_ERR,
8605 "Module protocol was given an overlong +class line (%zu bytes), skipping",
8606 length);
8607 break;
8608 }
8609
8610 // the class name will fit safely inside CF_MAXVARSIZE - 1 bytes
8611 sscanf(line + 1, "%1023[^\n]", content);
8612 Log(LOG_LEVEL_VERBOSE, "Activating classes from module protocol: '%s'", content);
8613 if (CheckID(content))
8614 {
8615 Buffer *tagbuf = StringSetToBuffer(tags, ',');
8616 EvalContextClassPutSoft(ctx, content, CONTEXT_SCOPE_NAMESPACE, BufferData(tagbuf));
8617 if (*persistence > 0)
8618 {
8619 Log(LOG_LEVEL_VERBOSE, "Module set persistent class '%s' for %ld minutes", content, *persistence);
8620 EvalContextHeapPersistentSave(ctx, content, *persistence, CONTEXT_STATE_POLICY_PRESERVE, BufferData(tagbuf));
8621 }
8622
8623 BufferDestroy(tagbuf);
8624 }
8625 else
8626 {
8627 Log(LOG_LEVEL_VERBOSE, "Automatically canonifying '%s'", content);
8628 CanonifyNameInPlace(content);
8629 Log(LOG_LEVEL_VERBOSE, "Automatically canonified to '%s'", content);
8630 Buffer *tagbuf = StringSetToBuffer(tags, ',');
8631 EvalContextClassPutSoft(ctx, content, CONTEXT_SCOPE_NAMESPACE, BufferData(tagbuf));
8632 BufferDestroy(tagbuf);
8633 }
8634 break;
8635 case '-':
8636 if (length > CF_MAXVARSIZE)
8637 {
8638 Log(LOG_LEVEL_ERR,
8639 "Module protocol was given an overlong -class line (%zu bytes), skipping",
8640 length);
8641 break;
8642 }
8643
8644 // the class name(s) will fit safely inside CF_MAXVARSIZE - 1 bytes
8645 sscanf(line + 1, "%1023[^\n]", content);
8646 Log(LOG_LEVEL_VERBOSE, "Deactivating classes from module protocol: '%s'", content);
8647 if (CheckID(content))
8648 {
8649 if (content[0] != '\0')
8650 {
8651 StringSet *negated = StringSetFromString(content, ',');
8652 StringSetIterator it = StringSetIteratorInit(negated);
8653 const char *negated_context = NULL;
8654 while ((negated_context = StringSetIteratorNext(&it)))
8655 {
8656 Class *cls = EvalContextClassGet(ctx, NULL, negated_context);
8657 if (cls && !cls->is_soft)
8658 {
8659 FatalError(ctx, "Cannot negate the reserved class '%s'", negated_context);
8660 }
8661
8662 ClassRef ref = ClassRefParse(negated_context);
8663 EvalContextClassRemove(ctx, ref.ns, ref.name);
8664 ClassRefDestroy(ref);
8665 }
8666 StringSetDestroy(negated);
8667 }
8668 }
8669 break;
8670 case '=':
8671 if (length > CF_BUFSIZE + 256)
8672 {
8673 Log(LOG_LEVEL_ERR,
8674 "Module protocol was given an overlong variable =line (%zu bytes), skipping",
8675 length);
8676 break;
8677 }
8678
8679 // TODO: the variable name is limited to 256 to accommodate the
8680 // context name once it's in the vartable. Maybe this can be relaxed.
8681 sscanf(line + 1, "%256[^=]=%4095[^\n]", name, content);
8682
8683 if (CheckID(name))
8684 {
8685 Log(LOG_LEVEL_VERBOSE, "Defined variable '%s' in context '%s' with value '%s'", name, context, content);
8686 VarRef *ref = VarRefParseFromScope(name, context);
8687
8688 Buffer *tagbuf = StringSetToBuffer(tags, ',');
8689 EvalContextVariablePut(ctx, ref, content, CF_DATA_TYPE_STRING, BufferData(tagbuf));
8690 BufferDestroy(tagbuf);
8691
8692 VarRefDestroy(ref);
8693 }
8694 break;
8695
8696 case '&':
8697 // TODO: the variable name is limited to 256 to accommodate the
8698 // context name once it's in the vartable. Maybe this can be relaxed.
8699 // &policy_varname=/path/to/file can be json/env/yaml/csv type, default: json
8700 sscanf(line + 1, "%256[^=]=%4095[^\n]", name, content);
8701
8702 if (CheckID(name))
8703 {
8704 if (FileCanOpen(content, "r"))
8705 {
8706 const int size_max = IntFromString("inf");
8707 const DataFileType requested_mode = GetDataFileTypeFromSuffix(content);
8708
8709 Log(LOG_LEVEL_DEBUG, "Module protocol parsing %s file '%s'",
8710 DataFileTypeToString(requested_mode), content);
8711
8712 JsonElement *json = JsonReadDataFile("module file protocol", content, requested_mode, size_max);
8713 if (json != NULL)
8714 {
8715 Buffer *tagbuf = StringSetToBuffer(tags, ',');
8716 VarRef *ref = VarRefParseFromScope(name, context);
8717
8718 EvalContextVariablePut(ctx, ref, json, CF_DATA_TYPE_CONTAINER, BufferData(tagbuf));
8719 VarRefDestroy(ref);
8720 BufferDestroy(tagbuf);
8721 JsonDestroy(json);
8722 }
8723 else
8724 {
8725 // reading / parsing failed, error message printed by JsonReadDataFile,
8726 // variable will not be created
8727 }
8728 }
8729 else
8730 {
8731 Log(LOG_LEVEL_ERR, "could not load module file '%s'", content);
8732 }
8733 }
8734 break;
8735
8736 case '%':
8737 // TODO: the variable name is limited to 256 to accommodate the
8738 // context name once it's in the vartable. Maybe this can be relaxed.
8739 sscanf(line + 1, "%256[^=]=", name);
8740
8741 if (CheckID(name))
8742 {
8743 JsonElement *json = NULL;
8744 Buffer *holder = BufferNewFrom(line+strlen(name)+1+1,
8745 length - strlen(name) - 1 - 1);
8746 const char *hold = BufferData(holder);
8747 Log(LOG_LEVEL_DEBUG, "Module protocol parsing JSON %s", content);
8748
8749 JsonParseError res = JsonParse(&hold, &json);
8750 if (res != JSON_PARSE_OK)
8751 {
8752 Log(LOG_LEVEL_INFO,
8753 "Failed to parse JSON '%s' for module protocol: %s",
8754 content, JsonParseErrorToString(res));
8755 }
8756 else
8757 {
8758 if (JsonGetElementType(json) == JSON_ELEMENT_TYPE_PRIMITIVE)
8759 {
8760 Log(LOG_LEVEL_INFO,
8761 "Module protocol JSON '%s' should be object or array; wasn't",
8762 content);
8763 }
8764 else
8765 {
8766 Log(LOG_LEVEL_VERBOSE,
8767 "Defined data container variable '%s' in context '%s' with value '%s'",
8768 name, context, BufferData(holder));
8769
8770 Buffer *tagbuf = StringSetToBuffer(tags, ',');
8771 VarRef *ref = VarRefParseFromScope(name, context);
8772
8773 EvalContextVariablePut(ctx, ref, json, CF_DATA_TYPE_CONTAINER, BufferData(tagbuf));
8774 VarRefDestroy(ref);
8775 BufferDestroy(tagbuf);
8776 }
8777
8778 JsonDestroy(json);
8779 }
8780
8781 BufferDestroy(holder);
8782 }
8783 break;
8784
8785 case '@':
8786 // TODO: should not need to exist. entry size matters, not line size. bufferize module protocol
8787 if (length > CF_BUFSIZE + 256 - 1)
8788 {
8789 Log(LOG_LEVEL_ERR,
8790 "Module protocol was given an overlong variable @line (%zu bytes), skipping",
8791 length);
8792 break;
8793 }
8794
8795 sscanf(line + 1, "%256[^=]=%4095[^\n]", name, content);
8796
8797 if (CheckID(name))
8798 {
8799 Rlist *list = RlistParseString(content);
8800 if (!list)
8801 {
8802 Log(LOG_LEVEL_ERR, "Module protocol could not parse variable %s's data content %s", name, content);
8803 }
8804 else
8805 {
8806 bool has_oversize_entry = false;
8807 for (const Rlist *rp = list; rp; rp = rp->next)
8808 {
8809 size_t entry_size = strlen(RlistScalarValue(rp));
8810 if (entry_size > CF_MAXVARSIZE)
8811 {
8812 has_oversize_entry = true;
8813 break;
8814 }
8815 }
8816
8817 if (has_oversize_entry)
8818 {
8819 Log(LOG_LEVEL_ERR, "Module protocol was given a variable @ line with an oversize entry, skipping");
8820 RlistDestroy(list);
8821 break;
8822 }
8823
8824 Log(LOG_LEVEL_VERBOSE, "Defined variable '%s' in context '%s' with value '%s'", name, context, content);
8825
8826 VarRef *ref = VarRefParseFromScope(name, context);
8827
8828 Buffer *tagbuf = StringSetToBuffer(tags, ',');
8829 EvalContextVariablePut(ctx, ref, list, CF_DATA_TYPE_STRING_LIST, BufferData(tagbuf));
8830 BufferDestroy(tagbuf);
8831
8832 VarRefDestroy(ref);
8833 RlistDestroy(list);
8834 }
8835 }
8836 break;
8837
8838 case '\0':
8839 break;
8840
8841 default:
8842 if (print)
8843 {
8844 Log(LOG_LEVEL_INFO, "M '%s': %s", command, line);
8845 }
8846 break;
8847 }
8848 }
8849
8850 /*********************************************************************/
8851
FnCallCFEngineCallers(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,ARG_UNUSED const Rlist * finalargs)8852 static FnCallResult FnCallCFEngineCallers(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, ARG_UNUSED const Rlist *finalargs)
8853 {
8854 bool promisersmode = (strcmp(fp->name, "callstack_promisers") == 0);
8855
8856 if (promisersmode)
8857 {
8858 Rlist *returnlist = EvalContextGetPromiseCallerMethods(ctx);
8859 return (FnCallResult) { FNCALL_SUCCESS, { returnlist, RVAL_TYPE_LIST } };
8860 }
8861
8862 JsonElement *callers = EvalContextGetPromiseCallers(ctx);
8863 return FnReturnContainerNoCopy(callers);
8864 }
8865
FnCallString(EvalContext * ctx,ARG_UNUSED const Policy * policy,const FnCall * fp,const Rlist * finalargs)8866 static FnCallResult FnCallString(EvalContext *ctx,
8867 ARG_UNUSED const Policy *policy,
8868 const FnCall *fp,
8869 const Rlist *finalargs)
8870 {
8871 assert(finalargs != NULL);
8872
8873 char *ret = RvalToString(finalargs->val);
8874 if (StringStartsWith(ret, "@(") || StringStartsWith(ret, "@{"))
8875 {
8876 bool allocated = false;
8877 JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs, false, &allocated);
8878
8879 if (json != NULL)
8880 {
8881 Writer *w = StringWriter();
8882 JsonWriteCompact(w, json);
8883 ret = StringWriterClose(w);
8884 if (allocated)
8885 {
8886 JsonDestroy(json);
8887 }
8888 }
8889 }
8890
8891 return FnReturnNoCopy(ret);
8892 }
8893
8894 /*********************************************************************/
8895 /* Level */
8896 /*********************************************************************/
8897
CheckIDChar(const char ch)8898 static bool CheckIDChar(const char ch)
8899 {
8900 return isalnum((int) ch) || (ch == '.') || (ch == '-') || (ch == '_') ||
8901 (ch == '[') || (ch == ']') || (ch == '/') ||
8902 (ch == '@');
8903 }
8904
CheckID(const char * id)8905 static bool CheckID(const char *id)
8906 {
8907 for (const char *sp = id; *sp != '\0'; sp++)
8908 {
8909 if (!CheckIDChar(*sp))
8910 {
8911 Log(LOG_LEVEL_VERBOSE,
8912 "Module protocol contained an illegal character '%c' in class/variable identifier '%s'.", *sp,
8913 id);
8914 }
8915 }
8916
8917 return true;
8918 }
8919
8920 /*********************************************************/
8921 /* Function prototypes */
8922 /*********************************************************/
8923
8924 static const FnCallArg ACCESSEDBEFORE_ARGS[] =
8925 {
8926 {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "Newer filename"},
8927 {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "Older filename"},
8928 {NULL, CF_DATA_TYPE_NONE, NULL}
8929 };
8930
8931 static const FnCallArg ACCUM_ARGS[] =
8932 {
8933 {"0,1000", CF_DATA_TYPE_INT, "Years"},
8934 {"0,1000", CF_DATA_TYPE_INT, "Months"},
8935 {"0,1000", CF_DATA_TYPE_INT, "Days"},
8936 {"0,1000", CF_DATA_TYPE_INT, "Hours"},
8937 {"0,1000", CF_DATA_TYPE_INT, "Minutes"},
8938 {"0,40000", CF_DATA_TYPE_INT, "Seconds"},
8939 {NULL, CF_DATA_TYPE_NONE, NULL}
8940 };
8941
8942 static const FnCallArg AND_ARGS[] =
8943 {
8944 {NULL, CF_DATA_TYPE_NONE, NULL}
8945 };
8946
8947 static const FnCallArg AGO_ARGS[] =
8948 {
8949 {"0,1000", CF_DATA_TYPE_INT, "Years"},
8950 {"0,1000", CF_DATA_TYPE_INT, "Months"},
8951 {"0,1000", CF_DATA_TYPE_INT, "Days"},
8952 {"0,1000", CF_DATA_TYPE_INT, "Hours"},
8953 {"0,1000", CF_DATA_TYPE_INT, "Minutes"},
8954 {"0,40000", CF_DATA_TYPE_INT, "Seconds"},
8955 {NULL, CF_DATA_TYPE_NONE, NULL}
8956 };
8957
8958 static const FnCallArg LATERTHAN_ARGS[] =
8959 {
8960 {"0,10000", CF_DATA_TYPE_INT, "Years"},
8961 {"0,1000", CF_DATA_TYPE_INT, "Months"},
8962 {"0,1000", CF_DATA_TYPE_INT, "Days"},
8963 {"0,1000", CF_DATA_TYPE_INT, "Hours"},
8964 {"0,1000", CF_DATA_TYPE_INT, "Minutes"},
8965 {"0,40000", CF_DATA_TYPE_INT, "Seconds"},
8966 {NULL, CF_DATA_TYPE_NONE, NULL}
8967 };
8968
8969 static const FnCallArg BASENAME_ARGS[] =
8970 {
8971 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "File path"},
8972 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Optional suffix"},
8973 {NULL, CF_DATA_TYPE_NONE, NULL},
8974 };
8975
8976 static const FnCallArg DATE_ARGS[] = /* for on() */
8977 {
8978 {"1970,3000", CF_DATA_TYPE_INT, "Year"},
8979 {"0,1000", CF_DATA_TYPE_INT, "Month (January = 0)"},
8980 {"0,1000", CF_DATA_TYPE_INT, "Day (First day of month = 0)"},
8981 {"0,1000", CF_DATA_TYPE_INT, "Hour"},
8982 {"0,1000", CF_DATA_TYPE_INT, "Minute"},
8983 {"0,1000", CF_DATA_TYPE_INT, "Second"},
8984 {NULL, CF_DATA_TYPE_NONE, NULL}
8985 };
8986
8987 static const FnCallArg CANONIFY_ARGS[] =
8988 {
8989 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "String containing non-identifier characters"},
8990 {NULL, CF_DATA_TYPE_NONE, NULL}
8991 };
8992
8993 static const FnCallArg CHANGEDBEFORE_ARGS[] =
8994 {
8995 {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "Newer filename"},
8996 {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "Older filename"},
8997 {NULL, CF_DATA_TYPE_NONE, NULL}
8998 };
8999
9000 static const FnCallArg CFVERSION_ARGS[] =
9001 {
9002 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine version number to compare against"},
9003 {NULL, CF_DATA_TYPE_NONE, NULL}
9004 };
9005
9006 static const FnCallArg CFVERSIONBETWEEN_ARGS[] =
9007 {
9008 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Lower CFEngine version number to compare against"},
9009 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Upper CFEngine version number to compare against"},
9010 {NULL, CF_DATA_TYPE_NONE, NULL}
9011 };
9012
9013 static const FnCallArg CLASSIFY_ARGS[] =
9014 {
9015 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Input string"},
9016 {NULL, CF_DATA_TYPE_NONE, NULL}
9017 };
9018
9019 static const FnCallArg CLASSMATCH_ARGS[] =
9020 {
9021 {NULL, CF_DATA_TYPE_NONE, NULL}
9022 };
9023
9024 static const FnCallArg CONCAT_ARGS[] =
9025 {
9026 {NULL, CF_DATA_TYPE_NONE, NULL}
9027 };
9028
9029 static const FnCallArg COUNTLINESMATCHING_ARGS[] =
9030 {
9031 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression"},
9032 {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "Filename"},
9033 {NULL, CF_DATA_TYPE_NONE, NULL}
9034 };
9035
9036 static const FnCallArg DIRNAME_ARGS[] =
9037 {
9038 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "File path"},
9039 {NULL, CF_DATA_TYPE_NONE, NULL},
9040 };
9041
9042 static const FnCallArg DISKFREE_ARGS[] =
9043 {
9044 {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "File system directory"},
9045 {NULL, CF_DATA_TYPE_NONE, NULL}
9046 };
9047
9048 static const FnCallArg ESCAPE_ARGS[] =
9049 {
9050 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "IP address or string to escape"},
9051 {NULL, CF_DATA_TYPE_NONE, NULL}
9052 };
9053
9054 static const FnCallArg EXECRESULT_ARGS[] =
9055 {
9056 {CF_PATHRANGE, CF_DATA_TYPE_STRING, "Fully qualified command path"},
9057 {"noshell,useshell,powershell", CF_DATA_TYPE_OPTION, "Shell encapsulation option"},
9058 {"both,stdout,stderr", CF_DATA_TYPE_OPTION, "Which output to return; stdout or stderr"},
9059 {NULL, CF_DATA_TYPE_NONE, NULL}
9060 };
9061
9062 // fileexists, isdir,isplain,islink
9063
9064 static const FnCallArg FILESTAT_ARGS[] =
9065 {
9066 {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "File object name"},
9067 {NULL, CF_DATA_TYPE_NONE, NULL}
9068 };
9069
9070 static const FnCallArg FILESTAT_DETAIL_ARGS[] =
9071 {
9072 {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "File object name"},
9073 {"size,gid,uid,ino,nlink,ctime,atime,mtime,xattr,mode,modeoct,permstr,permoct,type,devno,dev_minor,dev_major,basename,dirname,linktarget,linktarget_shallow", CF_DATA_TYPE_OPTION, "stat() field to get"},
9074 {NULL, CF_DATA_TYPE_NONE, NULL}
9075 };
9076
9077 static const FnCallArg FILESEXIST_ARGS[] =
9078 {
9079 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"},
9080 {NULL, CF_DATA_TYPE_NONE, NULL}
9081 };
9082
9083 static const FnCallArg FINDFILES_ARGS[] =
9084 {
9085 {NULL, CF_DATA_TYPE_NONE, NULL}
9086 };
9087
9088 static const FnCallArg FILTER_ARGS[] =
9089 {
9090 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression or string"},
9091 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"},
9092 {CF_BOOL, CF_DATA_TYPE_OPTION, "Match as regular expression if true, as exact string otherwise"},
9093 {CF_BOOL, CF_DATA_TYPE_OPTION, "Invert matches"},
9094 {CF_VALRANGE, CF_DATA_TYPE_INT, "Maximum number of matches to return"},
9095 {NULL, CF_DATA_TYPE_NONE, NULL}
9096 };
9097
9098 static const FnCallArg GETFIELDS_ARGS[] =
9099 {
9100 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression to match line"},
9101 {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "Filename to read"},
9102 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression to split fields"},
9103 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Return array name"},
9104 {NULL, CF_DATA_TYPE_NONE, NULL}
9105 };
9106
9107 static const FnCallArg GETINDICES_ARGS[] =
9108 {
9109 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"},
9110 {NULL, CF_DATA_TYPE_NONE, NULL}
9111 };
9112
9113 static const FnCallArg GETUSERS_ARGS[] =
9114 {
9115 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Comma separated list of User names"},
9116 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Comma separated list of UserID numbers"},
9117 {NULL, CF_DATA_TYPE_NONE, NULL}
9118 };
9119
9120 static const FnCallArg GETENV_ARGS[] =
9121 {
9122 {CF_IDRANGE, CF_DATA_TYPE_STRING, "Name of environment variable"},
9123 {CF_VALRANGE, CF_DATA_TYPE_INT, "Maximum number of characters to read "},
9124 {NULL, CF_DATA_TYPE_NONE, NULL}
9125 };
9126
9127 static const FnCallArg GETGID_ARGS[] =
9128 {
9129 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Group name in text"},
9130 {NULL, CF_DATA_TYPE_NONE, NULL}
9131 };
9132
9133 static const FnCallArg GETUID_ARGS[] =
9134 {
9135 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "User name in text"},
9136 {NULL, CF_DATA_TYPE_NONE, NULL}
9137 };
9138
9139 static const FnCallArg GETUSERINFO_ARGS[] =
9140 {
9141 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "User name in text"},
9142 {NULL, CF_DATA_TYPE_NONE, NULL}
9143 };
9144
9145 static const FnCallArg GREP_ARGS[] =
9146 {
9147 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression"},
9148 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"},
9149 {NULL, CF_DATA_TYPE_NONE, NULL}
9150 };
9151
9152 static const FnCallArg GROUPEXISTS_ARGS[] =
9153 {
9154 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Group name or identifier"},
9155 {NULL, CF_DATA_TYPE_NONE, NULL}
9156 };
9157
9158 static const FnCallArg HASH_ARGS[] =
9159 {
9160 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Input text"},
9161 {"md5,sha1,sha256,sha384,sha512", CF_DATA_TYPE_OPTION, "Hash or digest algorithm"},
9162 {NULL, CF_DATA_TYPE_NONE, NULL}
9163 };
9164
9165 static const FnCallArg FILE_HASH_ARGS[] =
9166 {
9167 {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "File object name"},
9168 {"md5,sha1,sha256,sha384,sha512", CF_DATA_TYPE_OPTION, "Hash or digest algorithm"},
9169 {NULL, CF_DATA_TYPE_NONE, NULL}
9170 };
9171
9172 static const FnCallArg HASHMATCH_ARGS[] =
9173 {
9174 {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "Filename to hash"},
9175 {"md5,sha1,sha256,sha384,sha512", CF_DATA_TYPE_OPTION, "Hash or digest algorithm"},
9176 {CF_IDRANGE, CF_DATA_TYPE_STRING, "ASCII representation of hash for comparison"},
9177 {NULL, CF_DATA_TYPE_NONE, NULL}
9178 };
9179
9180 static const FnCallArg HOST2IP_ARGS[] =
9181 {
9182 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Host name in ascii"},
9183 {NULL, CF_DATA_TYPE_NONE, NULL}
9184 };
9185
9186 static const FnCallArg IP2HOST_ARGS[] =
9187 {
9188 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "IP address (IPv4 or IPv6)"},
9189 {NULL, CF_DATA_TYPE_NONE, NULL}
9190 };
9191
9192 static const FnCallArg HOSTINNETGROUP_ARGS[] =
9193 {
9194 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Netgroup name"},
9195 {NULL, CF_DATA_TYPE_NONE, NULL}
9196 };
9197
9198 static const FnCallArg HOSTRANGE_ARGS[] =
9199 {
9200 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Hostname prefix"},
9201 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Enumerated range"},
9202 {NULL, CF_DATA_TYPE_NONE, NULL}
9203 };
9204
9205 static const FnCallArg HOSTSSEEN_ARGS[] =
9206 {
9207 {CF_VALRANGE, CF_DATA_TYPE_INT, "Horizon since last seen in hours"},
9208 {"lastseen,notseen", CF_DATA_TYPE_OPTION, "Complements for selection policy"},
9209 {"name,address", CF_DATA_TYPE_OPTION, "Type of return value desired"},
9210 {NULL, CF_DATA_TYPE_NONE, NULL}
9211 };
9212
9213 static const FnCallArg HOSTSWITHCLASS_ARGS[] =
9214 {
9215 {"[a-zA-Z0-9_]+", CF_DATA_TYPE_STRING, "Class name to look for"},
9216 {"name,address", CF_DATA_TYPE_OPTION, "Type of return value desired"},
9217 {NULL, CF_DATA_TYPE_NONE, NULL}
9218 };
9219
9220 static const FnCallArg IFELSE_ARGS[] =
9221 {
9222 {NULL, CF_DATA_TYPE_NONE, NULL}
9223 };
9224
9225 static const FnCallArg INT_ARGS[] =
9226 {
9227 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Numeric string to convert to integer"},
9228 {NULL, CF_DATA_TYPE_NONE, NULL}
9229 };
9230
9231 static const FnCallArg IPRANGE_ARGS[] =
9232 {
9233 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "IP address range syntax"},
9234 {NULL, CF_DATA_TYPE_NONE, NULL}
9235 };
9236
9237 static const FnCallArg IRANGE_ARGS[] =
9238 {
9239 {CF_INTRANGE, CF_DATA_TYPE_INT, "Integer start of range"},
9240 {CF_INTRANGE, CF_DATA_TYPE_INT, "Integer end of range"},
9241 {NULL, CF_DATA_TYPE_NONE, NULL}
9242 };
9243
9244 static const FnCallArg ISGREATERTHAN_ARGS[] =
9245 {
9246 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Larger string or value"},
9247 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Smaller string or value"},
9248 {NULL, CF_DATA_TYPE_NONE, NULL}
9249 };
9250
9251 static const FnCallArg ISLESSTHAN_ARGS[] =
9252 {
9253 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Smaller string or value"},
9254 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Larger string or value"},
9255 {NULL, CF_DATA_TYPE_NONE, NULL}
9256 };
9257
9258 static const FnCallArg ISNEWERTHAN_ARGS[] =
9259 {
9260 {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "Newer file name"},
9261 {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "Older file name"},
9262 {NULL, CF_DATA_TYPE_NONE, NULL}
9263 };
9264
9265 static const FnCallArg ISVARIABLE_ARGS[] =
9266 {
9267 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Variable identifier"},
9268 {NULL, CF_DATA_TYPE_NONE, NULL}
9269 };
9270
9271 static const FnCallArg JOIN_ARGS[] =
9272 {
9273 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Join glue-string"},
9274 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"},
9275 {NULL, CF_DATA_TYPE_NONE, NULL}
9276 };
9277
9278 static const FnCallArg LASTNODE_ARGS[] =
9279 {
9280 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Input string"},
9281 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Link separator, e.g. /,:"},
9282 {NULL, CF_DATA_TYPE_NONE, NULL}
9283 };
9284
9285 static const FnCallArg LDAPARRAY_ARGS[] =
9286 {
9287 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Array name"},
9288 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "URI"},
9289 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Distinguished name"},
9290 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Filter"},
9291 {"subtree,onelevel,base", CF_DATA_TYPE_OPTION, "Search scope policy"},
9292 {"none,ssl,sasl", CF_DATA_TYPE_OPTION, "Security level"},
9293 {NULL, CF_DATA_TYPE_NONE, NULL}
9294 };
9295
9296 static const FnCallArg LDAPLIST_ARGS[] =
9297 {
9298 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "URI"},
9299 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Distinguished name"},
9300 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Filter"},
9301 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Record name"},
9302 {"subtree,onelevel,base", CF_DATA_TYPE_OPTION, "Search scope policy"},
9303 {"none,ssl,sasl", CF_DATA_TYPE_OPTION, "Security level"},
9304 {NULL, CF_DATA_TYPE_NONE, NULL}
9305 };
9306
9307 static const FnCallArg LDAPVALUE_ARGS[] =
9308 {
9309 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "URI"},
9310 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Distinguished name"},
9311 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Filter"},
9312 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Record name"},
9313 {"subtree,onelevel,base", CF_DATA_TYPE_OPTION, "Search scope policy"},
9314 {"none,ssl,sasl", CF_DATA_TYPE_OPTION, "Security level"},
9315 {NULL, CF_DATA_TYPE_NONE, NULL}
9316 };
9317
9318 static const FnCallArg LSDIRLIST_ARGS[] =
9319 {
9320 {CF_PATHRANGE, CF_DATA_TYPE_STRING, "Path to base directory"},
9321 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression to match files or blank"},
9322 {CF_BOOL, CF_DATA_TYPE_OPTION, "Include the base path in the list"},
9323 {NULL, CF_DATA_TYPE_NONE, NULL}
9324 };
9325
9326 static const FnCallArg MAPLIST_ARGS[] =
9327 {
9328 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Pattern based on $(this) as original text"},
9329 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"},
9330 {NULL, CF_DATA_TYPE_NONE, NULL}
9331 };
9332
9333 static const FnCallArg EXPANDRANGE_ARGS[] =
9334 {
9335 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "String containing numerical range e.g. string[13-47]"},
9336 {CF_VALRANGE, CF_DATA_TYPE_INT, "Step size of numerical increments"},
9337 {NULL, CF_DATA_TYPE_NONE, NULL}
9338 };
9339
9340 static const FnCallArg MAPARRAY_ARGS[] =
9341 {
9342 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Pattern based on $(this.k) and $(this.v) as original text"},
9343 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON, the array variable to map"},
9344 {NULL, CF_DATA_TYPE_NONE, NULL}
9345 };
9346
9347 static const FnCallArg MAPDATA_ARGS[] =
9348 {
9349 {"none,canonify,json,json_pipe", CF_DATA_TYPE_OPTION, "Conversion to apply to the mapped string"},
9350 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Pattern based on $(this.k) and $(this.v) as original text"},
9351 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"},
9352 {NULL, CF_DATA_TYPE_NONE, NULL}
9353 };
9354
9355 static const FnCallArg MERGEDATA_ARGS[] =
9356 {
9357 {NULL, CF_DATA_TYPE_NONE, NULL}
9358 };
9359
9360 static const FnCallArg NOT_ARGS[] =
9361 {
9362 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Class value"},
9363 {NULL, CF_DATA_TYPE_NONE, NULL}
9364 };
9365
9366 static const FnCallArg NOW_ARGS[] =
9367 {
9368 {NULL, CF_DATA_TYPE_NONE, NULL}
9369 };
9370
9371 static const FnCallArg OR_ARGS[] =
9372 {
9373 {NULL, CF_DATA_TYPE_NONE, NULL}
9374 };
9375
9376 static const FnCallArg SUM_ARGS[] =
9377 {
9378 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"},
9379 {NULL, CF_DATA_TYPE_NONE, NULL}
9380 };
9381
9382 static const FnCallArg PRODUCT_ARGS[] =
9383 {
9384 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"},
9385 {NULL, CF_DATA_TYPE_NONE, NULL}
9386 };
9387
9388 static const FnCallArg PACKAGESMATCHING_ARGS[] =
9389 {
9390 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression (unanchored) to match package name"},
9391 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression (unanchored) to match package version"},
9392 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression (unanchored) to match package architecture"},
9393 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression (unanchored) to match package method"},
9394 {NULL, CF_DATA_TYPE_NONE, NULL}
9395 };
9396
9397 static const FnCallArg PEERS_ARGS[] =
9398 {
9399 {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "File name of host list"},
9400 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Comment regex pattern"},
9401 {"2,64", CF_DATA_TYPE_INT, "Peer group size"},
9402 {NULL, CF_DATA_TYPE_NONE, NULL}
9403 };
9404
9405 static const FnCallArg PEERLEADER_ARGS[] =
9406 {
9407 {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "File name of host list"},
9408 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Comment regex pattern"},
9409 {"2,64", CF_DATA_TYPE_INT, "Peer group size"},
9410 {NULL, CF_DATA_TYPE_NONE, NULL}
9411 };
9412
9413 static const FnCallArg PEERLEADERS_ARGS[] =
9414 {
9415 {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "File name of host list"},
9416 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Comment regex pattern"},
9417 {"2,64", CF_DATA_TYPE_INT, "Peer group size"},
9418 {NULL, CF_DATA_TYPE_NONE, NULL}
9419 };
9420
9421 static const FnCallArg RANDOMINT_ARGS[] =
9422 {
9423 {CF_INTRANGE, CF_DATA_TYPE_INT, "Lower inclusive bound"},
9424 {CF_INTRANGE, CF_DATA_TYPE_INT, "Upper exclusive bound"},
9425 {NULL, CF_DATA_TYPE_NONE, NULL}
9426 };
9427
9428 static const FnCallArg HASH_TO_INT_ARGS[] =
9429 {
9430 {CF_INTRANGE, CF_DATA_TYPE_INT, "Lower inclusive bound"},
9431 {CF_INTRANGE, CF_DATA_TYPE_INT, "Upper exclusive bound"},
9432 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Input string to hash"},
9433 {NULL, CF_DATA_TYPE_NONE, NULL}
9434 };
9435
9436 static const FnCallArg STRING_ARGS[] =
9437 {
9438 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Convert argument to string"},
9439 {NULL, CF_DATA_TYPE_NONE, NULL}
9440 };
9441
9442 static const FnCallArg READFILE_ARGS[] =
9443 {
9444 {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "File name"},
9445 {CF_VALRANGE, CF_DATA_TYPE_INT, "Maximum number of bytes to read"},
9446 {NULL, CF_DATA_TYPE_NONE, NULL}
9447 };
9448
9449 static const FnCallArg VALIDDATATYPE_ARGS[] =
9450 {
9451 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Data to validate"},
9452 {NULL, CF_DATA_TYPE_NONE, NULL}
9453 };
9454
9455 static const FnCallArg CLASSFILTERCSV_ARGS[] =
9456 {
9457 {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "File name"},
9458 {CF_BOOL, CF_DATA_TYPE_OPTION, "CSV file has heading"},
9459 {CF_VALRANGE, CF_DATA_TYPE_INT, "Column index to filter by, "
9460 "contains classes"},
9461 {CF_VALRANGE, CF_DATA_TYPE_INT, "Column index to sort by"},
9462 {NULL, CF_DATA_TYPE_NONE, NULL}
9463 };
9464
9465 static const FnCallArg READSTRINGARRAY_ARGS[] =
9466 {
9467 {CF_IDRANGE, CF_DATA_TYPE_STRING, "Array identifier to populate"},
9468 {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "File name to read"},
9469 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regex matching comments"},
9470 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regex to split data"},
9471 {CF_VALRANGE, CF_DATA_TYPE_INT, "Maximum number of entries to read"},
9472 {CF_VALRANGE, CF_DATA_TYPE_INT, "Maximum bytes to read"},
9473 {NULL, CF_DATA_TYPE_NONE, NULL}
9474 };
9475
9476 static const FnCallArg PARSESTRINGARRAY_ARGS[] =
9477 {
9478 {CF_IDRANGE, CF_DATA_TYPE_STRING, "Array identifier to populate"},
9479 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "A string to parse for input data"},
9480 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regex matching comments"},
9481 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regex to split data"},
9482 {CF_VALRANGE, CF_DATA_TYPE_INT, "Maximum number of entries to read"},
9483 {CF_VALRANGE, CF_DATA_TYPE_INT, "Maximum bytes to read"},
9484 {NULL, CF_DATA_TYPE_NONE, NULL}
9485 };
9486
9487 static const FnCallArg READSTRINGLIST_ARGS[] =
9488 {
9489 {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "File name to read"},
9490 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regex matching comments"},
9491 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regex to split data"},
9492 {CF_VALRANGE, CF_DATA_TYPE_INT, "Maximum number of entries to read"},
9493 {CF_VALRANGE, CF_DATA_TYPE_INT, "Maximum bytes to read"},
9494 {NULL, CF_DATA_TYPE_NONE, NULL}
9495 };
9496
9497 static const FnCallArg READDATA_ARGS[] =
9498 {
9499 {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "File name to read"},
9500 {"CSV,YAML,JSON,ENV,auto", CF_DATA_TYPE_OPTION, "Type of data to read"},
9501 {NULL, CF_DATA_TYPE_NONE, NULL}
9502 };
9503
9504 static const FnCallArg VALIDDATA_ARGS[] =
9505 {
9506 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Data to validate"},
9507 {"JSON", CF_DATA_TYPE_OPTION, "Type of data to validate"},
9508 {NULL, CF_DATA_TYPE_NONE, NULL}
9509 };
9510
9511 static const FnCallArg READMODULE_ARGS[] =
9512 {
9513 {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "File name to read and parse from"},
9514 {NULL, CF_DATA_TYPE_NONE, NULL}
9515 };
9516
9517 static const FnCallArg PARSEJSON_ARGS[] =
9518 {
9519 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "JSON string to parse"},
9520 {NULL, CF_DATA_TYPE_NONE, NULL}
9521 };
9522
9523 static const FnCallArg STOREJSON_ARGS[] =
9524 {
9525 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"},
9526 {NULL, CF_DATA_TYPE_NONE, NULL}
9527 };
9528
9529 static const FnCallArg READTCP_ARGS[] =
9530 {
9531 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Host name or IP address of server socket"},
9532 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Port number or service name"},
9533 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Protocol query string"},
9534 {CF_VALRANGE, CF_DATA_TYPE_INT, "Maximum number of bytes to read"},
9535 {NULL, CF_DATA_TYPE_NONE, NULL}
9536 };
9537
9538 static const FnCallArg REGARRAY_ARGS[] =
9539 {
9540 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"},
9541 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression"},
9542 {NULL, CF_DATA_TYPE_NONE, NULL}
9543 };
9544
9545 static const FnCallArg REGCMP_ARGS[] =
9546 {
9547 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression"},
9548 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Match string"},
9549 {NULL, CF_DATA_TYPE_NONE, NULL}
9550 };
9551
9552 static const FnCallArg REGEXTRACT_ARGS[] =
9553 {
9554 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression"},
9555 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Match string"},
9556 {CF_IDRANGE, CF_DATA_TYPE_STRING, "Identifier for back-references"},
9557 {NULL, CF_DATA_TYPE_NONE, NULL}
9558 };
9559
9560 static const FnCallArg DATA_REGEXTRACT_ARGS[] =
9561 {
9562 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression"},
9563 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Match string"},
9564 {NULL, CF_DATA_TYPE_NONE, NULL}
9565 };
9566
9567 static const FnCallArg REGEX_REPLACE_ARGS[] =
9568 {
9569 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Source string"},
9570 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression pattern"},
9571 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Replacement string"},
9572 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "sed/Perl-style options: gmsixUT"},
9573 {NULL, CF_DATA_TYPE_NONE, NULL}
9574 };
9575
9576 static const FnCallArg REGISTRYVALUE_ARGS[] =
9577 {
9578 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Windows registry key"},
9579 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Windows registry value-id"},
9580 {NULL, CF_DATA_TYPE_NONE, NULL}
9581 };
9582
9583 static const FnCallArg REGLINE_ARGS[] =
9584 {
9585 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression"},
9586 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Filename to search"},
9587 {NULL, CF_DATA_TYPE_NONE, NULL}
9588 };
9589
9590 static const FnCallArg REGLIST_ARGS[] =
9591 {
9592 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"},
9593 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression"},
9594 {NULL, CF_DATA_TYPE_NONE, NULL}
9595 };
9596
9597 static const FnCallArg MAKERULE_ARGS[] =
9598 {
9599 {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "Target filename"},
9600 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Source filename or CFEngine variable identifier or inline JSON"},
9601 {NULL, CF_DATA_TYPE_NONE, NULL}
9602 };
9603
9604 static const FnCallArg REGLDAP_ARGS[] =
9605 {
9606 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "URI"},
9607 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Distinguished name"},
9608 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Filter"},
9609 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Record name"},
9610 {"subtree,onelevel,base", CF_DATA_TYPE_OPTION, "Search scope policy"},
9611 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regex to match results"},
9612 {"none,ssl,sasl", CF_DATA_TYPE_OPTION, "Security level"},
9613 {NULL, CF_DATA_TYPE_NONE, NULL}
9614 };
9615
9616 static const FnCallArg REMOTESCALAR_ARGS[] =
9617 {
9618 {CF_IDRANGE, CF_DATA_TYPE_STRING, "Variable identifier"},
9619 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Hostname or IP address of server"},
9620 {CF_BOOL, CF_DATA_TYPE_OPTION, "Use enryption"},
9621 {NULL, CF_DATA_TYPE_NONE, NULL}
9622 };
9623
9624 static const FnCallArg HUB_KNOWLEDGE_ARGS[] =
9625 {
9626 {CF_IDRANGE, CF_DATA_TYPE_STRING, "Variable identifier"},
9627 {NULL, CF_DATA_TYPE_NONE, NULL}
9628 };
9629
9630 static const FnCallArg REMOTECLASSESMATCHING_ARGS[] =
9631 {
9632 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression"},
9633 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Server name or address"},
9634 {CF_BOOL, CF_DATA_TYPE_OPTION, "Use encryption"},
9635 {CF_IDRANGE, CF_DATA_TYPE_STRING, "Return class prefix"},
9636 {NULL, CF_DATA_TYPE_NONE, NULL}
9637 };
9638
9639 static const FnCallArg RETURNSZERO_ARGS[] =
9640 {
9641 {CF_PATHRANGE, CF_DATA_TYPE_STRING, "Command path"},
9642 {"noshell,useshell,powershell", CF_DATA_TYPE_OPTION, "Shell encapsulation option"},
9643 {NULL, CF_DATA_TYPE_NONE, NULL}
9644 };
9645
9646 static const FnCallArg RRANGE_ARGS[] =
9647 {
9648 {CF_REALRANGE, CF_DATA_TYPE_REAL, "Real number, start of range"},
9649 {CF_REALRANGE, CF_DATA_TYPE_REAL, "Real number, end of range"},
9650 {NULL, CF_DATA_TYPE_NONE, NULL}
9651 };
9652
9653 static const FnCallArg SELECTSERVERS_ARGS[] =
9654 {
9655 {CF_NAKEDLRANGE, CF_DATA_TYPE_STRING, "CFEngine list identifier, the list of hosts or addresses to contact"},
9656 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Port number or service name."},
9657 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "A query string"},
9658 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "A regular expression to match success"},
9659 {CF_VALRANGE, CF_DATA_TYPE_INT, "Maximum number of bytes to read from server"},
9660 {CF_IDRANGE, CF_DATA_TYPE_STRING, "Name for array of results"},
9661 {NULL, CF_DATA_TYPE_NONE, NULL}
9662 };
9663
9664 static const FnCallArg SPLAYCLASS_ARGS[] =
9665 {
9666 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Input string for classification"},
9667 {"daily,hourly", CF_DATA_TYPE_OPTION, "Splay time policy"},
9668 {NULL, CF_DATA_TYPE_NONE, NULL}
9669 };
9670
9671 static const FnCallArg SPLITSTRING_ARGS[] =
9672 {
9673 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "A data string"},
9674 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regex to split on"},
9675 {CF_VALRANGE, CF_DATA_TYPE_INT, "Maximum number of pieces"},
9676 {NULL, CF_DATA_TYPE_NONE, NULL}
9677 };
9678
9679 static const FnCallArg STRCMP_ARGS[] =
9680 {
9681 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "String"},
9682 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "String"},
9683 {NULL, CF_DATA_TYPE_NONE, NULL}
9684 };
9685
9686 static const FnCallArg STRFTIME_ARGS[] =
9687 {
9688 {"gmtime,localtime", CF_DATA_TYPE_OPTION, "Use GMT or local time"},
9689 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "A format string"},
9690 {CF_VALRANGE, CF_DATA_TYPE_INT, "The time as a Unix epoch offset"},
9691 {NULL, CF_DATA_TYPE_NONE, NULL}
9692 };
9693
9694 static const FnCallArg STRING_REPLACE_ARGS[] =
9695 {
9696 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Source string"},
9697 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "String to replace"},
9698 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Replacement string"},
9699 {NULL, CF_DATA_TYPE_NONE, NULL}
9700 };
9701
9702 static const FnCallArg STRING_TRIM_ARGS[] =
9703 {
9704 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Input string"},
9705 {NULL, CF_DATA_TYPE_NONE, NULL}
9706 };
9707
9708 static const FnCallArg SUBLIST_ARGS[] =
9709 {
9710 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"},
9711 {"head,tail", CF_DATA_TYPE_OPTION, "Whether to return elements from the head or from the tail of the list"},
9712 {CF_VALRANGE, CF_DATA_TYPE_INT, "Maximum number of elements to return"},
9713 {NULL, CF_DATA_TYPE_NONE, NULL}
9714 };
9715
9716 static const FnCallArg TRANSLATEPATH_ARGS[] =
9717 {
9718 {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "Unix style path"},
9719 {NULL, CF_DATA_TYPE_NONE, NULL}
9720 };
9721
9722 static const FnCallArg USEMODULE_ARGS[] =
9723 {
9724 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Name of module command"},
9725 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Argument string for the module"},
9726 {NULL, CF_DATA_TYPE_NONE, NULL}
9727 };
9728
9729 static const FnCallArg UNIQUE_ARGS[] =
9730 {
9731 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"},
9732 {NULL, CF_DATA_TYPE_NONE, NULL}
9733 };
9734
9735 static const FnCallArg NTH_ARGS[] =
9736 {
9737 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"},
9738 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Offset or key of element to return"},
9739 {NULL, CF_DATA_TYPE_NONE, NULL}
9740 };
9741
9742 static const FnCallArg EVERY_SOME_NONE_ARGS[] =
9743 {
9744 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression or string"},
9745 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"},
9746 {NULL, CF_DATA_TYPE_NONE, NULL}
9747 };
9748
9749 static const FnCallArg USEREXISTS_ARGS[] =
9750 {
9751 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "User name or identifier"},
9752 {NULL, CF_DATA_TYPE_NONE, NULL}
9753 };
9754
9755 static const FnCallArg SORT_ARGS[] =
9756 {
9757 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"},
9758 {"lex,int,real,IP,ip,MAC,mac", CF_DATA_TYPE_OPTION, "Sorting method: lex or int or real (floating point) or IPv4/IPv6 or MAC address"},
9759 {NULL, CF_DATA_TYPE_NONE, NULL}
9760 };
9761
9762 static const FnCallArg REVERSE_ARGS[] =
9763 {
9764 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"},
9765 {NULL, CF_DATA_TYPE_NONE, NULL}
9766 };
9767
9768 static const FnCallArg SHUFFLE_ARGS[] =
9769 {
9770 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"},
9771 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Any seed string"},
9772 {NULL, CF_DATA_TYPE_NONE, NULL}
9773 };
9774
9775 static const FnCallArg STAT_FOLD_ARGS[] =
9776 {
9777 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"},
9778 {NULL, CF_DATA_TYPE_NONE, NULL}
9779 };
9780
9781 static const FnCallArg SETOP_ARGS[] =
9782 {
9783 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine base variable identifier or inline JSON"},
9784 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine filter variable identifier or inline JSON"},
9785 {NULL, CF_DATA_TYPE_NONE, NULL}
9786 };
9787
9788 static const FnCallArg FORMAT_ARGS[] =
9789 {
9790 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine format string"},
9791 {NULL, CF_DATA_TYPE_NONE, NULL}
9792 };
9793
9794 static const FnCallArg EVAL_ARGS[] =
9795 {
9796 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Input string"},
9797 {"math,class", CF_DATA_TYPE_OPTION, "Evaluation type"},
9798 {"infix", CF_DATA_TYPE_OPTION, "Evaluation options"},
9799 {NULL, CF_DATA_TYPE_NONE, NULL}
9800 };
9801
9802 static const FnCallArg BUNDLESMATCHING_ARGS[] =
9803 {
9804 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression"},
9805 {NULL, CF_DATA_TYPE_NONE, NULL}
9806 };
9807
9808 static const FnCallArg XFORM_ARGS[] =
9809 {
9810 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Input string"},
9811 {NULL, CF_DATA_TYPE_NONE, NULL}
9812 };
9813
9814 static const FnCallArg XFORM_SUBSTR_ARGS[] =
9815 {
9816 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Input string"},
9817 {CF_INTRANGE, CF_DATA_TYPE_INT, "Maximum number of characters to return"},
9818 {NULL, CF_DATA_TYPE_NONE, NULL}
9819 };
9820
9821 static const FnCallArg DATASTATE_ARGS[] =
9822 {
9823 {NULL, CF_DATA_TYPE_NONE, NULL}
9824 };
9825
9826 static const FnCallArg BUNDLESTATE_ARGS[] =
9827 {
9828 {CF_IDRANGE, CF_DATA_TYPE_STRING, "Bundle name"},
9829 {NULL, CF_DATA_TYPE_NONE, NULL}
9830 };
9831
9832 static const FnCallArg GETCLASSMETATAGS_ARGS[] =
9833 {
9834 {CF_IDRANGE, CF_DATA_TYPE_STRING, "Class identifier"},
9835 {NULL, CF_DATA_TYPE_NONE, NULL}
9836 };
9837
9838 static const FnCallArg GETVARIABLEMETATAGS_ARGS[] =
9839 {
9840 {CF_IDRANGE, CF_DATA_TYPE_STRING, "Variable identifier"},
9841 {NULL, CF_DATA_TYPE_NONE, NULL}
9842 };
9843
9844 static const FnCallArg DATA_READSTRINGARRAY_ARGS[] =
9845 {
9846 {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "File name to read"},
9847 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regex matching comments"},
9848 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regex to split data"},
9849 {CF_VALRANGE, CF_DATA_TYPE_INT, "Maximum number of entries to read"},
9850 {CF_VALRANGE, CF_DATA_TYPE_INT, "Maximum bytes to read"},
9851 {NULL, CF_DATA_TYPE_NONE, NULL}
9852 };
9853
9854 static const FnCallArg DATA_EXPAND_ARGS[] =
9855 {
9856 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON"},
9857 {NULL, CF_DATA_TYPE_NONE, NULL}
9858 };
9859
9860 static const FnCallArg STRING_MUSTACHE_ARGS[] =
9861 {
9862 {NULL, CF_DATA_TYPE_NONE, NULL}
9863 };
9864
9865 static const FnCallArg CURL_ARGS[] =
9866 {
9867 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "URL to retrieve"},
9868 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "CFEngine variable identifier or inline JSON, can be blank"},
9869 {NULL, CF_DATA_TYPE_NONE, NULL}
9870 };
9871
9872 static const FnCallArg PROCESSEXISTS_ARGS[] =
9873 {
9874 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Regular expression to match process name"},
9875 {NULL, CF_DATA_TYPE_NONE, NULL}
9876 };
9877
9878 static const FnCallArg NETWORK_CONNECTIONS_ARGS[] =
9879 {
9880 {NULL, CF_DATA_TYPE_NONE, NULL}
9881 };
9882
9883 static const FnCallArg CFENGINE_PROMISERS_ARGS[] =
9884 {
9885 {NULL, CF_DATA_TYPE_NONE, NULL}
9886 };
9887
9888 static const FnCallArg CFENGINE_CALLERS_ARGS[] =
9889 {
9890 {NULL, CF_DATA_TYPE_NONE, NULL}
9891 };
9892
9893 static const FnCallArg SYSCTLVALUE_ARGS[] =
9894 {
9895 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "sysctl key"},
9896 {NULL, CF_DATA_TYPE_NONE, NULL}
9897 };
9898
9899 static const FnCallArg DATA_SYSCTLVALUES_ARGS[] =
9900 {
9901 {NULL, CF_DATA_TYPE_NONE, NULL}
9902 };
9903
9904 static const FnCallArg FINDFILES_UP_ARGS[] =
9905 {
9906 {CF_ABSPATHRANGE, CF_DATA_TYPE_STRING, "Path to search from"},
9907 {CF_PATHRANGE, CF_DATA_TYPE_STRING, "Glob pattern to match files"},
9908 {CF_VALRANGE, CF_DATA_TYPE_INT, "Number of levels to search"},
9909 {NULL, CF_DATA_TYPE_NONE, NULL}
9910 };
9911
9912 static const FnCallArg DATATYPE_ARGS[] =
9913 {
9914 {CF_ANYSTRING, CF_DATA_TYPE_STRING, "Variable identifier"},
9915 {CF_BOOL, CF_DATA_TYPE_OPTION, "Enable detailed type decription"},
9916 {NULL, CF_DATA_TYPE_NONE, NULL}
9917 };
9918
9919
9920 /*********************************************************/
9921 /* FnCalls are rvalues in certain promise constraints */
9922 /*********************************************************/
9923
9924 /* see fncall.h enum FnCallType */
9925
9926 const FnCallType CF_FNCALL_TYPES[] =
9927 {
9928 FnCallTypeNew("accessedbefore", CF_DATA_TYPE_CONTEXT, ACCESSEDBEFORE_ARGS, &FnCallIsAccessedBefore, "True if arg1 was accessed before arg2 (atime)",
9929 FNCALL_OPTION_NONE, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL),
9930 FnCallTypeNew("accumulated", CF_DATA_TYPE_INT, ACCUM_ARGS, &FnCallAccumulatedDate, "Convert an accumulated amount of time into a system representation",
9931 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
9932 FnCallTypeNew("ago", CF_DATA_TYPE_INT, AGO_ARGS, &FnCallAgoDate, "Convert a time relative to now to an integer system representation",
9933 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
9934 FnCallTypeNew("and", CF_DATA_TYPE_CONTEXT, AND_ARGS, &FnCallAnd, "Calculate whether all arguments evaluate to true",
9935 FNCALL_OPTION_VARARG, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
9936 FnCallTypeNew("basename", CF_DATA_TYPE_STRING, BASENAME_ARGS, &FnCallBasename, "Retrieves the basename of a filename.",
9937 FNCALL_OPTION_VARARG, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
9938 FnCallTypeNew("bundlesmatching", CF_DATA_TYPE_STRING_LIST, BUNDLESMATCHING_ARGS, &FnCallBundlesMatching, "Find all the bundles that match a regular expression and tags.",
9939 FNCALL_OPTION_VARARG, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
9940 FnCallTypeNew("bundlestate", CF_DATA_TYPE_CONTAINER, BUNDLESTATE_ARGS, &FnCallBundlestate, "Construct a container of the variables in a bundle and the global class state",
9941 FNCALL_OPTION_NONE, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL),
9942 FnCallTypeNew("canonify", CF_DATA_TYPE_STRING, CANONIFY_ARGS, &FnCallCanonify, "Convert an abitrary string into a legal class name",
9943 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
9944 FnCallTypeNew("canonifyuniquely", CF_DATA_TYPE_STRING, CANONIFY_ARGS, &FnCallCanonify, "Convert an abitrary string into a unique legal class name",
9945 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
9946 FnCallTypeNew("concat", CF_DATA_TYPE_STRING, CONCAT_ARGS, &FnCallConcat, "Concatenate all arguments into string",
9947 FNCALL_OPTION_VARARG, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
9948 FnCallTypeNew("cf_version_minimum", CF_DATA_TYPE_CONTEXT, CFVERSION_ARGS, &FnCallVersionMinimum, "True if local CFEngine version is newer than or equal to input",
9949 FNCALL_OPTION_NONE, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL),
9950 FnCallTypeNew("cf_version_after", CF_DATA_TYPE_CONTEXT, CFVERSION_ARGS, &FnCallVersionAfter, "True if local CFEngine version is newer than input",
9951 FNCALL_OPTION_NONE, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL),
9952 FnCallTypeNew("cf_version_maximum", CF_DATA_TYPE_CONTEXT, CFVERSION_ARGS, &FnCallVersionMaximum, "True if local CFEngine version is older than or equal to input",
9953 FNCALL_OPTION_NONE, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL),
9954 FnCallTypeNew("cf_version_before", CF_DATA_TYPE_CONTEXT, CFVERSION_ARGS, &FnCallVersionBefore, "True if local CFEngine version is older than input",
9955 FNCALL_OPTION_NONE, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL),
9956 FnCallTypeNew("cf_version_at", CF_DATA_TYPE_CONTEXT, CFVERSION_ARGS, &FnCallVersionAt, "True if local CFEngine version is the same as input",
9957 FNCALL_OPTION_NONE, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL),
9958 FnCallTypeNew("cf_version_between", CF_DATA_TYPE_CONTEXT, CFVERSIONBETWEEN_ARGS, &FnCallVersionBetween, "True if local CFEngine version is between two input versions (inclusive)",
9959 FNCALL_OPTION_NONE, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL),
9960 FnCallTypeNew("changedbefore", CF_DATA_TYPE_CONTEXT, CHANGEDBEFORE_ARGS, &FnCallIsChangedBefore, "True if arg1 was changed before arg2 (ctime)",
9961 FNCALL_OPTION_NONE, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL),
9962 FnCallTypeNew("classify", CF_DATA_TYPE_CONTEXT, CLASSIFY_ARGS, &FnCallClassify, "True if the canonicalization of the argument is a currently defined class",
9963 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
9964 FnCallTypeNew("classmatch", CF_DATA_TYPE_CONTEXT, CLASSMATCH_ARGS, &FnCallClassesMatching, "True if the regular expression matches any currently defined class",
9965 FNCALL_OPTION_VARARG, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL),
9966 FnCallTypeNew("classesmatching", CF_DATA_TYPE_STRING_LIST, CLASSMATCH_ARGS, &FnCallClassesMatching, "List the defined classes matching regex arg1 and tag regexes arg2,arg3,...",
9967 FNCALL_OPTION_VARARG, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL),
9968 FnCallTypeNew("classfiltercsv", CF_DATA_TYPE_CONTAINER, CLASSFILTERCSV_ARGS, &FnCallClassFilterCsv, "Parse a CSV file and create data container filtered by defined classes",
9969 FNCALL_OPTION_VARARG, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL),
9970 FnCallTypeNew("countclassesmatching", CF_DATA_TYPE_INT, CLASSMATCH_ARGS, &FnCallClassesMatching, "Count the number of defined classes matching regex arg1",
9971 FNCALL_OPTION_VARARG, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL),
9972 FnCallTypeNew("countlinesmatching", CF_DATA_TYPE_INT, COUNTLINESMATCHING_ARGS, &FnCallCountLinesMatching, "Count the number of lines matching regex arg1 in file arg2",
9973 FNCALL_OPTION_NONE, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL),
9974 FnCallTypeNew("url_get", CF_DATA_TYPE_CONTAINER, CURL_ARGS, &FnCallUrlGet, "Retrieve the contents at a URL with libcurl",
9975 FNCALL_OPTION_NONE, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL),
9976 FnCallTypeNew("datastate", CF_DATA_TYPE_CONTAINER, DATASTATE_ARGS, &FnCallDatastate, "Construct a container of the variable and class state",
9977 FNCALL_OPTION_NONE, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL),
9978 FnCallTypeNew("difference", CF_DATA_TYPE_STRING_LIST, SETOP_ARGS, &FnCallSetop, "Returns all the unique elements of list or array or data container arg1 that are not in list or array or data container arg2",
9979 FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
9980 FnCallTypeNew("dirname", CF_DATA_TYPE_STRING, DIRNAME_ARGS, &FnCallDirname, "Return the parent directory name for given path",
9981 FNCALL_OPTION_NONE, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL),
9982 FnCallTypeNew("diskfree", CF_DATA_TYPE_INT, DISKFREE_ARGS, &FnCallDiskFree, "Return the free space (in KB) available on the directory's current partition (0 if not found)",
9983 FNCALL_OPTION_NONE, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL),
9984 FnCallTypeNew("escape", CF_DATA_TYPE_STRING, ESCAPE_ARGS, &FnCallEscape, "Escape regular expression characters in a string",
9985 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
9986 FnCallTypeNew("eval", CF_DATA_TYPE_STRING, EVAL_ARGS, &FnCallEval, "Evaluate a mathematical expression",
9987 FNCALL_OPTION_VARARG, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
9988 FnCallTypeNew("every", CF_DATA_TYPE_CONTEXT, EVERY_SOME_NONE_ARGS, &FnCallEverySomeNone, "True if every element in the list or array or data container matches the given regular expression",
9989 FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
9990 FnCallTypeNew("execresult", CF_DATA_TYPE_STRING, EXECRESULT_ARGS, &FnCallExecResult, "Execute named command and assign output to variable",
9991 FNCALL_OPTION_CACHED | FNCALL_OPTION_VARARG | FNCALL_OPTION_UNSAFE, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL),
9992 FnCallTypeNew("execresult_as_data", CF_DATA_TYPE_CONTAINER, EXECRESULT_ARGS, &FnCallExecResult, "Execute command and return exit code and output in data container",
9993 FNCALL_OPTION_CACHED | FNCALL_OPTION_VARARG | FNCALL_OPTION_UNSAFE, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL),
9994 FnCallTypeNew("file_hash", CF_DATA_TYPE_STRING, FILE_HASH_ARGS, &FnCallHandlerHash, "Return the hash of file arg1, type arg2 and assign to a variable",
9995 FNCALL_OPTION_NONE, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL),
9996 FnCallTypeNew("expandrange", CF_DATA_TYPE_STRING_LIST, EXPANDRANGE_ARGS, &FnCallExpandRange, "Expand a name as a list of names numered according to a range",
9997 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
9998 FnCallTypeNew("fileexists", CF_DATA_TYPE_CONTEXT, FILESTAT_ARGS, &FnCallFileStat, "True if the named file can be accessed",
9999 FNCALL_OPTION_NONE, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL),
10000 FnCallTypeNew("filesexist", CF_DATA_TYPE_CONTEXT, FILESEXIST_ARGS, &FnCallFileSexist, "True if the named list of files can ALL be accessed",
10001 FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL),
10002 FnCallTypeNew("filesize", CF_DATA_TYPE_INT, FILESTAT_ARGS, &FnCallFileStat, "Returns the size in bytes of the file",
10003 FNCALL_OPTION_NONE, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL),
10004 FnCallTypeNew("filestat", CF_DATA_TYPE_STRING, FILESTAT_DETAIL_ARGS, &FnCallFileStatDetails, "Returns stat() details of the file",
10005 FNCALL_OPTION_NONE, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL),
10006 FnCallTypeNew("filter", CF_DATA_TYPE_STRING_LIST, FILTER_ARGS, &FnCallFilter, "Similarly to grep(), filter the list or array or data container arg2 for matches to arg2. The matching can be as a regular expression or exactly depending on arg3. The matching can be inverted with arg4. A maximum on the number of matches returned can be set with arg5.",
10007 FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10008 FnCallTypeNew("findfiles", CF_DATA_TYPE_STRING_LIST, FINDFILES_ARGS, &FnCallFindfiles, "Find files matching a shell glob pattern",
10009 FNCALL_OPTION_VARARG, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL),
10010 FnCallTypeNew("findprocesses", CF_DATA_TYPE_CONTAINER, PROCESSEXISTS_ARGS, &FnCallProcessExists, "Returns data container of processes matching the regular expression",
10011 FNCALL_OPTION_CACHED, FNCALL_CATEGORY_SYSTEM, SYNTAX_STATUS_NORMAL),
10012 FnCallTypeNew("format", CF_DATA_TYPE_STRING, FORMAT_ARGS, &FnCallFormat, "Applies a list of string values in arg2,arg3... to a string format in arg1 with sprintf() rules",
10013 FNCALL_OPTION_VARARG, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10014 FnCallTypeNew("getclassmetatags", CF_DATA_TYPE_STRING_LIST, GETCLASSMETATAGS_ARGS, &FnCallGetMetaTags, "Collect the class arg1's meta tags into an slist, optionally collecting only tag key arg2",
10015 FNCALL_OPTION_VARARG, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL),
10016 FnCallTypeNew("getenv", CF_DATA_TYPE_STRING, GETENV_ARGS, &FnCallGetEnv, "Return the environment variable named arg1, truncated at arg2 characters",
10017 FNCALL_OPTION_NONE, FNCALL_CATEGORY_SYSTEM, SYNTAX_STATUS_NORMAL),
10018 FnCallTypeNew("getfields", CF_DATA_TYPE_INT, GETFIELDS_ARGS, &FnCallGetFields, "Get an array of fields in the lines matching regex arg1 in file arg2, split on regex arg3 as array name arg4",
10019 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10020 FnCallTypeNew("getgid", CF_DATA_TYPE_INT, GETGID_ARGS, &FnCallGetGid, "Return the integer group id of the named group on this host",
10021 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10022 FnCallTypeNew("getindices", CF_DATA_TYPE_STRING_LIST, GETINDICES_ARGS, &FnCallGetIndices, "Get a list of keys to the list or array or data container whose id is the argument and assign to variable",
10023 FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10024 FnCallTypeNew("getuid", CF_DATA_TYPE_INT, GETUID_ARGS, &FnCallGetUid, "Return the integer user id of the named user on this host",
10025 FNCALL_OPTION_NONE, FNCALL_CATEGORY_SYSTEM, SYNTAX_STATUS_NORMAL),
10026 FnCallTypeNew("getusers", CF_DATA_TYPE_STRING_LIST, GETUSERS_ARGS, &FnCallGetUsers, "Get a list of all system users defined, minus those names defined in arg1 and uids in arg2",
10027 FNCALL_OPTION_NONE, FNCALL_CATEGORY_SYSTEM, SYNTAX_STATUS_NORMAL),
10028 FnCallTypeNew("getuserinfo", CF_DATA_TYPE_CONTAINER, GETUSERINFO_ARGS, &FnCallGetUserInfo, "Get a data container describing user arg1, defaulting to current user",
10029 FNCALL_OPTION_VARARG, FNCALL_CATEGORY_SYSTEM, SYNTAX_STATUS_NORMAL),
10030 FnCallTypeNew("getvalues", CF_DATA_TYPE_STRING_LIST, GETINDICES_ARGS, &FnCallGetValues, "Get a list of values in the list or array or data container arg1",
10031 FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10032 FnCallTypeNew("getvariablemetatags", CF_DATA_TYPE_STRING_LIST, GETVARIABLEMETATAGS_ARGS, &FnCallGetMetaTags, "Collect the variable arg1's meta tags into an slist, optionally collecting only tag key arg2",
10033 FNCALL_OPTION_VARARG, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL),
10034 FnCallTypeNew("grep", CF_DATA_TYPE_STRING_LIST, GREP_ARGS, &FnCallGrep, "Extract the sub-list if items matching the regular expression in arg1 of the list or array or data container arg2",
10035 FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10036 FnCallTypeNew("groupexists", CF_DATA_TYPE_CONTEXT, GROUPEXISTS_ARGS, &FnCallGroupExists, "True if group or numerical id exists on this host",
10037 FNCALL_OPTION_NONE, FNCALL_CATEGORY_SYSTEM, SYNTAX_STATUS_NORMAL),
10038 FnCallTypeNew("hash", CF_DATA_TYPE_STRING, HASH_ARGS, &FnCallHandlerHash, "Return the hash of arg1, type arg2 and assign to a variable",
10039 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10040 FnCallTypeNew("hashmatch", CF_DATA_TYPE_CONTEXT, HASHMATCH_ARGS, &FnCallHashMatch, "Compute the hash of arg1, of type arg2 and test if it matches the value in arg3",
10041 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10042 FnCallTypeNew("host2ip", CF_DATA_TYPE_STRING, HOST2IP_ARGS, &FnCallHost2IP, "Returns the primary name-service IP address for the named host",
10043 FNCALL_OPTION_CACHED, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL),
10044 FnCallTypeNew("ip2host", CF_DATA_TYPE_STRING, IP2HOST_ARGS, &FnCallIP2Host, "Returns the primary name-service host name for the IP address",
10045 FNCALL_OPTION_CACHED, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL),
10046 FnCallTypeNew("hostinnetgroup", CF_DATA_TYPE_CONTEXT, HOSTINNETGROUP_ARGS, &FnCallHostInNetgroup, "True if the current host is in the named netgroup",
10047 FNCALL_OPTION_NONE, FNCALL_CATEGORY_SYSTEM, SYNTAX_STATUS_NORMAL),
10048 FnCallTypeNew("hostrange", CF_DATA_TYPE_CONTEXT, HOSTRANGE_ARGS, &FnCallHostRange, "True if the current host lies in the range of enumerated hostnames specified",
10049 FNCALL_OPTION_NONE, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL),
10050 FnCallTypeNew("hostsseen", CF_DATA_TYPE_STRING_LIST, HOSTSSEEN_ARGS, &FnCallHostsSeen, "Extract the list of hosts last seen/not seen within the last arg1 hours",
10051 FNCALL_OPTION_NONE, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL),
10052 FnCallTypeNew("hostswithclass", CF_DATA_TYPE_STRING_LIST, HOSTSWITHCLASS_ARGS, &FnCallHostsWithClass, "Extract the list of hosts with the given class set from the hub database (enterprise extension)",
10053 FNCALL_OPTION_NONE, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL),
10054 FnCallTypeNew("hubknowledge", CF_DATA_TYPE_STRING, HUB_KNOWLEDGE_ARGS, &FnCallHubKnowledge, "Read global knowledge from the hub host by id (enterprise extension)",
10055 FNCALL_OPTION_CACHED, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL),
10056 FnCallTypeNew("ifelse", CF_DATA_TYPE_STRING, IFELSE_ARGS, &FnCallIfElse, "Do If-ElseIf-ElseIf-...-Else evaluation of arguments",
10057 FNCALL_OPTION_VARARG, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10058 FnCallTypeNew("int", CF_DATA_TYPE_INT, INT_ARGS, &FnCallInt, "Convert numeric string to int",
10059 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10060 FnCallTypeNew("intersection", CF_DATA_TYPE_STRING_LIST, SETOP_ARGS, &FnCallSetop, "Returns all the unique elements of list or array or data container arg1 that are also in list or array or data container arg2",
10061 FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10062 FnCallTypeNew("iprange", CF_DATA_TYPE_CONTEXT, IPRANGE_ARGS, &FnCallIPRange, "True if the current host lies in the range of IP addresses specified in arg1 (can be narrowed to specific interfaces with arg2).",
10063 FNCALL_OPTION_VARARG, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL),
10064 FnCallTypeNew("isipinsubnet", CF_DATA_TYPE_CONTEXT, IPRANGE_ARGS, &FnCallIsIpInSubnet, "True if an IP address specified in arg2, arg3, ... lies in the range of IP addresses specified in arg1",
10065 FNCALL_OPTION_VARARG, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL),
10066 FnCallTypeNew("irange", CF_DATA_TYPE_INT_RANGE, IRANGE_ARGS, &FnCallIRange, "Define a range of integer values for cfengine internal use",
10067 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10068 FnCallTypeNew("isdir", CF_DATA_TYPE_CONTEXT, FILESTAT_ARGS, &FnCallFileStat, "True if the named object is a directory",
10069 FNCALL_OPTION_NONE, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL),
10070 FnCallTypeNew("isexecutable", CF_DATA_TYPE_CONTEXT, FILESTAT_ARGS, &FnCallFileStat, "True if the named object has execution rights for the current user",
10071 FNCALL_OPTION_NONE, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL),
10072 FnCallTypeNew("isgreaterthan", CF_DATA_TYPE_CONTEXT, ISGREATERTHAN_ARGS, &FnCallIsLessGreaterThan, "True if arg1 is numerically greater than arg2, else compare strings like strcmp",
10073 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10074 FnCallTypeNew("islessthan", CF_DATA_TYPE_CONTEXT, ISLESSTHAN_ARGS, &FnCallIsLessGreaterThan, "True if arg1 is numerically less than arg2, else compare strings like NOT strcmp",
10075 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10076 FnCallTypeNew("islink", CF_DATA_TYPE_CONTEXT, FILESTAT_ARGS, &FnCallFileStat, "True if the named object is a symbolic link",
10077 FNCALL_OPTION_NONE, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL),
10078 FnCallTypeNew("isnewerthan", CF_DATA_TYPE_CONTEXT, ISNEWERTHAN_ARGS, &FnCallIsNewerThan, "True if arg1 is newer (modified later) than arg2 (mtime)",
10079 FNCALL_OPTION_NONE, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL),
10080 FnCallTypeNew("isplain", CF_DATA_TYPE_CONTEXT, FILESTAT_ARGS, &FnCallFileStat, "True if the named object is a plain/regular file",
10081 FNCALL_OPTION_NONE, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL),
10082 FnCallTypeNew("isvariable", CF_DATA_TYPE_CONTEXT, ISVARIABLE_ARGS, &FnCallIsVariable, "True if the named variable is defined",
10083 FNCALL_OPTION_NONE, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL),
10084 FnCallTypeNew("join", CF_DATA_TYPE_STRING, JOIN_ARGS, &FnCallJoin, "Join the items of list or array or data container arg2 into a string, using the conjunction in arg1",
10085 FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10086 FnCallTypeNew("lastnode", CF_DATA_TYPE_STRING, LASTNODE_ARGS, &FnCallLastNode, "Extract the last of a separated string, e.g. filename from a path",
10087 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10088 FnCallTypeNew("laterthan", CF_DATA_TYPE_CONTEXT, LATERTHAN_ARGS, &FnCallLaterThan, "True if the current time is later than the given date",
10089 FNCALL_OPTION_NONE, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL),
10090 FnCallTypeNew("ldaparray", CF_DATA_TYPE_CONTEXT, LDAPARRAY_ARGS, &FnCallLDAPArray, "Extract all values from an ldap record",
10091 FNCALL_OPTION_NONE, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL),
10092 FnCallTypeNew("ldaplist", CF_DATA_TYPE_STRING_LIST, LDAPLIST_ARGS, &FnCallLDAPList, "Extract all named values from multiple ldap records",
10093 FNCALL_OPTION_CACHED, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL),
10094 FnCallTypeNew("ldapvalue", CF_DATA_TYPE_STRING, LDAPVALUE_ARGS, &FnCallLDAPValue, "Extract the first matching named value from ldap",
10095 FNCALL_OPTION_CACHED, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL),
10096 FnCallTypeNew("lsdir", CF_DATA_TYPE_STRING_LIST, LSDIRLIST_ARGS, &FnCallLsDir, "Return a list of files in a directory matching a regular expression",
10097 FNCALL_OPTION_NONE, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL),
10098 FnCallTypeNew("makerule", CF_DATA_TYPE_STRING, MAKERULE_ARGS, &FnCallMakerule, "True if the target file arg1 does not exist or a source file arg2 or the list or array or data container arg2 is newer",
10099 FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10100 FnCallTypeNew("maparray", CF_DATA_TYPE_STRING_LIST, MAPARRAY_ARGS, &FnCallMapData, "Return a list with each element mapped from a list or array or data container by a pattern based on $(this.k) and $(this.v)",
10101 FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10102 FnCallTypeNew("mapdata", CF_DATA_TYPE_CONTAINER, MAPDATA_ARGS, &FnCallMapData, "Return a data container with each element parsed from a JSON string applied to every key-value pair of the list or array or data container, given as $(this.k) and $(this.v)",
10103 FNCALL_OPTION_COLLECTING|FNCALL_OPTION_DELAYED_EVALUATION, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10104 FnCallTypeNew("maplist", CF_DATA_TYPE_STRING_LIST, MAPLIST_ARGS, &FnCallMapList, "Return a mapping of the list or array or data container with each element modified by a pattern based $(this)",
10105 FNCALL_OPTION_COLLECTING|FNCALL_OPTION_DELAYED_EVALUATION, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10106 FnCallTypeNew("mergedata", CF_DATA_TYPE_CONTAINER, MERGEDATA_ARGS, &FnCallMergeData, "Merge two or more items, each a list or array or data container",
10107 FNCALL_OPTION_COLLECTING|FNCALL_OPTION_VARARG, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10108 FnCallTypeNew("none", CF_DATA_TYPE_CONTEXT, EVERY_SOME_NONE_ARGS, &FnCallEverySomeNone, "True if no element in the list or array or data container matches the given regular expression",
10109 FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10110 FnCallTypeNew("not", CF_DATA_TYPE_CONTEXT, NOT_ARGS, &FnCallNot, "Calculate whether argument is false",
10111 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10112 FnCallTypeNew("now", CF_DATA_TYPE_INT, NOW_ARGS, &FnCallNow, "Convert the current time into system representation",
10113 FNCALL_OPTION_NONE, FNCALL_CATEGORY_SYSTEM, SYNTAX_STATUS_NORMAL),
10114 FnCallTypeNew("nth", CF_DATA_TYPE_STRING, NTH_ARGS, &FnCallNth, "Get the element at arg2 in list or array or data container arg1",
10115 FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10116 FnCallTypeNew("on", CF_DATA_TYPE_INT, DATE_ARGS, &FnCallOn, "Convert an exact date/time to an integer system representation",
10117 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10118 FnCallTypeNew("or", CF_DATA_TYPE_CONTEXT, OR_ARGS, &FnCallOr, "Calculate whether any argument evaluates to true",
10119 FNCALL_OPTION_VARARG, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10120 FnCallTypeNew("packagesmatching", CF_DATA_TYPE_CONTAINER, PACKAGESMATCHING_ARGS, &FnCallPackagesMatching, "List the installed packages (\"name,version,arch,manager\") matching regex arg1=name,arg2=version,arg3=arch,arg4=method",
10121 FNCALL_OPTION_NONE, FNCALL_CATEGORY_SYSTEM, SYNTAX_STATUS_NORMAL),
10122 FnCallTypeNew("packageupdatesmatching", CF_DATA_TYPE_CONTAINER, PACKAGESMATCHING_ARGS, &FnCallPackagesMatching, "List the available patches (\"name,version,arch,manager\") matching regex arg1=name,arg2=version,arg3=arch,arg4=method. Enterprise only.",
10123 FNCALL_OPTION_NONE, FNCALL_CATEGORY_SYSTEM, SYNTAX_STATUS_NORMAL),
10124 FnCallTypeNew("parseintarray", CF_DATA_TYPE_INT, PARSESTRINGARRAY_ARGS, &FnCallParseIntArray, "Read an array of integers from a string, indexing by first entry on line and sequentially within each line; return line count",
10125 FNCALL_OPTION_NONE, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL),
10126 FnCallTypeNew("parsejson", CF_DATA_TYPE_CONTAINER, PARSEJSON_ARGS, &FnCallParseJson, "Parse a JSON data container from a string",
10127 FNCALL_OPTION_NONE, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL),
10128 FnCallTypeNew("parserealarray", CF_DATA_TYPE_INT, PARSESTRINGARRAY_ARGS, &FnCallParseRealArray, "Read an array of real numbers from a string, indexing by first entry on line and sequentially within each line; return line count",
10129 FNCALL_OPTION_NONE, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL),
10130 FnCallTypeNew("parsestringarray", CF_DATA_TYPE_INT, PARSESTRINGARRAY_ARGS, &FnCallParseStringArray, "Read an array of strings from a string, indexing by first word on line and sequentially within each line; return line count",
10131 FNCALL_OPTION_NONE, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL),
10132 FnCallTypeNew("parsestringarrayidx", CF_DATA_TYPE_INT, PARSESTRINGARRAY_ARGS, &FnCallParseStringArrayIndex, "Read an array of strings from a string, indexing by line number and sequentially within each line; return line count",
10133 FNCALL_OPTION_NONE, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL),
10134 FnCallTypeNew("parseyaml", CF_DATA_TYPE_CONTAINER, PARSEJSON_ARGS, &FnCallParseJson, "Parse a data container from a YAML string",
10135 FNCALL_OPTION_NONE, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL),
10136 FnCallTypeNew("peers", CF_DATA_TYPE_STRING_LIST, PEERS_ARGS, &FnCallPeers, "Get a list of peers (not including ourself) from the partition to which we belong",
10137 FNCALL_OPTION_NONE, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL),
10138 FnCallTypeNew("peerleader", CF_DATA_TYPE_STRING, PEERLEADER_ARGS, &FnCallPeerLeader, "Get the assigned peer-leader of the partition to which we belong",
10139 FNCALL_OPTION_NONE, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL),
10140 FnCallTypeNew("peerleaders", CF_DATA_TYPE_STRING_LIST, PEERLEADERS_ARGS, &FnCallPeerLeaders, "Get a list of peer leaders from the named partitioning",
10141 FNCALL_OPTION_NONE, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL),
10142 FnCallTypeNew("processexists", CF_DATA_TYPE_CONTEXT, PROCESSEXISTS_ARGS, &FnCallProcessExists, "True if the regular expression matches a process",
10143 FNCALL_OPTION_CACHED, FNCALL_CATEGORY_SYSTEM, SYNTAX_STATUS_NORMAL),
10144 FnCallTypeNew("randomint", CF_DATA_TYPE_INT, RANDOMINT_ARGS, &FnCallRandomInt, "Generate a random integer between the given limits, excluding the upper",
10145 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10146 FnCallTypeNew("hash_to_int", CF_DATA_TYPE_INT, HASH_TO_INT_ARGS, &FnCallHashToInt, "Generate an integer in given range based on string hash",
10147 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10148
10149 FnCallTypeNew("string", CF_DATA_TYPE_STRING, STRING_ARGS, &FnCallString, "Convert argument to string",
10150 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10151 // read functions for reading from file
10152 FnCallTypeNew("readdata", CF_DATA_TYPE_CONTAINER, READDATA_ARGS, &FnCallReadData, "Parse a YAML, JSON, CSV, etc. file and return a JSON data container with the contents",
10153 FNCALL_OPTION_NONE, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL),
10154 FnCallTypeNew("readfile", CF_DATA_TYPE_STRING, READFILE_ARGS, &FnCallReadFile, "Read max number of bytes from named file and assign to variable",
10155 FNCALL_OPTION_VARARG, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL),
10156 FnCallTypeNew("readcsv", CF_DATA_TYPE_CONTAINER, READFILE_ARGS, &FnCallReadCsv, "Parse a CSV file and return a JSON data container with the contents",
10157 FNCALL_OPTION_VARARG, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL),
10158 FnCallTypeNew("readenvfile", CF_DATA_TYPE_CONTAINER, READFILE_ARGS, &FnCallReadEnvFile, "Parse a ENV-style file and return a JSON data container with the contents",
10159 FNCALL_OPTION_VARARG, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL),
10160 FnCallTypeNew("readjson", CF_DATA_TYPE_CONTAINER, READFILE_ARGS, &FnCallReadJson, "Read a JSON data container from a file",
10161 FNCALL_OPTION_VARARG, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL),
10162 FnCallTypeNew("readyaml", CF_DATA_TYPE_CONTAINER, READFILE_ARGS, &FnCallReadYaml, "Read a data container from a YAML file",
10163 FNCALL_OPTION_VARARG, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL),
10164 FnCallTypeNew("read_module_protocol", CF_DATA_TYPE_CONTEXT, READMODULE_ARGS, &FnCallReadModuleProtocol, "Parse a file containing module protocol output (for cached modules)",
10165 FNCALL_OPTION_NONE, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL),
10166 FnCallTypeNew("readintarray", CF_DATA_TYPE_INT, READSTRINGARRAY_ARGS, &FnCallReadIntArray, "Read an array of integers from a file, indexed by first entry on line and sequentially on each line; return line count",
10167 FNCALL_OPTION_NONE, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL),
10168 FnCallTypeNew("readintlist", CF_DATA_TYPE_INT_LIST, READSTRINGLIST_ARGS, &FnCallReadIntList, "Read and assign a list variable from a file of separated ints",
10169 FNCALL_OPTION_NONE, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL),
10170 FnCallTypeNew("readrealarray", CF_DATA_TYPE_INT, READSTRINGARRAY_ARGS, &FnCallReadRealArray, "Read an array of real numbers from a file, indexed by first entry on line and sequentially on each line; return line count",
10171 FNCALL_OPTION_NONE, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL),
10172 FnCallTypeNew("readreallist", CF_DATA_TYPE_REAL_LIST, READSTRINGLIST_ARGS, &FnCallReadRealList, "Read and assign a list variable from a file of separated real numbers",
10173 FNCALL_OPTION_NONE, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL),
10174 FnCallTypeNew("readstringarray", CF_DATA_TYPE_INT, READSTRINGARRAY_ARGS, &FnCallReadStringArray, "Read an array of strings from a file, indexed by first entry on line and sequentially on each line; return line count",
10175 FNCALL_OPTION_NONE, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL),
10176 FnCallTypeNew("readstringarrayidx", CF_DATA_TYPE_INT, READSTRINGARRAY_ARGS, &FnCallReadStringArrayIndex, "Read an array of strings from a file, indexed by line number and sequentially on each line; return line count",
10177 FNCALL_OPTION_NONE, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL),
10178 FnCallTypeNew("readstringlist", CF_DATA_TYPE_STRING_LIST, READSTRINGLIST_ARGS, &FnCallReadStringList, "Read and assign a list variable from a file of separated strings",
10179 FNCALL_OPTION_NONE, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL),
10180 FnCallTypeNew("readtcp", CF_DATA_TYPE_STRING, READTCP_ARGS, &FnCallReadTcp, "Connect to tcp port, send string and assign result to variable",
10181 FNCALL_OPTION_CACHED, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL),
10182
10183 // reg functions for regex
10184 FnCallTypeNew("regarray", CF_DATA_TYPE_CONTEXT, REGARRAY_ARGS, &FnCallRegList, "True if the regular expression in arg1 matches any item in the list or array or data container arg2",
10185 FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10186 FnCallTypeNew("regcmp", CF_DATA_TYPE_CONTEXT, REGCMP_ARGS, &FnCallRegCmp, "True if arg1 is a regular expression matching that matches string arg2",
10187 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10188 FnCallTypeNew("regextract", CF_DATA_TYPE_CONTEXT, REGEXTRACT_ARGS, &FnCallRegExtract, "True if the regular expression in arg 1 matches the string in arg2 and sets a non-empty array of backreferences named arg3",
10189 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10190 FnCallTypeNew("registryvalue", CF_DATA_TYPE_STRING, REGISTRYVALUE_ARGS, &FnCallRegistryValue, "Returns a value for an MS-Win registry key,value pair",
10191 FNCALL_OPTION_NONE, FNCALL_CATEGORY_SYSTEM, SYNTAX_STATUS_NORMAL),
10192 FnCallTypeNew("regline", CF_DATA_TYPE_CONTEXT, REGLINE_ARGS, &FnCallRegLine, "True if the regular expression in arg1 matches a line in file arg2",
10193 FNCALL_OPTION_NONE, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL),
10194 FnCallTypeNew("reglist", CF_DATA_TYPE_CONTEXT, REGLIST_ARGS, &FnCallRegList, "True if the regular expression in arg2 matches any item in the list or array or data container whose id is arg1",
10195 FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10196 FnCallTypeNew("regldap", CF_DATA_TYPE_CONTEXT, REGLDAP_ARGS, &FnCallRegLDAP, "True if the regular expression in arg6 matches a value item in an ldap search",
10197 FNCALL_OPTION_CACHED, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL),
10198 FnCallTypeNew("remotescalar", CF_DATA_TYPE_STRING, REMOTESCALAR_ARGS, &FnCallRemoteScalar, "Read a scalar value from a remote cfengine server",
10199 FNCALL_OPTION_CACHED, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL),
10200
10201 FnCallTypeNew("remoteclassesmatching", CF_DATA_TYPE_CONTEXT, REMOTECLASSESMATCHING_ARGS, &FnCallRemoteClassesMatching, "Read persistent classes matching a regular expression from a remote cfengine server and add them into local context with prefix",
10202 FNCALL_OPTION_NONE, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL),
10203 FnCallTypeNew("returnszero", CF_DATA_TYPE_CONTEXT, RETURNSZERO_ARGS, &FnCallReturnsZero, "True if named shell command has exit status zero",
10204 FNCALL_OPTION_CACHED | FNCALL_OPTION_UNSAFE, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL),
10205 FnCallTypeNew("rrange", CF_DATA_TYPE_REAL_RANGE, RRANGE_ARGS, &FnCallRRange, "Define a range of real numbers for cfengine internal use",
10206 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10207 FnCallTypeNew("reverse", CF_DATA_TYPE_STRING_LIST, REVERSE_ARGS, &FnCallReverse, "Reverse a list or array or data container",
10208 FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10209 FnCallTypeNew("selectservers", CF_DATA_TYPE_INT, SELECTSERVERS_ARGS, &FnCallSelectServers, "Select tcp servers which respond correctly to a query and return their number, set array of names",
10210 FNCALL_OPTION_NONE, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL),
10211 FnCallTypeNew("shuffle", CF_DATA_TYPE_STRING_LIST, SHUFFLE_ARGS, &FnCallShuffle, "Shuffle the items in a list or array or data container",
10212 FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10213 FnCallTypeNew("some", CF_DATA_TYPE_CONTEXT, EVERY_SOME_NONE_ARGS, &FnCallEverySomeNone, "True if an element in the list or array or data container matches the given regular expression",
10214 FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10215 FnCallTypeNew("sort", CF_DATA_TYPE_STRING_LIST, SORT_ARGS, &FnCallSort, "Sort a list or array or data container",
10216 FNCALL_OPTION_COLLECTING | FNCALL_OPTION_VARARG, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10217 FnCallTypeNew("splayclass", CF_DATA_TYPE_CONTEXT, SPLAYCLASS_ARGS, &FnCallSplayClass, "True if the first argument's time-slot has arrived, according to a policy in arg2",
10218 FNCALL_OPTION_NONE, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL),
10219 FnCallTypeNew("splitstring", CF_DATA_TYPE_STRING_LIST, SPLITSTRING_ARGS, &FnCallSplitString, "Convert a string in arg1 into a list of max arg3 strings by splitting on a regular expression in arg2",
10220 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_DEPRECATED),
10221 FnCallTypeNew("storejson", CF_DATA_TYPE_STRING, STOREJSON_ARGS, &FnCallStoreJson, "Convert a list or array or data container to a JSON string",
10222 FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10223 FnCallTypeNew("strcmp", CF_DATA_TYPE_CONTEXT, STRCMP_ARGS, &FnCallStrCmp, "True if the two strings match exactly",
10224 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10225 FnCallTypeNew("strftime", CF_DATA_TYPE_STRING, STRFTIME_ARGS, &FnCallStrftime, "Format a date and time string",
10226 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10227 FnCallTypeNew("sublist", CF_DATA_TYPE_STRING_LIST, SUBLIST_ARGS, &FnCallSublist, "Returns arg3 element from either the head or the tail (according to arg2) of list or array or data container arg1.",
10228 FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10229 FnCallTypeNew("sysctlvalue", CF_DATA_TYPE_STRING, SYSCTLVALUE_ARGS, &FnCallSysctlValue, "Returns a value for sysctl key arg1 pair",
10230 FNCALL_OPTION_NONE, FNCALL_CATEGORY_SYSTEM, SYNTAX_STATUS_NORMAL),
10231 FnCallTypeNew("data_sysctlvalues", CF_DATA_TYPE_CONTAINER, DATA_SYSCTLVALUES_ARGS, &FnCallSysctlValue, "Returns a data container map of all the sysctl key,value pairs",
10232 FNCALL_OPTION_NONE, FNCALL_CATEGORY_SYSTEM, SYNTAX_STATUS_NORMAL),
10233 FnCallTypeNew("translatepath", CF_DATA_TYPE_STRING, TRANSLATEPATH_ARGS, &FnCallTranslatePath, "Translate path separators from Unix style to the host's native",
10234 FNCALL_OPTION_NONE, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL),
10235 FnCallTypeNew("unique", CF_DATA_TYPE_STRING_LIST, UNIQUE_ARGS, &FnCallSetop, "Returns all the unique elements of list or array or data container arg1",
10236 FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10237 FnCallTypeNew("usemodule", CF_DATA_TYPE_CONTEXT, USEMODULE_ARGS, &FnCallUseModule, "Execute cfengine module script and set class if successful",
10238 FNCALL_OPTION_NONE | FNCALL_OPTION_UNSAFE, FNCALL_CATEGORY_UTILS, SYNTAX_STATUS_NORMAL),
10239 FnCallTypeNew("userexists", CF_DATA_TYPE_CONTEXT, USEREXISTS_ARGS, &FnCallUserExists, "True if user name or numerical id exists on this host",
10240 FNCALL_OPTION_NONE, FNCALL_CATEGORY_SYSTEM, SYNTAX_STATUS_NORMAL),
10241 FnCallTypeNew("validdata", CF_DATA_TYPE_CONTEXT, VALIDDATA_ARGS, &FnCallValidData, "Check for errors in JSON or YAML data",
10242 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10243 FnCallTypeNew("validjson", CF_DATA_TYPE_CONTEXT, VALIDDATATYPE_ARGS, &FnCallValidJson, "Check for errors in JSON data",
10244 FNCALL_OPTION_VARARG, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10245 FnCallTypeNew("variablesmatching", CF_DATA_TYPE_STRING_LIST, CLASSMATCH_ARGS, &FnCallVariablesMatching, "List the variables matching regex arg1 and tag regexes arg2,arg3,...",
10246 FNCALL_OPTION_VARARG, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10247
10248 // Functions section following new naming convention
10249 FnCallTypeNew("string_mustache", CF_DATA_TYPE_STRING, STRING_MUSTACHE_ARGS, &FnCallStringMustache, "Expand a Mustache template from arg1 into a string using the optional data container in arg2 or datastate()",
10250 FNCALL_OPTION_COLLECTING|FNCALL_OPTION_VARARG, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10251 FnCallTypeNew("string_split", CF_DATA_TYPE_STRING_LIST, SPLITSTRING_ARGS, &FnCallStringSplit, "Convert a string in arg1 into a list of at most arg3 strings by splitting on a regular expression in arg2",
10252 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10253 FnCallTypeNew("string_replace", CF_DATA_TYPE_STRING, STRING_REPLACE_ARGS, &FnCallStringReplace, "Search through arg1, replacing occurences of arg2 with arg3.",
10254 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10255 FnCallTypeNew("string_trim", CF_DATA_TYPE_STRING, STRING_TRIM_ARGS, &FnCallStringTrim, "Trim whitespace from beginning and end of string",
10256 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10257 FnCallTypeNew("regex_replace", CF_DATA_TYPE_STRING, REGEX_REPLACE_ARGS, &FnCallRegReplace, "Replace occurrences of arg1 in arg2 with arg3, allowing backreferences. Perl-style options accepted in arg4.",
10258 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10259
10260 // Text xform functions
10261 FnCallTypeNew("string_downcase", CF_DATA_TYPE_STRING, XFORM_ARGS, &FnCallTextXform, "Convert a string to lowercase",
10262 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10263 FnCallTypeNew("string_head", CF_DATA_TYPE_STRING, XFORM_SUBSTR_ARGS, &FnCallTextXform, "Extract characters from the head of the string",
10264 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10265 FnCallTypeNew("string_reverse", CF_DATA_TYPE_STRING, XFORM_ARGS, &FnCallTextXform, "Reverse a string",
10266 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10267 FnCallTypeNew("string_length", CF_DATA_TYPE_INT, XFORM_ARGS, &FnCallTextXform, "Return the length of a string",
10268 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10269 FnCallTypeNew("string_tail", CF_DATA_TYPE_STRING, XFORM_SUBSTR_ARGS, &FnCallTextXform, "Extract characters from the tail of the string",
10270 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10271 FnCallTypeNew("string_upcase", CF_DATA_TYPE_STRING, XFORM_ARGS, &FnCallTextXform, "Convert a string to UPPERCASE",
10272 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10273
10274 // List folding functions
10275 FnCallTypeNew("length", CF_DATA_TYPE_INT, STAT_FOLD_ARGS, &FnCallLength, "Return the length of a list or array or data container",
10276 FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10277 FnCallTypeNew("max", CF_DATA_TYPE_STRING, SORT_ARGS, &FnCallFold, "Return the maximum value in a list or array or data container",
10278 FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10279 FnCallTypeNew("mean", CF_DATA_TYPE_REAL, STAT_FOLD_ARGS, &FnCallFold, "Return the mean (average) in a list or array or data container",
10280 FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10281 FnCallTypeNew("min", CF_DATA_TYPE_STRING, SORT_ARGS, &FnCallFold, "Return the minimum in a list or array or data container",
10282 FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10283 FnCallTypeNew("product", CF_DATA_TYPE_REAL, PRODUCT_ARGS, &FnCallFold, "Return the product of a list or array or data container of reals",
10284 FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10285 FnCallTypeNew("sum", CF_DATA_TYPE_REAL, SUM_ARGS, &FnCallFold, "Return the sum of a list or array or data container",
10286 FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10287 FnCallTypeNew("variance", CF_DATA_TYPE_REAL, STAT_FOLD_ARGS, &FnCallFold, "Return the variance of a list or array or data container",
10288 FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10289
10290 // CFEngine internal functions
10291 FnCallTypeNew("callstack_promisers", CF_DATA_TYPE_STRING_LIST, CFENGINE_PROMISERS_ARGS, &FnCallCFEngineCallers, "Get the list of promisers to the current promise execution path",
10292 FNCALL_OPTION_NONE, FNCALL_CATEGORY_INTERNAL, SYNTAX_STATUS_NORMAL),
10293 FnCallTypeNew("callstack_callers", CF_DATA_TYPE_CONTAINER, CFENGINE_CALLERS_ARGS, &FnCallCFEngineCallers, "Get the current promise execution stack in detail",
10294 FNCALL_OPTION_NONE, FNCALL_CATEGORY_INTERNAL, SYNTAX_STATUS_NORMAL),
10295
10296 // Data container functions
10297 FnCallTypeNew("data_regextract", CF_DATA_TYPE_CONTAINER, DATA_REGEXTRACT_ARGS, &FnCallRegExtract, "Matches the regular expression in arg 1 against the string in arg2 and returns a data container holding the backreferences by name",
10298 FNCALL_OPTION_NONE, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10299 FnCallTypeNew("data_expand", CF_DATA_TYPE_CONTAINER, DATA_EXPAND_ARGS, &FnCallDataExpand, "Expands any CFEngine variables in a list or array or data container, converting to a data container",
10300 FNCALL_OPTION_COLLECTING, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10301 FnCallTypeNew("variablesmatching_as_data", CF_DATA_TYPE_CONTAINER, CLASSMATCH_ARGS, &FnCallVariablesMatching, "Capture the variables matching regex arg1 and tag regexes arg2,arg3,... with their data",
10302 FNCALL_OPTION_VARARG, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10303
10304 // File parsing functions that output a data container
10305 FnCallTypeNew("data_readstringarray", CF_DATA_TYPE_CONTAINER, DATA_READSTRINGARRAY_ARGS, &FnCallDataRead, "Read an array of strings from a file into a data container map, using the first element as a key",
10306 FNCALL_OPTION_NONE, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL),
10307 FnCallTypeNew("data_readstringarrayidx", CF_DATA_TYPE_CONTAINER, DATA_READSTRINGARRAY_ARGS, &FnCallDataRead, "Read an array of strings from a file into a data container array",
10308 FNCALL_OPTION_NONE, FNCALL_CATEGORY_IO, SYNTAX_STATUS_NORMAL),
10309
10310 // Network probe functions
10311 FnCallTypeNew("network_connections", CF_DATA_TYPE_CONTAINER, NETWORK_CONNECTIONS_ARGS, &FnCallNetworkConnections, "Get the full list of TCP, TCP6, UDP, and UDP6 connections from /proc/net",
10312 FNCALL_OPTION_NONE, FNCALL_CATEGORY_COMM, SYNTAX_STATUS_NORMAL),
10313
10314 // Files functions
10315 FnCallTypeNew("findfiles_up", CF_DATA_TYPE_CONTAINER, FINDFILES_UP_ARGS, &FnCallFindfilesUp, "Find files matching a glob pattern by searching up the directory three from a given point in the tree structure",
10316 FNCALL_OPTION_VARARG, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL),
10317 FnCallTypeNew("search_up", CF_DATA_TYPE_CONTAINER, FINDFILES_UP_ARGS, &FnCallFindfilesUp, "Hush... This is a super secret alias name for function 'findfiles_up'",
10318 FNCALL_OPTION_VARARG, FNCALL_CATEGORY_FILES, SYNTAX_STATUS_NORMAL),
10319
10320 // Datatype functions
10321 FnCallTypeNew("type", CF_DATA_TYPE_STRING, DATATYPE_ARGS, &FnCallDatatype, "Get type description as string",
10322 FNCALL_OPTION_VARARG, FNCALL_CATEGORY_DATA, SYNTAX_STATUS_NORMAL),
10323
10324 FnCallTypeNewNull()
10325 };
10326