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