view usr/src/lib/smbsrv/libsmbns/common/smbns_netbios_cache.c @ 10717:fe0545fc3cdd

6612607 CIFS ADS client should use ldap_sasl_interactive_bind_s API 6877755 smbd should not route stderr, stdout to /dev/null 6882701 Wrong error message for attempt to map local user to Windows group, or vice versa 6885105 Potential for deadlock in smb_node_set_delete_on_close() 6881928 smbd core generated when running a script to join domain, set abe properties 6885538 Reduce dependencies on libsmbrdr 6820325 cifs service can't start on multi vlan+ipmp configuration
author Alan Wright <amw@Sun.COM>
date Mon, 05 Oct 2009 11:03:34 -0700
parents 6bebab7d43d5
children d5cb4988058c
line wrap: on
line source

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <synch.h>
#include <stdio.h>
#include <syslog.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <netdb.h>

#include <smbsrv/libsmbns.h>
#include <smbns_netbios.h>

#define	NETBIOS_HTAB_SZ	128
#define	NETBIOS_HKEY_SZ	(NETBIOS_NAME_SZ + NETBIOS_DOMAIN_NAME_MAX)

#define	NETBIOS_SAME_IP(addr1, addr2) \
	((addr1)->sin.sin_addr.s_addr == (addr2)->sin.sin_addr.s_addr)

typedef char nb_key_t[NETBIOS_HKEY_SZ];
static HT_HANDLE *smb_netbios_cache = 0;
static rwlock_t nb_cache_lock;

static void smb_strname(struct name_entry *name, char *buf, int bufsize);
static void hash_callback(HT_ITEM *item);
static int smb_netbios_match(const char *key1, const char *key2, size_t n);
static void smb_netbios_cache_key(char *key, unsigned char *name,
					unsigned char *scope);

int
smb_netbios_cache_init(void)
{
	(void) rw_wrlock(&nb_cache_lock);
	if (smb_netbios_cache == NULL) {
		smb_netbios_cache = ht_create_table(NETBIOS_HTAB_SZ,
		    NETBIOS_HKEY_SZ, HTHF_FIXED_KEY);
		if (smb_netbios_cache == NULL) {
			syslog(LOG_ERR, "nbns: cannot create name cache");
			(void) rw_unlock(&nb_cache_lock);
			return (-1);
		}
		(void) ht_register_callback(smb_netbios_cache, hash_callback);
		ht_set_cmpfn(smb_netbios_cache, smb_netbios_match);
	}
	(void) rw_unlock(&nb_cache_lock);

	return (0);
}

void
smb_netbios_cache_fini(void)
{
	(void) rw_wrlock(&nb_cache_lock);
	ht_destroy_table(smb_netbios_cache);
	smb_netbios_cache = NULL;
	(void) rw_unlock(&nb_cache_lock);
}

void
smb_netbios_cache_clean(void)
{
	(void) rw_wrlock(&nb_cache_lock);
	(void) ht_clean_table(smb_netbios_cache);
	(void) rw_unlock(&nb_cache_lock);
}

int
smb_netbios_cache_getfirst(nbcache_iter_t *iter)
{
	HT_ITEM *item;
	struct name_entry *entry;

	(void) rw_rdlock(&nb_cache_lock);
	item = ht_findfirst(smb_netbios_cache, &iter->nbc_hti);
	if (item == NULL || item->hi_data == NULL) {
		(void) rw_unlock(&nb_cache_lock);
		return (-1);
	}

	entry = (struct name_entry *)item->hi_data;
	(void) mutex_lock(&entry->mtx);
	iter->nbc_entry = smb_netbios_name_dup(entry, 1);
	(void) mutex_unlock(&entry->mtx);

	(void) rw_unlock(&nb_cache_lock);

	return ((iter->nbc_entry) ? 0 : -1);
}

int
smb_netbios_cache_getnext(nbcache_iter_t *iter)
{
	HT_ITEM *item;
	struct name_entry *entry;

	(void) rw_rdlock(&nb_cache_lock);
	item = ht_findnext(&iter->nbc_hti);
	if (item == NULL || item->hi_data == NULL) {
		(void) rw_unlock(&nb_cache_lock);
		return (-1);
	}

	entry = (struct name_entry *)item->hi_data;
	(void) mutex_lock(&entry->mtx);
	iter->nbc_entry = smb_netbios_name_dup(entry, 1);
	(void) mutex_unlock(&entry->mtx);

	(void) rw_unlock(&nb_cache_lock);

	return ((iter->nbc_entry) ? 0 : -1);
}

/*
 * smb_netbios_cache_lookup
 *
 * Searches the name cache for the given entry, if found
 * the entry will be locked before returning to caller
 * so caller MUST unlock the entry after it's done with it.
 */
struct name_entry *
smb_netbios_cache_lookup(struct name_entry *name)
{
	HT_ITEM *item;
	nb_key_t key;
	struct name_entry *entry = NULL;
	unsigned char hostname[MAXHOSTNAMELEN];

	if (NETBIOS_NAME_IS_STAR(name->name)) {
		/* Return our address */
		if (smb_getnetbiosname((char *)hostname, sizeof (hostname))
		    != 0)
			return (NULL);

		smb_encode_netbios_name(hostname, 0x00, NULL, name);
	}

	(void) rw_rdlock(&nb_cache_lock);

	smb_netbios_cache_key(key, name->name, name->scope);
	item = ht_find_item(smb_netbios_cache, key);
	if (item) {
		entry = (struct name_entry *)item->hi_data;
		(void) mutex_lock(&entry->mtx);
		if ((entry->attributes & NAME_ATTR_CONFLICT) != 0) {
			(void) mutex_unlock(&entry->mtx);
			entry = NULL;
		}
	}

	(void) rw_unlock(&nb_cache_lock);
	return (entry);
}

void
smb_netbios_cache_unlock_entry(struct name_entry *name)
{
	if (name)
		(void) mutex_unlock(&name->mtx);
}

/*
 * smb_netbios_cache_lookup_addr
 *
 * lookup the given 'name' in the cache and then checks
 * if the address also matches with the found entry.
 * 'name' is supposed to contain only one address.
 *
 * The found entry will be locked before returning to caller
 * so caller MUST unlock the entry after it's done with it.
 */
struct name_entry *
smb_netbios_cache_lookup_addr(struct name_entry *name)
{
	struct name_entry *entry = 0;
	addr_entry_t *addr;
	addr_entry_t *name_addr;
	HT_ITEM *item;
	nb_key_t key;

	(void) rw_rdlock(&nb_cache_lock);
	smb_netbios_cache_key(key, name->name, name->scope);
	item = ht_find_item(smb_netbios_cache, key);

	if (item && item->hi_data) {
		name_addr = &name->addr_list;
		entry = (struct name_entry *)item->hi_data;
		(void) mutex_lock(&entry->mtx);
		addr = &entry->addr_list;
		do {
			if (NETBIOS_SAME_IP(addr, name_addr)) {
				/* note that entry lock isn't released here */
				(void) rw_unlock(&nb_cache_lock);
				return (entry);
			}
			addr = addr->forw;
		} while (addr != &entry->addr_list);
		(void) mutex_unlock(&entry->mtx);
	}

	(void) rw_unlock(&nb_cache_lock);
	return (0);
}

int
smb_netbios_cache_insert(struct name_entry *name)
{
	struct name_entry *entry;
	addr_entry_t *addr;
	addr_entry_t *name_addr;
	HT_ITEM *item;
	nb_key_t key;
	int rc;

	/* No point in adding a name with IP address 255.255.255.255 */
	if (name->addr_list.sin.sin_addr.s_addr == 0xffffffff)
		return (0);

	(void) rw_wrlock(&nb_cache_lock);
	smb_netbios_cache_key(key, name->name, name->scope);
	item = ht_find_item(smb_netbios_cache, key);

	if (item && item->hi_data) {
		/* Name already exists */
		entry = (struct name_entry *)item->hi_data;
		(void) mutex_lock(&entry->mtx);

		name_addr = &name->addr_list;
		addr = &entry->addr_list;
		if (NETBIOS_SAME_IP(addr, name_addr) &&
		    (addr->sin.sin_port == name_addr->sin.sin_port)) {
			entry->attributes |=
			    name_addr->attributes & NAME_ATTR_LOCAL;
			(void) mutex_unlock(&entry->mtx);
			(void) rw_unlock(&nb_cache_lock);
			return (0);
		}

		/* Was not primary: looks for others */
		for (addr = entry->addr_list.forw;
		    addr != &entry->addr_list; addr = addr->forw) {
			if (NETBIOS_SAME_IP(addr, name_addr) &&
			    (addr->sin.sin_port == name_addr->sin.sin_port)) {
				(void) mutex_unlock(&entry->mtx);
				(void) rw_unlock(&nb_cache_lock);
				return (0);
			}
		}

		if ((addr = malloc(sizeof (addr_entry_t))) != NULL) {
			*addr = name->addr_list;
			entry->attributes |= addr->attributes;
			QUEUE_INSERT_TAIL(&entry->addr_list, addr);
			rc = 0;
		} else {
			rc = -1;
		}

		(void) mutex_unlock(&entry->mtx);
		(void) rw_unlock(&nb_cache_lock);
		return (rc);
	}

	if ((entry = malloc(sizeof (struct name_entry))) == NULL) {
		(void) rw_unlock(&nb_cache_lock);
		return (-1);
	}

	*entry = *name;
	entry->addr_list.forw = entry->addr_list.back = &entry->addr_list;
	entry->attributes |= entry->addr_list.attributes;
	(void) mutex_init(&entry->mtx, 0, 0);
	if (ht_replace_item(smb_netbios_cache, key, entry) == 0) {
		free(entry);
		(void) rw_unlock(&nb_cache_lock);
		return (-1);
	}

	(void) rw_unlock(&nb_cache_lock);
	return (0);
}


void
smb_netbios_cache_delete(struct name_entry *name)
{
	nb_key_t key;
	HT_ITEM *item;
	struct name_entry *entry;

	(void) rw_wrlock(&nb_cache_lock);
	smb_netbios_cache_key(key, name->name, name->scope);
	item = ht_find_item(smb_netbios_cache, key);
	if (item && item->hi_data) {
		entry = (struct name_entry *)item->hi_data;
		(void) mutex_lock(&entry->mtx);
		ht_mark_delete(smb_netbios_cache, item);
		(void) mutex_unlock(&entry->mtx);
	}
	(void) rw_unlock(&nb_cache_lock);
}

/*
 * smb_netbios_cache_insert_list
 *
 * Insert a name with multiple addresses
 */
int
smb_netbios_cache_insert_list(struct name_entry *name)
{
	struct name_entry entry;
	addr_entry_t *addr;

	addr = &name->addr_list;
	do {
		smb_init_name_struct(NETBIOS_EMPTY_NAME, 0, name->scope,
		    addr->sin.sin_addr.s_addr,
		    addr->sin.sin_port,
		    name->attributes,
		    addr->attributes,
		    &entry);
		(void) memcpy(entry.name, name->name, NETBIOS_NAME_SZ);
		entry.addr_list.refresh_ttl = entry.addr_list.ttl =
		    addr->refresh_ttl;
		(void) smb_netbios_cache_insert(&entry);
		addr = addr->forw;
	} while (addr != &name->addr_list);

	return (0);
}

void
smb_netbios_cache_update_entry(struct name_entry *entry,
    struct name_entry *name)
{
	addr_entry_t *addr;
	addr_entry_t *name_addr;

	addr = &entry->addr_list;
	name_addr = &name->addr_list;

	if (IS_UNIQUE(entry->attributes)) {
		do {
			addr->ttl = name_addr->ttl;
			addr = addr->forw;
		} while (addr != &entry->addr_list);

	} else {
		do {
			if (NETBIOS_SAME_IP(addr, name_addr) &&
			    (addr->sin.sin_port == name_addr->sin.sin_port)) {
				addr->ttl = name_addr->ttl;
				return;
			}
			addr = addr->forw;
		} while (addr != &entry->addr_list);
	}
}

/*
 * smb_netbios_cache_status
 *
 * Scan the name cache and gather status for
 * Node Status response for names in the given scope
 */
unsigned char *
smb_netbios_cache_status(unsigned char *buf, int bufsize, unsigned char *scope)
{
	HT_ITERATOR hti;
	HT_ITEM *item;
	struct name_entry *name;
	unsigned char *numnames;
	unsigned char *scan;
	unsigned char *scan_end;

	scan = buf;
	scan_end = scan + bufsize;

	numnames = scan++;
	*numnames = 0;

	(void) rw_rdlock(&nb_cache_lock);
	item = ht_findfirst(smb_netbios_cache, &hti);
	do {
		if (item == 0)
			break;

		if (item->hi_data == 0)
			continue;

		if ((scan + NETBIOS_NAME_SZ + 2) >= scan_end)
			/* no room for adding next entry */
			break;

		name = (struct name_entry *)item->hi_data;
		(void) mutex_lock(&name->mtx);

		if (IS_LOCAL(name->attributes) &&
		    (strcasecmp((char *)scope, (char *)name->scope) == 0)) {
			bcopy(name->name, scan, NETBIOS_NAME_SZ);
			scan += NETBIOS_NAME_SZ;
			*scan++ = PUBLIC_BITS(name->attributes) >> 8;
			*scan++ = PUBLIC_BITS(name->attributes);
			(*numnames)++;
		}

		(void) mutex_unlock(&name->mtx);
	} while ((item = ht_findnext(&hti)) != 0);
	(void) rw_unlock(&nb_cache_lock);

	return (scan);
}

void
smb_netbios_cache_reset_ttl()
{
	addr_entry_t *addr;
	struct name_entry *name;
	HT_ITERATOR hti;
	HT_ITEM *item;

	(void) rw_rdlock(&nb_cache_lock);
	item = ht_findfirst(smb_netbios_cache, &hti);
	do {
		if (item == 0)
			break;

		if (item->hi_data == 0)
			continue;

		name = (struct name_entry *)item->hi_data;
		(void) mutex_lock(&name->mtx);

		addr = &name->addr_list;
		do {
			if (addr->ttl < 1) {
				if (addr->refresh_ttl)
					addr->ttl = addr->refresh_ttl;
				else
					addr->refresh_ttl = addr->ttl =
					    TO_SECONDS(DEFAULT_TTL);
			}
			addr = addr->forw;
		} while (addr != &name->addr_list);

		(void) mutex_unlock(&name->mtx);
	} while ((item = ht_findnext(&hti)) != 0);
	(void) rw_unlock(&nb_cache_lock);
}

/*
 * Returns TRUE when given name is added to the refresh queue
 * FALSE if not.
 */
static boolean_t
smb_netbios_cache_insrefq(name_queue_t *refq, HT_ITEM *item)
{
	struct name_entry *name;
	struct name_entry *refent;

	name = (struct name_entry *)item->hi_data;

	if (IS_LOCAL(name->attributes)) {
		if (IS_UNIQUE(name->attributes)) {
			refent = smb_netbios_name_dup(name, 1);
			if (refent)
				QUEUE_INSERT_TAIL(&refq->head, refent)

			/* next name */
			return (B_TRUE);
		}
	} else {
		ht_mark_delete(smb_netbios_cache, item);
		refent = smb_netbios_name_dup(name, 0);
		if (refent)
			QUEUE_INSERT_TAIL(&refq->head, refent)

		/* next name */
		return (B_TRUE);
	}

	return (B_FALSE);
}

/*
 * smb_netbios_cache_refresh
 *
 * Scans the name cache and add all local unique names
 * and non-local names the passed refresh queue. Non-
 * local names will also be marked as deleted.
 *
 * NOTE that the caller MUST protect the queue using
 * its mutex
 */
void
smb_netbios_cache_refresh(name_queue_t *refq)
{
	struct name_entry *name;
	addr_entry_t *addr;
	HT_ITERATOR hti;
	HT_ITEM *item;

	bzero(&refq->head, sizeof (refq->head));
	refq->head.forw = refq->head.back = &refq->head;

	(void) rw_rdlock(&nb_cache_lock);
	item = ht_findfirst(smb_netbios_cache, &hti);
	do { /* name loop */
		if (item == 0)
			break;

		if (item->hi_data == 0)
			continue;

		name = (struct name_entry *)item->hi_data;
		(void) mutex_lock(&name->mtx);

		addr = &name->addr_list;
		do { /* address loop */
			if (addr->ttl > 0) {
				addr->ttl--;
				if (addr->ttl == 0) {
					if (smb_netbios_cache_insrefq(refq,
					    item))
						break;
				}
			}
			addr = addr->forw;
		} while (addr != &name->addr_list);

		(void) mutex_unlock(&name->mtx);
	} while ((item = ht_findnext(&hti)) != 0);
	(void) rw_unlock(&nb_cache_lock);
}

/*
 * smb_netbios_cache_delete_locals
 *
 * Scans the name cache and add all local names to
 * the passed delete queue.
 *
 * NOTE that the caller MUST protect the queue using
 * its mutex
 */
void
smb_netbios_cache_delete_locals(name_queue_t *delq)
{
	struct name_entry *entry;
	struct name_entry *delent;
	HT_ITERATOR hti;
	HT_ITEM *item;

	bzero(&delq->head, sizeof (delq->head));
	delq->head.forw = delq->head.back = &delq->head;

	(void) rw_wrlock(&nb_cache_lock);
	item = ht_findfirst(smb_netbios_cache, &hti);
	do {
		if (item == 0)
			break;

		if (item->hi_data == 0)
			continue;

		entry = (struct name_entry *)item->hi_data;
		(void) mutex_lock(&entry->mtx);

		if (IS_LOCAL(entry->attributes)) {
			ht_mark_delete(smb_netbios_cache, item);
			delent = smb_netbios_name_dup(entry, 1);
			if (delent)
				QUEUE_INSERT_TAIL(&delq->head, delent)
		}

		(void) mutex_unlock(&entry->mtx);
	} while ((item = ht_findnext(&hti)) != 0);
	(void) rw_unlock(&nb_cache_lock);
}

void
smb_netbios_name_freeaddrs(struct name_entry *entry)
{
	addr_entry_t *addr;

	if (entry == 0)
		return;

	while ((addr = entry->addr_list.forw) != &entry->addr_list) {
		QUEUE_CLIP(addr);
		free(addr);
	}
}

/*
 * smb_netbios_cache_count
 *
 * Returns the number of names in the cache
 */
int
smb_netbios_cache_count()
{
	int cnt;

	(void) rw_rdlock(&nb_cache_lock);
	cnt = ht_get_total_items(smb_netbios_cache);
	(void) rw_unlock(&nb_cache_lock);

	return (cnt);
}

void
smb_netbios_cache_dump(FILE *fp)
{
	struct name_entry *name;
	HT_ITERATOR hti;
	HT_ITEM *item;

	(void) rw_rdlock(&nb_cache_lock);

	if (ht_get_total_items(smb_netbios_cache) != 0) {
		(void) fprintf(fp, "\n%-22s %-16s %-16s  %s\n",
		    "Name", "Type", "Address", "TTL");
		(void) fprintf(fp, "%s%s\n",
		    "-------------------------------",
		    "------------------------------");
	}

	item = ht_findfirst(smb_netbios_cache, &hti);
	while (item) {
		if (item->hi_data) {
			name = (struct name_entry *)item->hi_data;
			(void) mutex_lock(&name->mtx);
			smb_netbios_name_dump(fp, name);
			(void) mutex_unlock(&name->mtx);
		}
		item = ht_findnext(&hti);
	}
	(void) rw_unlock(&nb_cache_lock);
}

void
smb_netbios_name_dump(FILE *fp, struct name_entry *entry)
{
	char		buf[MAXHOSTNAMELEN];
	addr_entry_t	*addr;
	char		*type;
	int		count = 0;

	smb_strname(entry, buf, sizeof (buf));
	type = (IS_UNIQUE(entry->attributes)) ? "UNIQUE" : "GROUP";

	(void) fprintf(fp, "%s %-6s (0x%04x)  ", buf, type, entry->attributes);

	addr = &entry->addr_list;
	do {
		if (count == 0)
			(void) fprintf(fp, "%-16s  %d\n",
			    inet_ntoa(addr->sin.sin_addr), addr->ttl);
		else
			(void) fprintf(fp, "%-28s  (0x%04x)  %-16s  %d\n",
			    " ", addr->attributes,
			    inet_ntoa(addr->sin.sin_addr), addr->ttl);
		++count;
		addr = addr->forw;
	} while (addr != &entry->addr_list);
}

void
smb_netbios_name_logf(struct name_entry *entry)
{
	char		namebuf[MAXHOSTNAMELEN];
	addr_entry_t	*addr;

	smb_strname(entry, namebuf, sizeof (namebuf));
	syslog(LOG_DEBUG, "%s flags=0x%x\n", namebuf, entry->attributes);
	addr = &entry->addr_list;
	do {
		syslog(LOG_DEBUG, "  %s ttl=%d flags=0x%x port=%d",
		    inet_ntoa(addr->sin.sin_addr),
		    addr->ttl, addr->attributes,
		    addr->sin.sin_port);
		addr = addr->forw;
	} while (addr && (addr != &entry->addr_list));
}

/*
 * smb_netbios_name_dup
 *
 * Duplicate the given name entry. If 'alladdr' is 0 only
 * copy the primary address otherwise duplicate all the
 * addresses. NOTE that the duplicate structure is not
 * like a regular cache entry i.e. it's a contiguous block
 * of memory and each addr structure doesn't have it's own
 * allocated memory. So, the returned structure can be freed
 * by one free call.
 */
struct name_entry *
smb_netbios_name_dup(struct name_entry *entry, int alladdr)
{
	addr_entry_t *addr;
	addr_entry_t *dup_addr;
	struct name_entry *dup;
	int addr_cnt = 0;
	int size = 0;

	if (alladdr) {
		addr = entry->addr_list.forw;
		while (addr && (addr != &entry->addr_list)) {
			addr_cnt++;
			addr = addr->forw;
		}
	}

	size = sizeof (struct name_entry) +
	    (addr_cnt * sizeof (addr_entry_t));
	dup = (struct name_entry *)malloc(size);
	if (dup == 0)
		return (0);

	bzero(dup, size);

	dup->forw = dup->back = dup;
	dup->attributes = entry->attributes;
	(void) memcpy(dup->name, entry->name, NETBIOS_NAME_SZ);
	(void) strlcpy((char *)dup->scope, (char *)entry->scope,
	    NETBIOS_DOMAIN_NAME_MAX);
	dup->addr_list = entry->addr_list;
	dup->addr_list.forw = dup->addr_list.back = &dup->addr_list;

	if (alladdr == 0)
		return (dup);

	/* LINTED - E_BAD_PTR_CAST_ALIGN */
	dup_addr = (addr_entry_t *)((unsigned char *)dup +
	    sizeof (struct name_entry));

	addr = entry->addr_list.forw;
	while (addr && (addr != &entry->addr_list)) {
		*dup_addr = *addr;
		QUEUE_INSERT_TAIL(&dup->addr_list, dup_addr);
		addr = addr->forw;
		dup_addr++;
	}

	return (dup);
}

static void
smb_strname(struct name_entry *entry, char *buf, int bufsize)
{
	char	tmp[MAXHOSTNAMELEN];
	char	*p;

	(void) snprintf(tmp, MAXHOSTNAMELEN, "%15.15s", entry->name);
	if ((p = strchr(tmp, ' ')) != NULL)
		*p = '\0';

	if (entry->scope[0] != '\0') {
		(void) strlcat(tmp, ".", MAXHOSTNAMELEN);
		(void) strlcat(tmp, (char *)entry->scope, MAXHOSTNAMELEN);
	}

	(void) snprintf(buf, bufsize, "%-16s  <%02X>", tmp, entry->name[15]);
}

static void
hash_callback(HT_ITEM *item)
{
	struct name_entry *entry;

	if (item && item->hi_data) {
		entry = (struct name_entry *)item->hi_data;
		smb_netbios_name_freeaddrs(entry);
		free(entry);
	}
}


/*ARGSUSED*/
static int
smb_netbios_match(const char *key1, const char *key2, size_t n)
{
	int res;

	res = bcmp(key1, key2, NETBIOS_NAME_SZ);
	if (res == 0) {
		/* Names are the same, compare scopes */
		res = strcmp(key1 + NETBIOS_NAME_SZ, key2 + NETBIOS_NAME_SZ);
	}

	return (res);
}

static void
smb_netbios_cache_key(char *key, unsigned char *name, unsigned char *scope)
{
	bzero(key, NETBIOS_HKEY_SZ);
	(void) memcpy(key, name, NETBIOS_NAME_SZ);
	(void) memcpy(key + NETBIOS_NAME_SZ, scope,
	    strlen((const char *)scope));
}