1 /*
2 FUSE: Filesystem in Userspace
3 Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
4
5 This program can be distributed under the terms of the GNU GPL.
6 See the file COPYING.
7 */
8 /* This program does the mounting and unmounting of FUSE filesystems */
9
10 #include <config.h>
11
12 #include "mount_util.h"
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <ctype.h>
17 #include <unistd.h>
18 #include <getopt.h>
19 #include <errno.h>
20 #include <fcntl.h>
21 #include <pwd.h>
22 #include <paths.h>
23
24 #ifdef __SOLARIS__
25 #include <sys/mnttab.h>
26 #else /* __SOLARIS__ */
27 #include <grp.h>
28 #include <mntent.h>
29 #include <sys/fsuid.h>
30 #endif /* __SOLARIS__ */
31
32 #include <sys/wait.h>
33 #include <sys/stat.h>
34 #include <sys/mount.h>
35 #include <sys/socket.h>
36 #include <sys/utsname.h>
37
38 #define FUSE_DEV_NEW "/dev/fuse"
39
40 #ifndef MS_DIRSYNC
41 #define MS_DIRSYNC 128
42 #endif
43
44 static const char *progname = "ntfs-3g-mount";
45
46 static int mount_max = 1000;
47
48 int drop_privs(void);
49 int restore_privs(void);
50
51 #ifdef __SOLARIS__
52
53 /*
54 * fusermount is not implemented in fuse-lite for Solaris,
55 * only the minimal functions are provided.
56 */
57
58 /*
59 * Solaris doesn't have setfsuid/setfsgid.
60 * This doesn't really matter anyway as this program shouldn't be made
61 * suid on Solaris. It should instead be used via a profile with the
62 * sys_mount privilege.
63 */
64
drop_privs(void)65 int drop_privs(void)
66 {
67 return (0);
68 }
69
restore_privs(void)70 int restore_privs(void)
71 {
72 return (0);
73 }
74
75 #else /* __SOLARIS__ */
76
get_user_name(void)77 static const char *get_user_name(void)
78 {
79 struct passwd *pw = getpwuid(getuid());
80 if (pw != NULL && pw->pw_name != NULL)
81 return pw->pw_name;
82 else {
83 fprintf(stderr, "%s: could not determine username\n", progname);
84 return NULL;
85 }
86 }
87
drop_privs(void)88 int drop_privs(void)
89 {
90 if (!getegid()) {
91
92 gid_t new_gid = getgid();
93
94 if (setresgid(-1, new_gid, getegid()) < 0) {
95 perror("priv drop: setresgid failed");
96 return -1;
97 }
98 if (getegid() != new_gid){
99 perror("dropping group privilege failed");
100 return -1;
101 }
102 }
103
104 if (!geteuid()) {
105
106 uid_t new_uid = getuid();
107
108 if (setresuid(-1, new_uid, geteuid()) < 0) {
109 perror("priv drop: setresuid failed");
110 return -1;
111 }
112 if (geteuid() != new_uid){
113 perror("dropping user privilege failed");
114 return -1;
115 }
116 }
117
118 return 0;
119 }
120
restore_privs(void)121 int restore_privs(void)
122 {
123 if (geteuid()) {
124
125 uid_t ruid, euid, suid;
126
127 if (getresuid(&ruid, &euid, &suid) < 0) {
128 perror("priv restore: getresuid failed");
129 return -1;
130 }
131 if (setresuid(-1, suid, -1) < 0) {
132 perror("priv restore: setresuid failed");
133 return -1;
134 }
135 if (geteuid() != suid) {
136 perror("restoring privilege failed");
137 return -1;
138 }
139 }
140
141 if (getegid()) {
142
143 gid_t rgid, egid, sgid;
144
145 if (getresgid(&rgid, &egid, &sgid) < 0) {
146 perror("priv restore: getresgid failed");
147 return -1;
148 }
149 if (setresgid(-1, sgid, -1) < 0) {
150 perror("priv restore: setresgid failed");
151 return -1;
152 }
153 if (getegid() != sgid){
154 perror("restoring group privilege failed");
155 return -1;
156 }
157 }
158
159 return 0;
160 }
161
162 #ifndef IGNORE_MTAB
add_mount(const char * source,const char * mnt,const char * type,const char * opts)163 static int add_mount(const char *source, const char *mnt, const char *type,
164 const char *opts)
165 {
166 return fuse_mnt_add_mount(progname, source, mnt, type, opts);
167 }
168
count_fuse_fs(void)169 static int count_fuse_fs(void)
170 {
171 struct mntent *entp;
172 int count = 0;
173 const char *mtab = _PATH_MOUNTED;
174 FILE *fp = setmntent(mtab, "r");
175 if (fp == NULL) {
176 fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab,
177 strerror(errno));
178 return -1;
179 }
180 while ((entp = getmntent(fp)) != NULL) {
181 if (strcmp(entp->mnt_type, "fuse") == 0 ||
182 strncmp(entp->mnt_type, "fuse.", 5) == 0)
183 count ++;
184 }
185 endmntent(fp);
186 return count;
187 }
188
189
190 #else /* IGNORE_MTAB */
count_fuse_fs()191 static int count_fuse_fs()
192 {
193 return 0;
194 }
195
add_mount(const char * source,const char * mnt,const char * type,const char * opts)196 static int add_mount(const char *source, const char *mnt, const char *type,
197 const char *opts)
198 {
199 (void) source;
200 (void) mnt;
201 (void) type;
202 (void) opts;
203 return 0;
204 }
205 #endif /* IGNORE_MTAB */
206
begins_with(const char * s,const char * beg)207 static int begins_with(const char *s, const char *beg)
208 {
209 if (strncmp(s, beg, strlen(beg)) == 0)
210 return 1;
211 else
212 return 0;
213 }
214
215 struct mount_flags {
216 const char *opt;
217 unsigned long flag;
218 int on;
219 int safe;
220 };
221
222 static struct mount_flags mount_flags[] = {
223 {"rw", MS_RDONLY, 0, 1},
224 {"ro", MS_RDONLY, 1, 1},
225 {"suid", MS_NOSUID, 0, 0},
226 {"nosuid", MS_NOSUID, 1, 1},
227 {"dev", MS_NODEV, 0, 0},
228 {"nodev", MS_NODEV, 1, 1},
229 {"exec", MS_NOEXEC, 0, 1},
230 {"noexec", MS_NOEXEC, 1, 1},
231 {"async", MS_SYNCHRONOUS, 0, 1},
232 {"sync", MS_SYNCHRONOUS, 1, 1},
233 {"atime", MS_NOATIME, 0, 1},
234 {"noatime", MS_NOATIME, 1, 1},
235 {"dirsync", MS_DIRSYNC, 1, 1},
236 {NULL, 0, 0, 0}
237 };
238
find_mount_flag(const char * s,unsigned len,int * on,int * flag)239 static int find_mount_flag(const char *s, unsigned len, int *on, int *flag)
240 {
241 int i;
242
243 for (i = 0; mount_flags[i].opt != NULL; i++) {
244 const char *opt = mount_flags[i].opt;
245 if (strlen(opt) == len && strncmp(opt, s, len) == 0) {
246 *on = mount_flags[i].on;
247 *flag = mount_flags[i].flag;
248 if (!mount_flags[i].safe && getuid() != 0) {
249 *flag = 0;
250 fprintf(stderr, "%s: unsafe option '%s' ignored\n",
251 progname, opt);
252 }
253 return 1;
254 }
255 }
256 return 0;
257 }
258
add_option(char ** optsp,const char * opt,unsigned expand)259 static int add_option(char **optsp, const char *opt, unsigned expand)
260 {
261 char *newopts;
262 if (*optsp == NULL)
263 newopts = strdup(opt);
264 else {
265 unsigned oldsize = strlen(*optsp);
266 unsigned newsize = oldsize + 1 + strlen(opt) + expand + 1;
267 newopts = (char *) realloc(*optsp, newsize);
268 if (newopts)
269 sprintf(newopts + oldsize, ",%s", opt);
270 }
271 if (newopts == NULL) {
272 fprintf(stderr, "%s: failed to allocate memory\n", progname);
273 return -1;
274 }
275 *optsp = newopts;
276 return 0;
277 }
278
get_mnt_opts(int flags,char * opts,char ** mnt_optsp)279 static int get_mnt_opts(int flags, char *opts, char **mnt_optsp)
280 {
281 int i;
282 int l;
283
284 if (!(flags & MS_RDONLY) && add_option(mnt_optsp, "rw", 0) == -1)
285 return -1;
286
287 for (i = 0; mount_flags[i].opt != NULL; i++) {
288 if (mount_flags[i].on && (flags & mount_flags[i].flag) &&
289 add_option(mnt_optsp, mount_flags[i].opt, 0) == -1)
290 return -1;
291 }
292
293 if (add_option(mnt_optsp, opts, 0) == -1)
294 return -1;
295 /* remove comma from end of opts*/
296 l = strlen(*mnt_optsp);
297 if ((*mnt_optsp)[l-1] == ',')
298 (*mnt_optsp)[l-1] = '\0';
299 if (getuid() != 0) {
300 const char *user = get_user_name();
301 if (user == NULL)
302 return -1;
303
304 if (add_option(mnt_optsp, "user=", strlen(user)) == -1)
305 return -1;
306 strcat(*mnt_optsp, user);
307 }
308 return 0;
309 }
310
opt_eq(const char * s,unsigned len,const char * opt)311 static int opt_eq(const char *s, unsigned len, const char *opt)
312 {
313 if(strlen(opt) == len && strncmp(s, opt, len) == 0)
314 return 1;
315 else
316 return 0;
317 }
318
get_string_opt(const char * s,unsigned len,const char * opt,char ** val)319 static int get_string_opt(const char *s, unsigned len, const char *opt,
320 char **val)
321 {
322 unsigned opt_len = strlen(opt);
323
324 if (*val)
325 free(*val);
326 *val = (char *) malloc(len - opt_len + 1);
327 if (!*val) {
328 fprintf(stderr, "%s: failed to allocate memory\n", progname);
329 return 0;
330 }
331
332 memcpy(*val, s + opt_len, len - opt_len);
333 (*val)[len - opt_len] = '\0';
334 return 1;
335 }
336
do_mount(const char * mnt,char ** typep,mode_t rootmode,int fd,const char * opts,const char * dev,char ** sourcep,char ** mnt_optsp)337 static int do_mount(const char *mnt, char **typep, mode_t rootmode,
338 int fd, const char *opts, const char *dev, char **sourcep,
339 char **mnt_optsp)
340 {
341 int res;
342 int flags = MS_NOSUID | MS_NODEV;
343 char *optbuf;
344 char *mnt_opts = NULL;
345 const char *s;
346 char *d;
347 char *fsname = NULL;
348 char *source = NULL;
349 char *type = NULL;
350 int blkdev = 0;
351
352 optbuf = (char *) malloc(strlen(opts) + 128);
353 if (!optbuf) {
354 fprintf(stderr, "%s: failed to allocate memory\n", progname);
355 return -1;
356 }
357
358 for (s = opts, d = optbuf; *s;) {
359 unsigned len;
360 const char *fsname_str = "fsname=";
361 for (len = 0; s[len] && s[len] != ','; len++);
362 if (begins_with(s, fsname_str)) {
363 if (!get_string_opt(s, len, fsname_str, &fsname))
364 goto err;
365 } else if (opt_eq(s, len, "blkdev")) {
366 blkdev = 1;
367 } else if (!begins_with(s, "fd=") &&
368 !begins_with(s, "rootmode=") &&
369 !begins_with(s, "user_id=") &&
370 !begins_with(s, "group_id=")) {
371 int on;
372 int flag;
373 int skip_option = 0;
374 if (opt_eq(s, len, "large_read")) {
375 struct utsname utsname;
376 unsigned kmaj, kmin;
377 res = uname(&utsname);
378 if (res == 0 &&
379 sscanf(utsname.release, "%u.%u", &kmaj, &kmin) == 2 &&
380 (kmaj > 2 || (kmaj == 2 && kmin > 4))) {
381 fprintf(stderr, "%s: note: 'large_read' mount option is "
382 "deprecated for %i.%i kernels\n", progname, kmaj, kmin);
383 skip_option = 1;
384 }
385 }
386 if (!skip_option) {
387 if (find_mount_flag(s, len, &on, &flag)) {
388 if (on)
389 flags |= flag;
390 else
391 flags &= ~flag;
392 } else {
393 memcpy(d, s, len);
394 d += len;
395 *d++ = ',';
396 }
397 }
398 }
399 s += len;
400 if (*s)
401 s++;
402 }
403 *d = '\0';
404 res = get_mnt_opts(flags, optbuf, &mnt_opts);
405 if (res == -1)
406 goto err;
407
408 sprintf(d, "fd=%i,rootmode=%o,user_id=%i,group_id=%i",
409 fd, rootmode, getuid(), getgid());
410
411 source = malloc((fsname ? strlen(fsname) : 0) + strlen(dev) + 32);
412
413 type = malloc(32);
414 if (!type || !source) {
415 fprintf(stderr, "%s: failed to allocate memory\n", progname);
416 goto err;
417 }
418
419 strcpy(type, blkdev ? "fuseblk" : "fuse");
420
421 if (fsname)
422 strcpy(source, fsname);
423 else
424 strcpy(source, dev);
425
426 if (restore_privs())
427 goto err;
428
429 res = mount(source, mnt, type, flags, optbuf);
430 if (res == -1 && errno == EINVAL) {
431 /* It could be an old version not supporting group_id */
432 sprintf(d, "fd=%i,rootmode=%o,user_id=%i", fd, rootmode, getuid());
433 res = mount(source, mnt, type, flags, optbuf);
434 }
435
436 if (drop_privs())
437 goto err;
438
439 if (res == -1) {
440 int errno_save = errno;
441 if (blkdev && errno == ENODEV && !fuse_mnt_check_fuseblk())
442 fprintf(stderr, "%s: 'fuseblk' support missing\n", progname);
443 else {
444 fprintf(stderr, "%s: mount failed: %s\n", progname, strerror(errno_save));
445 if (errno_save == EPERM)
446 fprintf(stderr, "User doesn't have privilege to mount. "
447 "For more information\nplease see: "
448 "http://tuxera.com/community/ntfs-3g-faq/#unprivileged\n");
449 }
450 goto err;
451 } else {
452 *sourcep = source;
453 *typep = type;
454 *mnt_optsp = mnt_opts;
455 }
456 out:
457 free(fsname);
458 free(optbuf);
459 return res;
460 err:
461 free(source);
462 free(type);
463 free(mnt_opts);
464 res = -1;
465 goto out;
466 }
467
check_perm(const char ** mntp,struct stat * stbuf,int * currdir_fd,int * mountpoint_fd)468 static int check_perm(const char **mntp, struct stat *stbuf, int *currdir_fd,
469 int *mountpoint_fd)
470 {
471 int res;
472 const char *mnt = *mntp;
473 const char *origmnt = mnt;
474
475 res = stat(mnt, stbuf);
476 if (res == -1) {
477 fprintf(stderr, "%s: failed to access mountpoint %s: %s\n",
478 progname, mnt, strerror(errno));
479 return -1;
480 }
481
482 /* No permission checking is done for root */
483 if (getuid() == 0)
484 return 0;
485
486 if (S_ISDIR(stbuf->st_mode)) {
487 *currdir_fd = open(".", O_RDONLY);
488 if (*currdir_fd == -1) {
489 fprintf(stderr, "%s: failed to open current directory: %s\n",
490 progname, strerror(errno));
491 return -1;
492 }
493 res = chdir(mnt);
494 if (res == -1) {
495 fprintf(stderr, "%s: failed to chdir to mountpoint: %s\n",
496 progname, strerror(errno));
497 return -1;
498 }
499 mnt = *mntp = ".";
500 res = lstat(mnt, stbuf);
501 if (res == -1) {
502 fprintf(stderr, "%s: failed to access mountpoint %s: %s\n",
503 progname, origmnt, strerror(errno));
504 return -1;
505 }
506
507 if ((stbuf->st_mode & S_ISVTX) && stbuf->st_uid != getuid()) {
508 fprintf(stderr, "%s: mountpoint %s not owned by user\n",
509 progname, origmnt);
510 return -1;
511 }
512
513 res = access(mnt, W_OK);
514 if (res == -1) {
515 fprintf(stderr, "%s: user has no write access to mountpoint %s\n",
516 progname, origmnt);
517 return -1;
518 }
519 } else if (S_ISREG(stbuf->st_mode)) {
520 static char procfile[256];
521 *mountpoint_fd = open(mnt, O_WRONLY);
522 if (*mountpoint_fd == -1) {
523 fprintf(stderr, "%s: failed to open %s: %s\n", progname, mnt,
524 strerror(errno));
525 return -1;
526 }
527 res = fstat(*mountpoint_fd, stbuf);
528 if (res == -1) {
529 fprintf(stderr, "%s: failed to access mountpoint %s: %s\n",
530 progname, mnt, strerror(errno));
531 return -1;
532 }
533 if (!S_ISREG(stbuf->st_mode)) {
534 fprintf(stderr, "%s: mountpoint %s is no longer a regular file\n",
535 progname, mnt);
536 return -1;
537 }
538
539 sprintf(procfile, "/proc/self/fd/%i", *mountpoint_fd);
540 *mntp = procfile;
541 } else {
542 fprintf(stderr,
543 "%s: mountpoint %s is not a directory or a regular file\n",
544 progname, mnt);
545 return -1;
546 }
547
548
549 return 0;
550 }
551
try_open(const char * dev,char ** devp)552 static int try_open(const char *dev, char **devp)
553 {
554 int fd;
555
556 if (restore_privs())
557 return -1;
558 fd = open(dev, O_RDWR);
559 if (drop_privs())
560 return -1;
561 if (fd != -1) {
562 *devp = strdup(dev);
563 if (*devp == NULL) {
564 fprintf(stderr, "%s: failed to allocate memory\n", progname);
565 close(fd);
566 fd = -1;
567 }
568 } else if (errno == ENODEV ||
569 errno == ENOENT) /* check for ENOENT too, for the udev case */
570 return -2;
571 else {
572 fprintf(stderr, "%s: failed to open %s: %s\n", progname, dev,
573 strerror(errno));
574 }
575 return fd;
576 }
577
open_fuse_device(char ** devp)578 static int open_fuse_device(char **devp)
579 {
580 int fd;
581
582 fd = try_open(FUSE_DEV_NEW, devp);
583 if (fd >= -1)
584 return fd;
585
586 fprintf(stderr, "%s: fuse device is missing, try 'modprobe fuse' as root\n",
587 progname);
588
589 return -1;
590 }
591
592
mount_fuse(const char * mnt,const char * opts)593 static int mount_fuse(const char *mnt, const char *opts)
594 {
595 int res;
596 int fd;
597 char *dev;
598 struct stat stbuf;
599 char *type = NULL;
600 char *source = NULL;
601 char *mnt_opts = NULL;
602 const char *real_mnt = mnt;
603 int currdir_fd = -1;
604 int mountpoint_fd = -1;
605
606 fd = open_fuse_device(&dev);
607 if (fd == -1)
608 return -1;
609
610 if (getuid() != 0 && mount_max != -1) {
611 if (count_fuse_fs() >= mount_max) {
612 fprintf(stderr, "%s: too many mounted FUSE filesystems (%d+)\n",
613 progname, mount_max);
614 goto err;
615 }
616 }
617
618 res = check_perm(&real_mnt, &stbuf, &currdir_fd, &mountpoint_fd);
619 if (res != -1)
620 res = do_mount(real_mnt, &type, stbuf.st_mode & S_IFMT, fd, opts, dev,
621 &source, &mnt_opts);
622
623 if (currdir_fd != -1) {
624 __attribute__((unused))int ignored_fchdir_status =
625 fchdir(currdir_fd);
626 close(currdir_fd);
627 }
628 if (mountpoint_fd != -1)
629 close(mountpoint_fd);
630
631 if (res == -1)
632 goto err;
633
634 if (restore_privs())
635 goto err;
636
637 if (geteuid() == 0) {
638
639 if (setgroups(0, NULL) == -1) {
640 perror("priv drop: setgroups failed");
641 goto err;
642 }
643
644 res = add_mount(source, mnt, type, mnt_opts);
645 if (res == -1) {
646 umount2(mnt, 2); /* lazy umount */
647 drop_privs();
648 goto err;
649 }
650 }
651
652 if (drop_privs())
653 goto err;
654 out:
655 free(source);
656 free(type);
657 free(mnt_opts);
658 free(dev);
659
660 return fd;
661 err:
662 close(fd);
663 fd = -1;
664 goto out;
665 }
666
fusermount(int unmount,int quiet,int lazy,const char * opts,const char * origmnt)667 int fusermount(int unmount, int quiet, int lazy, const char *opts,
668 const char *origmnt)
669 {
670 int res = -1;
671 char *mnt;
672 mode_t old_umask;
673
674 mnt = fuse_mnt_resolve_path(progname, origmnt);
675 if (mnt == NULL)
676 return -1;
677
678 old_umask = umask(033);
679
680 if (unmount) {
681
682 if (restore_privs())
683 goto out;
684
685 if (geteuid() == 0)
686 res = fuse_mnt_umount(progname, mnt, lazy);
687 else {
688 res = umount2(mnt, lazy ? 2 : 0);
689 if (res == -1 && !quiet)
690 fprintf(stderr, "%s: failed to unmount %s: %s\n", progname,
691 mnt, strerror(errno));
692 }
693
694 if (drop_privs())
695 res = -1;
696
697 } else
698 res = mount_fuse(mnt, opts);
699 out:
700 umask(old_umask);
701 free(mnt);
702 return res;
703 }
704
705 #endif /* __SOLARIS__ */
706