// $Id: NetbiosSSN.cc,v 1.14 2003/08/31 00:38:11 vern Exp $
//
// Copyright (c) 2001, 2002
//      The Regents of the University of California.  All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that: (1) source code distributions
// retain the above copyright notice and this paragraph in its entirety, (2)
// distributions including binary code include the above copyright notice and
// this paragraph in its entirety in the documentation or other materials
// provided with the distribution, and (3) all advertising materials mentioning
// features or use of this software display the following acknowledgement:
// ``This product includes software developed by the University of California,
// Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
// the University nor the names of its contributors may be used to endorse
// or promote products derived from this software without specific prior
// written permission.
// THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

#include "config.h"

#include <ctype.h>

#include "NetVar.h"
#include "NetbiosSSN.h"
#include "Sessions.h"
#include "Event.h"

double netbios_ssn_session_timeout = 15.0;

NetbiosSSN_RawMsgHdr::NetbiosSSN_RawMsgHdr(const u_char*& data, int& len)
	{
	type = *data;
	++data, --len;

	flags = *data;
	++data, --len;

	length = *data;
	++data, --len;

	length <<= 8;
	length |= *data;
	++data, --len;
	}


NetbiosSSN_Interpreter::NetbiosSSN_Interpreter(Connection* arg_conn)
	{
	conn = arg_conn;
	}

int NetbiosSSN_Interpreter::ParseMessage(unsigned int type, unsigned int flags,
				const u_char* data, int len, int is_query)
	{
	switch ( type ) {
	case NETBIOS_SSN_MSG:
		return ParseSessionMsg(data, len, is_query);

	case NETBIOS_SSN_REQ:
		return ParseSessionReq(data, len, is_query);

	case NETBIOS_SSN_POS_RESP:
		return ParseSessionPosResp(data, len, is_query);

	case NETBIOS_SSN_NEG_RESP:
		return ParseSessionNegResp(data, len, is_query);

	case NETBIOS_SSN_RETARG_RESP:
		return ParseRetArgResp(data, len, is_query);

	case NETBIOS_SSN_KEEP_ALIVE:
		return ParseKeepAlive(data, len, is_query);

	default:
		conn->Weird("unknown_netbios_type");
		return 1;
	}
	}

int NetbiosSSN_Interpreter::ParseMessage(const u_char* data, int len, int is_query)
	{
	NetbiosSSN_RawMsgHdr hdr(data, len);

	if ( hdr.length > unsigned(len) )
		conn->Weird("excess_netbios_hdr_len");

	else if ( hdr.length < unsigned(len) )
		{
		conn->Weird("deficit_netbios_hdr_len");
		len = hdr.length;
		}

	return ParseMessage(hdr.type, hdr.flags, data, len, is_query);
	}

int NetbiosSSN_Interpreter::ParseSessionMsg(const u_char* data, int len,
						int is_query)
	{
	if ( len < 4 || strncmp((const char*) data, "\xffSMB", 4) )
		{
		// This should be an event, too.
		conn->Weird("netbios_raw_session_msg");
		return 0;
		}

	else
		{
		data += 4;
		len -= 4;
		return ParseSambaMsg(data, len, is_query);
		}
	}

int NetbiosSSN_Interpreter::ParseSambaMsg(const u_char* data, int len,
						int is_query)
	{
	u_char command = data[0];
	int is_request = data[5] & 0x80;

	Val cmd_val(command, TYPE_COUNT);
	Val* cmd_name = samba_cmds->Lookup(&cmd_val);

	printf("SMB %s (%d) 0x%x (%d/%d)\n", cmd_name->AsString()->Bytes(), len, command, is_query, is_request == 0);

	return 0;
	}

int NetbiosSSN_Interpreter::ConvertName(const u_char* name, int name_len,
					u_char*& xname, int& xlen)
	{
	// Taken from tcpdump's smbutil.c.

	xname = 0;

	if ( name_len < 1 )
		return 0;

	int len = (*name++) / 2;
	xlen = len;

	if ( len > 30 || len < 1 || name_len < len )
		return 0;

	u_char* convert_name = new u_char[len + 1];
	*convert_name = 0;
	xname = convert_name;

	while ( len-- )
		{
		if ( name[0] < 'A' || name[0] > 'P' ||
		     name[1] < 'A' || name[1] > 'P' )
			{
			*convert_name = 0;
			return 0;
			}

		*convert_name = ((name[0] - 'A') << 4) + (name[1] - 'A');
		name += 2;
		++convert_name;
		}

	*convert_name = 0;

	return 1;
	}

int NetbiosSSN_Interpreter::ParseSessionReq(const u_char* data, int len,
						int is_query)
	{
	if ( ! is_query )
		conn->Weird("netbios_server_session_request");

	u_char* xname;
	int xlen;

	if ( ConvertName(data, len, xname, xlen) )
		Event(netbios_session_request, xname, xlen);

	delete xname;

	return 0;
	}

int NetbiosSSN_Interpreter::ParseSessionPosResp(const u_char* data, int len,
						int is_query)
	{
	if ( is_query )
		conn->Weird("netbios_client_session_reply");

	Event(netbios_session_accepted, data, len);

	return 0;
	}

int NetbiosSSN_Interpreter::ParseSessionNegResp(const u_char* data, int len,
						int is_query)
	{
	if ( is_query )
		conn->Weird("netbios_client_session_reply");

	Event(netbios_session_rejected, data, len);

#if 0
	case 0x80:
		printf("Not listening on called name\n");
		break;
	case 0x81:
		printf("Not listening for calling name\n");
		break;
	case 0x82:
		printf("Called name not present\n");
		break;
	case 0x83:
		printf("Called name present, but insufficient resources\n");
		break;
	default:
		printf("Unspecified error 0x%X\n",ecode);
		break;
#endif

	return 0;
	}

int NetbiosSSN_Interpreter::ParseRetArgResp(const u_char* data, int len,
						int is_query)
	{
	if ( is_query )
		conn->Weird("netbios_client_session_reply");

	Event(netbios_session_ret_arg_resp, data, len);

	return 0;
	}

int NetbiosSSN_Interpreter::ParseKeepAlive(const u_char* data, int len,
						int is_query)
	{
	Event(netbios_session_keepalive, data, len);

	return 0;
	}

void NetbiosSSN_Interpreter::Event(EventHandlerPtr event, const u_char* data, int len)
	{
	if ( ! event )
		return;

	val_list* vl = new val_list;
	vl->append(conn->BuildConnVal());
	vl->append(new StringVal(new BroString(data, len, 0)));

	conn->ConnectionEvent(event, vl);
	}


TCP_Endpoint_NetbiosSSN::TCP_Endpoint_NetbiosSSN(NetbiosSSN_Interpreter* arg_interp, int arg_is_orig)
: TCP_EndpointContents(arg_interp->TCP_Conn(), arg_is_orig)
	{
	interp = arg_interp;
	type = flags = msg_size = 0;
	is_orig = arg_is_orig;
	msg_buf = 0;
	buf_n = msg_size = 0;
	state = NETBIOS_SSN_TYPE;
	}

TCP_Endpoint_NetbiosSSN::~TCP_Endpoint_NetbiosSSN()
	{
	delete msg_buf;
	}

void TCP_Endpoint_NetbiosSSN::Flush()
	{
	if ( buf_n > 0 )
		{ // Deliver partial message.
		interp->ParseMessage(type, flags, msg_buf, buf_n, is_orig);
		msg_size = 0;
		}
	}

void TCP_Endpoint_NetbiosSSN::Deliver(double t, int seq, int len, u_char* data)
	{
	if ( state == NETBIOS_SSN_TYPE )
		{
		type = *data;
		state = NETBIOS_SSN_FLAGS;

		++data;
		--len;

		if ( len == 0 )
			return;
		}

	if ( state == NETBIOS_SSN_FLAGS )
		{
		flags = *data;
		state = NETBIOS_SSN_LEN_HI;

		++data;
		--len;

		if ( len == 0 )
			return;
		}

	if ( state == NETBIOS_SSN_LEN_HI )
		{
		msg_size = (*data) << 8;
		state = NETBIOS_SSN_LEN_LO;

		++data;
		--len;

		if ( len == 0 )
			return;
		}

	if ( state == NETBIOS_SSN_LEN_LO )
		{
		msg_size += *data;
		state = NETBIOS_SSN_BUF;

		buf_n = 0;

		if ( msg_buf )
			{
			if ( buf_len < msg_size )
				{
				buf_len = msg_size;
				msg_buf = (u_char*) safe_realloc((void*) msg_buf, buf_len);
				}
			}
		else
			{
			buf_len = msg_size;
			msg_buf = (u_char*) safe_malloc(buf_len);
			}

		++data;
		--len;

		if ( len == 0 )
			return;
		}

	if ( state != NETBIOS_SSN_BUF )
		conn->Internal("state inconsistency in TCP_Endpoint_NetbiosSSN::Deliver");

	int n;
	for ( n = 0; buf_n < msg_size && n < len; ++n )
		msg_buf[buf_n++] = data[n];

	if ( buf_n < msg_size )
		// Haven't filled up the message buffer yet, no more to do.
		return;

	(void) interp->ParseMessage(type, flags, msg_buf, msg_size, is_orig);
	buf_n = 0;

	state = NETBIOS_SSN_TYPE;

	if ( n < len )
		// More data to munch on.
		Deliver(t, seq, len - n, data + n);
	}

TCP_NetbiosSSN::TCP_NetbiosSSN(NetSessions* s, HashKey* k, double t,
		const ConnID* id, const struct tcphdr* tp)
: TCP_Connection(s, k, t, id, tp)
	{
	interp = new NetbiosSSN_Interpreter(this);
	}

void TCP_NetbiosSSN::BuildEndpoints()
	{
	orig = new TCP_Endpoint_NetbiosSSN(interp, 1);
	resp = new TCP_Endpoint_NetbiosSSN(interp, 0);
	}

TCP_NetbiosSSN::~TCP_NetbiosSSN()
	{
	delete interp;
	}

void TCP_NetbiosSSN::Done()
	{
	interp->Timeout();
	TCP_Connection::Done();
	}

void TCP_NetbiosSSN::ConnectionClosed(TCP_Endpoint* endpoint,
				TCP_Endpoint* peer, int gen_event)
	{
	((TCP_Endpoint_NetbiosSSN*) orig)->Flush();
	((TCP_Endpoint_NetbiosSSN*) resp)->Flush();

	TCP_Connection::ConnectionClosed(endpoint, peer, gen_event);
	}

UDP_NetbiosSSN::UDP_NetbiosSSN(NetSessions* s, HashKey* k, double t,
		const ConnID* id, const struct udphdr* up)
: UDP_Connection(s, k, t, id, up)
	{
	interp = new NetbiosSSN_Interpreter(this);
	did_session_done = 0;
	ADD_TIMER(&UDP_NetbiosSSN::ExpireTimer, t + netbios_ssn_session_timeout, 1);
	}

UDP_NetbiosSSN::~UDP_NetbiosSSN()
	{
	delete interp;
	}

void UDP_NetbiosSSN::Done()
	{
	if ( ! did_session_done )
		Event(udp_session_done);

	UDP_Connection::Done();
	}

int UDP_NetbiosSSN::Request(double /* t */, const u_char* data, int len)
	{
	return interp->ParseMessage(data, len, 1);
	}

int UDP_NetbiosSSN::Reply(double /* t */, const u_char* data, int len)
	{
	return interp->ParseMessage(data, len, 0);
	}

void UDP_NetbiosSSN::ExpireTimer(double t)
	{
	// The - 1.0 in the following is to allow 1 second for the
	// common case of a single request followed by a single reply,
	// so we don't needlessly set the timer twice in that case.
	if ( t - last_time >= netbios_ssn_session_timeout - 1.0 )
		{
		Event(connection_timeout);
		sessions->Remove(this, 1);
		}
	else
		ADD_TIMER(&UDP_NetbiosSSN::ExpireTimer, t + netbios_ssn_session_timeout, 1);
	}

int UDP_NetbiosSSN::IsReuse(double /* t */, const u_char* /* pkt */)
	{
	return did_session_done;
	}
