/*
 * 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 (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
 */

#include <ctype.h>
#include <stdio.h>
#include <stdarg.h>
#include <unistd.h>
#include <sys/fcntl.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/varargs.h>
#include <sys/types.h>
#include <sys/mnttab.h>
#include <tiuser.h>
#include <netconfig.h>
#include <netdir.h>
#include <sys/systeminfo.h>
#include <sys/utsname.h>
#include <libzfs.h>
#include <dlfcn.h>
#include <time.h>
#include <syslog.h>
#include <smbsrv/string.h>
#include <smbsrv/libsmb.h>

#define	SMB_LIB_ALT	"/usr/lib/smbsrv/libsmbex.so"

#define	SMB_TIMEBUF_SZ		16
#define	SMB_TRACEBUF_SZ		200

#define	SMB_LOG_FILE_FMT	"/var/smb/%s_log.txt"

typedef struct smb_log_pri {
	char	*lp_name;
	int	lp_value;
} smb_log_pri_t;

static smb_log_pri_t smb_log_pri[] = {
	"panic",	LOG_EMERG,
	"emerg",	LOG_EMERG,
	"alert",	LOG_ALERT,
	"crit",		LOG_CRIT,
	"error",	LOG_ERR,
	"err",		LOG_ERR,
	"warn",		LOG_WARNING,
	"warning",	LOG_WARNING,
	"notice",	LOG_NOTICE,
	"info",		LOG_INFO,
	"debug",	LOG_DEBUG
};

static void smb_log_trace(int, const char *);
static smb_log_t *smb_log_get(smb_log_hdl_t);
static void smb_log_dump(smb_log_t *);

static uint_t smb_make_mask(char *, uint_t);
static boolean_t smb_netmatch(struct netbuf *, char *);
static boolean_t smb_netgroup_match(struct nd_hostservlist *, char *, int);

extern  int __multi_innetgr();
extern int __netdir_getbyaddr_nosrv(struct netconfig *,
    struct nd_hostservlist **, struct netbuf *);

static smb_loglist_t smb_loglist;

#define	C2H(c)		"0123456789ABCDEF"[(c)]
#define	H2C(c)    (((c) >= '0' && (c) <= '9') ? ((c) - '0') :     \
	((c) >= 'a' && (c) <= 'f') ? ((c) - 'a' + 10) :         \
	((c) >= 'A' && (c) <= 'F') ? ((c) - 'A' + 10) :         \
	'\0')
#define	DEFAULT_SBOX_SIZE		256

/*
 *
 * hexdump
 *
 * Simple hex dump display function. Displays nbytes of buffer in hex and
 * printable format. Non-printing characters are shown as '.'. It is safe
 * to pass a null pointer. Each line begins with the offset. If nbytes is
 * 0, the line will be blank except for the offset. Example output:
 *
 * 00000000  54 68 69 73 20 69 73 20 61 20 70 72 6F 67 72 61  This is a progra
 * 00000010  6D 20 74 65 73 74 2E 00                          m test..
 *
 */
void
hexdump_offset(unsigned char *buffer, int nbytes, unsigned long *start)
{
	static char *hex = "0123456789ABCDEF";
	int i, count;
	int offset;
	unsigned char *p;
	char ascbuf[64];
	char hexbuf[64];
	char *ap = ascbuf;
	char *hp = hexbuf;

	if ((p = buffer) == NULL)
		return;

	offset = *start;

	*ap = '\0';
	*hp = '\0';
	count = 0;

	for (i = 0; i < nbytes; ++i) {
		if (i && (i % 16) == 0) {
			smb_tracef("%06X %s  %s", offset, hexbuf, ascbuf);
			ap = ascbuf;
			hp = hexbuf;
			count = 0;
			offset += 16;
		}

		ap += sprintf(ap, "%c",
		    (*p >= 0x20 && *p < 0x7F) ? *p : '.');
		hp += sprintf(hp, " %c%c",
		    hex[(*p >> 4) & 0x0F], hex[(*p & 0x0F)]);
		++p;
		++count;
	}

	if (count) {
		smb_tracef("%06X %-48s  %s", offset, hexbuf, ascbuf);
		offset += count;
	}

	*start = offset;
}

void
hexdump(unsigned char *buffer, int nbytes)
{
	unsigned long start = 0;

	hexdump_offset(buffer, nbytes, &start);
}

/*
 * bintohex
 *
 * Converts the given binary data (srcbuf) to
 * its equivalent hex chars (hexbuf).
 *
 * hexlen should be at least twice as srclen.
 * if hexbuf is not big enough returns 0.
 * otherwise returns number of valid chars in
 * hexbuf which is srclen * 2.
 */
size_t
bintohex(const char *srcbuf, size_t srclen,
    char *hexbuf, size_t hexlen)
{
	size_t outlen;
	char c;

	outlen = srclen << 1;

	if (hexlen < outlen)
		return (0);

	while (srclen-- > 0) {
		c = *srcbuf++;
		*hexbuf++ = C2H(c & 0xF);
		*hexbuf++ = C2H((c >> 4) & 0xF);
	}

	return (outlen);
}

/*
 * hextobin
 *
 * Converts hex to binary.
 *
 * Assuming hexbuf only contains hex digits (chars)
 * this function convert every two bytes of hexbuf
 * to one byte and put it in dstbuf.
 *
 * hexlen should be an even number.
 * dstlen should be at least half of hexlen.
 *
 * Returns 0 if sizes are not correct, otherwise
 * returns the number of converted bytes in dstbuf
 * which is half of hexlen.
 */
size_t
hextobin(const char *hexbuf, size_t hexlen,
    char *dstbuf, size_t dstlen)
{
	size_t outlen;

	if ((hexlen % 2) != 0)
		return (0);

	outlen = hexlen >> 1;
	if (dstlen < outlen)
		return (0);

	while (hexlen > 0) {
		*dstbuf = H2C(*hexbuf) & 0x0F;
		hexbuf++;
		*dstbuf++ |= (H2C(*hexbuf) << 4) & 0xF0;
		hexbuf++;

		hexlen -= 2;
	}

	return (outlen);
}

/*
 * Trim leading and trailing characters in the set defined by class
 * from a buffer containing a null-terminated string.
 * For example, if the input buffer contained "ABtext23" and class
 * contains "ABC123", the buffer will contain "text" on return.
 *
 * This function modifies the contents of buf in place and returns
 * a pointer to buf.
 */
char *
strtrim(char *buf, const char *class)
{
	char *p = buf;
	char *q = buf;

	if (buf == NULL)
		return (NULL);

	p += strspn(p, class);

	if (p != buf) {
		while ((*q = *p++) != '\0')
			++q;
	}

	while (q != buf) {
		--q;
		if (strspn(q, class) == 0)
			return (buf);
		*q = '\0';
	}

	return (buf);
}

/*
 * Strip the characters in the set defined by class from a buffer
 * containing a null-terminated string.
 * For example, if the input buffer contained "XYA 1textZ string3"
 * and class contains "123XYZ", the buffer will contain "A text string"
 * on return.
 *
 * This function modifies the contents of buf in place and returns
 * a pointer to buf.
 */
char *
strstrip(char *buf, const char *class)
{
	char *p = buf;
	char *q = buf;

	if (buf == NULL)
		return (NULL);

	while (*p) {
		p += strspn(p, class);
		*q++ = *p++;
	}

	*q = '\0';
	return (buf);
}

/*
 * trim_whitespace
 *
 * Trim leading and trailing whitespace chars (as defined by isspace)
 * from a buffer. Example; if the input buffer contained "  text  ",
 * it will contain "text", when we return. We assume that the buffer
 * contains a null terminated string. A pointer to the buffer is
 * returned.
 */
char *
trim_whitespace(char *buf)
{
	char *p = buf;
	char *q = buf;

	if (buf == NULL)
		return (NULL);

	while (*p && isspace(*p))
		++p;

	while ((*q = *p++) != 0)
		++q;

	if (q != buf) {
		while ((--q, isspace(*q)) != 0)
			*q = '\0';
	}

	return (buf);
}

/*
 * randomize
 *
 * Randomize the contents of the specified buffer.
 */
void
randomize(char *data, unsigned len)
{
	unsigned dwlen = len / 4;
	unsigned remlen = len % 4;
	unsigned tmp;
	unsigned i; /*LINTED E_BAD_PTR_CAST_ALIGN*/
	unsigned *p = (unsigned *)data;

	for (i = 0; i < dwlen; ++i)
		*p++ = random();

	if (remlen) {
		tmp = random();
		(void) memcpy(p, &tmp, remlen);
	}
}

/*
 * This is the hash mechanism used to encrypt passwords for commands like
 * SamrSetUserInformation. It uses a 256 byte s-box.
 */
void
rand_hash(
    unsigned char *data,
    size_t datalen,
    unsigned char *key,
    size_t keylen)
{
	unsigned char sbox[DEFAULT_SBOX_SIZE];
	unsigned char tmp;
	unsigned char index_i = 0;
	unsigned char index_j = 0;
	unsigned char j = 0;
	int i;

	for (i = 0; i < DEFAULT_SBOX_SIZE; ++i)
		sbox[i] = (unsigned char)i;

	for (i = 0; i < DEFAULT_SBOX_SIZE; ++i) {
		j += (sbox[i] + key[i % keylen]);

		tmp = sbox[i];
		sbox[i] = sbox[j];
		sbox[j] = tmp;
	}

	for (i = 0; i < datalen; ++i) {
		index_i++;
		index_j += sbox[index_i];

		tmp = sbox[index_i];
		sbox[index_i] = sbox[index_j];
		sbox[index_j] = tmp;

		tmp = sbox[index_i] + sbox[index_j];
		data[i] = data[i] ^ sbox[tmp];
	}
}

/*
 * smb_chk_hostaccess
 *
 * Determines whether the specified host is in the given access list.
 *
 * We match on aliases of the hostname as well as on the canonical name.
 * Names in the access list may be either hosts or netgroups;  they're
 * not distinguished syntactically.  We check for hosts first because
 * it's cheaper (just M*N strcmp()s), then try netgroups.
 *
 * Function returns:
 *	-1 for "all" (list is empty "" or "*")
 *	0 not found  (host is not in the list or list is NULL)
 *	1 found
 *
 */
int
smb_chk_hostaccess(smb_inaddr_t *ipaddr, char *access_list)
{
	int nentries;
	char *gr;
	char *lasts;
	char *host;
	int off;
	int i;
	int netgroup_match;
	int response;
	struct nd_hostservlist *clnames;
	struct in_addr inaddr;
	struct sockaddr_in sa;
	struct netbuf buf;
	struct netconfig *config;

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

	inaddr.s_addr = ipaddr->a_ipv4;

	/*
	 * If access list is empty or "*" - then it's "all"
	 */
	if (*access_list == '\0' || strcmp(access_list, "*") == 0)
		return (-1);

	nentries = 0;

	sa.sin_family = AF_INET;
	sa.sin_port = 0;
	sa.sin_addr = inaddr;

	buf.len = buf.maxlen = sizeof (sa);
	buf.buf = (char *)&sa;

	config = getnetconfigent("tcp");
	if (config == NULL)
		return (1);

	if (__netdir_getbyaddr_nosrv(config, &clnames, &buf)) {
		freenetconfigent(config);
		return (0);
	}
	freenetconfigent(config);

	for (gr = strtok_r(access_list, ":", &lasts);
	    gr != NULL; gr = strtok_r(NULL, ":", &lasts)) {

		/*
		 * If the list name has a '-' prepended
		 * then a match of the following name
		 * implies failure instead of success.
		 */
		if (*gr == '-') {
			response = 0;
			gr++;
		} else {
			response = 1;
		}

		/*
		 * The following loops through all the
		 * client's aliases.  Usually it's just one name.
		 */
		for (i = 0; i < clnames->h_cnt; i++) {
			host = clnames->h_hostservs[i].h_host;
			/*
			 * If the list name begins with a dot then
			 * do a domain name suffix comparison.
			 * A single dot matches any name with no
			 * suffix.
			 */
			if (*gr == '.') {
				if (*(gr + 1) == '\0') {  /* single dot */
					if (strchr(host, '.') == NULL)
						return (response);
				} else {
					off = strlen(host) - strlen(gr);
					if (off > 0 &&
					    strcasecmp(host + off, gr) == 0) {
						return (response);
					}
				}
			} else {

				/*
				 * If the list name begins with an at
				 * sign then do a network comparison.
				 */
				if (*gr == '@') {
					if (smb_netmatch(&buf, gr + 1))
						return (response);
				} else {
					/*
					 * Just do a hostname match
					 */
					if (strcasecmp(gr, host) == 0)
						return (response);
				}
			}
		}

		nentries++;
	}

	netgroup_match = smb_netgroup_match(clnames, access_list, nentries);

	return (netgroup_match);
}

/*
 * smb_make_mask
 *
 * Construct a mask for an IPv4 address using the @<dotted-ip>/<len>
 * syntax or use the default mask for the IP address.
 */
static uint_t
smb_make_mask(char *maskstr, uint_t addr)
{
	uint_t mask;
	uint_t bits;

	/*
	 * If the mask is specified explicitly then
	 * use that value, e.g.
	 *
	 *    @109.104.56/28
	 *
	 * otherwise assume a mask from the zero octets
	 * in the least significant bits of the address, e.g.
	 *
	 *   @109.104  or  @109.104.0.0
	 */
	if (maskstr) {
		bits = atoi(maskstr);
		mask = bits ? ~0 << ((sizeof (struct in_addr) * NBBY) - bits)
		    : 0;
		addr &= mask;
	} else {
		if ((addr & IN_CLASSA_HOST) == 0)
			mask = IN_CLASSA_NET;
		else if ((addr & IN_CLASSB_HOST) == 0)
			mask = IN_CLASSB_NET;
		else if ((addr & IN_CLASSC_HOST) == 0)
			mask = IN_CLASSC_NET;
		else
			mask = IN_CLASSE_NET;
	}

	return (mask);
}

/*
 * smb_netmatch
 *
 * Check to see if the address in the netbuf matches the "net"
 * specified by name.  The format of "name" can be:
 *	fully qualified domain name
 *	dotted IP address
 *	dotted IP address followed by '/<len>'
 *	See sharen_nfs(1M) for details.
 */

static boolean_t
smb_netmatch(struct netbuf *nb, char *name)
{
	uint_t claddr;
	struct netent n, *np;
	char *mp, *p;
	uint_t addr, mask;
	int i;
	char buff[256];

	/*
	 * Check if it's an IPv4 addr
	 */
	if (nb->len != sizeof (struct sockaddr_in))
		return (B_FALSE);

	(void) memcpy(&claddr,
	    /* LINTED pointer alignment */
	    &((struct sockaddr_in *)nb->buf)->sin_addr.s_addr,
	    sizeof (struct in_addr));
	claddr = ntohl(claddr);

	mp = strchr(name, '/');
	if (mp)
		*mp++ = '\0';

	if (isdigit(*name)) {
		/*
		 * Convert a dotted IP address
		 * to an IP address. The conversion
		 * is not the same as that in inet_addr().
		 */
		p = name;
		addr = 0;
		for (i = 0; i < 4; i++) {
			addr |= atoi(p) << ((3-i) * 8);
			p = strchr(p, '.');
			if (p == NULL)
				break;
			p++;
		}
	} else {
		/*
		 * Turn the netname into
		 * an IP address.
		 */
		np = getnetbyname_r(name, &n, buff, sizeof (buff));
		if (np == NULL) {
			return (B_FALSE);
		}
		addr = np->n_net;
	}

	mask = smb_make_mask(mp, addr);
	return ((claddr & mask) == addr);
}

/*
 * smb_netgroup_match
 *
 * Check whether any of the hostnames in clnames are
 * members (or non-members) of the netgroups in glist.
 * Since the innetgr lookup is rather expensive, the
 * result is cached. The cached entry is valid only
 * for VALID_TIME seconds.  This works well because
 * typically these lookups occur in clusters when
 * a client is mounting.
 *
 * Note that this routine establishes a host membership
 * in a list of netgroups - we've no idea just which
 * netgroup in the list it is a member of.
 *
 * glist is a character array containing grc strings
 * representing netgroup names (optionally prefixed
 * with '-'). Each string is ended with '\0'  and
 * followed immediately by the next string.
 */
static boolean_t
smb_netgroup_match(struct nd_hostservlist *clnames, char  *glist, int grc)
{
	char **grl;
	char *gr;
	int nhosts = clnames->h_cnt;
	char *host;
	int i, j, n;
	boolean_t response;
	boolean_t belong = B_FALSE;
	static char *domain = NULL;

	if (domain == NULL) {
		int	ssize;

		domain = malloc(SYS_NMLN);
		if (domain == NULL)
			return (B_FALSE);

		ssize = sysinfo(SI_SRPC_DOMAIN, domain, SYS_NMLN);
		if (ssize > SYS_NMLN) {
			free(domain);
			domain = malloc(ssize);
			if (domain == NULL)
				return (B_FALSE);
			ssize = sysinfo(SI_SRPC_DOMAIN, domain, ssize);
		}
		/* Check for error in syscall or NULL domain name */
		if (ssize <= 1)
			return (B_FALSE);
	}

	grl = calloc(grc, sizeof (char *));
	if (grl == NULL)
		return (B_FALSE);

	for (i = 0, gr = glist; i < grc && !belong; ) {
		/*
		 * If the netgroup name has a '-' prepended
		 * then a match of this name implies a failure
		 * instead of success.
		 */
		response = (*gr != '-') ? B_TRUE : B_FALSE;

		/*
		 * Subsequent names with or without a '-' (but no mix)
		 * can be grouped together for a single check.
		 */
		for (n = 0; i < grc; i++, n++, gr += strlen(gr) + 1) {
			if ((response && *gr == '-') ||
			    (!response && *gr != '-'))
				break;

			grl[n] = response ? gr : gr + 1;
		}

		/*
		 * Check the netgroup for each
		 * of the hosts names (usually just one).
		 */
		for (j = 0; j < nhosts && !belong; j++) {
			host = clnames->h_hostservs[j].h_host;
			if (__multi_innetgr(n, grl, 1, &host, 0, NULL,
			    1, &domain))
				belong = B_TRUE;
		}
	}

	free(grl);
	return (belong ? response : B_FALSE);
}

/*
 * Resolve the ZFS dataset from a path.
 * Returns,
 *	0  = On success.
 *	-1 = Failure to open /etc/mnttab file or to get ZFS dataset.
 */
int
smb_getdataset(const char *path, char *dataset, size_t len)
{
	char tmppath[MAXPATHLEN];
	char *cp;
	FILE *fp;
	struct mnttab mnttab;
	struct mnttab mntpref;
	int rc = -1;

	if ((fp = fopen(MNTTAB, "r")) == NULL)
		return (-1);

	(void) memset(&mnttab, '\0', sizeof (mnttab));
	(void) strlcpy(tmppath, path, MAXPATHLEN);
	cp = tmppath;

	while (*cp != '\0') {
		resetmnttab(fp);
		(void) memset(&mntpref, '\0', sizeof (mntpref));
		mntpref.mnt_mountp = tmppath;

		if (getmntany(fp, &mnttab, &mntpref) == 0) {
			if (mnttab.mnt_fstype == NULL)
				break;

			if (strcmp(mnttab.mnt_fstype, "zfs") != 0)
				break;
			/*
			 * Ensure that there are no leading slashes
			 * (required for zfs_open).
			 */
			cp = mnttab.mnt_special;
			cp += strspn(cp, "/");
			(void) strlcpy(dataset, cp, len);
			rc = 0;
			break;
		}

		if (strcmp(tmppath, "/") == 0)
			break;

		if ((cp = strrchr(tmppath, '/')) == NULL)
			break;

		/*
		 * The path has multiple components.
		 * Remove the last component and try again.
		 */
		*cp = '\0';
		if (tmppath[0] == '\0')
			(void) strcpy(tmppath, "/");

		cp = tmppath;
	}

	(void) fclose(fp);
	return (rc);
}

/*
 * smb_dlopen
 *
 * Check to see if an interposer library exists.  If it exists
 * and reports a valid version number and key (UUID), return
 * a handle to the library.  Otherwise, return NULL.
 */
void *
smb_dlopen(void)
{
	uuid_t uuid;
	void *interposer_hdl;
	typedef int (*smbex_versionfn_t)(smbex_version_t *);
	smbex_versionfn_t getversion;
	smbex_version_t *version;

	bzero(&uuid, sizeof (uuid_t));
	if (uuid_parse(SMBEX_KEY, uuid) < 0)
		return (NULL);

	interposer_hdl = dlopen(SMB_LIB_ALT, RTLD_NOW | RTLD_LOCAL);
	if (interposer_hdl == NULL)
		return (NULL);

	bzero(&getversion, sizeof (smbex_versionfn_t));
	getversion = (smbex_versionfn_t)dlsym(interposer_hdl,
	    "smbex_get_version");
	if ((getversion == NULL) ||
	    (version = malloc(sizeof (smbex_version_t))) == NULL) {
		(void) dlclose(interposer_hdl);
		return (NULL);
	}
	bzero(version, sizeof (smbex_version_t));

	if ((getversion(version) != 0) ||
	    (version->v_version != SMBEX_VERSION) ||
	    (uuid_compare(version->v_uuid, uuid) != 0)) {
		free(version);
		(void) dlclose(interposer_hdl);
		return (NULL);
	}

	free(version);
	return (interposer_hdl);
}

/*
 * smb_dlclose
 *
 * Closes handle to the interposed library.
 */
void
smb_dlclose(void *handle)
{
	if (handle)
		(void) dlclose(handle);
}

/*
 * This function is a wrapper for getnameinfo() to look up a hostname given an
 * IP address. The hostname returned by this function is used for constructing
 * the service principal name field of KRB AP-REQs. Hence, it should be
 * converted to lowercase for RFC 4120 section 6.2.1 conformance.
 */
int
smb_getnameinfo(smb_inaddr_t *ip, char *hostname, int hostlen, int flags)
{
	socklen_t salen;
	struct sockaddr_in6 sin6;
	struct sockaddr_in sin;
	void *sp;
	int rc;

	if (ip->a_family == AF_INET) {
		salen = sizeof (struct sockaddr_in);
		sin.sin_family = ip->a_family;
		sin.sin_port = 0;
		sin.sin_addr.s_addr = ip->a_ipv4;
		sp = &sin;
	} else {
		salen = sizeof (struct sockaddr_in6);
		sin6.sin6_family = ip->a_family;
		sin6.sin6_port = 0;
		(void) memcpy(&sin6.sin6_addr.s6_addr, &ip->a_ipv6,
		    sizeof (sin6.sin6_addr.s6_addr));
		sp = &sin6;
	}

	if ((rc = (getnameinfo((struct sockaddr *)sp, salen,
	    hostname, hostlen, NULL, 0, flags))) == 0)
		(void) smb_strlwr(hostname);

	return (rc);
}

/*
 * A share name is considered invalid if it contains control
 * characters or any of the following characters (MSDN 236388).
 *
 *	" / \ [ ] : | < > + ; , ? * =
 */
uint32_t
smb_name_validate_share(const char *sharename)
{
	const char *invalid = "\"/\\[]:|<>+;,?*=";
	const char *p;

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

	if (strpbrk(sharename, invalid) != NULL)
		return (ERROR_INVALID_NAME);

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

	return (ERROR_SUCCESS);
}

/*
 * User and group names are limited to 256 characters, cannot be terminated
 * by '.' and must not contain control characters or any of the following
 * characters.
 *
 *	" / \ [ ] < > + ; , ? * = @
 */
uint32_t
smb_name_validate_account(const char *name)
{
	const char	*invalid = "\"/\\[]<>+;,?*=@";
	const char	*p;
	int		len;

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

	len = strlen(name);
	if ((len > MAXNAMELEN) || (name[len - 1] == '.'))
		return (ERROR_INVALID_NAME);

	if (strpbrk(name, invalid) != NULL)
		return (ERROR_INVALID_NAME);

	for (p = name; *p != '\0'; p++) {
		if (iscntrl(*p))
			return (ERROR_INVALID_NAME);
	}

	return (ERROR_SUCCESS);
}

/*
 * Check a domain name for RFC 1035 and 1123 compliance.  Domain names may
 * contain alphanumeric characters, hyphens and dots.  The first and last
 * character of a label must be alphanumeric.  Interior characters may be
 * alphanumeric or hypens.
 *
 * Domain names should not contain underscores but we allow them because
 * Windows names are often in non-compliance with this rule.
 */
uint32_t
smb_name_validate_domain(const char *domain)
{
	boolean_t new_label = B_TRUE;
	const char *p;
	char label_terminator;

	if (domain == NULL)
		return (ERROR_INVALID_PARAMETER);

	if (*domain == '\0')
		return (ERROR_INVALID_NAME);

	label_terminator = *domain;

	for (p = domain; *p != '\0'; ++p) {
		if (new_label) {
			if (!isalnum(*p))
				return (ERROR_INVALID_NAME);
			new_label = B_FALSE;
			label_terminator = *p;
			continue;
		}

		if (*p == '.') {
			if (!isalnum(label_terminator))
				return (ERROR_INVALID_NAME);
			new_label = B_TRUE;
			label_terminator = *p;
			continue;
		}

		label_terminator = *p;

		if (isalnum(*p) || *p == '-' || *p == '_')
			continue;

		return (ERROR_INVALID_NAME);
	}

	if (!isalnum(label_terminator))
		return (ERROR_INVALID_NAME);

	return (ERROR_SUCCESS);
}

/*
 * A NetBIOS domain name can contain letters (a-zA-Z), numbers (0-9) and
 * hyphens.
 *
 * It cannot:
 * 	- be blank or longer than 15 chracters
 * 	- contain all numbers
 * 	- be the same as the computer name
 */
uint32_t
smb_name_validate_nbdomain(const char *name)
{
	char		netbiosname[NETBIOS_NAME_SZ];
	const char	*p;
	int		len;

	if (name == NULL)
		return (ERROR_INVALID_PARAMETER);

	len = strlen(name);
	if (len == 0 || len >= NETBIOS_NAME_SZ)
		return (ERROR_INVALID_NAME);

	if (strspn(name, "0123456789") == len)
		return (ERROR_INVALID_NAME);

	if (smb_getnetbiosname(netbiosname, NETBIOS_NAME_SZ) == 0) {
		if (smb_strcasecmp(name, netbiosname, 0) == 0)
			return (ERROR_INVALID_NAME);
	}

	for (p = name; *p != '\0'; ++p) {
		if (isalnum(*p) || *p == '-' || *p == '_')
			continue;

		return (ERROR_INVALID_NAME);
	}

	return (ERROR_SUCCESS);
}

/*
 * A workgroup name can contain 1 to 15 characters but cannot be the same
 * as the NetBIOS name.  The name must begin with a letter or number.
 *
 * The name cannot consist entirely of spaces or dots, which is covered
 * by the requirement that the name must begin with an alphanumeric
 * character.
 *
 * The name must not contain control characters or any of the following
 * characters.
 *
 *	" / \ [ ] : | < > + = ; , ?
 */
uint32_t
smb_name_validate_workgroup(const char *workgroup)
{
	char netbiosname[NETBIOS_NAME_SZ];
	const char *invalid = "\"/\\[]:|<>+=;,?";
	const char *p;

	if (workgroup == NULL)
		return (ERROR_INVALID_PARAMETER);

	if (*workgroup == '\0' || (!isalnum(*workgroup)))
		return (ERROR_INVALID_NAME);

	if (strlen(workgroup) >= NETBIOS_NAME_SZ)
		return (ERROR_INVALID_NAME);

	if (smb_getnetbiosname(netbiosname, NETBIOS_NAME_SZ) == 0) {
		if (smb_strcasecmp(workgroup, netbiosname, 0) == 0)
			return (ERROR_INVALID_NAME);
	}

	if (strpbrk(workgroup, invalid) != NULL)
		return (ERROR_INVALID_NAME);

	for (p = workgroup; *p != '\0'; p++) {
		if (iscntrl(*p))
			return (ERROR_INVALID_NAME);
	}

	return (ERROR_SUCCESS);
}

/*
 * Check for invalid characters in the given path.  The list of invalid
 * characters includes control characters and the following:
 *
 * " / \ [ ] : | < > + ; , ? * =
 *
 * Since this is checking a path not each component, '/' is accepted
 * as separator not an invalid character, except as the first character
 * since this is supposed to be a relative path.
 */
uint32_t
smb_name_validate_rpath(const char *relpath)
{
	char *invalid = "\"\\[]:|<>+;,?*=";
	char *cp;

	if ((relpath == NULL) || (*relpath == '\0') || (*relpath == '/'))
		return (ERROR_INVALID_NAME);

	if (strpbrk(relpath, invalid))
		return (ERROR_INVALID_NAME);

	for (cp = (char *)relpath; *cp != '\0'; cp++) {
		if (iscntrl(*cp))
			return (ERROR_INVALID_NAME);
	}

	return (ERROR_SUCCESS);
}

/*
 * Parse a string to obtain the account and domain names as separate strings.
 *
 * Names containing a backslash ('\') are known as qualified or composite
 * names.  The string preceding the backslash should be the domain name
 * and the string following the slash should be a name within that domain.
 *
 * Names that do not contain a backslash are known as isolated names.
 * An isolated name may be a single label, such as john, or may be in
 * user principal name (UPN) form, such as john@example.com.
 *
 *	domain\name
 *	domain/name
 *	name
 *	name@domain
 *
 * If we encounter any of the forms above in arg, the @, / or \ separator
 * is replaced by \0 and the name and domain pointers are set to point to
 * the appropriate components in arg.  Otherwise, name and domain pointers
 * will be set to NULL.
 */
void
smb_name_parse(char *arg, char **account, char **domain)
{
	char *p;

	*account = NULL;
	*domain = NULL;

	if ((p = strpbrk(arg, "/\\@")) != NULL) {
		if (*p == '@') {
			*p = '\0';
			++p;
			*domain = p;
			*account = arg;
		} else {
			*p = '\0';
			++p;
			*account = p;
			*domain = arg;
		}
	}
}

/*
 * The txid is an arbitrary transaction.  A new txid is returned on each call.
 *
 * 0 or -1 are not assigned so that they can be used to detect
 * invalid conditions.
 */
uint32_t
smb_get_txid(void)
{
	static mutex_t	txmutex;
	static uint32_t	txid;
	uint32_t	txid_ret;

	(void) mutex_lock(&txmutex);

	if (txid == 0)
		txid = time(NULL);

	do {
		++txid;
	} while (txid == 0 || txid == (uint32_t)-1);

	txid_ret = txid;
	(void) mutex_unlock(&txmutex);

	return (txid_ret);
}

/*
 *  Creates a log object and inserts it into a list of logs.
 */
smb_log_hdl_t
smb_log_create(int max_cnt, char *name)
{
	smb_loglist_item_t *log_node;
	smb_log_t *log = NULL;
	smb_log_hdl_t handle = 0;

	if (max_cnt <= 0 || name == NULL)
		return (0);

	(void) mutex_lock(&smb_loglist.ll_mtx);

	log_node = malloc(sizeof (smb_loglist_item_t));

	if (log_node != NULL) {
		log = &log_node->lli_log;

		bzero(log, sizeof (smb_log_t));

		handle = log->l_handle = smb_get_txid();
		log->l_max_cnt = max_cnt;
		(void) snprintf(log->l_file, sizeof (log->l_file),
		    SMB_LOG_FILE_FMT, name);

		list_create(&log->l_list, sizeof (smb_log_item_t),
		    offsetof(smb_log_item_t, li_lnd));

		if (smb_loglist.ll_list.list_size == 0)
			list_create(&smb_loglist.ll_list,
			    sizeof (smb_loglist_item_t),
			    offsetof(smb_loglist_item_t, lli_lnd));

		list_insert_tail(&smb_loglist.ll_list, log_node);
	}

	(void) mutex_unlock(&smb_loglist.ll_mtx);

	return (handle);
}

/*
 * Keep the most recent log entries, based on max count.
 * If the priority is LOG_ERR or higher then the entire log is
 * dumped to a file.
 *
 * The date format for each message is the same as a syslog entry.
 *
 * The log is also added to syslog via smb_log_trace().
 */
void
smb_log(smb_log_hdl_t hdl, int priority, const char *fmt, ...)
{
	va_list		ap;
	smb_log_t	*log;
	smb_log_item_t	*msg;
	time_t		now;
	struct		tm *tm;
	char		timebuf[SMB_TIMEBUF_SZ];
	char		buf[SMB_TRACEBUF_SZ];
	char		netbiosname[NETBIOS_NAME_SZ];
	char		*pri_name;
	int		i;

	va_start(ap, fmt);
	(void) vsnprintf(buf, SMB_TRACEBUF_SZ, fmt, ap);
	va_end(ap);

	priority &= LOG_PRIMASK;
	smb_log_trace(priority, buf);

	if ((log = smb_log_get(hdl)) == NULL)
		return;

	(void) mutex_lock(&log->l_mtx);

	(void) time(&now);
	tm = localtime(&now);
	(void) strftime(timebuf, SMB_TIMEBUF_SZ, "%b %d %H:%M:%S", tm);

	if (smb_getnetbiosname(netbiosname, NETBIOS_NAME_SZ) != 0)
		(void) strlcpy(netbiosname, "unknown", NETBIOS_NAME_SZ);

	if (log->l_cnt == log->l_max_cnt) {
		msg = list_head(&log->l_list);
		list_remove(&log->l_list, msg);
	} else {
		if ((msg = malloc(sizeof (smb_log_item_t))) == NULL) {
			(void) mutex_unlock(&log->l_mtx);
			return;
		}
		log->l_cnt++;
	}

	pri_name = "info";
	for (i = 0; i < sizeof (smb_log_pri) / sizeof (smb_log_pri[0]); i++) {
		if (priority == smb_log_pri[i].lp_value) {
			pri_name = smb_log_pri[i].lp_name;
			break;
		}
	}

	(void) snprintf(msg->li_msg, SMB_LOG_LINE_SZ,
	    "%s %s smb[%d]: [ID 0 daemon.%s] %s",
	    timebuf, netbiosname, getpid(), pri_name, buf);
	list_insert_tail(&log->l_list, msg);

	if (priority <= LOG_ERR)
		smb_log_dump(log);

	(void) mutex_unlock(&log->l_mtx);
}

/*
 * Dumps all the logs in the log list.
 */
void
smb_log_dumpall()
{
	smb_loglist_item_t *log_node;

	(void) mutex_lock(&smb_loglist.ll_mtx);

	log_node = list_head(&smb_loglist.ll_list);

	while (log_node != NULL) {
		smb_log_dump(&log_node->lli_log);
		log_node = list_next(&smb_loglist.ll_list, log_node);
	}

	(void) mutex_unlock(&smb_loglist.ll_mtx);
}

static void
smb_log_trace(int priority, const char *s)
{
	syslog(priority, "%s", s);
}

static smb_log_t *
smb_log_get(smb_log_hdl_t hdl)
{
	smb_loglist_item_t *log_node;
	smb_log_t *log;

	(void) mutex_lock(&smb_loglist.ll_mtx);

	log_node = list_head(&smb_loglist.ll_list);

	while (log_node != NULL) {
		if (log_node->lli_log.l_handle == hdl) {
			log = &log_node->lli_log;
			(void) mutex_unlock(&smb_loglist.ll_mtx);
			return (log);
		}
		log_node = list_next(&smb_loglist.ll_list, log_node);
	}

	(void) mutex_unlock(&smb_loglist.ll_mtx);
	return (NULL);
}

/*
 * Dumps the log to a file.
 */
static void
smb_log_dump(smb_log_t *log)
{
	smb_log_item_t *msg;
	FILE *fp;

	if ((fp = fopen(log->l_file, "w")) == NULL)
		return;

	msg = list_head(&log->l_list);

	while (msg != NULL) {
		(void) fprintf(fp, "%s\n", msg->li_msg);
		msg = list_next(&log->l_list, msg);
	}

	(void) fclose(fp);
}