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

/*
 * NetBIOS name resolution node types.
 *
 * A B-node (broadcast node) uses broadcasts for name registration
 * and resolution.  Routers typically do not forward broadcasts and
 * only computers on the local subnet will respond.
 *
 * A P-node (peer-to-peer node) uses a NetBIOS name server (WINS)
 * to resolve NetBIOS names, which allows it to work across routers.
 * In order to function in a P-node environment, all computers must
 * be configured to use the NetBIOS name server because P-nodes do
 * not broadcast on the network.
 *
 * A mixed node (M-node) behaves as a B-node by default.  If it cannot
 * resolve the name via broadcast then it tries a NetBIOS name server
 * lookup (P-node).
 *
 * A hybrid node (H-node) behaves as a P-node by default.  If it cannot
 * resolve the name using a NetBIOS name server then it resorts to
 * broadcasts (B-node).
 *
 * NetBIOS Name Service Protocols
 *
 * A REQUEST packet is always sent to the well known UDP port 137.
 * The destination address is normally either the IP broadcast address or
 * the address of the NAME - the address of the NAME server it set up at
 * initialization time.  In rare cases, a request packet will be sent to
 * an end node, e.g.  a NAME QUERY REQUEST sent to "challenge" a node.
 *
 * A RESPONSE packet is always sent to the source UDP port and source IP
 * address of the request packet.
 *
 * A DEMAND packet must always be sent to the well known UDP port 137.
 * There is no restriction on the target IP address.
 *
 * A transaction ID is a value composed from the requestor's IP address and
 * a unique 16 bit value generated by the originator of the transaction.
 */

#include <unistd.h>
#include <syslog.h>
#include <stdlib.h>
#include <synch.h>
#include <errno.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <arpa/inet.h>
#include <net/if_arp.h>

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

/*
 * RFC 1002 4.2.1.1.  HEADER
 */
#define QUESTION_TYPE_NETBIOS_GENERAL   0x20
#define QUESTION_TYPE_NETBIOS_STATUS    0x21

#define QUESTION_CLASS_INTERNET         0x0001

/*
 * RFC 1002 4.2.1.3.  RESOURCE RECORD
 */
#define RR_TYPE_IP_ADDRESS_RESOURCE     0x0001
#define RR_TYPE_NAME_SERVER_RESOURCE    0x0002
#define RR_TYPE_NULL_RESOURCE           0x000A
#define RR_TYPE_NETBIOS_RESOURCE        0x0020
#define RR_TYPE_NETBIOS_STATUS          0x0021

/*
 *
 * RESOURCE RECORD RR_CLASS field definitions
 */
#define RR_CLASS_INTERNET_CLASS         0x0001

/*
 * NB_FLAGS field of the RESOURCE RECORD RDATA field for RR_TYPE of NB.
 */
#define RR_FLAGS_NB_ONT_MASK            0x6000
#define RR_FLAGS_NB_ONT_B_NODE          0x0000
#define RR_FLAGS_NB_ONT_P_NODE          0x2000
#define RR_FLAGS_NB_ONT_M_NODE          0x4000
#define RR_FLAGS_NB_ONT_RESERVED        0x6000
#define RR_FLAGS_NB_GROUP_NAME          0x8000

#define NAME_FLAGS_PERMANENT_NAME       0x0200
#define NAME_FLAGS_ACTIVE_NAME          0x0400
#define NAME_FLAGS_CONFLICT             0x0800
#define NAME_FLAGS_DEREGISTER           0x1000
#define NAME_FLAGS_ONT_MASK             0x6000
#define NAME_FLAGS_ONT_B_NODE           0x0000
#define NAME_FLAGS_ONT_P_NODE           0x2000
#define NAME_FLAGS_ONT_M_NODE           0x4000
#define NAME_FLAGS_ONT_RESERVED         0x6000
#define NAME_FLAGS_GROUP_NAME           0x8000

#define MAX_NETBIOS_REPLY_DATA_SIZE     500

#define NAME_HEADER_SIZE                12

typedef struct nbt_name_reply {
        struct nbt_name_reply   *forw;
        struct nbt_name_reply   *back;
        struct name_packet      *packet;
        addr_entry_t            *addr;
        uint16_t                name_trn_id;
        boolean_t               reply_ready;
} nbt_name_reply_t;

char smb_node_type;
static nbt_name_reply_t reply_queue;
static mutex_t rq_mtx;
static cond_t rq_cv;

static mutex_t nbt_name_config_mtx;

static name_queue_t delete_queue;
static name_queue_t refresh_queue;

static int name_sock = 0;

static int bcast_num = 0;
static int nbns_num = 0;
static addr_entry_t smb_bcast_list[SMB_PI_MAX_NETWORKS];
static addr_entry_t smb_nbns[SMB_PI_MAX_WINS];

static int smb_netbios_process_response(uint16_t, addr_entry_t *,
    struct name_packet *, uint32_t);

static int smb_send_name_service_packet(addr_entry_t *addr,
    struct name_packet *packet);

/*
 * Allocate a transaction id.
 */
static uint16_t
smb_netbios_name_trn_id(void)
{
        static uint16_t trn_id;
        static mutex_t trn_id_mtx;

        (void) mutex_lock(&trn_id_mtx);

        do {
                ++trn_id;
        } while (trn_id == 0 || trn_id == (uint16_t)-1);

        (void) mutex_unlock(&trn_id_mtx);
        return (trn_id);
}

static int
smb_end_node_challenge(nbt_name_reply_t *reply_info)
{
        int                     rc;
        uint32_t                retry;
        uint16_t                tid;
        struct resource_record  *answer;
        struct name_question    question;
        addr_entry_t            *addr;
        struct name_entry       *destination;
        struct name_packet      packet;
        struct timespec         st;

        /*
         * The response packet has in it the address of the presumed owner
         * of the name.  Challenge that owner.  If owner either does not
         * respond or indicates that they no longer own the name, claim the
         * name.  Otherwise, the name cannot be claimed.
         */

        if ((answer = reply_info->packet->answer) == 0)
                return (-1);

        destination = answer->name;
        question.name = answer->name;

        packet.info = NAME_QUERY_REQUEST | NM_FLAGS_UNICAST;
        packet.qdcount = 1;     /* question entries */
        packet.question = &question;
        packet.ancount = 0;     /* answer recs */
        packet.answer = NULL;
        packet.nscount = 0;     /* authority recs */
        packet.authority = NULL;
        packet.arcount = 0;     /* additional recs */
        packet.additional = NULL;

        addr = &destination->addr_list;
        for (retry = 0; retry < UCAST_REQ_RETRY_COUNT; retry++) {
                tid = smb_netbios_name_trn_id();
                packet.name_trn_id = tid;
                if (smb_send_name_service_packet(addr, &packet) >= 0) {
                        if ((rc = smb_netbios_process_response(tid, addr,
                            &packet, UCAST_REQ_RETRY_TIMEOUT)) != 0)
                                return (rc);
                }
                st.tv_sec = 0;
                st.tv_nsec = (UCAST_REQ_RETRY_TIMEOUT * 1000000);
                (void) nanosleep(&st, 0);
        }
        /* No reply */
        return (0);
}

static nbt_name_reply_t *
smb_name_get_reply(uint16_t tid, uint32_t timeout)
{
        uint16_t                info;
        struct resource_record  *answer;
        nbt_name_reply_t        *reply;
        uint32_t                wait_time, to_save; /* in millisecond */
        struct timeval          wt;
        timestruc_t             to;

        to_save = timeout;
        reply = malloc(sizeof (nbt_name_reply_t));
        if (reply != NULL) {
                reply->reply_ready = B_FALSE;
                reply->name_trn_id = tid;
                (void) mutex_lock(&rq_mtx);
                QUEUE_INSERT_TAIL(&reply_queue, reply);
                (void) mutex_unlock(&rq_mtx);

                for (;;) {
                        (void) gettimeofday(&wt, 0);
                        wait_time = wt.tv_usec / 1000;

                        to.tv_sec = 0;
                        to.tv_nsec = timeout * 1000000;
                        (void) mutex_lock(&rq_mtx);
                        (void) cond_reltimedwait(&rq_cv, &rq_mtx, &to);
                        (void) mutex_unlock(&rq_mtx);

                        if (reply->reply_ready) {
                                info = reply->packet->info;
                                if (PACKET_TYPE(info) == WACK_RESPONSE) {
                                        answer = reply->packet->answer;
                                        wait_time = (answer) ?
                                            TO_MILLISECONDS(answer->ttl) :
                                            DEFAULT_TTL;
                                        free(reply->addr);
                                        free(reply->packet);
                                        timeout = to_save + wait_time;
                                        reply->reply_ready = B_FALSE;
                                        reply->name_trn_id = tid;
                                        (void) mutex_lock(&rq_mtx);
                                        QUEUE_INSERT_TAIL(&reply_queue, reply);
                                        (void) mutex_unlock(&rq_mtx);
                                        continue;
                                }
                                return (reply);
                        }
                        (void) gettimeofday(&wt, 0);
                        wait_time = (wt.tv_usec / 1000) - wait_time;
                        if (wait_time >= timeout) {
                                (void) mutex_lock(&rq_mtx);
                                QUEUE_CLIP(reply);
                                (void) mutex_unlock(&rq_mtx);
                                free(reply);
                                break;
                        }
                        timeout -= wait_time;
                }
        }

        return (0);
}

static void
smb_reply_ready(struct name_packet *packet, addr_entry_t *addr)
{
        nbt_name_reply_t *reply;
        struct resource_record *answer;

        (void) mutex_lock(&rq_mtx);
        for (reply = reply_queue.forw; reply != &reply_queue;
            reply = reply->forw) {
                if (reply->name_trn_id == packet->name_trn_id) {
                        QUEUE_CLIP(reply);

                        reply->addr = addr;
                        reply->packet = packet;
                        reply->reply_ready = B_TRUE;
                        (void) cond_signal(&rq_cv);
                        (void) mutex_unlock(&rq_mtx);
                        return;
                }
        }
        (void) mutex_unlock(&rq_mtx);

        /* Presumably nobody is waiting any more... */
        free(addr);

        answer = packet->answer;
        if (answer)
                smb_netbios_name_freeaddrs(answer->name);
        free(packet);
}

static int
smb_netbios_process_response(uint16_t tid, addr_entry_t *addr,
    struct name_packet *packet, uint32_t timeout)
{
        int                     rc = 0;
        uint16_t                info;
        nbt_name_reply_t        *reply;
        struct resource_record  *answer;
        struct name_entry       *name;
        struct name_entry       *entry;
        struct name_question    *question;
        uint32_t                ttl;

        if ((reply = smb_name_get_reply(tid, timeout)) == 0) {
                return (0); /* No reply: retry */
        }
        info = reply->packet->info;
        answer = reply->packet->answer;

        /* response */
        switch (PACKET_TYPE(info)) {
        case NAME_QUERY_RESPONSE:
                if (POSITIVE_RESPONSE(info)) {
                        addr = &answer->name->addr_list;
                        do {
                                /*
                                 * Make sure that remote name is not
                                 * flagged local
                                 */
                                addr->attributes &= ~NAME_ATTR_LOCAL;

                                if (answer->ttl)
                                        addr->ttl = answer->ttl;
                                else
                                        addr->ttl = DEFAULT_TTL;
                                addr->refresh_ttl = TO_SECONDS(addr->ttl);
                                addr->ttl = addr->refresh_ttl;

                                addr = addr->forw;
                        } while (addr != &answer->name->addr_list);
                        smb_netbios_name_logf(answer->name);
                        (void) smb_netbios_cache_insert_list(answer->name);
                        rc = 1;
                } else {
                        rc = -1;
                }
                break;

        case NAME_REGISTRATION_RESPONSE:
                if (NEGATIVE_RESPONSE(info)) {
                        if (RCODE(info) == RCODE_CFT_ERR) {
                                if (answer == 0) {
                                        rc = -RCODE(info);
                                        break;
                                }

                                name = answer->name;
                                entry = smb_netbios_cache_lookup(name);
                                if (entry) {
                                        /*
                                         * a name in the state "conflict
                                         * detected" does not "logically" exist
                                         * on that node. No further session
                                         * will be accepted on that name.
                                         * No datagrams can be sent against
                                         * that name.
                                         * Such an entry will not be used for
                                         * purposes of processing incoming
                                         * request packets.
                                         * The only valid user NetBIOS operation
                                         * against such a name is DELETE NAME.
                                         */
                                        entry->attributes |= NAME_ATTR_CONFLICT;
                                        syslog(LOG_DEBUG,
                                            "nbns: name conflict: %15.15s",
                                            entry->name);
                                        smb_netbios_cache_unlock_entry(entry);
                                }
                        }
                        rc = -RCODE(info);
                        break;
                }

                /*
                 * name can be added:
                 *   adjust refresh timeout value,
                 *   TTL, for this name
                 */
                question = packet->question;
                ttl = (answer && answer->ttl) ? answer->ttl : DEFAULT_TTL;
                ttl = TO_SECONDS(ttl);
                if ((entry = smb_netbios_cache_lookup(question->name)) != 0) {
                        addr = &entry->addr_list;
                        do {
                                if ((addr->refresh_ttl == 0) ||
                                    (ttl < addr->refresh_ttl))
                                        addr->refresh_ttl = addr->ttl = ttl;
                                addr = addr->forw;
                        } while (addr != &entry->addr_list);
                        smb_netbios_cache_unlock_entry(entry);
                }

                rc = 1;
                break;

        case NAME_RELEASE_RESPONSE:
                rc = 1;
                break;

        case END_NODE_CHALLENGE_REGISTRATION_REQUEST:
                /*
                 * The response packet has in it the
                 * address of the presumed owner of the
                 * name.  Challenge that owner.  If
                 * owner either does not respond or
                 * indicates that they no longer own the
                 * name, claim the name.  Otherwise,
                 * the name cannot be claimed.
                 */
                rc = smb_end_node_challenge(reply);
                break;

        default:
                rc = 0;
                break;
        }

        if (answer)
                smb_netbios_name_freeaddrs(answer->name);
        free(reply->addr);
        free(reply->packet);
        free(reply);
        return (rc);  /* retry */
}

/*
 * smb_name_buf_from_packet
 *
 * Description:
 *      Convert a NetBIOS Name Server Packet Block (npb)
 *      into the bits and bytes destined for the wire.
 *      The "buf" is used as a heap.
 *
 * Inputs:
 *      char *          buf     -> Buffer, from the wire
 *      unsigned        n_buf   -> Length of 'buf'
 *      name_packet     *npb    -> Packet block, decode into
 *      unsigned        n_npb   -> Max bytes in 'npb'
 *
 * Returns:
 *      >0      -> Encode successful, value is length of packet in "buf"
 *      -1      -> Hard error, can not possibly encode
 *      -2      -> Need more memory in buf -- it's too small
 */
static int
smb_name_buf_from_packet(unsigned char *buf, int n_buf,
    struct name_packet *npb)
{
        addr_entry_t            *raddr;
        unsigned char           *heap = buf;
        unsigned char           *end_heap = heap + n_buf;
        unsigned char           comp_name_buf[MAX_NAME_LENGTH];
        unsigned int            tmp;
        int                     i, step;

        if (n_buf < NAME_HEADER_SIZE)
                return (-1);            /* no header, impossible */

        BE_OUT16(heap, npb->name_trn_id);
        heap += 2;

        BE_OUT16(heap, npb->info);
        heap += 2;

        BE_OUT16(heap, npb->qdcount);
        heap += 2;

        BE_OUT16(heap, npb->ancount);
        heap += 2;

        BE_OUT16(heap, npb->nscount);
        heap += 2;

        BE_OUT16(heap, npb->arcount);
        heap += 2;

        for (i = 0; i < npb->qdcount; i++) {
                if ((heap + 34 + 4) > end_heap)
                        return (-2);

                (void) smb_first_level_name_encode(npb->question[i].name,
                    comp_name_buf, sizeof (comp_name_buf));
                (void) strcpy((char *)heap, (char *)comp_name_buf);
                heap += strlen((char *)comp_name_buf) + 1;

                BE_OUT16(heap, npb->question[i].question_type);
                heap += 2;

                BE_OUT16(heap, npb->question[i].question_class);
                heap += 2;
        }

        for (step = 1; step <= 3; step++) {
                struct resource_record *nrr;
                int n;

                /* truly ugly, but saves code copying */
                if (step == 1) {
                        n = npb->ancount;
                        nrr = npb->answer;
                } else if (step == 2) {
                        n = npb->nscount;
                        nrr = npb->authority;
                } else { /* step == 3 */
                        n = npb->arcount;
                        nrr = npb->additional;
                }

                for (i = 0; i < n; i++) {
                        if ((heap + 34 + 10) > end_heap)
                                return (-2);

                        (void) smb_first_level_name_encode(nrr->name,
                            comp_name_buf, sizeof (comp_name_buf));
                        (void) strcpy((char *)heap, (char *)comp_name_buf);
                        heap += strlen((char *)comp_name_buf) + 1;

                        BE_OUT16(heap, nrr[i].rr_type);
                        heap += 2;

                        BE_OUT16(heap, nrr[i].rr_class);
                        heap += 2;

                        BE_OUT32(heap, nrr[i].ttl);
                        heap += 4;

                        BE_OUT16(heap, nrr[i].rdlength);
                        heap += 2;

                        if ((tmp = nrr[i].rdlength) > 0) {
                                if ((heap + tmp) > end_heap)
                                        return (-2);

                                if (nrr[i].rr_type == NAME_RR_TYPE_NB &&
                                    nrr[i].rr_class == NAME_RR_CLASS_IN &&
                                    tmp >= 6 && nrr[i].rdata == 0) {
                                        tmp = nrr[i].name->attributes &
                                            (NAME_ATTR_GROUP |
                                            NAME_ATTR_OWNER_NODE_TYPE);
                                        BE_OUT16(heap, tmp);
                                        heap += 2;

                                        raddr = &nrr[i].name->addr_list;
                                        (void) memcpy(heap,
                                            &raddr->sin.sin_addr.s_addr,
                                            sizeof (uint32_t));
                                        heap += 4;
                                } else {
                                        bcopy(nrr[i].rdata, heap, tmp);
                                        heap += tmp;
                                }
                        }
                }
        }
        return (heap - buf);
}

/*
 * strnchr
 *
 * Lookup for character 'c' in first 'n' chars of string 's'.
 * Returns pointer to the found char, otherwise returns 0.
 */
static char *
strnchr(const char *s, char c, int n)
{
        char *ps = (char *)s;
        char *es = (char *)s + n;

        while (ps < es && *ps) {
                if (*ps == c)
                        return (ps);

                ++ps;
        }

        if (*ps == '\0' && c == '\0')
                return (ps);

        return (0);
}

static boolean_t
is_multihome(char *name)
{
        return (smb_nic_getnum(name) > 1);
}

/*
 * smb_netbios_getname
 *
 * Get the Netbios name part of the given record.
 * Does some boundary checks.
 *
 * Returns the name length on success, otherwise
 * returns 0.
 */
static int
smb_netbios_getname(char *name, char *buf, char *buf_end)
{
        char *name_end;
        int name_len;

        if (buf >= buf_end) {
                /* no room for a NB name */
                return (0);
        }

        name_end = strnchr(buf, '\0', buf_end - buf + 1);
        if (name_end == 0) {
                /* not a valid NB name */
                return (0);
        }

        name_len = name_end - buf + 1;

        (void) strlcpy(name, buf, name_len);
        return (name_len);
}

/*
 * smb_name_buf_to_packet
 *
 * Convert the bits and bytes that came from the wire into a NetBIOS
 * Name Server Packet Block (npb).  The "block" is used as a heap.
 *
 * Returns a pointer to a name packet on success.  Otherwise, returns
 * a NULL pointer.
 */
static struct name_packet *
smb_name_buf_to_packet(char *buf, int n_buf)
{
        struct name_packet *npb;
        unsigned char *heap;
        unsigned char *scan = (unsigned char *)buf;
        unsigned char *scan_end = scan + n_buf;
        char name_buf[MAX_NAME_LENGTH];
        struct resource_record *nrr = 0;
        int     rc, i, n, nn, ns;
        uint16_t name_trn_id, info;
        uint16_t qdcount, ancount, nscount, arcount;
        addr_entry_t *next;
        int name_len;

        if (n_buf < NAME_HEADER_SIZE) {
                /* truncated header */
                syslog(LOG_DEBUG, "nbns: short packet (%d bytes)", n_buf);
                return (NULL);
        }

        name_trn_id = BE_IN16(scan); scan += 2;
        info = BE_IN16(scan); scan += 2;
        qdcount = BE_IN16(scan); scan += 2;
        ancount = BE_IN16(scan); scan += 2;
        nscount = BE_IN16(scan); scan += 2;
        arcount = BE_IN16(scan); scan += 2;

        ns = sizeof (struct name_entry);
        n = n_buf + sizeof (struct name_packet) +
            ((unsigned)qdcount * (sizeof (struct name_question) + ns)) +
            ((unsigned)ancount * (sizeof (struct resource_record) + ns)) +
            ((unsigned)nscount * (sizeof (struct resource_record) + ns)) +
            ((unsigned)arcount * (sizeof (struct resource_record) + ns));

        if ((npb = malloc(n)) == NULL)
                return (NULL);

        bzero(npb, n);
        heap = npb->block_data;
        npb->name_trn_id = name_trn_id;
        npb->info = info;
        npb->qdcount = qdcount;
        npb->ancount = ancount;
        npb->nscount = nscount;
        npb->arcount = arcount;

        /* scan is in position for question entries */

        /*
         * Measure the space needed for the tables
         */
        if (qdcount > 0) {
                /* LINTED - E_BAD_PTR_CAST_ALIGN */
                npb->question = (struct name_question *)heap;
                heap += qdcount * sizeof (struct name_question);
                for (i = 0; i < qdcount; i++) {
                        /* LINTED - E_BAD_PTR_CAST_ALIGN */
                        npb->question[i].name = (struct name_entry *)heap;
                        heap += sizeof (struct name_entry);
                }
        }

        /* LINTED - E_BAD_PTR_CAST_ALIGN */
        nrr = (struct resource_record *)heap;

        if (ancount > 0) {
                /* LINTED - E_BAD_PTR_CAST_ALIGN */
                npb->answer = (struct resource_record *)heap;
                heap += ancount * sizeof (struct resource_record);
        }

        if (nscount > 0) {
                /* LINTED - E_BAD_PTR_CAST_ALIGN */
                npb->authority = (struct resource_record *)heap;
                heap += nscount * sizeof (struct resource_record);
        }

        if (arcount > 0) {
                /* LINTED - E_BAD_PTR_CAST_ALIGN */
                npb->additional = (struct resource_record *)heap;
                heap += arcount * sizeof (struct resource_record);
        }

        /*
         * Populate each resource_record's .name field.
         * Done as a second pass so that all resource records
         * (answer, authority, additional) are consecutive via nrr[i].
         */
        for (i = 0; i < (ancount + nscount + arcount); i++) {
                /* LINTED - E_BAD_PTR_CAST_ALIGN */
                nrr[i].name = (struct name_entry *)heap;
                heap += sizeof (struct name_entry);
        }


        for (i = 0; i < npb->qdcount; i++) {
                name_len = smb_netbios_getname(name_buf, (char *)scan,
                    (char *)scan_end);
                if (name_len <= 0) {
                        free(npb);
                        return (NULL);
                }

                smb_init_name_struct(NETBIOS_EMPTY_NAME, 0, 0, 0, 0, 0, 0,
                    npb->question[i].name);
                rc = smb_first_level_name_decode((unsigned char *)name_buf,
                    npb->question[i].name);
                if (rc < 0) {
                        /* Couldn't decode the question name */
                        free(npb);
                        return (NULL);
                }

                scan += name_len;
                if (scan + 4 > scan_end) {
                        /* no room for Question Type(2) and Class(2) fields */
                        free(npb);
                        return (NULL);
                }

                npb->question[i].question_type = BE_IN16(scan); scan += 2;
                npb->question[i].question_class = BE_IN16(scan); scan += 2;
        }

        /*
         * Cheat. Remaining sections are of the same resource_record
         * format. Table space is consecutive.
         */

        for (i = 0; i < (ancount + nscount + arcount); i++) {
                if (scan[0] == 0xc0) {
                        /* Namebuf is reused... */
                        rc = 2;
                } else {
                        name_len = smb_netbios_getname(name_buf, (char *)scan,
                            (char *)scan_end);
                        if (name_len <= 0) {
                                free(npb);
                                return (NULL);
                        }
                        rc = name_len;
                }
                scan += rc;

                if (scan + 10 > scan_end) {
                        /*
                         * no room for RR_TYPE (2), RR_CLASS (2), TTL (4) and
                         * RDLENGTH (2) fields.
                         */
                        free(npb);
                        return (NULL);
                }

                smb_init_name_struct(NETBIOS_EMPTY_NAME, 0, 0, 0, 0, 0, 0,
                    nrr[i].name);
                if ((rc = smb_first_level_name_decode((unsigned char *)name_buf,
                    nrr[i].name)) < 0) {
                        free(npb);
                        return (NULL);
                }

                nrr[i].rr_type = BE_IN16(scan); scan += 2;
                nrr[i].rr_class = BE_IN16(scan); scan += 2;
                nrr[i].ttl = BE_IN32(scan); scan += 4;
                nrr[i].rdlength = BE_IN16(scan); scan += 2;

                if ((n = nrr[i].rdlength) > 0) {
                        if ((scan + n) > scan_end) {
                                /* no room for RDATA */
                                free(npb);
                                return (NULL);
                        }
                        bcopy(scan, heap, n);

                        nn = n;
                        if (nrr[i].rr_type == 0x0020 &&
                            nrr[i].rr_class == 0x01 && n >= 6) {
                                while (nn) {
                                        if (nn == 6)
                                                next = &nrr[i].name->addr_list;
                                        else {
                                                next = malloc(
                                                    sizeof (addr_entry_t));
                                                if (next == 0) {
                                                        /* not enough memory */
                                                        free(npb);
                                                        return (NULL);
                                                }
                                                QUEUE_INSERT_TAIL(
                                                    &nrr[i].name->addr_list,
                                                    next);
                                        }
                                        nrr[i].name->attributes =
                                            BE_IN16(scan);
                                        next->sin.sin_family = AF_INET;
                                        next->sinlen = sizeof (next->sin);
                                        (void) memcpy(
                                            &next->sin.sin_addr.s_addr,
                                            scan + 2, sizeof (uint32_t));
                                        next->sin.sin_port =
                                            htons(IPPORT_NETBIOS_DGM);
                                        nn -= 6;
                                        scan += 6;
                                }
                        } else {
                                nrr[i].rdata = heap;
                                scan += n;
                        }
                        heap += n;
                }
        }
        return (npb);
}

/*
 * smb_send_name_service_packet
 *
 * Description:
 *
 *      Send out a name service packet to proper destination.
 *
 * Inputs:
 *      struct netbios_name *dest       -> NETBIOS name of destination
 *      struct name_packet *packet      -> Packet to send
 *
 * Returns:
 *      success ->  >0
 *      failure -> <=0
 */
static int
smb_send_name_service_packet(addr_entry_t *addr, struct name_packet *packet)
{
        unsigned char buf[MAX_DATAGRAM_LENGTH];
        int len;

        if ((len = smb_name_buf_from_packet(buf, sizeof (buf), packet)) < 0) {
                errno = EINVAL;
                return (-1);
        }

        return (sendto(name_sock, buf, len, MSG_EOR,
            (struct sockaddr *)&addr->sin, addr->sinlen));
}

/*
 * smb_netbios_send_rcv
 *
 * This function sends the given NetBIOS packet to the given
 * address and get back the response. If send operation is not
 * successful, it's repeated 'retries' times.
 *
 * Returns:
 *              0               Unsuccessful send operation; no reply
 *              1               Got reply
 */
static int
smb_netbios_send_rcv(int bcast, addr_entry_t *destination,
    struct name_packet *packet, uint32_t retries, uint32_t timeout)
{
        uint32_t retry;
        uint16_t        tid;
        struct timespec st;
        int     rc;

        for (retry = 0; retry < retries; retry++) {
                if ((destination->flags & ADDR_FLAG_VALID) == 0)
                        return (0);

                tid = smb_netbios_name_trn_id();
                packet->name_trn_id = tid;
                if (smb_send_name_service_packet(destination, packet) >= 0) {
                        rc = smb_netbios_process_response(tid, destination,
                            packet, timeout);

                        if ((rc > 0) || (bcast == BROADCAST))
                                return (1);

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

                st.tv_sec = 0;
                st.tv_nsec = (timeout * 1000000);
                (void) nanosleep(&st, 0);
        }

        return (0);
}

/*
 * RFC 1002 4.2.2.  NAME REGISTRATION REQUEST
 */
static int
smb_send_name_registration_request(int bcast, struct name_question *question,
    struct resource_record *additional)
{
        int gotreply = 0;
        uint32_t retries;
        uint32_t timeout;
        addr_entry_t *destination;
        struct name_packet packet;
        unsigned char type;
        int i, addr_num, rc;

        type = question->name->name[15];
        if ((type != NBT_WKSTA) && (type != NBT_SERVER)) {
                syslog(LOG_DEBUG, "nbns: name registration bad type (0x%02x)",
                    type);
                smb_netbios_name_logf(question->name);
                question->name->attributes &= ~NAME_ATTR_LOCAL;
                return (-1);
        }

        if (bcast == BROADCAST) {
                if (bcast_num == 0)
                        return (0);
                destination = smb_bcast_list;
                addr_num = bcast_num;
                retries = BCAST_REQ_RETRY_COUNT;
                timeout = BCAST_REQ_RETRY_TIMEOUT;
                packet.info = NAME_REGISTRATION_REQUEST | NM_FLAGS_BROADCAST;
        } else {
                if (nbns_num == 0)
                        return (0);
                destination = smb_nbns;
                addr_num = nbns_num;
                retries = UCAST_REQ_RETRY_COUNT;
                timeout = UCAST_REQ_RETRY_TIMEOUT;
                packet.info = NAME_REGISTRATION_REQUEST | NM_FLAGS_UNICAST;
        }

        packet.qdcount = 1;     /* question entries */
        packet.question = question;
        packet.ancount = 0;     /* answer recs */
        packet.answer = NULL;
        packet.nscount = 0;     /* authority recs */
        packet.authority = NULL;
        packet.arcount = 1;     /* additional recs */
        packet.additional = additional;

        if (IS_UNIQUE(question->name->attributes) &&
            (is_multihome((char *)(question->name->name))))
                packet.info |= NAME_MULTIHOME_REGISTRATION_REQUEST;

        for (i = 0; i < addr_num; i++) {
                /*
                 * Only register with the Primary WINS server,
                 * unless we got no reply.
                 */
                if ((bcast == UNICAST) && gotreply)
                        break;

                rc = smb_netbios_send_rcv(bcast, &destination[i], &packet,
                    retries, timeout);
                if (rc == 1)
                        gotreply = 1;
        }

        return (gotreply);
}

/*
 * RFC 1002 4.2.4.  NAME REFRESH REQUEST
 */
/*ARGSUSED*/
static int
smb_send_name_refresh_request(int bcast, struct name_question *question,
    struct resource_record *additional, int force)
{
        int rc = 0;
        int gotreply = 0;
        uint32_t retries;
        uint32_t timeout;
        addr_entry_t *addr;
        addr_entry_t *destination;
        struct name_packet packet;
        unsigned char type;
        int i, addr_num, q_addrs = 0;

        type = question->name->name[15];
        if ((type != NBT_WKSTA) && (type != NBT_SERVER)) {
                syslog(LOG_DEBUG, "nbns: name refresh bad type (0x%02x)", type);
                smb_netbios_name_logf(question->name);
                question->name->attributes &= ~NAME_ATTR_LOCAL;
                return (-1);
        }
        switch (bcast) {
        case BROADCAST :
                if (bcast_num == 0)
                        return (-1);
                destination = smb_bcast_list;
                addr_num = bcast_num;
                retries = BCAST_REQ_RETRY_COUNT;
                timeout = BCAST_REQ_RETRY_TIMEOUT;
                packet.info = NAME_REFRESH_REQUEST | NM_FLAGS_BROADCAST;
                break;

        case UNICAST :
                if (nbns_num == 0)
                        return (-1);
                destination = smb_nbns;
                addr_num = nbns_num;
                retries = UCAST_REQ_RETRY_COUNT;
                timeout = UCAST_REQ_RETRY_TIMEOUT;
                packet.info = NAME_REFRESH_REQUEST | NM_FLAGS_UNICAST;
                break;

        default:
                destination = &question->name->addr_list;
                /*
                 * the value of addr_num is irrelvant here, because
                 * the code is going to do special_process so it doesn't
                 * need the addr_num. We set a value here just to avoid
                 * compiler warning.
                 */
                addr_num = 0;
                retries = UCAST_REQ_RETRY_COUNT;
                timeout = UCAST_REQ_RETRY_TIMEOUT;
                packet.info = NAME_REFRESH_REQUEST | NM_FLAGS_UNICAST;
                q_addrs = 1;
                break;
        }

        if (IS_UNIQUE(question->name->attributes) &&
            (is_multihome((char *)(question->name->name))))
                packet.info |= NAME_MULTIHOME_REGISTRATION_REQUEST;

        packet.qdcount = 1;     /* question entries */
        packet.question = question;
        packet.ancount = 0;     /* answer recs */
        packet.answer = NULL;
        packet.nscount = 0;     /* authority recs */
        packet.authority = NULL;
        packet.arcount = 1;     /* additional recs */
        packet.additional = additional;

        if (q_addrs)
                goto special_process;

        for (i = 0; i < addr_num; i++) {
                rc = smb_netbios_send_rcv(bcast, &destination[i], &packet,
                    retries, timeout);
                if (rc == 1)
                        gotreply = 1;
        }

        return (gotreply);

special_process:
        addr = destination;
        do {
                rc = smb_netbios_send_rcv(bcast, addr, &packet,
                    retries, timeout);
                if (rc == 1)
                        gotreply = 1;
                addr = addr->forw;
        } while (addr != destination);

        return (gotreply);
}

/*
 * RFC 1002 4.2.5.  POSITIVE NAME REGISTRATION RESPONSE
 * RFC 1002 4.2.6.  NEGATIVE NAME REGISTRATION RESPONSE
 */
static int
smb_send_name_registration_response(addr_entry_t *addr,
    struct name_packet *original_packet, uint16_t rcode)
{
        struct name_packet      packet;
        struct resource_record  answer;

        bzero(&packet, sizeof (struct name_packet));
        bzero(&answer, sizeof (struct resource_record));

        packet.name_trn_id = original_packet->name_trn_id;
        packet.info = NAME_REGISTRATION_RESPONSE | NAME_NM_FLAGS_RA |
            (rcode & NAME_RCODE_MASK);
        packet.qdcount = 0;     /* question entries */
        packet.question = NULL;
        packet.ancount = 1;     /* answer recs */
        packet.answer = &answer;
        packet.nscount = 0;     /* authority recs */
        packet.authority = NULL;
        packet.arcount = 0;     /* additional recs */
        packet.additional = NULL;

        answer.name = original_packet->question->name;
        answer.rr_type = NAME_QUESTION_TYPE_NB;
        answer.rr_class = NAME_QUESTION_CLASS_IN;
        answer.ttl = original_packet->additional->ttl;
        answer.rdlength = original_packet->additional->rdlength;
        answer.rdata = original_packet->additional->rdata;

        return (smb_send_name_service_packet(addr, &packet));
}

/*
 * RFC 1002 4.2.9.  NAME RELEASE REQUEST & DEMAND
 */
static int
smb_send_name_release_request_and_demand(int bcast,
    struct name_question *question, struct resource_record *additional)
{
        int gotreply = 0;
        int i, rc;
        int addr_num;
        uint32_t retries;
        uint32_t timeout;
        addr_entry_t *destination;
        struct name_packet packet;

        if (bcast == BROADCAST) {
                if (bcast_num == 0)
                        return (-1);
                destination = smb_bcast_list;
                addr_num = bcast_num;
                retries = 1; /* BCAST_REQ_RETRY_COUNT */
                timeout = 100; /* BCAST_REQ_RETRY_TIMEOUT */
                packet.info = NAME_RELEASE_REQUEST | NM_FLAGS_BROADCAST;
        } else {
                if (nbns_num == 0)
                        return (-1);
                destination = smb_nbns;
                addr_num = nbns_num;
                retries = 1; /* UCAST_REQ_RETRY_COUNT */
                timeout = 100; /* UCAST_REQ_RETRY_TIMEOUT */
                packet.info = NAME_RELEASE_REQUEST | NM_FLAGS_UNICAST;
        }

        packet.qdcount = 1;     /* question entries */
        packet.question = question;
        packet.ancount = 0;     /* answer recs */
        packet.answer = NULL;
        packet.nscount = 0;     /* authority recs */
        packet.authority = NULL;
        packet.arcount = 1;     /* additional recs */
        packet.additional = additional;

        for (i = 0; i < addr_num; i++) {
                rc = smb_netbios_send_rcv(bcast, &destination[i], &packet,
                    retries, timeout);
                if (rc == 1)
                        gotreply = 1;
        }

        return (gotreply);
}

/*
 * RFC 1002 4.2.10.  POSITIVE NAME RELEASE RESPONSE
 * RFC 1002 4.2.11.  NEGATIVE NAME RELEASE RESPONSE
 */
static int
/* LINTED - E_STATIC_UNUSED */
smb_send_name_release_response(addr_entry_t *addr,
    struct name_packet *original_packet, uint16_t rcode)
{
        struct name_packet      packet;
        struct resource_record  answer;

        bzero(&packet, sizeof (struct name_packet));
        bzero(&answer, sizeof (struct resource_record));

        packet.name_trn_id = original_packet->name_trn_id;
        packet.info = NAME_RELEASE_RESPONSE | (rcode & NAME_RCODE_MASK);
        packet.qdcount = 0;     /* question entries */
        packet.question = NULL;
        packet.ancount = 1;     /* answer recs */
        packet.answer = &answer;
        packet.nscount = 0;     /* authority recs */
        packet.authority = NULL;
        packet.arcount = 0;     /* additional recs */
        packet.additional = NULL;

        answer.name = original_packet->question->name;
        answer.rr_type = NAME_QUESTION_TYPE_NB;
        answer.rr_class = NAME_QUESTION_CLASS_IN;
        answer.ttl = original_packet->additional->ttl;
        answer.rdlength = original_packet->additional->rdlength;
        answer.rdata = original_packet->additional->rdata;

        return (smb_send_name_service_packet(addr, &packet));
}

/*
 * RFC 1002 4.2.12.  NAME QUERY REQUEST
 */
static int
smb_send_name_query_request(int bcast, struct name_question *question)
{
        int                     rc = 0;
        uint32_t                retry, retries;
        uint32_t                timeout;
        uint16_t                tid;
        addr_entry_t            *destination;
        struct name_packet      packet;
        int                     i, addr_num;
        struct timespec         st;

        if (bcast == BROADCAST) {
                if (bcast_num == 0)
                        return (-1);
                destination = smb_bcast_list;
                addr_num = bcast_num;
                retries = BCAST_REQ_RETRY_COUNT;
                timeout = BCAST_REQ_RETRY_TIMEOUT;
                packet.info = NAME_QUERY_REQUEST | NM_FLAGS_BROADCAST;
        } else {
                if (nbns_num == 0)
                        return (-1);
                destination = smb_nbns;
                addr_num = nbns_num;
                retries = UCAST_REQ_RETRY_COUNT;
                timeout = UCAST_REQ_RETRY_TIMEOUT;
                packet.info = NAME_QUERY_REQUEST | NM_FLAGS_UNICAST;
        }
        packet.qdcount = 1;     /* question entries */
        packet.question = question;
        packet.ancount = 0;     /* answer recs */
        packet.answer = NULL;
        packet.nscount = 0;     /* authority recs */
        packet.authority = NULL;
        packet.arcount = 0;     /* additional recs */
        packet.additional = NULL;

        for (i = 0; i < addr_num; i++) {
                for (retry = 0; retry < retries; retry++) {
                        if ((destination[i].flags & ADDR_FLAG_VALID) == 0)
                                break;
                        tid = smb_netbios_name_trn_id();
                        packet.name_trn_id = tid;

                        if (smb_send_name_service_packet(&destination[i],
                            &packet) >= 0) {
                                if ((rc = smb_netbios_process_response(tid,
                                    &destination[i],
                                    &packet, timeout)) != 0)
                                        break;
                        }
                        st.tv_sec = 0;
                        st.tv_nsec = (timeout * 1000000);
                        (void) nanosleep(&st, 0);
                }
        }

        return (rc);
}

/*
 * RFC 1002 4.2.13.  POSITIVE NAME QUERY RESPONSE
 * RFC 1002 4.2.14.  NEGATIVE NAME QUERY RESPONSE
 */
static int
smb_send_name_query_response(addr_entry_t *addr,
    struct name_packet *original_packet, struct name_entry *entry,
    uint16_t rcode)
{
        addr_entry_t            *raddr;
        struct name_packet      packet;
        struct resource_record  answer;
        uint16_t                attr;
        unsigned char           data[MAX_DATAGRAM_LENGTH];
        unsigned char           *scan = data;
        uint32_t                ret_addr;

        packet.name_trn_id = original_packet->name_trn_id;
        packet.info = NAME_QUERY_RESPONSE | (rcode & NAME_RCODE_MASK);
        packet.qdcount = 0;     /* question entries */
        packet.question = NULL;
        packet.ancount = 1;     /* answer recs */
        packet.answer = &answer;
        packet.nscount = 0;     /* authority recs */
        packet.authority = NULL;
        packet.arcount = 0;     /* additional recs */
        packet.additional = NULL;

        answer.name = entry;
        answer.rr_class = NAME_QUESTION_CLASS_IN;
        answer.ttl = entry->addr_list.ttl;
        answer.rdata = data;
        if (rcode) {
                answer.rr_type = NAME_RR_TYPE_NULL;
                answer.rdlength = 0;
                bzero(data, 6);
        } else {
                answer.rdlength = 0;
                answer.rr_type = NAME_QUESTION_TYPE_NB;
                raddr = &entry->addr_list;
                scan = data;
                do {
                        attr = entry->attributes & (NAME_ATTR_GROUP |
                            NAME_ATTR_OWNER_NODE_TYPE);

                        BE_OUT16(scan, attr); scan += 2;
                        ret_addr = LE_32(raddr->sin.sin_addr.s_addr);
                        *scan++ = ret_addr;
                        *scan++ = ret_addr >> 8;
                        *scan++ = ret_addr >> 16;
                        *scan++ = ret_addr >> 24;

                        answer.rdlength += 6;
                        raddr = raddr->forw;
                } while (raddr != &entry->addr_list);
        }

        return (smb_send_name_service_packet(addr, &packet));
}

/*
 * RFC 1002 4.2.18.  NODE STATUS RESPONSE
 */
static int
smb_send_node_status_response(addr_entry_t *addr,
    struct name_packet *original_packet)
{
        uint32_t                net_ipaddr;
        int64_t                 max_connections;
        struct arpreq           arpreq;
        struct name_packet      packet;
        struct resource_record  answer;
        unsigned char           *scan;
        unsigned char           *scan_end;
        unsigned char           data[MAX_NETBIOS_REPLY_DATA_SIZE];
        boolean_t scan_done = B_FALSE;
        smb_inaddr_t ipaddr;

        bzero(&packet, sizeof (struct name_packet));
        bzero(&answer, sizeof (struct resource_record));

        packet.name_trn_id = original_packet->name_trn_id;
        packet.info = NODE_STATUS_RESPONSE;
        packet.qdcount = 0;     /* question entries */
        packet.question = NULL;
        packet.ancount = 1;     /* answer recs */
        packet.answer = &answer;
        packet.nscount = 0;     /* authority recs */
        packet.authority = NULL;
        packet.arcount = 0;     /* additional recs */
        packet.additional = NULL;

        answer.name = original_packet->question->name;
        answer.rr_type = NAME_RR_TYPE_NBSTAT;
        answer.rr_class = NAME_QUESTION_CLASS_IN;
        answer.ttl = 0;
        answer.rdata = data;

        scan = smb_netbios_cache_status(data, MAX_NETBIOS_REPLY_DATA_SIZE,
            original_packet->question->name->scope);

        scan_end = data + MAX_NETBIOS_REPLY_DATA_SIZE;

        ipaddr.a_ipv4 = addr->sin.sin_addr.s_addr;
        ipaddr.a_family = AF_INET;
        if (smb_nic_is_same_subnet(&ipaddr))
                net_ipaddr = addr->sin.sin_addr.s_addr;
        else
                net_ipaddr = 0;

        (void) smb_config_getnum(SMB_CI_MAX_CONNECTIONS, &max_connections);

        while (!scan_done) {
                if ((scan + 6) >= scan_end) {
                        packet.info |= NAME_NM_FLAGS_TC;
                        break;
                }

                if (net_ipaddr != 0) {
                        struct sockaddr_in *s_in;
                        int s;

                        s = socket(AF_INET, SOCK_DGRAM, 0);
                        /* LINTED - E_BAD_PTR_CAST_ALIGN */
                        s_in = (struct sockaddr_in *)&arpreq.arp_pa;
                        s_in->sin_family = AF_INET;
                        s_in->sin_addr.s_addr = net_ipaddr;
                        if (ioctl(s, SIOCGARP, (caddr_t)&arpreq) < 0) {
                                bzero(scan, 6);
                        } else {
                                bcopy(&arpreq.arp_ha.sa_data, scan, 6);
                        }
                        (void) close(s);
                } else {
                        bzero(scan, 6);
                }
                scan += 6;

                if ((scan + 26) >= scan_end) {
                        packet.info |= NAME_NM_FLAGS_TC;
                        break;
                }
                bzero(scan, 26);
                scan += 26;

                if ((scan + 2) >= scan_end) {
                        packet.info |= NAME_NM_FLAGS_TC;
                        break;
                }
                BE_OUT16(scan, 0); scan += 2;

                if ((scan + 2) >= scan_end) {
                        packet.info |= NAME_NM_FLAGS_TC;
                        break;
                }
                BE_OUT16(scan, 0); scan += 2;

                if ((scan + 2) >= scan_end) {
                        packet.info |= NAME_NM_FLAGS_TC;
                        break;
                }
                BE_OUT16(scan, 0); scan += 2;

                if ((scan + 2) >= scan_end) {
                        packet.info |= NAME_NM_FLAGS_TC;
                        break;
                }
                BE_OUT16(scan, 0); scan += 2;

                if ((scan + 2) >= scan_end) {
                        packet.info |= NAME_NM_FLAGS_TC;
                        break;
                }
                BE_OUT16(scan, 0); scan += 2;

                if ((scan + 2) >= scan_end) {
                        packet.info |= NAME_NM_FLAGS_TC;
                        break;
                }
                BE_OUT16(scan, 0); scan += 2;

                if ((scan + 2) >= scan_end) {
                        packet.info |= NAME_NM_FLAGS_TC;
                        break;
                }
                BE_OUT16(scan, 0); scan += 2;

                if ((scan + 2) >= scan_end) {
                        packet.info |= NAME_NM_FLAGS_TC;
                        break;
                }
                BE_OUT16(scan, max_connections); scan += 2;

                if ((scan + 2) >= scan_end) {
                        packet.info |= NAME_NM_FLAGS_TC;
                        break;
                }

                BE_OUT16(scan, 0); scan += 2;

                scan_done = B_TRUE;
        }
        answer.rdlength = scan - data;
        return (smb_send_name_service_packet(addr, &packet));
}

static int
smb_name_Bnode_add_name(struct name_entry *name)
{
        struct name_question            question;
        struct resource_record          additional;
        unsigned char                   data[8];
        uint16_t                        attr;
        addr_entry_t                    *addr;
        int rc = 0;

        addr = &name->addr_list;

        do {
                /* build name service packet */
                question.name = name;
                /*
                 * question.name->attributes |= NAME_NB_FLAGS_ONT_B;
                 * This is commented because NAME_NB_FLAGS_ONT_B is 0
                 */
                question.question_type = NAME_QUESTION_TYPE_NB;
                question.question_class = NAME_QUESTION_CLASS_IN;

                additional.name = name;
                additional.rr_class = NAME_QUESTION_CLASS_IN;
                additional.ttl = 0;
                additional.rdata = data;
                additional.rdlength = 6;
                additional.rr_type = NAME_QUESTION_TYPE_NB;
                attr = name->attributes & (NAME_ATTR_GROUP |
                    NAME_ATTR_OWNER_NODE_TYPE);

                BE_OUT16(&data[0], attr);
                (void) memcpy(&data[2], &addr->sin.sin_addr.s_addr,
                    sizeof (uint32_t));

                rc |= smb_send_name_registration_request(BROADCAST, &question,
                    &additional);
                addr = addr->forw;

        } while (addr != &name->addr_list);

        return (rc);
}

static int
smb_name_Bnode_find_name(struct name_entry *name)
{
        struct name_question    question;

        question.name = name;
        question.question_type = NAME_QUESTION_TYPE_NB;
        question.question_class = NAME_QUESTION_CLASS_IN;

        return (smb_send_name_query_request(BROADCAST, &question));
}

static int
smb_name_Bnode_delete_name(struct name_entry *name)
{
        struct name_question    question;
        struct resource_record  additional;
        addr_entry_t            *raddr;
        unsigned char           data[MAX_DATAGRAM_LENGTH];
        unsigned char           *scan = data;
        uint32_t                attr;
        uint32_t                ret_addr;

        /* build packet */
        question.name = name;
        question.question_type = NAME_QUESTION_TYPE_NB;
        question.question_class = NAME_QUESTION_CLASS_IN;

        additional.name = name;
        additional.rr_class = NAME_QUESTION_CLASS_IN;
        additional.ttl = 0;
        additional.rdata = data;
        additional.rdlength = 0;
        additional.rr_type = NAME_QUESTION_TYPE_NB;
        raddr = &name->addr_list;
        scan = data;
        do {
                attr = name->attributes & (NAME_ATTR_GROUP |
                    NAME_ATTR_OWNER_NODE_TYPE);

                BE_OUT16(scan, attr); scan += 2;
                ret_addr = LE_32(raddr->sin.sin_addr.s_addr);
                *scan++ = ret_addr;
                *scan++ = ret_addr >> 8;
                *scan++ = ret_addr >> 16;
                *scan++ = ret_addr >> 24;

                additional.rdlength += 6;
        } while (raddr != &name->addr_list);

        return (smb_send_name_release_request_and_demand(BROADCAST,
            &question, &additional));
}

static int
smb_name_Pnode_add_name(struct name_entry *name)
{
        struct name_question            question;
        struct resource_record          additional;
        unsigned char                   data[8];
        uint16_t                        attr;
        addr_entry_t                    *addr;
        int rc = 0;

        /* build packet */
        addr = &name->addr_list;
        do {
                question.name = name;
                question.question_type = NAME_QUESTION_TYPE_NB;
                question.question_class = NAME_QUESTION_CLASS_IN;

                additional.name = name;
                additional.rr_class = NAME_QUESTION_CLASS_IN;
                additional.ttl = 0;
                additional.rdata = data;
                additional.rdlength = 6;
                additional.rr_type = NAME_QUESTION_TYPE_NB;
                attr = name->attributes &
                    (NAME_ATTR_GROUP | NAME_ATTR_OWNER_NODE_TYPE);

                BE_OUT16(&data[0], attr);
                (void) memcpy(&data[2], &addr->sin.sin_addr.s_addr,
                    sizeof (uint32_t));

                rc |= smb_send_name_registration_request(UNICAST, &question,
                    &additional);

                addr = addr->forw;

        } while (addr != &name->addr_list);

        return (rc);
}

static int
smb_name_Pnode_refresh_name(struct name_entry *name)
{
        struct name_question            question;
        struct resource_record          additional;
        unsigned char                   data[8];
        uint16_t                        attr;
        addr_entry_t                    *addr;
        int rc = 0;

        /* build packet */
        addr = &name->addr_list;
        do {
                question.name = name;
                question.question_type = NAME_QUESTION_TYPE_NB;
                question.question_class = NAME_QUESTION_CLASS_IN;

                additional.name = name;
                additional.rr_class = NAME_QUESTION_CLASS_IN;
                additional.ttl = 0;
                additional.rdata = data;
                additional.rdlength = 6;
                additional.rr_type = NAME_QUESTION_TYPE_NB;
                attr = name->attributes &
                    (NAME_ATTR_GROUP | NAME_ATTR_OWNER_NODE_TYPE);

                BE_OUT16(&data[0], attr);
                (void) memcpy(&data[2], &addr->sin.sin_addr.s_addr,
                    sizeof (uint32_t));

                rc |= smb_send_name_refresh_request(UNICAST, &question,
                    &additional, 1);

                addr = addr->forw;
        } while (addr != &name->addr_list);

        return (rc);
}

static int
smb_name_Pnode_find_name(struct name_entry *name)
{
        struct name_question    question;

        /*
         * Host initiated processing for a P node
         */
        question.name = name;
        question.name->attributes |= NAME_NB_FLAGS_ONT_P;
        question.question_type = NAME_QUESTION_TYPE_NB;
        question.question_class = NAME_QUESTION_CLASS_IN;

        return (smb_send_name_query_request(UNICAST, &question));
}

static int
smb_name_Pnode_delete_name(struct name_entry *name)
{
        struct name_question    question;
        struct resource_record  additional;
        addr_entry_t            *raddr;
        unsigned char           data[MAX_DATAGRAM_LENGTH];
        unsigned char           *scan = data;
        uint32_t                attr;
        uint32_t                ret_addr;

        /* build packet */
        question.name = name;
        question.name->attributes |= NAME_NB_FLAGS_ONT_P;
        question.question_type = NAME_QUESTION_TYPE_NB;
        question.question_class = NAME_QUESTION_CLASS_IN;

        additional.name = name;
        additional.rr_class = NAME_QUESTION_CLASS_IN;
        additional.ttl = 0;
        additional.rdata = data;
        additional.rdlength = 0;
        additional.rr_type = NAME_QUESTION_TYPE_NB;
        raddr = &name->addr_list;
        do {
                scan = data;
                attr = name->attributes & (NAME_ATTR_GROUP |
                    NAME_ATTR_OWNER_NODE_TYPE);

                BE_OUT16(scan, attr); scan += 2;
                ret_addr = LE_32(raddr->sin.sin_addr.s_addr);
                *scan++ = ret_addr;
                *scan++ = ret_addr >> 8;
                *scan++ = ret_addr >> 16;
                *scan++ = ret_addr >> 24;

                additional.rdlength = 6;
                raddr = raddr->forw;
                (void) smb_send_name_release_request_and_demand(UNICAST,
                    &question, &additional);
        } while (raddr != &name->addr_list);

        return (1);
}

static int
smb_name_Mnode_add_name(struct name_entry *name)
{
        if (smb_name_Bnode_add_name(name) > 0) {
                if (nbns_num == 0)
                        return (1); /* No name server configured */

                return (smb_name_Pnode_add_name(name));
        }
        return (-1);
}

static int
smb_name_Hnode_add_name(struct name_entry *name)
{
        if (nbns_num > 0) {
                if (smb_name_Pnode_add_name(name) == 1)
                        return (1);
        }

        return (smb_name_Bnode_add_name(name));
}

static int
smb_name_Mnode_find_name(struct name_entry *name)
{
        if (smb_name_Bnode_find_name(name) == 1)
                return (1);

        if (nbns_num == 0)
                return (1); /* No name server configured */

        return (smb_name_Pnode_find_name(name));
}

static int
smb_name_Hnode_find_name(struct name_entry *name)
{
        if (nbns_num > 0)
                if (smb_name_Pnode_find_name(name) == 1)
                        return (1);

        return (smb_name_Bnode_find_name(name));
}

static int
smb_name_Mnode_delete_name(struct name_entry *name)
{
        (void) smb_name_Bnode_delete_name(name);

        if (nbns_num == 0)
                return (-1); /* No name server configured */

        if (smb_name_Pnode_delete_name(name) > 0)
                return (1);

        return (-1);
}

static int
smb_name_Hnode_delete_name(struct name_entry *name)
{
        if (nbns_num > 0)
                if (smb_name_Pnode_delete_name(name) > 0)
                        return (1);

        return (smb_name_Bnode_delete_name(name));
}

static void
smb_name_process_Bnode_packet(struct name_packet *packet, addr_entry_t *addr)
{
        struct name_entry       *name;
        struct name_entry       *entry;
        struct name_question    *question;
        struct resource_record  *additional;

        question = packet->question;
        additional = packet->additional;

        switch (packet->info & NAME_OPCODE_OPCODE_MASK) {
        case NAME_OPCODE_REFRESH:
                /* Guard against malformed packets */
                if ((question == 0) || (additional == 0))
                        break;
                if (additional->name->addr_list.sin.sin_addr.s_addr == 0)
                        break;

                name = question->name;
                name->addr_list.ttl = additional->ttl;
                name->attributes = additional->name->attributes;
                name->addr_list.sin = additional->name->addr_list.sin;
                name->addr_list.forw = name->addr_list.back = &name->addr_list;

                if ((entry = smb_netbios_cache_lookup_addr(name)) != 0) {
                        smb_netbios_cache_update_entry(entry, question->name);
                        smb_netbios_cache_unlock_entry(entry);
                }
                else
                        (void) smb_netbios_cache_insert(question->name);
                break;

        case NAME_OPCODE_QUERY:
                /*
                 * This opcode covers both NAME_QUERY_REQUEST and
                 * NODE_STATUS_REQUEST. They can be distinguished
                 * based on the type of question entry.
                 */

                /* All query requests have to have question entry */
                if (question == 0)
                        break;

                if (question->question_type == NAME_QUESTION_TYPE_NB) {
                        name = question->name;
                        if ((entry = smb_netbios_cache_lookup(name)) != 0) {
                                (void) smb_send_name_query_response(addr,
                                    packet, entry, 0);
                                smb_netbios_cache_unlock_entry(entry);
                        }
                }
                else
                if (question->question_type == NAME_QUESTION_TYPE_NBSTAT) {
                        /*
                         * Name of "*" may be used to force node to
                         * divulge status for administrative purposes
                         */
                        name = question->name;
                        entry = 0;
                        if (NETBIOS_NAME_IS_STAR(name->name) ||
                            ((entry = smb_netbios_cache_lookup(name)) != 0)) {
                                if (entry)
                                        smb_netbios_cache_unlock_entry(entry);
                                /*
                                 * send only those names that are
                                 * in the same scope as the scope
                                 * field in the request packet
                                 */
                                (void) smb_send_node_status_response(addr,
                                    packet);
                        }
                }
                break;

        default:
                break;
        }
}

static void
smb_name_process_Pnode_packet(struct name_packet *packet, addr_entry_t *addr)
{
        struct name_entry       *name;
        struct name_entry       *entry;
        struct name_question    *question;
        struct resource_record  *additional;

        question = packet->question;
        additional = packet->additional;

        if (packet->info & NAME_NM_FLAGS_B) {
                /*
                 * always ignore UDP broadcast packets
                 */
                return;
        }

        switch (packet->info & NAME_OPCODE_OPCODE_MASK) {
        case NAME_OPCODE_REFRESH:
                /* Guard against malformed packets */
                if ((question == 0) || (additional == 0))
                        break;
                if (additional->name->addr_list.sin.sin_addr.s_addr == 0)
                        break;

                name = question->name;
                name->addr_list.ttl = additional->ttl;
                name->attributes = additional->name->attributes;
                name->addr_list.sin = additional->name->addr_list.sin;
                name->addr_list.forw = name->addr_list.back = &name->addr_list;

                if ((entry = smb_netbios_cache_lookup(name)) != 0) {
                        smb_netbios_cache_update_entry(entry, name);
                        smb_netbios_cache_unlock_entry(entry);
                }
                else
                        (void) smb_netbios_cache_insert(name);

                (void) smb_send_name_registration_response(addr, packet, 0);
                break;

        case NAME_OPCODE_QUERY:
                /*
                 * This opcode covers both NAME_QUERY_REQUEST and
                 * NODE_STATUS_REQUEST. They can be distinguished
                 * based on the type of question entry.
                 */

                /* All query requests have to have question entry */
                if (question == 0)
                        break;

                if (question->question_type == NAME_QUESTION_TYPE_NB) {
                        name = question->name;
                        if ((entry = smb_netbios_cache_lookup(name)) != 0) {
                                /*
                                 * send response to the IP address and port
                                 * number from which the request was received.
                                 */
                                (void) smb_send_name_query_response(addr,
                                    packet, entry, 0);
                                smb_netbios_cache_unlock_entry(entry);
                        } else {
                                /*
                                 * send response to the requestor
                                 */
                                (void) smb_send_name_query_response(addr,
                                    packet, name, RCODE_NAM_ERR);
                        }
                }
                else
                if (question->question_type == NAME_QUESTION_TYPE_NBSTAT) {
                        /*
                         * Name of "*" may be used to force node to
                         * divulge status for administrative purposes
                         */
                        name = question->name;
                        entry = 0;
                        if (NETBIOS_NAME_IS_STAR(name->name) ||
                            ((entry = smb_netbios_cache_lookup(name)) != 0)) {
                                /*
                                 * send only those names that are
                                 * in the same scope as the scope
                                 * field in the request packet
                                 */
                                if (entry)
                                        smb_netbios_cache_unlock_entry(entry);
                                (void) smb_send_node_status_response(addr,
                                    packet);
                        }
                }
                break;

        default:
                break;
        }
}

static void
smb_name_process_Mnode_packet(struct name_packet *packet, addr_entry_t *addr)
{
        if (packet->info & NAME_NM_FLAGS_B)
                smb_name_process_Bnode_packet(packet, addr);
        else
                smb_name_process_Pnode_packet(packet, addr);
}

static void
smb_name_process_Hnode_packet(struct name_packet *packet, addr_entry_t *addr)
{
        if (packet->info & NAME_NM_FLAGS_B)
                smb_name_process_Bnode_packet(packet, addr);
        else
                smb_name_process_Pnode_packet(packet, addr);
}


/*
 * smb_netbios_name_tick
 *
 * Called once a second to handle name server timeouts.
 */
void
smb_netbios_name_tick(void)
{
        struct name_entry *name;
        struct name_entry *entry;

        (void) mutex_lock(&refresh_queue.mtx);
        smb_netbios_cache_refresh(&refresh_queue);

        while ((name = refresh_queue.head.forw) != &refresh_queue.head) {
                QUEUE_CLIP(name);
                if (IS_LOCAL(name->attributes)) {
                        if (IS_UNIQUE(name->attributes)) {
                                (void) smb_name_Pnode_refresh_name(name);
                        }
                } else {
                        entry = smb_name_find_name(name);
                        smb_name_unlock_name(entry);
                }
                free(name);
        }
        (void) mutex_unlock(&refresh_queue.mtx);

        smb_netbios_cache_reset_ttl();
}

/*
 * smb_name_find_name
 *
 * Lookup name cache for the given name.
 * If it's not in the cache it'll send a
 * name query request and then lookup the
 * cache again. Note that if a name is
 * returned it's locked and called MUST
 * unlock it by calling smb_name_unlock_name()
 */
struct name_entry *
smb_name_find_name(struct name_entry *name)
{
        struct name_entry *result;

        if ((result = smb_netbios_cache_lookup(name)) == 0) {
                switch (smb_node_type) {
                case 'B':
                        (void) smb_name_Bnode_find_name(name);
                        break;
                case 'P':
                        (void) smb_name_Pnode_find_name(name);
                        break;
                case 'M':
                        (void) smb_name_Mnode_find_name(name);
                        break;
                case 'H':
                default:
                        (void) smb_name_Hnode_find_name(name);
                        break;
                }
                return (smb_netbios_cache_lookup(name));
        }

        return (result);
}

void
smb_name_unlock_name(struct name_entry *name)
{
        smb_netbios_cache_unlock_entry(name);
}

int
smb_name_add_name(struct name_entry *name)
{
        int                     rc = 1;

        smb_netbios_name_logf(name);

        switch (smb_node_type) {
        case 'B':
                rc = smb_name_Bnode_add_name(name);
                break;
        case 'P':
                rc = smb_name_Pnode_add_name(name);
                break;
        case 'M':
                rc = smb_name_Mnode_add_name(name);
                break;
        case 'H':
        default:
                rc = smb_name_Hnode_add_name(name);
                break;
        }

        if (rc >= 0)
                (void) smb_netbios_cache_insert(name);

        return (rc);
}

int
smb_name_delete_name(struct name_entry *name)
{
        int                     rc;
        unsigned char type;

        type = name->name[15];
        if ((type != NBT_WKSTA) && (type != NBT_SERVER)) {
                syslog(LOG_DEBUG, "nbns: name delete bad type (0x%02x)", type);
                smb_netbios_name_logf(name);
                name->attributes &= ~NAME_ATTR_LOCAL;
                return (-1);
        }

        smb_netbios_cache_delete(name);

        switch (smb_node_type) {
        case 'B':
                rc = smb_name_Bnode_delete_name(name);
                break;
        case 'P':
                rc = smb_name_Pnode_delete_name(name);
                break;
        case 'M':
                rc = smb_name_Mnode_delete_name(name);
                break;
        case 'H':
        default:
                rc = smb_name_Hnode_delete_name(name);
                break;
        }

        if (rc > 0)
                return (0);

        return (-1);
}

typedef struct {
        addr_entry_t *addr;
        char *buf;
        int length;
} worker_param_t;

/*
 * smb_netbios_worker
 *
 * Process incoming request/response packets for Netbios
 * name service (on port 138).
 */
void *
smb_netbios_worker(void *arg)
{
        worker_param_t *p = (worker_param_t *)arg;
        addr_entry_t *addr = p->addr;
        struct name_packet *packet;

        if ((packet = smb_name_buf_to_packet(p->buf, p->length)) != NULL) {
                if (packet->info & NAME_OPCODE_R) {
                        /* Reply packet */
                        smb_reply_ready(packet, addr);
                        free(p->buf);
                        free(p);
                        return (NULL);
                }

                /* Request packet */
                switch (smb_node_type) {
                case 'B':
                        smb_name_process_Bnode_packet(packet, addr);
                        break;
                case 'P':
                        smb_name_process_Pnode_packet(packet, addr);
                        break;
                case 'M':
                        smb_name_process_Mnode_packet(packet, addr);
                        break;
                case 'H':
                default:
                        smb_name_process_Hnode_packet(packet, addr);
                        break;
                }

                if (packet->answer)
                        smb_netbios_name_freeaddrs(packet->answer->name);
                free(packet);
        } else {
                syslog(LOG_ERR, "nbns: packet decode failed");
        }

        free(addr);
        free(p->buf);
        free(p);
        return (NULL);
}

/*
 * Configure the node type.  If a WINS server has been specified,
 * act like an H-node.  Otherwise, behave like a B-node.
 */
static void
smb_netbios_node_config(void)
{
        static smb_cfg_id_t     wins[SMB_PI_MAX_WINS] = {
                SMB_CI_WINS_SRV1,
                SMB_CI_WINS_SRV2
        };
        char            ipstr[16];
        uint32_t        ipaddr;
        int             i;

        smb_node_type = SMB_NODETYPE_B;
        nbns_num = 0;
        bzero(smb_nbns, sizeof (addr_entry_t) * SMB_PI_MAX_WINS);

        for (i = 0; i < SMB_PI_MAX_WINS; ++i) {
                ipstr[0] = '\0';
                (void) smb_config_getstr(wins[i], ipstr, sizeof (ipstr));

                if ((ipaddr = inet_addr(ipstr)) == INADDR_NONE)
                        continue;

                smb_node_type = SMB_NODETYPE_H;
                smb_nbns[nbns_num].flags = ADDR_FLAG_VALID;
                smb_nbns[nbns_num].sinlen = sizeof (struct sockaddr_in);
                smb_nbns[nbns_num].sin.sin_family = AF_INET;
                smb_nbns[nbns_num].sin.sin_addr.s_addr = ipaddr;
                smb_nbns[nbns_num].sin.sin_port = htons(IPPORT_NETBIOS_NS);
                nbns_num++;
        }
}

static void
smb_netbios_name_registration(void)
{
        nbcache_iter_t nbc_iter;
        struct name_entry *name;
        int rc;

        rc = smb_netbios_cache_getfirst(&nbc_iter);
        while (rc == 0) {
                name = nbc_iter.nbc_entry;
                (void) smb_netbios_name_logf(name);
                if (IS_UNIQUE(name->attributes) && IS_LOCAL(name->attributes)) {
                        switch (smb_node_type) {
                        case SMB_NODETYPE_B:
                                (void) smb_name_Bnode_add_name(name);
                                break;
                        case SMB_NODETYPE_P:
                                (void) smb_name_Pnode_add_name(name);
                                break;
                        case SMB_NODETYPE_M:
                                (void) smb_name_Mnode_add_name(name);
                                break;
                        case SMB_NODETYPE_H:
                        default:
                                (void) smb_name_Hnode_add_name(name);
                                break;
                        }
                }
                free(name);
                rc = smb_netbios_cache_getnext(&nbc_iter);
        }
}

/*
 * Note that the node configuration must be setup before calling
 * smb_init_name_struct().
 */
void
smb_netbios_name_config(void)
{
        addr_entry_t            *bcast_entry;
        struct name_entry       name;
        smb_niciter_t           ni;
        int                     rc;

        (void) mutex_lock(&nbt_name_config_mtx);
        smb_netbios_node_config();

        bcast_num = 0;
        bzero(smb_bcast_list, sizeof (addr_entry_t) * SMB_PI_MAX_NETWORKS);

        rc = smb_nic_getfirst(&ni);
        while (rc == SMB_NIC_SUCCESS) {
                if ((ni.ni_nic.nic_smbflags & SMB_NICF_NBEXCL) ||
                    (ni.ni_nic.nic_smbflags & SMB_NICF_ALIAS)) {
                        rc = smb_nic_getnext(&ni);
                        continue;
                }

                bcast_entry = &smb_bcast_list[bcast_num];
                bcast_entry->flags = ADDR_FLAG_VALID;
                bcast_entry->attributes = NAME_ATTR_LOCAL;
                bcast_entry->sinlen = sizeof (struct sockaddr_in);
                bcast_entry->sin.sin_family = AF_INET;
                bcast_entry->sin.sin_port = htons(IPPORT_NETBIOS_NS);
                bcast_entry->sin.sin_addr.s_addr = ni.ni_nic.nic_bcast;
                bcast_num++;

                smb_init_name_struct((unsigned char *)ni.ni_nic.nic_host,
                    NBT_WKSTA, 0, ni.ni_nic.nic_ip.a_ipv4,
                    htons(IPPORT_NETBIOS_DGM),
                    NAME_ATTR_UNIQUE, NAME_ATTR_LOCAL, &name);
                (void) smb_netbios_cache_insert(&name);

                smb_init_name_struct((unsigned char *)ni.ni_nic.nic_host,
                    NBT_SERVER, 0, ni.ni_nic.nic_ip.a_ipv4,
                    htons(IPPORT_NETBIOS_DGM),
                    NAME_ATTR_UNIQUE, NAME_ATTR_LOCAL, &name);
                (void) smb_netbios_cache_insert(&name);

                rc = smb_nic_getnext(&ni);
        }

        smb_netbios_name_registration();
        (void) mutex_unlock(&nbt_name_config_mtx);
}

void
smb_netbios_name_unconfig(void)
{
        struct name_entry *name;

        (void) mutex_lock(&nbt_name_config_mtx);
        (void) mutex_lock(&delete_queue.mtx);
        smb_netbios_cache_delete_locals(&delete_queue);

        while ((name = delete_queue.head.forw) != &delete_queue.head) {
                QUEUE_CLIP(name);
                (void) smb_name_delete_name(name);
                free(name);
        }
        (void) mutex_unlock(&delete_queue.mtx);
        (void) mutex_unlock(&nbt_name_config_mtx);
}

void
smb_netbios_name_reconfig(void)
{
        smb_netbios_name_unconfig();
        smb_netbios_name_config();
}

/*
 * NetBIOS Name Service (port 137)
 */
/*ARGSUSED*/
void *
smb_netbios_name_service(void *arg)
{
        struct sockaddr_in      sin;
        addr_entry_t            *addr;
        int                     len;
        int                     flag = 1;
        char                    *buf;
        worker_param_t          *worker_param;
        smb_inaddr_t            ipaddr;

        /*
         * Initialize reply_queue
         */
        bzero(&reply_queue, sizeof (reply_queue));
        reply_queue.forw = reply_queue.back = &reply_queue;

        if ((name_sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
                syslog(LOG_ERR, "nbns: socket failed: %m");
                smb_netbios_event(NETBIOS_EVENT_ERROR);
                return (NULL);
        }

        flag = 1;
        (void) setsockopt(name_sock, SOL_SOCKET, SO_REUSEADDR, &flag,
            sizeof (flag));
        flag = 1;
        (void) setsockopt(name_sock, SOL_SOCKET, SO_BROADCAST, &flag,
            sizeof (flag));

        bzero(&sin, sizeof (struct sockaddr_in));
        sin.sin_family = AF_INET;
        sin.sin_port = htons(IPPORT_NETBIOS_NS);
        if (bind(name_sock, (struct sockaddr *)&sin, sizeof (sin)) != 0) {
                syslog(LOG_ERR, "nbns: bind(%d) failed: %m",
                    IPPORT_NETBIOS_NS);
                (void) close(name_sock);
                smb_netbios_event(NETBIOS_EVENT_ERROR);
                return (NULL);
        }

        smb_netbios_event(NETBIOS_EVENT_NS_START);

        while (smb_netbios_running()) {
                buf = malloc(MAX_DATAGRAM_LENGTH);
                addr = malloc(sizeof (addr_entry_t));
                if ((buf == NULL) || (addr == NULL)) {
                        /* Sleep for 10 seconds and try again */
                        free(addr);
                        free(buf);
                        smb_netbios_sleep(10);
                        continue;
                }
ignore:         bzero(addr, sizeof (addr_entry_t));
                addr->sinlen = sizeof (addr->sin);
                addr->forw = addr->back = addr;

                if ((len = recvfrom(name_sock, buf, MAX_DATAGRAM_LENGTH,
                    0, (struct sockaddr *)&addr->sin, &addr->sinlen)) < 0) {
                        if (errno == ENOMEM || errno == ENFILE ||
                            errno == EMFILE) {
                                /* Sleep for 10 seconds and try again */
                                free(buf);
                                free(addr);
                                smb_netbios_sleep(10);
                                continue;
                        }
                        syslog(LOG_ERR, "nbns: recvfrom failed: %m");
                        free(buf);
                        free(addr);
                        smb_netbios_event(NETBIOS_EVENT_ERROR);
                        goto shutdown;
                }

                /* Ignore any incoming packets from myself... */

                ipaddr.a_ipv4 = addr->sin.sin_addr.s_addr;
                ipaddr.a_family = AF_INET;
                if (smb_nic_is_local(&ipaddr))
                        goto ignore;

                /*
                 * Launch a netbios worker to process the received packet.
                 */
                worker_param = malloc(sizeof (worker_param_t));
                if (worker_param) {
                        pthread_t worker;
                        pthread_attr_t tattr;

                        worker_param->addr = addr;
                        worker_param->buf = buf;
                        worker_param->length = len;

                        (void) pthread_attr_init(&tattr);
                        (void) pthread_attr_setdetachstate(&tattr,
                            PTHREAD_CREATE_DETACHED);
                        (void) pthread_create(&worker, &tattr,
                            smb_netbios_worker, worker_param);
                        (void) pthread_attr_destroy(&tattr);
                }
        }

shutdown:
        smb_netbios_event(NETBIOS_EVENT_NS_STOP);
        smb_netbios_wait(NETBIOS_EVENT_BROWSER_STOP);

        if (!smb_netbios_error())
                smb_netbios_name_unconfig();

        (void) close(name_sock);
        return (NULL);
}