#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>
#define QUESTION_TYPE_NETBIOS_GENERAL 0x20
#define QUESTION_TYPE_NETBIOS_STATUS 0x21
#define QUESTION_CLASS_INTERNET 0x0001
#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
#define RR_CLASS_INTERNET_CLASS 0x0001
#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);
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;
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;
packet.question = &question;
packet.ancount = 0;
packet.answer = NULL;
packet.nscount = 0;
packet.authority = NULL;
packet.arcount = 0;
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);
}
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;
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);
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);
}
info = reply->packet->info;
answer = reply->packet->answer;
switch (PACKET_TYPE(info)) {
case NAME_QUERY_RESPONSE:
if (POSITIVE_RESPONSE(info)) {
addr = &answer->name->addr_list;
do {
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) {
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;
}
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:
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);
}
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);
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;
if (step == 1) {
n = npb->ancount;
nrr = npb->answer;
} else if (step == 2) {
n = npb->nscount;
nrr = npb->authority;
} else {
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);
}
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);
}
static int
smb_netbios_getname(char *name, char *buf, char *buf_end)
{
char *name_end;
int name_len;
if (buf >= buf_end) {
return (0);
}
name_end = strnchr(buf, '\0', buf_end - buf + 1);
if (name_end == 0) {
return (0);
}
name_len = name_end - buf + 1;
(void) strlcpy(name, buf, name_len);
return (name_len);
}
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) {
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;
if (qdcount > 0) {
npb->question = (struct name_question *)heap;
heap += qdcount * sizeof (struct name_question);
for (i = 0; i < qdcount; i++) {
npb->question[i].name = (struct name_entry *)heap;
heap += sizeof (struct name_entry);
}
}
nrr = (struct resource_record *)heap;
if (ancount > 0) {
npb->answer = (struct resource_record *)heap;
heap += ancount * sizeof (struct resource_record);
}
if (nscount > 0) {
npb->authority = (struct resource_record *)heap;
heap += nscount * sizeof (struct resource_record);
}
if (arcount > 0) {
npb->additional = (struct resource_record *)heap;
heap += arcount * sizeof (struct resource_record);
}
for (i = 0; i < (ancount + nscount + arcount); i++) {
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) {
free(npb);
return (NULL);
}
scan += name_len;
if (scan + 4 > scan_end) {
free(npb);
return (NULL);
}
npb->question[i].question_type = BE_IN16(scan); scan += 2;
npb->question[i].question_class = BE_IN16(scan); scan += 2;
}
for (i = 0; i < (ancount + nscount + arcount); i++) {
if (scan[0] == 0xc0) {
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) {
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) {
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) {
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);
}
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));
}
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);
}
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;
packet.question = question;
packet.ancount = 0;
packet.answer = NULL;
packet.nscount = 0;
packet.authority = NULL;
packet.arcount = 1;
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++) {
if ((bcast == UNICAST) && gotreply)
break;
rc = smb_netbios_send_rcv(bcast, &destination[i], &packet,
retries, timeout);
if (rc == 1)
gotreply = 1;
}
return (gotreply);
}
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;
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;
packet.question = question;
packet.ancount = 0;
packet.answer = NULL;
packet.nscount = 0;
packet.authority = NULL;
packet.arcount = 1;
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);
}
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;
packet.question = NULL;
packet.ancount = 1;
packet.answer = &answer;
packet.nscount = 0;
packet.authority = NULL;
packet.arcount = 0;
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));
}
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;
timeout = 100;
packet.info = NAME_RELEASE_REQUEST | NM_FLAGS_BROADCAST;
} else {
if (nbns_num == 0)
return (-1);
destination = smb_nbns;
addr_num = nbns_num;
retries = 1;
timeout = 100;
packet.info = NAME_RELEASE_REQUEST | NM_FLAGS_UNICAST;
}
packet.qdcount = 1;
packet.question = question;
packet.ancount = 0;
packet.answer = NULL;
packet.nscount = 0;
packet.authority = NULL;
packet.arcount = 1;
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);
}
static int
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;
packet.question = NULL;
packet.ancount = 1;
packet.answer = &answer;
packet.nscount = 0;
packet.authority = NULL;
packet.arcount = 0;
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));
}
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;
packet.question = question;
packet.ancount = 0;
packet.answer = NULL;
packet.nscount = 0;
packet.authority = NULL;
packet.arcount = 0;
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);
}
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;
packet.question = NULL;
packet.ancount = 1;
packet.answer = &answer;
packet.nscount = 0;
packet.authority = NULL;
packet.arcount = 0;
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));
}
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;
packet.question = NULL;
packet.ancount = 1;
packet.answer = &answer;
packet.nscount = 0;
packet.authority = NULL;
packet.arcount = 0;
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);
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 {
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(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;
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;
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;
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;
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;
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);
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);
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);
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:
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:
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 = 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);
(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) {
return;
}
switch (packet->info & NAME_OPCODE_OPCODE_MASK) {
case NAME_OPCODE_REFRESH:
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:
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 {
(void) smb_send_name_query_response(addr,
packet, name, RCODE_NAM_ERR);
}
}
else
if (question->question_type == NAME_QUESTION_TYPE_NBSTAT) {
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);
(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);
}
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();
}
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;
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) {
smb_reply_ready(packet, addr);
free(p->buf);
free(p);
return (NULL);
}
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);
}
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);
}
}
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();
}
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;
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)) {
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) {
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;
}
ipaddr.a_ipv4 = addr->sin.sin_addr.s_addr;
ipaddr.a_family = AF_INET;
if (smb_nic_is_local(&ipaddr))
goto ignore;
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);
}