/*
 * The Cryptonit security software suite is developped by IDEALX
 * Cryptonit Team (http://IDEALX.org/ and http://cryptonit.org).
 *
 * Copyright 2003-2006 IDEALX
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 as published by the Free Software Foundation.
 * 
 * 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., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301, USA. 
 *
 * In addition, as two special exceptions:
 *
 * 1) IDEALX S.A.S gives permission to:
 *  * link the code of portions of his program with the OpenSSL library under
 *    certain conditions described in each source file
 *  * distribute linked combinations including the two, with respect to the
 *    OpenSSL license and with the GPL
 *
 * You must obey the GNU General Public License in all respects for all of the
 * code used other than OpenSSL. If you modify file(s) with this exception,
 * you may extend this exception to your version of the file(s), but you are
 * not obligated to do so. If you do not wish to do so, delete this exception
 * statement from your version, in all files (this very one along with all
 * source files).

 * 2) IDEALX S.A.S acknowledges that portions of his sourcecode uses (by the
 * way of headers inclusion) some work published by 'RSA Security Inc.'. Those
 * portions are "derived from the RSA Security Inc. PKCS #11Cryptographic
 * Token Interface (Cryptoki)" as described in each individual source file.
 */

#include "LDAP.hh"

#include <iostream>
#include <string>

#include <stdlib.h> // Pour le atoi()
#include <sys/time.h> // struct timeval (timeout de la connexion LDAP)

namespace Cryptonit
{

ldap::ldap()
{
    ldap_session = NULL;
    current_host = "localhost";
    current_host = 389;
    current_base = "cn=*";
    setTimeout(20);
    setScope(LDAP_SCOPE_SUBTREE);
    v2compatibility=0;
}



ldap::ldap( const std::string uri )
{
    ldap_session = NULL;
    current_host = "localhost";
    current_host = 389;
    current_base = "cn=*";
    setTimeout(20);
    setScope(LDAP_SCOPE_SUBTREE);
    v2compatibility=0;

    // Strip the "ldap://" part if presents.
    std::string params( uri );
    unsigned int i = params.find("ldap://");
    if( i != std::string::npos)
	params.erase(i, strlen("ldap://") );
    
    unsigned int j = params.find_first_of(":");
    unsigned int k = params.find_first_of("/");
    
    // Check if port number is specified
    std::string port = ""; // "" wil be interpreted as the default port
    if( j != std::string::npos && j < k ) 
	port = std::string( params, j+1, (k-1)-j );
    
    // Retrieve server name
    std::string server;
    if( j < k )
	server = std::string(params, 0, j);
    else
	server = std::string(params, 0, k);

    // All characters after the first '/'
    params = std::string( params, k+1, params.size() );
    //  A FAIRE !!!
    //std::string p[] = {server, port }
    //read( p );
}




// Initialize a new LDAP connection if 'ldap_session' is NULL
// 'ldap_session != NULL' is interpreted like an already in use connection
// Return a new LDAP connection or NULL if it was failed.
  LDAP* ldap::init_ldap_session( const std::string host, int port, int v2compatibility )
{
  // set LDAP version3 by default
    int protocol_version=LDAP_VERSION3;
    if( ldap_session != NULL ) // Session may be already in use
	return NULL; 

    ldap_session = ldap_init( host.c_str(), port );
    if(v2compatibility){
      protocol_version=LDAP_VERSION2;
    }

    ldap_set_option( ldap_session, LDAP_OPT_PROTOCOL_VERSION,&protocol_version);

    if( ldap_simple_bind( ldap_session, NULL, NULL ) == -1 ) {
	ldap_perror( ldap_session, "Oops ldad_simple_bind()");
	ldap_memfree( ldap_session );
	return NULL;
    }
    return ldap_session;
}



// Add all attibutes of 'entry' with their values in the hash table.
bool ldap::add_entry( LDAP* ld, LDAPMessage *entry )
{
    BerElement *berptr = NULL;
    char *attr, *dn;
    
    dn = ldap_get_dn( ld, entry );

  
    for( attr = ldap_first_attribute( ld, entry, &berptr );
	 attr != NULL;
	 attr = ldap_next_attribute( ld, entry, berptr ) ) {
	char **values;
	int i;
	
	values = ldap_get_values( ld, entry, attr );
	
	for( i = 0; values[i] != NULL; i++ ) {

	    if( strstr( attr, ";binary" ) != NULL ) {
		if( ! append( std::string(dn), std::string(attr),
					  std::string(values[i], AttributeBinary ) ) ) {
#ifdef DEBUG
 		  std::cerr << "Oops: impossible d'ajouter un nouvel attribut"<< std::endl;
#endif 
		    return false;
		}
	    }

	    else {
		if( ! append( std::string(dn), std::string(attr), std::string(values[i]) ) ) {
#ifdef DEBUG
 		    std::cerr << "Oops: impossible d'ajouter un nouvel attribut"<< std::endl;
#endif 
		    return false;
		}
	    }

	}
	
	
	ldap_value_free( values );
	ldap_memfree( attr );
    }

    ber_free( berptr, 1 );
    ldap_memfree( dn );

    return true;
} 




bool ldap::bind( const std::string params[] )
{
    std::string host, base, filter, scope;
    int port;

    host = params[0];
    base = params[3];
    scope = params[5];

    if( params[1] == "" )
	port = LDAP_DEFAULT_PORT;
    else
	port = atoi(params[1].c_str());

    setScope( scope ); 

    if( host == "" || base == "" || port < 1 || port > 65536 )
	// We don't have to check 'scope' since it will be correctly set
	// in any case by setScope()
	return false;

    current_host = host;
    current_port = port;
    current_base = base;

    ldap_session = init_ldap_session( host, port, v2compatibility );
    if( ldap_session == NULL ) return false;

    return true;
}



bool ldap::findExpression( const std::string exp )
{
    static LDAPMessage *result = NULL;
    //    static LDAPMessage *entry;
    static int msgid;
    struct timeval *timeout = NULL;

    if( ldap_session != NULL ) // Session may be already in use
	return false;

    if( exp == "" ) return false;

    msgid = ldap_search( ldap_session, NULL, getScope(), exp.c_str(), NULL, 0);

    timeout = (struct timeval*) malloc (sizeof(struct timeval));
    if( timeout == NULL ) {
#ifdef DEBUG
 	std::cerr << "Oops allocation de timeout" << std::endl;
#endif 
	return false;
    }
    timeout->tv_sec = getTimeout();
    timeout->tv_usec = 0;


    if( ldap_result( ldap_session, msgid, 1, timeout, &result ) == -1 ) {

	ldap_perror(ldap_session, "ldap_result");
	free( timeout );
	ldap_unbind( ldap_session );
	ldap_memfree( ldap_session );

	return false;
    }
    return true;
}


Entry* ldap::getEntry( const std::string name )
{
    LDAPMessage *entry = NULL, *result = NULL;
    BerElement *berptr = NULL;
    int msgid;


    if( ldap_session == NULL || name == "" )
	return NULL;


    msgid = ldap_search( ldap_session, current_base.c_str(), getScope(), name.c_str(), NULL, 0);


    struct timeval *timeout = NULL;
    timeout = (struct timeval*) malloc (sizeof(struct timeval));
    if( timeout == NULL ) {
#ifdef DEBUG
 	std::cerr << "Oops allocation de timeout" << std::endl;
#endif 
	return NULL;
    }
    timeout->tv_sec = getTimeout();
    timeout->tv_usec = 0;


    // Tester l'expiration du timeout, retourne 0
    if( ldap_result( ldap_session, msgid, 1, timeout, &result ) == -1 ) {

	ldap_perror(ldap_session, "ldap_result");
	free( timeout );
	ldap_unbind( ldap_session );
	ldap_memfree( ldap_session );

	return NULL;
    }
    


//     for( entry = ldap_first_entry( ldap_session, result );
// 	 entry != NULL; 
// 	 entry = ldap_next_entry(ldap_session, entry) ) {
    // On se limite à la première entrée
    entry = ldap_first_entry( ldap_session, result );
    if( entry != NULL ) {
	Entry *dse = new Entry();
	char *attr;

	for( attr = ldap_first_attribute( ldap_session, entry, &berptr );
	     attr != NULL;
	     attr = ldap_next_attribute( ldap_session, entry, berptr ) ) {
	    
	    
	    if( strstr( attr, ";binary" ) != NULL ) {
		struct berval **bv;
		
		bv = ldap_get_values_len( ldap_session, entry, attr );
		
		for( int i = 0; bv[i]->bv_val != NULL; i++ )
		    dse->append( attr, 
				 new Attribute(bv[i]->bv_val, 
							       bv[i]->bv_len, 
							       AttributeBinary) 
				 );

		ldap_value_free_len( bv );
	    }

	    else {
		char **values;
		
		values = ldap_get_values( ldap_session, entry, attr );
		
		for( int i = 0; values[i] != NULL; i++ )
		    dse->append( attr, new Attribute(values[i]) );

		ldap_value_free( values );
	    }		    
	}


	ldap_msgfree( entry );
	ldap_msgfree( result );
	free( timeout );

	return dse;
    }

    
    if( result != NULL ) ldap_msgfree( result );
    free(timeout);
    return NULL;
}



unsigned int ldap::getTimeout()
{
    return timeout;
}

void ldap::setTimeout( unsigned int t )
{
    timeout = t;
}

unsigned int ldap::retainLDAPv2(){
  if(v2compatibility)
    return 1;
  else
    return 0;
}

void ldap::retainLDAPv2(int b) {
  v2compatibility=b;
}

int ldap::getScope()
{
    return current_scope;
}

void ldap::setScope( int s )
{
    if( s == LDAP_SCOPE_BASE || s == LDAP_SCOPE_ONELEVEL || s == LDAP_SCOPE_SUBTREE )
	current_scope = s;
    else
	current_scope = LDAP_SCOPE_BASE;
}

void ldap::setScope( const std::string s )
{
    if( s == "LDAP_SCOPE_BASE" )
	current_scope = LDAP_SCOPE_BASE;
    else if ( s == "LDAP_SCOPE_ONELEVEL" )
	current_scope = LDAP_SCOPE_ONELEVEL;
    else if ( s == "LDAP_SCOPE_SUBTREE" )
	current_scope = LDAP_SCOPE_SUBTREE;
    else
	current_scope = LDAP_SCOPE_SUBTREE;
}


} // Namespace
