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)  

verify_files.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 <verify_files.h>
26 
27 #include <actuator.h>
28 #include <promises.h>
29 #include <vars.h>
30 #include <dir.h>
31 #include <scope.h>
32 #include <eval_context.h>
33 #include <files_names.h>
34 #include <files_interfaces.h>
35 #include <files_lib.h>
36 #include <files_operators.h>
37 #include <hash.h>
38 #include <files_edit.h>
39 #include <files_editxml.h>
40 #include <files_editline.h>
41 #include <files_properties.h>
42 #include <files_select.h>
43 #include <item_lib.h>
44 #include <match_scope.h>
45 #include <attributes.h>
46 #include <locks.h>
47 #include <string_lib.h>
48 #include <verify_files_utils.h>
49 #include <verify_files_hashes.h>
50 #include <misc_lib.h>
51 #include <fncall.h>
53 #include <ornaments.h>
54 #include <audit.h>
55 #include <expand.h>
56 #include <mustache.h>
57 #include <known_dirs.h>
58 #include <evalfunction.h>
59 
61 static PromiseResult VerifyFilePromise(EvalContext *ctx, char *path, const Promise *pp);
62 
63 /*****************************************************************************/
64 
65 static bool FileSanityChecks(char *path, const Attributes *a, const Promise *pp)
66 {
67  assert(a != NULL);
68  if ((a->havelink) && (a->havecopy))
69  {
71  "Promise constraint conflicts - '%s' file cannot both be a copy of and a link to the source", path);
73  return false;
74  }
75 
76  /* We can't do this verification during parsing as we did not yet read the
77  * body, so we can't distinguish between link and copy source. In
78  * post-verification all bodies are already expanded, so we don't have the
79  * information either */
80  if ((a->havelink) && (!a->link.source))
81  {
82  Log(LOG_LEVEL_ERR, "Promise to establish a link at '%s' has no source", path);
84  return false;
85  }
86 
87  if ((a->haveeditline) && (a->haveeditxml))
88  {
89  Log(LOG_LEVEL_ERR, "Promise constraint conflicts - '%s' editing file as both line and xml makes no sense",
90  path);
92  return false;
93  }
94 
95  if ((a->havedepthsearch) && (a->haveedit))
96  {
97  Log(LOG_LEVEL_ERR, "Recursive depth_searches are not compatible with general file editing");
99  return false;
100  }
101 
102  if ((a->havedelete) && ((a->create) || (a->havecopy) || (a->haveedit) || (a->haverename)))
103  {
104  Log(LOG_LEVEL_ERR, "Promise constraint conflicts - '%s' cannot be deleted and exist at the same time", path);
106  return false;
107  }
108 
109  if ((a->haverename) && ((a->create) || (a->havecopy) || (a->haveedit)))
110  {
112  "Promise constraint conflicts - '%s' cannot be renamed/moved and exist there at the same time", path);
114  return false;
115  }
116 
117  if ((a->havedelete) && (a->havedepthsearch) && (!a->haveselect))
118  {
120  "Dangerous or ambiguous promise - '%s' specifies recursive deletion but has no file selection criteria",
121  path);
123  return false;
124  }
125 
126  if ((a->haveselect) && (!a->select.result))
127  {
128  Log(LOG_LEVEL_ERR, "File select constraint body promised no result (check body definition)");
130  return false;
131  }
132 
133  if ((a->havedelete) && (a->haverename))
134  {
135  Log(LOG_LEVEL_ERR, "File '%s' cannot promise both deletion and renaming", path);
137  return false;
138  }
139 
140  if ((a->havecopy) && (a->havedepthsearch) && (a->havedelete))
141  {
143  "depth_search of '%s' applies to both delete and copy, but these refer to different searches (source/destination)",
144  pp->promiser);
146  }
147 
148  if ((a->transaction.background) && (a->transaction.audit))
149  {
150  Log(LOG_LEVEL_ERR, "Auditing cannot be performed on backgrounded promises (this might change).");
152  return false;
153  }
154 
155  if (((a->havecopy) || (a->havelink)) && (a->transformer))
156  {
157  Log(LOG_LEVEL_ERR, "File object(s) '%s' cannot both be a copy of source and transformed simultaneously",
158  pp->promiser);
160  return false;
161  }
162 
163  if ((a->haveselect) && (a->select.result == NULL))
164  {
165  Log(LOG_LEVEL_ERR, "Missing file_result attribute in file_select body");
167  return false;
168  }
169 
170  if ((a->havedepthsearch) && (a->change.report_diffs))
171  {
172  Log(LOG_LEVEL_ERR, "Difference reporting is not allowed during a depth_search");
174  return false;
175  }
176 
177  if ((a->haveedit) && (a->file_type) && (!strncmp(a->file_type, "fifo", 5)))
178  {
179  Log(LOG_LEVEL_ERR, "Editing is not allowed on fifos");
181  return false;
182  }
183 
184  return true;
185 }
186 
187 static bool AttrHasNoAction(const Attributes *attr)
188 {
189  assert(attr != NULL);
190  /* Hopefully this includes all "actions" for a files promise. See struct
191  * Attributes for reference. */
192  if (!(attr->transformer || attr->haverename || attr->havedelete ||
193  attr->havecopy || attr->create || attr->touch || attr->havelink ||
194  attr->haveperms || attr->havechange || attr->acl.acl_entries ||
195  attr->haveedit || attr->haveeditline || attr->haveeditxml))
196  {
197  return true;
198  }
199  else
200  {
201  return false;
202  }
203 }
204 
205 /*
206  * Expands source in-place.
207  */
208 static char *ExpandThisPromiserScalar(EvalContext *ctx, const char *ns, const char *scope, const char *source)
209 {
210  if (!source)
211  {
212  return NULL;
213  }
214  Buffer *expanded = BufferNew();
215  ExpandScalar(ctx, ns, scope, source, expanded);
216  char *result = strdup(BufferData(expanded));
217  BufferDestroy(expanded);
218  return result;
219 }
220 
221 /*
222  * Overwrite non-specific attributes with expanded this.promiser.
223  */
225 {
226  const char *namespace = PromiseGetBundle(pp)->ns;
227  const char *scope = PromiseGetBundle(pp)->name;
228 
229  Attributes a = *attr; // shallow copy
230 
231  a.classes.change = ExpandList(ctx, namespace, scope, attr->classes.change, true);
232  a.classes.failure = ExpandList(ctx, namespace, scope, attr->classes.failure, true);
233  a.classes.denied = ExpandList(ctx, namespace, scope, attr->classes.denied, true);
234  a.classes.timeout = ExpandList(ctx, namespace, scope, attr->classes.timeout, true);
235  a.classes.kept = ExpandList(ctx, namespace, scope, attr->classes.kept, true);
236 
237  a.classes.del_change = ExpandList(ctx, namespace, scope, attr->classes.del_change, true);
238  a.classes.del_kept = ExpandList(ctx, namespace, scope, attr->classes.del_kept, true);
239  a.classes.del_notkept = ExpandList(ctx, namespace, scope, attr->classes.del_notkept, true);
240 
241  a.transaction.log_string = ExpandThisPromiserScalar(ctx, namespace, scope, attr->transaction.log_string);
242  a.transaction.log_kept = ExpandThisPromiserScalar(ctx, namespace, scope, attr->transaction.log_kept);
244  a.transaction.log_failed = ExpandThisPromiserScalar(ctx, namespace, scope, attr->transaction.log_failed);
245  a.transaction.measure_id = ExpandThisPromiserScalar(ctx, namespace, scope, attr->transaction.measure_id);
246 
247  // a.transformer = ExpandThisPromiserScalar(ctx, namespace, scope, attr->transformer);
248  a.edit_template = ExpandThisPromiserScalar(ctx, namespace, scope, attr->edit_template);
249  a.edit_template_string = ExpandThisPromiserScalar(ctx, namespace, scope, attr->edit_template_string);
250 
251  return a;
252 }
253 
255 {
264 
270 
273 
275 }
276 
277 static PromiseResult VerifyFilePromise(EvalContext *ctx, char *path, const Promise *pp)
278 {
279  struct stat osb, oslb, dsb;
280  CfLock thislock;
281  int exists;
282  bool link = false;
283 
284  Attributes attr = GetFilesAttributes(ctx, pp);
285 
286  if (!FileSanityChecks(path, &attr, pp))
287  {
288  ClearFilesAttributes(&attr);
289  return PROMISE_RESULT_NOOP;
290  }
291 
292  thislock = AcquireLock(ctx, path, VUQNAME, CFSTARTTIME, attr.transaction.ifelapsed, attr.transaction.expireafter, pp, false);
293  if (thislock.lock == NULL)
294  {
295  ClearFilesAttributes(&attr);
296  return PROMISE_RESULT_SKIPPED;
297  }
298 
299  EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_THIS, "promiser", path, CF_DATA_TYPE_STRING, "source=promise");
300  Attributes a = GetExpandedAttributes(ctx, pp, &attr);
301 
303 
304  /* if template_data was specified, it must have been resolved to a data
305  * container by now */
306  /* check this early to prevent creation of the file below in case of failure */
307  const Constraint *template_data_constraint = PromiseGetConstraint(pp, "template_data");
308  if (template_data_constraint != NULL &&
309  template_data_constraint->rval.type != RVAL_TYPE_CONTAINER)
310  {
311  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, &a,
312  "No template data for the promise '%s'", pp->promiser);
313  result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
314  goto exit;
315  }
316 
317  if (lstat(path, &oslb) == -1) /* Careful if the object is a link */
318  {
319  if ((a.create) || (a.touch))
320  {
321  if (!CfCreateFile(ctx, path, pp, &a, &result))
322  {
323  goto exit;
324  }
325  else
326  {
327  exists = (lstat(path, &oslb) != -1);
328  }
329  }
330 
331  exists = false;
332  }
333  else
334  {
335  if ((a.create) || (a.touch))
336  {
337  cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, &a, "File '%s' exists as promised", path);
338  }
339  exists = true;
340  link = true;
341  }
342 
343  if ((a.havedelete) && (!exists))
344  {
345  cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, &a, "File '%s' does not exist as promised", path);
346  goto exit;
347  }
348 
349  if (!a.havedepthsearch) /* if the search is trivial, make sure that we are in the parent dir of the leaf */
350  {
351  char basedir[CF_BUFSIZE];
352 
353  Log(LOG_LEVEL_DEBUG, "Direct file reference '%s', no search implied", path);
354  snprintf(basedir, sizeof(basedir), "%s", path);
355 
356  if (strcmp(ReadLastNode(basedir), ".") == 0)
357  {
358  // Handle /. notation for deletion of directories
359  ChopLastNode(basedir);
360  ChopLastNode(path);
361  }
362 
363  ChopLastNode(basedir);
364  if (safe_chdir(basedir))
365  {
366  char msg[CF_BUFSIZE];
367  snprintf(msg, sizeof(msg), "Failed to chdir into '%s'. (chdir: '%s')",
368  basedir, GetErrorStr());
369  if (errno == ENOLINK)
370  {
371  Log(LOG_LEVEL_ERR, "%s. There may be a symlink in the path that has a different "
372  "owner from the owner of its target (security risk).", msg);
373  }
374  else
375  {
376  Log(LOG_LEVEL_ERR, "%s", msg);
377  }
378  }
379  }
380 
381  /* If file or directory exists but it is not selected by body file_select
382  * (if we have one) then just exit. But continue if it's a directory and
383  * depth_search is on, so that we can file_select into it. */
384  if (exists
385  && (a.haveselect && !SelectLeaf(ctx, path, &oslb, &(a.select)))
386  && !(a.havedepthsearch && S_ISDIR(oslb.st_mode)))
387  {
388  goto exit;
389  }
390 
391  if (stat(path, &osb) == -1)
392  {
393  if ((a.create) || (a.touch))
394  {
395  if (!CfCreateFile(ctx, path, pp, &a, &result))
396  {
397  goto exit;
398  }
399  else
400  {
401  exists = true;
402  }
403  }
404  else
405  {
406  exists = false;
407  }
408  }
409  else
410  {
411  if (!S_ISDIR(osb.st_mode))
412  {
413  if (a.havedepthsearch)
414  {
416  "depth_search (recursion) is promised for a base object '%s' that is not a directory",
417  path);
418  goto exit;
419  }
420  }
421 
422  exists = true;
423  }
424 
425  if (a.link.link_children)
426  {
427  if (stat(a.link.source, &dsb) != -1)
428  {
429  if (!S_ISDIR(dsb.st_mode))
430  {
431  Log(LOG_LEVEL_ERR, "Cannot promise to link the children of '%s' as it is not a directory!",
432  a.link.source);
433  goto exit;
434  }
435  }
436  }
437 
438 /* Phase 1 - */
439 
440  if ((exists
441  && (a.haverename || a.haveperms || a.havechange || a.transformer ||
442  a.acl.acl_entries != NULL)
443  ) ||
444  ((exists || link) && a.havedelete))
445  {
446  lstat(path, &oslb); /* if doesn't exist have to stat again anyway */
447 
448  DepthSearch(ctx, path, &oslb, 0, &a, pp, oslb.st_dev, &result);
449 
450  /* normally searches do not include the base directory */
451 
453  {
454  int save_search = a.havedepthsearch;
455 
456  /* Handle this node specially */
457 
458  a.havedepthsearch = false;
459  DepthSearch(ctx, path, &oslb, 0, &a, pp, oslb.st_dev, &result);
460  a.havedepthsearch = save_search;
461  }
462  else
463  {
464  /* unless child nodes were repaired, set a promise kept class */
465  if (result == PROMISE_RESULT_NOOP)
466  {
467  cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, &a, "Basedir '%s' not promising anything", path);
468  }
469  }
470  }
471 
472 /* Phase 2a - copying is potentially threadable if no followup actions */
473 
474  if (a.havecopy)
475  {
476  result = PromiseResultUpdate(result, ScheduleCopyOperation(ctx, path, &a, pp));
477  }
478 
479 /* Phase 2b link after copy in case need file first */
480 
481  if ((a.havelink) && (a.link.link_children))
482  {
483  result = PromiseResultUpdate(result, ScheduleLinkChildrenOperation(ctx, path, a.link.source, 1, &a, pp));
484  }
485  else if (a.havelink)
486  {
487  result = PromiseResultUpdate(result, ScheduleLinkOperation(ctx, path, a.link.source, &a, pp));
488  }
489 
490 /* Phase 3 - content editing */
491 
492  if (a.haveedit)
493  {
494  if (exists)
495  {
496  result = PromiseResultUpdate(result, ScheduleEditOperation(ctx, path, &a, pp));
497  }
498  else
499  {
500  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, &a, "Promised to edit '%s', but file does not exist", path);
501  result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
502  }
503  }
504 
505 // Once more in case a file has been created as a result of editing or copying
506 
507  exists = (stat(path, &osb) != -1);
508 
509  if (exists && (S_ISREG(osb.st_mode))
510  && (!a.haveselect || SelectLeaf(ctx, path, &osb, &(a.select))))
511  {
512  VerifyFileLeaf(ctx, path, &osb, &a, pp, &result);
513  }
514 
515  if (!exists && a.havechange)
516  {
517  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, &a, "Promised to monitor '%s' for changes, but file does not exist", path);
518  result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
519  }
520 
521 exit:
522 
523  if (AttrHasNoAction(&a))
524  {
526  "No action was requested for file '%s'. Maybe a typo in the policy?", path);
527  }
528 
529  YieldCurrentLock(thislock);
530 
533 
534  return result;
535 }
536 
537 /*****************************************************************************/
538 
540  const Rlist *bundle_args, const Attributes *attr,
541  EditContext *edcontext)
542 {
543  assert(attr != NULL);
544  Attributes a = *attr; // TODO: Try to remove this copy
546 
547  Policy *tmp_policy = PolicyNew();
548  Bundle *bp = NULL;
549  if ((bp = MakeTemporaryBundleFromTemplate(ctx, tmp_policy, &a, pp, &result)))
550  {
551  a.haveeditline = true;
552 
553  EvalContextStackPushBundleFrame(ctx, bp, bundle_args, a.edits.inherit);
554  BundleResolve(ctx, bp);
555 
556  ScheduleEditLineOperations(ctx, bp, &a, pp, edcontext);
557 
559 
560  if (edcontext->num_edits == 0)
561  {
562  edcontext->num_edits++;
563  }
564  }
565 
566  PolicyDestroy(tmp_policy);
567 
568  return result;
569 }
570 
571 static bool SaveBufferCallback(const char *dest_filename, void *param, NewLineMode new_line_mode)
572 {
573  FILE *fp = safe_fopen(
574  dest_filename, (new_line_mode == NewLineMode_Native) ? "wt" : "w");
575  if (fp == NULL)
576  {
577  Log(LOG_LEVEL_ERR, "Unable to open destination file '%s' for writing. (fopen: %s)",
578  dest_filename, GetErrorStr());
579  return false;
580  }
581 
582  Buffer *output_buffer = param;
583 
584  size_t bytes_written = fwrite(BufferData(output_buffer), sizeof(char), BufferSize(output_buffer), fp);
585  if (bytes_written != BufferSize(output_buffer))
586  {
588  "Error writing to output file '%s' when writing. %zu bytes written but expected %u. (fclose: %s)",
589  dest_filename, bytes_written, BufferSize(output_buffer), GetErrorStr());
590  fclose(fp);
591  return false;
592  }
593 
594  if (fclose(fp) == -1)
595  {
596  Log(LOG_LEVEL_ERR, "Unable to close file '%s' after writing. (fclose: %s)",
597  dest_filename, GetErrorStr());
598  return false;
599  }
600 
601  return true;
602 }
603 
604 
606  EditContext *edcontext, const char *template)
607 {
608  assert(attr != NULL);
609  Attributes a = *attr; // TODO: Try to remove this copy
611 
612  JsonElement *destroy_this = NULL;
613 
614  if (a.template_data == NULL)
615  {
617  destroy_this = a.template_data;
618  }
619 
620  unsigned char existing_output_digest[EVP_MAX_MD_SIZE + 1] = { 0 };
621  if (access(pp->promiser, R_OK) == 0)
622  {
623  HashFile(pp->promiser, existing_output_digest, CF_DEFAULT_DIGEST,
624  edcontext->new_line_mode == NewLineMode_Native);
625  }
626 
627  Buffer *output_buffer = BufferNew();
628 
629  char *message;
630  if ( strcmp("inline_mustache", a.template_method) == 0)
631  {
632  message = xstrdup("inline");
633  }
634  else
635  {
636  message = xstrdup(a.edit_template);
637  }
638 
639  if (MustacheRender(output_buffer, template, a.template_data))
640  {
641  unsigned char rendered_output_digest[EVP_MAX_MD_SIZE + 1] = { 0 };
642  HashString(BufferData(output_buffer), BufferSize(output_buffer), rendered_output_digest, CF_DEFAULT_DIGEST);
643  if (!HashesMatch(existing_output_digest, rendered_output_digest, CF_DEFAULT_DIGEST))
644  {
645  if (a.transaction.action == cfa_warn || DONTDO)
646  {
648  "Need to update rendering of '%s' from mustache template '%s' but policy is dry-run",
649  pp->promiser, message);
650  result = PromiseResultUpdate(result, PROMISE_RESULT_WARN);
651  }
652  else
653  {
654  if (SaveAsFile(SaveBufferCallback, output_buffer, edcontext->filename, &a, edcontext->new_line_mode))
655  {
656  cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_CHANGE, pp, &a, "Updated rendering of '%s' from mustache template '%s'",
657  pp->promiser, message);
658  result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE);
659 
660  edcontext->num_rewrites++;
661  }
662  else
663  {
664  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, &a, "Failed to update rendering of '%s' from mustache template '%s'",
665  pp->promiser, message);
666  result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
667  }
668  }
669  }
670 
671  BufferDestroy(output_buffer);
672  free(message);
673  JsonDestroy(destroy_this);
674  return result;
675  }
676  else
677  {
678  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, &a, "Error rendering mustache template '%s'", a.edit_template);
679  result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
680  BufferDestroy(output_buffer);
681  JsonDestroy(destroy_this);
683  }
684 }
685 
687  EditContext *edcontext)
688 {
689  assert(a != NULL);
691 
692  if (!FileCanOpen(a->edit_template, "r"))
693  {
694  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Template file '%s' could not be opened for reading", a->edit_template);
696  }
697 
698 
699  int template_fd = safe_open(a->edit_template, O_RDONLY | O_TEXT);
700  Writer *template_writer = NULL;
701  if (template_fd >= 0)
702  {
703  template_writer = FileReadFromFd(template_fd, SIZE_MAX, NULL);
704  close(template_fd);
705  }
706  if (template_writer == NULL)
707  {
708  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Could not read template file '%s'", a->edit_template);
710  }
711 
712  result = RenderTemplateMustache(ctx, pp, a, edcontext, StringWriterData(template_writer));
713 
714  WriterClose(template_writer);
715 
716  return result;
717 }
718 
720  EditContext *edcontext)
721 {
722  assert(a != NULL);
723  if ( a->edit_template_string == NULL )
724  {
726 
727  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "'edit_template_string' not set for promiser: '%s'", pp->promiser);
729  }
730 
731  return RenderTemplateMustache(ctx, pp, a, edcontext, a->edit_template_string);
732 }
733 
734 PromiseResult ScheduleEditOperation(EvalContext *ctx, char *filename, const Attributes *a, const Promise *pp)
735 {
736  assert(a != NULL);
737  void *vp;
738  FnCall *fp;
739  Rlist *args = NULL;
740  char edit_bundle_name[CF_BUFSIZE], lockname[CF_BUFSIZE];
741  CfLock thislock;
742 
743  snprintf(lockname, CF_BUFSIZE - 1, "fileedit-%s", filename);
744  thislock = AcquireLock(ctx, lockname, VUQNAME, CFSTARTTIME, a->transaction.ifelapsed, a->transaction.expireafter, pp, false);
745 
746  if (thislock.lock == NULL)
747  {
748  return PROMISE_RESULT_SKIPPED;
749  }
750 
751  EditContext *edcontext = NewEditContext(filename, a);
752 
754  if (edcontext == NULL)
755  {
756  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "File '%s' was marked for editing but could not be opened", filename);
757  result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
758  goto exit;
759  }
760 
761  const Policy *policy = PolicyFromPromise(pp);
762 
763  if (a->haveeditline)
764  {
765  if ((vp = PromiseGetConstraintAsRval(pp, "edit_line", RVAL_TYPE_FNCALL)))
766  {
767  fp = (FnCall *) vp;
768  strcpy(edit_bundle_name, fp->name);
769  args = fp->args;
770  }
771  else if ((vp = PromiseGetConstraintAsRval(pp, "edit_line", RVAL_TYPE_SCALAR)))
772  {
773  strcpy(edit_bundle_name, (char *) vp);
774  args = NULL;
775  }
776  else
777  {
778  goto exit;
779  }
780 
781  Log(LOG_LEVEL_VERBOSE, "Handling file edits in edit_line bundle '%s'", edit_bundle_name);
782 
783  const Bundle *bp = EvalContextResolveBundleExpression(ctx, policy, edit_bundle_name, "edit_line");
784  if (bp)
785  {
786  EvalContextStackPushBundleFrame(ctx, bp, args, a->edits.inherit);
787 
788  BundleResolve(ctx, bp);
789 
790  ScheduleEditLineOperations(ctx, bp, a, pp, edcontext);
791 
793  }
794  else
795  {
796  Log(LOG_LEVEL_ERR, "Did not find bundle '%s' for edit operation", edit_bundle_name);
797  }
798  }
799 
800 
801  if (a->haveeditxml)
802  {
803  if ((vp = PromiseGetConstraintAsRval(pp, "edit_xml", RVAL_TYPE_FNCALL)))
804  {
805  fp = (FnCall *) vp;
806  strcpy(edit_bundle_name, fp->name);
807  args = fp->args;
808  }
809  else if ((vp = PromiseGetConstraintAsRval(pp, "edit_xml", RVAL_TYPE_SCALAR)))
810  {
811  strcpy(edit_bundle_name, (char *) vp);
812  args = NULL;
813  }
814  else
815  {
816  goto exit;
817  }
818 
819  Log(LOG_LEVEL_VERBOSE, "Handling file edits in edit_xml bundle '%s'", edit_bundle_name);
820 
821  const Bundle *bp = EvalContextResolveBundleExpression(ctx, policy, edit_bundle_name, "edit_xml");
822  if (bp)
823  {
824  EvalContextStackPushBundleFrame(ctx, bp, args, a->edits.inherit);
825  BundleResolve(ctx, bp);
826 
827  ScheduleEditXmlOperations(ctx, bp, a, pp, edcontext);
828 
830  }
831  }
832 
833  if (strcmp("cfengine", a->template_method) == 0)
834  {
835  if (a->edit_template)
836  {
837  Log(LOG_LEVEL_VERBOSE, "Rendering '%s' using template '%s' with method '%s'",
838  filename, a->edit_template, a->template_method);
839 
840  PromiseResult render_result = RenderTemplateCFEngine(ctx, pp, args, a, edcontext);
841  result = PromiseResultUpdate(result, render_result);
842  }
843  }
844  else if (strcmp("mustache", a->template_method) == 0)
845  {
846  if (a->edit_template)
847  {
848  Log(LOG_LEVEL_VERBOSE, "Rendering '%s' using template '%s' with method '%s'",
849  filename, a->edit_template, a->template_method);
850 
851  PromiseResult render_result = RenderTemplateMustacheFromFile(ctx, pp, a, edcontext);
852  result = PromiseResultUpdate(result, render_result);
853  }
854  }
855  else if (strcmp("inline_mustache", a->template_method) == 0)
856  {
857  if (a->edit_template_string)
858  {
859  Log(LOG_LEVEL_VERBOSE, "Rendering '%s' with method '%s'",
860  filename, a->template_method);
861 
862  PromiseResult render_result = RenderTemplateMustacheFromString(ctx, pp, a, edcontext);
863  result = PromiseResultUpdate(result, render_result);
864  }
865  }
866 
867 exit:
868  FinishEditContext(ctx, edcontext, a, pp, &result);
869  YieldCurrentLock(thislock);
870  return result;
871 }
872 
873 /*****************************************************************************/
874 
876 {
877  PromiseBanner(ctx, pp);
878  return FindFilePromiserObjects(ctx, pp);
879 }
880 
881 /*****************************************************************************/
882 
884 {
885  char *val = PromiseGetConstraintAsRval(pp, "pathtype", RVAL_TYPE_SCALAR);
886  int literal = (PromiseGetConstraintAsBoolean(ctx, "copy_from", pp)) || ((val != NULL) && (strcmp(val, "literal") == 0));
887 
888 /* Check if we are searching over a regular expression */
889 
891  if (literal)
892  {
893  // Prime the promiser temporarily, may override later
894  result = PromiseResultUpdate(result, VerifyFilePromise(ctx, pp->promiser, pp));
895  }
896  else // Default is to expand regex paths
897  {
899  }
900 
901  return result;
902 }
PromiseResult PromiseResultUpdate(PromiseResult prior, PromiseResult evidence)
Definition: actuator.c:28
char * xstrdup(const char *str)
Definition: alloc-mini.c:56
#define FREE_AND_NULL(ptr)
Definition: alloc.h:43
#define DESTROY_AND_NULL(destroy, ptr)
Definition: alloc.h:42
Attributes GetFilesAttributes(const EvalContext *ctx, const Promise *pp)
Definition: attributes.c:46
void ClearFilesAttributes(Attributes *whom)
Definition: attributes.c:40
void BufferDestroy(Buffer *buffer)
Destroys a buffer and frees the memory associated with it.
Definition: buffer.c:72
Buffer * BufferNew(void)
Buffer initialization routine.
Definition: buffer.c:48
const char * BufferData(const Buffer *buffer)
Provides a pointer to the internal data.
Definition: buffer.c:470
unsigned int BufferSize(const Buffer *buffer)
Returns the size of the buffer.
Definition: buffer.c:464
@ RVAL_TYPE_CONTAINER
Definition: cf3.defs.h:609
@ RVAL_TYPE_SCALAR
Definition: cf3.defs.h:606
@ RVAL_TYPE_FNCALL
Definition: cf3.defs.h:608
PromiseResult
Definition: cf3.defs.h:122
@ PROMISE_RESULT_CHANGE
Definition: cf3.defs.h:125
@ PROMISE_RESULT_NOOP
Definition: cf3.defs.h:124
@ PROMISE_RESULT_WARN
Definition: cf3.defs.h:126
@ PROMISE_RESULT_SKIPPED
Definition: cf3.defs.h:123
@ PROMISE_RESULT_FAIL
Definition: cf3.defs.h:127
@ cfa_warn
Definition: cf3.defs.h:719
@ CF_DATA_TYPE_STRING
Definition: cf3.defs.h:369
bool DONTDO
Definition: cf3globals.c:55
HashMethod CF_DEFAULT_DIGEST
Definition: cf3globals.c:88
time_t CFSTARTTIME
Definition: cf3globals.c:99
char VUQNAME[]
Definition: cf3globals.c:59
void free(void *)
#define CF_BUFSIZE
Definition: definitions.h:50
bool EvalContextVariableRemoveSpecial(const EvalContext *ctx, SpecialScope scope, const char *lval)
void cfPS(EvalContext *ctx, LogLevel level, PromiseResult status, const Promise *pp, const Attributes *attr, const char *fmt,...)
const Bundle * EvalContextResolveBundleExpression(const EvalContext *ctx, const Policy *policy, const char *callee_reference, const char *callee_type)
Find a bundle for a bundle call, given a callee reference (in the form of ns:bundle),...
void EvalContextStackPushBundleFrame(EvalContext *ctx, const Bundle *owner, const Rlist *args, bool inherits_previous)
void EvalContextStackPopFrame(EvalContext *ctx)
bool EvalContextVariablePutSpecial(EvalContext *ctx, SpecialScope scope, const char *lval, const void *value, DataType type, const char *tags)
JsonElement * DefaultTemplateData(const EvalContext *ctx, const char *wantbundle)
Rlist * ExpandList(EvalContext *ctx, const char *ns, const char *scope, const Rlist *list, int expandnaked)
Definition: expand.c:466
void BundleResolve(EvalContext *ctx, const Bundle *bundle)
Definition: expand.c:800
char * ExpandScalar(const EvalContext *ctx, const char *ns, const char *scope, const char *string, Buffer *out)
Definition: expand.c:516
int safe_chdir(const char *path)
Definition: file_lib.c:903
bool FileCanOpen(const char *path, const char *modes)
Definition: file_lib.c:46
Writer * FileReadFromFd(int fd, size_t max_size, bool *truncated)
Definition: file_lib.c:207
FILE * safe_fopen(const char *const path, const char *const mode)
Definition: file_lib.c:812
int safe_open(const char *pathname, int flags)
Definition: file_lib.c:516
NewLineMode
Definition: file_lib.h:35
@ NewLineMode_Native
Definition: file_lib.h:37
EditContext * NewEditContext(char *filename, const Attributes *a)
Definition: files_edit.c:39
void FinishEditContext(EvalContext *ctx, EditContext *ec, const Attributes *a, const Promise *pp, PromiseResult *result)
Definition: files_edit.c:91
Bundle * MakeTemporaryBundleFromTemplate(EvalContext *ctx, Policy *policy, const Attributes *a, const Promise *pp, PromiseResult *result)
bool ScheduleEditLineOperations(EvalContext *ctx, const Bundle *bp, const Attributes *a, const Promise *parentp, EditContext *edcontext)
bool ScheduleEditXmlOperations(EvalContext *ctx, const Bundle *bp, const Attributes *a, const Promise *parentp, EditContext *edcontext)
bool ChopLastNode(char *str)
Definition: files_names.c:422
const char * ReadLastNode(const char *str)
Definition: files_names.c:539
bool SaveAsFile(SaveCallbackFn callback, void *param, const char *file, const Attributes *a, NewLineMode new_line_mode)
bool SelectLeaf(EvalContext *ctx, char *path, const struct stat *sb, const FileSelect *fs)
Definition: files_select.c:58
int errno
#define NULL
Definition: getopt1.c:56
bool HashesMatch(const unsigned char digest1[EVP_MAX_MD_SIZE+1], const unsigned char digest2[EVP_MAX_MD_SIZE+1], HashMethod type)
Definition: hash.c:594
void HashString(const char *const buffer, const int len, unsigned char digest[EVP_MAX_MD_SIZE+1], HashMethod type)
Definition: hash.c:478
void HashFile(const char *const filename, unsigned char digest[EVP_MAX_MD_SIZE+1], HashMethod type, bool text_mode)
Definition: hash.c:443
void JsonDestroy(JsonElement *const element)
Destroy a JSON element.
Definition: json.c:386
void YieldCurrentLock(CfLock lock)
Definition: locks.c:948
CfLock AcquireLock(EvalContext *ctx, const char *operand, const char *host, time_t now, int ifelapsed, int expireafter, const Promise *pp, bool ignoreProcesses)
Definition: locks.c:789
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_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
bool MustacheRender(Buffer *out, const char *input, const JsonElement *hash)
Definition: mustache.c:896
void PromiseBanner(EvalContext *ctx, const Promise *pp)
Definition: ornaments.c:127
int lstat(const char *file_name, struct stat *buf)
#define S_ISDIR(m)
Definition: platform.h:916
#define O_TEXT
Definition: platform.h:1005
#define S_ISREG(m)
Definition: platform.h:913
#define ENOLINK
Definition: platform.h:808
Constraint * PromiseGetConstraint(const Promise *pp, const char *lval)
Get the first effective constraint from the promise, also does some checking.
Definition: policy.c:2944
const Policy * PolicyFromPromise(const Promise *promise)
Convenience function to get the policy object associated with a promise.
Definition: policy.c:477
void PolicyDestroy(Policy *policy)
Definition: policy.c:121
int PromiseGetConstraintAsBoolean(const EvalContext *ctx, const char *lval, const Promise *pp)
Get the trinary boolean value of the first effective constraint found matching, from a promise.
Definition: policy.c:2481
const Bundle * PromiseGetBundle(const Promise *pp)
Definition: policy.c:2671
void * PromiseGetConstraintAsRval(const Promise *pp, const char *lval, RvalType rtype)
Get the Rval value of the first effective constraint that matches the given type.
Definition: policy.c:3054
Policy * PolicyNew(void)
Definition: policy.c:100
PromiseResult LocateFilePromiserGroup(EvalContext *ctx, char *wildpath, const Promise *pp, PromiseResult(*fnptr)(EvalContext *ctx, char *path, const Promise *ptr))
void PromiseRef(LogLevel level, const Promise *pp)
Definition: promises.c:769
void RlistDestroy(Rlist *rl)
Definition: rlist.c:501
@ SPECIAL_SCOPE_THIS
Definition: scope.h:39
char * strdup(const char *str)
Definition: strdup.c:36
Rlist * acl_entries
Definition: cf3.defs.h:862
int haveselect
Definition: cf3.defs.h:1587
int haveperms
Definition: cf3.defs.h:1590
FileLink link
Definition: cf3.defs.h:1542
char * transformer
Definition: cf3.defs.h:1553
EditDefaults edits
Definition: cf3.defs.h:1543
char * edit_template
Definition: cf3.defs.h:1557
int haveeditxml
Definition: cf3.defs.h:1595
char * file_type
Definition: cf3.defs.h:1555
int haveedit
Definition: cf3.defs.h:1596
FileChange change
Definition: cf3.defs.h:1541
int haveeditline
Definition: cf3.defs.h:1594
char * template_method
Definition: cf3.defs.h:1559
int havechange
Definition: cf3.defs.h:1591
JsonElement * template_data
Definition: cf3.defs.h:1560
int haverename
Definition: cf3.defs.h:1588
DirectoryRecursion recursion
Definition: cf3.defs.h:1566
char * edit_template_string
Definition: cf3.defs.h:1558
DefineClasses classes
Definition: cf3.defs.h:1568
int create
Definition: cf3.defs.h:1562
FileSelect select
Definition: cf3.defs.h:1536
int havecopy
Definition: cf3.defs.h:1592
TransactionContext transaction
Definition: cf3.defs.h:1567
int havedepthsearch
Definition: cf3.defs.h:1586
int havelink
Definition: cf3.defs.h:1593
int havedelete
Definition: cf3.defs.h:1589
Definition: buffer.h:50
Definition: policy.h:70
char * name
Definition: policy.h:74
char * lock
Definition: cf3.defs.h:895
Rval rval
Definition: policy.h:133
Rlist * change
Definition: cf3.defs.h:954
Rlist * kept
Definition: cf3.defs.h:958
Rlist * del_kept
Definition: cf3.defs.h:962
Rlist * del_notkept
Definition: cf3.defs.h:963
Rlist * failure
Definition: cf3.defs.h:955
Rlist * denied
Definition: cf3.defs.h:956
Rlist * timeout
Definition: cf3.defs.h:957
Rlist * del_change
Definition: cf3.defs.h:961
int num_edits
Definition: files_edit.h:48
char * filename
Definition: files_edit.h:46
NewLineMode new_line_mode
Definition: files_edit.h:53
int num_rewrites
Definition: files_edit.h:49
int report_diffs
Definition: cf3.defs.h:1086
char * result
Definition: cf3.defs.h:1053
Definition: fncall.h:31
char * name
Definition: fncall.h:32
Rlist * args
Definition: fncall.h:33
Definition: policy.h:53
char * promiser
Definition: policy.h:115
Definition: rlist.h:35
RvalType type
Definition: cf3.defs.h:616
char * log_repaired
Definition: cf3.defs.h:933
enum cfopaction action
Definition: cf3.defs.h:927
Definition: writer.c:45
PromiseResult FindAndVerifyFilesPromises(EvalContext *ctx, const Promise *pp)
Definition: verify_files.c:875
Attributes GetExpandedAttributes(EvalContext *ctx, const Promise *pp, const Attributes *attr)
Definition: verify_files.c:224
static bool SaveBufferCallback(const char *dest_filename, void *param, NewLineMode new_line_mode)
Definition: verify_files.c:571
static PromiseResult VerifyFilePromise(EvalContext *ctx, char *path, const Promise *pp)
Definition: verify_files.c:277
static PromiseResult RenderTemplateMustache(EvalContext *ctx, const Promise *pp, const Attributes *attr, EditContext *edcontext, const char *template)
Definition: verify_files.c:605
static PromiseResult RenderTemplateCFEngine(EvalContext *ctx, const Promise *pp, const Rlist *bundle_args, const Attributes *attr, EditContext *edcontext)
Definition: verify_files.c:539
static bool FileSanityChecks(char *path, const Attributes *a, const Promise *pp)
Definition: verify_files.c:65
static PromiseResult FindFilePromiserObjects(EvalContext *ctx, const Promise *pp)
Definition: verify_files.c:883
static PromiseResult RenderTemplateMustacheFromFile(EvalContext *ctx, const Promise *pp, const Attributes *a, EditContext *edcontext)
Definition: verify_files.c:686
static char * ExpandThisPromiserScalar(EvalContext *ctx, const char *ns, const char *scope, const char *source)
Definition: verify_files.c:208
PromiseResult ScheduleEditOperation(EvalContext *ctx, char *filename, const Attributes *a, const Promise *pp)
Definition: verify_files.c:734
void ClearExpandedAttributes(Attributes *a)
Definition: verify_files.c:254
static PromiseResult RenderTemplateMustacheFromString(EvalContext *ctx, const Promise *pp, const Attributes *a, EditContext *edcontext)
Definition: verify_files.c:719
static bool AttrHasNoAction(const Attributes *attr)
Definition: verify_files.c:187
PromiseResult ScheduleLinkOperation(EvalContext *ctx, char *destination, char *source, const Attributes *attr, const Promise *pp)
PromiseResult ScheduleCopyOperation(EvalContext *ctx, char *destination, const Attributes *attr, const Promise *pp)
bool CfCreateFile(EvalContext *ctx, char *file, const Promise *pp, const Attributes *attr, PromiseResult *result)
void VerifyFileLeaf(EvalContext *ctx, char *path, const struct stat *sb, const Attributes *attr, const Promise *pp, PromiseResult *result)
bool DepthSearch(EvalContext *ctx, char *name, const struct stat *sb, int rlevel, const Attributes *attr, const Promise *pp, dev_t rootdevice, PromiseResult *result)
PromiseResult ScheduleLinkChildrenOperation(EvalContext *ctx, char *destination, char *source, int recurse, const Attributes *a, const Promise *pp)
const char * StringWriterData(const Writer *writer)
Definition: writer.c:229
void WriterClose(Writer *writer)
Definition: writer.c:242