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_utils.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_utils.h>
26 
27 #include <actuator.h>
28 #include <attributes.h>
29 #include <dir.h>
30 #include <files_names.h>
31 #include <files_links.h>
32 #include <files_copy.h>
33 #include <files_properties.h>
34 #include <locks.h>
35 #include <instrumentation.h>
36 #include <match_scope.h>
37 #include <files_interfaces.h>
38 #include <promises.h>
39 #include <files_operators.h>
40 #include <item_lib.h>
41 #include <client_code.h>
42 #include <hash.h>
43 #include <files_repository.h>
44 #include <files_select.h>
45 #include <files_changes.h>
46 #include <expand.h>
47 #include <conversion.h>
48 #include <pipes.h>
49 #include <verify_acl.h>
50 #include <eval_context.h>
51 #include <vars.h>
52 #include <exec_tools.h>
53 #include <comparray.h>
54 #include <string_lib.h>
55 #include <files_lib.h>
56 #include <rlist.h>
57 #include <policy.h>
58 #include <scope.h>
59 #include <misc_lib.h>
60 #include <abstract_dir.h>
61 #include <verify_files_hashes.h>
62 #include <audit.h>
63 #include <retcode.h>
65 #include <conn_cache.h>
66 #include <stat_cache.h> /* remote_stat,StatCacheLookup */
67 #include <known_dirs.h>
68 
69 #include <cf-windows-functions.h>
70 
71 #define CF_RECURSION_LIMIT 100
72 
73 static const Rlist *AUTO_DEFINE_LIST = NULL; /* GLOBAL_P */
74 
75 static Item *VSETXIDLIST = NULL;
76 
77 const Rlist *SINGLE_COPY_LIST = NULL; /* GLOBAL_P */
78 StringSet *SINGLE_COPY_CACHE = NULL; /* GLOBAL_X */
79 
80 static bool TransformFile(EvalContext *ctx, char *file, const Attributes *attr, const Promise *pp, PromiseResult *result);
81 static PromiseResult VerifyName(EvalContext *ctx, char *path, const struct stat *sb, const Attributes *attr, const Promise *pp);
83  const char *path, const struct stat *sb,
84  const Attributes *attr, const Promise *pp);
85 static PromiseResult VerifyCopy(EvalContext *ctx, const char *source, char *destination, const Attributes *attr, const Promise *pp,
86  CompressedArray **inode_cache, AgentConnection *conn);
87 static PromiseResult TouchFile(EvalContext *ctx, char *path, const Attributes *attr, const Promise *pp);
88 static PromiseResult VerifyFileAttributes(EvalContext *ctx, const char *file, const struct stat *dstat, const Attributes *attr, const Promise *pp);
89 static bool PushDirState(EvalContext *ctx, char *name, const struct stat *sb);
90 static bool PopDirState(int goback, char *name, const struct stat *sb, DirectoryRecursion r);
91 static bool CheckLinkSecurity(const struct stat *sb, char *name);
92 static bool CompareForFileCopy(char *sourcefile, char *destfile, const struct stat *ssb, const struct stat *dsb, const FileCopy *fc, AgentConnection *conn);
93 static void FileAutoDefine(EvalContext *ctx, char *destfile);
94 static void TruncateFile(char *name);
95 static void RegisterAHardLink(int i, char *value, const Attributes *attr, CompressedArray **inode_cache);
96 static PromiseResult VerifyCopiedFileAttributes(EvalContext *ctx, const char *src, const char *dest, const struct stat *sstat, const struct stat *dstat, const Attributes *attr, const Promise *pp);
97 static int cf_stat(const char *file, struct stat *buf, const FileCopy *fc, AgentConnection *conn);
98 #ifndef __MINGW32__
99 static int cf_readlink(EvalContext *ctx, char *sourcefile, char *linkbuf, int buffsize, const Attributes *attr, const Promise *pp, AgentConnection *conn, PromiseResult *result);
100 #endif
101 static bool SkipDirLinks(EvalContext *ctx, char *path, const char *lastnode, DirectoryRecursion r);
102 static bool DeviceBoundary(const struct stat *sb, dev_t rootdevice);
103 static PromiseResult LinkCopy(EvalContext *ctx, char *sourcefile, char *destfile, const struct stat *sb, const Attributes *attr,
104  const Promise *pp, CompressedArray **inode_cache, AgentConnection *conn);
105 
106 #ifndef __MINGW32__
107 static void LoadSetxid(void);
108 static void SaveSetxid(bool modified);
109 static PromiseResult VerifySetUidGid(EvalContext *ctx, const char *file, const struct stat *dstat, mode_t newperm, const Promise *pp, const Attributes *attr);
110 #endif
111 #ifdef __APPLE__
112 static int VerifyFinderType(EvalContext *ctx, const char *file, const Attributes *a, const Promise *pp, PromiseResult *result);
113 #endif
114 static void VerifyFileChanges(const char *file, const struct stat *sb, const Attributes *attr, const Promise *pp);
115 static PromiseResult VerifyFileIntegrity(EvalContext *ctx, const char *file, const Attributes *attr, const Promise *pp);
116 
117 extern Attributes GetExpandedAttributes(EvalContext *ctx, const Promise *pp, const Attributes *attr);
118 extern void ClearExpandedAttributes(Attributes *a);
119 
120 void SetFileAutoDefineList(const Rlist *auto_define_list)
121 {
122  AUTO_DEFINE_LIST = auto_define_list;
123 }
124 
125 void VerifyFileLeaf(EvalContext *ctx, char *path, const struct stat *sb, const Attributes *attr, const Promise *pp, PromiseResult *result)
126 {
127  // FIXME: This function completely ignores it's attr argument
128  assert(attr != NULL);
129 
130 /* Here we can assume that we are in the parent directory of the leaf */
131 
132  Log(LOG_LEVEL_VERBOSE, "Handling file existence constraints on '%s'", path);
133 
134  /* Update this.promiser again, and overwrite common attributes (classes, action) accordingly */
135 
136  EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_THIS, "promiser", path, CF_DATA_TYPE_STRING, "source=promise"); // Parameters may only be scalars
137  Attributes org_attr = GetFilesAttributes(ctx, pp);
138  Attributes new_attr = GetExpandedAttributes(ctx, pp, &org_attr);
139 
140  if (new_attr.transformer != NULL)
141  {
142  if (!TransformFile(ctx, path, &new_attr, pp, result))
143  {
144  /* NOP? */
145  }
146  }
147  else
148  {
149  if (new_attr.haverename)
150  {
151  *result = PromiseResultUpdate(*result, VerifyName(ctx, path, sb, &new_attr, pp));
152  }
153 
154  if (new_attr.havedelete)
155  {
156  *result = PromiseResultUpdate(*result, VerifyDelete(ctx, path, sb, &new_attr, pp));
157  }
158 
159  if (new_attr.touch)
160  {
161  *result = PromiseResultUpdate(*result, TouchFile(ctx, path, &new_attr, pp)); // intrinsically non-convergent op
162  }
163  }
164 
165  if (new_attr.haveperms || new_attr.havechange || new_attr.acl.acl_entries)
166  {
167  if (S_ISDIR(sb->st_mode) && new_attr.recursion.depth && !new_attr.recursion.include_basedir &&
168  (strcmp(path, pp->promiser) == 0))
169  {
170  Log(LOG_LEVEL_VERBOSE, "Promise to skip base directory '%s'", path);
171  }
172  else
173  {
174  *result = PromiseResultUpdate(*result, VerifyFileAttributes(ctx, path, sb, &new_attr, pp));
175  }
176  }
177  ClearExpandedAttributes(&new_attr);
178 }
179 
180 /* Checks whether item matches a list of wildcards */
181 static bool MatchRlistItem(EvalContext *ctx, const Rlist *listofregex, const char *teststring)
182 {
183  for (const Rlist *rp = listofregex; rp != NULL; rp = rp->next)
184  {
185  /* Avoid using regex if possible, due to memory leak */
186 
187  if (strcmp(teststring, RlistScalarValue(rp)) == 0)
188  {
189  return true;
190  }
191 
192  /* Make it commutative */
193 
194  if (FullTextMatch(ctx, RlistScalarValue(rp), teststring))
195  {
196  return true;
197  }
198  }
199 
200  return false;
201 }
202 
203 /* (conn == NULL) then copy is from localhost. */
204 static PromiseResult CfCopyFile(EvalContext *ctx, char *sourcefile,
205  char *destfile, const struct stat *ssb,
206  const Attributes *a, const Promise *pp,
207  CompressedArray **inode_cache,
208  AgentConnection *conn)
209 {
210  assert(a != NULL);
211  const char *lastnode;
212  struct stat dsb;
213  int found;
214  const mode_t srcmode = ssb->st_mode;
215 
216  const char *server;
217  if (conn != NULL)
218  {
219  server = conn->this_server;
220  }
221  else
222  {
223  server = "localhost";
224  }
225 
226 #ifdef __MINGW32__
227  if (a->copy.copy_links != NULL)
228  {
230  "copy_from.copylink_patterns is ignored on Windows "
231  "(source files cannot be symbolic links)");
232  }
233 #endif /* __MINGW32__ */
234 
235  Attributes attr = *a; // TODO: Avoid this copy
236  attr.link.when_no_file = cfa_force;
237 
238  if (strcmp(sourcefile, destfile) == 0 &&
239  strcmp(server, "localhost") == 0)
240  {
242  "File copy promise loop: file/dir '%s' is its own source",
243  sourcefile);
244  return PROMISE_RESULT_NOOP;
245  }
246 
247  if (attr.haveselect && !SelectLeaf(ctx, sourcefile, ssb, &(attr.select)))
248  {
249  Log(LOG_LEVEL_DEBUG, "Skipping non-selected file '%s'", sourcefile);
250  return PROMISE_RESULT_NOOP;
251  }
252 
254  {
255  Log(LOG_LEVEL_INFO, "Skipping single-copied file '%s'", destfile);
256  return PROMISE_RESULT_NOOP;
257  }
258 
259  if (attr.copy.link_type != FILE_LINK_TYPE_NONE)
260  {
261  lastnode = ReadLastNode(sourcefile);
262 
263  if (MatchRlistItem(ctx, attr.copy.link_instead, lastnode))
264  {
265  if (MatchRlistItem(ctx, attr.copy.copy_links, lastnode))
266  {
268  "File %s matches both copylink_patterns and linkcopy_patterns"
269  " - promise loop (skipping)!",
270  sourcefile);
271  return PROMISE_RESULT_NOOP;
272  }
273  else
274  {
275  Log(LOG_LEVEL_VERBOSE, "Copy item '%s' marked for linking",
276  sourcefile);
277 #ifdef __MINGW32__
279  "Links are not yet supported on Windows"
280  " - copying '%s' instead", sourcefile);
281 #else
282  return LinkCopy(ctx, sourcefile, destfile, ssb,
283  &attr, pp, inode_cache, conn);
284 #endif
285  }
286  }
287  }
288 
289  found = lstat(destfile, &dsb);
290 
291  if (found != -1)
292  {
293  if ((S_ISLNK(dsb.st_mode) && attr.copy.link_type == FILE_LINK_TYPE_NONE)
294  || (S_ISLNK(dsb.st_mode) && !S_ISLNK(srcmode)))
295  {
296  if (!S_ISLNK(srcmode) &&
297  attr.copy.type_check &&
299  {
300  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, &attr,
301  "File image exists but destination type is silly "
302  "(file/dir/link doesn't match)");
304  return PROMISE_RESULT_FAIL;
305  }
306 
307  if (DONTDO)
308  {
310  "Need to remove old symbolic link '%s' "
311  "to make way for copy", destfile);
312  }
313  else
314  {
315  if (unlink(destfile) == -1)
316  {
317  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, &attr,
318  "Couldn't remove link '%s'. (unlink: %s)",
319  destfile, GetErrorStr());
320  return PROMISE_RESULT_FAIL;
321  }
322 
324  "Removing old symbolic link '%s' to make way for copy",
325  destfile);
326  found = -1;
327  }
328  }
329  }
330  else
331  {
332  MakeParentDirectory(destfile, true);
333  }
334 
335  if (attr.copy.min_size != CF_NOINT)
336  {
337  if ((ssb->st_size < attr.copy.min_size)
338  || (ssb->st_size > attr.copy.max_size))
339  {
340  cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, &attr,
341  "Source file '%s' size is not in the permitted safety range",
342  sourcefile);
343  return PROMISE_RESULT_NOOP;
344  }
345  }
346 
348  if (found == -1)
349  {
350  if (attr.transaction.action == cfa_warn)
351  {
352  cfPS(ctx, LOG_LEVEL_WARNING, PROMISE_RESULT_WARN, pp, &attr,
353  "Image file '%s' is non-existent and should be a copy of '%s'",
354  destfile, sourcefile);
356  }
357 
358  if (S_ISREG(srcmode) ||
359  (S_ISLNK(srcmode) && attr.copy.link_type == FILE_LINK_TYPE_NONE))
360  {
361  if (DONTDO)
362  {
364  "'%s' wasn't at destination (needs copying)",
365  destfile);
366  return result;
367  }
368  else
369  {
371  "'%s' wasn't at destination (copying)",
372  destfile);
373 
374  Log(LOG_LEVEL_INFO, "Copying from '%s:%s'",
375  server, sourcefile);
376  }
377 
378  if (S_ISLNK(srcmode) && attr.copy.link_type != FILE_LINK_TYPE_NONE)
379  {
380  Log(LOG_LEVEL_VERBOSE, "'%s' is a symbolic link", sourcefile);
381  result = PromiseResultUpdate(
382  result, LinkCopy(ctx, sourcefile, destfile, ssb,
383  &attr, pp, inode_cache, conn));
384  }
385  else if (CopyRegularFile(ctx, sourcefile, destfile, ssb, &attr,
386  pp, inode_cache, conn, &result))
387  {
388  if (stat(destfile, &dsb) == -1)
389  {
391  "Can't stat destination file '%s'. (stat: %s)",
392  destfile, GetErrorStr());
393  }
394  else
395  {
396  result = PromiseResultUpdate(
397  result, VerifyCopiedFileAttributes(ctx, sourcefile,
398  destfile, ssb,
399  &dsb, &attr, pp));
400  }
401 
403  "Updated file from '%s:%s'",
404  server, sourcefile);
405 
406  result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE);
407 
408  if (SINGLE_COPY_LIST)
409  {
411  }
412 
413  if (MatchRlistItem(ctx, AUTO_DEFINE_LIST, destfile))
414  {
415  FileAutoDefine(ctx, destfile);
416  }
417  }
418  else
419  {
421  &attr, "Copy from '%s:%s' failed",
422  server, sourcefile);
423  result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
424  }
425 
426  return result;
427  }
428 
429  if (S_ISFIFO(srcmode))
430  {
431 #ifdef HAVE_MKFIFO
432  if (DONTDO)
433  {
434  Log(LOG_LEVEL_INFO, "Need to make FIFO '%s'", destfile);
435  }
436  else if (mkfifo(destfile, srcmode))
437  {
438  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, &attr,
439  "Cannot create fifo '%s'. (mkfifo: %s)",
440  destfile, GetErrorStr());
441  result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
442  return result;
443  }
444 
445  cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_CHANGE, pp, &attr,
446  "Created fifo '%s'", destfile);
447  result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE);
448 #endif
449  }
450  else
451  {
452 #ifndef __MINGW32__ // only regular files on windows
453  if (S_ISBLK(srcmode) || S_ISCHR(srcmode) || S_ISSOCK(srcmode))
454  {
455  if (DONTDO)
456  {
457  Log(LOG_LEVEL_INFO, "Make BLK/CHR/SOCK '%s'", destfile);
458  }
459  else if (mknod(destfile, srcmode, ssb->st_rdev))
460  {
461  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, &attr,
462  "Cannot create special file '%s'. (mknod: %s)",
463  destfile, GetErrorStr());
464  result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
465  return result;
466  }
467 
468  cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_CHANGE, pp, &attr,
469  "Created special file/device '%s'.", destfile);
470  result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE);
471  }
472 #endif /* !__MINGW32__ */
473  }
474 
475  if ((S_ISLNK(srcmode)) && (attr.copy.link_type != FILE_LINK_TYPE_NONE))
476  {
477  result = PromiseResultUpdate(
478  result, LinkCopy(ctx, sourcefile, destfile, ssb,
479  &attr, pp, inode_cache, conn));
480  }
481  }
482  else
483  {
484  int ok_to_copy = false;
485 
486  Log(LOG_LEVEL_VERBOSE, "Destination file '%s' already exists",
487  destfile);
488 
489  if (attr.copy.compare == FILE_COMPARATOR_EXISTS)
490  {
492  "Existence only is promised, no copying required");
493  return result;
494  }
495 
496  if (!attr.copy.force_update)
497  {
498  ok_to_copy = CompareForFileCopy(sourcefile, destfile, ssb,
499  &dsb, &attr.copy, conn);
500  }
501  else
502  {
503  ok_to_copy = true;
504  }
505 
506  if (attr.copy.type_check &&
508  {
509  // Mask mode with S_IFMT to extract and compare file types
510  if ((dsb.st_mode & S_IFMT) != (srcmode & S_IFMT))
511  {
512  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, &attr,
513  "Promised file copy %s "
514  "exists but type mismatch with source '%s'",
515  destfile, sourcefile);
516  result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
517  return result;
518  }
519  }
520 
521  if (ok_to_copy && (attr.transaction.action == cfa_warn))
522  {
523  cfPS(ctx, LOG_LEVEL_WARNING, PROMISE_RESULT_WARN, pp, &attr,
524  "Image file '%s' exists but is not up to date wrt '%s' "
525  "(only a warning has been promised)",
526  destfile, sourcefile);
527  result = PromiseResultUpdate(result, PROMISE_RESULT_WARN);
528  return result;
529  }
530 
531  if (attr.copy.force_update ||
532  ok_to_copy ||
533  S_ISLNK(srcmode)) /* Always check links */
534  {
535  if (S_ISREG(srcmode) ||
537  {
538  if (DONTDO)
539  {
541  "Should update file '%s' from source '%s' on '%s'",
542  destfile, sourcefile, server);
543  return result;
544  }
545 
546  if (MatchRlistItem(ctx, AUTO_DEFINE_LIST, destfile))
547  {
548  FileAutoDefine(ctx, destfile);
549  }
550 
551  if (CopyRegularFile(ctx, sourcefile, destfile, ssb, &attr,
552  pp, inode_cache, conn, &result))
553  {
554  if (stat(destfile, &dsb) == -1)
555  {
557  pp, &attr,
558  "Can't stat destination '%s'. (stat: %s)",
559  destfile, GetErrorStr());
560  result = PromiseResultUpdate(
562  }
563  else
564  {
566  &attr, "Updated '%s' from source '%s' on '%s'",
567  destfile, sourcefile, server);
568  result = PromiseResultUpdate(
569  result, PROMISE_RESULT_CHANGE);
570  result = PromiseResultUpdate(
571  result, VerifyCopiedFileAttributes(ctx, sourcefile,
572  destfile, ssb,
573  &dsb, &attr, pp));
574  }
575 
576  if (RlistIsInListOfRegex(SINGLE_COPY_LIST, destfile))
577  {
579  }
580  }
581  else
582  {
583  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, &attr,
584  "Was not able to copy '%s' to '%s'",
585  sourcefile, destfile);
586  result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
587  }
588 
589  return result;
590  }
591  else if (S_ISLNK(srcmode))
592  {
593  result = PromiseResultUpdate(
594  result, LinkCopy(ctx, sourcefile, destfile, ssb,
595  &attr, pp, inode_cache, conn));
596  }
597  }
598  else
599  {
600  result = PromiseResultUpdate(
601  result, VerifyCopiedFileAttributes(ctx, sourcefile, destfile,
602  ssb, &dsb, &attr, pp));
603 
604  /* Now we have to check for single copy, even though nothing was
605  copied otherwise we can get oscillations between multipe
606  versions if type is based on a checksum */
607 
608  if (RlistIsInListOfRegex(SINGLE_COPY_LIST, destfile))
609  {
611  }
612 
613  cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, &attr,
614  "File '%s' is an up to date copy of source", destfile);
615  }
616  }
617 
618  return result;
619 }
620 
621 static PromiseResult PurgeLocalFiles(EvalContext *ctx, Item *filelist, const char *localdir, const Attributes *attr,
622  const Promise *pp, AgentConnection *conn)
623 {
624  assert(attr != NULL);
625  Dir *dirh;
626  struct stat sb;
627  const struct dirent *dirp;
628  char filename[CF_BUFSIZE] = { 0 };
629 
630  if (strlen(localdir) < 2)
631  {
632  Log(LOG_LEVEL_ERR, "Purge of '%s' denied - too dangerous!", localdir);
633  return PROMISE_RESULT_NOOP;
634  }
635 
636  /* If we purge with no authentication we wipe out EVERYTHING ! */
637 
638  if (conn && (!conn->authenticated))
639  {
640  Log(LOG_LEVEL_VERBOSE, "Not purge local files '%s' - no authenticated contact with a source", localdir);
641  return PROMISE_RESULT_NOOP;
642  }
643 
644  if (!attr->havedepthsearch)
645  {
646  Log(LOG_LEVEL_VERBOSE, "No depth search when copying '%s' so purging does not apply", localdir);
647  return PROMISE_RESULT_NOOP;
648  }
649 
650 /* chdir to minimize the risk of race exploits during copy (which is inherently dangerous) */
651 
652  if (safe_chdir(localdir) == -1)
653  {
654  Log(LOG_LEVEL_VERBOSE, "Can't chdir to local directory '%s'. (chdir: %s)", localdir, GetErrorStr());
655  return PROMISE_RESULT_NOOP;
656  }
657 
658  if ((dirh = DirOpen(".")) == NULL)
659  {
660  Log(LOG_LEVEL_VERBOSE, "Can't open local directory '%s'. (opendir: %s)", localdir, GetErrorStr());
661  return PROMISE_RESULT_NOOP;
662  }
663 
665  for (dirp = DirRead(dirh); dirp != NULL; dirp = DirRead(dirh))
666  {
667  if (!ConsiderLocalFile(dirp->d_name, localdir))
668  {
669  continue;
670  }
671 
672  if (!IsItemIn(filelist, dirp->d_name))
673  {
674  strncpy(filename, localdir, CF_BUFSIZE - 2);
675 
676  AddSlash(filename);
677 
678  if (strlcat(filename, dirp->d_name, CF_BUFSIZE) >= CF_BUFSIZE)
679  {
680  Log(LOG_LEVEL_ERR, "Path name is too long in PurgeLocalFiles");
681  }
682 
683  if (DONTDO || attr->transaction.action == cfa_warn)
684  {
685  Log(LOG_LEVEL_WARNING, "Need to purge '%s' from copy dest directory", filename);
686  result = PromiseResultUpdate(result, PROMISE_RESULT_WARN);
687  }
688  else
689  {
690  if (lstat(filename, &sb) == -1)
691  {
692  cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_INTERRUPTED, pp, attr, "Couldn't stat '%s' while purging. (lstat: %s)",
693  filename, GetErrorStr());
695  }
696  else if (S_ISDIR(sb.st_mode))
697  {
698  if (!DeleteDirectoryTree(filename))
699  {
700  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, attr, "Unable to purge directory tree '%s'", filename);
701  result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
702  }
703  else if (rmdir(filename) == -1)
704  {
705  if (errno != ENOENT)
706  {
707  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, attr, "Unable to purge directory '%s'", filename);
708  result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
709  }
710  }
711  else
712  {
713  cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_CHANGE, pp, attr, "Purged directory '%s' in copy dest directory", filename);
714  result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE);
715  }
716  }
717  else if (unlink(filename) == -1)
718  {
719  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, attr, "Couldn't delete '%s' while purging", filename);
720  result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
721  }
722  else
723  {
724  cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_CHANGE, pp, attr, "Purged file '%s' in copy dest directory", filename);
725  result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE);
726  }
727  }
728  }
729  }
730 
731  DirClose(dirh);
732 
733  return result;
734 }
735 
736 static PromiseResult SourceSearchAndCopy(EvalContext *ctx, const char *from, char *to, int maxrecurse, const Attributes *attr,
737  const Promise *pp, dev_t rootdevice, CompressedArray **inode_cache, AgentConnection *conn)
738 {
739  struct stat sb, dsb;
740  /* TODO overflow check all these str*cpy()s in here! */
741  char newfrom[CF_BUFSIZE], newto[CF_BUFSIZE];
742  Item *namecache = NULL;
743  const struct dirent *dirp;
744  AbstractDir *dirh;
745 
746  if (maxrecurse == 0) /* reached depth limit */
747  {
748  Log(LOG_LEVEL_DEBUG, "MAXRECURSE ran out, quitting at level '%s'", from);
749  return PROMISE_RESULT_NOOP;
750  }
751 
752  if (strlen(from) == 0) /* Check for root dir */
753  {
754  from = "/";
755  }
756 
757  /* Check that dest dir exists before starting */
758 
759  strlcpy(newto, to, sizeof(newto) - 10);
760  AddSlash(newto);
761  strcat(newto, "dummy");
762 
763  if (attr->transaction.action != cfa_warn)
764  {
765  struct stat tostat;
766 
767  if (!MakeParentDirectory(newto, attr->move_obstructions))
768  {
769  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, attr, "Unable to make directory for '%s' in file-copy '%s' to '%s'", newto,
770  attr->copy.source, attr->copy.destination);
771  return PROMISE_RESULT_FAIL;
772  }
773 
774  DeleteSlash(to);
775 
776  /* Set aside symlinks */
777 
778  if (lstat(to, &tostat) != 0)
779  {
780  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_WARN, pp, attr, "Unable to stat newly created directory '%s'. (lstat: %s)",
781  to, GetErrorStr());
782  return PROMISE_RESULT_WARN;
783  }
784 
785  if (S_ISLNK(tostat.st_mode))
786  {
787  char backup[CF_BUFSIZE];
788  mode_t mask;
789 
790  if (!attr->move_obstructions)
791  {
792  Log(LOG_LEVEL_INFO, "Path '%s' is a symlink. Unable to move it aside without move_obstructions is set",
793  to);
794  return PROMISE_RESULT_NOOP;
795  }
796 
797  strcpy(backup, to);
798  DeleteSlash(to);
799  strcat(backup, ".cf-moved");
800 
801  if (rename(to, backup) == -1)
802  {
803  Log(LOG_LEVEL_INFO, "Unable to backup old '%s'", to);
804  unlink(to);
805  }
806 
807  mask = umask(0);
808  if (mkdir(to, DEFAULTMODE) == -1)
809  {
810  Log(LOG_LEVEL_ERR, "Unable to make directory '%s'. (mkdir: %s)", to, GetErrorStr());
811  umask(mask);
812  return PROMISE_RESULT_NOOP;
813  }
814  umask(mask);
815  }
816  }
817 
818  /* Send OPENDIR command. */
819  if ((dirh = AbstractDirOpen(from, &(attr->copy), conn)) == NULL)
820  {
821  cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_INTERRUPTED, pp, attr, "copy can't open directory '%s'", from);
823  }
824 
825  /* No backslashes over the network. */
826  const char sep = (conn != NULL) ? '/' : FILE_SEPARATOR;
827 
829  for (dirp = AbstractDirRead(dirh); dirp != NULL; dirp = AbstractDirRead(dirh))
830  {
831  /* This sends 1st STAT command. */
832  if (!ConsiderAbstractFile(dirp->d_name, from, &(attr->copy), conn))
833  {
834  if (conn != NULL &&
836  {
838  attr, "connection error");
839  AbstractDirClose(dirh);
841  }
842  else
843  {
844  continue;
845  }
846  }
847 
848  if (attr->copy.purge)
849  {
850  /* Append this file to the list of files not to purge */
851  AppendItem(&namecache, dirp->d_name, NULL);
852  }
853 
854  /* Assemble pathnames. TODO check overflow. */
855  strlcpy(newfrom, from, sizeof(newfrom));
856  strlcpy(newto, to, sizeof(newto));
857 
858  if (!PathAppend(newfrom, sizeof(newfrom), dirp->d_name, sep))
859  {
860  Log(LOG_LEVEL_ERR, "Internal limit reached in SourceSearchAndCopy(),"
861  " source path too long: '%s' + '%s'",
862  newfrom, dirp->d_name);
863  AbstractDirClose(dirh);
864  return result; /* TODO return FAIL? */
865  }
866 
867  /* This issues a 2nd STAT command, hopefully served from cache. */
868 
869  if ((attr->recursion.travlinks) || (attr->copy.link_type == FILE_LINK_TYPE_NONE))
870  {
871  /* No point in checking if there are untrusted symlinks here,
872  since this is from a trusted source, by definition */
873 
874  if (cf_stat(newfrom, &sb, &(attr->copy), conn) == -1)
875  {
876  Log(LOG_LEVEL_VERBOSE, "Can't stat '%s'. (cf_stat: %s)", newfrom, GetErrorStr());
877  if (conn != NULL &&
879  {
881  attr, "connection error");
882  AbstractDirClose(dirh);
884  }
885  else
886  {
887  continue;
888  }
889  }
890  }
891  else
892  {
893  if (cf_lstat(newfrom, &sb, &(attr->copy), conn) == -1)
894  {
895  Log(LOG_LEVEL_VERBOSE, "Can't stat '%s'. (cf_stat: %s)", newfrom, GetErrorStr());
896  if (conn != NULL &&
898  {
899  cfPS(ctx, LOG_LEVEL_INFO,
900  PROMISE_RESULT_INTERRUPTED, pp, attr,
901  "connection error");
902  AbstractDirClose(dirh);
904  }
905  else
906  {
907  continue;
908  }
909  }
910  }
911 
912  /* If "collapse_destination_dir" is set, we skip subdirectories, which
913  * means we are not creating them in the destination folder. */
914 
915  if (!attr->copy.collapse ||
916  (attr->copy.collapse && !S_ISDIR(sb.st_mode)))
917  {
918  if (!PathAppend(newto, sizeof(newto), dirp->d_name,
920  {
922  "Internal limit reached in SourceSearchAndCopy(),"
923  " dest path too long: '%s' + '%s'",
924  newto, dirp->d_name);
925  AbstractDirClose(dirh);
926  return result; /* TODO result FAIL? */
927  }
928  }
929 
930  if ((attr->recursion.xdev) && (DeviceBoundary(&sb, rootdevice)))
931  {
932  Log(LOG_LEVEL_VERBOSE, "Skipping '%s' on different device", newfrom);
933  continue;
934  }
935 
936  if (S_ISDIR(sb.st_mode))
937  {
938  if (attr->recursion.travlinks)
939  {
940  Log(LOG_LEVEL_VERBOSE, "Traversing directory links during copy is too dangerous, pruned");
941  continue;
942  }
943 
944  if (SkipDirLinks(ctx, newfrom, dirp->d_name, attr->recursion))
945  {
946  continue;
947  }
948 
949  memset(&dsb, 0, sizeof(struct stat));
950 
951  /* Only copy dirs if we are tracking subdirs */
952 
953  if ((!attr->copy.collapse) && (stat(newto, &dsb) == -1))
954  {
955  if (mkdir(newto, 0700) == -1)
956  {
957  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_INTERRUPTED, pp, attr, "Can't make directory '%s'. (mkdir: %s)",
958  newto, GetErrorStr());
960  continue;
961  }
962 
963  if (stat(newto, &dsb) == -1)
964  {
966  "Can't stat local copy '%s' - failed to establish directory. (stat: %s)", newto, GetErrorStr());
968  continue;
969  }
970  }
971 
972  Log(LOG_LEVEL_VERBOSE, "Entering '%s'", newto);
973 
974  if (!attr->copy.collapse)
975  {
976  VerifyCopiedFileAttributes(ctx, newfrom, newto, &sb, &dsb, attr, pp);
977  }
978 
979  result = PromiseResultUpdate(result, SourceSearchAndCopy(ctx, newfrom, newto, maxrecurse - 1, attr,
980  pp, rootdevice, inode_cache, conn));
981  }
982  else
983  {
984  result = PromiseResultUpdate(result, VerifyCopy(ctx, newfrom, newto, attr, pp, inode_cache, conn));
985  }
986 
987  if (conn != NULL &&
989  {
991  attr, "connection error");
992  AbstractDirClose(dirh);
994  }
995  }
996 
997  if (attr->copy.purge)
998  {
999  PurgeLocalFiles(ctx, namecache, to, attr, pp, conn);
1000  DeleteItemList(namecache);
1001  }
1002 
1003  AbstractDirClose(dirh);
1004 
1005  return result;
1006 }
1007 
1009  const char *source, char *destination,
1010  const Attributes *attr, const Promise *pp,
1011  CompressedArray **inode_cache,
1012  AgentConnection *conn)
1013 {
1014  assert(attr != NULL);
1015  AbstractDir *dirh;
1016  char sourcefile[CF_BUFSIZE];
1017  char sourcedir[CF_BUFSIZE];
1018  char destdir[CF_BUFSIZE];
1019  char destfile[CF_BUFSIZE];
1020  struct stat ssb, dsb;
1021  const struct dirent *dirp;
1022  int found;
1023 
1024  if (attr->copy.link_type == FILE_LINK_TYPE_NONE)
1025  {
1026  Log(LOG_LEVEL_DEBUG, "Treating links as files for '%s'", source);
1027  found = cf_stat(source, &ssb, &(attr->copy), conn);
1028  }
1029  else
1030  {
1031  found = cf_lstat(source, &ssb, &(attr->copy), conn);
1032  }
1033 
1034  if (found == -1)
1035  {
1036  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, attr,
1037  "Can't stat '%s' in verify copy", source);
1038  return PROMISE_RESULT_FAIL;
1039  }
1040 
1041  if (ssb.st_nlink > 1) /* Preserve hard link structure when copying */
1042  {
1043  RegisterAHardLink(ssb.st_ino, destination, attr, inode_cache);
1044  }
1045 
1046  if (S_ISDIR(ssb.st_mode))
1047  {
1049 
1050  strcpy(sourcedir, source);
1051  AddSlash(sourcedir);
1052  strcpy(destdir, destination);
1053  AddSlash(destdir);
1054 
1055  if ((dirh = AbstractDirOpen(sourcedir, &(attr->copy), conn)) == NULL)
1056  {
1057  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, attr,
1058  "Can't open directory '%s'. (opendir: %s)",
1059  sourcedir, GetErrorStr());
1060  return PROMISE_RESULT_FAIL;
1061  }
1062 
1063  /* Now check any overrides */
1064 
1065  if (stat(destdir, &dsb) == -1)
1066  {
1067  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, attr,
1068  "Can't stat directory '%s'. (stat: %s)",
1069  destdir, GetErrorStr());
1070  result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
1071  }
1072  else
1073  {
1074  result = PromiseResultUpdate(
1075  result, VerifyCopiedFileAttributes(ctx, sourcedir, destdir,
1076  &ssb, &dsb, attr, pp));
1077  }
1078 
1079  /* No backslashes over the network. */
1080  const char sep = (conn != NULL) ? '/' : FILE_SEPARATOR;
1081 
1082  for (dirp = AbstractDirRead(dirh); dirp != NULL;
1083  dirp = AbstractDirRead(dirh))
1084  {
1085  if (!ConsiderAbstractFile(dirp->d_name, sourcedir,
1086  &(attr->copy), conn))
1087  {
1088  if (conn != NULL &&
1090  {
1092  pp, attr, "connection error");
1094  }
1095  else
1096  {
1097  continue;
1098  }
1099  }
1100 
1101  strcpy(sourcefile, sourcedir);
1102 
1103  if (!PathAppend(sourcefile, sizeof(sourcefile), dirp->d_name,
1104  sep))
1105  {
1106  /* TODO return FAIL */
1107  FatalError(ctx, "VerifyCopy sourcefile buffer limit");
1108  }
1109 
1110  strcpy(destfile, destdir);
1111 
1112  if (!PathAppend(destfile, sizeof(destfile), dirp->d_name,
1113  FILE_SEPARATOR))
1114  {
1115  /* TODO return FAIL */
1116  FatalError(ctx, "VerifyCopy destfile buffer limit");
1117  }
1118 
1119  if (attr->copy.link_type == FILE_LINK_TYPE_NONE)
1120  {
1121  if (cf_stat(sourcefile, &ssb, &(attr->copy), conn) == -1)
1122  {
1123  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, attr,
1124  "Can't stat source file (notlinked) '%s'. (stat: %s)",
1125  sourcefile, GetErrorStr());
1126  return PROMISE_RESULT_FAIL;
1127  }
1128  }
1129  else
1130  {
1131  if (cf_lstat(sourcefile, &ssb, &(attr->copy), conn) == -1)
1132  {
1133  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, attr,
1134  "Can't stat source file '%s'. (lstat: %s)",
1135  sourcefile, GetErrorStr());
1136  return PROMISE_RESULT_FAIL;
1137  }
1138  }
1139 
1140  result = PromiseResultUpdate(
1141  result, CfCopyFile(ctx, sourcefile, destfile, &ssb,
1142  attr, pp, inode_cache, conn));
1143  }
1144 
1145  AbstractDirClose(dirh);
1146  return result;
1147  }
1148  else
1149  {
1150  strcpy(sourcefile, source);
1151  strcpy(destfile, destination);
1152 
1153  return CfCopyFile(ctx, sourcefile, destfile, &ssb,
1154  attr, pp, inode_cache, conn);
1155  }
1156 }
1157 
1158 static PromiseResult LinkCopy(EvalContext *ctx, char *sourcefile, char *destfile, const struct stat *sb, const Attributes *attr, const Promise *pp,
1159  CompressedArray **inode_cache, AgentConnection *conn)
1160 /* Link the file to the source, instead of copying */
1161 #ifdef __MINGW32__
1162 {
1163  Log(LOG_LEVEL_VERBOSE, "Windows does not support symbolic links");
1164  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, attr, "Windows can't link '%s' to '%s'", sourcefile, destfile);
1165  return PROMISE_RESULT_FAIL;
1166 }
1167 #else /* !__MINGW32__ */
1168 {
1169  assert(attr != NULL);
1170  char linkbuf[CF_BUFSIZE];
1171  const char *lastnode;
1172  struct stat dsb;
1174 
1175  linkbuf[0] = '\0';
1176 
1177  if ((S_ISLNK(sb->st_mode)) && (cf_readlink(ctx, sourcefile, linkbuf, CF_BUFSIZE, attr, pp, conn, &result) == -1))
1178  {
1179  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, attr, "Can't readlink '%s'", sourcefile);
1180  return PROMISE_RESULT_FAIL;
1181  }
1182  else if (S_ISLNK(sb->st_mode))
1183  {
1184  Log(LOG_LEVEL_VERBOSE, "Checking link from '%s' to '%s'", destfile, linkbuf);
1185 
1186  if ((attr->copy.link_type == FILE_LINK_TYPE_ABSOLUTE) && (!IsAbsoluteFileName(linkbuf))) /* Not absolute path - must fix */
1187  {
1188  char vbuff[CF_BUFSIZE];
1189 
1190  strlcpy(vbuff, sourcefile, CF_BUFSIZE);
1191  ChopLastNode(vbuff);
1192  AddSlash(vbuff);
1193  strncat(vbuff, linkbuf, CF_BUFSIZE - 1);
1194  strlcpy(linkbuf, vbuff, CF_BUFSIZE);
1195  }
1196  }
1197  else
1198  {
1199  strlcpy(linkbuf, sourcefile, CF_BUFSIZE);
1200  }
1201 
1202  lastnode = ReadLastNode(sourcefile);
1203 
1204  if (MatchRlistItem(ctx, attr->copy.copy_links, lastnode))
1205  {
1206  struct stat ssb;
1207 
1208  ExpandLinks(linkbuf, sourcefile, 0);
1209  Log(LOG_LEVEL_VERBOSE, "Link item in copy '%s' marked for copying from '%s' instead", sourcefile,
1210  linkbuf);
1211  stat(linkbuf, &ssb);
1212  return CfCopyFile(ctx, linkbuf, destfile, &ssb, attr, pp, inode_cache, conn);
1213  }
1214 
1215  int status;
1216  switch (attr->copy.link_type)
1217  {
1219 
1220  if (*linkbuf == '.')
1221  {
1222  status = VerifyRelativeLink(ctx, destfile, linkbuf, attr, pp);
1223  }
1224  else
1225  {
1226  status = VerifyLink(ctx, destfile, linkbuf, attr, pp);
1227  }
1228  break;
1229 
1231  status = VerifyRelativeLink(ctx, destfile, linkbuf, attr, pp);
1232  break;
1233 
1235  status = VerifyAbsoluteLink(ctx, destfile, linkbuf, attr, pp);
1236  break;
1237 
1239  status = VerifyHardLink(ctx, destfile, linkbuf, attr, pp);
1240  break;
1241 
1242  default:
1243  ProgrammingError("Unhandled link type in switch: %d", attr->copy.link_type);
1244  }
1245 
1246  if ((status == PROMISE_RESULT_CHANGE) || (status == PROMISE_RESULT_NOOP))
1247  {
1248  if (lstat(destfile, &dsb) == -1)
1249  {
1250  Log(LOG_LEVEL_ERR, "Can't lstat '%s'. (lstat: %s)", destfile, GetErrorStr());
1251  }
1252  else
1253  {
1254  result = PromiseResultUpdate(result, VerifyCopiedFileAttributes(ctx, sourcefile, destfile, sb, &dsb, attr, pp));
1255  }
1256 
1257  if (status == PROMISE_RESULT_CHANGE)
1258  {
1259  cfPS(ctx, LOG_LEVEL_INFO, status, pp, attr, "Created link '%s'", destfile);
1260  result = PromiseResultUpdate(result, status);
1261  }
1262  else if (status == PROMISE_RESULT_NOOP)
1263  {
1264  cfPS(ctx, LOG_LEVEL_VERBOSE, status, pp, attr, "Link '%s' as promised", destfile);
1265  result = PromiseResultUpdate(result, status);
1266  }
1267  else // TODO: is this reachable?
1268  {
1269  cfPS(ctx, LOG_LEVEL_INFO, status, pp, attr, "Unable to create link '%s'", destfile);
1270  result = PromiseResultUpdate(result, status);
1271  }
1272  }
1273 
1274  return result;
1275 }
1276 #endif /* !__MINGW32__ */
1277 
1278 bool CopyRegularFile(EvalContext *ctx, const char *source, const char *dest, const struct stat *sstat,
1279  const Attributes *attr, const Promise *pp, CompressedArray **inode_cache,
1280  AgentConnection *conn, PromiseResult *result)
1281 {
1282  assert(attr != NULL);
1283  char backup[CF_BUFSIZE];
1284  char new[CF_BUFSIZE], *linkable;
1285  int remote = false, backupisdir = false, backupok = false, discardbackup;
1286 
1287 #ifdef HAVE_UTIME_H
1288  struct utimbuf timebuf;
1289 #endif
1290 
1291 #ifdef __APPLE__
1292 /* For later copy from new to dest */
1293  char *rsrcbuf;
1294  int rsrcbytesr; /* read */
1295  int rsrcbytesw; /* written */
1296  int rsrcbytesl; /* to read */
1297  int rsrcrd;
1298  int rsrcwd;
1299 
1300 /* Keep track of if a resrouce fork */
1301  int rsrcfork = 0;
1302 #endif
1303 
1304  discardbackup = ((attr->copy.backup == BACKUP_OPTION_NO_BACKUP) || (attr->copy.backup == BACKUP_OPTION_REPOSITORY_STORE));
1305 
1306  if (DONTDO)
1307  {
1308  Log(LOG_LEVEL_ERR, "Promise requires copy from '%s' to '%s'", source, dest);
1309  return false;
1310  }
1311 
1312  /* Make an assoc array of inodes used to preserve hard links */
1313 
1314  linkable = CompressedArrayValue(*inode_cache, sstat->st_ino);
1315 
1316  if (sstat->st_nlink > 1) /* Preserve hard links, if possible */
1317  {
1318  if ((CompressedArrayElementExists(*inode_cache, sstat->st_ino)) && (strcmp(dest, linkable) != 0))
1319  {
1320  unlink(dest);
1321  MakeHardLink(ctx, dest, linkable, attr, pp, result);
1322  return true;
1323  }
1324  }
1325 
1326  if (conn != NULL)
1327  {
1328  assert(attr->copy.servers && strcmp(RlistScalarValue(attr->copy.servers), "localhost"));
1329  Log(LOG_LEVEL_DEBUG, "This is a remote copy from server '%s'", RlistScalarValue(attr->copy.servers));
1330  remote = true;
1331  }
1332 
1333 #ifdef __APPLE__
1334  if (strstr(dest, _PATH_RSRCFORKSPEC))
1335  {
1336  char *tmpstr = xstrndup(dest, CF_BUFSIZE);
1337 
1338  rsrcfork = 1;
1339  /* Drop _PATH_RSRCFORKSPEC */
1340  char *forkpointer = strstr(tmpstr, _PATH_RSRCFORKSPEC);
1341  *forkpointer = '\0';
1342 
1343  strlcpy(new, tmpstr, CF_BUFSIZE);
1344 
1345  free(tmpstr);
1346  }
1347  else
1348 #endif
1349  {
1350  strlcpy(new, dest, CF_BUFSIZE);
1351 
1352  if (!JoinSuffix(new, sizeof(new), CF_NEW))
1353  {
1354  Log(LOG_LEVEL_ERR, "Unable to construct filename for copy");
1355  return false;
1356  }
1357  }
1358 
1359  struct stat dest_stat;
1360  int ret = stat(dest, &dest_stat);
1361  bool dest_exists = (ret == 0);
1362 
1363  if (remote)
1364  {
1365  if (conn->error)
1366  {
1367  return false;
1368  }
1369 
1370  if (!CopyRegularFileNet(source, new, sstat->st_size, attr->copy.encrypt, conn))
1371  {
1372  return false;
1373  }
1374  }
1375  else
1376  {
1377  // If preserve is true, retain permissions of source file
1378  if (attr->copy.preserve)
1379  {
1380  if (!CopyRegularFileDisk(source, new))
1381  {
1382  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, attr,
1383  "Failed copying file '%s' to '%s'", source, new);
1384  *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
1385  return false;
1386  }
1387  }
1388  else
1389  {
1390  // Never preserve SUID bit (0777)
1391  int mode = dest_exists ? (dest_stat.st_mode & 0777) : CF_PERMS_DEFAULT;
1392  if (!CopyRegularFileDiskPerms(source, new, mode))
1393  {
1394  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, attr,
1395  "Failed copying file '%s' to '%s'", source, new);
1396  *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
1397  return false;
1398  }
1399  }
1400 
1401 #ifdef HAVE_UTIME_H
1402  if (attr->copy.stealth)
1403  {
1404  timebuf.actime = sstat->st_atime;
1405  timebuf.modtime = sstat->st_mtime;
1406  utime(source, &timebuf);
1407  }
1408 #endif
1409  }
1410 
1411  Log(LOG_LEVEL_VERBOSE, "Copy of regular file succeeded '%s' to '%s'", source, new);
1412 
1413  backup[0] = '\0';
1414 
1415  if (dest_exists)
1416  {
1417  if (!discardbackup)
1418  {
1419  char stamp[CF_BUFSIZE];
1420  time_t stampnow;
1421 
1422  Log(LOG_LEVEL_DEBUG, "Backup file '%s'", source);
1423 
1424  strlcpy(backup, dest, CF_BUFSIZE);
1425 
1426  if (attr->copy.backup == BACKUP_OPTION_TIMESTAMP)
1427  {
1428  stampnow = time((time_t *) NULL);
1429  snprintf(stamp, CF_BUFSIZE - 1, "_%lu_%s",
1430  CFSTARTTIME, CanonifyName(ctime(&stampnow)));
1431 
1432  if (!JoinSuffix(backup, sizeof(backup), stamp))
1433  {
1434  return false;
1435  }
1436  }
1437 
1438  if (!JoinSuffix(backup, sizeof(backup), CF_SAVED))
1439  {
1440  return false;
1441  }
1442 
1443  /* Now in case of multiple copies of same object,
1444  * try to avoid overwriting original backup */
1445 
1446  if (lstat(backup, &dest_stat) != -1)
1447  {
1448  /* if there is a dir in the way */
1449  if (S_ISDIR(dest_stat.st_mode))
1450  {
1451  backupisdir = true;
1452  PurgeLocalFiles(ctx, NULL, backup, attr, pp, conn);
1453  rmdir(backup);
1454  }
1455 
1456  unlink(backup);
1457  }
1458 
1459  if (rename(dest, backup) == -1)
1460  {
1461  /* ignore */
1462  }
1463 
1464  /* Did the rename() succeed? NFS-safe */
1465  backupok = (lstat(backup, &dest_stat) != -1);
1466  }
1467  else
1468  {
1469  /* Mainly important if there is a dir in the way */
1470  if (S_ISDIR(dest_stat.st_mode))
1471  {
1472  PurgeLocalFiles(ctx, NULL, dest, attr, pp, conn);
1473  rmdir(dest);
1474  }
1475  }
1476  }
1477 
1478  struct stat new_stat;
1479  if (lstat(new, &new_stat) == -1)
1480  {
1481  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, attr, "Can't stat new file '%s' - another agent has picked it up?. (stat: %s)",
1482  new, GetErrorStr());
1483  *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
1484  return false;
1485  }
1486 
1487  if ((S_ISREG(new_stat.st_mode)) && (new_stat.st_size != sstat->st_size))
1488  {
1489  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, attr,
1490  "New file '%s' seems to have been corrupted in transit, destination %d and source %d, aborting.", new,
1491  (int) new_stat.st_size, (int) sstat->st_size);
1492  *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
1493 
1494  if (backupok)
1495  {
1496  rename(backup, dest); /* ignore failure of this call, as there is nothing more we can do */
1497  }
1498 
1499  return false;
1500  }
1501 
1502  if (attr->copy.verify)
1503  {
1504  Log(LOG_LEVEL_VERBOSE, "Final verification of transmission ...");
1505 
1506  if (CompareFileHashes(source, new, sstat, &new_stat, &(attr->copy), conn))
1507  {
1508  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, attr,
1509  "New file '%s' seems to have been corrupted in transit, aborting.", new);
1510  *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
1511 
1512  if (backupok)
1513  {
1514  rename(backup, dest);
1515  }
1516 
1517  return false;
1518  }
1519  else
1520  {
1521  Log(LOG_LEVEL_VERBOSE, "New file '%s' transmitted correctly - verified", new);
1522  }
1523  }
1524 
1525 #ifdef __APPLE__
1526  if (rsrcfork)
1527  { /* Can't just "mv" the resource fork, unfortunately */
1528  rsrcrd = safe_open(new, O_RDONLY | O_BINARY);
1529  rsrcwd = safe_open(dest, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC);
1530 
1531  if (rsrcrd == -1 || rsrcwd == -1)
1532  {
1533  Log(LOG_LEVEL_INFO, "Open of Darwin resource fork rsrcrd/rsrcwd failed. (open: %s)", GetErrorStr());
1534  close(rsrcrd);
1535  close(rsrcwd);
1536  return (false);
1537  }
1538 
1539  rsrcbuf = xmalloc(CF_BUFSIZE);
1540 
1541  rsrcbytesr = 0;
1542 
1543  while (1)
1544  {
1545  rsrcbytesr = read(rsrcrd, rsrcbuf, CF_BUFSIZE);
1546 
1547  if (rsrcbytesr == -1)
1548  { /* Ck error */
1549  if (errno == EINTR)
1550  {
1551  continue;
1552  }
1553  else
1554  {
1555  Log(LOG_LEVEL_INFO, "Read of Darwin resource fork rsrcrd failed. (read: %s)", GetErrorStr());
1556  close(rsrcrd);
1557  close(rsrcwd);
1558  free(rsrcbuf);
1559  return (false);
1560  }
1561  }
1562 
1563  else if (rsrcbytesr == 0)
1564  {
1565  /* Reached EOF */
1566  close(rsrcrd);
1567  close(rsrcwd);
1568  free(rsrcbuf);
1569 
1570  unlink(new); /* Go ahead and unlink .cfnew */
1571  break;
1572  }
1573 
1574  rsrcbytesl = rsrcbytesr;
1575  rsrcbytesw = 0;
1576 
1577  while (rsrcbytesl > 0)
1578  {
1579  rsrcbytesw += write(rsrcwd, rsrcbuf, rsrcbytesl);
1580 
1581  if (rsrcbytesw == -1)
1582  {
1583  if (errno == EINTR)
1584  {
1585  continue;
1586  }
1587  else
1588  {
1589  Log(LOG_LEVEL_INFO, "Write of Darwin resource fork rsrcwd failed.");
1590  close(rsrcrd);
1591  close(rsrcwd);
1592  free(rsrcbuf);
1593  return (false);
1594  }
1595  }
1596  rsrcbytesl = rsrcbytesr - rsrcbytesw;
1597  }
1598  }
1599  }
1600  else
1601 #endif
1602  {
1603  if (rename(new, dest) == -1)
1604  {
1605  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, attr,
1606  "Could not install copy file as '%s', directory in the way?. (rename: %s)",
1607  dest, GetErrorStr());
1608  *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
1609 
1610  if (backupok)
1611  {
1612  rename(backup, dest); /* ignore failure */
1613  }
1614 
1615  return false;
1616  }
1617  }
1618 
1619  if ((!discardbackup) && backupisdir)
1620  {
1621  Log(LOG_LEVEL_INFO, "Cannot move a directory to repository, leaving at '%s'", backup);
1622  }
1623  else if ((!discardbackup) && (ArchiveToRepository(backup, attr)))
1624  {
1625  unlink(backup);
1626  }
1627 
1628 #ifdef HAVE_UTIME_H
1629  if (attr->copy.stealth)
1630  {
1631  timebuf.actime = sstat->st_atime;
1632  timebuf.modtime = sstat->st_mtime;
1633  utime(dest, &timebuf);
1634  }
1635 #endif
1636 
1637  return true;
1638 }
1639 
1640 static bool TransformFile(EvalContext *ctx, char *file, const Attributes *attr, const Promise *pp, PromiseResult *result)
1641 {
1642  assert(attr != NULL);
1643  FILE *pop = NULL;
1644  int transRetcode = 0;
1645 
1646  if (attr->transformer == NULL || file == NULL)
1647  {
1648  return false;
1649  }
1650 
1651  Buffer *command = BufferNew();
1652  ExpandScalar(ctx, PromiseGetBundle(pp)->ns, PromiseGetBundle(pp)->name, attr->transformer, command);
1653  Log(LOG_LEVEL_INFO, "Transforming '%s' ", BufferData(command));
1654 
1655  if (!IsExecutable(CommandArg0(BufferData(command))))
1656  {
1657  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, attr, "Transformer '%s' for file '%s' failed", attr->transformer, file);
1658  *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
1659  BufferDestroy(command);
1660  return false;
1661  }
1662 
1663  if (!DONTDO)
1664  {
1665  CfLock thislock = AcquireLock(ctx, BufferData(command), VUQNAME, CFSTARTTIME, attr->transaction.ifelapsed, attr->transaction.expireafter, pp, false);
1666 
1667  if (thislock.lock == NULL)
1668  {
1669  BufferDestroy(command);
1670  return false;
1671  }
1672 
1673  if ((pop = cf_popen(BufferData(command), "r", true)) == NULL)
1674  {
1675  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, attr, "Transformer '%s' for file '%s' failed", attr->transformer, file);
1676  *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
1677  YieldCurrentLock(thislock);
1678  BufferDestroy(command);
1679  return false;
1680  }
1681 
1682  size_t line_size = CF_BUFSIZE;
1683  char *line = xmalloc(line_size);
1684 
1685  for (;;)
1686  {
1687  ssize_t res = CfReadLine(&line, &line_size, pop);
1688  if (res == -1)
1689  {
1690  if (!feof(pop))
1691  {
1692  cf_pclose(pop);
1693  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, attr, "Transformer '%s' for file '%s' failed", attr->transformer, file);
1694  *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
1695  YieldCurrentLock(thislock);
1696  free(line);
1697  BufferDestroy(command);
1698  return false;
1699  }
1700  else
1701  {
1702  break;
1703  }
1704  }
1705 
1706  Log(LOG_LEVEL_INFO, "%s", line);
1707  }
1708 
1709  free(line);
1710 
1711  transRetcode = cf_pclose(pop);
1712 
1713  if (VerifyCommandRetcode(ctx, transRetcode, attr, pp, result))
1714  {
1715  Log(LOG_LEVEL_INFO, "Transformer '%s' => '%s' seemed to work ok", file, BufferData(command));
1716  }
1717  else
1718  {
1719  Log(LOG_LEVEL_ERR, "Transformer '%s' => '%s' returned error", file, BufferData(command));
1720  }
1721 
1722  YieldCurrentLock(thislock);
1723  }
1724  else
1725  {
1726  Log(LOG_LEVEL_ERR, "Need to transform file '%s' with '%s'", file, BufferData(command));
1727  }
1728 
1729  BufferDestroy(command);
1730  return true;
1731 }
1732 
1733 static PromiseResult VerifyName(EvalContext *ctx, char *path, const struct stat *sb, const Attributes *attr, const Promise *pp)
1734 {
1735  assert(attr != NULL);
1736  mode_t newperm;
1737  struct stat dsb;
1738 
1739  if (lstat(path, &dsb) == -1)
1740  {
1741  cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_NOOP, pp, attr, "File object named '%s' is not there (promise kept)", path);
1742  return PROMISE_RESULT_NOOP;
1743  }
1744  else
1745  {
1746  if (attr->rename.disable)
1747  {
1748  Log(LOG_LEVEL_WARNING, "File object '%s' exists, contrary to promise", path);
1749  }
1750  }
1751 
1753  if (attr->rename.newname)
1754  {
1755  if (DONTDO)
1756  {
1757  Log(LOG_LEVEL_INFO, "File '%s' should be renamed to '%s' to keep promise", path, attr->rename.newname);
1758  return PROMISE_RESULT_NOOP;
1759  }
1760  else
1761  {
1762  if (!FileInRepository(attr->rename.newname))
1763  {
1764  if (rename(path, attr->rename.newname) == -1)
1765  {
1766  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, attr, "Error occurred while renaming '%s'. (rename: %s)",
1767  path, GetErrorStr());
1768  result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
1769  }
1770  else
1771  {
1772  cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_CHANGE, pp, attr, "Renaming file '%s' to '%s'", path, attr->rename.newname);
1773  result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE);
1774  }
1775  }
1776  else
1777  {
1778  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_WARN, pp, attr,
1779  "Rename to same destination twice? Would overwrite saved copy - aborting");
1780  result = PromiseResultUpdate(result, PROMISE_RESULT_WARN);
1781  }
1782  }
1783 
1784  return result;
1785  }
1786 
1787  if (S_ISLNK(dsb.st_mode))
1788  {
1789  if (attr->rename.disable)
1790  {
1791  if (!DONTDO)
1792  {
1793  if (unlink(path) == -1)
1794  {
1795  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, attr, "Unable to unlink '%s'. (unlink: %s)",
1796  path, GetErrorStr());
1797  result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
1798  }
1799  else
1800  {
1801  cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_CHANGE, pp, attr, "Disabling symbolic link '%s' by deleting it", path);
1802  result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE);
1803  }
1804  }
1805  else
1806  {
1807  Log(LOG_LEVEL_INFO, "Need to disable link '%s' to keep promise", path);
1808  }
1809 
1810  return result;
1811  }
1812  }
1813 
1814 /* Normal disable - has priority */
1815 
1816  if (attr->rename.disable)
1817  {
1818  char newname[CF_BUFSIZE];
1819 
1820  if (attr->transaction.action == cfa_warn)
1821  {
1822  cfPS(ctx, LOG_LEVEL_WARNING, PROMISE_RESULT_WARN, pp, attr, "'%s' '%s' should be renamed",
1823  S_ISDIR(sb->st_mode) ? "Directory" : "File", path);
1824  result = PromiseResultUpdate(result, PROMISE_RESULT_WARN);
1825  return result;
1826  }
1827 
1828  if (attr->rename.newname && strlen(attr->rename.newname) > 0)
1829  {
1830  if (IsAbsPath(attr->rename.newname))
1831  {
1832  strlcpy(path, attr->rename.newname, CF_BUFSIZE);
1833  }
1834  else
1835  {
1836  strcpy(newname, path);
1837  ChopLastNode(newname);
1838 
1839  if (!PathAppend(newname, sizeof(newname),
1840  attr->rename.newname, FILE_SEPARATOR))
1841  {
1842  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, attr,
1843  "Internal buffer limit in rename operation,"
1844  " destination: '%s' + '%s'",
1845  newname, attr->rename.newname);
1846  result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
1847  return result;
1848  }
1849  }
1850  }
1851  else
1852  {
1853  strcpy(newname, path);
1854 
1855  if (attr->rename.disable_suffix)
1856  {
1857  if (!JoinSuffix(newname, sizeof(newname), attr->rename.disable_suffix))
1858  {
1859  return result;
1860  }
1861  }
1862  else
1863  {
1864  if (!JoinSuffix(newname, sizeof(newname), ".cfdisabled"))
1865  {
1866  return result;
1867  }
1868  }
1869  }
1870 
1871  if ((attr->rename.plus != CF_SAMEMODE) && (attr->rename.minus != CF_SAMEMODE))
1872  {
1873  newperm = (sb->st_mode & 07777);
1874  newperm |= attr->rename.plus;
1875  newperm &= ~(attr->rename.minus);
1876  }
1877  else
1878  {
1879  newperm = (mode_t) 0600;
1880  }
1881 
1882  if (DONTDO)
1883  {
1884  Log(LOG_LEVEL_INFO, "File '%s' should be renamed to '%s' to keep promise", path, newname);
1885  return result;
1886  }
1887  else
1888  {
1889  safe_chmod(path, newperm);
1890 
1891  if (!FileInRepository(newname))
1892  {
1893  if (rename(path, newname) == -1)
1894  {
1895  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, attr, "Error occurred while renaming '%s'. (rename: %s)",
1896  path, GetErrorStr());
1897  result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
1898  return result;
1899  }
1900  else
1901  {
1902  cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_CHANGE, pp, attr, "Disabling/renaming file '%s' to '%s' with mode %04jo", path,
1903  newname, (uintmax_t)newperm);
1904  result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE);
1905  }
1906 
1907  if (ArchiveToRepository(newname, attr))
1908  {
1909  unlink(newname);
1910  }
1911  }
1912  else
1913  {
1914  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_WARN, pp, attr,
1915  "Disable required twice? Would overwrite saved copy - changing permissions only");
1916  result = PromiseResultUpdate(result, PROMISE_RESULT_WARN);
1917  }
1918  }
1919 
1920  return result;
1921  }
1922 
1923  if (attr->rename.rotate == 0)
1924  {
1925  if (attr->transaction.action == cfa_warn)
1926  {
1927  cfPS(ctx, LOG_LEVEL_WARNING, PROMISE_RESULT_WARN, pp, attr, "File '%s' should be truncated", path);
1928  result = PromiseResultUpdate(result, PROMISE_RESULT_WARN);
1929  }
1930  else if (!DONTDO)
1931  {
1932  TruncateFile(path);
1933  cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_CHANGE, pp, attr, "Truncating (emptying) '%s'", path);
1934  result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE);
1935  }
1936  else
1937  {
1938  Log(LOG_LEVEL_ERR, " * File '%s' needs emptying", path);
1939  }
1940  return result;
1941  }
1942 
1943  if (attr->rename.rotate > 0)
1944  {
1945  if (attr->transaction.action == cfa_warn)
1946  {
1947  cfPS(ctx, LOG_LEVEL_WARNING, PROMISE_RESULT_WARN, pp, attr, "File '%s' should be rotated", path);
1948  result = PromiseResultUpdate(result, PROMISE_RESULT_WARN);
1949  }
1950  else if (!DONTDO)
1951  {
1952  RotateFiles(path, attr->rename.rotate);
1953  cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_CHANGE, pp, attr, "Rotating files '%s' in %d fifo", path, attr->rename.rotate);
1954  result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE);
1955  }
1956  else
1957  {
1958  Log(LOG_LEVEL_ERR, "File '%s' needs rotating", path);
1959  }
1960 
1961  return result;
1962  }
1963 
1964  return result;
1965 }
1966 
1968  const char *path, const struct stat *sb,
1969  const Attributes *attr, const Promise *pp)
1970 {
1971  assert(attr != NULL);
1972  const char *lastnode = ReadLastNode(path);
1973  Log(LOG_LEVEL_VERBOSE, "Verifying file deletions for '%s'", path);
1974 
1975  if (DONTDO)
1976  {
1977  Log(LOG_LEVEL_INFO, "Promise requires deletion of file object '%s'",
1978  path);
1979  return PROMISE_RESULT_NOOP;
1980  }
1981 
1982  switch (attr->transaction.action)
1983  {
1984  case cfa_warn:
1985 
1986  cfPS(ctx, LOG_LEVEL_WARNING, PROMISE_RESULT_WARN, pp, attr,
1987  "%s '%s' should be deleted",
1988  S_ISDIR(sb->st_mode) ? "Directory" : "File", path);
1989  return PROMISE_RESULT_WARN;
1990  break;
1991 
1992  case cfa_fix:
1993 
1994  if (!S_ISDIR(sb->st_mode)) /* file,symlink */
1995  {
1996  int ret = unlink(lastnode);
1997  if (ret == -1)
1998  {
1999  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, attr,
2000  "Couldn't unlink '%s' tidying. (unlink: %s)",
2001  path, GetErrorStr());
2002  return PROMISE_RESULT_FAIL;
2003  }
2004  else
2005  {
2006  cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_CHANGE, pp, attr,
2007  "Deleted file '%s'", path);
2008  return PROMISE_RESULT_CHANGE;
2009  }
2010  }
2011  else /* directory */
2012  {
2013  if (!attr->delete.rmdirs)
2014  {
2015  Log(LOG_LEVEL_VERBOSE, "Keeping directory '%s' "
2016  "since \"rmdirs\" attribute was not specified",
2017  path);
2018  return PROMISE_RESULT_NOOP;
2019  }
2020 
2021  if (attr->havedepthsearch && strcmp(path, pp->promiser) == 0)
2022  {
2024  "Skipping deletion of parent directory for recursive promise '%s', "
2025  "you must specify separate promise for deleting",
2026  path);
2027  return PROMISE_RESULT_NOOP;
2028  }
2029 
2030  int ret = rmdir(lastnode);
2031  if (ret == -1 && errno != EEXIST && errno != ENOTEMPTY)
2032  {
2033  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, attr,
2034  "Delete directory '%s' failed (rmdir: %s)",
2035  path, GetErrorStr());
2036  return PROMISE_RESULT_FAIL;
2037  }
2038  else if (ret == -1 &&
2039  (errno == EEXIST || errno == ENOTEMPTY))
2040  {
2041  /* It's never allowed to delete non-empty directories, they
2042  * are silently skipped. */
2044  "Delete directory '%s' not empty, skipping", path);
2045  return PROMISE_RESULT_NOOP;
2046  }
2047  else
2048  {
2049  assert(ret != -1);
2050  cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_CHANGE, pp, attr,
2051  "Deleted directory '%s'", path);
2052  return PROMISE_RESULT_CHANGE;
2053  }
2054  }
2055  break;
2056 
2057  default:
2058  ProgrammingError("Unhandled file action in switch: %d",
2059  attr->transaction.action);
2060  }
2061 
2062  assert(false); /* Unreachable! */
2063  return PROMISE_RESULT_NOOP;
2064 }
2065 
2066 static PromiseResult TouchFile(EvalContext *ctx, char *path, const Attributes *attr, const Promise *pp)
2067 {
2069  if (!DONTDO && attr->transaction.action == cfa_fix)
2070  {
2071  if (utime(path, NULL) != -1)
2072  {
2073  cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_CHANGE, pp, attr, "Touched (updated time stamps) for path '%s'", path);
2074  result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE);
2075  }
2076  else
2077  {
2078  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, attr,
2079  "Touch '%s' failed to update timestamps. (utime: %s)", path, GetErrorStr());
2080  result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
2081  }
2082  }
2083  else
2084  {
2085  cfPS(ctx, LOG_LEVEL_WARNING, PROMISE_RESULT_WARN, pp, attr,
2086  "Need to touch (update time stamps) for '%s', but only a warning was promised!", path);
2087  result = PromiseResultUpdate(result, PROMISE_RESULT_WARN);
2088  }
2089 
2090  return result;
2091 }
2092 
2093 static PromiseResult VerifyFileAttributes(EvalContext *ctx, const char *file, const struct stat *dstat, const Attributes *attr, const Promise *pp)
2094 {
2096 
2097 #ifndef __MINGW32__
2098  mode_t newperm = dstat->st_mode, maskvalue;
2099 
2100 # if defined HAVE_CHFLAGS
2101  u_long newflags;
2102 # endif
2103 
2104  maskvalue = umask(0); /* This makes the DEFAULT modes absolute */
2105 
2106  newperm = (dstat->st_mode & 07777);
2107 
2108  if ((attr->perms.plus != CF_SAMEMODE) && (attr->perms.minus != CF_SAMEMODE))
2109  {
2110  newperm |= attr->perms.plus;
2111  newperm &= ~(attr->perms.minus);
2112  /* directories must have x set if r set, regardless */
2113 
2114  if (S_ISDIR(dstat->st_mode))
2115  {
2116  if (attr->perms.rxdirs)
2117  {
2118  Log(LOG_LEVEL_DEBUG, "Directory...fixing x bits");
2119 
2120  if (newperm & S_IRUSR)
2121  {
2122  newperm |= S_IXUSR;
2123  }
2124 
2125  if (newperm & S_IRGRP)
2126  {
2127  newperm |= S_IXGRP;
2128  }
2129 
2130  if (newperm & S_IROTH)
2131  {
2132  newperm |= S_IXOTH;
2133  }
2134  }
2135  else
2136  {
2137  Log(LOG_LEVEL_VERBOSE, "NB: rxdirs is set to false - x for r bits not checked");
2138  }
2139  }
2140  }
2141 
2142  result = PromiseResultUpdate(result, VerifySetUidGid(ctx, file, dstat, newperm, pp, attr));
2143 
2144 # ifdef __APPLE__
2145  if (VerifyFinderType(ctx, file, attr, pp, &result))
2146  {
2147  /* nop */
2148  }
2149 # endif
2150 #endif
2151 
2152  if (VerifyOwner(ctx, file, pp, attr, dstat, &result))
2153  {
2154  /* nop */
2155  }
2156 
2157 #ifdef __MINGW32__
2158  if (NovaWin_FileExists(file) && !NovaWin_IsDir(file))
2159 #else
2160  if (attr->havechange && S_ISREG(dstat->st_mode))
2161 #endif
2162  {
2163  result = PromiseResultUpdate(result, VerifyFileIntegrity(ctx, file, attr, pp));
2164  }
2165 
2166  if (attr->havechange)
2167  {
2168  VerifyFileChanges(file, dstat, attr, pp);
2169  }
2170 
2171 #ifndef __MINGW32__
2172  if (S_ISLNK(dstat->st_mode)) /* No point in checking permission on a link */
2173  {
2174  KillGhostLink(ctx, file, attr, pp, &result);
2175  umask(maskvalue);
2176  return result;
2177  }
2178 #endif
2179 
2180 
2181 #ifndef __MINGW32__
2182  result = PromiseResultUpdate(result, VerifySetUidGid(ctx, file, dstat, dstat->st_mode, pp, attr));
2183 
2184  if ((newperm & 07777) == (dstat->st_mode & 07777)) /* file okay */
2185  {
2186  Log(LOG_LEVEL_DEBUG, "File okay, newperm '%jo', stat '%jo'",
2187  (uintmax_t) (newperm & 07777), (uintmax_t) (dstat->st_mode & 07777));
2188  cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, attr,
2189  "File permissions on '%s' as promised", file);
2190  result = PromiseResultUpdate(result, PROMISE_RESULT_NOOP);
2191  }
2192  else
2193  {
2194  Log(LOG_LEVEL_DEBUG, "Trying to fix mode...newperm '%jo', stat '%jo'",
2195  (uintmax_t) (newperm & 07777), (uintmax_t) (dstat->st_mode & 07777));
2196 
2197  if (attr->transaction.action == cfa_warn || DONTDO)
2198  {
2199 
2200  cfPS(ctx, LOG_LEVEL_WARNING, PROMISE_RESULT_WARN, pp, attr,
2201  "'%s' has permission %04jo - [should be %04jo]", file,
2202  (uintmax_t)dstat->st_mode & 07777, (uintmax_t)newperm & 07777);
2203  result = PromiseResultUpdate(result, PROMISE_RESULT_WARN);
2204  }
2205  else if (attr->transaction.action == cfa_fix)
2206  {
2207  if (safe_chmod(file, newperm & 07777) == -1)
2208  {
2210  "chmod failed on '%s'. (chmod: %s)",
2211  file, GetErrorStr());
2212  }
2213  else
2214  {
2215  cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_CHANGE, pp, attr,
2216  "Object '%s' had permission %04jo, changed it to %04jo", file,
2217  (uintmax_t)dstat->st_mode & 07777, (uintmax_t)newperm & 07777);
2218  result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE);
2219  }
2220  }
2221  else
2222  {
2223  ProgrammingError("Unhandled file action in switch: %d",
2224  attr->transaction.action);
2225  }
2226  }
2227 
2228 # if defined HAVE_CHFLAGS /* BSD special flags */
2229 
2230  newflags = (dstat->st_flags & CHFLAGS_MASK);
2231  newflags |= attr->perms.plus_flags;
2232  newflags &= ~(attr->perms.minus_flags);
2233 
2234  if ((newflags & CHFLAGS_MASK) == (dstat->st_flags & CHFLAGS_MASK)) /* file okay */
2235  {
2236  Log(LOG_LEVEL_DEBUG, "BSD File okay, flags '%jx', current '%jx'",
2237  (uintmax_t) (newflags & CHFLAGS_MASK),
2238  (uintmax_t) (dstat->st_flags & CHFLAGS_MASK));
2239  }
2240  else
2241  {
2242  Log(LOG_LEVEL_DEBUG, "BSD Fixing '%s', newflags '%jx', flags '%jx'",
2243  file, (uintmax_t) (newflags & CHFLAGS_MASK),
2244  (uintmax_t) (dstat->st_flags & CHFLAGS_MASK));
2245 
2246  switch (attr->transaction.action)
2247  {
2248  case cfa_warn:
2249 
2250  cfPS(ctx, LOG_LEVEL_WARNING, PROMISE_RESULT_WARN, pp, attr,
2251  "'%s' has flags %jo - [should be %jo]",
2252  file, (uintmax_t) (dstat->st_mode & CHFLAGS_MASK),
2253  (uintmax_t) (newflags & CHFLAGS_MASK));
2254  result = PromiseResultUpdate(result, PROMISE_RESULT_WARN);
2255  break;
2256 
2257  case cfa_fix:
2258 
2259  if (!DONTDO)
2260  {
2261  if (chflags(file, newflags & CHFLAGS_MASK) == -1)
2262  {
2263  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_DENIED, pp, attr,
2264  "Failed setting BSD flags '%jx' on '%s'. (chflags: %s)",
2265  (uintmax_t) newflags, file, GetErrorStr());
2266  result = PromiseResultUpdate(result, PROMISE_RESULT_DENIED);
2267  break;
2268  }
2269  else
2270  {
2271  cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_CHANGE, pp, attr,
2272  "'%s' had flags %jo, changed it to %jo", file,
2273  (uintmax_t) (dstat->st_flags & CHFLAGS_MASK),
2274  (uintmax_t) (newflags & CHFLAGS_MASK));
2275  result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE);
2276  }
2277  }
2278 
2279  break;
2280 
2281  default:
2282  ProgrammingError("Unhandled file action in switch: %d", attr->transaction.action);
2283  }
2284  }
2285 # endif
2286 #endif
2287 
2288  if (attr->acl.acl_entries)
2289  {
2290  result = PromiseResultUpdate(result, VerifyACL(ctx, file, attr, pp));
2291  }
2292 
2293  if (attr->touch)
2294  {
2295  if (utime(file, NULL) == -1)
2296  {
2297  cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_DENIED, pp, attr, "Touching file '%s' failed. (utime: %s)",
2298  file, GetErrorStr());
2299  result = PromiseResultUpdate(result, PROMISE_RESULT_DENIED);
2300  }
2301  else
2302  {
2303  cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_CHANGE, pp, attr, "Touching file '%s'", file);
2304  result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE);
2305  }
2306  }
2307 
2308 #ifndef __MINGW32__
2309  umask(maskvalue);
2310 #endif
2311 
2312  return result;
2313 }
2314 
2315 bool DepthSearch(EvalContext *ctx, char *name, const struct stat *sb, int rlevel, const Attributes *attr,
2316  const Promise *pp, dev_t rootdevice, PromiseResult *result)
2317 {
2318  assert(attr != NULL);
2319  Dir *dirh;
2320  int goback;
2321  const struct dirent *dirp;
2322  struct stat lsb;
2323  Seq *db_file_set = NULL;
2324  Seq *selected_files = NULL;
2325  bool retval = true;
2326 
2327  if (!attr->havedepthsearch) /* if the search is trivial, make sure that we are in the parent dir of the leaf */
2328  {
2329  char basedir[CF_BUFSIZE];
2330 
2331  Log(LOG_LEVEL_DEBUG, "Direct file reference '%s', no search implied", name);
2332  snprintf(basedir, sizeof(basedir), "%s", name);
2333  ChopLastNode(basedir);
2334  if (safe_chdir(basedir))
2335  {
2336  Log(LOG_LEVEL_ERR, "Failed to chdir into '%s'. (chdir: '%s')", basedir, GetErrorStr());
2337  return false;
2338  }
2339  if (!attr->haveselect || SelectLeaf(ctx, name, sb, &(attr->select)))
2340  {
2341  VerifyFileLeaf(ctx, name, sb, attr, pp, result);
2342  return true;
2343  }
2344  else
2345  {
2346  return false;
2347  }
2348  }
2349 
2350  if (rlevel > CF_RECURSION_LIMIT)
2351  {
2352  Log(LOG_LEVEL_WARNING, "Very deep nesting of directories (>%d deep) for '%s' (Aborting files)", rlevel, name);
2353  return false;
2354  }
2355 
2356  if (!PushDirState(ctx, name, sb))
2357  {
2358  return false;
2359  }
2360 
2361  if ((dirh = DirOpen(".")) == NULL)
2362  {
2363  Log(LOG_LEVEL_INFO, "Could not open existing directory '%s'. (opendir: %s)", name, GetErrorStr());
2364  return false;
2365  }
2366 
2367  if (attr->havechange)
2368  {
2369  db_file_set = SeqNew(1, &free);
2370  if (!FileChangesGetDirectoryList(name, db_file_set))
2371  {
2372  SeqDestroy(db_file_set);
2373  *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
2374  return false;
2375  }
2376  selected_files = SeqNew(1, &free);
2377  }
2378 
2379  char path[CF_BUFSIZE];
2380 
2381  for (dirp = DirRead(dirh); dirp != NULL; dirp = DirRead(dirh))
2382  {
2383  if (!ConsiderLocalFile(dirp->d_name, name))
2384  {
2385  continue;
2386  }
2387 
2388  memset(path, 0, sizeof(path));
2389  if (strlcpy(path, name, sizeof(path)-2) >= sizeof(path)-2)
2390  {
2391  Log(LOG_LEVEL_ERR, "Truncated filename %s while performing depth-first search", path);
2392  /* TODO return false? */
2393  }
2394 
2395  AddSlash(path);
2396 
2397  if (strlcat(path, dirp->d_name, sizeof(path)) >= sizeof(path))
2398  {
2399  Log(LOG_LEVEL_ERR, "Internal limit reached in DepthSearch(),"
2400  " path too long: '%s' + '%s'", path, dirp->d_name);
2401  retval = false;
2402  goto end;
2403  }
2404 
2405  if (lstat(dirp->d_name, &lsb) == -1)
2406  {
2407  Log(LOG_LEVEL_VERBOSE, "Recurse was looking at '%s' when an error occurred. (lstat: %s)", path, GetErrorStr());
2408  continue;
2409  }
2410 
2411  if (S_ISLNK(lsb.st_mode)) /* should we ignore links? */
2412  {
2413  if (KillGhostLink(ctx, path, attr, pp, result))
2414  {
2415  continue;
2416  }
2417  }
2418 
2419  /* See if we are supposed to treat links to dirs as dirs and descend */
2420 
2421  if ((attr->recursion.travlinks) && (S_ISLNK(lsb.st_mode)))
2422  {
2423  if ((lsb.st_uid != 0) && (lsb.st_uid != getuid()))
2424  {
2426  "File '%s' is an untrusted link: cfengine will not follow it with a destructive operation", path);
2427  continue;
2428  }
2429 
2430  /* if so, hide the difference by replacing with actual object */
2431 
2432  if (stat(dirp->d_name, &lsb) == -1)
2433  {
2434  Log(LOG_LEVEL_ERR, "Recurse was working on '%s' when this failed. (stat: %s)", path, GetErrorStr());
2435  continue;
2436  }
2437  }
2438 
2439  if ((attr->recursion.xdev) && (DeviceBoundary(&lsb, rootdevice)))
2440  {
2441  Log(LOG_LEVEL_VERBOSE, "Skipping '%s' on different device - use xdev option to change this. (stat: %s)", path, GetErrorStr());
2442  continue;
2443  }
2444 
2445  if (S_ISDIR(lsb.st_mode))
2446  {
2447  if (SkipDirLinks(ctx, path, dirp->d_name, attr->recursion))
2448  {
2449  continue;
2450  }
2451 
2452  if ((attr->recursion.depth > 1) && (rlevel <= attr->recursion.depth))
2453  {
2454  Log(LOG_LEVEL_VERBOSE, "Entering '%s', level %d", path, rlevel);
2455  goback = DepthSearch(ctx, path, &lsb, rlevel + 1, attr, pp, rootdevice, result);
2456  if (!PopDirState(goback, name, sb, attr->recursion))
2457  {
2458  FatalError(ctx, "Not safe to continue");
2459  }
2460  }
2461  }
2462 
2463  if (!attr->haveselect || SelectLeaf(ctx, path, &lsb, &(attr->select)))
2464  {
2465  if (attr->havechange)
2466  {
2467  if (!SeqBinaryLookup(db_file_set, dirp->d_name, (SeqItemComparator)strcmp))
2468  {
2469  // See comments in FileChangesCheckAndUpdateDirectory(),
2470  // regarding this function call.
2471  FileChangesLogNewFile(path, pp);
2472  }
2473  SeqAppend(selected_files, xstrdup(dirp->d_name));
2474  }
2475 
2476  VerifyFileLeaf(ctx, path, &lsb, attr, pp, result);
2477  }
2478  else
2479  {
2480  Log(LOG_LEVEL_DEBUG, "Skipping non-selected file '%s'", path);
2481  }
2482  }
2483 
2484  if (attr->havechange)
2485  {
2486  FileChangesCheckAndUpdateDirectory(name, selected_files, db_file_set,
2487  attr->change.update, pp, result);
2488  }
2489 
2490 end:
2491  SeqDestroy(selected_files);
2492  SeqDestroy(db_file_set);
2493  DirClose(dirh);
2494  return retval;
2495 }
2496 
2497 static bool PushDirState(EvalContext *ctx, char *name, const struct stat *sb)
2498 {
2499  if (safe_chdir(name) == -1)
2500  {
2501  Log(LOG_LEVEL_INFO, "Could not change to directory '%s', mode '%04jo' in tidy. (chdir: %s)",
2502  name, (uintmax_t)(sb->st_mode & 07777), GetErrorStr());
2503  return false;
2504  }
2505 
2506  if (!CheckLinkSecurity(sb, name))
2507  {
2508  FatalError(ctx, "Not safe to continue");
2509  }
2510  return true;
2511 }
2512 
2513 /**
2514  * @return true if safe for agent to continue
2515  */
2516 static bool PopDirState(int goback, char *name, const struct stat *sb, DirectoryRecursion r)
2517 {
2518  if (goback && (r.travlinks))
2519  {
2520  if (safe_chdir(name) == -1)
2521  {
2522  Log(LOG_LEVEL_ERR, "Error in backing out of recursive travlink descent securely to '%s'. (chdir: %s)",
2523  name, GetErrorStr());
2524  return false;
2525  }
2526 
2527  if (!CheckLinkSecurity(sb, name))
2528  {
2529  return false;
2530  }
2531  }
2532  else if (goback)
2533  {
2534  if (safe_chdir("..") == -1)
2535  {
2536  Log(LOG_LEVEL_ERR, "Error in backing out of recursive descent securely to '%s'. (chdir: %s)",
2537  name, GetErrorStr());
2538  return false;
2539  }
2540  }
2541 
2542  return true;
2543 }
2544 
2545 /**
2546  * @return true if it is safe for the agent to continue execution
2547  */
2548 static bool CheckLinkSecurity(const struct stat *sb, char *name)
2549 {
2550  struct stat security;
2551 
2552  Log(LOG_LEVEL_DEBUG, "Checking the inode and device to make sure we are where we think we are...");
2553 
2554  if (stat(".", &security) == -1)
2555  {
2556  Log(LOG_LEVEL_ERR, "Could not stat directory '%s' after entering. (stat: %s)",
2557  name, GetErrorStr());
2558  return true; // continue anyway
2559  }
2560 
2561  if ((sb->st_dev != security.st_dev) || (sb->st_ino != security.st_ino))
2562  {
2564  "SERIOUS SECURITY ALERT: path race exploited in recursion to/from '%s'. Not safe for agent to continue - aborting",
2565  name);
2566  return false; // too dangerous
2567  }
2568 
2569  return true;
2570 }
2571 
2572 static PromiseResult VerifyCopiedFileAttributes(EvalContext *ctx, const char *src, const char *dest, const struct stat *sstat,
2573  const struct stat *dstat, const Attributes *a, const Promise *pp)
2574 {
2575  assert(a != NULL);
2576  Attributes attr = *a; // TODO: try to remove this copy
2577 #ifndef __MINGW32__
2578  mode_t newplus, newminus;
2579  uid_t save_uid;
2580  gid_t save_gid;
2581 
2582 // If we get here, there is both a src and dest file
2583 
2584  save_uid = (attr.perms.owners)->uid;
2585  save_gid = (attr.perms.groups)->gid;
2586 
2587  if (attr.copy.preserve)
2588  {
2589  Log(LOG_LEVEL_VERBOSE, "Attempting to preserve file permissions from the source: %04jo",
2590  (uintmax_t)(sstat->st_mode & 07777));
2591 
2592  if ((attr.perms.owners)->uid == CF_SAME_OWNER) /* Preserve uid and gid */
2593  {
2594  (attr.perms.owners)->uid = sstat->st_uid;
2595  }
2596 
2597  if ((attr.perms.groups)->gid == CF_SAME_GROUP)
2598  {
2599  (attr.perms.groups)->gid = sstat->st_gid;
2600  }
2601 
2602 // Will this preserve if no mode set?
2603 
2604  newplus = (sstat->st_mode & 07777);
2605  newminus = ~~newplus & 07777;
2606  attr.perms.plus = newplus;
2607  attr.perms.minus = newminus;
2608  }
2609  else
2610  {
2611  if ((attr.perms.owners)->uid == CF_SAME_OWNER) /* Preserve uid and gid */
2612  {
2613  (attr.perms.owners)->uid = dstat->st_uid;
2614  }
2615 
2616  if ((attr.perms.groups)->gid == CF_SAME_GROUP)
2617  {
2618  (attr.perms.groups)->gid = dstat->st_gid;
2619  }
2620 
2621  if (attr.haveperms)
2622  {
2623  newplus = (dstat->st_mode & 07777) | attr.perms.plus;
2624  newminus = ~(newplus & ~(attr.perms.minus)) & 07777;
2625  attr.perms.plus = newplus;
2626  attr.perms.minus = newminus;
2627  }
2628  }
2629 #endif
2630  PromiseResult result = VerifyFileAttributes(ctx, dest, dstat, &attr, pp);
2631 
2632 #ifndef __MINGW32__
2633  (attr.perms.owners)->uid = save_uid;
2634  (attr.perms.groups)->gid = save_gid;
2635 #endif
2636 
2637  if (attr.copy.preserve &&
2638  ( attr.copy.servers == NULL
2639  || strcmp(RlistScalarValue(attr.copy.servers), "localhost") == 0))
2640  {
2641  if (!CopyFileExtendedAttributesDisk(src, dest))
2642  {
2643  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, &attr,
2644  "Could not preserve extended attributes"
2645  " (ACLs and security contexts) on file '%s'", dest);
2646  result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
2647  }
2648  }
2649 
2650  return result;
2651 }
2652 
2653 static PromiseResult CopyFileSources(EvalContext *ctx, char *destination, const Attributes *attr, const Promise *pp, AgentConnection *conn)
2654 {
2655  assert(attr != NULL);
2656  Buffer *source = BufferNew();
2657  // Expand this.promiser
2658  ExpandScalar(ctx,
2659  PromiseGetBundle(pp)->ns, PromiseGetBundle(pp)->name,
2660  attr->copy.source, source);
2661  char vbuff[CF_BUFSIZE];
2662  struct stat ssb, dsb;
2663  struct timespec start;
2664 
2665  if (conn != NULL && (!conn->authenticated))
2666  {
2667  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, attr,
2668  "No authenticated source '%s' in files.copy_from promise",
2669  BufferData(source));
2670  BufferDestroy(source);
2671  return PROMISE_RESULT_FAIL;
2672  }
2673 
2674  if (cf_stat(BufferData(source), &ssb, &(attr->copy), conn) == -1)
2675  {
2676  if (attr->copy.missing_ok)
2677  {
2678  cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, attr,
2679  "Can't stat file '%s' on '%s' but promise is kept because of"
2680  " 'missing_ok' in files.copy_from promise",
2681  BufferData(source), conn ? conn->remoteip : "localhost");
2682  BufferDestroy(source);
2683 
2684  return PROMISE_RESULT_CHANGE;
2685  }
2686  else
2687  {
2688  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, attr,
2689  "Can't stat file '%s' on '%s' in files.copy_from promise",
2690  BufferData(source), conn ? conn->remoteip : "localhost");
2691  BufferDestroy(source);
2692  return PROMISE_RESULT_FAIL;
2693  }
2694  }
2695 
2696  start = BeginMeasure();
2697 
2698  strlcpy(vbuff, destination, CF_BUFSIZE - 3);
2699 
2700  if (S_ISDIR(ssb.st_mode)) /* could be depth_search */
2701  {
2702  AddSlash(vbuff);
2703  strcat(vbuff, ".");
2704  }
2705 
2706  if (!MakeParentDirectory(vbuff, attr->move_obstructions))
2707  {
2708  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, attr,
2709  "Can't make directories for '%s' in files.copy_from promise",
2710  vbuff);
2711  BufferDestroy(source);
2712  return PROMISE_RESULT_FAIL;
2713  }
2714 
2715  CompressedArray *inode_cache = NULL;
2716 
2718  if (S_ISDIR(ssb.st_mode)) /* could be depth_search */
2719  {
2720  if (attr->copy.purge)
2721  {
2722  Log(LOG_LEVEL_VERBOSE, "Destination purging enabled");
2723  }
2724 
2725  Log(LOG_LEVEL_VERBOSE, "Entering directory '%s'", BufferData(source));
2726 
2727  result = PromiseResultUpdate(
2728  result, SourceSearchAndCopy(ctx, BufferData(source), destination,
2729  attr->recursion.depth, attr, pp,
2730  ssb.st_dev, &inode_cache, conn));
2731 
2732  if (stat(destination, &dsb) != -1)
2733  {
2734  if (attr->copy.check_root)
2735  {
2736  result = PromiseResultUpdate(
2737  result, VerifyCopiedFileAttributes(ctx, BufferData(source),
2738  destination, &ssb, &dsb,
2739  attr, pp));
2740  }
2741  }
2742  }
2743  else
2744  {
2745  result = PromiseResultUpdate(
2746  result, VerifyCopy(ctx, BufferData(source), destination,
2747  attr, pp, &inode_cache, conn));
2748  }
2749 
2750  DeleteCompressedArray(inode_cache);
2751 
2752  const char *mid = PromiseGetConstraintAsRval(pp, "measurement_class", RVAL_TYPE_SCALAR);
2753  if (mid)
2754  {
2755  char eventname[CF_BUFSIZE];
2756  snprintf(eventname, CF_BUFSIZE - 1, "Copy(%s:%s > %s)",
2757  conn ? conn->this_server : "localhost",
2758  BufferData(source), destination);
2759 
2760  EndMeasure(eventname, start);
2761  }
2762  else
2763  {
2764  EndMeasure(NULL, start);
2765  }
2766 
2767  BufferDestroy(source);
2768  return result;
2769 }
2770 
2771 /* Decide the protocol version the agent will use to connect: If the user has
2772  * specified a copy_from attribute then follow that one, else use the body
2773  * common control setting. */
2775  ProtocolVersion copyfrom_setting)
2776 {
2777  if (copyfrom_setting == CF_PROTOCOL_UNDEFINED)
2778  {
2779  /* TODO we would like to get the common control setting from
2780  * GenericAgentConfig. Given that we have only access to EvalContext here,
2781  * we get the raw string and reparse it every time. */
2782  const char *s = EvalContextVariableControlCommonGet(
2784  ProtocolVersion common_setting = ProtocolVersionParse(s);
2785  return common_setting;
2786  }
2787  else
2788  {
2789  return copyfrom_setting;
2790  }
2791 }
2792 
2794  const char *servername,
2795  const FileCopy *fc, bool background)
2796 {
2797  ConnectionFlags flags = {
2799  .cache_connection = !background,
2800  .force_ipv4 = fc->force_ipv4,
2801  .trust_server = fc->trustkey,
2802  .off_the_record = false
2803  };
2804 
2805  unsigned int conntimeout = fc->timeout;
2806  if (fc->timeout == CF_NOINT || fc->timeout < 0)
2807  {
2808  conntimeout = CONNTIMEOUT;
2809  }
2810 
2811  const char *port = (fc->port != NULL) ? fc->port : CFENGINE_PORT_STR;
2812 
2813  AgentConnection *conn = NULL;
2814  if (flags.cache_connection)
2815  {
2816  conn = ConnCache_FindIdleMarkBusy(servername, port, flags);
2817 
2818  if (conn != NULL) /* found idle connection in cache */
2819  {
2820  return conn;
2821  }
2822  else /* not found, open and cache new connection */
2823  {
2824  int err = 0;
2825  conn = ServerConnection(servername, port, conntimeout,
2826  flags, &err);
2827 
2828  /* WARNING: if cache already has non-idle connections to that
2829  * host, here we add more so that we connect in parallel. */
2830 
2831  if (conn == NULL) /* Couldn't connect */
2832  {
2833  /* Allocate and add to the cache as failure. */
2834  conn = NewAgentConn(servername, port, flags);
2836 
2838 
2839  return NULL;
2840  }
2841  else
2842  {
2843  /* Success! Put it in the cache as busy. */
2845  return conn;
2846  }
2847  }
2848  }
2849  else
2850  {
2851  int err = 0;
2852  conn = ServerConnection(servername, port, conntimeout,
2853  flags, &err);
2854  return conn;
2855  }
2856 }
2857 
2859 {
2860  if (conn->flags.cache_connection)
2861  {
2862  /* Mark the connection as available in the cache. */
2863  ConnCache_MarkNotBusy(conn);
2864  }
2865  else
2866  {
2867  DisconnectServer(conn);
2868  }
2869 }
2870 
2871 PromiseResult ScheduleCopyOperation(EvalContext *ctx, char *destination, const Attributes *attr, const Promise *pp)
2872 {
2873  assert(attr != NULL);
2874  /* TODO currently parser allows body copy_from to have no source!
2875  See tests/acceptance/10_files/02_maintain/017.cf and
2876  https://cfengine.com/bugtracker/view.php?id=687 */
2877  if (attr->copy.source == NULL)
2878  {
2880  "Body copy_from has no source! Maybe a typo in the policy?");
2881  return PROMISE_RESULT_FAIL;
2882  }
2883 
2884  Log(LOG_LEVEL_VERBOSE, "File '%s' copy_from '%s'",
2885  destination, attr->copy.source);
2886 
2887  /* Empty attr->copy.servers means copy from localhost. */
2888  bool copyfrom_localhost = (attr->copy.servers == NULL);
2889  AgentConnection *conn = NULL;
2890  Rlist *rp = attr->copy.servers;
2891 
2892  /* Iterate over all copy_from servers until connection succeeds. */
2893  while (rp != NULL && conn == NULL)
2894  {
2895  const char *servername = RlistScalarValue(rp);
2896 
2897  if (strcmp(servername, "localhost") == 0)
2898  {
2899  copyfrom_localhost = true;
2900  break;
2901  }
2902 
2903  conn = FileCopyConnectionOpen(ctx, servername, &(attr->copy),
2904  attr->transaction.background);
2905  if (conn == NULL)
2906  {
2907  Log(LOG_LEVEL_INFO, "Unable to establish connection to '%s'",
2908  servername);
2909  }
2910 
2911  rp = rp->next;
2912  }
2913 
2914  if (!copyfrom_localhost && conn == NULL)
2915  {
2916  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, attr,
2917  "No suitable server found");
2919  return PROMISE_RESULT_FAIL;
2920  }
2921 
2922  /* (conn == NULL) means local copy. */
2923  PromiseResult result = CopyFileSources(ctx, destination, attr, pp, conn);
2924 
2925  if (conn != NULL)
2926  {
2928  }
2929 
2930  return result;
2931 }
2932 
2933 PromiseResult ScheduleLinkOperation(EvalContext *ctx, char *destination, char *source, const Attributes *attr, const Promise *pp)
2934 {
2935  assert(attr != NULL);
2936  const char *lastnode;
2937 
2938  lastnode = ReadLastNode(destination);
2940 
2941  if (MatchRlistItem(ctx, attr->link.copy_patterns, lastnode))
2942  {
2943  Log(LOG_LEVEL_VERBOSE, "Link '%s' matches copy_patterns", destination);
2944  CompressedArray *inode_cache = NULL;
2945  result = PromiseResultUpdate(result, VerifyCopy(ctx, attr->link.source, destination, attr, pp, &inode_cache, NULL));
2946  DeleteCompressedArray(inode_cache);
2947  return result;
2948  }
2949 
2950  switch (attr->link.link_type)
2951  {
2953  result = VerifyLink(ctx, destination, source, attr, pp);
2954  break;
2956  result = VerifyHardLink(ctx, destination, source, attr, pp);
2957  break;
2959  result = VerifyRelativeLink(ctx, destination, source, attr, pp);
2960  break;
2962  result = VerifyAbsoluteLink(ctx, destination, source, attr, pp);
2963  break;
2964  default:
2965  Log(LOG_LEVEL_ERR, "Unknown link type - should not happen.");
2966  break;
2967  }
2968 
2969  return result;
2970 }
2971 
2972 PromiseResult ScheduleLinkChildrenOperation(EvalContext *ctx, char *destination, char *source, int recurse, const Attributes *a,
2973  const Promise *pp)
2974 {
2975  assert(a != NULL);
2976  Attributes attr = *a; // TODO: Remove this copy
2977  Dir *dirh;
2978  const struct dirent *dirp;
2979  char promiserpath[CF_BUFSIZE], sourcepath[CF_BUFSIZE];
2980  struct stat lsb;
2981  int ret;
2982 
2983  if ((ret = lstat(destination, &lsb)) != -1)
2984  {
2985  if (attr.move_obstructions && S_ISLNK(lsb.st_mode))
2986  {
2987  unlink(destination);
2988  }
2989  else if (!S_ISDIR(lsb.st_mode))
2990  {
2991  Log(LOG_LEVEL_ERR, "Cannot promise to link multiple files to children of '%s' as it is not a directory!",
2992  destination);
2993  return PROMISE_RESULT_NOOP;
2994  }
2995  }
2996 
2997  snprintf(promiserpath, sizeof(promiserpath), "%s/.", destination);
2998 
3000  if ((ret == -1 || !S_ISDIR(lsb.st_mode)) && !CfCreateFile(ctx, promiserpath, pp, &attr, &result))
3001  {
3002  Log(LOG_LEVEL_ERR, "Cannot promise to link multiple files to children of '%s' as it is not a directory!",
3003  destination);
3004  return result;
3005  }
3006 
3007  if ((dirh = DirOpen(source)) == NULL)
3008  {
3009  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, &attr,
3010  "Can't open source of children to link '%s'. (opendir: %s)",
3011  attr.link.source, GetErrorStr());
3012  result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
3013  return result;
3014  }
3015 
3016  for (dirp = DirRead(dirh); dirp != NULL; dirp = DirRead(dirh))
3017  {
3018  if (!ConsiderLocalFile(dirp->d_name, source))
3019  {
3020  continue;
3021  }
3022 
3023  /* Assemble pathnames */
3024 
3025  strlcpy(promiserpath, destination, sizeof(promiserpath));
3026  AddSlash(promiserpath);
3027 
3028  if (strlcat(promiserpath, dirp->d_name, sizeof(promiserpath))
3029  >= sizeof(promiserpath))
3030  {
3032  "Internal buffer limit while verifying child links,"
3033  " promiser: '%s' + '%s'",
3034  promiserpath, dirp->d_name);
3036  DirClose(dirh);
3037  return result;
3038  }
3039 
3040  strlcpy(sourcepath, source, sizeof(sourcepath));
3041  AddSlash(sourcepath);
3042 
3043  if (strlcat(sourcepath, dirp->d_name, sizeof(sourcepath))
3044  >= sizeof(sourcepath))
3045  {
3047  "Internal buffer limit while verifying child links,"
3048  " source filename: '%s' + '%s'",
3049  sourcepath, dirp->d_name);
3051  DirClose(dirh);
3052  return result;
3053  }
3054 
3055  if ((lstat(promiserpath, &lsb) != -1) && !S_ISLNK(lsb.st_mode) && !S_ISDIR(lsb.st_mode))
3056  {
3058  {
3059  attr.move_obstructions = true;
3060  }
3061  else
3062  {
3063  Log(LOG_LEVEL_VERBOSE, "Have promised not to disturb existing content belonging to '%s'", promiserpath);
3064  continue;
3065  }
3066  }
3067 
3068  if ((attr.recursion.depth > recurse) && (lstat(sourcepath, &lsb) != -1) && S_ISDIR(lsb.st_mode))
3069  {
3070  result = PromiseResultUpdate(result, ScheduleLinkChildrenOperation(ctx, promiserpath, sourcepath, recurse + 1, &attr, pp));
3071  }
3072  else
3073  {
3074  result = PromiseResultUpdate(result, ScheduleLinkOperation(ctx, promiserpath, sourcepath, &attr, pp));
3075  }
3076  }
3077 
3078  DirClose(dirh);
3079  return result;
3080 }
3081 
3082 static PromiseResult VerifyFileIntegrity(EvalContext *ctx, const char *file, const Attributes *attr, const Promise *pp)
3083 {
3084  assert(attr != NULL);
3085  unsigned char digest1[EVP_MAX_MD_SIZE + 1];
3086  unsigned char digest2[EVP_MAX_MD_SIZE + 1];
3087  int changed = false, one, two;
3088 
3090  {
3091  return PROMISE_RESULT_NOOP;
3092  }
3093 
3094  memset(digest1, 0, EVP_MAX_MD_SIZE + 1);
3095  memset(digest2, 0, EVP_MAX_MD_SIZE + 1);
3096 
3098  if (attr->change.hash == HASH_METHOD_BEST)
3099  {
3100  if (!DONTDO)
3101  {
3102  HashFile(file, digest1, HASH_METHOD_MD5, false);
3103  HashFile(file, digest2, HASH_METHOD_SHA1, false);
3104 
3105  one = FileChangesCheckAndUpdateHash(ctx, file, digest1, HASH_METHOD_MD5, attr, pp, &result);
3106  two = FileChangesCheckAndUpdateHash(ctx, file, digest2, HASH_METHOD_SHA1, attr, pp, &result);
3107 
3108  if (one || two)
3109  {
3110  changed = true;
3111  }
3112  }
3113  }
3114  else
3115  {
3116  if (!DONTDO)
3117  {
3118  HashFile(file, digest1, attr->change.hash, false);
3119 
3120  if (FileChangesCheckAndUpdateHash(ctx, file, digest1, attr->change.hash, attr, pp, &result))
3121  {
3122  changed = true;
3123  }
3124  }
3125  }
3126 
3127  if (changed)
3128  {
3130  EvalContextClassPutSoft(ctx, "checksum_alerts", CONTEXT_SCOPE_NAMESPACE, "");
3131  FileChangesLogChange(file, FILE_STATE_CONTENT_CHANGED, "Content changed", pp);
3132  }
3133 
3134  if (attr->change.report_diffs)
3135  {
3136  char destination[CF_BUFSIZE];
3137  if (!GetRepositoryPath(file, attr, destination))
3138  {
3139  destination[0] = '\0';
3140  }
3141  LogFileChange(ctx, file, changed, attr, pp, &CopyRegularFile, destination, &DeleteCompressedArray);
3142  }
3143 
3144  return result;
3145 }
3146 
3147 static bool CompareForFileCopy(char *sourcefile, char *destfile, const struct stat *ssb, const struct stat *dsb, const FileCopy *fc, AgentConnection *conn)
3148 {
3149  bool ok_to_copy;
3150 
3151  switch (fc->compare)
3152  {
3154  case FILE_COMPARATOR_HASH:
3155 
3156  if (S_ISREG(dsb->st_mode) && S_ISREG(ssb->st_mode))
3157  {
3158  ok_to_copy = CompareFileHashes(sourcefile, destfile, ssb, dsb, fc, conn);
3159  }
3160  else
3161  {
3162  Log(LOG_LEVEL_VERBOSE, "Checksum comparison replaced by ctime: files not regular");
3163  ok_to_copy = (dsb->st_ctime < ssb->st_ctime) || (dsb->st_mtime < ssb->st_mtime);
3164  }
3165 
3166  if (ok_to_copy)
3167  {
3168  Log(LOG_LEVEL_VERBOSE, "Image file '%s' has a wrong digest/checksum, should be copy of '%s'", destfile,
3169  sourcefile);
3170  return ok_to_copy;
3171  }
3172  break;
3173 
3175 
3176  if (S_ISREG(dsb->st_mode) && S_ISREG(ssb->st_mode))
3177  {
3178  ok_to_copy = CompareBinaryFiles(sourcefile, destfile, ssb, dsb, fc, conn);
3179  }
3180  else
3181  {
3182  Log(LOG_LEVEL_VERBOSE, "Byte comparison replaced by ctime: files not regular");
3183  ok_to_copy = (dsb->st_ctime < ssb->st_ctime) || (dsb->st_mtime < ssb->st_mtime);
3184  }
3185 
3186  if (ok_to_copy)
3187  {
3188  Log(LOG_LEVEL_VERBOSE, "Image file %s has a wrong binary checksum, should be copy of '%s'", destfile,
3189  sourcefile);
3190  return ok_to_copy;
3191  }
3192  break;
3193 
3194  case FILE_COMPARATOR_MTIME:
3195 
3196  ok_to_copy = (dsb->st_mtime < ssb->st_mtime);
3197 
3198  if (ok_to_copy)
3199  {
3200  Log(LOG_LEVEL_VERBOSE, "Image file '%s' out of date, should be copy of '%s'", destfile, sourcefile);
3201  return ok_to_copy;
3202  }
3203  break;
3204 
3205  case FILE_COMPARATOR_ATIME:
3206 
3207  ok_to_copy = (dsb->st_ctime < ssb->st_ctime) ||
3208  (dsb->st_mtime < ssb->st_mtime) || (CompareBinaryFiles(sourcefile, destfile, ssb, dsb, fc, conn));
3209 
3210  if (ok_to_copy)
3211  {
3212  Log(LOG_LEVEL_VERBOSE, "Image file '%s' seems out of date, should be copy of '%s'", destfile, sourcefile);
3213  return ok_to_copy;
3214  }
3215  break;
3216 
3217  default:
3218  ok_to_copy = (dsb->st_ctime < ssb->st_ctime) || (dsb->st_mtime < ssb->st_mtime);
3219 
3220  if (ok_to_copy)
3221  {
3222  Log(LOG_LEVEL_VERBOSE, "Image file '%s' out of date, should be copy of '%s'", destfile, sourcefile);
3223  return ok_to_copy;
3224  }
3225  break;
3226  }
3227 
3228  return false;
3229 }
3230 
3231 static void FileAutoDefine(EvalContext *ctx, char *destfile)
3232 {
3233  char context[CF_MAXVARSIZE];
3234 
3235  snprintf(context, CF_MAXVARSIZE, "auto_%s", CanonifyName(destfile));
3236  EvalContextClassPutSoft(ctx, context, CONTEXT_SCOPE_NAMESPACE, "source=promise");
3237  Log(LOG_LEVEL_INFO, "Auto defining class '%s'", context);
3238 }
3239 
3240 #ifndef __MINGW32__
3241 static void LoadSetxid(void)
3242 {
3243  assert(!VSETXIDLIST);
3244 
3245  char filename[CF_BUFSIZE];
3246  snprintf(filename, CF_BUFSIZE, "%s/cfagent.%s.log", GetLogDir(), VSYSNAME.nodename);
3247  ToLowerStrInplace(filename);
3248  MapName(filename);
3249 
3250  VSETXIDLIST = RawLoadItemList(filename);
3251 }
3252 
3253 static void SaveSetxid(bool modified)
3254 {
3255  if (!VSETXIDLIST)
3256  {
3257  return;
3258  }
3259 
3260  if (modified)
3261  {
3262  char filename[CF_BUFSIZE];
3263  snprintf(filename, CF_BUFSIZE, "%s/cfagent.%s.log", GetLogDir(), VSYSNAME.nodename);
3264  ToLowerStrInplace(filename);
3265  MapName(filename);
3266 
3267  PurgeItemList(&VSETXIDLIST, "SETUID/SETGID");
3268 
3269  Item *current = RawLoadItemList(filename);
3270  if (!ListsCompare(VSETXIDLIST, current))
3271  {
3272  mode_t oldmode = umask(077); // This setxidlist file must only be accesible by root
3274  "Updating setxidlist at '%s', umask was %o, will create setxidlist using umask 0077, file perms should be 0600.",
3275  filename,
3276  oldmode);
3278  umask(oldmode);
3279  Log(LOG_LEVEL_DEBUG, "Restored umask to %o", oldmode);
3280  }
3281  DeleteItemList(current);
3282  }
3283 
3285  VSETXIDLIST = NULL;
3286 }
3287 
3288 static bool IsInSetxidList(const char *file)
3289 {
3290  if (!VSETXIDLIST)
3291  {
3292  LoadSetxid();
3293  }
3294 
3295  return IsItemIn(VSETXIDLIST, file);
3296 }
3297 
3298 static PromiseResult VerifySetUidGid(EvalContext *ctx, const char *file, const struct stat *dstat, mode_t newperm,
3299  const Promise *pp, const Attributes *attr)
3300 {
3301  assert(attr != NULL);
3302  int amroot = true;
3304  bool setxid_modified = false;
3305 
3306 
3307  if (!IsPrivileged())
3308  {
3309  amroot = false;
3310  }
3311 
3312  if ((dstat->st_uid == 0) && (dstat->st_mode & S_ISUID))
3313  {
3314  if (newperm & S_ISUID)
3315  {
3316  if (!IsInSetxidList(file))
3317  {
3318  if (amroot)
3319  {
3320  Log(LOG_LEVEL_NOTICE, "NEW SETUID root PROGRAM '%s' ", file);
3321  }
3322 
3323  PrependItem(&VSETXIDLIST, file, NULL);
3324  setxid_modified = true;
3325  }
3326  }
3327  else
3328  {
3329  switch (attr->transaction.action)
3330  {
3331  case cfa_fix:
3332 
3333  cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_CHANGE, pp, attr, "Removing setuid (root) flag from '%s'", file);
3334  result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE);
3335  break;
3336 
3337  case cfa_warn:
3338 
3339  if (amroot)
3340  {
3341  cfPS(ctx, LOG_LEVEL_WARNING, PROMISE_RESULT_WARN, pp, attr, "Need to remove setuid (root) flag on '%s'", file);
3342  result = PromiseResultUpdate(result, PROMISE_RESULT_WARN);
3343  }
3344  break;
3345  }
3346  }
3347  }
3348 
3349  if (dstat->st_uid == 0 && (dstat->st_mode & S_ISGID))
3350  {
3351  if (newperm & S_ISGID)
3352  {
3353  if (S_ISDIR(dstat->st_mode))
3354  {
3355  /* setgid directory */
3356  }
3357  else if (!IsInSetxidList(file))
3358  {
3359  if (amroot)
3360  {
3361  Log(LOG_LEVEL_NOTICE, "NEW SETGID root PROGRAM '%s' ", file);
3362  }
3363 
3364  PrependItem(&VSETXIDLIST, file, NULL);
3365  setxid_modified = true;
3366  }
3367  }
3368  else
3369  {
3370  switch (attr->transaction.action)
3371  {
3372  case cfa_fix:
3373 
3374  cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_CHANGE, pp, attr, "Removing setgid (root) flag from '%s'", file);
3375  result = PromiseResultUpdate(result, PROMISE_RESULT_CHANGE);
3376  break;
3377 
3378  case cfa_warn:
3379 
3380  cfPS(ctx, LOG_LEVEL_WARNING, PROMISE_RESULT_WARN, pp, attr, "Need to remove setgid (root) flag on '%s'", file);
3381  result = PromiseResultUpdate(result, PROMISE_RESULT_WARN);
3382  break;
3383 
3384  default:
3385  break;
3386  }
3387  }
3388  }
3389 
3390  SaveSetxid(setxid_modified);
3391 
3392  return result;
3393 }
3394 #endif
3395 
3396 #ifdef __APPLE__
3397 
3398 static int VerifyFinderType(EvalContext *ctx, const char *file, const Attributes *a, const Promise *pp, PromiseResult *result)
3399 { /* Code modeled after hfstar's extract.c */
3400  assert(a != NULL);
3401  typedef struct
3402  {
3403  long fdType;
3404  long fdCreator;
3405  short fdFlags;
3406  short fdLocationV;
3407  short fdLocationH;
3408  short fdFldr;
3409  short fdIconID;
3410  short fdUnused[3];
3411  char fdScript;
3412  char fdXFlags;
3413  short fdComment;
3414  long fdPutAway;
3415  }
3416  FInfo;
3417 
3418  struct attrlist attrs;
3419  struct
3420  {
3421  long ssize;
3422  struct timespec created;
3423  struct timespec modified;
3424  struct timespec changed;
3425  struct timespec backup;
3426  FInfo fi;
3427  }
3428  fndrInfo;
3429  int retval;
3430 
3431  if (a->perms.findertype == NULL)
3432  {
3433  return 0;
3434  }
3435 
3436  Log(LOG_LEVEL_DEBUG, "VerifyFinderType of '%s' for '%s'", file, a->perms.findertype);
3437 
3438  if (strncmp(a->perms.findertype, "*", CF_BUFSIZE) == 0 || strncmp(a->perms.findertype, "", CF_BUFSIZE) == 0)
3439  {
3440  return 0;
3441  }
3442 
3443  attrs.bitmapcount = ATTR_BIT_MAP_COUNT;
3444  attrs.reserved = 0;
3445  attrs.commonattr = ATTR_CMN_CRTIME | ATTR_CMN_MODTIME | ATTR_CMN_CHGTIME | ATTR_CMN_BKUPTIME | ATTR_CMN_FNDRINFO;
3446  attrs.volattr = 0;
3447  attrs.dirattr = 0;
3448  attrs.fileattr = 0;
3449  attrs.forkattr = 0;
3450 
3451  memset(&fndrInfo, 0, sizeof(fndrInfo));
3452 
3453  getattrlist(file, &attrs, &fndrInfo, sizeof(fndrInfo), 0);
3454 
3455  if (fndrInfo.fi.fdType != *(long *) a->perms.findertype)
3456  {
3457  fndrInfo.fi.fdType = *(long *) a->perms.findertype;
3458 
3459  switch (a->transaction.action)
3460  {
3461  case cfa_fix:
3462 
3463  if (DONTDO)
3464  {
3465  Log(LOG_LEVEL_INFO, "Promised to set Finder Type code of '%s' to '%s'", file, a->perms.findertype);
3466  *result = PromiseResultUpdate(*result, PROMISE_RESULT_WARN);
3467  return 0;
3468  }
3469 
3470  /* setattrlist does not take back in the long ssize */
3471  retval = setattrlist(file, &attrs, &fndrInfo.created, 4 * sizeof(struct timespec) + sizeof(FInfo), 0);
3472 
3473  Log(LOG_LEVEL_DEBUG, "CheckFinderType setattrlist returned '%d'", retval);
3474 
3475  if (retval >= 0)
3476  {
3477  cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_CHANGE, pp, a, "Setting Finder Type code of '%s' to '%s'", file, a->perms.findertype);
3478  *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
3479  }
3480  else
3481  {
3482  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Setting Finder Type code of '%s' to '%s' failed", file,
3483  a->perms.findertype);
3484  *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
3485  }
3486 
3487  return retval;
3488 
3489  case cfa_warn:
3490  Log(LOG_LEVEL_WARNING, "Darwin FinderType does not match -- not fixing.");
3491  *result = PromiseResultUpdate(*result, PROMISE_RESULT_WARN);
3492  return 0;
3493 
3494  default:
3495  return 0;
3496  }
3497  }
3498  else
3499  {
3500  cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, a, "Finder Type code of '%s' to '%s' is as promised", file, a->perms.findertype);
3501  *result = PromiseResultUpdate(*result, PROMISE_RESULT_NOOP);
3502  return 0;
3503  }
3504 }
3505 
3506 #endif
3507 
3508 static void TruncateFile(char *name)
3509 {
3510  struct stat statbuf;
3511  int fd;
3512 
3513  if (stat(name, &statbuf) == -1)
3514  {
3515  Log(LOG_LEVEL_DEBUG, "Didn't find '%s' to truncate", name);
3516  }
3517  else
3518  {
3519  if ((fd = safe_creat(name, 000)) == -1) /* dummy mode ignored */
3520  {
3521  Log(LOG_LEVEL_ERR, "Failed to create or truncate file '%s'. (create: %s)", name, GetErrorStr());
3522  }
3523  else
3524  {
3525  close(fd);
3526  }
3527  }
3528 }
3529 
3530 static void RegisterAHardLink(int i, char *value, const Attributes *attr, CompressedArray **inode_cache)
3531 {
3532  if (!FixCompressedArrayValue(i, value, inode_cache))
3533  {
3534  /* Not root hard link, remove to preserve consistency */
3535  if (DONTDO)
3536  {
3537  Log(LOG_LEVEL_VERBOSE, "Need to remove old hard link '%s' to preserve structure", value);
3538  }
3539  else
3540  {
3541  if (attr->transaction.action == cfa_warn)
3542  {
3543  Log(LOG_LEVEL_WARNING, "Need to remove old hard link '%s' to preserve structure", value);
3544  }
3545  else
3546  {
3547  Log(LOG_LEVEL_VERBOSE, "Removing old hard link '%s' to preserve structure", value);
3548  unlink(value);
3549  }
3550  }
3551  }
3552 }
3553 
3554 static int cf_stat(const char *file, struct stat *buf, const FileCopy *fc, AgentConnection *conn)
3555 {
3556  if (!file)
3557  {
3558  return -1;
3559  }
3560 
3561  if (conn == NULL)
3562  {
3563  return stat(file, buf);
3564  }
3565  else
3566  {
3567  assert(fc->servers != NULL &&
3568  strcmp(RlistScalarValue(fc->servers), "localhost") != 0);
3569 
3570  return cf_remote_stat(conn, fc->encrypt, file, buf, "file");
3571  }
3572 }
3573 
3574 #ifndef __MINGW32__
3575 
3576 static int cf_readlink(EvalContext *ctx, char *sourcefile, char *linkbuf, int buffsize,
3577  const Attributes *attr, const Promise *pp, AgentConnection *conn, PromiseResult *result)
3578  /* wrapper for network access */
3579 {
3580  assert(attr != NULL);
3581  memset(linkbuf, 0, buffsize);
3582 
3583  if (conn == NULL)
3584  {
3585  return readlink(sourcefile, linkbuf, buffsize - 1);
3586  }
3587  assert(attr->copy.servers &&
3588  strcmp(RlistScalarValue(attr->copy.servers), "localhost"));
3589 
3590  const Stat *sp = StatCacheLookup(conn, sourcefile,
3591  RlistScalarValue(attr->copy.servers));
3592 
3593  if (sp)
3594  {
3595  if (sp->cf_readlink != NULL)
3596  {
3597  if (strlen(sp->cf_readlink) + 1 > buffsize)
3598  {
3599  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, attr, "readlink value is too large in cfreadlink");
3600  *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
3601  Log(LOG_LEVEL_ERR, "Contained '%s'", sp->cf_readlink);
3602  return -1;
3603  }
3604  else
3605  {
3606  memset(linkbuf, 0, buffsize);
3607  strcpy(linkbuf, sp->cf_readlink);
3608  return 0;
3609  }
3610  }
3611  }
3612 
3613  return -1;
3614 }
3615 
3616 #endif /* !__MINGW32__ */
3617 
3618 static bool SkipDirLinks(EvalContext *ctx, char *path, const char *lastnode, DirectoryRecursion r)
3619 {
3620  if (r.exclude_dirs)
3621  {
3622  if ((MatchRlistItem(ctx, r.exclude_dirs, path)) || (MatchRlistItem(ctx, r.exclude_dirs, lastnode)))
3623  {
3624  Log(LOG_LEVEL_VERBOSE, "Skipping matched excluded directory '%s'", path);
3625  return true;
3626  }
3627  }
3628 
3629  if (r.include_dirs)
3630  {
3631  if (!((MatchRlistItem(ctx, r.include_dirs, path)) || (MatchRlistItem(ctx, r.include_dirs, lastnode))))
3632  {
3633  Log(LOG_LEVEL_VERBOSE, "Skipping matched non-included directory '%s'", path);
3634  return true;
3635  }
3636  }
3637 
3638  return false;
3639 }
3640 
3641 #ifndef __MINGW32__
3642 
3643 bool VerifyOwner(EvalContext *ctx, const char *file, const Promise *pp, const Attributes *attr, const struct stat *sb, PromiseResult *result)
3644 {
3645  struct passwd *pw;
3646  struct group *gp;
3647  UidList *ulp;
3648  GidList *glp;
3649 
3650  /* The groups to change ownership to, using lchown(uid,gid). */
3651  uid_t uid = CF_UNKNOWN_OWNER; /* just init values */
3652  gid_t gid = CF_UNKNOWN_GROUP;
3653 
3654  /* SKIP if file is already owned by anyone of the promised owners. */
3655  for (ulp = attr->perms.owners; ulp != NULL; ulp = ulp->next)
3656  {
3657  if (ulp->uid == CF_SAME_OWNER || sb->st_uid == ulp->uid) /* "same" matches anything */
3658  {
3659  uid = CF_SAME_OWNER; /* chown(-1) doesn't change ownership */
3660  break;
3661  }
3662  }
3663 
3664  if (uid != CF_SAME_OWNER)
3665  {
3666  /* Change ownership to the first known user in the promised list. */
3667  for (ulp = attr->perms.owners; ulp != NULL; ulp = ulp->next)
3668  {
3669  if (ulp->uid != CF_UNKNOWN_OWNER)
3670  {
3671  uid = ulp->uid;
3672  break;
3673  }
3674  }
3675  if (ulp == NULL)
3676  {
3677  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, attr,
3678  "None of the promised owners for '%s' exist -- see INFO logs for more",
3679  file);
3680  *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
3681  uid = CF_SAME_OWNER; /* chown(-1) doesn't change ownership */
3682  }
3683  }
3684  assert(uid != CF_UNKNOWN_OWNER);
3685 
3686  /* SKIP if file is already group owned by anyone of the promised groups. */
3687  for (glp = attr->perms.groups; glp != NULL; glp = glp->next)
3688  {
3689  if (glp->gid == CF_SAME_GROUP || sb->st_gid == glp->gid)
3690  {
3691  gid = CF_SAME_GROUP; /* chown(-1) doesn't change ownership */
3692  break;
3693  }
3694  }
3695 
3696  /* Change group ownership to the first known group in the promised list. */
3697  if (gid != CF_SAME_GROUP)
3698  {
3699  for (glp = attr->perms.groups; glp != NULL; glp = glp->next)
3700  {
3701  if (glp->gid != CF_UNKNOWN_GROUP)
3702  {
3703  gid = glp->gid;
3704  break;
3705  }
3706  }
3707  if (glp == NULL)
3708  {
3709  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, attr,
3710  "None of the promised groups for '%s' exist -- see INFO logs for more",
3711  file);
3712  *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
3713  gid = CF_SAME_GROUP; /* chown(-1) doesn't change ownership */
3714  }
3715  }
3716  assert(gid != CF_UNKNOWN_GROUP);
3717 
3718  if (uid == CF_SAME_OWNER &&
3719  gid == CF_SAME_GROUP)
3720  {
3721  /* User and group as promised, skip completely. */
3722  return false;
3723  }
3724  else
3725  {
3726  switch (attr->transaction.action)
3727  {
3728  case cfa_fix:
3729 
3730  if (uid != CF_SAME_OWNER)
3731  {
3732  Log(LOG_LEVEL_DEBUG, "Change owner to uid '%ju' if possible",
3733  (uintmax_t) uid);
3734  }
3735 
3736  if (gid != CF_SAME_GROUP)
3737  {
3738  Log(LOG_LEVEL_DEBUG, "Change group to gid '%ju' if possible",
3739  (uintmax_t) gid);
3740  }
3741 
3742  if (!DONTDO && S_ISLNK(sb->st_mode))
3743  {
3744 # ifdef HAVE_LCHOWN
3745  Log(LOG_LEVEL_DEBUG, "Using lchown function");
3746  if (safe_lchown(file, uid, gid) == -1)
3747  {
3748  Log(LOG_LEVEL_INFO, "Cannot set ownership on link '%s'. (lchown: %s)", file, GetErrorStr());
3749  }
3750  else
3751  {
3752  return true;
3753  }
3754 # endif
3755  }
3756  else if (!DONTDO)
3757  {
3758  if (uid != CF_SAME_OWNER)
3759  {
3760  cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_CHANGE, pp, attr,
3761  "Owner of '%s' was %ju, setting to %ju",
3762  file, (uintmax_t) sb->st_uid, (uintmax_t) uid);
3763  *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
3764  }
3765 
3766  if (gid != CF_SAME_GROUP)
3767  {
3768  cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_CHANGE, pp, attr,
3769  "Group of '%s' was %ju, setting to %ju",
3770  file, (uintmax_t)sb->st_gid, (uintmax_t)gid);
3771  *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
3772  }
3773 
3774  if (!S_ISLNK(sb->st_mode))
3775  {
3776  if (safe_chown(file, uid, gid) == -1)
3777  {
3778  cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_DENIED, pp, attr, "Cannot set ownership on file '%s'. (chown: %s)",
3779  file, GetErrorStr());
3780  *result = PromiseResultUpdate(*result, PROMISE_RESULT_DENIED);
3781  }
3782  else
3783  {
3784  return true;
3785  }
3786  }
3787  }
3788  break;
3789 
3790  case cfa_warn:
3791 
3792  if ((pw = getpwuid(sb->st_uid)) == NULL)
3793  {
3794  Log(LOG_LEVEL_WARNING, "File '%s' is not owned by anybody in the passwd database", file);
3795  Log(LOG_LEVEL_WARNING, "(uid = %ju,gid = %ju)", (uintmax_t)sb->st_uid, (uintmax_t)sb->st_gid);
3796  *result = PromiseResultUpdate(*result, PROMISE_RESULT_WARN);
3797  break;
3798  }
3799 
3800  if ((gp = getgrgid(sb->st_gid)) == NULL)
3801  {
3802  cfPS(ctx, LOG_LEVEL_WARNING, PROMISE_RESULT_WARN, pp, attr, "File '%s' is not owned by any group in group database",
3803  file);
3804  *result = PromiseResultUpdate(*result, PROMISE_RESULT_WARN);
3805  break;
3806  }
3807 
3808  cfPS(ctx, LOG_LEVEL_WARNING, PROMISE_RESULT_WARN, pp, attr, "File '%s' is owned by '%s', group '%s'", file, pw->pw_name,
3809  gp->gr_name);
3810  *result = PromiseResultUpdate(*result, PROMISE_RESULT_WARN);
3811  break;
3812  }
3813  }
3814 
3815  return false;
3816 }
3817 
3818 #endif /* !__MINGW32__ */
3819 
3820 static void VerifyFileChanges(const char *file, const struct stat *sb, const Attributes *attr, const Promise *pp)
3821 {
3823  {
3824  return;
3825  }
3826 
3827  FileChangesCheckAndUpdateStats(file, sb, attr->change.update, pp);
3828 }
3829 
3830 bool CfCreateFile(EvalContext *ctx, char *file, const Promise *pp, const Attributes *attr, PromiseResult *result)
3831 {
3832  assert(attr != NULL);
3833  if (!IsAbsoluteFileName(file))
3834  {
3835  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, attr,
3836  "Cannot create a relative filename '%s' - has no invariant meaning. (create: %s)", file, GetErrorStr());
3837  *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
3838  return false;
3839  }
3840 
3841  /* If name ends in /., or depthsearch is set, then this is a directory */
3842  bool is_dir = attr->havedepthsearch || (strcmp(".", ReadLastNode(file)) == 0);
3843  if (is_dir)
3844  {
3845  Log(LOG_LEVEL_DEBUG, "File object '%s' seems to be a directory", file);
3846 
3847  if (!DONTDO && attr->transaction.action != cfa_warn)
3848  {
3849  if (!MakeParentDirectory(file, attr->move_obstructions))
3850  {
3851  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, attr, "Error creating directories for '%s'. (create: %s)",
3852  file, GetErrorStr());
3853  *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
3854  return false;
3855  }
3856 
3857  cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_CHANGE, pp, attr, "Created directory '%s'", file);
3858  *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
3859  }
3860  else
3861  {
3862  cfPS(ctx, LOG_LEVEL_WARNING, PROMISE_RESULT_WARN, pp, attr, "Warning promised, need to create directory '%s'", file);
3863  *result = PromiseResultUpdate(*result, PROMISE_RESULT_WARN);
3864  return false;
3865  }
3866  }
3867  else if (attr->file_type && !strncmp(attr->file_type, "fifo", 5))
3868  {
3869 #ifndef _WIN32
3870  if (!DONTDO)
3871  {
3872  mode_t saveumask = umask(0);
3873  mode_t filemode = 0600;
3874 
3875  if (PromiseGetConstraintAsRval(pp, "mode", RVAL_TYPE_SCALAR) == NULL)
3876  {
3877  /* Relying on umask is risky */
3878  filemode = 0600;
3879  Log(LOG_LEVEL_VERBOSE, "No mode was set, choose plain file default %04jo", (uintmax_t)filemode);
3880  }
3881  else
3882  {
3883  filemode = attr->perms.plus & ~(attr->perms.minus);
3884  }
3885 
3887 
3888  char errormsg[CF_BUFSIZE];
3889  if (!mkfifo(file, filemode))
3890  {
3891  snprintf(errormsg, sizeof(errormsg), "(mkfifo: %s)", GetErrorStr());
3892  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, attr, "Error creating file '%s', mode '%04jo'. %s",
3893  file, (uintmax_t)filemode, errormsg);
3894  umask(saveumask);
3895  return false;
3896  }
3897 
3898  umask(saveumask);
3899  return true;
3900  }
3901  else
3902  {
3903  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_WARN, pp, attr, "Warning promised, need to create fifo '%s'", file);
3904  *result = PromiseResultUpdate(*result, PROMISE_RESULT_WARN);
3905  return false;
3906  }
3907 #else
3908  cfPS(ctx, LOG_LEVEL_WARNING, PROMISE_RESULT_WARN, pp, attr, "Operation not supported on Windows");
3909  *result = PromiseResultUpdate(*result, PROMISE_RESULT_WARN);
3910  return false;
3911 #endif
3912  }
3913  else
3914  {
3915  if (!DONTDO && attr->transaction.action != cfa_warn)
3916  {
3917  mode_t saveumask = umask(0);
3918  mode_t filemode = 0600; /* Decide the mode for filecreation */
3919 
3920  if (PromiseGetConstraintAsRval(pp, "mode", RVAL_TYPE_SCALAR) == NULL)
3921  {
3922  /* Relying on umask is risky */
3923  filemode = 0600;
3924  Log(LOG_LEVEL_VERBOSE, "No mode was set, choose plain file default %04jo", (uintmax_t)filemode);
3925  }
3926  else
3927  {
3928  filemode = attr->perms.plus & ~(attr->perms.minus);
3929  }
3930 
3932 
3933  int fd = safe_open_create_perms(file, O_WRONLY | O_CREAT | O_EXCL, filemode);
3934  if (fd == -1)
3935  {
3936  char errormsg[CF_BUFSIZE];
3937  if (errno == EEXIST)
3938  {
3939  snprintf(errormsg, sizeof(errormsg), "(open: '%s'). "
3940  "Most likely a dangling symlink is in the way. "
3941  "Refusing to create the target file of dangling symlink (security risk).",
3942  GetErrorStr());
3943  }
3944  else
3945  {
3946  snprintf(errormsg, sizeof(errormsg), "(open: %s)", GetErrorStr());
3947  }
3948  cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, attr, "Error creating file '%s', mode '%04jo'. %s",
3949  file, (uintmax_t)filemode, errormsg);
3950  *result = PromiseResultUpdate(*result, PROMISE_RESULT_FAIL);
3951  umask(saveumask);
3952  return false;
3953  }
3954  else
3955  {
3956  cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_CHANGE, pp, attr, "Created file '%s', mode %04jo", file, (uintmax_t)filemode);
3957  *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE);
3958  close(fd);
3959  umask(saveumask);
3960  return true;
3961  }
3962  }
3963  else
3964  {
3965  cfPS(ctx, LOG_LEVEL_WARNING, PROMISE_RESULT_WARN, pp, attr, "Warning promised, need to create file '%s'", file);
3966  *result = PromiseResultUpdate(*result, PROMISE_RESULT_WARN);
3967  return false;
3968  }
3969  }
3970 
3971  return true;
3972 }
3973 
3974 static bool DeviceBoundary(const struct stat *sb, dev_t rootdevice)
3975 {
3976  if (sb->st_dev == rootdevice)
3977  {
3978  return false;
3979  }
3980  else
3981  {
3982  Log(LOG_LEVEL_VERBOSE, "Device change from %jd to %jd", (intmax_t) rootdevice, (intmax_t) sb->st_dev);
3983  return true;
3984  }
3985 }
const struct direct * AbstractDirRead(AbstractDir *dir)
Definition: abstract_dir.c:81
void AbstractDirClose(AbstractDir *dir)
Definition: abstract_dir.c:102
AbstractDir * AbstractDirOpen(const char *dirname, const FileCopy *fc, AgentConnection *conn)
Definition: abstract_dir.c:42
PromiseResult PromiseResultUpdate(PromiseResult prior, PromiseResult evidence)
Definition: actuator.c:28
void * xmalloc(size_t size)
Definition: alloc-mini.c:46
char * xstrdup(const char *str)
Definition: alloc-mini.c:56
char * xstrndup(const char *str, size_t n)
Definition: alloc.c:61
Attributes GetFilesAttributes(const EvalContext *ctx, const Promise *pp)
Definition: attributes.c:46
void FatalError(const EvalContext *ctx, char *s,...)
Definition: audit.c:94
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
PromiseResult LogFileChange(EvalContext *ctx, const char *file, int change, const Attributes *attr, const Promise *pp, CopyRegularFileFunction CopyRegularFilePtr, const char *destination, DeleteCompressedArrayFunction DeleteCompressedArrayPtr)
@ FILE_COMPARATOR_ATIME
Definition: cf3.defs.h:697
@ FILE_COMPARATOR_HASH
Definition: cf3.defs.h:701
@ FILE_COMPARATOR_MTIME
Definition: cf3.defs.h:698
@ FILE_COMPARATOR_EXISTS
Definition: cf3.defs.h:703
@ FILE_COMPARATOR_CHECKSUM
Definition: cf3.defs.h:700
@ FILE_COMPARATOR_BINARY
Definition: cf3.defs.h:702
@ FILE_CHANGE_REPORT_CONTENT_CHANGE
Definition: cf3.defs.h:747
@ FILE_CHANGE_REPORT_ALL
Definition: cf3.defs.h:749
@ FILE_CHANGE_REPORT_STATS_CHANGE
Definition: cf3.defs.h:748
#define CF_SAVED
Definition: cf3.defs.h:99
@ COMMON_CONTROL_PROTOCOL_VERSION
Definition: cf3.defs.h:433
@ RVAL_TYPE_SCALAR
Definition: cf3.defs.h:606
#define DEFAULTMODE
Definition: cf3.defs.h:342
#define CF_UNKNOWN_GROUP
Definition: cf3.defs.h:65
PromiseResult
Definition: cf3.defs.h:122
@ PROMISE_RESULT_CHANGE
Definition: cf3.defs.h:125
@ PROMISE_RESULT_INTERRUPTED
Definition: cf3.defs.h:130
@ PROMISE_RESULT_NOOP
Definition: cf3.defs.h:124
@ PROMISE_RESULT_WARN
Definition: cf3.defs.h:126
@ PROMISE_RESULT_DENIED
Definition: cf3.defs.h:128
@ PROMISE_RESULT_FAIL
Definition: cf3.defs.h:127
#define CF_SAME_OWNER
Definition: cf3.defs.h:62
@ cfa_force
Definition: cf3.defs.h:733
@ BACKUP_OPTION_NO_BACKUP
Definition: cf3.defs.h:725
@ BACKUP_OPTION_REPOSITORY_STORE
Definition: cf3.defs.h:728
@ BACKUP_OPTION_TIMESTAMP
Definition: cf3.defs.h:726
@ cfa_warn
Definition: cf3.defs.h:719
@ cfa_fix
Definition: cf3.defs.h:718
@ cfa_override
Definition: cf3.defs.h:740
#define CF_SAME_GROUP
Definition: cf3.defs.h:64
#define CF_NEW
Definition: cf3.defs.h:101
#define CF_SAMEMODE
Definition: cf3.defs.h:60
@ CF_DATA_TYPE_STRING
Definition: cf3.defs.h:369
@ CONTEXT_SCOPE_NAMESPACE
Definition: cf3.defs.h:946
@ FILE_LINK_TYPE_HARDLINK
Definition: cf3.defs.h:710
@ FILE_LINK_TYPE_NONE
Definition: cf3.defs.h:713
@ FILE_LINK_TYPE_RELATIVE
Definition: cf3.defs.h:711
@ FILE_LINK_TYPE_SYMLINK
Definition: cf3.defs.h:709
@ FILE_LINK_TYPE_ABSOLUTE
Definition: cf3.defs.h:712
#define CF_NOINT
Definition: cf3.defs.h:339
#define CF_UNKNOWN_OWNER
Definition: cf3.defs.h:63
bool DONTDO
Definition: cf3globals.c:55
time_t CONNTIMEOUT
Definition: cf3globals.c:106
time_t CFSTARTTIME
Definition: cf3globals.c:99
int CF_PERSISTENCE
Definition: cf3globals.c:41
char VUQNAME[]
Definition: cf3globals.c:59
struct utsname VSYSNAME
Definition: cf3globals.c:38
void free(void *)
char CFENGINE_PORT_STR[16]
Definition: client_code.c:78
bool CopyRegularFileNet(const char *source, const char *dest, off_t size, bool encrypt, AgentConnection *conn)
Definition: client_code.c:727
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
AgentConnection * NewAgentConn(const char *server, const char *port, ConnectionFlags flags)
Allocates a new AgentConnection (stores a connection from Agent to Server).
Definition: communication.c:38
void DeleteCompressedArray(CompressedArray *start)
Definition: comparray.c:59
bool CompressedArrayElementExists(CompressedArray *start, int key)
Definition: comparray.c:77
bool FixCompressedArrayValue(int i, char *value, CompressedArray **start)
Definition: comparray.c:35
char * CompressedArrayValue(CompressedArray *start, int key)
Definition: comparray.c:94
void ConnCache_Add(AgentConnection *conn, enum ConnCacheStatus status)
Definition: conn_cache.c:233
void ConnCache_MarkNotBusy(AgentConnection *conn)
Definition: conn_cache.c:190
AgentConnection * ConnCache_FindIdleMarkBusy(const char *server, const char *port, ConnectionFlags flags)
Definition: conn_cache.c:102
@ CONNCACHE_STATUS_OFFLINE
Definition: conn_cache.h:35
@ CONNCACHE_STATUS_BUSY
Definition: conn_cache.h:34
@ CONNECTIONINFO_STATUS_NOT_ESTABLISHED
@ CONNECTIONINFO_STATUS_ESTABLISHED
const char * CommandArg0(const char *execstr)
Definition: conversion.c:882
@ CONTEXT_STATE_POLICY_PRESERVE
Definition: db_structs.h:138
#define CF_PERMS_DEFAULT
Definition: definitions.h:58
#define CF_BUFSIZE
Definition: definitions.h:50
#define CF_MAXVARSIZE
Definition: definitions.h:36
const struct dirent * DirRead(Dir *dir)
Definition: unix_dir.c:92
void DirClose(Dir *dir)
Definition: unix_dir.c:118
Dir * DirOpen(const char *dirname)
Definition: unix_dir.c:41
void EvalContextHeapPersistentSave(EvalContext *ctx, const char *name, unsigned int ttl_minutes, PersistentClassPolicy policy, const char *tags)
Definition: eval_context.c:643
void cfPS(EvalContext *ctx, LogLevel level, PromiseResult status, const Promise *pp, const Attributes *attr, const char *fmt,...)
const void * EvalContextVariableControlCommonGet(const EvalContext *ctx, CommonControl lval)
bool EvalContextVariablePutSpecial(EvalContext *ctx, SpecialScope scope, const char *lval, const void *value, DataType type, const char *tags)
bool EvalContextClassPutSoft(EvalContext *ctx, const char *name, ContextScope scope, const char *tags)
bool IsExecutable(const char *file)
Definition: unix.c:130
char * ExpandScalar(const EvalContext *ctx, const char *ns, const char *scope, const char *string, Buffer *out)
Definition: expand.c:516
bool DeleteDirectoryTree(const char *path)
Deletes directory path recursively. Symlinks are not followed. Note that this function only deletes t...
Definition: file_lib.c:1304
int safe_chmod(const char *path, mode_t mode)
Definition: file_lib.c:1122
int safe_chdir(const char *path)
Definition: file_lib.c:903
int safe_lchown(const char *path, uid_t owner, gid_t group)
Definition: file_lib.c:1110
ssize_t CfReadLine(char **buff, size_t *size, FILE *fp)
Works exactly like posix 'getline', EXCEPT it does not include carriage return at the end.
Definition: file_lib.c:1476
bool IsAbsoluteFileName(const char *f)
Definition: file_lib.c:333
int safe_open(const char *pathname, int flags)
Definition: file_lib.c:516
int safe_chown(const char *path, uid_t owner, gid_t group)
Definition: file_lib.c:1093
int safe_open_create_perms(const char *const pathname, int flags, const mode_t create_perms)
Definition: file_lib.c:541
int safe_creat(const char *pathname, mode_t mode)
Definition: file_lib.c:1203
char * MapName(char *s)
Definition: file_lib.c:441
#define FILE_SEPARATOR
Definition: file_lib.h:102
@ NewLineMode_Unix
Definition: file_lib.h:36
void FileChangesLogNewFile(const char *path, const Promise *pp)
bool FileChangesGetDirectoryList(const char *path, Seq *files)
void FileChangesCheckAndUpdateStats(const char *file, const struct stat *sb, bool update, const Promise *pp)
void FileChangesCheckAndUpdateDirectory(const char *name, const Seq *file_set, const Seq *db_file_set, bool update, const Promise *pp, PromiseResult *result)
void FileChangesLogChange(const char *file, FileState status, char *msg, const Promise *pp)
bool FileChangesCheckAndUpdateHash(EvalContext *ctx, const char *filename, unsigned char digest[EVP_MAX_MD_SIZE+1], HashMethod type, const Attributes *attr, const Promise *pp, PromiseResult *result)
@ FILE_STATE_CONTENT_CHANGED
Definition: files_changes.h:34
bool CopyRegularFileDisk(const char *source, const char *destination)
Definition: files_copy.c:106
bool CopyFileExtendedAttributesDisk(const char *source, const char *destination)
Definition: files_copy.c:197
bool CopyRegularFileDiskPerms(const char *source, const char *destination, int mode)
Definition: files_copy.c:39
int cf_lstat(const char *file, struct stat *buf, const FileCopy *fc, AgentConnection *conn)
void RotateFiles(char *name, int number)
Definition: files_lib.c:546
void PurgeItemList(Item **list, char *name)
Definition: files_lib.c:44
bool MakeParentDirectory(const char *parentandchild, bool force)
Definition: files_lib.c:133
bool IsAbsPath(const char *path)
Definition: files_names.c:214
bool ChopLastNode(char *str)
Definition: files_names.c:422
char * CanonifyName(const char *str)
Definition: files_names.c:483
char * JoinSuffix(char *path, size_t path_size, const char *leaf)
Definition: files_names.c:152
void AddSlash(char *str)
Definition: files_names.c:230
const char * ReadLastNode(const char *str)
Definition: files_names.c:539
void DeleteSlash(char *str)
Definition: files_names.c:320
bool ConsiderAbstractFile(const char *filename, const char *directory, const FileCopy *fc, AgentConnection *conn)
bool ConsiderLocalFile(const char *filename, const char *directory)
bool FileInRepository(const char *filename)
bool ArchiveToRepository(const char *file, const Attributes *attr)
bool GetRepositoryPath(const char *file, const Attributes *attr, char *destination)
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
void HashFile(const char *const filename, unsigned char digest[EVP_MAX_MD_SIZE+1], HashMethod type, bool text_mode)
Definition: hash.c:443
@ HASH_METHOD_BEST
Definition: hash_method.h:44
@ HASH_METHOD_MD5
Definition: hash_method.h:37
@ HASH_METHOD_SHA1
Definition: hash_method.h:42
void EndMeasure(char *eventname, struct timespec start)
struct timespec BeginMeasure()
bool RawSaveItemList(const Item *liststart, const char *filename, NewLineMode new_line_mode)
Definition: item_lib.c:1016
bool ListsCompare(const Item *list1, const Item *list2)
Definition: item_lib.c:262
void AppendItem(Item **liststart, const char *itemstring, const char *classes)
Definition: item_lib.c:415
void DeleteItemList(Item *item)
Definition: item_lib.c:808
Item * RawLoadItemList(const char *filename)
Definition: item_lib.c:1055
bool IsItemIn(const Item *list, const char *item)
Definition: item_lib.c:226
Item * PrependItem(Item **liststart, const char *itemstring, const char *classes)
Definition: item_lib.c:372
const char * GetLogDir(void)
Definition: known_dirs.c:146
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_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
bool FullTextMatch(EvalContext *ctx, const char *regexp, const char *teststring)
Definition: match_scope.c:87
#define ProgrammingError(...)
Definition: misc_lib.h:33
bool IsPrivileged()
Definition: patches.c:105
int cf_pclose(FILE *pp)
Definition: pipes_unix.c:812
FILE * cf_popen(const char *command, const char *type, bool capture_stderr)
Definition: pipes_unix.c:332
#define S_IXGRP
Definition: platform.h:945
#define S_IROTH
Definition: platform.h:950
uid_t getuid(void)
#define O_BINARY
Definition: platform.h:1001
#define S_IXOTH
Definition: platform.h:952
#define S_IRGRP
Definition: platform.h:943
#define CHFLAGS_MASK
Definition: platform.h:996
int lstat(const char *file_name, struct stat *buf)
#define S_ISDIR(m)
Definition: platform.h:916
#define S_ISBLK(m)
Definition: platform.h:928
#define S_ISSOCK(m)
Definition: platform.h:931
#define S_ISCHR(m)
Definition: platform.h:925
#define S_ISFIFO(m)
Definition: platform.h:922
#define S_ISLNK(m)
Definition: platform.h:919
#define S_IRUSR
Definition: platform.h:936
#define dirent
Definition: platform.h:160
#define S_IFMT
Definition: platform.h:909
#define S_ISREG(m)
Definition: platform.h:913
#define S_IXUSR
Definition: platform.h:938
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
void PromiseRef(LogLevel level, const Promise *pp)
Definition: promises.c:769
ProtocolVersion
@ CF_PROTOCOL_UNDEFINED
#define ProtocolVersionParse
bool VerifyCommandRetcode(EvalContext *ctx, int retcode, const Attributes *a, const Promise *pp, PromiseResult *result)
Definition: retcode.c:32
char * RlistScalarValue(const Rlist *rlist)
Definition: rlist.c:83
bool RlistIsInListOfRegex(const Rlist *list, const char *str)
Definition: rlist.c:267
@ SPECIAL_SCOPE_THIS
Definition: scope.h:39
void * SeqBinaryLookup(Seq *seq, const void *key, SeqItemComparator Compare)
Performs a binary search looking for the item matching the given key. It is the programmer's responsi...
Definition: sequence.c:175
Seq * SeqNew(size_t initialCapacity, void(ItemDestroy)(void *item))
Definition: sequence.c:31
void SeqDestroy(Seq *seq)
Destroy an existing Sequence.
Definition: sequence.c:60
void SeqAppend(Seq *seq, void *item)
Append a new item to the Sequence.
Definition: sequence.c:104
int(* SeqItemComparator)(const void *, const void *, void *user_data)
Function to compare two items in a Sequence.
Definition: sequence.h:100
bool StringSetContains(const StringSet *set, const char *element)
Definition: set.c:34
void StringSetAdd(const StringSet *set, char *element)
Definition: set.c:34
int cf_remote_stat(AgentConnection *conn, bool encrypt, const char *file, struct stat *statbuf, const char *stattype)
Definition: stat_cache.c:108
const Stat * StatCacheLookup(const AgentConnection *conn, const char *file_name, const char *server_name)
Definition: stat_cache.c:300
bool PathAppend(char *path, size_t path_size, const char *leaf, char sep)
Definition: string_lib.c:1454
void ToLowerStrInplace(char *str)
Definition: string_lib.c:162
size_t strlcat(char *dst, const char *src, size_t siz)
Definition: strlcat.c:36
size_t strlcpy(char *dst, const char *src, size_t siz)
Definition: strlcpy.c:34
char * strstr(const char *haystack, const char *needle)
Definition: strstr.c:35
Rlist * acl_entries
Definition: cf3.defs.h:862
int authenticated
Definition: cfnet.h:99
ConnectionInfo * conn_info
Definition: cfnet.h:98
ConnectionFlags flags
Definition: cfnet.h:111
short error
Definition: cfnet.h:106
char * this_server
Definition: cfnet.h:112
char remoteip[64]
Definition: cfnet.h:103
int haveselect
Definition: cf3.defs.h:1587
int haveperms
Definition: cf3.defs.h:1590
FilePerms perms
Definition: cf3.defs.h:1537
FileRename rename
Definition: cf3.defs.h:1540
FileDelete delete
Definition: cf3.defs.h:1539
FileLink link
Definition: cf3.defs.h:1542
char * transformer
Definition: cf3.defs.h:1553
FileCopy copy
Definition: cf3.defs.h:1538
char * file_type
Definition: cf3.defs.h:1555
FileChange change
Definition: cf3.defs.h:1541
int havechange
Definition: cf3.defs.h:1591
int move_obstructions
Definition: cf3.defs.h:1563
int haverename
Definition: cf3.defs.h:1588
DirectoryRecursion recursion
Definition: cf3.defs.h:1566
FileSelect select
Definition: cf3.defs.h:1536
TransactionContext transaction
Definition: cf3.defs.h:1567
int havedepthsearch
Definition: cf3.defs.h:1586
int havedelete
Definition: cf3.defs.h:1589
Definition: buffer.h:50
char * lock
Definition: cf3.defs.h:895
ProtocolVersion protocol_version
Definition: cfnet.h:56
bool cache_connection
Definition: cfnet.h:57
ConnectionStatus status
Definition: unix_dir.c:33
Rlist * exclude_dirs
Definition: cf3.defs.h:920
Rlist * include_dirs
Definition: cf3.defs.h:919
int report_diffs
Definition: cf3.defs.h:1086
int update
Definition: cf3.defs.h:1087
HashMethod hash
Definition: cf3.defs.h:1084
FileChangeReport report_changes
Definition: cf3.defs.h:1085
int check_root
Definition: cf3.defs.h:1519
Rlist * copy_links
Definition: cf3.defs.h:1514
int force_ipv4
Definition: cf3.defs.h:1522
ProtocolVersion protocol_version
Definition: cf3.defs.h:1530
size_t min_size
Definition: cf3.defs.h:1523
size_t max_size
Definition: cf3.defs.h:1524
int verify
Definition: cf3.defs.h:1527
int collapse
Definition: cf3.defs.h:1518
const char * source
Definition: cf3.defs.h:1507
bool missing_ok
Definition: cf3.defs.h:1531
const char * port
Definition: cf3.defs.h:1508
BackupOption backup
Definition: cf3.defs.h:1515
Rlist * link_instead
Definition: cf3.defs.h:1513
int stealth
Definition: cf3.defs.h:1516
Rlist * servers
Definition: cf3.defs.h:1512
int purge
Definition: cf3.defs.h:1528
FileLinkType link_type
Definition: cf3.defs.h:1511
int trustkey
Definition: cf3.defs.h:1525
char * destination
Definition: cf3.defs.h:1509
int type_check
Definition: cf3.defs.h:1520
FileComparator compare
Definition: cf3.defs.h:1510
int force_update
Definition: cf3.defs.h:1521
short timeout
Definition: cf3.defs.h:1529
int preserve
Definition: cf3.defs.h:1517
int encrypt
Definition: cf3.defs.h:1526
int rmdirs
Definition: cf3.defs.h:1065
char * findertype
Definition: cf3.defs.h:1025
u_long plus_flags
Definition: cf3.defs.h:1026
GidList * groups
Definition: cf3.defs.h:1024
u_long minus_flags
Definition: cf3.defs.h:1027
mode_t minus
Definition: cf3.defs.h:1022
int rxdirs
Definition: cf3.defs.h:1028
UidList * owners
Definition: cf3.defs.h:1023
mode_t plus
Definition: cf3.defs.h:1021
mode_t plus
Definition: cf3.defs.h:1076
int disable
Definition: cf3.defs.h:1074
char * disable_suffix
Definition: cf3.defs.h:1073
int rotate
Definition: cf3.defs.h:1075
char * newname
Definition: cf3.defs.h:1072
mode_t minus
Definition: cf3.defs.h:1077
GidList * next
Definition: cf3.defs.h:331
gid_t gid
Definition: cf3.defs.h:329
Definition: item_lib.h:33
char * promiser
Definition: policy.h:115
Definition: rlist.h:35
Rlist * next
Definition: rlist.h:37
Sequence data-structure.
Definition: sequence.h:50
char * cf_readlink
Definition: stat_cache.h:57
Definition: set.h:138
enum cfopaction action
Definition: cf3.defs.h:927
UidList * next
Definition: cf3.defs.h:319
uid_t uid
Definition: cf3.defs.h:317
char nodename[257]
Definition: platform.h:109
PromiseResult VerifyACL(EvalContext *ctx, const char *file, const Attributes *attr, const Promise *pp)
Definition: verify_acl.c:51
bool CompareBinaryFiles(const char *file1, const char *file2, const struct stat *sstat, const struct stat *dstat, const FileCopy *fc, AgentConnection *conn)
bool CompareFileHashes(const char *file1, const char *file2, const struct stat *sstat, const struct stat *dstat, const FileCopy *fc, AgentConnection *conn)
static bool PopDirState(int goback, char *name, const struct stat *sb, DirectoryRecursion r)
static void LoadSetxid(void)
static PromiseResult VerifyCopy(EvalContext *ctx, const char *source, char *destination, const Attributes *attr, const Promise *pp, CompressedArray **inode_cache, AgentConnection *conn)
#define CF_RECURSION_LIMIT
static void RegisterAHardLink(int i, char *value, const Attributes *attr, CompressedArray **inode_cache)
static const Rlist * AUTO_DEFINE_LIST
static PromiseResult VerifyCopiedFileAttributes(EvalContext *ctx, const char *src, const char *dest, const struct stat *sstat, const struct stat *dstat, const Attributes *attr, const Promise *pp)
static bool MatchRlistItem(EvalContext *ctx, const Rlist *listofregex, const char *teststring)
Attributes GetExpandedAttributes(EvalContext *ctx, const Promise *pp, const Attributes *attr)
Definition: verify_files.c:224
static int cf_stat(const char *file, struct stat *buf, const FileCopy *fc, AgentConnection *conn)
PromiseResult ScheduleLinkOperation(EvalContext *ctx, char *destination, char *source, const Attributes *attr, const Promise *pp)
static PromiseResult CopyFileSources(EvalContext *ctx, char *destination, const Attributes *attr, const Promise *pp, AgentConnection *conn)
bool CopyRegularFile(EvalContext *ctx, const char *source, const char *dest, const struct stat *sstat, const Attributes *attr, const Promise *pp, CompressedArray **inode_cache, AgentConnection *conn, PromiseResult *result)
PromiseResult ScheduleCopyOperation(EvalContext *ctx, char *destination, const Attributes *attr, const Promise *pp)
static PromiseResult TouchFile(EvalContext *ctx, char *path, const Attributes *attr, const Promise *pp)
static bool CompareForFileCopy(char *sourcefile, char *destfile, const struct stat *ssb, const struct stat *dsb, const FileCopy *fc, AgentConnection *conn)
static void FileAutoDefine(EvalContext *ctx, char *destfile)
bool CfCreateFile(EvalContext *ctx, char *file, const Promise *pp, const Attributes *attr, PromiseResult *result)
static void TruncateFile(char *name)
void VerifyFileLeaf(EvalContext *ctx, char *path, const struct stat *sb, const Attributes *attr, const Promise *pp, PromiseResult *result)
void SetFileAutoDefineList(const Rlist *auto_define_list)
static PromiseResult LinkCopy(EvalContext *ctx, char *sourcefile, char *destfile, const struct stat *sb, const Attributes *attr, const Promise *pp, CompressedArray **inode_cache, AgentConnection *conn)
static Item * VSETXIDLIST
static PromiseResult VerifyName(EvalContext *ctx, char *path, const struct stat *sb, const Attributes *attr, const Promise *pp)
static PromiseResult SourceSearchAndCopy(EvalContext *ctx, const char *from, char *to, int maxrecurse, const Attributes *attr, const Promise *pp, dev_t rootdevice, CompressedArray **inode_cache, AgentConnection *conn)
void FileCopyConnectionClose(AgentConnection *conn)
static PromiseResult VerifyDelete(EvalContext *ctx, const char *path, const struct stat *sb, const Attributes *attr, const Promise *pp)
static void SaveSetxid(bool modified)
static bool PushDirState(EvalContext *ctx, char *name, const struct stat *sb)
bool VerifyOwner(EvalContext *ctx, const char *file, const Promise *pp, const Attributes *attr, const struct stat *sb, PromiseResult *result)
static bool TransformFile(EvalContext *ctx, char *file, const Attributes *attr, const Promise *pp, PromiseResult *result)
static void VerifyFileChanges(const char *file, const struct stat *sb, const Attributes *attr, const Promise *pp)
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)
StringSet * SINGLE_COPY_CACHE
static PromiseResult VerifySetUidGid(EvalContext *ctx, const char *file, const struct stat *dstat, mode_t newperm, const Promise *pp, const Attributes *attr)
static PromiseResult CfCopyFile(EvalContext *ctx, char *sourcefile, char *destfile, const struct stat *ssb, const Attributes *a, const Promise *pp, CompressedArray **inode_cache, AgentConnection *conn)
static PromiseResult VerifyFileAttributes(EvalContext *ctx, const char *file, const struct stat *dstat, const Attributes *attr, const Promise *pp)
static ProtocolVersion DecideProtocol(const EvalContext *ctx, ProtocolVersion copyfrom_setting)
static PromiseResult PurgeLocalFiles(EvalContext *ctx, Item *filelist, const char *localdir, const Attributes *attr, const Promise *pp, AgentConnection *conn)
static bool DeviceBoundary(const struct stat *sb, dev_t rootdevice)
static bool SkipDirLinks(EvalContext *ctx, char *path, const char *lastnode, DirectoryRecursion r)
static PromiseResult VerifyFileIntegrity(EvalContext *ctx, const char *file, const Attributes *attr, const Promise *pp)
void ClearExpandedAttributes(Attributes *a)
Definition: verify_files.c:254
static bool CheckLinkSecurity(const struct stat *sb, char *name)
static AgentConnection * FileCopyConnectionOpen(const EvalContext *ctx, const char *servername, const FileCopy *fc, bool background)
const Rlist * SINGLE_COPY_LIST
static int cf_readlink(EvalContext *ctx, char *sourcefile, char *linkbuf, int buffsize, const Attributes *attr, const Promise *pp, AgentConnection *conn, PromiseResult *result)
static bool IsInSetxidList(const char *file)