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