/* FUSE: Filesystem in Userspace Copyright (C) 2001-2005 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 */ /* * NOTE: This program should be part of (or be called from) /bin/mount * * Unless that is done, operations on /etc/mtab are not under lock, and so * data in this file may be lost. (I will _not_ reimplement that locking, * and anyway that should be done in libc, if possible. But probably it * isn't). */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define FUSE_COMMFD_ENV "_FUSE_COMMFD" #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" #define FUSE_MAJOR 10 #define FUSE_MINOR 229 static const char *progname; static int user_allow_other = 0; static int mount_max = 1000; static const char *get_user_name() { 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 int drop_privs(void) { oldfsuid = setfsuid(getuid()); oldfsgid = setfsgid(getgid()); return 0; } static void restore_privs(void) { setfsuid(oldfsuid); setfsgid(oldfsgid); } static int do_unmount(const char *mnt, int quiet, int lazy) { int res = umount2(mnt, lazy ? 2 : 0); if (res == -1) { if (!quiet) fprintf(stderr, "%s: failed to unmount %s: %s\n", progname, mnt, strerror(errno)); } return res; } #ifndef USE_UCLIBC /* use a lock file so that multiple fusermount processes don't try and modify the mtab file at once! */ static int lock_mtab() { const char *mtab_lock = _PATH_MOUNTED ".fuselock"; int mtablock; int res; mtablock = open(mtab_lock, O_RDWR | O_CREAT, 0600); if (mtablock >= 0) { res = lockf(mtablock, F_LOCK, 0); if (res < 0) fprintf(stderr, "%s: error getting lock", progname); } else fprintf(stderr, "%s: unable to open fuse lock file\n", progname); return mtablock; } static void unlock_mtab(int mtablock) { if (mtablock >= 0) { lockf(mtablock, F_ULOCK, 0); close(mtablock); } } static int add_mount(const char *fsname, const char *mnt, const char *type, const char *opts) { int res; const char *mtab = _PATH_MOUNTED; struct mntent ent; FILE *fp; fp = setmntent(mtab, "a"); if (fp == NULL) { fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab, strerror(errno)); return -1; } ent.mnt_fsname = (char *) fsname; ent.mnt_dir = (char *) mnt; ent.mnt_type = (char *) type; ent.mnt_opts = (char *) opts; ent.mnt_freq = 0; ent.mnt_passno = 0; res = addmntent(fp, &ent); if (res != 0) { fprintf(stderr, "%s: failed to add entry to %s: %s\n", progname, mtab, strerror(errno)); return -1; } endmntent(fp); return 0; } static int remove_mount(const char *mnt, int quiet, const char *mtab, const char *mtab_new) { int res; struct mntent *entp; FILE *fp; FILE *newfp; const char *user = NULL; int found; fp = setmntent(mtab, "r"); if (fp == NULL) { fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab, strerror(errno)); return -1; } newfp = setmntent(mtab_new, "w"); if (newfp == NULL) { fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab_new, strerror(errno)); return -1; } if (getuid() != 0) { user = get_user_name(); if (user == NULL) return -1; } found = 0; while ((entp = getmntent(fp)) != NULL) { int remove = 0; if (!found && strcmp(entp->mnt_dir, mnt) == 0 && strcmp(entp->mnt_type, "fuse") == 0) { if (user == NULL) remove = 1; else { char *p = strstr(entp->mnt_opts, "user="); if (p != NULL && strcmp(p + 5, user) == 0) remove = 1; } } if (remove) found = 1; else { res = addmntent(newfp, entp); if (res != 0) { fprintf(stderr, "%s: failed to add entry to %s: %s\n", progname, mtab_new, strerror(errno)); } } } endmntent(fp); endmntent(newfp); if (!found) { if (!quiet) fprintf(stderr, "%s: entry for %s not found in %s\n", progname, mnt, mtab); unlink(mtab_new); return -1; } return 0; } static int count_fuse_fs() { struct mntent *entp; int count = 0; const char *mtab = _PATH_MOUNTED; FILE *fp = setmntent(mtab, "r"); if (fp == NULL) { fprintf(stderr, "%s: faild to open %s: %s\n", progname, mtab, strerror(errno)); return -1; } while ((entp = getmntent(fp)) != NULL) { if (strcmp(entp->mnt_type, "fuse") == 0) count ++; } endmntent(fp); return count; } static int unmount_rename(const char *mnt, int quiet, int lazy, const char *mtab, const char *mtab_new) { int res; if (getuid() != 0) { res = drop_privs(); if (res == -1) return -1; } res = do_unmount(mnt, quiet, lazy); if (res == -1) return -1; if (getuid() != 0) restore_privs(); res = rename(mtab_new, mtab); if (res == -1) { fprintf(stderr, "%s: failed to rename %s to %s: %s\n", progname, mtab_new, mtab, strerror(errno)); return -1; } return 0; } static int unmount_fuse(const char *mnt, int quiet, int lazy) { int res; const char *mtab = _PATH_MOUNTED; const char *mtab_new = _PATH_MOUNTED "~fuse~"; res = remove_mount(mnt, quiet, mtab, mtab_new); if (res == -1) return -1; res = unmount_rename(mnt, quiet, lazy, mtab, mtab_new); if (res == -1) { unlink(mtab_new); return -1; } return 0; } #else /* USE_UCLIBC */ static int lock_mtab() { return 0; } static void unlock_mtab(int mtablock) { (void) mtablock; } static int count_fuse_fs() { return 0; } static int add_mount(const char *fsname, const char *mnt, const char *type, const char *opts) { (void) fsname; (void) mnt; (void) type; (void) opts; return 0; } static int unmount_fuse(const char *mnt, int quiet, int lazy) { return do_unmount(mnt, quiet, lazy); } #endif 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}, {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 = 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 do_mount(const char *mnt, const char *type, mode_t rootmode, int fd, const char *opts, const char *dev, char **fsnamep, 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; optbuf = malloc(strlen(opts) + 64); 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)) { unsigned fsname_str_len = strlen(fsname_str); if (fsname) free(fsname); fsname = malloc(len - fsname_str_len + 1); if (!fsname) { fprintf(stderr, "%s: failed to allocate memory\n", progname); free(optbuf); return -1; } memcpy(fsname, s + fsname_str_len, len - fsname_str_len); fsname[len - fsname_str_len] = '\0'; } else if (!begins_with(s, "fd=") && !begins_with(s, "rootmode=") && !begins_with(s, "user_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); free(optbuf); return -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) { free(mnt_opts); free(optbuf); return -1; } sprintf(d, "fd=%i,rootmode=%o,user_id=%i", fd, rootmode, getuid()); if (fsname == NULL) { fsname = strdup(dev); if (!fsname) { fprintf(stderr, "%s: failed to allocate memory\n", progname); free(optbuf); return -1; } } res = mount(fsname, mnt, type, flags, optbuf); if (res == -1) { fprintf(stderr, "%s: mount failed: %s\n", progname, strerror(errno)); free(fsname); free(mnt_opts); } else { *fsnamep = fsname; *mnt_optsp = mnt_opts; } free(optbuf); return res; } 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 res; const char *mnt = *mntp; const char *origmnt; 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)) { fprintf(stderr, "%s: mountpoint %s is not a directory\n", progname, mnt); return -1; } *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; } origmnt = mnt; 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; } 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) return -2; else if (!silent) { fprintf(stderr, "%s: failed to open %s: %s\n", progname, dev, strerror(errno)); } return fd; } #define FUSE_TMP_DIRNAME "/tmp/.fuse_devXXXXXX" #define FUSE_TMP_DEVNAME "/fuse" static int try_open_new_temp(dev_t devnum, char **devp) { int res; int fd; char dirname[] = FUSE_TMP_DIRNAME; char filename[] = FUSE_TMP_DIRNAME FUSE_TMP_DEVNAME; if (mkdtemp(dirname) == NULL) { fprintf(stderr, "%s: failed to create temporary device directory: %s\n", progname, strerror(errno)); return -1; } sprintf(filename, "%s%s", dirname, FUSE_TMP_DEVNAME); res = mknod(filename, S_IFCHR | 0600, devnum); if (res == -1) { fprintf(stderr, "%s: failed to create device node: %s\n", progname, strerror(errno)); rmdir(dirname); return -1; } fd = try_open(filename, devp, 0); unlink(filename); rmdir(dirname); return fd; } static int try_open_fuse_device(char **devp) { int fd = try_open(FUSE_DEV_NEW, devp, 1); if (fd >= 0) return fd; if (fd == -1) { fd = try_open_new_temp(makedev(FUSE_MAJOR, FUSE_MINOR), devp); if (fd != -2) return fd; } fd = try_open(FUSE_DEV_OLD, devp, 1); if (fd >= 0) return fd; return -1; } static int open_fuse_device(char **devp) { int fd; if (1 #ifndef AUTO_MODPROBE && getuid() == 0 #endif ) { int status; pid_t pid; fd = try_open_fuse_device(devp); if (fd >= 0) return fd; #ifndef USE_UCLIBC pid = fork(); #else pid = vfork(); #endif if (pid == 0) { setuid(0); execl("/sbin/modprobe", "/sbin/modprobe", "fuse", NULL); exit(1); } if (pid != -1) waitpid(pid, &status, 0); } fd = try_open_fuse_device(devp); if (fd >= 0) 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; const char *type = "fuse"; struct stat stbuf; char *fsname; char *mnt_opts; const char *real_mnt = mnt; int currdir_fd = -1; int mtablock = -1; fd = open_fuse_device(&dev); if (fd == -1) return -1; if (geteuid() == 0) { mtablock = lock_mtab(); if (mtablock < 0) { close(fd); return -1; } } if (getuid() != 0) { res = drop_privs(); if (res == -1) { close(fd); unlock_mtab(mtablock); return -1; } } 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); unlock_mtab(mtablock); return -1; } } res = check_version(dev); if (res != -1) { res = check_perm(&real_mnt, &stbuf, &currdir_fd); if (res != -1) res = do_mount(real_mnt, type, stbuf.st_mode & S_IFMT, fd, opts, dev, &fsname, &mnt_opts); } if (getuid() != 0) restore_privs(); if (res == -1) { close(fd); unlock_mtab(mtablock); return -1; } if (currdir_fd != -1) { fchdir(currdir_fd); close(currdir_fd); } if (geteuid() == 0) { res = add_mount(fsname, mnt, type, mnt_opts); unlock_mtab(mtablock); if (res == -1) { umount2(mnt, 2); /* lazy umount */ close(fd); return -1; } } free(fsname); free(mnt_opts); free(dev); return fd; } static char *resolve_path(const char *orig) { char buf[PATH_MAX]; char *copy; char *dst; char *end; char *lastcomp; const char *toresolv; copy = strdup(orig); if (copy == NULL) { fprintf(stderr, "%s: failed to allocate memory\n", progname); return NULL; } toresolv = copy; lastcomp = NULL; for (end = copy + strlen(copy) - 1; end > copy && *end == '/'; end --); if (end[0] != '/') { char *tmp; end[1] = '\0'; tmp = strrchr(copy, '/'); if (tmp == NULL) { lastcomp = copy; toresolv = "."; } else { lastcomp = tmp + 1; if (tmp == copy) toresolv = "/"; } if (strcmp(lastcomp, ".") == 0 || strcmp(lastcomp, "..") == 0) { lastcomp = NULL; toresolv = copy; } else if (tmp) tmp[0] = '\0'; } if (realpath(toresolv, buf) == NULL) { fprintf(stderr, "%s: bad mount point %s: %s\n", progname, orig, strerror(errno)); free(copy); return NULL; } if (lastcomp == NULL) dst = strdup(buf); else { dst = (char *) malloc(strlen(buf) + 1 + strlen(lastcomp) + 1); if (dst) { unsigned buflen = strlen(buf); if (buflen && buf[buflen-1] == '/') sprintf(dst, "%s%s", buf, lastcomp); else sprintf(dst, "%s/%s", buf, lastcomp); } } free(copy); if (dst == NULL) fprintf(stderr, "%s: failed to allocate memory\n", progname); return dst; } static int send_fd(int sock_fd, int fd) { int retval; struct msghdr msg; struct cmsghdr *p_cmsg; struct iovec vec; char cmsgbuf[CMSG_SPACE(sizeof(fd))]; int *p_fds; char sendchar = 0; msg.msg_control = cmsgbuf; msg.msg_controllen = sizeof(cmsgbuf); p_cmsg = CMSG_FIRSTHDR(&msg); p_cmsg->cmsg_level = SOL_SOCKET; p_cmsg->cmsg_type = SCM_RIGHTS; p_cmsg->cmsg_len = CMSG_LEN(sizeof(fd)); p_fds = (int *) CMSG_DATA(p_cmsg); *p_fds = fd; msg.msg_controllen = p_cmsg->cmsg_len; msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = &vec; msg.msg_iovlen = 1; msg.msg_flags = 0; /* "To pass file descriptors or credentials you need to send/read at * least one byte" (man 7 unix) */ vec.iov_base = &sendchar; vec.iov_len = sizeof(sendchar); while ((retval = sendmsg(sock_fd, &msg, 0)) == -1 && errno == EINTR); if (retval != 1) { perror("sending file descriptor"); return -1; } return 0; } static void usage() { fprintf(stderr, "%s: [options] mountpoint\n" "Options:\n" " -h print help\n" " -o opt[,opt...] mount options\n" " -u unmount\n" " -q quiet\n" " -z lazy unmount\n", progname); exit(1); } int main(int argc, char *argv[]) { int a; int fd; int res; char *origmnt; char *mnt; int unmount = 0; int lazy = 0; char *commfd; int quiet = 0; int cfd; const char *opts = ""; progname = argv[0]; for (a = 1; a < argc; a++) { if (argv[a][0] != '-') break; switch (argv[a][1]) { case 'h': usage(); break; case 'o': a++; if (a == argc) { fprintf(stderr, "%s: missing argument to -o\n", progname); exit(1); } opts = argv[a]; break; case 'u': unmount = 1; break; case 'z': lazy = 1; break; case 'q': quiet = 1; break; default: fprintf(stderr, "%s: unknown option %s\n", progname, argv[a]); fprintf(stderr, "Try `%s -h' for more information\n", progname); exit(1); } } if (a == argc) { fprintf(stderr, "%s: missing mountpoint argument\n", progname); exit(1); } origmnt = argv[a++]; if (getuid() != 0) { res = drop_privs(); if (res == -1) exit(1); } mnt = resolve_path(origmnt); if (mnt == NULL) exit(1); if (getuid() != 0) restore_privs(); if (unmount) { if (geteuid() == 0) { int mtablock = lock_mtab(); res = unmount_fuse(mnt, quiet, lazy); unlock_mtab(mtablock); } else res = do_unmount(mnt, quiet, lazy); if (res == -1) exit(1); return 0; } commfd = getenv(FUSE_COMMFD_ENV); if (commfd == NULL) { fprintf(stderr, "%s: old style mounting not supported\n", progname); exit(1); } fd = mount_fuse(mnt, opts); if (fd == -1) exit(1); cfd = atoi(commfd); res = send_fd(cfd, fd); if (res == -1) exit(1); return 0; }