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