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