6 |
* SELinux support by Yuichi Nakamura <ynakam@hitachisoft.jp> |
* SELinux support by Yuichi Nakamura <ynakam@hitachisoft.jp> |
7 |
* |
* |
8 |
* Licensed under GPLv2 or later, see file LICENSE in this tarball for details. |
* Licensed under GPLv2 or later, see file LICENSE in this tarball for details. |
|
* |
|
9 |
*/ |
*/ |
|
|
|
10 |
#include "libbb.h" |
#include "libbb.h" |
11 |
|
|
12 |
|
// FEATURE_NON_POSIX_CP: |
13 |
|
// |
14 |
// POSIX: if exists and -i, ask (w/o -i assume yes). |
// POSIX: if exists and -i, ask (w/o -i assume yes). |
15 |
// Then open w/o EXCL (yes, not unlink!). |
// Then open w/o EXCL (yes, not unlink!). |
16 |
// If open still fails and -f, try unlink, then try open again. |
// If open still fails and -f, try unlink, then try open again. |
17 |
// Result: a mess: |
// Result: a mess: |
18 |
// If dest is a softlink, we overwrite softlink's destination! |
// If dest is a (sym)link, we overwrite link destination! |
19 |
// (or fail, if it points to dir/nonexistent location/etc). |
// (or fail, if it points to dir/nonexistent location/etc). |
20 |
// This is strange, but POSIX-correct. |
// This is strange, but POSIX-correct. |
21 |
// coreutils cp has --remove-destination to override this... |
// coreutils cp has --remove-destination to override this... |
|
// |
|
|
// NB: we have special code which still allows for "cp file /dev/node" |
|
|
// to work POSIX-ly (the only realistic case where it makes sense) |
|
22 |
|
|
23 |
#define DO_POSIX_CP 0 /* 1 - POSIX behavior, 0 - safe behavior */ |
/* Called if open of destination, link creation etc fails. |
24 |
|
* errno must be set to relevant value ("why we cannot create dest?") |
25 |
// errno must be set to relevant value ("why we cannot create dest?") |
* to give reasonable error message */ |
|
// for POSIX mode to give reasonable error message |
|
26 |
static int ask_and_unlink(const char *dest, int flags) |
static int ask_and_unlink(const char *dest, int flags) |
27 |
{ |
{ |
28 |
int e = errno; |
int e = errno; |
29 |
#if DO_POSIX_CP |
|
30 |
|
#if !ENABLE_FEATURE_NON_POSIX_CP |
31 |
if (!(flags & (FILEUTILS_FORCE|FILEUTILS_INTERACTIVE))) { |
if (!(flags & (FILEUTILS_FORCE|FILEUTILS_INTERACTIVE))) { |
32 |
// Either it exists, or the *path* doesnt exist |
/* Either it exists, or the *path* doesnt exist */ |
33 |
bb_perror_msg("cannot create '%s'", dest); |
bb_perror_msg("can't create '%s'", dest); |
34 |
return -1; |
return -1; |
35 |
} |
} |
36 |
#endif |
#endif |
37 |
// If !DO_POSIX_CP, act as if -f is always in effect - we don't want |
// else: act as if -f is always in effect. |
38 |
// "cannot create" msg, we want unlink to be done (silently unless -i). |
// We don't want "can't create" msg, we want unlink to be done |
39 |
|
// (silently unless -i). Why? POSIX cp usually succeeds with |
40 |
|
// O_TRUNC open of existing file, and user is left ignorantly happy. |
41 |
|
// With above block unconditionally enabled, non-POSIX cp |
42 |
|
// will complain a lot more than POSIX one. |
43 |
|
|
44 |
// TODO: maybe we should do it only if ctty is present? |
/* TODO: maybe we should do it only if ctty is present? */ |
45 |
if (flags & FILEUTILS_INTERACTIVE) { |
if (flags & FILEUTILS_INTERACTIVE) { |
46 |
// We would not do POSIX insanity. -i asks, |
// We would not do POSIX insanity. -i asks, |
47 |
// then _unlinks_ the offender. Presto. |
// then _unlinks_ the offender. Presto. |
49 |
// Or else we will end up having 3 open()s! |
// Or else we will end up having 3 open()s! |
50 |
fprintf(stderr, "%s: overwrite '%s'? ", applet_name, dest); |
fprintf(stderr, "%s: overwrite '%s'? ", applet_name, dest); |
51 |
if (!bb_ask_confirmation()) |
if (!bb_ask_confirmation()) |
52 |
return 0; // not allowed to overwrite |
return 0; /* not allowed to overwrite */ |
53 |
} |
} |
54 |
if (unlink(dest) < 0) { |
if (unlink(dest) < 0) { |
55 |
#if ENABLE_FEATURE_VERBOSE_CP_MESSAGE |
#if ENABLE_FEATURE_VERBOSE_CP_MESSAGE |
56 |
if (e == errno && e == ENOENT) { |
if (e == errno && e == ENOENT) { |
57 |
/* e == ENOTDIR is similar: path has non-dir component, |
/* e == ENOTDIR is similar: path has non-dir component, |
58 |
* but in this case we don't even reach copy_file() */ |
* but in this case we don't even reach copy_file() */ |
59 |
bb_error_msg("cannot create '%s': Path does not exist", dest); |
bb_error_msg("can't create '%s': Path does not exist", dest); |
60 |
return -1; // error |
return -1; /* error */ |
61 |
} |
} |
62 |
#endif |
#endif |
63 |
errno = e; |
errno = e; /* do not use errno from unlink */ |
64 |
bb_perror_msg("cannot create '%s'", dest); |
bb_perror_msg("can't create '%s'", dest); |
65 |
return -1; // error |
return -1; /* error */ |
66 |
} |
} |
67 |
return 1; // ok (to try again) |
return 1; /* ok (to try again) */ |
68 |
} |
} |
69 |
|
|
70 |
/* Return: |
/* Return: |
83 |
signed char ovr; |
signed char ovr; |
84 |
|
|
85 |
/* Inverse of cp -d ("cp without -d") */ |
/* Inverse of cp -d ("cp without -d") */ |
86 |
#define FLAGS_DEREF (flags & FILEUTILS_DEREFERENCE) |
#define FLAGS_DEREF (flags & (FILEUTILS_DEREFERENCE + FILEUTILS_DEREFERENCE_L0)) |
87 |
|
|
88 |
if ((FLAGS_DEREF ? stat : lstat)(source, &source_stat) < 0) { |
if ((FLAGS_DEREF ? stat : lstat)(source, &source_stat) < 0) { |
89 |
// This may be a dangling symlink. |
/* This may be a dangling symlink. |
90 |
// Making [sym]links to dangling symlinks works, so... |
* Making [sym]links to dangling symlinks works, so... */ |
91 |
if (flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK)) |
if (flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK)) |
92 |
goto make_links; |
goto make_links; |
93 |
bb_perror_msg("cannot stat '%s'", source); |
bb_perror_msg("can't stat '%s'", source); |
94 |
return -1; |
return -1; |
95 |
} |
} |
96 |
|
|
97 |
if (lstat(dest, &dest_stat) < 0) { |
if (lstat(dest, &dest_stat) < 0) { |
98 |
if (errno != ENOENT) { |
if (errno != ENOENT) { |
99 |
bb_perror_msg("cannot stat '%s'", dest); |
bb_perror_msg("can't stat '%s'", dest); |
100 |
return -1; |
return -1; |
101 |
} |
} |
102 |
} else { |
} else { |
114 |
security_context_t con; |
security_context_t con; |
115 |
if (lgetfilecon(source, &con) >= 0) { |
if (lgetfilecon(source, &con) >= 0) { |
116 |
if (setfscreatecon(con) < 0) { |
if (setfscreatecon(con) < 0) { |
117 |
bb_perror_msg("cannot set setfscreatecon %s", con); |
bb_perror_msg("can't set setfscreatecon %s", con); |
118 |
freecon(con); |
freecon(con); |
119 |
return -1; |
return -1; |
120 |
} |
} |
121 |
} else if (errno == ENOTSUP || errno == ENODATA) { |
} else if (errno == ENOTSUP || errno == ENODATA) { |
122 |
setfscreatecon_or_die(NULL); |
setfscreatecon_or_die(NULL); |
123 |
} else { |
} else { |
124 |
bb_perror_msg("cannot lgetfilecon %s", source); |
bb_perror_msg("can't lgetfilecon %s", source); |
125 |
return -1; |
return -1; |
126 |
} |
} |
127 |
} |
} |
166 |
mode |= S_IRWXU; |
mode |= S_IRWXU; |
167 |
if (mkdir(dest, mode) < 0) { |
if (mkdir(dest, mode) < 0) { |
168 |
umask(saved_umask); |
umask(saved_umask); |
169 |
bb_perror_msg("cannot create directory '%s'", dest); |
bb_perror_msg("can't create directory '%s'", dest); |
170 |
return -1; |
return -1; |
171 |
} |
} |
172 |
umask(saved_umask); |
umask(saved_umask); |
173 |
/* need stat info for add_to_ino_dev_hashtable */ |
/* need stat info for add_to_ino_dev_hashtable */ |
174 |
if (lstat(dest, &dest_stat) < 0) { |
if (lstat(dest, &dest_stat) < 0) { |
175 |
bb_perror_msg("cannot stat '%s'", dest); |
bb_perror_msg("can't stat '%s'", dest); |
176 |
return -1; |
return -1; |
177 |
} |
} |
178 |
} |
} |
194 |
if (new_source == NULL) |
if (new_source == NULL) |
195 |
continue; |
continue; |
196 |
new_dest = concat_path_file(dest, d->d_name); |
new_dest = concat_path_file(dest, d->d_name); |
197 |
if (copy_file(new_source, new_dest, flags) < 0) |
if (copy_file(new_source, new_dest, flags & ~FILEUTILS_DEREFERENCE_L0) < 0) |
198 |
retval = -1; |
retval = -1; |
199 |
free(new_source); |
free(new_source); |
200 |
free(new_dest); |
free(new_dest); |
204 |
if (!dest_exists |
if (!dest_exists |
205 |
&& chmod(dest, source_stat.st_mode & ~saved_umask) < 0 |
&& chmod(dest, source_stat.st_mode & ~saved_umask) < 0 |
206 |
) { |
) { |
207 |
bb_perror_msg("cannot preserve %s of '%s'", "permissions", dest); |
bb_perror_msg("can't preserve %s of '%s'", "permissions", dest); |
208 |
/* retval = -1; - WRONG! copy *WAS* made */ |
/* retval = -1; - WRONG! copy *WAS* made */ |
209 |
} |
} |
210 |
goto preserve_mode_ugid_time; |
goto preserve_mode_ugid_time; |
213 |
if (flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK)) { |
if (flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK)) { |
214 |
int (*lf)(const char *oldpath, const char *newpath); |
int (*lf)(const char *oldpath, const char *newpath); |
215 |
make_links: |
make_links: |
216 |
// Hmm... maybe |
/* Hmm... maybe |
217 |
// if (DEREF && MAKE_SOFTLINK) source = realpath(source) ? |
* if (DEREF && MAKE_SOFTLINK) source = realpath(source) ? |
218 |
// (but realpath returns NULL on dangling symlinks...) |
* (but realpath returns NULL on dangling symlinks...) */ |
219 |
lf = (flags & FILEUTILS_MAKE_SOFTLINK) ? symlink : link; |
lf = (flags & FILEUTILS_MAKE_SOFTLINK) ? symlink : link; |
220 |
if (lf(source, dest) < 0) { |
if (lf(source, dest) < 0) { |
221 |
ovr = ask_and_unlink(dest, flags); |
ovr = ask_and_unlink(dest, flags); |
222 |
if (ovr <= 0) |
if (ovr <= 0) |
223 |
return ovr; |
return ovr; |
224 |
if (lf(source, dest) < 0) { |
if (lf(source, dest) < 0) { |
225 |
bb_perror_msg("cannot create link '%s'", dest); |
bb_perror_msg("can't create link '%s'", dest); |
226 |
return -1; |
return -1; |
227 |
} |
} |
228 |
} |
} |
229 |
/* _Not_ jumping to preserve_mode_ugid_time: |
/* _Not_ jumping to preserve_mode_ugid_time: |
230 |
* hard/softlinks don't have those */ |
* (sym)links don't have those */ |
231 |
return 0; |
return 0; |
232 |
} |
} |
233 |
|
|
257 |
if (ovr <= 0) |
if (ovr <= 0) |
258 |
return ovr; |
return ovr; |
259 |
if (link(link_target, dest) < 0) { |
if (link(link_target, dest) < 0) { |
260 |
bb_perror_msg("cannot create link '%s'", dest); |
bb_perror_msg("can't create link '%s'", dest); |
261 |
return -1; |
return -1; |
262 |
} |
} |
263 |
} |
} |
275 |
if (!S_ISREG(source_stat.st_mode)) |
if (!S_ISREG(source_stat.st_mode)) |
276 |
new_mode = 0666; |
new_mode = 0666; |
277 |
|
|
278 |
/* POSIX way is a security problem versus symlink attacks, |
// POSIX way is a security problem versus (sym)link attacks |
279 |
* we do it only for non-symlinks, and only for non-recursive, |
if (!ENABLE_FEATURE_NON_POSIX_CP) { |
|
* non-interactive cp. NB: it is still racy |
|
|
* for "cp file /home/bad_user/file" case |
|
|
* (user can rm file and create a link to /etc/passwd) */ |
|
|
if (DO_POSIX_CP |
|
|
|| (dest_exists |
|
|
&& !(flags & (FILEUTILS_RECUR|FILEUTILS_INTERACTIVE)) |
|
|
&& !S_ISLNK(dest_stat.st_mode)) |
|
|
) { |
|
280 |
dst_fd = open(dest, O_WRONLY|O_CREAT|O_TRUNC, new_mode); |
dst_fd = open(dest, O_WRONLY|O_CREAT|O_TRUNC, new_mode); |
281 |
} else /* safe way: */ |
} else { /* safe way: */ |
282 |
dst_fd = open(dest, O_WRONLY|O_CREAT|O_EXCL, new_mode); |
dst_fd = open(dest, O_WRONLY|O_CREAT|O_EXCL, new_mode); |
283 |
|
} |
284 |
if (dst_fd == -1) { |
if (dst_fd == -1) { |
285 |
ovr = ask_and_unlink(dest, flags); |
ovr = ask_and_unlink(dest, flags); |
286 |
if (ovr <= 0) { |
if (ovr <= 0) { |
318 |
retval = -1; |
retval = -1; |
319 |
/* Ok, writing side I can understand... */ |
/* Ok, writing side I can understand... */ |
320 |
if (close(dst_fd) < 0) { |
if (close(dst_fd) < 0) { |
321 |
bb_perror_msg("cannot close '%s'", dest); |
bb_perror_msg("can't close '%s'", dest); |
322 |
retval = -1; |
retval = -1; |
323 |
} |
} |
324 |
/* ...but read size is already checked by bb_copyfd_eof */ |
/* ...but read size is already checked by bb_copyfd_eof */ |
345 |
int r = symlink(lpath, dest); |
int r = symlink(lpath, dest); |
346 |
free(lpath); |
free(lpath); |
347 |
if (r < 0) { |
if (r < 0) { |
348 |
bb_perror_msg("cannot create symlink '%s'", dest); |
bb_perror_msg("can't create symlink '%s'", dest); |
349 |
return -1; |
return -1; |
350 |
} |
} |
351 |
if (flags & FILEUTILS_PRESERVE_STATUS) |
if (flags & FILEUTILS_PRESERVE_STATUS) |
352 |
if (lchown(dest, source_stat.st_uid, source_stat.st_gid) < 0) |
if (lchown(dest, source_stat.st_uid, source_stat.st_gid) < 0) |
353 |
bb_perror_msg("cannot preserve %s of '%s'", "ownership", dest); |
bb_perror_msg("can't preserve %s of '%s'", "ownership", dest); |
354 |
} |
} |
355 |
/* _Not_ jumping to preserve_mode_ugid_time: |
/* _Not_ jumping to preserve_mode_ugid_time: |
356 |
* symlinks don't have those */ |
* symlinks don't have those */ |
360 |
|| S_ISSOCK(source_stat.st_mode) || S_ISFIFO(source_stat.st_mode) |
|| S_ISSOCK(source_stat.st_mode) || S_ISFIFO(source_stat.st_mode) |
361 |
) { |
) { |
362 |
if (mknod(dest, source_stat.st_mode, source_stat.st_rdev) < 0) { |
if (mknod(dest, source_stat.st_mode, source_stat.st_rdev) < 0) { |
363 |
bb_perror_msg("cannot create '%s'", dest); |
bb_perror_msg("can't create '%s'", dest); |
364 |
return -1; |
return -1; |
365 |
} |
} |
366 |
} else { |
} else { |
374 |
/* Cannot happen: */ |
/* Cannot happen: */ |
375 |
/* && !(flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK)) */ |
/* && !(flags & (FILEUTILS_MAKE_SOFTLINK|FILEUTILS_MAKE_HARDLINK)) */ |
376 |
) { |
) { |
377 |
struct utimbuf times; |
struct timeval times[2]; |
378 |
|
|
379 |
times.actime = source_stat.st_atime; |
times[1].tv_sec = times[0].tv_sec = source_stat.st_mtime; |
380 |
times.modtime = source_stat.st_mtime; |
times[1].tv_usec = times[0].tv_usec = 0; |
381 |
/* BTW, utimes sets usec-precision time - just FYI */ |
/* BTW, utimes sets usec-precision time - just FYI */ |
382 |
if (utime(dest, ×) < 0) |
if (utimes(dest, times) < 0) |
383 |
bb_perror_msg("cannot preserve %s of '%s'", "times", dest); |
bb_perror_msg("can't preserve %s of '%s'", "times", dest); |
384 |
if (chown(dest, source_stat.st_uid, source_stat.st_gid) < 0) { |
if (chown(dest, source_stat.st_uid, source_stat.st_gid) < 0) { |
385 |
source_stat.st_mode &= ~(S_ISUID | S_ISGID); |
source_stat.st_mode &= ~(S_ISUID | S_ISGID); |
386 |
bb_perror_msg("cannot preserve %s of '%s'", "ownership", dest); |
bb_perror_msg("can't preserve %s of '%s'", "ownership", dest); |
387 |
} |
} |
388 |
if (chmod(dest, source_stat.st_mode) < 0) |
if (chmod(dest, source_stat.st_mode) < 0) |
389 |
bb_perror_msg("cannot preserve %s of '%s'", "permissions", dest); |
bb_perror_msg("can't preserve %s of '%s'", "permissions", dest); |
390 |
} |
} |
391 |
|
|
392 |
return retval; |
return retval; |