/*
 * 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 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * SMB/CIFS share cache implementation.
 */

#include <errno.h>
#include <synch.h>
#include <stdlib.h>
#include <strings.h>
#include <syslog.h>
#include <thread.h>
#include <pthread.h>
#include <assert.h>
#include <libshare.h>

#include <smbsrv/libsmb.h>
#include <smbsrv/libsmbns.h>
#include <smbsrv/libmlsvc.h>

#include <smbsrv/lm.h>
#include <smbsrv/smb_share.h>
#include <smbsrv/cifs.h>
#include <smbsrv/nterror.h>

#define	SMB_SHR_ERROR_THRESHOLD		3

/*
 * Cache functions and vars
 */
#define	SMB_SHR_HTAB_SZ			1024

/*
 * Cache handle
 *
 * Shares cache is a hash table.
 *
 * sc_cache		pointer to hash table handle
 * sc_cache_lck		synchronize cache read/write accesses
 * sc_state		cache state machine values
 * sc_nops		number of inflight/pending cache operations
 * sc_mtx		protects handle fields
 */
typedef struct smb_shr_cache {
	HT_HANDLE	*sc_cache;
	rwlock_t	sc_cache_lck;
	mutex_t		sc_mtx;
	cond_t		sc_cv;
	uint32_t	sc_state;
	uint32_t	sc_nops;
} smb_shr_cache_t;

/*
 * Cache states
 */
#define	SMB_SHR_CACHE_STATE_NONE	0
#define	SMB_SHR_CACHE_STATE_CREATED	1
#define	SMB_SHR_CACHE_STATE_DESTROYING	2

/*
 * Cache lock modes
 */
#define	SMB_SHR_CACHE_RDLOCK	0
#define	SMB_SHR_CACHE_WRLOCK	1

static smb_shr_cache_t smb_shr_cache;

static uint32_t smb_shr_cache_create(void);
static void smb_shr_cache_destroy(void);
static uint32_t smb_shr_cache_lock(int);
static void smb_shr_cache_unlock(void);
static int smb_shr_cache_count(void);
static smb_share_t *smb_shr_cache_iterate(smb_shriter_t *);

static smb_share_t *smb_shr_cache_findent(char *);
static uint32_t smb_shr_cache_addent(smb_share_t *);
static void smb_shr_cache_delent(char *);
static void smb_shr_cache_freent(HT_ITEM *);

/*
 * sharemgr functions
 */
static void *smb_shr_sa_loadall(void *);
static void smb_shr_sa_loadgrp(sa_group_t);
static uint32_t smb_shr_sa_load(sa_share_t, sa_resource_t);
static uint32_t smb_shr_sa_get(sa_share_t, sa_resource_t, smb_share_t *);

/*
 * share publishing
 */
#define	SMB_SHR_PUBLISH		0
#define	SMB_SHR_UNPUBLISH	1

typedef struct smb_shr_pitem {
	list_node_t	spi_lnd;
	char		spi_name[MAXNAMELEN];
	char		spi_container[MAXPATHLEN];
	char		spi_op;
} smb_shr_pitem_t;

/*
 * publish queue states
 */
#define	SMB_SHR_PQS_NOQUEUE	0
#define	SMB_SHR_PQS_READY	1	/* the queue is ready */
#define	SMB_SHR_PQS_PUBLISHING	2	/* publisher thread is running */
#define	SMB_SHR_PQS_STOPPING	3

/*
 * share publishing queue
 */
typedef struct smb_shr_pqueue {
	list_t		spq_list;
	mutex_t		spq_mtx;
	cond_t		spq_cv;
	uint32_t	spq_state;
} smb_shr_pqueue_t;

static smb_shr_pqueue_t ad_queue;

static int smb_shr_publisher_start(void);
static void smb_shr_publisher_stop(void);
static void smb_shr_publisher_send(smb_ads_handle_t *, list_t *, const char *);
static void smb_shr_publisher_queue(const char *, const char *, char);
static void *smb_shr_publisher(void *);
static void smb_shr_publisher_flush(list_t *);
static void smb_shr_publish(const char *, const char *);
static void smb_shr_unpublish(const char *, const char *);

/*
 * Utility/helper functions
 */
static uint32_t smb_shr_addipc(void);
static void smb_shr_set_oemname(smb_share_t *);

/*
 * Starts the publisher thread and another thread which
 * populates the share cache by share information stored
 * by sharemgr
 */
int
smb_shr_start(void)
{
	pthread_t load_thr;
	pthread_attr_t tattr;
	int rc;

	if ((rc = smb_shr_publisher_start()) != 0)
		return (rc);

	if (smb_shr_cache_create() != NERR_Success)
		return (ENOMEM);

	if (smb_shr_addipc() != NERR_Success)
		return (ENOMEM);

	(void) pthread_attr_init(&tattr);
	(void) pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED);
	rc = pthread_create(&load_thr, &tattr, smb_shr_sa_loadall, 0);
	(void) pthread_attr_destroy(&tattr);

	return (rc);
}

void
smb_shr_stop(void)
{
	smb_shr_cache_destroy();
	smb_shr_publisher_stop();
}

/*
 * Return the total number of shares
 */
int
smb_shr_count(void)
{
	int n_shares = 0;

	if (smb_shr_cache_lock(SMB_SHR_CACHE_RDLOCK) == NERR_Success) {
		n_shares = smb_shr_cache_count();
		smb_shr_cache_unlock();
	}

	return (n_shares);
}

/*
 * smb_shr_iterinit
 *
 * Initialize given iterator for traversing hash table.
 */
void
smb_shr_iterinit(smb_shriter_t *shi)
{
	bzero(shi, sizeof (smb_shriter_t));
	shi->si_first = B_TRUE;
}

/*
 * smb_shr_iterate
 *
 * Iterate on the shares in the hash table. The iterator must be initialized
 * before the first iteration. On subsequent calls, the iterator must be
 * passed unchanged.
 *
 * Returns NULL on failure or when all shares are visited, otherwise
 * returns information of visited share.
 */
smb_share_t *
smb_shr_iterate(smb_shriter_t *shi)
{
	smb_share_t *share = NULL;
	smb_share_t *cached_si;

	if (shi == NULL)
		return (NULL);

	if (smb_shr_cache_lock(SMB_SHR_CACHE_RDLOCK) == NERR_Success) {
		if ((cached_si = smb_shr_cache_iterate(shi)) != NULL) {
			share = &shi->si_share;
			bcopy(cached_si, share, sizeof (smb_share_t));
		}
		smb_shr_cache_unlock();
	}

	return (share);
}

/*
 * Adds the given share to cache, publishes the share in ADS
 * if it has an AD container, calls kernel to take a hold on
 * the shared file system. If it can't take a hold on the
 * shared file system, it's either because shared directory
 * does not exist or some other error has occurred, in any
 * case the share is removed from the cache.
 *
 * If the specified share is an autohome share which already
 * exists in the cache, just increments the reference count.
 */
uint32_t
smb_shr_add(smb_share_t *si)
{
	smb_share_t *cached_si;
	uint32_t status;
	int rc;

	assert(si != NULL);

	if (!smb_shr_chkname(si->shr_name))
		return (ERROR_INVALID_NAME);

	if (smb_shr_cache_lock(SMB_SHR_CACHE_WRLOCK) != NERR_Success)
		return (NERR_InternalError);

	cached_si = smb_shr_cache_findent(si->shr_name);
	if (cached_si) {
		if (si->shr_flags & SMB_SHRF_AUTOHOME) {
			cached_si->shr_refcnt++;
			status = NERR_Success;
		} else {
			status = NERR_DuplicateShare;
		}
		smb_shr_cache_unlock();
		return (status);
	}

	if ((status = smb_shr_cache_addent(si)) != NERR_Success) {
		smb_shr_cache_unlock();
		return (status);
	}

	/* don't hold the lock across door call */
	smb_shr_cache_unlock();

	/* call kernel to take a hold on the shared file system */
	rc = mlsvc_set_share(SMB_SHROP_ADD, si->shr_path, si->shr_name);

	if (rc == 0) {
		smb_shr_publish(si->shr_name, si->shr_container);
		return (NERR_Success);
	}

	if (smb_shr_cache_lock(SMB_SHR_CACHE_WRLOCK) == NERR_Success) {
		smb_shr_cache_delent(si->shr_name);
		smb_shr_cache_unlock();
	}

	/*
	 * rc == ENOENT means the shared directory doesn't exist
	 */
	return ((rc == ENOENT) ? NERR_UnknownDevDir : NERR_InternalError);
}

/*
 * Removes the specified share from cache, removes it from AD
 * if it has an AD container, and calls the kernel to release
 * the hold on the shared file system.
 *
 * If this is an autohome share then decrement the reference
 * count. If it reaches 0 then it proceeds with removing steps.
 */
uint32_t
smb_shr_remove(char *sharename)
{
	smb_share_t *si;
	char path[MAXPATHLEN];
	char container[MAXPATHLEN];

	assert(sharename != NULL);

	if (!smb_shr_chkname(sharename))
		return (ERROR_INVALID_NAME);

	if (smb_shr_cache_lock(SMB_SHR_CACHE_WRLOCK) != NERR_Success)
		return (NERR_InternalError);

	if ((si = smb_shr_cache_findent(sharename)) == NULL) {
		smb_shr_cache_unlock();
		return (NERR_NetNameNotFound);
	}

	if (si->shr_type & STYPE_IPC) {
		/* IPC$ share cannot be removed */
		smb_shr_cache_unlock();
		return (ERROR_ACCESS_DENIED);
	}

	if (si->shr_flags & SMB_SHRF_AUTOHOME) {
		if ((--si->shr_refcnt) > 0) {
			smb_shr_cache_unlock();
			return (NERR_Success);
		}
	}

	(void) strlcpy(path, si->shr_path, sizeof (path));
	(void) strlcpy(container, si->shr_container, sizeof (container));
	smb_shr_cache_delent(sharename);
	smb_shr_cache_unlock();

	smb_shr_unpublish(sharename, container);

	/* call kernel to release the hold on the shared file system */
	(void) mlsvc_set_share(SMB_SHROP_DELETE, path, sharename);

	return (NERR_Success);
}

/*
 * Rename a share. Check that the current name exists and the new name
 * doesn't exist. The rename is performed by deleting the current share
 * definition and creating a new share with the new name.
 */
uint32_t
smb_shr_rename(char *from_name, char *to_name)
{
	smb_share_t *from_si;
	smb_share_t to_si;
	uint32_t status;

	assert((from_name != NULL) && (to_name != NULL));

	if (!smb_shr_chkname(from_name) || !smb_shr_chkname(to_name))
		return (ERROR_INVALID_NAME);

	if (smb_shr_cache_lock(SMB_SHR_CACHE_WRLOCK) != NERR_Success)
		return (NERR_InternalError);

	if ((from_si = smb_shr_cache_findent(from_name)) == NULL) {
		smb_shr_cache_unlock();
		return (NERR_NetNameNotFound);
	}

	if (from_si->shr_type & STYPE_IPC) {
		/* IPC$ share cannot be renamed */
		smb_shr_cache_unlock();
		return (ERROR_ACCESS_DENIED);
	}

	if (smb_shr_cache_findent(to_name) != NULL) {
		smb_shr_cache_unlock();
		return (NERR_DuplicateShare);
	}

	bcopy(from_si, &to_si, sizeof (smb_share_t));
	(void) strlcpy(to_si.shr_name, to_name, sizeof (to_si.shr_name));

	if ((status = smb_shr_cache_addent(&to_si)) != NERR_Success) {
		smb_shr_cache_unlock();
		return (status);
	}

	smb_shr_cache_delent(from_name);
	smb_shr_cache_unlock();

	smb_shr_unpublish(from_name, to_si.shr_container);
	smb_shr_publish(to_name, to_si.shr_container);

	return (NERR_Success);
}

/*
 * Load the information for the specified share into the supplied share
 * info structure.
 */
uint32_t
smb_shr_get(char *sharename, smb_share_t *si)
{
	smb_share_t *cached_si;
	uint32_t status = NERR_NetNameNotFound;

	if (sharename == NULL || *sharename == '\0')
		return (NERR_NetNameNotFound);

	if (smb_shr_cache_lock(SMB_SHR_CACHE_RDLOCK) == NERR_Success) {
		cached_si = smb_shr_cache_findent(sharename);
		if (cached_si != NULL) {
			bcopy(cached_si, si, sizeof (smb_share_t));
			status = NERR_Success;
		}

		smb_shr_cache_unlock();
	}

	return (status);
}

/*
 * Modifies an existing share. Properties that can be modified are:
 *
 *   o comment
 *   o AD container
 *   o host access
 */
uint32_t
smb_shr_modify(smb_share_t *new_si)
{
	smb_share_t *si;
	boolean_t adc_changed = B_FALSE;
	char old_container[MAXPATHLEN];
	uint32_t access;

	assert(new_si != NULL);

	if (smb_shr_cache_lock(SMB_SHR_CACHE_WRLOCK) != NERR_Success)
		return (NERR_InternalError);

	if ((si = smb_shr_cache_findent(new_si->shr_name)) == NULL) {
		smb_shr_cache_unlock();
		return (NERR_NetNameNotFound);
	}

	if (si->shr_type & STYPE_IPC) {
		/* IPC$ share cannot be modified */
		smb_shr_cache_unlock();
		return (ERROR_ACCESS_DENIED);
	}

	if (strcmp(new_si->shr_cmnt, si->shr_cmnt) != 0)
		(void) strlcpy(si->shr_cmnt, new_si->shr_cmnt,
		    sizeof (si->shr_cmnt));

	adc_changed = (strcmp(new_si->shr_container, si->shr_container) != 0);
	if (adc_changed) {
		/* save current container - needed for unpublishing */
		(void) strlcpy(old_container, si->shr_container,
		    sizeof (old_container));
		(void) strlcpy(si->shr_container, new_si->shr_container,
		    sizeof (si->shr_container));
	}

	access = (new_si->shr_flags & SMB_SHRF_ACC_ALL);
	si->shr_flags |= access;

	if (access & SMB_SHRF_ACC_NONE)
		(void) strlcpy(si->shr_access_none, new_si->shr_access_none,
		    sizeof (si->shr_access_none));

	if (access & SMB_SHRF_ACC_RO)
		(void) strlcpy(si->shr_access_ro, new_si->shr_access_ro,
		    sizeof (si->shr_access_ro));

	if (access & SMB_SHRF_ACC_RW)
		(void) strlcpy(si->shr_access_rw, new_si->shr_access_rw,
		    sizeof (si->shr_access_rw));

	smb_shr_cache_unlock();

	if (adc_changed) {
		smb_shr_unpublish(new_si->shr_name, old_container);
		smb_shr_publish(new_si->shr_name, new_si->shr_container);
	}

	return (NERR_Success);
}

/*
 * smb_shr_exists
 *
 * Returns B_TRUE if the share exists. Otherwise returns B_FALSE
 */
boolean_t
smb_shr_exists(char *sharename)
{
	boolean_t exists = B_FALSE;

	if (sharename == NULL || *sharename == '\0')
		return (B_FALSE);

	if (smb_shr_cache_lock(SMB_SHR_CACHE_RDLOCK) == NERR_Success) {
		exists = (smb_shr_cache_findent(sharename) != NULL);
		smb_shr_cache_unlock();
	}

	return (exists);
}

/*
 * If the shared directory does not begin with a /, one will be
 * inserted as a prefix. If ipaddr is not zero, then also return
 * information about access based on the host level access lists, if
 * present. Also return access check if there is an IP address and
 * shr_accflags.
 *
 * The value of smb_chk_hostaccess is checked for an access match.
 * -1 is wildcard match
 * 0 is no match
 * 1 is match
 *
 * Precedence is none is checked first followed by ro then rw if
 * needed.  If x is wildcard (< 0) then check to see if the other
 * values are a match. If a match, that wins.
 */
void
smb_shr_hostaccess(smb_share_t *si, ipaddr_t ipaddr)
{
	int acc = SMB_SHRF_ACC_OPEN;

	/*
	 * Check to see if there area any share level access
	 * restrictions.
	 */
	if (ipaddr != 0 && (si->shr_flags & SMB_SHRF_ACC_ALL) != 0) {
		int none = SMB_SHRF_ACC_OPEN;
		int rw = SMB_SHRF_ACC_OPEN;
		int ro = SMB_SHRF_ACC_OPEN;

		if (si->shr_flags & SMB_SHRF_ACC_NONE)
			none = smb_chk_hostaccess(ipaddr, si->shr_access_none);
		if (si->shr_flags & SMB_SHRF_ACC_RW)
			rw = smb_chk_hostaccess(ipaddr, si->shr_access_rw);
		if (si->shr_flags & SMB_SHRF_ACC_RO)
			ro = smb_chk_hostaccess(ipaddr, si->shr_access_ro);

		/* make first pass to get basic value */
		if (none != 0)
			acc = SMB_SHRF_ACC_NONE;
		else if (ro != 0)
			acc = SMB_SHRF_ACC_RO;
		else if (rw != 0)
			acc = SMB_SHRF_ACC_RW;

		/* make second pass to handle '*' case */
		if (none < 0) {
			acc = SMB_SHRF_ACC_NONE;
			if (ro > 0)
				acc = SMB_SHRF_ACC_RO;
			else if (rw > 0)
				acc = SMB_SHRF_ACC_RW;
		} else if (ro < 0) {
			acc = SMB_SHRF_ACC_RO;
			if (none > 0)
				acc = SMB_SHRF_ACC_NONE;
			else if (rw > 0)
				acc = SMB_SHRF_ACC_RW;
		} else if (rw < 0) {
			acc = SMB_SHRF_ACC_RW;
			if (none > 0)
				acc = SMB_SHRF_ACC_NONE;
			else if (ro > 0)
				acc = SMB_SHRF_ACC_RO;
		}
	}
	si->shr_access_value = acc;	/* return access here */
}

/*
 * smb_shr_is_special
 *
 * Special share reserved for interprocess communication (IPC$) or
 * remote administration of the server (ADMIN$). Can also refer to
 * administrative shares such as C$, D$, E$, and so forth.
 */
int
smb_shr_is_special(char *sharename)
{
	int len;

	if (sharename == NULL)
		return (0);

	if ((len = strlen(sharename)) == 0)
		return (0);

	if (sharename[len - 1] == '$')
		return (STYPE_SPECIAL);

	return (0);
}

/*
 * smb_shr_is_restricted
 *
 * Check whether or not there is a restriction on a share. Restricted
 * shares are generally STYPE_SPECIAL, for example, IPC$. All the
 * administration share names are restricted: C$, D$ etc. Returns B_TRUE
 * if the share is restricted. Otherwise B_FALSE is returned to indicate
 * that there are no restrictions.
 */
boolean_t
smb_shr_is_restricted(char *sharename)
{
	static char *restricted[] = {
		"IPC$"
	};

	int i;

	if (sharename == NULL)
		return (B_FALSE);

	for (i = 0; i < sizeof (restricted)/sizeof (restricted[0]); i++) {
		if (utf8_strcasecmp(restricted[i], sharename) == 0)
			return (B_TRUE);
	}

	return (smb_shr_is_admin(sharename));
}

/*
 * smb_shr_is_admin
 *
 * Check whether or not access to the share should be restricted to
 * administrators. This is a bit of a hack because what we're doing
 * is checking for the default admin shares: C$, D$ etc.. There are
 * other shares that have restrictions: see smb_shr_is_restricted().
 *
 * Returns B_TRUE if the shares is an admin share. Otherwise B_FALSE
 * is returned to indicate that there are no restrictions.
 */
boolean_t
smb_shr_is_admin(char *sharename)
{
	if (sharename == NULL)
		return (B_FALSE);

	if (strlen(sharename) == 2 &&
	    mts_isalpha(sharename[0]) && sharename[1] == '$') {
		return (B_TRUE);
	}

	return (B_FALSE);
}

/*
 * smb_shr_chkname
 *
 * Check for invalid characters in a share name.  The list of invalid
 * characters includes control characters and the following:
 *
 * " / \ [ ] : | < > + ; , ? * =
 */
boolean_t
smb_shr_chkname(char *sharename)
{
	char *invalid = "\"/\\[]:|<>+;,?*=";
	char *cp;

	if (sharename == NULL)
		return (B_FALSE);

	if (strpbrk(sharename, invalid))
		return (B_FALSE);

	for (cp = sharename; *cp != '\0'; cp++) {
		if (iscntrl(*cp))
			return (B_FALSE);
	}

	return (B_TRUE);
}

/*
 * smb_shr_get_realpath
 *
 * Derive the real path for a share from the path provided by a client.
 * For instance, the real path of C:\ may be /cvol or the real path of
 * F:\home may be /vol1/home.
 *
 * clntpath - path provided by the Windows client is in the
 *            format of <drive letter>:\<dir>
 * realpath - path that will be stored as the directory field of
 *            the smb_share_t structure of the share.
 * maxlen   - maximum length of the realpath buffer
 *
 * Return LAN Manager network error code.
 */
uint32_t
smb_shr_get_realpath(const char *clntpath, char *realpath, int maxlen)
{
	const char *p;
	int len;

	if ((p = strchr(clntpath, ':')) != NULL)
		++p;
	else
		p = clntpath;

	(void) strlcpy(realpath, p, maxlen);
	(void) strcanon(realpath, "/\\");
	(void) strsubst(realpath, '\\', '/');

	len = strlen(realpath);
	if ((len > 1) && (realpath[len - 1] == '/'))
		realpath[len - 1] = '\0';

	return (NERR_Success);
}

void
smb_shr_list(int offset, smb_shrlist_t *list)
{
	smb_shriter_t iterator;
	smb_share_t *si;
	int n = 0;

	bzero(list, sizeof (smb_shrlist_t));
	smb_shr_iterinit(&iterator);

	while ((si = smb_shr_iterate(&iterator)) != NULL) {
		if (--offset > 0)
			continue;

		if ((si->shr_flags & SMB_SHRF_TRANS) &&
		    ((si->shr_type & STYPE_IPC) == 0)) {
			bcopy(si, &list->sl_shares[n], sizeof (smb_share_t));
			if (++n == LMSHARES_PER_REQUEST)
				break;
		}
	}

	list->sl_cnt = n;
}

/*
 * ============================================
 * Private helper/utility functions
 * ============================================
 */

/*
 * Add IPC$ to the cache upon startup.
 */
static uint32_t
smb_shr_addipc(void)
{
	smb_share_t ipc;
	uint32_t status = NERR_InternalError;

	bzero(&ipc, sizeof (smb_share_t));
	(void) strcpy(ipc.shr_name, "IPC$");
	(void) strcpy(ipc.shr_cmnt, "Remote IPC");
	ipc.shr_flags = SMB_SHRF_TRANS;
	ipc.shr_type = STYPE_IPC;

	if (smb_shr_cache_lock(SMB_SHR_CACHE_WRLOCK) == NERR_Success) {
		status = smb_shr_cache_addent(&ipc);
		smb_shr_cache_unlock();
	}

	return (status);
}

/*
 * smb_shr_set_oemname
 *
 * Generate the OEM name for the specified share.  If the name is
 * shorter than 13 bytes the oemname will be saved in si->shr_oemname.
 * Otherwise si->shr_oemname will be empty and SMB_SHRF_LONGNAME will
 * be set in si->shr_flags.
 */
static void
smb_shr_set_oemname(smb_share_t *si)
{
	unsigned int cpid = oem_get_smb_cpid();
	mts_wchar_t *unibuf;
	char *oem_name;
	int length;

	length = strlen(si->shr_name) + 1;

	oem_name = malloc(length);
	unibuf = malloc(length * sizeof (mts_wchar_t));
	if ((oem_name == NULL) || (unibuf == NULL)) {
		free(oem_name);
		free(unibuf);
		return;
	}

	(void) mts_mbstowcs(unibuf, si->shr_name, length);

	if (unicodestooems(oem_name, unibuf, length, cpid) == 0)
		(void) strcpy(oem_name, si->shr_name);

	free(unibuf);

	if (strlen(oem_name) + 1 > SMB_SHARE_OEMNAME_MAX) {
		si->shr_flags |= SMB_SHRF_LONGNAME;
		*si->shr_oemname = '\0';
	} else {
		si->shr_flags &= ~SMB_SHRF_LONGNAME;
		(void) strlcpy(si->shr_oemname, oem_name,
		    SMB_SHARE_OEMNAME_MAX);
	}

	free(oem_name);
}

/*
 * ============================================
 * Cache management functions
 *
 * All cache functions are private
 * ============================================
 */

/*
 * Create the share cache (hash table).
 */
static uint32_t
smb_shr_cache_create(void)
{
	uint32_t status = NERR_Success;

	(void) mutex_lock(&smb_shr_cache.sc_mtx);
	switch (smb_shr_cache.sc_state) {
	case SMB_SHR_CACHE_STATE_NONE:
		smb_shr_cache.sc_cache = ht_create_table(SMB_SHR_HTAB_SZ,
		    MAXNAMELEN, 0);
		if (smb_shr_cache.sc_cache == NULL) {
			status = NERR_InternalError;
			break;
		}

		(void) ht_register_callback(smb_shr_cache.sc_cache,
		    smb_shr_cache_freent);
		smb_shr_cache.sc_nops = 0;
		smb_shr_cache.sc_state = SMB_SHR_CACHE_STATE_CREATED;
		break;

	default:
		assert(0);
		status = NERR_InternalError;
		break;
	}
	(void) mutex_unlock(&smb_shr_cache.sc_mtx);

	return (status);
}

/*
 * Destroy the share cache (hash table).
 * Wait for inflight/pending operations to finish or abort before
 * destroying the cache.
 */
static void
smb_shr_cache_destroy(void)
{
	(void) mutex_lock(&smb_shr_cache.sc_mtx);
	if (smb_shr_cache.sc_state == SMB_SHR_CACHE_STATE_CREATED) {
		smb_shr_cache.sc_state = SMB_SHR_CACHE_STATE_DESTROYING;
		while (smb_shr_cache.sc_nops > 0)
			(void) cond_wait(&smb_shr_cache.sc_cv,
			    &smb_shr_cache.sc_mtx);

		smb_shr_cache.sc_cache = NULL;
		smb_shr_cache.sc_state = SMB_SHR_CACHE_STATE_NONE;
	}
	(void) mutex_unlock(&smb_shr_cache.sc_mtx);
}

/*
 * If the cache is in "created" state, lock the cache for read
 * or read/write based on the specified mode.
 *
 * Whenever a lock is granted, the number of inflight cache
 * operations is incremented.
 */
static uint32_t
smb_shr_cache_lock(int mode)
{
	(void) mutex_lock(&smb_shr_cache.sc_mtx);
	switch (smb_shr_cache.sc_state) {
	case SMB_SHR_CACHE_STATE_CREATED:
		smb_shr_cache.sc_nops++;
		break;

	case SMB_SHR_CACHE_STATE_DESTROYING:
		(void) mutex_unlock(&smb_shr_cache.sc_mtx);
		return (NERR_InternalError);

	case SMB_SHR_CACHE_STATE_NONE:
	default:
		assert(0);
		(void) mutex_unlock(&smb_shr_cache.sc_mtx);
		return (NERR_InternalError);

	}
	(void) mutex_unlock(&smb_shr_cache.sc_mtx);

	/*
	 * Lock has to be taken outside the mutex otherwise
	 * there could be a deadlock
	 */
	if (mode == SMB_SHR_CACHE_RDLOCK)
		(void) rw_rdlock(&smb_shr_cache.sc_cache_lck);
	else
		(void) rw_wrlock(&smb_shr_cache.sc_cache_lck);

	return (NERR_Success);
}

/*
 * Decrement the number of inflight operations and then unlock.
 */
static void
smb_shr_cache_unlock(void)
{
	(void) mutex_lock(&smb_shr_cache.sc_mtx);
	assert(smb_shr_cache.sc_nops > 0);
	smb_shr_cache.sc_nops--;
	(void) cond_broadcast(&smb_shr_cache.sc_cv);
	(void) mutex_unlock(&smb_shr_cache.sc_mtx);

	(void) rw_unlock(&smb_shr_cache.sc_cache_lck);
}

/*
 * Return the total number of shares
 */
static int
smb_shr_cache_count(void)
{
	return (ht_get_total_items(smb_shr_cache.sc_cache));
}

/*
 * looks up the given share name in the cache and if it
 * finds a match returns a pointer to the cached entry.
 * Note that since a pointer is returned this function
 * MUST be protected by smb_shr_cache_lock/unlock pair
 */
static smb_share_t *
smb_shr_cache_findent(char *sharename)
{
	HT_ITEM *item;

	(void) utf8_strlwr(sharename);
	item = ht_find_item(smb_shr_cache.sc_cache, sharename);
	if (item && item->hi_data)
		return ((smb_share_t *)item->hi_data);

	return (NULL);
}

/*
 * Return a pointer to the first/next entry in
 * the cache based on the given iterator.
 *
 * Calls to this function MUST be protected by
 * smb_shr_cache_lock/unlock.
 */
static smb_share_t *
smb_shr_cache_iterate(smb_shriter_t *shi)
{
	HT_ITEM *item;

	if (shi->si_first) {
		item = ht_findfirst(smb_shr_cache.sc_cache, &shi->si_hashiter);
		shi->si_first = B_FALSE;
	} else {
		item = ht_findnext(&shi->si_hashiter);
	}

	if (item && item->hi_data)
		return ((smb_share_t *)item->hi_data);

	return (NULL);
}

/*
 * Add the specified share to the cache.  Memory needs to be allocated
 * for the cache entry and the passed information is copied to the
 * allocated space.
 */
static uint32_t
smb_shr_cache_addent(smb_share_t *si)
{
	smb_share_t *cache_ent;
	uint32_t status = NERR_Success;

	if ((cache_ent = malloc(sizeof (smb_share_t))) == NULL)
		return (ERROR_NOT_ENOUGH_MEMORY);

	bcopy(si, cache_ent, sizeof (smb_share_t));

	(void) utf8_strlwr(cache_ent->shr_name);
	smb_shr_set_oemname(cache_ent);

	if ((si->shr_type & STYPE_IPC) == 0)
		cache_ent->shr_type = STYPE_DISKTREE;
	cache_ent->shr_type |= smb_shr_is_special(cache_ent->shr_name);

	if (smb_shr_is_admin(cache_ent->shr_name))
		cache_ent->shr_flags |= SMB_SHRF_ADMIN;

	if (si->shr_flags & SMB_SHRF_AUTOHOME)
		cache_ent->shr_refcnt = 1;

	if (ht_add_item(smb_shr_cache.sc_cache, cache_ent->shr_name, cache_ent)
	    == NULL) {
		syslog(LOG_DEBUG, "share: %s: cache update failed",
		    cache_ent->shr_name);
		free(cache_ent);
		status = NERR_InternalError;
	}

	return (status);
}

/*
 * Delete the specified share from the cache.
 */
static void
smb_shr_cache_delent(char *sharename)
{
	(void) utf8_strlwr(sharename);
	(void) ht_remove_item(smb_shr_cache.sc_cache, sharename);
}

/*
 * Call back to free the given cache entry.
 */
static void
smb_shr_cache_freent(HT_ITEM *item)
{
	if (item && item->hi_data)
		free(item->hi_data);
}

/*
 * ============================================
 * Interfaces to sharemgr
 *
 * All functions in this section are private
 * ============================================
 */

/*
 * Load shares from sharemgr
 */
/*ARGSUSED*/
static void *
smb_shr_sa_loadall(void *args)
{
	sa_handle_t handle;
	sa_group_t group, subgroup;
	char *gstate;
	boolean_t gdisabled;

	if ((handle = sa_init(SA_INIT_SHARE_API)) == NULL) {
		syslog(LOG_ERR, "share: failed to get libshare API handle");
		return (NULL);
	}

	for (group = sa_get_group(handle, NULL);
	    group != NULL; group = sa_get_next_group(group)) {
		gstate = sa_get_group_attr(group, "state");
		if (gstate == NULL)
			continue;

		gdisabled = (strcasecmp(gstate, "disabled") == 0);
		sa_free_attr_string(gstate);
		if (gdisabled)
			continue;

		smb_shr_sa_loadgrp(group);

		for (subgroup = sa_get_sub_group(group);
		    subgroup != NULL;
		    subgroup = sa_get_next_group(subgroup)) {
			smb_shr_sa_loadgrp(subgroup);
		}

	}

	sa_fini(handle);
	return (NULL);
}

/*
 * Load the shares contained in the specified group.
 *
 * Don't process groups on which the smb protocol is disabled.
 * The top level ZFS group won't have the smb protocol enabled
 * but sub-groups will.
 *
 * We will tolerate a limited number of errors and then give
 * up on the current group.  A typical error might be that the
 * shared directory no longer exists.
 */
static void
smb_shr_sa_loadgrp(sa_group_t group)
{
	sa_share_t share;
	sa_resource_t resource;
	int error_count = 0;

	if (sa_get_optionset(group, SMB_PROTOCOL_NAME) == NULL)
		return;

	for (share = sa_get_share(group, NULL);
	    share != NULL;
	    share = sa_get_next_share(share)) {
		for (resource = sa_get_share_resource(share, NULL);
		    resource != NULL;
		    resource = sa_get_next_resource(resource)) {
			if (smb_shr_sa_load(share, resource))
				++error_count;

			if (error_count > SMB_SHR_ERROR_THRESHOLD)
				break;
		}

		if (error_count > SMB_SHR_ERROR_THRESHOLD)
			break;
	}
}

/*
 * Load a share definition from sharemgr and add it to the cache.
 */
static uint32_t
smb_shr_sa_load(sa_share_t share, sa_resource_t resource)
{
	smb_share_t si;
	uint32_t status;

	if ((status = smb_shr_sa_get(share, resource, &si)) != NERR_Success) {
		syslog(LOG_DEBUG, "share: failed to load %s (%d)",
		    si.shr_name, status);
		return (status);
	}

	if ((status = smb_shr_add(&si)) != NERR_Success) {
		syslog(LOG_DEBUG, "share: failed to cache %s (%d)",
		    si.shr_name, status);
		return (status);
	}

	return (NERR_Success);
}

/*
 * Read the specified share information from sharemgr and return
 * it in the given smb_share_t structure.
 *
 * Shares read from sharemgr are marked as permanent/persistent.
 */
static uint32_t
smb_shr_sa_get(sa_share_t share, sa_resource_t resource, smb_share_t *si)
{
	sa_property_t prop;
	sa_optionset_t opts;
	char *val = NULL;
	char *path;
	char *rname;

	if ((path = sa_get_share_attr(share, "path")) == NULL)
		return (NERR_InternalError);

	if ((rname = sa_get_resource_attr(resource, "name")) == NULL) {
		sa_free_attr_string(path);
		return (NERR_InternalError);
	}

	bzero(si, sizeof (smb_share_t));
	si->shr_flags = SMB_SHRF_PERM;

	(void) strlcpy(si->shr_path, path, sizeof (si->shr_path));
	(void) strlcpy(si->shr_name, rname, sizeof (si->shr_name));

	sa_free_attr_string(path);
	sa_free_attr_string(rname);

	val = sa_get_resource_description(resource);
	if (val == NULL)
		val = sa_get_share_description(share);

	if (val != NULL) {
		(void) strlcpy(si->shr_cmnt, val, sizeof (si->shr_cmnt));
		sa_free_share_description(val);
	}

	opts = sa_get_derived_optionset(resource, SMB_PROTOCOL_NAME, 1);
	if (opts == NULL)
		return (NERR_Success);

	prop = (sa_property_t)sa_get_property(opts, SMB_SHROPT_AD_CONTAINER);
	if (prop != NULL) {
		if ((val = sa_get_property_attr(prop, "value")) != NULL) {
			(void) strlcpy(si->shr_container, val,
			    sizeof (si->shr_container));
			free(val);
		}
	}

	prop = (sa_property_t)sa_get_property(opts, SHOPT_NONE);
	if (prop != NULL) {
		if ((val = sa_get_property_attr(prop, "value")) != NULL) {
			(void) strlcpy(si->shr_access_none, val,
			    sizeof (si->shr_access_none));
			free(val);
			si->shr_flags |= SMB_SHRF_ACC_NONE;
		}
	}

	prop = (sa_property_t)sa_get_property(opts, SHOPT_RO);
	if (prop != NULL) {
		if ((val = sa_get_property_attr(prop, "value")) != NULL) {
			(void) strlcpy(si->shr_access_ro, val,
			    sizeof (si->shr_access_ro));
			free(val);
			si->shr_flags |= SMB_SHRF_ACC_RO;
		}
	}

	prop = (sa_property_t)sa_get_property(opts, SHOPT_RW);
	if (prop != NULL) {
		if ((val = sa_get_property_attr(prop, "value")) != NULL) {
			(void) strlcpy(si->shr_access_rw, val,
			    sizeof (si->shr_access_rw));
			free(val);
			si->shr_flags |= SMB_SHRF_ACC_RW;
		}
	}

	sa_free_derived_optionset(opts);
	return (NERR_Success);
}

/*
 * ============================================
 * Share publishing functions
 *
 * All the functions are private
 * ============================================
 */

static void
smb_shr_publish(const char *sharename, const char *container)
{
	smb_shr_publisher_queue(sharename, container, SMB_SHR_PUBLISH);
}

static void
smb_shr_unpublish(const char *sharename, const char *container)
{
	smb_shr_publisher_queue(sharename, container, SMB_SHR_UNPUBLISH);
}

/*
 * In domain mode, put a share on the publisher queue.
 * This is a no-op if the smb service is in Workgroup mode.
 */
static void
smb_shr_publisher_queue(const char *sharename, const char *container, char op)
{
	smb_shr_pitem_t *item = NULL;

	if (container == NULL || *container == '\0')
		return;

	if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN)
		return;

	(void) mutex_lock(&ad_queue.spq_mtx);
	switch (ad_queue.spq_state) {
	case SMB_SHR_PQS_READY:
	case SMB_SHR_PQS_PUBLISHING:
		break;
	default:
		(void) mutex_unlock(&ad_queue.spq_mtx);
		return;
	}
	(void) mutex_unlock(&ad_queue.spq_mtx);

	if ((item = malloc(sizeof (smb_shr_pitem_t))) == NULL)
		return;

	item->spi_op = op;
	(void) strlcpy(item->spi_name, sharename, sizeof (item->spi_name));
	(void) strlcpy(item->spi_container, container,
	    sizeof (item->spi_container));

	(void) mutex_lock(&ad_queue.spq_mtx);
	list_insert_tail(&ad_queue.spq_list, item);
	(void) cond_signal(&ad_queue.spq_cv);
	(void) mutex_unlock(&ad_queue.spq_mtx);
}

/*
 * Publishing won't be activated if the smb service is running in
 * Workgroup mode.
 */
static int
smb_shr_publisher_start(void)
{
	pthread_t publish_thr;
	pthread_attr_t tattr;
	int rc;

	if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN)
		return (0);

	(void) mutex_lock(&ad_queue.spq_mtx);
	if (ad_queue.spq_state != SMB_SHR_PQS_NOQUEUE) {
		(void) mutex_unlock(&ad_queue.spq_mtx);
		errno = EINVAL;
		return (-1);
	}

	list_create(&ad_queue.spq_list, sizeof (smb_shr_pitem_t),
	    offsetof(smb_shr_pitem_t, spi_lnd));
	ad_queue.spq_state = SMB_SHR_PQS_READY;
	(void) mutex_unlock(&ad_queue.spq_mtx);

	(void) pthread_attr_init(&tattr);
	(void) pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_DETACHED);
	rc = pthread_create(&publish_thr, &tattr, smb_shr_publisher, 0);
	(void) pthread_attr_destroy(&tattr);

	return (rc);
}

static void
smb_shr_publisher_stop(void)
{
	if (smb_config_get_secmode() != SMB_SECMODE_DOMAIN)
		return;

	(void) mutex_lock(&ad_queue.spq_mtx);
	switch (ad_queue.spq_state) {
	case SMB_SHR_PQS_READY:
	case SMB_SHR_PQS_PUBLISHING:
		ad_queue.spq_state = SMB_SHR_PQS_STOPPING;
		(void) cond_signal(&ad_queue.spq_cv);
		break;
	default:
		break;
	}
	(void) mutex_unlock(&ad_queue.spq_mtx);
}

/*
 * This is the publisher daemon thread.  While running, the thread waits
 * on a conditional variable until notified that a share needs to be
 * [un]published or that the thread should be terminated.
 *
 * Entries may remain in the outgoing queue if the Active Directory
 * service is inaccessible, in which case the thread wakes up every 60
 * seconds to retry.
 */
/*ARGSUSED*/
static void *
smb_shr_publisher(void *arg)
{
	smb_ads_handle_t *ah;
	smb_shr_pitem_t *shr;
	list_t publist;
	timestruc_t pubretry;
	char hostname[MAXHOSTNAMELEN];

	(void) mutex_lock(&ad_queue.spq_mtx);
	if (ad_queue.spq_state != SMB_SHR_PQS_READY) {
		(void) mutex_unlock(&ad_queue.spq_mtx);
		return (NULL);
	}
	ad_queue.spq_state = SMB_SHR_PQS_PUBLISHING;
	(void) mutex_unlock(&ad_queue.spq_mtx);

	(void) smb_gethostname(hostname, MAXHOSTNAMELEN, 0);

	list_create(&publist, sizeof (smb_shr_pitem_t),
	    offsetof(smb_shr_pitem_t, spi_lnd));

	for (;;) {
		(void) mutex_lock(&ad_queue.spq_mtx);

		while (list_is_empty(&ad_queue.spq_list) &&
		    (ad_queue.spq_state == SMB_SHR_PQS_PUBLISHING)) {
			if (list_is_empty(&publist)) {
				(void) cond_wait(&ad_queue.spq_cv,
				    &ad_queue.spq_mtx);
			} else {
				pubretry.tv_sec = 60;
				pubretry.tv_nsec = 0;
				(void) cond_reltimedwait(&ad_queue.spq_cv,
				    &ad_queue.spq_mtx, &pubretry);
				break;
			}
		}

		if (ad_queue.spq_state != SMB_SHR_PQS_PUBLISHING) {
			(void) mutex_unlock(&ad_queue.spq_mtx);
			break;
		}

		/*
		 * Transfer queued items to the local list so that
		 * the mutex can be released.
		 */
		while ((shr = list_head(&ad_queue.spq_list)) != NULL) {
			list_remove(&ad_queue.spq_list, shr);
			list_insert_tail(&publist, shr);
		}

		(void) mutex_unlock(&ad_queue.spq_mtx);

		if ((ah = smb_ads_open()) != NULL) {
			smb_shr_publisher_send(ah, &publist, hostname);
			smb_ads_close(ah);
		}
	}

	(void) mutex_lock(&ad_queue.spq_mtx);
	smb_shr_publisher_flush(&ad_queue.spq_list);
	list_destroy(&ad_queue.spq_list);
	ad_queue.spq_state = SMB_SHR_PQS_NOQUEUE;
	(void) mutex_unlock(&ad_queue.spq_mtx);

	smb_shr_publisher_flush(&publist);
	list_destroy(&publist);
	return (NULL);
}

/*
 * Remove items from the specified queue and [un]publish them.
 */
static void
smb_shr_publisher_send(smb_ads_handle_t *ah, list_t *publist, const char *host)
{
	smb_shr_pitem_t *shr;

	while ((shr = list_head(publist)) != NULL) {
		(void) mutex_lock(&ad_queue.spq_mtx);
		if (ad_queue.spq_state != SMB_SHR_PQS_PUBLISHING) {
			(void) mutex_unlock(&ad_queue.spq_mtx);
			return;
		}
		(void) mutex_unlock(&ad_queue.spq_mtx);

		list_remove(publist, shr);

		if (shr->spi_op == SMB_SHR_PUBLISH)
			(void) smb_ads_publish_share(ah, shr->spi_name,
			    NULL, shr->spi_container, host);
		else
			(void) smb_ads_remove_share(ah, shr->spi_name,
			    NULL, shr->spi_container, host);

		free(shr);
	}
}

/*
 * Flush all remaining items from the specified list/queue.
 */
static void
smb_shr_publisher_flush(list_t *lst)
{
	smb_shr_pitem_t *shr;

	while ((shr = list_head(lst)) != NULL) {
		list_remove(lst, shr);
		free(shr);
	}
}