// $Id: Portmap.cc,v 1.12 2003/08/31 00:38:11 vern Exp $
//
// Copyright (c) 1996, 1997, 1998, 1999, 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 "NetVar.h"
#include "XDR.h"
#include "Portmap.h"
#include "Event.h"

#define PMAPPROC_NULL 0
#define PMAPPROC_SET 1
#define PMAPPROC_UNSET 2
#define PMAPPROC_GETPORT 3
#define PMAPPROC_DUMP 4
#define PMAPPROC_CALLIT 5

int PortmapperInterp::RPC_BuildCall(RPC_CallInfo* c, const u_char*& buf, int& n)
	{
	if ( c->Program() != 100000 )
		Weird("bad_RPC_program");

	switch ( c->Proc() ) {
	case PMAPPROC_NULL:
		break;

	case PMAPPROC_SET:
		{
		Val* m = ExtractMapping(buf, n);
		if ( ! m )
			return 0;
		c->AddVal(m);
		}
		break;

	case PMAPPROC_UNSET:
		{
		Val* m = ExtractMapping(buf, n);
		if ( ! m )
			return 0;
		c->AddVal(m);
		}
		break;

	case PMAPPROC_GETPORT:
		{
		Val* pr = ExtractPortRequest(buf, n);
		if ( ! pr )
			return 0;
		c->AddVal(pr);
		}
		break;

	case PMAPPROC_DUMP:
		break;

	case PMAPPROC_CALLIT:
		{
		Val* call_it = ExtractCallItRequest(buf, n);
		if ( ! call_it )
			return 0;
		c->AddVal(call_it);
		}
		break;

	default:
		return 0;
	}

	return 1;
	}

int PortmapperInterp::RPC_BuildReply(const RPC_CallInfo* c, int success,
					const u_char*& buf, int& n,
					EventHandlerPtr& event, Val*& reply)
	{
	reply = 0;

	switch ( c->Proc() ) {
	case PMAPPROC_NULL:
		event = success ? pm_request_null : pm_attempt_null;
		break;

	case PMAPPROC_SET:
		if ( success )
			{
			uint32 status = extract_XDR_uint32(buf, n);
			if ( ! buf )
				return 0;

			reply = new Val(status, TYPE_BOOL);
			event = pm_request_set;
			}
		else
			event = pm_attempt_set;

		break;

	case PMAPPROC_UNSET:
		if ( success )
			{
			uint32 status = extract_XDR_uint32(buf, n);
			if ( ! buf )
				return 0;

			reply = new Val(status, TYPE_BOOL);
			event = pm_request_unset;
			}
		else
			event = pm_attempt_unset;

		break;

	case PMAPPROC_GETPORT:
		if ( success )
			{
			uint32 port = extract_XDR_uint32(buf, n);
			if ( ! buf )
				return 0;

			RecordVal* rv = c->RequestVal()->AsRecordVal();
			Val* is_tcp = rv->Lookup(2);
			reply = new PortVal(CheckPort(port), is_tcp->IsOne());
			event = pm_request_getport;
			}
		else
			event = pm_attempt_getport;
		break;

	case PMAPPROC_DUMP:
		event = success ? pm_request_dump : pm_attempt_dump;
		if ( success )
			{
			TableVal* mappings = new TableVal(pm_mappings);
			uint32 nmap = 0;

			// Each call in the loop test pulls the next "opted"
			// element to see if there are more mappings.
			while ( extract_XDR_uint32(buf, n) && buf )
				{
				Val* m = ExtractMapping(buf, n);
				if ( ! m )
					break;

				Val* index = new Val(++nmap, TYPE_COUNT);
				mappings->Assign(index, m);
				Unref(index);
				}

			if ( ! buf )
				{
				Unref(mappings);
				return 0;
				}

			reply = mappings;
			event = pm_request_dump;
			}
		else
			event = pm_attempt_dump;
		break;

	case PMAPPROC_CALLIT:
		if ( success )
			{
			uint32 port = extract_XDR_uint32(buf, n);
			int reply_n;
			const u_char* opaque_reply =
				extract_XDR_opaque(buf, n, reply_n);
			if ( ! opaque_reply )
				return 0;

			reply = new PortVal(CheckPort(port), 0);
			event = pm_request_callit;
			}
		else
			event = pm_attempt_callit;
		break;

	default:
		return 0;
	}

	return 1;
	}

Val* PortmapperInterp::ExtractMapping(const u_char*& buf, int& len)
	{
	RecordVal* mapping = new RecordVal(pm_mapping);

	mapping->Assign(0, new Val(extract_XDR_uint32(buf, len), TYPE_COUNT));
	mapping->Assign(1, new Val(extract_XDR_uint32(buf, len), TYPE_COUNT));

	int is_tcp = extract_XDR_uint32(buf, len) == IPPROTO_TCP;
	uint32 port = extract_XDR_uint32(buf, len);
	mapping->Assign(2, new PortVal(CheckPort(port), is_tcp));

	if ( ! buf )
		{
		Unref(mapping);
		return 0;
		}

	return mapping;
	}

Val* PortmapperInterp::ExtractPortRequest(const u_char*& buf, int& len)
	{
	RecordVal* pr = new RecordVal(pm_port_request);

	pr->Assign(0, new Val(extract_XDR_uint32(buf, len), TYPE_COUNT));
	pr->Assign(1, new Val(extract_XDR_uint32(buf, len), TYPE_COUNT));

	int is_tcp = extract_XDR_uint32(buf, len) == IPPROTO_TCP;
	pr->Assign(2, new Val(is_tcp, TYPE_BOOL));
	(void) extract_XDR_uint32(buf, len);	// consume the bogus port

	if ( ! buf )
		{
		Unref(pr);
		return 0;
		}

	return pr;
	}

Val* PortmapperInterp::ExtractCallItRequest(const u_char*& buf, int& len)
	{
	RecordVal* c = new RecordVal(pm_callit_request);

	c->Assign(0, new Val(extract_XDR_uint32(buf, len), TYPE_COUNT));
	c->Assign(1, new Val(extract_XDR_uint32(buf, len), TYPE_COUNT));
	c->Assign(2, new Val(extract_XDR_uint32(buf, len), TYPE_COUNT));

	int arg_n;
	(void) extract_XDR_opaque(buf, len, arg_n);
	c->Assign(3, new Val(arg_n, TYPE_COUNT));

	if ( ! buf )
		{
		Unref(c);
		return 0;
		}

	return c;
	}

uint32 PortmapperInterp::CheckPort(uint32 port)
	{
	if ( port >= 65536 )
		{
		if ( pm_bad_port )
			{
			val_list* vl = new val_list;
			vl->append(conn->BuildConnVal());
			vl->append(new Val(port, TYPE_COUNT));
			conn->ConnectionEvent(pm_bad_port, vl);
			}

		port = 0;
		}

	return port;
	}

void PortmapperInterp::Event(EventHandlerPtr f, Val* request, int status, Val* reply)
	{
	if ( ! f )
		{
		Unref(request);
		Unref(reply);
		return;
		}

	val_list* vl = new val_list;

	vl->append(conn->BuildConnVal());
	if ( status == RPC_SUCCESS )
		{
		if ( request )
			vl->append(request);
		if ( reply )
			vl->append(reply);
		}
	else
		{
		vl->append(new Val(status, TYPE_COUNT));
		if ( request )
			vl->append(request);
		}

	conn->ConnectionEvent(f, vl);
	}

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

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

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

void PortmapperConn::Done()
	{
	if ( ((TCP_Endpoint_RPC*) orig)->State() != RPC_COMPLETE &&
	     (orig->state == TCP_CLOSED || orig->prev_state == TCP_CLOSED) &&
	     // Sometimes things like tcpwrappers will immediately close
	     // the connection, without any data having been transferred.
	     // Don't bother flagging these.
	     orig->Size() > 0 )
		Weird("partial_portmapper_request");

	interp->Timeout();

	TCP_Connection::Done();
	}
