/*
 * 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 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * References used throughout this code:
 *
 * [RFC1001] :	PROTOCOL STANDARD FOR A NetBIOS SERVICE
 *			ON A TCP/UDP TRANSPORT:
 *			CONCEPTS AND METHODS
 *		NetBIOS Working Group, March 1987
 *
 * [RFC1002] :	PROTOCOL STANDARD FOR A NetBIOS SERVICE
 *			ON A TCP/UDP TRANSPORT:
 *			DETAILED SPECIFICATIONS
 *		NetBIOS Working Group, March 1987
 */

#include <fcntl.h>
#include "snoop.h"
#include <stdio.h>
#include <ctype.h>
#include "snoop.h"

extern char *dlc_header;
char *show_type();

/* See snoop_smb.c */
extern void interpret_smb(int flags, uchar_t *data, int len);

/*
 * NBT Session Packet Header
 * [RFC 1002, Sec. 4.3.1]
 */
struct nbt_ss {
	uchar_t type;
	uchar_t flags;
	ushort_t length;
};

/*
 * NBT Session Request Packet trailer
 * [RFC 1002, Sec. 4.3.2]
 */
struct callnames {
	uchar_t space;		/* padding */
	uchar_t calledname[32];
	uchar_t nullchar;		/* padding */
	uchar_t space2;		/* padding */
	uchar_t callingname[32];
	uchar_t nullchar2;	/* padding */
};


static void interpret_netbios_names(int flags, uchar_t *data, int len,
					char *xtra);
static void netbiosname2ascii(char *asciiname, uchar_t *netbiosname);

/*
 * Helpers to read network-order values,
 * with NO alignment assumed.
 */
static ushort_t
getshort(uchar_t *p)
{
	return (p[1] + (p[0]<<8));
}
static uint_t
getlong(uchar_t *p)
{
	return (p[3] + (p[2]<<8) + (p[1]<<16) + (p[0]<<24));
}

/*
 * NM_FLAGS fields in the NetBIOS Name Service Packet header.
 * [RFC 1002,  Sec. 4.2.1.1]
 */
static void
print_flag_details(int headerflags)
{
	if (headerflags & 1<<4)
		sprintf(get_line(0, 0), "   - Broadcast");
	if (headerflags & 1<<7)
		sprintf(get_line(0, 0), "   - Recursion Available");
	if (headerflags & 1<<8)
		sprintf(get_line(0, 0), "   - Recursion Desired");
	if (headerflags & 1<<9)
		sprintf(get_line(0, 0), "   - Truncation Flag");
	if (headerflags & 1<<10)
		sprintf(get_line(0, 0), "   - Authoritative Answer");
}

/*
 * Possible errors in NetBIOS name service packets.
 * [RFC 1002,  Sec. 4.2.6, 4.2.11, 4.2.14]
 */
static void
getrcodeerr(int headerflags, char *errortype)
{
	int error = (headerflags & 0xf);

	switch (error) {
	case 0:
		sprintf(errortype, "Success");
		break;
	case 1:
		sprintf(errortype, "Format Error");
		break;
	case 2:
		sprintf(errortype, "Server Failure");
		break;
	case 3:
		sprintf(errortype, "Name Error");
		break;
	case 4:
		sprintf(errortype, "Unsupported Request Error");
		break;
	case 5:
		sprintf(errortype, "Refused Error");
		break;
	case 6:
		sprintf(errortype, "Active Error");
		break;
	case 7:
		sprintf(errortype, "Name in Conflict Error");
		break;
	default:
		sprintf(errortype, "Unknown Error");
		break;
	}
}

/*
 * OPCODE fields in the NetBIOS Name Service Packet header.
 * [RFC 1002, Sec. 4.2.1.1]
 */
static void
print_ns_type(int flags, int headerflags, char *xtra)
{
	int opcode = (headerflags & 0x7800)>>11;
	int response = (headerflags & 1<<15);
	char *resptype = response ? "Response" : "Request";
	char *optype;

	switch (opcode) {
	case 0:
		optype = "Query";
		break;
	case 5:
		optype = "Registration";
		break;
	case 6:
		optype = "Release";
		break;
	case 7:
		optype = "WACK";
		break;
	case 8:
		optype = "Refresh";
		break;
	default:
		optype = "Unknown";
		break;
	}

	if (flags & F_DTAIL)
		sprintf(get_line(0, 0), "Type = %s %s", optype, resptype);
	else
		sprintf(xtra, "%s %s", optype, resptype);
}


/*
 * Interpret Datagram Packets
 * [RFC 1002, Sec. 4.4]
 */
void
interpret_netbios_datagram(int flags, uchar_t *data, int len)
{
	char name[24];
	int packettype = data[0];
	int packetlen;
	data++;

	if (packettype < 0x10 || packettype > 0x11)
		return;

	if (flags & F_SUM) {
		data += 14;
		netbiosname2ascii(name, data);
		sprintf(get_sum_line(),
		    "NBT Datagram Service Type=%d Source=%s",
		    packettype, name);
	}

	if (flags & F_DTAIL) {
		show_header("NBT:  ", "Netbios Datagram Service Header", len);
		show_space();
		sprintf(get_line(0, 0), "Datagram Packet Type = 0x%.2x",
		    packettype);
		sprintf(get_line(0, 0), "Datagram Flags = 0x%.2x",
		    data[0]);
		data++;
		sprintf(get_line(0, 0), "Datagram ID = 0x%.4x",
		    getshort(data));
		data += 2;
		sprintf(get_line(0, 0), "Source IP = %d.%d.%d.%d",
		    data[0], data[1], data[2], data[3]);
		data += 4;
		sprintf(get_line(0, 0), "Source Port = %d",
		    getshort(data));
		data += 2;
		packetlen = getshort(data);
		sprintf(get_line(0, 0), "Datagram Length = 0x%.4x",
		    packetlen);
		data += 2;
		sprintf(get_line(0, 0), "Packet Offset = 0x%.4x",
		    getshort(data));
		data += 3;
		netbiosname2ascii(name, data);
		sprintf(get_line(0, 0), "Source Name = %s", name);
		data += 34;
		netbiosname2ascii(name, data);
		sprintf(get_line(0, 0), "Destination Name = %s", name);
		sprintf(get_line(0, 0), "Number of data bytes remaining = %d",
		    packetlen - 68);
		show_trailer();
	}
}

/*
 * Interpret NetBIOS Name Service packets.
 * [RFC 1002, Sec. 4.2]
 */
void
interpret_netbios_ns(int flags, uchar_t *data, int len)
{
	int headerflags, qcount, acount, nscount, arcount;
	int transid;
	char name[24];
	char extra[256];
	char errortype[50];
	int rdatalen;
	int rrflags;
	int nameptr;
	int nodecode;
	char *nodetype;
	uchar_t *data0 = data;

	transid = getshort(data); data += 2;
	headerflags = getshort(data); data += 2;
	qcount = getshort(data); data += 2;
	acount = getshort(data); data += 2;
	nscount = getshort(data); data += 2;
	arcount = getshort(data); data += 2;
	getrcodeerr(headerflags, errortype);

	if (flags & F_SUM) {
		print_ns_type(flags, headerflags, extra);
		data++;
		netbiosname2ascii(name, data);
		sprintf(get_sum_line(), "NBT NS %s for %s, %s",
		    extra, name, errortype);

	}


	if (flags & F_DTAIL) {
		show_header("NBT:  ", "Netbios Name Service Header", len);
		show_space();
		print_ns_type(flags, headerflags, 0);
		sprintf(get_line(0, 0), "Status = %s", errortype);
		sprintf(get_line(0, 0), "Transaction ID = 0x%.4x", transid);
		sprintf(get_line(0, 0), "Flags Summary = 0x%.4x",
		    headerflags);
		print_flag_details(headerflags);
		sprintf(get_line(0, 0), "Question count = %d", qcount);
		sprintf(get_line(0, 0), "Answer Count = %d", acount);
		sprintf(get_line(0, 0), "Name Service Count = %d", nscount);
		sprintf(get_line(0, 0),
		    "Additional Record Count = %d", arcount);

		/*
		 * Question Section Packet Description from
		 * [RFC 1002, Sec. 4.2.1.2]
		 */

		if (qcount) {
			data++;
			netbiosname2ascii(name, data);
			sprintf(get_line(0, 0), "Question Name = %s", name);
			data += 33;
			sprintf(get_line(0, 0), "Question Type = 0x%.4x",
			    getshort(data));
			data += 2;
			sprintf(get_line(0, 0), "Question Class = 0x%.4x",
			    getshort(data));
			data += 2;
		}

		/*
		 * Resrouce Record Packet Description from
		 * [RFC 1002, Sec. 4.2.1.3]
		 */

		if ((acount || nscount || arcount) ||
		    (qcount+acount+nscount+arcount == 0)) {
			/* Second level encoding from RFC883 (p.31, 32) */
			if (data[0] & 0xc0) {
				nameptr = getshort(data)&0x3fff;
				netbiosname2ascii(name, (data0+nameptr+1));
				sprintf(get_line(0, 0),
				    "Resource Record Name = %s", name);
				data += 2;
			} else {
				data++;
				netbiosname2ascii(name, data);
				sprintf(get_line(0, 0),
				    "Resource Record Name = %s", name);
				data += 33;
			}
			sprintf(get_line(0, 0),
			    "Resource Record Type = 0x%.4x",
			    getshort(data));
			data += 2;
			sprintf(get_line(0, 0),
			    "Resource Record Class = 0x%.4x",
			    getshort(data));
			data += 2;
			sprintf(get_line(0, 0),
			    "Time to Live (Milliseconds) = %d",
			    getlong(data));
			data += 4;
			rdatalen = getshort(data);
			sprintf(get_line(0, 0), "RDATA Length = 0x%.4x",
			    rdatalen);
			data += 2;
			/* 15.4.2.1.3 */
			if (rdatalen == 6) {
				rrflags = getshort(data);
				data += 2;
				sprintf(get_line(0, 0),
				    "Resource Record Flags = 0x%.4x",
				    rrflags);
				nodecode = (rrflags>>13)& 0x11;
				if (nodecode == 0) nodetype = "B";
				if (nodecode == 1) nodetype = "P";
				if (nodecode == 2) nodetype = "M";
				sprintf(get_line(0, 0), "   - %s, %s node",
				    (rrflags & 1<<15) ?
				    "Group NetBIOS Name":
				    "Unique NetBIOS Name", nodetype);
				sprintf(get_line(0, 0),
				    "Owner IP Address = %d.%d.%d.%d",
				    data[0], data[1], data[2], data[3]);
			}
		}
		show_trailer();

	}
}

/*
 * Interpret NetBIOS session packets.
 * [RFC 1002, Sec. 4.3]
 */
void
interpret_netbios_ses(int flags, uchar_t *data, int len)
{
	struct nbt_ss *ss;
	uchar_t *trailer;
	int length = len - 4;   /* NBT packet length without header */
	char *type;
	char extrainfo[300];

	if (len < sizeof (struct nbt_ss))
		return;

	/*
	 * Packets that are fragments of a large NetBIOS session
	 * message will have no NetBIOS header.  (Only the first
	 * TCP segment will have a NetBIOS header.)  It turns out
	 * that very often, such fragments start with SMB data, so
	 * we should try to recognize and decode them.
	 */
	if (data[0] == 0xff &&
	    data[1] == 'S' &&
	    data[2] == 'M' &&
	    data[3] == 'B') {
		interpret_smb(flags, data, len);
		return;
	}

	/* LINTED PTRALIGN */
	ss = (struct nbt_ss *)data;
	trailer = data + sizeof (*ss);
	extrainfo[0] = '\0';

	if (flags & F_SUM) {
		switch (ss->type) {
		case 0x00:
			type = "SESSION MESSAGE";
			break;
		case 0x81:
			type = "SESSION REQUEST";
			interpret_netbios_names(flags, trailer,
			    length, extrainfo);
			break;
		case 0x82:
			type = "POSITIVE SESSION RESPONSE";
			break;
		case 0x83:
			type = "NEGATIVE SESSION RESPONSE";
			break;
		case 0x84:
			type = "RETARGET SESSION RESPONSE";
			break;
		case 0x85:
			type = "SESSION KEEP ALIVE";
			break;
		default:
			type = "Unknown";
			break;
		}
		(void) sprintf(get_sum_line(),
		    "NBT Type=%s %sLength=%d", type, extrainfo, length);
	}

	if (flags & F_DTAIL) {
		show_header("NBT:  ", "NBT Header", len);
		show_space();

		switch (ss->type) {
		case 0x00:
			(void) sprintf(get_line(0, 0),
			    "Type = SESSION MESSAGE");
			break;
		case 0x81:
			(void) sprintf(get_line(0, 0),
			    "Type = SESSION REQUEST");
			interpret_netbios_names(flags, trailer, length, 0);
			break;
		case 0x82:
			(void) sprintf(get_line(0, 0),
			    "Type = POSITIVE SESSION RESPONSE");
			break;
		case 0x83:
			(void) sprintf(get_line(0, 0),
			    "Type = NEGATIVE SESSION RESPONSE");
			break;
		case 0x84:
			(void) sprintf(get_line(0, 0),
			    "Type = RETARGET SESSION RESPONSE");
			break;
		case 0x85:
			(void) sprintf(get_line(0, 0),
			    "Type = SESSION KEEP ALIVE");
			break;
		default:
			(void) sprintf(get_line(0, 0),
			    "Type = Unknown");
			break;
		}

		(void) sprintf(get_line(0, 0), "Length = %d bytes", length);
		show_trailer();
	}

	/*
	 * SMB packets have { 0xff, 'S', 'M', 'B' }
	 * in the first four bytes.  If we find that,
	 * let snoop_smb.c have a look at it.
	 */
	if (ss->type == 0x00 &&
	    length > 0 &&
	    trailer[0] == 0xff &&
	    trailer[1] == 'S' &&
	    trailer[2] == 'M' &&
	    trailer[3] == 'B')
		interpret_smb(flags, trailer, length);
}

/*
 * NetBIOS name encoding (First Level Encoding)
 * [RFC 1001, Sec. 14.1]
 */
static void
netbiosname2ascii(char *aname, uchar_t *nbname)
{
	int c, i, j;

	i = j = 0;
	for (;;) {
		c = nbname[i++] - 'A';
		c = (c << 4) + nbname[i++] - 'A';
		/* 16th char is the "type" */
		if (i >= 32)
			break;
		if (iscntrl(c))
			c = '.';
		if (c != ' ')
			aname[j++] = c;
	}
	c &= 0xff;
	sprintf(&aname[j], "[%x]", c);
}

/*
 * Interpret the names in a Session Request packet.
 * [RFC 1002, Sec. 4.3.2]
 */
static void
interpret_netbios_names(int flags, uchar_t *data, int len, char *xtra)
{
	char  calledname[24];
	char callingname[24];
	struct callnames *names = (struct callnames *)data;

	if (len < sizeof (*names))
		return;

	netbiosname2ascii(calledname, names->calledname);
	netbiosname2ascii(callingname, names->callingname);

	if (flags & F_SUM) {
		sprintf(xtra, "Dest=%s Source=%s ", calledname, callingname);
	}

	if (flags & F_DTAIL) {
		sprintf(get_line(0, 0), "Destination = %s", calledname);
		sprintf(get_line(0, 0), "Source = %s", callingname);
	}
}
