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