claws-mail/src/ldapquery.c
2004-05-10 10:22:28 +00:00

1403 lines
35 KiB
C

/*
* Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
* Copyright (C) 2003-2004 Match Grun
*
* This program 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.
*
* 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
/*
* Functions necessary to define and perform LDAP queries.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#ifdef USE_LDAP
#include <glib.h>
#include <sys/time.h>
#include <string.h>
#include <lber.h>
#include "ldapquery.h"
#include "ldapctrl.h"
#include "mgutils.h"
#include "addritem.h"
#include "addrcache.h"
#include "ldapquery.h"
/*
* Key for thread specific data.
*/
static pthread_key_t _queryThreadKey_;
static gboolean _queryThreadInit_ = FALSE;
/**
* Create new LDAP query object.
* \return Initialized query object.
*/
LdapQuery *ldapqry_create( void ) {
LdapQuery *qry;
qry = g_new0( LdapQuery, 1 );
ADDRQUERY_TYPE(qry) = ADDRQUERY_LDAP;
ADDRQUERY_ID(qry) = 0;
ADDRQUERY_SEARCHTYPE(qry) = ADDRSEARCH_NONE;
ADDRQUERY_NAME(qry) = NULL;
ADDRQUERY_RETVAL(qry) = LDAPRC_SUCCESS;
ADDRQUERY_FOLDER(qry) = NULL;
ADDRQUERY_SEARCHVALUE(qry) = NULL;
qry->control = NULL;
qry->server = NULL;
qry->entriesRead = 0;
qry->elapsedTime = 0;
qry->stopFlag = FALSE;
qry->busyFlag = FALSE;
qry->agedFlag = FALSE;
qry->completed = FALSE;
qry->thread = NULL;
qry->callBackEntry = NULL;
qry->callBackEnd = NULL;
qry->ldap = NULL;
qry->data = NULL;
/* Mutex to protect stop and busy flags */
qry->mutexStop = g_malloc0( sizeof( pthread_mutex_t ) );
pthread_mutex_init( qry->mutexStop, NULL );
qry->mutexBusy = g_malloc0( sizeof( pthread_mutex_t ) );
pthread_mutex_init( qry->mutexBusy, NULL );
/* Mutex to protect critical section */
qry->mutexEntry = g_malloc0( sizeof( pthread_mutex_t ) );
pthread_mutex_init( qry->mutexEntry, NULL );
return qry;
}
/**
* Specify the reference to control data that will be used for the query. The calling
* module should be responsible for creating and destroying this control object.
* \param qry Query object.
* \param ctl Control object.
*/
void ldapqry_set_control( LdapQuery *qry, LdapControl *ctl ) {
g_return_if_fail( qry != NULL );
qry->control = ctl;
}
/**
* Specify query name to be used.
* \param qry Query object.
* \param value Name.
*/
void ldapqry_set_name( LdapQuery* qry, const gchar *value ) {
ADDRQUERY_NAME(qry) = mgu_replace_string( ADDRQUERY_NAME(qry), value );
g_strstrip( ADDRQUERY_NAME(qry) );
}
/**
* Specify search value to be used.
* \param qry Query object.
* \param value
*/
void ldapqry_set_search_value( LdapQuery *qry, const gchar *value ) {
ADDRQUERY_SEARCHVALUE(qry) = mgu_replace_string( ADDRQUERY_SEARCHVALUE(qry), value );
g_strstrip( ADDRQUERY_SEARCHVALUE(qry) );
}
/**
* Specify error/status.
* \param qry Query object.
* \param value Status.
*/
void ldapqry_set_error_status( LdapQuery* qry, const gint value ) {
ADDRQUERY_RETVAL(qry) = value;
}
/**
* Specify query type.
* \param qry Query object.
* \param value Query type, either:
* <ul>
* <li><code>LDAPQUERY_NONE</code></li>
* <li><code>LDAPQUERY_STATIC</code></li>
* <li><code>LDAPQUERY_DYNAMIC</code></li>
* </ul>
*/
/*
void ldapqry_set_query_type( LdapQuery* qry, const gint value ) {
ADDRQUERY_TYPE(qry) = value;
}
*/
/**
* Specify search type.
* \param qry Query object.
* \param value Type.
*/
void ldapqry_set_search_type( LdapQuery *qry, const AddrSearchType value ) {
g_return_if_fail( qry != NULL );
ADDRQUERY_SEARCHTYPE(qry) = value;
}
/**
* Specify query ID.
* \param qry Query object.
* \param value ID for the query.
*/
void ldapqry_set_query_id( LdapQuery* qry, const gint value ) {
ADDRQUERY_ID(qry) = value;
}
/**
* Specify maximum number of LDAP entries to retrieve.
* \param qry Query object.
* \param value Entries to read.
*/
void ldapqry_set_entries_read( LdapQuery* qry, const gint value ) {
if( value > 0 ) {
qry->entriesRead = value;
}
else {
qry->entriesRead = 0;
}
}
/**
* Register a callback function that will be executed when each entry
* has been read and processed. When called, the function will be passed
* this query object and a GList of ItemEMail objects as arguments. An
* example of typical usage is shown below.
*
* <pre>
* ------------------------------------------------------------
* void myCallbackEntry( LdapQuery *qry, GList *listEMail ) {
* GList *node;
*
* node = listEMail;
* while( node ) {
* ItemEMail *email = node->data;
* ... process email object ...
* node = g_list_next( node );
* }
* g_list_free( listEMail );
* }
* ...
* ...
* ldapqry_set_callback_entry( qry, myCallbackEntry );
* ------------------------------------------------------------
* </pre>
*
* \param qry Query object.
* \param func Function.
*/
void ldapqry_set_callback_entry( LdapQuery *qry, void *func ) {
pthread_mutex_lock( qry->mutexEntry );
qry->callBackEntry = func;
pthread_mutex_unlock( qry->mutexEntry );
}
/**
* Register a callback function that will be executed when the search
* is complete. When called, the function will be passed this query
* object as an argument.
* \param qry Query object.
* \param func Function.
*/
void ldapqry_set_callback_end( LdapQuery *qry, void *func ) {
qry->callBackEnd = func;
}
/**
* Notify query to start/stop executing. This method should be called with a
* value if <i>TRUE</i> to terminate an existing running query.
*
* \param qry Query object.
* \param value Value of stop flag.
*/
void ldapqry_set_stop_flag( LdapQuery *qry, const gboolean value ) {
g_return_if_fail( qry != NULL );
pthread_mutex_lock( qry->mutexStop );
qry->stopFlag = value;
pthread_mutex_unlock( qry->mutexStop );
}
/**
* Test value of stop flag. This method should be used to determine whether a
* query has stopped running.
* \param qry Query object.
* \return Value of stop flag.
*/
gboolean ldapqry_get_stop_flag( LdapQuery *qry ) {
gboolean value;
g_return_if_fail( qry != NULL );
pthread_mutex_lock( qry->mutexStop );
value = qry->stopFlag;
pthread_mutex_unlock( qry->mutexStop );
return value;
}
/**
* Set busy flag.
* \param qry Query object.
* \param value Value of busy flag.
*/
void ldapqry_set_busy_flag( LdapQuery *qry, const gboolean value ) {
g_return_if_fail( qry != NULL );
pthread_mutex_lock( qry->mutexBusy );
qry->busyFlag = value;
pthread_mutex_unlock( qry->mutexBusy );
}
/**
* Test value of busy flag. This method will return a value of <i>FALSE</i>
* when a query has completed running.
* \param qry Query object.
* \return Value of busy flag.
*/
gboolean ldapqry_get_busy_flag( LdapQuery *qry ) {
gboolean value;
g_return_if_fail( qry != NULL );
pthread_mutex_lock( qry->mutexBusy );
value = qry->busyFlag;
pthread_mutex_unlock( qry->mutexBusy );
return value;
}
/**
* Set query aged flag.
* \param qry Query object.
* \param value Value of aged flag.
*/
void ldapqry_set_aged_flag( LdapQuery *qry, const gboolean value ) {
g_return_if_fail( qry != NULL );
qry->agedFlag = value;
}
/**
* Test value of aged flag.
* \param qry Query object.
* \return <i>TRUE</i> if query has been marked as aged (and can be retired).
*/
gboolean ldapqry_get_aged_flag( LdapQuery *qry ) {
g_return_if_fail( qry != NULL );
return qry->agedFlag;
}
/**
* Specify user data for query.
* \param qry Query object.
* \param value Data to set.
*/
void ldapqry_set_data( LdapQuery *qry, const gpointer value ) {
g_return_if_fail( qry != NULL );
qry->data = value;
}
/**
* Retrieve user data associated with query.
* \param qry Query object.
* \return Data.
*/
gpointer ldapqry_get_data( LdapQuery *qry ) {
g_return_if_fail( qry != NULL );
return qry->data;
}
/**
* Release the LDAP control data associated with the query.
* \param qry Query object to process.
*/
void ldapqry_release_control( LdapQuery *qry ) {
g_return_if_fail( qry != NULL );
if( qry->control != NULL ) {
ldapctl_free( qry->control );
}
qry->control = NULL;
}
/**
* Clear LDAP query member variables.
* \param qry Query object.
*/
void ldapqry_clear( LdapQuery *qry ) {
g_return_if_fail( qry != NULL );
/* Free internal stuff */
g_free( ADDRQUERY_NAME(qry) );
g_free( ADDRQUERY_SEARCHVALUE(qry) );
/* Clear pointers and value */
ADDRQUERY_NAME(qry) = NULL;
ADDRQUERY_SEARCHVALUE(qry) = NULL;
ADDRQUERY_ID(qry) = 0;
ADDRQUERY_SEARCHTYPE(qry) = ADDRSEARCH_NONE;
ADDRQUERY_RETVAL(qry) = LDAPRC_SUCCESS;
qry->entriesRead = 0;
qry->elapsedTime = 0;
qry->stopFlag = FALSE;
qry->busyFlag = FALSE;
qry->agedFlag = FALSE;
qry->completed = FALSE;
qry->callBackEntry = NULL;
qry->callBackEnd = NULL;
qry->ldap = NULL;
qry->data = NULL;
}
/**
* Free up LDAP query object by releasing internal memory. Note that
* the thread object will be freed by the OS.
* \param qry Query object to process.
*/
void ldapqry_free( LdapQuery *qry ) {
g_return_if_fail( qry != NULL );
/* Clear out internal members */
ADDRQUERY_TYPE(qry) = ADDRQUERY_NONE;
ldapqry_clear( qry );
/* Free the mutex */
pthread_mutex_destroy( qry->mutexStop );
pthread_mutex_destroy( qry->mutexBusy );
pthread_mutex_destroy( qry->mutexEntry );
g_free( qry->mutexStop );
g_free( qry->mutexBusy );
g_free( qry->mutexEntry );
qry->mutexEntry = NULL;
qry->mutexBusy = NULL;
qry->mutexStop = NULL;
/* Do not free folder - parent server object should free */
ADDRQUERY_FOLDER(qry) = NULL;
/* Do not free thread - thread should be terminated before freeing */
qry->thread = NULL;
/* Do not free LDAP control - should be destroyed before freeing */
qry->control = NULL;
/* Now release object */
g_free( qry );
}
/**
* Display object to specified stream.
* \param qry Query object to process.
* \param stream Output stream.
*/
void ldapqry_print( const LdapQuery *qry, FILE *stream ) {
g_return_if_fail( qry != NULL );
fprintf( stream, "LdapQuery:\n" );
fprintf( stream, " control?: %s\n", qry->control ? "yes" : "no" );
fprintf( stream, "err/status: %d\n", ADDRQUERY_RETVAL(qry) );
fprintf( stream, "query type: %d\n", ADDRQUERY_TYPE(qry) );
fprintf( stream, "searchType: %d\n", ADDRQUERY_SEARCHTYPE(qry) );
fprintf( stream, "query name: '%s'\n", ADDRQUERY_NAME(qry) );
fprintf( stream, "search val: '%s'\n", ADDRQUERY_SEARCHVALUE(qry) );
fprintf( stream, " queryID: %d\n", ADDRQUERY_ID(qry) );
fprintf( stream, " entries: %d\n", qry->entriesRead );
fprintf( stream, " elapsed: %d\n", qry->elapsedTime );
fprintf( stream, " stop flag: %s\n", qry->stopFlag ? "yes" : "no" );
fprintf( stream, " busy flag: %s\n", qry->busyFlag ? "yes" : "no" );
fprintf( stream, " aged flag: %s\n", qry->agedFlag ? "yes" : "no" );
fprintf( stream, " completed: %s\n", qry->completed ? "yes" : "no" );
}
/**
* Free linked lists of character strings.
* \param listName List of common names.
* \param listAddr List of addresses.
* \param listFirst List of first names.
* \param listLast List of last names.
*/
static void ldapqry_free_lists(
GSList *listName, GSList *listAddr, GSList *listFirst,
GSList *listLast )
{
mgu_free_list( listName );
mgu_free_list( listAddr );
mgu_free_list( listFirst );
mgu_free_list( listLast );
}
/**
* Add all LDAP attribute values to a list.
* \param ld LDAP handle.
* \param entry LDAP entry to process.
* \param attr LDAP attribute.
* \return List of values.
*/
static GSList *ldapqry_add_list_values(
LDAP *ld, LDAPMessage *entry, char *attr )
{
GSList *list = NULL;
gint i;
gchar **vals;
if( ( vals = ldap_get_values( ld, entry, attr ) ) != NULL ) {
for( i = 0; vals[i] != NULL; i++ ) {
/* printf( "lv\t%s: %s\n", attr, vals[i] ); */
list = g_slist_append( list, g_strdup( vals[i] ) );
}
}
ldap_value_free( vals );
return list;
}
/**
* Add a single attribute value to a list.
* \param ld LDAP handle.
* \param entry LDAP entry to process.
* \param attr LDAP attribute name to process.
* \return List of values; only one value will be present.
*/
static GSList *ldapqry_add_single_value( LDAP *ld, LDAPMessage *entry, char *attr ) {
GSList *list = NULL;
gchar **vals;
if( ( vals = ldap_get_values( ld, entry, attr ) ) != NULL ) {
if( vals[0] != NULL ) {
/* printf( "sv\t%s: %s\n", attr, vals[0] ); */
list = g_slist_append( list, g_strdup( vals[0] ) );
}
}
ldap_value_free( vals );
return list;
}
/**
* Build an address list entry and append to list of address items. Name is formatted
* as "<first-name> <last-name>".
*
* \param cache Address cache to load.
* \param qry Query object to process.
* \param dn DN for entry found on server.
* \param listName List of common names for entry; see notes below.
* \param listAddr List of EMail addresses for entry.
* \param listFirst List of first names for entry.
* \param listLast List of last names for entry.
*
* \return List of ItemEMail objects.
*
* Notes:
* 1) Each LDAP server entry may have multiple LDAP attributes with the same
* name. For example, a single entry for a person may have more than one
* common name, email address, etc.
*
* 2) The DN for the entry is unique for the server.
*/
static GList *ldapqry_build_items_fl(
AddressCache *cache, LdapQuery *qry, gchar *dn,
GSList *listName, GSList *listAddr, GSList *listFirst,
GSList *listLast )
{
GSList *nodeAddress;
gchar *firstName = NULL, *lastName = NULL, *fullName = NULL;
gboolean allocated;
ItemPerson *person;
ItemEMail *email;
ItemFolder *folder;
GList *listReturn = NULL;
folder = ADDRQUERY_FOLDER(qry);
if( folder == NULL ) return listReturn;
if( listAddr == NULL ) return listReturn;
/* Find longest first name in list */
firstName = mgu_slist_longest_entry( listFirst );
/* Format last name */
if( listLast ) {
lastName = listLast->data;
}
/* Find longest common name */
allocated = FALSE;
fullName = mgu_slist_longest_entry( listName );
if( fullName == NULL ) {
/* Format a full name from first and last names */
if( firstName ) {
if( lastName ) {
fullName = g_strdup_printf( "%s %s", firstName, lastName );
}
else {
fullName = g_strdup_printf( "%s", firstName );
}
}
else {
if( lastName ) {
fullName = g_strdup_printf( "%s", lastName );
}
}
if( fullName ) {
g_strchug( fullName ); g_strchomp( fullName );
allocated = TRUE;
}
}
/* Add person into folder */
person = addritem_create_item_person();
addritem_person_set_common_name( person, fullName );
addritem_person_set_first_name( person, firstName );
addritem_person_set_last_name( person, lastName );
addrcache_id_person( cache, person );
addritem_person_set_external_id( person, dn );
addrcache_folder_add_person( cache, ADDRQUERY_FOLDER(qry), person );
qry->entriesRead++;
/* Add each address item */
nodeAddress = listAddr;
while( nodeAddress ) {
email = addritem_create_item_email();
addritem_email_set_address( email, nodeAddress->data );
addrcache_id_email( cache, email );
addrcache_person_add_email( cache, person, email );
addritem_person_add_email( person, email );
listReturn = g_list_append( listReturn, email );
nodeAddress = g_slist_next( nodeAddress );
}
/* Free any allocated memory */
if( allocated ) {
g_free( fullName );
}
fullName = firstName = lastName = NULL;
return listReturn;
}
/**
* Process a single search entry.
* \param cache Address cache to load.
* \param qry Query object to process.
* \param ld LDAP handle.
* \param e LDAP message.
* \return List of EMail objects found.
*/
static GList *ldapqry_process_single_entry(
AddressCache *cache, LdapQuery *qry, LDAP *ld, LDAPMessage *e )
{
char *dnEntry;
char *attribute;
LdapControl *ctl;
BerElement *ber;
GSList *listName = NULL, *listAddress = NULL;
GSList *listFirst = NULL, *listLast = NULL;
GList *listReturn;
listReturn = NULL;
ctl = qry->control;
dnEntry = ldap_get_dn( ld, e );
/* printf( "DN: %s\n", dnEntry ); */
/* Process all attributes */
for( attribute = ldap_first_attribute( ld, e, &ber ); attribute != NULL;
attribute = ldap_next_attribute( ld, e, ber ) ) {
if( strcasecmp( attribute, ctl->attribEMail ) == 0 ) {
listAddress = ldapqry_add_list_values( ld, e, attribute );
}
else if( strcasecmp( attribute, ctl->attribCName ) == 0 ) {
listName = ldapqry_add_list_values( ld, e, attribute );
}
else if( strcasecmp( attribute, ctl->attribFName ) == 0 ) {
listFirst = ldapqry_add_list_values( ld, e, attribute );
}
else if( strcasecmp( attribute, ctl->attribLName ) == 0 ) {
listLast = ldapqry_add_single_value( ld, e, attribute );
}
/* Free memory used to store attribute */
ldap_memfree( attribute );
}
/* Format and add items to cache */
listReturn = ldapqry_build_items_fl(
cache, qry, dnEntry, listName, listAddress, listFirst, listLast );
/* Free up */
ldapqry_free_lists( listName, listAddress, listFirst, listLast );
listName = listAddress = listFirst = listLast = NULL;
if( ber != NULL ) {
ber_free( ber, 0 );
}
g_free( dnEntry );
return listReturn;
}
/**
* Check parameters that are required for a search. This should
* be called before performing a search.
* \param qry Query object to process.
* \return <i>TRUE</i> if search criteria appear OK.
*/
gboolean ldapqry_check_search( LdapQuery *qry ) {
LdapControl *ctl;
ADDRQUERY_RETVAL(qry) = LDAPRC_CRITERIA;
/* Test for control data */
ctl = qry->control;
if( ctl == NULL ) {
return FALSE;
}
/* Test for search value */
if( ADDRQUERY_SEARCHVALUE(qry) == NULL ) {
return FALSE;
}
if( strlen( ADDRQUERY_SEARCHVALUE(qry) ) < 1 ) {
return FALSE;
}
ADDRQUERY_RETVAL(qry) = LDAPRC_SUCCESS;
return TRUE;
}
/**
* Touch the query. This nudges the touch time with the current time.
* \param qry Query object to process.
*/
void ldapqry_touch( LdapQuery *qry ) {
qry->touchTime = time( NULL );
qry->agedFlag = FALSE;
}
/**
* Connect to LDAP server.
* \param qry Query object to process.
* \return Error/status code.
*/
static gint ldapqry_connect( LdapQuery *qry ) {
LdapControl *ctl;
LDAP *ld;
gint rc;
gint version;
/* Initialize connection */
/* printf( "===ldapqry_connect===\n" ); */
/* ldapqry_print( qry, stdout ); */
ctl = qry->control;
/* ldapctl_print( ctl, stdout ); */
/* printf( "======\n" ); */
ldapqry_touch( qry );
qry->startTime = qry->touchTime;
qry->elapsedTime = -1;
ADDRQUERY_RETVAL(qry) = LDAPRC_INIT;
if( ( ld = ldap_init( ctl->hostName, ctl->port ) ) == NULL ) {
return ADDRQUERY_RETVAL(qry);
}
qry->ldap = ld;
ADDRQUERY_RETVAL(qry) = LDAPRC_STOP_FLAG;
if( ldapqry_get_stop_flag( qry ) ) {
return ADDRQUERY_RETVAL(qry);
}
ldapqry_touch( qry );
/*
printf( "connected to LDAP host %s on port %d\n", ctl->hostName, ctl->port );
*/
#ifdef USE_LDAP_TLS
/* Handle TLS */
version = LDAP_VERSION3;
rc = ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, &version );
if( rc == LDAP_OPT_SUCCESS ) {
ctl->version = LDAP_VERSION3;
}
if( ctl->version == LDAP_VERSION3 ) {
if( ctl->enableTLS ) {
ADDRQUERY_RETVAL(qry) = LDAPRC_TLS;
rc = ldap_start_tls_s( ld, NULL, NULL );
/*
printf( "rc=%d\n", rc );
printf( "LDAP Status: set_option: %s\n", ldap_err2string( rc ) );
*/
if( rc != LDAP_SUCCESS ) {
return ADDRQUERY_RETVAL(qry);
}
}
}
#endif
/* Bind to the server, if required */
ADDRQUERY_RETVAL(qry) = LDAPRC_BIND;
if( ctl->bindDN ) {
if( * ctl->bindDN != '\0' ) {
/* printf( "binding...\n" ); */
rc = ldap_simple_bind_s( ld, ctl->bindDN, ctl->bindPass );
/* printf( "rc=%d\n", rc ); */
if( rc != LDAP_SUCCESS ) {
/*
printf( "LDAP Error: ldap_simple_bind_s: %s\n",
ldap_err2string( rc ) );
*/
return ADDRQUERY_RETVAL(qry);
}
}
}
ADDRQUERY_RETVAL(qry) = LDAPRC_STOP_FLAG;
if( ldapqry_get_stop_flag( qry ) ) {
return ADDRQUERY_RETVAL(qry);
}
ldapqry_touch( qry );
ADDRQUERY_RETVAL(qry) = LDAP_SUCCESS;
return ADDRQUERY_RETVAL(qry);
}
/**
* Connect to LDAP server.
* \param qry Query object to process.
* \return Error/status code.
*/
static gint ldapqry_disconnect( LdapQuery *qry ) {
/* Disconnect */
if( qry->ldap ) ldap_unbind( qry->ldap );
qry->ldap = NULL;
ldapqry_touch( qry );
qry->elapsedTime = qry->touchTime - qry->startTime;
return ADDRQUERY_RETVAL(qry);
}
/**
* Perform the LDAP search, reading LDAP entries into cache.
* Note that one LDAP entry can have multiple values for many of its
* attributes. If these attributes are E-Mail addresses; these are
* broken out into separate address items. For any other attribute,
* only the first occurrence is read.
*
* \param qry Query object to process.
* \return Error/status code.
*/
static gint ldapqry_search_retrieve( LdapQuery *qry ) {
LdapControl *ctl;
LDAP *ld;
LDAPMessage *result, *e;
char **attribs;
gchar *criteria;
gboolean searchFlag;
gboolean entriesFound;
gboolean first;
struct timeval timeout;
gint rc;
AddressCache *cache;
GList *listEMail;
/* Initialize some variables */
ld = qry->ldap;
ctl = qry->control;
cache = qry->server->addressCache;
timeout.tv_sec = ctl->timeOut;
timeout.tv_usec = 0L;
entriesFound = FALSE;
ADDRQUERY_RETVAL(qry) = LDAPRC_SUCCESS;
/* Define all attributes we are interested in. */
attribs = ldapctl_attribute_array( ctl );
/* Create LDAP search string */
criteria = ldapctl_format_criteria( ctl, ADDRQUERY_SEARCHVALUE(qry) );
/* printf( "Search criteria ::%s::\n", criteria ); */
/*
* Execute the search - this step may take some time to complete
* depending on network traffic and server response time.
*/
ADDRQUERY_RETVAL(qry) = LDAPRC_TIMEOUT;
rc = ldap_search_ext_s( ld, ctl->baseDN, LDAP_SCOPE_SUBTREE, criteria,
attribs, 0, NULL, NULL, &timeout, 0, &result );
ldapctl_free_attribute_array( attribs );
g_free( criteria );
criteria = NULL;
if( rc == LDAP_TIMEOUT ) {
return ADDRQUERY_RETVAL(qry);
}
ADDRQUERY_RETVAL(qry) = LDAPRC_SEARCH;
/* Test valid returns */
searchFlag = FALSE;
if( rc == LDAP_ADMINLIMIT_EXCEEDED ) {
searchFlag = TRUE;
}
else if( rc == LDAP_SUCCESS ) {
searchFlag = TRUE;
}
else if( rc == LDAP_PARTIAL_RESULTS ) {
searchFlag = TRUE;
}
else {
/*
printf( "LDAP Error: ldap_search_st: %d\n", rc );
printf( "LDAP Error: ldap_search_st: %s\n", ldap_err2string( rc ) );
*/
return ADDRQUERY_RETVAL(qry);
}
ADDRQUERY_RETVAL(qry) = LDAPRC_STOP_FLAG;
/*
printf( "Total results are: %d\n", ldap_count_entries( ld, result ) );
*/
/* Process results */
first = TRUE;
while( searchFlag ) {
ldapqry_touch( qry );
if( qry->entriesRead >= ctl->maxEntries ) break;
/* Test for stop */
if( ldapqry_get_stop_flag( qry ) ) {
break;
}
/* Retrieve entry */
if( first ) {
first = FALSE;
e = ldap_first_entry( ld, result );
}
else {
e = ldap_next_entry( ld, e );
}
if( e == NULL ) break;
entriesFound = TRUE;
/* Setup a critical section here */
pthread_mutex_lock( qry->mutexEntry );
/* Process entry */
listEMail = ldapqry_process_single_entry( cache, qry, ld, e );
/* Process callback */
if( qry->callBackEntry ) {
qry->callBackEntry( qry, ADDRQUERY_ID(qry), listEMail, qry->data );
}
else {
g_list_free( listEMail );
}
pthread_mutex_unlock( qry->mutexEntry );
}
/* Free up and disconnect */
ldap_msgfree( result );
if( searchFlag ) {
if( entriesFound ) {
ADDRQUERY_RETVAL(qry) = LDAPRC_SUCCESS;
}
else {
ADDRQUERY_RETVAL(qry) = LDAPRC_NOENTRIES;
}
}
return ADDRQUERY_RETVAL(qry);
}
/**
* Connection, perform search and disconnect.
* \param qry Query object to process.
* \return Error/status code.
*/
static gint ldapqry_perform_search( LdapQuery *qry ) {
/* Check search criteria */
if( ! ldapqry_check_search( qry ) ) {
return ADDRQUERY_RETVAL(qry);
}
/* Connect */
qry->ldap = NULL;
ldapqry_connect( qry );
if( ADDRQUERY_RETVAL(qry) == LDAPRC_SUCCESS ) {
/* Perform search */
ldapqry_search_retrieve( qry );
}
/* Disconnect */
ldapqry_disconnect( qry );
qry->ldap = NULL;
return ADDRQUERY_RETVAL(qry);
}
/**
* Wrapper around search.
* \param qry Query object to process.
* \return Error/status code.
*/
gint ldapqry_search( LdapQuery *qry ) {
gint retVal;
g_return_val_if_fail( qry != NULL, -1 );
g_return_val_if_fail( qry->control != NULL, -1 );
ldapqry_touch( qry );
qry->completed = FALSE;
/* Setup pointer to thread specific area */
pthread_setspecific( _queryThreadKey_, qry );
pthread_detach( pthread_self() );
/* Now perform the search */
qry->entriesRead = 0;
ADDRQUERY_RETVAL(qry) = LDAPRC_SUCCESS;
ldapqry_set_busy_flag( qry, TRUE );
ldapqry_set_stop_flag( qry, FALSE );
if( ADDRQUERY_SEARCHTYPE(qry) == ADDRSEARCH_LOCATE ) {
retVal = ldapqry_perform_locate( qry );
}
else {
retVal = ldapqry_perform_search( qry );
}
if( retVal == LDAPRC_SUCCESS ) {
qry->server->addressCache->dataRead = TRUE;
qry->server->addressCache->accessFlag = FALSE;
if( ldapqry_get_stop_flag( qry ) ) {
/*
printf( "Search was terminated prematurely\n" );
*/
}
else {
ldapqry_touch( qry );
qry->completed = TRUE;
/*
printf( "Search ran to completion\n" );
*/
}
}
ldapqry_set_stop_flag( qry, TRUE );
ldapqry_set_busy_flag( qry, FALSE );
/* Process callback */
if( qry->callBackEnd ) {
qry->callBackEnd( qry, ADDRQUERY_ID(qry), ADDRQUERY_RETVAL(qry), qry->data );
}
return ADDRQUERY_RETVAL(qry);
}
/**
* Read data into list using a background thread. Callback function will be
* notified when search is complete.
* \param qry Query object to process.
* \return Error/status code.
*/
gint ldapqry_read_data_th( LdapQuery *qry ) {
g_return_val_if_fail( qry != NULL, -1 );
g_return_val_if_fail( qry->control != NULL, -1 );
ldapqry_set_stop_flag( qry, FALSE );
ldapqry_touch( qry );
if( ldapqry_check_search( qry ) ) {
if( ADDRQUERY_RETVAL(qry) == LDAPRC_SUCCESS ) {
/*
printf( "Starting LDAP search thread\n");
*/
ldapqry_set_busy_flag( qry, TRUE );
qry->thread = g_malloc0( sizeof( pthread_t ) );
/* Setup thread */
pthread_create( qry->thread, NULL,
(void *) ldapqry_search, (void *) qry );
}
}
return ADDRQUERY_RETVAL(qry);
}
/**
* Cleanup LDAP thread data. This function will be called when each thread
* exits. Note that the thread object will be freed by the kernel.
* \param ptr Pointer to object being destroyed (a query object in this case).
*/
static void ldapqry_destroyer( void * ptr ) {
LdapQuery *qry;
qry = ( LdapQuery * ) ptr;
/*
printf( "ldapqry_destroyer::%d::%s\n", (int) pthread_self(), ADDRQUERY_NAME(qry) );
*/
/* Perform any destruction here */
if( qry->control != NULL ) {
ldapctl_free( qry->control );
}
qry->control = NULL;
qry->thread = NULL;
ldapqry_set_busy_flag( qry, FALSE );
}
/**
* Cancel thread associated with query.
* \param qry Query object to process.
*/
void ldapqry_cancel( LdapQuery *qry ) {
g_return_if_fail( qry != NULL );
/*
printf( "cancelling::%d::%s\n", (int) pthread_self(), ADDRQUERY_NAME(qry) );
*/
if( ldapqry_get_busy_flag( qry ) ) {
if( qry->thread ) {
/* printf( "calling pthread_cancel\n" ); */
pthread_cancel( * qry->thread );
}
}
}
/**
* Initialize LDAP query. This function should be called once before executing
* any LDAP queries to initialize thread specific data.
*/
void ldapqry_initialize( void ) {
/* printf( "ldapqry_initialize...\n" ); */
if( ! _queryThreadInit_ ) {
/*
printf( "ldapqry_initialize::creating thread specific area\n" );
*/
pthread_key_create( &_queryThreadKey_, ldapqry_destroyer );
_queryThreadInit_ = TRUE;
}
/* printf( "ldapqry_initialize... done!\n" ); */
}
/**
* Age the query based on LDAP control parameters.
* \param qry Query object to process.
* \param maxAge Maximum age of query (in seconds).
*/
void ldapqry_age( LdapQuery *qry, gint maxAge ) {
gint age;
g_return_if_fail( qry != NULL );
/* Limit the time that queries can hang around */
if( maxAge < 1 ) maxAge = LDAPCTL_MAX_QUERY_AGE;
/* Check age of query */
age = time( NULL ) - qry->touchTime;
if( age > maxAge ) {
qry->agedFlag = TRUE;
}
}
/**
* Delete folder associated with query results.
* \param qry Query object to process.
*/
void ldapqry_delete_folder( LdapQuery *qry ) {
AddressCache *cache;
ItemFolder *folder;
g_return_if_fail( qry != NULL );
folder = ADDRQUERY_FOLDER(qry);
if( folder ) {
cache = qry->server->addressCache;
folder = addrcache_remove_folder_delete( cache, folder );
if( folder ) {
addritem_free_item_folder( folder );
}
ADDRQUERY_FOLDER(qry) = NULL;
}
}
/**
* Create a name/value pair object.
* \param n Name.
* \param v Value.
* \return Initialized object.
*/
static NameValuePair *ldapqry_create_name_value( const gchar *n, const gchar *v ) {
NameValuePair *nvp = g_new0( NameValuePair, 1 );
nvp->name = g_strdup( n );
nvp->value = g_strdup( v );
return nvp;
}
/**
* Free up name/value pair object.
* \param nvp Name/value object.
*/
void ldapqry_free_name_value( NameValuePair *nvp ) {
if( nvp ) {
g_free( nvp->name );
g_free( nvp->value );
nvp->name = nvp->value = NULL;
g_free( nvp );
}
}
/**
* Print name/value pair object for debug.
* \param nvp Name/value object.
* \param stream Output stream.
*/
void ldapqry_print_name_value( NameValuePair *nvp, FILE *stream ) {
if( nvp ) {
fprintf( stream, "n/v ::%s::%s::\n", nvp->name, nvp->value );
}
}
/**
* Free up a list name/value pair objects.
* \param list List of name/value objects.
*/
void ldapqry_free_list_name_value( GList *list ) {
GList *node;
node = list;
while( node ) {
NameValuePair *nvp = ( NameValuePair * ) node->data;
ldapqry_free_name_value( nvp );
node->data = NULL;
node = g_list_next( node );
}
g_list_free( list );
}
/**
* Load a list of name/value pairs from LDAP attributes.
* \param ld LDAP handle.
* \param e LDAP message.
* \param attr Attribute name.
* \param listValues List to populate.
* \return List of attribute name/value pairs.
*/
static GList *ldapqry_load_attrib_values(
LDAP *ld, LDAPMessage *entry, char *attr,
GList *listValues )
{
GList *list = NULL;
gint i;
gchar **vals;
NameValuePair *nvp;
list = listValues;
if( ( vals = ldap_get_values( ld, entry, attr ) ) != NULL ) {
for( i = 0; vals[i] != NULL; i++ ) {
nvp = ldapqry_create_name_value( attr, vals[i] );
list = g_list_append( list, nvp );
}
}
ldap_value_free( vals );
return list;
}
/**
* Fetch a list of all attributes.
* \param ld LDAP handle.
* \param e LDAP message.
* \return List of attribute name/value pairs.
*/
static GList *ldapqry_fetch_attribs( LDAP *ld, LDAPMessage *e )
{
char *attribute;
BerElement *ber;
GList *listValues = NULL;
/* Process all attributes */
for( attribute = ldap_first_attribute( ld, e, &ber ); attribute != NULL;
attribute = ldap_next_attribute( ld, e, ber ) ) {
listValues = ldapqry_load_attrib_values( ld, e, attribute, listValues );
ldap_memfree( attribute );
}
/* Free up */
if( ber != NULL ) {
ber_free( ber, 0 );
}
return listValues;
}
#define CRITERIA_SINGLE "(objectclass=*)"
/**
* Perform the data retrieval for a specific LDAP record.
*
* \param qry Query object to process.
* \return Error/status code.
*/
static gint ldapqry_locate_retrieve( LdapQuery *qry ) {
LdapControl *ctl;
LDAP *ld;
LDAPMessage *result, *e;
gboolean entriesFound;
gboolean first;
struct timeval timeout;
gint rc;
gchar *dn;
GList *listValues;
/* Initialize some variables */
ld = qry->ldap;
ctl = qry->control;
dn = ADDRQUERY_SEARCHVALUE(qry);
timeout.tv_sec = ctl->timeOut;
timeout.tv_usec = 0L;
entriesFound = FALSE;
/*
* Execute the search - this step may take some time to complete
* depending on network traffic and server response time.
*/
ADDRQUERY_RETVAL(qry) = LDAPRC_TIMEOUT;
rc = ldap_search_ext_s( ld, dn, LDAP_SCOPE_BASE, CRITERIA_SINGLE,
NULL, 0, NULL, NULL, &timeout, 0, &result );
if( rc == LDAP_TIMEOUT ) {
return ADDRQUERY_RETVAL(qry);
}
ADDRQUERY_RETVAL(qry) = LDAPRC_SEARCH;
if( rc != LDAP_SUCCESS ) {
/*
printf( "LDAP Error: ldap_search_st: %s\n", ldap_err2string( rc ) );
*/
return ADDRQUERY_RETVAL(qry);
}
/*
printf( "Total results are: %d\n", ldap_count_entries( ld, result ) );
*/
/* Process results */
ADDRQUERY_RETVAL(qry) = LDAPRC_STOP_FLAG;
first = TRUE;
while( TRUE ) {
ldapqry_touch( qry );
if( qry->entriesRead >= ctl->maxEntries ) break;
/* Test for stop */
if( ldapqry_get_stop_flag( qry ) ) {
break;
}
/* Retrieve entry */
if( first ) {
first = FALSE;
e = ldap_first_entry( ld, result );
}
else {
e = ldap_next_entry( ld, e );
}
if( e == NULL ) break;
entriesFound = TRUE;
/* Setup a critical section here */
pthread_mutex_lock( qry->mutexEntry );
/* Process entry */
listValues = ldapqry_fetch_attribs( ld, e );
/* Process callback */
if( qry->callBackEntry ) {
qry->callBackEntry( qry, ADDRQUERY_ID(qry), listValues, qry->data );
}
ldapqry_free_list_name_value( listValues );
listValues = NULL;
pthread_mutex_unlock( qry->mutexEntry );
}
/* Free up and disconnect */
ldap_msgfree( result );
if( entriesFound ) {
ADDRQUERY_RETVAL(qry) = LDAPRC_SUCCESS;
}
else {
ADDRQUERY_RETVAL(qry) = LDAPRC_NOENTRIES;
}
return ADDRQUERY_RETVAL(qry);
}
/**
* Perform the search to locate a specific LDAP record identified by
* distinguished name (dn).
*
* \param qry Query object to process.
* \return Error/status code.
*/
gint ldapqry_perform_locate( LdapQuery *qry ) {
/* Connect */
qry->ldap = NULL;
ldapqry_connect( qry );
if( ADDRQUERY_RETVAL(qry) == LDAPRC_SUCCESS ) {
/* Perform search */
ldapqry_locate_retrieve( qry );
}
/* Disconnect */
ldapqry_disconnect( qry );
qry->ldap = NULL;
/* Process callback */
if( qry->callBackEnd ) {
qry->callBackEnd( qry, ADDRQUERY_ID(qry), ADDRQUERY_RETVAL(qry), qry->data );
}
return ADDRQUERY_RETVAL(qry);
}
/**
* Remove results (folder and data) for specified LDAP query.
* \param qry Query object to process.
* \return TRUE if folder deleted successfully.
*/
gboolean ldapquery_remove_results( LdapQuery *qry ) {
gboolean retVal = FALSE;
/* Set query as aged - will be retired on a later call */
ldapqry_set_aged_flag( qry, TRUE );
/*
printf( "ldapquery_remove_results...\n" );
printf( "testing busy flag...\n" );
*/
if( ldapqry_get_busy_flag( qry ) ) {
/* Query is still busy - cancel query */
/* printf( "\tquery is still busy running...\n" ); */
ldapqry_set_stop_flag( qry, TRUE );
/* ldapqry_cancel( qry ); */
}
else {
/* Delete folder */
/* printf( "\tquery can be deleted!\n" ); */
/* ldapqry_delete_folder( qry ); */
retVal = TRUE;
/* printf( "\tquery deleted!\n" ); */
}
return retVal;
}
#endif /* USE_LDAP */
/*
* End of Source.
*/