cfengine  3.15.4
About: CFEngine is a configuration management system for configuring and maintaining Unix-like computers (using an own high level policy language). Community version.
  Fossies Dox: cfengine-3.15.4.tar.gz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

evalfunction.c
Go to the documentation of this 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 
148 {
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 
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  {
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 
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  {
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. */
277  "Skipping scalar '%s' because 'allow_scalars' is false",
278  data);
279  }
280  }
281  default:
282  *allocated = true;
283 
284  {
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:
324  {
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  {
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  {
371  }
372  }
373  JsonObjectAppendArray(holder, last_key, array);
374  }
375  break;
376 
377  default:
378  break;
379  }
380  }
381  }
382 
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);
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  {
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 
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);
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  }
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 
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 
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 /
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 
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 
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);
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 
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);
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  {
1288  {
1289  FatalError(ctx, "in function '%s', '%s'", fp->name, SyntaxTypeMatchToString(err));
1290  }
1291  }
1292 
1293  Rlist *matches = NULL;
1294 
1295  {
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);
1315  }
1316 
1317  if (check_only && count >= 1)
1318  {
1319  return FnReturnContext(true);
1320  }
1321 
1322  {
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);
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;
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  {
1455  {
1456  FatalError(ctx, "In function '%s', %s", fp->name, SyntaxTypeMatchToString(err));
1457  }
1458  }
1459 
1460  Rlist *matches = NULL;
1461 
1462  {
1464  JsonElement *global_matches = VariablesMatching(ctx, fp, iter, finalargs, fulldata);
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;
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 
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  {
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  {
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  {
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  {
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 
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 
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 
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 
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;
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 
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 
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 */
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) */
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 
2526  {
2527  return FnFailure();
2528  }
2529 
2530  if (maxbytes < 0 || maxbytes > CF_BUFSIZE - 1)
2531  {
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 
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  {
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 
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 
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  {
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 
2727  {
2728  JsonDestroyMaybe(json, allocated);
2729  return (FnCallResult) { FNCALL_SUCCESS, { keys, RVAL_TYPE_LIST } };
2730  }
2731 
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 
2757 {
2759  {
2760  JsonIterator iter = JsonIteratorInit(container);
2761  const JsonElement *el;
2762  while ((el = JsonIteratorNextValue(&iter)))
2763  {
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);
2847  if (!e)
2848  {
2849  return FnReturn("");
2850  }
2851 
2852  Buffer *result = BufferNew();
2854 
2855  while ((e = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true)))
2856  {
2857  BufferAppendString(result, delimiter);
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  }
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 
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  {
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  {
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  {
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 
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();
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  {
3281  CF_DATA_TYPE_STRING, "source=function,function=maparray",
3282  jsonmode);
3283 
3284  switch (JsonGetElementType(e))
3285  {
3287  BufferClear(expbuf);
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);
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));
3318 
3319  break;
3320 
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  {
3333  CF_DATA_TYPE_STRING, "source=function,function=maparray",
3334  jsonmode);
3335  }
3336 
3337  BufferClear(expbuf);
3338 
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);
3356  if (havekey)
3357  {
3359  }
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  {
3376  }
3378  position++;
3379  }
3380  }
3381  break;
3382 
3383  default:
3384  break;
3385  }
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 
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  }
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);
3488  BufferDestroy(expbuf);
3489  JsonDestroyMaybe(json, allocated);
3490  RlistDestroy(expargs);
3491  return FnFailure();
3492  }
3493 
3494  RlistAppendScalar(&newlist, BufferData(expbuf));
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
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 
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  }
3672 
3674  while ((cls = ClassTableIteratorNext(it)))
3675  {
3676  char *key = ClassRefToString(cls->ns, cls->name);
3677  JsonObjectAppendBool(classes, key, true);
3678  free(key);
3679  }
3681  }
3682 
3683  {
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  }
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 
3731  ARG_UNUSED const Policy *policy,
3732  ARG_UNUSED const FnCall *fp,
3733  ARG_UNUSED const Rlist *args)
3734 {
3737 }
3738 
3740  ARG_UNUSED const Policy *policy,
3741  ARG_UNUSED const FnCall *fp,
3742  ARG_UNUSED const Rlist *args)
3743 {
3745 
3746  if (state == NULL ||
3748  JsonLength(state) < 1)
3749  {
3750  if (state != NULL)
3751  {
3752  JsonDestroy(state);
3753  }
3754 
3755  return FnFailure();
3756  }
3757  else
3758  {
3760  }
3761 }
3762 
3763 
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  {
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  {
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  {
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  {
3835  "selectservers: invalid number of bytes %zd to read, defaulting to %d",
3836  maxbytes, CF_BUFSIZE - 1);
3837  maxbytes = CF_BUFSIZE - 1;
3838  }
3839 
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  {
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);
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  {
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);
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  }
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 
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  {
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  {
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  {
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);
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  {
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 
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  {
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 
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  }
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  }
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  {
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
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  }
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  {
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 
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  }
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 
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 
4902  {
4903  switch (JsonGetContainerType(jelement))
4904  {
4906  WriterWrite(typestring, "json_object");
4907  break;
4909  WriterWrite(typestring, "json_array");
4910  break;
4911  }
4912  }
4913  else if (JsonGetElementType(jelement) == JSON_ELEMENT_TYPE_PRIMITIVE)
4914  {
4915  switch (JsonGetPrimitiveType(jelement))
4916  {
4918  WriterWrite(typestring, "json_string");
4919  break;
4921  WriterWrite(typestring, "json_integer");
4922  break;
4924  WriterWrite(typestring, "json_real");
4925  break;
4927  WriterWrite(typestring, "json_bool");
4928  break;
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  }
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;
4973  {
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 &&
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  }
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  {
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  {
5093  sorted = MACSortRListNames(sorted);
5094  }
5095  else // "lex"
5096  {
5097  sorted = AlphaSortRListNames(sorted);
5098  }
5099 
5100  return (FnCallResult) { FNCALL_SUCCESS, (Rval) { sorted, RVAL_TYPE_LIST } };
5101 }
5102 
5103 /*********************************************************************/
5104 
5105 static FnCallResult FnCallFormat(EvalContext *ctx, ARG_UNUSED const Policy *policy, const FnCall *fp, const Rlist *finalargs)
5106 {
5107  const char *const id = "built-in FnCall format-arg";
5108 
5109  /* We need to check all the arguments, ArgTemplate does not check varadic functions */
5110  for (const Rlist *arg = finalargs; arg; arg = arg->next)
5111  {
5112  SyntaxTypeMatch err = CheckConstraintTypeMatch(id, arg->val, CF_DATA_TYPE_STRING, "", 1);
5114  {
5115  FatalError(ctx, "in %s: %s", id, SyntaxTypeMatchToString(err));
5116  }
5117  }
5118 
5119  if (finalargs == NULL)
5120  {
5121  return FnFailure();
5122  }
5123 
5124  char *format = RlistScalarValue(finalargs);
5125 
5126  if (format == NULL)
5127  {
5128  return FnFailure();
5129  }
5130 
5131  const Rlist *rp = finalargs->next;
5132 
5133  char *check = strchr(format, '%');
5134  char check_buffer[CF_BUFSIZE];
5135  Buffer *buf = BufferNew();
5136 
5137  if (check != NULL)
5138  {
5139  BufferAppend(buf, format, check - format);
5140  Seq *s;
5141 
5142  while (check != NULL &&
5143  (s = StringMatchCaptures("^(%%|%[^diouxXeEfFgGaAcsCSpnm%]*?[diouxXeEfFgGaAcsCSpnm])([^%]*)(.*)$", check, false)))
5144  {
5145  {
5146  if (SeqLength(s) >= 2)
5147  {
5148  const char *format_piece = BufferData(SeqAt(s, 1));
5149  bool percent = StringEqualN(format_piece, "%%", 2);
5150  char *data = NULL;
5151 
5152  if (percent)
5153  {
5154  // "%%" in format string
5155  }
5156  else if (rp != NULL)
5157  {
5158  data = RlistScalarValue(rp);
5159  rp = rp->next;
5160  }
5161  else // not %% and no data
5162  {
5163  Log(LOG_LEVEL_ERR, "format() didn't have enough parameters");
5164  BufferDestroy(buf);
5165  SeqDestroy(s);
5166  return FnFailure();
5167  }
5168 
5169  char piece[CF_BUFSIZE];
5170  memset(piece, 0, CF_BUFSIZE);
5171 
5172  const char bad_modifiers[] = "hLqjzt";
5173  const size_t length = strlen(bad_modifiers);
5174  for (int b = 0; b < length; b++)
5175  {
5176  if (strchr(format_piece, bad_modifiers[b]) != NULL)
5177  {
5178  Log(LOG_LEVEL_ERR, "format() does not allow modifier character '%c' in format specifier '%s'.",
5179  bad_modifiers[b],
5180  format_piece);
5181  BufferDestroy(buf);
5182  SeqDestroy(s);
5183  return FnFailure();
5184  }
5185  }
5186 
5187  if (strrchr(format_piece, 'd') != NULL || strrchr(format_piece, 'o') != NULL || strrchr(format_piece, 'x') != NULL)
5188  {
5189  long x = 0;
5190  sscanf(data, "%ld", &x);
5191  snprintf(piece, CF_BUFSIZE, format_piece, x);
5192  BufferAppend(buf, piece, strlen(piece));
5193  }
5194  else if (percent)
5195  {
5196  // "%%" -> "%"
5197  BufferAppend(buf, "%", 1);
5198  }
5199  else if (strrchr(format_piece, 'f') != NULL)
5200  {
5201  double x = 0;
5202  sscanf(data, "%lf", &x);
5203  snprintf(piece, CF_BUFSIZE, format_piece, x);
5204  BufferAppend(buf, piece, strlen(piece));
5205  }
5206  else if (strrchr(format_piece, 's') != NULL)
5207  {
5208  BufferAppendF(buf, format_piece, data);
5209  }
5210  else if (strrchr(format_piece, 'S') != NULL)
5211  {
5212  char *found_format_spec = NULL;
5213  char format_rewrite[CF_BUFSIZE];
5214 
5215  strlcpy(format_rewrite, format_piece, CF_BUFSIZE);
5216  found_format_spec = strrchr(format_rewrite, 'S');
5217 
5218  if (found_format_spec != NULL)
5219  {
5220  *found_format_spec = 's';
5221  }
5222  else
5223  {
5224  ProgrammingError("Couldn't find the expected S format spec in %s", format_piece);
5225  }
5226 
5227  const char* const varname = data;
5228  VarRef *ref = VarRefParse(varname);
5229  DataType type;
5230  const void *value = EvalContextVariableGet(ctx, ref, &type);
5231  VarRefDestroy(ref);
5232 
5233  if (type == CF_DATA_TYPE_CONTAINER)
5234  {
5235  Writer *w = StringWriter();
5236  JsonWriteCompact(w, value);
5237  BufferAppendF(buf, format_rewrite, StringWriterData(w));
5238  WriterClose(w);
5239  }
5240  else // it might be a list reference
5241  {
5242  DataType data_type;
5243  const Rlist *list = GetListReferenceArgument(ctx, fp, varname, &data_type);
5244  if (data_type == CF_DATA_TYPE_STRING_LIST)
5245  {
5246  Writer *w = StringWriter();
5247  WriterWrite(w, "{ ");
5248  for (const Rlist *rp = list; rp; rp = rp->next)
5249  {
5250  char *escaped = EscapeCharCopy(RlistScalarValue(rp), '"', '\\');
5251  WriterWriteF(w, "\"%s\"", escaped);
5252  free(escaped);
5253 
5254  if (rp != NULL && rp->next != NULL)
5255  {
5256  WriterWrite(w, ", ");
5257  }
5258  }
5259  WriterWrite(w, " }");
5260 
5261  BufferAppendF(buf, format_rewrite, StringWriterData(w));
5262  WriterClose(w);
5263  }
5264  else // whatever this is, it's not a list reference or a data container
5265  {
5266  Log(LOG_LEVEL_VERBOSE, "format() with %%S specifier needs a data container or a list instead of '%s'.",
5267  varname);
5268  BufferDestroy(buf);
5269  SeqDestroy(s);
5270  return FnFailure();
5271  }
5272  }
5273  }
5274  else
5275  {
5276  char error[] = "(unhandled format)";
5277  BufferAppend(buf, error, strlen(error));
5278  }
5279  }
5280  else
5281  {
5282  check = NULL;
5283  }
5284  }
5285 
5286  {
5287  if (SeqLength(s) >= 3)
5288  {
5289  BufferAppend(buf, BufferData(SeqAt(s, 2)), BufferSize(SeqAt(s, 2)));
5290  }
5291  else
5292  {
5293  check = NULL;
5294  }
5295  }
5296 
5297  {
5298  if (SeqLength(s) >= 4)
5299  {
5300  strlcpy(check_buffer, BufferData(SeqAt(s, 3)), CF_BUFSIZE);
5301  check = check_buffer;
5302  }
5303  else
5304  {
5305  check = NULL;
5306  }
5307  }
5308 
5309  SeqDestroy(s);
5310  }
5311  }
5312  else
5313  {
5314  BufferAppend(buf, format, strlen(format));
5315  }
5316 
5317  return FnReturnBuffer(buf);
5318 }
5319 
5320 /*********************************************************************/
5321 
5323  const FnCall *fp, const Rlist *finalargs)
5324 {
5325  const char *range = RlistScalarValue(finalargs);
5326  const Rlist *ifaces = finalargs->next;
5327 
5328  if (!FuzzyMatchParse(range))
5329  {
5331  "%s(%s): argument is not a valid address range",
5332  fp->name, range);
5333  return FnFailure();
5334  }
5335 
5336  for (const Item *ip = EvalContextGetIpAddresses(ctx);
5337  ip != NULL;
5338  ip = ip->next)
5339  {
5340  if (FuzzySetMatch(range, ip->name) == 0)
5341  {
5342  /*
5343  * MODE1: iprange(range)
5344  * Match range on the address of any interface.
5345  */
5346  if (ifaces == NULL)
5347  {
5348  Log(LOG_LEVEL_DEBUG, "%s(%s): Match on IP '%s'",
5349  fp->name, range, ip->name);
5350  return FnReturnContext(true);
5351  }
5352  /*
5353  * MODE2: iprange(range, args...)
5354  * Match range only on the addresses of args interfaces.
5355  */
5356  else
5357  {
5358  for (const Rlist *i = ifaces; i != NULL; i = i->next)
5359  {
5360  char *iface = xstrdup(RlistScalarValue(i));
5361  CanonifyNameInPlace(iface);
5362 
5363  const char *ip_iface = ip->classes;
5364 
5365  if (ip_iface != NULL &&
5366  strcmp(iface, ip_iface) == 0)
5367  {
5369  "%s(%s): Match on IP '%s' interface '%s'",
5370  fp->name, range, ip->name, ip->classes);
5371 
5372  free(iface);
5373  return FnReturnContext(true);
5374  }
5375  free(iface);
5376  }
5377  }
5378  }
5379  }
5380 
5381  Log(LOG_LEVEL_DEBUG, "%s(%s): no match", fp->name, range);
5382  return FnReturnContext(false);
5383 }
5384 
5386  ARG_UNUSED const Policy *policy,
5387  const FnCall *fp, const Rlist *finalargs)
5388 {
5389  const char *range = RlistScalarValue(finalargs);
5390  const Rlist *ips = finalargs->next;
5391 
5392  if (!FuzzyMatchParse(range))
5393  {
5395  "%s(%s): argument is not a valid address range",
5396  fp->name, range);
5397  return FnFailure();
5398  }
5399 
5400  for (const Rlist *ip = ips; ip != NULL; ip = ip->next)
5401  {
5402  const char *ip_s = RlistScalarValue(ip);
5403 
5404  if (FuzzySetMatch(range, ip_s) == 0)
5405  {
5406  Log(LOG_LEVEL_DEBUG, "%s(%s): Match on IP '%s'",
5407  fp->name, range, ip_s);
5408  return FnReturnContext(true);
5409  }
5410  }
5411 
5412  Log(LOG_LEVEL_DEBUG, "%s(%s): no match", fp->name, range);
5413  return FnReturnContext(false);
5414 }
5415 /*********************************************************************/
5416 
5417 static FnCallResult FnCallHostRange(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
5418 {
5419  char *prefix = RlistScalarValue(finalargs);
5420  char *range = RlistScalarValue(finalargs->next);
5421 
5422  if (!FuzzyHostParse(range))
5423  {
5424  return FnFailure();
5425  }
5426 
5427  return FnReturnContext(FuzzyHostMatch(prefix, range, VUQNAME) == 0);
5428 }
5429 
5430 /*********************************************************************/
5431 
5433 {
5434  setnetgrent(RlistScalarValue(finalargs));
5435 
5436  bool found = false;
5437  char *host, *user, *domain;
5438  while (getnetgrent(&host, &user, &domain))
5439  {
5440  if (host == NULL)
5441  {
5442  Log(LOG_LEVEL_VERBOSE, "Matched '%s' in netgroup '%s'",
5443  VFQNAME, RlistScalarValue(finalargs));
5444  found = true;
5445  break;
5446  }
5447 
5448  if (strcmp(host, VFQNAME) == 0 ||
5449  strcmp(host, VUQNAME) == 0)
5450  {
5451  Log(LOG_LEVEL_VERBOSE, "Matched '%s' in netgroup '%s'",
5452  host, RlistScalarValue(finalargs));
5453  found = true;
5454  break;
5455  }
5456  }
5457 
5458  endnetgrent();
5459 
5460  return FnReturnContext(found);
5461 }
5462 
5463 /*********************************************************************/
5464 
5465 static FnCallResult FnCallIsVariable(EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
5466 {
5467  const char *lval = RlistScalarValue(finalargs);
5468  bool found = false;
5469 
5470  if (lval)
5471  {
5472  VarRef *ref = VarRefParse(lval);
5473  DataType value_type;
5474  EvalContextVariableGet(ctx, ref, &value_type);
5475  if (value_type != CF_DATA_TYPE_NONE)
5476  {
5477  found = true;
5478  }
5479  VarRefDestroy(ref);
5480  }
5481 
5482  return FnReturnContext(found);
5483 }
5484 
5485 /*********************************************************************/
5486 
5487 static FnCallResult FnCallStrCmp(ARG_UNUSED EvalContext *ctx, ARG_UNUSED const Policy *policy, ARG_UNUSED const FnCall *fp, const Rlist *finalargs)
5488 {
5489  return FnReturnContext(strcmp(RlistScalarValue(finalargs), RlistScalarValue(finalargs->next)) == 0);
5490 }
5491 
5492 /*********************************************************************/
5493 
5494 static