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