diff options
Diffstat (limited to 'src/bindfs.c')
-rw-r--r-- | src/bindfs.c | 1329 |
1 files changed, 1329 insertions, 0 deletions
diff --git a/src/bindfs.c b/src/bindfs.c new file mode 100644 index 0000000..ff7b35f --- /dev/null +++ b/src/bindfs.c @@ -0,0 +1,1329 @@ +/* + Copyright 2006,2007,2008,2009,2010 Martin Pärtel <martin.partel@gmail.com> + + This file is part of bindfs. + + bindfs is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + bindfs is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with bindfs. If not, see <http://www.gnu.org/licenses/>. + + + This file is based on fusexmp_fh.c from FUSE 2.5.3, + which had the following notice: + --- + FUSE: Filesystem in Userspace + Copyright (C) 2001-2006 Miklos Szeredi <miklos@szeredi.hu> + + This program can be distributed under the terms of the GNU GPL. + See the file COPYING. + --- + +*/ + +#include <config.h> + +/* For pread/pwrite */ +#define _XOPEN_SOURCE 500 + +#include <stdlib.h> +#include <stddef.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif +#include <sys/statvfs.h> +#include <unistd.h> +#include <fcntl.h> +#include <dirent.h> +#include <errno.h> +#include <getopt.h> +#include <assert.h> +#include <pwd.h> +#include <grp.h> +#ifdef HAVE_SETXATTR +#include <sys/xattr.h> +#endif + +#include <fuse.h> +#include <fuse_opt.h> + +#include "debug.h" +#include "permchain.h" +#include "userinfo.h" +#include "misc.h" + +/* SETTINGS */ +static struct settings { + const char *progname; + struct permchain *permchain; /* permission bit rules. see permchain.h */ + uid_t new_uid; /* user-specified uid */ + gid_t new_gid; /* user-specified gid */ + uid_t create_for_uid; + gid_t create_for_gid; + const char *mntsrc; + const char *mntdest; + int mntsrc_fd; + + enum CreatePolicy { + CREATE_AS_USER, + CREATE_AS_MOUNTER + } create_policy; + + struct permchain *create_permchain; /* the --create-with-perms option */ + + enum ChownPolicy { + CHOWN_NORMAL, + CHOWN_IGNORE, + CHOWN_DENY + } chown_policy; + + enum ChgrpPolicy { + CHGRP_NORMAL, + CHGRP_IGNORE, + CHGRP_DENY + } chgrp_policy; + + enum ChmodPolicy { + CHMOD_NORMAL, + CHMOD_IGNORE, + CHMOD_DENY + } chmod_policy; + + int chmod_allow_x; + + enum XAttrPolicy { + XATTR_UNIMPLEMENTED, + XATTR_READ_ONLY, + XATTR_READ_WRITE + } xattr_policy; + + int mirrored_users_only; + uid_t* mirrored_users; + int num_mirrored_users; + gid_t *mirrored_members; + int num_mirrored_members; + + int ctime_from_mtime; +} settings; + + + +/* PROTOTYPES */ + +static int is_mirroring_enabled(); + +/* Checks whether the uid is to be the mirrored owner of all files. */ +static int is_mirrored_user(uid_t uid); + +/* Processes the virtual path to a real path. Don't free() the result. */ +static const char *process_path(const char *path); + +/* The common parts of getattr and fgetattr */ +static int getattr_common(const char *path, struct stat *stbuf); + + +/* FUSE callbacks */ +static void *bindfs_init(); +static void bindfs_destroy(void *private_data); +static int bindfs_getattr(const char *path, struct stat *stbuf); +static int bindfs_fgetattr(const char *path, struct stat *stbuf, + struct fuse_file_info *fi); +static int bindfs_readlink(const char *path, char *buf, size_t size); +static int bindfs_opendir(const char *path, struct fuse_file_info *fi); +static inline DIR *get_dirp(struct fuse_file_info *fi); +static int bindfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, + off_t offset, struct fuse_file_info *fi); +static int bindfs_releasedir(const char *path, struct fuse_file_info *fi); +static int bindfs_mknod(const char *path, mode_t mode, dev_t rdev); +static int bindfs_mkdir(const char *path, mode_t mode); +static int bindfs_unlink(const char *path); +static int bindfs_rmdir(const char *path); +static int bindfs_symlink(const char *from, const char *to); +static int bindfs_rename(const char *from, const char *to); +static int bindfs_link(const char *from, const char *to); +static int bindfs_chmod(const char *path, mode_t mode); +static int bindfs_chown(const char *path, uid_t uid, gid_t gid); +static int bindfs_truncate(const char *path, off_t size); +static int bindfs_ftruncate(const char *path, off_t size, + struct fuse_file_info *fi); +static int bindfs_utime(const char *path, struct utimbuf *buf); +static int bindfs_create(const char *path, mode_t mode, struct fuse_file_info *fi); +static int bindfs_open(const char *path, struct fuse_file_info *fi); +static int bindfs_read(const char *path, char *buf, size_t size, off_t offset, + struct fuse_file_info *fi); +static int bindfs_write(const char *path, const char *buf, size_t size, + off_t offset, struct fuse_file_info *fi); +static int bindfs_statfs(const char *path, struct statvfs *stbuf); +static int bindfs_release(const char *path, struct fuse_file_info *fi); +static int bindfs_fsync(const char *path, int isdatasync, + struct fuse_file_info *fi); + + +static void print_usage(const char *progname); +static void atexit_func(); +static int process_option(void *data, const char *arg, int key, + struct fuse_args *outargs); + +static int is_mirroring_enabled() +{ + return settings.num_mirrored_users + settings.num_mirrored_members > 0; +} + +static int is_mirrored_user(uid_t uid) +{ + int i; + for (i = 0; i < settings.num_mirrored_users; ++i) { + if (settings.mirrored_users[i] == uid) + break; + } + if (i < settings.num_mirrored_users) { /* found in mirrored_users */ + return 1; + } else { + for (i = 0; i < settings.num_mirrored_members; ++i) { + if (user_belongs_to_group(uid, settings.mirrored_members[i])) + break; + } + if (i < settings.num_mirrored_members) /* found in mirrored_members */ + return 1; + } + return 0; +} + + +static const char *process_path(const char *path) +{ + if (path == NULL) /* possible? */ + return NULL; + + while (*path == '/') + ++path; + + if (*path == '\0') + return "."; + else + return path; +} + +static int getattr_common(const char *procpath, struct stat *stbuf) +{ + struct fuse_context *fc = fuse_get_context(); + + /* Copy mtime (file content modification time) + to ctime (inode/status change time) + if the user asked for that */ + if (settings.ctime_from_mtime) + stbuf->st_ctime = stbuf->st_mtime; + + /* Report user-defined owner/group if specified */ + if (settings.new_uid != -1) + stbuf->st_uid = settings.new_uid; + if (settings.new_gid != -1) + stbuf->st_gid = settings.new_gid; + + /* Mirrored user? */ + if (is_mirroring_enabled() && is_mirrored_user(fc->uid)) { + stbuf->st_uid = fc->uid; + } else if (settings.mirrored_users_only && fc->uid != 0) { + stbuf->st_mode &= ~0777; /* Deny all access if mirror-only and not root */ + return 0; + } + + if ((stbuf->st_mode & S_IFLNK) == S_IFLNK) + return 0; /* don't bother with symlink permissions -- they don't matter */ + + /* Apply user-defined permission bit modifications */ + stbuf->st_mode = permchain_apply(settings.permchain, stbuf->st_mode); + + /* Check that we can really do what we promise */ + if (access(procpath, R_OK) == -1) + stbuf->st_mode &= ~0444; + if (access(procpath, W_OK) == -1) + stbuf->st_mode &= ~0222; + if (access(procpath, X_OK) == -1) + stbuf->st_mode &= ~0111; + + return 0; +} + +static void *bindfs_init() +{ + assert(settings.permchain != NULL); + assert(settings.mntsrc_fd > 0); + + if (fchdir(settings.mntsrc_fd) != 0) { + fprintf( + stderr, + "Could not change working directory to '%s': %s\n", + settings.mntsrc, + strerror(errno) + ); + fuse_exit(fuse_get_context()->fuse); + } + + return NULL; +} + +static void bindfs_destroy(void *private_data) +{ +} + +static int bindfs_getattr(const char *path, struct stat *stbuf) +{ + path = process_path(path); + + if (lstat(path, stbuf) == -1) + return -errno; + return getattr_common(path, stbuf); +} + +static int bindfs_fgetattr(const char *path, struct stat *stbuf, + struct fuse_file_info *fi) +{ + path = process_path(path); + + if (fstat(fi->fh, stbuf) == -1) + return -errno; + return getattr_common(path, stbuf); +} + +static int bindfs_readlink(const char *path, char *buf, size_t size) +{ + int res; + + path = process_path(path); + + /* No need to check for access to the link itself, since symlink + permissions don't matter. Access to the path components of the symlink + are automatically queried by FUSE. */ + + res = readlink(path, buf, size - 1); + if (res == -1) + return -errno; + + buf[res] = '\0'; + return 0; +} + +static int bindfs_opendir(const char *path, struct fuse_file_info *fi) +{ + DIR *dp; + + path = process_path(path); + + dp = opendir(path); + if (dp == NULL) + return -errno; + + fi->fh = (unsigned long) dp; + return 0; +} + +static inline DIR *get_dirp(struct fuse_file_info *fi) +{ + return (DIR *) (uintptr_t) fi->fh; +} + +static int bindfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, + off_t offset, struct fuse_file_info *fi) +{ + DIR *dp = get_dirp(fi); + struct dirent *de; + + (void) path; + seekdir(dp, offset); + while ((de = readdir(dp)) != NULL) { + struct stat st; + memset(&st, 0, sizeof(st)); + st.st_ino = de->d_ino; + st.st_mode = de->d_type << 12; + if (filler(buf, de->d_name, &st, telldir(dp))) + break; + } + + return 0; +} + +static int bindfs_releasedir(const char *path, struct fuse_file_info *fi) +{ + DIR *dp = get_dirp(fi); + (void) path; + closedir(dp); + return 0; +} + +static int bindfs_mknod(const char *path, mode_t mode, dev_t rdev) +{ + int res; + struct fuse_context *fc; + uid_t file_owner = -1; + gid_t file_group = -1; + + path = process_path(path); + + mode = permchain_apply(settings.create_permchain, mode); + + if (S_ISFIFO(mode)) + res = mkfifo(path, mode); + else + res = mknod(path, mode, rdev); + if (res == -1) + return -errno; + + if (settings.create_policy == CREATE_AS_USER) { + fc = fuse_get_context(); + file_owner = fc->uid; + file_group = fc->gid; + } + + if (settings.create_for_uid != -1) + file_owner = settings.create_for_uid; + if (settings.create_for_gid != -1) + file_group = settings.create_for_gid; + + if ((file_owner != -1) || (file_group != -1)) { + if (chown(path, file_owner, file_group) == -1) { + DPRINTF("Failed to chown new device node (%d)", errno); + } + } + + return 0; +} + +static int bindfs_mkdir(const char *path, mode_t mode) +{ + int res; + struct fuse_context *fc; + uid_t file_owner = -1; + gid_t file_group = -1; + + path = process_path(path); + + mode |= S_IFDIR; /* tell permchain_apply this is a directory */ + mode = permchain_apply(settings.create_permchain, mode); + + res = mkdir(path, mode & 0777); + if (res == -1) + return -errno; + + if (settings.create_policy == CREATE_AS_USER) { + fc = fuse_get_context(); + file_owner = fc->uid; + file_group = fc->gid; + } + + if (settings.create_for_uid != -1) + file_owner = settings.create_for_uid; + if (settings.create_for_gid != -1) + file_group = settings.create_for_gid; + + if ((file_owner != -1) || (file_group != -1)) { + if (chown(path, file_owner, file_group) == -1) { + DPRINTF("Failed to chown new directory (%d)", errno); + } + } + + return 0; +} + +static int bindfs_unlink(const char *path) +{ + int res; + + path = process_path(path); + + res = unlink(path); + if (res == -1) + return -errno; + + return 0; +} + +static int bindfs_rmdir(const char *path) +{ + int res; + + path = process_path(path); + + res = rmdir(path); + if (res == -1) + return -errno; + + return 0; +} + +static int bindfs_symlink(const char *from, const char *to) +{ + int res; + struct fuse_context *fc; + uid_t file_owner = -1; + gid_t file_group = -1; + + to = process_path(to); + + res = symlink(from, to); + if (res == -1) + return -errno; + + if (settings.create_policy == CREATE_AS_USER) { + fc = fuse_get_context(); + file_owner = fc->uid; + file_group = fc->gid; + } + + if (settings.create_for_uid != -1) + file_owner = settings.create_for_uid; + if (settings.create_for_gid != -1) + file_group = settings.create_for_gid; + + if ((file_owner != -1) || (file_group != -1)) { + if (lchown(to, file_owner, file_group) == -1) { + DPRINTF("Failed to lchown new symlink (%d)", errno); + } + } + + return 0; +} + +static int bindfs_rename(const char *from, const char *to) +{ + int res; + + from = process_path(from); + to = process_path(to); + + res = rename(from, to); + if (res == -1) + return -errno; + + return 0; +} + +static int bindfs_link(const char *from, const char *to) +{ + int res; + + from = process_path(from); + to = process_path(to); + + res = link(from, to); + if (res == -1) + return -errno; + + return 0; +} + +static int bindfs_chmod(const char *path, mode_t mode) +{ + int file_execute_only = 0; + struct stat st; + mode_t diff = 0; + + path = process_path(path); + + if (settings.chmod_allow_x) { + /* Get the old permission bits and see which bits would change. */ + if (lstat(path, &st) == -1) + return -errno; + + if (S_ISREG(st.st_mode)) { + diff = (st.st_mode & 07777) ^ (mode & 07777); + file_execute_only = 1; + } + } + + switch (settings.chmod_policy) { + case CHMOD_NORMAL: + if (chmod(path, mode) == -1) + return -errno; + return 0; + case CHMOD_IGNORE: + if (file_execute_only) { + diff &= 00111; /* See which execute bits were flipped. + Forget about other differences. */ + if (chmod(path, st.st_mode ^ diff) == -1) + return -errno; + } + return 0; + case CHMOD_DENY: + if (file_execute_only) { + if ((diff & 07666) == 0) { + /* Only execute bits have changed, so we can allow this. */ + if (chmod(path, mode) == -1) + return -errno; + return 0; + } + } + return -EPERM; + default: + assert(0); + } +} + +static int bindfs_chown(const char *path, uid_t uid, gid_t gid) +{ + int res; + + if (uid != -1) { + switch (settings.chown_policy) { + case CHOWN_NORMAL: + break; + case CHOWN_IGNORE: + uid = -1; + break; + case CHOWN_DENY: + return -EPERM; + } + } + + if (gid != -1) { + switch (settings.chgrp_policy) { + case CHGRP_NORMAL: + break; + case CHGRP_IGNORE: + gid = -1; + break; + case CHGRP_DENY: + return -EPERM; + } + } + + if (uid != -1 || gid != -1) { + path = process_path(path); + res = lchown(path, uid, gid); + if (res == -1) + return -errno; + } + + return 0; +} + +static int bindfs_truncate(const char *path, off_t size) +{ + int res; + + path = process_path(path); + + res = truncate(path, size); + if (res == -1) + return -errno; + + return 0; +} + +static int bindfs_ftruncate(const char *path, off_t size, + struct fuse_file_info *fi) +{ + int res; + + (void) path; + + res = ftruncate(fi->fh, size); + if (res == -1) + return -errno; + + return 0; +} + +static int bindfs_utime(const char *path, struct utimbuf *buf) +{ + int res; + + path = process_path(path); + + res = utime(path, buf); + if (res == -1) + return -errno; + + return 0; +} + +static int bindfs_create(const char *path, mode_t mode, struct fuse_file_info *fi) +{ + int fd; + struct fuse_context *fc; + uid_t file_owner = -1; + gid_t file_group = -1; + + path = process_path(path); + + mode |= S_IFREG; /* tell permchain_apply this is a regular file */ + mode = permchain_apply(settings.create_permchain, mode); + + fd = open(path, fi->flags, mode & 0777); + if (fd == -1) + return -errno; + + if (settings.create_policy == CREATE_AS_USER) { + fc = fuse_get_context(); + file_owner = fc->uid; + file_group = fc->gid; + } + + if (settings.create_for_uid != -1) + file_owner = settings.create_for_uid; + if (settings.create_for_gid != -1) + file_group = settings.create_for_gid; + + if ((file_owner != -1) || (file_group != -1)) { + if (chown(path, file_owner, file_group) == -1) { + DPRINTF("Failed to chown new file (%d)", errno); + } + } + + fi->fh = fd; + return 0; +} + +static int bindfs_open(const char *path, struct fuse_file_info *fi) +{ + int fd; + + path = process_path(path); + + fd = open(path, fi->flags); + if (fd == -1) + return -errno; + + fi->fh = fd; + return 0; +} + +static int bindfs_read(const char *path, char *buf, size_t size, off_t offset, + struct fuse_file_info *fi) +{ + int res; + + (void) path; + res = pread(fi->fh, buf, size, offset); + if (res == -1) + res = -errno; + + return res; +} + +static int bindfs_write(const char *path, const char *buf, size_t size, + off_t offset, struct fuse_file_info *fi) +{ + int res; + + (void) path; + res = pwrite(fi->fh, buf, size, offset); + if (res == -1) + res = -errno; + + return res; +} + +static int bindfs_statfs(const char *path, struct statvfs *stbuf) +{ + int res; + + path = process_path(path); + + res = statvfs(path, stbuf); + if (res == -1) + return -errno; + + return 0; +} + +static int bindfs_release(const char *path, struct fuse_file_info *fi) +{ + (void) path; + close(fi->fh); + + return 0; +} + +static int bindfs_fsync(const char *path, int isdatasync, + struct fuse_file_info *fi) +{ + int res; + (void) path; + +#ifndef HAVE_FDATASYNC + (void) isdatasync; +#else + if (isdatasync) + res = fdatasync(fi->fh); + else +#endif + res = fsync(fi->fh); + if (res == -1) + return -errno; + + return 0; +} + +#ifdef HAVE_SETXATTR +/* If HAVE_L*XATTR is not defined, we assume Mac/BSD -style *xattr() */ + +static int bindfs_setxattr(const char *path, const char *name, const char *value, + size_t size, int flags) +{ + DPRINTF("setxattr %s %s=%s", path, name, value); + + if (settings.xattr_policy == XATTR_READ_ONLY) + return -EACCES; + + /* fuse checks permissions for us */ + path = process_path(path); +#ifdef HAVE_LSETXATTR + if (lsetxattr(path, name, value, size, flags) == -1) +#else + if (setxattr(path, name, value, size, 0, flags | XATTR_NOFOLLOW) == -1) +#endif + return -errno; + return 0; +} + +static int bindfs_getxattr(const char *path, const char *name, char *value, + size_t size) +{ + int res; + + DPRINTF("getxattr %s %s", path, name); + + path = process_path(path); + /* fuse checks permissions for us */ +#ifdef HAVE_LGETXATTR + res = lgetxattr(path, name, value, size); +#else + res = getxattr(path, name, value, size, 0, XATTR_NOFOLLOW); +#endif + if (res == -1) + return -errno; + return res; +} + +static int bindfs_listxattr(const char *path, char *list, size_t size) +{ + int res; + + DPRINTF("listxattr %s", path); + + path = process_path(path); + /* fuse checks permissions for us */ +#ifdef HAVE_LLISTXATTR + res = llistxattr(path, list, size); +#else + res = listxattr(path, list, size, XATTR_NOFOLLOW); +#endif + if (res == -1) + return -errno; + return res; +} + +static int bindfs_removexattr(const char *path, const char *name) +{ + DPRINTF("removexattr %s %s", path, name); + + if (settings.xattr_policy == XATTR_READ_ONLY) + return -EACCES; + + path = process_path(path); + /* fuse checks permissions for us */ +#ifdef HAVE_LREMOVEXATTR + if (lremovexattr(path, name) == -1) +#else + if (removexattr(path, name, XATTR_NOFOLLOW) == -1) +#endif + return -errno; + return 0; +} +#endif /* HAVE_SETXATTR */ + + +static struct fuse_operations bindfs_oper = { + .init = bindfs_init, + .destroy = bindfs_destroy, + .getattr = bindfs_getattr, + .fgetattr = bindfs_fgetattr, + /* no access() since we always use -o default_permissions */ + .readlink = bindfs_readlink, + .opendir = bindfs_opendir, + .readdir = bindfs_readdir, + .releasedir = bindfs_releasedir, + .mknod = bindfs_mknod, + .mkdir = bindfs_mkdir, + .symlink = bindfs_symlink, + .unlink = bindfs_unlink, + .rmdir = bindfs_rmdir, + .rename = bindfs_rename, + .link = bindfs_link, + .chmod = bindfs_chmod, + .chown = bindfs_chown, + .truncate = bindfs_truncate, + .ftruncate = bindfs_ftruncate, + .utime = bindfs_utime, + .create = bindfs_create, + .open = bindfs_open, + .read = bindfs_read, + .write = bindfs_write, + .statfs = bindfs_statfs, + .release = bindfs_release, + .fsync = bindfs_fsync, +#ifdef HAVE_SETXATTR + .setxattr = bindfs_setxattr, + .getxattr = bindfs_getxattr, + .listxattr = bindfs_listxattr, + .removexattr= bindfs_removexattr, +#endif +}; + +static void print_usage(const char *progname) +{ + if (progname == NULL) + progname = "bindfs"; + + printf("\n" + "Usage: %s [options] dir mountpoint\n" + "Information:\n" + " -h --help Print this and exit.\n" + " -V --version Print version number and exit.\n" + "\n" + "Options:\n" + " -u --user, --owner Set file owner.\n" + " -g --group Set file group.\n" + " -m --mirror Comma-separated list of users who will see\n" + " themselves as the owners of all files.\n" + " -M --mirror-only Like --mirror but disallow access for\n" + " all other users.\n" + " -n --no-allow-other Do not add -o allow_other to fuse options.\n" + "\n" + "Permission bits:\n" + " -p --perms Specify permissions, similar to chmod\n" + " e.g. og-x,og+rD,u=rwX,g+rw or 0644,a+X\n" + "\n" + "File creation policy:\n" + " --create-as-user New files owned by creator (default for root). *\n" + " --create-as-mounter New files owned by fs mounter (default for users).\n" + " --create-for-user New files owned by specified user. *\n" + " --create-for-group New files owned by specified group. *\n" + " --create-with-perms Alter permissions of new files.\n" + "\n" + "Chown policy:\n" + " --chown-normal Try to chown the original files (the default).\n" + " --chown-ignore Have all chowns fail silently.\n" + " --chown-deny Have all chowns fail with 'permission denied'.\n" + "\n" + "Chgrp policy:\n" + " --chgrp-normal Try to chgrp the original files (the default).\n" + " --chgrp-ignore Have all chgrps fail silently.\n" + " --chgrp-deny Have all chgrps fail with 'permission denied'.\n" + "\n" + "Chmod policy:\n" + " --chmod-normal Try to chmod the original files (the default).\n" + " --chmod-ignore Have all chmods fail silently.\n" + " --chmod-deny Have all chmods fail with 'permission denied'.\n" + " --chmod-allow-x Allow changing file execute bits in any case.\n" + "\n" + "Extended attribute policy:\n" + " --xattr-none Do not implement xattr operations.\n" + " --xattr-ro Read-only xattr operations.\n" + " --xattr-rw Read-write xattr operations (the default).\n" + "\n" + "Time-related:\n" + " --ctime-from-mtime Read file properties' change time\n" + " from file content modification time.\n" + "\n" + "FUSE options:\n" + " -o opt[,opt,...] Mount options.\n" + " -r -o ro Mount strictly read-only.\n" + " -d -o debug Enable debug output (implies -f).\n" + " -f Foreground operation.\n" + " -s Disable multithreaded operation.\n" + "\n" + "(*: root only)\n" + "\n", + progname); +} + + +static void atexit_func() +{ + permchain_destroy(settings.permchain); + settings.permchain = NULL; + permchain_destroy(settings.create_permchain); + settings.create_permchain = NULL; + free(settings.mirrored_users); + settings.mirrored_users = NULL; + free(settings.mirrored_members); + settings.mirrored_members = NULL; +} + +enum OptionKey { + OPTKEY_NONOPTION = -2, + OPTKEY_UNKNOWN = -1, + OPTKEY_HELP, + OPTKEY_VERSION, + OPTKEY_CREATE_AS_USER, + OPTKEY_CREATE_AS_MOUNTER, + OPTKEY_CHOWN_NORMAL, + OPTKEY_CHOWN_IGNORE, + OPTKEY_CHOWN_DENY, + OPTKEY_CHGRP_NORMAL, + OPTKEY_CHGRP_IGNORE, + OPTKEY_CHGRP_DENY, + OPTKEY_CHMOD_NORMAL, + OPTKEY_CHMOD_IGNORE, + OPTKEY_CHMOD_DENY, + OPTKEY_CHMOD_ALLOW_X, + OPTKEY_XATTR_NONE, + OPTKEY_XATTR_READ_ONLY, + OPTKEY_XATTR_READ_WRITE, + OPTKEY_CTIME_FROM_MTIME +}; + +static int process_option(void *data, const char *arg, int key, + struct fuse_args *outargs) +{ + switch ((enum OptionKey)key) + { + case OPTKEY_HELP: + print_usage(my_basename(settings.progname)); + exit(0); + + case OPTKEY_VERSION: + printf("%s\n", PACKAGE_STRING); + exit(0); + + case OPTKEY_CREATE_AS_USER: + if (getuid() == 0) { + settings.create_policy = CREATE_AS_USER; + } else { + fprintf(stderr, "Error: You need to be root to use --create-as-user !\n"); + return -1; + } + return 0; + case OPTKEY_CREATE_AS_MOUNTER: + settings.create_policy = CREATE_AS_MOUNTER; + return 0; + + case OPTKEY_CHOWN_NORMAL: + settings.chown_policy = CHOWN_NORMAL; + return 0; + case OPTKEY_CHOWN_IGNORE: + settings.chown_policy = CHOWN_IGNORE; + return 0; + case OPTKEY_CHOWN_DENY: + settings.chown_policy = CHOWN_DENY; + return 0; + + case OPTKEY_CHGRP_NORMAL: + settings.chgrp_policy = CHGRP_NORMAL; + return 0; + case OPTKEY_CHGRP_IGNORE: + settings.chgrp_policy = CHGRP_IGNORE; + return 0; + case OPTKEY_CHGRP_DENY: + settings.chgrp_policy = CHGRP_DENY; + return 0; + + case OPTKEY_CHMOD_NORMAL: + settings.chmod_policy = CHMOD_NORMAL; + return 0; + case OPTKEY_CHMOD_IGNORE: + settings.chmod_policy = CHMOD_IGNORE; + return 0; + case OPTKEY_CHMOD_DENY: + settings.chmod_policy = CHMOD_DENY; + return 0; + + case OPTKEY_CHMOD_ALLOW_X: + settings.chmod_allow_x = 1; + return 0; + + case OPTKEY_XATTR_NONE: + settings.xattr_policy = XATTR_UNIMPLEMENTED; + return 0; + case OPTKEY_XATTR_READ_ONLY: + settings.xattr_policy = XATTR_READ_ONLY; + return 0; + case OPTKEY_XATTR_READ_WRITE: + settings.xattr_policy = XATTR_READ_WRITE; + return 0; + + case OPTKEY_CTIME_FROM_MTIME: + settings.ctime_from_mtime = 1; + return 0; + + case OPTKEY_NONOPTION: + if (!settings.mntsrc) { + settings.mntsrc = arg; + return 0; + } else if (!settings.mntdest) { + settings.mntdest = arg; + return 1; /* leave this argument for fuse_main */ + } else { + fprintf(stderr, "Too many arguments given\n"); + return -1; + } + + default: + return 1; + } +} + + +int main(int argc, char *argv[]) +{ + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + + /* Fuse's option parser will store things here. */ + static struct OptionData { + char *user; + char *group; + char *perms; + char *mirror; + char *mirror_only; + char *create_for_user; + char *create_for_group; + char *create_with_perms; + int no_allow_other; + } od = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0}; + + #define OPT2(one, two, key) \ + FUSE_OPT_KEY(one, key), \ + FUSE_OPT_KEY(two, key) + #define OPT_OFFSET2(one, two, offset, key) \ + {one, offsetof(struct OptionData, offset), key}, \ + {two, offsetof(struct OptionData, offset), key} + #define OPT_OFFSET3(one, two, three, offset, key) \ + {one, offsetof(struct OptionData, offset), key}, \ + {two, offsetof(struct OptionData, offset), key}, \ + {three, offsetof(struct OptionData, offset), key} + static const struct fuse_opt options[] = { + OPT2("-h", "--help", OPTKEY_HELP), + OPT2("-V", "--version", OPTKEY_VERSION), + OPT_OFFSET2("-u %s", "--user=%s", user, -1), + OPT_OFFSET2( "--owner=%s", "owner=%s", user, -1), + OPT_OFFSET3("-g %s", "--group=%s", "group=%s", group, -1), + OPT_OFFSET3("-p %s", "--perms=%s", "perms=%s", perms, -1), + OPT_OFFSET3("-m %s", "--mirror=%s", "mirror=%s", mirror, -1), + OPT_OFFSET3("-M %s", "--mirror-only=%s", "mirror-only=%s", mirror_only, -1), + OPT_OFFSET3("-n", "--no-allow-other", "no-allow-other", no_allow_other, -1), + OPT2("--create-as-user", "create-as-user", OPTKEY_CREATE_AS_USER), + OPT2("--create-as-mounter", "create-as-mounter", OPTKEY_CREATE_AS_MOUNTER), + OPT_OFFSET2("--create-for-user=%s", "create-for-user=%s", create_for_user, -1), + OPT_OFFSET2("--create-for-group=%s", "create-for-group=%s", create_for_group, -1), + OPT_OFFSET2("--create-with-perms=%s", "create-with-perms=%s", create_with_perms, -1), + OPT2("--chown-normal", "chown-normal", OPTKEY_CHOWN_NORMAL), + OPT2("--chown-ignore", "chown-ignore", OPTKEY_CHOWN_IGNORE), + OPT2("--chown-deny", "chown-deny", OPTKEY_CHOWN_DENY), + OPT2("--chgrp-normal", "chgrp-normal", OPTKEY_CHGRP_NORMAL), + OPT2("--chgrp-ignore", "chgrp-ignore", OPTKEY_CHGRP_IGNORE), + OPT2("--chgrp-deny", "chgrp-deny", OPTKEY_CHGRP_DENY), + OPT2("--chmod-normal", "chmod-normal", OPTKEY_CHMOD_NORMAL), + OPT2("--chmod-ignore", "chmod-ignore", OPTKEY_CHMOD_IGNORE), + OPT2("--chmod-deny", "chmod-deny", OPTKEY_CHMOD_DENY), + OPT2("--chmod-allow-x", "chmod-allow-x", OPTKEY_CHMOD_ALLOW_X), + OPT2("--xattr-none", "xattr-none", OPTKEY_XATTR_NONE), + OPT2("--xattr-ro", "xattr-ro", OPTKEY_XATTR_READ_ONLY), + OPT2("--xattr-rw", "xattr-rw", OPTKEY_XATTR_READ_WRITE), + OPT2("--ctime-from-mtime", "ctime-from-mtime", OPTKEY_CTIME_FROM_MTIME), + FUSE_OPT_END + }; + + /* General-purpose variables */ + int i, j; + char *p, *tmpstr; + + int fuse_main_return; + + + /* Initialize settings */ + settings.progname = argv[0]; + settings.permchain = permchain_create(); + settings.new_uid = -1; + settings.new_gid = -1; + settings.create_for_uid = -1; + settings.create_for_gid = -1; + settings.mntsrc = NULL; + settings.mntdest = NULL; + settings.create_policy = (getuid() == 0) ? CREATE_AS_USER : CREATE_AS_MOUNTER; + settings.create_permchain = permchain_create(); + settings.chown_policy = CHOWN_NORMAL; + settings.chgrp_policy = CHGRP_NORMAL; + settings.chmod_policy = CHMOD_NORMAL; + settings.chmod_allow_x = 0; + settings.xattr_policy = XATTR_READ_WRITE; + settings.mirrored_users_only = 0; + settings.mirrored_users = NULL; + settings.num_mirrored_users = 0; + settings.mirrored_members = NULL; + settings.num_mirrored_members = 0; + settings.ctime_from_mtime = 0; + atexit(&atexit_func); + + /* Parse options */ + if (fuse_opt_parse(&args, &od, options, &process_option) == -1) + return 1; + + /* Check that a source directory and a mount point was given */ + if (!settings.mntsrc || !settings.mntdest) { + print_usage(my_basename(argv[0])); + return 1; + } + + /* Parse new owner and group */ + if (od.user) { + if (!user_uid(od.user, &settings.new_uid)) { + fprintf(stderr, "Not a valid user ID: %s\n", od.user); + return 1; + } + } + if (od.group) { + if (!group_gid(od.group, &settings.new_gid)) { + fprintf(stderr, "Not a valid group ID: %s\n", od.group); + return 1; + } + } + + /* Parse user and group for new creates */ + if (od.create_for_user) { + if (getuid() != 0) { + fprintf(stderr, "Error: You need to be root to use --create-for-user !\n"); + return 1; + } + if (!user_uid(od.create_for_user, &settings.create_for_uid)) { + fprintf(stderr, "Not a valid user ID: %s\n", od.create_for_user); + return 1; + } + } + if (od.create_for_group) { + if (getuid() != 0) { + fprintf(stderr, "Error: You need to be root to use --create-for-group !\n"); + return 1; + } + if (!group_gid(od.create_for_group, &settings.create_for_gid)) { + fprintf(stderr, "Not a valid group ID: %s\n", od.create_for_group); + return 1; + } + } + + /* Parse mirrored users and groups */ + if (od.mirror && od.mirror_only) { + fprintf(stderr, "Cannot specify both -m|--mirror and -M|--mirror-only\n"); + return 1; + } + if (od.mirror_only) { + settings.mirrored_users_only = 1; + od.mirror = od.mirror_only; + } + if (od.mirror) { + settings.num_mirrored_users = count_chars(od.mirror, ',') + + count_chars(od.mirror, ':') + 1; + settings.num_mirrored_members = ((*od.mirror == '@') ? 1 : 0) + + count_substrs(od.mirror, ",@") + + count_substrs(od.mirror, ":@"); + settings.num_mirrored_users -= settings.num_mirrored_members; + settings.mirrored_users = malloc(settings.num_mirrored_users*sizeof(uid_t)); + settings.mirrored_members = malloc(settings.num_mirrored_members*sizeof(gid_t)); + + i = 0; /* iterate over mirrored_users */ + j = 0; /* iterate over mirrored_members */ + p = od.mirror; + while (i < settings.num_mirrored_users || j < settings.num_mirrored_members) { + tmpstr = strdup_until(p, ",:"); + + if (*tmpstr == '@') { /* This is a group name */ + if (!group_gid(tmpstr + 1, &settings.mirrored_members[j++])) { + fprintf(stderr, "Invalid group ID: '%s'\n", tmpstr + 1); + free(tmpstr); + return 1; + } + } else { + if (!user_uid(tmpstr, &settings.mirrored_users[i++])) { + fprintf(stderr, "Invalid user ID: '%s'\n", tmpstr); + free(tmpstr); + return 1; + } + } + free(tmpstr); + + while (*p != '\0' && *p != ',' && *p != ':') + ++p; + if (*p != '\0') + ++p; + else { + /* Done. The counters should match. */ + assert(i == settings.num_mirrored_users); + assert(j == settings.num_mirrored_members); + } + } + } + + /* Parse permission bits */ + if (od.perms) { + if (add_chmod_rules_to_permchain(od.perms, settings.permchain) != 0) { + fprintf(stderr, "Invalid permission specification: '%s'\n", od.perms); + return 1; + } + } + if (od.create_with_perms) { + if (add_chmod_rules_to_permchain(od.create_with_perms, settings.create_permchain) != 0) { + fprintf(stderr, "Invalid permission specification: '%s'\n", od.create_with_perms); + return 1; + } + } + + + /* Add default fuse options */ + if (!od.no_allow_other) { + fuse_opt_add_arg(&args, "-oallow_other"); + } + + /* We want the kernel to do our access checks for us based on what getattr gives it. */ + fuse_opt_add_arg(&args, "-odefault_permissions"); + + /* We need to disable the attribute cache whenever two users + can see different attributes. For now, only mirroring can do that. */ + if (is_mirroring_enabled()) { + fuse_opt_add_arg(&args, "-oattr_timeout=0"); + } + + /* If the mount source and destination directories are the same + then don't require that the directory be empty. */ + if (strcmp(settings.mntsrc, settings.mntdest) == 0) + fuse_opt_add_arg(&args, "-ononempty"); + + /* Open mount source for chrooting in bindfs_init */ + settings.mntsrc_fd = open(settings.mntsrc, O_RDONLY); + if (settings.mntsrc_fd == -1) { + fprintf(stderr, "Could not open source directory\n"); + return 1; + } + + /* Ignore the umask of the mounter on file creation */ + umask(0); + + /* Remove xattr implementation if the user doesn't want it */ + if (settings.xattr_policy == XATTR_UNIMPLEMENTED) { + bindfs_oper.setxattr = NULL; + bindfs_oper.getxattr = NULL; + bindfs_oper.listxattr = NULL; + bindfs_oper.removexattr = NULL; + } + + fuse_main_return = fuse_main(args.argc, args.argv, &bindfs_oper); + + fuse_opt_free_args(&args); + close(settings.mntsrc_fd); + + return fuse_main_return; +} |