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