/*
 * 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 "LDAPCache.hh"

//#include <string>
#include <iostream>

#include <stdlib.h> // atoi()
#include <sys/time.h> // struct timeval (LDAP connection timeout)

#include <string.h> // strstr

namespace Cryptonit
{


std::string removeSpaces( const std::string& s )
{
    if( s.length() == 0 )
	return s;
    int b = s.find_first_not_of(" \t");
    int e = s.find_last_not_of(" \t");
    if(b == -1) // No non-spaces
	return "";
    return std::string(s, b, e - b + 1);
}



std::string getBeforeSep( std::string& src, const std::string separator )
{
    std::string result = "";

    if( src != "" && separator != "" ) {
	unsigned int i = src.find_first_of(separator);
	if( i == std::string::npos ) i = src.size();
	result = std::string(src, 0, i);
	src.erase( 0, result.size() + 1 );
    }
    return result;
}



bool LDAPCache::parseURI( const std::string uri, 
			  std::string& server, std::string& port,
			  std::string& dn, std::string& scope, std::string& filter, 
			  std::vector<std::string>& attributes )
{
    // This rudimentary code tries to follow the rfc2255

    std::string params( uri );

    // Checks if the 'scheme' part is "ldapcache"
//     if( params.find_first_of("://") != std::string::npos ) {
// 	if( params.find("ldapcache://") == std::string::npos ) 
// 	    return false;
//     }


    // Strip the "ldapcache://" part if presents.
    unsigned int i = params.find("ldapcache://");
    if( i != std::string::npos)
	params.erase(i, strlen("ldapcache://") );


    unsigned int j = params.find_first_of(":");
    unsigned int k = params.find_first_of("/");
    
    // Check if port number is specified
    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
    server = "localhost";
    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() );

    // Retrieves the DN value
    dn = getBeforeSep( params, "?" );

    // Retrieves the attribute list
    std::string attribute( getBeforeSep( params, "?" ) );
    std::string buffer;
    do {
	attribute = removeSpaces( attribute );
	buffer = getBeforeSep( attribute, "," );
	attributes.push_back( buffer );
    } while( attribute != "" );

    // Retrieves the scope value
    scope = getBeforeSep( params, "?" );

    // Retrieves the scope value
    filter = getBeforeSep( params, "?" );

    return true;
}




LDAPCache::LDAPCache()
{
    ldap_session = NULL;
    setTimeout(20);
    setScope(LDAP_SCOPE_SUBTREE);

    current_host =""; current_port = 0; 
    current_base = ""; current_filter = "";
    current_scope = 0;
    current_attributes = std::vector<std::string> (0);
}


LDAPCache::LDAPCache( const std::string uri )
{
    ldap_session = NULL;
    setTimeout(20);
    setScope(LDAP_SCOPE_SUBTREE);

    current_host =""; current_port = 0; 
    current_base = ""; current_filter = "";
    current_scope = 0; 
    current_attributes = std::vector<std::string> (0);

    std::string server, port, dn, scope, filter;
    std::vector<std::string> attributes;
    if( parseURI( uri, server, port, dn, scope, filter, attributes ) ) {

	current_host = server; current_port = atoi(port.c_str());
	current_base = dn; current_filter = filter;
	current_scope = atoi(scope.c_str());
	current_attributes = attributes;

// 	std::string p[] = { server, port, dn, filter, scope };

	// Adds the attribute list to the 'p' array
// 	unsigned int i = 5; 
// 	for( std::vector<std::string>::iterator itr = attributes.begin(); 
// 	     itr != attributes.end() ; 
// 	     itr++, i++ )
// 	    p[i] = std::string( *itr );

	//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* LDAPCache::initLdapSession( const std::string host, int port, int v2compatibility,
				  std::string login, std::string passwd )
{
    if( ldap_session != NULL ) // Session may be already in use
	return NULL; 

    ldap_session = ldap_init( host.c_str(), port );
    // set LDAP version 3 by default
    int protocol_version=LDAP_VERSION3;
    if(v2compatibility)
      protocol_version=LDAP_VERSION2;

#ifdef DEBUG
    std::cout << "protocol_version =>" << protocol_version << std::endl;
#endif
    ldap_set_option( ldap_session, LDAP_OPT_PROTOCOL_VERSION, &protocol_version );

    const char* loginname = NULL;
    const char* password = NULL;

    if( login != "" ) loginname = login.c_str();
    if( passwd != "" ) password = passwd.c_str();

    if( ldap_simple_bind( ldap_session, loginname, password ) == -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 LDAPCache::addEntry( 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 ) ) {


	if( strstr( attr, ";binary" ) != NULL ) {
	    struct berval **bv;
	    
	    bv = ldap_get_values_len( ldap_session, entry, attr );

	    for( int i = 0; bv[i] != NULL; i++ ) {
		if( ! append( std::string(dn), std::string(attr), bv[i]->bv_val, 
					  (unsigned long)bv[i]->bv_len, AttributeBinary ) )
		    return false;

	    }
	    ldap_value_free_len( bv );
	}

	else {
	    char **values;
	
	    values = ldap_get_values( ldap_session, entry, attr );

	    for( int i = 0; values[i] != NULL; i++ )
		if( ! append( std::string(dn), std::string(attr), values[i] ) ) return false;

	    ldap_value_free( values );
	}		    

	ldap_memfree( attr );
    }

    // Valgrind sees this as an invalid free
    // ber_free( berptr, 1 );

    ldap_memfree( dn );

    return true;
} 


// Read all LDAP entries and storing them in memory, for
// "offline" consultations
// Params are:
// params[0] = LDAP server host name
// params[1] = LDAP server port
// params[2] = Retain LDAPv2 compatibility
// params[3] = base for search (ex: "O=COMPANY,C=FR")
// params[4] = search filter (ex: "cn=*")
// params[5] = scope type, could be "LDAP_SCOPE_BASE", "LDAP_SCOPE_ONELEVEL"
//             or "LDAP_SCOPE_SUBTREE", default is "LDAP_SCOPE_SUBTREE"
// params[6] = ""
// The last unused argument must be set to the empty string: ""
bool LDAPCache::read( const std::string params[] )
{
    std::string host = "localhost";
    int v2compatibility = 0;
    std::string base = "";
    std::string filter = "objectClass=*";
    std::string scope = "LDAP_SCOPE_SUBTREE";
    int port = LDAP_DEFAULT_PORT;

    LDAPMessage *result, *entry;
    int msgid;
    struct timeval *timeout = NULL;

    unsigned int size = 0;
    if( params != NULL )
	while( params[size] != "" ) size++;
    else
	size = 1;

#ifdef DEBUG
     std::cout << "Params size = " << size << std::endl;
     std::cout << params[0] << params[1] << params[2] << params[3]<<params[4]<< params[5]<<std::endl;
#endif 


    switch( size ) {
    case 1: {
	if( params == NULL ) {
	    host = current_host;
	    port = current_port;
	    v2compatibility = current_v2compatibility;
	    base = current_base;
	    filter = current_filter;
	    scope = current_scope;
	}
	break;
    }
    case 4: {
	host = params[0];
	port = ( params[1] == "" ? LDAP_DEFAULT_PORT : atoi(params[1].c_str()) );
	v2compatibility = (params[2] == "" ? 0 : atoi(params[2].c_str()));
	base = params[3];
	break;
    }
    case 5: {
	host = params[0];
	port = ( params[1] == "" ? LDAP_DEFAULT_PORT : atoi(params[1].c_str()) );
	v2compatibility = (params[2] == "" ? 0 : atoi(params[2].c_str()));
	base = params[3];
	filter = params[4];
	break;
    }
    case 6: {
	host = params[0];
	port = ( params[1] == "" ? LDAP_DEFAULT_PORT : atoi(params[1].c_str()) );
	v2compatibility = (params[2] == "" ? 0 : atoi(params[2].c_str()));
	base = params[3];
	filter = params[4];
	scope = params[5];
	break;
    }
    default: {
#ifdef DEBUG
 	std::cerr << "Wrong number of arguments." << std::endl;
#endif 
	return false;
    }
    }

    setScope( scope ); 


    if( host == "" || base == "" || filter == "" || port < 1 || port > 65536 ) {
	// We don't have to check 'scope' since it will be correctly set
	// in any case by setScope()
#ifdef DEBUG
 	std::cerr << "One or more of the arguments are invalid." << std::endl;
#endif 
	return false;
    }


    ldap_session = initLdapSession( host, port, v2compatibility, current_login, current_password);
    if( ldap_session == NULL ) return false;
    
    if( current_attributes.size() > 0 ) {
	char* attributes[current_attributes.size() + 1];

	for( unsigned int i = 0; i < current_attributes.size(); i++ )
	    attributes[i] = strdup( current_attributes[i].c_str() );
	attributes[ current_attributes.size() ] = NULL;

	msgid = ldap_search( ldap_session, base.c_str(), getScope(), filter.c_str(), attributes, 0 );

	for( unsigned int i = 0; i < current_attributes.size(); i++ )
	    free( attributes[i] );

    }
    else
	msgid = ldap_search( ldap_session, base.c_str(), getScope(), filter.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;

    // tester le timeout, retourn 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;
    }


    for( entry = ldap_first_entry( ldap_session, result );
	 entry != NULL; 
	 entry = ldap_next_entry(ldap_session, entry) ) {

	if( ! addEntry( ldap_session, entry ) )  {
#ifdef DEBUG
 	    std::cerr << "Oops: erreur lors de l'ajout d'une l'entrée" << std::endl;
#endif 
	}
    }


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

    ldap_unbind( ldap_session );

    // Valgrind sees this as an invalid free
    // ldap_memfree( ldap_session );

    ldap_session = NULL;

    return true;
    
}




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

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

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

void LDAPCache::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 LDAPCache::setScope( const std::string s )
{
    if( s == "LDAP_SCOPE_BASE" || s == "base" )
	current_scope = LDAP_SCOPE_BASE;
    else if ( s == "LDAP_SCOPE_ONELEVEL" || s == "one" )
	current_scope = LDAP_SCOPE_ONELEVEL;
    else if ( s == "LDAP_SCOPE_SUBTREE" || s == "sub" )
	current_scope = LDAP_SCOPE_SUBTREE;
    else
	current_scope = LDAP_SCOPE_SUBTREE;
}


bool LDAPCache::setLogin( const std::string login )
{
    current_login = login;
    return true;
}


bool LDAPCache::setPassword( const std::string passwd )
{
    current_password = passwd;
    return true;
}

bool LDAPCache::retainV2(const int b) 
{
  current_v2compatibility = b;
  return true;

}
} // Namespace
