"Fossies" - the Fresh Open Source Software Archive

Member "cfengine-3.15.4/libpromises/evalfunction.c" (7 Jun 2021, 344681 Bytes) of package /linux/misc/cfengine-3.15.4.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    1 /*
    2   Copyright 2019 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-yaml.h>
   66 #include <json-utils.h>
   67 #include <known_dirs.h>
   68 #include <mustache.h>
   69 #include <processes_select.h>
   70 #include <sysinfo.h>
   71 #include <string_sequence.h>
   72 #include <string_lib.h>
   73 
   74 #include <math_eval.h>
   75 
   76 #include <libgen.h>
   77 
   78 #include <ctype.h>
   79 
   80 #ifdef HAVE_LIBCURL
   81 #include <curl/curl.h>
   82 #endif
   83 
   84 #ifdef HAVE_LIBCURL
   85 static bool CURL_INITIALIZED = false; /* GLOBAL */
   86 static JsonElement *CURL_CACHE = NULL;
   87 #endif
   88 
   89 #define SPLAY_PSEUDO_RANDOM_CONSTANT 8192
   90 
   91 static FnCallResult FilterInternal(EvalContext *ctx, const FnCall *fp, const char *regex, const Rlist* rp, bool do_regex, bool invert, long max);
   92 
   93 static char *StripPatterns(char *file_buffer, const char *pattern, const char *filename);
   94 static int BuildLineArray(EvalContext *ctx, const Bundle *bundle, const char *array_lval, const char *file_buffer,
   95                           const char *split, int maxent, DataType type, bool int_index);
   96 static JsonElement* BuildData(EvalContext *ctx, const char *file_buffer,  const char *split, int maxent, bool make_array);
   97 static bool ExecModule(EvalContext *ctx, char *command);
   98 
   99 static bool CheckIDChar(const char ch);
  100 static bool CheckID(const char *id);
  101 static const Rlist *GetListReferenceArgument(const EvalContext *ctx, const FnCall *fp, const char *lval_str, DataType *datatype_out);
  102 static char *CfReadFile(const char *filename, int maxsize);
  103 
  104 /*******************************************************************/
  105 
  106 int FnNumArgs(const FnCallType *call_type)
  107 {
  108     for (int i = 0;; i++)
  109     {
  110         if (call_type->args[i].pattern == NULL)
  111         {
  112             return i;
  113         }
  114     }
  115 }
  116 
  117 /*******************************************************************/
  118 
  119 /* assume args are all scalar literals by the time we get here
  120      and each handler allocates the memory it returns. There is
  121      a protocol to be followed here:
  122      Set args,
  123      Eval Content,
  124      Set rtype,
  125      ErrorFlags
  126 
  127      returnval = FnCallXXXResult(fp)
  128 
  129   */
  130 
  131 /*
  132  * Return successful FnCallResult with copy of str retained.
  133  */
  134 static FnCallResult FnReturn(const char *str)
  135 {
  136     return (FnCallResult) { FNCALL_SUCCESS, { xstrdup(str), RVAL_TYPE_SCALAR } };
  137 }
  138 
  139 /*
  140  * Return successful FnCallResult with str as is.
  141  */
  142 static FnCallResult FnReturnNoCopy(char *str)
  143 {
  144     return (FnCallResult) { FNCALL_SUCCESS, { str, RVAL_TYPE_SCALAR } };
  145 }
  146 
  147 static FnCallResult FnReturnBuffer(Buffer *buf)
  148 {
  149     return (FnCallResult) { FNCALL_SUCCESS, { BufferClose(buf), RVAL_TYPE_SCALAR } };
  150 }
  151 
  152 static FnCallResult FnReturnF(const char *fmt, ...) FUNC_ATTR_PRINTF(1, 2);
  153 
  154 static FnCallResult FnReturnF(const char *fmt, ...)
  155 {
  156     va_list ap;
  157     va_start(ap, fmt);
  158     char *buffer;
  159     xvasprintf(&buffer, fmt, ap);
  160     va_end(ap);
  161     return FnReturnNoCopy(buffer);
  162 }
  163 
  164 static FnCallResult FnReturnContext(bool result)
  165 {
  166     return FnReturn(result ? "any" : "!any");
  167 }
  168 
  169 static FnCallResult FnFailure(void)
  170 {
  171     return (FnCallResult) { FNCALL_FAILURE, { 0 } };
  172 }
  173 
  174 static VarRef* ResolveAndQualifyVarName(const FnCall *fp, const char *varname)
  175 {
  176     VarRef *ref = NULL;
  177     if (varname != NULL &&
  178         IsVarList(varname) &&
  179         strlen(varname) < CF_MAXVARSIZE)
  180     {
  181         char naked[CF_MAXVARSIZE] = "";
  182         GetNaked(naked, varname);
  183         ref = VarRefParse(naked);
  184     }
  185     else
  186     {
  187         ref = VarRefParse(varname);
  188     }
  189 
  190     if (!VarRefIsQualified(ref))
  191     {
  192         if (fp->caller)
  193         {
  194             const Bundle *caller_bundle = PromiseGetBundle(fp->caller);
  195             VarRefQualify(ref, caller_bundle->ns, caller_bundle->name);
  196         }
  197         else
  198         {
  199             Log(LOG_LEVEL_WARNING,
  200                 "Function '%s' was not called from a promise; "
  201                 "the unqualified variable reference %s cannot be qualified automatically.",
  202                 fp->name,
  203                 varname);
  204             VarRefDestroy(ref);
  205             return NULL;
  206         }
  207     }
  208 
  209     return ref;
  210 }
  211 
  212 static JsonElement* VarRefValueToJson(const EvalContext *ctx, const FnCall *fp, const VarRef *ref,
  213                                       const DataType disallowed_datatypes[], size_t disallowed_count,
  214                                       bool allow_scalars, bool *allocated)
  215 {
  216     assert(ref);
  217 
  218     DataType value_type = CF_DATA_TYPE_NONE;
  219     const void *value = EvalContextVariableGet(ctx, ref, &value_type);
  220     bool want_type = true;
  221 
  222     // Convenience storage for the name of the function, since fp can be NULL
  223     const char* fp_name = (fp ? fp->name : "VarRefValueToJson");
  224 
  225     for (int di = 0; di < disallowed_count; di++)
  226     {
  227         if (disallowed_datatypes[di] == value_type)
  228         {
  229             want_type = false;
  230             break;
  231         }
  232     }
  233 
  234     JsonElement *convert = NULL;
  235     if (want_type)
  236     {
  237         switch (DataTypeToRvalType(value_type))
  238         {
  239         case RVAL_TYPE_LIST:
  240             convert = JsonArrayCreate(RlistLen(value));
  241             for (const Rlist *rp = value; rp != NULL; rp = rp->next)
  242             {
  243                 if (rp->val.type == RVAL_TYPE_SCALAR) /* TODO what if it's an ilist */
  244                 {
  245                     JsonArrayAppendString(convert, RlistScalarValue(rp));
  246                 }
  247                 else
  248                 {
  249                     ProgrammingError("Ignored Rval of list type: %s",
  250                         RvalTypeToString(rp->val.type));
  251                 }
  252             }
  253 
  254             *allocated = true;
  255             break;
  256 
  257         case RVAL_TYPE_CONTAINER:
  258             // TODO: look into optimizing this if necessary
  259             convert = JsonCopy(value);
  260             *allocated = true;
  261             break;
  262 
  263         case RVAL_TYPE_SCALAR:
  264         {
  265             const char* data = value;
  266             if (allow_scalars)
  267             {
  268                 convert = JsonStringCreate(value);
  269                 *allocated = true;
  270                 break;
  271             }
  272             else
  273             {
  274                 /* regarray,mergedata,maparray,mapdata only care for arrays
  275                  * and ignore strings, so they go through this path. */
  276                 Log(LOG_LEVEL_DEBUG,
  277                     "Skipping scalar '%s' because 'allow_scalars' is false",
  278                     data);
  279             }
  280         }
  281         default:
  282             *allocated = true;
  283 
  284             {
  285                 VariableTableIterator *iter = EvalContextVariableTableFromRefIteratorNew(ctx, ref);
  286                 convert = JsonObjectCreate(10);
  287                 const size_t ref_num_indices = ref->num_indices;
  288                 char *last_key = NULL;
  289                 Variable *var;
  290 
  291                 while ((var = VariableTableIteratorNext(iter)) != NULL)
  292                 {
  293                     JsonElement *holder = convert;
  294                     JsonElement *holder_parent = NULL;
  295                     if (var->ref->num_indices - ref_num_indices == 1)
  296                     {
  297                         last_key = var->ref->indices[ref_num_indices];
  298                     }
  299                     else if (var->ref->num_indices - ref_num_indices > 1)
  300                     {
  301                         Log(LOG_LEVEL_DEBUG, "%s: got ref with starting depth %zu and index count %zu",
  302                             fp_name, ref_num_indices, var->ref->num_indices);
  303                         for (int index = ref_num_indices; index < var->ref->num_indices-1; index++)
  304                         {
  305                             JsonElement *local = JsonObjectGet(holder, var->ref->indices[index]);
  306                             if (local == NULL)
  307                             {
  308                                 local = JsonObjectCreate(1);
  309                                 JsonObjectAppendObject(holder, var->ref->indices[index], local);
  310                             }
  311 
  312                             last_key = var->ref->indices[index+1];
  313                             holder_parent = holder;
  314                             holder = local;
  315                         }
  316                     }
  317 
  318                     if (last_key != NULL && holder != NULL)
  319                     {
  320                         switch (var->rval.type)
  321                         {
  322                         case RVAL_TYPE_SCALAR:
  323                             if (JsonGetElementType(holder) != JSON_ELEMENT_TYPE_CONTAINER)
  324                             {
  325                                 Log(LOG_LEVEL_WARNING,
  326                                     "Replacing a non-container JSON element '%s' with a new empty container"
  327                                     " (for the '%s' subkey)",
  328                                     JsonGetPropertyAsString(holder), last_key);
  329 
  330                                 assert(holder_parent != NULL);
  331 
  332                                 JsonElement *empty_container = JsonObjectCreate(10);
  333 
  334                                 /* we have to duplicate 'holder->propertyName'
  335                                  * instead of just using a pointer to it here
  336                                  * because 'holder' is destroyed as part of the
  337                                  * JsonObjectAppendElement() call below */
  338                                 char *element_name = xstrdup(JsonGetPropertyAsString(holder));
  339                                 JsonObjectAppendElement(holder_parent,
  340                                                         element_name,
  341                                                         empty_container);
  342                                 free (element_name);
  343                                 holder = empty_container;
  344                                 JsonObjectAppendString(holder, last_key, var->rval.item);
  345                             }
  346                             else
  347                             {
  348                                 JsonElement *child = JsonObjectGet(holder, last_key);
  349                                 if (child != NULL && JsonGetElementType(child) == JSON_ELEMENT_TYPE_CONTAINER)
  350                                 {
  351                                     Log(LOG_LEVEL_WARNING,
  352                                         "Not replacing the container '%s' with a non-container value '%s'",
  353                                         JsonGetPropertyAsString(child), (char*) var->rval.item);
  354                                 }
  355                                 else
  356                                 {
  357                                     /* everything ok, just append the string */
  358                                     JsonObjectAppendString(holder, last_key, var->rval.item);
  359                                 }
  360                             }
  361                             break;
  362 
  363                         case RVAL_TYPE_LIST:
  364                         {
  365                             JsonElement *array = JsonArrayCreate(10);
  366                             for (const Rlist *rp = RvalRlistValue(var->rval); rp != NULL; rp = rp->next)
  367                             {
  368                                 if (rp->val.type == RVAL_TYPE_SCALAR)
  369                                 {
  370                                     JsonArrayAppendString(array, RlistScalarValue(rp));
  371                                 }
  372                             }
  373                             JsonObjectAppendArray(holder, last_key, array);
  374                         }
  375                         break;
  376 
  377                         default:
  378                             break;
  379                         }
  380                     }
  381                 }
  382 
  383                 VariableTableIteratorDestroy(iter);
  384 
  385                 if (JsonLength(convert) < 1)
  386                 {
  387                     char *varname = VarRefToString(ref, true);
  388                     Log(LOG_LEVEL_VERBOSE, "%s: argument '%s' does not resolve to a container or a list or a CFEngine array",
  389                         fp_name, varname);
  390                     free(varname);
  391                     JsonDestroy(convert);
  392                     return NULL;
  393                 }
  394 
  395                 break;
  396             } // end of default case
  397         } // end of data type switch
  398     }
  399     else // !wanted_type
  400     {
  401         char *varname = VarRefToString(ref, true);
  402         Log(LOG_LEVEL_DEBUG, "%s: argument '%s' resolved to an undesired data type",
  403             fp_name, varname);
  404         free(varname);
  405     }
  406 
  407     return convert;
  408 }
  409 
  410 static JsonElement *LookupVarRefToJson(void *ctx, const char **data)
  411 {
  412     Buffer* varname = NULL;
  413     Seq *s = StringMatchCaptures("^(([a-zA-Z0-9_]+\\.)?[a-zA-Z0-9._]+)(\\[[^\\[\\]]+\\])?", *data, false);
  414 
  415     if (s && SeqLength(s) > 0) // got a variable name
  416     {
  417         varname = BufferCopy((const Buffer*) SeqAt(s, 0));
  418     }
  419 
  420     if (s)
  421     {
  422         SeqDestroy(s);
  423     }
  424 
  425     VarRef *ref = NULL;
  426     if (varname)
  427     {
  428         ref = VarRefParse(BufferData(varname));
  429         // advance to the last character of the matched variable name
  430         *data += strlen(BufferData(varname))-1;
  431         BufferDestroy(varname);
  432     }
  433 
  434     if (!ref)
  435     {
  436         return NULL;
  437     }
  438 
  439     bool allocated = false;
  440     JsonElement *vardata = VarRefValueToJson(ctx, NULL, ref, NULL, 0, true, &allocated);
  441     VarRefDestroy(ref);
  442 
  443     // This should always return a copy
  444     if (!allocated)
  445     {
  446         vardata = JsonCopy(vardata);
  447     }
  448 
  449     return vardata;
  450 }
  451 
  452 static JsonElement* VarNameOrInlineToJson(EvalContext *ctx, const FnCall *fp, const Rlist* rp, bool allow_scalars, bool *allocated)
  453 {
  454     JsonElement *inline_data = NULL;
  455 
  456     assert(rp);
  457 
  458     if (rp->val.type == RVAL_TYPE_CONTAINER)
  459     {
  460         return (JsonElement*) rp->val.item;
  461     }
  462 
  463     const char* data = RlistScalarValue(rp);
  464 
  465     JsonParseError res = JsonParseWithLookup(ctx, &LookupVarRefToJson, &data, &inline_data);
  466 
  467     if (res == JSON_PARSE_OK)
  468     {
  469         if (JsonGetElementType(inline_data) == JSON_ELEMENT_TYPE_PRIMITIVE)
  470         {
  471             JsonDestroy(inline_data);
  472             inline_data = NULL;
  473         }
  474         else
  475         {
  476             *allocated = true;
  477             return inline_data;
  478         }
  479     }
  480 
  481     VarRef *ref = ResolveAndQualifyVarName(fp, data);
  482     if (!ref)
  483     {
  484         return NULL;
  485     }
  486 
  487     JsonElement *vardata = VarRefValueToJson(ctx, fp, ref, NULL, 0, allow_scalars, allocated);
  488     VarRefDestroy(ref);
  489 
  490     return vardata;
  491 }
  492 
  493 static Rlist *GetHostsFromLastseenDB(Item *addresses, time_t horizon, bool return_address, bool return_recent)
  494 {
  495     Rlist *recent = NULL, *aged = NULL;
  496     Item *ip;
  497     time_t now = time(NULL);
  498     double entrytime;
  499     char address[CF_MAXVARSIZE];
  500 
  501     for (ip = addresses; ip != NULL; ip = ip->next)
  502     {
  503         if (sscanf(ip->classes, "%lf", &entrytime) != 1)
  504         {
  505             Log(LOG_LEVEL_ERR, "Could not get host entry age");
  506             continue;
  507         }
  508 
  509         if (return_address)
  510         {
  511             snprintf(address, sizeof(address), "%s", ip->name);
  512         }
  513         else
  514         {
  515             char hostname[NI_MAXHOST];
  516             if (IPString2Hostname(hostname, ip->name, sizeof(hostname)) != -1)
  517             {
  518                 snprintf(address, sizeof(address), "%s", hostname);
  519             }
  520             else
  521             {
  522                 /* Not numeric address was requested, but IP was unresolvable. */
  523                 snprintf(address, sizeof(address), "%s", ip->name);
  524             }
  525         }
  526 
  527         if (entrytime < now - horizon)
  528         {
  529             Log(LOG_LEVEL_DEBUG, "Old entry");
  530 
  531             if (RlistKeyIn(recent, address))
  532             {
  533                 Log(LOG_LEVEL_DEBUG, "There is recent entry for this address. Do nothing.");
  534             }
  535             else
  536             {
  537                 Log(LOG_LEVEL_DEBUG, "Adding to list of aged hosts.");
  538                 RlistPrependScalarIdemp(&aged, address);
  539             }
  540         }
  541         else
  542         {
  543             Log(LOG_LEVEL_DEBUG, "Recent entry");
  544 
  545             Rlist *r = RlistKeyIn(aged, address);
  546             if (r)
  547             {
  548                 Log(LOG_LEVEL_DEBUG, "Purging from list of aged hosts.");
  549                 RlistDestroyEntry(&aged, r);
  550             }
  551 
  552             Log(LOG_LEVEL_DEBUG, "Adding to list of recent hosts.");
  553             RlistPrependScalarIdemp(&recent, address);
  554         }
  555     }
  556 
  557     if (return_recent)
  558     {
  559         RlistDestroy(aged);
  560         return recent;
  561     }
  562     else
  563     {
  564         RlistDestroy(recent);
  565         return aged;
  566     }
  567 }
  568 
  569 /*********************************************************************/
  570 
  571 static FnCallResult FnCallAnd(EvalContext *ctx,
  572                               ARG_UNUSED const Policy *policy,
  573                               ARG_UNUSED const FnCall *fp,
  574                               const Rlist *finalargs)
  575 {
  576 
  577     for (const Rlist *arg = finalargs; arg; arg = arg->next)
  578     {
  579         SyntaxTypeMatch err = CheckConstraintTypeMatch(fp->name, arg->val, CF_DATA_TYPE_STRING, "", 1);
  580         if (err != SYNTAX_TYPE_MATCH_OK && err != SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED)
  581         {
  582             FatalError(ctx, "Function '%s', %s", fp->name, SyntaxTypeMatchToString(err));
  583         }
  584     }
  585 
  586     for (const Rlist *arg = finalargs; arg; arg = arg->next)
  587     {
  588         if (!IsDefinedClass(ctx, RlistScalarValue(arg)))
  589         {
  590             return FnReturnContext(false);
  591         }
  592     }
  593 
  594     return FnReturnContext(true);
  595 }
  596 
  597 /*******************************************************************/
  598 
  599 static bool CallHostsSeenCallback(const char *hostkey, const char *address,
  600                                   ARG_UNUSED bool incoming, const KeyHostSeen *quality,
  601                                   void *ctx)
  602 {
  603     Item **addresses = ctx;
  604 
  605     if (HostKeyAddressUnknown(hostkey))
  606     {
  607         return true;
  608     }
  609 
  610     char buf[PRINTSIZE(uintmax_t)];
  611     xsnprintf(buf, sizeof(buf), "%ju", (uintmax_t) quality->lastseen);
  612 
  613     PrependItem(addresses, address, buf);
  614 
  615     return true;
  616 }
  617 
  618 /*******************************************************************/
  619 
  620 static FnCallResult FnCallHostsSeen(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
  621 {
  622     Item *addresses = NULL;
  623 
  624     int horizon = IntFromString(RlistScalarValue(finalargs)) * 3600;
  625     char *hostseen_policy = RlistScalarValue(finalargs->next);
  626     char *format = RlistScalarValue(finalargs->next->next);
  627 
  628     Log(LOG_LEVEL_DEBUG, "Calling hostsseen(%d,%s,%s)",
  629         horizon, hostseen_policy, format);
  630 
  631     if (!ScanLastSeenQuality(&CallHostsSeenCallback, &addresses))
  632     {
  633         return FnFailure();
  634     }
  635 
  636     Rlist *returnlist = GetHostsFromLastseenDB(addresses, horizon,
  637                                                strcmp(format, "address") == 0,
  638                                                strcmp(hostseen_policy, "lastseen") == 0);
  639 
  640     DeleteItemList(addresses);
  641 
  642     {
  643         Writer *w = StringWriter();
  644         WriterWrite(w, "hostsseen return values:");
  645         for (Rlist *rp = returnlist; rp; rp = rp->next)
  646         {
  647             WriterWriteF(w, " '%s'", RlistScalarValue(rp));
  648         }
  649         Log(LOG_LEVEL_DEBUG, "%s", StringWriterData(w));
  650         WriterClose(w);
  651     }
  652 
  653     if (returnlist == NULL)
  654     {
  655         return FnFailure();
  656     }
  657 
  658     return (FnCallResult) { FNCALL_SUCCESS, { returnlist, RVAL_TYPE_LIST } };
  659 }
  660 
  661 /*********************************************************************/
  662 
  663 static FnCallResult FnCallHostsWithClass(EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
  664 {
  665     Rlist *returnlist = NULL;
  666 
  667     char *class_name = RlistScalarValue(finalargs);
  668     char *return_format = RlistScalarValue(finalargs->next);
  669 
  670     if (!ListHostsWithClass(ctx, &returnlist, class_name, return_format))
  671     {
  672         return FnFailure();
  673     }
  674 
  675     return (FnCallResult) { FNCALL_SUCCESS, { returnlist, RVAL_TYPE_LIST } };
  676 }
  677 
  678 /*********************************************************************/
  679 
  680 /** @brief Convert function call from/to variables to range
  681  *
  682  *  Swap the two integers in place if the first is bigger
  683  *  Check for CF_NOINT, indicating invalid arguments
  684  *
  685  *  @return Absolute (positive) difference, -1 for error (0 for equal)
  686 */
  687 static int int_range_convert(int *from, int *to)
  688 {
  689     int old_from = *from;
  690     int old_to = *to;
  691     if (old_from == CF_NOINT || old_to == CF_NOINT)
  692     {
  693         return -1;
  694     }
  695     if (old_from == old_to)
  696     {
  697         return 0;
  698     }
  699     if (old_from > old_to)
  700     {
  701         *from = old_to;
  702         *to = old_from;
  703     }
  704     assert(*to > *from);
  705     return (*to) - (*from);
  706 }
  707 
  708 static FnCallResult FnCallRandomInt(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
  709 {
  710     if (finalargs->next == NULL)
  711     {
  712         return FnFailure();
  713     }
  714 
  715     int from = IntFromString(RlistScalarValue(finalargs));
  716     int to = IntFromString(RlistScalarValue(finalargs->next));
  717 
  718     int range = int_range_convert(&from, &to);
  719     if (range == -1)
  720     {
  721         return FnFailure();
  722     }
  723     if (range == 0)
  724     {
  725         return FnReturnF("%d", from);
  726     }
  727 
  728     assert(range > 0);
  729 
  730     int result = from + (int) (drand48() * (double) range);
  731 
  732     return FnReturnF("%d", result);
  733 }
  734 
  735 // Read an array of bytes as unsigned integers
  736 // Convert to 64 bit unsigned integer
  737 // Cross platform/arch, bytes[0] is always LSB of result
  738 static uint64_t BytesToUInt64(uint8_t *bytes)
  739 {
  740     uint64_t result = 0;
  741     size_t n = 8;
  742     for (size_t i = 0; i<n; ++i)
  743     {
  744         result += ((uint64_t)(bytes[i])) << (8 * i);
  745     }
  746     return result;
  747 }
  748 
  749 static FnCallResult FnCallHashToInt(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
  750 {
  751     if (finalargs->next == NULL || finalargs->next->next == NULL)
  752     {
  753         return FnFailure();
  754     }
  755     signed int from = IntFromString(RlistScalarValue(finalargs));
  756     signed int to = IntFromString(RlistScalarValue(finalargs->next));
  757 
  758     signed int range = int_range_convert(&from, &to);
  759     if (range == -1)
  760     {
  761         return FnFailure();
  762     }
  763     if (range == 0)
  764     {
  765         return FnReturnF("%d", from);
  766     }
  767     assert(range > 0);
  768 
  769     const unsigned char * const inp = RlistScalarValue(finalargs->next->next);
  770 
  771     // Use beginning of SHA checksum as basis:
  772     unsigned char digest[EVP_MAX_MD_SIZE + 1];
  773     memset(digest, 0, sizeof(digest));
  774     HashString(inp, strlen(inp), digest, HASH_METHOD_SHA256);
  775     uint64_t converted_sha = BytesToUInt64((uint8_t*)digest);
  776 
  777     // Limit using modulo:
  778     signed int result = from + (converted_sha % range);
  779     return FnReturnF("%d", result);
  780 }
  781 
  782 /*********************************************************************/
  783 
  784 static FnCallResult FnCallGetEnv(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
  785 {
  786     char buffer[CF_BUFSIZE] = "", ctrlstr[CF_SMALLBUF];
  787 
  788     char *name = RlistScalarValue(finalargs);
  789     int limit = IntFromString(RlistScalarValue(finalargs->next));
  790 
  791     snprintf(ctrlstr, CF_SMALLBUF, "%%.%ds", limit);    // -> %45s
  792 
  793     if (getenv(name))
  794     {
  795         snprintf(buffer, CF_BUFSIZE - 1, ctrlstr, getenv(name));
  796     }
  797 
  798     return FnReturn(buffer);
  799 }
  800 
  801 /*********************************************************************/
  802 
  803 #if defined(HAVE_GETPWENT)
  804 
  805 static FnCallResult FnCallGetUsers(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
  806 {
  807     const char *except_name = RlistScalarValue(finalargs);
  808     const char *except_uid = RlistScalarValue(finalargs->next);
  809 
  810     Rlist *except_names = RlistFromSplitString(except_name, ',');
  811     Rlist *except_uids = RlistFromSplitString(except_uid, ',');
  812 
  813     setpwent();
  814 
  815     Rlist *newlist = NULL;
  816     struct passwd *pw;
  817     while ((pw = getpwent()))
  818     {
  819         char *pw_uid_str = StringFromLong((int)pw->pw_uid);
  820 
  821         if (!RlistKeyIn(except_names, pw->pw_name) && !RlistKeyIn(except_uids, pw_uid_str))
  822         {
  823             RlistAppendScalarIdemp(&newlist, pw->pw_name);
  824         }
  825 
  826         free(pw_uid_str);
  827     }
  828 
  829     endpwent();
  830 
  831     RlistDestroy(except_names);
  832     RlistDestroy(except_uids);
  833 
  834     return (FnCallResult) { FNCALL_SUCCESS, { newlist, RVAL_TYPE_LIST } };
  835 }
  836 
  837 #else
  838 
  839 static FnCallResult FnCallGetUsers(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, ARG_UNUSED const Rlist *finalargs)
  840 {
  841     Log(LOG_LEVEL_ERR, "getusers is not implemented");
  842     return FnFailure();
  843 }
  844 
  845 #endif
  846 
  847 /*********************************************************************/
  848 
  849 static FnCallResult FnCallEscape(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
  850 {
  851     char buffer[CF_BUFSIZE];
  852 
  853     buffer[0] = '\0';
  854 
  855     char *name = RlistScalarValue(finalargs);
  856 
  857     EscapeSpecialChars(name, buffer, CF_BUFSIZE - 1, "", "");
  858 
  859     return FnReturn(buffer);
  860 }
  861 
  862 /*********************************************************************/
  863 
  864 static FnCallResult FnCallHost2IP(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
  865 {
  866     char *name = RlistScalarValue(finalargs);
  867     char ipaddr[CF_MAX_IP_LEN];
  868 
  869     if (Hostname2IPString(ipaddr, name, sizeof(ipaddr)) != -1)
  870     {
  871         return FnReturn(ipaddr);
  872     }
  873     else
  874     {
  875         /* Retain legacy behaviour,
  876            return hostname in case resolution fails. */
  877         return FnReturn(name);
  878     }
  879 
  880 }
  881 
  882 /*********************************************************************/
  883 
  884 static FnCallResult FnCallIP2Host(ARG_UNUSED EvalContext *ctx,
  885                                   ARG_UNUSED ARG_UNUSED const Policy *policy,
  886                                   ARG_UNUSED const FnCall *fp,
  887                                   const Rlist *finalargs)
  888 {
  889     char hostname[NI_MAXHOST];
  890     char *ip = RlistScalarValue(finalargs);
  891 
  892     if (IPString2Hostname(hostname, ip, sizeof(hostname)) != -1)
  893     {
  894         return FnReturn(hostname);
  895     }
  896     else
  897     {
  898         /* Retain legacy behaviour,
  899            return ip address in case resolution fails. */
  900         return FnReturn(ip);
  901     }
  902 }
  903 
  904 /*********************************************************************/
  905 
  906 static FnCallResult FnCallSysctlValue(ARG_UNUSED EvalContext *ctx,
  907                                       ARG_UNUSED ARG_UNUSED const Policy *policy,
  908                                       ARG_LINUX_ONLY const FnCall *fp,
  909                                       ARG_LINUX_ONLY const Rlist *finalargs)
  910 {
  911 #ifdef __linux__
  912     const bool sysctlvalue_mode = (strcmp(fp->name, "sysctlvalue") == 0);
  913 
  914     size_t max_sysctl_data = 16 * 1024;
  915     Buffer *procrootbuf = BufferNew();
  916     // Assumes that FILE_SEPARATOR is /
  917     BufferAppendString(procrootbuf, GetRelocatedProcdirRoot());
  918     BufferAppendString(procrootbuf, "/proc/sys");
  919 
  920     if (sysctlvalue_mode)
  921     {
  922         Buffer *key = BufferNewFrom(RlistScalarValue(finalargs),
  923                                     strlen(RlistScalarValue(finalargs)));
  924 
  925         // Note that in the single-key mode, we just reuse procrootbuf.
  926         Buffer *filenamebuf = procrootbuf;
  927         // Assumes that FILE_SEPARATOR is /
  928         BufferAppendChar(filenamebuf, '/');
  929         BufferSearchAndReplace(key, "\\.", "/", "gT");
  930         BufferAppendString(filenamebuf, BufferData(key));
  931         BufferDestroy(key);
  932 
  933         if (IsDir(BufferData(filenamebuf)))
  934         {
  935             Log(LOG_LEVEL_INFO, "Error while reading file '%s' because it's a directory (%s)",
  936                 BufferData(filenamebuf), GetErrorStr());
  937             BufferDestroy(filenamebuf);
  938             return FnFailure();
  939         }
  940 
  941         Writer *w = NULL;
  942         bool truncated = false;
  943         int fd = safe_open(BufferData(filenamebuf), O_RDONLY | O_TEXT);
  944         if (fd >= 0)
  945         {
  946             w = FileReadFromFd(fd, max_sysctl_data, &truncated);
  947             close(fd);
  948         }
  949 
  950         if (w == NULL)
  951         {
  952             Log(LOG_LEVEL_INFO, "Error while reading file '%s' (%s)",
  953                 BufferData(filenamebuf), GetErrorStr());
  954             BufferDestroy(filenamebuf);
  955             return FnFailure();
  956         }
  957 
  958         BufferDestroy(filenamebuf);
  959 
  960         char *result = StringWriterClose(w);
  961         StripTrailingNewline(result, max_sysctl_data);
  962         return FnReturnNoCopy(result);
  963     }
  964 
  965     JsonElement *sysctl_data = JsonObjectCreate(10);
  966 
  967     // For the remaining operations, we want the trailing slash on this.
  968     BufferAppendChar(procrootbuf, '/');
  969 
  970     Buffer *filematchbuf = BufferCopy(procrootbuf);
  971     BufferAppendString(filematchbuf, "**/*");
  972 
  973     StringSet *sysctls = GlobFileList(BufferData(filematchbuf));
  974     BufferDestroy(filematchbuf);
  975 
  976     StringSetIterator it = StringSetIteratorInit(sysctls);
  977     const char *filename = NULL;
  978     while ((filename = StringSetIteratorNext(&it)))
  979     {
  980         Writer *w = NULL;
  981         bool truncated = false;
  982 
  983         if (IsDir(filename))
  984         {
  985             // No warning: this is normal as we match wildcards.
  986             continue;
  987         }
  988 
  989         int fd = safe_open(filename, O_RDONLY | O_TEXT);
  990         if (fd >= 0)
  991         {
  992             w = FileReadFromFd(fd, max_sysctl_data, &truncated);
  993             close(fd);
  994         }
  995 
  996         if (!w)
  997         {
  998             Log(LOG_LEVEL_INFO, "Error while reading file '%s' (%s)",
  999                 filename, GetErrorStr());
 1000             continue;
 1001         }
 1002 
 1003         char *result = StringWriterClose(w);
 1004         StripTrailingNewline(result, max_sysctl_data);
 1005 
 1006         Buffer *var = BufferNewFrom(filename, strlen(filename));
 1007         BufferSearchAndReplace(var, BufferData(procrootbuf), "", "T");
 1008         BufferSearchAndReplace(var, "/", ".", "gT");
 1009         JsonObjectAppendString(sysctl_data, BufferData(var), result);
 1010         BufferDestroy(var);
 1011     }
 1012 
 1013     BufferDestroy(procrootbuf);
 1014     return (FnCallResult) { FNCALL_SUCCESS, (Rval) { sysctl_data, RVAL_TYPE_CONTAINER } };
 1015 #else
 1016     return FnFailure();
 1017 #endif
 1018 }
 1019 
 1020 /*********************************************************************/
 1021 
 1022 /* TODO move platform-specific code to libenv. */
 1023 
 1024 static FnCallResult FnCallGetUserInfo(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
 1025 {
 1026 #ifdef __MINGW32__
 1027     // TODO NetUserGetInfo(NULL, username, 1, &buf), see:
 1028     // https://msdn.microsoft.com/en-us/library/windows/desktop/aa370654(v=vs.85).aspx
 1029     return FnFailure();
 1030 
 1031 #else /* !__MINGW32__ */
 1032 
 1033     struct passwd *pw = NULL;
 1034 
 1035     if (finalargs == NULL)
 1036     {
 1037         pw = getpwuid(getuid());
 1038     }
 1039     else
 1040     {
 1041         char *arg = RlistScalarValue(finalargs);
 1042         if (StringIsNumeric(arg))
 1043         {
 1044             uid_t uid = Str2Uid(arg, NULL, NULL);
 1045             if (uid == CF_SAME_OWNER) // user "*"
 1046             {
 1047                 uid = getuid();
 1048             }
 1049             else if (uid == CF_UNKNOWN_OWNER)
 1050             {
 1051                 return FnFailure();
 1052             }
 1053 
 1054             pw = getpwuid(uid);
 1055         }
 1056         else
 1057         {
 1058             pw = getpwnam(arg);
 1059         }
 1060     }
 1061 
 1062     JsonElement *result = GetUserInfo(pw);
 1063 
 1064     if (result == NULL)
 1065     {
 1066         return FnFailure();
 1067     }
 1068 
 1069     return (FnCallResult) { FNCALL_SUCCESS, (Rval) { result, RVAL_TYPE_CONTAINER } };
 1070 #endif
 1071 }
 1072 
 1073 /*********************************************************************/
 1074 
 1075 static FnCallResult FnCallGetUid(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
 1076 {
 1077 #ifdef __MINGW32__
 1078     return FnFailure();                                         /* TODO */
 1079 
 1080 #else /* !__MINGW32__ */
 1081 
 1082     struct passwd *pw = getpwnam(RlistScalarValue(finalargs));
 1083 
 1084     if (pw == NULL)
 1085     {
 1086         return FnFailure();
 1087     }
 1088 
 1089     return FnReturnF("%ju", (uintmax_t)pw->pw_uid);
 1090 #endif
 1091 }
 1092 
 1093 /*********************************************************************/
 1094 
 1095 static FnCallResult FnCallGetGid(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
 1096 {
 1097 #ifdef __MINGW32__
 1098     return FnFailure();                                         /* TODO */
 1099 
 1100 #else /* !__MINGW32__ */
 1101 
 1102     struct group *gr = getgrnam(RlistScalarValue(finalargs));
 1103 
 1104     if (gr == NULL)
 1105     {
 1106         return FnFailure();
 1107     }
 1108 
 1109     return FnReturnF("%ju", (uintmax_t)gr->gr_gid);
 1110 #endif
 1111 }
 1112 
 1113 /*********************************************************************/
 1114 
 1115 static FnCallResult FnCallHandlerHash(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
 1116 /* Hash(string,md5|sha1|crypt) */
 1117 {
 1118     unsigned char digest[EVP_MAX_MD_SIZE + 1];
 1119     HashMethod type;
 1120 
 1121     char *string_or_filename = RlistScalarValue(finalargs);
 1122     char *typestring = RlistScalarValue(finalargs->next);
 1123     const bool filehash_mode = strcmp(fp->name, "file_hash") == 0;
 1124 
 1125     type = HashIdFromName(typestring);
 1126 
 1127     if (FIPS_MODE && type == HASH_METHOD_MD5)
 1128     {
 1129         Log(LOG_LEVEL_ERR, "FIPS mode is enabled, and md5 is not an approved algorithm in call to %s()", fp->name);
 1130     }
 1131 
 1132     if (filehash_mode)
 1133     {
 1134         HashFile(string_or_filename, digest, type, false);
 1135     }
 1136     else
 1137     {
 1138         HashString(string_or_filename, strlen(string_or_filename), digest, type);
 1139     }
 1140 
 1141     char hashbuffer[CF_HOSTKEY_STRING_SIZE];
 1142     HashPrintSafe(hashbuffer, sizeof(hashbuffer),
 1143                   digest, type, true);
 1144 
 1145     return FnReturn(SkipHashType(hashbuffer));
 1146 }
 1147 
 1148 /*********************************************************************/
 1149 
 1150 static FnCallResult FnCallHashMatch(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
 1151 /* HashMatch(string,md5|sha1|crypt,"abdxy98edj") */
 1152 {
 1153     unsigned char digest[EVP_MAX_MD_SIZE + 1];
 1154     HashMethod type;
 1155 
 1156     char *string = RlistScalarValue(finalargs);
 1157     char *typestring = RlistScalarValue(finalargs->next);
 1158     char *compare = RlistScalarValue(finalargs->next->next);
 1159 
 1160     type = HashIdFromName(typestring);
 1161     HashFile(string, digest, type, false);
 1162 
 1163     char hashbuffer[CF_HOSTKEY_STRING_SIZE];
 1164     HashPrintSafe(hashbuffer, sizeof(hashbuffer),
 1165                   digest, type, true);
 1166 
 1167     Log(LOG_LEVEL_VERBOSE,
 1168         "File '%s' hashes to '%s', compare to '%s'",
 1169         string, hashbuffer, compare);
 1170 
 1171     return FnReturnContext(strcmp(hashbuffer + 4, compare) == 0);
 1172 }
 1173 
 1174 /*********************************************************************/
 1175 
 1176 static FnCallResult FnCallConcat(EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
 1177 {
 1178     char id[CF_BUFSIZE];
 1179     char result[CF_BUFSIZE] = "";
 1180 
 1181     snprintf(id, CF_BUFSIZE, "built-in FnCall concat-arg");
 1182 
 1183 /* We need to check all the arguments, ArgTemplate does not check varadic functions */
 1184     for (const Rlist *arg = finalargs; arg; arg = arg->next)
 1185     {
 1186         SyntaxTypeMatch err = CheckConstraintTypeMatch(id, arg->val, CF_DATA_TYPE_STRING, "", 1);
 1187         if (err != SYNTAX_TYPE_MATCH_OK && err != SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED)
 1188         {
 1189             FatalError(ctx, "in %s: %s", id, SyntaxTypeMatchToString(err));
 1190         }
 1191     }
 1192 
 1193     for (const Rlist *arg = finalargs; arg; arg = arg->next)
 1194     {
 1195         if (strlcat(result, RlistScalarValue(arg), CF_BUFSIZE) >= CF_BUFSIZE)
 1196         {
 1197             /* Complain */
 1198             Log(LOG_LEVEL_ERR, "Unable to evaluate concat() function, arguments are too long");
 1199             return FnFailure();
 1200         }
 1201     }
 1202 
 1203     return FnReturn(result);
 1204 }
 1205 
 1206 /*********************************************************************/
 1207 
 1208 static FnCallResult FnCallIfElse(EvalContext *ctx,
 1209                                  ARG_UNUSED const Policy *policy,
 1210                                  ARG_UNUSED const FnCall *fp,
 1211                                  const Rlist *finalargs)
 1212 {
 1213     unsigned int argcount = 0;
 1214     char id[CF_BUFSIZE];
 1215 
 1216     snprintf(id, CF_BUFSIZE, "built-in FnCall ifelse-arg");
 1217 
 1218     /* We need to check all the arguments, ArgTemplate does not check varadic functions */
 1219     for (const Rlist *arg = finalargs; arg; arg = arg->next)
 1220     {
 1221         SyntaxTypeMatch err = CheckConstraintTypeMatch(id, arg->val, CF_DATA_TYPE_STRING, "", 1);
 1222         if (err != SYNTAX_TYPE_MATCH_OK && err != SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED)
 1223         {
 1224             FatalError(ctx, "in %s: %s", id, SyntaxTypeMatchToString(err));
 1225         }
 1226         argcount++;
 1227     }
 1228 
 1229     /* Require an odd number of arguments. We will always return something. */
 1230     if ((argcount % 2) == 0)
 1231     {
 1232         FatalError(ctx, "in built-in FnCall ifelse: even number of arguments");
 1233     }
 1234 
 1235     const Rlist *arg;
 1236     for (arg = finalargs;        /* Start with arg set to finalargs. */
 1237          arg && arg->next;       /* We must have arg and arg->next to proceed. */
 1238          arg = arg->next->next)  /* arg steps forward *twice* every time. */
 1239     {
 1240         /* Similar to classmatch(), we evaluate the first of the two
 1241          * arguments as a class. */
 1242         if (IsDefinedClass(ctx, RlistScalarValue(arg)))
 1243         {
 1244             /* If the evaluation returned true in the current context,
 1245              * return the second of the two arguments. */
 1246             return FnReturn(RlistScalarValue(arg->next));
 1247         }
 1248     }
 1249 
 1250     /* If we get here, we've reached the last argument (arg->next is NULL). */
 1251     return FnReturn(RlistScalarValue(arg));
 1252 }
 1253 
 1254 /*********************************************************************/
 1255 
 1256 static FnCallResult FnCallClassesMatching(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
 1257 {
 1258     bool count_only = false;
 1259     bool check_only = false;
 1260     unsigned count = 0;
 1261 
 1262     if (StringEqual(fp->name, "classesmatching"))
 1263     {
 1264         // Expected / default case
 1265     }
 1266     else if (StringEqual(fp->name, "classmatch"))
 1267     {
 1268         check_only = true;
 1269     }
 1270     else if (StringEqual(fp->name, "countclassesmatching"))
 1271     {
 1272         count_only = true;
 1273     }
 1274     else
 1275     {
 1276         FatalError(ctx, "FnCallClassesMatching: got unknown function name '%s', aborting", fp->name);
 1277     }
 1278 
 1279     if (!finalargs)
 1280     {
 1281         FatalError(ctx, "Function '%s' requires at least one argument", fp->name);
 1282     }
 1283 
 1284     for (const Rlist *arg = finalargs; arg; arg = arg->next)
 1285     {
 1286         SyntaxTypeMatch err = CheckConstraintTypeMatch(fp->name, arg->val, CF_DATA_TYPE_STRING, "", 1);
 1287         if (err != SYNTAX_TYPE_MATCH_OK && err != SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED)
 1288         {
 1289             FatalError(ctx, "in function '%s', '%s'", fp->name, SyntaxTypeMatchToString(err));
 1290         }
 1291     }
 1292 
 1293     Rlist *matches = NULL;
 1294 
 1295     {
 1296         ClassTableIterator *iter = EvalContextClassTableIteratorNewGlobal(ctx, NULL, true, true);
 1297         StringSet *global_matches = ClassesMatching(ctx, iter, RlistScalarValue(finalargs), finalargs->next, check_only);
 1298 
 1299         StringSetIterator it = StringSetIteratorInit(global_matches);
 1300         const char *element = NULL;
 1301         while ((element = StringSetIteratorNext(&it)))
 1302         {
 1303             if (count_only || check_only)
 1304             {
 1305                 count++;
 1306             }
 1307             else
 1308             {
 1309                 RlistPrepend(&matches, element, RVAL_TYPE_SCALAR);
 1310             }
 1311         }
 1312 
 1313         StringSetDestroy(global_matches);
 1314         ClassTableIteratorDestroy(iter);
 1315     }
 1316 
 1317     if (check_only && count >= 1)
 1318     {
 1319         return FnReturnContext(true);
 1320     }
 1321 
 1322     {
 1323         ClassTableIterator *iter = EvalContextClassTableIteratorNewLocal(ctx);
 1324         StringSet *local_matches = ClassesMatching(ctx, iter, RlistScalarValue(finalargs), finalargs->next, check_only);
 1325 
 1326         StringSetIterator it = StringSetIteratorInit(local_matches);
 1327         const char *element = NULL;
 1328         while ((element = StringSetIteratorNext(&it)))
 1329         {
 1330             if (count_only || check_only)
 1331             {
 1332                 count++;
 1333             }
 1334             else
 1335             {
 1336                 RlistPrepend(&matches, element, RVAL_TYPE_SCALAR);
 1337             }
 1338         }
 1339 
 1340         StringSetDestroy(local_matches);
 1341         ClassTableIteratorDestroy(iter);
 1342     }
 1343 
 1344     if (check_only)
 1345     {
 1346         return FnReturnContext(count >= 1);
 1347     }
 1348     else if (count_only)
 1349     {
 1350         return FnReturnF("%u", count);
 1351     }
 1352 
 1353     // else, this is classesmatching()
 1354     return (FnCallResult) { FNCALL_SUCCESS, { matches, RVAL_TYPE_LIST } };
 1355 }
 1356 
 1357 
 1358 static JsonElement *VariablesMatching(const EvalContext *ctx, const FnCall *fp, VariableTableIterator *iter, const Rlist *args, bool collect_full_data)
 1359 {
 1360     JsonElement *matching = JsonObjectCreate(10);
 1361 
 1362     const char *regex = RlistScalarValue(args);
 1363     pcre *rx = CompileRegex(regex);
 1364 
 1365     Variable *v = NULL;
 1366     while ((v = VariableTableIteratorNext(iter)))
 1367     {
 1368         char *expr = VarRefToString(v->ref, true);
 1369 
 1370         if (rx != NULL && StringMatchFullWithPrecompiledRegex(rx, expr))
 1371         {
 1372             StringSet *tagset = EvalContextVariableTags(ctx, v->ref);
 1373             bool pass = false;
 1374 
 1375             if (args->next)
 1376             {
 1377                 for (const Rlist *arg = args->next; arg; arg = arg->next)
 1378                 {
 1379                     const char* tag_regex = RlistScalarValue(arg);
 1380                     const char *element = NULL;
 1381                     StringSetIterator it = StringSetIteratorInit(tagset);
 1382                     while ((element = SetIteratorNext(&it)))
 1383                     {
 1384                         if (StringMatchFull(tag_regex, element))
 1385                         {
 1386                             pass = true;
 1387                             break;
 1388                         }
 1389                     }
 1390                 }
 1391             }
 1392             else                        // without any tags queried, accept variable
 1393             {
 1394                 pass = true;
 1395             }
 1396 
 1397             if (pass)
 1398             {
 1399                 JsonElement *data = NULL;
 1400                 bool allocated = false;
 1401                 if (collect_full_data)
 1402                 {
 1403                     data = VarRefValueToJson(ctx, fp, v->ref, NULL, 0, true, &allocated);
 1404                 }
 1405 
 1406                 /*
 1407                  * When we don't collect the full variable data
 1408                  * (collect_full_data is false), we still create a JsonObject
 1409                  * with empty strings as the values. It will be destroyed soon
 1410                  * afterwards, but the code is cleaner if we do it this way than
 1411                  * if we make a JsonArray in one branch and a JsonObject in the
 1412                  * other branch. The empty strings provide assurance that
 1413                  * serializing this JsonObject (e.g. for logging) will not
 1414                  * create problems. The extra memory usage from the empty
 1415                  * strings is negligible.
 1416                  */
 1417                 if (data == NULL)
 1418                 {
 1419                     JsonObjectAppendString(matching, expr, "");
 1420                 }
 1421                 else
 1422                 {
 1423                     if (!allocated)
 1424                     {
 1425                         data = JsonCopy(data);
 1426                     }
 1427                     JsonObjectAppendElement(matching, expr, data);
 1428                 }
 1429             }
 1430         }
 1431         free(expr);
 1432     }
 1433 
 1434     if (rx)
 1435     {
 1436         pcre_free(rx);
 1437     }
 1438 
 1439     return matching;
 1440 }
 1441 
 1442 static FnCallResult FnCallVariablesMatching(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
 1443 {
 1444     bool fulldata = (strcmp(fp->name, "variablesmatching_as_data") == 0);
 1445 
 1446     if (!finalargs)
 1447     {
 1448         FatalError(ctx, "Function '%s' requires at least one argument", fp->name);
 1449     }
 1450 
 1451     for (const Rlist *arg = finalargs; arg; arg = arg->next)
 1452     {
 1453         SyntaxTypeMatch err = CheckConstraintTypeMatch(fp->name, arg->val, CF_DATA_TYPE_STRING, "", 1);
 1454         if (err != SYNTAX_TYPE_MATCH_OK && err != SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED)
 1455         {
 1456             FatalError(ctx, "In function '%s', %s", fp->name, SyntaxTypeMatchToString(err));
 1457         }
 1458     }
 1459 
 1460     Rlist *matches = NULL;
 1461 
 1462     {
 1463         VariableTableIterator *iter = EvalContextVariableTableIteratorNew(ctx, NULL, NULL, NULL);
 1464         JsonElement *global_matches = VariablesMatching(ctx, fp, iter, finalargs, fulldata);
 1465         VariableTableIteratorDestroy(iter);
 1466 
 1467         assert (JsonGetContainerType(global_matches) == JSON_CONTAINER_TYPE_OBJECT);
 1468 
 1469         if (fulldata)
 1470         {
 1471             return (FnCallResult) { FNCALL_SUCCESS, (Rval) { global_matches, RVAL_TYPE_CONTAINER } };
 1472         }
 1473 
 1474         JsonIterator jiter = JsonIteratorInit(global_matches);
 1475         const char *key;
 1476         while ((key = JsonIteratorNextKey(&jiter)) != NULL)
 1477         {
 1478             assert (key != NULL);
 1479             RlistPrepend(&matches, key, RVAL_TYPE_SCALAR);
 1480         }
 1481 
 1482         JsonDestroy(global_matches);
 1483     }
 1484 
 1485     return (FnCallResult) { FNCALL_SUCCESS, { matches, RVAL_TYPE_LIST } };
 1486 }
 1487 
 1488 /*********************************************************************/
 1489 
 1490 static FnCallResult FnCallGetMetaTags(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
 1491 {
 1492     if (!finalargs)
 1493     {
 1494         FatalError(ctx, "Function '%s' requires at least one argument", fp->name);
 1495     }
 1496 
 1497     Rlist *tags = NULL;
 1498     StringSet *tagset = NULL;
 1499 
 1500     if (strcmp(fp->name, "getvariablemetatags") == 0)
 1501     {
 1502         VarRef *ref = VarRefParse(RlistScalarValue(finalargs));
 1503         tagset = EvalContextVariableTags(ctx, ref);
 1504         VarRefDestroy(ref);
 1505     }
 1506     else if (strcmp(fp->name, "getclassmetatags") == 0)
 1507     {
 1508         ClassRef ref = ClassRefParse(RlistScalarValue(finalargs));
 1509         tagset = EvalContextClassTags(ctx, ref.ns, ref.name);
 1510         ClassRefDestroy(ref);
 1511     }
 1512     else
 1513     {
 1514         FatalError(ctx, "FnCallGetMetaTags: got unknown function name '%s', aborting", fp->name);
 1515     }
 1516 
 1517     if (tagset == NULL)
 1518     {
 1519         Log(LOG_LEVEL_VERBOSE, "%s found variable or class %s without a tagset", fp->name, RlistScalarValue(finalargs));
 1520         return (FnCallResult) { FNCALL_FAILURE, { 0 } };
 1521     }
 1522 
 1523     char *key = NULL;
 1524     if (finalargs->next != NULL)
 1525     {
 1526         Buffer *keybuf = BufferNew();
 1527         BufferPrintf(keybuf, "%s=", RlistScalarValue(finalargs->next));
 1528         key = BufferClose(keybuf);
 1529     }
 1530 
 1531     char *element;
 1532     StringSetIterator it = StringSetIteratorInit(tagset);
 1533     while ((element = SetIteratorNext(&it)))
 1534     {
 1535         if (key != NULL)
 1536         {
 1537             if (StringStartsWith(element, key))
 1538             {
 1539                 RlistAppendScalar(&tags, element+strlen(key));
 1540             }
 1541         }
 1542         else
 1543         {
 1544             RlistAppendScalar(&tags, element);
 1545         }
 1546     }
 1547 
 1548     free(key);
 1549     return (FnCallResult) { FNCALL_SUCCESS, { tags, RVAL_TYPE_LIST } };
 1550 }
 1551 
 1552 /*********************************************************************/
 1553 
 1554 static FnCallResult FnCallBasename(ARG_UNUSED EvalContext *ctx,
 1555                                    ARG_UNUSED const Policy *policy,
 1556                                    const FnCall *fp,
 1557                                    const Rlist *args)
 1558 {
 1559     assert(fp != NULL);
 1560     assert(fp->name != NULL);
 1561     if (args == NULL)
 1562     {
 1563         Log(LOG_LEVEL_ERR, "Function %s requires a filename as first arg!",
 1564             fp->name);
 1565         return FnFailure();
 1566     }
 1567 
 1568     char dir[PATH_MAX];
 1569     strlcpy(dir, RlistScalarValue(args), PATH_MAX);
 1570     if (dir[0] == '\0')
 1571     {
 1572         return FnReturn(dir);
 1573     }
 1574 
 1575     char *base = basename(dir);
 1576 
 1577     if (args->next != NULL)
 1578     {
 1579         char *suffix = RlistScalarValue(args->next);
 1580         if (StringEndsWith(base, suffix))
 1581         {
 1582             size_t base_len = strlen(base);
 1583             size_t suffix_len = strlen(suffix);
 1584 
 1585             // Remove only if actually a suffix, not the same string
 1586             if (suffix_len < base_len)
 1587             {
 1588                 // On Solaris, trying to edit the buffer returned by basename
 1589                 // causes segfault(!)
 1590                 base = xstrndup(base, base_len - suffix_len);
 1591                 return FnReturnNoCopy(base);
 1592             }
 1593         }
 1594     }
 1595 
 1596     return FnReturn(base);
 1597 }
 1598 
 1599 /*********************************************************************/
 1600 
 1601 static FnCallResult FnCallBundlesMatching(EvalContext *ctx, const Policy *policy, const FnCall *fp, const Rlist *finalargs)
 1602 {
 1603     if (!finalargs)
 1604     {
 1605         return FnFailure();
 1606     }
 1607 
 1608     const char *regex = RlistScalarValue(finalargs);
 1609     pcre *rx = CompileRegex(regex);
 1610     if (!rx)
 1611     {
 1612         return FnFailure();
 1613     }
 1614 
 1615     const Rlist *tag_args = finalargs->next;
 1616 
 1617     Rlist *matches = NULL;
 1618     for (size_t i = 0; i < SeqLength(policy->bundles); i++)
 1619     {
 1620         const Bundle *bp = SeqAt(policy->bundles, i);
 1621 
 1622         char *bundle_name = BundleQualifiedName(bp);
 1623         if (StringMatchFullWithPrecompiledRegex(rx, bundle_name))
 1624         {
 1625             VarRef *ref = VarRefParseFromBundle("tags", bp);
 1626             VarRefSetMeta(ref, true);
 1627             DataType type;
 1628             const void *bundle_tags = EvalContextVariableGet(ctx, ref, &type);
 1629             VarRefDestroy(ref);
 1630 
 1631             bool found = false; // case where tag_args are given and the bundle has no tags
 1632 
 1633             if (tag_args == NULL)
 1634             {
 1635                 // we declare it found if no tags were requested
 1636                 found = true;
 1637             }
 1638             /* was the variable "tags" found? */
 1639             else if (type != CF_DATA_TYPE_NONE)
 1640             {
 1641                 switch (DataTypeToRvalType(type))
 1642                 {
 1643                 case RVAL_TYPE_SCALAR:
 1644                     {
 1645                         Rlist *searched = RlistFromSplitString(bundle_tags, ',');
 1646                         found = RlistMatchesRegexRlist(searched, tag_args);
 1647                         RlistDestroy(searched);
 1648                     }
 1649                     break;
 1650 
 1651                 case RVAL_TYPE_LIST:
 1652                     found = RlistMatchesRegexRlist(bundle_tags, tag_args);
 1653                     break;
 1654 
 1655                 default:
 1656                     Log(LOG_LEVEL_WARNING, "Function '%s' only matches tags defined as a scalar or a list.  "
 1657                         "Bundle '%s' had meta defined as '%s'", fp->name, bundle_name, DataTypeToString(type));
 1658                     found = false;
 1659                     break;
 1660                 }
 1661             }
 1662 
 1663             if (found)
 1664             {
 1665                 RlistPrepend(&matches, bundle_name, RVAL_TYPE_SCALAR);
 1666             }
 1667         }
 1668 
 1669         free(bundle_name);
 1670     }
 1671 
 1672     pcre_free(rx);
 1673 
 1674     return (FnCallResult) { FNCALL_SUCCESS, { matches, RVAL_TYPE_LIST } };
 1675 }
 1676 
 1677 /*********************************************************************/
 1678 
 1679 static bool AddPackagesMatchingJsonLine(pcre *matcher, JsonElement *json, char *line)
 1680 {
 1681     const size_t line_length = strlen(line);
 1682     if (line_length > CF_BUFSIZE - 80)
 1683     {
 1684         Log(LOG_LEVEL_ERR,
 1685             "Line from package inventory is too long (%zu) to be sensible",
 1686             line_length);
 1687         return false;
 1688     }
 1689 
 1690 
 1691     if (StringMatchFullWithPrecompiledRegex(matcher, line))
 1692     {
 1693         Seq *list = SeqParseCsvString(line);
 1694         if (SeqLength(list) != 4)
 1695         {
 1696             Log(LOG_LEVEL_ERR,
 1697                 "Line from package inventory '%s' did not yield correct number of elements.",
 1698                 line);
 1699             SeqDestroy(list);
 1700             return true;
 1701         }
 1702 
 1703         JsonElement *line_obj = JsonObjectCreate(4);
 1704         JsonObjectAppendString(line_obj, "name",    SeqAt(list, 0));
 1705         JsonObjectAppendString(line_obj, "version", SeqAt(list, 1));
 1706         JsonObjectAppendString(line_obj, "arch",    SeqAt(list, 2));
 1707         JsonObjectAppendString(line_obj, "method",  SeqAt(list, 3));
 1708 
 1709         SeqDestroy(list);
 1710         JsonArrayAppendObject(json, line_obj);
 1711     }
 1712 
 1713     return true;
 1714 }
 1715 
 1716 static bool GetLegacyPackagesMatching(pcre *matcher, JsonElement *json, const bool installed_mode)
 1717 {
 1718     char filename[CF_MAXVARSIZE];
 1719     if (installed_mode)
 1720     {
 1721         GetSoftwareCacheFilename(filename);
 1722     }
 1723     else
 1724     {
 1725         GetSoftwarePatchesFilename(filename);
 1726     }
 1727 
 1728     Log(LOG_LEVEL_DEBUG, "Reading inventory from '%s'", filename);
 1729 
 1730     FILE *const fin = fopen(filename, "r");
 1731     if (fin == NULL)
 1732     {
 1733         Log(LOG_LEVEL_VERBOSE,
 1734             "Cannot open the %s packages inventory '%s' - "
 1735             "This is not necessarily an error. "
 1736             "Either the inventory policy has not been included, "
 1737             "or it has not had time to have an effect yet or you are using"
 1738             "new package promise and check for legacy promise is made."
 1739             "A future call may still succeed. (fopen: %s)",
 1740             installed_mode ? "installed" : "available",
 1741             filename,
 1742             GetErrorStr());
 1743 
 1744         return true;
 1745     }
 1746 
 1747     char *line;
 1748     while ((line = GetCsvLineNext(fin)) != NULL)
 1749     {
 1750         if (!AddPackagesMatchingJsonLine(matcher, json, line))
 1751         {
 1752             free(line);
 1753             break;
 1754         }
 1755         free(line);
 1756     }
 1757 
 1758     bool ret = (feof(fin) != 0);
 1759     fclose(fin);
 1760 
 1761     return ret;
 1762 }
 1763 
 1764 static bool GetPackagesMatching(pcre *matcher, JsonElement *json, const bool installed_mode, Rlist *default_inventory)
 1765 {
 1766     dbid database = (installed_mode == true ? dbid_packages_installed : dbid_packages_updates);
 1767 
 1768     for (const Rlist *rp = default_inventory; rp != NULL; rp = rp->next)
 1769     {
 1770         const char *pm_name =  RlistScalarValue(rp);
 1771         size_t pm_name_size = strlen(pm_name);
 1772 
 1773         Log(LOG_LEVEL_DEBUG, "Reading packages (%d) for package module [%s]",
 1774                 database, pm_name);
 1775 
 1776         CF_DB *db_cached;
 1777         if (!OpenSubDB(&db_cached, database, pm_name))
 1778         {
 1779             Log(LOG_LEVEL_ERR, "Can not open database %d to get packages data.", database);
 1780             return false;
 1781         }
 1782 
 1783         char *key = "<inventory>";
 1784         int data_size = ValueSizeDB(db_cached, key, strlen(key) + 1);
 1785 
 1786         Log(LOG_LEVEL_DEBUG, "Reading inventory from database: %d", data_size);
 1787 
 1788         /* For empty list we are storing one byte value in database. */
 1789         if (data_size > 1)
 1790         {
 1791             char *buff = xmalloc(data_size + 1);
 1792             buff[data_size] = '\0';
 1793             if (!ReadDB(db_cached, key, buff, data_size))
 1794             {
 1795                 Log(LOG_LEVEL_WARNING, "Can not read installed packages database "
 1796                     "for '%s' package module.", pm_name);
 1797                 continue;
 1798             }
 1799 
 1800             Seq *packages_from_module = SeqStringFromString(buff, '\n');
 1801             free(buff);
 1802 
 1803             if (packages_from_module)
 1804             {
 1805                 // Iterate over and see where match is.
 1806                 for (int i = 0; i < SeqLength(packages_from_module); i++)
 1807                 {
 1808                     // With the new package promise we are storing inventory
 1809                     // information it the database. This set of lines ('\n' separated)
 1810                     // containing packages information. Each line is comma
 1811                     // separated set of data containing name, version and architecture.
 1812                     //
 1813                     // Legacy package promise is using 4 values, where the last one
 1814                     // is package method. In our case, method is simply package
 1815                     // module name. To make sure regex matching is working as
 1816                     // expected (we are comparing whole lines, containing package
 1817                     // method) we need to extend the line to contain package
 1818                     // module before regex match is taking place.
 1819                     char *line = SeqAt(packages_from_module, i);
 1820                     size_t new_line_size = strlen(line) + pm_name_size + 2; // we need coma and terminator
 1821                     char new_line[new_line_size];
 1822                     strcpy(new_line, line);
 1823                     strcat(new_line, ",");
 1824                     strcat(new_line, pm_name);
 1825 
 1826                     if (!AddPackagesMatchingJsonLine(matcher, json, new_line))
 1827                     {
 1828                         break;
 1829                     }
 1830                 }
 1831                 SeqDestroy(packages_from_module);
 1832             }
 1833             else
 1834             {
 1835                  Log(LOG_LEVEL_WARNING, "Can not parse packages database for '%s' "
 1836                      "package module.", pm_name);
 1837 
 1838             }
 1839         }
 1840         CloseDB(db_cached);
 1841     }
 1842     return true;
 1843 }
 1844 
 1845 static FnCallResult FnCallPackagesMatching(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
 1846 {
 1847     const bool installed_mode = (strcmp(fp->name, "packagesmatching") == 0);
 1848     pcre *matcher;
 1849     {
 1850         const char *regex_package = RlistScalarValue(finalargs);
 1851         const char *regex_version = RlistScalarValue(finalargs->next);
 1852         const char *regex_arch = RlistScalarValue(finalargs->next->next);
 1853         const char *regex_method = RlistScalarValue(finalargs->next->next->next);
 1854         char regex[CF_BUFSIZE];
 1855 
 1856         // Here we will truncate the regex if the parameters add up to over CF_BUFSIZE
 1857         snprintf(regex, sizeof(regex), "^%s,%s,%s,%s$",
 1858                  regex_package, regex_version, regex_arch, regex_method);
 1859         matcher = CompileRegex(regex);
 1860         if (matcher == NULL)
 1861         {
 1862             return FnFailure();
 1863         }
 1864     }
 1865 
 1866     JsonElement *json = JsonArrayCreate(50);
 1867     bool ret = false;
 1868 
 1869     Rlist *default_inventory = GetDefaultInventoryFromContext(ctx);
 1870     if (!default_inventory)
 1871     {
 1872         // Legacy package promise
 1873         ret = GetLegacyPackagesMatching(matcher, json, installed_mode);
 1874     }
 1875     else
 1876     {
 1877         // We are using package modules.
 1878         ret = GetPackagesMatching(matcher, json, installed_mode, default_inventory);
 1879     }
 1880 
 1881     pcre_free(matcher);
 1882 
 1883     if (ret == false)
 1884     {
 1885         Log(LOG_LEVEL_ERR,
 1886             "%s: Unable to read package inventory.", fp->name);
 1887         JsonDestroy(json);
 1888         return FnFailure();
 1889     }
 1890 
 1891     return (FnCallResult) { FNCALL_SUCCESS, (Rval) { json, RVAL_TYPE_CONTAINER } };
 1892 }
 1893 
 1894 /*********************************************************************/
 1895 
 1896 static FnCallResult FnCallCanonify(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
 1897 {
 1898     char buf[CF_BUFSIZE];
 1899     char *string = RlistScalarValue(finalargs);
 1900 
 1901     buf[0] = '\0';
 1902 
 1903     if (!strcmp(fp->name, "canonifyuniquely"))
 1904     {
 1905         char hashbuffer[CF_HOSTKEY_STRING_SIZE];
 1906         unsigned char digest[EVP_MAX_MD_SIZE + 1];
 1907         HashMethod type;
 1908 
 1909         type = HashIdFromName("sha1");
 1910         HashString(string, strlen(string), digest, type);
 1911         snprintf(buf, CF_BUFSIZE, "%s_%s", string,
 1912                  SkipHashType(HashPrintSafe(hashbuffer, sizeof(hashbuffer),
 1913                                             digest, type, true)));
 1914     }
 1915     else
 1916     {
 1917         snprintf(buf, CF_BUFSIZE, "%s", string);
 1918     }
 1919 
 1920     return FnReturn(CanonifyName(buf));
 1921 }
 1922 
 1923 /*********************************************************************/
 1924 
 1925 static FnCallResult FnCallTextXform(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
 1926 {
 1927     char *string = RlistScalarValue(finalargs);
 1928     const size_t len = strlen(string);
 1929     /* In case of string_length(), buf needs enough space to hold a number. */
 1930     const size_t bufsiz = MAX(len + 1, PRINTSIZE(len));
 1931     char *buf = xcalloc(bufsiz, sizeof(char));
 1932     memcpy(buf, string, len + 1);
 1933 
 1934     if (!strcmp(fp->name, "string_downcase"))
 1935     {
 1936         int pos = 0;
 1937         for (pos = 0; pos < len; pos++)
 1938         {
 1939             buf[pos] = tolower(buf[pos]);
 1940         }
 1941     }
 1942     else if (!strcmp(fp->name, "string_upcase"))
 1943     {
 1944         int pos = 0;
 1945         for (pos = 0; pos < len; pos++)
 1946         {
 1947             buf[pos] = toupper(buf[pos]);
 1948         }
 1949     }
 1950     else if (!strcmp(fp->name, "string_reverse"))
 1951     {
 1952         int c, i, j;
 1953         for (i = 0, j = len - 1; i < j; i++, j--)
 1954         {
 1955             c = buf[i];
 1956             buf[i] = buf[j];
 1957             buf[j] = c;
 1958         }
 1959     }
 1960     else if (!strcmp(fp->name, "string_length"))
 1961     {
 1962         xsnprintf(buf, bufsiz, "%zu", len);
 1963     }
 1964     else if (!strcmp(fp->name, "string_head"))
 1965     {
 1966         long max = IntFromString(RlistScalarValue(finalargs->next));
 1967         // A negative offset -N on string_head() means the user wants up to the Nth from the end
 1968         if (max < 0)
 1969         {
 1970             max = len - labs(max);
 1971         }
 1972 
 1973         // If the negative offset was too big, return an empty string
 1974         if (max < 0)
 1975         {
 1976             max = 0;
 1977         }
 1978 
 1979         if (max < bufsiz)
 1980         {
 1981             buf[max] = '\0';
 1982         }
 1983     }
 1984     else if (!strcmp(fp->name, "string_tail"))
 1985     {
 1986         const long max = IntFromString(RlistScalarValue(finalargs->next));
 1987         // A negative offset -N on string_tail() means the user wants up to the Nth from the start
 1988 
 1989         if (max < 0)
 1990         {
 1991             size_t offset = MIN(labs(max), len);
 1992             memcpy(buf, string + offset , len - offset + 1);
 1993         }
 1994         else if (max < len)
 1995         {
 1996             memcpy(buf, string + len - max, max + 1);
 1997         }
 1998     }
 1999     else
 2000     {
 2001         Log(LOG_LEVEL_ERR, "text xform with unknown call function %s, aborting", fp->name);
 2002         free(buf);
 2003         return FnFailure();
 2004     }
 2005 
 2006     return FnReturnNoCopy(buf);
 2007 }
 2008 
 2009 /*********************************************************************/
 2010 
 2011 static FnCallResult FnCallLastNode(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
 2012 {
 2013     char *name = RlistScalarValue(finalargs);
 2014     char *split = RlistScalarValue(finalargs->next);
 2015 
 2016     Rlist *newlist = RlistFromSplitRegex(name, split, 100, true);
 2017     if (newlist != NULL)
 2018     {
 2019         char *res = NULL;
 2020         const Rlist *rp = newlist;
 2021         while (rp->next != NULL)
 2022         {
 2023             rp = rp->next;
 2024         }
 2025         assert(rp && !rp->next);
 2026 
 2027         if (rp->val.item)
 2028         {
 2029             res = xstrdup(RlistScalarValue(rp));
 2030         }
 2031 
 2032         RlistDestroy(newlist);
 2033         if (res)
 2034         {
 2035             return FnReturnNoCopy(res);
 2036         }
 2037     }
 2038     return FnFailure();
 2039 }
 2040 
 2041 /*******************************************************************/
 2042 
 2043 static FnCallResult FnCallDirname(ARG_UNUSED EvalContext *ctx,
 2044                                   ARG_UNUSED const Policy *policy,
 2045                                   ARG_UNUSED const FnCall *fp,
 2046                                   const Rlist *finalargs)
 2047 {
 2048     char dir[PATH_MAX];
 2049     strlcpy(dir, RlistScalarValue(finalargs), PATH_MAX);
 2050 
 2051     DeleteSlash(dir);
 2052     ChopLastNode(dir);
 2053 
 2054     return FnReturn(dir);
 2055 }
 2056 
 2057 /*********************************************************************/
 2058 
 2059 static FnCallResult FnCallClassify(EvalContext *ctx,
 2060                                    ARG_UNUSED const Policy *policy,
 2061                                    ARG_UNUSED const FnCall *fp,
 2062                                    const Rlist *finalargs)
 2063 {
 2064     bool is_defined = IsDefinedClass(ctx, CanonifyName(RlistScalarValue(finalargs)));
 2065 
 2066     return FnReturnContext(is_defined);
 2067 }
 2068 
 2069 /*********************************************************************/
 2070 /* Executions                                                        */
 2071 /*********************************************************************/
 2072 
 2073 static FnCallResult FnCallReturnsZero(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
 2074 {
 2075     char comm[CF_BUFSIZE];
 2076     const char *shell_option = RlistScalarValue(finalargs->next);
 2077     ShellType shelltype = SHELL_TYPE_NONE;
 2078     bool need_executable_check = false;
 2079 
 2080     if (strcmp(shell_option, "useshell") == 0)
 2081     {
 2082         shelltype = SHELL_TYPE_USE;
 2083     }
 2084     else if (strcmp(shell_option, "powershell") == 0)
 2085     {
 2086         shelltype = SHELL_TYPE_POWERSHELL;
 2087     }
 2088 
 2089     if (IsAbsoluteFileName(RlistScalarValue(finalargs)))
 2090     {
 2091         need_executable_check = true;
 2092     }
 2093     else if (shelltype == SHELL_TYPE_NONE)
 2094     {
 2095         Log(LOG_LEVEL_ERR, "returnszero '%s' does not have an absolute path", RlistScalarValue(finalargs));
 2096         return FnReturnContext(false);
 2097     }
 2098 
 2099     if (need_executable_check && !IsExecutable(CommandArg0(RlistScalarValue(finalargs))))
 2100     {
 2101         Log(LOG_LEVEL_ERR, "returnszero '%s' is assumed to be executable but isn't", RlistScalarValue(finalargs));
 2102         return FnReturnContext(false);
 2103     }
 2104 
 2105     snprintf(comm, CF_BUFSIZE, "%s", RlistScalarValue(finalargs));
 2106 
 2107     if (ShellCommandReturnsZero(comm, shelltype))
 2108     {
 2109         Log(LOG_LEVEL_VERBOSE, "%s ran '%s' successfully and it returned zero", fp->name, RlistScalarValue(finalargs));
 2110         return FnReturnContext(true);
 2111     }
 2112     else
 2113     {
 2114         Log(LOG_LEVEL_VERBOSE, "%s ran '%s' successfully and it did not return zero", fp->name, RlistScalarValue(finalargs));
 2115         return FnReturnContext(false);
 2116     }
 2117 }
 2118 
 2119 /*********************************************************************/
 2120 
 2121 static FnCallResult FnCallExecResult(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
 2122 {
 2123     const char *shell_option = RlistScalarValue(finalargs->next);
 2124     ShellType shelltype = SHELL_TYPE_NONE;
 2125     bool need_executable_check = false;
 2126 
 2127     if (strcmp(shell_option, "useshell") == 0)
 2128     {
 2129         shelltype = SHELL_TYPE_USE;
 2130     }
 2131     else if (strcmp(shell_option, "powershell") == 0)
 2132     {
 2133         shelltype = SHELL_TYPE_POWERSHELL;
 2134     }
 2135 
 2136     if (IsAbsoluteFileName(RlistScalarValue(finalargs)))
 2137     {
 2138         need_executable_check = true;
 2139     }
 2140     else if (shelltype == SHELL_TYPE_NONE)
 2141     {
 2142         Log(LOG_LEVEL_ERR, "%s '%s' does not have an absolute path", fp->name, RlistScalarValue(finalargs));
 2143         return FnFailure();
 2144     }
 2145 
 2146     if (need_executable_check && !IsExecutable(CommandArg0(RlistScalarValue(finalargs))))
 2147     {
 2148         Log(LOG_LEVEL_ERR, "%s '%s' is assumed to be executable but isn't", fp->name, RlistScalarValue(finalargs));
 2149         return FnFailure();
 2150     }
 2151 
 2152     size_t buffer_size = CF_EXPANDSIZE;
 2153     char *buffer = xcalloc(1, buffer_size);
 2154 
 2155     if (GetExecOutput(RlistScalarValue(finalargs), &buffer, &buffer_size, shelltype))
 2156     {
 2157         Log(LOG_LEVEL_VERBOSE, "%s ran '%s' successfully", fp->name, RlistScalarValue(finalargs));
 2158         FnCallResult res = FnReturn(buffer);
 2159         free(buffer);
 2160         return res;
 2161     }
 2162     else
 2163     {
 2164         Log(LOG_LEVEL_VERBOSE, "%s could not run '%s' successfully", fp->name, RlistScalarValue(finalargs));
 2165         free(buffer);
 2166         return FnFailure();
 2167     }
 2168 }
 2169 
 2170 /*********************************************************************/
 2171 
 2172 static FnCallResult FnCallUseModule(EvalContext *ctx,
 2173                                     ARG_UNUSED const Policy *policy,
 2174                                     ARG_UNUSED const FnCall *fp,
 2175                                     const Rlist *finalargs)
 2176   /* usemodule("/programpath",varargs) */
 2177 {
 2178     char modulecmd[CF_BUFSIZE];
 2179     struct stat statbuf;
 2180 
 2181     char *command = RlistScalarValue(finalargs);
 2182     char *args = RlistScalarValue(finalargs->next);
 2183     const char* const workdir = GetWorkDir();
 2184 
 2185     snprintf(modulecmd, CF_BUFSIZE, "\"%s%cmodules%c%s\"",
 2186              workdir, FILE_SEPARATOR, FILE_SEPARATOR, command);
 2187 
 2188     if (stat(CommandArg0(modulecmd), &statbuf) == -1)
 2189     {
 2190         Log(LOG_LEVEL_ERR, "Plug-in module '%s' not found", modulecmd);
 2191         return FnFailure();
 2192     }
 2193 
 2194     if ((statbuf.st_uid != 0) && (statbuf.st_uid != getuid()))
 2195     {
 2196         Log(LOG_LEVEL_ERR, "Module '%s' was not owned by uid %ju who is executing agent", modulecmd, (uintmax_t)getuid());
 2197         return FnFailure();
 2198     }
 2199 
 2200     snprintf(modulecmd, CF_BUFSIZE, "\"%s%cmodules%c%s\" %s",
 2201              workdir, FILE_SEPARATOR, FILE_SEPARATOR, command, args);
 2202 
 2203     Log(LOG_LEVEL_VERBOSE, "Executing and using module [%s]", modulecmd);
 2204 
 2205     if (!ExecModule(ctx, modulecmd))
 2206     {
 2207         return FnFailure();
 2208     }
 2209 
 2210     return FnReturnContext(true);
 2211 }
 2212 
 2213 /*********************************************************************/
 2214 /* Misc                                                              */
 2215 /*********************************************************************/
 2216 
 2217 static FnCallResult FnCallSplayClass(EvalContext *ctx,
 2218                                      ARG_UNUSED const Policy *policy,
 2219                                      ARG_UNUSED const FnCall *fp,
 2220                                      const Rlist *finalargs)
 2221 {
 2222     char class_name[CF_MAXVARSIZE];
 2223 
 2224     Interval splay_policy = IntervalFromString(RlistScalarValue(finalargs->next));
 2225 
 2226     if (splay_policy == INTERVAL_HOURLY)
 2227     {
 2228         /* 12 5-minute slots in hour */
 2229         int slot = StringHash(RlistScalarValue(finalargs), 0);
 2230         slot &= (SPLAY_PSEUDO_RANDOM_CONSTANT - 1);
 2231         slot = slot * 12 / SPLAY_PSEUDO_RANDOM_CONSTANT;
 2232         snprintf(class_name, CF_MAXVARSIZE, "Min%02d_%02d", slot * 5, ((slot + 1) * 5) % 60);
 2233     }
 2234     else
 2235     {
 2236         /* 12*24 5-minute slots in day */
 2237         int dayslot = StringHash(RlistScalarValue(finalargs), 0);
 2238         dayslot &= (SPLAY_PSEUDO_RANDOM_CONSTANT - 1);
 2239         dayslot = dayslot * 12 * 24 / SPLAY_PSEUDO_RANDOM_CONSTANT;
 2240         int hour = dayslot / 12;
 2241         int slot = dayslot % 12;
 2242 
 2243         snprintf(class_name, CF_MAXVARSIZE, "Min%02d_%02d.Hr%02d", slot * 5, ((slot + 1) * 5) % 60, hour);
 2244     }
 2245 
 2246     Log(LOG_LEVEL_VERBOSE, "Computed context for '%s' splayclass: '%s'", RlistScalarValue(finalargs), class_name);
 2247     return FnReturnContext(IsDefinedClass(ctx, class_name));
 2248 }
 2249 
 2250 /*********************************************************************/
 2251 
 2252 #ifdef HAVE_LIBCURL
 2253 struct _curl_userdata
 2254 {
 2255     const FnCall *fp;
 2256     const char *desc;
 2257     size_t max_size;
 2258     Buffer* content;
 2259 };
 2260 
 2261 static size_t cfengine_curl_write_callback(char *ptr, size_t size, size_t nmemb, void *userdata)
 2262 {
 2263     struct _curl_userdata *options = (struct _curl_userdata*) userdata;
 2264     unsigned int old = BufferSize(options->content);
 2265     size_t requested = size*nmemb;
 2266     size_t granted = requested;
 2267 
 2268     if (old + requested > options->max_size)
 2269     {
 2270         granted = options->max_size - old;
 2271         Log(LOG_LEVEL_VERBOSE,
 2272             "%s: while receiving %s, current %u + requested %zu bytes would be over the maximum %zu; only accepting %zu bytes",
 2273             options->fp->name, options->desc, old, requested, options->max_size, granted);
 2274     }
 2275 
 2276     BufferAppend(options->content, ptr, granted);
 2277 
 2278     // `written` is actually (BufferSize(options->content) - old) but
 2279     // libcurl doesn't like that
 2280     size_t written = requested;
 2281 
 2282     // extra caution
 2283     BufferTrimToMaxLength(options->content, options->max_size);
 2284     return written;
 2285 }
 2286 
 2287 static void CurlCleanup()
 2288 {
 2289     if (CURL_CACHE == NULL)
 2290     {
 2291         JsonElement *temp = CURL_CACHE;
 2292         CURL_CACHE = NULL;
 2293         JsonDestroy(temp);
 2294     }
 2295 
 2296     if (CURL_INITIALIZED)
 2297     {
 2298         curl_global_cleanup();
 2299         CURL_INITIALIZED = false;
 2300     }
 2301 
 2302 }
 2303 #endif
 2304 
 2305 static FnCallResult FnCallUrlGet(ARG_UNUSED EvalContext *ctx,
 2306                                  ARG_UNUSED const Policy *policy,
 2307                                  const FnCall *fp,
 2308                                  const Rlist *finalargs)
 2309 {
 2310 
 2311 #ifdef HAVE_LIBCURL
 2312 
 2313     char *url = RlistScalarValue(finalargs);
 2314     bool allocated = false;
 2315     JsonElement *options = VarNameOrInlineToJson(ctx, fp, finalargs->next, false, &allocated);
 2316 
 2317     if (options == NULL)
 2318     {
 2319         return FnFailure();
 2320     }
 2321 
 2322     if (JsonGetElementType(options) != JSON_ELEMENT_TYPE_CONTAINER ||
 2323         JsonGetContainerType(options) != JSON_CONTAINER_TYPE_OBJECT)
 2324     {
 2325         JsonDestroyMaybe(options, allocated);
 2326         return FnFailure();
 2327     }
 2328 
 2329     Writer *cache_w = StringWriter();
 2330     WriterWriteF(cache_w, "url = %s; options = ", url);
 2331     JsonWriteCompact(cache_w, options);
 2332 
 2333     if (CURL_CACHE == NULL)
 2334     {
 2335         CURL_CACHE = JsonObjectCreate(10);
 2336         atexit(&CurlCleanup);
 2337     }
 2338 
 2339     JsonElement *old_result = JsonObjectGetAsObject(CURL_CACHE, StringWriterData(cache_w));
 2340 
 2341     if (old_result != NULL)
 2342     {
 2343         Log(LOG_LEVEL_VERBOSE, "%s: found cached request for %s", fp->name, url);
 2344         WriterClose(cache_w);
 2345         JsonDestroyMaybe(options, allocated);
 2346         return (FnCallResult) { FNCALL_SUCCESS, (Rval) { JsonCopy(old_result), RVAL_TYPE_CONTAINER } };
 2347     }
 2348 
 2349     if (!CURL_INITIALIZED && curl_global_init(CURL_GLOBAL_DEFAULT) != 0)
 2350     {
 2351         Log(LOG_LEVEL_ERR, "%s: libcurl initialization failed, sorry", fp->name);
 2352 
 2353         WriterClose(cache_w);
 2354         JsonDestroyMaybe(options, allocated);
 2355         return FnFailure();
 2356     }
 2357 
 2358     CURL_INITIALIZED = true;
 2359 
 2360     CURL *curl = curl_easy_init();
 2361     if (!curl)
 2362     {
 2363         Log(LOG_LEVEL_ERR, "%s: libcurl easy_init failed, sorry", fp->name);
 2364 
 2365         WriterClose(cache_w);
 2366         JsonDestroyMaybe(options, allocated);
 2367         return FnFailure();
 2368     }
 2369 
 2370     Buffer *content = BufferNew();
 2371     Buffer *headers = BufferNew();
 2372     curl_easy_setopt(curl, CURLOPT_URL, url);
 2373     curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); // do not use signals
 2374     curl_easy_setopt(curl, CURLOPT_TIMEOUT, 3L); // set default timeout
 2375     curl_easy_setopt(curl, CURLOPT_VERBOSE, 0);
 2376     curl_easy_setopt(curl,
 2377                      CURLOPT_PROTOCOLS,
 2378 
 2379                      // Allowed protocols
 2380                      CURLPROTO_FILE |
 2381                      CURLPROTO_FTP |
 2382                      CURLPROTO_FTPS |
 2383                      CURLPROTO_HTTP |
 2384                      CURLPROTO_HTTPS);
 2385 
 2386     curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, cfengine_curl_write_callback);
 2387     curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, cfengine_curl_write_callback);
 2388 
 2389     size_t max_content = 4096;
 2390     size_t max_headers = 4096;
 2391     JsonIterator iter = JsonIteratorInit(options);
 2392     const JsonElement *e;
 2393     while ((e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true)))
 2394     {
 2395         const char *key = JsonIteratorCurrentKey(&iter);
 2396         const char *value = JsonPrimitiveGetAsString(e);
 2397 
 2398         if (strcmp(key, "url.timeout") == 0)
 2399         {
 2400             Log(LOG_LEVEL_VERBOSE, "%s: setting timeout to %ld seconds", fp->name, IntFromString(value));
 2401             curl_easy_setopt(curl, CURLOPT_TIMEOUT, IntFromString(value));
 2402         }
 2403         else if (strcmp(key, "url.verbose") == 0)
 2404         {
 2405             Log(LOG_LEVEL_VERBOSE, "%s: setting verbosity to %ld", fp->name, IntFromString(value));
 2406             curl_easy_setopt(curl, CURLOPT_VERBOSE, IntFromString(value));
 2407         }
 2408         else if (strcmp(key, "url.header") == 0)
 2409         {
 2410             Log(LOG_LEVEL_VERBOSE, "%s: setting inline headers to %ld", fp->name, IntFromString(value));
 2411             curl_easy_setopt(curl, CURLOPT_HEADER, IntFromString(value));
 2412         }
 2413         else if (strcmp(key, "url.referer") == 0)
 2414         {
 2415             Log(LOG_LEVEL_VERBOSE, "%s: setting referer to %s", fp->name, value);
 2416             curl_easy_setopt(curl, CURLOPT_REFERER, value);
 2417         }
 2418         else if (strcmp(key, "url.user-agent") == 0)
 2419         {
 2420             Log(LOG_LEVEL_VERBOSE, "%s: setting user agent string to %s", fp->name, value);
 2421             curl_easy_setopt(curl, CURLOPT_USERAGENT, value);
 2422         }
 2423         else if (strcmp(key, "url.max_content") == 0)
 2424         {
 2425             Log(LOG_LEVEL_VERBOSE, "%s: setting max contents to %ld", fp->name, IntFromString(value));
 2426             max_content = IntFromString(value);
 2427         }
 2428         else if (strcmp(key, "url.max_headers") == 0)
 2429         {
 2430             Log(LOG_LEVEL_VERBOSE, "%s: setting max headers to %ld", fp->name, IntFromString(value));
 2431             max_headers = IntFromString(value);
 2432         }
 2433         else
 2434         {
 2435             Log(LOG_LEVEL_INFO, "%s: unknown option %s", fp->name, key);
 2436         }
 2437     }
 2438 
 2439     struct _curl_userdata data = { fp, "content", max_content, content };
 2440     curl_easy_setopt(curl, CURLOPT_WRITEDATA, &data);
 2441 
 2442     struct _curl_userdata header_data = { fp, "headers", max_headers, headers };
 2443     curl_easy_setopt(curl, CURLOPT_HEADERDATA, &header_data);
 2444 
 2445     JsonElement *options_headers = JsonObjectGetAsArray(options, "url.headers");
 2446     struct curl_slist *header_list = NULL;
 2447 
 2448     if (options_headers != NULL)
 2449     {
 2450         iter = JsonIteratorInit(options_headers);
 2451         while ((e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true)))
 2452         {
 2453             header_list = curl_slist_append(header_list, JsonPrimitiveGetAsString(e));
 2454         }
 2455     }
 2456 
 2457     if (header_list != NULL)
 2458     {
 2459         curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header_list);
 2460     }
 2461 
 2462     JsonElement *result = JsonObjectCreate(10);
 2463     CURLcode res = curl_easy_perform(curl);
 2464     if (header_list != NULL)
 2465     {
 2466         curl_slist_free_all(header_list);
 2467         header_list = NULL;
 2468     }
 2469 
 2470     long returncode = 0;
 2471     curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &returncode);
 2472     JsonObjectAppendInteger(result, "returncode",  returncode);
 2473 
 2474     curl_easy_cleanup(curl);
 2475 
 2476     JsonObjectAppendInteger(result, "rc",  0+res);
 2477 
 2478     bool success = (CURLE_OK == res);
 2479     JsonObjectAppendBool(result, "success", success);
 2480 
 2481     if (!success)
 2482     {
 2483         JsonObjectAppendString(result, "error_message", curl_easy_strerror(res));
 2484     }
 2485 
 2486 
 2487 
 2488     BufferTrimToMaxLength(content, max_content);
 2489     JsonObjectAppendString(result, "content",  BufferData(content));
 2490     BufferDestroy(content);
 2491 
 2492     BufferTrimToMaxLength(headers, max_headers);
 2493     JsonObjectAppendString(result, "headers",  BufferData(headers));
 2494     BufferDestroy(headers);
 2495 
 2496     JsonObjectAppendObject(CURL_CACHE, StringWriterData(cache_w), JsonCopy(result));
 2497     WriterClose(cache_w);
 2498 
 2499     JsonDestroyMaybe(options, allocated);
 2500     return (FnCallResult) { FNCALL_SUCCESS, (Rval) { result, RVAL_TYPE_CONTAINER } };
 2501 
 2502 #else
 2503 
 2504     UNUSED(finalargs);                 /* suppress unused parameter warning */
 2505     Log(LOG_LEVEL_ERR,
 2506         "%s: libcurl integration is not compiled into CFEngine, sorry", fp->name);
 2507     return FnFailure();
 2508 
 2509 #endif
 2510 }
 2511 
 2512 /*********************************************************************/
 2513 
 2514 /* ReadTCP(localhost,80,'GET index.html',1000) */
 2515 static FnCallResult FnCallReadTcp(ARG_UNUSED EvalContext *ctx,
 2516                                   ARG_UNUSED const Policy *policy,
 2517                                   ARG_UNUSED const FnCall *fp,
 2518                                   const Rlist *finalargs)
 2519 {
 2520     char *hostnameip = RlistScalarValue(finalargs);
 2521     char *port = RlistScalarValue(finalargs->next);
 2522     char *sendstring = RlistScalarValue(finalargs->next->next);
 2523     ssize_t maxbytes = IntFromString(RlistScalarValue(finalargs->next->next->next));
 2524 
 2525     if (THIS_AGENT_TYPE == AGENT_TYPE_COMMON)
 2526     {
 2527         return FnFailure();
 2528     }
 2529 
 2530     if (maxbytes < 0 || maxbytes > CF_BUFSIZE - 1)
 2531     {
 2532         Log(LOG_LEVEL_VERBOSE,
 2533             "readtcp: invalid number of bytes %zd to read, defaulting to %d",
 2534             maxbytes, CF_BUFSIZE - 1);
 2535         maxbytes = CF_BUFSIZE - 1;
 2536     }
 2537 
 2538     char txtaddr[CF_MAX_IP_LEN] = "";
 2539     int sd = SocketConnect(hostnameip, port, CONNTIMEOUT, false,
 2540                            txtaddr, sizeof(txtaddr));
 2541     if (sd == -1)
 2542     {
 2543         Log(LOG_LEVEL_INFO, "readtcp: Couldn't connect. (socket: %s)",
 2544             GetErrorStr());
 2545         return FnFailure();
 2546     }
 2547 
 2548     if (strlen(sendstring) > 0)
 2549     {
 2550         int sent = 0;
 2551         int result = 0;
 2552         size_t length = strlen(sendstring);
 2553         do
 2554         {
 2555             result = send(sd, sendstring, length, 0);
 2556             if (result < 0)
 2557             {
 2558                 cf_closesocket(sd);
 2559                 return FnFailure();
 2560             }
 2561             else
 2562             {
 2563                 sent += result;
 2564             }
 2565         } while (sent < length);
 2566     }
 2567 
 2568     char recvbuf[CF_BUFSIZE];
 2569     ssize_t n_read = recv(sd, recvbuf, maxbytes, 0);
 2570     cf_closesocket(sd);
 2571 
 2572     if (n_read == -1)
 2573     {
 2574         Log(LOG_LEVEL_INFO, "readtcp: Error while receiving (%s)",
 2575             GetErrorStr());
 2576         return FnFailure();
 2577     }
 2578 
 2579     assert(n_read < sizeof(recvbuf));
 2580     recvbuf[n_read] = '\0';
 2581 
 2582     Log(LOG_LEVEL_VERBOSE,
 2583         "readtcp: requested %zd maxbytes, got %zd bytes from %s",
 2584         maxbytes, n_read, txtaddr);
 2585 
 2586     return FnReturn(recvbuf);
 2587 }
 2588 
 2589 /*********************************************************************/
 2590 
 2591 /**
 2592  * Look for the indices of a variable in #finalargs if it is an array.
 2593  *
 2594  * @return *Always* return an slist of the indices; if the variable is not an
 2595  *         array or does not resolve at all, return an empty slist.
 2596  *
 2597  * @NOTE
 2598  * This is needed for literally one acceptance test:
 2599  * 01_vars/02_functions/getindices_returns_expected_list_from_array.cf
 2600  *
 2601  * The case is that we have a[x] = "1" AND a[x][y] = "2" which is
 2602  * ambiguous, but classic CFEngine arrays allow it. So we want the
 2603  * classic getindices("a[x]") to return "y" in this case.
 2604  */
 2605 static FnCallResult FnCallGetIndicesClassic(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
 2606 {
 2607     VarRef *ref = VarRefParse(RlistScalarValue(finalargs));
 2608     if (!VarRefIsQualified(ref))
 2609     {
 2610         if (fp->caller)
 2611         {
 2612             const Bundle *caller_bundle = PromiseGetBundle(fp->caller);
 2613             VarRefQualify(ref, caller_bundle->ns, caller_bundle->name);
 2614         }
 2615         else
 2616         {
 2617             Log(LOG_LEVEL_WARNING,
 2618                 "Function '%s' was given an unqualified variable reference, "
 2619                 "and it was not called from a promise. "
 2620                 "No way to automatically qualify the reference '%s'",
 2621                 fp->name, RlistScalarValue(finalargs));
 2622             VarRefDestroy(ref);
 2623             return FnFailure();
 2624         }
 2625     }
 2626 
 2627     Rlist *keys = NULL;
 2628 
 2629     VariableTableIterator *iter = EvalContextVariableTableFromRefIteratorNew(ctx, ref);
 2630     const Variable *itervar;
 2631     while ((itervar = VariableTableIteratorNext(iter)) != NULL)
 2632     {
 2633         /*
 2634         Log(LOG_LEVEL_DEBUG,
 2635             "%s(%s): got itervar->ref->num_indices %zu while ref->num_indices is %zu",
 2636             fp->name, RlistScalarValue(finalargs),
 2637             itervar->ref->num_indices, ref->num_indices);
 2638         */
 2639         /* Does the variable we found have more indices than the one we
 2640          * requested? For example, if we requested the variable "blah", it has
 2641          * 0 indices, so a found variable blah[i] will be acceptable. */
 2642         if (itervar->ref->num_indices > ref->num_indices)
 2643         {
 2644             RlistAppendScalarIdemp(&keys, itervar->ref->indices[ref->num_indices]);
 2645         }
 2646     }
 2647 
 2648     VariableTableIteratorDestroy(iter);
 2649     VarRefDestroy(ref);
 2650 
 2651     return (FnCallResult) { FNCALL_SUCCESS, { keys, RVAL_TYPE_LIST } };
 2652 }
 2653 
 2654 /*********************************************************************/
 2655 
 2656 static FnCallResult FnCallGetIndices(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
 2657 {
 2658     const char *name_str = RlistScalarValueSafe(finalargs);
 2659     bool allocated = false;
 2660     JsonElement *json = NULL;
 2661 
 2662     // Protect against collected args (their rval type will be data
 2663     // container). This is a special case to preserve legacy behavior
 2664     // for array lookups that requires a scalar in finalargs.
 2665     if (RlistValueIsType(finalargs, RVAL_TYPE_SCALAR))
 2666     {
 2667         VarRef *ref = ResolveAndQualifyVarName(fp, name_str);
 2668         DataType type;
 2669         EvalContextVariableGet(ctx, ref, &type);
 2670 
 2671         /* A variable holding a data container. */
 2672         if (type == CF_DATA_TYPE_CONTAINER)
 2673         {
 2674             json = VarRefValueToJson(ctx, fp, ref, NULL, 0, true, &allocated);
 2675         }
 2676         /* Resolves to a different type or does not resolve at all. It's
 2677          * normal not to resolve, for example "blah" will not resolve if the
 2678          * variable table only contains "blah[1]"; we have to go through
 2679          * FnCallGetIndicesClassic() to extract these indices. */
 2680         else
 2681         {
 2682             JsonParseError res = JsonParseWithLookup(ctx, &LookupVarRefToJson, &name_str, &json);
 2683             if (res == JSON_PARSE_OK)
 2684             {
 2685                 if (JsonGetElementType(json) == JSON_ELEMENT_TYPE_PRIMITIVE)
 2686                 {
 2687                     // VarNameOrInlineToJson() would now look up this primitive
 2688                     // in the variable table, returning a JSON container for
 2689                     // whatever type it is, but since we already know that it's
 2690                     // not a native container type (thanks to the
 2691                     // CF_DATA_TYPE_CONTAINER check above) we skip that, and
 2692                     // stick to the legacy data types.
 2693                     JsonDestroy(json);
 2694                     VarRefDestroy(ref);
 2695                     return FnCallGetIndicesClassic(ctx, policy, fp, finalargs);
 2696                 }
 2697                 else
 2698                 {
 2699                     // Inline JSON of some sort.
 2700                     allocated = true;
 2701                 }
 2702             }
 2703             else
 2704             {
 2705                 /* Invalid inline JSON. */
 2706                 VarRefDestroy(ref);
 2707                 return FnCallGetIndicesClassic(ctx, policy, fp, finalargs);
 2708             }
 2709         }
 2710 
 2711         VarRefDestroy(ref);
 2712     }
 2713     else
 2714     {
 2715         json = VarNameOrInlineToJson(ctx, fp, finalargs, true, &allocated);
 2716     }
 2717 
 2718     // we failed to produce a valid JsonElement, so give up
 2719     if (json == NULL)
 2720     {
 2721         return FnFailure();
 2722     }
 2723 
 2724     Rlist *keys = NULL;
 2725 
 2726     if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER)
 2727     {
 2728         JsonDestroyMaybe(json, allocated);
 2729         return (FnCallResult) { FNCALL_SUCCESS, { keys, RVAL_TYPE_LIST } };
 2730     }
 2731 
 2732     if (JsonGetContainerType(json) == JSON_CONTAINER_TYPE_OBJECT)
 2733     {
 2734         JsonIterator iter = JsonIteratorInit(json);
 2735         const char *key;
 2736         while ((key = JsonIteratorNextKey(&iter)))
 2737         {
 2738             RlistAppendScalar(&keys, key);
 2739         }
 2740     }
 2741     else
 2742     {
 2743         for (size_t i = 0; i < JsonLength(json); i++)
 2744         {
 2745             Rval key = (Rval) { StringFromLong(i), RVAL_TYPE_SCALAR };
 2746             RlistAppendRval(&keys, key);
 2747         }
 2748     }
 2749 
 2750     JsonDestroyMaybe(json, allocated);
 2751     return (FnCallResult) { FNCALL_SUCCESS, { keys, RVAL_TYPE_LIST } };
 2752 }
 2753 
 2754 /*********************************************************************/
 2755 
 2756 void CollectContainerValues(EvalContext *ctx, Rlist **values, const JsonElement *container)
 2757 {
 2758     if (JsonGetElementType(container) == JSON_ELEMENT_TYPE_CONTAINER)
 2759     {
 2760         JsonIterator iter = JsonIteratorInit(container);
 2761         const JsonElement *el;
 2762         while ((el = JsonIteratorNextValue(&iter)))
 2763         {
 2764             if (JsonGetElementType(el) == JSON_ELEMENT_TYPE_CONTAINER)
 2765             {
 2766                 CollectContainerValues(ctx, values, el);
 2767             }
 2768             else
 2769             {
 2770                 char *value = JsonPrimitiveToString(el);
 2771                 if (value != NULL)
 2772                 {
 2773                     RlistAppendScalar(values, value);
 2774                     free(value);
 2775                 }
 2776             }
 2777         }
 2778     }
 2779     else if (JsonGetElementType(container) == JSON_ELEMENT_TYPE_PRIMITIVE)
 2780     {
 2781         char *value = JsonPrimitiveToString(container);
 2782         if (value != NULL)
 2783         {
 2784             RlistAppendScalar(values, value);
 2785             free(value);
 2786         }
 2787     }
 2788 }
 2789 
 2790 /*********************************************************************/
 2791 
 2792 static FnCallResult FnCallGetValues(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
 2793 {
 2794     // try to load directly
 2795     bool allocated = false;
 2796     JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs, true, &allocated);
 2797 
 2798     // we failed to produce a valid JsonElement, so give up
 2799     if (json == NULL)
 2800     {
 2801         /* CFE-2479: Inexistent variable, return an empty slist. */
 2802         Log(LOG_LEVEL_DEBUG, "getvalues('%s'):"
 2803             " unresolvable variable, returning an empty list",
 2804             RlistScalarValueSafe(finalargs));
 2805         return (FnCallResult) { FNCALL_SUCCESS, { NULL, RVAL_TYPE_LIST } };
 2806     }
 2807 
 2808     Rlist *values = NULL;                      /* start with an empty Rlist */
 2809     CollectContainerValues(ctx, &values, json);
 2810 
 2811     JsonDestroyMaybe(json, allocated);
 2812     return (FnCallResult) { FNCALL_SUCCESS, { values, RVAL_TYPE_LIST } };
 2813 }
 2814 
 2815 /*********************************************************************/
 2816 
 2817 static FnCallResult FnCallGrep(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
 2818 {
 2819     return FilterInternal(ctx,
 2820                           fp,
 2821                           RlistScalarValue(finalargs), // regex
 2822                           finalargs->next, // list identifier
 2823                           1, // regex match = TRUE
 2824                           0, // invert matches = FALSE
 2825                           LONG_MAX); // max results = max int
 2826 }
 2827 
 2828 /*********************************************************************/
 2829 
 2830 static FnCallResult FnCallRegList(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
 2831 {
 2832     return FilterInternal(ctx,
 2833                           fp,
 2834                           RlistScalarValue(finalargs->next), // regex or string
 2835                           finalargs, // list identifier
 2836                           1,
 2837                           0,
 2838                           LONG_MAX);
 2839 }
 2840 
 2841 /*********************************************************************/
 2842 
 2843 static FnCallResult JoinContainer(const JsonElement *container, const char *delimiter)
 2844 {
 2845     JsonIterator iter = JsonIteratorInit(container);
 2846     const JsonElement *e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true);
 2847     if (!e)
 2848     {
 2849         return FnReturn("");
 2850     }
 2851 
 2852     Buffer *result = BufferNew();
 2853     BufferAppendString(result, JsonPrimitiveGetAsString(e));
 2854 
 2855     while ((e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true)))
 2856     {
 2857         BufferAppendString(result, delimiter);
 2858         BufferAppendString(result, JsonPrimitiveGetAsString(e));
 2859     }
 2860 
 2861     return FnReturnBuffer(result);
 2862 }
 2863 
 2864 static FnCallResult FnCallJoin(EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
 2865 {
 2866     const char *delimiter = RlistScalarValue(finalargs);
 2867     const char *name_str = RlistScalarValueSafe(finalargs->next);
 2868 
 2869     // try to load directly
 2870     bool allocated = false;
 2871     JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs->next, false, &allocated);
 2872 
 2873     // we failed to produce a valid JsonElement, so give up
 2874     if (json == NULL)
 2875     {
 2876         return FnFailure();
 2877     }
 2878     else if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER)
 2879     {
 2880         Log(LOG_LEVEL_VERBOSE, "Function '%s', argument '%s' was not a data container or list",
 2881             fp->name, name_str);
 2882         JsonDestroyMaybe(json, allocated);
 2883         return FnFailure();
 2884     }
 2885 
 2886     FnCallResult result = JoinContainer(json, delimiter);
 2887     JsonDestroyMaybe(json, allocated);
 2888     return result;
 2889 
 2890 }
 2891 
 2892 /*********************************************************************/
 2893 
 2894 static FnCallResult FnCallGetFields(EvalContext *ctx,
 2895                                     ARG_UNUSED const Policy *policy,
 2896                                     const FnCall *fp,
 2897                                     const Rlist *finalargs)
 2898 {
 2899     pcre *rx = CompileRegex(RlistScalarValue(finalargs));
 2900     if (!rx)
 2901     {
 2902         return FnFailure();
 2903     }
 2904 
 2905     const char *filename = RlistScalarValue(finalargs->next);
 2906     const char *split = RlistScalarValue(finalargs->next->next);
 2907     const char *array_lval = RlistScalarValue(finalargs->next->next->next);
 2908 
 2909     FILE *fin = safe_fopen(filename, "rt");
 2910     if (!fin)
 2911     {
 2912         Log(LOG_LEVEL_ERR, "File '%s' could not be read in getfields(). (fopen: %s)", filename, GetErrorStr());
 2913         pcre_free(rx);
 2914         return FnFailure();
 2915     }
 2916 
 2917     size_t line_size = CF_BUFSIZE;
 2918     char *line = xmalloc(CF_BUFSIZE);
 2919 
 2920     int line_count = 0;
 2921 
 2922     while (CfReadLine(&line, &line_size, fin) != -1)
 2923     {
 2924         if (!StringMatchFullWithPrecompiledRegex(rx, line))
 2925         {
 2926             continue;
 2927         }
 2928 
 2929         if (line_count == 0)
 2930         {
 2931             Rlist *newlist = RlistFromSplitRegex(line, split, 31, true);
 2932             int vcount = 1;
 2933 
 2934             for (const Rlist *rp = newlist; rp != NULL; rp = rp->next)
 2935             {
 2936                 char name[CF_MAXVARSIZE];
 2937                 snprintf(name, CF_MAXVARSIZE - 1, "%s[%d]", array_lval, vcount);
 2938                 VarRef *ref = VarRefParse(name);
 2939                 if (!VarRefIsQualified(ref))
 2940                 {
 2941                     if (fp->caller)
 2942                     {
 2943                         const Bundle *caller_bundle = PromiseGetBundle(fp->caller);
 2944                         VarRefQualify(ref, caller_bundle->ns, caller_bundle->name);
 2945                     }
 2946                     else
 2947                     {
 2948                         Log(LOG_LEVEL_WARNING,
 2949                             "Function '%s' was given an unqualified variable reference, "
 2950                             "and it was not called from a promise. No way to automatically qualify the reference '%s'.",
 2951                             fp->name, RlistScalarValue(finalargs));
 2952                         VarRefDestroy(ref);
 2953                         free(line);
 2954                         RlistDestroy(newlist);
 2955                         pcre_free(rx);
 2956                         return FnFailure();
 2957                     }
 2958                 }
 2959 
 2960                 EvalContextVariablePut(ctx, ref, RlistScalarValue(rp), CF_DATA_TYPE_STRING, "source=function,function=getfields");
 2961                 VarRefDestroy(ref);
 2962                 Log(LOG_LEVEL_VERBOSE, "getfields: defining '%s' => '%s'", name, RlistScalarValue(rp));
 2963                 vcount++;
 2964             }
 2965 
 2966             RlistDestroy(newlist);
 2967         }
 2968 
 2969         line_count++;
 2970     }
 2971 
 2972     pcre_free(rx);
 2973     free(line);
 2974 
 2975     if (!feof(fin))
 2976     {
 2977         Log(LOG_LEVEL_ERR, "Unable to read data from file '%s'. (fgets: %s)", filename, GetErrorStr());
 2978         fclose(fin);
 2979         return FnFailure();
 2980     }
 2981 
 2982     fclose(fin);
 2983 
 2984     return FnReturnF("%d", line_count);
 2985 }
 2986 
 2987 /*********************************************************************/
 2988 
 2989 static FnCallResult FnCallCountLinesMatching(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
 2990 {
 2991     pcre *rx = CompileRegex(RlistScalarValue(finalargs));
 2992     if (!rx)
 2993     {
 2994         return FnFailure();
 2995     }
 2996 
 2997     char *filename = RlistScalarValue(finalargs->next);
 2998 
 2999     FILE *fin = safe_fopen(filename, "rt");
 3000     if (!fin)
 3001     {
 3002         Log(LOG_LEVEL_ERR, "File '%s' could not be read in countlinesmatching(). (fopen: %s)", filename, GetErrorStr());
 3003         pcre_free(rx);
 3004         return FnReturn("0");
 3005     }
 3006 
 3007     int lcount = 0;
 3008     {
 3009         size_t line_size = CF_BUFSIZE;
 3010         char *line = xmalloc(line_size);
 3011 
 3012         while (CfReadLine(&line, &line_size, fin) != -1)
 3013         {
 3014             if (StringMatchFullWithPrecompiledRegex(rx, line))
 3015             {
 3016                 lcount++;
 3017                 Log(LOG_LEVEL_VERBOSE, "countlinesmatching: matched '%s'", line);
 3018                 continue;
 3019             }
 3020         }
 3021 
 3022         free(line);
 3023     }
 3024 
 3025     pcre_free(rx);
 3026 
 3027     if (!feof(fin))
 3028     {
 3029         Log(LOG_LEVEL_ERR, "Unable to read data from file '%s'. (fgets: %s)", filename, GetErrorStr());
 3030         fclose(fin);
 3031         return FnFailure();
 3032     }
 3033 
 3034     fclose(fin);
 3035 
 3036     return FnReturnF("%d", lcount);
 3037 }
 3038 
 3039 /*********************************************************************/
 3040 
 3041 static FnCallResult FnCallLsDir(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
 3042 {
 3043     Rlist *newlist = NULL;
 3044 
 3045     char *dirname = RlistScalarValue(finalargs);
 3046     char *regex = RlistScalarValue(finalargs->next);
 3047     int includepath = BooleanFromString(RlistScalarValue(finalargs->next->next));
 3048 
 3049     Dir *dirh = DirOpen(dirname);
 3050     if (dirh == NULL)
 3051     {
 3052         Log(LOG_LEVEL_ERR, "Directory '%s' could not be accessed in lsdir(), (opendir: %s)", dirname, GetErrorStr());
 3053         return (FnCallResult) { FNCALL_SUCCESS, { newlist, RVAL_TYPE_LIST } };
 3054     }
 3055 
 3056     const struct dirent *dirp;
 3057     for (dirp = DirRead(dirh); dirp != NULL; dirp = DirRead(dirh))
 3058     {
 3059         if (strlen(regex) == 0 || StringMatchFull(regex, dirp->d_name))
 3060         {
 3061             if (includepath)
 3062             {
 3063                 char line[CF_BUFSIZE];
 3064                 snprintf(line, CF_BUFSIZE, "%s/%s", dirname, dirp->d_name);
 3065                 MapName(line);
 3066                 RlistPrepend(&newlist, line, RVAL_TYPE_SCALAR);
 3067             }
 3068             else
 3069             {
 3070                 RlistPrepend(&newlist, dirp->d_name, RVAL_TYPE_SCALAR);
 3071             }
 3072         }
 3073     }
 3074     DirClose(dirh);
 3075 
 3076     return (FnCallResult) { FNCALL_SUCCESS, { newlist, RVAL_TYPE_LIST } };
 3077 }
 3078 
 3079 /*********************************************************************/
 3080 
 3081 bool EvalContextVariablePutSpecialEscaped(EvalContext *ctx, SpecialScope scope, const char *lval, const void *value, DataType type, const char *tags, bool escape)
 3082 {
 3083     if (escape)
 3084     {
 3085         char *escaped = EscapeCharCopy(value, '"', '\\');
 3086         bool ret = EvalContextVariablePutSpecial(ctx, scope, lval, escaped, type, tags);
 3087         free(escaped);
 3088         return ret;
 3089     }
 3090 
 3091     return EvalContextVariablePutSpecial(ctx, scope, lval, value, type, tags);
 3092 }
 3093 
 3094 /*********************************************************************/
 3095 
 3096 static JsonElement* ExecJSON_Pipe(const char *cmd, JsonElement *container)
 3097 {
 3098     IOData io = cf_popen_full_duplex(cmd, false, false);
 3099 
 3100     if (io.write_fd == -1 || io.read_fd == -1)
 3101     {
 3102         Log(LOG_LEVEL_INFO, "An error occurred while communicating with '%s'", cmd);
 3103 
 3104         return NULL;
 3105     }
 3106 
 3107     Log(LOG_LEVEL_DEBUG, "Opened fds %d and %d for command '%s'.",
 3108         io.read_fd, io.write_fd, cmd);
 3109 
 3110     // write the container to a string
 3111     Writer *w = StringWriter();
 3112     JsonWrite(w, container, 0);
 3113     char *container_str = StringWriterClose(w);
 3114 
 3115     if (PipeWrite(&io, container_str) != strlen(container_str))
 3116     {
 3117         Log(LOG_LEVEL_VERBOSE, "Couldn't send whole container data to '%s'.", cmd);
 3118         free(container_str);
 3119 
 3120         container_str = NULL;
 3121     }
 3122 
 3123     Rlist *returnlist = NULL;
 3124     if (container_str)
 3125     {
 3126         free(container_str);
 3127         /* We can have some error message here. */
 3128         returnlist = PipeReadData(&io, 5, 5);
 3129     }
 3130 
 3131     /* If script returns non 0 status */
 3132     int close = cf_pclose_full_duplex(&io);
 3133     if (close != EXIT_SUCCESS)
 3134     {
 3135         Log(LOG_LEVEL_VERBOSE,
 3136             "%s returned with non zero return code: %d",
 3137             cmd, close);
 3138     }
 3139 
 3140     // Exit if no data was obtained from the pipe
 3141     if (returnlist == NULL)
 3142     {
 3143         return NULL;
 3144     }
 3145 
 3146     JsonElement *returnjq = JsonArrayCreate(5);
 3147 
 3148     Buffer *buf = BufferNew();
 3149     for (const Rlist *rp = returnlist; rp != NULL; rp = rp->next)
 3150     {
 3151         const char *data = RlistScalarValue(rp);
 3152 
 3153         if (BufferSize(buf) != 0)
 3154         {
 3155             // simulate the newline
 3156             BufferAppendString(buf, "\n");
 3157         }
 3158 
 3159         BufferAppendString(buf, data);
 3160         const char *bufdata = BufferData(buf);
 3161         JsonElement *parsed = NULL;
 3162         if (JsonParse(&bufdata, &parsed) == JSON_PARSE_OK)
 3163         {
 3164             JsonArrayAppendElement(returnjq, parsed);
 3165             BufferClear(buf);
 3166         }
 3167         else
 3168         {
 3169             Log(LOG_LEVEL_DEBUG, "'%s' generated invalid JSON '%s', appending next line", cmd, data);
 3170         }
 3171     }
 3172 
 3173     BufferDestroy(buf);
 3174 
 3175     RlistDestroy(returnlist);
 3176 
 3177     return returnjq;
 3178 }
 3179 
 3180 /*********************************************************************/
 3181 
 3182 static FnCallResult FnCallMapData(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, ARG_UNUSED const Rlist *finalargs)
 3183 {
 3184     if (!fp->caller)
 3185     {
 3186         Log(LOG_LEVEL_ERR, "Function '%s' must be called from a promise", fp->name);
 3187         return FnFailure();
 3188     }
 3189 
 3190     bool mapdatamode = (strcmp(fp->name, "mapdata") == 0);
 3191     Rlist *returnlist = NULL;
 3192 
 3193     // This is a delayed evaluation function, so we have to resolve arguments ourselves
 3194     // We resolve them once now, to get the second or third argument with the iteration data
 3195     Rlist *expargs = NewExpArgs(ctx, policy, fp, NULL);
 3196 
 3197     Rlist *varpointer = NULL;
 3198     const char* conversion = NULL;
 3199 
 3200     if (mapdatamode)
 3201     {
 3202         if (expargs == NULL || RlistIsUnresolved(expargs->next->next))
 3203         {
 3204             RlistDestroy(expargs);
 3205             return FnFailure();
 3206         }
 3207 
 3208         conversion = RlistScalarValue(expargs);
 3209         varpointer = expargs->next->next;
 3210     }
 3211     else
 3212     {
 3213         if (expargs == NULL || RlistIsUnresolved(expargs->next))
 3214         {
 3215             RlistDestroy(expargs);
 3216             return FnFailure();
 3217         }
 3218 
 3219         conversion = "none";
 3220         varpointer = expargs->next;
 3221     }
 3222 
 3223     const char* varname = RlistScalarValueSafe(varpointer);
 3224 
 3225     bool jsonmode      = (strcmp(conversion, "json")      == 0);
 3226     bool canonifymode  = (strcmp(conversion, "canonify")  == 0);
 3227     bool json_pipemode = (strcmp(conversion, "json_pipe") == 0);
 3228 
 3229     bool allocated = false;
 3230     JsonElement *container = VarNameOrInlineToJson(ctx, fp, varpointer, false, &allocated);
 3231 
 3232     if (container == NULL)
 3233     {
 3234         RlistDestroy(expargs);
 3235         return FnFailure();
 3236     }
 3237 
 3238     if (JsonGetElementType(container) != JSON_ELEMENT_TYPE_CONTAINER)
 3239     {
 3240         Log(LOG_LEVEL_ERR, "Function '%s' got an unexpected non-container from argument '%s'", fp->name, varname);
 3241         JsonDestroyMaybe(container, allocated);
 3242 
 3243         RlistDestroy(expargs);
 3244         return FnFailure();
 3245     }
 3246 
 3247     if (mapdatamode && json_pipemode)
 3248     {
 3249         JsonElement *returnjson_pipe = ExecJSON_Pipe(RlistScalarValue(expargs->next), container);
 3250 
 3251         RlistDestroy(expargs);
 3252 
 3253         if (returnjson_pipe == NULL)
 3254         {
 3255             Log(LOG_LEVEL_ERR, "Function %s failed to get output from 'json_pipe' execution", fp->name);
 3256             return FnFailure();
 3257         }
 3258 
 3259         JsonDestroyMaybe(container, allocated);
 3260 
 3261         return (FnCallResult) { FNCALL_SUCCESS, { returnjson_pipe, RVAL_TYPE_CONTAINER } };
 3262     }
 3263 
 3264     Buffer *expbuf = BufferNew();
 3265     if (JsonGetContainerType(container) != JSON_CONTAINER_TYPE_OBJECT)
 3266     {
 3267         JsonElement *temp = JsonObjectCreate(0);
 3268         JsonElement *temp2 = JsonMerge(temp, container);
 3269         JsonDestroy(temp);
 3270         JsonDestroyMaybe(container, allocated);
 3271 
 3272         container = temp2;
 3273     }
 3274 
 3275     JsonIterator iter = JsonIteratorInit(container);
 3276     const JsonElement *e;
 3277 
 3278     while ((e = JsonIteratorNextValue(&iter)) != NULL)
 3279     {
 3280         EvalContextVariablePutSpecialEscaped(ctx, SPECIAL_SCOPE_THIS, "k", JsonGetPropertyAsString(e),
 3281                                              CF_DATA_TYPE_STRING, "source=function,function=maparray",
 3282                                              jsonmode);
 3283 
 3284         switch (JsonGetElementType(e))
 3285         {
 3286         case JSON_ELEMENT_TYPE_PRIMITIVE:
 3287             BufferClear(expbuf);
 3288             EvalContextVariablePutSpecialEscaped(ctx, SPECIAL_SCOPE_THIS, "v", JsonPrimitiveGetAsString(e),
 3289                                                  CF_DATA_TYPE_STRING, "source=function,function=maparray",
 3290                                                  jsonmode);
 3291 
 3292             // This is a delayed evaluation function, so we have to resolve arguments ourselves
 3293             // We resolve them every time now, to get the arg_map argument
 3294             Rlist *local_expargs = NewExpArgs(ctx, policy, fp, NULL);
 3295             const char *arg_map = RlistScalarValueSafe(mapdatamode ? local_expargs->next : local_expargs);
 3296             ExpandScalar(ctx, PromiseGetBundle(fp->caller)->ns, PromiseGetBundle(fp->caller)->name, arg_map, expbuf);
 3297             RlistDestroy(local_expargs);
 3298 
 3299             if (strstr(BufferData(expbuf), "$(this.k)") || strstr(BufferData(expbuf), "${this.k}") ||
 3300                 strstr(BufferData(expbuf), "$(this.v)") || strstr(BufferData(expbuf), "${this.v}"))
 3301             {
 3302                 RlistDestroy(returnlist);
 3303                 EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_THIS, "k");
 3304                 EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_THIS, "v");
 3305                 BufferDestroy(expbuf);
 3306                 JsonDestroyMaybe(container, allocated);
 3307                 RlistDestroy(expargs);
 3308                 return FnFailure();
 3309             }
 3310 
 3311             if (canonifymode)
 3312             {
 3313                 BufferCanonify(expbuf);
 3314             }
 3315 
 3316             RlistAppendScalar(&returnlist, BufferData(expbuf));
 3317             EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_THIS, "v");
 3318 
 3319             break;
 3320 
 3321         case JSON_ELEMENT_TYPE_CONTAINER:
 3322         {
 3323             const JsonElement *e2;
 3324             JsonIterator iter2 = JsonIteratorInit(e);
 3325             int position = 0;
 3326             while ((e2 = JsonIteratorNextValueByType(&iter2, JSON_ELEMENT_TYPE_PRIMITIVE, true)) != NULL)
 3327             {
 3328                 char *key = (char*) JsonGetPropertyAsString(e2);
 3329                 bool havekey = (key != NULL);
 3330                 if (havekey)
 3331                 {
 3332                     EvalContextVariablePutSpecialEscaped(ctx, SPECIAL_SCOPE_THIS, "k[1]", key,
 3333                                                          CF_DATA_TYPE_STRING, "source=function,function=maparray",
 3334                                                          jsonmode);
 3335                 }
 3336 
 3337                 BufferClear(expbuf);
 3338 
 3339                 EvalContextVariablePutSpecialEscaped(ctx, SPECIAL_SCOPE_THIS, "v", JsonPrimitiveGetAsString(e2),
 3340                                                      CF_DATA_TYPE_STRING, "source=function,function=maparray",
 3341                                                      jsonmode);
 3342 
 3343                 // This is a delayed evaluation function, so we have to resolve arguments ourselves
 3344                 // We resolve them every time now, to get the arg_map argument
 3345                 Rlist *local_expargs = NewExpArgs(ctx, policy, fp, NULL);
 3346                 const char *arg_map = RlistScalarValueSafe(mapdatamode ? local_expargs->next : local_expargs);
 3347                 ExpandScalar(ctx, PromiseGetBundle(fp->caller)->ns, PromiseGetBundle(fp->caller)->name, arg_map, expbuf);
 3348                 RlistDestroy(local_expargs);
 3349 
 3350                 if (strstr(BufferData(expbuf), "$(this.k)") || strstr(BufferData(expbuf), "${this.k}") ||
 3351                     (havekey && (strstr(BufferData(expbuf), "$(this.k[1])") || strstr(BufferData(expbuf), "${this.k[1]}"))) ||
 3352                     strstr(BufferData(expbuf), "$(this.v)") || strstr(BufferData(expbuf), "${this.v}"))
 3353                 {
 3354                     RlistDestroy(returnlist);
 3355                     EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_THIS, "k");
 3356                     if (havekey)
 3357                     {
 3358                         EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_THIS, "k[1]");
 3359                     }
 3360                     EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_THIS, "v");
 3361                     BufferDestroy(expbuf);
 3362                     JsonDestroyMaybe(container, allocated);
 3363                     RlistDestroy(expargs);
 3364                     return FnFailure();
 3365                 }
 3366 
 3367                 if (canonifymode)
 3368                 {
 3369                     BufferCanonify(expbuf);
 3370                 }
 3371 
 3372                 RlistAppendScalarIdemp(&returnlist, BufferData(expbuf));
 3373                 if (havekey)
 3374                 {
 3375                     EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_THIS, "k[1]");
 3376                 }
 3377                 EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_THIS, "v");
 3378                 position++;
 3379             }
 3380         }
 3381         break;
 3382 
 3383         default:
 3384             break;
 3385         }
 3386         EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_THIS, "k");
 3387     }
 3388 
 3389     BufferDestroy(expbuf);
 3390     JsonDestroyMaybe(container, allocated);
 3391     RlistDestroy(expargs);
 3392 
 3393     JsonElement *returnjson = NULL;
 3394 
 3395     // this is mapdata()
 3396     if (mapdatamode)
 3397     {
 3398         returnjson = JsonArrayCreate(RlistLen(returnlist));
 3399         for (const Rlist *rp = returnlist; rp != NULL; rp = rp->next)
 3400         {
 3401             const char *data = RlistScalarValue(rp);
 3402             if (jsonmode)
 3403             {
 3404                 JsonElement *parsed = NULL;
 3405                 if (JsonParseWithLookup(ctx, &LookupVarRefToJson, &data, &parsed) == JSON_PARSE_OK)
 3406                 {
 3407                     JsonArrayAppendElement(returnjson, parsed);
 3408                 }
 3409                 else
 3410                 {
 3411                     Log(LOG_LEVEL_VERBOSE, "Function '%s' could not parse dynamic JSON '%s', skipping it", fp->name, data);
 3412                 }
 3413             }
 3414             else
 3415             {
 3416                 JsonArrayAppendString(returnjson, data);
 3417             }
 3418         }
 3419 
 3420         RlistDestroy(returnlist);
 3421         return (FnCallResult) { FNCALL_SUCCESS, { returnjson, RVAL_TYPE_CONTAINER } };
 3422     }
 3423 
 3424     // this is maparray()
 3425     return (FnCallResult) { FNCALL_SUCCESS, { returnlist, RVAL_TYPE_LIST } };
 3426 }
 3427 
 3428 /*********************************************************************/
 3429 
 3430 static FnCallResult FnCallMapList(EvalContext *ctx,
 3431                                   const Policy *policy,
 3432                                   const FnCall *fp,
 3433                                   ARG_UNUSED const Rlist *finalargs)
 3434 {
 3435     // This is a delayed evaluation function, so we have to resolve arguments ourselves
 3436     // We resolve them once now, to get the second argument
 3437     Rlist *expargs = NewExpArgs(ctx, policy, fp, NULL);
 3438 
 3439     if (expargs == NULL || RlistIsUnresolved(expargs->next))
 3440     {
 3441         RlistDestroy(expargs);
 3442         return FnFailure();
 3443     }
 3444 
 3445     const char *name_str = RlistScalarValueSafe(expargs->next);
 3446 
 3447     // try to load directly
 3448     bool allocated = false;
 3449     JsonElement *json = VarNameOrInlineToJson(ctx, fp, expargs->next, false, &allocated);
 3450 
 3451     // we failed to produce a valid JsonElement, so give up
 3452     if (json == NULL)
 3453     {
 3454         RlistDestroy(expargs);
 3455         return FnFailure();
 3456     }
 3457     else if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER)
 3458     {
 3459         Log(LOG_LEVEL_VERBOSE, "Function '%s', argument '%s' was not a data container or list",
 3460             fp->name, name_str);
 3461         JsonDestroyMaybe(json, allocated);
 3462         RlistDestroy(expargs);
 3463         return FnFailure();
 3464     }
 3465 
 3466     Rlist *newlist = NULL;
 3467     Buffer *expbuf = BufferNew();
 3468     JsonIterator iter = JsonIteratorInit(json);
 3469     const JsonElement *e;
 3470     while ((e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true)))
 3471     {
 3472         const char* value = JsonPrimitiveGetAsString(e);
 3473 
 3474         BufferClear(expbuf);
 3475         EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_THIS, "this", value, CF_DATA_TYPE_STRING, "source=function,function=maplist");
 3476 
 3477         // This is a delayed evaluation function, so we have to resolve arguments ourselves
 3478         // We resolve them every time now, to get the first argument
 3479         Rlist *local_expargs = NewExpArgs(ctx, policy, fp, NULL);
 3480         const char *arg_map = RlistScalarValueSafe(local_expargs);
 3481         ExpandScalar(ctx, NULL, "this", arg_map, expbuf);
 3482         RlistDestroy(local_expargs);
 3483 
 3484         if (strstr(BufferData(expbuf), "$(this)") || strstr(BufferData(expbuf), "${this}"))
 3485         {
 3486             RlistDestroy(newlist);
 3487             EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_THIS, "this");
 3488             BufferDestroy(expbuf);
 3489             JsonDestroyMaybe(json, allocated);
 3490             RlistDestroy(expargs);
 3491             return FnFailure();
 3492         }
 3493 
 3494         RlistAppendScalar(&newlist, BufferData(expbuf));
 3495         EvalContextVariableRemoveSpecial(ctx, SPECIAL_SCOPE_THIS, "this");
 3496     }
 3497     BufferDestroy(expbuf);
 3498     JsonDestroyMaybe(json, allocated);
 3499     RlistDestroy(expargs);
 3500 
 3501     return (FnCallResult) { FNCALL_SUCCESS, { newlist, RVAL_TYPE_LIST } };
 3502 }
 3503 
 3504 /******************************************************************************/
 3505 
 3506 static FnCallResult FnCallExpandRange(EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
 3507 {
 3508     Rlist *newlist = NULL;
 3509     const char *template = RlistScalarValue(finalargs);
 3510     char *step = RlistScalarValue(finalargs->next);
 3511     size_t template_size = strlen(template) + 1;
 3512     char *before = xstrdup(template);
 3513     char *after = xcalloc(template_size, 1);
 3514     char *work = xstrdup(template);
 3515     int from = CF_NOINT, to = CF_NOINT, step_size = atoi(step);
 3516 
 3517     if (*template == '[')
 3518     {
 3519         *before = '\0';
 3520         sscanf(template, "[%d-%d]%[^\n]", &from, &to, after);
 3521     }
 3522     else
 3523     {
 3524         sscanf(template, "%[^[\[][%d-%d]%[^\n]", before, &from, &to, after);
 3525     }
 3526 
 3527     if (step_size < 1 || abs(from-to) < step_size)
 3528     {
 3529         FatalError(ctx, "EXPANDRANGE Step size cannot be less than 1 or greater than the interval");
 3530     }
 3531 
 3532     if (from == CF_NOINT || to == CF_NOINT)
 3533     {
 3534         FatalError(ctx, "EXPANDRANGE malformed range expression");
 3535     }
 3536 
 3537     if (from > to)
 3538     {
 3539         for (int i = from; i >= to; i -= step_size)
 3540         {
 3541             xsnprintf(work, template_size, "%s%d%s",before,i,after);;
 3542             RlistAppendScalar(&newlist, work);
 3543         }
 3544     }
 3545     else
 3546     {
 3547         for (int i = from; i <= to; i += step_size)
 3548         {
 3549             xsnprintf(work, template_size, "%s%d%s",before,i,after);;
 3550             RlistAppendScalar(&newlist, work);
 3551         }
 3552     }
 3553 
 3554     free(before);
 3555     free(after);
 3556 
 3557     return (FnCallResult) { FNCALL_SUCCESS, { newlist, RVAL_TYPE_LIST } };
 3558 }
 3559 
 3560 /*****************************************************************************/
 3561 
 3562 static FnCallResult FnCallMergeData(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *args)
 3563 {
 3564     if (RlistLen(args) == 0)
 3565     {
 3566         Log(LOG_LEVEL_ERR, "%s needs at least one argument, a reference to a container variable", fp->name);
 3567         return FnFailure();
 3568     }
 3569 
 3570     for (const Rlist *arg = args; arg; arg = arg->next)
 3571     {
 3572         if (args->val.type != RVAL_TYPE_SCALAR &&
 3573             args->val.type != RVAL_TYPE_CONTAINER)
 3574         {
 3575             Log(LOG_LEVEL_ERR, "%s: argument is not a variable reference", fp->name);
 3576             return FnFailure();
 3577         }
 3578     }
 3579 
 3580     Seq *containers = SeqNew(10, &JsonDestroy);
 3581 
 3582     for (const Rlist *arg = args; arg; arg = arg->next)
 3583     {
 3584         // try to load directly
 3585         bool allocated = false;
 3586         JsonElement *json = VarNameOrInlineToJson(ctx, fp, arg, false, &allocated);
 3587 
 3588         // we failed to produce a valid JsonElement, so give up
 3589         if (json == NULL)
 3590         {
 3591             SeqDestroy(containers);
 3592 
 3593             return FnFailure();
 3594         }
 3595 
 3596         // Fail on json primitives, only merge containers
 3597         if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER)
 3598         {
 3599             if (allocated)
 3600             {
 3601                 JsonDestroy(json);
 3602             }
 3603 
 3604             Log(LOG_LEVEL_ERR, "%s is not mergeable as it it not a container", RvalToString(arg->val));
 3605             SeqDestroy(containers);
 3606             return FnFailure();
 3607         }
 3608 
 3609         // This can be optimized better
 3610         if (allocated)
 3611         {
 3612             SeqAppend(containers, json);
 3613         }
 3614         else
 3615         {
 3616             SeqAppend(containers, JsonCopy(json));
 3617         }
 3618 
 3619     } // end of args loop
 3620 
 3621     if (SeqLength(containers) == 1)
 3622     {
 3623         JsonElement *first = JsonCopy(SeqAt(containers, 0));
 3624         SeqDestroy(containers);
 3625         return  (FnCallResult) { FNCALL_SUCCESS, (Rval) { first, RVAL_TYPE_CONTAINER } };
 3626     }
 3627     else
 3628     {
 3629         JsonElement *first = SeqAt(containers, 0);
 3630         JsonElement *second = SeqAt(containers, 1);
 3631         JsonElement *result = JsonMerge(first, second);
 3632 
 3633         for (size_t i = 2; i < SeqLength(containers); i++)
 3634         {
 3635             JsonElement *cur = SeqAt(containers, i);
 3636             JsonElement *tmp = JsonMerge(result, cur);
 3637             JsonDestroy(result);
 3638             result = tmp;
 3639         }
 3640 
 3641         SeqDestroy(containers);
 3642         return (FnCallResult) { FNCALL_SUCCESS, (Rval) { result, RVAL_TYPE_CONTAINER } };
 3643     }
 3644 
 3645     assert(false);
 3646 }
 3647 
 3648 JsonElement *DefaultTemplateData(const EvalContext *ctx, const char *wantbundle)
 3649 {
 3650     JsonElement *hash = JsonObjectCreate(30);
 3651     JsonElement *classes = NULL;
 3652     JsonElement *bundles = NULL;
 3653 
 3654     bool want_all_bundles = (wantbundle == NULL);
 3655 
 3656     if (want_all_bundles) // no specific bundle
 3657     {
 3658         classes = JsonObjectCreate(50);
 3659         bundles = JsonObjectCreate(50);
 3660         JsonObjectAppendObject(hash, "classes", classes);
 3661         JsonObjectAppendObject(hash, "vars", bundles);
 3662 
 3663         ClassTableIterator *it = EvalContextClassTableIteratorNewGlobal(ctx, NULL, true, true);
 3664         Class *cls;
 3665         while ((cls = ClassTableIteratorNext(it)))
 3666         {
 3667             char *key = ClassRefToString(cls->ns, cls->name);
 3668             JsonObjectAppendBool(classes, key, true);
 3669             free(key);
 3670         }
 3671         ClassTableIteratorDestroy(it);
 3672 
 3673         it = EvalContextClassTableIteratorNewLocal(ctx);
 3674         while ((cls = ClassTableIteratorNext(it)))
 3675         {
 3676             char *key = ClassRefToString(cls->ns, cls->name);
 3677             JsonObjectAppendBool(classes, key, true);
 3678             free(key);
 3679         }
 3680         ClassTableIteratorDestroy(it);
 3681     }
 3682 
 3683     {
 3684         VariableTableIterator *it = EvalContextVariableTableIteratorNew(ctx, NULL, NULL, NULL);
 3685         Variable *var;
 3686         while ((var = VariableTableIteratorNext(it)))
 3687         {
 3688             // TODO: need to get a CallRef, this is bad
 3689             char *scope_key = ClassRefToString(var->ref->ns, var->ref->scope);
 3690 
 3691             JsonElement *scope_obj = NULL;
 3692             if (want_all_bundles)
 3693             {
 3694                 scope_obj = JsonObjectGetAsObject(bundles, scope_key);
 3695                 if (!scope_obj)
 3696                 {
 3697                     scope_obj = JsonObjectCreate(50);
 3698                     JsonObjectAppendObject(bundles, scope_key, scope_obj);
 3699                 }
 3700             }
 3701             else if (strcmp(scope_key, wantbundle) == 0)
 3702             {
 3703                 scope_obj = hash;
 3704             }
 3705 
 3706             free(scope_key);
 3707 
 3708             if (scope_obj != NULL)
 3709             {
 3710                 char *lval_key = VarRefToString(var->ref, false);
 3711                 // don't collect mangled refs
 3712                 if (strchr(lval_key, CF_MANGLED_SCOPE) == NULL)
 3713                 {
 3714                     JsonObjectAppendElement(scope_obj, lval_key, RvalToJson(var->rval));
 3715                 }
 3716                 free(lval_key);
 3717             }
 3718         }
 3719         VariableTableIteratorDestroy(it);
 3720     }
 3721 
 3722     Writer *w = StringWriter();
 3723     JsonWrite(w, hash, 0);
 3724     Log(LOG_LEVEL_DEBUG, "Generated DefaultTemplateData '%s'", StringWriterData(w));
 3725     WriterClose(w);
 3726 
 3727     return hash;
 3728 }
 3729 
 3730 static FnCallResult FnCallDatastate(EvalContext *ctx,
 3731                                     ARG_UNUSED const Policy *policy,
 3732                                     ARG_UNUSED const FnCall *fp,
 3733                                     ARG_UNUSED const Rlist *args)
 3734 {
 3735     JsonElement *state = DefaultTemplateData(ctx, NULL);
 3736     return  (FnCallResult) { FNCALL_SUCCESS, (Rval) { state, RVAL_TYPE_CONTAINER } };
 3737 }
 3738 
 3739 static FnCallResult FnCallBundlestate(EvalContext *ctx,
 3740                                       ARG_UNUSED const Policy *policy,
 3741                                       ARG_UNUSED const FnCall *fp,
 3742                                       ARG_UNUSED const Rlist *args)
 3743 {
 3744     JsonElement *state = DefaultTemplateData(ctx, RlistScalarValue(args));
 3745 
 3746     if (state == NULL ||
 3747         JsonGetElementType(state) != JSON_ELEMENT_TYPE_CONTAINER ||
 3748         JsonLength(state) < 1)
 3749     {
 3750         if (state != NULL)
 3751         {
 3752             JsonDestroy(state);
 3753         }
 3754 
 3755         return FnFailure();
 3756     }
 3757     else
 3758     {
 3759         return  (FnCallResult) { FNCALL_SUCCESS, (Rval) { state, RVAL_TYPE_CONTAINER } };
 3760     }
 3761 }
 3762 
 3763 
 3764 static FnCallResult FnCallSelectServers(EvalContext *ctx,
 3765                                         ARG_UNUSED const Policy *policy,
 3766                                         const FnCall *fp,
 3767                                         const Rlist *finalargs)
 3768 {
 3769     const char *listvar = RlistScalarValue(finalargs);
 3770     const char *port = RlistScalarValue(finalargs->next);
 3771     const char *sendstring = RlistScalarValue(finalargs->next->next);
 3772     const char *regex = RlistScalarValue(finalargs->next->next->next);
 3773     ssize_t maxbytes = IntFromString(RlistScalarValue(finalargs->next->next->next->next));
 3774     char *array_lval = xstrdup(RlistScalarValue(finalargs->next->next->next->next->next));
 3775 
 3776     if (!IsQualifiedVariable(array_lval))
 3777     {
 3778         if (fp->caller)
 3779         {
 3780             VarRef *ref = VarRefParseFromBundle(array_lval, PromiseGetBundle(fp->caller));
 3781             free(array_lval);
 3782             array_lval = VarRefToString(ref, true);
 3783             VarRefDestroy(ref);
 3784         }
 3785         else
 3786         {
 3787             Log(LOG_LEVEL_ERR, "Function '%s' called with an unqualifed array reference '%s', "
 3788                 "and the reference could not be automatically qualified as the function was not called from a promise.",
 3789                 fp->name, array_lval);
 3790             free(array_lval);
 3791             return FnFailure();
 3792         }
 3793     }
 3794 
 3795     char naked[CF_MAXVARSIZE] = "";
 3796 
 3797     if (IsVarList(listvar))
 3798     {
 3799         GetNaked(naked, listvar);
 3800     }
 3801     else
 3802     {
 3803         Log(LOG_LEVEL_VERBOSE,
 3804             "Function selectservers was promised a list called '%s' but this was not found",
 3805             listvar);
 3806         return FnFailure();
 3807     }
 3808 
 3809     VarRef *ref = VarRefParse(naked);
 3810     DataType value_type;
 3811     const Rlist *hostnameip = EvalContextVariableGet(ctx, ref, &value_type);
 3812     if (value_type == CF_DATA_TYPE_NONE)
 3813     {
 3814         Log(LOG_LEVEL_VERBOSE,
 3815             "Function selectservers was promised a list called '%s' but this was not found from context '%s.%s'",
 3816             listvar, ref->scope, naked);
 3817         VarRefDestroy(ref);
 3818         free(array_lval);
 3819         return FnFailure();
 3820     }
 3821     VarRefDestroy(ref);
 3822 
 3823     if (DataTypeToRvalType(value_type) != RVAL_TYPE_LIST)
 3824     {
 3825         Log(LOG_LEVEL_VERBOSE,
 3826             "Function selectservers was promised a list called '%s' but this variable is not a list",
 3827             listvar);
 3828         free(array_lval);
 3829         return FnFailure();
 3830     }
 3831 
 3832     if (maxbytes < 0 || maxbytes > CF_BUFSIZE - 1)
 3833     {
 3834         Log(LOG_LEVEL_VERBOSE,
 3835             "selectservers: invalid number of bytes %zd to read, defaulting to %d",
 3836             maxbytes, CF_BUFSIZE - 1);
 3837         maxbytes = CF_BUFSIZE - 1;
 3838     }
 3839 
 3840     if (THIS_AGENT_TYPE != AGENT_TYPE_AGENT)
 3841     {
 3842         free(array_lval);
 3843         return FnReturnF("%d", 0);
 3844     }
 3845 
 3846     Policy *select_server_policy = PolicyNew();
 3847     {
 3848         Bundle *bp = PolicyAppendBundle(select_server_policy, NamespaceDefault(),
 3849                                         "select_server_bundle", "agent", NULL, NULL);
 3850         PromiseType *tp = BundleAppendPromiseType(bp, "select_server");
 3851 
 3852         PromiseTypeAppendPromise(tp, "function", (Rval) { NULL, RVAL_TYPE_NOPROMISEE }, NULL, NULL);
 3853     }
 3854 
 3855     size_t count = 0;
 3856     for (const Rlist *rp = hostnameip; rp != NULL; rp = rp->next)
 3857     {
 3858         const char *host = RlistScalarValue(rp);
 3859         Log(LOG_LEVEL_DEBUG, "Want to read %zd bytes from %s port %s",
 3860             maxbytes, host, port);
 3861 
 3862         char txtaddr[CF_MAX_IP_LEN] = "";
 3863         int sd = SocketConnect(host, port, CONNTIMEOUT, false,
 3864                                txtaddr, sizeof(txtaddr));
 3865         if (sd == -1)
 3866         {
 3867             continue;
 3868         }
 3869 
 3870         if (strlen(sendstring) > 0)
 3871         {
 3872             if (SendSocketStream(sd, sendstring, strlen(sendstring)) != -1)
 3873             {
 3874                 char recvbuf[CF_BUFSIZE];
 3875                 ssize_t n_read = recv(sd, recvbuf, maxbytes, 0);
 3876 
 3877                 if (n_read != -1)
 3878                 {
 3879                     /* maxbytes was checked earlier, but just make sure... */
 3880                     assert(n_read < sizeof(recvbuf));
 3881                     recvbuf[n_read] = '\0';
 3882 
 3883                     if (strlen(regex) == 0 || StringMatchFull(regex, recvbuf))
 3884                     {
 3885                         Log(LOG_LEVEL_VERBOSE,
 3886                             "selectservers: Got matching reply from host %s address %s",
 3887                             host, txtaddr);
 3888 
 3889                         char buffer[CF_MAXVARSIZE] = "";
 3890                         snprintf(buffer, sizeof(buffer), "%s[%zu]", array_lval, count);
 3891                         VarRef *ref = VarRefParse(buffer);
 3892                         EvalContextVariablePut(ctx, ref, host, CF_DATA_TYPE_STRING,
 3893                                                "source=function,function=selectservers");
 3894                         VarRefDestroy(ref);
 3895 
 3896                         count++;
 3897                     }
 3898                 }
 3899             }
 3900         }
 3901         else                      /* If query is empty, all hosts are added */
 3902         {
 3903             Log(LOG_LEVEL_VERBOSE,
 3904                 "selectservers: Got reply from host %s address %s",
 3905                 host, txtaddr);
 3906 
 3907             char buffer[CF_MAXVARSIZE] = "";
 3908             snprintf(buffer, sizeof(buffer), "%s[%zu]", array_lval, count);
 3909             VarRef *ref = VarRefParse(buffer);
 3910             EvalContextVariablePut(ctx, ref, host, CF_DATA_TYPE_STRING,
 3911                                    "source=function,function=selectservers");
 3912             VarRefDestroy(ref);
 3913 
 3914             count++;
 3915         }
 3916 
 3917         cf_closesocket(sd);
 3918     }
 3919 
 3920     PolicyDestroy(select_server_policy);
 3921     free(array_lval);
 3922 
 3923     Log(LOG_LEVEL_VERBOSE, "selectservers: found %zu servers", count);
 3924     return FnReturnF("%zu", count);
 3925 }
 3926 
 3927 
 3928 static FnCallResult FnCallShuffle(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
 3929 {
 3930     const char *seed_str = RlistScalarValue(finalargs->next);
 3931 
 3932     const char *name_str = RlistScalarValueSafe(finalargs);
 3933 
 3934     // try to load directly
 3935     bool allocated = false;
 3936     JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs, false, &allocated);
 3937 
 3938     // we failed to produce a valid JsonElement, so give up
 3939     if (json == NULL)
 3940     {
 3941         return FnFailure();
 3942     }
 3943     else if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER)
 3944     {
 3945         Log(LOG_LEVEL_VERBOSE, "Function '%s', argument '%s' was not a data container or list",
 3946             fp->name, name_str);
 3947         JsonDestroyMaybe(json, allocated);
 3948         return FnFailure();
 3949     }
 3950 
 3951     Seq *seq = SeqNew(100, NULL);
 3952     JsonIterator iter = JsonIteratorInit(json);
 3953     const JsonElement *e;
 3954     while ((e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true)))
 3955     {
 3956         SeqAppend(seq, (void*)JsonPrimitiveGetAsString(e));
 3957     }
 3958 
 3959     SeqShuffle(seq, StringHash(seed_str, 0));
 3960 
 3961     Rlist *shuffled = NULL;
 3962     for (size_t i = 0; i < SeqLength(seq); i++)
 3963     {
 3964         RlistPrepend(&shuffled, SeqAt(seq, i), RVAL_TYPE_SCALAR);
 3965     }
 3966 
 3967     SeqDestroy(seq);
 3968     JsonDestroyMaybe(json, allocated);
 3969     return (FnCallResult) { FNCALL_SUCCESS, (Rval) { shuffled, RVAL_TYPE_LIST } };
 3970 }
 3971 
 3972 
 3973 static FnCallResult FnCallIsNewerThan(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
 3974 {
 3975     struct stat frombuf, tobuf;
 3976 
 3977     if (stat(RlistScalarValue(finalargs),     &frombuf) == -1 ||
 3978         stat(RlistScalarValue(finalargs->next), &tobuf) == -1)
 3979     {
 3980         return FnFailure();
 3981     }
 3982 
 3983     return FnReturnContext(frombuf.st_mtime > tobuf.st_mtime);
 3984 }
 3985 
 3986 /*********************************************************************/
 3987 
 3988 static FnCallResult FnCallIsAccessedBefore(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
 3989 {
 3990     struct stat frombuf, tobuf;
 3991 
 3992     if (stat(RlistScalarValue(finalargs),     &frombuf) == -1 ||
 3993         stat(RlistScalarValue(finalargs->next), &tobuf) == -1)
 3994     {
 3995         return FnFailure();
 3996     }
 3997 
 3998     return FnReturnContext(frombuf.st_atime < tobuf.st_atime);
 3999 }
 4000 
 4001 /*********************************************************************/
 4002 
 4003 static FnCallResult FnCallIsChangedBefore(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
 4004 {
 4005     struct stat frombuf, tobuf;
 4006 
 4007     if (stat(RlistScalarValue(finalargs),     &frombuf) == -1 ||
 4008         stat(RlistScalarValue(finalargs->next), &tobuf) == -1)
 4009     {
 4010         return FnFailure();
 4011     }
 4012 
 4013     return FnReturnContext(frombuf.st_ctime > tobuf.st_ctime);
 4014 }
 4015 
 4016 /*********************************************************************/
 4017 
 4018 static FnCallResult FnCallFileStat(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
 4019 {
 4020     char *path = RlistScalarValue(finalargs);
 4021     struct stat statbuf;
 4022 
 4023     if (lstat(path, &statbuf) == -1)
 4024     {
 4025         if (StringEqual(fp->name, "filesize"))
 4026         {
 4027             return FnFailure();
 4028         }
 4029         return FnReturnContext(false);
 4030     }
 4031 
 4032     if (!strcmp(fp->name, "isexecutable"))
 4033     {
 4034         if (S_ISLNK(statbuf.st_mode) && stat(path, &statbuf) == -1)
 4035         {
 4036             // stat on link target failed - probably broken link
 4037             return FnReturnContext(false);
 4038         }
 4039         return FnReturnContext(IsExecutable(path));
 4040     }
 4041     if (!strcmp(fp->name, "isdir"))
 4042     {
 4043         return FnReturnContext(S_ISDIR(statbuf.st_mode));
 4044     }
 4045     if (!strcmp(fp->name, "islink"))
 4046     {
 4047         return FnReturnContext(S_ISLNK(statbuf.st_mode));
 4048     }
 4049     if (!strcmp(fp->name, "isplain"))
 4050     {
 4051         return FnReturnContext(S_ISREG(statbuf.st_mode));
 4052     }
 4053     if (!strcmp(fp->name, "fileexists"))
 4054     {
 4055         return FnReturnContext(true);
 4056     }
 4057     if (!strcmp(fp->name, "filesize"))
 4058     {
 4059         return FnReturnF("%ju", (uintmax_t) statbuf.st_size);
 4060     }
 4061 
 4062     ProgrammingError("Unexpected function name in FnCallFileStat: %s", fp->name);
 4063 }
 4064 
 4065 /*********************************************************************/
 4066 
 4067 static FnCallResult FnCallFileStatDetails(ARG_UNUSED EvalContext *ctx,
 4068                                           ARG_UNUSED const Policy *policy,
 4069                                           const FnCall *fp,
 4070                                           const Rlist *finalargs)
 4071 {
 4072     char buffer[CF_BUFSIZE];
 4073     const char *path = RlistScalarValue(finalargs);
 4074     char *detail = RlistScalarValue(finalargs->next);
 4075     struct stat statbuf;
 4076 
 4077     buffer[0] = '\0';
 4078 
 4079     if (lstat(path, &statbuf) == -1)
 4080     {
 4081         return FnFailure();
 4082     }
 4083     else if (!strcmp(detail, "xattr"))
 4084     {
 4085 #if defined(WITH_XATTR)
 4086         // Extended attributes include both POSIX ACLs and SELinux contexts.
 4087         char attr_raw_names[CF_BUFSIZE];
 4088         ssize_t attr_raw_names_size = llistxattr(path, attr_raw_names, sizeof(attr_raw_names));
 4089 
 4090         if (attr_raw_names_size < 0)
 4091         {
 4092             if (errno != ENOTSUP && errno != ENODATA)
 4093             {
 4094                 Log(LOG_LEVEL_ERR, "Can't read extended attributes of '%s'. (llistxattr: %s)",
 4095                     path, GetErrorStr());
 4096             }
 4097         }
 4098         else
 4099         {
 4100             Buffer *printattr = BufferNew();
 4101             for (int pos = 0; pos < attr_raw_names_size;)
 4102             {
 4103                 const char *current = attr_raw_names + pos;
 4104                 pos += strlen(current) + 1;
 4105 
 4106                 if (!StringIsPrintable(current))
 4107                 {
 4108                     Log(LOG_LEVEL_INFO, "Skipping extended attribute of '%s', it has a non-printable name: '%s'",
 4109                         path, current);
 4110                     continue;
 4111                 }
 4112 
 4113                 char data[CF_BUFSIZE];
 4114                 int datasize = lgetxattr(path, current, data, sizeof(data));
 4115                 if (datasize < 0)
 4116                 {
 4117                     if (errno == ENOTSUP)
 4118                     {
 4119                         continue;
 4120                     }
 4121                     else
 4122                     {
 4123                         Log(LOG_LEVEL_ERR, "Can't read extended attribute '%s' of '%s'. (lgetxattr: %s)",
 4124                             path, current, GetErrorStr());
 4125                     }
 4126                 }
 4127                 else
 4128                 {
 4129                   if (!StringIsPrintable(data))
 4130                   {
 4131                       Log(LOG_LEVEL_INFO, "Skipping extended attribute of '%s', it has non-printable data: '%s=%s'",
 4132                           path, current, data);
 4133                       continue;
 4134                   }
 4135 
 4136                   BufferPrintf(printattr, "%s=%s", current, data);
 4137 
 4138                   // Append a newline for multiple attributes.
 4139                   if (attr_raw_names_size > 0)
 4140                   {
 4141                       BufferAppendChar(printattr, '\n');
 4142                   }
 4143                 }
 4144             }
 4145 
 4146             snprintf(buffer, CF_MAXVARSIZE, "%s", BufferData(printattr));
 4147             BufferDestroy(printattr);
 4148         }
 4149 #else // !WITH_XATTR
 4150     // do nothing, leave the buffer empty
 4151 #endif
 4152     }
 4153     else if (!strcmp(detail, "size"))
 4154     {
 4155         snprintf(buffer, CF_MAXVARSIZE, "%ju", (uintmax_t) statbuf.st_size);
 4156     }
 4157     else if (!strcmp(detail, "gid"))
 4158     {
 4159         snprintf(buffer, CF_MAXVARSIZE, "%ju", (uintmax_t) statbuf.st_gid);
 4160     }
 4161     else if (!strcmp(detail, "uid"))
 4162     {
 4163         snprintf(buffer, CF_MAXVARSIZE, "%ju", (uintmax_t) statbuf.st_uid);
 4164     }
 4165     else if (!strcmp(detail, "ino"))
 4166     {
 4167         snprintf(buffer, CF_MAXVARSIZE, "%ju", (uintmax_t) statbuf.st_ino);
 4168     }
 4169     else if (!strcmp(detail, "nlink"))
 4170     {
 4171         snprintf(buffer, CF_MAXVARSIZE, "%ju", (uintmax_t) statbuf.st_nlink);
 4172     }
 4173     else if (!strcmp(detail, "ctime"))
 4174     {
 4175         snprintf(buffer, CF_MAXVARSIZE, "%jd", (intmax_t) statbuf.st_ctime);
 4176     }
 4177     else if (!strcmp(detail, "mtime"))
 4178     {
 4179         snprintf(buffer, CF_MAXVARSIZE, "%jd", (intmax_t) statbuf.st_mtime);
 4180     }
 4181     else if (!strcmp(detail, "atime"))
 4182     {
 4183         snprintf(buffer, CF_MAXVARSIZE, "%jd", (intmax_t) statbuf.st_atime);
 4184     }
 4185     else if (!strcmp(detail, "permstr"))
 4186     {
 4187         snprintf(buffer, CF_MAXVARSIZE,
 4188                  "%c%c%c%c%c%c%c%c%c%c",
 4189                  S_ISDIR(statbuf.st_mode) ? 'd' : '-',
 4190                  (statbuf.st_mode & S_IRUSR) ? 'r' : '-',
 4191                  (statbuf.st_mode & S_IWUSR) ? 'w' : '-',
 4192                  (statbuf.st_mode & S_IXUSR) ? 'x' : '-',
 4193                  (statbuf.st_mode & S_IRGRP) ? 'r' : '-',
 4194                  (statbuf.st_mode & S_IWGRP) ? 'w' : '-',
 4195                  (statbuf.st_mode & S_IXGRP) ? 'x' : '-',
 4196                  (statbuf.st_mode & S_IROTH) ? 'r' : '-',
 4197                  (statbuf.st_mode & S_IWOTH) ? 'w' : '-',
 4198                  (statbuf.st_mode & S_IXOTH) ? 'x' : '-');
 4199     }
 4200     else if (!strcmp(detail, "permoct"))
 4201     {
 4202         snprintf(buffer, CF_MAXVARSIZE, "%jo", (uintmax_t) (statbuf.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)));
 4203     }
 4204     else if (!strcmp(detail, "modeoct"))
 4205     {
 4206         snprintf(buffer, CF_MAXVARSIZE, "%jo", (uintmax_t) statbuf.st_mode);
 4207     }
 4208     else if (!strcmp(detail, "mode"))
 4209     {
 4210         snprintf(buffer, CF_MAXVARSIZE, "%ju", (uintmax_t) statbuf.st_mode);
 4211     }
 4212     else if (!strcmp(detail, "type"))
 4213     {
 4214         switch (statbuf.st_mode & S_IFMT)
 4215         {
 4216         case S_IFBLK:  snprintf(buffer, CF_MAXVARSIZE, "%s", "block device");     break;
 4217         case S_IFCHR:  snprintf(buffer, CF_MAXVARSIZE, "%s", "character device"); break;
 4218         case S_IFDIR:  snprintf(buffer, CF_MAXVARSIZE, "%s", "directory");        break;
 4219         case S_IFIFO:  snprintf(buffer, CF_MAXVARSIZE, "%s", "FIFO/pipe");        break;
 4220         case S_IFLNK:  snprintf(buffer, CF_MAXVARSIZE, "%s", "symlink");          break;
 4221         case S_IFREG:  snprintf(buffer, CF_MAXVARSIZE, "%s", "regular file");     break;
 4222         case S_IFSOCK: snprintf(buffer, CF_MAXVARSIZE, "%s", "socket");           break;
 4223         default:       snprintf(buffer, CF_MAXVARSIZE, "%s", "unknown");          break;
 4224         }
 4225     }
 4226     else if (!strcmp(detail, "dev_minor"))
 4227     {
 4228 #if !defined(__MINGW32__)
 4229         snprintf(buffer, CF_MAXVARSIZE, "%ju", (uintmax_t) minor(statbuf.st_dev) );
 4230 #else
 4231         snprintf(buffer, CF_MAXVARSIZE, "Not available on Windows");
 4232 #endif
 4233     }
 4234     else if (!strcmp(detail, "dev_major"))
 4235     {
 4236 #if !defined(__MINGW32__)
 4237         snprintf(buffer, CF_MAXVARSIZE, "%ju", (uintmax_t) major(statbuf.st_dev) );
 4238 #else
 4239         snprintf(buffer, CF_MAXVARSIZE, "Not available on Windows");
 4240 #endif
 4241     }
 4242     else if (!strcmp(detail, "devno"))
 4243     {
 4244 #if !defined(__MINGW32__)
 4245         snprintf(buffer, CF_MAXVARSIZE, "%ju", (uintmax_t) statbuf.st_dev );
 4246 #else
 4247         snprintf(buffer, CF_MAXVARSIZE, "%c:", statbuf.st_dev + 'A');
 4248 #endif
 4249     }
 4250     else if (!strcmp(detail, "dirname"))
 4251     {
 4252         snprintf(buffer, CF_MAXVARSIZE, "%s", path);
 4253         ChopLastNode(buffer);
 4254         MapName(buffer);
 4255     }
 4256     else if (!strcmp(detail, "basename"))
 4257     {
 4258         snprintf(buffer, CF_MAXVARSIZE, "%s", ReadLastNode(path));
 4259     }
 4260     else if (!strcmp(detail, "linktarget") || !strcmp(detail, "linktarget_shallow"))
 4261     {
 4262 #if !defined(__MINGW32__)
 4263         char path_buffer[CF_BUFSIZE];
 4264         bool recurse = !strcmp(detail, "linktarget");
 4265         int cycles = 0;
 4266         int max_cycles = 30; // This allows for up to 31 levels of indirection.
 4267 
 4268         strlcpy(path_buffer, path, CF_MAXVARSIZE);
 4269 
 4270         // Iterate while we're looking at a link.
 4271         while (S_ISLNK(statbuf.st_mode))
 4272         {
 4273             if (cycles > max_cycles)
 4274             {
 4275                 Log(LOG_LEVEL_INFO,
 4276                     "%s bailing on link '%s' (original '%s') because %d cycles were chased",
 4277                     fp->name, path_buffer, path, cycles + 1);
 4278                 break;
 4279             }
 4280 
 4281             Log(LOG_LEVEL_VERBOSE, "%s cycle %d, resolving link: %s", fp->name, cycles+1, path_buffer);
 4282 
 4283             /* Note we subtract 1 since we may need an extra char for '\0'. */
 4284             ssize_t got = readlink(path_buffer, buffer, CF_BUFSIZE - 1);
 4285             if (got < 0)
 4286             {
 4287                 // An error happened.  Empty the buffer (don't keep the last target).
 4288                 Log(LOG_LEVEL_ERR, "%s could not readlink '%s'", fp->name, path_buffer);
 4289                 path_buffer[0] = '\0';
 4290                 break;
 4291             }
 4292             buffer[got] = '\0';             /* readlink() doesn't terminate */
 4293 
 4294             /* If it is a relative path, then in order to follow it further we
 4295              * need to prepend the directory. */
 4296             if (!IsAbsoluteFileName(buffer) &&
 4297                 strcmp(detail, "linktarget") == 0)
 4298             {
 4299                 DeleteSlash(path_buffer);
 4300                 ChopLastNode(path_buffer);
 4301                 AddSlash(path_buffer);
 4302                 strlcat(path_buffer, buffer, sizeof(path_buffer));
 4303                 /* Use buffer again as a tmp buffer. */
 4304                 CompressPath(buffer, sizeof(buffer), path_buffer);
 4305             }
 4306 
 4307             // We got a good link target into buffer.  Copy it to path_buffer.
 4308             strlcpy(path_buffer, buffer, CF_MAXVARSIZE);
 4309 
 4310             Log(LOG_LEVEL_VERBOSE, "Link resolved to: %s", path_buffer);
 4311 
 4312             if (!recurse)
 4313             {
 4314                 Log(LOG_LEVEL_VERBOSE,
 4315                     "%s bailing on link '%s' (original '%s') because linktarget_shallow was requested",
 4316                     fp->name, path_buffer, path);
 4317                 break;
 4318             }
 4319             else if (lstat(path_buffer, &statbuf) == -1)
 4320             {
 4321                 Log(LOG_LEVEL_INFO,
 4322                     "%s bailing on link '%s' (original '%s') because it could not be read",
 4323                     fp->name, path_buffer, path);
 4324                 break;
 4325             }
 4326 
 4327             // At this point we haven't bailed, path_buffer has the link target
 4328             cycles++;
 4329         }
 4330 
 4331         // Get the path_buffer back into buffer.
 4332         strlcpy(buffer, path_buffer, CF_MAXVARSIZE);
 4333 #else
 4334         // Always return the original path on W32.
 4335         strlcpy(buffer, path, CF_MAXVARSIZE);
 4336 #endif
 4337     }
 4338 
 4339     return FnReturn(buffer);
 4340 }
 4341 
 4342 /*********************************************************************/
 4343 
 4344 static FnCallResult FnCallFindfiles(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
 4345 {
 4346     Rlist *returnlist = NULL;
 4347     char id[CF_BUFSIZE];
 4348 
 4349     snprintf(id, CF_BUFSIZE, "built-in FnCall %s-arg", fp->name);
 4350 
 4351     /* We need to check all the arguments, ArgTemplate does not check varadic functions */
 4352     for (const Rlist *arg = finalargs; arg; arg = arg->next)
 4353     {
 4354         SyntaxTypeMatch err = CheckConstraintTypeMatch(id, arg->val, CF_DATA_TYPE_STRING, "", 1);
 4355         if (err != SYNTAX_TYPE_MATCH_OK && err != SYNTAX_TYPE_MATCH_ERROR_UNEXPANDED)
 4356         {
 4357             FatalError(ctx, "in %s: %s", id, SyntaxTypeMatchToString(err));
 4358         }
 4359     }
 4360 
 4361     for (const Rlist *arg = finalargs;  /* Start with arg set to finalargs. */
 4362          arg;              /* We must have arg to proceed. */
 4363          arg = arg->next)  /* arg steps forward every time. */
 4364     {
 4365         const char *pattern = RlistScalarValue(arg);
 4366 
 4367         if (!IsAbsoluteFileName(pattern))
 4368         {
 4369             Log(LOG_LEVEL_WARNING,
 4370                 "Non-absolute path in findfiles(), skipping: %s",
 4371                 pattern);
 4372             continue;
 4373         }
 4374 
 4375         StringSet *found = GlobFileList(pattern);
 4376 
 4377         char fname[CF_BUFSIZE];
 4378 
 4379         StringSetIterator it = StringSetIteratorInit(found);
 4380         const char *element = NULL;
 4381         while ((element = StringSetIteratorNext(&it)))
 4382         {
 4383             // TODO: this truncates the filename and may be removed
 4384             // if Rlist and the core are OK with that possibility
 4385             strlcpy(fname, element, CF_BUFSIZE);
 4386             Log(LOG_LEVEL_VERBOSE, "%s pattern '%s' found match '%s'", fp->name, pattern, fname);
 4387             RlistAppendScalarIdemp(&returnlist, fname);
 4388         }
 4389         StringSetDestroy(found);
 4390     }
 4391 
 4392     return (FnCallResult) { FNCALL_SUCCESS, { returnlist, RVAL_TYPE_LIST } };
 4393 }
 4394 
 4395 /*********************************************************************/
 4396 
 4397 static FnCallResult FnCallFilter(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
 4398 {
 4399     return FilterInternal(ctx,
 4400                           fp,
 4401                           RlistScalarValue(finalargs), // regex or string
 4402                           finalargs->next, // list identifier
 4403                           BooleanFromString(RlistScalarValue(finalargs->next->next)), // match as regex or exactly
 4404                           BooleanFromString(RlistScalarValue(finalargs->next->next->next)), // invert matches
 4405                           IntFromString(RlistScalarValue(finalargs->next->next->next->next))); // max results
 4406 }
 4407 
 4408 /*********************************************************************/
 4409 
 4410 static const Rlist *GetListReferenceArgument(const EvalContext *ctx, const FnCall *fp, const char *lval_str, DataType *datatype_out)
 4411 {
 4412     VarRef *ref = VarRefParse(lval_str);
 4413     DataType value_type;
 4414     const Rlist *value = EvalContextVariableGet(ctx, ref, &value_type);
 4415     VarRefDestroy(ref);
 4416 
 4417     /* Error 1: variable not found. */
 4418     if (value_type == CF_DATA_TYPE_NONE)
 4419     {
 4420         Log(LOG_LEVEL_VERBOSE,
 4421             "Could not resolve expected list variable '%s' in function '%s'",
 4422             lval_str, fp->name);
 4423         assert(value == NULL);
 4424     }
 4425     /* Error 2: variable is not a list. */
 4426     else if (DataTypeToRvalType(value_type) != RVAL_TYPE_LIST)
 4427     {
 4428         Log(LOG_LEVEL_ERR, "Function '%s' expected a list variable,"
 4429             " got variable of type '%s'",
 4430             fp->name, DataTypeToString(value_type));
 4431 
 4432         value      = NULL;
 4433         value_type = CF_DATA_TYPE_NONE;
 4434     }
 4435 
 4436     if (datatype_out)
 4437     {
 4438         *datatype_out = value_type;
 4439     }
 4440     return value;
 4441 }
 4442 
 4443 /*********************************************************************/
 4444 
 4445 static FnCallResult FilterInternal(EvalContext *ctx,
 4446                                    const FnCall *fp,
 4447                                    const char *regex,
 4448                                    const Rlist* rp,
 4449                                    bool do_regex,
 4450                                    bool invert,
 4451                                    long max)
 4452 {
 4453     pcre *rx = NULL;
 4454     if (do_regex)
 4455     {
 4456         rx = CompileRegex(regex);
 4457         if (!rx)
 4458         {
 4459             return FnFailure();
 4460         }
 4461     }
 4462 
 4463     bool allocated = false;
 4464     JsonElement *json = VarNameOrInlineToJson(ctx, fp, rp, false, &allocated);
 4465 
 4466     // we failed to produce a valid JsonElement, so give up
 4467     if (json == NULL)
 4468     {
 4469         pcre_free(rx);
 4470         return FnFailure();
 4471     }
 4472     else if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER)
 4473     {
 4474         Log(LOG_LEVEL_VERBOSE, "Function '%s', argument '%s' was not a data container or list",
 4475             fp->name, RlistScalarValueSafe(rp));
 4476         JsonDestroyMaybe(json, allocated);
 4477         pcre_free(rx);
 4478         return FnFailure();
 4479     }
 4480 
 4481     Rlist *returnlist = NULL;
 4482 
 4483     long match_count = 0;
 4484     long total = 0;
 4485 
 4486     JsonIterator iter = JsonIteratorInit(json);
 4487     const JsonElement *el = NULL;
 4488     while ((el = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true)) &&
 4489            match_count < max)
 4490     {
 4491         char *val = JsonPrimitiveToString(el);
 4492         if (val != NULL)
 4493         {
 4494             bool found;
 4495             if (do_regex)
 4496             {
 4497                 found = StringMatchFullWithPrecompiledRegex(rx, val);
 4498             }
 4499             else
 4500             {
 4501                 found = (0==strcmp(regex, val));
 4502             }
 4503 
 4504             if (invert ? !found : found)
 4505             {
 4506                 RlistAppendScalar(&returnlist, val);
 4507                 match_count++;
 4508 
 4509                 if (strcmp(fp->name, "some")     == 0 ||
 4510                     strcmp(fp->name, "regarray") == 0)
 4511                 {
 4512                     free(val);
 4513                     break;
 4514                 }
 4515             }
 4516             else if (strcmp(fp->name, "every") == 0)
 4517             {
 4518                 total++;
 4519                 free(val);
 4520                 break;
 4521             }
 4522 
 4523             total++;
 4524             free(val);
 4525         }
 4526     }
 4527 
 4528     JsonDestroyMaybe(json, allocated);
 4529 
 4530     if (rx)
 4531     {
 4532         pcre_free(rx);
 4533     }
 4534 
 4535     bool contextmode = false;
 4536     bool ret = false;
 4537     if (strcmp(fp->name, "every") == 0)
 4538     {
 4539         contextmode = true;
 4540         ret = (match_count == total && total > 0);
 4541     }
 4542     else if (strcmp(fp->name, "none") == 0)
 4543     {
 4544         contextmode = true;
 4545         ret = (match_count == 0);
 4546     }
 4547     else if (strcmp(fp->name, "some")     == 0 ||
 4548              strcmp(fp->name, "regarray") == 0 ||
 4549              strcmp(fp->name, "reglist")  == 0)
 4550     {
 4551         contextmode = true;
 4552         ret = (match_count > 0);
 4553     }
 4554     else if (strcmp(fp->name, "grep")   != 0 &&
 4555              strcmp(fp->name, "filter") != 0)
 4556     {
 4557         ProgrammingError("built-in FnCall %s: unhandled FilterInternal() contextmode", fp->name);
 4558     }
 4559 
 4560     if (contextmode)
 4561     {
 4562         RlistDestroy(returnlist);
 4563         return FnReturnContext(ret);
 4564     }
 4565 
 4566     // else, return the list itself
 4567     return (FnCallResult) { FNCALL_SUCCESS, { returnlist, RVAL_TYPE_LIST } };
 4568 }
 4569 
 4570 /*********************************************************************/
 4571 
 4572 static FnCallResult FnCallSublist(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
 4573 {
 4574     const char *name_str = RlistScalarValueSafe(finalargs);
 4575 
 4576     // try to load directly
 4577     bool allocated = false;
 4578     JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs, false, &allocated);
 4579 
 4580     // we failed to produce a valid JsonElement, so give up
 4581     if (json == NULL)
 4582     {
 4583         return FnFailure();
 4584     }
 4585     else if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER)
 4586     {
 4587         Log(LOG_LEVEL_VERBOSE, "Function '%s', argument '%s' was not a data container or list",
 4588             fp->name, name_str);
 4589         JsonDestroyMaybe(json, allocated);
 4590         return FnFailure();
 4591     }
 4592 
 4593     bool head = (strcmp(RlistScalarValue(finalargs->next), "head") == 0); // heads or tails
 4594     long max = IntFromString(RlistScalarValue(finalargs->next->next)); // max results
 4595 
 4596     Rlist *input_list = NULL;
 4597     Rlist *returnlist = NULL;
 4598 
 4599     JsonIterator iter = JsonIteratorInit(json);
 4600     const JsonElement *e;
 4601     while ((e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true)))
 4602     {
 4603         RlistAppendScalar(&input_list, JsonPrimitiveGetAsString(e));
 4604     }
 4605 
 4606     JsonDestroyMaybe(json, allocated);
 4607 
 4608     if (head)
 4609     {
 4610         long count = 0;
 4611         for (const Rlist *rp = input_list; rp != NULL && count < max; rp = rp->next)
 4612         {
 4613             RlistAppendScalar(&returnlist, RlistScalarValue(rp));
 4614             count++;
 4615         }
 4616     }
 4617     else if (max > 0) // tail mode
 4618     {
 4619         const Rlist *rp = input_list;
 4620         int length = RlistLen((const Rlist *) rp);
 4621 
 4622         int offset = max >= length ? 0 : length-max;
 4623 
 4624         for (; rp != NULL && offset--; rp = rp->next)
 4625         {
 4626             /* skip to offset */
 4627         }
 4628 
 4629         for (; rp != NULL; rp = rp->next)
 4630         {
 4631             RlistAppendScalar(&returnlist, RlistScalarValue(rp));
 4632         }
 4633     }
 4634 
 4635     RlistDestroy(input_list);
 4636     return (FnCallResult) { FNCALL_SUCCESS, { returnlist, RVAL_TYPE_LIST } };
 4637 }
 4638 
 4639 /*********************************************************************/
 4640 
 4641 // TODO: This monstrosity needs refactoring
 4642 static FnCallResult FnCallSetop(EvalContext *ctx,
 4643                                 ARG_UNUSED const Policy *policy,
 4644                                 const FnCall *fp, const Rlist *finalargs)
 4645 {
 4646     bool difference_mode = (strcmp(fp->name, "difference") == 0);
 4647     bool unique_mode = (strcmp(fp->name, "unique") == 0);
 4648 
 4649     const char *name_str = RlistScalarValueSafe(finalargs);
 4650     bool allocated = false;
 4651     JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs, false, &allocated);
 4652 
 4653     // we failed to produce a valid JsonElement, so give up
 4654     if (json == NULL)
 4655     {
 4656         return FnFailure();
 4657     }
 4658     else if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER)
 4659     {
 4660         Log(LOG_LEVEL_VERBOSE, "Function '%s', argument '%s' was not a data container or list",
 4661             fp->name, name_str);
 4662         JsonDestroyMaybe(json, allocated);
 4663         return FnFailure();
 4664     }
 4665 
 4666     JsonElement *json_b = NULL;
 4667     bool allocated_b = false;
 4668     if (!unique_mode)
 4669     {
 4670         const char *name_str_b = RlistScalarValueSafe(finalargs->next);
 4671         json_b = VarNameOrInlineToJson(ctx, fp, finalargs->next, false, &allocated_b);
 4672 
 4673         // we failed to produce a valid JsonElement, so give up
 4674         if (json_b == NULL)
 4675         {
 4676             JsonDestroyMaybe(json, allocated);
 4677             return FnFailure();
 4678         }
 4679         else if (JsonGetElementType(json_b) != JSON_ELEMENT_TYPE_CONTAINER)
 4680         {
 4681             Log(LOG_LEVEL_VERBOSE, "Function '%s', argument '%s' was not a data container or list",
 4682                 fp->name, name_str_b);
 4683             JsonDestroyMaybe(json, allocated);
 4684             JsonDestroyMaybe(json_b, allocated_b);
 4685             return FnFailure();
 4686         }
 4687     }
 4688 
 4689     StringSet *set_b = StringSetNew();
 4690     if (!unique_mode)
 4691     {
 4692         JsonIterator iter = JsonIteratorInit(json_b);
 4693         const JsonElement *e;
 4694         while ((e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true)))
 4695         {
 4696             StringSetAdd(set_b, xstrdup(JsonPrimitiveGetAsString(e)));
 4697         }
 4698     }
 4699 
 4700     Rlist *returnlist = NULL;
 4701 
 4702     JsonIterator iter = JsonIteratorInit(json);
 4703     const JsonElement *e;
 4704     while ((e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true)))
 4705     {
 4706         const char *value = JsonPrimitiveGetAsString(e);
 4707 
 4708         // Yes, this is an XOR.  But it's more legible this way.
 4709         if (!unique_mode && difference_mode && StringSetContains(set_b, value))
 4710         {
 4711             continue;
 4712         }
 4713 
 4714         if (!unique_mode && !difference_mode && !StringSetContains(set_b, value))
 4715         {
 4716             continue;
 4717         }
 4718 
 4719         RlistAppendScalarIdemp(&returnlist, value);
 4720     }
 4721 
 4722     JsonDestroyMaybe(json, allocated);
 4723     if (json_b != NULL)
 4724     {
 4725         JsonDestroyMaybe(json_b, allocated_b);
 4726     }
 4727 
 4728 
 4729     StringSetDestroy(set_b);
 4730 
 4731     return (FnCallResult) { FNCALL_SUCCESS, (Rval) { returnlist, RVAL_TYPE_LIST } };
 4732 }
 4733 
 4734 static FnCallResult FnCallLength(EvalContext *ctx,
 4735                                  ARG_UNUSED const Policy *policy,
 4736                                  const FnCall *fp, const Rlist *finalargs)
 4737 {
 4738     const char *name_str = RlistScalarValueSafe(finalargs);
 4739 
 4740     // try to load directly
 4741     bool allocated = false;
 4742     JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs, false, &allocated);
 4743 
 4744     // we failed to produce a valid JsonElement, so give up
 4745     if (json == NULL)
 4746     {
 4747         return FnFailure();
 4748     }
 4749     else if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER)
 4750     {
 4751         Log(LOG_LEVEL_VERBOSE, "Function '%s', argument '%s' was not a data container or list",
 4752             fp->name, name_str);
 4753         JsonDestroyMaybe(json, allocated);
 4754         return FnFailure();
 4755     }
 4756 
 4757     size_t len = JsonLength(json);
 4758     JsonDestroyMaybe(json, allocated);
 4759     return FnReturnF("%zu", len);
 4760 }
 4761 
 4762 static FnCallResult FnCallFold(EvalContext *ctx,
 4763                                ARG_UNUSED const Policy *policy,
 4764                                const FnCall *fp, const Rlist *finalargs)
 4765 {
 4766     const char *sort_type = finalargs->next ? RlistScalarValue(finalargs->next) : NULL;
 4767 
 4768     size_t count = 0;
 4769     double product = 1.0; // this could overflow
 4770     double sum = 0; // this could overflow
 4771     double mean = 0;
 4772     double M2 = 0;
 4773     char* min = NULL;
 4774     char* max = NULL;
 4775     bool variance_mode = strcmp(fp->name, "variance") == 0;
 4776     bool mean_mode = strcmp(fp->name, "mean") == 0;
 4777     bool max_mode = strcmp(fp->name, "max") == 0;
 4778     bool min_mode = strcmp(fp->name, "min") == 0;
 4779     bool sum_mode = strcmp(fp->name, "sum") == 0;
 4780     bool product_mode = strcmp(fp->name, "product") == 0;
 4781 
 4782     bool allocated = false;
 4783     JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs, false, &allocated);
 4784 
 4785     if (!json)
 4786     {
 4787         return FnFailure();
 4788     }
 4789 
 4790     JsonIterator iter = JsonIteratorInit(json);
 4791     const JsonElement *el;
 4792     while ((el = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true)))
 4793     {
 4794         char *value = JsonPrimitiveToString(el);
 4795 
 4796         if (value != NULL)
 4797         {
 4798             if (sort_type)
 4799             {
 4800                 if (min_mode && (min == NULL || !GenericStringItemLess(sort_type, min, value)))
 4801                 {
 4802                     free(min);
 4803                     min = xstrdup(value);
 4804                 }
 4805 
 4806                 if (max_mode && (max == NULL || GenericStringItemLess(sort_type, max, value)))
 4807                 {
 4808                     free(max);
 4809                     max = xstrdup(value);
 4810                 }
 4811             }
 4812 
 4813             count++;
 4814 
 4815             if (mean_mode || variance_mode || sum_mode || product_mode)
 4816             {
 4817                 double x;
 4818                 if (sscanf(value, "%lf", &x) != 1)
 4819                 {
 4820                     x = 0; /* treat non-numeric entries as zero */
 4821                 }
 4822 
 4823                 // Welford's algorithm
 4824                 double delta = x - mean;
 4825                 mean += delta/count;
 4826                 M2 += delta * (x - mean);
 4827                 sum += x;
 4828                 product *= x;
 4829             }
 4830 
 4831             free(value);
 4832         }
 4833     }
 4834 
 4835     JsonDestroyMaybe(json, allocated);
 4836 
 4837     if (mean_mode)
 4838     {
 4839         return count == 0 ? FnFailure() : FnReturnF("%lf", mean);
 4840     }
 4841     else if (sum_mode)
 4842     {
 4843         return FnReturnF("%lf", sum);
 4844     }
 4845     else if (product_mode)
 4846     {
 4847         return FnReturnF("%lf", product);
 4848     }
 4849     else if (variance_mode)
 4850     {
 4851         double variance = 0;
 4852 
 4853         if (count == 0)
 4854         {
 4855             return FnFailure();
 4856         }
 4857 
 4858         // if count is 1, variance is 0
 4859 
 4860         if (count > 1)
 4861         {
 4862             variance = M2/(count - 1);
 4863         }
 4864 
 4865         return FnReturnF("%lf", variance);
 4866     }
 4867     else if (max_mode)
 4868     {
 4869         return max == NULL ? FnFailure() : FnReturnNoCopy(max);
 4870     }
 4871     else if (min_mode)
 4872     {
 4873         return min == NULL ? FnFailure() : FnReturnNoCopy(min);
 4874     }
 4875 
 4876     // else, we don't know this fp->name
 4877     ProgrammingError("Unknown function call %s to FnCallFold", fp->name);
 4878     return FnFailure();
 4879 }
 4880 
 4881 /*********************************************************************/
 4882 /* This function has been removed from the function list for now     */
 4883 /*********************************************************************/
 4884 #ifdef SUPPORT_FNCALL_DATATYPE
 4885 static FnCallResult FnCallDatatype(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
 4886 {
 4887     const char* const varname = RlistScalarValue(finalargs);
 4888 
 4889     VarRef* const ref = VarRefParse(varname);
 4890     DataType type;
 4891     const void *value = EvalContextVariableGet(ctx, ref, &type);
 4892     VarRefDestroy(ref);
 4893 
 4894     Writer* const typestring = StringWriter();
 4895 
 4896     if (type == CF_DATA_TYPE_CONTAINER)
 4897     {
 4898 
 4899         const JsonElement* const jelement = value;
 4900 
 4901         if (JsonGetElementType(jelement) == JSON_ELEMENT_TYPE_CONTAINER)
 4902         {
 4903             switch (JsonGetContainerType(jelement))
 4904             {
 4905             case JSON_CONTAINER_TYPE_OBJECT:
 4906                 WriterWrite(typestring, "json_object");
 4907                 break;
 4908             case JSON_CONTAINER_TYPE_ARRAY:
 4909                 WriterWrite(typestring, "json_array");
 4910                 break;
 4911             }
 4912         }
 4913         else if (JsonGetElementType(jelement) == JSON_ELEMENT_TYPE_PRIMITIVE)
 4914         {
 4915             switch (JsonGetPrimitiveType(jelement))
 4916             {
 4917             case JSON_PRIMITIVE_TYPE_STRING:
 4918                 WriterWrite(typestring, "json_string");
 4919                 break;
 4920             case JSON_PRIMITIVE_TYPE_INTEGER:
 4921                 WriterWrite(typestring, "json_integer");
 4922                 break;
 4923             case JSON_PRIMITIVE_TYPE_REAL:
 4924                 WriterWrite(typestring, "json_real");
 4925                 break;
 4926             case JSON_PRIMITIVE_TYPE_BOOL:
 4927                 WriterWrite(typestring, "json_bool");
 4928                 break;
 4929             case JSON_PRIMITIVE_TYPE_NULL:
 4930                 WriterWrite(typestring, "json_null");
 4931                 break;
 4932             }
 4933         }
 4934 
 4935     }
 4936     else
 4937     {
 4938         Log(LOG_LEVEL_VERBOSE, "%s: variable '%s' is not a data container", fp->name, varname);
 4939         return FnFailure();
 4940     }
 4941 
 4942     return FnReturnNoCopy(StringWriterClose(typestring));
 4943 }
 4944 #endif /* unused code */
 4945 /*********************************************************************/
 4946 
 4947 static FnCallResult FnCallNth(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
 4948 {
 4949     const char* const key = RlistScalarValue(finalargs->next);
 4950 
 4951     const char *name_str = RlistScalarValueSafe(finalargs);
 4952 
 4953     // try to load directly
 4954     bool allocated = false;
 4955     JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs, false, &allocated);
 4956 
 4957     // we failed to produce a valid JsonElement, so give up
 4958     if (json == NULL)
 4959     {
 4960         return FnFailure();
 4961     }
 4962     else if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER)
 4963     {
 4964         Log(LOG_LEVEL_VERBOSE, "Function '%s', argument '%s' was not a data container or list",
 4965             fp->name, name_str);
 4966         JsonDestroyMaybe(json, allocated);
 4967         return FnFailure();
 4968     }
 4969 
 4970     const char *jstring = NULL;
 4971     FnCallResult result;
 4972     if (JsonGetElementType(json) == JSON_ELEMENT_TYPE_CONTAINER)
 4973     {
 4974         JsonContainerType ct = JsonGetContainerType(json);
 4975         JsonElement* jelement = NULL;
 4976 
 4977         if (JSON_CONTAINER_TYPE_OBJECT == ct)
 4978         {
 4979             jelement = JsonObjectGet(json, key);
 4980         }
 4981         else if (JSON_CONTAINER_TYPE_ARRAY == ct)
 4982         {
 4983             long index = IntFromString(key);
 4984             if (index < 0)
 4985             {
 4986                 index += JsonLength(json);
 4987             }
 4988 
 4989             if (index >= 0 && index < (long) JsonLength(json))
 4990             {
 4991                 jelement = JsonAt(json, index);
 4992             }
 4993         }
 4994         else
 4995         {
 4996             ProgrammingError("JSON Container is neither array nor object but type %d", (int) ct);
 4997         }
 4998 
 4999         if (jelement != NULL &&
 5000             JsonGetElementType(jelement) == JSON_ELEMENT_TYPE_PRIMITIVE)
 5001         {
 5002             jstring = JsonPrimitiveGetAsString(jelement);
 5003             if (jstring != NULL)
 5004             {
 5005                 result = FnReturn(jstring);
 5006             }
 5007         }
 5008     }
 5009 
 5010     JsonDestroyMaybe(json, allocated);
 5011 
 5012     if (jstring == NULL)
 5013     {
 5014         return FnFailure();
 5015     }
 5016 
 5017     return result;
 5018 }
 5019 
 5020 /*********************************************************************/
 5021 
 5022 static FnCallResult FnCallEverySomeNone(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
 5023 {
 5024     return FilterInternal(ctx,
 5025                           fp,
 5026                           RlistScalarValue(finalargs), // regex or string
 5027                           finalargs->next, // list identifier
 5028                           1,
 5029                           0,
 5030                           LONG_MAX);
 5031 }
 5032 
 5033 static FnCallResult FnCallSort(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
 5034 {
 5035     if (finalargs == NULL)
 5036     {
 5037         FatalError(ctx, "in built-in FnCall %s: missing first argument, a list name", fp->name);
 5038     }
 5039 
 5040     const char *sort_type = NULL;
 5041 
 5042     if (finalargs->next)
 5043     {
 5044         sort_type = RlistScalarValue(finalargs->next); // sort mode
 5045     }
 5046     else
 5047     {
 5048         sort_type = "lex";
 5049     }
 5050 
 5051     const char *name_str = RlistScalarValueSafe(finalargs);
 5052 
 5053     // try to load directly
 5054     bool allocated = false;
 5055     JsonElement *json = VarNameOrInlineToJson(ctx, fp, finalargs, false, &allocated);
 5056 
 5057     // we failed to produce a valid JsonElement, so give up
 5058     if (json == NULL)
 5059     {
 5060         return FnFailure();
 5061     }
 5062     else if (JsonGetElementType(json) != JSON_ELEMENT_TYPE_CONTAINER)
 5063     {
 5064         Log(LOG_LEVEL_VERBOSE, "Function '%s', argument '%s' was not a data container or list",
 5065             fp->name, name_str);
 5066         JsonDestroyMaybe(json, allocated);
 5067         return FnFailure();
 5068     }
 5069 
 5070     Rlist *sorted = NULL;
 5071     JsonIterator iter = JsonIteratorInit(json);
 5072     const JsonElement *e;
 5073     while ((e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true)))
 5074     {
 5075         RlistAppendScalar(&sorted, JsonPrimitiveGetAsString(e));
 5076     }
 5077     JsonDestroyMaybe(json, allocated);
 5078 
 5079     if (strcmp(sort_type, "int") == 0)
 5080     {
 5081         sorted = IntSortRListNames(sorted);
 5082     }
 5083     else if (strcmp(sort_type, "real") == 0)
 5084     {
 5085         sorted = RealSortRListNames(sorted);
 5086     }
 5087     else if (strcmp(sort_type, "IP") == 0 || strcmp(sort_type, "ip") == 0)
 5088     {
 5089         sorted = IPSortRListNames(sorted);
 5090     }
 5091     else if (strcmp(sort_type, "MAC") == 0 || strcmp(sort_type, "mac") == 0)
 5092     {