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