"Fossies" - the Fresh Open Source Software Archive

Member "cfengine-3.15.4/cf-runagent/cf-runagent.c" (7 Jun 2021, 28260 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.

    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 <generic_agent.h>
   26 
   27 #include <known_dirs.h>
   28 #include <unix.h>
   29 #include <eval_context.h>
   30 #include <lastseen.h>
   31 #include <crypto.h>
   32 #include <files_names.h>
   33 #include <promises.h>
   34 #include <conversion.h>
   35 #include <vars.h>
   36 #include <client_code.h>
   37 #include <communication.h>
   38 #include <net.h>
   39 #include <string_lib.h>
   40 #include <rlist.h>
   41 #include <scope.h>
   42 #include <policy.h>
   43 #include <audit.h>
   44 #include <man.h>
   45 #include <connection_info.h>
   46 #include <addr_lib.h>
   47 #include <loading.h>
   48 #include <expand.h>                                 /* ProtocolVersionParse */
   49 #include <hash.h>
   50 #include <string_lib.h>
   51 #include <cleanup.h>
   52 
   53 #define CF_RA_EXIT_CODE_OTHER_ERR 101
   54 
   55 typedef enum
   56 {
   57     RUNAGENT_CONTROL_HOSTS,
   58     RUNAGENT_CONTROL_PORT_NUMBER,
   59     RUNAGENT_CONTROL_FORCE_IPV4,
   60     RUNAGENT_CONTROL_TRUSTKEY,
   61     RUNAGENT_CONTROL_ENCRYPT,
   62     RUNAGENT_CONTROL_BACKGROUND,
   63     RUNAGENT_CONTROL_MAX_CHILD,
   64     RUNAGENT_CONTROL_OUTPUT_TO_FILE,
   65     RUNAGENT_CONTROL_OUTPUT_DIRECTORY,
   66     RUNAGENT_CONTROL_TIMEOUT,
   67     RUNAGENT_CONTROL_NONE
   68 } RunagentControl;
   69 
   70 static void ThisAgentInit(void);
   71 static GenericAgentConfig *CheckOpts(int argc, char **argv);
   72 
   73 static void KeepControlPromises(EvalContext *ctx, const Policy *policy);
   74 static int HailServer(const EvalContext *ctx, const GenericAgentConfig *config, char *host);
   75 static void SendClassData(AgentConnection *conn);
   76 static int HailExec(AgentConnection *conn, char *peer);
   77 static FILE *NewStream(char *name);
   78 
   79 /*******************************************************************/
   80 /* Command line options                                            */
   81 /*******************************************************************/
   82 
   83 static const char *const CF_RUNAGENT_SHORT_DESCRIPTION =
   84     "activate cf-agent on a remote host";
   85 
   86 static const char *const CF_RUNAGENT_MANPAGE_LONG_DESCRIPTION =
   87     "cf-runagent connects to a list of running instances of "
   88     "cf-serverd. It allows foregoing the usual cf-execd schedule "
   89     "to activate cf-agent. Additionally, a user "
   90     "may send classes to be defined on the remote\n"
   91     "host. Two kinds of classes may be sent: classes to decide "
   92     "on which hosts cf-agent will be started, and classes that "
   93     "the user requests cf-agent should define on execution. "
   94     "The latter type is regulated by cf-serverd's role based access control.";
   95 
   96 static const struct option OPTIONS[] =
   97 {
   98     {"help", no_argument, 0, 'h'},
   99     {"background", optional_argument, 0, 'b'},
  100     {"debug", no_argument, 0, 'd'},
  101     {"verbose", no_argument, 0, 'v'},
  102     {"log-level", required_argument, 0, 'g'},
  103     {"dry-run", no_argument, 0, 'n'},
  104     {"version", no_argument, 0, 'V'},
  105     {"file", required_argument, 0, 'f'},
  106     {"define-class", required_argument, 0, 'D'},
  107     {"select-class", required_argument, 0, 's'},
  108     {"inform", no_argument, 0, 'I'},
  109     {"remote-options", required_argument, 0, 'o'},
  110     {"diagnostic", no_argument, 0, 'x'},
  111     {"hail", required_argument, 0, 'H'},
  112     {"interactive", no_argument, 0, 'i'},
  113     {"timeout", required_argument, 0, 't'},
  114     {"color", optional_argument, 0, 'C'},
  115     {"timestamp", no_argument, 0, 'l'},
  116     /* Only long option for the rest */
  117     {"log-modules", required_argument, 0, 0},
  118     {"remote-bundles", required_argument, 0, 0},
  119     {NULL, 0, 0, '\0'}
  120 };
  121 
  122 static const char *const HINTS[] =
  123 {
  124     "Print the help message",
  125     "Parallelize connections (50 by default)",
  126     "Enable debugging output",
  127     "Output verbose information about the behaviour of cf-runagent",
  128     "Specify how detailed logs should be. Possible values: 'error', 'warning', 'notice', 'info', 'verbose', 'debug'",
  129     "All talk and no action mode - make no changes, only inform of promises not kept",
  130     "Output the version of the software",
  131     "Specify an alternative input file than the default. This option is overridden by FILE if supplied as argument.",
  132     "Define a list of comma separated classes to be sent to a remote agent",
  133     "Define a list of comma separated classes to be used to select remote agents by constraint",
  134     "Print basic information about changes made to the system, i.e. promises repaired",
  135     "(deprecated)",
  136     "(deprecated)",
  137     "Hail the following comma-separated lists of hosts, overriding default list",
  138     "Enable interactive mode for key trust",
  139     "Connection timeout, seconds",
  140     "Enable colorized output. Possible values: 'always', 'auto', 'never'. If option is used, the default value is 'auto'",
  141     "Log timestamps on each line of log output",
  142     "Enable even more detailed debug logging for specific areas of the implementation. Use together with '-d'. Use --log-modules=help for a list of available modules",
  143     "Bundles to execute on the remote agent",
  144     NULL
  145 };
  146 
  147 extern const ConstraintSyntax CFR_CONTROLBODY[];
  148 
  149 int INTERACTIVE = false; /* GLOBAL_A */
  150 int OUTPUT_TO_FILE = false; /* GLOBAL_P */
  151 char OUTPUT_DIRECTORY[CF_BUFSIZE] = ""; /* GLOBAL_P */
  152 int BACKGROUND = false; /* GLOBAL_P GLOBAL_A */
  153 int MAXCHILD = 50; /* GLOBAL_P GLOBAL_A */
  154 
  155 const Rlist *HOSTLIST = NULL;                          /* GLOBAL_P GLOBAL_A */
  156 
  157 char   SENDCLASSES[CF_MAXVARSIZE] = "";                         /* GLOBAL_A */
  158 char DEFINECLASSES[CF_MAXVARSIZE] = "";                         /* GLOBAL_A */
  159 char REMOTEBUNDLES[CF_MAXVARSIZE] = "";
  160 
  161 /*****************************************************************************/
  162 
  163 /**
  164  * @param is_exit_code whether #remote_exit_status is a exit code directly
  165  *                     (#true) or a status from wait() (#false)
  166  */
  167 static inline void UpdateExitCode(int *exit_code, int remote_exit_status, bool one_host, bool is_exit_code)
  168 {
  169     assert(exit_code != NULL);
  170 
  171     if (one_host)
  172     {
  173         if (is_exit_code)
  174         {
  175             *exit_code = remote_exit_status;
  176             return;
  177         }
  178 
  179         if (WIFEXITED(remote_exit_status))
  180         {
  181             *exit_code = WEXITSTATUS(remote_exit_status);
  182             return;
  183         }
  184 
  185         *exit_code = CF_RA_EXIT_CODE_OTHER_ERR;
  186         return;
  187     }
  188 
  189     /* Other error should always take priority, otherwise, count failed remote
  190      * agent runs. */
  191     if ((*exit_code < CF_RA_EXIT_CODE_OTHER_ERR) &&
  192         (!WIFEXITED(remote_exit_status) || (WEXITSTATUS(remote_exit_status) != EXIT_SUCCESS)))
  193     {
  194         *exit_code = MIN(*exit_code + 1, 100);
  195     }
  196 }
  197 
  198 int main(int argc, char *argv[])
  199 {
  200 #if !defined(__MINGW32__)
  201     int count = 0;
  202     int status;
  203     int pid;
  204 #endif
  205 
  206     GenericAgentConfig *config = CheckOpts(argc, argv);
  207     EvalContext *ctx = EvalContextNew();
  208     GenericAgentConfigApply(ctx, config);
  209 
  210     const char *program_invocation_name = argv[0];
  211     const char *last_dir_sep = strrchr(program_invocation_name, FILE_SEPARATOR);
  212     const char *program_name = (last_dir_sep != NULL ? last_dir_sep + 1 : program_invocation_name);
  213     GenericAgentDiscoverContext(ctx, config, program_name);
  214 
  215     Policy *policy = LoadPolicy(ctx, config);
  216 
  217     GenericAgentPostLoadInit(ctx);
  218     ThisAgentInit();
  219 
  220     KeepControlPromises(ctx, policy);      // Set RUNATTR using copy
  221 
  222     /* Exit codes:
  223      * - exit code from the remote agent run if only 1 host specified
  224      * - number of failed remote agent runs up to 100 otherwise
  225      * - >100 in case of other errors */
  226     int exit_code = 0;
  227 
  228     if (BACKGROUND && INTERACTIVE)
  229     {
  230         Log(LOG_LEVEL_ERR, "You cannot specify background mode and interactive mode together");
  231         DoCleanupAndExit(CF_RA_EXIT_CODE_OTHER_ERR);
  232     }
  233 
  234 /* HvB */
  235     const bool one_host = (HOSTLIST != NULL) && (HOSTLIST->next == NULL);
  236     if (HOSTLIST)
  237     {
  238         const Rlist *rp = HOSTLIST;
  239         while (rp != NULL)
  240         {
  241 
  242 #ifdef __MINGW32__
  243             if (BACKGROUND)
  244             {
  245                 Log(LOG_LEVEL_VERBOSE,
  246                     "Windows does not support starting processes in the background - starting in foreground");
  247                 BACKGROUND = false;
  248             }
  249 #else
  250             if (BACKGROUND)     /* parallel */
  251             {
  252                 if (count < MAXCHILD)
  253                 {
  254                     if (fork() == 0)    /* child process */
  255                     {
  256                         int remote_exit_code = HailServer(ctx, config, RlistScalarValue(rp));
  257                         DoCleanupAndExit(remote_exit_code  > 0 ? remote_exit_code : CF_RA_EXIT_CODE_OTHER_ERR);
  258                     }
  259                     else        /* parent process */
  260                     {
  261                         rp = rp->next;
  262                         count++;
  263                     }
  264                 }
  265                 else
  266                 {
  267                     pid = wait(&status);
  268                     Log(LOG_LEVEL_DEBUG, "child = %d, child number = %d", pid, count);
  269                     count--;
  270                     UpdateExitCode(&exit_code, status, one_host, false);
  271                 }
  272             }
  273             else                /* serial */
  274 #endif /* __MINGW32__ */
  275             {
  276                 int remote_exit_code = HailServer(ctx, config, RlistScalarValue(rp));
  277                 UpdateExitCode(&exit_code, remote_exit_code, one_host, true);
  278                 rp = rp->next;
  279             }
  280         }                       /* end while */
  281     }                           /* end if HOSTLIST */
  282 
  283 #ifndef __MINGW32__
  284     if (BACKGROUND)
  285     {
  286         Log(LOG_LEVEL_NOTICE, "Waiting for child processes to finish");
  287         while (count > 0)
  288         {
  289             pid = wait(&status);
  290             Log(LOG_LEVEL_VERBOSE, "Child %d ended, number %d", pid, count);
  291             count--;
  292             UpdateExitCode(&exit_code, status, one_host, false);
  293         }
  294     }
  295 #endif
  296 
  297     PolicyDestroy(policy);
  298     GenericAgentFinalize(ctx, config);
  299 
  300     CallCleanupFunctions();
  301     return exit_code;
  302 }
  303 
  304 /*******************************************************************/
  305 
  306 static GenericAgentConfig *CheckOpts(int argc, char **argv)
  307 {
  308     extern char *optarg;
  309     int c;
  310     GenericAgentConfig *config = GenericAgentConfigNewDefault(AGENT_TYPE_RUNAGENT, GetTTYInteractive());
  311 
  312     DEFINECLASSES[0] = '\0';
  313     SENDCLASSES[0]   = '\0';
  314     REMOTEBUNDLES[0] = '\0';
  315 
  316     int longopt_idx;
  317     while ((c = getopt_long(argc, argv, "t:q:db::vnKhIif:g:D:VSxo:s:MH:C::l",
  318                             OPTIONS, &longopt_idx))
  319            != -1)
  320     {
  321         switch (c)
  322         {
  323         case 'f':
  324             GenericAgentConfigSetInputFile(config, GetInputDir(), optarg);
  325             MINUSF = true;
  326             break;
  327 
  328         case 'b':
  329             BACKGROUND = true;
  330             if (optarg)
  331             {
  332                 MAXCHILD = StringToLongExitOnError(optarg);
  333             }
  334             break;
  335 
  336         case 'd':
  337             LogSetGlobalLevel(LOG_LEVEL_DEBUG);
  338             break;
  339 
  340         case 'K':
  341             config->ignore_locks = true;
  342             break;
  343 
  344         case 's':
  345         {
  346             size_t len = strlen(SENDCLASSES);
  347             StrCatDelim(SENDCLASSES, sizeof(SENDCLASSES), &len,
  348                         optarg, ',');
  349             if (len >= sizeof(SENDCLASSES))
  350             {
  351                 Log(LOG_LEVEL_ERR, "Argument too long (-s)");
  352                 DoCleanupAndExit(EXIT_FAILURE);
  353             }
  354             break;
  355         }
  356         case 'D':
  357         {
  358             size_t len = strlen(DEFINECLASSES);
  359             StrCatDelim(DEFINECLASSES, sizeof(DEFINECLASSES), &len,
  360                         optarg, ',');
  361             if (len >= sizeof(DEFINECLASSES))
  362             {
  363                 Log(LOG_LEVEL_ERR, "Argument too long (-D)");
  364                 DoCleanupAndExit(EXIT_FAILURE);
  365             }
  366             break;
  367         }
  368         case 'H':
  369             HOSTLIST = RlistFromSplitString(optarg, ',');
  370             break;
  371 
  372         case 'o':
  373             Log(LOG_LEVEL_ERR, "Option \"-o\" has been deprecated,"
  374                 " you can not pass arbitrary arguments to remote cf-agent");
  375             DoCleanupAndExit(EXIT_FAILURE);
  376             break;
  377 
  378         case 'I':
  379             LogSetGlobalLevel(LOG_LEVEL_INFO);
  380             break;
  381 
  382         case 'i':
  383             INTERACTIVE = true;
  384             break;
  385 
  386         case 'v':
  387             LogSetGlobalLevel(LOG_LEVEL_VERBOSE);
  388             break;
  389 
  390         case 'g':
  391             LogSetGlobalLevelArgOrExit(optarg);
  392             break;
  393 
  394         case 'n':
  395             DONTDO = true;
  396             config->ignore_locks = true;
  397             break;
  398 
  399         case 't':
  400             CONNTIMEOUT = StringToLongExitOnError(optarg);
  401             break;
  402 
  403         case 'V':
  404         {
  405             Writer *w = FileWriter(stdout);
  406             GenericAgentWriteVersion(w);
  407             FileWriterDetach(w);
  408         }
  409         DoCleanupAndExit(EXIT_SUCCESS);
  410 
  411         case 'h':
  412         {
  413             Writer *w = FileWriter(stdout);
  414             WriterWriteHelp(w, "cf-runagent", OPTIONS, HINTS, NULL, false, true);
  415             FileWriterDetach(w);
  416         }
  417         DoCleanupAndExit(EXIT_SUCCESS);
  418 
  419         case 'M':
  420         {
  421             Writer *out = FileWriter(stdout);
  422             ManPageWrite(out, "cf-runagent", time(NULL),
  423                          CF_RUNAGENT_SHORT_DESCRIPTION,
  424                          CF_RUNAGENT_MANPAGE_LONG_DESCRIPTION,
  425                          OPTIONS, HINTS,
  426                          NULL, false,
  427                          true);
  428             FileWriterDetach(out);
  429             DoCleanupAndExit(EXIT_SUCCESS);
  430         }
  431 
  432         case 'x':
  433             Log(LOG_LEVEL_ERR, "Option \"-x\" has been deprecated");
  434             DoCleanupAndExit(EXIT_FAILURE);
  435 
  436         case 'C':
  437             if (!GenericAgentConfigParseColor(config, optarg))
  438             {
  439                 DoCleanupAndExit(EXIT_FAILURE);
  440             }
  441             break;
  442 
  443         case 'l':
  444             LoggingEnableTimestamps(true);
  445             break;
  446 
  447         /* long options only */
  448         case 0:
  449 
  450             if (strcmp(OPTIONS[longopt_idx].name, "log-modules") == 0)
  451             {
  452                 bool ret = LogEnableModulesFromString(optarg);
  453                 if (!ret)
  454                 {
  455                     DoCleanupAndExit(EXIT_FAILURE);
  456                 }
  457             }
  458             else if (strcmp(OPTIONS[longopt_idx].name, "remote-bundles") == 0)
  459             {
  460                 size_t len = strlen(REMOTEBUNDLES);
  461                 StrCatDelim(REMOTEBUNDLES, sizeof(REMOTEBUNDLES), &len,
  462                             optarg, ',');
  463                 if (len >= sizeof(REMOTEBUNDLES))
  464                 {
  465                     Log(LOG_LEVEL_ERR, "Argument too long (--remote-bundles)");
  466                     DoCleanupAndExit(EXIT_FAILURE);
  467                 }
  468             }
  469             break;
  470 
  471         default:
  472         {
  473             Writer *w = FileWriter(stdout);
  474             WriterWriteHelp(w, "cf-runagent", OPTIONS, HINTS, NULL, false, true);
  475             FileWriterDetach(w);
  476         }
  477         DoCleanupAndExit(EXIT_FAILURE);
  478 
  479         }
  480     }
  481 
  482     if (!GenericAgentConfigParseArguments(config, argc - optind, argv + optind))
  483     {
  484         Log(LOG_LEVEL_ERR, "Too many arguments");
  485         DoCleanupAndExit(EXIT_FAILURE);
  486     }
  487 
  488     return config;
  489 }
  490 
  491 /*******************************************************************/
  492 
  493 static void ThisAgentInit(void)
  494 {
  495     umask(077);
  496 }
  497 
  498 /********************************************************************/
  499 
  500 static int HailServer(const EvalContext *ctx, const GenericAgentConfig *config, char *host)
  501 {
  502     assert(host != NULL);
  503 
  504     AgentConnection *conn;
  505     char hostkey[CF_HOSTKEY_STRING_SIZE], user[CF_SMALLBUF];
  506     bool gotkey;
  507     char reply[8];
  508     bool trustkey = false;
  509 
  510     char *hostname, *port;
  511     ParseHostPort(host, &hostname, &port);
  512 
  513     if (hostname == NULL)
  514     {
  515         Log(LOG_LEVEL_INFO, "No remote hosts were specified to connect to");
  516         return -1;
  517     }
  518     if (port == NULL)
  519     {
  520         port = "5308";
  521     }
  522 
  523     char ipaddr[CF_MAX_IP_LEN];
  524     if (Hostname2IPString(ipaddr, hostname, sizeof(ipaddr)) == -1)
  525     {
  526         Log(LOG_LEVEL_ERR,
  527             "HailServer: ERROR, could not resolve '%s'", hostname);
  528         return -1;
  529     }
  530 
  531     Address2Hostkey(hostkey, sizeof(hostkey), ipaddr);
  532     GetCurrentUserName(user, sizeof(user));
  533 
  534     if (INTERACTIVE)
  535     {
  536         Log(LOG_LEVEL_VERBOSE, "Using interactive key trust...");
  537 
  538         gotkey = HavePublicKey(user, ipaddr, hostkey) != NULL;
  539         if (!gotkey)
  540         {
  541             /* TODO print the hash of the connecting host. But to do that we
  542              * should open the connection first, and somehow pass that hash
  543              * here! redmine#7212 */
  544             printf("WARNING - You do not have a public key from host %s = %s\n",
  545                    hostname, ipaddr);
  546             printf("          Do you want to accept one on trust? (yes/no)\n\n--> ");
  547 
  548             while (true)
  549             {
  550                 if (fgets(reply, sizeof(reply), stdin) == NULL)
  551                 {
  552                     FatalError(ctx, "EOF trying to read answer from terminal");
  553                 }
  554 
  555                 if (Chop(reply, CF_EXPANDSIZE) == -1)
  556                 {
  557                     Log(LOG_LEVEL_ERR, "Chop was called on a string that seemed to have no terminator");
  558                 }
  559 
  560                 if (strcmp(reply, "yes") == 0)
  561                 {
  562                     printf("Will trust the key...\n");
  563                     trustkey = true;
  564                     break;
  565                 }
  566                 else if (strcmp(reply, "no") == 0)
  567                 {
  568                     printf("Will not trust the key...\n");
  569                     trustkey = false;
  570                     break;
  571                 }
  572                 else
  573                 {
  574                     printf("Please reply yes or no...(%s)\n", reply);
  575                 }
  576             }
  577         }
  578     }
  579 
  580 
  581 #ifndef __MINGW32__
  582     if (BACKGROUND)
  583     {
  584         Log(LOG_LEVEL_INFO, "Hailing %s : %s (in the background)",
  585             hostname, port);
  586     }
  587     else
  588 #endif
  589     {
  590         Log(LOG_LEVEL_INFO,
  591             "........................................................................");
  592         Log(LOG_LEVEL_INFO, "Hailing %s : %s",
  593             hostname, port);
  594         Log(LOG_LEVEL_INFO,
  595             "........................................................................");
  596     }
  597 
  598     ConnectionFlags connflags = {
  599         .protocol_version = config->protocol_version,
  600         .trust_server = trustkey,
  601         .off_the_record = false
  602     };
  603     int err = 0;
  604     conn = ServerConnection(hostname, port, CONNTIMEOUT, connflags, &err);
  605 
  606     if (conn == NULL)
  607     {
  608         Log(LOG_LEVEL_ERR, "Failed to connect to host: %s", hostname);
  609         return -1;
  610     }
  611 
  612     /* Send EXEC command. */
  613     return HailExec(conn, hostname);
  614 }
  615 
  616 /********************************************************************/
  617 /* Level 2                                                          */
  618 /********************************************************************/
  619 
  620 static void KeepControlPromises(EvalContext *ctx, const Policy *policy)
  621 {
  622     Seq *constraints = ControlBodyConstraints(policy, AGENT_TYPE_RUNAGENT);
  623     if (constraints)
  624     {
  625         for (size_t i = 0; i < SeqLength(constraints); i++)
  626         {
  627             Constraint *cp = SeqAt(constraints, i);
  628 
  629             if (!IsDefinedClass(ctx, cp->classes))
  630             {
  631                 continue;
  632             }
  633 
  634             VarRef *ref = VarRefParseFromScope(cp->lval, "control_runagent");
  635             DataType value_type;
  636             const void *value = EvalContextVariableGet(ctx, ref, &value_type);
  637             VarRefDestroy(ref);
  638 
  639             /* If var not found, or if it's an empty list. */
  640             if (value_type == CF_DATA_TYPE_NONE || value == NULL)
  641             {
  642                 Log(LOG_LEVEL_ERR, "Unknown lval '%s' in runagent control body", cp->lval);
  643                 continue;
  644             }
  645 
  646             if (strcmp(cp->lval, CFR_CONTROLBODY[RUNAGENT_CONTROL_FORCE_IPV4].lval) == 0)
  647             {
  648                 continue;
  649             }
  650 
  651             if (strcmp(cp->lval, CFR_CONTROLBODY[RUNAGENT_CONTROL_TRUSTKEY].lval) == 0)
  652             {
  653                 continue;
  654             }
  655 
  656             if (strcmp(cp->lval, CFR_CONTROLBODY[RUNAGENT_CONTROL_ENCRYPT].lval) == 0)
  657             {
  658                 continue;
  659             }
  660 
  661             if (strcmp(cp->lval, CFR_CONTROLBODY[RUNAGENT_CONTROL_PORT_NUMBER].lval) == 0)
  662             {
  663                 continue;
  664             }
  665 
  666             if (strcmp(cp->lval, CFR_CONTROLBODY[RUNAGENT_CONTROL_BACKGROUND].lval) == 0)
  667             {
  668                 /*
  669                  * Only process this option if are is no -b or -i options specified on
  670                  * command line.
  671                  */
  672                 if (BACKGROUND || INTERACTIVE)
  673                 {
  674                     Log(LOG_LEVEL_WARNING,
  675                         "'background_children' setting from 'body runagent control' is overridden by command-line option.");
  676                 }
  677                 else
  678                 {
  679                     BACKGROUND = BooleanFromString(value);
  680                 }
  681                 continue;
  682             }
  683 
  684             if (strcmp(cp->lval, CFR_CONTROLBODY[RUNAGENT_CONTROL_MAX_CHILD].lval) == 0)
  685             {
  686                 MAXCHILD = (short) IntFromString(value);
  687                 continue;
  688             }
  689 
  690             if (strcmp(cp->lval, CFR_CONTROLBODY[RUNAGENT_CONTROL_OUTPUT_TO_FILE].lval) == 0)
  691             {
  692                 OUTPUT_TO_FILE = BooleanFromString(value);
  693                 continue;
  694             }
  695 
  696             if (strcmp(cp->lval, CFR_CONTROLBODY[RUNAGENT_CONTROL_OUTPUT_DIRECTORY].lval) == 0)
  697             {
  698                 if (IsAbsPath(value))
  699                 {
  700                     strlcpy(OUTPUT_DIRECTORY, value, CF_BUFSIZE);
  701                     Log(LOG_LEVEL_VERBOSE, "Setting output direcory to '%s'", OUTPUT_DIRECTORY);
  702                 }
  703                 continue;
  704             }
  705 
  706             if (strcmp(cp->lval, CFR_CONTROLBODY[RUNAGENT_CONTROL_TIMEOUT].lval) == 0)
  707             {
  708                 continue;
  709             }
  710 
  711             if (strcmp(cp->lval, CFR_CONTROLBODY[RUNAGENT_CONTROL_HOSTS].lval) == 0)
  712             {
  713                 if (HOSTLIST == NULL)       // Don't override if command line setting
  714                 {
  715                     HOSTLIST = value;
  716                 }
  717 
  718                 continue;
  719             }
  720         }
  721     }
  722 
  723     const char *expire_after = EvalContextVariableControlCommonGet(ctx, COMMON_CONTROL_LASTSEEN_EXPIRE_AFTER);
  724     if (expire_after)
  725     {
  726         LASTSEENEXPIREAFTER = IntFromString(expire_after) * 60;
  727     }
  728 
  729 }
  730 
  731 static void SendClassData(AgentConnection *conn)
  732 {
  733     Rlist *classes, *rp;
  734 
  735     classes = RlistFromSplitRegex(SENDCLASSES, "[,: ]", 99, false);
  736 
  737     for (rp = classes; rp != NULL; rp = rp->next)
  738     {
  739         if (SendTransaction(conn->conn_info, RlistScalarValue(rp), 0, CF_DONE) == -1)
  740         {
  741             Log(LOG_LEVEL_ERR, "Transaction failed. (send: %s)", GetErrorStr());
  742             return;
  743         }
  744     }
  745 
  746     if (SendTransaction(conn->conn_info, CFD_TERMINATOR, 0, CF_DONE) == -1)
  747     {
  748         Log(LOG_LEVEL_ERR, "Transaction failed. (send: %s)", GetErrorStr());
  749         return;
  750     }
  751 }
  752 
  753 /********************************************************************/
  754 
  755 static int HailExec(AgentConnection *conn, char *peer)
  756 {
  757     char sendbuf[CF_BUFSIZE - CF_INBAND_OFFSET] = "EXEC";
  758     size_t sendbuf_len = strlen(sendbuf);
  759 
  760     if (!NULL_OR_EMPTY(DEFINECLASSES))
  761     {
  762         StrCat(sendbuf, sizeof(sendbuf), &sendbuf_len, " -D", 0);
  763         StrCat(sendbuf, sizeof(sendbuf), &sendbuf_len, DEFINECLASSES, 0);
  764     }
  765     if (!NULL_OR_EMPTY(REMOTEBUNDLES))
  766     {
  767         StrCat(sendbuf, sizeof(sendbuf), &sendbuf_len, " -b ", 0);
  768         StrCat(sendbuf, sizeof(sendbuf), &sendbuf_len, REMOTEBUNDLES, 0);
  769     }
  770 
  771     if (sendbuf_len >= sizeof(sendbuf))
  772     {
  773         Log(LOG_LEVEL_ERR, "Command longer than maximum transaction packet");
  774         DisconnectServer(conn);
  775         return -1;
  776     }
  777 
  778     if (SendTransaction(conn->conn_info, sendbuf, 0, CF_DONE) == -1)
  779     {
  780         Log(LOG_LEVEL_ERR, "Transmission rejected. (send: %s)", GetErrorStr());
  781         DisconnectServer(conn);
  782         return -1;
  783     }
  784 
  785     /* TODO we are sending class data right after EXEC, when the server might
  786      * have already rejected us with BAD reply. So this class data with the
  787      * CFD_TERMINATOR will be interpreted by the server as a new, bogus
  788      * protocol command, and the server will complain. */
  789     SendClassData(conn);
  790 
  791     char recvbuffer[CF_BUFSIZE];
  792     FILE *fp = NewStream(peer);
  793     int exit_code = -1;
  794     while (true)
  795     {
  796         memset(recvbuffer, 0, sizeof(recvbuffer));
  797 
  798         if (ReceiveTransaction(conn->conn_info, recvbuffer, NULL) == -1)
  799         {
  800             break;
  801         }
  802         if (strncmp(recvbuffer, CFD_TERMINATOR, strlen(CFD_TERMINATOR)) == 0)
  803         {
  804             break;
  805         }
  806 
  807         const size_t recv_len = strlen(recvbuffer);
  808         const char   *ipaddr  = conn->remoteip;
  809 
  810         if (strncmp(recvbuffer, "BAD:", 4) == 0)
  811         {
  812             fprintf(fp, "%s> !! %s\n", ipaddr, recvbuffer + 4);
  813         }
  814         /* cf-serverd >= 3.7 quotes command output with "> ". */
  815         else if (strncmp(recvbuffer, "> ", 2) == 0)
  816         {
  817             fprintf(fp, "%s> -> %s", ipaddr, &recvbuffer[2]);
  818         }
  819         else
  820         {
  821             /* '(exit code: N)' is a special line, not prefixed with '>' (so not
  822              * part of output) and sent last by new cf-serverd (3.18.0+) */
  823             if (StringStartsWith(recvbuffer, "(exit code:"))
  824             {
  825                 /* Should never show up twice. */
  826                 assert(exit_code == -1);
  827                 int scanned = sscanf(recvbuffer, "(exit code: %d)", &exit_code);
  828                 if (scanned != 1)
  829                 {
  830                     Log(LOG_LEVEL_ERR, "Failed to parse exit code from '%s'", recvbuffer);
  831                 }
  832             }
  833             fprintf(fp, "%s> %s", ipaddr, recvbuffer);
  834         }
  835 
  836         if (recv_len > 0 && recvbuffer[recv_len - 1] != '\n')
  837         {
  838             /* We'll be printing double newlines here with new cf-serverd
  839              * versions, so check for already trailing newlines. */
  840             /* TODO deprecate this path in a couple of versions. cf-serverd is
  841              * supposed to munch the newlines so we must always append one. */
  842             fputc('\n', fp);
  843         }
  844     }
  845 
  846     if (fp != stdout)
  847     {
  848         fclose(fp);
  849     }
  850     DisconnectServer(conn);
  851     return exit_code;
  852 }
  853 
  854 /********************************************************************/
  855 /* Level                                                            */
  856 /********************************************************************/
  857 
  858 static FILE *NewStream(char *name)
  859 {
  860     char filename[CF_BUFSIZE];
  861 
  862     if (OUTPUT_DIRECTORY[0] != '\0')
  863     {
  864         snprintf(filename, CF_BUFSIZE, "%s/%s_runagent.out", OUTPUT_DIRECTORY, name);
  865     }
  866     else
  867     {
  868         snprintf(filename, CF_BUFSIZE, "%s%coutputs%c%s_runagent.out",
  869                  GetWorkDir(), FILE_SEPARATOR, FILE_SEPARATOR, name);
  870     }
  871 
  872     FILE *fp;
  873     if (OUTPUT_TO_FILE)
  874     {
  875         printf("Opening file... %s\n", filename);
  876 
  877         fp = safe_fopen(filename, "w");
  878         if (fp == NULL)
  879         {
  880             Log(LOG_LEVEL_ERR, "Unable to open file '%s' (fopen: %s)", filename, GetErrorStr());
  881             fp = stdout;
  882         }
  883     }
  884     else
  885     {
  886         fp = stdout;
  887     }
  888 
  889     return fp;
  890 }