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