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