/*
* 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 2010 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* ACL support for smbfs
*/
#include <sys/systm.h> /* bcopy, ... */
#include <sys/errno.h>
#include <sys/cred.h>
#include <sys/cmn_err.h>
#include <sys/kmem.h>
#include <sys/sunddi.h>
#include <sys/acl.h>
#include <sys/vnode.h>
#include <sys/vfs.h>
#include <sys/byteorder.h>
#include <netsmb/mchain.h>
#include <netsmb/smb.h>
#include <netsmb/smb_conn.h>
#include <netsmb/smb_osdep.h>
#include <netsmb/smb_subr.h>
#include <smbfs/smbfs.h>
#include <smbfs/smbfs_node.h>
#include <smbfs/smbfs_subr.h>
#include <sys/fs/smbfs_ioctl.h>
#include <fs/fs_subr.h>
#include "smbfs_ntacl.h"
/* Sanity check SD sizes */
#define MAX_RAW_SD_SIZE 32768
#define SMALL_SD_SIZE 1024
/*
* smbfs_getsd() is a common function used by both
* smbfs_ioctl SMBFSIO_GETSD and VOP_GETSECATTR.
* Handles required rights, tmpopen/tmpclose.
*
* Note: smbfs_getsd allocates and returns an mblk chain,
* which the caller must free.
*/
static int
smbfs_getsd(vnode_t *vp, uint32_t selector, mblk_t **mp, cred_t *cr)
{
struct smb_cred scred;
int error, cerror;
smbmntinfo_t *smi;
smbnode_t *np;
u_int16_t fid = SMB_FID_UNUSED;
uint32_t sdlen = SMALL_SD_SIZE;
uint32_t rights = STD_RIGHT_READ_CONTROL_ACCESS;
if (selector & SACL_SECURITY_INFORMATION)
rights |= SEC_RIGHT_SYSTEM_SECURITY;
np = VTOSMB(vp);
smi = VTOSMI(vp);
/* Shared lock for (possible) n_fid use. */
if (smbfs_rw_enter_sig(&np->r_lkserlock, RW_READER, SMBINTR(vp)))
return (EINTR);
smb_credinit(&scred, cr);
error = smbfs_smb_tmpopen(np, rights, &scred, &fid);
if (error)
goto out;
again:
/*
* This does the OTW Get
*/
error = smbfs_smb_getsec_m(smi->smi_share, fid,
&scred, selector, mp, &sdlen);
/*
* Server may give us an error indicating that we
* need a larger data buffer to receive the SD,
* and the size we'll need. Use the given size,
* but only after a sanity check.
*
* Let's check for specific error values here.
* The NT error is: STATUS_BUFFER_TOO_SMALL,
* or with old error codes, one of these:
* ERRSRV/ERRnoroom, ERRDOS/122, ERRDOS/111
* Those are mapped to: EMOREDATA, which is
* later converted to E2BIG.
*/
if (error == E2BIG &&
sdlen > SMALL_SD_SIZE &&
sdlen <= MAX_RAW_SD_SIZE)
goto again;
cerror = smbfs_smb_tmpclose(np, fid, &scred);
if (cerror)
SMBVDEBUG("error %d closing file %s\n",
cerror, np->n_rpath);
out:
smb_credrele(&scred);
smbfs_rw_exit(&np->r_lkserlock);
return (error);
}
/*
* smbfs_setsd() is a common function used by both
* smbfs_ioctl SMBFSIO_SETSD and VOP_SETSECATTR.
* Handles required rights, tmpopen/tmpclose.
*
* Note: smbfs_setsd _consumes_ the passed *mp and
* clears the pointer (so the caller won't free it)
*/
static int
smbfs_setsd(vnode_t *vp, uint32_t selector, mblk_t **mp, cred_t *cr)
{
struct smb_cred scred;
int error, cerror;
smbmntinfo_t *smi;
smbnode_t *np;
uint32_t rights;
u_int16_t fid = SMB_FID_UNUSED;
np = VTOSMB(vp);
smi = VTOSMI(vp);
/*
* Which parts of the SD are we setting?
* What rights do we need for that?
*/
if (selector == 0)
return (0);
rights = 0;
if (selector & (OWNER_SECURITY_INFORMATION |
GROUP_SECURITY_INFORMATION))
rights |= STD_RIGHT_WRITE_OWNER_ACCESS;
if (selector & DACL_SECURITY_INFORMATION)
rights |= STD_RIGHT_WRITE_DAC_ACCESS;
if (selector & SACL_SECURITY_INFORMATION)
rights |= SEC_RIGHT_SYSTEM_SECURITY;
/* Shared lock for (possible) n_fid use. */
if (smbfs_rw_enter_sig(&np->r_lkserlock, RW_READER, SMBINTR(vp)))
return (EINTR);
smb_credinit(&scred, cr);
error = smbfs_smb_tmpopen(np, rights, &scred, &fid);
if (error)
goto out;
/*
* We're setting the remote ACL now, so
* invalidate our cached ACL just in case
* the server doesn't do exactly as we ask.
*/
mutex_enter(&np->r_statelock);
np->r_sectime = gethrtime();
mutex_exit(&np->r_statelock);
/*
* This does the OTW Set
*/
error = smbfs_smb_setsec_m(smi->smi_share, fid,
&scred, selector, mp);
cerror = smbfs_smb_tmpclose(np, fid, &scred);
if (cerror)
SMBVDEBUG("error %d closing file %s\n",
cerror, np->n_rpath);
out:
smb_credrele(&scred);
smbfs_rw_exit(&np->r_lkserlock);
return (error);
}
/*
* Helper for VOP_IOCTL: SMBFSIO_GETSD
*/
int
smbfs_acl_iocget(vnode_t *vp, intptr_t arg, int flag, cred_t *cr)
{
ioc_sdbuf_t iocb;
mdchain_t *mdp, md_store;
mblk_t *m;
void *ubuf;
int error;
/*
* Get the buffer information
*/
if (ddi_copyin((void *)arg, &iocb, sizeof (iocb), flag))
return (EFAULT);
/*
* This does the OTW Get (and maybe open, close)
* Allocates and returns an mblk in &m.
*/
error = smbfs_getsd(vp, iocb.selector, &m, cr);
if (error)
return (error);
/*
* Have m. Must free it before return.
*/
mdp = &md_store;
md_initm(mdp, m);
iocb.used = m_fixhdr(m);
/*
* Always copyout the buffer information,
* so the user can realloc and try again
* after an EOVERFLOW return.
*/
if (ddi_copyout(&iocb, (void *)arg, sizeof (iocb), flag)) {
error = EFAULT;
goto out;
}
if (iocb.used > iocb.alloc) {
error = EOVERFLOW;
goto out;
}
/*
* Copyout the buffer contents (SD)
*/
ubuf = (void *)(uintptr_t)iocb.addr;
error = md_get_mem(mdp, ubuf, iocb.used, MB_MUSER);
out:
/* Note: m_freem(m) is done by... */
md_done(mdp);
return (error);
}
/*
* Helper for VOP_IOCTL: SMBFSIO_SETSD
*/
int
smbfs_acl_iocset(vnode_t *vp, intptr_t arg, int flag, cred_t *cr)
{
ioc_sdbuf_t iocb;
mbchain_t *mbp, mb_store;
void *ubuf;
int error;
/*
* Get the buffer information
*/
if (ddi_copyin((void *)arg, &iocb, sizeof (iocb), flag))
return (EFAULT);
if (iocb.used < sizeof (ntsecdesc_t) ||
iocb.used >= MAX_RAW_SD_SIZE)
return (EINVAL);
/*
* Get the buffer contents (security descriptor data)
*/
mbp = &mb_store;
(void) mb_init(mbp);
ubuf = (void *)(uintptr_t)iocb.addr;
error = mb_put_mem(mbp, ubuf, iocb.used, MB_MUSER);
if (error)
goto out;
/*
* This does the OTW Set (and maybe open, close)
* It clears mb_top when consuming the message.
*/
error = smbfs_setsd(vp, iocb.selector, &mbp->mb_top, cr);
out:
mb_done(mbp);
return (error);
}
/*
* Refresh our cached copy of the security attributes
*/
static int
smbfs_acl_refresh(vnode_t *vp, cred_t *cr)
{
smbnode_t *np;
smbmntinfo_t *smi;
mdchain_t *mdp, md_store;
mblk_t *m = NULL;
i_ntsd_t *sd = NULL;
vsecattr_t vsa, ovsa;
uint32_t selector;
uid_t uid;
gid_t gid;
int error;
np = VTOSMB(vp);
smi = VTOSMI(vp);
bzero(&md_store, sizeof (md_store));
mdp = &md_store;
/*
* Which parts of the SD we request.
* Not getting the SACL for now.
*/
selector = DACL_SECURITY_INFORMATION |
OWNER_SECURITY_INFORMATION |
GROUP_SECURITY_INFORMATION;
/*
* This does the OTW Get (and maybe open, close)
* Allocates and returns an mblk in &m.
*/
error = smbfs_getsd(vp, selector, &m, cr);
if (error)
goto out;
/* Note: allocated *m */
md_initm(mdp, m);
/*
* Parse the OtW security descriptor,
* storing in our internal form.
*/
error = md_get_ntsd(mdp, &sd);
if (error)
goto out;
/*
* Convert the Windows security descriptor to a
* ZFS ACL (and owner ID, primary group ID).
*/
bzero(&vsa, sizeof (vsa));
vsa.vsa_mask = VSA_ACE | VSA_ACECNT;
error = smbfs_acl_sd2zfs(sd, &vsa, &uid, &gid);
if (error)
goto out;
ASSERT(vsa.vsa_aclentp != NULL);
SMBVDEBUG("uid=%u, gid=%u", uid, gid);
/*
* Store the results in r_secattr, n_uid, n_gid
*/
mutex_enter(&np->r_statelock);
ovsa = np->r_secattr;
np->r_secattr = vsa;
np->n_uid = uid;
np->n_gid = gid;
/*
* ACLs don't change frequently, so cache these
* for a relatively long time (ac dir max).
*/
np->r_sectime = gethrtime() + smi->smi_acdirmax;
mutex_exit(&np->r_statelock);
/* Allocated in: smbfs_acl_sd2zfs */
if (ovsa.vsa_aclentp != NULL)
kmem_free(ovsa.vsa_aclentp, ovsa.vsa_aclentsz);
out:
if (sd != NULL)
smbfs_acl_free_sd(sd);
/* Note: m_freem(m) is done by... */
md_done(mdp);
return (error);
}
/*
* Helper for smbfsgetattr()
*
* Just refresh the ACL cache if needed,
* which updates n_uid/n_gid
*/
int
smbfs_acl_getids(vnode_t *vp, cred_t *cr)
{
smbnode_t *np;
int error;
np = VTOSMB(vp);
/*
* NB: extended attribute files and directories
* do not have ACLs separate from the parent.
* Let the caller do ACL fabrication.
*/
if (np->n_flag & N_XATTR)
return (ENOSYS);
mutex_enter(&np->r_statelock);
if (gethrtime() >= np->r_sectime) {
/* Need to update r_secattr */
mutex_exit(&np->r_statelock);
error = smbfs_acl_refresh(vp, cr);
return (error);
}
mutex_exit(&np->r_statelock);
return (0);
}
/*
* Helper for VOP_GETSECATTR
*
* Refresh the ACL cache if needed, then
* duplicate the requested parts of the vsecattr.
*/
/* ARGSUSED */
int
smbfs_acl_getvsa(vnode_t *vp, vsecattr_t *vsa,
int flag, cred_t *cr)
{
smbnode_t *np;
int error;
np = VTOSMB(vp);
/*
* NB: extended attribute files and directories
* do not have ACLs separate from the parent.
* Let the caller do ACL fabrication.
*/
if (np->n_flag & N_XATTR)
return (ENOSYS);
mutex_enter(&np->r_statelock);
if (np->r_secattr.vsa_aclentp == NULL ||
gethrtime() >= np->r_sectime) {
/* Need to update r_secattr */
mutex_exit(&np->r_statelock);
error = smbfs_acl_refresh(vp, cr);
if (error)
return (error);
mutex_enter(&np->r_statelock);
}
ASSERT(np->r_secattr.vsa_aclentp != NULL);
/*
* Duplicate requested parts of r_secattr
*/
if (vsa->vsa_mask & VSA_ACECNT)
vsa->vsa_aclcnt = np->r_secattr.vsa_aclcnt;
if (vsa->vsa_mask & VSA_ACE) {
vsa->vsa_aclentsz = np->r_secattr.vsa_aclentsz;
vsa->vsa_aclentp = kmem_alloc(vsa->vsa_aclentsz, KM_SLEEP);
bcopy(np->r_secattr.vsa_aclentp, vsa->vsa_aclentp,
vsa->vsa_aclentsz);
}
mutex_exit(&np->r_statelock);
return (0);
}
/*
* Helper for smbfs_acl_setids, smbfs_acl_setvsa
*/
static int
smbfs_acl_store(vnode_t *vp, vsecattr_t *vsa, uid_t uid, gid_t gid,
uint32_t selector, cred_t *cr)
{
mbchain_t *mbp, mb_store;
i_ntsd_t *sd;
int error;
ASSERT(selector != 0);
sd = NULL;
bzero(&mb_store, sizeof (mb_store));
mbp = &mb_store;
/*
* Convert a ZFS ACL (and owner ID, group ID)
* into an NT SD, internal form.
*/
error = smbfs_acl_zfs2sd(vsa, uid, gid, selector, &sd);
if (error)
goto out;
/*
* Marshall the internal form SD into an
* OtW security descriptor.
*/
(void) mb_init(mbp);
error = mb_put_ntsd(mbp, sd);
if (error)
goto out;
/*
* This does the OTW Set (and maybe open, close)
* It clears mb_top when consuming the message.
*/
error = smbfs_setsd(vp, selector, &mbp->mb_top, cr);
out:
if (sd != NULL)
smbfs_acl_free_sd(sd);
mb_done(mbp);
return (error);
}
/*
* Helper for smbfs_setattr()
*
* Set the passed UID/GID as indicated by va_mask.
*/
int
smbfs_acl_setids(vnode_t *vp, vattr_t *vap, cred_t *cr)
{
uid_t uid = (uid_t)-1;
gid_t gid = (uid_t)-1;
uint32_t selector = 0;
int error;
if (vap->va_mask & AT_UID) {
selector |= OWNER_SECURITY_INFORMATION;
uid = vap->va_uid;
}
if (vap->va_mask & AT_GID) {
selector |= GROUP_SECURITY_INFORMATION;
gid = vap->va_gid;
}
if (selector == 0)
return (0);
error = smbfs_acl_store(vp, NULL, uid, gid, selector, cr);
return (error);
}
/*
* Helper for VOP_SETSECATTR
* Convert ZFS to NT form, call smbfs_setsd.
*/
/* ARGSUSED */
int
smbfs_acl_setvsa(vnode_t *vp, vsecattr_t *vsa,
int flag, cred_t *cr)
{
uint32_t selector = DACL_SECURITY_INFORMATION;
smbnode_t *np = VTOSMB(vp);
int error;
/*
* NB: extended attribute files and directories
* do not have ACLs separate from the parent.
*/
if (np->n_flag & N_XATTR)
return (ENOSYS);
/*
* When handling ACE_OWNER or ACE_GROUP entries,
* we need the current owner and group.
*/
error = smbfs_acl_getids(vp, cr);
if (error)
return (error);
error = smbfs_acl_store(vp, vsa, np->n_uid, np->n_gid, selector, cr);
return (error);
}