// Copyright (c) Vitaliy Filippov, 2019+ // License: VNPL-1.1 (see README.md for details) // // NFS proxy over VitastorKV database - GETATTR #include #include "nfs_proxy.h" #include "nfs_kv.h" // Attributes are always stored in the inode void kv_read_inode(nfs_proxy_t *proxy, uint64_t ino, std::function cb, bool allow_cache) { if (!ino) { // Zero value can not exist cb(-ENOENT, "", json11::Json()); return; } auto key = kv_inode_key(ino); proxy->db->get(key, [=](int res, const std::string & value) { if (ino == KV_ROOT_INODE && res == -ENOENT) { // Allow root inode to not exist cb(0, "", json11::Json(json11::Json::object{ { "type", "dir" } })); return; } if (res < 0) { if (res != -ENOENT) fprintf(stderr, "Error reading inode %s: %s (code %d)\n", kv_inode_key(ino).c_str(), strerror(-res), res); cb(res, "", json11::Json()); return; } std::string err; auto attrs = json11::Json::parse(value, err); if (err != "") { fprintf(stderr, "Invalid JSON in inode %s = %s: %s\n", kv_inode_key(ino).c_str(), value.c_str(), err.c_str()); res = -EIO; } cb(res, value, attrs); }, allow_cache); } int kv_nfs3_getattr_proc(void *opaque, rpc_op_t *rop) { nfs_client_t *self = (nfs_client_t*)opaque; GETATTR3args *args = (GETATTR3args*)rop->request; GETATTR3res *reply = (GETATTR3res*)rop->reply; std::string fh = args->object; auto ino = kv_fh_inode(fh); if (self->parent->trace) fprintf(stderr, "[%d] GETATTR %ju\n", self->nfs_fd, ino); if (!kv_fh_valid(fh) || !ino) { *reply = (GETATTR3res){ .status = NFS3ERR_INVAL }; rpc_queue_reply(rop); return 0; } kv_read_inode(self->parent, ino, [=](int res, const std::string & value, json11::Json attrs) { if (self->parent->trace) fprintf(stderr, "[%d] GETATTR %ju -> %s\n", self->nfs_fd, ino, value.c_str()); if (res == 0 && self->parent->enforce_perms && !kv_is_accessible(rop->auth_sys, attrs, ACCESS3_READ)) { res = -EACCES; } if (res < 0) { *reply = (GETATTR3res){ .status = vitastor_nfs_map_err(-res) }; } else { *reply = (GETATTR3res){ .status = NFS3_OK, .resok = (GETATTR3resok){ .obj_attributes = get_kv_attributes(self->parent, ino, attrs), }, }; } rpc_queue_reply(rop); }); return 1; } uint32_t kv_get_access(const authsys_parms & auth_sys, const json11::Json & attrs) { uint32_t mode = attrs["mode"].is_null() ? (attrs["type"] == "dir" ? 0755 : 0644) : attrs["mode"].uint64_value(); uint32_t uid = attrs["uid"].uint64_value(); uint32_t gid = attrs["gid"].uint64_value(); uint32_t access = 0; if (!auth_sys.uid) { return (ACCESS3_READ|ACCESS3_LOOKUP|ACCESS3_MODIFY|ACCESS3_EXTEND|ACCESS3_DELETE|ACCESS3_EXECUTE); } if (uid == auth_sys.uid) { access |= ((mode & (1 << 8)) ? ACCESS3_READ|ACCESS3_LOOKUP : 0); access |= ((mode & (1 << 7)) ? ACCESS3_MODIFY|ACCESS3_EXTEND|ACCESS3_DELETE : 0); access |= ((mode & (1 << 6)) ? ACCESS3_EXECUTE : 0); } for (uint32_t i = 0; i < auth_sys.gids.gids_len; i++) { if (gid == auth_sys.gids.gids_val[i]) gid = auth_sys.gid; } if (gid == auth_sys.gid) { access |= ((mode & (1 << 5)) ? ACCESS3_READ|ACCESS3_LOOKUP : 0); access |= ((mode & (1 << 4)) ? ACCESS3_MODIFY|ACCESS3_EXTEND|ACCESS3_DELETE : 0); access |= ((mode & (1 << 3)) ? ACCESS3_EXECUTE : 0); } access |= ((mode & (1 << 2)) ? ACCESS3_READ|ACCESS3_LOOKUP : 0); access |= ((mode & (1 << 1)) ? ACCESS3_MODIFY|ACCESS3_EXTEND|ACCESS3_DELETE : 0); access |= ((mode & (1 << 0)) ? ACCESS3_EXECUTE : 0); return access; } bool kv_is_accessible(const authsys_parms & auth_sys, const json11::Json & attrs, uint32_t access) { uint32_t mode = attrs["mode"].is_null() ? (attrs["type"] == "dir" ? 0755 : 0644) : attrs["mode"].uint64_value(); if (!auth_sys.uid) return true; uint32_t mask = 1 << (access == ACCESS3_EXECUTE ? 0 : (access == ACCESS3_MODIFY || access == ACCESS3_EXTEND || access == ACCESS3_DELETE ? 1 : 2)); if (mode & mask) return true; uint32_t uid = attrs["uid"].uint64_value(); if ((mode & (mask << 6)) && (uint32_t)uid == auth_sys.uid) return true; if ((mode & (mask << 3))) { uint32_t gid = attrs["gid"].uint64_value(); if (gid == auth_sys.gid) return true; for (uint32_t i = 0; i < auth_sys.gids.gids_len; i++) if (gid == auth_sys.gids.gids_val[i]) return true; } return false; } int kv_nfs3_access_proc(void *opaque, rpc_op_t *rop) { nfs_client_t *self = (nfs_client_t*)opaque; ACCESS3args *args = (ACCESS3args*)rop->request; ACCESS3res *reply = (ACCESS3res*)rop->reply; std::string fh = args->object; auto ino = kv_fh_inode(fh); if (self->parent->trace) fprintf(stderr, "[%d] ACCESS %ju\n", self->nfs_fd, ino); if (!kv_fh_valid(fh) || !ino) { *reply = (ACCESS3res){ .status = NFS3ERR_INVAL }; rpc_queue_reply(rop); return 0; } kv_read_inode(self->parent, ino, [=](int res, const std::string & value, json11::Json attrs) { if (self->parent->trace) fprintf(stderr, "[%d] ACCESS %ju -> %s\n", self->nfs_fd, ino, value.c_str()); if (res < 0) { // Broken inode (non-existing), allow only root access if (!rop->auth_sys.uid) { *reply = (ACCESS3res){ .status = NFS3_OK, .resok = (ACCESS3resok){ .access = args->access, }, }; } else { *reply = (ACCESS3res){ .status = vitastor_nfs_map_err(-res) }; } } else { uint32_t actual = kv_get_access(rop->auth_sys, attrs); if (args->access & actual) { *reply = (ACCESS3res){ .status = NFS3_OK, .resok = (ACCESS3resok){ .access = actual, }, }; } else { *reply = (ACCESS3res){ .status = NFS3ERR_ACCES, }; } } rpc_queue_reply(rop); }); return 1; }