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 <alloc.h>              /* xstrdup() */
27 #include <dir.h>                /* DirOpen(), DirRead(), DirClose() */
28 #include <eval_context.h>       /* ToChangesChroot() */
29 #include <file_lib.h>           /* FILE_SEPARATOR */
30 #include <files_lib.h>          /* MakeParentDirectory() */
31 #include <files_copy.h>         /* CopyRegularFileDisk(), CopyFilePermissionsDisk() */
32 #include <files_names.h>        /* IsAbsPath(), JoinPaths() */
33 #include <files_links.h>        /* ExpandLinks() */
34 #include <string_lib.h>         /* StringEqual() */
35 #include <string_sequence.h>    /* WriteLenPrefixedString() */
36 #include <writer.h>             /* FileWriter(), Writer */
37 #include <csv_writer.h>         /* CsvWriter */
38 
39 #include <changes_chroot.h>
40 
41 
GetLastFileSeparator(const char * path,const char * end)42 static inline const char *GetLastFileSeparator(const char *path, const char *end)
43 {
44     const char *cp = end;
45     for (; (cp > path) && (*cp != FILE_SEPARATOR); cp--);
46     return cp;
47 }
48 
GetFirstNonExistingParentDir(const char * path,char * buf)49 static char *GetFirstNonExistingParentDir(const char *path, char *buf)
50 {
51     /* Get rid of the trailing file separator (if any). */
52     size_t path_len = strlen(path);
53     if (path[path_len - 1] == FILE_SEPARATOR)
54     {
55         strncpy(buf, path, path_len);
56         buf[path_len] = '\0';
57     }
58     else
59     {
60         strncpy(buf, path, path_len + 1);
61     }
62 
63     char *last_sep = (char *) GetLastFileSeparator(buf, buf + path_len);
64     while (last_sep != buf)
65     {
66         *last_sep = '\0';
67         if (access(buf, F_OK) == 0)
68         {
69             *last_sep = FILE_SEPARATOR;
70             *(last_sep + 1) = '\0';
71             return buf;
72         }
73         last_sep = (char *) GetLastFileSeparator(buf, last_sep - 1);
74     }
75     *last_sep = '\0';
76     return buf;
77 }
78 
MirrorDirTreePermsToChroot(const char * path)79 static bool MirrorDirTreePermsToChroot(const char *path)
80 {
81     const char *chrooted = ToChangesChroot(path);
82 
83     if (!CopyFilePermissionsDisk(path, chrooted))
84     {
85         return false;
86     }
87 
88     size_t path_len = strlen(path);
89     char path_copy[path_len + 1];
90     strcpy(path_copy, path);
91 
92     const char *const path_copy_end = path_copy + (path_len - 1);
93     const char *const chrooted_end = chrooted + strlen(chrooted) - 1;
94 
95     char *last_sep = (char *) GetLastFileSeparator(path_copy, path_copy_end);
96     while (last_sep != path_copy)
97     {
98         char *last_sep_chrooted = (char *) chrooted_end - (path_copy_end - last_sep);
99         *last_sep = '\0';
100         *last_sep_chrooted = '\0';
101         if (!CopyFilePermissionsDisk(path_copy, chrooted))
102         {
103             return false;
104         }
105         *last_sep = FILE_SEPARATOR;
106         *last_sep_chrooted = FILE_SEPARATOR;
107         last_sep = (char *) GetLastFileSeparator(path_copy, last_sep - 1);
108     }
109     return true;
110 }
111 
112 #ifndef __MINGW32__
113 /**
114  * Mirror the symlink #path to #chrooted_path together with its target, then
115  * mirror the target if it is a symlink too,..., recursively.
116  */
ChrootSymlinkDeep(const char * path,const char * chrooted_path,struct stat * sb)117 static void ChrootSymlinkDeep(const char *path, const char *chrooted_path, struct stat *sb)
118 {
119     assert(sb != NULL);
120 
121     size_t target_size = (sb->st_size != 0 ? sb->st_size + 1 : PATH_MAX);
122     char target[target_size];
123     ssize_t ret = readlink(path, target, target_size);
124     if (ret == -1)
125     {
126         /* Should never happen, but nothing to do here if it does. */
127         return;
128     }
129     target[ret] = '\0';
130 
131     if (IsAbsPath(target))
132     {
133         const char *chrooted_target = ToChangesChroot(target);
134         if (symlink(chrooted_target, chrooted_path) != 0)
135         {
136             /* Should never happen, but nothing to do here if it does. */
137             return;
138         }
139         else
140         {
141             PrepareChangesChroot(target);
142         }
143     }
144     else
145     {
146         if (symlink(target, chrooted_path) != 0)
147         {
148             /* Should never happen, but nothing to do here if it does. */
149             return;
150         }
151         else
152         {
153             char expanded_target[PATH_MAX];
154             if (!ExpandLinks(expanded_target, path, 0, 1))
155             {
156                 /* Should never happen, but nothing to do here if it does. */
157                 return;
158             }
159             PrepareChangesChroot(expanded_target);
160         }
161     }
162 }
163 #endif  /* __MINGW32__ */
164 
PrepareChangesChroot(const char * path)165 void PrepareChangesChroot(const char *path)
166 {
167     struct stat sb;
168 
169     /* We need to create a copy because ToChangesChroot() returns a pointer to
170      * its internal buffer which gets overwritten by later calls of the
171      * function. */
172     char *chrooted = xstrdup(ToChangesChroot(path));
173     if (lstat(chrooted, &sb) != -1)
174     {
175         /* chrooted 'path' already exists, we are done */
176         free(chrooted);
177         return;
178     }
179 
180     {
181         char first_nonexisting_parent[PATH_MAX];
182         GetFirstNonExistingParentDir(path, first_nonexisting_parent);
183         MakeParentDirectory(first_nonexisting_parent, true, NULL);
184         MirrorDirTreePermsToChroot(first_nonexisting_parent);
185     }
186 
187     if (lstat(path, &sb) == -1)
188     {
189         /* 'path' doesn't exist, nothing to do here */
190         return;
191     }
192 
193 #ifndef __MINGW32__
194     if (S_ISLNK(sb.st_mode))
195     {
196         ChrootSymlinkDeep(path, chrooted, &sb);
197     }
198     else
199 #endif  /* __MINGW32__ */
200     if (S_ISDIR(sb.st_mode))
201     {
202         mkdir(chrooted, sb.st_mode);
203         Dir *dir = DirOpen(path);
204         if (dir == NULL)
205         {
206             /* Should never happen, but nothing to do here if it does. */
207             free(chrooted);
208             return;
209         }
210 
211         for (const struct dirent *entry = DirRead(dir); entry != NULL; entry = DirRead(dir))
212         {
213             if (StringEqual(entry->d_name, ".") || StringEqual(entry->d_name, ".."))
214             {
215                 continue;
216             }
217             char entry_path[PATH_MAX];
218             strcpy(entry_path, path);
219             JoinPaths(entry_path, PATH_MAX, entry->d_name);
220             PrepareChangesChroot(entry_path);
221         }
222         DirClose(dir);
223     }
224     else
225     {
226         /* TODO: sockets, pipes, devices,... ? */
227         CopyRegularFileDisk(path, chrooted);
228     }
229     CopyFilePermissionsDisk(path, chrooted);
230 
231     free(chrooted);
232 }
233 
RecordFileChangedInChroot(const char * path)234 bool RecordFileChangedInChroot(const char *path)
235 {
236     FILE *chroot_changes = safe_fopen(ToChangesChroot(CHROOT_CHANGES_LIST_FILE), "a");
237     Writer *writer = FileWriter(chroot_changes);
238 
239     bool ret = WriteLenPrefixedString(writer, path);
240 
241     WriterClose(writer);
242     return ret;
243 }
244 
RecordFileRenamedInChroot(const char * old_name,const char * new_name)245 bool RecordFileRenamedInChroot(const char *old_name, const char *new_name)
246 {
247     FILE *chroot_renames = safe_fopen(ToChangesChroot(CHROOT_RENAMES_LIST_FILE), "a");
248     Writer *writer = FileWriter(chroot_renames);
249 
250     bool ret = WriteLenPrefixedString(writer, old_name);
251     ret = (ret && WriteLenPrefixedString(writer, new_name));
252 
253     WriterClose(writer);
254     return ret;
255 }
256 
RecordFileEvaluatedInChroot(const char * path)257 bool RecordFileEvaluatedInChroot(const char *path)
258 {
259     FILE *chroot_changes = safe_fopen(ToChangesChroot(CHROOT_KEPT_LIST_FILE), "a");
260     Writer *writer = FileWriter(chroot_changes);
261 
262     bool ret = WriteLenPrefixedString(writer, path);
263 
264     WriterClose(writer);
265     return ret;
266 }
267 
RecordPkgOperationInChroot(const char * op,const char * name,const char * arch,const char * version)268 bool RecordPkgOperationInChroot(const char *op, const char *name, const char *arch, const char *version)
269 {
270     assert(op != NULL);
271     assert(name != NULL);
272     /* the rest is optional */
273 
274     FILE *chroot_pkgs_ops = safe_fopen(ToChangesChroot(CHROOT_PKGS_OPS_FILE), "a");
275     if (chroot_pkgs_ops == NULL)
276     {
277         Log(LOG_LEVEL_ERR, "Failed to open package operations record file '%s'",
278             CHROOT_PKGS_OPS_FILE);
279         return false;
280     }
281 
282     Writer *writer = FileWriter(chroot_pkgs_ops);
283     if (writer == NULL)
284     {
285         Log(LOG_LEVEL_ERR, "Failed to create a writer for package operations record file '%s'",
286             CHROOT_PKGS_OPS_FILE);
287         fclose(chroot_pkgs_ops);
288         return false;
289     }
290 
291     CsvWriter *csv_writer = CsvWriterOpen(writer);
292     if (csv_writer == NULL)
293     {
294         Log(LOG_LEVEL_ERR, "Failed to create a CSV writer for package operations record file '%s'",
295             CHROOT_PKGS_OPS_FILE);
296         WriterClose(writer);
297         return false;
298     }
299 
300     CsvWriterField(csv_writer, op);
301     CsvWriterField(csv_writer, name);
302     CsvWriterField(csv_writer, NULL_TO_EMPTY_STRING(arch));
303     CsvWriterField(csv_writer, NULL_TO_EMPTY_STRING(version));
304 
305     CsvWriterNewRecord(csv_writer);
306     CsvWriterClose(csv_writer);
307     WriterClose(writer);
308 
309     return true;
310 }
311