/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2014 Nexenta Systems, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * These are the interface routines for the server side of the * NFS ACL server. See the NFS ACL protocol specification * for a description of this interface. */ /* ARGSUSED */ void acl2_getacl(GETACL2args *args, GETACL2res *resp, struct exportinfo *exi, struct svc_req *req, cred_t *cr, bool_t ro) { int error; vnode_t *vp; vattr_t va; vp = nfs_fhtovp(&args->fh, exi); if (vp == NULL) { resp->status = NFSERR_STALE; return; } bzero((caddr_t)&resp->resok.acl, sizeof (resp->resok.acl)); resp->resok.acl.vsa_mask = args->mask; error = VOP_GETSECATTR(vp, &resp->resok.acl, 0, cr, NULL); if ((error == ENOSYS) && !(exi->exi_export.ex_flags & EX_NOACLFAB)) { /* * If the underlying file system doesn't support * aclent_t type acls, fabricate an acl. This is * required in order to to support existing clients * that require the call to VOP_GETSECATTR to * succeed while making the assumption that all * file systems support aclent_t type acls. This * causes problems for servers exporting ZFS file * systems because ZFS supports ace_t type acls, * and fails (with ENOSYS) when asked for aclent_t * type acls. * * Note: if the fs_fab_acl() fails, we have other problems. * This error should be returned to the caller. */ error = fs_fab_acl(vp, &resp->resok.acl, 0, cr, NULL); } if (error) { VN_RELE(vp); resp->status = puterrno(error); return; } va.va_mask = AT_ALL; error = rfs4_delegated_getattr(vp, &va, 0, cr); VN_RELE(vp); /* check for overflowed values */ if (!error) { error = vattr_to_nattr(&va, &resp->resok.attr); } if (error) { resp->status = puterrno(error); if (resp->resok.acl.vsa_aclcnt > 0 && resp->resok.acl.vsa_aclentp != NULL) { kmem_free((caddr_t)resp->resok.acl.vsa_aclentp, resp->resok.acl.vsa_aclcnt * sizeof (aclent_t)); } if (resp->resok.acl.vsa_dfaclcnt > 0 && resp->resok.acl.vsa_dfaclentp != NULL) { kmem_free((caddr_t)resp->resok.acl.vsa_dfaclentp, resp->resok.acl.vsa_dfaclcnt * sizeof (aclent_t)); } return; } resp->status = NFS_OK; if (!(args->mask & NA_ACL)) { if (resp->resok.acl.vsa_aclcnt > 0 && resp->resok.acl.vsa_aclentp != NULL) { kmem_free((caddr_t)resp->resok.acl.vsa_aclentp, resp->resok.acl.vsa_aclcnt * sizeof (aclent_t)); } resp->resok.acl.vsa_aclentp = NULL; } if (!(args->mask & NA_DFACL)) { if (resp->resok.acl.vsa_dfaclcnt > 0 && resp->resok.acl.vsa_dfaclentp != NULL) { kmem_free((caddr_t)resp->resok.acl.vsa_dfaclentp, resp->resok.acl.vsa_dfaclcnt * sizeof (aclent_t)); } resp->resok.acl.vsa_dfaclentp = NULL; } } void * acl2_getacl_getfh(GETACL2args *args) { return (&args->fh); } void acl2_getacl_free(GETACL2res *resp) { if (resp->status == NFS_OK) { if (resp->resok.acl.vsa_aclcnt > 0 && resp->resok.acl.vsa_aclentp != NULL) { kmem_free((caddr_t)resp->resok.acl.vsa_aclentp, resp->resok.acl.vsa_aclcnt * sizeof (aclent_t)); } if (resp->resok.acl.vsa_dfaclcnt > 0 && resp->resok.acl.vsa_dfaclentp != NULL) { kmem_free((caddr_t)resp->resok.acl.vsa_dfaclentp, resp->resok.acl.vsa_dfaclcnt * sizeof (aclent_t)); } } } /* ARGSUSED */ void acl2_setacl(SETACL2args *args, SETACL2res *resp, struct exportinfo *exi, struct svc_req *req, cred_t *cr, bool_t ro) { int error; vnode_t *vp; vattr_t va; vp = nfs_fhtovp(&args->fh, exi); if (vp == NULL) { resp->status = NFSERR_STALE; return; } if (rdonly(ro, vp)) { VN_RELE(vp); resp->status = NFSERR_ROFS; return; } (void) VOP_RWLOCK(vp, V_WRITELOCK_TRUE, NULL); error = VOP_SETSECATTR(vp, &args->acl, 0, cr, NULL); if (error) { VOP_RWUNLOCK(vp, V_WRITELOCK_TRUE, NULL); VN_RELE(vp); resp->status = puterrno(error); return; } va.va_mask = AT_ALL; error = rfs4_delegated_getattr(vp, &va, 0, cr); VOP_RWUNLOCK(vp, V_WRITELOCK_TRUE, NULL); VN_RELE(vp); /* check for overflowed values */ if (!error) { error = vattr_to_nattr(&va, &resp->resok.attr); } if (error) { resp->status = puterrno(error); return; } resp->status = NFS_OK; } void * acl2_setacl_getfh(SETACL2args *args) { return (&args->fh); } /* ARGSUSED */ void acl2_getattr(GETATTR2args *args, GETATTR2res *resp, struct exportinfo *exi, struct svc_req *req, cred_t *cr, bool_t ro) { int error; vnode_t *vp; vattr_t va; vp = nfs_fhtovp(&args->fh, exi); if (vp == NULL) { resp->status = NFSERR_STALE; return; } va.va_mask = AT_ALL; error = rfs4_delegated_getattr(vp, &va, 0, cr); VN_RELE(vp); /* check for overflowed values */ if (!error) { error = vattr_to_nattr(&va, &resp->resok.attr); } if (error) { resp->status = puterrno(error); return; } resp->status = NFS_OK; } void * acl2_getattr_getfh(GETATTR2args *args) { return (&args->fh); } /* ARGSUSED */ void acl2_access(ACCESS2args *args, ACCESS2res *resp, struct exportinfo *exi, struct svc_req *req, cred_t *cr, bool_t ro) { int error; vnode_t *vp; vattr_t va; int checkwriteperm; vp = nfs_fhtovp(&args->fh, exi); if (vp == NULL) { resp->status = NFSERR_STALE; return; } /* * If the file system is exported read only, it is not appropriate * to check write permissions for regular files and directories. * Special files are interpreted by the client, so the underlying * permissions are sent back to the client for interpretation. */ if (rdonly(ro, vp) && (vp->v_type == VREG || vp->v_type == VDIR)) checkwriteperm = 0; else checkwriteperm = 1; /* * We need the mode so that we can correctly determine access * permissions relative to a mandatory lock file. Access to * mandatory lock files is denied on the server, so it might * as well be reflected to the server during the open. */ va.va_mask = AT_MODE; error = VOP_GETATTR(vp, &va, 0, cr, NULL); if (error) { VN_RELE(vp); resp->status = puterrno(error); return; } resp->resok.access = 0; if (args->access & ACCESS2_READ) { error = VOP_ACCESS(vp, VREAD, 0, cr, NULL); if (!error && !MANDLOCK(vp, va.va_mode)) resp->resok.access |= ACCESS2_READ; } if ((args->access & ACCESS2_LOOKUP) && vp->v_type == VDIR) { error = VOP_ACCESS(vp, VEXEC, 0, cr, NULL); if (!error) resp->resok.access |= ACCESS2_LOOKUP; } if (checkwriteperm && (args->access & (ACCESS2_MODIFY|ACCESS2_EXTEND))) { error = VOP_ACCESS(vp, VWRITE, 0, cr, NULL); if (!error && !MANDLOCK(vp, va.va_mode)) resp->resok.access |= (args->access & (ACCESS2_MODIFY|ACCESS2_EXTEND)); } if (checkwriteperm && (args->access & ACCESS2_DELETE) && (vp->v_type == VDIR)) { error = VOP_ACCESS(vp, VWRITE, 0, cr, NULL); if (!error) resp->resok.access |= ACCESS2_DELETE; } if (args->access & ACCESS2_EXECUTE) { error = VOP_ACCESS(vp, VEXEC, 0, cr, NULL); if (!error && !MANDLOCK(vp, va.va_mode)) resp->resok.access |= ACCESS2_EXECUTE; } va.va_mask = AT_ALL; error = rfs4_delegated_getattr(vp, &va, 0, cr); VN_RELE(vp); /* check for overflowed values */ if (!error) { error = vattr_to_nattr(&va, &resp->resok.attr); } if (error) { resp->status = puterrno(error); return; } resp->status = NFS_OK; } void * acl2_access_getfh(ACCESS2args *args) { return (&args->fh); } /* ARGSUSED */ void acl2_getxattrdir(GETXATTRDIR2args *args, GETXATTRDIR2res *resp, struct exportinfo *exi, struct svc_req *req, cred_t *cr, bool_t ro) { int error; int flags; vnode_t *vp, *avp; vp = nfs_fhtovp(&args->fh, exi); if (vp == NULL) { resp->status = NFSERR_STALE; return; } flags = LOOKUP_XATTR; if (args->create) flags |= CREATE_XATTR_DIR; else { ulong_t val = 0; error = VOP_PATHCONF(vp, _PC_SATTR_EXISTS, &val, cr, NULL); if (!error && val == 0) { error = VOP_PATHCONF(vp, _PC_XATTR_EXISTS, &val, cr, NULL); if (!error && val == 0) { VN_RELE(vp); resp->status = NFSERR_NOENT; return; } } } error = VOP_LOOKUP(vp, "", &avp, NULL, flags, NULL, cr, NULL, NULL, NULL); if (!error && avp == vp) { /* lookup of "" on old FS? */ error = EINVAL; VN_RELE(avp); } if (!error) { struct vattr va; va.va_mask = AT_ALL; error = rfs4_delegated_getattr(avp, &va, 0, cr); if (!error) { error = vattr_to_nattr(&va, &resp->resok.attr); if (!error) error = makefh(&resp->resok.fh, avp, exi); } VN_RELE(avp); } VN_RELE(vp); if (error) { resp->status = puterrno(error); return; } resp->status = NFS_OK; } void * acl2_getxattrdir_getfh(GETXATTRDIR2args *args) { return (&args->fh); } void acl3_getacl(GETACL3args *args, GETACL3res *resp, struct exportinfo *exi, struct svc_req *req __unused, cred_t *cr, bool_t ro __unused) { int error; vnode_t *vp; vattr_t *vap; vattr_t va; vap = NULL; vp = nfs3_fhtovp(&args->fh, exi); if (vp == NULL) { error = ESTALE; goto out; } va.va_mask = AT_ALL; vap = rfs4_delegated_getattr(vp, &va, 0, cr) ? NULL : &va; bzero((caddr_t)&resp->resok.acl, sizeof (resp->resok.acl)); resp->resok.acl.vsa_mask = args->mask; error = VOP_GETSECATTR(vp, &resp->resok.acl, 0, cr, NULL); if ((error == ENOSYS) && !(exi->exi_export.ex_flags & EX_NOACLFAB)) { /* * If the underlying file system doesn't support * aclent_t type acls, fabricate an acl. This is * required in order to to support existing clients * that require the call to VOP_GETSECATTR to * succeed while making the assumption that all * file systems support aclent_t type acls. This * causes problems for servers exporting ZFS file * systems because ZFS supports ace_t type acls, * and fails (with ENOSYS) when asked for aclent_t * type acls. * * Note: if the fs_fab_acl() fails, we have other problems. * This error should be returned to the caller. */ error = fs_fab_acl(vp, &resp->resok.acl, 0, cr, NULL); } if (error) goto out; va.va_mask = AT_ALL; vap = rfs4_delegated_getattr(vp, &va, 0, cr) ? NULL : &va; VN_RELE(vp); resp->status = NFS3_OK; vattr_to_post_op_attr(vap, &resp->resok.attr); if (!(args->mask & NA_ACL)) { if (resp->resok.acl.vsa_aclcnt > 0 && resp->resok.acl.vsa_aclentp != NULL) { kmem_free((caddr_t)resp->resok.acl.vsa_aclentp, resp->resok.acl.vsa_aclcnt * sizeof (aclent_t)); } resp->resok.acl.vsa_aclentp = NULL; } if (!(args->mask & NA_DFACL)) { if (resp->resok.acl.vsa_dfaclcnt > 0 && resp->resok.acl.vsa_dfaclentp != NULL) { kmem_free((caddr_t)resp->resok.acl.vsa_dfaclentp, resp->resok.acl.vsa_dfaclcnt * sizeof (aclent_t)); } resp->resok.acl.vsa_dfaclentp = NULL; } return; out: if (curthread->t_flag & T_WOULDBLOCK) { curthread->t_flag &= ~T_WOULDBLOCK; resp->status = NFS3ERR_JUKEBOX; } else { resp->status = puterrno3(error); } if (vp != NULL) VN_RELE(vp); vattr_to_post_op_attr(vap, &resp->resfail.attr); } void * acl3_getacl_getfh(GETACL3args *args) { return (&args->fh); } void acl3_getacl_free(GETACL3res *resp) { if (resp->status == NFS3_OK) { if (resp->resok.acl.vsa_aclcnt > 0 && resp->resok.acl.vsa_aclentp != NULL) { kmem_free((caddr_t)resp->resok.acl.vsa_aclentp, resp->resok.acl.vsa_aclcnt * sizeof (aclent_t)); } if (resp->resok.acl.vsa_dfaclcnt > 0 && resp->resok.acl.vsa_dfaclentp != NULL) { kmem_free((caddr_t)resp->resok.acl.vsa_dfaclentp, resp->resok.acl.vsa_dfaclcnt * sizeof (aclent_t)); } } } /* ARGSUSED */ void acl3_setacl(SETACL3args *args, SETACL3res *resp, struct exportinfo *exi, struct svc_req *req, cred_t *cr, bool_t ro) { int error; vnode_t *vp; vattr_t *vap; vattr_t va; vap = NULL; vp = nfs3_fhtovp(&args->fh, exi); if (vp == NULL) { error = ESTALE; goto out1; } (void) VOP_RWLOCK(vp, V_WRITELOCK_TRUE, NULL); va.va_mask = AT_ALL; vap = rfs4_delegated_getattr(vp, &va, 0, cr) ? NULL : &va; if (rdonly(ro, vp)) { resp->status = NFS3ERR_ROFS; goto out1; } error = VOP_SETSECATTR(vp, &args->acl, 0, cr, NULL); va.va_mask = AT_ALL; vap = rfs4_delegated_getattr(vp, &va, 0, cr) ? NULL : &va; if (error) goto out; VOP_RWUNLOCK(vp, V_WRITELOCK_TRUE, NULL); VN_RELE(vp); resp->status = NFS3_OK; vattr_to_post_op_attr(vap, &resp->resok.attr); return; out: if (curthread->t_flag & T_WOULDBLOCK) { curthread->t_flag &= ~T_WOULDBLOCK; resp->status = NFS3ERR_JUKEBOX; } else resp->status = puterrno3(error); out1: if (vp != NULL) { VOP_RWUNLOCK(vp, V_WRITELOCK_TRUE, NULL); VN_RELE(vp); } vattr_to_post_op_attr(vap, &resp->resfail.attr); } void * acl3_setacl_getfh(SETACL3args *args) { return (&args->fh); } /* ARGSUSED */ void acl3_getxattrdir(GETXATTRDIR3args *args, GETXATTRDIR3res *resp, struct exportinfo *exi, struct svc_req *req, cred_t *cr, bool_t ro) { int error; int flags; vnode_t *vp, *avp; vp = nfs3_fhtovp(&args->fh, exi); if (vp == NULL) { resp->status = NFS3ERR_STALE; return; } flags = LOOKUP_XATTR; if (args->create) flags |= CREATE_XATTR_DIR; else { ulong_t val = 0; error = VOP_PATHCONF(vp, _PC_SATTR_EXISTS, &val, cr, NULL); if (!error && val == 0) { error = VOP_PATHCONF(vp, _PC_XATTR_EXISTS, &val, cr, NULL); if (!error && val == 0) { VN_RELE(vp); resp->status = NFS3ERR_NOENT; return; } } } error = VOP_LOOKUP(vp, "", &avp, NULL, flags, NULL, cr, NULL, NULL, NULL); if (!error && avp == vp) { /* lookup of "" on old FS? */ error = EINVAL; VN_RELE(avp); } if (!error) { struct vattr va; va.va_mask = AT_ALL; error = rfs4_delegated_getattr(avp, &va, 0, cr); if (!error) { vattr_to_post_op_attr(&va, &resp->resok.attr); error = makefh3(&resp->resok.fh, avp, exi); } VN_RELE(avp); } VN_RELE(vp); if (error) { resp->status = puterrno3(error); return; } resp->status = NFS3_OK; } void * acl3_getxattrdir_getfh(GETXATTRDIR3args *args) { return (&args->fh); }