/*
   +----------------------------------------------------------------------+
   | PHP HTML Embedded Scripting Language Version 3.0                     |
   +----------------------------------------------------------------------+
   | Copyright (c) 1997-1999 PHP Development Team (See Credits file)      |
   +----------------------------------------------------------------------+
   | This program is free software; you can redistribute it and/or modify |
   | it under the terms of one of the following licenses:                 |
   |                                                                      |
   |  A) 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.                                               |
   |                                                                      |
   |  B) the PHP License as published by the PHP Development Team and     |
   |     included in the distribution in the file: LICENSE                |
   |                                                                      |
   | 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 both licenses referred to here.   |
   | If you did not, or have any questions about PHP licensing, please    |
   | contact core@php.net.                                                |
   +----------------------------------------------------------------------+
   | Authors: Amitay Isaacs  <amitay@w-o-i.com>                           |
   |          Eric Warnke    <ericw@albany.edu>                           |
   |          Rasmus Lerdorf <rasmus@lerdorf.on.ca>                       |
   +----------------------------------------------------------------------+
 */
 

/* $Id: ldap.c,v 1.66 1999/02/01 02:31:10 sas Exp $ */
#define IS_EXT_MODULE
#if !PHP_31 && defined(THREAD_SAFE)
#undef THREAD_SAFE
#endif
#ifdef THREAD_SAFE
#include "tls.h"
DWORD ldapTLS;
static int numthreads=0;
void *ldap_mutex;
#endif

#if !(WIN32|WINNT)
#include "config.h"
#endif
#include "php.h"
#include "internal_functions.h"

#if HAVE_LDAP

#if COMPILE_DL
#if PHP_31
#include "ext/phpdl.h"
#include "ext/standard/dl.h"
#else
#include "dl/phpdl.h"
#include "functions/dl.h"
#endif
#endif
#include "php3_list.h"
#include "php3_ldap.h"

#include <string.h>


#if WIN32|WINNT
#if HAVE_NSLDAP
#include <winsock.h>
#endif
#define strdup _strdup
#undef WINDOWS
#undef strcasecmp
#undef strncasecmp
#define WINSOCK 1
#define __STDC__ 1
#endif

#if PHP_31
#include "ext/standard/php3_string.h"
#else
#include "functions/php3_string.h"
#endif


#if THREAD_SAFE & HAVE_NSLDAP
#include "php3_threads.h"
/* Structure for LDAP error values */
#define LDAP_TLS_VARS ldap_module *PHP3_TLS_GET(ldapTLS,php3_ldap_module)
#define LDAP_GLOBAL(a) php3_ldap_module->a
#else
#define LDAP_TLS_VARS
#define LDAP_GLOBAL(a) php3_ldap_module.a
ldap_module php3_ldap_module;
#endif



/*
	This is just a small subset of the functionality provided by the LDAP library. All the 
	operations are synchronous. Referrals are not handled automatically.
*/

function_entry ldap_functions[] = {
	{"ldap_connect", 				php3_ldap_connect,				NULL},
	{"ldap_close", 					php3_ldap_unbind,				NULL},
	{"ldap_bind",					php3_ldap_bind,					NULL},
	{"ldap_unbind",					php3_ldap_unbind,				NULL},
	{"ldap_read",					php3_ldap_read,					NULL},
	{"ldap_list",					php3_ldap_list,					NULL},
	{"ldap_search",					php3_ldap_search,				NULL},
	{"ldap_free_result", 			php3_ldap_free_result,			NULL},
	{"ldap_count_entries", 			php3_ldap_count_entries, 		NULL},
	{"ldap_sort_entries",			php3_ldap_sort_entries,			NULL},
	{"ldap_first_entry",			php3_ldap_first_entry,			NULL},
	{"ldap_next_entry",				php3_ldap_next_entry,			NULL},
	{"ldap_get_entries",			php3_ldap_get_entries,			NULL},
	{"ldap_first_attribute",		php3_ldap_first_attribute,		NULL},
	{"ldap_next_attribute",			php3_ldap_next_attribute,		NULL},
	{"ldap_get_attributes",			php3_ldap_get_attributes,		NULL},
	{"ldap_get_values",				php3_ldap_get_values,			NULL},
	{"ldap_get_dn",					php3_ldap_get_dn,				NULL},
	{"ldap_explode_dn",				php3_ldap_explode_dn,			NULL},
	{"ldap_dn2ufn",					php3_ldap_dn2ufn,				NULL},
	{"ldap_add", 					php3_ldap_add,					NULL},
	{"ldap_delete",					php3_ldap_delete,				NULL},
	{"ldap_modify",					php3_ldap_modify,				NULL},
	{NULL, NULL, NULL}
};


php3_module_entry ldap_module_entry = {
	"LDAP", ldap_functions, php3_minit_ldap, php3_mshutdown_ldap, NULL, NULL, php3_info_ldap, STANDARD_MODULE_PROPERTIES
};



#if COMPILE_DL
DLEXPORT php3_module_entry *get_module(void ) { return &ldap_module_entry; }
#endif


#if 0 /* see my note in php3_ldap.h.  smc */
/* Function for setting thread-specific LDAP error values */
static void php3_ldap_set_ld_error( int err, char *matched, char *errmsg, void *dummy )
{
	LDAP_TLS_VARS;

   /* Set the error code returned by the LDAP operation */
   LDAP_GLOBAL(le_errno) = err;
   /* Specify the components of the DN that matched (if 
      an "NO_SUCH_OBJECT" error occurred */
   if ( LDAP_GLOBAL(le_matched) != NULL ) {
      ldap_memfree( LDAP_GLOBAL(le_matched) );
   }
   LDAP_GLOBAL(le_matched) = matched;
   /* Specify the error message corresponding to the error code */
   if ( LDAP_GLOBAL(le_errmsg) != NULL ) {
      ldap_memfree( LDAP_GLOBAL(le_errmsg) );
   }
   LDAP_GLOBAL(le_errmsg) = errmsg;
}

/* Function for getting the thread-specific LDAP error values */
static int php3_ldap_get_ld_error( char **matched, char **errmsg, void *dummy )
{
	LDAP_TLS_VARS;
   /* Retrieve the error values */
   if ( matched != NULL ) {
      *matched = LDAP_GLOBAL(le_matched);
   }
   if ( errmsg != NULL ) {
      *errmsg = LDAP_GLOBAL(le_errmsg);
   }
   return( LDAP_GLOBAL(le_errno) );
}

/* Function for setting the value of the errno variable */
static void php3_ldap_set_errno( int err )
{
   errno = err;
}

/* Function for getting the value of the errno variable */
static int php3_ldap_get_errno( void )
{
   return( errno );
}
#endif /* THREAD_SAFE && NSLDAP */

static void _close_ldap_link(LDAP *ld)
{
	LDAP_TLS_VARS;
  	ldap_unbind_s(ld);
	/* php3_printf("Freeing ldap connection");*/
	LDAP_GLOBAL(num_links)--;
}


static void _free_ldap_result(LDAPMessage *result)
{
        ldap_msgfree(result);
}

int php3_minit_ldap(INIT_FUNC_ARGS)
{
#if defined(THREAD_SAFE)
	ldap_module	*php3_ldap_module;
	PHP3_MUTEX_ALLOC(ldap_mutex);
	PHP3_MUTEX_LOCK(ldap_mutex);
	numthreads++;
	if (numthreads==1){
		if (!PHP3_TLS_PROC_STARTUP(ldapTLS)){
			PHP3_MUTEX_UNLOCK(ldap_mutex);
			PHP3_MUTEX_FREE(ldap_mutex);
			return 0;
		}
	}
	PHP3_MUTEX_UNLOCK(ldap_mutex);
	if(!PHP3_TLS_THREAD_INIT(ldapTLS,php3_ldap_module,ldap_module))
		return 0;
#if 0 /*HAVE_NSLDAP*/
   /* Set up the ldap_thread_fns structure with pointers 
      to the functions that you want called */
   memset( &LDAP_GLOBAL(tfns), '\0', sizeof(struct ldap_thread_fns) );
   /* Specify the functions that you want called */
   /* Call the my_mutex_alloc() function whenever mutexes 
      need to be allocated */
   LDAP_GLOBAL(tfns).ltf_mutex_alloc = (void *(*)(void)) php3_mutex_alloc;
   /* Call the my_mutex_free() function whenever mutexes 
      need to be destroyed */
   LDAP_GLOBAL(tfns).ltf_mutex_free = (void (*)(void *)) php3_mutex_free;
   /* Call the pthread_mutex_lock() function whenever a 
      thread needs to lock a mutex. */
   LDAP_GLOBAL(tfns).ltf_mutex_lock = (int (*)(void *)) php3_mutex_lock;
   /* Call the pthread_mutex_unlock() function whenever a 
      thread needs to unlock a mutex. */
   LDAP_GLOBAL(tfns).ltf_mutex_unlock = (int (*)(void *)) php3_mutex_unlock;
   /* Call the get_errno() function to get the value of errno */
   LDAP_GLOBAL(tfns).ltf_get_errno = php3_ldap_get_errno;
   /* Call the set_errno() function to set the value of errno */
   LDAP_GLOBAL(tfns).ltf_set_errno = php3_ldap_set_errno;
   /* Call the get_ld_error() function to get error values from 
      calls to functions in the libldap library */
   LDAP_GLOBAL(tfns).ltf_get_lderrno = php3_ldap_get_ld_error;
   /* Call the set_ld_error() function to set error values for 
      calls to functions in the libldap library */
   LDAP_GLOBAL(tfns).ltf_set_lderrno = php3_ldap_set_ld_error;
   /* Don't pass any extra parameter to the functions for 
     getting and setting libldap function call errors */
   LDAP_GLOBAL(tfns).ltf_lderrno_arg = NULL;
/* Set the session option that specifies the functions to call for multi-threaded clients */
	if (ldap_set_option( ld, LDAP_OPT_THREAD_FN_PTRS, (void *) &LDAP_GLOBAL(tfns))!= 0) {
		ldap_perror( ld, "ldap_set_option: thread pointers" );
	}
#endif
#endif
	if (cfg_get_long("ldap.max_links", &LDAP_GLOBAL(max_links)) == FAILURE) {
		LDAP_GLOBAL(max_links) = -1;
	}

	if (cfg_get_string("ldap.base_dn", &LDAP_GLOBAL(base_dn)) == FAILURE) {
		LDAP_GLOBAL(base_dn) = NULL;
	}

	LDAP_GLOBAL(le_result) = register_list_destructors(_free_ldap_result, NULL);
	LDAP_GLOBAL(le_link) = register_list_destructors(_close_ldap_link, NULL);

	/*FIXME is this safe in threaded environment? if so, please comment*/
	ldap_module_entry.type = type;

	return SUCCESS;
}

int php3_mshutdown_ldap(void){
#ifdef THREAD_SAFE
	LDAP_TLS_VARS;
	PHP3_TLS_THREAD_FREE(php3_ldap_module);
	PHP3_MUTEX_LOCK(ldap_mutex);
	numthreads--;
	if (!numthreads) {
		PHP3_TLS_PROC_SHUTDOWN(ldapTLS);
	}
	PHP3_MUTEX_UNLOCK(ldap_mutex);
	PHP3_MUTEX_FREE(ldap_mutex);
#endif
	return SUCCESS;
}

void php3_info_ldap(void)
{
	char maxl[16];
#if HAVE_NSLDAP
	LDAPVersion ver;
	double SDKVersion;
	/* Print version information */
	SDKVersion = ldap_version( &ver );
#endif
	LDAP_TLS_VARS;

	if (LDAP_GLOBAL(max_links) == -1) {
		strcpy(maxl, "Unlimited");
	} else {
		snprintf(maxl, 15, "%ld", LDAP_GLOBAL(max_links));
		maxl[15] = 0;
	}

	php3_printf("<table>"
				"<tr><td>Total links:</td><td>%d/%s</td></tr>\n"
		        "<tr><td>RCS Version:</td><td>$Id: ldap.c,v 1.66 1999/02/01 02:31:10 sas Exp $</td></tr>\n"
#if HAVE_NSLDAP
				"<tr><td>SDK Version:</td><td>%f</td></tr>"
				"<tr><td>Highest LDAP Protocol Supported:</td><td>%f</td></tr>"
				"<tr><td>SSL Level Supported:</td><td>%f</td></tr>"
#endif
				,LDAP_GLOBAL(num_links),maxl
#if HAVE_NSLDAP
				,SDKVersion/100.0,ver.protocol_version/100.0,ver.SSL_version/100.0
#endif
				);
#if HAVE_NSLDAP
	if ( ver.security_level != LDAP_SECURITY_NONE ) {
	   php3_printf( "<tr><td>Level of encryption:</td><td>%d bits</td></tr>\n", ver.security_level );
	} else {
	   php3_printf( "<tr><td>SSL not enabled.</td><td></td></tr>\n" );
	}
#endif
	php3_printf("</table>\n");

}

/* {{{ proto int ldap_connect([string host [, int port]])
   Connect to an LDAP server */
void php3_ldap_connect(INTERNAL_FUNCTION_PARAMETERS)
{
	char *host;
	int port;
	/*	char *hashed_details;
	int hashed_details_length;*/
	LDAP *ldap;
	LDAP_TLS_VARS;

	switch(ARG_COUNT(ht)) {
		case 0: 
			host = NULL;
			port = 0;
			/* hashed_details = estrndup("ldap_", 5);
			hashed_details_length = 4+1; */
			break;

		case 1: {
				pval *yyhost;

				if (getParameters(ht, 1, &yyhost) == FAILURE) {
					RETURN_FALSE;
				}

				convert_to_string(yyhost);
				host = yyhost->value.str.val;
				port = 389; /* Default port */

				/* hashed_details_length = yyhost->value.str.len+4+1;
				hashed_details = emalloc(hashed_details_length+1); 
				sprintf(hashed_details, "ldap_%s", yyhost->value.str.val);*/ 
			}
			break;

		case 2: {
				pval *yyhost, *yyport;

				if (getParameters(ht, 2, &yyhost, &yyport) == FAILURE) {
					RETURN_FALSE;
				}

				convert_to_string(yyhost);
				host = yyhost->value.str.val;
				convert_to_long(yyport);
				port = yyport->value.lval;

			/* Do we need to take care of hosts running multiple LDAP servers ? */
				/*	hashed_details_length = yyhost->value.str.len+4+1;
				hashed_details = emalloc(hashed_details_length+1);
				sprintf(hashed_details, "ldap_%s", yyhost->value.str.val);*/ 
			}
			break;

		default:
			WRONG_PARAM_COUNT;
			break;
	}

	if (LDAP_GLOBAL(max_links)!=-1 && LDAP_GLOBAL(num_links)>=LDAP_GLOBAL(max_links)) {
	  php3_error(E_WARNING, "LDAP: Too many open links (%d)", LDAP_GLOBAL(num_links));
	  RETURN_FALSE;
	}

	ldap = ldap_open(host,port);
	if ( ldap == NULL ) {
	  RETURN_FALSE;
	} else {
	  RETURN_LONG(php3_list_insert((void*)ldap,LDAP_GLOBAL(le_link)));
	}

}
/* }}} */


static LDAP * _get_ldap_link(pval *link, HashTable *list)
{
	LDAP *ldap;
	int type;
	LDAP_TLS_VARS;

	convert_to_long(link);
	ldap = (LDAP *) php3_list_find(link->value.lval, &type);
	
	if (!ldap || !(type == LDAP_GLOBAL(le_link))) {
	  php3_error(E_WARNING, "%d is not a LDAP link index", link->value.lval);
	  return NULL;
	}
	return ldap;
}


static LDAPMessage * _get_ldap_result(pval *result, HashTable *list)
{
	LDAPMessage *ldap_result;
	int type;
	LDAP_TLS_VARS;

	convert_to_long(result);
	ldap_result = (LDAPMessage *) php3_list_find(result->value.lval, &type);

	if (!ldap_result || type != LDAP_GLOBAL(le_result)) {
		php3_error(E_WARNING, "%d is not a LDAP result index", result->value.lval);
		return NULL;
	}

	return ldap_result;
}


static LDAPMessage * _get_ldap_result_entry(pval *result, HashTable *list)
{
	LDAPMessage *ldap_result_entry;
	int type;
	LDAP_TLS_VARS;

	convert_to_long(result);
	ldap_result_entry = (LDAPMessage *) php3_list_find(result->value.lval, &type);

	if (!ldap_result_entry || type != LDAP_GLOBAL(le_result_entry)) {
		php3_error(E_WARNING, "%d is not a LDAP result entry index", result->value.lval);
		return NULL;
	}

	return ldap_result_entry;
}


static BerElement * _get_ber_entry(pval *berp, HashTable *list)
{
	BerElement *ber;
	int type;
	LDAP_TLS_VARS;

	convert_to_long(berp);
	ber = (BerElement *) php3_list_find(berp->value.lval, &type);

	if ( type != LDAP_GLOBAL(le_ber_entry)) {
		php3_error(E_WARNING, "%d is not a BerElement index", berp->value.lval);
		return NULL;
	}

	return ber;
}

#if 0
void php3_ber_free(INTERNAL_FUNCTION_PARAMETERS)
{
        pval *berp;
		
	if ( getParameters(ht,1,&berp) == FAILURE ) RETURN_FALSE;
	
	php3_list_delete(berp->value.lval);
	RETURN_TRUE;
}
#endif

/* {{{ proto int ldap_bind(int link [, string dn, string password])
   Bind to LDAP directory */
void php3_ldap_bind(INTERNAL_FUNCTION_PARAMETERS)
{
pval *link, *bind_rdn, *bind_pw;
char *ldap_bind_rdn, *ldap_bind_pw;
LDAP *ldap;

	switch(ARG_COUNT(ht)) {
		case 1: /* Anonymous Bind */
			if (getParameters(ht, 1, &link) == FAILURE) {
				RETURN_FALSE;
			}

			ldap_bind_rdn = NULL;
			ldap_bind_pw = NULL;

			break;

		case 3 :
			if (getParameters(ht, 3, &link, &bind_rdn, &bind_pw) == FAILURE) {
				RETURN_FALSE;
			}

			convert_to_string(bind_rdn);
			convert_to_string(bind_pw);

			ldap_bind_rdn = bind_rdn->value.str.val;
			ldap_bind_pw = bind_pw->value.str.val;

			break;

		default:
			WRONG_PARAM_COUNT;
			break;
	}	

	ldap = _get_ldap_link(link, list);
	if (ldap == NULL) RETURN_FALSE;

	if (ldap_bind_s(ldap, ldap_bind_rdn, ldap_bind_pw, LDAP_AUTH_SIMPLE) != LDAP_SUCCESS) {
#if !HAVE_NSLDAP
#if LDAP_API_VERSION > 2000
		/* New versions of OpenLDAP do it this way */
		php3_error(E_WARNING,"LDAP:  Unable to bind to server: %s",ldap_err2string(ldap_get_lderrno(ldap,NULL,NULL)));
#else
		php3_error(E_WARNING,"LDAP:  Unable to bind to server: %s",ldap_err2string(ldap->ld_errno));
#endif
#endif
		RETURN_FALSE;
	} else {
		RETURN_TRUE;
	}
}
/* }}} */

/* {{{ proto int ldap_close(int link)
   Alias to ldap_unbind */
/* }}} */

/* {{{ proto int ldap_unbind(int link)
   Unbind from LDAP directory */
void php3_ldap_unbind(INTERNAL_FUNCTION_PARAMETERS)
{
pval *link;
LDAP *ldap;

	if (ARG_COUNT(ht) != 1 || getParameters(ht, 1, &link) == FAILURE) {
		WRONG_PARAM_COUNT;
	}

	convert_to_long(link);

	ldap = _get_ldap_link(link, list);
	if (ldap == NULL) RETURN_FALSE;

	php3_list_delete(link->value.lval);
	RETURN_TRUE;
}
/* }}} */


static void php3_ldap_do_search(INTERNAL_FUNCTION_PARAMETERS, int scope)
{
	pval *link, *base_dn, *filter, *attrs, *attr;
	char *ldap_base_dn, *ldap_filter;
	LDAP *ldap;
	char **ldap_attrs = NULL; 
	int attrsonly;
	LDAPMessage *ldap_result;
	int num_attribs=0, i;
	LDAP_TLS_VARS;

	switch(ARG_COUNT(ht)) {
		case 3 :
			if (getParameters(ht, 3, &link, &base_dn, &filter) == FAILURE) {
				RETURN_FALSE;
			}

			convert_to_string(base_dn);
			convert_to_string(filter);

			ldap_base_dn = base_dn->value.str.val;
			ldap_filter = filter->value.str.val;

			break;

		case 4 : 
			if (getParameters(ht, 4, &link, &base_dn, &filter, &attrs) == FAILURE) {
				RETURN_FALSE;
			}

			if (attrs->type != IS_ARRAY) {
				php3_error(E_WARNING, "LDAP: Expected Array as last element");
				RETURN_FALSE;
			}

			convert_to_string(base_dn);
			convert_to_string(filter);

			ldap_base_dn = base_dn->value.str.val;
			ldap_filter = filter->value.str.val;

			num_attribs = _php3_hash_num_elements(attrs->value.ht);
			if ((ldap_attrs = emalloc((num_attribs+1) * sizeof(char *))) == NULL) {
				php3_error(E_WARNING, "LDAP: Could not allocate memory");
				RETURN_FALSE;
				return;
			}

			for(i=0; i<num_attribs; i++) {
				if (_php3_hash_index_find(attrs->value.ht, i, (void **) &attr) == FAILURE) {
					php3_error(E_WARNING, "LDAP: Array initialization wrong");
					RETURN_FALSE;
					return;
				}
				convert_to_string(attr);
				ldap_attrs[i] = attr->value.str.val;
			}
			ldap_attrs[num_attribs] = NULL;

			break;

		default:
			WRONG_PARAM_COUNT;
			break;
	}

	/* fix to make null base_dn's work */
	if ( strlen(ldap_base_dn) < 1 ) {
	  ldap_base_dn = NULL;
	}

	ldap = _get_ldap_link(link, list);
	if (ldap == NULL) RETURN_FALSE;

	/* Is it useful to only get the attributes ? */
	attrsonly = 0;	

	/* We can possibly add the timeout value also */

	if (ldap_search_s(ldap, ldap_base_dn, scope, ldap_filter, ldap_attrs, attrsonly, &ldap_result) != LDAP_SUCCESS) {
#if !HAVE_NSLDAP
#if LDAP_API_VERSION > 2000
		php3_error(E_WARNING,"LDAP:  Unable to perform the search: %s",ldap_err2string(ldap_get_lderrno(ldap,NULL,NULL)));
#else
		php3_error(E_WARNING, "LDAP: Unable to perform the search: %s", ldap_err2string(ldap->ld_errno));
#endif
#endif
		RETVAL_FALSE;
	} else  {
		RETVAL_LONG(php3_list_insert(ldap_result, LDAP_GLOBAL(le_result)));
	}

	if (ldap_attrs != NULL) {
	  /*	for(i=0; i<num_attribs; i++) efree(ldap_attrs[i]); */
		efree(ldap_attrs);
	}
	return;
}

/* {{{ proto int ldap_read(int link, string base_dn, string filter [, array attributes])
   Read an entry */
void php3_ldap_read(INTERNAL_FUNCTION_PARAMETERS)
{
	php3_ldap_do_search(INTERNAL_FUNCTION_PARAM_PASSTHRU, LDAP_SCOPE_BASE);
}
/* }}} */

/* {{{ proto int ldap_list(int link, string base_dn, string filter [, array attributes])
   Single-level search */
void php3_ldap_list(INTERNAL_FUNCTION_PARAMETERS)
{
	php3_ldap_do_search(INTERNAL_FUNCTION_PARAM_PASSTHRU, LDAP_SCOPE_ONELEVEL);
}
/* }}} */


/* {{{ proto int ldap_search(int link, string base_dn, string filter [, array attributes])
   Search LDAP tree under base_dn */
void php3_ldap_search(INTERNAL_FUNCTION_PARAMETERS)
{
	php3_ldap_do_search(INTERNAL_FUNCTION_PARAM_PASSTHRU, LDAP_SCOPE_SUBTREE);
}
/* }}} */

/* {{{ proto int ldap_free_result(int result)
   Free result memory */
void php3_ldap_free_result(INTERNAL_FUNCTION_PARAMETERS)
{
pval *result;
LDAPMessage *ldap_result;

	if (ARG_COUNT(ht) != 1 || getParameters(ht, 1, &result) == FAILURE) {
		WRONG_PARAM_COUNT;
	}

	ldap_result = _get_ldap_result(result, list);
	if (ldap_result == NULL) {
		RETVAL_FALSE;
	} else {
		php3_list_delete(result->value.lval);  /* Delete list entry and call registered destructor function */
		RETVAL_TRUE;
	}
	return;
}
/* }}} */

/* {{{ proto int ldap_count_entries(int link, int result)
   Count the number of entries in a search result */
void php3_ldap_count_entries(INTERNAL_FUNCTION_PARAMETERS)
{
pval *result, *link;
LDAP *ldap;
LDAPMessage *ldap_result;

	if (ARG_COUNT(ht) != 2 || getParameters(ht, 2, &link, &result) == FAILURE) {
		WRONG_PARAM_COUNT;
	}

	ldap = _get_ldap_link(link, list);
	if (ldap == NULL) RETURN_FALSE;

	ldap_result = _get_ldap_result(result, list);
	if (ldap_result == NULL) RETURN_FALSE;

	RETURN_LONG(ldap_count_entries(ldap, ldap_result));
}
/* }}} */


/* {{{ proto int ldap_sort_entries(int link, int result, string attr)
   Sort the entries in a search result */
void php3_ldap_sort_entries(INTERNAL_FUNCTION_PARAMETERS)
{
pval *result, *link, *attr;
LDAP *ldap;
LDAPMessage *ldap_result;
char *attribute;


	if (ARG_COUNT(ht) != 3 || getParameters(ht, 3, &link, &result, &attr) == FAILURE) {
		WRONG_PARAM_COUNT;
	}

	ldap = _get_ldap_link(link, list);
	if (ldap == NULL) RETURN_FALSE;

	ldap_result = _get_ldap_result(result, list);
	if (ldap_result == NULL) RETURN_FALSE;

	convert_to_string(attr);
	attribute = attr->value.str.val;
	
	if (attribute != NULL){
		//extern int strcasecmp();
	if ( ldap_sort_entries( ldap, &ldap_result, ( *attribute == '\0' ) ? NULL : attribute, strcasecmp ) != LDAP_SUCCESS ) {
		RETURN_FALSE;
	} else { 
		RETURN_TRUE;
	}
	}
}
/* }}} */




/* {{{ proto int ldap_first_entry(int link, int result)
   Return first result id */
void php3_ldap_first_entry(INTERNAL_FUNCTION_PARAMETERS)
{
	pval *result, *link;
	LDAP *ldap;
	LDAPMessage *ldap_result;
	LDAPMessage *ldap_result_entry;
	LDAP_TLS_VARS;

	if (ARG_COUNT(ht) != 2 || getParameters(ht, 2, &link, &result) == FAILURE) {
		WRONG_PARAM_COUNT;
	}

	ldap = _get_ldap_link(link, list);
	if (ldap == NULL) RETURN_FALSE;

	ldap_result = _get_ldap_result(result, list);
	if (ldap_result == NULL) RETURN_FALSE;

	if ((ldap_result_entry = ldap_first_entry(ldap, ldap_result)) == NULL) {
		RETURN_FALSE;
	} else {
		RETURN_LONG(php3_list_insert(ldap_result_entry, LDAP_GLOBAL(le_result_entry)));
	}
}
/* }}} */

/* {{{ proto int ldap_next_entry(int link, int entry)
   Get next result entry */
void php3_ldap_next_entry(INTERNAL_FUNCTION_PARAMETERS)
{
	pval *result_entry, *link;
	LDAP *ldap;
	LDAPMessage *ldap_result_entry, *ldap_result_entry_next;
	LDAP_TLS_VARS;

	if (ARG_COUNT(ht) != 2 || getParameters(ht, 2, &link, &result_entry) == FAILURE) {
		WRONG_PARAM_COUNT;
	}

	ldap = _get_ldap_link(link, list);
	if (ldap == NULL) RETURN_FALSE;

	ldap_result_entry = _get_ldap_result_entry(result_entry, list);
	if (ldap_result_entry == NULL) RETURN_FALSE;

	if ((ldap_result_entry_next = ldap_next_entry(ldap, ldap_result_entry)) == NULL) {
		RETURN_FALSE;
	} else {
		RETURN_LONG(php3_list_insert(ldap_result_entry_next, LDAP_GLOBAL(le_result_entry)));
	}
}
/* }}} */

/* {{{ proto array ldap_get_entries(int link, int result)
   Get all result entries */
void php3_ldap_get_entries(INTERNAL_FUNCTION_PARAMETERS)
{
pval *link, *result;
LDAPMessage *ldap_result, *ldap_result_entry;
pval tmp1, tmp2;
LDAP *ldap;
int num_entries, num_attrib, num_values, i;
int attr_count, entry_count;
BerElement *ber;
char *attribute;
char **ldap_value;
char *dn;

	if (ARG_COUNT(ht) != 2 || getParameters(ht, 2, &link, &result) == FAILURE) {
		WRONG_PARAM_COUNT;
	}

	ldap = _get_ldap_link(link, list);
	if (ldap == NULL) RETURN_FALSE;

	ldap_result = _get_ldap_result(result, list);
	if (ldap_result == NULL) RETURN_FALSE;

	num_entries = ldap_count_entries(ldap, ldap_result);

	array_init(return_value);
	add_assoc_long(return_value, "count", num_entries);

	if (num_entries == 0) return;
	
	ldap_result_entry = ldap_first_entry(ldap, ldap_result);
	if (ldap_result_entry == NULL) RETURN_FALSE;
	
	entry_count = 0;

	while(ldap_result_entry != NULL) {

		num_attrib = 0;
		attribute = ldap_first_attribute(ldap, ldap_result_entry, &ber);
		if (attribute == NULL) RETURN_FALSE;
		while (attribute != NULL) {
			num_attrib++;
			attribute = ldap_next_attribute(ldap, ldap_result_entry, ber);
		}

		array_init(&tmp1);

		attr_count = 0;
		attribute = ldap_first_attribute(ldap, ldap_result_entry, &ber);
		while (attribute != NULL) {
			ldap_value = ldap_get_values(ldap, ldap_result_entry, attribute);
			num_values = ldap_count_values(ldap_value);

			array_init(&tmp2);
			add_assoc_long(&tmp2, "count", num_values);
			for(i=0; i<num_values; i++) {
				add_index_string(&tmp2, i, ldap_value[i], 1);
			}	
			ldap_value_free(ldap_value);

			_php3_hash_update(tmp1.value.ht, _php3_strtolower(attribute), strlen(attribute)+1, (void *) &tmp2, sizeof(pval), NULL);
			add_index_string(&tmp1, attr_count, attribute, 1);

			attr_count++;
			attribute = ldap_next_attribute(ldap, ldap_result_entry, ber);
		}

		add_assoc_long(&tmp1, "count", num_attrib);
		dn = ldap_get_dn(ldap, ldap_result_entry);
		add_assoc_string(&tmp1, "dn", dn, 1);

		_php3_hash_index_update(return_value->value.ht, entry_count, (void *) &tmp1, sizeof(pval), NULL);
		
		entry_count++;
		ldap_result_entry = ldap_next_entry(ldap, ldap_result_entry);
	}

	add_assoc_long(return_value, "count", num_entries);
}
/* }}} */

/* {{{ proto string ldap_first_attribute(int link, int result, int ber)
   Return first attribute */
void php3_ldap_first_attribute(INTERNAL_FUNCTION_PARAMETERS)
{
	pval *result,*link,*berp;
	LDAP *ldap;
	LDAPMessage *ldap_result_entry;
	BerElement *ber;
	char *attribute;
	LDAP_TLS_VARS;

	if (ARG_COUNT(ht) != 3 || getParameters(ht, 3, &link, &result,&berp) == FAILURE || ParameterPassedByReference(ht,3)==0 ) {
		WRONG_PARAM_COUNT;
	}

	ldap = _get_ldap_link(link,list);
	if (ldap == NULL) RETURN_FALSE;

	ldap_result_entry = _get_ldap_result_entry(result,list);
	if (ldap_result_entry == NULL) RETURN_FALSE;

	if ((attribute = ldap_first_attribute(ldap, ldap_result_entry, &ber)) == NULL) {
		RETURN_FALSE;
	} else {
		/* brep is passed by ref so we do not have to account for memory */
		berp->type=IS_LONG;
		berp->value.lval=php3_list_insert(ber, LDAP_GLOBAL(le_ber_entry));

		RETVAL_STRING(attribute,1);
#ifdef WINDOWS
		ldap_memfree(attribute);
#endif
	}
}
/* }}} */

/* {{{ proto string ldap_next_attribute(int link, int result, int ber)
   Get the next attribute in result */
void php3_ldap_next_attribute(INTERNAL_FUNCTION_PARAMETERS)
{
pval *result,*link,*berp;
LDAP *ldap;
LDAPMessage *ldap_result_entry;
BerElement *ber;
char *attribute;

	if (ARG_COUNT(ht) != 3 || getParameters(ht, 3, &link, &result,&berp) == FAILURE ) {
		WRONG_PARAM_COUNT;
	}

	ldap = _get_ldap_link(link,list);
	if (ldap == NULL) RETURN_FALSE;

	ldap_result_entry = _get_ldap_result_entry(result,list);
	if (ldap_result_entry == NULL) RETURN_FALSE;

	ber = _get_ber_entry(berp,list);

	if ((attribute = ldap_next_attribute(ldap, ldap_result_entry, ber)) == NULL) {
		RETURN_FALSE;
	} else {
		RETVAL_STRING(attribute,1);
#ifdef WINDOWS
		ldap_memfree(attribute);
#endif
	}
}
/* }}} */

/* {{{ proto array ldap_get_attributes(int link, int result)
   Get attributes from a search result entry */
void php3_ldap_get_attributes(INTERNAL_FUNCTION_PARAMETERS)
{
pval *link, *result_entry;
pval tmp;
LDAP *ldap;
LDAPMessage *ldap_result_entry;
char *attribute;
char **ldap_value;
int i, count, num_values, num_attrib;
BerElement *ber;

	if (ARG_COUNT(ht) != 2 || getParameters(ht, 2, &link, &result_entry) == FAILURE) {
		WRONG_PARAM_COUNT;
	}

	ldap = _get_ldap_link(link, list);
	if (ldap == NULL) RETURN_FALSE;

	ldap_result_entry = _get_ldap_result_entry(result_entry, list);
	if (ldap_result_entry == NULL) RETURN_FALSE;

	num_attrib = 0;
	attribute = ldap_first_attribute(ldap, ldap_result_entry, &ber);
	if (attribute == NULL) RETURN_FALSE;
	while (attribute != NULL) {
		num_attrib++;
		attribute = ldap_next_attribute(ldap, ldap_result_entry, ber);
	}
	
	array_init(return_value);

	count=0;
	attribute = ldap_first_attribute(ldap, ldap_result_entry, &ber);
	while (attribute != NULL) {
		ldap_value = ldap_get_values(ldap, ldap_result_entry, attribute);
		num_values = ldap_count_values(ldap_value);

		array_init(&tmp);
		add_assoc_long(&tmp, "count", num_values);
		for(i=0; i<num_values; i++) {
			add_index_string(&tmp, i, ldap_value[i], 1);
		}
		ldap_value_free(ldap_value);

		_php3_hash_update(return_value->value.ht, attribute, strlen(attribute)+1, (void *) &tmp, sizeof(pval), NULL);
		add_index_string(return_value, count, attribute, 1);

		count++;
		attribute = ldap_next_attribute(ldap, ldap_result_entry, ber);
	}
	
	add_assoc_long(return_value, "count", num_attrib);
}
/* }}} */

/* {{{ proto array ldap_get_values(int link, int result, string attribute)
   Get all values from a result entry */
void php3_ldap_get_values(INTERNAL_FUNCTION_PARAMETERS)
{
pval *link, *result_entry, *attr;
LDAP *ldap;
LDAPMessage *ldap_result_entry;
char *attribute;
char **ldap_value;
int i, num_values;

	if (ARG_COUNT(ht) != 3 || getParameters(ht, 3, &link, &result_entry, &attr) == FAILURE) {
		WRONG_PARAM_COUNT;
	}

	ldap = _get_ldap_link(link, list);
	if (ldap == NULL) RETURN_FALSE;

	ldap_result_entry = _get_ldap_result_entry(result_entry, list);
	if (ldap_result_entry == NULL) RETURN_FALSE;

	convert_to_string(attr);
	attribute = attr->value.str.val;

	if ((ldap_value = ldap_get_values(ldap, ldap_result_entry, attribute)) == NULL) {
#if !HAVE_NSLDAP
#if LDAP_API_VERSION > 2000
		php3_error(E_WARNING, "LDAP: Cannot get the value(s) of attribute %s", ldap_err2string(ldap_get_lderrno(ldap,NULL,NULL)));
#else
		php3_error(E_WARNING, "LDAP: Cannot get the value(s) of attribute %s", ldap_err2string(ldap->ld_errno));
#endif
#endif
		RETURN_FALSE;
	}

	num_values = ldap_count_values(ldap_value);

	if (array_init(return_value) == FAILURE) {
		RETURN_FALSE;
	}

	for(i=0; i<num_values; i++) {
		add_next_index_string(return_value, ldap_value[i], 1);
	}
	
	add_assoc_long(return_value, "count", num_values);

	ldap_value_free(ldap_value);
}
/* }}} */

/* {{{ proto string ldap_get_dn(int link, int result)
   Get the DN of a result entry */
void php3_ldap_get_dn(INTERNAL_FUNCTION_PARAMETERS) 
{
	pval *link,*entryp;
	LDAP *ld;
	LDAPMessage *entry;
	char *text;

	if (ARG_COUNT(ht) != 2 || getParameters(ht, 2, &link, &entryp) == FAILURE) {
		WRONG_PARAM_COUNT;
	}
	
	ld = _get_ldap_link(link, list);
	if (ld == NULL) RETURN_FALSE;

	entry = _get_ldap_result_entry(entryp, list);
	if (entry == NULL) RETURN_FALSE;

	text = ldap_get_dn(ld, entry);
	if ( text != NULL ) {
		RETVAL_STRING(text,1);
#ifdef WINDOWS
		ldap_memfree(text);
#endif
	} else {
		RETURN_FALSE;
	}
}
/* }}} */

/* {{{ proto array ldap_explode_dn(string dn, int with_attrib)
   Splits DN into its component parts */
void php3_ldap_explode_dn(INTERNAL_FUNCTION_PARAMETERS)
{
pval *dn, *with_attrib;
char **ldap_value;
int i, count;

	if (ARG_COUNT(ht) != 2 || getParameters(ht, 2, &dn, &with_attrib)== FAILURE) {
		WRONG_PARAM_COUNT;
	}

	convert_to_string(dn);
	convert_to_long(with_attrib);

	ldap_value = ldap_explode_dn(dn->value.str.val, with_attrib->value.lval);

	i=0;
	while(ldap_value[i] != NULL) i++;
	count = i;

	if (array_init(return_value) == FAILURE) {
		RETURN_FALSE;
	}

	add_assoc_long(return_value, "count", count);
	for(i=0; i<count; i++) {
		add_index_string(return_value, i, ldap_value[i], 1);
	}

	ldap_value_free(ldap_value);
}
/* }}} */

/* {{{ proto string ldap_dn2ufn(string dn)
   Convert DN to User Friendly Naming format */
void php3_ldap_dn2ufn(INTERNAL_FUNCTION_PARAMETERS)
{
	pval *dn;
	char *ufn;

	if (ARG_COUNT(ht) !=1 || getParameters(ht,1,&dn)==FAILURE) {
		WRONG_PARAM_COUNT;
	}
	
	convert_to_string(dn);
	
	ufn = ldap_dn2ufn(dn->value.str.val);
	
	if (ufn !=NULL) {
		RETVAL_STRING(ufn,1);
#ifdef WINDOWS
		ldap_memfree(ufn);
#endif
	} else {
		RETURN_FALSE;
	}
}
/* }}} */
	

static void php3_ldap_do_modify(INTERNAL_FUNCTION_PARAMETERS, int oper)
{
pval *link, *dn, *entry, *value, *ivalue;
LDAP *ldap;
char *ldap_dn;
LDAPMod **ldap_mods;
int i, j, num_attribs, num_values;
char *attribute;
ulong index;

	if (ARG_COUNT(ht) != 3 || getParameters(ht, 3, &link, &dn, &entry) == FAILURE) {
		WRONG_PARAM_COUNT;
	}	

	if (entry->type != IS_ARRAY) {
		php3_error(E_WARNING, "LDAP: Expected Array as the last element");
		RETURN_FALSE;
	}

	ldap = _get_ldap_link(link, list);
	if (ldap == NULL) RETURN_FALSE;

	convert_to_string(dn);
	ldap_dn = dn->value.str.val;

	num_attribs = _php3_hash_num_elements(entry->value.ht);

	ldap_mods = emalloc((num_attribs+1) * sizeof(LDAPMod *));

	_php3_hash_internal_pointer_reset(entry->value.ht);

	for(i=0; i<num_attribs; i++) {
		ldap_mods[i] = emalloc(sizeof(LDAPMod));

		ldap_mods[i]->mod_op = oper;

		if (_php3_hash_get_current_key(entry->value.ht, &attribute, &index) == HASH_KEY_IS_STRING) {
			ldap_mods[i]->mod_type = estrdup(attribute);
			efree(attribute);
		} else {
			php3_error(E_WARNING, "LDAP: Unknown Attribute in the data");
		}

		_php3_hash_get_current_data(entry->value.ht, (void **) &value);

		if (value->type != IS_ARRAY) {
			num_values = 1;
		} else {
			num_values = _php3_hash_num_elements(value->value.ht);
		}

		ldap_mods[i]->mod_values = emalloc((num_values+1) * sizeof(char *));
		
		if (num_values == 1) {
			convert_to_string(value);
			ldap_mods[i]->mod_values[0] = value->value.str.val;
		} else {	
			for(j=0; j<num_values; j++) {
				_php3_hash_index_find(value->value.ht, j, (void **) &ivalue);
				convert_to_string(ivalue);
				ldap_mods[i]->mod_values[j] = ivalue->value.str.val;
			}
		}
		ldap_mods[i]->mod_values[num_values] = NULL;

		_php3_hash_move_forward(entry->value.ht);
	}
	ldap_mods[num_attribs] = NULL;

	if (oper == LDAP_MOD_ADD) {
		if (ldap_add_s(ldap, ldap_dn, ldap_mods) != LDAP_SUCCESS) {
			ldap_perror(ldap, "LDAP");
			php3_error(E_WARNING, "LDAP: add operation could not be completed.");
			RETVAL_FALSE;
		} else RETVAL_TRUE;
	} else {
		if (ldap_modify_s(ldap, ldap_dn, ldap_mods) != LDAP_SUCCESS) {
			php3_error(E_WARNING, "LDAP: modify operation could not be completed.");
			RETVAL_FALSE;
		} else RETVAL_TRUE;	
	}

	for(i=0; i<num_attribs; i++) {
		efree(ldap_mods[i]->mod_type);
		efree(ldap_mods[i]->mod_values);
		efree(ldap_mods[i]);
	}
	efree(ldap_mods);	

	return;
}

/* {{{ proto int ldap_add(int link, string dn, array entry)
   Add entries to LDAP directory */
void php3_ldap_add(INTERNAL_FUNCTION_PARAMETERS)
{
	php3_ldap_do_modify(INTERNAL_FUNCTION_PARAM_PASSTHRU, LDAP_MOD_ADD);
}
/* }}} */


/* {{{ proto int ldap_modify(int link, string dn, array entry)
   Modify an LDAP entry */
void php3_ldap_modify(INTERNAL_FUNCTION_PARAMETERS)
{
	php3_ldap_do_modify(INTERNAL_FUNCTION_PARAM_PASSTHRU, LDAP_MOD_REPLACE); 
}
/* }}} */

/* {{{ proto int ldap_delete(int link, string dn)
   Delete an entry from a directory */
void php3_ldap_delete(INTERNAL_FUNCTION_PARAMETERS)
{
pval *link, *dn;
LDAP *ldap;
char *ldap_dn;

	if (ARG_COUNT(ht) != 2 || getParameters(ht, 2, &link, &dn) == FAILURE) {
		WRONG_PARAM_COUNT;
	}

	ldap = _get_ldap_link(link, list);
	if (ldap == NULL) RETURN_FALSE;

	convert_to_string(dn);
	ldap_dn = dn->value.str.val;

	if (ldap_delete_s(ldap, ldap_dn) != LDAP_SUCCESS) {
		ldap_perror(ldap, "LDAP");
		RETURN_FALSE;
	}

	RETURN_TRUE;
}
/* }}} */

#endif
