cfengine  3.15.4
About: CFEngine is a configuration management system for configuring and maintaining Unix-like computers (using an own high level policy language). Community version.
  Fossies Dox: cfengine-3.15.4.tar.gz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

cf-runagent.c
Go to the documentation of this 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 {
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 */
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 
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");
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  {
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 
301  return exit_code;
302 }
303 
304 /*******************************************************************/
305 
306 static GenericAgentConfig *CheckOpts(int argc, char **argv)
307 {
308  extern char *optarg;
309  int c;
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':
325  MINUSF = true;
326  break;
327 
328  case 'b':
329  BACKGROUND = true;
330  if (optarg)
331  {
333  }
334  break;
335 
336  case 'd':
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':
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':
380  break;
381 
382  case 'i':
383  INTERACTIVE = true;
384  break;
385 
386  case 'v':
388  break;
389 
390  case 'g':
392  break;
393 
394  case 'n':
395  DONTDO = true;
396  config->ignore_locks = true;
397  break;
398 
399  case 't':
401  break;
402 
403  case 'V':
404  {
405  Writer *w = FileWriter(stdout);
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),
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':
445  break;
446 
447  /* long options only */
448  case 0:
449 
450  if (strcmp(OPTIONS[longopt_idx].name, "log-modules") == 0)
451  {
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  {
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  {
591  "........................................................................");
592  Log(LOG_LEVEL_INFO, "Hailing %s : %s",
593  hostname, port);
595  "........................................................................");
596  }
597 
598  ConnectionFlags connflags = {
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  {
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 
691  {
693  continue;
694  }
695 
697  {
698  if (IsAbsPath(value))
699  {
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 
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 
761  {
762  StrCat(sendbuf, sizeof(sendbuf), &sendbuf_len, " -D", 0);
763  StrCat(sendbuf, sizeof(sendbuf), &sendbuf_len, DEFINECLASSES, 0);
764  }
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",
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 }
AddressType ParseHostPort(char *s, char **hostname, char **port)
Definition: addr_lib.c:588
void FatalError(const EvalContext *ctx, char *s,...)
Definition: audit.c:94
int BACKGROUND
Definition: cf-runagent.c:152
int main(int argc, char *argv[])
Definition: cf-runagent.c:198
const Rlist * HOSTLIST
Definition: cf-runagent.c:155
static void SendClassData(AgentConnection *conn)
Definition: cf-runagent.c:731
static const char *const CF_RUNAGENT_SHORT_DESCRIPTION
Definition: cf-runagent.c:83
static void UpdateExitCode(int *exit_code, int remote_exit_status, bool one_host, bool is_exit_code)
Definition: cf-runagent.c:167
const ConstraintSyntax CFR_CONTROLBODY[]
Definition: mod_common.c:361
char SENDCLASSES[1024]
Definition: cf-runagent.c:157
int INTERACTIVE
Definition: cf-runagent.c:149
static GenericAgentConfig * CheckOpts(int argc, char **argv)
Definition: cf-runagent.c:306
static const char *const HINTS[]
Definition: cf-runagent.c:122
static const char *const CF_RUNAGENT_MANPAGE_LONG_DESCRIPTION
Definition: cf-runagent.c:86
#define CF_RA_EXIT_CODE_OTHER_ERR
Definition: cf-runagent.c:53
static int HailServer(const EvalContext *ctx, const GenericAgentConfig *config, char *host)
Definition: cf-runagent.c:500
char DEFINECLASSES[1024]
Definition: cf-runagent.c:158
static FILE * NewStream(char *name)
Definition: cf-runagent.c:858
int OUTPUT_TO_FILE
Definition: cf-runagent.c:150
char OUTPUT_DIRECTORY[4096]
Definition: cf-runagent.c:151
static const struct option OPTIONS[]
Definition: cf-runagent.c:96
int MAXCHILD
Definition: cf-runagent.c:153
static void KeepControlPromises(EvalContext *ctx, const Policy *policy)
Definition: cf-runagent.c:620
static void ThisAgentInit(void)
Definition: cf-runagent.c:493
static int HailExec(AgentConnection *conn, char *peer)
Definition: cf-runagent.c:755
RunagentControl
Definition: cf-runagent.c:56
@ RUNAGENT_CONTROL_HOSTS
Definition: cf-runagent.c:57
@ RUNAGENT_CONTROL_TRUSTKEY
Definition: cf-runagent.c:60
@ RUNAGENT_CONTROL_NONE
Definition: cf-runagent.c:67
@ RUNAGENT_CONTROL_PORT_NUMBER
Definition: cf-runagent.c:58
@ RUNAGENT_CONTROL_BACKGROUND
Definition: cf-runagent.c:62
@ RUNAGENT_CONTROL_MAX_CHILD
Definition: cf-runagent.c:63
@ RUNAGENT_CONTROL_OUTPUT_DIRECTORY
Definition: cf-runagent.c:65
@ RUNAGENT_CONTROL_TIMEOUT
Definition: cf-runagent.c:66
@ RUNAGENT_CONTROL_OUTPUT_TO_FILE
Definition: cf-runagent.c:64
@ RUNAGENT_CONTROL_FORCE_IPV4
Definition: cf-runagent.c:59
@ RUNAGENT_CONTROL_ENCRYPT
Definition: cf-runagent.c:61
char REMOTEBUNDLES[1024]
Definition: cf-runagent.c:159
#define CFD_TERMINATOR
Definition: cf3.defs.h:102
@ COMMON_CONTROL_LASTSEEN_EXPIRE_AFTER
Definition: cf3.defs.h:422
@ AGENT_TYPE_RUNAGENT
Definition: cf3.defs.h:406
DataType
Definition: cf3.defs.h:368
@ CF_DATA_TYPE_NONE
Definition: cf3.defs.h:385
bool DONTDO
Definition: cf3globals.c:55
time_t CONNTIMEOUT
Definition: cf3globals.c:106
long LASTSEENEXPIREAFTER
Definition: cf3globals.c:49
bool MINUSF
Definition: cf3globals.c:150
#define CF_DONE
Definition: cfnet.h:45
#define CF_MAX_IP_LEN
Definition: cfnet.h:39
#define CF_INBAND_OFFSET
Definition: cfnet.h:51
void CallCleanupFunctions(void)
Definition: cleanup.c:39
void DoCleanupAndExit(int ret)
Definition: cleanup.c:57
AgentConnection * ServerConnection(const char *server, const char *port, unsigned int connect_timeout, ConnectionFlags flags, int *err)
Definition: client_code.c:190
void DisconnectServer(AgentConnection *conn)
Definition: client_code.c:309
int Hostname2IPString(char *dst, const char *hostname, size_t dst_size)
DNS lookup of hostname, store the address as string into dst of size dst_size.
long IntFromString(const char *s)
Definition: conversion.c:390
bool BooleanFromString(const char *s)
Definition: conversion.c:354
RSA * HavePublicKey(const char *username, const char *ipaddress, const char *digest)
Search for a key:
Definition: crypto.c:344
#define CF_BUFSIZE
Definition: definitions.h:50
#define CF_EXPANDSIZE
Definition: definitions.h:51
#define CF_MAXVARSIZE
Definition: definitions.h:36
#define CF_SMALLBUF
Definition: definitions.h:49
EvalContext * EvalContextNew(void)
const void * EvalContextVariableControlCommonGet(const EvalContext *ctx, CommonControl lval)
const void * EvalContextVariableGet(const EvalContext *ctx, const VarRef *ref, DataType *type_out)
static bool IsDefinedClass(const EvalContext *ctx, const char *context)
Definition: eval_context.h:213
FILE * safe_fopen(const char *const path, const char *const mode)
Definition: file_lib.c:812
#define FILE_SEPARATOR
Definition: file_lib.h:102
bool IsAbsPath(const char *path)
Definition: files_names.c:214
void GenericAgentConfigSetInputFile(GenericAgentConfig *config, const char *inputdir, const char *input_file)
GenericAgentConfig * GenericAgentConfigNewDefault(AgentType agent_type, bool tty_interactive)
bool GenericAgentPostLoadInit(const EvalContext *ctx)
Seq * ControlBodyConstraints(const Policy *policy, AgentType agent)
void GenericAgentFinalize(EvalContext *ctx, GenericAgentConfig *config)
void GenericAgentDiscoverContext(EvalContext *ctx, GenericAgentConfig *config, const char *program_name)
bool GenericAgentConfigParseArguments(GenericAgentConfig *config, int argc, char **argv)
bool GetTTYInteractive(void)
bool GenericAgentConfigParseColor(GenericAgentConfig *config, const char *mode)
void GenericAgentConfigApply(EvalContext *ctx, const GenericAgentConfig *config)
void GenericAgentWriteVersion(Writer *w)
#define NULL
Definition: getopt1.c:56
int optind
Definition: getopt.c:102
char * optarg
Definition: getopt.c:87
#define no_argument
Definition: getopt.h:98
#define required_argument
Definition: getopt.h:99
int getopt_long()
#define optional_argument
Definition: getopt.h:100
#define CF_HOSTKEY_STRING_SIZE
Definition: hash.h:151
const char * GetInputDir(void)
Definition: known_dirs.c:182
const char * GetWorkDir(void)
Definition: known_dirs.c:114
bool Address2Hostkey(char *dst, size_t dst_size, const char *address)
Definition: lastseen.c:196
Policy * LoadPolicy(EvalContext *ctx, GenericAgentConfig *config)
Definition: loading.c:495
bool LogEnableModulesFromString(char *s)
Definition: logging.c:476
void LogSetGlobalLevelArgOrExit(const char *const arg)
Definition: logging.c:567
void LoggingEnableTimestamps(bool enable)
Definition: logging.c:88
void LogSetGlobalLevel(LogLevel level)
Definition: logging.c:561
const char * GetErrorStr(void)
Definition: logging.c:275
void Log(LogLevel level, const char *fmt,...)
Definition: logging.c:409
@ LOG_LEVEL_ERR
Definition: logging.h:42
@ LOG_LEVEL_NOTICE
Definition: logging.h:44
@ LOG_LEVEL_DEBUG
Definition: logging.h:47
@ LOG_LEVEL_WARNING
Definition: logging.h:43
@ LOG_LEVEL_VERBOSE
Definition: logging.h:46
@ LOG_LEVEL_INFO
Definition: logging.h:45
void ManPageWrite(Writer *out, const char *program, time_t last_modified, const char *short_description, const char *long_description, const struct option options[], const char *const option_hints[], const Description *commands, bool command_first, bool accepts_file_argument)
Definition: man.c:226
int ReceiveTransaction(ConnectionInfo *conn_info, char *buffer, int *more)
Definition: net.c:149
int SendTransaction(ConnectionInfo *conn_info, const char *buffer, int len, char status)
Definition: net.c:61
#define MIN(a, b)
Definition: platform.h:478
#define WEXITSTATUS(s)
Definition: platform.h:141
#define WIFEXITED(s)
Definition: platform.h:144
void PolicyDestroy(Policy *policy)
Definition: policy.c:121
char * RlistScalarValue(const Rlist *rlist)
Definition: rlist.c:83
Rlist * RlistFromSplitRegex(const char *string, const char *regex, size_t max_entries, bool allow_blanks)
Definition: rlist.c:1139
Rlist * RlistFromSplitString(const char *string, char sep)
Definition: rlist.c:1067
size_t SeqLength(const Seq *seq)
Length of the sequence.
Definition: sequence.c:354
static void * SeqAt(const Seq *seq, int i)
Definition: sequence.h:57
bool StringStartsWith(const char *str, const char *prefix)
Check if a string starts with the given prefix.
Definition: string_lib.c:1335
void StrCatDelim(char *dst, size_t dst_size, size_t *dst_len, const char *src, char sep)
Definition: string_lib.c:1534
int Chop(char *str, size_t max_length)
Remove trailing spaces.
Definition: string_lib.c:1174
void StrCat(char *dst, size_t dst_size, size_t *dst_len, const char *src, size_t src_len)
Definition: string_lib.c:1494
long StringToLongExitOnError(const char *str)
Converts a string of numerals in base 10 to a long integer, exits on error.
Definition: string_lib.c:584
#define NULL_OR_EMPTY(str)
Definition: string_lib.h:43
size_t strlcpy(char *dst, const char *src, size_t siz)
Definition: strlcpy.c:34
ConnectionInfo * conn_info
Definition: cfnet.h:98
char remoteip[64]
Definition: cfnet.h:103
ProtocolVersion protocol_version
Definition: cfnet.h:56
const char * lval
Definition: cf3.defs.h:656
char * classes
Definition: policy.h:135
char * lval
Definition: policy.h:132
ProtocolVersion protocol_version
Definition: generic_agent.h:66
Definition: policy.h:53
Definition: rlist.h:35
Rlist * next
Definition: rlist.h:37
Sequence data-structure.
Definition: sequence.h:50
Definition: writer.c:45
Definition: getopt.h:83
char * name
Definition: getopt.h:87
bool GetCurrentUserName(char *userName, int userNameLen)
Definition: unix.c:107
VarRef * VarRefParseFromScope(const char *var_ref_string, const char *scope)
void VarRefDestroy(VarRef *ref)
void WriterWriteHelp(Writer *w, const char *component, const struct option options[], const char *const hints[], const Description *commands, bool command_first, bool accepts_file_argument)
Definition: writer.c:331
Writer * FileWriter(FILE *file)
Definition: writer.c:56
FILE * FileWriterDetach(Writer *writer)
Definition: writer.c:277