/* FUSE: Filesystem in Userspace Copyright (C) 2001 Miklos Szeredi (mszeredi@inf.bme.hu) 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 #define CHECK_PERMISSION 1 #ifndef MS_PERMISSION #define MS_PERMISSION 128 #endif #define FUSE_DEV "/proc/fs/fuse/dev" #define FUSE_MOUNTED_ENV "_FUSE_MOUNTED" #define FUSE_UMOUNT_CMD_ENV "_FUSE_UNMOUNT_CMD" #define FUSE_KERNEL_VERSION_ENV "_FUSE_KERNEL_VERSION" #define FUSE_COMMFD_ENV "_FUSE_COMMFD" const char *progname; 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; } } /* 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) perror("error getting lock"); } else fprintf(stderr, "unable to open fuse lock file, continuing anyway\n"); 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) { int res; const char *mtab = _PATH_MOUNTED; struct mntent ent; FILE *fp; char *opts; fp = setmntent(mtab, "a"); if(fp == NULL) { fprintf(stderr, "%s failed to open %s: %s\n", progname, mtab, strerror(errno)); return -1; } if(getuid() != 0) { const char *user = get_user_name(); if(user == NULL) return -1; opts = malloc(strlen(user) + 128); if(opts != NULL) sprintf(opts, "rw,nosuid,nodev,user=%s", user); } else opts = strdup("rw,nosuid,nodev"); if(opts == NULL) return -1; ent.mnt_fsname = (char *) fsname; ent.mnt_dir = (char *) mnt; ent.mnt_type = (char *) type; ent.mnt_opts = 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 res; const char *mtab = _PATH_MOUNTED; const char *mtab_new = _PATH_MOUNTED "~"; 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", progname, mtab_new, strerror(errno)); } } } endmntent(fp); endmntent(newfp); if(found) { res = umount2(mnt, 2); /* Lazy umount */ if(res == -1) { fprintf(stderr, "%s: failed to unmount %s: %s\n", progname, mnt, strerror(errno)); found = -1; } } if(found == 1) { 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; } } else { if(!found) fprintf(stderr, "%s: entry for %s not found in %s\n", progname, mnt, mtab); unlink(mtab_new); return -1; } return 0; } /* Until there is a nice interface for capabilities in _libc_, this will remain here. I don't think it is fair to expect users to compile libcap for this program. And anyway what's all this fuss about versioning the kernel interface? It is quite good as is. */ #define _LINUX_CAPABILITY_VERSION 0x19980330 typedef struct __user_cap_header_struct { unsigned int version; int pid; } *cap_user_header_t; typedef struct __user_cap_data_struct { unsigned int effective; unsigned int permitted; unsigned int inheritable; } *cap_user_data_t; int capget(cap_user_header_t header, cap_user_data_t data); int capset(cap_user_header_t header, cap_user_data_t data); #define CAP_SYS_ADMIN 21 static uid_t oldfsuid; static gid_t oldfsgid; static struct __user_cap_data_struct oldcaps; static int drop_privs() { int res; struct __user_cap_header_struct head; struct __user_cap_data_struct newcaps; head.version = _LINUX_CAPABILITY_VERSION; head.pid = 0; res = capget(&head, &oldcaps); if(res == -1) { fprintf(stderr, "%s: failed to get capabilities: %s\n", progname, strerror(errno)); return -1; } oldfsuid = setfsuid(getuid()); oldfsgid = setfsgid(getgid()); newcaps = oldcaps; /* Keep CAP_SYS_ADMIN for mount */ newcaps.effective &= (1 << CAP_SYS_ADMIN); head.version = _LINUX_CAPABILITY_VERSION; head.pid = 0; res = capset(&head, &newcaps); if(res == -1) { fprintf(stderr, "%s: failed to set capabilities: %s\n", progname, strerror(errno)); return -1; } return 0; } static void restore_privs() { struct __user_cap_header_struct head; int res; head.version = _LINUX_CAPABILITY_VERSION; head.pid = 0; res = capset(&head, &oldcaps); if(res == -1) fprintf(stderr, "%s: failed to restore capabilities: %s\n", progname, strerror(errno)); setfsuid(oldfsuid); setfsgid(oldfsgid); } static int do_mount(const char *dev, const char *mnt, const char *type, mode_t rootmode, int fd, int fuseflags) { int res; struct fuse_mount_data data; int flags = MS_NOSUID | MS_NODEV; if(getuid() != 0) { res = drop_privs(); if(res == -1) return -1; flags |= MS_PERMISSION; } data.version = FUSE_KERNEL_VERSION; data.fd = fd; data.rootmode = rootmode; data.uid = getuid(); data.flags = fuseflags; res = mount(dev, mnt, type, flags, &data); if(res == -1) fprintf(stderr, "%s: mount failed: %s\n", progname, strerror(errno)); if(getuid() != 0) restore_privs(); return res; } static int check_perm(const char *mnt, struct stat *stbuf) { int res; res = lstat(mnt, stbuf); if(res == -1) { fprintf(stderr, "%s: failed to access mountpoint %s: %s\n", progname, mnt, strerror(errno)); return -1; } if(!S_ISDIR(stbuf->st_mode) && !S_ISREG(stbuf->st_mode)) { fprintf(stderr, "%s: mountpoint %s is a special file\n", progname, mnt); return -1; } /* Should be done by the kernel */ #ifdef CHECK_PERMISSION if(getuid() != 0) { if((stbuf->st_mode & S_ISVTX) && stbuf->st_uid != getuid()) { fprintf(stderr, "%s: mountpoint %s not owned by user\n", progname, mnt); return -1; } res = access(mnt, W_OK); if(res == -1) { fprintf(stderr, "%s: user has no write access to mountpoint %s\n", progname, mnt); return -1; } } #endif return 0; } static int mount_fuse(const char *mnt, int flags, const char *fsname) { int res; int fd; const char *dev = FUSE_DEV; const char *type = "fuse"; struct stat stbuf; int mtablock; res = check_perm(mnt, &stbuf); if(res == -1) return -1; fd = open(dev, O_RDWR); if(fd == -1) { int status; pid_t pid = fork(); if(pid == 0) { setuid(0); execl("/sbin/modprobe", "/sbin/modprobe", "fuse", NULL); exit(1); } if(pid != -1) waitpid(pid, &status, 0); fd = open(dev, O_RDWR); } if(fd == -1) { fprintf(stderr, "%s: unable to open fuse device %s: %s\n", progname, dev, strerror(errno)); return -1; } if(fsname == NULL) fsname = dev; res = do_mount(fsname, mnt, type, stbuf.st_mode & S_IFMT, fd, flags); if(res == -1) return -1; mtablock = lock_mtab(); res = add_mount(fsname, mnt, type); unlock_mtab(mtablock); if(res == -1) { umount(mnt); return -1; } return fd; } static char *resolve_path(const char *orig, int unmount) { char buf[PATH_MAX]; if(unmount) { /* Resolving at unmount can only be done very carefully, not touching the mountpoint... So for the moment it's not done. Just remove trailing slashes instead. */ char *dst = strdup(orig); char *end; for(end = dst + strlen(dst) - 1; end > dst && *end == '/'; end --) *end = '\0'; return dst; } if(realpath(orig, buf) == NULL) { fprintf(stderr, "%s: Bad mount point %s: %s\n", progname, orig, strerror(errno)); return NULL; } return strdup(buf); } 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 [program [args ...]]\n" "Options:\n" " -h print help\n" " -u unmount\n" " -p check default permissions on files\n" " -c cache in kernel space if possible\n" " -x allow other users to access the files (only for root)\n" " -d name add 'name' as the filesystem name to mtab\n", progname); exit(1); } int main(int argc, char *argv[]) { int a; int fd; int res; char *origmnt; char *mnt; int unmount = 0; char **userprog; int numargs; char mypath[PATH_MAX]; char *unmount_cmd; char *commfd; const char *fsname = NULL; char verstr[128]; int flags = 0; progname = argv[0]; for(a = 1; a < argc; a++) { if(argv[a][0] != '-') break; switch(argv[a][1]) { case 'c': flags |= FUSE_KERNEL_CACHE; break; case 'h': usage(); break; case 'u': unmount = 1; break; case 'p': flags |= FUSE_DEFAULT_PERMISSIONS; break; case 'x': if(getuid() != 0) { fprintf(stderr, "%s: option %s is allowed only for root\n", progname, argv[a]); exit(1); } flags |= FUSE_ALLOW_OTHER; break; case 'd': a++; if(a == argc) { fprintf(stderr, "%s: Missing argument to -d\n", progname); exit(1); } fsname = argv[a]; break; default: fprintf(stderr, "%s: Unknown option %s\n", progname, argv[a]); exit(1); } } if(a == argc) { fprintf(stderr, "%s: Missing mountpoint argument\n", progname); exit(1); } origmnt = argv[a++]; if(getpid() != 0) drop_privs(); mnt = resolve_path(origmnt, unmount); if(mnt == NULL) exit(1); if(getpid() != 0) restore_privs(); if(unmount) { int mtablock = lock_mtab(); res = remove_mount(mnt); unlock_mtab(mtablock); if(res == -1) exit(1); return 0; } commfd = getenv(FUSE_COMMFD_ENV); if(a == argc && commfd == NULL) { fprintf(stderr, "%s: Missing program argument\n", progname); exit(1); } userprog = argv + a; numargs = argc - a; fd = mount_fuse(mnt, flags, fsname); if(fd == -1) exit(1); if(commfd != NULL) { int cfd = atoi(commfd); res = send_fd(cfd, fd); if(res == -1) exit(1); exit(0); } /* Dup the file descriptor to stdin */ if(fd != 0) { dup2(fd, 0); close(fd); } /* Strangely this doesn't work after dropping permissions... */ res = readlink("/proc/self/exe", mypath, sizeof(mypath) - 1); if(res == -1) { fprintf(stderr, "%s: failed to determine self path: %s\n", progname, strerror(errno)); strcpy(mypath, "fusermount"); fprintf(stderr, "using %s as the default\n", mypath); } else mypath[res] = '\0'; /* Drop setuid/setgid permissions */ setuid(getuid()); setgid(getgid()); unmount_cmd = (char *) malloc(strlen(mypath) + strlen(mnt) + 64); sprintf(unmount_cmd, "%s -u %s", mypath, mnt); setenv(FUSE_UMOUNT_CMD_ENV, unmount_cmd, 1); sprintf(verstr, "%i", FUSE_KERNEL_VERSION); setenv(FUSE_KERNEL_VERSION_ENV, verstr, 1); setenv(FUSE_MOUNTED_ENV, "", 1); execvp(userprog[0], userprog); fprintf(stderr, "%s: failed to exec %s: %s\n", progname, userprog[0], strerror(errno)); close(0); system(unmount_cmd); return 1; }