/* 
   Unix SMB/CIFS implementation.

   endpoint server for the epmapper pipe

   Copyright (C) Andrew Tridgell 2003
   Copyright (C) Jelmer Vernooij 2004
   
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.
   
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
   
   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include "includes.h"
#include "librpc/gen_ndr/ndr_epmapper.h"
#include "rpc_server/dcerpc_server.h"
#include "rpc_server/common/common.h"

typedef uint32_t error_status_t;

/* handle types for this module */
enum handle_types {HTYPE_LOOKUP};

/* a endpoint combined with an interface description */
struct dcesrv_ep_iface {
	const char *name;
	struct epm_tower ep;
};

/*
  simple routine to compare a GUID string to a GUID structure
*/
static int guid_cmp(TALLOC_CTX *mem_ctx, const struct GUID *guid, const char *uuid_str)
{
	const char *s = GUID_string(mem_ctx, guid);
	if (!s || strcasecmp(s, uuid_str)) {
		return -1;
	}
	return 0;
}

/*
  build a list of all interfaces handled by all endpoint servers
*/
static uint32_t build_ep_list(TALLOC_CTX *mem_ctx,
			      struct dcesrv_endpoint *endpoint_list,
			      struct dcesrv_ep_iface **eps)
{
	struct dcesrv_endpoint *d;
	uint32_t total = 0;
	NTSTATUS status;

	*eps = NULL;

	for (d=endpoint_list; d; d=d->next) {
		struct dcesrv_if_list *iface;
		struct dcerpc_binding description;

		for (iface=d->interface_list;iface;iface=iface->next) {
			(*eps) = talloc_realloc_p(mem_ctx, 
						  *eps, 
						  struct dcesrv_ep_iface,
						  total + 1);
			if (!*eps) {
				return 0;
			}
			(*eps)[total].name = iface->iface.name;

			description = d->ep_description;
			GUID_from_string(iface->iface.uuid, &description.object);
			description.object_version = iface->iface.if_version;

			status = dcerpc_binding_build_tower(mem_ctx, &description, &(*eps)[total].ep);
			if (NT_STATUS_IS_ERR(status)) {
				DEBUG(1, ("Unable to build tower for %s\n", iface->iface.name));
				continue;
			}
			total++;
		}
	}

	return total;
}


static error_status_t epm_Insert(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, 
				 struct epm_Insert *r)
{
	DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR);
}

static error_status_t epm_Delete(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, 
				 struct epm_Delete *r)
{
	DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR);
}


/*
  implement epm_Lookup. This call is used to enumerate the interfaces
  available on a rpc server
*/
static error_status_t epm_Lookup(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, 
				 struct epm_Lookup *r)
{
	struct dcesrv_handle *h;
	struct rpc_eps {
		uint32_t count;
		struct dcesrv_ep_iface *e;
	} *eps;
	uint32_t num_ents;
	int i;

	h = dcesrv_handle_fetch(dce_call->conn, r->in.entry_handle, HTYPE_LOOKUP);
	DCESRV_CHECK_HANDLE(h);

	eps = h->data;

	if (!eps) {
		/* this is the first call - fill the list. Subsequent calls 
		   will feed from this list, stored in the handle */
		eps = talloc_p(h, struct rpc_eps);
		if (!eps) {
			return EPMAPPER_STATUS_NO_MEMORY;
		}
		h->data = eps;

		eps->count = build_ep_list(h, dce_call->conn->dce_ctx->endpoint_list, &eps->e);
	}

	/* return the next N elements */
	num_ents = r->in.max_ents;
	if (num_ents > eps->count) {
		num_ents = eps->count;
	}

	*r->out.entry_handle = h->wire_handle;
	r->out.num_ents = num_ents;

	if (num_ents == 0) {
		r->out.entries = NULL;
		ZERO_STRUCTP(r->out.entry_handle);
		dcesrv_handle_destroy(dce_call->conn, h);
		return EPMAPPER_STATUS_NO_MORE_ENTRIES;
	}

	r->out.entries = talloc_array_p(mem_ctx, struct epm_entry_t, num_ents);
	if (!r->out.entries) {
		return EPMAPPER_STATUS_NO_MEMORY;
	}

	for (i=0;i<num_ents;i++) {
		ZERO_STRUCT(r->out.entries[i].object);
		r->out.entries[i].annotation = eps->e[i].name;
		r->out.entries[i].tower = talloc_p(mem_ctx, struct epm_twr_t);
		if (!r->out.entries[i].tower) {
			return EPMAPPER_STATUS_NO_MEMORY;
		}
		r->out.entries[i].tower->tower = eps->e[i].ep;
	}

	eps->count -= num_ents;
	eps->e += num_ents;

	return EPMAPPER_STATUS_OK;
}


/*
  implement epm_Map. This is used to find the specific endpoint to talk to given
  a generic protocol tower
*/
static error_status_t epm_Map(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, 
			      struct epm_Map *r)
{
	uint32_t count;
	int i;
	struct dcesrv_ep_iface *eps;
	struct epm_floor *floors;
	enum dcerpc_transport_t transport;

	count = build_ep_list(mem_ctx, dce_call->conn->dce_ctx->endpoint_list, &eps);

	ZERO_STRUCT(*r->out.entry_handle);
	r->out.num_towers = 1;
	r->out.towers = talloc_p(mem_ctx, struct epm_twr_p_t);
	if (!r->out.towers) {
		return EPMAPPER_STATUS_NO_MEMORY;
	}
	r->out.towers->twr = talloc_p(mem_ctx, struct epm_twr_t);
	if (!r->out.towers->twr) {
		return EPMAPPER_STATUS_NO_MEMORY;
	}
	
	if (!r->in.map_tower || r->in.max_towers == 0 || 
	    r->in.map_tower->tower.num_floors < 3) {
		goto failed;
	}

	floors = r->in.map_tower->tower.floors;

	if (floors[1].lhs.protocol != EPM_PROTOCOL_UUID ||
	    guid_cmp(mem_ctx, &floors[1].lhs.info.uuid.uuid, NDR_GUID) != 0 ||
	    floors[1].lhs.info.uuid.version != NDR_GUID_VERSION) {
		goto failed;
	}

	transport = dcerpc_transport_by_tower(&r->in.map_tower->tower);

	if (transport == -1) {
		DEBUG(1, ("Client requested unknown transport with levels: "));
		for (i = 2; i < r->in.map_tower->tower.num_floors; i++) {
			DEBUG(1, ("%d, ", r->in.map_tower->tower.floors[i].lhs.protocol));
		}
		goto failed;
	}

	for (i=0;i<count;i++) {
		if (!GUID_equal(&r->in.map_tower->tower.floors[0].lhs.info.uuid.uuid,
					   &eps[i].ep.floors[0].lhs.info.uuid.uuid) ||
			r->in.map_tower->tower.floors[0].lhs.info.uuid.version != 
				eps[i].ep.floors[0].lhs.info.uuid.version ||
				transport != dcerpc_transport_by_tower(&eps[i].ep)) {
			continue;
		}
		
		r->out.towers->twr->tower = eps[i].ep;
		r->out.towers->twr->tower_length = 0;
		return EPMAPPER_STATUS_OK;
	}


failed:
	r->out.num_towers = 0;
	r->out.towers->twr = NULL;

	return EPMAPPER_STATUS_NO_MORE_ENTRIES;
}

static error_status_t epm_LookupHandleFree(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, 
					   struct epm_LookupHandleFree *r)
{
	DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR);
}

static error_status_t epm_InqObject(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, 
				    struct epm_InqObject *r)
{
	DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR);
}

static error_status_t epm_MgmtDelete(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx, 
			       struct epm_MgmtDelete *r)
{
	DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR);
}

static error_status_t epm_MapAuth(struct dcesrv_call_state *dce_call, TALLOC_CTX *mem_ctx,
			    struct epm_MapAuth *r)
{
	DCESRV_FAULT(DCERPC_FAULT_OP_RNG_ERROR);
}

/* include the generated boilerplate */
#include "librpc/gen_ndr/ndr_epmapper_s.c"