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