// $Id: NVT.cc,v 1.22 2003/06/13 00:05:46 vern Exp $
//
// Copyright (c) 1999, 2000, 2001, 2002, 2003
//      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 <stdlib.h>

#include "NVT.h"
#include "NetVar.h"
#include "Event.h"

#define IS_3_BYTE_OPTION(c) (c >= 251 && c <= 254)

#define TELNET_OPT_SB 250
#define TELNET_OPT_SE 240

#define TELNET_OPT_IS 0
#define TELNET_OPT_SEND 1

#define TELNET_OPT_WILL 251
#define TELNET_OPT_WONT 252
#define TELNET_OPT_DO 253
#define TELNET_OPT_DONT 254

#define TELNET_IAC 255

TelnetOption::TelnetOption(TCP_NVT* arg_endp, unsigned int arg_code)
	{
	endp = arg_endp;
	code = arg_code;
	flags = 0;
	active = 0;
	}

void TelnetOption::RecvOption(unsigned int type)
	{
	TelnetOption* peer = endp->FindPeerOption(code);
	if ( ! peer )
		internal_error("option peer missing in TelnetOption::RecvOption");

	// WILL/WONT/DO/DONT are messages we've *received* from our peer.
	switch ( type ) {
	case TELNET_OPT_WILL:
		if ( SaidDont() || peer->SaidWont() || peer->IsActive() )
			InconsistentOption(type);

		peer->SetWill();

		if ( SaidDo() )
			peer->SetActive(1);
		break;

	case TELNET_OPT_WONT:
		if ( peer->SaidWill() && ! SaidDont() )
			InconsistentOption(type);

		peer->SetWont();

		if ( SaidDont() )
			peer->SetActive(0);
		break;

	case TELNET_OPT_DO:
		if ( SaidWont() || peer->SaidDont() || IsActive() )
			InconsistentOption(type);

		peer->SetDo();

		if ( SaidWill() )
			SetActive(1);
		break;

	case TELNET_OPT_DONT:
		if ( peer->SaidDo() && ! SaidWont() )
			InconsistentOption(type);

		peer->SetDont();

		if ( SaidWont() )
			SetActive(0);
		break;

	default:
		internal_error("bad option type in TelnetOption::RecvOption");
	}
	}

void TelnetOption::RecvSubOption(u_char* /* data */, int /* len */)
	{
	}

void TelnetOption::SetActive(int is_active)
	{
	active = is_active;
	}

void TelnetOption::InconsistentOption(unsigned int /* type */)
	{
	endp->conn->Event(inconsistent_option);
	}

void TelnetOption::BadOption()
	{
	endp->conn->Event(bad_option);
	}


void TelnetTerminalOption::RecvSubOption(u_char* data, int len)
	{
	if ( len <= 0 )
		{
		BadOption();
		return;
		}

	if ( data[0] == TELNET_OPT_SEND )
		return;

	if ( data[0] != TELNET_OPT_IS )
		{
		BadOption();
		return;
		}

	endp->SetTerminal(data + 1, len - 1);
	}


#define ENCRYPT_SET_ALGORITHM 0
#define ENCRYPT_SUPPORT_ALGORITM 1
#define ENCRYPT_REPLY 2
#define ENCRYPT_STARTING_TO_ENCRYPT 3
#define ENCRYPT_NO_LONGER_ENCRYPTING 4
#define ENCRYPT_REQUEST_START_TO_ENCRYPT 5
#define ENCRYPT_REQUEST_NO_LONGER_ENCRYPT 6
#define ENCRYPT_ENCRYPT_KEY 7
#define ENCRYPT_DECRYPT_KEY 8

void TelnetEncryptOption::RecvSubOption(u_char* data, int len)
	{
	if ( ! active )
		{
		InconsistentOption(0);
		return;
		}

	if ( len <= 0 )
		{
		BadOption();
		return;
		}

	unsigned int opt = data[0];

	if ( opt == ENCRYPT_REQUEST_START_TO_ENCRYPT )
		++did_encrypt_request;

	else if ( opt == ENCRYPT_STARTING_TO_ENCRYPT )
		{
		TelnetEncryptOption* peer =
			(TelnetEncryptOption*) endp->FindPeerOption(code);

		if ( ! peer )
			internal_error("option peer missing in TelnetEncryptOption::RecvSubOption");

		if ( peer->DidRequest() || peer->DoingEncryption() ||
		     peer->Endpoint()->AuthenticationHasBeenAccepted() )
			{
			endp->SetEncrypting(1);
			++doing_encryption;
			}
		else
			InconsistentOption(0);
		}
	}

#define HERE_IS_AUTHENTICATION 0
#define SEND_ME_AUTHENTICATION 1
#define AUTHENTICATION_STATUS 2
#define AUTHENTICATION_NAME 3

#define AUTH_REJECT 1
#define AUTH_ACCEPT 2

void TelnetAuthenticateOption::RecvSubOption(u_char* data, int len)
	{
	if ( len <= 0 )
		{
		BadOption();
		return;
		}

	switch ( data[0] ) {
	case HERE_IS_AUTHENTICATION:
		{
		TelnetAuthenticateOption* peer =
			(TelnetAuthenticateOption*) endp->FindPeerOption(code);

		if ( ! peer )
			internal_error("option peer missing in TelnetAuthenticateOption::RecvSubOption");

		if ( ! peer->DidRequestAuthentication() )
			InconsistentOption(0);
		}
		break;

	case SEND_ME_AUTHENTICATION:
		++authentication_requested;
		break;

	case AUTHENTICATION_STATUS:
		if ( len <= 1 )
			{
			BadOption();
			return;
			}

		if ( data[1] == AUTH_REJECT )
			endp->AuthenticationRejected();
		else if ( data[1] == AUTH_ACCEPT )
			endp->AuthenticationAccepted();
		else
			// Don't complain, there may be replies we don't
			// know about.
			;
		break;

	case AUTHENTICATION_NAME:
		{
		char* auth_name = new char[len];
		strncpy(auth_name, (char *) data + 1, len - 1);
		auth_name[len - 1] = '\0';
		endp->SetAuthName(auth_name);
		}
		break;

	default:
		BadOption();
	}
	}

#define ENVIRON_IS 0
#define ENVIRON_SEND 1
#define ENVIRON_INFO 2

#define ENVIRON_VAR 0
#define ENVIRON_VAL 1
#define ENVIRON_ESC 2
#define ENVIRON_USERVAR 3

void TelnetEnvironmentOption::RecvSubOption(u_char* data, int len)
	{
	if ( len <= 0 )
		{
		BadOption();
		return;
		}

	if ( data[0] == ENVIRON_SEND )
		//### We should track the dialog and make sure both sides agree.
		return;

	if ( data[0] != ENVIRON_IS && data[0] != ENVIRON_INFO )
		{
		BadOption();
		return;
		}

	--len;	// Discard code.
	++data;

	while ( len > 0 )
		{
		int code1, code2;
		char* var_name = ExtractEnv(data, len, code1);
		char* var_val = ExtractEnv(data, len, code2);

		if ( ! var_name || ! var_val ||
		     (code1 != ENVIRON_VAR && code1 != ENVIRON_USERVAR) ||
		     code2 != ENVIRON_VAL )
			{
			BadOption();
			break;
			}

		if ( var_name && var_val )
			endp->conn->SetEnv(endp, var_name, var_val);
		else
			{ // One of var_name/var_val might be set; avoid leak.
			delete var_name;
			delete var_val;
			}
		}
	}

char* TelnetEnvironmentOption::ExtractEnv(u_char*& data, int& len, int& code)
	{
	code = data[0];

	if ( code != ENVIRON_VAR && code != ENVIRON_VAL &&
	     code != ENVIRON_USERVAR )
		return 0;

	// Move past code.
	--len;
	++data;

	// Find the end of this piece of the option.
	u_char* data_end = data + len;
	u_char* d;
	for ( d = data; d < data_end; ++d )
		{
		if ( *d == ENVIRON_VAR || *d == ENVIRON_VAL || *d == ENVIRON_USERVAR )
			break;

		if ( *d == ENVIRON_ESC )
			{
			++d;	// move past ESC
			if ( d >= data_end )
				return 0;
			break;
			}
		}

	int size = d - data;
	char* env = new char[size+1];

	// Now copy into env.
	int d_ind = 0;
	int i;
	for ( i = 0; i < size; ++i )
		{
		if ( data[d_ind] == ENVIRON_ESC )
			++d_ind;

		env[i] = data[d_ind];
		++d_ind;
		}

	env[i] = '\0';

	data = d;
	len -= size;

	return env;
	}

void TelnetBinaryOption::SetActive(int is_active)
	{
	endp->SetBinaryMode(is_active);
	active = is_active;
	}

void TelnetBinaryOption::InconsistentOption(unsigned int /* type */)
	{
	// I don't know why, but this gets turned on redundantly -
	// doesn't do any harm, so ignore it.  Example is
	// in ex/redund-binary-opt.trace.
	}


TCP_NVT::TCP_NVT(TCP_Connection* arg_conn, int is_orig,
			int arg_is_NUL_sensitive, int arg_skip_partial,
		        int CRLF_as_EOL)
: TCP_EndpointLine(arg_conn, is_orig, arg_is_NUL_sensitive,
		   arg_skip_partial, CRLF_as_EOL)
	{
	is_TCP_NVT = 1;
	is_suboption = last_was_IAC = pending_IAC = 0;
	IAC_pos = 0;
	num_options = 0;
	authentication_has_been_accepted = encrypting_mode = binary_mode = 0;
	auth_name = 0;
	}

TCP_NVT::~TCP_NVT()
	{
	for ( int i = 0; i < num_options; ++i )
		delete options[i];

	delete auth_name;
	}

TelnetOption* TCP_NVT::FindOption(unsigned int code)
	{
	if ( ! peer->AsNVT() )
		// We can't do option processing if our peer doesn't
		// keep track of connection contents, as then we can't
		// see what's ack'd and what isn't.
		return 0;

	int i;
	for ( i = 0; i < num_options; ++i )
		if ( options[i]->Code() == code )
			return options[i];

	TelnetOption* opt = 0;
	if ( i < NUM_TELNET_OPTIONS )
		{ // Maybe we haven't created this option yet.
		switch ( code ) {
		case TELNET_OPTION_BINARY:
			opt = new TelnetBinaryOption(this);
			break;

		case TELNET_OPTION_TERMINAL:
			opt = new TelnetTerminalOption(this);
			break;

		case TELNET_OPTION_ENCRYPT:
			opt = new TelnetEncryptOption(this);
			break;

		case TELNET_OPTION_AUTHENTICATE:
			opt = new TelnetAuthenticateOption(this);
			break;

		case TELNET_OPTION_ENVIRON:
			opt = new TelnetEnvironmentOption(this);
			break;
		}
		}

	if ( opt )
		options[num_options++] = opt;

	return opt;
	}

TelnetOption* TCP_NVT::FindPeerOption(unsigned int code)
	{
	if ( peer->AsNVT() )
		return peer->AsNVT()->FindOption(code);
	else
		return 0;
	}

void TCP_NVT::AuthenticationAccepted()
	{
	authentication_has_been_accepted = 1;
	conn->Event(authentication_accepted, PeerAuthName());
	}

void TCP_NVT::AuthenticationRejected()
	{
	authentication_has_been_accepted = 0;
	conn->Event(authentication_rejected, PeerAuthName());
	}

const char* TCP_NVT::PeerAuthName() const
	{
	const char* p_auth_name = ((TCP_NVT*) peer)->AuthName();
	return p_auth_name ? p_auth_name : "<unknown>";
	}


void TCP_NVT::SetTerminal(const u_char* terminal, int len)
	{
	if ( login_terminal )
		{
		val_list* vl = new val_list;
		vl->append(conn->BuildConnVal());
		vl->append(new StringVal(new BroString(terminal, len, 0)));

		conn->ConnectionEvent(login_terminal, vl);
		}
	}

void TCP_NVT::SetEncrypting(int mode)
	{
	skip_deliveries = encrypting_mode = mode;
	if ( mode )
		conn->Event(activating_encryption);
	}

#define MAX_DELIVER_UNIT 128

void TCP_NVT::DoDeliver(double t, int seq, int len, u_char* data)
	{
	// This code is very similar to that for TCP_EndpointLine.  We
	// don't virtualize out the differences because some of them
	// would require per-character function calls, too expensive.
	if ( pending_IAC )
		{
		ScanOption(t, seq, len, data);
		return;
		}

	// Add data up to IAC or end.
	for ( ; len > 0; --len, ++data )
		{
		if ( offset >= buf_len )
			Init(buf_len * 2);

		int c = data[0];

		if ( binary_mode && c != TELNET_IAC )
			c &= 0x7f;

#define EMIT_LINE \
	{ \
	buf[offset] = '\0'; \
	conn->NewLine(this, offset, buf, 0); \
	offset = 0; \
	}

		switch ( c ) {
		case '\r':
			if ( CR_LF_as_EOL & CR_as_EOL )
				EMIT_LINE
			else
				buf[offset++] = c;
			break;

		case '\n':
			if ( last_char == '\r' )
				{
				--offset; // remove '\r'
				EMIT_LINE
				}
			else if ( CR_LF_as_EOL & LF_as_EOL )
				EMIT_LINE
			else
				{
				if ( conn->FlagEvent(SINGULAR_LF) )
					conn->Weird("line_terminated_with_single_LF");
				buf[offset++] = c;
				}
			break;

		case '\0':
			if ( last_char == '\r' )
				// Allow a NUL just after a \r - Solaris
				// Telnet servers generate these, and they
				// appear harmless.
				;

			else if ( flag_NULs )
				CheckNUL();

			else
				buf[offset++] = c;
			break;

		case TELNET_IAC:
			pending_IAC = 1;
			IAC_pos = offset;
			is_suboption = 0;
			buf[offset++] = c;
			ScanOption(t, seq, len - 1, data + 1);
			return;

		default:
			buf[offset++] = c;
			break;
		}

		if ( ! (CR_LF_as_EOL & CR_as_EOL) && 
		     last_char == '\r' && c != '\n' && c != '\0' )
			{
			if ( conn->FlagEvent(SINGULAR_CR) )
				conn->Weird("line_terminated_with_single_CR");
			}

		last_char = c;
		}
	}

void TCP_NVT::ScanOption(double t, int seq, int len, u_char* data)
	{
	if ( len <= 0 )
		return;

	if ( IAC_pos == offset - 1 )
		{ // All we've seen so far is the IAC.
		unsigned int code = data[0];

		if ( code == TELNET_IAC )
			{
			// An escaped 255, throw away the second
			// instance and drop the IAC state.
			pending_IAC = 0;
			last_char = code;
			}

		else if ( code == TELNET_OPT_SB )
			{
			is_suboption = 1;
			last_was_IAC = 0;
			buf[offset++] = code;
			}

		else if ( IS_3_BYTE_OPTION(code) )
			{
			is_suboption = 0;
			buf[offset++] = code;
			}

		else
			{
			// We've got the whole 2-byte option.
			SawOption(code);

			// Throw it and the IAC away.
			--offset;
			pending_IAC = 0;
			}

		// Recurse to munch on the remainder.
		Deliver(t, seq, len - 1, data + 1);
		return;
		}

	if ( ! is_suboption )
		{
		// We now have the full 3-byte option.
		SawOption(u_char(buf[offset-1]), data[0]);

		// Delete the option.
		offset -= 2;	// code + IAC
		pending_IAC = 0;

		Deliver(t, seq, len - 1, data + 1);
		return;
		}

	// A suboption.  Spin looking for end.
	for ( ; len > 0; --len, ++data )
		{
		unsigned int code = data[0];

		if ( last_was_IAC )
			{
			last_was_IAC = 0;

			if ( code == TELNET_IAC )
				{
				// This is an escaped IAC, eat
				// the second copy.
				continue;
				}

			if ( code != TELNET_OPT_SE )
				// BSD Telnet treats this case as terminating
				// the suboption, so that's what we do here
				// too.  Below we make sure to munch on the
				// new IAC.
				BadOptionTermination(code);

			int opt_start = IAC_pos + 2;
			int opt_stop = offset - 1;
			int opt_len = opt_stop - opt_start;
			SawSubOption(&buf[opt_start], opt_len);

			// Delete suboption.
			offset = IAC_pos;
			pending_IAC = is_suboption = 0;

			if ( code == TELNET_OPT_SE )
				Deliver(t, seq, len - 1, data + 1);
			else
				{
				// Munch on the new (broken) option.
				pending_IAC = 1;
				IAC_pos = offset;
				buf[offset++] = TELNET_IAC;
				Deliver(t, seq, len, data);
				}
			return;
			}

		else
			{
			buf[offset++] = code;
			last_was_IAC = (code == TELNET_IAC);
			}
		}
	}

void TCP_NVT::SawOption(unsigned int /* code */)
	{
	}

void TCP_NVT::SawOption(unsigned int code, unsigned int subcode)
	{
	TelnetOption* opt = FindOption(subcode);
	if ( opt )
		opt->RecvOption(code);
	}

void TCP_NVT::SawSubOption(const char* subopt, int len)
	{
	unsigned int subcode = u_char(subopt[0]);

	++subopt;
	--len;

	TelnetOption* opt = FindOption(subcode);
	if ( opt )
		opt->RecvSubOption((u_char*) subopt, len);
	}

void TCP_NVT::BadOptionTermination(unsigned int /* code */)
	{
	conn->Event(bad_option_termination);
	}
