diff options
Diffstat (limited to 'src/fcopy/main.c')
-rw-r--r-- | src/fcopy/main.c | 345 |
1 files changed, 345 insertions, 0 deletions
diff --git a/src/fcopy/main.c b/src/fcopy/main.c new file mode 100644 index 000000000..4437b90e5 --- /dev/null +++ b/src/fcopy/main.c | |||
@@ -0,0 +1,345 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014-2016 Firejail Authors | ||
3 | * | ||
4 | * This file is part of firejail project | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation; either version 2 of the License, or | ||
9 | * (at your option) any later version. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License along | ||
17 | * with this program; if not, write to the Free Software Foundation, Inc., | ||
18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | ||
19 | */ | ||
20 | |||
21 | #include "../include/common.h" | ||
22 | #include <fcntl.h> | ||
23 | #include <ftw.h> | ||
24 | |||
25 | |||
26 | #define COPY_LIMIT (500 * 1024 *1024) | ||
27 | static int size_limit_reached = 0; | ||
28 | static unsigned file_cnt = 0; | ||
29 | static unsigned size_cnt = 0; | ||
30 | |||
31 | static char *outpath = NULL; | ||
32 | static char *inpath = NULL; | ||
33 | |||
34 | |||
35 | // 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) { | ||
37 | assert(srcname); | ||
38 | assert(destname); | ||
39 | mode &= 07777; | ||
40 | |||
41 | // open source | ||
42 | int src = open(srcname, O_RDONLY); | ||
43 | if (src < 0) { | ||
44 | fprintf(stderr, "Warning: cannot open %s, file not copied\n", srcname); | ||
45 | return; | ||
46 | } | ||
47 | |||
48 | // open destination | ||
49 | int dst = open(destname, O_CREAT|O_WRONLY|O_TRUNC, 0755); | ||
50 | if (dst < 0) { | ||
51 | fprintf(stderr, "Warning fcopy: cannot open %s, file not copied\n", destname); | ||
52 | close(src); | ||
53 | return; | ||
54 | } | ||
55 | |||
56 | // copy | ||
57 | ssize_t len; | ||
58 | static const int BUFLEN = 1024; | ||
59 | unsigned char buf[BUFLEN]; | ||
60 | while ((len = read(src, buf, BUFLEN)) > 0) { | ||
61 | int done = 0; | ||
62 | while (done != len) { | ||
63 | int rv = write(dst, buf + done, len - done); | ||
64 | if (rv == -1) | ||
65 | goto errexit; | ||
66 | done += rv; | ||
67 | } | ||
68 | } | ||
69 | fflush(0); | ||
70 | |||
71 | if (fchown(dst, uid, gid) == -1) | ||
72 | goto errexit; | ||
73 | if (fchmod(dst, mode) == -1) | ||
74 | goto errexit; | ||
75 | |||
76 | close(src); | ||
77 | close(dst); | ||
78 | |||
79 | return; | ||
80 | |||
81 | errexit: | ||
82 | close(src); | ||
83 | close(dst); | ||
84 | unlink(destname); | ||
85 | fprintf(stderr, "Warning fcopy: cannot copy %s\n", destname); | ||
86 | } | ||
87 | |||
88 | |||
89 | |||
90 | // 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) { | ||
92 | assert(fname); | ||
93 | mode &= 07777; | ||
94 | |||
95 | if (mkdir(fname, mode) == -1 || | ||
96 | chmod(fname, mode) == -1) { | ||
97 | fprintf(stderr, "Error fcopy: failed to create %s directory\n", fname); | ||
98 | errExit("mkdir/chmod"); | ||
99 | } | ||
100 | if (chown(fname, uid, gid)) | ||
101 | fprintf(stderr, "Warning fcopy: failed to change ownership of %s\n", fname); | ||
102 | } | ||
103 | |||
104 | void copy_link(const char *target, const char *linkpath, mode_t mode, uid_t uid, gid_t gid) { | ||
105 | char *rp = realpath(target, NULL); | ||
106 | if (rp) { | ||
107 | if (symlink(rp, linkpath) == -1) | ||
108 | goto errout; | ||
109 | free(rp); | ||
110 | } | ||
111 | else | ||
112 | goto errout; | ||
113 | |||
114 | return; | ||
115 | errout: | ||
116 | fprintf(stderr, "Warning fcopy: cannot create symbolic link %s\n", target); | ||
117 | } | ||
118 | |||
119 | static int first = 1; | ||
120 | static int fs_copydir(const char *infname, const struct stat *st, int ftype, struct FTW *sftw) { | ||
121 | (void) st; | ||
122 | (void) sftw; | ||
123 | assert(infname); | ||
124 | assert(*infname != '\0'); | ||
125 | assert(outpath); | ||
126 | assert(*outpath != '\0'); | ||
127 | assert(inpath); | ||
128 | |||
129 | // check size limit | ||
130 | if (size_limit_reached) | ||
131 | return 0; | ||
132 | |||
133 | char *outfname; | ||
134 | if (asprintf(&outfname, "%s%s", outpath, infname + strlen(inpath)) == -1) | ||
135 | errExit("asprintf"); | ||
136 | |||
137 | // don't copy it if we already have the file | ||
138 | struct stat s; | ||
139 | if (stat(outfname, &s) == 0) { | ||
140 | if (first) | ||
141 | first = 0; | ||
142 | else | ||
143 | fprintf(stderr, "Warning fcopy: skipping %s, file already present\n", infname); | ||
144 | free(outfname); | ||
145 | return 0; | ||
146 | } | ||
147 | |||
148 | // extract mode and ownership | ||
149 | if (stat(infname, &s) != 0) { | ||
150 | fprintf(stderr, "Warning fcopy: skipping %s, cannot find inode\n", infname); | ||
151 | free(outfname); | ||
152 | return 0; | ||
153 | } | ||
154 | uid_t uid = s.st_uid; | ||
155 | gid_t gid = s.st_gid; | ||
156 | mode_t mode = s.st_mode; | ||
157 | |||
158 | // recalculate size | ||
159 | if ((s.st_size + size_cnt) > COPY_LIMIT) { | ||
160 | fprintf(stderr, "Error fcopy: size limit of %dMB reached\n", (COPY_LIMIT / 1024) / 1024); | ||
161 | size_limit_reached = 1; | ||
162 | free(outfname); | ||
163 | return 0; | ||
164 | } | ||
165 | |||
166 | file_cnt++; | ||
167 | size_cnt += s.st_size; | ||
168 | |||
169 | if(ftype == FTW_F) { | ||
170 | copy_file(infname, outfname, mode, uid, gid); | ||
171 | } | ||
172 | else if (ftype == FTW_D) { | ||
173 | mkdir_attr(outfname, mode, uid, gid); | ||
174 | } | ||
175 | else if (ftype == FTW_SL) { | ||
176 | copy_link(infname, outfname, mode, uid, gid); | ||
177 | } | ||
178 | |||
179 | return(0); | ||
180 | } | ||
181 | |||
182 | static char *check(const char *src) { | ||
183 | struct stat s; | ||
184 | char *rsrc = realpath(src, NULL); | ||
185 | if (!rsrc || stat(rsrc, &s) == -1) { | ||
186 | fprintf(stderr, "Error fcopy: cannot find %s directory\n", src); | ||
187 | exit(1); | ||
188 | } | ||
189 | |||
190 | // check uid | ||
191 | if (s.st_uid != getuid() || s.st_gid != getgid()) { | ||
192 | fprintf(stderr, "Error fcopy: uid/gid mismatch for %s\n", rsrc); | ||
193 | exit(1); | ||
194 | } | ||
195 | |||
196 | // dir, link, regular file | ||
197 | if (S_ISDIR(s.st_mode) || S_ISREG(s.st_mode) || S_ISLNK(s.st_mode)) { | ||
198 | return rsrc; // normal exit from the function | ||
199 | } | ||
200 | fprintf(stderr, "Error fcopy: invalid directory %s\n", rsrc); | ||
201 | exit(1); | ||
202 | } | ||
203 | |||
204 | static void duplicate_dir(const char *src, const char *dest, struct stat *s) { | ||
205 | (void) s; | ||
206 | char *rsrc = check(src); | ||
207 | char *rdest = check(dest); | ||
208 | inpath = rsrc; | ||
209 | outpath = rdest; | ||
210 | |||
211 | // walk | ||
212 | if(nftw(rsrc, fs_copydir, 1, FTW_PHYS) != 0) { | ||
213 | fprintf(stderr, "Error: unable to copy file\n"); | ||
214 | exit(1); | ||
215 | } | ||
216 | |||
217 | free(rsrc); | ||
218 | free(rdest); | ||
219 | } | ||
220 | |||
221 | static void duplicate_file(const char *src, const char *dest, struct stat *s) { | ||
222 | char *rsrc = check(src); | ||
223 | char *rdest = check(dest); | ||
224 | uid_t uid = s->st_uid; | ||
225 | gid_t gid = s->st_gid; | ||
226 | mode_t mode = s->st_mode; | ||
227 | |||
228 | // build destination file name | ||
229 | char *name; | ||
230 | char *ptr = strrchr(rsrc, '/'); | ||
231 | ptr++; | ||
232 | if (asprintf(&name, "%s/%s", rdest, ptr) == -1) | ||
233 | errExit("asprintf"); | ||
234 | |||
235 | // copy | ||
236 | copy_file(rsrc, name, mode, uid, gid); | ||
237 | |||
238 | free(name); | ||
239 | free(rsrc); | ||
240 | free(rdest); | ||
241 | } | ||
242 | |||
243 | static void duplicate_link(const char *src, const char *dest, struct stat *s) { | ||
244 | char *rsrc = check(src); | ||
245 | char *rdest = check(dest); | ||
246 | uid_t uid = s->st_uid; | ||
247 | gid_t gid = s->st_gid; | ||
248 | mode_t mode = s->st_mode; | ||
249 | |||
250 | // build destination file name | ||
251 | char *name; | ||
252 | char *ptr = strrchr(rsrc, '/'); | ||
253 | ptr++; | ||
254 | if (asprintf(&name, "%s/%s", rdest, ptr) == -1) | ||
255 | errExit("asprintf"); | ||
256 | |||
257 | // copy | ||
258 | copy_link(rsrc, name, mode, uid, gid); | ||
259 | |||
260 | free(name); | ||
261 | free(rsrc); | ||
262 | free(rdest); | ||
263 | } | ||
264 | |||
265 | static void usage(void) { | ||
266 | printf("Usage: fcopy src dest\n"); | ||
267 | printf("Copy src file in dest directory. If src is a directory, copy all the files in\n"); | ||
268 | printf("src recoursively\n"); | ||
269 | } | ||
270 | |||
271 | int main(int argc, char **argv) { | ||
272 | #if 0 | ||
273 | { | ||
274 | //system("cat /proc/self/status"); | ||
275 | int i; | ||
276 | for (i = 0; i < argc; i++) | ||
277 | printf("*%s* ", argv[i]); | ||
278 | printf("\n"); | ||
279 | } | ||
280 | #endif | ||
281 | if (argc != 3) { | ||
282 | fprintf(stderr, "Error fcopy: files missing\n"); | ||
283 | usage(); | ||
284 | exit(1); | ||
285 | } | ||
286 | |||
287 | int i; | ||
288 | int index = 1; | ||
289 | for (i = 1; i < (argc - 2); i++) { | ||
290 | if (strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-?") ==0) { | ||
291 | usage(); | ||
292 | return 0; | ||
293 | } | ||
294 | } | ||
295 | |||
296 | // check the two files; remove ending / | ||
297 | char *src = argv[index]; | ||
298 | int len = strlen(src); | ||
299 | if (src[len - 1] == '/') | ||
300 | src[len - 1] = '\0'; | ||
301 | if (strcspn(src, "\\*&!?\"'<>%^(){}[];,") != (size_t)len) { | ||
302 | fprintf(stderr, "Error fcopy: invalid file name %s\n", src); | ||
303 | exit(1); | ||
304 | } | ||
305 | |||
306 | char *dest = argv[index + 1]; | ||
307 | len = strlen(dest); | ||
308 | if (dest[len - 1] == '/') | ||
309 | dest[len - 1] = '\0'; | ||
310 | if (strcspn(dest, "\\*&!?\"'<>%^(){}[];,~") != (size_t)len) { | ||
311 | fprintf(stderr, "Error fcopy: invalid file name %s\n", dest); | ||
312 | exit(1); | ||
313 | } | ||
314 | |||
315 | |||
316 | // the destination should be a directory; remove ending / | ||
317 | struct stat s; | ||
318 | if (stat(dest, &s) == -1) { | ||
319 | fprintf(stderr, "Error fcopy: cannot find destination directory\n"); | ||
320 | exit(1); | ||
321 | } | ||
322 | if (S_ISDIR(s.st_mode) == -1) { | ||
323 | fprintf(stderr, "Error fcopy: the destination should be a directory\n"); | ||
324 | exit(1); | ||
325 | } | ||
326 | |||
327 | // copy files | ||
328 | if (lstat(src, &s) == -1) { | ||
329 | fprintf(stderr, "Error fcopy: cannot find source file\n"); | ||
330 | exit(1); | ||
331 | } | ||
332 | |||
333 | if (S_ISDIR(s.st_mode)) | ||
334 | duplicate_dir(src, dest, &s); | ||
335 | else if (S_ISREG(s.st_mode)) | ||
336 | duplicate_file(src, dest, &s); | ||
337 | else if (S_ISLNK(s.st_mode)) | ||
338 | duplicate_link(src, dest, &s); | ||
339 | else { | ||
340 | fprintf(stderr, "Error fcopy: source file unsupported\n"); | ||
341 | exit(1); | ||
342 | } | ||
343 | |||
344 | return 0; | ||
345 | } | ||