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