diff --git a/libfuse-lite/Makefile.am b/libfuse-lite/Makefile.am index f1455544..986f7a3e 100644 --- a/libfuse-lite/Makefile.am +++ b/libfuse-lite/Makefile.am @@ -26,6 +26,7 @@ libfuse_lite_la_SOURCES = \ fuse_opt.c \ fuse_session.c \ fuse_signals.c \ + fusermount.c \ helper.c \ mount.c \ mount_util.c \ diff --git a/libfuse-lite/fusermount.c b/libfuse-lite/fusermount.c new file mode 100644 index 00000000..290b47c3 --- /dev/null +++ b/libfuse-lite/fusermount.c @@ -0,0 +1,793 @@ +/* + 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 + +#define FUSE_DEV_OLD "/proc/fs/fuse/dev" +#define FUSE_DEV_NEW "/dev/fuse" +#define FUSE_VERSION_FILE_OLD "/proc/fs/fuse/version" +#define FUSE_CONF "/etc/fuse.conf" + +#ifndef MS_DIRSYNC +#define MS_DIRSYNC 128 +#endif + +static const char *progname = "ntfs-3g-mount"; + +static int user_allow_other = 0; +static int mount_max = 1000; + +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; + } +} + +static uid_t oldfsuid; +static gid_t oldfsgid; + +static void drop_privs(void) +{ + if (getuid() != 0) { + oldfsuid = setfsuid(getuid()); + oldfsgid = setfsgid(getgid()); + } +} + +static void restore_privs(void) +{ + if (getuid() != 0) { + setfsuid(oldfsuid); + setfsgid(oldfsgid); + } +} + +#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 unmount_fuse(const char *mnt, int quiet, int lazy) +{ + if (getuid() != 0) { + struct mntent *entp; + FILE *fp; + const char *user = NULL; + char uidstr[32]; + unsigned uidlen = 0; + int found; + const char *mtab = _PATH_MOUNTED; + + user = get_user_name(); + if (user == NULL) + return -1; + + fp = setmntent(mtab, "r"); + if (fp == NULL) { + fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab, + strerror(errno)); + return -1; + } + + uidlen = sprintf(uidstr, "%u", getuid()); + + found = 0; + while ((entp = getmntent(fp)) != NULL) { + if (!found && strcmp(entp->mnt_dir, mnt) == 0 && + (strcmp(entp->mnt_type, "fuse") == 0 || + strcmp(entp->mnt_type, "fuseblk") == 0 || + strncmp(entp->mnt_type, "fuse.", 5) == 0 || + strncmp(entp->mnt_type, "fuseblk.", 8) == 0)) { + char *p = strstr(entp->mnt_opts, "user="); + if (p && (p == entp->mnt_opts || *(p-1) == ',') && + strcmp(p + 5, user) == 0) { + found = 1; + break; + } + /* /etc/mtab is a link pointing to /proc/mounts: */ + else if ((p = strstr(entp->mnt_opts, "user_id=")) && + (p == entp->mnt_opts || *(p-1) == ',') && + strncmp(p + 8, uidstr, uidlen) == 0 && + (*(p+8+uidlen) == ',' || *(p+8+uidlen) == '\0')) { + found = 1; + break; + } + } + } + endmntent(fp); + + if (!found) { + if (!quiet) + fprintf(stderr, "%s: entry for %s not found in %s\n", progname, + mnt, mtab); + return -1; + } + } + + return fuse_mnt_umount(progname, mnt, lazy); +} + +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; +} + +static int unmount_fuse(const char *mnt, int quiet, int lazy) +{ + return fuse_mnt_umount(progname, mnt, lazy); +} +#endif /* IGNORE_MTAB */ + +static void strip_line(char *line) +{ + char *s = strchr(line, '#'); + if (s != NULL) + s[0] = '\0'; + for (s = line + strlen(line) - 1; s >= line && isspace((unsigned char) *s); s--); + s[1] = '\0'; + for (s = line; isspace((unsigned char) *s); s++); + if (s != line) + memmove(line, s, strlen(s)+1); +} + +static void parse_line(char *line, int linenum) +{ + int tmp; + if (strcmp(line, "user_allow_other") == 0) + user_allow_other = 1; + else if (sscanf(line, "mount_max = %i", &tmp) == 1) + mount_max = tmp; + else if(line[0]) + fprintf(stderr, "%s: unknown parameter in %s at line %i: '%s'\n", + progname, FUSE_CONF, linenum, line); +} + +static void read_conf(void) +{ + FILE *fp = fopen(FUSE_CONF, "r"); + if (fp != NULL) { + int linenum = 1; + char line[256]; + int isnewline = 1; + while (fgets(line, sizeof(line), fp) != NULL) { + if (isnewline) { + if (line[strlen(line)-1] == '\n') { + strip_line(line); + parse_line(line, linenum); + } else { + fprintf(stderr, "%s: reading %s: line %i too long\n", + progname, FUSE_CONF, linenum); + isnewline = 0; + } + } else if(line[strlen(line)-1] == '\n') + isnewline = 1; + if (isnewline) + linenum ++; + } + fclose(fp); + } else if (errno != ENOENT) { + fprintf(stderr, "%s: failed to open %s: %s\n", progname, FUSE_CONF, + strerror(errno)); + } +} + +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, off_t rootsize) +{ + int res; + int flags = MS_NOSUID | MS_NODEV; + char *optbuf; + char *mnt_opts = NULL; + const char *s; + char *d; + char *fsname = NULL; + char *subtype = NULL; + char *source = NULL; + char *type = NULL; + int check_empty = 1; + 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="; + const char *subtype_str = "subtype="; + 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 (begins_with(s, subtype_str)) { + if (!get_string_opt(s, len, subtype_str, &subtype)) + goto err; + } else if (opt_eq(s, len, "blkdev")) { + if (getuid() != 0) { + fprintf(stderr, "%s: option blkdev is privileged\n", progname); + goto err; + } + blkdev = 1; + } else if (opt_eq(s, len, "nonempty")) { + check_empty = 0; + } 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 (getuid() != 0 && !user_allow_other && + (opt_eq(s, len, "allow_other") || + opt_eq(s, len, "allow_root"))) { + fprintf(stderr, "%s: option %.*s only allowed if 'user_allow_other' is set in /etc/fuse.conf\n", progname, len, s); + goto err; + } + 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()); + + if (check_empty && + fuse_mnt_check_empty(progname, mnt, rootmode, rootsize) == -1) + goto err; + + source = malloc((fsname ? strlen(fsname) : 0) + + (subtype ? strlen(subtype) : 0) + strlen(dev) + 32); + + type = malloc((subtype ? strlen(subtype) : 0) + 32); + if (!type || !source) { + fprintf(stderr, "%s: failed to allocate memory\n", progname); + goto err; + } + + if (subtype) + sprintf(type, "%s.%s", blkdev ? "fuseblk" : "fuse", subtype); + else + strcpy(type, blkdev ? "fuseblk" : "fuse"); + + if (fsname) + strcpy(source, fsname); + else + strcpy(source, subtype ? subtype : dev); + + res = mount(source, mnt, type, flags, optbuf); + if (res == -1 && errno == ENODEV && subtype) { + /* Probably missing subtype support */ + strcpy(type, blkdev ? "fuseblk" : "fuse"); + if (fsname) { + if (!blkdev) + sprintf(source, "%s#%s", subtype, fsname); + } else { + strcpy(source, type); + } + + 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 (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)); + goto err; + } else { + *sourcep = source; + *typep = type; + *mnt_optsp = mnt_opts; + } + free(optbuf); + + return res; + + err: + free(fsname); + free(subtype); + free(source); + free(type); + free(mnt_opts); + free(optbuf); + return -1; +} + +static int check_version(const char *dev) +{ + int res; + int majorver; + int minorver; + const char *version_file; + FILE *vf; + + if (strcmp(dev, FUSE_DEV_OLD) != 0) + return 0; + + version_file = FUSE_VERSION_FILE_OLD; + vf = fopen(version_file, "r"); + if (vf == NULL) { + fprintf(stderr, "%s: kernel interface too old\n", progname); + return -1; + } + res = fscanf(vf, "%i.%i", &majorver, &minorver); + fclose(vf); + if (res != 2) { + fprintf(stderr, "%s: error reading %s\n", progname, version_file); + return -1; + } + if (majorver < 3) { + fprintf(stderr, "%s: kernel interface too old\n", progname); + return -1; + } + return 0; +} + +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 = lstat(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 silent) +{ + int fd = open(dev, O_RDWR); + 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 if (!silent) { + fprintf(stderr, "%s: failed to open %s: %s\n", progname, dev, + strerror(errno)); + } + return fd; +} + +static int try_open_fuse_device(char **devp) +{ + int fd; + int err; + + drop_privs(); + fd = try_open(FUSE_DEV_NEW, devp, 0); + restore_privs(); + if (fd >= 0) + return fd; + + err = fd; + fd = try_open(FUSE_DEV_OLD, devp, 1); + if (fd >= 0) + return fd; + + return err; +} + +static int open_fuse_device(char **devp) +{ + int fd = try_open_fuse_device(devp); + if (fd >= -1) + return fd; + + fprintf(stderr, "%s: fuse device not found, try 'modprobe fuse' first\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; + + drop_privs(); + read_conf(); + + if (getuid() != 0 && mount_max != -1) { + int mount_count = count_fuse_fs(); + if (mount_count >= mount_max) { + fprintf(stderr, "%s: too many FUSE filesystems mounted; " + "mount_max=N can be set in /etc/fuse.conf\n", progname); + close(fd); + return -1; + } + } + + res = check_version(dev); + if (res != -1) { + res = check_perm(&real_mnt, &stbuf, &currdir_fd, &mountpoint_fd); + restore_privs(); + if (res != -1) + res = do_mount(real_mnt, &type, stbuf.st_mode & S_IFMT, fd, opts, + dev, &source, &mnt_opts, stbuf.st_size); + } else + restore_privs(); + + if (currdir_fd != -1) { + fchdir(currdir_fd); + close(currdir_fd); + } + if (mountpoint_fd != -1) + close(mountpoint_fd); + + if (res == -1) { + close(fd); + return -1; + } + + if (geteuid() == 0) { + res = add_mount(source, mnt, type, mnt_opts); + if (res == -1) { + umount2(mnt, 2); /* lazy umount */ + close(fd); + return -1; + } + } + + free(source); + free(type); + free(mnt_opts); + free(dev); + + return fd; +} + +int fusermount(int unmount, int quiet, int lazy, const char *opts, + const char *origmnt) +{ + int res; + char *mnt; + mode_t old_umask; + + if (lazy && !unmount) { + fprintf(stderr, "%s: -z can only be used with -u\n", progname); + return -1; + } + + drop_privs(); + mnt = fuse_mnt_resolve_path(progname, origmnt); + restore_privs(); + if (mnt == NULL) + return -1; + + old_umask = umask(033); + if (unmount) { + if (geteuid() == 0) + res = unmount_fuse(mnt, quiet, 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)); + } + } else + res = mount_fuse(mnt, opts); + + umask(old_umask); + return res; +} diff --git a/libfuse-lite/mount.c b/libfuse-lite/mount.c index 55bd7a38..2c95e5eb 100644 --- a/libfuse-lite/mount.c +++ b/libfuse-lite/mount.c @@ -23,13 +23,6 @@ #include #include -#define FUSERMOUNT_PROG "fusermount" -#define FUSE_COMMFD_ENV "_FUSE_COMMFD" - -#ifndef HAVE_FORK -#define fork() vfork() -#endif - #ifndef MS_DIRSYNC #define MS_DIRSYNC 128 #endif @@ -103,38 +96,6 @@ static const struct fuse_opt fuse_mount_opts[] = { FUSE_OPT_END }; -static void mount_help(void) -{ - fprintf(stderr, - " -o allow_other allow access to other users\n" - " -o allow_root allow access to root\n" - " -o nonempty allow mounts over non-empty file/dir\n" - " -o default_permissions enable permission checking by kernel\n" - " -o fsname=NAME set filesystem name\n" - " -o subtype=NAME set filesystem type\n" - " -o large_read issue large read requests (2.4 only)\n" - " -o max_read=N set maximum size of read requests\n" - "\n" - ); -} - -static void exec_fusermount(const char *argv[]) -{ - execv(FUSERMOUNT_DIR "/" FUSERMOUNT_PROG, (char **) argv); - execvp(FUSERMOUNT_PROG, (char **) argv); -} - -static void mount_version(void) -{ - int pid = fork(); - if (!pid) { - const char *argv[] = { FUSERMOUNT_PROG, "--version", NULL }; - exec_fusermount(argv); - _exit(1); - } else if (pid != -1) - waitpid(pid, NULL, 0); -} - struct mount_flags { const char *opt; unsigned long flag; @@ -208,66 +169,19 @@ static int fuse_mount_opt_proc(void *data, const char *arg, int key, return fuse_opt_add_opt(&mo->mtab_opts, arg); case KEY_HELP: - mount_help(); mo->ishelp = 1; break; case KEY_VERSION: - mount_version(); mo->ishelp = 1; break; } return 1; } -/* return value: - * >= 0 => fd - * -1 => error - */ -static int receive_fd(int fd) -{ - struct msghdr msg; - struct iovec iov; - char buf[1]; - int rv; - size_t ccmsg[CMSG_SPACE(sizeof(int)) / sizeof(size_t)]; - struct cmsghdr *cmsg; - - iov.iov_base = buf; - iov.iov_len = 1; - - msg.msg_name = 0; - msg.msg_namelen = 0; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - /* old BSD implementations should use msg_accrights instead of - * msg_control; the interface is different. */ - msg.msg_control = ccmsg; - msg.msg_controllen = sizeof(ccmsg); - - while(((rv = recvmsg(fd, &msg, 0)) == -1) && errno == EINTR); - if (rv == -1) { - perror("recvmsg"); - return -1; - } - if(!rv) { - /* EOF */ - return -1; - } - - cmsg = CMSG_FIRSTHDR(&msg); - if (!cmsg->cmsg_type == SCM_RIGHTS) { - fprintf(stderr, "got control message of unknown type %d\n", - cmsg->cmsg_type); - return -1; - } - return *(int*)CMSG_DATA(cmsg); -} - void fuse_kern_unmount(const char *mountpoint, int fd) { int res; - int pid; if (!mountpoint) return; @@ -294,81 +208,17 @@ void fuse_kern_unmount(const char *mountpoint, int fd) if (res == 0) return; - pid = fork(); - if(pid == -1) - return; - - if(pid == 0) { - const char *argv[] = - { FUSERMOUNT_PROG, "-u", "-q", "-z", "--", mountpoint, NULL }; - - exec_fusermount(argv); - _exit(1); - } - waitpid(pid, NULL, 0); + fusermount(1, 0, 1, "", mountpoint); } -static int fuse_mount_fusermount(const char *mountpoint, const char *opts, - int quiet) +static int fuse_mount_fusermount(const char *mountpoint, const char *opts) { - int fds[2], pid; - int res; - int rv; + if (!mountpoint) { + fprintf(stderr, "fuse: missing mountpoint\n"); + return -1; + } - if (!mountpoint) { - fprintf(stderr, "fuse: missing mountpoint\n"); - return -1; - } - - res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds); - if(res == -1) { - perror("fuse: socketpair() failed"); - return -1; - } - - pid = fork(); - if(pid == -1) { - perror("fuse: fork() failed"); - close(fds[0]); - close(fds[1]); - return -1; - } - - if(pid == 0) { - char env[10]; - const char *argv[32]; - int a = 0; - - if (quiet) { - int fd = open("/dev/null", O_RDONLY); - dup2(fd, 1); - dup2(fd, 2); - } - - argv[a++] = FUSERMOUNT_PROG; - if (opts) { - argv[a++] = "-o"; - argv[a++] = opts; - } - argv[a++] = "--"; - argv[a++] = mountpoint; - argv[a++] = NULL; - - close(fds[1]); - fcntl(fds[0], F_SETFD, 0); - snprintf(env, sizeof(env), "%i", fds[0]); - setenv(FUSE_COMMFD_ENV, env, 1); - exec_fusermount(argv); - perror("fuse: failed to exec fusermount"); - _exit(1); - } - - close(fds[0]); - rv = receive_fd(fds[1]); - close(fds[1]); - waitpid(pid, NULL, 0); /* bury zombie */ - - return rv; + return fusermount(0, 0, 0, opts ? opts : "", mountpoint); } static int fuse_mount_sys(const char *mnt, struct mount_opts *mo, @@ -550,12 +400,12 @@ int fuse_kern_mount(const char *mountpoint, struct fuse_args *args) goto out; } - res = fuse_mount_fusermount(mountpoint, tmp_opts, 1); + res = fuse_mount_fusermount(mountpoint, tmp_opts); free(tmp_opts); if (res == -1) - res = fuse_mount_fusermount(mountpoint, mnt_opts, 0); + res = fuse_mount_fusermount(mountpoint, mnt_opts); } else { - res = fuse_mount_fusermount(mountpoint, mnt_opts, 0); + res = fuse_mount_fusermount(mountpoint, mnt_opts); } } out: diff --git a/libfuse-lite/mount_util.h b/libfuse-lite/mount_util.h index 29de2beb..b7350213 100644 --- a/libfuse-lite/mount_util.h +++ b/libfuse-lite/mount_util.h @@ -15,3 +15,7 @@ char *fuse_mnt_resolve_path(const char *progname, const char *orig); int fuse_mnt_check_empty(const char *progname, const char *mnt, mode_t rootmode, off_t rootsize); int fuse_mnt_check_fuseblk(void); + +int fusermount(int unmount, int quiet, int lazy, const char *opts, + const char *origmnt); + diff --git a/src/ntfs-3g.c b/src/ntfs-3g.c index 9f302e11..8bd8890d 100644 --- a/src/ntfs-3g.c +++ b/src/ntfs-3g.c @@ -2100,7 +2100,7 @@ static fuse_fstype load_fuse_module(void) struct timespec req = { 0, 100000000 }; /* 100 msec */ fuse_fstype fstype; - if (!stat(cmd, &st) && !getuid()) { + if (!stat(cmd, &st) && !geteuid()) { pid = fork(); if (!pid) { execl(cmd, cmd, "fuse", NULL); @@ -2185,14 +2185,34 @@ static void set_user_mount_option(char *parsed_options, uid_t uid) strcat(parsed_options, option); } +static int set_uid(uid_t uid) +{ + if (setuid(uid)) { + ntfs_log_perror("Failed to set uid to %d", uid); + return NTFS_VOLUME_NO_PRIVILEGE; + } + return NTFS_VOLUME_OK; +} + static struct fuse *mount_fuse(char *parsed_options) { + uid_t uid, euid; struct fuse *fh = NULL; struct fuse_args args = FUSE_ARGS_INIT(0, NULL); /* Libfuse can't always find fusermount, so let's help it. */ if (setenv("PATH", ":/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin", 0)) ntfs_log_perror("WARNING: Failed to set $PATH\n"); + /* + * We must raise privilege if possible, otherwise the user[s] fstab + * option doesn't work because mount(8) always drops privilege what + * the blkdev option requires. + */ + uid = getuid(); + euid = geteuid(); + + if (set_uid(euid)) + return NULL; ctx->fc = try_fuse_mount(parsed_options); if (!ctx->fc) @@ -2210,14 +2230,23 @@ static struct fuse *mount_fuse(char *parsed_options) if (!fh) goto err; - if (fuse_set_signal_handlers(fuse_get_session(fh))) { - fuse_destroy(fh); - fh = NULL; - goto err; - } + if (fuse_set_signal_handlers(fuse_get_session(fh))) + goto err_destory; + /* + * We can't drop privilege if internal FUSE is used because internal + * unmount needs it. Kernel 2.6.25 may include unprivileged full + * mount/unmount support. + */ +#ifndef FUSE_INTERNAL + if (set_uid(uid)) + goto err_destory; +#endif out: fuse_opt_free_args(&args); return fh; +err_destory: + fuse_destroy(fh); + fh = NULL; err: fuse_unmount(opts.mnt_point, ctx->fc); goto out; @@ -2229,7 +2258,6 @@ int main(int argc, char *argv[]) struct fuse *fh; fuse_fstype fstype = FSTYPE_UNKNOWN; struct stat sbuf; - uid_t uid, euid; int err; utils_set_locale(); @@ -2248,16 +2276,7 @@ int main(int argc, char *argv[]) err = NTFS_VOLUME_SYNTAX_ERROR; goto err_out; } - - uid = getuid(); - euid = geteuid(); - if (setuid(euid)) { - ntfs_log_perror("Failed to set user ID to %d", euid); - err = NTFS_VOLUME_NO_PRIVILEGE; - goto err_out; - } - #if defined(linux) || defined(__uClinux__) fstype = get_fuse_fstype(); if (fstype == FSTYPE_NONE || fstype == FSTYPE_UNKNOWN) @@ -2282,7 +2301,7 @@ int main(int argc, char *argv[]) if (ctx->blkdev) { /* Must do after ntfs_open() to set the right blksize. */ set_fuseblk_options(parsed_options); - set_user_mount_option(parsed_options, uid); + set_user_mount_option(parsed_options, getuid()); } fh = mount_fuse(parsed_options); @@ -2290,13 +2309,7 @@ int main(int argc, char *argv[]) err = NTFS_VOLUME_FUSE_ERROR; goto err_out; } - - if (setuid(uid)) { - ntfs_log_perror("Failed to drop privilege (uid to %d)", uid); - err = NTFS_VOLUME_NO_PRIVILEGE; - goto err_umount; - } - + ctx->mounted = TRUE; #if defined(linux) || defined(__uClinux__) @@ -2326,7 +2339,7 @@ int main(int argc, char *argv[]) fuse_loop(fh); err = 0; -err_umount: + fuse_unmount(opts.mnt_point, ctx->fc); fuse_destroy(fh); err_out: diff --git a/src/utils.c b/src/utils.c index ce93a5fb..2627d83f 100644 --- a/src/utils.c +++ b/src/utils.c @@ -85,8 +85,9 @@ static const char *fakeraid_msg = "to mount NTFS. Please see the 'dmraid' documentation for help.\n"; static const char *access_denied_msg = -"Please check the volume and the NTFS-3G binary permissions, the mounting\n" -"user and group ID, and the mount options.\n"; +"Please check the device and the ntfs-3g binary permissions, the mounting\n" +"user and group ID, and the mount options. You can find more explanation\n" +"at http://ntfs-3g.org/support.html#useroption\n"; static const char *forced_mount_msg = "\n"