/* FUSE: Filesystem in Userspace Copyright (C) 2001-2007 Miklos Szeredi This program can be distributed under the terms of the GNU GPL. See the file COPYING. */ /* This program does the mounting and unmounting of FUSE filesystems */ #include #include "mount_util.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define FUSE_DEV_NEW "/dev/fuse" #ifndef MS_DIRSYNC #define MS_DIRSYNC 128 #endif static const char *progname = "ntfs-3g-mount"; static int mount_max = 1000; int drop_privs(void); int restore_privs(void); static const char *get_user_name(void) { struct passwd *pw = getpwuid(getuid()); if (pw != NULL && pw->pw_name != NULL) return pw->pw_name; else { fprintf(stderr, "%s: could not determine username\n", progname); return NULL; } } int drop_privs(void) { if (!getegid()) { gid_t new_gid = getgid(); if (setresgid(-1, new_gid, getegid()) < 0) { perror("priv drop: setresgid failed"); return -1; } if (getegid() != new_gid){ perror("dropping group privilege failed"); return -1; } } if (!geteuid()) { uid_t new_uid = getuid(); if (setresuid(-1, new_uid, geteuid()) < 0) { perror("priv drop: setresuid failed"); return -1; } if (geteuid() != new_uid){ perror("dropping user privilege failed"); return -1; } } return 0; } int restore_privs(void) { if (geteuid()) { uid_t ruid, euid, suid; if (getresuid(&ruid, &euid, &suid) < 0) { perror("priv restore: getresuid failed"); return -1; } if (setresuid(-1, suid, -1) < 0) { perror("priv restore: setresuid failed"); return -1; } if (geteuid() != suid) { perror("restoring privilege failed"); return -1; } } if (getegid()) { gid_t rgid, egid, sgid; if (getresgid(&rgid, &egid, &sgid) < 0) { perror("priv restore: getresgid failed"); return -1; } if (setresgid(-1, sgid, -1) < 0) { perror("priv restore: setresgid failed"); return -1; } if (getegid() != sgid){ perror("restoring group privilege failed"); return -1; } } return 0; } #ifndef IGNORE_MTAB static int add_mount(const char *source, const char *mnt, const char *type, const char *opts) { return fuse_mnt_add_mount(progname, source, mnt, type, opts); } static int count_fuse_fs(void) { struct mntent *entp; int count = 0; const char *mtab = _PATH_MOUNTED; FILE *fp = setmntent(mtab, "r"); if (fp == NULL) { fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab, strerror(errno)); return -1; } while ((entp = getmntent(fp)) != NULL) { if (strcmp(entp->mnt_type, "fuse") == 0 || strncmp(entp->mnt_type, "fuse.", 5) == 0) count ++; } endmntent(fp); return count; } #else /* IGNORE_MTAB */ static int count_fuse_fs() { return 0; } static int add_mount(const char *source, const char *mnt, const char *type, const char *opts) { (void) source; (void) mnt; (void) type; (void) opts; return 0; } #endif /* IGNORE_MTAB */ static int begins_with(const char *s, const char *beg) { if (strncmp(s, beg, strlen(beg)) == 0) return 1; else return 0; } struct mount_flags { const char *opt; unsigned long flag; int on; int safe; }; static struct mount_flags mount_flags[] = { {"rw", MS_RDONLY, 0, 1}, {"ro", MS_RDONLY, 1, 1}, {"suid", MS_NOSUID, 0, 0}, {"nosuid", MS_NOSUID, 1, 1}, {"dev", MS_NODEV, 0, 0}, {"nodev", MS_NODEV, 1, 1}, {"exec", MS_NOEXEC, 0, 1}, {"noexec", MS_NOEXEC, 1, 1}, {"async", MS_SYNCHRONOUS, 0, 1}, {"sync", MS_SYNCHRONOUS, 1, 1}, {"atime", MS_NOATIME, 0, 1}, {"noatime", MS_NOATIME, 1, 1}, {"dirsync", MS_DIRSYNC, 1, 1}, {NULL, 0, 0, 0} }; static int find_mount_flag(const char *s, unsigned len, int *on, int *flag) { int i; for (i = 0; mount_flags[i].opt != NULL; i++) { const char *opt = mount_flags[i].opt; if (strlen(opt) == len && strncmp(opt, s, len) == 0) { *on = mount_flags[i].on; *flag = mount_flags[i].flag; if (!mount_flags[i].safe && getuid() != 0) { *flag = 0; fprintf(stderr, "%s: unsafe option '%s' ignored\n", progname, opt); } return 1; } } return 0; } static int add_option(char **optsp, const char *opt, unsigned expand) { char *newopts; if (*optsp == NULL) newopts = strdup(opt); else { unsigned oldsize = strlen(*optsp); unsigned newsize = oldsize + 1 + strlen(opt) + expand + 1; newopts = (char *) realloc(*optsp, newsize); if (newopts) sprintf(newopts + oldsize, ",%s", opt); } if (newopts == NULL) { fprintf(stderr, "%s: failed to allocate memory\n", progname); return -1; } *optsp = newopts; return 0; } static int get_mnt_opts(int flags, char *opts, char **mnt_optsp) { int i; int l; if (!(flags & MS_RDONLY) && add_option(mnt_optsp, "rw", 0) == -1) return -1; for (i = 0; mount_flags[i].opt != NULL; i++) { if (mount_flags[i].on && (flags & mount_flags[i].flag) && add_option(mnt_optsp, mount_flags[i].opt, 0) == -1) return -1; } if (add_option(mnt_optsp, opts, 0) == -1) return -1; /* remove comma from end of opts*/ l = strlen(*mnt_optsp); if ((*mnt_optsp)[l-1] == ',') (*mnt_optsp)[l-1] = '\0'; if (getuid() != 0) { const char *user = get_user_name(); if (user == NULL) return -1; if (add_option(mnt_optsp, "user=", strlen(user)) == -1) return -1; strcat(*mnt_optsp, user); } return 0; } static int opt_eq(const char *s, unsigned len, const char *opt) { if(strlen(opt) == len && strncmp(s, opt, len) == 0) return 1; else return 0; } static int get_string_opt(const char *s, unsigned len, const char *opt, char **val) { unsigned opt_len = strlen(opt); if (*val) free(*val); *val = (char *) malloc(len - opt_len + 1); if (!*val) { fprintf(stderr, "%s: failed to allocate memory\n", progname); return 0; } memcpy(*val, s + opt_len, len - opt_len); (*val)[len - opt_len] = '\0'; return 1; } static int do_mount(const char *mnt, char **typep, mode_t rootmode, int fd, const char *opts, const char *dev, char **sourcep, char **mnt_optsp) { int res; int flags = MS_NOSUID | MS_NODEV; char *optbuf; char *mnt_opts = NULL; const char *s; char *d; char *fsname = NULL; char *source = NULL; char *type = NULL; int blkdev = 0; optbuf = (char *) malloc(strlen(opts) + 128); if (!optbuf) { fprintf(stderr, "%s: failed to allocate memory\n", progname); return -1; } for (s = opts, d = optbuf; *s;) { unsigned len; const char *fsname_str = "fsname="; for (len = 0; s[len] && s[len] != ','; len++); if (begins_with(s, fsname_str)) { if (!get_string_opt(s, len, fsname_str, &fsname)) goto err; } else if (opt_eq(s, len, "blkdev")) { blkdev = 1; } else if (!begins_with(s, "fd=") && !begins_with(s, "rootmode=") && !begins_with(s, "user_id=") && !begins_with(s, "group_id=")) { int on; int flag; int skip_option = 0; if (opt_eq(s, len, "large_read")) { struct utsname utsname; unsigned kmaj, kmin; res = uname(&utsname); if (res == 0 && sscanf(utsname.release, "%u.%u", &kmaj, &kmin) == 2 && (kmaj > 2 || (kmaj == 2 && kmin > 4))) { fprintf(stderr, "%s: note: 'large_read' mount option is " "deprecated for %i.%i kernels\n", progname, kmaj, kmin); skip_option = 1; } } if (!skip_option) { if (find_mount_flag(s, len, &on, &flag)) { if (on) flags |= flag; else flags &= ~flag; } else { memcpy(d, s, len); d += len; *d++ = ','; } } } s += len; if (*s) s++; } *d = '\0'; res = get_mnt_opts(flags, optbuf, &mnt_opts); if (res == -1) goto err; sprintf(d, "fd=%i,rootmode=%o,user_id=%i,group_id=%i", fd, rootmode, getuid(), getgid()); source = malloc((fsname ? strlen(fsname) : 0) + strlen(dev) + 32); type = malloc(32); if (!type || !source) { fprintf(stderr, "%s: failed to allocate memory\n", progname); goto err; } strcpy(type, blkdev ? "fuseblk" : "fuse"); if (fsname) strcpy(source, fsname); else strcpy(source, dev); if (restore_privs()) goto err; res = mount(source, mnt, type, flags, optbuf); if (res == -1 && errno == EINVAL) { /* It could be an old version not supporting group_id */ sprintf(d, "fd=%i,rootmode=%o,user_id=%i", fd, rootmode, getuid()); res = mount(source, mnt, type, flags, optbuf); } if (drop_privs()) goto err; if (res == -1) { int errno_save = errno; if (blkdev && errno == ENODEV && !fuse_mnt_check_fuseblk()) fprintf(stderr, "%s: 'fuseblk' support missing\n", progname); else { fprintf(stderr, "%s: mount failed: %s\n", progname, strerror(errno_save)); if (errno_save == EPERM) fprintf(stderr, "User doesn't have privilege to mount. " "For more information\nplease see: " "http://tuxera.com/community/ntfs-3g-faq/#unprivileged\n"); } goto err; } else { *sourcep = source; *typep = type; *mnt_optsp = mnt_opts; } out: free(fsname); free(optbuf); return res; err: free(source); free(type); free(mnt_opts); res = -1; goto out; } static int check_perm(const char **mntp, struct stat *stbuf, int *currdir_fd, int *mountpoint_fd) { int res; const char *mnt = *mntp; const char *origmnt = mnt; res = stat(mnt, stbuf); if (res == -1) { fprintf(stderr, "%s: failed to access mountpoint %s: %s\n", progname, mnt, strerror(errno)); return -1; } /* No permission checking is done for root */ if (getuid() == 0) return 0; if (S_ISDIR(stbuf->st_mode)) { *currdir_fd = open(".", O_RDONLY); if (*currdir_fd == -1) { fprintf(stderr, "%s: failed to open current directory: %s\n", progname, strerror(errno)); return -1; } res = chdir(mnt); if (res == -1) { fprintf(stderr, "%s: failed to chdir to mountpoint: %s\n", progname, strerror(errno)); return -1; } mnt = *mntp = "."; res = lstat(mnt, stbuf); if (res == -1) { fprintf(stderr, "%s: failed to access mountpoint %s: %s\n", progname, origmnt, strerror(errno)); return -1; } if ((stbuf->st_mode & S_ISVTX) && stbuf->st_uid != getuid()) { fprintf(stderr, "%s: mountpoint %s not owned by user\n", progname, origmnt); return -1; } res = access(mnt, W_OK); if (res == -1) { fprintf(stderr, "%s: user has no write access to mountpoint %s\n", progname, origmnt); return -1; } } else if (S_ISREG(stbuf->st_mode)) { static char procfile[256]; *mountpoint_fd = open(mnt, O_WRONLY); if (*mountpoint_fd == -1) { fprintf(stderr, "%s: failed to open %s: %s\n", progname, mnt, strerror(errno)); return -1; } res = fstat(*mountpoint_fd, stbuf); if (res == -1) { fprintf(stderr, "%s: failed to access mountpoint %s: %s\n", progname, mnt, strerror(errno)); return -1; } if (!S_ISREG(stbuf->st_mode)) { fprintf(stderr, "%s: mountpoint %s is no longer a regular file\n", progname, mnt); return -1; } sprintf(procfile, "/proc/self/fd/%i", *mountpoint_fd); *mntp = procfile; } else { fprintf(stderr, "%s: mountpoint %s is not a directory or a regular file\n", progname, mnt); return -1; } return 0; } static int try_open(const char *dev, char **devp) { int fd; if (restore_privs()) return -1; fd = open(dev, O_RDWR); if (drop_privs()) return -1; if (fd != -1) { *devp = strdup(dev); if (*devp == NULL) { fprintf(stderr, "%s: failed to allocate memory\n", progname); close(fd); fd = -1; } } else if (errno == ENODEV || errno == ENOENT) /* check for ENOENT too, for the udev case */ return -2; else { fprintf(stderr, "%s: failed to open %s: %s\n", progname, dev, strerror(errno)); } return fd; } static int open_fuse_device(char **devp) { int fd; fd = try_open(FUSE_DEV_NEW, devp); if (fd >= -1) return fd; fprintf(stderr, "%s: fuse device is missing, try 'modprobe fuse' as root\n", progname); return -1; } static int mount_fuse(const char *mnt, const char *opts) { int res; int fd; char *dev; struct stat stbuf; char *type = NULL; char *source = NULL; char *mnt_opts = NULL; const char *real_mnt = mnt; int currdir_fd = -1; int mountpoint_fd = -1; fd = open_fuse_device(&dev); if (fd == -1) return -1; if (getuid() != 0 && mount_max != -1) { if (count_fuse_fs() >= mount_max) { fprintf(stderr, "%s: too many mounted FUSE filesystems (%d+)\n", progname, mount_max); goto err; } } res = check_perm(&real_mnt, &stbuf, &currdir_fd, &mountpoint_fd); if (res != -1) res = do_mount(real_mnt, &type, stbuf.st_mode & S_IFMT, fd, opts, dev, &source, &mnt_opts); if (currdir_fd != -1) { __attribute__((unused))int ignored_fchdir_status = fchdir(currdir_fd); close(currdir_fd); } if (mountpoint_fd != -1) close(mountpoint_fd); if (res == -1) goto err; if (restore_privs()) goto err; if (geteuid() == 0) { if (setgroups(0, NULL) == -1) { perror("priv drop: setgroups failed"); goto err; } res = add_mount(source, mnt, type, mnt_opts); if (res == -1) { umount2(mnt, 2); /* lazy umount */ drop_privs(); goto err; } } if (drop_privs()) goto err; out: free(source); free(type); free(mnt_opts); free(dev); return fd; err: close(fd); fd = -1; goto out; } int fusermount(int unmount, int quiet, int lazy, const char *opts, const char *origmnt) { int res = -1; char *mnt; mode_t old_umask; mnt = fuse_mnt_resolve_path(progname, origmnt); if (mnt == NULL) return -1; old_umask = umask(033); if (unmount) { if (restore_privs()) goto out; if (geteuid() == 0) res = fuse_mnt_umount(progname, mnt, lazy); else { res = umount2(mnt, lazy ? 2 : 0); if (res == -1 && !quiet) fprintf(stderr, "%s: failed to unmount %s: %s\n", progname, mnt, strerror(errno)); } if (drop_privs()) res = -1; } else res = mount_fuse(mnt, opts); out: umask(old_umask); free(mnt); return res; }