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