// $Id: Ident.cc,v 1.20 2002/10/26 04:15:44 vern Exp $
//
// Copyright (c) 1999, 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 "Ident.h"
#include "Event.h"
#include "TCP_Rewriter.h"

IdentConn::IdentConn(NetSessions* s, HashKey* k, double t, const ConnID* id,
		const struct tcphdr* tp)
: TCP_Connection(s, k, t, id, tp)
	{
	did_bad_reply = did_deliver = 0;
	}

void IdentConn::BuildEndpoints()
	{
	orig = new TCP_EndpointLine(this, 1, 0, 1);
	resp = new TCP_EndpointLine(this, 0, 0, 1);
	}

void IdentConn::Done()
	{
	if ( (! did_deliver || orig->AsEndpointLine()->HasPartialLine()) &&
	     (orig->state == TCP_CLOSED || orig->prev_state == TCP_CLOSED) &&
	     (orig->prev_state != TCP_PARTIAL && resp->prev_state != TCP_PARTIAL) &&
	     (orig->prev_state != TCP_INACTIVE && resp->prev_state != TCP_INACTIVE) )
		Weird("partial_ident_request");

	TCP_Connection::Done();
	}

void IdentConn::NewLine(TCP_Endpoint* s, int length, const char* line, int /* is_const */)
	{
	int remote_port, local_port;
	const char* orig_line = line;
	const char* end_of_line = line + length;

	if ( s == orig )
		{
		if ( ! ident_request )
			return;

		line = ParsePair(line, end_of_line, remote_port, local_port);
		if ( ! line )
			{
			if ( s->state == TCP_CLOSED &&
			     (s->prev_state == TCP_INACTIVE ||
			      s->prev_state == TCP_PARTIAL) )
				// not surprising the request is mangled.
				return;

			BadRequest(length, orig_line);
			return;
			}

		if ( line != end_of_line )
			Weird("ident_request_addendum", length, orig_line);

		val_list* vl = new val_list;
		vl->append(BuildConnVal());
		vl->append(new PortVal(local_port, 1));
		vl->append(new PortVal(remote_port, 1));

		ConnectionEvent(ident_request, vl);

		did_deliver = 1;
		}

	else
		{
		if ( ! ident_reply )
			return;

		line = ParsePair(line, end_of_line, remote_port, local_port);

		if ( ! line || line == end_of_line || line[0] != ':' )
			{
			if ( s->state == TCP_CLOSED &&
			     (s->prev_state == TCP_INACTIVE ||
			      s->prev_state == TCP_PARTIAL) )
				// not surprising the request is mangled.
				return;

			BadReply(length, orig_line);
			return;
			}

		line = skip_whitespace(line + 1, end_of_line);
		int restlen = end_of_line - line;

		int is_error;
		if ( restlen >= 5 && ! strncmp(line, "ERROR", 5) )
			{
			is_error = 1;
			line += 5;
			}
		else if ( restlen >= 6 && ! strncmp(line, "USERID", 6) )
			{
			is_error = 0;
			line += 6;
			}
		else
			{
			BadReply(length, orig_line);
			return;
			}

		line = skip_whitespace(line, end_of_line);

		if ( line >= end_of_line || line[0] != ':' )
			{
			BadReply(length, orig_line);
			return;
			}

		line = skip_whitespace(line + 1, end_of_line);

		if ( is_error )
			{
			val_list* vl = new val_list;
			vl->append(BuildConnVal());
			vl->append(new PortVal(local_port, 1));
			vl->append(new PortVal(remote_port, 1));
			vl->append(new StringVal(end_of_line - line, line));

			ConnectionEvent(ident_error, vl);
			}

		else
			{
			const char* sys_type = line;
			const char* colon = strchr_n(line, end_of_line, ':');
			const char* comma = strchr_n(line, end_of_line, ',');
			if ( ! colon )
				{
				BadReply(length, orig_line);
				return;
				}

			const char* sys_end = (comma && comma < colon) ?
						comma : colon;

			while ( --sys_end > sys_type && isspace(*sys_end) )
				;

			BroString* sys_type_s =
				new BroString((const u_char*) sys_type,
						sys_end - sys_type + 1, 1);

			line = skip_whitespace(colon + 1, end_of_line);

			val_list* vl = new val_list;
			vl->append(BuildConnVal());
			vl->append(new PortVal(local_port, 1));
			vl->append(new PortVal(remote_port, 1));
			vl->append(new StringVal(end_of_line - line, line));
			vl->append(new StringVal(sys_type_s));

			ConnectionEvent(ident_reply, vl);
			}
		}
	}

const char* IdentConn::ParsePair(const char* line, const char* end_of_line, int & p1, int& p2)
	{
	line = ParseInt(line, end_of_line, p1);
	if ( ! line )
		{
		return 0;
		}

	if ( line >= end_of_line || line[0] != ',' )
		return 0;

	++line;

	line = ParseInt(line, end_of_line, p2);
	if ( ! line )
		return 0;

	return line;
	}

const char* IdentConn::ParseInt(const char* line, const char* end_of_line, int& pn)
	{
	int n = 0;

	line = skip_whitespace(line, end_of_line);
	if ( ! isdigit(*line) )
		return 0;

	do
		{
		n = n * 10 + (*line - '0');
		++line;
		}
	while ( isdigit(*line) );

	line = skip_whitespace(line, end_of_line);
	pn = n;

	return line;
	}

void IdentConn::BadRequest(int length, const char* line)
	{
	Weird("bad_ident_request", length, line);
	}

void IdentConn::BadReply(int length, const char* line)
	{
	if ( ! did_bad_reply )
		{
		Weird("bad_ident_reply", length, line);
		did_bad_reply = 1;
		}
	}

#include "ident-rw.bif.func_def"

