/* Copyright (c) 2010 Gluster, Inc. This file is part of GlusterFS. GlusterFS is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. GlusterFS 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ #ifndef _CONFIG_H #define _CONFIG_H #include "config.h" #endif #include "access-control.h" #include "xlator.h" #include "call-stub.h" #include "defaults.h" #include "iatt.h" /* Careful, this function erases the stub from frame->local. Dont call this if * a subsequent callback requires retaining access to the stub. This should be * called at the end of all access-control related operations, i.e. once the * frame will be handed off to the actual fop and the next callback that will * be called is the default callback. IOW, the function where call_resume is * called. * NOTE: this is required because FRAME_DESTROY tries to free frame->local if * it finds it to be non-NULL. */ call_stub_t * __get_frame_stub (call_frame_t *fr) { call_stub_t *st = NULL; if (!fr) return NULL; st = fr->local; fr->local = NULL; return st; } int ac_test_owner_access (struct iatt *ia, uid_t uid, int accesstest) { int ret = -1; if (!ia) return -1; /* First test permissions using the uid. */ if (ia->ia_uid != uid) { ret = -1; goto out; } /* At this point we know, the uid matches that of the stat structure, so * if the caller does not care, we should return success. */ if (ac_test_dontcare (accesstest)) { ret = 0; goto out; } if (ac_test_read (accesstest)) ret = IA_PROT_RUSR (ia->ia_prot); if (ac_test_write (accesstest)) ret = IA_PROT_WUSR (ia->ia_prot); if (ac_test_exec (accesstest)) ret = IA_PROT_XUSR (ia->ia_prot); /* For failed access test for owner, we need to return EACCES */ if (!ret) ret = -1; else ret = 0; out: return ret; } int ac_test_group_access (struct iatt *ia, gid_t gid, gid_t *auxgids, int auxcount, int accesstest) { int ret = -1; int testgid = -1; int x = 0; if (!ia) return -1; /* First, determine which gid to test against. This will be determined * by first checking which of the gids given to us match the gid in the * stat. If none match, then we go to checking with others as the user. */ /* If we are only given the primary gid. Dont depend on @auxgids * being NULL since I know users of this function can pass statically * allocated arrays which cant be NULL and yet contain no valid gids. */ if ((ia->ia_gid != gid) && (auxcount == 0)) { gf_log (ACTRL, GF_LOG_DEBUG, "GID mismatch (orig: %d, user: %d)", ia->ia_gid, gid); ret = -1; goto out; } if (ia->ia_gid == gid) testgid = gid; else { for (; x < auxcount; ++x) { if (ia->ia_gid == auxgids[x]) { testgid = ia->ia_gid; break; } } } /* None of the gids match with the gid in the stat. */ if (testgid == -1) { gf_log (ACTRL, GF_LOG_DEBUG, "None of the gids match with gid " "on the stat"); ret = -1; goto out; } /* At this point, at least one gid matches that in the stat, now we must * check whether the caller is interested in the access check at all. */ if (ac_test_dontcare (accesstest)) { ret = 0; goto out; } if (ac_test_read (accesstest)) ret = IA_PROT_RGRP (ia->ia_prot); if (ac_test_write (accesstest)) ret = IA_PROT_WGRP (ia->ia_prot); if (ac_test_exec (accesstest)) ret = IA_PROT_XGRP (ia->ia_prot); if (!ret) ret = -1; else ret = 0; out: return ret; } int ac_test_other_access (struct iatt *ia, int accesstest) { int ret = 0; if (!ia) return -1; if (ac_test_read (accesstest)) ret = IA_PROT_ROTH (ia->ia_prot); if (ac_test_write (accesstest)) ret = IA_PROT_WOTH (ia->ia_prot); if (ac_test_exec (accesstest)) ret = IA_PROT_XOTH (ia->ia_prot); if (!ret) ret = -1; else ret = 0; return ret; } /* Returns -1 on a failed access test with @operrno set to the relevant error * number. */ int ac_test_access (struct iatt *ia, uid_t uid, gid_t gid, gid_t *auxgids, int auxcount, int accesstest, int testwho, int *operrno) { int ret = -1; if ((!ia) || (!operrno)) return -1; if ((uid == 0) && (gid == 0)) { gf_log (ACTRL, GF_LOG_TRACE, "Root has access"); return 0; } if (ac_test_owner (testwho)) { gf_log (ACTRL, GF_LOG_TRACE, "Testing owner access"); ret = ac_test_owner_access (ia, uid, accesstest); } if (ret == 0) { gf_log (ACTRL, GF_LOG_TRACE, "Owner has access"); goto out; } if (ac_test_group (testwho)) { gf_log (ACTRL, GF_LOG_TRACE, "Testing group access"); ret = ac_test_group_access (ia, gid, auxgids, auxcount, accesstest); } if (ret == 0) { gf_log (ACTRL, GF_LOG_TRACE, "Group has access"); goto out; } if (ac_test_other (testwho)) { gf_log (ACTRL, GF_LOG_TRACE, "Testing other access"); ret = ac_test_other_access (ia, accesstest); } if (ret == 0) gf_log (ACTRL, GF_LOG_TRACE, "Other has access"); out: if (ret == -1) { gf_log (ACTRL, GF_LOG_TRACE, "No access allowed"); *operrno = EPERM; } return ret; } int ac_loc_fill (loc_t *loc, inode_t *inode, inode_t *parent, char *path) { int ret = -EFAULT; if (!loc) return ret; if (inode) { loc->inode = inode_ref (inode); loc->ino = inode->ino; } if (parent) loc->parent = inode_ref (parent); loc->path = gf_strdup (path); if (!loc->path) { gf_log (ACTRL, GF_LOG_ERROR, "strdup failed"); goto loc_wipe; } loc->name = strrchr (loc->path, '/'); if (loc->name) loc->name++; else goto loc_wipe; ret = 0; loc_wipe: if (ret < 0) loc_wipe (loc); return ret; } int ac_inode_loc_fill (inode_t *inode, loc_t *loc) { char *resolvedpath = NULL; inode_t *parent = NULL; int ret = -EFAULT; if ((!inode) || (!loc)) return ret; if ((inode) && (inode->ino == 1)) goto ignore_parent; parent = inode_parent (inode, 0, NULL); if (!parent) goto err; ignore_parent: ret = inode_path (inode, NULL, &resolvedpath); if (ret < 0) goto err; ret = ac_loc_fill (loc, inode, parent, resolvedpath); if (ret < 0) goto err; err: if (parent) inode_unref (parent); if (resolvedpath) GF_FREE (resolvedpath); return ret; } int ac_parent_loc_fill (loc_t *parentloc, loc_t *childloc) { if ((!parentloc) || (!childloc)) return -1; return ac_inode_loc_fill (childloc->parent, parentloc); } int32_t ac_truncate_resume (call_frame_t *frame, xlator_t *this, loc_t *loc, off_t offset) { STACK_WIND (frame, default_truncate_cbk, FIRST_CHILD(this), FIRST_CHILD(this)->fops->truncate, loc, offset); return 0; } int32_t ac_truncate_stat_cbk (call_frame_t *frame, void *cookie, xlator_t *this, int32_t op_ret, int32_t op_errno, struct iatt *buf) { call_stub_t *stub = NULL; stub = __get_frame_stub (frame); if (op_ret == -1) goto out; op_ret = ac_test_access (buf, frame->root->uid, frame->root->gid, frame->root->groups, frame->root->ngrps, ACCTEST_WRITE, ACCTEST_ANY, &op_errno); if (op_ret == -1) goto out; call_resume (stub); out: if (op_ret < 0) { STACK_UNWIND_STRICT (truncate, frame, -1, op_errno, NULL, NULL); if (stub) call_stub_destroy (stub); } return 0; } int32_t ac_truncate (call_frame_t *frame, xlator_t *this, loc_t *loc, off_t offset) { call_stub_t *stub = NULL; int ret = -EFAULT; if (__is_fuse_call (frame)) { ac_truncate_resume (frame, this, loc, offset); return 0; } stub = fop_truncate_stub (frame, ac_truncate_resume, loc, offset); if (!stub) { gf_log (this->name, GF_LOG_ERROR, "cannot create call stub: " "(out of memory)"); ret = -ENOMEM; goto out; } frame->local = stub; STACK_WIND (frame, ac_truncate_stat_cbk, FIRST_CHILD (this), FIRST_CHILD (this)->fops->stat, loc); ret = 0; out: if (ret < 0) STACK_UNWIND_STRICT (truncate, frame, -1, -ret, NULL, NULL); return 0; } int32_t ac_access_resume (call_frame_t *frame, xlator_t *this, loc_t *loc, int32_t mask) { STACK_WIND (frame, default_access_cbk, FIRST_CHILD(this), FIRST_CHILD(this)->fops->access, loc, mask); return 0; } int32_t ac_access_stat_cbk (call_frame_t *frame, void *cookie, xlator_t *this, int32_t op_ret, int32_t op_errno, struct iatt *buf) { call_stub_t *stub = NULL; int32_t mask = 0; int acctest = 0; stub = __get_frame_stub (frame); mask = stub->args.access.mask; /* If mask requests test for file existence then do not * return a failure with ENOENT, instead return a failed * access test. */ if (op_ret == -1) { if (mask & F_OK) op_errno = EACCES; else op_errno = errno; goto out; } if (R_OK & mask) acctest |= ACCTEST_READ; else if (W_OK & mask) acctest |= ACCTEST_WRITE; else if (X_OK & mask) acctest |= ACCTEST_EXEC; else acctest = 0; op_ret = ac_test_access (buf, frame->root->uid, frame->root->gid, frame->root->groups, frame->root->ngrps, acctest, ACCTEST_ANY, &op_errno); if (op_ret == -1) goto out; call_resume (stub); out: if (op_ret < 0) { STACK_UNWIND_STRICT (access, frame, -1, op_errno); if (stub) call_stub_destroy (stub); } return 0; } int32_t ac_access (call_frame_t *frame, xlator_t *this, loc_t *loc, int32_t mask) { call_stub_t *stub = NULL; int ret = -EFAULT; if (__is_fuse_call (frame)) { ac_access_resume (frame, this, loc, mask); return 0; } stub = fop_access_stub (frame, ac_access_resume, loc, mask); if (!stub) { gf_log (this->name, GF_LOG_ERROR, "cannot create call stub: " "(out of memory)"); ret = -ENOMEM; goto out; } frame->local = stub; STACK_WIND (frame, ac_access_stat_cbk, FIRST_CHILD (this), FIRST_CHILD (this)->fops->stat, loc); ret = 0; out: if (ret < 0) STACK_UNWIND_STRICT (access, frame, -1, -ret); return 0; } int32_t ac_readlink_resume (call_frame_t *frame, xlator_t *this, loc_t *loc, size_t size) { STACK_WIND (frame, default_readlink_cbk, FIRST_CHILD(this), FIRST_CHILD(this)->fops->readlink, loc, size); return 0; } int32_t ac_readlink_stat_cbk (call_frame_t *frame, void *cookie, xlator_t *this, int32_t op_ret, int32_t op_errno, struct iatt *buf) { call_stub_t *stub = NULL; stub = __get_frame_stub (frame); if (op_ret == -1) goto out; op_ret = ac_test_access (buf, frame->root->uid, frame->root->gid, frame->root->groups, frame->root->ngrps, ACCTEST_READ, ACCTEST_ANY, &op_errno); if (op_ret == -1) goto out; call_resume (stub); out: if (op_ret < 0) { STACK_UNWIND_STRICT (readlink, frame, -1, op_errno, NULL, NULL); if (stub) call_stub_destroy (stub); } return 0; } int32_t ac_readlink (call_frame_t *frame, xlator_t *this, loc_t *loc, size_t size) { call_stub_t *stub = NULL; int ret = -EFAULT; if (__is_fuse_call (frame)) { ac_readlink_resume (frame, this, loc, size); return 0; } stub = fop_readlink_stub (frame, ac_readlink_resume, loc, size); if (!stub) { gf_log (this->name, GF_LOG_ERROR, "cannot create call stub: " "(out of memory)"); ret = -ENOMEM; goto out; } frame->local = stub; STACK_WIND (frame, ac_readlink_stat_cbk, FIRST_CHILD (this), FIRST_CHILD (this)->fops->stat, loc); ret = 0; out: if (ret < 0) STACK_UNWIND_STRICT (readlink, frame, -1, -ret, NULL, NULL); return 0; } int ac_mknod_resume (call_frame_t *frame, xlator_t *this, loc_t *loc, mode_t mode, dev_t rdev, dict_t *params) { STACK_WIND (frame, default_mknod_cbk, FIRST_CHILD(this), FIRST_CHILD(this)->fops->mknod, loc, mode, rdev, params); return 0; } int32_t ac_mknod_stat_cbk (call_frame_t *frame, void *cookie, xlator_t *this, int32_t op_ret, int32_t op_errno, struct iatt *buf) { call_stub_t *stub = NULL; stub = __get_frame_stub (frame); if (op_ret == -1) goto out; op_ret = ac_test_access (buf, frame->root->uid, frame->root->gid, frame->root->groups, frame->root->ngrps, ACCTEST_WRITE, ACCTEST_ANY, &op_errno); if (op_ret == -1) { op_errno = EACCES; goto out; } call_resume (stub); out: if (op_ret < 0) { STACK_UNWIND_STRICT (mknod, frame, -1, op_errno, NULL, NULL, NULL, NULL); if (stub) call_stub_destroy (stub); } return 0; } int ac_mknod (call_frame_t *frame, xlator_t *this, loc_t *loc, mode_t mode, dev_t rdev, dict_t *params) { call_stub_t *stub = NULL; int ret = -EFAULT; loc_t parentloc = {0, }; if (__is_fuse_call (frame)) { ac_mknod_resume (frame, this, loc, mode, rdev, params); return 0; } stub = fop_mknod_stub (frame, ac_mknod_resume, loc, mode, rdev, params); if (!stub) { gf_log (this->name, GF_LOG_ERROR, "cannot create call stub: " "(out of memory)"); ret = -ENOMEM; goto out; } frame->local = stub; ret = ac_parent_loc_fill (&parentloc, loc); if (ret < 0) goto out; STACK_WIND (frame, ac_mknod_stat_cbk, FIRST_CHILD (this), FIRST_CHILD (this)->fops->stat, &parentloc); loc_wipe (&parentloc); ret = 0; out: if (ret < 0) { /* Erase any stored frame before unwinding. */ stub = __get_frame_stub (frame); if (stub) call_stub_destroy (stub); STACK_UNWIND_STRICT (mknod, frame, -1, -ret, NULL, NULL, NULL, NULL); } return 0; } int ac_mkdir_resume (call_frame_t *frame, xlator_t *this, loc_t *loc, mode_t mode, dict_t *params) { STACK_WIND (frame, default_mkdir_cbk, FIRST_CHILD(this), FIRST_CHILD(this)->fops->mkdir, loc, mode, params); return 0; } int32_t ac_mkdir_stat_cbk (call_frame_t *frame, void *cookie, xlator_t *this, int32_t op_ret, int32_t op_errno, struct iatt *buf) { call_stub_t *stub = NULL; stub = __get_frame_stub (frame); if (op_ret == -1) goto out; op_ret = ac_test_access (buf, frame->root->uid, frame->root->gid, frame->root->groups, frame->root->ngrps, ACCTEST_WRITE, ACCTEST_ANY, &op_errno); if (op_ret == -1) { /* On a failed write test on parent dir, we need to return * EACCES, not EPERM that is returned by default by * ac_test_access. */ op_errno = EACCES; goto out; } call_resume (stub); out: if (op_ret < 0) { STACK_UNWIND_STRICT (mkdir, frame, -1, op_errno, NULL, NULL, NULL, NULL); if (stub) call_stub_destroy (stub); } return 0; } int ac_mkdir (call_frame_t *frame, xlator_t *this, loc_t *loc, mode_t mode, dict_t *params) { call_stub_t *stub = NULL; int ret = -EFAULT; loc_t parentloc = {0, }; if (__is_fuse_call (frame)) { ac_mkdir_resume (frame, this, loc, mode, params); return 0; } stub = fop_mkdir_stub (frame, ac_mkdir_resume, loc, mode, params); if (!stub) { gf_log (this->name, GF_LOG_ERROR, "cannot create call stub: " "(out of memory)"); ret = -ENOMEM; goto out; } frame->local = stub; ret = ac_parent_loc_fill (&parentloc, loc); if (ret < 0) goto out; STACK_WIND (frame, ac_mkdir_stat_cbk, FIRST_CHILD (this), FIRST_CHILD (this)->fops->stat, &parentloc); loc_wipe (&parentloc); ret = 0; out: if (ret < 0) { /* Erase the stored stub before unwinding. */ stub = __get_frame_stub (frame); if (stub) call_stub_destroy (stub); STACK_UNWIND_STRICT (mkdir, frame, -1, -ret, NULL, NULL, NULL, NULL); } return 0; } int32_t ac_unlink_resume (call_frame_t *frame, xlator_t *this, loc_t *loc) { STACK_WIND (frame, default_unlink_cbk, FIRST_CHILD(this), FIRST_CHILD(this)->fops->unlink, loc); return 0; } int32_t ac_unlink_stat_cbk (call_frame_t *frame, void *cookie, xlator_t *this, int32_t op_ret, int32_t op_errno, struct iatt *buf) { call_stub_t *stub = NULL; stub = __get_frame_stub (frame); if (op_ret == -1) goto out; if (frame->root->uid != buf->ia_uid) { op_ret = -1; op_errno = EACCES; goto out; } call_resume (stub); out: if (op_ret < 0) { STACK_UNWIND_STRICT (unlink, frame, -1, op_errno, NULL, NULL); if (stub) call_stub_destroy (stub); } return 0; } int32_t ac_unlink_parent_stat_cbk (call_frame_t *frame, void *cookie, xlator_t *this, int32_t op_ret, int32_t op_errno, struct iatt *buf) { call_stub_t *stub = NULL; stub = frame->local; if (op_ret == -1) goto out; if (buf->ia_prot.sticky == 0) { op_ret = ac_test_access (buf, frame->root->uid, frame->root->gid, frame->root->groups, frame->root->ngrps, ACCTEST_WRITE, ACCTEST_ANY, &op_errno); if (op_ret == -1) { op_errno = EACCES; goto out; } } else { if ((frame->root->uid == 0) || (frame->root->uid == buf->ia_uid)) goto access; STACK_WIND (frame, ac_unlink_stat_cbk, FIRST_CHILD (this), FIRST_CHILD (this)->fops->stat, &stub->args.unlink.loc); goto out; } access: stub = __get_frame_stub (frame); call_resume (stub); out: if (op_ret < 0) { stub = __get_frame_stub (frame); STACK_UNWIND_STRICT (unlink, frame, -1, op_errno, NULL, NULL); if (stub) call_stub_destroy (stub); } return 0; } int32_t ac_unlink (call_frame_t *frame, xlator_t *this, loc_t *loc) { call_stub_t *stub = NULL; int ret = -EFAULT; loc_t parentloc = {0, }; if (__is_fuse_call (frame)) { ac_unlink_resume (frame, this, loc); return 0; } stub = fop_unlink_stub (frame, ac_unlink_resume, loc); if (!stub) { gf_log (this->name, GF_LOG_ERROR, "cannot create call stub: " "(out of memory)"); ret = -ENOMEM; goto out; } frame->local = stub; ret = ac_parent_loc_fill (&parentloc, loc); if (ret < 0) goto out; STACK_WIND (frame, ac_unlink_parent_stat_cbk, FIRST_CHILD (this), FIRST_CHILD (this)->fops->stat, &parentloc); loc_wipe (&parentloc); ret = 0; out: if (ret < 0) { /* Erase the stored stub before unwinding. */ stub = __get_frame_stub (frame); if (stub) call_stub_destroy (stub); STACK_UNWIND_STRICT (unlink, frame, -1, -ret, NULL, NULL); } return 0; } int ac_rmdir_resume (call_frame_t *frame, xlator_t *this, loc_t *loc, int flags) { STACK_WIND (frame, default_rmdir_cbk, FIRST_CHILD(this), FIRST_CHILD(this)->fops->rmdir, loc, flags); return 0; } int32_t ac_rmdir_stat_cbk (call_frame_t *frame, void *cookie, xlator_t *this, int32_t op_ret, int32_t op_errno, struct iatt *buf) { call_stub_t *stub = NULL; stub = __get_frame_stub (frame); if (op_ret == -1) goto out; if (frame->root->uid != buf->ia_uid) { op_ret = -1; op_errno = EACCES; goto out; } call_resume (stub); out: if (op_ret < 0) { STACK_UNWIND_STRICT (rmdir, frame, -1, op_errno, NULL, NULL); if (stub) call_stub_destroy (stub); } return 0; } int32_t ac_rmdir_parent_stat_cbk (call_frame_t *frame, void *cookie, xlator_t *this, int32_t op_ret, int32_t op_errno, struct iatt *buf) { call_stub_t *stub = NULL; stub = frame->local; if (op_ret == -1) goto out; if (buf->ia_prot.sticky == 0) { op_ret = ac_test_access (buf, frame->root->uid, frame->root->gid, frame->root->groups, frame->root->ngrps, ACCTEST_WRITE, ACCTEST_ANY, &op_errno); if (op_ret == -1) { op_errno = EACCES; goto out; } } else { if ((frame->root->uid == 0) || (frame->root->uid == buf->ia_uid)) goto access; STACK_WIND (frame, ac_rmdir_stat_cbk, FIRST_CHILD (this), FIRST_CHILD (this)->fops->stat, &stub->args.rmdir.loc); goto out; } access: stub = __get_frame_stub (frame); call_resume (stub); out: if (op_ret < 0) { stub = __get_frame_stub (frame); STACK_UNWIND_STRICT (rmdir, frame, -1, op_errno, NULL, NULL); if (stub) call_stub_destroy (stub); } return 0; } int ac_rmdir (call_frame_t *frame, xlator_t *this, loc_t *loc, int flags) { call_stub_t *stub = NULL; int ret = -EFAULT; loc_t parentloc = {0, }; if (__is_fuse_call (frame)) { ac_rmdir_resume (frame, this, loc, flags); return 0; } stub = fop_rmdir_stub (frame, ac_rmdir_resume, loc, flags); if (!stub) { gf_log (this->name, GF_LOG_ERROR, "cannot create call stub: " "(out of memory)"); ret = -ENOMEM; goto out; } frame->local = stub; ret = ac_parent_loc_fill (&parentloc, loc); if (ret < 0) goto out; STACK_WIND (frame, ac_rmdir_parent_stat_cbk, FIRST_CHILD (this), FIRST_CHILD (this)->fops->stat, &parentloc); loc_wipe (&parentloc); ret = 0; out: if (ret < 0) { /* Erase the stored stub before unwinding. */ stub = __get_frame_stub (frame); if (stub) call_stub_destroy (stub); STACK_UNWIND_STRICT (rmdir, frame, -1, -ret, NULL, NULL); } return 0; } int32_t ac_symlink_resume (call_frame_t *frame, xlator_t *this, const char *linkname, loc_t *loc, dict_t *params) { STACK_WIND (frame, default_symlink_cbk, FIRST_CHILD(this), FIRST_CHILD(this)->fops->symlink, linkname, loc, params); return 0; } int32_t ac_symlink_stat_cbk (call_frame_t *frame, void *cookie, xlator_t *this, int32_t op_ret, int32_t op_errno, struct iatt *buf) { call_stub_t *stub = NULL; stub = __get_frame_stub (frame); if (op_ret == -1) goto out; op_ret = ac_test_access (buf, frame->root->uid, frame->root->gid, frame->root->groups, frame->root->ngrps, ACCTEST_WRITE, ACCTEST_ANY, &op_errno); if (op_ret == -1) { op_errno = EACCES; goto out; } call_resume (stub); out: if (op_ret < 0) { STACK_UNWIND_STRICT (symlink, frame, -1, op_errno, NULL, NULL, NULL, NULL); if (stub) call_stub_destroy (stub); } return 0; } int ac_symlink (call_frame_t *frame, xlator_t *this, const char *linkname, loc_t *loc, dict_t *params) { call_stub_t *stub = NULL; int ret = -EFAULT; loc_t parentloc = {0, }; if (__is_fuse_call (frame)) { ac_symlink_resume (frame, this, linkname, loc, params); return 0; } stub = fop_symlink_stub (frame, ac_symlink_resume, linkname, loc, params); if (!stub) { gf_log (this->name, GF_LOG_ERROR, "cannot create call stub: " "(out of memory)"); ret = -ENOMEM; goto out; } frame->local = stub; ret = ac_parent_loc_fill (&parentloc, loc); if (ret < 0) goto out; STACK_WIND (frame, ac_symlink_stat_cbk, FIRST_CHILD (this), FIRST_CHILD (this)->fops->stat, &parentloc); loc_wipe (&parentloc); ret = 0; out: if (ret < 0) { /* Erase the stored stub before unwinding. */ stub = __get_frame_stub (frame); if (stub) call_stub_destroy (stub); STACK_UNWIND_STRICT (symlink, frame, -1, -ret, NULL, NULL, NULL, NULL); } return 0; } int32_t ac_rename_resume (call_frame_t *frame, xlator_t *this, loc_t *oldloc, loc_t *newloc) { STACK_WIND (frame, default_rename_cbk, FIRST_CHILD(this), FIRST_CHILD(this)->fops->rename, oldloc, newloc); return 0; } int32_t ac_rename_dst_stat_cbk (call_frame_t *frame, void *cookie, xlator_t *this, int32_t op_ret, int32_t op_errno, struct iatt *buf) { call_stub_t *stub = NULL; stub = __get_frame_stub (frame); if ((op_ret == -1) && (op_errno == ENOENT)) goto access; if (op_ret == -1) goto out; if (frame->root->uid == buf->ia_uid) goto access; else { op_ret = -1; op_errno = EACCES; goto out; } access: call_resume (stub); out: if (op_ret < 0) { STACK_UNWIND_STRICT (rename, frame, -1, op_errno, NULL, NULL, NULL, NULL, NULL); if (stub) call_stub_destroy (stub); } return 0; } int32_t ac_rename_dst_parent_stat_cbk (call_frame_t *frame, void *cookie, xlator_t *this, int32_t op_ret, int32_t op_errno, struct iatt *buf) { call_stub_t *stub = NULL; stub = frame->local; if (op_ret == -1) goto out; if (buf->ia_prot.sticky == 0) { op_ret = ac_test_access (buf, frame->root->uid, frame->root->gid, frame->root->groups, frame->root->ngrps, ACCTEST_WRITE, ACCTEST_ANY, &op_errno); if (op_ret == -1) { op_errno = EACCES; goto out; } } else { if ((frame->root->uid == 0) || (frame->root->uid == buf->ia_uid)) goto access; STACK_WIND (frame, ac_rename_dst_stat_cbk, FIRST_CHILD (this), FIRST_CHILD (this)->fops->stat, &stub->args.rename.new); goto out; } access: stub = __get_frame_stub (frame); call_resume (stub); out: if (op_ret < 0) { stub = __get_frame_stub (frame); STACK_UNWIND_STRICT (rename, frame, -1, op_errno, NULL, NULL, NULL, NULL, NULL); if (stub) call_stub_destroy (stub); } return 0; } int32_t ac_rename_src_stat_cbk (call_frame_t *frame, void *cookie, xlator_t *this, int32_t op_ret, int32_t op_errno, struct iatt *buf) { call_stub_t *stub = NULL; loc_t parentloc = {0, }; stub = frame->local; if (op_ret == -1) goto out; if (buf->ia_uid != frame->root->uid) { op_ret = -1; op_errno = EACCES; goto out; } op_ret = ac_parent_loc_fill (&parentloc, &stub->args.rename.new); if (op_ret < 0) { op_errno = -EFAULT; goto out; } STACK_WIND (frame, ac_rename_dst_parent_stat_cbk, FIRST_CHILD (this), FIRST_CHILD (this)->fops->stat, &parentloc); loc_wipe (&parentloc); out: if (op_ret < 0) { /* Erase the stored stub before unwinding. */ stub = __get_frame_stub (frame); if (stub) call_stub_destroy (stub); STACK_UNWIND_STRICT (rename, frame, -1, op_errno, NULL, NULL, NULL, NULL, NULL); } return 0; } int32_t ac_rename_src_parent_stat_cbk (call_frame_t *frame, void *cookie, xlator_t *this, int32_t op_ret, int32_t op_errno, struct iatt *buf) { call_stub_t *stub = NULL; loc_t parentloc = {0, }; stub = frame->local; if (op_ret == -1) goto out; if (buf->ia_prot.sticky == 0) { op_ret = ac_test_access (buf, frame->root->uid, frame->root->gid, frame->root->groups, frame->root->ngrps, ACCTEST_WRITE, ACCTEST_ANY, &op_errno); if (op_ret == -1) { op_errno = EACCES; goto out; } } else { if ((buf->ia_uid == frame->root->uid) || (frame->root->uid ==0)) goto access; STACK_WIND (frame, ac_rename_src_stat_cbk, FIRST_CHILD (this), FIRST_CHILD (this)->fops->stat, &stub->args.rename.old); goto out; } access: op_ret = ac_parent_loc_fill (&parentloc, &stub->args.rename.new); if (op_ret < 0) { op_errno = -EFAULT; goto out; } STACK_WIND (frame, ac_rename_dst_parent_stat_cbk, FIRST_CHILD (this), FIRST_CHILD (this)->fops->stat, &parentloc); loc_wipe (&parentloc); out: if (op_ret < 0) { /* Erase the stored stub before unwinding. */ stub = __get_frame_stub (frame); if (stub) call_stub_destroy (stub); STACK_UNWIND_STRICT (rename, frame, -1, op_errno, NULL, NULL, NULL, NULL, NULL); } return 0; } int32_t ac_rename (call_frame_t *frame, xlator_t *this, loc_t *oldloc, loc_t *newloc) { call_stub_t *stub = NULL; int ret = -EFAULT; loc_t parentloc = {0, }; if (__is_fuse_call (frame)) { ac_rename_resume (frame, this, oldloc, newloc); return 0; } stub = fop_rename_stub (frame, ac_rename_resume, oldloc, newloc); if (!stub) { gf_log (this->name, GF_LOG_ERROR, "cannot create call stub: " "(out of memory)"); ret = -ENOMEM; goto out; } frame->local = stub; ret = ac_parent_loc_fill (&parentloc, oldloc); if (ret < 0) goto out; STACK_WIND (frame, ac_rename_src_parent_stat_cbk, FIRST_CHILD (this), FIRST_CHILD (this)->fops->stat, &parentloc); loc_wipe (&parentloc); ret = 0; out: if (ret < 0) { /* Erase the stored stub before unwinding. */ stub = __get_frame_stub (frame); if (stub) call_stub_destroy (stub); STACK_UNWIND_STRICT (rename, frame, -1, -ret, NULL, NULL, NULL, NULL, NULL); } return 0; } int32_t ac_link_resume (call_frame_t *frame, xlator_t *this, loc_t *oldloc, loc_t *newloc) { STACK_WIND (frame, default_link_cbk, FIRST_CHILD(this), FIRST_CHILD(this)->fops->link, oldloc, newloc); return 0; } int32_t ac_link_stat_cbk (call_frame_t *frame, void *cookie, xlator_t *this, int32_t op_ret, int32_t op_errno, struct iatt *buf) { call_stub_t *stub = NULL; stub = __get_frame_stub (frame); if (op_ret == -1) goto out; op_ret = ac_test_access (buf, frame->root->uid, frame->root->gid, frame->root->groups, frame->root->ngrps, ACCTEST_WRITE, ACCTEST_ANY, &op_errno); if (op_ret == -1) { /* By default ac_test_access sets the op_errno to EPERM * but in the case of link, we need to return EACCES to meet * posix requirements when a write permission is not available * for the new directory. */ op_errno = EACCES; goto out; } call_resume (stub); out: if (op_ret < 0) { STACK_UNWIND_STRICT (link, frame, -1, op_errno, NULL, NULL, NULL, NULL); if (stub) call_stub_destroy (stub); } return 0; } int32_t ac_link (call_frame_t *frame, xlator_t *this, loc_t *oldloc, loc_t *newloc) { call_stub_t *stub = NULL; int ret = -EFAULT; loc_t parentloc = {0, }; if (__is_fuse_call (frame)) { ac_link_resume (frame, this, oldloc, newloc); return 0; } stub = fop_link_stub (frame, ac_link_resume, oldloc, newloc); if (!stub) { gf_log (this->name, GF_LOG_ERROR, "cannot create call stub: " "(out of memory)"); ret = -ENOMEM; goto out; } frame->local = stub; ret = ac_parent_loc_fill (&parentloc, newloc); if (ret < 0) goto out; STACK_WIND (frame, ac_link_stat_cbk, FIRST_CHILD (this), FIRST_CHILD (this)->fops->stat, &parentloc); loc_wipe (&parentloc); ret = 0; out: if (ret < 0) { /* Erase the stored stub before unwinding. */ stub = __get_frame_stub (frame); if (stub) call_stub_destroy (stub); STACK_UNWIND_STRICT (link, frame, -1, -ret, NULL, NULL, NULL, NULL); } return 0; } int32_t ac_create_resume (call_frame_t *frame, xlator_t *this, loc_t *loc, int32_t flags, mode_t mode, fd_t *fd, dict_t *params) { STACK_WIND (frame, default_create_cbk, FIRST_CHILD(this), FIRST_CHILD(this)->fops->create, loc, flags, mode, fd, params); return 0; } int32_t ac_create_stat_cbk (call_frame_t *frame, void *cookie, xlator_t *this, int32_t op_ret, int32_t op_errno, struct iatt *buf) { call_stub_t *stub = NULL; stub = __get_frame_stub (frame); if (op_ret == -1) goto out; op_ret = ac_test_access (buf, frame->root->uid, frame->root->gid, frame->root->groups, frame->root->ngrps, ACCTEST_WRITE, ACCTEST_ANY, &op_errno); if (op_ret == -1) { op_errno = EACCES; goto out; } call_resume (stub); out: if (op_ret < 0) { STACK_UNWIND_STRICT (create, frame, -1, op_errno, NULL, NULL, NULL, NULL, NULL); if (stub) call_stub_destroy (stub); } return 0; } int32_t ac_create (call_frame_t *frame, xlator_t *this, loc_t *loc, int32_t flags, mode_t mode, fd_t *fd, dict_t *params) { call_stub_t *stub = NULL; int ret = -EFAULT; loc_t parentloc = {0, }; if (__is_fuse_call (frame)) { ac_create_resume (frame, this, loc, flags, mode, fd, params); return 0; } stub = fop_create_stub (frame, ac_create_resume, loc, flags, mode, fd, params); if (!stub) { gf_log (this->name, GF_LOG_ERROR, "cannot create call stub: " "(out of memory)"); ret = -ENOMEM; goto out; } frame->local = stub; ret = ac_parent_loc_fill (&parentloc, loc); if (ret < 0) goto out; STACK_WIND (frame, ac_create_stat_cbk, FIRST_CHILD (this), FIRST_CHILD (this)->fops->stat, &parentloc); loc_wipe (&parentloc); ret = 0; out: if (ret < 0) { /* Erase the stored stub before unwinding. */ stub = __get_frame_stub (frame); if (stub) call_stub_destroy (stub); STACK_UNWIND_STRICT (create, frame, -1, -ret, NULL, NULL, NULL, NULL, NULL); } return 0; } int32_t ac_open_resume (call_frame_t *frame, xlator_t *this, loc_t *loc, int32_t flags, fd_t *fd, int32_t wbflags) { STACK_WIND (frame, default_open_cbk, FIRST_CHILD(this), FIRST_CHILD(this)->fops->open, loc, flags, fd, wbflags); return 0; } int32_t ac_open_create_stat_cbk (call_frame_t *frame, void *cookie, xlator_t *this, int32_t op_ret, int32_t op_errno, struct iatt *buf) { call_stub_t *stub = NULL; stub = __get_frame_stub (frame); if (op_ret == -1) goto out; op_ret = ac_test_access (buf, frame->root->uid, frame->root->gid, frame->root->groups, frame->root->ngrps, ACCTEST_WRITE, ACCTEST_ANY, &op_errno); if (op_ret == -1) { op_errno = EACCES; goto out; } call_resume (stub); out: if (op_ret < 0) { STACK_UNWIND_STRICT (open, frame, -1, op_errno, NULL); if (stub) call_stub_destroy (stub); } return 0; } int ac_open_create (call_stub_t *stub) { int ret = -EFAULT; loc_t parentloc = {0, }; xlator_t *this = NULL; if (!stub) return ret; ret = ac_parent_loc_fill (&parentloc, &stub->args.open.loc); if (ret < 0) goto out; this = stub->frame->this; STACK_WIND (stub->frame, ac_open_create_stat_cbk, FIRST_CHILD (this), FIRST_CHILD (this)->fops->stat, &parentloc); loc_wipe (&parentloc); ret = 0; out: return ret; } int32_t ac_open_only_stat_cbk (call_frame_t *frame, void *cookie, xlator_t *this, int32_t op_ret, int32_t op_errno, struct iatt *buf) { call_stub_t *stub = NULL; int acctest = 0; int32_t flags = 0; stub = __get_frame_stub (frame); if (op_ret == -1) goto out; flags = stub->args.open.flags; /* The permissions we test for depend on how the open needs to be * performed. */ if ((flags & O_ACCMODE) == O_RDONLY) acctest = ACCTEST_READ; else if (((flags & O_ACCMODE) == O_RDWR) || ((flags & O_ACCMODE) == O_WRONLY)) acctest = ACCTEST_WRITE; op_ret = ac_test_access (buf, frame->root->uid, frame->root->gid, frame->root->groups, frame->root->ngrps, acctest, ACCTEST_ANY, &op_errno); if (op_ret == -1) goto out; call_resume (stub); out: if (op_ret < 0) { STACK_UNWIND_STRICT (open, frame, -1, op_errno, NULL); if (stub) call_stub_destroy (stub); } return 0; } int ac_open_only (call_stub_t *stub) { int ret = -EFAULT; xlator_t *this = NULL; if (!stub) return ret; this = stub->frame->this; STACK_WIND (stub->frame, ac_open_only_stat_cbk, FIRST_CHILD (this), FIRST_CHILD (this)->fops->stat, &stub->args.open.loc); return 0; } int32_t ac_open (call_frame_t *frame, xlator_t *this, loc_t *loc, int32_t flags, fd_t *fd, int32_t wbflags) { call_stub_t *stub = NULL; int ret = -EFAULT; if (__is_fuse_call (frame)) { ret = ac_open_resume (frame, this, loc, flags, fd, wbflags); return 0; } stub = fop_open_stub (frame, ac_open_resume, loc, flags, fd, wbflags); if (!stub) { gf_log (this->name, GF_LOG_ERROR, "cannot create call stub: " "(out of memory)"); ret = -ENOMEM; goto out; } frame->local = stub; /* If we are not supposed to create the file then there is no need to * check the parent dir permissions. */ if (flags & O_CREAT) ret = ac_open_create (stub); else ret = ac_open_only (stub); out: if (ret < 0) { /* Erase the stored stub before unwinding. */ stub = __get_frame_stub (frame); if (stub) call_stub_destroy (stub); STACK_UNWIND_STRICT (open, frame, -1, -ret, NULL); } return 0; } int32_t ac_readv_resume (call_frame_t *frame, xlator_t *this, fd_t *fd, size_t size, off_t offset) { STACK_WIND (frame, default_readv_cbk, FIRST_CHILD (this), FIRST_CHILD (this)->fops->readv, fd, size, offset); return 0; } int32_t ac_readv_fstat_cbk (call_frame_t *frame, void *cookie, xlator_t *this, int32_t op_ret, int32_t op_errno, struct iatt *buf) { call_stub_t *stub = NULL; stub = __get_frame_stub (frame); if (op_ret == -1) goto out; op_ret = ac_test_access (buf, frame->root->uid, frame->root->gid, frame->root->groups, frame->root->ngrps, ACCTEST_READ, ACCTEST_ANY, &op_errno); if (op_ret == -1) { op_errno = EACCES; goto out; } call_resume (stub); out: if (op_ret < 0) { STACK_UNWIND_STRICT (readv, frame, -1, op_errno, NULL, 0, NULL, NULL); if (stub) call_stub_destroy (stub); } return 0; } int32_t ac_readv (call_frame_t *frame, xlator_t *this, fd_t *fd, size_t size, off_t offset) { call_stub_t *stub = NULL; int ret = -EFAULT; if (__is_fuse_call (frame)) { ret = ac_readv_resume (frame, this, fd, size, offset); return 0; } stub = fop_readv_stub (frame, ac_readv_resume, fd, size, offset); if (!stub) { gf_log (this->name, GF_LOG_ERROR, "cannot create call stub: " "(out of memory)"); ret = -ENOMEM; goto out; } frame->local = stub; STACK_WIND (frame, ac_readv_fstat_cbk, FIRST_CHILD (this), FIRST_CHILD (this)->fops->fstat, fd); ret = 0; out: if (ret < 0) STACK_UNWIND_STRICT (readv, frame, -1, -ret, NULL, 0, NULL, NULL); return 0; } int32_t ac_writev_resume (call_frame_t *frame, xlator_t *this, fd_t *fd, struct iovec *vector, int32_t count, off_t offset, struct iobref *iobref) { STACK_WIND (frame, default_writev_cbk, FIRST_CHILD (this), FIRST_CHILD (this)->fops->writev, fd, vector, count, offset, iobref); return 0; } int32_t ac_writev_fstat_cbk (call_frame_t *frame, void *cookie, xlator_t *this, int32_t op_ret, int32_t op_errno, struct iatt *buf) { call_stub_t *stub = NULL; stub = __get_frame_stub (frame); if (op_ret == -1) goto out; op_ret = ac_test_access (buf, frame->root->uid, frame->root->gid, frame->root->groups, frame->root->ngrps, ACCTEST_WRITE, ACCTEST_ANY, &op_errno); if (op_ret == -1) { op_errno = EACCES; goto out; } call_resume (stub); out: if (op_ret < 0) { STACK_UNWIND_STRICT (writev, frame, -1, op_errno, NULL, NULL); if (stub) call_stub_destroy (stub); } return 0; } int32_t ac_writev (call_frame_t *frame, xlator_t *this, fd_t *fd, struct iovec *vector, int32_t count, off_t offset, struct iobref *iobref) { call_stub_t *stub = NULL; int ret = -EFAULT; if (__is_fuse_call (frame)) { ret = ac_writev_resume (frame, this, fd, vector, count, offset, iobref); return 0; } stub = fop_writev_stub (frame, ac_writev_resume, fd, vector, count, offset, iobref); if (!stub) { gf_log (this->name, GF_LOG_ERROR, "cannot create call stub: " "(out of memory)"); ret = -ENOMEM; goto out; } frame->local = stub; STACK_WIND (frame, ac_writev_fstat_cbk, FIRST_CHILD (this), FIRST_CHILD (this)->fops->fstat, fd); ret = 0; out: if (ret < 0) STACK_UNWIND_STRICT (writev, frame, -1, -ret, NULL, NULL); return 0; } int32_t ac_opendir_resume (call_frame_t *frame, xlator_t *this, loc_t *loc, fd_t *fd) { STACK_WIND (frame, default_opendir_cbk, FIRST_CHILD(this), FIRST_CHILD(this)->fops->opendir, loc, fd); return 0; } int32_t ac_opendir_stat_cbk (call_frame_t *frame, void *cookie, xlator_t *this, int32_t op_ret, int32_t op_errno, struct iatt *buf) { call_stub_t *stub = NULL; stub = __get_frame_stub (frame); if (op_ret == -1) goto out; op_ret = ac_test_access (buf, frame->root->uid, frame->root->gid, frame->root->groups, frame->root->ngrps, ACCTEST_READ, ACCTEST_ANY, &op_errno); if (op_ret == -1) goto out; call_resume (stub); out: if (op_ret < 0) { STACK_UNWIND_STRICT (opendir, frame, -1, op_errno, NULL); if (stub) call_stub_destroy (stub); } return 0; } int32_t ac_opendir (call_frame_t *frame, xlator_t *this, loc_t *loc, fd_t *fd) { call_stub_t *stub = NULL; int ret = -EFAULT; if (__is_fuse_call (frame)) { ret = ac_opendir_resume (frame, this, loc, fd); return 0; } stub = fop_opendir_stub (frame, ac_opendir_resume, loc, fd); if (!stub) { gf_log (this->name, GF_LOG_ERROR, "cannot create call stub: " "(out of memory)"); ret = -ENOMEM; goto out; } frame->local = stub; STACK_WIND (frame, ac_opendir_stat_cbk, FIRST_CHILD (this), FIRST_CHILD (this)->fops->stat, loc); ret = 0; out: if (ret < 0) STACK_UNWIND_STRICT (opendir, frame, -1, -ret, NULL); return 0; } int32_t ac_setattr_resume (call_frame_t *frame, xlator_t *this, loc_t *loc, struct iatt *buf, int32_t valid) { STACK_WIND (frame, default_setattr_cbk, FIRST_CHILD(this), FIRST_CHILD(this)->fops->setattr, loc, buf, valid); return 0; } int32_t ac_setattr_stat_cbk (call_frame_t *frame, void *cookie, xlator_t *this, int32_t op_ret, int32_t op_errno, struct iatt *buf) { call_stub_t *stub = NULL; int32_t valid = 0; struct iatt *setbuf = NULL; stub = __get_frame_stub (frame); if (op_ret == -1) goto out; if ((frame->root->uid != buf->ia_uid) && (frame->root->uid != 0)) { op_ret = ac_test_access (buf, frame->root->uid, frame->root->gid, frame->root->groups, frame->root->ngrps, ACCTEST_WRITE, ACCTEST_ANY, &op_errno); if (op_ret == -1) { gf_log (ACTRL, GF_LOG_DEBUG, "Don't have write permision"); goto out; } } valid = stub->args.setattr.valid; setbuf = &stub->args.setattr.stbuf; if (gf_attr_uid_set (valid) || gf_attr_gid_set (valid) || gf_attr_mode_set(valid)) { /* chown/chmod returns EPERM if the operation would change the * ownership/permissions, but the effective user ID is not the * super-user and the process is not an owner of the file. * Ref: posix-testsuite/chown/07.t */ if ((frame->root->uid != 0) && gf_attr_mode_set(valid)) { if (frame->root->uid != buf->ia_uid) { gf_log (ACTRL, GF_LOG_DEBUG, "Mismatch effective_uid %d file_uid %d", frame->root->uid, buf->ia_uid); op_ret = -1; op_errno = EPERM; goto out; } /* POSIX: If the calling process does not have appropriate privileges, and if the group ID of the file does not match the effective group ID or one of the supplementary group IDs and if the file is a regular file, bit S_ISGID (set-group-ID on execution) in the file's mode shall be cleared upon successful return from chmod().*/ if (setbuf->ia_prot.sgid == 1) { op_ret = ac_test_access (buf, 0, frame->root->gid, frame->root->groups, frame->root->ngrps, ACCTEST_DONTCARE, ACCTEST_GROUP, &op_errno); if (op_ret == -1) { gf_log (ACTRL, GF_LOG_DEBUG, "Reseting set_gid bit"); setbuf->ia_prot.sgid = 0; op_ret = 0; } } } if ((frame->root->uid != 0) && gf_attr_uid_set (valid)) { /*Only super user can change the owner, * for other users changing uid should match * owner uid*/ if (setbuf->ia_uid != buf->ia_uid) { gf_log (ACTRL, GF_LOG_DEBUG, "Mismatch set_uid %d file_uid %d", setbuf->ia_uid, buf->ia_uid); op_ret = -1; op_errno = EPERM; goto out; } } /* non-super-user can modify file group if he is owner of a * file and gid he is setting is in his groups list. * Ref: posix-testsuite/chown/00.t */ if ((frame->root->uid != 0) && (gf_attr_gid_set (valid))) { if (frame->root->uid != buf->ia_uid) { gf_log (ACTRL, GF_LOG_DEBUG, "Mismatch effective_uid %d file_uid %d", frame->root->uid, buf->ia_uid); op_ret = -1; op_errno = EPERM; goto out; } op_ret = ac_test_access (setbuf, 0, frame->root->gid, frame->root->groups, frame->root->ngrps, ACCTEST_DONTCARE, ACCTEST_GROUP, &op_errno); if (op_ret == -1) { gf_log (ACTRL, GF_LOG_DEBUG, "Set_gid %d is not in aux_gid_list", setbuf->ia_gid); goto out; } } } call_resume (stub); out: if (op_ret < 0) { STACK_UNWIND_STRICT (setattr, frame, -1, op_errno, NULL, NULL); if (stub) call_stub_destroy (stub); } return 0; } int32_t ac_setattr (call_frame_t *frame, xlator_t *this, loc_t *loc, struct iatt *buf, int32_t valid) { call_stub_t *stub = NULL; int ret = -EFAULT; if (__is_fuse_call (frame)) { ret = ac_setattr_resume (frame, this, loc, buf, valid); return 0; } stub = fop_setattr_stub (frame, ac_setattr_resume, loc, buf, valid); if (!stub) { gf_log (this->name, GF_LOG_ERROR, "cannot create call stub: " "(out of memory)"); ret = -ENOMEM; goto out; } frame->local = stub; STACK_WIND (frame, ac_setattr_stat_cbk, FIRST_CHILD (this), FIRST_CHILD (this)->fops->stat, loc); ret = 0; out: if (ret < 0) STACK_UNWIND_STRICT (setattr, frame, -1, -ret, NULL, NULL); return 0; } int32_t ac_fsetattr_resume (call_frame_t *frame, xlator_t *this, fd_t *fd, struct iatt *buf, int32_t valid) { STACK_WIND (frame, default_fsetattr_cbk, FIRST_CHILD(this), FIRST_CHILD(this)->fops->fsetattr, fd, buf, valid); return 0; } int32_t ac_fsetattr_fstat_cbk (call_frame_t *frame, void *cookie, xlator_t *this, int32_t op_ret, int32_t op_errno, struct iatt *buf) { call_stub_t *stub = NULL; int32_t valid = 0; struct iatt *setbuf = NULL; stub = __get_frame_stub (frame); if (op_ret == -1) goto out; if ((frame->root->uid != buf->ia_uid) && (frame->root->uid != 0)) { op_ret = ac_test_access (buf, frame->root->uid, frame->root->gid, frame->root->groups, frame->root->ngrps, ACCTEST_WRITE, ACCTEST_ANY, &op_errno); if (op_ret == -1) { gf_log (ACTRL, GF_LOG_DEBUG, "Don't have write permision"); goto out; } } valid = stub->args.fsetattr.valid; setbuf = &stub->args.fsetattr.stbuf; if (gf_attr_uid_set (valid) || gf_attr_gid_set (valid) || gf_attr_mode_set(valid)) { /* chown/chmod returns EPERM if the operation would change the * ownership/permissions, but the effective user ID is not the * super-user and the process is not an owner of the file. * Ref: posix-testsuite/chown/07.t */ if ((frame->root->uid != 0) && gf_attr_mode_set(valid)) { if (frame->root->uid != buf->ia_uid) { gf_log (ACTRL, GF_LOG_DEBUG, "Mismatch effective_uid %d file_uid %d", frame->root->uid, buf->ia_uid); op_ret = -1; op_errno = EPERM; goto out; } /* POSIX: If the calling process does not have appropriate privileges, and if the group ID of the file does not match the effective group ID or one of the supplementary group IDs and if the file is a regular file, bit S_ISGID (set-group-ID on execution) in the file's mode shall be cleared upon successful return from chmod().*/ if (setbuf->ia_prot.sgid == 1) { op_ret = ac_test_access (buf, 0, frame->root->gid, frame->root->groups, frame->root->ngrps, ACCTEST_DONTCARE, ACCTEST_GROUP, &op_errno); if (op_ret == -1) { gf_log (ACTRL, GF_LOG_DEBUG, "Reseting set_gid bit"); setbuf->ia_prot.sgid = 0; op_ret = 0; } } } if ((frame->root->uid != 0) && gf_attr_uid_set (valid)) { /*Only super user can change the owner, * for other users changing uid should match * owner uid*/ if (setbuf->ia_uid != buf->ia_uid) { gf_log (ACTRL, GF_LOG_DEBUG, "Mismatch set_uid %d file_uid %d", setbuf->ia_uid, buf->ia_uid); op_ret = -1; op_errno = EPERM; goto out; } } /* non-super-user can modify file group if he is owner of a * file and gid he is setting is in his groups list. * Ref: posix-testsuite/chown/00.t */ if ((frame->root->uid != 0) && (gf_attr_gid_set (valid))) { if (frame->root->uid != buf->ia_uid) { gf_log (ACTRL, GF_LOG_DEBUG, "Mismatch effective_uid %d file_uid %d", frame->root->uid, buf->ia_uid); op_ret = -1; op_errno = EPERM; goto out; } op_ret = ac_test_access (setbuf, 0, frame->root->gid, frame->root->groups, frame->root->ngrps, ACCTEST_DONTCARE, ACCTEST_GROUP, &op_errno); if (op_ret == -1) { gf_log (ACTRL, GF_LOG_DEBUG, "Set_gid %d is not in aux_gid_list", setbuf->ia_gid); goto out; } } } call_resume (stub); out: if (op_ret < 0) { STACK_UNWIND_STRICT (fsetattr, frame, -1, op_errno, NULL, NULL); if (stub) call_stub_destroy (stub); } return 0; } int32_t ac_fsetattr (call_frame_t *frame, xlator_t *this, fd_t *fd, struct iatt *buf, int32_t valid) { call_stub_t *stub = NULL; int ret = -EFAULT; if (__is_fuse_call (frame)) { ret = ac_fsetattr_resume (frame, this, fd, buf, valid); return 0; } stub = fop_fsetattr_stub (frame, ac_fsetattr_resume, fd, buf, valid); if (!stub) { gf_log (this->name, GF_LOG_ERROR, "cannot create call stub: " "(out of memory)"); ret = -ENOMEM; goto out; } frame->local = stub; STACK_WIND (frame, ac_fsetattr_fstat_cbk, FIRST_CHILD (this), FIRST_CHILD (this)->fops->fstat, fd); ret = 0; out: if (ret < 0) STACK_UNWIND_STRICT (fsetattr, frame, -1, -ret, NULL, NULL); return 0; } struct xlator_fops fops = { .truncate = ac_truncate, .access = ac_access, .readlink = ac_readlink, .mknod = ac_mknod, .mkdir = ac_mkdir, .unlink = ac_unlink, .rmdir = ac_rmdir, .symlink = ac_symlink, .rename = ac_rename, .link = ac_link, .create = ac_create, .open = ac_open, /* * Allow Writes and Reads to proceed without permission checks because: * a. We expect that when the fds are opened, thats when the perm checks happen * depending on the read/write mode used. * * b. In case of nfs clients, we expect the nfs clients to perform the checks * based on getattr/access nfs requests. * * Keep these functions around in case we ever run into a nfs client that * depends on nfs server to perform these checks. Till then, just remove the * references from here instead. .readv = ac_readv, .writev = ac_writev, */ .opendir = ac_opendir, .setattr = ac_setattr, .fsetattr = ac_fsetattr, }; int init (xlator_t *this) { int ret = -1; if (!this->children || this->children->next) { gf_log (this->name, GF_LOG_ERROR, "FATAL: access-control not configured with " "exactly one child"); goto out; } if (!this->parents) { gf_log (this->name, GF_LOG_WARNING, "dangling volume. check volfile "); } ret = 0; out: return ret; } void fini (xlator_t *this) { return; } struct xlator_cbks cbks = { };