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