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