1 /*
2   Copyright 2021 Northern.tech AS
3 
4   This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5 
6   This program is free software; you can redistribute it and/or modify it
7   under the terms of the GNU General Public License as published by the
8   Free Software Foundation; version 3.
9 
10   This program is distributed in the hope that it will be useful,
11   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   GNU General Public License for more details.
14 
15   You should have received a copy of the GNU General Public License
16   along with this program; if not, write to the Free Software
17   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
18 
19   To the extent this program is licensed as part of the Enterprise
20   versions of CFEngine, the applicable Commercial Open Source License
21   (COSL) may apply to this file if you as a licensee so wish it. See
22   included file COSL.txt.
23 */
24 
25 #include <files_names.h>
26 
27 #include <policy.h>
28 #include <promises.h>
29 #include <cf3.defs.h>
30 #include <dir.h>
31 #include <item_lib.h>
32 #include <files_interfaces.h>
33 #include <string_lib.h>
34 #include <known_dirs.h>
35 #include <conversion.h>
36 
37 #include <cf-windows-functions.h>
38 
39 /*********************************************************************/
40 
IsNewerFileTree(const char * dir,time_t reftime)41 bool IsNewerFileTree(const char *dir, time_t reftime)
42 {
43     const struct dirent *dirp;
44     Dir *dirh;
45     struct stat sb;
46 
47 // Assumes that race conditions on the file path are unlikely and unimportant
48 
49     if (lstat(dir, &sb) == -1)
50     {
51         Log(LOG_LEVEL_ERR, "Unable to stat directory '%s' in IsNewerFileTree. (stat: %s)", dir, GetErrorStr());
52         // return true to provoke update
53         return true;
54     }
55 
56     if (S_ISDIR(sb.st_mode))
57     {
58         if (sb.st_mtime > reftime)
59         {
60             Log(LOG_LEVEL_VERBOSE, " >> Detected change in %s", dir);
61             return true;
62         }
63     }
64 
65     if ((dirh = DirOpen(dir)) == NULL)
66     {
67         Log(LOG_LEVEL_ERR, "Unable to open directory '%s' in IsNewerFileTree. (opendir: %s)", dir, GetErrorStr());
68         return false;
69     }
70     else
71     {
72         for (dirp = DirRead(dirh); dirp != NULL; dirp = DirRead(dirh))
73         {
74             if (!strcmp(dirp->d_name, ".") || !strcmp(dirp->d_name, ".."))
75             {
76                 continue;
77             }
78 
79             char path[CF_BUFSIZE];
80             size_t ret = (size_t) snprintf(path, sizeof(path), "%s%c%s",
81                                            dir, FILE_SEPARATOR, dirp->d_name);
82 
83             if (ret >= sizeof(path))
84             {
85                 Log(LOG_LEVEL_ERR,
86                     "Internal limit reached in IsNewerFileTree(),"
87                     " path too long: '%s' + '%s'",
88                     dir, dirp->d_name);
89                 DirClose(dirh);
90                 return false;
91             }
92 
93             if (lstat(path, &sb) == -1)
94             {
95                 Log(LOG_LEVEL_ERR, "Unable to stat directory '%s' in IsNewerFileTree. (lstat: %s)", path, GetErrorStr());
96                 DirClose(dirh);
97                 // return true to provoke update
98                 return true;
99             }
100 
101             if (S_ISDIR(sb.st_mode))
102             {
103                 if (sb.st_mtime > reftime)
104                 {
105                     Log(LOG_LEVEL_VERBOSE, " >> Detected change in %s", path);
106                     DirClose(dirh);
107                     return true;
108                 }
109                 else
110                 {
111                     if (IsNewerFileTree(path, reftime))
112                     {
113                         DirClose(dirh);
114                         return true;
115                     }
116                 }
117             }
118         }
119     }
120 
121     DirClose(dirh);
122     return false;
123 }
124 
125 /*********************************************************************/
126 
IsDir(const char * path)127 bool IsDir(const char *path)
128 /*
129 Checks if the object pointed to by path exists and is a directory.
130 Returns true if so, false otherwise.
131 */
132 {
133 #ifdef __MINGW32__
134     return NovaWin_IsDir(path);
135 #else
136     struct stat sb;
137 
138     if (stat(path, &sb) != -1)
139     {
140         if (S_ISDIR(sb.st_mode))
141         {
142             return true;
143         }
144     }
145 
146     return false;
147 #endif /* !__MINGW32__ */
148 }
149 
150 /*********************************************************************/
151 
JoinSuffix(char * path,size_t path_size,const char * leaf)152 char *JoinSuffix(char *path, size_t path_size, const char *leaf)
153 {
154     int len = strlen(leaf);
155 
156     if (Chop(path, path_size) == -1)
157     {
158         Log(LOG_LEVEL_ERR, "Chop was called on a string that seemed to have no terminator");
159     }
160     DeleteSlash(path);
161 
162     if (strlen(path) + len + 1 > path_size)
163     {
164         Log(LOG_LEVEL_ERR, "JoinSuffix: Internal limit reached. Tried to add %s to %s",
165               leaf, path);
166         return NULL;
167     }
168 
169     strlcat(path, leaf, path_size);
170     return path;
171 }
172 
173 /**
174  * Just like the JoinSuffix() above, but makes sure there's a FILE_SEPARATOR
175  * between @path and @leaf_path. The only exception is the case where @path is
176  * "" and @leaf_path doesn't start with a FILE_SEPARATOR. In that case:
177  *   JoinPaths("", PATH_MAX, "some_path") -> "some_path"
178  *
179  * This function is similar to Python's os.path.join() except that unlike the
180  * Python function this one actually joins @path and @leaf_path even if
181  * @leaf_path starts with a FILE_SEPARATOR.
182  */
JoinPaths(char * path,size_t path_size,const char * leaf_path)183 char *JoinPaths(char *path, size_t path_size, const char *leaf_path)
184 {
185     size_t len = strlen(leaf_path);
186     size_t path_len = strnlen(path, path_size);
187 
188     if (Chop(path, path_size - 1) == -1)
189     {
190         Log(LOG_LEVEL_ERR, "Chop was called on a string that seemed to have no terminator");
191         return NULL;
192     }
193 
194     if (path_len + len + 1 > path_size)
195     {
196         Log(LOG_LEVEL_ERR, "JoinPaths: Internal limit reached. Tried to add %s to %s",
197               leaf_path, path);
198         return NULL;
199     }
200 
201     /* make sure there's a FILE_SEPARATOR between path and leaf_path */
202     if ((path_len > 0 && !IsFileSep(path[path_len - 1])) && !IsFileSep(leaf_path[0]))
203     {
204         strlcat(path, FILE_SEPARATOR_STR, path_size);
205     }
206     else if ((path_len > 0 && IsFileSep(path[path_len - 1])) && IsFileSep(leaf_path[0]))
207     {
208         leaf_path += 1;
209     }
210     strlcat(path, leaf_path, path_size);
211     return path;
212 }
213 
IsAbsPath(const char * path)214 bool IsAbsPath(const char *path)
215 {
216     if (IsFileSep(*path))
217     {
218         return true;
219     }
220     else
221     {
222         return false;
223     }
224 }
225 
226 /**
227  * Append a slash, of the kind that the string already has, only if the string
228  * doesn't end in one.
229  */
AddSlash(char * str)230 void AddSlash(char *str)
231 {
232     char *sp, *sep = FILE_SEPARATOR_STR;
233     int f = false, b = false;
234 
235     if (str == NULL)
236     {
237         return;
238     }
239 
240     if (strlen(str) == 0)
241     {
242         strcpy(str, sep);
243         return;
244     }
245 
246 /* Try to see what convention is being used for filenames
247    in case this is a cross-system copy from Win/Unix */
248 
249     for (sp = str; *sp != '\0'; sp++)
250     {
251         switch (*sp)
252         {
253         case '/':
254             f = true;
255             break;
256         case '\\':
257             b = true;
258             break;
259         default:
260             break;
261         }
262     }
263 
264     if (f && (!b))
265     {
266         sep = "/";
267     }
268     else if (b && (!f))
269     {
270         sep = "\\";
271     }
272 
273     if (!IsFileSep(str[strlen(str) - 1]))
274     {
275         strcat(str, sep);
276     }
277 }
278 
279 /*********************************************************************/
280 
GetParentDirectoryCopy(const char * path)281 char *GetParentDirectoryCopy(const char *path)
282 /**
283  * WARNING: Remember to free return value.
284  **/
285 {
286     assert(path);
287     assert(strlen(path) > 0);
288 
289     char *path_copy = xstrdup(path);
290 
291     if(strcmp(path_copy, "/") == 0)
292     {
293         return path_copy;
294     }
295 
296     char *sp = (char *)LastFileSeparator(path_copy);
297 
298     if(!sp)
299     {
300         Log(LOG_LEVEL_ERR, "Path %s does not contain file separators (GetParentDirectory())", path_copy);
301         free(path_copy);
302         return NULL;
303     }
304 
305     if(sp == FirstFileSeparator(path_copy))  // don't chop off first path separator
306     {
307         *(sp + 1) = '\0';
308     }
309     else
310     {
311         *sp = '\0';
312     }
313 
314     return path_copy;
315 }
316 
317 /*********************************************************************/
318 
319 // Can remove several slashes if they are redundant.
DeleteSlash(char * str)320 void DeleteSlash(char *str)
321 {
322     int size = strlen(str);
323     if ((size == 0) || (str == NULL))
324     {
325         return;
326     }
327 
328     int root = RootDirLength(str);
329     while (IsFileSep(str[size - 1]) && size - 1 > root)
330     {
331         size--;
332     }
333     str[size] = '\0'; /* no-op if we didn't change size */
334 }
335 
336 /*********************************************************************/
337 
DeleteRedundantSlashes(char * str)338 void DeleteRedundantSlashes(char *str)
339 {
340     int move_from;
341     // Invariant: newpos <= oldpos
342     int oldpos = RootDirLength(str);
343     int newpos = oldpos;
344     while (str[oldpos] != '\0')
345     {
346         // Skip over subsequent separators.
347         while (IsFileSep(str[oldpos]))
348         {
349             oldpos++;
350         }
351         move_from = oldpos;
352 
353         // And then skip over the next path component.
354         while (str[oldpos] != '\0' && !IsFileSep(str[oldpos]))
355         {
356             oldpos++;
357         }
358 
359         // If next character is file separator, move past it, since we want to keep one.
360         if (IsFileSep(str[oldpos]))
361         {
362             oldpos++;
363         }
364 
365         int move_len = oldpos - move_from;
366         memmove(&str[newpos], &str[move_from], move_len);
367         newpos += move_len;
368     }
369 
370     str[newpos] = '\0';
371 }
372 
373 /*********************************************************************/
374 
FirstFileSeparator(const char * str)375 const char *FirstFileSeparator(const char *str)
376 {
377     assert(str);
378     assert(strlen(str) > 0);
379 
380     if(strncmp(str, "\\\\", 2) == 0)  // windows share
381     {
382         return str + 1;
383     }
384 
385     for(const char *pos = str; *pos != '\0'; pos++)
386     {
387         if(IsFileSep(*pos))
388         {
389             return pos;
390         }
391     }
392 
393     return NULL;
394 }
395 
396 /*********************************************************************/
397 
LastFileSeparator(const char * str)398 const char *LastFileSeparator(const char *str)
399   /* Return pointer to last file separator in string, or NULL if
400      string does not contains any file separtors */
401 {
402     const char *sp;
403 
404 /* Walk through string backwards */
405 
406     sp = str + strlen(str) - 1;
407 
408     while (sp >= str)
409     {
410         if (IsFileSep(*sp))
411         {
412             return sp;
413         }
414         sp--;
415     }
416 
417     return NULL;
418 }
419 
420 /*********************************************************************/
421 
ChopLastNode(char * str)422 bool ChopLastNode(char *str)
423   /* Chop off trailing node name (possible blank) starting from
424      last character and removing up to the first / encountered
425      e.g. /a/b/c -> /a/b
426      /a/b/ -> /a/b
427      Will also collapse redundant/repeating path separators.
428   */
429 {
430     char *sp;
431     bool ret;
432 
433     DeleteRedundantSlashes(str);
434 
435 /* Here cast is necessary and harmless, str is modifiable */
436     if ((sp = (char *) LastFileSeparator(str)) == NULL)
437     {
438         int pos = RootDirLength(str);
439         if (str[pos] == '\0')
440         {
441             ret = false;
442         }
443         else
444         {
445             str[pos] = '.';
446             str[pos + 1] = '\0';
447             ret = true;
448         }
449     }
450     else
451     {
452         // Don't chop the root slash in an absolute path.
453         if (IsAbsoluteFileName(str) && FirstFileSeparator(str) == sp)
454         {
455             *(++sp) = '\0';
456         }
457         else
458         {
459             *sp = '\0';
460         }
461         ret = true;
462     }
463 
464     return ret;
465 }
466 
467 /*********************************************************************/
468 
TransformNameInPlace(char * s,char from,char to)469 void TransformNameInPlace(char *s, char from, char to)
470 {
471     for (; *s != '\0'; s++)
472     {
473         if (*s == from)
474         {
475             *s = to;
476         }
477     }
478 }
479 
480 /*********************************************************************/
481 
482 /* TODO remove, kill, burn this function! Replace with BufferCanonify or CanonifyNameInPlace */
CanonifyName(const char * str)483 char *CanonifyName(const char *str)
484 {
485     static char buffer[CF_BUFSIZE]; /* GLOBAL_R, no initialization needed */
486 
487     strlcpy(buffer, str, CF_BUFSIZE);
488     CanonifyNameInPlace(buffer);
489     return buffer;
490 }
491 
492 /*********************************************************************/
493 
CanonifyChar(const char * str,char ch)494 char *CanonifyChar(const char *str, char ch)
495 {
496     static char buffer[CF_BUFSIZE]; /* GLOBAL_R, no initialization needed */
497     char *sp;
498 
499     strlcpy(buffer, str, CF_BUFSIZE);
500 
501     for (sp = buffer; *sp != '\0'; sp++)
502     {
503         if (*sp == ch)
504         {
505             *sp = '_';
506         }
507     }
508 
509     return buffer;
510 }
511 
512 /*********************************************************************/
513 
CompareCSVName(const char * s1,const char * s2)514 int CompareCSVName(const char *s1, const char *s2)
515 {
516     const char *sp1, *sp2;
517     char ch1, ch2;
518 
519     for (sp1 = s1, sp2 = s2; (*sp1 != '\0') || (*sp2 != '\0'); sp1++, sp2++)
520     {
521         ch1 = (*sp1 == ',') ? '_' : *sp1;
522         ch2 = (*sp2 == ',') ? '_' : *sp2;
523 
524         if (ch1 > ch2)
525         {
526             return 1;
527         }
528         else if (ch1 < ch2)
529         {
530             return -1;
531         }
532     }
533 
534     return 0;
535 }
536 
537 /*********************************************************************/
538 
ReadLastNode(const char * str)539 const char *ReadLastNode(const char *str)
540 /* Return the last node of a pathname string  */
541 {
542     const char *sp;
543 
544     if ((sp = LastFileSeparator(str)) == NULL)
545     {
546         return str;
547     }
548     else
549     {
550         return sp + 1;
551     }
552 }
553 
554 /*********************************************************************/
555 
CompressPath(char * dest,size_t dest_size,const char * src)556 bool CompressPath(char *dest, size_t dest_size, const char *src)
557 {
558     char node[CF_BUFSIZE];
559     int nodelen;
560     int rootlen;
561 
562     memset(dest, 0, dest_size);
563 
564     rootlen = RootDirLength(src);
565 
566     if((size_t) rootlen >= dest_size)
567     {
568         Log(LOG_LEVEL_ERR,
569             "Internal limit reached in CompressPath(),"
570             "src path too long (%d bytes): '%s'",
571                 rootlen, src);
572             return false;
573     }
574 
575     memcpy(dest, src, rootlen);
576 
577     for (const char *sp = src + rootlen; *sp != '\0'; sp++)
578     {
579         if (IsFileSep(*sp))
580         {
581             continue;
582         }
583 
584         for (nodelen = 0; (sp[nodelen] != '\0') && (!IsFileSep(sp[nodelen])); nodelen++)
585         {
586             if (nodelen > CF_MAXLINKSIZE)
587             {
588                 Log(LOG_LEVEL_ERR, "Link in path suspiciously large");
589                 return false;
590             }
591         }
592 
593         strncpy(node, sp, nodelen);
594         node[nodelen] = '\0';
595 
596         sp += nodelen - 1;
597 
598         if (strcmp(node, ".") == 0)
599         {
600             continue;
601         }
602 
603         if (strcmp(node, "..") == 0)
604         {
605             if (!ChopLastNode(dest))
606             {
607                 Log(LOG_LEVEL_DEBUG, "used .. beyond top of filesystem!");
608                 return false;
609             }
610 
611             continue;
612         }
613 
614         AddSlash(dest);
615 
616         size_t ret = strlcat(dest, node, dest_size);
617 
618         if (ret >= CF_BUFSIZE)
619         {
620             Log(LOG_LEVEL_ERR,
621                 "Internal limit reached in CompressPath(),"
622                 " path too long: '%s' + '%s'",
623                 dest, node);
624             return false;
625         }
626     }
627 
628     return true;
629 }
630 
631 /*********************************************************************/
632 
633 /**
634  * Get absolute path of @path. If @path is already an absolute path this
635  * function just returns a compressed (see CompressPath()) copy of it. Otherwise
636  * this function prepends the curent working directory before @path and returns
637  * the result compressed with CompressPath(). If anything goes wrong, an empty
638  * string is returned.
639  *
640  * WARNING: Remember to free return value.
641  **/
GetAbsolutePath(const char * path)642 char *GetAbsolutePath(const char *path)
643 {
644     if (NULL_OR_EMPTY(path))
645     {
646         return NULL;
647     }
648     char abs_path[PATH_MAX] = { 0 };
649     if (IsAbsoluteFileName(path))
650     {
651         CompressPath(abs_path, PATH_MAX, path);
652         return xstrdup(abs_path);
653     }
654     else
655     {
656         /* the full_path can potentially be long (with many '../' parts)*/
657         char full_path[2 * PATH_MAX] = { 0 };
658         if (getcwd(full_path, PATH_MAX) == NULL)
659         {
660             Log(LOG_LEVEL_WARNING,
661                 "Could not determine current directory (getcwd: %s)",
662                 GetErrorStr());
663         }
664         JoinPaths(full_path, 2 * PATH_MAX, path);
665         CompressPath(abs_path, PATH_MAX, full_path);
666         return xstrdup(abs_path);
667     }
668 }
669 
GetRealPath(const char * const path)670 char *GetRealPath(const char *const path)
671 {
672     if (NULL_OR_EMPTY(path))
673     {
674         return NULL;
675     }
676     char *const abs_path = GetAbsolutePath(path);
677     if (NULL_OR_EMPTY(abs_path))
678     {
679         free(abs_path);
680         return NULL;
681     }
682 
683 #ifdef __linux__ // POSIX 2008 - could add newer versions of BSD / solaris
684     char *real_path = realpath(abs_path, NULL);
685     if (NOT_NULL_AND_EMPTY(real_path))
686     {
687         free(real_path);
688         real_path = NULL;
689     }
690 #else // Pre POSIX 2008 - realpath arg cannot be NULL
691     char *const path_buf = xcalloc(1, PATH_MAX);
692     char *real_path = realpath(abs_path, path_buf);
693     if (NULL_OR_EMPTY(real_path))
694     {
695         free(path_buf);
696         real_path = NULL;
697     }
698 #endif
699 
700     free(abs_path);
701     return real_path;
702 }
703 
704 /*********************************************************************/
705 
FilePathGetType(const char * file_path)706 FilePathType FilePathGetType(const char *file_path)
707 {
708     if (IsAbsoluteFileName(file_path))
709     {
710         return FILE_PATH_TYPE_ABSOLUTE;
711     }
712     else if (*file_path == '.')
713     {
714         return FILE_PATH_TYPE_RELATIVE;
715     }
716     else
717     {
718         return FILE_PATH_TYPE_NON_ANCHORED;
719     }
720 }
721 
IsFileOutsideDefaultRepository(const char * f)722 bool IsFileOutsideDefaultRepository(const char *f)
723 {
724     return !StringStartsWith(f, GetInputDir());
725 }
726 
727 /*******************************************************************/
728 
UnixRootDirLength(const char * f)729 static int UnixRootDirLength(const char *f)
730 {
731     if (IsFileSep(*f))
732     {
733         return 1;
734     }
735 
736     return 0;
737 }
738 
739 #ifdef _WIN32
NTRootDirLength(const char * f)740 static int NTRootDirLength(const char *f)
741 {
742     int len;
743 
744     if (f[0] == '\\' && f[1] == '\\')
745     {
746         /* UNC style path */
747 
748         /* Skip over host name */
749         for (len = 2; f[len] != '\\'; len++)
750         {
751             if (f[len] == '\0')
752             {
753                 return len;
754             }
755         }
756 
757         /* Skip over share name */
758 
759         for (len++; f[len] != '\\'; len++)
760         {
761             if (f[len] == '\0')
762             {
763                 return len;
764             }
765         }
766 
767         /* Skip over file separator */
768         len++;
769 
770         return len;
771     }
772 
773     if (isalpha(f[0]) && f[1] == ':')
774     {
775         if (IsFileSep(f[2]))
776         {
777             return 3;
778         }
779 
780         return 2;
781     }
782 
783     return UnixRootDirLength(f);
784 }
785 #endif
786 
RootDirLength(const char * f)787 int RootDirLength(const char *f)
788   /* Return length of Initial directory in path - */
789 {
790 #ifdef _WIN32
791     return NTRootDirLength(f);
792 #else
793     return UnixRootDirLength(f);
794 #endif
795 }
796 
797 /* Buffer should be at least CF_MAXVARSIZE large */
GetSoftwareCacheFilename(char * buffer)798 const char *GetSoftwareCacheFilename(char *buffer)
799 {
800     snprintf(buffer, CF_MAXVARSIZE, "%s/%s", GetStateDir(), SOFTWARE_PACKAGES_CACHE);
801     MapName(buffer);
802     return buffer;
803 }
804 
805 /* Buffer should be at least CF_MAXVARSIZE large */
GetSoftwarePatchesFilename(char * buffer)806 const char *GetSoftwarePatchesFilename(char *buffer)
807 {
808     snprintf(buffer, CF_MAXVARSIZE, "%s/%s", GetStateDir(), SOFTWARE_PATCHES_CACHE);
809     MapName(buffer);
810     return buffer;
811 }
812 
RealPackageManager(const char * manager)813 const char *RealPackageManager(const char *manager)
814 {
815     assert(manager);
816 
817     const char *pos = strchr(manager, ' ');
818     if (strncmp(manager, "env ", 4) != 0
819         && (!pos || pos - manager < 4 || strncmp(pos - 4, "/env", 4) != 0))
820     {
821         return CommandArg0(manager);
822     }
823 
824     // Look for variable assignments.
825     const char *last_pos;
826     bool eq_sign_found = false;
827     while (true)
828     {
829         if (eq_sign_found)
830         {
831             last_pos = pos + 1;
832         }
833         else
834         {
835             last_pos = pos + strspn(pos, " "); // Skip over consecutive spaces.
836         }
837         pos = strpbrk(last_pos, "= ");
838         if (!pos)
839         {
840             break;
841         }
842         if (*pos == '=')
843         {
844             eq_sign_found = true;
845         }
846         else if (eq_sign_found)
847         {
848             eq_sign_found = false;
849         }
850         else
851         {
852             return CommandArg0(last_pos);
853         }
854     }
855 
856     // Reached the end? Weird. Must be env command with no real command.
857     return CommandArg0(manager);
858 }
859