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 
26 #include <platform.h>
27 
28 #include <files_copy.h>
29 
30 #include <files_names.h>
31 #include <files_interfaces.h>
32 #include <instrumentation.h>
33 #include <policy.h>
34 #include <files_lib.h>
35 #include <file_lib.h>
36 #include <string_lib.h>
37 #include <acl_tools.h>
38 
CopyRegularFileDiskPerms(const char * source,const char * destination,int mode)39 bool CopyRegularFileDiskPerms(const char *source, const char *destination,
40                               int mode)
41 {
42     assert(source != NULL);
43     assert(destination != NULL);
44 
45     int sd = safe_open(source, O_RDONLY | O_BINARY);
46     if (sd == -1)
47     {
48         Log(LOG_LEVEL_INFO, "Can't copy '%s' (open: %s)",
49             source, GetErrorStr());
50         return false;
51     }
52 
53     /* unlink() + safe_open(O_CREAT|O_EXCL) to avoid
54        symlink attacks and races. */
55     unlink(destination);
56 
57     int dd = safe_open_create_perms(destination,
58                                     O_WRONLY | O_CREAT | O_EXCL | O_BINARY,
59                                     mode);
60     if (dd == -1)
61     {
62         Log(LOG_LEVEL_INFO,
63             "Unable to open destination file while copying '%s' to '%s'"
64             " (open: %s)", source, destination, GetErrorStr());
65         close(sd);
66         return false;
67     }
68 
69     /* We need to stat the file to get the block size of the source file */
70     struct stat statbuf;
71     if (fstat(sd, &statbuf) == -1)
72     {
73         Log(LOG_LEVEL_INFO, "Can't copy '%s' (fstat: %s)",
74             source, GetErrorStr());
75         close(sd);
76         close(dd);
77         return false;
78     }
79 
80     size_t total_bytes_written;
81     bool last_write_was_hole;
82     bool ret = FileSparseCopy(sd, source, dd, destination,
83                               ST_BLKSIZE(statbuf),
84                               &total_bytes_written, &last_write_was_hole);
85     if (!ret)
86     {
87         unlink(destination);
88         close(sd);
89         close(dd);
90         return false;
91     }
92 
93     bool do_sync = false;
94     ret = FileSparseClose(dd, destination, do_sync,
95                           total_bytes_written, last_write_was_hole);
96     if (!ret)
97     {
98         unlink(destination);
99     }
100 
101     close(sd);
102     close(dd);
103     return ret;
104 }
105 
CopyRegularFileDisk(const char * source,const char * destination)106 bool CopyRegularFileDisk(const char *source, const char *destination)
107 {
108     assert(source != NULL);
109     assert(destination != NULL);
110 
111     bool ok1 = false, ok2 = false;       /* initialize before the goto end; */
112 
113     int sd = safe_open(source, O_RDONLY | O_BINARY);
114     if (sd == -1)
115     {
116         Log(LOG_LEVEL_INFO, "Can't copy '%s' (open: %s)",
117             source, GetErrorStr());
118         goto end;
119     }
120 
121     /* We need to stat the file to get the right source permissions. */
122     struct stat statbuf;
123     if (fstat(sd, &statbuf) == -1)
124     {
125         Log(LOG_LEVEL_INFO, "Can't copy '%s' (fstat: %s)",
126             source, GetErrorStr());
127         goto end;
128     }
129 
130     /* unlink() + safe_open(O_CREAT|O_EXCL) to avoid
131        symlink attacks and races. */
132     unlink(destination);
133 
134     int dd = safe_open_create_perms(destination,
135                                     O_WRONLY | O_CREAT | O_EXCL | O_BINARY,
136                                     statbuf.st_mode);
137     if (dd == -1)
138     {
139         Log(LOG_LEVEL_INFO,
140             "Unable to open destination file while copying '%s' to '%s'"
141             " (open: %s)", source, destination, GetErrorStr());
142         goto end;
143     }
144 
145     size_t total_bytes_written;
146     bool   last_write_was_hole;
147     ok1 = FileSparseCopy(sd, source, dd, destination,
148                          ST_BLKSIZE(statbuf),
149                          &total_bytes_written, &last_write_was_hole);
150     bool do_sync = false;
151     ok2 = FileSparseClose(dd, destination, do_sync,
152                           total_bytes_written, last_write_was_hole);
153 
154     if (!ok1 || !ok2)
155     {
156         unlink(destination);
157     }
158 
159   end:
160     if (sd != -1)
161     {
162         close(sd);
163     }
164     return ok1 && ok2;
165 }
166 
CopyFilePermissionsDisk(const char * source,const char * destination)167 bool CopyFilePermissionsDisk(const char *source, const char *destination)
168 {
169     struct stat statbuf;
170 
171     if (stat(source, &statbuf) == -1)
172     {
173         Log(LOG_LEVEL_INFO, "Can't copy permissions '%s'. (stat: %s)", source, GetErrorStr());
174         return false;
175     }
176 
177     if (safe_chmod(destination, statbuf.st_mode) != 0)
178     {
179         Log(LOG_LEVEL_INFO, "Can't copy permissions '%s'. (chmod: %s)", source, GetErrorStr());
180         return false;
181     }
182 
183     if (safe_chown(destination, statbuf.st_uid, statbuf.st_gid) != 0)
184     {
185         Log(LOG_LEVEL_INFO, "Can't copy permissions '%s'. (chown: %s)", source, GetErrorStr());
186         return false;
187     }
188 
189     if (!CopyFileExtendedAttributesDisk(source, destination, NULL))
190     {
191         return false;
192     }
193 
194     return true;
195 }
196 
CopyFileExtendedAttributesDisk(const char * source,const char * destination,bool * change)197 bool CopyFileExtendedAttributesDisk(const char *source, const char *destination, bool *change)
198 {
199 #if defined(WITH_XATTR)
200     // Extended attributes include both POSIX ACLs and SELinux contexts.
201     ssize_t attr_raw_names_size;
202     char attr_raw_names[CF_BUFSIZE];
203 
204     attr_raw_names_size = llistxattr(source, attr_raw_names, sizeof(attr_raw_names));
205     if (attr_raw_names_size < 0)
206     {
207         if (errno == ENOTSUP || errno == ENODATA)
208         {
209             if (change != NULL)
210             {
211                 *change = false;
212             }
213             return true;
214         }
215         else
216         {
217             Log(LOG_LEVEL_ERR, "Can't copy extended attributes from '%s' to '%s'. (llistxattr: %s)",
218                 source, destination, GetErrorStr());
219             return false;
220         }
221     }
222 
223     int pos;
224     for (pos = 0; pos < attr_raw_names_size;)
225     {
226         const char *current = attr_raw_names + pos;
227         pos += strlen(current) + 1;
228 
229         char src_data[CF_BUFSIZE];
230         int src_datasize = lgetxattr(source, current, src_data, sizeof(src_data));
231         if (src_datasize < 0)
232         {
233             if (errno == ENOTSUP)
234             {
235                 continue;
236             }
237             else
238             {
239                 Log(LOG_LEVEL_ERR, "Can't copy extended attributes from '%s' to '%s'. (lgetxattr: %s: %s)",
240                     source, destination, GetErrorStr(), current);
241                 return false;
242             }
243         }
244         char dst_data[CF_BUFSIZE];
245         int dst_datasize = lgetxattr(destination, current, dst_data, sizeof(dst_data));
246         if ((dst_datasize < 0) && (errno == ENOTSUP))
247         {
248             continue;
249         }
250         else if ((src_datasize == dst_datasize) &&
251                  (memcmp(src_data, dst_data, src_datasize) == 0))
252         {
253             /* The value is the same, no need to overwrite it. */
254             continue;
255         }
256 
257         int ret = lsetxattr(destination, current, src_data, src_datasize, 0);
258         if (ret < 0)
259         {
260             if (errno == ENOTSUP)
261             {
262                 continue;
263             }
264             else
265             {
266                 Log(LOG_LEVEL_ERR, "Can't copy extended attributes from '%s' to '%s'. (lsetxattr: %s: %s)",
267                     source, destination, GetErrorStr(), current);
268                 return false;
269             }
270         }
271         if (change != NULL)
272         {
273             *change = true;
274         }
275     }
276 
277 #else // !WITH_XATTR
278     // ACLs are included in extended attributes, but fall back to CopyACLs if xattr is not available.
279     if (!CopyACLs(source, destination, change))
280     {
281         return false;
282     }
283 #endif
284 
285     return true;
286 }
287