diff options
Diffstat (limited to 'src/fcopy/main.c')
-rw-r--r-- | src/fcopy/main.c | 340 |
1 files changed, 340 insertions, 0 deletions
diff --git a/src/fcopy/main.c b/src/fcopy/main.c new file mode 100644 index 000000000..b1e2813db --- /dev/null +++ b/src/fcopy/main.c | |||
@@ -0,0 +1,340 @@ | |||
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 | (void) mode; | ||
106 | (void) uid; | ||
107 | (void) gid; | ||
108 | char *rp = realpath(target, NULL); | ||
109 | if (rp) { | ||
110 | if (symlink(rp, linkpath) == -1) | ||
111 | goto errout; | ||
112 | free(rp); | ||
113 | } | ||
114 | else | ||
115 | goto errout; | ||
116 | |||
117 | return; | ||
118 | errout: | ||
119 | fprintf(stderr, "Warning fcopy: cannot create symbolic link %s\n", target); | ||
120 | } | ||
121 | |||
122 | static int first = 1; | ||
123 | static int fs_copydir(const char *infname, const struct stat *st, int ftype, struct FTW *sftw) { | ||
124 | (void) st; | ||
125 | (void) sftw; | ||
126 | assert(infname); | ||
127 | assert(*infname != '\0'); | ||
128 | assert(outpath); | ||
129 | assert(*outpath != '\0'); | ||
130 | assert(inpath); | ||
131 | |||
132 | // check size limit | ||
133 | if (size_limit_reached) | ||
134 | return 0; | ||
135 | |||
136 | |||
137 | char *outfname; | ||
138 | if (asprintf(&outfname, "%s%s", outpath, infname + strlen(inpath)) == -1) | ||
139 | errExit("asprintf"); | ||
140 | |||
141 | //printf("outpaht %s\n", outpath); | ||
142 | //printf("inpath %s\n", inpath); | ||
143 | //printf("infname %s\n", infname); | ||
144 | //printf("outfname %s\n\n", outfname); | ||
145 | |||
146 | // don't copy it if we already have the file | ||
147 | struct stat s; | ||
148 | if (stat(outfname, &s) == 0) { | ||
149 | if (first) | ||
150 | first = 0; | ||
151 | else | ||
152 | fprintf(stderr, "Warning fcopy: skipping %s, file already present\n", infname); | ||
153 | free(outfname); | ||
154 | return 0; | ||
155 | } | ||
156 | |||
157 | // extract mode and ownership | ||
158 | if (stat(infname, &s) != 0) { | ||
159 | fprintf(stderr, "Warning fcopy: skipping %s, cannot find inode\n", infname); | ||
160 | free(outfname); | ||
161 | return 0; | ||
162 | } | ||
163 | uid_t uid = s.st_uid; | ||
164 | gid_t gid = s.st_gid; | ||
165 | mode_t mode = s.st_mode; | ||
166 | |||
167 | // recalculate size | ||
168 | if ((s.st_size + size_cnt) > COPY_LIMIT) { | ||
169 | fprintf(stderr, "Error fcopy: size limit of %dMB reached\n", (COPY_LIMIT / 1024) / 1024); | ||
170 | size_limit_reached = 1; | ||
171 | free(outfname); | ||
172 | return 0; | ||
173 | } | ||
174 | |||
175 | file_cnt++; | ||
176 | size_cnt += s.st_size; | ||
177 | |||
178 | if(ftype == FTW_F) { | ||
179 | copy_file(infname, outfname, mode, uid, gid); | ||
180 | } | ||
181 | else if (ftype == FTW_D) { | ||
182 | mkdir_attr(outfname, mode, uid, gid); | ||
183 | } | ||
184 | else if (ftype == FTW_SL) { | ||
185 | copy_link(infname, outfname, mode, uid, gid); | ||
186 | } | ||
187 | |||
188 | return(0); | ||
189 | } | ||
190 | |||
191 | static char *check(const char *src) { | ||
192 | struct stat s; | ||
193 | char *rsrc = realpath(src, NULL); | ||
194 | if (!rsrc || stat(rsrc, &s) == -1) | ||
195 | goto errexit; | ||
196 | |||
197 | // check uid | ||
198 | if (s.st_uid != getuid() || s.st_gid != getgid()) | ||
199 | goto errexit; | ||
200 | |||
201 | // dir, link, regular file | ||
202 | if (S_ISDIR(s.st_mode) || S_ISREG(s.st_mode) || S_ISLNK(s.st_mode)) | ||
203 | return rsrc; // normal exit from the function | ||
204 | |||
205 | errexit: | ||
206 | fprintf(stderr, "Error fcopy: invalid file %s\n", src); | ||
207 | exit(1); | ||
208 | } | ||
209 | |||
210 | static void duplicate_dir(const char *src, const char *dest, struct stat *s) { | ||
211 | (void) s; | ||
212 | char *rsrc = check(src); | ||
213 | char *rdest = check(dest); | ||
214 | inpath = rsrc; | ||
215 | outpath = rdest; | ||
216 | |||
217 | // walk | ||
218 | if(nftw(rsrc, fs_copydir, 1, FTW_PHYS) != 0) { | ||
219 | fprintf(stderr, "Error: unable to copy file\n"); | ||
220 | exit(1); | ||
221 | } | ||
222 | |||
223 | free(rsrc); | ||
224 | free(rdest); | ||
225 | } | ||
226 | |||
227 | static void duplicate_file(const char *src, const char *dest, struct stat *s) { | ||
228 | char *rsrc = check(src); | ||
229 | char *rdest = check(dest); | ||
230 | uid_t uid = s->st_uid; | ||
231 | gid_t gid = s->st_gid; | ||
232 | mode_t mode = s->st_mode; | ||
233 | |||
234 | // build destination file name | ||
235 | char *name; | ||
236 | char *ptr = strrchr(rsrc, '/'); | ||
237 | ptr++; | ||
238 | if (asprintf(&name, "%s/%s", rdest, ptr) == -1) | ||
239 | errExit("asprintf"); | ||
240 | |||
241 | // copy | ||
242 | copy_file(rsrc, name, mode, uid, gid); | ||
243 | |||
244 | free(name); | ||
245 | free(rsrc); | ||
246 | free(rdest); | ||
247 | } | ||
248 | |||
249 | 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 | ||
251 | char *rdest = check(dest); | ||
252 | uid_t uid = s->st_uid; | ||
253 | gid_t gid = s->st_gid; | ||
254 | mode_t mode = s->st_mode; | ||
255 | |||
256 | // build destination file name | ||
257 | char *name; | ||
258 | // char *ptr = strrchr(rsrc, '/'); | ||
259 | char *ptr = strrchr(src, '/'); | ||
260 | ptr++; | ||
261 | if (asprintf(&name, "%s/%s", rdest, ptr) == -1) | ||
262 | errExit("asprintf"); | ||
263 | |||
264 | // copy | ||
265 | copy_link(rsrc, name, mode, uid, gid); | ||
266 | |||
267 | free(name); | ||
268 | free(rsrc); | ||
269 | free(rdest); | ||
270 | } | ||
271 | |||
272 | static void usage(void) { | ||
273 | printf("Usage: fcopy src dest\n"); | ||
274 | printf("Copy src file in dest directory. If src is a directory, copy all the files in\n"); | ||
275 | printf("src recoursively. If the destination directory does not exist, it will be created.\n"); | ||
276 | } | ||
277 | |||
278 | int main(int argc, char **argv) { | ||
279 | #if 0 | ||
280 | { | ||
281 | //system("cat /proc/self/status"); | ||
282 | int i; | ||
283 | for (i = 0; i < argc; i++) | ||
284 | printf("*%s* ", argv[i]); | ||
285 | printf("\n"); | ||
286 | } | ||
287 | #endif | ||
288 | if (argc != 3) { | ||
289 | fprintf(stderr, "Error fcopy: files missing\n"); | ||
290 | usage(); | ||
291 | exit(1); | ||
292 | } | ||
293 | |||
294 | // check the two files; remove ending / | ||
295 | char *src = argv[1]; | ||
296 | int len = strlen(src); | ||
297 | if (src[len - 1] == '/') | ||
298 | src[len - 1] = '\0'; | ||
299 | if (strcspn(src, "\\*&!?\"'<>%^(){}[];,") != (size_t)len) { | ||
300 | fprintf(stderr, "Error fcopy: invalid file name %s\n", src); | ||
301 | exit(1); | ||
302 | } | ||
303 | |||
304 | char *dest = argv[2]; | ||
305 | len = strlen(dest); | ||
306 | if (dest[len - 1] == '/') | ||
307 | dest[len - 1] = '\0'; | ||
308 | if (strcspn(dest, "\\*&!?\"'<>%^(){}[];,~") != (size_t)len) { | ||
309 | fprintf(stderr, "Error fcopy: invalid file name %s\n", dest); | ||
310 | exit(1); | ||
311 | } | ||
312 | |||
313 | |||
314 | // the destination should be a directory; | ||
315 | struct stat s; | ||
316 | if (stat(dest, &s) == -1 || | ||
317 | !S_ISDIR(s.st_mode)) { | ||
318 | fprintf(stderr, "Error fcopy: invalid destination directory\n"); | ||
319 | exit(1); | ||
320 | } | ||
321 | |||
322 | // copy files | ||
323 | if (lstat(src, &s) == -1) { | ||
324 | fprintf(stderr, "Error fcopy: cannot find source file\n"); | ||
325 | exit(1); | ||
326 | } | ||
327 | |||
328 | if (S_ISDIR(s.st_mode)) | ||
329 | duplicate_dir(src, dest, &s); | ||
330 | else if (S_ISREG(s.st_mode)) | ||
331 | duplicate_file(src, dest, &s); | ||
332 | else if (S_ISLNK(s.st_mode)) | ||
333 | duplicate_link(src, dest, &s); | ||
334 | else { | ||
335 | fprintf(stderr, "Error fcopy: source file unsupported\n"); | ||
336 | exit(1); | ||
337 | } | ||
338 | |||
339 | return 0; | ||
340 | } | ||