/*
 * LDAP module - Configuration file parser
 *
 * Copyright (C) 2001-2003 FhG FOKUS
 * Copyright (C) 2004,2005 Free Software Foundation, Inc.
 * Copyright (C) 2005,2006 iptelorg GmbH
 *
 * This file is part of ser, a free SIP server.
 *
 * SER 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.
 *
 * SER 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.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include "ld_cfg.h"
#include "ld_mod.h"
#include "ld_uri.h"

#include "../../cfg_parser.h"
#include "../../mem/mem.h"
#include "../../dprint.h"
#include "../../trim.h"
#include "../../ut.h"
#include "../../resolve.h"

#include <ldap.h>
#include <stdlib.h>
#include <stdio.h>
#include <libgen.h>


enum section_type {
	LDAP_CON_SECTION = 0,
	LDAP_TABLE_SECTION
};

static struct ld_cfg* cfg = NULL;

static struct ld_con_info* con = NULL;


void ld_cfg_free(void)
{
	struct ld_con_info* c;
	struct ld_cfg* ptr;
	int i;

	while (cfg) {
		ptr = cfg;
		cfg = cfg->next;

		if (ptr->table.s) pkg_free(ptr->table.s);
		if (ptr->base.s) pkg_free(ptr->base.s);
		if (ptr->filter.s) pkg_free(ptr->filter.s);

		for(i = 0; i < ptr->n; i++) {
			if (ptr->field[i].s) pkg_free(ptr->field[i].s);
			if (ptr->attr[i].s) pkg_free(ptr->attr[i].s);
		}
		if (ptr->field) pkg_free(ptr->field);
		if (ptr->attr) pkg_free(ptr->attr);
		if (ptr->syntax) pkg_free(ptr->syntax);
	}

	while (con) {
		c = con;
		con = con->next;

		if (c->id.s) pkg_free(c->id.s);
		if (c->host.s) pkg_free(c->host.s);
		if (c->username.s) pkg_free(c->username.s);
		if (c->password.s) pkg_free(c->password.s);

		pkg_free(c);
	}

}


static int parse_field_map(void* param, cfg_parser_t* st, unsigned int flags)
{
	int ret;
	cfg_token_t t;
	void* ptr;
	static cfg_option_t syntaxes[] = {
		{"GeneralizedTime", .val = LD_SYNTAX_GENTIME},
		{"Integer",         .val = LD_SYNTAX_INT    },
		{"BitString",       .val = LD_SYNTAX_BIT    },
		{"Boolean",         .val = LD_SYNTAX_BOOL   },
		{"String",          .val = LD_SYNTAX_STRING },
		{"Binary",          .val = LD_SYNTAX_BIN    },
		{"Float",           .val = LD_SYNTAX_FLOAT  },
		{0}
	};

	cfg_option_t* syntax;

	if (cfg_eat_equal(st, flags)) return -1;

	if (!(ptr = pkg_realloc(cfg->field, sizeof(str) * (cfg->n + 1)))) {
		ERR("ldap:%s:%d:%d Out of memory\n", st->file, st->line, st->col);
		return -1;
	}
	cfg->field = (str*)ptr;
	cfg->field[cfg->n].s = NULL;

	if (!(ptr = pkg_realloc(cfg->attr, sizeof(str) * (cfg->n + 1)))) {
		ERR("ldap:%s:%d:%d: Out of memory\n", st->file, st->line, st->col);
		return -1;
	}
	cfg->attr = (str*)ptr;
	cfg->attr[cfg->n].s = NULL;

	if (!(ptr = pkg_realloc(cfg->syntax, sizeof(enum ld_syntax)*(cfg->n+1)))) {
		ERR("ldap:%s:%d:%d: Out of memory\n", st->file, st->line, st->col);
		return -1;
	}
	cfg->syntax = (enum ld_syntax*)ptr;
	cfg->syntax[cfg->n] = LD_SYNTAX_STRING;

	cfg->n++;

	ret = cfg_get_token(&t, st, 0);
	if (ret < 0) return -1;
	if (ret > 0) {
		ERR("ldap:%s:%d:%d: Database field name expected\n",
		    st->file, st->line, st->col);
		return -1;
	}

	if (t.type != CFG_TOKEN_ALPHA) {
		ERR("ldap:%s:%d:%d: Invalid field name format %d:'%.*s'\n",
		    st->file, t.start.line, t.start.col,
		    t.type, STR_FMT(&t.val));
		return -1;
	}

	if ((cfg->field[cfg->n - 1].s = as_asciiz(&t.val)) == NULL) {
		ERR("ldap:%s:%d:%d: Out of memory\n", st->file,
			t.start.line, t.start.col);
		return -1;
	}
	cfg->field[cfg->n - 1].len = t.val.len;

	ret = cfg_get_token(&t, st, 0);
	if (ret < 0) return -1;
	if (ret > 0) {
		ERR("ldap:%s:%d:%d: Delimiter ':' missing\n",
		    st->file, st->line, st->col);
		return -1;
	}
	if (t.type != ':') {
		ERR("ldap:%s:%d:%d: Syntax error, ':' expected\n",
		    st->file, t.start.line, t.start.col);
		return -1;
	}

	ret = cfg_get_token(&t, st, 0);
	if (ret < 0) return -1;
	if (ret > 0) {
		ERR("ldap:%s:%d:%d: LDAP Attribute syntax or name expected\n",
		    st->file, st->line, st->col);
		return -1;
	}

	if (t.type == '(') {
		ret = cfg_get_token(&t, st, 0);
		if (ret < 0) return -1;
		if (ret > 0) {
			ERR("ldap:%s:%d:%d: LDAP Attribute Syntax expected\n",
				st->file, st->line, st->col);
			return -1;
		}
		if (t.type != CFG_TOKEN_ALPHA) {
			ERR("ldap:%s:%d:%d: Invalid LDAP attribute syntax format %d:'%.*s'\n",
				st->file, t.start.line, t.start.col,
				t.type, STR_FMT(&t.val));
			return -1;
		}

		if ((syntax = cfg_lookup_token(syntaxes, &t.val)) == NULL) {
			ERR("ldap:%s:%d:%d: Invalid syntaxt value '%.*s'\n",
				st->file, t.start.line, t.start.col, STR_FMT(&t.val));
			return -1;
		}
		cfg->syntax[cfg->n - 1] = syntax->val;

		ret = cfg_get_token(&t, st, 0);
		if (ret < 0) return -1;
		if (ret > 0) {
			ERR("ldap:%s:%d:%d: Closing ')' missing in attribute syntax\n",
				st->file, st->line, st->col);
			return -1;
		}

		if (t.type != ')') {
			ERR("ldap:%s:%d:%d: Syntax error, ')' expected\n",
				st->file, st->line, st->col);
			return -1;
		}

		ret = cfg_get_token(&t, st, 0);
		if (ret < 0) return -1;
		if (ret > 0) {
			ERR("ldap:%s:%d:%d: LDAP Attribute name expected\n",
				st->file, st->line, st->col);
			return -1;
		}
	}

	if (t.type != CFG_TOKEN_ALPHA) {
		ERR("ldap:%s:%d:%d: Invalid LDAP attribute name format %d:'%.*s'\n",
		    st->file, t.start.line, t.start.col, t.type, STR_FMT(&t.val));
		return -1;
	}

	if ((cfg->attr[cfg->n - 1].s = as_asciiz(&t.val)) == NULL) {
		ERR("ldap:%s:%d:%d: Out of memory\n",
		    st->file, t.start.line, t.start.col);
		return -1;
	}
	cfg->attr[cfg->n - 1].len = t.val.len;

	if (cfg_eat_eol(st, flags)) return -1;
	return 0;
}


static cfg_option_t scope_values[] = {
	{"base",     .val = LDAP_SCOPE_BASE    },
	{"onelevel", .val = LDAP_SCOPE_ONELEVEL},
	{"one",      .val = LDAP_SCOPE_ONELEVEL},
	{"subtree",  .val = LDAP_SCOPE_SUBTREE },
	{"sub",      .val = LDAP_SCOPE_SUBTREE },
#if defined HAVE_SCOPE_CHILDREN
	{"children", .val = LDAP_SCOPE_CHILDREN},
#endif
	{0}
};


static cfg_option_t deref_values[] = {
	{"never",     .val = LDAP_DEREF_NEVER },   /* default, 0x00 */
	{"searching", .val = LDAP_DEREF_SEARCHING},
	{"finding",   .val = LDAP_DEREF_FINDING },
	{"always",    .val = LDAP_DEREF_ALWAYS },
	{0}
};

static cfg_option_t ldap_tab_options[] = {
	{"scope",     .param = scope_values, .f = cfg_parse_enum_opt},
	{"field_map", .f = parse_field_map},
	{"filter",    .f = cfg_parse_str_opt, .flags = CFG_STR_PKGMEM},
	{"base",      .f = cfg_parse_str_opt, .flags = CFG_STR_PKGMEM},
	{"timelimit", .f = cfg_parse_int_opt},
	{"sizelimit", .f = cfg_parse_int_opt},
	{"chase_references",  .param = deref_values, .f = cfg_parse_enum_opt},
	{"chase_referrals",   .f = cfg_parse_bool_opt},
	{0}
};


static cfg_option_t auth_values[] = {
	{"none",       .val = LDAP_AUTHMECH_NONE},
	{"simple",     .val = LDAP_AUTHMECH_SIMPLE},
	{"digest-md5", .val = LDAP_AUTHMECH_DIGESTMD5},
	{"external",   .val = LDAP_AUTHMECH_EXTERNAL},
	{0}
};


static cfg_option_t ldap_con_options[] = {
	{"host",     		    .f = cfg_parse_str_opt, .flags = CFG_STR_PKGMEM},
	{"port",     		    .f = cfg_parse_int_opt},
	{"username", 		    .f = cfg_parse_str_opt, .flags = CFG_STR_PKGMEM},
	{"password", 		    .f = cfg_parse_str_opt, .flags = CFG_STR_PKGMEM},
	{"authtype", 		    .param = auth_values, .f = cfg_parse_enum_opt},
	{"tls",			    .f = cfg_parse_bool_opt},
	{"ca_list",  		    .f = cfg_parse_str_opt, .flags = CFG_STR_PKGMEM},
	{"require_certificate",     .f = cfg_parse_str_opt, .flags = CFG_STR_PKGMEM},
	{0}
};


static cfg_option_t section_types[] = {
	{"connection", .val = LDAP_CON_SECTION},
	{"con",        .val = LDAP_CON_SECTION},
	{"table",      .val = LDAP_TABLE_SECTION},
	{0}
};


static int parse_section(void* param, cfg_parser_t* st, unsigned int flags)
{
	cfg_token_t t;
	int ret, type, i;
	cfg_option_t* opt;
	str* id = NULL;
	struct ld_cfg* tab;
	struct ld_con_info* cinfo;

	ret = cfg_get_token(&t, st, 0);
	if (ret < 0) return -1;
	if (ret > 0) {
		ERR("%s:%d:%d: Section type missing\n",
		    st->file, st->line, st->col);
		return -1;
	}

	if (t.type != CFG_TOKEN_ALPHA ||
	    ((opt = cfg_lookup_token(section_types, &t.val)) == NULL)) {
		ERR("%s:%d:%d: Invalid section type %d:'%.*s'\n",
		    st->file, t.start.line, t.start.col, t.type, STR_FMT(&t.val));
		return -1;
	}
	type = opt->val;

	if (type == LDAP_TABLE_SECTION) {
		if ((tab = pkg_malloc(sizeof(*tab))) == NULL) {
			ERR("ldap:%s:%d: Out of memory\n", st->file, st->line);
			return -1;
		}
		memset(tab, '\0', sizeof(*tab));
		tab->next = cfg;
		cfg = tab;

		cfg_set_options(st, ldap_tab_options);
		ldap_tab_options[2].param = &cfg->filter;
		ldap_tab_options[3].param = &cfg->base;
		for(i = 0; scope_values[i].name; i++) {
			scope_values[i].param = &cfg->scope;
		}
		ldap_tab_options[4].param = &cfg->timelimit;
		ldap_tab_options[5].param = &cfg->sizelimit;
		for(i = 0; deref_values[i].name; i++) {
			deref_values[i].param = &cfg->chase_references;
		}
		ldap_tab_options[7].param = &cfg->chase_referrals;
	} else if (type == LDAP_CON_SECTION) {
		if ((cinfo = pkg_malloc(sizeof(*cinfo))) == NULL) {
			ERR("ldap:%s:%d: Out of memory\n", st->file, st->line);
			return -1;
		}
		memset(cinfo, '\0', sizeof(*cinfo));
		cinfo->next = con;
		con = cinfo;

		cfg_set_options(st, ldap_con_options);
		ldap_con_options[0].param = &con->host;
		ldap_con_options[1].param = &con->port;
		ldap_con_options[2].param = &con->username;
		ldap_con_options[3].param = &con->password;
		for(i = 0; auth_values[i].name; i++) {
			auth_values[i].param = &con->authmech;
		}
		ldap_con_options[5].param = &con->tls;
		ldap_con_options[6].param = &con->ca_list;
		ldap_con_options[7].param = &con->req_cert;
	} else {
		BUG("%s:%d:%d: Unsupported section type %c\n",
			st->file, t.start.line, t.start.col, t.type);
		return -1;
	}

	ret = cfg_get_token(&t, st, 0);
	if (ret < 0) return -1;
	if (ret > 0) {
		ERR("%s:%d:%d: Delimiter ':' expected.\n",
		    st->file, st->line, st->col);
		return -1;
	}

	if (type == LDAP_TABLE_SECTION) {
		id = &cfg->table;
	} else if (type == LDAP_CON_SECTION) {
		id = &con->id;
	} else {
		BUG("%s:%d:%d: Invalid section type %d\n", st->file,
			st->line, st->col, type);
	}

	ret = cfg_parse_str(id, st, CFG_STR_PKGMEM);
	if (ret < 0) return -1;
	if (ret > 0) {
		ERR("%s:%d:%d: Section identifier expected\n",
			st->file, st->line, st->col);
		return -1;
	}

	ret = cfg_get_token(&t, st, 0);
	if (ret < 0) return ret;
	if (ret > 0) {
		ERR("%s:%d:%d: Missing closing ']'.\n",
			st->file, st->line, st->col);
		return -1;
	}
	if (t. type != ']') {
		ERR("%s:%d:%d: Syntax error, ']' expected.\n",
			st->file, t.start.line, t.start.col);
		return -1;
	}

	if (cfg_eat_eol(st, flags)) return -1;
	return 0;
}


struct ld_cfg* ld_find_cfg(str* table)
{
	struct ld_cfg* ptr;

	ptr = cfg;
	while(ptr) {
		if (ptr->table.len == table->len &&
			!strncmp(ptr->table.s, table->s, table->len))
			return ptr;
		ptr = ptr->next;
	}
	return NULL;
}


char* ld_find_attr_name(enum ld_syntax* syntax, struct ld_cfg* cfg, char* fld_name)
{
	int i;

	for(i = 0; i < cfg->n; i++) {
		if (!strcmp(fld_name, cfg->field[i].s)) {
			*syntax = cfg->syntax[i];
			return cfg->attr[i].s;
		}
	}
	return NULL;
}


struct ld_con_info* ld_find_conn_info(str* conn_id)
{
	struct ld_con_info* ptr;

	ptr = con;
	while(ptr) {
		if (ptr->id.len == conn_id->len &&
			!memcmp(ptr->id.s, conn_id->s, conn_id->len)) {
			return ptr;
		}
		ptr = ptr->next;
	}
	return NULL;
}


static int ld_cfg_validity_check(struct ld_cfg *cfg)
{
	struct ld_cfg *pcfg;

	for (pcfg = cfg; pcfg; pcfg = pcfg->next) {
		if (pcfg->sizelimit < 0 || pcfg->sizelimit > LD_MAXINT) {
			ERR("ldap: invalid sizelimit (%d) specified\n", pcfg->sizelimit);
			return -1;
		}
		if (pcfg->timelimit < 0 || pcfg->timelimit > LD_MAXINT) {
			ERR("ldap: invalid timelimit (%d) specified\n", pcfg->timelimit);
			return -1;
		}
	}

	return 0;
}


int ld_load_cfg(str* filename)
{
	cfg_parser_t* parser;
	cfg = NULL;

	if ((parser = cfg_parser_init(0, filename)) == NULL) {
		ERR("ldap: Error while initializing configuration file parser.\n");
		return -1;
	}

	cfg_section_parser(parser, parse_section, NULL);

	if (sr_cfg_parse(parser)) {
		if (cfg == NULL) {
			ERR("ldap: A table name (i.e. [table_name]) is missing in the "
				"configuration file.\n");
		}
		cfg_parser_close(parser);
		ld_cfg_free();
		return -1;
	}
	cfg_parser_close(parser);

	if (ld_cfg_validity_check(cfg)) {
		ld_cfg_free();
		return -1;
	}

	return 0;
}