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