/* 
   Unix SMB/CIFS implementation.

   rootDSE ldb module

   Copyright (C) Andrew Tridgell 2005
   Copyright (C) Simo Sorce 2005
   
   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 "lib/ldb/include/ldb.h"
#include "lib/ldb/include/ldb_errors.h"
#include "lib/ldb/include/ldb_private.h"
#include "auth/gensec/gensec.h"
#include "system/time.h"

struct private_data {
	int num_controls;
	char **controls;
};

/*
  return 1 if a specific attribute has been requested
*/
static int do_attribute(const char * const *attrs, const char *name)
{
	return attrs == NULL ||
		ldb_attr_in_list(attrs, name) ||
		ldb_attr_in_list(attrs, "*");
}


/*
  add dynamically generated attributes to rootDSE result
*/
static int rootdse_add_dynamic(struct ldb_module *module, struct ldb_request *req)
{
	struct private_data *priv = talloc_get_type(module->private_data, struct private_data);
	struct ldb_search *s = &req->op.search;
	struct ldb_message *msg;
	struct cli_credentials *server_creds;

	/* this is gross, and will be removed when I change ldb_result not
	   to be so pointer crazy :-) */
	if (s->res->msgs == NULL) {
		return LDB_SUCCESS;
	}

	msg = s->res->msgs[0];

	msg->dn = ldb_dn_explode(msg, "");

	if (do_attribute(s->attrs, "currentTime")) {
		if (ldb_msg_add_steal_string(msg, "currentTime", 
					     ldb_timestring(msg, time(NULL))) != 0) {
			goto failed;
		}
	}

	if (do_attribute(s->attrs, "supportedControl")) {
 		int i;
		for (i = 0; i < priv->num_controls; i++) {
			char *control = talloc_strdup(msg, priv->controls[i]);
			if (!control) {
				goto failed;
			}
			if (ldb_msg_add_steal_string(msg, "supportedControl",
						     control) != 0) {
				goto failed;
 			}
 		}
 	}

	server_creds = talloc_get_type(ldb_get_opaque(module->ldb, "server_credentials"), 
				       struct cli_credentials);
	if (server_creds && do_attribute(s->attrs, "supportedSASLMechanisms")) {
		struct gensec_security_ops **backends = gensec_security_all();
		enum credentials_use_kerberos use_kerberos
			= cli_credentials_get_kerberos_state(server_creds);
		struct gensec_security_ops **ops
			= gensec_use_kerberos_mechs(req, backends, use_kerberos);
		int i;
		for (i = 0; ops && ops[i]; i++) {
			if (ops[i]->sasl_name) {
				char *sasl_name = talloc_strdup(msg, ops[i]->sasl_name);
				if (!sasl_name) {
					goto failed;
				}
				if (ldb_msg_add_steal_string(msg, "supportedSASLMechanisms",
							     sasl_name) != 0) {
					goto failed;
				}
			}
		}
	}

	if (do_attribute(s->attrs, "highestCommittedUSN")) {
		if (module->ldb->sequence_number != NULL && 
		    ldb_msg_add_fmt(msg, "highestCommittedUSN", 
				    "%llu", module->ldb->sequence_number(module->ldb)) != 0) {
			goto failed;
		}
	}
	
	/* TODO: lots more dynamic attributes should be added here */

	return 0;

failed:
	return LDB_ERR_OPERATIONS_ERROR;
}

/*
  handle search requests
*/
static int rootdse_search_bytree(struct ldb_module *module, struct ldb_request *req)
{
	struct ldb_search *s = &req->op.search;
	int ret;
	TALLOC_CTX *tmp_ctx;

	/* see if its for the rootDSE */
	if (s->scope != LDB_SCOPE_BASE ||
	    (s->base && s->base->comp_num != 0)) {
		return ldb_next_request(module, req);
	}

	tmp_ctx = talloc_new(module);

	/* in our db we store the rootDSE with a DN of cn=rootDSE */
	s->base = ldb_dn_explode(tmp_ctx, "cn=rootDSE");
	s->tree = ldb_parse_tree(tmp_ctx, "dn=*");
	if (s->base == NULL || s->tree == NULL) {
		ldb_oom(module->ldb);
		talloc_free(tmp_ctx);
		return LDB_ERR_OPERATIONS_ERROR;
	}

	/* grab the static contents of the record */
	ret = ldb_next_request(module, req);

	req->op.search.res = s->res;

	if (ret == LDB_SUCCESS) {
		ret = rootdse_add_dynamic(module, req);
	}

	talloc_free(tmp_ctx);

	return ret;
}

static int rootdse_register_control(struct ldb_module *module, struct ldb_request *req)
{
	struct private_data *priv = talloc_get_type(module->private_data, struct private_data);
	char **list;

	list = talloc_realloc(priv, priv->controls, char *, priv->num_controls + 1);
	if (!list) {
		return LDB_ERR_OPERATIONS_ERROR;
	}

	list[priv->num_controls] = talloc_strdup(list, req->op.reg.oid);
	if (!list[priv->num_controls]) {
		return LDB_ERR_OPERATIONS_ERROR;
	}

	priv->num_controls += 1;
	priv->controls = list;

	return LDB_SUCCESS;
}
 

static int rootdse_request(struct ldb_module *module, struct ldb_request *req)
{
	switch (req->operation) {
	case LDB_REQ_SEARCH:
		return rootdse_search_bytree(module, req);
	case LDB_REQ_REGISTER:
		return rootdse_register_control(module, req);
	default:
		break;
	}
	return ldb_next_request(module, req);
}

static int rootdse_init(struct ldb_module *module)
{
	struct private_data *data;

	data = talloc(module, struct private_data);
	if (data == NULL) {
		return -1;
	}

	data->num_controls = 0;
	data->controls = NULL;
	module->private_data = data;

	return ldb_next_init(module);
}

static const struct ldb_module_ops rootdse_ops = {
	.name			= "rootdse",
	.init_context           = rootdse_init,
	.request		= rootdse_request
};

int rootdse_module_init(void)
{
	return ldb_register_module(&rootdse_ops);
}