#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <syslog.h>
#include <synch.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <smbns_netbios.h>
#include <smbsrv/libsmbns.h>
static int datagram_sock = -1;
static short datagram_id = 1;
static struct datagram_queue smb_datagram_queue;
static mutex_t smb_dgq_mtx;
static void smb_netbios_datagram_error(unsigned char *buf);
void
smb_netbios_datagram_tick(void)
{
struct datagram *entry;
struct datagram *next;
(void) mutex_lock(&smb_dgq_mtx);
for (entry = smb_datagram_queue.forw;
entry != (struct datagram *)((uintptr_t)&smb_datagram_queue);
entry = next) {
next = entry->forw;
if (--entry->discard_timer == 0) {
QUEUE_CLIP(entry);
free(entry);
}
}
(void) mutex_unlock(&smb_dgq_mtx);
}
void
smb_netbios_datagram_fini()
{
struct datagram *entry;
(void) mutex_lock(&smb_dgq_mtx);
while ((entry = smb_datagram_queue.forw) !=
(struct datagram *)((uintptr_t)&smb_datagram_queue)) {
QUEUE_CLIP(entry);
free(entry);
}
(void) mutex_unlock(&smb_dgq_mtx);
}
int
smb_netbios_datagram_send(struct name_entry *src, struct name_entry *dest,
unsigned char *data, int length)
{
smb_inaddr_t ipaddr;
size_t count, srclen, destlen, sinlen;
addr_entry_t *addr;
struct sockaddr_in sin;
char *buffer;
char ha_source[NETBIOS_DOMAIN_NAME_MAX];
char ha_dest[NETBIOS_DOMAIN_NAME_MAX];
(void) smb_first_level_name_encode(src, (unsigned char *)ha_source,
sizeof (ha_source));
srclen = strlen(ha_source) + 1;
(void) smb_first_level_name_encode(dest, (unsigned char *)ha_dest,
sizeof (ha_dest));
destlen = strlen(ha_dest) + 1;
if ((buffer = malloc(MAX_DATAGRAM_LENGTH * 4)) == NULL) {
syslog(LOG_ERR, "nbt datagram: send: %m");
return (-1);
}
buffer[0] = DATAGRAM_TYPE_DIRECT_UNIQUE;
switch (smb_node_type) {
case 'B':
buffer[1] = DATAGRAM_FLAGS_B_NODE | DATAGRAM_FLAGS_FIRST;
break;
case 'P':
buffer[1] = DATAGRAM_FLAGS_P_NODE | DATAGRAM_FLAGS_FIRST;
break;
case 'M':
buffer[1] = DATAGRAM_FLAGS_M_NODE | DATAGRAM_FLAGS_FIRST;
break;
case 'H':
default:
buffer[1] = DATAGRAM_FLAGS_H_NODE | DATAGRAM_FLAGS_FIRST;
break;
}
datagram_id++;
BE_OUT16(&buffer[2], datagram_id);
(void) memcpy(&buffer[4], &src->addr_list.sin.sin_addr.s_addr,
sizeof (uint32_t));
(void) memcpy(&buffer[8], &src->addr_list.sin.sin_port,
sizeof (uint16_t));
BE_OUT16(&buffer[10], length + srclen + destlen);
BE_OUT16(&buffer[12], 0);
bcopy(ha_source, &buffer[14], srclen);
bcopy(ha_dest, &buffer[14 + srclen], destlen);
bcopy(data, &buffer[14 + srclen + destlen], length);
count = &buffer[14 + srclen + destlen + length] - buffer;
bzero(&sin, sizeof (sin));
sin.sin_family = AF_INET;
sinlen = sizeof (sin);
addr = &dest->addr_list;
do {
ipaddr.a_ipv4 = addr->sin.sin_addr.s_addr;
ipaddr.a_family = AF_INET;
if (smb_nic_is_local(&ipaddr))
goto next;
sin.sin_addr.s_addr = ipaddr.a_ipv4;
sin.sin_port = addr->sin.sin_port;
(void) sendto(datagram_sock, buffer, count, 0,
(struct sockaddr *)&sin, sinlen);
next: addr = addr->forw;
} while (addr != &dest->addr_list);
free(buffer);
return (0);
}
int
smb_netbios_datagram_send_to_net(struct name_entry *src,
struct name_entry *dest, char *data, int length)
{
smb_inaddr_t ipaddr;
size_t count, srclen, destlen, sinlen;
addr_entry_t *addr;
struct sockaddr_in sin;
char *buffer;
char ha_source[NETBIOS_DOMAIN_NAME_MAX];
char ha_dest[NETBIOS_DOMAIN_NAME_MAX];
(void) smb_first_level_name_encode(src, (unsigned char *)ha_source,
sizeof (ha_source));
srclen = strlen(ha_source) + 1;
(void) smb_first_level_name_encode(dest, (unsigned char *)ha_dest,
sizeof (ha_dest));
destlen = strlen(ha_dest) + 1;
if ((buffer = malloc(MAX_DATAGRAM_LENGTH * 4)) == NULL) {
syslog(LOG_ERR, "nbt datagram: send_to_net: %m");
return (-1);
}
buffer[0] = DATAGRAM_TYPE_DIRECT_UNIQUE;
switch (smb_node_type) {
case 'B':
buffer[1] = DATAGRAM_FLAGS_B_NODE | DATAGRAM_FLAGS_FIRST;
break;
case 'P':
buffer[1] = DATAGRAM_FLAGS_P_NODE | DATAGRAM_FLAGS_FIRST;
break;
case 'M':
buffer[1] = DATAGRAM_FLAGS_M_NODE | DATAGRAM_FLAGS_FIRST;
break;
case 'H':
default:
buffer[1] = DATAGRAM_FLAGS_H_NODE | DATAGRAM_FLAGS_FIRST;
break;
}
datagram_id++;
BE_OUT16(&buffer[2], datagram_id);
(void) memcpy(&buffer[4], &src->addr_list.sin.sin_addr.s_addr,
sizeof (uint32_t));
(void) memcpy(&buffer[8], &src->addr_list.sin.sin_port,
sizeof (uint16_t));
BE_OUT16(&buffer[10], length + srclen + destlen);
BE_OUT16(&buffer[12], 0);
bcopy(ha_source, &buffer[14], srclen);
bcopy(ha_dest, &buffer[14 + srclen], destlen);
bcopy(data, &buffer[14 + srclen + destlen], length);
count = &buffer[14 + srclen + destlen + length] - buffer;
bzero(&sin, sizeof (sin));
sin.sin_family = AF_INET;
sinlen = sizeof (sin);
addr = &dest->addr_list;
do {
ipaddr.a_ipv4 = addr->sin.sin_addr.s_addr;
ipaddr.a_family = AF_INET;
if (smb_nic_is_local(&ipaddr))
goto next;
sin.sin_addr.s_addr = ipaddr.a_ipv4;
sin.sin_port = addr->sin.sin_port;
(void) sendto(datagram_sock, buffer, count, 0,
(struct sockaddr *)&sin, sinlen);
next: addr = addr->forw;
} while (addr != &dest->addr_list);
free(buffer);
return (0);
}
int
smb_datagram_decode(struct datagram *datagram, int bytes)
{
unsigned char *ha_src;
unsigned char *ha_dest;
unsigned char *data;
if (bytes == DATAGRAM_ERR_HEADER_LENGTH) {
if (datagram->rawbuf[0] == DATAGRAM_TYPE_ERROR_DATAGRAM)
smb_netbios_datagram_error(datagram->rawbuf);
return (-1);
}
if (bytes >= DATAGRAM_HEADER_LENGTH) {
ha_src = &datagram->rawbuf[DATAGRAM_HEADER_LENGTH];
ha_dest = ha_src + strlen((char *)ha_src) + 1;
data = ha_dest + strlen((char *)ha_dest) + 1;
bzero(&datagram->src, sizeof (struct name_entry));
bzero(&datagram->dest, sizeof (struct name_entry));
datagram->rawbytes = bytes;
datagram->packet_type = datagram->rawbuf[0];
datagram->flags = datagram->rawbuf[1];
datagram->datagram_id = BE_IN16(&datagram->rawbuf[2]);
datagram->src.addr_list.sinlen = sizeof (struct sockaddr_in);
(void) memcpy(&datagram->src.addr_list.sin.sin_addr.s_addr,
&datagram->rawbuf[4], sizeof (uint32_t));
(void) memcpy(&datagram->src.addr_list.sin.sin_port,
&datagram->rawbuf[8], sizeof (uint16_t));
datagram->src.addr_list.forw = datagram->src.addr_list.back =
&datagram->src.addr_list;
datagram->data = data;
datagram->data_length = BE_IN16(&datagram->rawbuf[10]);
datagram->offset = BE_IN16(&datagram->rawbuf[12]);
if (smb_first_level_name_decode(ha_src, &datagram->src) < 0) {
smb_tracef("NbtDatagram[%s]: invalid calling name",
inet_ntoa(datagram->src.addr_list.sin.sin_addr));
smb_tracef("Calling name: <%02X>%32.32s",
ha_src[0], &ha_src[1]);
}
datagram->dest.addr_list.forw = datagram->dest.addr_list.back =
&datagram->dest.addr_list;
if (smb_first_level_name_decode(ha_dest, &datagram->dest) < 0) {
smb_tracef("NbtDatagram[%s]: invalid called name",
inet_ntoa(datagram->src.addr_list.sin.sin_addr));
smb_tracef("Called name: <%02X>%32.32s", ha_dest[0],
&ha_dest[1]);
}
return (0);
}
return (-1);
}
static void
smb_netbios_datagram_error(unsigned char *buf)
{
int error;
int datagram_id;
if (buf[0] != DATAGRAM_TYPE_ERROR_DATAGRAM)
return;
datagram_id = BE_IN16(&buf[2]);
error = buf[DATAGRAM_ERR_HEADER_LENGTH - 1];
switch (error) {
case DATAGRAM_INVALID_SOURCE_NAME_FORMAT:
smb_tracef("NbtDatagramError[%d]: invalid source name format",
datagram_id);
break;
case DATAGRAM_INVALID_DESTINATION_NAME_FORMAT:
smb_tracef("NbtDatagramError[%d]: invalid destination name "
"format", datagram_id);
break;
case DATAGRAM_DESTINATION_NAME_NOT_PRESENT:
default:
break;
}
}
static struct datagram *
smb_netbios_datagram_getq(struct datagram *datagram)
{
struct datagram *prev = 0;
(void) mutex_lock(&smb_dgq_mtx);
for (prev = smb_datagram_queue.forw;
prev != (struct datagram *)((uintptr_t)&smb_datagram_queue);
prev = prev->forw) {
if (prev->src.addr_list.sin.sin_addr.s_addr ==
datagram->src.addr_list.sin.sin_addr.s_addr) {
QUEUE_CLIP(prev);
(void) mutex_unlock(&smb_dgq_mtx);
bcopy(datagram->data, &prev->data[prev->data_length],
datagram->data_length);
prev->data_length += datagram->data_length;
free(datagram);
return (prev);
}
}
(void) mutex_unlock(&smb_dgq_mtx);
return (0);
}
static void
smb_netbios_BPM_datagram(struct datagram *datagram)
{
struct name_entry *entry = 0;
struct datagram *qpacket = 0;
pthread_t browser_dispatch;
switch (datagram->packet_type) {
case DATAGRAM_TYPE_BROADCAST :
if (smb_node_type == 'P') {
break;
}
case DATAGRAM_TYPE_DIRECT_UNIQUE :
case DATAGRAM_TYPE_DIRECT_GROUP :
if ((datagram->flags & DATAGRAM_FLAGS_FIRST) != 0) {
if (datagram->flags & DATAGRAM_FLAGS_MORE) {
datagram->discard_timer = FRAGMENT_TIMEOUT;
(void) mutex_lock(&smb_dgq_mtx);
QUEUE_INSERT_TAIL(&smb_datagram_queue, datagram)
(void) mutex_unlock(&smb_dgq_mtx);
return;
}
} else {
qpacket = smb_netbios_datagram_getq(datagram);
if (qpacket) {
datagram = qpacket;
goto process_datagram;
}
break;
}
process_datagram:
entry = 0;
if ((strcmp((char *)datagram->dest.name, "*") == 0) ||
((entry =
smb_netbios_cache_lookup(&datagram->dest)) != 0)) {
if (entry) {
int is_local = IS_LOCAL(entry->attributes);
smb_netbios_cache_unlock_entry(entry);
if (is_local) {
(void) pthread_create(&browser_dispatch,
0, smb_browser_dispatch,
(void *)datagram);
(void) pthread_detach(browser_dispatch);
return;
}
}
datagram->rawbuf[0] = DATAGRAM_TYPE_ERROR_DATAGRAM;
datagram->rawbuf[1] &= DATAGRAM_FLAGS_SRC_TYPE;
(void) memcpy(&datagram->rawbuf[4],
&datagram->src.addr_list.sin.sin_addr.s_addr,
sizeof (uint32_t));
BE_OUT16(&datagram->rawbuf[8], IPPORT_NETBIOS_DGM);
(void) sendto(datagram_sock, datagram->rawbuf,
datagram->rawbytes, 0,
(struct sockaddr *)&datagram->src.addr_list.sin,
datagram->src.addr_list.sinlen);
}
break;
case DATAGRAM_TYPE_ERROR_DATAGRAM :
break;
}
free(datagram);
}
void *
smb_netbios_datagram_service(void *arg)
{
struct sockaddr_in sin;
struct datagram *datagram;
int bytes, flag = 1;
smb_inaddr_t ipaddr;
(void) mutex_lock(&smb_dgq_mtx);
bzero(&smb_datagram_queue, sizeof (smb_datagram_queue));
smb_datagram_queue.forw = smb_datagram_queue.back =
(struct datagram *)((uintptr_t)&smb_datagram_queue);
(void) mutex_unlock(&smb_dgq_mtx);
if ((datagram_sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
syslog(LOG_ERR, "nbt datagram: socket failed: %m");
smb_netbios_event(NETBIOS_EVENT_ERROR);
return (NULL);
}
flag = 1;
(void) setsockopt(datagram_sock, SOL_SOCKET, SO_REUSEADDR, &flag,
sizeof (flag));
bzero(&sin, sizeof (sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(IPPORT_NETBIOS_DGM);
if (bind(datagram_sock, (struct sockaddr *)&sin, sizeof (sin)) != 0) {
syslog(LOG_ERR, "nbt datagram: bind(%d) failed: %m",
IPPORT_NETBIOS_DGM);
(void) close(datagram_sock);
smb_netbios_event(NETBIOS_EVENT_ERROR);
return (NULL);
}
flag = 1;
(void) setsockopt(datagram_sock, SOL_SOCKET, SO_BROADCAST, &flag,
sizeof (flag));
smb_netbios_event(NETBIOS_EVENT_DGM_START);
while (smb_netbios_running()) {
if ((datagram = malloc(sizeof (struct datagram))) == NULL) {
smb_netbios_sleep(10);
continue;
}
ignore: bzero(&datagram->inaddr, sizeof (addr_entry_t));
datagram->inaddr.sinlen = sizeof (datagram->inaddr.sin);
datagram->inaddr.forw = datagram->inaddr.back =
&datagram->inaddr;
if ((bytes = recvfrom(datagram_sock, datagram->rawbuf,
MAX_DATAGRAM_LENGTH, 0,
(struct sockaddr *)&datagram->inaddr.sin,
&datagram->inaddr.sinlen)) < 0) {
syslog(LOG_ERR, "nbt datagram: recvfrom failed: %m");
smb_netbios_event(NETBIOS_EVENT_ERROR);
break;
}
ipaddr.a_ipv4 = datagram->inaddr.sin.sin_addr.s_addr;
ipaddr.a_family = AF_INET;
if (smb_nic_is_local(&ipaddr)) {
goto ignore;
}
if (smb_datagram_decode(datagram, bytes) < 0)
goto ignore;
smb_netbios_BPM_datagram(datagram);
}
smb_netbios_event(NETBIOS_EVENT_DGM_STOP);
(void) smb_netbios_wait(NETBIOS_EVENT_BROWSER_STOP);
(void) close(datagram_sock);
smb_netbios_datagram_fini();
return (NULL);
}
static char
nb_fmt_flags(unsigned char flags)
{
switch (flags & DATAGRAM_FLAGS_SRC_TYPE) {
case DATAGRAM_FLAGS_B_NODE: return ('B');
case DATAGRAM_FLAGS_P_NODE: return ('P');
case DATAGRAM_FLAGS_M_NODE: return ('M');
case DATAGRAM_FLAGS_H_NODE: return ('H');
default: return ('?');
}
}