1 /*
2 Copyright 2021 Northern.tech AS
3
4 This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation; version 3.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
18
19 To the extent this program is licensed as part of the Enterprise
20 versions of CFEngine, the applicable Commercial Open Source License
21 (COSL) may apply to this file if you as a licensee so wish it. See
22 included file COSL.txt.
23 */
24
25 #include <cf-execd.h>
26
27 #ifndef __MINGW32__
28 #include <sys/types.h>
29 #include <sys/socket.h>
30 #include <sys/un.h>
31 #include <files_lib.h>
32 #include <cf-execd-runagent.h>
33 #include <files_names.h> /* ChopLastNode */
34
35 #ifndef AF_LOCAL
36 #define AF_LOCAL AF_UNIX
37 #endif
38
39 #endif
40
41 #include <cf-execd-runner.h>
42 #include <item_lib.h>
43 #include <known_dirs.h>
44 #include <man.h>
45 #include <ornaments.h>
46 #include <exec_tools.h>
47 #include <signals.h>
48 #include <processes_select.h>
49 #include <bootstrap.h>
50 #include <policy_server.h>
51 #include <sysinfo.h>
52 #include <timeout.h>
53 #include <time_classes.h>
54 #include <loading.h>
55 #include <printsize.h>
56 #include <cleanup.h>
57 #include <repair.h>
58 #include <dbm_api.h> /* CheckDBRepairFlagFile() */
59 #include <string_lib.h>
60 #include <acl_tools.h> /* AllowAccessForUsers() */
61
62 #include <cf-windows-functions.h>
63
64 #define CF_EXEC_IFELAPSED 0
65 #define CF_EXEC_EXPIREAFTER 1
66
67 #define CF_EXECD_RUNAGENT_SOCKET_NAME "runagent.socket"
68
69 /* The listen() queue doesn't need to be long, new connections are accepted
70 * quickly and handed over to forked child processes so a pile up means some
71 * serious problem and it's better to just throw such connections away. */
72 #define CF_EXECD_RUNAGENT_SOCKET_LISTEN_QUEUE 5
73
74 static bool PERFORM_DB_CHECK = false;
75 static int NO_FORK = false; /* GLOBAL_A */
76 static int ONCE = false; /* GLOBAL_A */
77 static int WINSERVICE = true; /* GLOBAL_A */
78
79 static char *RUNAGENT_SOCKET_DIR = NULL;
80
81 static pthread_attr_t threads_attrs; /* GLOBAL_T, initialized by pthread_attr_init */
82
83 /*******************************************************************/
84
85 static GenericAgentConfig *CheckOpts(int argc, char **argv);
86
87 void ThisAgentInit(void);
88 static bool ScheduleRun(EvalContext *ctx, Policy **policy, GenericAgentConfig *config,
89 ExecdConfig **execd_config, ExecConfig **exec_config);
90 #ifndef __MINGW32__
91 static pid_t LocalExecInFork(const ExecConfig *config);
92 static void Apoptosis(void);
93 static inline bool GetRunagentSocketInfo(struct sockaddr_un *sock_info);
94 static inline bool SetRunagentSocketACLs(char *sock_path, StringSet *allow_users);
95 #else
96 static bool LocalExecInThread(const ExecConfig *config);
97 #endif
98
99
100 /*******************************************************************/
101 /* Command line options */
102 /*******************************************************************/
103
104 static const char *const CF_EXECD_SHORT_DESCRIPTION =
105 "scheduling daemon for cf-agent";
106
107 static const char *const CF_EXECD_MANPAGE_LONG_DESCRIPTION =
108 "cf-execd is the scheduling daemon for cf-agent. It runs cf-agent locally according to a schedule specified in "
109 "policy code (executor control body). After a cf-agent run is completed, cf-execd gathers output from cf-agent, "
110 "and may be configured to email the output to a specified address. It may also be configured to splay (randomize) the "
111 "execution schedule to prevent synchronized cf-agent runs across a network. "
112 "Note: this daemon reloads it's config when the SIGHUP signal is received.";
113
114 static const struct option OPTIONS[] =
115 {
116 {"help", no_argument, 0, 'h'},
117 {"debug", no_argument, 0, 'd'},
118 {"verbose", no_argument, 0, 'v'},
119 {"dry-run", no_argument, 0, 'n'},
120 {"version", no_argument, 0, 'V'},
121 {"file", required_argument, 0, 'f'},
122 {"define", required_argument, 0, 'D'},
123 {"negate", required_argument, 0, 'N'},
124 {"no-lock", no_argument, 0, 'K'},
125 {"inform", no_argument, 0, 'I'},
126 {"diagnostic", no_argument, 0, 'x'},
127 {"log-level", required_argument, 0, 'g'},
128 {"no-fork", no_argument, 0, 'F'},
129 {"once", no_argument, 0, 'O'},
130 {"no-winsrv", no_argument, 0, 'W'},
131 {"ld-library-path", required_argument, 0, 'L'},
132 {"color", optional_argument, 0, 'C'},
133 {"timestamp", no_argument, 0, 'l'},
134 /* Only long option for the rest */
135 {"ignore-preferred-augments", no_argument, 0, 0},
136 {"skip-db-check", optional_argument, 0, 0 },
137 {"with-runagent-socket", required_argument, 0, 0},
138 {NULL, 0, 0, '\0'}
139 };
140
141 static const char *const HINTS[] =
142 {
143 "Print the help message",
144 "Enable debugging output",
145 "Output verbose information about the behaviour of cf-execd",
146 "All talk and no action mode - make no changes, only inform of promises not kept",
147 "Output the version of the software",
148 "Specify an alternative input file than the default. This option is overridden by FILE if supplied as argument.",
149 "Define a list of comma separated classes to be defined at the start of execution",
150 "Define a list of comma separated classes to be undefined at the start of execution",
151 "Ignore locking constraints during execution (ifelapsed/expireafter) if \"too soon\" to run",
152 "Print basic information about changes made to the system, i.e. promises repaired",
153 "Activate internal diagnostics (developers only)",
154 "Specify how detailed logs should be. Possible values: 'error', 'warning', 'notice', 'info', 'verbose', 'debug'",
155 "Run as a foreground processes (do not fork)",
156 "Run once and then exit (implies no-fork)",
157 "Do not run as a service on windows - use this when running from a command shell (CFEngine Nova only)",
158 "Set the internal value of LD_LIBRARY_PATH for child processes",
159 "Enable colorized output. Possible values: 'always', 'auto', 'never'. If option is used, the default value is 'auto'",
160 "Log timestamps on each line of log output",
161 "Ignore def_preferred.json file in favor of def.json",
162 "Do not run database integrity checks and repairs at startup",
163 "Specify the directory for the socket for runagent requests or 'no' to disable the socket",
164 NULL
165 };
166
167 /*****************************************************************************/
168
main(int argc,char * argv[])169 int main(int argc, char *argv[])
170 {
171 GenericAgentConfig *config = CheckOpts(argc, argv);
172 bool force_repair = CheckDBRepairFlagFile();
173 if (force_repair || PERFORM_DB_CHECK)
174 {
175 repair_lmdb_default(force_repair);
176 }
177
178 EvalContext *ctx = EvalContextNew();
179 GenericAgentConfigApply(ctx, config);
180
181 const char *program_invocation_name = argv[0];
182 const char *last_dir_sep = strrchr(program_invocation_name, FILE_SEPARATOR);
183 const char *program_name = (last_dir_sep != NULL ? last_dir_sep + 1 : program_invocation_name);
184 GenericAgentDiscoverContext(ctx, config, program_name);
185
186 Policy *policy = SelectAndLoadPolicy(config, ctx, false, false);
187
188 if (!policy)
189 {
190 Log(LOG_LEVEL_ERR, "Error reading CFEngine policy. Exiting...");
191 DoCleanupAndExit(EXIT_FAILURE);
192 }
193
194 GenericAgentPostLoadInit(ctx);
195 ThisAgentInit();
196
197 ExecConfig *exec_config = ExecConfigNew(!ONCE, ctx, policy);
198 ExecdConfig *execd_config = ExecdConfigNew(ctx, policy);
199 SetFacility(execd_config->log_facility);
200
201 #ifdef __MINGW32__
202 if (WINSERVICE)
203 {
204 NovaWin_StartExecService();
205 }
206 else
207 #endif /* __MINGW32__ */
208 {
209 StartServer(ctx, policy, config, &execd_config, &exec_config);
210 }
211
212 GenericAgentFinalize(ctx, config);
213 ExecConfigDestroy(exec_config);
214 ExecdConfigDestroy(execd_config);
215 free(RUNAGENT_SOCKET_DIR);
216 CallCleanupFunctions();
217 return 0;
218 }
219
220 /*****************************************************************************/
221 /* Level 1 */
222 /*****************************************************************************/
223
CheckOpts(int argc,char ** argv)224 static GenericAgentConfig *CheckOpts(int argc, char **argv)
225 {
226 extern char *optarg;
227 int c;
228
229 GenericAgentConfig *config = GenericAgentConfigNewDefault(AGENT_TYPE_EXECUTOR, GetTTYInteractive());
230
231
232 int longopt_idx;
233 while ((c = getopt_long(argc, argv, "dvnKIf:g:D:N:VxL:hFOV1gMWC::l",
234 OPTIONS, &longopt_idx))
235 != -1)
236 {
237 switch (c)
238 {
239 case 'f':
240 GenericAgentConfigSetInputFile(config, GetInputDir(), optarg);
241 MINUSF = true;
242 break;
243
244 case 'd':
245 LogSetGlobalLevel(LOG_LEVEL_DEBUG);
246 break;
247
248 case 'K':
249 config->ignore_locks = true;
250 break;
251
252 case 'D':
253 {
254 StringSet *defined_classes = StringSetFromString(optarg, ',');
255 if (! config->heap_soft)
256 {
257 config->heap_soft = defined_classes;
258 }
259 else
260 {
261 StringSetJoin(config->heap_soft, defined_classes, xstrdup);
262 StringSetDestroy(defined_classes);
263 }
264 }
265 break;
266
267 case 'N':
268 {
269 StringSet *negated_classes = StringSetFromString(optarg, ',');
270 if (! config->heap_negated)
271 {
272 config->heap_negated = negated_classes;
273 }
274 else
275 {
276 StringSetJoin(config->heap_negated, negated_classes, xstrdup);
277 StringSetDestroy(negated_classes);
278 }
279 }
280 break;
281
282 case 'I':
283 LogSetGlobalLevel(LOG_LEVEL_INFO);
284 break;
285
286 case 'v':
287 LogSetGlobalLevel(LOG_LEVEL_VERBOSE);
288 NO_FORK = true; // TODO: really?
289 break;
290
291 case 'g':
292 LogSetGlobalLevelArgOrExit(optarg);
293 break;
294
295 case 'n':
296 EVAL_MODE = EVAL_MODE_DRY_RUN;
297 config->ignore_locks = true;
298 break;
299
300 case 'L':
301 {
302 Log(LOG_LEVEL_VERBOSE, "Setting 'LD_LIBRARY_PATH=%s'", optarg);
303 setenv_wrapper("LD_LIBRARY_PATH", optarg, 1);
304 break;
305 }
306 case 'W':
307 WINSERVICE = false;
308 break;
309
310 case 'F':
311 NO_FORK = true;
312 break;
313
314 case 'O':
315 ONCE = true;
316 NO_FORK = true;
317 break;
318
319 case 'V':
320 {
321 Writer *w = FileWriter(stdout);
322 GenericAgentWriteVersion(w);
323 FileWriterDetach(w);
324 }
325 DoCleanupAndExit(EXIT_SUCCESS);
326
327 case 'h':
328 {
329 Writer *w = FileWriter(stdout);
330 WriterWriteHelp(w, "cf-execd", OPTIONS, HINTS, NULL, false, true);
331 FileWriterDetach(w);
332 }
333 DoCleanupAndExit(EXIT_SUCCESS);
334
335 case 'M':
336 {
337 Writer *out = FileWriter(stdout);
338 ManPageWrite(out, "cf-execd", time(NULL),
339 CF_EXECD_SHORT_DESCRIPTION,
340 CF_EXECD_MANPAGE_LONG_DESCRIPTION,
341 OPTIONS, HINTS,
342 NULL, false,
343 true);
344 FileWriterDetach(out);
345 DoCleanupAndExit(EXIT_SUCCESS);
346 }
347
348 case 'x':
349 Log(LOG_LEVEL_ERR, "Self-diagnostic functionality is retired.");
350 DoCleanupAndExit(EXIT_SUCCESS);
351
352 case 'C':
353 if (!GenericAgentConfigParseColor(config, optarg))
354 {
355 DoCleanupAndExit(EXIT_FAILURE);
356 }
357 break;
358
359 case 'l':
360 LoggingEnableTimestamps(true);
361 break;
362
363 case 0:
364 {
365 const char *const option_name = OPTIONS[longopt_idx].name;
366 if (StringEqual(option_name, "ignore-preferred-augments"))
367 {
368 config->ignore_preferred_augments = true;
369 }
370 else if (StringEqual(option_name, "skip-db-check"))
371 {
372 if (optarg == NULL)
373 {
374 PERFORM_DB_CHECK = false; // Skip (no arg), check = false
375 }
376 else if (StringEqual_IgnoreCase(optarg, "yes"))
377 {
378 PERFORM_DB_CHECK = false; // Skip = yes, check = false
379 }
380 else if (StringEqual_IgnoreCase(optarg, "no"))
381 {
382 PERFORM_DB_CHECK = true; // Skip = no, check = true
383 }
384 else
385 {
386 Log(LOG_LEVEL_ERR,
387 "Invalid argument for --skip-db-check(yes/no): '%s'",
388 optarg);
389 DoCleanupAndExit(EXIT_FAILURE);
390 }
391 }
392 else if (StringEqual(option_name, "with-runagent-socket"))
393 {
394 assert(optarg != NULL); /* required_argument */
395 RUNAGENT_SOCKET_DIR = xstrdup(optarg);
396 }
397
398 break;
399 }
400 default:
401 {
402 Writer *w = FileWriter(stdout);
403 WriterWriteHelp(w, "cf-execd", OPTIONS, HINTS, NULL, false, true);
404 FileWriterDetach(w);
405 }
406 DoCleanupAndExit(EXIT_FAILURE);
407
408 }
409 }
410
411 if (!GenericAgentConfigParseArguments(config, argc - optind, argv + optind))
412 {
413 Log(LOG_LEVEL_ERR, "Too many arguments");
414 DoCleanupAndExit(EXIT_FAILURE);
415 }
416
417 return config;
418 }
419
420 /*****************************************************************************/
421
ThisAgentInit(void)422 void ThisAgentInit(void)
423 {
424 umask(077);
425 }
426
427 /*****************************************************************************/
428
429
430 #ifndef __MINGW32__
431
UsingRunagentSocket()432 static inline bool UsingRunagentSocket()
433 {
434 /* No runagent socket dir specified (use the default) or a directory
435 * specified ("no" disables the functionality). */
436 return ((RUNAGENT_SOCKET_DIR == NULL) || (!StringEqual_IgnoreCase(RUNAGENT_SOCKET_DIR, "no")));
437 }
438
439 /**
440 * Sleep for the given number of seconds while handling requests from sockets.
441 *
442 * @return Whether to terminate (skip any further actions) or not.
443 */
HandleRequestsOrSleep(time_t seconds,const char * reason,int runagent_socket,const char * local_run_command)444 static bool HandleRequestsOrSleep(time_t seconds, const char *reason,
445 int runagent_socket, const char *local_run_command)
446 {
447 if (IsPendingTermination())
448 {
449 return true;
450 }
451
452 Log(LOG_LEVEL_VERBOSE, "Sleeping for %s %ju seconds", reason, (intmax_t) seconds);
453
454 if (runagent_socket >= 0)
455 {
456 time_t sleep_started = time(NULL);
457 struct timeval remaining = {seconds, 0};
458 while (remaining.tv_sec != 0)
459 {
460 fd_set rfds;
461 FD_ZERO(&rfds);
462 FD_SET(runagent_socket, &rfds);
463
464 int ret = select(runagent_socket + 1, &rfds, NULL, NULL, &remaining);
465 if ((ret == -1) && (errno != EINTR))
466 {
467 /* unexpected error */
468 Log(LOG_LEVEL_ERR, "Failed to sleep for %s using select(): %s",
469 reason, GetErrorStr());
470 }
471 else if (ret == 0)
472 {
473 /* timeout -- slept for the specified time */
474 remaining.tv_sec = 0;
475 }
476 else
477 {
478 /* runagent_socket ready or signal received (EINTR) */
479
480 // We are sleeping above, so make sure a terminating signal did not
481 // arrive during that time.
482 if (IsPendingTermination())
483 {
484 return true;
485 }
486
487 if (ret > 0)
488 {
489 assert(FD_ISSET(runagent_socket, &rfds));
490 int data_socket = accept(runagent_socket, NULL, NULL);
491 pid_t pid = fork();
492 if (pid == 0)
493 {
494 /* child */
495 signal(SIGPIPE, SIG_DFL);
496 HandleRunagentRequest(data_socket, local_run_command);
497 _exit(EXIT_SUCCESS);
498 }
499 else if (pid == -1)
500 {
501 /* error */
502 Log(LOG_LEVEL_ERR, "Failed to fork runagent request handler: %s",
503 GetErrorStr());
504 }
505 /* parent: nothing more to do, go back to sleep */
506 }
507
508 remaining.tv_sec = MAX(0, seconds - (time(NULL) - sleep_started));
509 }
510 }
511 }
512 else
513 {
514 sleep(seconds);
515 }
516
517 // We are sleeping above, so make sure a terminating signal did not
518 // arrive during that time.
519 if (IsPendingTermination())
520 {
521 return true;
522 }
523
524 return false;
525 }
526
CFExecdMainLoop(EvalContext * ctx,Policy ** policy,GenericAgentConfig * config,ExecdConfig ** execd_config,ExecConfig ** exec_config,int runagent_socket)527 static void CFExecdMainLoop(EvalContext *ctx, Policy **policy, GenericAgentConfig *config,
528 ExecdConfig **execd_config, ExecConfig **exec_config,
529 int runagent_socket)
530 {
531 bool terminate = false;
532 while (!IsPendingTermination())
533 {
534 /* reap child processes (if any) */
535 while (waitpid(-1, NULL, WNOHANG) > 0)
536 {
537 Log(LOG_LEVEL_DEBUG, "Reaped child process");
538 }
539
540 if (ScheduleRun(ctx, policy, config, execd_config, exec_config))
541 {
542 terminate = HandleRequestsOrSleep((*execd_config)->splay_time, "splay time",
543 runagent_socket, (*execd_config)->local_run_command);
544 if (terminate)
545 {
546 break;
547 }
548 pid_t child_pid = LocalExecInFork(*exec_config);
549 if (child_pid < 0)
550 {
551 Log(LOG_LEVEL_INFO,
552 "Unable to run agent in a fork, falling back to blocking execution");
553 LocalExec(*exec_config);
554 }
555 }
556 /* 1 Minute resolution is enough */
557 terminate = HandleRequestsOrSleep(CFPULSETIME, "pulse time", runagent_socket,
558 (*execd_config)->local_run_command);
559 if (terminate)
560 {
561 break;
562 }
563 }
564
565 /* Remove the runagent socket (if any). */
566 if (UsingRunagentSocket())
567 {
568 struct sockaddr_un sock_info;
569 if (GetRunagentSocketInfo(&sock_info))
570 {
571 unlink(sock_info.sun_path);
572 }
573 }
574 }
575
GetRunagentSocketInfo(struct sockaddr_un * sock_info)576 static inline bool GetRunagentSocketInfo(struct sockaddr_un *sock_info)
577 {
578 assert(sock_info != NULL);
579
580 memset(sock_info, 0, sizeof(*sock_info));
581
582 /* This can easily fail if GetStateDir() returns some long path,
583 * 'sock_info.sun_path' is limited to 140 characters or even
584 * fewer. "/var/cfengine/state" is fine, crazy long temporary state
585 * directories used in the tests are too long. */
586 int ret;
587 if (RUNAGENT_SOCKET_DIR == NULL)
588 {
589 ret = snprintf(sock_info->sun_path, sizeof(sock_info->sun_path) - 1,
590 "%s/cf-execd.sockets/"CF_EXECD_RUNAGENT_SOCKET_NAME, GetStateDir());
591 }
592 else
593 {
594 ret = snprintf(sock_info->sun_path, sizeof(sock_info->sun_path) - 1,
595 "%s/"CF_EXECD_RUNAGENT_SOCKET_NAME, RUNAGENT_SOCKET_DIR);
596 }
597 return ((ret > 0) && ((size_t) ret <= (sizeof(sock_info->sun_path) - 1)));
598 }
599
SetRunagentSocketACLs(char * sock_path,StringSet * allow_users)600 static inline bool SetRunagentSocketACLs(char *sock_path, StringSet *allow_users)
601 {
602 /* Allow access to the socket (rw) */
603 bool success = AllowAccessForUsers(sock_path, allow_users, true, false);
604
605 /* Need to ensure access to the parent folder too (rx) */
606 if (success)
607 {
608 ChopLastNode(sock_path);
609 success = AllowAccessForUsers(sock_path, allow_users, false, true);
610 }
611 return success;
612 }
613
SetupRunagentSocket(const ExecdConfig * execd_config)614 static int SetupRunagentSocket(const ExecdConfig *execd_config)
615 {
616 assert(execd_config != NULL);
617
618 int runagent_socket = -1;
619
620 struct sockaddr_un sock_info;
621 if (GetRunagentSocketInfo(&sock_info))
622 {
623 sock_info.sun_family = AF_LOCAL;
624
625 bool created;
626 MakeParentDirectory(sock_info.sun_path, true, &created);
627
628 /* Make sure the permissions are correct if the directory was created
629 * (note: this code doesn't run on Windows). */
630 if (created)
631 {
632 char *last_slash = strrchr(sock_info.sun_path, '/');
633 *last_slash = '\0';
634 chmod(sock_info.sun_path, (mode_t) 0750);
635 *last_slash = '/';
636 }
637
638 /* Remove potential left-overs from old processes. */
639 unlink(sock_info.sun_path);
640
641 runagent_socket = socket(AF_LOCAL, SOCK_STREAM, 0);
642 assert(runagent_socket >= 0);
643 }
644 if (runagent_socket < 0)
645 {
646 Log(LOG_LEVEL_ERR, "Failed to create socket for runagent requests");
647 }
648 else
649 {
650 int ret = bind(runagent_socket, (const struct sockaddr *) &sock_info, sizeof(sock_info));
651 assert(ret == 0);
652 if (ret == -1)
653 {
654 Log(LOG_LEVEL_ERR, "Failed to bind the runagent socket: %s", GetErrorStr());
655 close(runagent_socket);
656 runagent_socket = -1;
657 }
658 else
659 {
660 ret = listen(runagent_socket, CF_EXECD_RUNAGENT_SOCKET_LISTEN_QUEUE);
661 assert(ret == 0);
662 if (ret == -1)
663 {
664 Log(LOG_LEVEL_ERR, "Failed to listen on runagent socket: %s", GetErrorStr());
665 close(runagent_socket);
666 runagent_socket = -1;
667 }
668 }
669 if (StringSetSize(execd_config->runagent_allow_users) > 0)
670 {
671 bool success = SetRunagentSocketACLs(sock_info.sun_path,
672 execd_config->runagent_allow_users);
673 if (!success)
674 {
675 Log(LOG_LEVEL_ERR,
676 "Failed to allow runagent_socket_allow_users users access the runagent socket");
677 /* keep going anyway */
678 }
679 }
680 }
681 return runagent_socket;
682 }
683
684 #else /* ! __MINGW32__ */
685
686 /**
687 * Sleep if not pending termination and log a message.
688 *
689 * @note: #msg_format should include exactly one "%u".
690 */
MaybeSleepLog(LogLevel level,const char * msg_format,unsigned int seconds)691 static inline unsigned int MaybeSleepLog(LogLevel level, const char *msg_format, unsigned int seconds)
692 {
693 if (IsPendingTermination())
694 {
695 return seconds;
696 }
697
698 Log(level, msg_format, seconds);
699
700 return sleep(seconds);
701 }
702
CFExecdMainLoop(EvalContext * ctx,Policy ** policy,GenericAgentConfig * config,ExecdConfig ** execd_config,ExecConfig ** exec_config,ARG_UNUSED int runagent_socket)703 static void CFExecdMainLoop(EvalContext *ctx, Policy **policy, GenericAgentConfig *config,
704 ExecdConfig **execd_config, ExecConfig **exec_config,
705 ARG_UNUSED int runagent_socket)
706 {
707 while (!IsPendingTermination())
708 {
709 if (ScheduleRun(ctx, policy, config, execd_config, exec_config))
710 {
711 MaybeSleepLog(LOG_LEVEL_VERBOSE,
712 "Sleeping for splaytime %u seconds",
713 (*execd_config)->splay_time);
714
715 // We are sleeping above, so make sure a terminating signal did not
716 // arrive during that time.
717 if (IsPendingTermination())
718 {
719 break;
720 }
721 if (!LocalExecInThread(*exec_config))
722 {
723 Log(LOG_LEVEL_INFO,
724 "Unable to run agent in thread, falling back to blocking execution");
725 LocalExec(*exec_config);
726 }
727 }
728 /* 1 Minute resolution is enough */
729 MaybeSleepLog(LOG_LEVEL_VERBOSE, "Sleeping for pulse time %u seconds...", CFPULSETIME);
730 }
731 }
732 #endif /* ! __MINGW32__ */
733
734 /* Might be called back from NovaWin_StartExecService */
StartServer(EvalContext * ctx,Policy * policy,GenericAgentConfig * config,ExecdConfig ** execd_config,ExecConfig ** exec_config)735 void StartServer(EvalContext *ctx, Policy *policy, GenericAgentConfig *config, ExecdConfig **execd_config, ExecConfig **exec_config)
736 {
737 pthread_attr_init(&threads_attrs);
738 pthread_attr_setdetachstate(&threads_attrs, PTHREAD_CREATE_DETACHED);
739 pthread_attr_setstacksize(&threads_attrs, (size_t)2048*1024);
740
741 Banner("Starting executor");
742
743 #ifndef __MINGW32__
744 if (!ONCE)
745 {
746 /* Kill previous instances of cf-execd if those are still running */
747 Apoptosis();
748 }
749
750 time_t now = time(NULL);
751 if ((!NO_FORK) && (fork() != 0))
752 {
753 Log(LOG_LEVEL_INFO, "cf-execd starting %.24s", ctime(&now));
754 _exit(EXIT_SUCCESS);
755 }
756
757 if (!NO_FORK)
758 {
759 ActAsDaemon();
760 }
761
762 #else /* __MINGW32__ */
763
764 if (!NO_FORK)
765 {
766 Log(LOG_LEVEL_VERBOSE, "Windows does not support starting processes in the background - starting in foreground");
767 }
768
769 #endif
770
771 WritePID("cf-execd.pid");
772 signal(SIGINT, HandleSignalsForDaemon);
773 signal(SIGTERM, HandleSignalsForDaemon);
774 signal(SIGBUS, HandleSignalsForDaemon);
775 signal(SIGHUP, HandleSignalsForDaemon);
776 signal(SIGPIPE, SIG_IGN);
777 signal(SIGUSR1, HandleSignalsForDaemon);
778 signal(SIGUSR2, HandleSignalsForDaemon);
779
780 umask(077);
781
782 int runagent_socket = -1;
783
784 #ifndef __MINGW32__
785 if (UsingRunagentSocket())
786 {
787 runagent_socket = SetupRunagentSocket(*execd_config);
788 }
789 #endif
790
791 if (ONCE)
792 {
793 LocalExec(*exec_config);
794 CloseLog();
795 }
796 else
797 {
798 CFExecdMainLoop(ctx, &policy, config, execd_config, exec_config, runagent_socket);
799 }
800 PolicyDestroy(policy);
801 }
802
803 /*****************************************************************************/
804
805 #ifndef __MINGW32__
LocalExecInFork(const ExecConfig * config)806 static pid_t LocalExecInFork(const ExecConfig *config)
807 {
808 Log(LOG_LEVEL_VERBOSE, "Forking for exec_command execution");
809
810 pid_t pid = fork();
811 if (pid == -1)
812 {
813 Log(LOG_LEVEL_ERR, "Failed to fork for exec_command execution: %s",
814 GetErrorStr());
815 return -1;
816 }
817 else if (pid == 0)
818 {
819 /* child */
820 LocalExec(config);
821 Log(LOG_LEVEL_VERBOSE, "Finished exec_command execution, terminating the forked process");
822 _exit(0);
823 }
824 else
825 {
826 /* parent */
827 return pid;
828 }
829 }
830
831 #else
LocalExecThread(void * param)832 static void *LocalExecThread(void *param)
833 {
834 ExecConfig *config = (ExecConfig *) param;
835 LocalExec(config);
836 ExecConfigDestroy(config);
837
838 Log(LOG_LEVEL_VERBOSE, "Finished exec_command execution, terminating thread");
839 return NULL;
840 }
841
LocalExecInThread(const ExecConfig * config)842 static bool LocalExecInThread(const ExecConfig *config)
843 {
844 ExecConfig *thread_config = ExecConfigCopy(config);
845 pthread_t tid;
846
847 Log(LOG_LEVEL_VERBOSE, "Spawning thread for exec_command execution");
848 int ret = pthread_create(&tid, &threads_attrs, LocalExecThread, thread_config);
849 if (ret != 0)
850 {
851 ExecConfigDestroy(thread_config);
852 Log(LOG_LEVEL_ERR, "Failed to create thread (pthread_create: %s)",
853 GetErrorStr());
854 return false;
855 }
856
857 return true;
858 }
859 #endif /* ! __MINGW32__ */
860
861 #ifndef __MINGW32__
862
Apoptosis(void)863 static void Apoptosis(void)
864 {
865 char promiser_buf[CF_SMALLBUF];
866 snprintf(promiser_buf, sizeof(promiser_buf), "%s%ccf-execd",
867 GetBinDir(), FILE_SEPARATOR);
868
869 if (LoadProcessTable())
870 {
871 char myuid[PRINTSIZE(unsigned)];
872 xsnprintf(myuid, sizeof(myuid), "%u", (unsigned) getuid());
873
874 Rlist *owners = NULL;
875 RlistPrepend(&owners, myuid, RVAL_TYPE_SCALAR);
876
877 ProcessSelect process_select = PROCESS_SELECT_INIT;
878 process_select.owner = owners;
879 process_select.process_result = "process_owner";
880
881 Item *killlist = SelectProcesses(promiser_buf, &(process_select), true);
882 RlistDestroy(owners);
883
884 for (Item *ip = killlist; ip != NULL; ip = ip->next)
885 {
886 pid_t pid = ip->counter;
887
888 if (pid != getpid() && kill(pid, SIGTERM) < 0)
889 {
890 if (errno == ESRCH)
891 {
892 /* That's ok, process exited voluntarily */
893 }
894 else
895 {
896 Log(LOG_LEVEL_ERR, "Unable to kill stale cf-execd process pid=%d. (kill: %s)",
897 (int)pid, GetErrorStr());
898 }
899 }
900 }
901 }
902
903 ClearProcessTable();
904
905 Log(LOG_LEVEL_VERBOSE, "Pruning complete");
906 }
907
908 #endif
909
910 typedef enum
911 {
912 RELOAD_ENVIRONMENT,
913 RELOAD_FULL
914 } Reload;
915
CheckNewPromises(GenericAgentConfig * config)916 static Reload CheckNewPromises(GenericAgentConfig *config)
917 {
918 Log(LOG_LEVEL_DEBUG, "Checking file updates for input file '%s'", config->input_file);
919
920 time_t validated_at = ReadTimestampFromPolicyValidatedFile(config, NULL);
921
922 bool reload_config = false;
923
924 if (config->agent_specific.daemon.last_validated_at < validated_at)
925 {
926 Log(LOG_LEVEL_VERBOSE, "New promises detected...");
927 reload_config = true;
928 }
929 if (ReloadConfigRequested())
930 {
931 Log(LOG_LEVEL_VERBOSE, "Force reload of inputs files...");
932 reload_config = true;
933 }
934
935 if (reload_config)
936 {
937 ClearRequestReloadConfig();
938
939 /* Rereading policies now, so update timestamp. */
940 config->agent_specific.daemon.last_validated_at = validated_at;
941
942 if (GenericAgentArePromisesValid(config))
943 {
944 return RELOAD_FULL;
945 }
946 else
947 {
948 Log(LOG_LEVEL_INFO, "New promises file contains syntax errors -- ignoring");
949 }
950 }
951 else
952 {
953 Log(LOG_LEVEL_DEBUG, "No new promises found");
954 }
955
956 return RELOAD_ENVIRONMENT;
957 }
958
ScheduleRun(EvalContext * ctx,Policy ** policy,GenericAgentConfig * config,ExecdConfig ** execd_config,ExecConfig ** exec_config)959 static bool ScheduleRun(EvalContext *ctx, Policy **policy, GenericAgentConfig *config,
960 ExecdConfig **execd_config, ExecConfig **exec_config)
961 {
962 /*
963 * FIXME: this logic duplicates the one from cf-serverd.c. Unify ASAP.
964 */
965
966 if (CheckNewPromises(config) == RELOAD_FULL)
967 {
968 /* Full reload */
969
970 Log(LOG_LEVEL_INFO, "Re-reading promise file '%s'", config->input_file);
971
972 EvalContextClear(ctx);
973
974 strcpy(VDOMAIN, "undefined.domain");
975
976 PolicyDestroy(*policy);
977 *policy = NULL;
978
979 EvalContextSetPolicyServerFromFile(ctx, GetWorkDir());
980 UpdateLastPolicyUpdateTime(ctx);
981
982 DetectEnvironment(ctx);
983 GenericAgentDiscoverContext(ctx, config, NULL);
984
985 EvalContextClassPutHard(ctx, CF_AGENTTYPES[AGENT_TYPE_EXECUTOR], "cfe_internal,source=agent");
986
987 time_t t = SetReferenceTime();
988 UpdateTimeClasses(ctx, t);
989
990 GenericAgentConfigSetBundleSequence(config, NULL);
991
992 #ifndef __MINGW32__
993 /* Take over the runagent_socket_allow_users set for comparison. */
994 StringSet *old_runagent_allow_users = NULL;
995 if (UsingRunagentSocket())
996 {
997 old_runagent_allow_users = (*execd_config)->runagent_allow_users;
998 (*execd_config)->runagent_allow_users = NULL;
999 }
1000 #endif
1001
1002 *policy = LoadPolicy(ctx, config);
1003 ExecConfigDestroy(*exec_config);
1004 ExecdConfigDestroy(*execd_config);
1005
1006 *exec_config = ExecConfigNew(!ONCE, ctx, *policy);
1007 *execd_config = ExecdConfigNew(ctx, *policy);
1008
1009 #ifndef __MINGW32__
1010 if (UsingRunagentSocket())
1011 {
1012 /* Check if the old list and the new one differ. */
1013 if (!StringSetIsEqual(old_runagent_allow_users,
1014 (*execd_config)->runagent_allow_users))
1015 {
1016 struct sockaddr_un sock_info;
1017 if (GetRunagentSocketInfo(&sock_info))
1018 {
1019 bool success = SetRunagentSocketACLs(sock_info.sun_path,
1020 (*execd_config)->runagent_allow_users);
1021 if (!success)
1022 {
1023 Log(LOG_LEVEL_ERR,
1024 "Failed to allow new runagent_socket_allow_users users access the runagent socket"
1025 " (on policy reload)");
1026 /* keep going anyway */
1027 }
1028 }
1029 else
1030 {
1031 Log(LOG_LEVEL_ERR, "Failed to get runagent.socket path");
1032 }
1033 }
1034 StringSetDestroy(old_runagent_allow_users);
1035 }
1036 #endif
1037
1038 SetFacility((*execd_config)->log_facility);
1039 }
1040 else
1041 {
1042 /* Environment reload */
1043
1044 EvalContextClear(ctx);
1045
1046 DetectEnvironment(ctx);
1047
1048 time_t t = SetReferenceTime();
1049 UpdateTimeClasses(ctx, t);
1050 }
1051
1052 {
1053 StringSetIterator it = StringSetIteratorInit((*execd_config)->schedule);
1054 const char *time_context = NULL;
1055 while ((time_context = StringSetIteratorNext(&it)))
1056 {
1057 if (IsDefinedClass(ctx, time_context))
1058 {
1059 Log(LOG_LEVEL_VERBOSE, "Waking up the agent at %s ~ %s", ctime(&CFSTARTTIME), time_context);
1060 return true;
1061 }
1062 }
1063 }
1064
1065 Log(LOG_LEVEL_VERBOSE, "Nothing to do at %s", ctime(&CFSTARTTIME));
1066 return false;
1067 }
1068