"Fossies" - the Fresh Open Source Software Archive

Member "cfengine-3.15.4/cf-serverd/cf-serverd-functions.c" (7 Jun 2021, 25488 Bytes) of package /linux/misc/cfengine-3.15.4.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "cf-serverd-functions.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 3.15.3_vs_3.15.4.

    1 /*
    2   Copyright 2019 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
  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 
  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 
  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: */
  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 
  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, NULL);
  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     }
  470 }
  471 
  472 
  473 /* Set up standard signal-handling. */
  474 static void InitSignals()
  475 {
  476     MakeSignalPipe();
  477 
  478     signal(SIGINT, HandleSignalsForDaemon);
  479     signal(SIGTERM, HandleSignalsForDaemon);
  480     signal(SIGBUS, HandleSignalsForDaemon);
  481     signal(SIGHUP, HandleSignalsForDaemon);
  482     signal(SIGPIPE, SIG_IGN);
  483     signal(SIGUSR1, HandleSignalsForDaemon);
  484     signal(SIGUSR2, HandleSignalsForDaemon);
  485 }
  486 
  487 /* Prepare synthetic agent promise and lock it. */
  488 static CfLock AcquireServerLock(EvalContext *ctx,
  489                                 GenericAgentConfig *config,
  490                                 Policy *server_policy)
  491 {
  492     Promise *pp = NULL;
  493     {
  494         Bundle *bp = PolicyAppendBundle(server_policy, NamespaceDefault(),
  495                                         "server_cfengine_bundle", "agent",
  496                                         NULL, NULL);
  497         PromiseType *tp = BundleAppendPromiseType(bp, "server_cfengine");
  498 
  499         pp = PromiseTypeAppendPromise(tp, config->input_file,
  500                                       (Rval) { NULL, RVAL_TYPE_NOPROMISEE },
  501                                       NULL, NULL);
  502     }
  503     assert(pp);
  504 
  505     return AcquireLock(ctx, pp->promiser, VUQNAME, CFSTARTTIME, 0, 1, pp, false);
  506 }
  507 
  508 /* Final preparations for running as server */
  509 static void PrepareServer(int sd)
  510 {
  511     if (sd != -1)
  512     {
  513         Log(LOG_LEVEL_VERBOSE,
  514             "Listening for connections on socket descriptor %d ...", sd);
  515     }
  516 
  517     if (!NO_FORK)
  518 #ifdef __MINGW32__
  519     {
  520         Log(LOG_LEVEL_VERBOSE,
  521             "Windows does not support starting processes in the background - running in foreground");
  522     }
  523 #else
  524     {
  525         if (fork() != 0)                                        /* parent */
  526         {
  527             _exit(EXIT_SUCCESS);
  528         }
  529 
  530         ActAsDaemon();
  531     }
  532 #endif
  533 
  534     /* Close sd on exec, needed for not passing the socket to cf-runagent
  535      * spawned commands. */
  536     SetCloseOnExec(sd, true);
  537 
  538     Log(LOG_LEVEL_NOTICE, "Server is starting...");
  539     WritePID("cf-serverd.pid"); /* Arranges for cleanup() to tidy it away */
  540 }
  541 
  542 /* Wait for connection-handler threads to finish their work.
  543  *
  544  * @return Number of live threads remaining after waiting.
  545  */
  546 static int WaitOnThreads()
  547 {
  548     int result = 1;
  549     for (int i = 2; i > 0; i--)
  550     {
  551         ThreadLock(cft_server_children);
  552         result = ACTIVE_THREADS;
  553         ThreadUnlock(cft_server_children);
  554 
  555         if (result == 0)
  556         {
  557             break;
  558         }
  559 
  560         Log(LOG_LEVEL_VERBOSE,
  561             "Waiting %ds for %d connection threads to finish",
  562             i, result);
  563 
  564         sleep(1);
  565     }
  566 
  567     if (result > 0)
  568     {
  569         Log(LOG_LEVEL_VERBOSE,
  570             "There are %d connection threads left, exiting anyway",
  571             result);
  572     }
  573     else
  574     {
  575         assert(result == 0);
  576         Log(LOG_LEVEL_VERBOSE,
  577             "All threads are done, cleaning up allocations");
  578         ClearAuthAndACLs();
  579         ServerTLSDeInitialize(NULL, NULL, NULL);
  580     }
  581 
  582     return result;
  583 }
  584 
  585 static void CollectCallIfDue(EvalContext *ctx)
  586 {
  587     /* Check whether we have established peering with a hub */
  588     if (CollectCallHasPending())
  589     {
  590         extern int COLLECT_WINDOW;
  591         int waiting_queue = 0;
  592         int new_client = CollectCallGetPending(&waiting_queue);
  593         assert(new_client >= 0);
  594         if (waiting_queue > COLLECT_WINDOW)
  595         {
  596             Log(LOG_LEVEL_INFO,
  597                 "Abandoning collect call attempt with queue longer than collect_window [%d > %d]",
  598                 waiting_queue, COLLECT_WINDOW);
  599             cf_closesocket(new_client);
  600             CollectCallMarkProcessed();
  601         }
  602         else
  603         {
  604             ConnectionInfo *info = ConnectionInfoNew();
  605             assert(info);
  606             Log(LOG_LEVEL_DEBUG,
  607                 "Hub has %d seconds to complete report collection (collect_window)", COLLECT_WINDOW);
  608             ConnectionInfoSetSocket(info, new_client);
  609             info->is_call_collect = true; /* Mark processed when done. */
  610             ServerEntryPoint(ctx, PolicyServerGetIP(), info);
  611         }
  612     }
  613 }
  614 
  615 /* Check for new policy just before spawning a thread.
  616  *
  617  * Server reconfiguration can only happen when no threads are active,
  618  * so this is a good time to do it; but we do still have to check for
  619  * running threads. */
  620 static void PolicyUpdateIfSafe(EvalContext *ctx, Policy **policy,
  621                                GenericAgentConfig *config)
  622 {
  623     ThreadLock(cft_server_children);
  624     int prior = COLLECT_INTERVAL;
  625     if (ACTIVE_THREADS == 0)
  626     {
  627         CheckFileChanges(ctx, policy, config);
  628     }
  629     ThreadUnlock(cft_server_children);
  630 
  631     /* Check for change in call-collect interval: */
  632     if (prior != COLLECT_INTERVAL)
  633     {
  634         /* Start, stop or change schedule, as appropriate. */
  635         CollectCallStart(COLLECT_INTERVAL);
  636     }
  637 }
  638 
  639 /* Try to accept a connection; handle if we get one. */
  640 static void AcceptAndHandle(EvalContext *ctx, int sd)
  641 {
  642     /* TODO embed ConnectionInfo into ServerConnectionState. */
  643     ConnectionInfo *info = ConnectionInfoNew(); /* Uses xcalloc() */
  644 
  645     info->ss_len = sizeof(info->ss);
  646     info->sd = accept(sd, (struct sockaddr *) &info->ss, &info->ss_len);
  647     if (info->sd == -1)
  648     {
  649         Log(LOG_LEVEL_INFO, "Error accepting connection (%s)", GetErrorStr());
  650         ConnectionInfoDestroy(&info);
  651         return;
  652     }
  653 
  654     Log(LOG_LEVEL_DEBUG, "Socket descriptor returned from accept(): %d",
  655         info->sd);
  656 
  657     /* Just convert IP address to string, no DNS lookup. */
  658     char ipaddr[CF_MAX_IP_LEN] = "";
  659     getnameinfo((const struct sockaddr *) &info->ss, info->ss_len,
  660                 ipaddr, sizeof(ipaddr),
  661                 NULL, 0, NI_NUMERICHOST);
  662 
  663     /* IPv4 mapped addresses (e.g. "::ffff:192.168.1.2") are
  664      * hereby represented with their IPv4 counterpart. */
  665     ServerEntryPoint(ctx, MapAddress(ipaddr), info);
  666 }
  667 
  668 static size_t GetListenQueueSize(void)
  669 {
  670     const char *const queue_size_var = getenv("CF_SERVERD_LISTEN_QUEUE_SIZE");
  671     if (queue_size_var != NULL)
  672     {
  673         long queue_size;
  674         int ret = StringToLong(queue_size_var, &queue_size);
  675         if ((ret == 0) && (queue_size > 0) && (queue_size <= MAX_LISTEN_QUEUE_SIZE))
  676         {
  677             return (size_t) queue_size;
  678         }
  679         Log(LOG_LEVEL_WARNING,
  680             "$CF_SERVERD_LISTEN_QUEUE_SIZE = '%s' doesn't specify a valid number for listen queue size, "
  681             "falling back to default (%d).",
  682             queue_size_var, DEFAULT_LISTEN_QUEUE_SIZE);
  683     }
  684 
  685     return DEFAULT_LISTEN_QUEUE_SIZE;
  686 }
  687 
  688 /**
  689  *  @retval >0 Number of threads still working
  690  *  @retval 0  All threads are done
  691  *  @retval -1 Server didn't run
  692  */
  693 int StartServer(EvalContext *ctx, Policy **policy, GenericAgentConfig *config)
  694 {
  695     InitSignals();
  696 
  697     bool tls_init_ok = ServerTLSInitialize(NULL, NULL, NULL);
  698     if (!tls_init_ok)
  699     {
  700         return -1;
  701     }
  702 
  703     size_t queue_size = GetListenQueueSize();
  704     int sd = SetServerListenState(ctx, queue_size, NULL, SERVER_LISTEN, &InitServer);
  705 
  706     /* Necessary for our use of select() to work in WaitForIncoming(): */
  707     assert(sd < sizeof(fd_set) * CHAR_BIT &&
  708            GetSignalPipe() < sizeof(fd_set) * CHAR_BIT);
  709 
  710     Policy *server_cfengine_policy = PolicyNew();
  711     CfLock thislock = AcquireServerLock(ctx, config, server_cfengine_policy);
  712     if (thislock.lock == NULL)
  713     {
  714         PolicyDestroy(server_cfengine_policy);
  715         if (sd >= 0)
  716         {
  717             cf_closesocket(sd);
  718         }
  719         return -1;
  720     }
  721 
  722     PrepareServer(sd);
  723     CollectCallStart(COLLECT_INTERVAL);
  724 
  725     while (!IsPendingTermination())
  726     {
  727         CollectCallIfDue(ctx);
  728 
  729         int selected = WaitForIncoming(sd, WAIT_INCOMING_TIMEOUT);
  730 
  731         Log(LOG_LEVEL_DEBUG, "select(): %d", selected);
  732         if (selected == -1)
  733         {
  734             Log(LOG_LEVEL_ERR,
  735                 "Error while waiting for connections. (select: %s)",
  736                 GetErrorStr());
  737             break;
  738         }
  739         else if (selected >= 0) /* timeout or success */
  740         {
  741             PolicyUpdateIfSafe(ctx, policy, config);
  742 
  743             /* Is there a new connection pending at our listening socket? */
  744             if (selected > 0)
  745             {
  746                 AcceptAndHandle(ctx, sd);
  747             }
  748         } /* else: interrupted, maybe pending termination. */
  749     }
  750     Log(LOG_LEVEL_NOTICE, "Cleaning up and exiting...");
  751 
  752     CollectCallStop();
  753     if (sd != -1)
  754     {
  755         Log(LOG_LEVEL_VERBOSE, "Closing listening socket");
  756         cf_closesocket(sd);                       /* Close listening socket */
  757     }
  758 
  759     /* This is a graceful exit, give 2 seconds chance to threads. */
  760     int threads_left = WaitOnThreads();
  761     YieldCurrentLock(thislock);
  762     PolicyDestroy(server_cfengine_policy);
  763 
  764     return threads_left;
  765 }