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