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-serverd-functions.h>
26 #include <cf-serverd-enterprise-stubs.h>
27 
28 #include <server_access.h>
29 #include <client_code.h>
30 #include <server_code.h>
31 #include <server_transform.h>
32 #include <bootstrap.h>
33 #include <policy_server.h>
34 #include <scope.h>
35 #include <signals.h>
36 #include <systype.h>
37 #include <mutex.h>
38 #include <global_mutex.h>
39 #include <locks.h>
40 #include <exec_tools.h>
41 #include <unix.h>
42 #include <man.h>
43 #include <server_tls.h>                              /* ServerTLSInitialize */
44 #include <timeout.h>
45 #include <known_dirs.h>
46 #include <sysinfo.h>
47 #include <time_classes.h>
48 #include <connection_info.h>
49 #include <string_lib.h>
50 #include <file_lib.h>
51 #include <loading.h>
52 #include <printsize.h>
53 #include <cleanup.h>
54 #if HAVE_SYSTEMD_SD_DAEMON_H
55 #include <systemd/sd-daemon.h>          // sd_notifyf
56 #endif // HAVE_SYSTEMD_SD_DAEMON_H
57 
58 
59 #define WAIT_INCOMING_TIMEOUT 10
60 
61 /* see man:listen(3) */
62 #define DEFAULT_LISTEN_QUEUE_SIZE 128
63 #define MAX_LISTEN_QUEUE_SIZE 2048
64 
65 int NO_FORK = false; /* GLOBAL_A */
66 int GRACEFUL = 0;
67 
68 /*******************************************************************/
69 /* Command line option parsing                                     */
70 /*******************************************************************/
71 
72 static const char *const CF_SERVERD_SHORT_DESCRIPTION = "CFEngine file server daemon";
73 
74 static const char *const CF_SERVERD_MANPAGE_LONG_DESCRIPTION =
75         "cf-serverd is a socket listening daemon providing two services: it acts as a file server for remote file copying "
76         "and it allows an authorized cf-runagent to start a cf-agent run. cf-agent typically connects to a "
77         "cf-serverd instance to request updated policy code, but may also request additional files for download. "
78         "cf-serverd employs role based access control (defined in policy code) to authorize requests. "
79         "Note: this daemon reloads it's config when the SIGHUP signal is received.";
80 
81 static const struct option OPTIONS[] =
82 {
83     {"help", no_argument, 0, 'h'},
84     {"log-level", required_argument, 0, 'g'},
85     {"debug", no_argument, 0, 'd'},
86     {"verbose", no_argument, 0, 'v'},
87     {"version", no_argument, 0, 'V'},
88     {"file", required_argument, 0, 'f'},
89     {"define", required_argument, 0, 'D'},
90     {"negate", required_argument, 0, 'N'},
91     {"no-lock", no_argument, 0, 'K'},
92     {"inform", no_argument, 0, 'I'},
93     {"diagnostic", no_argument, 0, 'x'},
94     {"no-fork", no_argument, 0, 'F'},
95     {"ld-library-path", required_argument, 0, 'L'},
96     {"generate-avahi-conf", no_argument, 0, 'A'},
97     {"color", optional_argument, 0, 'C'},
98     {"timestamp", no_argument, 0, 'l'},
99     {"graceful-detach", optional_argument, 0, 't'},
100     /* Only long option for the rest */
101     {"ignore-preferred-augments", no_argument, 0, 0},
102     {NULL, 0, 0, '\0'}
103 };
104 
105 static const char *const HINTS[] =
106 {
107     "Print the help message",
108     "Specify how detailed logs should be. Possible values: 'error', 'warning', 'notice', 'info', 'verbose', 'debug'",
109     "Enable debugging output",
110     "Output verbose information about the behaviour of the agent",
111     "Output the version of the software",
112     "Specify an alternative input file than the default. This option is overridden by FILE if supplied as argument.",
113     "Define a list of comma separated classes to be defined at the start of execution",
114     "Define a list of comma separated classes to be undefined at the start of execution",
115     "Ignore locking constraints during execution (ifelapsed/expireafter) if \"too soon\" to run",
116     "Print basic information about changes made to the system, i.e. promises repaired",
117     "Activate internal diagnostics (developers only)",
118     "Run as a foreground processes (do not fork)",
119     "Set the internal value of LD_LIBRARY_PATH for child processes",
120     "Generates avahi configuration file to enable policy server to be discovered in the network",
121     "Enable colorized output. Possible values: 'always', 'auto', 'never'. If option is used, the default value is 'auto'",
122     "Log timestamps on each line of log output",
123     "Terminate gracefully on SIGHUP by detaching from systemd and waiting n seconds before terminating",
124     "Ignore def_preferred.json file in favor of def.json",
125     NULL
126 };
127 
128 #ifdef HAVE_AVAHI_CLIENT_CLIENT_H
129 #ifdef HAVE_AVAHI_COMMON_ADDRESS_H
GenerateAvahiConfig(const char * path)130 static int GenerateAvahiConfig(const char *path)
131 {
132     FILE *fout = safe_fopen(path, "w+");
133     if (fout == NULL)
134     {
135         Log(LOG_LEVEL_ERR, "Unable to open '%s'", path);
136         return -1;
137     }
138     Writer *writer = FileWriter(fout);
139     fprintf(fout, "<?xml version=\"1.0\" standalone='no'?>\n");
140     fprintf(fout, "<!DOCTYPE service-group SYSTEM \"avahi-service.dtd\">\n");
141     XmlComment(writer, "This file has been automatically generated by cf-serverd.");
142     XmlStartTag(writer, "service-group", 0);
143     FprintAvahiCfengineTag(fout);
144     XmlStartTag(writer, "service", 0);
145     XmlTag(writer, "type", "_cfenginehub._tcp",0);
146     DetermineCfenginePort();
147     XmlStartTag(writer, "port", 0);
148     WriterWriteF(writer, "%d", CFENGINE_PORT);
149     XmlEndTag(writer, "port");
150     XmlEndTag(writer, "service");
151     XmlEndTag(writer, "service-group");
152     fclose(fout);
153 
154     return 0;
155 }
156 #define SUPPORT_AVAHI_CONFIG
157 #endif
158 #endif
159 
CheckOpts(int argc,char ** argv)160 GenericAgentConfig *CheckOpts(int argc, char **argv)
161 {
162     extern char *optarg;
163     int c;
164     GenericAgentConfig *config = GenericAgentConfigNewDefault(AGENT_TYPE_SERVER, GetTTYInteractive());
165 
166     int longopt_idx;
167     while ((c = getopt_long(argc, argv, "dvIKf:g:D:N:VSxLFMhAC::lt::",
168                             OPTIONS, &longopt_idx))
169            != -1)
170     {
171         switch (c)
172         {
173         case 'f':
174             GenericAgentConfigSetInputFile(config, GetInputDir(), optarg);
175             MINUSF = true;
176             break;
177 
178         case 'd':
179             LogSetGlobalLevel(LOG_LEVEL_DEBUG);
180             NO_FORK = true;
181             break;
182 
183         case 'K':
184             config->ignore_locks = true;
185             break;
186 
187         case 'D':
188             {
189                 StringSet *defined_classes = StringSetFromString(optarg, ',');
190                 if (! config->heap_soft)
191                 {
192                     config->heap_soft = defined_classes;
193                 }
194                 else
195                 {
196                     StringSetJoin(config->heap_soft, defined_classes, xstrdup);
197                     StringSetDestroy(defined_classes);
198                 }
199             }
200             break;
201 
202         case 'N':
203             {
204                 StringSet *negated_classes = StringSetFromString(optarg, ',');
205                 if (! config->heap_negated)
206                 {
207                     config->heap_negated = negated_classes;
208                 }
209                 else
210                 {
211                     StringSetJoin(config->heap_negated, negated_classes, xstrdup);
212                     StringSetDestroy(negated_classes);
213                 }
214             }
215             break;
216 
217         case 'I':
218             LogSetGlobalLevel(LOG_LEVEL_INFO);
219             break;
220 
221         case 'v':
222             LogSetGlobalLevel(LOG_LEVEL_VERBOSE);
223             NO_FORK = true;
224             break;
225 
226         case 'g':
227             LogSetGlobalLevelArgOrExit(optarg);
228             break;
229 
230         case 'F':
231             NO_FORK = true;
232             break;
233 
234         case 'L':
235         {
236             Log(LOG_LEVEL_VERBOSE, "Setting LD_LIBRARY_PATH to '%s'", optarg);
237             setenv_wrapper("LD_LIBRARY_PATH", optarg, 1);
238             break;
239         }
240 
241         case 'V':
242             {
243                 Writer *w = FileWriter(stdout);
244                 GenericAgentWriteVersion(w);
245                 FileWriterDetach(w);
246             }
247             DoCleanupAndExit(EXIT_SUCCESS);
248 
249         case 'h':
250             {
251                 Writer *w = FileWriter(stdout);
252                 WriterWriteHelp(w, "cf-serverd", OPTIONS, HINTS, NULL, false, true);
253                 FileWriterDetach(w);
254             }
255             DoCleanupAndExit(EXIT_SUCCESS);
256 
257         case 'M':
258             {
259                 Writer *out = FileWriter(stdout);
260                 ManPageWrite(out, "cf-serverd", time(NULL),
261                              CF_SERVERD_SHORT_DESCRIPTION,
262                              CF_SERVERD_MANPAGE_LONG_DESCRIPTION,
263                              OPTIONS, HINTS,
264                              NULL, false,
265                              true);
266                 FileWriterDetach(out);
267                 DoCleanupAndExit(EXIT_SUCCESS);
268             }
269 
270         case 'x':
271             Log(LOG_LEVEL_ERR, "Self-diagnostic functionality is retired.");
272             DoCleanupAndExit(EXIT_SUCCESS);
273 
274         case 'A':
275 #ifdef SUPPORT_AVAHI_CONFIG
276             Log(LOG_LEVEL_NOTICE, "Generating Avahi configuration file.");
277             if (GenerateAvahiConfig("/etc/avahi/services/cfengine-hub.service") != 0)
278             {
279                 DoCleanupAndExit(EXIT_FAILURE);
280             }
281             cf_popen("/etc/init.d/avahi-daemon restart", "r", true);
282             Log(LOG_LEVEL_NOTICE, "Avahi configuration file generated successfully.");
283 #else
284             Log(LOG_LEVEL_ERR, "Generating avahi configuration can only be done when avahi-daemon and libavahi are installed on the machine.");
285 #endif
286             DoCleanupAndExit(EXIT_SUCCESS);
287 
288         case 'C':
289             if (!GenericAgentConfigParseColor(config, optarg))
290             {
291                 DoCleanupAndExit(EXIT_FAILURE);
292             }
293             break;
294 
295         case 'l':
296             LoggingEnableTimestamps(true);
297             break;
298 
299         case 't':
300             if (optarg == NULL)
301             {
302                 GRACEFUL = 60;
303             }
304             else
305             {
306                 GRACEFUL = StringToLongExitOnError(optarg);
307             }
308             break;
309 
310         /* long options only */
311         case 0:
312         {
313             const char *const option_name = OPTIONS[longopt_idx].name;
314             if (StringEqual(option_name, "ignore-preferred-augments"))
315             {
316                 config->ignore_preferred_augments = true;
317             }
318             break;
319         }
320         default:
321             {
322                 Writer *w = FileWriter(stdout);
323                 WriterWriteHelp(w, "cf-serverd", OPTIONS, HINTS, NULL, false, true);
324                 FileWriterDetach(w);
325             }
326             DoCleanupAndExit(EXIT_FAILURE);
327         }
328     }
329 
330     if (!GenericAgentConfigParseArguments(config, argc - optind, argv + optind))
331     {
332         Log(LOG_LEVEL_ERR, "Too many arguments");
333         DoCleanupAndExit(EXIT_FAILURE);
334     }
335 
336     return config;
337 }
338 
339 /*********************************************************************/
340 /* Policy Reloading                                                  */
341 /*********************************************************************/
342 
DeleteAuthList(Auth ** list,Auth ** list_tail)343 static void DeleteAuthList(Auth **list, Auth **list_tail)
344 {
345     Auth *ap = *list;
346 
347     while (ap != NULL)
348     {
349         Auth *ap_next = ap->next;
350 
351         DeleteItemList(ap->accesslist);
352         DeleteItemList(ap->maproot);
353         free(ap->path);
354         free(ap);
355 
356         /* Just make sure the tail was consistent. */
357         if (ap_next == NULL)
358             assert(ap == *list_tail);
359 
360         ap = ap_next;
361     }
362 
363     *list = NULL;
364     *list_tail = NULL;
365 }
366 
367 /* Must not be called unless ACTIVE_THREADS is zero: */
ClearAuthAndACLs(void)368 static void ClearAuthAndACLs(void)
369 {
370     /* Must have no currently open connections to free the ACLs. */
371     assert(SERVER_ACCESS.connectionlist == NULL);
372 
373     /* Bundle server access_rules legacy ACLs */
374     DeleteAuthList(&SERVER_ACCESS.admit, &SERVER_ACCESS.admittail);
375     DeleteAuthList(&SERVER_ACCESS.deny, &SERVER_ACCESS.denytail);
376     DeleteAuthList(&SERVER_ACCESS.varadmit, &SERVER_ACCESS.varadmittail);
377     DeleteAuthList(&SERVER_ACCESS.vardeny, &SERVER_ACCESS.vardenytail);
378 
379     /* body server control ACLs */
380     DeleteItemList(SERVER_ACCESS.trustkeylist);        SERVER_ACCESS.trustkeylist = NULL;
381     DeleteItemList(SERVER_ACCESS.attackerlist);        SERVER_ACCESS.attackerlist = NULL;
382     DeleteItemList(SERVER_ACCESS.nonattackerlist);     SERVER_ACCESS.nonattackerlist = NULL;
383     DeleteItemList(SERVER_ACCESS.allowuserlist);       SERVER_ACCESS.allowuserlist = NULL;
384     DeleteItemList(SERVER_ACCESS.multiconnlist);       SERVER_ACCESS.multiconnlist = NULL;
385     DeleteItemList(SERVER_ACCESS.allowuserlist);       SERVER_ACCESS.allowuserlist = NULL;
386     DeleteItemList(SERVER_ACCESS.allowlegacyconnects); SERVER_ACCESS.allowlegacyconnects = NULL;
387 
388     StringMapDestroy(SERVER_ACCESS.path_shortcuts);    SERVER_ACCESS.path_shortcuts  = NULL;
389     free(SERVER_ACCESS.allowciphers);                  SERVER_ACCESS.allowciphers    = NULL;
390     free(SERVER_ACCESS.allowtlsversion);               SERVER_ACCESS.allowtlsversion = NULL;
391 
392     /* body server control new ACLs */
393     NEED_REVERSE_LOOKUP = false;
394     acl_Free(paths_acl);    paths_acl    = NULL;
395     acl_Free(classes_acl);  classes_acl  = NULL;
396     acl_Free(vars_acl);     vars_acl     = NULL;
397     acl_Free(literals_acl); literals_acl = NULL;
398     acl_Free(query_acl);    query_acl    = NULL;
399     acl_Free(bundles_acl);  bundles_acl  = NULL;
400     acl_Free(roles_acl);    roles_acl    = NULL;
401 }
402 
CheckFileChanges(EvalContext * ctx,Policy ** policy,GenericAgentConfig * config)403 static void CheckFileChanges(EvalContext *ctx, Policy **policy, GenericAgentConfig *config)
404 {
405     Log(LOG_LEVEL_DEBUG, "Checking file updates for input file '%s'",
406         config->input_file);
407 
408     time_t validated_at = ReadTimestampFromPolicyValidatedFile(config, NULL);
409 
410     bool reload_config = false;
411 
412     if (config->agent_specific.daemon.last_validated_at < validated_at)
413     {
414         Log(LOG_LEVEL_VERBOSE, "New promises detected...");
415         reload_config = true;
416     }
417     if (ReloadConfigRequested())
418     {
419         Log(LOG_LEVEL_VERBOSE, "Force reload of inputs files...");
420         reload_config = true;
421     }
422 
423     if (reload_config)
424     {
425         ClearRequestReloadConfig();
426 
427         /* Rereading policies now, so update timestamp. */
428         config->agent_specific.daemon.last_validated_at = validated_at;
429 
430         if (GenericAgentArePromisesValid(config))
431         {
432             Log(LOG_LEVEL_NOTICE, "Rereading policy file '%s'",
433                 config->input_file);
434 
435             /* STEP 1: Free everything */
436 
437             EvalContextClear(ctx);
438 
439             strcpy(VDOMAIN, "undefined.domain");
440 
441             ClearAuthAndACLs();
442             PolicyDestroy(*policy);               *policy = NULL;
443 
444             /* STEP 2: Set Environment, Parse and Evaluate policy */
445 
446             /*
447              * TODO why is this done separately here? What's the difference to
448              * calling the same steps as in cf-serverd.c:main()? Those are:
449              *   GenericAgentConfigApply();     // not here!
450              *   GenericAgentDiscoverContext(); // not here!
451              *   EvalContextClassPutHard("server");             // only here!
452              *   if (GenericAgentCheckPolicy()) // not here!
453              *     policy = LoadPolicy();
454              *   ThisAgentInit();               // not here, only calls umask()
455              *   ReloadHAConfig();                              // only here!
456              *   KeepPromises();
457              *   Summarize();
458              * Plus the following from within StartServer() which is only
459              * called during startup:
460              *   InitSignals();                  // not here
461              *   ServerTLSInitialize();          // not here
462              *   SetServerListenState();         // not here
463              *   InitServer()                    // not here
464              *   PolicyNew()+AcquireServerLock() // not here
465              *   PrepareServer(sd);              // not here
466              *   CollectCallStart();  // both
467              */
468 
469             EvalContextSetPolicyServerFromFile(ctx, GetWorkDir());
470 
471             UpdateLastPolicyUpdateTime(ctx);
472 
473             DetectEnvironment(ctx);
474             GenericAgentDiscoverContext(ctx, config, NULL);
475 
476             /* During startup this is done in GenericAgentDiscoverContext(). */
477             EvalContextClassPutHard(ctx, CF_AGENTTYPES[AGENT_TYPE_SERVER], "cfe_internal,source=agent");
478 
479             time_t t = SetReferenceTime();
480             UpdateTimeClasses(ctx, t);
481 
482             /* TODO BUG: this modifies config, but previous config has not
483              * been reset/free'd. Ideally we would want LoadPolicy to not
484              * modify config at all, but only modify ctx. */
485             *policy = LoadPolicy(ctx, config);
486 
487             /* Reload HA related configuration */
488             ReloadHAConfig();
489 
490             KeepPromises(ctx, *policy, config);
491             Summarize();
492         }
493         else
494         {
495             Log(LOG_LEVEL_INFO, "File changes contain errors -- ignoring");
496         }
497     }
498     else
499     {
500         Log(LOG_LEVEL_DEBUG, "No new promises found");
501         EvalContextUpdateDumpReports(ctx);
502     }
503 }
504 
505 
506 /* Set up standard signal-handling. */
InitSignals()507 static void InitSignals()
508 {
509     MakeSignalPipe();
510 
511     signal(SIGINT, HandleSignalsForDaemon);
512     signal(SIGTERM, HandleSignalsForDaemon);
513     signal(SIGBUS, HandleSignalsForDaemon);
514     signal(SIGHUP, HandleSignalsForDaemon);
515     signal(SIGPIPE, SIG_IGN);
516     signal(SIGUSR1, HandleSignalsForDaemon);
517     signal(SIGUSR2, HandleSignalsForDaemon);
518 }
519 
520 /* Prepare synthetic agent promise and lock it. */
AcquireServerLock(EvalContext * ctx,GenericAgentConfig * config,Policy * server_policy)521 static CfLock AcquireServerLock(EvalContext *ctx,
522                                 GenericAgentConfig *config,
523                                 Policy *server_policy)
524 {
525     Promise *pp = NULL;
526     {
527         Bundle *bp = PolicyAppendBundle(server_policy, NamespaceDefault(),
528                                         "server_cfengine_bundle", "agent",
529                                         NULL, NULL);
530         BundleSection *sp = BundleAppendSection(bp, "server_cfengine");
531 
532         pp = BundleSectionAppendPromise(sp, config->input_file,
533                                         (Rval) { NULL, RVAL_TYPE_NOPROMISEE },
534                                         NULL, NULL);
535     }
536     assert(pp);
537 
538     return AcquireLock(ctx, pp->promiser, VUQNAME, CFSTARTTIME, 0, 1, pp, false);
539 }
540 
541 /* Final preparations for running as server */
PrepareServer(int sd)542 static void PrepareServer(int sd)
543 {
544     if (sd != -1)
545     {
546         Log(LOG_LEVEL_VERBOSE,
547             "Listening for connections on socket descriptor %d ...", sd);
548     }
549 
550     if (!NO_FORK)
551 #ifdef __MINGW32__
552     {
553         Log(LOG_LEVEL_VERBOSE,
554             "Windows does not support starting processes in the background - running in foreground");
555     }
556 #else
557     {
558         if (fork() != 0)                                        /* parent */
559         {
560             _exit(EXIT_SUCCESS);
561         }
562 
563         ActAsDaemon();
564     }
565 #endif
566 
567     /* Close sd on exec, needed for not passing the socket to cf-runagent
568      * spawned commands. */
569     SetCloseOnExec(sd, true);
570 
571     Log(LOG_LEVEL_NOTICE, "Server is starting...");
572     WritePID("cf-serverd.pid"); /* Arranges for cleanup() to tidy it away */
573 }
574 
575 #if HAVE_SYSTEMD_SD_DAEMON_H
576 /* Graceful Stop
577  * This runs a new main process that will die and that init can restart (as systemd can do).
578  * This can prevent systemd from killing us if there is a problem.
579  * But this makes it possible to finish handling connections while systemd tries to restart us.
580  * If there is no systemd make sure alternative init restarts us. */
GracefulStop()581 static void GracefulStop()
582 {
583     Log(LOG_LEVEL_NOTICE, "Stopping gracefully");
584     /* Fork twice and tell systemd to follow our grand child
585      * The child will exit and systemd will follow grand child
586      * The grand child will exit and systemd will ignore us */
587     int child_pipe[2];
588     if (pipe(child_pipe) == -1)
589     {
590         Log(LOG_LEVEL_ERR, "Cannot detach graceful process (no pipe)");
591         return;
592     }
593 #ifdef HAVE_SD_NOTIFY_BARRIER
594     unsigned char anything = 1;
595     int grand_child_pipe[2];
596     if (pipe(grand_child_pipe) == -1)
597     {
598         Log(LOG_LEVEL_ERR, "Cannot detach graceful process (no pipe)");
599         return;
600     }
601 #endif
602     pid_t child_pid = fork();
603     if (child_pid == 0)
604     {
605         /* Child */
606         /* double fork to reattach to init, otherwise it doesn't receive sigchild */
607         pid_t grand_child_pid = fork();
608         if (grand_child_pid == 0)
609         {
610             /* grand child */
611             /* Wait for systemd to follow us then exit. */
612 #ifdef HAVE_SD_NOTIFY_BARRIER
613             /* use sd_notify_barrier in parent to know when to exit on recent versions of systemd */
614             close(grand_child_pipe[1]);
615             read(grand_child_pipe[0], &anything, sizeof(anything));
616             close(grand_child_pipe[0]);
617 #else
618             /* use sleep synchronization on old systemd */
619             sleep(2);
620 #endif
621             exit(0);
622         }
623         else
624         {
625 #ifdef HAVE_SD_NOTIFY_BARRIER
626             /* not needed here */
627             close(grand_child_pipe[0]);
628             close(grand_child_pipe[1]);
629 #endif
630             /* first child */
631             /* Send grand child pid to parent then exit to give grand child to systemd */
632             close(child_pipe[0]);
633             write(child_pipe[1], &grand_child_pid, sizeof(grand_child_pid));
634             close(child_pipe[1]);
635             exit(0);
636         }
637     }
638     else
639     {
640         /* Parent */
641         pid_t grand_child_pid;
642         /* read grand child pid from first child */
643         close(child_pipe[1]);
644         read(child_pipe[0], &grand_child_pid, sizeof(grand_child_pid));
645         close(child_pipe[0]);
646         waitpid(child_pid, NULL, 0);
647         /* send it to systemd */
648         sd_notifyf(0, "MAINPID=%d", grand_child_pid);
649 #ifdef HAVE_SD_NOTIFY_BARRIER
650         sd_notify_barrier(0, 2 * 1000000);
651         /* notify grand child it can die */
652         close(grand_child_pipe[0]);
653         write(grand_child_pipe[0], &anything, sizeof(anything));
654         close(grand_child_pipe[1]);
655 #endif
656     }
657 }
658 #endif // HAVE_SYSTEMD_SD_DAEMON_H
659 
660 /* Wait for connection-handler threads to finish their work.
661  *
662  * @return Number of live threads remaining after waiting.
663  */
WaitOnThreads(int graceful_time)664 static int WaitOnThreads(int graceful_time)
665 {
666     int result = 1;
667     for (int i = graceful_time; i > 0; i--)
668     {
669         ThreadLock(cft_server_children);
670         result = ACTIVE_THREADS;
671         ThreadUnlock(cft_server_children);
672 
673         if (result == 0)
674         {
675             break;
676         }
677 
678         Log(LOG_LEVEL_VERBOSE,
679             "Waiting %ds for %d connection threads to finish",
680             i, result);
681 
682         sleep(1);
683     }
684 
685     if (result > 0)
686     {
687         Log(LOG_LEVEL_VERBOSE,
688             "There are %d connection threads left, exiting anyway",
689             result);
690     }
691     else
692     {
693         assert(result == 0);
694         Log(LOG_LEVEL_VERBOSE,
695             "All threads are done, cleaning up allocations");
696         ClearAuthAndACLs();
697         ServerTLSDeInitialize(NULL, NULL, NULL);
698     }
699 
700     return result;
701 }
702 
CollectCallIfDue(EvalContext * ctx)703 static void CollectCallIfDue(EvalContext *ctx)
704 {
705     /* Check whether we have established peering with a hub */
706     if (CollectCallHasPending())
707     {
708         extern int COLLECT_WINDOW;
709         int waiting_queue = 0;
710         int new_client = CollectCallGetPending(&waiting_queue);
711         assert(new_client >= 0);
712         if (waiting_queue > COLLECT_WINDOW)
713         {
714             Log(LOG_LEVEL_INFO,
715                 "Abandoning collect call attempt with queue longer than collect_window [%d > %d]",
716                 waiting_queue, COLLECT_WINDOW);
717             cf_closesocket(new_client);
718             CollectCallMarkProcessed();
719         }
720         else
721         {
722             ConnectionInfo *info = ConnectionInfoNew();
723             assert(info);
724             Log(LOG_LEVEL_DEBUG,
725                 "Hub has %d seconds to complete report collection (collect_window)", COLLECT_WINDOW);
726             ConnectionInfoSetSocket(info, new_client);
727             info->is_call_collect = true; /* Mark processed when done. */
728             ServerEntryPoint(ctx, PolicyServerGetIP(), info);
729         }
730     }
731 }
732 
733 /* Check for new policy just before spawning a thread.
734  *
735  * Server reconfiguration can only happen when no threads are active,
736  * so this is a good time to do it; but we do still have to check for
737  * running threads. */
PolicyUpdateIfSafe(EvalContext * ctx,Policy ** policy,GenericAgentConfig * config)738 static void PolicyUpdateIfSafe(EvalContext *ctx, Policy **policy,
739                                GenericAgentConfig *config)
740 {
741     ThreadLock(cft_server_children);
742     int prior = COLLECT_INTERVAL;
743     if (ACTIVE_THREADS == 0)
744     {
745         CheckFileChanges(ctx, policy, config);
746     }
747     ThreadUnlock(cft_server_children);
748 
749     /* Check for change in call-collect interval: */
750     if (prior != COLLECT_INTERVAL)
751     {
752         /* Start, stop or change schedule, as appropriate. */
753         CollectCallStart(COLLECT_INTERVAL);
754     }
755 }
756 
757 /* Try to accept a connection; handle if we get one. */
AcceptAndHandle(EvalContext * ctx,int sd)758 static void AcceptAndHandle(EvalContext *ctx, int sd)
759 {
760     /* TODO embed ConnectionInfo into ServerConnectionState. */
761     ConnectionInfo *info = ConnectionInfoNew(); /* Uses xcalloc() */
762 
763     info->ss_len = sizeof(info->ss);
764     info->sd = accept(sd, (struct sockaddr *) &info->ss, &info->ss_len);
765     if (info->sd == -1)
766     {
767         Log(LOG_LEVEL_INFO, "Error accepting connection (%s)", GetErrorStr());
768         ConnectionInfoDestroy(&info);
769         return;
770     }
771 
772     Log(LOG_LEVEL_DEBUG, "Socket descriptor returned from accept(): %d",
773         info->sd);
774 
775     /* Just convert IP address to string, no DNS lookup. */
776     char ipaddr[CF_MAX_IP_LEN] = "";
777     getnameinfo((const struct sockaddr *) &info->ss, info->ss_len,
778                 ipaddr, sizeof(ipaddr),
779                 NULL, 0, NI_NUMERICHOST);
780 
781     /* IPv4 mapped addresses (e.g. "::ffff:192.168.1.2") are
782      * hereby represented with their IPv4 counterpart. */
783     ServerEntryPoint(ctx, MapAddress(ipaddr), info);
784 }
785 
GetListenQueueSize(void)786 static size_t GetListenQueueSize(void)
787 {
788     const char *const queue_size_var = getenv("CF_SERVERD_LISTEN_QUEUE_SIZE");
789     if (queue_size_var != NULL)
790     {
791         long queue_size;
792         int ret = StringToLong(queue_size_var, &queue_size);
793         if ((ret == 0) && (queue_size > 0) && (queue_size <= MAX_LISTEN_QUEUE_SIZE))
794         {
795             return (size_t) queue_size;
796         }
797         Log(LOG_LEVEL_WARNING,
798             "$CF_SERVERD_LISTEN_QUEUE_SIZE = '%s' doesn't specify a valid number for listen queue size, "
799             "falling back to default (%d).",
800             queue_size_var, DEFAULT_LISTEN_QUEUE_SIZE);
801     }
802 
803     return DEFAULT_LISTEN_QUEUE_SIZE;
804 }
805 
806 /**
807  *  @retval >0 Number of threads still working
808  *  @retval 0  All threads are done
809  *  @retval -1 Server didn't run
810  */
StartServer(EvalContext * ctx,Policy ** policy,GenericAgentConfig * config)811 int StartServer(EvalContext *ctx, Policy **policy, GenericAgentConfig *config)
812 {
813     InitSignals();
814 
815     bool tls_init_ok = ServerTLSInitialize(NULL, NULL, NULL);
816     if (!tls_init_ok)
817     {
818         return -1;
819     }
820 
821     size_t queue_size = GetListenQueueSize();
822     int sd = SetServerListenState(ctx, queue_size, NULL, SERVER_LISTEN, &InitServer);
823 
824     /* Necessary for our use of select() to work in WaitForIncoming(): */
825     assert((size_t) sd < sizeof(fd_set) * CHAR_BIT &&
826            (size_t) GetSignalPipe() < sizeof(fd_set) * CHAR_BIT);
827 
828     Policy *server_cfengine_policy = PolicyNew();
829     CfLock thislock = AcquireServerLock(ctx, config, server_cfengine_policy);
830     if (thislock.lock == NULL)
831     {
832         PolicyDestroy(server_cfengine_policy);
833         if (sd >= 0)
834         {
835             cf_closesocket(sd);
836         }
837         return -1;
838     }
839 
840     PrepareServer(sd);
841     CollectCallStart(COLLECT_INTERVAL);
842 
843     while (!IsPendingTermination())
844     {
845         CollectCallIfDue(ctx);
846 
847         int selected = WaitForIncoming(sd, WAIT_INCOMING_TIMEOUT);
848 
849         Log(LOG_LEVEL_DEBUG, "select(): %d", selected);
850         if (selected == -1)
851         {
852             Log(LOG_LEVEL_ERR,
853                 "Error while waiting for connections. (select: %s)",
854                 GetErrorStr());
855             break;
856         }
857         else if (selected >= 0) /* timeout or success */
858         {
859             PolicyUpdateIfSafe(ctx, policy, config);
860 
861             /* Is there a new connection pending at our listening socket? */
862             if (selected > 0)
863             {
864                 AcceptAndHandle(ctx, sd);
865             }
866         } /* else: interrupted, maybe pending termination. */
867 #if HAVE_SYSTEMD_SD_DAEMON_H
868         /* if we have a reload config requested but not yet processed
869          * it means we still have clients, let's do a graceful restart */
870         if (ReloadConfigRequested() && GRACEFUL != 0)
871         {
872             Log(LOG_LEVEL_INFO, "Doing a Graceful restart");
873             break;
874         }
875 #endif
876     }
877     Log(LOG_LEVEL_NOTICE, "Cleaning up and exiting...");
878 
879     CollectCallStop();
880     if (sd != -1)
881     {
882         Log(LOG_LEVEL_VERBOSE, "Closing listening socket");
883         cf_closesocket(sd);                       /* Close listening socket */
884     }
885 
886     int threads_left;
887 
888 #if HAVE_SYSTEMD_SD_DAEMON_H
889     if (ReloadConfigRequested() && GRACEFUL != 0)
890     {
891         /* This is a graceful restart */
892         YieldCurrentLock(thislock); // must be done before restart
893         GracefulStop();
894         threads_left = WaitOnThreads(GRACEFUL);
895     }
896     else
897 #endif
898     {
899         /* This is a graceful exit, give 2 seconds chance to threads. */
900         threads_left = WaitOnThreads(2);
901         YieldCurrentLock(thislock); // can we do this one first too ?
902     }
903 
904     PolicyDestroy(server_cfengine_policy);
905 
906     return threads_left;
907 }
908