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 <platform.h>
26 #include <file_lib.h>
27 #include <misc_lib.h>
28 #include <dir.h>
29 #include <logging.h>
30 
31 #include <alloc.h>
32 #include <definitions.h>                               /* CF_PERMS_DEFAULT */
33 #include <libgen.h>
34 #include <logging.h>
35 #include <string_lib.h>                                         /* memcchr */
36 #include <path.h>
37 
38 #ifndef __MINGW32__
39 #include <glob.h>
40 #else
41 #include <windows.h>            /* LockFileEx and friends */
42 #endif
43 
44 #define SYMLINK_MAX_DEPTH 32
45 
FileCanOpen(const char * path,const char * modes)46 bool FileCanOpen(const char *path, const char *modes)
47 {
48     FILE *test = NULL;
49 
50     if ((test = fopen(path, modes)) != NULL)
51     {
52         fclose(test);
53         return true;
54     }
55     else
56     {
57         return false;
58     }
59 }
60 
61 #define READ_BUFSIZE 4096
62 
FileRead(const char * filename,size_t max_size,bool * truncated)63 Writer *FileRead(const char *filename, size_t max_size, bool *truncated)
64 {
65     int fd = safe_open(filename, O_RDONLY);
66     if (fd == -1)
67     {
68         return NULL;
69     }
70 
71     Writer *w = FileReadFromFd(fd, max_size, truncated);
72     close(fd);
73     return w;
74 }
75 
ReadFileStreamToBuffer(FILE * file,size_t max_bytes,char * buf)76 ssize_t ReadFileStreamToBuffer(FILE *file, size_t max_bytes, char *buf)
77 {
78     size_t bytes_read = 0;
79     size_t n = 0;
80     while (bytes_read < max_bytes)
81     {
82         n = fread(buf + bytes_read, 1, max_bytes - bytes_read, file);
83         if (ferror(file) && !feof(file))
84         {
85             return FILE_ERROR_READ;
86         }
87         else if (n == 0)
88         {
89             break;
90         }
91         bytes_read += n;
92     }
93     if (ferror(file))
94     {
95         return FILE_ERROR_READ;
96     }
97     return bytes_read;
98 }
99 
File_Copy(const char * src,const char * dst)100 bool File_Copy(const char *src, const char *dst)
101 {
102     assert(src != NULL);
103     assert(dst != NULL);
104 
105     Log(LOG_LEVEL_INFO, "Copying: '%s' -> '%s'", src, dst);
106 
107     FILE *in = safe_fopen(src, "r");
108     if (in == NULL)
109     {
110         Log(LOG_LEVEL_ERR, "Could not open '%s' (%s)", src, strerror(errno));
111         return false;
112     }
113 
114     FILE *out = safe_fopen_create_perms(dst, "w", CF_PERMS_DEFAULT);
115     if (out == NULL)
116     {
117         Log(LOG_LEVEL_ERR, "Could not open '%s' (%s)", dst, strerror(errno));
118         fclose(in);
119         return false;
120     }
121 
122     size_t bytes_in = 0;
123     size_t bytes_out = 0;
124     bool ret = true;
125     do
126     {
127 #define BUFSIZE 1024
128         char buf[BUFSIZE] = {0};
129 
130         bytes_in = fread(buf, sizeof(char), sizeof(buf), in);
131         bytes_out = fwrite(buf, sizeof(char), bytes_in, out);
132         while (bytes_out < bytes_in && !ferror(out))
133         {
134             bytes_out += fwrite(
135                 buf + bytes_out, sizeof(char), bytes_in - bytes_out, out);
136         }
137     } while (!feof(in) && !ferror(in) && !ferror(out) &&
138              bytes_in == bytes_out);
139 
140     if (ferror(in))
141     {
142         Log(LOG_LEVEL_ERR, "Error encountered while reading '%s'", src);
143         ret = false;
144     }
145     else if (ferror(out))
146     {
147         Log(LOG_LEVEL_ERR, "Error encountered while writing '%s'", dst);
148         ret = false;
149     }
150     else if (bytes_in != bytes_out)
151     {
152         Log(LOG_LEVEL_ERR, "Did not copy the whole file");
153         ret = false;
154     }
155 
156     const int i = fclose(in);
157     if (i != 0)
158     {
159         Log(LOG_LEVEL_ERR,
160             "Error encountered while closing '%s' (%s)",
161             src,
162             strerror(errno));
163         ret = false;
164     }
165     const int o = fclose(out);
166     if (o != 0)
167     {
168         Log(LOG_LEVEL_ERR,
169             "Error encountered while closing '%s' (%s)",
170             dst,
171             strerror(errno));
172         ret = false;
173     }
174     return ret;
175 }
176 
File_CopyToDir(const char * src,const char * dst_dir)177 bool File_CopyToDir(const char *src, const char *dst_dir)
178 {
179     assert(src != NULL);
180     assert(dst_dir != NULL);
181     assert(StringEndsWith(dst_dir, FILE_SEPARATOR_STR));
182 
183     const char *filename = Path_Basename(src);
184     if (filename == NULL)
185     {
186         Log(LOG_LEVEL_ERR, "Cannot find filename in '%s'", src);
187         return false;
188     }
189 
190     char dst[PATH_MAX] = {0};
191     const int s = snprintf(dst, PATH_MAX, "%s%s", dst_dir, filename);
192     if (s >= PATH_MAX)
193     {
194         Log(LOG_LEVEL_ERR, "Copy destination path too long: '%s...'", dst);
195         return false;
196     }
197 
198     if (!File_Copy(src, dst))
199     {
200         Log(LOG_LEVEL_ERR, "Copying '%s' failed", filename);
201         return false;
202     }
203 
204     return true;
205 }
206 
FileReadFromFd(int fd,size_t max_size,bool * truncated)207 Writer *FileReadFromFd(int fd, size_t max_size, bool *truncated)
208 {
209     if (truncated)
210     {
211         *truncated = false;
212     }
213 
214     Writer *w = StringWriter();
215     for (;;)
216     {
217         char buf[READ_BUFSIZE];
218         /* Reading more data than needed is deliberate. It is a truncation detection. */
219         ssize_t read_ = read(fd, buf, READ_BUFSIZE);
220 
221         if (read_ == 0)
222         {
223             /* Done. */
224             return w;
225         }
226         else if (read_ < 0)
227         {
228             if (errno != EINTR)
229             {
230                 /* Something went wrong. */
231                 WriterClose(w);
232                 return NULL;
233             }
234             /* Else: interrupted - try again. */
235         }
236         else if (read_ + StringWriterLength(w) > max_size)
237         {
238             WriterWriteLen(w, buf, max_size - StringWriterLength(w));
239             /* Reached limit - stop. */
240             if (truncated)
241             {
242                 *truncated = true;
243             }
244             return w;
245         }
246         else /* Filled buffer; copy and ask for more. */
247         {
248             WriterWriteLen(w, buf, read_);
249         }
250     }
251 }
252 
FullWrite(int desc,const char * ptr,size_t len)253 ssize_t FullWrite(int desc, const char *ptr, size_t len)
254 {
255     ssize_t total_written = 0;
256 
257     while (len > 0)
258     {
259         int written = write(desc, ptr, len);
260 
261         if (written < 0)
262         {
263             if (errno == EINTR)
264             {
265                 continue;
266             }
267 
268             return written;
269         }
270 
271         total_written += written;
272         ptr += written;
273         len -= written;
274     }
275 
276     return total_written;
277 }
278 
FullRead(int fd,char * ptr,size_t len)279 ssize_t FullRead(int fd, char *ptr, size_t len)
280 {
281     ssize_t total_read = 0;
282 
283     while (len > 0)
284     {
285         ssize_t bytes_read = read(fd, ptr, len);
286 
287         if (bytes_read < 0)
288         {
289             if (errno == EINTR)
290             {
291                 continue;
292             }
293 
294             return -1;
295         }
296 
297         if (bytes_read == 0)
298         {
299             return total_read;
300         }
301 
302         total_read += bytes_read;
303         ptr += bytes_read;
304         len -= bytes_read;
305     }
306 
307     return total_read;
308 }
309 
310 /**
311  * @note difference with files_names.h:IsDir() is that this doesn't
312  *       follow symlinks, so a symlink is never a directory...
313  */
IsDirReal(const char * path)314 bool IsDirReal(const char *path)
315 {
316     struct stat s;
317 
318     if (lstat(path, &s) == -1)
319     {
320         return false; // Error
321     }
322 
323     return (S_ISDIR(s.st_mode) != 0);
324 }
325 
326 #ifndef __MINGW32__
FileNewLineMode(ARG_UNUSED const char * file)327 NewLineMode FileNewLineMode(ARG_UNUSED const char *file)
328 {
329     return NewLineMode_Unix;
330 }
331 #endif // !__MINGW32__
332 
IsAbsoluteFileName(const char * f)333 bool IsAbsoluteFileName(const char *f)
334 {
335     int off = 0;
336 
337 // Check for quoted strings
338 
339     for (off = 0; f[off] == '\"'; off++)
340     {
341     }
342 
343 #ifdef _WIN32
344     if (IsFileSep(f[off]) && IsFileSep(f[off + 1]))
345     {
346         return true;
347     }
348 
349     if (isalpha(f[off]) && f[off + 1] == ':' && IsFileSep(f[off + 2]))
350     {
351         return true;
352     }
353 #endif
354     if (IsFileSep(f[off]))
355     {
356         return true;
357     }
358 
359     return false;
360 }
361 
362 /* We assume that s is at least MAX_FILENAME large.
363  * MapName() is thread-safe, but the argument is modified. */
364 
365 #ifdef _WIN32
366 # if defined(__MINGW32__)
367 
MapNameCopy(const char * s)368 char *MapNameCopy(const char *s)
369 {
370     char *str = xstrdup(s);
371 
372     char *c = str;
373     while ((c = strchr(c, '/')))
374     {
375         *c = '\\';
376     }
377 
378     return str;
379 }
380 
MapName(char * s)381 char *MapName(char *s)
382 {
383     char *c = s;
384 
385     while ((c = strchr(c, '/')))
386     {
387         *c = '\\';
388     }
389     return s;
390 }
391 
392 # elif defined(__CYGWIN__)
393 
MapNameCopy(const char * s)394 char *MapNameCopy(const char *s)
395 {
396     Writer *w = StringWriter();
397 
398     /* c:\a\b -> /cygdrive/c\a\b */
399     if (s[0] && isalpha(s[0]) && s[1] == ':')
400     {
401         WriterWriteF(w, "/cygdrive/%c", s[0]);
402         s += 2;
403     }
404 
405     for (; *s; s++)
406     {
407         /* a//b//c -> a/b/c */
408         /* a\\b\\c -> a\b\c */
409         if (IsFileSep(*s) && IsFileSep(*(s + 1)))
410         {
411             continue;
412         }
413 
414         /* a\b\c -> a/b/c */
415         WriterWriteChar(w, *s == '\\' ? '/' : *s);
416     }
417 
418     return StringWriterClose(w);
419 }
420 
MapName(char * s)421 char *MapName(char *s)
422 {
423     char *ret = MapNameCopy(s);
424 
425     if (strlcpy(s, ret, MAX_FILENAME) >= MAX_FILENAME)
426     {
427         FatalError(ctx, "Expanded path (%s) is longer than MAX_FILENAME ("
428                    TO_STRING(MAX_FILENAME) ") characters",
429                    ret);
430     }
431     free(ret);
432 
433     return s;
434 }
435 
436 # else/* !__MINGW32__ && !__CYGWIN__ */
437 #  error Unknown NT-based compilation environment
438 # endif/* __MINGW32__ || __CYGWIN__ */
439 #else /* !_WIN32 */
440 
MapName(char * s)441 char *MapName(char *s)
442 {
443     return s;
444 }
445 
MapNameCopy(const char * s)446 char *MapNameCopy(const char *s)
447 {
448     return xstrdup(s);
449 }
450 
451 #endif /* !_WIN32 */
452 
MapNameForward(char * s)453 char *MapNameForward(char *s)
454 /* Like MapName(), but maps all slashes to forward */
455 {
456     while ((s = strchr(s, '\\')))
457     {
458         *s = '/';
459     }
460     return s;
461 }
462 
463 
464 #ifdef TEST_SYMLINK_ATOMICITY
465 void switch_symlink_hook();
466 #define TEST_SYMLINK_SWITCH_POINT switch_symlink_hook();
467 #else
468 #define TEST_SYMLINK_SWITCH_POINT
469 #endif
470 
ListDir(const char * dir,const char * extension)471 Seq *ListDir(const char *dir, const char *extension)
472 {
473     Dir *dirh = DirOpen(dir);
474     if (dirh == NULL)
475     {
476         return NULL;
477     }
478 
479     Seq *contents = SeqNew(10, free);
480 
481     const struct dirent *dirp;
482 
483     while ((dirp = DirRead(dirh)) != NULL)
484     {
485         const char *name = dirp->d_name;
486         if (extension == NULL || StringEndsWithCase(name, extension, true))
487         {
488             SeqAppend(contents, Path_JoinAlloc(dir, name));
489         }
490     }
491     DirClose(dirh);
492 
493     return contents;
494 }
495 
SetUmask(mode_t new_mask)496 mode_t SetUmask(mode_t new_mask)
497 {
498     const mode_t old_mask = umask(new_mask);
499     Log(LOG_LEVEL_DEBUG, "Set umask to %o, was %o", new_mask, old_mask);
500     return old_mask;
501 }
RestoreUmask(mode_t old_mask)502 void RestoreUmask(mode_t old_mask)
503 {
504     umask(old_mask);
505     Log(LOG_LEVEL_DEBUG, "Restored umask to %o", old_mask);
506 }
507 
508 /**
509  * Opens a file safely, with default (strict) permissions on creation.
510  * See safe_open_create_perms for more documentation.
511  *
512  * @param pathname The path to open.
513  * @param flags Same flags as for system open().
514  * @return Same errors as open().
515  */
safe_open(const char * pathname,int flags)516 int safe_open(const char *pathname, int flags)
517 {
518     return safe_open_create_perms(pathname, flags, CF_PERMS_DEFAULT);
519 }
520 
521 /**
522  * Opens a file safely. It will follow symlinks, but only if the symlink is
523  * trusted, that is, if the owner of the symlink and the owner of the target are
524  * the same, or if the owner of the symlink is either root or the user running
525  * the current process. All components are checked, even symlinks encountered in
526  * earlier parts of the path name.
527  *
528  * It should always be used when opening a file or directory that is not
529  * guaranteed to be owned by root.
530  *
531  * safe_open and safe_fopen both default to secure (0600) file creation perms.
532  * The _create_perms variants allow you to explicitly set different permissions.
533  *
534  * @param pathname The path to open
535  * @param flags Same flags as for system open()
536  * @param create_perms Permissions for file, only relevant on file creation
537  * @return Same errors as open()
538  * @see safe_fopen_create_perms()
539  * @see safe_open()
540  */
safe_open_create_perms(const char * const pathname,int flags,const mode_t create_perms)541 int safe_open_create_perms(
542     const char *const pathname, int flags, const mode_t create_perms)
543 {
544     if (flags & O_TRUNC)
545     {
546         /* Undefined behaviour otherwise, according to the standard. */
547         assert((flags & O_RDWR) || (flags & O_WRONLY));
548     }
549 
550     if (!pathname)
551     {
552         errno = EINVAL;
553         return -1;
554     }
555 
556     if (*pathname == '\0')
557     {
558         errno = ENOENT;
559         return -1;
560     }
561 
562 #ifdef __MINGW32__
563     // Windows gets off easy. No symlinks there.
564     return open(pathname, flags, create_perms);
565 #elif defined(__ANDROID__)
566     // if effective user is not root then don't try to open
567     // all paths from '/' up, might not have permissions.
568     uid_t p_euid = geteuid();
569     if (p_euid != 0)
570     {
571         return open(pathname, flags, create_perms);
572     }
573 #else // !__MINGW32__ and !__ANDROID__
574     const size_t path_bufsize = strlen(pathname) + 1;
575     char path[path_bufsize];
576     const size_t res_len = StringCopy(pathname, path, path_bufsize);
577     UNUSED(res_len);
578     assert(res_len == strlen(pathname));
579     int currentfd;
580     const char *first_dir;
581     bool trunc = false;
582     const int orig_flags = flags;
583     char *next_component = path;
584     bool p_uid;
585 
586     if (*next_component == '/')
587     {
588         first_dir = "/";
589         // Eliminate double slashes.
590         while (*(++next_component) == '/') { /*noop*/ }
591         if (!*next_component)
592         {
593             next_component = NULL;
594         }
595     }
596     else
597     {
598         first_dir = ".";
599     }
600     currentfd = openat(AT_FDCWD, first_dir, O_RDONLY);
601     if (currentfd < 0)
602     {
603         return -1;
604     }
605 
606     // current process user id
607     p_uid = geteuid();
608 
609     size_t final_size = (size_t) -1;
610     while (next_component)
611     {
612         char *component = next_component;
613         next_component = strchr(component + 1, '/');
614         // Used to restore the slashes in the final path component.
615         char *restore_slash = NULL;
616         if (next_component)
617         {
618             restore_slash = next_component;
619             *next_component = '\0';
620             // Eliminate double slashes.
621             while (*(++next_component) == '/') { /*noop*/ }
622             if (!*next_component)
623             {
624                 next_component = NULL;
625             }
626             else
627             {
628                 restore_slash = NULL;
629             }
630         }
631 
632         // In cases of a race condition when creating a file, our attempt to open it may fail
633         // (see O_EXCL and O_CREAT flags below). However, this can happen even under normal
634         // circumstances, if we are unlucky; it does not mean that someone is up to something bad.
635         // So retry it a few times before giving up.
636         int attempts = 3;
637         trunc = false;
638         while (true)
639         {
640 
641             if ((  (orig_flags & O_RDWR) || (orig_flags & O_WRONLY))
642                 && (orig_flags & O_TRUNC))
643             {
644                 trunc = true;
645                 /* We need to check after we have opened the file whether we
646                  * opened the right one. But if we truncate it, the damage is
647                  * already done, we have destroyed the contents, and that file
648                  * might have been a symlink to /etc/shadow! So save that flag
649                  * and apply the truncation afterwards instead. */
650                 flags &= ~O_TRUNC;
651             }
652 
653             if (restore_slash)
654             {
655                 *restore_slash = '\0';
656             }
657 
658             struct stat stat_before, stat_after;
659             int stat_before_result = fstatat(currentfd, component, &stat_before, AT_SYMLINK_NOFOLLOW);
660             if (stat_before_result < 0
661                 && (errno != ENOENT
662                     || next_component // Meaning "not a leaf".
663                     || !(flags & O_CREAT)))
664             {
665                 close(currentfd);
666                 return -1;
667             }
668 
669             /*
670              * This testing entry point is about the following real-world
671              * scenario: There can be an attacker that at this point
672              * overwrites the existing file or writes a file, invalidating
673              * basically the previous fstatat().
674              *
675              * - We make sure that can't happen if the file did not exist, by
676              *   creating with O_EXCL.
677              * - We make sure that can't happen if the file existed, by
678              *   comparing with fstat() result after the open().
679              *
680              */
681             TEST_SYMLINK_SWITCH_POINT
682 
683             if (!next_component)                          /* last component */
684             {
685                 if (stat_before_result < 0)
686                 {
687                     assert(flags & O_CREAT);
688 
689                     // Doesn't exist. Make sure *we* create it.
690                     flags |= O_EXCL;
691 
692                     /* No need to ftruncate() the file at the end. */
693                     trunc = false;
694                 }
695                 else
696                 {
697                     if ((flags & O_CREAT) && (flags & O_EXCL))
698                     {
699                         close(currentfd);
700                         errno = EEXIST;
701                         return -1;
702                     }
703 
704                     // Already exists. Make sure we *don't* create it.
705                     flags &= ~O_CREAT;
706                 }
707                 if (restore_slash)
708                 {
709                     *restore_slash = '/';
710                 }
711                 int filefd = openat(currentfd, component, flags, create_perms);
712                 if (filefd < 0)
713                 {
714                     if ((stat_before_result < 0  && !(orig_flags & O_EXCL)  && errno == EEXIST) ||
715                         (stat_before_result >= 0 &&  (orig_flags & O_CREAT) && errno == ENOENT))
716                     {
717                         if (--attempts >= 0)
718                         {
719                             // Might be our fault. Try again.
720                             flags = orig_flags;
721                             continue;
722                         }
723                         else
724                         {
725                             // Too many attempts. Give up.
726                             // Most likely a link that is in the way of file creation, but can also
727                             // be a file that is constantly created and deleted (race condition).
728                             // It is not possible to separate between the two, so return EACCES to
729                             // signal that we denied access.
730                             errno = EACCES;
731                         }
732                     }
733                     close(currentfd);
734                     return -1;
735                 }
736                 close(currentfd);
737                 currentfd = filefd;
738             }
739             else
740             {
741                 int new_currentfd = openat(currentfd, component, O_RDONLY);
742                 close(currentfd);
743                 if (new_currentfd < 0)
744                 {
745                     return -1;
746                 }
747                 currentfd = new_currentfd;
748             }
749 
750             /* If file did exist, we fstat() again and compare with previous. */
751 
752             if (stat_before_result == 0)
753             {
754                 if (fstat(currentfd, &stat_after) < 0)
755                 {
756                     close(currentfd);
757                     return -1;
758                 }
759                 // Some attacks may use symlinks to get higher privileges
760                 // The safe cases are:
761                 // * symlinks owned by root
762                 // * symlinks owned by the user running the process
763                 // * symlinks that have the same owner and group as the destination
764                 if (stat_before.st_uid != 0 &&
765                     stat_before.st_uid != p_uid &&
766                     (stat_before.st_uid != stat_after.st_uid || stat_before.st_gid != stat_after.st_gid))
767                 {
768                     close(currentfd);
769                     Log(LOG_LEVEL_ERR, "Cannot follow symlink '%s'; it is not "
770                         "owned by root or the user running this process, and "
771                         "the target owner and/or group differs from that of "
772                         "the symlink itself.", pathname);
773                     // Return ENOLINK to signal that the link cannot be followed
774                     // ('Link has been severed').
775                     errno = ENOLINK;
776                     return -1;
777                 }
778 
779                 final_size = (size_t) stat_after.st_size;
780             }
781 
782             // If we got here, we've been successful, so don't try again.
783             break;
784         }
785     }
786 
787     /* Truncate if O_CREAT and the file preexisted. */
788     if (trunc)
789     {
790         /* Do not truncate if the size is already zero, some
791          * filesystems don't support ftruncate() with offset>=size. */
792         assert(final_size != (size_t) -1);
793 
794         if (final_size != 0)
795         {
796             int tr_ret = ftruncate(currentfd, 0);
797             if (tr_ret < 0)
798             {
799                 Log(LOG_LEVEL_ERR,
800                     "safe_open: unexpected failure (ftruncate: %s)",
801                     GetErrorStr());
802                 close(currentfd);
803                 return -1;
804             }
805         }
806     }
807 
808     return currentfd;
809 #endif // !__MINGW32__
810 }
811 
safe_fopen(const char * const path,const char * const mode)812 FILE *safe_fopen(const char *const path, const char *const mode)
813 {
814     return safe_fopen_create_perms(path, mode, CF_PERMS_DEFAULT);
815 }
816 
817 /**
818  * Opens a file safely. It will follow symlinks, but only if the symlink is trusted,
819  * that is, if the owner of the symlink and the owner of the target are the same,
820  * or if the owner of the symlink is either root or the user running the current process.
821  * All components are checked, even symlinks encountered in earlier parts of the
822  * path name.
823  *
824  * It should always be used when opening a directory that is not guaranteed to be
825  * owned by root.
826  *
827  * @param pathname The path to open.
828  * @param flags Same mode as for system fopen().
829  * @param create_perms Permissions for file, only relevant on file creation.
830  * @return Same errors as fopen().
831  */
safe_fopen_create_perms(const char * const path,const char * const mode,const mode_t create_perms)832 FILE *safe_fopen_create_perms(
833     const char *const path, const char *const mode, const mode_t create_perms)
834 {
835     if (!path || !mode)
836     {
837         errno = EINVAL;
838         return NULL;
839     }
840 
841     int flags = 0;
842     for (int c = 0; mode[c]; c++)
843     {
844         switch (mode[c])
845         {
846         case 'r':
847             flags |= O_RDONLY;
848             break;
849         case 'w':
850             flags |= O_WRONLY | O_TRUNC | O_CREAT;
851             break;
852         case 'a':
853             flags |= O_WRONLY | O_CREAT;
854             break;
855         case '+':
856             flags &= ~(O_RDONLY | O_WRONLY);
857             flags |= O_RDWR;
858             break;
859         case 'b':
860             flags |= O_BINARY;
861             break;
862         case 't':
863             flags |= O_TEXT;
864             break;
865         case 'x':
866             flags |= O_EXCL;
867             break;
868         default:
869             ProgrammingError("Invalid flag for fopen: %s", mode);
870             return NULL;
871         }
872     }
873 
874     int fd = safe_open_create_perms(path, flags, create_perms);
875     if (fd < 0)
876     {
877         return NULL;
878     }
879     FILE *ret = fdopen(fd, mode);
880     if (!ret)
881     {
882         close(fd);
883         return NULL;
884     }
885 
886     if (mode[0] == 'a')
887     {
888         if (fseek(ret, 0, SEEK_END) < 0)
889         {
890             fclose(ret);
891             return NULL;
892         }
893     }
894 
895     return ret;
896 }
897 
898 /**
899  * Use this instead of chdir(). It changes into the directory safely, using safe_open().
900  * @param path Path to change into.
901  * @return Same return values as chdir().
902  */
safe_chdir(const char * path)903 int safe_chdir(const char *path)
904 {
905 #ifdef __MINGW32__
906     return chdir(path);
907 #else // !__MINGW32__
908     int fd = safe_open(path, O_RDONLY);
909     if (fd < 0)
910     {
911         return -1;
912     }
913     if (fchdir(fd) < 0)
914     {
915         close(fd);
916         return -1;
917     }
918     close(fd);
919     return 0;
920 #endif // !__MINGW32__
921 }
922 
923 #ifndef __MINGW32__
924 
925 /**
926  * Opens the true parent dir of the file in the path given. The notable
927  * difference from doing it the naive way (open(dirname(path))) is that it
928  * can follow the symlinks of the path, ending up in the true parent dir of the
929  * path. It follows the same safe mechanisms as `safe_open()` to do so. If
930  * AT_SYMLINK_NOFOLLOW is given, it is equivalent to doing it the naive way (but
931  * still following "safe" semantics).
932  * @param path           Path to open parent directory of.
933  * @param flags          Flags to use for fchownat.
934  * @param link_user      If we have traversed a link already, which user was it.
935  * @param link_group     If we have traversed a link already, which group was it.
936  * @param traversed_link Whether we have traversed a link. If this is false the
937  *                       two previus arguments are ignored. This is used enforce
938  *                       the correct UID/GID combination when following links.
939  *                       Initially this is false, but will be set to true in
940  *                       sub invocations if we follow a link.
941  * @param loop_countdown Protection against infinite loop following.
942  * @return File descriptor pointing to the parent directory of path, or -1 on
943  *         error.
944  */
safe_open_true_parent_dir(const char * path,int flags,uid_t link_user,gid_t link_group,bool traversed_link,int loop_countdown)945 static int safe_open_true_parent_dir(const char *path,
946                                      int flags,
947                                      uid_t link_user,
948                                      gid_t link_group,
949                                      bool traversed_link,
950                                      int loop_countdown)
951 {
952     int dirfd = -1;
953     int ret = -1;
954 
955     char *parent_dir_alloc = xstrdup(path);
956     char *leaf_alloc = xstrdup(path);
957     char *parent_dir = dirname(parent_dir_alloc);
958     char *leaf = basename(leaf_alloc);
959     struct stat statbuf;
960     uid_t p_uid = geteuid();
961 
962     if ((dirfd = safe_open(parent_dir, O_RDONLY)) == -1)
963     {
964         goto cleanup;
965     }
966 
967     if ((ret = fstatat(dirfd, leaf, &statbuf, AT_SYMLINK_NOFOLLOW)) == -1)
968     {
969         goto cleanup;
970     }
971 
972     // Some attacks may use symlinks to get higher privileges
973     // The safe cases are:
974     // * symlinks owned by root
975     // * symlinks owned by the user running the process
976     // * symlinks that have the same owner and group as the destination
977     if (traversed_link &&
978         link_user != 0 &&
979         link_user != p_uid &&
980         (link_user != statbuf.st_uid || link_group != statbuf.st_gid))
981     {
982         errno = ENOLINK;
983         ret = -1;
984         goto cleanup;
985     }
986 
987     if (S_ISLNK(statbuf.st_mode) && !(flags & AT_SYMLINK_NOFOLLOW))
988     {
989         if (--loop_countdown <= 0)
990         {
991             ret = -1;
992             errno = ELOOP;
993             goto cleanup;
994         }
995 
996         // Add one byte for '\0', and one byte to make sure size doesn't change
997         // in between calls.
998         char *link = xmalloc(statbuf.st_size + 2);
999         ret = readlinkat(dirfd, leaf, link, statbuf.st_size + 1);
1000         if (ret < 0 || ret > statbuf.st_size)
1001         {
1002             // Link either disappeared or was changed under our feet. Be safe
1003             // and bail out.
1004             free(link);
1005             errno = ENOLINK;
1006             ret = -1;
1007             goto cleanup;
1008         }
1009         link[ret] = '\0';
1010 
1011         char *resolved_link;
1012         if (link[0] == FILE_SEPARATOR)
1013         {
1014             // Takes ownership of link's memory, so no free().
1015             resolved_link = link;
1016         }
1017         else
1018         {
1019             xasprintf(&resolved_link, "%s%c%s", parent_dir,
1020                       FILE_SEPARATOR, link);
1021             free(link);
1022         }
1023 
1024         ret = safe_open_true_parent_dir(resolved_link, flags, statbuf.st_uid,
1025                                         statbuf.st_gid, true, loop_countdown);
1026 
1027         free(resolved_link);
1028         goto cleanup;
1029     }
1030 
1031     // We now know it either isn't a link, or we don't want to follow it if it
1032     // is. Return the parent dir.
1033     ret = dirfd;
1034     dirfd = -1;
1035 
1036 cleanup:
1037     free(parent_dir_alloc);
1038     free(leaf_alloc);
1039 
1040     if (dirfd != -1)
1041     {
1042         close(dirfd);
1043     }
1044 
1045     return ret;
1046 }
1047 
1048 /**
1049  * Implementation of safe_chown.
1050  * @param path Path to chown.
1051  * @param owner          Owner to set on path.
1052  * @param group          Group to set on path.
1053  * @param flags          Flags to use for fchownat.
1054  * @param link_user      If we have traversed a link already, which user was it.
1055  * @param link_group     If we have traversed a link already, which group was it.
1056  * @param traversed_link Whether we have traversed a link. If this is false the
1057  *                       two previus arguments are ignored. This is used enforce
1058  *                       the correct UID/GID combination when following links.
1059  *                       Initially this is false, but will be set to true in
1060  *                       sub invocations if we follow a link.
1061  * @param loop_countdown Protection against infinite loop following.
1062  */
safe_chown_impl(const char * path,uid_t owner,gid_t group,int flags)1063 int safe_chown_impl(const char *path, uid_t owner, gid_t group, int flags)
1064 {
1065     int dirfd = safe_open_true_parent_dir(path, flags, 0, 0, false, SYMLINK_MAX_DEPTH);
1066     if (dirfd < 0)
1067     {
1068         return -1;
1069     }
1070 
1071     char *leaf_alloc = xstrdup(path);
1072     char *leaf = basename(leaf_alloc);
1073 
1074     // We now know it either isn't a link, or we don't want to follow it if it
1075     // is. In either case make sure we don't try to follow it.
1076     flags |= AT_SYMLINK_NOFOLLOW;
1077 
1078     int ret = fchownat(dirfd, leaf, owner, group, flags);
1079     free(leaf_alloc);
1080     close(dirfd);
1081     return ret;
1082 }
1083 
1084 #endif // !__MINGW32__
1085 
1086 /**
1087  * Use this instead of chown(). It changes file owner safely, using safe_open().
1088  * @param path Path to operate on.
1089  * @param owner Owner.
1090  * @param group Group.
1091  * @return Same return values as chown().
1092  */
safe_chown(const char * path,uid_t owner,gid_t group)1093 int safe_chown(const char *path, uid_t owner, gid_t group)
1094 {
1095 #ifdef __MINGW32__
1096     return chown(path, owner, group);
1097 #else // !__MINGW32__
1098     return safe_chown_impl(path, owner, group, 0);
1099 #endif // !__MINGW32__
1100 }
1101 
1102 /**
1103  * Use this instead of lchown(). It changes file owner safely, using safe_open().
1104  * @param path Path to operate on.
1105  * @param owner Owner.
1106  * @param group Group.
1107  * @return Same return values as lchown().
1108  */
1109 #ifndef __MINGW32__
safe_lchown(const char * path,uid_t owner,gid_t group)1110 int safe_lchown(const char *path, uid_t owner, gid_t group)
1111 {
1112     return safe_chown_impl(path, owner, group, AT_SYMLINK_NOFOLLOW);
1113 }
1114 #endif // !__MINGW32__
1115 
1116 /**
1117  * Use this instead of chmod(). It changes file permissions safely, using safe_open().
1118  * @param path Path to operate on.
1119  * @param mode Permissions.
1120  * @return Same return values as chmod().
1121  */
safe_chmod(const char * path,mode_t mode)1122 int safe_chmod(const char *path, mode_t mode)
1123 {
1124 #ifdef __MINGW32__
1125     return chmod(path, mode);
1126 #else // !__MINGW32__
1127     int dirfd = -1;
1128     int ret = -1;
1129 
1130     char *leaf_alloc = xstrdup(path);
1131     char *leaf = basename(leaf_alloc);
1132     struct stat statbuf;
1133     uid_t olduid = 0;
1134 
1135     if ((dirfd = safe_open_true_parent_dir(path, 0, 0, 0, false, SYMLINK_MAX_DEPTH)) == -1)
1136     {
1137         goto cleanup;
1138     }
1139 
1140     if ((ret = fstatat(dirfd, leaf, &statbuf, AT_SYMLINK_NOFOLLOW)) == -1)
1141     {
1142         goto cleanup;
1143     }
1144 
1145     if (S_ISFIFO(statbuf.st_mode) || S_ISSOCK(statbuf.st_mode))
1146     {
1147         /* For FIFOs/sockets we cannot resort to the method of opening the file
1148            first, since it might block. But we also cannot use chmod directly,
1149            because the file may be switched with a symlink to a sensitive file
1150            under our feet, and there is no way to avoid following it. So
1151            instead, switch effective UID to the owner of the FIFO, and then use
1152            chmod.
1153         */
1154 
1155         /* save old euid */
1156         olduid = geteuid();
1157 
1158         if ((ret = seteuid(statbuf.st_uid)) == -1)
1159         {
1160             goto cleanup;
1161         }
1162 
1163         ret = fchmodat(dirfd, leaf, mode, 0);
1164 
1165         // Make sure EUID is set back before we check error condition, so that we
1166         // never return with lowered privileges.
1167         if (seteuid(olduid) == -1)
1168         {
1169             ProgrammingError("safe_chmod: Could not set EUID back. Should never happen.");
1170         }
1171 
1172         goto cleanup;
1173     }
1174 
1175     int file_fd = safe_open(path, 0);
1176     if (file_fd < 0)
1177     {
1178         ret = -1;
1179         goto cleanup;
1180     }
1181 
1182     ret = fchmod(file_fd, mode);
1183     close(file_fd);
1184 
1185 cleanup:
1186     free(leaf_alloc);
1187 
1188     if (dirfd != -1)
1189     {
1190         close(dirfd);
1191     }
1192 
1193     return ret;
1194 #endif // !__MINGW32__
1195 }
1196 
1197 /**
1198  * Use this instead of creat(). It creates a file safely, using safe_open().
1199  * @param path Path to operate on.
1200  * @param mode Permissions.
1201  * @return Same return values as creat().
1202  */
safe_creat(const char * pathname,mode_t mode)1203 int safe_creat(const char *pathname, mode_t mode)
1204 {
1205     return safe_open_create_perms(pathname,
1206                                   O_CREAT | O_WRONLY | O_TRUNC,
1207                                   mode);
1208 }
1209 
1210 // Windows implementation in Enterprise.
1211 #ifndef _WIN32
SetCloseOnExec(int fd,bool enable)1212 bool SetCloseOnExec(int fd, bool enable)
1213 {
1214     int flags = fcntl(fd, F_GETFD);
1215     if (enable)
1216     {
1217         flags |= FD_CLOEXEC;
1218     }
1219     else
1220     {
1221         flags &= ~FD_CLOEXEC;
1222     }
1223     return (fcntl(fd, F_SETFD, flags) == 0);
1224 }
1225 #endif // !_WIN32
1226 
DeleteDirectoryTreeInternal(const char * basepath,const char * path)1227 static bool DeleteDirectoryTreeInternal(const char *basepath, const char *path)
1228 {
1229     Dir *dirh = DirOpen(path);
1230     const struct dirent *dirp;
1231     bool failed = false;
1232 
1233     if (dirh == NULL)
1234     {
1235         if (errno == ENOENT)
1236         {
1237             /* Directory disappeared on its own */
1238             return true;
1239         }
1240 
1241         Log(LOG_LEVEL_INFO, "Unable to open directory '%s' during purge of directory tree '%s' (opendir: %s)",
1242             path, basepath, GetErrorStr());
1243         return false;
1244     }
1245 
1246     for (dirp = DirRead(dirh); dirp != NULL; dirp = DirRead(dirh))
1247     {
1248         if (!strcmp(dirp->d_name, ".") || !strcmp(dirp->d_name, ".."))
1249         {
1250             continue;
1251         }
1252 
1253         char subpath[PATH_MAX];
1254         snprintf(subpath, sizeof(subpath), "%s" FILE_SEPARATOR_STR "%s", path, dirp->d_name);
1255 
1256         struct stat lsb;
1257         if (lstat(subpath, &lsb) == -1)
1258         {
1259             if (errno == ENOENT)
1260             {
1261                 /* File disappeared on its own */
1262                 continue;
1263             }
1264 
1265             Log(LOG_LEVEL_VERBOSE, "Unable to stat file '%s' during purge of directory tree '%s' (lstat: %s)", path, basepath, GetErrorStr());
1266             failed = true;
1267         }
1268         else
1269         {
1270             if (S_ISDIR(lsb.st_mode))
1271             {
1272                 if (!DeleteDirectoryTreeInternal(basepath, subpath))
1273                 {
1274                     failed = true;
1275                 }
1276 
1277                 if (rmdir(subpath) == -1)
1278                 {
1279                     failed = true;
1280                 }
1281             }
1282             else
1283             {
1284                 if (unlink(subpath) == -1)
1285                 {
1286                     if (errno == ENOENT)
1287                     {
1288                         /* File disappeared on its own */
1289                         continue;
1290                     }
1291 
1292                     Log(LOG_LEVEL_VERBOSE, "Unable to remove file '%s' during purge of directory tree '%s'. (unlink: %s)",
1293                         subpath, basepath, GetErrorStr());
1294                     failed = true;
1295                 }
1296             }
1297         }
1298     }
1299 
1300     DirClose(dirh);
1301     return !failed;
1302 }
1303 
DeleteDirectoryTree(const char * path)1304 bool DeleteDirectoryTree(const char *path)
1305 {
1306     return DeleteDirectoryTreeInternal(path, path);
1307 }
1308 
1309 /**
1310  * @NOTE Better use FileSparseCopy() if you are copying file to file
1311  *       (that one callse this function).
1312  *
1313  * @NOTE Always use FileSparseWrite() to close the file descriptor, to avoid
1314  *       losing data.
1315  */
FileSparseWrite(int fd,const void * buf,size_t count,bool * wrote_hole)1316 bool FileSparseWrite(int fd, const void *buf, size_t count,
1317                      bool *wrote_hole)
1318 {
1319     bool all_zeroes = (memcchr(buf, '\0', count) == NULL);
1320 
1321     if (all_zeroes)                                     /* write a hole */
1322     {
1323         off_t seek_ret = lseek(fd, count, SEEK_CUR);
1324         if (seek_ret == (off_t) -1)
1325         {
1326             Log(LOG_LEVEL_ERR,
1327                 "Failed to write a hole in sparse file (lseek: %s)",
1328                 GetErrorStr());
1329             return false;
1330         }
1331     }
1332     else                                              /* write normally */
1333     {
1334         ssize_t w_ret = FullWrite(fd, buf, count);
1335         if (w_ret < 0)
1336         {
1337             Log(LOG_LEVEL_ERR,
1338                 "Failed to write to destination file (write: %s)",
1339                 GetErrorStr());
1340             return false;
1341         }
1342     }
1343 
1344     *wrote_hole = all_zeroes;
1345     return true;
1346 }
1347 
1348 /**
1349  * Copy data jumping over areas filled by '\0' greater than blk_size, so
1350  * files automatically become sparse if possible.
1351  *
1352  * File descriptors should already be open, the filenames #source and
1353  * #destination are only for logging purposes.
1354  *
1355  * @NOTE Always use FileSparseClose() to close the file descriptor, to avoid
1356  *       losing data.
1357  */
FileSparseCopy(int sd,const char * src_name,int dd,const char * dst_name,size_t blk_size,size_t * total_bytes_written,bool * last_write_was_a_hole)1358 bool FileSparseCopy(int sd, const char *src_name,
1359                     int dd, const char *dst_name,
1360                     size_t blk_size,
1361                     size_t *total_bytes_written,
1362                     bool   *last_write_was_a_hole)
1363 {
1364     assert(total_bytes_written   != NULL);
1365     assert(last_write_was_a_hole != NULL);
1366 
1367     const size_t buf_size  = blk_size;
1368     void *buf              = xmalloc(buf_size);
1369 
1370     size_t n_read_total = 0;
1371     bool   retval       = false;
1372 
1373     *last_write_was_a_hole = false;
1374 
1375     while (true)
1376     {
1377         ssize_t n_read = FullRead(sd, buf, buf_size);
1378         if (n_read < 0)
1379         {
1380             Log(LOG_LEVEL_ERR,
1381                 "Unable to read source file while copying '%s' to '%s'"
1382                 " (read: %s)", src_name, dst_name, GetErrorStr());
1383             break;
1384         }
1385         else if (n_read == 0)                                   /* EOF */
1386         {
1387             retval = true;
1388             break;
1389         }
1390 
1391         bool ret = FileSparseWrite(dd, buf, n_read,
1392                                    last_write_was_a_hole);
1393         if (!ret)
1394         {
1395             Log(LOG_LEVEL_ERR, "Failed to copy '%s' to '%s'",
1396                 src_name, dst_name);
1397             break;
1398         }
1399 
1400         n_read_total += n_read;
1401     }
1402 
1403     free(buf);
1404     *total_bytes_written   = n_read_total;
1405     return retval;
1406 }
1407 
1408 /**
1409  * Always close a written sparse file using this function, else truncation
1410  * might occur if the last part was a hole.
1411  *
1412  * If the tail of the file was a hole (and hence lseek(2)ed on destination
1413  * instead of being written), do a ftruncate(2) here to ensure the whole file
1414  * is written to the disc. But ftruncate() fails with EPERM on non-native
1415  * Linux filesystems (e.g. vfat, vboxfs) when the count is >= than the
1416  * size of the file. So we write() one byte and then ftruncate() it back.
1417  *
1418  * No need for this function to return anything, since the filedescriptor is
1419  * (attempted to) closed in either success or failure.
1420  *
1421  * TODO? instead of needing the #total_bytes_written parameter, we could
1422  * figure the offset after writing the one byte using lseek(fd,0,SEEK_CUR) and
1423  * truncate -1 from that offset. It's probably not worth adding an extra
1424  * system call for simplifying code.
1425  */
FileSparseClose(int fd,const char * filename,bool do_sync,size_t total_bytes_written,bool last_write_was_hole)1426 bool FileSparseClose(int fd, const char *filename,
1427                      bool do_sync,
1428                      size_t total_bytes_written,
1429                      bool last_write_was_hole)
1430 {
1431     if (last_write_was_hole)
1432     {
1433         ssize_t ret1 = FullWrite(fd, "", 1);
1434         if (ret1 == -1)
1435         {
1436             Log(LOG_LEVEL_ERR,
1437                 "Failed to close sparse file '%s' (write: %s)",
1438                 filename, GetErrorStr());
1439             close(fd);
1440             return false;
1441         }
1442 
1443         int ret2 = ftruncate(fd, total_bytes_written);
1444         if (ret2 == -1)
1445         {
1446             Log(LOG_LEVEL_ERR,
1447                 "Failed to close sparse file '%s' (ftruncate: %s)",
1448                 filename, GetErrorStr());
1449             close(fd);
1450             return false;
1451         }
1452     }
1453 
1454     if (do_sync)
1455     {
1456         if (fsync(fd) != 0)
1457         {
1458             Log(LOG_LEVEL_WARNING,
1459                 "Could not sync to disk file '%s' (fsync: %s)",
1460                 filename, GetErrorStr());
1461         }
1462     }
1463 
1464     int ret3 = close(fd);
1465     if (ret3 == -1)
1466     {
1467         Log(LOG_LEVEL_ERR,
1468             "Failed to close file '%s' (close: %s)",
1469             filename, GetErrorStr());
1470         return false;
1471     }
1472 
1473     return true;
1474 }
1475 
CfReadLine(char ** buff,size_t * size,FILE * fp)1476 ssize_t CfReadLine(char **buff, size_t *size, FILE *fp)
1477 {
1478     ssize_t b = getline(buff, size, fp);
1479     assert(b != 0 && "To the best of my knowledge, getline never returns zero");
1480 
1481     if (b > 0)
1482     {
1483         if ((*buff)[b - 1] == '\n')
1484         {
1485             (*buff)[b - 1] = '\0';
1486             b--;
1487         }
1488     }
1489 
1490     return b;
1491 }
1492 
CfReadLines(char ** buff,size_t * size,FILE * fp,Seq * lines)1493 ssize_t CfReadLines(char **buff, size_t *size, FILE *fp, Seq *lines)
1494 {
1495     assert(size != NULL);
1496     assert(fp != NULL);
1497     assert(lines != NULL);
1498     assert((buff != NULL) || *size == 0);
1499 
1500     ssize_t appended = 0;
1501 
1502     ssize_t ret;
1503     bool free_buff = (buff == NULL);
1504     while (!feof(fp))
1505     {
1506         assert((buff != NULL) || *size == 0);
1507         ret = CfReadLine(buff, size, fp);
1508         if (ret == -1)
1509         {
1510             if (!feof(fp))
1511             {
1512                 if (free_buff)
1513                 {
1514                     free(*buff);
1515                 }
1516                 return -1;
1517             }
1518         }
1519         else
1520         {
1521             SeqAppend(lines, xstrdup(*buff));
1522             appended++;
1523         }
1524     }
1525     if (free_buff)
1526     {
1527         free(*buff);
1528     }
1529 
1530     return appended;
1531 }
1532 
GlobFileList(const char * pattern)1533 StringSet* GlobFileList(const char *pattern)
1534 {
1535     StringSet *set = StringSetNew();
1536     glob_t globbuf;
1537     int globflags = 0; // TODO: maybe add GLOB_BRACE later
1538 
1539     const char* r_candidates[] = { "*", "*/*", "*/*/*", "*/*/*/*", "*/*/*/*/*", "*/*/*/*/*/*" };
1540     bool starstar = ( strstr(pattern, "**") != NULL );
1541     const char** candidates   = starstar ? r_candidates : NULL;
1542     const int candidate_count = starstar ? 6 : 1;
1543 
1544     for (int pi = 0; pi < candidate_count; pi++)
1545     {
1546         char *expanded = starstar ?
1547             SearchAndReplace(pattern, "**", candidates[pi]) :
1548             xstrdup(pattern);
1549 
1550 #ifdef _WIN32
1551         if (strchr(expanded, '\\'))
1552         {
1553             Log(LOG_LEVEL_VERBOSE, "Found backslash escape character in glob pattern '%s'. "
1554                 "Was forward slash intended?", expanded);
1555         }
1556 #endif
1557 
1558         if (glob(expanded, globflags, NULL, &globbuf) == 0)
1559         {
1560             for (size_t i = 0; i < globbuf.gl_pathc; i++)
1561             {
1562                 StringSetAdd(set, xstrdup(globbuf.gl_pathv[i]));
1563             }
1564 
1565             globfree(&globbuf);
1566         }
1567 
1568         free(expanded);
1569     }
1570 
1571     return set;
1572 }
1573 
1574 /*******************************************************************/
1575 
GetRelocatedProcdirRoot()1576 const char* GetRelocatedProcdirRoot()
1577 {
1578     const char *procdir = getenv("CFENGINE_TEST_OVERRIDE_PROCDIR");
1579     if (procdir == NULL)
1580     {
1581         procdir = "";
1582     }
1583     else
1584     {
1585         Log(LOG_LEVEL_VERBOSE, "Overriding /proc location to be %s", procdir);
1586     }
1587 
1588     return procdir;
1589 }
1590 
1591 
1592 #if !defined(__MINGW32__)
1593 
LockFD(int fd,short int lock_type,bool wait)1594 static int LockFD(int fd, short int lock_type, bool wait)
1595 {
1596     struct flock lock_spec = {
1597         .l_type = lock_type,
1598         .l_whence = SEEK_SET,
1599         .l_start = 0, /* start of the region to which the lock applies */
1600         .l_len = 0    /* till EOF */
1601     };
1602 
1603     if (wait)
1604     {
1605         while (fcntl(fd, F_SETLKW, &lock_spec) == -1)
1606         {
1607             if (errno != EINTR)
1608             {
1609                 Log(LOG_LEVEL_DEBUG, "Failed to acquire file lock for FD %d: %s",
1610                     fd, GetErrorStr());
1611                 return -1;
1612             }
1613         }
1614         return 0;
1615     }
1616     else
1617     {
1618         if (fcntl(fd, F_SETLK, &lock_spec) == -1)
1619         {
1620             Log(LOG_LEVEL_DEBUG, "Failed to acquire file lock for FD %d: %s",
1621                 fd, GetErrorStr());
1622             return -1;
1623         }
1624         /* else */
1625         return 0;
1626     }
1627 }
1628 
UnlockFD(int fd)1629 static int UnlockFD(int fd)
1630 {
1631     struct flock lock_spec = {
1632         .l_type = F_UNLCK,
1633         .l_whence = SEEK_SET,
1634         .l_start = 0, /* start of the region to which the lock applies */
1635         .l_len = 0    /* till EOF */
1636     };
1637 
1638     if (fcntl(fd, F_SETLK, &lock_spec) == -1)
1639     {
1640         Log(LOG_LEVEL_DEBUG, "Failed to release file lock for FD %d: %s",
1641             fd, GetErrorStr());
1642         return -1;
1643     }
1644     /* else */
1645     return 0;
1646 }
1647 
ExclusiveFileLock(FileLock * lock,bool wait)1648 int ExclusiveFileLock(FileLock *lock, bool wait)
1649 {
1650     assert(lock != NULL);
1651     assert(lock->fd >= 0);
1652 
1653     return LockFD(lock->fd, F_WRLCK, wait);
1654 }
1655 
SharedFileLock(FileLock * lock,bool wait)1656 int SharedFileLock(FileLock *lock, bool wait)
1657 {
1658     assert(lock != NULL);
1659     assert(lock->fd >= 0);
1660 
1661     return LockFD(lock->fd, F_RDLCK, wait);
1662 }
1663 
ExclusiveFileLockCheck(FileLock * lock)1664 bool ExclusiveFileLockCheck(FileLock *lock)
1665 {
1666     assert(lock != NULL);
1667     assert(lock->fd >= 0);
1668 
1669     struct flock lock_spec = {
1670         .l_type = F_WRLCK,
1671         .l_whence = SEEK_SET,
1672         .l_start = 0, /* start of the region to which the lock applies */
1673         .l_len = 0    /* till EOF */
1674     };
1675     if (fcntl(lock->fd, F_GETLK, &lock_spec) == -1)
1676     {
1677         /* should never happen */
1678         Log(LOG_LEVEL_ERR, "Error when checking locks on FD %d", lock->fd);
1679         return false;
1680     }
1681     return (lock_spec.l_type == F_UNLCK);
1682 }
1683 
ExclusiveFileUnlock(FileLock * lock,bool close_fd)1684 int ExclusiveFileUnlock(FileLock *lock, bool close_fd)
1685 {
1686     assert(lock != NULL);
1687     assert(lock->fd >= 0);
1688 
1689     if (close_fd)
1690     {
1691         /* also releases the lock */
1692         int ret = close(lock->fd);
1693         if (ret != 0)
1694         {
1695             Log(LOG_LEVEL_ERR, "Failed to close lock file with FD %d: %s",
1696                 lock->fd, GetErrorStr());
1697             lock->fd = -1;
1698             return -1;
1699         }
1700         /* else*/
1701         lock->fd = -1;
1702         return 0;
1703     }
1704     else
1705     {
1706         return UnlockFD(lock->fd);
1707     }
1708 }
1709 
SharedFileUnlock(FileLock * lock,bool close_fd)1710 int SharedFileUnlock(FileLock *lock, bool close_fd)
1711 {
1712     assert(lock != NULL);
1713     assert(lock->fd >= 0);
1714 
1715     /* unlocking is the same for both kinds of locks */
1716     return ExclusiveFileUnlock(lock, close_fd);
1717 }
1718 
1719 #else  /* __MINGW32__ */
1720 
LockFD(int fd,DWORD flags,bool wait)1721 static int LockFD(int fd, DWORD flags, bool wait)
1722 {
1723     OVERLAPPED ol = { 0 };
1724     ol.Offset = INT_MAX;
1725 
1726     if (!wait)
1727     {
1728         flags |= LOCKFILE_FAIL_IMMEDIATELY;
1729     }
1730 
1731     HANDLE fh = (HANDLE)_get_osfhandle(fd);
1732 
1733     if (!LockFileEx(fh, flags, 0, 1, 0, &ol))
1734     {
1735         Log(LOG_LEVEL_DEBUG, "Failed to acquire file lock for FD %d: %s",
1736             fd, GetErrorStr());
1737         return -1;
1738     }
1739 
1740     return 0;
1741 }
1742 
ExclusiveFileLock(FileLock * lock,bool wait)1743 int ExclusiveFileLock(FileLock *lock, bool wait)
1744 {
1745     assert(lock != NULL);
1746     assert(lock->fd >= 0);
1747 
1748     return LockFD(lock->fd, LOCKFILE_EXCLUSIVE_LOCK, wait);
1749 }
1750 
SharedFileLock(FileLock * lock,bool wait)1751 int SharedFileLock(FileLock *lock, bool wait)
1752 {
1753     assert(lock != NULL);
1754     assert(lock->fd >= 0);
1755 
1756     return LockFD(lock->fd, 0, wait);
1757 }
1758 
UnlockFD(int fd)1759 static int UnlockFD(int fd)
1760 {
1761     OVERLAPPED ol = { 0 };
1762     ol.Offset = INT_MAX;
1763 
1764     HANDLE fh = (HANDLE)_get_osfhandle(fd);
1765 
1766     if (!UnlockFileEx(fh, 0, 1, 0, &ol))
1767     {
1768         Log(LOG_LEVEL_DEBUG, "Failed to release file lock for FD %d: %s",
1769             fd, GetErrorStr());
1770         return -1;
1771     }
1772 
1773     return 0;
1774 }
1775 
ExclusiveFileLockCheck(FileLock * lock)1776 bool ExclusiveFileLockCheck(FileLock *lock)
1777 {
1778     /* XXX: there seems to be no way to check if the current process is holding
1779      * a lock on a file */
1780     return false;
1781 }
1782 
ExclusiveFileUnlock(FileLock * lock,bool close_fd)1783 int ExclusiveFileUnlock(FileLock *lock, bool close_fd)
1784 {
1785     assert(lock != NULL);
1786     assert(lock->fd >= 0);
1787 
1788     int ret = UnlockFD(lock->fd);
1789     if (close_fd)
1790     {
1791         close(lock->fd);
1792         lock->fd = -1;
1793     }
1794     return ret;
1795 }
1796 
SharedFileUnlock(FileLock * lock,bool close_fd)1797 int SharedFileUnlock(FileLock *lock, bool close_fd)
1798 {
1799     assert(lock != NULL);
1800     assert(lock->fd >= 0);
1801 
1802     /* unlocking is the same for both kinds of locks */
1803     return ExclusiveFileUnlock(lock, close_fd);
1804 }
1805 
1806 #endif  /* __MINGW32__ */
1807 
ExclusiveFileLockPath(FileLock * lock,const char * fpath,bool wait)1808 int ExclusiveFileLockPath(FileLock *lock, const char *fpath, bool wait)
1809 {
1810     assert(lock != NULL);
1811     assert(lock->fd < 0);
1812 
1813     int fd = safe_open(fpath, O_CREAT|O_RDWR);
1814     if (fd < 0)
1815     {
1816         Log(LOG_LEVEL_ERR, "Failed to open '%s' for locking", fpath);
1817         return -2;
1818     }
1819 
1820     lock->fd = fd;
1821     int ret = ExclusiveFileLock(lock, wait);
1822     if (ret != 0)
1823     {
1824         lock->fd = -1;
1825     }
1826     return ret;
1827 }
1828 
SharedFileLockPath(FileLock * lock,const char * fpath,bool wait)1829 int SharedFileLockPath(FileLock *lock, const char *fpath, bool wait)
1830 {
1831     assert(lock != NULL);
1832     assert(lock->fd < 0);
1833 
1834     int fd = safe_open(fpath, O_CREAT|O_RDONLY);
1835     if (fd < 0)
1836     {
1837         Log(LOG_LEVEL_ERR, "Failed to open '%s' for locking", fpath);
1838         return -2;
1839     }
1840 
1841     lock->fd = fd;
1842     int ret = SharedFileLock(lock, wait);
1843     if (ret != 0)
1844     {
1845         lock->fd = -1;
1846     }
1847     return ret;
1848 }
1849