diff options
Diffstat (limited to 'src/fcopy/main.c')
-rw-r--r-- | src/fcopy/main.c | 536 |
1 files changed, 276 insertions, 260 deletions
diff --git a/src/fcopy/main.c b/src/fcopy/main.c index b1e2813db..9f19b6dd8 100644 --- a/src/fcopy/main.c +++ b/src/fcopy/main.c | |||
@@ -1,5 +1,5 @@ | |||
1 | /* | 1 | /* |
2 | * Copyright (C) 2014-2016 Firejail Authors | 2 | * Copyright (C) 2014-2017 Firejail Authors |
3 | * | 3 | * |
4 | * This file is part of firejail project | 4 | * This file is part of firejail project |
5 | * | 5 | * |
@@ -21,6 +21,9 @@ | |||
21 | #include "../include/common.h" | 21 | #include "../include/common.h" |
22 | #include <fcntl.h> | 22 | #include <fcntl.h> |
23 | #include <ftw.h> | 23 | #include <ftw.h> |
24 | #include <errno.h> | ||
25 | |||
26 | static int arg_follow_link = 0; | ||
24 | 27 | ||
25 | 28 | ||
26 | #define COPY_LIMIT (500 * 1024 *1024) | 29 | #define COPY_LIMIT (500 * 1024 *1024) |
@@ -34,245 +37,244 @@ static char *inpath = NULL; | |||
34 | 37 | ||
35 | // modified version of the function from util.c | 38 | // modified version of the function from util.c |
36 | static void copy_file(const char *srcname, const char *destname, mode_t mode, uid_t uid, gid_t gid) { | 39 | static void copy_file(const char *srcname, const char *destname, mode_t mode, uid_t uid, gid_t gid) { |
37 | assert(srcname); | 40 | assert(srcname); |
38 | assert(destname); | 41 | assert(destname); |
39 | mode &= 07777; | 42 | mode &= 07777; |
40 | 43 | ||
41 | // open source | 44 | // open source |
42 | int src = open(srcname, O_RDONLY); | 45 | int src = open(srcname, O_RDONLY); |
43 | if (src < 0) { | 46 | if (src < 0) { |
44 | fprintf(stderr, "Warning: cannot open %s, file not copied\n", srcname); | 47 | fprintf(stderr, "Warning fcopy: cannot open %s, file not copied\n", srcname); |
45 | return; | 48 | return; |
46 | } | 49 | } |
47 | 50 | ||
48 | // open destination | 51 | // open destination |
49 | int dst = open(destname, O_CREAT|O_WRONLY|O_TRUNC, 0755); | 52 | int dst = open(destname, O_CREAT|O_WRONLY|O_TRUNC, 0755); |
50 | if (dst < 0) { | 53 | if (dst < 0) { |
51 | fprintf(stderr, "Warning fcopy: cannot open %s, file not copied\n", destname); | 54 | fprintf(stderr, "Warning fcopy: cannot open %s, file not copied\n", destname); |
52 | close(src); | 55 | close(src); |
53 | return; | 56 | return; |
54 | } | 57 | } |
55 | 58 | ||
56 | // copy | 59 | // copy |
57 | ssize_t len; | 60 | ssize_t len; |
58 | static const int BUFLEN = 1024; | 61 | static const int BUFLEN = 1024; |
59 | unsigned char buf[BUFLEN]; | 62 | unsigned char buf[BUFLEN]; |
60 | while ((len = read(src, buf, BUFLEN)) > 0) { | 63 | while ((len = read(src, buf, BUFLEN)) > 0) { |
61 | int done = 0; | 64 | int done = 0; |
62 | while (done != len) { | 65 | while (done != len) { |
63 | int rv = write(dst, buf + done, len - done); | 66 | int rv = write(dst, buf + done, len - done); |
64 | if (rv == -1) | 67 | if (rv == -1) |
65 | goto errexit; | 68 | goto errexit; |
66 | done += rv; | 69 | done += rv; |
67 | } | 70 | } |
68 | } | 71 | } |
69 | fflush(0); | 72 | fflush(0); |
70 | 73 | ||
71 | if (fchown(dst, uid, gid) == -1) | 74 | if (fchown(dst, uid, gid) == -1) |
72 | goto errexit; | 75 | goto errexit; |
73 | if (fchmod(dst, mode) == -1) | 76 | if (fchmod(dst, mode) == -1) |
74 | goto errexit; | 77 | goto errexit; |
75 | 78 | ||
76 | close(src); | 79 | close(src); |
77 | close(dst); | 80 | close(dst); |
78 | 81 | ||
79 | return; | 82 | return; |
80 | 83 | ||
81 | errexit: | 84 | errexit: |
82 | close(src); | 85 | close(src); |
83 | close(dst); | 86 | close(dst); |
84 | unlink(destname); | 87 | unlink(destname); |
85 | fprintf(stderr, "Warning fcopy: cannot copy %s\n", destname); | 88 | fprintf(stderr, "Warning fcopy: cannot copy %s\n", destname); |
86 | } | 89 | } |
87 | 90 | ||
88 | 91 | ||
89 | 92 | ||
90 | // modified version of the function in firejail/util.c | 93 | // modified version of the function in firejail/util.c |
91 | static void mkdir_attr(const char *fname, mode_t mode, uid_t uid, gid_t gid) { | 94 | static void mkdir_attr(const char *fname, mode_t mode, uid_t uid, gid_t gid) { |
92 | assert(fname); | 95 | assert(fname); |
93 | mode &= 07777; | 96 | mode &= 07777; |
94 | 97 | ||
95 | if (mkdir(fname, mode) == -1 || | 98 | if (mkdir(fname, mode) == -1 || |
96 | chmod(fname, mode) == -1) { | 99 | chmod(fname, mode) == -1) { |
97 | fprintf(stderr, "Error fcopy: failed to create %s directory\n", fname); | 100 | fprintf(stderr, "Error fcopy: failed to create %s directory\n", fname); |
98 | errExit("mkdir/chmod"); | 101 | errExit("mkdir/chmod"); |
99 | } | 102 | } |
100 | if (chown(fname, uid, gid)) | 103 | if (chown(fname, uid, gid)) |
101 | fprintf(stderr, "Warning fcopy: failed to change ownership of %s\n", fname); | 104 | fprintf(stderr, "Warning fcopy: failed to change ownership of %s\n", fname); |
102 | } | 105 | } |
103 | 106 | ||
104 | void copy_link(const char *target, const char *linkpath, mode_t mode, uid_t uid, gid_t gid) { | 107 | void copy_link(const char *target, const char *linkpath, mode_t mode, uid_t uid, gid_t gid) { |
105 | (void) mode; | 108 | (void) mode; |
106 | (void) uid; | 109 | (void) uid; |
107 | (void) gid; | 110 | (void) gid; |
108 | char *rp = realpath(target, NULL); | 111 | char *rp = realpath(target, NULL); |
109 | if (rp) { | 112 | if (rp) { |
110 | if (symlink(rp, linkpath) == -1) | 113 | if (symlink(rp, linkpath) == -1) |
111 | goto errout; | 114 | goto errout; |
112 | free(rp); | 115 | free(rp); |
113 | } | 116 | } |
114 | else | 117 | else |
115 | goto errout; | 118 | goto errout; |
116 | 119 | ||
117 | return; | 120 | return; |
118 | errout: | 121 | errout: |
119 | fprintf(stderr, "Warning fcopy: cannot create symbolic link %s\n", target); | 122 | fprintf(stderr, "Warning fcopy: cannot create symbolic link %s\n", target); |
120 | } | 123 | } |
121 | 124 | ||
122 | static int first = 1; | 125 | static int first = 1; |
123 | static int fs_copydir(const char *infname, const struct stat *st, int ftype, struct FTW *sftw) { | 126 | static int fs_copydir(const char *infname, const struct stat *st, int ftype, struct FTW *sftw) { |
124 | (void) st; | 127 | (void) st; |
125 | (void) sftw; | 128 | (void) sftw; |
126 | assert(infname); | 129 | assert(infname); |
127 | assert(*infname != '\0'); | 130 | assert(*infname != '\0'); |
128 | assert(outpath); | 131 | assert(outpath); |
129 | assert(*outpath != '\0'); | 132 | assert(*outpath != '\0'); |
130 | assert(inpath); | 133 | assert(inpath); |
131 | 134 | ||
132 | // check size limit | 135 | // check size limit |
133 | if (size_limit_reached) | 136 | if (size_limit_reached) |
134 | return 0; | 137 | return 0; |
135 | 138 | ||
136 | 139 | ||
137 | char *outfname; | 140 | char *outfname; |
138 | if (asprintf(&outfname, "%s%s", outpath, infname + strlen(inpath)) == -1) | 141 | if (asprintf(&outfname, "%s%s", outpath, infname + strlen(inpath)) == -1) |
139 | errExit("asprintf"); | 142 | errExit("asprintf"); |
140 | 143 | ||
141 | //printf("outpaht %s\n", outpath); | 144 | // don't copy it if we already have the file |
142 | //printf("inpath %s\n", inpath); | 145 | struct stat s; |
143 | //printf("infname %s\n", infname); | 146 | if (stat(outfname, &s) == 0) { |
144 | //printf("outfname %s\n\n", outfname); | 147 | if (first) |
145 | 148 | first = 0; | |
146 | // don't copy it if we already have the file | 149 | else |
147 | struct stat s; | 150 | fprintf(stderr, "Warning fcopy: skipping %s, file already present\n", infname); |
148 | if (stat(outfname, &s) == 0) { | 151 | free(outfname); |
149 | if (first) | 152 | return 0; |
150 | first = 0; | 153 | } |
151 | else | 154 | |
152 | fprintf(stderr, "Warning fcopy: skipping %s, file already present\n", infname); | 155 | // extract mode and ownership |
153 | free(outfname); | 156 | if (stat(infname, &s) != 0) { |
154 | return 0; | 157 | fprintf(stderr, "Warning fcopy: skipping %s, cannot find inode\n", infname); |
155 | } | 158 | free(outfname); |
156 | 159 | return 0; | |
157 | // extract mode and ownership | 160 | } |
158 | if (stat(infname, &s) != 0) { | 161 | uid_t uid = s.st_uid; |
159 | fprintf(stderr, "Warning fcopy: skipping %s, cannot find inode\n", infname); | 162 | gid_t gid = s.st_gid; |
160 | free(outfname); | 163 | mode_t mode = s.st_mode; |
161 | return 0; | 164 | |
162 | } | 165 | // recalculate size |
163 | uid_t uid = s.st_uid; | 166 | if ((s.st_size + size_cnt) > COPY_LIMIT) { |
164 | gid_t gid = s.st_gid; | 167 | fprintf(stderr, "Error fcopy: size limit of %dMB reached\n", (COPY_LIMIT / 1024) / 1024); |
165 | mode_t mode = s.st_mode; | 168 | size_limit_reached = 1; |
166 | 169 | free(outfname); | |
167 | // recalculate size | 170 | return 0; |
168 | if ((s.st_size + size_cnt) > COPY_LIMIT) { | 171 | } |
169 | fprintf(stderr, "Error fcopy: size limit of %dMB reached\n", (COPY_LIMIT / 1024) / 1024); | 172 | |
170 | size_limit_reached = 1; | 173 | file_cnt++; |
171 | free(outfname); | 174 | size_cnt += s.st_size; |
172 | return 0; | 175 | |
173 | } | 176 | if(ftype == FTW_F) { |
174 | 177 | copy_file(infname, outfname, mode, uid, gid); | |
175 | file_cnt++; | 178 | } |
176 | size_cnt += s.st_size; | 179 | else if (ftype == FTW_D) { |
177 | 180 | mkdir_attr(outfname, mode, uid, gid); | |
178 | if(ftype == FTW_F) { | 181 | } |
179 | copy_file(infname, outfname, mode, uid, gid); | 182 | else if (ftype == FTW_SL) { |
180 | } | 183 | copy_link(infname, outfname, mode, uid, gid); |
181 | else if (ftype == FTW_D) { | 184 | } |
182 | mkdir_attr(outfname, mode, uid, gid); | 185 | |
183 | } | 186 | return(0); |
184 | else if (ftype == FTW_SL) { | ||
185 | copy_link(infname, outfname, mode, uid, gid); | ||
186 | } | ||
187 | |||
188 | return(0); | ||
189 | } | 187 | } |
190 | 188 | ||
191 | static char *check(const char *src) { | 189 | static char *check(const char *src) { |
192 | struct stat s; | 190 | struct stat s; |
193 | char *rsrc = realpath(src, NULL); | 191 | char *rsrc = realpath(src, NULL); |
194 | if (!rsrc || stat(rsrc, &s) == -1) | 192 | if (!rsrc || stat(rsrc, &s) == -1) |
195 | goto errexit; | 193 | goto errexit; |
196 | 194 | ||
197 | // check uid | 195 | // check uid |
198 | if (s.st_uid != getuid() || s.st_gid != getgid()) | 196 | if (s.st_uid != getuid() || s.st_gid != getgid()) |
199 | goto errexit; | 197 | goto errexit; |
200 | 198 | ||
201 | // dir, link, regular file | 199 | // dir, link, regular file |
202 | if (S_ISDIR(s.st_mode) || S_ISREG(s.st_mode) || S_ISLNK(s.st_mode)) | 200 | if (S_ISDIR(s.st_mode) || S_ISREG(s.st_mode) || S_ISLNK(s.st_mode)) |
203 | return rsrc; // normal exit from the function | 201 | return rsrc; // normal exit from the function |
204 | 202 | ||
205 | errexit: | 203 | errexit: |
206 | fprintf(stderr, "Error fcopy: invalid file %s\n", src); | 204 | fprintf(stderr, "Error fcopy: invalid file %s\n", src); |
207 | exit(1); | 205 | exit(1); |
208 | } | 206 | } |
209 | 207 | ||
210 | static void duplicate_dir(const char *src, const char *dest, struct stat *s) { | 208 | static void duplicate_dir(const char *src, const char *dest, struct stat *s) { |
211 | (void) s; | 209 | (void) s; |
212 | char *rsrc = check(src); | 210 | char *rsrc = check(src); |
213 | char *rdest = check(dest); | 211 | char *rdest = check(dest); |
214 | inpath = rsrc; | 212 | inpath = rsrc; |
215 | outpath = rdest; | 213 | outpath = rdest; |
216 | 214 | ||
217 | // walk | 215 | // walk |
218 | if(nftw(rsrc, fs_copydir, 1, FTW_PHYS) != 0) { | 216 | if(nftw(rsrc, fs_copydir, 1, FTW_PHYS) != 0) { |
219 | fprintf(stderr, "Error: unable to copy file\n"); | 217 | fprintf(stderr, "Error: unable to copy file\n"); |
220 | exit(1); | 218 | exit(1); |
221 | } | 219 | } |
222 | 220 | ||
223 | free(rsrc); | 221 | free(rsrc); |
224 | free(rdest); | 222 | free(rdest); |
225 | } | 223 | } |
226 | 224 | ||
227 | static void duplicate_file(const char *src, const char *dest, struct stat *s) { | 225 | static void duplicate_file(const char *src, const char *dest, struct stat *s) { |
228 | char *rsrc = check(src); | 226 | char *rsrc = check(src); |
229 | char *rdest = check(dest); | 227 | char *rdest = check(dest); |
230 | uid_t uid = s->st_uid; | 228 | uid_t uid = s->st_uid; |
231 | gid_t gid = s->st_gid; | 229 | gid_t gid = s->st_gid; |
232 | mode_t mode = s->st_mode; | 230 | mode_t mode = s->st_mode; |
233 | 231 | ||
234 | // build destination file name | 232 | // build destination file name |
235 | char *name; | 233 | char *name; |
236 | char *ptr = strrchr(rsrc, '/'); | 234 | char *ptr = (arg_follow_link)? strrchr(src, '/'): strrchr(rsrc, '/'); |
237 | ptr++; | 235 | ptr++; |
238 | if (asprintf(&name, "%s/%s", rdest, ptr) == -1) | 236 | if (asprintf(&name, "%s/%s", rdest, ptr) == -1) |
239 | errExit("asprintf"); | 237 | errExit("asprintf"); |
240 | 238 | ||
241 | // copy | 239 | // copy |
242 | copy_file(rsrc, name, mode, uid, gid); | 240 | copy_file(rsrc, name, mode, uid, gid); |
243 | 241 | ||
244 | free(name); | 242 | free(name); |
245 | free(rsrc); | 243 | free(rsrc); |
246 | free(rdest); | 244 | free(rdest); |
247 | } | 245 | } |
248 | 246 | ||
249 | static void duplicate_link(const char *src, const char *dest, struct stat *s) { | 247 | static void duplicate_link(const char *src, const char *dest, struct stat *s) { |
250 | char *rsrc = check(src); // we drop the result and use the original name | 248 | char *rsrc = check(src); // we drop the result and use the original name |
251 | char *rdest = check(dest); | 249 | char *rdest = check(dest); |
252 | uid_t uid = s->st_uid; | 250 | uid_t uid = s->st_uid; |
253 | gid_t gid = s->st_gid; | 251 | gid_t gid = s->st_gid; |
254 | mode_t mode = s->st_mode; | 252 | mode_t mode = s->st_mode; |
255 | 253 | ||
256 | // build destination file name | 254 | // build destination file name |
257 | char *name; | 255 | char *name; |
258 | // char *ptr = strrchr(rsrc, '/'); | 256 | // char *ptr = strrchr(rsrc, '/'); |
259 | char *ptr = strrchr(src, '/'); | 257 | char *ptr = strrchr(src, '/'); |
260 | ptr++; | 258 | ptr++; |
261 | if (asprintf(&name, "%s/%s", rdest, ptr) == -1) | 259 | if (asprintf(&name, "%s/%s", rdest, ptr) == -1) |
262 | errExit("asprintf"); | 260 | errExit("asprintf"); |
263 | 261 | ||
264 | // copy | 262 | // copy |
265 | copy_link(rsrc, name, mode, uid, gid); | 263 | copy_link(rsrc, name, mode, uid, gid); |
266 | 264 | ||
267 | free(name); | 265 | free(name); |
268 | free(rsrc); | 266 | free(rsrc); |
269 | free(rdest); | 267 | free(rdest); |
270 | } | 268 | } |
271 | 269 | ||
272 | static void usage(void) { | 270 | static void usage(void) { |
273 | printf("Usage: fcopy src dest\n"); | 271 | fputs("Usage: fcopy [--follow-link] src dest\n" |
274 | printf("Copy src file in dest directory. If src is a directory, copy all the files in\n"); | 272 | "\n" |
275 | printf("src recoursively. If the destination directory does not exist, it will be created.\n"); | 273 | "Copy SRC to DEST/SRC. SRC may be a file, directory, or symbolic link.\n" |
274 | "If SRC is a directory it is copied recursively. If it is a symlink,\n" | ||
275 | "the link itself is duplicated, unless --follow-link is given,\n" | ||
276 | "in which case the destination of the link is copied.\n" | ||
277 | "DEST must already exist and must be a directory.\n", stderr); | ||
276 | } | 278 | } |
277 | 279 | ||
278 | int main(int argc, char **argv) { | 280 | int main(int argc, char **argv) { |
@@ -285,56 +287,70 @@ for (i = 0; i < argc; i++) | |||
285 | printf("\n"); | 287 | printf("\n"); |
286 | } | 288 | } |
287 | #endif | 289 | #endif |
288 | if (argc != 3) { | 290 | char *src; |
289 | fprintf(stderr, "Error fcopy: files missing\n"); | 291 | char *dest; |
290 | usage(); | 292 | |
291 | exit(1); | 293 | if (argc == 3) { |
292 | } | 294 | src = argv[1]; |
293 | 295 | dest = argv[2]; | |
294 | // check the two files; remove ending / | 296 | arg_follow_link = 0; |
295 | char *src = argv[1]; | 297 | } |
296 | int len = strlen(src); | 298 | else if (argc == 4 && !strcmp(argv[1], "--follow-link")) { |
297 | if (src[len - 1] == '/') | 299 | src = argv[2]; |
298 | src[len - 1] = '\0'; | 300 | dest = argv[3]; |
299 | if (strcspn(src, "\\*&!?\"'<>%^(){}[];,") != (size_t)len) { | 301 | arg_follow_link = 1; |
300 | fprintf(stderr, "Error fcopy: invalid file name %s\n", src); | 302 | } |
301 | exit(1); | 303 | else { |
302 | } | 304 | fprintf(stderr, "Error: arguments missing\n"); |
303 | 305 | usage(); | |
304 | char *dest = argv[2]; | 306 | exit(1); |
305 | len = strlen(dest); | 307 | } |
306 | if (dest[len - 1] == '/') | 308 | |
307 | dest[len - 1] = '\0'; | 309 | // check the two files; remove ending / |
308 | if (strcspn(dest, "\\*&!?\"'<>%^(){}[];,~") != (size_t)len) { | 310 | int len = strlen(src); |
309 | fprintf(stderr, "Error fcopy: invalid file name %s\n", dest); | 311 | if (src[len - 1] == '/') |
310 | exit(1); | 312 | src[len - 1] = '\0'; |
311 | } | 313 | if (strcspn(src, "\\*&!?\"'<>%^(){}[];,") != (size_t)len) { |
312 | 314 | fprintf(stderr, "Error fcopy: invalid source file name %s\n", src); | |
313 | 315 | exit(1); | |
314 | // the destination should be a directory; | 316 | } |
315 | struct stat s; | 317 | |
316 | if (stat(dest, &s) == -1 || | 318 | len = strlen(dest); |
317 | !S_ISDIR(s.st_mode)) { | 319 | if (dest[len - 1] == '/') |
318 | fprintf(stderr, "Error fcopy: invalid destination directory\n"); | 320 | dest[len - 1] = '\0'; |
319 | exit(1); | 321 | if (strcspn(dest, "\\*&!?\"'<>%^(){}[];,~") != (size_t)len) { |
320 | } | 322 | fprintf(stderr, "Error fcopy: invalid dest file name %s\n", dest); |
321 | 323 | exit(1); | |
322 | // copy files | 324 | } |
323 | if (lstat(src, &s) == -1) { | 325 | |
324 | fprintf(stderr, "Error fcopy: cannot find source file\n"); | 326 | |
325 | exit(1); | 327 | // the destination should be a directory; |
326 | } | 328 | struct stat s; |
327 | 329 | if (stat(dest, &s) == -1) { | |
328 | if (S_ISDIR(s.st_mode)) | 330 | fprintf(stderr, "Error fcopy: dest dir %s: %s\n", dest, strerror(errno)); |
329 | duplicate_dir(src, dest, &s); | 331 | exit(1); |
330 | else if (S_ISREG(s.st_mode)) | 332 | } |
331 | duplicate_file(src, dest, &s); | 333 | if (!S_ISDIR(s.st_mode)) { |
332 | else if (S_ISLNK(s.st_mode)) | 334 | fprintf(stderr, "Error fcopy: dest %s is not a directory\n", dest); |
333 | duplicate_link(src, dest, &s); | 335 | exit(1); |
334 | else { | 336 | } |
335 | fprintf(stderr, "Error fcopy: source file unsupported\n"); | 337 | |
336 | exit(1); | 338 | // copy files |
337 | } | 339 | if ((arg_follow_link ? stat : lstat)(src, &s) == -1) { |
338 | 340 | fprintf(stderr, "Error fcopy: src %s: %s\n", src, strerror(errno)); | |
339 | return 0; | 341 | exit(1); |
342 | } | ||
343 | |||
344 | if (S_ISDIR(s.st_mode)) | ||
345 | duplicate_dir(src, dest, &s); | ||
346 | else if (S_ISREG(s.st_mode)) | ||
347 | duplicate_file(src, dest, &s); | ||
348 | else if (S_ISLNK(s.st_mode)) | ||
349 | duplicate_link(src, dest, &s); | ||
350 | else { | ||
351 | fprintf(stderr, "Error fcopy: src %s is an unsupported type of file\n", src); | ||
352 | exit(1); | ||
353 | } | ||
354 | |||
355 | return 0; | ||
340 | } | 356 | } |