smbfs_security.c   [plain text]


/*
 * Copyright (c) 2009 - 2010 Apple Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */

#include <sys/mount.h>
#include <sys/kauth.h>
#include <sys/syslog.h>

#include <sys/smb_byte_order.h>
#include <sys/smb_apple.h>
#include <sys/mchain.h>
#include <sys/msfscc.h>

#include <netsmb/smb.h>
#include <netsmb/smb_conn.h>
#include <smbfs/smbfs.h>
#include <smbfs/smbfs_node.h>
#include <smbfs/smbfs_subr.h>
#include <smbfs/smbfs_security.h>


#define MAX_SID_PRINTBUFFER	256	/* Used to print out the sid in case of an error */

/*
 * Directory Service generates these UUIDs for SIDs that are unknown. These UUIDs 
 * are used so we can round trip a translation from SID-->UUID-->SID. The first 
 * 12 bytes are well known and allow us to tell if this is a temporary UUID.
 */
static const uint8_t tmpuuid1[12] = {	0xFF, 0xFF, 0xEE, 0xEE, 0xDD, 0xDD, 
										0xCC, 0xCC, 0xBB, 0xBB, 0xAA, 0xAA};
static const uint8_t tmpuuid2[12] = {	0xAA, 0xAA, 0xBB, 0xBB, 0xCC, 0xCC, 
										0xDD, 0xDD, 0xEE, 0xEE, 0xFF, 0xFF};

static const ntsid_t unix_users_domsid =
{ 1, 1, {0, 0, 0, 0, 0, 22}, {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0} };

static const ntsid_t unix_groups_domsid =
{ 1, 1, {0, 0, 0, 0, 0, 22}, {2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0} };

static void * smb_sdoffset(struct ntsecdesc *w_secp, size_t w_seclen, int sd_type);

#define sdowner(s, s_len) (struct ntsid *)smb_sdoffset(s, s_len, OWNER_SECURITY_INFORMATION)
#define sdgroup(s, s_len) (struct ntsid *)smb_sdoffset(s, s_len, GROUP_SECURITY_INFORMATION)
#define sdsacl(s, s_len) (struct ntacl *)smb_sdoffset(s, s_len, SACL_SECURITY_INFORMATION)
#define sddacl(s, s_len) (struct ntacl *)smb_sdoffset(s, s_len, DACL_SECURITY_INFORMATION)

/*
 * Check to see if this is a temporary uuid, generated by Directory Service
 */
int 
is_memberd_tempuuid(const guid_t *uuidp)
{
	if ((bcmp(uuidp, tmpuuid1, sizeof(tmpuuid1)) == 0) || 
		(bcmp(uuidp, tmpuuid2, sizeof(tmpuuid2)) == 0)) {
		return TRUE;
	}
	return FALSE;
}

/*
 * Free any memory and clear any value used by the acl caching
 * We have a lock around this routine to make sure no one plays
 * with these values until we are done.
 */
void
smbfs_clear_acl_cache(struct smbnode *np)
{
	lck_mtx_lock(&np->f_ACLCacheLock);
    if (np->acl_cache_data) {
        SMB_FREE(np->acl_cache_data, M_TEMP);
    }
	np->acl_cache_data = NULL;
	np->acl_cache_timer.tv_sec = 0;
	np->acl_cache_timer.tv_nsec = 0;
	np->acl_error = 0;
	np->acl_cache_len = 0;
	lck_mtx_unlock(&np->f_ACLCacheLock);
}

/*
 * Get a pointer to the offset requested. Verify that the offset is 
 * is in bounds and the structure does not go past the end of the buffer.
 */
static void * 
smb_sdoffset(struct ntsecdesc *w_secp, size_t w_seclen, int sd_type)
{
	void	*rt_ptr;
	int32_t	sd_off = 0;
	int32_t	sd_len = 0;
	int32_t	end_len = 0;
	
	if (sd_type == OWNER_SECURITY_INFORMATION) {
		sd_len = (int32_t)sizeof(struct ntsid);
		sd_off = letohl(w_secp->OffsetOwner);
	}
	else if (sd_type == GROUP_SECURITY_INFORMATION) {
		sd_len = (int32_t)sizeof(struct ntsid);		
		sd_off = letohl(w_secp->OffsetGroup);
	}
	else if (sd_type == DACL_SECURITY_INFORMATION) {
		sd_len = (int32_t)sizeof(struct ntacl);		
		sd_off = letohl(w_secp->OffsetDacl);
	}
	else if (sd_type == SACL_SECURITY_INFORMATION) {
		sd_len = (int32_t)sizeof(struct ntacl);		
		sd_off = letohl(w_secp->OffsetSacl);
	}
	
	/* Make sure w_seclen is reasonable, once typed cast */
	if ((int32_t)w_seclen < 0)
		return 	NULL;
	
	/* Make sure the length is reasonable */
	if (sd_len > (int32_t)w_seclen)
		return 	NULL;
	
	/* 
	 * Make sure the offset is reasonable. NOTE: We can get a zero offset which
	 * is legal, just means no entry was sent. So we just return a null pointer
	 * since that doesn't cause an error.
	 */
	if ((sd_off <= 0) || (sd_off > (int32_t)w_seclen))
		return 	NULL;
	
	/* Make sure adding them together is reasonable */
	end_len = sd_off+sd_len;
	if ((end_len < 0) || (end_len > (int32_t)w_seclen))
		return 	NULL;
	
	rt_ptr = sd_off+(uint8_t *)w_secp;
	
	return 	rt_ptr;
}

/*
 * Used for debugging and writing error messages into the system log. Still  
 * needs to have buffer checking done on it. 
 */
static void 
smb_printsid(struct ntsid *sidptr, char *sidendptr, const char *printstr, 
				  const char *filename, int index, int error)
{
	char sidprintbuf[MAX_SID_PRINTBUFFER];
	char *s = sidprintbuf;
	int subs;
	uint64_t auth = 0;
	unsigned i, *ip;
	size_t len;
	uint32_t *subauthptr = (uint32_t *)((char *)sidptr + sizeof(struct ntsid));
	char *subauthendptr;
	
	bzero(sidprintbuf, MAX_SID_PRINTBUFFER);
	for (i = 0; i < sizeof(sidptr->sid_authority); i++)
		auth = (auth << 8) | sidptr->sid_authority[i];
	s += snprintf(s, MAX_SID_PRINTBUFFER, "S-%u-%llu", sidptr->sid_revision, auth);
	
	subs = sidptr->sid_subauthcount;
	if (subs > KAUTH_NTSID_MAX_AUTHORITIES) {
		SMBERROR("sid_subauthcount > KAUTH_NTSID_MAX_AUTHORITIES : %d\n", subs);
		subs = KAUTH_NTSID_MAX_AUTHORITIES;
	}
	/*
	 * We know that sid_subauthcount has to be less than or equal to 
	 * KAUTH_NTSID_MAX_AUTHORITIES which is currently 16. So the highest
	 * this can go is 16 * sizeof(uint32_t) so no overflow problem here.
	 */
	subauthendptr = (char *)((char *)subauthptr + (subs * sizeof(uint32_t)));
	
	if (subauthendptr > sidendptr) {
		len = MAX_SID_PRINTBUFFER - (s - sidprintbuf);
		(void)snprintf(s, len, " buffer overflow prevented: %p > %p", 
					   subauthendptr, sidendptr); 
		return;		
	}
	
	for (ip = subauthptr; subs--; ip++)  { 
		len = MAX_SID_PRINTBUFFER - (s - sidprintbuf);
		DBG_ASSERT(len > 0)
		s += snprintf(s, len, "-%u", *ip); 
	}
	
	if (error) {
		SMBWARNING("%s: sid[%d] = %s error = %d %s%s\n", printstr, index, 
				   sidprintbuf, error, (filename) ? "for " : "", filename);
	} else {
		SMBWARNING("%s: sid[%d] = %s %s%s\n", printstr, index, sidprintbuf, 
				   (filename) ? "for " : "", filename);
	}
}

static int 
smb_sid_is_equal(const ntsid_t * rhs, const ntsid_t * lhs)
{
	if (rhs->sid_kind != lhs->sid_kind) {
		return 0;
	}
	
	if (rhs->sid_authcount != lhs->sid_authcount) {
		return 0;
	}
	
	if (bcmp(rhs->sid_authority, lhs->sid_authority,
			 sizeof(rhs->sid_authority)) != 0) {
		return 0;
	}
	
	if (bcmp(rhs->sid_authorities, lhs->sid_authorities,
			 sizeof(uint32_t) * rhs->sid_authcount) != 0) {
		return 0;
	}
	return 1;
}

/* 
 * Return 1 or 0, depending on whether the SID is in the domain given by the 
 * domain SID. 
 */
static int 
smb_sid_in_domain(const ntsid_t * domain, const ntsid_t * sid)
{
	ntsid_t tmp = *sid;
	
	if (tmp.sid_authcount == 0) {
		SMBDEBUG("Bogus network sid sid_authcount = %d\n", tmp.sid_authcount);
		return 0;
	}
	tmp.sid_authcount -= 1;
	return smb_sid_is_equal(domain, &tmp);
}

/*
 * If a Windows Server 2008 R2 NFS share is configured to enable Unmapped UNIX 
 * User Access and there is no existing mapping available to the NFSserver (via 
 * either [MS-UNMP] or [RFC2307]) then the server will encode the owner, group, 
 * and mode of a file into a security descriptor directly using generated SIDs. 
 * The NFSserver uses a specific sub-authority (SECURITY_NFS_ID_BASE_RID == 0x00000058) 
 * relative to the well known authority "NT Authority" (SECURITY_NT_AUTHORITY == {0,0,0,0,0,5}). 
 * The NFSserver then uses further relative sub-authorities to build SIDs for 
 * different NfsTypes that represent the owner (0x00000001), the group (0x00000002), 
 * and the permissions mask (0x00000003) for the file. A further SID is also 
 * generated, which is used to store the other, or world access mask, within the 
 * security descriptor.
 *
 * "<NTSecurityAuthority>-<SECURITY_NFS_ID_BASE_RID>-<NfsSidType>-<NfsSidValue>"
 *  
 * To construct a complete security descriptor, the NFSserver generates a set 
 * of NFS-specific SIDs based on the UID, GID, and mode bits to be represented:
 *  
 * Owner SID based on the UID (for example, "S-1-5-88-1-<uid>")
 * Group SID based on the GID (for example, "S-1-5-88-2-<gid>")
 * Mode SID based on the UNIX mode bits (for example, "S-1-5-88-3-<mode>")
 * Other SID, a constant value (for example, "S-1-5-88-4")
 */
static const uint8_t security_nt_authority[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x05};
#define SECURITY_NFS_ID_BASE_RID	0x00000058
enum {
	NfsSidTypeOwner = 1,
	NfsSidTypeGroup = 2,
	NfsSidTypeModes = 3,
	NfsSidTypeOther = 4
};

static Boolean 
WindowsNfsSID(struct smbnode *np, ntsid_t *sidptr)
{
	if ((sidptr->sid_kind == 1) && (sidptr->sid_authcount == 3) && 
		(memcmp(sidptr->sid_authority, security_nt_authority, sizeof(security_nt_authority)) == 0) && 
		(sidptr->sid_authorities[0] == SECURITY_NFS_ID_BASE_RID)) {
		
		switch (sidptr->sid_authorities[1]) {
			case NfsSidTypeOwner:
				SMB_LOG_ACCESS("%s has a NfsSidTypeOwner of %d\n", 
							   np->n_name, sidptr->sid_authorities[2]);
				np->n_nfs_uid = sidptr->sid_authorities[2];
				break;
			case NfsSidTypeGroup:
				SMB_LOG_ACCESS("%s has a NfsSidTypeGroup of %d\n", 
							   np->n_name, sidptr->sid_authorities[2]);
				np->n_nfs_gid = sidptr->sid_authorities[2];
				break;
			case NfsSidTypeModes:
				SMB_LOG_ACCESS("%s has a NfsSidTypeModes of O%o\n", 
							   np->n_name, sidptr->sid_authorities[2]);
				np->n_flag |= NHAS_POSIXMODES;
				np->n_mode &= ~ACCESSPERMS;
				np->n_mode |= (mode_t)(sidptr->sid_authorities[2] & ACCESSPERMS);
				break;
			case NfsSidTypeOther:
				SMB_LOG_ACCESS("%s has a NfsSidTypeOther of O%o\n", 
							   np->n_name, sidptr->sid_authorities[2]);
				break;
			default:
				SMB_LOG_ACCESS("%s: unknown NfsSidType of 0x%x 0x%x\n", np->n_name, 
							   sidptr->sid_authorities[1], sidptr->sid_authorities[2]);
				break;
		}
		return TRUE;
	}
	return FALSE;
}

/*
 * The calling routine will guarantee that sidptr has enough space to hold the 
 * ntsid structure, but we need to protect ourself from going pass the size
 * of the ntsid structure and any values contain inside the ntsid structure.
 *
 * We no longer need to call smb_sid_endianize before calling this routine. We
 * now do it place, should help with performance.
 */
static void 
smb_sid2sid16(struct ntsid *sidptr, ntsid_t *sid16ptr, char *sidendptr)
{
	uint8_t ii;
	uint32_t *subauthptr = (uint32_t *)((char *)sidptr + sizeof(struct ntsid));
	char *subauthendptr;
	
	bzero(sid16ptr, sizeof(*sid16ptr));
	if (sidptr->sid_subauthcount > KAUTH_NTSID_MAX_AUTHORITIES) {
		SMBERROR("sidp->sid_subauthcount count too big: %d\n", 
				 sidptr->sid_subauthcount);
		return;
	}
	
	/*
	 * We know that sid_subauthcount has to be less than or equal to 
	 * KAUTH_NTSID_MAX_AUTHORITIES which is currently 16. So the highest
	 * this can go is 16 * sizeof(uint32_t) so no overflow problem here.
	 */
	subauthendptr = (char *)((char *)subauthptr + 
							 (sidptr->sid_subauthcount * sizeof(uint32_t)));
	if (subauthendptr > sidendptr) {
		SMBERROR("Too many sid authorities: %p %p\n", subauthendptr, sidendptr);
		return;		
	}
	sid16ptr->sid_kind = sidptr->sid_revision;
	sid16ptr->sid_authcount = sidptr->sid_subauthcount;
	
	/* Why not just a bcopy? */
	for (ii = 0; ii < sizeof(sid16ptr->sid_authority); ii++)
		sid16ptr->sid_authority[ii] = sidptr->sid_authority[ii];
	
	for (ii = 0; ii < sid16ptr->sid_authcount; ii++) {
		sid16ptr->sid_authorities[ii] = letohl(*subauthptr);
		subauthptr++;
	}
}

/*
 * The calling routine will guarantee that sidptr has enough space to hold the 
 * ntsid structure, but we need to protect ourself from going pass the size
 * of the ntsid structure and any values contain inside the ntsid structure.
 *
 * Really over kill, but what the heck lets double check that the user land
 * code didn't send us something bad.
 */
static void 
smb_sid_endianize(struct ntsid *sidptr, size_t len)
{
	char *sidendptr = (char *)sidptr + len;
	uint32_t *subauthptr = (uint32_t *)((char *)sidptr + sizeof(struct ntsid));
	char *subauthendptr;
	int n;
	
	/*
	 * We know that sid_subauthcount has to be less than or equal to 
	 * KAUTH_NTSID_MAX_AUTHORITIES which is currently 16. So the highest
	 * this can go is 16 * sizeof(uint32_t) so no overflow problem here.
	 */
	subauthendptr = (char *)((char *)subauthptr + 
							 (sidptr->sid_subauthcount * sizeof(uint32_t)));	
	if (subauthendptr > sidendptr) {
		SMBERROR("Too many sid authorities: %p %p\n", subauthendptr, sidendptr);
		return;		
	}
	
	n = sidptr->sid_subauthcount;
	while (n--) {
		*subauthptr = letohl(*subauthptr);
		subauthptr++;
	}
}

/*
 * This is the main routine that goes across the network to get our acl 
 * information. We now always ask for everything so we can make less calls. If 
 * the cache data is up to date then we will return that information. We also do 
 * negative caching, if the server returns an error we cache that fact and 
 * continue to return the error until the cache information times out. 
 *
 * Remember that the vfs will help us with caching, but not in the negative case. 
 * Also it does not solve the problem of multiple different calls coming into us 
 * back to back. So in a typical case we will get the following calls and they
 * will require an acl lookup for each item.
 * 
 * UID and GID request
 * Do we have write access
 * Do we have read access
 * Do we have search/excute access
 *
 * So by caching we are removing 12 network calls for each file in a directory. 
 * We only hold on to this cache for a very short time, because it has a memory 
 * cost that we don't want to pay for any real length of time. This is ok, becasue 
 * one we go through this process the vfs layer will handle the longer caching of 
 * these request.
 */
static int 
smbfs_update_acl_cache(struct smb_share *share, struct smbnode *np, 
					   vfs_context_t context, struct ntsecdesc **w_sec, 
					   size_t *seclen)
{
	uint32_t selector = OWNER_SECURITY_INFORMATION | 
						GROUP_SECURITY_INFORMATION | 
						DACL_SECURITY_INFORMATION;
	uint16_t fid = 0;
	struct timespec	ts;
	struct ntsecdesc *acl_cache_data = NULL;
	size_t acl_cache_len = 0;
	int	error;
	
	/* Check to see if the cache has time out */
	nanouptime(&ts);
	if (timespeccmp(&np->acl_cache_timer, &ts, >)) {
		/* Ok the cache is still good take a lock and retrieve the data */
		lck_mtx_lock(&np->f_ACLCacheLock);
		if (timespeccmp(&np->acl_cache_timer, &ts, >)) {
			/* We have the lock and the cache is still good, use the cached ACL */
			goto done;
		} else {
			/* 
			 * Cache expired while we were waiting on the lock, release the lock 
			 * and get the ACL from the network.
			 */
			lck_mtx_unlock(&np->f_ACLCacheLock);
		}
	}
	
	error = smbfs_tmpopen(share, np, SMB2_READ_CONTROL, &fid, context);
	if (error == 0) {
		int cerror;
		
		error = smbfs_getsec(share, fid, selector, 
                             (struct ntsecdesc **)&acl_cache_data, 
                             &acl_cache_len, context);
		cerror = smbfs_tmpclose(share, np, fid, context);
		if (cerror)
			SMBWARNING("error %d closing fid %d file %s\n", cerror, fid, np->n_name);	
		if ((error == 0) && (acl_cache_data == NULL))
			error = EBADRPC;
	}
	
	/* Don't let anyone play with the acl cache until we are done */
	lck_mtx_lock(&np->f_ACLCacheLock);
	/* Free the old data no longer needed */
	if (np->acl_cache_data)
		SMB_FREE(np->acl_cache_data, M_TEMP);
	np->acl_cache_data = acl_cache_data;
	np->acl_cache_len = acl_cache_len;
	np->acl_error = error;
	/* We have new information reset our timer  */
	SET_ACL_CACHE_TIME(np);
	
done:
	if (np->acl_error || (np->acl_cache_data == NULL)) {
		*w_sec = NULL;
		*seclen = 0;
		if (np->acl_error == 0)
			np->acl_error =  EBADRPC; /* Should never happen, but just to be safe */
	} else {
		SMB_MALLOC(*w_sec, struct ntsecdesc *, np->acl_cache_len, M_TEMP, M_WAITOK);
		if (*w_sec) {
			*seclen = np->acl_cache_len;		
			bcopy(np->acl_cache_data, *w_sec, np->acl_cache_len);
		} else {
			*w_sec = np->acl_cache_data;
			*seclen = np->acl_cache_len;
			np->acl_cache_data = NULL;
			np->acl_cache_len = 0;
			np->acl_cache_timer.tv_sec = 0;
			np->acl_cache_timer.tv_nsec = 0;
		}
	}
	error = np->acl_error;
	lck_mtx_unlock(&np->f_ACLCacheLock);
	return error;	
}

/*
 * Universal routine for getting the UUID/GUID and setting the nodes 
 * uid/gid. We will get the nodes UUID and set its uid if the owner flag
 * is set, otherwise we get the nodes GUID and set its gid. 
 */
static void 
smbfs_set_node_identifier(struct smbnode *np, struct ntsecdesc *w_sec, 
						  size_t seclen, guid_t *unique_identifier, int owner)
{	
	struct smbmount *smp = np->n_mount;
	struct ntsid	*w_sidp = NULL;
	ntsid_t			sid;
	uid_t			*node_identifier;
	int				error;
	
	if (owner) {
		if (w_sec)	/* Getting the security descriptor failed */
			w_sidp = sdowner(w_sec, seclen);
		node_identifier = &np->n_uid;
	} else {
		if (w_sec)	/* Getting the security descriptor failed */
			w_sidp = sdgroup(w_sec, seclen);
		node_identifier = &np->n_gid;
	}
	
	if (!w_sidp || !w_sec) {
		SMB_LOG_ACCESS("no %s sid received, file %s\n", 
					   (owner) ? "user" : "group", np->n_name);
		goto error_out;
	}
	
	smb_sid2sid16(w_sidp, &sid, (char*)w_sec+seclen);
	/* We are mapping the owner id, so if its a match replace it with the local id */
	if (owner && (smp->sm_flags & MNT_MAPS_NETWORK_LOCAL_USER) && 
		(bcmp(&smp->ntwrk_sids[0], &sid, sizeof(sid)) == 0)) {
		*unique_identifier = smp->sm_args.uuid;
		*node_identifier = smp->sm_args.uid;
		return; /* We are done */
	}
	
	error = kauth_cred_ntsid2guid(&sid, unique_identifier);
	if (error) {
		if (smbfs_loglevel == SMB_ACL_LOG_LEVEL)
			smb_printsid(w_sidp, (char*)w_sec+seclen, "Owner/Group lookup failed", 
						 (const char  *)np->n_name, 0, error);
		goto error_out;
	} 
	
	/* 
	 * This is a cheap call since we lookup the uuid/guid above, so the kernel 
	 * will have the uid or gid in its cache. If we get a temp gid/uid and we
	 * already have a uid/gid always use the one we already have.
	 */
	if (is_memberd_tempuuid(unique_identifier) && 
		(*node_identifier != KAUTH_UID_NONE)) {
		return; /* We already have a real uid/gid from the server keep using it */
	}
	
	if (owner)
		error = kauth_cred_ntsid2uid(&sid, node_identifier);
	else
		error = kauth_cred_ntsid2gid(&sid, node_identifier);
	if (error == 0)
		return; /* We are done */
	
error_out:
	/* Not sure what else to do here, so we default to the mounted users uid/gid */
	if (*node_identifier == KAUTH_UID_NONE)
		*node_identifier = (owner) ? smp->sm_args.uid : smp->sm_args.gid;
	/* Something bad happen and we didn't get the UUID/GUID */
	if (kauth_guid_equal(unique_identifier, &kauth_null_guid)) {
		/* At this point we have a uid/gid, so use it to get the UUID/GUID */
		if (owner)
			error = kauth_cred_uid2guid(*node_identifier, unique_identifier);
		else 
			error = kauth_cred_gid2guid(*node_identifier, unique_identifier);
		/* Should never error out in the case, but just in case lets log it */
		if (error) {
			SMB_LOG_ACCESS("%s couldn't translate the uid/gid %d to a UUID/GUID, error = %d\n", 
						   np->n_name, *node_identifier, error);
		}
	}
}

/*
 * This routine will retrieve the owner, group and any ACLs associate with
 * this node. We treat an access error the same as an empty security descriptor.
 *
 * The calling routine must hold a reference on the share
 *
 */
int 
smbfs_getsecurity(struct smb_share	*share, struct smbnode *np, 
					  struct vnode_attr *vap, vfs_context_t context)
{
	struct smbmount		*smp = np->n_mount;
	int					error;
	struct ntsecdesc	*w_sec;	/* Wire sec descriptor */
	size_t				seclen = 0;
	kauth_acl_t			res = NULL;	/* acl result buffer */
	
	/* We do not support acl access on a stream node */
	if (vnode_isnamedstream(np->n_vnode))
		return EINVAL;
	
	if (VATTR_IS_ACTIVE(vap, va_acl))
		vap->va_acl = NULL;					/* default */
	
	if (VATTR_IS_ACTIVE(vap, va_guuid))
		vap->va_guuid = kauth_null_guid;	/* default */
	
	if (VATTR_IS_ACTIVE(vap, va_uuuid))
		vap->va_uuuid = kauth_null_guid;	/* default */
	
	/* Check to make sure we have current acl information */
	error = smbfs_update_acl_cache(share, np, context, &w_sec, &seclen);
	if (error) {
		if (w_sec)
			SMB_FREE(w_sec, M_TEMP);
		w_sec = NULL;
		/* 
		 * When should we eat the error and when shouldn't we, that is the
		 * real question? Any error here will fail the copy engine. Not sure 
		 * thats what we really want. We currently only ignore an access error,
		 * but in the future we may want return all errors or none. The old
		 * code ignored all errors, now we only ignore EACCES.
		 */
		if (error == EACCES) {
			error = 0;
		} else {
			SMB_LOG_ACCESS("smbfs_update_acl_cache of %s failed with error = %d\n", 
						   np->n_name, error);
		}
	}
	
	/* 
	 * The smbfs_set_node_identifier routine will check to see if w_sec
	 * is null. If null it will do what is need to return the correct 
	 * values.
	 */
	if (VATTR_IS_ACTIVE(vap, va_guuid)) {
		smbfs_set_node_identifier(np, w_sec, seclen, &vap->va_guuid, FALSE);
	}
	if (VATTR_IS_ACTIVE(vap, va_uuuid)) {
		smbfs_set_node_identifier(np, w_sec, seclen, &vap->va_uuuid, TRUE);
	}
	
	if (VATTR_IS_ACTIVE(vap, va_acl)) {
		struct ntacl		*w_dacl = NULL;
		char				*endptr;
		uint32_t			acecount, j, aflags;
		struct ntsid		*w_sidp;	/* Wire SID */
		struct ntace		*w_acep = NULL;	/* Wire ACE */
		kauth_ace_rights_t	arights;
		uint32_t			w_rights;
		ntsid_t				sid;	/* temporary, for a kauth sid */
		
		if (w_sec)
			w_dacl = sddacl(w_sec, seclen);
		if (!w_dacl || !w_sec)
			goto exit;
		/* Is there anything we can do to verify acecount, just not sure */
		acecount = letohs(w_dacl->acl_acecount);
		res = kauth_acl_alloc(acecount);
		if (!res) {
			error = ENOMEM;
			goto exit;
		}
		/* Only count entries we add to the array, don't count dropped entries */
		res->acl_entrycount = 0;
		res->acl_flags = letohs(w_sec->ControlFlags);
		if (res->acl_flags & SE_DACL_PROTECTED)
			res->acl_flags |= KAUTH_FILESEC_NO_INHERIT;
		else
			res->acl_flags &= ~KAUTH_FILESEC_NO_INHERIT;
		
		endptr = (char *)w_sec+seclen;
		
		for (j = 0, w_acep = aclace(w_dacl); (((char *)acesid(w_acep) < endptr) && 
				(j < acecount));  j++, w_acep = aceace(w_acep)) {
			int	warn_error = 0;
			
			switch(acetype(w_acep)) {
			    case ACCESS_ALLOWED_ACE_TYPE:
					aflags = KAUTH_ACE_PERMIT;
					break;
			    case ACCESS_DENIED_ACE_TYPE:
					aflags = KAUTH_ACE_DENY;
					break;
			    case SYSTEM_AUDIT_ACE_TYPE:
					aflags = KAUTH_ACE_AUDIT;
					break;
			    case SYSTEM_ALARM_ACE_TYPE:
					aflags = KAUTH_ACE_ALARM;
					break;
			    default:
					SMBERROR("ACE type %d file(%s)\n", acetype(w_acep), np->n_name);
					error = EPROTO;	/* Should it be EIO */
					goto exit;
			}
			w_sidp = acesid(w_acep);
			if ((char *)w_sidp+sizeof(*w_sidp) > endptr) {
				SMBERROR("ACE type %d file(%s) would have caused a buffer overrun!\n", 
						 acetype(w_acep), np->n_name);
				error = EPROTO;	/* Should it be EIO */
				goto exit;				
			}
			smb_sid2sid16(w_sidp, &sid, (char*)w_sec+seclen);
			if (WindowsNfsSID(np, &sid)) {
				continue;
			}
			if ((smp->sm_flags & MNT_MAPS_NETWORK_LOCAL_USER) && 
				(bcmp(&smp->ntwrk_sids[0], &sid, sizeof(sid)) == 0)) {
				res->acl_ace[res->acl_entrycount].ace_applicable = smp->sm_args.uuid;
			} else {
				warn_error = kauth_cred_ntsid2guid(&sid, &res->acl_ace[res->acl_entrycount].ace_applicable);
			}
			if (warn_error) {
				if (smbfs_loglevel == SMB_ACL_LOG_LEVEL) {
					smb_printsid(w_sidp, (char*)w_sec+seclen, "ACL lookup failed", 
								 (const char  *)np->n_name, j, warn_error);
				}
				continue;
			}
			if (aceflags(w_acep) & OBJECT_INHERIT_ACE_FLAG)
				aflags |= KAUTH_ACE_FILE_INHERIT;
			if (aceflags(w_acep) & CONTAINER_INHERIT_ACE_FLAG)
				aflags |= KAUTH_ACE_DIRECTORY_INHERIT;
			if (aceflags(w_acep) & NO_PROPAGATE_INHERIT_ACE_FLAG)
				aflags |= KAUTH_ACE_LIMIT_INHERIT;
			if (aceflags(w_acep) & INHERIT_ONLY_ACE_FLAG)
				aflags |= KAUTH_ACE_ONLY_INHERIT;
			if (aceflags(w_acep) & INHERITED_ACE_FLAG)
				aflags |= KAUTH_ACE_INHERITED;
			if (aceflags(w_acep) & UNDEF_ACE_FLAG)
				SMBERROR("unknown ACE flag on file(%s)\n", np->n_name);
			if (aceflags(w_acep) & SUCCESSFUL_ACCESS_ACE_FLAG)
				aflags |= KAUTH_ACE_SUCCESS;
			if (aceflags(w_acep) & FAILED_ACCESS_ACE_FLAG)
				aflags |= KAUTH_ACE_FAILURE;
			res->acl_ace[res->acl_entrycount].ace_flags = aflags;
			w_rights = acerights(w_acep);
			arights = 0;
			if (w_rights & SMB2_GENERIC_READ)
				arights |= KAUTH_ACE_GENERIC_READ;
			if (w_rights & SMB2_GENERIC_WRITE)
				arights |= KAUTH_ACE_GENERIC_WRITE;
			if (w_rights & SMB2_GENERIC_EXECUTE)
				arights |= KAUTH_ACE_GENERIC_EXECUTE;
			if (w_rights & SMB2_GENERIC_ALL)
				arights |= KAUTH_ACE_GENERIC_ALL;
			if (w_rights & SMB2_SYNCHRONIZE)
				arights |= KAUTH_VNODE_SYNCHRONIZE;
			if (w_rights & SMB2_WRITE_OWNER)
				arights |= KAUTH_VNODE_CHANGE_OWNER;
			if (w_rights & SMB2_WRITE_DAC)
				arights |= KAUTH_VNODE_WRITE_SECURITY;
			if (w_rights & SMB2_READ_CONTROL)
				arights |= KAUTH_VNODE_READ_SECURITY;
			if (w_rights & SMB2_DELETE)
				arights |= KAUTH_VNODE_DELETE;
			
			if (w_rights & SMB2_FILE_WRITE_ATTRIBUTES)
				arights |= KAUTH_VNODE_WRITE_ATTRIBUTES;
			if (w_rights & SMB2_FILE_READ_ATTRIBUTES)
				arights |= KAUTH_VNODE_READ_ATTRIBUTES;
			if (w_rights & SMB2_FILE_DELETE_CHILD)
				arights |= KAUTH_VNODE_DELETE_CHILD;
			if (w_rights & SMB2_FILE_EXECUTE)
				arights |= KAUTH_VNODE_EXECUTE;
			if (w_rights & SMB2_FILE_WRITE_EA)
				arights |= KAUTH_VNODE_WRITE_EXTATTRIBUTES;
			if (w_rights & SMB2_FILE_READ_EA)
				arights |= KAUTH_VNODE_READ_EXTATTRIBUTES;
			if (w_rights & SMB2_FILE_APPEND_DATA)
				arights |= KAUTH_VNODE_APPEND_DATA;
			if (w_rights & SMB2_FILE_WRITE_DATA)
				arights |= KAUTH_VNODE_WRITE_DATA;
			if (w_rights & SMB2_FILE_READ_DATA)
				arights |= KAUTH_VNODE_READ_DATA;
			res->acl_ace[res->acl_entrycount].ace_rights = arights;
			/* Success we have an entry, now count it */ 
			res->acl_entrycount++;
		}
		/* Only return the acl if we have at least one ace. */ 
		if (res->acl_entrycount) {
			vap->va_acl = res;
			res = NULL;			
		}
	}
	
exit:
	if (VATTR_IS_ACTIVE(vap, va_acl))
		VATTR_SET_SUPPORTED(vap, va_acl);
	if (VATTR_IS_ACTIVE(vap, va_guuid))
		VATTR_SET_SUPPORTED(vap, va_guuid);
	if (VATTR_IS_ACTIVE(vap, va_uuuid))
		VATTR_SET_SUPPORTED(vap, va_uuuid);
	
	if (res)
		kauth_acl_free(res);
	
	if (w_sec)
		SMB_FREE(w_sec, M_TEMP);
	
	return error;
}

/*
 * Fill in the network ace used to describe the posix uid, gid and modes.
 */
static struct ntace *
set_nfs_ace(struct ntace *w_acep, ntsid_t *nfs_sid, size_t needed)
{
	struct ntsid	*w_sidp;
	
	wset_acetype(w_acep, ACCESS_DENIED_ACE_TYPE);
	wset_aceflags(w_acep, 0);
	wset_acerights(w_acep, 0);
	w_sidp = acesid(w_acep);
	bcopy(nfs_sid, w_sidp, sizeof(ntsid_t));
	smb_sid_endianize(w_sidp, needed);
	wset_acelen(w_acep, sizeof(struct ntace) + sidlen(w_sidp));			
	return aceace(w_acep);
}

/*
 * This routine will set the owner, group and any ACLs associate with
 * this node. 
 *
 * The calling routine must hold a reference on the share
 *
 */
int 
smbfs_setsecurity(struct smb_share *share, vnode_t vp, struct vnode_attr *vap, 
				  vfs_context_t context)
{
	struct smbnode *np = VTOSMB(vp);
	struct smbmount *smp = np->n_mount;
	uint32_t selector = 0, acecount;
	struct ntsid	*w_usr = NULL, *w_grp = NULL, *w_sidp;
	struct ntacl	*w_dacl = NULL;	/* Wire DACL */
	int error;
	struct ntace *w_acep, *start_acep;	/* Wire ACE */
	struct kauth_ace *acep;
	uint8_t aflags;
	uint32_t arights, openrights;
	size_t needed;
	uint16_t ControlFlags = 0;
	uint16_t fid = 0;
	uuid_string_t out_str;
	
	/* We do not support acl access on a stream node */
	if (vnode_isnamedstream(vp))
		return ENOTSUP;
	
	openrights = SMB2_READ_CONTROL;
	error = 0;
	if (VATTR_IS_ACTIVE(vap, va_guuid) &&  !kauth_guid_equal(&vap->va_guuid, &kauth_null_guid)) {
		SMB_MALLOC(w_grp, struct ntsid *, MAXSIDLEN, M_TEMP, M_WAITOK);
		bzero(w_grp, MAXSIDLEN);
		error = kauth_cred_guid2ntsid(&vap->va_guuid, (ntsid_t *)w_grp);
		if (error) {
			uuid_unparse(*((const uuid_t *)&vap->va_guuid), out_str);
			SMBERROR("kauth_cred_guid2ntsid failed with va_guuid %s and error %d\n", 
					 out_str, error);
			goto exit;
		}
		smb_sid_endianize(w_grp, MAXSIDLEN);
		openrights |= SMB2_WRITE_OWNER;
		selector |= GROUP_SECURITY_INFORMATION;
	}
	if (VATTR_IS_ACTIVE(vap, va_uuuid) && !kauth_guid_equal(&vap->va_uuuid, &kauth_null_guid)) {
		SMB_MALLOC(w_usr, struct ntsid *, MAXSIDLEN, M_TEMP, M_WAITOK);
		bzero(w_usr, MAXSIDLEN);
		/* We are mapping the owner id, so if its a match replace it with the network sid */
		if ((smp->sm_flags & MNT_MAPS_NETWORK_LOCAL_USER) && 
			(kauth_guid_equal(&smp->sm_args.uuid, &vap->va_uuuid))) {
			bcopy(&smp->ntwrk_sids[0], w_usr, sizeof(ntsid_t));
			error = 0;
		} else {
			error = kauth_cred_guid2ntsid(&vap->va_uuuid, (ntsid_t *)w_usr);
		}
		if (error) {
			uuid_unparse(*((const uuid_t *)&vap->va_uuuid), out_str);
			SMBERROR("kauth_cred_guid2ntsid failed with va_uuuid %s and error %d\n", 
					 out_str, error);
			goto exit;
		}
		smb_sid_endianize(w_usr, MAXSIDLEN);
		openrights |= SMB2_WRITE_OWNER;
		selector |= OWNER_SECURITY_INFORMATION;
	}
	if (VATTR_IS_ACTIVE(vap, va_acl)) {
		ntsid_t nfs_sid;

		openrights |= SMB2_WRITE_DAC;
		selector |= DACL_SECURITY_INFORMATION;
		if (vap->va_acl) {
			if (vap->va_acl->acl_flags & KAUTH_FILESEC_NO_INHERIT) {
				selector |= PROTECTED_DACL_SECURITY_INFORMATION;
				ControlFlags |= SE_DACL_PROTECTED;
			} else {
				selector |= UNPROTECTED_DACL_SECURITY_INFORMATION;
				ControlFlags &= ~SE_DACL_PROTECTED;
			}
		}
		
		if ((vap->va_acl == NULL) || (vap->va_acl->acl_entrycount == KAUTH_FILESEC_NOACL)) {
			ControlFlags |= SE_DACL_PRESENT;
			/* If we are removing the ACL nothing left to do but set it. */
			goto set_dacl;
		}
		
		if (vap->va_acl->acl_entrycount > KAUTH_ACL_MAX_ENTRIES) {
			SMBERROR("acl_entrycount=%d, file(%s)\n", 
					 vap->va_acl->acl_entrycount, np->n_name);
			error = EINVAL;
			goto exit;
		}
		acecount = vap->va_acl->acl_entrycount;
		if (np->n_nfs_uid != KAUTH_UID_NONE) {
			/* Make room for the Windows NFS UID ACE */
			acecount += 1;
		}
		if (np->n_nfs_gid != KAUTH_GID_NONE) {
			/* Make room for the Windows NFS GID ACE */
			acecount += 1;
		}
		if (np->n_flag & NHAS_POSIXMODES) {
			/* Make room for the Windows NFS Modes ACE */
			acecount += 1;
		}
		needed = sizeof(struct ntacl) + acecount * (sizeof(struct ntace) + MAXSIDLEN);
		SMB_MALLOC(w_dacl, struct ntacl *, needed, M_TEMP, M_WAITOK);
		bzero(w_dacl, needed);
		w_dacl->acl_revision = 0x02;
		wset_aclacecount(w_dacl, acecount);
		
		start_acep = aclace(w_dacl);
		nfs_sid.sid_kind = 1;
		nfs_sid.sid_authcount = 3;
		memcpy(nfs_sid.sid_authority, security_nt_authority, sizeof(security_nt_authority));
		nfs_sid.sid_authorities[0] = SECURITY_NFS_ID_BASE_RID;

		if (np->n_nfs_uid != KAUTH_UID_NONE) {			
			/* Set the Windows nfs uid ace */
			nfs_sid.sid_authorities[1] = NfsSidTypeOwner;
			nfs_sid.sid_authorities[2] = np->n_nfs_uid;
			start_acep = set_nfs_ace(start_acep,&nfs_sid, needed);
			acecount--;
		}
		if (np->n_nfs_gid != KAUTH_GID_NONE) {
			/* Set the Windows nfs gid ace */
			nfs_sid.sid_authorities[1] = NfsSidTypeGroup;
			nfs_sid.sid_authorities[2] = np->n_nfs_gid;
			start_acep = set_nfs_ace(start_acep,&nfs_sid, needed);
			acecount--;
		}
		if (np->n_flag & NHAS_POSIXMODES) {
			/* Set the Windows nfs posix modes ace */
			nfs_sid.sid_authorities[1] = NfsSidTypeModes;
			nfs_sid.sid_authorities[2] = np->n_mode;
			start_acep = set_nfs_ace(start_acep,&nfs_sid, needed);
			acecount--;
		}

		for (w_acep = start_acep, acep = &vap->va_acl->acl_ace[0];
		     acecount--; w_acep = aceace(w_acep), acep++) {
			switch(acep->ace_flags & KAUTH_ACE_KINDMASK) {
				case KAUTH_ACE_PERMIT:
					wset_acetype(w_acep, ACCESS_ALLOWED_ACE_TYPE);
					break;
			    case KAUTH_ACE_DENY:
					wset_acetype(w_acep, ACCESS_DENIED_ACE_TYPE);
					break;
			    case KAUTH_ACE_AUDIT:
					wset_acetype(w_acep, SYSTEM_AUDIT_ACE_TYPE);
					break;
			    case KAUTH_ACE_ALARM:
					wset_acetype(w_acep, SYSTEM_ALARM_ACE_TYPE);
					break;
			    default:
					SMBERROR("ace_flags=0x%x, file(%s)\n", 
							 acep->ace_flags, np->n_name);
					error = EINVAL;
					goto exit;
			}
			aflags = 0;
			if (acep->ace_flags & KAUTH_ACE_INHERITED)
				aflags |= INHERITED_ACE_FLAG;
			if (acep->ace_flags & KAUTH_ACE_FILE_INHERIT)
				aflags |= OBJECT_INHERIT_ACE_FLAG;
			if (acep->ace_flags & KAUTH_ACE_DIRECTORY_INHERIT)
				aflags |= CONTAINER_INHERIT_ACE_FLAG;
			if (acep->ace_flags & KAUTH_ACE_LIMIT_INHERIT)
				aflags |= NO_PROPAGATE_INHERIT_ACE_FLAG;
			if (acep->ace_flags & KAUTH_ACE_ONLY_INHERIT)
				aflags |= INHERIT_ONLY_ACE_FLAG;
			if (acep->ace_flags & KAUTH_ACE_SUCCESS)
				aflags |= SUCCESSFUL_ACCESS_ACE_FLAG;
			if (acep->ace_flags & KAUTH_ACE_FAILURE)
				aflags |= FAILED_ACCESS_ACE_FLAG;
			wset_aceflags(w_acep, aflags);
			arights = 0;
			if (acep->ace_rights & KAUTH_ACE_GENERIC_READ)
				arights |= SMB2_GENERIC_READ;
			if (acep->ace_rights & KAUTH_ACE_GENERIC_WRITE)
				arights |= SMB2_GENERIC_WRITE;
			if (acep->ace_rights & KAUTH_ACE_GENERIC_EXECUTE)
				arights |= SMB2_GENERIC_EXECUTE;
			if (acep->ace_rights & KAUTH_ACE_GENERIC_ALL)
				arights |= SMB2_GENERIC_ALL;
			if (acep->ace_rights & KAUTH_VNODE_SYNCHRONIZE)
				arights |= SMB2_SYNCHRONIZE;
			if (acep->ace_rights & KAUTH_VNODE_CHANGE_OWNER)
				arights |= SMB2_WRITE_OWNER;
			if (acep->ace_rights & KAUTH_VNODE_WRITE_SECURITY)
				arights |= SMB2_WRITE_DAC;
			if (acep->ace_rights & KAUTH_VNODE_READ_SECURITY)
				arights |= SMB2_READ_CONTROL;
			if (acep->ace_rights & KAUTH_VNODE_WRITE_EXTATTRIBUTES)
				arights |= SMB2_FILE_WRITE_EA;
			if (acep->ace_rights & KAUTH_VNODE_READ_EXTATTRIBUTES)
				arights |= SMB2_FILE_READ_EA;
			if (acep->ace_rights & KAUTH_VNODE_WRITE_ATTRIBUTES)
				arights |= SMB2_FILE_WRITE_ATTRIBUTES;
			if (acep->ace_rights & KAUTH_VNODE_READ_ATTRIBUTES)
				arights |= SMB2_FILE_READ_ATTRIBUTES;
			if (acep->ace_rights & KAUTH_VNODE_DELETE_CHILD)
				arights |= SMB2_FILE_DELETE_CHILD;
			if (acep->ace_rights & KAUTH_VNODE_APPEND_DATA)
				arights |= SMB2_FILE_APPEND_DATA;
			if (acep->ace_rights & KAUTH_VNODE_DELETE)
				arights |= SMB2_DELETE;
			if (acep->ace_rights & KAUTH_VNODE_EXECUTE)
				arights |= SMB2_FILE_EXECUTE;
			if (acep->ace_rights & KAUTH_VNODE_WRITE_DATA)
				arights |= SMB2_FILE_WRITE_DATA;
			if (acep->ace_rights & KAUTH_VNODE_READ_DATA)
				arights |= SMB2_FILE_READ_DATA;
			wset_acerights(w_acep, arights);
			w_sidp = acesid(w_acep);
			if ((smp->sm_flags & MNT_MAPS_NETWORK_LOCAL_USER) && 
				(kauth_guid_equal(&smp->sm_args.uuid, &acep->ace_applicable))) {
				bcopy(&smp->ntwrk_sids[0], w_sidp, sizeof(ntsid_t));
			} else {
				error = kauth_cred_guid2ntsid(&acep->ace_applicable, (ntsid_t *)w_sidp);
			}
			if (error) {
				uuid_unparse(*((const uuid_t *)&acep->ace_applicable), out_str);
				SMBERROR("kauth_cred_guid2ntsid failed with va_acl %s and error %d\n", 
						 out_str, error);
				goto exit;
			}
			smb_sid_endianize(w_sidp, needed);
			wset_acelen(w_acep, sizeof(struct ntace) + sidlen(w_sidp));
		}
		wset_acllen(w_dacl, ((char *)w_acep - (char *)w_dacl));
	}
	
set_dacl:
	error = smbfs_tmpopen(share, np, openrights, &fid, context);
	if (error == 0) {
		error = smbfs_setsec(share, fid, selector, ControlFlags,
                             w_usr, w_grp, NULL, w_dacl, context);
		(void)smbfs_tmpclose(share, np, fid, context);
	}
exit:
	SMB_FREE(w_usr, M_TEMP);
	SMB_FREE(w_grp, M_TEMP);
	SMB_FREE(w_dacl, M_TEMP);
	/* The current cache is out of date clear it */
	smbfs_clear_acl_cache(np);
	return (error);
}

/*
* The calling routine must hold a reference on the share
*/
void 
smb_get_sid_list(struct smb_share *share, struct smbmount *smp, struct mdchain *mdp, 
					  uint32_t ntwrk_sids_cnt, uint32_t ntwrk_sid_size)
{
	uint32_t ii;
	int error;
	void *sidbufptr = NULL;
	char *endsidbufptr;
	char *nextsidbufptr;
	struct ntsid *ntwrk_wire_sid;
	ntsid_t *ntwrk_sids = NULL;
	ntsid_t tmpsid;
	uint32_t sidCnt = 0;
		
	if ((ntwrk_sids_cnt == 0) || (ntwrk_sid_size == 0)) {
		SMBDEBUG("ntwrk_sids_cnt = %d ntwrk_sid_size = %d\n", 
				 ntwrk_sids_cnt, ntwrk_sid_size);
		goto done; /* Nothing to do here we are done */
	}
	
	/* Never allocate more than we could have received in this message */
	if (ntwrk_sid_size > SSTOVC(share)->vc_txmax) {
		SMBDEBUG("Too big ntwrk_sid_size = %d\n", ntwrk_sid_size);
		goto done;
	}
	
	/* Max number we will support, about 9K */
	if (ntwrk_sids_cnt > KAUTH_ACL_MAX_ENTRIES) 
		ntwrk_sids_cnt = KAUTH_ACL_MAX_ENTRIES;
	
	SMB_MALLOC(ntwrk_sids, void *, ntwrk_sids_cnt * sizeof(*ntwrk_sids) , M_TEMP, 
		   M_WAITOK | M_ZERO);
	if (ntwrk_sids == NULL) {
		SMBDEBUG("ntwrk_sids malloc failed!\n");
		goto done;		
	}
	SMB_MALLOC(sidbufptr, void *, ntwrk_sid_size, M_TEMP, M_WAITOK);
	if (sidbufptr == NULL) {
		SMBDEBUG("SID malloc failed!\n");
		goto done;
	}
	error = md_get_mem(mdp, sidbufptr, ntwrk_sid_size, MB_MSYSTEM);
	if (error) {
		SMBDEBUG("Could get the list of sids? error = %d\n", error);
		goto done;
	}
	
	endsidbufptr = (char *)sidbufptr + ntwrk_sid_size;
	nextsidbufptr = sidbufptr;
	for (ii = 0; ii < ntwrk_sids_cnt; ii++) {		
		ntwrk_wire_sid = (struct ntsid *)nextsidbufptr;		
		nextsidbufptr += sizeof(*ntwrk_wire_sid);
		/* Make sure we don't overrun our buffer */
		if (nextsidbufptr > endsidbufptr) {
			SMBDEBUG("Network sid[%d] buffer to small start %p current %p end %p\n", 
					 ii, sidbufptr, nextsidbufptr, endsidbufptr);
			break;
		}
		/* 
		 * We are done with nextsidbufptr for this loop, reset it to the next 
		 * entry. The smb_sid2sid16 routine will protect us from any buffer overruns,
		 * so no need to check here.
		 */
		nextsidbufptr += (ntwrk_wire_sid->sid_subauthcount * sizeof(uint32_t));
		
		smb_sid2sid16(ntwrk_wire_sid, &tmpsid, endsidbufptr);
		
		/* Don't store any unix_users or unix_groups sids */
		if (!smb_sid_in_domain(&unix_users_domsid, &tmpsid) &&
			!smb_sid_in_domain(&unix_groups_domsid, &tmpsid)) {
			ntwrk_sids[sidCnt++] = tmpsid;
		} else {
			SMBDEBUG("Skipping ntwrk_wire_sid entry %d\n", ii);
			continue;
		}
		
		if (smbfs_loglevel == SMB_ACL_LOG_LEVEL) {
			smb_printsid(ntwrk_wire_sid, endsidbufptr, "WHOAMI network", NULL, ii, 0);
		}		
	}
	
	/* We skipped some unix_users or unix_groups, resize the buffer down */
	if (sidCnt != ntwrk_sids_cnt) {
		size_t sidarraysize = sidCnt * sizeof(*ntwrk_sids);
		ntsid_t *holdSids = ntwrk_sids;

		ntwrk_sids = NULL;
		SMB_MALLOC(ntwrk_sids, void *, sidarraysize, M_TEMP, M_WAITOK | M_ZERO);
		if (ntwrk_sids) {
			bcopy(holdSids, ntwrk_sids, sidarraysize);
		}
		SMB_FREE(holdSids, M_TEMP);
	}
	
	/*
	 * We found a list of sid returned by the server, we alway use those over
	 * the LSA ones. Remove the LSA ones and mark that we have WHOAMI SIDS.
	 */
	if (sidCnt && ntwrk_sids) {
		SMB_FREE(smp->ntwrk_sids, M_TEMP);
		smp->ntwrk_sids_cnt = sidCnt;
		smp->ntwrk_sids = ntwrk_sids;
		ntwrk_sids = NULL;
		UNIX_CAPS(share) |= UNIX_QFS_POSIX_WHOAMI_SID_CAP;
	}
	
done:
	/* Just clean up */
	SMB_FREE(ntwrk_sids, M_TEMP);
	SMB_FREE(sidbufptr, M_TEMP);
}

/*
 * This server must be our Samba server, so use the ACL and the list of SIDs
 * return in the whoami call to calculate the true maximum access.
 *
 * The calling routine must hold a reference on the share
 *
 */
static void 
UpdateMaximumAccessFromACLs(struct smb_share *share, struct smbnode *np, 
							vfs_context_t context)
{
	struct ntsecdesc *w_sec = NULL;
	size_t				seclen;
	struct ntacl		*w_dacl = NULL;
	char				*endptr;
	struct ntace		*w_acep = NULL;
	int					error;
	uint32_t			AccessRightsWeChecked = SA_RIGHT_FILE_ALL_ACCESS | STD_RIGHT_ALL_ACCESS;
	uint32_t			CurrentRights = SMB2_DELETE; /* Always allow delete unless its denied */
	uint32_t			j;
	
	/* 
	 * Default to full access, incase we have errors, always let the server 
	 * have the final nay say 
	 */
	np->maxAccessRights = SA_RIGHT_FILE_ALL_ACCESS | STD_RIGHT_ALL_ACCESS;
	error = smbfs_update_acl_cache(share, np, context, &w_sec, &seclen);
	if (error || (w_sec == NULL))
		return;
	
	endptr = (char *)w_sec+seclen;
	
	w_dacl = sddacl(w_sec, seclen);
	if (w_dacl == NULL) {
		SMB_FREE(w_sec, M_TEMP);
		return;
	}
	
	for (j = 0, w_acep = aclace(w_dacl); (((char *)acesid(w_acep) < endptr) && 
			(j < letohs(w_dacl->acl_acecount))); j++, w_acep = aceace(w_acep)) {
		struct ntsid		*w_sidp = acesid(w_acep);
		ntsid_t				sid;
		uint32_t sid_index;
		
		if ((acetype(w_acep) != ACCESS_ALLOWED_ACE_TYPE) && 
			(acetype(w_acep) != ACCESS_DENIED_ACE_TYPE)) {
			continue;	/* We only care about allow and denied ACE */
		}
		if (aceflags(w_acep) & INHERIT_ONLY_ACE_FLAG) {
			continue;	/* Skip inherit-only entries. */
		}
		if ((char *)w_sidp+sizeof(*w_sidp) > endptr) {
			break; /* Must be done */				
		}
		smb_sid2sid16(w_sidp, &sid, (char*)w_sec+seclen);
		for (sid_index = 0; sid_index < np->n_mount->ntwrk_sids_cnt; sid_index++) {
			if (bcmp(&np->n_mount->ntwrk_sids[sid_index], &sid, sizeof(sid)) == 0) {
				/* only care about rights we havn't looked at yet */
				uint32_t w_rights = acerights(w_acep) & AccessRightsWeChecked;
				
				if (acetype(w_acep) == ACCESS_ALLOWED_ACE_TYPE)
					CurrentRights |= w_rights;
				else if (acetype(w_acep) == ACCESS_DENIED_ACE_TYPE)
					CurrentRights &= ~w_rights;
				
				AccessRightsWeChecked &= ~w_rights;
				break;
			}
		}
	}
	if (w_sec)
		SMB_FREE(w_sec, M_TEMP);
	np->maxAccessRights = CurrentRights;
	/*
	 * We weren't granted delete access, but the parent allows delete child
	 * so say we have delete access on the item. If no parent then just
	 * say we have delete access (let the server control it).
	 */
	if (((np->maxAccessRights & SMB2_DELETE) != SMB2_DELETE) &&
		(!np->n_parent || (np->n_parent->maxAccessRights & SMB2_FILE_DELETE_CHILD))) {
		np->maxAccessRights |= SMB2_DELETE;
	}
}

/*
 * Need to check to see if the maximum access rights needs to be updated. We
 * use the node's change time to determine when we need to update. We check to
 * see if the node's change time has changed since the last time we got the
 * maximum access rights. The change time is cached as part of the node's meta
 * data. So the maximum access rights is cached based on the node's meta cache
 * timer and the node's change time.
 *
 * Now we have some issues we need to deal with here. We can get the maximum 
 * access rights from the extended open reply. Windows server always return the 
 * correct maximum access rights, but some servers lie. Samba just returns that
 * you have full access. Also if the server doesn't support the extended open
 * reply we never test again and the cache never expired. 
 *
 * For servers that supports the extended open reply we just believe they are
 * returning the correct information. We do make one exception, our Samba server
 * supports sending us all the users' SIDs in the whoami call. So if we have the
 * user's SIDs then we get the ACLs and compute the maximum access rights ourself.
 * Since we always have at least one network SID, make sure there is more than one
 * before computing the maximal access ourself.
 *
 * If the server doesn't support the extended open reply, then we will set the
 * maximum access rights to full access. We mark that the call failed so we
 * don't need to update ever agian.
 *
 * The calling routine must hold a reference on the share
 *
 */
uint32_t 
smbfs_get_maximum_access(struct smb_share *share, vnode_t vp, vfs_context_t context)
{
	struct smbnode *np;
	uint32_t maxAccessRights;
	
	/* 
	 * Need to have the node locked while getting the maximum access rights. In
	 * the future we may want to only lock what we need.
	 */
	if (smbnode_lock(VTOSMB(vp), SMBFS_EXCLUSIVE_LOCK))
		return 0;
	
	np = VTOSMB(vp);
	np->n_lastvop = smbfs_get_maximum_access;
	/*
	 * We can't open a reparse point that has a Dfs tag, so don't even try. Let
	 * the server handle any security issues.
	 */
	if ((np->n_dosattr &  SMB_EFA_REPARSE_POINT) && 
        (np->n_reparse_tag == IO_REPARSE_TAG_DFS)) {
		np->maxAccessRights = SA_RIGHT_FILE_ALL_ACCESS | STD_RIGHT_ALL_ACCESS;
		goto done;
	}
	/* This server doesn't support maximum access, just lie */
	if (np->n_flag & NO_EXTENDEDOPEN)
		goto done;
	
	/* Cache is still good, return the last value we had */
	if (timespeccmp(&np->maxAccessRightChTime, &np->n_chtime, ==))
		goto done;
	
	/* 
	 * XXX - 10809405 Another case were we should quit working around server bugs.
	 *
     *       This is a hack and a performance killer, but it works around a bug in Samba Servers.
     *       They always report that you have complete access. We want to be backwards compatible 
     *       to Leopard and Snow Leopard Servers, so if they support ACLs use the ACLs to 
     *       find out the access. Very bad for performance, but its what we did in the past.
     *       Some day soon we should just remove this code. 
     */
	if ((share->ss_attributes & FILE_PERSISTENT_ACLS) && (!(SSTOVC(share)->vc_flags & SMBV_DARWIN)) &&
		((UNIX_CAPS(share) & UNIX_QFS_POSIX_WHOAMI_SID_CAP))) {
		/* Get the maximum access from the acl and our list of whoami sids. */
		UpdateMaximumAccessFromACLs(share, np, context);
		/* The open call will not set the cache timer for Samba  so it here */
		np->maxAccessRightChTime = np->n_chtime;
	} 
    else {
		int			error;
		uint16_t	fid = 0;
		
		/* 
		 * For Windows we only need to open and close the item. Need to see if 
		 * we can write a routine that does a CreateAndX/close in one message.  
		 * Also Windows server allow us to get the maximum access by opening the 
		 * file without requesting access. This allows us to get the maximum  
		 * access, even when we can't read the security descriptor. We need to 
		 * test with other servers and see if they behave the same as windows or 
		 * do we need to open them with SMB2_READ_CONTROL?
         *
         * <9874997> In Lion, we started using maximal access returned by the
         * server. One odd setup with a Windows 2003 server where the maximal
         * access returned in the Tree Connect response (share ACL) gave more 
         * access than the maximal access given by CreateAndX on the '\' folder
         * (filesystem ACL).
         * Treat the root folder as a special case.
         * 1) If the CreateAndX fails on the root, then assume full access
         * 2) If the CreateAndX works on the root, if no ReadAttr or Execute BUT
         * the share ACL grants ReadAttr or Execute then assume '\' also has
         * ReadAttr or Execute.
		 */
        /*
         * We could solve a lot of headaches by testing for the servers that do
         * not support opening the item with no access. Something like the following
         * should work (accessOpenModes defaults to zero):
         *
         * error = smbfs_tmpopen(share, np, share->accessOpenModes, &fid, context);
         * if (error && share->firstAccessOpen) {
         *      share->accessOpenModes = SMB2_READ_CONTROL;
         *      error = smbfs_tmpopen(share, np, share->accessOpenModes, &fid, context);
         *  }
         *  share->firstAccessOpen = TRUE;
         *
         * At this point we should just trust what they say, may want to make an exception
         * for the root node, and non darwin Unix systems.
         */
		error = smbfs_tmpopen(share, np, 0, &fid, context);
		if (error) {
            if (error != EACCES) {
 				/* We have no idea why it failed, give them full access. */
				np->maxAccessRights = SA_RIGHT_FILE_ALL_ACCESS | STD_RIGHT_ALL_ACCESS;
            } else {
                if (vnode_isvroot(np->n_vnode)) {
                    np->maxAccessRights = SA_RIGHT_FILE_ALL_ACCESS | STD_RIGHT_ALL_ACCESS;                  
                } else if (((!UNIX_SERVER(SSTOVC(share))) || (SSTOVC(share)->vc_flags & SMBV_DARWIN))) {
                    /* Windows or Darwin Server and they told us we have no access. */
                    np->maxAccessRights = 0;
                } else {
                    np->maxAccessRights = SA_RIGHT_FILE_ALL_ACCESS | STD_RIGHT_ALL_ACCESS;                                    
                }
            }			
            SMB_LOG_ACCESS("Opening %s failed with error = %d, granting %s access\n",
						   np->n_name, error, (np->maxAccessRights) ? "full" : "no");
			/* 
             * The open call failed so the cache timer wasn't set, so do that 
             * here 
             */
			np->maxAccessRightChTime = np->n_chtime;
		} else {
			smbfs_tmpclose(share, np, fid, context);

            if (vnode_isvroot(np->n_vnode)) {
                /* 
                 * its the root folder, if no Execute, but share grants
                 * Execute then grant Execute to root folder
                 */
                if ((!(np->maxAccessRights & SMB2_FILE_EXECUTE)) && 
                    (share->maxAccessRights & SMB2_FILE_EXECUTE)) {
                    np->maxAccessRights |= SMB2_FILE_EXECUTE;
                }
                
                /* 
                 * its the root, if no ReadAttr, but share grants
                 * ReadAttr then grant ReadAttr to root 
                 */
                if ((!(np->maxAccessRights & SMB2_FILE_READ_ATTRIBUTES)) && 
                    (share->maxAccessRights & SMB2_FILE_READ_ATTRIBUTES)) {
                    np->maxAccessRights |= SMB2_FILE_READ_ATTRIBUTES;
                }
            }
        }
	}
	SMB_LOG_ACCESS("%s maxAccessRights = 0x%x\n", np->n_name, 
                                                  np->maxAccessRights);
done:
	maxAccessRights = np->maxAccessRights;
	smbnode_unlock(VTOSMB(vp));
	return maxAccessRights;
}

/* 
 * The composition:
 *
 * (RED, REA, RID, RIA) is what we receive with the vnop. Those are:
 *	RED - Requested Explicit Deny
 *	REA - Requested Explicit Allow
 *	RID - Requested Inherited Deny
 *	RIA - Requested Inherited Allow
 * That's the canonical order the ACEs should have arrived in, but in
 * reality we should never get inherited ACE here and the order could
 * be different depending on what the calling application is trying to
 * accomplish. If copying the item they may want to add a full ACE as
 * the first element and then remove it when they are done. We no longer
 * inforce the order at our level.
 *
 * (SED, SEA, SID, SIA) is what we receive from the server. Those are:
 *	SED - Server Explicit (defaulted) Deny
 *	SEA - Server Explicit (defaulted) Allow
 *	SID - Server Inherited Deny
 *	SIA - Server Inherited Allow
 *
 * This is the canonical order the ACEs should have arrived in, but in
 * reality we should only get inherited ACE here. Now Samba will send 
 * us a DIRECT ACE that represents the POSIX MODES. We always trust that
 * the server has these stored in the correct canonical.
 *
 * NOTE: Windows normally has an allow-all ACE for the object owner and 
 * another allow ACE for Local System.
 *
 * NOTE: If we were going the put these in canonical orer this is what
 * we would need to do. We would take the (RED, REA, RID, RIA) and the 
 * (SED, SEA, SID, SIA) and write back (SED, RED, SEA, REA, SID, RID, SIA, RIA)
 * All non-deny ACEs, for instance audit or alarm types, can be
 * treated the same w/r/t canonicalizing the ACE order.
 *
 * With that said this is what we do here. We create a new ACL that is large
 * enough to hold both ACLS. We remove any inherited ACEs that are in the 
 * VNOP ACL. We then combind the two set of ACEs into one set VNOP ACEs first
 * followed by the SERVER ACEs. Since we are only adding direct ACEs and the 
 * server ACEs should all be inherited this sould be correct.
 */
int 
smbfs_compose_create_acl(struct vnode_attr *vap, struct vnode_attr *svrva, 
						 kauth_acl_t *savedacl)
{
	int32_t entries, allocated;
	struct kauth_ace *acep;
	kauth_acl_t newacl;
	uint32_t j;

	allocated = vap->va_acl->acl_entrycount + svrva->va_acl->acl_entrycount;
	newacl = kauth_acl_alloc(allocated);
	if (newacl == NULL) {
		SMBERROR("kauth_acl_alloc, %d\n", allocated);
		return ENOMEM;
	}
	
	newacl->acl_flags = svrva->va_acl->acl_flags;
	entries = 0;		/* output index for ACL we're building */
	
	/* First add the vnop ACEs, skipping any inherited ACEs or dups */
	for (j = 0; j < vap->va_acl->acl_entrycount; j++) {
		acep = &vap->va_acl->acl_ace[j];
		if (acep->ace_flags & KAUTH_ACE_INHERITED) {
			SMBERROR("Skipping ACE becuase VNOP is trying to set an inherited ACE\n");
			continue;
		}
		newacl->acl_ace[entries++] = *acep;
		if (entries > allocated) {
			kauth_acl_free(newacl);
			return EINVAL;
		}
	}
	
	/* Now add the create ACE assume they are all inherited. */
	for (j = 0; j < svrva->va_acl->acl_entrycount; j++) {
		acep = &svrva->va_acl->acl_ace[j];
		newacl->acl_ace[entries++] = *acep;
		if (entries > allocated) {
			kauth_acl_free(newacl);
			return EINVAL;
		}
	}
	newacl->acl_entrycount = entries;
	*savedacl = vap->va_acl;
	vap->va_acl = newacl;
	return 0;
}	

/* 
 * Check to see if Directory Service understands this sid
 */
int 
smbfs_is_sid_known(ntsid_t *sid)
{
	int error;
	guid_t unique_identifier;
	ntsid_t sidFromUUID;
	
	/* See if DS can translate the SID into a UUID */ 
	error = kauth_cred_ntsid2guid(sid, &unique_identifier);
	if (error) {
		SMBDEBUG("kauth_cred_ntsid2guid failed error = %d\n", error);
		return FALSE;
	}
	/* See if DS gave us a temp UUID */ 
	if (is_memberd_tempuuid(&unique_identifier)) {
		return FALSE;
	}
	/* See if DS can translate the UUID back into a SID */ 
	error = kauth_cred_guid2ntsid(&unique_identifier, &sidFromUUID);
	if (error) {
		SMBDEBUG("kauth_cred_guid2ntsid failed error = %d\n", error);
		return FALSE;
	}
	/* Could we round trip the sid, nope the turn off ACLS */ 
	if (memcmp(&sidFromUUID, sid, sizeof(sidFromUUID)) != 0) {
		SMBWARNING("Couldn't round trip the SID\n");
		return FALSE;
	}
	return TRUE;
} 

/*
 * This items doesn't have any ACL, must only have posix modes. Just set the
 * posix ace.
 */
static int
smbfs_set_default_nfs_ace(struct smb_share *share, struct smbnode *np, vfs_context_t context)
{
	int error;
	uint16_t fid = 0;
	ntsid_t nfs_sid;
	size_t needed;
	uint32_t acecount = 1;
	struct ntacl *w_dacl = NULL;	/* Wire DACL */
	struct ntace *w_acep;	/* Wire ACE */
	
	error = smbfs_tmpopen(share, np, SMB2_READ_CONTROL | SMB2_WRITE_DAC, &fid,
                          context);
	if (error) {
		return error;
	}
	
	needed = sizeof(struct ntacl) + acecount * (sizeof(struct ntace) + MAXSIDLEN);
	SMB_MALLOC(w_dacl, struct ntacl *, needed, M_TEMP, M_WAITOK | M_ZERO);
	w_dacl->acl_revision = 0x02;
	wset_aclacecount(w_dacl, acecount);
	
	w_acep = aclace(w_dacl);
	nfs_sid.sid_kind = 1;
	nfs_sid.sid_authcount = 3;
	memcpy(nfs_sid.sid_authority, security_nt_authority, sizeof(security_nt_authority));
	nfs_sid.sid_authorities[0] = SECURITY_NFS_ID_BASE_RID;
	/* Set the Windows nfs posix modes ace */
	nfs_sid.sid_authorities[1] = NfsSidTypeModes;
	nfs_sid.sid_authorities[2] = np->n_mode;
	w_acep = set_nfs_ace(w_acep, &nfs_sid, needed);
	wset_acllen(w_dacl, ((char *)w_acep - (char *)w_dacl));
	error = smbfs_setsec(share, fid, DACL_SECURITY_INFORMATION, 0, NULL, NULL, 
                         NULL, w_dacl, context);
	
	(void)smbfs_tmpclose(share, np, fid, context);
	SMB_FREE(w_dacl, M_TEMP);
	/* The current cache is out of date clear it */
	smbfs_clear_acl_cache(np);
	return error;
}

/*
 * Use the Windows NFS ACE to hold the Posix modes. Should we store the UID and
 * GID? Currently no, we only care about the posix modes, use the OWNER and
 * GROUP ACE to handle the uid/gid.
 */
int
smbfs_set_ace_modes(struct smb_share *share, struct smbnode *np, uint64_t vamode, vfs_context_t context)
{
	int error;
	struct vnode_attr va;
	mode_t		save_mode = np->n_mode;
	
	/* Get the ACL from the server, so we can do the set */
	memset(&va, 0, sizeof(va));
	VATTR_INIT(&va);
	VATTR_SET_ACTIVE(&va, va_acl);
	error = smbfs_getsecurity(share, np, &va, context);
	if (error) {
		return error;
	}	
	/* 
	 * Set that we have a Windows NFS posix mode ace, so the set acl routine 
	 * will add them to the ACL. 
	 */
	np->n_flag |= NHAS_POSIXMODES;
	np->n_mode &= ~ACCESSPERMS;
	np->n_mode |= (mode_t)(vamode & ACCESSPERMS);
	if (va.va_acl == NULL) {
		error = smbfs_set_default_nfs_ace(share, np,context);
	} else {
		VATTR_SET_ACTIVE(&va, va_acl);
		error = smbfs_setsecurity(share, np->n_vnode, &va, context);
	}

	if (error) {
		/* Reset the posix modes back since we failed. */
		np->n_mode &= ~ACCESSPERMS;
		np->n_mode |= (mode_t)(save_mode & ACCESSPERMS);
	}
	/* Free any ACL we got from the smbfs_getsecurity routine */
	if (va.va_acl) {
		kauth_acl_free(va.va_acl);
	}
	return error;
}