/*
 * Kamailio LDAP Module
 *
 * Copyright (C) 2007 University of North Carolina
 *
 * Original author: Christian Schlatter, cs@unc.edu
 *
 *
 * This file is part of Kamailio, a free SIP server.
 *
 * Kamailio 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
 *
 * Kamailio 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 <string.h>
#include <unistd.h>
#include <stdio.h>

#include <ldap.h>

#include "ldap_connect.h"
#include "ld_session.h"
#include "../../core/mem/mem.h"
#include "../../core/ut.h"

int ldap_connect_ex(char *_ld_name, int llevel)
{
	int rc;
	int ldap_bind_result_code;
	char *ldap_err_str;
	int ldap_proto_version;
	int msgid;
	LDAPMessage *result;
	struct ld_session *lds;
	struct berval ldap_cred;

	/*
	* get ld session and session config parameters
	*/

	if((lds = get_ld_session(_ld_name)) == NULL) {
		LM_ERR("ld_session [%s] not found\n", _ld_name);
		return -1;
	}

	/*
	 * ldap_initialize
	 */

	rc = ldap_initialize(&lds->handle, lds->host_name);
	if(rc != LDAP_SUCCESS) {
		LM_ERR("[%s]: ldap_initialize (%s) failed: %s\n", _ld_name,
				lds->host_name, ldap_err2string(rc));
		return -1;
	}

	/*
	 * set LDAP OPTIONS
	 */

	/* LDAP_OPT_PROTOCOL_VERSION */
	switch(lds->version) {
		case 2:
			ldap_proto_version = LDAP_VERSION2;
			break;
		case 3:
			ldap_proto_version = LDAP_VERSION3;
			break;
		default:
			LM_ERR("[%s]: Invalid LDAP protocol version [%d]\n", _ld_name,
					lds->version);
			return -1;
	}
	if(ldap_set_option(
			   lds->handle, LDAP_OPT_PROTOCOL_VERSION, &ldap_proto_version)
			!= LDAP_OPT_SUCCESS) {
		LM_ERR("[%s]: Could not set LDAP_OPT_PROTOCOL_VERSION [%d]\n", _ld_name,
				ldap_proto_version);
		return -1;
	}

	/* LDAP_OPT_RESTART */
	if(ldap_set_option(lds->handle, LDAP_OPT_RESTART, LDAP_OPT_ON)
			!= LDAP_OPT_SUCCESS) {
		LM_ERR("[%s]: Could not set LDAP_OPT_RESTART to ON\n", _ld_name);
		return -1;
	}

	/* LDAP_OPT_TIMELIMIT */
	/*
	if (lds->server_search_timeout > 0) {
		if (ldap_set_option(lds->handle,
				LDAP_OPT_TIMELIMIT,
				&lds->server_search_timeout)
			!= LDAP_OPT_SUCCESS) {
			LM_ERR("[%s]: Could not set LDAP_OPT_TIMELIMIT to [%d]\n",
			_ld_name, lds->server_search_timeout);
			return -1;
		}
	}
	*/

	/* LDAP_OPT_NETWORK_TIMEOUT */
	if((lds->network_timeout.tv_sec > 0)
			|| (lds->network_timeout.tv_usec > 0)) {
		if(ldap_set_option(lds->handle, LDAP_OPT_NETWORK_TIMEOUT,
				   (const void *)&lds->network_timeout)
				!= LDAP_OPT_SUCCESS) {
			LM_ERR("[%s]: Could not set"
				   " LDAP_NETWORK_TIMEOUT to [%d.%d]\n",
					_ld_name, (int)lds->network_timeout.tv_sec,
					(int)lds->network_timeout.tv_usec);
		}
	}

	/*
	  * ldap_sasl_bind (LDAP_SASL_SIMPLE)
	 */


	ldap_cred.bv_val = lds->bind_pwd;
	ldap_cred.bv_len = strlen(lds->bind_pwd);
	rc = ldap_sasl_bind(lds->handle, lds->bind_dn, LDAP_SASL_SIMPLE, &ldap_cred,
			NULL, NULL, &msgid);
	if(rc != LDAP_SUCCESS) {
		LM_ERR("[%s]: ldap bind failed: %s\n", _ld_name, ldap_err2string(rc));
		return -1;
	}


	if((lds->client_bind_timeout.tv_sec == 0)
			&& (lds->client_bind_timeout.tv_usec == 0)) {
		rc = ldap_result(lds->handle, msgid, 1, NULL, &result);
	} else {
		rc = ldap_result(
				lds->handle, msgid, 1, &lds->client_bind_timeout, &result);
	}


	if(rc == -1) {
		ldap_get_option(lds->handle, LDAP_OPT_ERROR_NUMBER, &rc);
		ldap_err_str = ldap_err2string(rc);
		LM_ERR("[%s]: ldap_result failed: %s\n", _ld_name, ldap_err_str);
		return -1;
	} else if(rc == 0) {
		LM_ERR("[%s]: bind operation timed out\n", _ld_name);
		return -1;
	}


	rc = ldap_parse_result(lds->handle, result, &ldap_bind_result_code, NULL,
			NULL, NULL, NULL, 1);
	if(rc != LDAP_SUCCESS) {
		LM_ERR("[%s]: ldap_parse_result failed: %s\n", _ld_name,
				ldap_err2string(rc));
		return -1;
	}
	if(ldap_bind_result_code != LDAP_SUCCESS) {
		LM_ERR("[%s]: ldap bind failed: %s\n", _ld_name,
				ldap_err2string(ldap_bind_result_code));
		return -1;
	}


	/* freeing result leads to segfault ... bind result is probably used by openldap lib */
	/* ldap_msgfree(result); */


	LOG(llevel, "[%s]: LDAP bind successful (ldap_host [%s])\n", _ld_name,
			lds->host_name);

	return 0;
}

int ldap_connect(char *_ld_name)
{
	return ldap_connect_ex(_ld_name, L_DBG);
}

int ldap_disconnect(char *_ld_name)
{
	struct ld_session *lds;

	/*
		* get ld session
		*/

	if((lds = get_ld_session(_ld_name)) == NULL) {
		LM_ERR("ld_session [%s] not found\n", _ld_name);
		return -1;
	}

	if(lds->handle == NULL) {
		return 0;
	}

	ldap_unbind_ext(lds->handle, NULL, NULL);
	lds->handle = NULL;

	return 0;
}

int ldap_reconnect(char *_ld_name)
{
	int rc;

	if(ldap_disconnect(_ld_name) != 0) {
		LM_ERR("[%s]: disconnect failed\n", _ld_name);
		return -1;
	}

	if((rc = ldap_connect_ex(_ld_name, L_INFO)) != 0) {
		LM_ERR("[%s]: reconnect failed\n", _ld_name);
	} else {
		LM_NOTICE("[%s]: LDAP reconnect successful\n", _ld_name);
	}
	return rc;
}

int ldap_get_vendor_version(char **_version)
{
	static char version[128];
	LDAPAPIInfo api;
	int rc;

#ifdef LDAP_API_INFO_VERSION
	api.ldapai_info_version = LDAP_API_INFO_VERSION;
#else
	api.ldapai_info_version = 1;
#endif

	if(ldap_get_option(NULL, LDAP_OPT_API_INFO, &api) != LDAP_SUCCESS) {
		LM_ERR("ldap_get_option(API_INFO) failed\n");
		return -1;
	}

	rc = snprintf(version, 128, "%s - %d", api.ldapai_vendor_name,
			api.ldapai_vendor_version);
	if((rc >= 128) || (rc < 0)) {
		LM_ERR("snprintf failed\n");
		return -1;
	}

	*_version = version;
	return 0;
}