/*-
 * Written by Atsushi Murai <amurai@spec.co.jp>
 * Copyright (c) 1998, System Planning and Engineering Co.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *  TODO:
 *       oClean up.
 *       oConsidering for word alignment for other platform.
 */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD: src/sys/netinet/libalias/alias_nbt.c,v 1.19 2005/05/06 11:07:49 glebius Exp $");

/*
    alias_nbt.c performs special processing for NetBios over TCP/IP
    sessions by UDP.

    Initial version:  May, 1998  (Atsushi Murai <amurai@spec.co.jp>)

    See HISTORY file for record of revisions.
*/

/* Includes */
#ifdef _KERNEL
#include <sys/param.h>
#include <sys/ctype.h>
#include <sys/libkern.h>
#else
#include <sys/types.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#endif

#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <netinet/tcp.h>

#ifdef _KERNEL
#include <netinet/libalias/alias.h>
#include <netinet/libalias/alias_local.h>
#else
#include "alias_local.h"
#endif

typedef struct {
	struct in_addr	oldaddr;
	u_short		oldport;
	struct in_addr	newaddr;
	u_short		newport;
	u_short        *uh_sum;
}		NBTArguments;

typedef struct {
	unsigned char	type;
	unsigned char	flags;
	u_short		id;
	struct in_addr	source_ip;
	u_short		source_port;
	u_short		len;
	u_short		offset;
}		NbtDataHeader;

#define OpQuery		0
#define OpUnknown	4
#define OpRegist	5
#define OpRelease	6
#define OpWACK		7
#define OpRefresh	8
typedef struct {
	u_short		nametrid;
	u_short		dir:	1, opcode:4, nmflags:7, rcode:4;
	u_short		qdcount;
	u_short		ancount;
	u_short		nscount;
	u_short		arcount;
}		NbtNSHeader;

#define FMT_ERR		0x1
#define SRV_ERR		0x2
#define IMP_ERR		0x4
#define RFS_ERR		0x5
#define ACT_ERR		0x6
#define CFT_ERR		0x7


#ifdef LIBALIAS_DEBUG
static void
PrintRcode(u_char rcode)
{

	switch (rcode) {
		case FMT_ERR:
		printf("\nFormat Error.");
	case SRV_ERR:
		printf("\nSever failure.");
	case IMP_ERR:
		printf("\nUnsupported request error.\n");
	case RFS_ERR:
		printf("\nRefused error.\n");
	case ACT_ERR:
		printf("\nActive error.\n");
	case CFT_ERR:
		printf("\nName in conflict error.\n");
	default:
		printf("\n?%c?=%0x\n", '?', rcode);

	}
}

#endif


/* Handling Name field */
static u_char  *
AliasHandleName(u_char * p, char *pmax)
{

	u_char *s;
	u_char c;
	int compress;

	/* Following length field */

	if (p == NULL || (char *)p >= pmax)
		return (NULL);

	if (*p & 0xc0) {
		p = p + 2;
		if ((char *)p > pmax)
			return (NULL);
		return ((u_char *) p);
	}
	while ((*p & 0x3f) != 0x00) {
		s = p + 1;
		if (*p == 0x20)
			compress = 1;
		else
			compress = 0;

		/* Get next length field */
		p = (u_char *) (p + (*p & 0x3f) + 1);
		if ((char *)p > pmax) {
			p = NULL;
			break;
		}
#ifdef LIBALIAS_DEBUG
		printf(":");
#endif
		while (s < p) {
			if (compress == 1) {
				c = (u_char) (((((*s & 0x0f) << 4) | (*(s + 1) & 0x0f)) - 0x11));
#ifdef LIBALIAS_DEBUG
				if (isprint(c))
					printf("%c", c);
				else
					printf("<0x%02x>", c);
#endif
				s += 2;
			} else {
#ifdef LIBALIAS_DEBUG
				printf("%c", *s);
#endif
				s++;
			}
		}
#ifdef LIBALIAS_DEBUG
		printf(":");
		fflush(stdout);
#endif
	}

	/* Set up to out of Name field */
	if (p == NULL || (char *)p >= pmax)
		p = NULL;
	else
		p++;
	return ((u_char *) p);
}

/*
 * NetBios Datagram Handler (IP/UDP)
 */
#define DGM_DIRECT_UNIQ		0x10
#define DGM_DIRECT_GROUP	0x11
#define DGM_BROADCAST		0x12
#define DGM_ERROR			0x13
#define DGM_QUERY			0x14
#define DGM_POSITIVE_RES	0x15
#define DGM_NEGATIVE_RES	0x16

int
AliasHandleUdpNbt(
    struct libalias *la,
    struct ip *pip,		/* IP packet to examine/patch */
    struct alias_link *lnk,
    struct in_addr *alias_address,
    u_short alias_port
)
{
	struct udphdr *uh;
	NbtDataHeader *ndh;
	u_char *p = NULL;
	char *pmax;

	(void)la;
	(void)lnk;

	/* Calculate data length of UDP packet */
	uh = (struct udphdr *)ip_next(pip);
	pmax = (char *)uh + ntohs(uh->uh_ulen);

	ndh = (NbtDataHeader *)udp_next(uh);
	if ((char *)(ndh + 1) > pmax)
		return (-1);
#ifdef LIBALIAS_DEBUG
	printf("\nType=%02x,", ndh->type);
#endif
	switch (ndh->type) {
	case DGM_DIRECT_UNIQ:
	case DGM_DIRECT_GROUP:
	case DGM_BROADCAST:
		p = (u_char *) ndh + 14;
		p = AliasHandleName(p, pmax);	/* Source Name */
		p = AliasHandleName(p, pmax);	/* Destination Name */
		break;
	case DGM_ERROR:
		p = (u_char *) ndh + 11;
		break;
	case DGM_QUERY:
	case DGM_POSITIVE_RES:
	case DGM_NEGATIVE_RES:
		p = (u_char *) ndh + 10;
		p = AliasHandleName(p, pmax);	/* Destination Name */
		break;
	}
	if (p == NULL || (char *)p > pmax)
		p = NULL;
#ifdef LIBALIAS_DEBUG
	printf("%s:%d-->", inet_ntoa(ndh->source_ip), ntohs(ndh->source_port));
#endif
	/* Doing an IP address and Port number Translation */
	if (uh->uh_sum != 0) {
		int acc;
		u_short *sptr;

		acc = ndh->source_port;
		acc -= alias_port;
		sptr = (u_short *) & (ndh->source_ip);
		acc += *sptr++;
		acc += *sptr;
		sptr = (u_short *) alias_address;
		acc -= *sptr++;
		acc -= *sptr;
		ADJUST_CHECKSUM(acc, uh->uh_sum);
	}
	ndh->source_ip = *alias_address;
	ndh->source_port = alias_port;
#ifdef LIBALIAS_DEBUG
	printf("%s:%d\n", inet_ntoa(ndh->source_ip), ntohs(ndh->source_port));
	fflush(stdout);
#endif
	return ((p == NULL) ? -1 : 0);
}

/* Question Section */
#define QS_TYPE_NB		0x0020
#define QS_TYPE_NBSTAT	0x0021
#define QS_CLAS_IN		0x0001
typedef struct {
	u_short		type;	/* The type of Request */
	u_short		class;	/* The class of Request */
}		NBTNsQuestion;

static u_char  *
AliasHandleQuestion(
    u_short count,
    NBTNsQuestion * q,
    char *pmax,
    NBTArguments * nbtarg)
{

	(void)nbtarg;

	while (count != 0) {
		/* Name Filed */
		q = (NBTNsQuestion *) AliasHandleName((u_char *) q, pmax);

		if (q == NULL || (char *)(q + 1) > pmax) {
			q = NULL;
			break;
		}
		/* Type and Class filed */
		switch (ntohs(q->type)) {
		case QS_TYPE_NB:
		case QS_TYPE_NBSTAT:
			q = q + 1;
			break;
		default:
#ifdef LIBALIAS_DEBUG
			printf("\nUnknown Type on Question %0x\n", ntohs(q->type));
#endif
			break;
		}
		count--;
	}

	/* Set up to out of Question Section */
	return ((u_char *) q);
}

/* Resource Record */
#define RR_TYPE_A		0x0001
#define RR_TYPE_NS		0x0002
#define RR_TYPE_NULL	0x000a
#define RR_TYPE_NB		0x0020
#define RR_TYPE_NBSTAT	0x0021
#define RR_CLAS_IN		0x0001
#define SizeOfNsResource	8
typedef struct {
	u_short		type;
	u_short		class;
	unsigned int	ttl;
	u_short		rdlen;
}		NBTNsResource;

#define SizeOfNsRNB			6
typedef struct {
	u_short		g:	1  , ont:2, resv:13;
	struct in_addr	addr;
}		NBTNsRNB;

static u_char  *
AliasHandleResourceNB(
    NBTNsResource * q,
    char *pmax,
    NBTArguments * nbtarg)
{
	NBTNsRNB *nb;
	u_short bcount;

	if (q == NULL || (char *)(q + 1) > pmax)
		return (NULL);
	/* Check out a length */
	bcount = ntohs(q->rdlen);

	/* Forward to Resource NB position */
	nb = (NBTNsRNB *) ((u_char *) q + SizeOfNsResource);

	/* Processing all in_addr array */
#ifdef LIBALIAS_DEBUG
	printf("NB rec[%s", inet_ntoa(nbtarg->oldaddr));
	printf("->%s, %dbytes] ", inet_ntoa(nbtarg->newaddr), bcount);
#endif
	while (nb != NULL && bcount != 0) {
		if ((char *)(nb + 1) > pmax) {
			nb = NULL;
			break;
		}
#ifdef LIBALIAS_DEBUG
		printf("<%s>", inet_ntoa(nb->addr));
#endif
		if (!bcmp(&nbtarg->oldaddr, &nb->addr, sizeof(struct in_addr))) {
			if (*nbtarg->uh_sum != 0) {
				int acc;
				u_short *sptr;

				sptr = (u_short *) & (nb->addr);
				acc = *sptr++;
				acc += *sptr;
				sptr = (u_short *) & (nbtarg->newaddr);
				acc -= *sptr++;
				acc -= *sptr;
				ADJUST_CHECKSUM(acc, *nbtarg->uh_sum);
			}
			nb->addr = nbtarg->newaddr;
#ifdef LIBALIAS_DEBUG
			printf("O");
#endif
		}
#ifdef LIBALIAS_DEBUG
		else {
			printf(".");
		}
#endif
		nb = (NBTNsRNB *) ((u_char *) nb + SizeOfNsRNB);
		bcount -= SizeOfNsRNB;
	}
	if (nb == NULL || (char *)(nb + 1) > pmax) {
		nb = NULL;
	}
	return ((u_char *) nb);
}

#define SizeOfResourceA		6
typedef struct {
	struct in_addr	addr;
}		NBTNsResourceA;

static u_char  *
AliasHandleResourceA(
    NBTNsResource * q,
    char *pmax,
    NBTArguments * nbtarg)
{
	NBTNsResourceA *a;
	u_short bcount;

	if (q == NULL || (char *)(q + 1) > pmax)
		return (NULL);

	/* Forward to Resource A position */
	a = (NBTNsResourceA *) ((u_char *) q + sizeof(NBTNsResource));

	/* Check out of length */
	bcount = ntohs(q->rdlen);

	/* Processing all in_addr array */
#ifdef LIBALIAS_DEBUG
	printf("Arec [%s", inet_ntoa(nbtarg->oldaddr));
	printf("->%s]", inet_ntoa(nbtarg->newaddr));
#endif
	while (bcount != 0) {
		if (a == NULL || (char *)(a + 1) > pmax)
			return (NULL);
#ifdef LIBALIAS_DEBUG
		printf("..%s", inet_ntoa(a->addr));
#endif
		if (!bcmp(&nbtarg->oldaddr, &a->addr, sizeof(struct in_addr))) {
			if (*nbtarg->uh_sum != 0) {
				int acc;
				u_short *sptr;

				sptr = (u_short *) & (a->addr);	/* Old */
				acc = *sptr++;
				acc += *sptr;
				sptr = (u_short *) & nbtarg->newaddr;	/* New */
				acc -= *sptr++;
				acc -= *sptr;
				ADJUST_CHECKSUM(acc, *nbtarg->uh_sum);
			}
			a->addr = nbtarg->newaddr;
		}
		a++;		/* XXXX */
		bcount -= SizeOfResourceA;
	}
	if (a == NULL || (char *)(a + 1) > pmax)
		a = NULL;
	return ((u_char *) a);
}

typedef struct {
	u_short		opcode:4, flags:8, resv:4;
}		NBTNsResourceNULL;

static u_char  *
AliasHandleResourceNULL(
    NBTNsResource * q,
    char *pmax,
    NBTArguments * nbtarg)
{
	NBTNsResourceNULL *n;
	u_short bcount;

	(void)nbtarg;

	if (q == NULL || (char *)(q + 1) > pmax)
		return (NULL);

	/* Forward to Resource NULL position */
	n = (NBTNsResourceNULL *) ((u_char *) q + sizeof(NBTNsResource));

	/* Check out of length */
	bcount = ntohs(q->rdlen);

	/* Processing all in_addr array */
	while (bcount != 0) {
		if ((char *)(n + 1) > pmax) {
			n = NULL;
			break;
		}
		n++;
		bcount -= sizeof(NBTNsResourceNULL);
	}
	if ((char *)(n + 1) > pmax)
		n = NULL;

	return ((u_char *) n);
}

static u_char  *
AliasHandleResourceNS(
    NBTNsResource * q,
    char *pmax,
    NBTArguments * nbtarg)
{
	NBTNsResourceNULL *n;
	u_short bcount;

	(void)nbtarg;

	if (q == NULL || (char *)(q + 1) > pmax)
		return (NULL);

	/* Forward to Resource NULL position */
	n = (NBTNsResourceNULL *) ((u_char *) q + sizeof(NBTNsResource));

	/* Check out of length */
	bcount = ntohs(q->rdlen);

	/* Resource Record Name Filed */
	q = (NBTNsResource *) AliasHandleName((u_char *) n, pmax);	/* XXX */

	if (q == NULL || (char *)((u_char *) n + bcount) > pmax)
		return (NULL);
	else
		return ((u_char *) n + bcount);
}

typedef struct {
	u_short		numnames;
}		NBTNsResourceNBSTAT;

static u_char  *
AliasHandleResourceNBSTAT(
    NBTNsResource * q,
    char *pmax,
    NBTArguments * nbtarg)
{
	NBTNsResourceNBSTAT *n;
	u_short bcount;

	(void)nbtarg;

	if (q == NULL || (char *)(q + 1) > pmax)
		return (NULL);

	/* Forward to Resource NBSTAT position */
	n = (NBTNsResourceNBSTAT *) ((u_char *) q + sizeof(NBTNsResource));

	/* Check out of length */
	bcount = ntohs(q->rdlen);

	if (q == NULL || (char *)((u_char *) n + bcount) > pmax)
		return (NULL);
	else
		return ((u_char *) n + bcount);
}

static u_char  *
AliasHandleResource(
    u_short count,
    NBTNsResource * q,
    char *pmax,
    NBTArguments
    * nbtarg)
{
	while (count != 0) {
		/* Resource Record Name Filed */
		q = (NBTNsResource *) AliasHandleName((u_char *) q, pmax);

		if (q == NULL || (char *)(q + 1) > pmax)
			break;
#ifdef LIBALIAS_DEBUG
		printf("type=%02x, count=%d\n", ntohs(q->type), count);
#endif

		/* Type and Class filed */
		switch (ntohs(q->type)) {
		case RR_TYPE_NB:
			q = (NBTNsResource *) AliasHandleResourceNB(
			    q,
			    pmax,
			    nbtarg
			    );
			break;
		case RR_TYPE_A:
			q = (NBTNsResource *) AliasHandleResourceA(
			    q,
			    pmax,
			    nbtarg
			    );
			break;
		case RR_TYPE_NS:
			q = (NBTNsResource *) AliasHandleResourceNS(
			    q,
			    pmax,
			    nbtarg
			    );
			break;
		case RR_TYPE_NULL:
			q = (NBTNsResource *) AliasHandleResourceNULL(
			    q,
			    pmax,
			    nbtarg
			    );
			break;
		case RR_TYPE_NBSTAT:
			q = (NBTNsResource *) AliasHandleResourceNBSTAT(
			    q,
			    pmax,
			    nbtarg
			    );
			break;
		default:
#ifdef LIBALIAS_DEBUG
			printf(
			    "\nUnknown Type of Resource %0x\n",
			    ntohs(q->type)
			    );
			fflush(stdout);
#endif
			break;
		}
		count--;
	}
	return ((u_char *) q);
}

int
AliasHandleUdpNbtNS(
    struct libalias *la,
    struct ip *pip,		/* IP packet to examine/patch */
    struct alias_link *lnk,
    struct in_addr *alias_address,
    u_short * alias_port,
    struct in_addr *original_address,
    u_short * original_port)
{
	struct udphdr *uh;
	NbtNSHeader *nsh;
	u_char *p;
	char *pmax;
	NBTArguments nbtarg;

	(void)la;
	(void)lnk;

	/* Set up Common Parameter */
	nbtarg.oldaddr = *alias_address;
	nbtarg.oldport = *alias_port;
	nbtarg.newaddr = *original_address;
	nbtarg.newport = *original_port;

	/* Calculate data length of UDP packet */
	uh = (struct udphdr *)ip_next(pip);
	nbtarg.uh_sum = &(uh->uh_sum);
	nsh = (NbtNSHeader *)udp_next(uh);
	p = (u_char *) (nsh + 1);
	pmax = (char *)uh + ntohs(uh->uh_ulen);

	if ((char *)(nsh + 1) > pmax)
		return (-1);

#ifdef LIBALIAS_DEBUG
	printf(" [%s] ID=%02x, op=%01x, flag=%02x, rcode=%01x, qd=%04x"
	    ", an=%04x, ns=%04x, ar=%04x, [%d]-->",
	    nsh->dir ? "Response" : "Request",
	    nsh->nametrid,
	    nsh->opcode,
	    nsh->nmflags,
	    nsh->rcode,
	    ntohs(nsh->qdcount),
	    ntohs(nsh->ancount),
	    ntohs(nsh->nscount),
	    ntohs(nsh->arcount),
	    (u_char *) p - (u_char *) nsh
	    );
#endif

	/* Question Entries */
	if (ntohs(nsh->qdcount) != 0) {
		p = AliasHandleQuestion(
		    ntohs(nsh->qdcount),
		    (NBTNsQuestion *) p,
		    pmax,
		    &nbtarg
		    );
	}
	/* Answer Resource Records */
	if (ntohs(nsh->ancount) != 0) {
		p = AliasHandleResource(
		    ntohs(nsh->ancount),
		    (NBTNsResource *) p,
		    pmax,
		    &nbtarg
		    );
	}
	/* Authority Resource Recodrs */
	if (ntohs(nsh->nscount) != 0) {
		p = AliasHandleResource(
		    ntohs(nsh->nscount),
		    (NBTNsResource *) p,
		    pmax,
		    &nbtarg
		    );
	}
	/* Additional Resource Recodrs */
	if (ntohs(nsh->arcount) != 0) {
		p = AliasHandleResource(
		    ntohs(nsh->arcount),
		    (NBTNsResource *) p,
		    pmax,
		    &nbtarg
		    );
	}
#ifdef LIBALIAS_DEBUG
	PrintRcode(nsh->rcode);
#endif
	return ((p == NULL) ? -1 : 0);
}
