1 /*
2   Copyright 2021 Northern.tech AS
3 
4   This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5 
6   This program is free software; you can redistribute it and/or modify it
7   under the terms of the GNU General Public License as published by the
8   Free Software Foundation; version 3.
9 
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14 
15   You should have received a copy of the GNU General Public License
16   along with this program; if not, write to the Free Software
17   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
18 
19   To the extent this program is licensed as part of the Enterprise
20   versions of CFEngine, the applicable Commercial Open Source License
21   (COSL) may apply to this file if you as a licensee so wish it. See
22   included file COSL.txt.
23 */
24 
25 
26 #include <generic_agent.h>
27 
28 #include <bootstrap.h>
29 #include <policy_server.h>
30 #include <sysinfo.h>
31 #include <known_dirs.h>
32 #include <eval_context.h>
33 #include <policy.h>
34 #include <promises.h>
35 #include <files_lib.h>
36 #include <files_names.h>
37 #include <files_interfaces.h>
38 #include <hash.h>
39 #include <parser.h>
40 #include <dbm_api.h>
41 #include <crypto.h>
42 #include <vars.h>
43 #include <syntax.h>
44 #include <conversion.h>
45 #include <expand.h>
46 #include <locks.h>
47 #include <scope.h>
48 #include <cleanup.h>
49 #include <unix.h>
50 #include <client_code.h>
51 #include <string_lib.h>
52 #include <regex.h>      // pcre
53 #include <writer.h>
54 #include <exec_tools.h>
55 #include <list.h>
56 #include <misc_lib.h>
57 #include <fncall.h>
58 #include <rlist.h>
59 #include <syslog_client.h>
60 #include <audit.h>
61 #include <verify_classes.h>
62 #include <verify_vars.h>
63 #include <timeout.h>
64 #include <time_classes.h>
65 #include <constants.h>
66 #include <ornaments.h>
67 #include <cf-windows-functions.h>
68 #include <loading.h>
69 #include <signals.h>
70 #include <addr_lib.h>
71 #include <openssl/evp.h>
72 #include <libcrypto-compat.h>
73 #include <libgen.h>
74 #include <cleanup.h>
75 #include <cmdb.h>               /* LoadCMDBData() */
76 
77 #define AUGMENTS_VARIABLES_TAGS "tags"
78 #define AUGMENTS_VARIABLES_DATA "value"
79 #define AUGMENTS_CLASSES_TAGS "tags"
80 #define AUGMENTS_CLASSES_CLASS_EXPRESSIONS "class_expressions"
81 #define AUGMENTS_CLASSES_REGULAR_EXPRESSIONS "regular_expressions"
82 #define AUGMENTS_COMMENT_KEY "comment"
83 
84 static pthread_once_t pid_cleanup_once = PTHREAD_ONCE_INIT; /* GLOBAL_T */
85 
86 static char PIDFILE[CF_BUFSIZE] = ""; /* GLOBAL_C */
87 
88 /* Used for 'ident' argument to openlog() */
89 static char CF_PROGRAM_NAME[256] = "";
90 
91 static void CheckWorkingDirectories(EvalContext *ctx);
92 
93 static void GetAutotagDir(char *dirname, size_t max_size, const char *maybe_dirname);
94 static void GetPromisesValidatedFile(char *filename, size_t max_size, const GenericAgentConfig *config, const char *maybe_dirname);
95 static bool WriteReleaseIdFile(const char *filename, const char *dirname);
96 static bool GeneratePolicyReleaseIDFromGit(char *release_id_out, size_t out_size,
97                                            const char *policy_dir);
98 static bool GeneratePolicyReleaseID(char *release_id_out, size_t out_size,
99                                     const char *policy_dir);
100 static char* ReadReleaseIdFromReleaseIdFileMasterfiles(const char *maybe_dirname);
101 
102 static bool MissingInputFile(const char *input_file);
103 
104 static bool LoadAugmentsFiles(EvalContext *ctx, const char* filename);
105 
106 static void GetChangesChrootDir(char *buf, size_t buf_size);
107 static void DeleteChangesChroot();
108 static int ParseFacility(const char *name);
109 static inline const char *LogFacilityToString(int facility);
110 
111 #if !defined(__MINGW32__)
112 static void OpenLog(int facility);
113 #endif
114 
115 /*****************************************************************************/
116 
SanitizeEnvironment()117 static void SanitizeEnvironment()
118 {
119     /* ps(1) and other utilities invoked by CFEngine may be affected */
120     unsetenv("COLUMNS");
121 
122     /* Make sure subprocesses output is not localized */
123     unsetenv("LANG");
124     unsetenv("LANGUAGE");
125     unsetenv("LC_MESSAGES");
126 }
127 
128 /*****************************************************************************/
129 
ENTERPRISE_VOID_FUNC_2ARG_DEFINE_STUB(void,GenericAgentSetDefaultDigest,HashMethod *,digest,int *,digest_len)130 ENTERPRISE_VOID_FUNC_2ARG_DEFINE_STUB(void, GenericAgentSetDefaultDigest, HashMethod *, digest, int *, digest_len)
131 {
132     *digest = HASH_METHOD_MD5;
133     *digest_len = CF_MD5_LEN;
134 }
135 
MarkAsPolicyServer(EvalContext * ctx)136 void MarkAsPolicyServer(EvalContext *ctx)
137 {
138     EvalContextClassPutHard(ctx, "am_policy_hub",
139                             "source=bootstrap,deprecated,alias=policy_server");
140     Log(LOG_LEVEL_VERBOSE, "Additional class defined: am_policy_hub");
141     EvalContextClassPutHard(ctx, "policy_server",
142                             "inventory,attribute_name=CFEngine roles,source=bootstrap");
143     Log(LOG_LEVEL_VERBOSE, "Additional class defined: policy_server");
144 }
145 
SelectAndLoadPolicy(GenericAgentConfig * config,EvalContext * ctx,bool validate_policy,bool write_validated_file)146 Policy *SelectAndLoadPolicy(GenericAgentConfig *config, EvalContext *ctx, bool validate_policy, bool write_validated_file)
147 {
148     Policy *policy = NULL;
149 
150     if (GenericAgentCheckPolicy(config, validate_policy, write_validated_file))
151     {
152         policy = LoadPolicy(ctx, config);
153     }
154     else if (config->tty_interactive)
155     {
156         Log(LOG_LEVEL_ERR,
157                "Failsafe condition triggered. Interactive session detected, skipping failsafe.cf execution.");
158     }
159     else
160     {
161         Log(LOG_LEVEL_ERR, "CFEngine was not able to get confirmation of promises from cf-promises, so going to failsafe");
162         EvalContextClassPutHard(ctx, "failsafe_fallback", "report,attribute_name=Errors,source=agent");
163 
164         if (CheckAndGenerateFailsafe(GetInputDir(), "failsafe.cf"))
165         {
166             GenericAgentConfigSetInputFile(config, GetInputDir(), "failsafe.cf");
167             Log(LOG_LEVEL_ERR, "CFEngine failsafe.cf: %s %s", config->input_dir, config->input_file);
168             policy = LoadPolicy(ctx, config);
169 
170             /* Doing failsafe, set the release_id to "failsafe" and also
171              * overwrite the cfe_release_id file so that sub-agent executed as
172              * part of failsafe can just pick it up and then rewrite it with the
173              * actual value from masterfiles. */
174             free(policy->release_id);
175             policy->release_id = xstrdup("failsafe");
176 
177             char filename[PATH_MAX];
178             GetReleaseIdFile(GetInputDir(), filename, sizeof(filename));
179             FILE *release_id_stream = safe_fopen_create_perms(filename, "w",
180                                                               CF_PERMS_DEFAULT);
181             if (release_id_stream == NULL)
182             {
183                 Log(LOG_LEVEL_ERR, "Failed to open the release_id file for writing during failsafe");
184             }
185             else
186             {
187                 Writer *release_id_writer = FileWriter(release_id_stream);
188                 WriterWrite(release_id_writer, "{ releaseId: \"failsafe\" }\n");
189                 WriterClose(release_id_writer);
190             }
191         }
192     }
193     return policy;
194 }
195 
CheckContextClassmatch(EvalContext * ctx,const char * class_str)196 static bool CheckContextClassmatch(EvalContext *ctx, const char *class_str)
197 {
198     if (StringEndsWith(class_str, "::")) // Treat as class expression, not regex
199     {
200         const size_t length = strlen(class_str);
201         if (length <= 2)
202         {
203             assert(length == 2); // True because StringEndsWith
204             Log(LOG_LEVEL_ERR,
205                 "Invalid class expression in augments: '%s'",
206                 class_str);
207             return false;
208         }
209 
210         char *const tmp_class_str = xstrdup(class_str);
211         assert(strlen(tmp_class_str) == length);
212 
213         tmp_class_str[length - 2] = '\0'; // 2 = strlen("::")
214         const bool found = IsDefinedClass(ctx, tmp_class_str);
215 
216         free(tmp_class_str);
217         return found;
218     }
219 
220     ClassTableIterator *iter = EvalContextClassTableIteratorNewGlobal(ctx, NULL, true, true);
221     StringSet *global_matches = ClassesMatching(ctx, iter, class_str, NULL, true); // returns early
222 
223     const bool found = (StringSetSize(global_matches) > 0);
224 
225     StringSetDestroy(global_matches);
226     ClassTableIteratorDestroy(iter);
227 
228     return found;
229 }
230 
GetTagsFromAugmentsTags(const char * item_type,const char * key,const JsonElement * json_tags,const char * default_tag,const char * filename)231 static StringSet *GetTagsFromAugmentsTags(const char *item_type,
232                                           const char *key,
233                                           const JsonElement *json_tags,
234                                           const char *default_tag,
235                                           const char *filename)
236 {
237     StringSet *tags = NULL;
238     if (JSON_NOT_NULL(json_tags))
239     {
240         if ((JsonGetType(json_tags) != JSON_TYPE_ARRAY) ||
241             (!JsonArrayContainsOnlyPrimitives((JsonElement*) json_tags)))
242         {
243             Log(LOG_LEVEL_ERR,
244                 "Invalid tags information for %s '%s' in augments file '%s':"
245                 " must be a JSON array of strings",
246                 item_type, key, filename);
247         }
248         else
249         {
250             tags = JsonArrayToStringSet(json_tags);
251             if (tags == NULL)
252             {
253                 Log(LOG_LEVEL_ERR,
254                     "Invalid meta information %s '%s' in augments file '%s':"
255                     " must be a JSON array of strings",
256                     item_type, key, filename);
257             }
258         }
259     }
260     if (tags == NULL)
261     {
262         tags = StringSetNew();
263     }
264     StringSetAdd(tags, xstrdup(default_tag));
265 
266     return tags;
267 }
268 
CanSetVariable(const EvalContext * ctx,VarRef * var_ref)269 static inline bool CanSetVariable(const EvalContext *ctx, VarRef *var_ref)
270 {
271     assert(var_ref != NULL);
272 
273     bool null_ns = false;
274     if (var_ref->ns == NULL)
275     {
276         null_ns = true;
277         var_ref->ns = "default";
278     }
279     StringSet *tags = EvalContextVariableTags(ctx, var_ref);
280     bool can_set = ((tags == NULL) || !StringSetContains(tags, CMDB_SOURCE_TAG));
281     if (!can_set)
282     {
283         Log(LOG_LEVEL_VERBOSE,
284             "Cannot set variable %s:%s.%s from augments, already defined from host-specific data",
285             var_ref->ns, var_ref->scope, var_ref->lval);
286     }
287     if (null_ns)
288     {
289         var_ref->ns = NULL;
290     }
291 
292     return can_set;
293 }
294 
CanSetClass(const EvalContext * ctx,const char * class_spec)295 static inline bool CanSetClass(const EvalContext *ctx, const char *class_spec)
296 {
297     char *ns = NULL;
298     char *ns_delim = strchr(class_spec, ':');
299     if (ns_delim != NULL)
300     {
301         ns = xstrndup(class_spec, ns_delim - class_spec);
302         class_spec = ns_delim + 1;
303     }
304 
305     StringSet *tags = EvalContextClassTags(ctx, ns, class_spec);
306     bool can_set = ((tags == NULL) || !StringSetContains(tags, CMDB_SOURCE_TAG));
307     if (!can_set)
308     {
309         Log(LOG_LEVEL_VERBOSE,
310             "Cannot set class %s:%s from augments, already defined from host-specific data",
311             ns, class_spec);
312     }
313 
314     return can_set;
315 }
316 
GetAugmentsComment(const char * item_type,const char * identifier,const char * file_name,const JsonElement * json_object)317 static inline const char *GetAugmentsComment(const char *item_type, const char *identifier,
318                                              const char *file_name, const JsonElement *json_object)
319 {
320     assert(JsonGetType(json_object) == JSON_TYPE_OBJECT);
321 
322     JsonElement *json_comment = JsonObjectGet(json_object, AUGMENTS_COMMENT_KEY);
323     if (NULL_JSON(json_comment))
324     {
325         return NULL;
326     }
327 
328     if (JsonGetType(json_comment) != JSON_TYPE_STRING)
329     {
330         Log(LOG_LEVEL_ERR,
331             "Invalid type of the 'comment' field for the '%s' %s in augments data in '%s', must be a string",
332             identifier, item_type, file_name);
333         return NULL;
334     }
335 
336     return JsonPrimitiveGetAsString(json_comment);
337 }
338 
LoadAugmentsData(EvalContext * ctx,const char * filename,const JsonElement * augment)339 static bool LoadAugmentsData(EvalContext *ctx, const char *filename, const JsonElement* augment)
340 {
341     bool loaded = false;
342 
343     if (JsonGetElementType(augment) != JSON_ELEMENT_TYPE_CONTAINER ||
344         JsonGetContainerType(augment) != JSON_CONTAINER_TYPE_OBJECT)
345     {
346         Log(LOG_LEVEL_ERR, "Invalid augments file contents in '%s', must be a JSON object", filename);
347     }
348     else
349     {
350         loaded = true;
351         Log(LOG_LEVEL_VERBOSE, "Loaded augments file '%s', installing contents", filename);
352 
353         JsonIterator iter = JsonIteratorInit(augment);
354         const char *key;
355         while ((key = JsonIteratorNextKey(&iter)))
356         {
357             if (!(StringEqual(key, "vars") ||
358                   StringEqual(key, "classes") ||
359                   StringEqual(key, "inputs") ||
360                   StringEqual(key, "augments")))
361             {
362                 Log(LOG_LEVEL_VERBOSE, "Unknown augments key '%s' in file '%s', skipping it",
363                     key, filename);
364             }
365         }
366 
367         /* load variables (if any) */
368         JsonElement *element = JsonObjectGet(augment, "vars");
369         if (JSON_NOT_NULL(element))
370         {
371             JsonElement* vars = JsonExpandElement(ctx, element);
372 
373             if (vars == NULL ||
374                 JsonGetElementType(vars) != JSON_ELEMENT_TYPE_CONTAINER ||
375                 JsonGetContainerType(vars) != JSON_CONTAINER_TYPE_OBJECT)
376             {
377                 Log(LOG_LEVEL_ERR, "Invalid augments vars in '%s', must be a JSON object", filename);
378                 goto vars_cleanup;
379             }
380 
381             JsonIterator iter = JsonIteratorInit(vars);
382             const char *vkey;
383             while ((vkey = JsonIteratorNextKey(&iter)))
384             {
385                 VarRef *ref = VarRefParse(vkey);
386                 if (ref->ns != NULL)
387                 {
388                     if (ref->scope == NULL)
389                     {
390                         Log(LOG_LEVEL_ERR, "Invalid variable specification in augments data in '%s': '%s'"
391                             " (bundle name has to be specified if namespace is specified)", filename, vkey);
392                         VarRefDestroy(ref);
393                         continue;
394                     }
395                 }
396                 if (ref->scope == NULL)
397                 {
398                     ref->scope = xstrdup("def");
399                 }
400 
401                 JsonElement *data = JsonObjectGet(vars, vkey);
402                 if (JsonGetElementType(data) == JSON_ELEMENT_TYPE_PRIMITIVE)
403                 {
404                     char *value = JsonPrimitiveToString(data);
405                     if ((ref->ns == NULL) && (ref->scope == NULL))
406                     {
407                         Log(LOG_LEVEL_VERBOSE, "Installing augments variable '%s.%s=%s' from file '%s'",
408                             SpecialScopeToString(SPECIAL_SCOPE_DEF), vkey, value, filename);
409                         if (CanSetVariable(ctx, ref))
410                         {
411                             EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_DEF, vkey, value, CF_DATA_TYPE_STRING, "source=augments_file");
412                         }
413                     }
414                     else
415                     {
416                         Log(LOG_LEVEL_VERBOSE, "Installing augments variable '%s=%s' from file '%s'",
417                             vkey, value, filename);
418                         if (CanSetVariable(ctx, ref))
419                         {
420                             EvalContextVariablePut(ctx, ref, value, CF_DATA_TYPE_STRING, "source=augments_file");
421                         }
422                     }
423                     free(value);
424                 }
425                 else if (JsonGetElementType(data) == JSON_ELEMENT_TYPE_CONTAINER &&
426                          JsonGetContainerType(data) == JSON_CONTAINER_TYPE_ARRAY &&
427                          JsonArrayContainsOnlyPrimitives(data))
428                 {
429                     // map to slist if the data only has primitives
430                     Rlist *data_as_rlist = RlistFromContainer(data);
431                     if ((ref->ns == NULL) && (ref->scope == NULL))
432                     {
433                         Log(LOG_LEVEL_VERBOSE, "Installing augments slist variable '%s.%s' from file '%s'",
434                             SpecialScopeToString(SPECIAL_SCOPE_DEF), vkey, filename);
435                         if (CanSetVariable(ctx, ref))
436                         {
437                             EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_DEF,
438                                                           vkey, data_as_rlist,
439                                                           CF_DATA_TYPE_STRING_LIST,
440                                                           "source=augments_file");
441                         }
442                     }
443                     else
444                     {
445                         Log(LOG_LEVEL_VERBOSE, "Installing augments slist variable '%s' from file '%s'",
446                             vkey, filename);
447                         if (CanSetVariable(ctx, ref))
448                         {
449                             EvalContextVariablePut(ctx, ref, data_as_rlist, CF_DATA_TYPE_STRING_LIST,
450                                                    "source=augments_file");
451                         }
452                     }
453 
454                     RlistDestroy(data_as_rlist);
455                 }
456                 else // install as a data container
457                 {
458                     if ((ref->ns == NULL) && (ref->scope == NULL))
459                     {
460                         Log(LOG_LEVEL_VERBOSE, "Installing augments data container variable '%s.%s' from file '%s'",
461                             SpecialScopeToString(SPECIAL_SCOPE_DEF), vkey, filename);
462                         if (CanSetVariable(ctx, ref))
463                         {
464                             EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_DEF,
465                                                           vkey, data,
466                                                           CF_DATA_TYPE_CONTAINER,
467                                                           "source=augments_file");
468                         }
469                     }
470                     else
471                     {
472                         Log(LOG_LEVEL_VERBOSE, "Installing augments data container variable '%s' from file '%s'",
473                             vkey, filename);
474                         if (CanSetVariable(ctx, ref))
475                         {
476                             EvalContextVariablePut(ctx, ref, data,
477                                                    CF_DATA_TYPE_CONTAINER,
478                                                    "source=augments_file");
479                         }
480                     }
481                 }
482                 VarRefDestroy(ref);
483             }
484 
485           vars_cleanup:
486             JsonDestroy(vars);
487         }
488 
489         /* Uses the new format allowing metadata (CFE-3633) */
490         element = JsonObjectGet(augment, "variables");
491         if (JSON_NOT_NULL(element))
492         {
493             JsonElement* variables = JsonExpandElement(ctx, element);
494 
495             if (variables == NULL || JsonGetType(variables) != JSON_TYPE_OBJECT)
496             {
497                 Log(LOG_LEVEL_ERR, "Invalid augments variables in '%s', must be a JSON object", filename);
498                 goto variables_cleanup;
499             }
500 
501             JsonIterator variables_iter = JsonIteratorInit(variables);
502             const char *vkey;
503             while ((vkey = JsonIteratorNextKey(&variables_iter)))
504             {
505                 VarRef *ref = VarRefParse(vkey);
506                 if (ref->ns != NULL)
507                 {
508                     if (ref->scope == NULL)
509                     {
510                         Log(LOG_LEVEL_ERR, "Invalid variable specification in augments data in '%s': '%s'"
511                             " (bundle name has to be specified if namespace is specified)", filename, vkey);
512                         VarRefDestroy(ref);
513                         continue;
514                     }
515                 }
516                 if (ref->scope == NULL)
517                 {
518                     ref->scope = xstrdup("def");
519                 }
520 
521                 const JsonElement *const var_info = JsonObjectGet(variables, vkey);
522 
523                 const JsonElement *data;
524                 StringSet *tags;
525                 const char *comment = NULL;
526 
527                 if (JsonGetType(var_info) == JSON_TYPE_OBJECT)
528                 {
529                     data = JsonObjectGet(var_info, AUGMENTS_VARIABLES_DATA);
530 
531                     if (NULL_JSON(data))
532                     {
533                         Log(LOG_LEVEL_ERR, "Missing value for the augments variable '%s' in '%s' (value field is required)",
534                             vkey, filename);
535                         VarRefDestroy(ref);
536                         continue;
537                     }
538 
539                     const JsonElement *json_tags = JsonObjectGet(var_info, AUGMENTS_VARIABLES_TAGS);
540                     tags = GetTagsFromAugmentsTags("variable", vkey, json_tags, "source=augments_file", filename);
541                     comment = GetAugmentsComment("variable", vkey, filename, var_info);
542                 }
543                 else
544                 {
545                     // Just a bare value, like in "vars", no metadata
546                     data = var_info;
547                     tags = GetTagsFromAugmentsTags("variable", vkey, NULL, "source=augments_file", filename);
548                 }
549 
550                 assert(tags != NULL);
551                 assert(data != NULL);
552 
553                 bool installed = false;
554                 if (JsonGetElementType(data) == JSON_ELEMENT_TYPE_PRIMITIVE)
555                 {
556                     char *value = JsonPrimitiveToString(data);
557                     if ((ref->ns == NULL) && (ref->scope == NULL))
558                     {
559                         Log(LOG_LEVEL_VERBOSE, "Installing augments variable '%s.%s=%s' from file '%s'",
560                             SpecialScopeToString(SPECIAL_SCOPE_DEF), vkey, value, filename);
561                         if (CanSetVariable(ctx, ref))
562                         {
563                             installed = EvalContextVariablePutSpecialTagsSetWithComment(ctx, SPECIAL_SCOPE_DEF, vkey, value,
564                                                                                         CF_DATA_TYPE_STRING, tags, comment);
565                         }
566                     }
567                     else
568                     {
569                         Log(LOG_LEVEL_VERBOSE, "Installing augments variable '%s=%s' from file '%s'",
570                             vkey, value, filename);
571                         if (CanSetVariable(ctx, ref))
572                         {
573                             installed = EvalContextVariablePutTagsSetWithComment(ctx, ref, value, CF_DATA_TYPE_STRING,
574                                                                                  tags, comment);
575                         }
576                     }
577                     free(value);
578                 }
579                 else if (JsonGetElementType(data) == JSON_ELEMENT_TYPE_CONTAINER &&
580                          JsonGetContainerType(data) == JSON_CONTAINER_TYPE_ARRAY &&
581                          JsonArrayContainsOnlyPrimitives((JsonElement *) data))
582                 {
583                     // map to slist if the data only has primitives
584                     Rlist *data_as_rlist = RlistFromContainer(data);
585                     if ((ref->ns == NULL) && (ref->scope == NULL))
586                     {
587                         Log(LOG_LEVEL_VERBOSE, "Installing augments slist variable '%s.%s' from file '%s'",
588                             SpecialScopeToString(SPECIAL_SCOPE_DEF), vkey, filename);
589                         if (CanSetVariable(ctx, ref))
590                         {
591                             installed = EvalContextVariablePutSpecialTagsSetWithComment(ctx, SPECIAL_SCOPE_DEF,
592                                                                                         vkey, data_as_rlist,
593                                                                                         CF_DATA_TYPE_STRING_LIST,
594                                                                                         tags, comment);
595                         }
596                     }
597                     else
598                     {
599                         Log(LOG_LEVEL_VERBOSE, "Installing augments slist variable '%s' from file '%s'",
600                             vkey, filename);
601                         if (CanSetVariable(ctx, ref))
602                         {
603                             installed = EvalContextVariablePutTagsSetWithComment(ctx, ref, data_as_rlist,
604                                                                                  CF_DATA_TYPE_STRING_LIST,
605                                                                                  tags, comment);
606                         }
607                     }
608 
609                     RlistDestroy(data_as_rlist);
610                 }
611                 else // install as a data container
612                 {
613                     if ((ref->ns == NULL) && (ref->scope == NULL))
614                     {
615                         Log(LOG_LEVEL_VERBOSE, "Installing augments data container variable '%s.%s' from file '%s'",
616                             SpecialScopeToString(SPECIAL_SCOPE_DEF), vkey, filename);
617                         if (CanSetVariable(ctx, ref))
618                         {
619                             installed = EvalContextVariablePutSpecialTagsSetWithComment(ctx, SPECIAL_SCOPE_DEF,
620                                                                                         vkey, data,
621                                                                                         CF_DATA_TYPE_CONTAINER,
622                                                                                         tags, comment);
623                         }
624                     }
625                     else
626                     {
627                         Log(LOG_LEVEL_VERBOSE, "Installing augments data container variable '%s' from file '%s'",
628                             vkey, filename);
629                         if (CanSetVariable(ctx, ref))
630                         {
631                             installed = EvalContextVariablePutTagsSetWithComment(ctx, ref, data,
632                                                                                  CF_DATA_TYPE_CONTAINER,
633                                                                                  tags, comment);
634                         }
635                     }
636                 }
637                 VarRefDestroy(ref);
638                 if (!installed)
639                 {
640                     /* EvalContextVariablePutTagsSetWithComment() and
641                      * EvalContextVariablePutSpecialTagsSetWithComment() take
642                      * over tags in case of success. Otherwise we have to
643                      * destroy the set. */
644                     StringSetDestroy(tags);
645                 }
646             }
647 
648           variables_cleanup:
649             JsonDestroy(variables);
650         }
651 
652         /* load classes (if any) */
653         element = JsonObjectGet(augment, "classes");
654         if (JSON_NOT_NULL(element))
655         {
656             JsonElement* classes = JsonExpandElement(ctx, element);
657 
658             if (JsonGetElementType(classes) != JSON_ELEMENT_TYPE_CONTAINER ||
659                 JsonGetContainerType(classes) != JSON_CONTAINER_TYPE_OBJECT)
660             {
661                 Log(LOG_LEVEL_ERR, "Invalid augments classes in '%s', must be a JSON object", filename);
662                 goto classes_cleanup;
663             }
664 
665             const char default_tags[] = "source=augments_file";
666             JsonIterator iter = JsonIteratorInit(classes);
667             const char *ckey;
668             while ((ckey = JsonIteratorNextKey(&iter)))
669             {
670                 JsonElement *data = JsonObjectGet(classes, ckey);
671                 if (JsonGetElementType(data) == JSON_ELEMENT_TYPE_PRIMITIVE)
672                 {
673                     char *check = JsonPrimitiveToString(data);
674                     // check if class is true
675                     if (CheckContextClassmatch(ctx, check))
676                     {
677                         Log(LOG_LEVEL_VERBOSE, "Installing augments class '%s' (checked '%s') from file '%s'",
678                             ckey, check, filename);
679                         if (CanSetClass(ctx, ckey))
680                         {
681                             EvalContextClassPutSoft(ctx, ckey, CONTEXT_SCOPE_NAMESPACE, default_tags);
682                         }
683                     }
684                     free(check);
685                 }
686                 else if (JsonGetElementType(data) == JSON_ELEMENT_TYPE_CONTAINER &&
687                          JsonGetContainerType(data) == JSON_CONTAINER_TYPE_ARRAY &&
688                          JsonArrayContainsOnlyPrimitives(data))
689                 {
690                     // check if each class is true
691                     JsonIterator data_iter = JsonIteratorInit(data);
692                     const JsonElement *el;
693                     while ((el = JsonIteratorNextValueByType(&data_iter, JSON_ELEMENT_TYPE_PRIMITIVE, true)))
694                     {
695                         char *check = JsonPrimitiveToString(el);
696                         if (CheckContextClassmatch(ctx, check))
697                         {
698                             Log(LOG_LEVEL_VERBOSE, "Installing augments class '%s' (checked array entry '%s') from file '%s'",
699                                 ckey, check, filename);
700                             if (CanSetClass(ctx, ckey))
701                             {
702                                 EvalContextClassPutSoft(ctx, ckey, CONTEXT_SCOPE_NAMESPACE, default_tags);
703                             }
704                             free(check);
705                             break;
706                         }
707 
708                         free(check);
709                     }
710                 }
711                 else if (JsonGetType(data) == JSON_TYPE_OBJECT)
712                 {
713                     const JsonElement *class_exprs = JsonObjectGet(data, AUGMENTS_CLASSES_CLASS_EXPRESSIONS);
714                     const JsonElement *reg_exprs = JsonObjectGet(data, AUGMENTS_CLASSES_REGULAR_EXPRESSIONS);
715                     const JsonElement *json_tags = JsonObjectGet(data, AUGMENTS_CLASSES_TAGS);
716 
717                     if ((JSON_NOT_NULL(class_exprs) && JSON_NOT_NULL(reg_exprs)) ||
718                         (NULL_JSON(class_exprs) && NULL_JSON(reg_exprs)))
719                     {
720                         Log(LOG_LEVEL_ERR, "Invalid augments class data for class '%s' in '%s':"
721                             " either \"class_expressions\" or \"regular_expressions\" need to be specified",
722                             ckey, filename);
723                         continue;
724                     }
725 
726                     StringSet *tags = GetTagsFromAugmentsTags("class", ckey, json_tags,
727                                                               "source=augments_file", filename);
728                     const char *comment = GetAugmentsComment("class", ckey, filename, data);
729                     bool installed = false;
730                     JsonIterator exprs_iter = JsonIteratorInit(class_exprs ? class_exprs : reg_exprs);
731                     const JsonElement *el;
732                     while ((el = JsonIteratorNextValueByType(&exprs_iter, JSON_ELEMENT_TYPE_PRIMITIVE, true)))
733                     {
734                         char *check = JsonPrimitiveToString(el);
735                         if (CheckContextClassmatch(ctx, check))
736                         {
737                             Log(LOG_LEVEL_VERBOSE, "Installing augments class '%s' (checked array entry '%s') from file '%s'",
738                                 ckey, check, filename);
739                             if (CanSetClass(ctx, ckey))
740                             {
741                                 installed = EvalContextClassPutSoftTagsSetWithComment(ctx, ckey, CONTEXT_SCOPE_NAMESPACE,
742                                                                                       tags, comment);
743                             }
744                             free(check);
745                             break;
746                         }
747 
748                         free(check);
749                     }
750                     if (!installed)
751                     {
752                         /* EvalContextClassPutSoftTagsSetWithComment() takes over tags in
753                          * case of success. Otherwise we have to destroy the set. */
754                         StringSetDestroy(tags);
755                     }
756                 }
757                 else
758                 {
759                     Log(LOG_LEVEL_ERR, "Invalid augments class data for class '%s' in '%s'",
760                         ckey, filename);
761                 }
762             }
763 
764           classes_cleanup:
765             JsonDestroy(classes);
766         }
767 
768         /* load inputs (if any) */
769         element = JsonObjectGet(augment, "inputs");
770         if (JSON_NOT_NULL(element))
771         {
772             JsonElement* inputs = JsonExpandElement(ctx, element);
773 
774             if (JsonGetElementType(inputs) == JSON_ELEMENT_TYPE_CONTAINER &&
775                 JsonGetContainerType(inputs) == JSON_CONTAINER_TYPE_ARRAY &&
776                 JsonArrayContainsOnlyPrimitives(inputs))
777             {
778                 Log(LOG_LEVEL_VERBOSE, "Installing augments def.augments_inputs from file '%s'",
779                     filename);
780                 Rlist *rlist = RlistFromContainer(inputs);
781                 EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_DEF,
782                                               "augments_inputs", rlist,
783                                               CF_DATA_TYPE_STRING_LIST,
784                                               "source=augments_file");
785                 RlistDestroy(rlist);
786             }
787             else
788             {
789                 Log(LOG_LEVEL_ERR, "Trying to augment inputs in '%s' but the value was not a list of strings",
790                     filename);
791             }
792 
793             JsonDestroy(inputs);
794         }
795 
796         /* load further def.json files (if any) */
797         element = JsonObjectGet(augment, "augments");
798         if (JSON_NOT_NULL(element))
799         {
800             JsonElement* further_augments = element;
801             assert(further_augments != NULL);
802 
803             if (JsonGetElementType(further_augments) == JSON_ELEMENT_TYPE_CONTAINER &&
804                 JsonGetContainerType(further_augments) == JSON_CONTAINER_TYPE_ARRAY &&
805                 JsonArrayContainsOnlyPrimitives(further_augments))
806             {
807                 JsonIterator iter = JsonIteratorInit(further_augments);
808                 const JsonElement *el;
809                 while ((el = JsonIteratorNextValueByType(&iter, JSON_ELEMENT_TYPE_PRIMITIVE, true)) != NULL)
810                 {
811                     char *nested_filename = JsonPrimitiveToString(el);
812                     bool further_loaded = LoadAugmentsFiles(ctx, nested_filename);
813                     if (further_loaded)
814                     {
815                         Log(LOG_LEVEL_VERBOSE, "Completed augmenting from file '%s'", nested_filename);
816                     }
817                     else
818                     {
819                         Log(LOG_LEVEL_ERR, "Could not load requested further augments from file '%s'", nested_filename);
820                     }
821                     free(nested_filename);
822                 }
823             }
824             else
825             {
826                 Log(LOG_LEVEL_ERR, "Trying to augment inputs in '%s' but the value was not a list of strings",
827                     filename);
828             }
829         }
830     }
831 
832     return loaded;
833 }
834 
LoadAugmentsFiles(EvalContext * ctx,const char * unexpanded_filename)835 static bool LoadAugmentsFiles(EvalContext *ctx, const char *unexpanded_filename)
836 {
837     bool loaded = false;
838 
839     char *filename = ExpandScalar(ctx, NULL, "this", unexpanded_filename, NULL);
840 
841     if (strstr(filename, "/.json"))
842     {
843         Log(LOG_LEVEL_DEBUG,
844             "Skipping augments file '%s' because it failed to expand the base filename, resulting in '%s'",
845             unexpanded_filename, filename);
846     }
847     else
848     {
849         Log(LOG_LEVEL_DEBUG, "Searching for augments file '%s' (original '%s')",
850             filename, unexpanded_filename);
851         if (FileCanOpen(filename, "r"))
852         {
853             // 5 MB should be enough for most reasonable def.json data
854             JsonElement* augment = ReadJsonFile(filename, LOG_LEVEL_ERR, 5 * 1024 * 1024);
855             if (augment != NULL)
856             {
857                 loaded = LoadAugmentsData(ctx, filename, augment);
858                 JsonDestroy(augment);
859             }
860         }
861         else
862         {
863             Log(LOG_LEVEL_VERBOSE, "could not load JSON augments from '%s'", filename);
864         }
865     }
866 
867     free(filename);
868     return loaded;
869 }
870 
IsFile(const char * const filename)871 static bool IsFile(const char *const filename)
872 {
873     struct stat buffer;
874     if (stat(filename, &buffer) != 0)
875     {
876         return false;
877     }
878     if (S_ISREG(buffer.st_mode) != 0)
879     {
880         return true;
881     }
882     return false;
883 }
884 
LoadAugments(EvalContext * ctx,GenericAgentConfig * config)885 void LoadAugments(EvalContext *ctx, GenericAgentConfig *config)
886 {
887     assert(config != NULL);
888 
889     char* def_json = NULL;
890     // --ignore-preferred-augments command line option:
891     if (config->ignore_preferred_augments)
892     {
893         EvalContextClassPutHard(ctx, "ignore_preferred_augments", "source=command_line_option");
894         // def_json is NULL so it will be assigned below
895     }
896     else
897     {
898         def_json = StringFormat("%s%c%s", config->input_dir, FILE_SEPARATOR, "def_preferred.json");
899         if (!IsFile(def_json))
900         {
901             // def_preferred.json does not exist or we cannot read it
902             FREE_AND_NULL(def_json);
903         }
904     }
905 
906     if (def_json == NULL)
907     {
908         // No def_preferred.json, either because the feature is disabled
909         // or we could not read the file.
910         // Fall back to old / default behavior, using def.json:
911         def_json = StringFormat("%s%c%s", config->input_dir, FILE_SEPARATOR, "def.json");
912     }
913     Log(LOG_LEVEL_VERBOSE, "Loading JSON augments from '%s' (input dir '%s', input file '%s'", def_json, config->input_dir, config->input_file);
914     LoadAugmentsFiles(ctx, def_json);
915     free(def_json);
916 }
917 
AddPolicyEntryVariables(EvalContext * ctx,const GenericAgentConfig * config)918 static void AddPolicyEntryVariables (EvalContext *ctx, const GenericAgentConfig *config)
919 {
920     char *abs_input_path = GetAbsolutePath(config->input_file);
921     /* both dirname() and basename() may actually modify the string they are given (see man:basename(3)) */
922     char *dirname_path = xstrdup(abs_input_path);
923     char *basename_path = xstrdup(abs_input_path);
924     EvalContextSetEntryPoint(ctx, abs_input_path);
925     EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS,
926                                   "policy_entry_filename",
927                                   abs_input_path,
928                                   CF_DATA_TYPE_STRING, "source=agent");
929     EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS,
930                                   "policy_entry_dirname",
931                                   dirname(dirname_path),
932                                   CF_DATA_TYPE_STRING, "source=agent");
933     EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS,
934                                   "policy_entry_basename",
935                                   basename(basename_path),
936                                   CF_DATA_TYPE_STRING, "source=agent");
937     free(abs_input_path);
938     free(dirname_path);
939     free(basename_path);
940 }
941 
GenericAgentDiscoverContext(EvalContext * ctx,GenericAgentConfig * config,const char * program_name)942 void GenericAgentDiscoverContext(EvalContext *ctx, GenericAgentConfig *config,
943                                  const char *program_name)
944 {
945     strcpy(VPREFIX, "");
946     if (program_name != NULL)
947     {
948         strncpy(CF_PROGRAM_NAME, program_name, sizeof(CF_PROGRAM_NAME));
949     }
950 
951     Log(LOG_LEVEL_VERBOSE, " %s", NameVersion());
952     Banner("Initialization preamble");
953 
954     GenericAgentSetDefaultDigest(&CF_DEFAULT_DIGEST, &CF_DEFAULT_DIGEST_LEN);
955     GenericAgentInitialize(ctx, config);
956 
957     time_t t = SetReferenceTime();
958     UpdateTimeClasses(ctx, t);
959     SanitizeEnvironment();
960 
961     THIS_AGENT_TYPE = config->agent_type;
962     LoggingSetAgentType(CF_AGENTTYPES[config->agent_type]);
963     EvalContextClassPutHard(ctx, CF_AGENTTYPES[config->agent_type],
964                             "cfe_internal,source=agent");
965 
966     DetectEnvironment(ctx);
967     AddPolicyEntryVariables(ctx, config);
968 
969     EvalContextHeapPersistentLoadAll(ctx);
970     LoadSystemConstants(ctx);
971 
972     const char *bootstrap_arg =
973         config->agent_specific.agent.bootstrap_argument;
974     const char *bootstrap_ip =
975         config->agent_specific.agent.bootstrap_ip;
976 
977     /* Are we bootstrapping the agent? */
978     if (config->agent_type == AGENT_TYPE_AGENT && bootstrap_arg != NULL)
979     {
980         EvalContextClassPutHard(ctx, "bootstrap_mode", "report,source=environment");
981 
982         if (!config->agent_specific.agent.bootstrap_trigger_policy)
983         {
984             EvalContextClassPutHard(ctx, "skip_policy_on_bootstrap", "report,source=environment");
985         }
986 
987         if (!RemoveAllExistingPolicyInInputs(GetInputDir()))
988         {
989             Log(LOG_LEVEL_ERR,
990                 "Error removing existing input files prior to bootstrap");
991             DoCleanupAndExit(EXIT_FAILURE);
992         }
993 
994         if (!WriteBuiltinFailsafePolicy(GetInputDir()))
995         {
996             Log(LOG_LEVEL_ERR,
997                 "Error writing builtin failsafe to inputs prior to bootstrap");
998             DoCleanupAndExit(EXIT_FAILURE);
999         }
1000         GenericAgentConfigSetInputFile(config, GetInputDir(), "failsafe.cf");
1001 
1002         char canonified_ipaddr[strlen(bootstrap_ip) + 1];
1003         StringCanonify(canonified_ipaddr, bootstrap_ip);
1004 
1005         bool am_policy_server =
1006             EvalContextClassGet(ctx, NULL, canonified_ipaddr) != NULL;
1007 
1008         if (am_policy_server)
1009         {
1010             Log(LOG_LEVEL_INFO, "Assuming role as policy server,"
1011                 " with policy distribution point at: %s", GetMasterDir());
1012             MarkAsPolicyServer(ctx);
1013 
1014             if (!MasterfileExists(GetMasterDir()))
1015             {
1016                 Log(LOG_LEVEL_ERR, "In order to bootstrap as a policy server,"
1017                     " the file '%s/promises.cf' must exist.", GetMasterDir());
1018                 DoCleanupAndExit(EXIT_FAILURE);
1019             }
1020 
1021             CheckAndSetHAState(GetWorkDir(), ctx);
1022         }
1023         else
1024         {
1025             Log(LOG_LEVEL_INFO, "Assuming role as regular client,"
1026                 " bootstrapping to policy server: %s", bootstrap_arg);
1027 
1028             if (config->agent_specific.agent.bootstrap_trust_server)
1029             {
1030                 EvalContextClassPutHard(ctx, "trust_server", "source=agent");
1031                 Log(LOG_LEVEL_NOTICE,
1032                     "Bootstrap mode: implicitly trusting server, "
1033                     "use --trust-server=no if server trust is already established");
1034             }
1035         }
1036 
1037         WriteAmPolicyHubFile(am_policy_server);
1038 
1039         PolicyServerWriteFile(GetWorkDir(), bootstrap_arg);
1040         EvalContextSetPolicyServer(ctx, bootstrap_arg);
1041         char *const bootstrap_id = CreateBootstrapIDFile(GetWorkDir());
1042         if (bootstrap_id != NULL)
1043         {
1044             EvalContextSetBootstrapID(ctx, bootstrap_id);
1045             free(bootstrap_id);
1046         }
1047 
1048         /* FIXME: Why it is called here? Can't we move both invocations to before if? */
1049         UpdateLastPolicyUpdateTime(ctx);
1050     }
1051     else
1052     {
1053         char *existing_policy_server = PolicyServerReadFile(GetWorkDir());
1054         if (existing_policy_server)
1055         {
1056             Log(LOG_LEVEL_VERBOSE, "This agent is bootstrapped to: %s",
1057                 existing_policy_server);
1058             EvalContextSetPolicyServer(ctx, existing_policy_server);
1059             char *const bootstrap_id = ReadBootstrapIDFile(GetWorkDir());
1060             if (bootstrap_id != NULL)
1061             {
1062                 EvalContextSetBootstrapID(ctx, bootstrap_id);
1063                 free(bootstrap_id);
1064             }
1065             free(existing_policy_server);
1066             UpdateLastPolicyUpdateTime(ctx);
1067             if (GetAmPolicyHub())
1068             {
1069                 MarkAsPolicyServer(ctx);
1070 
1071                 /* Should this go in MarkAsPolicyServer() ? */
1072                 CheckAndSetHAState(GetWorkDir(), ctx);
1073             }
1074         }
1075         else
1076         {
1077             Log(LOG_LEVEL_VERBOSE, "This agent is not bootstrapped -"
1078                 " can't find policy_server.dat in: %s", GetWorkDir());
1079         }
1080     }
1081 
1082     /* Load CMDB data *before* augments. */
1083     if (!LoadCMDBData(ctx))
1084     {
1085         Log(LOG_LEVEL_ERR, "Failed to load CMDB data");
1086     }
1087 
1088     /* load augments here so that they can make use of the classes added above
1089      * (especially 'am_policy_hub' and 'policy_server') */
1090     LoadAugments(ctx, config);
1091 }
1092 
IsPolicyPrecheckNeeded(GenericAgentConfig * config,bool force_validation)1093 static bool IsPolicyPrecheckNeeded(GenericAgentConfig *config, bool force_validation)
1094 {
1095     bool check_policy = false;
1096 
1097     if (IsFileOutsideDefaultRepository(config->input_file))
1098     {
1099         check_policy = true;
1100         Log(LOG_LEVEL_VERBOSE, "Input file is outside default repository, validating it");
1101     }
1102     if (GenericAgentIsPolicyReloadNeeded(config))
1103     {
1104         check_policy = true;
1105         Log(LOG_LEVEL_VERBOSE, "Input file is changed since last validation, validating it");
1106     }
1107     if (force_validation)
1108     {
1109         check_policy = true;
1110         Log(LOG_LEVEL_VERBOSE, "always_validate is set, forcing policy validation");
1111     }
1112 
1113     return check_policy;
1114 }
1115 
GenericAgentCheckPolicy(GenericAgentConfig * config,bool force_validation,bool write_validated_file)1116 bool GenericAgentCheckPolicy(GenericAgentConfig *config, bool force_validation, bool write_validated_file)
1117 {
1118     if (!MissingInputFile(config->input_file))
1119     {
1120         {
1121             if (config->agent_type == AGENT_TYPE_SERVER ||
1122                 config->agent_type == AGENT_TYPE_MONITOR ||
1123                 config->agent_type == AGENT_TYPE_EXECUTOR)
1124             {
1125                 time_t validated_at = ReadTimestampFromPolicyValidatedFile(config, NULL);
1126                 config->agent_specific.daemon.last_validated_at = validated_at;
1127             }
1128         }
1129 
1130         if (IsPolicyPrecheckNeeded(config, force_validation))
1131         {
1132             bool policy_check_ok = GenericAgentArePromisesValid(config);
1133             if (policy_check_ok && write_validated_file)
1134             {
1135                 GenericAgentTagReleaseDirectory(config,
1136                                                 NULL, // use GetAutotagDir
1137                                                 write_validated_file, // true
1138                                                 GetAmPolicyHub()); // write release ID?
1139             }
1140 
1141             if (config->agent_specific.agent.bootstrap_argument && !policy_check_ok)
1142             {
1143                 Log(LOG_LEVEL_VERBOSE, "Policy is not valid, but proceeding with bootstrap");
1144                 return true;
1145             }
1146 
1147             return policy_check_ok;
1148         }
1149         else
1150         {
1151             Log(LOG_LEVEL_VERBOSE, "Policy is already validated");
1152             return true;
1153         }
1154     }
1155     return false;
1156 }
1157 
ReadPolicyValidatedFile(const char * filename)1158 static JsonElement *ReadPolicyValidatedFile(const char *filename)
1159 {
1160     bool missing = true;
1161     struct stat sb;
1162     if (stat(filename, &sb) != -1)
1163     {
1164         missing = false;
1165     }
1166 
1167     JsonElement *validated_doc = ReadJsonFile(filename, LOG_LEVEL_DEBUG, 5 * 1024 * 1024);
1168     if (validated_doc == NULL)
1169     {
1170         Log(missing ? LOG_LEVEL_DEBUG : LOG_LEVEL_VERBOSE, "Could not parse policy_validated JSON file '%s', using dummy data", filename);
1171         validated_doc = JsonObjectCreate(2);
1172         if (missing)
1173         {
1174             JsonObjectAppendInteger(validated_doc, "timestamp", 0);
1175         }
1176         else
1177         {
1178             JsonObjectAppendInteger(validated_doc, "timestamp", sb.st_mtime);
1179         }
1180     }
1181 
1182     return validated_doc;
1183 }
1184 
ReadPolicyValidatedFileFromMasterfiles(const GenericAgentConfig * config,const char * maybe_dirname)1185 static JsonElement *ReadPolicyValidatedFileFromMasterfiles(const GenericAgentConfig *config, const char *maybe_dirname)
1186 {
1187     char filename[CF_MAXVARSIZE];
1188 
1189     GetPromisesValidatedFile(filename, sizeof(filename), config, maybe_dirname);
1190 
1191     return ReadPolicyValidatedFile(filename);
1192 }
1193 
1194 /**
1195  * @brief Writes a file with a contained timestamp to mark a policy file as validated
1196  * @param filename the filename
1197  * @return True if successful.
1198  */
WritePolicyValidatedFile(ARG_UNUSED const GenericAgentConfig * config,const char * filename)1199 static bool WritePolicyValidatedFile(ARG_UNUSED const GenericAgentConfig *config, const char *filename)
1200 {
1201     if (!MakeParentDirectory(filename, true, NULL))
1202     {
1203         Log(LOG_LEVEL_ERR,
1204             "Could not write policy validated marker file: %s", filename);
1205         return false;
1206     }
1207 
1208     int fd = creat(filename, CF_PERMS_DEFAULT);
1209     if (fd == -1)
1210     {
1211         Log(LOG_LEVEL_ERR, "While writing policy validated marker file '%s', could not create file (create: %s)", filename, GetErrorStr());
1212         return false;
1213     }
1214 
1215     JsonElement *info = JsonObjectCreate(3);
1216     JsonObjectAppendInteger(info, "timestamp", time(NULL));
1217 
1218     Writer *w = FileWriter(fdopen(fd, "w"));
1219     JsonWrite(w, info, 0);
1220 
1221     WriterClose(w);
1222     JsonDestroy(info);
1223 
1224     Log(LOG_LEVEL_VERBOSE, "Saved policy validated marker file '%s'", filename);
1225     return true;
1226 }
1227 
1228 /**
1229  * @brief Writes the policy validation file and release ID to a directory
1230  * @return True if successful.
1231  */
GenericAgentTagReleaseDirectory(const GenericAgentConfig * config,const char * dirname,bool write_validated,bool write_release)1232 bool GenericAgentTagReleaseDirectory(const GenericAgentConfig *config, const char *dirname, bool write_validated, bool write_release)
1233 {
1234     char local_dirname[PATH_MAX + 1];
1235     if (dirname == NULL)
1236     {
1237         GetAutotagDir(local_dirname, PATH_MAX, NULL);
1238         dirname = local_dirname;
1239     }
1240 
1241     char filename[CF_MAXVARSIZE];
1242     char git_checksum[GENERIC_AGENT_CHECKSUM_SIZE];
1243     bool have_git_checksum = GeneratePolicyReleaseIDFromGit(git_checksum, sizeof(git_checksum), dirname);
1244 
1245     Log(LOG_LEVEL_DEBUG, "Tagging directory %s for release (write_validated: %s, write_release: %s)",
1246         dirname,
1247         write_validated ? "yes" : "no",
1248         write_release ? "yes" : "no");
1249 
1250     if (write_release)
1251     {
1252         // first, tag the release ID
1253         GetReleaseIdFile(dirname, filename, sizeof(filename));
1254         char *id = ReadReleaseIdFromReleaseIdFileMasterfiles(dirname);
1255         if (id == NULL
1256             || (have_git_checksum &&
1257                 strcmp(id, git_checksum) != 0))
1258         {
1259             if (id == NULL)
1260             {
1261                 Log(LOG_LEVEL_DEBUG, "The release_id of %s was missing", dirname);
1262             }
1263             else
1264             {
1265                 Log(LOG_LEVEL_DEBUG, "The release_id of %s needs to be updated", dirname);
1266             }
1267 
1268             bool wrote_release = WriteReleaseIdFile(filename, dirname);
1269             if (!wrote_release)
1270             {
1271                 Log(LOG_LEVEL_VERBOSE, "The release_id file %s was NOT updated", filename);
1272                 free(id);
1273                 return false;
1274             }
1275             else
1276             {
1277                 Log(LOG_LEVEL_DEBUG, "The release_id file %s was updated", filename);
1278             }
1279         }
1280 
1281         free(id);
1282     }
1283 
1284     // now, tag the promises_validated
1285     if (write_validated)
1286     {
1287         Log(LOG_LEVEL_DEBUG, "Tagging directory %s for validation", dirname);
1288 
1289         GetPromisesValidatedFile(filename, sizeof(filename), config, dirname);
1290 
1291         bool wrote_validated = WritePolicyValidatedFile(config, filename);
1292 
1293         if (!wrote_validated)
1294         {
1295             Log(LOG_LEVEL_VERBOSE, "The promises_validated file %s was NOT updated", filename);
1296             return false;
1297         }
1298 
1299         Log(LOG_LEVEL_DEBUG, "The promises_validated file %s was updated", filename);
1300         return true;
1301     }
1302 
1303     return true;
1304 }
1305 
1306 /**
1307  * @brief Writes a file with a contained release ID based on git SHA,
1308  *        or file checksum if git SHA is not available.
1309  * @param filename the release_id file
1310  * @param dirname the directory to checksum or get the Git hash
1311  * @return True if successful
1312  */
WriteReleaseIdFile(const char * filename,const char * dirname)1313 static bool WriteReleaseIdFile(const char *filename, const char *dirname)
1314 {
1315     char release_id[GENERIC_AGENT_CHECKSUM_SIZE];
1316 
1317     bool have_release_id =
1318         GeneratePolicyReleaseID(release_id, sizeof(release_id), dirname);
1319 
1320     if (!have_release_id)
1321     {
1322         return false;
1323     }
1324 
1325     int fd = creat(filename, CF_PERMS_DEFAULT);
1326     if (fd == -1)
1327     {
1328         Log(LOG_LEVEL_ERR, "While writing policy release ID file '%s', could not create file (create: %s)", filename, GetErrorStr());
1329         return false;
1330     }
1331 
1332     JsonElement *info = JsonObjectCreate(3);
1333     JsonObjectAppendString(info, "releaseId", release_id);
1334 
1335     Writer *w = FileWriter(fdopen(fd, "w"));
1336     JsonWrite(w, info, 0);
1337 
1338     WriterClose(w);
1339     JsonDestroy(info);
1340 
1341     Log(LOG_LEVEL_VERBOSE, "Saved policy release ID file '%s'", filename);
1342     return true;
1343 }
1344 
GenericAgentArePromisesValid(const GenericAgentConfig * config)1345 bool GenericAgentArePromisesValid(const GenericAgentConfig *config)
1346 {
1347     assert(config != NULL);
1348 
1349     char cmd[CF_BUFSIZE];
1350     const char* const bindir = GetBinDir();
1351 
1352     Log(LOG_LEVEL_VERBOSE, "Verifying the syntax of the inputs...");
1353     {
1354         char cfpromises[CF_MAXVARSIZE];
1355 
1356         snprintf(cfpromises, sizeof(cfpromises), "%s%ccf-promises%s",
1357                  bindir, FILE_SEPARATOR, EXEC_SUFFIX);
1358 
1359         struct stat sb;
1360         if (stat(cfpromises, &sb) == -1)
1361         {
1362             Log(LOG_LEVEL_ERR,
1363                 "cf-promises%s needs to be installed in %s for pre-validation of full configuration",
1364                 EXEC_SUFFIX, bindir);
1365 
1366             return false;
1367         }
1368 
1369         if (config->bundlesequence)
1370         {
1371             snprintf(cmd, sizeof(cmd), "\"%s\" \"", cfpromises);
1372         }
1373         else
1374         {
1375             snprintf(cmd, sizeof(cmd), "\"%s\" -c \"", cfpromises);
1376         }
1377     }
1378 
1379     strlcat(cmd, config->input_file, CF_BUFSIZE);
1380 
1381     strlcat(cmd, "\"", CF_BUFSIZE);
1382 
1383     if (config->bundlesequence)
1384     {
1385         strlcat(cmd, " -b \"", CF_BUFSIZE);
1386         for (const Rlist *rp = config->bundlesequence; rp; rp = rp->next)
1387         {
1388             const char *bundle_ref = RlistScalarValue(rp);
1389             strlcat(cmd, bundle_ref, CF_BUFSIZE);
1390 
1391             if (rp->next)
1392             {
1393                 strlcat(cmd, ",", CF_BUFSIZE);
1394             }
1395         }
1396         strlcat(cmd, "\"", CF_BUFSIZE);
1397     }
1398 
1399     if (config->ignore_preferred_augments)
1400     {
1401         strlcat(cmd, " --ignore-preferred-augments", CF_BUFSIZE);
1402     }
1403 
1404     Log(LOG_LEVEL_VERBOSE, "Checking policy with command '%s'", cmd);
1405 
1406     if (!ShellCommandReturnsZero(cmd, true))
1407     {
1408         Log(LOG_LEVEL_ERR, "Policy failed validation with command '%s'", cmd);
1409         return false;
1410     }
1411 
1412     return true;
1413 }
1414 
1415 
1416 
1417 
1418 /*****************************************************************************/
1419 
1420 #if !defined(__MINGW32__)
OpenLog(int facility)1421 static void OpenLog(int facility)
1422 {
1423     openlog(CF_PROGRAM_NAME, LOG_PID | LOG_NOWAIT | LOG_ODELAY, facility);
1424 }
1425 #endif
1426 
1427 /*****************************************************************************/
1428 
1429 #if !defined(__MINGW32__)
CloseLog(void)1430 void CloseLog(void)
1431 {
1432     closelog();
1433 }
1434 #endif
1435 
ENTERPRISE_VOID_FUNC_1ARG_DEFINE_STUB(void,GenericAgentAddEditionClasses,EvalContext *,ctx)1436 ENTERPRISE_VOID_FUNC_1ARG_DEFINE_STUB(void, GenericAgentAddEditionClasses, EvalContext *, ctx)
1437 {
1438     EvalContextClassPutHard(ctx, "community_edition", "inventory,attribute_name=none,source=agent");
1439 }
1440 
GetDefaultLogFacility()1441 static int GetDefaultLogFacility()
1442 {
1443     char log_facility_file[PATH_MAX];
1444     NDEBUG_UNUSED int written = snprintf(log_facility_file, sizeof(log_facility_file) - 1,
1445                                          "%s%c%s_log_facility.dat", GetStateDir(),
1446                                          FILE_SEPARATOR, CF_PROGRAM_NAME);
1447     assert(written < PATH_MAX);
1448     if (access(log_facility_file, R_OK) != 0)
1449     {
1450         return LOG_USER;
1451     }
1452 
1453     FILE *f = fopen(log_facility_file, "r");
1454     if (f == NULL)
1455     {
1456         return LOG_USER;
1457     }
1458     char facility_str[16] = {0}; /* at most "LOG_DAEMON\n" */
1459     size_t n_read = fread(facility_str, 1, sizeof(facility_str) - 1, f);
1460     fclose(f);
1461     if (n_read == 0)
1462     {
1463         return LOG_USER;
1464     }
1465     if (facility_str[n_read - 1] == '\n')
1466     {
1467         facility_str[n_read - 1] = '\0';
1468     }
1469     return ParseFacility(facility_str);
1470 }
1471 
StoreDefaultLogFacility()1472 static bool StoreDefaultLogFacility()
1473 {
1474     char log_facility_file[PATH_MAX];
1475     NDEBUG_UNUSED int written = snprintf(log_facility_file, sizeof(log_facility_file) - 1,
1476                                          "%s%c%s_log_facility.dat", GetStateDir(),
1477                                          FILE_SEPARATOR, CF_PROGRAM_NAME);
1478     assert(written < PATH_MAX);
1479 
1480     FILE *f = fopen(log_facility_file, "w");
1481     if (f == NULL)
1482     {
1483         return false;
1484     }
1485     const char *facility_str = LogFacilityToString(GetSyslogFacility());
1486     NDEBUG_UNUSED int printed = fprintf(f, "%s\n", facility_str);
1487     assert(printed > 0);
1488 
1489     fclose(f);
1490     return true;
1491 }
1492 
GenericAgentInitialize(EvalContext * ctx,GenericAgentConfig * config)1493 void GenericAgentInitialize(EvalContext *ctx, GenericAgentConfig *config)
1494 {
1495     int force = false;
1496     struct stat statbuf, sb;
1497     char vbuff[CF_BUFSIZE];
1498     char ebuff[CF_EXPANDSIZE];
1499 
1500 #ifdef __MINGW32__
1501     InitializeWindows();
1502 #endif
1503 
1504     /* Set output to line-buffered to avoid truncated debug logs. */
1505 
1506     /* Bug on HP-UX: Buffered output is discarded if you switch buffering mode
1507        without flushing the buffered output first. This will happen anyway when
1508        switching modes, so no performance is lost. */
1509     fflush(stdout);
1510 
1511 #ifndef SUNOS_5
1512     setlinebuf(stdout);
1513 #else
1514     /* CFE-2527: On Solaris we avoid calling setlinebuf, since fprintf() on
1515        Solaris 10 and 11 truncates output under certain conditions. We fully
1516        disable buffering to avoid truncated debug logs; performance impact
1517        should be minimal because we mostly write full lines anyway. */
1518     setvbuf(stdout, NULL, _IONBF, 0);
1519 #endif
1520 
1521     DetermineCfenginePort();
1522 
1523     int default_facility = GetDefaultLogFacility();
1524     OpenLog(default_facility);
1525     SetSyslogFacility(default_facility);
1526 
1527     EvalContextClassPutHard(ctx, "any", "source=agent");
1528 
1529     GenericAgentAddEditionClasses(ctx);
1530 
1531     /* Make sure the chroot for recording changes this process would normally
1532      * make on the system is setup if that was requested. */
1533     if (ChrootChanges())
1534     {
1535         char changes_chroot[PATH_MAX] = {0};
1536         GetChangesChrootDir(changes_chroot, sizeof(changes_chroot));
1537         SetChangesChroot(changes_chroot);
1538         RegisterCleanupFunction(DeleteChangesChroot);
1539         Log(LOG_LEVEL_WARNING, "All changes in files will be made in the '%s' chroot",
1540             changes_chroot);
1541     }
1542 
1543 /* Define trusted directories */
1544 
1545     const char *workdir = GetWorkDir();
1546     const char *bindir = GetBinDir();
1547 
1548     if (!workdir)
1549     {
1550         FatalError(ctx, "Error determining working directory");
1551     }
1552 
1553     Log(LOG_LEVEL_VERBOSE, "Work directory is %s", workdir);
1554 
1555     snprintf(vbuff, CF_BUFSIZE, "%s%cupdate.conf", GetInputDir(), FILE_SEPARATOR);
1556     MakeParentInternalDirectory(vbuff, force, NULL);
1557     snprintf(vbuff, CF_BUFSIZE, "%s%ccf-agent", bindir, FILE_SEPARATOR);
1558     MakeParentInternalDirectory(vbuff, force, NULL);
1559     snprintf(vbuff, CF_BUFSIZE, "%s%coutputs%cspooled_reports", workdir, FILE_SEPARATOR, FILE_SEPARATOR);
1560     MakeParentInternalDirectory(vbuff, force, NULL);
1561     snprintf(vbuff, CF_BUFSIZE, "%s%clastseen%cintermittencies", workdir, FILE_SEPARATOR, FILE_SEPARATOR);
1562     MakeParentInternalDirectory(vbuff, force, NULL);
1563     snprintf(vbuff, CF_BUFSIZE, "%s%creports%cvarious", workdir, FILE_SEPARATOR, FILE_SEPARATOR);
1564     MakeParentInternalDirectory(vbuff, force, NULL);
1565 
1566     snprintf(vbuff, CF_BUFSIZE, "%s%c.", GetLogDir(), FILE_SEPARATOR);
1567     MakeParentInternalDirectory(vbuff, force, NULL);
1568     snprintf(vbuff, CF_BUFSIZE, "%s%c.", GetPidDir(), FILE_SEPARATOR);
1569     MakeParentInternalDirectory(vbuff, force, NULL);
1570     snprintf(vbuff, CF_BUFSIZE, "%s%c.", GetStateDir(), FILE_SEPARATOR);
1571     MakeParentInternalDirectory(vbuff, force, NULL);
1572 
1573     MakeParentInternalDirectory(GetLogDir(), force, NULL);
1574 
1575     snprintf(vbuff, CF_BUFSIZE, "%s", GetInputDir());
1576 
1577     if (stat(vbuff, &sb) == -1)
1578     {
1579         FatalError(ctx, " No access to WORKSPACE/inputs dir");
1580     }
1581 
1582     /* ensure WORKSPACE/inputs directory has all user bits set (u+rwx) */
1583     if ((sb.st_mode & 0700) != 0700)
1584     {
1585         chmod(vbuff, sb.st_mode | 0700);
1586     }
1587 
1588     snprintf(vbuff, CF_BUFSIZE, "%s%coutputs", workdir, FILE_SEPARATOR);
1589 
1590     if (stat(vbuff, &sb) == -1)
1591     {
1592         FatalError(ctx, " No access to WORKSPACE/outputs dir");
1593     }
1594 
1595     /* ensure WORKSPACE/outputs directory has all user bits set (u+rwx) */
1596     if ((sb.st_mode & 0700) != 0700)
1597     {
1598         chmod(vbuff, sb.st_mode | 0700);
1599     }
1600 
1601     const char* const statedir = GetStateDir();
1602 
1603     snprintf(ebuff, sizeof(ebuff), "%s%ccf_procs",
1604              statedir, FILE_SEPARATOR);
1605     MakeParentDirectory(ebuff, force, NULL);
1606 
1607     if (stat(ebuff, &statbuf) == -1)
1608     {
1609         CreateEmptyFile(ebuff);
1610     }
1611 
1612     snprintf(ebuff, sizeof(ebuff), "%s%ccf_rootprocs",
1613              statedir, FILE_SEPARATOR);
1614 
1615     if (stat(ebuff, &statbuf) == -1)
1616     {
1617         CreateEmptyFile(ebuff);
1618     }
1619 
1620     snprintf(ebuff, sizeof(ebuff), "%s%ccf_otherprocs",
1621              statedir, FILE_SEPARATOR);
1622 
1623     if (stat(ebuff, &statbuf) == -1)
1624     {
1625         CreateEmptyFile(ebuff);
1626     }
1627 
1628     snprintf(ebuff, sizeof(ebuff), "%s%cprevious_state%c",
1629              statedir, FILE_SEPARATOR, FILE_SEPARATOR);
1630     MakeParentDirectory(ebuff, force, NULL);
1631 
1632     snprintf(ebuff, sizeof(ebuff), "%s%cdiff%c",
1633              statedir, FILE_SEPARATOR, FILE_SEPARATOR);
1634     MakeParentDirectory(ebuff, force, NULL);
1635 
1636     snprintf(ebuff, sizeof(ebuff), "%s%cuntracked%c",
1637              statedir, FILE_SEPARATOR, FILE_SEPARATOR);
1638     MakeParentDirectory(ebuff, force, NULL);
1639 
1640     OpenNetwork();
1641     CryptoInitialize();
1642 
1643     CheckWorkingDirectories(ctx);
1644 
1645     /* Initialize keys and networking. cf-key, doesn't need keys. In fact it
1646        must function properly even without them, so that it generates them! */
1647     if (config->agent_type != AGENT_TYPE_KEYGEN)
1648     {
1649         LoadSecretKeys(NULL, NULL, NULL, NULL);
1650         char *ipaddr = NULL, *port = NULL;
1651         PolicyServerLookUpFile(workdir, &ipaddr, &port);
1652         PolicyHubUpdateKeys(ipaddr);
1653         free(ipaddr);
1654         free(port);
1655     }
1656 
1657     size_t cwd_size = PATH_MAX;
1658     while (true)
1659     {
1660         char cwd[cwd_size];
1661         if (!getcwd(cwd, cwd_size))
1662         {
1663             if (errno == ERANGE)
1664             {
1665                 cwd_size *= 2;
1666                 continue;
1667             }
1668             else
1669             {
1670                 Log(LOG_LEVEL_WARNING,
1671                     "Could not determine current directory (getcwd: %s)",
1672                     GetErrorStr());
1673                 break;
1674             }
1675         }
1676 
1677         EvalContextSetLaunchDirectory(ctx, cwd);
1678         break;
1679     }
1680 
1681     if (!MINUSF)
1682     {
1683         GenericAgentConfigSetInputFile(config, GetInputDir(), "promises.cf");
1684     }
1685 }
1686 
GetChangesChrootDir(char * buf,size_t buf_size)1687 static void GetChangesChrootDir(char *buf, size_t buf_size)
1688 {
1689     snprintf(buf, buf_size, "%s/%ju.changes", GetStateDir(), (uintmax_t) getpid());
1690 }
1691 
DeleteChangesChroot()1692 static void DeleteChangesChroot()
1693 {
1694     char changes_chroot[PATH_MAX] = {0};
1695     GetChangesChrootDir(changes_chroot, sizeof(changes_chroot));
1696     Log(LOG_LEVEL_VERBOSE, "Deleting changes chroot '%s'", changes_chroot);
1697     DeleteDirectoryTree(changes_chroot);
1698 
1699     /* DeleteDirectoryTree() doesn't delete the root of the tree. */
1700     if (rmdir(changes_chroot) != 0)
1701     {
1702         Log(LOG_LEVEL_ERR, "Failed to delete changes chroot '%s'", changes_chroot);
1703     }
1704 }
1705 
GenericAgentFinalize(EvalContext * ctx,GenericAgentConfig * config)1706 void GenericAgentFinalize(EvalContext *ctx, GenericAgentConfig *config)
1707 {
1708     /* TODO, FIXME: what else from the above do we need to undo here ? */
1709     if (config->agent_type != AGENT_TYPE_KEYGEN)
1710     {
1711         cfnet_shut();
1712     }
1713     CryptoDeInitialize();
1714     GenericAgentConfigDestroy(config);
1715     EvalContextDestroy(ctx);
1716 }
1717 
MissingInputFile(const char * input_file)1718 static bool MissingInputFile(const char *input_file)
1719 {
1720     struct stat sb;
1721 
1722     if (stat(input_file, &sb) == -1)
1723     {
1724         Log(LOG_LEVEL_ERR, "There is no readable input file at '%s'. (stat: %s)", input_file, GetErrorStr());
1725         return true;
1726     }
1727 
1728     return false;
1729 }
1730 
1731 // Git only.
GeneratePolicyReleaseIDFromGit(char * release_id_out,ARG_UNUSED size_t out_size,const char * policy_dir)1732 static bool GeneratePolicyReleaseIDFromGit(char *release_id_out,
1733 #ifdef NDEBUG /* out_size is only used in an assertion */
1734                                            ARG_UNUSED
1735 #endif
1736                                            size_t out_size,
1737                                            const char *policy_dir)
1738 {
1739     char git_filename[PATH_MAX + 1];
1740     snprintf(git_filename, PATH_MAX, "%s/.git/HEAD", policy_dir);
1741     MapName(git_filename);
1742 
1743     // Note: Probably we should not be reading all of these filenames directly,
1744     // and should instead use git plumbing commands to retrieve the data.
1745     FILE *git_file = safe_fopen(git_filename, "r");
1746     if (git_file)
1747     {
1748         char git_head[128];
1749         int scanned = fscanf(git_file, "ref: %127s", git_head);
1750 
1751         if (scanned == 1)
1752         // Found HEAD Reference which means we are on a checked out branch
1753         {
1754             fclose(git_file);
1755             snprintf(git_filename, PATH_MAX, "%s/.git/%s",
1756                      policy_dir, git_head);
1757             git_file = safe_fopen(git_filename, "r");
1758             Log(LOG_LEVEL_DEBUG, "Found a git HEAD ref");
1759         }
1760         else
1761         {
1762             Log(LOG_LEVEL_DEBUG,
1763                 "Unable to find HEAD ref in '%s', looking for commit instead",
1764                 git_filename);
1765             assert(out_size > 40);
1766             fseek(git_file, 0, SEEK_SET);
1767             scanned = fscanf(git_file, "%40s", release_id_out);
1768             fclose(git_file);
1769 
1770             if (scanned == 1)
1771             {
1772                 Log(LOG_LEVEL_DEBUG,
1773                     "Found current git checkout pointing to: %s",
1774                     release_id_out);
1775                 return true;
1776             }
1777             else
1778             {
1779                 /* We didn't find a commit sha in .git/HEAD, so we assume the
1780                  * git information is invalid. */
1781                 git_file = NULL;
1782             }
1783         }
1784         if (git_file)
1785         {
1786             assert(out_size > 40);
1787             scanned = fscanf(git_file, "%40s", release_id_out);
1788             fclose(git_file);
1789             return scanned == 1;
1790         }
1791         else
1792         {
1793             Log(LOG_LEVEL_DEBUG, "While generating policy release ID, found git head ref '%s', but unable to open (errno: %s)",
1794                 policy_dir, GetErrorStr());
1795         }
1796     }
1797     else
1798     {
1799         Log(LOG_LEVEL_DEBUG, "While generating policy release ID, directory is '%s' not a git repository",
1800             policy_dir);
1801     }
1802 
1803     return false;
1804 }
1805 
GeneratePolicyReleaseIDFromTree(char * release_id_out,size_t out_size,const char * policy_dir)1806 static bool GeneratePolicyReleaseIDFromTree(char *release_id_out, size_t out_size,
1807                                             const char *policy_dir)
1808 {
1809     if (access(policy_dir, R_OK) != 0)
1810     {
1811         Log(LOG_LEVEL_ERR, "Cannot access policy directory '%s' to generate release ID", policy_dir);
1812         return false;
1813     }
1814 
1815     // fallback, produce some pseudo sha1 hash
1816     const EVP_MD *const md = HashDigestFromId(GENERIC_AGENT_CHECKSUM_METHOD);
1817     if (md == NULL)
1818     {
1819         Log(LOG_LEVEL_ERR,
1820             "Could not determine function for file hashing");
1821         return false;
1822     }
1823 
1824     EVP_MD_CTX *crypto_ctx = EVP_MD_CTX_new();
1825     if (crypto_ctx == NULL)
1826     {
1827         Log(LOG_LEVEL_ERR, "Could not allocate openssl hash context");
1828         return false;
1829     }
1830 
1831     EVP_DigestInit(crypto_ctx, md);
1832 
1833     bool success = HashDirectoryTree(policy_dir,
1834                                      (const char *[]) { ".cf", ".dat", ".txt", ".conf", ".mustache", ".json", ".yaml", NULL},
1835                                      crypto_ctx);
1836 
1837     int md_len;
1838     unsigned char digest[EVP_MAX_MD_SIZE + 1] = { 0 };
1839     EVP_DigestFinal(crypto_ctx, digest, &md_len);
1840     EVP_MD_CTX_free(crypto_ctx);
1841 
1842     HashPrintSafe(release_id_out, out_size, digest,
1843                   GENERIC_AGENT_CHECKSUM_METHOD, false);
1844     return success;
1845 }
1846 
GeneratePolicyReleaseID(char * release_id_out,size_t out_size,const char * policy_dir)1847 static bool GeneratePolicyReleaseID(char *release_id_out, size_t out_size,
1848                                     const char *policy_dir)
1849 {
1850     if (GeneratePolicyReleaseIDFromGit(release_id_out, out_size, policy_dir))
1851     {
1852         return true;
1853     }
1854 
1855     return GeneratePolicyReleaseIDFromTree(release_id_out, out_size,
1856                                            policy_dir);
1857 }
1858 
1859 /**
1860  * @brief Gets the promises_validated file name depending on context and options
1861  */
GetPromisesValidatedFile(char * filename,size_t max_size,const GenericAgentConfig * config,const char * maybe_dirname)1862 static void GetPromisesValidatedFile(char *filename, size_t max_size, const GenericAgentConfig *config, const char *maybe_dirname)
1863 {
1864     char dirname[max_size];
1865 
1866     /* TODO overflow error checking! */
1867     GetAutotagDir(dirname, max_size, maybe_dirname);
1868 
1869     if (maybe_dirname == NULL && MINUSF)
1870     {
1871         snprintf(filename, max_size, "%s/validated_%s", dirname, CanonifyName(config->original_input_file));
1872     }
1873     else
1874     {
1875         snprintf(filename, max_size, "%s/cf_promises_validated", dirname);
1876     }
1877 
1878     MapName(filename);
1879 }
1880 
1881  /**
1882  * @brief Gets the promises_validated file name depending on context and options
1883  */
GetAutotagDir(char * dirname,size_t max_size,const char * maybe_dirname)1884 static void GetAutotagDir(char *dirname, size_t max_size, const char *maybe_dirname)
1885 {
1886     if (maybe_dirname != NULL)
1887     {
1888         strlcpy(dirname, maybe_dirname, max_size);
1889     }
1890     else if (MINUSF)
1891     {
1892         strlcpy(dirname, GetStateDir(), max_size);
1893     }
1894     else
1895     {
1896         strlcpy(dirname, GetMasterDir(), max_size);
1897     }
1898 
1899     MapName(dirname);
1900 }
1901 
1902 /**
1903  * @brief Gets the release_id file name in the given base_path.
1904  */
GetReleaseIdFile(const char * base_path,char * filename,size_t max_size)1905 void GetReleaseIdFile(const char *base_path, char *filename, size_t max_size)
1906 {
1907     snprintf(filename, max_size, "%s/cf_promises_release_id", base_path);
1908     MapName(filename);
1909 }
1910 
ReadReleaseIdFileFromMasterfiles(const char * maybe_dirname)1911 static JsonElement *ReadReleaseIdFileFromMasterfiles(const char *maybe_dirname)
1912 {
1913     char filename[CF_MAXVARSIZE];
1914 
1915     GetReleaseIdFile((maybe_dirname == NULL) ? GetMasterDir() : maybe_dirname,
1916                      filename, sizeof(filename));
1917 
1918     JsonElement *doc = ReadJsonFile(filename, LOG_LEVEL_DEBUG, 5 * 1024 * 1024);
1919     if (doc == NULL)
1920     {
1921         Log(LOG_LEVEL_VERBOSE, "Could not parse release_id JSON file %s", filename);
1922     }
1923 
1924     return doc;
1925 }
1926 
ReadReleaseIdFromReleaseIdFileMasterfiles(const char * maybe_dirname)1927 static char* ReadReleaseIdFromReleaseIdFileMasterfiles(const char *maybe_dirname)
1928 {
1929     JsonElement *doc = ReadReleaseIdFileFromMasterfiles(maybe_dirname);
1930     char *id = NULL;
1931     if (doc)
1932     {
1933         JsonElement *jid = JsonObjectGet(doc, "releaseId");
1934         if (jid)
1935         {
1936             id = xstrdup(JsonPrimitiveGetAsString(jid));
1937         }
1938         JsonDestroy(doc);
1939     }
1940 
1941     return id;
1942 }
1943 
1944 // TODO: refactor Read*FromPolicyValidatedMasterfiles
ReadTimestampFromPolicyValidatedFile(const GenericAgentConfig * config,const char * maybe_dirname)1945 time_t ReadTimestampFromPolicyValidatedFile(const GenericAgentConfig *config, const char *maybe_dirname)
1946 {
1947     time_t validated_at = 0;
1948     {
1949         JsonElement *validated_doc = ReadPolicyValidatedFileFromMasterfiles(config, maybe_dirname);
1950         if (validated_doc)
1951         {
1952             JsonElement *timestamp = JsonObjectGet(validated_doc, "timestamp");
1953             if (timestamp)
1954             {
1955                 validated_at = JsonPrimitiveGetAsInteger(timestamp);
1956             }
1957             JsonDestroy(validated_doc);
1958         }
1959     }
1960 
1961     return validated_at;
1962 }
1963 
1964 // TODO: refactor Read*FromPolicyValidatedMasterfiles
ReadChecksumFromPolicyValidatedMasterfiles(const GenericAgentConfig * config,const char * maybe_dirname)1965 char* ReadChecksumFromPolicyValidatedMasterfiles(const GenericAgentConfig *config, const char *maybe_dirname)
1966 {
1967     char *checksum_str = NULL;
1968 
1969     {
1970         JsonElement *validated_doc = ReadPolicyValidatedFileFromMasterfiles(config, maybe_dirname);
1971         if (validated_doc)
1972         {
1973             JsonElement *checksum = JsonObjectGet(validated_doc, "checksum");
1974             if (checksum )
1975             {
1976                 checksum_str = xstrdup(JsonPrimitiveGetAsString(checksum));
1977             }
1978             JsonDestroy(validated_doc);
1979         }
1980     }
1981 
1982     return checksum_str;
1983 }
1984 
1985 /**
1986  * @NOTE Updates the config->agent_specific.daemon.last_validated_at timestamp
1987  *       used by serverd, execd etc daemons when checking for new policies.
1988  */
GenericAgentIsPolicyReloadNeeded(const GenericAgentConfig * config)1989 bool GenericAgentIsPolicyReloadNeeded(const GenericAgentConfig *config)
1990 {
1991     time_t validated_at = ReadTimestampFromPolicyValidatedFile(config, NULL);
1992     time_t now = time(NULL);
1993 
1994     if (validated_at > now)
1995     {
1996         Log(LOG_LEVEL_INFO,
1997             "Clock seems to have jumped back in time, mtime of %jd is newer than current time %jd, touching it",
1998             (intmax_t) validated_at, (intmax_t) now);
1999 
2000         GenericAgentTagReleaseDirectory(config,
2001                                         NULL, // use GetAutotagDir
2002                                         true, // write validated
2003                                         false); // write release ID
2004         return true;
2005     }
2006 
2007     {
2008         struct stat sb;
2009         if (stat(config->input_file, &sb) == -1)
2010         {
2011             Log(LOG_LEVEL_VERBOSE, "There is no readable input file at '%s'. (stat: %s)", config->input_file, GetErrorStr());
2012             return true;
2013         }
2014         else if (sb.st_mtime > validated_at)
2015         {
2016             Log(LOG_LEVEL_VERBOSE, "Input file '%s' has changed since the last policy read attempt (file is newer than previous)", config->input_file);
2017             return true;
2018         }
2019     }
2020 
2021     // Check the directories first for speed and because non-input/data files should trigger an update
2022     {
2023         if (IsNewerFileTree( (char *)GetInputDir(), validated_at))
2024         {
2025             Log(LOG_LEVEL_VERBOSE, "Quick search detected file changes");
2026             return true;
2027         }
2028     }
2029 
2030     {
2031         char filename[MAX_FILENAME];
2032         snprintf(filename, MAX_FILENAME, "%s/policy_server.dat", GetWorkDir());
2033         MapName(filename);
2034 
2035         struct stat sb;
2036         if ((stat(filename, &sb) != -1) && (sb.st_mtime > validated_at))
2037         {
2038             return true;
2039         }
2040     }
2041 
2042     return false;
2043 }
2044 
2045 /*******************************************************************/
2046 
ControlBodyConstraints(const Policy * policy,AgentType agent)2047 Seq *ControlBodyConstraints(const Policy *policy, AgentType agent)
2048 {
2049     for (size_t i = 0; i < SeqLength(policy->bodies); i++)
2050     {
2051         const Body *body = SeqAt(policy->bodies, i);
2052 
2053         if (strcmp(body->type, CF_AGENTTYPES[agent]) == 0)
2054         {
2055             if (strcmp(body->name, "control") == 0)
2056             {
2057                 return body->conlist;
2058             }
2059         }
2060     }
2061 
2062     return NULL;
2063 }
2064 
2065 /*******************************************************************/
2066 
ParseFacility(const char * name)2067 static int ParseFacility(const char *name)
2068 {
2069     if (strcmp(name, "LOG_USER") == 0)
2070     {
2071         return LOG_USER;
2072     }
2073     if (strcmp(name, "LOG_DAEMON") == 0)
2074     {
2075         return LOG_DAEMON;
2076     }
2077     if (strcmp(name, "LOG_LOCAL0") == 0)
2078     {
2079         return LOG_LOCAL0;
2080     }
2081     if (strcmp(name, "LOG_LOCAL1") == 0)
2082     {
2083         return LOG_LOCAL1;
2084     }
2085     if (strcmp(name, "LOG_LOCAL2") == 0)
2086     {
2087         return LOG_LOCAL2;
2088     }
2089     if (strcmp(name, "LOG_LOCAL3") == 0)
2090     {
2091         return LOG_LOCAL3;
2092     }
2093     if (strcmp(name, "LOG_LOCAL4") == 0)
2094     {
2095         return LOG_LOCAL4;
2096     }
2097     if (strcmp(name, "LOG_LOCAL5") == 0)
2098     {
2099         return LOG_LOCAL5;
2100     }
2101     if (strcmp(name, "LOG_LOCAL6") == 0)
2102     {
2103         return LOG_LOCAL6;
2104     }
2105     if (strcmp(name, "LOG_LOCAL7") == 0)
2106     {
2107         return LOG_LOCAL7;
2108     }
2109     return -1;
2110 }
2111 
LogFacilityToString(int facility)2112 static inline const char *LogFacilityToString(int facility)
2113 {
2114     switch(facility)
2115     {
2116         case LOG_LOCAL0: return "LOG_LOCAL0";
2117         case LOG_LOCAL1: return "LOG_LOCAL1";
2118         case LOG_LOCAL2: return "LOG_LOCAL2";
2119         case LOG_LOCAL3: return "LOG_LOCAL3";
2120         case LOG_LOCAL4: return "LOG_LOCAL4";
2121         case LOG_LOCAL5: return "LOG_LOCAL5";
2122         case LOG_LOCAL6: return "LOG_LOCAL6";
2123         case LOG_LOCAL7: return "LOG_LOCAL7";
2124         case LOG_USER:   return "LOG_USER";
2125         case LOG_DAEMON: return "LOG_DAEMON";
2126         default:         return "UNKNOWN";
2127     }
2128 }
2129 
SetFacility(const char * retval)2130 void SetFacility(const char *retval)
2131 {
2132     Log(LOG_LEVEL_VERBOSE, "SET Syslog FACILITY = %s", retval);
2133 
2134     CloseLog();
2135     OpenLog(ParseFacility(retval));
2136     SetSyslogFacility(ParseFacility(retval));
2137     if (!StoreDefaultLogFacility())
2138     {
2139         Log(LOG_LEVEL_ERR, "Failed to store default log facility");
2140     }
2141 }
2142 
CheckWorkingDirectories(EvalContext * ctx)2143 static void CheckWorkingDirectories(EvalContext *ctx)
2144 /* NOTE: We do not care about permissions (ACLs) in windows */
2145 {
2146     struct stat statbuf;
2147     char vbuff[CF_BUFSIZE];
2148 
2149     const char* const workdir = GetWorkDir();
2150     const char* const statedir = GetStateDir();
2151 
2152     if (uname(&VSYSNAME) == -1)
2153     {
2154         Log(LOG_LEVEL_ERR, "Couldn't get kernel name info. (uname: %s)", GetErrorStr());
2155         memset(&VSYSNAME, 0, sizeof(VSYSNAME));
2156     }
2157 
2158     snprintf(vbuff, CF_BUFSIZE, "%s%c.", workdir, FILE_SEPARATOR);
2159     MakeParentDirectory(vbuff, false, NULL);
2160 
2161     /* check that GetWorkDir() exists */
2162     if (stat(GetWorkDir(), &statbuf) == -1)
2163     {
2164         FatalError(ctx,"Unable to stat working directory '%s'! (stat: %s)\n",
2165                    GetWorkDir(), GetErrorStr());
2166     }
2167 
2168     Log(LOG_LEVEL_VERBOSE, "Making sure that internal directories are private...");
2169 
2170     Log(LOG_LEVEL_VERBOSE, "Checking integrity of the trusted workdir");
2171 
2172     /* fix any improper uid/gid ownership on workdir */
2173     if (statbuf.st_uid != getuid() || statbuf.st_gid != getgid())
2174     {
2175         if (chown(workdir, getuid(), getgid()) == -1)
2176         {
2177             const char* error_reason = GetErrorStr();
2178 
2179             Log(LOG_LEVEL_ERR, "Unable to set ownership on '%s' to '%ju.%ju'. (chown: %s)",
2180                 workdir, (uintmax_t)getuid(), (uintmax_t)getgid(), error_reason);
2181         }
2182     }
2183 
2184     /* ensure workdir permissions are go-w */
2185     if ((statbuf.st_mode & 022) != 0)
2186     {
2187         if (chmod(workdir, (mode_t) (statbuf.st_mode & ~022)) == -1)
2188         {
2189             Log(LOG_LEVEL_ERR, "Unable to set permissions on '%s' to go-w. (chmod: %s)",
2190                 workdir, GetErrorStr());
2191         }
2192     }
2193 
2194     MakeParentDirectory(GetStateDir(), false, NULL);
2195     Log(LOG_LEVEL_VERBOSE, "Checking integrity of the state database");
2196 
2197     snprintf(vbuff, CF_BUFSIZE, "%s", statedir);
2198 
2199     if (stat(vbuff, &statbuf) == -1)
2200     {
2201         snprintf(vbuff, CF_BUFSIZE, "%s%c", statedir, FILE_SEPARATOR);
2202         MakeParentDirectory(vbuff, false, NULL);
2203 
2204         if (chown(vbuff, getuid(), getgid()) == -1)
2205         {
2206             Log(LOG_LEVEL_ERR, "Unable to set owner on '%s' to '%ju.%ju'. (chown: %s)", vbuff,
2207                 (uintmax_t)getuid(), (uintmax_t)getgid(), GetErrorStr());
2208         }
2209 
2210         chmod(vbuff, (mode_t) 0755);
2211     }
2212     else
2213     {
2214 #ifndef __MINGW32__
2215         if (statbuf.st_mode & 022)
2216         {
2217             Log(LOG_LEVEL_ERR, "UNTRUSTED: State directory %s (mode %jo) was not private, world and/or group writeable!", statedir,
2218                   (uintmax_t)(statbuf.st_mode & 0777));
2219         }
2220 #endif /* !__MINGW32__ */
2221     }
2222 
2223     Log(LOG_LEVEL_VERBOSE, "Checking integrity of the module directory");
2224 
2225     snprintf(vbuff, CF_BUFSIZE, "%s%cmodules", workdir, FILE_SEPARATOR);
2226 
2227     if (stat(vbuff, &statbuf) == -1)
2228     {
2229         snprintf(vbuff, CF_BUFSIZE, "%s%cmodules%c.", workdir, FILE_SEPARATOR, FILE_SEPARATOR);
2230         MakeParentDirectory(vbuff, false, NULL);
2231 
2232         if (chown(vbuff, getuid(), getgid()) == -1)
2233         {
2234             Log(LOG_LEVEL_ERR, "Unable to set owner on '%s' to '%ju.%ju'. (chown: %s)", vbuff,
2235                 (uintmax_t)getuid(), (uintmax_t)getgid(), GetErrorStr());
2236         }
2237 
2238         chmod(vbuff, (mode_t) 0700);
2239     }
2240     else
2241     {
2242 #ifndef __MINGW32__
2243         if (statbuf.st_mode & 022)
2244         {
2245             Log(LOG_LEVEL_ERR, "UNTRUSTED: Module directory %s (mode %jo) was not private!", vbuff,
2246                   (uintmax_t)(statbuf.st_mode & 0777));
2247         }
2248 #endif /* !__MINGW32__ */
2249     }
2250 
2251     Log(LOG_LEVEL_VERBOSE, "Checking integrity of the PKI directory");
2252 
2253     snprintf(vbuff, CF_BUFSIZE, "%s%cppkeys", workdir, FILE_SEPARATOR);
2254 
2255     if (stat(vbuff, &statbuf) == -1)
2256     {
2257         snprintf(vbuff, CF_BUFSIZE, "%s%cppkeys%c", workdir, FILE_SEPARATOR, FILE_SEPARATOR);
2258         MakeParentDirectory(vbuff, false, NULL);
2259 
2260         chmod(vbuff, (mode_t) 0700); /* Keys must be immutable to others */
2261     }
2262     else
2263     {
2264 #ifndef __MINGW32__
2265         if (statbuf.st_mode & 077)
2266         {
2267             FatalError(ctx, "UNTRUSTED: Private key directory %s%cppkeys (mode %jo) was not private!\n", workdir,
2268                        FILE_SEPARATOR, (uintmax_t)(statbuf.st_mode & 0777));
2269         }
2270 #endif /* !__MINGW32__ */
2271     }
2272 }
2273 
2274 
GenericAgentResolveInputPath(const GenericAgentConfig * config,const char * input_file)2275 const char *GenericAgentResolveInputPath(const GenericAgentConfig *config, const char *input_file)
2276 {
2277     static char input_path[CF_BUFSIZE]; /* GLOBAL_R, no initialization needed */
2278 
2279     switch (FilePathGetType(input_file))
2280     {
2281     case FILE_PATH_TYPE_ABSOLUTE:
2282         strlcpy(input_path, input_file, CF_BUFSIZE);
2283         break;
2284 
2285     case FILE_PATH_TYPE_NON_ANCHORED:
2286     case FILE_PATH_TYPE_RELATIVE:
2287         snprintf(input_path, CF_BUFSIZE, "%s%c%s", config->input_dir, FILE_SEPARATOR, input_file);
2288         break;
2289     }
2290 
2291     return MapName(input_path);
2292 }
2293 
ENTERPRISE_VOID_FUNC_1ARG_DEFINE_STUB(void,GenericAgentWriteVersion,Writer *,w)2294 ENTERPRISE_VOID_FUNC_1ARG_DEFINE_STUB(void, GenericAgentWriteVersion, Writer *, w)
2295 {
2296     WriterWriteF(w, "%s\n", NameVersion());
2297 }
2298 
2299 /*******************************************************************/
2300 
Version(void)2301 const char *Version(void)
2302 {
2303     return VERSION;
2304 }
2305 
2306 /*******************************************************************/
2307 
NameVersion(void)2308 const char *NameVersion(void)
2309 {
2310     return "CFEngine Core " VERSION;
2311 }
2312 
2313 /********************************************************************/
2314 
CleanPidFile(void)2315 static void CleanPidFile(void)
2316 {
2317     if (unlink(PIDFILE) != 0)
2318     {
2319         if (errno != ENOENT)
2320         {
2321             Log(LOG_LEVEL_ERR, "Unable to remove pid file '%s'. (unlink: %s)", PIDFILE, GetErrorStr());
2322         }
2323     }
2324 }
2325 
2326 /********************************************************************/
2327 
RegisterPidCleanup(void)2328 static void RegisterPidCleanup(void)
2329 {
2330     RegisterCleanupFunction(&CleanPidFile);
2331 }
2332 
2333 /********************************************************************/
2334 
WritePID(char * filename)2335 void WritePID(char *filename)
2336 {
2337     pthread_once(&pid_cleanup_once, RegisterPidCleanup);
2338 
2339     snprintf(PIDFILE, CF_BUFSIZE - 1, "%s%c%s", GetPidDir(), FILE_SEPARATOR, filename);
2340 
2341     FILE *fp = safe_fopen_create_perms(PIDFILE, "w", CF_PERMS_DEFAULT);
2342     if (fp == NULL)
2343     {
2344         Log(LOG_LEVEL_INFO, "Could not write to PID file '%s'. (fopen: %s)", filename, GetErrorStr());
2345         return;
2346     }
2347 
2348     fprintf(fp, "%ju\n", (uintmax_t)getpid());
2349 
2350     fclose(fp);
2351 }
2352 
ReadPID(char * filename)2353 pid_t ReadPID(char *filename)
2354 {
2355     char pidfile[PATH_MAX];
2356     snprintf(pidfile, PATH_MAX - 1, "%s%c%s", GetPidDir(), FILE_SEPARATOR, filename);
2357 
2358     if (access(pidfile, F_OK) != 0)
2359     {
2360         Log(LOG_LEVEL_VERBOSE, "PID file '%s' doesn't exist", pidfile);
2361         return -1;
2362     }
2363 
2364     FILE *fp = safe_fopen(pidfile, "r");
2365     if (fp == NULL)
2366     {
2367         Log(LOG_LEVEL_ERR, "Could not read PID file '%s' (fopen: %s)", filename, GetErrorStr());
2368         return -1;
2369     }
2370 
2371     intmax_t pid;
2372     if (fscanf(fp, "%jd", &pid) != 1)
2373     {
2374         Log(LOG_LEVEL_ERR, "Could not read PID from '%s'", pidfile);
2375         fclose(fp);
2376         return -1;
2377     }
2378     fclose(fp);
2379     return ((pid_t) pid);
2380 }
2381 
GenericAgentConfigParseArguments(GenericAgentConfig * config,int argc,char ** argv)2382 bool GenericAgentConfigParseArguments(GenericAgentConfig *config, int argc, char **argv)
2383 {
2384     if (argc == 0)
2385     {
2386         return true;
2387     }
2388 
2389     if (argc > 1)
2390     {
2391         return false;
2392     }
2393 
2394     GenericAgentConfigSetInputFile(config, NULL, argv[0]);
2395     MINUSF = true;
2396     return true;
2397 }
2398 
GenericAgentConfigParseWarningOptions(GenericAgentConfig * config,const char * warning_options)2399 bool GenericAgentConfigParseWarningOptions(GenericAgentConfig *config, const char *warning_options)
2400 {
2401     if (strlen(warning_options) == 0)
2402     {
2403         return false;
2404     }
2405 
2406     if (strcmp("error", warning_options) == 0)
2407     {
2408         config->agent_specific.common.parser_warnings_error |= PARSER_WARNING_ALL;
2409         return true;
2410     }
2411 
2412     const char *options_start = warning_options;
2413     bool warnings_as_errors = false;
2414 
2415     if (StringStartsWith(warning_options, "error="))
2416     {
2417         options_start = warning_options + strlen("error=");
2418         warnings_as_errors = true;
2419     }
2420 
2421     StringSet *warnings_set = StringSetFromString(options_start, ',');
2422     StringSetIterator it = StringSetIteratorInit(warnings_set);
2423     const char *warning_str = NULL;
2424     while ((warning_str = StringSetIteratorNext(&it)))
2425     {
2426         int warning = ParserWarningFromString(warning_str);
2427         if (warning == -1)
2428         {
2429             Log(LOG_LEVEL_ERR, "Unrecognized warning '%s'", warning_str);
2430             StringSetDestroy(warnings_set);
2431             return false;
2432         }
2433 
2434         if (warnings_as_errors)
2435         {
2436             config->agent_specific.common.parser_warnings_error |= warning;
2437         }
2438         else
2439         {
2440             config->agent_specific.common.parser_warnings |= warning;
2441         }
2442     }
2443 
2444     StringSetDestroy(warnings_set);
2445     return true;
2446 }
2447 
GenericAgentConfigParseColor(GenericAgentConfig * config,const char * mode)2448 bool GenericAgentConfigParseColor(GenericAgentConfig *config, const char *mode)
2449 {
2450     if (!mode || strcmp("auto", mode) == 0)
2451     {
2452         config->color = config->tty_interactive;
2453         return true;
2454     }
2455     else if (strcmp("always", mode) == 0)
2456     {
2457         config->color = true;
2458         return true;
2459     }
2460     else if (strcmp("never", mode) == 0)
2461     {
2462         config->color = false;
2463         return true;
2464     }
2465     else
2466     {
2467         Log(LOG_LEVEL_ERR, "Unrecognized color mode '%s'", mode);
2468         return false;
2469     }
2470 }
2471 
GetTTYInteractive(void)2472 bool GetTTYInteractive(void)
2473 {
2474     return isatty(0) || isatty(1) || isatty(2);
2475 }
2476 
GenericAgentConfigNewDefault(AgentType agent_type,bool tty_interactive)2477 GenericAgentConfig *GenericAgentConfigNewDefault(AgentType agent_type, bool tty_interactive)
2478 {
2479     GenericAgentConfig *config = xmalloc(sizeof(GenericAgentConfig));
2480 
2481     LoggingSetAgentType(CF_AGENTTYPES[agent_type]);
2482     config->agent_type = agent_type;
2483     config->tty_interactive = tty_interactive;
2484 
2485     const char *color_env = getenv("CFENGINE_COLOR");
2486     config->color = (color_env && strcmp(color_env, "1") == 0);
2487 
2488     config->bundlesequence = NULL;
2489 
2490     config->original_input_file = NULL;
2491     config->input_file = NULL;
2492     config->input_dir = NULL;
2493     config->tag_release_dir = NULL;
2494 
2495     config->check_not_writable_by_others = agent_type != AGENT_TYPE_COMMON;
2496     config->check_runnable = agent_type != AGENT_TYPE_COMMON;
2497     config->ignore_missing_bundles = false;
2498     config->ignore_missing_inputs = false;
2499     config->ignore_preferred_augments = false;
2500 
2501     config->heap_soft = NULL;
2502     config->heap_negated = NULL;
2503     config->ignore_locks = false;
2504 
2505     config->protocol_version = CF_PROTOCOL_UNDEFINED;
2506 
2507     config->agent_specific.agent.bootstrap_argument = NULL;
2508     config->agent_specific.agent.bootstrap_ip = NULL;
2509     config->agent_specific.agent.bootstrap_port = NULL;
2510     config->agent_specific.agent.bootstrap_host = NULL;
2511 
2512     /* By default we trust the network when bootstrapping. */
2513     config->agent_specific.agent.bootstrap_trust_server = true;
2514 
2515     /* By default we run promises.cf as the last step of boostrapping */
2516     config->agent_specific.agent.bootstrap_trigger_policy = true;
2517 
2518     /* Log classes */
2519     config->agent_specific.agent.report_class_log = false;
2520 
2521     switch (agent_type)
2522     {
2523     case AGENT_TYPE_COMMON:
2524         config->agent_specific.common.eval_functions = true;
2525         config->agent_specific.common.show_classes = NULL;
2526         config->agent_specific.common.show_variables = NULL;
2527         config->agent_specific.common.policy_output_format = GENERIC_AGENT_CONFIG_COMMON_POLICY_OUTPUT_FORMAT_NONE;
2528         /* Bitfields of warnings to be recorded, or treated as errors. */
2529         config->agent_specific.common.parser_warnings = PARSER_WARNING_ALL;
2530         config->agent_specific.common.parser_warnings_error = 0;
2531         break;
2532 
2533     case AGENT_TYPE_AGENT:
2534         config->agent_specific.agent.show_evaluated_classes = NULL;
2535         config->agent_specific.agent.show_evaluated_variables = NULL;
2536         break;
2537 
2538     default:
2539         break;
2540     }
2541 
2542     return config;
2543 }
2544 
GenericAgentConfigDestroy(GenericAgentConfig * config)2545 void GenericAgentConfigDestroy(GenericAgentConfig *config)
2546 {
2547     if (config != NULL)
2548     {
2549         RlistDestroy(config->bundlesequence);
2550         StringSetDestroy(config->heap_soft);
2551         StringSetDestroy(config->heap_negated);
2552         free(config->original_input_file);
2553         free(config->input_file);
2554         free(config->input_dir);
2555         free(config->tag_release_dir);
2556         free(config->agent_specific.agent.bootstrap_argument);
2557         free(config->agent_specific.agent.bootstrap_host);
2558         free(config->agent_specific.agent.bootstrap_ip);
2559         free(config->agent_specific.agent.bootstrap_port);
2560         free(config);
2561     }
2562 }
2563 
GenericAgentConfigApply(EvalContext * ctx,const GenericAgentConfig * config)2564 void GenericAgentConfigApply(EvalContext *ctx, const GenericAgentConfig *config)
2565 {
2566     assert(config != NULL);
2567 
2568     if (config->heap_soft)
2569     {
2570         StringSetIterator it = StringSetIteratorInit(config->heap_soft);
2571         const char *context = NULL;
2572         while ((context = StringSetIteratorNext(&it)))
2573         {
2574             Class *cls = EvalContextClassGet(ctx, NULL, context);
2575             if (cls && !cls->is_soft)
2576             {
2577                 FatalError(ctx, "You cannot use -D to define a reserved class");
2578             }
2579 
2580             EvalContextClassPutSoft(ctx, context, CONTEXT_SCOPE_NAMESPACE, "source=environment");
2581         }
2582     }
2583 
2584     if (config->heap_negated != NULL)
2585     {
2586         /* Takes ownership of heap_negated. */
2587         EvalContextSetNegatedClasses(ctx, config->heap_negated);
2588         ((GenericAgentConfig *)config)->heap_negated = NULL;
2589     }
2590 
2591     switch (LogGetGlobalLevel())
2592     {
2593     case LOG_LEVEL_DEBUG:
2594         EvalContextClassPutHard(ctx, "debug_mode", "cfe_internal,source=agent");
2595         EvalContextClassPutHard(ctx, "opt_debug", "cfe_internal,source=agent");
2596         // fall through
2597     case LOG_LEVEL_VERBOSE:
2598         EvalContextClassPutHard(ctx, "verbose_mode", "cfe_internal,source=agent");
2599         // fall through
2600     case LOG_LEVEL_INFO:
2601         EvalContextClassPutHard(ctx, "inform_mode", "cfe_internal,source=agent");
2602         break;
2603     default:
2604         break;
2605     }
2606 
2607     if (config->color)
2608     {
2609         LoggingSetColor(config->color);
2610     }
2611 
2612     if (config->agent_type == AGENT_TYPE_COMMON)
2613     {
2614         EvalContextSetEvalOption(ctx, EVAL_OPTION_FULL, false);
2615         if (config->agent_specific.common.eval_functions)
2616         {
2617             EvalContextSetEvalOption(ctx, EVAL_OPTION_EVAL_FUNCTIONS, true);
2618         }
2619     }
2620 
2621     EvalContextSetIgnoreLocks(ctx, config->ignore_locks);
2622 
2623     if (DONTDO)
2624     {
2625         EvalContextClassPutHard(ctx, "opt_dry_run", "cfe_internal,source=environment");
2626     }
2627 }
2628 
CheckAndGenerateFailsafe(const char * inputdir,const char * input_file)2629 bool CheckAndGenerateFailsafe(const char *inputdir, const char *input_file)
2630 {
2631     char failsafe_path[CF_BUFSIZE];
2632 
2633     if (strlen(inputdir) + strlen(input_file) > sizeof(failsafe_path) - 2)
2634     {
2635         Log(LOG_LEVEL_ERR,
2636             "Unable to generate path for %s/%s file. Path too long.",
2637             inputdir, input_file);
2638         /* We could create dynamically allocated buffer able to hold the
2639            whole content of the path but this should be unlikely that we
2640            will end up here. */
2641         return false;
2642     }
2643     snprintf(failsafe_path, CF_BUFSIZE - 1, "%s/%s", inputdir, input_file);
2644     MapName(failsafe_path);
2645 
2646     if (access(failsafe_path, R_OK) != 0)
2647     {
2648         return WriteBuiltinFailsafePolicyToPath(failsafe_path);
2649     }
2650     return true;
2651 }
2652 
GenericAgentConfigSetInputFile(GenericAgentConfig * config,const char * inputdir,const char * input_file)2653 void GenericAgentConfigSetInputFile(GenericAgentConfig *config, const char *inputdir, const char *input_file)
2654 {
2655     free(config->original_input_file);
2656     free(config->input_file);
2657     free(config->input_dir);
2658 
2659     config->original_input_file = xstrdup(input_file);
2660 
2661     if (inputdir && FilePathGetType(input_file) == FILE_PATH_TYPE_NON_ANCHORED)
2662     {
2663         config->input_file = StringFormat("%s%c%s", inputdir, FILE_SEPARATOR, input_file);
2664     }
2665     else
2666     {
2667         config->input_file = xstrdup(input_file);
2668     }
2669 
2670     config->input_dir = xstrdup(config->input_file);
2671     if (!ChopLastNode(config->input_dir))
2672     {
2673         free(config->input_dir);
2674         config->input_dir = xstrdup(".");
2675     }
2676 }
2677 
GenericAgentConfigSetBundleSequence(GenericAgentConfig * config,const Rlist * bundlesequence)2678 void GenericAgentConfigSetBundleSequence(GenericAgentConfig *config, const Rlist *bundlesequence)
2679 {
2680     RlistDestroy(config->bundlesequence);
2681     config->bundlesequence = RlistCopy(bundlesequence);
2682 }
2683 
GenericAgentPostLoadInit(const EvalContext * ctx)2684 bool GenericAgentPostLoadInit(const EvalContext *ctx)
2685 {
2686     const char *tls_ciphers =
2687         EvalContextVariableControlCommonGet(ctx, COMMON_CONTROL_TLS_CIPHERS);
2688     const char *tls_min_version =
2689         EvalContextVariableControlCommonGet(ctx, COMMON_CONTROL_TLS_MIN_VERSION);
2690 
2691     const char *system_log_level_str =
2692         EvalContextVariableControlCommonGet(ctx, COMMON_CONTROL_SYSTEM_LOG_LEVEL);
2693 
2694     LogLevel system_log_level = LogLevelFromString(system_log_level_str);
2695     if (system_log_level != LOG_LEVEL_NOTHING)
2696     {
2697         LogSetGlobalSystemLogLevel(system_log_level);
2698     }
2699 
2700     return cfnet_init(tls_min_version, tls_ciphers);
2701 }
2702 
SetupSignalsForAgent(void)2703 void SetupSignalsForAgent(void)
2704 {
2705     signal(SIGINT, HandleSignalsForAgent);
2706     signal(SIGTERM, HandleSignalsForAgent);
2707     signal(SIGBUS, HandleSignalsForAgent);
2708     signal(SIGHUP, SIG_IGN);
2709     signal(SIGPIPE, SIG_IGN);
2710     signal(SIGUSR1, HandleSignalsForAgent);
2711     signal(SIGUSR2, HandleSignalsForAgent);
2712 }
2713 
GenericAgentShowContextsFormatted(EvalContext * ctx,const char * regexp)2714 void GenericAgentShowContextsFormatted(EvalContext *ctx, const char *regexp)
2715 {
2716     assert(regexp != NULL);
2717 
2718     ClassTableIterator *iter = EvalContextClassTableIteratorNewGlobal(ctx, NULL, true, true);
2719 
2720     Seq *seq = SeqNew(1000, free);
2721 
2722     pcre *rx = CompileRegex(regexp);
2723 
2724     if (rx == NULL)
2725     {
2726         Log(LOG_LEVEL_ERR, "Sorry, we could not compile regular expression %s", regexp);
2727         return;
2728     }
2729 
2730     Class *cls = NULL;
2731     while ((cls = ClassTableIteratorNext(iter)) != NULL)
2732     {
2733         char *class_name = ClassRefToString(cls->ns, cls->name);
2734 
2735         if (!RegexPartialMatch(rx, class_name))
2736         {
2737             free(class_name);
2738             continue;
2739         }
2740 
2741         StringSet *tagset = cls->tags;
2742         Buffer *tagbuf = StringSetToBuffer(tagset, ',');
2743 
2744         char *line;
2745         xasprintf(&line, "%-60s %-40s %-40s", class_name, BufferData(tagbuf), NULL_TO_EMPTY_STRING(cls->comment));
2746         SeqAppend(seq, line);
2747 
2748         BufferDestroy(tagbuf);
2749         free(class_name);
2750     }
2751 
2752     pcre_free(rx);
2753 
2754     SeqSort(seq, StrCmpWrapper, NULL);
2755 
2756     printf("%-60s %-40s %-40s\n", "Class name", "Meta tags", "Comment");
2757 
2758     for (size_t i = 0; i < SeqLength(seq); i++)
2759     {
2760         const char *context = SeqAt(seq, i);
2761         printf("%s\n", context);
2762     }
2763 
2764     SeqDestroy(seq);
2765 
2766     ClassTableIteratorDestroy(iter);
2767 }
2768 
GenericAgentShowVariablesFormatted(EvalContext * ctx,const char * regexp)2769 void GenericAgentShowVariablesFormatted(EvalContext *ctx, const char *regexp)
2770 {
2771     assert(regexp != NULL);
2772 
2773     VariableTableIterator *iter = EvalContextVariableTableIteratorNew(ctx, NULL, NULL, NULL);
2774     Variable *v = NULL;
2775 
2776     Seq *seq = SeqNew(2000, free);
2777 
2778     pcre *rx = CompileRegex(regexp);
2779 
2780     if (rx == NULL)
2781     {
2782         Log(LOG_LEVEL_ERR, "Sorry, we could not compile regular expression %s", regexp);
2783         return;
2784     }
2785 
2786     while ((v = VariableTableIteratorNext(iter)))
2787     {
2788         char *varname = VarRefToString(VariableGetRef(v), true);
2789 
2790         if (!RegexPartialMatch(rx, varname))
2791         {
2792             free(varname);
2793             continue;
2794         }
2795 
2796         Writer *w = StringWriter();
2797 
2798         Rval var_rval = VariableGetRval(v, false);
2799         if (var_rval.type == RVAL_TYPE_CONTAINER)
2800         {
2801             JsonWriteCompact(w, RvalContainerValue(var_rval));
2802         }
2803         else
2804         {
2805             RvalWrite(w, var_rval);
2806         }
2807 
2808         const char *var_value;
2809         if (StringIsPrintable(StringWriterData(w)))
2810         {
2811             var_value = StringWriterData(w);
2812         }
2813         else
2814         {
2815             var_value = "<non-printable>";
2816         }
2817 
2818 
2819         Buffer *tagbuf = NULL;
2820         StringSet *tagset = VariableGetTags(v);
2821         if (tagset != NULL)
2822         {
2823             tagbuf = StringSetToBuffer(tagset, ',');
2824         }
2825         const char *comment = VariableGetComment(v);
2826 
2827         char *line;
2828         xasprintf(&line, "%-40s %-60s %-40s %-40s",
2829                   varname, var_value,
2830                   tagbuf != NULL ? BufferData(tagbuf) : "",
2831                   NULL_TO_EMPTY_STRING(comment));
2832 
2833         SeqAppend(seq, line);
2834 
2835         BufferDestroy(tagbuf);
2836         WriterClose(w);
2837         free(varname);
2838     }
2839 
2840     pcre_free(rx);
2841 
2842     SeqSort(seq, StrCmpWrapper, NULL);
2843 
2844     printf("%-40s %-60s %-40s %-40s\n", "Variable name", "Variable value", "Meta tags", "Comment");
2845 
2846     for (size_t i = 0; i < SeqLength(seq); i++)
2847     {
2848         const char *variable = SeqAt(seq, i);
2849         printf("%s\n", variable);
2850     }
2851 
2852     SeqDestroy(seq);
2853     VariableTableIteratorDestroy(iter);
2854 }
2855